jeo-code 0.8.32 → 0.8.34

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
@@ -6,6 +6,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  The README mirrors the latest 5 entries — regenerate with `bun run changelog:sync`.
8
8
 
9
+ ## [0.8.34] - 2026-07-21
10
+ _Provider/model-catalog sync against gjc's actual current source (a shallow clone of the real, public `Yeachan-Heo/gajae-code` repo at v0.11.5 — direct `packages/ai/src/models.json` comparison, not inference from release notes). The system prompt's philosophy, the `/route` command, and the `tencent` provider were explicitly out of scope and are unchanged._
11
+
12
+ ### Added
13
+ - **`deepinfra` provider** (`src/ai/providers/openai-compatible-catalog.ts`) — `https://api.deepinfra.com/v1/openai`, `DEEPINFRA_API_KEY`, default `deepinfra/deepseek-ai/DeepSeek-V3.2`, plus known-model picker fallbacks (DeepSeek-R1-0528, Llama-3.3-70B-Instruct-Turbo, Kimi-K2.6, Qwen3-Coder-480B, gpt-oss-120b) — a real, popular inference cloud gjc's catalog has that jeo's didn't; one data-driven catalog row, auto-registered (no new adapter code, per this file's own "add a provider = one table row" design).
14
+ - **`litellm` provider** — `http://localhost:4000/v1` (LiteLLM's own documented local-proxy default, expected to be overridden via `jeo provider add litellm --base-url <your-proxy>`), `LITELLM_API_KEY`. Deliberately has no `knownModels` list — unlike every other row, LiteLLM is a self-hosted proxy whose model catalog is entirely user-defined (whatever their own `config.yaml` routes to), so there is no globally-fixed list to offer.
15
+ - `src/ai/types.ts`'s `ProviderName` and `src/auth/storage.ts`'s `AuthProvider`/`API_KEY_ONLY_PROVIDERS` gained both new names; `test/deepinfra-litellm-provider.test.ts` (+5 tests: registration, env var, base URL, routing, credential-gated readiness).
16
+
17
+ ### Scope notes (why the rest of gjc's current provider list wasn't ported wholesale)
18
+ - **`tencent` — explicitly kept**, per instruction, even though gjc's current `models.json` has no such entry at all (jeo's Tencent Cloud MaaS integration predates and is independent of gjc's own catalog).
19
+ - **`moonshot`** and **`firepass`** are gjc's OWN renamed/split entries for base URLs jeo already serves under different names — `moonshot`'s `https://api.moonshot.ai/v1` is jeo's existing `kimi` provider's base URL (jeo intentionally keeps ONE `kimi` provider spanning both the OAuth Kimi-Code subscription and the plain API-key path, rather than gjc's two separate provider identifiers); `firepass`'s `https://api.fireworks.ai/inference/v1` is byte-identical to jeo's existing `fireworks` entry. Already at parity, no action needed.
20
+ - **`opencode` / `opencode-go` / `opencode-zen`** — these are the competing `opencode` CLI's own hosted proxy/gateway products; a jeo user would need an opencode account to use them, and integrating a rival agent's own product as an upstream provider has little practical value for jeo's userbase. Not ported.
21
+ - **`azure-openai`, `amazon-bedrock`, `google-vertex`, `cursor`, `github-copilot`, `gitlab-duo`** — each requires a fundamentally new, non-trivial auth/wire-protocol adapter (Azure resource/deployment config, AWS SigV4 request signing, GCP OAuth + region/project config, Cursor's proprietary undocumented agent API, a GitHub Copilot-specific device-auth flow, GitLab OAuth) — none fit the existing "OpenAI-compatible base URL + API key" pattern every other row here uses; each is a real, separate feature in its own right, not a catalog row. Not ported in this pass to avoid fabricating auth flows without live verification.
22
+ - **`cloudflare-ai-gateway` / `vercel-ai-gateway`** — both require a per-account/per-gateway URL embedded in the base (`.../v1/<account>/<gateway>/...`), which isn't a good fit for a fixed catalog row; already achievable today via jeo's existing `jeo provider add --base-url` custom-endpoint mechanism.
23
+ - **`ollama-cloud`** — `src/ai/providers/ollama.ts`'s adapter never sends an `Authorization` header (by design, for the keyless local daemon); serving the hosted `https://ollama.com` cloud variant needs that adapter to accept a bearer credential, a real (if modest) code change beyond a catalog row. Deferred as a follow-up rather than rushed.
24
+ - System prompt philosophy (`src/prompts/`) and the `/route` command (`src/commands/launch/route-slash.ts`, `src/agent/route-history.ts`) were not touched — out of scope per the request.
25
+
26
+ ### Verified
27
+ - `test/deepinfra-litellm-provider.test.ts` — 5 pass (new).
28
+ - `bun run typecheck` clean; full `bun test` 3102 pass / 0 fail across 305 files (an initial run under heavy concurrent system load hit many unrelated timeouts across subsystems this pass never touched — browser, daemon, tokenizer, tmux, search-gitignore — all confirmed pre-existing/load-induced via isolated re-runs, then a clean full re-run came back 0 fail).
29
+
30
+ ## [0.8.33] - 2026-07-20
31
+ _Real terminal-resize testing (repeated tmux resize-window + keystroke reproductions, byte-for-byte ANSI replay outside jeo/tmux to isolate root causes deterministically) uncovered and closed a progressive screen-corruption bug: resizing down to a narrow terminal (e.g. a ~20-column tmux pane) produced a growing stack of duplicate status-bar lines on every subsequent keystroke, and a separate, independently-reproducible scroll bug in the mid-turn live renderer._
32
+
33
+ ### Fixed
34
+ - **Narrow-terminal overflow across three width-floor call sites, the actual root cause of the live-reproduced corruption** — `src/tui/components/status.ts`'s `renderStatusBar` and its internal `leftBudget`, and `src/tui/components/input-box.ts`'s `renderInputFrame`, each clamped their `cols` UP to a fixed floor (24, 24, 8) regardless of the REAL terminal width, so a genuinely narrow terminal (e.g. 20 columns) still received a 24-column-wide status bar / input box. A real terminal hard-wraps that overflow, which desynced the idle-prompt footer's own row bookkeeping on every subsequent repaint — reproduced live via tmux (`resize-window` down + repeated keystrokes) as a stack of duplicate `⬢ model … ▦ ctx%` lines that grew by one on every keystroke, and confirmed via a byte-for-byte replay of the exact captured ANSI sequences through `cat` into a fresh tmux pane with jeo entirely absent — proving the bug lived in the emitted bytes themselves, not timing or readline interaction. All three floors now clamp to a minimum of 1, degrading gracefully (a small, correctly-sized box/bar) instead of overflowing.
35
+ - **`src/tui/components/welcome.ts`'s narrow (`cols < 30`) one-line fallback no longer overflows** — an unbounded `jeo v{version} · {model}` line previously overflowed a real narrow terminal by up to 47 columns with a realistic provider-qualified model id (e.g. `antigravity/claude-sonnet-4-6 (antigravity)`). Now truncated to `cols - 1` — the SAME "leave the last column free" margin the boxed banner already uses elsewhere in this file, deliberately NOT `cols` exactly: a line filled to the exact terminal width followed by its own trailing newline is ambiguous to real terminals (pending-autowrap vs. explicit-LF), which independently reproduced the same duplicate-line corruption even after the overflow itself was fixed.
36
+ - **`src/tui/renderer.ts`'s reserve-mode resize path no longer scrolls the terminal on a shrink** — `Renderer.render()`'s resize branch called `clear()` unconditionally, which zeroes `coverRows`/`prev` as an independent write; the SAME call's own reserve-block then read that now-wrong occupancy (0) and spuriously inserted real `"\n"` characters even when the post-resize frame was SHORTER than the pre-resize one. Literal `"\n"` genuinely scrolls a real terminal, corrupting whatever sits above the live frame and permanently desyncing the renderer's row math. Reserve mode now uses the same `reset()`-style bookkeeping the periodic self-heal resync already relies on (remembers the occupied rows via `coverRows`, no output) instead of an immediate `clear()`; non-reserve (alt-screen/pipe) mode is unaffected and keeps its defensive full clear. Reproduced deterministically with a standalone `Renderer` unit harness (no tmux/timing involved) before and after the fix.
37
+
38
+ ### Verified
39
+ - `test/status-bar.test.ts`, `test/input-box.test.ts` — new regression tests: neither ever emits a line wider than a realistically narrow `cols` (5 through 23), closing the exact floor-overflow class found live.
40
+ - `test/tui-welcome.test.ts` — updated + new tests: the narrow fallback is now `cols - 1`-capped (never overflows, even with a long provider-qualified model id).
41
+ - `test/tui-renderer.test.ts` — new regression test: a reserve-mode resize down to a shorter frame never emits `"\n"`, and a second render right after stays clean (no lingering desync).
42
+ - Full live tmux reproduction cycle for BOTH the idle-prompt path and the mid-turn `LaunchTui` path: resized a real `jeo --tmux` session down (e.g. 90×26 → 55×18, 80×24 → 20×10) and typed multiple keystrokes after each — before this pass, the idle-prompt footer visibly duplicated once per keystroke; after, it stays clean across repeated keystrokes at both a moderate (55×18) and an extreme (20×10) narrow width, confirmed via direct pane capture, not just unit assertions.
43
+ - Full `bun test` — 3097 pass / 0 fail across 304 files.
44
+ - `bun run typecheck` — no errors.
45
+
9
46
  ## [0.8.32] - 2026-07-20
10
47
  _gjc parity: TUI display-width/wrapping is now grapheme-cluster-aware for emoji sequences (VS16 presentation, skin-tone modifiers, keycaps, ZWJ-joined emoji like family/profession glyphs), fixing box-border and wrap misalignment ("깨짐") that the old per-code-point width summation produced whenever such a sequence appeared in a message — Korean/CJK-only text was already correctly handled and is unaffected._
11
48
 
package/README.ja.md CHANGED
@@ -283,11 +283,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
283
283
  ## 変更履歴 (Changelog)
284
284
 
285
285
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
286
+ - **[0.8.34]** (2026-07-21) — Provider/model-catalog sync against gjc's actual current source (a shallow clone of the real, public `Yeachan-Heo/gajae-code` repo at v0.11.5 — direct `packages/ai/src/models.json` comparison, not inference from release notes). The system prompt's philosophy, the `/route` command, and the `tencent` provider were explicitly out of scope and are unchanged.
287
+ - **[0.8.33]** (2026-07-20) — Real terminal-resize testing (repeated tmux resize-window + keystroke reproductions, byte-for-byte ANSI replay outside jeo/tmux to isolate root causes deterministically) uncovered and closed a progressive screen-corruption bug: resizing down to a narrow terminal (e.g. a ~20-column tmux pane) produced a growing stack of duplicate status-bar lines on every subsequent keystroke, and a separate, independently-reproducible scroll bug in the mid-turn live renderer.
286
288
  - **[0.8.32]** (2026-07-20) — gjc parity: TUI display-width/wrapping is now grapheme-cluster-aware for emoji sequences (VS16 presentation, skin-tone modifiers, keycaps, ZWJ-joined emoji like family/profession glyphs), fixing box-border and wrap misalignment ("깨짐") that the old per-code-point width summation produced whenever such a sequence appeared in a message — Korean/CJK-only text was already correctly handled and is unaffected.
287
289
  - **[0.8.31]** (2026-07-20) — `web_search` no longer silently degrades every non-Anthropic model to keyless DuckDuckGo scraping — OpenAI and Gemini sessions now get their own native, hosted search tool (matching the same active-model-gated, credential-required design Anthropic already had), with DuckDuckGo remaining the always-on terminal fallback for everyone else.
288
290
  - **[0.8.30]** (2026-07-20) — `bun run build` (the host-only dev binary build) has been silently broken for every user who ran it — a drift between the plain `package.json` `build` script and the actual working release-binary build command (`scripts/ci-release-build-binaries.ts`), which had already documented and fixed the exact same crash.
289
- - **[0.8.29]** (2026-07-20) — gajae-code (gjc) v0.10.2→v0.11.4 gap analysis: most of that range is gjc-specific architecture jeo intentionally does not replicate (SDK broker/session-index recovery, Gajae Pet, coordinator-mcp, Telegram/Discord rich-delivery internals, browser tab workers, worktree-subcommand removal, RPC durable model selection) — one genuinely applicable TUI bug was found and closed; the CLI empty-non-TTY-stdin fix (gjc #2586) was investigated and found already-correct by construction (jeo's one-shot gate forces true whenever stdin isn't a TTY, regardless of args, so the hang gjc fixed cannot occur here).
290
- - **[0.8.28]** (2026-07-16) — Improved the welcome UI's right-side table to display dynamic sections (What's New, Flow keys, Project pulse, and Session trail) matching gajae-code (gjc) >= 0.8.0 features.
291
291
 
292
292
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
293
293
  <!-- CHANGELOG:END -->
package/README.ko.md CHANGED
@@ -284,11 +284,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
284
284
  ## 변경 이력 (Changelog)
285
285
 
286
286
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
287
+ - **[0.8.34]** (2026-07-21) — Provider/model-catalog sync against gjc's actual current source (a shallow clone of the real, public `Yeachan-Heo/gajae-code` repo at v0.11.5 — direct `packages/ai/src/models.json` comparison, not inference from release notes). The system prompt's philosophy, the `/route` command, and the `tencent` provider were explicitly out of scope and are unchanged.
288
+ - **[0.8.33]** (2026-07-20) — Real terminal-resize testing (repeated tmux resize-window + keystroke reproductions, byte-for-byte ANSI replay outside jeo/tmux to isolate root causes deterministically) uncovered and closed a progressive screen-corruption bug: resizing down to a narrow terminal (e.g. a ~20-column tmux pane) produced a growing stack of duplicate status-bar lines on every subsequent keystroke, and a separate, independently-reproducible scroll bug in the mid-turn live renderer.
287
289
  - **[0.8.32]** (2026-07-20) — gjc parity: TUI display-width/wrapping is now grapheme-cluster-aware for emoji sequences (VS16 presentation, skin-tone modifiers, keycaps, ZWJ-joined emoji like family/profession glyphs), fixing box-border and wrap misalignment ("깨짐") that the old per-code-point width summation produced whenever such a sequence appeared in a message — Korean/CJK-only text was already correctly handled and is unaffected.
288
290
  - **[0.8.31]** (2026-07-20) — `web_search` no longer silently degrades every non-Anthropic model to keyless DuckDuckGo scraping — OpenAI and Gemini sessions now get their own native, hosted search tool (matching the same active-model-gated, credential-required design Anthropic already had), with DuckDuckGo remaining the always-on terminal fallback for everyone else.
289
291
  - **[0.8.30]** (2026-07-20) — `bun run build` (the host-only dev binary build) has been silently broken for every user who ran it — a drift between the plain `package.json` `build` script and the actual working release-binary build command (`scripts/ci-release-build-binaries.ts`), which had already documented and fixed the exact same crash.
290
- - **[0.8.29]** (2026-07-20) — gajae-code (gjc) v0.10.2→v0.11.4 gap analysis: most of that range is gjc-specific architecture jeo intentionally does not replicate (SDK broker/session-index recovery, Gajae Pet, coordinator-mcp, Telegram/Discord rich-delivery internals, browser tab workers, worktree-subcommand removal, RPC durable model selection) — one genuinely applicable TUI bug was found and closed; the CLI empty-non-TTY-stdin fix (gjc #2586) was investigated and found already-correct by construction (jeo's one-shot gate forces true whenever stdin isn't a TTY, regardless of args, so the hang gjc fixed cannot occur here).
291
- - **[0.8.28]** (2026-07-16) — Improved the welcome UI's right-side table to display dynamic sections (What's New, Flow keys, Project pulse, and Session trail) matching gajae-code (gjc) >= 0.8.0 features.
292
292
 
293
293
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
294
294
  <!-- CHANGELOG:END -->
package/README.md CHANGED
@@ -291,11 +291,11 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
291
291
  ## Changelog
292
292
 
293
293
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
294
+ - **[0.8.34]** (2026-07-21) — Provider/model-catalog sync against gjc's actual current source (a shallow clone of the real, public `Yeachan-Heo/gajae-code` repo at v0.11.5 — direct `packages/ai/src/models.json` comparison, not inference from release notes). The system prompt's philosophy, the `/route` command, and the `tencent` provider were explicitly out of scope and are unchanged.
295
+ - **[0.8.33]** (2026-07-20) — Real terminal-resize testing (repeated tmux resize-window + keystroke reproductions, byte-for-byte ANSI replay outside jeo/tmux to isolate root causes deterministically) uncovered and closed a progressive screen-corruption bug: resizing down to a narrow terminal (e.g. a ~20-column tmux pane) produced a growing stack of duplicate status-bar lines on every subsequent keystroke, and a separate, independently-reproducible scroll bug in the mid-turn live renderer.
294
296
  - **[0.8.32]** (2026-07-20) — gjc parity: TUI display-width/wrapping is now grapheme-cluster-aware for emoji sequences (VS16 presentation, skin-tone modifiers, keycaps, ZWJ-joined emoji like family/profession glyphs), fixing box-border and wrap misalignment ("깨짐") that the old per-code-point width summation produced whenever such a sequence appeared in a message — Korean/CJK-only text was already correctly handled and is unaffected.
295
297
  - **[0.8.31]** (2026-07-20) — `web_search` no longer silently degrades every non-Anthropic model to keyless DuckDuckGo scraping — OpenAI and Gemini sessions now get their own native, hosted search tool (matching the same active-model-gated, credential-required design Anthropic already had), with DuckDuckGo remaining the always-on terminal fallback for everyone else.
296
298
  - **[0.8.30]** (2026-07-20) — `bun run build` (the host-only dev binary build) has been silently broken for every user who ran it — a drift between the plain `package.json` `build` script and the actual working release-binary build command (`scripts/ci-release-build-binaries.ts`), which had already documented and fixed the exact same crash.
297
- - **[0.8.29]** (2026-07-20) — gajae-code (gjc) v0.10.2→v0.11.4 gap analysis: most of that range is gjc-specific architecture jeo intentionally does not replicate (SDK broker/session-index recovery, Gajae Pet, coordinator-mcp, Telegram/Discord rich-delivery internals, browser tab workers, worktree-subcommand removal, RPC durable model selection) — one genuinely applicable TUI bug was found and closed; the CLI empty-non-TTY-stdin fix (gjc #2586) was investigated and found already-correct by construction (jeo's one-shot gate forces true whenever stdin isn't a TTY, regardless of args, so the hang gjc fixed cannot occur here).
298
- - **[0.8.28]** (2026-07-16) — Improved the welcome UI's right-side table to display dynamic sections (What's New, Flow keys, Project pulse, and Session trail) matching gajae-code (gjc) >= 0.8.0 features.
299
299
 
300
300
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
301
301
  <!-- CHANGELOG:END -->
package/README.zh.md CHANGED
@@ -283,11 +283,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
283
283
  ## 更新日志 (Changelog)
284
284
 
285
285
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
286
+ - **[0.8.34]** (2026-07-21) — Provider/model-catalog sync against gjc's actual current source (a shallow clone of the real, public `Yeachan-Heo/gajae-code` repo at v0.11.5 — direct `packages/ai/src/models.json` comparison, not inference from release notes). The system prompt's philosophy, the `/route` command, and the `tencent` provider were explicitly out of scope and are unchanged.
287
+ - **[0.8.33]** (2026-07-20) — Real terminal-resize testing (repeated tmux resize-window + keystroke reproductions, byte-for-byte ANSI replay outside jeo/tmux to isolate root causes deterministically) uncovered and closed a progressive screen-corruption bug: resizing down to a narrow terminal (e.g. a ~20-column tmux pane) produced a growing stack of duplicate status-bar lines on every subsequent keystroke, and a separate, independently-reproducible scroll bug in the mid-turn live renderer.
286
288
  - **[0.8.32]** (2026-07-20) — gjc parity: TUI display-width/wrapping is now grapheme-cluster-aware for emoji sequences (VS16 presentation, skin-tone modifiers, keycaps, ZWJ-joined emoji like family/profession glyphs), fixing box-border and wrap misalignment ("깨짐") that the old per-code-point width summation produced whenever such a sequence appeared in a message — Korean/CJK-only text was already correctly handled and is unaffected.
287
289
  - **[0.8.31]** (2026-07-20) — `web_search` no longer silently degrades every non-Anthropic model to keyless DuckDuckGo scraping — OpenAI and Gemini sessions now get their own native, hosted search tool (matching the same active-model-gated, credential-required design Anthropic already had), with DuckDuckGo remaining the always-on terminal fallback for everyone else.
288
290
  - **[0.8.30]** (2026-07-20) — `bun run build` (the host-only dev binary build) has been silently broken for every user who ran it — a drift between the plain `package.json` `build` script and the actual working release-binary build command (`scripts/ci-release-build-binaries.ts`), which had already documented and fixed the exact same crash.
289
- - **[0.8.29]** (2026-07-20) — gajae-code (gjc) v0.10.2→v0.11.4 gap analysis: most of that range is gjc-specific architecture jeo intentionally does not replicate (SDK broker/session-index recovery, Gajae Pet, coordinator-mcp, Telegram/Discord rich-delivery internals, browser tab workers, worktree-subcommand removal, RPC durable model selection) — one genuinely applicable TUI bug was found and closed; the CLI empty-non-TTY-stdin fix (gjc #2586) was investigated and found already-correct by construction (jeo's one-shot gate forces true whenever stdin isn't a TTY, regardless of args, so the hang gjc fixed cannot occur here).
290
- - **[0.8.28]** (2026-07-16) — Improved the welcome UI's right-side table to display dynamic sections (What's New, Flow keys, Project pulse, and Session trail) matching gajae-code (gjc) >= 0.8.0 features.
291
291
 
292
292
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
293
293
  <!-- CHANGELOG:END -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jeo-code",
3
- "version": "0.8.32",
3
+ "version": "0.8.34",
4
4
  "description": "Clean, highly optimized AI coding agent using spec-first loop",
5
5
  "type": "module",
6
6
  "main": "src/cli.ts",
@@ -59,6 +59,13 @@ export const OPENAI_COMPAT_PROVIDERS: readonly OpenAICompatProviderDef[] = [
59
59
  { name: "zenmux", label: "ZenMux", baseUrl: "https://zenmux.ai/api/v1", apiKeyEnv: "ZENMUX_API_KEY", defaultModel: "zenmux/anthropic/claude-opus-4.6" },
60
60
  { name: "qianfan", label: "Qianfan", baseUrl: "https://qianfan.baidubce.com/v2", apiKeyEnv: "QIANFAN_API_KEY", defaultModel: "qianfan/deepseek-v3.2" },
61
61
  { name: "xiaomi", label: "Xiaomi", baseUrl: "https://api.xiaomimimo.com/v1", apiKeyEnv: "XIAOMI_API_KEY", defaultModel: "xiaomi/mimo-v2-flash" },
62
+ { name: "deepinfra", label: "DeepInfra", baseUrl: "https://api.deepinfra.com/v1/openai", apiKeyEnv: "DEEPINFRA_API_KEY", defaultModel: "deepinfra/deepseek-ai/DeepSeek-V3.2", knownModels: ["deepseek-ai/DeepSeek-V3.2", "deepseek-ai/DeepSeek-R1-0528", "meta-llama/Llama-3.3-70B-Instruct-Turbo", "moonshotai/Kimi-K2.6", "Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo", "openai/gpt-oss-120b"] },
63
+ // Self-hosted OpenAI-compatible proxy (LiteLLM) — the model catalog behind it is
64
+ // entirely user-configured (whatever the user's own litellm config.yaml routes to),
65
+ // so unlike every other row here there is no globally-fixed `knownModels` list to
66
+ // offer; `baseUrl` is LiteLLM's own documented local-proxy default and is expected
67
+ // to be overridden via `jeo provider add litellm --base-url <your-proxy-url>`.
68
+ { name: "litellm", label: "LiteLLM", baseUrl: "http://localhost:4000/v1", apiKeyEnv: "LITELLM_API_KEY", defaultModel: "litellm/gpt-4o-mini" },
62
69
  { name: "xiaomi-token-plan-ams", label: "Xiaomi Token Plan (Europe)", baseUrl: "https://token-plan-ams.xiaomimimo.com/v1", apiKeyEnv: "XIAOMI_TOKEN_PLAN_AMS_API_KEY", defaultModel: "xiaomi-token-plan-ams/mimo-v2.5", subscription: true },
63
70
  { name: "xiaomi-token-plan-cn", label: "Xiaomi Token Plan (China)", baseUrl: "https://token-plan-cn.xiaomimimo.com/v1", apiKeyEnv: "XIAOMI_TOKEN_PLAN_CN_API_KEY", defaultModel: "xiaomi-token-plan-cn/mimo-v2.5", subscription: true },
64
71
  { name: "xiaomi-token-plan-sgp", label: "Xiaomi Token Plan (Singapore)", baseUrl: "https://token-plan-sgp.xiaomimimo.com/v1", apiKeyEnv: "XIAOMI_TOKEN_PLAN_SGP_API_KEY", defaultModel: "xiaomi-token-plan-sgp/mimo-v2.5", subscription: true },
package/src/ai/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Credential } from "../auth";
2
2
 
3
- export type ProviderName = "anthropic" | "openai" | "gemini" | "antigravity" | "ollama" | "lmstudio" | "xai" | "kimi" | "groq" | "deepseek" | "mistral" | "openrouter" | "together" | "cerebras" | "fireworks" | "nvidia" | "alibaba-coding-plan" | "huggingface" | "nanogpt" | "qwen-portal" | "synthetic" | "venice" | "zenmux" | "qianfan" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "minimax-code" | "minimax-code-cn" | "zai" | "minimax" | "tencent";
3
+ export type ProviderName = "anthropic" | "openai" | "gemini" | "antigravity" | "ollama" | "lmstudio" | "xai" | "kimi" | "groq" | "deepseek" | "mistral" | "openrouter" | "together" | "cerebras" | "fireworks" | "nvidia" | "alibaba-coding-plan" | "huggingface" | "nanogpt" | "qwen-portal" | "synthetic" | "venice" | "zenmux" | "qianfan" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "minimax-code" | "minimax-code-cn" | "zai" | "minimax" | "tencent" | "deepinfra" | "litellm";
4
4
 
5
5
 
6
6
  /** An image attached to a (user) message — base64 payload + IANA media type. */
@@ -8,12 +8,12 @@ import { jeoEnv } from "../util/env";
8
8
  /** Providers with an interactive OAuth login + refresh flow. */
9
9
  export type OAuthProvider = "anthropic" | "openai" | "gemini" | "antigravity" | "kimi";
10
10
  /** Every provider jeo resolves a credential for: OAuth-capable ∪ API-key-only. */
11
- export type AuthProvider = OAuthProvider | "xai" | "groq" | "deepseek" | "mistral" | "openrouter" | "together" | "cerebras" | "fireworks" | "nvidia" | "alibaba-coding-plan" | "huggingface" | "nanogpt" | "qwen-portal" | "synthetic" | "venice" | "zenmux" | "qianfan" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "minimax-code" | "minimax-code-cn" | "zai" | "minimax" | "tencent";
11
+ export type AuthProvider = OAuthProvider | "xai" | "groq" | "deepseek" | "mistral" | "openrouter" | "together" | "cerebras" | "fireworks" | "nvidia" | "alibaba-coding-plan" | "huggingface" | "nanogpt" | "qwen-portal" | "synthetic" | "venice" | "zenmux" | "qianfan" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "minimax-code" | "minimax-code-cn" | "zai" | "minimax" | "tencent" | "deepinfra" | "litellm";
12
12
 
13
13
 
14
14
  export const OAUTH_PROVIDERS: readonly OAuthProvider[] = ["anthropic", "openai", "gemini", "antigravity", "kimi"];
15
15
  /** API-key-only providers (no OAuth flow) — resolved from config.providers / `<NAME>_API_KEY`. */
16
- export const API_KEY_ONLY_PROVIDERS: readonly AuthProvider[] = ["xai", "groq", "deepseek", "mistral", "openrouter", "together", "cerebras", "fireworks", "nvidia", "alibaba-coding-plan", "huggingface", "nanogpt", "qwen-portal", "synthetic", "venice", "zenmux", "qianfan", "xiaomi", "xiaomi-token-plan-ams", "xiaomi-token-plan-cn", "xiaomi-token-plan-sgp", "minimax-code", "minimax-code-cn", "zai", "minimax", "tencent"];
16
+ export const API_KEY_ONLY_PROVIDERS: readonly AuthProvider[] = ["xai", "groq", "deepseek", "mistral", "openrouter", "together", "cerebras", "fireworks", "nvidia", "alibaba-coding-plan", "huggingface", "nanogpt", "qwen-portal", "synthetic", "venice", "zenmux", "qianfan", "xiaomi", "xiaomi-token-plan-ams", "xiaomi-token-plan-cn", "xiaomi-token-plan-sgp", "minimax-code", "minimax-code-cn", "zai", "minimax", "tencent", "deepinfra", "litellm"];
17
17
 
18
18
  /** Narrow an AuthProvider to the OAuth-capable subset (xai has no OAuth flow). */
19
19
  export function isOAuthProvider(p: AuthProvider): p is OAuthProvider {
@@ -764,7 +764,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
764
764
  * mid-turn TUI binding): full last reply + full last tool output. */
765
765
  const composeDetailLines = (): string[] => {
766
766
  if (!lastReply && !lastToolDetail) return [];
767
- const sep = "─".repeat(Math.min(48, Math.max(20, (process.stdout.columns ?? 80) - 1)));
767
+ const sep = "─".repeat(Math.min(48, Math.max(1, (process.stdout.columns ?? 80) - 1)));
768
768
  const toolDetail = lastToolDetail
769
769
  ? [sep, `detail · full last tool output (${lastToolDetail.tool})`, sep, ...lastToolDetail.output.split("\n").slice(0, 2000).map(sanitizeForTerminal)]
770
770
  : [];
@@ -816,7 +816,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
816
816
  // exact prior conversation instead of a bare message count.
817
817
  if (messages.length > 0) {
818
818
  resumedWithHistory = true;
819
- const sep = "─".repeat(Math.min(48, Math.max(20, (process.stdout.columns ?? 80) - 1)));
819
+ const sep = "─".repeat(Math.min(48, Math.max(1, (process.stdout.columns ?? 80) - 1)));
820
820
  console.log([sep, ...formatTranscript(history, { maxTurns: 6, color: true, unicode: true }), sep].join("\n"));
821
821
  }
822
822
 
@@ -880,7 +880,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
880
880
  // screen instead of just a bare message count.
881
881
  if (messages.length > 0) {
882
882
  resumedWithHistory = true;
883
- const sep = "─".repeat(Math.min(48, Math.max(20, (process.stdout.columns ?? 80) - 1)));
883
+ const sep = "─".repeat(Math.min(48, Math.max(1, (process.stdout.columns ?? 80) - 1)));
884
884
  console.log([sep, ...formatTranscript(history, { maxTurns: 6, color: true, unicode: true }), sep].join("\n"));
885
885
  }
886
886
 
@@ -2209,7 +2209,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
2209
2209
  // so it never repeats. Screen-safe: prints BEFORE the prompt is armed.
2210
2210
  try {
2211
2211
  const whatsNew = await consumeLaunchWhatsNew({
2212
- cols: Math.min(100, Math.max(40, (process.stdout.columns ?? 80) - 2)),
2212
+ cols: Math.min(100, Math.max(1, (process.stdout.columns ?? 80) - 2)),
2213
2213
  unicode: supportsUnicode(),
2214
2214
  color: welcomeTheme.color,
2215
2215
  });
@@ -2224,7 +2224,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
2224
2224
  {
2225
2225
  const card = formatForgeBox(
2226
2226
  { title: "[skill]", lines: skillInvocationCard(skill, intent) },
2227
- { width: scaleForgeWidth(Math.min(100, Math.max(40, (process.stdout.columns ?? 80) - 2))), unicode: supportsUnicode(), paint: accentPaint(uiTheme), paintShadow: accentShadowPaint(uiTheme), color: uiTheme.color },
2227
+ { width: scaleForgeWidth(Math.min(100, Math.max(1, (process.stdout.columns ?? 80) - 2))), unicode: supportsUnicode(), paint: accentPaint(uiTheme), paintShadow: accentShadowPaint(uiTheme), color: uiTheme.color },
2228
2228
  );
2229
2229
  logLines(card);
2230
2230
  }
@@ -2729,7 +2729,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
2729
2729
  let lastFooterKey = "";
2730
2730
  const logLines = (lines: string | string[]) => {
2731
2731
  const arr = Array.isArray(lines) ? lines : [lines];
2732
- const cols = Math.max(20, (process.stdout.columns ?? 80) - 1);
2732
+ const cols = Math.max(1, (process.stdout.columns ?? 80) - 1);
2733
2733
  for (const line of arr) {
2734
2734
  console.log(truncateAnsi(line, cols));
2735
2735
  }
@@ -2934,7 +2934,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
2934
2934
  });
2935
2935
  };
2936
2936
  const previewLines = (line: string, selected = -1): string[] => {
2937
- const cols = Math.max(24, (process.stdout.columns ?? 80) - 1);
2937
+ const cols = Math.max(1, (process.stdout.columns ?? 80) - 1);
2938
2938
  // Caret offset comes from readline's live cursor when it matches the rendered
2939
2939
  // line (arrow keys/Home/End move it); otherwise (history nav mismatch) caret
2940
2940
  // sits at the end of the text.
@@ -2983,7 +2983,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
2983
2983
  // with ↑/↓ counters. Recomputes the scroll bound/page size each paint so scroll
2984
2984
  // keys can clamp correctly. Mirrors the mid-turn LaunchTui panel.
2985
2985
  const historyPreviewLines = (detail: string[]): string[] => {
2986
- const cols = Math.max(24, (process.stdout.columns ?? 80) - 1);
2986
+ const cols = Math.max(1, (process.stdout.columns ?? 80) - 1);
2987
2987
  const physical = detail.flatMap(line => line.split("\n")).map(line => truncateAnsi(line, cols));
2988
2988
  const bodyLimit = Math.max(1, footerRows - 2); // status bar + title rows
2989
2989
  const scrollable = physical.length > bodyLimit;
@@ -107,7 +107,13 @@ function normalizeHighlights(
107
107
  * can park the REAL terminal cursor right after `>` — moving with the arrow keys.
108
108
  */
109
109
  export function renderInputFrame(line: string, opts: InputBoxOptions = {}): InputFrame {
110
- const cols = Math.max(24, Math.trunc(opts.cols ?? 80));
110
+ // Never clamp UP past what the caller reports as the real terminal width — a
111
+ // fixed floor here (previously 24) silently overflowed any narrower terminal
112
+ // (e.g. a resize down to 20 cols still drew a 24-col box), which real terminals
113
+ // then hard-wrap/split at the boundary (the exact box-border corruption a real
114
+ // resize-down reproduces live). Degrading to a very small, ugly-but-correctly-
115
+ // sized box is strictly better than one that overflows and gets torn.
116
+ const cols = Math.max(1, Math.trunc(opts.cols ?? 80));
111
117
  const useColor = opts.color !== false;
112
118
  const placeholder = opts.placeholder ?? "Type your message...";
113
119
  const bodyWidth = Math.max(1, cols - 4);
@@ -66,7 +66,14 @@ function shortenCwd(cwd: string, maxLen: number, unicode: boolean): string {
66
66
  export function renderStatusBar(d: StatusBarData): string {
67
67
  const unicode = d.unicode !== false;
68
68
  const useColor = d.color !== false;
69
- const cols = Math.max(24, Math.trunc(d.cols));
69
+ // Never clamp UP past the caller's real terminal width — a fixed floor here
70
+ // (previously 24) silently overflowed any narrower terminal (e.g. a resize down
71
+ // to 20 cols still emitted a 24-col status bar), which a real terminal then
72
+ // hard-wraps at the boundary and desyncs the idle-footer's own row bookkeeping
73
+ // on every subsequent repaint (reproduced live: a growing stack of duplicate
74
+ // status-bar lines on each keystroke after a resize-down, root-caused to this
75
+ // exact overflow via a byte-for-byte ANSI replay outside jeo/tmux entirely).
76
+ const cols = Math.max(1, Math.trunc(d.cols));
70
77
  const sep = " / ";
71
78
 
72
79
  // Right side first — it has priority and fixed width.
@@ -97,7 +104,7 @@ export function renderStatusBar(d: StatusBarData): string {
97
104
  bits.push(`${unicode ? "⑂" : "y"} ${d.branch}${dirty}`);
98
105
  }
99
106
  // Budget the cwd into whatever width remains (right stats + 2-col gap reserved).
100
- const leftBudget = Math.max(8, cols - rightWidth - (rightWidth > 0 ? 2 : 0));
107
+ const leftBudget = Math.max(1, cols - rightWidth - (rightWidth > 0 ? 2 : 0));
101
108
  if (d.cwd) {
102
109
  const used = visibleWidth(` ${bits.join(sep)}${sep}${unicode ? "▸" : ">"} `);
103
110
  const room = leftBudget - used - 1;
@@ -185,7 +185,17 @@ export function renderWelcome(d: WelcomeData): string[] {
185
185
  const useColor = d.color !== false;
186
186
 
187
187
  if (cols < 30) {
188
- return [ `jeo v${d.version} · ${d.model}` ];
188
+ // Narrow-terminal one-line fallback — MUST itself be width-capped: an unbounded
189
+ // `jeo v{version} · {model}` overflows badly with a realistic provider-qualified
190
+ // model id (e.g. "antigravity/claude-sonnet-4-6 (antigravity)" overflows a 10-col
191
+ // terminal by 47 columns), which is exactly the box-border/wrap misalignment a
192
+ // real terminal-resize-down reproduces live. `cols - 1` (not `cols`) — same
193
+ // "leave the last column free" convention as the boxed banner below: a line
194
+ // truncated to EXACTLY `cols` is ambiguous to real terminals (a full-width row
195
+ // followed by this line's own trailing "\n" can double-advance a row via the
196
+ // pending-autowrap/explicit-LF ambiguity), which is a second, independent
197
+ // corruption vector reproduced live — not just a cosmetic nicety.
198
+ return [truncate(`jeo v${d.version} · ${d.model}`, Math.max(0, cols - 1))];
189
199
  }
190
200
 
191
201
  // The banner fills the full terminal width (gjc forge: flush with the input box and
@@ -49,7 +49,29 @@ export class Renderer {
49
49
  const currentRows = size().rows;
50
50
  if ((this.prevCols !== undefined && this.prevCols !== currentCols) ||
51
51
  (this.prevRows !== undefined && this.prevRows !== currentRows)) {
52
- this.clear();
52
+ if (this.reserve) {
53
+ // Inline/reserve mode: a resize needs a full repaint, but NOT an immediate
54
+ // clear() — clear() zeroes coverRows/prev to 0 as an independent write, which
55
+ // then fools the reserve block below (right after this) into believing NOTHING
56
+ // currently occupies the screen (occupied=0) — even though the old frame's rows
57
+ // are still physically there. With occupied wrongly 0, `next.length > occupied`
58
+ // spuriously trips even when the new (post-resize) frame is SHORTER than the
59
+ // old one, inserting real "\n" characters that ACTUALLY SCROLL the terminal —
60
+ // corrupting whatever sits above the live frame and permanently desyncing this
61
+ // renderer's row bookkeeping from reality (every later diff then paints at the
62
+ // wrong absolute row, producing progressively worse duplicate/torn content on
63
+ // each subsequent tick — reproduced deterministically with a standalone
64
+ // Renderer harness before this fix landed). reset() is the same self-heal path
65
+ // the periodic full-resync already uses: it remembers the old occupied rows via
66
+ // coverRows (no output), so the diff loop below EL-clears exactly the excess
67
+ // rows in place — no scroll, no desync.
68
+ this.reset();
69
+ } else {
70
+ // Non-reserve (alt-screen/pipe) mode has no reserve block to fool (gated on
71
+ // `this.reserve`), and the underlying screen buffer's own dimensions just
72
+ // changed — keep the defensive full ED clear.
73
+ this.clear();
74
+ }
53
75
  }
54
76
  this.prevCols = currentCols;
55
77
  this.prevRows = currentRows;