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 +13 -0
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/package.json +1 -1
- package/src/commands/doctor.ts +95 -23
- package/src/tui/components/forge.ts +7 -1
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
package/src/commands/doctor.ts
CHANGED
|
@@ -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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
355
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("-")
|