@prism-d1/cli 1.9.0 → 1.10.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.
Files changed (64) hide show
  1. package/README.md +48 -0
  2. package/dist/assets/github-workflows/prism-eval-gate-kiro.yml +133 -42
  3. package/dist/assets/gitlab-workflows/prism-eval-gate.yml +100 -35
  4. package/dist/assets/kirocrew-app/README.md +12 -9
  5. package/dist/assets/kirocrew-app/app.json +3 -1
  6. package/dist/assets/kirocrew-app/assets/screenshots/page-dark.png +0 -0
  7. package/dist/assets/kirocrew-app/assets/screenshots/page-light.png +0 -0
  8. package/dist/assets/kirocrew-app/assets/screenshots/report-dark.png +0 -0
  9. package/dist/assets/kirocrew-app/assets/screenshots/report-light.png +0 -0
  10. package/dist/bin/cli.d.ts +2 -0
  11. package/dist/bin/cli.d.ts.map +1 -0
  12. package/dist/bin/cli.js +19 -0
  13. package/dist/bin/cli.js.map +1 -0
  14. package/dist/bin/prism-cli.js +12 -17
  15. package/dist/bin/prism-cli.js.map +1 -1
  16. package/dist/src/commands/_top/dashboard.d.ts +2 -0
  17. package/dist/src/commands/_top/dashboard.d.ts.map +1 -1
  18. package/dist/src/commands/_top/dashboard.js +43 -15
  19. package/dist/src/commands/_top/dashboard.js.map +1 -1
  20. package/dist/src/commands/_top/mcp.d.ts.map +1 -1
  21. package/dist/src/commands/_top/mcp.js +3 -15
  22. package/dist/src/commands/_top/mcp.js.map +1 -1
  23. package/dist/src/commands/_top/statusline.d.ts +13 -0
  24. package/dist/src/commands/_top/statusline.d.ts.map +1 -0
  25. package/dist/src/commands/_top/statusline.js +12 -0
  26. package/dist/src/commands/_top/statusline.js.map +1 -0
  27. package/dist/src/commands/_top/version.d.ts +2 -0
  28. package/dist/src/commands/_top/version.d.ts.map +1 -1
  29. package/dist/src/commands/_top/version.js +4 -0
  30. package/dist/src/commands/_top/version.js.map +1 -1
  31. package/dist/src/commands/assessment/deploy-cdk.js +1 -1
  32. package/dist/src/commands/assessment/deploy-cdk.js.map +1 -1
  33. package/dist/src/commands/enable/claude-code.d.ts +81 -0
  34. package/dist/src/commands/enable/claude-code.d.ts.map +1 -0
  35. package/dist/src/commands/enable/claude-code.js +381 -0
  36. package/dist/src/commands/enable/claude-code.js.map +1 -0
  37. package/dist/src/dashboard/page.d.ts +25 -0
  38. package/dist/src/dashboard/page.d.ts.map +1 -1
  39. package/dist/src/dashboard/page.js +66 -7
  40. package/dist/src/dashboard/page.js.map +1 -1
  41. package/dist/src/dashboard/setup-page.d.ts +1 -1
  42. package/dist/src/dashboard/setup-page.d.ts.map +1 -1
  43. package/dist/src/dashboard/setup-page.js +15 -2
  44. package/dist/src/dashboard/setup-page.js.map +1 -1
  45. package/dist/src/dashboard/snapshot.d.ts +31 -0
  46. package/dist/src/dashboard/snapshot.d.ts.map +1 -0
  47. package/dist/src/dashboard/snapshot.js +65 -0
  48. package/dist/src/dashboard/snapshot.js.map +1 -0
  49. package/dist/src/mcp/dashboard-process.d.ts +83 -0
  50. package/dist/src/mcp/dashboard-process.d.ts.map +1 -0
  51. package/dist/src/mcp/dashboard-process.js +184 -0
  52. package/dist/src/mcp/dashboard-process.js.map +1 -0
  53. package/dist/src/mcp/server.d.ts.map +1 -1
  54. package/dist/src/mcp/server.js +8 -3
  55. package/dist/src/mcp/server.js.map +1 -1
  56. package/dist/src/mcp/tools.d.ts +7 -1
  57. package/dist/src/mcp/tools.d.ts.map +1 -1
  58. package/dist/src/mcp/tools.js +100 -2
  59. package/dist/src/mcp/tools.js.map +1 -1
  60. package/dist/src/statusline/statusline.d.ts +135 -0
  61. package/dist/src/statusline/statusline.d.ts.map +1 -0
  62. package/dist/src/statusline/statusline.js +398 -0
  63. package/dist/src/statusline/statusline.js.map +1 -0
  64. package/package.json +3 -1
@@ -12,5 +12,5 @@ export declare const THEME_BOOT = "(function(){try{var t=localStorage.getItem('p
12
12
  /** The header toggle: Light, Dark or System (no data-theme; follows prefers-color-scheme). Saved in localStorage only. */
13
13
  export declare const THEME_SCRIPT = "\nconst THEME_KEY = 'prism-theme';\nfunction storedTheme() { try { const t = localStorage.getItem(THEME_KEY); return t === 'light' || t === 'dark' ? t : 'system'; } catch (e) { return 'system'; } }\nfunction applyTheme(t) {\n const root = document.documentElement;\n if (t === 'light' || t === 'dark') root.setAttribute('data-theme', t); else root.removeAttribute('data-theme');\n for (const b of document.querySelectorAll('[data-theme-set]')) b.setAttribute('aria-pressed', String(b.getAttribute('data-theme-set') === t));\n}\nfunction setTheme(t) {\n const v = t === 'light' || t === 'dark' ? t : 'system';\n try { if (v === 'system') localStorage.removeItem(THEME_KEY); else localStorage.setItem(THEME_KEY, v); } catch (e) { /* storage off: applies for this page only */ }\n applyTheme(v);\n}\nfor (const b of document.querySelectorAll('[data-theme-set]')) b.addEventListener('click', () => setTheme(b.getAttribute('data-theme-set')));\napplyTheme(storedTheme());\n";
14
14
  export declare const SETUP_STYLE = "\n[hidden] { display:none !important; }\nnav.tabs { display:flex; gap:4px; }\nnav.tabs a { color:var(--muted); text-decoration:none; padding:4px 10px; border-radius:6px; border:1px solid transparent; }\nnav.tabs a[aria-current=\"page\"] { color:var(--text); background:var(--bg); border-color:var(--line); font-weight:600; }\na:focus-visible, button:focus-visible, input:focus-visible, summary:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }\n.theme { display:inline-flex; border:1px solid var(--line); border-radius:6px; overflow:hidden; }\n.theme button { border:none; border-radius:0; background:var(--card); color:var(--muted); padding:4px 9px; }\n.theme button[aria-pressed=\"true\"] { background:var(--bg); color:var(--text); font-weight:600; }\n.srow { padding:8px 0; border-top:1px solid var(--line); overflow-wrap:anywhere; } .srow:first-of-type { border-top:none; }\n.srow-head { display:flex; gap:8px; align-items:baseline; flex-wrap:wrap; }\n.srow-ctl { display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-top:6px; }\n.cmdline { display:inline-flex; gap:6px; align-items:center; flex-wrap:wrap; max-width:100%; } .cmdline code { white-space:normal; overflow-wrap:anywhere; }\n.badge { font-size:11px; padding:0 6px; border-radius:8px; border:1px solid var(--line); color:var(--muted); background:var(--raised); white-space:nowrap; }\nbutton.run { background:var(--accent); border-color:var(--accent); color:var(--card); font-weight:600; }\nbutton.run.danger { background:var(--card); border-color:var(--bad); color:var(--bad); }\nbutton:disabled { opacity:.55; cursor:not-allowed; }\ninput.url { font:inherit; padding:4px 8px; border:1px solid var(--line); border-radius:6px; background:var(--bg); color:var(--text); min-width:min(320px,100%); flex:1; }\ninput.url::placeholder { color:var(--muted); }\n.msg { color:var(--bad); font-size:12px; }\nol.steps { margin:4px 0; padding-left:20px; } ol.steps li { padding:3px 0; overflow-wrap:anywhere; }\ndl.kv { display:grid; grid-template-columns:8em minmax(0,1fr); gap:2px 10px; font-size:13px; margin:6px 0; overflow-wrap:anywhere; } dl.kv dt { color:var(--muted); } dl.kv dd { margin:0; }\ndetails.copies > summary { cursor:pointer; color:var(--muted); font-size:12px; padding:4px 0; } details.copies > div { padding:3px 0; }\ndialog.confirm { background:var(--card); color:var(--text); border:1px solid var(--line); border-radius:10px; padding:16px 18px; width:min(640px, 92vw); box-shadow:0 10px 30px rgb(0 0 0 / .35); }\ndialog.confirm::backdrop { background:rgb(0 0 0 / .5); }\ndialog.confirm h2 { margin-bottom:6px; } dialog.confirm p { margin:6px 0; }\ndialog.confirm .actions { display:flex; gap:8px; justify-content:flex-end; margin-top:14px; }\npre.cmd, pre.out { font-family:ui-monospace, SFMono-Regular, Menlo, monospace; font-size:12px; background:var(--bg); color:var(--text); border:1px solid var(--line); border-radius:6px; padding:8px 10px; white-space:pre-wrap; overflow-wrap:anywhere; margin:6px 0; }\npre.out { max-height:420px; overflow:auto; }\nlabel.extra { display:flex; gap:6px; align-items:flex-start; margin-top:10px; color:var(--bad); font-weight:600; }\n";
15
- export declare const SETUP_SCRIPT = "\nconst CSRF = (document.querySelector('meta[name=\"prism-csrf\"]') || {}).content || '';\nlet SETUP = null, POLL = null, DIALOG = null, CHECK = null, URL_DRAFT = '';\nconst OUT = { status: el('div', { class: 'srow-head' }), pre: el('pre', { class: 'out', 'aria-live': 'polite' }) };\nconst ACTION_PANEL = el('section', { class: 'card wide', hidden: '' }, el('h2', {}, 'Action output'), OUT.status, OUT.pre,\n el('p', { class: 'note' }, 'Secrets are masked. Each run is logged to ~/.prism/logs/dashboard-actions.log (time, action, repo, exit code).'));\n\nfunction currentView() { return location.hash === '#setup' ? 'setup' : 'dashboard'; }\nfunction showView() {\n const v = currentView();\n document.getElementById('main').hidden = v !== 'dashboard';\n document.getElementById('setup').hidden = v !== 'setup';\n for (const a of document.querySelectorAll('[data-view]')) {\n if (a.getAttribute('data-view') === v) a.setAttribute('aria-current', 'page'); else a.removeAttribute('aria-current');\n }\n}\nwindow.addEventListener('hashchange', showView);\nshowView();\n\n/** GET, or POST JSON with the CSRF header. referrerPolicy same-origin so the browser sends this page's Origin. */\nasync function api(path, body) {\n const init = body === undefined ? { credentials: 'same-origin', cache: 'no-store' } : {\n method: 'POST', credentials: 'same-origin', cache: 'no-store', referrerPolicy: 'same-origin',\n headers: { 'Content-Type': 'application/json', 'X-Prism-CSRF': CSRF }, body: JSON.stringify(body),\n };\n const res = await fetch(path, init);\n let json = null;\n try { json = await res.json(); } catch (e) { json = null; }\n return { ok: res.ok, status: res.status, json: json || { error: 'HTTP ' + res.status } };\n}\n\nfunction ago(iso) {\n const t = Date.parse(iso);\n if (isNaN(t)) return 'unknown';\n const s = Math.max(0, (Date.now() - t) / 1000);\n for (const [n, w] of [[86400, 'day'], [3600, 'hour'], [60, 'minute']]) {\n if (s >= n) { const k = Math.floor(s / n); return k + ' ' + w + (k === 1 ? '' : 's') + ' ago'; }\n }\n return 'just now';\n}\n\nfunction copyButton(cmd) {\n const b = el('button', { type: 'button', title: 'Copy to the clipboard' }, 'Copy');\n b.addEventListener('click', async () => {\n try { await navigator.clipboard.writeText(cmd); b.textContent = 'Copied'; } catch (e) { b.textContent = 'Select the text to copy'; }\n setTimeout(() => { b.textContent = 'Copy'; }, 1500);\n });\n return el('span', { class: 'cmdline' }, el('code', {}, cmd), b);\n}\nconst copyOnly = (cmd, why) => el('div', { class: 'srow-ctl' }, copyButton(cmd), why ? el('span', { class: 'badge' }, why) : null);\n\n/** Client-side copy of the server's collector URL rule; the server checks again. */\nfunction urlProblem(s) {\n if (!s) return 'enter the collector URL';\n if (s.length > 2048) return 'the URL is longer than 2048 characters';\n let u;\n try { u = new URL(s); } catch (e) { return 'not a valid URL'; }\n if (u.protocol !== 'https:') return 'the URL must use https';\n if (!u.hostname) return 'the URL has no hostname';\n if (u.username || u.password) return 'the URL must not contain a user name or password';\n return null;\n}\n\nconst actionDef = (id) => SETUP && (SETUP.actions || []).find((a) => a.id === id);\n\n/** A Run button for an allowlisted action; null when the action is not available here. */\nfunction runButton(id, o) {\n const a = actionDef(id);\n if (!a || !a.available) return null;\n o = o || {};\n const b = el('button', { type: 'button', class: 'run' + (a.confirm ? ' danger' : '') }, o.label || 'Run');\n b.addEventListener('click', () => {\n let url;\n if (a.url) {\n url = o.urlInput ? o.urlInput.value : '';\n const p = urlProblem(url);\n if (p) { if (o.msg) o.msg.textContent = p; return; }\n if (o.msg) o.msg.textContent = '';\n }\n confirmRun(a, { repo: o.repo, where: o.where, url });\n });\n return b;\n}\n\nfunction confirmRun(a, o) {\n if (!DIALOG) { DIALOG = el('dialog', { class: 'confirm', 'aria-labelledby': 'confirm-title' }); document.body.append(DIALOG); }\n const cmd = a.command + (o.url ? ' ' + o.url : '');\n const run = el('button', { type: 'button', class: 'run' + (a.confirm ? ' danger' : '') }, 'Run');\n const cancel = el('button', { type: 'button' }, 'Cancel');\n const kids = [el('h2', { id: 'confirm-title' }, a.label), el('p', {}, 'This will run:'), el('pre', { class: 'cmd' }, cmd),\n el('p', { class: 'fix' }, 'in ' + txt(o.where || (SETUP && SETUP.cwd) || '')),\n el('p', { class: 'note' }, a.readOnlyAws ? 'Read-only in AWS: describe, get and list calls only. Nothing in AWS is changed.' : 'Changes setup on this machine only. No AWS calls.'),\n el('p', { class: 'note' }, 'It runs without a terminal, so it cannot ask questions; it is stopped after ' + Math.max(1, Math.round(a.timeoutSec / 60)) + ' min.')];\n if (a.confirm) {\n const cb = el('input', { type: 'checkbox' });\n run.disabled = true;\n cb.addEventListener('change', () => { run.disabled = !cb.checked; });\n kids.push(el('label', { class: 'extra' }, cb, 'Yes, remove the scheduled codeburn sync push from this machine'));\n }\n kids.push(el('div', { class: 'actions' }, cancel, run));\n DIALOG.replaceChildren(...kids);\n cancel.addEventListener('click', () => DIALOG.close());\n run.addEventListener('click', () => { DIALOG.close(); startAction(a, o); });\n DIALOG.showModal();\n cancel.focus();\n}\n\nasync function startAction(a, o) {\n const body = { action: a.id };\n if (a.repo) body.repo = o.repo;\n if (a.url) body.url = o.url;\n if (a.confirm) body.confirm = true;\n ACTION_PANEL.hidden = false;\n OUT.status.replaceChildren(pill('warn', 'starting'), el('code', {}, a.command));\n OUT.pre.textContent = '';\n let r;\n try { r = await api('/api/action', body); } catch (e) { r = { ok: false, json: { error: e.message } }; }\n if (!r.ok) { OUT.status.replaceChildren(pill('error', 'refused'), txt(r.json.error)); return; }\n showStatus(r.json);\n ACTION_PANEL.scrollIntoView({ block: 'nearest' });\n poll();\n}\n\nfunction showStatus(s) {\n if (!s || s.state === 'idle') return;\n ACTION_PANEL.hidden = false;\n const p = s.state === 'running' ? pill('warn', 'running') : s.exitCode === 0 ? pill('ok', 'exit 0') : pill('error', s.timedOut ? 'timed out' : 'exit ' + s.exitCode);\n OUT.status.replaceChildren(p, el('code', {}, txt(s.command)), el('span', { class: 'fix' }, (s.repo ? 'in ' + txt(s.repo) + ' \u00B7 ' : '') + 'started ' + when(s.startedAt)));\n OUT.pre.textContent = txt(s.output) || (s.state === 'running' ? '(no output yet)' : '(no output)');\n OUT.pre.scrollTop = OUT.pre.scrollHeight;\n}\n\nfunction poll() {\n clearTimeout(POLL);\n POLL = setTimeout(async () => {\n try {\n const r = await api('/api/action/status');\n if (r.ok) showStatus(r.json);\n if (r.ok && r.json.state !== 'running') { load(); return; }\n } catch (e) { /* retry */ }\n poll();\n }, 1000);\n}\n\nfunction srow(status, label, detail, ...ctl) {\n const c = ctl.filter(Boolean);\n return el('div', { class: 'srow' }, el('div', { class: 'srow-head' }, status, el('b', {}, label)),\n detail ? el('div', { class: 'fix' }, detail) : null, c.length ? el('div', { class: 'srow-ctl' }, c) : null);\n}\n\nfunction machineSection(data) {\n const s = el('section', { class: 'card' }, el('h2', {}, 'This machine'));\n for (const i of data.machine.setup) {\n if (i.id === 'codeburn-sync' || i.id === 'aws-credentials') continue;\n let ctl = null;\n if (i.id === 'codewatch-global' && i.status !== 'ok') {\n const elsewhere = (data.machine.globalHooks || {}).state === 'elsewhere';\n ctl = elsewhere ? copyOnly('prism-cli enable codewatch --global --force', 'replaces another global hooks path')\n : runButton('codewatch-global', { label: 'Enable for every repo' });\n } else if (i.fix) {\n ctl = copyOnly(i.fix, i.id === 'gitleaks' ? 'installs software' : i.id === 'codeburn' ? 'asks before installing' : null);\n }\n s.append(srow(pill(i.status), i.label, i.detail, ctl));\n }\n s.append(el('p', { class: 'note' }, 'AWS credentials: see the AWS section.'));\n return s;\n}\n\nconst CW_PILL = { ok: 'ok', warn: 'warn', missing: 'missing', 'opted-out': 'skipped' };\nfunction reposSection(data) {\n const s = el('section', { class: 'card wide' }, el('h2', {}, 'Repos'));\n const recheck = el('button', { type: 'button' }, 'Re-check status');\n recheck.addEventListener('click', async () => { recheck.disabled = true; try { await api('/api/recheck'); } catch (e) { /* load() reports */ } await load(); });\n s.append(el('div', { class: 'srow-ctl' }, recheck, el('span', { class: 'fix' }, 'Codewatch hooks and Security Agent setup per repo, from local files.')));\n const ids = SETUP.repoIds || {}, cds = SETUP.repoCd || {}, cp = SETUP.copy || {};\n const rowOf = (r) => {\n const cw = el('div', {}, pill(CW_PILL[r.status] || 'missing', r.status === 'opted-out' ? 'opted out' : r.status + (r.status === 'ok' && r.global ? ' (global)' : '')), ' ',\n el('span', { class: 'fix' }, r.status === 'ok' ? 'reviewer ' + txt(r.reviewer || 'none') : txt(r.detail)));\n if ((r.status === 'missing' || r.status === 'warn') && ids[r.path]) cw.append(el('div', { class: 'srow-ctl' }, runButton('codewatch-repo', { repo: ids[r.path], where: r.path, label: 'Enable codewatch' })));\n const sec = r.security || { status: 'missing' };\n const sc = el('div', {}, pill(sec.status === 'missing' ? 'missing' : 'ok', sec.status === 'in-use' ? 'in use' : sec.status));\n const cd = cds[r.path] ? cds[r.path] + ' && ' : '';\n if (sec.status === 'missing') sc.append(copyOnly(sec.fix || cd + cp.enableSecurity, 'AWS'));\n else sc.append(el('details', { class: 'copies' }, el('summary', {}, 'Security Agent commands (AWS, copy only)'),\n [cp.codeReview, cp.threatModel, cp.pentest].map((c) => el('div', {}, copyButton(cd + c)))));\n return [repoName(r), cw, sc];\n };\n const rows = data.overview.setup || [];\n const shown = rows.filter((r) => !r.collapsed), hidden = rows.filter((r) => r.collapsed);\n if (shown.length) s.append(table(['Repo', 'Codewatch', 'Security Agent'], shown.map(rowOf)));\n else if (!hidden.length) s.append(el('p', { class: 'empty' }, 'No git repositories found under ' + txt(data.scope.root_display) + '.'));\n if (hidden.length) s.append(el('details', { class: 'missing-group', 'data-key': 'setup-view:other' },\n el('summary', {}, hidden.length + ' other repo' + (hidden.length === 1 ? '' : 's') + ': not set up'), table(['Repo', 'Codewatch', 'Security Agent'], hidden.map(rowOf))));\n return s;\n}\n\nfunction syncSection(data) {\n const s = el('section', { class: 'card' }, el('h2', {}, 'Usage sync (codeburn)'));\n const item = data.machine.setup.find((i) => i.id === 'codeburn-sync');\n const mode = (data.machine.codeburnSync || {}).mode || 'local';\n if (item) s.append(srow(pill(item.status), item.label, item.detail));\n if (item && item.help) s.append(helpBlock(item.help));\n s.append(el('p', { class: 'fix' }, 'Sync mode: ' + mode));\n s.append(srow(null, 'Local only', 'codeburn usage stays on this machine; no endpoint and no schedule.', runButton('codeburn-local', { label: 'Keep usage local' })));\n const input = el('input', { type: 'url', class: 'url', placeholder: 'collector URL (https, from your platform team)', maxlength: '2048', 'aria-label': 'Collector URL', spellcheck: 'false', autocomplete: 'off' });\n input.value = URL_DRAFT;\n input.addEventListener('input', () => { URL_DRAFT = input.value; });\n const msg = el('span', { class: 'msg', role: 'status' });\n s.append(srow(null, \"Sync to my team's PRISM\", 'Sets up codeburn sync for the collector URL and schedules the push. codeburn opens a browser to sign in.',\n input, runButton('codeburn-team', { label: 'Sync to team', urlInput: input, msg }), msg));\n if (actionDef('codeburn-systemd') && actionDef('codeburn-systemd').available) {\n s.append(srow(null, 'Use a systemd user timer', mode === 'team' ? 'Schedules the sync push with a systemd user timer; your crontab is not touched.' : 'Team mode only: set up sync to your team first.',\n mode === 'team' ? runButton('codeburn-systemd', { label: 'Use systemd timer' }) : null));\n }\n s.append(srow(null, 'Remove the sync schedule', 'Removes the scheduled sync push (cron entry or systemd timer). codeburn and its sync config stay.', runButton('codeburn-remove', { label: 'Remove schedule' })));\n s.append(srow(null, 'Status', 'codeburn, sync and schedule status.', runButton('codeburn-status', { label: 'Show status' })));\n s.append(srow(null, 'Platform team: add a developer', 'Creates a telemetry account in AWS (Cognito), so it is copy only.', copyOnly((SETUP.copy || {}).addDeveloper, 'AWS write')));\n return s;\n}\n\nconst STEP_PILL = { used: ['ok', 'used'], skipped: ['skipped', 'skipped'], 'not-set': ['info', 'not set'], 'n/a': ['skipped', 'n/a'] };\nfunction kv(pairs) { return el('dl', { class: 'kv' }, pairs.filter(Boolean).flatMap(([k, v]) => [el('dt', {}, k), el('dd', {}, v)])); }\n\nfunction checkResult(r) {\n if (!r) return null;\n if (!r.ok) return el('div', {}, pill('error', 'refused'), ' ', txt(r.json.error));\n const j = r.json;\n if (j.ok) {\n const m = j.matches === true ? pill('ok', 'matches the saved account') : j.matches === false ? pill('error', 'does not match saved account ' + txt(j.savedAccount)) : pill('info', 'no saved account');\n return el('div', {}, pill('ok', 'credentials work'), kv([['Account', txt(j.account)], ['Caller', txt(j.arn)], ['Region', txt(j.region || 'not set')], ['Saved', m]]), el('p', { class: 'note' }, txt(j.note)));\n }\n return el('div', {}, pill('error', j.expired ? 'expired or invalid' : 'check failed'), ' ', el('span', {}, txt(j.error)),\n el('div', { class: 'help' }, 'Fixes:', el('ul', { class: 'help-steps' }, (j.fixes || []).map((f) => el('li', {}, /^(aws|prism-cli) /.test(f) ? copyButton(f) : txt(f))))));\n}\n\nfunction awsSection(a) {\n const s = el('section', { class: 'card wide' }, el('h2', {}, 'AWS'));\n s.append(el('p', { class: 'note' }, 'From the environment and local config files only: no AWS call until you press Check now. Only setting names are read from ~/.aws; secret values are never read or shown.'));\n s.append(el('div', { class: 'srow' }, el('div', {}, 'prism-cli would use ', el('b', {}, a.profile ? 'profile ' + txt(a.profile) : txt(a.source)), ' \u00B7 ', el('span', { class: 'badge' }, txt(a.credentialLabel))),\n el('div', { class: 'fix' }, 'chosen by ' + txt(a.source) + ' \u00B7 region ' + (a.region ? txt(a.region.value) + ' (' + txt(a.region.source) + ')' : 'not set'))));\n for (const w of a.warnings || []) s.append(el('div', { class: 'callout warn', role: 'note' }, txt(w)));\n s.append(el('h3', {}, 'Resolution order'), el('ol', { class: 'steps' }, (a.steps || []).map((st) => {\n const p = STEP_PILL[st.state] || ['skipped', st.state];\n return el('li', {}, el('b', {}, txt(st.label)), ' ', pill(p[0], p[1]), ' ', el('span', { class: 'fix' }, txt(st.detail)));\n })));\n s.append(el('h3', {}, 'Saved aws connect'));\n if (a.saved) {\n const v = a.saved;\n s.append(kv([['Account', el('span', {}, txt(v.account), v.alias ? el('span', { class: 'tag' }, txt(v.alias)) : null)], ['Profile', txt(v.profile) + (v.inUse ? '' : ' (not in use)')],\n ['Region', txt(v.region)], ['Verified', v.verifiedAt ? el('span', { title: when(v.verifiedAt) }, ago(v.verifiedAt)) : 'unknown'], ['File', txt(v.path) + ' (' + v.scope + ')'],\n ['Admin / org', 'not stored by aws connect (it prints them when it runs)']]));\n } else s.append(el('p', { class: 'empty-line' }, 'None saved. ', copyButton((SETUP.copy || {}).awsConnect)));\n const btn = el('button', { type: 'button', class: 'run' }, 'Check now');\n const out = el('div', { 'aria-live': 'polite' }, checkResult(CHECK));\n btn.addEventListener('click', async () => {\n btn.disabled = true;\n out.replaceChildren(el('span', { class: 'fix' }, 'checking...'));\n try { CHECK = await api('/api/check-now', {}); } catch (e) { CHECK = { ok: false, json: { error: e.message } }; }\n out.replaceChildren(checkResult(CHECK));\n btn.disabled = false;\n if (CHECK.ok && CHECK.json.updated) load();\n });\n s.append(el('h3', {}, 'Check now'), el('div', { class: 'srow-ctl' }, btn, el('span', { class: 'fix' }, 'One read-only call: aws sts get-caller-identity, as aws connect does it.')), out);\n s.append(el('h3', {}, 'Read-only AWS commands'),\n srow(null, 'Account check', 'Every call is a describe, get or list; the result is saved to .prism/results/.', runButton('aws-check', { label: 'Run aws check' })),\n srow(null, 'Team stack info', 'Reads the PRISM stack outputs (collector URL, user pool).', runButton('team-info', { label: 'Run team info' })));\n const cp = SETUP.copy || {};\n s.append(el('h3', {}, 'Copy only (writes to AWS or needs a terminal)'),\n srow(null, 'Choose and save the AWS profile', 'Asks questions on a terminal.', copyOnly(cp.awsConnect, 'terminal')),\n srow(null, 'Set up the Security Agent in a repo', 'Creates AWS resources.', copyOnly(cp.enableSecurity, 'AWS write')),\n srow(null, 'Add a developer telemetry account', 'Creates a Cognito user.', copyOnly(cp.addDeveloper, 'AWS write')),\n srow(null, 'Security Agent runs', 'Uploads code and starts AWS jobs.', copyOnly(cp.codeReview, 'AWS write'), copyOnly(cp.threatModel, 'AWS write'), copyOnly(cp.pentest, 'AWS write')));\n return s;\n}\n\nasync function renderSetup(data) {\n try { const r = await api('/api/setup'); if (r.ok) SETUP = r.json; } catch (e) { /* keep the last good copy */ }\n const root = document.getElementById('setup');\n const openState = new Map([...root.querySelectorAll('details[data-key]')].map((d) => [d.getAttribute('data-key'), d.open]));\n root.replaceChildren();\n if (!SETUP) { root.append(el('section', { class: 'card wide' }, pill('error'), ' could not load the setup data')); return; }\n root.append(machineSection(data), syncSection(data), reposSection(data), awsSection(SETUP.aws || {}), ACTION_PANEL);\n for (const d of root.querySelectorAll('details[data-key]')) { const k = d.getAttribute('data-key'); if (openState.has(k)) d.open = openState.get(k); }\n}\n// A reload while an action runs picks its output back up.\napi('/api/action/status').then((r) => { if (r.ok && r.json.state === 'running') { showStatus(r.json); poll(); } }).catch(() => {});\n";
15
+ export declare const SETUP_SCRIPT = "\nconst CSRF = (document.querySelector('meta[name=\"prism-csrf\"]') || {}).content || '';\nlet SETUP = null, POLL = null, DIALOG = null, CHECK = null, URL_DRAFT = '';\nconst OUT = { status: el('div', { class: 'srow-head' }), pre: el('pre', { class: 'out', 'aria-live': 'polite' }) };\nconst ACTION_PANEL = el('section', { class: 'card wide', hidden: '' }, el('h2', {}, 'Action output'), OUT.status, OUT.pre,\n el('p', { class: 'note' }, 'Secrets are masked. Each run is logged to ~/.prism/logs/dashboard-actions.log (time, action, repo, exit code).'));\n\nfunction currentView() { return location.hash === '#setup' ? 'setup' : 'dashboard'; }\nfunction showView() {\n const v = currentView();\n document.getElementById('main').hidden = v !== 'dashboard';\n document.getElementById('setup').hidden = v !== 'setup';\n for (const a of document.querySelectorAll('[data-view]')) {\n if (a.getAttribute('data-view') === v) a.setAttribute('aria-current', 'page'); else a.removeAttribute('aria-current');\n }\n}\nwindow.addEventListener('hashchange', showView);\nshowView();\n\n/** GET, or POST JSON with the CSRF header. referrerPolicy same-origin so the browser sends this page's Origin. */\nasync function api(path, body) {\n // A static snapshot answers the two reads it embeds and refuses everything else; it never fetches.\n if (SNAPSHOT) {\n if (body === undefined && path === '/api/setup') return { ok: true, status: 200, json: SNAPSHOT.setup };\n if (body === undefined && path === '/api/action/status') return { ok: true, status: 200, json: { state: 'idle' } };\n return { ok: false, status: 0, json: { error: 'this is a static snapshot: nothing runs from it' } };\n }\n const init = body === undefined ? { credentials: 'same-origin', cache: 'no-store' } : {\n method: 'POST', credentials: 'same-origin', cache: 'no-store', referrerPolicy: 'same-origin',\n headers: { 'Content-Type': 'application/json', 'X-Prism-CSRF': CSRF }, body: JSON.stringify(body),\n };\n const res = await fetch(path, init);\n let json = null;\n try { json = await res.json(); } catch (e) { json = null; }\n return { ok: res.ok, status: res.status, json: json || { error: 'HTTP ' + res.status } };\n}\n\nfunction ago(iso) {\n const t = Date.parse(iso);\n if (isNaN(t)) return 'unknown';\n const s = Math.max(0, (Date.now() - t) / 1000);\n for (const [n, w] of [[86400, 'day'], [3600, 'hour'], [60, 'minute']]) {\n if (s >= n) { const k = Math.floor(s / n); return k + ' ' + w + (k === 1 ? '' : 's') + ' ago'; }\n }\n return 'just now';\n}\n\nfunction copyButton(cmd) {\n const b = el('button', { type: 'button', title: 'Copy to the clipboard' }, 'Copy');\n b.addEventListener('click', async () => {\n try { await navigator.clipboard.writeText(cmd); b.textContent = 'Copied'; } catch (e) { b.textContent = 'Select the text to copy'; }\n setTimeout(() => { b.textContent = 'Copy'; }, 1500);\n });\n return el('span', { class: 'cmdline' }, el('code', {}, cmd), b);\n}\nconst copyOnly = (cmd, why) => el('div', { class: 'srow-ctl' }, copyButton(cmd), why ? el('span', { class: 'badge' }, why) : null);\n\n/** Client-side copy of the server's collector URL rule; the server checks again. */\nfunction urlProblem(s) {\n if (!s) return 'enter the collector URL';\n if (s.length > 2048) return 'the URL is longer than 2048 characters';\n let u;\n try { u = new URL(s); } catch (e) { return 'not a valid URL'; }\n if (u.protocol !== 'https:') return 'the URL must use https';\n if (!u.hostname) return 'the URL has no hostname';\n if (u.username || u.password) return 'the URL must not contain a user name or password';\n return null;\n}\n\nconst actionDef = (id) => SETUP && (SETUP.actions || []).find((a) => a.id === id);\n\n/** A Run button for an allowlisted action; null when the action is not available here. */\nfunction runButton(id, o) {\n const a = actionDef(id);\n if (!a || !a.available) return null;\n o = o || {};\n if (SNAPSHOT) {\n const cd = o.where && SETUP.repoCd && SETUP.repoCd[o.where];\n return copyOnly((cd ? cd + ' && ' : '') + a.command + (a.url ? ' <collector-url>' : ''), 'snapshot: copy only');\n }\n const b = el('button', { type: 'button', class: 'run' + (a.confirm ? ' danger' : '') }, o.label || 'Run');\n b.addEventListener('click', () => {\n let url;\n if (a.url) {\n url = o.urlInput ? o.urlInput.value : '';\n const p = urlProblem(url);\n if (p) { if (o.msg) o.msg.textContent = p; return; }\n if (o.msg) o.msg.textContent = '';\n }\n confirmRun(a, { repo: o.repo, where: o.where, url });\n });\n return b;\n}\n\nfunction confirmRun(a, o) {\n if (!DIALOG) { DIALOG = el('dialog', { class: 'confirm', 'aria-labelledby': 'confirm-title' }); document.body.append(DIALOG); }\n const cmd = a.command + (o.url ? ' ' + o.url : '');\n const run = el('button', { type: 'button', class: 'run' + (a.confirm ? ' danger' : '') }, 'Run');\n const cancel = el('button', { type: 'button' }, 'Cancel');\n const kids = [el('h2', { id: 'confirm-title' }, a.label), el('p', {}, 'This will run:'), el('pre', { class: 'cmd' }, cmd),\n el('p', { class: 'fix' }, 'in ' + txt(o.where || (SETUP && SETUP.cwd) || '')),\n el('p', { class: 'note' }, a.readOnlyAws ? 'Read-only in AWS: describe, get and list calls only. Nothing in AWS is changed.' : 'Changes setup on this machine only. No AWS calls.'),\n el('p', { class: 'note' }, 'It runs without a terminal, so it cannot ask questions; it is stopped after ' + Math.max(1, Math.round(a.timeoutSec / 60)) + ' min.')];\n if (a.confirm) {\n const cb = el('input', { type: 'checkbox' });\n run.disabled = true;\n cb.addEventListener('change', () => { run.disabled = !cb.checked; });\n kids.push(el('label', { class: 'extra' }, cb, 'Yes, remove the scheduled codeburn sync push from this machine'));\n }\n kids.push(el('div', { class: 'actions' }, cancel, run));\n DIALOG.replaceChildren(...kids);\n cancel.addEventListener('click', () => DIALOG.close());\n run.addEventListener('click', () => { DIALOG.close(); startAction(a, o); });\n DIALOG.showModal();\n cancel.focus();\n}\n\nasync function startAction(a, o) {\n const body = { action: a.id };\n if (a.repo) body.repo = o.repo;\n if (a.url) body.url = o.url;\n if (a.confirm) body.confirm = true;\n ACTION_PANEL.hidden = false;\n OUT.status.replaceChildren(pill('warn', 'starting'), el('code', {}, a.command));\n OUT.pre.textContent = '';\n let r;\n try { r = await api('/api/action', body); } catch (e) { r = { ok: false, json: { error: e.message } }; }\n if (!r.ok) { OUT.status.replaceChildren(pill('error', 'refused'), txt(r.json.error)); return; }\n showStatus(r.json);\n ACTION_PANEL.scrollIntoView({ block: 'nearest' });\n poll();\n}\n\nfunction showStatus(s) {\n if (!s || s.state === 'idle') return;\n ACTION_PANEL.hidden = false;\n const p = s.state === 'running' ? pill('warn', 'running') : s.exitCode === 0 ? pill('ok', 'exit 0') : pill('error', s.timedOut ? 'timed out' : 'exit ' + s.exitCode);\n OUT.status.replaceChildren(p, el('code', {}, txt(s.command)), el('span', { class: 'fix' }, (s.repo ? 'in ' + txt(s.repo) + ' \u00B7 ' : '') + 'started ' + when(s.startedAt)));\n OUT.pre.textContent = txt(s.output) || (s.state === 'running' ? '(no output yet)' : '(no output)');\n OUT.pre.scrollTop = OUT.pre.scrollHeight;\n}\n\nfunction poll() {\n clearTimeout(POLL);\n POLL = setTimeout(async () => {\n try {\n const r = await api('/api/action/status');\n if (r.ok) showStatus(r.json);\n if (r.ok && r.json.state !== 'running') { load(); return; }\n } catch (e) { /* retry */ }\n poll();\n }, 1000);\n}\n\nfunction srow(status, label, detail, ...ctl) {\n const c = ctl.filter(Boolean);\n return el('div', { class: 'srow' }, el('div', { class: 'srow-head' }, status, el('b', {}, label)),\n detail ? el('div', { class: 'fix' }, detail) : null, c.length ? el('div', { class: 'srow-ctl' }, c) : null);\n}\n\nfunction machineSection(data) {\n const s = el('section', { class: 'card' }, el('h2', {}, 'This machine'));\n for (const i of data.machine.setup) {\n if (i.id === 'codeburn-sync' || i.id === 'aws-credentials') continue;\n let ctl = null;\n if (i.id === 'codewatch-global' && i.status !== 'ok') {\n const elsewhere = (data.machine.globalHooks || {}).state === 'elsewhere';\n ctl = elsewhere ? copyOnly('prism-cli enable codewatch --global --force', 'replaces another global hooks path')\n : runButton('codewatch-global', { label: 'Enable for every repo' });\n } else if (i.fix) {\n ctl = copyOnly(i.fix, i.id === 'gitleaks' ? 'installs software' : i.id === 'codeburn' ? 'asks before installing' : null);\n }\n s.append(srow(pill(i.status), i.label, i.detail, ctl));\n }\n s.append(el('p', { class: 'note' }, 'AWS credentials: see the AWS section.'));\n return s;\n}\n\nconst CW_PILL = { ok: 'ok', warn: 'warn', missing: 'missing', 'opted-out': 'skipped' };\nfunction reposSection(data) {\n const s = el('section', { class: 'card wide' }, el('h2', {}, 'Repos'));\n const recheck = el('button', { type: 'button', disabled: SNAPSHOT ? '' : null, title: SNAPSHOT ? 'Not in a static snapshot' : null }, 'Re-check status');\n recheck.addEventListener('click', async () => { recheck.disabled = true; try { await api('/api/recheck'); } catch (e) { /* load() reports */ } await load(); });\n s.append(el('div', { class: 'srow-ctl' }, recheck, el('span', { class: 'fix' }, 'Codewatch hooks and Security Agent setup per repo, from local files.')));\n const ids = SETUP.repoIds || {}, cds = SETUP.repoCd || {}, cp = SETUP.copy || {};\n const rowOf = (r) => {\n const cw = el('div', {}, pill(CW_PILL[r.status] || 'missing', r.status === 'opted-out' ? 'opted out' : r.status + (r.status === 'ok' && r.global ? ' (global)' : '')), ' ',\n el('span', { class: 'fix' }, r.status === 'ok' ? 'reviewer ' + txt(r.reviewer || 'none') : txt(r.detail)));\n if ((r.status === 'missing' || r.status === 'warn') && ids[r.path]) cw.append(el('div', { class: 'srow-ctl' }, runButton('codewatch-repo', { repo: ids[r.path], where: r.path, label: 'Enable codewatch' })));\n const sec = r.security || { status: 'missing' };\n const sc = el('div', {}, pill(sec.status === 'missing' ? 'missing' : 'ok', sec.status === 'in-use' ? 'in use' : sec.status));\n const cd = cds[r.path] ? cds[r.path] + ' && ' : '';\n if (sec.status === 'missing') sc.append(copyOnly(sec.fix || cd + cp.enableSecurity, 'AWS'));\n else sc.append(el('details', { class: 'copies' }, el('summary', {}, 'Security Agent commands (AWS, copy only)'),\n [cp.codeReview, cp.threatModel, cp.pentest].map((c) => el('div', {}, copyButton(cd + c)))));\n return [repoName(r), cw, sc];\n };\n const rows = data.overview.setup || [];\n const shown = rows.filter((r) => !r.collapsed), hidden = rows.filter((r) => r.collapsed);\n if (shown.length) s.append(table(['Repo', 'Codewatch', 'Security Agent'], shown.map(rowOf)));\n else if (!hidden.length) s.append(el('p', { class: 'empty' }, 'No git repositories found under ' + txt(data.scope.root_display) + '.'));\n if (hidden.length) s.append(el('details', { class: 'missing-group', 'data-key': 'setup-view:other' },\n el('summary', {}, hidden.length + ' other repo' + (hidden.length === 1 ? '' : 's') + ': not set up'), table(['Repo', 'Codewatch', 'Security Agent'], hidden.map(rowOf))));\n return s;\n}\n\nfunction syncSection(data) {\n const s = el('section', { class: 'card' }, el('h2', {}, 'Usage sync (codeburn)'));\n const item = data.machine.setup.find((i) => i.id === 'codeburn-sync');\n const mode = (data.machine.codeburnSync || {}).mode || 'local';\n if (item) s.append(srow(pill(item.status), item.label, item.detail));\n if (item && item.help) s.append(helpBlock(item.help));\n s.append(el('p', { class: 'fix' }, 'Sync mode: ' + mode));\n s.append(srow(null, 'Local only', 'codeburn usage stays on this machine; no endpoint and no schedule.', runButton('codeburn-local', { label: 'Keep usage local' })));\n const input = el('input', { type: 'url', class: 'url', placeholder: 'collector URL (https, from your platform team)', maxlength: '2048', 'aria-label': 'Collector URL', spellcheck: 'false', autocomplete: 'off' });\n input.value = URL_DRAFT;\n input.addEventListener('input', () => { URL_DRAFT = input.value; });\n const msg = el('span', { class: 'msg', role: 'status' });\n s.append(srow(null, \"Sync to my team's PRISM\", 'Sets up codeburn sync for the collector URL and schedules the push. codeburn opens a browser to sign in.',\n input, runButton('codeburn-team', { label: 'Sync to team', urlInput: input, msg }), msg));\n if (actionDef('codeburn-systemd') && actionDef('codeburn-systemd').available) {\n s.append(srow(null, 'Use a systemd user timer', mode === 'team' ? 'Schedules the sync push with a systemd user timer; your crontab is not touched.' : 'Team mode only: set up sync to your team first.',\n mode === 'team' ? runButton('codeburn-systemd', { label: 'Use systemd timer' }) : null));\n }\n s.append(srow(null, 'Remove the sync schedule', 'Removes the scheduled sync push (cron entry or systemd timer). codeburn and its sync config stay.', runButton('codeburn-remove', { label: 'Remove schedule' })));\n s.append(srow(null, 'Status', 'codeburn, sync and schedule status.', runButton('codeburn-status', { label: 'Show status' })));\n s.append(srow(null, 'Platform team: add a developer', 'Creates a telemetry account in AWS (Cognito), so it is copy only.', copyOnly((SETUP.copy || {}).addDeveloper, 'AWS write')));\n return s;\n}\n\nconst STEP_PILL = { used: ['ok', 'used'], skipped: ['skipped', 'skipped'], 'not-set': ['info', 'not set'], 'n/a': ['skipped', 'n/a'] };\nfunction kv(pairs) { return el('dl', { class: 'kv' }, pairs.filter(Boolean).flatMap(([k, v]) => [el('dt', {}, k), el('dd', {}, v)])); }\n\nfunction checkResult(r) {\n if (!r) return null;\n if (!r.ok) return el('div', {}, pill('error', 'refused'), ' ', txt(r.json.error));\n const j = r.json;\n if (j.ok) {\n const m = j.matches === true ? pill('ok', 'matches the saved account') : j.matches === false ? pill('error', 'does not match saved account ' + txt(j.savedAccount)) : pill('info', 'no saved account');\n return el('div', {}, pill('ok', 'credentials work'), kv([['Account', txt(j.account)], ['Caller', txt(j.arn)], ['Region', txt(j.region || 'not set')], ['Saved', m]]), el('p', { class: 'note' }, txt(j.note)));\n }\n return el('div', {}, pill('error', j.expired ? 'expired or invalid' : 'check failed'), ' ', el('span', {}, txt(j.error)),\n el('div', { class: 'help' }, 'Fixes:', el('ul', { class: 'help-steps' }, (j.fixes || []).map((f) => el('li', {}, /^(aws|prism-cli) /.test(f) ? copyButton(f) : txt(f))))));\n}\n\nfunction awsSection(a) {\n const s = el('section', { class: 'card wide' }, el('h2', {}, 'AWS'));\n s.append(el('p', { class: 'note' }, 'From the environment and local config files only: no AWS call until you press Check now. Only setting names are read from ~/.aws; secret values are never read or shown.'));\n s.append(el('div', { class: 'srow' }, el('div', {}, 'prism-cli would use ', el('b', {}, a.profile ? 'profile ' + txt(a.profile) : txt(a.source)), ' \u00B7 ', el('span', { class: 'badge' }, txt(a.credentialLabel))),\n el('div', { class: 'fix' }, 'chosen by ' + txt(a.source) + ' \u00B7 region ' + (a.region ? txt(a.region.value) + ' (' + txt(a.region.source) + ')' : 'not set'))));\n for (const w of a.warnings || []) s.append(el('div', { class: 'callout warn', role: 'note' }, txt(w)));\n s.append(el('h3', {}, 'Resolution order'), el('ol', { class: 'steps' }, (a.steps || []).map((st) => {\n const p = STEP_PILL[st.state] || ['skipped', st.state];\n return el('li', {}, el('b', {}, txt(st.label)), ' ', pill(p[0], p[1]), ' ', el('span', { class: 'fix' }, txt(st.detail)));\n })));\n s.append(el('h3', {}, 'Saved aws connect'));\n if (a.saved) {\n const v = a.saved;\n s.append(kv([['Account', el('span', {}, txt(v.account), v.alias ? el('span', { class: 'tag' }, txt(v.alias)) : null)], ['Profile', txt(v.profile) + (v.inUse ? '' : ' (not in use)')],\n ['Region', txt(v.region)], ['Verified', v.verifiedAt ? el('span', { title: when(v.verifiedAt) }, ago(v.verifiedAt)) : 'unknown'], ['File', txt(v.path) + ' (' + v.scope + ')'],\n ['Admin / org', 'not stored by aws connect (it prints them when it runs)']]));\n } else s.append(el('p', { class: 'empty-line' }, 'None saved. ', copyButton((SETUP.copy || {}).awsConnect)));\n const btn = el('button', { type: 'button', class: 'run' }, 'Check now');\n const out = el('div', { 'aria-live': 'polite' }, checkResult(CHECK));\n btn.addEventListener('click', async () => {\n btn.disabled = true;\n out.replaceChildren(el('span', { class: 'fix' }, 'checking...'));\n try { CHECK = await api('/api/check-now', {}); } catch (e) { CHECK = { ok: false, json: { error: e.message } }; }\n out.replaceChildren(checkResult(CHECK));\n btn.disabled = false;\n if (CHECK.ok && CHECK.json.updated) load();\n });\n if (SNAPSHOT) btn.disabled = true;\n s.append(el('h3', {}, 'Check now'), el('div', { class: 'srow-ctl' }, btn, el('span', { class: 'fix' }, SNAPSHOT\n ? 'Disabled in a static snapshot, which makes no calls. Run prism-cli dashboard for a live check, or run aws sts get-caller-identity yourself.'\n : 'One read-only call: aws sts get-caller-identity, as aws connect does it.')), out);\n s.append(el('h3', {}, 'Read-only AWS commands'),\n srow(null, 'Account check', 'Every call is a describe, get or list; the result is saved to .prism/results/.', runButton('aws-check', { label: 'Run aws check' })),\n srow(null, 'Team stack info', 'Reads the PRISM stack outputs (collector URL, user pool).', runButton('team-info', { label: 'Run team info' })));\n const cp = SETUP.copy || {};\n s.append(el('h3', {}, 'Copy only (writes to AWS or needs a terminal)'),\n srow(null, 'Choose and save the AWS profile', 'Asks questions on a terminal.', copyOnly(cp.awsConnect, 'terminal')),\n srow(null, 'Set up the Security Agent in a repo', 'Creates AWS resources.', copyOnly(cp.enableSecurity, 'AWS write')),\n srow(null, 'Add a developer telemetry account', 'Creates a Cognito user.', copyOnly(cp.addDeveloper, 'AWS write')),\n srow(null, 'Security Agent runs', 'Uploads code and starts AWS jobs.', copyOnly(cp.codeReview, 'AWS write'), copyOnly(cp.threatModel, 'AWS write'), copyOnly(cp.pentest, 'AWS write')));\n return s;\n}\n\nasync function renderSetup(data) {\n try { const r = await api('/api/setup'); if (r.ok) SETUP = r.json; } catch (e) { /* keep the last good copy */ }\n const root = document.getElementById('setup');\n const openState = new Map([...root.querySelectorAll('details[data-key]')].map((d) => [d.getAttribute('data-key'), d.open]));\n root.replaceChildren();\n if (!SETUP) { root.append(el('section', { class: 'card wide' }, pill('error'), ' could not load the setup data')); return; }\n root.append(machineSection(data), syncSection(data), reposSection(data), awsSection(SETUP.aws || {}), ACTION_PANEL);\n for (const d of root.querySelectorAll('details[data-key]')) { const k = d.getAttribute('data-key'); if (openState.has(k)) d.open = openState.get(k); }\n}\n// A reload while an action runs picks its output back up.\napi('/api/action/status').then((r) => { if (r.ok && r.json.state === 'running') { showStatus(r.json); poll(); } }).catch(() => {});\n";
16
16
  //# sourceMappingURL=setup-page.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"setup-page.d.ts","sourceRoot":"","sources":["../../../src/dashboard/setup-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,uGAAuG;AACvG,eAAO,MAAM,UAAU,gKAAgK,CAAC;AAExL,0HAA0H;AAC1H,eAAO,MAAM,YAAY,m9BAexB,CAAC;AAEF,eAAO,MAAM,WAAW,inGA8BvB,CAAC;AAEF,eAAO,MAAM,YAAY,qpkBA8RxB,CAAC"}
1
+ {"version":3,"file":"setup-page.d.ts","sourceRoot":"","sources":["../../../src/dashboard/setup-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,uGAAuG;AACvG,eAAO,MAAM,UAAU,gKAAgK,CAAC;AAExL,0HAA0H;AAC1H,eAAO,MAAM,YAAY,m9BAexB,CAAC;AAEF,eAAO,MAAM,WAAW,inGA8BvB,CAAC;AAEF,eAAO,MAAM,YAAY,qlmBA2SxB,CAAC"}
@@ -78,6 +78,12 @@ showView();
78
78
 
79
79
  /** GET, or POST JSON with the CSRF header. referrerPolicy same-origin so the browser sends this page's Origin. */
80
80
  async function api(path, body) {
81
+ // A static snapshot answers the two reads it embeds and refuses everything else; it never fetches.
82
+ if (SNAPSHOT) {
83
+ if (body === undefined && path === '/api/setup') return { ok: true, status: 200, json: SNAPSHOT.setup };
84
+ if (body === undefined && path === '/api/action/status') return { ok: true, status: 200, json: { state: 'idle' } };
85
+ return { ok: false, status: 0, json: { error: 'this is a static snapshot: nothing runs from it' } };
86
+ }
81
87
  const init = body === undefined ? { credentials: 'same-origin', cache: 'no-store' } : {
82
88
  method: 'POST', credentials: 'same-origin', cache: 'no-store', referrerPolicy: 'same-origin',
83
89
  headers: { 'Content-Type': 'application/json', 'X-Prism-CSRF': CSRF }, body: JSON.stringify(body),
@@ -127,6 +133,10 @@ function runButton(id, o) {
127
133
  const a = actionDef(id);
128
134
  if (!a || !a.available) return null;
129
135
  o = o || {};
136
+ if (SNAPSHOT) {
137
+ const cd = o.where && SETUP.repoCd && SETUP.repoCd[o.where];
138
+ return copyOnly((cd ? cd + ' && ' : '') + a.command + (a.url ? ' <collector-url>' : ''), 'snapshot: copy only');
139
+ }
130
140
  const b = el('button', { type: 'button', class: 'run' + (a.confirm ? ' danger' : '') }, o.label || 'Run');
131
141
  b.addEventListener('click', () => {
132
142
  let url;
@@ -228,7 +238,7 @@ function machineSection(data) {
228
238
  const CW_PILL = { ok: 'ok', warn: 'warn', missing: 'missing', 'opted-out': 'skipped' };
229
239
  function reposSection(data) {
230
240
  const s = el('section', { class: 'card wide' }, el('h2', {}, 'Repos'));
231
- const recheck = el('button', { type: 'button' }, 'Re-check status');
241
+ const recheck = el('button', { type: 'button', disabled: SNAPSHOT ? '' : null, title: SNAPSHOT ? 'Not in a static snapshot' : null }, 'Re-check status');
232
242
  recheck.addEventListener('click', async () => { recheck.disabled = true; try { await api('/api/recheck'); } catch (e) { /* load() reports */ } await load(); });
233
243
  s.append(el('div', { class: 'srow-ctl' }, recheck, el('span', { class: 'fix' }, 'Codewatch hooks and Security Agent setup per repo, from local files.')));
234
244
  const ids = SETUP.repoIds || {}, cds = SETUP.repoCd || {}, cp = SETUP.copy || {};
@@ -319,7 +329,10 @@ function awsSection(a) {
319
329
  btn.disabled = false;
320
330
  if (CHECK.ok && CHECK.json.updated) load();
321
331
  });
322
- s.append(el('h3', {}, 'Check now'), el('div', { class: 'srow-ctl' }, btn, el('span', { class: 'fix' }, 'One read-only call: aws sts get-caller-identity, as aws connect does it.')), out);
332
+ if (SNAPSHOT) btn.disabled = true;
333
+ s.append(el('h3', {}, 'Check now'), el('div', { class: 'srow-ctl' }, btn, el('span', { class: 'fix' }, SNAPSHOT
334
+ ? 'Disabled in a static snapshot, which makes no calls. Run prism-cli dashboard for a live check, or run aws sts get-caller-identity yourself.'
335
+ : 'One read-only call: aws sts get-caller-identity, as aws connect does it.')), out);
323
336
  s.append(el('h3', {}, 'Read-only AWS commands'),
324
337
  srow(null, 'Account check', 'Every call is a describe, get or list; the result is saved to .prism/results/.', runButton('aws-check', { label: 'Run aws check' })),
325
338
  srow(null, 'Team stack info', 'Reads the PRISM stack outputs (collector URL, user pool).', runButton('team-info', { label: 'Run team info' })));
@@ -1 +1 @@
1
- {"version":3,"file":"setup-page.js","sourceRoot":"","sources":["../../../src/dashboard/setup-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,uGAAuG;AACvG,MAAM,CAAC,MAAM,UAAU,GAAG,6JAA6J,CAAC;AAExL,0HAA0H;AAC1H,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;CAe3B,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8B1B,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8R3B,CAAC"}
1
+ {"version":3,"file":"setup-page.js","sourceRoot":"","sources":["../../../src/dashboard/setup-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,uGAAuG;AACvG,MAAM,CAAC,MAAM,UAAU,GAAG,6JAA6J,CAAC;AAExL,0HAA0H;AAC1H,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;CAe3B,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8B1B,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2S3B,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `prism-cli dashboard --snapshot <file>` and the MCP tool prism_snapshot: the
3
+ * dashboard written as one static HTML file (renderSnapshotPage), with the
4
+ * same /api/data payload and /api/setup answer the live page fetches. No
5
+ * server, no port, no token. The file is written with mode 0600 because,
6
+ * like the live page, it shows local paths and the saved AWS account.
7
+ */
8
+ import { spawn } from 'node:child_process';
9
+ import { type PayloadOptions } from './payload.js';
10
+ export interface SnapshotOptions {
11
+ cwd: string;
12
+ home: string;
13
+ root?: string;
14
+ version: string;
15
+ usage: NonNullable<PayloadOptions['usage']>;
16
+ pathEnv?: string;
17
+ machineDeps?: PayloadOptions['machineDeps'];
18
+ env?: NodeJS.ProcessEnv;
19
+ now?: () => Date;
20
+ }
21
+ export declare function buildSnapshotHtml(o: SnapshotOptions): Promise<string>;
22
+ /** Writes (or overwrites) `file` with mode 0600; returns its size in bytes. */
23
+ export declare function writeSnapshot(file: string, html: string): number;
24
+ /** True when a browser can be opened here: never over SSH; on Linux only with a display. */
25
+ export declare function canOpen(env: NodeJS.ProcessEnv, platform: NodeJS.Platform): boolean;
26
+ /**
27
+ * Opens the URL (or file) in the default browser. argv only, detached; failure
28
+ * is non-fatal and goes to stderr, never stdout (the MCP server's protocol stream).
29
+ */
30
+ export declare function openBrowser(url: string, spawnFn?: typeof spawn, platform?: NodeJS.Platform): void;
31
+ //# sourceMappingURL=snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../../src/dashboard/snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAOjE,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,WAAW,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,cAAc,CAAC,aAAa,CAAC,CAAC;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAgB3E;AAED,+EAA+E;AAC/E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKhE;AAED,4FAA4F;AAC5F,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,OAAO,CAIlF;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,OAAO,KAAa,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,IAAI,CAW1H"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * `prism-cli dashboard --snapshot <file>` and the MCP tool prism_snapshot: the
3
+ * dashboard written as one static HTML file (renderSnapshotPage), with the
4
+ * same /api/data payload and /api/setup answer the live page fetches. No
5
+ * server, no port, no token. The file is written with mode 0600 because,
6
+ * like the live page, it shows local paths and the saved AWS account.
7
+ */
8
+ import { spawn } from 'node:child_process';
9
+ import { chmodSync, writeFileSync } from 'node:fs';
10
+ import { buildPayload } from './payload.js';
11
+ import { renderSnapshotPage } from './page.js';
12
+ import { ActionRunner, cliEntry, repoId } from './actions.js';
13
+ import { setupApi } from './setup-api.js';
14
+ import { repoRootOf } from '../aws/connection.js';
15
+ import { systemdUserAvailable } from '../utils/otel-sync-schedule.js';
16
+ export async function buildSnapshotHtml(o) {
17
+ const data = await buildPayload({ cwd: o.cwd, root: o.root, home: o.home, pathEnv: o.pathEnv, machineDeps: o.machineDeps, usage: o.usage });
18
+ const repos = data.repos.map((r) => ({ id: repoId(r.path), name: r.name, path: r.path }));
19
+ // Only the action catalog is read (labels, commands, availability); nothing is started.
20
+ const runner = new ActionRunner({
21
+ execPath: process.execPath, entry: cliEntry(), cwd: o.cwd, home: o.home, platform: process.platform,
22
+ repos: async () => repos, systemdAvailable: () => systemdUserAvailable(),
23
+ });
24
+ const api = setupApi({
25
+ runner, cwd: o.cwd,
26
+ repoPaths: async () => repos.map((r) => r.path),
27
+ identity: { env: o.env ?? process.env, home: o.home, repoRoot: repoRootOf(o.cwd) },
28
+ refresh: async () => data,
29
+ });
30
+ const setup = await api.getApi['/api/setup']();
31
+ return renderSnapshotPage({ data, setup, generatedAt: (o.now ?? (() => new Date()))().toISOString(), version: o.version });
32
+ }
33
+ /** Writes (or overwrites) `file` with mode 0600; returns its size in bytes. */
34
+ export function writeSnapshot(file, html) {
35
+ writeFileSync(file, html, { mode: 0o600 });
36
+ // An existing file keeps its old mode through writeFileSync.
37
+ chmodSync(file, 0o600);
38
+ return Buffer.byteLength(html, 'utf8');
39
+ }
40
+ /** True when a browser can be opened here: never over SSH; on Linux only with a display. */
41
+ export function canOpen(env, platform) {
42
+ if (env.SSH_CONNECTION || env.SSH_TTY)
43
+ return false;
44
+ if (platform === 'darwin' || platform === 'win32')
45
+ return true;
46
+ return !!(env.DISPLAY || env.WAYLAND_DISPLAY);
47
+ }
48
+ /**
49
+ * Opens the URL (or file) in the default browser. argv only, detached; failure
50
+ * is non-fatal and goes to stderr, never stdout (the MCP server's protocol stream).
51
+ */
52
+ export function openBrowser(url, spawnFn = spawn, platform = process.platform) {
53
+ const [cmd, args] = platform === 'darwin' ? ['open', [url]]
54
+ : platform === 'win32' ? ['rundll32', ['url.dll,FileProtocolHandler', url]]
55
+ : ['xdg-open', [url]];
56
+ try {
57
+ const child = spawnFn(cmd, args, { stdio: 'ignore', detached: true });
58
+ child.on('error', () => process.stderr.write(' (could not open a browser; open the URL above)\n'));
59
+ child.unref();
60
+ }
61
+ catch {
62
+ process.stderr.write(' (could not open a browser; open the URL above)\n');
63
+ }
64
+ }
65
+ //# sourceMappingURL=snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../../src/dashboard/snapshot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The live dashboard behind the MCP tools prism_dashboard and
3
+ * prism_dashboard_stop.
4
+ *
5
+ * prism_dashboard starts `prism-cli dashboard` as a child of the MCP server:
6
+ * argv with shell:false, not detached, with `--exit-on-stdin-close`. The
7
+ * server keeps all its own protections (bound to 127.0.0.1, a constant; a
8
+ * per-run token in the URL; Origin and CSRF checks on POST). This module adds
9
+ * no bind option and no other way in.
10
+ *
11
+ * The child ends with the MCP server, three ways: the server kills it when
12
+ * its stdin closes or it gets SIGINT/SIGTERM; a process 'exit' hook kills it;
13
+ * and the child itself exits when its stdin pipe closes, which also covers
14
+ * the MCP server being killed outright.
15
+ *
16
+ * One instance per user: ~/.prism/run/dashboard.json (dir 0700, file 0600)
17
+ * holds the pid, port and URL. The URL holds the token, so prism-cli writes it
18
+ * only there and returns it to the client; it never logs it. The client's own
19
+ * transcript does hold it (the user asked to see it); the token is useless once
20
+ * the server stops, and the server is reachable only from this machine. A state file whose
21
+ * pid is dead, or whose port does not answer as a PRISM dashboard, is ignored.
22
+ */
23
+ import { type ChildProcess, type SpawnOptions } from 'node:child_process';
24
+ export declare const RUN_SCHEMA = "prism.dashboard-run.v1";
25
+ export declare const START_TIMEOUT_MS = 15000;
26
+ export declare const STOP_TIMEOUT_MS = 3000;
27
+ export declare const PROBE_TIMEOUT_MS = 1000;
28
+ /** The URL line `prism-cli dashboard` prints; only a 127.0.0.1 URL is accepted. */
29
+ export declare const URL_LINE: RegExp;
30
+ export declare const runStatePath: (home: string) => string;
31
+ export interface RunState {
32
+ schema: string;
33
+ pid: number;
34
+ port: number;
35
+ url: string;
36
+ startedAt: string;
37
+ }
38
+ export type SpawnFn = (file: string, args: string[], opts: SpawnOptions) => ChildProcess;
39
+ export interface DashboardDeps {
40
+ home: string;
41
+ cwd: string;
42
+ root?: string;
43
+ env: NodeJS.ProcessEnv;
44
+ /** The CLI to run: node and its entry script (defaultCommand()). */
45
+ command?: {
46
+ file: string;
47
+ args: string[];
48
+ };
49
+ spawn?: SpawnFn;
50
+ probe?: (port: number) => Promise<boolean>;
51
+ alive?: (pid: number) => boolean;
52
+ timeoutMs?: number;
53
+ }
54
+ /** node + dist/bin/prism-cli.js; from source (tsx), the .ts entry with this process's loader flags. */
55
+ export declare function defaultCommand(): {
56
+ file: string;
57
+ args: string[];
58
+ };
59
+ /** True when `pid` is a process this user can signal. */
60
+ export declare function pidAlive(pid: number): boolean;
61
+ /** True when 127.0.0.1:<port> answers an untokened GET / with the dashboard's own 401. */
62
+ export declare function probeDashboard(port: number): Promise<boolean>;
63
+ /** The recorded instance when its pid is alive and its port answers as a dashboard; else null. */
64
+ export declare function liveInstance(d: DashboardDeps): Promise<RunState | null>;
65
+ /** Kills every dashboard this process started (sync: safe in an 'exit' handler). */
66
+ export declare function killStartedDashboards(home?: string): void;
67
+ export interface StartResult {
68
+ url: string;
69
+ port: number;
70
+ pid: number;
71
+ reused: boolean;
72
+ }
73
+ /** The running instance's URL, else starts one and waits for its URL line. */
74
+ export declare function startDashboard(d: DashboardDeps): Promise<StartResult>;
75
+ export interface StopResult {
76
+ stopped: boolean;
77
+ note: string;
78
+ }
79
+ /** Stops the instance in the state file (one started by prism_dashboard), never any other dashboard. */
80
+ export declare function stopDashboard(d: DashboardDeps): Promise<StopResult>;
81
+ /** What to tell the user when the browser is not opened here. */
82
+ export declare function remoteNote(port: number): string;
83
+ //# sourceMappingURL=dashboard-process.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-process.d.ts","sourceRoot":"","sources":["../../../src/mcp/dashboard-process.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAsB,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQ9F,eAAO,MAAM,UAAU,2BAA2B,CAAC;AACnD,eAAO,MAAM,gBAAgB,QAAS,CAAC;AACvC,eAAO,MAAM,eAAe,OAAQ,CAAC;AACrC,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AACtC,mFAAmF;AACnF,eAAO,MAAM,QAAQ,QAA2E,CAAC;AAEjG,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,WAAkD,CAAC;AAE5F,MAAM,WAAW,QAAQ;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE;AAEvG,MAAM,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,YAAY,KAAK,YAAY,CAAC;AAEzF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,oEAAoE;IACpE,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC3C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,uGAAuG;AACvG,wBAAgB,cAAc,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAIjE;AAED,yDAAyD;AACzD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED,0FAA0F;AAC1F,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAa7D;AASD,kGAAkG;AAClG,wBAAsB,YAAY,CAAC,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAK7E;AAUD,oFAAoF;AACpF,wBAAgB,qBAAqB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAMzD;AAED,MAAM,WAAW,WAAW;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE;AAExF,8EAA8E;AAC9E,wBAAsB,cAAc,CAAC,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CAiC3E;AAED,MAAM,WAAW,UAAU;IAAG,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAE9D,wGAAwG;AACxG,wBAAsB,aAAa,CAAC,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CAiBzE;AAED,iEAAiE;AACjE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C"}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * The live dashboard behind the MCP tools prism_dashboard and
3
+ * prism_dashboard_stop.
4
+ *
5
+ * prism_dashboard starts `prism-cli dashboard` as a child of the MCP server:
6
+ * argv with shell:false, not detached, with `--exit-on-stdin-close`. The
7
+ * server keeps all its own protections (bound to 127.0.0.1, a constant; a
8
+ * per-run token in the URL; Origin and CSRF checks on POST). This module adds
9
+ * no bind option and no other way in.
10
+ *
11
+ * The child ends with the MCP server, three ways: the server kills it when
12
+ * its stdin closes or it gets SIGINT/SIGTERM; a process 'exit' hook kills it;
13
+ * and the child itself exits when its stdin pipe closes, which also covers
14
+ * the MCP server being killed outright.
15
+ *
16
+ * One instance per user: ~/.prism/run/dashboard.json (dir 0700, file 0600)
17
+ * holds the pid, port and URL. The URL holds the token, so prism-cli writes it
18
+ * only there and returns it to the client; it never logs it. The client's own
19
+ * transcript does hold it (the user asked to see it); the token is useless once
20
+ * the server stops, and the server is reachable only from this machine. A state file whose
21
+ * pid is dead, or whose port does not answer as a PRISM dashboard, is ignored.
22
+ */
23
+ import { spawn as nodeSpawn } from 'node:child_process';
24
+ import { existsSync, rmSync } from 'node:fs';
25
+ import { request } from 'node:http';
26
+ import { join } from 'node:path';
27
+ import { cliEntry } from '../dashboard/actions.js';
28
+ import { BIND_HOST } from '../dashboard/server.js';
29
+ import { readJson, writePrivateJson } from '../utils/private-json.js';
30
+ export const RUN_SCHEMA = 'prism.dashboard-run.v1';
31
+ export const START_TIMEOUT_MS = 15_000;
32
+ export const STOP_TIMEOUT_MS = 3_000;
33
+ export const PROBE_TIMEOUT_MS = 1_000;
34
+ /** The URL line `prism-cli dashboard` prints; only a 127.0.0.1 URL is accepted. */
35
+ export const URL_LINE = /^\s*(http:\/\/127\.0\.0\.1:(\d{1,5})\/\?token=[A-Za-z0-9_-]{20,})\s*$/m;
36
+ export const runStatePath = (home) => join(home, '.prism', 'run', 'dashboard.json');
37
+ /** node + dist/bin/prism-cli.js; from source (tsx), the .ts entry with this process's loader flags. */
38
+ export function defaultCommand() {
39
+ const js = cliEntry();
40
+ if (existsSync(js))
41
+ return { file: process.execPath, args: [js] };
42
+ return { file: process.execPath, args: [...process.execArgv, js.replace(/\.js$/, '.ts')] };
43
+ }
44
+ /** True when `pid` is a process this user can signal. */
45
+ export function pidAlive(pid) {
46
+ try {
47
+ process.kill(pid, 0);
48
+ return true;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
54
+ /** True when 127.0.0.1:<port> answers an untokened GET / with the dashboard's own 401. */
55
+ export function probeDashboard(port) {
56
+ return new Promise((done) => {
57
+ const req = request({ host: BIND_HOST, port, path: '/', method: 'GET', headers: { Host: `${BIND_HOST}:${port}` }, timeout: PROBE_TIMEOUT_MS }, (res) => {
58
+ let body = '';
59
+ res.setEncoding('utf8');
60
+ res.on('data', (c) => { if (body.length < 512)
61
+ body += c; });
62
+ res.on('end', () => done(res.statusCode === 401 && body.includes('prism-cli dashboard')));
63
+ res.on('error', () => done(false));
64
+ });
65
+ req.on('timeout', () => { req.destroy(); done(false); });
66
+ req.on('error', () => done(false));
67
+ req.end();
68
+ });
69
+ }
70
+ function readState(home) {
71
+ const s = readJson(runStatePath(home));
72
+ if (!s || s.schema !== RUN_SCHEMA || !Number.isInteger(s.pid) || s.pid <= 0 || !Number.isInteger(s.port) || typeof s.url !== 'string')
73
+ return null;
74
+ const m = URL_LINE.exec(s.url);
75
+ return m && Number(m[2]) === s.port ? s : null;
76
+ }
77
+ /** The recorded instance when its pid is alive and its port answers as a dashboard; else null. */
78
+ export async function liveInstance(d) {
79
+ const s = readState(d.home);
80
+ if (!s)
81
+ return null;
82
+ if (!(d.alive ?? pidAlive)(s.pid))
83
+ return null;
84
+ return (await (d.probe ?? probeDashboard)(s.port)) ? s : null;
85
+ }
86
+ /** Children this process started; killed when it exits. */
87
+ const children = new Map();
88
+ function clearStateFor(home, pid) {
89
+ const s = readState(home);
90
+ if (s && s.pid === pid)
91
+ rmSync(runStatePath(home), { force: true });
92
+ }
93
+ /** Kills every dashboard this process started (sync: safe in an 'exit' handler). */
94
+ export function killStartedDashboards(home) {
95
+ for (const [pid, c] of children) {
96
+ try {
97
+ c.kill('SIGTERM');
98
+ }
99
+ catch { /* already gone */ }
100
+ if (home)
101
+ clearStateFor(home, pid);
102
+ }
103
+ children.clear();
104
+ }
105
+ /** The running instance's URL, else starts one and waits for its URL line. */
106
+ export async function startDashboard(d) {
107
+ const cur = await liveInstance(d);
108
+ if (cur)
109
+ return { url: cur.url, port: cur.port, pid: cur.pid, reused: true };
110
+ const cmd = d.command ?? defaultCommand();
111
+ const args = [...cmd.args, 'dashboard', '--no-open', '--exit-on-stdin-close', ...(d.root ? ['--root', d.root] : [])];
112
+ const child = (d.spawn ?? nodeSpawn)(cmd.file, args, { cwd: d.cwd, env: d.env, stdio: ['pipe', 'pipe', 'pipe'], shell: false, detached: false });
113
+ const pid = child.pid;
114
+ if (!pid)
115
+ throw new Error('could not start prism-cli dashboard');
116
+ children.set(pid, child);
117
+ child.on('exit', () => { children.delete(pid); clearStateFor(d.home, pid); });
118
+ // The token is in stdout: it is read for the URL and never forwarded or logged.
119
+ let out = '';
120
+ let lastErr = '';
121
+ child.stderr?.setEncoding('utf8');
122
+ child.stderr?.on('data', (c) => {
123
+ for (const l of c.split('\n'))
124
+ if (l.trim()) {
125
+ lastErr = l.trim().slice(0, 300);
126
+ process.stderr.write(`prism dashboard: ${lastErr}\n`);
127
+ }
128
+ });
129
+ const url = await new Promise((resolve, reject) => {
130
+ const timer = setTimeout(() => fail(new Error(`prism-cli dashboard printed no URL within ${Math.round((d.timeoutMs ?? START_TIMEOUT_MS) / 1000)} s`)), d.timeoutMs ?? START_TIMEOUT_MS);
131
+ const fail = (e) => { clearTimeout(timer); try {
132
+ child.kill('SIGTERM');
133
+ }
134
+ catch { /* gone */ } reject(e); };
135
+ child.stdout?.setEncoding('utf8');
136
+ child.stdout?.on('data', (c) => {
137
+ if (out.length > 64 * 1024)
138
+ return;
139
+ out += c;
140
+ const m = URL_LINE.exec(out);
141
+ if (m) {
142
+ clearTimeout(timer);
143
+ resolve({ url: m[1], port: Number(m[2]) });
144
+ }
145
+ });
146
+ child.on('exit', (code) => fail(new Error(`prism-cli dashboard exited (code ${code}) before printing its URL${lastErr ? `: ${lastErr}` : ''}`)));
147
+ child.on('error', (e) => fail(new Error(`could not start prism-cli dashboard: ${e.message}`)));
148
+ });
149
+ child.stdout?.resume();
150
+ writePrivateJson(runStatePath(d.home), { schema: RUN_SCHEMA, pid, port: url.port, url: url.url, startedAt: new Date().toISOString() });
151
+ return { ...url, pid, reused: false };
152
+ }
153
+ /** Stops the instance in the state file (one started by prism_dashboard), never any other dashboard. */
154
+ export async function stopDashboard(d) {
155
+ const s = readState(d.home);
156
+ if (!s)
157
+ return { stopped: false, note: 'no dashboard started by prism_dashboard is recorded' };
158
+ const live = await liveInstance(d);
159
+ if (!live) {
160
+ rmSync(runStatePath(d.home), { force: true });
161
+ return { stopped: false, note: 'the recorded dashboard was no longer running; its state file was removed' };
162
+ }
163
+ const own = children.get(live.pid);
164
+ try {
165
+ if (own)
166
+ own.kill('SIGTERM');
167
+ else
168
+ process.kill(live.pid, 'SIGTERM');
169
+ }
170
+ catch { /* exited meanwhile */ }
171
+ const alive = d.alive ?? pidAlive;
172
+ const deadline = Date.now() + STOP_TIMEOUT_MS;
173
+ while (Date.now() < deadline && alive(live.pid))
174
+ await new Promise((r) => setTimeout(r, 50));
175
+ rmSync(runStatePath(d.home), { force: true });
176
+ return alive(live.pid)
177
+ ? { stopped: false, note: `sent SIGTERM to pid ${live.pid}, but it is still running` }
178
+ : { stopped: true, note: `stopped the dashboard on ${BIND_HOST}:${live.port}` };
179
+ }
180
+ /** What to tell the user when the browser is not opened here. */
181
+ export function remoteNote(port) {
182
+ return `Not opened: no display here (an SSH session). In a VS Code, Kiro or Cursor Remote-SSH terminal the editor usually forwards the port, so Ctrl/Cmd+click the URL (it is not guaranteed). From plain SSH, run on your own machine: ssh -L ${port}:127.0.0.1:${port} <host>, then open the URL there.`;
183
+ }
184
+ //# sourceMappingURL=dashboard-process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-process.js","sourceRoot":"","sources":["../../../src/mcp/dashboard-process.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/mcp/server.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAGnE,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAE/D,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAkBrD;AAED,yGAAyG;AACzG,wBAAgB,eAAe,IAAI,IAAI,CAMtC;AAED,wBAAsB,QAAQ,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB9D"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/mcp/server.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAGnE,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAG/D,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAkBrD;AAED,yGAAyG;AACzG,wBAAgB,eAAe,IAAI,IAAI,CAMtC;AAED,wBAAsB,QAAQ,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB9D"}