diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index 8ff34cd2..448a183d 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -19,6 +19,7 @@ export interface Args { continue?: boolean; resume?: boolean; help?: boolean; + version?: boolean; mode?: Mode; noSession?: boolean; session?: string; @@ -48,6 +49,8 @@ export function parseArgs(args: string[]): Args { if (arg === "--help" || arg === "-h") { result.help = true; + } else if (arg === "--version" || arg === "-v") { + result.version = true; } else if (arg === "--mode" && i + 1 < args.length) { const mode = args[++i]; if (mode === "text" || mode === "json" || mode === "rpc") { @@ -139,6 +142,7 @@ ${chalk.bold("Options:")} --hook Load a hook file (can be used multiple times) --export Export session file to HTML and exit --help, -h Show this help + --version, -v Show version number ${chalk.bold("Examples:")} # Interactive mode diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index eb40896e..635a98fb 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -139,6 +139,11 @@ function prepareInitialMessage(parsed: Args): { export async function main(args: string[]) { const parsed = parseArgs(args); + if (parsed.version) { + console.log(VERSION); + return; + } + if (parsed.help) { printHelp(); return; diff --git a/packages/coding-agent/test/args.test.ts b/packages/coding-agent/test/args.test.ts new file mode 100644 index 00000000..ab764ee1 --- /dev/null +++ b/packages/coding-agent/test/args.test.ts @@ -0,0 +1,192 @@ +import { describe, expect, test } from "vitest"; +import { parseArgs } from "../src/cli/args.js"; + +describe("parseArgs", () => { + describe("--version flag", () => { + test("parses --version flag", () => { + const result = parseArgs(["--version"]); + expect(result.version).toBe(true); + }); + + test("parses -v shorthand", () => { + const result = parseArgs(["-v"]); + expect(result.version).toBe(true); + }); + + test("--version takes precedence over other args", () => { + const result = parseArgs(["--version", "--help", "some message"]); + expect(result.version).toBe(true); + expect(result.help).toBe(true); + expect(result.messages).toContain("some message"); + }); + }); + + describe("--help flag", () => { + test("parses --help flag", () => { + const result = parseArgs(["--help"]); + expect(result.help).toBe(true); + }); + + test("parses -h shorthand", () => { + const result = parseArgs(["-h"]); + expect(result.help).toBe(true); + }); + }); + + describe("--print flag", () => { + test("parses --print flag", () => { + const result = parseArgs(["--print"]); + expect(result.print).toBe(true); + }); + + test("parses -p shorthand", () => { + const result = parseArgs(["-p"]); + expect(result.print).toBe(true); + }); + }); + + describe("--continue flag", () => { + test("parses --continue flag", () => { + const result = parseArgs(["--continue"]); + expect(result.continue).toBe(true); + }); + + test("parses -c shorthand", () => { + const result = parseArgs(["-c"]); + expect(result.continue).toBe(true); + }); + }); + + describe("--resume flag", () => { + test("parses --resume flag", () => { + const result = parseArgs(["--resume"]); + expect(result.resume).toBe(true); + }); + + test("parses -r shorthand", () => { + const result = parseArgs(["-r"]); + expect(result.resume).toBe(true); + }); + }); + + describe("flags with values", () => { + test("parses --provider", () => { + const result = parseArgs(["--provider", "openai"]); + expect(result.provider).toBe("openai"); + }); + + test("parses --model", () => { + const result = parseArgs(["--model", "gpt-4o"]); + expect(result.model).toBe("gpt-4o"); + }); + + test("parses --api-key", () => { + const result = parseArgs(["--api-key", "sk-test-key"]); + expect(result.apiKey).toBe("sk-test-key"); + }); + + test("parses --system-prompt", () => { + const result = parseArgs(["--system-prompt", "You are a helpful assistant"]); + expect(result.systemPrompt).toBe("You are a helpful assistant"); + }); + + test("parses --append-system-prompt", () => { + const result = parseArgs(["--append-system-prompt", "Additional context"]); + expect(result.appendSystemPrompt).toBe("Additional context"); + }); + + test("parses --mode", () => { + const result = parseArgs(["--mode", "json"]); + expect(result.mode).toBe("json"); + }); + + test("parses --mode rpc", () => { + const result = parseArgs(["--mode", "rpc"]); + expect(result.mode).toBe("rpc"); + }); + + test("parses --session", () => { + const result = parseArgs(["--session", "/path/to/session.jsonl"]); + expect(result.session).toBe("/path/to/session.jsonl"); + }); + + test("parses --export", () => { + const result = parseArgs(["--export", "session.jsonl"]); + expect(result.export).toBe("session.jsonl"); + }); + + test("parses --thinking", () => { + const result = parseArgs(["--thinking", "high"]); + expect(result.thinking).toBe("high"); + }); + + test("parses --models as comma-separated list", () => { + const result = parseArgs(["--models", "gpt-4o,claude-sonnet,gemini-pro"]); + expect(result.models).toEqual(["gpt-4o", "claude-sonnet", "gemini-pro"]); + }); + }); + + describe("--no-session flag", () => { + test("parses --no-session flag", () => { + const result = parseArgs(["--no-session"]); + expect(result.noSession).toBe(true); + }); + }); + + describe("--hook flag", () => { + test("parses single --hook", () => { + const result = parseArgs(["--hook", "./my-hook.ts"]); + expect(result.hooks).toEqual(["./my-hook.ts"]); + }); + + test("parses multiple --hook flags", () => { + const result = parseArgs(["--hook", "./hook1.ts", "--hook", "./hook2.ts"]); + expect(result.hooks).toEqual(["./hook1.ts", "./hook2.ts"]); + }); + }); + + describe("messages and file args", () => { + test("parses plain text messages", () => { + const result = parseArgs(["hello", "world"]); + expect(result.messages).toEqual(["hello", "world"]); + }); + + test("parses @file arguments", () => { + const result = parseArgs(["@README.md", "@src/main.ts"]); + expect(result.fileArgs).toEqual(["README.md", "src/main.ts"]); + }); + + test("parses mixed messages and file args", () => { + const result = parseArgs(["@file.txt", "explain this", "@image.png"]); + expect(result.fileArgs).toEqual(["file.txt", "image.png"]); + expect(result.messages).toEqual(["explain this"]); + }); + + test("ignores unknown flags starting with -", () => { + const result = parseArgs(["--unknown-flag", "message"]); + expect(result.messages).toEqual(["message"]); + }); + }); + + describe("complex combinations", () => { + test("parses multiple flags together", () => { + const result = parseArgs([ + "--provider", + "anthropic", + "--model", + "claude-sonnet", + "--print", + "--thinking", + "high", + "@prompt.md", + "Do the task", + ]); + expect(result.provider).toBe("anthropic"); + expect(result.model).toBe("claude-sonnet"); + expect(result.print).toBe(true); + expect(result.thinking).toBe("high"); + expect(result.fileArgs).toEqual(["prompt.md"]); + expect(result.messages).toEqual(["Do the task"]); + }); + }); +});