Skip to content

feat(cli): unify the CLI as noun-verb commands (#96) - #111

Merged
V3RON merged 17 commits into
mainfrom
claude/pensive-sagan-hoi3m2
Sep 25, 2026
Merged

V3RON merged 17 commits into
mainfrom
claude/pensive-sagan-hoi3m2

Conversation

@V3RON

@V3RON V3RON commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Closes #96

What changed

Every CLI command is now appduct <noun> <verb> [selector] [args], the shape daemon run|start|stop|status already used:

  • sessions ls|revoke|link replaces ls, revoke and link.
  • tools ls|describe|call replaces tools, tools <name> and invoke.
  • events tail|since <cursor> replaces events and events --since.

This is a clean break with no aliases, since we are pre-1.0. A removed top-level word, or a missing or unrecognized verb, is a usage error that names the fix. Every place that documented or hinted at an old form now uses only the new ones: the shipped skill, the READMEs, the website, docs/, and the CLI's own output hints.

Acceptance criteria

# Criterion Test Tier
1 Each new verb produces the same stdout, exit code and --json as the command it replaces; SIGINT still cancels tools call; events since still prints the trailing cursor line cli-v2.integration.test.ts, events.integration.test.ts, exit-codes.integration.test.ts, e2e/invoke-cancel.e2e.test.ts integration, e2e
2 ls, revoke, link and invoke exit 64 naming their replacement noun-verb-cli.integration.test.ts ("removed top-level commands...") integration
3 A bare or unknown verb on sessions/tools/events exits 64 naming that noun's verbs, like daemon; --help on the noun itself names every verb (via .usage(...)), and so does the global --help noun-verb-cli.integration.test.ts ("a bare noun with no verb...") integration
4 events ls exits 64 naming tail and since until #95 lands noun-verb-cli.integration.test.ts ("events ls is reserved for #95...") integration
5 A boolean flag ahead of a positional (tools ls --full <selector>, sessions link --qr --open ios-sim) doesn't swallow it create-cli.test.ts unit
6 The shipped skill, the READMEs, website/, docs/ (including ARCHITECTURE.md §10) and the CLI's own hints (output.ts, commands/init.ts) use only the new forms; the CLI integration tests run against them cli.integration.test.ts, output.test.ts (hint text), and each listed doc checked by hand with pnpm check:links integration, docs
7 A Breaking changelog entry maps each old form to its new one CHANGELOG.md under ## Unreleased docs

E2E evidence

Not run; waived by the maintainer (decision on #96). The iOS row the e2e-device skill requires couldn't run in the cloud container, which has no Xcode, no simulator and no KVM.

The change is CLI routing, output hints and docs only. The RPC, the daemon and the app side are untouched. The CLI integration and e2e suites cover the new commands: 69 files in appduct, all green on the merged head.

Checklist

  • CHANGELOG.md has an entry under Unreleased
  • User-facing docs updated for every surface the change touches
  • No new import past a module's index.ts; no new direct node:* I/O outside an adapter
  • Simplification checklist from the architecture skill applied (see below)
  • docs/ARCHITECTURE.md updated (§10, plus the §9 MCP cross-references)

Simplification notes:

  • command-options.ts's splitOptionalSelectorAndTarget is deleted. It decided whether a single positional was a selector or a tool name with the selector omitted, and that can't happen once tools ls and tools describe are separate commands. commands/tools.ts's handleToolsCommand drops the branch that used to guess between them.
  • tools/shared-options.ts parses the listing flags for both tools ls and tools describe, so it has the two callers the "simplest thing" rule asks for.

Out of scope

sessions describe, --name on events tail/since (#94), and a working events ls (#95) are all non-goals in the issue. Nothing else came up in passing.

Status

Implement: done (7/7 green) Review: round 4, approve (both nits fixed) E2E: waived (no macOS host; maintainer decision on #96) Ready: yes

🤖 Generated with Claude Code

https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt

Adds red coverage for sessions/tools/events as noun-verb commands, updates
every existing CLI test (integration, e2e, snapshot, unit) to the new forms,
and adds two new suites: create-cli.test.ts (a boolean flag ahead of a
positional must not swallow it) and noun-verb-cli.integration.test.ts (a
removed top-level word names its replacement; a bare or unknown verb names
its noun's verbs; events ls stays reserved for #95).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt
Every command is now appduct <noun> <verb> [selector] [args], the shape
daemon run|start|stop|status already used:

- routes/sessions/, routes/tools/ and routes/events/ are routers exactly
  like routes/daemon/, one route module per verb (ls, revoke, link;
  ls, describe, call; tail, since).
- create-cli.ts's sessions/tools/events commands each declare every flag
  any of their verbs use, so cac's boolean/string flag table never lets a
  flag swallow a positional (tools ls --full <selector>).
- dispatch.ts's root router drops ls, revoke, link and invoke entirely (no
  aliases, pre-1.0); a removed word or any other unmatched command is a
  usage error, and REMOVED_COMMANDS additionally names the replacement for
  the four that used to exist.
- command-options.ts's splitOptionalSelectorAndTarget is deleted: the old
  ambiguity it resolved (is a single positional the selector, or the name
  with the selector omitted?) no longer exists once ls and describe are
  separate commands, so commands/tools.ts's handleToolsCommand drops the
  probing branch that used to guess between them.
- events since <cursor> takes the cursor as a required positional
  (splitSelectorAndRequiredTarget), not the removed --since flag.
- output.ts's renderSuccessData switches on the new command names, and its
  human hints (and commands/init.ts's) point at the new forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt
Every reference to a removed command (ls, revoke, link, tools <name>,
invoke, events --since) now reads as its noun-verb replacement: the
appduct package README, ARCHITECTURE.md/PROTOCOL.md/SECURITY.md/TOOLS.md,
the react-native/native/playground READMEs, the website's CLI/agents/
install/quick-start pages, and the shipped skill (skills/appduct/).

Adds the Unreleased changelog entry for the breaking rename.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt

@V3RON V3RON left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Request changes (GitHub won't let this account request changes on its own PR, so this is posted as a comment): 2 blockers, 2 should-fix. The spec is issue #96's rewritten body; the first ## Design comment is superseded.
Fix first: the website landing page still shows appduct invoke, which now exits 64.

Two findings are in files this PR doesn't touch, so they have no inline comment:

  • Blocker (criterion 6): website/src/pages/index.astro:21 (invokeCode = "appduct invoke seed_cart ...") and website/src/components/Hero.astro:65 (appduct invoke) still use the removed form. A visitor who copies the homepage example gets exit 64. Switch both to appduct tools call.
  • Should-fix: .claude/skills/e2e-device/SKILL.md lines 46, 47, 60, 75-81 and 108 run pnpm playground:appduct -- link|ls|invoke. The pending E2E stage for this PR follows that skill, so its pairing loop and every smoke step will exit 64. Update it to sessions link, sessions ls and tools call.

Generated by Claude Code

Comment thread packages/appduct/src/cli/create-cli.ts
Comment thread CHANGELOG.md Outdated
sessions/tools/events --help exited 0 without naming any verb: cac's
per-command help never prints a command's description, only usage and
flags. Red for that reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt
sessions/tools/events --help exited 0 but never named a verb, since
cac's per-command help shows usage and flags, never a command's
description. Each noun now sets a `.usage(...)` naming its verbs, so
`appduct events --help` names `since` and `appduct tools --help` names
`describe`, matching criterion 3 as the issue states it.

1 failing -> 0 failing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt
The landing page's terminal code sample and the hero diagram's caller
label still read "appduct invoke", which now exits 64.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt
The Unreleased entry said "tools" was gone, which was false — only its
bare and "tools <name>" forms went — and it didn't say what any old
form became. Rewritten as explicit pairs for ls, revoke, link, tools,
tools <name>, invoke, events and events --since <cursor>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt
e2e-device's smoke pass and connect step still used link, ls and
invoke, the forms the E2E stage on this PR would have run against.
writing-changelog's tool-groups example still showed the old `tools
--group` form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt

@V3RON V3RON left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Verdict: request-changes (round 2). 1 blocker, 0 should-fix.
Spec: issue #96 body. All four round-1 findings are fixed; I checked the <noun> --help output against the built CLI.
Fix first: the website playground page, playground/README.md and the playground app's on-screen hints still use playground:appduct -- link/ls/tools/invoke/events --follow, and every one of those now exits 64 (criterion 6).


Generated by Claude Code

Comment thread website/src/content/docs/start/playground.mdx
Round-2 review found the sweep missed `playground:appduct -- <verb>`
forms: website/start/playground.mdx, playground/README.md and the
playground app's on-screen hints still told readers to run `link`,
`ls`, `tools`, `invoke` and `events --follow`, each exiting 64.

Also fixed the same stale bare forms turned up by a wider grep in the
native playgrounds (Info.plist, PlaygroundTools.swift,
PlaygroundApplication.kt) and a comment in command-options.ts that
described `events --since` as a flag after it became the `<cursor>`
positional.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt

@V3RON V3RON left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Verdict: request changes. 2 blockers, 0 should-fix, 1 nit. The round-2 blocker is fixed.
Spec: issue #96 body; the first ## Design comment is superseded.
Fix first: the appduct events since cursor hint in output.ts:754 still says pass --since <cursor>, a removed flag.


Generated by Claude Code

Comment thread packages/appduct/src/output.ts
Comment thread packages/appduct/README.md
Comment thread packages/appduct/src/cli/command-options.ts Outdated
output.test.ts pinned the removed --since flag in the human-mode
cursor line; expect the noun-verb command instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt
The human-mode trailing cursor line named the removed --since flag.
It now names appduct events since <cursor>, which actually resumes.

1 failing -> 0 failing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt
)

README.md and ARCHITECTURE.md still named the removed `invoke` command
outside the earlier diff. Also swept source comments, test titles and
docblocks repo-wide for the same staleness: `invoke`, bare `ls`/`link`/
`revoke`, `tools <name>`/`tools [selector]`, and `--since` as an
example flag, replacing each with its noun-verb equivalent.

No behavior change; comments, test names, a stale snapshot key and
doc prose only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt

@V3RON V3RON left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Verdict: approve. 0 blockers, 0 should-fix, 2 nits. Ready for the device test. The spec is issue #96's body.
All three round-3 fixes hold. On the built CLI, events since now prints run "appduct events since N". A repo-wide search, including commands split across line breaks, finds one removed form left: appduct link at docs/ARCHITECTURE.md:578-579. Fix that first, before merge.


Generated by Claude Code

Comment thread packages/appduct/src/output.ts Outdated
Comment thread docs/ARCHITECTURE.md
1 failing -> 0 failing

Copying the hint after `appduct events since pixel-8 0` dropped the
device selector, so with two devices connected the suggested command
resolved to ambiguous_session instead of resuming.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt
The removed `appduct link` form survived the noun-verb sweep because
it wrapped across two lines, with "appduct" ending one line and
"link" starting the next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt
Resolves the .agents/memory/INBOX.md conflict by keeping main's notes
and appending this PR's note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wk7yigkPHWoWLy9nvocyt
@V3RON
V3RON marked this pull request as ready for review September 25, 2026 08:43
@V3RON
V3RON merged commit cfe4788 into main Sep 25, 2026
10 checks passed
@V3RON
V3RON deleted the claude/pensive-sagan-hoi3m2 branch September 25, 2026 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unify the CLI as noun-verb commands: sessions, tools, events

2 participants