diff --git a/src/index.ts b/src/index.ts index 7a24d5b..a1eac2b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,7 +22,7 @@ Commands doctor Fire a test payload through the hook to prove it is wired verify Exit non-zero if a decoy was read or a canary leaked clean Remove planted decoys and unregister the hook - hook Internal: read a PreToolUse payload on stdin and judge it + hook Internal: read a tool-call hook payload on stdin and judge it session-start Internal: report outstanding trips at the start of a session Options @@ -36,6 +36,28 @@ Options Docs: https://github.com/aiagentflow/canaryhook `; +/** Flags each command accepts beyond --json, -h and -v. */ +const FLAGS: Record = { + init: ["--commit", "--force"], + clean: ["--keep-alerts"], + status: [], + alerts: [], + doctor: [], + verify: [], +}; + +/** + * A mistyped flag would otherwise be ignored, so `init --froce` reports + * "already initialized" and `clean --keep-alert` deletes the record it meant + * to keep. The internal hook commands are left out: a hook must never fail. + */ +function unknownArg(command: string, args: string[]): string | null { + const allowed = FLAGS[command]; + if (allowed === undefined) return null; + const known = new Set([...allowed, "--json", "-h", "--help", "-v", "--version"]); + return args.find((arg) => (arg.startsWith("-") ? !known.has(arg) : arg !== command)) ?? null; +} + async function main(argv: string[]): Promise { const args = argv.slice(2); const has = (flag: string) => args.includes(flag); @@ -54,6 +76,12 @@ async function main(argv: string[]): Promise { return 0; } + const unknown = unknownArg(command, args); + if (unknown !== null) { + process.stderr.write(`Unknown option for ${command}: ${unknown}\n\n${HELP}`); + return 1; + } + switch (command) { case "hook": return runHook(); diff --git a/test/root.test.js b/test/root.test.js index f4ebe49..a2f726c 100644 --- a/test/root.test.js +++ b/test/root.test.js @@ -85,3 +85,12 @@ test("verify passes a fresh install with no trips", () => { const { root } = project(); assert.equal(run(["verify"], root).status, 0); }); + +test("rejects a mistyped flag instead of ignoring it", () => { + const { root } = project(); + const result = run(["clean", "--keep-alert"], root); + assert.equal(result.status, 1); + assert.match(result.stderr, /Unknown option for clean: --keep-alert/); + assert.equal(run(["status", "extra"], root).status, 1); + assert.equal(run(["status", "--json"], root).status, 0); +});