Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions bin/gstack-model-benchmark
Original file line number Diff line number Diff line change
Expand Up @@ -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'> {
Expand Down Expand Up @@ -79,7 +103,7 @@ function resolvePrompt(positional: string | undefined): string {
}

async function main(): Promise<void> {
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())!;
Expand Down
27 changes: 27 additions & 0 deletions test/benchmark-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading