jeo-code 0.9.9 → 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,16 @@ The README mirrors the latest 5 entries — regenerate with `bun run changelog:s
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.9.10] - 2026-07-27
12
+ _`jeo update` now verifies the binary you actually run, and recovers from bun's stale registry cache._
13
+
14
+ ### Fixed
15
+ - **`jeo update` claimed success while the machine still ran the old build** (`src/commands/update.ts`) — a package manager's zero exit was treated as proof of an update. In practice bun can fail to resolve a just-published version (`No version matching "x.y.z" ... (but package exists)`) from a stale manifest, jeo then fell back to npm, npm installed into a prefix the user's PATH does not resolve, and the command printed `Successfully installed` while `jeo --version` still reported the previous release. The install now retries bun with `--force` (which re-resolves against the registry) before falling back to npm, stops only once the binary on PATH reports the requested version, and reports a stale active binary as a failure (exit 1, `installed: false` in `--json`) with the exact recovery commands.
16
+
17
+ ### Verified
18
+ - Update suite: 33 pass / 0 fail, including the new stale-active-binary failure, its `--json` shape, and the bun `--force` retry ordering.
19
+ - Reproduced live: `bun install -g jeo-code@0.9.9` resolved nothing from cache while `--force` installed it correctly.
20
+
11
21
  ## [0.9.9] - 2026-07-27
12
22
  _Root-causes the frozen `jeo --tmux` TUI: the window was pinned to its launch size, so resizing the terminal only cut the view._
13
23
 
package/README.ja.md CHANGED
@@ -285,10 +285,10 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
285
285
 
286
286
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
287
287
  - **[Unreleased]**
288
+ - **[0.9.10]** (2026-07-27) — `jeo update` now verifies the binary you actually run, and recovers from bun's stale registry cache.
288
289
  - **[0.9.9]** (2026-07-27) — Root-causes the frozen `jeo --tmux` TUI: the window was pinned to its launch size, so resizing the terminal only cut the view.
289
290
  - **[0.9.8]** (2026-07-27) — The provider model list now persists and rehydrates, so an account's live models survive across launches (gjc parity).
290
291
  - **[0.9.7]** (2026-07-27) — Fixes the broken 0.9.3-0.9.6 npm releases: `jeo` and `jeo --tmux` crashed at startup right after `jeo update`.
291
- - **[0.9.6]** (2026-07-27) — Resize-safe ledger flush and terminal clear handling after viewport shrink.
292
292
 
293
293
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
294
294
  <!-- CHANGELOG:END -->
package/README.ko.md CHANGED
@@ -286,10 +286,10 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
286
286
 
287
287
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
288
288
  - **[Unreleased]**
289
+ - **[0.9.10]** (2026-07-27) — `jeo update` now verifies the binary you actually run, and recovers from bun's stale registry cache.
289
290
  - **[0.9.9]** (2026-07-27) — Root-causes the frozen `jeo --tmux` TUI: the window was pinned to its launch size, so resizing the terminal only cut the view.
290
291
  - **[0.9.8]** (2026-07-27) — The provider model list now persists and rehydrates, so an account's live models survive across launches (gjc parity).
291
292
  - **[0.9.7]** (2026-07-27) — Fixes the broken 0.9.3-0.9.6 npm releases: `jeo` and `jeo --tmux` crashed at startup right after `jeo update`.
292
- - **[0.9.6]** (2026-07-27) — Resize-safe ledger flush and terminal clear handling after viewport shrink.
293
293
 
294
294
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
295
295
  <!-- CHANGELOG:END -->
package/README.md CHANGED
@@ -293,10 +293,10 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
293
293
 
294
294
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
295
295
  - **[Unreleased]**
296
+ - **[0.9.10]** (2026-07-27) — `jeo update` now verifies the binary you actually run, and recovers from bun's stale registry cache.
296
297
  - **[0.9.9]** (2026-07-27) — Root-causes the frozen `jeo --tmux` TUI: the window was pinned to its launch size, so resizing the terminal only cut the view.
297
298
  - **[0.9.8]** (2026-07-27) — The provider model list now persists and rehydrates, so an account's live models survive across launches (gjc parity).
298
299
  - **[0.9.7]** (2026-07-27) — Fixes the broken 0.9.3-0.9.6 npm releases: `jeo` and `jeo --tmux` crashed at startup right after `jeo update`.
299
- - **[0.9.6]** (2026-07-27) — Resize-safe ledger flush and terminal clear handling after viewport shrink.
300
300
 
301
301
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
302
302
  <!-- CHANGELOG:END -->
package/README.zh.md CHANGED
@@ -285,10 +285,10 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
285
285
 
286
286
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
287
287
  - **[Unreleased]**
288
+ - **[0.9.10]** (2026-07-27) — `jeo update` now verifies the binary you actually run, and recovers from bun's stale registry cache.
288
289
  - **[0.9.9]** (2026-07-27) — Root-causes the frozen `jeo --tmux` TUI: the window was pinned to its launch size, so resizing the terminal only cut the view.
289
290
  - **[0.9.8]** (2026-07-27) — The provider model list now persists and rehydrates, so an account's live models survive across launches (gjc parity).
290
291
  - **[0.9.7]** (2026-07-27) — Fixes the broken 0.9.3-0.9.6 npm releases: `jeo` and `jeo --tmux` crashed at startup right after `jeo update`.
291
- - **[0.9.6]** (2026-07-27) — Resize-safe ledger flush and terminal clear handling after viewport shrink.
292
292
 
293
293
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
294
294
  <!-- CHANGELOG:END -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jeo-code",
3
- "version": "0.9.9",
3
+ "version": "0.9.10",
4
4
  "description": "Clean, highly optimized AI coding agent using spec-first loop",
5
5
  "type": "module",
6
6
  "main": "src/cli.ts",
@@ -43,6 +43,36 @@ export interface UpdateDeps {
43
43
  activeVersion?: () => string | null;
44
44
  }
45
45
 
46
+ /**
47
+ * Ordered install attempts for `target` (`jeo-code@<version>`).
48
+ *
49
+ * `bun install -g` alone is not enough in practice: right after a publish bun can
50
+ * still hold a stale registry manifest and fail with `No version matching "<v>"
51
+ * found ... (but package exists)`. `--force` re-resolves against the registry and
52
+ * is what actually recovers that state. npm remains the last resort for
53
+ * npm-installed globals. Pure so the policy is unit-testable without spawning.
54
+ */
55
+ export function installCandidates(target: string): string[][] {
56
+ return [
57
+ ["bun", "install", "-g", target],
58
+ // Cache-buster: bun's resolver held a stale manifest for a just-published version.
59
+ ["bun", "install", "-g", target, "--force"],
60
+ ["npm", "install", "-g", target],
61
+ ];
62
+ }
63
+
64
+ /** Version of the `jeo` binary PATH actually resolves, or null when unreadable. */
65
+ function readActiveVersion(): string | null {
66
+ try {
67
+ const proc = Bun.spawnSync(["jeo", "--version"], { stdout: "pipe", stderr: "pipe" });
68
+ if (!proc.success) return null;
69
+ const out = new TextDecoder().decode(proc.stdout);
70
+ const m = out.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
71
+ return m ? m[1] : null;
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
46
76
  export const defaultDeps: UpdateDeps = {
47
77
  fetchJson: async (url: string, options?: { signal?: AbortSignal }) => {
48
78
  const res = await fetch(url, options);
@@ -65,17 +95,20 @@ export const defaultDeps: UpdateDeps = {
65
95
  // Self-update the global install via the SAME toolchain it was installed with. jeo
66
96
  // ships as a `#!/usr/bin/env bun` script so bun is the common case, but npm-installed
67
97
  // globals exist too — try bun first, fall back to npm (and tolerate either missing).
68
- // `@<version>` (the resolved latest) forces the newest publish past a stale global cache.
69
98
  const target = `jeo-code@${version ?? "latest"}`;
70
- const managers: string[][] = [
71
- ["bun", "install", "-g", target],
72
- ["npm", "install", "-g", target],
73
- ];
74
99
  let lastErr = "";
75
- for (const cmd of managers) {
100
+ for (const cmd of installCandidates(target)) {
76
101
  try {
77
102
  const proc = Bun.spawnSync(cmd, { stdout: "inherit", stderr: "inherit" });
78
- if (proc.success) return { success: true };
103
+ if (proc.success) {
104
+ // A zero exit is NOT proof the user's `jeo` moved: npm's global prefix is
105
+ // often a different directory than the bun prefix PATH actually resolves,
106
+ // so an npm fallback can "succeed" while the active binary is untouched.
107
+ // Only stop once the binary on PATH really reports the requested version.
108
+ if (!version || readActiveVersion() === version) return { success: true };
109
+ lastErr = `${cmd[0]} reported success but the active 'jeo' is still ${readActiveVersion() ?? "unknown"}`;
110
+ continue;
111
+ }
79
112
  lastErr = `${cmd[0]} exited with code ${proc.exitCode}`;
80
113
  } catch (err: any) {
81
114
  lastErr = `${cmd[0]} unavailable: ${err?.message ?? String(err)}`;
@@ -84,20 +117,10 @@ export const defaultDeps: UpdateDeps = {
84
117
  return { success: false, stderr: lastErr };
85
118
  }
86
119
  ,
87
- activeVersion: () => {
88
- // Read the version of the `jeo` actually on PATH (may differ from this process's bundled
89
- // version after a self-update, e.g. when bun installed to ~/.bun/bin but PATH prefers an
90
- // npm global). Used to detect a silent "installed but PATH unchanged" no-op.
91
- try {
92
- const proc = Bun.spawnSync(["jeo", "--version"], { stdout: "pipe", stderr: "pipe" });
93
- if (!proc.success) return null;
94
- const out = new TextDecoder().decode(proc.stdout);
95
- const m = out.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
96
- return m ? m[1] : null;
97
- } catch {
98
- return null;
99
- }
100
- }
120
+ // Version of the `jeo` actually on PATH — may differ from this process's bundled
121
+ // version after a self-update (e.g. npm installed into a prefix PATH does not
122
+ // resolve). Detects the silent "installed but PATH unchanged" no-op.
123
+ activeVersion: readActiveVersion
101
124
  ,
102
125
  showWhatsNew: () => {
103
126
  try {
@@ -279,25 +302,31 @@ export async function runUpdateCommandWith(args: string[], deps: UpdateDeps): Pr
279
302
  try {
280
303
  const result = await deps.install(latest ?? undefined);
281
304
  if (result.success) {
305
+ // A package manager's zero exit does not prove the user's `jeo` moved: npm's
306
+ // global prefix is frequently a different directory than the one PATH resolves,
307
+ // so the install lands somewhere the user never runs. Verify the ACTIVE binary
308
+ // and treat a still-stale one as a failed update (exit 1) rather than printing
309
+ // a success line the machine contradicts.
310
+ const active = deps.activeVersion?.();
311
+ const stale = !!(active && latest && compareVersions(active, latest) < 0);
282
312
  if (hasJson) {
283
313
  console.log(JSON.stringify({
284
314
  current,
285
315
  latest,
286
316
  upToDate: false,
287
- installed: true
317
+ installed: !stale,
318
+ ...(stale ? { activeVersion: active, error: "active jeo still reports an older version" } : {}),
288
319
  }));
320
+ if (stale) process.exitCode = 1;
321
+ } else if (stale) {
322
+ console.error(`[FAILED] Installed jeo-code@${latest}, but the active 'jeo' on PATH still reports ${active}.`);
323
+ console.error(`The install landed in a prefix your PATH does not use. Fix it with one of:`);
324
+ console.error(` bun install -g jeo-code@${latest} --force`);
325
+ console.error(` npm install -g jeo-code@${latest}`);
326
+ console.error(`Then confirm with: jeo --version`);
327
+ process.exitCode = 1;
289
328
  } else {
290
329
  console.log(`Successfully installed jeo-code@${latest}`);
291
- // Verify the `jeo` on PATH actually picked up the new install — a bun-vs-npm or
292
- // PATH mismatch can leave an older binary in front, which looks like "update did
293
- // nothing". Surface that loudly with the manual fix instead of failing silently.
294
- const active = deps.activeVersion?.();
295
- if (active && latest && compareVersions(active, latest) < 0) {
296
- console.warn(`Warning: installed ${latest}, but the active 'jeo' on PATH still reports ${active}.`);
297
- console.warn(`Your PATH points at a different install. Fix it with one of:`);
298
- console.warn(` npm install -g jeo-code@${latest}`);
299
- console.warn(` bun install -g jeo-code@${latest}`);
300
- }
301
330
  deps.showWhatsNew?.();
302
331
  }
303
332
  } else {