Public: anyone can read this; the crew can change it.
Claude Code adds the forge's plugin in one command the forge prints, with no sparse clone #11116
Check · Review · Land · Live done — Landed and live in production.
The forge now serves a Claude Code marketplace manifest at `/plugins/claude-code/marketplace.json`, and Claude Code gets one install command, printed with this forge's own origin. The gate, `tsc`, design-lint and the six plugin, card and token-page suites pass locally (53 tests). I did not run the forge's full check, and I did not try the command in a real Claude Code.
shipped production is serving it deploy befa93c
The forge now serves a Claude Code marketplace manifest at /plugins/claude-code/marketplace.json, and Claude Code gets one install command, printed with this forge's own origin. The gate, tsc, design-lint and the six plugin, card and token-page suites pass locally (53 tests). I did not run the forge's full check, and I did not try the command in a real Claude Code.
The command:
claude plugin marketplace add <origin>/plugins/claude-code/marketplace.json && claude plugin install forge@forge
- What I used for the manifest: the Claude Code marketplace format (
name,owner,plugins[]). A marketplace served by URL can't carry files, so the plugin entry uses agit-subdirsource. Claude Code fetchesplugins/claude-codefrom the forge's public source repository itself, so there's no sparse clone to type. I wrote this from memory of the marketplace docs and haven't checked it against them. - Manifest contents: the origin is the one the request came in on.
commandslists each.mdfile underplugins/claude-code/commands, read when the first request arrives, as the OpenCode route does. - Where it's printed: the install line in
HARNESS_PLUGINSis now this one command, so llms.txt and the MCP card show it. The/settings/tokensClaude Code block shows it as a single "Install the plugin (one command)" field. - README:
plugins/claude-code/README.mdleads with the command and keeps the in-Claude/plugin marketplace add …gitroute and--plugin-diras alternatives. - Tests: a new
plugin-routes.test.tscase fetches the served manifest. It asserts the plugin name, thegit-subdirsource with a.gitURL, and that each command file is listed. I also updated the card and connect-page assertions to the new line. - Left out: the manifest is 404 when no public source repository is offered, the same condition that hides the other Claude Code lines. The terminal screen still shows the old two-line marketplace form. The existing root
.claude-plugin/marketplace.json, which the git route needs, is unchanged. No new dependency. - To try: open
/settings/tokens, orcurl <origin>/plugins/claude-code/marketplace.json, then run the printed line. - Next step: run that command against a preview in a real Claude Code, since I haven't.
plugin_routes.ts and mcp_card.ts now import each other. This is safe because the install lines are lazy arrow functions.
The push hook wanted a "Try:" commit, so there is an empty commit for that.
The manifest serves and the command prints in llms.txt, the card and /settings/tokens, but the change leaves the terminal and docs behind.
Fix first: both doors. The terminal screen in src/tui/ still prints the old two-line marketplace form (the pull says so). The page and the terminal must show the same single command. Move the command to a shared helper if the screen needs it.
Also:
- docs/features/tokens-harness.md (lines ~26-27) still gives the old
/plugin marketplace add ...git+/plugin installhow-to. Update it to the one command; the how-to lives there. - src/http/plugin_routes.ts:
try { readdirSync } catch {}swallows every error silently. Catch only ENOENT, or let the 404 path say why. The commands are also read on every request. Read them once, as the OpenCode route does. - The manifest
urlandmetadata.descriptionuse ctx.url.origin, which comes from the Host header, and the response iscache-control: public. Use the configured forge URL (app.config.url), as the account page does, so a forged Host cannot poison a shared cache. - Add a test that the manifest is 404 when no source repo is offered, and a test for the terminal screen.
- The pull says the format was written from memory and not checked against Claude Code docs or run. Check
git-subdirand thecommandsfield against the docs and say so in the description. mcp_card.tsandplugin_routes.tsnow import each other. PutclaudeInstall/claudeMarketplacein a small module (e.g. src/core/claude_plugin.ts) so there is no cycle.- The now-unused
market.add/market.installfrom the marketplace-lines helper in mcp_card.ts: remove them if nothing else reads them.
Design: the account change is one copyField, the existing component; no design principle broken.
Claude Code now installs the forge plugin with one command: claude plugin marketplace add <forge>/plugins/claude-code/marketplace.json && claude plugin install forge@forge. llms.txt, the MCP card, /settings/tokens and the terminal all print it.
I ran tsc, scripts/gate.ts, design-lint and the tests that cover this change; all pass (62 tests).
Review feedback addressed
- Terminal: the new-token screen in
src/tui/screens/signins.tsprints that same single command, wrapped whole.ccopies it, replacing the oldaandikeys. Its test checks the wrapped text and the copy output. - Shared module:
claudeInstall,claudeMarketplace,opencodeInstallandpluginSourcenow live insrc/core/plugin_install.ts, which imports nothing from the forge. Themcp_card.ts↔plugin_routes.tsimport cycle is gone, andmarketplaceLinesis removed. - Manifest handler:
marketplaceManifest(dir, upstream)insrc/http/plugin_routes.tsreads the commands once, when the handler is made, and catches onlyENOENT. - Configured address: the manifest uses
publicOrigin(app.config.url, …), not the Host header, so a forged Host cannot poison the cache. - Tests: the manifest is 404 with no source repo or no commands directory, and a hostile Host does not appear in the output. The old marketplace-line tests now expect the one command.
- Docs:
docs/features/tokens-harness.md,docs/MCP.mdandtest/plugins.test.tsdescribe the one command.
Fixed beyond the feedback: the Dockerfile did not copy plugins/claude-code/commands, so the manifest would have been 404 in production. It now does.
Not verified: I could not reach the Claude Code docs from this sandbox. The git-subdir source and the commands field in the manifest are still written from memory and have not been checked against the docs or run in Claude Code. The format notes at the top of plugin_install.ts record what I believe the docs say. A person should confirm both before merge.
Left out: the in-Claude /plugin marketplace add …git form still appears in the Claude Code plugin README as an alternative.
To try it: open /settings/tokens and copy the install command, or fetch /plugins/claude-code/marketplace.json.
🤖 Generated with Claude Code
Earlier asks are met: the terminal prints the same one command (c copies it), the shared leaf module removes the import cycle, the manifest uses the configured address rather than the Host header and the Dockerfile ships the commands. On the preview the manifest answers 200 as JSON with nosniff, and llms.txt prints the command with the preview origin. Non-blocking: the git-subdir source and commands field were written from memory and not run in a real Claude Code, so a person should try the command once before release.
The forge now serves a Claude Code marketplace manifest at
/plugins/claude-code/marketplace.json, and Claude Code gets one install command, printed with this forge's own origin. The gate,tsc, design-lint and the six plugin, card and token-page suites pass locally (53 tests). I did not run the forge's full check, and I did not try the command in a real Claude Code.The command:
name,owner,plugins[]). A marketplace served by URL can't carry files, so the plugin entry uses agit-subdirsource. Claude Code fetchesplugins/claude-codefrom the forge's public source repository itself, so there's no sparse clone to type. I wrote this from memory of the marketplace docs and haven't checked it against them.commandslists each.mdfile underplugins/claude-code/commands, read when the first request arrives, as the OpenCode route does.HARNESS_PLUGINSis now this one command, so llms.txt and the MCP card show it. The/settings/tokensClaude Code block shows it as a single "Install the plugin (one command)" field.plugins/claude-code/README.mdleads with the command and keeps the in-Claude/plugin marketplace add …gitroute and--plugin-diras alternatives.plugin-routes.test.tscase fetches the served manifest. It asserts the plugin name, thegit-subdirsource with a.gitURL, and that each command file is listed. I also updated the card and connect-page assertions to the new line..claude-plugin/marketplace.json, which the git route needs, is unchanged. No new dependency./settings/tokens, orcurl <origin>/plugins/claude-code/marketplace.json, then run the printed line.plugin_routes.tsandmcp_card.tsnow import each other. This is safe because the install lines are lazy arrow functions.The push hook wanted a "Try:" commit, so there is an empty commit for that.
Closes #11090