Skip to content

Commit cb6241f

Browse files
lidge-junclaude
andcommitted
feat(cli): ocx update — self-update to the latest published version
Add `ocx update` (src/update.ts): detects whether opencodex was installed via bun or npm (from the running module path) and runs `bun add -g opencodex@latest` / `npm install -g opencodex@latest`; a source checkout is told to `git pull` instead, and it's a no-op when already current. The Release CI publishes the versions it fetches. Documented in the README CLI list + docs CLI reference. Verified: tsc clean, source-checkout detection works, listed in `ocx help`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 26961d9 commit cb6241f

4 files changed

Lines changed: 87 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ ocx login <xai|anthropic|kimi> # OAuth login
111111
ocx logout <provider> # remove a stored login
112112
ocx gui # open the web dashboard
113113
ocx service <install|start|stop|status|uninstall> # run as a background service
114+
ocx update # update opencodex to the latest published version
114115
```
115116

116117
## Configuration

docs-site/src/content/docs/reference/cli.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,22 @@ ocx service status
8888
ocx service uninstall
8989
```
9090

91+
## Updating
92+
93+
### `ocx update`
94+
95+
Self-update opencodex to the latest version published on npm, using the package manager it was
96+
installed with (`bun add -g opencodex@latest` or `npm install -g opencodex@latest`). It detects a
97+
source checkout and tells you to `git pull && bun install` instead, and is a no-op if you're already
98+
on the newest version. Restart the proxy afterward (`ocx stop && ocx start`) to run the new build.
99+
100+
```bash
101+
ocx update
102+
```
103+
104+
New versions become available the moment the [Release workflow](https://github.com/lidge-jun/opencodex/actions/workflows/release.yml)
105+
publishes them to npm.
106+
91107
## Help
92108

93109
`ocx help`, `ocx --help`, `ocx -h` — print usage and examples.

src/cli.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Usage:
2121
ocx status Check proxy server status
2222
ocx login <provider> OAuth login (xai) — opens browser, stores token in ~/.opencodex/auth.json
2323
ocx logout <provider> Remove a stored OAuth login
24+
ocx update Update opencodex to the latest published version
2425
ocx help Show this help message
2526
2627
Examples:
@@ -164,6 +165,11 @@ switch (command) {
164165
case "service":
165166
serviceCommand(args[1]);
166167
break;
168+
case "update": {
169+
const { runUpdate } = await import("./update");
170+
runUpdate();
171+
break;
172+
}
167173
case "help":
168174
case "--help":
169175
case "-h":

src/update.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { spawnSync } from "node:child_process";
2+
import { readFileSync } from "node:fs";
3+
import { fileURLToPath } from "node:url";
4+
import { dirname, join } from "node:path";
5+
6+
const PKG = "opencodex";
7+
const HERE = dirname(fileURLToPath(import.meta.url)); // .../opencodex/src
8+
9+
type Installer = "bun" | "npm" | "source";
10+
11+
/** Infer how opencodex is installed from the running module's path. */
12+
function detectInstall(): Installer {
13+
if (!HERE.includes("node_modules")) return "source"; // a git checkout, not a global install
14+
return HERE.includes(".bun") ? "bun" : "npm";
15+
}
16+
17+
function currentVersion(): string {
18+
try {
19+
return (JSON.parse(readFileSync(join(HERE, "..", "package.json"), "utf8")).version as string) ?? "?";
20+
} catch {
21+
return "?";
22+
}
23+
}
24+
25+
/** Latest published version from the registry (best-effort; null if npm isn't available). */
26+
function latestVersion(): string | null {
27+
const r = spawnSync("npm", ["view", PKG, "version"], { encoding: "utf8", timeout: 12000, windowsHide: true });
28+
return r.status === 0 ? r.stdout.trim() : null;
29+
}
30+
31+
/**
32+
* `ocx update` — self-update opencodex to the latest published version, using the same package
33+
* manager it was installed with (bun or npm global). A source checkout is told to `git pull` instead.
34+
*/
35+
export function runUpdate(): void {
36+
const installer = detectInstall();
37+
const current = currentVersion();
38+
console.log(`opencodex v${current} (installed via ${installer})`);
39+
40+
if (installer === "source") {
41+
console.log("Running from a source checkout — update with: git pull && bun install");
42+
return;
43+
}
44+
45+
const latest = latestVersion();
46+
if (latest && latest === current) {
47+
console.log(`Already on the latest version (v${latest}).`);
48+
return;
49+
}
50+
51+
const bin = installer === "bun" ? "bun" : "npm";
52+
const cmdArgs = installer === "bun"
53+
? ["add", "-g", `${PKG}@latest`]
54+
: ["install", "-g", `${PKG}@latest`];
55+
console.log(`Updating${latest ? ` to v${latest}` : ""}…\n$ ${bin} ${cmdArgs.join(" ")}`);
56+
57+
const r = spawnSync(bin, cmdArgs, { stdio: "inherit", timeout: 180000, windowsHide: true });
58+
if (r.status === 0) {
59+
console.log(`\n✅ Updated${latest ? ` to v${latest}` : ""}. Restart the proxy: ocx stop && ocx start`);
60+
} else {
61+
console.error(`\n⚠️ Update failed (${bin} exit ${r.status ?? "?"}). Try manually: ${bin} ${cmdArgs.join(" ")}`);
62+
process.exit(1);
63+
}
64+
}

0 commit comments

Comments
 (0)