@idl3/claude-control 0.1.21 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/lib/config.js +39 -3
- package/lib/mlx.js +260 -0
- package/lib/models.js +66 -0
- package/package.json +1 -1
- package/server.js +64 -2
- package/web/dist/assets/{core-CyYMg33t.js → core-CZTz1vMx.js} +1 -1
- package/web/dist/assets/{index-BeJg6Cs1.js → index-Bup-kzmD.js} +31 -31
- package/web/dist/assets/{index-Dn7NDGPq.css → index-D21GSqEK.css} +1 -1
- package/web/dist/index.html +4 -2
package/README.md
CHANGED
|
@@ -20,6 +20,16 @@ npm install -g @idl3/claude-control # or run once: npx @idl3/claude-control
|
|
|
20
20
|
|
|
21
21
|
**Prerequisites:** Node ≥20 and **tmux** on your `PATH` (`brew install tmux` · `sudo apt install tmux`). Optional: **ttyd** for the in-browser raw terminal (`brew install ttyd` · `sudo apt install ttyd`) — set `CLAUDE_CONTROL_TTYD` to override its path. The web UI ships prebuilt — no build step on install.
|
|
22
22
|
|
|
23
|
+
**Optional local AI (no API key):**
|
|
24
|
+
|
|
25
|
+
- **Voice → text** — `brew install ffmpeg whisper-cpp` and drop a model at `~/.claude-control/models/ggml-base.en.bin`. The mic in the composer records audio and transcribes it locally.
|
|
26
|
+
- **Prompt enhancer (✨)** — defaults to a **local MLX model** on Apple Silicon. One-time setup:
|
|
27
|
+
```bash
|
|
28
|
+
python3 -m venv ~/.claude-control/mlx-venv
|
|
29
|
+
~/.claude-control/mlx-venv/bin/pip install mlx-lm
|
|
30
|
+
```
|
|
31
|
+
claude-control lazily starts `mlx_lm.server` on first use, keeps it warm, and shuts it down when idle. The model (default `mlx-community/Llama-3.2-3B-Instruct-4bit`, ~1.8 GB) auto-downloads on first run. Pick the backend + model in **Settings** (`mlx` → `claude -p` → rules fallback). Without the venv (or on non-Apple hardware) the enhancer falls back to `claude -p`, then a deterministic rules optimiser. Env overrides: `CLAUDE_CONTROL_MLX_PYTHON`, `CLAUDE_CONTROL_MLX_PORT`.
|
|
32
|
+
|
|
23
33
|
```bash
|
|
24
34
|
claude-control # start the server (prints the URL)
|
|
25
35
|
claude-control --help # config + subcommands
|
package/lib/config.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import fs from 'node:fs';
|
|
23
23
|
import path from 'node:path';
|
|
24
24
|
import os from 'node:os';
|
|
25
|
+
import { detectMachine, recommendMlxModel, recommendClaudeModel } from './models.js';
|
|
25
26
|
|
|
26
27
|
// Env lookup mirrors server.js: prefer CLAUDE_CONTROL_<X>, fall back to the
|
|
27
28
|
// legacy COCKPIT_<X> so existing launchers keep working.
|
|
@@ -41,14 +42,21 @@ function configPath() {
|
|
|
41
42
|
const LAUNCH_MAX = 500;
|
|
42
43
|
const OPTIMIZE_MODEL_MAX = 200;
|
|
43
44
|
const CLAUDE_BIN_MAX = 500;
|
|
45
|
+
const MLX_MODEL_MAX = 200;
|
|
46
|
+
const OPTIMIZE_BACKENDS = ['mlx', 'claude', 'rules'];
|
|
44
47
|
|
|
45
48
|
/** Defaults, recomputed each call so a changed HOME/env is honoured. */
|
|
46
49
|
function defaults() {
|
|
47
50
|
return {
|
|
48
51
|
launchCommand: 'claude',
|
|
49
52
|
defaultCwd: os.homedir(),
|
|
50
|
-
optimizeModel:
|
|
53
|
+
optimizeModel: recommendClaudeModel(),
|
|
51
54
|
claudeBin: '',
|
|
55
|
+
// Prompt-enhancer backend: 'mlx' (local model → claude → rules chain),
|
|
56
|
+
// 'claude' (claude -p → rules), or 'rules' (deterministic, offline).
|
|
57
|
+
optimizeBackend: 'mlx',
|
|
58
|
+
// Default MLX model auto-picked for this machine's unified memory.
|
|
59
|
+
mlxModel: recommendMlxModel(detectMachine().ramGB),
|
|
52
60
|
};
|
|
53
61
|
}
|
|
54
62
|
|
|
@@ -56,7 +64,7 @@ function defaults() {
|
|
|
56
64
|
* Read the persisted config, merged over defaults. Never throws — a missing,
|
|
57
65
|
* empty, or corrupt file falls back to defaults. Only known keys are surfaced.
|
|
58
66
|
*
|
|
59
|
-
* @returns {{ launchCommand: string, defaultCwd: string, optimizeModel: string, claudeBin: string }}
|
|
67
|
+
* @returns {{ launchCommand: string, defaultCwd: string, optimizeModel: string, claudeBin: string, optimizeBackend: string, mlxModel: string }}
|
|
60
68
|
*/
|
|
61
69
|
export function readConfig() {
|
|
62
70
|
const base = defaults();
|
|
@@ -84,6 +92,15 @@ export function readConfig() {
|
|
|
84
92
|
typeof parsed.claudeBin === 'string'
|
|
85
93
|
? parsed.claudeBin
|
|
86
94
|
: base.claudeBin,
|
|
95
|
+
optimizeBackend:
|
|
96
|
+
typeof parsed.optimizeBackend === 'string' &&
|
|
97
|
+
OPTIMIZE_BACKENDS.includes(parsed.optimizeBackend)
|
|
98
|
+
? parsed.optimizeBackend
|
|
99
|
+
: base.optimizeBackend,
|
|
100
|
+
mlxModel:
|
|
101
|
+
typeof parsed.mlxModel === 'string' && parsed.mlxModel.trim()
|
|
102
|
+
? parsed.mlxModel
|
|
103
|
+
: base.mlxModel,
|
|
87
104
|
};
|
|
88
105
|
}
|
|
89
106
|
|
|
@@ -99,7 +116,7 @@ export function readConfig() {
|
|
|
99
116
|
* Existence is NOT verified at write time (path may differ across hosts).
|
|
100
117
|
*
|
|
101
118
|
* @param {{ launchCommand?: unknown, defaultCwd?: unknown, optimizeModel?: unknown, claudeBin?: unknown }} partial
|
|
102
|
-
* @returns {{ launchCommand: string, defaultCwd: string, optimizeModel: string, claudeBin: string }} the saved config
|
|
119
|
+
* @returns {{ launchCommand: string, defaultCwd: string, optimizeModel: string, claudeBin: string, optimizeBackend: string, mlxModel: string }} the saved config
|
|
103
120
|
*/
|
|
104
121
|
export function writeConfig(partial = {}) {
|
|
105
122
|
const current = readConfig();
|
|
@@ -155,6 +172,25 @@ export function writeConfig(partial = {}) {
|
|
|
155
172
|
next.claudeBin = bin;
|
|
156
173
|
}
|
|
157
174
|
|
|
175
|
+
if (partial.optimizeBackend !== undefined) {
|
|
176
|
+
const b = partial.optimizeBackend;
|
|
177
|
+
if (typeof b !== 'string' || !OPTIMIZE_BACKENDS.includes(b)) {
|
|
178
|
+
throw new Error(`optimizeBackend must be one of: ${OPTIMIZE_BACKENDS.join(', ')}`);
|
|
179
|
+
}
|
|
180
|
+
next.optimizeBackend = b;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (partial.mlxModel !== undefined) {
|
|
184
|
+
const m = partial.mlxModel;
|
|
185
|
+
if (typeof m !== 'string' || !m.trim()) {
|
|
186
|
+
throw new Error('mlxModel must be a non-empty string');
|
|
187
|
+
}
|
|
188
|
+
if (m.length > MLX_MODEL_MAX) {
|
|
189
|
+
throw new Error(`mlxModel must be ≤${MLX_MODEL_MAX} characters`);
|
|
190
|
+
}
|
|
191
|
+
next.mlxModel = m;
|
|
192
|
+
}
|
|
193
|
+
|
|
158
194
|
const dir = dataDir();
|
|
159
195
|
fs.mkdirSync(dir, { recursive: true });
|
|
160
196
|
fs.writeFileSync(configPath(), JSON.stringify(next, null, 2), { mode: 0o600 });
|
package/lib/mlx.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/mlx.js — local LLM backend via a managed mlx_lm.server (Apple Silicon).
|
|
3
|
+
*
|
|
4
|
+
* Spawns a singleton OpenAI-compatible MLX server on first use, keeps it warm,
|
|
5
|
+
* and shuts it down after an idle period. No API key, no network — fully local.
|
|
6
|
+
* Used by the prompt enhancer as the first link in the mlx → claude → rules
|
|
7
|
+
* chain (server.js handleOptimize).
|
|
8
|
+
*
|
|
9
|
+
* Exports:
|
|
10
|
+
* - resolveMlxPython() → string | null (venv python that has mlx_lm)
|
|
11
|
+
* - serverBase(port) → string (pure)
|
|
12
|
+
* - buildChatBody(prompt, model, maxTokens) → object (pure)
|
|
13
|
+
* - parseChatContent(json) → string (pure; throws on bad/empty shape)
|
|
14
|
+
* - complete(prompt, { model, port, maxTokens }) → Promise<string>
|
|
15
|
+
* - shutdown() (kill the child; for exit/tests)
|
|
16
|
+
*
|
|
17
|
+
* Config/env: model from config.mlxModel (default below); port via
|
|
18
|
+
* CLAUDE_CONTROL_MLX_PORT (default 8080); python via CLAUDE_CONTROL_MLX_PYTHON
|
|
19
|
+
* else ~/.claude-control/mlx-venv/bin/python else a PATH python3 with mlx_lm.
|
|
20
|
+
*/
|
|
21
|
+
import fs from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import os from 'node:os';
|
|
24
|
+
import { spawn, execFileSync } from 'node:child_process';
|
|
25
|
+
import { readConfig } from './config.js';
|
|
26
|
+
|
|
27
|
+
export const DEFAULT_MODEL = 'mlx-community/Llama-3.2-3B-Instruct-4bit';
|
|
28
|
+
// Dedicated port for OUR managed sidecar. NOT 8080 — that's a very common port
|
|
29
|
+
// (LM Studio, other local LLM/TTS servers) and colliding makes us POST our model
|
|
30
|
+
// to a foreign server that can't serve it → hang. Overridable via env.
|
|
31
|
+
const DEFAULT_PORT = Number(process.env.CLAUDE_CONTROL_MLX_PORT) || 4319;
|
|
32
|
+
// How long a SINGLE request waits for the server to be ready before giving up
|
|
33
|
+
// and letting the caller fall back (to claude -p). The spawned server keeps
|
|
34
|
+
// loading in the background, so the next request finds it warm (~1s). Cold
|
|
35
|
+
// model load can take ~30-90s under launchd, so we never block a request that
|
|
36
|
+
// long — we fail over fast and warm up for next time.
|
|
37
|
+
const REQUEST_READY_MS = Number(process.env.CLAUDE_CONTROL_MLX_TIMEOUT_MS) || 8_000;
|
|
38
|
+
const IDLE_MS = 15 * 60_000; // free ~2GB after 15 min idle
|
|
39
|
+
const MAX_TOKENS = 700;
|
|
40
|
+
|
|
41
|
+
/** @param {number} [port] */
|
|
42
|
+
export function serverBase(port = DEFAULT_PORT) {
|
|
43
|
+
return `http://127.0.0.1:${port}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a python interpreter that can `import mlx_lm`.
|
|
48
|
+
* @returns {string | null}
|
|
49
|
+
*/
|
|
50
|
+
export function resolveMlxPython() {
|
|
51
|
+
const envPy = process.env.CLAUDE_CONTROL_MLX_PYTHON;
|
|
52
|
+
const venvPy = path.join(os.homedir(), '.claude-control', 'mlx-venv', 'bin', 'python');
|
|
53
|
+
for (const p of [envPy, venvPy]) {
|
|
54
|
+
if (p && fs.existsSync(p)) return p;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const p = execFileSync('which', ['python3'], { encoding: 'utf8' }).trim();
|
|
58
|
+
if (p) {
|
|
59
|
+
execFileSync(p, ['-c', 'import mlx_lm'], { stdio: 'ignore' });
|
|
60
|
+
return p;
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
/* no mlx_lm on PATH python */
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── server singleton ────────────────────────────────────────────────────────
|
|
69
|
+
let child = null;
|
|
70
|
+
let childModel = null; // model id the current child was spawned with
|
|
71
|
+
let idleTimer = null;
|
|
72
|
+
|
|
73
|
+
function bumpIdle() {
|
|
74
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
75
|
+
idleTimer = setTimeout(() => shutdown(), IDLE_MS);
|
|
76
|
+
if (idleTimer.unref) idleTimer.unref();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Kill the managed server (no-op if none / external). */
|
|
80
|
+
export function shutdown() {
|
|
81
|
+
if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; }
|
|
82
|
+
if (child) {
|
|
83
|
+
try { child.kill('SIGTERM'); } catch { /* ignore */ }
|
|
84
|
+
child = null;
|
|
85
|
+
}
|
|
86
|
+
childModel = null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function ping(port) {
|
|
90
|
+
try {
|
|
91
|
+
const r = await fetch(serverBase(port) + '/v1/models', { signal: AbortSignal.timeout(1500) });
|
|
92
|
+
return r.ok;
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The model id a server on `port` is currently serving (via /v1/models), or null.
|
|
99
|
+
async function servedModel(port) {
|
|
100
|
+
try {
|
|
101
|
+
const r = await fetch(serverBase(port) + '/v1/models', { signal: AbortSignal.timeout(1500) });
|
|
102
|
+
if (!r.ok) return null;
|
|
103
|
+
const j = await r.json();
|
|
104
|
+
const id = j?.data?.[0]?.id;
|
|
105
|
+
return typeof id === 'string' ? id : null;
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Best-effort: kill whatever process holds `port` (used to reclaim the port from
|
|
112
|
+
// an orphaned mlx server that's serving the wrong model). No-op if lsof/kill fail.
|
|
113
|
+
function freePort(port) {
|
|
114
|
+
try {
|
|
115
|
+
const out = execFileSync('lsof', ['-ti', `tcp:${port}`], { encoding: 'utf8' }).trim();
|
|
116
|
+
for (const pid of out.split('\n').filter(Boolean)) {
|
|
117
|
+
try { process.kill(Number(pid), 'SIGTERM'); } catch { /* already gone */ }
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
/* nothing on the port, or lsof unavailable */
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Is the model already in the local HuggingFace cache (so selecting it won't
|
|
126
|
+
* trigger a multi-GB download)? Checks `~/.cache/huggingface/hub/models--…`.
|
|
127
|
+
* @param {string} id @returns {boolean}
|
|
128
|
+
*/
|
|
129
|
+
export function isModelCached(id) {
|
|
130
|
+
const dir = path.join(
|
|
131
|
+
process.env.HF_HOME || path.join(os.homedir(), '.cache', 'huggingface'),
|
|
132
|
+
'hub',
|
|
133
|
+
`models--${String(id).replace(/\//g, '--')}`,
|
|
134
|
+
);
|
|
135
|
+
try {
|
|
136
|
+
const snaps = path.join(dir, 'snapshots');
|
|
137
|
+
if (!fs.existsSync(snaps)) return false;
|
|
138
|
+
return fs.readdirSync(snaps).some((s) => {
|
|
139
|
+
try {
|
|
140
|
+
return fs.readdirSync(path.join(snaps, s)).length > 0;
|
|
141
|
+
} catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
} catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Spawn the mlx_lm.server child (once). Logs to ~/.claude-control/logs so a
|
|
151
|
+
// failed/slow start is diagnosable. Sets HOME explicitly (launchd may not).
|
|
152
|
+
function spawnServer(model, port) {
|
|
153
|
+
const py = resolveMlxPython();
|
|
154
|
+
if (!py) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
'mlx_lm not installed — create ~/.claude-control/mlx-venv and `pip install mlx-lm`',
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
let out = 'ignore';
|
|
160
|
+
try {
|
|
161
|
+
const logPath = path.join(os.homedir(), '.claude-control', 'logs', 'mlx-server.log');
|
|
162
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
163
|
+
out = fs.openSync(logPath, 'a');
|
|
164
|
+
} catch {
|
|
165
|
+
/* fall back to ignored stdio */
|
|
166
|
+
}
|
|
167
|
+
child = spawn(
|
|
168
|
+
py,
|
|
169
|
+
['-m', 'mlx_lm.server', '--model', model, '--host', '127.0.0.1', '--port', String(port)],
|
|
170
|
+
{ stdio: ['ignore', out, out], env: { ...process.env, HOME: os.homedir() } },
|
|
171
|
+
);
|
|
172
|
+
childModel = model;
|
|
173
|
+
child.on('exit', () => { child = null; childModel = null; });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Ensure a server serving EXACTLY `model` is answering on `port`. Reuses our
|
|
177
|
+
// warm child or any server already serving the right model; otherwise restarts
|
|
178
|
+
// — killing a wrong-model child and reclaiming the port from a wrong-model
|
|
179
|
+
// orphan, so swapping models never POSTs a model the running server lacks (which
|
|
180
|
+
// would trigger an in-request download and hang). Waits only REQUEST_READY_MS;
|
|
181
|
+
// if the (new) model is still loading/downloading, throws so the caller falls
|
|
182
|
+
// back while it finishes in the background.
|
|
183
|
+
async function ensureServer(model, port) {
|
|
184
|
+
if (child && childModel === model && (await ping(port))) return;
|
|
185
|
+
const served = await servedModel(port);
|
|
186
|
+
if (served === model) return; // right model already up (orphan/external) → reuse
|
|
187
|
+
if (child) shutdown(); // our child is serving the wrong model → stop it
|
|
188
|
+
if (served) freePort(port); // an orphan holds the port with the wrong model → reclaim
|
|
189
|
+
spawnServer(model, port);
|
|
190
|
+
const deadline = Date.now() + REQUEST_READY_MS;
|
|
191
|
+
while (Date.now() < deadline) {
|
|
192
|
+
await new Promise((r) => setTimeout(r, 600));
|
|
193
|
+
if ((await servedModel(port)) === model) return;
|
|
194
|
+
}
|
|
195
|
+
throw new Error('mlx server still warming up');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Build the OpenAI chat-completions request body. Pure.
|
|
200
|
+
* @param {string} prompt @param {string} model @param {number} [maxTokens]
|
|
201
|
+
*/
|
|
202
|
+
export function buildChatBody(prompt, model, maxTokens = MAX_TOKENS) {
|
|
203
|
+
return {
|
|
204
|
+
model,
|
|
205
|
+
messages: [{ role: 'user', content: prompt }],
|
|
206
|
+
max_tokens: maxTokens,
|
|
207
|
+
temperature: 0.2,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Extract the assistant text from an OpenAI chat-completions response. Pure.
|
|
213
|
+
* @param {any} json @returns {string}
|
|
214
|
+
*/
|
|
215
|
+
export function parseChatContent(json) {
|
|
216
|
+
const c = json?.choices?.[0]?.message?.content;
|
|
217
|
+
if (typeof c !== 'string' || !c.trim()) throw new Error('empty MLX completion');
|
|
218
|
+
return c;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Best-effort pre-warm: spawn + load the server in the background so the first
|
|
223
|
+
* real request is fast. No-op-safe — swallows the "still warming" throw; the
|
|
224
|
+
* child keeps loading. Call at startup when the MLX backend is selected.
|
|
225
|
+
* @param {number} [port]
|
|
226
|
+
*/
|
|
227
|
+
export function warm(port = DEFAULT_PORT) {
|
|
228
|
+
const model = readConfig().mlxModel || DEFAULT_MODEL;
|
|
229
|
+
ensureServer(model, port).catch(() => {});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Complete a prompt via the local MLX server (spawning + warming it if needed).
|
|
234
|
+
* Throws on any failure so the caller can fall through to the next backend.
|
|
235
|
+
*
|
|
236
|
+
* @param {string} prompt
|
|
237
|
+
* @param {{ model?: string, port?: number, maxTokens?: number }} [opts]
|
|
238
|
+
* @returns {Promise<string>}
|
|
239
|
+
*/
|
|
240
|
+
export async function complete(prompt, { model, port = DEFAULT_PORT, maxTokens = MAX_TOKENS } = {}) {
|
|
241
|
+
const m = model || readConfig().mlxModel || DEFAULT_MODEL;
|
|
242
|
+
await ensureServer(m, port);
|
|
243
|
+
const res = await fetch(serverBase(port) + '/v1/chat/completions', {
|
|
244
|
+
method: 'POST',
|
|
245
|
+
headers: { 'content-type': 'application/json' },
|
|
246
|
+
body: JSON.stringify(buildChatBody(prompt, m, maxTokens)),
|
|
247
|
+
signal: AbortSignal.timeout(60_000),
|
|
248
|
+
});
|
|
249
|
+
if (!res.ok) throw new Error(`MLX server HTTP ${res.status}`);
|
|
250
|
+
const json = await res.json();
|
|
251
|
+
bumpIdle();
|
|
252
|
+
return parseChatContent(json);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Best-effort: don't leave the child server orphaned when the parent exits
|
|
256
|
+
// cleanly. (SIGKILL can't be trapped; an orphan is harmless — ensureServer
|
|
257
|
+
// reuses whatever is already answering on the port.)
|
|
258
|
+
process.on('exit', shutdown);
|
|
259
|
+
process.on('SIGTERM', () => { shutdown(); process.exit(0); });
|
|
260
|
+
process.on('SIGINT', () => { shutdown(); process.exit(0); });
|
package/lib/models.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/models.js — curated model catalogs + machine-aware recommendations.
|
|
3
|
+
*
|
|
4
|
+
* The enhancer's Claude and MLX models are picked from these fixed lists (the
|
|
5
|
+
* UI shows dropdowns, not freeform inputs, to minimise typos / bad ids). MLX
|
|
6
|
+
* picks are sized for Apple-Silicon unified memory (16–48 GB), and the default
|
|
7
|
+
* is chosen automatically from the host's detected RAM.
|
|
8
|
+
*
|
|
9
|
+
* Exports:
|
|
10
|
+
* - MLX_MODELS, CLAUDE_MODELS (catalogs)
|
|
11
|
+
* - detectMachine() → { ramGB, arch, platform, appleSilicon }
|
|
12
|
+
* - recommendMlxModel(ramGB) → id
|
|
13
|
+
* - recommendClaudeModel() → id
|
|
14
|
+
*/
|
|
15
|
+
import os from 'node:os';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Curated MLX instruct models (4-bit, no "thinking" mode → clean JSON for the
|
|
19
|
+
* enhancer). `sizeGB` ≈ on-disk weights; `minRamGB` is the unified-memory tier
|
|
20
|
+
* at/above which the model is a comfortable pick alongside other apps.
|
|
21
|
+
* @type {{ id: string, label: string, sizeGB: number, minRamGB: number }[]}
|
|
22
|
+
*/
|
|
23
|
+
export const MLX_MODELS = [
|
|
24
|
+
{ id: 'mlx-community/Llama-3.2-3B-Instruct-4bit', label: 'Llama 3.2 3B', sizeGB: 1.8, minRamGB: 16 },
|
|
25
|
+
{ id: 'mlx-community/Qwen2.5-3B-Instruct-4bit', label: 'Qwen2.5 3B', sizeGB: 1.8, minRamGB: 16 },
|
|
26
|
+
{ id: 'mlx-community/Qwen2.5-7B-Instruct-4bit', label: 'Qwen2.5 7B', sizeGB: 4.3, minRamGB: 24 },
|
|
27
|
+
{ id: 'mlx-community/Llama-3.1-8B-Instruct-4bit', label: 'Llama 3.1 8B', sizeGB: 4.5, minRamGB: 24 },
|
|
28
|
+
{ id: 'mlx-community/Qwen2.5-14B-Instruct-4bit', label: 'Qwen2.5 14B', sizeGB: 8.5, minRamGB: 32 },
|
|
29
|
+
{ id: 'mlx-community/Qwen2.5-32B-Instruct-4bit', label: 'Qwen2.5 32B', sizeGB: 18, minRamGB: 48 },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Curated Claude models for the `claude -p` enhancer backend/fallback.
|
|
34
|
+
* @type {{ id: string, label: string }[]}
|
|
35
|
+
*/
|
|
36
|
+
export const CLAUDE_MODELS = [
|
|
37
|
+
{ id: 'claude-haiku-4-5', label: 'Haiku 4.5 — fast, cheap' },
|
|
38
|
+
{ id: 'claude-sonnet-4-6', label: 'Sonnet 4.6 — balanced' },
|
|
39
|
+
{ id: 'claude-opus-4-8', label: 'Opus 4.8 — most capable' },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
/** Detect host specs relevant to model selection. */
|
|
43
|
+
export function detectMachine() {
|
|
44
|
+
const ramGB = Math.round(os.totalmem() / 1024 ** 3);
|
|
45
|
+
const arch = os.arch();
|
|
46
|
+
const platform = os.platform();
|
|
47
|
+
return { ramGB, arch, platform, appleSilicon: platform === 'darwin' && arch === 'arm64' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Recommend an MLX model id for a given unified-memory size. Conservative so it
|
|
52
|
+
* stays snappy alongside the user's other apps: 3B (≤23 GB) → 7B (24–47 GB) →
|
|
53
|
+
* 14B (≥48 GB).
|
|
54
|
+
* @param {number} ramGB
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
export function recommendMlxModel(ramGB) {
|
|
58
|
+
if (ramGB >= 48) return 'mlx-community/Qwen2.5-14B-Instruct-4bit';
|
|
59
|
+
if (ramGB >= 24) return 'mlx-community/Qwen2.5-7B-Instruct-4bit';
|
|
60
|
+
return 'mlx-community/Llama-3.2-3B-Instruct-4bit';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The enhancer is a short, cheap task → Haiku is the sensible default. */
|
|
64
|
+
export function recommendClaudeModel() {
|
|
65
|
+
return 'claude-haiku-4-5';
|
|
66
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idl3/claude-control",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
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
|
@@ -25,8 +25,16 @@ import { sweepUploads, resolveUploadPath } from './lib/uploads.js';
|
|
|
25
25
|
import { getVersionInfo, currentVersion } from './lib/version.js';
|
|
26
26
|
import * as push from './lib/push.js';
|
|
27
27
|
import { readConfig, writeConfig } from './lib/config.js';
|
|
28
|
-
import { optimizePrompt } from './lib/optimize.js';
|
|
28
|
+
import { optimizePrompt, rulesOptimize } from './lib/optimize.js';
|
|
29
29
|
import { complete as claudeCliComplete } from './lib/claude-cli.js';
|
|
30
|
+
import * as mlx from './lib/mlx.js';
|
|
31
|
+
import {
|
|
32
|
+
MLX_MODELS,
|
|
33
|
+
CLAUDE_MODELS,
|
|
34
|
+
detectMachine,
|
|
35
|
+
recommendMlxModel,
|
|
36
|
+
recommendClaudeModel,
|
|
37
|
+
} from './lib/models.js';
|
|
30
38
|
import { transcribe } from './lib/transcribe.js';
|
|
31
39
|
import { listSkills } from './lib/skills.js';
|
|
32
40
|
// Note: the client offers [WS_PROTOCOL, token] as subprotocols; the `ws`
|
|
@@ -255,6 +263,19 @@ const server = http.createServer((req, res) => {
|
|
|
255
263
|
if (!checkToken(req)) return endJson(res, 401, { error: 'unauthorized' });
|
|
256
264
|
return handleOptimize(req, res);
|
|
257
265
|
}
|
|
266
|
+
if (u.pathname === '/api/models') {
|
|
267
|
+
if (!checkToken(req)) return endJson(res, 401, { error: 'unauthorized' });
|
|
268
|
+
const machine = detectMachine();
|
|
269
|
+
return endJson(res, 200, {
|
|
270
|
+
machine,
|
|
271
|
+
// Mark which MLX models are already in the local HF cache so the UI can
|
|
272
|
+
// show downloaded vs. will-download (avoids a surprise multi-GB fetch).
|
|
273
|
+
mlxModels: MLX_MODELS.map((m) => ({ ...m, installed: mlx.isModelCached(m.id) })),
|
|
274
|
+
claudeModels: CLAUDE_MODELS,
|
|
275
|
+
recommendedMlxModel: recommendMlxModel(machine.ramGB),
|
|
276
|
+
recommendedClaudeModel: recommendClaudeModel(),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
258
279
|
if (u.pathname === '/api/transcribe') {
|
|
259
280
|
if (req.method !== 'POST') return endJson(res, 405, { error: 'method not allowed' });
|
|
260
281
|
if (!checkToken(req)) return endJson(res, 401, { error: 'unauthorized' });
|
|
@@ -474,6 +495,13 @@ async function handleConfigSave(req, res) {
|
|
|
474
495
|
}
|
|
475
496
|
try {
|
|
476
497
|
const saved = writeConfig(body);
|
|
498
|
+
// If the MLX backend is active, (re)warm the selected model now — this
|
|
499
|
+
// restarts the local server with the new model and starts any needed
|
|
500
|
+
// download in the background, so the user doesn't hit a cold stall (or a
|
|
501
|
+
// wrong-model hang) on their next ✨ enhance.
|
|
502
|
+
if (saved.optimizeBackend === 'mlx' && mlx.resolveMlxPython()) {
|
|
503
|
+
mlx.warm();
|
|
504
|
+
}
|
|
477
505
|
return endJson(res, 200, saved);
|
|
478
506
|
} catch (err) {
|
|
479
507
|
return endJson(res, 400, { error: String(err?.message || err) });
|
|
@@ -495,13 +523,37 @@ async function handleOptimize(req, res) {
|
|
|
495
523
|
if (text.length > 8000) return endJson(res, 400, { error: 'text exceeds 8000 character limit' });
|
|
496
524
|
const intent = typeof body.intent === 'string' ? body.intent : undefined;
|
|
497
525
|
try {
|
|
498
|
-
const result = await
|
|
526
|
+
const result = await runOptimize(text, intent);
|
|
499
527
|
return endJson(res, 200, result);
|
|
500
528
|
} catch (err) {
|
|
501
529
|
return endJson(res, 500, { error: String(err?.message || err) });
|
|
502
530
|
}
|
|
503
531
|
}
|
|
504
532
|
|
|
533
|
+
// Run the enhancer through the configured backend chain, recording WHICH backend
|
|
534
|
+
// actually produced the result so the UI can label it accurately:
|
|
535
|
+
// - 'mlx' → try local MLX, then claude -p, then rules.
|
|
536
|
+
// - 'claude' → try claude -p, then rules.
|
|
537
|
+
// - 'rules' → deterministic rules optimiser only.
|
|
538
|
+
// optimizePrompt returns mode:'rules' when its injected complete() fails, so a
|
|
539
|
+
// non-'llm' mode means that backend fell through → try the next.
|
|
540
|
+
async function runOptimize(text, intent) {
|
|
541
|
+
const cfg = readConfig();
|
|
542
|
+
const backend = cfg.optimizeBackend;
|
|
543
|
+
if (backend === 'rules') {
|
|
544
|
+
return { ...rulesOptimize(text), backend: 'rules' };
|
|
545
|
+
}
|
|
546
|
+
const order = backend === 'claude' ? ['claude'] : ['mlx', 'claude'];
|
|
547
|
+
for (const b of order) {
|
|
548
|
+
const complete = b === 'mlx' ? (p) => mlx.complete(p) : claudeCliComplete;
|
|
549
|
+
const r = await optimizePrompt(text, { complete, intent });
|
|
550
|
+
if (r.mode === 'llm') {
|
|
551
|
+
return { ...r, backend: b, model: b === 'mlx' ? cfg.mlxModel : cfg.optimizeModel };
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return { ...rulesOptimize(text), backend: 'rules' };
|
|
555
|
+
}
|
|
556
|
+
|
|
505
557
|
// POST /api/transcribe — local speech-to-text. Accepts a raw audio body (the
|
|
506
558
|
// MediaRecorder blob from the voice dialog; ?ext=webm|mp4|wav names the format),
|
|
507
559
|
// caps the size, writes it to a temp file, and runs ffmpeg→whisper.cpp via
|
|
@@ -1454,6 +1506,16 @@ async function main() {
|
|
|
1454
1506
|
} else {
|
|
1455
1507
|
console.log(' (no COCKPIT_TOKEN set — relying on 127.0.0.1 bind. This UI can type into your sessions.)');
|
|
1456
1508
|
}
|
|
1509
|
+
// Pre-warm the local MLX enhancer so the first ✨ enhance is fast (best-effort;
|
|
1510
|
+
// only when that backend is selected and an mlx python is available).
|
|
1511
|
+
try {
|
|
1512
|
+
if (readConfig().optimizeBackend === 'mlx' && mlx.resolveMlxPython()) {
|
|
1513
|
+
mlx.warm();
|
|
1514
|
+
console.log(' (pre-warming local MLX enhancer model…)');
|
|
1515
|
+
}
|
|
1516
|
+
} catch {
|
|
1517
|
+
/* best-effort */
|
|
1518
|
+
}
|
|
1457
1519
|
});
|
|
1458
1520
|
}
|
|
1459
1521
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{g as Ve}from"./index-BeJg6Cs1.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-Bup-kzmD.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};
|