great-cto 2.77.1 → 2.78.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/board/.claude-plugin/plugin.json +1 -1
- package/board/packages/board/lib/alerts.mjs +16 -15
- package/board/packages/board/lib/beads.mjs +12 -3
- package/board/packages/board/lib/config.mjs +5 -1
- package/board/packages/board/lib/log.mjs +44 -0
- package/board/packages/board/lib/projects.mjs +89 -15
- package/board/packages/board/lib/routes.mjs +13 -24
- package/board/packages/board/lib/util.mjs +14 -1
- package/board/packages/board/lib/watchers.mjs +30 -1
- package/board/packages/board/public/assets/fonts/PROVENANCE.md +21 -0
- package/board/packages/board/public/assets/fonts/fonts.css +26 -0
- package/board/packages/board/public/assets/fonts/geist-mono-variable.woff2 +0 -0
- package/board/packages/board/public/assets/fonts/geist-variable.woff2 +0 -0
- package/board/packages/board/public/assets/vendor/PROVENANCE.md +21 -0
- package/board/packages/board/public/assets/vendor/marked.min.js +6 -0
- package/board/packages/board/public/assets/vendor/purify.min.js +3 -0
- package/board/packages/board/public/index.html +19 -7
- package/board/packages/board/public/share.html +7 -3
- package/board/packages/board/server.mjs +15 -8
- package/dist/telemetry.js +3 -3
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "great_cto",
|
|
3
3
|
"id": "great_cto",
|
|
4
4
|
"description": "Engineering process for solo founders and teams up to 50 engineers. Agents do architecture, code review, QA, and security. You make two decisions per feature.",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.78.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Great CTO",
|
|
8
8
|
"url": "https://github.com/avelikiy/great_cto"
|
|
@@ -16,6 +16,7 @@ import { getTasks } from './beads.mjs';
|
|
|
16
16
|
import { readVerdicts } from './verdicts.mjs';
|
|
17
17
|
import { isFailure } from './fleet.mjs';
|
|
18
18
|
import { getShareState, toggleShare } from './share.mjs';
|
|
19
|
+
import { log } from './log.mjs';
|
|
19
20
|
|
|
20
21
|
// ── Email alerts (Resend) ─────────────────────────────────────────────────
|
|
21
22
|
// Dispatch model: read webhooks.json on every fire (idempotent if disabled
|
|
@@ -91,12 +92,12 @@ async function fireEmailAlert(eventName, dedupeKey, payload) {
|
|
|
91
92
|
});
|
|
92
93
|
if (!res.ok) {
|
|
93
94
|
const txt = await res.text().catch(() => '');
|
|
94
|
-
|
|
95
|
+
log.warn(`fireEmailAlert ${eventName}: relay HTTP ${res.status} ${txt.slice(0, 200)}`);
|
|
95
96
|
return;
|
|
96
97
|
}
|
|
97
|
-
|
|
98
|
+
log.info(`fireEmailAlert: sent ${eventName} (${dedupeKey})`);
|
|
98
99
|
} catch (e) {
|
|
99
|
-
|
|
100
|
+
log.warn(`fireEmailAlert ${eventName} failed:`, e.message);
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -119,12 +120,12 @@ async function firePushAlert(eventName, dedupeKey, payload) {
|
|
|
119
120
|
catch (e) {
|
|
120
121
|
// 410 = subscription expired — browser unsubscribed, clean up
|
|
121
122
|
if (e.statusCode === 410) removeSubscription(PUSH_SUBS_FILE, sub.endpoint);
|
|
122
|
-
else
|
|
123
|
+
else log.warn(`firePushAlert send failed for ${sub.endpoint}:`, e.message);
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
fired[pushKey] = new Date().toISOString();
|
|
126
127
|
writeAlertsFired(fired);
|
|
127
|
-
} catch (e) {
|
|
128
|
+
} catch (e) { log.warn('firePushAlert failed:', e.message); }
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
// ── Cron: scan gates / cost / weekly digest ───────────────────────────────
|
|
@@ -172,7 +173,7 @@ function startAlertCron() {
|
|
|
172
173
|
firePushAlert('incident.p0', dedupeKey, p0Payload);
|
|
173
174
|
}
|
|
174
175
|
}
|
|
175
|
-
} catch (e) {
|
|
176
|
+
} catch (e) { log.warn('cron incident.p0 failed:', e.message); }
|
|
176
177
|
}, FIVE_MIN);
|
|
177
178
|
|
|
178
179
|
// gate.blocked: new BLOCKED verdict from security-officer
|
|
@@ -207,7 +208,7 @@ function startAlertCron() {
|
|
|
207
208
|
addNotification('gate.blocked', blockedPayload);
|
|
208
209
|
firePushAlert('gate.blocked', dedupeKey, blockedPayload);
|
|
209
210
|
}
|
|
210
|
-
} catch (e) {
|
|
211
|
+
} catch (e) { log.warn('cron gate.blocked failed:', e.message); }
|
|
211
212
|
}, FIVE_MIN);
|
|
212
213
|
|
|
213
214
|
// gate.stale: gate task open between 2h and 7 days.
|
|
@@ -238,7 +239,7 @@ function startAlertCron() {
|
|
|
238
239
|
firePushAlert('gate.stale', dedupeKey, stalePayload);
|
|
239
240
|
}
|
|
240
241
|
}
|
|
241
|
-
} catch (e) {
|
|
242
|
+
} catch (e) { log.warn('cron gate.stale failed:', e.message); }
|
|
242
243
|
}, FIVE_MIN);
|
|
243
244
|
|
|
244
245
|
// cost.threshold: monthly LLM spend at 80% / 100% of budget
|
|
@@ -277,7 +278,7 @@ function startAlertCron() {
|
|
|
277
278
|
firePushAlert('cost.threshold', dedupeKey, costPayload);
|
|
278
279
|
}
|
|
279
280
|
}
|
|
280
|
-
} catch (e) {
|
|
281
|
+
} catch (e) { log.warn('cron cost.threshold failed:', e.message); }
|
|
281
282
|
}, ONE_HOUR);
|
|
282
283
|
|
|
283
284
|
// digest.weekly: Friday 09:00 UTC ± server tick
|
|
@@ -317,7 +318,7 @@ function startAlertCron() {
|
|
|
317
318
|
addNotification('digest.weekly', weeklyPayload);
|
|
318
319
|
await firePushAlert('digest.weekly', dedupeKey, weeklyPayload);
|
|
319
320
|
}
|
|
320
|
-
} catch (e) {
|
|
321
|
+
} catch (e) { log.warn('cron digest.weekly failed:', e.message); }
|
|
321
322
|
}, FIVE_MIN);
|
|
322
323
|
|
|
323
324
|
// digest.daily: morning summary (Mon–Fri, 08:00 UTC).
|
|
@@ -384,7 +385,7 @@ function startAlertCron() {
|
|
|
384
385
|
addNotification('digest.daily', dailyPayload);
|
|
385
386
|
await firePushAlert('digest.daily', dedupeKey, dailyPayload);
|
|
386
387
|
}
|
|
387
|
-
} catch (e) {
|
|
388
|
+
} catch (e) { log.warn('cron digest.daily failed:', e.message); }
|
|
388
389
|
}, FIVE_MIN);
|
|
389
390
|
|
|
390
391
|
// report.daily: republish share reports every day at 09:00 UTC
|
|
@@ -401,13 +402,13 @@ function startAlertCron() {
|
|
|
401
402
|
if (_reportRepublishDedupeSet.has(dedupeKey)) continue;
|
|
402
403
|
_reportRepublishDedupeSet.add(dedupeKey);
|
|
403
404
|
toggleShare(true, proj.path, true)
|
|
404
|
-
.then(() =>
|
|
405
|
-
.catch(e =>
|
|
405
|
+
.then(() => log.info(`report.daily: republished ${proj.slug}`))
|
|
406
|
+
.catch(e => log.warn(`report.daily: ${proj.slug} failed: ${e.message}`));
|
|
406
407
|
}
|
|
407
|
-
} catch (e) {
|
|
408
|
+
} catch (e) { log.warn('cron report.daily failed:', e.message); }
|
|
408
409
|
}, FIVE_MIN);
|
|
409
410
|
|
|
410
|
-
|
|
411
|
+
log.info('Alert cron started: gate.stale (5min), sla.escalate (5min), connector.health (5min), cost.threshold (1h), digest.daily (Mon–Fri 08:00), digest.weekly (Fri 09:00), report.daily (09:00)');
|
|
411
412
|
}
|
|
412
413
|
|
|
413
414
|
export { readAlertsFired, writeAlertsFired, fireEmailAlert, firePushAlert, startAlertCron };
|
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import { spawnSync } from 'child_process';
|
|
5
5
|
import { bdCache } from './state.mjs';
|
|
6
|
+
import { log } from './log.mjs';
|
|
6
7
|
|
|
7
8
|
// ── Beads data ─────────────────────────────────────────────────────────────────
|
|
8
9
|
// Cache bdList output per cwd for BD_CACHE_TTL_MS. Invalidated when the project's
|
|
@@ -85,19 +86,26 @@ function checkBeadsAvailable(cwd) {
|
|
|
85
86
|
let _bdWriteChain = Promise.resolve();
|
|
86
87
|
function bdWriteSerialised(fn) {
|
|
87
88
|
const next = _bdWriteChain.then(() => fn()).catch((e) => {
|
|
88
|
-
|
|
89
|
+
log.error('[bd-write-serialised]', e?.message || e);
|
|
89
90
|
return null;
|
|
90
91
|
});
|
|
91
92
|
_bdWriteChain = next.then(() => undefined).catch(() => undefined);
|
|
92
93
|
return next;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
// On a transient bd failure (nonzero exit, dolt lock, timeout, throw), keep
|
|
97
|
+
// serving the last-good cached data instead of overwriting it with [] — an
|
|
98
|
+
// empty result is indistinguishable from "no tasks" and would wipe a
|
|
99
|
+
// populated board on every SSE push (great_cto-e2ew). We deliberately do NOT
|
|
100
|
+
// refresh cached.ts on failure, so the next call retries bd immediately
|
|
101
|
+
// rather than being TTL-gated on a failed read.
|
|
102
|
+
function bdList(cwd = process.cwd(), runner = bd) {
|
|
96
103
|
const cached = bdCache.get(cwd);
|
|
97
104
|
if (cached && Date.now() - cached.ts < BD_CACHE_TTL_MS) return cached.data;
|
|
98
105
|
try {
|
|
99
|
-
const result =
|
|
106
|
+
const result = runner(['list', '--json', '--all', '--include-gates'], { cwd });
|
|
100
107
|
if (result.status !== 0) {
|
|
108
|
+
if (cached) return cached.data; // last-good data, cache untouched
|
|
101
109
|
bdCache.set(cwd, { ts: Date.now(), data: [] });
|
|
102
110
|
return [];
|
|
103
111
|
}
|
|
@@ -105,6 +113,7 @@ function bdList(cwd = process.cwd()) {
|
|
|
105
113
|
bdCache.set(cwd, { ts: Date.now(), data });
|
|
106
114
|
return data;
|
|
107
115
|
} catch {
|
|
116
|
+
if (cached) return cached.data; // last-good data, cache untouched
|
|
108
117
|
bdCache.set(cwd, { ts: Date.now(), data: [] });
|
|
109
118
|
return [];
|
|
110
119
|
}
|
|
@@ -21,7 +21,11 @@ const HOST = (() => {
|
|
|
21
21
|
})();
|
|
22
22
|
const GREAT_CTO_DIR = path.join(os.homedir(), '.great_cto');
|
|
23
23
|
const SHARE_STATE_FILE = path.join(GREAT_CTO_DIR, 'board-share.json');
|
|
24
|
-
|
|
24
|
+
// Test seam: honor an explicit override so tests can point the registry at a
|
|
25
|
+
// tmp fixture without touching the real ~/.great_cto/projects.json (same
|
|
26
|
+
// convention as GREAT_CTO_BD_BIN in lib/beads.mjs). Unset in production —
|
|
27
|
+
// zero behavior change at runtime.
|
|
28
|
+
const PROJECTS_FILE = process.env.GREAT_CTO_PROJECTS_FILE || path.join(GREAT_CTO_DIR, 'projects.json');
|
|
25
29
|
const SHARE_ENDPOINT = 'https://greatcto.systems/r/';
|
|
26
30
|
const VAPID_KEYS_FILE = path.join(GREAT_CTO_DIR, 'vapid-keys.json');
|
|
27
31
|
const PUSH_SUBS_FILE = path.join(GREAT_CTO_DIR, 'push-subscriptions.json');
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Zero-dep structured logger for the great_cto board server.
|
|
2
|
+
//
|
|
3
|
+
// Design:
|
|
4
|
+
// - Levels: debug < info < warn < error. Threshold read once from
|
|
5
|
+
// GREAT_CTO_LOG_LEVEL (default 'info'); unrecognized values fall back to 'info'.
|
|
6
|
+
// - Output: single-line `<ISO timestamp> <LEVEL> <message>` per call.
|
|
7
|
+
// - Routing: error → stderr; debug/info/warn → stdout. This matches Unix
|
|
8
|
+
// convention (stdout = normal operation, stderr = attention-worthy) and
|
|
9
|
+
// keeps the board's stdout free of interleaved error noise.
|
|
10
|
+
// - No external deps, ESM only, node: prefix for built-ins.
|
|
11
|
+
|
|
12
|
+
const LEVELS = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
13
|
+
|
|
14
|
+
function currentThreshold() {
|
|
15
|
+
const raw = (process.env.GREAT_CTO_LOG_LEVEL || 'info').toLowerCase();
|
|
16
|
+
return LEVELS[raw] ?? LEVELS.info;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function format(level, args) {
|
|
20
|
+
const ts = new Date().toISOString();
|
|
21
|
+
const msg = args
|
|
22
|
+
.map(a => (typeof a === 'string' ? a : a instanceof Error ? (a.stack || a.message) : JSON.stringify(a)))
|
|
23
|
+
.join(' ');
|
|
24
|
+
return `${ts} ${level.toUpperCase()} ${msg}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function emit(level, args) {
|
|
28
|
+
if (LEVELS[level] < currentThreshold()) return;
|
|
29
|
+
const line = format(level, args);
|
|
30
|
+
if (level === 'error') {
|
|
31
|
+
process.stderr.write(line + '\n');
|
|
32
|
+
} else {
|
|
33
|
+
process.stdout.write(line + '\n');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const log = {
|
|
38
|
+
debug: (...args) => emit('debug', args),
|
|
39
|
+
info: (...args) => emit('info', args),
|
|
40
|
+
warn: (...args) => emit('warn', args),
|
|
41
|
+
error: (...args) => emit('error', args),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default log;
|
|
@@ -4,6 +4,20 @@ import os from 'os';
|
|
|
4
4
|
import { spawnSync } from 'child_process';
|
|
5
5
|
import { planGates } from '../../../scripts/lib/gate-plan.mjs';
|
|
6
6
|
import { GREAT_CTO_DIR, PROJECTS_FILE } from './config.mjs';
|
|
7
|
+
import { isInsideDir } from './util.mjs';
|
|
8
|
+
|
|
9
|
+
// Same HOME-boundary policy /api/projects/register enforces (lib/routes.mjs):
|
|
10
|
+
// a raw absolute/tilde path must resolve inside the operator's home directory,
|
|
11
|
+
// no exceptions. Without this, a request like ?project=/etc or ?project=/tmp
|
|
12
|
+
// makes the board read/write project data (.beads, verdicts/, PROJECT.md)
|
|
13
|
+
// at an arbitrary filesystem location the operator never registered. Returns
|
|
14
|
+
// null (not process.cwd()) so callers can distinguish "not a raw path" from
|
|
15
|
+
// "raw path rejected" and fall back explicitly.
|
|
16
|
+
function resolveRawPathWithinHome(slugOrPath) {
|
|
17
|
+
const raw = slugOrPath.startsWith('~') ? slugOrPath.replace(/^~/, os.homedir()) : slugOrPath;
|
|
18
|
+
const resolved = path.resolve(raw);
|
|
19
|
+
return isInsideDir(os.homedir(), resolved) ? resolved : null;
|
|
20
|
+
}
|
|
7
21
|
|
|
8
22
|
// ── Project registry ───────────────────────────────────────────────────────────
|
|
9
23
|
function readProjectsRegistry() {
|
|
@@ -11,9 +25,44 @@ function readProjectsRegistry() {
|
|
|
11
25
|
catch {}
|
|
12
26
|
return { projects: [] };
|
|
13
27
|
}
|
|
28
|
+
// Pick the best entry among several sharing a slug: prefer one whose path
|
|
29
|
+
// still exists on disk; among several existing (or several missing), prefer
|
|
30
|
+
// the most recently added_at. Never returns a non-existent path when an
|
|
31
|
+
// existing match is available (great_cto-7xfc).
|
|
32
|
+
function pickBestBySlug(candidates) {
|
|
33
|
+
let best = null;
|
|
34
|
+
for (const p of candidates) {
|
|
35
|
+
if (!best) { best = p; continue; }
|
|
36
|
+
const pExists = existsSafe(p.path);
|
|
37
|
+
const bestExists = existsSafe(best.path);
|
|
38
|
+
if (pExists && !bestExists) { best = p; continue; }
|
|
39
|
+
if (!pExists && bestExists) continue;
|
|
40
|
+
// Both exist or both missing — prefer most recent added_at.
|
|
41
|
+
if (String(p.added_at || '') > String(best.added_at || '')) best = p;
|
|
42
|
+
}
|
|
43
|
+
return best;
|
|
44
|
+
}
|
|
45
|
+
function existsSafe(p) {
|
|
46
|
+
try { return !!p && fs.existsSync(p); } catch { return false; }
|
|
47
|
+
}
|
|
48
|
+
// Dedupe registry entries by slug before writing, so the registry self-heals
|
|
49
|
+
// on every write regardless of how the duplicate got in (great_cto-7xfc).
|
|
50
|
+
function dedupeProjects(projects) {
|
|
51
|
+
const bySlug = new Map();
|
|
52
|
+
for (const p of projects) {
|
|
53
|
+
if (!p || !p.slug) continue;
|
|
54
|
+
const group = bySlug.get(p.slug) || [];
|
|
55
|
+
group.push(p);
|
|
56
|
+
bySlug.set(p.slug, group);
|
|
57
|
+
}
|
|
58
|
+
const out = [];
|
|
59
|
+
for (const group of bySlug.values()) out.push(pickBestBySlug(group));
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
14
62
|
function writeProjectsRegistry(reg) {
|
|
15
63
|
fs.mkdirSync(GREAT_CTO_DIR, { recursive: true });
|
|
16
|
-
|
|
64
|
+
const deduped = { ...reg, projects: dedupeProjects(reg.projects || []) };
|
|
65
|
+
fs.writeFileSync(PROJECTS_FILE, JSON.stringify(deduped, null, 2));
|
|
17
66
|
}
|
|
18
67
|
// Map legacy / informal archetype names to canonical archetype slugs.
|
|
19
68
|
// This lets the board correctly badge old PROJECT.md files (mobile, saas-platform,
|
|
@@ -99,10 +148,20 @@ function autoRegisterProject(dir) {
|
|
|
99
148
|
const meta = readProjectMd(dir);
|
|
100
149
|
if (!meta) return null;
|
|
101
150
|
const reg = readProjectsRegistry();
|
|
102
|
-
|
|
151
|
+
const existingByPath = reg.projects.find(p => p.path === meta.path);
|
|
152
|
+
if (existingByPath) return meta; // already registered at this path, nothing to do
|
|
153
|
+
const existingBySlug = reg.projects.find(p => p.slug === meta.slug);
|
|
154
|
+
if (existingBySlug) {
|
|
155
|
+
// Repo moved: update the existing entry's path in place instead of
|
|
156
|
+
// inserting a second entry for the same slug (great_cto-7xfc).
|
|
157
|
+
existingBySlug.path = meta.path;
|
|
158
|
+
existingBySlug.archetype = meta.archetype;
|
|
159
|
+
existingBySlug.description = meta.description;
|
|
160
|
+
existingBySlug.added_at = meta.added_at;
|
|
161
|
+
} else {
|
|
103
162
|
reg.projects.push(meta);
|
|
104
|
-
writeProjectsRegistry(reg);
|
|
105
163
|
}
|
|
164
|
+
writeProjectsRegistry(reg);
|
|
106
165
|
return meta;
|
|
107
166
|
}
|
|
108
167
|
|
|
@@ -206,12 +265,22 @@ function listProjects() {
|
|
|
206
265
|
}
|
|
207
266
|
function resolveProjectCwd(slugOrPath) {
|
|
208
267
|
if (!slugOrPath) return process.cwd();
|
|
209
|
-
// If absolute path or starts with ~,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
//
|
|
268
|
+
// If absolute path or starts with ~, it's raw user input — enforce the same
|
|
269
|
+
// HOME-boundary policy /api/projects/register uses. A path outside HOME
|
|
270
|
+
// falls back to process.cwd() (same fallback already used below for an
|
|
271
|
+
// unknown slug) rather than being honored verbatim.
|
|
272
|
+
if (slugOrPath.startsWith('/') || slugOrPath.startsWith('~')) {
|
|
273
|
+
const withinHome = resolveRawPathWithinHome(slugOrPath);
|
|
274
|
+
return withinHome || process.cwd();
|
|
275
|
+
}
|
|
276
|
+
// Else look up in registry by slug — among duplicate slugs (great_cto-7xfc),
|
|
277
|
+
// prefer the entry whose path exists on disk; among several existing (or
|
|
278
|
+
// several missing), prefer the most recently added_at. Registry entries were
|
|
279
|
+
// already validated at registration time (register enforces the HOME
|
|
280
|
+
// boundary before writing), so slug resolution is untouched.
|
|
213
281
|
const reg = readProjectsRegistry();
|
|
214
|
-
const
|
|
282
|
+
const matches = reg.projects.filter(p => p.slug === slugOrPath);
|
|
283
|
+
const found = pickBestBySlug(matches);
|
|
215
284
|
return found ? found.path : process.cwd();
|
|
216
285
|
}
|
|
217
286
|
|
|
@@ -223,19 +292,22 @@ function resolveProjectCwd(slugOrPath) {
|
|
|
223
292
|
*
|
|
224
293
|
* resolved values:
|
|
225
294
|
* 'cwd' — no project param passed; using server cwd as documented
|
|
226
|
-
* 'path' — absolute / tilde path passed
|
|
295
|
+
* 'path' — absolute / tilde path passed, resolved inside HOME, used directly
|
|
227
296
|
* 'slug' — slug found in registry
|
|
228
|
-
* 'fallback' — slug requested but NOT in registry
|
|
229
|
-
*
|
|
297
|
+
* 'fallback' — slug requested but NOT in registry, OR a raw path was requested
|
|
298
|
+
* that resolves outside HOME (great_cto-qvg9); using cwd as
|
|
299
|
+
* fallback. Caller should warn the user (header + log).
|
|
230
300
|
*/
|
|
231
301
|
function resolveProjectInfo(slugOrPath) {
|
|
232
302
|
if (!slugOrPath) return { cwd: process.cwd(), resolved: 'cwd' };
|
|
233
|
-
if (slugOrPath.startsWith('/')
|
|
234
|
-
|
|
235
|
-
return { cwd:
|
|
303
|
+
if (slugOrPath.startsWith('/') || slugOrPath.startsWith('~')) {
|
|
304
|
+
const withinHome = resolveRawPathWithinHome(slugOrPath);
|
|
305
|
+
if (withinHome) return { cwd: withinHome, resolved: 'path' };
|
|
306
|
+
return { cwd: process.cwd(), resolved: 'fallback', requested: slugOrPath };
|
|
236
307
|
}
|
|
237
308
|
const reg = readProjectsRegistry();
|
|
238
|
-
const
|
|
309
|
+
const matches = reg.projects.filter(p => p.slug === slugOrPath);
|
|
310
|
+
const found = pickBestBySlug(matches);
|
|
239
311
|
if (found) return { cwd: found.path, resolved: 'slug' };
|
|
240
312
|
return { cwd: process.cwd(), resolved: 'fallback', requested: slugOrPath };
|
|
241
313
|
}
|
|
@@ -243,6 +315,8 @@ function resolveProjectInfo(slugOrPath) {
|
|
|
243
315
|
export {
|
|
244
316
|
readProjectsRegistry,
|
|
245
317
|
writeProjectsRegistry,
|
|
318
|
+
pickBestBySlug,
|
|
319
|
+
dedupeProjects,
|
|
246
320
|
ARCHETYPE_ALIASES,
|
|
247
321
|
normalizeArchetype,
|
|
248
322
|
extractArchetype,
|
|
@@ -6,13 +6,14 @@ import {
|
|
|
6
6
|
addSubscription,
|
|
7
7
|
removeSubscription,
|
|
8
8
|
} from '../push-adapter.mjs';
|
|
9
|
-
import {
|
|
10
|
-
import { eventSurface, readFileSafe } from './util.mjs';
|
|
9
|
+
import { GREAT_CTO_DIR, VAPID_KEYS_FILE, PUSH_SUBS_FILE, BUILD_VERSION } from './config.mjs';
|
|
10
|
+
import { eventSurface, readFileSafe, originAllowed } from './util.mjs';
|
|
11
11
|
import { sseClients, notifHistory } from './state.mjs';
|
|
12
12
|
import { autoRegisterProject, listProjects, resolveProjectCwd, getChangeTier } from './projects.mjs';
|
|
13
13
|
import { broadcastTasks } from './sse.mjs';
|
|
14
14
|
import { saveNotifHistory } from './notifications.mjs';
|
|
15
15
|
import { getMemory, getPipeline, getCostHistory, getInbox } from './data-readers.mjs';
|
|
16
|
+
import { log } from './log.mjs';
|
|
16
17
|
import { bdCacheInvalidate, checkBeadsAvailable, bdWriteSerialised, bd, bdErr, getTasks } from './beads.mjs';
|
|
17
18
|
import { getMetrics } from './metrics.mjs';
|
|
18
19
|
import { readVerdicts } from './verdicts.mjs';
|
|
@@ -54,18 +55,12 @@ async function dispatch(req, res, url, cwd) {
|
|
|
54
55
|
// it MUST reject cross-origin requests. The board listens on 127.0.0.1
|
|
55
56
|
// but a malicious page the user visits can still issue text/plain POSTs
|
|
56
57
|
// (simple CORS request — no preflight) to localhost. Two gates:
|
|
57
|
-
// 1) Origin / Referer must
|
|
58
|
+
// 1) Origin / Referer must be same-origin (originAllowed() in lib/util.mjs —
|
|
59
|
+
// also enforced as the top-level CSRF guard in server.mjs before dispatch()
|
|
60
|
+
// is ever called, so this is defense-in-depth, not the only check).
|
|
58
61
|
// 2) Resolved target path must live inside HOME — no /tmp, no /etc.
|
|
59
62
|
if (pathname === '/api/projects/register' && req.method === 'POST') {
|
|
60
|
-
|
|
61
|
-
const expectedOrigin = `http://localhost:${PORT}`;
|
|
62
|
-
const expectedOrigin2 = `http://127.0.0.1:${PORT}`;
|
|
63
|
-
const originOk = !origin
|
|
64
|
-
|| origin === expectedOrigin
|
|
65
|
-
|| origin === expectedOrigin2
|
|
66
|
-
|| origin.startsWith(expectedOrigin + '/')
|
|
67
|
-
|| origin.startsWith(expectedOrigin2 + '/');
|
|
68
|
-
if (!originOk) {
|
|
63
|
+
if (!originAllowed(req)) {
|
|
69
64
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
70
65
|
res.end(JSON.stringify({ error: 'origin not allowed' }));
|
|
71
66
|
return true;
|
|
@@ -384,8 +379,8 @@ async function dispatch(req, res, url, cwd) {
|
|
|
384
379
|
const shareState = getShareState(gateCwd);
|
|
385
380
|
if (shareState.enabled) {
|
|
386
381
|
toggleShare(true, gateCwd, true)
|
|
387
|
-
.then(() =>
|
|
388
|
-
.catch(e =>
|
|
382
|
+
.then(() => log.info(`report: auto-republished after gate approve (${id})`))
|
|
383
|
+
.catch(e => log.warn(`report: republish after gate failed: ${e.message}`));
|
|
389
384
|
}
|
|
390
385
|
}
|
|
391
386
|
});
|
|
@@ -850,16 +845,10 @@ async function dispatch(req, res, url, cwd) {
|
|
|
850
845
|
// (DESIGN-agents-fleet-view §9 Top-2 #2: reversible sidecar chosen over
|
|
851
846
|
// filesystem move; founder may revise.)
|
|
852
847
|
if (pathname.startsWith('/api/agents/') && req.method === 'POST') {
|
|
853
|
-
// Cross-origin guard — same
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
const originOk = !origin
|
|
858
|
-
|| origin === expectedOrigin
|
|
859
|
-
|| origin === expectedOrigin2
|
|
860
|
-
|| origin.startsWith(expectedOrigin + '/')
|
|
861
|
-
|| origin.startsWith(expectedOrigin2 + '/');
|
|
862
|
-
if (!originOk) {
|
|
848
|
+
// Cross-origin guard — same helper as BH-23 on /api/projects/register
|
|
849
|
+
// (originAllowed() in lib/util.mjs; also enforced as the top-level CSRF
|
|
850
|
+
// guard in server.mjs before dispatch() is ever called).
|
|
851
|
+
if (!originAllowed(req)) {
|
|
863
852
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
864
853
|
res.end(JSON.stringify({ error: 'origin_not_allowed' }));
|
|
865
854
|
return true;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
2
3
|
import { PORT } from './config.mjs';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -33,4 +34,16 @@ function readFileSafe(p) {
|
|
|
33
34
|
try { return fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : null; } catch { return null; }
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
// Path-containment check used by any handler that joins user-controlled input
|
|
38
|
+
// onto a base directory (static file serving, doc reads, etc.). Resolves both
|
|
39
|
+
// sides and requires `target` to be exactly `base`, or a real descendant of
|
|
40
|
+
// it (base + path.sep prefix) — this is what actually blocks ".." escapes,
|
|
41
|
+
// since a naive startsWith(base) would wrongly allow a sibling directory
|
|
42
|
+
// like "/public-evil" when base is "/public".
|
|
43
|
+
function isInsideDir(base, target) {
|
|
44
|
+
const resolvedBase = path.resolve(base);
|
|
45
|
+
const resolvedTarget = path.resolve(target);
|
|
46
|
+
return resolvedTarget === resolvedBase || resolvedTarget.startsWith(resolvedBase + path.sep);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { csvCell, originAllowed, eventSurface, readFileSafe, isInsideDir };
|
|
@@ -40,17 +40,46 @@ function watchBeads() {
|
|
|
40
40
|
}, 200));
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
// Tracks dirs whose interactions.jsonl file watch is already registered,
|
|
44
|
+
// so the late-registration path (b) below doesn't double-watch a file
|
|
45
|
+
// that was already picked up at startup by (a).
|
|
46
|
+
const interactionsWatched = new Set();
|
|
47
|
+
|
|
48
|
+
const watchInteractionsFile = (dir) => {
|
|
49
|
+
if (interactionsWatched.has(dir)) return;
|
|
50
|
+
const interactionsFile = path.join(dir, '.beads', 'interactions.jsonl');
|
|
51
|
+
try {
|
|
52
|
+
fs.watch(interactionsFile, () => schedule(dir));
|
|
53
|
+
interactionsWatched.add(dir);
|
|
54
|
+
} catch {}
|
|
55
|
+
};
|
|
56
|
+
|
|
43
57
|
for (const dir of dirs) {
|
|
44
58
|
// (a) interactions.jsonl — captures bd update/close
|
|
45
59
|
const interactionsFile = path.join(dir, '.beads', 'interactions.jsonl');
|
|
46
60
|
if (fs.existsSync(interactionsFile)) {
|
|
47
|
-
|
|
61
|
+
watchInteractionsFile(dir);
|
|
48
62
|
}
|
|
49
63
|
// (b) dolt embeddeddolt directory (recursive) — captures bd create
|
|
50
64
|
const doltDir = path.join(dir, '.beads', 'embeddeddolt');
|
|
51
65
|
if (fs.existsSync(doltDir)) {
|
|
52
66
|
try { fs.watch(doltDir, { recursive: true }, () => schedule(dir)); } catch {}
|
|
53
67
|
}
|
|
68
|
+
// (c) .beads dir itself — interactions.jsonl is only registered above if
|
|
69
|
+
// it already exists at startup; if bd creates it later (first `bd
|
|
70
|
+
// update`/`close` on a project with no prior interaction log), that
|
|
71
|
+
// event was previously missed entirely (great_cto-lvai). Watch the
|
|
72
|
+
// .beads dir (non-recursive) and register the file watch as soon as it
|
|
73
|
+
// appears, guarded against double-registration via interactionsWatched.
|
|
74
|
+
const beadsDir = path.join(dir, '.beads');
|
|
75
|
+
if (fs.existsSync(beadsDir)) {
|
|
76
|
+
try {
|
|
77
|
+
fs.watch(beadsDir, (eventType, filename) => {
|
|
78
|
+
if (filename === 'interactions.jsonl') watchInteractionsFile(dir);
|
|
79
|
+
schedule(dir);
|
|
80
|
+
});
|
|
81
|
+
} catch {}
|
|
82
|
+
}
|
|
54
83
|
}
|
|
55
84
|
}
|
|
56
85
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Vendored fonts
|
|
2
|
+
|
|
3
|
+
great_cto ships zero telemetry (see docs/PRIVACY.md). The board previously
|
|
4
|
+
loaded Geist / Geist Mono from fonts.googleapis.com + fonts.gstatic.com on
|
|
5
|
+
every open — an IP leak and an offline-breaker. Only the two families the
|
|
6
|
+
CSS actually uses (`--sans`/`--serif` → Geist, `--mono` → Geist Mono; Inter
|
|
7
|
+
and JetBrains Mono in the stack were unused fallback names, not vendored)
|
|
8
|
+
are fetched here and served locally.
|
|
9
|
+
|
|
10
|
+
| File | Family | Source | Subset |
|
|
11
|
+
|------|--------|--------|--------|
|
|
12
|
+
| `geist-variable.woff2` | Geist (variable, wght 400-700) | https://fonts.gstatic.com/s/geist/v5/gyByhwUxId8gMEwcGFWNOITd.woff2 | latin |
|
|
13
|
+
| `geist-mono-variable.woff2` | Geist Mono (variable, wght 400-500) | https://fonts.gstatic.com/s/geistmono/v6/or3nQ6H-1_WfwkMZI_qYFrcdmhHkjko.woff2 | latin |
|
|
14
|
+
|
|
15
|
+
Fetched via the standard `fonts.googleapis.com/css2` request (same URLs
|
|
16
|
+
Google Fonts already serves to browsers); only the `latin` subset was kept
|
|
17
|
+
since the UI is English-only — cyrillic/vietnamese/symbols subsets were
|
|
18
|
+
dropped. Total added weight: ~56 KB. If a font file is ever unreachable at
|
|
19
|
+
build/vendor time, `index.html`'s font stack already falls back to
|
|
20
|
+
`-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif` / system-mono
|
|
21
|
+
so the UI still renders — see the `--sans`/`--mono` custom properties.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* Local Geist / Geist Mono @font-face rules (great_cto-kge6).
|
|
2
|
+
* Vendored to replace the fonts.googleapis.com / fonts.gstatic.com CDN
|
|
3
|
+
* request the board used to make on every open — see PROVENANCE.md.
|
|
4
|
+
* Each family ships as a single variable-font woff2 (weight axis 400-700+),
|
|
5
|
+
* so one @font-face declaration with a wide font-weight range covers every
|
|
6
|
+
* weight the page uses (400/500/600/700), matching what the CDN's per-weight
|
|
7
|
+
* CSS actually served (identical bytes for each weight in the latin subset).
|
|
8
|
+
* Only the latin subset is vendored — the UI is English-only; falls back to
|
|
9
|
+
* the system stack (see --sans/--mono in index.html) for anything else.
|
|
10
|
+
*/
|
|
11
|
+
@font-face {
|
|
12
|
+
font-family: 'Geist';
|
|
13
|
+
font-style: normal;
|
|
14
|
+
font-weight: 400 700;
|
|
15
|
+
font-display: swap;
|
|
16
|
+
src: url('geist-variable.woff2') format('woff2');
|
|
17
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
18
|
+
}
|
|
19
|
+
@font-face {
|
|
20
|
+
font-family: 'Geist Mono';
|
|
21
|
+
font-style: normal;
|
|
22
|
+
font-weight: 400 500;
|
|
23
|
+
font-display: swap;
|
|
24
|
+
src: url('geist-mono-variable.woff2') format('woff2');
|
|
25
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
26
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Vendored third-party assets
|
|
2
|
+
|
|
3
|
+
great_cto ships zero telemetry and phones home to nothing (see docs/PRIVACY.md).
|
|
4
|
+
These files were previously loaded from external CDNs on every board open
|
|
5
|
+
(fonts.googleapis.com, cdn.jsdelivr.net) — an IP leak and an offline-breaker.
|
|
6
|
+
They're now vendored as static assets and served locally instead.
|
|
7
|
+
|
|
8
|
+
## JS libraries
|
|
9
|
+
|
|
10
|
+
| File | Package | Version | Source | SHA-384 (matches original SRI hash) |
|
|
11
|
+
|------|---------|---------|--------|--------------------------------------|
|
|
12
|
+
| `marked.min.js` | [marked](https://www.npmjs.com/package/marked) | 14.1.3 | https://cdn.jsdelivr.net/npm/marked@14.1.3/marked.min.js | `k8o8HikHweyzW55Wd3wl18ovJj6vHVYNQeQbeSM0fxx+0WiH4TcccOG9uz8Xd2JR` |
|
|
13
|
+
| `purify.min.js` | [dompurify](https://www.npmjs.com/package/dompurify) | 3.1.7 | https://cdn.jsdelivr.net/npm/dompurify@3.1.7/dist/purify.min.js | `XQqX/4yiUGu+oyr87jvWzRuqBUK/adrY0DunhL+tID9m/9dwSpV8h9Fk/Sg6ifVQ` |
|
|
14
|
+
|
|
15
|
+
Downloaded verbatim; hashes verified to match the SRI `integrity` attributes
|
|
16
|
+
the board previously used on the CDN `<script>` tags. These are static
|
|
17
|
+
vendored assets, not npm runtime dependencies — no package.json entry.
|
|
18
|
+
|
|
19
|
+
## Fonts
|
|
20
|
+
|
|
21
|
+
See `../fonts/PROVENANCE.md` for the vendored Geist / Geist Mono woff2 files.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* marked v14.1.3 - a markdown parser
|
|
3
|
+
* Copyright (c) 2011-2024, Christopher Jeffrey. (MIT Licensed)
|
|
4
|
+
* https://github.com/markedjs/marked
|
|
5
|
+
*/
|
|
6
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).marked={})}(this,(function(e){"use strict";function t(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}function n(t){e.defaults=t}e.defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};const s=/[&<>"']/,r=new RegExp(s.source,"g"),i=/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,l=new RegExp(i.source,"g"),o={"&":"&","<":"<",">":">",'"':""","'":"'"},a=e=>o[e];function c(e,t){if(t){if(s.test(e))return e.replace(r,a)}else if(i.test(e))return e.replace(l,a);return e}const h=/(^|[^\[])\^/g;function p(e,t){let n="string"==typeof e?e:e.source;t=t||"";const s={replace:(e,t)=>{let r="string"==typeof t?t:t.source;return r=r.replace(h,"$1"),n=n.replace(e,r),s},getRegex:()=>new RegExp(n,t)};return s}function u(e){try{e=encodeURI(e).replace(/%25/g,"%")}catch{return null}return e}const k={exec:()=>null};function g(e,t){const n=e.replace(/\|/g,((e,t,n)=>{let s=!1,r=t;for(;--r>=0&&"\\"===n[r];)s=!s;return s?"|":" |"})).split(/ \|/);let s=0;if(n[0].trim()||n.shift(),n.length>0&&!n[n.length-1].trim()&&n.pop(),t)if(n.length>t)n.splice(t);else for(;n.length<t;)n.push("");for(;s<n.length;s++)n[s]=n[s].trim().replace(/\\\|/g,"|");return n}function f(e,t,n){const s=e.length;if(0===s)return"";let r=0;for(;r<s;){const i=e.charAt(s-r-1);if(i!==t||n){if(i===t||!n)break;r++}else r++}return e.slice(0,s-r)}function d(e,t,n,s){const r=t.href,i=t.title?c(t.title):null,l=e[1].replace(/\\([\[\]])/g,"$1");if("!"!==e[0].charAt(0)){s.state.inLink=!0;const e={type:"link",raw:n,href:r,title:i,text:l,tokens:s.inlineTokens(l)};return s.state.inLink=!1,e}return{type:"image",raw:n,href:r,title:i,text:c(l)}}class x{options;rules;lexer;constructor(t){this.options=t||e.defaults}space(e){const t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){const t=this.rules.block.code.exec(e);if(t){const e=t[0].replace(/^(?: {1,4}| {0,3}\t)/gm,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?e:f(e,"\n")}}}fences(e){const t=this.rules.block.fences.exec(e);if(t){const e=t[0],n=function(e,t){const n=e.match(/^(\s+)(?:```)/);if(null===n)return t;const s=n[1];return t.split("\n").map((e=>{const t=e.match(/^\s+/);if(null===t)return e;const[n]=t;return n.length>=s.length?e.slice(s.length):e})).join("\n")}(e,t[3]||"");return{type:"code",raw:e,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:n}}}heading(e){const t=this.rules.block.heading.exec(e);if(t){let e=t[2].trim();if(/#$/.test(e)){const t=f(e,"#");this.options.pedantic?e=t.trim():t&&!/ $/.test(t)||(e=t.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:e,tokens:this.lexer.inline(e)}}}hr(e){const t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:f(t[0],"\n")}}blockquote(e){const t=this.rules.block.blockquote.exec(e);if(t){let e=f(t[0],"\n").split("\n"),n="",s="";const r=[];for(;e.length>0;){let t=!1;const i=[];let l;for(l=0;l<e.length;l++)if(/^ {0,3}>/.test(e[l]))i.push(e[l]),t=!0;else{if(t)break;i.push(e[l])}e=e.slice(l);const o=i.join("\n"),a=o.replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,"\n $1").replace(/^ {0,3}>[ \t]?/gm,"");n=n?`${n}\n${o}`:o,s=s?`${s}\n${a}`:a;const c=this.lexer.state.top;if(this.lexer.state.top=!0,this.lexer.blockTokens(a,r,!0),this.lexer.state.top=c,0===e.length)break;const h=r[r.length-1];if("code"===h?.type)break;if("blockquote"===h?.type){const t=h,i=t.raw+"\n"+e.join("\n"),l=this.blockquote(i);r[r.length-1]=l,n=n.substring(0,n.length-t.raw.length)+l.raw,s=s.substring(0,s.length-t.text.length)+l.text;break}if("list"!==h?.type);else{const t=h,i=t.raw+"\n"+e.join("\n"),l=this.list(i);r[r.length-1]=l,n=n.substring(0,n.length-h.raw.length)+l.raw,s=s.substring(0,s.length-t.raw.length)+l.raw,e=i.substring(r[r.length-1].raw.length).split("\n")}}return{type:"blockquote",raw:n,tokens:r,text:s}}}list(e){let t=this.rules.block.list.exec(e);if(t){let n=t[1].trim();const s=n.length>1,r={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");const i=new RegExp(`^( {0,3}${n})((?:[\t ][^\\n]*)?(?:\\n|$))`);let l=!1;for(;e;){let n=!1,s="",o="";if(!(t=i.exec(e)))break;if(this.rules.block.hr.test(e))break;s=t[0],e=e.substring(s.length);let a=t[2].split("\n",1)[0].replace(/^\t+/,(e=>" ".repeat(3*e.length))),c=e.split("\n",1)[0],h=!a.trim(),p=0;if(this.options.pedantic?(p=2,o=a.trimStart()):h?p=t[1].length+1:(p=t[2].search(/[^ ]/),p=p>4?1:p,o=a.slice(p),p+=t[1].length),h&&/^[ \t]*$/.test(c)&&(s+=c+"\n",e=e.substring(c.length+1),n=!0),!n){const t=new RegExp(`^ {0,${Math.min(3,p-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`),n=new RegExp(`^ {0,${Math.min(3,p-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),r=new RegExp(`^ {0,${Math.min(3,p-1)}}(?:\`\`\`|~~~)`),i=new RegExp(`^ {0,${Math.min(3,p-1)}}#`),l=new RegExp(`^ {0,${Math.min(3,p-1)}}<[a-z].*>`,"i");for(;e;){const u=e.split("\n",1)[0];let k;if(c=u,this.options.pedantic?(c=c.replace(/^ {1,4}(?=( {4})*[^ ])/g," "),k=c):k=c.replace(/\t/g," "),r.test(c))break;if(i.test(c))break;if(l.test(c))break;if(t.test(c))break;if(n.test(c))break;if(k.search(/[^ ]/)>=p||!c.trim())o+="\n"+k.slice(p);else{if(h)break;if(a.replace(/\t/g," ").search(/[^ ]/)>=4)break;if(r.test(a))break;if(i.test(a))break;if(n.test(a))break;o+="\n"+c}h||c.trim()||(h=!0),s+=u+"\n",e=e.substring(u.length+1),a=k.slice(p)}}r.loose||(l?r.loose=!0:/\n[ \t]*\n[ \t]*$/.test(s)&&(l=!0));let u,k=null;this.options.gfm&&(k=/^\[[ xX]\] /.exec(o),k&&(u="[ ] "!==k[0],o=o.replace(/^\[[ xX]\] +/,""))),r.items.push({type:"list_item",raw:s,task:!!k,checked:u,loose:!1,text:o,tokens:[]}),r.raw+=s}r.items[r.items.length-1].raw=r.items[r.items.length-1].raw.trimEnd(),r.items[r.items.length-1].text=r.items[r.items.length-1].text.trimEnd(),r.raw=r.raw.trimEnd();for(let e=0;e<r.items.length;e++)if(this.lexer.state.top=!1,r.items[e].tokens=this.lexer.blockTokens(r.items[e].text,[]),!r.loose){const t=r.items[e].tokens.filter((e=>"space"===e.type)),n=t.length>0&&t.some((e=>/\n.*\n/.test(e.raw)));r.loose=n}if(r.loose)for(let e=0;e<r.items.length;e++)r.items[e].loose=!0;return r}}html(e){const t=this.rules.block.html.exec(e);if(t){return{type:"html",block:!0,raw:t[0],pre:"pre"===t[1]||"script"===t[1]||"style"===t[1],text:t[0]}}}def(e){const t=this.rules.block.def.exec(e);if(t){const e=t[1].toLowerCase().replace(/\s+/g," "),n=t[2]?t[2].replace(/^<(.*)>$/,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",s=t[3]?t[3].substring(1,t[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):t[3];return{type:"def",tag:e,raw:t[0],href:n,title:s}}}table(e){const t=this.rules.block.table.exec(e);if(!t)return;if(!/[:|]/.test(t[2]))return;const n=g(t[1]),s=t[2].replace(/^\||\| *$/g,"").split("|"),r=t[3]&&t[3].trim()?t[3].replace(/\n[ \t]*$/,"").split("\n"):[],i={type:"table",raw:t[0],header:[],align:[],rows:[]};if(n.length===s.length){for(const e of s)/^ *-+: *$/.test(e)?i.align.push("right"):/^ *:-+: *$/.test(e)?i.align.push("center"):/^ *:-+ *$/.test(e)?i.align.push("left"):i.align.push(null);for(let e=0;e<n.length;e++)i.header.push({text:n[e],tokens:this.lexer.inline(n[e]),header:!0,align:i.align[e]});for(const e of r)i.rows.push(g(e,i.header.length).map(((e,t)=>({text:e,tokens:this.lexer.inline(e),header:!1,align:i.align[t]}))));return i}}lheading(e){const t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:"="===t[2].charAt(0)?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){const t=this.rules.block.paragraph.exec(e);if(t){const e="\n"===t[1].charAt(t[1].length-1)?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:e,tokens:this.lexer.inline(e)}}}text(e){const t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){const t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:c(t[1])}}tag(e){const t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&/^<a /i.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&/^<\/a>/i.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){const t=this.rules.inline.link.exec(e);if(t){const e=t[2].trim();if(!this.options.pedantic&&/^</.test(e)){if(!/>$/.test(e))return;const t=f(e.slice(0,-1),"\\");if((e.length-t.length)%2==0)return}else{const e=function(e,t){if(-1===e.indexOf(t[1]))return-1;let n=0;for(let s=0;s<e.length;s++)if("\\"===e[s])s++;else if(e[s]===t[0])n++;else if(e[s]===t[1]&&(n--,n<0))return s;return-1}(t[2],"()");if(e>-1){const n=(0===t[0].indexOf("!")?5:4)+t[1].length+e;t[2]=t[2].substring(0,e),t[0]=t[0].substring(0,n).trim(),t[3]=""}}let n=t[2],s="";if(this.options.pedantic){const e=/^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(n);e&&(n=e[1],s=e[3])}else s=t[3]?t[3].slice(1,-1):"";return n=n.trim(),/^</.test(n)&&(n=this.options.pedantic&&!/>$/.test(e)?n.slice(1):n.slice(1,-1)),d(t,{href:n?n.replace(this.rules.inline.anyPunctuation,"$1"):n,title:s?s.replace(this.rules.inline.anyPunctuation,"$1"):s},t[0],this.lexer)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){const e=t[(n[2]||n[1]).replace(/\s+/g," ").toLowerCase()];if(!e){const e=n[0].charAt(0);return{type:"text",raw:e,text:e}}return d(n,e,n[0],this.lexer)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s)return;if(s[3]&&n.match(/[\p{L}\p{N}]/u))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){const n=[...s[0]].length-1;let r,i,l=n,o=0;const a="*"===s[0][0]?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(a.lastIndex=0,t=t.slice(-1*e.length+n);null!=(s=a.exec(t));){if(r=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!r)continue;if(i=[...r].length,s[3]||s[4]){l+=i;continue}if((s[5]||s[6])&&n%3&&!((n+i)%3)){o+=i;continue}if(l-=i,l>0)continue;i=Math.min(i,i+l+o);const t=[...s[0]][0].length,a=e.slice(0,n+s.index+t+i);if(Math.min(n,i)%2){const e=a.slice(1,-1);return{type:"em",raw:a,text:e,tokens:this.lexer.inlineTokens(e)}}const c=a.slice(2,-2);return{type:"strong",raw:a,text:c,tokens:this.lexer.inlineTokens(c)}}}}codespan(e){const t=this.rules.inline.code.exec(e);if(t){let e=t[2].replace(/\n/g," ");const n=/[^ ]/.test(e),s=/^ /.test(e)&&/ $/.test(e);return n&&s&&(e=e.substring(1,e.length-1)),e=c(e,!0),{type:"codespan",raw:t[0],text:e}}}br(e){const t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){const t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){const t=this.rules.inline.autolink.exec(e);if(t){let e,n;return"@"===t[2]?(e=c(t[1]),n="mailto:"+e):(e=c(t[1]),n=e),{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let e,n;if("@"===t[2])e=c(t[0]),n="mailto:"+e;else{let s;do{s=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??""}while(s!==t[0]);e=c(t[0]),n="www."===t[1]?"http://"+t[0]:t[0]}return{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}inlineText(e){const t=this.rules.inline.text.exec(e);if(t){let e;return e=this.lexer.state.inRawBlock?t[0]:c(t[0]),{type:"text",raw:t[0],text:e}}}}const b=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,w=/(?:[*+-]|\d{1,9}[.)])/,m=p(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/).replace(/bull/g,w).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).getRegex(),y=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,$=/(?!\s*\])(?:\\.|[^\[\]\\])+/,z=p(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",$).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),T=p(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,w).getRegex(),R="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",_=/<!--(?:-?>|[\s\S]*?(?:-->|$))/,A=p("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$))","i").replace("comment",_).replace("tag",R).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),S=p(y).replace("hr",b).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",R).getRegex(),I={blockquote:p(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",S).getRegex(),code:/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,def:z,fences:/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,hr:b,html:A,lheading:m,list:T,newline:/^(?:[ \t]*(?:\n|$))+/,paragraph:S,table:k,text:/^[^\n]+/},E=p("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",b).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3}\t)[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",R).getRegex(),q={...I,table:E,paragraph:p(y).replace("hr",b).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",E).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",R).getRegex()},Z={...I,html:p("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:\"[^\"]*\"|'[^']*'|\\s[^'\"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",_).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:k,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:p(y).replace("hr",b).replace("heading"," *#{1,6} *[^\n]").replace("lheading",m).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},P=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,L=/^( {2,}|\\)\n(?!\s*$)/,v="\\p{P}\\p{S}",Q=p(/^((?![*_])[\spunctuation])/,"u").replace(/punctuation/g,v).getRegex(),B=p(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/,"u").replace(/punct/g,v).getRegex(),M=p("^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)[punct](\\*+)(?=[\\s]|$)|[^punct\\s](\\*+)(?!\\*)(?=[punct\\s]|$)|(?!\\*)[punct\\s](\\*+)(?=[^punct\\s])|[\\s](\\*+)(?!\\*)(?=[punct])|(?!\\*)[punct](\\*+)(?!\\*)(?=[punct])|[^punct\\s](\\*+)(?=[^punct\\s])","gu").replace(/punct/g,v).getRegex(),O=p("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)[punct](_+)(?=[\\s]|$)|[^punct\\s](_+)(?!_)(?=[punct\\s]|$)|(?!_)[punct\\s](_+)(?=[^punct\\s])|[\\s](_+)(?!_)(?=[punct])|(?!_)[punct](_+)(?!_)(?=[punct])","gu").replace(/punct/g,v).getRegex(),j=p(/\\([punct])/,"gu").replace(/punct/g,v).getRegex(),D=p(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),C=p(_).replace("(?:--\x3e|$)","--\x3e").getRegex(),H=p("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment",C).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),U=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,X=p(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/).replace("label",U).replace("href",/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),F=p(/^!?\[(label)\]\[(ref)\]/).replace("label",U).replace("ref",$).getRegex(),N=p(/^!?\[(ref)\](?:\[\])?/).replace("ref",$).getRegex(),G={_backpedal:k,anyPunctuation:j,autolink:D,blockSkip:/\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g,br:L,code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,del:k,emStrongLDelim:B,emStrongRDelimAst:M,emStrongRDelimUnd:O,escape:P,link:X,nolink:N,punctuation:Q,reflink:F,reflinkSearch:p("reflink|nolink(?!\\()","g").replace("reflink",F).replace("nolink",N).getRegex(),tag:H,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,url:k},J={...G,link:p(/^!?\[(label)\]\((.*?)\)/).replace("label",U).getRegex(),reflink:p(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",U).getRegex()},K={...G,escape:p(P).replace("])","~|])").getRegex(),url:p(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,"i").replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/},V={...K,br:p(L).replace("{2,}","*").getRegex(),text:p(K.text).replace("\\b_","\\b_| {2,}\\n").replace(/\{2,\}/g,"*").getRegex()},W={normal:I,gfm:q,pedantic:Z},Y={normal:G,gfm:K,breaks:V,pedantic:J};class ee{tokens;options;state;tokenizer;inlineQueue;constructor(t){this.tokens=[],this.tokens.links=Object.create(null),this.options=t||e.defaults,this.options.tokenizer=this.options.tokenizer||new x,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};const n={block:W.normal,inline:Y.normal};this.options.pedantic?(n.block=W.pedantic,n.inline=Y.pedantic):this.options.gfm&&(n.block=W.gfm,this.options.breaks?n.inline=Y.breaks:n.inline=Y.gfm),this.tokenizer.rules=n}static get rules(){return{block:W,inline:Y}}static lex(e,t){return new ee(t).lex(e)}static lexInline(e,t){return new ee(t).inlineTokens(e)}lex(e){e=e.replace(/\r\n|\r/g,"\n"),this.blockTokens(e,this.tokens);for(let e=0;e<this.inlineQueue.length;e++){const t=this.inlineQueue[e];this.inlineTokens(t.src,t.tokens)}return this.inlineQueue=[],this.tokens}blockTokens(e,t=[],n=!1){let s,r,i;for(this.options.pedantic&&(e=e.replace(/\t/g," ").replace(/^ +$/gm,""));e;)if(!(this.options.extensions&&this.options.extensions.block&&this.options.extensions.block.some((n=>!!(s=n.call({lexer:this},e,t))&&(e=e.substring(s.raw.length),t.push(s),!0)))))if(s=this.tokenizer.space(e))e=e.substring(s.raw.length),1===s.raw.length&&t.length>0?t[t.length-1].raw+="\n":t.push(s);else if(s=this.tokenizer.code(e))e=e.substring(s.raw.length),r=t[t.length-1],!r||"paragraph"!==r.type&&"text"!==r.type?t.push(s):(r.raw+="\n"+s.raw,r.text+="\n"+s.text,this.inlineQueue[this.inlineQueue.length-1].src=r.text);else if(s=this.tokenizer.fences(e))e=e.substring(s.raw.length),t.push(s);else if(s=this.tokenizer.heading(e))e=e.substring(s.raw.length),t.push(s);else if(s=this.tokenizer.hr(e))e=e.substring(s.raw.length),t.push(s);else if(s=this.tokenizer.blockquote(e))e=e.substring(s.raw.length),t.push(s);else if(s=this.tokenizer.list(e))e=e.substring(s.raw.length),t.push(s);else if(s=this.tokenizer.html(e))e=e.substring(s.raw.length),t.push(s);else if(s=this.tokenizer.def(e))e=e.substring(s.raw.length),r=t[t.length-1],!r||"paragraph"!==r.type&&"text"!==r.type?this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title}):(r.raw+="\n"+s.raw,r.text+="\n"+s.raw,this.inlineQueue[this.inlineQueue.length-1].src=r.text);else if(s=this.tokenizer.table(e))e=e.substring(s.raw.length),t.push(s);else if(s=this.tokenizer.lheading(e))e=e.substring(s.raw.length),t.push(s);else{if(i=e,this.options.extensions&&this.options.extensions.startBlock){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startBlock.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(i=e.substring(0,t+1))}if(this.state.top&&(s=this.tokenizer.paragraph(i)))r=t[t.length-1],n&&"paragraph"===r?.type?(r.raw+="\n"+s.raw,r.text+="\n"+s.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=r.text):t.push(s),n=i.length!==e.length,e=e.substring(s.raw.length);else if(s=this.tokenizer.text(e))e=e.substring(s.raw.length),r=t[t.length-1],r&&"text"===r.type?(r.raw+="\n"+s.raw,r.text+="\n"+s.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=r.text):t.push(s);else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n,s,r,i,l,o,a=e;if(this.tokens.links){const e=Object.keys(this.tokens.links);if(e.length>0)for(;null!=(i=this.tokenizer.rules.inline.reflinkSearch.exec(a));)e.includes(i[0].slice(i[0].lastIndexOf("[")+1,-1))&&(a=a.slice(0,i.index)+"["+"a".repeat(i[0].length-2)+"]"+a.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;null!=(i=this.tokenizer.rules.inline.blockSkip.exec(a));)a=a.slice(0,i.index)+"["+"a".repeat(i[0].length-2)+"]"+a.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);for(;null!=(i=this.tokenizer.rules.inline.anyPunctuation.exec(a));)a=a.slice(0,i.index)+"++"+a.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;e;)if(l||(o=""),l=!1,!(this.options.extensions&&this.options.extensions.inline&&this.options.extensions.inline.some((s=>!!(n=s.call({lexer:this},e,t))&&(e=e.substring(n.raw.length),t.push(n),!0)))))if(n=this.tokenizer.escape(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.tag(e))e=e.substring(n.raw.length),s=t[t.length-1],s&&"text"===n.type&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(n=this.tokenizer.link(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.reflink(e,this.tokens.links))e=e.substring(n.raw.length),s=t[t.length-1],s&&"text"===n.type&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(n=this.tokenizer.emStrong(e,a,o))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.codespan(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.br(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.del(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.autolink(e))e=e.substring(n.raw.length),t.push(n);else if(this.state.inLink||!(n=this.tokenizer.url(e))){if(r=e,this.options.extensions&&this.options.extensions.startInline){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startInline.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(r=e.substring(0,t+1))}if(n=this.tokenizer.inlineText(r))e=e.substring(n.raw.length),"_"!==n.raw.slice(-1)&&(o=n.raw.slice(-1)),l=!0,s=t[t.length-1],s&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}else e=e.substring(n.raw.length),t.push(n);return t}}class te{options;parser;constructor(t){this.options=t||e.defaults}space(e){return""}code({text:e,lang:t,escaped:n}){const s=(t||"").match(/^\S*/)?.[0],r=e.replace(/\n$/,"")+"\n";return s?'<pre><code class="language-'+c(s)+'">'+(n?r:c(r,!0))+"</code></pre>\n":"<pre><code>"+(n?r:c(r,!0))+"</code></pre>\n"}blockquote({tokens:e}){return`<blockquote>\n${this.parser.parse(e)}</blockquote>\n`}html({text:e}){return e}heading({tokens:e,depth:t}){return`<h${t}>${this.parser.parseInline(e)}</h${t}>\n`}hr(e){return"<hr>\n"}list(e){const t=e.ordered,n=e.start;let s="";for(let t=0;t<e.items.length;t++){const n=e.items[t];s+=this.listitem(n)}const r=t?"ol":"ul";return"<"+r+(t&&1!==n?' start="'+n+'"':"")+">\n"+s+"</"+r+">\n"}listitem(e){let t="";if(e.task){const n=this.checkbox({checked:!!e.checked});e.loose?e.tokens.length>0&&"paragraph"===e.tokens[0].type?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&"text"===e.tokens[0].tokens[0].type&&(e.tokens[0].tokens[0].text=n+" "+e.tokens[0].tokens[0].text)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" "}):t+=n+" "}return t+=this.parser.parse(e.tokens,!!e.loose),`<li>${t}</li>\n`}checkbox({checked:e}){return"<input "+(e?'checked="" ':"")+'disabled="" type="checkbox">'}paragraph({tokens:e}){return`<p>${this.parser.parseInline(e)}</p>\n`}table(e){let t="",n="";for(let t=0;t<e.header.length;t++)n+=this.tablecell(e.header[t]);t+=this.tablerow({text:n});let s="";for(let t=0;t<e.rows.length;t++){const r=e.rows[t];n="";for(let e=0;e<r.length;e++)n+=this.tablecell(r[e]);s+=this.tablerow({text:n})}return s&&(s=`<tbody>${s}</tbody>`),"<table>\n<thead>\n"+t+"</thead>\n"+s+"</table>\n"}tablerow({text:e}){return`<tr>\n${e}</tr>\n`}tablecell(e){const t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+`</${n}>\n`}strong({tokens:e}){return`<strong>${this.parser.parseInline(e)}</strong>`}em({tokens:e}){return`<em>${this.parser.parseInline(e)}</em>`}codespan({text:e}){return`<code>${e}</code>`}br(e){return"<br>"}del({tokens:e}){return`<del>${this.parser.parseInline(e)}</del>`}link({href:e,title:t,tokens:n}){const s=this.parser.parseInline(n),r=u(e);if(null===r)return s;let i='<a href="'+(e=r)+'"';return t&&(i+=' title="'+t+'"'),i+=">"+s+"</a>",i}image({href:e,title:t,text:n}){const s=u(e);if(null===s)return n;let r=`<img src="${e=s}" alt="${n}"`;return t&&(r+=` title="${t}"`),r+=">",r}text(e){return"tokens"in e&&e.tokens?this.parser.parseInline(e.tokens):e.text}}class ne{strong({text:e}){return e}em({text:e}){return e}codespan({text:e}){return e}del({text:e}){return e}html({text:e}){return e}text({text:e}){return e}link({text:e}){return""+e}image({text:e}){return""+e}br(){return""}}class se{options;renderer;textRenderer;constructor(t){this.options=t||e.defaults,this.options.renderer=this.options.renderer||new te,this.renderer=this.options.renderer,this.renderer.options=this.options,this.renderer.parser=this,this.textRenderer=new ne}static parse(e,t){return new se(t).parse(e)}static parseInline(e,t){return new se(t).parseInline(e)}parse(e,t=!0){let n="";for(let s=0;s<e.length;s++){const r=e[s];if(this.options.extensions&&this.options.extensions.renderers&&this.options.extensions.renderers[r.type]){const e=r,t=this.options.extensions.renderers[e.type].call({parser:this},e);if(!1!==t||!["space","hr","heading","code","table","blockquote","list","html","paragraph","text"].includes(e.type)){n+=t||"";continue}}const i=r;switch(i.type){case"space":n+=this.renderer.space(i);continue;case"hr":n+=this.renderer.hr(i);continue;case"heading":n+=this.renderer.heading(i);continue;case"code":n+=this.renderer.code(i);continue;case"table":n+=this.renderer.table(i);continue;case"blockquote":n+=this.renderer.blockquote(i);continue;case"list":n+=this.renderer.list(i);continue;case"html":n+=this.renderer.html(i);continue;case"paragraph":n+=this.renderer.paragraph(i);continue;case"text":{let r=i,l=this.renderer.text(r);for(;s+1<e.length&&"text"===e[s+1].type;)r=e[++s],l+="\n"+this.renderer.text(r);n+=t?this.renderer.paragraph({type:"paragraph",raw:l,text:l,tokens:[{type:"text",raw:l,text:l}]}):l;continue}default:{const e='Token with "'+i.type+'" type was not found.';if(this.options.silent)return console.error(e),"";throw new Error(e)}}}return n}parseInline(e,t){t=t||this.renderer;let n="";for(let s=0;s<e.length;s++){const r=e[s];if(this.options.extensions&&this.options.extensions.renderers&&this.options.extensions.renderers[r.type]){const e=this.options.extensions.renderers[r.type].call({parser:this},r);if(!1!==e||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(r.type)){n+=e||"";continue}}const i=r;switch(i.type){case"escape":case"text":n+=t.text(i);break;case"html":n+=t.html(i);break;case"link":n+=t.link(i);break;case"image":n+=t.image(i);break;case"strong":n+=t.strong(i);break;case"em":n+=t.em(i);break;case"codespan":n+=t.codespan(i);break;case"br":n+=t.br(i);break;case"del":n+=t.del(i);break;default:{const e='Token with "'+i.type+'" type was not found.';if(this.options.silent)return console.error(e),"";throw new Error(e)}}}return n}}class re{options;block;constructor(t){this.options=t||e.defaults}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(e){return e}postprocess(e){return e}processAllTokens(e){return e}provideLexer(){return this.block?ee.lex:ee.lexInline}provideParser(){return this.block?se.parse:se.parseInline}}class ie{defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};options=this.setOptions;parse=this.parseMarkdown(!0);parseInline=this.parseMarkdown(!1);Parser=se;Renderer=te;TextRenderer=ne;Lexer=ee;Tokenizer=x;Hooks=re;constructor(...e){this.use(...e)}walkTokens(e,t){let n=[];for(const s of e)switch(n=n.concat(t.call(this,s)),s.type){case"table":{const e=s;for(const s of e.header)n=n.concat(this.walkTokens(s.tokens,t));for(const s of e.rows)for(const e of s)n=n.concat(this.walkTokens(e.tokens,t));break}case"list":{const e=s;n=n.concat(this.walkTokens(e.items,t));break}default:{const e=s;this.defaults.extensions?.childTokens?.[e.type]?this.defaults.extensions.childTokens[e.type].forEach((s=>{const r=e[s].flat(1/0);n=n.concat(this.walkTokens(r,t))})):e.tokens&&(n=n.concat(this.walkTokens(e.tokens,t)))}}return n}use(...e){const t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach((e=>{const n={...e};if(n.async=this.defaults.async||n.async||!1,e.extensions&&(e.extensions.forEach((e=>{if(!e.name)throw new Error("extension name required");if("renderer"in e){const n=t.renderers[e.name];t.renderers[e.name]=n?function(...t){let s=e.renderer.apply(this,t);return!1===s&&(s=n.apply(this,t)),s}:e.renderer}if("tokenizer"in e){if(!e.level||"block"!==e.level&&"inline"!==e.level)throw new Error("extension level must be 'block' or 'inline'");const n=t[e.level];n?n.unshift(e.tokenizer):t[e.level]=[e.tokenizer],e.start&&("block"===e.level?t.startBlock?t.startBlock.push(e.start):t.startBlock=[e.start]:"inline"===e.level&&(t.startInline?t.startInline.push(e.start):t.startInline=[e.start]))}"childTokens"in e&&e.childTokens&&(t.childTokens[e.name]=e.childTokens)})),n.extensions=t),e.renderer){const t=this.defaults.renderer||new te(this.defaults);for(const n in e.renderer){if(!(n in t))throw new Error(`renderer '${n}' does not exist`);if(["options","parser"].includes(n))continue;const s=n,r=e.renderer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n||""}}n.renderer=t}if(e.tokenizer){const t=this.defaults.tokenizer||new x(this.defaults);for(const n in e.tokenizer){if(!(n in t))throw new Error(`tokenizer '${n}' does not exist`);if(["options","rules","lexer"].includes(n))continue;const s=n,r=e.tokenizer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.tokenizer=t}if(e.hooks){const t=this.defaults.hooks||new re;for(const n in e.hooks){if(!(n in t))throw new Error(`hook '${n}' does not exist`);if(["options","block"].includes(n))continue;const s=n,r=e.hooks[s],i=t[s];re.passThroughHooks.has(n)?t[s]=e=>{if(this.defaults.async)return Promise.resolve(r.call(t,e)).then((e=>i.call(t,e)));const n=r.call(t,e);return i.call(t,n)}:t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.hooks=t}if(e.walkTokens){const t=this.defaults.walkTokens,s=e.walkTokens;n.walkTokens=function(e){let n=[];return n.push(s.call(this,e)),t&&(n=n.concat(t.call(this,e))),n}}this.defaults={...this.defaults,...n}})),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return ee.lex(e,t??this.defaults)}parser(e,t){return se.parse(e,t??this.defaults)}parseMarkdown(e){return(t,n)=>{const s={...n},r={...this.defaults,...s},i=this.onError(!!r.silent,!!r.async);if(!0===this.defaults.async&&!1===s.async)return i(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(null==t)return i(new Error("marked(): input parameter is undefined or null"));if("string"!=typeof t)return i(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(t)+", string expected"));r.hooks&&(r.hooks.options=r,r.hooks.block=e);const l=r.hooks?r.hooks.provideLexer():e?ee.lex:ee.lexInline,o=r.hooks?r.hooks.provideParser():e?se.parse:se.parseInline;if(r.async)return Promise.resolve(r.hooks?r.hooks.preprocess(t):t).then((e=>l(e,r))).then((e=>r.hooks?r.hooks.processAllTokens(e):e)).then((e=>r.walkTokens?Promise.all(this.walkTokens(e,r.walkTokens)).then((()=>e)):e)).then((e=>o(e,r))).then((e=>r.hooks?r.hooks.postprocess(e):e)).catch(i);try{r.hooks&&(t=r.hooks.preprocess(t));let e=l(t,r);r.hooks&&(e=r.hooks.processAllTokens(e)),r.walkTokens&&this.walkTokens(e,r.walkTokens);let n=o(e,r);return r.hooks&&(n=r.hooks.postprocess(n)),n}catch(e){return i(e)}}}onError(e,t){return n=>{if(n.message+="\nPlease report this to https://github.com/markedjs/marked.",e){const e="<p>An error occurred:</p><pre>"+c(n.message+"",!0)+"</pre>";return t?Promise.resolve(e):e}if(t)return Promise.reject(n);throw n}}}const le=new ie;function oe(e,t){return le.parse(e,t)}oe.options=oe.setOptions=function(e){return le.setOptions(e),oe.defaults=le.defaults,n(oe.defaults),oe},oe.getDefaults=t,oe.defaults=e.defaults,oe.use=function(...e){return le.use(...e),oe.defaults=le.defaults,n(oe.defaults),oe},oe.walkTokens=function(e,t){return le.walkTokens(e,t)},oe.parseInline=le.parseInline,oe.Parser=se,oe.parser=se.parse,oe.Renderer=te,oe.TextRenderer=ne,oe.Lexer=ee,oe.lexer=ee.lex,oe.Tokenizer=x,oe.Hooks=re,oe.parse=oe;const ae=oe.options,ce=oe.setOptions,he=oe.use,pe=oe.walkTokens,ue=oe.parseInline,ke=oe,ge=se.parse,fe=ee.lex;e.Hooks=re,e.Lexer=ee,e.Marked=ie,e.Parser=se,e.Renderer=te,e.TextRenderer=ne,e.Tokenizer=x,e.getDefaults=t,e.lexer=fe,e.marked=oe,e.options=ae,e.parse=ke,e.parseInline=ue,e.parser=ge,e.setOptions=ce,e.use=he,e.walkTokens=pe}));
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/*! @license DOMPurify 3.1.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.7/LICENSE */
|
|
2
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).DOMPurify=t()}(this,(function(){"use strict";const{entries:e,setPrototypeOf:t,isFrozen:n,getPrototypeOf:o,getOwnPropertyDescriptor:r}=Object;let{freeze:i,seal:a,create:l}=Object,{apply:c,construct:s}="undefined"!=typeof Reflect&&Reflect;i||(i=function(e){return e}),a||(a=function(e){return e}),c||(c=function(e,t,n){return e.apply(t,n)}),s||(s=function(e,t){return new e(...t)});const u=b(Array.prototype.forEach),m=b(Array.prototype.pop),p=b(Array.prototype.push),f=b(String.prototype.toLowerCase),d=b(String.prototype.toString),h=b(String.prototype.match),g=b(String.prototype.replace),T=b(String.prototype.indexOf),y=b(String.prototype.trim),E=b(Object.prototype.hasOwnProperty),_=b(RegExp.prototype.test),A=(N=TypeError,function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return s(N,t)});var N;function b(e){return function(t){for(var n=arguments.length,o=new Array(n>1?n-1:0),r=1;r<n;r++)o[r-1]=arguments[r];return c(e,t,o)}}function S(e,o){let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:f;t&&t(e,null);let i=o.length;for(;i--;){let t=o[i];if("string"==typeof t){const e=r(t);e!==t&&(n(o)||(o[i]=e),t=e)}e[t]=!0}return e}function R(e){for(let t=0;t<e.length;t++){E(e,t)||(e[t]=null)}return e}function w(t){const n=l(null);for(const[o,r]of e(t)){E(t,o)&&(Array.isArray(r)?n[o]=R(r):r&&"object"==typeof r&&r.constructor===Object?n[o]=w(r):n[o]=r)}return n}function C(e,t){for(;null!==e;){const n=r(e,t);if(n){if(n.get)return b(n.get);if("function"==typeof n.value)return b(n.value)}e=o(e)}return function(){return null}}const L=i(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),v=i(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),D=i(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),O=i(["animate","color-profile","cursor","discard","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor","unknown","use"]),x=i(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover","mprescripts"]),k=i(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),M=i(["#text"]),I=i(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","controls","controlslist","coords","crossorigin","datetime","decoding","default","dir","disabled","disablepictureinpicture","disableremoteplayback","download","draggable","enctype","enterkeyhint","face","for","headers","height","hidden","high","href","hreflang","id","inputmode","integrity","ismap","kind","label","lang","list","loading","loop","low","max","maxlength","media","method","min","minlength","multiple","muted","name","nonce","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","playsinline","popover","popovertarget","popovertargetaction","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","span","srclang","start","src","srcset","step","style","summary","tabindex","title","translate","type","usemap","valign","value","width","wrap","xmlns","slot"]),U=i(["accent-height","accumulate","additive","alignment-baseline","amplitude","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clippathunits","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dur","edgemode","elevation","end","exponent","fill","fill-opacity","fill-rule","filter","filterunits","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","intercept","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","preserveaspectratio","primitiveunits","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","slope","specularconstant","specularexponent","spreadmethod","startoffset","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","systemlanguage","tabindex","tablevalues","targetx","targety","transform","transform-origin","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","version","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"]),P=i(["accent","accentunder","align","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","dir","display","displaystyle","encoding","fence","frame","height","href","id","largeop","length","linethickness","lspace","lquote","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"]),F=i(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),H=a(/\{\{[\w\W]*|[\w\W]*\}\}/gm),z=a(/<%[\w\W]*|[\w\W]*%>/gm),B=a(/\${[\w\W]*}/gm),W=a(/^data-[\-\w.\u00B7-\uFFFF]/),G=a(/^aria-[\-\w]+$/),Y=a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),j=a(/^(?:\w+script|data):/i),X=a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),q=a(/^html$/i),$=a(/^[a-z][.\w]*(-[.\w]+)+$/i);var K=Object.freeze({__proto__:null,MUSTACHE_EXPR:H,ERB_EXPR:z,TMPLIT_EXPR:B,DATA_ATTR:W,ARIA_ATTR:G,IS_ALLOWED_URI:Y,IS_SCRIPT_OR_DATA:j,ATTR_WHITESPACE:X,DOCTYPE_NAME:q,CUSTOM_ELEMENT:$});const V=1,Z=3,J=7,Q=8,ee=9,te=function(){return"undefined"==typeof window?null:window};var ne=function t(){let n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:te();const o=e=>t(e);if(o.version="3.1.7",o.removed=[],!n||!n.document||n.document.nodeType!==ee)return o.isSupported=!1,o;let{document:r}=n;const a=r,c=a.currentScript,{DocumentFragment:s,HTMLTemplateElement:N,Node:b,Element:R,NodeFilter:H,NamedNodeMap:z=n.NamedNodeMap||n.MozNamedAttrMap,HTMLFormElement:B,DOMParser:W,trustedTypes:G}=n,j=R.prototype,X=C(j,"cloneNode"),$=C(j,"remove"),ne=C(j,"nextSibling"),oe=C(j,"childNodes"),re=C(j,"parentNode");if("function"==typeof N){const e=r.createElement("template");e.content&&e.content.ownerDocument&&(r=e.content.ownerDocument)}let ie,ae="";const{implementation:le,createNodeIterator:ce,createDocumentFragment:se,getElementsByTagName:ue}=r,{importNode:me}=a;let pe={};o.isSupported="function"==typeof e&&"function"==typeof re&&le&&void 0!==le.createHTMLDocument;const{MUSTACHE_EXPR:fe,ERB_EXPR:de,TMPLIT_EXPR:he,DATA_ATTR:ge,ARIA_ATTR:Te,IS_SCRIPT_OR_DATA:ye,ATTR_WHITESPACE:Ee,CUSTOM_ELEMENT:_e}=K;let{IS_ALLOWED_URI:Ae}=K,Ne=null;const be=S({},[...L,...v,...D,...x,...M]);let Se=null;const Re=S({},[...I,...U,...P,...F]);let we=Object.seal(l(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),Ce=null,Le=null,ve=!0,De=!0,Oe=!1,xe=!0,ke=!1,Me=!0,Ie=!1,Ue=!1,Pe=!1,Fe=!1,He=!1,ze=!1,Be=!0,We=!1,Ge=!0,Ye=!1,je={},Xe=null;const qe=S({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let $e=null;const Ke=S({},["audio","video","img","source","image","track"]);let Ve=null;const Ze=S({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Je="http://www.w3.org/1998/Math/MathML",Qe="http://www.w3.org/2000/svg",et="http://www.w3.org/1999/xhtml";let tt=et,nt=!1,ot=null;const rt=S({},[Je,Qe,et],d);let it=null;const at=["application/xhtml+xml","text/html"];let lt=null,ct=null;const st=r.createElement("form"),ut=function(e){return e instanceof RegExp||e instanceof Function},mt=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!ct||ct!==e){if(e&&"object"==typeof e||(e={}),e=w(e),it=-1===at.indexOf(e.PARSER_MEDIA_TYPE)?"text/html":e.PARSER_MEDIA_TYPE,lt="application/xhtml+xml"===it?d:f,Ne=E(e,"ALLOWED_TAGS")?S({},e.ALLOWED_TAGS,lt):be,Se=E(e,"ALLOWED_ATTR")?S({},e.ALLOWED_ATTR,lt):Re,ot=E(e,"ALLOWED_NAMESPACES")?S({},e.ALLOWED_NAMESPACES,d):rt,Ve=E(e,"ADD_URI_SAFE_ATTR")?S(w(Ze),e.ADD_URI_SAFE_ATTR,lt):Ze,$e=E(e,"ADD_DATA_URI_TAGS")?S(w(Ke),e.ADD_DATA_URI_TAGS,lt):Ke,Xe=E(e,"FORBID_CONTENTS")?S({},e.FORBID_CONTENTS,lt):qe,Ce=E(e,"FORBID_TAGS")?S({},e.FORBID_TAGS,lt):{},Le=E(e,"FORBID_ATTR")?S({},e.FORBID_ATTR,lt):{},je=!!E(e,"USE_PROFILES")&&e.USE_PROFILES,ve=!1!==e.ALLOW_ARIA_ATTR,De=!1!==e.ALLOW_DATA_ATTR,Oe=e.ALLOW_UNKNOWN_PROTOCOLS||!1,xe=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,ke=e.SAFE_FOR_TEMPLATES||!1,Me=!1!==e.SAFE_FOR_XML,Ie=e.WHOLE_DOCUMENT||!1,Fe=e.RETURN_DOM||!1,He=e.RETURN_DOM_FRAGMENT||!1,ze=e.RETURN_TRUSTED_TYPE||!1,Pe=e.FORCE_BODY||!1,Be=!1!==e.SANITIZE_DOM,We=e.SANITIZE_NAMED_PROPS||!1,Ge=!1!==e.KEEP_CONTENT,Ye=e.IN_PLACE||!1,Ae=e.ALLOWED_URI_REGEXP||Y,tt=e.NAMESPACE||et,we=e.CUSTOM_ELEMENT_HANDLING||{},e.CUSTOM_ELEMENT_HANDLING&&ut(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(we.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&ut(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(we.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(we.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),ke&&(De=!1),He&&(Fe=!0),je&&(Ne=S({},M),Se=[],!0===je.html&&(S(Ne,L),S(Se,I)),!0===je.svg&&(S(Ne,v),S(Se,U),S(Se,F)),!0===je.svgFilters&&(S(Ne,D),S(Se,U),S(Se,F)),!0===je.mathMl&&(S(Ne,x),S(Se,P),S(Se,F))),e.ADD_TAGS&&(Ne===be&&(Ne=w(Ne)),S(Ne,e.ADD_TAGS,lt)),e.ADD_ATTR&&(Se===Re&&(Se=w(Se)),S(Se,e.ADD_ATTR,lt)),e.ADD_URI_SAFE_ATTR&&S(Ve,e.ADD_URI_SAFE_ATTR,lt),e.FORBID_CONTENTS&&(Xe===qe&&(Xe=w(Xe)),S(Xe,e.FORBID_CONTENTS,lt)),Ge&&(Ne["#text"]=!0),Ie&&S(Ne,["html","head","body"]),Ne.table&&(S(Ne,["tbody"]),delete Ce.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw A('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw A('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');ie=e.TRUSTED_TYPES_POLICY,ae=ie.createHTML("")}else void 0===ie&&(ie=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let n=null;const o="data-tt-policy-suffix";t&&t.hasAttribute(o)&&(n=t.getAttribute(o));const r="dompurify"+(n?"#"+n:"");try{return e.createPolicy(r,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+r+" could not be created."),null}}(G,c)),null!==ie&&"string"==typeof ae&&(ae=ie.createHTML(""));i&&i(e),ct=e}},pt=S({},["mi","mo","mn","ms","mtext"]),ft=S({},["annotation-xml"]),dt=S({},["title","style","font","a","script"]),ht=S({},[...v,...D,...O]),gt=S({},[...x,...k]),Tt=function(e){p(o.removed,{element:e});try{re(e).removeChild(e)}catch(t){$(e)}},yt=function(e,t){try{p(o.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){p(o.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!Se[e])if(Fe||He)try{Tt(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},Et=function(e){let t=null,n=null;if(Pe)e="<remove></remove>"+e;else{const t=h(e,/^[\r\n\t ]+/);n=t&&t[0]}"application/xhtml+xml"===it&&tt===et&&(e='<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>'+e+"</body></html>");const o=ie?ie.createHTML(e):e;if(tt===et)try{t=(new W).parseFromString(o,it)}catch(e){}if(!t||!t.documentElement){t=le.createDocument(tt,"template",null);try{t.documentElement.innerHTML=nt?ae:o}catch(e){}}const i=t.body||t.documentElement;return e&&n&&i.insertBefore(r.createTextNode(n),i.childNodes[0]||null),tt===et?ue.call(t,Ie?"html":"body")[0]:Ie?t.documentElement:i},_t=function(e){return ce.call(e.ownerDocument||e,e,H.SHOW_ELEMENT|H.SHOW_COMMENT|H.SHOW_TEXT|H.SHOW_PROCESSING_INSTRUCTION|H.SHOW_CDATA_SECTION,null)},At=function(e){return e instanceof B&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof z)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},Nt=function(e){return"function"==typeof b&&e instanceof b},bt=function(e,t,n){pe[e]&&u(pe[e],(e=>{e.call(o,t,n,ct)}))},St=function(e){let t=null;if(bt("beforeSanitizeElements",e,null),At(e))return Tt(e),!0;const n=lt(e.nodeName);if(bt("uponSanitizeElement",e,{tagName:n,allowedTags:Ne}),e.hasChildNodes()&&!Nt(e.firstElementChild)&&_(/<[/\w]/g,e.innerHTML)&&_(/<[/\w]/g,e.textContent))return Tt(e),!0;if(e.nodeType===J)return Tt(e),!0;if(Me&&e.nodeType===Q&&_(/<[/\w]/g,e.data))return Tt(e),!0;if(!Ne[n]||Ce[n]){if(!Ce[n]&&wt(n)){if(we.tagNameCheck instanceof RegExp&&_(we.tagNameCheck,n))return!1;if(we.tagNameCheck instanceof Function&&we.tagNameCheck(n))return!1}if(Ge&&!Xe[n]){const t=re(e)||e.parentNode,n=oe(e)||e.childNodes;if(n&&t){for(let o=n.length-1;o>=0;--o){const r=X(n[o],!0);r.__removalCount=(e.__removalCount||0)+1,t.insertBefore(r,ne(e))}}}return Tt(e),!0}return e instanceof R&&!function(e){let t=re(e);t&&t.tagName||(t={namespaceURI:tt,tagName:"template"});const n=f(e.tagName),o=f(t.tagName);return!!ot[e.namespaceURI]&&(e.namespaceURI===Qe?t.namespaceURI===et?"svg"===n:t.namespaceURI===Je?"svg"===n&&("annotation-xml"===o||pt[o]):Boolean(ht[n]):e.namespaceURI===Je?t.namespaceURI===et?"math"===n:t.namespaceURI===Qe?"math"===n&&ft[o]:Boolean(gt[n]):e.namespaceURI===et?!(t.namespaceURI===Qe&&!ft[o])&&!(t.namespaceURI===Je&&!pt[o])&&!gt[n]&&(dt[n]||!ht[n]):!("application/xhtml+xml"!==it||!ot[e.namespaceURI]))}(e)?(Tt(e),!0):"noscript"!==n&&"noembed"!==n&&"noframes"!==n||!_(/<\/no(script|embed|frames)/i,e.innerHTML)?(ke&&e.nodeType===Z&&(t=e.textContent,u([fe,de,he],(e=>{t=g(t,e," ")})),e.textContent!==t&&(p(o.removed,{element:e.cloneNode()}),e.textContent=t)),bt("afterSanitizeElements",e,null),!1):(Tt(e),!0)},Rt=function(e,t,n){if(Be&&("id"===t||"name"===t)&&(n in r||n in st))return!1;if(De&&!Le[t]&&_(ge,t));else if(ve&&_(Te,t));else if(!Se[t]||Le[t]){if(!(wt(e)&&(we.tagNameCheck instanceof RegExp&&_(we.tagNameCheck,e)||we.tagNameCheck instanceof Function&&we.tagNameCheck(e))&&(we.attributeNameCheck instanceof RegExp&&_(we.attributeNameCheck,t)||we.attributeNameCheck instanceof Function&&we.attributeNameCheck(t))||"is"===t&&we.allowCustomizedBuiltInElements&&(we.tagNameCheck instanceof RegExp&&_(we.tagNameCheck,n)||we.tagNameCheck instanceof Function&&we.tagNameCheck(n))))return!1}else if(Ve[t]);else if(_(Ae,g(n,Ee,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==T(n,"data:")||!$e[e]){if(Oe&&!_(ye,g(n,Ee,"")));else if(n)return!1}else;return!0},wt=function(e){return"annotation-xml"!==e&&h(e,_e)},Ct=function(e){bt("beforeSanitizeAttributes",e,null);const{attributes:t}=e;if(!t)return;const n={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:Se};let r=t.length;for(;r--;){const i=t[r],{name:a,namespaceURI:l,value:c}=i,s=lt(a);let p="value"===a?c:y(c);if(n.attrName=s,n.attrValue=p,n.keepAttr=!0,n.forceKeepAttr=void 0,bt("uponSanitizeAttribute",e,n),p=n.attrValue,n.forceKeepAttr)continue;if(yt(a,e),!n.keepAttr)continue;if(!xe&&_(/\/>/i,p)){yt(a,e);continue}ke&&u([fe,de,he],(e=>{p=g(p,e," ")}));const f=lt(e.nodeName);if(Rt(f,s,p))if(!We||"id"!==s&&"name"!==s||(yt(a,e),p="user-content-"+p),Me&&_(/((--!?|])>)|<\/(style|title)/i,p))yt(a,e);else{if(ie&&"object"==typeof G&&"function"==typeof G.getAttributeType)if(l);else switch(G.getAttributeType(f,s)){case"TrustedHTML":p=ie.createHTML(p);break;case"TrustedScriptURL":p=ie.createScriptURL(p)}try{l?e.setAttributeNS(l,a,p):e.setAttribute(a,p),At(e)?Tt(e):m(o.removed)}catch(e){}}}bt("afterSanitizeAttributes",e,null)},Lt=function e(t){let n=null;const o=_t(t);for(bt("beforeSanitizeShadowDOM",t,null);n=o.nextNode();)bt("uponSanitizeShadowNode",n,null),St(n)||(n.content instanceof s&&e(n.content),Ct(n));bt("afterSanitizeShadowDOM",t,null)};return o.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=null,r=null,i=null,l=null;if(nt=!e,nt&&(e="\x3c!--\x3e"),"string"!=typeof e&&!Nt(e)){if("function"!=typeof e.toString)throw A("toString is not a function");if("string"!=typeof(e=e.toString()))throw A("dirty is not a string, aborting")}if(!o.isSupported)return e;if(Ue||mt(t),o.removed=[],"string"==typeof e&&(Ye=!1),Ye){if(e.nodeName){const t=lt(e.nodeName);if(!Ne[t]||Ce[t])throw A("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof b)n=Et("\x3c!----\x3e"),r=n.ownerDocument.importNode(e,!0),r.nodeType===V&&"BODY"===r.nodeName||"HTML"===r.nodeName?n=r:n.appendChild(r);else{if(!Fe&&!ke&&!Ie&&-1===e.indexOf("<"))return ie&&ze?ie.createHTML(e):e;if(n=Et(e),!n)return Fe?null:ze?ae:""}n&&Pe&&Tt(n.firstChild);const c=_t(Ye?e:n);for(;i=c.nextNode();)St(i)||(i.content instanceof s&&Lt(i.content),Ct(i));if(Ye)return e;if(Fe){if(He)for(l=se.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return(Se.shadowroot||Se.shadowrootmode)&&(l=me.call(a,l,!0)),l}let m=Ie?n.outerHTML:n.innerHTML;return Ie&&Ne["!doctype"]&&n.ownerDocument&&n.ownerDocument.doctype&&n.ownerDocument.doctype.name&&_(q,n.ownerDocument.doctype.name)&&(m="<!DOCTYPE "+n.ownerDocument.doctype.name+">\n"+m),ke&&u([fe,de,he],(e=>{m=g(m,e," ")})),ie&&ze?ie.createHTML(m):m},o.setConfig=function(){mt(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),Ue=!0},o.clearConfig=function(){ct=null,Ue=!1},o.isValidAttribute=function(e,t,n){ct||mt({});const o=lt(e),r=lt(t);return Rt(o,r,n)},o.addHook=function(e,t){"function"==typeof t&&(pe[e]=pe[e]||[],p(pe[e],t))},o.removeHook=function(e){if(pe[e])return m(pe[e])},o.removeHooks=function(e){pe[e]&&(pe[e]=[])},o.removeAllHooks=function(){pe={}},o}();return ne}));
|
|
3
|
+
//# sourceMappingURL=purify.min.js.map
|
|
@@ -26,11 +26,9 @@
|
|
|
26
26
|
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32.png" />
|
|
27
27
|
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16.png" />
|
|
28
28
|
<link rel="shortcut icon" href="/assets/favicon.ico" />
|
|
29
|
-
<link
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
<script src="https://cdn.jsdelivr.net/npm/marked@14.1.3/marked.min.js" integrity="sha384-k8o8HikHweyzW55Wd3wl18ovJj6vHVYNQeQbeSM0fxx+0WiH4TcccOG9uz8Xd2JR" crossorigin="anonymous"></script>
|
|
33
|
-
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.1.7/dist/purify.min.js" integrity="sha384-XQqX/4yiUGu+oyr87jvWzRuqBUK/adrY0DunhL+tID9m/9dwSpV8h9Fk/Sg6ifVQ" crossorigin="anonymous"></script>
|
|
29
|
+
<link href="/assets/fonts/fonts.css" rel="stylesheet" />
|
|
30
|
+
<script src="/assets/vendor/marked.min.js"></script>
|
|
31
|
+
<script src="/assets/vendor/purify.min.js"></script>
|
|
34
32
|
<style>
|
|
35
33
|
:root {
|
|
36
34
|
/* Dark emerald — matches landing greatcto.systems */
|
|
@@ -3694,8 +3692,7 @@ function connectSSE() {
|
|
|
3694
3692
|
if (window._gctoSSE) { try { window._gctoSSE.close(); } catch {} }
|
|
3695
3693
|
const es = new EventSource(`/api/sse${pqs()}`);
|
|
3696
3694
|
window._gctoSSE = es;
|
|
3697
|
-
|
|
3698
|
-
const newTasks = JSON.parse(e.data);
|
|
3695
|
+
const applyTasksSnapshot = (newTasks) => {
|
|
3699
3696
|
// Detect status transitions for toast feed
|
|
3700
3697
|
if (allTasks.length) {
|
|
3701
3698
|
const prevById = new Map(allTasks.map(t => [t.id, t]));
|
|
@@ -3713,6 +3710,21 @@ function connectSSE() {
|
|
|
3713
3710
|
updateNavCounts();
|
|
3714
3711
|
try { renderKanban(filterTasks(searchQuery)); } catch (e) { console.warn('SSE renderKanban failed', e); }
|
|
3715
3712
|
refreshInbox();
|
|
3713
|
+
};
|
|
3714
|
+
es.addEventListener('tasks', e => {
|
|
3715
|
+
const newTasks = JSON.parse(e.data);
|
|
3716
|
+
// A transient bd failure (dolt lock, timeout) can produce an empty
|
|
3717
|
+
// snapshot indistinguishable from "no tasks" (great_cto-e2ew). Don't
|
|
3718
|
+
// blindly wipe a populated board — refetch once via the regular API
|
|
3719
|
+
// (which applies the same last-good-cache guard server-side) and only
|
|
3720
|
+
// accept the empty state if the refetch confirms it.
|
|
3721
|
+
if (newTasks.length === 0 && allTasks.length > 0) {
|
|
3722
|
+
api(`/api/tasks${pqs()}`).then(refetched => {
|
|
3723
|
+
applyTasksSnapshot(refetched || []);
|
|
3724
|
+
});
|
|
3725
|
+
return;
|
|
3726
|
+
}
|
|
3727
|
+
applyTasksSnapshot(newTasks);
|
|
3716
3728
|
});
|
|
3717
3729
|
es.addEventListener('pipeline', e => {
|
|
3718
3730
|
try {
|
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
<title>{{PROJECT}} · Engineering Report · {{DATE}}</title>
|
|
8
8
|
<meta property="og:title" content="{{PROJECT}} · Engineering Report" />
|
|
9
9
|
<meta property="og:description" content="AI agents shipped features, saved time and cost. See the breakdown." />
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
<!-- great_cto-kge6: this page is published to an external host
|
|
11
|
+
(greatcto.systems/r/<hash>), not served from the local board — it can't
|
|
12
|
+
reference /assets/fonts locally-vendored files. Dropped the Google Fonts
|
|
13
|
+
CDN call entirely; the --sans/--serif/--mono stacks below already fall
|
|
14
|
+
back to system fonts (-apple-system, BlinkMacSystemFont, etc.) when
|
|
15
|
+
"Geist"/"Geist Mono" aren't available, so this renders cleanly with zero
|
|
16
|
+
external calls instead of leaking the viewer's IP to Google. -->
|
|
13
17
|
<style>
|
|
14
18
|
:root {
|
|
15
19
|
/* Dark emerald — matches landing greatcto.systems */
|
|
@@ -13,11 +13,12 @@ import fs from 'fs';
|
|
|
13
13
|
import path from 'path';
|
|
14
14
|
import { spawnSync } from 'child_process';
|
|
15
15
|
import { PORT, PUBLIC, HOST } from './lib/config.mjs';
|
|
16
|
-
import { originAllowed } from './lib/util.mjs';
|
|
16
|
+
import { originAllowed, isInsideDir } from './lib/util.mjs';
|
|
17
17
|
import { discoverProjects, resolveProjectInfo } from './lib/projects.mjs';
|
|
18
18
|
import { startAlertCron } from './lib/alerts.mjs';
|
|
19
19
|
import { watchBeads, watchVerdicts } from './lib/watchers.mjs';
|
|
20
20
|
import { dispatch } from './lib/routes.mjs';
|
|
21
|
+
import { log } from './lib/log.mjs';
|
|
21
22
|
|
|
22
23
|
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
23
24
|
|
|
@@ -77,7 +78,13 @@ const server = http.createServer(async (req, res) => {
|
|
|
77
78
|
// Static files
|
|
78
79
|
let filePath = pathname === '/' ? '/index.html' : pathname;
|
|
79
80
|
const fullPath = path.join(PUBLIC, filePath);
|
|
80
|
-
|
|
81
|
+
// Path-traversal guard (mirrors the /api/doc containment check in lib/routes.mjs):
|
|
82
|
+
// the joined path must resolve to somewhere inside PUBLIC. Without this, a decoded
|
|
83
|
+
// pathname like /..%2F..%2Fetc%2Fpasswd can escape the public/ directory via
|
|
84
|
+
// path.join's ".." collapsing. Reject with 404 (same as the generic static 404
|
|
85
|
+
// below) so a traversal attempt is indistinguishable from a missing file — no
|
|
86
|
+
// signal to a prober about what exists outside PUBLIC.
|
|
87
|
+
if (isInsideDir(PUBLIC, fullPath) && fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
|
|
81
88
|
const ext = path.extname(fullPath);
|
|
82
89
|
const mime = { '.html': 'text/html', '.css': 'text/css', '.js': 'text/javascript', '.svg': 'image/svg+xml' };
|
|
83
90
|
// HTML must never cache — board UI is iterated daily and stale layouts
|
|
@@ -102,15 +109,15 @@ const server = http.createServer(async (req, res) => {
|
|
|
102
109
|
|
|
103
110
|
// ── Start ──────────────────────────────────────────────────────────────────────
|
|
104
111
|
server.listen(PORT, HOST, () => {
|
|
105
|
-
|
|
112
|
+
log.info(`great_cto board → http://${HOST === '0.0.0.0' ? 'localhost' : HOST}:${PORT}`);
|
|
106
113
|
if (HOST !== '127.0.0.1' && HOST !== 'localhost') {
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
log.info(` ⚠ bound to ${HOST} — reachable beyond this machine. Operators authenticate via invite`);
|
|
115
|
+
log.info(` links; put your reverse-proxy auth in front for anything admin-grade.`);
|
|
109
116
|
}
|
|
110
117
|
// Discover all great_cto projects on disk asynchronously — don't block
|
|
111
118
|
// the listening event so /api/tasks is available immediately.
|
|
112
119
|
discoverProjects().then(n => {
|
|
113
|
-
if (n > 0)
|
|
120
|
+
if (n > 0) log.info(` → discovered ${n} project${n === 1 ? '' : 's'} with .great_cto/PROJECT.md`);
|
|
114
121
|
}).catch(() => {}); // non-fatal
|
|
115
122
|
watchBeads();
|
|
116
123
|
startAlertCron();
|
|
@@ -125,9 +132,9 @@ server.listen(PORT, HOST, () => {
|
|
|
125
132
|
|
|
126
133
|
server.on('error', e => {
|
|
127
134
|
if (e.code === 'EADDRINUSE') {
|
|
128
|
-
|
|
135
|
+
log.info(`Port ${PORT} in use — board already running at http://localhost:${PORT}`);
|
|
129
136
|
} else {
|
|
130
|
-
|
|
137
|
+
log.error(e);
|
|
131
138
|
}
|
|
132
139
|
process.exit(0);
|
|
133
140
|
});
|
package/dist/telemetry.js
CHANGED
|
@@ -27,9 +27,9 @@ import * as path from "node:path";
|
|
|
27
27
|
import * as os from "node:os";
|
|
28
28
|
import * as crypto from "node:crypto";
|
|
29
29
|
const TELEMETRY_ENDPOINT = process.env.GREAT_CTO_TELEMETRY_ENDPOINT
|
|
30
|
-
|| "https://
|
|
31
|
-
//
|
|
32
|
-
//
|
|
30
|
+
|| "https://telemetry.greatcto.systems/v1/event";
|
|
31
|
+
// Override anytime with GREAT_CTO_TELEMETRY_ENDPOINT (e.g. to point at a
|
|
32
|
+
// workers.dev URL during local worker development).
|
|
33
33
|
const TELEMETRY_TIMEOUT_MS = 1000;
|
|
34
34
|
// Allowlist — anything else is dropped client-side and server-side.
|
|
35
35
|
const ALLOWED_COMMANDS = new Set([
|
package/package.json
CHANGED