codebase-onboarder 0.7.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -12
- package/bin/onboarder.js +0 -0
- package/cli/agent/command.d.ts +71 -0
- package/cli/agent/command.js +177 -0
- package/cli/agent/command.js.map +1 -0
- package/cli/agent/config.d.ts +31 -0
- package/cli/agent/config.js +98 -0
- package/cli/agent/config.js.map +1 -0
- package/cli/agent/contracts.d.ts +77 -0
- package/cli/agent/contracts.js +2 -0
- package/cli/agent/contracts.js.map +1 -0
- package/cli/agent/files.d.ts +19 -0
- package/cli/agent/files.js +109 -0
- package/cli/agent/files.js.map +1 -0
- package/cli/agent/gitSource.d.ts +18 -0
- package/cli/agent/gitSource.js +69 -0
- package/cli/agent/gitSource.js.map +1 -0
- package/cli/agent/github.d.ts +21 -0
- package/cli/agent/github.js +94 -0
- package/cli/agent/github.js.map +1 -0
- package/cli/agent/mcp.d.ts +15 -0
- package/cli/agent/mcp.js +78 -0
- package/cli/agent/mcp.js.map +1 -0
- package/cli/agent/process.d.ts +18 -0
- package/cli/agent/process.js +118 -0
- package/cli/agent/process.js.map +1 -0
- package/cli/agent/protocol.d.ts +3 -0
- package/cli/agent/protocol.js +26 -0
- package/cli/agent/protocol.js.map +1 -0
- package/cli/agent/runner.d.ts +34 -0
- package/cli/agent/runner.js +152 -0
- package/cli/agent/runner.js.map +1 -0
- package/cli/agent/stdio.d.ts +1 -0
- package/cli/agent/stdio.js +62 -0
- package/cli/agent/stdio.js.map +1 -0
- package/cli/agent/tools.d.ts +7 -0
- package/cli/agent/tools.js +225 -0
- package/cli/agent/tools.js.map +1 -0
- package/cli/chat/analysis.d.ts +26 -0
- package/cli/chat/analysis.js +188 -0
- package/cli/chat/analysis.js.map +1 -0
- package/cli/chat/app.d.ts +16 -0
- package/cli/chat/app.js +363 -0
- package/cli/chat/app.js.map +1 -0
- package/cli/chat/commands.d.ts +11 -0
- package/cli/chat/commands.js +122 -0
- package/cli/chat/commands.js.map +1 -0
- package/cli/chat/composer.d.ts +75 -0
- package/cli/chat/composer.js +467 -0
- package/cli/chat/composer.js.map +1 -0
- package/cli/chat/controller.d.ts +64 -0
- package/cli/chat/controller.js +402 -0
- package/cli/chat/controller.js.map +1 -0
- package/cli/chat/logo.d.ts +3 -0
- package/cli/chat/logo.js +14 -0
- package/cli/chat/logo.js.map +1 -0
- package/cli/chat/models.d.ts +18 -0
- package/cli/chat/models.js +88 -0
- package/cli/chat/models.js.map +1 -0
- package/cli/chat/profile.d.ts +5 -0
- package/cli/chat/profile.js +98 -0
- package/cli/chat/profile.js.map +1 -0
- package/cli/chat/render.d.ts +40 -0
- package/cli/chat/render.js +259 -0
- package/cli/chat/render.js.map +1 -0
- package/cli/chat/screen.d.ts +26 -0
- package/cli/chat/screen.js +117 -0
- package/cli/chat/screen.js.map +1 -0
- package/cli/chat/source.d.ts +29 -0
- package/cli/chat/source.js +116 -0
- package/cli/chat/source.js.map +1 -0
- package/cli/chat/store.d.ts +43 -0
- package/cli/chat/store.js +94 -0
- package/cli/chat/store.js.map +1 -0
- package/cli/chat/text.d.ts +4 -0
- package/cli/chat/text.js +27 -0
- package/cli/chat/text.js.map +1 -0
- package/cli/explorer/app.d.ts +1 -0
- package/cli/explorer/app.js +20 -1
- package/cli/explorer/app.js.map +1 -1
- package/cli/explorer/commands.js +5 -0
- package/cli/explorer/commands.js.map +1 -1
- package/cli/explorer/featureViews.d.ts +1 -0
- package/cli/explorer/featureViews.js +4 -0
- package/cli/explorer/featureViews.js.map +1 -1
- package/cli/main.js +47 -13
- package/cli/main.js.map +1 -1
- package/docs/HERMES_RESEARCH.md +51 -0
- package/package.json +2 -1
- package/public/app.js +98 -24
- package/public/app.js.map +1 -1
- package/public/assets/onboarder-mark.svg +11 -0
- package/public/browser.d.ts +3 -0
- package/public/browser.js.map +1 -1
- package/public/index.html +24 -7
- package/public/js/about.js +2 -2
- package/public/js/about.js.map +1 -1
- package/public/js/aiDraft.d.ts +1 -0
- package/public/js/aiDraft.js +66 -17
- package/public/js/aiDraft.js.map +1 -1
- package/public/js/api.d.ts +8 -5
- package/public/js/api.js +45 -22
- package/public/js/api.js.map +1 -1
- package/public/js/deepAnalysisView.js +2 -2
- package/public/js/deepAnalysisView.js.map +1 -1
- package/public/js/diagramPane.d.ts +1 -0
- package/public/js/diagramPane.js +5 -0
- package/public/js/diagramPane.js.map +1 -1
- package/public/js/docsView.js +6 -6
- package/public/js/docsView.js.map +1 -1
- package/public/js/dom.d.ts +8 -0
- package/public/js/dom.js.map +1 -1
- package/public/js/llm.d.ts +3 -1
- package/public/js/llm.js +15 -8
- package/public/js/llm.js.map +1 -1
- package/public/js/repoFiles.js +1 -1
- package/public/js/repoFiles.js.map +1 -1
- package/public/js/reviewView.js +1 -1
- package/public/js/reviewView.js.map +1 -1
- package/public/styles.css +25 -0
- package/server/gitDiff.d.ts +1 -1
- package/server/hermesChat.d.ts +13 -0
- package/server/hermesChat.js +145 -0
- package/server/hermesChat.js.map +1 -0
- package/server/http.d.ts +5 -4
- package/server/http.js +0 -4
- package/server/http.js.map +1 -1
- package/server/mcp/server.d.ts +1 -1
- package/server/mcp/server.js +2 -2
- package/server/mcp/server.js.map +1 -1
- package/server/router.js +7 -3
- package/server/router.js.map +1 -1
- package/server/tools/scan.js +16 -2
- package/server/tools/scan.js.map +1 -1
- package/server/tools.d.ts +1 -0
- package/server/tools.js +46 -6
- package/server/tools.js.map +1 -1
- package/shared/diagram/aiMermaid.js +16 -13
- package/shared/diagram/aiMermaid.js.map +1 -1
package/public/index.html
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
</button>
|
|
41
41
|
<button class="btn btn-ghost" id="themeBtn" title="Switch theme">Dark</button>
|
|
42
42
|
<button class="btn btn-ghost" id="newRepoBtn" hidden>New repo</button>
|
|
43
|
-
<button class="btn btn-ghost" id="settingsBtn">
|
|
43
|
+
<button class="btn btn-ghost" id="settingsBtn">AI connection</button>
|
|
44
44
|
<button class="btn btn-ghost" id="serverBtn" title="Server mode, network, access key, and tunnels">Server</button>
|
|
45
45
|
<span class="me-links" title="Built by Amit Pandey">
|
|
46
46
|
<a class="me-link" href="https://github.com/Amitpandey88" target="_blank" rel="noopener" title="Amit on GitHub" aria-label="Amit Pandey on GitHub">
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
<footer class="landing-foot" id="landingFoot">
|
|
162
162
|
<span>Everything runs locally.</span>
|
|
163
163
|
<span>Diagrams export as Mermaid or SVG.</span>
|
|
164
|
-
<span>Explanations optional —
|
|
164
|
+
<span>Explanations optional — connect Hermes or your own AI endpoint.</span>
|
|
165
165
|
<span class="foot-license" id="footCredit">MIT licensed · created by <a href="https://theamit.me" target="_blank" rel="noopener">Amit</a></span>
|
|
166
166
|
</footer>
|
|
167
167
|
</main>
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
<div class="crumbs" id="crumbs"></div>
|
|
186
186
|
<div class="stage-actions">
|
|
187
187
|
<button class="btn btn-ghost btn-sm" id="mmToggleBtn" hidden>Open folders</button>
|
|
188
|
-
<button class="btn btn-ghost btn-sm" id="aiDrawBtn">AI
|
|
188
|
+
<button class="btn btn-ghost btn-sm" id="aiDrawBtn">AI sketch</button>
|
|
189
189
|
<button class="btn btn-ghost btn-sm" id="copyMermaidBtn">Copy Mermaid</button>
|
|
190
190
|
<button class="btn btn-ghost btn-sm" id="svgBtn">Save SVG</button>
|
|
191
191
|
<button class="btn btn-ghost btn-sm" id="fitBtn">Fit</button>
|
|
@@ -297,10 +297,26 @@
|
|
|
297
297
|
<button class="btn btn-ghost btn-sm" id="drawerClose">Close</button>
|
|
298
298
|
</header>
|
|
299
299
|
<p class="drawer-note">
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
OpenRouter, Ollama, LM Studio…). Without one you still get the offline notes.
|
|
300
|
+
Connect with your existing Hermes login, or use your own AI endpoint.
|
|
301
|
+
Your offline notes are always available.
|
|
303
302
|
</p>
|
|
303
|
+
<label class="field">
|
|
304
|
+
<span class="field-label">Connect through</span>
|
|
305
|
+
<select id="setConnection" class="text-input">
|
|
306
|
+
<option value="api">API endpoint</option>
|
|
307
|
+
<option value="hermes">Hermes · use my configured login</option>
|
|
308
|
+
</select>
|
|
309
|
+
</label>
|
|
310
|
+
<section id="hermesConnection" class="hermes-connection" hidden aria-label="Hermes connection">
|
|
311
|
+
<div class="hermes-connection-title"><img src="/assets/onboarder-mark.svg" width="36" height="36" alt=""><div><strong>Powered by Hermes</strong><p>No API key to enter here</p></div></div>
|
|
312
|
+
<p id="hermesConnectionStatus" role="status">Checking your Hermes configuration…</p>
|
|
313
|
+
<dl class="hermes-connection-details"><div><dt>Model</dt><dd id="hermesModel">—</dd></div><div><dt>Provider</dt><dd id="hermesProvider">—</dd></div></dl>
|
|
314
|
+
<p class="drawer-note">Uses the same Hermes profile as the Onboarder CLI, on the computer running this server.</p>
|
|
315
|
+
<p class="drawer-note">To sign in or change models, run this in your terminal:</p>
|
|
316
|
+
<code class="hermes-setup-command">onboarder agent model</code>
|
|
317
|
+
<button class="btn btn-outline btn-sm" id="refreshHermes" type="button">Refresh configuration</button>
|
|
318
|
+
</section>
|
|
319
|
+
<div id="apiConnection">
|
|
304
320
|
<label class="field">
|
|
305
321
|
<span class="field-label">Base URL</span>
|
|
306
322
|
<input type="text" id="setBaseUrl" class="text-input" placeholder="https://api.openai.com/v1" spellcheck="false">
|
|
@@ -313,12 +329,13 @@
|
|
|
313
329
|
<span class="field-label">Model</span>
|
|
314
330
|
<input type="text" id="setModel" class="text-input" placeholder="gpt-4o-mini" spellcheck="false">
|
|
315
331
|
</label>
|
|
332
|
+
</div>
|
|
316
333
|
<div class="drawer-actions">
|
|
317
334
|
<button class="btn btn-ink" id="saveSettings">Save</button>
|
|
318
335
|
<button class="btn btn-outline" id="testSettings">Test connection</button>
|
|
319
336
|
</div>
|
|
320
337
|
<p class="drawer-status" id="settingsStatus"></p>
|
|
321
|
-
<p class="drawer-fine">
|
|
338
|
+
<p class="drawer-fine" id="aiConnectionNote">
|
|
322
339
|
Stored in this browser’s localStorage, forwarded through your local server,
|
|
323
340
|
never written to disk or logged. Close the tab and it stays here; clear site
|
|
324
341
|
data and it’s gone.
|
package/public/js/about.js
CHANGED
|
@@ -127,7 +127,7 @@ async function summarizeStackItem(name) {
|
|
|
127
127
|
return hooks.onToast('Unknown dependency.');
|
|
128
128
|
if (!llm.isConfigured()) {
|
|
129
129
|
hooks.onOpenSettings();
|
|
130
|
-
return hooks.onToast('
|
|
130
|
+
return hooks.onToast('Choose Hermes or an endpoint in AI connection first.');
|
|
131
131
|
}
|
|
132
132
|
const row = host.querySelector(`[data-stack-sum="${CSS.escape(name)}"]`)?.closest('.stack-item');
|
|
133
133
|
const body = row?.querySelector('[data-stack-body]');
|
|
@@ -166,7 +166,7 @@ async function summarizeStackItem(name) {
|
|
|
166
166
|
slot.innerHTML = mdLite(text) + '<span class="caret"></span>';
|
|
167
167
|
}
|
|
168
168
|
slot.innerHTML = mdLite(text)
|
|
169
|
-
+ `<p class="explain-src">${docsText ? `From ${escapeHtml(docsTitle)}` : 'No docs fetched — general knowledge'} · ${settings.model}</p>`;
|
|
169
|
+
+ `<p class="explain-src">${docsText ? `From ${escapeHtml(docsTitle)}` : 'No docs fetched — general knowledge'} · ${escapeHtml(settings.model)}</p>`;
|
|
170
170
|
}
|
|
171
171
|
catch (err) {
|
|
172
172
|
slot.innerHTML = `<p style="color:var(--warn)">${escapeHtml(err.message)}</p>`;
|
package/public/js/about.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"about.js","sourceRoot":"","sources":["../../src/public/js/about.ts"],"names":[],"mappings":"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","sourcesContent":["// The About view: what this repo is, what it's built from, and how the scan\n// that produced this page actually went.\n//\n// The rendering needs the DOM, but the parts worth checking — the coverage\n// prose, the GitHub URL parsing, date formatting — are pure and exported for\n// that reason. Nothing here runs at import time.\n\nimport { escapeHtml } from './html.js';\nimport { state } from './state.js';\nimport { streamExplain } from './api.js';\nimport { mdLite } from './markdown.js';\nimport * as llm from './llm.js';\n\nlet host = null;\nlet hooks = { onToast: (..._args: unknown[]) => {}, onOpenSettings: (..._args: unknown[]) => {}, repoOverview: () => '' };\n\nexport function initAbout(options) {\n host = options.host;\n hooks = { ...hooks, ...options };\n host.addEventListener('click', (event) => {\n const b = event.target.closest('[data-stack-sum]');\n if (b) summarizeStackItem(b.dataset.stackSum);\n });\n}\n\nexport function renderAbout() {\n if (state.about.rendered) return;\n state.about.rendered = true;\n const { scan, facts } = state;\n const gh = scan.gitUrl ? githubRepoPath(scan.gitUrl) : null;\n\n const sourceLine = scan.gitUrl\n ? `Cloned from ${scan.gitUrl}`\n : state.browserSource\n ? 'Picked in the browser — files never left this tab.'\n : `Read in place from ${scan.root}`;\n\n host.innerHTML = `\n <div class=\"doc-page\">\n <h1 class=\"doc-title\">${escapeHtml(scan.name)}</h1>\n <p class=\"doc-sub\">${escapeHtml(scan.tempId ? scan.tempId + ' — a temp clone' : scan.root)}</p>\n <p class=\"doc-sub\">${escapeHtml(sourceLine)}</p>\n\n <div class=\"about-numbers\">\n ${aboutNum(scan.stats.filesParsed, 'code files')}\n ${aboutNum(scan.stats.edgeCount, 'connections')}\n ${aboutNum(facts.entries.length, 'entry points')}\n ${aboutNum(facts.hubs.length, 'hubs')}\n ${aboutNum(facts.cycles.length, 'cycles')}\n ${aboutNum(Object.keys(scan.stats.languages).length, 'languages')}\n </div>\n\n <h2 class=\"doc-section-h\">Stack</h2>\n <div id=\"stackZone\"></div>\n\n ${renderHistorySection(state.history)}\n\n <h2 class=\"doc-section-h\">From the remote</h2>\n <div id=\"aboutRemote\">${\n gh\n ? '<span class=\"doc-readme-none\">Asking GitHub…</span>'\n : `<span class=\"doc-readme-none\">${scan.gitUrl ? 'Not a GitHub URL — no remote facts to fetch.' : 'A local repo — no remote to ask about.'}</span>`\n }</div>\n\n <h2 class=\"doc-section-h\">Lineage</h2>\n <ul class=\"about-lineage\">\n <li>Scanned ${new Date(scan.scannedAt).toLocaleString()} in ${scan.stats.tookMs} ms.</li>\n ${lineageCoverage(scan)}\n ${scan.tempId ? `<li>The clone lives in your temp folder as <code>${escapeHtml(scan.tempId)}</code> until you hit New repo.</li>` : ''}\n </ul>\n </div>`;\n\n if (gh) loadRemoteInfo(gh);\n if (state.stack) renderStackZone(host.querySelector('#stackZone'));\n}\n\nfunction renderHistorySection(h) {\n if (!h) return '';\n if (!h.available) {\n return `<h2 class=\"doc-section-h\">History</h2><p class=\"doc-readme-none\">${escapeHtml(h.reason || 'No history available.')}</p>`;\n }\n return `\n <h2 class=\"doc-section-h\">History & Activity</h2>\n <div class=\"about-numbers\">\n ${aboutNum(h.commitCount, h.truncated ? 'commits analyzed' : 'commits')}\n ${aboutNum(h.authors.length, 'contributors')}\n ${aboutNum(h.soloFiles, 'solo-author files')}\n ${aboutNum(h.coChanged.length, 'co-change pairs')}\n </div>\n <ul class=\"about-lineage\">\n ${h.firstCommitAt ? `<li>First commit: <b>${aboutDate(h.firstCommitAt)}</b> · Latest: <b>${aboutDate(h.lastCommitAt)}</b></li>` : ''}\n ${h.authors.length ? `<li>Top contributors: ${h.authors.slice(0, 5).map((a) => `<b>${escapeHtml(a.name || a.email)}</b> (${a.commits})`).join(', ')}.</li>` : ''}\n </ul>\n `;\n}\n\n// ---- Stack: detected languages, package managers, and frameworks ----------\n\nfunction renderStackZone(zone) {\n const st = state.stack;\n if (!st) return (zone.innerHTML = '');\n\n const langs = Object.entries(st.languages)\n .map(([name, count]) => `<span class=\"stack-lang\"><i>${count}</i> ${escapeHtml(name)}</span>`)\n .join('');\n\n const rows = st.items\n .map(\n (it) => `\n <div class=\"stack-item\">\n <span class=\"stack-name\">${escapeHtml(it.name)}</span>\n <span class=\"stack-cat\">${escapeHtml(it.category)}</span>\n ${it.version ? `<span class=\"stack-ver\">v${escapeHtml(it.version)}</span>` : ''}\n ${it.dev ? '<span class=\"stack-dev\">dev</span>' : ''}\n <span class=\"stack-lang\">${escapeHtml(it.lang)}</span>\n <a class=\"stack-doc\" href=\"${escapeHtml(it.docs)}\" target=\"_blank\" rel=\"noopener\">docs ↗</a>\n <button class=\"stack-sum\" data-stack-sum=\"${escapeHtml(it.name)}\" title=\"Summarize from official docs\">summarize</button>\n <div class=\"stack-body\" data-stack-body></div>\n </div>`\n )\n .join('');\n\n zone.innerHTML = `\n <div class=\"stack-block\">\n <div class=\"stack-langs\">${langs || '<span class=\"doc-readme-none\">No languages parsed.</span>'}</div>\n ${st.pm.length ? `<p class=\"stack-pm\">package managers: ${escapeHtml(st.pm.join(', '))}</p>` : ''}\n ${st.items.length\n ? `<div class=\"stack-grid\">${rows}</div>\n <p class=\"stack-hint\">Each “summarize” fetches that library’s official docs and folds them into an AI field note.</p>`\n : '<p class=\"doc-readme-none\">No declared dependencies — nothing to analyze.</p>'}\n </div>`;\n}\n\n// Fetch a library's official docs (server-side, SSRF-safe) and summarize it\n// with the AI, streamed into the row.\nasync function summarizeStackItem(name) {\n const item = state.stack.items.find((i) => i.name === name);\n if (!item) return hooks.onToast('Unknown dependency.');\n if (!llm.isConfigured()) {\n hooks.onOpenSettings();\n return hooks.onToast('Add an endpoint and model first.');\n }\n const row = host.querySelector(`[data-stack-sum=\"${CSS.escape(name)}\"]`)?.closest('.stack-item');\n const body = row?.querySelector('[data-stack-body]');\n if (!body) return;\n\n row.querySelector('.stack-sum').textContent = 'looking…';\n row.querySelector('.stack-sum').disabled = true;\n\n let docsText = '';\n let docsTitle = '';\n try {\n const r = await fetch('/api/doc', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ url: item.docs }),\n });\n const j = await r.json();\n if (j.text) {\n docsText = j.text;\n docsTitle = j.title || item.docs;\n }\n } catch { /* summarized from knowledge */ }\n\n body.innerHTML = '<br><div class=\"stack-body-text\"><span class=\"caret\"></span></div>';\n const slot = body.querySelector('.stack-body-text');\n const settings = llm.getSettings();\n const messages = llm.stackSummaryMessages({\n repoName: state.scan.name,\n item,\n docsText,\n repoNote: hooks.repoOverview(),\n });\n let text = '';\n try {\n for await (const delta of streamExplain({ ...settings, messages, maxTokens: 700 })) {\n text += delta;\n slot.innerHTML = mdLite(text) + '<span class=\"caret\"></span>';\n }\n slot.innerHTML = mdLite(text)\n + `<p class=\"explain-src\">${docsText ? `From ${escapeHtml(docsTitle)}` : 'No docs fetched — general knowledge'} · ${settings.model}</p>`;\n } catch (err) {\n slot.innerHTML = `<p style=\"color:var(--warn)\">${escapeHtml(err.message)}</p>`;\n } finally {\n const btn = row.querySelector('.stack-sum');\n if (btn) {\n btn.textContent = body.textContent.trim() ? 'summarize again' : 'summarize';\n btn.disabled = false;\n }\n }\n}\n\n// ---- the pure parts -------------------------------------------------------\n\nexport function aboutNum(value, label) {\n return `<div class=\"about-num\"><b>${value}</b><span>${label}</span></div>`;\n}\n\n// What the scan left out, itemized. The line here used to read \"N files skipped\n// by the ignore rules\", which was one number covering eight unrelated reasons —\n// most of them not the ignore rules at all.\nexport function lineageCoverage(scan) {\n const s = scan.stats;\n const k = s.skips || {};\n const out = [];\n\n if (s.truncated) {\n out.push(\n `<li><b>Partial scan.</b> Stopped at the ${s.truncated.atFiles}-file cap with ` +\n `${s.truncated.dirsQueued} ${s.truncated.dirsQueued === 1 ? 'directory' : 'directories'} ` +\n `still unvisited — every count below is a floor, not a total.</li>`\n );\n }\n\n const reasons = [\n [k.ignored, 'matched .gitignore'],\n [k.vendorDir, 'in vendor or build folders'],\n [k.notSource, 'not source (images, lockfiles, bundles)'],\n [k.notCode, 'no analyzer for the extension'],\n [k.tooLarge, 'over the size cap'],\n [k.readFailed, 'could not be read'],\n [k.analyzeFailed, 'could not be parsed'],\n [k.listFailed, 'directories that would not list'],\n ].filter(([n]) => n > 0).map(([n, why]) => `${n} ${why}`);\n if (reasons.length) {\n out.push(`<li>${s.filesParsed} of ${s.filesTotal} files analyzed. Left out: ${reasons.join('; ')}.</li>`);\n }\n\n const imp = s.imports;\n if (imp?.total) {\n const worst = imp.worst?.length\n ? ` Most common misses: ${imp.worst.slice(0, 3).map((w) => `<code>${escapeHtml(w.spec)}</code>`).join(', ')}.`\n : '';\n out.push(\n `<li><b>${imp.confidence}% of ${imp.total} imports placed</b> — ${imp.internal} internal, ` +\n `${imp.external} external packages, ${imp.unresolved} unresolved.${imp.unresolved ? worst : ''}</li>`\n );\n }\n return out.join('\\n');\n}\n\n// The GitHub URL parsing and the reading of the API payload also exist in\n// `/shared/analyzer/github.js`, where the terminal reads them. This copy is not\n// an oversight and cannot simply be deleted: this module is loaded by Node tests\n// (`tests/about.test.js`), and a `/shared/…` specifier does not resolve in Node,\n// so importing the shared version here would make this whole file unloadable\n// outside a browser. The duplication is therefore pinned by a test —\n// \"the two GitHub URL parsers agree\" — rather than left to drift, which is what\n// two unconnected copies of this regex had already done: the browser counted\n// `watchers_count` (a legacy alias for the *stargazer* count) and called it\n// \"watching\", printing the star number twice.\nexport function githubRepoPath(url) {\n const m = String(url ?? '').match(/github\\.com[:/]([^/\\s]+\\/[^/\\s]+?)(?:\\.git)?\\/?$/);\n return m ? m[1] : null;\n}\n\nexport function aboutDate(iso) {\n if (!iso) return 'unknown';\n return new Date(iso).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\nasync function loadRemoteInfo(repoPath) {\n const box = host.querySelector('#aboutRemote');\n try {\n const res = await fetch(`https://api.github.com/repos/${repoPath}`);\n if (!res.ok) {\n throw new Error(res.status === 403\n ? 'GitHub rate-limited the ask — unauthenticated allowance is 60 an hour.'\n : `GitHub answered ${res.status}.`);\n }\n const r = await res.json();\n // `subscribers_count`, not `watchers_count`. The API carries both and\n // `watchers_count` is a legacy alias for the *stargazer* count, so reading\n // it printed the star number a second time and called it \"watching\". The\n // real watcher count is the subscribers number.\n const watching = Number.isFinite(r.subscribers_count) ? r.subscribers_count : 0;\n const license = r.license?.spdx_id && r.license.spdx_id !== 'NOASSERTION' ? r.license.spdx_id : '';\n box.innerHTML = `\n <div class=\"about-numbers\">\n ${aboutNum(r.stargazers_count ?? 0, 'stars')}\n ${aboutNum(r.forks_count ?? 0, 'forks')}\n ${aboutNum(watching, 'watching')}\n ${aboutNum(r.open_issues_count ?? 0, 'open issues')}\n </div>\n ${r.description ? `<p class=\"about-desc\">${escapeHtml(r.description)}</p>` : ''}\n <ul class=\"about-lineage\">\n ${license ? `<li>License: ${escapeHtml(license)}</li>` : ''}\n <li>Created ${aboutDate(r.created_at)} · last push ${aboutDate(r.pushed_at)}.</li>\n ${r.default_branch ? `<li>Default branch: <code>${escapeHtml(r.default_branch)}</code></li>` : ''}\n ${r.topics?.length ? `<li>Topics: ${r.topics.map((t) => `<span class=\"stat-chip\">${escapeHtml(t)}</span>`).join(' ')}</li>` : ''}\n ${r.homepage ? `<li><a href=\"${escapeHtml(r.homepage)}\" target=\"_blank\" rel=\"noopener noreferrer\">${escapeHtml(r.homepage)}</a></li>` : ''}\n <li class=\"doc-readme-none\">These are GitHub's numbers, not this repository's — <code>health</code> and <code>risks</code> are about the code.</li>\n </ul>`;\n } catch (err) {\n box.innerHTML = `<span class=\"doc-readme-none\">${escapeHtml(err.message)}</span>`;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"about.js","sourceRoot":"","sources":["../../src/public/js/about.ts"],"names":[],"mappings":"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","sourcesContent":["// The About view: what this repo is, what it's built from, and how the scan\n// that produced this page actually went.\n//\n// The rendering needs the DOM, but the parts worth checking — the coverage\n// prose, the GitHub URL parsing, date formatting — are pure and exported for\n// that reason. Nothing here runs at import time.\n\nimport { escapeHtml } from './html.js';\nimport { state } from './state.js';\nimport { streamExplain } from './api.js';\nimport { mdLite } from './markdown.js';\nimport * as llm from './llm.js';\n\nlet host = null;\nlet hooks = { onToast: (..._args: unknown[]) => {}, onOpenSettings: (..._args: unknown[]) => {}, repoOverview: () => '' };\n\nexport function initAbout(options) {\n host = options.host;\n hooks = { ...hooks, ...options };\n host.addEventListener('click', (event) => {\n const b = event.target.closest('[data-stack-sum]');\n if (b) summarizeStackItem(b.dataset.stackSum);\n });\n}\n\nexport function renderAbout() {\n if (state.about.rendered) return;\n state.about.rendered = true;\n const { scan, facts } = state;\n const gh = scan.gitUrl ? githubRepoPath(scan.gitUrl) : null;\n\n const sourceLine = scan.gitUrl\n ? `Cloned from ${scan.gitUrl}`\n : state.browserSource\n ? 'Picked in the browser — files never left this tab.'\n : `Read in place from ${scan.root}`;\n\n host.innerHTML = `\n <div class=\"doc-page\">\n <h1 class=\"doc-title\">${escapeHtml(scan.name)}</h1>\n <p class=\"doc-sub\">${escapeHtml(scan.tempId ? scan.tempId + ' — a temp clone' : scan.root)}</p>\n <p class=\"doc-sub\">${escapeHtml(sourceLine)}</p>\n\n <div class=\"about-numbers\">\n ${aboutNum(scan.stats.filesParsed, 'code files')}\n ${aboutNum(scan.stats.edgeCount, 'connections')}\n ${aboutNum(facts.entries.length, 'entry points')}\n ${aboutNum(facts.hubs.length, 'hubs')}\n ${aboutNum(facts.cycles.length, 'cycles')}\n ${aboutNum(Object.keys(scan.stats.languages).length, 'languages')}\n </div>\n\n <h2 class=\"doc-section-h\">Stack</h2>\n <div id=\"stackZone\"></div>\n\n ${renderHistorySection(state.history)}\n\n <h2 class=\"doc-section-h\">From the remote</h2>\n <div id=\"aboutRemote\">${\n gh\n ? '<span class=\"doc-readme-none\">Asking GitHub…</span>'\n : `<span class=\"doc-readme-none\">${scan.gitUrl ? 'Not a GitHub URL — no remote facts to fetch.' : 'A local repo — no remote to ask about.'}</span>`\n }</div>\n\n <h2 class=\"doc-section-h\">Lineage</h2>\n <ul class=\"about-lineage\">\n <li>Scanned ${new Date(scan.scannedAt).toLocaleString()} in ${scan.stats.tookMs} ms.</li>\n ${lineageCoverage(scan)}\n ${scan.tempId ? `<li>The clone lives in your temp folder as <code>${escapeHtml(scan.tempId)}</code> until you hit New repo.</li>` : ''}\n </ul>\n </div>`;\n\n if (gh) loadRemoteInfo(gh);\n if (state.stack) renderStackZone(host.querySelector('#stackZone'));\n}\n\nfunction renderHistorySection(h) {\n if (!h) return '';\n if (!h.available) {\n return `<h2 class=\"doc-section-h\">History</h2><p class=\"doc-readme-none\">${escapeHtml(h.reason || 'No history available.')}</p>`;\n }\n return `\n <h2 class=\"doc-section-h\">History & Activity</h2>\n <div class=\"about-numbers\">\n ${aboutNum(h.commitCount, h.truncated ? 'commits analyzed' : 'commits')}\n ${aboutNum(h.authors.length, 'contributors')}\n ${aboutNum(h.soloFiles, 'solo-author files')}\n ${aboutNum(h.coChanged.length, 'co-change pairs')}\n </div>\n <ul class=\"about-lineage\">\n ${h.firstCommitAt ? `<li>First commit: <b>${aboutDate(h.firstCommitAt)}</b> · Latest: <b>${aboutDate(h.lastCommitAt)}</b></li>` : ''}\n ${h.authors.length ? `<li>Top contributors: ${h.authors.slice(0, 5).map((a) => `<b>${escapeHtml(a.name || a.email)}</b> (${a.commits})`).join(', ')}.</li>` : ''}\n </ul>\n `;\n}\n\n// ---- Stack: detected languages, package managers, and frameworks ----------\n\nfunction renderStackZone(zone) {\n const st = state.stack;\n if (!st) return (zone.innerHTML = '');\n\n const langs = Object.entries(st.languages)\n .map(([name, count]) => `<span class=\"stack-lang\"><i>${count}</i> ${escapeHtml(name)}</span>`)\n .join('');\n\n const rows = st.items\n .map(\n (it) => `\n <div class=\"stack-item\">\n <span class=\"stack-name\">${escapeHtml(it.name)}</span>\n <span class=\"stack-cat\">${escapeHtml(it.category)}</span>\n ${it.version ? `<span class=\"stack-ver\">v${escapeHtml(it.version)}</span>` : ''}\n ${it.dev ? '<span class=\"stack-dev\">dev</span>' : ''}\n <span class=\"stack-lang\">${escapeHtml(it.lang)}</span>\n <a class=\"stack-doc\" href=\"${escapeHtml(it.docs)}\" target=\"_blank\" rel=\"noopener\">docs ↗</a>\n <button class=\"stack-sum\" data-stack-sum=\"${escapeHtml(it.name)}\" title=\"Summarize from official docs\">summarize</button>\n <div class=\"stack-body\" data-stack-body></div>\n </div>`\n )\n .join('');\n\n zone.innerHTML = `\n <div class=\"stack-block\">\n <div class=\"stack-langs\">${langs || '<span class=\"doc-readme-none\">No languages parsed.</span>'}</div>\n ${st.pm.length ? `<p class=\"stack-pm\">package managers: ${escapeHtml(st.pm.join(', '))}</p>` : ''}\n ${st.items.length\n ? `<div class=\"stack-grid\">${rows}</div>\n <p class=\"stack-hint\">Each “summarize” fetches that library’s official docs and folds them into an AI field note.</p>`\n : '<p class=\"doc-readme-none\">No declared dependencies — nothing to analyze.</p>'}\n </div>`;\n}\n\n// Fetch a library's official docs (server-side, SSRF-safe) and summarize it\n// with the AI, streamed into the row.\nasync function summarizeStackItem(name) {\n const item = state.stack.items.find((i) => i.name === name);\n if (!item) return hooks.onToast('Unknown dependency.');\n if (!llm.isConfigured()) {\n hooks.onOpenSettings();\n return hooks.onToast('Choose Hermes or an endpoint in AI connection first.');\n }\n const row = host.querySelector(`[data-stack-sum=\"${CSS.escape(name)}\"]`)?.closest('.stack-item');\n const body = row?.querySelector('[data-stack-body]');\n if (!body) return;\n\n row.querySelector('.stack-sum').textContent = 'looking…';\n row.querySelector('.stack-sum').disabled = true;\n\n let docsText = '';\n let docsTitle = '';\n try {\n const r = await fetch('/api/doc', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ url: item.docs }),\n });\n const j = await r.json();\n if (j.text) {\n docsText = j.text;\n docsTitle = j.title || item.docs;\n }\n } catch { /* summarized from knowledge */ }\n\n body.innerHTML = '<br><div class=\"stack-body-text\"><span class=\"caret\"></span></div>';\n const slot = body.querySelector('.stack-body-text');\n const settings = llm.getSettings();\n const messages = llm.stackSummaryMessages({\n repoName: state.scan.name,\n item,\n docsText,\n repoNote: hooks.repoOverview(),\n });\n let text = '';\n try {\n for await (const delta of streamExplain({ ...settings, messages, maxTokens: 700 })) {\n text += delta;\n slot.innerHTML = mdLite(text) + '<span class=\"caret\"></span>';\n }\n slot.innerHTML = mdLite(text)\n + `<p class=\"explain-src\">${docsText ? `From ${escapeHtml(docsTitle)}` : 'No docs fetched — general knowledge'} · ${escapeHtml(settings.model)}</p>`;\n } catch (err) {\n slot.innerHTML = `<p style=\"color:var(--warn)\">${escapeHtml(err.message)}</p>`;\n } finally {\n const btn = row.querySelector('.stack-sum');\n if (btn) {\n btn.textContent = body.textContent.trim() ? 'summarize again' : 'summarize';\n btn.disabled = false;\n }\n }\n}\n\n// ---- the pure parts -------------------------------------------------------\n\nexport function aboutNum(value, label) {\n return `<div class=\"about-num\"><b>${value}</b><span>${label}</span></div>`;\n}\n\n// What the scan left out, itemized. The line here used to read \"N files skipped\n// by the ignore rules\", which was one number covering eight unrelated reasons —\n// most of them not the ignore rules at all.\nexport function lineageCoverage(scan) {\n const s = scan.stats;\n const k = s.skips || {};\n const out = [];\n\n if (s.truncated) {\n out.push(\n `<li><b>Partial scan.</b> Stopped at the ${s.truncated.atFiles}-file cap with ` +\n `${s.truncated.dirsQueued} ${s.truncated.dirsQueued === 1 ? 'directory' : 'directories'} ` +\n `still unvisited — every count below is a floor, not a total.</li>`\n );\n }\n\n const reasons = [\n [k.ignored, 'matched .gitignore'],\n [k.vendorDir, 'in vendor or build folders'],\n [k.notSource, 'not source (images, lockfiles, bundles)'],\n [k.notCode, 'no analyzer for the extension'],\n [k.tooLarge, 'over the size cap'],\n [k.readFailed, 'could not be read'],\n [k.analyzeFailed, 'could not be parsed'],\n [k.listFailed, 'directories that would not list'],\n ].filter(([n]) => n > 0).map(([n, why]) => `${n} ${why}`);\n if (reasons.length) {\n out.push(`<li>${s.filesParsed} of ${s.filesTotal} files analyzed. Left out: ${reasons.join('; ')}.</li>`);\n }\n\n const imp = s.imports;\n if (imp?.total) {\n const worst = imp.worst?.length\n ? ` Most common misses: ${imp.worst.slice(0, 3).map((w) => `<code>${escapeHtml(w.spec)}</code>`).join(', ')}.`\n : '';\n out.push(\n `<li><b>${imp.confidence}% of ${imp.total} imports placed</b> — ${imp.internal} internal, ` +\n `${imp.external} external packages, ${imp.unresolved} unresolved.${imp.unresolved ? worst : ''}</li>`\n );\n }\n return out.join('\\n');\n}\n\n// The GitHub URL parsing and the reading of the API payload also exist in\n// `/shared/analyzer/github.js`, where the terminal reads them. This copy is not\n// an oversight and cannot simply be deleted: this module is loaded by Node tests\n// (`tests/about.test.js`), and a `/shared/…` specifier does not resolve in Node,\n// so importing the shared version here would make this whole file unloadable\n// outside a browser. The duplication is therefore pinned by a test —\n// \"the two GitHub URL parsers agree\" — rather than left to drift, which is what\n// two unconnected copies of this regex had already done: the browser counted\n// `watchers_count` (a legacy alias for the *stargazer* count) and called it\n// \"watching\", printing the star number twice.\nexport function githubRepoPath(url) {\n const m = String(url ?? '').match(/github\\.com[:/]([^/\\s]+\\/[^/\\s]+?)(?:\\.git)?\\/?$/);\n return m ? m[1] : null;\n}\n\nexport function aboutDate(iso) {\n if (!iso) return 'unknown';\n return new Date(iso).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\nasync function loadRemoteInfo(repoPath) {\n const box = host.querySelector('#aboutRemote');\n try {\n const res = await fetch(`https://api.github.com/repos/${repoPath}`);\n if (!res.ok) {\n throw new Error(res.status === 403\n ? 'GitHub rate-limited the ask — unauthenticated allowance is 60 an hour.'\n : `GitHub answered ${res.status}.`);\n }\n const r = await res.json();\n // `subscribers_count`, not `watchers_count`. The API carries both and\n // `watchers_count` is a legacy alias for the *stargazer* count, so reading\n // it printed the star number a second time and called it \"watching\". The\n // real watcher count is the subscribers number.\n const watching = Number.isFinite(r.subscribers_count) ? r.subscribers_count : 0;\n const license = r.license?.spdx_id && r.license.spdx_id !== 'NOASSERTION' ? r.license.spdx_id : '';\n box.innerHTML = `\n <div class=\"about-numbers\">\n ${aboutNum(r.stargazers_count ?? 0, 'stars')}\n ${aboutNum(r.forks_count ?? 0, 'forks')}\n ${aboutNum(watching, 'watching')}\n ${aboutNum(r.open_issues_count ?? 0, 'open issues')}\n </div>\n ${r.description ? `<p class=\"about-desc\">${escapeHtml(r.description)}</p>` : ''}\n <ul class=\"about-lineage\">\n ${license ? `<li>License: ${escapeHtml(license)}</li>` : ''}\n <li>Created ${aboutDate(r.created_at)} · last push ${aboutDate(r.pushed_at)}.</li>\n ${r.default_branch ? `<li>Default branch: <code>${escapeHtml(r.default_branch)}</code></li>` : ''}\n ${r.topics?.length ? `<li>Topics: ${r.topics.map((t) => `<span class=\"stat-chip\">${escapeHtml(t)}</span>`).join(' ')}</li>` : ''}\n ${r.homepage ? `<li><a href=\"${escapeHtml(r.homepage)}\" target=\"_blank\" rel=\"noopener noreferrer\">${escapeHtml(r.homepage)}</a></li>` : ''}\n <li class=\"doc-readme-none\">These are GitHub's numbers, not this repository's — <code>health</code> and <code>risks</code> are about the code.</li>\n </ul>`;\n } catch (err) {\n box.innerHTML = `<span class=\"doc-readme-none\">${escapeHtml(err.message)}</span>`;\n }\n}\n"]}
|
package/public/js/aiDraft.d.ts
CHANGED
package/public/js/aiDraft.js
CHANGED
|
@@ -8,19 +8,21 @@
|
|
|
8
8
|
// Three states for one button: draw, drawn (flip back to static), and drawing.
|
|
9
9
|
// Nothing here is required for the app to work; with no endpoint configured the
|
|
10
10
|
// button opens the settings drawer and says so.
|
|
11
|
-
import { extractAIDiagram, matchNodeText } from '
|
|
11
|
+
import { extractAIDiagram, matchNodeText } from '../../shared/diagram/aiMermaid.js';
|
|
12
12
|
import { streamExplain } from './api.js';
|
|
13
13
|
import { state, aiViewKey } from './state.js';
|
|
14
14
|
import { gatherFileContext } from './repoFiles.js';
|
|
15
|
-
import { diagramTarget, externalsFor, overviewFacts, layersFacts, servicesFacts, fileFacts, tourStopFacts, folderFacts, } from '
|
|
15
|
+
import { diagramTarget, externalsFor, overviewFacts, layersFacts, servicesFacts, fileFacts, tourStopFacts, folderFacts, } from '../../shared/diagram/aiFacts.js';
|
|
16
16
|
import * as llm from './llm.js';
|
|
17
17
|
const MAX_TOKENS = 4000;
|
|
18
|
-
const PROGRESS_MS =
|
|
18
|
+
const PROGRESS_MS = 1000;
|
|
19
19
|
let btn = null;
|
|
20
20
|
let statusEl = null;
|
|
21
21
|
let nodeHost = null;
|
|
22
|
+
let pending = null;
|
|
22
23
|
let hooks = {
|
|
23
24
|
onRender: (..._args) => { },
|
|
25
|
+
onValidate: async (_source) => { },
|
|
24
26
|
onOpenFile: (..._args) => { },
|
|
25
27
|
onOpenSettings: (..._args) => { },
|
|
26
28
|
onToast: (..._args) => { },
|
|
@@ -31,33 +33,57 @@ export function initAiDraft(options) {
|
|
|
31
33
|
nodeHost = options.nodeHost;
|
|
32
34
|
hooks = { ...hooks, ...options };
|
|
33
35
|
btn.addEventListener('click', () => {
|
|
36
|
+
if (pending) {
|
|
37
|
+
stopAiDraft();
|
|
38
|
+
updateAiBtn();
|
|
39
|
+
hooks.onToast('Sketch cancelled.');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
34
42
|
const key = aiViewKey();
|
|
35
43
|
if (state.aiActiveKey === key) {
|
|
36
44
|
state.aiActiveKey = null; // back to static
|
|
45
|
+
updateAiBtn(key);
|
|
37
46
|
hooks.onRender();
|
|
38
47
|
return;
|
|
39
48
|
}
|
|
40
49
|
if (state.aiDiagrams[key]) {
|
|
41
50
|
state.aiActiveKey = key; // cached draft, instant
|
|
51
|
+
updateAiBtn(key);
|
|
42
52
|
hooks.onRender();
|
|
43
53
|
return;
|
|
44
54
|
}
|
|
45
|
-
drawWithAI(key);
|
|
55
|
+
return drawWithAI(key);
|
|
46
56
|
});
|
|
47
57
|
}
|
|
48
58
|
// Which of the three things the button currently offers. Called on every canvas
|
|
49
59
|
// render, because the answer depends on the view.
|
|
50
60
|
export function updateAiBtn(key = aiViewKey()) {
|
|
61
|
+
if (pending && (pending.scan !== state.scan || pending.key !== key))
|
|
62
|
+
stopAiDraft();
|
|
51
63
|
btn.hidden = state.view === 'docs' || state.view === 'about'
|
|
52
64
|
|| (state.view === 'atlas' && !state.atlasOpen);
|
|
53
|
-
if (
|
|
65
|
+
if (pending) {
|
|
66
|
+
btn.textContent = 'Cancel sketch';
|
|
67
|
+
btn.title = 'Stop generating this sketch';
|
|
68
|
+
btn.setAttribute('aria-busy', 'true');
|
|
69
|
+
}
|
|
70
|
+
else if (state.aiActiveKey === key) {
|
|
54
71
|
btn.textContent = 'Static';
|
|
55
72
|
btn.title = 'Back to the static diagram';
|
|
56
73
|
}
|
|
57
74
|
else {
|
|
58
|
-
btn.textContent = state.aiDiagrams[key] ? 'AI
|
|
75
|
+
btn.textContent = state.aiDiagrams[key] ? 'AI sketch ✓' : 'AI sketch';
|
|
59
76
|
btn.title = 'Ask the AI to draw this view';
|
|
60
77
|
}
|
|
78
|
+
if (!pending)
|
|
79
|
+
btn.removeAttribute('aria-busy');
|
|
80
|
+
}
|
|
81
|
+
export function stopAiDraft() {
|
|
82
|
+
pending?.controller.abort();
|
|
83
|
+
pending = null;
|
|
84
|
+
if (statusEl)
|
|
85
|
+
statusEl.hidden = true;
|
|
86
|
+
statusEl?.removeAttribute('aria-busy');
|
|
61
87
|
}
|
|
62
88
|
// AI nodes carry no stable ids — clicks are matched by label text instead.
|
|
63
89
|
export function wireAIClicks() {
|
|
@@ -75,53 +101,76 @@ export function wireAIClicks() {
|
|
|
75
101
|
async function drawWithAI(key) {
|
|
76
102
|
if (!llm.isConfigured()) {
|
|
77
103
|
hooks.onOpenSettings();
|
|
78
|
-
hooks.onToast('
|
|
104
|
+
hooks.onToast('Choose Hermes or an endpoint in AI connection first.');
|
|
79
105
|
return;
|
|
80
106
|
}
|
|
107
|
+
if (!state.scan)
|
|
108
|
+
return;
|
|
81
109
|
const settings = llm.getSettings();
|
|
82
|
-
|
|
83
|
-
|
|
110
|
+
const job = pending = { key, scan: state.scan, controller: new AbortController() };
|
|
111
|
+
updateAiBtn(key);
|
|
84
112
|
statusEl.hidden = false;
|
|
113
|
+
statusEl.setAttribute('aria-busy', 'true');
|
|
85
114
|
// Live progress: elapsed time plus how much the model has written so far. A
|
|
86
115
|
// reasoning model can be quiet for half a minute, and a canvas that says
|
|
87
116
|
// nothing is indistinguishable from one that has hung.
|
|
88
117
|
const startedAt = Date.now();
|
|
89
118
|
let received = 0;
|
|
90
119
|
const tick = () => {
|
|
120
|
+
if (pending !== job)
|
|
121
|
+
return;
|
|
91
122
|
const secs = Math.round((Date.now() - startedAt) / 1000);
|
|
92
123
|
const kb = received >= 1000 ? (received / 1000).toFixed(1) + 'k' : String(received);
|
|
93
|
-
statusEl.textContent =
|
|
124
|
+
statusEl.textContent = `${settings.connection === 'hermes' ? 'Hermes' : 'The AI'} is sketching this view… ${kb} chars · ${secs}s`;
|
|
94
125
|
};
|
|
95
126
|
tick();
|
|
96
127
|
const progressTimer = setInterval(tick, PROGRESS_MS);
|
|
97
128
|
try {
|
|
98
129
|
const { kind, facts } = await factsForView();
|
|
99
|
-
|
|
130
|
+
job.controller.signal.throwIfAborted();
|
|
131
|
+
const messages = llm.diagramMessages({ repoName: job.scan.name, kind, facts });
|
|
100
132
|
let text = '';
|
|
101
133
|
// Big budget, and no visible thinking where the dialect allows it:
|
|
102
134
|
// reasoning models otherwise spend the whole allowance before a single
|
|
103
135
|
// diagram line appears. Azure rejects that parameter outright.
|
|
104
136
|
const providerOptions = llm.isAzureHost(settings.baseUrl) ? {} : { reasoning: { exclude: true } };
|
|
105
|
-
for await (const delta of streamExplain({ ...settings, messages, maxTokens: MAX_TOKENS, providerOptions })) {
|
|
137
|
+
for await (const delta of streamExplain({ ...settings, messages, maxTokens: MAX_TOKENS, providerOptions, purpose: 'diagram', signal: job.controller.signal })) {
|
|
106
138
|
text += delta;
|
|
107
139
|
received += delta.length;
|
|
140
|
+
if (text.length > 120000)
|
|
141
|
+
throw new Error('The sketch was too large. Try a smaller folder or file.');
|
|
108
142
|
}
|
|
143
|
+
job.controller.signal.throwIfAborted();
|
|
109
144
|
const parsed = extractAIDiagram(text);
|
|
110
145
|
if (!parsed)
|
|
111
146
|
throw new Error('The model answered without a diagram. Try again.');
|
|
147
|
+
try {
|
|
148
|
+
await hooks.onValidate(parsed.body);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
throw new Error('The AI returned a diagram that could not be drawn. Try again.');
|
|
152
|
+
}
|
|
153
|
+
job.controller.signal.throwIfAborted();
|
|
154
|
+
if (pending !== job || state.scan !== job.scan || aiViewKey() !== key)
|
|
155
|
+
return;
|
|
112
156
|
state.aiDiagrams[key] = { body: parsed.body, caption: parsed.caption };
|
|
113
157
|
state.aiActiveKey = key;
|
|
114
158
|
statusEl.hidden = true;
|
|
115
|
-
hooks.onRender();
|
|
159
|
+
await hooks.onRender();
|
|
116
160
|
}
|
|
117
161
|
catch (err) {
|
|
118
|
-
|
|
119
|
-
|
|
162
|
+
if (pending === job && !job.controller.signal.aborted) {
|
|
163
|
+
statusEl.hidden = true;
|
|
164
|
+
hooks.onToast(err.message);
|
|
165
|
+
}
|
|
120
166
|
}
|
|
121
167
|
finally {
|
|
122
168
|
clearInterval(progressTimer);
|
|
123
|
-
|
|
124
|
-
|
|
169
|
+
if (pending === job) {
|
|
170
|
+
pending = null;
|
|
171
|
+
statusEl.removeAttribute('aria-busy');
|
|
172
|
+
updateAiBtn();
|
|
173
|
+
}
|
|
125
174
|
}
|
|
126
175
|
}
|
|
127
176
|
// Picks the sheet for the current view and fetches the source the file-shaped
|
package/public/js/aiDraft.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aiDraft.js","sourceRoot":"","sources":["../../src/public/js/aiDraft.ts"],"names":[],"mappings":"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","sourcesContent":["// The \"AI draft\" button: ask a model to redraw whatever the canvas is showing.\n//\n// Every view has a static diagram built from the import graph, and that is what\n// loads. This is the optional second opinion — the model gets a fact sheet (see\n// aiFacts.js) and answers with Mermaid, which is cached per view so flipping\n// between the two is instant afterwards.\n//\n// Three states for one button: draw, drawn (flip back to static), and drawing.\n// Nothing here is required for the app to work; with no endpoint configured the\n// button opens the settings drawer and says so.\n\nimport { extractAIDiagram, matchNodeText } from '/shared/diagram/aiMermaid.js';\nimport { streamExplain } from './api.js';\nimport { state, aiViewKey } from './state.js';\nimport { gatherFileContext } from './repoFiles.js';\nimport {\n diagramTarget, externalsFor, overviewFacts, layersFacts, servicesFacts,\n fileFacts, tourStopFacts, folderFacts,\n} from '/shared/diagram/aiFacts.js';\nimport * as llm from './llm.js';\n\nconst MAX_TOKENS = 4000;\nconst PROGRESS_MS = 250;\n\nlet btn = null;\nlet statusEl = null;\nlet nodeHost = null;\nlet hooks = {\n onRender: (..._args: unknown[]) => {},\n onOpenFile: (..._args: unknown[]) => {},\n onOpenSettings: (..._args: unknown[]) => {},\n onToast: (..._args: unknown[]) => {},\n};\n\nexport function initAiDraft(options) {\n btn = options.host;\n statusEl = options.statusEl;\n nodeHost = options.nodeHost;\n hooks = { ...hooks, ...options };\n\n btn.addEventListener('click', () => {\n const key = aiViewKey();\n if (state.aiActiveKey === key) {\n state.aiActiveKey = null; // back to static\n hooks.onRender();\n return;\n }\n if (state.aiDiagrams[key]) {\n state.aiActiveKey = key; // cached draft, instant\n hooks.onRender();\n return;\n }\n drawWithAI(key);\n });\n}\n\n// Which of the three things the button currently offers. Called on every canvas\n// render, because the answer depends on the view.\nexport function updateAiBtn(key = aiViewKey()) {\n btn.hidden = state.view === 'docs' || state.view === 'about'\n || (state.view === 'atlas' && !state.atlasOpen);\n if (state.aiActiveKey === key) {\n btn.textContent = 'Static';\n btn.title = 'Back to the static diagram';\n } else {\n btn.textContent = state.aiDiagrams[key] ? 'AI draft ✓' : 'AI draft';\n btn.title = 'Ask the AI to draw this view';\n }\n}\n\n// AI nodes carry no stable ids — clicks are matched by label text instead.\nexport function wireAIClicks() {\n for (const g of nodeHost.querySelectorAll('g.node')) {\n const path = matchNodeText(g.textContent, state.scan.files);\n if (!path) continue;\n g.style.cursor = 'pointer';\n g.addEventListener('click', (event) => {\n event.stopPropagation();\n hooks.onOpenFile(path);\n });\n }\n}\n\nasync function drawWithAI(key) {\n if (!llm.isConfigured()) {\n hooks.onOpenSettings();\n hooks.onToast('Add an endpoint and model first.');\n return;\n }\n const settings = llm.getSettings();\n btn.disabled = true;\n btn.textContent = 'drawing…';\n statusEl.hidden = false;\n\n // Live progress: elapsed time plus how much the model has written so far. A\n // reasoning model can be quiet for half a minute, and a canvas that says\n // nothing is indistinguishable from one that has hung.\n const startedAt = Date.now();\n let received = 0;\n const tick = () => {\n const secs = Math.round((Date.now() - startedAt) / 1000);\n const kb = received >= 1000 ? (received / 1000).toFixed(1) + 'k' : String(received);\n statusEl.textContent = `The AI is sketching this view… ${kb} chars · ${secs}s`;\n };\n tick();\n const progressTimer = setInterval(tick, PROGRESS_MS);\n\n try {\n const { kind, facts } = await factsForView();\n const messages = llm.diagramMessages({ repoName: state.scan.name, kind, facts });\n let text = '';\n // Big budget, and no visible thinking where the dialect allows it:\n // reasoning models otherwise spend the whole allowance before a single\n // diagram line appears. Azure rejects that parameter outright.\n const providerOptions = llm.isAzureHost(settings.baseUrl) ? {} : { reasoning: { exclude: true } };\n for await (const delta of streamExplain({ ...settings, messages, maxTokens: MAX_TOKENS, providerOptions })) {\n text += delta;\n received += delta.length;\n }\n const parsed = extractAIDiagram(text);\n if (!parsed) throw new Error('The model answered without a diagram. Try again.');\n state.aiDiagrams[key] = { body: parsed.body, caption: parsed.caption };\n state.aiActiveKey = key;\n statusEl.hidden = true;\n hooks.onRender();\n } catch (err) {\n statusEl.hidden = true;\n hooks.onToast(err.message);\n } finally {\n clearInterval(progressTimer);\n btn.disabled = false;\n updateAiBtn(key);\n }\n}\n\n// Picks the sheet for the current view and fetches the source the file-shaped\n// ones need. The choosing is pure and tested in aiFacts.js; this is the read.\nasync function factsForView() {\n const { scan, facts, manifest, patterns } = state;\n const target = diagramTarget(state);\n\n switch (target.view) {\n case 'map': return overviewFacts(scan, facts);\n case 'patterns': return layersFacts(facts, patterns);\n case 'services': return servicesFacts(scan, manifest);\n case 'tour': {\n const stop = state.tour.stops[state.tour.idx];\n if (!stop) return tourStopFacts({ stop: null });\n return tourStopFacts({\n ctx: await gatherFileContext(target.path),\n externals: externalsFor(scan, target.path),\n stop,\n index: state.tour.idx,\n total: state.tour.stops.length,\n });\n }\n case 'file': return fileFacts({\n ctx: await gatherFileContext(target.path),\n externals: externalsFor(scan, target.path),\n });\n default: return folderFacts(scan, facts, target.path);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"aiDraft.js","sourceRoot":"","sources":["../../src/public/js/aiDraft.ts"],"names":[],"mappings":"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","sourcesContent":["// The \"AI draft\" button: ask a model to redraw whatever the canvas is showing.\n//\n// Every view has a static diagram built from the import graph, and that is what\n// loads. This is the optional second opinion — the model gets a fact sheet (see\n// aiFacts.js) and answers with Mermaid, which is cached per view so flipping\n// between the two is instant afterwards.\n//\n// Three states for one button: draw, drawn (flip back to static), and drawing.\n// Nothing here is required for the app to work; with no endpoint configured the\n// button opens the settings drawer and says so.\n\nimport { extractAIDiagram, matchNodeText } from '../../shared/diagram/aiMermaid.js';\nimport { streamExplain } from './api.js';\nimport { state, aiViewKey } from './state.js';\nimport { gatherFileContext } from './repoFiles.js';\nimport {\n diagramTarget, externalsFor, overviewFacts, layersFacts, servicesFacts,\n fileFacts, tourStopFacts, folderFacts,\n} from '../../shared/diagram/aiFacts.js';\nimport * as llm from './llm.js';\n\nconst MAX_TOKENS = 4000;\nconst PROGRESS_MS = 1000;\n\nlet btn = null;\nlet statusEl = null;\nlet nodeHost = null;\nlet pending: { key: string; scan: typeof state.scan; controller: AbortController } | null = null;\nlet hooks = {\n onRender: (..._args: unknown[]) => {},\n onValidate: async (_source: string) => {},\n onOpenFile: (..._args: unknown[]) => {},\n onOpenSettings: (..._args: unknown[]) => {},\n onToast: (..._args: unknown[]) => {},\n};\n\nexport function initAiDraft(options) {\n btn = options.host;\n statusEl = options.statusEl;\n nodeHost = options.nodeHost;\n hooks = { ...hooks, ...options };\n\n btn.addEventListener('click', () => {\n if (pending) { stopAiDraft(); updateAiBtn(); hooks.onToast('Sketch cancelled.'); return; }\n const key = aiViewKey();\n if (state.aiActiveKey === key) {\n state.aiActiveKey = null; // back to static\n updateAiBtn(key);\n hooks.onRender();\n return;\n }\n if (state.aiDiagrams[key]) {\n state.aiActiveKey = key; // cached draft, instant\n updateAiBtn(key);\n hooks.onRender();\n return;\n }\n return drawWithAI(key);\n });\n}\n\n// Which of the three things the button currently offers. Called on every canvas\n// render, because the answer depends on the view.\nexport function updateAiBtn(key = aiViewKey()) {\n if (pending && (pending.scan !== state.scan || pending.key !== key)) stopAiDraft();\n btn.hidden = state.view === 'docs' || state.view === 'about'\n || (state.view === 'atlas' && !state.atlasOpen);\n if (pending) {\n btn.textContent = 'Cancel sketch';\n btn.title = 'Stop generating this sketch';\n btn.setAttribute('aria-busy', 'true');\n } else if (state.aiActiveKey === key) {\n btn.textContent = 'Static';\n btn.title = 'Back to the static diagram';\n } else {\n btn.textContent = state.aiDiagrams[key] ? 'AI sketch ✓' : 'AI sketch';\n btn.title = 'Ask the AI to draw this view';\n }\n if (!pending) btn.removeAttribute('aria-busy');\n}\n\nexport function stopAiDraft() {\n pending?.controller.abort();\n pending = null;\n if (statusEl) statusEl.hidden = true;\n statusEl?.removeAttribute('aria-busy');\n}\n\n// AI nodes carry no stable ids — clicks are matched by label text instead.\nexport function wireAIClicks() {\n for (const g of nodeHost.querySelectorAll('g.node')) {\n const path = matchNodeText(g.textContent, state.scan.files);\n if (!path) continue;\n g.style.cursor = 'pointer';\n g.addEventListener('click', (event) => {\n event.stopPropagation();\n hooks.onOpenFile(path);\n });\n }\n}\n\nasync function drawWithAI(key) {\n if (!llm.isConfigured()) {\n hooks.onOpenSettings();\n hooks.onToast('Choose Hermes or an endpoint in AI connection first.');\n return;\n }\n if (!state.scan) return;\n const settings = llm.getSettings();\n const job = pending = { key, scan: state.scan, controller: new AbortController() };\n updateAiBtn(key);\n statusEl.hidden = false;\n statusEl.setAttribute('aria-busy', 'true');\n\n // Live progress: elapsed time plus how much the model has written so far. A\n // reasoning model can be quiet for half a minute, and a canvas that says\n // nothing is indistinguishable from one that has hung.\n const startedAt = Date.now();\n let received = 0;\n const tick = () => {\n if (pending !== job) return;\n const secs = Math.round((Date.now() - startedAt) / 1000);\n const kb = received >= 1000 ? (received / 1000).toFixed(1) + 'k' : String(received);\n statusEl.textContent = `${settings.connection === 'hermes' ? 'Hermes' : 'The AI'} is sketching this view… ${kb} chars · ${secs}s`;\n };\n tick();\n const progressTimer = setInterval(tick, PROGRESS_MS);\n\n try {\n const { kind, facts } = await factsForView();\n job.controller.signal.throwIfAborted();\n const messages = llm.diagramMessages({ repoName: job.scan.name, kind, facts });\n let text = '';\n // Big budget, and no visible thinking where the dialect allows it:\n // reasoning models otherwise spend the whole allowance before a single\n // diagram line appears. Azure rejects that parameter outright.\n const providerOptions = llm.isAzureHost(settings.baseUrl) ? {} : { reasoning: { exclude: true } };\n for await (const delta of streamExplain({ ...settings, messages, maxTokens: MAX_TOKENS, providerOptions, purpose: 'diagram', signal: job.controller.signal })) {\n text += delta;\n received += delta.length;\n if (text.length > 120000) throw new Error('The sketch was too large. Try a smaller folder or file.');\n }\n job.controller.signal.throwIfAborted();\n const parsed = extractAIDiagram(text);\n if (!parsed) throw new Error('The model answered without a diagram. Try again.');\n try { await hooks.onValidate(parsed.body); }\n catch { throw new Error('The AI returned a diagram that could not be drawn. Try again.'); }\n job.controller.signal.throwIfAborted();\n if (pending !== job || state.scan !== job.scan || aiViewKey() !== key) return;\n state.aiDiagrams[key] = { body: parsed.body, caption: parsed.caption };\n state.aiActiveKey = key;\n statusEl.hidden = true;\n await hooks.onRender();\n } catch (err) {\n if (pending === job && !job.controller.signal.aborted) {\n statusEl.hidden = true;\n hooks.onToast(err.message);\n }\n } finally {\n clearInterval(progressTimer);\n if (pending === job) { pending = null; statusEl.removeAttribute('aria-busy'); updateAiBtn(); }\n }\n}\n\n// Picks the sheet for the current view and fetches the source the file-shaped\n// ones need. The choosing is pure and tested in aiFacts.js; this is the read.\nasync function factsForView() {\n const { scan, facts, manifest, patterns } = state;\n const target = diagramTarget(state);\n\n switch (target.view) {\n case 'map': return overviewFacts(scan, facts);\n case 'patterns': return layersFacts(facts, patterns);\n case 'services': return servicesFacts(scan, manifest);\n case 'tour': {\n const stop = state.tour.stops[state.tour.idx];\n if (!stop) return tourStopFacts({ stop: null });\n return tourStopFacts({\n ctx: await gatherFileContext(target.path),\n externals: externalsFor(scan, target.path),\n stop,\n index: state.tour.idx,\n total: state.tour.stops.length,\n });\n }\n case 'file': return fileFacts({\n ctx: await gatherFileContext(target.path),\n externals: externalsFor(scan, target.path),\n });\n default: return folderFacts(scan, facts, target.path);\n }\n}\n"]}
|
package/public/js/api.d.ts
CHANGED
|
@@ -11,15 +11,18 @@ export declare function fetchMcpStatus(): Promise<any>;
|
|
|
11
11
|
export declare function startMcpServer(): Promise<any>;
|
|
12
12
|
export declare function stopMcpServer(): Promise<any>;
|
|
13
13
|
export declare function fetchMcpCommand(): Promise<any>;
|
|
14
|
-
export declare function streamExplain({ baseUrl, apiKey, model, messages, maxTokens, providerOptions, signal }: {
|
|
15
|
-
apiKey
|
|
16
|
-
baseUrl
|
|
14
|
+
export declare function streamExplain({ connection, baseUrl, apiKey, model, messages, maxTokens, providerOptions, purpose, signal }: {
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
connection?: string;
|
|
17
18
|
maxTokens?: number;
|
|
18
19
|
messages: any;
|
|
19
|
-
model
|
|
20
|
+
model?: string;
|
|
20
21
|
providerOptions?: {};
|
|
22
|
+
purpose?: string;
|
|
21
23
|
signal?: AbortSignal;
|
|
22
|
-
}): AsyncGenerator<
|
|
24
|
+
}): AsyncGenerator<string, void, unknown>;
|
|
25
|
+
export declare function fetchHermesStatus(signal?: AbortSignal): Promise<any>;
|
|
23
26
|
export declare function fetchServerSettings(): Promise<any>;
|
|
24
27
|
export declare function updateServerSettings(patch: any): Promise<any>;
|
|
25
28
|
export declare function rotateServerAccessKey(): Promise<any>;
|
package/public/js/api.js
CHANGED
|
@@ -158,12 +158,13 @@ export async function fetchMcpCommand() {
|
|
|
158
158
|
// Streams an OpenAI-compatible chat completion through the local proxy.
|
|
159
159
|
// Yields text deltas as they arrive; throws with the provider's message on
|
|
160
160
|
// a non-200 from upstream.
|
|
161
|
-
export async function* streamExplain({ baseUrl, apiKey, model, messages, maxTokens = 1200, providerOptions = {}, signal = undefined }) {
|
|
161
|
+
export async function* streamExplain({ connection = 'api', baseUrl = '', apiKey = '', model = '', messages, maxTokens = 1200, providerOptions = {}, purpose = '', signal = undefined }) {
|
|
162
162
|
const res = await fetch('/api/explain', {
|
|
163
163
|
signal,
|
|
164
164
|
method: 'POST',
|
|
165
165
|
headers: { 'content-type': 'application/json' },
|
|
166
|
-
body: JSON.stringify(
|
|
166
|
+
body: JSON.stringify(connection === 'hermes' ? { connection, messages, stream: true, ...(purpose === 'diagram' ? { purpose } : {}) }
|
|
167
|
+
: { baseUrl, apiKey, model, messages, stream: true, max_tokens: maxTokens, ...providerOptions }),
|
|
167
168
|
});
|
|
168
169
|
if (!res.ok) {
|
|
169
170
|
let detail = '';
|
|
@@ -176,35 +177,57 @@ export async function* streamExplain({ baseUrl, apiKey, model, messages, maxToke
|
|
|
176
177
|
}
|
|
177
178
|
throw new Error(detail || `The provider said ${res.status}.`);
|
|
178
179
|
}
|
|
180
|
+
if (!res.body)
|
|
181
|
+
throw new Error('The connection returned no answer stream.');
|
|
179
182
|
const reader = res.body.getReader();
|
|
180
183
|
const decoder = new TextDecoder();
|
|
181
184
|
let buffer = '';
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
185
|
+
try {
|
|
186
|
+
while (true) {
|
|
187
|
+
const { done, value } = await reader.read();
|
|
188
|
+
if (done)
|
|
189
|
+
break;
|
|
190
|
+
buffer = (buffer + decoder.decode(value, { stream: true })).replace(/\r\n/g, '\n');
|
|
191
|
+
if (buffer.length > 2 * 1024 * 1024)
|
|
192
|
+
throw new Error('The answer exceeded the stream limit.');
|
|
193
|
+
const frames = buffer.split('\n\n');
|
|
194
|
+
buffer = frames.pop();
|
|
195
|
+
for (const frame of frames) {
|
|
196
|
+
for (const line of frame.split('\n')) {
|
|
197
|
+
if (!line.startsWith('data:'))
|
|
198
|
+
continue;
|
|
199
|
+
const payload = line.slice(5).trim();
|
|
200
|
+
if (payload === '[DONE]')
|
|
201
|
+
return;
|
|
202
|
+
let json;
|
|
203
|
+
try {
|
|
204
|
+
json = JSON.parse(payload);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (json.error)
|
|
210
|
+
throw new Error(typeof json.error === 'string' ? json.error : json.error.message || 'The assistant could not finish its answer.');
|
|
198
211
|
const delta = json.choices?.[0]?.delta?.content;
|
|
199
|
-
if (delta)
|
|
212
|
+
if (typeof delta === 'string' && delta)
|
|
200
213
|
yield delta;
|
|
201
214
|
}
|
|
202
|
-
catch {
|
|
203
|
-
/* a partial JSON frame — the next chunk completes it */
|
|
204
|
-
}
|
|
205
215
|
}
|
|
206
216
|
}
|
|
217
|
+
if (connection === 'hermes')
|
|
218
|
+
throw new Error('Hermes disconnected before finishing its answer. Try again.');
|
|
207
219
|
}
|
|
220
|
+
finally {
|
|
221
|
+
await reader.cancel().catch(() => { });
|
|
222
|
+
reader.releaseLock();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
export async function fetchHermesStatus(signal) {
|
|
226
|
+
const res = await fetch('/api/hermes', { signal });
|
|
227
|
+
const data = await res.json();
|
|
228
|
+
if (!res.ok)
|
|
229
|
+
throw new Error(data.error || 'Could not check Hermes.');
|
|
230
|
+
return data;
|
|
208
231
|
}
|
|
209
232
|
// The Server drawer's three round-trips. The read is a GET that comes back
|
|
210
233
|
// with the secret masked; writes go through PUT with only the changed keys;
|