jeo-code 0.9.0 → 0.9.1

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,19 @@ 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.9.1] - 2026-07-24
10
+ _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._
11
+
12
+ ### Fixed
13
+ - **`jeo doctor`'s Provider connectivity table no longer breaks on a narrow terminal** (`src/commands/doctor.ts`) — replaced the fixed 75-column separator and fixed-width `padEnd` columns (never resized, hard-wrapped under ~80 cols) with jeo's own forge/markdown-table box-drawn table (`renderMarkdownTables`), sized off the LIVE terminal width every run. The free-text Detail column absorbs whatever budget the other short, fixed-shape columns leave, and the grid falls back to a stacked per-provider list when even that can't fit. The header block (Bun runtime/model/config/terminal size) now renders as a forge-style bordered card (`formatForgeBox` + `scaleForgeWidth`) that scales with the terminal too, with a plain-text fallback below the card's own ~26-column floor.
14
+ - **`formatForgeBox`'s "N more lines ⟦Ctrl+O for more⟧" clip-hint row could overflow a narrow card's declared width** (`src/tui/components/forge.ts`) — the hint text is appended AFTER the normal wrap-to-width step, so on a narrow box it could poke past the right border; every forge card in the live TUI (tool-execution cards, skill-invocation cards, etc.) was exposed to this whenever content was long enough to clip on a narrow terminal. Added a defense-in-depth truncation in `contentRow` so no content path — wrapped or not — can widen a box past its declared width.
15
+
16
+ ### Verified
17
+ - Pure-function width sweep (20-200 columns) confirmed zero overflow for both the provider table and the header card, including the fallback thresholds.
18
+ - `bun run typecheck` clean.
19
+ - `test/doctor.test.ts` + `test/forge-status.test.ts` + `test/forge-mark-anim.test.ts` — 54 pass; `test/tui-components.test.ts` + `test/tui-frame-width.test.ts` + `test/tui-frame-wrap-tail.test.ts` + `test/tui-app.test.ts` + `test/tui-renderer.test.ts` + `test/todo-card.test.ts` — 96 pass; 0 fail across both targeted runs.
20
+ - Full `bun test` — 3134 pass / 23 fail across 308 files; every failure (team-parallel, engine-multitool ×3 timeouts, notify-daemon-control timing, team-subagent timeout) reproduces identically on a clean `git stash` of this pass's changes — confirmed pre-existing, unrelated flakiness, not caused by this pass.
21
+
9
22
  ## [0.9.0] - 2026-07-22
10
23
  _Session-scoped async execution, detached subagent control, background monitors, and parallel TUI activity are now documented and shipped for the gjc-parity release line._
11
24
 
package/README.ja.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.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.
287
288
  - **[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.
288
289
  - **[0.8.42]** (2026-07-21) — Request (paraphrased): "adopt gjc's subagent execution model and TUI parallel-processing monitoring — all the async/parallel parts — into jeo-code, with real operational verification." Investigated live before assuming a gap: jeo already has a mature, gjc-parity concurrent fan-out worker pool (`MAX_FANOUT`-bounded `Promise.all`, broadcast steering hub, detached/subagent/job/irc tools, a live TUI panel for concurrent subagents). Booted real `jeo --tmux` sessions with real credentials and repeatedly drove a genuine 3-file concurrent executor fan-out to verify this stack end to end — not to re-implement what already works, but to find what's actually broken in it. Found and fixed three real, live-reproduced bugs across the fan-out call path, none of them "missing gjc parity" — all pre-existing defects in jeo's own already-built implementation.
289
290
  - **[0.8.41]** (2026-07-21) — Ponytail-mode review requested directly ("review ponytail") over the two files this session's most recent pass (v0.8.40) touched: `src/agent/notify/daemon-control.ts` and `src/commands/launch/route-slash.ts`. Self-review found no correctness bugs (the v0.8.40 logic held up), but did find real, concrete style-bloat and one genuine test-coverage gap — both fixed with evidence, per the established convention.
290
291
  - **[0.8.40]** (2026-07-21) — User report (paraphrased): "the /route command doesn't work, fix it." Investigated live before assuming a defect — booted a real `jeo --tmux` session with real Anthropic OAuth credentials from the actual `~/.jeo/config.json` and drove `/route status|on|off|why|history|bogus` plus real end-to-end routed turns. Every subcommand and the routing engine itself (heuristic tiering, `roles.smol` resolution, live provider call, per-turn "thinking" card showing the routed model) worked correctly, including a real stale-catalog edge case (`roles.smol: "claude-haiku-4-5"`, removed from jeo's own catalog in an earlier curation pass, still being accepted directly by the Anthropic API). The one genuine, reproducible gap: `/route on` is SESSION-LOCAL ONLY — `config.routing.enabled` in `~/.jeo/config.json` is never written, so a user has to retype `/route on` at the start of every single session, and (separately, working as designed but easy to miss) an explicit `--model`/`/model` pin silently blocks routing from ever engaging unless `/route on` is run to override it that session. The "settings don't stick" experience is the most likely reading of "doesn't work."
291
- - **[0.8.39]** (2026-07-21) — User report (paraphrased): "when I copy and paste content from elsewhere, it doesn't need to show every line — like gjc — update it so the composer stays a reasonable size and shows a clear indicator when width/length isn't enough." Investigated live before assuming intent: pasting a large multi-line block into the composer already caps the visible box height and scrolls to the caret, but the scrolled-off content above/below was flagged with a bare `…` — no indication of HOW MUCH text was hidden. (Checked gjc's own actual paste-handling source directly: it does not have a general text-paste-collapse feature either — only image-path detection — so this fix targets the concrete, reproducible UX gap found live, not a specific gjc feature to port.)
292
292
 
293
293
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
294
294
  <!-- CHANGELOG:END -->
package/README.ko.md CHANGED
@@ -285,11 +285,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
285
285
  ## 변경 이력 (Changelog)
286
286
 
287
287
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
288
+ - **[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.
288
289
  - **[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.
289
290
  - **[0.8.42]** (2026-07-21) — Request (paraphrased): "adopt gjc's subagent execution model and TUI parallel-processing monitoring — all the async/parallel parts — into jeo-code, with real operational verification." Investigated live before assuming a gap: jeo already has a mature, gjc-parity concurrent fan-out worker pool (`MAX_FANOUT`-bounded `Promise.all`, broadcast steering hub, detached/subagent/job/irc tools, a live TUI panel for concurrent subagents). Booted real `jeo --tmux` sessions with real credentials and repeatedly drove a genuine 3-file concurrent executor fan-out to verify this stack end to end — not to re-implement what already works, but to find what's actually broken in it. Found and fixed three real, live-reproduced bugs across the fan-out call path, none of them "missing gjc parity" — all pre-existing defects in jeo's own already-built implementation.
290
291
  - **[0.8.41]** (2026-07-21) — Ponytail-mode review requested directly ("review ponytail") over the two files this session's most recent pass (v0.8.40) touched: `src/agent/notify/daemon-control.ts` and `src/commands/launch/route-slash.ts`. Self-review found no correctness bugs (the v0.8.40 logic held up), but did find real, concrete style-bloat and one genuine test-coverage gap — both fixed with evidence, per the established convention.
291
292
  - **[0.8.40]** (2026-07-21) — User report (paraphrased): "the /route command doesn't work, fix it." Investigated live before assuming a defect — booted a real `jeo --tmux` session with real Anthropic OAuth credentials from the actual `~/.jeo/config.json` and drove `/route status|on|off|why|history|bogus` plus real end-to-end routed turns. Every subcommand and the routing engine itself (heuristic tiering, `roles.smol` resolution, live provider call, per-turn "thinking" card showing the routed model) worked correctly, including a real stale-catalog edge case (`roles.smol: "claude-haiku-4-5"`, removed from jeo's own catalog in an earlier curation pass, still being accepted directly by the Anthropic API). The one genuine, reproducible gap: `/route on` is SESSION-LOCAL ONLY — `config.routing.enabled` in `~/.jeo/config.json` is never written, so a user has to retype `/route on` at the start of every single session, and (separately, working as designed but easy to miss) an explicit `--model`/`/model` pin silently blocks routing from ever engaging unless `/route on` is run to override it that session. The "settings don't stick" experience is the most likely reading of "doesn't work."
292
- - **[0.8.39]** (2026-07-21) — User report (paraphrased): "when I copy and paste content from elsewhere, it doesn't need to show every line — like gjc — update it so the composer stays a reasonable size and shows a clear indicator when width/length isn't enough." Investigated live before assuming intent: pasting a large multi-line block into the composer already caps the visible box height and scrolls to the caret, but the scrolled-off content above/below was flagged with a bare `…` — no indication of HOW MUCH text was hidden. (Checked gjc's own actual paste-handling source directly: it does not have a general text-paste-collapse feature either — only image-path detection — so this fix targets the concrete, reproducible UX gap found live, not a specific gjc feature to port.)
293
293
 
294
294
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
295
295
  <!-- CHANGELOG:END -->
package/README.md CHANGED
@@ -292,11 +292,11 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
292
292
  ## Changelog
293
293
 
294
294
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
295
+ - **[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.
295
296
  - **[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.
296
297
  - **[0.8.42]** (2026-07-21) — Request (paraphrased): "adopt gjc's subagent execution model and TUI parallel-processing monitoring — all the async/parallel parts — into jeo-code, with real operational verification." Investigated live before assuming a gap: jeo already has a mature, gjc-parity concurrent fan-out worker pool (`MAX_FANOUT`-bounded `Promise.all`, broadcast steering hub, detached/subagent/job/irc tools, a live TUI panel for concurrent subagents). Booted real `jeo --tmux` sessions with real credentials and repeatedly drove a genuine 3-file concurrent executor fan-out to verify this stack end to end — not to re-implement what already works, but to find what's actually broken in it. Found and fixed three real, live-reproduced bugs across the fan-out call path, none of them "missing gjc parity" — all pre-existing defects in jeo's own already-built implementation.
297
298
  - **[0.8.41]** (2026-07-21) — Ponytail-mode review requested directly ("review ponytail") over the two files this session's most recent pass (v0.8.40) touched: `src/agent/notify/daemon-control.ts` and `src/commands/launch/route-slash.ts`. Self-review found no correctness bugs (the v0.8.40 logic held up), but did find real, concrete style-bloat and one genuine test-coverage gap — both fixed with evidence, per the established convention.
298
299
  - **[0.8.40]** (2026-07-21) — User report (paraphrased): "the /route command doesn't work, fix it." Investigated live before assuming a defect — booted a real `jeo --tmux` session with real Anthropic OAuth credentials from the actual `~/.jeo/config.json` and drove `/route status|on|off|why|history|bogus` plus real end-to-end routed turns. Every subcommand and the routing engine itself (heuristic tiering, `roles.smol` resolution, live provider call, per-turn "thinking" card showing the routed model) worked correctly, including a real stale-catalog edge case (`roles.smol: "claude-haiku-4-5"`, removed from jeo's own catalog in an earlier curation pass, still being accepted directly by the Anthropic API). The one genuine, reproducible gap: `/route on` is SESSION-LOCAL ONLY — `config.routing.enabled` in `~/.jeo/config.json` is never written, so a user has to retype `/route on` at the start of every single session, and (separately, working as designed but easy to miss) an explicit `--model`/`/model` pin silently blocks routing from ever engaging unless `/route on` is run to override it that session. The "settings don't stick" experience is the most likely reading of "doesn't work."
299
- - **[0.8.39]** (2026-07-21) — User report (paraphrased): "when I copy and paste content from elsewhere, it doesn't need to show every line — like gjc — update it so the composer stays a reasonable size and shows a clear indicator when width/length isn't enough." Investigated live before assuming intent: pasting a large multi-line block into the composer already caps the visible box height and scrolls to the caret, but the scrolled-off content above/below was flagged with a bare `…` — no indication of HOW MUCH text was hidden. (Checked gjc's own actual paste-handling source directly: it does not have a general text-paste-collapse feature either — only image-path detection — so this fix targets the concrete, reproducible UX gap found live, not a specific gjc feature to port.)
300
300
 
301
301
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
302
302
  <!-- CHANGELOG:END -->
package/README.zh.md CHANGED
@@ -284,11 +284,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
284
284
  ## 更新日志 (Changelog)
285
285
 
286
286
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
287
+ - **[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.
287
288
  - **[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.
288
289
  - **[0.8.42]** (2026-07-21) — Request (paraphrased): "adopt gjc's subagent execution model and TUI parallel-processing monitoring — all the async/parallel parts — into jeo-code, with real operational verification." Investigated live before assuming a gap: jeo already has a mature, gjc-parity concurrent fan-out worker pool (`MAX_FANOUT`-bounded `Promise.all`, broadcast steering hub, detached/subagent/job/irc tools, a live TUI panel for concurrent subagents). Booted real `jeo --tmux` sessions with real credentials and repeatedly drove a genuine 3-file concurrent executor fan-out to verify this stack end to end — not to re-implement what already works, but to find what's actually broken in it. Found and fixed three real, live-reproduced bugs across the fan-out call path, none of them "missing gjc parity" — all pre-existing defects in jeo's own already-built implementation.
289
290
  - **[0.8.41]** (2026-07-21) — Ponytail-mode review requested directly ("review ponytail") over the two files this session's most recent pass (v0.8.40) touched: `src/agent/notify/daemon-control.ts` and `src/commands/launch/route-slash.ts`. Self-review found no correctness bugs (the v0.8.40 logic held up), but did find real, concrete style-bloat and one genuine test-coverage gap — both fixed with evidence, per the established convention.
290
291
  - **[0.8.40]** (2026-07-21) — User report (paraphrased): "the /route command doesn't work, fix it." Investigated live before assuming a defect — booted a real `jeo --tmux` session with real Anthropic OAuth credentials from the actual `~/.jeo/config.json` and drove `/route status|on|off|why|history|bogus` plus real end-to-end routed turns. Every subcommand and the routing engine itself (heuristic tiering, `roles.smol` resolution, live provider call, per-turn "thinking" card showing the routed model) worked correctly, including a real stale-catalog edge case (`roles.smol: "claude-haiku-4-5"`, removed from jeo's own catalog in an earlier curation pass, still being accepted directly by the Anthropic API). The one genuine, reproducible gap: `/route on` is SESSION-LOCAL ONLY — `config.routing.enabled` in `~/.jeo/config.json` is never written, so a user has to retype `/route on` at the start of every single session, and (separately, working as designed but easy to miss) an explicit `--model`/`/model` pin silently blocks routing from ever engaging unless `/route on` is run to override it that session. The "settings don't stick" experience is the most likely reading of "doesn't work."
291
- - **[0.8.39]** (2026-07-21) — User report (paraphrased): "when I copy and paste content from elsewhere, it doesn't need to show every line — like gjc — update it so the composer stays a reasonable size and shows a clear indicator when width/length isn't enough." Investigated live before assuming intent: pasting a large multi-line block into the composer already caps the visible box height and scrolls to the caret, but the scrolled-off content above/below was flagged with a bare `…` — no indication of HOW MUCH text was hidden. (Checked gjc's own actual paste-handling source directly: it does not have a general text-paste-collapse feature either — only image-path detection — so this fix targets the concrete, reproducible UX gap found live, not a specific gjc feature to port.)
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.0",
3
+ "version": "0.9.1",
4
4
  "description": "Clean, highly optimized AI coding agent using spec-first loop",
5
5
  "type": "module",
6
6
  "main": "src/cli.ts",
@@ -6,6 +6,10 @@ import { effectiveCredentialForProvider } from "../ai/model-manager";
6
6
  import { resolveModelId } from "../ai/model-registry";
7
7
  import { meter } from "../tui/components/meter";
8
8
  import { size } from "../tui/terminal";
9
+ import { formatForgeBox, scaleForgeWidth } from "../tui/components/forge";
10
+ import { renderMarkdownTables } from "../tui/components/markdown-table";
11
+ import { supportsUnicode } from "../tui/components/capability";
12
+ import { visibleWidth, truncateToWidth } from "../tui/components/width";
9
13
  import chalk from "chalk";
10
14
  import { extractChatgptAccountId, CODEX_RESPONSES_URL } from "../ai/providers/openai-responses";
11
15
  import { resolveTierModel, tierModelPool, cheapestCredentialed } from "../agent/prompt-router";
@@ -153,15 +157,65 @@ function colorStatus(status: ProbeResult["status"], label: string): string {
153
157
  return chalk.red(label);
154
158
  }
155
159
 
156
- function formatRow(provider: string, credKind: string, result: ProbeResult): string {
157
- const status =
158
- result.status === "ok" ? " OK " :
159
- result.status === "skipped" ? " SKIP " :
160
- " FAIL ";
161
- const latency = result.latencyMs !== undefined ? `${result.latencyMs}ms` : "—";
162
- // Visual latency bar (relative to a 2s baseline) for OK probes.
163
- const bar = result.status === "ok" && result.latencyMs !== undefined ? ` ${meter(result.latencyMs, 2000, 12)}` : "";
164
- return ` ${provider.padEnd(10)} ${credKind.padEnd(16)} [${colorStatus(result.status, status)}] ${latency.padEnd(7)} ${result.detail}${bar}`;
160
+ /**
161
+ * Build the "Provider connectivity" table as a box-drawn GFM table (jeo's forge/
162
+ * markdown-table renderer — the same component that boxes tables in assistant
163
+ * output) sized to the LIVE terminal width, so it never hard-wraps on a narrow
164
+ * terminal. The old layout used a fixed 75-col separator and fixed `padEnd`
165
+ * columns that broke (wrapped mid-row) on anything narrower than ~80 cols and
166
+ * never shrank OR grew with the terminal. Only the free-text Detail column is
167
+ * width-capped; the other columns size naturally off their own short, fixed-
168
+ * shape content.
169
+ */
170
+ function providerTableLines(
171
+ probes: { name: string; credKind: string; result: ProbeResult }[],
172
+ cols: number,
173
+ unicode: boolean,
174
+ ): string[] {
175
+ const escape = (s: string) => s.replace(/\|/g, "\\|");
176
+ const statusLabel = (s: ProbeResult["status"]) => (s === "ok" ? "OK" : s === "skipped" ? "SKIP" : "FAIL");
177
+ const header = ["Provider", "Credential", "Status", "Latency", "Detail"];
178
+ const rows = probes.map(p => {
179
+ const latency = p.result.latencyMs !== undefined ? `${p.result.latencyMs}ms` : "—";
180
+ // Visual latency bar (relative to a 2s baseline) for OK probes.
181
+ const bar = p.result.status === "ok" && p.result.latencyMs !== undefined ? ` ${meter(p.result.latencyMs, 2000, 12)}` : "";
182
+ return [
183
+ escape(p.name),
184
+ escape(p.credKind),
185
+ colorStatus(p.result.status, statusLabel(p.result.status)),
186
+ latency,
187
+ escape(p.result.detail) + bar,
188
+ ];
189
+ });
190
+ // 5 columns → fixed box-drawing overhead (borders + 1-space padding on each side)
191
+ // is `3*5 + 1 = 16` display columns; whatever's left over after the other four
192
+ // columns' natural (content-driven) widths goes to Detail.
193
+ const colWidth = (i: number) => Math.max(visibleWidth(header[i]!), ...rows.map(r => visibleWidth(r[i]!)));
194
+ const overhead = 16;
195
+ const fixedWidth = colWidth(0) + colWidth(1) + colWidth(2) + colWidth(3);
196
+ const minDetailWidth = 8;
197
+ // Provider/Credential/Status/Latency are fixed-shape and can't shrink below their
198
+ // own content width — a grid table can't fit under `overhead + fixedWidth +
199
+ // minDetailWidth` cols no matter how tight Detail is capped. Below that floor, fall
200
+ // back to a stacked one-block-per-provider list (each line truncated to `cols`)
201
+ // instead of drawing a table that overflows and tears the terminal.
202
+ if (cols < overhead + fixedWidth + minDetailWidth) {
203
+ const out: string[] = [];
204
+ for (const p of probes) {
205
+ const latency = p.result.latencyMs !== undefined ? `${p.result.latencyMs}ms` : "—";
206
+ const head = `${p.name} · ${p.credKind} · [${colorStatus(p.result.status, statusLabel(p.result.status))}] ${latency}`;
207
+ out.push(truncateToWidth(head, cols));
208
+ out.push(truncateToWidth(` ${p.result.detail}`, cols));
209
+ }
210
+ return out;
211
+ }
212
+ const detailBudget = Math.max(minDetailWidth, cols - overhead - fixedWidth);
213
+ const md = [
214
+ `| ${header.join(" | ")} |`,
215
+ `| ${header.map(() => "---").join(" | ")} |`,
216
+ ...rows.map(r => `| ${r.join(" | ")} |`),
217
+ ].join("\n");
218
+ return renderMarkdownTables(md, { unicode, maxColWidth: detailBudget }).split("\n");
165
219
  }
166
220
 
167
221
  export async function runDoctorCommand(args: string[] = []): Promise<void> {
@@ -338,27 +392,45 @@ export async function runDoctorCommand(args: string[] = []): Promise<void> {
338
392
  }
339
393
 
340
394
  // --- Human output ---
341
- console.log("");
342
- console.log(`=== ${APP_NAME} doctor ===`);
343
- console.log("");
344
- console.log(`Bun runtime: v${Bun.version}`);
345
- console.log(`Default model: ${config.defaultModel}${resolvedModel !== config.defaultModel ? ` → ${resolvedModel}` : ""} → ${defaultProvider}`);
346
- console.log(`Config: ${process.env.HOME}/.jeo/config.json`);
347
- if (config.openaiBaseUrl) console.log(`OpenAI base: ${config.openaiBaseUrl}`);
348
- console.log(`Ollama base: ${ollamaBase}`);
349
-
350
395
  const termSize = size();
396
+ // LIVE terminal width — every panel below is sized off this, not a fixed guess, so
397
+ // the report reflows (never hard-wraps/tears) whatever the terminal's current size is.
398
+ const cols = Math.max(20, termSize.cols);
399
+ const unicode = supportsUnicode();
400
+
351
401
  const tuiVerdict = termSize.cols < 40
352
402
  ? `${termSize.cols}x${termSize.rows} ${chalk.red("(too narrow for ASCII art)")}`
353
403
  : `${termSize.cols}x${termSize.rows} ${chalk.green("(ASCII art enabled)")}`;
354
- console.log(`Terminal size: ${tuiVerdict}`);
355
- console.log(`Color support: Level ${chalk.level} (${chalk.level > 0 ? chalk.green("enabled") : "disabled"})`);
404
+ const headerLines = [
405
+ `Bun runtime: v${Bun.version}`,
406
+ `Default model: ${config.defaultModel}${resolvedModel !== config.defaultModel ? ` → ${resolvedModel}` : ""} → ${defaultProvider}`,
407
+ `Config: ${process.env.HOME}/.jeo/config.json`,
408
+ ];
409
+ if (config.openaiBaseUrl) headerLines.push(`OpenAI base: ${config.openaiBaseUrl}`);
410
+ headerLines.push(`Ollama base: ${ollamaBase}`);
411
+ headerLines.push(`Terminal size: ${tuiVerdict}`);
412
+ headerLines.push(`Color support: Level ${chalk.level} (${chalk.level > 0 ? chalk.green("enabled") : "disabled"})`);
413
+
414
+ console.log("");
415
+ // gjc/forge-style bordered card, width-scaled off the live terminal (same
416
+ // formatForgeBox + scaleForgeWidth the interactive TUI's tool cards use) so the
417
+ // panel reflows cleanly on any width instead of raw unbounded console.log lines.
418
+ // scaleForgeWidth floors at 24 (its own minimum readable card width, shared by
419
+ // every forge card app-wide) — below `24 + 2 border cols`, the box itself would
420
+ // be wider than the terminal, so fall back to plain unboxed lines instead.
421
+ if (cols >= 26) {
422
+ for (const line of formatForgeBox(
423
+ { title: `${APP_NAME} doctor`, lines: headerLines },
424
+ { width: scaleForgeWidth(cols - 2), unicode, color: chalk.level > 0 },
425
+ )) console.log(line);
426
+ } else {
427
+ console.log(`=== ${APP_NAME} doctor ===`);
428
+ for (const line of headerLines) console.log(truncateToWidth(line, cols));
429
+ }
356
430
  console.log("");
357
431
 
358
432
  console.log("Provider connectivity:");
359
- console.log(` ${"Provider".padEnd(10)} ${"Credential".padEnd(16)} ${"Status".padEnd(8)} ${"Latency".padEnd(7)} Detail`);
360
- console.log(` ${"-".repeat(75)}`);
361
- for (const p of probes) console.log(formatRow(p.name, p.credKind, p.result));
433
+ for (const line of providerTableLines(probes, cols, unicode)) console.log(line);
362
434
 
363
435
  console.log("");
364
436
  const oauthLines = oauthHealth.map(o => {
@@ -582,7 +582,13 @@ export function formatForgeBox(summary: ForgeSummary, opts: ForgeBoxOptions = {}
582
582
  const highlightLang = opts.color !== false && summary.language && CODE_LANGS.has(summary.language) ? summary.language : undefined;
583
583
  const contentRow = (line: string): string => {
584
584
  const lit = highlightLang ? lightHighlightLine(line, highlightLang) : line;
585
- const padded = padLineTo(` ${lit}`, inner, "left");
585
+ // Defense-in-depth truncation: ordinary content already arrives pre-wrapped to
586
+ // `gutterWidth` (inner - 1) below, but the clip-hint rows (line ~605/609) append
587
+ // the "N more lines ⟦Ctrl+O for more⟧" marker AFTER that wrap step, so on a narrow
588
+ // box the marker text alone can exceed `inner` and poke the row past the right
589
+ // border (the "screen breaks on resize" corruption). Cap every row here so no
590
+ // content path — wrapped or not — can widen the box past its declared width.
591
+ const padded = padLineTo(` ${truncateToWidth(lit, Math.max(1, inner - 1))}`, inner, "left");
586
592
  const body = diffRows && line.startsWith("+")
587
593
  ? diffRows.add(padded)
588
594
  : diffRows && line.startsWith("-")