Update lifecycle for deepagents-code.
Handles version checking against PyPI (with caching), install-method detection, auto-upgrade execution, config-driven opt-in/out, notification throttling, and "what's new" tracking.
Most public entry points absorb errors and return sentinel values.
set_auto_update raises on write failures so callers can surface
actionable feedback.
PyPI JSON API endpoint for version checks.
PyPI JSON API endpoint for reading deepagents SDK release metadata.
The CLI only reads release-age metadata from this endpoint; it never performs SDK update checks.
User-Agent header sent with PyPI requests.
Path to the user's model configuration file (~/.deepagents/config.toml).
Directory for app-managed internal state (~/.deepagents/.state).
Holds files the app writes for its own bookkeeping — OAuth tokens, the
sessions database, version-check caches, input history. Kept separate from
top-level user-facing config and agent directories so listing/iterating
~/.deepagents doesn't conflate state with agents.
On-disk cache of the latest published dcode/SDK versions and SDK release times.
Populated by get_latest_version; reads short-circuit on the cached payload
when it is younger than CACHE_TTL. SDK upload timestamps are stored under
_SDK_RELEASE_TIMES_KEY.
Persistent flags for the update-notification UX.
Tracks which version the user has been notified about (notified_version,
notified_at) and the most recent version they've seen the splash for
(seen_version, seen_at). Read by should_notify_update and friends
to suppress repeat notifications across invocations. Auto-update opt-outs
live in config.toml, not here.
Maximum age in seconds before CACHE_FILE entries are considered stale.
A cached latest_version.json younger than this is reused without an HTTP
call to PyPI; older payloads trigger a fresh fetch. Set conservatively at
24h since release cadence is on the order of days, not minutes.
Minimum installed-version age before update notices call it out explicitly.
Generic upgrade hint used when install-method detection fails.
Callers that surface an upgrade command in user-facing text should prefer
upgrade_command(); this constant exists so those callers have something
to render when detection raises unexpectedly. The documented install path
is uv tool install (see scripts/install.sh), so the uv command is the
right display fallback. Uses uv tool install -U rather than uv tool upgrade for the same receipt-pin reason documented on _UPGRADE_COMMANDS:
showing a user the upgrade form would hand them a command that silently
stays on the old version for a pinned install. Execution paths still refuse
unrecognized installs instead of updating a separate environment.
Promoted public install command for Deep Agents Code.
Directory for persisted update command logs.
Delete update logs older than this many days.
Keep at most this many newest update logs.
Return whether installed package metadata is at least version.
Check for updates using only a fresh local cache entry.
This is the startup fast path: it never contacts PyPI. Stale, missing, corrupt, or unparsable cache data is treated as "no cached update answer" so callers can launch immediately and let a background update check refresh the cache later.
Fetch the latest deepagents-code version from PyPI, with caching.
Results are cached to CACHE_FILE to avoid repeated network calls.
The cache stores both the latest stable and pre-release versions so a
single PyPI request serves both code paths.
Return whether installing version needs uv pre-release resolution.
Return the cached ISO-8601 upload time for version, or None.
Only versions captured during a prior get_latest_version call are
available; unknown versions, or a None input, return None.
Return a human-readable age for version (e.g., 'released 3d ago').
Returns an empty string when the upload time is unknown (cache entry
lacks release_times for this version, or a None version) so callers
can concatenate unconditionally.
Return ", released Nd ago" for version, or "" when unknown.
The ", " separator is included so callers can splice the age into a
parenthetical unconditionally — if the age is unknown, the empty
string collapses cleanly into the surrounding text.
Return " (released Nd ago)" for version, or "" when unknown.
Return " (N days old)" for installed versions at least a week old.
Return the ISO-8601 upload time for deepagents SDK version.
Reads from CACHE_FILE under sdk_release_times, falling back to a
single PyPI fetch on cache miss and writing the result back so
subsequent calls stay local.
Return a human-readable age for SDK version (e.g., 'released 3d ago').
May trigger a single PyPI fetch on cache miss (3s timeout). Returns an empty string on any failure so callers can concatenate unconditionally.
Return ", released Nd ago" for SDK version, or "" when unknown.
The ", " separator is included so callers can splice the age into a
line unconditionally — if the age is unknown, the empty string
collapses cleanly into the surrounding text. May trigger a single
PyPI fetch on cache miss.
Return whether the user should be notified about version latest.
Throttles notifications to at most once per CACHE_TTL period for a
given version, preventing repeated banners every session.
Record that the user was notified about version latest.
Writes into the shared update state file so a subsequent
should_notify_update call can suppress duplicate banners.
Clear the "already notified" marker so the update modal re-opens next launch.
Removes both notified_at and notified_version from the shared
update state file.
Check whether a newer version of deepagents-code is available.
When the installed version is a pre-release (e.g. 0.0.35a1),
pre-release versions on PyPI are included in the comparison so alpha
testers are notified of newer alphas and the eventual stable release.
Stable installs only compare against stable PyPI releases unless
include_prereleases is explicitly set.
Detect how deepagents-code was installed.
Checks sys.prefix against known paths for uv and Homebrew.
Return the shell command to upgrade deepagents-code.
Falls back to the documented uv command for display-only guidance.
Return whether pre-release upgrades are supported for the install method.
Pre-release channel switching is only safe for uv tool installs, where
uv tool upgrade --prerelease allow re-resolves against the pre-release
feed. Other package managers can't be steered onto that channel, so callers
should refuse before promising an upgrade.
Return whether a dependency-only refresh is possible for the install.
A dependency refresh reinstalls the current deepagents-code version with
upgraded dependency resolution (uv tool install -U deepagents-code==<v>).
Only uv-managed installs can express that without crossing to a newer app
version, so callers should refuse before prompting or shelling out. This is
the single source of truth for both the gate in the TUI and the refusal in
perform_dependency_refresh.
Return the shadowing dcode entry point on the user's PATH, if any.
After a successful uv tool upgrade, the upgraded binary only takes effect
on the next launch if the user's PATH resolves to uv's tool bin dir for
dcode (and deepagents-code). A pre-uv install earlier on PATH will
silently win and report the old version, which looks like "the upgrade
didn't work" to the user.
This compares each supported console script against uv's tool bin dir. A mismatch means a different binary will run next launch for that entry point.
Caveat: a dcode symlink that lives in some unrelated bin dir but
points into the upgraded tool venv (e.g. a manually-created
convenience symlink) is reported as shadowing even though the next
launch would actually run the upgraded entry point. Comparing
directories rather than resolved targets is intentional — see the
inline note below for why — and this edge is rare enough that we
accept a benign false positive over a class of false negatives.
Best-effort detect_shadowed_dcode that never raises.
The shadow check only ever runs to decorate an already-successful upgrade,
so a defect in detection — or an unexpected error escaping the narrow
OSError guards inside detect_shadowed_dcode — must not turn a working
upgrade into a user-facing failure. Any unexpected exception is logged and
treated as "no shadow detected", matching the fail-open bias the detector
already applies internally.
Render a user-facing warning for a shadowed-dcode situation.
Shared by the /update slash command, the update-notification "Install
now" action, and the pre-launch auto-update path so the wording stays
consistent.
Return a session-scoped shell command to prefer the upgraded shim.
The command targets the shell that matches the current platform: PowerShell
on Windows (where _uv_tool_bin_dir can resolve %USERPROFILE%/.local/bin
and export/hash are not valid), and POSIX sh/bash/zsh elsewhere.
Remove old update logs while preserving the newest recent logs.
Return a new timestamped update log path and clean stale logs.
Attempt to upgrade deepagents-code using the detected install method.
Only tries the detected method — does not fall back to other package managers to avoid cross-environment contamination.
Refresh dependencies while keeping deepagents-code on this version.
Runs uv tool install -U deepagents-code==<current> instead of
uv tool upgrade deepagents-code, so compatible dependency releases can be
picked up without crossing to a newer app version. Only uv-managed installs
are supported; other install methods cannot safely express this operation.
Resolve a dependency refresh plan without mutating the tool environment.
uv tool install has no --dry-run, so this targets the running tool
environment with uv pip install --dry-run --python <sys.executable>. It
uses the same pinned deepagents-code requirement, installed extras, and
preserved --with packages as the real refresh command.
Parse package version changes from uv's environment-diff output.
uv reports environment changes as paired - pkg==old / + pkg==new
lines; this collapses them into one DependencyChange per package,
preserving first-seen order.
Render dependency changes as an aligned, human-readable block.
Return whether extra is safe to embed in package-extra syntax.
Return whether package is safe to embed in a --with install command.
Return the uv command that upgrades dcode while clearing stale pins.
Built specifically to avoid the uv tool upgrade receipt-pin trap: when
the tool was originally installed via uv tool install deepagents-code==X.Y.Z
— or when a prior dependency_refresh_command rewrote the receipt with a
version-pinned requirement — uv tool upgrade deepagents-code will only
re-resolve within that pin and silently keep the user on the same
version. Re-running uv tool install -U deepagents-code[<extras>] (no
version pin) rewrites the receipt's requirement to unpinned so the next
upgrade can actually move forward. Callers can still pass version when
the resolver must allow pre-release dependencies for a stable app target;
that prevents the root deepagents-code package from floating to a newer
app pre-release. Installed extras and --with packages are preserved to
mirror dependency_refresh_command.
Return the uv command that refreshes deps for the current dcode version.
Return the uv command that plans a dependency refresh without installing.
Return the shell command that adds a package to the dcode tool env.
The result is built for execution (via perform_install_package), not for
display — surfacing raw uv tool invocations to the user is intentionally
avoided. package is validated here against PEP 508 grammar and then
shlex.quote-d by the shared builder: the validation already blocks shell
metacharacters, so the quoting is defense in depth that keeps the command
safe even if the pattern is later loosened.
Delegates to _uv_tool_install_command (the same builder the extras path
uses), passing the new package as a --with requirement. That builder folds
already-installed extras into the deepagents-code[...] requirement, and
preserves the uv-managed Python interpreter, the receipt's existing --with
packages, and the installed pre-release channel. Without this, reinstalling
to add a second package would replace the tool with a plain deepagents-code
(dropping extras the user added through /install <extra>), rebuild with
only the newest --with package (dropping previously configured custom
providers), or silently downgrade a pre-release install to the latest stable.
Like the extras path (_install_extra_uv_tool_command), passes
reinstall=True so the upgrade rebuilds the tool environment cleanly; see
_uv_tool_install_command's reinstall parameter for why an in-place
upgrade is unsafe.
Return the install-script command that installs dcode extras.
Return the install-script command that adds extra to dcode.
The promoted install path is the install script (see scripts/install.sh).
This helper is display-only and avoids uv receipt introspection so
unsupported installs can surface method-specific guidance before any uv
receipt is read. Already-detected extras from distribution metadata are
included when available, so following the command does not drop them.
Return a manual recovery command for the current install method.
uv-managed installs can preserve the uv receipt's Python interpreter and
--with requirements, so their recovery command uses the same uv path as
the automatic installer. Unsupported methods keep the install-script command
and deliberately avoid reading uv receipts.
Return the canonical action hint for editable installs missing an extra.
Shared by every site that detects an editable install and points the user
at the correct uv tool install --editable invocation, so wording stays
consistent and the literal [<extra>] bracket fragment is centrally
defined (callers that print through Rich markup must still escape it).
Return the canonical action hint for editable installs needing a package.
Editable installs can't have packages added automatically, so this points
the user at adding it to their own development environment. Phrased without
a raw install command, since surfacing uv tool invocations to the user is
intentionally avoided.
Add extra to the installed dcode tool environment.
Runs uv tool install --reinstall -U 'deepagents-code[<extras>]',
preserving any extras that are already installed. Editable installs are
refused — the caller should rerun their uv tool install --editable command
with --with 'deepagents-code[<extra>]' added so the extra is resolved
against the editable source.
Add an arbitrary package to the installed dcode tool environment.
Runs uv tool install --reinstall -U 'deepagents-code[<extras>]' --with <package>, the escape hatch for a provider whose package is not a
deepagents-code extra (e.g. a custom or in-house class_path model).
Already-installed extras are preserved so the reinstall does not drop them.
Editable installs are refused
— the caller should rerun their uv tool install --editable command with
--with <package> added so it resolves against the editable source.
Return whether update checks are enabled.
Checks DEEPAGENTS_CODE_NO_UPDATE_CHECK env var and the [update].check key
in config.toml.
Defaults to enabled.
Return whether auto-update is enabled.
Opt-out via DEEPAGENTS_CODE_AUTO_UPDATE=0 env var or
[update].auto_update = false in config.toml.
Defaults to True.
Unrecognized env values (neither truthy nor falsy) are ignored with a warning and fall through to the config read below.
If config.toml exists but cannot be parsed, returns False (fail-closed):
a corrupt file may hold an explicit opt-out, so it is not treated as the
permissive default. A genuinely absent config falls through to True.
Always disabled for editable installs.
Persist the auto-update preference to config.toml.
Writes [update].auto_update so the setting survives across sessions.
Return whether the user explicitly chose an auto-update preference.
True when DEEPAGENTS_CODE_AUTO_UPDATE holds a recognized boolean or
[update].auto_update is present in config.toml. Distinguishes a
deliberate opt-in/out from the implicit opt-out default.
Return whether to show the one-time auto-update default migration notice.
True when no explicit env/config preference is set (so auto-update is on
only implicitly, via the opt-out default) and the notice has not been
acknowledged yet. This does not itself verify that auto-update is enabled;
callers must gate on is_auto_update_enabled first (e.g. an editable
install has no explicit preference but never auto-updates).
Record that the one-time auto-update default migration notice was shown.
Return the last version the user saw the "what's new" banner for.
Record that the user has seen the "what's new" banner for version.
Return True if this is the first launch on a newer version.
A different dcode entry point is winning on PATH than the one we upgraded.
Returned by detect_shadowed_dcode after a successful upgrade so the TUI can
warn the user that re-launching will pick up the wrong binary. The most
common cause is a pre-uv install (e.g. a leftover from a previous
pipx/pip-based install) earlier on PATH than the uv tool shims.
A frozen dataclass rather than a NamedTuple (unlike the sibling
DependencyChange) so __post_init__ can enforce the conflict invariant
the type's name promises: an instance only exists when there genuinely is
a shadow. The producer already guarantees this, so the check is defensive
against future direct construction, not a runtime gate on the hot path.
A single package version change parsed from uv's environment-diff output.
Emitted by both uv tool upgrade and uv tool install -U (the
dependency-refresh path), so the wording stays command-agnostic. old is
None for a newly added package and new is None for a removed one; both
are set for an in-place version bump. The (None, None) state is invalid —
see kind.
Raised when uv tool requested requirements cannot be preserved.