@vortex-os/base 0.9.0 → 0.11.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 CHANGED
@@ -58,9 +58,28 @@ npx vortex --list # list available commands
58
58
  npx vortex status # instance state report
59
59
  npx vortex import --from X # bring an existing notes folder into data/
60
60
  npx vortex session-start # start-of-session boot report (git pull + counts + catch-up)
61
- npx vortex session-end # worklog safety net
61
+ npx vortex session-end # no-op (kept for hook compatibility; gap handling is at session-start)
62
62
  npx vortex doctor # health diagnosis
63
63
  npx vortex update # refresh framework templates (never clobbers your edits)
64
+ npx vortex statusline # Claude Code status-bar renderer (see below)
65
+ ```
66
+
67
+ ## Claude Code status bar (optional)
68
+
69
+ `vortex statusline` renders a colored status bar for Claude Code's `statusLine` setting — model + a reasoning-effort meter (one bar whose height/color encode `low`→`ultracode`), a context-window gauge with used/total tokens, a clock, session cost, the 5-hour/7-day rate-limit gauges with time-to-reset, cache hit-rate, project + git branch/commit, session duration, lines changed, the number of open Claude Code sessions (best-effort process count) — and, inside a VortEX instance, the framework version and the latest worklog file:
70
+
71
+ ```
72
+ 🧠 Fable 5 │ █ max │ █░░░░░░░░░ 12% · 120K/1M │ 🕐 22:25 │ 💰 $9.22
73
+ 5h ██████░░ 75%(1h9m) │ 7d ███████░ 96%(7h30m) │ 📦 cache 99%
74
+ 📁 my-project │ ⎇ main 2b0949d │ ⏱ 1h26m │ +90 -49 │ ⧉ 1
75
+ 🌀 VortEX v0.11.0 │ last: 2026-06-10_0452-notes.md
76
+ ```
77
+
78
+ Wire it up once — this writes the `statusLine` entry into the **current folder's** `.claude/settings.json` and **keeps any bar you already configured** (replace it explicitly with `--force`):
79
+
80
+ ```bash
81
+ npx vortex statusline install # full bar (4 lines inside an instance, 3 outside)
82
+ npx vortex statusline install --lite # compact 1-line bar
64
83
  ```
65
84
 
66
85
  ## Library usage
@@ -0,0 +1,470 @@
1
+ // ../plugins/session-rituals/dist/statusline.js
2
+ import { execFileSync } from "child_process";
3
+ import { closeSync, existsSync, openSync, readSync, readdirSync, readFileSync, statSync } from "fs";
4
+ import { join } from "path";
5
+
6
+ // ../plugins/session-rituals/dist/ensure-hooks.js
7
+ var SESSION_START_COMMAND = "npx --no-install vortex session-start || exit 0";
8
+ var SESSION_END_COMMAND = "npx --no-install vortex session-end || exit 0";
9
+ var LEGACY_COMMANDS = {
10
+ SessionStart: [
11
+ "npx --no-install -p @vortex-os/base vortex session-start || exit 0",
12
+ "npx --no-install -p @vortex-os/base vortex session-start"
13
+ ],
14
+ SessionEnd: [
15
+ "npx --no-install -p @vortex-os/base vortex session-end || exit 0",
16
+ "npx --no-install -p @vortex-os/base vortex session-end"
17
+ ]
18
+ };
19
+ function parseSettings(text) {
20
+ const trimmed = (text ?? "").trim();
21
+ if (trimmed.length === 0)
22
+ return {};
23
+ let parsed;
24
+ try {
25
+ parsed = JSON.parse(trimmed);
26
+ } catch (e) {
27
+ throw new Error(`.claude/settings.json is not valid JSON \u2014 refusing to overwrite. Fix or remove it first. (${e.message})`);
28
+ }
29
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
30
+ throw new Error(".claude/settings.json is not a JSON object \u2014 refusing to overwrite.");
31
+ }
32
+ return parsed;
33
+ }
34
+ function ensureVortexHooks(existing) {
35
+ const base = existing && typeof existing === "object" ? existing : {};
36
+ const hooks = { ...base.hooks ?? {} };
37
+ const added = [];
38
+ const wire = (event, command) => {
39
+ const legacy = LEGACY_COMMANDS[event];
40
+ const src = hooks[event] ?? [];
41
+ let changed = false;
42
+ let kept = false;
43
+ const groups = [];
44
+ for (const g of src) {
45
+ const hookList = [];
46
+ for (const h of g.hooks ?? []) {
47
+ const migrated = legacy.includes(h.command);
48
+ const cmd = migrated ? command : h.command;
49
+ if (cmd === command) {
50
+ if (kept) {
51
+ changed = true;
52
+ continue;
53
+ }
54
+ kept = true;
55
+ if (migrated)
56
+ changed = true;
57
+ hookList.push(migrated ? { ...h, command } : h);
58
+ } else {
59
+ hookList.push(h);
60
+ }
61
+ }
62
+ if (hookList.length > 0)
63
+ groups.push({ ...g, hooks: hookList });
64
+ else
65
+ changed = true;
66
+ }
67
+ if (!kept) {
68
+ groups.push({ hooks: [{ type: "command", command }] });
69
+ changed = true;
70
+ }
71
+ hooks[event] = groups;
72
+ if (changed)
73
+ added.push(event);
74
+ };
75
+ wire("SessionStart", SESSION_START_COMMAND);
76
+ wire("SessionEnd", SESSION_END_COMMAND);
77
+ const settings = { ...base, hooks };
78
+ return { settings, added, alreadyWired: added.length === 0 };
79
+ }
80
+ function serializeSettings(settings) {
81
+ return JSON.stringify(settings, null, 2) + "\n";
82
+ }
83
+
84
+ // ../plugins/session-rituals/dist/statusline.js
85
+ var RST = "\x1B[0m";
86
+ var CYAN = "\x1B[36m";
87
+ var GREEN = "\x1B[32m";
88
+ var YELLOW = "\x1B[33m";
89
+ var RED = "\x1B[31m";
90
+ var DIM = "\x1B[2m";
91
+ var BOLD = "\x1B[1m";
92
+ var WHITE = "\x1B[37m";
93
+ var MAGENTA = "\x1B[35m";
94
+ var BLUE = "\x1B[34m";
95
+ var GREY = "\x1B[38;5;242m";
96
+ var ORANGE = "\x1B[38;5;208m";
97
+ var SEP = ` ${DIM}\u2502${RST} `;
98
+ function num(v, fallback = 0) {
99
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
100
+ }
101
+ function nonneg(v, fallback = 0) {
102
+ return Math.max(0, num(v, fallback));
103
+ }
104
+ function clampPct(v) {
105
+ return Math.min(100, Math.max(0, v));
106
+ }
107
+ function safeSegment(s, max = 60) {
108
+ const cleaned = s.replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim();
109
+ return cleaned.length > max ? cleaned.slice(0, max - 1) + "\u2026" : cleaned;
110
+ }
111
+ function str(v) {
112
+ return typeof v === "string" && v.length > 0 ? v : null;
113
+ }
114
+ function obj(v) {
115
+ return typeof v === "object" && v !== null ? v : {};
116
+ }
117
+ function parseStatuslineInput(text) {
118
+ const root = obj(JSON.parse(text));
119
+ const model = obj(root.model);
120
+ const effort = obj(root.effort);
121
+ const workspace = obj(root.workspace);
122
+ const cost = obj(root.cost);
123
+ const ctx = obj(root.context_window);
124
+ const usage = obj(ctx.current_usage);
125
+ const limits = obj(root.rate_limits);
126
+ const five = obj(limits.five_hour);
127
+ const seven = obj(limits.seven_day);
128
+ const rawName = str(model.display_name) ?? "Claude";
129
+ const windowSize = nonneg(ctx.context_window_size, 2e5);
130
+ return {
131
+ modelName: rawName.replace(/^Claude\s+/, ""),
132
+ effortLevel: str(effort.level),
133
+ transcriptPath: str(root.transcript_path),
134
+ dir: str(workspace.current_dir) ?? str(root.cwd),
135
+ contextWindowSize: windowSize > 0 ? windowSize : 2e5,
136
+ usedPercentage: clampPct(num(ctx.used_percentage)),
137
+ cacheReadTokens: nonneg(usage.cache_read_input_tokens),
138
+ cacheCreationTokens: nonneg(usage.cache_creation_input_tokens),
139
+ costUsd: nonneg(cost.total_cost_usd),
140
+ durationMs: nonneg(cost.total_duration_ms),
141
+ linesAdded: nonneg(cost.total_lines_added),
142
+ linesRemoved: nonneg(cost.total_lines_removed),
143
+ fiveHourUsedPct: clampPct(num(five.used_percentage)),
144
+ fiveHourResetsAt: nonneg(five.resets_at),
145
+ sevenDayUsedPct: clampPct(num(seven.used_percentage)),
146
+ sevenDayResetsAt: nonneg(seven.resets_at)
147
+ };
148
+ }
149
+ function effortMeter(level) {
150
+ switch (level) {
151
+ case "low":
152
+ return { meter: "\u2582", color: GREY };
153
+ case "medium":
154
+ return { meter: "\u2584", color: GREEN };
155
+ case "high":
156
+ return { meter: "\u2586", color: YELLOW };
157
+ case "xhigh":
158
+ return { meter: "\u2587", color: ORANGE };
159
+ case "max":
160
+ return { meter: "\u2588", color: RED };
161
+ case "ultracode":
162
+ return { meter: "\u2588\u2726", color: MAGENTA };
163
+ default:
164
+ return null;
165
+ }
166
+ }
167
+ function formatTokens(t) {
168
+ if (t >= 1e6)
169
+ return `${Math.floor(t / 1e6)}.${Math.floor(t % 1e6 / 1e5)}M`;
170
+ if (t >= 1e3)
171
+ return `${Math.floor(t / 1e3)}K`;
172
+ return String(Math.floor(t));
173
+ }
174
+ function formatWindow(t) {
175
+ if (t >= 1e6)
176
+ return `${Math.floor(t / 1e6)}M`;
177
+ if (t >= 1e3)
178
+ return `${Math.floor(t / 1e3)}K`;
179
+ return String(Math.floor(t));
180
+ }
181
+ function makeBar(pct, width) {
182
+ const filled = Math.min(width, Math.max(0, Math.floor(pct * width / 100)));
183
+ return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
184
+ }
185
+ function usageColor(pct) {
186
+ if (pct >= 80)
187
+ return RED;
188
+ if (pct >= 50)
189
+ return YELLOW;
190
+ return GREEN;
191
+ }
192
+ function healthColor(remaining) {
193
+ if (remaining <= 30)
194
+ return RED;
195
+ if (remaining <= 60)
196
+ return YELLOW;
197
+ return GREEN;
198
+ }
199
+ function formatDuration(ms) {
200
+ const totalMin = Math.floor(ms / 6e4);
201
+ if (totalMin >= 60)
202
+ return `${Math.floor(totalMin / 60)}h${totalMin % 60}m`;
203
+ return `${totalMin}m${Math.floor(ms / 1e3) % 60}s`;
204
+ }
205
+ function untilReset(resetsAtSec, now) {
206
+ if (resetsAtSec <= 0)
207
+ return "";
208
+ const diffSec = resetsAtSec - Math.floor(now.getTime() / 1e3);
209
+ if (diffSec <= 0)
210
+ return "";
211
+ if (diffSec >= 86400)
212
+ return `${Math.floor(diffSec / 86400)}d${Math.floor(diffSec % 86400 / 3600)}h`;
213
+ return `${Math.floor(diffSec / 3600)}h${Math.floor(diffSec % 3600 / 60)}m`;
214
+ }
215
+ function pad2(n) {
216
+ return String(n).padStart(2, "0");
217
+ }
218
+ function renderStatusline(d, p, mode = "full") {
219
+ const usedPctDisplay = Math.round(d.usedPercentage);
220
+ const ctxColor = usageColor(usedPctDisplay);
221
+ const ctxUsedTokens = Math.round(d.usedPercentage * d.contextWindowSize / 100);
222
+ const ctxText = `${WHITE}${formatTokens(ctxUsedTokens)}/${formatWindow(d.contextWindowSize)}${RST}`;
223
+ const clock = `\u{1F550} ${WHITE}${pad2(p.now.getHours())}:${pad2(p.now.getMinutes())}${RST}`;
224
+ const level = p.effortLevel ?? d.effortLevel;
225
+ const effort = effortMeter(level);
226
+ const effortSeg = effort ? `${effort.color}${effort.meter}${RST} ${GREY}${safeSegment(level ?? "")}${RST}` : null;
227
+ const modelSeg = `\u{1F9E0} ${BOLD}${CYAN}${safeSegment(d.modelName, 30)}${RST}`;
228
+ const fiveRemain = Math.max(0, 100 - Math.round(d.fiveHourUsedPct));
229
+ const sevenRemain = Math.max(0, 100 - Math.round(d.sevenDayUsedPct));
230
+ const fiveColor = healthColor(fiveRemain);
231
+ const sevenColor = healthColor(sevenRemain);
232
+ const sessionSeg = `${WHITE}\u29C9 ${Math.max(1, Math.floor(p.sessionCount))}${RST}`;
233
+ const project = safeSegment(p.vortex || d.dir ? (d.dir ?? "").replace(/[\\/]+$/, "").split(/[\\/]/).pop() || "?" : "?", 40);
234
+ const gitBranch = safeSegment(p.gitBranch, 40);
235
+ const gitHash = safeSegment(p.gitHash, 16);
236
+ if (mode === "lite") {
237
+ const parts = [
238
+ `\u{1F4C1} ${BLUE}${project}${RST}`,
239
+ `${WHITE}\u2387${RST} ${YELLOW}${gitBranch}${RST}`,
240
+ effortSeg ? `${modelSeg}${SEP}${effortSeg}` : modelSeg,
241
+ `${ctxColor}${makeBar(usedPctDisplay, 8)} ${usedPctDisplay}%${RST} ${DIM}\xB7${RST} ${ctxText}`,
242
+ `${GREY}5h${RST} ${fiveColor}${fiveRemain}%${RST} ${DIM}\xB7${RST} ${GREY}7d${RST} ${sevenColor}${sevenRemain}%${RST}`,
243
+ sessionSeg,
244
+ ...p.vortex?.version ? [`\u{1F300} ${MAGENTA}v${safeSegment(p.vortex.version, 20)}${RST}`] : [],
245
+ clock
246
+ ];
247
+ return parts.join(SEP);
248
+ }
249
+ const l1 = [
250
+ effortSeg ? `${modelSeg}${SEP}${effortSeg}` : modelSeg,
251
+ `${ctxColor}${makeBar(usedPctDisplay, 10)} ${usedPctDisplay}%${RST} ${DIM}\xB7${RST} ${ctxText}`,
252
+ clock,
253
+ `\u{1F4B0} ${BOLD}${YELLOW}$${d.costUsd.toFixed(2)}${RST}`
254
+ ].join(SEP);
255
+ const cacheTotal = d.cacheReadTokens + d.cacheCreationTokens;
256
+ const cachePct = cacheTotal > 0 ? Math.floor(d.cacheReadTokens * 100 / cacheTotal) : 0;
257
+ const fiveReset = untilReset(d.fiveHourResetsAt, p.now);
258
+ const sevenReset = untilReset(d.sevenDayResetsAt, p.now);
259
+ const l2 = [
260
+ `${GREY}5h${RST} ${fiveColor}${makeBar(fiveRemain, 8)} ${fiveRemain}%${RST}` + (fiveReset ? `${GREY}(${fiveReset})${RST}` : ""),
261
+ `${GREY}7d${RST} ${sevenColor}${makeBar(sevenRemain, 8)} ${sevenRemain}%${RST}` + (sevenReset ? `${GREY}(${sevenReset})${RST}` : ""),
262
+ `\u{1F4E6} ${GREEN}cache ${cachePct}%${RST}`
263
+ ].join(SEP);
264
+ const l3 = [
265
+ `\u{1F4C1} ${BLUE}${project}${RST}`,
266
+ `${WHITE}\u2387${RST} ${YELLOW}${gitBranch}${RST} ${GREY}${gitHash}${RST}`,
267
+ `\u23F1 ${MAGENTA}${formatDuration(d.durationMs)}${RST}`,
268
+ `${GREEN}+${d.linesAdded}${RST} ${RED}-${d.linesRemoved}${RST}`,
269
+ sessionSeg
270
+ ].join(SEP);
271
+ const lines = [l1, l2, l3];
272
+ if (p.vortex) {
273
+ let vx = `\u{1F300} ${BOLD}${MAGENTA}VortEX${RST}`;
274
+ if (p.vortex.version)
275
+ vx += ` ${GREY}v${safeSegment(p.vortex.version, 20)}${RST}`;
276
+ if (p.vortex.lastWorklog)
277
+ vx += `${SEP}${GREY}last:${RST} ${safeSegment(p.vortex.lastWorklog, 70)}`;
278
+ lines.push(vx);
279
+ }
280
+ return lines.join("\n");
281
+ }
282
+ function gitOut(dir, args) {
283
+ return execFileSync("git", ["-C", dir, ...args], {
284
+ encoding: "utf8",
285
+ stdio: ["ignore", "pipe", "ignore"]
286
+ }).trim();
287
+ }
288
+ function sniffEffortFromTranscript(transcriptPath, maxBytes = 4e6) {
289
+ try {
290
+ const size = statSync(transcriptPath).size;
291
+ const start = Math.max(0, size - maxBytes);
292
+ const length = size - start;
293
+ if (length <= 0)
294
+ return null;
295
+ const buf = Buffer.alloc(length);
296
+ const fd = openSync(transcriptPath, "r");
297
+ try {
298
+ readSync(fd, buf, 0, length, start);
299
+ } finally {
300
+ closeSync(fd);
301
+ }
302
+ const text = buf.toString("utf8");
303
+ const matches = text.match(/Set effort level to [a-z]+/g);
304
+ if (!matches || matches.length === 0)
305
+ return null;
306
+ return matches[matches.length - 1].slice("Set effort level to ".length);
307
+ } catch {
308
+ return null;
309
+ }
310
+ }
311
+ function countClaudeSessions() {
312
+ try {
313
+ if (process.platform === "win32") {
314
+ const out2 = execFileSync("tasklist", [], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
315
+ const n2 = (out2.match(/claude\.exe/gi) ?? []).length;
316
+ return n2 > 0 ? n2 : 1;
317
+ }
318
+ const out = execFileSync("pgrep", ["-x", "claude"], {
319
+ encoding: "utf8",
320
+ stdio: ["ignore", "pipe", "ignore"]
321
+ });
322
+ const n = out.split(/\r?\n/).filter(Boolean).length;
323
+ return n > 0 ? n : 1;
324
+ } catch {
325
+ return 1;
326
+ }
327
+ }
328
+ function newestWorklogName(repoRoot) {
329
+ try {
330
+ const dir = join(repoRoot, "data", "worklog");
331
+ const entries = readdirSync(dir, { recursive: true });
332
+ let best = null;
333
+ for (const rel of entries) {
334
+ const s = String(rel);
335
+ if (!s.endsWith(".md") || s.endsWith("_INDEX.md"))
336
+ continue;
337
+ if (best === null || s > best)
338
+ best = s;
339
+ }
340
+ return best ? best.replace(/\\/g, "/").split("/").pop() : null;
341
+ } catch {
342
+ return null;
343
+ }
344
+ }
345
+ function collectStatuslineProbes(d, now = /* @__PURE__ */ new Date()) {
346
+ const dir = d.dir ?? process.cwd();
347
+ let gitBranch = "-";
348
+ let gitHash = "-";
349
+ try {
350
+ const branch = gitOut(dir, ["rev-parse", "--abbrev-ref", "HEAD"]);
351
+ gitBranch = branch === "HEAD" ? "detached" : branch || "-";
352
+ gitHash = gitOut(dir, ["rev-parse", "--short", "HEAD"]) || "-";
353
+ } catch {
354
+ }
355
+ let vortex = null;
356
+ try {
357
+ if (existsSync(join(dir, ".agent", "vortex.json"))) {
358
+ let version = null;
359
+ try {
360
+ const pkg = JSON.parse(readFileSync(join(dir, "node_modules", "@vortex-os", "base", "package.json"), "utf8"));
361
+ version = typeof pkg.version === "string" ? pkg.version : null;
362
+ } catch {
363
+ }
364
+ vortex = { version, lastWorklog: newestWorklogName(dir) };
365
+ }
366
+ } catch {
367
+ vortex = null;
368
+ }
369
+ let effortLevel = null;
370
+ if (d.effortLevel === "xhigh" && d.transcriptPath) {
371
+ const sniffed = sniffEffortFromTranscript(d.transcriptPath);
372
+ if (sniffed === "ultracode")
373
+ effortLevel = "ultracode";
374
+ }
375
+ return {
376
+ gitBranch,
377
+ gitHash,
378
+ sessionCount: countClaudeSessions(),
379
+ vortex,
380
+ effortLevel,
381
+ now
382
+ };
383
+ }
384
+ function statuslineCommand(lite) {
385
+ return `npx --no-install vortex statusline${lite ? " lite" : ""} || exit 0`;
386
+ }
387
+ function ensureStatusline(existing, lite, force = false) {
388
+ const command = statuslineCommand(lite);
389
+ const current = existing.statusLine;
390
+ const currentCmd = typeof current?.command === "string" ? current.command : void 0;
391
+ const isOurs = currentCmd === statuslineCommand(false) || currentCmd === statuslineCommand(true);
392
+ if (currentCmd === command && current?.type === "command") {
393
+ return { settings: existing, status: "already-ours" };
394
+ }
395
+ if (current && !isOurs && !force) {
396
+ return { settings: existing, status: "kept-existing", existing: currentCmd ?? JSON.stringify(current) };
397
+ }
398
+ return {
399
+ settings: { ...existing, statusLine: { type: "command", command } },
400
+ status: "installed"
401
+ };
402
+ }
403
+ async function runStatuslineCli(argv, repoRoot, out, err) {
404
+ if (argv[0] === "install") {
405
+ const lite = argv.includes("--lite");
406
+ const force = argv.includes("--force");
407
+ const settingsPath = join(repoRoot, ".claude", "settings.json");
408
+ const existingText = existsSync(settingsPath) ? readFileSync(settingsPath, "utf8") : null;
409
+ const parsed = parseSettings(existingText);
410
+ const result = ensureStatusline(parsed, lite, force);
411
+ if (result.status === "installed") {
412
+ const { mkdirSync, writeFileSync } = await import("fs");
413
+ mkdirSync(join(repoRoot, ".claude"), { recursive: true });
414
+ writeFileSync(settingsPath, serializeSettings(result.settings), "utf8");
415
+ }
416
+ const payload = {
417
+ status: result.status,
418
+ settingsPath,
419
+ ...result.status !== "kept-existing" ? { command: statuslineCommand(lite) } : {},
420
+ ...result.existing !== void 0 ? { existing: result.existing } : {}
421
+ };
422
+ out(JSON.stringify(payload, null, 2) + "\n");
423
+ if (result.status === "kept-existing") {
424
+ err("[vortex] statusLine already configured with a different command \u2014 kept it. Re-run with --force to replace.\n");
425
+ }
426
+ return 0;
427
+ }
428
+ const mode = argv[0] === "lite" ? "lite" : "full";
429
+ if (process.stdin.isTTY) {
430
+ err("[vortex] statusline renders Claude Code's statusLine stdin JSON \u2014 pipe it in, or wire it up with `vortex statusline install [--lite]`.\n");
431
+ return 0;
432
+ }
433
+ let raw = "";
434
+ try {
435
+ raw = readFileSync(0, "utf8");
436
+ } catch {
437
+ raw = "";
438
+ }
439
+ if (!raw.trim())
440
+ return 0;
441
+ let data;
442
+ try {
443
+ data = parseStatuslineInput(raw);
444
+ } catch {
445
+ return 0;
446
+ }
447
+ out(renderStatusline(data, collectStatuslineProbes(data), mode));
448
+ return 0;
449
+ }
450
+
451
+ export {
452
+ SESSION_START_COMMAND,
453
+ SESSION_END_COMMAND,
454
+ parseSettings,
455
+ ensureVortexHooks,
456
+ serializeSettings,
457
+ safeSegment,
458
+ parseStatuslineInput,
459
+ effortMeter,
460
+ formatTokens,
461
+ formatWindow,
462
+ makeBar,
463
+ renderStatusline,
464
+ sniffEffortFromTranscript,
465
+ collectStatuslineProbes,
466
+ statuslineCommand,
467
+ ensureStatusline,
468
+ runStatuslineCli
469
+ };
470
+ //# sourceMappingURL=chunk-DWANI3LV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../plugins/session-rituals/src/statusline.ts","../../plugins/session-rituals/src/ensure-hooks.ts"],"sourcesContent":["/**\n * `vortex statusline` — a Claude Code statusLine renderer.\n *\n * Claude Code pipes a JSON snapshot of the session (model, context window,\n * cost, rate limits, workspace) to the configured statusLine command on every\n * refresh and renders whatever the command prints. This module turns that\n * snapshot into a colored status bar:\n *\n * full (default, 3 lines + a VortEX line inside an instance):\n * 🧠 Fable 5 │ █ max │ █░░░░░░░░░ 12% · 120K/1M │ 🕐 22:25 │ 💰 $9.22\n * 5h ██████░░ 75%(2h13m) │ 7d ███████░ 96%(0d8h) │ 📦 cache 98%\n * 📁 my-project │ ⎇ main 2b0949d │ ⏱ 1h19m │ +78 -38 │ ⧉ 1\n * 🌀 VortEX v0.11.0 │ last: 2026-06-10_0452-….md\n *\n * lite (1 line):\n * 📁 my-project │ ⎇ main │ 🧠 Fable 5 │ █ max │ ░░░░░░░░ 12% · 120K/1M │ 5h 75% · 7d 96% │ ⧉ 1 │ 🌀 v0.11.0 │ 🕐 22:25\n *\n * Design notes:\n * - Rendering is PURE (`renderStatusline(data, probes, mode)`) — every\n * environment lookup (git, process list, clock, VortEX instance files) is\n * collected separately in `collectStatuslineProbes`, so the composition is\n * unit-testable without a repo or a terminal.\n * - Reasoning effort is shown as one bar whose height + color encode the\n * level. Claude Code reports `ultracode` as plain `xhigh` (ultracode is\n * \"xhigh + workflow orchestration\"), so when the level reads `xhigh` we\n * additionally sniff the session transcript for the last `/effort` change\n * notice — a best-effort heuristic that degrades to showing `xhigh`.\n * - Every probe is wrapped: a statusline must NEVER throw or block the bar —\n * on any failure a segment silently falls back to a neutral value.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { closeSync, existsSync, openSync, readSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { parseSettings, serializeSettings, type ClaudeSettings } from \"./ensure-hooks.js\";\n\n// --- ANSI palette (kept as raw escapes so the bar renders in any ANSI terminal) ---\nconst RST = \"\\x1b[0m\";\nconst CYAN = \"\\x1b[36m\";\nconst GREEN = \"\\x1b[32m\";\nconst YELLOW = \"\\x1b[33m\";\nconst RED = \"\\x1b[31m\";\nconst DIM = \"\\x1b[2m\";\nconst BOLD = \"\\x1b[1m\";\nconst WHITE = \"\\x1b[37m\";\nconst MAGENTA = \"\\x1b[35m\";\nconst BLUE = \"\\x1b[34m\";\nconst GREY = \"\\x1b[38;5;242m\";\nconst ORANGE = \"\\x1b[38;5;208m\";\n\nconst SEP = ` ${DIM}│${RST} `;\n\n/** The slice of Claude Code's statusLine input JSON this renderer consumes. */\nexport interface StatuslineData {\n readonly modelName: string;\n /** Reasoning effort level as reported (`effort.level`), or null. */\n readonly effortLevel: string | null;\n readonly transcriptPath: string | null;\n /** Project directory (workspace.current_dir, falling back to cwd). */\n readonly dir: string | null;\n readonly contextWindowSize: number;\n /** Context used, percent — kept as reported (may be fractional). */\n readonly usedPercentage: number;\n readonly cacheReadTokens: number;\n readonly cacheCreationTokens: number;\n readonly costUsd: number;\n readonly durationMs: number;\n readonly linesAdded: number;\n readonly linesRemoved: number;\n readonly fiveHourUsedPct: number;\n readonly fiveHourResetsAt: number;\n readonly sevenDayUsedPct: number;\n readonly sevenDayResetsAt: number;\n}\n\n/** Environment lookups the renderer composes in — collected impurely, injected purely. */\nexport interface StatuslineProbes {\n readonly gitBranch: string;\n readonly gitHash: string;\n readonly sessionCount: number;\n /** Set when `dir` is a VortEX instance root; lastWorklog is the newest worklog file name. */\n readonly vortex: { readonly version: string | null; readonly lastWorklog: string | null } | null;\n /** Effort level after the ultracode transcript sniff (null → use data.effortLevel). */\n readonly effortLevel: string | null;\n readonly now: Date;\n}\n\nfunction num(v: unknown, fallback = 0): number {\n return typeof v === \"number\" && Number.isFinite(v) ? v : fallback;\n}\n/** Non-negative numeric field (counters, costs, sizes — a negative is sender garbage). */\nfunction nonneg(v: unknown, fallback = 0): number {\n return Math.max(0, num(v, fallback));\n}\n/** Clamp a reported percentage into the displayable 0..100 range. */\nfunction clampPct(v: number): number {\n return Math.min(100, Math.max(0, v));\n}\n/**\n * Make a dynamic string safe to put in a single-line ANSI bar: drop control\n * chars (incl. ESC — no injected sequences/newlines), collapse whitespace, and\n * truncate long values. The bar must render intact whatever a branch name,\n * model name, or worklog filename contains.\n */\nexport function safeSegment(s: string, max = 60): string {\n // eslint-disable-next-line no-control-regex\n const cleaned = s.replace(/[\\u0000-\\u001f\\u007f]/g, \" \").replace(/\\s+/g, \" \").trim();\n return cleaned.length > max ? cleaned.slice(0, max - 1) + \"…\" : cleaned;\n}\nfunction str(v: unknown): string | null {\n return typeof v === \"string\" && v.length > 0 ? v : null;\n}\nfunction obj(v: unknown): Record<string, unknown> {\n return typeof v === \"object\" && v !== null ? (v as Record<string, unknown>) : {};\n}\n\n/** Parse Claude Code's statusLine stdin JSON into the fields the bar uses. Never throws on shape — only on non-JSON. */\nexport function parseStatuslineInput(text: string): StatuslineData {\n const root = obj(JSON.parse(text));\n const model = obj(root.model);\n const effort = obj(root.effort);\n const workspace = obj(root.workspace);\n const cost = obj(root.cost);\n const ctx = obj(root.context_window);\n const usage = obj(ctx.current_usage);\n const limits = obj(root.rate_limits);\n const five = obj(limits.five_hour);\n const seven = obj(limits.seven_day);\n\n const rawName = str(model.display_name) ?? \"Claude\";\n const windowSize = nonneg(ctx.context_window_size, 200_000);\n return {\n modelName: rawName.replace(/^Claude\\s+/, \"\"),\n effortLevel: str(effort.level),\n transcriptPath: str(root.transcript_path),\n dir: str(workspace.current_dir) ?? str(root.cwd),\n contextWindowSize: windowSize > 0 ? windowSize : 200_000,\n usedPercentage: clampPct(num(ctx.used_percentage)),\n cacheReadTokens: nonneg(usage.cache_read_input_tokens),\n cacheCreationTokens: nonneg(usage.cache_creation_input_tokens),\n costUsd: nonneg(cost.total_cost_usd),\n durationMs: nonneg(cost.total_duration_ms),\n linesAdded: nonneg(cost.total_lines_added),\n linesRemoved: nonneg(cost.total_lines_removed),\n fiveHourUsedPct: clampPct(num(five.used_percentage)),\n fiveHourResetsAt: nonneg(five.resets_at),\n sevenDayUsedPct: clampPct(num(seven.used_percentage)),\n sevenDayResetsAt: nonneg(seven.resets_at),\n };\n}\n\n/** One-character effort meter: height + color encode the level (`█✦` for ultracode). */\nexport function effortMeter(level: string | null): { meter: string; color: string } | null {\n switch (level) {\n case \"low\":\n return { meter: \"▂\", color: GREY };\n case \"medium\":\n return { meter: \"▄\", color: GREEN };\n case \"high\":\n return { meter: \"▆\", color: YELLOW };\n case \"xhigh\":\n return { meter: \"▇\", color: ORANGE };\n case \"max\":\n return { meter: \"█\", color: RED };\n case \"ultracode\":\n return { meter: \"█✦\", color: MAGENTA };\n default:\n return null;\n }\n}\n\n/** `7` → `7`, `70_000` → `70K`, `1_200_000` → `1.2M` (token quantities). */\nexport function formatTokens(t: number): string {\n if (t >= 1_000_000) return `${Math.floor(t / 1_000_000)}.${Math.floor((t % 1_000_000) / 100_000)}M`;\n if (t >= 1_000) return `${Math.floor(t / 1_000)}K`;\n return String(Math.floor(t));\n}\n\n/** Window sizes render without a decimal: `200_000` → `200K`, `1_000_000` → `1M`. */\nexport function formatWindow(t: number): string {\n if (t >= 1_000_000) return `${Math.floor(t / 1_000_000)}M`;\n if (t >= 1_000) return `${Math.floor(t / 1_000)}K`;\n return String(Math.floor(t));\n}\n\n/** `█`-filled gauge, `width` cells, floor-scaled so 100% and only 100% fills it. */\nexport function makeBar(pct: number, width: number): string {\n const filled = Math.min(width, Math.max(0, Math.floor((pct * width) / 100)));\n return \"█\".repeat(filled) + \"░\".repeat(width - filled);\n}\n\n/** Color for a USAGE percentage (high = bad): <50 green, <80 yellow, else red. */\nfunction usageColor(pct: number): string {\n if (pct >= 80) return RED;\n if (pct >= 50) return YELLOW;\n return GREEN;\n}\n\n/** Color for a REMAINING percentage (low = bad): <=30 red, <=60 yellow, else green. */\nfunction healthColor(remaining: number): string {\n if (remaining <= 30) return RED;\n if (remaining <= 60) return YELLOW;\n return GREEN;\n}\n\nfunction formatDuration(ms: number): string {\n const totalMin = Math.floor(ms / 60_000);\n if (totalMin >= 60) return `${Math.floor(totalMin / 60)}h${totalMin % 60}m`;\n return `${totalMin}m${Math.floor(ms / 1000) % 60}s`;\n}\n\n/** `(2h13m)` until an epoch-seconds reset, or \"\" when absent/past. */\nfunction untilReset(resetsAtSec: number, now: Date): string {\n if (resetsAtSec <= 0) return \"\";\n const diffSec = resetsAtSec - Math.floor(now.getTime() / 1000);\n if (diffSec <= 0) return \"\";\n if (diffSec >= 86_400) return `${Math.floor(diffSec / 86_400)}d${Math.floor((diffSec % 86_400) / 3600)}h`;\n return `${Math.floor(diffSec / 3600)}h${Math.floor((diffSec % 3600) / 60)}m`;\n}\n\nfunction pad2(n: number): string {\n return String(n).padStart(2, \"0\");\n}\n\n/** Compose the status bar. Pure — see `collectStatuslineProbes` for the impure half. */\nexport function renderStatusline(\n d: StatuslineData,\n p: StatuslineProbes,\n mode: \"full\" | \"lite\" = \"full\",\n): string {\n const usedPctDisplay = Math.round(d.usedPercentage);\n const ctxColor = usageColor(usedPctDisplay);\n const ctxUsedTokens = Math.round((d.usedPercentage * d.contextWindowSize) / 100);\n const ctxText = `${WHITE}${formatTokens(ctxUsedTokens)}/${formatWindow(d.contextWindowSize)}${RST}`;\n const clock = `🕐 ${WHITE}${pad2(p.now.getHours())}:${pad2(p.now.getMinutes())}${RST}`;\n\n const level = p.effortLevel ?? d.effortLevel;\n const effort = effortMeter(level);\n const effortSeg = effort\n ? `${effort.color}${effort.meter}${RST} ${GREY}${safeSegment(level ?? \"\")}${RST}`\n : null;\n const modelSeg = `🧠 ${BOLD}${CYAN}${safeSegment(d.modelName, 30)}${RST}`;\n\n const fiveRemain = Math.max(0, 100 - Math.round(d.fiveHourUsedPct));\n const sevenRemain = Math.max(0, 100 - Math.round(d.sevenDayUsedPct));\n const fiveColor = healthColor(fiveRemain);\n const sevenColor = healthColor(sevenRemain);\n const sessionSeg = `${WHITE}⧉ ${Math.max(1, Math.floor(p.sessionCount))}${RST}`;\n\n const project = safeSegment(\n p.vortex || d.dir ? (d.dir ?? \"\").replace(/[\\\\/]+$/, \"\").split(/[\\\\/]/).pop() || \"?\" : \"?\",\n 40,\n );\n const gitBranch = safeSegment(p.gitBranch, 40);\n const gitHash = safeSegment(p.gitHash, 16);\n\n if (mode === \"lite\") {\n const parts = [\n `📁 ${BLUE}${project}${RST}`,\n `${WHITE}⎇${RST} ${YELLOW}${gitBranch}${RST}`,\n effortSeg ? `${modelSeg}${SEP}${effortSeg}` : modelSeg,\n `${ctxColor}${makeBar(usedPctDisplay, 8)} ${usedPctDisplay}%${RST} ${DIM}·${RST} ${ctxText}`,\n `${GREY}5h${RST} ${fiveColor}${fiveRemain}%${RST} ${DIM}·${RST} ${GREY}7d${RST} ${sevenColor}${sevenRemain}%${RST}`,\n sessionSeg,\n ...(p.vortex?.version ? [`🌀 ${MAGENTA}v${safeSegment(p.vortex.version, 20)}${RST}`] : []),\n clock,\n ];\n return parts.join(SEP);\n }\n\n const l1 = [\n effortSeg ? `${modelSeg}${SEP}${effortSeg}` : modelSeg,\n `${ctxColor}${makeBar(usedPctDisplay, 10)} ${usedPctDisplay}%${RST} ${DIM}·${RST} ${ctxText}`,\n clock,\n `💰 ${BOLD}${YELLOW}$${d.costUsd.toFixed(2)}${RST}`,\n ].join(SEP);\n\n const cacheTotal = d.cacheReadTokens + d.cacheCreationTokens;\n const cachePct = cacheTotal > 0 ? Math.floor((d.cacheReadTokens * 100) / cacheTotal) : 0;\n const fiveReset = untilReset(d.fiveHourResetsAt, p.now);\n const sevenReset = untilReset(d.sevenDayResetsAt, p.now);\n const l2 = [\n `${GREY}5h${RST} ${fiveColor}${makeBar(fiveRemain, 8)} ${fiveRemain}%${RST}` +\n (fiveReset ? `${GREY}(${fiveReset})${RST}` : \"\"),\n `${GREY}7d${RST} ${sevenColor}${makeBar(sevenRemain, 8)} ${sevenRemain}%${RST}` +\n (sevenReset ? `${GREY}(${sevenReset})${RST}` : \"\"),\n `📦 ${GREEN}cache ${cachePct}%${RST}`,\n ].join(SEP);\n\n const l3 = [\n `📁 ${BLUE}${project}${RST}`,\n `${WHITE}⎇${RST} ${YELLOW}${gitBranch}${RST} ${GREY}${gitHash}${RST}`,\n `⏱ ${MAGENTA}${formatDuration(d.durationMs)}${RST}`,\n `${GREEN}+${d.linesAdded}${RST} ${RED}-${d.linesRemoved}${RST}`,\n sessionSeg,\n ].join(SEP);\n\n const lines = [l1, l2, l3];\n if (p.vortex) {\n let vx = `🌀 ${BOLD}${MAGENTA}VortEX${RST}`;\n if (p.vortex.version) vx += ` ${GREY}v${safeSegment(p.vortex.version, 20)}${RST}`;\n if (p.vortex.lastWorklog) vx += `${SEP}${GREY}last:${RST} ${safeSegment(p.vortex.lastWorklog, 70)}`;\n lines.push(vx);\n }\n return lines.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Impure probes — every lookup is individually guarded; a failure falls back.\n// ---------------------------------------------------------------------------\n\nfunction gitOut(dir: string, args: readonly string[]): string {\n return execFileSync(\"git\", [\"-C\", dir, ...args], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n}\n\n/**\n * Claude Code reports `ultracode` as `xhigh`; the transcript keeps the last\n * `/effort` change notice. Read the transcript tail and return the level named\n * by the LAST such notice, or null. Best-effort by design — a format change\n * simply degrades the display to `xhigh`.\n */\nexport function sniffEffortFromTranscript(transcriptPath: string, maxBytes = 4_000_000): string | null {\n try {\n const size = statSync(transcriptPath).size;\n const start = Math.max(0, size - maxBytes);\n const length = size - start;\n if (length <= 0) return null;\n const buf = Buffer.alloc(length);\n const fd = openSync(transcriptPath, \"r\");\n try {\n readSync(fd, buf, 0, length, start);\n } finally {\n closeSync(fd);\n }\n const text = buf.toString(\"utf8\");\n const matches = text.match(/Set effort level to [a-z]+/g);\n if (!matches || matches.length === 0) return null;\n return matches[matches.length - 1]!.slice(\"Set effort level to \".length);\n } catch {\n return null;\n }\n}\n\n/** Count open Claude Code sessions (one `claude` process per session). Falls back to 1. */\nfunction countClaudeSessions(): number {\n try {\n if (process.platform === \"win32\") {\n const out = execFileSync(\"tasklist\", [], { encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"ignore\"] });\n const n = (out.match(/claude\\.exe/gi) ?? []).length;\n return n > 0 ? n : 1;\n }\n const out = execFileSync(\"pgrep\", [\"-x\", \"claude\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n });\n const n = out.split(/\\r?\\n/).filter(Boolean).length;\n return n > 0 ? n : 1;\n } catch {\n return 1;\n }\n}\n\n/** Newest worklog file name under `data/worklog` — by name, which sorts chronologically. */\nfunction newestWorklogName(repoRoot: string): string | null {\n try {\n const dir = join(repoRoot, \"data\", \"worklog\");\n const entries = readdirSync(dir, { recursive: true }) as string[];\n let best: string | null = null;\n for (const rel of entries) {\n const s = String(rel);\n if (!s.endsWith(\".md\") || s.endsWith(\"_INDEX.md\")) continue;\n if (best === null || s > best) best = s;\n }\n return best ? best.replace(/\\\\/g, \"/\").split(\"/\").pop()! : null;\n } catch {\n return null;\n }\n}\n\n/** Collect every environment lookup the renderer needs for this input. */\nexport function collectStatuslineProbes(d: StatuslineData, now = new Date()): StatuslineProbes {\n const dir = d.dir ?? process.cwd();\n\n let gitBranch = \"-\";\n let gitHash = \"-\";\n try {\n const branch = gitOut(dir, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n gitBranch = branch === \"HEAD\" ? \"detached\" : branch || \"-\";\n gitHash = gitOut(dir, [\"rev-parse\", \"--short\", \"HEAD\"]) || \"-\";\n } catch {\n // not a git repo / git absent — keep \"-\"\n }\n\n let vortex: StatuslineProbes[\"vortex\"] = null;\n try {\n if (existsSync(join(dir, \".agent\", \"vortex.json\"))) {\n let version: string | null = null;\n try {\n const pkg = JSON.parse(\n readFileSync(join(dir, \"node_modules\", \"@vortex-os\", \"base\", \"package.json\"), \"utf8\"),\n ) as { version?: string };\n version = typeof pkg.version === \"string\" ? pkg.version : null;\n } catch {\n // base not installed locally — show the VortEX line without a version\n }\n vortex = { version, lastWorklog: newestWorklogName(dir) };\n }\n } catch {\n vortex = null;\n }\n\n let effortLevel: string | null = null;\n if (d.effortLevel === \"xhigh\" && d.transcriptPath) {\n const sniffed = sniffEffortFromTranscript(d.transcriptPath);\n if (sniffed === \"ultracode\") effortLevel = \"ultracode\";\n }\n\n return {\n gitBranch,\n gitHash,\n sessionCount: countClaudeSessions(),\n vortex,\n effortLevel,\n now,\n };\n}\n\n// ---------------------------------------------------------------------------\n// `vortex statusline install [--lite]` — merge-safe settings wiring.\n// ---------------------------------------------------------------------------\n\n/** The statusLine command written by `install`. Mirrors the session hooks: bin-name resolution, fail-closed, self-silencing. */\nexport function statuslineCommand(lite: boolean): string {\n return `npx --no-install vortex statusline${lite ? \" lite\" : \"\"} || exit 0`;\n}\n\nexport interface StatuslineInstallResult {\n readonly status: \"installed\" | \"already-ours\" | \"kept-existing\";\n readonly settingsPath: string;\n readonly command?: string;\n /** Present when status is \"kept-existing\": the command we refused to overwrite. */\n readonly existing?: string;\n}\n\n/**\n * Merge `statusLine` into a settings object. NON-DESTRUCTIVE: an existing\n * statusLine that is not ours is kept (the user owns their bar) unless `force`.\n * Switching full↔lite of our own command counts as ours and is updated.\n */\nexport function ensureStatusline(\n existing: ClaudeSettings,\n lite: boolean,\n force = false,\n): { settings: ClaudeSettings; status: StatuslineInstallResult[\"status\"]; existing?: string } {\n const command = statuslineCommand(lite);\n const current = existing.statusLine as { type?: string; command?: string } | undefined;\n const currentCmd = typeof current?.command === \"string\" ? current.command : undefined;\n const isOurs =\n currentCmd === statuslineCommand(false) || currentCmd === statuslineCommand(true);\n // `already-ours` requires the whole entry to be well-formed — a matching\n // command under a wrong/missing `type` is rewritten (repaired) below.\n if (currentCmd === command && current?.type === \"command\") {\n return { settings: existing, status: \"already-ours\" };\n }\n if (current && !isOurs && !force) {\n return { settings: existing, status: \"kept-existing\", existing: currentCmd ?? JSON.stringify(current) };\n }\n return {\n settings: { ...existing, statusLine: { type: \"command\", command } },\n status: \"installed\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// CLI entry — `vortex statusline [lite] [install [--lite] [--force]]`\n// ---------------------------------------------------------------------------\n\n/**\n * Run the statusline CLI. `install` wires `.claude/settings.json` (at\n * `repoRoot`); anything else renders the bar from stdin JSON. A render must\n * never break the bar: bad/absent input prints nothing and exits 0.\n */\nexport async function runStatuslineCli(\n argv: readonly string[],\n repoRoot: string,\n out: (s: string) => void,\n err: (s: string) => void,\n): Promise<number> {\n if (argv[0] === \"install\") {\n const lite = argv.includes(\"--lite\");\n const force = argv.includes(\"--force\");\n const settingsPath = join(repoRoot, \".claude\", \"settings.json\");\n const existingText = existsSync(settingsPath) ? readFileSync(settingsPath, \"utf8\") : null;\n const parsed = parseSettings(existingText);\n const result = ensureStatusline(parsed, lite, force);\n if (result.status === \"installed\") {\n const { mkdirSync, writeFileSync } = await import(\"node:fs\");\n mkdirSync(join(repoRoot, \".claude\"), { recursive: true });\n writeFileSync(settingsPath, serializeSettings(result.settings), \"utf8\");\n }\n const payload: StatuslineInstallResult = {\n status: result.status,\n settingsPath,\n ...(result.status !== \"kept-existing\" ? { command: statuslineCommand(lite) } : {}),\n ...(result.existing !== undefined ? { existing: result.existing } : {}),\n };\n out(JSON.stringify(payload, null, 2) + \"\\n\");\n if (result.status === \"kept-existing\") {\n err(\n \"[vortex] statusLine already configured with a different command — kept it. Re-run with --force to replace.\\n\",\n );\n }\n return 0;\n }\n\n const mode: \"full\" | \"lite\" = argv[0] === \"lite\" ? \"lite\" : \"full\";\n if (process.stdin.isTTY) {\n // Typed by hand with no piped JSON — don't block waiting on stdin.\n err(\n \"[vortex] statusline renders Claude Code's statusLine stdin JSON — pipe it in, \" +\n \"or wire it up with `vortex statusline install [--lite]`.\\n\",\n );\n return 0;\n }\n let raw = \"\";\n try {\n raw = readFileSync(0, \"utf8\");\n } catch {\n raw = \"\";\n }\n if (!raw.trim()) return 0; // no input — print nothing, never an error in the bar\n let data: StatuslineData;\n try {\n data = parseStatuslineInput(raw);\n } catch {\n return 0; // malformed input — keep the bar blank rather than erroring\n }\n out(renderStatusline(data, collectStatuslineProbes(data), mode));\n return 0;\n}\n","/**\r\n * Hook wiring for `/vortex init`: make sure the instance's\r\n * `.claude/settings.json` registers the VortEX SessionStart / SessionEnd hooks,\r\n * so the boot report + worklog-net fire automatically without the user knowing\r\n * any command. NON-DESTRUCTIVE — like the MCP install merge, this preserves\r\n * every other hook and top-level field and only adds our two entries if absent.\r\n *\r\n * Pure functions here (parse / merge / detect); the command does the actual\r\n * file read/write around them. Keeping them pure makes the merge unit-testable\r\n * and the \"writes only what's missing\" guarantee verifiable.\r\n */\r\n\r\n// Hook commands invoke the published CLI via `npx`, NOT checkout-relative\r\n// `node plugins/...` paths. An npm-installed instance (`npm i @vortex-os/base`)\r\n// has no monorepo checkout — but it does have the `vortex` bin on disk (the\r\n// instance's local `node_modules/.bin`, or the global npm bin on PATH), so\r\n// `npx --no-install vortex session-{start,end}` runs it. Two deliberate choices:\r\n// • `--no-install` — a SessionStart/End hook fires automatically; bare\r\n// `npx vortex` would silently install an arbitrary `vortex` package from the\r\n// network on a cache miss. `--no-install` fails closed instead.\r\n// • Resolve by BIN NAME (no `-p @vortex-os/base`) — npx searches the current\r\n// folder's `node_modules/.bin` FIRST, then PATH, which includes a global\r\n// `npm i -g @vortex-os/base`. This is what makes `vortex global-setup` fire in\r\n// EVERY folder: a `-p <pkg>` form (this command's earlier shape) resolves only\r\n// a LOCAL install and ignores the global one — so the GLOBAL hook silently\r\n// no-opped everywhere except the instance folder, defeating global-setup's one\r\n// job. Resolving the bin instead keeps \"local install wins\" (so the instance's\r\n// pinned version still runs there) while letting the global bin cover all other\r\n// folders. The cost: a same-named `vortex` bin earlier on PATH/local could\r\n// shadow ours — but `--no-install` still blocks the network-install case and a\r\n// colliding bin is unlikely, an acceptable trade for the any-folder guarantee.\r\n// These map to the `session-start` / `session-end` subcommands of the CLI.\r\n//\r\n// The trailing `|| exit 0` makes the hook SELF-SILENCING: the global hook fires in\r\n// every folder, but `npx --no-install` still fails where NO `vortex` is resolvable\r\n// (no local install AND nothing on PATH) — and Claude Code surfaces a non-zero\r\n// SessionStart hook as a \"hook error\" to the user. `|| exit 0` swallows that\r\n// (exit 0 → no error notice; stderr not injected) so such folders stay quiet.\r\n// `||` + `exit 0` are valid in both cmd.exe (the default Windows child-process\r\n// shell) and POSIX sh. Keeping the per-instance and global commands BYTE-IDENTICAL\r\n// is what lets Claude Code dedup them (no double session-start in the instance).\r\nexport const SESSION_START_COMMAND =\r\n \"npx --no-install vortex session-start || exit 0\";\r\nexport const SESSION_END_COMMAND =\r\n \"npx --no-install vortex session-end || exit 0\";\r\n\r\n// Older command shapes a prior `init`/`global-setup` may have written. On the next\r\n// `ensureVortexHooks` run each migrates IN PLACE to the current command, so the\r\n// per-instance hook keeps matching the global one (and stays dedup-able). Covers\r\n// the `-p @vortex-os/base` form (self-silencing and its pre-`|| exit 0` variant)\r\n// that this command carried before resolution moved to the bare bin name.\r\nconst LEGACY_COMMANDS: Record<\"SessionStart\" | \"SessionEnd\", readonly string[]> = {\r\n SessionStart: [\r\n \"npx --no-install -p @vortex-os/base vortex session-start || exit 0\",\r\n \"npx --no-install -p @vortex-os/base vortex session-start\",\r\n ],\r\n SessionEnd: [\r\n \"npx --no-install -p @vortex-os/base vortex session-end || exit 0\",\r\n \"npx --no-install -p @vortex-os/base vortex session-end\",\r\n ],\r\n};\r\n\r\ninterface HookCommand {\r\n readonly type: \"command\";\r\n readonly command: string;\r\n}\r\ninterface HookGroup {\r\n readonly hooks: readonly HookCommand[];\r\n readonly matcher?: string;\r\n}\r\nexport interface ClaudeSettings {\r\n hooks?: {\r\n SessionStart?: HookGroup[];\r\n SessionEnd?: HookGroup[];\r\n [event: string]: HookGroup[] | undefined;\r\n };\r\n [key: string]: unknown;\r\n}\r\n\r\nexport interface EnsureHooksResult {\r\n readonly settings: ClaudeSettings;\r\n /**\r\n * Hook events that CHANGED — a VortEX entry was added, or a legacy one was\r\n * migrated/de-duplicated in place (empty when nothing changed). Callers should\r\n * key \"did we need to write?\" off `alreadyWired`, not the name \"added\".\r\n */\r\n readonly added: readonly (\"SessionStart\" | \"SessionEnd\")[];\r\n /** True when nothing changed — every VortEX hook was already present. */\r\n readonly alreadyWired: boolean;\r\n}\r\n\r\n/**\r\n * Parse existing settings.json text. Empty/whitespace → `{}` (fresh). Throws on\r\n * malformed JSON so the caller aborts rather than clobbering a hand-edited file.\r\n */\r\nexport function parseSettings(text: string | null | undefined): ClaudeSettings {\r\n const trimmed = (text ?? \"\").trim();\r\n if (trimmed.length === 0) return {};\r\n let parsed: unknown;\r\n try {\r\n parsed = JSON.parse(trimmed);\r\n } catch (e) {\r\n throw new Error(\r\n `.claude/settings.json is not valid JSON — refusing to overwrite. Fix or remove it first. (${(e as Error).message})`,\r\n );\r\n }\r\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\r\n throw new Error(\".claude/settings.json is not a JSON object — refusing to overwrite.\");\r\n }\r\n return parsed as ClaudeSettings;\r\n}\r\n\r\n/**\r\n * Merge the VortEX hooks into an existing settings object WITHOUT mutating the\r\n * input. A hook event is left untouched if it already references our command\r\n * (idempotent); otherwise our group is appended alongside any existing groups.\r\n */\r\nexport function ensureVortexHooks(existing: ClaudeSettings | null | undefined): EnsureHooksResult {\r\n const base: ClaudeSettings = existing && typeof existing === \"object\" ? existing : {};\r\n const hooks = { ...(base.hooks ?? {}) };\r\n const added: (\"SessionStart\" | \"SessionEnd\")[] = [];\r\n\r\n const wire = (event: \"SessionStart\" | \"SessionEnd\", command: string) => {\r\n const legacy = LEGACY_COMMANDS[event];\r\n const src = hooks[event] ?? [];\r\n let changed = false;\r\n let kept = false; // already retained one entry with the current command?\r\n const groups: HookGroup[] = [];\r\n for (const g of src) {\r\n const hookList: HookCommand[] = [];\r\n for (const h of g.hooks ?? []) {\r\n // Migrate a LEGACY VortEX command to the current one (in place, so the\r\n // group's other hooks and its `matcher` are preserved).\r\n const migrated = legacy.includes(h.command);\r\n const cmd = migrated ? command : h.command;\r\n if (cmd === command) {\r\n if (kept) {\r\n changed = true; // drop a duplicate VortEX hook (e.g. legacy + current)\r\n continue;\r\n }\r\n kept = true;\r\n if (migrated) changed = true;\r\n hookList.push(migrated ? { ...h, command } : h);\r\n } else {\r\n hookList.push(h);\r\n }\r\n }\r\n if (hookList.length > 0) groups.push({ ...g, hooks: hookList });\r\n else changed = true; // group held only legacy/duplicate VortEX hooks → drop it\r\n }\r\n if (!kept) {\r\n groups.push({ hooks: [{ type: \"command\", command }] });\r\n changed = true;\r\n }\r\n hooks[event] = groups;\r\n if (changed) added.push(event);\r\n };\r\n\r\n wire(\"SessionStart\", SESSION_START_COMMAND);\r\n wire(\"SessionEnd\", SESSION_END_COMMAND);\r\n\r\n const settings: ClaudeSettings = { ...base, hooks };\r\n return { settings, added, alreadyWired: added.length === 0 };\r\n}\r\n\r\n/** Serialize settings the way Claude writes them (2-space, trailing newline). */\r\nexport function serializeSettings(settings: ClaudeSettings): string {\r\n return JSON.stringify(settings, null, 2) + \"\\n\";\r\n}\r\n"],"mappings":";AA+BA,SAAS,oBAAoB;AAC7B,SAAS,WAAW,YAAY,UAAU,UAAU,aAAa,cAAc,gBAAgB;AAC/F,SAAS,YAAY;;;ACQd,IAAM,wBACX;AACK,IAAM,sBACX;AAOF,IAAM,kBAA4E;EAChF,cAAc;IACZ;IACA;;EAEF,YAAY;IACV;IACA;;;AAqCE,SAAU,cAAc,MAA+B;AAC3D,QAAM,WAAW,QAAQ,IAAI,KAAI;AACjC,MAAI,QAAQ,WAAW;AAAG,WAAO,CAAA;AACjC,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,OAAO;EAC7B,SAAS,GAAG;AACV,UAAM,IAAI,MACR,kGAA8F,EAAY,OAAO,GAAG;EAExH;AACA,MAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GAAG;AAC1E,UAAM,IAAI,MAAM,0EAAqE;EACvF;AACA,SAAO;AACT;AAOM,SAAU,kBAAkB,UAA2C;AAC3E,QAAM,OAAuB,YAAY,OAAO,aAAa,WAAW,WAAW,CAAA;AACnF,QAAM,QAAQ,EAAE,GAAI,KAAK,SAAS,CAAA,EAAG;AACrC,QAAM,QAA2C,CAAA;AAEjD,QAAM,OAAO,CAAC,OAAsC,YAAmB;AACrE,UAAM,SAAS,gBAAgB,KAAK;AACpC,UAAM,MAAM,MAAM,KAAK,KAAK,CAAA;AAC5B,QAAI,UAAU;AACd,QAAI,OAAO;AACX,UAAM,SAAsB,CAAA;AAC5B,eAAW,KAAK,KAAK;AACnB,YAAM,WAA0B,CAAA;AAChC,iBAAW,KAAK,EAAE,SAAS,CAAA,GAAI;AAG7B,cAAM,WAAW,OAAO,SAAS,EAAE,OAAO;AAC1C,cAAM,MAAM,WAAW,UAAU,EAAE;AACnC,YAAI,QAAQ,SAAS;AACnB,cAAI,MAAM;AACR,sBAAU;AACV;UACF;AACA,iBAAO;AACP,cAAI;AAAU,sBAAU;AACxB,mBAAS,KAAK,WAAW,EAAE,GAAG,GAAG,QAAO,IAAK,CAAC;QAChD,OAAO;AACL,mBAAS,KAAK,CAAC;QACjB;MACF;AACA,UAAI,SAAS,SAAS;AAAG,eAAO,KAAK,EAAE,GAAG,GAAG,OAAO,SAAQ,CAAE;;AACzD,kBAAU;IACjB;AACA,QAAI,CAAC,MAAM;AACT,aAAO,KAAK,EAAE,OAAO,CAAC,EAAE,MAAM,WAAW,QAAO,CAAE,EAAC,CAAE;AACrD,gBAAU;IACZ;AACA,UAAM,KAAK,IAAI;AACf,QAAI;AAAS,YAAM,KAAK,KAAK;EAC/B;AAEA,OAAK,gBAAgB,qBAAqB;AAC1C,OAAK,cAAc,mBAAmB;AAEtC,QAAM,WAA2B,EAAE,GAAG,MAAM,MAAK;AACjD,SAAO,EAAE,UAAU,OAAO,cAAc,MAAM,WAAW,EAAC;AAC5D;AAGM,SAAU,kBAAkB,UAAwB;AACxD,SAAO,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI;AAC7C;;;ADnIA,IAAM,MAAM;AACZ,IAAM,OAAO;AACb,IAAM,QAAQ;AACd,IAAM,SAAS;AACf,IAAM,MAAM;AACZ,IAAM,MAAM;AACZ,IAAM,OAAO;AACb,IAAM,QAAQ;AACd,IAAM,UAAU;AAChB,IAAM,OAAO;AACb,IAAM,OAAO;AACb,IAAM,SAAS;AAEf,IAAM,MAAM,IAAI,GAAG,SAAI,GAAG;AAqC1B,SAAS,IAAI,GAAY,WAAW,GAAC;AACnC,SAAO,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,IAAI,IAAI;AAC3D;AAEA,SAAS,OAAO,GAAY,WAAW,GAAC;AACtC,SAAO,KAAK,IAAI,GAAG,IAAI,GAAG,QAAQ,CAAC;AACrC;AAEA,SAAS,SAAS,GAAS;AACzB,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,CAAC,CAAC;AACrC;AAOM,SAAU,YAAY,GAAW,MAAM,IAAE;AAE7C,QAAM,UAAU,EAAE,QAAQ,0BAA0B,GAAG,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAI;AAClF,SAAO,QAAQ,SAAS,MAAM,QAAQ,MAAM,GAAG,MAAM,CAAC,IAAI,WAAM;AAClE;AACA,SAAS,IAAI,GAAU;AACrB,SAAO,OAAO,MAAM,YAAY,EAAE,SAAS,IAAI,IAAI;AACrD;AACA,SAAS,IAAI,GAAU;AACrB,SAAO,OAAO,MAAM,YAAY,MAAM,OAAQ,IAAgC,CAAA;AAChF;AAGM,SAAU,qBAAqB,MAAY;AAC/C,QAAM,OAAO,IAAI,KAAK,MAAM,IAAI,CAAC;AACjC,QAAM,QAAQ,IAAI,KAAK,KAAK;AAC5B,QAAM,SAAS,IAAI,KAAK,MAAM;AAC9B,QAAM,YAAY,IAAI,KAAK,SAAS;AACpC,QAAM,OAAO,IAAI,KAAK,IAAI;AAC1B,QAAM,MAAM,IAAI,KAAK,cAAc;AACnC,QAAM,QAAQ,IAAI,IAAI,aAAa;AACnC,QAAM,SAAS,IAAI,KAAK,WAAW;AACnC,QAAM,OAAO,IAAI,OAAO,SAAS;AACjC,QAAM,QAAQ,IAAI,OAAO,SAAS;AAElC,QAAM,UAAU,IAAI,MAAM,YAAY,KAAK;AAC3C,QAAM,aAAa,OAAO,IAAI,qBAAqB,GAAO;AAC1D,SAAO;IACL,WAAW,QAAQ,QAAQ,cAAc,EAAE;IAC3C,aAAa,IAAI,OAAO,KAAK;IAC7B,gBAAgB,IAAI,KAAK,eAAe;IACxC,KAAK,IAAI,UAAU,WAAW,KAAK,IAAI,KAAK,GAAG;IAC/C,mBAAmB,aAAa,IAAI,aAAa;IACjD,gBAAgB,SAAS,IAAI,IAAI,eAAe,CAAC;IACjD,iBAAiB,OAAO,MAAM,uBAAuB;IACrD,qBAAqB,OAAO,MAAM,2BAA2B;IAC7D,SAAS,OAAO,KAAK,cAAc;IACnC,YAAY,OAAO,KAAK,iBAAiB;IACzC,YAAY,OAAO,KAAK,iBAAiB;IACzC,cAAc,OAAO,KAAK,mBAAmB;IAC7C,iBAAiB,SAAS,IAAI,KAAK,eAAe,CAAC;IACnD,kBAAkB,OAAO,KAAK,SAAS;IACvC,iBAAiB,SAAS,IAAI,MAAM,eAAe,CAAC;IACpD,kBAAkB,OAAO,MAAM,SAAS;;AAE5C;AAGM,SAAU,YAAY,OAAoB;AAC9C,UAAQ,OAAO;IACb,KAAK;AACH,aAAO,EAAE,OAAO,UAAK,OAAO,KAAI;IAClC,KAAK;AACH,aAAO,EAAE,OAAO,UAAK,OAAO,MAAK;IACnC,KAAK;AACH,aAAO,EAAE,OAAO,UAAK,OAAO,OAAM;IACpC,KAAK;AACH,aAAO,EAAE,OAAO,UAAK,OAAO,OAAM;IACpC,KAAK;AACH,aAAO,EAAE,OAAO,UAAK,OAAO,IAAG;IACjC,KAAK;AACH,aAAO,EAAE,OAAO,gBAAM,OAAO,QAAO;IACtC;AACE,aAAO;EACX;AACF;AAGM,SAAU,aAAa,GAAS;AACpC,MAAI,KAAK;AAAW,WAAO,GAAG,KAAK,MAAM,IAAI,GAAS,CAAC,IAAI,KAAK,MAAO,IAAI,MAAa,GAAO,CAAC;AAChG,MAAI,KAAK;AAAO,WAAO,GAAG,KAAK,MAAM,IAAI,GAAK,CAAC;AAC/C,SAAO,OAAO,KAAK,MAAM,CAAC,CAAC;AAC7B;AAGM,SAAU,aAAa,GAAS;AACpC,MAAI,KAAK;AAAW,WAAO,GAAG,KAAK,MAAM,IAAI,GAAS,CAAC;AACvD,MAAI,KAAK;AAAO,WAAO,GAAG,KAAK,MAAM,IAAI,GAAK,CAAC;AAC/C,SAAO,OAAO,KAAK,MAAM,CAAC,CAAC;AAC7B;AAGM,SAAU,QAAQ,KAAa,OAAa;AAChD,QAAM,SAAS,KAAK,IAAI,OAAO,KAAK,IAAI,GAAG,KAAK,MAAO,MAAM,QAAS,GAAG,CAAC,CAAC;AAC3E,SAAO,SAAI,OAAO,MAAM,IAAI,SAAI,OAAO,QAAQ,MAAM;AACvD;AAGA,SAAS,WAAW,KAAW;AAC7B,MAAI,OAAO;AAAI,WAAO;AACtB,MAAI,OAAO;AAAI,WAAO;AACtB,SAAO;AACT;AAGA,SAAS,YAAY,WAAiB;AACpC,MAAI,aAAa;AAAI,WAAO;AAC5B,MAAI,aAAa;AAAI,WAAO;AAC5B,SAAO;AACT;AAEA,SAAS,eAAe,IAAU;AAChC,QAAM,WAAW,KAAK,MAAM,KAAK,GAAM;AACvC,MAAI,YAAY;AAAI,WAAO,GAAG,KAAK,MAAM,WAAW,EAAE,CAAC,IAAI,WAAW,EAAE;AACxE,SAAO,GAAG,QAAQ,IAAI,KAAK,MAAM,KAAK,GAAI,IAAI,EAAE;AAClD;AAGA,SAAS,WAAW,aAAqB,KAAS;AAChD,MAAI,eAAe;AAAG,WAAO;AAC7B,QAAM,UAAU,cAAc,KAAK,MAAM,IAAI,QAAO,IAAK,GAAI;AAC7D,MAAI,WAAW;AAAG,WAAO;AACzB,MAAI,WAAW;AAAQ,WAAO,GAAG,KAAK,MAAM,UAAU,KAAM,CAAC,IAAI,KAAK,MAAO,UAAU,QAAU,IAAI,CAAC;AACtG,SAAO,GAAG,KAAK,MAAM,UAAU,IAAI,CAAC,IAAI,KAAK,MAAO,UAAU,OAAQ,EAAE,CAAC;AAC3E;AAEA,SAAS,KAAK,GAAS;AACrB,SAAO,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG;AAClC;AAGM,SAAU,iBACd,GACA,GACA,OAAwB,QAAM;AAE9B,QAAM,iBAAiB,KAAK,MAAM,EAAE,cAAc;AAClD,QAAM,WAAW,WAAW,cAAc;AAC1C,QAAM,gBAAgB,KAAK,MAAO,EAAE,iBAAiB,EAAE,oBAAqB,GAAG;AAC/E,QAAM,UAAU,GAAG,KAAK,GAAG,aAAa,aAAa,CAAC,IAAI,aAAa,EAAE,iBAAiB,CAAC,GAAG,GAAG;AACjG,QAAM,QAAQ,aAAM,KAAK,GAAG,KAAK,EAAE,IAAI,SAAQ,CAAE,CAAC,IAAI,KAAK,EAAE,IAAI,WAAU,CAAE,CAAC,GAAG,GAAG;AAEpF,QAAM,QAAQ,EAAE,eAAe,EAAE;AACjC,QAAM,SAAS,YAAY,KAAK;AAChC,QAAM,YAAY,SACd,GAAG,OAAO,KAAK,GAAG,OAAO,KAAK,GAAG,GAAG,IAAI,IAAI,GAAG,YAAY,SAAS,EAAE,CAAC,GAAG,GAAG,KAC7E;AACJ,QAAM,WAAW,aAAM,IAAI,GAAG,IAAI,GAAG,YAAY,EAAE,WAAW,EAAE,CAAC,GAAG,GAAG;AAEvE,QAAM,aAAa,KAAK,IAAI,GAAG,MAAM,KAAK,MAAM,EAAE,eAAe,CAAC;AAClE,QAAM,cAAc,KAAK,IAAI,GAAG,MAAM,KAAK,MAAM,EAAE,eAAe,CAAC;AACnE,QAAM,YAAY,YAAY,UAAU;AACxC,QAAM,aAAa,YAAY,WAAW;AAC1C,QAAM,aAAa,GAAG,KAAK,UAAK,KAAK,IAAI,GAAG,KAAK,MAAM,EAAE,YAAY,CAAC,CAAC,GAAG,GAAG;AAE7E,QAAM,UAAU,YACd,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,IAAI,QAAQ,WAAW,EAAE,EAAE,MAAM,OAAO,EAAE,IAAG,KAAM,MAAM,KACvF,EAAE;AAEJ,QAAM,YAAY,YAAY,EAAE,WAAW,EAAE;AAC7C,QAAM,UAAU,YAAY,EAAE,SAAS,EAAE;AAEzC,MAAI,SAAS,QAAQ;AACnB,UAAM,QAAQ;MACZ,aAAM,IAAI,GAAG,OAAO,GAAG,GAAG;MAC1B,GAAG,KAAK,SAAI,GAAG,IAAI,MAAM,GAAG,SAAS,GAAG,GAAG;MAC3C,YAAY,GAAG,QAAQ,GAAG,GAAG,GAAG,SAAS,KAAK;MAC9C,GAAG,QAAQ,GAAG,QAAQ,gBAAgB,CAAC,CAAC,IAAI,cAAc,IAAI,GAAG,IAAI,GAAG,OAAI,GAAG,IAAI,OAAO;MAC1F,GAAG,IAAI,KAAK,GAAG,IAAI,SAAS,GAAG,UAAU,IAAI,GAAG,IAAI,GAAG,OAAI,GAAG,IAAI,IAAI,KAAK,GAAG,IAAI,UAAU,GAAG,WAAW,IAAI,GAAG;MACjH;MACA,GAAI,EAAE,QAAQ,UAAU,CAAC,aAAM,OAAO,IAAI,YAAY,EAAE,OAAO,SAAS,EAAE,CAAC,GAAG,GAAG,EAAE,IAAI,CAAA;MACvF;;AAEF,WAAO,MAAM,KAAK,GAAG;EACvB;AAEA,QAAM,KAAK;IACT,YAAY,GAAG,QAAQ,GAAG,GAAG,GAAG,SAAS,KAAK;IAC9C,GAAG,QAAQ,GAAG,QAAQ,gBAAgB,EAAE,CAAC,IAAI,cAAc,IAAI,GAAG,IAAI,GAAG,OAAI,GAAG,IAAI,OAAO;IAC3F;IACA,aAAM,IAAI,GAAG,MAAM,IAAI,EAAE,QAAQ,QAAQ,CAAC,CAAC,GAAG,GAAG;IACjD,KAAK,GAAG;AAEV,QAAM,aAAa,EAAE,kBAAkB,EAAE;AACzC,QAAM,WAAW,aAAa,IAAI,KAAK,MAAO,EAAE,kBAAkB,MAAO,UAAU,IAAI;AACvF,QAAM,YAAY,WAAW,EAAE,kBAAkB,EAAE,GAAG;AACtD,QAAM,aAAa,WAAW,EAAE,kBAAkB,EAAE,GAAG;AACvD,QAAM,KAAK;IACT,GAAG,IAAI,KAAK,GAAG,IAAI,SAAS,GAAG,QAAQ,YAAY,CAAC,CAAC,IAAI,UAAU,IAAI,GAAG,MACvE,YAAY,GAAG,IAAI,IAAI,SAAS,IAAI,GAAG,KAAK;IAC/C,GAAG,IAAI,KAAK,GAAG,IAAI,UAAU,GAAG,QAAQ,aAAa,CAAC,CAAC,IAAI,WAAW,IAAI,GAAG,MAC1E,aAAa,GAAG,IAAI,IAAI,UAAU,IAAI,GAAG,KAAK;IACjD,aAAM,KAAK,SAAS,QAAQ,IAAI,GAAG;IACnC,KAAK,GAAG;AAEV,QAAM,KAAK;IACT,aAAM,IAAI,GAAG,OAAO,GAAG,GAAG;IAC1B,GAAG,KAAK,SAAI,GAAG,IAAI,MAAM,GAAG,SAAS,GAAG,GAAG,IAAI,IAAI,GAAG,OAAO,GAAG,GAAG;IACnE,UAAK,OAAO,GAAG,eAAe,EAAE,UAAU,CAAC,GAAG,GAAG;IACjD,GAAG,KAAK,IAAI,EAAE,UAAU,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,YAAY,GAAG,GAAG;IAC7D;IACA,KAAK,GAAG;AAEV,QAAM,QAAQ,CAAC,IAAI,IAAI,EAAE;AACzB,MAAI,EAAE,QAAQ;AACZ,QAAI,KAAK,aAAM,IAAI,GAAG,OAAO,SAAS,GAAG;AACzC,QAAI,EAAE,OAAO;AAAS,YAAM,IAAI,IAAI,IAAI,YAAY,EAAE,OAAO,SAAS,EAAE,CAAC,GAAG,GAAG;AAC/E,QAAI,EAAE,OAAO;AAAa,YAAM,GAAG,GAAG,GAAG,IAAI,QAAQ,GAAG,IAAI,YAAY,EAAE,OAAO,aAAa,EAAE,CAAC;AACjG,UAAM,KAAK,EAAE;EACf;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAMA,SAAS,OAAO,KAAa,MAAuB;AAClD,SAAO,aAAa,OAAO,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG;IAC/C,UAAU;IACV,OAAO,CAAC,UAAU,QAAQ,QAAQ;GACnC,EAAE,KAAI;AACT;AAQM,SAAU,0BAA0B,gBAAwB,WAAW,KAAS;AACpF,MAAI;AACF,UAAM,OAAO,SAAS,cAAc,EAAE;AACtC,UAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,QAAQ;AACzC,UAAM,SAAS,OAAO;AACtB,QAAI,UAAU;AAAG,aAAO;AACxB,UAAM,MAAM,OAAO,MAAM,MAAM;AAC/B,UAAM,KAAK,SAAS,gBAAgB,GAAG;AACvC,QAAI;AACF,eAAS,IAAI,KAAK,GAAG,QAAQ,KAAK;IACpC;AACE,gBAAU,EAAE;IACd;AACA,UAAM,OAAO,IAAI,SAAS,MAAM;AAChC,UAAM,UAAU,KAAK,MAAM,6BAA6B;AACxD,QAAI,CAAC,WAAW,QAAQ,WAAW;AAAG,aAAO;AAC7C,WAAO,QAAQ,QAAQ,SAAS,CAAC,EAAG,MAAM,uBAAuB,MAAM;EACzE,QAAQ;AACN,WAAO;EACT;AACF;AAGA,SAAS,sBAAmB;AAC1B,MAAI;AACF,QAAI,QAAQ,aAAa,SAAS;AAChC,YAAMA,OAAM,aAAa,YAAY,CAAA,GAAI,EAAE,UAAU,QAAQ,OAAO,CAAC,UAAU,QAAQ,QAAQ,EAAC,CAAE;AAClG,YAAMC,MAAKD,KAAI,MAAM,eAAe,KAAK,CAAA,GAAI;AAC7C,aAAOC,KAAI,IAAIA,KAAI;IACrB;AACA,UAAM,MAAM,aAAa,SAAS,CAAC,MAAM,QAAQ,GAAG;MAClD,UAAU;MACV,OAAO,CAAC,UAAU,QAAQ,QAAQ;KACnC;AACD,UAAM,IAAI,IAAI,MAAM,OAAO,EAAE,OAAO,OAAO,EAAE;AAC7C,WAAO,IAAI,IAAI,IAAI;EACrB,QAAQ;AACN,WAAO;EACT;AACF;AAGA,SAAS,kBAAkB,UAAgB;AACzC,MAAI;AACF,UAAM,MAAM,KAAK,UAAU,QAAQ,SAAS;AAC5C,UAAM,UAAU,YAAY,KAAK,EAAE,WAAW,KAAI,CAAE;AACpD,QAAI,OAAsB;AAC1B,eAAW,OAAO,SAAS;AACzB,YAAM,IAAI,OAAO,GAAG;AACpB,UAAI,CAAC,EAAE,SAAS,KAAK,KAAK,EAAE,SAAS,WAAW;AAAG;AACnD,UAAI,SAAS,QAAQ,IAAI;AAAM,eAAO;IACxC;AACA,WAAO,OAAO,KAAK,QAAQ,OAAO,GAAG,EAAE,MAAM,GAAG,EAAE,IAAG,IAAM;EAC7D,QAAQ;AACN,WAAO;EACT;AACF;AAGM,SAAU,wBAAwB,GAAmB,MAAM,oBAAI,KAAI,GAAE;AACzE,QAAM,MAAM,EAAE,OAAO,QAAQ,IAAG;AAEhC,MAAI,YAAY;AAChB,MAAI,UAAU;AACd,MAAI;AACF,UAAM,SAAS,OAAO,KAAK,CAAC,aAAa,gBAAgB,MAAM,CAAC;AAChE,gBAAY,WAAW,SAAS,aAAa,UAAU;AACvD,cAAU,OAAO,KAAK,CAAC,aAAa,WAAW,MAAM,CAAC,KAAK;EAC7D,QAAQ;EAER;AAEA,MAAI,SAAqC;AACzC,MAAI;AACF,QAAI,WAAW,KAAK,KAAK,UAAU,aAAa,CAAC,GAAG;AAClD,UAAI,UAAyB;AAC7B,UAAI;AACF,cAAM,MAAM,KAAK,MACf,aAAa,KAAK,KAAK,gBAAgB,cAAc,QAAQ,cAAc,GAAG,MAAM,CAAC;AAEvF,kBAAU,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU;MAC5D,QAAQ;MAER;AACA,eAAS,EAAE,SAAS,aAAa,kBAAkB,GAAG,EAAC;IACzD;EACF,QAAQ;AACN,aAAS;EACX;AAEA,MAAI,cAA6B;AACjC,MAAI,EAAE,gBAAgB,WAAW,EAAE,gBAAgB;AACjD,UAAM,UAAU,0BAA0B,EAAE,cAAc;AAC1D,QAAI,YAAY;AAAa,oBAAc;EAC7C;AAEA,SAAO;IACL;IACA;IACA,cAAc,oBAAmB;IACjC;IACA;IACA;;AAEJ;AAOM,SAAU,kBAAkB,MAAa;AAC7C,SAAO,qCAAqC,OAAO,UAAU,EAAE;AACjE;AAeM,SAAU,iBACd,UACA,MACA,QAAQ,OAAK;AAEb,QAAM,UAAU,kBAAkB,IAAI;AACtC,QAAM,UAAU,SAAS;AACzB,QAAM,aAAa,OAAO,SAAS,YAAY,WAAW,QAAQ,UAAU;AAC5E,QAAM,SACJ,eAAe,kBAAkB,KAAK,KAAK,eAAe,kBAAkB,IAAI;AAGlF,MAAI,eAAe,WAAW,SAAS,SAAS,WAAW;AACzD,WAAO,EAAE,UAAU,UAAU,QAAQ,eAAc;EACrD;AACA,MAAI,WAAW,CAAC,UAAU,CAAC,OAAO;AAChC,WAAO,EAAE,UAAU,UAAU,QAAQ,iBAAiB,UAAU,cAAc,KAAK,UAAU,OAAO,EAAC;EACvG;AACA,SAAO;IACL,UAAU,EAAE,GAAG,UAAU,YAAY,EAAE,MAAM,WAAW,QAAO,EAAE;IACjE,QAAQ;;AAEZ;AAWA,eAAsB,iBACpB,MACA,UACA,KACA,KAAwB;AAExB,MAAI,KAAK,CAAC,MAAM,WAAW;AACzB,UAAM,OAAO,KAAK,SAAS,QAAQ;AACnC,UAAM,QAAQ,KAAK,SAAS,SAAS;AACrC,UAAM,eAAe,KAAK,UAAU,WAAW,eAAe;AAC9D,UAAM,eAAe,WAAW,YAAY,IAAI,aAAa,cAAc,MAAM,IAAI;AACrF,UAAM,SAAS,cAAc,YAAY;AACzC,UAAM,SAAS,iBAAiB,QAAQ,MAAM,KAAK;AACnD,QAAI,OAAO,WAAW,aAAa;AACjC,YAAM,EAAE,WAAW,cAAa,IAAK,MAAM,OAAO,IAAS;AAC3D,gBAAU,KAAK,UAAU,SAAS,GAAG,EAAE,WAAW,KAAI,CAAE;AACxD,oBAAc,cAAc,kBAAkB,OAAO,QAAQ,GAAG,MAAM;IACxE;AACA,UAAM,UAAmC;MACvC,QAAQ,OAAO;MACf;MACA,GAAI,OAAO,WAAW,kBAAkB,EAAE,SAAS,kBAAkB,IAAI,EAAC,IAAK,CAAA;MAC/E,GAAI,OAAO,aAAa,SAAY,EAAE,UAAU,OAAO,SAAQ,IAAK,CAAA;;AAEtE,QAAI,KAAK,UAAU,SAAS,MAAM,CAAC,IAAI,IAAI;AAC3C,QAAI,OAAO,WAAW,iBAAiB;AACrC,UACE,mHAA8G;IAElH;AACA,WAAO;EACT;AAEA,QAAM,OAAwB,KAAK,CAAC,MAAM,SAAS,SAAS;AAC5D,MAAI,QAAQ,MAAM,OAAO;AAEvB,QACE,+IAC8D;AAEhE,WAAO;EACT;AACA,MAAI,MAAM;AACV,MAAI;AACF,UAAM,aAAa,GAAG,MAAM;EAC9B,QAAQ;AACN,UAAM;EACR;AACA,MAAI,CAAC,IAAI,KAAI;AAAI,WAAO;AACxB,MAAI;AACJ,MAAI;AACF,WAAO,qBAAqB,GAAG;EACjC,QAAQ;AACN,WAAO;EACT;AACA,MAAI,iBAAiB,MAAM,wBAAwB,IAAI,GAAG,IAAI,CAAC;AAC/D,SAAO;AACT;","names":["out","n"]}