@idl3/claude-control 0.1.13 → 0.1.20

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/lib/answer.js CHANGED
@@ -1,53 +1,85 @@
1
1
  // Translate an AskUserQuestion selection into Claude Code TUI picker keystrokes.
2
2
  //
3
- // Verified against the live Claude Code picker (a multi-question tabbed UI):
4
- // - Options are NUMBERED (1..N) in question.options order, with extra meta
5
- // options ("Type something", "Chat about this") appended after.
6
- // - SINGLE-select: pressing the option's number selects it AND auto-advances
7
- // to the next question tab.
8
- // - MULTI-select: pressing a number TOGGLES that option (cursor stays); press
9
- // Right (→) to advance to the next tab.
10
- // - After the last question the picker lands on the "Submit" tab showing
11
- // "1. Submit answers / 2. Cancel"; pressing "1" commits all answers.
12
- // Keys are sent one at a time with a small delay (see tmux.sendRawKeysSequenced)
13
- // so the single-select auto-advance re-render completes before the next key.
14
-
15
- const MAX_NUMBERED = 9; // number-key selection only works for options 1..9
16
-
17
- function numberKey(optionIndex) {
18
- const n = optionIndex + 1;
19
- if (n > MAX_NUMBERED) {
20
- throw new Error(`option #${n} beyond number-key range (1..${MAX_NUMBERED})`);
21
- }
22
- return String(n);
23
- }
3
+ // Key model — matches the live picker footer and the CONTRACT.md spec:
4
+ // footer: "Enter to select · ↑/↓ to navigate · n to add notes · Tab to switch
5
+ // questions · Esc to cancel"
6
+ // spec: single-select = ['Down'*index, 'Enter'];
7
+ // multi-select = navigate Down to each chosen index, press Space, then Enter.
8
+ //
9
+ // - Each question lists its options vertically; a cursor starts on the FIRST
10
+ // option (index 0) and moves with Up/Down. There are NO number shortcuts
11
+ // digits do not select, so the previous number-key model was a no-op against
12
+ // this UI (the cause of "answer sent but nothing happened").
13
+ // - SINGLE-select: navigate Down to the chosen option, then press Enter. Enter
14
+ // commits the answer and advances to the next question (or submits on the last).
15
+ // - MULTI-select: navigate Down to each chosen option (top-to-bottom, so a
16
+ // monotonic run of Downs) pressing Space to toggle it, then press Enter to
17
+ // confirm the question and advance/submit.
18
+ // - There is no separate "Submit" step: the final question's Enter submits the
19
+ // whole picker. (The old `Right`-to-Submit-tab + `'1'` model was stale.)
20
+ //
21
+ // We deliberately avoid the `n` (add notes) key: it opens a free-text input that
22
+ // would swallow every subsequent keystroke. Navigation is arrows + Space/Enter only.
23
+ //
24
+ // Keys are sent one at a time with a delay (see tmux.sendRawKeysSequenced) so the
25
+ // picker's re-render settles between keys and none are dropped.
24
26
 
25
27
  /**
26
- * Keys to answer ONE question (not including the final Submit).
27
- * @param {{multiSelect?: boolean, options: {label:string}[]}} question
28
+ * Resolve the selected labels to option indices, in top-to-bottom order.
29
+ * @param {{options: {label:string}[]}} question
28
30
  * @param {string[]} selectedLabels
29
- * @returns {string[]}
31
+ * @returns {number[]} ascending option indices
30
32
  */
31
- export function buildAnswerKeys(question, selectedLabels) {
33
+ function selectedIndices(question, selectedLabels) {
32
34
  const options = Array.isArray(question?.options) ? question.options : [];
33
35
  const indices = (selectedLabels || [])
34
36
  .map((label) => options.findIndex((o) => o.label === label))
35
37
  .filter((i) => i >= 0)
36
38
  .sort((a, b) => a - b);
37
-
38
39
  if (indices.length === 0) throw new Error('no valid option selected for question');
40
+ return indices;
41
+ }
42
+
43
+ /**
44
+ * Keys to answer ONE question, including the trailing Enter that confirms it and
45
+ * advances the picker (or submits, on the final question).
46
+ *
47
+ * Single-select: Down×index, Enter.
48
+ * Multi-select: for each chosen index (ascending) Down to it (relative to the
49
+ * current cursor) then Space to toggle; finally Enter to confirm.
50
+ *
51
+ * @param {{multiSelect?: boolean, options: {label:string}[]}} question
52
+ * @param {string[]} selectedLabels
53
+ * @returns {string[]}
54
+ */
55
+ export function buildAnswerKeys(question, selectedLabels) {
56
+ const indices = selectedIndices(question, selectedLabels);
57
+ const keys = [];
39
58
 
40
59
  if (!question.multiSelect) {
41
- // Selecting a numbered option auto-advances to the next tab.
42
- return [numberKey(indices[0])];
60
+ for (let i = 0; i < indices[0]; i += 1) keys.push('Down');
61
+ keys.push('Enter');
62
+ return keys;
63
+ }
64
+
65
+ // Multi-select: walk down through the chosen options in order, toggling each
66
+ // with Space; the cursor starts at option 0, so move only the delta between
67
+ // successive targets. A trailing Enter confirms the question.
68
+ let cursor = 0;
69
+ for (const target of indices) {
70
+ for (let i = cursor; i < target; i += 1) keys.push('Down');
71
+ keys.push('Space');
72
+ cursor = target;
43
73
  }
44
- // Toggle each chosen option, then advance to the next tab with Right.
45
- return [...indices.map(numberKey), 'Right'];
74
+ keys.push('Enter');
75
+ return keys;
46
76
  }
47
77
 
48
78
  /**
49
- * Full key program for a (possibly multi-question) AskUserQuestion, ending with
50
- * the Submit-tab confirmation.
79
+ * Full key program for a (possibly multi-question) AskUserQuestion. Each question
80
+ * ends with the Enter that confirms it and advances; the last question's Enter
81
+ * submits the whole picker.
82
+ *
51
83
  * @param {{questions: object[]}} pending
52
84
  * @param {string[][]} selections selections[i] = chosen labels for questions[i]
53
85
  * @returns {string[]}
@@ -59,6 +91,5 @@ export function buildAnswerProgram(pending, selections) {
59
91
  for (let i = 0; i < questions.length; i += 1) {
60
92
  program.push(...buildAnswerKeys(questions[i], selections?.[i] || []));
61
93
  }
62
- program.push('1'); // "Submit answers" on the Submit tab
63
94
  return program;
64
95
  }
package/lib/sessions.js CHANGED
@@ -31,6 +31,7 @@ function isClaudeCmd(cmd) {
31
31
  const TAIL_BYTES = 64 * 1024; // 64 KB max tail read
32
32
  const REFRESH_INTERVAL_MS = 4000;
33
33
  const CTX_POLL_INTERVAL_MS = 12000; // TUI ctx%/model capture — slower than refresh
34
+ const THINKING_POLL_INTERVAL_MS = 2000; // bottom-5-line capture for the live "thinking" flag
34
35
 
35
36
  /**
36
37
  * Encode an absolute cwd the way Claude Code names its transcript project
@@ -329,10 +330,14 @@ export class SessionRegistry extends EventEmitter {
329
330
  this._pendingMap = new Map();
330
331
  /** @type {Map<string, {ctxPct:number|null, model:string|null}>} target -> TUI status */
331
332
  this._ctxMap = new Map();
333
+ /** @type {Map<string, boolean>} target -> actively-generating flag */
334
+ this._thinkingMap = new Map();
332
335
  /** @type {ReturnType<setInterval>|null} */
333
336
  this._interval = null;
334
337
  /** @type {ReturnType<setInterval>|null} */
335
338
  this._ctxInterval = null;
339
+ /** @type {ReturnType<setInterval>|null} */
340
+ this._thinkingInterval = null;
336
341
  }
337
342
 
338
343
  // -------------------------------------------------------------------------
@@ -465,6 +470,7 @@ export class SessionRegistry extends EventEmitter {
465
470
  isClaude: true,
466
471
  model: ctx.model || prettyModel(transcript?.model) || null,
467
472
  ctxPct: ctx.ctxPct ?? null,
473
+ thinking: this._thinkingMap.get(win.target) ?? false,
468
474
  };
469
475
  });
470
476
 
@@ -500,17 +506,50 @@ export class SessionRegistry extends EventEmitter {
500
506
  this._maybeEmit();
501
507
  }
502
508
 
503
- /** Start periodic refresh (every 4 s) + a slower ctx poll, and fire both once. */
509
+ /**
510
+ * Fast, cheap poll for the live "thinking" flag. Captures only the bottom ~5
511
+ * lines of each Claude pane and updates ONLY the thinking flag — the
512
+ * model/ctx values are left to the slower _pollCtx(). Best-effort.
513
+ */
514
+ async _pollThinking() {
515
+ const sessions = this._sessions;
516
+ await Promise.all(
517
+ sessions.map(async (s) => {
518
+ if (!this._tmux.isValidTarget(s.target)) return;
519
+ try {
520
+ const cap = await this._tmux.capturePane(s.target, 5);
521
+ const { thinking } = parseTuiStatus(cap);
522
+ this._thinkingMap.set(s.target, thinking);
523
+ // Merge into the live session object without a full rebuild.
524
+ s.thinking = thinking;
525
+ } catch {
526
+ // pane gone / capture failed — leave previous value
527
+ }
528
+ }),
529
+ );
530
+ this._maybeEmit();
531
+ }
532
+
533
+ /**
534
+ * Start periodic refresh (every 4 s) + a slower ctx poll + a fast thinking
535
+ * poll, and fire each once.
536
+ */
504
537
  start() {
505
- this.refresh().then(() => this._pollCtx()).catch(() => {});
538
+ this.refresh()
539
+ .then(() => Promise.all([this._pollCtx(), this._pollThinking()]))
540
+ .catch(() => {});
506
541
  this._interval = setInterval(() => {
507
542
  this.refresh().catch(() => {});
508
543
  }, REFRESH_INTERVAL_MS);
509
544
  this._ctxInterval = setInterval(() => {
510
545
  this._pollCtx().catch(() => {});
511
546
  }, CTX_POLL_INTERVAL_MS);
547
+ this._thinkingInterval = setInterval(() => {
548
+ this._pollThinking().catch(() => {});
549
+ }, THINKING_POLL_INTERVAL_MS);
512
550
  if (this._interval.unref) this._interval.unref();
513
551
  if (this._ctxInterval.unref) this._ctxInterval.unref();
552
+ if (this._thinkingInterval.unref) this._thinkingInterval.unref();
514
553
  }
515
554
 
516
555
  /** Stop periodic refresh. */
@@ -523,6 +562,10 @@ export class SessionRegistry extends EventEmitter {
523
562
  clearInterval(this._ctxInterval);
524
563
  this._ctxInterval = null;
525
564
  }
565
+ if (this._thinkingInterval) {
566
+ clearInterval(this._thinkingInterval);
567
+ this._thinkingInterval = null;
568
+ }
526
569
  }
527
570
 
528
571
  // -------------------------------------------------------------------------
package/lib/tmux.js CHANGED
@@ -436,16 +436,16 @@ export async function sendRawKeys(target, keys) {
436
436
 
437
437
  /**
438
438
  * Send key names ONE AT A TIME with a delay between each. Needed for the
439
- * AskUserQuestion picker, whose single-select number keys trigger an async
440
- * tab-advance re-render — firing the next key too soon lands it on the wrong
441
- * question.
439
+ * AskUserQuestion picker: each Down/Space/Enter triggers an async re-render, and
440
+ * firing the next key before it settles drops the key (so the picker stalls and
441
+ * the answer silently never lands).
442
442
  *
443
443
  * @param {string} target
444
444
  * @param {string[]} keys
445
- * @param {number} [delayMs=130]
445
+ * @param {number} [delayMs=160]
446
446
  * @returns {Promise<void>}
447
447
  */
448
- export async function sendRawKeysSequenced(target, keys, delayMs = 130) {
448
+ export async function sendRawKeysSequenced(target, keys, delayMs = 160) {
449
449
  assertTarget(target);
450
450
  if (!Array.isArray(keys) || keys.length === 0) return;
451
451
  for (let i = 0; i < keys.length; i += 1) {
package/lib/transcript.js CHANGED
@@ -1,12 +1,41 @@
1
1
  // lib/transcript.js — bounded transcript tailing for claude-cockpit.
2
2
  // Resource doctrine: NEVER read a whole file. Initial load reads only the last
3
- // min(size, 1 MB) bytes (tail), then watches and reads ONLY new bytes via offset.
4
- // Files can be 200 MB+; whole-file reads will blow RAM.
3
+ // min(size, TAIL_MAX_BYTES) bytes (tail), then watches and reads ONLY new bytes
4
+ // via offset. Files can be 200 MB+; whole-file reads will blow RAM.
5
5
 
6
6
  import fs from 'node:fs';
7
7
  import { EventEmitter } from 'node:events';
8
8
 
9
- const TAIL_MAX_BYTES = 1 * 1024 * 1024; // 1 MB initial tail cap
9
+ // Env lookup mirroring server.js: prefer CLAUDE_CONTROL_<X>, fall back to the
10
+ // legacy COCKPIT_<X> so existing launchers keep working after the rename.
11
+ function envInt(name) {
12
+ const raw =
13
+ process.env[`CLAUDE_CONTROL_${name}`] ?? process.env[`COCKPIT_${name}`];
14
+ if (raw == null) return null;
15
+ const n = Number(raw);
16
+ return Number.isFinite(n) && n > 0 ? n : null;
17
+ }
18
+
19
+ // Initial-tail byte cap. A fresh subscribe reads only the last min(size,
20
+ // TAIL_MAX_BYTES) bytes of the JSONL (NEVER the whole file — transcripts reach
21
+ // 200 MB+). In busy sessions a single assistant turn can carry hundreds of KB
22
+ // of tool output, so the old 1 MB window held only a handful of messages and
23
+ // the user's own recent turns fell outside it — they vanished on reload.
24
+ //
25
+ // 8 MB is the balance point: at a few KB/record it yields several hundred to a
26
+ // few thousand messages (enough that the message-count cap, not bytes, governs
27
+ // what a fresh subscribe serves), while staying ~25x below the largest real
28
+ // files and bounded per open session. A phone renders a capped subset anyway.
29
+ // Override with CLAUDE_CONTROL_TAIL_BYTES (legacy: COCKPIT_TAIL_BYTES).
30
+ const TAIL_MAX_BYTES = envInt('TAIL_BYTES') ?? 8 * 1024 * 1024; // 8 MB
31
+
32
+ // Default message-count cap for the in-memory buffer. Raised 1500 → 4000 so a
33
+ // fresh subscribe serves deeper scrollback (within the 8 MB tail window the
34
+ // count cap, not the byte window, governs how much history is served). At a few
35
+ // hundred bytes/normalized message this is a few MB resident per open session,
36
+ // well within the server's RSS budget.
37
+ // Override with CLAUDE_CONTROL_MAX_BUFFER (legacy: COCKPIT_MAX_BUFFER).
38
+ const DEFAULT_MAX_BUFFER = envInt('MAX_BUFFER') ?? 4000;
10
39
 
11
40
  // ---------------------------------------------------------------------------
12
41
  // Internal helper: read the last `maxBytes` of a file without loading it all.
@@ -167,7 +196,7 @@ export class TranscriptTailer extends EventEmitter {
167
196
  * @param {string} filePath
168
197
  * @param {{ maxBuffer?: number, debounceMs?: number }} options
169
198
  */
170
- constructor(filePath, { maxBuffer = 500, debounceMs = 150 } = {}) {
199
+ constructor(filePath, { maxBuffer = DEFAULT_MAX_BUFFER, debounceMs = 150 } = {}) {
171
200
  super();
172
201
  this._filePath = filePath;
173
202
  this._maxBuffer = maxBuffer;
package/lib/tui.js CHANGED
@@ -4,16 +4,21 @@
4
4
  // /claude-cockpit Opus 4.8 (1M context) ctx:35% Remote Control active
5
5
  // and a title rule line such as:
6
6
  // ───────────────────── auto-cleanup-uploads ──
7
- // We extract the model label, the context-remaining percentage, and (best
8
- // effort) the title. All fields are optional older/narrower panes may omit them.
7
+ // We extract the model label, the context-remaining percentage, and whether
8
+ // Claude is actively generating ("esc to interrupt" working line). All fields
9
+ // are optional — older/narrower panes may omit them.
9
10
 
10
11
  const ANSI_RE = /\x1b\[[0-9;]*m/g;
11
12
  const CTX_RE = /ctx:\s*(\d+)\s*%/i;
12
13
  const MODEL_RE = /\b(Opus|Sonnet|Haiku)\s+[\d.]+(?:\s*\([^)]*\))?/i;
14
+ // Claude is actively generating when the TUI shows the working line, which
15
+ // always ends with "esc to interrupt" (the verb/glyph vary). Crucially we must
16
+ // NOT trip on the AskUserQuestion picker, which shows "esc to cancel".
17
+ const THINKING_RE = /esc to interrupt/i;
13
18
 
14
19
  /**
15
20
  * @param {string} capture raw `tmux capture-pane -p` output (ANSI ok)
16
- * @returns {{ ctxPct: number|null, model: string|null }}
21
+ * @returns {{ ctxPct: number|null, model: string|null, thinking: boolean }}
17
22
  */
18
23
  export function parseTuiStatus(capture) {
19
24
  const text = String(capture || '').replace(ANSI_RE, '');
@@ -29,7 +34,9 @@ export function parseTuiStatus(capture) {
29
34
  const modelMatch = text.match(MODEL_RE);
30
35
  if (modelMatch) model = modelMatch[0].replace(/\s+/g, ' ').trim();
31
36
 
32
- return { ctxPct, model };
37
+ const thinking = THINKING_RE.test(text);
38
+
39
+ return { ctxPct, model, thinking };
33
40
  }
34
41
 
35
42
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idl3/claude-control",
3
- "version": "0.1.13",
3
+ "version": "0.1.20",
4
4
  "type": "module",
5
5
  "description": "Local web UI to watch and drive your Claude Code sessions running in tmux — live transcripts, reply, answer AskUserQuestion, attach files, from a browser or phone.",
6
6
  "keywords": [
package/server.js CHANGED
@@ -67,13 +67,21 @@ const CONFIG = {
67
67
  // tripped "over limit" permanently. Override with CLAUDE_CONTROL_RSS_LIMIT_MB.
68
68
  rssLimitMB: Number(env('RSS_LIMIT_MB')) || 768,
69
69
  token: env('TOKEN') || readPersistedToken() || null,
70
- maxBuffer: Number(env('MAX_BUFFER')) || 500,
70
+ // 4000: within lib/transcript's 8 MB byte tail, the message-count cap governs
71
+ // how much history a fresh subscribe serves. Raised 1500 → 4000 for deeper
72
+ // scrollback. Shares the CLAUDE_CONTROL_MAX_BUFFER override with lib/transcript.
73
+ maxBuffer: Number(env('MAX_BUFFER')) || 4000,
71
74
  maxUploadMB: Number(env('MAX_UPLOAD_MB')) || 25,
72
75
  uploadsDir:
73
76
  env('UPLOADS') || path.join(os.homedir(), '.claude-control', 'uploads'),
74
77
  uploadTtlHours: Number(env('UPLOAD_TTL_HOURS')) || 24,
75
78
  pinsFile:
76
79
  env('PINS') || path.join(os.homedir(), '.claude-control', 'pins.json'),
80
+ // Custom PWA home-screen icon (PNG). When present it overrides the bundled
81
+ // default robot logo for the manifest icons + apple-touch-icon. Uploaded via
82
+ // POST /api/icon, removed via DELETE /api/icon.
83
+ iconFile:
84
+ env('ICON') || path.join(os.homedir(), '.claude-control', 'icon.png'),
77
85
  };
78
86
 
79
87
  const MIME = {
@@ -253,6 +261,22 @@ const server = http.createServer((req, res) => {
253
261
  return handleServeUpload(req, res, u);
254
262
  }
255
263
 
264
+ // PWA home-screen icon. GET is token-FREE: the OS fetches manifest icons and
265
+ // the apple-touch-icon with no Authorization header, so this surface must be
266
+ // open (it only ever returns an image). POST/DELETE (replace/reset the custom
267
+ // icon) are token-gated.
268
+ if (u.pathname === '/api/icon') {
269
+ if (req.method === 'POST') {
270
+ if (!checkToken(req)) return endJson(res, 401, { error: 'unauthorized' });
271
+ return handleIconUpload(req, res);
272
+ }
273
+ if (req.method === 'DELETE') {
274
+ if (!checkToken(req)) return endJson(res, 401, { error: 'unauthorized' });
275
+ return handleIconReset(res);
276
+ }
277
+ return handleServeIcon(res, u);
278
+ }
279
+
256
280
  // Raw-terminal escape hatch: token-gated reverse proxy to an on-demand,
257
281
  // loopback-bound ttyd attached to this session's tmux pane. ttyd itself runs
258
282
  // with no auth; this branch (and the matching upgrade branch) is the gate.
@@ -594,6 +618,70 @@ function handleServeFile(res, u) {
594
618
  });
595
619
  }
596
620
 
621
+ // 8-byte PNG file signature.
622
+ const PNG_SIG = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
623
+
624
+ // GET /api/icon[?size=192|512] — serve the custom icon if one was uploaded,
625
+ // else the bundled default robot logo at the closest bundled size. Token-free
626
+ // (see the route guard) because the OS fetches it without auth headers.
627
+ function handleServeIcon(res, u) {
628
+ const size = Number(u.searchParams.get('size')) || 192;
629
+ const fallback = path.join(PUBLIC_DIR, size >= 512 ? 'icon-512.png' : 'icon-192.png');
630
+ const file = fs.existsSync(CONFIG.iconFile) ? CONFIG.iconFile : fallback;
631
+ fs.readFile(file, (err, data) => {
632
+ if (err) { res.writeHead(404); return res.end('not found'); }
633
+ res.writeHead(200, {
634
+ 'content-type': 'image/png',
635
+ // The home-screen icon may change at runtime; never let the phone pin a
636
+ // stale one (it already re-reads the manifest on reinstall).
637
+ 'cache-control': 'no-store, must-revalidate',
638
+ });
639
+ res.end(data);
640
+ });
641
+ }
642
+
643
+ // POST /api/icon — replace the custom home-screen icon with the raw PNG body.
644
+ // PNG-only (validated by signature) so handleServeIcon's image/png is honest.
645
+ function handleIconUpload(req, res) {
646
+ const maxBytes = 4 * 1024 * 1024;
647
+ const chunks = [];
648
+ let size = 0;
649
+ let aborted = false;
650
+ req.on('data', (c) => {
651
+ if (aborted) return;
652
+ size += c.length;
653
+ if (size > maxBytes) {
654
+ aborted = true;
655
+ endJson(res, 413, { error: 'icon exceeds 4 MB limit' });
656
+ req.destroy();
657
+ return;
658
+ }
659
+ chunks.push(c);
660
+ });
661
+ req.on('end', async () => {
662
+ if (aborted) return;
663
+ const buf = Buffer.concat(chunks);
664
+ if (buf.length < 8 || !buf.subarray(0, 8).equals(PNG_SIG)) {
665
+ return endJson(res, 400, { error: 'icon must be a PNG image' });
666
+ }
667
+ try {
668
+ await fs.promises.mkdir(path.dirname(CONFIG.iconFile), { recursive: true });
669
+ await fs.promises.writeFile(CONFIG.iconFile, buf, { mode: 0o600 });
670
+ endJson(res, 200, { ok: true, custom: true });
671
+ } catch (err) {
672
+ endJson(res, 500, { error: String(err?.message || err) });
673
+ }
674
+ });
675
+ }
676
+
677
+ // DELETE /api/icon — drop the custom icon, reverting to the bundled default.
678
+ function handleIconReset(res) {
679
+ fs.promises
680
+ .rm(CONFIG.iconFile, { force: true })
681
+ .then(() => endJson(res, 200, { ok: true, custom: false }))
682
+ .catch((err) => endJson(res, 500, { error: String(err?.message || err) }));
683
+ }
684
+
597
685
  // Set or clear a manual transcript pin. Body: { id, transcriptPath }.
598
686
  // transcriptPath null/empty clears the pin. The pin is keyed by the session's
599
687
  // stable windowId.paneIndex so it survives tmux window renumbering.
@@ -957,8 +1045,24 @@ async function handleClientMessage(ws, msg) {
957
1045
  throw new Error('stale question (already answered or changed)');
958
1046
  }
959
1047
  const keys = buildAnswerProgram(pending, msg.selections || []);
960
- // Sequenced (with delays) so single-select auto-advance settles between keys.
961
- await tmux.sendRawKeysSequenced(session.target, keys);
1048
+ // Log the resolved key program so a failure to drive the picker is
1049
+ // diagnosable from ~/.claude-control/logs/out.log (no logging existed before).
1050
+ console.log(
1051
+ `[answer] toolUseId=${msg.toolUseId} target=${session.target} keys=${JSON.stringify(keys)}`,
1052
+ );
1053
+ try {
1054
+ // Sequenced (with delays) so the picker's re-render settles between keys.
1055
+ await tmux.sendRawKeysSequenced(session.target, keys);
1056
+ } catch (err) {
1057
+ // Surface the failure to the log and re-throw so the outer handler nacks
1058
+ // (ok:false) — never let an "answer sent" ack imply success when the keys
1059
+ // never landed in the pane.
1060
+ console.error(
1061
+ `[answer] FAILED toolUseId=${msg.toolUseId} target=${session.target}: ${String(err?.message || err)}`,
1062
+ );
1063
+ throw err;
1064
+ }
1065
+ console.log(`[answer] sent toolUseId=${msg.toolUseId} (${keys.length} keys)`);
962
1066
  return send(ws, { type: 'ack', op: 'answer', ok: true });
963
1067
  }
964
1068
  case 'capture': {
Binary file
@@ -1,3 +1,3 @@
1
- import{g as Ve}from"./index-DRMuP6dA.js";function xe(e){return e instanceof Map?e.clear=e.delete=e.set=function(){throw new Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=function(){throw new Error("set is read-only")}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach(t=>{const i=e[t],u=typeof i;(u==="object"||u==="function")&&!Object.isFrozen(i)&&xe(i)}),e}class he{constructor(t){t.data===void 0&&(t.data={}),this.data=t.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function we(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;")}function B(e,...t){const i=Object.create(null);for(const u in e)i[u]=e[u];return t.forEach(function(u){for(const b in u)i[b]=u[b]}),i}const qe="</span>",pe=e=>!!e.scope,Qe=(e,{prefix:t})=>{if(e.startsWith("language:"))return e.replace("language:","language-");if(e.includes(".")){const i=e.split(".");return[`${t}${i.shift()}`,...i.map((u,b)=>`${u}${"_".repeat(b+1)}`)].join(" ")}return`${t}${e}`};class me{constructor(t,i){this.buffer="",this.classPrefix=i.classPrefix,t.walk(this)}addText(t){this.buffer+=we(t)}openNode(t){if(!pe(t))return;const i=Qe(t.scope,{prefix:this.classPrefix});this.span(i)}closeNode(t){pe(t)&&(this.buffer+=qe)}value(){return this.buffer}span(t){this.buffer+=`<span class="${t}">`}}const de=(e={})=>{const t={children:[]};return Object.assign(t,e),t};class te{constructor(){this.rootNode=de(),this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(t){this.top.children.push(t)}openNode(t){const i=de({scope:t});this.add(i),this.stack.push(i)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(t){return this.constructor._walk(t,this.rootNode)}static _walk(t,i){return typeof i=="string"?t.addText(i):i.children&&(t.openNode(i),i.children.forEach(u=>this._walk(t,u)),t.closeNode(i)),t}static _collapse(t){typeof t!="string"&&t.children&&(t.children.every(i=>typeof i=="string")?t.children=[t.children.join("")]:t.children.forEach(i=>{te._collapse(i)}))}}class et extends te{constructor(t){super(),this.options=t}addText(t){t!==""&&this.add(t)}startScope(t){this.openNode(t)}endScope(){this.closeNode()}__addSublanguage(t,i){const u=t.root;i&&(u.scope=`language:${i}`),this.add(u)}toHTML(){return new me(this,this.options).value()}finalize(){return this.closeAllNodes(),!0}}function P(e){return e?typeof e=="string"?e:e.source:null}function Oe(e){return C("(?=",e,")")}function tt(e){return C("(?:",e,")*")}function nt(e){return C("(?:",e,")?")}function C(...e){return e.map(i=>P(i)).join("")}function it(e){const t=e[e.length-1];return typeof t=="object"&&t.constructor===Object?(e.splice(e.length-1,1),t):{}}function ne(...e){return"("+(it(e).capture?"":"?:")+e.map(u=>P(u)).join("|")+")"}function Re(e){return new RegExp(e.toString()+"|").exec("").length-1}function st(e,t){const i=e&&e.exec(t);return i&&i.index===0}const rt=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;function ie(e,{joinWith:t}){let i=0;return e.map(u=>{i+=1;const b=i;let _=P(u),c="";for(;_.length>0;){const r=rt.exec(_);if(!r){c+=_;break}c+=_.substring(0,r.index),_=_.substring(r.index+r[0].length),r[0][0]==="\\"&&r[1]?c+="\\"+String(Number(r[1])+b):(c+=r[0],r[0]==="("&&i++)}return c}).map(u=>`(${u})`).join(t)}const ct=/\b\B/,ye="[a-zA-Z]\\w*",se="[a-zA-Z_]\\w*",Se="\\b\\d+(\\.\\d+)?",Ne="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",Ae="\\b(0b[01]+)",ot="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",at=(e={})=>{const t=/^#![ ]*\//;return e.binary&&(e.begin=C(t,/.*\b/,e.binary,/\b.*/)),B({scope:"meta",begin:t,end:/$/,relevance:0,"on:begin":(i,u)=>{i.index!==0&&u.ignoreMatch()}},e)},U={begin:"\\\\[\\s\\S]",relevance:0},lt={scope:"string",begin:"'",end:"'",illegal:"\\n",contains:[U]},ut={scope:"string",begin:'"',end:'"',illegal:"\\n",contains:[U]},ft={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},Y=function(e,t,i={}){const u=B({scope:"comment",begin:e,end:t,contains:[]},i);u.contains.push({scope:"doctag",begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)",end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0});const b=ne("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/);return u.contains.push({begin:C(/[ ]+/,"(",b,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),u},gt=Y("//","$"),ht=Y("/\\*","\\*/"),pt=Y("#","$"),dt={scope:"number",begin:Se,relevance:0},Et={scope:"number",begin:Ne,relevance:0},bt={scope:"number",begin:Ae,relevance:0},_t={scope:"regexp",begin:/\/(?=[^/\n]*\/)/,end:/\/[gimuy]*/,contains:[U,{begin:/\[/,end:/\]/,relevance:0,contains:[U]}]},Mt={scope:"title",begin:ye,relevance:0},xt={scope:"title",begin:se,relevance:0},wt={begin:"\\.\\s*"+se,relevance:0},Ot=function(e){return Object.assign(e,{"on:begin":(t,i)=>{i.data._beginMatch=t[1]},"on:end":(t,i)=>{i.data._beginMatch!==t[1]&&i.ignoreMatch()}})};var z=Object.freeze({__proto__:null,APOS_STRING_MODE:lt,BACKSLASH_ESCAPE:U,BINARY_NUMBER_MODE:bt,BINARY_NUMBER_RE:Ae,COMMENT:Y,C_BLOCK_COMMENT_MODE:ht,C_LINE_COMMENT_MODE:gt,C_NUMBER_MODE:Et,C_NUMBER_RE:Ne,END_SAME_AS_BEGIN:Ot,HASH_COMMENT_MODE:pt,IDENT_RE:ye,MATCH_NOTHING_RE:ct,METHOD_GUARD:wt,NUMBER_MODE:dt,NUMBER_RE:Se,PHRASAL_WORDS_MODE:ft,QUOTE_STRING_MODE:ut,REGEXP_MODE:_t,RE_STARTERS_RE:ot,SHEBANG:at,TITLE_MODE:Mt,UNDERSCORE_IDENT_RE:se,UNDERSCORE_TITLE_MODE:xt});function Rt(e,t){e.input[e.index-1]==="."&&t.ignoreMatch()}function yt(e,t){e.className!==void 0&&(e.scope=e.className,delete e.className)}function St(e,t){t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",e.__beforeBegin=Rt,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,e.relevance===void 0&&(e.relevance=0))}function Nt(e,t){Array.isArray(e.illegal)&&(e.illegal=ne(...e.illegal))}function At(e,t){if(e.match){if(e.begin||e.end)throw new Error("begin & end are not supported with match");e.begin=e.match,delete e.match}}function kt(e,t){e.relevance===void 0&&(e.relevance=1)}const Tt=(e,t)=>{if(!e.beforeMatch)return;if(e.starts)throw new Error("beforeMatch cannot be used with starts");const i=Object.assign({},e);Object.keys(e).forEach(u=>{delete e[u]}),e.keywords=i.keywords,e.begin=C(i.beforeMatch,Oe(i.begin)),e.starts={relevance:0,contains:[Object.assign(i,{endsParent:!0})]},e.relevance=0,delete i.beforeMatch},It=["of","and","for","in","not","or","if","then","parent","list","value"],Bt="keyword";function ke(e,t,i=Bt){const u=Object.create(null);return typeof e=="string"?b(i,e.split(" ")):Array.isArray(e)?b(i,e):Object.keys(e).forEach(function(_){Object.assign(u,ke(e[_],t,_))}),u;function b(_,c){t&&(c=c.map(r=>r.toLowerCase())),c.forEach(function(r){const l=r.split("|");u[l[0]]=[_,Dt(l[0],l[1])]})}}function Dt(e,t){return t?Number(t):vt(e)?0:1}function vt(e){return It.includes(e.toLowerCase())}const Ee={},v=e=>{console.error(e)},be=(e,...t)=>{console.log(`WARN: ${e}`,...t)},L=(e,t)=>{Ee[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),Ee[`${e}/${t}`]=!0)},X=new Error;function Te(e,t,{key:i}){let u=0;const b=e[i],_={},c={};for(let r=1;r<=t.length;r++)c[r+u]=b[r],_[r+u]=!0,u+=Re(t[r-1]);e[i]=c,e[i]._emit=_,e[i]._multi=!0}function Ct(e){if(Array.isArray(e.begin)){if(e.skip||e.excludeBegin||e.returnBegin)throw v("skip, excludeBegin, returnBegin not compatible with beginScope: {}"),X;if(typeof e.beginScope!="object"||e.beginScope===null)throw v("beginScope must be object"),X;Te(e,e.begin,{key:"beginScope"}),e.begin=ie(e.begin,{joinWith:""})}}function Lt(e){if(Array.isArray(e.end)){if(e.skip||e.excludeEnd||e.returnEnd)throw v("skip, excludeEnd, returnEnd not compatible with endScope: {}"),X;if(typeof e.endScope!="object"||e.endScope===null)throw v("endScope must be object"),X;Te(e,e.end,{key:"endScope"}),e.end=ie(e.end,{joinWith:""})}}function Ht(e){e.scope&&typeof e.scope=="object"&&e.scope!==null&&(e.beginScope=e.scope,delete e.scope)}function jt(e){Ht(e),typeof e.beginScope=="string"&&(e.beginScope={_wrap:e.beginScope}),typeof e.endScope=="string"&&(e.endScope={_wrap:e.endScope}),Ct(e),Lt(e)}function Pt(e){function t(c,r){return new RegExp(P(c),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(r?"g":""))}class i{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(r,l){l.position=this.position++,this.matchIndexes[this.matchAt]=l,this.regexes.push([l,r]),this.matchAt+=Re(r)+1}compile(){this.regexes.length===0&&(this.exec=()=>null);const r=this.regexes.map(l=>l[1]);this.matcherRe=t(ie(r,{joinWith:"|"}),!0),this.lastIndex=0}exec(r){this.matcherRe.lastIndex=this.lastIndex;const l=this.matcherRe.exec(r);if(!l)return null;const w=l.findIndex((j,Z)=>Z>0&&j!==void 0),M=this.matchIndexes[w];return l.splice(0,w),Object.assign(l,M)}}class u{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(r){if(this.multiRegexes[r])return this.multiRegexes[r];const l=new i;return this.rules.slice(r).forEach(([w,M])=>l.addRule(w,M)),l.compile(),this.multiRegexes[r]=l,l}resumingScanAtSamePosition(){return this.regexIndex!==0}considerAll(){this.regexIndex=0}addRule(r,l){this.rules.push([r,l]),l.type==="begin"&&this.count++}exec(r){const l=this.getMatcher(this.regexIndex);l.lastIndex=this.lastIndex;let w=l.exec(r);if(this.resumingScanAtSamePosition()&&!(w&&w.index===this.lastIndex)){const M=this.getMatcher(0);M.lastIndex=this.lastIndex+1,w=M.exec(r)}return w&&(this.regexIndex+=w.position+1,this.regexIndex===this.count&&this.considerAll()),w}}function b(c){const r=new u;return c.contains.forEach(l=>r.addRule(l.begin,{rule:l,type:"begin"})),c.terminatorEnd&&r.addRule(c.terminatorEnd,{type:"end"}),c.illegal&&r.addRule(c.illegal,{type:"illegal"}),r}function _(c,r){const l=c;if(c.isCompiled)return l;[yt,At,jt,Tt].forEach(M=>M(c,r)),e.compilerExtensions.forEach(M=>M(c,r)),c.__beforeBegin=null,[St,Nt,kt].forEach(M=>M(c,r)),c.isCompiled=!0;let w=null;return typeof c.keywords=="object"&&c.keywords.$pattern&&(c.keywords=Object.assign({},c.keywords),w=c.keywords.$pattern,delete c.keywords.$pattern),w=w||/\w+/,c.keywords&&(c.keywords=ke(c.keywords,e.case_insensitive)),l.keywordPatternRe=t(w,!0),r&&(c.begin||(c.begin=/\B|\b/),l.beginRe=t(l.begin),!c.end&&!c.endsWithParent&&(c.end=/\B|\b/),c.end&&(l.endRe=t(l.end)),l.terminatorEnd=P(l.end)||"",c.endsWithParent&&r.terminatorEnd&&(l.terminatorEnd+=(c.end?"|":"")+r.terminatorEnd)),c.illegal&&(l.illegalRe=t(c.illegal)),c.contains||(c.contains=[]),c.contains=[].concat(...c.contains.map(function(M){return Ut(M==="self"?c:M)})),c.contains.forEach(function(M){_(M,l)}),c.starts&&_(c.starts,r),l.matcher=b(l),l}if(e.compilerExtensions||(e.compilerExtensions=[]),e.contains&&e.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return e.classNameAliases=B(e.classNameAliases||{}),_(e)}function Ie(e){return e?e.endsWithParent||Ie(e.starts):!1}function Ut(e){return e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map(function(t){return B(e,{variants:null},t)})),e.cachedVariants?e.cachedVariants:Ie(e)?B(e,{starts:e.starts?B(e.starts):null}):Object.isFrozen(e)?B(e):e}var $t="11.11.1";class Gt extends Error{constructor(t,i){super(t),this.name="HTMLInjectionError",this.html=i}}const ee=we,_e=B,Me=Symbol("nomatch"),Wt=7,Be=function(e){const t=Object.create(null),i=Object.create(null),u=[];let b=!0;const _="Could not find the language '{}', did you forget to load/include a language module?",c={disableAutodetect:!0,name:"Plain text",contains:[]};let r={ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",cssSelector:"pre code",languages:null,__emitter:et};function l(n){return r.noHighlightRe.test(n)}function w(n){let a=n.className+" ";a+=n.parentNode?n.parentNode.className:"";const h=r.languageDetectRe.exec(a);if(h){const d=T(h[1]);return d||(be(_.replace("{}",h[1])),be("Falling back to no-highlight mode for this block.",n)),d?h[1]:"no-highlight"}return a.split(/\s+/).find(d=>l(d)||T(d))}function M(n,a,h){let d="",x="";typeof a=="object"?(d=n,h=a.ignoreIllegals,x=a.language):(L("10.7.0","highlight(lang, code, ...args) has been deprecated."),L("10.7.0",`Please use highlight(code, options) instead.
1
+ import{g as Ve}from"./index-D2hrAUsb.js";function xe(e){return e instanceof Map?e.clear=e.delete=e.set=function(){throw new Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=function(){throw new Error("set is read-only")}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach(t=>{const i=e[t],u=typeof i;(u==="object"||u==="function")&&!Object.isFrozen(i)&&xe(i)}),e}class he{constructor(t){t.data===void 0&&(t.data={}),this.data=t.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function we(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;")}function B(e,...t){const i=Object.create(null);for(const u in e)i[u]=e[u];return t.forEach(function(u){for(const b in u)i[b]=u[b]}),i}const qe="</span>",pe=e=>!!e.scope,Qe=(e,{prefix:t})=>{if(e.startsWith("language:"))return e.replace("language:","language-");if(e.includes(".")){const i=e.split(".");return[`${t}${i.shift()}`,...i.map((u,b)=>`${u}${"_".repeat(b+1)}`)].join(" ")}return`${t}${e}`};class me{constructor(t,i){this.buffer="",this.classPrefix=i.classPrefix,t.walk(this)}addText(t){this.buffer+=we(t)}openNode(t){if(!pe(t))return;const i=Qe(t.scope,{prefix:this.classPrefix});this.span(i)}closeNode(t){pe(t)&&(this.buffer+=qe)}value(){return this.buffer}span(t){this.buffer+=`<span class="${t}">`}}const de=(e={})=>{const t={children:[]};return Object.assign(t,e),t};class te{constructor(){this.rootNode=de(),this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(t){this.top.children.push(t)}openNode(t){const i=de({scope:t});this.add(i),this.stack.push(i)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(t){return this.constructor._walk(t,this.rootNode)}static _walk(t,i){return typeof i=="string"?t.addText(i):i.children&&(t.openNode(i),i.children.forEach(u=>this._walk(t,u)),t.closeNode(i)),t}static _collapse(t){typeof t!="string"&&t.children&&(t.children.every(i=>typeof i=="string")?t.children=[t.children.join("")]:t.children.forEach(i=>{te._collapse(i)}))}}class et extends te{constructor(t){super(),this.options=t}addText(t){t!==""&&this.add(t)}startScope(t){this.openNode(t)}endScope(){this.closeNode()}__addSublanguage(t,i){const u=t.root;i&&(u.scope=`language:${i}`),this.add(u)}toHTML(){return new me(this,this.options).value()}finalize(){return this.closeAllNodes(),!0}}function P(e){return e?typeof e=="string"?e:e.source:null}function Oe(e){return C("(?=",e,")")}function tt(e){return C("(?:",e,")*")}function nt(e){return C("(?:",e,")?")}function C(...e){return e.map(i=>P(i)).join("")}function it(e){const t=e[e.length-1];return typeof t=="object"&&t.constructor===Object?(e.splice(e.length-1,1),t):{}}function ne(...e){return"("+(it(e).capture?"":"?:")+e.map(u=>P(u)).join("|")+")"}function Re(e){return new RegExp(e.toString()+"|").exec("").length-1}function st(e,t){const i=e&&e.exec(t);return i&&i.index===0}const rt=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;function ie(e,{joinWith:t}){let i=0;return e.map(u=>{i+=1;const b=i;let _=P(u),c="";for(;_.length>0;){const r=rt.exec(_);if(!r){c+=_;break}c+=_.substring(0,r.index),_=_.substring(r.index+r[0].length),r[0][0]==="\\"&&r[1]?c+="\\"+String(Number(r[1])+b):(c+=r[0],r[0]==="("&&i++)}return c}).map(u=>`(${u})`).join(t)}const ct=/\b\B/,ye="[a-zA-Z]\\w*",se="[a-zA-Z_]\\w*",Se="\\b\\d+(\\.\\d+)?",Ne="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",Ae="\\b(0b[01]+)",ot="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",at=(e={})=>{const t=/^#![ ]*\//;return e.binary&&(e.begin=C(t,/.*\b/,e.binary,/\b.*/)),B({scope:"meta",begin:t,end:/$/,relevance:0,"on:begin":(i,u)=>{i.index!==0&&u.ignoreMatch()}},e)},U={begin:"\\\\[\\s\\S]",relevance:0},lt={scope:"string",begin:"'",end:"'",illegal:"\\n",contains:[U]},ut={scope:"string",begin:'"',end:'"',illegal:"\\n",contains:[U]},ft={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},Y=function(e,t,i={}){const u=B({scope:"comment",begin:e,end:t,contains:[]},i);u.contains.push({scope:"doctag",begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)",end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0});const b=ne("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/);return u.contains.push({begin:C(/[ ]+/,"(",b,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),u},gt=Y("//","$"),ht=Y("/\\*","\\*/"),pt=Y("#","$"),dt={scope:"number",begin:Se,relevance:0},Et={scope:"number",begin:Ne,relevance:0},bt={scope:"number",begin:Ae,relevance:0},_t={scope:"regexp",begin:/\/(?=[^/\n]*\/)/,end:/\/[gimuy]*/,contains:[U,{begin:/\[/,end:/\]/,relevance:0,contains:[U]}]},Mt={scope:"title",begin:ye,relevance:0},xt={scope:"title",begin:se,relevance:0},wt={begin:"\\.\\s*"+se,relevance:0},Ot=function(e){return Object.assign(e,{"on:begin":(t,i)=>{i.data._beginMatch=t[1]},"on:end":(t,i)=>{i.data._beginMatch!==t[1]&&i.ignoreMatch()}})};var z=Object.freeze({__proto__:null,APOS_STRING_MODE:lt,BACKSLASH_ESCAPE:U,BINARY_NUMBER_MODE:bt,BINARY_NUMBER_RE:Ae,COMMENT:Y,C_BLOCK_COMMENT_MODE:ht,C_LINE_COMMENT_MODE:gt,C_NUMBER_MODE:Et,C_NUMBER_RE:Ne,END_SAME_AS_BEGIN:Ot,HASH_COMMENT_MODE:pt,IDENT_RE:ye,MATCH_NOTHING_RE:ct,METHOD_GUARD:wt,NUMBER_MODE:dt,NUMBER_RE:Se,PHRASAL_WORDS_MODE:ft,QUOTE_STRING_MODE:ut,REGEXP_MODE:_t,RE_STARTERS_RE:ot,SHEBANG:at,TITLE_MODE:Mt,UNDERSCORE_IDENT_RE:se,UNDERSCORE_TITLE_MODE:xt});function Rt(e,t){e.input[e.index-1]==="."&&t.ignoreMatch()}function yt(e,t){e.className!==void 0&&(e.scope=e.className,delete e.className)}function St(e,t){t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",e.__beforeBegin=Rt,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,e.relevance===void 0&&(e.relevance=0))}function Nt(e,t){Array.isArray(e.illegal)&&(e.illegal=ne(...e.illegal))}function At(e,t){if(e.match){if(e.begin||e.end)throw new Error("begin & end are not supported with match");e.begin=e.match,delete e.match}}function kt(e,t){e.relevance===void 0&&(e.relevance=1)}const Tt=(e,t)=>{if(!e.beforeMatch)return;if(e.starts)throw new Error("beforeMatch cannot be used with starts");const i=Object.assign({},e);Object.keys(e).forEach(u=>{delete e[u]}),e.keywords=i.keywords,e.begin=C(i.beforeMatch,Oe(i.begin)),e.starts={relevance:0,contains:[Object.assign(i,{endsParent:!0})]},e.relevance=0,delete i.beforeMatch},It=["of","and","for","in","not","or","if","then","parent","list","value"],Bt="keyword";function ke(e,t,i=Bt){const u=Object.create(null);return typeof e=="string"?b(i,e.split(" ")):Array.isArray(e)?b(i,e):Object.keys(e).forEach(function(_){Object.assign(u,ke(e[_],t,_))}),u;function b(_,c){t&&(c=c.map(r=>r.toLowerCase())),c.forEach(function(r){const l=r.split("|");u[l[0]]=[_,Dt(l[0],l[1])]})}}function Dt(e,t){return t?Number(t):vt(e)?0:1}function vt(e){return It.includes(e.toLowerCase())}const Ee={},v=e=>{console.error(e)},be=(e,...t)=>{console.log(`WARN: ${e}`,...t)},L=(e,t)=>{Ee[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),Ee[`${e}/${t}`]=!0)},X=new Error;function Te(e,t,{key:i}){let u=0;const b=e[i],_={},c={};for(let r=1;r<=t.length;r++)c[r+u]=b[r],_[r+u]=!0,u+=Re(t[r-1]);e[i]=c,e[i]._emit=_,e[i]._multi=!0}function Ct(e){if(Array.isArray(e.begin)){if(e.skip||e.excludeBegin||e.returnBegin)throw v("skip, excludeBegin, returnBegin not compatible with beginScope: {}"),X;if(typeof e.beginScope!="object"||e.beginScope===null)throw v("beginScope must be object"),X;Te(e,e.begin,{key:"beginScope"}),e.begin=ie(e.begin,{joinWith:""})}}function Lt(e){if(Array.isArray(e.end)){if(e.skip||e.excludeEnd||e.returnEnd)throw v("skip, excludeEnd, returnEnd not compatible with endScope: {}"),X;if(typeof e.endScope!="object"||e.endScope===null)throw v("endScope must be object"),X;Te(e,e.end,{key:"endScope"}),e.end=ie(e.end,{joinWith:""})}}function Ht(e){e.scope&&typeof e.scope=="object"&&e.scope!==null&&(e.beginScope=e.scope,delete e.scope)}function jt(e){Ht(e),typeof e.beginScope=="string"&&(e.beginScope={_wrap:e.beginScope}),typeof e.endScope=="string"&&(e.endScope={_wrap:e.endScope}),Ct(e),Lt(e)}function Pt(e){function t(c,r){return new RegExp(P(c),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(r?"g":""))}class i{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(r,l){l.position=this.position++,this.matchIndexes[this.matchAt]=l,this.regexes.push([l,r]),this.matchAt+=Re(r)+1}compile(){this.regexes.length===0&&(this.exec=()=>null);const r=this.regexes.map(l=>l[1]);this.matcherRe=t(ie(r,{joinWith:"|"}),!0),this.lastIndex=0}exec(r){this.matcherRe.lastIndex=this.lastIndex;const l=this.matcherRe.exec(r);if(!l)return null;const w=l.findIndex((j,Z)=>Z>0&&j!==void 0),M=this.matchIndexes[w];return l.splice(0,w),Object.assign(l,M)}}class u{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(r){if(this.multiRegexes[r])return this.multiRegexes[r];const l=new i;return this.rules.slice(r).forEach(([w,M])=>l.addRule(w,M)),l.compile(),this.multiRegexes[r]=l,l}resumingScanAtSamePosition(){return this.regexIndex!==0}considerAll(){this.regexIndex=0}addRule(r,l){this.rules.push([r,l]),l.type==="begin"&&this.count++}exec(r){const l=this.getMatcher(this.regexIndex);l.lastIndex=this.lastIndex;let w=l.exec(r);if(this.resumingScanAtSamePosition()&&!(w&&w.index===this.lastIndex)){const M=this.getMatcher(0);M.lastIndex=this.lastIndex+1,w=M.exec(r)}return w&&(this.regexIndex+=w.position+1,this.regexIndex===this.count&&this.considerAll()),w}}function b(c){const r=new u;return c.contains.forEach(l=>r.addRule(l.begin,{rule:l,type:"begin"})),c.terminatorEnd&&r.addRule(c.terminatorEnd,{type:"end"}),c.illegal&&r.addRule(c.illegal,{type:"illegal"}),r}function _(c,r){const l=c;if(c.isCompiled)return l;[yt,At,jt,Tt].forEach(M=>M(c,r)),e.compilerExtensions.forEach(M=>M(c,r)),c.__beforeBegin=null,[St,Nt,kt].forEach(M=>M(c,r)),c.isCompiled=!0;let w=null;return typeof c.keywords=="object"&&c.keywords.$pattern&&(c.keywords=Object.assign({},c.keywords),w=c.keywords.$pattern,delete c.keywords.$pattern),w=w||/\w+/,c.keywords&&(c.keywords=ke(c.keywords,e.case_insensitive)),l.keywordPatternRe=t(w,!0),r&&(c.begin||(c.begin=/\B|\b/),l.beginRe=t(l.begin),!c.end&&!c.endsWithParent&&(c.end=/\B|\b/),c.end&&(l.endRe=t(l.end)),l.terminatorEnd=P(l.end)||"",c.endsWithParent&&r.terminatorEnd&&(l.terminatorEnd+=(c.end?"|":"")+r.terminatorEnd)),c.illegal&&(l.illegalRe=t(c.illegal)),c.contains||(c.contains=[]),c.contains=[].concat(...c.contains.map(function(M){return Ut(M==="self"?c:M)})),c.contains.forEach(function(M){_(M,l)}),c.starts&&_(c.starts,r),l.matcher=b(l),l}if(e.compilerExtensions||(e.compilerExtensions=[]),e.contains&&e.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return e.classNameAliases=B(e.classNameAliases||{}),_(e)}function Ie(e){return e?e.endsWithParent||Ie(e.starts):!1}function Ut(e){return e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map(function(t){return B(e,{variants:null},t)})),e.cachedVariants?e.cachedVariants:Ie(e)?B(e,{starts:e.starts?B(e.starts):null}):Object.isFrozen(e)?B(e):e}var $t="11.11.1";class Gt extends Error{constructor(t,i){super(t),this.name="HTMLInjectionError",this.html=i}}const ee=we,_e=B,Me=Symbol("nomatch"),Wt=7,Be=function(e){const t=Object.create(null),i=Object.create(null),u=[];let b=!0;const _="Could not find the language '{}', did you forget to load/include a language module?",c={disableAutodetect:!0,name:"Plain text",contains:[]};let r={ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",cssSelector:"pre code",languages:null,__emitter:et};function l(n){return r.noHighlightRe.test(n)}function w(n){let a=n.className+" ";a+=n.parentNode?n.parentNode.className:"";const h=r.languageDetectRe.exec(a);if(h){const d=T(h[1]);return d||(be(_.replace("{}",h[1])),be("Falling back to no-highlight mode for this block.",n)),d?h[1]:"no-highlight"}return a.split(/\s+/).find(d=>l(d)||T(d))}function M(n,a,h){let d="",x="";typeof a=="object"?(d=n,h=a.ignoreIllegals,x=a.language):(L("10.7.0","highlight(lang, code, ...args) has been deprecated."),L("10.7.0",`Please use highlight(code, options) instead.
2
2
  https://github.com/highlightjs/highlight.js/issues/2277`),x=n,d=a),h===void 0&&(h=!0);const S={code:d,language:x};G("before:highlight",S);const I=S.result?S.result:j(S.language,S.code,h);return I.code=S.code,G("after:highlight",I),I}function j(n,a,h,d){const x=Object.create(null);function S(s,o){return s.keywords[o]}function I(){if(!f.keywords){O.addText(E);return}let s=0;f.keywordPatternRe.lastIndex=0;let o=f.keywordPatternRe.exec(E),g="";for(;o;){g+=E.substring(s,o.index);const p=A.case_insensitive?o[0].toLowerCase():o[0],R=S(f,p);if(R){const[k,Ze]=R;if(O.addText(g),g="",x[p]=(x[p]||0)+1,x[p]<=Wt&&(F+=Ze),k.startsWith("_"))g+=o[0];else{const Je=A.classNameAliases[k]||k;N(o[0],Je)}}else g+=o[0];s=f.keywordPatternRe.lastIndex,o=f.keywordPatternRe.exec(E)}g+=E.substring(s),O.addText(g)}function W(){if(E==="")return;let s=null;if(typeof f.subLanguage=="string"){if(!t[f.subLanguage]){O.addText(E);return}s=j(f.subLanguage,E,!0,ge[f.subLanguage]),ge[f.subLanguage]=s._top}else s=J(E,f.subLanguage.length?f.subLanguage:null);f.relevance>0&&(F+=s.relevance),O.__addSublanguage(s._emitter,s.language)}function y(){f.subLanguage!=null?W():I(),E=""}function N(s,o){s!==""&&(O.startScope(o),O.addText(s),O.endScope())}function ae(s,o){let g=1;const p=o.length-1;for(;g<=p;){if(!s._emit[g]){g++;continue}const R=A.classNameAliases[s[g]]||s[g],k=o[g];R?N(k,R):(E=k,I(),E=""),g++}}function le(s,o){return s.scope&&typeof s.scope=="string"&&O.openNode(A.classNameAliases[s.scope]||s.scope),s.beginScope&&(s.beginScope._wrap?(N(E,A.classNameAliases[s.beginScope._wrap]||s.beginScope._wrap),E=""):s.beginScope._multi&&(ae(s.beginScope,o),E="")),f=Object.create(s,{parent:{value:f}}),f}function ue(s,o,g){let p=st(s.endRe,g);if(p){if(s["on:end"]){const R=new he(s);s["on:end"](o,R),R.isMatchIgnored&&(p=!1)}if(p){for(;s.endsParent&&s.parent;)s=s.parent;return s}}if(s.endsWithParent)return ue(s.parent,o,g)}function Ke(s){return f.matcher.regexIndex===0?(E+=s[0],1):(m=!0,0)}function Fe(s){const o=s[0],g=s.rule,p=new he(g),R=[g.__beforeBegin,g["on:begin"]];for(const k of R)if(k&&(k(s,p),p.isMatchIgnored))return Ke(o);return g.skip?E+=o:(g.excludeBegin&&(E+=o),y(),!g.returnBegin&&!g.excludeBegin&&(E=o)),le(g,s),g.returnBegin?0:o.length}function ze(s){const o=s[0],g=a.substring(s.index),p=ue(f,s,g);if(!p)return Me;const R=f;f.endScope&&f.endScope._wrap?(y(),N(o,f.endScope._wrap)):f.endScope&&f.endScope._multi?(y(),ae(f.endScope,s)):R.skip?E+=o:(R.returnEnd||R.excludeEnd||(E+=o),y(),R.excludeEnd&&(E=o));do f.scope&&O.closeNode(),!f.skip&&!f.subLanguage&&(F+=f.relevance),f=f.parent;while(f!==p.parent);return p.starts&&le(p.starts,s),R.returnEnd?0:o.length}function Xe(){const s=[];for(let o=f;o!==A;o=o.parent)o.scope&&s.unshift(o.scope);s.forEach(o=>O.openNode(o))}let K={};function fe(s,o){const g=o&&o[0];if(E+=s,g==null)return y(),0;if(K.type==="begin"&&o.type==="end"&&K.index===o.index&&g===""){if(E+=a.slice(o.index,o.index+1),!b){const p=new Error(`0 width match regex (${n})`);throw p.languageName=n,p.badRule=K.rule,p}return 1}if(K=o,o.type==="begin")return Fe(o);if(o.type==="illegal"&&!h){const p=new Error('Illegal lexeme "'+g+'" for mode "'+(f.scope||"<unnamed>")+'"');throw p.mode=f,p}else if(o.type==="end"){const p=ze(o);if(p!==Me)return p}if(o.type==="illegal"&&g==="")return E+=`
3
3
  `,1;if(Q>1e5&&Q>o.index*3)throw new Error("potential infinite loop, way more iterations than matches");return E+=g,g.length}const A=T(n);if(!A)throw v(_.replace("{}",n)),new Error('Unknown language: "'+n+'"');const Ye=Pt(A);let q="",f=d||Ye;const ge={},O=new r.__emitter(r);Xe();let E="",F=0,D=0,Q=0,m=!1;try{if(A.__emitTokens)A.__emitTokens(a,O);else{for(f.matcher.considerAll();;){Q++,m?m=!1:f.matcher.considerAll(),f.matcher.lastIndex=D;const s=f.matcher.exec(a);if(!s)break;const o=a.substring(D,s.index),g=fe(o,s);D=s.index+g}fe(a.substring(D))}return O.finalize(),q=O.toHTML(),{language:n,value:q,relevance:F,illegal:!1,_emitter:O,_top:f}}catch(s){if(s.message&&s.message.includes("Illegal"))return{language:n,value:ee(a),illegal:!0,relevance:0,_illegalBy:{message:s.message,index:D,context:a.slice(D-100,D+100),mode:s.mode,resultSoFar:q},_emitter:O};if(b)return{language:n,value:ee(a),illegal:!1,relevance:0,errorRaised:s,_emitter:O,_top:f};throw s}}function Z(n){const a={value:ee(n),illegal:!1,relevance:0,_top:c,_emitter:new r.__emitter(r)};return a._emitter.addText(n),a}function J(n,a){a=a||r.languages||Object.keys(t);const h=Z(n),d=a.filter(T).filter(oe).map(y=>j(y,n,!1));d.unshift(h);const x=d.sort((y,N)=>{if(y.relevance!==N.relevance)return N.relevance-y.relevance;if(y.language&&N.language){if(T(y.language).supersetOf===N.language)return 1;if(T(N.language).supersetOf===y.language)return-1}return 0}),[S,I]=x,W=S;return W.secondBest=I,W}function De(n,a,h){const d=a&&i[a]||h;n.classList.add("hljs"),n.classList.add(`language-${d}`)}function V(n){let a=null;const h=w(n);if(l(h))return;if(G("before:highlightElement",{el:n,language:h}),n.dataset.highlighted){console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",n);return}if(n.children.length>0&&(r.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."),console.warn("https://github.com/highlightjs/highlight.js/wiki/security"),console.warn("The element with unescaped HTML:"),console.warn(n)),r.throwUnescapedHTML))throw new Gt("One of your code blocks includes unescaped HTML.",n.innerHTML);a=n;const d=a.textContent,x=h?M(d,{language:h,ignoreIllegals:!0}):J(d);n.innerHTML=x.value,n.dataset.highlighted="yes",De(n,h,x.language),n.result={language:x.language,re:x.relevance,relevance:x.relevance},x.secondBest&&(n.secondBest={language:x.secondBest.language,relevance:x.secondBest.relevance}),G("after:highlightElement",{el:n,result:x,text:d})}function ve(n){r=_e(r,n)}const Ce=()=>{$(),L("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")};function Le(){$(),L("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.")}let re=!1;function $(){function n(){$()}if(document.readyState==="loading"){re||window.addEventListener("DOMContentLoaded",n,!1),re=!0;return}document.querySelectorAll(r.cssSelector).forEach(V)}function He(n,a){let h=null;try{h=a(e)}catch(d){if(v("Language definition for '{}' could not be registered.".replace("{}",n)),b)v(d);else throw d;h=c}h.name||(h.name=n),t[n]=h,h.rawDefinition=a.bind(null,e),h.aliases&&ce(h.aliases,{languageName:n})}function je(n){delete t[n];for(const a of Object.keys(i))i[a]===n&&delete i[a]}function Pe(){return Object.keys(t)}function T(n){return n=(n||"").toLowerCase(),t[n]||t[i[n]]}function ce(n,{languageName:a}){typeof n=="string"&&(n=[n]),n.forEach(h=>{i[h.toLowerCase()]=a})}function oe(n){const a=T(n);return a&&!a.disableAutodetect}function Ue(n){n["before:highlightBlock"]&&!n["before:highlightElement"]&&(n["before:highlightElement"]=a=>{n["before:highlightBlock"](Object.assign({block:a.el},a))}),n["after:highlightBlock"]&&!n["after:highlightElement"]&&(n["after:highlightElement"]=a=>{n["after:highlightBlock"](Object.assign({block:a.el},a))})}function $e(n){Ue(n),u.push(n)}function Ge(n){const a=u.indexOf(n);a!==-1&&u.splice(a,1)}function G(n,a){const h=n;u.forEach(function(d){d[h]&&d[h](a)})}function We(n){return L("10.7.0","highlightBlock will be removed entirely in v12.0"),L("10.7.0","Please use highlightElement now."),V(n)}Object.assign(e,{highlight:M,highlightAuto:J,highlightAll:$,highlightElement:V,highlightBlock:We,configure:ve,initHighlighting:Ce,initHighlightingOnLoad:Le,registerLanguage:He,unregisterLanguage:je,listLanguages:Pe,getLanguage:T,registerAliases:ce,autoDetection:oe,inherit:_e,addPlugin:$e,removePlugin:Ge}),e.debugMode=function(){b=!1},e.safeMode=function(){b=!0},e.versionString=$t,e.regex={concat:C,lookahead:Oe,either:ne,optional:nt,anyNumberOfTimes:tt};for(const n in z)typeof z[n]=="object"&&xe(z[n]);return Object.assign(e,z),e},H=Be({});H.newInstance=()=>Be({});var Kt=H;H.HighlightJS=H;H.default=H;const zt=Ve(Kt);export{zt as HighlightJS,zt as default};