@v1nvn/statusline 0.30.8 → 0.31.0
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/README.md +37 -28
- package/dist/index.js +85 -40
- package/dist/index.js.map +1 -1
- package/dist/resolver.mjs +74 -0
- package/dist/resolver.mjs.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# @v1nvn/statusline
|
|
2
2
|
|
|
3
3
|
The statusline package: the CLI that writes the two settings keys painting
|
|
4
|
-
Claude Code's status line and agent panel,
|
|
5
|
-
`dist/
|
|
4
|
+
Claude Code's status line and agent panel, plus the resolver those keys spawn
|
|
5
|
+
and the renderer it imports — `dist/resolver.mjs` deployed into the plugin
|
|
6
|
+
data dir, `dist/render.mjs` resolved from the installed plugin. Pure
|
|
6
7
|
TypeScript, zero bash.
|
|
7
8
|
|
|
8
9
|
User-facing docs: [root README § statusline](../../README.md#statusline).
|
|
@@ -22,7 +23,7 @@ offers the picker in chat, and writes the pick.
|
|
|
22
23
|
In a terminal outside Claude Code — the wizard, the terminal guide:
|
|
23
24
|
|
|
24
25
|
```sh
|
|
25
|
-
npx -y @v1nvn/statusline@0.
|
|
26
|
+
npx -y @v1nvn/statusline@0.31.0 configure
|
|
26
27
|
```
|
|
27
28
|
|
|
28
29
|
Pass one stacks the five theme bars: `j/k` focus · `w` width · enter picks.
|
|
@@ -35,31 +36,35 @@ One CLI, both ways: `/lab` inside a session runs these same commands; `npx`
|
|
|
35
36
|
runs them in a terminal. Every subcommand takes `--home <dir>` to operate on
|
|
36
37
|
another home instead of `$HOME`.
|
|
37
38
|
|
|
38
|
-
| Command | Does
|
|
39
|
-
| ----------- |
|
|
40
|
-
| `configure` | the sole writer — writes both settings keys and
|
|
41
|
-
| `preview` | render the candidate bar and panel row through the same
|
|
42
|
-
| `catalog` | the themes block (`*` marks the theme the key names) then one line per item, `*` marking the resolved variant; `--themes` cuts to the block
|
|
43
|
-
| `status` | one row per fact — node, the
|
|
44
|
-
| `restore` | both keys back to their pre-lab values from `backup.json`, then deletes the lab data — `--dry-run` prints the plan; `--force` splices over a key changed after the takeover
|
|
39
|
+
| Command | Does |
|
|
40
|
+
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| `configure` | the sole writer — writes both settings keys and deploys the bundled resolver into the data dir. Bare on a TTY: the wizard. With flags: `--theme <name>` (quiet, lean, classic, rich, custom) is the base design, item flags override it, `--layout` overrides its layout; a layout item nothing picks is an error naming it; a foreign key needs `--force` |
|
|
42
|
+
| `preview` | render the candidate bar and panel row through the same theme resolution the keys paint with, writing nothing; `--plain` strips the color escapes so glyphs survive chat |
|
|
43
|
+
| `catalog` | the themes block (`*` marks the theme the key names) then one line per item, `*` marking the resolved variant; `--themes` cuts to the block |
|
|
44
|
+
| `status` | one row per fact — node, the renderer the install record resolves, both keys, the theme the key names, backup, captures — plus a verdict; exit 0 healthy, 1 needs action, every action row names its fix |
|
|
45
|
+
| `restore` | both keys back to their pre-lab values from `backup.json`, then deletes the lab data — `--dry-run` prints the plan; `--force` splices over a key changed after the takeover |
|
|
45
46
|
|
|
46
47
|
```sh
|
|
47
|
-
npx -y @v1nvn/statusline@0.
|
|
48
|
-
npx -y @v1nvn/statusline@0.
|
|
49
|
-
npx -y @v1nvn/statusline@0.
|
|
50
|
-
npx -y @v1nvn/statusline@0.
|
|
51
|
-
npx -y @v1nvn/statusline@0.
|
|
52
|
-
npx -y @v1nvn/statusline@0.
|
|
48
|
+
npx -y @v1nvn/statusline@0.31.0 preview --theme rich --plain # chat-safe sketch, nothing written
|
|
49
|
+
npx -y @v1nvn/statusline@0.31.0 configure --theme rich # the write — live on the next paint
|
|
50
|
+
npx -y @v1nvn/statusline@0.31.0 configure --theme rich --bar percent # one swap on top of the theme
|
|
51
|
+
npx -y @v1nvn/statusline@0.31.0 catalog --themes
|
|
52
|
+
npx -y @v1nvn/statusline@0.31.0 status
|
|
53
|
+
npx -y @v1nvn/statusline@0.31.0 restore
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
## Develop
|
|
56
57
|
|
|
57
58
|
```sh
|
|
58
|
-
yarn workspace @v1nvn/statusline build # vite → dist/ (index.js + render.mjs)
|
|
59
|
+
yarn workspace @v1nvn/statusline build # vite → dist/ (index.js + render.mjs + resolver.mjs)
|
|
59
60
|
yarn workspace @v1nvn/statusline test # vitest
|
|
60
|
-
yarn lint && yarn typecheck
|
|
61
|
+
yarn lint && yarn typecheck # from the repo root
|
|
62
|
+
yarn build # root — every package, then syncs the plugin artifact
|
|
61
63
|
```
|
|
62
64
|
|
|
65
|
+
`packages/statusline/plugin/render.mjs` is a committed build artifact: the root
|
|
66
|
+
`yarn build` copies `dist/render.mjs` there, and CI drift-gates the copy.
|
|
67
|
+
|
|
63
68
|
Node ≥ 22. One workspace dep: `@v1nvn/agentic-core` (usage/exit helpers).
|
|
64
69
|
|
|
65
70
|
## Modules
|
|
@@ -78,8 +83,10 @@ Node ≥ 22. One workspace dep: `@v1nvn/agentic-core` (usage/exit helpers).
|
|
|
78
83
|
| `src/wizard.ts` · `src/wizard-tui.ts` | the terminal wizard — theme pass, then refinement of the pick's overrides |
|
|
79
84
|
| `src/payloads.ts` · `src/demo-repo.ts` | preview plumbing — fixtures anchored to the moment, in-process renders; demo git repo |
|
|
80
85
|
| `src/render/entry.ts` | the `render.mjs` entry the keys spawn — argv to line or panel, both capture tees |
|
|
86
|
+
| `src/render/resolver.ts` | the deployed entry the keys spawn — resolves the install record, imports its render.mjs |
|
|
87
|
+
| `src/render/install-record.ts` | `resolveInstall` — the install record, newest usable entry wins |
|
|
81
88
|
| `src/render/argv.ts` | the renderer's argv grammar — `node:util` parseArgs, never commander |
|
|
82
|
-
| `src/render/theme.ts` | `resolvePaint` — the one theme
|
|
89
|
+
| `src/render/theme.ts` | `resolvePaint` — the one theme resolution, shared by paint and display |
|
|
83
90
|
| `src/render/engine.ts` | the line engine — compose, `vlen`, the fit ladder |
|
|
84
91
|
| `src/render/items.ts` | the item registry — 16 items, defaults, rungs, `DEFAULT_LAYOUT` |
|
|
85
92
|
| `src/render/segments.ts` | the per-item segment renderers |
|
|
@@ -93,27 +100,29 @@ Node ≥ 22. One workspace dep: `@v1nvn/agentic-core` (usage/exit helpers).
|
|
|
93
100
|
|
|
94
101
|
## Contracts
|
|
95
102
|
|
|
96
|
-
- `configure` is the sole writer: one run writes both keys and
|
|
97
|
-
bundled
|
|
103
|
+
- `configure` is the sole writer: one run writes both keys and deploys the
|
|
104
|
+
bundled resolver as the data-dir `render.mjs` — its only other write is
|
|
98
105
|
`backup.json`. `preview`, `catalog`, and `status` write nothing;
|
|
99
106
|
`captures/` is the renderer's paint-time tee, not a CLI write.
|
|
100
107
|
- The key value is one direct data-dir command:
|
|
101
108
|
`node "$HOME/.claude/plugins/data/statusline-agentic/render.mjs" --theme=lean --bar=gauge || true`
|
|
102
|
-
—
|
|
109
|
+
— the data-dir file is a resolver: it reads Claude Code's install record
|
|
110
|
+
(`installed_plugins.json`) and imports the newest installed plugin's
|
|
111
|
+
`render.mjs` with argv and stdin intact; the panel key adds the `panel`
|
|
103
112
|
positional. Key spellings are pinned byte-exact by tests.
|
|
104
113
|
- Decisions ride argv: `--theme` first, then one `--<item>=<alt>` per pick
|
|
105
114
|
that differs from the theme's own, `--layout` only when passed. Ambient
|
|
106
115
|
state stays env — `NO_COLOR`, `COLUMNS`, `HOME`.
|
|
107
116
|
- The renderer resolves the theme at paint: item flags beat it, `--layout`
|
|
108
|
-
beats its layout — one
|
|
109
|
-
it, `preview` and the wizard render through it in-process, and
|
|
110
|
-
reads the theme name straight from the key.
|
|
117
|
+
beats its layout — one resolution path, no second one. `catalog` stars
|
|
118
|
+
through it, `preview` and the wizard render through it in-process, and
|
|
119
|
+
`status` reads the theme name straight from the key.
|
|
111
120
|
- The theme name rides the key: `status` and `catalog` read it straight from
|
|
112
121
|
the key, swaps appended — `theme: lean +bar=gauge` — so a swap keeps the
|
|
113
122
|
name.
|
|
114
|
-
- The
|
|
115
|
-
own argv — never commander, never `@v1nvn/agentic-core` — so any
|
|
116
|
-
the host has paints.
|
|
123
|
+
- The render bundles are dependency-free (node built-ins only) and parse
|
|
124
|
+
their own argv — never commander, never `@v1nvn/agentic-core` — so any
|
|
125
|
+
node ≥ 18 the host has paints.
|
|
117
126
|
- One splice home: the settings.json span primitives live in `configure.ts`;
|
|
118
127
|
`restore` imports them.
|
|
119
128
|
- `restore` never runs the renderer — it works uninstalled, deleting
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { delimiter, dirname, join, sep } from "node:path";
|
|
4
|
+
import { basename, delimiter, dirname, join, sep } from "node:path";
|
|
5
5
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { createHash } from "node:crypto";
|
|
9
8
|
//#region ../core/dist/index.js
|
|
10
9
|
function parseQuietly(program, args, recover) {
|
|
11
10
|
try {
|
|
@@ -1332,7 +1331,7 @@ function catalog(options) {
|
|
|
1332
1331
|
//#region src/cli.ts
|
|
1333
1332
|
var VERSION = {
|
|
1334
1333
|
name: "@v1nvn/statusline",
|
|
1335
|
-
version: "0.
|
|
1334
|
+
version: "0.31.0",
|
|
1336
1335
|
description: "Browse statusline variants and configure the live status line and agent panel — the statusline CLI behind the statusline plugin.",
|
|
1337
1336
|
type: "module",
|
|
1338
1337
|
bin: "dist/index.js",
|
|
@@ -1467,11 +1466,10 @@ function parseArgs(args) {
|
|
|
1467
1466
|
...typeof options.plain === "boolean" ? { plain: options.plain } : {}
|
|
1468
1467
|
};
|
|
1469
1468
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
const source = readFileSync(BUNDLED_RENDERER);
|
|
1469
|
+
fileURLToPath(new URL("../dist/render.mjs", import.meta.url));
|
|
1470
|
+
var BUNDLED_RESOLVER = fileURLToPath(new URL("../dist/resolver.mjs", import.meta.url));
|
|
1471
|
+
function syncResolver(home) {
|
|
1472
|
+
const source = readFileSync(BUNDLED_RESOLVER);
|
|
1475
1473
|
const dest = renderMjsPath(home);
|
|
1476
1474
|
if (existsSync(dest) && readFileSync(dest).equals(source)) return;
|
|
1477
1475
|
mkdirSync(dirname(dest), { recursive: true });
|
|
@@ -1480,7 +1478,7 @@ function syncRenderer(home) {
|
|
|
1480
1478
|
renameSync(tmp, dest);
|
|
1481
1479
|
}
|
|
1482
1480
|
var SETTINGS_KEYS = ["statusLine", "subagentStatusLine"];
|
|
1483
|
-
function isObject(value) {
|
|
1481
|
+
function isObject$1(value) {
|
|
1484
1482
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1485
1483
|
}
|
|
1486
1484
|
function readOrNull(file) {
|
|
@@ -1498,11 +1496,11 @@ function parseSettings(file, raw) {
|
|
|
1498
1496
|
} catch (e) {
|
|
1499
1497
|
throw new Error(`${file} is not valid JSON`, { cause: e });
|
|
1500
1498
|
}
|
|
1501
|
-
if (!isObject(parsed)) throw new Error(`${file} does not hold a settings object`);
|
|
1499
|
+
if (!isObject$1(parsed)) throw new Error(`${file} does not hold a settings object`);
|
|
1502
1500
|
return parsed;
|
|
1503
1501
|
}
|
|
1504
1502
|
function memberCommand(value) {
|
|
1505
|
-
if (!isObject(value) || value.type !== "command") return null;
|
|
1503
|
+
if (!isObject$1(value) || value.type !== "command") return null;
|
|
1506
1504
|
return typeof value.command === "string" ? value.command : null;
|
|
1507
1505
|
}
|
|
1508
1506
|
function isOurMember(key, value) {
|
|
@@ -1722,7 +1720,7 @@ function configure(options) {
|
|
|
1722
1720
|
}, options.force ?? false);
|
|
1723
1721
|
if (plan.adds.length > 0 || plan.repoints.length > 0) writeBackupIfAbsent(options.home, plan);
|
|
1724
1722
|
commitSettings(plan);
|
|
1725
|
-
|
|
1723
|
+
syncResolver(options.home);
|
|
1726
1724
|
}
|
|
1727
1725
|
//#endregion
|
|
1728
1726
|
//#region src/demo-repo.ts
|
|
@@ -2031,7 +2029,7 @@ var PANEL_STEP_S = 300;
|
|
|
2031
2029
|
function anchoredP1(repoDir, now) {
|
|
2032
2030
|
const payload = JSON.parse(readFileSync(join(PAYLOADS_DIR, "p1.json"), "utf8"));
|
|
2033
2031
|
const cache = payload.prompt_cache;
|
|
2034
|
-
if (isObject(cache)) {
|
|
2032
|
+
if (isObject$1(cache)) {
|
|
2035
2033
|
if (cache.warm === true) {
|
|
2036
2034
|
cache.expires_at = now + P1_WARM_IN;
|
|
2037
2035
|
cache.last_miss_at = now + P1_WARM_IN - 3900;
|
|
@@ -2041,9 +2039,9 @@ function anchoredP1(repoDir, now) {
|
|
|
2041
2039
|
}
|
|
2042
2040
|
}
|
|
2043
2041
|
const limits = payload.rate_limits;
|
|
2044
|
-
if (isObject(limits)) for (const [limitKey, offset] of Object.entries(P1_RESETS_IN)) {
|
|
2042
|
+
if (isObject$1(limits)) for (const [limitKey, offset] of Object.entries(P1_RESETS_IN)) {
|
|
2045
2043
|
const limit = limits[limitKey];
|
|
2046
|
-
if (isObject(limit)) limit.resets_at = now + offset;
|
|
2044
|
+
if (isObject$1(limit)) limit.resets_at = now + offset;
|
|
2047
2045
|
}
|
|
2048
2046
|
payload.workspace.current_dir = repoDir;
|
|
2049
2047
|
return `${JSON.stringify(payload, null, 2)}\n`;
|
|
@@ -2154,7 +2152,7 @@ function readBackup(home) {
|
|
|
2154
2152
|
} catch (e) {
|
|
2155
2153
|
throw new Error(`${file} is not valid JSON`, { cause: e });
|
|
2156
2154
|
}
|
|
2157
|
-
if (!isObject(parsed) || typeof parsed.createdFile !== "boolean" || !isObject(parsed.keys)) throw new Error(`${file} is not a lab backup`);
|
|
2155
|
+
if (!isObject$1(parsed) || typeof parsed.createdFile !== "boolean" || !isObject$1(parsed.keys)) throw new Error(`${file} is not a lab backup`);
|
|
2158
2156
|
const keys = {};
|
|
2159
2157
|
for (const key of SETTINGS_KEYS) {
|
|
2160
2158
|
const saved = parsed.keys[key];
|
|
@@ -2263,6 +2261,60 @@ function restore(options) {
|
|
|
2263
2261
|
};
|
|
2264
2262
|
}
|
|
2265
2263
|
//#endregion
|
|
2264
|
+
//#region src/render/install-record.ts
|
|
2265
|
+
var RECORD_FILE = join(".claude", "plugins", "installed_plugins.json");
|
|
2266
|
+
var UNREADABLE = "installed_plugins.json unreadable";
|
|
2267
|
+
var NONE_USABLE = "no statusline@agentic install with a render.mjs";
|
|
2268
|
+
function isObject(value) {
|
|
2269
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2270
|
+
}
|
|
2271
|
+
function ourEntries(home) {
|
|
2272
|
+
let raw;
|
|
2273
|
+
try {
|
|
2274
|
+
raw = readFileSync(join(home, RECORD_FILE), "utf8");
|
|
2275
|
+
} catch {
|
|
2276
|
+
return null;
|
|
2277
|
+
}
|
|
2278
|
+
let parsed;
|
|
2279
|
+
try {
|
|
2280
|
+
parsed = JSON.parse(raw);
|
|
2281
|
+
} catch {
|
|
2282
|
+
return null;
|
|
2283
|
+
}
|
|
2284
|
+
const plugins = isObject(parsed) ? parsed.plugins : void 0;
|
|
2285
|
+
const ours = isObject(plugins) ? plugins["statusline@agentic"] : null;
|
|
2286
|
+
return Array.isArray(ours) ? ours : ours === void 0 ? [] : null;
|
|
2287
|
+
}
|
|
2288
|
+
function stamp(value) {
|
|
2289
|
+
const ms = typeof value === "string" ? Date.parse(value) : NaN;
|
|
2290
|
+
return Number.isNaN(ms) ? 0 : ms;
|
|
2291
|
+
}
|
|
2292
|
+
function isNewer(candidate, incumbent) {
|
|
2293
|
+
const updated = stamp(candidate.lastUpdated) - stamp(incumbent.lastUpdated);
|
|
2294
|
+
return updated > 0 || updated === 0 && stamp(candidate.installedAt) > stamp(incumbent.installedAt);
|
|
2295
|
+
}
|
|
2296
|
+
function usable(value) {
|
|
2297
|
+
return isObject(value) && typeof value.installPath === "string" && existsSync(join(value.installPath, "render.mjs"));
|
|
2298
|
+
}
|
|
2299
|
+
function resolveInstall(home) {
|
|
2300
|
+
const entries = ourEntries(home);
|
|
2301
|
+
if (entries === null) return {
|
|
2302
|
+
kind: "unresolved",
|
|
2303
|
+
reason: UNREADABLE
|
|
2304
|
+
};
|
|
2305
|
+
const installed = entries.filter(usable);
|
|
2306
|
+
if (installed.length === 0) return {
|
|
2307
|
+
kind: "unresolved",
|
|
2308
|
+
reason: NONE_USABLE
|
|
2309
|
+
};
|
|
2310
|
+
const best = installed.reduce((incumbent, candidate) => isNewer(candidate, incumbent) ? candidate : incumbent);
|
|
2311
|
+
return {
|
|
2312
|
+
installPath: best.installPath,
|
|
2313
|
+
kind: "resolved",
|
|
2314
|
+
version: typeof best.version === "string" ? best.version : basename(best.installPath)
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
//#endregion
|
|
2266
2318
|
//#region src/status.ts
|
|
2267
2319
|
function keyState(key, value) {
|
|
2268
2320
|
if (value === void 0) return { kind: "absent" };
|
|
@@ -2319,33 +2371,25 @@ function driftFindings(config) {
|
|
|
2319
2371
|
function nodeOnPath(pathVar) {
|
|
2320
2372
|
return pathVar.split(delimiter).filter((dir) => dir !== "").map((dir) => join(dir, "node")).find(existsSync) ?? null;
|
|
2321
2373
|
}
|
|
2322
|
-
function rendererHash(bytes) {
|
|
2323
|
-
return createHash("sha256").update(bytes).digest("hex").slice(0, 12);
|
|
2324
|
-
}
|
|
2325
2374
|
function rendererState(home) {
|
|
2326
|
-
if (!existsSync(
|
|
2327
|
-
const
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
return hash === rendererHash(cli) ? {
|
|
2332
|
-
hash,
|
|
2333
|
-
kind: "current"
|
|
2375
|
+
if (!existsSync(renderMjsPath(home))) return { kind: "missing" };
|
|
2376
|
+
const record = resolveInstall(home);
|
|
2377
|
+
return record.kind === "resolved" ? {
|
|
2378
|
+
kind: "resolves",
|
|
2379
|
+
version: record.version
|
|
2334
2380
|
} : {
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
kind: "stale"
|
|
2381
|
+
kind: "unresolved",
|
|
2382
|
+
reason: record.reason
|
|
2338
2383
|
};
|
|
2339
2384
|
}
|
|
2340
|
-
function rendererRow(state) {
|
|
2341
|
-
if (state.kind === "
|
|
2342
|
-
if (state.kind === "
|
|
2343
|
-
|
|
2344
|
-
return "renderer: missing — fix: rerun configure --force --theme classic";
|
|
2385
|
+
function rendererRow(state, theme) {
|
|
2386
|
+
if (state.kind === "resolves") return `renderer: resolves → ${state.version}`;
|
|
2387
|
+
if (state.kind === "missing") return `renderer: missing — fix: rerun configure --force --theme ${theme}`;
|
|
2388
|
+
return `renderer: unresolved — ${state.reason} — fix: claude plugin install statusline@agentic`;
|
|
2345
2389
|
}
|
|
2346
|
-
function configRow(findings) {
|
|
2390
|
+
function configRow(findings, theme) {
|
|
2347
2391
|
if (findings.length === 0) return "config: no drift";
|
|
2348
|
-
return `config: drift — ${findings.map(findingText).join(", ")} — fix: rerun configure --theme
|
|
2392
|
+
return `config: drift — ${findings.map(findingText).join(", ")} — fix: rerun configure --theme ${theme}`;
|
|
2349
2393
|
}
|
|
2350
2394
|
function backupRow(home) {
|
|
2351
2395
|
let backup;
|
|
@@ -2386,20 +2430,21 @@ function status(options) {
|
|
|
2386
2430
|
const main = keyState("statusLine", members.statusLine);
|
|
2387
2431
|
const subagent = keyState("subagentStatusLine", members.subagentStatusLine);
|
|
2388
2432
|
const config = readKeyConfig(options.home);
|
|
2433
|
+
const theme = config.theme ?? "classic";
|
|
2389
2434
|
const panelConfig = parsePanelCommand(memberCommand(members.subagentStatusLine));
|
|
2390
2435
|
const findings = main.kind === "ours" ? driftFindings(config) : [];
|
|
2391
2436
|
const node = nodeOnPath(options.path ?? process.env.PATH ?? "");
|
|
2392
2437
|
const renderer = rendererState(options.home);
|
|
2393
2438
|
const rows = [
|
|
2394
2439
|
node === null ? "node: missing — fix: install node ≥ 18 from nodejs.org, then restart Claude Code" : `node: on PATH (${node})`,
|
|
2395
|
-
rendererRow(renderer),
|
|
2440
|
+
rendererRow(renderer, theme),
|
|
2396
2441
|
keyRow("statusLine", main, configDetail(config)),
|
|
2397
2442
|
keyRow("subagentStatusLine", subagent, panelDetail(panelConfig)),
|
|
2398
|
-
...main.kind === "ours" ? [configRow(findings), ...themeRow(config)] : [],
|
|
2443
|
+
...main.kind === "ours" ? [configRow(findings, theme), ...themeRow(config)] : [],
|
|
2399
2444
|
backupRow(options.home),
|
|
2400
2445
|
capturesRow(options.home)
|
|
2401
2446
|
];
|
|
2402
|
-
const healthy = node !== null && renderer.kind === "
|
|
2447
|
+
const healthy = node !== null && renderer.kind === "resolves" && main.kind === "ours" && subagent.kind === "ours" && findings.length === 0;
|
|
2403
2448
|
return {
|
|
2404
2449
|
healthy,
|
|
2405
2450
|
rows: [...rows, healthy ? "healthy" : "unhealthy"]
|