fix: make client-owned web fetch discoverable (#252) - #263
Conversation
`web fetch` always worked via the main.ts fast path but was invisible to `--help`, `list`, `cli-manifest.json`, and completions unless the `web` plugin was installed, and `web fetch -h` threw instead of printing help. Register the command from clis/web/fetch.js via a makeWebFetchCommand() factory so build-manifest and filesystem discovery both see it, and keep execution on the fast path so hosted mode never cloud-routes it. The fast path now honours the flags its help advertises: -f/--format for output and structured --help, an error for unsupported formats, and an error for a flag-shaped --timeout/--max-chars value instead of coercing it to 1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🟠 Maintainer review suggested — low confidenceThe automated review could not reach a fully supported conclusion. Limitations
This review is advisory and does not block merging. |
Maintainer review: changes requestedIssue #252 is valid. Before this lands, the local/hosted ownership contract needs to be explicit. Blocking Cloud contract ambiguityThis PR adds At the same time, Today Cloud sees no root That creates two different ownership paths:
This contradicts the stated client-owned behavior in this PR and makes network-origin, timeout, billing, proxy, and SSRF semantics depend on how the command was reached. Required product decisionPlease choose and encode one of these contracts:
Do not rely on the current missing- The north-star currently emphasizes a thin hosted client and server-side hosted execution, while this PR emphasizes an intentional client-owned exception. Either can work, but the exception must be represented in metadata rather than inferred from Required testsPlease add integration-level assertions for:
The unit tests added here cover the parser/presentation helpers, but the bug concerns cross-surface discoverability and ownership, so at least one end-to-end surface test is warranted. Scope noteThis PR also expands output-format and validation behavior beyond the discoverability bug. Those changes are defensible if retained, but they should be tested as compatibility changes rather than assumed to follow automatically from Commander help. In particular, keep the hand-parsed fast path aligned with accepted common-option spellings. Please coordinate this with #271 and the Cloud loader change rather than releasing the manifest changes independently. |
web fetch is executed by the CLI on every path, including hosted mode. That was only true because of where main.ts intercepts it, so once Cloud reads the staged manifest the same grammar could also resolve to a server-side default with different network, timeout, billing and SSRF semantics. Carry the ownership in metadata: commands can declare clientOwned, it flows through cli() into the manifest, and deriveHostedAvailability marks such a command local-only with reason client-owned. The command stays in local help, list and completions; the hosted contract states it is never served. Adds e2e coverage for each surface (help, list, completions, help without --url, structured help, local execution in both modes) and the contract. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Agreed the ownership had to be encoded rather than inferred. Pushed Reasoning, so you can push back if the north-star says otherwise: the command exists precisely because it needs no browser and no server — a thin local fast path is the feature. Routing it to Cloud (option 2) would add a network hop and a billing/proxy/SSRF surface to the one command that deliberately has none. If you'd rather it become server-owned in hosted mode, say so and I'll redo it as option 2 instead; the metadata flag is the only thing that would change. The contract, in metadata
Nothing about the decision depends on the missing- TestsNew e2e file
Plus a unit assertion in
Scope noteFair point on the output-format/validation work — the hand-parsed fast path now rejects an unknown |
The e2e project only runs a named subset of browser files in CI, so the new assertions would never have executed there. The plugin project runs in full on every OS after a build, which is what a test driving the built binary needs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Follow-up |
Fixes #252.
The problem
webcmd web fetchalways works, but the CLI never tells you it exists.The command is intercepted on the client-owned fast path in
src/main.ts:79, before adapter discovery — so it runs on a fresh install with zero plugins. It just isn't listed anywhere, and asking for help throws:So the only way to discover a built-in command is to already know it.
What I changed
clis/web/fetch.js— registers the command through a newmakeWebFetchCommand()factory, so bothbuild-manifestand the runtime filesystem scan pick it up.src/discovery.ts— the module pattern now matchesmake<Pascal>Command(, the same conventionsrc/build-manifest.ts:48already used.cli-manifest.json— gains the generatedweb/fetchentry. It regenerates byte-identical,check:hosted-contractpasses, andhosted-contract.jsonis unchanged because this command is not browser-based.src/fetch/command.ts— now that help is real, the fast path honours what it advertises:-f/--formatfor output (markdown still the default), structured--help -f yaml|json, an error on an unsupported format instead of silently falling back to a table, and an error when--timeout/--max-charsis given a flag-shaped value instead of quietly coercing it to1.The fast path itself stays exactly where it was — execution never touches the registry, so hosted mode still never cloud-routes this command.
Proof
webcmd --helpwebwebunder Site adapterswebcmd listfetch [public] … [builtin]webcmd list -f jsonwebcmd web fetch -hArgumentErrorwebcmd --get-completions web ''fetch,fetch-browserVerified against a rebuilt
dist. Four new tests insrc/fetch/command.test.tscover the-foutput path, structured help, the format rejection and the--timeout -5rejection. Full suite passes excepttests/e2e/plugin-management.test.ts, which clones plugins over the network and fails identically onmain(fixed separately in #267).Not in this PR
ArgumentErrorstill prints a raw stack trace on the fast path.clis/missing frompackage.jsonfiles. Unreachable here while theargv[0] === 'web' && argv[1] === 'fetch'prefix holds, and pre-existing forfetch-browser. Fixed in fix: ship web fetch-browser so the blocked-fetch escalation works (#247) #271.🤖 Generated with Claude Code