jeo-code 0.9.3 → 0.9.5
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 +26 -0
- package/README.ja.md +2 -2
- package/README.ko.md +2 -2
- package/README.md +2 -2
- package/README.zh.md +2 -2
- package/package.json +1 -1
- package/src/ai/model-discovery.ts +13 -2
- package/src/cli/runner.ts +1 -1
- package/src/commands/auth.ts +24 -0
- package/src/commands/update.ts +90 -16
- package/src/tui/renderer.ts +9 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,32 @@ The README mirrors the latest 5 entries — regenerate with `bun run changelog:s
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.9.5] - 2026-07-27
|
|
12
|
+
_Resize-safe live TUI rendering for terminal viewport shrink/grow transitions._
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **TUI viewport shrink anchoring** (`src/tui/renderer.ts`) — bounds differential row walks and cursor movement to the visible terminal height, preventing clipped frames from moving the repaint anchor past the bottom margin and corrupting subsequent footer/input rendering.
|
|
16
|
+
|
|
17
|
+
### Verified
|
|
18
|
+
- Renderer resize suite: 24 pass / 0 fail, including a viewport shrink below the previous frame and recovery after growth.
|
|
19
|
+
- LaunchTui resize integration: real renderer output remains scroll-free and cursor-bounded during a live 30-row → 6-row transition.
|
|
20
|
+
- `bun run typecheck`: clean.
|
|
21
|
+
|
|
22
|
+
## [0.9.4] - 2026-07-27
|
|
23
|
+
_Real OpenAI/Codex model discovery after login, npm package-content hardening, and pinned self-update support._
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- **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.
|
|
27
|
+
- **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`.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- **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.
|
|
31
|
+
|
|
32
|
+
### Verified
|
|
33
|
+
- Release-focused suite: 148 pass / 0 fail across model discovery, update, package workflow, Codex responses, model manager, and catalog tests.
|
|
34
|
+
- `bun run typecheck`: clean.
|
|
35
|
+
- Clean-worktree package manifest excludes the previously leaked monitor/playwriter files.
|
|
36
|
+
|
|
11
37
|
## [0.9.3] - 2026-07-26
|
|
12
38
|
_Released Telegram integration reliability improvements adapted from GJC v0.11.10, with safe jeo-native behavior and live verification._
|
|
13
39
|
|
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.5]** (2026-07-27) — Resize-safe live TUI rendering for terminal viewport shrink/grow transitions.
|
|
289
|
+
- **[0.9.4]** (2026-07-27) — Real OpenAI/Codex model discovery after login, npm package-content hardening, and pinned self-update support.
|
|
288
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.
|
|
289
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.
|
|
290
|
-
- **[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.5]** (2026-07-27) — Resize-safe live TUI rendering for terminal viewport shrink/grow transitions.
|
|
290
|
+
- **[0.9.4]** (2026-07-27) — Real OpenAI/Codex model discovery after login, npm package-content hardening, and pinned self-update support.
|
|
289
291
|
- **[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
292
|
- **[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
|
-
- **[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.5]** (2026-07-27) — Resize-safe live TUI rendering for terminal viewport shrink/grow transitions.
|
|
297
|
+
- **[0.9.4]** (2026-07-27) — Real OpenAI/Codex model discovery after login, npm package-content hardening, and pinned self-update support.
|
|
296
298
|
- **[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
299
|
- **[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
|
-
- **[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.5]** (2026-07-27) — Resize-safe live TUI rendering for terminal viewport shrink/grow transitions.
|
|
289
|
+
- **[0.9.4]** (2026-07-27) — Real OpenAI/Codex model discovery after login, npm package-content hardening, and pinned self-update support.
|
|
288
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.
|
|
289
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.
|
|
290
|
-
- **[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
|
@@ -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
|
-
|
|
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);
|
package/src/commands/auth.ts
CHANGED
|
@@ -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) {
|
package/src/commands/update.ts
CHANGED
|
@@ -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
|
-
//
|
|
131
|
-
|
|
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(
|
|
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
|
-
|
|
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:
|
|
237
|
+
error: contextMsg
|
|
176
238
|
}));
|
|
177
239
|
} else {
|
|
178
240
|
if (hasStrict) {
|
|
179
|
-
console.error(`Error:
|
|
241
|
+
console.error(`Error: ${contextMsg}`);
|
|
180
242
|
} else {
|
|
181
|
-
console.warn(`Warning:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
308
|
-
|
|
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");
|
package/src/tui/renderer.ts
CHANGED
|
@@ -77,12 +77,15 @@ export class Renderer {
|
|
|
77
77
|
this.prevRows = currentRows;
|
|
78
78
|
|
|
79
79
|
const next = lines.map(line => truncate(line, currentCols));
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
// A terminal can shrink below the number of rows occupied by the prior frame.
|
|
81
|
+
// Rows beyond the new viewport are not addressable: cursor-down stops at the
|
|
82
|
+
// bottom margin and a later cursor-up would then underflow the anchor, leaving
|
|
83
|
+
// every following diff one or more rows out of phase. Limit both the physical
|
|
84
|
+
// occupancy and diff walk to visible rows; the next resize/grow repaints from
|
|
85
|
+
// the bounded baseline at the restored geometry.
|
|
86
|
+
const viewportRows = Math.max(1, currentRows);
|
|
87
|
+
const occupied = Math.min(Math.max(this.prev.length, this.coverRows), viewportRows);
|
|
88
|
+
const maxLen = Math.min(Math.max(this.prev.length, next.length, this.coverRows), viewportRows);
|
|
86
89
|
this.coverRows = 0;
|
|
87
90
|
let cursorRow = 0;
|
|
88
91
|
let out = "";
|