@azure-id/orc 2.0.2 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +455 -0
- package/README-id.md +52 -23
- package/README.md +36 -20
- package/bin/build-agents.js +205 -206
- package/bin/clear-logs.js +620 -0
- package/bin/cli.js +47417 -46420
- package/bin/fix.js +459 -0
- package/bin/gotcha.js +288 -8
- package/bin/habit.js +1538 -1453
- package/bin/mockrun-catalog.js +3 -0
- package/bin/onboarding-content.js +2 -1
- package/bin/pricing.json +28 -21
- package/bin/trace-write.js +869 -657
- package/bin/verify-contracts.js +5552 -5471
- package/bin/verify-package.js +716 -705
- package/bin/webui/api.js +125 -6
- package/bin/webui/css/panels/hookui.css +79 -0
- package/bin/webui/fixtures/behaviour.js +30 -0
- package/bin/webui/fixtures/extra.js +2036 -2036
- package/bin/webui/fixtures/flow.js +81 -81
- package/bin/webui/fixtures/hookui.js +506 -431
- package/bin/webui/fixtures/index.js +32 -3
- package/bin/webui/fixtures/knowledge.js +2 -2
- package/bin/webui/fixtures/maintenance.js +123 -42
- package/bin/webui/fixtures/rules.js +1007 -1002
- package/bin/webui/fixtures/settings.js +6 -7
- package/bin/webui/fixtures/stats.js +9 -6
- package/bin/webui/fixtures/wait.js +98 -97
- package/bin/webui/i18n/en/behaviour.json +13 -0
- package/bin/webui/i18n/en/hookui.json +290 -194
- package/bin/webui/i18n/en/maintenance.json +71 -52
- package/bin/webui/i18n/en/settings.json +60 -60
- package/bin/webui/i18n/id/behaviour.json +13 -0
- package/bin/webui/i18n/id/hookui.json +290 -194
- package/bin/webui/i18n/id/maintenance.json +71 -52
- package/bin/webui/i18n/id/settings.json +60 -60
- package/bin/webui/js/05-banners.js +181 -172
- package/bin/webui/js/06-edit.js +193 -183
- package/bin/webui/js/panels/behaviour.js +77 -12
- package/bin/webui/js/panels/hookui.js +2196 -1691
- package/bin/webui/js/panels/maintenance.js +336 -235
- package/bin/webui/js/panels/settings.js +726 -719
- package/mock-run/INDEX.md +1 -0
- package/mock-run/a-normal-day.md +2 -2
- package/mock-run/extra-slots.md +177 -177
- package/mock-run/habits.md +1 -1
- package/mock-run/orc-aftermath.md +392 -392
- package/mock-run/orc-budget.md +29 -33
- package/mock-run/orc-cli.md +200 -200
- package/mock-run/orc-explain.md +86 -86
- package/mock-run/orc-extra.md +389 -392
- package/mock-run/orc-fast.md +2 -2
- package/mock-run/orc-fix.md +87 -0
- package/mock-run/orc-pattern.md +112 -112
- package/mock-run/orc-quick.md +146 -146
- package/mock-run/orc.md +4 -4
- package/package.json +1 -1
- package/templates/agents/MODEL-MAPPING.md +176 -173
- package/templates/agents/orc-analyze-mini-sonnet-5-high.md +2 -2
- package/templates/agents/{orc-claude-writer-opus-5-med.md → orc-claude-writer-opus-5-low.md} +4 -4
- package/templates/agents/orc-executor-haiku-4-5.md +1 -1
- package/templates/agents/orc-executor-opus-5-low.md +1 -1
- package/templates/agents/orc-executor-sonnet-4-6-high.md +1 -1
- package/templates/agents/orc-executor-sonnet-4-6-med.md +1 -1
- package/templates/agents/orc-executor-sonnet-5-high.md +2 -2
- package/templates/agents/orc-executor-sonnet-5-low.md +155 -0
- package/templates/agents/orc-executor-sonnet-5-med.md +155 -0
- package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +2 -2
- package/templates/agents/orc-planner-mini-sonnet-5-high.md +2 -2
- package/templates/agents/{orc-recon-sonnet-4-6-med.md → orc-recon-sonnet-5-med.md} +4 -4
- package/templates/agents/orc-retro-sonnet-5-high.md +2 -2
- package/templates/agents/{orc-reviewer-opus-5-med.md → orc-reviewer-opus-5-low.md} +4 -4
- package/templates/agents/{orc-wiki-scanner-opus-5-med.md → orc-wiki-scanner-opus-5-low.md} +3 -3
- package/templates/agents/orc-wiki-scanner-sonnet-5-high.md +2 -2
- package/templates/commands/orc-fast.md +10 -10
- package/templates/commands/orc-fix.md +18 -0
- package/templates/commands/orc-wiki.md +64 -64
- package/templates/hooks/README.md +75 -7
- package/templates/hooks/orc-session-hook.js +78 -3
- package/templates/hooks/orc-statusline-render.js +317 -23
- package/templates/hooks/orc-statusline.js +162 -49
- package/templates/hooks/orc-subagent-line.js +23 -5
- package/templates/hooks/orc-weather-fetch.js +123 -0
- package/templates/skills/_shared/extra-dispatch.md +1331 -1331
- package/templates/skills/_shared/gotchas.md +27 -7
- package/templates/skills/_shared/habits.md +2 -2
- package/templates/skills/_shared/opus5-only.md +137 -137
- package/templates/skills/_shared/phases/review.md +2 -2
- package/templates/skills/_shared/phases/trace-verbs.md +17 -3
- package/templates/skills/_shared/phases/trace.md +136 -136
- package/templates/skills/_shared/review-slice.md +28 -1
- package/templates/skills/_shared/wait.md +1 -0
- package/templates/skills/orc/README.md +2 -2
- package/templates/skills/orc/SKILL.md +227 -227
- package/templates/skills/orc/config.md +25 -27
- package/templates/skills/orc/examples/full-run-mock.md +74 -73
- package/templates/skills/orc/references/effort-and-mode.md +222 -222
- package/templates/skills/orc/references/preflight-report.md +3 -3
- package/templates/skills/orc/references/ultra-mode.md +1 -1
- package/templates/skills/orc/schemas/checkpoint.md +122 -122
- package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +3 -3
- package/templates/skills/orc/subskills/orc-review-verify/core.md +1 -1
- package/templates/skills/orc-analyze/SKILL.md +4 -2
- package/templates/skills/orc-claude/SKILL.md +14 -14
- package/templates/skills/orc-claude/examples/claude-run-mock.md +65 -65
- package/templates/skills/orc-diy/SKILL.md +5 -5
- package/templates/skills/orc-fast/SKILL.md +210 -208
- package/templates/skills/orc-fix/SKILL.md +110 -0
- package/templates/skills/orc-grill/SKILL.md +232 -232
- package/templates/skills/orc-grill/references/grill-doc.md +1 -1
- package/templates/skills/orc-mini/SKILL.md +4 -4
- package/templates/skills/orc-mini/examples/mini-run-mock.md +4 -2
- package/templates/skills/orc-quick/README.md +536 -536
- package/templates/skills/orc-quick/SKILL.md +2 -2
- package/templates/skills/orc-quick/references/context-doc.md +145 -145
- package/templates/skills/orc-quick/references/dispatch-gate.md +220 -219
- package/templates/skills/orc-quick/references/gh-mode.md +4 -2
- package/templates/skills/orc-retro/SKILL.md +1 -1
- package/templates/skills/orc-retro/examples/retro-mock.md +16 -16
- package/templates/skills/orc-wiki/README.md +325 -325
- package/templates/skills/orc-wiki/SKILL.md +195 -195
- package/templates/skills/orc-wiki/examples/wiki-run-mock.md +37 -37
- package/templates/skills/orc-wiki/references/extra.md +2 -2
- package/templates/skills/orc-wiki/references/partial-refresh.md +276 -271
- package/templates/skills/orc-wiki/references/phases/phase-0.md +1 -1
- package/templates/skills/orc-wiki/references/phases/phase-2.md +3 -3
- package/templates/skills/orc-wiki/references/phases/phase-3c.md +3 -2
- package/templates/agents/orc-claude-writer-opus-4-8-high.md +0 -48
- package/templates/agents/orc-wiki-scanner-opus-4-8-high.md +0 -74
|
@@ -95,6 +95,10 @@ const BINDINGS = {
|
|
|
95
95
|
"cost.usd": (c) => num(c.payload.cost && c.payload.cost.total_cost_usd),
|
|
96
96
|
"cost.rate_usd_h": (c) => ratePerHour(c),
|
|
97
97
|
"cost.wall_ms": (c) => num(c.payload.cost && c.payload.cost.total_duration_ms),
|
|
98
|
+
// Whole minutes, for the api-time / wall-time parts. A raw ms count read
|
|
99
|
+
// as minutes is `api 960000m`.
|
|
100
|
+
"cost.api_min": (c) => mins(num(c.payload.cost && c.payload.cost.total_api_duration_ms)),
|
|
101
|
+
"cost.wall_min": (c) => mins(num(c.payload.cost && c.payload.cost.total_duration_ms)),
|
|
98
102
|
"cost.api_ms": (c) => num(c.payload.cost && c.payload.cost.total_api_duration_ms),
|
|
99
103
|
"cost.api_ratio": (c) => ratioPct(num(c.payload.cost && c.payload.cost.total_api_duration_ms), num(c.payload.cost && c.payload.cost.total_duration_ms)),
|
|
100
104
|
"cost.lines_added": (c) => num(c.payload.cost && c.payload.cost.total_lines_added),
|
|
@@ -292,6 +296,16 @@ const BINDINGS = {
|
|
|
292
296
|
"clock.now": (c) => c.now,
|
|
293
297
|
"session.elapsed_min": (c) => (c.ledger.started_at ? Math.max(0, Math.round((c.now - c.ledger.started_at) / 60000)) : null),
|
|
294
298
|
"static.text": () => null, // supplied per-op by `lit`; present so the set is total
|
|
299
|
+
|
|
300
|
+
// — weather (v2.0.4): a cache file the detached fetcher writes ————————
|
|
301
|
+
// The hook never waits on the network; these read what the last fetch left.
|
|
302
|
+
"weather.temp": (c) => {
|
|
303
|
+
const w = c.scan.weather;
|
|
304
|
+
if (!w) return null;
|
|
305
|
+
return num(c.param && c.param.units === "us" ? w.temp_f : w.temp_c);
|
|
306
|
+
},
|
|
307
|
+
"weather.state": (c) => (c.scan.weather && c.scan.weather.state) || null,
|
|
308
|
+
"weather.desc": (c) => (c.scan.weather && c.scan.weather.desc) || null,
|
|
295
309
|
};
|
|
296
310
|
|
|
297
311
|
// ── small helpers the bindings share ────────────────────────────────────────
|
|
@@ -468,6 +482,20 @@ function trendOf(series) {
|
|
|
468
482
|
}
|
|
469
483
|
|
|
470
484
|
// ── Number formatting (design-language.md §4) ───────────────────────────────
|
|
485
|
+
|
|
486
|
+
function mins(ms) {
|
|
487
|
+
return ms == null ? null : Math.round(ms / 60000);
|
|
488
|
+
}
|
|
489
|
+
// A `tf` on a val op reads the number as epoch ms and prints LOCAL time. The
|
|
490
|
+
// raw epoch is never a clock.
|
|
491
|
+
function formatTime(ms, tf) {
|
|
492
|
+
const d = new Date(ms);
|
|
493
|
+
const p = (n) => (n < 10 ? "0" : "") + n;
|
|
494
|
+
const h = d.getHours();
|
|
495
|
+
if (tf === "h:mm a") return (h % 12 || 12) + ":" + p(d.getMinutes()) + " " + (h < 12 ? "am" : "pm");
|
|
496
|
+
const s = p(h) + ":" + p(d.getMinutes());
|
|
497
|
+
return tf === "HH:mm:ss" ? s + ":" + p(d.getSeconds()) : s;
|
|
498
|
+
}
|
|
471
499
|
function formatValue(v, f) {
|
|
472
500
|
if (v == null) return null;
|
|
473
501
|
if (typeof v !== "number") return String(v);
|
|
@@ -524,6 +552,72 @@ function truncate(s, max, mode) {
|
|
|
524
552
|
return s.slice(0, max - 1) + "…";
|
|
525
553
|
}
|
|
526
554
|
|
|
555
|
+
// ── Cell width and styled text (v2.0.4) ─────────────────────────────────────
|
|
556
|
+
// Terminal cells, not string length. Only a line that uses flex, align or a
|
|
557
|
+
// caption pays for this; every other line never measures.
|
|
558
|
+
const SGR_RE = new RegExp(ESC + "\\[[0-9;?]*[A-Za-z]", "g");
|
|
559
|
+
const OSC8_RE = new RegExp(ESC + "\\]8;;[^" + BEL + "]*" + BEL, "g");
|
|
560
|
+
const SENT_RE = /\u0000[^\u0000]*\u0000/g;
|
|
561
|
+
const WIDE = [
|
|
562
|
+
[0x1100, 0x115f], [0x231a, 0x231b], [0x23e9, 0x23ec], [0x23f0, 0x23f0], [0x23f3, 0x23f3],
|
|
563
|
+
[0x25fd, 0x25fe], [0x2614, 0x2615], [0x2648, 0x2653], [0x267f, 0x267f], [0x2693, 0x2693],
|
|
564
|
+
[0x26a1, 0x26a1], [0x26aa, 0x26ab], [0x26bd, 0x26be], [0x26c4, 0x26c5], [0x26ce, 0x26ce],
|
|
565
|
+
[0x26d4, 0x26d4], [0x26ea, 0x26ea], [0x26f2, 0x26f3], [0x26f5, 0x26f5], [0x26fa, 0x26fa],
|
|
566
|
+
[0x26fd, 0x26fd], [0x2705, 0x2705], [0x270a, 0x270b], [0x2728, 0x2728], [0x274c, 0x274c],
|
|
567
|
+
[0x274e, 0x274e], [0x2753, 0x2755], [0x2757, 0x2757], [0x2795, 0x2797], [0x27b0, 0x27b0],
|
|
568
|
+
[0x27bf, 0x27bf], [0x2b1b, 0x2b1c], [0x2b50, 0x2b50], [0x2b55, 0x2b55], [0x2e80, 0xa4cf],
|
|
569
|
+
[0xac00, 0xd7a3], [0xf900, 0xfaff], [0xfe30, 0xfe4f], [0xff00, 0xff60], [0xffe0, 0xffe6],
|
|
570
|
+
[0x1f000, 0x1faff],
|
|
571
|
+
];
|
|
572
|
+
function cellWidth(s) {
|
|
573
|
+
const t = String(s == null ? "" : s).replace(OSC8_RE, "").replace(SGR_RE, "").replace(SENT_RE, "");
|
|
574
|
+
let n = 0;
|
|
575
|
+
for (const ch of t) {
|
|
576
|
+
const cp = ch.codePointAt(0);
|
|
577
|
+
if (cp < 0x300) {
|
|
578
|
+
n += 1;
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
// Zero width: combining marks, the ZW/direction marks, VS16.
|
|
582
|
+
if ((cp >= 0x300 && cp <= 0x36f) || (cp >= 0x1ab0 && cp <= 0x1aff) || (cp >= 0x1dc0 && cp <= 0x1dff) ||
|
|
583
|
+
(cp >= 0x20d0 && cp <= 0x20ff) || (cp >= 0xfe20 && cp <= 0xfe2f) || (cp >= 0x200b && cp <= 0x200f) || cp === 0xfe0f)
|
|
584
|
+
continue;
|
|
585
|
+
let w = 1;
|
|
586
|
+
for (const r of WIDE) {
|
|
587
|
+
if (cp < r[0]) break;
|
|
588
|
+
if (cp <= r[1]) {
|
|
589
|
+
w = 2;
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
n += w;
|
|
594
|
+
}
|
|
595
|
+
return n;
|
|
596
|
+
}
|
|
597
|
+
function styleMap(from, to) {
|
|
598
|
+
const m = {};
|
|
599
|
+
const a = [...from];
|
|
600
|
+
const b = [...to];
|
|
601
|
+
for (let i = 0; i < a.length; i++) {
|
|
602
|
+
m[a[i]] = b[i];
|
|
603
|
+
m[a[i].toUpperCase()] = b[i];
|
|
604
|
+
}
|
|
605
|
+
return m;
|
|
606
|
+
}
|
|
607
|
+
const STYLES = {
|
|
608
|
+
small: styleMap("abcdefghijklmnopqrstuvwxyz", "ᴀʙᴄᴅᴇꜰɢʜɪᴊᴋʟᴍɴᴏᴘǫʀꜱᴛᴜᴠᴡxʏᴢ"),
|
|
609
|
+
super: styleMap("0123456789+-=()abcdefghijklmnoprstuvwxyz", "⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛʷˣʸᶻ"),
|
|
610
|
+
sub: styleMap("0123456789+-=()aehijklmnoprstuvx", "₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓ"),
|
|
611
|
+
};
|
|
612
|
+
// A character with no styled form passes through unchanged.
|
|
613
|
+
function styleText(s, style) {
|
|
614
|
+
const m = STYLES[style];
|
|
615
|
+
if (!m || s == null) return s;
|
|
616
|
+
let out = "";
|
|
617
|
+
for (const ch of String(s)) out += m[ch] || ch;
|
|
618
|
+
return out;
|
|
619
|
+
}
|
|
620
|
+
|
|
527
621
|
// ── The renderers ──────────────────────────────────────────────────────────
|
|
528
622
|
// Every one takes a value, a glyph set and a width, and returns cells. None of
|
|
529
623
|
// them decides a colour: colour arrives as precomputed `sgr` ops around them.
|
|
@@ -532,6 +626,11 @@ function glyphs(gs, ascii) {
|
|
|
532
626
|
return ascii && gs.ascii ? Object.assign({}, gs, gs.ascii) : gs;
|
|
533
627
|
}
|
|
534
628
|
|
|
629
|
+
// D3: a value above zero fills at least one cell. 6% on ten cells is not 0%.
|
|
630
|
+
function fullCells(p, w) {
|
|
631
|
+
const n = Math.floor((p / 100) * w);
|
|
632
|
+
return Math.min(w, p > 0 ? Math.max(1, n) : n);
|
|
633
|
+
}
|
|
535
634
|
function barCells(pct, g, w, kind) {
|
|
536
635
|
const p = Math.max(0, Math.min(100, pct == null ? 0 : pct));
|
|
537
636
|
const exact = (p / 100) * w;
|
|
@@ -544,7 +643,8 @@ function barCells(pct, g, w, kind) {
|
|
|
544
643
|
if (full < w && idx >= 0) out += g.part[idx];
|
|
545
644
|
return padCells(out, w, g.empty);
|
|
546
645
|
}
|
|
547
|
-
|
|
646
|
+
const n = fullCells(p, w);
|
|
647
|
+
out = g.fill.repeat(n) + g.empty.repeat(Math.max(0, w - n));
|
|
548
648
|
return out.slice(0, w);
|
|
549
649
|
}
|
|
550
650
|
function padCells(s, w, filler) {
|
|
@@ -563,7 +663,7 @@ function microCell(pct, g) {
|
|
|
563
663
|
}
|
|
564
664
|
function dotsCells(pct, g, w) {
|
|
565
665
|
const p = Math.max(0, Math.min(100, pct == null ? 0 : pct));
|
|
566
|
-
const on = Math.round((p / 100) * w);
|
|
666
|
+
const on = Math.min(w, p > 0 ? Math.max(1, Math.round((p / 100) * w)) : 0);
|
|
567
667
|
return g.on.repeat(on) + g.off.repeat(Math.max(0, w - on));
|
|
568
668
|
}
|
|
569
669
|
function markerCells(pct, g, w) {
|
|
@@ -607,9 +707,12 @@ function trafficCells(idx, n, g) {
|
|
|
607
707
|
// advances while the session is active and FREEZES when it is idle, which is
|
|
608
708
|
// true and is the point. ORC_STATUSLINE_MOTION=0 REMOVES it — a frozen frame of
|
|
609
709
|
// a cycling animation is a bug that looks like a hang.
|
|
610
|
-
|
|
710
|
+
// v2.1.0 (DE-23): with a refresh timer the hook passes `step` (one per run), and
|
|
711
|
+
// the frame is "the one after the last one shown" — never skipped, never repeated.
|
|
712
|
+
function motifFrame(set, now, ms, motion, step) {
|
|
611
713
|
if (!set || !set.length) return "";
|
|
612
714
|
if (!motion) return set[0];
|
|
715
|
+
if (Number.isInteger(step)) return set[step % set.length];
|
|
613
716
|
return set[Math.floor(now / (ms || 200)) % set.length];
|
|
614
717
|
}
|
|
615
718
|
|
|
@@ -677,38 +780,114 @@ function render(prog, ctx) {
|
|
|
677
780
|
scan: ctx.scan || {},
|
|
678
781
|
derived: ctx.derived || { verdict: null, reasons: [], version: null },
|
|
679
782
|
now: ctx.now || Date.now(),
|
|
783
|
+
// v2.1.0 (DE-23): one step per run when a refresh timer is set; else null.
|
|
784
|
+
step: Number.isInteger(ctx.step) ? ctx.step : null,
|
|
680
785
|
};
|
|
681
786
|
const hasRun = !!(rctx.scan.phase || rctx.scan.spawns);
|
|
682
787
|
|
|
683
788
|
// H4 PLAN — one lookup, no measuring. The last plan whose `cols` ≤ COLUMNS
|
|
684
789
|
// wins; its ids are dropped.
|
|
685
790
|
const dropped = new Set();
|
|
686
|
-
|
|
791
|
+
// A plan applies only when the terminal is narrower than the widest line.
|
|
792
|
+
const need = (prog.lines || []).reduce((m, l) => Math.max(m, Number(l && l.static_width) || 0), 0);
|
|
793
|
+
if (cols && Array.isArray(prog.plans) && !(need && cols >= need)) {
|
|
687
794
|
let chosen = null;
|
|
688
795
|
for (const p of prog.plans) if (p.cols <= cols && (!chosen || p.cols >= chosen.cols)) chosen = p;
|
|
796
|
+
// Narrower than every plan: the smallest real plan (the most it drops).
|
|
797
|
+
if (need && (!chosen || !chosen.cols)) {
|
|
798
|
+
for (const p of prog.plans) if (p.cols > 0 && (!chosen || !chosen.cols || p.cols < chosen.cols)) chosen = p;
|
|
799
|
+
}
|
|
689
800
|
if (chosen) for (const id of chosen.drop) dropped.add(id);
|
|
690
801
|
}
|
|
691
802
|
|
|
692
803
|
const lines = [];
|
|
693
804
|
for (const line of prog.lines || []) {
|
|
694
805
|
let out = "";
|
|
806
|
+
const o = { color, ascii, motion, cols, hasRun, dropped, flex: false, caps: [] };
|
|
695
807
|
for (const op of line.ops || []) {
|
|
696
808
|
// H5 FAILURE ISOLATION — each item renders inside its own try. One bad
|
|
697
809
|
// component never takes the bar down; it emits its unknown form and the
|
|
698
810
|
// rest of the line survives.
|
|
699
811
|
try {
|
|
700
|
-
out += walk(op, prog, rctx,
|
|
812
|
+
out += walk(op, prog, rctx, o, errors);
|
|
701
813
|
} catch (e) {
|
|
702
814
|
errors.push(String((e && e.message) || e));
|
|
703
815
|
}
|
|
704
816
|
}
|
|
705
|
-
|
|
817
|
+
let text = line.prefix ? line.prefix + out : out;
|
|
818
|
+
// A line with no flex, no align and no caption stops here, unmeasured.
|
|
819
|
+
const align = line.align === "right" || line.align === "center" ? line.align : null;
|
|
820
|
+
if (!o.flex && !o.caps.length && !(align && cols > 0)) {
|
|
821
|
+
lines.push(text);
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
if (o.flex) text = applyFlex(text, cols);
|
|
825
|
+
else if (align && cols > 0) {
|
|
826
|
+
const gap = cols - cellWidth(text);
|
|
827
|
+
if (gap > 0) text = " ".repeat(align === "right" ? gap : Math.floor(gap / 2)) + text;
|
|
828
|
+
}
|
|
829
|
+
if (!o.caps.length) {
|
|
830
|
+
lines.push(text);
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
const rows = captionRows(text, o.caps);
|
|
834
|
+
if (rows.above) lines.push(rows.above);
|
|
835
|
+
lines.push(rows.line);
|
|
836
|
+
if (rows.below) lines.push(rows.below);
|
|
706
837
|
}
|
|
707
838
|
// A trailing empty line is not a line. It would print as a blank row.
|
|
708
839
|
while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
|
|
840
|
+
// D1: Claude Code trims every row, so a caption row or an indent that starts
|
|
841
|
+
// with a space moves to column 0. U+2800 is one blank cell it keeps. A row
|
|
842
|
+
// that starts with ESC is not trimmed and stays as it is.
|
|
843
|
+
if (!ascii) for (let i = 0; i < lines.length; i++) if (lines[i][0] === " ") lines[i] = "\u2800" + lines[i].slice(1);
|
|
709
844
|
return { text: lines.join("\n"), errors };
|
|
710
845
|
}
|
|
711
846
|
|
|
847
|
+
// FLEX: slack = cols − the line's width, shared by weight (the remainder to the
|
|
848
|
+
// last); each flex prints max(min, share). cols 0 → each prints its min. One
|
|
849
|
+
// column stays free: a row exactly COLUMNS wide can wrap (D5).
|
|
850
|
+
function applyFlex(text, cols) {
|
|
851
|
+
const re = /\u0000F(\d+),(\d+)\u0000/g;
|
|
852
|
+
const fl = [];
|
|
853
|
+
let m;
|
|
854
|
+
while ((m = re.exec(text))) fl.push({ w: Number(m[1]), min: Number(m[2]) });
|
|
855
|
+
const slack = cols > 0 ? Math.max(0, cols - cellWidth(text) - 1) : 0;
|
|
856
|
+
const tot = fl.reduce((a, f) => a + f.w, 0) || 1;
|
|
857
|
+
let used = 0;
|
|
858
|
+
const n = fl.map((f, i) => {
|
|
859
|
+
const share = i === fl.length - 1 ? slack - used : Math.floor((slack * f.w) / tot);
|
|
860
|
+
used += share;
|
|
861
|
+
return Math.max(f.min, cols > 0 ? share : 0);
|
|
862
|
+
});
|
|
863
|
+
let i = 0;
|
|
864
|
+
return text.replace(re, () => " ".repeat(n[i++]));
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// CAPTIONS: one row above and one below, each placed left to right; a caption
|
|
868
|
+
// that would overlap the previous one starts one space after it.
|
|
869
|
+
function captionRows(text, caps) {
|
|
870
|
+
const re = /\u0000C(\d+)\u0000/g;
|
|
871
|
+
const at = [];
|
|
872
|
+
let m;
|
|
873
|
+
while ((m = re.exec(text))) at.push({ cap: caps[Number(m[1])], col: cellWidth(text.slice(0, m.index)) });
|
|
874
|
+
const row = { above: "", below: "" };
|
|
875
|
+
const cur = { above: 0, below: 0 };
|
|
876
|
+
const any = { above: false, below: false };
|
|
877
|
+
for (const a of at) {
|
|
878
|
+
const k = a.cap;
|
|
879
|
+
const col = a.col + k.off;
|
|
880
|
+
let start = col;
|
|
881
|
+
if (k.align === "center") start = col + Math.max(0, Math.floor((k.content - k.w) / 2));
|
|
882
|
+
else if (k.align === "right") start = col + Math.max(0, k.content - k.w);
|
|
883
|
+
if (any[k.pos] && start < cur[k.pos] + 1) start = cur[k.pos] + 1;
|
|
884
|
+
row[k.pos] += " ".repeat(Math.max(0, start - cur[k.pos])) + k.text;
|
|
885
|
+
cur[k.pos] = Math.max(start, cur[k.pos]) + k.w;
|
|
886
|
+
any[k.pos] = true;
|
|
887
|
+
}
|
|
888
|
+
return { above: row.above, line: text.replace(re, ""), below: row.below };
|
|
889
|
+
}
|
|
890
|
+
|
|
712
891
|
function walk(op, prog, c, o, errors) {
|
|
713
892
|
switch (op.op) {
|
|
714
893
|
case "lit":
|
|
@@ -735,7 +914,9 @@ function walk(op, prog, c, o, errors) {
|
|
|
735
914
|
return OSC8 + url + BEL + inner + OSC8 + BEL;
|
|
736
915
|
}
|
|
737
916
|
case "flex":
|
|
738
|
-
|
|
917
|
+
// A sentinel; render() swaps it for its share of the slack.
|
|
918
|
+
o.flex = true;
|
|
919
|
+
return "\u0000F" + Math.max(1, op.w || 1) + "," + Math.max(op.min || 0, 0) + "\u0000";
|
|
739
920
|
case "val":
|
|
740
921
|
return renderVal(op, prog, c, o);
|
|
741
922
|
case "bar":
|
|
@@ -746,6 +927,8 @@ function walk(op, prog, c, o, errors) {
|
|
|
746
927
|
return renderMotif(op, prog, c, o);
|
|
747
928
|
case "series":
|
|
748
929
|
return renderSeries(op, prog, c, o);
|
|
930
|
+
case "sprite":
|
|
931
|
+
return renderSprite(op, c, o);
|
|
749
932
|
default:
|
|
750
933
|
throw new Error("unknown op: " + op.op);
|
|
751
934
|
}
|
|
@@ -760,7 +943,7 @@ function children(op, prog, c, o, errors) {
|
|
|
760
943
|
function walkItem(op, prog, c, o, errors) {
|
|
761
944
|
if (o.dropped.has(op.id)) return "";
|
|
762
945
|
let probe = null;
|
|
763
|
-
if (op.b) probe = bind(op.b, c);
|
|
946
|
+
if (op.b) probe = bind(op.b, op.p ? Object.assign({}, c, { param: op.p }) : c);
|
|
764
947
|
if (hidden(op, probe, { cols: o.cols, hasRun: o.hasRun })) return "";
|
|
765
948
|
let body;
|
|
766
949
|
try {
|
|
@@ -773,13 +956,33 @@ function walkItem(op, prog, c, o, errors) {
|
|
|
773
956
|
if (body === "" && !op.draw_empty) return "";
|
|
774
957
|
const l = " ".repeat(op.pad_l || 0);
|
|
775
958
|
const r = " ".repeat(op.pad_r || 0);
|
|
776
|
-
|
|
959
|
+
const all = l + body + r;
|
|
960
|
+
// A CAPTION rides as a sentinel at the item's start; render() measures the
|
|
961
|
+
// column once the line is final (after flex and align) and strips it.
|
|
962
|
+
if (op.cap && body !== "" && o.caps) {
|
|
963
|
+
let t = "";
|
|
964
|
+
for (const ch of op.cap.ops || []) t += walk(ch, prog, c, o, errors);
|
|
965
|
+
if (t === "") return all;
|
|
966
|
+
const sw = op.sw || 0;
|
|
967
|
+
const pl = op.pad_l || 0;
|
|
968
|
+
o.caps.push({
|
|
969
|
+
pos: op.cap.pos === "above" ? "above" : "below",
|
|
970
|
+
align: op.cap.align || "left",
|
|
971
|
+
text: t,
|
|
972
|
+
w: cellWidth(t),
|
|
973
|
+
off: sw + pl,
|
|
974
|
+
content: Math.max(0, cellWidth(all) - sw - pl - (op.pad_r || 0)),
|
|
975
|
+
});
|
|
976
|
+
return "\u0000C" + (o.caps.length - 1) + "\u0000" + all;
|
|
977
|
+
}
|
|
978
|
+
return all;
|
|
777
979
|
}
|
|
778
980
|
|
|
779
981
|
function renderVal(op, prog, c, o) {
|
|
780
982
|
const f = prog.formats[op.f] || {};
|
|
781
|
-
|
|
782
|
-
|
|
983
|
+
// `p` is the op's own parameter (the `config` part's key).
|
|
984
|
+
const v = bind(op.b, op.p ? Object.assign({}, c, { param: op.p }) : c);
|
|
985
|
+
let s = op.tf && typeof v === "number" && isFinite(v) ? formatTime(v, op.tf) : formatValue(v, f);
|
|
783
986
|
// UNKNOWN IS NOT ZERO. An em dash keeps the slot and says "not measured"; a
|
|
784
987
|
// `0` would say the thing was free. And it carries NEITHER the prefix nor the
|
|
785
988
|
// suffix — `Dur —m` reads as "minus minutes".
|
|
@@ -789,6 +992,7 @@ function renderVal(op, prog, c, o) {
|
|
|
789
992
|
if (f.case === "upper") s = s.toUpperCase();
|
|
790
993
|
else if (f.case === "lower") s = s.toLowerCase();
|
|
791
994
|
else if (f.case === "title") s = s.replace(/\w/g, (m) => m.toUpperCase());
|
|
995
|
+
if (f.case === "small" || f.case === "super" || f.case === "sub") s = styleText(s, f.case);
|
|
792
996
|
s = truncate(s, f.max_len, f.truncate);
|
|
793
997
|
s = (f.prefix || "") + pad(s, f) + (f.suffix || "");
|
|
794
998
|
// A ramp derives the colour from the VALUE, so it colours a number as much as
|
|
@@ -805,9 +1009,27 @@ function renderVal(op, prog, c, o) {
|
|
|
805
1009
|
|
|
806
1010
|
function renderBar(op, prog, c, o) {
|
|
807
1011
|
const g = glyphs(prog.glyphsets[op.g] || {}, o.ascii);
|
|
808
|
-
|
|
1012
|
+
let v = bind(op.b, c);
|
|
1013
|
+
// An ordered state (`o`) becomes its rank as a percent; an unlisted state
|
|
1014
|
+
// is unknown. Without this `effort` on `dots` draws nothing for "high".
|
|
1015
|
+
if (Array.isArray(op.o) && typeof v === "string") {
|
|
1016
|
+
const i = op.o.indexOf(v);
|
|
1017
|
+
v = i < 0 ? null : ((i + 1) / op.o.length) * 100;
|
|
1018
|
+
}
|
|
809
1019
|
const w = op.w || 10;
|
|
810
|
-
const
|
|
1020
|
+
const single = op.k === "gauge" || op.k === "ring" || op.k === "micro";
|
|
1021
|
+
// D2: UNKNOWN IS NOT ZERO. An empty bar or a 0% glyph reads as "0%"; the
|
|
1022
|
+
// unknown form keeps the width and says "not measured".
|
|
1023
|
+
if (v == null || (typeof v === "number" && !Number.isFinite(v))) {
|
|
1024
|
+
const d = o.ascii ? "-" : "—";
|
|
1025
|
+
return single ? d : d + " ".repeat(Math.max(0, w - 1));
|
|
1026
|
+
}
|
|
1027
|
+
const ramp = op.r != null ? rampSgr(prog.ramps[op.r], v, o.color) : "";
|
|
1028
|
+
// Precedence: `gc` (gradient) > `fs`/`es` > the ramp. A single-cell kind
|
|
1029
|
+
// takes `fs` as its whole colour.
|
|
1030
|
+
const split = o.color && (op.fs || op.es);
|
|
1031
|
+
const sgr = split && single && op.fs ? op.fs : ramp;
|
|
1032
|
+
const p = Math.max(0, Math.min(100, v == null ? 0 : v));
|
|
811
1033
|
let cells;
|
|
812
1034
|
switch (op.k) {
|
|
813
1035
|
case "gauge":
|
|
@@ -834,16 +1056,20 @@ function renderBar(op, prog, c, o) {
|
|
|
834
1056
|
case "gradient": {
|
|
835
1057
|
// Each cell coloured by ITS OWN position along the ramp. Decoration, and
|
|
836
1058
|
// the panel labels it as decoration — never a component default.
|
|
837
|
-
|
|
1059
|
+
const full = fullCells(p, w);
|
|
1060
|
+
if (o.color && Array.isArray(op.gc) && op.gc.length) {
|
|
1061
|
+
let s1 = "";
|
|
1062
|
+
for (let i = 0; i < w; i++) s1 += (op.gc[i] || op.gc[op.gc.length - 1]) + (i < full ? g.fill : g.empty);
|
|
1063
|
+
return s1 + RESET;
|
|
1064
|
+
}
|
|
1065
|
+
if (split || !o.color || op.r == null) {
|
|
838
1066
|
cells = barCells(v, g, w, "blocks");
|
|
839
1067
|
break;
|
|
840
1068
|
}
|
|
841
|
-
const
|
|
842
|
-
const p = Math.max(0, Math.min(100, v == null ? 0 : v));
|
|
843
|
-
const full = Math.floor((p / 100) * w);
|
|
1069
|
+
const rp = prog.ramps[op.r];
|
|
844
1070
|
let s2 = "";
|
|
845
1071
|
for (let i = 0; i < w; i++) {
|
|
846
|
-
s2 += rampSgr(
|
|
1072
|
+
s2 += rampSgr(rp, ((i + 1) / w) * 100, true) + (i < full ? g.fill : g.empty);
|
|
847
1073
|
}
|
|
848
1074
|
return s2 + RESET;
|
|
849
1075
|
}
|
|
@@ -856,9 +1082,31 @@ function renderBar(op, prog, c, o) {
|
|
|
856
1082
|
default:
|
|
857
1083
|
cells = barCells(v, g, w, "blocks");
|
|
858
1084
|
}
|
|
1085
|
+
if (split && !single) {
|
|
1086
|
+
const n = filledCount(op.k, p, w, g, op.thr);
|
|
1087
|
+
const list = [...cells];
|
|
1088
|
+
// A glyph set whose cells are not one code point each cannot be cut by
|
|
1089
|
+
// index; it takes the filled colour whole.
|
|
1090
|
+
if (list.length !== w) return (op.fs || ramp) ? (op.fs || ramp) + cells + RESET : cells;
|
|
1091
|
+
const run = (s, c) => (s === "" ? "" : c ? c + s + RESET : s);
|
|
1092
|
+
return run(list.slice(0, n).join(""), op.fs || ramp) + run(list.slice(n).join(""), op.es || ramp);
|
|
1093
|
+
}
|
|
859
1094
|
return sgr ? sgr + cells + RESET : cells;
|
|
860
1095
|
}
|
|
861
1096
|
|
|
1097
|
+
// How many leading cells a bar kind draws as FILLED — the cut for `fs`/`es`.
|
|
1098
|
+
function filledCount(k, p, w, g, thr) {
|
|
1099
|
+
if (k === "dots" || k === "meter") return Math.min(w, p > 0 ? Math.max(1, Math.round((p / 100) * w)) : 0);
|
|
1100
|
+
if (k === "marker") return Math.min(w - 1, Math.round((p / 100) * (w - 1))) + 1;
|
|
1101
|
+
const full = Math.floor((p / 100) * w);
|
|
1102
|
+
if (k === "fine" && g.part && g.part.length && full < w) {
|
|
1103
|
+
const idx = Math.min(g.part.length - 1, Math.floor(((p / 100) * w - full) * (g.part.length + 1)) - 1);
|
|
1104
|
+
return idx >= 0 ? full + 1 : full;
|
|
1105
|
+
}
|
|
1106
|
+
if (k === "split" || k === "fine") return Math.min(w, full);
|
|
1107
|
+
return fullCells(p, w);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
862
1110
|
function renderState(op, prog, c, o) {
|
|
863
1111
|
const m = prog.statemaps[op.m] || { glyphs: {}, sgr: {} };
|
|
864
1112
|
const v = bind(op.b, c);
|
|
@@ -877,15 +1125,25 @@ function renderMotif(op, prog, c, o) {
|
|
|
877
1125
|
const set = (m.motifs && (m.motifs[String(kind)] || m.motifs.generic)) || null;
|
|
878
1126
|
if (!set) return "";
|
|
879
1127
|
const frames = o.ascii ? set.a : set.u;
|
|
880
|
-
return motifFrame(frames, c.now, set.ms, o.motion);
|
|
1128
|
+
return motifFrame(frames, c.now, set.ms, o.motion, c.step);
|
|
881
1129
|
}
|
|
882
1130
|
|
|
883
1131
|
function renderSeries(op, prog, c, o) {
|
|
884
1132
|
const g = glyphs(prog.glyphsets[op.g] || {}, o.ascii);
|
|
885
|
-
|
|
1133
|
+
let s = seriesOf(c, op.s);
|
|
886
1134
|
const w = op.w || 8;
|
|
1135
|
+
const dash = o.ascii ? "-" : "—";
|
|
1136
|
+
// D4: a spark takes the CURRENT value as its live last point, so it shows
|
|
1137
|
+
// something on the first render and moves on every render.
|
|
1138
|
+
if (op.k === "spark" || op.k === "spark-braille" || !op.k) {
|
|
1139
|
+
const cur = op.b ? bind(op.b, op.p ? Object.assign({}, c, { param: op.p }) : c) : null;
|
|
1140
|
+
if (typeof cur === "number" && Number.isFinite(cur)) s = (s || []).concat([cur]);
|
|
1141
|
+
if (!s || !s.length) return dash + " ".repeat(Math.max(0, w - 1));
|
|
1142
|
+
}
|
|
887
1143
|
switch (op.k) {
|
|
888
1144
|
case "trend":
|
|
1145
|
+
// One point has no direction; the flat arrow would claim one.
|
|
1146
|
+
if (!s || s.length < 2) return dash;
|
|
889
1147
|
return trendGlyph(trendOf(s), g);
|
|
890
1148
|
case "delta": {
|
|
891
1149
|
if (!s || s.length < 2) return "—";
|
|
@@ -894,7 +1152,7 @@ function renderSeries(op, prog, c, o) {
|
|
|
894
1152
|
}
|
|
895
1153
|
case "spark-braille": {
|
|
896
1154
|
const set = g.spark_braille || ["⠀", "⣀", "⣤", "⣶", "⣿"];
|
|
897
|
-
|
|
1155
|
+
s = s.slice(-2 * w);
|
|
898
1156
|
const lo = Math.min(...s);
|
|
899
1157
|
const hi = Math.max(...s);
|
|
900
1158
|
const span = hi - lo || 1;
|
|
@@ -903,20 +1161,56 @@ function renderSeries(op, prog, c, o) {
|
|
|
903
1161
|
const v = (s[i] + (s[i + 1] == null ? s[i] : s[i + 1])) / 2;
|
|
904
1162
|
out += set[Math.min(set.length - 1, Math.round(((v - lo) / span) * (set.length - 1)))];
|
|
905
1163
|
}
|
|
906
|
-
|
|
1164
|
+
// Exactly `w` cells, like `spark`: the history grows in from the right.
|
|
1165
|
+
const n = [...out].length;
|
|
1166
|
+
return n >= w ? out : " ".repeat(w - n) + out;
|
|
907
1167
|
}
|
|
908
1168
|
default:
|
|
909
1169
|
return sparkCells(s, g, w);
|
|
910
1170
|
}
|
|
911
1171
|
}
|
|
912
1172
|
|
|
1173
|
+
// ── Sprites (v2.0.4) ───────────────────────────────────────────────────────
|
|
1174
|
+
// A pet on a track of `w` cells. The frame and the column both come off the
|
|
1175
|
+
// wall clock, so it moves only while the line redraws. Every frame cell is one
|
|
1176
|
+
// terminal cell (the CLI checks that), so a cell is a code point here.
|
|
1177
|
+
// MOTION=0 puts frame 0 at the left: motion REMOVED, never frozen mid-run.
|
|
1178
|
+
function renderSprite(op, c, o) {
|
|
1179
|
+
const w = Math.max(1, op.w || 16);
|
|
1180
|
+
const set = o.ascii && Array.isArray(op.a) && op.a.length ? op.a : op.f || [];
|
|
1181
|
+
if (!set.length) return " ".repeat(w);
|
|
1182
|
+
const tick = !o.motion ? 0 : Number.isInteger(c.step) ? c.step : Math.floor(c.now / (op.ms > 0 ? op.ms : 1000));
|
|
1183
|
+
const frame = [...String(set[tick % set.length])];
|
|
1184
|
+
const fw = frame.length;
|
|
1185
|
+
let x = 0;
|
|
1186
|
+
if (o.motion && op.mode === "run") x = (tick % (w + fw - 1)) - (fw - 1); // always one cell on the track
|
|
1187
|
+
else if (o.motion && op.mode === "bounce") {
|
|
1188
|
+
const span = Math.max(1, w - fw);
|
|
1189
|
+
const p = tick % (2 * span);
|
|
1190
|
+
x = p < span ? p : 2 * span - p;
|
|
1191
|
+
}
|
|
1192
|
+
// `ahead_a` / `behind_a` are the ASCII twins of the fillers, when they differ.
|
|
1193
|
+
const ahead = (o.ascii && op.ahead_a != null ? op.ahead_a : op.ahead) || " ";
|
|
1194
|
+
const behind = (o.ascii && op.behind_a != null ? op.behind_a : op.behind) || " ";
|
|
1195
|
+
let out = "";
|
|
1196
|
+
for (let i = 0; i < w; i++) out += i < x ? behind : i >= x + fw ? ahead : frame[i - x];
|
|
1197
|
+
return out;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// Every op the walker handles. The hooks check a compiled program against
|
|
1201
|
+
// this before running it, so an op from a newer compiler is a skew, not a dash.
|
|
1202
|
+
const OPS = { lit: 1, sgr: 1, reset: 1, item: 1, cond: 1, link: 1, flex: 1, val: 1, bar: 1, state: 1, motif: 1, series: 1, sprite: 1 };
|
|
1203
|
+
|
|
913
1204
|
module.exports = {
|
|
914
1205
|
BINDINGS,
|
|
1206
|
+
OPS,
|
|
915
1207
|
render,
|
|
916
1208
|
formatValue,
|
|
917
1209
|
barCells,
|
|
918
1210
|
sparkCells,
|
|
919
1211
|
motifFrame,
|
|
1212
|
+
cellWidth,
|
|
1213
|
+
styleText,
|
|
920
1214
|
// exported for the CLI's measure pass and its R1/R4 checks
|
|
921
|
-
_internals: { pad, truncate, si, bytes, bandState, hidden, rampSgr, glyphs },
|
|
1215
|
+
_internals: { pad, truncate, si, bytes, bandState, hidden, rampSgr, glyphs, burnRate, tokSpeed },
|
|
922
1216
|
};
|