vault-go 0.31.2 → 0.33.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.
@@ -6,7 +6,7 @@ export function dashboardHtml() {
6
6
 
7
7
  /* Same identity tokens and glass surfaces as the Vault portal. */
8
8
  :root{--c-canvas:8 11 18;--c-surface:24 29 45;--c-surface-2:32 38 56;--c-line:53 61 81;--c-fg:241 243 252;--c-muted:172 180 203;--c-faint:148 159 187;--c-accent:185 182 255;color:rgb(var(--c-fg));background:rgb(var(--c-canvas))}body{background:radial-gradient(ellipse at 85% 8%,#534ca02e,transparent 45%),linear-gradient(145deg,#080b12,#101321 58%,#080b12)}[hidden]{display:none!important}.shell{grid-template-columns:258px 1fr;max-width:1600px}.rail{background:linear-gradient(160deg,#1c2135f5,#101522f5);border-color:#cdd8ff26}.brand{font-size:15px;letter-spacing:-.3px;gap:10px;white-space:nowrap}.brand img,.onboarding-logo{object-fit:contain;filter:drop-shadow(0 5px 12px #7775c533)}.brand-sub{display:block;font-size:9px;letter-spacing:1.7px;color:#acb4cb;margin-top:7px}.card,.panel,.onboarding,.step,.notice{background:linear-gradient(135deg,#272c3ed1,#131724e0);border-color:#cdd8ff26;box-shadow:0 24px 70px -30px #0009,inset 0 1px 0 #ffffff0c;backdrop-filter:blur(28px) saturate(135%)}.nav a.active,.nav a:hover{background:#b9b6ff20;color:#d3d1ff}.eyebrow,.step-number,.empty-symbol{color:#b9b6ff}p,.card-label{color:#acb4cb}.card-note,.row-meta,.rail-footer,.rail-label,.footer{color:#949fbb}.pulse{color:#b9b6ff;border-color:#b9b6ff30;background:#b9b6ff0d}.button{background:#202638;color:#f1f3fc;border-color:#cdd8ff26;text-decoration:none;justify-content:center}.button:hover{border-color:#b9b6ff88}.primary{background:linear-gradient(135deg,#c8c5ff,#9d9bed);color:#15152f;border-color:transparent;font-weight:650;padding:13px 20px;font-size:14px}.primary:hover{background:#d3d1ff}.onboarding{border:1px solid #cdd8ff26;border-radius:20px;overflow:hidden;margin-top:30px;animation:arrive .6s ease both}.onboarding-hero{position:relative;padding:38px;background:radial-gradient(ellipse at 100% 0%,#7370d02c,transparent 62%)}.onboarding-logo{width:86px;height:86px;margin-bottom:24px}.onboarding h1{font-size:36px;max-width:600px;line-height:1.12;margin:12px 0 18px}.onboarding-hero>p{max-width:570px;font-size:14px;line-height:1.8}.actions{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:25px}.link{color:#d3d1ff;text-decoration:none;font-size:12px}.link:hover{text-decoration:underline}.onboarding-hint{font-size:11px!important;color:#949fbb;margin-top:15px!important}.steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));border-top:1px solid #cdd8ff26}.step{padding:25px;background:#080b122b;box-shadow:none;border-right:1px solid #cdd8ff16}.step:last-child{border:0}.step-number{font-family:monospace;font-size:11px;margin-bottom:13px}.step h2{margin-bottom:9px}.step p{font-size:12px}.terminal-fallback{padding:22px 30px;border-top:1px solid #cdd8ff26;background:#080b1233}.terminal-fallback p{font-size:12px}.terminal-fallback code{display:block;margin-top:10px;font-size:12px;color:#d3d1ff;overflow-wrap:anywhere}.connection-message{margin-top:18px;font-size:12px;color:#d3d1ff;min-height:18px}.next-step{display:flex;justify-content:space-between;align-items:center;gap:20px;margin-top:22px;padding:23px;border:1px solid #b9b6ff30;border-radius:14px;background:#b9b6ff09}.next-step h2{margin-bottom:8px}.next-step p{font-size:12px}.next-step .actions{margin:0;flex-shrink:0}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible{outline-color:#b9b6ff}.notice code{color:#d3d1ff}.search{border-color:#cdd8ff26;background:#0307117a}.breadcrumb b{color:#f1f3fc}.rail-footer .dot{background:#b9b6ff}.footer{margin-top:28px}@media(max-width:1100px){.shell{grid-template-columns:225px 1fr}.brand{font-size:13px}.main{padding:25px}.onboarding-hero{padding:30px}.next-step{align-items:flex-start;flex-direction:column}}@media(max-width:680px){.shell{display:block}.brand{font-size:16px}.onboarding{margin-top:22px}.onboarding-hero{padding:26px}.onboarding h1{font-size:30px}.onboarding-logo{width:65px;height:65px;margin-bottom:15px}.steps{grid-template-columns:1fr}.step{padding:20px 26px;border-right:0;border-bottom:1px solid #cdd8ff16}.step-number{margin-bottom:8px}.terminal-fallback{padding:22px 26px}.main{padding:20px}.next-step .actions{flex-shrink:1}.onboarding .actions .button{width:100%}.onboarding .actions .link{padding:8px 0}.card-value{overflow-wrap:anywhere}.nav{padding-bottom:2px}}
9
- .danger{border-color:#ed9a9a66;color:#ffb5b5;background:#53272d55}.danger:hover{border-color:#ffb5b5}.danger-title{color:#ffb5b5}.pagination{display:flex;gap:9px;align-items:center;flex-wrap:wrap;border-top:1px solid #cdd8ff20;margin-top:15px;padding-top:15px;color:#acb4cb;font-size:11px}.pagination label{display:flex;align-items:center;gap:7px}.pagination .page-summary{flex:1;min-width:95px}.pagination select{padding:7px}.pagination .button{padding:7px 10px}.pagination button:disabled{cursor:default}.row-open{background:transparent;border:0;text-align:left;padding:0;color:#d3d1ff;cursor:pointer;font:inherit}.row-open:hover{text-decoration:underline}.row-actions{padding-top:5px}.detail-dialog{color:#f1f3fc;background:linear-gradient(135deg,#272c3e,#131724);border:1px solid #cdd8ff40;border-radius:18px;max-width:800px;width:calc(100% - 32px);max-height:85vh;padding:26px;box-shadow:0 30px 100px #0009}.detail-dialog::backdrop{background:#050710bd;backdrop-filter:blur(5px)}.detail-dialog h2{font-size:20px;overflow-wrap:anywhere}.detail-fields{margin:0}.detail-fields dt{color:#acb4cb;font-size:11px;margin:20px 0 8px}.detail-fields dd{margin:0;font-size:13px;line-height:1.7;white-space:pre-wrap;overflow-wrap:anywhere}.detail-fields pre{font:inherit;white-space:pre-wrap;margin:0}.engine-options{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;margin-top:18px}.engine-keys{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px;margin-top:16px}.engine-keys label{display:block;font-size:12px;margin-bottom:8px}.engine-keys input{width:100%;background:#0307117a;color:#f1f3fc;border:1px solid #cdd8ff26;border-radius:9px;padding:10px 12px;font:inherit;font-size:13px;margin-bottom:8px}.engine-option{display:block;padding:17px;border:1px solid #cdd8ff26;border-radius:10px;background:#080b1233;cursor:pointer}.engine-option:has(input:checked){border-color:#b9b6ff;background:#b9b6ff12}.engine-option:has(input:disabled){opacity:.7;cursor:default}.engine-option input{accent-color:#b9b6ff;margin-right:8px}.engine-option strong{font-size:12px}.engine-option p{font-size:11px;margin-top:9px}.engine-message{margin-top:13px;font-size:12px;color:#d3d1ff}.context-editor{margin-top:20px}.context-editor label{display:block;font-size:12px;margin-bottom:8px}.context-editor textarea{width:100%;min-height:130px;resize:vertical;background:#0307117a;color:#f1f3fc;border:1px solid #cdd8ff26;border-radius:9px;padding:12px;font:inherit;font-size:13px}.context-editor textarea:focus-visible{outline:2px solid #b9b6ff;outline-offset:3px}.mcp-clients{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px;margin-top:14px}.mcp-client{display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid #cdd8ff26;border-radius:10px;padding:10px 12px;background:#080b1233;font-size:13px;flex-wrap:wrap}.mcp-client strong{font-size:13px;font-weight:600}.mcp-state{font-size:11px;color:#8edbc8;white-space:nowrap}.mcp-dim{opacity:.75}.mcp-evidence{font-size:10px;color:#949fbb;width:100%}.mcp-note{color:#949fbb;font-size:12px}.mcp-message{color:#ed9a9a;font-size:12px;margin-top:10px}.mcp-message:empty{display:none}.mcp-fallback{color:#949fbb;font-size:11px;margin-top:12px}.mcp-fallback code{font-size:11px;color:#c7d4df;background:#0307117a;border-radius:6px;padding:2px 6px}@media(max-width:800px){.engine-options{grid-template-columns:1fr}}@media(prefers-reduced-motion:reduce){.detail-dialog::backdrop{backdrop-filter:none}}</style><script src="/app.js" defer></script></head><body><div class="shell"><aside class="rail"><div class="brand"><img src="/logo.png" alt="" width="46" height="46"><div>AI Vault Memory<span class="brand-sub">LOCAL AGENT</span></div></div><div class="rail-label" data-i18n="workspace">WORKSPACE</div><nav class="nav" aria-label="Navigation"><a class="active" href="#overview"><span class="icon">◈</span><span data-i18n="overview">Overview</span></a><a href="#activity"><span class="icon">⌁</span><span data-i18n="activity">Activity</span></a><a href="#memories"><span class="icon">▤</span><span data-i18n="memories">Memories</span></a><a href="#context"><span class="icon">✧</span><span data-i18n="contextEngine">Context engine</span></a><a href="#projects"><span class="icon">▱</span><span data-i18n="projects">Projects</span></a></nav><div class="rail-footer"><span class="dot"></span><span data-i18n="localWorkspace">Your local workspace</span><br><span data-i18n="privacy">Activity metadata stays local.</span></div></aside><main class="main" id="overview"><header class="top"><div class="breadcrumb">Vault <span aria-hidden="true"> / </span><b data-i18n="localMonitor">Local monitor</b></div><div class="controls"><select id="locale" aria-label="Language"><option value="en">English</option><option value="pt">Português</option><option value="es">Español</option></select><button class="button" id="logout" type="button" hidden data-i18n="logout">Sign out of dashboard</button><button class="button" id="reconnect" type="button" hidden data-i18n="reconnect">Reconnect account</button><button class="button" id="refresh" type="button"><span aria-hidden="true">↻</span><span data-i18n="refresh">Refresh cloud</span></button></div></header><div id="error" class="error" role="status" aria-live="polite"></div><section class="onboarding" id="onboarding"><div class="onboarding-hero"><img class="onboarding-logo" src="/logo.png" alt="" width="86" height="86"><div class="eyebrow" data-i18n="connectEyebrow">AI VAULT MEMORY · LOCAL</div><h1 data-i18n="connectTitle">Your memory. Connected to this machine.</h1><p data-i18n="connectIntro">Connect your Vault account to see your memories, projects and recent activity in one place.</p><div class="actions"><button type="button" class="button primary" id="connect" data-i18n="connect">Connect to Vault</button><a class="link" href="${VAULT_WEB_APP_URL}" target="_blank" rel="noopener noreferrer" data-i18n="openPortal">Open Vault portal ↗</a></div><p class="onboarding-hint" data-i18n="connectHint">Sign in securely in a separate window. Your password stays with the Vault portal.</p><div class="connection-message" id="connection-message" role="status" aria-live="polite"></div><a id="auth-fallback" class="button" hidden target="_blank" rel="noopener noreferrer" data-i18n="continueAuth">Continue sign-in ↗</a></div><div class="steps"><article class="step"><div class="step-number">01 / VAULT</div><h2 data-i18n="stepAccount">Connect your account</h2><p data-i18n="stepAccountBody">Sign in to Vault and authorize this local agent.</p></article><article class="step"><div class="step-number">02 / MCP</div><h2 data-i18n="stepClient">Connect your AI clients</h2><p data-i18n="stepClientBody">After signing in, connect Claude, Codex and other clients with one click, right here.</p></article><article class="step"><div class="step-number">03 / MEMORY</div><h2 data-i18n="stepMemory">Build your first memory</h2><p data-i18n="stepMemoryBody">Ask your connected client to save useful context, then follow its activity here.</p></article></div><div class="terminal-fallback"><button class="button" id="verify-connection" type="button" data-i18n="verifyConnection">Already signed in? Check connection</button><p style="margin-top:16px" data-i18n="terminalFallback">Already connected through the CLI? Open a secure local session:</p><code>bunx --bun vault-go@latest local open</code></div></section><div id="dashboard" hidden><div class="welcome"><div><div class="eyebrow" data-i18n="eyebrow">YOUR KNOWLEDGE, CONNECTED</div><h1 data-i18n="title">Your local workspace.</h1><p data-i18n="subtitle">Your Vault account, memories and activity on this machine.</p></div><img src="/logo.png" alt="" width="56" height="56"></div><div id="session-notice" hidden></div><section class="cards" aria-label="Status"><article class="card"><div class="card-label"><span data-i18n="localService">Local service</span><span aria-hidden="true">◈</span></div><div class="card-value" id="local-status">—</div><div class="card-note" id="local-note">localhost:38850</div></article><article class="card"><div class="card-label"><span data-i18n="cloudConnection">Cloud connection</span><span aria-hidden="true">↗</span></div><div class="card-value" id="cloud-status">—</div><div class="card-note" id="cloud-note">—</div></article><article class="card"><div class="card-label"><span data-i18n="recentOperations">Recent operations</span><span aria-hidden="true">⌁</span></div><div class="card-value" id="activity-total">—</div><div class="card-note" data-i18n="operationsNote">Recent MCP request metadata</div></article></section><section class="next-step" id="first-memory"><div><h2 data-i18n="firstTitle">Connect your AI clients</h2><p data-i18n="firstBody">Connect Claude, Codex and other clients here in one click, then ask them to save context. Memories appear after a cloud refresh.</p><div class="mcp-clients" id="mcp-clients" role="group" aria-label="AI clients"></div><p id="mcp-message" role="status" aria-live="polite" class="mcp-message"></p><p data-i18n="rememberPrompt" style="margin-top:10px"></p><p class="mcp-fallback"><span data-i18n="mcpFallback"></span> <code>bunx --bun vault-go@latest install</code></p></div><div class="actions"><a class="link" href="${VAULT_WEB_MCP_URL}" target="_blank" rel="noopener noreferrer" data-i18n="otherClients">Other clients ↗</a><a class="link" href="${VAULT_WEB_MEMORY_URL}" target="_blank" rel="noopener noreferrer" data-i18n="openMemories">Open memories ↗</a></div></section><div class="layout"><section class="panel wide" id="security"><div class="panel-head"><h2 data-i18n="securityTitle">Security on this machine</h2></div><div id="security-facts"></div><div class="actions"><button class="button danger" id="disconnect-device" type="button" data-i18n="disconnectDevice">Disconnect device</button></div></section><section class="panel wide" id="context"><div class="panel-head"><h2 data-i18n="contextEngine">Context engine</h2></div><p data-i18n="engineIntro">Choose how Vault prepares your context.</p><div class="engine-options" id="engine-options" role="radiogroup" aria-label="Context engine"></div><div class="engine-options" id="model-options" role="radiogroup" aria-label="Context model"></div><div class="engine-keys" id="engine-keys"></div><div class="engine-message" id="engine-message" role="status" aria-live="polite"></div><div class="context-editor"><label for="context-text" data-i18n="contextText">Text to prepare</label><textarea id="context-text" maxlength="16000" aria-describedby="context-note"></textarea><p id="context-note" data-i18n="contextNote"></p><div class="actions"><button class="button primary" id="context-preview" type="button" data-i18n="contextPreview"></button><button class="button" id="context-stop" type="button" data-i18n="cancel" hidden></button></div><p id="context-progress" role="status" aria-live="polite"></p></div></section><section class="panel" id="activity"><div class="panel-head"><h2 data-i18n="activity">Activity</h2><span class="count" id="activity-count">0</span></div><input class="search" id="activity-search" type="search" data-placeholder="searchActivity" aria-label="Search activity"><div id="activity-list"></div><div class="pagination" id="activity-pagination"></div></section><section class="panel" id="memories"><div class="panel-head"><h2 data-i18n="memories">Memories</h2><span class="count" id="memory-count">0</span></div><input class="search" id="memory-search" type="search" data-placeholder="searchMemories" aria-label="Search memories"><div id="memory-list"></div><div class="pagination" id="memory-pagination"></div></section><section class="panel wide" id="projects"><div class="panel-head"><h2 data-i18n="projects">Projects</h2><span class="count" id="project-count">0</span></div><input class="search" id="project-search" type="search" data-placeholder="searchProjects" aria-label="Search projects"><div id="project-list"></div><div class="pagination" id="project-pagination"></div></section></div></div><footer class="footer"><span data-i18n="footer">Vault Local · Context with a place to call home.</span><span id="updated" aria-live="off">—</span></footer></main></div><dialog class="detail-dialog" id="detail-dialog" aria-labelledby="detail-title"><div class="panel-head"><h2 id="detail-title"></h2><button class="button" type="button" id="detail-copy" hidden data-i18n="copyResult">Copy result</button><button class="button" type="button" id="detail-close" data-i18n="close">Close</button></div><dl class="detail-fields" id="detail-fields"></dl></dialog><dialog class="detail-dialog" id="logout-dialog" aria-labelledby="logout-title" aria-describedby="logout-description"><h2 id="logout-title" data-i18n="logout"></h2><p id="logout-description" data-i18n="logoutDescription" style="margin-top:16px"></p><div class="actions"><button class="button" type="button" id="logout-cancel" data-i18n="cancel"></button><button class="button primary" type="button" id="logout-confirm" data-i18n="logoutConfirm"></button></div></dialog><dialog class="detail-dialog" id="context-confirm-dialog" aria-labelledby="context-confirm-title" aria-describedby="context-confirm-note"><h2 id="context-confirm-title" data-i18n="contextPreview"></h2><p id="context-confirm-engine" style="margin-top:16px"></p><p id="context-confirm-billing"></p><p id="context-confirm-note" data-i18n="contextConsentNote" style="margin-top:16px"></p><pre id="context-confirm-text" style="white-space:pre-wrap;overflow-wrap:anywhere;max-height:32vh;overflow:auto;font-size:12px;line-height:1.7"></pre><label style="display:flex;gap:10px;align-items:flex-start;font-size:12px"><input id="context-consent" type="checkbox"><span data-i18n="contextConsent"></span></label><div class="actions"><button class="button" id="context-cancel" type="button" data-i18n="cancel"></button><button class="button primary" id="context-generate" type="button" disabled data-i18n="contextGenerate"></button></div></dialog><dialog class="detail-dialog" id="disconnect-dialog" aria-labelledby="disconnect-title" aria-describedby="disconnect-description"><h2 class="danger-title" id="disconnect-title" data-i18n="disconnectDevice"></h2><p id="disconnect-description" data-i18n="disconnectDescription" style="margin-top:16px"></p><p id="disconnect-error" class="error" role="alert"></p><div class="actions"><button class="button" type="button" id="disconnect-cancel" data-i18n="cancel"></button><button class="button danger" type="button" id="disconnect-confirm" data-i18n="disconnectConfirm"></button></div></dialog></body></html>`;
9
+ .danger{border-color:#ed9a9a66;color:#ffb5b5;background:#53272d55}.danger:hover{border-color:#ffb5b5}.danger-title{color:#ffb5b5}.pagination{display:flex;gap:9px;align-items:center;flex-wrap:wrap;border-top:1px solid #cdd8ff20;margin-top:15px;padding-top:15px;color:#acb4cb;font-size:11px}.pagination label{display:flex;align-items:center;gap:7px}.pagination .page-summary{flex:1;min-width:95px}.pagination select{padding:7px}.pagination .button{padding:7px 10px}.pagination button:disabled{cursor:default}.row-open{background:transparent;border:0;text-align:left;padding:0;color:#d3d1ff;cursor:pointer;font:inherit}.row-open:hover{text-decoration:underline}.row-actions{padding-top:5px}.detail-dialog{color:#f1f3fc;background:linear-gradient(135deg,#272c3e,#131724);border:1px solid #cdd8ff40;border-radius:18px;max-width:800px;width:calc(100% - 32px);max-height:85vh;padding:26px;box-shadow:0 30px 100px #0009}.detail-dialog::backdrop{background:#050710bd;backdrop-filter:blur(5px)}.detail-dialog h2{font-size:20px;overflow-wrap:anywhere}.detail-fields{margin:0}.detail-fields dt{color:#acb4cb;font-size:11px;margin:20px 0 8px}.detail-fields dd{margin:0;font-size:13px;line-height:1.7;white-space:pre-wrap;overflow-wrap:anywhere}.detail-fields pre{font:inherit;white-space:pre-wrap;margin:0}.clamped{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:8;overflow:hidden}.row-title.clamped{-webkit-line-clamp:3}.show-more{background:transparent;border:0;padding:6px 0 0;color:#b9b6ff;font:inherit;font-size:12px;cursor:pointer}.show-more:hover{text-decoration:underline}.show-more:focus-visible{outline:2px solid #b9b6ff;outline-offset:3px;border-radius:4px}.row-tags{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}.tag{font-size:10px;letter-spacing:.02em;color:#c9c6ff;background:#b9b6ff14;border:1px solid #b9b6ff33;border-radius:999px;padding:2px 8px}.detail-list{margin:0;padding-left:18px}.detail-list li{margin:2px 0}.detail-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:22px}.engine-options{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;margin-top:18px}.engine-keys{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px;margin-top:16px}.engine-keys label{display:block;font-size:12px;margin-bottom:8px}.engine-keys input{width:100%;background:#0307117a;color:#f1f3fc;border:1px solid #cdd8ff26;border-radius:9px;padding:10px 12px;font:inherit;font-size:13px;margin-bottom:8px}.engine-option{display:block;padding:17px;border:1px solid #cdd8ff26;border-radius:10px;background:#080b1233;cursor:pointer}.engine-option:has(input:checked){border-color:#b9b6ff;background:#b9b6ff12}.engine-option:has(input:disabled){opacity:.7;cursor:default}.engine-option input{accent-color:#b9b6ff;margin-right:8px}.engine-option strong{font-size:12px}.engine-option p{font-size:11px;margin-top:9px}.engine-message{margin-top:13px;font-size:12px;color:#d3d1ff}.context-editor{margin-top:20px}.context-editor label{display:block;font-size:12px;margin-bottom:8px}.context-editor textarea{width:100%;min-height:130px;resize:vertical;background:#0307117a;color:#f1f3fc;border:1px solid #cdd8ff26;border-radius:9px;padding:12px;font:inherit;font-size:13px}.context-editor textarea:focus-visible{outline:2px solid #b9b6ff;outline-offset:3px}.mcp-clients{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px;margin-top:14px}.mcp-client{display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid #cdd8ff26;border-radius:10px;padding:10px 12px;background:#080b1233;font-size:13px;flex-wrap:wrap}.mcp-client strong{font-size:13px;font-weight:600}.mcp-state{font-size:11px;color:#8edbc8;white-space:nowrap}.mcp-dim{opacity:.75}.mcp-evidence{font-size:10px;color:#949fbb;width:100%}.mcp-note{color:#949fbb;font-size:12px}.mcp-message{color:#ed9a9a;font-size:12px;margin-top:10px}.mcp-message:empty{display:none}.mcp-fallback{color:#949fbb;font-size:11px;margin-top:12px}.mcp-fallback code{font-size:11px;color:#c7d4df;background:#0307117a;border-radius:6px;padding:2px 6px}@media(max-width:800px){.engine-options{grid-template-columns:1fr}}@media(prefers-reduced-motion:reduce){.detail-dialog::backdrop{backdrop-filter:none}}</style><script src="/app.js" defer></script></head><body><div class="shell"><aside class="rail"><div class="brand"><img src="/logo.png" alt="" width="46" height="46"><div>AI Vault Memory<span class="brand-sub">LOCAL AGENT</span></div></div><div class="rail-label" data-i18n="workspace">WORKSPACE</div><nav class="nav" aria-label="Navigation"><a class="active" href="#overview"><span class="icon">◈</span><span data-i18n="overview">Overview</span></a><a href="#activity"><span class="icon">⌁</span><span data-i18n="activity">Activity</span></a><a href="#memories"><span class="icon">▤</span><span data-i18n="memories">Memories</span></a><a href="#context"><span class="icon">✧</span><span data-i18n="contextEngine">Context engine</span></a><a href="#projects"><span class="icon">▱</span><span data-i18n="projects">Projects</span></a></nav><div class="rail-footer"><span class="dot"></span><span data-i18n="localWorkspace">Your local workspace</span><br><span data-i18n="privacy">Activity metadata stays local.</span></div></aside><main class="main" id="overview"><header class="top"><div class="breadcrumb">Vault <span aria-hidden="true"> / </span><b data-i18n="localMonitor">Local monitor</b></div><div class="controls"><select id="locale" aria-label="Language"><option value="en">English</option><option value="pt">Português</option><option value="es">Español</option></select><button class="button" id="logout" type="button" hidden data-i18n="logout">Sign out of dashboard</button><button class="button" id="reconnect" type="button" hidden data-i18n="reconnect">Reconnect account</button><button class="button" id="refresh" type="button"><span aria-hidden="true">↻</span><span data-i18n="refresh">Refresh cloud</span></button></div></header><div id="error" class="error" role="status" aria-live="polite"></div><section class="onboarding" id="onboarding"><div class="onboarding-hero"><img class="onboarding-logo" src="/logo.png" alt="" width="86" height="86"><div class="eyebrow" data-i18n="connectEyebrow">AI VAULT MEMORY · LOCAL</div><h1 data-i18n="connectTitle">Your memory. Connected to this machine.</h1><p data-i18n="connectIntro">Connect your Vault account to see your memories, projects and recent activity in one place.</p><div class="actions"><button type="button" class="button primary" id="connect" data-i18n="connect">Connect to Vault</button><a class="link" href="${VAULT_WEB_APP_URL}" target="_blank" rel="noopener noreferrer" data-i18n="openPortal">Open Vault portal ↗</a></div><p class="onboarding-hint" data-i18n="connectHint">Sign in securely in a separate window. Your password stays with the Vault portal.</p><div class="connection-message" id="connection-message" role="status" aria-live="polite"></div><a id="auth-fallback" class="button" hidden target="_blank" rel="noopener noreferrer" data-i18n="continueAuth">Continue sign-in ↗</a></div><div class="steps"><article class="step"><div class="step-number">01 / VAULT</div><h2 data-i18n="stepAccount">Connect your account</h2><p data-i18n="stepAccountBody">Sign in to Vault and authorize this local agent.</p></article><article class="step"><div class="step-number">02 / MCP</div><h2 data-i18n="stepClient">Connect your AI clients</h2><p data-i18n="stepClientBody">After signing in, connect Claude, Codex and other clients with one click, right here.</p></article><article class="step"><div class="step-number">03 / MEMORY</div><h2 data-i18n="stepMemory">Build your first memory</h2><p data-i18n="stepMemoryBody">Ask your connected client to save useful context, then follow its activity here.</p></article></div><div class="terminal-fallback"><button class="button" id="verify-connection" type="button" data-i18n="verifyConnection">Already signed in? Check connection</button><p style="margin-top:16px" data-i18n="terminalFallback">Already connected through the CLI? Open a secure local session:</p><code>bunx --bun vault-go@latest local open</code></div></section><div id="dashboard" hidden><div class="welcome"><div><div class="eyebrow" data-i18n="eyebrow">YOUR KNOWLEDGE, CONNECTED</div><h1 data-i18n="title">Your local workspace.</h1><p data-i18n="subtitle">Your Vault account, memories and activity on this machine.</p></div><img src="/logo.png" alt="" width="56" height="56"></div><div id="session-notice" hidden></div><section class="cards" aria-label="Status"><article class="card"><div class="card-label"><span data-i18n="localService">Local service</span><span aria-hidden="true">◈</span></div><div class="card-value" id="local-status">—</div><div class="card-note" id="local-note">localhost:38850</div></article><article class="card"><div class="card-label"><span data-i18n="cloudConnection">Cloud connection</span><span aria-hidden="true">↗</span></div><div class="card-value" id="cloud-status">—</div><div class="card-note" id="cloud-note">—</div></article><article class="card"><div class="card-label"><span data-i18n="recentOperations">Recent operations</span><span aria-hidden="true">⌁</span></div><div class="card-value" id="activity-total">—</div><div class="card-note" data-i18n="operationsNote">Recent MCP request metadata</div></article></section><section class="next-step" id="first-memory"><div><h2 data-i18n="firstTitle">Connect your AI clients</h2><p data-i18n="firstBody">Connect Claude, Codex and other clients here in one click, then ask them to save context. Memories appear after a cloud refresh.</p><div class="mcp-clients" id="mcp-clients" role="group" aria-label="AI clients"></div><p id="mcp-message" role="status" aria-live="polite" class="mcp-message"></p><p data-i18n="rememberPrompt" style="margin-top:10px"></p><p class="mcp-fallback"><span data-i18n="mcpFallback"></span> <code>bunx --bun vault-go@latest install</code></p></div><div class="actions"><a class="link" href="${VAULT_WEB_MCP_URL}" target="_blank" rel="noopener noreferrer" data-i18n="otherClients">Other clients ↗</a><a class="link" href="${VAULT_WEB_MEMORY_URL}" target="_blank" rel="noopener noreferrer" data-i18n="openMemories">Open memories ↗</a></div></section><div class="layout"><section class="panel wide" id="security"><div class="panel-head"><h2 data-i18n="securityTitle">Security on this machine</h2></div><div id="security-facts"></div><div class="actions"><button class="button danger" id="disconnect-device" type="button" data-i18n="disconnectDevice">Disconnect device</button></div></section><section class="panel wide" id="context"><div class="panel-head"><h2 data-i18n="contextEngine">Context engine</h2></div><p data-i18n="engineIntro">Choose how Vault prepares your context.</p><div class="engine-options" id="engine-options" role="radiogroup" aria-label="Context engine"></div><div class="engine-options" id="model-options" role="radiogroup" aria-label="Context model"></div><div class="engine-keys" id="engine-keys"></div><div class="engine-message" id="engine-message" role="status" aria-live="polite"></div><div class="context-editor"><label for="context-text" data-i18n="contextText">Text to prepare</label><textarea id="context-text" maxlength="16000" aria-describedby="context-note"></textarea><p id="context-note" data-i18n="contextNote"></p><div class="actions"><button class="button primary" id="context-preview" type="button" data-i18n="contextPreview"></button><button class="button" id="context-stop" type="button" data-i18n="cancel" hidden></button></div><p id="context-progress" role="status" aria-live="polite"></p></div></section><section class="panel" id="activity"><div class="panel-head"><h2 data-i18n="activity">Activity</h2><span class="count" id="activity-count">0</span></div><input class="search" id="activity-search" type="search" data-placeholder="searchActivity" aria-label="Search activity"><div id="activity-list"></div><div class="pagination" id="activity-pagination"></div></section><section class="panel" id="memories"><div class="panel-head"><h2 data-i18n="memories">Memories</h2><span class="count" id="memory-count">0</span></div><input class="search" id="memory-search" type="search" data-placeholder="searchMemories" aria-label="Search memories"><div id="memory-list"></div><div class="pagination" id="memory-pagination"></div></section><section class="panel wide" id="projects"><div class="panel-head"><h2 data-i18n="projects">Projects</h2><span class="count" id="project-count">0</span></div><input class="search" id="project-search" type="search" data-placeholder="searchProjects" aria-label="Search projects"><div id="project-list"></div><div class="pagination" id="project-pagination"></div></section></div></div><footer class="footer"><span data-i18n="footer">Vault Local · Context with a place to call home.</span><span id="updated" aria-live="off">—</span></footer></main></div><dialog class="detail-dialog" id="detail-dialog" aria-labelledby="detail-title"><div class="panel-head"><h2 id="detail-title"></h2><a class="button" id="detail-open" hidden target="_blank" rel="noopener noreferrer" data-i18n="openInVault">Open in Vault ↗</a><button class="button" type="button" id="detail-copy" hidden data-i18n="copyResult">Copy result</button><button class="button" type="button" id="detail-close" data-i18n="close">Close</button></div><dl class="detail-fields" id="detail-fields"></dl></dialog><dialog class="detail-dialog" id="logout-dialog" aria-labelledby="logout-title" aria-describedby="logout-description"><h2 id="logout-title" data-i18n="logout"></h2><p id="logout-description" data-i18n="logoutDescription" style="margin-top:16px"></p><div class="actions"><button class="button" type="button" id="logout-cancel" data-i18n="cancel"></button><button class="button primary" type="button" id="logout-confirm" data-i18n="logoutConfirm"></button></div></dialog><dialog class="detail-dialog" id="context-confirm-dialog" aria-labelledby="context-confirm-title" aria-describedby="context-confirm-note"><h2 id="context-confirm-title" data-i18n="contextPreview"></h2><p id="context-confirm-engine" style="margin-top:16px"></p><p id="context-confirm-billing"></p><p id="context-confirm-note" data-i18n="contextConsentNote" style="margin-top:16px"></p><pre id="context-confirm-text" style="white-space:pre-wrap;overflow-wrap:anywhere;max-height:32vh;overflow:auto;font-size:12px;line-height:1.7"></pre><label style="display:flex;gap:10px;align-items:flex-start;font-size:12px"><input id="context-consent" type="checkbox"><span data-i18n="contextConsent"></span></label><div class="actions"><button class="button" id="context-cancel" type="button" data-i18n="cancel"></button><button class="button primary" id="context-generate" type="button" disabled data-i18n="contextGenerate"></button></div></dialog><dialog class="detail-dialog" id="disconnect-dialog" aria-labelledby="disconnect-title" aria-describedby="disconnect-description"><h2 class="danger-title" id="disconnect-title" data-i18n="disconnectDevice"></h2><p id="disconnect-description" data-i18n="disconnectDescription" style="margin-top:16px"></p><p id="disconnect-error" class="error" role="alert"></p><div class="actions"><button class="button" type="button" id="disconnect-cancel" data-i18n="cancel"></button><button class="button danger" type="button" id="disconnect-confirm" data-i18n="disconnectConfirm"></button></div></dialog></body></html>`;
10
10
  }
11
11
  export function dashboardScript() {
12
12
  return `"use strict";
@@ -34,7 +34,13 @@ export function dashboardScript() {
34
34
  en:{engineUpdateCli:'Update the official CLI to a version supporting isolated context generation.',engineClaudeLogin:'Sign in with a Claude subscription using claude auth login.',engineCodexLogin:'Sign in with ChatGPT using codex login.',engineCliUnavailable:'Official CLI or subscription login unavailable. Install or sign in using the provider CLI.',engineVaultUnavailable:'Vault AI Resume is not configured or unavailable for this account.',engineProviderCheck:'Provider readiness is checked when generating.',engineObserverMissing:'Managed observer is not configured.',engineOpenrouterKey:'Configure an OpenRouter API key with vault-go engine key openrouter.',engineGeminiKey:'Configure a Gemini API key with vault-go engine key gemini.',billingVault:'Uses the Vault observer allowance.',billingClaude:'Uses the provider allowance for Claude Code non-interactive runs; limits/additional charges may apply.',billingOpenai:'Uses your ChatGPT subscription through Codex. Provider limits or credits may apply.',billingOpenrouter:'Uses your OpenRouter key and credits. Provider limits may apply.',billingGemini:'Uses your Gemini API key. Google rate limits or billing may apply.',openrouterKey:'OpenRouter API key',geminiKey:'Gemini API key',saveKey:'Save key',keyConfigured:'Key configured on this machine',keyMissing:'Paste the key and save',keySaved:'Key stored with private file permissions.',keyFailed:'Could not store the key. Try again.',keyOs:'Operating system keychain',keyFile:'Local file protected by permissions',keyEphemeral:'In memory for this run only',keyUnavailable:'Unavailable'},
35
35
  es:{engineUpdateCli:'Actualiza el CLI oficial a una versión compatible con la generación aislada de contexto.',engineClaudeLogin:'Inicia sesión con tu suscripción Claude usando claude auth login.',engineCodexLogin:'Inicia sesión con tu cuenta ChatGPT usando codex login.',engineCliUnavailable:'CLI oficial o sesión de la suscripción no disponible. Instala el CLI del proveedor o inicia sesión mediante él.',engineVaultUnavailable:'Vault AI Resume no está configurado o no está disponible para esta cuenta.',engineProviderCheck:'La disponibilidad del proveedor se comprobará al generar.',engineObserverMissing:'El observador administrado no está configurado.',engineOpenrouterKey:'Configura una clave OpenRouter con vault-go engine key openrouter.',engineGeminiKey:'Configura una clave Gemini con vault-go engine key gemini.',billingVault:'Usa la cuota del observador de Vault.',billingClaude:'Usa la cuota del proveedor para ejecuciones no interactivas de Claude Code; pueden aplicarse límites y cargos adicionales.',billingOpenai:'Usa tu suscripción ChatGPT mediante Codex. Pueden aplicarse límites o créditos de uso del proveedor.',billingOpenrouter:'Usa tu clave y créditos de OpenRouter. Pueden aplicarse límites del proveedor.',billingGemini:'Usa tu clave de la API Gemini. Pueden aplicarse límites o facturación de Google.',openrouterKey:'Clave OpenRouter',geminiKey:'Clave Gemini',saveKey:'Guardar clave',keyConfigured:'Clave configurada en este equipo',keyMissing:'Pega la clave y guarda',keySaved:'Clave guardada con permisos privados.',keyFailed:'No se pudo guardar la clave. Inténtalo de nuevo.',keyOs:'Llavero del sistema operativo',keyFile:'Archivo local protegido por permisos',keyEphemeral:'Solo en memoria durante esta ejecución',keyUnavailable:'No disponible'}
36
36
  };
37
- for (const language of Object.keys(strings)) Object.assign(strings[language],onboardingCopy[language],detailCopy[language],engineCopy[language]);
37
+ const disclosureCopy={en:{showMore:'Show more',showLess:'Show less'},pt:{showMore:'Mostrar mais',showLess:'Mostrar menos'},es:{showMore:'Mostrar más',showLess:'Mostrar menos'}};
38
+ const memoryCopy={
39
+ en:{openInVault:'Open in Vault ↗',fieldContent:'Content',fieldFacts:'Facts',fieldConcepts:'Concepts',fieldFilesRead:'Files read',fieldFilesModified:'Files modified',fieldType:'Type',fieldProject:'Project',fieldSource:'Source',fieldCreated:'Created',fieldId:'ID',typeDecision:'Decision',typeDiscovery:'Discovery',typeFix:'Fix',typeFeature:'Feature',typeRefactor:'Refactor',typeChange:'Change',typeTask:'Task',typeQuestion:'Question',typeInstruction:'Instruction',kindPrompt:'Prompt',kindSummary:'Summary',kindObservation:'Observation'},
40
+ pt:{openInVault:'Abrir no Vault ↗',fieldContent:'Conteúdo',fieldFacts:'Fatos',fieldConcepts:'Conceitos',fieldFilesRead:'Arquivos lidos',fieldFilesModified:'Arquivos alterados',fieldType:'Tipo',fieldProject:'Projeto',fieldSource:'Origem',fieldCreated:'Criada em',fieldId:'ID',typeDecision:'Decisão',typeDiscovery:'Descoberta',typeFix:'Correção',typeFeature:'Funcionalidade',typeRefactor:'Refatoração',typeChange:'Mudança',typeTask:'Tarefa',typeQuestion:'Pergunta',typeInstruction:'Instrução',kindPrompt:'Pedido',kindSummary:'Resumo',kindObservation:'Observação'},
41
+ es:{openInVault:'Abrir en Vault ↗',fieldContent:'Contenido',fieldFacts:'Hechos',fieldConcepts:'Conceptos',fieldFilesRead:'Archivos leídos',fieldFilesModified:'Archivos modificados',fieldType:'Tipo',fieldProject:'Proyecto',fieldSource:'Origen',fieldCreated:'Creada',fieldId:'ID',typeDecision:'Decisión',typeDiscovery:'Descubrimiento',typeFix:'Corrección',typeFeature:'Funcionalidad',typeRefactor:'Refactorización',typeChange:'Cambio',typeTask:'Tarea',typeQuestion:'Pregunta',typeInstruction:'Instrucción',kindPrompt:'Petición',kindSummary:'Resumen',kindObservation:'Observación'}
42
+ };
43
+ for (const language of Object.keys(strings)) Object.assign(strings[language],onboardingCopy[language],detailCopy[language],engineCopy[language],disclosureCopy[language],memoryCopy[language]);
38
44
  let locale = navigator.language.slice(0,2);
39
45
  try { locale = localStorage.getItem('vault-local-locale') || locale; } catch {}
40
46
  if (!strings[locale]) locale = 'en';
@@ -54,14 +60,52 @@ export function dashboardScript() {
54
60
  let mcpClients = null, mcpChecked = 0, mcpMessage = '';
55
61
  const mcpBusy = {};
56
62
  let contextPreview=null, contextJob=null, contextTimer=null, contextStarted=0, contextMessage='', contextResult=null, contextStarting=false;
57
- function showDetails(title,item,trigger) {
63
+ // Machine-driven sessions produce multi-thousand-character prompts and payloads.
64
+ // Past these bounds a value is clamped behind a Show more disclosure instead of
65
+ // pushing every other field (or row) off screen.
66
+ const CLAMP_LINES=8, CLAMP_MIN_CHARS=600;
67
+ const isLong=value=>value.split('\\n').length>CLAMP_LINES||value.length>CLAMP_MIN_CHARS;
68
+ function disclosure(target) {
69
+ target.classList.add('clamped');
70
+ const toggle=node('button','show-more',t('showMore'));toggle.type='button';toggle.setAttribute('aria-expanded','false');
71
+ toggle.addEventListener('click',()=>{const expanded=target.classList.toggle('clamped')===false;toggle.setAttribute('aria-expanded',String(expanded));toggle.textContent=t(expanded?'showLess':'showMore');});
72
+ return toggle;
73
+ }
74
+ // The cloud feed wraps each memory as {memory, platformSource}; older caches hold the memory itself.
75
+ const memoryOf=item=>{const record=object(item),inner=object(record.memory);return Object.keys(inner).length?{...inner,platformSource:inner.platformSource||record.platformSource}:record;};
76
+ const SOURCE_NAMES={claude:'Claude Code','claude-code':'Claude Code',codex:'Codex',cursor:'Cursor',windsurf:'Windsurf',agy:'Antigravity',antigravity:'Antigravity',gemini:'Gemini CLI',grok:'Grok',opencode:'OpenCode',copilot:'GitHub Copilot',vscode:'VS Code',warp:'Warp',goose:'Goose',hermes:'Hermes','vault-desktop':'Vault Desktop','vault-go':'Vault Go',chatgpt:'ChatGPT'};
77
+ const sourceName=value=>SOURCE_NAMES[scalar(value).toLowerCase()]||scalar(value);
78
+ const typeName=memory=>{const type=scalar(memory.memoryType||memory.type).toLowerCase(),kind=scalar(memory.kind).toLowerCase();const key=type?'type'+type[0].toUpperCase()+type.slice(1):'';const label=key&&strings[locale][key];return label||(kind&&strings[locale]['kind'+kind[0].toUpperCase()+kind.slice(1)])||type||kind;};
79
+ const projectName=id=>{const project=array(state&&state.projects).map(object).find(item=>item.id===id);return project?first(project,['name','title']):'';};
80
+ const epochDate=value=>typeof value==='number'?date(value):date(scalar(value));
81
+ const vaultLink=(params)=>'${VAULT_WEB_MEMORY_URL}?'+new URLSearchParams(params).toString();
82
+ const uuid=/^[0-9a-f-]{36}$/i;
83
+ function showMemory(memory,trigger) {
58
84
  contextResult=null;el('detail-copy').hidden=true;
85
+ detailFocus=trigger.id;text('detail-title',first(memory,['title','summary'])||t('untitled'));
86
+ const fields=el('detail-fields');fields.replaceChildren();
87
+ const add=(label,value)=>{if(value===undefined||value===null||value===''||(Array.isArray(value)&&!value.length))return;const term=node('dt','',t(label)),definition=node('dd','');
88
+ if(Array.isArray(value)){const list=node('ul','detail-list');for(const item of value.slice(0,200))list.append(node('li','',scalar(item)));definition.append(list);}
89
+ else{const content=node('div','',String(value));definition.append(content);if(isLong(content.textContent))definition.append(disclosure(content));}
90
+ fields.append(term,definition);};
91
+ add('fieldContent',scalar(memory.content));add('fieldFacts',array(memory.facts));add('fieldConcepts',array(memory.concepts).length?array(memory.concepts).map(scalar).join(' · '):'');
92
+ add('fieldFilesModified',array(memory.filesModified));add('fieldFilesRead',array(memory.filesRead));
93
+ add('fieldType',typeName(memory));add('fieldProject',projectName(memory.projectId));add('fieldSource',sourceName(memory.platformSource));
94
+ add('fieldCreated',epochDate(memory.createdAtEpoch||memory.createdAt));add('fieldId',scalar(memory.id));
95
+ const open=el('detail-open');const id=scalar(memory.id),projectId=scalar(memory.projectId);
96
+ open.hidden=!uuid.test(id);if(!open.hidden)open.href=vaultLink({memory:id,...(uuid.test(projectId)?{project:projectId}:{})});
97
+ el('detail-dialog').showModal();el('detail-close').focus();
98
+ }
99
+ function showDetails(title,item,trigger) {
100
+ contextResult=null;el('detail-copy').hidden=true;el('detail-open').hidden=true;
59
101
  detailFocus=trigger.id; text('detail-title',title);
60
102
  const fields=el('detail-fields'); fields.replaceChildren();
61
103
  const entries=Object.entries(object(item));
62
104
  for(const [key,value] of entries.length ? entries : [[t('details'),item]]) {
63
105
  const term=node('dt','',key),definition=node('dd','');
64
- definition.textContent=typeof value==='string' ? value : JSON.stringify(value,null,2);
106
+ const content=node('div','',typeof value==='string' ? value : JSON.stringify(value,null,2));
107
+ definition.append(content);
108
+ if(isLong(content.textContent))definition.append(disclosure(content));
65
109
  fields.append(term,definition);
66
110
  }
67
111
  el('detail-dialog').showModal(); el('detail-close').focus();
@@ -69,7 +113,7 @@ export function dashboardScript() {
69
113
  function viewButton(title,item,kind,index) {
70
114
  const button=node('button','button',t('view'));button.type='button';button.id=kind+'-view-'+index;
71
115
  button.setAttribute('aria-label',t('view')+': '+title.slice(0,100));
72
- button.addEventListener('click',()=>showDetails(title,item,button));return button;
116
+ button.addEventListener('click',()=>kind==='memory'?showMemory(item,button):showDetails(title,item,button));return button;
73
117
  }
74
118
  function pageRows(kind,rows,total) {
75
119
  const paging=pagination[kind],pages=Math.max(1,Math.ceil(rows.length/paging.size));
@@ -92,20 +136,30 @@ export function dashboardScript() {
92
136
  function list(kind) {
93
137
  const source=array(state && state[kind==='memory'?'memories':kind==='project'?'projects':'activity']);
94
138
  const search=el(kind+'-search').value.toLocaleLowerCase(locale).trim();
95
- const rows=source.map((item,index)=>({item,index,title:first(item,kind==='activity'?['operation']:['title','name','summary','content','text','id'])||t('untitled'),detail:first(item,['project','project_name','description','created_at','createdAt'])}));
139
+ const rows=source.map((item,index)=>{
140
+ if(kind==='memory'){const memory=memoryOf(item);return {item:memory,index,title:first(memory,['title','summary','content','text','id'])||t('untitled'),detail:[projectName(memory.projectId),epochDate(memory.createdAtEpoch||memory.createdAt)].filter(Boolean).join(' · '),tags:[typeName(memory),sourceName(memory.platformSource)].filter(Boolean)};}
141
+ return {item,index,title:first(item,kind==='activity'?['operation']:['title','name','summary','content','text','id'])||t('untitled'),detail:first(item,['project','project_name','description','created_at','createdAt'])};
142
+ });
96
143
  const filtered=rows.filter(row=>JSON.stringify(row.item).toLocaleLowerCase(locale).includes(search));
97
144
  const target=el(kind+'-list');target.replaceChildren();
98
145
  const visible=pageRows(kind,filtered,source.length);
99
146
  if(!visible.length)return empty(target,t(source.length?'noResults':kind==='memory'?'memoriesEmpty':kind==='project'?'projectsEmpty':'activityEmpty'));
100
147
  for(const row of visible) {
101
148
  const wrap=node('div','row'),body=node('div','row-body'),title=node('button','row-title row-open',row.title.slice(0,600));
102
- title.type='button';title.addEventListener('click',()=>showDetails(row.title,row.item,button));
149
+ const open=()=>kind==='memory'?showMemory(row.item,button):showDetails(row.title,row.item,button);
150
+ title.type='button';title.addEventListener('click',open);
103
151
  body.append(title);
152
+ if(row.title.length>200)title.classList.add('clamped');
104
153
  if(kind==='activity') {
105
154
  const item=object(row.item);body.append(node('div','row-meta',(item.ok===true?t('success'):t('failed'))+(typeof item.durationMs==='number'?' · '+Math.round(item.durationMs)+' ms':'')));
106
155
  wrap.append(node('span','row-marker'+(item.ok===true?'':' failed')));
107
156
  } else if(row.detail)body.append(node('div','row-meta',row.detail.slice(0,200)));
108
- const actions=node('div','row-actions'),button=viewButton(row.title,row.item,kind,row.index);actions.append(button);body.append(actions);wrap.append(body);
157
+ if(row.tags&&row.tags.length){const tags=node('div','row-tags');for(const tag of row.tags)tags.append(node('span','tag',tag));body.append(tags);}
158
+ const actions=node('div','row-actions'),button=viewButton(row.title,row.item,kind,row.index);
159
+ actions.append(button);
160
+ const projectId=kind==='project'?scalar(object(row.item).id):'';
161
+ if(uuid.test(projectId)){const link=node('a','link',t('openInVault'));link.href=vaultLink({project:projectId});link.target='_blank';link.rel='noopener noreferrer';link.style.marginLeft='12px';actions.append(link);}
162
+ body.append(actions);wrap.append(body);
109
163
  if(kind==='activity')wrap.append(node('time','row-time',date(object(row.item).time)));
110
164
  target.append(wrap);
111
165
  }
@@ -3,6 +3,7 @@ import { homedir, platform } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { detectMcpClients, installMcpClient, MCP_CLIENTS, vaultDesktopDotMcpPath, vaultDesktopMcpPath, } from "./installer.js";
6
+ import { hasYamlEntry } from "./yaml-entry.js";
6
7
  export const MCP_CLIENT_LABELS = {
7
8
  codex: "Codex",
8
9
  claude: "Claude Code",
@@ -16,6 +17,9 @@ export const MCP_CLIENT_LABELS = {
16
17
  grok: "Grok",
17
18
  agy: "Antigravity",
18
19
  gemini: "Gemini CLI",
20
+ warp: "Warp",
21
+ goose: "Goose",
22
+ hermes: "Hermes",
19
23
  "vault-desktop": "Vault Desktop",
20
24
  };
21
25
  function vscodeMcpPaths(home) {
@@ -65,6 +69,12 @@ function configLocations(client, home, cwd) {
65
69
  ];
66
70
  case "gemini":
67
71
  return [[join(home, ".gemini", "settings.json"), "mcpServers"]];
72
+ case "warp":
73
+ return [[join(home, ".warp", ".mcp.json"), "mcpServers"]];
74
+ case "goose":
75
+ return [[join(home, ".config", "goose", "config.yaml"), "yaml:extensions"]];
76
+ case "hermes":
77
+ return [[join(home, ".hermes", "config.yaml"), "yaml:mcp_servers"]];
68
78
  case "vault-desktop":
69
79
  return [
70
80
  [vaultDesktopMcpPath(home), "mcpServers"],
@@ -76,7 +86,7 @@ function serverConfiguredInJson(path, rootKey) {
76
86
  if (!existsSync(path))
77
87
  return false;
78
88
  try {
79
- const parsed = JSON.parse(readFileSync(path, "utf8"));
89
+ const parsed = JSON.parse(readFileSync(path, "utf8").replace(/^\uFEFF/, ""));
80
90
  const servers = parsed && typeof parsed === "object" ? parsed[rootKey] : undefined;
81
91
  return Boolean(servers && typeof servers === "object" && "vault-go" in servers);
82
92
  }
@@ -84,6 +94,16 @@ function serverConfiguredInJson(path, rootKey) {
84
94
  return false;
85
95
  }
86
96
  }
97
+ function serverConfiguredInYaml(path, topKey) {
98
+ if (!existsSync(path))
99
+ return false;
100
+ try {
101
+ return hasYamlEntry(readFileSync(path, "utf8"), topKey, "vault-go");
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
87
107
  function serverConfiguredInToml(path) {
88
108
  if (!existsSync(path))
89
109
  return false;
@@ -122,7 +142,12 @@ export function mcpClientConnected(client, home = homedir(), cwd = process.cwd()
122
142
  if (client === "codex" || client === "claude")
123
143
  return clientCliInstalled(client, home);
124
144
  for (const [path, rootKey] of configLocations(client, home, cwd)) {
125
- if (rootKey === "toml" ? serverConfiguredInToml(path) : serverConfiguredInJson(path, rootKey)) {
145
+ const configured = rootKey === "toml"
146
+ ? serverConfiguredInToml(path)
147
+ : rootKey.startsWith("yaml:")
148
+ ? serverConfiguredInYaml(path, rootKey.slice(5))
149
+ : serverConfiguredInJson(path, rootKey);
150
+ if (configured) {
126
151
  return true;
127
152
  }
128
153
  }
@@ -5,6 +5,7 @@ export declare function jsonResult(value: unknown): {
5
5
  }[];
6
6
  structuredContent: Record<string, unknown>;
7
7
  };
8
+ export declare function errorMessage(error: unknown): string;
8
9
  export declare function run(operation: () => Promise<unknown>): Promise<{
9
10
  content: {
10
11
  type: "text";
@@ -3,13 +3,30 @@ export function jsonResult(value) {
3
3
  ? value
4
4
  : { value };
5
5
  return {
6
- content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],
6
+ // Compact: indentation only spends the client's context window; structuredContent carries the same value.
7
+ content: [{ type: 'text', text: JSON.stringify(value) ?? 'null' }],
7
8
  structuredContent,
8
9
  };
9
10
  }
10
- function errorResult(error) {
11
+ // What the agent can do next for each cloud status, so a failure is actionable instead of a bare code.
12
+ const STATUS_HINTS = {
13
+ 401: 'A sessão do Vault expirou ou não existe. Peça ao usuário para rodar: bunx --bun vault-go@latest login',
14
+ 403: 'Esta conta não tem acesso a esse recurso.',
15
+ 404: 'Não encontrado nesta conta. Confira o ID ou use scope "account".',
16
+ 409: 'O recurso mudou ou ainda não está pronto; leia o estado atual e tente de novo.',
17
+ 413: 'Entrada grande demais; reduza o texto, a lista ou o limite.',
18
+ 429: 'Limite de uso atingido; tente novamente em instantes.',
19
+ };
20
+ export function errorMessage(error) {
11
21
  const message = error instanceof Error ? error.message : 'Erro inesperado.';
12
- return { content: [{ type: 'text', text: message }], isError: true };
22
+ const status = error && typeof error === 'object' ? error.status : undefined;
23
+ if (typeof status !== 'number')
24
+ return message;
25
+ const hint = STATUS_HINTS[status] ?? (status >= 500 ? 'O Vault está temporariamente indisponível; tente novamente em instantes.' : '');
26
+ return hint && !message.includes(hint) ? `${message} — ${hint}` : message;
27
+ }
28
+ function errorResult(error) {
29
+ return { content: [{ type: 'text', text: errorMessage(error) }], isError: true };
13
30
  }
14
31
  export async function run(operation) {
15
32
  try {
@@ -1,5 +1,7 @@
1
1
  import { findWorkspaceProject } from './hooks.js';
2
2
  const CACHE_MS = 60_000;
3
+ const FILTER_KEYS = new Set(['kind', 'memoryType', 'platformSource', 'type']);
4
+ const ALL = /^\s*(all|any|\*|todos?|todas?)\s*$/i;
3
5
  /**
4
6
  * Why: MCP reads used to span every project of the account, so an agent in one
5
7
  * repository received memories from unrelated clients. Reads now default to the
@@ -8,7 +10,9 @@ const CACHE_MS = 60_000;
8
10
  export function createProjectScope(cloud, cwd = () => process.cwd()) {
9
11
  let cached;
10
12
  return async (input) => {
11
- const { scope = 'project', ...rest } = input;
13
+ const { scope = 'project', ...given } = input;
14
+ // Agents often send "all" to mean "no filter"; as a literal value it matches no memory.
15
+ const rest = Object.fromEntries(Object.entries(given).filter(([key, value]) => !(FILTER_KEYS.has(key) && typeof value === 'string' && ALL.test(value))));
12
16
  if (rest.projectId || scope === 'account')
13
17
  return rest;
14
18
  const workspace = cwd();
@@ -0,0 +1,25 @@
1
+ export declare const FILE_READ_GATE_MIN_BYTES: number;
2
+ export interface ReadGateRequest {
3
+ adapter: string;
4
+ toolName: string;
5
+ toolInput: Record<string, unknown>;
6
+ agentId: string;
7
+ workspace: string;
8
+ enabled: boolean;
9
+ }
10
+ /** Whether the file has more lines than `count`, reading one chunk at a time. Null when unreadable. */
11
+ export declare function fileHasMoreLinesThan(path: string, count: number): boolean | null;
12
+ /**
13
+ * The file a Read would gate, or undefined when any cheap condition fails:
14
+ * Claude Code main session (never subagents or other clients), gate enabled, a
15
+ * single file_path Read starting at line 1, a code file inside the workspace
16
+ * between 32 KB and the smart-tool limit, and a Read that returns the whole file.
17
+ */
18
+ export declare function readGateCandidate(request: ReadGateRequest): {
19
+ path: string;
20
+ mtimeMs: number;
21
+ } | undefined;
22
+ /** Newest update time among the memories, in epoch milliseconds; 0 when none is known. */
23
+ export declare function newestMemoryEpoch(value: unknown): number;
24
+ /** The deny reason shown to Claude. Memory titles are history, never the file's current contents. */
25
+ export declare function readGateReason(requestedPath: string, memoryIds: readonly string[], context: string): string;
@@ -0,0 +1,121 @@
1
+ import { closeSync, openSync, readSync, realpathSync, statSync } from "node:fs";
2
+ import { extname, isAbsolute, relative, resolve, sep } from "node:path";
3
+ // File Read Gate: a whole-file Read of a large code file that already has
4
+ // memory history is denied with that history, and Claude is routed to the
5
+ // structural tools or a targeted Read. Below this size reading the file costs
6
+ // about what the detour would, so small files are always read normally.
7
+ export const FILE_READ_GATE_MIN_BYTES = 32 * 1024;
8
+ // The smart tools refuse files above this, so the gate never routes Claude to them.
9
+ const SMART_TOOL_MAX_BYTES = 1_048_576;
10
+ const LINE_COUNT_CHUNK_BYTES = 64 * 1024;
11
+ // Code the smart tools outline by symbol. Markup, data and prose stay readable.
12
+ const GATED_EXTENSIONS = new Set([
13
+ ".js", ".jsx", ".mjs", ".cjs", ".ts", ".mts", ".cts", ".tsx",
14
+ ".py", ".pyw", ".go", ".rs", ".rb", ".java", ".c", ".h", ".cpp", ".cc", ".cxx", ".hpp", ".hh",
15
+ ".kt", ".kts", ".swift", ".php", ".lua", ".scala", ".sc", ".hs", ".zig",
16
+ ]);
17
+ function windowValue(value) {
18
+ const numeric = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
19
+ return typeof numeric === "number" && Number.isFinite(numeric) && numeric >= 0 ? numeric : undefined;
20
+ }
21
+ /** Whether the file has more lines than `count`, reading one chunk at a time. Null when unreadable. */
22
+ export function fileHasMoreLinesThan(path, count) {
23
+ let descriptor;
24
+ try {
25
+ descriptor = openSync(path, "r");
26
+ const chunk = Buffer.allocUnsafe(LINE_COUNT_CHUNK_BYTES);
27
+ let newlines = 0, endsWithNewline = false;
28
+ for (;;) {
29
+ const read = readSync(descriptor, chunk, 0, chunk.length, null);
30
+ if (read === 0)
31
+ break;
32
+ const bytes = chunk.subarray(0, read);
33
+ for (let index = bytes.indexOf(0x0a); index !== -1; index = bytes.indexOf(0x0a, index + 1)) {
34
+ if (++newlines > count)
35
+ return true;
36
+ }
37
+ endsWithNewline = bytes[read - 1] === 0x0a;
38
+ }
39
+ return (endsWithNewline ? newlines : newlines + 1) > count;
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ finally {
45
+ if (descriptor !== undefined)
46
+ closeSync(descriptor);
47
+ }
48
+ }
49
+ /**
50
+ * The file a Read would gate, or undefined when any cheap condition fails:
51
+ * Claude Code main session (never subagents or other clients), gate enabled, a
52
+ * single file_path Read starting at line 1, a code file inside the workspace
53
+ * between 32 KB and the smart-tool limit, and a Read that returns the whole file.
54
+ */
55
+ export function readGateCandidate(request) {
56
+ if (!request.enabled || request.adapter !== "claude" || request.agentId || request.toolName !== "Read")
57
+ return undefined;
58
+ const filePath = request.toolInput.file_path;
59
+ if (typeof filePath !== "string" || !filePath.trim())
60
+ return undefined;
61
+ if ((windowValue(request.toolInput.offset) ?? 0) > 1)
62
+ return undefined;
63
+ if (!GATED_EXTENSIONS.has(extname(filePath).toLowerCase()))
64
+ return undefined;
65
+ let path, root, size, mtimeMs;
66
+ try {
67
+ root = realpathSync(request.workspace);
68
+ path = realpathSync(isAbsolute(filePath) ? filePath : resolve(request.workspace, filePath));
69
+ const stats = statSync(path);
70
+ if (!stats.isFile())
71
+ return undefined;
72
+ size = stats.size;
73
+ mtimeMs = stats.mtimeMs;
74
+ }
75
+ catch {
76
+ return undefined;
77
+ }
78
+ // Same containment the smart tools apply: a symlink leading outside the workspace is outside.
79
+ const inside = relative(root, path);
80
+ if (!inside || inside.startsWith(".." + sep) || inside === ".." || isAbsolute(inside))
81
+ return undefined;
82
+ if (size < FILE_READ_GATE_MIN_BYTES || size > SMART_TOOL_MAX_BYTES)
83
+ return undefined;
84
+ const limit = windowValue(request.toolInput.limit);
85
+ if (limit !== undefined && fileHasMoreLinesThan(path, limit) !== false)
86
+ return undefined;
87
+ return { path, mtimeMs };
88
+ }
89
+ /** Newest update time among the memories, in epoch milliseconds; 0 when none is known. */
90
+ export function newestMemoryEpoch(value) {
91
+ const record = value && typeof value === "object" ? value : {};
92
+ const rows = Array.isArray(value) ? value : Array.isArray(record.memories) ? record.memories : Array.isArray(record.value) ? record.value : [];
93
+ let newest = 0;
94
+ for (const row of rows) {
95
+ const item = row && typeof row === "object" ? row : {};
96
+ for (const key of ["updatedAtEpoch", "createdAtEpoch"]) {
97
+ if (typeof item[key] === "number" && Number.isFinite(item[key]))
98
+ newest = Math.max(newest, item[key]);
99
+ }
100
+ for (const key of ["updatedAt", "updated_at", "createdAt", "created_at"]) {
101
+ const parsed = typeof item[key] === "string" ? Date.parse(item[key]) : NaN;
102
+ if (Number.isFinite(parsed))
103
+ newest = Math.max(newest, parsed);
104
+ }
105
+ }
106
+ return newest;
107
+ }
108
+ /** The deny reason shown to Claude. Memory titles are history, never the file's current contents. */
109
+ export function readGateReason(requestedPath, memoryIds, context) {
110
+ const shown = requestedPath;
111
+ const safe = JSON.stringify(requestedPath);
112
+ return [
113
+ `Full-file Read blocked by Vault Memory: ${shown} is a large file with prior memories (listed below). Get what you need without reading the whole file:`,
114
+ `- Current code: call vault_go_smart_outline(filePath=${safe}) for its symbols and line numbers, then vault_go_smart_unfold(filePath=${safe}, symbolName="<name>") for the ones you need (MCP tools mcp__vault-go__vault_go_smart_outline / __vault_go_smart_unfold; load them with ToolSearch if they are deferred).`,
115
+ `- Past work: call vault_go_observations(ids=${JSON.stringify(memoryIds.slice(0, 20))}) for the memories that matter.`,
116
+ `- Exact lines, e.g. before an Edit: call Read on this file again with offset and limit. Partial reads are allowed and satisfy Edit's read requirement.`,
117
+ `The memories below are history from earlier sessions, not the file's current contents.`,
118
+ "",
119
+ context,
120
+ ].join("\n");
121
+ }
package/dist/server.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { VAULT_WEB_MEMORY_URL } from './vault-cloud.js';
1
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
3
  import { z } from 'zod';
3
4
  import { jsonResult, run } from './mcp-result.js';
@@ -18,6 +19,25 @@ import { estimateAgentCost } from './agent-cost.js';
18
19
  import { flattenArchive, groupArchive, recordsFromExport } from './memory-archive.js';
19
20
  import { CONTEXT_ENGINE_IDS, generateContext, listContextEngines, parseEngineModel, selectContextEngine, selectEngineModel, selectedContextEngine, selectedEngineModel, } from './context-engines.js';
20
21
  import packageJson from '../package.json' with { type: 'json' };
22
+ // Sent once at initialize; clients put it in the model's context, so it stays short.
23
+ const SERVER_INSTRUCTIONS = [
24
+ 'Vault Memory: persistent memory of past sessions, scoped to the project of the current directory (scope "account" widens a read to every project).',
25
+ 'Recall cheaply in layers: vault_go_search_index (compact list) → vault_go_timeline (around one result) → vault_go_observations (full text of the chosen IDs). vault_go_retrieve picks the strategy for a natural-language question.',
26
+ 'Before reading a large code file, prefer vault_go_smart_outline and vault_go_smart_unfold, then a Read with offset/limit for exact lines.',
27
+ 'Save durable decisions, discoveries and fixes with vault_go_remember. Never store secrets; text inside <private> tags is never stored.',
28
+ 'When a tool fails with an expired session, ask the user to run: bunx --bun vault-go@latest login',
29
+ ].join('\n');
30
+ // A saved memory carries its page in the Vault web app, so the agent can hand the user a direct link.
31
+ function withVaultLink(value) {
32
+ if (!value || typeof value !== 'object' || Array.isArray(value))
33
+ return value;
34
+ const record = value;
35
+ const id = record.id, projectId = record.projectId;
36
+ if (typeof id !== 'string' || !/^[0-9a-f-]{36}$/i.test(id))
37
+ return value;
38
+ const params = new URLSearchParams({ memory: id, ...(typeof projectId === 'string' && /^[0-9a-f-]{36}$/i.test(projectId) ? { project: projectId } : {}) });
39
+ return { ...record, url: `${VAULT_WEB_MEMORY_URL}?${params}` };
40
+ }
21
41
  export const VERSION = packageJson.version;
22
42
  const uuid = z.string().uuid();
23
43
  const knowledgeFilters = z.object({
@@ -54,7 +74,7 @@ async function managedEngineStatus(cloud) {
54
74
  }
55
75
  }
56
76
  export function createVaultGoServer(home = vaultHome(), cloud = new VaultCloudClient(home)) {
57
- const server = new McpServer({ name: 'vault-go', version: VERSION });
77
+ const server = new McpServer({ name: 'vault-go', version: VERSION }, { instructions: SERVER_INSTRUCTIONS });
58
78
  registerVaultModes(server);
59
79
  registerCrystalTools(server, home);
60
80
  registerKnowledgeAgentTools(server, home, cloud);
@@ -170,7 +190,7 @@ export function createVaultGoServer(home = vaultHome(), cloud = new VaultCloudCl
170
190
  filesModified: z.array(z.string().trim().min(1).max(2_000)).max(500).default([]),
171
191
  },
172
192
  annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
173
- }, async (input) => run(() => cloud.remember(redact(input))));
193
+ }, async (input) => run(async () => withVaultLink(await cloud.remember(redact(input)))));
174
194
  server.registerTool('vault_go_forget', {
175
195
  title: 'Excluir memória',
176
196
  description: 'Exclui uma memória da conta autenticada.',
@@ -14,8 +14,8 @@ export function registerTranscriptTools(server, home, cloud) {
14
14
  server.registerTool('vault_go_transcripts_poll', { description: 'Processa um lote limitado dos watchers já consentidos. Retomada e idempotência persistem entre reinícios; não ativa captura nem muda o motor.', inputSchema: {}, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true } }, () => run(() => pollTranscripts(home, cloud)));
15
15
  server.registerTool('vault_go_context_chain', { description: 'Lista vínculos locais entre workspaces para contexto multi-project do SessionStart. Vínculos são específicos desta conta.', inputSchema: {}, annotations: { readOnlyHint: true, openWorldHint: false } }, () => run(async () => contextChainStatus(home)));
16
16
  server.registerTool('vault_go_context_chain_configure', {
17
- description: 'Define até quatro raízes relacionadas e/ou o checkout pai de um worktree. Exige consentimento; só consulta projetos já existentes e acessíveis na conta, dentro do orçamento total de contexto. Lista vazia remove os vínculos.',
18
- inputSchema: { workspace: path, roots: z.array(path).max(4), consent: z.boolean().default(false), includeWorktreeParent: z.boolean().default(false) },
17
+ description: 'Define até quatro raízes relacionadas, o checkout pai de um worktree e/ou allProjects (todos os projetos não privados da conta, até cinco no total). Exige consentimento; só consulta projetos já existentes e acessíveis na conta, dentro do orçamento total de contexto. Lista vazia remove os vínculos.',
18
+ inputSchema: { workspace: path, roots: z.array(path).max(4), consent: z.boolean().default(false), includeWorktreeParent: z.boolean().default(false), allProjects: z.boolean().default(false) },
19
19
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
20
20
  }, input => run(async () => configureContextChain(home, input)));
21
21
  }
@@ -0,0 +1,6 @@
1
+ /** Scalars and string lists rendered as JSON, which YAML reads as the same values. */
2
+ export type YamlEntryValue = string | number | boolean | readonly string[] | Readonly<Record<string, string>>;
3
+ /** `text` with `<topKey>.<name>` set to `fields`, replacing an entry of that name. */
4
+ export declare function upsertYamlEntry(text: string, topKey: string, name: string, fields: Record<string, YamlEntryValue>): string;
5
+ /** Whether `<topKey>.<name>` exists as a block entry. */
6
+ export declare function hasYamlEntry(text: string, topKey: string, name: string): boolean;