From 4d4449866b057a3145ac7e8561aa369222df2bcb Mon Sep 17 00:00:00 2001 From: nntrivi2001 Date: Tue, 8 Sep 2026 21:37:41 +0700 Subject: [PATCH] fix(codex): let plugin remove take the same spec plugin add and list use --- server/__tests__/codex-cli.test.js | 6 +++++- server/lib/codex-cli.js | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/server/__tests__/codex-cli.test.js b/server/__tests__/codex-cli.test.js index e0802ef8..37fb58f0 100644 --- a/server/__tests__/codex-cli.test.js +++ b/server/__tests__/codex-cli.test.js @@ -52,7 +52,8 @@ describe("codex-cli argument validation", () => { it("refuses a name that could be read as a flag or a path", async () => { await assert.rejects(() => cli.mcpRemove("--force"), /name/i); await assert.rejects(() => cli.mcpRemove("../../etc"), /name/i); - await assert.rejects(() => cli.pluginRemove("--force"), /name/i); + await assert.rejects(() => cli.pluginRemove("--force"), /spec/i); + await assert.rejects(() => cli.pluginRemove("../../etc"), /spec/i); await assert.rejects(() => cli.marketplaceRemove("--force"), /name/i); await assert.rejects(() => cli.mcpAdd({ name: "--force", command: "node" }), /name/i); }); @@ -178,6 +179,9 @@ describe("codex-cli argv construction", () => { assert.deepEqual(argvOf(await cli.pluginList()), ["plugin", "list", "--json"]); assert.deepEqual(argvOf(await cli.pluginAdd("demo@sg")), ["plugin", "add", "demo@sg"]); assert.deepEqual(argvOf(await cli.pluginRemove("demo")), ["plugin", "remove", "demo"]); + // `plugin list` reports marketplace-scoped plugins as `@`, + // and `plugin add` takes that form — so remove has to accept it back. + assert.deepEqual(argvOf(await cli.pluginRemove("demo@sg")), ["plugin", "remove", "demo@sg"]); } finally { restore(); } diff --git a/server/lib/codex-cli.js b/server/lib/codex-cli.js index f1750233..1f83223e 100644 --- a/server/lib/codex-cli.js +++ b/server/lib/codex-cli.js @@ -239,9 +239,12 @@ async function pluginAdd(spec) { return serialize(() => runCodex(["plugin", "add", s])); } -async function pluginRemove(name) { - const n = assertName(name); - return serialize(() => runCodex(["plugin", "remove", n])); +async function pluginRemove(spec) { + // A spec, not a bare name: `plugin add` accepts `@`, so + // `plugin remove` has to accept back what `plugin list` hands out. + // cc-plugin-cli.js validates every plugin verb the same way. + const s = assertPluginSpec(spec); + return serialize(() => runCodex(["plugin", "remove", s])); } // ── Marketplaces ───────────────────────────────────────────────────────