diff --git a/bin/moshcode.mjs b/bin/moshcode.mjs index e1732c4..a09505b 100755 --- a/bin/moshcode.mjs +++ b/bin/moshcode.mjs @@ -265,10 +265,12 @@ async function main() { return; } if (cmd === "mcp") { - return mcpCommand(rest); + process.exitCode = (await mcpCommand(rest)) || 0; + return; } if (cmd === "skill" || cmd === "skills") { - return skillCommand(rest); + process.exitCode = (await skillCommand(rest)) || 0; + return; } if (cmd === "install") { const target = rest.find((a) => !a.startsWith("-"))?.toLowerCase(); diff --git a/src/integrations.mjs b/src/integrations.mjs index ad10a7f..186b16b 100644 --- a/src/integrations.mjs +++ b/src/integrations.mjs @@ -155,9 +155,9 @@ function summarize(results) { /** Run `/mcp …`. `tokens` are the words after `mcp`. */ export async function mcpCommand(tokens) { const parsed = parseMcp(tokens); - if (parsed.list) { printMcpTargets(); return; } - if (parsed.showCatalog) { printMcpCatalog(); return; } - if (parsed.error) { console.log(err(parsed.error)); return; } + if (parsed.list) { printMcpTargets(); return 0; } + if (parsed.showCatalog) { printMcpCatalog(); return 0; } + if (parsed.error) { console.log(err(parsed.error)); return 1; } const { spec } = parsed; console.log(info(`registering ${bone(spec.name)} → ${ash(spec.target)} across MCP engines…`)); @@ -174,15 +174,16 @@ export async function mcpCommand(tokens) { if (spec.headers.length || /^https?:/i.test(spec.target)) { console.log(ash(" note: OAuth/HTTP servers may still need per-engine auth (e.g. `opencode mcp auth`, `codex mcp login`).")); } + return 0; } /** Run `/skill …`. `tokens` are the words after `skill`. */ export async function skillCommand(tokens) { const verb = tokens[0]; - if (!verb || verb === "list") { printSkillTargets(); return; } + if (!verb || verb === "list") { printSkillTargets(); return 0; } if (verb !== "install") { console.log(err(`unknown skill verb "${verb}" — try ${SKILL_VERBS.map(({ name }) => name).join(" or ")}`)); - return; + return 1; } const rest = tokens.slice(1); @@ -190,16 +191,17 @@ export async function skillCommand(tokens) { for (let i = 0; i < rest.length; i++) { if (rest[i] === "--name") { const next = flagValue(rest, i, rest[i]); - if (next.error) { console.log(err(next.error)); return; } + if (next.error) { console.log(err(next.error)); return 1; } name = next.value; i++; } else if (!source) source = rest[i]; } - if (!source) { console.log(err("usage: /skill install [--name ]")); return; } + if (!source) { console.log(err("usage: /skill install [--name ]")); return 1; } const spec = { source, name: skillName(source, name) }; console.log(info(`installing skill ${bone(spec.name)} → ${ash(source)} across skills engines…`)); const results = await runSkillInstall(planSkillInstall(spec)); summarize(results); + return 0; } diff --git a/test/skill-command.test.mjs b/test/skill-command.test.mjs index 6fb702f..8bc1907 100644 --- a/test/skill-command.test.mjs +++ b/test/skill-command.test.mjs @@ -12,9 +12,33 @@ for (const extraArgs of [["--name"], ["--name", "--bogus"]]) { encoding: "utf8", }); - assert.equal(result.status, 0); + assert.equal(result.status, 1); assert.equal(result.stderr, ""); assert.match(result.stdout, /--name requires a value/); assert.doesNotMatch(result.stdout, /installing skill/); }); } + +for (const { args, message } of [ + { args: ["skill", "install"], message: /usage: \/skill install/ }, + { args: ["skill", "bogus"], message: /unknown skill verb/ }, + { args: ["mcp", "install"], message: /needs an explicit --name/ }, + { args: ["mcp", "bogus"], message: /unknown mcp verb/ }, +]) { + test(`${args.join(" ")} exits non-zero`, () => { + const result = spawnSync(process.execPath, [BIN, ...args], { encoding: "utf8" }); + + assert.equal(result.status, 1); + assert.equal(result.stderr, ""); + assert.match(result.stdout, message); + }); +} + +for (const args of [["skill", "list"], ["mcp", "list"], ["mcp", "catalog"]]) { + test(`${args.join(" ")} still exits successfully`, () => { + const result = spawnSync(process.execPath, [BIN, ...args], { encoding: "utf8" }); + + assert.equal(result.status, 0); + assert.equal(result.stderr, ""); + }); +}