diff --git a/bin/gstack-model-benchmark b/bin/gstack-model-benchmark index 34227652c5..c5f5cb5b65 100755 --- a/bin/gstack-model-benchmark +++ b/bin/gstack-model-benchmark @@ -40,16 +40,40 @@ const ADAPTER_FACTORIES = { type OutputFormat = 'table' | 'json' | 'markdown'; +const CLI_ARGS = process.argv.slice(2); +const VALUE_FLAGS = new Set(['--models', '--prompt', '--workdir', '--timeout-ms', '--output']); + function arg(name: string, def?: string): string | undefined { - const idx = process.argv.findIndex(a => a === name || a.startsWith(name + '=')); + const idx = CLI_ARGS.findIndex(a => a === name || a.startsWith(name + '=')); if (idx < 0) return def; - const eqIdx = process.argv[idx].indexOf('='); - if (eqIdx >= 0) return process.argv[idx].slice(eqIdx + 1); - return process.argv[idx + 1]; + const eqIdx = CLI_ARGS[idx].indexOf('='); + if (eqIdx >= 0) return CLI_ARGS[idx].slice(eqIdx + 1); + return CLI_ARGS[idx + 1]; } function flag(name: string): boolean { - return process.argv.includes(name); + return CLI_ARGS.includes(name); +} + +function positionalArgs(args: string[]): string[] { + const positional: string[] = []; + for (let i = 0; i < args.length; i++) { + const current = args[i]; + if (current === '--') { + positional.push(...args.slice(i + 1)); + break; + } + if (current.startsWith('--')) { + const eqIdx = current.indexOf('='); + const flagName = eqIdx >= 0 ? current.slice(0, eqIdx) : current; + if (eqIdx < 0 && VALUE_FLAGS.has(flagName) && i + 1 < args.length) { + i++; + } + continue; + } + positional.push(current); + } + return positional; } function parseProviders(s: string | undefined): Array<'claude' | 'gpt' | 'gemini'> { @@ -79,7 +103,7 @@ function resolvePrompt(positional: string | undefined): string { } async function main(): Promise { - const positional = process.argv.slice(2).find(a => !a.startsWith('--')); + const positional = positionalArgs(CLI_ARGS)[0]; const prompt = resolvePrompt(positional); const providers = parseProviders(arg('--models')); const workdir = arg('--workdir', process.cwd())!; diff --git a/test/benchmark-cli.test.ts b/test/benchmark-cli.test.ts index 2932ec0c4c..8edea3b245 100644 --- a/test/benchmark-cli.test.ts +++ b/test/benchmark-cli.test.ts @@ -163,6 +163,33 @@ describe('gstack-model-benchmark prompt resolution', () => { } }); + test('positional file still works when value flags come first', () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'bench-prompt-')); + const promptFile = path.join(tmp, 'prompt.txt'); + fs.writeFileSync(promptFile, 'hello after flags'); + try { + const r = run(['--models', 'claude', '--output', 'json', promptFile, '--dry-run']); + expect(r.status).toBe(0); + expect(r.stdout).toContain('hello after flags'); + expect(r.stdout).not.toContain('EISDIR'); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }); + + test('positional file still works after equals-form value flags', () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'bench-prompt-')); + const promptFile = path.join(tmp, 'prompt.txt'); + fs.writeFileSync(promptFile, 'hello after equals flags'); + try { + const r = run(['--models=claude', '--output=markdown', promptFile, '--dry-run']); + expect(r.status).toBe(0); + expect(r.stdout).toContain('hello after equals flags'); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }); + test('positional non-file arg is treated as inline prompt', () => { const r = run(['treat-me-as-inline', '--dry-run']); expect(r.status).toBe(0);