Skip to content

fix(cli): suggest the real command instead of a plugin that does not exist - #421

Open
ankitranjan7 wants to merge 1 commit into
mainfrom
fix/cli-command-suggestions
Open

fix(cli): suggest the real command instead of a plugin that does not exist#421
ankitranjan7 wants to merge 1 commit into
mainfrom
fix/cli-command-suggestions

Conversation

@ankitranjan7

Copy link
Copy Markdown
Contributor

webcmd adapters told you to install a plugin named "adapters". There is no such plugin, and there never will be — the command is webcmd adapter, one character away.

The problem

  1. rootCommandSuggestion() was a hardcoded 9-entry map. Anything not literally in it fell through to missingPluginGuidance(), which routes the caller into a plugin hunt that cannot succeed. adaptersadapter is edit distance 1 and was missed. fetchweb fetch was missed.
  2. The root command:* handler called program.outputHelp(), which writes to stdout. With --json in argv that is a well-formed JSON document on stdout beside an error on stderr — anything parsing stdout reads it as success. Hosted mode did the same.
  3. Unknown subcommands inside a namespace got Commander's bare unknown command 'x': no suggestion, no list of what is valid.

What changed

  1. New src/command-suggest.ts: ~15-line Levenshtein (no dependency) matched against everything registered on the program — root command names, aliases, subcommand leaves (so fetch finds web fetch), installed site adapters, external CLIs. Distance ≤ 2 for tokens of 8+ chars, ≤ 1 below that; up to 3 candidates.
  2. The hardcoded map stays as high-priority intent overrides (marketplaceplugin search, adapter listadapter status) and runs before the distance engine; missingPluginGuidance is now the last resort, not the first.
  3. Every built-in namespace (adapter, plugin, session, profile, daemon, external, browser, site, auth, skills) gets one shared command:* handler that prints the suggestion plus the namespace's valid subcommands. The one-off browser.on('command:*') handler and its retired-fork map moved into the shared path. Site adapter groups keep Commander's own suggestion so hosted mode stays byte-compatible.
  4. No help on stdout on any error path, in local and hosted mode (src/hosted/runner.ts dropped the root-help stdout payload from three throws).
  5. Before claiming "Site X is not installed", check ~/.webcmd/plugins/<X> and ~/.webcmd/clis/<X>. If the directory is there, say the adapter failed to load and point at WEBCMD_VERBOSE=1 instead of sending the user shopping.

Before / After

# before
$ webcmd adapters --json 1>/tmp/out 2>/tmp/err ; echo $?
2
$ wc -c /tmp/out
2410 /tmp/out          <- entire root help, valid JSON, on stdout
$ cat /tmp/err
Site "adapters" is not installed.
Search: webcmd plugin search adapters
Install using the installSource returned by search.

$ webcmd fetch --url https://x.com
Site "fetch" is not installed.
Search: webcmd plugin search fetch
Install using the installSource returned by search.
Usage: webcmd [options] [command]
...

$ webcmd adapter list rest
error: unknown command 'list'
ok: false
error:
  code: UNKNOWN
  message: 'error: unknown command ''list'''
  exitCode: 1
# after
$ webcmd adapters --json 1>/tmp/out 2>/tmp/err ; echo $?
2
$ wc -c /tmp/out
0 /tmp/out
$ cat /tmp/err
Unknown command "adapters".
Did you mean: webcmd adapter

$ webcmd fetch --url https://x.com
Unknown command "fetch".
Did you mean: webcmd web fetch

$ webcmd adapter list rest
error: unknown command 'list'
Did you mean: webcmd adapter status
Valid webcmd adapter commands: override, path, reset, source, status

$ webcmd zzzqqqwww
Site "zzzqqqwww" is not installed.
Search: webcmd plugin search zzzqqqwww
Install using the installSource returned by search.

Tests

src/command-suggest.test.ts (11 cases): adaptersadapter; fetchweb fetch; marketplace override wins over distance; adapter listadapter status plus the valid-subcommand list; retired browser fork names its replacement; an installed-but-unregistered directory reports a load failure, not a missing plugin; a genuinely unknown token still gets plugin-search guidance; and both error paths write zero bytes to stdout while exiting 2.

Updated existing expectations in src/cli.test.ts, src/hosted/runner.test.ts, src/hosted/root-command-surface.test.ts, and src/hosted/main-lifecycle.test.ts that asserted root help lands on stdout during an error.

npm run typecheck clean. npx vitest run --project unit: 2712 passed, 2 failed — both in src/browser/run/playwright-client-build.test.ts, both failing identically on origin/main in this worktree (the sandbox build script cannot resolve node_modules/playwright-core here). Unrelated to this change.

🤖 Generated with Claude Code

…exist

A mistyped token fell through a hardcoded 9-entry map straight into
missingPluginGuidance, sending the caller to search for a plugin that
cannot exist ("webcmd adapters" -> "webcmd plugin search adapters").
The root command:* handler also dumped the whole root help to stdout,
so "webcmd adapters --json" wrote a well-formed JSON document to stdout
next to an error on stderr.

Add a Levenshtein suggestion engine over everything registered on the
program (root names, aliases, subcommand leaves, site adapters, external
CLIs), keep the hardcoded map as high-priority intent overrides, and give
every built-in namespace the same treatment plus a list of its valid
subcommands. Error output goes to stderr only, in hosted mode too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🟠 Maintainer review suggested — low confidence

The automated review could not reach a fully supported conclusion.

Limitations

  • The automated review returned an invalid structured result.

This review is advisory and does not block merging.

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.

1 participant