Skip to content

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

Description

@V3RON

Why

The CLI mixes three command shapes. daemon status|stop is noun then verb. ls, revoke, link, invoke are bare verbs. tools [selector] [name] and events [selector] are nouns whose positional argument doubles as a selector, so listing and acting share one word and the first positional is ambiguous (packages/appduct/src/cli/command-options.ts:128). The cost lands in three places:

  • Agents cannot guess a command from its neighbours. Knowing appduct tools says nothing about appduct ls or appduct invoke, so the shipped skill has to spell out every form.
  • New listings have nowhere natural to go. Let apps declare the events they post, and let agents list them #95 needs a flag, appduct events --list, because a bare appduct events ls would parse ls as a session selector. appduct events --since is likewise a mode switch on a streaming command.
  • Selector placement differs per command, so --help output is the only way to learn each one.

Expected outcome

Every command is appduct <noun> <verb> [selector] [args], with daemon as the model:

  • appduct sessions ls | revoke [selector] | link [...] replacing ls, revoke and link.
  • appduct tools ls [selector] [--full|--groups|--group|--filter|--limit|--offset] | describe [selector] <name> | call [selector] <name> --input ... [--timeout] replacing tools, tools <name> and invoke.
  • appduct events tail [selector] | since [selector] <cursor> replacing events and events --since. events ls is reserved for the catalog from Let apps declare the events they post, and let agents list them #95.
  • daemon, init, keygen, doctor and mcp keep their shape; the last four are one-verb nouns already.

This is a clean break, with no aliases: we are pre-1.0. A removed form fails with a usage error that names its replacement, so an agent working from an old prompt can correct itself in one step. --json data does not change, so scripts that parse output only need the command renamed. The shipped skill, the READMEs, website/, docs/ and the CLI's own hints all switch to the new forms.

Shape: routes/sessions/, routes/tools/ and routes/events/ become routers like routes/daemon/, one route per verb. tools describe and tools call take [selector] <name> through splitSelectorAndRequiredTarget, and splitOptionalSelectorAndTarget is deleted. cac matches only the first word and reads flag types from that command, so each noun command in create-cli.ts declares every option its verbs use. The keys in output.ts's renderSuccessData become the new paths. The root router's unknown-command error carries a map from each removed form to its replacement.

Constraints and non-goals

How we know it is done

  1. appduct sessions ls, sessions revoke [selector], sessions link [flags], tools ls [selector] [flags], tools describe [selector] <name>, tools call [selector] <name> --input '{}', events tail [selector] and events since [selector] <cursor> each produce the same stdout, exit code and --json data as the command they replace. SIGINT during tools call cancels the call as it does for invoke today, and events since prints the trailing cursor line.
  2. appduct ls, revoke, link and invoke exit 64 with a message naming the new form, e.g. Unknown command "ls"; use "appduct sessions ls".
  3. appduct sessions, appduct tools and appduct events with no verb, or with an unknown verb, exit 64 naming that noun's verbs, like appduct daemon does. With --help they print the verbs and exit 0.
  4. Until Let apps declare the events they post, and let agents list them #95 lands, appduct events ls exits 64 naming tail and since.
  5. A boolean flag placed before a positional does not swallow it: appduct tools ls --full <selector> and appduct sessions link --qr --open ios-sim parse as intended.
  6. The shipped skill, both READMEs, website/, docs/ (including ARCHITECTURE.md §10) and the CLI's own hints (output.ts, commands/init.ts) use only the new forms, and the CLI integration tests run against them.
  7. A Breaking changelog entry maps each old form to its new one.

Alternatives considered

  • Keep bare verbs and add flags per listing (events --list): works, but leaves three command shapes and the positional ambiguity.
  • Deprecated aliases for a release: rejected. We are pre-1.0, and aliases would need a fallback router, legacy routes and a notice that stays silent under --json, all for one release.
  • Verb-noun (appduct list tools): reads well but does not match daemon status, the one noun-verb command already shipped.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:clipackages/appduct CLI commands and outputstatus:readySpec and fix direction are clear; an agent can pick it uptype:featureSomething Appduct should do that it does not today

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions