claude-highlight 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2282 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // src/claude-highlight.ts
5
+ import { existsSync, mkdirSync, openSync, readFileSync, statSync, writeFileSync, writeSync } from "fs";
6
+ import { basename, dirname, resolve } from "path";
7
+
8
+ // src/util.ts
9
+ import { homedir } from "os";
10
+ import { join } from "path";
11
+ function configFile(app) {
12
+ const base = process.env["XDG_CONFIG_HOME"] || join(process.env["HOME"] || homedir(), ".config");
13
+ return join(base, app, "config.json");
14
+ }
15
+ function decodeIgnore(buf) {
16
+ let out = "";
17
+ const n = buf.length;
18
+ let i = 0;
19
+ while (i < n) {
20
+ const b = buf[i] ?? 0;
21
+ if (b < 128) {
22
+ out += String.fromCharCode(b);
23
+ i++;
24
+ continue;
25
+ }
26
+ let len, cp;
27
+ if (b >= 194 && b <= 223) {
28
+ len = 2;
29
+ cp = b & 31;
30
+ } else if (b >= 224 && b <= 239) {
31
+ len = 3;
32
+ cp = b & 15;
33
+ } else if (b >= 240 && b <= 244) {
34
+ len = 4;
35
+ cp = b & 7;
36
+ } else {
37
+ i++;
38
+ continue;
39
+ }
40
+ if (i + len > n)
41
+ break;
42
+ let ok = true;
43
+ for (let k = 1;k < len; k++) {
44
+ const c = buf[i + k] ?? 128;
45
+ if ((c & 192) !== 128) {
46
+ ok = false;
47
+ break;
48
+ }
49
+ cp = cp << 6 | c & 63;
50
+ }
51
+ if (!ok) {
52
+ i++;
53
+ continue;
54
+ }
55
+ if (len === 3 && (cp < 2048 || cp >= 55296 && cp <= 57343))
56
+ ok = false;
57
+ if (len === 4 && (cp < 65536 || cp > 1114111))
58
+ ok = false;
59
+ if (!ok) {
60
+ i++;
61
+ continue;
62
+ }
63
+ out += String.fromCodePoint(cp);
64
+ i += len;
65
+ }
66
+ return out;
67
+ }
68
+ function environ() {
69
+ const out = {};
70
+ for (const [key, value] of Object.entries(process.env)) {
71
+ if (value !== undefined)
72
+ out[key] = value;
73
+ }
74
+ return out;
75
+ }
76
+
77
+ // src/highlight_filter.ts
78
+ var WORD_TAIL = /[A-Za-z0-9'_-]+$/;
79
+ var TRANSPARENT = /^\x1b\[\d*[GC]$/;
80
+ var MAX_HOLD = 48;
81
+ var EMPTY = Buffer.alloc(0);
82
+ var SPACE = Buffer.from(" ", "latin1");
83
+ function finditer(pat, s) {
84
+ const re = pat.global ? pat : new RegExp(pat.source, pat.flags + "g");
85
+ re.lastIndex = 0;
86
+ const out = [];
87
+ let m;
88
+ while ((m = re.exec(s)) !== null) {
89
+ out.push({ start: m.index, end: m.index + m[0].length });
90
+ if (m[0].length === 0)
91
+ re.lastIndex += 1;
92
+ }
93
+ return out;
94
+ }
95
+ function asciiReplace(b) {
96
+ return b.toString("latin1").replace(/[\u0080-\u00ff]/g, "\uFFFD");
97
+ }
98
+ var isDigits = (s) => /^[0-9]+$/.test(s);
99
+
100
+ class AnsiHighlighter {
101
+ rules;
102
+ onlyUnstyled;
103
+ rewrites = [];
104
+ alt = false;
105
+ holdPrefixes = null;
106
+ restore = Buffer.from("\x1B[39m", "latin1");
107
+ state = "GROUND";
108
+ raw = [];
109
+ text = [];
110
+ segs = [];
111
+ fg = null;
112
+ bg = null;
113
+ constructor(rules, onlyUnstyled = true) {
114
+ this.rules = rules;
115
+ this.onlyUnstyled = onlyUnstyled;
116
+ }
117
+ noteSgr(seq) {
118
+ if (seq[seq.length - 1] !== 109)
119
+ return;
120
+ const params = asciiReplace(seq.subarray(2, seq.length - 1));
121
+ const toks = params ? params.split(";") : ["0"];
122
+ let i = 0;
123
+ while (i < toks.length) {
124
+ const t = toks[i] || "0";
125
+ const n = isDigits(t) ? Number(t) : -1;
126
+ if (n === 0) {
127
+ this.fg = null;
128
+ this.bg = null;
129
+ } else if (n === 39) {
130
+ this.fg = null;
131
+ } else if (n === 49) {
132
+ this.bg = null;
133
+ } else if (n >= 30 && n <= 37 || n >= 90 && n <= 97) {
134
+ this.fg = t;
135
+ } else if (n >= 40 && n <= 47 || n >= 100 && n <= 107) {
136
+ this.bg = t;
137
+ } else if (n === 38 || n === 48) {
138
+ const attr = n === 38 ? "fg" : "bg";
139
+ if (i + 1 < toks.length && toks[i + 1] === "5") {
140
+ this[attr] = toks.slice(i, i + 3).join(";");
141
+ i += 2;
142
+ } else if (i + 1 < toks.length && toks[i + 1] === "2") {
143
+ this[attr] = toks.slice(i, i + 5).join(";");
144
+ i += 4;
145
+ }
146
+ }
147
+ i += 1;
148
+ }
149
+ }
150
+ noteMode(seq) {
151
+ const last = seq[seq.length - 1];
152
+ if (last !== 104 && last !== 108 || !seq.includes(63))
153
+ return;
154
+ const nums = asciiReplace(seq.subarray(3, seq.length - 1)).split(";");
155
+ if (nums.some((n) => n === "1049" || n === "1047" || n === "47"))
156
+ this.alt = last === 104;
157
+ }
158
+ pushText() {
159
+ if (this.text.length) {
160
+ const styled = this.fg !== null || this.bg !== null;
161
+ this.segs.push({ data: Buffer.from(this.text), kind: "t", styled });
162
+ this.text = [];
163
+ }
164
+ }
165
+ static width(kind, data) {
166
+ return kind === "t" ? data.length : kind === "jump" ? 1 : 0;
167
+ }
168
+ static visible(segs) {
169
+ return Buffer.concat(segs.map((s) => s.kind === "t" ? s.data : s.kind === "jump" ? SPACE : EMPTY));
170
+ }
171
+ static split(segs, vpos) {
172
+ const before = [];
173
+ const after = [];
174
+ let seen = 0;
175
+ for (const s of segs) {
176
+ const width = AnsiHighlighter.width(s.kind, s.data);
177
+ if (seen >= vpos && width) {
178
+ after.push(s);
179
+ } else if (seen + width <= vpos) {
180
+ before.push(s);
181
+ } else if (s.kind !== "t") {
182
+ after.push(s);
183
+ } else {
184
+ const cut = vpos - seen;
185
+ before.push({ data: s.data.subarray(0, cut), kind: s.kind, styled: s.styled });
186
+ after.push({ data: s.data.subarray(cut), kind: s.kind, styled: s.styled });
187
+ }
188
+ seen += width;
189
+ }
190
+ return [before, after];
191
+ }
192
+ render(segs) {
193
+ if (segs.length === 0)
194
+ return EMPTY;
195
+ let work = segs;
196
+ if (this.rewrites.length && !this.alt) {
197
+ work = segs.map((s) => s.kind === "t" ? { data: this.rewrite(s.data), kind: s.kind, styled: s.styled } : s);
198
+ }
199
+ const raw = Buffer.concat(work.map((s) => s.data));
200
+ const vis = AnsiHighlighter.visible(work);
201
+ const visStr = vis.toString("latin1");
202
+ const blocked = [];
203
+ let seen = 0;
204
+ for (const s of work) {
205
+ const w = AnsiHighlighter.width(s.kind, s.data);
206
+ if (s.kind === "t" && s.styled)
207
+ blocked.push([seen, seen + w]);
208
+ seen += w;
209
+ }
210
+ const spans = [];
211
+ for (const { pat, style } of this.rules) {
212
+ for (const m of finditer(pat, visStr)) {
213
+ if (this.onlyUnstyled && blocked.some(([a, b]) => a < m.end && m.start < b))
214
+ continue;
215
+ if (!spans.some(([a, b]) => a < m.end && m.start < b)) {
216
+ let end = m.end;
217
+ while (end > m.start && visStr.charCodeAt(end - 1) === 32)
218
+ end -= 1;
219
+ spans.push([m.start, end, style]);
220
+ }
221
+ }
222
+ }
223
+ if (spans.length === 0)
224
+ return raw;
225
+ const marks = new Map;
226
+ const mark = (pos, b) => {
227
+ marks.set(pos, Buffer.concat([marks.get(pos) ?? EMPTY, b]));
228
+ };
229
+ for (const [a, b, style] of spans) {
230
+ mark(a, style);
231
+ mark(b, this.restore);
232
+ }
233
+ const out = [];
234
+ let vpos = 0;
235
+ for (const s of work) {
236
+ if (s.kind !== "t") {
237
+ const mk = marks.get(vpos);
238
+ if (mk !== undefined) {
239
+ out.push(mk);
240
+ marks.delete(vpos);
241
+ }
242
+ out.push(s.data);
243
+ vpos += AnsiHighlighter.width(s.kind, s.data);
244
+ } else {
245
+ const t = s.data;
246
+ let i = 0;
247
+ const hits = [...marks.keys()].filter((k) => vpos <= k && k <= vpos + t.length).sort((x, y) => x - y);
248
+ for (const pos of hits) {
249
+ const cut = pos - vpos;
250
+ out.push(t.subarray(i, cut));
251
+ out.push(marks.get(pos) ?? EMPTY);
252
+ marks.delete(pos);
253
+ i = cut;
254
+ }
255
+ out.push(t.subarray(i));
256
+ vpos += t.length;
257
+ }
258
+ }
259
+ const tail = marks.get(vpos);
260
+ if (tail !== undefined)
261
+ out.push(tail);
262
+ return Buffer.concat(out);
263
+ }
264
+ rewrite(text) {
265
+ let s = text.toString("latin1");
266
+ for (const { pat, repl } of this.rewrites) {
267
+ pat.lastIndex = 0;
268
+ s = s.replace(pat, repl);
269
+ }
270
+ return Buffer.from(s, "latin1");
271
+ }
272
+ flushText(hold) {
273
+ this.pushText();
274
+ const segs = this.segs;
275
+ if (segs.length === 0)
276
+ return EMPTY;
277
+ const vis = AnsiHighlighter.visible(segs);
278
+ const visStr = vis.toString("latin1");
279
+ let start = vis.length;
280
+ if (hold && vis.length) {
281
+ if (this.holdPrefixes !== null) {
282
+ for (let pos = Math.max(0, vis.length - MAX_HOLD);pos < vis.length; pos++) {
283
+ const tail = decodeIgnore(vis.subarray(pos)).toLowerCase();
284
+ if (tail && this.holdPrefixes.has(tail)) {
285
+ start = pos;
286
+ break;
287
+ }
288
+ }
289
+ } else {
290
+ const m = WORD_TAIL.exec(visStr);
291
+ if (m && vis.length - m.index <= MAX_HOLD)
292
+ start = m.index;
293
+ }
294
+ if (start < vis.length) {
295
+ for (const { pat } of this.rules) {
296
+ for (const m of finditer(pat, visStr)) {
297
+ if (m.start < start && start < m.end)
298
+ start = m.start;
299
+ }
300
+ }
301
+ }
302
+ }
303
+ const [emit, keep] = AnsiHighlighter.split(segs, start);
304
+ this.segs = keep;
305
+ return this.render(emit);
306
+ }
307
+ feed(chunk) {
308
+ const out = [];
309
+ for (const b of chunk) {
310
+ if (this.state === "GROUND") {
311
+ if (b === 27) {
312
+ this.state = "ESC";
313
+ this.raw = [b];
314
+ } else {
315
+ this.text.push(b);
316
+ if (b === 10 || b === 13 || b === 8 || b === 9) {
317
+ out.push(this.flushText(false));
318
+ }
319
+ }
320
+ continue;
321
+ }
322
+ this.raw.push(b);
323
+ if (this.state === "ESC") {
324
+ if (b === 91) {
325
+ this.state = "CSI";
326
+ } else if (b === 93) {
327
+ this.state = "OSC";
328
+ } else if (b === 80 || b === 88 || b === 94 || b === 95) {
329
+ this.state = "DCS";
330
+ } else if (b >= 32 && b <= 47) {
331
+ this.state = "ESCI";
332
+ } else {
333
+ out.push(this.flushText(false));
334
+ out.push(Buffer.from(this.raw));
335
+ this.state = "GROUND";
336
+ this.raw = [];
337
+ }
338
+ } else if (this.state === "CSI") {
339
+ if (b >= 64 && b <= 126) {
340
+ const seq = Buffer.from(this.raw);
341
+ if (TRANSPARENT.test(seq.toString("latin1"))) {
342
+ this.pushText();
343
+ this.segs.push({ data: seq, kind: "jump", styled: false });
344
+ } else if (b === 109) {
345
+ this.pushText();
346
+ this.noteSgr(seq);
347
+ this.segs.push({ data: seq, kind: "sgr", styled: false });
348
+ } else {
349
+ this.noteMode(seq);
350
+ out.push(this.flushText(false));
351
+ out.push(seq);
352
+ }
353
+ this.state = "GROUND";
354
+ this.raw = [];
355
+ }
356
+ } else if (this.state === "ESCI") {
357
+ if (b >= 48 && b <= 126) {
358
+ out.push(this.flushText(false));
359
+ out.push(Buffer.from(this.raw));
360
+ this.state = "GROUND";
361
+ this.raw = [];
362
+ }
363
+ } else {
364
+ const n = this.raw.length;
365
+ if (b === 7 || n >= 2 && this.raw[n - 2] === 27 && this.raw[n - 1] === 92) {
366
+ out.push(this.flushText(false));
367
+ out.push(Buffer.from(this.raw));
368
+ this.state = "GROUND";
369
+ this.raw = [];
370
+ }
371
+ }
372
+ }
373
+ out.push(this.flushText(true));
374
+ return Buffer.concat(out);
375
+ }
376
+ drain(force = true) {
377
+ if (this.state !== "GROUND" && !force)
378
+ return EMPTY;
379
+ return this.flushText(!force);
380
+ }
381
+ }
382
+
383
+ // src/hedge_lexicon.ts
384
+ var LEXICON = {
385
+ inference: [
386
+ "likely",
387
+ "probably",
388
+ "presumably",
389
+ "most likely",
390
+ "chances are",
391
+ "i suspect",
392
+ "i'd (?:guess|bet|expect)",
393
+ "my guess",
394
+ "i imagine",
395
+ "chances? (?:are|of)",
396
+ "chances are"
397
+ ],
398
+ appearance: [
399
+ "seems?(?: (?:to|like|that))?",
400
+ "appears?(?: (?:to|that))?",
401
+ "looks like",
402
+ "apparently",
403
+ "ostensibly",
404
+ "supposedly",
405
+ "as far as i can tell",
406
+ "from what i can see",
407
+ "on the surface"
408
+ ],
409
+ assumption: [
410
+ "assuming",
411
+ "i(?:'m| am) assuming",
412
+ "assumption",
413
+ "presuming",
414
+ "if i'm right",
415
+ "in theory",
416
+ "in principle",
417
+ "on paper",
418
+ "should (?:work|be|already|still)",
419
+ "ought to",
420
+ "by design",
421
+ "mostly"
422
+ ],
423
+ modal: [
424
+ "might",
425
+ "may (?:be|have|not|still|need|want|require)",
426
+ "could be",
427
+ "can be",
428
+ "possibly",
429
+ "potentially",
430
+ "perhaps",
431
+ "conceivably"
432
+ ],
433
+ unknown: [
434
+ "not sure",
435
+ "unsure",
436
+ "unclear",
437
+ "hard to say",
438
+ "i don'?t know",
439
+ "can'?t tell",
440
+ "can'?t verify",
441
+ "couldn'?t verify",
442
+ "can'?t (?:test|check|confirm|reproduce|measure|be sure)",
443
+ "couldn'?t (?:test|check|confirm|reproduce)",
444
+ "no (?:easy )?way to (?:test|check|reproduce)",
445
+ "no way to (?:know|tell|check)",
446
+ "unverified",
447
+ "untested",
448
+ "haven'?t (?:tested|verified|checked|run|confirmed)",
449
+ "didn'?t (?:test|verify|check|run|confirm)",
450
+ "without (?:testing|running|checking)",
451
+ "needs? (?:testing|verification|confirmation)",
452
+ "i'd need to (?:check|verify|test|look)"
453
+ ],
454
+ vagueness: [
455
+ "roughly",
456
+ "approximately",
457
+ "about \\d",
458
+ "or so",
459
+ "a (?:few|couple)",
460
+ "several",
461
+ "various",
462
+ "some(?:what)?",
463
+ "generally",
464
+ "typically",
465
+ "usually",
466
+ "often",
467
+ "in most cases",
468
+ "more or less",
469
+ "basically",
470
+ "essentially",
471
+ "effectively",
472
+ "pretty much"
473
+ ],
474
+ overclaim: [
475
+ "definitely",
476
+ "certainly",
477
+ "obviously",
478
+ "clearly",
479
+ "of course",
480
+ "without a doubt",
481
+ "guaranteed",
482
+ "always works",
483
+ "never fails",
484
+ "trivially",
485
+ "simply (?:add|change|run|do)",
486
+ "just (?:add|change|run|do)"
487
+ ],
488
+ softener: [
489
+ "a bit",
490
+ "slightly",
491
+ "somewhat",
492
+ "fairly",
493
+ "relatively",
494
+ "more or less",
495
+ "kind of",
496
+ "sort of",
497
+ "to some extent",
498
+ "at least in",
499
+ "for the most part"
500
+ ]
501
+ };
502
+ function expand(term) {
503
+ const chAt = (s, i) => i < s.length ? s.charAt(i) : "";
504
+ function seq(i, stop) {
505
+ let out = [""];
506
+ while (i < term.length && (stop === null || !stop.includes(chAt(term, i)))) {
507
+ const c = chAt(term, i);
508
+ if (c === "(") {
509
+ let [alts, ni] = alternatives(i + 3);
510
+ i = ni + 1;
511
+ if (i < term.length && chAt(term, i) === "?") {
512
+ i += 1;
513
+ alts = [...alts, ""];
514
+ }
515
+ out = out.flatMap((a) => alts.map((b) => a + b));
516
+ } else if (c === "\\") {
517
+ const nxt = chAt(term, i + 1);
518
+ if (nxt === "")
519
+ throw new Error("trailing backslash");
520
+ const pool = nxt === "d" ? "0123456789" : nxt;
521
+ out = out.flatMap((a) => [...pool].map((d) => a + d));
522
+ i += 2;
523
+ } else if (chAt(term, i + 1) === "?") {
524
+ out = [...out.map((a) => a + c), ...out];
525
+ i += 2;
526
+ } else {
527
+ out = out.map((a) => a + c);
528
+ i += 1;
529
+ }
530
+ }
531
+ return [out, i];
532
+ }
533
+ function alternatives(i) {
534
+ const alts = [];
535
+ while (true) {
536
+ const [opts, ni] = seq(i, "|)");
537
+ alts.push(...opts);
538
+ if (ni < term.length && chAt(term, ni) === "|") {
539
+ i = ni + 1;
540
+ continue;
541
+ }
542
+ return [alts, ni];
543
+ }
544
+ }
545
+ return seq(0, null)[0];
546
+ }
547
+ function userPattern(term) {
548
+ return term.startsWith("re:") ? term.slice(3) : term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
549
+ }
550
+ function literals(extra = []) {
551
+ const out = new Set;
552
+ for (const terms of [...Object.values(LEXICON), extra]) {
553
+ for (const t of terms) {
554
+ try {
555
+ for (const x of expand(t))
556
+ if (x.length > 0)
557
+ out.add(x.toLowerCase());
558
+ } catch {}
559
+ }
560
+ }
561
+ return out;
562
+ }
563
+ function growablePrefixes(extra = []) {
564
+ const lits = literals(extra);
565
+ const out = new Set;
566
+ for (const lit of lits) {
567
+ for (let n = 1;n < lit.length; n++)
568
+ out.add(lit.slice(0, n));
569
+ }
570
+ return out;
571
+ }
572
+
573
+ // src/screen_model.ts
574
+ var CSI = /\x1b\[([\x30-\x3f]*)([\x20-\x2f]*)([\x40-\x7e])/y;
575
+ var FRAME_END = /\x1b\[\?2026l/g;
576
+ var PARTIAL_CSI = /^\x1b\[[\x30-\x3f]*[\x20-\x2f]*$/;
577
+ var MAX_FIX = 4096;
578
+ var MAX_PENDING = 4096;
579
+ var RULE_CHARS = new Set("\u2500\u2501\u254C\u254D\u2504\u2505\u2508\u2509\u2502\u2503\u256D\u256E\u2570\u256F\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C");
580
+ var BOTTOM_CHROME = 10;
581
+ var MAX_COMPOSER = 24;
582
+ var WIDE = [
583
+ 4352,
584
+ 4447,
585
+ 8986,
586
+ 8987,
587
+ 9001,
588
+ 9002,
589
+ 9193,
590
+ 9196,
591
+ 9200,
592
+ 9200,
593
+ 9203,
594
+ 9203,
595
+ 9725,
596
+ 9726,
597
+ 9748,
598
+ 9749,
599
+ 9776,
600
+ 9783,
601
+ 9800,
602
+ 9811,
603
+ 9855,
604
+ 9855,
605
+ 9866,
606
+ 9871,
607
+ 9875,
608
+ 9875,
609
+ 9889,
610
+ 9889,
611
+ 9898,
612
+ 9899,
613
+ 9917,
614
+ 9918,
615
+ 9924,
616
+ 9925,
617
+ 9934,
618
+ 9934,
619
+ 9940,
620
+ 9940,
621
+ 9962,
622
+ 9962,
623
+ 9970,
624
+ 9971,
625
+ 9973,
626
+ 9973,
627
+ 9978,
628
+ 9978,
629
+ 9981,
630
+ 9981,
631
+ 9989,
632
+ 9989,
633
+ 9994,
634
+ 9995,
635
+ 10024,
636
+ 10024,
637
+ 10060,
638
+ 10060,
639
+ 10062,
640
+ 10062,
641
+ 10067,
642
+ 10069,
643
+ 10071,
644
+ 10071,
645
+ 10133,
646
+ 10135,
647
+ 10160,
648
+ 10160,
649
+ 10175,
650
+ 10175,
651
+ 11035,
652
+ 11036,
653
+ 11088,
654
+ 11088,
655
+ 11093,
656
+ 11093,
657
+ 11904,
658
+ 11929,
659
+ 11931,
660
+ 12019,
661
+ 12032,
662
+ 12245,
663
+ 12272,
664
+ 12350,
665
+ 12353,
666
+ 12438,
667
+ 12441,
668
+ 12543,
669
+ 12549,
670
+ 12591,
671
+ 12593,
672
+ 12686,
673
+ 12688,
674
+ 12773,
675
+ 12783,
676
+ 12830,
677
+ 12832,
678
+ 12871,
679
+ 12880,
680
+ 42124,
681
+ 42128,
682
+ 42182,
683
+ 43360,
684
+ 43388,
685
+ 44032,
686
+ 55203,
687
+ 63744,
688
+ 64255,
689
+ 65040,
690
+ 65049,
691
+ 65072,
692
+ 65106,
693
+ 65108,
694
+ 65126,
695
+ 65128,
696
+ 65131,
697
+ 65281,
698
+ 65376,
699
+ 65504,
700
+ 65510,
701
+ 94176,
702
+ 94180,
703
+ 94192,
704
+ 94193,
705
+ 94208,
706
+ 100343,
707
+ 100352,
708
+ 101589,
709
+ 101631,
710
+ 101640,
711
+ 110576,
712
+ 110579,
713
+ 110581,
714
+ 110587,
715
+ 110589,
716
+ 110590,
717
+ 110592,
718
+ 110882,
719
+ 110898,
720
+ 110898,
721
+ 110928,
722
+ 110930,
723
+ 110933,
724
+ 110933,
725
+ 110948,
726
+ 110951,
727
+ 110960,
728
+ 111355,
729
+ 119552,
730
+ 119638,
731
+ 119648,
732
+ 119670,
733
+ 126980,
734
+ 126980,
735
+ 127183,
736
+ 127183,
737
+ 127374,
738
+ 127374,
739
+ 127377,
740
+ 127386,
741
+ 127488,
742
+ 127490,
743
+ 127504,
744
+ 127547,
745
+ 127552,
746
+ 127560,
747
+ 127568,
748
+ 127569,
749
+ 127584,
750
+ 127589,
751
+ 127744,
752
+ 127776,
753
+ 127789,
754
+ 127797,
755
+ 127799,
756
+ 127868,
757
+ 127870,
758
+ 127891,
759
+ 127904,
760
+ 127946,
761
+ 127951,
762
+ 127955,
763
+ 127968,
764
+ 127984,
765
+ 127988,
766
+ 127988,
767
+ 127992,
768
+ 128062,
769
+ 128064,
770
+ 128064,
771
+ 128066,
772
+ 128252,
773
+ 128255,
774
+ 128317,
775
+ 128331,
776
+ 128334,
777
+ 128336,
778
+ 128359,
779
+ 128378,
780
+ 128378,
781
+ 128405,
782
+ 128406,
783
+ 128420,
784
+ 128420,
785
+ 128507,
786
+ 128591,
787
+ 128640,
788
+ 128709,
789
+ 128716,
790
+ 128716,
791
+ 128720,
792
+ 128722,
793
+ 128725,
794
+ 128727,
795
+ 128732,
796
+ 128735,
797
+ 128747,
798
+ 128748,
799
+ 128756,
800
+ 128764,
801
+ 128992,
802
+ 129003,
803
+ 129008,
804
+ 129008,
805
+ 129292,
806
+ 129338,
807
+ 129340,
808
+ 129349,
809
+ 129351,
810
+ 129535,
811
+ 129648,
812
+ 129660,
813
+ 129664,
814
+ 129673,
815
+ 129679,
816
+ 129734,
817
+ 129742,
818
+ 129756,
819
+ 129759,
820
+ 129769,
821
+ 129776,
822
+ 129784,
823
+ 131072,
824
+ 196605,
825
+ 196608,
826
+ 262141
827
+ ];
828
+ var ZERO = /[\p{Mn}\p{Me}\p{Cf}]/u;
829
+ var ZERO_EXTRA = new Set([
830
+ 5909,
831
+ 5940,
832
+ 6980,
833
+ 7082,
834
+ 7154,
835
+ 7155,
836
+ 12334,
837
+ 12335,
838
+ 43347,
839
+ 43456,
840
+ 70080,
841
+ 70197,
842
+ 70477,
843
+ 70607,
844
+ 71350,
845
+ 71997,
846
+ 73537,
847
+ 94192,
848
+ 94193,
849
+ 119141,
850
+ 119142,
851
+ 119149,
852
+ 119150,
853
+ 119151,
854
+ 119152,
855
+ 119153,
856
+ 119154
857
+ ].map((c) => String.fromCodePoint(c)));
858
+ function cellWidth(ch) {
859
+ const cp = ch.codePointAt(0) ?? 0;
860
+ if (cp < 173)
861
+ return 1;
862
+ if (ZERO.test(ch) || ZERO_EXTRA.has(ch))
863
+ return 0;
864
+ let lo = 0, hi = WIDE.length / 2 - 1;
865
+ while (lo <= hi) {
866
+ const mid = lo + hi >> 1;
867
+ if (cp < WIDE[mid * 2])
868
+ hi = mid - 1;
869
+ else if (cp > WIDE[mid * 2 + 1])
870
+ lo = mid + 1;
871
+ else
872
+ return 2;
873
+ }
874
+ return 1;
875
+ }
876
+
877
+ class ScreenModel {
878
+ palette;
879
+ rows;
880
+ cols;
881
+ chars;
882
+ fg;
883
+ ours;
884
+ rule;
885
+ x;
886
+ y;
887
+ curFg;
888
+ curOurs;
889
+ saved;
890
+ top;
891
+ bot;
892
+ alt;
893
+ wrapPending;
894
+ dirty;
895
+ pending;
896
+ inFrame;
897
+ constructor(rows, cols, palette = []) {
898
+ this.palette = new Set(palette);
899
+ this.resize(rows, cols);
900
+ }
901
+ resize(rows, cols) {
902
+ this.rows = Math.max(rows, 1);
903
+ this.cols = Math.max(cols, 1);
904
+ this.invalidate();
905
+ }
906
+ invalidate() {
907
+ this.chars = Array.from({ length: this.rows }, () => new Array(this.cols).fill(null));
908
+ this.fg = Array.from({ length: this.rows }, () => new Array(this.cols).fill(null));
909
+ this.ours = Array.from({ length: this.rows }, () => new Array(this.cols).fill(false));
910
+ this.rule = new Array(this.rows).fill(null);
911
+ this.x = this.y = 0;
912
+ this.curFg = null;
913
+ this.curOurs = false;
914
+ this.saved = [0, 0, null];
915
+ this.top = 0;
916
+ this.bot = this.rows - 1;
917
+ this.alt = false;
918
+ this.wrapPending = false;
919
+ this.dirty = new Set;
920
+ this.pending = Buffer.alloc(0);
921
+ this.inFrame = false;
922
+ }
923
+ blankRow() {
924
+ return [
925
+ new Array(this.cols).fill(null),
926
+ new Array(this.cols).fill(null),
927
+ new Array(this.cols).fill(false)
928
+ ];
929
+ }
930
+ set(x, ch) {
931
+ this.chars[this.y][x] = ch;
932
+ this.fg[this.y][x] = this.curFg;
933
+ this.ours[this.y][x] = this.curOurs;
934
+ this.dirty.add(this.y);
935
+ this.rule[this.y] = null;
936
+ }
937
+ put(text) {
938
+ for (const ch of text) {
939
+ const w = cellWidth(ch);
940
+ if (w === 0)
941
+ continue;
942
+ if (this.wrapPending || this.x + w > this.cols) {
943
+ this.x = 0;
944
+ this.index();
945
+ this.wrapPending = false;
946
+ }
947
+ this.set(this.x, ch);
948
+ if (w === 2 && this.x + 1 < this.cols)
949
+ this.set(this.x + 1, "");
950
+ this.x += w;
951
+ if (this.x >= this.cols) {
952
+ this.x = this.cols - 1;
953
+ this.wrapPending = true;
954
+ }
955
+ }
956
+ }
957
+ index() {
958
+ if (this.y === this.bot)
959
+ this.scroll(1);
960
+ else
961
+ this.y = Math.min(this.y + 1, this.rows - 1);
962
+ }
963
+ scroll(n, up = true) {
964
+ for (let k = 0;k < Math.max(n, 1); k++) {
965
+ const [c, f, o] = this.blankRow();
966
+ if (up) {
967
+ this.chars.splice(this.top, 1);
968
+ this.chars.splice(this.bot, 0, c);
969
+ this.fg.splice(this.top, 1);
970
+ this.fg.splice(this.bot, 0, f);
971
+ this.ours.splice(this.top, 1);
972
+ this.ours.splice(this.bot, 0, o);
973
+ } else {
974
+ this.chars.splice(this.bot, 1);
975
+ this.chars.splice(this.top, 0, c);
976
+ this.fg.splice(this.bot, 1);
977
+ this.fg.splice(this.top, 0, f);
978
+ this.ours.splice(this.bot, 1);
979
+ this.ours.splice(this.top, 0, o);
980
+ }
981
+ }
982
+ for (let y = this.top;y <= this.bot; y++)
983
+ this.dirty.add(y);
984
+ this.rule = new Array(this.rows).fill(null);
985
+ }
986
+ sgr(params) {
987
+ const toks = params ? params.split(";") : ["0"];
988
+ let i = 0;
989
+ while (i < toks.length) {
990
+ const t = toks[i] || "0";
991
+ const n = /^[0-9]+$/.test(t) ? parseInt(t, 10) : -1;
992
+ if (n === 0 || n === 39) {
993
+ this.curFg = null;
994
+ } else if (n >= 30 && n <= 37 || n >= 90 && n <= 97) {
995
+ this.curFg = t;
996
+ } else if (n === 38) {
997
+ if (toks[i + 1] === "5") {
998
+ this.curFg = toks.slice(i, i + 3).join(";");
999
+ i += 2;
1000
+ } else if (toks[i + 1] === "2") {
1001
+ this.curFg = toks.slice(i, i + 5).join(";");
1002
+ i += 4;
1003
+ }
1004
+ }
1005
+ i += 1;
1006
+ }
1007
+ this.curOurs = this.curFg !== null && this.palette.has(this.curFg);
1008
+ }
1009
+ erase(cells) {
1010
+ for (const x of cells) {
1011
+ this.chars[this.y][x] = " ";
1012
+ this.fg[this.y][x] = null;
1013
+ this.ours[this.y][x] = false;
1014
+ }
1015
+ this.dirty.add(this.y);
1016
+ this.rule[this.y] = null;
1017
+ }
1018
+ csi(priv, fin, params) {
1019
+ const parts = params ? params.split(";") : [];
1020
+ const nums = parts.map((p) => /^[0-9]+$/.test(p) ? parseInt(p, 10) : 0);
1021
+ const a = nums.length ? nums[0] : 0;
1022
+ if (priv) {
1023
+ if ((fin === "h" || fin === "l") && parts.some((p) => p === "1049" || p === "1047" || p === "47")) {
1024
+ this.invalidate();
1025
+ this.alt = fin === "h";
1026
+ } else if ((fin === "h" || fin === "l") && parts.includes("2026")) {
1027
+ this.inFrame = fin === "h";
1028
+ }
1029
+ return;
1030
+ }
1031
+ if (fin === "H" || fin === "f") {
1032
+ this.y = Math.max(0, Math.min(nums.length ? nums[0] - 1 : 0, this.rows - 1));
1033
+ this.x = Math.max(0, Math.min(nums.length > 1 ? nums[1] - 1 : 0, this.cols - 1));
1034
+ this.wrapPending = false;
1035
+ } else if (fin === "A") {
1036
+ this.y = Math.max(0, this.y - Math.max(a, 1));
1037
+ this.wrapPending = false;
1038
+ } else if (fin === "B") {
1039
+ this.y = Math.min(this.rows - 1, this.y + Math.max(a, 1));
1040
+ this.wrapPending = false;
1041
+ } else if (fin === "C") {
1042
+ this.x = Math.min(this.cols - 1, this.x + Math.max(a, 1));
1043
+ this.wrapPending = false;
1044
+ } else if (fin === "D") {
1045
+ this.x = Math.max(0, this.x - Math.max(a, 1));
1046
+ this.wrapPending = false;
1047
+ } else if (fin === "E") {
1048
+ this.y = Math.min(this.rows - 1, this.y + Math.max(a, 1));
1049
+ this.x = 0;
1050
+ } else if (fin === "F") {
1051
+ this.y = Math.max(0, this.y - Math.max(a, 1));
1052
+ this.x = 0;
1053
+ } else if (fin === "G") {
1054
+ this.x = Math.max(0, Math.min((a || 1) - 1, this.cols - 1));
1055
+ this.wrapPending = false;
1056
+ } else if (fin === "d") {
1057
+ this.y = Math.max(0, Math.min((a || 1) - 1, this.rows - 1));
1058
+ } else if (fin === "K") {
1059
+ if (a === 1)
1060
+ this.erase(span(0, this.x + 1));
1061
+ else if (a === 2)
1062
+ this.erase(span(0, this.cols));
1063
+ else
1064
+ this.erase(span(this.x, this.cols));
1065
+ } else if (fin === "J") {
1066
+ if (a === 2 || a === 3) {
1067
+ for (let y = 0;y < this.rows; y++) {
1068
+ this.y = y;
1069
+ this.erase(span(0, this.cols));
1070
+ }
1071
+ this.y = 0;
1072
+ } else if (a === 0) {
1073
+ this.erase(span(this.x, this.cols));
1074
+ const keep = this.y;
1075
+ for (let y = keep + 1;y < this.rows; y++) {
1076
+ this.y = y;
1077
+ this.erase(span(0, this.cols));
1078
+ }
1079
+ this.y = keep;
1080
+ }
1081
+ } else if (fin === "X") {
1082
+ this.erase(span(this.x, Math.min(this.x + Math.max(a, 1), this.cols)));
1083
+ } else if (fin === "S") {
1084
+ this.scroll(a, true);
1085
+ } else if (fin === "T") {
1086
+ this.scroll(a, false);
1087
+ } else if (fin === "L" || fin === "M") {
1088
+ const n = Math.max(a, 1);
1089
+ for (let k = 0;k < n; k++) {
1090
+ const [c, f, o] = this.blankRow();
1091
+ const at = fin === "L" ? this.y : this.bot;
1092
+ const from = fin === "L" ? this.bot : this.y;
1093
+ this.chars.splice(from, 1);
1094
+ this.chars.splice(at, 0, c);
1095
+ this.fg.splice(from, 1);
1096
+ this.fg.splice(at, 0, f);
1097
+ this.ours.splice(from, 1);
1098
+ this.ours.splice(at, 0, o);
1099
+ }
1100
+ for (let y = this.y;y <= this.bot; y++)
1101
+ this.dirty.add(y);
1102
+ this.rule = new Array(this.rows).fill(null);
1103
+ } else if (fin === "P" || fin === "@") {
1104
+ const n = Math.max(a, 1);
1105
+ const chars = this.chars[this.y], fg = this.fg[this.y], ours = this.ours[this.y];
1106
+ if (fin === "P") {
1107
+ chars.splice(this.x, n);
1108
+ while (chars.length < this.cols)
1109
+ chars.push(null);
1110
+ fg.splice(this.x, n);
1111
+ while (fg.length < this.cols)
1112
+ fg.push(null);
1113
+ ours.splice(this.x, n);
1114
+ while (ours.length < this.cols)
1115
+ ours.push(false);
1116
+ } else {
1117
+ for (let k = 0;k < n; k++) {
1118
+ chars.splice(this.x, 0, null);
1119
+ chars.pop();
1120
+ fg.splice(this.x, 0, null);
1121
+ fg.pop();
1122
+ ours.splice(this.x, 0, false);
1123
+ ours.pop();
1124
+ }
1125
+ }
1126
+ this.dirty.add(this.y);
1127
+ this.rule[this.y] = null;
1128
+ } else if (fin === "r") {
1129
+ this.top = Math.max(0, nums.length ? nums[0] - 1 : 0);
1130
+ this.bot = Math.min(this.rows - 1, nums.length > 1 ? nums[1] - 1 : this.rows - 1);
1131
+ if (this.top >= this.bot) {
1132
+ this.top = 0;
1133
+ this.bot = this.rows - 1;
1134
+ }
1135
+ this.x = this.y = 0;
1136
+ } else if (fin === "m") {
1137
+ this.sgr(params);
1138
+ }
1139
+ }
1140
+ static partialUtf8(data) {
1141
+ for (let back = 1;back <= Math.min(4, data.length); back++) {
1142
+ const b = data[data.length - back];
1143
+ if (b < 128)
1144
+ return 0;
1145
+ if (b >= 192) {
1146
+ const need = b < 224 ? 2 : b < 240 ? 3 : 4;
1147
+ return back < need ? back : 0;
1148
+ }
1149
+ }
1150
+ return 0;
1151
+ }
1152
+ feed(data) {
1153
+ if (this.pending.length) {
1154
+ data = Buffer.concat([this.pending, data]);
1155
+ this.pending = Buffer.alloc(0);
1156
+ }
1157
+ const s = data.toString("latin1");
1158
+ let i = 0;
1159
+ const n = data.length;
1160
+ while (i < n) {
1161
+ const b = data[i];
1162
+ if (b === 27) {
1163
+ CSI.lastIndex = i;
1164
+ const m = CSI.exec(s);
1165
+ if (m) {
1166
+ const p = m[1];
1167
+ const priv = "?><=".includes(p.slice(0, 1)) ? p.slice(0, 1) : "";
1168
+ this.csi(priv, m[3], p.slice(priv.length));
1169
+ i = CSI.lastIndex;
1170
+ continue;
1171
+ }
1172
+ const nxt = data[i + 1];
1173
+ if (nxt === undefined || nxt === 91 && PARTIAL_CSI.test(s.slice(i))) {
1174
+ return this.hold(data.subarray(i));
1175
+ }
1176
+ if (nxt >= 32 && nxt <= 47) {
1177
+ let j = i + 1;
1178
+ while (j < n && data[j] >= 32 && data[j] <= 47)
1179
+ j++;
1180
+ if (j >= n)
1181
+ return this.hold(data.subarray(i));
1182
+ i = j + 1;
1183
+ continue;
1184
+ }
1185
+ if (nxt === 80 || nxt === 88 || nxt === 94 || nxt === 95 || nxt === 93) {
1186
+ const bel = s.indexOf("\x07", i + 2);
1187
+ const st = s.indexOf("\x1B\\", i + 2);
1188
+ const ends = [bel, st].filter((j) => j !== -1);
1189
+ if (!ends.length)
1190
+ return this.hold(data.subarray(i));
1191
+ const end = Math.min(...ends);
1192
+ i = end + (end === bel ? 1 : 2);
1193
+ continue;
1194
+ }
1195
+ if (nxt === 55) {
1196
+ this.saved = [this.x, this.y, this.curFg];
1197
+ } else if (nxt === 56) {
1198
+ [this.x, this.y, this.curFg] = this.saved;
1199
+ this.curOurs = this.curFg !== null && this.palette.has(this.curFg);
1200
+ } else if (nxt === 77) {
1201
+ if (this.y === this.top)
1202
+ this.scroll(1, false);
1203
+ else
1204
+ this.y = Math.max(0, this.y - 1);
1205
+ }
1206
+ i += 2;
1207
+ continue;
1208
+ }
1209
+ if (b === 10) {
1210
+ this.index();
1211
+ this.wrapPending = false;
1212
+ i += 1;
1213
+ continue;
1214
+ }
1215
+ if (b === 13) {
1216
+ this.x = 0;
1217
+ this.wrapPending = false;
1218
+ i += 1;
1219
+ continue;
1220
+ }
1221
+ if (b === 8) {
1222
+ this.x = Math.max(0, this.x - 1);
1223
+ this.wrapPending = false;
1224
+ i += 1;
1225
+ continue;
1226
+ }
1227
+ if (b === 9) {
1228
+ this.x = Math.min(this.cols - 1, (Math.floor(this.x / 8) + 1) * 8);
1229
+ i += 1;
1230
+ continue;
1231
+ }
1232
+ let j = i;
1233
+ while (j < n && data[j] >= 32 && data[j] !== 27)
1234
+ j++;
1235
+ if (j === i) {
1236
+ i += 1;
1237
+ continue;
1238
+ }
1239
+ let run = data.subarray(i, j);
1240
+ if (j === n) {
1241
+ const cut = ScreenModel.partialUtf8(run);
1242
+ if (cut) {
1243
+ const hold = run.subarray(run.length - cut);
1244
+ run = run.subarray(0, run.length - cut);
1245
+ this.put(run.toString("utf8"));
1246
+ return this.hold(hold);
1247
+ }
1248
+ }
1249
+ this.put(run.toString("utf8"));
1250
+ i = j;
1251
+ }
1252
+ }
1253
+ hold(tail) {
1254
+ this.pending = tail.length <= MAX_PENDING ? Buffer.from(tail) : Buffer.alloc(0);
1255
+ }
1256
+ isRule(y) {
1257
+ const cached = this.rule[y];
1258
+ if (cached !== null && cached !== undefined)
1259
+ return cached;
1260
+ let marks = 0, other = 0;
1261
+ for (const c of this.chars[y]) {
1262
+ if (c !== null && RULE_CHARS.has(c))
1263
+ marks += 1;
1264
+ else if (c !== null && c !== "" && c !== " ") {
1265
+ other += 1;
1266
+ break;
1267
+ }
1268
+ }
1269
+ const v = other === 0 && marks >= this.cols * 0.8;
1270
+ this.rule[y] = v;
1271
+ return v;
1272
+ }
1273
+ composerRows() {
1274
+ const rules = [];
1275
+ for (let y = this.rows - 1;y > Math.max(this.rows - 32, -1); y--) {
1276
+ if (this.isRule(y))
1277
+ rules.push(y);
1278
+ }
1279
+ const low = rules[0];
1280
+ if (low === undefined || low < this.rows - BOTTOM_CHROME)
1281
+ return [];
1282
+ let top = low;
1283
+ for (const y of rules.slice(1)) {
1284
+ if (low - y <= MAX_COMPOSER) {
1285
+ top = y;
1286
+ break;
1287
+ }
1288
+ }
1289
+ return span(top, this.rows);
1290
+ }
1291
+ rowBytes(y) {
1292
+ const parts = [];
1293
+ const idx = [];
1294
+ const row = this.chars[y];
1295
+ for (let x = 0;x < row.length; x++) {
1296
+ const ch = row[x];
1297
+ if (ch === "")
1298
+ continue;
1299
+ const enc = Buffer.from(ch ? ch : " ", "utf8");
1300
+ parts.push(enc);
1301
+ for (let k = 0;k < enc.length; k++)
1302
+ idx.push(x);
1303
+ }
1304
+ return [Buffer.concat(parts), idx];
1305
+ }
1306
+ desired(y, rules, onlyUnstyled, offLimits = []) {
1307
+ if (offLimits.includes(y)) {
1308
+ return new Array(this.cols).fill(null);
1309
+ }
1310
+ const [buf, idx] = this.rowBytes(y);
1311
+ const text = buf.toString("latin1");
1312
+ const want = new Array(this.cols).fill(null);
1313
+ for (const { pat, style } of rules) {
1314
+ const fg = style.subarray(2, style.length - 1).toString("latin1");
1315
+ for (const m of text.matchAll(pat)) {
1316
+ const start = m.index;
1317
+ let end = start + m[0].length;
1318
+ while (end > start && text[end - 1] === " ")
1319
+ end -= 1;
1320
+ if (end <= start)
1321
+ continue;
1322
+ const cells = span(idx[start], idx[end - 1] + 1);
1323
+ if (cells.some((c) => want[c] !== null))
1324
+ continue;
1325
+ if (onlyUnstyled && cells.some((c) => this.fg[y][c] !== null && !this.ours[y][c])) {
1326
+ continue;
1327
+ }
1328
+ for (const c of cells)
1329
+ want[c] = fg;
1330
+ }
1331
+ }
1332
+ return want;
1333
+ }
1334
+ corrections(rules, onlyUnstyled = true) {
1335
+ if (!this.alt || this.dirty.size === 0)
1336
+ return Buffer.alloc(0);
1337
+ const rows = [...this.dirty].sort((p, q) => p - q);
1338
+ this.dirty = new Set;
1339
+ const offLimits = this.composerRows();
1340
+ const out = [];
1341
+ let len = 0, spent = false;
1342
+ for (const y of rows) {
1343
+ if (spent) {
1344
+ this.dirty.add(y);
1345
+ continue;
1346
+ }
1347
+ if (!this.ours[y].some((v) => v))
1348
+ continue;
1349
+ const want = this.desired(y, rules, onlyUnstyled, offLimits);
1350
+ let x = 0;
1351
+ while (x < this.cols) {
1352
+ if (!(this.ours[y][x] && this.fg[y][x] !== want[x])) {
1353
+ x += 1;
1354
+ continue;
1355
+ }
1356
+ let start = x;
1357
+ const target = want[x];
1358
+ while (x < this.cols && this.ours[y][x] && this.fg[y][x] !== want[x] && want[x] === target)
1359
+ x += 1;
1360
+ if (this.chars[y][start] === "" && start)
1361
+ start -= 1;
1362
+ const text = this.chars[y].slice(start, x).filter((c) => c !== "" && c !== null).join("");
1363
+ if (!text)
1364
+ continue;
1365
+ const fix = Buffer.concat([
1366
+ Buffer.from(`\x1B[${y + 1};${start + 1}H`, "latin1"),
1367
+ Buffer.from(target ? `\x1B[${target}m` : "\x1B[39m", "latin1"),
1368
+ Buffer.from(text, "utf8")
1369
+ ]);
1370
+ if (len + fix.length > MAX_FIX) {
1371
+ this.dirty.add(y);
1372
+ spent = true;
1373
+ break;
1374
+ }
1375
+ out.push(fix);
1376
+ len += fix.length;
1377
+ for (let c = start;c < x; c++) {
1378
+ this.fg[y][c] = target;
1379
+ this.ours[y][c] = target !== null;
1380
+ }
1381
+ }
1382
+ }
1383
+ if (!len)
1384
+ return Buffer.alloc(0);
1385
+ return Buffer.concat([
1386
+ Buffer.from("\x1B7\x1B[?7l", "latin1"),
1387
+ ...out,
1388
+ Buffer.from("\x1B[?7h\x1B8", "latin1")
1389
+ ]);
1390
+ }
1391
+ reconcile(data, rules, onlyUnstyled = true) {
1392
+ if (!data.length)
1393
+ return data;
1394
+ const out = [];
1395
+ let i = 0;
1396
+ for (const m of data.toString("latin1").matchAll(FRAME_END)) {
1397
+ const seg = data.subarray(i, m.index);
1398
+ this.feed(seg);
1399
+ out.push(seg);
1400
+ out.push(this.corrections(rules, onlyUnstyled));
1401
+ const mark = data.subarray(m.index, m.index + m[0].length);
1402
+ out.push(mark);
1403
+ this.feed(mark);
1404
+ i = m.index + m[0].length;
1405
+ }
1406
+ const tail = data.subarray(i);
1407
+ this.feed(tail);
1408
+ out.push(tail);
1409
+ if (tail.length && !this.inFrame) {
1410
+ out.push(this.corrections(rules, onlyUnstyled));
1411
+ }
1412
+ return Buffer.concat(out);
1413
+ }
1414
+ }
1415
+ function span(a, b) {
1416
+ const out = [];
1417
+ for (let i = a;i < b; i++)
1418
+ out.push(i);
1419
+ return out;
1420
+ }
1421
+
1422
+ // src/pty.ts
1423
+ import { closeSync } from "fs";
1424
+
1425
+ // src/sysffi.ts
1426
+ import { dlopen, FFIType } from "bun:ffi";
1427
+ var APPLE_VARARGS = process.platform === "darwin" && process.arch === "arm64";
1428
+ var IOCTL_PAD = [
1429
+ FFIType.u64,
1430
+ FFIType.u64,
1431
+ FFIType.u64,
1432
+ FFIType.u64,
1433
+ FFIType.u64,
1434
+ FFIType.u64
1435
+ ];
1436
+ var IOCTL = {
1437
+ ioctl: {
1438
+ args: APPLE_VARARGS ? [FFIType.i32, FFIType.u64, ...IOCTL_PAD, FFIType.ptr] : [FFIType.i32, FFIType.u64, FFIType.ptr],
1439
+ returns: FFIType.i32
1440
+ }
1441
+ };
1442
+ var CORE = {
1443
+ read: { args: [FFIType.i32, FFIType.ptr, FFIType.u64], returns: FFIType.i64 },
1444
+ write: { args: [FFIType.i32, FFIType.ptr, FFIType.u64], returns: FFIType.i64 },
1445
+ close: { args: [FFIType.i32], returns: FFIType.i32 },
1446
+ kill: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 },
1447
+ tcgetattr: { args: [FFIType.i32, FFIType.ptr], returns: FFIType.i32 },
1448
+ tcsetattr: { args: [FFIType.i32, FFIType.i32, FFIType.ptr], returns: FFIType.i32 },
1449
+ cfmakeraw: { args: [FFIType.ptr], returns: FFIType.i32 }
1450
+ };
1451
+ var OPENPTY = {
1452
+ openpty: {
1453
+ args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
1454
+ returns: FFIType.i32
1455
+ }
1456
+ };
1457
+ function bind(symbols) {
1458
+ return symbols;
1459
+ }
1460
+ function load() {
1461
+ if (process.platform === "darwin") {
1462
+ const lib = dlopen("/usr/lib/libSystem.dylib", { ...CORE, ...IOCTL, ...OPENPTY });
1463
+ const syms = bind(lib.symbols);
1464
+ return { core: syms, openpty: syms };
1465
+ }
1466
+ const core = bind(dlopen("libc.so.6", { ...CORE, ...IOCTL }).symbols);
1467
+ for (const name of ["libc.so.6", "libutil.so.1", "libutil.so"]) {
1468
+ try {
1469
+ return { core, openpty: bind(dlopen(name, OPENPTY).symbols) };
1470
+ } catch {}
1471
+ }
1472
+ throw new Error("openpty not found in libc.so.6, libutil.so.1 or libutil.so");
1473
+ }
1474
+ var ffi = load();
1475
+ var TCSANOW = 0;
1476
+ var TIOCGWINSZ = process.platform === "darwin" ? 1074295912 : 21523;
1477
+ var TIOCSWINSZ = process.platform === "darwin" ? 2148037735 : 21524;
1478
+ var SIGWINCH = 28;
1479
+ var SIGHUP = 1;
1480
+ var SIGTERM = 15;
1481
+ function saveAndSetRawStdin() {
1482
+ const cur = Buffer.alloc(128);
1483
+ if (ffi.core.tcgetattr(0, cur) !== 0)
1484
+ return null;
1485
+ const saved = Buffer.from(cur);
1486
+ ffi.core.cfmakeraw(cur);
1487
+ ffi.core.tcsetattr(0, TCSANOW, cur);
1488
+ return saved;
1489
+ }
1490
+ function restoreStdin(saved) {
1491
+ ffi.core.tcsetattr(0, TCSANOW, saved);
1492
+ }
1493
+ function setWinsize(master, rows, cols) {
1494
+ const ws = Buffer.alloc(8);
1495
+ ws.writeUInt16LE(Math.max(rows, 1) & 65535, 0);
1496
+ ws.writeUInt16LE(Math.max(cols, 1) & 65535, 2);
1497
+ return ioctlPtr(master, TIOCSWINSZ, ws);
1498
+ }
1499
+ function ioctlPtr(fd, request, arg) {
1500
+ return APPLE_VARARGS ? ffi.core.ioctl(fd, BigInt(request), 0n, 0n, 0n, 0n, 0n, 0n, arg) : ffi.core.ioctl(fd, BigInt(request), arg);
1501
+ }
1502
+ function getWinsize(fd) {
1503
+ const ws = Buffer.alloc(8);
1504
+ if (ioctlPtr(fd, TIOCGWINSZ, ws) !== 0)
1505
+ return null;
1506
+ return [ws.readUInt16LE(0), ws.readUInt16LE(2)];
1507
+ }
1508
+ function writeAll(fd, data) {
1509
+ let off = 0;
1510
+ while (off < data.length) {
1511
+ const n = Number(ffi.core.write(fd, data.subarray(off), data.length - off));
1512
+ if (n <= 0)
1513
+ return;
1514
+ off += n;
1515
+ }
1516
+ }
1517
+ var SLEEPER = new Int32Array(new SharedArrayBuffer(4));
1518
+ function sleepSync(ms) {
1519
+ Atomics.wait(SLEEPER, 0, 0, ms);
1520
+ }
1521
+
1522
+ // src/pty.ts
1523
+ function workerUrl(name) {
1524
+ return new URL(`./${name}${import.meta.url.endsWith(".ts") ? ".ts" : ".js"}`, import.meta.url);
1525
+ }
1526
+ function ptySpawn(file, args, opts) {
1527
+ const m = Buffer.alloc(4);
1528
+ const s = Buffer.alloc(4);
1529
+ const name = Buffer.alloc(128);
1530
+ const ws = Buffer.alloc(8);
1531
+ ws.writeUInt16LE(Math.max(opts.rows, 1) & 65535, 0);
1532
+ ws.writeUInt16LE(Math.max(opts.cols, 1) & 65535, 2);
1533
+ const rc = ffi.openpty.openpty(m, s, name, null, ws);
1534
+ if (rc !== 0)
1535
+ throw new Error(`openpty failed (${rc})`);
1536
+ const master = m.readInt32LE(0);
1537
+ const slave = s.readInt32LE(0);
1538
+ const proc = Bun.spawn([file, ...args], {
1539
+ stdin: slave,
1540
+ stdout: slave,
1541
+ stderr: slave,
1542
+ env: opts.env ?? environ(),
1543
+ ...opts.cwd === undefined ? {} : { cwd: opts.cwd }
1544
+ });
1545
+ closeSync(slave);
1546
+ const reader = new Worker(workerUrl("pty-read-worker"));
1547
+ const writer = new Worker(workerUrl("pty-write-worker"));
1548
+ let dataCb = null;
1549
+ let markDrained = () => {};
1550
+ const drained = new Promise((res) => {
1551
+ markDrained = res;
1552
+ });
1553
+ reader.addEventListener("message", (event) => {
1554
+ const chunk = event.data;
1555
+ if (chunk === null)
1556
+ markDrained();
1557
+ else
1558
+ dataCb?.(Buffer.from(chunk));
1559
+ });
1560
+ const exited = (async () => Number(await proc.exited))();
1561
+ let reading = false;
1562
+ return {
1563
+ pid: proc.pid,
1564
+ get exited() {
1565
+ return exited;
1566
+ },
1567
+ get drained() {
1568
+ return drained;
1569
+ },
1570
+ write(data) {
1571
+ writer.postMessage({ fd: master, data: new Uint8Array(data) });
1572
+ },
1573
+ onData(cb) {
1574
+ dataCb = cb;
1575
+ if (!reading) {
1576
+ reading = true;
1577
+ reader.postMessage({ fd: master });
1578
+ }
1579
+ },
1580
+ resize(rows, cols) {
1581
+ setWinsize(master, rows, cols);
1582
+ try {
1583
+ proc.kill(SIGWINCH);
1584
+ } catch {}
1585
+ },
1586
+ kill(signal = SIGTERM) {
1587
+ try {
1588
+ proc.kill(signal);
1589
+ } catch {}
1590
+ },
1591
+ destroy() {
1592
+ try {
1593
+ proc.kill(SIGHUP);
1594
+ } catch {}
1595
+ try {
1596
+ closeSync(master);
1597
+ } catch {}
1598
+ markDrained();
1599
+ reader.terminate();
1600
+ writer.terminate();
1601
+ }
1602
+ };
1603
+ }
1604
+
1605
+ // src/json.ts
1606
+ function isObject(v) {
1607
+ return typeof v === "object" && v !== null && v !== undefined && !Array.isArray(v);
1608
+ }
1609
+ function parseJson(text) {
1610
+ try {
1611
+ return JSON.parse(text);
1612
+ } catch {
1613
+ return null;
1614
+ }
1615
+ }
1616
+ function truthy(v) {
1617
+ if (v === null || v === false)
1618
+ return false;
1619
+ if (typeof v === "number")
1620
+ return v !== 0;
1621
+ if (typeof v === "string")
1622
+ return v.length > 0;
1623
+ if (Array.isArray(v))
1624
+ return v.length > 0;
1625
+ if (typeof v === "object")
1626
+ return Object.keys(v).length > 0;
1627
+ return v;
1628
+ }
1629
+
1630
+ // src/claude-highlight.ts
1631
+ var DOC = `claude-highlight \u2014 run Claude Code behind a PTY that colors epistemic markers.
1632
+
1633
+ claude-highlight [any claude args...]
1634
+
1635
+ Spawns the real \`claude\` on a pseudo-terminal and forwards bytes both ways,
1636
+ injecting SGR color around matched words on the way out. Because SGR is
1637
+ zero-width, the child's layout math is untouched \u2014 see highlight_filter.ts.
1638
+
1639
+ Hotkey (default F9, or bind cmd+/ in Ghostty \u2014 see README) opens a plugin
1640
+ menu to toggle categories live. Config lives at
1641
+ ~/.config/claude-highlight/config.json and is re-read whenever it changes.
1642
+ `;
1643
+ var CONFIG = configFile("claude-highlight");
1644
+ var HOTKEY = "\x1B[20~";
1645
+ var PASTE_ON = "\x1B[200~";
1646
+ var PASTE_MARK = /\x1b\[20[01]~/g;
1647
+ var IDLE = 20;
1648
+ var FORCE_IDLE = 300;
1649
+ var REPAINT_IDLE = 500;
1650
+ var DEFAULT_STYLES = {
1651
+ inference: { color: "38;5;203", on: true, desc: "hedged claim (likely, probably)" },
1652
+ unknown: { color: "38;5;170", on: true, desc: "admitted gap (untested, can't verify)" },
1653
+ assumption: { color: "38;5;214", on: true, desc: "unverified premise (assuming, in theory)" },
1654
+ appearance: { color: "38;5;179", on: true, desc: "impression (seems, looks like)" },
1655
+ overclaim: { color: "38;5;51", on: true, desc: "unearned certainty (obviously, clearly)" },
1656
+ modal: { color: "38;5;33", on: false, desc: "possibility (might, could be)" },
1657
+ vagueness: { color: "38;5;99", on: false, desc: "imprecision (roughly, several)" },
1658
+ softener: { color: "38;5;105", on: false, desc: "hedge-after-the-fact (a bit, fairly)" }
1659
+ };
1660
+ var ORDER = Object.keys(DEFAULT_STYLES);
1661
+ function wordlist(x) {
1662
+ if (typeof x === "string")
1663
+ return [x];
1664
+ return Array.isArray(x) ? x.map((w) => String(w)) : [];
1665
+ }
1666
+ function text(v, fallback) {
1667
+ return v === undefined ? fallback : String(v);
1668
+ }
1669
+ function loadConfig() {
1670
+ const parsed = existsSync(CONFIG) ? parseJson(readFileSync(CONFIG, "utf8")) : null;
1671
+ const user = isObject(parsed) ? parsed : {};
1672
+ const catsRaw = user["categories"];
1673
+ const cats = isObject(catsRaw) ? catsRaw : {};
1674
+ const customRaw = user["custom"];
1675
+ const customObj = isObject(customRaw) ? customRaw : {};
1676
+ const cfg = {};
1677
+ for (const cat of ORDER) {
1678
+ const dflt = DEFAULT_STYLES[cat];
1679
+ const specRaw = cats[cat];
1680
+ const spec = isObject(specRaw) ? specRaw : {};
1681
+ const on = spec["on"];
1682
+ cfg[cat] = {
1683
+ ...spec,
1684
+ color: text(spec["color"], dflt.color),
1685
+ on: on === undefined ? dflt.on : truthy(on),
1686
+ desc: text(spec["desc"], dflt.desc),
1687
+ add: wordlist(spec["add"])
1688
+ };
1689
+ }
1690
+ const custom = {};
1691
+ for (const [name, specRaw] of Object.entries(customObj)) {
1692
+ if (!isObject(specRaw))
1693
+ continue;
1694
+ const on = specRaw["on"];
1695
+ custom[name] = {
1696
+ color: text(specRaw["color"], "38;5;99"),
1697
+ on: on === undefined ? true : truthy(on),
1698
+ desc: text(specRaw["desc"], "custom"),
1699
+ terms: wordlist(specRaw["terms"])
1700
+ };
1701
+ if (name in cfg)
1702
+ continue;
1703
+ cfg[name] = { ...custom[name], add: custom[name].terms };
1704
+ }
1705
+ const cfgAll = {
1706
+ categories: cfg,
1707
+ custom,
1708
+ annotations: truthy(user["annotations"] ?? true),
1709
+ prose_only: truthy(user["prose_only"] ?? true),
1710
+ idle_repaint: truthy(user["idle_repaint"] ?? false)
1711
+ };
1712
+ if (!existsSync(CONFIG)) {
1713
+ mkdirSync(dirname(CONFIG), { recursive: true });
1714
+ writeFileSync(CONFIG, JSON.stringify(cfgAll, null, 2));
1715
+ }
1716
+ return cfgAll;
1717
+ }
1718
+ function selfName() {
1719
+ const argv0 = process.argv[1] ?? "";
1720
+ const base = basename(argv0);
1721
+ const typed = process.env["_"];
1722
+ const candidates = [
1723
+ ...typed === undefined ? [] : [basename(typed)],
1724
+ base,
1725
+ base.replace(/\.[jt]s$/, "")
1726
+ ];
1727
+ for (const name of candidates) {
1728
+ const found = Bun.which(name);
1729
+ if (!found)
1730
+ continue;
1731
+ try {
1732
+ const a = statSync(found), b = statSync(resolve(argv0));
1733
+ if (a.ino === b.ino && a.dev === b.dev)
1734
+ return name;
1735
+ } catch {}
1736
+ }
1737
+ return argv0;
1738
+ }
1739
+ function buildRewrites() {
1740
+ return [{ pat: /\bclaude(?= --resume\b)/g, repl: toLatin1(selfName()) }];
1741
+ }
1742
+ function toLatin1(s) {
1743
+ return Buffer.from(s, "utf8").toString("latin1");
1744
+ }
1745
+ function categoryTerms(cat, c) {
1746
+ const terms = [...LEXICON[cat] ?? []];
1747
+ const bad = [];
1748
+ for (const word of c.add) {
1749
+ const frag = userPattern(word);
1750
+ try {
1751
+ new RegExp(frag);
1752
+ } catch {
1753
+ bad.push(word);
1754
+ continue;
1755
+ }
1756
+ terms.push(frag);
1757
+ }
1758
+ return [terms, bad];
1759
+ }
1760
+ function userFragments(cfg) {
1761
+ const out = [];
1762
+ for (const [cat, c] of Object.entries(cfg.categories)) {
1763
+ out.push(...categoryTerms(cat, c)[0].slice((LEXICON[cat] ?? []).length));
1764
+ }
1765
+ return out;
1766
+ }
1767
+ function rejectedTerms(cfg) {
1768
+ const out = {};
1769
+ for (const [cat, c] of Object.entries(cfg.categories)) {
1770
+ const bad = categoryTerms(cat, c)[1];
1771
+ if (bad.length)
1772
+ out[cat] = bad;
1773
+ }
1774
+ return out;
1775
+ }
1776
+ function paintPalette(cfg) {
1777
+ const out = new Set(Object.values(cfg.categories).map((c) => c.color));
1778
+ out.add("38;5;203");
1779
+ return out;
1780
+ }
1781
+ function buildRules(cfg) {
1782
+ const rules = [];
1783
+ for (const [cat, c] of Object.entries(cfg.categories)) {
1784
+ if (!c.on)
1785
+ continue;
1786
+ const [terms] = categoryTerms(cat, c);
1787
+ if (!terms.length)
1788
+ continue;
1789
+ const pat = "\\b(?:" + terms.join("|") + ")\\b";
1790
+ rules.push({
1791
+ pat: new RegExp(toLatin1(pat), "gi"),
1792
+ style: Buffer.from(`\x1B[${c.color}m`, "latin1")
1793
+ });
1794
+ }
1795
+ if (cfg.annotations) {
1796
+ rules.push({
1797
+ pat: /\[[^\]\n]{1,80}\]\((?:low certainty|assumed|unverified)\)/g,
1798
+ style: Buffer.from("\x1B[38;5;203m", "latin1")
1799
+ });
1800
+ }
1801
+ return rules;
1802
+ }
1803
+ var REPAINT_HOLD = 80;
1804
+ function forceRepaint(pty, rows, cols) {
1805
+ pty.resize(Math.max(rows - 1, 1), cols);
1806
+ sleepSync(REPAINT_HOLD);
1807
+ pty.resize(rows, cols);
1808
+ }
1809
+ var TRUECOLOR = ["truecolor", "24bit"].includes((process.env["COLORTERM"] ?? "").toLowerCase());
1810
+ var UNICODE = (process.env["LC_ALL"] || process.env["LC_CTYPE"] || process.env["LANG"] || "utf-8").toLowerCase().replace(/-/g, "").includes("utf8");
1811
+ var c_ = (trueSeq, indexed) => TRUECOLOR ? trueSeq : indexed;
1812
+ var PANEL_BG = c_("48;2;38;42;64", "48;5;236");
1813
+ var PANEL_FG = c_("38;2;205;212;232", "38;5;252");
1814
+ var PANEL_DIM = c_("38;2;132;140;170", "38;5;245");
1815
+ var BORDER_FG = c_("38;2;122;162;247", "38;5;75");
1816
+ var TITLE_BG = c_("48;2;122;162;247", "48;5;75");
1817
+ var TITLE_FG = c_("38;2;16;18;28", "38;5;235");
1818
+ var SEL_BG = c_("48;2;64;74;112", "48;5;238");
1819
+ var SEL_FG = c_("38;2;255;255;255", "38;5;255");
1820
+ var SEL_BAR = c_("38;2;255;214;102", "38;5;221");
1821
+ var G = UNICODE ? {
1822
+ tl: "\u256D",
1823
+ tr: "\u256E",
1824
+ bl: "\u2570",
1825
+ br: "\u256F",
1826
+ h: "\u2500",
1827
+ v: "\u2502",
1828
+ vsel: "\u2503",
1829
+ mark: " \u25B8 ",
1830
+ swatch: "\u2588\u2588",
1831
+ up: "\u2191\u2193",
1832
+ dot: "\xB7"
1833
+ } : {
1834
+ tl: "+",
1835
+ tr: "+",
1836
+ bl: "+",
1837
+ br: "+",
1838
+ h: "-",
1839
+ v: "|",
1840
+ vsel: "|",
1841
+ mark: " > ",
1842
+ swatch: "##",
1843
+ up: "up/dn",
1844
+ dot: "-"
1845
+ };
1846
+ function row(segments, width, bg) {
1847
+ let out = `\x1B[${bg}m`;
1848
+ let used = 0;
1849
+ for (const [raw, fg] of segments) {
1850
+ if (used >= width)
1851
+ break;
1852
+ const text = raw.slice(0, width - used);
1853
+ used += text.length;
1854
+ out += (fg ? `\x1B[${fg}m` : "") + text;
1855
+ }
1856
+ return out + " ".repeat(Math.max(0, width - used)) + "\x1B[0m";
1857
+ }
1858
+ function menuLines(cfg, cols, sel) {
1859
+ const w = Math.max(46, cols - 1);
1860
+ const inner = w - 2;
1861
+ const title = ` claude-highlight ${G["dot"]} plugins `;
1862
+ const lines = [
1863
+ row([
1864
+ [G["tl"], BORDER_FG],
1865
+ [title, null],
1866
+ [G["h"].repeat(Math.max(0, inner - title.length)), BORDER_FG],
1867
+ [G["tr"], BORDER_FG]
1868
+ ], w, TITLE_BG + ";" + TITLE_FG)
1869
+ ];
1870
+ const cats = Object.keys(cfg.categories);
1871
+ for (let i = 0;i < cats.length; i++) {
1872
+ const cat = cats[i];
1873
+ const c = cfg.categories[cat];
1874
+ const selected = i === sel;
1875
+ const bg = selected ? SEL_BG : PANEL_BG;
1876
+ const fg = selected ? SEL_FG : PANEL_FG;
1877
+ lines.push(row([
1878
+ [selected ? G["vsel"] : G["v"], selected ? SEL_BAR : BORDER_FG],
1879
+ [selected ? G["mark"] : " ".repeat(G["mark"].length), selected ? SEL_FG : PANEL_DIM],
1880
+ [`[${c.on ? "on " : "off"}] `, fg],
1881
+ [G["swatch"], c.color],
1882
+ [` ${cat.padEnd(11)}`, fg],
1883
+ [c.desc, selected ? fg : PANEL_DIM]
1884
+ ], w - 1, bg) + `\x1B[${bg}m\x1B[${BORDER_FG}m` + G["v"] + "\x1B[0m");
1885
+ }
1886
+ const hint = ` ${G["up"]} move ${G["dot"]} space / enter toggle ` + `${G["dot"]} q / esc / F9 close `;
1887
+ lines.push(row([
1888
+ [G["bl"], BORDER_FG],
1889
+ [hint, TITLE_FG],
1890
+ [G["h"].repeat(Math.max(0, inner - hint.length)), BORDER_FG],
1891
+ [G["br"], BORDER_FG]
1892
+ ], w, TITLE_BG));
1893
+ return lines;
1894
+ }
1895
+ function drawMenu(cfg, rows, cols, sel) {
1896
+ const lines = menuLines(cfg, cols, sel);
1897
+ const top = Math.max(rows - lines.length, 0);
1898
+ const out = ["\x1B7", "\x1B[?25l"];
1899
+ lines.forEach((text, i) => out.push(`\x1B[${top + i + 1};1H\x1B[2K` + text));
1900
+ out.push("\x1B8");
1901
+ writeAll(1, Buffer.from(out.join(""), "utf8"));
1902
+ }
1903
+ var PALETTE = [
1904
+ [
1905
+ "reds \u2014 loudest, for the claim you most want to catch",
1906
+ "inference",
1907
+ [203, 196, 202, 209, 167, 174, 210, 168]
1908
+ ],
1909
+ [
1910
+ "pinks and magentas \u2014 loud but not alarming",
1911
+ "unknown",
1912
+ [170, 176, 177, 183, 213, 205, 141, 218]
1913
+ ],
1914
+ [
1915
+ "ambers and yellows \u2014 warm, reads as 'check this'",
1916
+ "assumption",
1917
+ [214, 215, 220, 221, 222, 178, 136, 223]
1918
+ ],
1919
+ [
1920
+ "golds and tans \u2014 quieter warmth, good for a busy category",
1921
+ "appearance",
1922
+ [179, 180, 187, 144, 137, 173, 143, 229]
1923
+ ],
1924
+ [
1925
+ "cyans and teals \u2014 cold, opposite end from the reds",
1926
+ "overclaim",
1927
+ [51, 45, 44, 80, 87, 116, 73, 37]
1928
+ ],
1929
+ [
1930
+ "greens",
1931
+ "unused \u2014 free for a category of your own",
1932
+ [71, 78, 108, 114, 150, 84, 42, 155]
1933
+ ],
1934
+ [
1935
+ "blues and violets \u2014 cool, and nothing in warm prose competes with them",
1936
+ "modal, vagueness, softener",
1937
+ [33, 39, 99, 105, 27, 63, 93, 135]
1938
+ ]
1939
+ ];
1940
+ function paletteLines(cfg, cols, full = false) {
1941
+ const out = ["", " in use now"];
1942
+ for (const [cat, c] of Object.entries(cfg.categories)) {
1943
+ const state = c.on ? "on " : "off";
1944
+ out.push(` \x1B[${c.color}m${cat.padEnd(11)}\x1B[0m ${state} ` + `${c.color.padEnd(10)} ${c.desc}`);
1945
+ }
1946
+ out.push("", " to change one, edit the config -- it is re-read live, so the next", " line Claude Code prints already has the new colour:", "", ` ${CONFIG}`, ' { "categories": { "assumption": { "color": "38;5;220" } } }');
1947
+ const word = "mostly";
1948
+ const cell = word.length + 5;
1949
+ const perRow = Math.max(1, Math.floor((cols - 6) / cell));
1950
+ for (const [title, who, codes] of PALETTE) {
1951
+ out.push("", ` ${title}`, ` (${who})`);
1952
+ for (let i = 0;i < codes.length; i += perRow) {
1953
+ const line = codes.slice(i, i + perRow).map((n) => `\x1B[38;5;${n}m${word}\x1B[39m ${String(n).padEnd(3)} `).join("");
1954
+ out.push(" " + line);
1955
+ }
1956
+ }
1957
+ if (full) {
1958
+ out.push("", " the whole 256-colour ramp");
1959
+ for (let base = 16;base < 256; base += 12) {
1960
+ let line = "";
1961
+ for (let n = base;n < Math.min(base + 12, 256); n++) {
1962
+ line += `\x1B[38;5;${n}m\u2588\u2588\x1B[39m${String(n).padEnd(4)}`;
1963
+ }
1964
+ out.push(" " + line);
1965
+ }
1966
+ }
1967
+ out.push("");
1968
+ return out;
1969
+ }
1970
+ function selftest(cfg) {
1971
+ const hl = new AnsiHighlighter(buildRules(cfg), cfg.prose_only);
1972
+ const samples = [
1973
+ ["prose", "", "It seems likely this is probably fine, assuming nothing breaks."],
1974
+ ["bold prose", "\x1B[1m", "That seems untested and I can't verify it.\x1B[22m"],
1975
+ ["admissions", "", "I can't test this for you; the result is unverified."],
1976
+ ["overclaim", "", "Obviously this clearly works and definitely always will."],
1977
+ ["inline code", "\x1B[38;2;177;185;249m", "it seems likely here\x1B[39m"],
1978
+ ["fenced code", "\x1B[32m", "# it seems likely that this is code\x1B[39m"],
1979
+ [
1980
+ "user message",
1981
+ "\x1B[48;2;55;55;55m\x1B[38;2;255;255;255m",
1982
+ "it seems likely this is what you typed\x1B[0m"
1983
+ ]
1984
+ ];
1985
+ const out = [
1986
+ Buffer.from(`
1987
+ claude-highlight self-test - top four lines should show color,`, "latin1"),
1988
+ Buffer.from(` bottom three should be untouched.
1989
+ `, "latin1")
1990
+ ];
1991
+ for (const [label, prefix, text] of samples) {
1992
+ const body = Buffer.from(prefix + text, "latin1");
1993
+ out.push(Buffer.concat([
1994
+ Buffer.from(` ${label.padEnd(13)}`, "latin1"),
1995
+ hl.feed(body),
1996
+ hl.drain(),
1997
+ Buffer.from("\x1B[0m", "latin1")
1998
+ ]));
1999
+ }
2000
+ out.push(Buffer.alloc(0));
2001
+ const bad = rejectedTerms(cfg);
2002
+ if (Object.keys(bad).length) {
2003
+ out.push(Buffer.from(" \x1B[38;5;203mrejected config words (invalid patterns):\x1B[39m", "latin1"));
2004
+ for (const [cat, words] of Object.entries(bad)) {
2005
+ out.push(Buffer.from(` ${cat}: ${words.join(", ")}`, "utf8"));
2006
+ }
2007
+ out.push(Buffer.alloc(0));
2008
+ }
2009
+ for (const [cat, c] of Object.entries(cfg.categories)) {
2010
+ const state = c.on ? "on " : "off";
2011
+ const extra = c.add.length;
2012
+ const tag = extra ? ` +${extra} from config` : "";
2013
+ out.push(Buffer.from(` \x1B[${c.color}m${cat.padEnd(11)}\x1B[0m ${state} ${c.desc}${tag}`, "utf8"));
2014
+ }
2015
+ writeAll(1, Buffer.concat([joinBuf(out, `
2016
+ `), Buffer.from(`
2017
+ `, "latin1")]));
2018
+ return 0;
2019
+ }
2020
+ function joinBuf(parts, sep) {
2021
+ const s = Buffer.from(sep, "latin1");
2022
+ const out = [];
2023
+ parts.forEach((p, i) => {
2024
+ if (i)
2025
+ out.push(s);
2026
+ out.push(p);
2027
+ });
2028
+ return Buffer.concat(out);
2029
+ }
2030
+ function parseArgs(argv) {
2031
+ const a = {
2032
+ hlHelp: false,
2033
+ hlSelftest: false,
2034
+ hlMenu: false,
2035
+ hlPalette: null,
2036
+ hlRecord: null,
2037
+ rest: []
2038
+ };
2039
+ for (let i = 0;i < argv.length; i++) {
2040
+ const arg = argv[i];
2041
+ const eq = arg.indexOf("=");
2042
+ const name = eq === -1 ? arg : arg.slice(0, eq);
2043
+ const inline = eq === -1 ? null : arg.slice(eq + 1);
2044
+ if (name === "--hl-help")
2045
+ a.hlHelp = true;
2046
+ else if (name === "--hl-selftest")
2047
+ a.hlSelftest = true;
2048
+ else if (name === "--hl-menu")
2049
+ a.hlMenu = true;
2050
+ else if (name === "--hl-palette") {
2051
+ if (inline !== null)
2052
+ a.hlPalette = inline;
2053
+ else {
2054
+ const next = argv[i + 1];
2055
+ if (next !== undefined && !next.startsWith("-")) {
2056
+ a.hlPalette = next;
2057
+ i++;
2058
+ } else
2059
+ a.hlPalette = "curated";
2060
+ }
2061
+ } else if (name === "--hl-record") {
2062
+ if (inline !== null)
2063
+ a.hlRecord = inline;
2064
+ else if (argv[i + 1] !== undefined) {
2065
+ a.hlRecord = argv[i + 1];
2066
+ i++;
2067
+ }
2068
+ } else {
2069
+ a.rest.push(arg);
2070
+ }
2071
+ }
2072
+ return a;
2073
+ }
2074
+ function stdoutCols() {
2075
+ const sz = getWinsize(1);
2076
+ if (sz && sz[1])
2077
+ return sz[1];
2078
+ return Number(process.env["COLUMNS"]) || 100;
2079
+ }
2080
+ async function main(argv) {
2081
+ const known = parseArgs(argv);
2082
+ if (known.hlHelp) {
2083
+ writeAll(1, Buffer.from(DOC + `
2084
+ `, "utf8"));
2085
+ return 0;
2086
+ }
2087
+ let cfg = loadConfig();
2088
+ if (known.hlSelftest)
2089
+ return selftest(cfg);
2090
+ if (known.hlPalette) {
2091
+ writeAll(1, Buffer.from(paletteLines(cfg, stdoutCols(), known.hlPalette === "all").join(`
2092
+ `), "utf8"));
2093
+ return 0;
2094
+ }
2095
+ if (known.hlMenu) {
2096
+ writeAll(1, Buffer.from(`
2097
+ ` + menuLines(cfg, stdoutCols(), 0).join(`
2098
+ `) + `
2099
+
2100
+ `, "utf8"));
2101
+ return 0;
2102
+ }
2103
+ let mtime = existsSync(CONFIG) ? statSync(CONFIG).mtimeMs : 0;
2104
+ const hl = new AnsiHighlighter(buildRules(cfg), cfg.prose_only);
2105
+ hl.rewrites = buildRewrites();
2106
+ hl.holdPrefixes = growablePrefixes(userFragments(cfg));
2107
+ const screen = new ScreenModel(24, 80, paintPalette(cfg));
2108
+ const child = process.env["CLAUDE_HIGHLIGHT_CMD"] ?? "claude";
2109
+ const recRaw = known.hlRecord === null ? null : openSync(known.hlRecord + ".raw", "w");
2110
+ const recOut = known.hlRecord === null ? null : openSync(known.hlRecord + ".out", "w");
2111
+ let [rows, cols] = getWinsize(1) ?? [24, 80];
2112
+ const pty = ptySpawn(child, known.rest, { rows, cols, env: environ() });
2113
+ screen.resize(rows, cols);
2114
+ const old = saveAndSetRawStdin();
2115
+ process.on("SIGWINCH", () => {
2116
+ const sz = getWinsize(1);
2117
+ [rows, cols] = sz ?? [24, 80];
2118
+ pty.resize(rows, cols);
2119
+ screen.resize(rows, cols);
2120
+ });
2121
+ let menu = false, sel = 0, pendingIn = "", pasting = false;
2122
+ let repainted = true;
2123
+ let lastData = performance.now();
2124
+ let sawData = false;
2125
+ let stdinOpen = true;
2126
+ const emit = (out) => {
2127
+ if (!out.length)
2128
+ return;
2129
+ writeAll(1, out);
2130
+ if (recOut !== null)
2131
+ writeSync(recOut, out);
2132
+ };
2133
+ const closeMenu = () => {
2134
+ menu = false;
2135
+ screen.invalidate();
2136
+ writeAll(1, Buffer.from("\x1B[?25h", "latin1"));
2137
+ writeFileSync(CONFIG, JSON.stringify(cfg, null, 2));
2138
+ mtime = statSync(CONFIG).mtimeMs;
2139
+ forceRepaint(pty, rows, cols);
2140
+ };
2141
+ pty.onData((data) => {
2142
+ sawData = true;
2143
+ lastData = performance.now();
2144
+ repainted = false;
2145
+ if (recRaw !== null)
2146
+ writeSync(recRaw, data);
2147
+ if (!menu) {
2148
+ emit(screen.reconcile(hl.feed(data), hl.rules, hl.onlyUnstyled));
2149
+ } else {
2150
+ hl.feed(data);
2151
+ screen.invalidate();
2152
+ }
2153
+ });
2154
+ const onStdin = (data) => {
2155
+ sawData = true;
2156
+ let buf = pendingIn + data.toString("latin1");
2157
+ pendingIn = "";
2158
+ if (menu) {
2159
+ if (buf.includes(PASTE_ON)) {
2160
+ closeMenu();
2161
+ } else {
2162
+ for (const key of buf.match(/\x1b\[[A-D]|[\s\S]/g) ?? []) {
2163
+ const cats = Object.keys(cfg.categories);
2164
+ if (key === "\x1B[A")
2165
+ sel = (sel - 1 + cats.length) % cats.length;
2166
+ else if (key === "\x1B[B")
2167
+ sel = (sel + 1) % cats.length;
2168
+ else if (key === " " || key === "\r" || key === `
2169
+ `) {
2170
+ const cat = cats[sel];
2171
+ cfg.categories[cat].on = !cfg.categories[cat].on;
2172
+ hl.rules = buildRules(cfg);
2173
+ } else if (key === "q" || key === "\x1B") {
2174
+ closeMenu();
2175
+ break;
2176
+ }
2177
+ }
2178
+ if (menu)
2179
+ drawMenu(cfg, rows, cols, sel);
2180
+ return;
2181
+ }
2182
+ }
2183
+ const toChild = [];
2184
+ let pos = 0, opened = false;
2185
+ PASTE_MARK.lastIndex = 0;
2186
+ for (const m of buf.matchAll(PASTE_MARK)) {
2187
+ let seg = buf.slice(pos, m.index);
2188
+ if (!pasting && seg.includes(HOTKEY)) {
2189
+ seg = seg.split(HOTKEY).join("");
2190
+ opened = true;
2191
+ }
2192
+ toChild.push(seg + m[0]);
2193
+ pasting = m[0] === PASTE_ON;
2194
+ pos = m.index + m[0].length;
2195
+ }
2196
+ buf = buf.slice(pos);
2197
+ if (!pasting && buf.includes(HOTKEY)) {
2198
+ buf = buf.split(HOTKEY).join("");
2199
+ opened = true;
2200
+ }
2201
+ if (opened) {
2202
+ menu = true;
2203
+ sel = 0;
2204
+ screen.invalidate();
2205
+ drawMenu(cfg, rows, cols, sel);
2206
+ }
2207
+ for (let n = 1;n < (pasting ? 0 : HOTKEY.length); n++) {
2208
+ if (buf.endsWith(HOTKEY.slice(0, n))) {
2209
+ pendingIn = buf.slice(buf.length - n);
2210
+ buf = buf.slice(0, buf.length - n);
2211
+ break;
2212
+ }
2213
+ }
2214
+ if (buf)
2215
+ toChild.push(buf);
2216
+ if (toChild.length)
2217
+ pty.write(Buffer.from(toChild.join(""), "latin1"));
2218
+ };
2219
+ const stdinReader = new Worker(workerUrl("pty-read-worker"));
2220
+ stdinReader.addEventListener("message", (event) => {
2221
+ const chunk = event.data;
2222
+ if (chunk === null)
2223
+ stdinOpen = false;
2224
+ else if (stdinOpen)
2225
+ onStdin(Buffer.from(chunk));
2226
+ });
2227
+ stdinReader.postMessage({ fd: 0 });
2228
+ const tick = () => {
2229
+ if (sawData) {
2230
+ sawData = false;
2231
+ return;
2232
+ }
2233
+ const now = performance.now();
2234
+ emit(screen.reconcile(hl.drain(now - lastData > FORCE_IDLE), hl.rules, hl.onlyUnstyled));
2235
+ if (cfg.idle_repaint && !menu && !repainted && now - lastData > REPAINT_IDLE) {
2236
+ repainted = true;
2237
+ forceRepaint(pty, rows, cols);
2238
+ }
2239
+ if (existsSync(CONFIG) && statSync(CONFIG).mtimeMs !== mtime) {
2240
+ mtime = statSync(CONFIG).mtimeMs;
2241
+ cfg = loadConfig();
2242
+ hl.rules = buildRules(cfg);
2243
+ hl.onlyUnstyled = cfg.prose_only;
2244
+ hl.holdPrefixes = growablePrefixes(userFragments(cfg));
2245
+ hl.rewrites = buildRewrites();
2246
+ screen.palette = paintPalette(cfg);
2247
+ }
2248
+ };
2249
+ const timer = setInterval(tick, IDLE);
2250
+ await pty.drained;
2251
+ clearInterval(timer);
2252
+ stdinReader.terminate();
2253
+ if (old !== null)
2254
+ restoreStdin(old);
2255
+ try {
2256
+ emit(hl.drain(true));
2257
+ } catch {}
2258
+ const code = await pty.exited;
2259
+ pty.destroy();
2260
+ return code;
2261
+ }
2262
+ if (import.meta.main) {
2263
+ process.exit(await main(process.argv.slice(2)));
2264
+ }
2265
+ export {
2266
+ CONFIG,
2267
+ ORDER,
2268
+ buildRewrites,
2269
+ buildRules,
2270
+ categoryTerms,
2271
+ drawMenu,
2272
+ loadConfig,
2273
+ main,
2274
+ menuLines,
2275
+ paintPalette,
2276
+ paletteLines,
2277
+ parseArgs,
2278
+ rejectedTerms,
2279
+ selfName,
2280
+ selftest,
2281
+ userFragments
2282
+ };