Skip to content

feat(cli/dir): dirctl install list, agents, outdated, and pin/unpin - #2132

Merged
akijakya merged 2 commits into
mainfrom
feat/dirctl-install-subcommands
Sep 16, 2026
Merged

akijakya merged 2 commits into
mainfrom
feat/dirctl-install-subcommands

Conversation

@akijakya

@akijakya akijakya commented Sep 10, 2026

Copy link
Copy Markdown
Member

The read side of package management: see what is installed, and see what is stale.

Builds on #2134, now merged, which made the manifest the single source of truth.

install list and install agents

dirctl install list lists installed packages from the manifest — name, agent, kind, scope, version, flags — with -o json.

dirctl install list <name> mirrors brew list <formula>: that package's skill files and MCP server keys. It answers "what did this actually put on my machine?", otherwise unanswerable without reading every agent config by hand.

Both stat what they report, since the manifest is the only provenance an installed artifact has and a user can delete one behind our back. Anything a row names but disk does not have is marked missing.

dirctl install agents is the old install list under its new name — detected agents and the files install would touch.

install outdated

Compares each row against the Directory it was installed from.

Only rows needing attention are listed, with --all for the full table. Two groups qualify: upgradable, and rows that could not be assessed at all. The second is there because a silently omitted missing or not found row reads as "fine".

Status Meaning
upgradable A higher version exists, or the same version now resolves to different content (content changed)
up to date Nothing newer. Also covers an upstream that is behind — a downgrade is never offered
pinned Held. The newer version is still shown
missing The recorded artifacts are gone, so nothing can be upgraded
not found No record under that name
non-semver The versions carry no ordering, so no claim is made
skipped Installed from a different client context

--exit-code gates CI. It is computed on the unfiltered set, so --all cannot change it, and on upgradable alone, since no upgrade would fix a missing row. Also --pre and --include-pinned.

Its first four columns match install list, so the two tables read as views of one manifest.

install pin / unpin

Hold a package at its installed version, and release the hold, across every agent it is installed into.

They exist as verbs because install-time pinning alone cannot hold a version you already have: you upgraded, something broke, you want to sit still.

How the comparison works

cli/internal/pkgupdate is a pure unit. The resolver, the stat, the binary's own version, the active context and the row filters are all injected, so every status above is reachable in a unit test with no server and no filesystem.

It resolves once per distinct name, not once per row, through namingv1.Resolve(name, ""), which returns every {name, version, cid} without pulling record bodies. A package in three agents costs one call.

A resolver failure is a per-row status, so one unreachable name cannot hide the verdict on the others. builtin rows compare against skill.RecordVersion() and issue no RPC.

The manifest gains a context field, recorded on every install. A row pulled from a colleague's Directory says nothing about the one configured now, so it is skipped rather than compared. An empty name on either side means "no claim" and matches anything, so older rows and unnamed contexts stay checkable.

Everything here except outdated runs without a client.

Breaking change

dirctl install list shipped in v1.6.2 showing detected agents. It now lists installed packages, and that view is dirctl install agents. Docs and skill references are updated here, and the release notes carry a BREAKING entry.

Out of scope

install upgrade, manifest rows for dirctl init's built-in install, and bare-name resolution by highest version are #2030.

Closes #2029.

Validation

  • cd cli && go test ./... and task lint:go (pinned golangci-lint 2.13.2): pass, 0 issues. ./cmd/install/... ./internal/... also pass at -count=2, which catches the in-process opts singleton trap.
  • Two failures elsewhere in task testserver/store/oci integration and cli/cmd/auth at -count=2 — reproduce on unmodified main.
  • Every pkgupdate status has a unit test with an injected resolver and stat, including the once-per-name call count, the built-in path issuing no RPC, and the lexical trap that ranks 1.10.0 below 1.9.0.
  • Manual, against a local daemon with two versions of one record pushed: list, list <name>, -o json, agents, outdated --all, pin/unpin and uninstall all behave as described.

@akijakya
akijakya requested a review from a team as a code owner September 10, 2026 11:37
@github-actions github-actions Bot added the size/XL Denotes a PR that changes 2000+ lines label Sep 10, 2026
@akijakya akijakya added kind/feature Categorizes issue or PR as related to a new feature. area/cli/dir area/cli labels Sep 10, 2026
@codecov

codecov Bot commented Sep 10, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

No-op installs fail to reconcile the manifest, and recorded uninstall has selection, digest, and failure-handling correctness issues.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds manifest-driven package inspection, update checks, pinning, and offline global uninstall behavior to dirctl.

Changes:

  • Adds install agents, list, outdated, pin, and unpin.
  • Introduces package update comparison and artifact inspection.
  • Makes global uninstall manifest-driven and simplifies no-op reporting.
File summaries
File Description
docs/content/dir/dir-features-scenarios.md Updates install command guidance.
docs/content/dir/dir-cli-reference.md Documents package-management commands.
cli/util/reference/reference.go Simplifies resolver errors.
cli/internal/pkgupdate/pkgupdate.go Adds update comparison engine.
cli/internal/pkgupdate/pkgupdate_test.go Tests update statuses.
cli/internal/pkgstate/pkgstate.go Adds context and artifact-kind metadata.
cli/internal/pkgstate/pkgstate_test.go Tests new manifest fields.
cli/internal/agentinstall/remove.go Adds manifest-driven removal.
cli/internal/agentinstall/remove_test.go Tests recorded removal.
cli/internal/agentinstall/installed.go Inspects recorded artifacts.
cli/internal/agentinstall/installed_test.go Tests artifact inspection.
cli/internal/agentcfg/summary.go Omits unchanged summary lines.
cli/internal/agentcfg/summary_test.go Tests summary filtering.
cli/internal/agentcfg/selection.go Adds agent lookup by ID.
cli/internal/agentcfg/result.go Adds change detection helpers.
cli/internal/agentcfg/plan.go Omits unchanged plan lines.
cli/internal/agentcfg/plan_test.go Tests plan filtering.
cli/cmd/root.go Configures offline commands.
cli/cmd/install/uninstall.go Routes global uninstall through manifest.
cli/cmd/install/uninstall_recorded.go Implements recorded uninstall flow.
cli/cmd/install/uninstall_recorded_test.go Tests recorded uninstall behavior.
cli/cmd/install/pin.go Implements pin and unpin.
cli/cmd/install/pin_test.go Tests pin state changes.
cli/cmd/install/output.go Adds table and JSON output helpers.
cli/cmd/install/outdated.go Implements outdated reporting.
cli/cmd/install/outdated_test.go Tests outdated output.
cli/cmd/install/manifest.go Records context and adds manifest helpers.
cli/cmd/install/list.go Implements installed-package listing.
cli/cmd/install/list_test.go Tests list output.
cli/cmd/install/install.go Registers commands and skips no-op prompts.
cli/cmd/install/install_test.go Tests command registration.
cli/cmd/install/context.go Resolves active context names.
cli/cmd/install/batch.go Skips no-op batch confirmation.
cli/cmd/install/agents.go Preserves the former agent listing.
CHANGELOG.md Records features and breaking changes.
.skill/SKILL.md Updates connectivity guidance.
.skill/references/install.md Updates install workflows.
Review details

Suppressed comments (1)

cli/cmd/install/outdated.go:253

  • Raw string inequality does not mean the upstream is older. Equivalent versions such as installed v1.0.0 and upstream 1.0.0 compare equal in pkgupdate, but this renders the latter in parentheses as a downgrade. Use the semantic version comparison here and parenthesize only when the latest version is strictly lower.
	if row.Status == pkgupdate.StatusUpToDate && row.Latest != row.Entry.Version {
		return "(" + row.Latest + ")"
  • Files reviewed: 37/37 changed files
  • Comments generated: 9
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cli/cmd/install/batch.go Outdated
Comment thread cli/cmd/install/install.go Outdated
Comment thread cli/cmd/install/uninstall_recorded.go
Comment thread cli/cmd/install/uninstall_recorded.go
Comment thread cli/cmd/install/install.go Outdated
Comment thread cli/cmd/install/outdated.go
Comment thread cli/cmd/install/uninstall_recorded.go Outdated
Comment thread cli/cmd/install/uninstall_recorded.go Outdated
Comment thread .skill/SKILL.md Outdated
@akijakya
akijakya changed the base branch from main to refactor/dirctl-install September 10, 2026 16:16
@akijakya
akijakya force-pushed the feat/dirctl-install-subcommands branch from af1b784 to c175e18 Compare September 10, 2026 16:24
Comment thread cli/cmd/install/outdated.go
@akijakya
akijakya force-pushed the refactor/dirctl-install branch from fc883ad to d35eecc Compare September 11, 2026 14:32
@akijakya
akijakya force-pushed the feat/dirctl-install-subcommands branch 2 times, most recently from 52a62c2 to cd3cd1b Compare September 11, 2026 15:43
@akijakya
akijakya requested a balanced review from Copilot September 11, 2026 15:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Context provenance and edge-case status reporting can produce incorrect outdated results.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 21/21 changed files
  • Comments generated: 4
  • Review effort level: Balanced

Comment thread cli/cmd/install/context.go Outdated
Comment thread cli/internal/pkgupdate/pkgupdate.go
Comment thread cli/cmd/install/outdated.go Outdated
Comment thread docs/content/dir/dir-cli-reference.md Outdated
@akijakya
akijakya force-pushed the refactor/dirctl-install branch from 0d2c634 to b0cf409 Compare September 15, 2026 08:21
Base automatically changed from refactor/dirctl-install to main September 15, 2026 08:22
The read side of package management, on top of the manifest refactor.

`dirctl install list` lists installed packages from the manifest — name,
agent, kind, scope, version, flags — with `-o json`. `install list <name>`
mirrors `brew list <formula>`: that package's skill files and MCP server
keys, which answers "what did this actually put on my machine?", otherwise
unanswerable without reading agent config by hand. Both stat what they
report, since the manifest is the only provenance an installed artifact has.

`dirctl install agents` is the old `install list` under its new name, which
is a breaking change: `list` now means what it means in every other package
manager.

`dirctl install outdated [name...]` compares each row against the Directory
it was installed from. Only rows needing attention are listed, `--all` shows
the rest. Two groups qualify: upgradable, and rows that could not be assessed
at all — the second because a silently omitted `missing` or `not found` row
reads as "fine". `--exit-code` gates CI, on the unfiltered set so `--all`
cannot change it, and on upgradable alone since no upgrade fixes a missing
row. Also `--pre` and `--include-pinned`.

`cli/internal/pkgupdate` is the comparison engine and a pure unit: resolver,
stat, the binary's own version, the active context and the row filters are
all injected, so every status is reachable in a unit test with no server and
no filesystem. It resolves once per distinct name, not once per row. A
resolver failure is a per-row status, so one unreachable name cannot hide the
verdict on the others. `builtin` rows compare against skill.RecordVersion()
and issue no RPC.

The manifest gains a `context` field, recorded on every install: a row pulled
from a colleague's Directory says nothing about the one configured now, so it
is `skipped` rather than compared. An empty name on either side means "no
claim" and matches anything, so older rows and unnamed contexts stay
checkable.

`pin` and `unpin` hold a package at its installed version and release the
hold, across every agent it is installed into. Install-time pinning alone
cannot hold a version you already have.

Signed-off-by: András Jáky <ajaky@cisco.com>
…tory

Eight review findings from Copilot.

**A no-op install skipped the manifest.** Both `runApplyCmd` and the piped
path returned as soon as the plan moved nothing on disk, before recording.
That defeats the release note telling users to re-install pre-v1.8 packages
to get a row, and it kept `--pin` from taking hold without a version change.
The plan is now recorded and only the prompt is skipped.

**`--agents` could split a shared skill folder.** Claude Code and Claude
Desktop share one folder, so removing it for one took the artifact from the
other and stranded its row. The manifest can see every claimant, which the
old record-derived path could not, so the selection is rejected with the
agents that have to go together.

**Named digests were not verified.** `uninstall name@digest-A` could remove
a row recording digest-B. Every part the reference carries now has to agree.

**A failed removal read as "already gone".** `!HasChanges` is also false for
skipped and failed, so an unreadable config printed "already gone" and
"Cleared 1 row" while the row survived and the error was never shown. It now
needs every outcome to be `ActionUnchanged`.

**`forgetRemoved` swallowed save failures.** Dropping the row is what
uninstall was asked to do, so it uses `editManifest` and propagates.

**The recorded context could name the wrong Directory.** `--server-addr` and
`DIRECTORY_CLIENT_SERVER_ADDRESS` replace a context's endpoint while leaving
its name in place, so an install against an overridden endpoint was recorded
under a context it never used. `Entry.Context` becomes `Entry.Directory` and
holds the effective server address, which also makes two contexts pointing at
one Directory compare equal.

**An unorderable version could hide.** Installed `main` with only prerelease
upstreams returned `up to date` before the installed version was validated,
so the row vanished from the default view. It is checked first.

**Two reporting bugs.** `outdated` claimed "All packages are up to date" when
the only rows were pinned ones holding a newer version back, and it dropped
the `only prereleases published` hint because the row was assessed. Also
`install list` documented its columns in the pre-rename order.

Signed-off-by: András Jáky <ajaky@cisco.com>
@akijakya
akijakya force-pushed the feat/dirctl-install-subcommands branch from 233c547 to de2615f Compare September 15, 2026 08:40
@akijakya
akijakya requested a review from ramizpolic September 15, 2026 09:22

@ramizpolic ramizpolic left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, great work!

i would suggest to consider pulling these common methods into a dedicated package (at least the list locally installed operation). we could potentially leverage it inside the Runtime component

@akijakya
akijakya merged commit c1c6f48 into main Sep 16, 2026
47 of 56 checks passed
@akijakya
akijakya deleted the feat/dirctl-install-subcommands branch September 16, 2026 08:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/cli/dir area/cli kind/feature Categorizes issue or PR as related to a new feature. size/XL Denotes a PR that changes 2000+ lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dirctl install list, agents, outdated, and pin/unpin

3 participants