diff --git a/changelog.d/fixed-278.md b/changelog.d/fixed-278.md new file mode 100644 index 0000000..2105280 --- /dev/null +++ b/changelog.d/fixed-278.md @@ -0,0 +1 @@ +- Bare value-taking flags (e.g. `pluk send s --text hello`, `--run-dir /tmp/x`, `--filter x`) are now rejected with exit code 2 and a `--flag=` hint instead of being parsed as the string `true` (which typed `true` into the agent pane); fixes #278. diff --git a/src/cli.ts b/src/cli.ts index 0e1ad75..81a7710 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -58,6 +58,11 @@ ${ANSI_CYAN}Examples:${ANSI_RESET} `); } +const VALUE_FLAGS = new Set([ + 'text', 'session', 'run-dir', 'cli', 'command', 'cli-args', 'rebuttal', + 'dir', 'rationguard-bin', 'patterns-dir', 'pane', 'filter', +]); + function parseArgs(args: string[]): { positional: string[]; flags: Record } { const positional: string[] = []; const flags: Record = {}; @@ -68,7 +73,12 @@ function parseArgs(args: string[]): { positional: string[]; flags: Record 0) { flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1); } else { - flags[arg.slice(2)] = 'true'; + const name = arg.slice(2); + if (VALUE_FLAGS.has(name)) { + console.error(`${paintStderr(ANSI_RED, 'Error:')} --${name} requires a value: use --${name}=`); + process.exit(2); + } + flags[name] = 'true'; } } else { positional.push(arg); diff --git a/test/cli-error-paths.test.js b/test/cli-error-paths.test.js index dafc542..1318065 100644 --- a/test/cli-error-paths.test.js +++ b/test/cli-error-paths.test.js @@ -311,3 +311,24 @@ test('patterns --cli=unknown warns on stderr and lists available CLIs', () => { assert.match(r.stderr, /no pattern set for nonesuch; available CLIs: .*omp/); assert.match(r.stdout, /Patterns for nonesuch:/); }); + +test('bare value-taking flags exit 2 with a --flag= hint', () => { + for (const [args, flag] of [ + [['send', 's', '--text', 'hello'], 'text'], + [['attach', 's', '--run-dir', '/x'], 'run-dir'], + [['subscribe', 's', '--filter', 'rate_limit'], 'filter'], + ]) { + const r = spawnSync(process.execPath, [CLI, ...args], { encoding: 'utf8' }); + assert.equal(r.status, 2, args.join(' ')); + assert.match(r.stderr, new RegExp(`--${flag} requires a value: use --${flag}=`)); + } +}); + +test('bare boolean switches are still accepted', () => { + const r = spawnSync(process.execPath, [CLI, 'sessions', '--json'], { + encoding: 'utf8', + env: { ...process.env, PATH: `${stubBin}:${process.env.PATH}` }, + }); + assert.equal(r.status, 0); + assert.doesNotMatch(r.stderr, /requires a value/); +});