@v1nvn/statusline 0.28.0 → 0.30.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 +69 -47
- package/dist/index.js +1553 -397
- package/dist/index.js.map +1 -1
- package/dist/render.mjs +1452 -0
- package/dist/render.mjs.map +1 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
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 {
|
|
5
|
-
import { Command } from "commander";
|
|
4
|
+
import { delimiter, dirname, join, sep } from "node:path";
|
|
6
5
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
6
|
+
import { Command } from "commander";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
8
9
|
//#region ../core/dist/index.js
|
|
9
10
|
function parseQuietly(program, args, recover) {
|
|
10
11
|
try {
|
|
@@ -23,6 +24,1090 @@ function printUsageAndExit(program) {
|
|
|
23
24
|
}
|
|
24
25
|
Number.MAX_SAFE_INTEGER;
|
|
25
26
|
//#endregion
|
|
27
|
+
//#region src/render/git.ts
|
|
28
|
+
var BRANCH_HEAD = "# branch.head ";
|
|
29
|
+
var BRANCH_AB = /^# branch\.ab \+(\d+) -(\d+)$/;
|
|
30
|
+
function gitEnv(env) {
|
|
31
|
+
return {
|
|
32
|
+
HOME: env.home,
|
|
33
|
+
LC_ALL: "C",
|
|
34
|
+
PATH: process.env.PATH ?? ""
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function gitText(dir, args, env) {
|
|
38
|
+
const run = spawnSync("git", [
|
|
39
|
+
"-C",
|
|
40
|
+
dir,
|
|
41
|
+
...args
|
|
42
|
+
], {
|
|
43
|
+
encoding: "utf8",
|
|
44
|
+
env: gitEnv(env)
|
|
45
|
+
});
|
|
46
|
+
return run.status === 0 ? run.stdout : null;
|
|
47
|
+
}
|
|
48
|
+
function lineCount(text) {
|
|
49
|
+
return text === null || text === "" ? 0 : (text.match(/\n/g) ?? []).length;
|
|
50
|
+
}
|
|
51
|
+
function count(text) {
|
|
52
|
+
const parsed = text === null ? NaN : Number(text.trim());
|
|
53
|
+
return Number.isFinite(parsed) ? Math.trunc(parsed) : 0;
|
|
54
|
+
}
|
|
55
|
+
function readGit(dir, env) {
|
|
56
|
+
const facts = {
|
|
57
|
+
ahead: 0,
|
|
58
|
+
behind: 0,
|
|
59
|
+
branch: "",
|
|
60
|
+
modified: 0,
|
|
61
|
+
staged: 0,
|
|
62
|
+
stashes: 0,
|
|
63
|
+
untracked: 0
|
|
64
|
+
};
|
|
65
|
+
if (dir === "") return facts;
|
|
66
|
+
if (spawnSync("git", [
|
|
67
|
+
"-C",
|
|
68
|
+
dir,
|
|
69
|
+
"rev-parse",
|
|
70
|
+
"--git-dir"
|
|
71
|
+
], {
|
|
72
|
+
encoding: "utf8",
|
|
73
|
+
env: gitEnv(env)
|
|
74
|
+
}).status !== 0) return facts;
|
|
75
|
+
const status = gitText(dir, [
|
|
76
|
+
"status",
|
|
77
|
+
"--porcelain=v2",
|
|
78
|
+
"--branch"
|
|
79
|
+
], env);
|
|
80
|
+
for (const line of (status ?? "").split("\n")) {
|
|
81
|
+
if (line.startsWith(BRANCH_HEAD)) {
|
|
82
|
+
facts.branch = line.slice(14);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const ab = BRANCH_AB.exec(line);
|
|
86
|
+
if (ab !== null) {
|
|
87
|
+
facts.ahead = count(ab[1]);
|
|
88
|
+
facts.behind = count(ab[2]);
|
|
89
|
+
} else if ((line.startsWith("1 ") || line.startsWith("2 ")) && line.length >= 4) {
|
|
90
|
+
if (line[2] !== ".") facts.staged += 1;
|
|
91
|
+
if (line[3] !== ".") facts.modified += 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (facts.branch === "(detached)") facts.branch = "";
|
|
95
|
+
facts.untracked = lineCount(gitText(dir, [
|
|
96
|
+
"ls-files",
|
|
97
|
+
"--others",
|
|
98
|
+
"--exclude-standard"
|
|
99
|
+
], env));
|
|
100
|
+
facts.stashes = lineCount(gitText(dir, ["stash", "list"], env));
|
|
101
|
+
return facts;
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/render/items.ts
|
|
105
|
+
var ITEMS = [
|
|
106
|
+
{
|
|
107
|
+
alternatives: [
|
|
108
|
+
"plain",
|
|
109
|
+
"block",
|
|
110
|
+
"pill",
|
|
111
|
+
"zen"
|
|
112
|
+
],
|
|
113
|
+
default: "plain",
|
|
114
|
+
item: "model"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
alternatives: [
|
|
118
|
+
"plain",
|
|
119
|
+
"dim",
|
|
120
|
+
"hidden"
|
|
121
|
+
],
|
|
122
|
+
default: "plain",
|
|
123
|
+
item: "effort"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
alternatives: ["none", "pills"],
|
|
127
|
+
default: "none",
|
|
128
|
+
item: "state"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
alternatives: [
|
|
132
|
+
"init",
|
|
133
|
+
"full",
|
|
134
|
+
"tail",
|
|
135
|
+
"base",
|
|
136
|
+
"icon"
|
|
137
|
+
],
|
|
138
|
+
default: "init",
|
|
139
|
+
item: "cwd"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
alternatives: [
|
|
143
|
+
"initials",
|
|
144
|
+
"full",
|
|
145
|
+
"last",
|
|
146
|
+
"icon",
|
|
147
|
+
"none"
|
|
148
|
+
],
|
|
149
|
+
default: "initials",
|
|
150
|
+
item: "branch"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
alternatives: [
|
|
154
|
+
"counts",
|
|
155
|
+
"icons",
|
|
156
|
+
"none"
|
|
157
|
+
],
|
|
158
|
+
default: "counts",
|
|
159
|
+
item: "status"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
alternatives: ["none", "arrows"],
|
|
163
|
+
default: "none",
|
|
164
|
+
item: "ahead"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
alternatives: ["none", "badge"],
|
|
168
|
+
default: "none",
|
|
169
|
+
item: "pr"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
alternatives: [
|
|
173
|
+
"flat",
|
|
174
|
+
"gauge",
|
|
175
|
+
"percent",
|
|
176
|
+
"none",
|
|
177
|
+
"flat6",
|
|
178
|
+
"flat4"
|
|
179
|
+
],
|
|
180
|
+
default: "flat",
|
|
181
|
+
item: "bar"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
alternatives: [
|
|
185
|
+
"full",
|
|
186
|
+
"compact",
|
|
187
|
+
"free",
|
|
188
|
+
"none"
|
|
189
|
+
],
|
|
190
|
+
default: "full",
|
|
191
|
+
item: "tokens"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
alternatives: [
|
|
195
|
+
"hit",
|
|
196
|
+
"coldin",
|
|
197
|
+
"fuse",
|
|
198
|
+
"none"
|
|
199
|
+
],
|
|
200
|
+
default: "hit",
|
|
201
|
+
item: "cache"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
alternatives: [
|
|
205
|
+
"plain",
|
|
206
|
+
"burn",
|
|
207
|
+
"none"
|
|
208
|
+
],
|
|
209
|
+
default: "plain",
|
|
210
|
+
item: "cost"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
alternatives: [
|
|
214
|
+
"clock",
|
|
215
|
+
"hours",
|
|
216
|
+
"none"
|
|
217
|
+
],
|
|
218
|
+
default: "clock",
|
|
219
|
+
item: "duration"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
alternatives: ["none", "diffstat"],
|
|
223
|
+
default: "none",
|
|
224
|
+
item: "lines"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
alternatives: ["none", "strip"],
|
|
228
|
+
default: "none",
|
|
229
|
+
item: "rate"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
alternatives: [
|
|
233
|
+
"plain",
|
|
234
|
+
"dots",
|
|
235
|
+
"dim",
|
|
236
|
+
"bare"
|
|
237
|
+
],
|
|
238
|
+
default: "plain",
|
|
239
|
+
item: "style"
|
|
240
|
+
}
|
|
241
|
+
];
|
|
242
|
+
var DEFAULT_LAYOUT = "{model effort state} {cwd branch status ahead pr} {bar tokens cache} {cost} {duration} {lines} {rate}";
|
|
243
|
+
var BY_ITEM = new Map(ITEMS.map((spec) => [spec.item, spec]));
|
|
244
|
+
function specFor(item) {
|
|
245
|
+
return BY_ITEM.get(item);
|
|
246
|
+
}
|
|
247
|
+
var ITEM_IDS = ITEMS.map((spec) => spec.item);
|
|
248
|
+
var DEFAULT_PICKS = Object.fromEntries(ITEMS.map(({ default: alt, item }) => [item, alt]));
|
|
249
|
+
var RUNG_ORDERS = {
|
|
250
|
+
bar: [
|
|
251
|
+
"flat",
|
|
252
|
+
"flat6",
|
|
253
|
+
"flat4",
|
|
254
|
+
"percent",
|
|
255
|
+
"none"
|
|
256
|
+
],
|
|
257
|
+
branch: [
|
|
258
|
+
"icon",
|
|
259
|
+
"full",
|
|
260
|
+
"initials",
|
|
261
|
+
"last",
|
|
262
|
+
"none"
|
|
263
|
+
],
|
|
264
|
+
cache: [
|
|
265
|
+
"hit",
|
|
266
|
+
"coldin",
|
|
267
|
+
"none"
|
|
268
|
+
],
|
|
269
|
+
cwd: [
|
|
270
|
+
"icon",
|
|
271
|
+
"full",
|
|
272
|
+
"init",
|
|
273
|
+
"tail",
|
|
274
|
+
"base"
|
|
275
|
+
],
|
|
276
|
+
duration: [
|
|
277
|
+
"clock",
|
|
278
|
+
"hours",
|
|
279
|
+
"none"
|
|
280
|
+
],
|
|
281
|
+
effort: [
|
|
282
|
+
"plain",
|
|
283
|
+
"dim",
|
|
284
|
+
"hidden"
|
|
285
|
+
],
|
|
286
|
+
status: [
|
|
287
|
+
"counts",
|
|
288
|
+
"icons",
|
|
289
|
+
"none"
|
|
290
|
+
],
|
|
291
|
+
tokens: [
|
|
292
|
+
"full",
|
|
293
|
+
"free",
|
|
294
|
+
"compact",
|
|
295
|
+
"none"
|
|
296
|
+
]
|
|
297
|
+
};
|
|
298
|
+
var FULL_STEPS = [
|
|
299
|
+
["duration", "none"],
|
|
300
|
+
["cache", "none"],
|
|
301
|
+
["tokens", "compact"],
|
|
302
|
+
["bar", "flat6"],
|
|
303
|
+
["status", "none"],
|
|
304
|
+
["branch", "initials"],
|
|
305
|
+
["cwd", "init"],
|
|
306
|
+
["branch", "last"],
|
|
307
|
+
["bar", "flat4"],
|
|
308
|
+
["model", "strip"],
|
|
309
|
+
["bar", "percent"],
|
|
310
|
+
["branch", "none"],
|
|
311
|
+
["cwd", "tail"],
|
|
312
|
+
["effort", "hidden"],
|
|
313
|
+
["cwd", "base"],
|
|
314
|
+
["tokens", "none"]
|
|
315
|
+
];
|
|
316
|
+
var L1_STEPS = [
|
|
317
|
+
["status", "none"],
|
|
318
|
+
["branch", "initials"],
|
|
319
|
+
["cwd", "init"],
|
|
320
|
+
["branch", "last"],
|
|
321
|
+
["model", "strip"],
|
|
322
|
+
["branch", "none"],
|
|
323
|
+
["cwd", "tail"],
|
|
324
|
+
["effort", "hidden"],
|
|
325
|
+
["cwd", "base"]
|
|
326
|
+
];
|
|
327
|
+
var L2_STEPS = [
|
|
328
|
+
["duration", "none"],
|
|
329
|
+
["cache", "none"],
|
|
330
|
+
["tokens", "compact"],
|
|
331
|
+
["bar", "flat6"],
|
|
332
|
+
["bar", "flat4"],
|
|
333
|
+
["tokens", "none"],
|
|
334
|
+
["bar", "percent"]
|
|
335
|
+
];
|
|
336
|
+
//#endregion
|
|
337
|
+
//#region src/render/layout.ts
|
|
338
|
+
function parseClusters(layout) {
|
|
339
|
+
const clusters = [];
|
|
340
|
+
let words = [];
|
|
341
|
+
let word = "";
|
|
342
|
+
let open = false;
|
|
343
|
+
function pushWord() {
|
|
344
|
+
if (word !== "") {
|
|
345
|
+
words.push(word);
|
|
346
|
+
word = "";
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
for (const c of layout) if (c === "{") {
|
|
350
|
+
if (open) throw new Error(`layout '${layout}': '{' inside a cluster`);
|
|
351
|
+
open = true;
|
|
352
|
+
words = [];
|
|
353
|
+
} else if (c === "}") {
|
|
354
|
+
if (!open) throw new Error(`layout '${layout}': '}' outside a cluster`);
|
|
355
|
+
pushWord();
|
|
356
|
+
open = false;
|
|
357
|
+
if (words.length > 0) clusters.push(words);
|
|
358
|
+
} else if (c === " ") {
|
|
359
|
+
if (open) pushWord();
|
|
360
|
+
else if (word !== "") throw new Error(`layout '${layout}': '${word}' sits outside a cluster`);
|
|
361
|
+
} else if (/[a-z0-9]/.test(c)) word += c;
|
|
362
|
+
else throw new Error(`layout '${layout}': '${c}' is not layout grammar (braces, item ids, spaces)`);
|
|
363
|
+
if (open) throw new Error(`layout '${layout}': unterminated cluster`);
|
|
364
|
+
if (word !== "") throw new Error(`layout '${layout}': '${word}' sits outside a cluster`);
|
|
365
|
+
if (clusters.length === 0) throw new Error(`layout '${layout}': no clusters`);
|
|
366
|
+
return clusters;
|
|
367
|
+
}
|
|
368
|
+
function layoutItemsOf(layout) {
|
|
369
|
+
const items = [];
|
|
370
|
+
for (const cluster of parseClusters(layout)) for (const item of cluster) if (!items.includes(item)) items.push(item);
|
|
371
|
+
return items;
|
|
372
|
+
}
|
|
373
|
+
function layoutItems(layout, valid) {
|
|
374
|
+
const known = new Set(valid);
|
|
375
|
+
const items = layoutItemsOf(layout);
|
|
376
|
+
for (const item of items) if (!known.has(item)) throw new Error(`unknown layout item '${item}' — valid items: ${valid.join(" ")}`);
|
|
377
|
+
return items;
|
|
378
|
+
}
|
|
379
|
+
//#endregion
|
|
380
|
+
//#region src/render/jq.ts
|
|
381
|
+
function isRecord(value) {
|
|
382
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
383
|
+
}
|
|
384
|
+
function orElse(value, fallback) {
|
|
385
|
+
return value === void 0 || value === null || value === false ? fallback : value;
|
|
386
|
+
}
|
|
387
|
+
function jqText(value) {
|
|
388
|
+
if (typeof value === "string") return value;
|
|
389
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
390
|
+
return JSON.stringify(value);
|
|
391
|
+
}
|
|
392
|
+
function tsvEscape(text) {
|
|
393
|
+
return text.replace(/\\/g, "\\\\").replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
394
|
+
}
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region src/render/payload.ts
|
|
397
|
+
function member(source, key) {
|
|
398
|
+
return isRecord(source) ? source[key] : void 0;
|
|
399
|
+
}
|
|
400
|
+
function field$1(source, path) {
|
|
401
|
+
let current = source;
|
|
402
|
+
for (const key of path.split(".")) current = member(current, key);
|
|
403
|
+
return current;
|
|
404
|
+
}
|
|
405
|
+
function textOf(value) {
|
|
406
|
+
if (typeof value === "string") return value;
|
|
407
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
408
|
+
return "";
|
|
409
|
+
}
|
|
410
|
+
function scalar(source, path, fallback) {
|
|
411
|
+
return textOf(orElse(field$1(source, path), fallback));
|
|
412
|
+
}
|
|
413
|
+
function numberField(source, path) {
|
|
414
|
+
return Number(scalar(source, path, "0"));
|
|
415
|
+
}
|
|
416
|
+
function pctField(source) {
|
|
417
|
+
const intPart = scalar(source, "context_window.used_percentage", "0").split(".")[0];
|
|
418
|
+
return intPart === "" ? 0 : Number(intPart);
|
|
419
|
+
}
|
|
420
|
+
var RATE_LIMITS = [
|
|
421
|
+
["5h", "five_hour"],
|
|
422
|
+
["7d", "seven_day"],
|
|
423
|
+
["spend", "spend_limit"]
|
|
424
|
+
];
|
|
425
|
+
function rateRows(source) {
|
|
426
|
+
const limits = field$1(source, "rate_limits");
|
|
427
|
+
const rows = [];
|
|
428
|
+
for (const [label, key] of RATE_LIMITS) {
|
|
429
|
+
const limit = member(limits, key);
|
|
430
|
+
if (!isRecord(limit) || Object.keys(limit).length === 0) continue;
|
|
431
|
+
rows.push({
|
|
432
|
+
label,
|
|
433
|
+
pctText: scalar(limit, "used_percentage", "0"),
|
|
434
|
+
resets: Number(scalar(limit, "resets_at", "0"))
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
return rows;
|
|
438
|
+
}
|
|
439
|
+
function parseRow(payload) {
|
|
440
|
+
const root = JSON.parse(payload);
|
|
441
|
+
const hitText = scalar(root, "prompt_cache.hit_ratio", "");
|
|
442
|
+
return {
|
|
443
|
+
agent: scalar(root, "agent.name", ""),
|
|
444
|
+
cost: numberField(root, "cost.total_cost_usd"),
|
|
445
|
+
ctxSize: numberField(root, "context_window.context_window_size"),
|
|
446
|
+
dir: scalar(root, "workspace.current_dir", ""),
|
|
447
|
+
durationMs: numberField(root, "cost.total_duration_ms"),
|
|
448
|
+
effort: scalar(root, "effort.level", ""),
|
|
449
|
+
expires: numberField(root, "prompt_cache.expires_at"),
|
|
450
|
+
hit: hitText === "" ? null : Number(hitText),
|
|
451
|
+
la: numberField(root, "cost.total_lines_added"),
|
|
452
|
+
lr: numberField(root, "cost.total_lines_removed"),
|
|
453
|
+
model: scalar(root, "model.display_name", ""),
|
|
454
|
+
pct: pctField(root),
|
|
455
|
+
prn: scalar(root, "pr.number", ""),
|
|
456
|
+
prs: scalar(root, "pr.review_state", ""),
|
|
457
|
+
rateRows: rateRows(root),
|
|
458
|
+
think: scalar(root, "thinking.enabled", "false") === "true",
|
|
459
|
+
tokens: numberField(root, "context_window.total_input_tokens"),
|
|
460
|
+
ttl: scalar(root, "prompt_cache.ttl", ""),
|
|
461
|
+
vim: scalar(root, "vim.mode", ""),
|
|
462
|
+
warm: scalar(root, "prompt_cache.warm", "false") === "true",
|
|
463
|
+
wt: scalar(root, "worktree.name", ""),
|
|
464
|
+
styleName: scalar(root, "output_style.name", "")
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region src/render/awk.ts
|
|
469
|
+
var TEN = 10n;
|
|
470
|
+
function exactParts(ax) {
|
|
471
|
+
const view = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(8));
|
|
472
|
+
view.setFloat64(0, ax);
|
|
473
|
+
const hi = view.getUint32(0);
|
|
474
|
+
const lo = view.getUint32(4);
|
|
475
|
+
const biased = hi >>> 20 & 2047;
|
|
476
|
+
const frac = BigInt(hi & 1048575) << 32n | BigInt(lo);
|
|
477
|
+
if (biased === 0) return {
|
|
478
|
+
exp: -1074,
|
|
479
|
+
mant: frac
|
|
480
|
+
};
|
|
481
|
+
return {
|
|
482
|
+
exp: biased - 1075,
|
|
483
|
+
mant: frac | 1n << 52n
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function roundScaled(ax, d) {
|
|
487
|
+
const { exp, mant } = exactParts(ax);
|
|
488
|
+
const pow10 = TEN ** BigInt(d);
|
|
489
|
+
const num = exp >= 0 ? (mant << BigInt(exp)) * pow10 : mant * pow10;
|
|
490
|
+
const den = exp >= 0 ? 1n : 1n << BigInt(-exp);
|
|
491
|
+
const q = num / den;
|
|
492
|
+
const twice = num % den * 2n;
|
|
493
|
+
if (twice > den || twice === den && q % 2n === 1n) return q + 1n;
|
|
494
|
+
return q;
|
|
495
|
+
}
|
|
496
|
+
function fmtFixed(x, d) {
|
|
497
|
+
const q = roundScaled(Math.abs(x), d);
|
|
498
|
+
const sign = x < 0 && q !== 0n ? "-" : "";
|
|
499
|
+
const digits = q.toString();
|
|
500
|
+
if (d === 0) return `${sign}${digits}`;
|
|
501
|
+
return `${sign}${digits.length <= d ? "0" : digits.slice(0, digits.length - d)}.${digits.padStart(d + 1, "0").slice(-d)}`;
|
|
502
|
+
}
|
|
503
|
+
function pad2(n) {
|
|
504
|
+
return String(n).padStart(2, "0");
|
|
505
|
+
}
|
|
506
|
+
function fmtK(n, d) {
|
|
507
|
+
return `${fmtFixed(n / 1e3, d)}k`;
|
|
508
|
+
}
|
|
509
|
+
function fmtM(n) {
|
|
510
|
+
return `${fmtFixed(n / 1e6, 0)}M`;
|
|
511
|
+
}
|
|
512
|
+
//#endregion
|
|
513
|
+
//#region src/render/segments.ts
|
|
514
|
+
var BLUE = "\x1B[34m";
|
|
515
|
+
var CYAN$1 = "\x1B[36m";
|
|
516
|
+
var DIM = "\x1B[2m";
|
|
517
|
+
var GREEN$1 = "\x1B[32m";
|
|
518
|
+
var PURPLE = "\x1B[35m";
|
|
519
|
+
var RED$1 = "\x1B[31m";
|
|
520
|
+
var RESET$1 = "\x1B[0m";
|
|
521
|
+
var YELLOW$1 = "\x1B[33m";
|
|
522
|
+
var BOLD = "\x1B[1m";
|
|
523
|
+
var GRAY = "\x1B[38;2;68;71;90m";
|
|
524
|
+
var MARK = "\x1B[1;97m";
|
|
525
|
+
var SHORT_AT = 15;
|
|
526
|
+
var PART = [
|
|
527
|
+
"▏",
|
|
528
|
+
"▎",
|
|
529
|
+
"▍",
|
|
530
|
+
"▌",
|
|
531
|
+
"▋",
|
|
532
|
+
"▊",
|
|
533
|
+
"▉",
|
|
534
|
+
"█"
|
|
535
|
+
];
|
|
536
|
+
var HEAT = [
|
|
537
|
+
"\x1B[38;2;48;242;36m",
|
|
538
|
+
"\x1B[38;2;63;242;36m",
|
|
539
|
+
"\x1B[38;2;79;242;36m",
|
|
540
|
+
"\x1B[38;2;95;242;36m",
|
|
541
|
+
"\x1B[38;2;111;242;36m",
|
|
542
|
+
"\x1B[38;2;126;242;36m",
|
|
543
|
+
"\x1B[38;2;142;242;36m",
|
|
544
|
+
"\x1B[38;2;158;242;36m",
|
|
545
|
+
"\x1B[38;2;173;242;36m",
|
|
546
|
+
"\x1B[38;2;189;242;36m",
|
|
547
|
+
"\x1B[38;2;205;242;36m",
|
|
548
|
+
"\x1B[38;2;220;242;36m",
|
|
549
|
+
"\x1B[38;2;236;242;36m",
|
|
550
|
+
"\x1B[38;2;242;232;36m",
|
|
551
|
+
"\x1B[38;2;242;216;36m",
|
|
552
|
+
"\x1B[38;2;242;200;36m",
|
|
553
|
+
"\x1B[38;2;242;185;36m",
|
|
554
|
+
"\x1B[38;2;242;169;36m",
|
|
555
|
+
"\x1B[38;2;242;153;36m",
|
|
556
|
+
"\x1B[38;2;242;138;36m",
|
|
557
|
+
"\x1B[38;2;242;122;36m",
|
|
558
|
+
"\x1B[38;2;242;106;36m",
|
|
559
|
+
"\x1B[38;2;242;91;36m",
|
|
560
|
+
"\x1B[38;2;242;75;36m",
|
|
561
|
+
"\x1B[38;2;242;59;36m",
|
|
562
|
+
"\x1B[38;2;242;44;36m"
|
|
563
|
+
];
|
|
564
|
+
function trunc(n) {
|
|
565
|
+
return Math.trunc(n);
|
|
566
|
+
}
|
|
567
|
+
function modelPlain({ model }) {
|
|
568
|
+
return model === "" ? "" : `${CYAN$1}${model}${RESET$1}`;
|
|
569
|
+
}
|
|
570
|
+
function modelBlock({ model }) {
|
|
571
|
+
return model === "" ? "" : `\x1b[48;5;61m\x1b[38;5;231m ${model} \x1b[0m`;
|
|
572
|
+
}
|
|
573
|
+
function modelPill({ model }) {
|
|
574
|
+
return model === "" ? "" : `\x1b[48;5;61m\x1b[38;5;231m\u{e0b6} ${model} \u{e0b4}\x1b[0m`;
|
|
575
|
+
}
|
|
576
|
+
function modelZen({ model }) {
|
|
577
|
+
return model === "" ? "" : `${DIM}${model.replace(/[A-Z]/g, (c) => c.toLowerCase())}${RESET$1}`;
|
|
578
|
+
}
|
|
579
|
+
function effortPlain({ row }) {
|
|
580
|
+
return row.effort === "" ? "" : `${CYAN$1}${row.effort}${RESET$1}`;
|
|
581
|
+
}
|
|
582
|
+
function effortDim({ row }) {
|
|
583
|
+
return row.effort === "" ? "" : `${DIM}${row.effort}${RESET$1}`;
|
|
584
|
+
}
|
|
585
|
+
function statePill(label, bg, fg) {
|
|
586
|
+
return `\x1b[48;5;${bg}m\x1b[38;5;${fg}m\u{e0b6} ${label} \u{e0b4}\x1b[0m`;
|
|
587
|
+
}
|
|
588
|
+
function statePills({ row }) {
|
|
589
|
+
let out = "";
|
|
590
|
+
if (row.vim !== "") out = row.vim === "INSERT" ? statePill(row.vim, 97, 16) : statePill(row.vim, 240, 231);
|
|
591
|
+
if (row.think) out += ` ${statePill("THINK", 66, 16)}`;
|
|
592
|
+
if (row.agent !== "") out += ` ${statePill(row.agent, 60, 231)}`;
|
|
593
|
+
if (row.wt !== "") out += ` ${statePill(`wt:${row.wt}`, 131, 231)}`;
|
|
594
|
+
if (row.styleName !== "" && row.styleName !== "default") out += ` ${statePill(row.styleName, 95, 16)}`;
|
|
595
|
+
return out;
|
|
596
|
+
}
|
|
597
|
+
function splitPath(raw, home) {
|
|
598
|
+
let p = raw;
|
|
599
|
+
if (home !== "" && p.startsWith(home)) p = `~${p.slice(home.length)}`;
|
|
600
|
+
return {
|
|
601
|
+
lead: p.startsWith("/") ? "/" : "",
|
|
602
|
+
parts: p.split("/").filter((part) => part !== ""),
|
|
603
|
+
text: p
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function pathInit(raw, home) {
|
|
607
|
+
const { lead, parts, text } = splitPath(raw, home);
|
|
608
|
+
const n = parts.length;
|
|
609
|
+
if (n <= 2) return text;
|
|
610
|
+
let out = `${lead}${parts[0]}`;
|
|
611
|
+
for (let i = 1; i < n - 1; i++) {
|
|
612
|
+
const ini = parts[i].startsWith(".") ? parts[i].slice(0, 2) : parts[i].slice(0, 1);
|
|
613
|
+
out += `/${ini}`;
|
|
614
|
+
}
|
|
615
|
+
return `${out}/${parts[n - 1]}`;
|
|
616
|
+
}
|
|
617
|
+
function pathTail(raw, home, lim) {
|
|
618
|
+
const { lead, parts, text } = splitPath(raw, home);
|
|
619
|
+
const cnt = parts.length;
|
|
620
|
+
if (cnt <= lim || cnt <= 1) return text;
|
|
621
|
+
let out = `${lead}${parts[0]}/…`;
|
|
622
|
+
for (let i = cnt - lim; i < cnt; i++) out += `/${parts[i]}`;
|
|
623
|
+
return out;
|
|
624
|
+
}
|
|
625
|
+
function cwdInit({ home, row }) {
|
|
626
|
+
const { text } = splitPath(row.dir, home);
|
|
627
|
+
return Buffer.byteLength(text, "utf8") > SHORT_AT ? pathInit(row.dir, home) : text;
|
|
628
|
+
}
|
|
629
|
+
function cwdFull({ home, row }) {
|
|
630
|
+
return splitPath(row.dir, home).text;
|
|
631
|
+
}
|
|
632
|
+
function cwdTail({ home, row }) {
|
|
633
|
+
return pathTail(row.dir, home, 1);
|
|
634
|
+
}
|
|
635
|
+
function baseName(dir) {
|
|
636
|
+
return dir.slice(dir.lastIndexOf("/") + 1);
|
|
637
|
+
}
|
|
638
|
+
function cwdBase({ row }) {
|
|
639
|
+
return baseName(row.dir);
|
|
640
|
+
}
|
|
641
|
+
function cwdIcon({ row }) {
|
|
642
|
+
return `\u{f07b} ${baseName(row.dir)}`;
|
|
643
|
+
}
|
|
644
|
+
function branchInitials({ git }) {
|
|
645
|
+
const branch = git.branch;
|
|
646
|
+
if (branch === "") return "";
|
|
647
|
+
if (Buffer.byteLength(branch, "utf8") <= SHORT_AT) return branch;
|
|
648
|
+
const parts = branch.split("/").filter((part) => part !== "");
|
|
649
|
+
if (parts.length <= 1) return branch;
|
|
650
|
+
let out = "";
|
|
651
|
+
for (let i = 0; i < parts.length - 1; i++) out += `${parts[i].slice(0, 1)}/`;
|
|
652
|
+
return `${out}${parts[parts.length - 1]}`;
|
|
653
|
+
}
|
|
654
|
+
function branchFull({ git }) {
|
|
655
|
+
return git.branch;
|
|
656
|
+
}
|
|
657
|
+
function branchLast({ git }) {
|
|
658
|
+
return git.branch.slice(git.branch.lastIndexOf("/") + 1);
|
|
659
|
+
}
|
|
660
|
+
function branchIcon({ git }) {
|
|
661
|
+
return git.branch === "" ? "" : `\u{e0a0} ${git.branch}`;
|
|
662
|
+
}
|
|
663
|
+
function statusCounts({ git }) {
|
|
664
|
+
if (git.branch === "") return "";
|
|
665
|
+
let c = "";
|
|
666
|
+
if (git.staged > 0) c = `${GREEN$1}+${git.staged}${RESET$1}`;
|
|
667
|
+
if (git.modified > 0) c = `${c} ${YELLOW$1}~${git.modified}${RESET$1}`;
|
|
668
|
+
return c;
|
|
669
|
+
}
|
|
670
|
+
function statusIcons({ git }) {
|
|
671
|
+
if (git.branch === "") return "";
|
|
672
|
+
let out = "";
|
|
673
|
+
let sep = "";
|
|
674
|
+
if (git.staged > 0) {
|
|
675
|
+
out += `${GREEN$1}●${git.staged}${RESET$1}`;
|
|
676
|
+
sep = " ";
|
|
677
|
+
}
|
|
678
|
+
if (git.modified > 0) {
|
|
679
|
+
out += `${sep}${YELLOW$1}✎${git.modified}${RESET$1}`;
|
|
680
|
+
sep = " ";
|
|
681
|
+
}
|
|
682
|
+
if (git.untracked > 0) {
|
|
683
|
+
out += `${sep}${CYAN$1}+${git.untracked}${RESET$1}`;
|
|
684
|
+
sep = " ";
|
|
685
|
+
}
|
|
686
|
+
if (git.stashes > 0) out += `${sep}${PURPLE}⚑${git.stashes}${RESET$1}`;
|
|
687
|
+
return out;
|
|
688
|
+
}
|
|
689
|
+
function aheadArrows({ git }) {
|
|
690
|
+
let out = "";
|
|
691
|
+
if (git.ahead > 0) out = `${GREEN$1}↑${git.ahead}${RESET$1}`;
|
|
692
|
+
if (git.behind > 0) out += ` ${RED$1}↓${git.behind}${RESET$1}`;
|
|
693
|
+
return out;
|
|
694
|
+
}
|
|
695
|
+
function prBadge({ row }) {
|
|
696
|
+
if (row.prn === "") return "";
|
|
697
|
+
let col;
|
|
698
|
+
let mark;
|
|
699
|
+
switch (row.prs) {
|
|
700
|
+
case "approved":
|
|
701
|
+
col = GREEN$1;
|
|
702
|
+
mark = "✓";
|
|
703
|
+
break;
|
|
704
|
+
case "changes_requested":
|
|
705
|
+
col = RED$1;
|
|
706
|
+
mark = "✗";
|
|
707
|
+
break;
|
|
708
|
+
case "pending":
|
|
709
|
+
col = YELLOW$1;
|
|
710
|
+
mark = "⏳";
|
|
711
|
+
break;
|
|
712
|
+
default:
|
|
713
|
+
col = "\x1B[90m";
|
|
714
|
+
mark = "◌";
|
|
715
|
+
}
|
|
716
|
+
return `${BLUE}#${row.prn}${RESET$1} ${col}${mark} ${row.prs}${RESET$1}`;
|
|
717
|
+
}
|
|
718
|
+
function barFlatAt(input, w) {
|
|
719
|
+
const pct = input.row.pct;
|
|
720
|
+
let f = trunc(pct * w / 100);
|
|
721
|
+
if (f > w) f = w;
|
|
722
|
+
return `${pct >= 90 ? RED$1 : pct >= 70 ? YELLOW$1 : GREEN$1}${"█".repeat(f)}${"░".repeat(w - f)}${RESET$1}`;
|
|
723
|
+
}
|
|
724
|
+
function barFlat(input) {
|
|
725
|
+
return barFlatAt(input, 10);
|
|
726
|
+
}
|
|
727
|
+
function barGauge({ row }) {
|
|
728
|
+
const pct = row.pct;
|
|
729
|
+
const p = Math.max(0, Math.min(100, pct));
|
|
730
|
+
const full = trunc(p * 26 / 100);
|
|
731
|
+
const idx = trunc(p * 26 % 100 * 8 / 100);
|
|
732
|
+
const lead = p >= 50 ? `\x1b[38;2;242;${trunc(4845 * (15e3 + 1683 * (100 - p)) / 2e6)};36m` : `\x1b[38;2;${trunc(4845 * (185e3 - 1683 * (100 - p)) / 2e6)};242;36m`;
|
|
733
|
+
const moon = [
|
|
734
|
+
"○",
|
|
735
|
+
"◔",
|
|
736
|
+
"◑",
|
|
737
|
+
"◕",
|
|
738
|
+
"●"
|
|
739
|
+
][Math.min(trunc(p / 20), 4)];
|
|
740
|
+
let bar = "";
|
|
741
|
+
for (let i = 0; i < 26; i++) {
|
|
742
|
+
let col;
|
|
743
|
+
let cell;
|
|
744
|
+
if (i === 20) {
|
|
745
|
+
col = MARK;
|
|
746
|
+
cell = i <= full ? "┃" : "│";
|
|
747
|
+
} else if (i <= full) {
|
|
748
|
+
col = HEAT[i];
|
|
749
|
+
cell = i === full ? PART[idx] : "█";
|
|
750
|
+
} else {
|
|
751
|
+
col = GRAY;
|
|
752
|
+
cell = "░";
|
|
753
|
+
}
|
|
754
|
+
bar += `${col}${cell}`;
|
|
755
|
+
}
|
|
756
|
+
return `${moon} ${lead}${bar}${RESET$1} ${MARK}${pct}%${RESET$1}`;
|
|
757
|
+
}
|
|
758
|
+
function tokensFull({ row }) {
|
|
759
|
+
const t = row.tokens >= 1e3 ? fmtK(row.tokens, 1) : String(row.tokens);
|
|
760
|
+
let c = String(row.ctxSize);
|
|
761
|
+
if (row.ctxSize >= 1e6) c = fmtM(row.ctxSize);
|
|
762
|
+
else if (row.ctxSize >= 1e3) c = fmtK(row.ctxSize, 0);
|
|
763
|
+
return `${t}/${c}`;
|
|
764
|
+
}
|
|
765
|
+
function tokensCompact({ row }) {
|
|
766
|
+
return row.tokens >= 1e3 ? fmtK(row.tokens, 0) : String(row.tokens);
|
|
767
|
+
}
|
|
768
|
+
function tokensFree({ row }) {
|
|
769
|
+
const free = row.ctxSize - row.tokens;
|
|
770
|
+
return `${DIM}${free >= 1e3 ? fmtK(free, 0) : String(free)} free${RESET$1}`;
|
|
771
|
+
}
|
|
772
|
+
function cacheHitPct(row) {
|
|
773
|
+
return row.hit === null ? null : trunc(row.hit * 100);
|
|
774
|
+
}
|
|
775
|
+
function cacheHCol(hp) {
|
|
776
|
+
return hp >= 90 ? GREEN$1 : hp >= 50 ? YELLOW$1 : RED$1;
|
|
777
|
+
}
|
|
778
|
+
function cacheHit({ row }) {
|
|
779
|
+
const hp = cacheHitPct(row);
|
|
780
|
+
return hp === null ? "" : `${cacheHCol(hp)}⚡${hp}%${RESET$1}`;
|
|
781
|
+
}
|
|
782
|
+
function cacheColdin({ now, row }) {
|
|
783
|
+
const hp = cacheHitPct(row);
|
|
784
|
+
if (hp === null) return "";
|
|
785
|
+
if (row.warm && row.expires > now) {
|
|
786
|
+
const mins = trunc((row.expires - now) / 60);
|
|
787
|
+
return `${cacheHCol(hp)}⚡${hp}%${RESET$1} ${DIM}· cold in ${mins}m${RESET$1}`;
|
|
788
|
+
}
|
|
789
|
+
return `❄ ${DIM}cold · ${hp}%${RESET$1}`;
|
|
790
|
+
}
|
|
791
|
+
function cacheFuse({ now, row }) {
|
|
792
|
+
if (row.ttl === "" || row.expires === 0) return "";
|
|
793
|
+
const span = row.ttl === "1h" ? 3600 : 300;
|
|
794
|
+
let left = row.expires - now;
|
|
795
|
+
if (left < 0) left = 0;
|
|
796
|
+
if (left <= 0) return `${RED$1}❄ cold${RESET$1}`;
|
|
797
|
+
const full = trunc(left * 10 / span);
|
|
798
|
+
const rem = left * 10 % span;
|
|
799
|
+
let bar = left * 4 > span ? GREEN$1 : left * 25 > span * 2 ? YELLOW$1 : RED$1;
|
|
800
|
+
for (let i = 0; i < 10; i++) if (i < full) bar += "▰";
|
|
801
|
+
else if (i === full && rem * 20 > span) bar += PART[trunc(rem * 8 / span)];
|
|
802
|
+
else bar += `${DIM}▱`;
|
|
803
|
+
return `${bar}${RESET$1} ${DIM}${pad2(trunc(left / 60))}:${pad2(left % 60)}${RESET$1}`;
|
|
804
|
+
}
|
|
805
|
+
function costPlain({ row }) {
|
|
806
|
+
return row.cost >= .005 ? `${YELLOW$1}$${fmtFixed(row.cost, 2)}${RESET$1}` : "";
|
|
807
|
+
}
|
|
808
|
+
function costBurn(input) {
|
|
809
|
+
const hr = input.row.durationMs / 36e5;
|
|
810
|
+
const burn = hr > .02 ? fmtFixed(input.row.cost / hr, 2) : "0";
|
|
811
|
+
let out = costPlain(input);
|
|
812
|
+
if (burn !== "0.00") out += ` ${DIM}· $${burn}/hr${RESET$1}`;
|
|
813
|
+
return out;
|
|
814
|
+
}
|
|
815
|
+
function durationClock({ row }) {
|
|
816
|
+
return `${trunc(row.durationMs / 6e4)}m${pad2(trunc(row.durationMs % 6e4 / 1e3))}s`;
|
|
817
|
+
}
|
|
818
|
+
function durationHours({ row }) {
|
|
819
|
+
const m = trunc(row.durationMs / 6e4);
|
|
820
|
+
return `${trunc(m / 60)}h${pad2(m % 60)}m`;
|
|
821
|
+
}
|
|
822
|
+
function linesDiffstat({ row }) {
|
|
823
|
+
if (row.la <= 0 && row.lr <= 0) return "";
|
|
824
|
+
return `${GREEN$1}+${row.la}${RESET$1}${DIM}/${RESET$1}${RED$1}−${row.lr}${RESET$1}`;
|
|
825
|
+
}
|
|
826
|
+
function rateStrip({ now, row }) {
|
|
827
|
+
const segs = [];
|
|
828
|
+
for (const limit of row.rateRows) {
|
|
829
|
+
const [intpText, fracp = ""] = limit.pctText.split(".");
|
|
830
|
+
let f = 0;
|
|
831
|
+
let scale = 1;
|
|
832
|
+
for (const digit of fracp) {
|
|
833
|
+
f = f * 10 + Number(digit);
|
|
834
|
+
scale *= 10;
|
|
835
|
+
}
|
|
836
|
+
const intp = Number(intpText);
|
|
837
|
+
const value = intp * scale + f;
|
|
838
|
+
const col = value < 70 * scale ? GREEN$1 : value < 90 * scale ? YELLOW$1 : RED$1;
|
|
839
|
+
const full = trunc(value * 14 / (100 * scale));
|
|
840
|
+
const idx = trunc(value * 14 % (100 * scale) * 8 / (100 * scale));
|
|
841
|
+
let bar = col;
|
|
842
|
+
for (let i = 0; i < 14; i++) if (i < full) bar += "█";
|
|
843
|
+
else if (i === full) bar += PART[idx];
|
|
844
|
+
else bar += `${DIM}░`;
|
|
845
|
+
const lbl = fracp === "" || f * 2 < scale ? intp : f * 2 > scale ? intp + 1 : intp + intp % 2;
|
|
846
|
+
const s = limit.resets - now;
|
|
847
|
+
let dur;
|
|
848
|
+
if (s >= 3600) dur = `${trunc(s / 3600)}h${pad2(trunc(s % 3600 / 60))}m`;
|
|
849
|
+
else {
|
|
850
|
+
const sec = (s % 60 + 60) % 60;
|
|
851
|
+
dur = `${trunc((s - sec) / 60)}m${pad2(sec)}s`;
|
|
852
|
+
}
|
|
853
|
+
segs.push(`${DIM}${limit.label}${RESET$1} ${bar}${RESET$1} ${BOLD}${lbl}%${RESET$1} ${DIM}· resets ${dur}${RESET$1}`);
|
|
854
|
+
}
|
|
855
|
+
return segs.join(` ${DIM}│${RESET$1} `);
|
|
856
|
+
}
|
|
857
|
+
function emptySegment() {
|
|
858
|
+
return "";
|
|
859
|
+
}
|
|
860
|
+
function barFlat6(input) {
|
|
861
|
+
return barFlatAt(input, 6);
|
|
862
|
+
}
|
|
863
|
+
function barFlat4(input) {
|
|
864
|
+
return barFlatAt(input, 4);
|
|
865
|
+
}
|
|
866
|
+
function barPercent({ row }) {
|
|
867
|
+
return `${row.pct}%`;
|
|
868
|
+
}
|
|
869
|
+
var SEGMENTS = {
|
|
870
|
+
ahead: {
|
|
871
|
+
arrows: aheadArrows,
|
|
872
|
+
none: emptySegment
|
|
873
|
+
},
|
|
874
|
+
bar: {
|
|
875
|
+
flat4: barFlat4,
|
|
876
|
+
flat6: barFlat6,
|
|
877
|
+
flat: barFlat,
|
|
878
|
+
gauge: barGauge,
|
|
879
|
+
none: emptySegment,
|
|
880
|
+
percent: barPercent
|
|
881
|
+
},
|
|
882
|
+
branch: {
|
|
883
|
+
full: branchFull,
|
|
884
|
+
icon: branchIcon,
|
|
885
|
+
initials: branchInitials,
|
|
886
|
+
last: branchLast,
|
|
887
|
+
none: emptySegment
|
|
888
|
+
},
|
|
889
|
+
cache: {
|
|
890
|
+
coldin: cacheColdin,
|
|
891
|
+
fuse: cacheFuse,
|
|
892
|
+
hit: cacheHit,
|
|
893
|
+
none: emptySegment
|
|
894
|
+
},
|
|
895
|
+
cost: {
|
|
896
|
+
burn: costBurn,
|
|
897
|
+
none: emptySegment,
|
|
898
|
+
plain: costPlain
|
|
899
|
+
},
|
|
900
|
+
cwd: {
|
|
901
|
+
base: cwdBase,
|
|
902
|
+
full: cwdFull,
|
|
903
|
+
icon: cwdIcon,
|
|
904
|
+
init: cwdInit,
|
|
905
|
+
tail: cwdTail
|
|
906
|
+
},
|
|
907
|
+
duration: {
|
|
908
|
+
clock: durationClock,
|
|
909
|
+
hours: durationHours,
|
|
910
|
+
none: emptySegment
|
|
911
|
+
},
|
|
912
|
+
effort: {
|
|
913
|
+
dim: effortDim,
|
|
914
|
+
hidden: emptySegment,
|
|
915
|
+
plain: effortPlain
|
|
916
|
+
},
|
|
917
|
+
lines: {
|
|
918
|
+
diffstat: linesDiffstat,
|
|
919
|
+
none: emptySegment
|
|
920
|
+
},
|
|
921
|
+
model: {
|
|
922
|
+
block: modelBlock,
|
|
923
|
+
pill: modelPill,
|
|
924
|
+
plain: modelPlain,
|
|
925
|
+
zen: modelZen
|
|
926
|
+
},
|
|
927
|
+
pr: {
|
|
928
|
+
badge: prBadge,
|
|
929
|
+
none: emptySegment
|
|
930
|
+
},
|
|
931
|
+
rate: {
|
|
932
|
+
none: emptySegment,
|
|
933
|
+
strip: rateStrip
|
|
934
|
+
},
|
|
935
|
+
state: {
|
|
936
|
+
none: emptySegment,
|
|
937
|
+
pills: statePills
|
|
938
|
+
},
|
|
939
|
+
status: {
|
|
940
|
+
counts: statusCounts,
|
|
941
|
+
icons: statusIcons,
|
|
942
|
+
none: emptySegment
|
|
943
|
+
},
|
|
944
|
+
tokens: {
|
|
945
|
+
compact: tokensCompact,
|
|
946
|
+
full: tokensFull,
|
|
947
|
+
free: tokensFree,
|
|
948
|
+
none: emptySegment
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
function renderSegment(item, alt, input) {
|
|
952
|
+
const segment = SEGMENTS[item]?.[alt];
|
|
953
|
+
return segment === void 0 ? "" : segment(input);
|
|
954
|
+
}
|
|
955
|
+
function styleSeparators(alt) {
|
|
956
|
+
switch (alt) {
|
|
957
|
+
case "bare": return {
|
|
958
|
+
join: " ",
|
|
959
|
+
sep: " "
|
|
960
|
+
};
|
|
961
|
+
case "dim": return {
|
|
962
|
+
join: " ",
|
|
963
|
+
sep: `${DIM} │ ${RESET$1}`
|
|
964
|
+
};
|
|
965
|
+
case "dots": return {
|
|
966
|
+
join: " · ",
|
|
967
|
+
sep: " · "
|
|
968
|
+
};
|
|
969
|
+
default: return {
|
|
970
|
+
join: " ",
|
|
971
|
+
sep: " │ "
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
//#endregion
|
|
976
|
+
//#region src/render/engine.ts
|
|
977
|
+
var RUNG_ITEMS = Object.keys(RUNG_ORDERS);
|
|
978
|
+
function stripSgr(text) {
|
|
979
|
+
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
980
|
+
}
|
|
981
|
+
function vlen$1(text) {
|
|
982
|
+
const plain = stripSgr(text);
|
|
983
|
+
let n = Array.from(plain).length;
|
|
984
|
+
for (const ch of plain) if (ch === "⚡") n += 1;
|
|
985
|
+
return n;
|
|
986
|
+
}
|
|
987
|
+
function warn(message) {
|
|
988
|
+
process.stderr.write(`${message}\n`);
|
|
989
|
+
}
|
|
990
|
+
function stripModelSuffix(name) {
|
|
991
|
+
const at = name.lastIndexOf("[");
|
|
992
|
+
const cut = at === -1 ? name : name.slice(0, at);
|
|
993
|
+
return cut.endsWith(" ") ? cut.slice(0, -1) : cut;
|
|
994
|
+
}
|
|
995
|
+
function resolvePicks(picks) {
|
|
996
|
+
const resolved = {};
|
|
997
|
+
for (const spec of ITEMS) {
|
|
998
|
+
const pick = picks?.[spec.item] ?? spec.default;
|
|
999
|
+
if (spec.alternatives.includes(pick)) resolved[spec.item] = pick;
|
|
1000
|
+
else {
|
|
1001
|
+
warn(`statusline: ${spec.item}=${pick} is not available, using ${spec.item}=${spec.default}`);
|
|
1002
|
+
resolved[spec.item] = spec.default;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return resolved;
|
|
1006
|
+
}
|
|
1007
|
+
function parseLayout(layout, known) {
|
|
1008
|
+
let parsed;
|
|
1009
|
+
try {
|
|
1010
|
+
parsed = parseClusters(layout);
|
|
1011
|
+
} catch (e) {
|
|
1012
|
+
warn(`statusline: ${e.message}`);
|
|
1013
|
+
return [];
|
|
1014
|
+
}
|
|
1015
|
+
const clusters = [];
|
|
1016
|
+
for (const words of parsed) {
|
|
1017
|
+
const kept = words.filter((word) => {
|
|
1018
|
+
if (known.has(word)) return true;
|
|
1019
|
+
warn(`statusline: layout item '${word}' is not available, skipped`);
|
|
1020
|
+
return false;
|
|
1021
|
+
});
|
|
1022
|
+
if (kept.length > 0) clusters.push(kept);
|
|
1023
|
+
}
|
|
1024
|
+
return clusters;
|
|
1025
|
+
}
|
|
1026
|
+
function renderStatusline(input) {
|
|
1027
|
+
const row = parseRow(input.payload);
|
|
1028
|
+
const git = readGit(row.dir, { home: input.home });
|
|
1029
|
+
const picks = resolvePicks(input.picks);
|
|
1030
|
+
const { join, sep } = styleSeparators(picks.style);
|
|
1031
|
+
const clusters = parseLayout(input.layout ?? "{model effort state} {cwd branch status ahead pr} {bar tokens cache} {cost} {duration} {lines} {rate}", new Set(ITEMS.map((spec) => spec.item)));
|
|
1032
|
+
const wrapAt = clusters.length > 2 ? 2 : 1;
|
|
1033
|
+
let width = input.columns;
|
|
1034
|
+
if (width === void 0 || !Number.isInteger(width) || width < 0) width = 200;
|
|
1035
|
+
if (width < 20) width = 20;
|
|
1036
|
+
const avail = width - 3;
|
|
1037
|
+
let model = row.model;
|
|
1038
|
+
const initialRungs = RUNG_ITEMS.map((item) => [item, picks[item]]);
|
|
1039
|
+
function segmentInput() {
|
|
1040
|
+
return {
|
|
1041
|
+
git,
|
|
1042
|
+
home: input.home,
|
|
1043
|
+
model,
|
|
1044
|
+
now: input.now,
|
|
1045
|
+
row
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
function compose(mode) {
|
|
1049
|
+
const from = mode === "l2" ? wrapAt : 0;
|
|
1050
|
+
const to = mode === "l1" ? wrapAt : clusters.length;
|
|
1051
|
+
let line = "";
|
|
1052
|
+
let first = true;
|
|
1053
|
+
for (let ci = from; ci < to; ci++) {
|
|
1054
|
+
let cseg = "";
|
|
1055
|
+
let cf = true;
|
|
1056
|
+
for (const item of clusters[ci]) {
|
|
1057
|
+
const out = renderSegment(item, picks[item], segmentInput());
|
|
1058
|
+
if (out === "") continue;
|
|
1059
|
+
if (cf) {
|
|
1060
|
+
cseg = out;
|
|
1061
|
+
cf = false;
|
|
1062
|
+
} else cseg += join + out;
|
|
1063
|
+
}
|
|
1064
|
+
if (cseg === "") continue;
|
|
1065
|
+
if (first) {
|
|
1066
|
+
line = cseg;
|
|
1067
|
+
first = false;
|
|
1068
|
+
} else line += sep + cseg;
|
|
1069
|
+
}
|
|
1070
|
+
return line;
|
|
1071
|
+
}
|
|
1072
|
+
function fits(line) {
|
|
1073
|
+
return vlen$1(line) <= avail;
|
|
1074
|
+
}
|
|
1075
|
+
function demote(item, target) {
|
|
1076
|
+
const order = RUNG_ORDERS[item];
|
|
1077
|
+
if (order === void 0) return;
|
|
1078
|
+
const at = order.indexOf(picks[item]);
|
|
1079
|
+
if (at === -1) return;
|
|
1080
|
+
if (order.slice(at + 1).includes(target)) picks[item] = target;
|
|
1081
|
+
}
|
|
1082
|
+
function resetRungs() {
|
|
1083
|
+
model = row.model;
|
|
1084
|
+
for (const [item, pick] of initialRungs) picks[item] = pick;
|
|
1085
|
+
}
|
|
1086
|
+
function applyStep(step) {
|
|
1087
|
+
if (step[0] === "model" && step[1] === "strip") model = stripModelSuffix(model);
|
|
1088
|
+
else demote(step[0], step[1]);
|
|
1089
|
+
}
|
|
1090
|
+
function fit(mode, steps) {
|
|
1091
|
+
let out = compose(mode);
|
|
1092
|
+
if (fits(out)) return out;
|
|
1093
|
+
for (const step of steps) {
|
|
1094
|
+
applyStep(step);
|
|
1095
|
+
out = compose(mode);
|
|
1096
|
+
if (fits(out)) return out;
|
|
1097
|
+
}
|
|
1098
|
+
return out;
|
|
1099
|
+
}
|
|
1100
|
+
function emit(line) {
|
|
1101
|
+
return `${input.noColor === true ? stripSgr(line) : line}\n`;
|
|
1102
|
+
}
|
|
1103
|
+
const full = fit("full", FULL_STEPS);
|
|
1104
|
+
if (fits(full)) return emit(full);
|
|
1105
|
+
resetRungs();
|
|
1106
|
+
const l1 = fit("l1", L1_STEPS);
|
|
1107
|
+
const l2 = fit("l2", L2_STEPS);
|
|
1108
|
+
return emit(l1) + emit(l2);
|
|
1109
|
+
}
|
|
1110
|
+
//#endregion
|
|
26
1111
|
//#region src/themes.ts
|
|
27
1112
|
var THEME_NAMES = [
|
|
28
1113
|
"classic",
|
|
@@ -31,127 +1116,148 @@ var THEME_NAMES = [
|
|
|
31
1116
|
"quiet",
|
|
32
1117
|
"rich"
|
|
33
1118
|
];
|
|
34
|
-
var
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
1119
|
+
var THEMES = {
|
|
1120
|
+
quiet: {
|
|
1121
|
+
layout: "{model cwd}",
|
|
1122
|
+
summary: "model and directory, nothing else",
|
|
1123
|
+
variants: {
|
|
1124
|
+
cwd: "tail",
|
|
1125
|
+
model: "zen",
|
|
1126
|
+
style: "bare"
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
classic: {
|
|
1130
|
+
layout: DEFAULT_LAYOUT,
|
|
1131
|
+
summary: "the shipped defaults, named",
|
|
1132
|
+
variants: {}
|
|
1133
|
+
},
|
|
1134
|
+
lean: {
|
|
1135
|
+
layout: DEFAULT_LAYOUT,
|
|
1136
|
+
summary: "text only, no graphics",
|
|
1137
|
+
variants: {
|
|
1138
|
+
model: "plain",
|
|
1139
|
+
effort: "dim",
|
|
1140
|
+
state: "none",
|
|
1141
|
+
cwd: "init",
|
|
1142
|
+
branch: "initials",
|
|
1143
|
+
status: "counts",
|
|
1144
|
+
ahead: "arrows",
|
|
1145
|
+
pr: "badge",
|
|
1146
|
+
bar: "percent",
|
|
1147
|
+
tokens: "full",
|
|
1148
|
+
cache: "hit",
|
|
1149
|
+
cost: "plain",
|
|
1150
|
+
duration: "clock",
|
|
1151
|
+
lines: "diffstat",
|
|
1152
|
+
rate: "none",
|
|
1153
|
+
style: "dots"
|
|
1154
|
+
}
|
|
1155
|
+
},
|
|
1156
|
+
rich: {
|
|
1157
|
+
layout: DEFAULT_LAYOUT,
|
|
1158
|
+
summary: "every gauge and counter",
|
|
1159
|
+
variants: {
|
|
1160
|
+
model: "pill",
|
|
1161
|
+
effort: "plain",
|
|
1162
|
+
state: "pills",
|
|
1163
|
+
cwd: "icon",
|
|
1164
|
+
branch: "icon",
|
|
1165
|
+
status: "icons",
|
|
1166
|
+
ahead: "arrows",
|
|
1167
|
+
pr: "badge",
|
|
1168
|
+
bar: "gauge",
|
|
1169
|
+
tokens: "full",
|
|
1170
|
+
cache: "fuse",
|
|
1171
|
+
cost: "burn",
|
|
1172
|
+
duration: "clock",
|
|
1173
|
+
lines: "diffstat",
|
|
1174
|
+
rate: "strip",
|
|
1175
|
+
style: "plain"
|
|
1176
|
+
}
|
|
1177
|
+
},
|
|
1178
|
+
custom: {
|
|
1179
|
+
layout: DEFAULT_LAYOUT,
|
|
1180
|
+
summary: "bare; you decide everything",
|
|
1181
|
+
variants: {
|
|
1182
|
+
model: "zen",
|
|
1183
|
+
effort: "hidden",
|
|
1184
|
+
state: "none",
|
|
1185
|
+
cwd: "base",
|
|
1186
|
+
branch: "none",
|
|
1187
|
+
status: "none",
|
|
1188
|
+
ahead: "none",
|
|
1189
|
+
pr: "none",
|
|
1190
|
+
bar: "none",
|
|
1191
|
+
tokens: "none",
|
|
1192
|
+
cache: "none",
|
|
1193
|
+
cost: "none",
|
|
1194
|
+
duration: "none",
|
|
1195
|
+
lines: "none",
|
|
1196
|
+
rate: "none",
|
|
1197
|
+
style: "bare"
|
|
1198
|
+
}
|
|
53
1199
|
}
|
|
1200
|
+
};
|
|
1201
|
+
//#endregion
|
|
1202
|
+
//#region src/render/theme.ts
|
|
1203
|
+
function resolvePaint(input) {
|
|
1204
|
+
const name = input.theme;
|
|
1205
|
+
const theme = name === void 0 ? void 0 : THEMES[name];
|
|
1206
|
+
if (name !== void 0 && theme === void 0) warn(`statusline: theme=${name} is not a known theme, ignored`);
|
|
54
1207
|
return {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
cwd: "tail",
|
|
60
|
-
model: "zen",
|
|
61
|
-
style: "bare"
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
classic: {
|
|
65
|
-
layout: runtime.defaultLayout,
|
|
66
|
-
summary: "the shipped defaults, named",
|
|
67
|
-
variants: variantsOf((item) => item.default)
|
|
68
|
-
},
|
|
69
|
-
lean: {
|
|
70
|
-
layout: runtime.defaultLayout,
|
|
71
|
-
summary: "text only, no graphics",
|
|
72
|
-
variants: {
|
|
73
|
-
model: "plain",
|
|
74
|
-
effort: "dim",
|
|
75
|
-
state: "none",
|
|
76
|
-
cwd: "init",
|
|
77
|
-
branch: "initials",
|
|
78
|
-
status: "counts",
|
|
79
|
-
ahead: "arrows",
|
|
80
|
-
pr: "badge",
|
|
81
|
-
bar: "percent",
|
|
82
|
-
tokens: "full",
|
|
83
|
-
cache: "hit",
|
|
84
|
-
cost: "plain",
|
|
85
|
-
duration: "clock",
|
|
86
|
-
lines: "diffstat",
|
|
87
|
-
rate: "none",
|
|
88
|
-
style: "dots"
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
rich: {
|
|
92
|
-
layout: runtime.defaultLayout,
|
|
93
|
-
summary: "every gauge and counter",
|
|
94
|
-
variants: {
|
|
95
|
-
model: "pill",
|
|
96
|
-
effort: "plain",
|
|
97
|
-
state: "pills",
|
|
98
|
-
cwd: "icon",
|
|
99
|
-
branch: "icon",
|
|
100
|
-
status: "icons",
|
|
101
|
-
ahead: "arrows",
|
|
102
|
-
pr: "badge",
|
|
103
|
-
bar: "gauge",
|
|
104
|
-
tokens: "full",
|
|
105
|
-
cache: "fuse",
|
|
106
|
-
cost: "burn",
|
|
107
|
-
duration: "clock",
|
|
108
|
-
lines: "diffstat",
|
|
109
|
-
rate: "strip",
|
|
110
|
-
style: "plain"
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
custom: {
|
|
114
|
-
layout: runtime.defaultLayout,
|
|
115
|
-
summary: "bare; you decide everything",
|
|
116
|
-
variants: variantsOf(absenceSeed)
|
|
1208
|
+
layout: input.layout ?? theme?.layout ?? "{model effort state} {cwd branch status ahead pr} {bar tokens cache} {cost} {duration} {lines} {rate}",
|
|
1209
|
+
picks: {
|
|
1210
|
+
...theme?.variants ?? {},
|
|
1211
|
+
...input.picks
|
|
117
1212
|
}
|
|
118
1213
|
};
|
|
119
1214
|
}
|
|
120
1215
|
//#endregion
|
|
121
|
-
//#region src/
|
|
122
|
-
|
|
123
|
-
return Object.entries(key).every(([item, variant]) => theme[item] === variant) && Object.entries(theme).every(([item, variant]) => key[item] === variant);
|
|
124
|
-
}
|
|
125
|
-
function liveTheme(key, runtime) {
|
|
126
|
-
const themes = themesFor(runtime);
|
|
127
|
-
for (const name of Object.keys(themes)) {
|
|
128
|
-
const { layout, variants } = themes[name];
|
|
129
|
-
if (key.layout === layout && sameAssignments(key.values, variants)) return name;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
1216
|
+
//#region src/render/capture.ts
|
|
1217
|
+
var DATA_DIR = join(".claude", "plugins", "data", "statusline-agentic");
|
|
132
1218
|
//#endregion
|
|
133
1219
|
//#region src/resolve.ts
|
|
134
|
-
var DATA_REL = join(".claude", "plugins", "data", "statusline-agentic");
|
|
135
1220
|
function capturePath(home, surface) {
|
|
136
|
-
return join(home,
|
|
1221
|
+
return join(home, DATA_DIR, "captures", `${surface}.json`);
|
|
137
1222
|
}
|
|
138
1223
|
function backupPath(home) {
|
|
139
|
-
return join(home,
|
|
1224
|
+
return join(home, DATA_DIR, "backup.json");
|
|
1225
|
+
}
|
|
1226
|
+
function renderMjsPath(home) {
|
|
1227
|
+
return join(home, DATA_DIR, "render.mjs");
|
|
1228
|
+
}
|
|
1229
|
+
var RENDER_PROGRAM = `node "$HOME/${DATA_DIR.split(sep).join("/")}/render.mjs"`;
|
|
1230
|
+
var PANEL_PROGRAM = `${RENDER_PROGRAM} panel`;
|
|
1231
|
+
var KEY_SUFFIX = " || true";
|
|
1232
|
+
function keyValue(program, theme, layout, flags) {
|
|
1233
|
+
return `${[
|
|
1234
|
+
program,
|
|
1235
|
+
...theme === null ? [] : [`--theme=${theme}`],
|
|
1236
|
+
...layout === null ? [] : [`--layout='${layout}'`],
|
|
1237
|
+
...flags
|
|
1238
|
+
].join(" ")}${KEY_SUFFIX}`;
|
|
1239
|
+
}
|
|
1240
|
+
function mainKeyValue(theme, layout, flags) {
|
|
1241
|
+
return keyValue(RENDER_PROGRAM, theme, layout, flags);
|
|
1242
|
+
}
|
|
1243
|
+
function panelKeyValue(theme, flags) {
|
|
1244
|
+
return keyValue(PANEL_PROGRAM, theme, null, flags);
|
|
140
1245
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return `${MAIN_PREFIX}${[`STATUSLINE_LAB_LAYOUT='${layout}'`, ...assignments].join(" ")} ${MAIN_SUFFIX}`;
|
|
1246
|
+
function commandMiddle(program, command) {
|
|
1247
|
+
if (!command.startsWith(program) || !command.endsWith(KEY_SUFFIX)) return null;
|
|
1248
|
+
const middle = command.slice(program.length, command.length - 8);
|
|
1249
|
+
return middle === "" || middle.startsWith(" --") ? middle : null;
|
|
146
1250
|
}
|
|
147
|
-
var subagentKeyValue = `${KEY_RESOLVER}; bash "\${d}runtime/subagent.sh" 2>/dev/null || true`;
|
|
148
1251
|
function isOurMainCommand(command) {
|
|
149
|
-
return command
|
|
1252
|
+
return commandMiddle(RENDER_PROGRAM, command) !== null;
|
|
150
1253
|
}
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
return command.slice(MAIN_PREFIX.length, command.length - 52 - 1);
|
|
1254
|
+
function isOurPanelCommand(command) {
|
|
1255
|
+
return commandMiddle(PANEL_PROGRAM, command) !== null;
|
|
154
1256
|
}
|
|
1257
|
+
var NO_DECISIONS = {
|
|
1258
|
+
layout: null,
|
|
1259
|
+
values: {}
|
|
1260
|
+
};
|
|
155
1261
|
function settingsCommand(home) {
|
|
156
1262
|
let raw;
|
|
157
1263
|
try {
|
|
@@ -170,106 +1276,43 @@ function settingsCommand(home) {
|
|
|
170
1276
|
const command = member.command;
|
|
171
1277
|
return typeof command === "string" ? command : null;
|
|
172
1278
|
}
|
|
173
|
-
function
|
|
174
|
-
const command = settingsCommand(home);
|
|
175
|
-
const middle = command === null ? null : mainKeyMiddle(command);
|
|
176
|
-
if (middle === null) return {
|
|
177
|
-
layout: null,
|
|
178
|
-
values: {}
|
|
179
|
-
};
|
|
180
|
-
const layout = /^STATUSLINE_LAB_LAYOUT='([^']*)'/.exec(middle);
|
|
181
|
-
if (layout === null) return {
|
|
182
|
-
layout: null,
|
|
183
|
-
values: {}
|
|
184
|
-
};
|
|
1279
|
+
function parseKeyFlags(flags) {
|
|
185
1280
|
const values = {};
|
|
186
|
-
|
|
1281
|
+
let theme;
|
|
1282
|
+
for (const [, name, alt] of flags.matchAll(/(?:^| )--([a-z][a-z0-9]*)=([a-z0-9]+)/g)) if (name === "theme") theme = alt;
|
|
1283
|
+
else if (name !== "now" && name !== "layout") values[name] = alt;
|
|
1284
|
+
const layout = /--layout='([^']*)'/.exec(flags);
|
|
187
1285
|
return {
|
|
188
|
-
layout: layout[1],
|
|
189
|
-
values
|
|
1286
|
+
layout: layout === null ? null : layout[1],
|
|
1287
|
+
values,
|
|
1288
|
+
...theme === void 0 ? {} : { theme }
|
|
190
1289
|
};
|
|
191
1290
|
}
|
|
192
|
-
function
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
const delta = (parseInt(left[i] ?? "0", 10) || 0) - (parseInt(right[i] ?? "0", 10) || 0);
|
|
197
|
-
if (delta !== 0) return delta;
|
|
198
|
-
}
|
|
199
|
-
return 0;
|
|
200
|
-
}
|
|
201
|
-
function newestCacheDir(cacheRoot) {
|
|
202
|
-
let versions;
|
|
203
|
-
try {
|
|
204
|
-
versions = readdirSync(cacheRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
205
|
-
} catch {
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
if (versions.length === 0) return null;
|
|
209
|
-
versions.sort(byVersion);
|
|
210
|
-
return join(cacheRoot, versions[versions.length - 1]);
|
|
211
|
-
}
|
|
212
|
-
function readAlternatives(dir) {
|
|
213
|
-
const declared = /* @__PURE__ */ new Map();
|
|
214
|
-
const components = join(dir, "components");
|
|
215
|
-
for (const file of readdirSync(components).sort()) {
|
|
216
|
-
if (!file.endsWith(".sh")) continue;
|
|
217
|
-
for (const line of readFileSync(join(components, file), "utf8").split("\n")) {
|
|
218
|
-
if (!line.startsWith("#")) break;
|
|
219
|
-
const match = /alternatives:\s*(.+)$/.exec(line);
|
|
220
|
-
if (match) declared.set(file.slice(0, -3), match[1].split("|").map((alt) => alt.trim().replace(/\s*\(current\)$/, "")));
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return declared;
|
|
224
|
-
}
|
|
225
|
-
function readItems(dir) {
|
|
226
|
-
const alternatives = readAlternatives(dir);
|
|
227
|
-
const defaults = /* @__PURE__ */ new Map();
|
|
228
|
-
for (const [, item, alt] of readFileSync(join(dir, "lib.sh"), "utf8").matchAll(/([a-z]+)\) echo ([a-z]+) ;;/g)) defaults.set(item, alt);
|
|
229
|
-
const match = /^COMPS="(.+)"$/m.exec(readFileSync(join(dir, "statusline.sh"), "utf8"));
|
|
230
|
-
if (match === null) throw new Error(`statusline.sh in ${dir} declares no COMPS order`);
|
|
231
|
-
return match[1].split(" ").map((item) => {
|
|
232
|
-
const alt = defaults.get(item);
|
|
233
|
-
if (alt === void 0) throw new Error(`lib.sh in ${dir} declares no default for '${item}'`);
|
|
234
|
-
return {
|
|
235
|
-
alternatives: alternatives.get(item) ?? [],
|
|
236
|
-
default: alt,
|
|
237
|
-
item
|
|
238
|
-
};
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
function readDefaultLayout(dir) {
|
|
242
|
-
const match = /^export DEFAULT_LAYOUT='(.*)'$/m.exec(readFileSync(join(dir, "lib.sh"), "utf8"));
|
|
243
|
-
if (match === null) throw new Error(`lib.sh in ${dir} declares no DEFAULT_LAYOUT`);
|
|
244
|
-
return match[1];
|
|
245
|
-
}
|
|
246
|
-
function resolveRuntimeDir(dir) {
|
|
247
|
-
return {
|
|
248
|
-
defaultLayout: readDefaultLayout(dir),
|
|
249
|
-
dir,
|
|
250
|
-
items: readItems(dir)
|
|
251
|
-
};
|
|
1291
|
+
function readKeyConfig(home) {
|
|
1292
|
+
const command = settingsCommand(home);
|
|
1293
|
+
const flags = command === null ? null : commandMiddle(RENDER_PROGRAM, command);
|
|
1294
|
+
return flags === null ? NO_DECISIONS : parseKeyFlags(flags);
|
|
252
1295
|
}
|
|
253
|
-
function
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
if (dir === null || !existsSync(join(dir, "statusline.sh"))) throw new Error(`no statusline runtime under ${home} — install the plugin first: claude plugin install statusline@agentic`);
|
|
257
|
-
return resolveRuntimeDir(dir);
|
|
1296
|
+
function parsePanelCommand(command) {
|
|
1297
|
+
const flags = command === null ? null : commandMiddle(PANEL_PROGRAM, command);
|
|
1298
|
+
return flags === null ? NO_DECISIONS : parseKeyFlags(flags);
|
|
258
1299
|
}
|
|
259
1300
|
//#endregion
|
|
260
1301
|
//#region src/catalog.ts
|
|
261
1302
|
function catalog(options) {
|
|
262
1303
|
if (options.themes === true && options.items !== void 0) throw new Error("--themes cannot combine with item flags");
|
|
263
|
-
const runtime = resolveRuntime({ home: options.home });
|
|
264
1304
|
const key = readKeyConfig(options.home);
|
|
265
|
-
const
|
|
266
|
-
const block = Object.entries(themesFor(runtime)).map(([name, theme]) => `${name}${live === name ? "*" : ""}: ${theme.summary}`);
|
|
1305
|
+
const block = Object.entries(THEMES).map(([name, theme]) => `${name}${key.theme === name ? "*" : ""}: ${theme.summary}`);
|
|
267
1306
|
if (options.themes === true) return block.join("\n");
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
1307
|
+
const resolved = resolvePaint({
|
|
1308
|
+
layout: key.layout ?? void 0,
|
|
1309
|
+
picks: key.values,
|
|
1310
|
+
theme: key.theme
|
|
1311
|
+
});
|
|
1312
|
+
const itemLines = (options.items ?? ITEM_IDS).map((item) => {
|
|
1313
|
+
const entry = specFor(item);
|
|
1314
|
+
if (entry === void 0) throw new Error(`unknown item '${item}' — valid items: ${ITEM_IDS.join(" ")}`);
|
|
1315
|
+
const current = resolved.picks[item] ?? entry.default;
|
|
273
1316
|
return `${item}: ${entry.alternatives.map((alt) => alt === current ? `${alt}*` : alt).join(" | ")}`;
|
|
274
1317
|
});
|
|
275
1318
|
return [
|
|
@@ -282,12 +1325,12 @@ function catalog(options) {
|
|
|
282
1325
|
//#region src/cli.ts
|
|
283
1326
|
var VERSION = {
|
|
284
1327
|
name: "@v1nvn/statusline",
|
|
285
|
-
version: "0.
|
|
1328
|
+
version: "0.30.0",
|
|
286
1329
|
description: "Browse statusline variants and configure the live status line and agent panel — the statusline CLI behind the statusline plugin.",
|
|
287
1330
|
type: "module",
|
|
288
1331
|
bin: "dist/index.js",
|
|
289
1332
|
scripts: {
|
|
290
|
-
"build": "vite build",
|
|
1333
|
+
"build": "vite build && vite build --mode render",
|
|
291
1334
|
"test": "vitest run",
|
|
292
1335
|
"test:watch": "vitest"
|
|
293
1336
|
},
|
|
@@ -318,24 +1361,6 @@ var VERSION = {
|
|
|
318
1361
|
"vitest": "^4.1.10"
|
|
319
1362
|
}
|
|
320
1363
|
}.version;
|
|
321
|
-
var ITEM_IDS = [
|
|
322
|
-
"model",
|
|
323
|
-
"effort",
|
|
324
|
-
"state",
|
|
325
|
-
"cwd",
|
|
326
|
-
"branch",
|
|
327
|
-
"status",
|
|
328
|
-
"ahead",
|
|
329
|
-
"pr",
|
|
330
|
-
"bar",
|
|
331
|
-
"tokens",
|
|
332
|
-
"cache",
|
|
333
|
-
"cost",
|
|
334
|
-
"duration",
|
|
335
|
-
"lines",
|
|
336
|
-
"rate",
|
|
337
|
-
"style"
|
|
338
|
-
];
|
|
339
1364
|
var QUIET = {
|
|
340
1365
|
writeOut: () => void 0,
|
|
341
1366
|
writeErr: () => void 0
|
|
@@ -437,46 +1462,17 @@ function parseArgs(args) {
|
|
|
437
1462
|
}
|
|
438
1463
|
//#endregion
|
|
439
1464
|
//#region src/configure.ts
|
|
440
|
-
var
|
|
441
|
-
function
|
|
442
|
-
const
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
function parseClusters(layout) {
|
|
451
|
-
const clusters = [];
|
|
452
|
-
let words = [];
|
|
453
|
-
let word = "";
|
|
454
|
-
let open = false;
|
|
455
|
-
function pushWord() {
|
|
456
|
-
if (word !== "") {
|
|
457
|
-
words.push(word);
|
|
458
|
-
word = "";
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
for (const c of layout) if (c === "{") {
|
|
462
|
-
if (open) throw new Error(`layout '${layout}': '{' inside a cluster`);
|
|
463
|
-
open = true;
|
|
464
|
-
words = [];
|
|
465
|
-
} else if (c === "}") {
|
|
466
|
-
if (!open) throw new Error(`layout '${layout}': '}' outside a cluster`);
|
|
467
|
-
pushWord();
|
|
468
|
-
open = false;
|
|
469
|
-
if (words.length > 0) clusters.push(words);
|
|
470
|
-
} else if (c === " ") {
|
|
471
|
-
if (open) pushWord();
|
|
472
|
-
else if (word !== "") throw new Error(`layout '${layout}': '${word}' sits outside a cluster`);
|
|
473
|
-
} else if (/[a-z0-9]/.test(c)) word += c;
|
|
474
|
-
else throw new Error(`layout '${layout}': '${c}' is not layout grammar (braces, item ids, spaces)`);
|
|
475
|
-
if (open) throw new Error(`layout '${layout}': unterminated cluster`);
|
|
476
|
-
if (word !== "") throw new Error(`layout '${layout}': '${word}' sits outside a cluster`);
|
|
477
|
-
if (clusters.length === 0) throw new Error(`layout '${layout}': no clusters`);
|
|
478
|
-
return clusters;
|
|
1465
|
+
var BUNDLED_RENDERER = fileURLToPath(new URL("../dist/render.mjs", import.meta.url));
|
|
1466
|
+
function syncRenderer(home) {
|
|
1467
|
+
const source = readFileSync(BUNDLED_RENDERER);
|
|
1468
|
+
const dest = renderMjsPath(home);
|
|
1469
|
+
if (existsSync(dest) && readFileSync(dest).equals(source)) return;
|
|
1470
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
1471
|
+
const tmp = `${dest}.tmp`;
|
|
1472
|
+
writeFileSync(tmp, source);
|
|
1473
|
+
renameSync(tmp, dest);
|
|
479
1474
|
}
|
|
1475
|
+
var SETTINGS_KEYS = ["statusLine", "subagentStatusLine"];
|
|
480
1476
|
function isObject(value) {
|
|
481
1477
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
482
1478
|
}
|
|
@@ -505,14 +1501,11 @@ function memberCommand(value) {
|
|
|
505
1501
|
function isOurMember(key, value) {
|
|
506
1502
|
const command = memberCommand(value);
|
|
507
1503
|
if (command === null) return false;
|
|
508
|
-
return key === "statusLine" ? isOurMainCommand(command) : command
|
|
1504
|
+
return key === "statusLine" ? isOurMainCommand(command) : isOurPanelCommand(command);
|
|
509
1505
|
}
|
|
510
|
-
function planSettings(home,
|
|
1506
|
+
function planSettings(home, commands, force) {
|
|
511
1507
|
const file = join(home, ".claude", "settings.json");
|
|
512
|
-
const wanted =
|
|
513
|
-
statusLine: mainCommand,
|
|
514
|
-
subagentStatusLine: subagentKeyValue
|
|
515
|
-
};
|
|
1508
|
+
const wanted = commands;
|
|
516
1509
|
const raw = readOrNull(file);
|
|
517
1510
|
if (raw === null) return {
|
|
518
1511
|
adds: SETTINGS_KEYS.map((key) => [key, wanted[key]]),
|
|
@@ -687,44 +1680,42 @@ function themeNamed(name, themes) {
|
|
|
687
1680
|
if (theme === void 0) throw new Error(`unknown theme '${name}' — valid themes: ${THEME_NAMES.join(" ")}`);
|
|
688
1681
|
return theme;
|
|
689
1682
|
}
|
|
690
|
-
function
|
|
691
|
-
const
|
|
692
|
-
const themes = themesFor(runtime);
|
|
693
|
-
const theme = options.theme === void 0 ? void 0 : themeNamed(options.theme, themes);
|
|
1683
|
+
function validateSelection(options) {
|
|
1684
|
+
const theme = options.theme === void 0 ? void 0 : themeNamed(options.theme, THEMES);
|
|
694
1685
|
const layout = options.layout ?? theme?.layout;
|
|
695
1686
|
if (layout === void 0) throw new Error("no layout — pass --layout <spec> or --theme <name>");
|
|
696
|
-
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
...variants
|
|
701
|
-
};
|
|
702
|
-
const missing = items.filter((item) => !(item in values));
|
|
703
|
-
if (missing.length > 0) {
|
|
704
|
-
const hints = missing.map((item) => `--${item} <alt>`).join(" ");
|
|
705
|
-
const names = missing.map((item) => `'${item}'`).join(" ");
|
|
706
|
-
const plural = missing.length > 1 ? "s" : "";
|
|
707
|
-
throw new Error(theme === void 0 ? `no variant for layout item${plural} ${names} — pass ${hints}, or name a theme: --theme ${THEME_NAMES.join("|")}` : `theme '${options.theme}' has no pick for layout item${plural} ${names} — pass ${hints}, or drop ${missing.length > 1 ? "them" : "it"} from the layout`);
|
|
708
|
-
}
|
|
709
|
-
for (const [item, alt] of Object.entries(values)) {
|
|
710
|
-
const entry = byItem.get(item);
|
|
711
|
-
if (entry === void 0) throw new Error(`unknown item '${item}' — valid items: ${[...byItem.keys()].join(" ")}`);
|
|
1687
|
+
layoutItems(layout, ITEM_IDS);
|
|
1688
|
+
for (const [item, alt] of Object.entries(options.variants ?? {})) {
|
|
1689
|
+
const entry = specFor(item);
|
|
1690
|
+
if (entry === void 0) throw new Error(`unknown item '${item}' — valid items: ${ITEM_IDS.join(" ")}`);
|
|
712
1691
|
if (!entry.alternatives.includes(alt)) throw new Error(`unknown variant '${alt}' for item '${item}' — valid: ${entry.alternatives.join(" | ")}`);
|
|
713
1692
|
}
|
|
714
|
-
return
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
1693
|
+
return theme;
|
|
1694
|
+
}
|
|
1695
|
+
function decisionFlags(base, decisions) {
|
|
1696
|
+
return ITEMS.flatMap(({ item }) => {
|
|
1697
|
+
const pick = decisions[item];
|
|
1698
|
+
return pick === void 0 || pick === base[item] ? [] : [`--${item}=${pick}`];
|
|
1699
|
+
});
|
|
719
1700
|
}
|
|
720
1701
|
function configure(options) {
|
|
721
|
-
const runtime = resolveRuntime({ home: options.home });
|
|
722
1702
|
if (options.theme === void 0 && options.layout === void 0 && Object.keys(options.variants ?? {}).length === 0) throw new Error("no theme and no item flags — on a terminal run 'statusline configure' for the wizard; in Claude Code use the /lab skill");
|
|
723
|
-
const
|
|
724
|
-
const
|
|
725
|
-
|
|
1703
|
+
const theme = validateSelection(options);
|
|
1704
|
+
const base = {
|
|
1705
|
+
...DEFAULT_PICKS,
|
|
1706
|
+
...theme?.variants
|
|
1707
|
+
};
|
|
1708
|
+
const decisions = options.variants ?? {};
|
|
1709
|
+
const baseLayout = theme?.layout ?? "{model effort state} {cwd branch status ahead pr} {bar tokens cache} {cost} {duration} {lines} {rate}";
|
|
1710
|
+
const layout = options.layout === void 0 || options.layout === baseLayout ? null : options.layout;
|
|
1711
|
+
const flags = decisionFlags(base, decisions);
|
|
1712
|
+
const plan = planSettings(options.home, {
|
|
1713
|
+
statusLine: mainKeyValue(options.theme ?? null, layout, flags),
|
|
1714
|
+
subagentStatusLine: panelKeyValue(options.theme ?? null, flags.filter((flag) => flag.startsWith("--style=")))
|
|
1715
|
+
}, options.force ?? false);
|
|
726
1716
|
if (plan.adds.length > 0 || plan.repoints.length > 0) writeBackupIfAbsent(options.home, plan);
|
|
727
1717
|
commitSettings(plan);
|
|
1718
|
+
syncRenderer(options.home);
|
|
728
1719
|
}
|
|
729
1720
|
//#endregion
|
|
730
1721
|
//#region src/demo-repo.ts
|
|
@@ -869,6 +1860,157 @@ function materializeDemoRepo(home) {
|
|
|
869
1860
|
return dir;
|
|
870
1861
|
}
|
|
871
1862
|
//#endregion
|
|
1863
|
+
//#region src/render/panel.ts
|
|
1864
|
+
var CYAN = "\x1B[36m";
|
|
1865
|
+
var GREEN = "\x1B[32m";
|
|
1866
|
+
var RED = "\x1B[31m";
|
|
1867
|
+
var RESET = "\x1B[0m";
|
|
1868
|
+
var YELLOW = "\x1B[33m";
|
|
1869
|
+
var MS_THRESHOLD = 2e11;
|
|
1870
|
+
var STEPS = [
|
|
1871
|
+
["descd", 1],
|
|
1872
|
+
["durd", 1],
|
|
1873
|
+
["statd", 1],
|
|
1874
|
+
["barb", 6],
|
|
1875
|
+
["modeld", 1],
|
|
1876
|
+
["barb", 4],
|
|
1877
|
+
["barb", 0],
|
|
1878
|
+
["modeld", 2],
|
|
1879
|
+
["statd", 2]
|
|
1880
|
+
];
|
|
1881
|
+
function field(source, key, fallback) {
|
|
1882
|
+
return orElse(source[key], fallback);
|
|
1883
|
+
}
|
|
1884
|
+
function cell(value) {
|
|
1885
|
+
return tsvEscape(jqText(value));
|
|
1886
|
+
}
|
|
1887
|
+
function truncateDesc(desc) {
|
|
1888
|
+
const chars = Array.from(desc);
|
|
1889
|
+
return chars.length > 24 ? `${chars.slice(0, 23).join("")}…` : desc;
|
|
1890
|
+
}
|
|
1891
|
+
function intValue(text) {
|
|
1892
|
+
return /^-?\d+$/.test(text) ? Number(text) : null;
|
|
1893
|
+
}
|
|
1894
|
+
function stylePick(picks) {
|
|
1895
|
+
const spec = specFor("style");
|
|
1896
|
+
if (spec === void 0) return "plain";
|
|
1897
|
+
const wanted = picks?.style ?? "";
|
|
1898
|
+
const alt = wanted === "" ? spec.default : wanted;
|
|
1899
|
+
if (!spec.alternatives.includes(alt)) {
|
|
1900
|
+
warn(`statusline: style=${alt} is not available, using style=${spec.default}`);
|
|
1901
|
+
return spec.default;
|
|
1902
|
+
}
|
|
1903
|
+
return alt;
|
|
1904
|
+
}
|
|
1905
|
+
function availColumns(tick) {
|
|
1906
|
+
const text = jqText(isRecord(tick) ? field(tick, "columns", 200) : 200);
|
|
1907
|
+
const columns = /^\d+$/.test(text) ? Number(text) : 200;
|
|
1908
|
+
return Math.max(columns, 20) - 1;
|
|
1909
|
+
}
|
|
1910
|
+
function extractFields(task) {
|
|
1911
|
+
return {
|
|
1912
|
+
ctx: cell(field(task, "contextWindowSize", 0)),
|
|
1913
|
+
desc: cell(truncateDesc(jqText(field(task, "description", "")))),
|
|
1914
|
+
effort: cell(field(task, "effort", "")),
|
|
1915
|
+
id: cell(field(task, "id", "")),
|
|
1916
|
+
label: cell(field(task, "label", "")),
|
|
1917
|
+
model: cell(field(task, "model", "")),
|
|
1918
|
+
name: cell(field(task, "name", "")),
|
|
1919
|
+
start: cell(field(task, "startTime", 0)),
|
|
1920
|
+
tokens: cell(field(task, "tokenCount", 0))
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
function makeBar(pct, width) {
|
|
1924
|
+
let f = Math.trunc(pct * width / 100);
|
|
1925
|
+
if (f > width) f = width;
|
|
1926
|
+
let bar = "";
|
|
1927
|
+
for (let i = 0; i < width; i++) bar += i < f ? "█" : "░";
|
|
1928
|
+
return bar;
|
|
1929
|
+
}
|
|
1930
|
+
function duration(startText, now) {
|
|
1931
|
+
const start = intValue(startText);
|
|
1932
|
+
if (start === null || start <= 0) return "";
|
|
1933
|
+
const seconds = start > MS_THRESHOLD ? Math.trunc(start / 1e3) : start;
|
|
1934
|
+
const elapsed = Math.trunc(now) - seconds;
|
|
1935
|
+
if (elapsed < 0) return "";
|
|
1936
|
+
return `${Math.trunc(elapsed / 60)}m${pad2(Math.trunc(elapsed % 60))}s`;
|
|
1937
|
+
}
|
|
1938
|
+
function rowFormats(fields, now) {
|
|
1939
|
+
const tokens = intValue(fields.tokens) ?? 0;
|
|
1940
|
+
const ctx = intValue(fields.ctx) ?? 0;
|
|
1941
|
+
let ctxText = fields.ctx;
|
|
1942
|
+
if (ctx >= 1e6) ctxText = fmtM(ctx);
|
|
1943
|
+
else if (ctx >= 1e3) ctxText = fmtK(ctx, 0);
|
|
1944
|
+
return {
|
|
1945
|
+
ctx: ctxText,
|
|
1946
|
+
dur: duration(fields.start, now),
|
|
1947
|
+
tickTok: tokens >= 1e3 ? fmtK(tokens, 0) : String(tokens),
|
|
1948
|
+
tokens: tokens >= 1e3 ? fmtK(tokens, 1) : String(tokens)
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
function renderRow(fields, formats, state, sep) {
|
|
1952
|
+
let s = fields.label === "" ? fields.name : fields.label;
|
|
1953
|
+
if (fields.desc !== "" && fields.desc !== fields.label && state.descd === 0) s += ` ${fields.desc}`;
|
|
1954
|
+
if (fields.model !== "") {
|
|
1955
|
+
let name = fields.model;
|
|
1956
|
+
if (state.modeld >= 1) name = stripModelSuffix(name);
|
|
1957
|
+
const effort = state.modeld >= 2 ? "" : fields.effort;
|
|
1958
|
+
if (effort !== "") name += ` ${effort}`;
|
|
1959
|
+
s += `${sep}${CYAN}${name}${RESET}`;
|
|
1960
|
+
}
|
|
1961
|
+
const ctx = intValue(fields.ctx);
|
|
1962
|
+
if (ctx !== null && ctx > 0) {
|
|
1963
|
+
const tokens = intValue(fields.tokens) ?? 0;
|
|
1964
|
+
const pct = Math.trunc(tokens * 100 / ctx);
|
|
1965
|
+
const barColor = pct >= 90 ? RED : pct >= 70 ? YELLOW : GREEN;
|
|
1966
|
+
if (state.barb > 0) s += `${sep}${barColor}${makeBar(pct, state.barb)}${RESET} ${pct}%`;
|
|
1967
|
+
else s += `${sep}${barColor}${pct}%${RESET}`;
|
|
1968
|
+
const stats = state.statd === 1 ? formats.tickTok : state.statd === 2 ? "" : `${formats.tokens}/${formats.ctx}`;
|
|
1969
|
+
if (stats !== "") s += ` ${stats}`;
|
|
1970
|
+
}
|
|
1971
|
+
if (formats.dur !== "" && state.durd === 0) s += `${sep}${formats.dur}`;
|
|
1972
|
+
return s;
|
|
1973
|
+
}
|
|
1974
|
+
function vlen(text) {
|
|
1975
|
+
return Array.from(stripSgr(text)).length;
|
|
1976
|
+
}
|
|
1977
|
+
function fitRow(fields, formats, sep, avail) {
|
|
1978
|
+
const state = {
|
|
1979
|
+
barb: 10,
|
|
1980
|
+
descd: 0,
|
|
1981
|
+
durd: 0,
|
|
1982
|
+
modeld: 0,
|
|
1983
|
+
statd: 0
|
|
1984
|
+
};
|
|
1985
|
+
let out = renderRow(fields, formats, state, sep);
|
|
1986
|
+
if (vlen(out) <= avail) return out;
|
|
1987
|
+
for (const [key, value] of STEPS) {
|
|
1988
|
+
state[key] = value;
|
|
1989
|
+
out = renderRow(fields, formats, state, sep);
|
|
1990
|
+
if (vlen(out) <= avail) return out;
|
|
1991
|
+
}
|
|
1992
|
+
return out;
|
|
1993
|
+
}
|
|
1994
|
+
function emitLine(id, content) {
|
|
1995
|
+
return `{"id":${JSON.stringify(id)},"content":${JSON.stringify(content)}}`;
|
|
1996
|
+
}
|
|
1997
|
+
function renderPanel(input) {
|
|
1998
|
+
const tick = JSON.parse(input.payload);
|
|
1999
|
+
const avail = availColumns(tick);
|
|
2000
|
+
const sep = styleSeparators(stylePick(input.picks)).sep;
|
|
2001
|
+
const tasks = isRecord(tick) && Array.isArray(tick.tasks) ? tick.tasks : [];
|
|
2002
|
+
let out = "";
|
|
2003
|
+
for (const task of tasks) {
|
|
2004
|
+
if (!isRecord(task)) continue;
|
|
2005
|
+
const fields = extractFields(task);
|
|
2006
|
+
if (fields.id === "") continue;
|
|
2007
|
+
const row = fitRow(fields, rowFormats(fields, input.now), sep, avail);
|
|
2008
|
+
const content = input.noColor === true ? stripSgr(row) : row;
|
|
2009
|
+
out += `${emitLine(fields.id, content)}\n`;
|
|
2010
|
+
}
|
|
2011
|
+
return out;
|
|
2012
|
+
}
|
|
2013
|
+
//#endregion
|
|
872
2014
|
//#region src/payloads.ts
|
|
873
2015
|
var PAYLOADS_DIR = fileURLToPath(new URL("../assets/payloads", import.meta.url));
|
|
874
2016
|
var TICKS_DIR = fileURLToPath(new URL("../assets/ticks", import.meta.url));
|
|
@@ -947,68 +2089,46 @@ function firstPanelRow(output) {
|
|
|
947
2089
|
const content = JSON.parse(row).content;
|
|
948
2090
|
return typeof content === "string" ? content : "";
|
|
949
2091
|
}
|
|
950
|
-
function runtimeRenderer(spec) {
|
|
951
|
-
const run = spawnSync("bash", [spec.bin], {
|
|
952
|
-
input: spec.stdin,
|
|
953
|
-
env: {
|
|
954
|
-
PATH: process.env.PATH ?? "",
|
|
955
|
-
...spec.env,
|
|
956
|
-
LC_ALL: "C",
|
|
957
|
-
TZ: "UTC"
|
|
958
|
-
},
|
|
959
|
-
timeout: 3e4
|
|
960
|
-
});
|
|
961
|
-
if (run.status !== 0) throw new Error(`render failed: ${run.stderr.toString("utf8")}`);
|
|
962
|
-
const stdout = run.stdout.toString("utf8");
|
|
963
|
-
const warn = run.stderr.toString("utf8").trim();
|
|
964
|
-
return warn === "" ? stdout : `${stdout}${warn}\n`;
|
|
965
|
-
}
|
|
966
2092
|
function renderPreview(bag) {
|
|
967
|
-
const variantEnv = Object.fromEntries(Object.entries(bag.values).map(([item, alt]) => [`STATUSLINE_LAB_${item.toUpperCase()}`, alt]));
|
|
968
2093
|
const plain = bag.plain === true || (process.env.NO_COLOR ?? "") !== "";
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
2094
|
+
const paint = resolvePaint({
|
|
2095
|
+
layout: bag.layout,
|
|
2096
|
+
picks: bag.values,
|
|
2097
|
+
theme: bag.theme
|
|
2098
|
+
});
|
|
973
2099
|
return {
|
|
974
|
-
line:
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
},
|
|
983
|
-
stdin: bag.main
|
|
2100
|
+
line: renderStatusline({
|
|
2101
|
+
home: bag.home,
|
|
2102
|
+
layout: paint.layout,
|
|
2103
|
+
noColor: plain,
|
|
2104
|
+
now: Number(bag.now),
|
|
2105
|
+
payload: bag.main,
|
|
2106
|
+
picks: paint.picks,
|
|
2107
|
+
...bag.width === void 0 ? {} : { columns: bag.width }
|
|
984
2108
|
}).replace(/\n+$/, ""),
|
|
985
|
-
panel: firstPanelRow(
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
NOW: bag.now
|
|
991
|
-
},
|
|
992
|
-
stdin: bag.tick
|
|
2109
|
+
panel: firstPanelRow(renderPanel({
|
|
2110
|
+
noColor: plain,
|
|
2111
|
+
now: Number(bag.now),
|
|
2112
|
+
payload: bag.tick,
|
|
2113
|
+
picks: paint.picks
|
|
993
2114
|
}).replace(/\n+$/, ""))
|
|
994
2115
|
};
|
|
995
2116
|
}
|
|
996
2117
|
//#endregion
|
|
997
2118
|
//#region src/preview.ts
|
|
998
2119
|
function preview(options) {
|
|
999
|
-
|
|
1000
|
-
const { layout, values } = resolveSelection(runtime, options);
|
|
2120
|
+
validateSelection(options);
|
|
1001
2121
|
const sources = previewSources(options.home, Number(options.now));
|
|
1002
2122
|
try {
|
|
1003
2123
|
const { line, panel } = renderPreview({
|
|
1004
2124
|
home: options.home,
|
|
1005
|
-
layout,
|
|
2125
|
+
layout: options.layout,
|
|
1006
2126
|
main: sources.main,
|
|
1007
2127
|
now: options.now,
|
|
1008
2128
|
plain: options.plain ?? !process.stdout.isTTY,
|
|
1009
|
-
runtime,
|
|
1010
2129
|
tick: sources.tick,
|
|
1011
|
-
|
|
2130
|
+
theme: options.theme,
|
|
2131
|
+
values: options.variants
|
|
1012
2132
|
});
|
|
1013
2133
|
return `preview at 200 columns — nothing written\n${line}\npanel ${panel}\n`;
|
|
1014
2134
|
} finally {
|
|
@@ -1099,18 +2219,20 @@ function restore(options) {
|
|
|
1099
2219
|
next = null;
|
|
1100
2220
|
} else next = text;
|
|
1101
2221
|
}
|
|
1102
|
-
const dataDir = join(options.home,
|
|
2222
|
+
const dataDir = join(options.home, DATA_DIR);
|
|
1103
2223
|
const capturesDir = join(dataDir, "captures");
|
|
1104
2224
|
const backupFile = backupPath(options.home);
|
|
1105
|
-
|
|
2225
|
+
const rendererFile = renderMjsPath(options.home);
|
|
2226
|
+
if (actions.length === 0 && !existsSync(backupFile) && !existsSync(capturesDir) && !existsSync(rendererFile)) return {
|
|
1106
2227
|
mode: "nothing",
|
|
1107
|
-
text: "nothing to restore — no lab keys in settings.json, no backup, no captures"
|
|
2228
|
+
text: "nothing to restore — no lab keys in settings.json, no backup, no captures, no renderer"
|
|
1108
2229
|
};
|
|
1109
2230
|
if (options.dryRun) {
|
|
1110
2231
|
const lines = ["restore plan — nothing written"];
|
|
1111
2232
|
for (const action of actions) lines.push(action.kind === "remove" ? ` remove ${action.key} (absent before the lab)` : ` splice the saved ${action.key} value back`);
|
|
1112
2233
|
if (deleteFile) lines.push(" delete settings.json (the lab created it and it is now empty)");
|
|
1113
2234
|
if (existsSync(capturesDir)) lines.push(" delete captures/");
|
|
2235
|
+
if (existsSync(rendererFile)) lines.push(" delete render.mjs");
|
|
1114
2236
|
if (existsSync(backupFile)) lines.push(" delete backup.json");
|
|
1115
2237
|
lines.push(" remove the data dir if empty");
|
|
1116
2238
|
return {
|
|
@@ -1121,6 +2243,7 @@ function restore(options) {
|
|
|
1121
2243
|
if (deleteFile) rmSync(settings, { force: true });
|
|
1122
2244
|
else if (next !== null && actions.length > 0) writeFileSync(settings, next);
|
|
1123
2245
|
clearCaptures(capturesDir);
|
|
2246
|
+
rmSync(rendererFile, { force: true });
|
|
1124
2247
|
rmSync(backupFile, { force: true });
|
|
1125
2248
|
rmdirIfEmpty(dataDir);
|
|
1126
2249
|
let line;
|
|
@@ -1134,7 +2257,6 @@ function restore(options) {
|
|
|
1134
2257
|
}
|
|
1135
2258
|
//#endregion
|
|
1136
2259
|
//#region src/status.ts
|
|
1137
|
-
var INSTALL_FIX = "claude plugin install statusline@agentic";
|
|
1138
2260
|
function keyState(key, value) {
|
|
1139
2261
|
if (value === void 0) return { kind: "absent" };
|
|
1140
2262
|
if (isOurMember(key, value)) return { kind: "ours" };
|
|
@@ -1148,25 +2270,29 @@ function keyRow(key, state, detail = "") {
|
|
|
1148
2270
|
if (state.kind === "absent") return `${key}: absent — fix: rerun configure --theme classic`;
|
|
1149
2271
|
return `${key}: foreign${state.command === null ? "" : ` (${state.command})`} — fix: rerun configure --force --theme classic`;
|
|
1150
2272
|
}
|
|
1151
|
-
function
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
return items;
|
|
2273
|
+
function keyItems(config) {
|
|
2274
|
+
const layout = config.layout === null ? [] : layoutItemsOf(config.layout);
|
|
2275
|
+
return [.../* @__PURE__ */ new Set([...layout, ...Object.keys(config.values)])];
|
|
1155
2276
|
}
|
|
1156
2277
|
function configDetail(config) {
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
2278
|
+
const assignments = keyItems(config).filter((item) => Object.hasOwn(config.values, item)).map((item) => `${item}=${config.values[item]}`);
|
|
2279
|
+
return [...config.layout === null ? [] : [`layout='${config.layout}'`], ...assignments].join(" ");
|
|
2280
|
+
}
|
|
2281
|
+
function panelDetail(config) {
|
|
2282
|
+
return [...config.theme === void 0 ? [] : [`theme=${config.theme}`], ...Object.entries(config.values).map(([item, alt]) => `${item}=${alt}`)].join(" ");
|
|
2283
|
+
}
|
|
2284
|
+
function themeRow(config) {
|
|
2285
|
+
if (config.theme === void 0) return [];
|
|
2286
|
+
const swaps = Object.entries(config.values).map(([item, alt]) => `+${item}=${alt}`);
|
|
2287
|
+
return [`theme: ${[config.theme, ...swaps].join(" ")}`];
|
|
1160
2288
|
}
|
|
1161
2289
|
function findingText(finding) {
|
|
1162
2290
|
return finding.kind === "unknown-item" ? `unknown item '${finding.item}'` : `unknown variant '${finding.alt}' for '${finding.item}'`;
|
|
1163
2291
|
}
|
|
1164
|
-
function driftFindings(config
|
|
1165
|
-
if (config.layout === null) return [];
|
|
1166
|
-
const byItem = new Map(runtime.items.map((entry) => [entry.item, entry]));
|
|
2292
|
+
function driftFindings(config) {
|
|
1167
2293
|
const findings = [];
|
|
1168
|
-
for (const item of
|
|
1169
|
-
const entry =
|
|
2294
|
+
for (const item of keyItems(config)) {
|
|
2295
|
+
const entry = specFor(item);
|
|
1170
2296
|
if (entry === void 0) {
|
|
1171
2297
|
findings.push({
|
|
1172
2298
|
item,
|
|
@@ -1183,9 +2309,32 @@ function driftFindings(config, runtime) {
|
|
|
1183
2309
|
}
|
|
1184
2310
|
return findings;
|
|
1185
2311
|
}
|
|
1186
|
-
function
|
|
1187
|
-
|
|
1188
|
-
|
|
2312
|
+
function nodeOnPath(pathVar) {
|
|
2313
|
+
return pathVar.split(delimiter).filter((dir) => dir !== "").map((dir) => join(dir, "node")).find(existsSync) ?? null;
|
|
2314
|
+
}
|
|
2315
|
+
function rendererHash(bytes) {
|
|
2316
|
+
return createHash("sha256").update(bytes).digest("hex").slice(0, 12);
|
|
2317
|
+
}
|
|
2318
|
+
function rendererState(home) {
|
|
2319
|
+
if (!existsSync(BUNDLED_RENDERER)) return { kind: "no-bundle" };
|
|
2320
|
+
const cli = readFileSync(BUNDLED_RENDERER);
|
|
2321
|
+
const dataFile = renderMjsPath(home);
|
|
2322
|
+
if (!existsSync(dataFile)) return { kind: "missing" };
|
|
2323
|
+
const hash = rendererHash(readFileSync(dataFile));
|
|
2324
|
+
return hash === rendererHash(cli) ? {
|
|
2325
|
+
hash,
|
|
2326
|
+
kind: "current"
|
|
2327
|
+
} : {
|
|
2328
|
+
cli: rendererHash(cli),
|
|
2329
|
+
data: hash,
|
|
2330
|
+
kind: "stale"
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
function rendererRow(state) {
|
|
2334
|
+
if (state.kind === "current") return `renderer: current — ${state.hash}`;
|
|
2335
|
+
if (state.kind === "stale") return `renderer: stale — data ${state.data}, this CLI ${state.cli} — fix: rerun configure --force --theme classic`;
|
|
2336
|
+
if (state.kind === "no-bundle") return "renderer: this CLI ships no render.mjs — fix: rerun /lab to refresh the CLI";
|
|
2337
|
+
return "renderer: missing — fix: rerun configure --force --theme classic";
|
|
1189
2338
|
}
|
|
1190
2339
|
function configRow(findings) {
|
|
1191
2340
|
if (findings.length === 0) return "config: no drift";
|
|
@@ -1196,7 +2345,7 @@ function backupRow(home) {
|
|
|
1196
2345
|
try {
|
|
1197
2346
|
backup = readBackup(home);
|
|
1198
2347
|
} catch {
|
|
1199
|
-
return `backup: unreadable — fix: delete ~/${
|
|
2348
|
+
return `backup: unreadable — fix: delete ~/${DATA_DIR}/backup.json`;
|
|
1200
2349
|
}
|
|
1201
2350
|
if (backup === null) return "backup: absent";
|
|
1202
2351
|
const saved = SETTINGS_KEYS.filter((key) => backup.keys[key] !== void 0);
|
|
@@ -1225,30 +2374,25 @@ function settingsMembers(home) {
|
|
|
1225
2374
|
const raw = readOrNull(file);
|
|
1226
2375
|
return raw === null ? {} : parseSettings(file, raw);
|
|
1227
2376
|
}
|
|
1228
|
-
function resolveOrNull(home) {
|
|
1229
|
-
try {
|
|
1230
|
-
return resolveRuntime({ home });
|
|
1231
|
-
} catch {
|
|
1232
|
-
return null;
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
2377
|
function status(options) {
|
|
1236
|
-
const runtime = resolveOrNull(options.home);
|
|
1237
2378
|
const members = settingsMembers(options.home);
|
|
1238
2379
|
const main = keyState("statusLine", members.statusLine);
|
|
1239
2380
|
const subagent = keyState("subagentStatusLine", members.subagentStatusLine);
|
|
1240
2381
|
const config = readKeyConfig(options.home);
|
|
1241
|
-
const
|
|
1242
|
-
const findings =
|
|
2382
|
+
const panelConfig = parsePanelCommand(memberCommand(members.subagentStatusLine));
|
|
2383
|
+
const findings = main.kind === "ours" ? driftFindings(config) : [];
|
|
2384
|
+
const node = nodeOnPath(options.path ?? process.env.PATH ?? "");
|
|
2385
|
+
const renderer = rendererState(options.home);
|
|
1243
2386
|
const rows = [
|
|
1244
|
-
|
|
2387
|
+
node === null ? "node: missing — fix: install node ≥ 18 from nodejs.org, then restart Claude Code" : `node: on PATH (${node})`,
|
|
2388
|
+
rendererRow(renderer),
|
|
1245
2389
|
keyRow("statusLine", main, configDetail(config)),
|
|
1246
|
-
keyRow("subagentStatusLine", subagent),
|
|
1247
|
-
...
|
|
2390
|
+
keyRow("subagentStatusLine", subagent, panelDetail(panelConfig)),
|
|
2391
|
+
...main.kind === "ours" ? [configRow(findings), ...themeRow(config)] : [],
|
|
1248
2392
|
backupRow(options.home),
|
|
1249
2393
|
capturesRow(options.home)
|
|
1250
2394
|
];
|
|
1251
|
-
const healthy =
|
|
2395
|
+
const healthy = node !== null && renderer.kind === "current" && main.kind === "ours" && subagent.kind === "ours" && findings.length === 0;
|
|
1252
2396
|
return {
|
|
1253
2397
|
healthy,
|
|
1254
2398
|
rows: [...rows, healthy ? "healthy" : "unhealthy"]
|
|
@@ -1326,58 +2470,67 @@ var WIDTHS = [
|
|
|
1326
2470
|
];
|
|
1327
2471
|
var THEME_KEYMAP = "j/k focus · w width · enter pick · q cancel";
|
|
1328
2472
|
var REFINE_KEYMAP = "j/k move · h/l variant · s none · t themes · w width · enter save · q cancel";
|
|
2473
|
+
var STYLE = "style";
|
|
1329
2474
|
function opensWizard(args, isTty) {
|
|
1330
2475
|
return isTty && args.layout === void 0 && args.theme === void 0 && args.variants === void 0;
|
|
1331
2476
|
}
|
|
1332
2477
|
async function createWizard(options, deps) {
|
|
1333
|
-
const
|
|
1334
|
-
const themes = themesFor(runtime);
|
|
1335
|
-
const names = Object.keys(themes);
|
|
1336
|
-
const byItem = new Map(runtime.items.map((item) => [item.item, item]));
|
|
2478
|
+
const names = Object.keys(THEMES);
|
|
1337
2479
|
const sources = previewSources(options.home, Number(options.now));
|
|
1338
2480
|
const tickBase = JSON.parse(sources.tick);
|
|
1339
2481
|
let pass = "themes";
|
|
1340
2482
|
let themeAt = 0;
|
|
1341
|
-
let
|
|
2483
|
+
let picked = names[0];
|
|
2484
|
+
let base = DEFAULT_PICKS;
|
|
1342
2485
|
let offered = [];
|
|
1343
|
-
const
|
|
2486
|
+
const overrides = /* @__PURE__ */ new Map();
|
|
1344
2487
|
let focus = 0;
|
|
1345
2488
|
let widthAt = 0;
|
|
1346
|
-
function bag(layout, values) {
|
|
2489
|
+
function bag(theme, layout, values = {}) {
|
|
1347
2490
|
return {
|
|
1348
2491
|
home: options.home,
|
|
1349
|
-
layout,
|
|
2492
|
+
...layout === void 0 ? {} : { layout },
|
|
1350
2493
|
main: sources.main,
|
|
1351
2494
|
now: options.now,
|
|
1352
|
-
runtime,
|
|
1353
2495
|
tick: `${JSON.stringify({
|
|
1354
2496
|
...tickBase,
|
|
1355
2497
|
columns: WIDTHS[widthAt]
|
|
1356
2498
|
}, null, 2)}\n`,
|
|
2499
|
+
theme,
|
|
1357
2500
|
values,
|
|
1358
2501
|
width: WIDTHS[widthAt]
|
|
1359
2502
|
};
|
|
1360
2503
|
}
|
|
1361
2504
|
function enterRefine(name) {
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
2505
|
+
picked = name;
|
|
2506
|
+
base = {
|
|
2507
|
+
...DEFAULT_PICKS,
|
|
2508
|
+
...THEMES[name].variants
|
|
2509
|
+
};
|
|
2510
|
+
const items = layoutItems(THEMES[name].layout, ITEM_IDS);
|
|
2511
|
+
offered = [.../* @__PURE__ */ new Set([...items, STYLE])].flatMap((item) => {
|
|
2512
|
+
const entry = specFor(item);
|
|
1366
2513
|
return entry === void 0 ? [] : [{
|
|
1367
2514
|
alternatives: entry.alternatives,
|
|
1368
2515
|
item
|
|
1369
2516
|
}];
|
|
1370
2517
|
});
|
|
1371
|
-
|
|
1372
|
-
for (const [item, alt] of Object.entries(theme.variants)) draft.set(item, alt);
|
|
2518
|
+
overrides.clear();
|
|
1373
2519
|
focus = 0;
|
|
1374
2520
|
}
|
|
2521
|
+
function effectivePick(item) {
|
|
2522
|
+
return overrides.get(item) ?? base[item];
|
|
2523
|
+
}
|
|
2524
|
+
function setPick(item, alt) {
|
|
2525
|
+
if (alt === base[item]) overrides.delete(item);
|
|
2526
|
+
else overrides.set(item, alt);
|
|
2527
|
+
}
|
|
1375
2528
|
function cycle(delta) {
|
|
1376
2529
|
const focused = offered.at(focus);
|
|
1377
2530
|
if (focused === void 0 || focused.alternatives.length === 0) return;
|
|
1378
2531
|
const alts = focused.alternatives;
|
|
1379
|
-
const at = alts.indexOf(
|
|
1380
|
-
|
|
2532
|
+
const at = alts.indexOf(effectivePick(focused.item));
|
|
2533
|
+
setPick(focused.item, alts[(at + delta + alts.length) % alts.length]);
|
|
1381
2534
|
}
|
|
1382
2535
|
function step(key) {
|
|
1383
2536
|
const down = key === "\x1B[B" || key === "j";
|
|
@@ -1393,7 +2546,7 @@ async function createWizard(options, deps) {
|
|
|
1393
2546
|
else if (key === "\x1B[D" || key === "h") cycle(-1);
|
|
1394
2547
|
else if (key === "s") {
|
|
1395
2548
|
const focused = offered.at(focus);
|
|
1396
|
-
if (focused?.alternatives.includes("none"))
|
|
2549
|
+
if (focused?.alternatives.includes("none")) setPick(focused.item, "none");
|
|
1397
2550
|
}
|
|
1398
2551
|
return pass;
|
|
1399
2552
|
}
|
|
@@ -1402,8 +2555,8 @@ async function createWizard(options, deps) {
|
|
|
1402
2555
|
let panel = "";
|
|
1403
2556
|
for (let at = 0; at < names.length; at += 1) {
|
|
1404
2557
|
const name = names[at];
|
|
1405
|
-
const theme =
|
|
1406
|
-
const surfaces = deps.preview(bag(
|
|
2558
|
+
const theme = THEMES[name];
|
|
2559
|
+
const surfaces = deps.preview(bag(name));
|
|
1407
2560
|
const focused = at === themeAt;
|
|
1408
2561
|
if (focused) panel = surfaces.panel;
|
|
1409
2562
|
rows.push(`${focused ? ">" : " "} ${name}: ${theme.summary}`);
|
|
@@ -1422,13 +2575,16 @@ async function createWizard(options, deps) {
|
|
|
1422
2575
|
function drawRefine() {
|
|
1423
2576
|
const focused = offered.at(focus);
|
|
1424
2577
|
if (focused === void 0) return;
|
|
1425
|
-
const values = Object.fromEntries(
|
|
1426
|
-
const samples = focused.alternatives.map((alt) =>
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
2578
|
+
const values = Object.fromEntries(overrides);
|
|
2579
|
+
const samples = focused.alternatives.map((alt) => {
|
|
2580
|
+
const surfaces = deps.preview(bag(picked, focused.item === STYLE ? void 0 : `{${focused.item}}`, {
|
|
2581
|
+
...values,
|
|
2582
|
+
[focused.item]: alt
|
|
2583
|
+
}));
|
|
2584
|
+
return focused.item === STYLE ? surfaces.panel : surfaces.line;
|
|
2585
|
+
});
|
|
2586
|
+
const full = deps.preview(bag(picked, void 0, values));
|
|
2587
|
+
const rows = offered.map((entry, at) => `${at === focus ? ">" : " "} ${entry.item.padEnd(9)} ${effectivePick(entry.item)}`);
|
|
1432
2588
|
deps.render([
|
|
1433
2589
|
`statusline configure · ${WIDTHS[widthAt]} columns (w cycles)`,
|
|
1434
2590
|
"",
|
|
@@ -1436,7 +2592,7 @@ async function createWizard(options, deps) {
|
|
|
1436
2592
|
` panel ${full.panel}`,
|
|
1437
2593
|
"",
|
|
1438
2594
|
`${focused.item}: ${focused.alternatives.join(" | ")}`,
|
|
1439
|
-
...focused.alternatives.map((alt, at) => ` ${alt ===
|
|
2595
|
+
...focused.alternatives.map((alt, at) => ` ${alt === effectivePick(focused.item) ? "*" : " "} ${samples[at]}`),
|
|
1440
2596
|
"",
|
|
1441
2597
|
...rows,
|
|
1442
2598
|
"",
|
|
@@ -1452,8 +2608,8 @@ async function createWizard(options, deps) {
|
|
|
1452
2608
|
configure({
|
|
1453
2609
|
force: options.force,
|
|
1454
2610
|
home: options.home,
|
|
1455
|
-
|
|
1456
|
-
variants: Object.fromEntries(
|
|
2611
|
+
theme: picked,
|
|
2612
|
+
variants: Object.fromEntries(overrides)
|
|
1457
2613
|
});
|
|
1458
2614
|
} catch (e) {
|
|
1459
2615
|
deps.render(`save failed: ${e.message}\nfix it and rerun: npx -y @v1nvn/statusline@${VERSION} configure\n`);
|