@v1nvn/statusline 0.27.3 → 0.29.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.
@@ -0,0 +1,1452 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from "node:util";
3
+ import { mkdirSync, renameSync, writeFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ //#region src/render/items.ts
7
+ var ITEMS = [
8
+ {
9
+ alternatives: [
10
+ "plain",
11
+ "block",
12
+ "pill",
13
+ "zen"
14
+ ],
15
+ default: "plain",
16
+ item: "model"
17
+ },
18
+ {
19
+ alternatives: [
20
+ "plain",
21
+ "dim",
22
+ "hidden"
23
+ ],
24
+ default: "plain",
25
+ item: "effort"
26
+ },
27
+ {
28
+ alternatives: ["none", "pills"],
29
+ default: "none",
30
+ item: "state"
31
+ },
32
+ {
33
+ alternatives: [
34
+ "init",
35
+ "full",
36
+ "tail",
37
+ "base",
38
+ "icon"
39
+ ],
40
+ default: "init",
41
+ item: "cwd"
42
+ },
43
+ {
44
+ alternatives: [
45
+ "initials",
46
+ "full",
47
+ "last",
48
+ "icon",
49
+ "none"
50
+ ],
51
+ default: "initials",
52
+ item: "branch"
53
+ },
54
+ {
55
+ alternatives: [
56
+ "counts",
57
+ "icons",
58
+ "none"
59
+ ],
60
+ default: "counts",
61
+ item: "status"
62
+ },
63
+ {
64
+ alternatives: ["none", "arrows"],
65
+ default: "none",
66
+ item: "ahead"
67
+ },
68
+ {
69
+ alternatives: ["none", "badge"],
70
+ default: "none",
71
+ item: "pr"
72
+ },
73
+ {
74
+ alternatives: [
75
+ "flat",
76
+ "gauge",
77
+ "percent",
78
+ "none",
79
+ "flat6",
80
+ "flat4"
81
+ ],
82
+ default: "flat",
83
+ item: "bar"
84
+ },
85
+ {
86
+ alternatives: [
87
+ "full",
88
+ "compact",
89
+ "free",
90
+ "none"
91
+ ],
92
+ default: "full",
93
+ item: "tokens"
94
+ },
95
+ {
96
+ alternatives: [
97
+ "hit",
98
+ "coldin",
99
+ "fuse",
100
+ "none"
101
+ ],
102
+ default: "hit",
103
+ item: "cache"
104
+ },
105
+ {
106
+ alternatives: [
107
+ "plain",
108
+ "burn",
109
+ "none"
110
+ ],
111
+ default: "plain",
112
+ item: "cost"
113
+ },
114
+ {
115
+ alternatives: [
116
+ "clock",
117
+ "hours",
118
+ "none"
119
+ ],
120
+ default: "clock",
121
+ item: "duration"
122
+ },
123
+ {
124
+ alternatives: ["none", "diffstat"],
125
+ default: "none",
126
+ item: "lines"
127
+ },
128
+ {
129
+ alternatives: ["none", "strip"],
130
+ default: "none",
131
+ item: "rate"
132
+ },
133
+ {
134
+ alternatives: [
135
+ "plain",
136
+ "dots",
137
+ "dim",
138
+ "bare"
139
+ ],
140
+ default: "plain",
141
+ item: "style"
142
+ }
143
+ ];
144
+ var DEFAULT_LAYOUT = "{model effort state} {cwd branch status ahead pr} {bar tokens cache} {cost} {duration} {lines} {rate}";
145
+ var BY_ITEM = new Map(ITEMS.map((spec) => [spec.item, spec]));
146
+ function specFor(item) {
147
+ return BY_ITEM.get(item);
148
+ }
149
+ ITEMS.map((spec) => spec.item);
150
+ Object.fromEntries(ITEMS.map(({ default: alt, item }) => [item, alt]));
151
+ var RUNG_ORDERS = {
152
+ bar: [
153
+ "flat",
154
+ "flat6",
155
+ "flat4",
156
+ "percent",
157
+ "none"
158
+ ],
159
+ branch: [
160
+ "icon",
161
+ "full",
162
+ "initials",
163
+ "last",
164
+ "none"
165
+ ],
166
+ cache: [
167
+ "hit",
168
+ "coldin",
169
+ "none"
170
+ ],
171
+ cwd: [
172
+ "icon",
173
+ "full",
174
+ "init",
175
+ "tail",
176
+ "base"
177
+ ],
178
+ duration: [
179
+ "clock",
180
+ "hours",
181
+ "none"
182
+ ],
183
+ effort: [
184
+ "plain",
185
+ "dim",
186
+ "hidden"
187
+ ],
188
+ status: [
189
+ "counts",
190
+ "icons",
191
+ "none"
192
+ ],
193
+ tokens: [
194
+ "full",
195
+ "free",
196
+ "compact",
197
+ "none"
198
+ ]
199
+ };
200
+ var FULL_STEPS = [
201
+ ["duration", "none"],
202
+ ["cache", "none"],
203
+ ["tokens", "compact"],
204
+ ["bar", "flat6"],
205
+ ["status", "none"],
206
+ ["branch", "initials"],
207
+ ["cwd", "init"],
208
+ ["branch", "last"],
209
+ ["bar", "flat4"],
210
+ ["model", "strip"],
211
+ ["bar", "percent"],
212
+ ["branch", "none"],
213
+ ["cwd", "tail"],
214
+ ["effort", "hidden"],
215
+ ["cwd", "base"],
216
+ ["tokens", "none"]
217
+ ];
218
+ var L1_STEPS = [
219
+ ["status", "none"],
220
+ ["branch", "initials"],
221
+ ["cwd", "init"],
222
+ ["branch", "last"],
223
+ ["model", "strip"],
224
+ ["branch", "none"],
225
+ ["cwd", "tail"],
226
+ ["effort", "hidden"],
227
+ ["cwd", "base"]
228
+ ];
229
+ var L2_STEPS = [
230
+ ["duration", "none"],
231
+ ["cache", "none"],
232
+ ["tokens", "compact"],
233
+ ["bar", "flat6"],
234
+ ["bar", "flat4"],
235
+ ["tokens", "none"],
236
+ ["bar", "percent"]
237
+ ];
238
+ //#endregion
239
+ //#region src/render/argv.ts
240
+ function warn$1(warnings, message) {
241
+ warnings.push(message);
242
+ }
243
+ function parseArgv(argv) {
244
+ const parsed = parseArgs({
245
+ allowPositionals: true,
246
+ args: [...argv],
247
+ options: {
248
+ layout: { type: "string" },
249
+ now: { type: "string" },
250
+ theme: { type: "string" }
251
+ },
252
+ strict: false
253
+ });
254
+ const warnings = [];
255
+ const picks = {};
256
+ let mode = "line";
257
+ let theme;
258
+ let layout;
259
+ let now;
260
+ for (const positional of parsed.positionals) if (positional === "panel") mode = "panel";
261
+ else warn$1(warnings, `statusline: unexpected argument '${positional}', ignored`);
262
+ for (const [name, value] of Object.entries(parsed.values)) {
263
+ if (typeof value !== "string") {
264
+ warn$1(warnings, `statusline: --${name} needs a value, ignored`);
265
+ continue;
266
+ }
267
+ if (name === "theme") {
268
+ theme = value;
269
+ continue;
270
+ }
271
+ if (name === "layout") {
272
+ layout = value;
273
+ continue;
274
+ }
275
+ if (name === "now") {
276
+ const epoch = Number(value);
277
+ if (Number.isFinite(epoch) && value.trim() !== "") now = epoch;
278
+ else warn$1(warnings, `statusline: --now=${value} is not a number, ignored`);
279
+ continue;
280
+ }
281
+ const spec = specFor(name);
282
+ if (spec === void 0) {
283
+ warn$1(warnings, `statusline: --${name} is not a known flag, ignored`);
284
+ continue;
285
+ }
286
+ if (spec.alternatives.includes(value)) picks[name] = value;
287
+ else warn$1(warnings, `statusline: ${name}=${value} is not available, using ${name}=${spec.default}`);
288
+ }
289
+ return {
290
+ mode,
291
+ picks,
292
+ warnings,
293
+ ...theme === void 0 ? {} : { theme },
294
+ ...layout === void 0 ? {} : { layout },
295
+ ...now === void 0 ? {} : { now }
296
+ };
297
+ }
298
+ //#endregion
299
+ //#region src/render/capture.ts
300
+ var DATA_DIR = join(".claude", "plugins", "data", "statusline-agentic");
301
+ function capturePayload(home, surface, payload) {
302
+ const dest = join(home, DATA_DIR, "captures", `${surface}.json`);
303
+ try {
304
+ mkdirSync(dirname(dest), { recursive: true });
305
+ writeFileSync(`${dest}.tmp`, payload);
306
+ renameSync(`${dest}.tmp`, dest);
307
+ } catch {}
308
+ }
309
+ //#endregion
310
+ //#region src/render/git.ts
311
+ var BRANCH_HEAD = "# branch.head ";
312
+ var BRANCH_AB = /^# branch\.ab \+(\d+) -(\d+)$/;
313
+ function gitEnv(env) {
314
+ return {
315
+ HOME: env.home,
316
+ LC_ALL: "C",
317
+ PATH: process.env.PATH ?? ""
318
+ };
319
+ }
320
+ function gitText(dir, args, env) {
321
+ const run = spawnSync("git", [
322
+ "-C",
323
+ dir,
324
+ ...args
325
+ ], {
326
+ encoding: "utf8",
327
+ env: gitEnv(env)
328
+ });
329
+ return run.status === 0 ? run.stdout : null;
330
+ }
331
+ function lineCount(text) {
332
+ return text === null || text === "" ? 0 : (text.match(/\n/g) ?? []).length;
333
+ }
334
+ function count(text) {
335
+ const parsed = text === null ? NaN : Number(text.trim());
336
+ return Number.isFinite(parsed) ? Math.trunc(parsed) : 0;
337
+ }
338
+ function readGit(dir, env) {
339
+ const facts = {
340
+ ahead: 0,
341
+ behind: 0,
342
+ branch: "",
343
+ modified: 0,
344
+ staged: 0,
345
+ stashes: 0,
346
+ untracked: 0
347
+ };
348
+ if (dir === "") return facts;
349
+ if (spawnSync("git", [
350
+ "-C",
351
+ dir,
352
+ "rev-parse",
353
+ "--git-dir"
354
+ ], {
355
+ encoding: "utf8",
356
+ env: gitEnv(env)
357
+ }).status !== 0) return facts;
358
+ const status = gitText(dir, [
359
+ "status",
360
+ "--porcelain=v2",
361
+ "--branch"
362
+ ], env);
363
+ for (const line of (status ?? "").split("\n")) {
364
+ if (line.startsWith(BRANCH_HEAD)) {
365
+ facts.branch = line.slice(14);
366
+ continue;
367
+ }
368
+ const ab = BRANCH_AB.exec(line);
369
+ if (ab !== null) {
370
+ facts.ahead = count(ab[1]);
371
+ facts.behind = count(ab[2]);
372
+ } else if ((line.startsWith("1 ") || line.startsWith("2 ")) && line.length >= 4) {
373
+ if (line[2] !== ".") facts.staged += 1;
374
+ if (line[3] !== ".") facts.modified += 1;
375
+ }
376
+ }
377
+ if (facts.branch === "(detached)") facts.branch = "";
378
+ facts.untracked = lineCount(gitText(dir, [
379
+ "ls-files",
380
+ "--others",
381
+ "--exclude-standard"
382
+ ], env));
383
+ facts.stashes = lineCount(gitText(dir, ["stash", "list"], env));
384
+ return facts;
385
+ }
386
+ //#endregion
387
+ //#region src/render/layout.ts
388
+ function parseClusters(layout) {
389
+ const clusters = [];
390
+ let words = [];
391
+ let word = "";
392
+ let open = false;
393
+ function pushWord() {
394
+ if (word !== "") {
395
+ words.push(word);
396
+ word = "";
397
+ }
398
+ }
399
+ for (const c of layout) if (c === "{") {
400
+ if (open) throw new Error(`layout '${layout}': '{' inside a cluster`);
401
+ open = true;
402
+ words = [];
403
+ } else if (c === "}") {
404
+ if (!open) throw new Error(`layout '${layout}': '}' outside a cluster`);
405
+ pushWord();
406
+ open = false;
407
+ if (words.length > 0) clusters.push(words);
408
+ } else if (c === " ") {
409
+ if (open) pushWord();
410
+ else if (word !== "") throw new Error(`layout '${layout}': '${word}' sits outside a cluster`);
411
+ } else if (/[a-z0-9]/.test(c)) word += c;
412
+ else throw new Error(`layout '${layout}': '${c}' is not layout grammar (braces, item ids, spaces)`);
413
+ if (open) throw new Error(`layout '${layout}': unterminated cluster`);
414
+ if (word !== "") throw new Error(`layout '${layout}': '${word}' sits outside a cluster`);
415
+ if (clusters.length === 0) throw new Error(`layout '${layout}': no clusters`);
416
+ return clusters;
417
+ }
418
+ //#endregion
419
+ //#region src/render/jq.ts
420
+ function isRecord(value) {
421
+ return typeof value === "object" && value !== null && !Array.isArray(value);
422
+ }
423
+ function orElse(value, fallback) {
424
+ return value === void 0 || value === null || value === false ? fallback : value;
425
+ }
426
+ function jqText(value) {
427
+ if (typeof value === "string") return value;
428
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
429
+ return JSON.stringify(value);
430
+ }
431
+ function tsvEscape(text) {
432
+ return text.replace(/\\/g, "\\\\").replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
433
+ }
434
+ //#endregion
435
+ //#region src/render/payload.ts
436
+ function member(source, key) {
437
+ return isRecord(source) ? source[key] : void 0;
438
+ }
439
+ function field$1(source, path) {
440
+ let current = source;
441
+ for (const key of path.split(".")) current = member(current, key);
442
+ return current;
443
+ }
444
+ function textOf(value) {
445
+ if (typeof value === "string") return value;
446
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
447
+ return "";
448
+ }
449
+ function scalar(source, path, fallback) {
450
+ return textOf(orElse(field$1(source, path), fallback));
451
+ }
452
+ function numberField(source, path) {
453
+ return Number(scalar(source, path, "0"));
454
+ }
455
+ function pctField(source) {
456
+ const intPart = scalar(source, "context_window.used_percentage", "0").split(".")[0];
457
+ return intPart === "" ? 0 : Number(intPart);
458
+ }
459
+ var RATE_LIMITS = [
460
+ ["5h", "five_hour"],
461
+ ["7d", "seven_day"],
462
+ ["spend", "spend_limit"]
463
+ ];
464
+ function rateRows(source) {
465
+ const limits = field$1(source, "rate_limits");
466
+ const rows = [];
467
+ for (const [label, key] of RATE_LIMITS) {
468
+ const limit = member(limits, key);
469
+ if (!isRecord(limit) || Object.keys(limit).length === 0) continue;
470
+ rows.push({
471
+ label,
472
+ pctText: scalar(limit, "used_percentage", "0"),
473
+ resets: Number(scalar(limit, "resets_at", "0"))
474
+ });
475
+ }
476
+ return rows;
477
+ }
478
+ function parseRow(payload) {
479
+ const root = JSON.parse(payload);
480
+ const hitText = scalar(root, "prompt_cache.hit_ratio", "");
481
+ return {
482
+ agent: scalar(root, "agent.name", ""),
483
+ cost: numberField(root, "cost.total_cost_usd"),
484
+ ctxSize: numberField(root, "context_window.context_window_size"),
485
+ dir: scalar(root, "workspace.current_dir", ""),
486
+ durationMs: numberField(root, "cost.total_duration_ms"),
487
+ effort: scalar(root, "effort.level", ""),
488
+ expires: numberField(root, "prompt_cache.expires_at"),
489
+ hit: hitText === "" ? null : Number(hitText),
490
+ la: numberField(root, "cost.total_lines_added"),
491
+ lr: numberField(root, "cost.total_lines_removed"),
492
+ model: scalar(root, "model.display_name", ""),
493
+ pct: pctField(root),
494
+ prn: scalar(root, "pr.number", ""),
495
+ prs: scalar(root, "pr.review_state", ""),
496
+ rateRows: rateRows(root),
497
+ think: scalar(root, "thinking.enabled", "false") === "true",
498
+ tokens: numberField(root, "context_window.total_input_tokens"),
499
+ ttl: scalar(root, "prompt_cache.ttl", ""),
500
+ vim: scalar(root, "vim.mode", ""),
501
+ warm: scalar(root, "prompt_cache.warm", "false") === "true",
502
+ wt: scalar(root, "worktree.name", ""),
503
+ styleName: scalar(root, "output_style.name", "")
504
+ };
505
+ }
506
+ //#endregion
507
+ //#region src/render/awk.ts
508
+ var TEN = 10n;
509
+ function exactParts(ax) {
510
+ const view = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(8));
511
+ view.setFloat64(0, ax);
512
+ const hi = view.getUint32(0);
513
+ const lo = view.getUint32(4);
514
+ const biased = hi >>> 20 & 2047;
515
+ const frac = BigInt(hi & 1048575) << 32n | BigInt(lo);
516
+ if (biased === 0) return {
517
+ exp: -1074,
518
+ mant: frac
519
+ };
520
+ return {
521
+ exp: biased - 1075,
522
+ mant: frac | 1n << 52n
523
+ };
524
+ }
525
+ function roundScaled(ax, d) {
526
+ const { exp, mant } = exactParts(ax);
527
+ const pow10 = TEN ** BigInt(d);
528
+ const num = exp >= 0 ? (mant << BigInt(exp)) * pow10 : mant * pow10;
529
+ const den = exp >= 0 ? 1n : 1n << BigInt(-exp);
530
+ const q = num / den;
531
+ const twice = num % den * 2n;
532
+ if (twice > den || twice === den && q % 2n === 1n) return q + 1n;
533
+ return q;
534
+ }
535
+ function fmtFixed(x, d) {
536
+ const q = roundScaled(Math.abs(x), d);
537
+ const sign = x < 0 && q !== 0n ? "-" : "";
538
+ const digits = q.toString();
539
+ if (d === 0) return `${sign}${digits}`;
540
+ return `${sign}${digits.length <= d ? "0" : digits.slice(0, digits.length - d)}.${digits.padStart(d + 1, "0").slice(-d)}`;
541
+ }
542
+ function pad2(n) {
543
+ return String(n).padStart(2, "0");
544
+ }
545
+ function fmtK(n, d) {
546
+ return `${fmtFixed(n / 1e3, d)}k`;
547
+ }
548
+ function fmtM(n) {
549
+ return `${fmtFixed(n / 1e6, 0)}M`;
550
+ }
551
+ //#endregion
552
+ //#region src/render/segments.ts
553
+ var BLUE = "\x1B[34m";
554
+ var CYAN$1 = "\x1B[36m";
555
+ var DIM = "\x1B[2m";
556
+ var GREEN$1 = "\x1B[32m";
557
+ var PURPLE = "\x1B[35m";
558
+ var RED$1 = "\x1B[31m";
559
+ var RESET$1 = "\x1B[0m";
560
+ var YELLOW$1 = "\x1B[33m";
561
+ var BOLD = "\x1B[1m";
562
+ var GRAY = "\x1B[38;2;68;71;90m";
563
+ var MARK = "\x1B[1;97m";
564
+ var SHORT_AT = 15;
565
+ var PART = [
566
+ "▏",
567
+ "▎",
568
+ "▍",
569
+ "▌",
570
+ "▋",
571
+ "▊",
572
+ "▉",
573
+ "█"
574
+ ];
575
+ var HEAT = [
576
+ "\x1B[38;2;48;242;36m",
577
+ "\x1B[38;2;63;242;36m",
578
+ "\x1B[38;2;79;242;36m",
579
+ "\x1B[38;2;95;242;36m",
580
+ "\x1B[38;2;111;242;36m",
581
+ "\x1B[38;2;126;242;36m",
582
+ "\x1B[38;2;142;242;36m",
583
+ "\x1B[38;2;158;242;36m",
584
+ "\x1B[38;2;173;242;36m",
585
+ "\x1B[38;2;189;242;36m",
586
+ "\x1B[38;2;205;242;36m",
587
+ "\x1B[38;2;220;242;36m",
588
+ "\x1B[38;2;236;242;36m",
589
+ "\x1B[38;2;242;232;36m",
590
+ "\x1B[38;2;242;216;36m",
591
+ "\x1B[38;2;242;200;36m",
592
+ "\x1B[38;2;242;185;36m",
593
+ "\x1B[38;2;242;169;36m",
594
+ "\x1B[38;2;242;153;36m",
595
+ "\x1B[38;2;242;138;36m",
596
+ "\x1B[38;2;242;122;36m",
597
+ "\x1B[38;2;242;106;36m",
598
+ "\x1B[38;2;242;91;36m",
599
+ "\x1B[38;2;242;75;36m",
600
+ "\x1B[38;2;242;59;36m",
601
+ "\x1B[38;2;242;44;36m"
602
+ ];
603
+ function trunc(n) {
604
+ return Math.trunc(n);
605
+ }
606
+ function modelPlain({ model }) {
607
+ return model === "" ? "" : `${CYAN$1}${model}${RESET$1}`;
608
+ }
609
+ function modelBlock({ model }) {
610
+ return model === "" ? "" : `\x1b[48;5;61m\x1b[38;5;231m ${model} \x1b[0m`;
611
+ }
612
+ function modelPill({ model }) {
613
+ return model === "" ? "" : `\x1b[48;5;61m\x1b[38;5;231m\u{e0b6} ${model} \u{e0b4}\x1b[0m`;
614
+ }
615
+ function modelZen({ model }) {
616
+ return model === "" ? "" : `${DIM}${model.replace(/[A-Z]/g, (c) => c.toLowerCase())}${RESET$1}`;
617
+ }
618
+ function effortPlain({ row }) {
619
+ return row.effort === "" ? "" : `${CYAN$1}${row.effort}${RESET$1}`;
620
+ }
621
+ function effortDim({ row }) {
622
+ return row.effort === "" ? "" : `${DIM}${row.effort}${RESET$1}`;
623
+ }
624
+ function statePill(label, bg, fg) {
625
+ return `\x1b[48;5;${bg}m\x1b[38;5;${fg}m\u{e0b6} ${label} \u{e0b4}\x1b[0m`;
626
+ }
627
+ function statePills({ row }) {
628
+ let out = "";
629
+ if (row.vim !== "") out = row.vim === "INSERT" ? statePill(row.vim, 97, 16) : statePill(row.vim, 240, 231);
630
+ if (row.think) out += ` ${statePill("THINK", 66, 16)}`;
631
+ if (row.agent !== "") out += ` ${statePill(row.agent, 60, 231)}`;
632
+ if (row.wt !== "") out += ` ${statePill(`wt:${row.wt}`, 131, 231)}`;
633
+ if (row.styleName !== "" && row.styleName !== "default") out += ` ${statePill(row.styleName, 95, 16)}`;
634
+ return out;
635
+ }
636
+ function splitPath(raw, home) {
637
+ let p = raw;
638
+ if (home !== "" && p.startsWith(home)) p = `~${p.slice(home.length)}`;
639
+ return {
640
+ lead: p.startsWith("/") ? "/" : "",
641
+ parts: p.split("/").filter((part) => part !== ""),
642
+ text: p
643
+ };
644
+ }
645
+ function pathInit(raw, home) {
646
+ const { lead, parts, text } = splitPath(raw, home);
647
+ const n = parts.length;
648
+ if (n <= 2) return text;
649
+ let out = `${lead}${parts[0]}`;
650
+ for (let i = 1; i < n - 1; i++) {
651
+ const ini = parts[i].startsWith(".") ? parts[i].slice(0, 2) : parts[i].slice(0, 1);
652
+ out += `/${ini}`;
653
+ }
654
+ return `${out}/${parts[n - 1]}`;
655
+ }
656
+ function pathTail(raw, home, lim) {
657
+ const { lead, parts, text } = splitPath(raw, home);
658
+ const cnt = parts.length;
659
+ if (cnt <= lim || cnt <= 1) return text;
660
+ let out = `${lead}${parts[0]}/…`;
661
+ for (let i = cnt - lim; i < cnt; i++) out += `/${parts[i]}`;
662
+ return out;
663
+ }
664
+ function cwdInit({ home, row }) {
665
+ const { text } = splitPath(row.dir, home);
666
+ return Buffer.byteLength(text, "utf8") > SHORT_AT ? pathInit(row.dir, home) : text;
667
+ }
668
+ function cwdFull({ home, row }) {
669
+ return splitPath(row.dir, home).text;
670
+ }
671
+ function cwdTail({ home, row }) {
672
+ return pathTail(row.dir, home, 1);
673
+ }
674
+ function baseName(dir) {
675
+ return dir.slice(dir.lastIndexOf("/") + 1);
676
+ }
677
+ function cwdBase({ row }) {
678
+ return baseName(row.dir);
679
+ }
680
+ function cwdIcon({ row }) {
681
+ return `\u{f07b} ${baseName(row.dir)}`;
682
+ }
683
+ function branchInitials({ git }) {
684
+ const branch = git.branch;
685
+ if (branch === "") return "";
686
+ if (Buffer.byteLength(branch, "utf8") <= SHORT_AT) return branch;
687
+ const parts = branch.split("/").filter((part) => part !== "");
688
+ if (parts.length <= 1) return branch;
689
+ let out = "";
690
+ for (let i = 0; i < parts.length - 1; i++) out += `${parts[i].slice(0, 1)}/`;
691
+ return `${out}${parts[parts.length - 1]}`;
692
+ }
693
+ function branchFull({ git }) {
694
+ return git.branch;
695
+ }
696
+ function branchLast({ git }) {
697
+ return git.branch.slice(git.branch.lastIndexOf("/") + 1);
698
+ }
699
+ function branchIcon({ git }) {
700
+ return git.branch === "" ? "" : `\u{e0a0} ${git.branch}`;
701
+ }
702
+ function statusCounts({ git }) {
703
+ if (git.branch === "") return "";
704
+ let c = "";
705
+ if (git.staged > 0) c = `${GREEN$1}+${git.staged}${RESET$1}`;
706
+ if (git.modified > 0) c = `${c} ${YELLOW$1}~${git.modified}${RESET$1}`;
707
+ return c;
708
+ }
709
+ function statusIcons({ git }) {
710
+ if (git.branch === "") return "";
711
+ let out = "";
712
+ let sep = "";
713
+ if (git.staged > 0) {
714
+ out += `${GREEN$1}●${git.staged}${RESET$1}`;
715
+ sep = " ";
716
+ }
717
+ if (git.modified > 0) {
718
+ out += `${sep}${YELLOW$1}✎${git.modified}${RESET$1}`;
719
+ sep = " ";
720
+ }
721
+ if (git.untracked > 0) {
722
+ out += `${sep}${CYAN$1}+${git.untracked}${RESET$1}`;
723
+ sep = " ";
724
+ }
725
+ if (git.stashes > 0) out += `${sep}${PURPLE}⚑${git.stashes}${RESET$1}`;
726
+ return out;
727
+ }
728
+ function aheadArrows({ git }) {
729
+ let out = "";
730
+ if (git.ahead > 0) out = `${GREEN$1}↑${git.ahead}${RESET$1}`;
731
+ if (git.behind > 0) out += ` ${RED$1}↓${git.behind}${RESET$1}`;
732
+ return out;
733
+ }
734
+ function prBadge({ row }) {
735
+ if (row.prn === "") return "";
736
+ let col;
737
+ let mark;
738
+ switch (row.prs) {
739
+ case "approved":
740
+ col = GREEN$1;
741
+ mark = "✓";
742
+ break;
743
+ case "changes_requested":
744
+ col = RED$1;
745
+ mark = "✗";
746
+ break;
747
+ case "pending":
748
+ col = YELLOW$1;
749
+ mark = "⏳";
750
+ break;
751
+ default:
752
+ col = "\x1B[90m";
753
+ mark = "◌";
754
+ }
755
+ return `${BLUE}#${row.prn}${RESET$1} ${col}${mark} ${row.prs}${RESET$1}`;
756
+ }
757
+ function barFlatAt(input, w) {
758
+ const pct = input.row.pct;
759
+ let f = trunc(pct * w / 100);
760
+ if (f > w) f = w;
761
+ return `${pct >= 90 ? RED$1 : pct >= 70 ? YELLOW$1 : GREEN$1}${"█".repeat(f)}${"░".repeat(w - f)}${RESET$1}`;
762
+ }
763
+ function barFlat(input) {
764
+ return barFlatAt(input, 10);
765
+ }
766
+ function barGauge({ row }) {
767
+ const pct = row.pct;
768
+ const p = Math.max(0, Math.min(100, pct));
769
+ const full = trunc(p * 26 / 100);
770
+ const idx = trunc(p * 26 % 100 * 8 / 100);
771
+ 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`;
772
+ const moon = [
773
+ "○",
774
+ "◔",
775
+ "◑",
776
+ "◕",
777
+ "●"
778
+ ][Math.min(trunc(p / 20), 4)];
779
+ let bar = "";
780
+ for (let i = 0; i < 26; i++) {
781
+ let col;
782
+ let cell;
783
+ if (i === 20) {
784
+ col = MARK;
785
+ cell = i <= full ? "┃" : "│";
786
+ } else if (i <= full) {
787
+ col = HEAT[i];
788
+ cell = i === full ? PART[idx] : "█";
789
+ } else {
790
+ col = GRAY;
791
+ cell = "░";
792
+ }
793
+ bar += `${col}${cell}`;
794
+ }
795
+ return `${moon} ${lead}${bar}${RESET$1} ${MARK}${pct}%${RESET$1}`;
796
+ }
797
+ function tokensFull({ row }) {
798
+ const t = row.tokens >= 1e3 ? fmtK(row.tokens, 1) : String(row.tokens);
799
+ let c = String(row.ctxSize);
800
+ if (row.ctxSize >= 1e6) c = fmtM(row.ctxSize);
801
+ else if (row.ctxSize >= 1e3) c = fmtK(row.ctxSize, 0);
802
+ return `${t}/${c}`;
803
+ }
804
+ function tokensCompact({ row }) {
805
+ return row.tokens >= 1e3 ? fmtK(row.tokens, 0) : String(row.tokens);
806
+ }
807
+ function tokensFree({ row }) {
808
+ const free = row.ctxSize - row.tokens;
809
+ return `${DIM}${free >= 1e3 ? fmtK(free, 0) : String(free)} free${RESET$1}`;
810
+ }
811
+ function cacheHitPct(row) {
812
+ return row.hit === null ? null : trunc(row.hit * 100);
813
+ }
814
+ function cacheHCol(hp) {
815
+ return hp >= 90 ? GREEN$1 : hp >= 50 ? YELLOW$1 : RED$1;
816
+ }
817
+ function cacheHit({ row }) {
818
+ const hp = cacheHitPct(row);
819
+ return hp === null ? "" : `${cacheHCol(hp)}⚡${hp}%${RESET$1}`;
820
+ }
821
+ function cacheColdin({ now, row }) {
822
+ const hp = cacheHitPct(row);
823
+ if (hp === null) return "";
824
+ if (row.warm && row.expires > now) {
825
+ const mins = trunc((row.expires - now) / 60);
826
+ return `${cacheHCol(hp)}⚡${hp}%${RESET$1} ${DIM}· cold in ${mins}m${RESET$1}`;
827
+ }
828
+ return `❄ ${DIM}cold · ${hp}%${RESET$1}`;
829
+ }
830
+ function cacheFuse({ now, row }) {
831
+ if (row.ttl === "" || row.expires === 0) return "";
832
+ const span = row.ttl === "1h" ? 3600 : 300;
833
+ let left = row.expires - now;
834
+ if (left < 0) left = 0;
835
+ if (left <= 0) return `${RED$1}❄ cold${RESET$1}`;
836
+ const full = trunc(left * 10 / span);
837
+ const rem = left * 10 % span;
838
+ let bar = left * 4 > span ? GREEN$1 : left * 25 > span * 2 ? YELLOW$1 : RED$1;
839
+ for (let i = 0; i < 10; i++) if (i < full) bar += "▰";
840
+ else if (i === full && rem * 20 > span) bar += PART[trunc(rem * 8 / span)];
841
+ else bar += `${DIM}▱`;
842
+ return `${bar}${RESET$1} ${DIM}${pad2(trunc(left / 60))}:${pad2(left % 60)}${RESET$1}`;
843
+ }
844
+ function costPlain({ row }) {
845
+ return row.cost >= .005 ? `${YELLOW$1}$${fmtFixed(row.cost, 2)}${RESET$1}` : "";
846
+ }
847
+ function costBurn(input) {
848
+ const hr = input.row.durationMs / 36e5;
849
+ const burn = hr > .02 ? fmtFixed(input.row.cost / hr, 2) : "0";
850
+ let out = costPlain(input);
851
+ if (burn !== "0.00") out += ` ${DIM}· $${burn}/hr${RESET$1}`;
852
+ return out;
853
+ }
854
+ function durationClock({ row }) {
855
+ return `${trunc(row.durationMs / 6e4)}m${pad2(trunc(row.durationMs % 6e4 / 1e3))}s`;
856
+ }
857
+ function durationHours({ row }) {
858
+ const m = trunc(row.durationMs / 6e4);
859
+ return `${trunc(m / 60)}h${pad2(m % 60)}m`;
860
+ }
861
+ function linesDiffstat({ row }) {
862
+ if (row.la <= 0 && row.lr <= 0) return "";
863
+ return `${GREEN$1}+${row.la}${RESET$1}${DIM}/${RESET$1}${RED$1}−${row.lr}${RESET$1}`;
864
+ }
865
+ function rateStrip({ now, row }) {
866
+ const segs = [];
867
+ for (const limit of row.rateRows) {
868
+ const [intpText, fracp = ""] = limit.pctText.split(".");
869
+ let f = 0;
870
+ let scale = 1;
871
+ for (const digit of fracp) {
872
+ f = f * 10 + Number(digit);
873
+ scale *= 10;
874
+ }
875
+ const intp = Number(intpText);
876
+ const value = intp * scale + f;
877
+ const col = value < 70 * scale ? GREEN$1 : value < 90 * scale ? YELLOW$1 : RED$1;
878
+ const full = trunc(value * 14 / (100 * scale));
879
+ const idx = trunc(value * 14 % (100 * scale) * 8 / (100 * scale));
880
+ let bar = col;
881
+ for (let i = 0; i < 14; i++) if (i < full) bar += "█";
882
+ else if (i === full) bar += PART[idx];
883
+ else bar += `${DIM}░`;
884
+ const lbl = fracp === "" || f * 2 < scale ? intp : f * 2 > scale ? intp + 1 : intp + intp % 2;
885
+ const s = limit.resets - now;
886
+ let dur;
887
+ if (s >= 3600) dur = `${trunc(s / 3600)}h${pad2(trunc(s % 3600 / 60))}m`;
888
+ else {
889
+ const sec = (s % 60 + 60) % 60;
890
+ dur = `${trunc((s - sec) / 60)}m${pad2(sec)}s`;
891
+ }
892
+ segs.push(`${DIM}${limit.label}${RESET$1} ${bar}${RESET$1} ${BOLD}${lbl}%${RESET$1} ${DIM}· resets ${dur}${RESET$1}`);
893
+ }
894
+ return segs.join(` ${DIM}│${RESET$1} `);
895
+ }
896
+ function emptySegment() {
897
+ return "";
898
+ }
899
+ function barFlat6(input) {
900
+ return barFlatAt(input, 6);
901
+ }
902
+ function barFlat4(input) {
903
+ return barFlatAt(input, 4);
904
+ }
905
+ function barPercent({ row }) {
906
+ return `${row.pct}%`;
907
+ }
908
+ var SEGMENTS = {
909
+ ahead: {
910
+ arrows: aheadArrows,
911
+ none: emptySegment
912
+ },
913
+ bar: {
914
+ flat4: barFlat4,
915
+ flat6: barFlat6,
916
+ flat: barFlat,
917
+ gauge: barGauge,
918
+ none: emptySegment,
919
+ percent: barPercent
920
+ },
921
+ branch: {
922
+ full: branchFull,
923
+ icon: branchIcon,
924
+ initials: branchInitials,
925
+ last: branchLast,
926
+ none: emptySegment
927
+ },
928
+ cache: {
929
+ coldin: cacheColdin,
930
+ fuse: cacheFuse,
931
+ hit: cacheHit,
932
+ none: emptySegment
933
+ },
934
+ cost: {
935
+ burn: costBurn,
936
+ none: emptySegment,
937
+ plain: costPlain
938
+ },
939
+ cwd: {
940
+ base: cwdBase,
941
+ full: cwdFull,
942
+ icon: cwdIcon,
943
+ init: cwdInit,
944
+ tail: cwdTail
945
+ },
946
+ duration: {
947
+ clock: durationClock,
948
+ hours: durationHours,
949
+ none: emptySegment
950
+ },
951
+ effort: {
952
+ dim: effortDim,
953
+ hidden: emptySegment,
954
+ plain: effortPlain
955
+ },
956
+ lines: {
957
+ diffstat: linesDiffstat,
958
+ none: emptySegment
959
+ },
960
+ model: {
961
+ block: modelBlock,
962
+ pill: modelPill,
963
+ plain: modelPlain,
964
+ zen: modelZen
965
+ },
966
+ pr: {
967
+ badge: prBadge,
968
+ none: emptySegment
969
+ },
970
+ rate: {
971
+ none: emptySegment,
972
+ strip: rateStrip
973
+ },
974
+ state: {
975
+ none: emptySegment,
976
+ pills: statePills
977
+ },
978
+ status: {
979
+ counts: statusCounts,
980
+ icons: statusIcons,
981
+ none: emptySegment
982
+ },
983
+ tokens: {
984
+ compact: tokensCompact,
985
+ full: tokensFull,
986
+ free: tokensFree,
987
+ none: emptySegment
988
+ }
989
+ };
990
+ function renderSegment(item, alt, input) {
991
+ const segment = SEGMENTS[item]?.[alt];
992
+ return segment === void 0 ? "" : segment(input);
993
+ }
994
+ function styleSeparators(alt) {
995
+ switch (alt) {
996
+ case "bare": return {
997
+ join: " ",
998
+ sep: " "
999
+ };
1000
+ case "dim": return {
1001
+ join: " ",
1002
+ sep: `${DIM} │ ${RESET$1}`
1003
+ };
1004
+ case "dots": return {
1005
+ join: " · ",
1006
+ sep: " · "
1007
+ };
1008
+ default: return {
1009
+ join: " ",
1010
+ sep: " │ "
1011
+ };
1012
+ }
1013
+ }
1014
+ //#endregion
1015
+ //#region src/render/engine.ts
1016
+ var RUNG_ITEMS = Object.keys(RUNG_ORDERS);
1017
+ function stripSgr(text) {
1018
+ return text.replace(/\x1b\[[0-9;]*m/g, "");
1019
+ }
1020
+ function vlen$1(text) {
1021
+ const plain = stripSgr(text);
1022
+ let n = Array.from(plain).length;
1023
+ for (const ch of plain) if (ch === "⚡") n += 1;
1024
+ return n;
1025
+ }
1026
+ function warn(message) {
1027
+ process.stderr.write(`${message}\n`);
1028
+ }
1029
+ function stripModelSuffix(name) {
1030
+ const at = name.lastIndexOf("[");
1031
+ const cut = at === -1 ? name : name.slice(0, at);
1032
+ return cut.endsWith(" ") ? cut.slice(0, -1) : cut;
1033
+ }
1034
+ function resolvePicks(picks) {
1035
+ const resolved = {};
1036
+ for (const spec of ITEMS) {
1037
+ const pick = picks?.[spec.item] ?? spec.default;
1038
+ if (spec.alternatives.includes(pick)) resolved[spec.item] = pick;
1039
+ else {
1040
+ warn(`statusline: ${spec.item}=${pick} is not available, using ${spec.item}=${spec.default}`);
1041
+ resolved[spec.item] = spec.default;
1042
+ }
1043
+ }
1044
+ return resolved;
1045
+ }
1046
+ function parseLayout(layout, known) {
1047
+ let parsed;
1048
+ try {
1049
+ parsed = parseClusters(layout);
1050
+ } catch (e) {
1051
+ warn(`statusline: ${e.message}`);
1052
+ return [];
1053
+ }
1054
+ const clusters = [];
1055
+ for (const words of parsed) {
1056
+ const kept = words.filter((word) => {
1057
+ if (known.has(word)) return true;
1058
+ warn(`statusline: layout item '${word}' is not available, skipped`);
1059
+ return false;
1060
+ });
1061
+ if (kept.length > 0) clusters.push(kept);
1062
+ }
1063
+ return clusters;
1064
+ }
1065
+ function renderStatusline(input) {
1066
+ const row = parseRow(input.payload);
1067
+ const git = readGit(row.dir, { home: input.home });
1068
+ const picks = resolvePicks(input.picks);
1069
+ const { join, sep } = styleSeparators(picks.style);
1070
+ 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)));
1071
+ const wrapAt = clusters.length > 2 ? 2 : 1;
1072
+ let width = input.columns;
1073
+ if (width === void 0 || !Number.isInteger(width) || width < 0) width = 200;
1074
+ if (width < 20) width = 20;
1075
+ const avail = width - 3;
1076
+ let model = row.model;
1077
+ const initialRungs = RUNG_ITEMS.map((item) => [item, picks[item]]);
1078
+ function segmentInput() {
1079
+ return {
1080
+ git,
1081
+ home: input.home,
1082
+ model,
1083
+ now: input.now,
1084
+ row
1085
+ };
1086
+ }
1087
+ function compose(mode) {
1088
+ const from = mode === "l2" ? wrapAt : 0;
1089
+ const to = mode === "l1" ? wrapAt : clusters.length;
1090
+ let line = "";
1091
+ let first = true;
1092
+ for (let ci = from; ci < to; ci++) {
1093
+ let cseg = "";
1094
+ let cf = true;
1095
+ for (const item of clusters[ci]) {
1096
+ const out = renderSegment(item, picks[item], segmentInput());
1097
+ if (out === "") continue;
1098
+ if (cf) {
1099
+ cseg = out;
1100
+ cf = false;
1101
+ } else cseg += join + out;
1102
+ }
1103
+ if (cseg === "") continue;
1104
+ if (first) {
1105
+ line = cseg;
1106
+ first = false;
1107
+ } else line += sep + cseg;
1108
+ }
1109
+ return line;
1110
+ }
1111
+ function fits(line) {
1112
+ return vlen$1(line) <= avail;
1113
+ }
1114
+ function demote(item, target) {
1115
+ const order = RUNG_ORDERS[item];
1116
+ if (order === void 0) return;
1117
+ const at = order.indexOf(picks[item]);
1118
+ if (at === -1) return;
1119
+ if (order.slice(at + 1).includes(target)) picks[item] = target;
1120
+ }
1121
+ function resetRungs() {
1122
+ model = row.model;
1123
+ for (const [item, pick] of initialRungs) picks[item] = pick;
1124
+ }
1125
+ function applyStep(step) {
1126
+ if (step[0] === "model" && step[1] === "strip") model = stripModelSuffix(model);
1127
+ else demote(step[0], step[1]);
1128
+ }
1129
+ function fit(mode, steps) {
1130
+ let out = compose(mode);
1131
+ if (fits(out)) return out;
1132
+ for (const step of steps) {
1133
+ applyStep(step);
1134
+ out = compose(mode);
1135
+ if (fits(out)) return out;
1136
+ }
1137
+ return out;
1138
+ }
1139
+ function emit(line) {
1140
+ return `${input.noColor === true ? stripSgr(line) : line}\n`;
1141
+ }
1142
+ const full = fit("full", FULL_STEPS);
1143
+ if (fits(full)) return emit(full);
1144
+ resetRungs();
1145
+ const l1 = fit("l1", L1_STEPS);
1146
+ const l2 = fit("l2", L2_STEPS);
1147
+ return emit(l1) + emit(l2);
1148
+ }
1149
+ //#endregion
1150
+ //#region src/render/panel.ts
1151
+ var CYAN = "\x1B[36m";
1152
+ var GREEN = "\x1B[32m";
1153
+ var RED = "\x1B[31m";
1154
+ var RESET = "\x1B[0m";
1155
+ var YELLOW = "\x1B[33m";
1156
+ var MS_THRESHOLD = 2e11;
1157
+ var STEPS = [
1158
+ ["descd", 1],
1159
+ ["durd", 1],
1160
+ ["statd", 1],
1161
+ ["barb", 6],
1162
+ ["modeld", 1],
1163
+ ["barb", 4],
1164
+ ["barb", 0],
1165
+ ["modeld", 2],
1166
+ ["statd", 2]
1167
+ ];
1168
+ function field(source, key, fallback) {
1169
+ return orElse(source[key], fallback);
1170
+ }
1171
+ function cell(value) {
1172
+ return tsvEscape(jqText(value));
1173
+ }
1174
+ function truncateDesc(desc) {
1175
+ const chars = Array.from(desc);
1176
+ return chars.length > 24 ? `${chars.slice(0, 23).join("")}…` : desc;
1177
+ }
1178
+ function intValue(text) {
1179
+ return /^-?\d+$/.test(text) ? Number(text) : null;
1180
+ }
1181
+ function stylePick(picks) {
1182
+ const spec = specFor("style");
1183
+ if (spec === void 0) return "plain";
1184
+ const wanted = picks?.style ?? "";
1185
+ const alt = wanted === "" ? spec.default : wanted;
1186
+ if (!spec.alternatives.includes(alt)) {
1187
+ warn(`statusline: style=${alt} is not available, using style=${spec.default}`);
1188
+ return spec.default;
1189
+ }
1190
+ return alt;
1191
+ }
1192
+ function availColumns(tick) {
1193
+ const text = jqText(isRecord(tick) ? field(tick, "columns", 200) : 200);
1194
+ const columns = /^\d+$/.test(text) ? Number(text) : 200;
1195
+ return Math.max(columns, 20) - 1;
1196
+ }
1197
+ function extractFields(task) {
1198
+ return {
1199
+ ctx: cell(field(task, "contextWindowSize", 0)),
1200
+ desc: cell(truncateDesc(jqText(field(task, "description", "")))),
1201
+ effort: cell(field(task, "effort", "")),
1202
+ id: cell(field(task, "id", "")),
1203
+ label: cell(field(task, "label", "")),
1204
+ model: cell(field(task, "model", "")),
1205
+ name: cell(field(task, "name", "")),
1206
+ start: cell(field(task, "startTime", 0)),
1207
+ tokens: cell(field(task, "tokenCount", 0))
1208
+ };
1209
+ }
1210
+ function makeBar(pct, width) {
1211
+ let f = Math.trunc(pct * width / 100);
1212
+ if (f > width) f = width;
1213
+ let bar = "";
1214
+ for (let i = 0; i < width; i++) bar += i < f ? "█" : "░";
1215
+ return bar;
1216
+ }
1217
+ function duration(startText, now) {
1218
+ const start = intValue(startText);
1219
+ if (start === null || start <= 0) return "";
1220
+ const seconds = start > MS_THRESHOLD ? Math.trunc(start / 1e3) : start;
1221
+ const elapsed = Math.trunc(now) - seconds;
1222
+ if (elapsed < 0) return "";
1223
+ return `${Math.trunc(elapsed / 60)}m${pad2(Math.trunc(elapsed % 60))}s`;
1224
+ }
1225
+ function rowFormats(fields, now) {
1226
+ const tokens = intValue(fields.tokens) ?? 0;
1227
+ const ctx = intValue(fields.ctx) ?? 0;
1228
+ let ctxText = fields.ctx;
1229
+ if (ctx >= 1e6) ctxText = fmtM(ctx);
1230
+ else if (ctx >= 1e3) ctxText = fmtK(ctx, 0);
1231
+ return {
1232
+ ctx: ctxText,
1233
+ dur: duration(fields.start, now),
1234
+ tickTok: tokens >= 1e3 ? fmtK(tokens, 0) : String(tokens),
1235
+ tokens: tokens >= 1e3 ? fmtK(tokens, 1) : String(tokens)
1236
+ };
1237
+ }
1238
+ function renderRow(fields, formats, state, sep) {
1239
+ let s = fields.label === "" ? fields.name : fields.label;
1240
+ if (fields.desc !== "" && fields.desc !== fields.label && state.descd === 0) s += ` ${fields.desc}`;
1241
+ if (fields.model !== "") {
1242
+ let name = fields.model;
1243
+ if (state.modeld >= 1) name = stripModelSuffix(name);
1244
+ const effort = state.modeld >= 2 ? "" : fields.effort;
1245
+ if (effort !== "") name += ` ${effort}`;
1246
+ s += `${sep}${CYAN}${name}${RESET}`;
1247
+ }
1248
+ const ctx = intValue(fields.ctx);
1249
+ if (ctx !== null && ctx > 0) {
1250
+ const tokens = intValue(fields.tokens) ?? 0;
1251
+ const pct = Math.trunc(tokens * 100 / ctx);
1252
+ const barColor = pct >= 90 ? RED : pct >= 70 ? YELLOW : GREEN;
1253
+ if (state.barb > 0) s += `${sep}${barColor}${makeBar(pct, state.barb)}${RESET} ${pct}%`;
1254
+ else s += `${sep}${barColor}${pct}%${RESET}`;
1255
+ const stats = state.statd === 1 ? formats.tickTok : state.statd === 2 ? "" : `${formats.tokens}/${formats.ctx}`;
1256
+ if (stats !== "") s += ` ${stats}`;
1257
+ }
1258
+ if (formats.dur !== "" && state.durd === 0) s += `${sep}${formats.dur}`;
1259
+ return s;
1260
+ }
1261
+ function vlen(text) {
1262
+ return Array.from(stripSgr(text)).length;
1263
+ }
1264
+ function fitRow(fields, formats, sep, avail) {
1265
+ const state = {
1266
+ barb: 10,
1267
+ descd: 0,
1268
+ durd: 0,
1269
+ modeld: 0,
1270
+ statd: 0
1271
+ };
1272
+ let out = renderRow(fields, formats, state, sep);
1273
+ if (vlen(out) <= avail) return out;
1274
+ for (const [key, value] of STEPS) {
1275
+ state[key] = value;
1276
+ out = renderRow(fields, formats, state, sep);
1277
+ if (vlen(out) <= avail) return out;
1278
+ }
1279
+ return out;
1280
+ }
1281
+ function emitLine(id, content) {
1282
+ return `{"id":${JSON.stringify(id)},"content":${JSON.stringify(content)}}`;
1283
+ }
1284
+ function renderPanel(input) {
1285
+ const tick = JSON.parse(input.payload);
1286
+ const avail = availColumns(tick);
1287
+ const sep = styleSeparators(stylePick(input.picks)).sep;
1288
+ const tasks = isRecord(tick) && Array.isArray(tick.tasks) ? tick.tasks : [];
1289
+ let out = "";
1290
+ for (const task of tasks) {
1291
+ if (!isRecord(task)) continue;
1292
+ const fields = extractFields(task);
1293
+ if (fields.id === "") continue;
1294
+ const row = fitRow(fields, rowFormats(fields, input.now), sep, avail);
1295
+ const content = input.noColor === true ? stripSgr(row) : row;
1296
+ out += `${emitLine(fields.id, content)}\n`;
1297
+ }
1298
+ return out;
1299
+ }
1300
+ //#endregion
1301
+ //#region src/themes.ts
1302
+ var THEMES = {
1303
+ quiet: {
1304
+ layout: "{model cwd}",
1305
+ summary: "model and directory, nothing else",
1306
+ variants: {
1307
+ cwd: "tail",
1308
+ model: "zen",
1309
+ style: "bare"
1310
+ }
1311
+ },
1312
+ classic: {
1313
+ layout: DEFAULT_LAYOUT,
1314
+ summary: "the shipped defaults, named",
1315
+ variants: {}
1316
+ },
1317
+ lean: {
1318
+ layout: DEFAULT_LAYOUT,
1319
+ summary: "text only, no graphics",
1320
+ variants: {
1321
+ model: "plain",
1322
+ effort: "dim",
1323
+ state: "none",
1324
+ cwd: "init",
1325
+ branch: "initials",
1326
+ status: "counts",
1327
+ ahead: "arrows",
1328
+ pr: "badge",
1329
+ bar: "percent",
1330
+ tokens: "full",
1331
+ cache: "hit",
1332
+ cost: "plain",
1333
+ duration: "clock",
1334
+ lines: "diffstat",
1335
+ rate: "none",
1336
+ style: "dots"
1337
+ }
1338
+ },
1339
+ rich: {
1340
+ layout: DEFAULT_LAYOUT,
1341
+ summary: "every gauge and counter",
1342
+ variants: {
1343
+ model: "pill",
1344
+ effort: "plain",
1345
+ state: "pills",
1346
+ cwd: "icon",
1347
+ branch: "icon",
1348
+ status: "icons",
1349
+ ahead: "arrows",
1350
+ pr: "badge",
1351
+ bar: "gauge",
1352
+ tokens: "full",
1353
+ cache: "fuse",
1354
+ cost: "burn",
1355
+ duration: "clock",
1356
+ lines: "diffstat",
1357
+ rate: "strip",
1358
+ style: "plain"
1359
+ }
1360
+ },
1361
+ custom: {
1362
+ layout: DEFAULT_LAYOUT,
1363
+ summary: "bare; you decide everything",
1364
+ variants: {
1365
+ model: "zen",
1366
+ effort: "hidden",
1367
+ state: "none",
1368
+ cwd: "base",
1369
+ branch: "none",
1370
+ status: "none",
1371
+ ahead: "none",
1372
+ pr: "none",
1373
+ bar: "none",
1374
+ tokens: "none",
1375
+ cache: "none",
1376
+ cost: "none",
1377
+ duration: "none",
1378
+ lines: "none",
1379
+ rate: "none",
1380
+ style: "bare"
1381
+ }
1382
+ }
1383
+ };
1384
+ //#endregion
1385
+ //#region src/render/theme.ts
1386
+ function resolvePaint(input) {
1387
+ const name = input.theme;
1388
+ const theme = name === void 0 ? void 0 : THEMES[name];
1389
+ if (name !== void 0 && theme === void 0) warn(`statusline: theme=${name} is not a known theme, ignored`);
1390
+ return {
1391
+ layout: input.layout ?? theme?.layout ?? "{model effort state} {cwd branch status ahead pr} {bar tokens cache} {cost} {duration} {lines} {rate}",
1392
+ picks: {
1393
+ ...theme?.variants ?? {},
1394
+ ...input.picks
1395
+ }
1396
+ };
1397
+ }
1398
+ //#endregion
1399
+ //#region src/render/entry.ts
1400
+ function readStdin() {
1401
+ return new Promise((resolve, reject) => {
1402
+ let payload = "";
1403
+ process.stdin.setEncoding("utf8");
1404
+ process.stdin.on("data", (chunk) => {
1405
+ payload += chunk;
1406
+ });
1407
+ process.stdin.on("end", () => {
1408
+ resolve(payload);
1409
+ });
1410
+ process.stdin.on("error", reject);
1411
+ });
1412
+ }
1413
+ function columnsFromEnv(text) {
1414
+ if (text === void 0 || text === "" || !/^\d+$/.test(text)) return;
1415
+ return Number(text);
1416
+ }
1417
+ async function main() {
1418
+ const argv = parseArgv(process.argv.slice(2));
1419
+ for (const warning of argv.warnings) process.stderr.write(`${warning}\n`);
1420
+ const payload = await readStdin();
1421
+ const env = process.env;
1422
+ const home = env.HOME ?? "";
1423
+ const noColor = (env.NO_COLOR ?? "") !== "";
1424
+ const now = argv.now ?? Math.floor(Date.now() / 1e3);
1425
+ const paint = resolvePaint(argv);
1426
+ if (argv.mode === "panel") {
1427
+ capturePayload(home, "tick", payload);
1428
+ process.stdout.write(renderPanel({
1429
+ now,
1430
+ payload,
1431
+ picks: paint.picks,
1432
+ noColor
1433
+ }));
1434
+ return;
1435
+ }
1436
+ capturePayload(home, "main", payload);
1437
+ const columns = columnsFromEnv(env.COLUMNS);
1438
+ process.stdout.write(renderStatusline({
1439
+ home,
1440
+ now,
1441
+ payload,
1442
+ picks: paint.picks,
1443
+ noColor,
1444
+ layout: paint.layout,
1445
+ ...columns === void 0 ? {} : { columns }
1446
+ }));
1447
+ }
1448
+ await main();
1449
+ //#endregion
1450
+ export {};
1451
+
1452
+ //# sourceMappingURL=render.mjs.map