@prism-d1/cli 1.5.0 → 1.7.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 (79) hide show
  1. package/dist/src/aws/check.js +1 -1
  2. package/dist/src/aws/connect.d.ts +1 -1
  3. package/dist/src/aws/connect.d.ts.map +1 -1
  4. package/dist/src/aws/connect.js.map +1 -1
  5. package/dist/src/aws/team.d.ts +39 -0
  6. package/dist/src/aws/team.d.ts.map +1 -0
  7. package/dist/src/aws/team.js +172 -0
  8. package/dist/src/aws/team.js.map +1 -0
  9. package/dist/src/commands/_top/dashboard.d.ts.map +1 -1
  10. package/dist/src/commands/_top/dashboard.js +22 -3
  11. package/dist/src/commands/_top/dashboard.js.map +1 -1
  12. package/dist/src/commands/enable/codeburn.d.ts +6 -2
  13. package/dist/src/commands/enable/codeburn.d.ts.map +1 -1
  14. package/dist/src/commands/enable/codeburn.js +126 -21
  15. package/dist/src/commands/enable/codeburn.js.map +1 -1
  16. package/dist/src/commands/team/add-developer.d.ts +17 -0
  17. package/dist/src/commands/team/add-developer.d.ts.map +1 -0
  18. package/dist/src/commands/team/add-developer.js +22 -0
  19. package/dist/src/commands/team/add-developer.js.map +1 -0
  20. package/dist/src/commands/team/info.d.ts +16 -0
  21. package/dist/src/commands/team/info.d.ts.map +1 -0
  22. package/dist/src/commands/team/info.js +30 -0
  23. package/dist/src/commands/team/info.js.map +1 -0
  24. package/dist/src/dashboard/actions.d.ts +112 -0
  25. package/dist/src/dashboard/actions.d.ts.map +1 -0
  26. package/dist/src/dashboard/actions.js +242 -0
  27. package/dist/src/dashboard/actions.js.map +1 -0
  28. package/dist/src/dashboard/aws-identity.d.ts +115 -0
  29. package/dist/src/dashboard/aws-identity.d.ts.map +1 -0
  30. package/dist/src/dashboard/aws-identity.js +240 -0
  31. package/dist/src/dashboard/aws-identity.js.map +1 -0
  32. package/dist/src/dashboard/page.d.ts +1 -1
  33. package/dist/src/dashboard/page.d.ts.map +1 -1
  34. package/dist/src/dashboard/page.js +36 -9
  35. package/dist/src/dashboard/page.js.map +1 -1
  36. package/dist/src/dashboard/payload.d.ts +7 -0
  37. package/dist/src/dashboard/payload.d.ts.map +1 -1
  38. package/dist/src/dashboard/payload.js +2 -0
  39. package/dist/src/dashboard/payload.js.map +1 -1
  40. package/dist/src/dashboard/server.d.ts +27 -6
  41. package/dist/src/dashboard/server.d.ts.map +1 -1
  42. package/dist/src/dashboard/server.js +75 -10
  43. package/dist/src/dashboard/server.js.map +1 -1
  44. package/dist/src/dashboard/setup-api.d.ts +63 -0
  45. package/dist/src/dashboard/setup-api.d.ts.map +1 -0
  46. package/dist/src/dashboard/setup-api.js +66 -0
  47. package/dist/src/dashboard/setup-api.js.map +1 -0
  48. package/dist/src/dashboard/setup-page.d.ts +16 -0
  49. package/dist/src/dashboard/setup-page.d.ts.map +1 -0
  50. package/dist/src/dashboard/setup-page.js +347 -0
  51. package/dist/src/dashboard/setup-page.js.map +1 -0
  52. package/dist/src/dashboard/setup.d.ts +11 -2
  53. package/dist/src/dashboard/setup.d.ts.map +1 -1
  54. package/dist/src/dashboard/setup.js +35 -16
  55. package/dist/src/dashboard/setup.js.map +1 -1
  56. package/dist/src/dashboard/summary.d.ts +1 -1
  57. package/dist/src/dashboard/summary.d.ts.map +1 -1
  58. package/dist/src/dashboard/summary.js +2 -1
  59. package/dist/src/dashboard/summary.js.map +1 -1
  60. package/dist/src/index.d.ts.map +1 -1
  61. package/dist/src/index.js +8 -0
  62. package/dist/src/index.js.map +1 -1
  63. package/dist/src/utils/aws.d.ts +2 -0
  64. package/dist/src/utils/aws.d.ts.map +1 -1
  65. package/dist/src/utils/aws.js +1 -1
  66. package/dist/src/utils/aws.js.map +1 -1
  67. package/dist/src/utils/exec.d.ts +1 -1
  68. package/dist/src/utils/exec.d.ts.map +1 -1
  69. package/dist/src/utils/exec.js +3 -1
  70. package/dist/src/utils/exec.js.map +1 -1
  71. package/dist/src/utils/otel-sync-schedule.d.ts +2 -0
  72. package/dist/src/utils/otel-sync-schedule.d.ts.map +1 -1
  73. package/dist/src/utils/otel-sync-schedule.js +13 -0
  74. package/dist/src/utils/otel-sync-schedule.js.map +1 -1
  75. package/dist/src/utils/sync-mode.d.ts +43 -0
  76. package/dist/src/utils/sync-mode.d.ts.map +1 -0
  77. package/dist/src/utils/sync-mode.js +84 -0
  78. package/dist/src/utils/sync-mode.js.map +1 -0
  79. package/package.json +1 -1
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Routes behind the Setup page:
3
+ * GET /api/setup action catalog, repo ids, copy-only commands, AWS identity (no AWS call)
4
+ * GET /api/action/status the current or last action and its redacted output
5
+ * GET /api/recheck rebuilds the dashboard data now (status checks)
6
+ * POST /api/action start an allowlisted action
7
+ * POST /api/check-now one read-only sts get-caller-identity
8
+ * Account ids and ARNs appear only here and in the page, never in `dashboard --json`.
9
+ */
10
+ import { gitleaksInstallHint, shellPath } from './setup.js';
11
+ import { ACTIONS, displayCommand, redact, repoId } from './actions.js';
12
+ import { checkNow, checkNowAws, resolveAwsIdentity } from './aws-identity.js';
13
+ import { loadConnection } from '../aws/connection.js';
14
+ import { tildeHome } from './payload.js';
15
+ /** Commands the page shows as Copy only: each writes to AWS, needs a terminal, or both. */
16
+ export const COPY_ONLY = {
17
+ enableSecurity: 'prism-cli enable security',
18
+ addDeveloper: 'prism-cli team add-developer <email>',
19
+ codeReview: 'prism-cli security code-review',
20
+ threatModel: 'prism-cli security threat-model',
21
+ pentest: 'prism-cli security pentest',
22
+ awsConnect: 'prism-cli aws connect',
23
+ };
24
+ export function setupApi(d) {
25
+ const catalog = () => Object.keys(ACTIONS).map((id) => {
26
+ const a = ACTIONS[id];
27
+ return {
28
+ id, label: a.label, command: displayCommand(a.args), repo: !!a.repo, url: !!a.url, confirm: !!a.confirm,
29
+ readOnlyAws: !!a.readOnlyAws, timeoutSec: a.timeoutMs / 1000, available: d.runner.available(id),
30
+ };
31
+ });
32
+ return {
33
+ getApi: {
34
+ '/api/setup': async () => {
35
+ const paths = await d.repoPaths();
36
+ return {
37
+ actions: catalog(),
38
+ repoIds: Object.fromEntries(paths.map((p) => [p, repoId(p)])),
39
+ /** `cd <repo>` prefixes for the copy-only per-repo commands. */
40
+ repoCd: Object.fromEntries(paths.map((p) => [p, `cd ${shellPath(p)}`])),
41
+ cwd: tildeHome(d.cwd, d.identity.home),
42
+ copy: { ...COPY_ONLY, gitleaks: gitleaksInstallHint() },
43
+ aws: resolveAwsIdentity(d.identity),
44
+ };
45
+ },
46
+ '/api/action/status': async () => d.runner.status(),
47
+ '/api/recheck': async () => d.refresh(),
48
+ },
49
+ postApi: {
50
+ '/api/action': (body) => d.runner.start(body),
51
+ '/api/check-now': (body) => {
52
+ if (!body || typeof body !== 'object' || Array.isArray(body) || Object.keys(body).length) {
53
+ return Promise.resolve({ status: 400, json: { error: 'check-now takes an empty JSON object' } });
54
+ }
55
+ return d.runner.exclusive(() => checkNow({
56
+ identity: resolveAwsIdentity(d.identity),
57
+ saved: loadConnection(d.identity.home, d.identity.repoRoot),
58
+ aws: d.checkAws ?? checkNowAws,
59
+ now: d.now ?? (() => new Date()),
60
+ redact,
61
+ }));
62
+ },
63
+ },
64
+ };
65
+ }
66
+ //# sourceMappingURL=setup-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-api.js","sourceRoot":"","sources":["../../../src/dashboard/setup-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The Setup view and the theme toggle, as CSS and JS strings concatenated into
3
+ * page.ts's single document (same nonce, same el()/txt()/pill() helpers).
4
+ *
5
+ * Every value from the server is placed with textContent or an attribute.
6
+ * Run buttons only send an action id (plus a repo id or the collector URL);
7
+ * the server maps the id to a fixed argv. Each Run shows the exact command in
8
+ * a confirm dialog first. AWS-writing commands are Copy only.
9
+ */
10
+ /** Runs in <head> before first paint: applies a saved Light/Dark choice so the page does not flash. */
11
+ export declare const THEME_BOOT = "(function(){try{var t=localStorage.getItem('prism-theme');if(t==='light'||t==='dark')document.documentElement.setAttribute('data-theme',t);}catch(e){}})();";
12
+ /** The header toggle: Light, Dark or System (no data-theme; follows prefers-color-scheme). Saved in localStorage only. */
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
+ 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";
16
+ //# sourceMappingURL=setup-page.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,347 @@
1
+ /**
2
+ * The Setup view and the theme toggle, as CSS and JS strings concatenated into
3
+ * page.ts's single document (same nonce, same el()/txt()/pill() helpers).
4
+ *
5
+ * Every value from the server is placed with textContent or an attribute.
6
+ * Run buttons only send an action id (plus a repo id or the collector URL);
7
+ * the server maps the id to a fixed argv. Each Run shows the exact command in
8
+ * a confirm dialog first. AWS-writing commands are Copy only.
9
+ */
10
+ /** Runs in <head> before first paint: applies a saved Light/Dark choice so the page does not flash. */
11
+ export const THEME_BOOT = `(function(){try{var t=localStorage.getItem('prism-theme');if(t==='light'||t==='dark')document.documentElement.setAttribute('data-theme',t);}catch(e){}})();`;
12
+ /** The header toggle: Light, Dark or System (no data-theme; follows prefers-color-scheme). Saved in localStorage only. */
13
+ export const THEME_SCRIPT = `
14
+ const THEME_KEY = 'prism-theme';
15
+ function storedTheme() { try { const t = localStorage.getItem(THEME_KEY); return t === 'light' || t === 'dark' ? t : 'system'; } catch (e) { return 'system'; } }
16
+ function applyTheme(t) {
17
+ const root = document.documentElement;
18
+ if (t === 'light' || t === 'dark') root.setAttribute('data-theme', t); else root.removeAttribute('data-theme');
19
+ for (const b of document.querySelectorAll('[data-theme-set]')) b.setAttribute('aria-pressed', String(b.getAttribute('data-theme-set') === t));
20
+ }
21
+ function setTheme(t) {
22
+ const v = t === 'light' || t === 'dark' ? t : 'system';
23
+ try { if (v === 'system') localStorage.removeItem(THEME_KEY); else localStorage.setItem(THEME_KEY, v); } catch (e) { /* storage off: applies for this page only */ }
24
+ applyTheme(v);
25
+ }
26
+ for (const b of document.querySelectorAll('[data-theme-set]')) b.addEventListener('click', () => setTheme(b.getAttribute('data-theme-set')));
27
+ applyTheme(storedTheme());
28
+ `;
29
+ export const SETUP_STYLE = `
30
+ [hidden] { display:none !important; }
31
+ nav.tabs { display:flex; gap:4px; }
32
+ nav.tabs a { color:var(--muted); text-decoration:none; padding:4px 10px; border-radius:6px; border:1px solid transparent; }
33
+ nav.tabs a[aria-current="page"] { color:var(--text); background:var(--bg); border-color:var(--line); font-weight:600; }
34
+ a:focus-visible, button:focus-visible, input:focus-visible, summary:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }
35
+ .theme { display:inline-flex; border:1px solid var(--line); border-radius:6px; overflow:hidden; }
36
+ .theme button { border:none; border-radius:0; background:var(--card); color:var(--muted); padding:4px 9px; }
37
+ .theme button[aria-pressed="true"] { background:var(--bg); color:var(--text); font-weight:600; }
38
+ .srow { padding:8px 0; border-top:1px solid var(--line); overflow-wrap:anywhere; } .srow:first-of-type { border-top:none; }
39
+ .srow-head { display:flex; gap:8px; align-items:baseline; flex-wrap:wrap; }
40
+ .srow-ctl { display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-top:6px; }
41
+ .cmdline { display:inline-flex; gap:6px; align-items:center; flex-wrap:wrap; max-width:100%; } .cmdline code { white-space:normal; overflow-wrap:anywhere; }
42
+ .badge { font-size:11px; padding:0 6px; border-radius:8px; border:1px solid var(--line); color:var(--muted); background:var(--raised); white-space:nowrap; }
43
+ button.run { background:var(--accent); border-color:var(--accent); color:var(--card); font-weight:600; }
44
+ button.run.danger { background:var(--card); border-color:var(--bad); color:var(--bad); }
45
+ button:disabled { opacity:.55; cursor:not-allowed; }
46
+ input.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; }
47
+ input.url::placeholder { color:var(--muted); }
48
+ .msg { color:var(--bad); font-size:12px; }
49
+ ol.steps { margin:4px 0; padding-left:20px; } ol.steps li { padding:3px 0; overflow-wrap:anywhere; }
50
+ dl.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; }
51
+ details.copies > summary { cursor:pointer; color:var(--muted); font-size:12px; padding:4px 0; } details.copies > div { padding:3px 0; }
52
+ dialog.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); }
53
+ dialog.confirm::backdrop { background:rgb(0 0 0 / .5); }
54
+ dialog.confirm h2 { margin-bottom:6px; } dialog.confirm p { margin:6px 0; }
55
+ dialog.confirm .actions { display:flex; gap:8px; justify-content:flex-end; margin-top:14px; }
56
+ pre.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; }
57
+ pre.out { max-height:420px; overflow:auto; }
58
+ label.extra { display:flex; gap:6px; align-items:flex-start; margin-top:10px; color:var(--bad); font-weight:600; }
59
+ `;
60
+ export const SETUP_SCRIPT = `
61
+ const CSRF = (document.querySelector('meta[name="prism-csrf"]') || {}).content || '';
62
+ let SETUP = null, POLL = null, DIALOG = null, CHECK = null, URL_DRAFT = '';
63
+ const OUT = { status: el('div', { class: 'srow-head' }), pre: el('pre', { class: 'out', 'aria-live': 'polite' }) };
64
+ const ACTION_PANEL = el('section', { class: 'card wide', hidden: '' }, el('h2', {}, 'Action output'), OUT.status, OUT.pre,
65
+ el('p', { class: 'note' }, 'Secrets are masked. Each run is logged to ~/.prism/logs/dashboard-actions.log (time, action, repo, exit code).'));
66
+
67
+ function currentView() { return location.hash === '#setup' ? 'setup' : 'dashboard'; }
68
+ function showView() {
69
+ const v = currentView();
70
+ document.getElementById('main').hidden = v !== 'dashboard';
71
+ document.getElementById('setup').hidden = v !== 'setup';
72
+ for (const a of document.querySelectorAll('[data-view]')) {
73
+ if (a.getAttribute('data-view') === v) a.setAttribute('aria-current', 'page'); else a.removeAttribute('aria-current');
74
+ }
75
+ }
76
+ window.addEventListener('hashchange', showView);
77
+ showView();
78
+
79
+ /** GET, or POST JSON with the CSRF header. referrerPolicy same-origin so the browser sends this page's Origin. */
80
+ async function api(path, body) {
81
+ const init = body === undefined ? { credentials: 'same-origin', cache: 'no-store' } : {
82
+ method: 'POST', credentials: 'same-origin', cache: 'no-store', referrerPolicy: 'same-origin',
83
+ headers: { 'Content-Type': 'application/json', 'X-Prism-CSRF': CSRF }, body: JSON.stringify(body),
84
+ };
85
+ const res = await fetch(path, init);
86
+ let json = null;
87
+ try { json = await res.json(); } catch (e) { json = null; }
88
+ return { ok: res.ok, status: res.status, json: json || { error: 'HTTP ' + res.status } };
89
+ }
90
+
91
+ function ago(iso) {
92
+ const t = Date.parse(iso);
93
+ if (isNaN(t)) return 'unknown';
94
+ const s = Math.max(0, (Date.now() - t) / 1000);
95
+ for (const [n, w] of [[86400, 'day'], [3600, 'hour'], [60, 'minute']]) {
96
+ if (s >= n) { const k = Math.floor(s / n); return k + ' ' + w + (k === 1 ? '' : 's') + ' ago'; }
97
+ }
98
+ return 'just now';
99
+ }
100
+
101
+ function copyButton(cmd) {
102
+ const b = el('button', { type: 'button', title: 'Copy to the clipboard' }, 'Copy');
103
+ b.addEventListener('click', async () => {
104
+ try { await navigator.clipboard.writeText(cmd); b.textContent = 'Copied'; } catch (e) { b.textContent = 'Select the text to copy'; }
105
+ setTimeout(() => { b.textContent = 'Copy'; }, 1500);
106
+ });
107
+ return el('span', { class: 'cmdline' }, el('code', {}, cmd), b);
108
+ }
109
+ const copyOnly = (cmd, why) => el('div', { class: 'srow-ctl' }, copyButton(cmd), why ? el('span', { class: 'badge' }, why) : null);
110
+
111
+ /** Client-side copy of the server's collector URL rule; the server checks again. */
112
+ function urlProblem(s) {
113
+ if (!s) return 'enter the collector URL';
114
+ if (s.length > 2048) return 'the URL is longer than 2048 characters';
115
+ let u;
116
+ try { u = new URL(s); } catch (e) { return 'not a valid URL'; }
117
+ if (u.protocol !== 'https:') return 'the URL must use https';
118
+ if (!u.hostname) return 'the URL has no hostname';
119
+ if (u.username || u.password) return 'the URL must not contain a user name or password';
120
+ return null;
121
+ }
122
+
123
+ const actionDef = (id) => SETUP && (SETUP.actions || []).find((a) => a.id === id);
124
+
125
+ /** A Run button for an allowlisted action; null when the action is not available here. */
126
+ function runButton(id, o) {
127
+ const a = actionDef(id);
128
+ if (!a || !a.available) return null;
129
+ o = o || {};
130
+ const b = el('button', { type: 'button', class: 'run' + (a.confirm ? ' danger' : '') }, o.label || 'Run');
131
+ b.addEventListener('click', () => {
132
+ let url;
133
+ if (a.url) {
134
+ url = o.urlInput ? o.urlInput.value : '';
135
+ const p = urlProblem(url);
136
+ if (p) { if (o.msg) o.msg.textContent = p; return; }
137
+ if (o.msg) o.msg.textContent = '';
138
+ }
139
+ confirmRun(a, { repo: o.repo, where: o.where, url });
140
+ });
141
+ return b;
142
+ }
143
+
144
+ function confirmRun(a, o) {
145
+ if (!DIALOG) { DIALOG = el('dialog', { class: 'confirm', 'aria-labelledby': 'confirm-title' }); document.body.append(DIALOG); }
146
+ const cmd = a.command + (o.url ? ' ' + o.url : '');
147
+ const run = el('button', { type: 'button', class: 'run' + (a.confirm ? ' danger' : '') }, 'Run');
148
+ const cancel = el('button', { type: 'button' }, 'Cancel');
149
+ const kids = [el('h2', { id: 'confirm-title' }, a.label), el('p', {}, 'This will run:'), el('pre', { class: 'cmd' }, cmd),
150
+ el('p', { class: 'fix' }, 'in ' + txt(o.where || (SETUP && SETUP.cwd) || '')),
151
+ 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.'),
152
+ 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.')];
153
+ if (a.confirm) {
154
+ const cb = el('input', { type: 'checkbox' });
155
+ run.disabled = true;
156
+ cb.addEventListener('change', () => { run.disabled = !cb.checked; });
157
+ kids.push(el('label', { class: 'extra' }, cb, 'Yes, remove the scheduled codeburn sync push from this machine'));
158
+ }
159
+ kids.push(el('div', { class: 'actions' }, cancel, run));
160
+ DIALOG.replaceChildren(...kids);
161
+ cancel.addEventListener('click', () => DIALOG.close());
162
+ run.addEventListener('click', () => { DIALOG.close(); startAction(a, o); });
163
+ DIALOG.showModal();
164
+ cancel.focus();
165
+ }
166
+
167
+ async function startAction(a, o) {
168
+ const body = { action: a.id };
169
+ if (a.repo) body.repo = o.repo;
170
+ if (a.url) body.url = o.url;
171
+ if (a.confirm) body.confirm = true;
172
+ ACTION_PANEL.hidden = false;
173
+ OUT.status.replaceChildren(pill('warn', 'starting'), el('code', {}, a.command));
174
+ OUT.pre.textContent = '';
175
+ let r;
176
+ try { r = await api('/api/action', body); } catch (e) { r = { ok: false, json: { error: e.message } }; }
177
+ if (!r.ok) { OUT.status.replaceChildren(pill('error', 'refused'), txt(r.json.error)); return; }
178
+ showStatus(r.json);
179
+ ACTION_PANEL.scrollIntoView({ block: 'nearest' });
180
+ poll();
181
+ }
182
+
183
+ function showStatus(s) {
184
+ if (!s || s.state === 'idle') return;
185
+ ACTION_PANEL.hidden = false;
186
+ const p = s.state === 'running' ? pill('warn', 'running') : s.exitCode === 0 ? pill('ok', 'exit 0') : pill('error', s.timedOut ? 'timed out' : 'exit ' + s.exitCode);
187
+ OUT.status.replaceChildren(p, el('code', {}, txt(s.command)), el('span', { class: 'fix' }, (s.repo ? 'in ' + txt(s.repo) + ' · ' : '') + 'started ' + when(s.startedAt)));
188
+ OUT.pre.textContent = txt(s.output) || (s.state === 'running' ? '(no output yet)' : '(no output)');
189
+ OUT.pre.scrollTop = OUT.pre.scrollHeight;
190
+ }
191
+
192
+ function poll() {
193
+ clearTimeout(POLL);
194
+ POLL = setTimeout(async () => {
195
+ try {
196
+ const r = await api('/api/action/status');
197
+ if (r.ok) showStatus(r.json);
198
+ if (r.ok && r.json.state !== 'running') { load(); return; }
199
+ } catch (e) { /* retry */ }
200
+ poll();
201
+ }, 1000);
202
+ }
203
+
204
+ function srow(status, label, detail, ...ctl) {
205
+ const c = ctl.filter(Boolean);
206
+ return el('div', { class: 'srow' }, el('div', { class: 'srow-head' }, status, el('b', {}, label)),
207
+ detail ? el('div', { class: 'fix' }, detail) : null, c.length ? el('div', { class: 'srow-ctl' }, c) : null);
208
+ }
209
+
210
+ function machineSection(data) {
211
+ const s = el('section', { class: 'card' }, el('h2', {}, 'This machine'));
212
+ for (const i of data.machine.setup) {
213
+ if (i.id === 'codeburn-sync' || i.id === 'aws-credentials') continue;
214
+ let ctl = null;
215
+ if (i.id === 'codewatch-global' && i.status !== 'ok') {
216
+ const elsewhere = (data.machine.globalHooks || {}).state === 'elsewhere';
217
+ ctl = elsewhere ? copyOnly('prism-cli enable codewatch --global --force', 'replaces another global hooks path')
218
+ : runButton('codewatch-global', { label: 'Enable for every repo' });
219
+ } else if (i.fix) {
220
+ ctl = copyOnly(i.fix, i.id === 'gitleaks' ? 'installs software' : i.id === 'codeburn' ? 'asks before installing' : null);
221
+ }
222
+ s.append(srow(pill(i.status), i.label, i.detail, ctl));
223
+ }
224
+ s.append(el('p', { class: 'note' }, 'AWS credentials: see the AWS section.'));
225
+ return s;
226
+ }
227
+
228
+ const CW_PILL = { ok: 'ok', warn: 'warn', missing: 'missing', 'opted-out': 'skipped' };
229
+ function reposSection(data) {
230
+ const s = el('section', { class: 'card wide' }, el('h2', {}, 'Repos'));
231
+ const recheck = el('button', { type: 'button' }, 'Re-check status');
232
+ recheck.addEventListener('click', async () => { recheck.disabled = true; try { await api('/api/recheck'); } catch (e) { /* load() reports */ } await load(); });
233
+ s.append(el('div', { class: 'srow-ctl' }, recheck, el('span', { class: 'fix' }, 'Codewatch hooks and Security Agent setup per repo, from local files.')));
234
+ const ids = SETUP.repoIds || {}, cds = SETUP.repoCd || {}, cp = SETUP.copy || {};
235
+ const rowOf = (r) => {
236
+ const cw = el('div', {}, pill(CW_PILL[r.status] || 'missing', r.status === 'opted-out' ? 'opted out' : r.status + (r.status === 'ok' && r.global ? ' (global)' : '')), ' ',
237
+ el('span', { class: 'fix' }, r.status === 'ok' ? 'reviewer ' + txt(r.reviewer || 'none') : txt(r.detail)));
238
+ 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' })));
239
+ const sec = r.security || { status: 'missing' };
240
+ const sc = el('div', {}, pill(sec.status === 'missing' ? 'missing' : 'ok', sec.status === 'in-use' ? 'in use' : sec.status));
241
+ const cd = cds[r.path] ? cds[r.path] + ' && ' : '';
242
+ if (sec.status === 'missing') sc.append(copyOnly(sec.fix || cd + cp.enableSecurity, 'AWS'));
243
+ else sc.append(el('details', { class: 'copies' }, el('summary', {}, 'Security Agent commands (AWS, copy only)'),
244
+ [cp.codeReview, cp.threatModel, cp.pentest].map((c) => el('div', {}, copyButton(cd + c)))));
245
+ return [repoName(r), cw, sc];
246
+ };
247
+ const rows = data.overview.setup || [];
248
+ const shown = rows.filter((r) => !r.collapsed), hidden = rows.filter((r) => r.collapsed);
249
+ if (shown.length) s.append(table(['Repo', 'Codewatch', 'Security Agent'], shown.map(rowOf)));
250
+ else if (!hidden.length) s.append(el('p', { class: 'empty' }, 'No git repositories found under ' + txt(data.scope.root_display) + '.'));
251
+ if (hidden.length) s.append(el('details', { class: 'missing-group', 'data-key': 'setup-view:other' },
252
+ el('summary', {}, hidden.length + ' other repo' + (hidden.length === 1 ? '' : 's') + ': not set up'), table(['Repo', 'Codewatch', 'Security Agent'], hidden.map(rowOf))));
253
+ return s;
254
+ }
255
+
256
+ function syncSection(data) {
257
+ const s = el('section', { class: 'card' }, el('h2', {}, 'Usage sync (codeburn)'));
258
+ const item = data.machine.setup.find((i) => i.id === 'codeburn-sync');
259
+ const mode = (data.machine.codeburnSync || {}).mode || 'local';
260
+ if (item) s.append(srow(pill(item.status), item.label, item.detail));
261
+ if (item && item.help) s.append(helpBlock(item.help));
262
+ s.append(el('p', { class: 'fix' }, 'Sync mode: ' + mode));
263
+ s.append(srow(null, 'Local only', 'codeburn usage stays on this machine; no endpoint and no schedule.', runButton('codeburn-local', { label: 'Keep usage local' })));
264
+ 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' });
265
+ input.value = URL_DRAFT;
266
+ input.addEventListener('input', () => { URL_DRAFT = input.value; });
267
+ const msg = el('span', { class: 'msg', role: 'status' });
268
+ 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.',
269
+ input, runButton('codeburn-team', { label: 'Sync to team', urlInput: input, msg }), msg));
270
+ if (actionDef('codeburn-systemd') && actionDef('codeburn-systemd').available) {
271
+ 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.',
272
+ mode === 'team' ? runButton('codeburn-systemd', { label: 'Use systemd timer' }) : null));
273
+ }
274
+ 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' })));
275
+ s.append(srow(null, 'Status', 'codeburn, sync and schedule status.', runButton('codeburn-status', { label: 'Show status' })));
276
+ 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')));
277
+ return s;
278
+ }
279
+
280
+ const STEP_PILL = { used: ['ok', 'used'], skipped: ['skipped', 'skipped'], 'not-set': ['info', 'not set'], 'n/a': ['skipped', 'n/a'] };
281
+ function kv(pairs) { return el('dl', { class: 'kv' }, pairs.filter(Boolean).flatMap(([k, v]) => [el('dt', {}, k), el('dd', {}, v)])); }
282
+
283
+ function checkResult(r) {
284
+ if (!r) return null;
285
+ if (!r.ok) return el('div', {}, pill('error', 'refused'), ' ', txt(r.json.error));
286
+ const j = r.json;
287
+ if (j.ok) {
288
+ 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');
289
+ 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)));
290
+ }
291
+ return el('div', {}, pill('error', j.expired ? 'expired or invalid' : 'check failed'), ' ', el('span', {}, txt(j.error)),
292
+ 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))))));
293
+ }
294
+
295
+ function awsSection(a) {
296
+ const s = el('section', { class: 'card wide' }, el('h2', {}, 'AWS'));
297
+ 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.'));
298
+ s.append(el('div', { class: 'srow' }, el('div', {}, 'prism-cli would use ', el('b', {}, a.profile ? 'profile ' + txt(a.profile) : txt(a.source)), ' · ', el('span', { class: 'badge' }, txt(a.credentialLabel))),
299
+ el('div', { class: 'fix' }, 'chosen by ' + txt(a.source) + ' · region ' + (a.region ? txt(a.region.value) + ' (' + txt(a.region.source) + ')' : 'not set'))));
300
+ for (const w of a.warnings || []) s.append(el('div', { class: 'callout warn', role: 'note' }, txt(w)));
301
+ s.append(el('h3', {}, 'Resolution order'), el('ol', { class: 'steps' }, (a.steps || []).map((st) => {
302
+ const p = STEP_PILL[st.state] || ['skipped', st.state];
303
+ return el('li', {}, el('b', {}, txt(st.label)), ' ', pill(p[0], p[1]), ' ', el('span', { class: 'fix' }, txt(st.detail)));
304
+ })));
305
+ s.append(el('h3', {}, 'Saved aws connect'));
306
+ if (a.saved) {
307
+ const v = a.saved;
308
+ 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)')],
309
+ ['Region', txt(v.region)], ['Verified', v.verifiedAt ? el('span', { title: when(v.verifiedAt) }, ago(v.verifiedAt)) : 'unknown'], ['File', txt(v.path) + ' (' + v.scope + ')'],
310
+ ['Admin / org', 'not stored by aws connect (it prints them when it runs)']]));
311
+ } else s.append(el('p', { class: 'empty-line' }, 'None saved. ', copyButton((SETUP.copy || {}).awsConnect)));
312
+ const btn = el('button', { type: 'button', class: 'run' }, 'Check now');
313
+ const out = el('div', { 'aria-live': 'polite' }, checkResult(CHECK));
314
+ btn.addEventListener('click', async () => {
315
+ btn.disabled = true;
316
+ out.replaceChildren(el('span', { class: 'fix' }, 'checking...'));
317
+ try { CHECK = await api('/api/check-now', {}); } catch (e) { CHECK = { ok: false, json: { error: e.message } }; }
318
+ out.replaceChildren(checkResult(CHECK));
319
+ btn.disabled = false;
320
+ if (CHECK.ok && CHECK.json.updated) load();
321
+ });
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);
323
+ s.append(el('h3', {}, 'Read-only AWS commands'),
324
+ 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
+ srow(null, 'Team stack info', 'Reads the PRISM stack outputs (collector URL, user pool).', runButton('team-info', { label: 'Run team info' })));
326
+ const cp = SETUP.copy || {};
327
+ s.append(el('h3', {}, 'Copy only (writes to AWS or needs a terminal)'),
328
+ srow(null, 'Choose and save the AWS profile', 'Asks questions on a terminal.', copyOnly(cp.awsConnect, 'terminal')),
329
+ srow(null, 'Set up the Security Agent in a repo', 'Creates AWS resources.', copyOnly(cp.enableSecurity, 'AWS write')),
330
+ srow(null, 'Add a developer telemetry account', 'Creates a Cognito user.', copyOnly(cp.addDeveloper, 'AWS write')),
331
+ 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')));
332
+ return s;
333
+ }
334
+
335
+ async function renderSetup(data) {
336
+ try { const r = await api('/api/setup'); if (r.ok) SETUP = r.json; } catch (e) { /* keep the last good copy */ }
337
+ const root = document.getElementById('setup');
338
+ const openState = new Map([...root.querySelectorAll('details[data-key]')].map((d) => [d.getAttribute('data-key'), d.open]));
339
+ root.replaceChildren();
340
+ if (!SETUP) { root.append(el('section', { class: 'card wide' }, pill('error'), ' could not load the setup data')); return; }
341
+ root.append(machineSection(data), syncSection(data), reposSection(data), awsSection(SETUP.aws || {}), ACTION_PANEL);
342
+ for (const d of root.querySelectorAll('details[data-key]')) { const k = d.getAttribute('data-key'); if (openState.has(k)) d.open = openState.get(k); }
343
+ }
344
+ // A reload while an action runs picks its output back up.
345
+ api('/api/action/status').then((r) => { if (r.ok && r.json.state === 'running') { showStatus(r.json); poll(); } }).catch(() => {});
346
+ `;
347
+ //# sourceMappingURL=setup-page.js.map
@@ -0,0 +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,15 +1,20 @@
1
1
  import { type PrismResult } from '../codewatch/results.js';
2
2
  import { gitleaksInstallHint } from '../codewatch/gitleaks.js';
3
3
  import { type GlobalStatus } from '../codewatch/globalHooks.js';
4
- export type CheckStatus = 'ok' | 'warn' | 'missing';
4
+ import { type CheckHelp } from '../utils/sync-mode.js';
5
+ /** info: nothing to fix (a choice, like local-only sync); counted as neither ok nor a warning. */
6
+ export type CheckStatus = 'ok' | 'info' | 'warn' | 'missing';
5
7
  export interface CheckItem {
6
8
  id: string;
7
9
  label: string;
8
10
  status: CheckStatus;
9
11
  detail: string;
10
- /** One-line fix; empty when status is ok. */
12
+ /** One-line fix; empty when status is ok or info. */
11
13
  fix: string;
14
+ /** Guidance with doc links: a fix that needs the platform team, or what a chosen mode means. */
15
+ help?: CheckHelp;
12
16
  }
17
+ export type { CheckHelp };
13
18
  export interface MachineDeps {
14
19
  pathEnv?: string;
15
20
  home?: string;
@@ -26,6 +31,8 @@ export declare const GLOBAL_FORCE_CMD = "prism-cli enable codewatch --global --f
26
31
  /** The machine row for the global hooks (~/.prism/hooks via git config --global core.hooksPath). */
27
32
  export declare function globalHooksCheck(g: GlobalStatus): CheckItem;
28
33
  export { gitleaksInstallHint };
34
+ /** The codeburn sync row, by sync mode (utils/sync-mode.ts). */
35
+ export declare function codeburnSyncCheck(home: string, deps?: MachineDeps): CheckItem;
29
36
  /** Checks that do not depend on a repository. */
30
37
  export declare function machineChecks(deps?: MachineDeps): CheckItem[];
31
38
  export interface CodewatchSetup {
@@ -46,6 +53,8 @@ export interface CodewatchSetup {
46
53
  /** Empty when ok. */
47
54
  fix: string;
48
55
  }
56
+ /** Shell-quotes a path for the copy-paste fix line. */
57
+ export declare function shellPath(p: string): string;
49
58
  export declare function enableCodewatchFix(repo: string): string;
50
59
  /** The only per-repo check: does codewatch run here (per repo or globally), and which reviewer would run. */
51
60
  export declare function codewatchSetup(repo: string, deps?: {
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../src/dashboard/setup.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAA6E,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAM3I,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;AACpD,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,MAAM,CAAC;IAC7B,4FAA4F;IAC5F,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC;IACjC,0EAA0E;IAC1E,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,eAAO,MAAM,kBAAkB,wCAAwC,CAAC;AACxE,eAAO,MAAM,gBAAgB,gDAAgD,CAAC;AAE9E,oGAAoG;AACpG,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,YAAY,GAAG,SAAS,CAY3D;AAGD,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAM/B,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,IAAI,GAAE,WAAgB,GAAG,SAAS,EAAE,CA+CjE;AAWD,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,CAAC;IAChD,8DAA8D;IAC9D,MAAM,EAAE,OAAO,CAAC;IAChB,mFAAmF;IACnF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,yFAAyF;IACzF,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,GAAG,EAAE,MAAM,CAAC;CACb;AAOD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAOD,6GAA6G;AAC7G,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,CAAA;CAAO,GAAG,cAAc,CA+BnH;AAED,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,MAAM,EAAE,IAAI,GAAG,QAAQ,GAAG,SAAS,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,uEAAuE;AACvE,eAAO,MAAM,qBAAqB,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAEhE,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAW,EAAO,GAAG,aAAa,CAUtF"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../src/dashboard/setup.ts"],"names":[],"mappings":"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"}