Public: anyone can read this; the crew can change it.
Claude Code installs the forge plugin from the forge's own repository with `/plugin marketplace add`, without cloning it #9949
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/9933-claude-code-installs-the-forge-plugin-fr → main 1 commit fork
4 files
changed +47
−2
.claude-plugin/marketplace.json added +12
| @@ -0,0 +1,12 @@ | ||
| 1 | { | |
| 2 | "name": "forge", | |
| 3 | "owner": { "name": "trevato" }, | |
| 4 | "plugins": [ | |
| 5 | { | |
| 6 | "name": "forge", | |
| 7 | "source": "./plugins/claude-code", | |
| 8 | "description": "Your forge inside Claude Code: its MCP server and a /forge command.", | |
| 9 | "version": "0.2.0" | |
| 10 | } | |
| 11 | ] | |
| 12 | } |
docs/MCP.md +4
| @@ -288,3 +288,7 @@ answers | ||
| 288 | 288 | entries: `forge` sends no Authorization header, so Claude Code signs in |
| 289 | 289 | through the OAuth flow (the default); `forge-token` sends `FORGE_TOKEN` as a |
| 290 | 290 | bearer, for scripts. |
| 291 | Install it without a clone: `/plugin marketplace add <FORGE_URL>/trevato/forge.git` | |
| 292 | (the clone URL as the forge prints it), then `/plugin install forge@forge` | |
| 293 | (the root `.claude-plugin/marketplace.json` names the marketplace `forge`). | |
| 294 | From a clone, `claude --plugin-dir plugins/claude-code` is the local way. |
plugins/claude-code/README.md +8 −2
| @@ -1,7 +1,13 @@ | ||
| 1 | 1 | # forge for Claude Code |
| 2 | 2 | |
| 3 | Set `FORGE_URL` (the forge's address, no trailing slash), then add this directory as a plugin | |
| 4 | (`claude --plugin-dir plugins/claude-code`). It registers the forge's MCP | |
| 3 | Set `FORGE_URL` (the forge's address, no trailing slash), then install it without cloning anything: | |
| 4 | ||
| 5 | ||
| 6 | /plugin marketplace add $FORGE_URL/trevato/forge.git | |
| 7 | /plugin install forge@forge | |
| 8 | ||
| 9 | ||
| 10 | (Write the clone URL as the forge prints it, `<FORGE_URL>/trevato/forge.git`; the marketplace is named `forge`.) From a clone, `claude --plugin-dir plugins/claude-code` loads it locally instead. It registers the forge's MCP | |
| 5 | 11 | server and a `/forge` command: `new`, `tell`, `status`, `decide`, `plan`, `preview`, |
| 6 | 12 | `pulls`, `runs`, `log`, `asked`. |
| 7 | 13 |
test/plugins.test.ts +23
| @@ -211,3 +211,26 @@ describe("the two plugin commands offer the same verbs", () => { | ||
| 211 | 211 | expect(verbDiff(missing, full)).toEqual({ onlyA: [], onlyB: ["plan"] }); |
| 212 | 212 | }); |
| 213 | 213 | }); |
| 214 | ||
| 215 | describe("the repository is a Claude Code marketplace", () => { | |
| 216 | test("marketplace.json lists forge, whose directory holds a plugin.json with the same name and version", () => { | |
| 217 | const market = JSON.parse(read(".claude-plugin/marketplace.json")); | |
| 218 | expect(market.name).toBe("forge"); | |
| 219 | expect(market.plugins).toHaveLength(1); | |
| 220 | const entry = market.plugins[0]; | |
| 221 | expect(entry.name).toBe("forge"); | |
| 222 | expect(entry.source).toBe("./plugins/claude-code"); | |
| 223 | const plugin = JSON.parse(read(`${entry.source.slice(2)}/.claude-plugin/plugin.json`)); | |
| 224 | expect(plugin.name).toBe(entry.name); | |
| 225 | expect(plugin.version).toBe(entry.version); | |
| 226 | }); | |
| 227 | ||
| 228 | test("both docs show the two commands and the install", () => { | |
| 229 | for (const p of ["plugins/claude-code/README.md", "docs/MCP.md"]) { | |
| 230 | const t = read(p); | |
| 231 | expect(t).toContain("/plugin marketplace add"); | |
| 232 | expect(t).toContain("/plugin install forge@forge"); | |
| 233 | expect(t).toContain("--plugin-dir plugins/claude-code"); | |
| 234 | } | |
| 235 | }); | |
| 236 | }); |