What happens
webcmd web fetch always works — it is intercepted on the client-owned fast path in src/main.ts:79 before adapter discovery and before the hosted-mode branch, so it runs on a fresh install with zero plugins and in hosted mode. But nothing in the CLI's own surface says it exists.
On current main (0.5.4 build), with the web plugin not installed:
$ webcmd --help # no `web` under Commands or Site adapters
$ webcmd list # no web fetch
$ webcmd list -f json # 0 entries with "site": "web"
$ webcmd --get-completions webcmd web
# (empty)
$ webcmd web fetch --help
ArgumentError: --url must be an http or https URL
at clientOptions (…/dist/src/fetch/command.js:34:15)
hosted-contract.json has web/fetch-browser but no web/fetch entry either.
Meanwhile the command is fully functional:
$ webcmd web fetch --url https://example.com
# Example Domain
…
Why this happens
There are two registration paths for the same command and only one of them feeds discovery:
src/main.ts:79-82 — the fast path. Hardcoded argv[0] === 'web' && argv[1] === 'fetch', hand-rolled argv parsing in clientOptions(). Always available. Never touches commander, so it contributes nothing to help, list, cli-manifest.json, or completions, and has no -h.
plugins/web/fetch.js — a one-line side-effect import (import '@agentrhq/webcmd/fetch/command') that registers webFetchCommand from src/fetch/command.ts:5 in the registry. This is what makes the command visible — but only for users who have installed the web plugin.
So availability and discoverability are decoupled: the command works for everyone, and is listed only for the subset who installed a plugin they did not need in order to use it.
Why it matters
web fetch is the first call skills/smart-search/SKILL.md mandates, and the skill ships in the npm package (files includes skills/**). An agent that follows the documented practice of reading live command metadata before executing — webcmd list -f json, webcmd <site> <command> -h, exactly what smart-search instructs elsewhere — will conclude the command does not exist, or will try -h and get a stack trace telling it --url is required.
The --help output also advertises Run 'webcmd list' for full command details, which is inaccurate for this command.
Related but distinct: #247 is the mirror-image asymmetry (fetch-browser is listed in the hosted contract but not installed by default). #246 covers the stack-trace formatting on the fast path — this issue is that -h is not a supported argument at all, not that its failure prints badly.
Suggested fix
Register the web site in the built-in program so web fetch appears in --help, list, cli-manifest.json, and completions without a plugin install, and so webcmd web fetch -h prints real help.
Keep the src/main.ts fast path as-is. The comment above it is explicit that hosted mode must not read ~/.webcmd/clis or local site memory just to decide what commands exist, and the fast path is what guarantees web fetch stays client-owned in hosted mode. The two paths should stay separate; only the discovery side needs adding.
Two follow-ons if this lands:
- Decide whether
web/fetch belongs in hosted-contract.json. It is client-owned, so it may warrant an explicit availability mode rather than a plain entry — npm run check:hosted-contract will need to agree either way.
- Once help is real,
clientOptions() and the cli({...}) arg spec in src/fetch/command.ts are two hand-maintained copies of the same flags (--url --timeout --max-chars --allow-private). Worth collapsing so help cannot drift from what the fast path actually parses.
What happens
webcmd web fetchalways works — it is intercepted on the client-owned fast path insrc/main.ts:79before adapter discovery and before the hosted-mode branch, so it runs on a fresh install with zero plugins and in hosted mode. But nothing in the CLI's own surface says it exists.On current
main(0.5.4 build), with thewebplugin not installed:hosted-contract.jsonhasweb/fetch-browserbut noweb/fetchentry either.Meanwhile the command is fully functional:
Why this happens
There are two registration paths for the same command and only one of them feeds discovery:
src/main.ts:79-82— the fast path. Hardcodedargv[0] === 'web' && argv[1] === 'fetch', hand-rolled argv parsing inclientOptions(). Always available. Never touches commander, so it contributes nothing to help,list,cli-manifest.json, or completions, and has no-h.plugins/web/fetch.js— a one-line side-effect import (import '@agentrhq/webcmd/fetch/command') that registerswebFetchCommandfromsrc/fetch/command.ts:5in the registry. This is what makes the command visible — but only for users who have installed thewebplugin.So availability and discoverability are decoupled: the command works for everyone, and is listed only for the subset who installed a plugin they did not need in order to use it.
Why it matters
web fetchis the first callskills/smart-search/SKILL.mdmandates, and the skill ships in the npm package (filesincludesskills/**). An agent that follows the documented practice of reading live command metadata before executing —webcmd list -f json,webcmd <site> <command> -h, exactly what smart-search instructs elsewhere — will conclude the command does not exist, or will try-hand get a stack trace telling it--urlis required.The
--helpoutput also advertisesRun 'webcmd list' for full command details, which is inaccurate for this command.Related but distinct: #247 is the mirror-image asymmetry (
fetch-browseris listed in the hosted contract but not installed by default). #246 covers the stack-trace formatting on the fast path — this issue is that-his not a supported argument at all, not that its failure prints badly.Suggested fix
Register the
website in the built-in program soweb fetchappears in--help,list,cli-manifest.json, and completions without a plugin install, and sowebcmd web fetch -hprints real help.Keep the
src/main.tsfast path as-is. The comment above it is explicit that hosted mode must not read~/.webcmd/clisor local site memory just to decide what commands exist, and the fast path is what guaranteesweb fetchstays client-owned in hosted mode. The two paths should stay separate; only the discovery side needs adding.Two follow-ons if this lands:
web/fetchbelongs inhosted-contract.json. It is client-owned, so it may warrant an explicit availability mode rather than a plain entry —npm run check:hosted-contractwill need to agree either way.clientOptions()and thecli({...})arg spec insrc/fetch/command.tsare two hand-maintained copies of the same flags (--url --timeout --max-chars --allow-private). Worth collapsing so help cannot drift from what the fast path actually parses.