jeo-code 0.9.3 → 0.9.4

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,21 @@ The README mirrors the latest 5 entries — regenerate with `bun run changelog:s
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.9.4] - 2026-07-27
12
+ _Real OpenAI/Codex model discovery after login, npm package-content hardening, and pinned self-update support._
13
+
14
+ ### Added
15
+ - **Live OpenAI/Codex models after login** (`src/commands/auth.ts`, `src/ai/model-discovery.ts`) — immediately probes the authenticated account's Codex models endpoint after manual or interactive OpenAI login, prefers that OAuth credential for the report, and keeps login successful when discovery is unavailable.
16
+ - **Pinned self-update targets** (`src/commands/update.ts`) — `jeo update --version <semver>` resolves and installs an exact published release without falling back to npm `latest`.
17
+
18
+ ### Fixed
19
+ - **npm package-content CI guard** (`.github/workflows/npm-publish.yml`) — asserts `npm pack --dry-run --json` against git-tracked files, required release files, and forbidden test/runtime artifacts before publishing.
20
+
21
+ ### Verified
22
+ - Release-focused suite: 148 pass / 0 fail across model discovery, update, package workflow, Codex responses, model manager, and catalog tests.
23
+ - `bun run typecheck`: clean.
24
+ - Clean-worktree package manifest excludes the previously leaked monitor/playwriter files.
25
+
11
26
  ## [0.9.3] - 2026-07-26
12
27
  _Released Telegram integration reliability improvements adapted from GJC v0.11.10, with safe jeo-native behavior and live verification._
13
28
 
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.4]** (2026-07-27) — Real OpenAI/Codex model discovery after login, npm package-content hardening, and pinned self-update support.
288
289
  - **[0.9.3]** (2026-07-26) — Released Telegram integration reliability improvements adapted from GJC v0.11.10, with safe jeo-native behavior and live verification.
289
290
  - **[0.9.2]** (2026-07-26) — Released the jeo-native GJC v0.11.6–v0.11.9 parity improvements after a complete cross-audit and live verification pass.
290
291
  - **[0.9.1]** (2026-07-24) — User request (paraphrased, Korean): "make the TUI resize responsively like gjc's TUI when terminal width/height changes, without breaking the layout — also borrow forge's layout and table components." Investigated the existing resize infrastructure first (already extensive: throttled live-frame resize repaint, a poll-based SIGWINCH safety net, resize-aware interactive pickers) before assuming a gap. Found one concrete un-responsive panel — `jeo doctor`'s Provider connectivity table — and, while rebuilding it on forge's own box-drawn table renderer, also found and fixed a real, previously-unknown overflow bug in `formatForgeBox` itself, shared by every tool card across the whole live TUI.
291
- - **[0.9.0]** (2026-07-22) — Session-scoped async execution, detached subagent control, background monitors, and parallel TUI activity are now documented and shipped for the gjc-parity release line.
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.4]** (2026-07-27) — Real OpenAI/Codex model discovery after login, npm package-content hardening, and pinned self-update support.
289
290
  - **[0.9.3]** (2026-07-26) — Released Telegram integration reliability improvements adapted from GJC v0.11.10, with safe jeo-native behavior and live verification.
290
291
  - **[0.9.2]** (2026-07-26) — Released the jeo-native GJC v0.11.6–v0.11.9 parity improvements after a complete cross-audit and live verification pass.
291
292
  - **[0.9.1]** (2026-07-24) — User request (paraphrased, Korean): "make the TUI resize responsively like gjc's TUI when terminal width/height changes, without breaking the layout — also borrow forge's layout and table components." Investigated the existing resize infrastructure first (already extensive: throttled live-frame resize repaint, a poll-based SIGWINCH safety net, resize-aware interactive pickers) before assuming a gap. Found one concrete un-responsive panel — `jeo doctor`'s Provider connectivity table — and, while rebuilding it on forge's own box-drawn table renderer, also found and fixed a real, previously-unknown overflow bug in `formatForgeBox` itself, shared by every tool card across the whole live TUI.
292
- - **[0.9.0]** (2026-07-22) — Session-scoped async execution, detached subagent control, background monitors, and parallel TUI activity are now documented and shipped for the gjc-parity release line.
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.4]** (2026-07-27) — Real OpenAI/Codex model discovery after login, npm package-content hardening, and pinned self-update support.
296
297
  - **[0.9.3]** (2026-07-26) — Released Telegram integration reliability improvements adapted from GJC v0.11.10, with safe jeo-native behavior and live verification.
297
298
  - **[0.9.2]** (2026-07-26) — Released the jeo-native GJC v0.11.6–v0.11.9 parity improvements after a complete cross-audit and live verification pass.
298
299
  - **[0.9.1]** (2026-07-24) — User request (paraphrased, Korean): "make the TUI resize responsively like gjc's TUI when terminal width/height changes, without breaking the layout — also borrow forge's layout and table components." Investigated the existing resize infrastructure first (already extensive: throttled live-frame resize repaint, a poll-based SIGWINCH safety net, resize-aware interactive pickers) before assuming a gap. Found one concrete un-responsive panel — `jeo doctor`'s Provider connectivity table — and, while rebuilding it on forge's own box-drawn table renderer, also found and fixed a real, previously-unknown overflow bug in `formatForgeBox` itself, shared by every tool card across the whole live TUI.
299
- - **[0.9.0]** (2026-07-22) — Session-scoped async execution, detached subagent control, background monitors, and parallel TUI activity are now documented and shipped for the gjc-parity release line.
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.4]** (2026-07-27) — Real OpenAI/Codex model discovery after login, npm package-content hardening, and pinned self-update support.
288
289
  - **[0.9.3]** (2026-07-26) — Released Telegram integration reliability improvements adapted from GJC v0.11.10, with safe jeo-native behavior and live verification.
289
290
  - **[0.9.2]** (2026-07-26) — Released the jeo-native GJC v0.11.6–v0.11.9 parity improvements after a complete cross-audit and live verification pass.
290
291
  - **[0.9.1]** (2026-07-24) — User request (paraphrased, Korean): "make the TUI resize responsively like gjc's TUI when terminal width/height changes, without breaking the layout — also borrow forge's layout and table components." Investigated the existing resize infrastructure first (already extensive: throttled live-frame resize repaint, a poll-based SIGWINCH safety net, resize-aware interactive pickers) before assuming a gap. Found one concrete un-responsive panel — `jeo doctor`'s Provider connectivity table — and, while rebuilding it on forge's own box-drawn table renderer, also found and fixed a real, previously-unknown overflow bug in `formatForgeBox` itself, shared by every tool card across the whole live TUI.
291
- - **[0.9.0]** (2026-07-22) — Session-scoped async execution, detached subagent control, background monitors, and parallel TUI activity are now documented and shipped for the gjc-parity release line.
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.3",
3
+ "version": "0.9.4",
4
4
  "description": "Clean, highly optimized AI coding agent using spec-first loop",
5
5
  "type": "module",
6
6
  "main": "src/cli.ts",
@@ -39,6 +39,15 @@ export interface DiscoveryOptions {
39
39
  limit?: number;
40
40
  /** Config snapshot used for provider base URLs. */
41
41
  config?: Config;
42
+ /**
43
+ * Narrowly scoped for the post-login report (`jeo auth login openai`): keep
44
+ * the just-stored OAuth credential even when an unrelated `providers.openai`
45
+ * API key is also configured, so the report reflects the account that was
46
+ * JUST logged in rather than silently swapping to the API key. Does not
47
+ * change any other discovery caller — the TUI/`discoverModels` default
48
+ * (API key preferred when configured) is untouched.
49
+ */
50
+ preferOAuth?: boolean;
42
51
  }
43
52
 
44
53
  const DEFAULT_TIMEOUT = 5000;
@@ -48,7 +57,7 @@ const DEFAULT_LIMIT = 100;
48
57
  // to receive the full current list (verified live 2026-06-12: 0.46→[], 0.99→gpt-5.4,
49
58
  // 1.0/2.0→full gpt-5.5 set). On drift the catalog fallback keeps Codex usable.
50
59
  const CODEX_CLIENT_VERSION = "2.0.0";
51
- const CODEX_MODELS_URL = `https://chatgpt.com/backend-api/codex/models?client_version=${CODEX_CLIENT_VERSION}`;
60
+ export const CODEX_MODELS_URL = `https://chatgpt.com/backend-api/codex/models?client_version=${CODEX_CLIENT_VERSION}`;
52
61
  const ANTIGRAVITY_MODELS_URL = "https://daily-cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels";
53
62
  const ANTIGRAVITY_MODEL_DENYLIST = new Set([
54
63
  "chat_20706",
@@ -360,7 +369,9 @@ export async function listProviderModels(
360
369
  const prov = authProvider!;
361
370
  // Antigravity's list endpoint accepts ONLY OAuth (the request builder sends
362
371
  // no api-key header), so never swap its credential to an api_key.
363
- if (provider !== "antigravity" && cred.kind === "oauth" && config.providers?.[prov]) {
372
+ // `preferOAuth` (post-login report only) skips this swap so the report
373
+ // reflects the freshly stored OAuth credential, not an unrelated key.
374
+ if (provider !== "antigravity" && cred.kind === "oauth" && config.providers?.[prov] && !opts.preferOAuth) {
364
375
  // An API key is the broader, documented path — prefer it for live discovery.
365
376
  cred = { kind: "api_key", provider: prov, token: config.providers[prov]! };
366
377
  source = "api_key";
package/src/cli/runner.ts CHANGED
@@ -217,7 +217,7 @@ export const COMMANDS: readonly CommandSpec[] = [
217
217
  {
218
218
  name: "update",
219
219
  summary: "Update jeo-code to the latest npm release (bare = install; --check only checks).",
220
- usage: "update [--check|--install] [--json] [--strict]",
220
+ usage: "update [--check|--install] [--json] [--strict] [--version <semver>]",
221
221
  loader: async () => {
222
222
  const m = await import("../commands/update");
223
223
  return args => m.runUpdateCommand(args);
@@ -5,6 +5,7 @@ import { type StoredOAuth } from "../agent/state";
5
5
  import { createInterface } from "node:readline/promises";
6
6
  import { readGlobalConfig } from "../agent/state";
7
7
  import { jeoEnv } from "../util/env";
8
+ import { listProviderModels, CODEX_MODELS_URL } from "../ai/model-discovery";
8
9
  import {
9
10
  OAUTH_FLOWS,
10
11
  OAUTH_FLOW_REGISTRY,
@@ -121,6 +122,7 @@ async function runAuthLogin(rest: string[]): Promise<void> {
121
122
  rl.close();
122
123
  await loginOAuth(chosen, manualToken.trim());
123
124
  console.log(`[SUCCESS] Stored manual OAuth bearer for ${chosen} (no auto-refresh).`);
125
+ if (chosen === "openai") await reportOpenAiCodexModels();
124
126
  return;
125
127
  }
126
128
 
@@ -128,6 +130,7 @@ async function runAuthLogin(rest: string[]): Promise<void> {
128
130
  const { email } = await interactiveOAuthLogin(chosen, rl);
129
131
  console.log(`\n[SUCCESS] OAuth login complete for ${chosen}${email ? ` (${email})` : ""}.`);
130
132
  console.log("Stored access + refresh tokens in ~/.jeo/config.json; jeo will auto-refresh on expiry.");
133
+ if (chosen === "openai") await reportOpenAiCodexModels();
131
134
  } catch (err) {
132
135
  console.log(`\n[FAILED] ${(err as Error).message}`);
133
136
  console.log("Tip: paste the redirect URL when prompted, or use 'jeo auth login <provider> --token <bearer>'.");
@@ -137,6 +140,27 @@ async function runAuthLogin(rest: string[]): Promise<void> {
137
140
  }
138
141
  }
139
142
 
143
+ /**
144
+ * Best-effort, timeout-bounded live Codex model check run immediately after a
145
+ * successful `jeo auth login openai`. Uses `preferOAuth` so the report reflects
146
+ * the account that was JUST logged in — never the catalog fallback, and never
147
+ * silently swapped to an unrelated pre-existing `providers.openai` API key.
148
+ * A failure here is NEVER fatal: login has already succeeded and is not rolled
149
+ * back by this check. Prints only model ids/count — no tokens/secrets.
150
+ */
151
+ async function reportOpenAiCodexModels(): Promise<void> {
152
+ try {
153
+ const result = await listProviderModels("openai", { preferOAuth: true });
154
+ if (result.ok) {
155
+ console.log(`Live Codex models (${result.models.length}): ${result.models.join(", ")}`);
156
+ } else {
157
+ console.log(`[WARN] Could not verify live Codex models from ${CODEX_MODELS_URL}: ${result.error ?? "unknown error"} (non-fatal — login succeeded).`);
158
+ }
159
+ } catch (err) {
160
+ console.log(`[WARN] Could not verify live Codex models from ${CODEX_MODELS_URL}: ${(err as Error).message} (non-fatal — login succeeded).`);
161
+ }
162
+ }
163
+
140
164
  async function runAuthImport(rest: string[]): Promise<void> {
141
165
  const provider = rest.find(a => a !== "--import") as AuthProvider | undefined;
142
166
  if (!provider) {
@@ -120,15 +120,42 @@ export async function runUpdateCommandWith(args: string[], deps: UpdateDeps): Pr
120
120
  const hasJson = args.includes("--json");
121
121
  const hasStrict = args.includes("--strict");
122
122
 
123
- const KNOWN_FLAGS = new Set(["--check", "--install", "--json", "--strict", "-h", "--help"]);
123
+ const KNOWN_FLAGS = new Set(["--check", "--install", "--json", "--strict", "--version", "-h", "--help"]);
124
+ // A semver-ish shape: MAJOR.MINOR.PATCH with optional -prerelease and/or +build metadata,
125
+ // matching what the npm registry actually publishes (and what activeVersion() parses back out).
126
+ const SEMVER_RE = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/;
124
127
 
125
128
  if (isHelp) {
126
129
  printUsage();
127
130
  return;
128
131
  }
129
132
 
130
- // Check for unknown flags
131
- for (const arg of args) {
133
+ // `--version <semver>` pins the exact npm release instead of always resolving `/latest`
134
+ // (the recurrence-prevention escape hatch: pin a known-good release, or roll back off a
135
+ // bad one). It consumes the following arg as its value, so unknown-flag scanning below
136
+ // must skip that value instead of flagging it.
137
+ const versionFlagIdx = args.indexOf("--version");
138
+ let pinnedVersion: string | undefined;
139
+ let pinnedVersionError: string | null = null;
140
+ if (versionFlagIdx !== -1) {
141
+ const value = args[versionFlagIdx + 1];
142
+ if (value === undefined || value.startsWith("--")) {
143
+ pinnedVersionError = "--version requires a semver value, e.g. --version 1.2.3";
144
+ } else if (!SEMVER_RE.test(value)) {
145
+ pinnedVersionError = `--version value "${value}" is not a valid semver (expected e.g. 1.2.3)`;
146
+ } else {
147
+ pinnedVersion = value;
148
+ }
149
+ }
150
+
151
+ // Check for unknown flags (skipping --version's consumed value, valid or not — it was
152
+ // already validated above, not a flag to re-check here).
153
+ for (let i = 0; i < args.length; i++) {
154
+ const arg = args[i];
155
+ if (arg === "--version") {
156
+ i++;
157
+ continue;
158
+ }
132
159
  if (!KNOWN_FLAGS.has(arg)) {
133
160
  console.log(`Unknown flag: ${arg}`);
134
161
  printUsage();
@@ -138,18 +165,45 @@ export async function runUpdateCommandWith(args: string[], deps: UpdateDeps): Pr
138
165
  }
139
166
 
140
167
  const current = deps.localVersion();
168
+
169
+ if (pinnedVersionError) {
170
+ if (hasJson) {
171
+ console.log(JSON.stringify({
172
+ current,
173
+ latest: null,
174
+ upToDate: false,
175
+ error: pinnedVersionError
176
+ }));
177
+ } else {
178
+ console.error(`Error: ${pinnedVersionError}`);
179
+ }
180
+ process.exitCode = 1;
181
+ return;
182
+ }
183
+
141
184
  let latest: string | null = null;
142
185
  let upToDate = false;
143
186
 
187
+ const registryUrl = pinnedVersion
188
+ ? `https://registry.npmjs.org/jeo-code/${encodeURIComponent(pinnedVersion)}`
189
+ : "https://registry.npmjs.org/jeo-code/latest";
190
+
144
191
  try {
145
192
  const signal = AbortSignal.timeout(10000);
146
- const data = await deps.fetchJson("https://registry.npmjs.org/jeo-code/latest", { signal });
193
+ const data = await deps.fetchJson(registryUrl, { signal });
147
194
  if (!data || typeof data.version !== "string") {
148
195
  throw new Error("Invalid response format from npm registry");
149
196
  }
197
+ if (pinnedVersion && data.version !== pinnedVersion) {
198
+ // Defensive: the registry must echo back exactly the pinned version we asked for —
199
+ // never silently install something else because of a registry quirk.
200
+ throw new Error(`Registry returned version ${data.version}, expected pinned ${pinnedVersion}`);
201
+ }
150
202
  latest = data.version as string;
151
203
  const cmp = compareVersions(current, latest);
152
- upToDate = cmp >= 0;
204
+ // Choice: pinning is an exact-match target (rollback-capable) — only skip the install
205
+ // when current already equals the pin. Latest-mode keeps its "don't downgrade" rule.
206
+ upToDate = pinnedVersion ? cmp === 0 : cmp >= 0;
153
207
  } catch (err: any) {
154
208
  const is404 = err.status === 404 || String(err.message).includes("404") || String(err.message).toLowerCase().includes("not found");
155
209
  if (is404) {
@@ -157,28 +211,36 @@ export async function runUpdateCommandWith(args: string[], deps: UpdateDeps): Pr
157
211
  console.log(JSON.stringify({
158
212
  current,
159
213
  latest: null,
160
- upToDate: true,
161
- error: "Package not found on registry"
214
+ upToDate: pinnedVersion ? false : true,
215
+ error: pinnedVersion ? `Version ${pinnedVersion} not found on registry` : "Package not found on registry"
162
216
  }));
163
217
  } else {
164
- console.log("Package not found on registry: jeo-code");
218
+ console.log(pinnedVersion ? `Version ${pinnedVersion} not found on registry: jeo-code` : "Package not found on registry: jeo-code");
219
+ }
220
+ // A pinned version that doesn't exist on the registry is a real, actionable error —
221
+ // unlike the bare-latest 404 case, this always exits nonzero regardless of --strict.
222
+ if (pinnedVersion) {
223
+ process.exitCode = 1;
165
224
  }
166
225
  return;
167
226
  } else {
168
227
  // Network failure
169
228
  const errMsg = err.message || String(err);
229
+ const contextMsg = pinnedVersion
230
+ ? `Network failure while resolving pinned version ${pinnedVersion}: ${errMsg}`
231
+ : `Network failure: ${errMsg}`;
170
232
  if (hasJson) {
171
233
  console.log(JSON.stringify({
172
234
  current,
173
235
  latest: null,
174
236
  upToDate: false,
175
- error: `Network failure: ${errMsg}`
237
+ error: contextMsg
176
238
  }));
177
239
  } else {
178
240
  if (hasStrict) {
179
- console.error(`Error: Network failure: ${errMsg}`);
241
+ console.error(`Error: ${contextMsg}`);
180
242
  } else {
181
- console.warn(`Warning: Network failure: ${errMsg}`);
243
+ console.warn(`Warning: ${contextMsg}`);
182
244
  }
183
245
  }
184
246
  if (hasStrict) {
@@ -204,11 +266,15 @@ export async function runUpdateCommandWith(args: string[], deps: UpdateDeps): Pr
204
266
  installed: false
205
267
  }));
206
268
  } else {
207
- console.log(`jeo-code is already up-to-date (${current}).`);
269
+ console.log(pinnedVersion
270
+ ? `jeo-code is already at the pinned version (${current}).`
271
+ : `jeo-code is already up-to-date (${current}).`);
208
272
  }
209
273
  } else {
210
274
  if (!hasJson) {
211
- console.log(`Installing update: ${current} -> ${latest}...`);
275
+ console.log(pinnedVersion
276
+ ? `Installing pinned version: ${current} -> ${latest}...`
277
+ : `Installing update: ${current} -> ${latest}...`);
212
278
  }
213
279
  try {
214
280
  const result = await deps.install(latest ?? undefined);
@@ -294,7 +360,9 @@ export async function runUpdateCommandWith(args: string[], deps: UpdateDeps): Pr
294
360
  upToDate: true
295
361
  }));
296
362
  } else {
297
- console.log(`jeo-code is up-to-date (${current}).`);
363
+ console.log(pinnedVersion
364
+ ? `jeo-code is already at the pinned version (${current}).`
365
+ : `jeo-code is up-to-date (${current}).`);
298
366
  }
299
367
  } else {
300
368
  if (hasJson) {
@@ -304,8 +372,12 @@ export async function runUpdateCommandWith(args: string[], deps: UpdateDeps): Pr
304
372
  upToDate: false
305
373
  }));
306
374
  } else {
307
- console.log(`Newer version available: ${latest} (current: ${current}).`);
308
- console.log("Run 'jeo update' to install it.");
375
+ console.log(pinnedVersion
376
+ ? `Pinned version ${latest} differs from current (${current}).`
377
+ : `Newer version available: ${latest} (current: ${current}).`);
378
+ console.log(pinnedVersion
379
+ ? `Run 'jeo update --version ${latest}' to install it.`
380
+ : "Run 'jeo update' to install it.");
309
381
  }
310
382
  }
311
383
  }
@@ -320,6 +392,8 @@ function printUsage() {
320
392
  console.log(" (default) Check and install if a newer version is available");
321
393
  console.log(" --check Only check; do not install");
322
394
  console.log(" --install Force install if newer (also used with --json)");
395
+ console.log(" --version <semver> Pin an exact npm release instead of resolving latest");
396
+ console.log(" (e.g. --version 1.2.3); combine with --install to apply it");
323
397
  console.log(" --json Output result in JSON format");
324
398
  console.log(" --strict Exit with code 1 on network/registry errors");
325
399
  console.log(" -h, --help Show this help message");