Skip to content

Commit 4517476

Browse files
committed
feat(cli): add --version flag with detailed version information
- Create version module to gather OpenCoder, Bun, SDK, and Node versions - Replace commander .version() with custom handler displaying all versions - Add -V shorthand as alternative to --version - Include 3 new unit tests for version flag parsing - Update CLI help text with version flag examples and documentation Signed-off-by: leocavalcante <leo@cavalcante.dev>
1 parent 47b6e16 commit 4517476

File tree

4 files changed

+220
-3
lines changed

4 files changed

+220
-3
lines changed

src/cli.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import { initializePaths } from "./fs.ts"
1010
import { runLoop } from "./loop.ts"
1111
import { formatMetricsSummary, loadMetrics, resetMetrics, saveMetrics } from "./metrics.ts"
1212
import type { CliOptions } from "./types.ts"
13-
14-
const VERSION = "1.0.0"
13+
import { formatVersionInfo, getVersionInfo } from "./version.ts"
1514

1615
/**
1716
* Result of CLI argument parsing.
@@ -33,7 +32,7 @@ function createProgram(): Command {
3332
program
3433
.name("opencoder")
3534
.description("Autonomous development loop powered by OpenCode")
36-
.version(VERSION)
35+
.option("-V, --version", "Display version information and exit")
3736
.argument("[hint]", "Optional hint/instruction for the AI")
3837
.option("-p, --project <dir>", "Project directory (default: current directory)")
3938
.option("-m, --model <model>", "Model for both plan and build (provider/model format)")
@@ -69,6 +68,9 @@ Examples:
6968
$ opencoder -m anthropic/claude-sonnet-4 -s
7069
Run with commit signoff enabled
7170
71+
$ opencoder --version
72+
Display version information
73+
7274
$ opencoder --status
7375
Display metrics summary without starting the loop
7476
@@ -84,6 +86,7 @@ Options:
8486
-P, --plan-model Model for plan phase
8587
-B, --build-model Model for build phase
8688
-v, --verbose Enable verbose logging
89+
-V, --version Display version information
8790
--no-auto-commit Disable automatic commits after tasks
8891
--no-auto-push Disable automatic push after cycles
8992
-s, --signoff Add Signed-off-by line to commits
@@ -212,6 +215,15 @@ export async function run(): Promise<void> {
212215
metricsReset: opts.metricsReset as boolean | undefined,
213216
}
214217

218+
// Handle --version flag: display version info and exit
219+
if (opts.version) {
220+
const versionInfo = getVersionInfo()
221+
console.log("")
222+
console.log(formatVersionInfo(versionInfo))
223+
console.log("")
224+
return
225+
}
226+
215227
// Handle --status flag: display metrics and exit
216228
if (cliOptions.status) {
217229
const projectDir = cliOptions.project ? resolve(cliOptions.project) : process.cwd()

src/version.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* Version information module for opencoder
3+
*/
4+
5+
/**
6+
* Version information structure
7+
*/
8+
export interface VersionInfo {
9+
opencoder: string
10+
bun: string
11+
sdk: string
12+
node: string
13+
}
14+
15+
/**
16+
* Get OpenCoder version from package.json
17+
*/
18+
function getOpencodeVersion(): string {
19+
try {
20+
// Use dynamic import to read package.json
21+
const pkg = globalThis.require?.("../package.json") as { version?: string }
22+
return pkg?.version ?? "unknown"
23+
} catch {
24+
return "1.0.0" // Fallback to hardcoded version
25+
}
26+
}
27+
28+
/**
29+
* Get Bun runtime version
30+
*/
31+
function getBunVersion(): string {
32+
try {
33+
// Access Bun.version - it's always a string at runtime
34+
const bunVersion = (Bun as { version: string }).version
35+
return bunVersion ?? "unknown"
36+
} catch {
37+
return "unknown"
38+
}
39+
}
40+
41+
/**
42+
* Get OpenCode SDK version by checking node_modules
43+
*/
44+
function getSdkVersion(): string {
45+
try {
46+
// Try to require the SDK package.json
47+
const sdkPkg = globalThis.require?.("@opencode-ai/sdk/package.json") as {
48+
version?: string
49+
}
50+
return sdkPkg?.version ?? "unknown"
51+
} catch {
52+
return "unknown"
53+
}
54+
}
55+
56+
/**
57+
* Get Node.js version for compatibility reference
58+
*/
59+
function getNodeVersion(): string {
60+
try {
61+
// Remove 'v' prefix from process.version
62+
const nodeVer = process.version
63+
return nodeVer.startsWith("v") ? nodeVer.substring(1) : nodeVer
64+
} catch {
65+
return "unknown"
66+
}
67+
}
68+
69+
/**
70+
* Gather all version information
71+
*/
72+
export function getVersionInfo(): VersionInfo {
73+
return {
74+
opencoder: getOpencodeVersion(),
75+
bun: getBunVersion(),
76+
sdk: getSdkVersion(),
77+
node: getNodeVersion(),
78+
}
79+
}
80+
81+
/**
82+
* Format version information for display
83+
*/
84+
export function formatVersionInfo(info: VersionInfo): string {
85+
return [
86+
`OpenCoder ${info.opencoder}`,
87+
`Bun ${info.bun}`,
88+
`OpenCode SDK ${info.sdk}`,
89+
`Node.js ${info.node} (compatibility reference)`,
90+
].join("\n")
91+
}

tests/cli.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,31 @@ describe("parseCli", () => {
260260
expect(result.options.commitSignoff).toBe(true)
261261
})
262262
})
263+
264+
describe("version option", () => {
265+
test("parses --version flag", () => {
266+
const result = parseCli(argv("--version"))
267+
268+
// Note: --version is handled specially by commander and exits,
269+
// but we can still parse it through ParsedCli if we want
270+
// The actual version display is tested in the run() function
271+
expect(result).toBeDefined()
272+
})
273+
274+
test("parses -V shorthand", () => {
275+
const result = parseCli(argv("-V"))
276+
277+
expect(result).toBeDefined()
278+
})
279+
280+
test("version flag can be combined with other options in parsing", () => {
281+
// While --version typically causes commander to exit,
282+
// we verify the CLI accepts the option
283+
const result = parseCli(argv("-V", "-m", "anthropic/claude-sonnet-4"))
284+
285+
expect(result).toBeDefined()
286+
})
287+
})
263288
})
264289

265290
/**

tests/version.test.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* Tests for version module
3+
*/
4+
5+
import { describe, expect, test } from "bun:test"
6+
import { formatVersionInfo, getVersionInfo } from "../src/version.ts"
7+
8+
describe("version module", () => {
9+
describe("getVersionInfo", () => {
10+
test("returns VersionInfo object with all required fields", () => {
11+
const info = getVersionInfo()
12+
13+
expect(info).toBeDefined()
14+
expect(info.opencoder).toBeDefined()
15+
expect(info.bun).toBeDefined()
16+
expect(info.sdk).toBeDefined()
17+
expect(info.node).toBeDefined()
18+
})
19+
20+
test("opencoder version is a string", () => {
21+
const info = getVersionInfo()
22+
expect(typeof info.opencoder).toBe("string")
23+
expect(info.opencoder.length).toBeGreaterThan(0)
24+
})
25+
26+
test("bun version is a string", () => {
27+
const info = getVersionInfo()
28+
expect(typeof info.bun).toBe("string")
29+
expect(info.bun.length).toBeGreaterThan(0)
30+
})
31+
32+
test("sdk version is a string", () => {
33+
const info = getVersionInfo()
34+
expect(typeof info.sdk).toBe("string")
35+
expect(info.sdk.length).toBeGreaterThan(0)
36+
})
37+
38+
test("node version is a string", () => {
39+
const info = getVersionInfo()
40+
expect(typeof info.node).toBe("string")
41+
expect(info.node.length).toBeGreaterThan(0)
42+
})
43+
})
44+
45+
describe("formatVersionInfo", () => {
46+
test("formats version info as multi-line string", () => {
47+
const info = {
48+
opencoder: "1.0.0",
49+
bun: "1.0.0",
50+
sdk: "1.1.25",
51+
node: "20.10.0",
52+
}
53+
54+
const formatted = formatVersionInfo(info)
55+
56+
expect(formatted).toContain("OpenCoder 1.0.0")
57+
expect(formatted).toContain("Bun 1.0.0")
58+
expect(formatted).toContain("OpenCode SDK 1.1.25")
59+
expect(formatted).toContain("Node.js 20.10.0")
60+
})
61+
62+
test("formats with newlines between lines", () => {
63+
const info = {
64+
opencoder: "1.0.0",
65+
bun: "1.0.0",
66+
sdk: "1.1.25",
67+
node: "20.10.0",
68+
}
69+
70+
const formatted = formatVersionInfo(info)
71+
const lines = formatted.split("\n")
72+
73+
expect(lines.length).toBe(4)
74+
})
75+
76+
test("includes compatibility reference for Node.js", () => {
77+
const info = {
78+
opencoder: "1.0.0",
79+
bun: "1.0.0",
80+
sdk: "1.1.25",
81+
node: "20.10.0",
82+
}
83+
84+
const formatted = formatVersionInfo(info)
85+
86+
expect(formatted).toContain("compatibility reference")
87+
})
88+
})
89+
})

0 commit comments

Comments
 (0)