@idl3/claude-control 1.4.7 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/picker-send-guard.js +35 -0
- package/lib/prompt.js +23 -0
- package/lib/reply-guard.js +24 -0
- package/lib/sessions.js +27 -5
- package/lib/tmux.js +31 -5
- package/lib/transcript.js +79 -12
- package/package.json +1 -1
- package/server.js +89 -10
- package/web/dist/assets/{core-BeFEsKn3.js → core-7Rg8ndFI.js} +1 -1
- package/web/dist/assets/index-Baa2AtDj.css +1 -0
- package/web/dist/assets/index-BvVlQxBg.js +104 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-4j41LhdC.js +0 -104
- package/web/dist/assets/index-CJQIxV4S.css +0 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* picker-send-guard.js — pure predicate for the send-time synchronous picker guard.
|
|
3
|
+
*
|
|
4
|
+
* Extracted so it can be unit-tested without importing server.js (which starts
|
|
5
|
+
* a server on load). The integration glue (capture + the kind-appropriate parser:
|
|
6
|
+
* parsePanePrompt for claude, parseCodexPrompt for codex) lives in server.js; this
|
|
7
|
+
* file owns only the parser-agnostic decision logic on the parsed result.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns true when a free-text `reply` should be REFUSED because a picker is
|
|
12
|
+
* open in the pane.
|
|
13
|
+
*
|
|
14
|
+
* @param {{ viaAnswer: boolean|undefined, kind: string, transport: string, parsedPicker: object|null }} opts
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
export function shouldRefuseSendForPicker({ viaAnswer, kind, transport, parsedPicker }) {
|
|
18
|
+
// viaAnswer replies are the trailing keystroke AFTER the answer component has
|
|
19
|
+
// already navigated the picker — they ARE the answer, so they must pass through.
|
|
20
|
+
if (viaAnswer) return false;
|
|
21
|
+
|
|
22
|
+
// Only keystroke-TUI panes have a tmux picker to guard. Claude (AskUserQuestion
|
|
23
|
+
// + pane-scrape menus, via parsePanePrompt) and Codex (exec/patch/trust approvals
|
|
24
|
+
// + numbered questions, via parseCodexPrompt) both qualify. The caller picks the
|
|
25
|
+
// parser by kind and passes its result as parsedPicker; this predicate only reads
|
|
26
|
+
// the boolean presence, so one decision path covers both kinds.
|
|
27
|
+
if (kind !== 'claude' && kind !== 'codex') return false;
|
|
28
|
+
|
|
29
|
+
// claude-print transport is not a keystroke TUI — no tmux picker to guard.
|
|
30
|
+
// (Codex panes never use the 'print' transport, so this is a Claude-only carve-out.)
|
|
31
|
+
if (transport === 'print') return false;
|
|
32
|
+
|
|
33
|
+
// ANY picker present (question, numbered menu, trust/plan/permission scrape) → refuse.
|
|
34
|
+
return Boolean(parsedPicker);
|
|
35
|
+
}
|
package/lib/prompt.js
CHANGED
|
@@ -35,6 +35,29 @@ const ESC_HINT_RE = /\besc\b[^\n]*(cancel|reject|keep)/i;
|
|
|
35
35
|
const BOTTOM_REGION = 80;
|
|
36
36
|
const MAX_LABEL = 80;
|
|
37
37
|
|
|
38
|
+
// Stable phrases that mark a GENUINE Claude system prompt (permission / trust /
|
|
39
|
+
// plan-review) — as opposed to a custom numbered picker an agent or skill draws
|
|
40
|
+
// itself, or a prose question the assistant typed. Only system prompts should
|
|
41
|
+
// pop the question component via the pane scrape; AskUserQuestion flows through
|
|
42
|
+
// the structured transcript path instead. Conservative by design: an
|
|
43
|
+
// unrecognized picker is treated as NOT a system prompt and suppressed.
|
|
44
|
+
const SYSTEM_PROMPT_RE =
|
|
45
|
+
/don'?t ask again|tell claude what to do differently|keep planning|auto-?accept edits|manually approve edits|do you want to proceed|would you like to proceed|do you trust|yes,?\s*proceed|no,?\s*exit/i;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Is this parsed prompt a recognized Claude system prompt (permission / trust /
|
|
49
|
+
* plan-review)? Matches the question text + option labels against stable system
|
|
50
|
+
* phrasings. Returns false for custom agent/skill pickers and prose questions.
|
|
51
|
+
*
|
|
52
|
+
* @param {{question?:string, options?:{label:string}[]}|null} prompt
|
|
53
|
+
* @returns {boolean}
|
|
54
|
+
*/
|
|
55
|
+
export function isSystemPrompt(prompt) {
|
|
56
|
+
if (!prompt) return false;
|
|
57
|
+
const text = [prompt.question || '', ...(prompt.options || []).map((o) => o.label)].join(' \n ');
|
|
58
|
+
return SYSTEM_PROMPT_RE.test(text);
|
|
59
|
+
}
|
|
60
|
+
|
|
38
61
|
/**
|
|
39
62
|
* Parse a Claude Code numbered selection prompt out of a pane capture.
|
|
40
63
|
*
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/reply-guard.js — Pure predicate for the server-side reply safety guard.
|
|
3
|
+
*
|
|
4
|
+
* Extracted so it can be unit-tested without importing server.js (which starts
|
|
5
|
+
* an HTTP server on import and carries heavyweight side effects).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Returns true when sending a raw reply (bracketed-paste + Enter via tmux) must
|
|
10
|
+
* be blocked because an AskUserQuestion picker is currently open in the pane.
|
|
11
|
+
*
|
|
12
|
+
* Callers should pass BOTH available pending signals so neither source can be
|
|
13
|
+
* individually defeated by a race or stale state:
|
|
14
|
+
* - tailerPending — real-time pending object from TranscriptTailer.getPending()
|
|
15
|
+
* (non-null when an open tool_use has no matching tool_result)
|
|
16
|
+
* - flagPending — registry session.pending flag (set via setPending / _pollThinking)
|
|
17
|
+
*
|
|
18
|
+
* @param {object|null|undefined} tailerPending TranscriptTailer.getPending() result
|
|
19
|
+
* @param {boolean|null|undefined} flagPending registry session.pending flag
|
|
20
|
+
* @returns {boolean}
|
|
21
|
+
*/
|
|
22
|
+
export function replyShouldBlock(tailerPending, flagPending) {
|
|
23
|
+
return Boolean(tailerPending) || Boolean(flagPending);
|
|
24
|
+
}
|
package/lib/sessions.js
CHANGED
|
@@ -15,7 +15,7 @@ import { execFile as _execFile } from 'node:child_process';
|
|
|
15
15
|
import { promisify } from 'node:util';
|
|
16
16
|
|
|
17
17
|
import { parseTuiStatus, prettyModel } from './tui.js';
|
|
18
|
-
import { parsePanePrompt } from './prompt.js';
|
|
18
|
+
import { parsePanePrompt, isSystemPrompt } from './prompt.js';
|
|
19
19
|
import { assignTranscripts, parseEtime, fingerprintScore, shouldRebind } from './match.js';
|
|
20
20
|
import { pinKey } from './pins.js';
|
|
21
21
|
import { readPaneRegistry, gcPaneRegistry } from './pane-registry.js';
|
|
@@ -56,7 +56,8 @@ function isClaudeCmd(cmd) {
|
|
|
56
56
|
return /^\d+\.\d+(\.\d+)?$/.test(String(cmd || '').trim());
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const TAIL_BYTES = 64 * 1024; // 64 KB
|
|
59
|
+
const TAIL_BYTES = 64 * 1024; // 64 KB initial tail read
|
|
60
|
+
const MAX_TAIL_BYTES = 1024 * 1024; // 1 MB ceiling — ensures a single oversized AskUserQuestion record is never split at the front
|
|
60
61
|
const REFRESH_INTERVAL_MS = 4000;
|
|
61
62
|
const CTX_POLL_INTERVAL_MS = 12000; // TUI ctx%/model capture — slower than refresh
|
|
62
63
|
const THINKING_POLL_INTERVAL_MS = 2000; // bottom-5-line capture for the live "thinking" flag
|
|
@@ -260,10 +261,27 @@ async function readTail(filePath, maxBytes) {
|
|
|
260
261
|
* @param {number} [birthtime] birthtime (ms since epoch) of the file
|
|
261
262
|
* @returns {Promise<object|null>}
|
|
262
263
|
*/
|
|
263
|
-
async function extractTailRecord(filePath, mtime, birthtime = null) {
|
|
264
|
-
|
|
264
|
+
export async function extractTailRecord(filePath, mtime, birthtime = null) {
|
|
265
|
+
let buf = await readTail(filePath, TAIL_BYTES);
|
|
265
266
|
if (!buf) return null;
|
|
266
267
|
|
|
268
|
+
// If the file is larger than TAIL_BYTES, the read started mid-file, so the
|
|
269
|
+
// first line of the buffer is almost certainly partial (truncated at the
|
|
270
|
+
// front). A single large AskUserQuestion record (>64 KB) can have its
|
|
271
|
+
// opening `{"type":"assistant"...tool_use...` in that discarded partial
|
|
272
|
+
// line, causing detectTranscriptPending to miss the open question entirely.
|
|
273
|
+
// Re-reading up to MAX_TAIL_BYTES guarantees any single record ≤ 1 MB is
|
|
274
|
+
// captured whole at the front of the buffer.
|
|
275
|
+
try {
|
|
276
|
+
const stat = await fs.stat(filePath);
|
|
277
|
+
if (stat.size > TAIL_BYTES) {
|
|
278
|
+
const larger = await readTail(filePath, Math.min(stat.size, MAX_TAIL_BYTES));
|
|
279
|
+
if (larger) buf = larger;
|
|
280
|
+
}
|
|
281
|
+
} catch {
|
|
282
|
+
// stat failed — proceed with the initial 64 KB buffer
|
|
283
|
+
}
|
|
284
|
+
|
|
267
285
|
const text = buf.toString('utf8');
|
|
268
286
|
// Split on newlines; the first segment may be a partial line (the tail read
|
|
269
287
|
// can start part-way through a line), so we never trust it — we only walk
|
|
@@ -1072,7 +1090,11 @@ export class SessionRegistry extends EventEmitter {
|
|
|
1072
1090
|
// numbered picker means a question is waiting — even if the transcript
|
|
1073
1091
|
// isn't matched. This is why some sessions wrongly read as "sleeping".
|
|
1074
1092
|
if (s.kind === 'claude') {
|
|
1075
|
-
|
|
1093
|
+
// Only a RECOGNIZED system prompt (permission/trust/plan) counts from
|
|
1094
|
+
// the scrape; custom agent/skill pickers don't light the ASK badge.
|
|
1095
|
+
// Real AskUserQuestion still flows via the transcript (_pendingMap).
|
|
1096
|
+
const parsed = parsePanePrompt(cap);
|
|
1097
|
+
const prompt = isSystemPrompt(parsed) ? parsed : null;
|
|
1076
1098
|
const rec = { pending: !!prompt, question: prompt?.question ?? null };
|
|
1077
1099
|
this._panePromptMap.set(s.target, rec);
|
|
1078
1100
|
const merged =
|
package/lib/tmux.js
CHANGED
|
@@ -521,16 +521,28 @@ let _pasteBufferSeq = 0;
|
|
|
521
521
|
* Falls back to the old literal `send-keys` path if the buffer route errors, so
|
|
522
522
|
* behaviour never regresses below today's baseline.
|
|
523
523
|
*
|
|
524
|
+
* Submit is DETERMINISTIC, not timed: after the paste we poll the pane until the
|
|
525
|
+
* TUI's "Pasting…" indicator clears, THEN send Enter. A pasted image path is read
|
|
526
|
+
* + encoded asynchronously by the TUI (it shows "Pasting…" meanwhile); an Enter
|
|
527
|
+
* sent during that window is swallowed and the message sits unsent in the box —
|
|
528
|
+
* the exact bug a fixed delay could only guess around. `settleMs` is the MAX time
|
|
529
|
+
* to wait for "Pasting…" to clear (a ceiling, not a fixed cost — the poll exits as
|
|
530
|
+
* soon as the paste finishes). The reply handler scales it per attachment.
|
|
531
|
+
*
|
|
524
532
|
* @param {string} target e.g. "0:3"
|
|
525
533
|
* @param {string} text
|
|
526
|
-
* @param {{ _run?: Function, _delay?: Function }} [_injected] Test
|
|
534
|
+
* @param {{ _run?: Function, _delay?: Function, settleMs?: number }} [_injected] Test seam + poll budget.
|
|
527
535
|
* @returns {Promise<void>}
|
|
528
536
|
*/
|
|
529
|
-
export async function sendText(target, text, { _run, _delay } = {}) {
|
|
537
|
+
export async function sendText(target, text, { _run, _delay, settleMs } = {}) {
|
|
530
538
|
assertTarget(target);
|
|
531
539
|
const runner = _run ?? runTmux;
|
|
532
540
|
const delay = _delay ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
533
|
-
const
|
|
541
|
+
const BUDGET_MS = Number.isFinite(settleMs) ? settleMs : 2000; // max wait for paste to settle
|
|
542
|
+
const INITIAL_MS = 120; // let the TUI enter its "Pasting…" state before polling
|
|
543
|
+
const POLL_MS = 120; // re-check cadence
|
|
544
|
+
const POST_PASTE_MS = 250; // commit settle after "Pasting…" clears, before Enter
|
|
545
|
+
const PASTING_RE = /Pasting/i;
|
|
534
546
|
|
|
535
547
|
const bufName = `cc-paste-${process.pid}-${_pasteBufferSeq++}`;
|
|
536
548
|
try {
|
|
@@ -538,8 +550,22 @@ export async function sendText(target, text, { _run, _delay } = {}) {
|
|
|
538
550
|
await runner(['set-buffer', '-b', bufName, text]);
|
|
539
551
|
// Bracketed paste into the pane (-p), deleting the buffer after (-d).
|
|
540
552
|
await runner(['paste-buffer', '-d', '-p', '-b', bufName, '-t', target]);
|
|
541
|
-
//
|
|
542
|
-
|
|
553
|
+
// Wait for the TUI to FINISH ingesting the paste rather than guessing a delay:
|
|
554
|
+
// poll the pane until "Pasting…" is gone (bounded by the budget as a ceiling).
|
|
555
|
+
await delay(INITIAL_MS);
|
|
556
|
+
const maxPolls = Math.max(1, Math.ceil(BUDGET_MS / POLL_MS));
|
|
557
|
+
for (let p = 0; p < maxPolls; p++) {
|
|
558
|
+
let cap = '';
|
|
559
|
+
try {
|
|
560
|
+
cap = (await runner(['capture-pane', '-p', '-t', target])).stdout || '';
|
|
561
|
+
} catch {
|
|
562
|
+
break; // capture failed — don't hang; fall through to Enter
|
|
563
|
+
}
|
|
564
|
+
if (!PASTING_RE.test(cap)) break;
|
|
565
|
+
await delay(POLL_MS);
|
|
566
|
+
}
|
|
567
|
+
// Let the input commit the just-ingested content (image chip) before submit.
|
|
568
|
+
await delay(POST_PASTE_MS);
|
|
543
569
|
await runner(['send-keys', '-t', target, 'Enter']);
|
|
544
570
|
} catch {
|
|
545
571
|
// Fallback to the legacy literal path (also clean up a possibly-orphaned buffer).
|
package/lib/transcript.js
CHANGED
|
@@ -36,6 +36,7 @@ const TAIL_MAX_BYTES = envInt('TAIL_BYTES') ?? 8 * 1024 * 1024; // 8 MB
|
|
|
36
36
|
// well within the server's RSS budget.
|
|
37
37
|
// Override with CLAUDE_CONTROL_MAX_BUFFER (legacy: COCKPIT_MAX_BUFFER).
|
|
38
38
|
const DEFAULT_MAX_BUFFER = envInt('MAX_BUFFER') ?? 4000;
|
|
39
|
+
const DEFAULT_POLL_MS = envInt('TAIL_POLL_MS') ?? 1000;
|
|
39
40
|
|
|
40
41
|
// ---------------------------------------------------------------------------
|
|
41
42
|
// Internal helper: read the last `maxBytes` of a file without loading it all.
|
|
@@ -132,6 +133,27 @@ export function parseRecord(line) {
|
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
const rawType = record.type;
|
|
136
|
+
|
|
137
|
+
// A message typed while the agent is busy is stored by Claude Code as a
|
|
138
|
+
// `queued_command` attachment — NOT a type=user record. Surface human prompt
|
|
139
|
+
// queues as user messages so they render as a real bubble AND let the cockpit's
|
|
140
|
+
// optimistic send bubble reconcile (it matches on user-message text). Marked
|
|
141
|
+
// queued:true so convert drops it if the same text later lands as a type=user.
|
|
142
|
+
if (rawType === 'attachment' && record.attachment?.type === 'queued_command') {
|
|
143
|
+
const a = record.attachment;
|
|
144
|
+
if (a.origin?.kind !== 'human' || a.commandMode !== 'prompt') return null;
|
|
145
|
+
const prompt = typeof a.prompt === 'string' ? a.prompt : '';
|
|
146
|
+
if (!prompt.trim()) return null;
|
|
147
|
+
return {
|
|
148
|
+
uuid: record.uuid ?? null,
|
|
149
|
+
role: 'user',
|
|
150
|
+
ts: record.timestamp ?? null,
|
|
151
|
+
blocks: [{ kind: 'text', text: prompt }],
|
|
152
|
+
rawType: 'queued_command',
|
|
153
|
+
queued: true,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
135
157
|
if (rawType !== 'user' && rawType !== 'assistant') return null;
|
|
136
158
|
|
|
137
159
|
const msg = record.message;
|
|
@@ -194,13 +216,24 @@ export function parseRecord(line) {
|
|
|
194
216
|
export class TranscriptTailer extends EventEmitter {
|
|
195
217
|
/**
|
|
196
218
|
* @param {string} filePath
|
|
197
|
-
* @param {{ maxBuffer?: number, debounceMs?: number, parser?: Function }} options
|
|
219
|
+
* @param {{ maxBuffer?: number, debounceMs?: number, pollMs?: number, watch?: boolean, parser?: Function }} options
|
|
198
220
|
*/
|
|
199
|
-
constructor(
|
|
221
|
+
constructor(
|
|
222
|
+
filePath,
|
|
223
|
+
{
|
|
224
|
+
maxBuffer = DEFAULT_MAX_BUFFER,
|
|
225
|
+
debounceMs = 150,
|
|
226
|
+
pollMs = DEFAULT_POLL_MS,
|
|
227
|
+
watch = true,
|
|
228
|
+
parser = parseRecord,
|
|
229
|
+
} = {},
|
|
230
|
+
) {
|
|
200
231
|
super();
|
|
201
232
|
this._filePath = filePath;
|
|
202
233
|
this._maxBuffer = maxBuffer;
|
|
203
234
|
this._debounceMs = debounceMs;
|
|
235
|
+
this._pollMs = Number.isFinite(pollMs) && pollMs > 0 ? pollMs : 0;
|
|
236
|
+
this._watchEnabled = watch !== false;
|
|
204
237
|
this._parse = parser;
|
|
205
238
|
|
|
206
239
|
/** @type {import('./transcript.js').NormalizedMessage[]} */
|
|
@@ -221,6 +254,9 @@ export class TranscriptTailer extends EventEmitter {
|
|
|
221
254
|
/** fs.FSWatcher | null */
|
|
222
255
|
this._watcher = null;
|
|
223
256
|
|
|
257
|
+
/** Poll fallback timer handle */
|
|
258
|
+
this._pollTimer = null;
|
|
259
|
+
|
|
224
260
|
/** Debounce timer handle */
|
|
225
261
|
this._debounceTimer = null;
|
|
226
262
|
|
|
@@ -229,6 +265,9 @@ export class TranscriptTailer extends EventEmitter {
|
|
|
229
265
|
|
|
230
266
|
/** Set by stop(); guards against attaching a watcher after teardown. */
|
|
231
267
|
this._stopped = false;
|
|
268
|
+
|
|
269
|
+
/** start() is one-shot even when watch=false and polling owns the tail. */
|
|
270
|
+
this._started = false;
|
|
232
271
|
}
|
|
233
272
|
|
|
234
273
|
// -------------------------------------------------------------------------
|
|
@@ -258,11 +297,13 @@ export class TranscriptTailer extends EventEmitter {
|
|
|
258
297
|
* if already watching.
|
|
259
298
|
*/
|
|
260
299
|
async start() {
|
|
261
|
-
if (this.
|
|
300
|
+
if (this._started) return;
|
|
301
|
+
this._started = true;
|
|
262
302
|
|
|
263
303
|
try {
|
|
264
304
|
await this._initialLoad();
|
|
265
305
|
} catch (err) {
|
|
306
|
+
this._started = false;
|
|
266
307
|
this.emit('error', err);
|
|
267
308
|
return;
|
|
268
309
|
}
|
|
@@ -272,14 +313,36 @@ export class TranscriptTailer extends EventEmitter {
|
|
|
272
313
|
// watcher to a tailer nobody is listening to — that would leak an fd.
|
|
273
314
|
if (this._stopped) return;
|
|
274
315
|
|
|
275
|
-
// Start watching. Use 'rename' events too (handles log rotation).
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
316
|
+
// Start watching. Use 'rename' events too (handles log rotation). fs.watch
|
|
317
|
+
// is best-effort on macOS and network filesystems, so a poll fallback below
|
|
318
|
+
// is intentionally kept active even when the watcher starts successfully.
|
|
319
|
+
if (this._watchEnabled) {
|
|
320
|
+
try {
|
|
321
|
+
this._watcher = fs.watch(this._filePath, { persistent: false }, () => {
|
|
322
|
+
this._scheduleRead();
|
|
323
|
+
});
|
|
324
|
+
this._watcher.on('error', (err) => this.emit('error', err));
|
|
325
|
+
} catch (err) {
|
|
326
|
+
if (!this._pollMs) {
|
|
327
|
+
this._started = false;
|
|
328
|
+
this.emit('error', err);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (this._pollMs) {
|
|
335
|
+
this._pollTimer = setInterval(() => {
|
|
336
|
+
this._readIncremental().catch((err) => {
|
|
337
|
+
if (err.code !== 'ENOENT') this.emit('error', err);
|
|
338
|
+
});
|
|
339
|
+
}, this._pollMs);
|
|
340
|
+
if (this._pollTimer.unref) this._pollTimer.unref();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (!this._watcher && !this._pollTimer) {
|
|
344
|
+
this._started = false;
|
|
345
|
+
this.emit('error', new Error('TranscriptTailer has neither watch nor poll enabled'));
|
|
283
346
|
return;
|
|
284
347
|
}
|
|
285
348
|
|
|
@@ -288,7 +351,7 @@ export class TranscriptTailer extends EventEmitter {
|
|
|
288
351
|
// Kick off an immediate incremental read to catch any bytes missed in that gap.
|
|
289
352
|
// Use setImmediate to let the watcher finish OS-level registration first.
|
|
290
353
|
setImmediate(() => {
|
|
291
|
-
if (
|
|
354
|
+
if (this._stopped) return; // stop() was called before this fired
|
|
292
355
|
this._readIncremental().catch((err) => {
|
|
293
356
|
// ENOENT means the file was deleted/rotated; not a hard error here.
|
|
294
357
|
if (err.code !== 'ENOENT') this.emit('error', err);
|
|
@@ -303,6 +366,10 @@ export class TranscriptTailer extends EventEmitter {
|
|
|
303
366
|
clearTimeout(this._debounceTimer);
|
|
304
367
|
this._debounceTimer = null;
|
|
305
368
|
}
|
|
369
|
+
if (this._pollTimer !== null) {
|
|
370
|
+
clearInterval(this._pollTimer);
|
|
371
|
+
this._pollTimer = null;
|
|
372
|
+
}
|
|
306
373
|
if (this._watcher) {
|
|
307
374
|
try { this._watcher.close(); } catch { /* ignore */ }
|
|
308
375
|
this._watcher = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idl3/claude-control",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
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
|
@@ -22,7 +22,7 @@ import * as terminal from './lib/terminal.js';
|
|
|
22
22
|
import * as shell from './lib/shell.js';
|
|
23
23
|
import { TranscriptTailer } from './lib/transcript.js';
|
|
24
24
|
import { SubAgentsWatcher, CodexSubAgentsWatcher, listAgents } from './lib/subagents.js';
|
|
25
|
-
import { parsePanePrompt } from './lib/prompt.js';
|
|
25
|
+
import { parsePanePrompt, isSystemPrompt } from './lib/prompt.js';
|
|
26
26
|
import { SessionRegistry, listRecentTranscripts } from './lib/sessions.js';
|
|
27
27
|
import { loadPins, savePins, validateTranscriptPath, pinKey } from './lib/pins.js';
|
|
28
28
|
import { writePaneRegistryRecord } from './lib/pane-registry.js';
|
|
@@ -46,6 +46,8 @@ import {
|
|
|
46
46
|
recommendClaudeModel,
|
|
47
47
|
} from './lib/models.js';
|
|
48
48
|
import { transcribe } from './lib/transcribe.js';
|
|
49
|
+
import { replyShouldBlock } from './lib/reply-guard.js';
|
|
50
|
+
import { shouldRefuseSendForPicker } from './lib/picker-send-guard.js';
|
|
49
51
|
import { listSkills, readSkill } from './lib/skills.js';
|
|
50
52
|
// Note: the client offers [WS_PROTOCOL, token] as subprotocols; the `ws`
|
|
51
53
|
// library auto-selects the FIRST offered one (the non-secret WS_PROTOCOL label)
|
|
@@ -1779,6 +1781,7 @@ async function ensureClaudePrintForSession(session) {
|
|
|
1779
1781
|
function startPromptPoller(id, sub) {
|
|
1780
1782
|
if (sub.promptTimer) return;
|
|
1781
1783
|
sub._lastPrompt = undefined;
|
|
1784
|
+
sub._lastPickerOpen = undefined; // tracks last-broadcast picker state for Item 2
|
|
1782
1785
|
sub._promptTicking = false;
|
|
1783
1786
|
const tick = async () => {
|
|
1784
1787
|
if (sub._promptTicking) return;
|
|
@@ -1787,11 +1790,26 @@ function startPromptPoller(id, sub) {
|
|
|
1787
1790
|
const session = sessionById(id);
|
|
1788
1791
|
if (!session || !tmux.isValidTarget(session.target)) return;
|
|
1789
1792
|
let prompt = null;
|
|
1793
|
+
let pickerOpen = false;
|
|
1790
1794
|
try {
|
|
1791
|
-
//
|
|
1792
|
-
//
|
|
1793
|
-
const cap = await tmux.capturePane(session.target, 80);
|
|
1794
|
-
|
|
1795
|
+
// Visible pane only (no scrollback) — an answered picker frozen in
|
|
1796
|
+
// history must not re-fire. The live prompt is always on screen.
|
|
1797
|
+
const cap = await tmux.capturePane(session.target, 80, false, false, { visibleOnly: true });
|
|
1798
|
+
if (session.kind === 'codex') {
|
|
1799
|
+
prompt = parseCodexPrompt(cap);
|
|
1800
|
+
pickerOpen = !!prompt; // for codex, pickerOpen tracks the same parsed prompt
|
|
1801
|
+
} else {
|
|
1802
|
+
// Claude: only surface RECOGNIZED system prompts (permission / trust /
|
|
1803
|
+
// plan-review). Custom agent/skill pickers and prose questions are NOT
|
|
1804
|
+
// shown — a real AskUserQuestion flows through the structured transcript
|
|
1805
|
+
// path (pending), not this scrape.
|
|
1806
|
+
const parsed = parsePanePrompt(cap);
|
|
1807
|
+
prompt = isSystemPrompt(parsed) ? parsed : null;
|
|
1808
|
+
// pickerOpen is broader than prompt: ANY numbered picker with cursor/footer
|
|
1809
|
+
// qualifies (not just the narrower isSystemPrompt subset). This reuses the
|
|
1810
|
+
// SAME capture already taken — zero new tmux execs.
|
|
1811
|
+
pickerOpen = !!parsed;
|
|
1812
|
+
}
|
|
1795
1813
|
} catch {
|
|
1796
1814
|
return;
|
|
1797
1815
|
}
|
|
@@ -1806,6 +1824,13 @@ function startPromptPoller(id, sub) {
|
|
|
1806
1824
|
});
|
|
1807
1825
|
broadcastTo(id, { type: 'prompt', id, prompt });
|
|
1808
1826
|
}
|
|
1827
|
+
// Broadcast picker awareness separately from the narrower prompt rendering.
|
|
1828
|
+
// pickerOpen=true for ANY parsePanePrompt hit (not just isSystemPrompt) so
|
|
1829
|
+
// clients can disable free-text send without waiting for the next poll cycle.
|
|
1830
|
+
if (pickerOpen !== sub._lastPickerOpen) {
|
|
1831
|
+
sub._lastPickerOpen = pickerOpen;
|
|
1832
|
+
broadcastTo(id, { type: 'picker', id, open: pickerOpen });
|
|
1833
|
+
}
|
|
1809
1834
|
} finally {
|
|
1810
1835
|
sub._promptTicking = false;
|
|
1811
1836
|
}
|
|
@@ -1839,7 +1864,7 @@ wss.on('connection', (ws) => {
|
|
|
1839
1864
|
try {
|
|
1840
1865
|
await handleClientMessage(ws, msg);
|
|
1841
1866
|
} catch (err) {
|
|
1842
|
-
send(ws, { type: 'ack', op: msg?.type || 'unknown', ok: false, error: String(err?.message || err) });
|
|
1867
|
+
send(ws, { type: 'ack', op: msg?.type || 'unknown', ok: false, error: String(err?.message || err), reqId: msg?.reqId });
|
|
1843
1868
|
}
|
|
1844
1869
|
});
|
|
1845
1870
|
|
|
@@ -1880,7 +1905,35 @@ async function handleClientMessage(ws, msg) {
|
|
|
1880
1905
|
const session = sessionById(msg.id);
|
|
1881
1906
|
if (!session) throw new Error('unknown session');
|
|
1882
1907
|
if (!tmux.isValidTarget(session.target)) throw new Error('invalid tmux target');
|
|
1908
|
+
// SAFETY: never send a raw reply (paste+Enter) into a pane with an OPEN
|
|
1909
|
+
// picker (AskUserQuestion OR a pane-scrape permission/trust/plan/numbered
|
|
1910
|
+
// menu) — the Enter would select an option, silently answering it. The
|
|
1911
|
+
// client must answer via the structured 'answer'/'promptkey'/'promptselect'
|
|
1912
|
+
// path. Refuse raw replies here as defense-in-depth (covers stale/racey
|
|
1913
|
+
// clients). EXCEPTION: a reply flagged `viaAnswer` is the trailing free-text
|
|
1914
|
+
// of a DELIBERATE answer the inline component sends AFTER it has already
|
|
1915
|
+
// navigated the picker — that IS the answer, so it must pass through.
|
|
1916
|
+
if (!msg.viaAnswer) {
|
|
1917
|
+
const replySub = subscriptions.get(msg.id);
|
|
1918
|
+
const tailerPending = replySub?.tailer ? replySub.tailer.getPending() : null;
|
|
1919
|
+
const flagPending = !!session.pending;
|
|
1920
|
+
if (replyShouldBlock(tailerPending, flagPending)) {
|
|
1921
|
+
send(ws, { type: 'ack', op: 'reply', ok: false, reqId: msg.reqId, error: 'A question is open — answer it via the question component' });
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1883
1925
|
const replyText = String(msg.text ?? '');
|
|
1926
|
+
const reqId = msg.reqId; // correlation id: echoed in the ack so the client
|
|
1927
|
+
// marks THIS send delivered (not just WS-written).
|
|
1928
|
+
// Per-attachment settle: each pasted image path is ingested asynchronously
|
|
1929
|
+
// by the TUI (read + validate/encode), and a too-early Enter lands before
|
|
1930
|
+
// that finishes, leaving the message unsent in the box. Scale the paste→Enter
|
|
1931
|
+
// gap by the attachment count (capped) so the Enter always submits.
|
|
1932
|
+
const attachments = Math.max(0, Number(msg.attachments) || 0);
|
|
1933
|
+
// sendText polls for the TUI's "Pasting…" to clear before Enter; settleMs is
|
|
1934
|
+
// the MAX it waits (a ceiling, not a fixed cost — it exits as soon as the
|
|
1935
|
+
// paste finishes). Budget generously per image so even a slow read fits.
|
|
1936
|
+
const settleMs = Math.min(20000, 1500 + attachments * 3000);
|
|
1884
1937
|
return runSerial(session.target, async () => {
|
|
1885
1938
|
if (session.kind === 'claude' && session.transport === 'print') {
|
|
1886
1939
|
await ensureClaudePrintForSession(session);
|
|
@@ -1891,7 +1944,7 @@ async function handleClientMessage(ws, msg) {
|
|
|
1891
1944
|
registry.setThinking(session.target, false);
|
|
1892
1945
|
throw err;
|
|
1893
1946
|
}
|
|
1894
|
-
send(ws, { type: 'ack', op: 'reply', ok: true, transport: 'claude-print' });
|
|
1947
|
+
send(ws, { type: 'ack', op: 'reply', ok: true, transport: 'claude-print', reqId });
|
|
1895
1948
|
return;
|
|
1896
1949
|
}
|
|
1897
1950
|
if (session.kind === 'codex') {
|
|
@@ -1904,15 +1957,41 @@ async function handleClientMessage(ws, msg) {
|
|
|
1904
1957
|
registry.setThinking(session.target, false);
|
|
1905
1958
|
throw err;
|
|
1906
1959
|
}
|
|
1907
|
-
send(ws, { type: 'ack', op: 'reply', ok: true, transport: 'codex-rpc' });
|
|
1960
|
+
send(ws, { type: 'ack', op: 'reply', ok: true, transport: 'codex-rpc', reqId });
|
|
1908
1961
|
return;
|
|
1909
1962
|
}
|
|
1910
1963
|
// Codex TUI compatibility: only non-app-server Codex panes may use
|
|
1911
1964
|
// tmux keystrokes. RPC app-server panes must never receive prompt text
|
|
1912
1965
|
// in their terminal buffer.
|
|
1913
1966
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1967
|
+
// ── Synchronous send-time picker guard ─────────────────────────────
|
|
1968
|
+
// This is the race-free authority: it checks the ACTUAL current screen
|
|
1969
|
+
// at send-time, independent of poll cadence, the 64KB flag window, and
|
|
1970
|
+
// tailer state. The replyShouldBlock flag-check above (lines ~1901-1909)
|
|
1971
|
+
// stays as cheap defense-in-depth that can short-circuit before we even
|
|
1972
|
+
// get here. viaAnswer replies bypass BOTH guards (they are the answer).
|
|
1973
|
+
//
|
|
1974
|
+
// Gate: keystroke-TUI panes (claude AND codex-TUI). Print transport has no
|
|
1975
|
+
// keystroke TUI to guard against, and codex-rpc panes already returned above
|
|
1976
|
+
// (so any codex pane reaching here fell through to tmux sendText). The parser
|
|
1977
|
+
// is chosen by kind — parsePanePrompt for claude, parseCodexPrompt for codex —
|
|
1978
|
+
// because the two TUIs scrape pickers differently; the pure predicate then
|
|
1979
|
+
// decides on the boolean presence of a parsed picker, identical for both.
|
|
1980
|
+
if (!msg.viaAnswer && (session.kind === 'claude' || session.kind === 'codex') && session.transport !== 'print') {
|
|
1981
|
+
try {
|
|
1982
|
+
const cap = await tmux.capturePane(session.target, 80, false, false, { visibleOnly: true });
|
|
1983
|
+
const parsedPicker = session.kind === 'codex' ? parseCodexPrompt(cap) : parsePanePrompt(cap);
|
|
1984
|
+
if (shouldRefuseSendForPicker({ viaAnswer: msg.viaAnswer, kind: session.kind, transport: session.transport, parsedPicker })) {
|
|
1985
|
+
send(ws, { type: 'ack', op: 'reply', ok: false, reqId, error: 'A question/picker is open in this pane — answer it via the question UI, not a free-text reply.' });
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
} catch {
|
|
1989
|
+
// Capture failed: do NOT block. The replyShouldBlock flag-check above
|
|
1990
|
+
// is the fallback. Better to allow a send than to stall on a tmux error.
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
await tmux.sendText(session.target, replyText, { settleMs });
|
|
1994
|
+
send(ws, { type: 'ack', op: 'reply', ok: true, reqId });
|
|
1916
1995
|
});
|
|
1917
1996
|
}
|
|
1918
1997
|
case 'answer': {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{g as Ve}from"./index-4j41LhdC.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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}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-BvVlQxBg.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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}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};
|