vault-go 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -87,6 +87,8 @@ Comandos explícitos:
87
87
  ```sh
88
88
  bunx --bun vault-go@latest setup
89
89
  bunx --bun vault-go@latest setup --clients codex,claude,copilot
90
+ bunx --bun vault-go@latest setup --engine openrouter
91
+ bunx --bun vault-go@latest setup --engine claude --model sonnet
90
92
  bunx --bun vault-go@latest login --force
91
93
  bunx --bun vault-go@latest serve
92
94
  bunx --bun vault-go@latest engine list
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { type McpClient } from './installer.js';
2
+ import { type ContextEngineId } from './context-engines.js';
2
3
  export type CliMode = 'help' | 'version' | 'setup' | 'login' | 'serve' | 'local' | 'engine' | 'invalid';
3
4
  export declare function resolveCliMode(argument: string | undefined, stdinIsTTY: boolean, stderrIsTTY: boolean): CliMode;
5
+ export declare function parseEngineChoice(value: string): ContextEngineId;
4
6
  export declare function parseNumberedClients(value: string): McpClient[];
5
7
  export declare function cliArguments(args: string[]): {
6
8
  argument: string | undefined;
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@ import { authenticateWithApiKey, authenticateWithBrowser, validateExistingAuthen
2
2
  import { vaultHome } from './config.js';
3
3
  import { detectMcpClients, installMcpClient, MCP_CLIENTS, parseClientSelection, } from './installer.js';
4
4
  import { promptSecret, promptText } from './prompt.js';
5
- import { CONTEXT_ENGINE_IDS, isApiEngine, listContextEngines, saveEngineKey, selectContextEngine, } from './context-engines.js';
5
+ import { CONTEXT_ENGINE_IDS, ENGINE_NAMES, engineModels, isApiEngine, listContextEngines, loadEngineKey, parseEngineModel, saveEngineKey, selectContextEngine, selectEngineModel, selectedContextEngine, selectedEngineModel, } from './context-engines.js';
6
6
  import { VaultCloudClient } from './cloud.js';
7
7
  import { copy, resolveLocale } from './locale.js';
8
8
  import { installLocal, localStatus, localUrl, openLocal, startLocal, stopLocal, uninstallLocal } from './local-install.js';
@@ -56,6 +56,30 @@ function defaultClients() {
56
56
  return detected.map((item) => item.client);
57
57
  return ['codex', 'claude', 'cursor', 'vscode', 'copilot'];
58
58
  }
59
+ const ENGINE_ALIASES = {
60
+ '1': 'vault-ai-resume',
61
+ '2': 'claude-subscription',
62
+ '3': 'openai-subscription',
63
+ '4': 'openrouter',
64
+ '5': 'gemini',
65
+ vault: 'vault-ai-resume',
66
+ 'vault-ai': 'vault-ai-resume',
67
+ 'vault-ai-resume': 'vault-ai-resume',
68
+ claude: 'claude-subscription',
69
+ 'claude-subscription': 'claude-subscription',
70
+ openai: 'openai-subscription',
71
+ chatgpt: 'openai-subscription',
72
+ codex: 'openai-subscription',
73
+ 'openai-subscription': 'openai-subscription',
74
+ openrouter: 'openrouter',
75
+ gemini: 'gemini',
76
+ };
77
+ export function parseEngineChoice(value) {
78
+ const engine = ENGINE_ALIASES[value.trim().toLowerCase()];
79
+ if (!engine)
80
+ throw new Error(`Motor inválido: ${value}`);
81
+ return engine;
82
+ }
59
83
  export function parseNumberedClients(value) {
60
84
  return parseClientSelection(value.split(',').map(item => {
61
85
  const trimmed = item.trim();
@@ -80,6 +104,71 @@ async function chooseClients(args, locale) {
80
104
  const answer = await promptText(`${t.choose} [${defaults.join(',')}]: `);
81
105
  return answer ? parseNumberedClients(answer) : defaults;
82
106
  }
107
+ async function chooseEngine(args, locale, home) {
108
+ const t = copy[locale];
109
+ const configured = optionValue(args, '--engine');
110
+ const engine = configured ? parseEngineChoice(configured) : await promptEngine(home, locale);
111
+ selectContextEngine(home, engine);
112
+ process.stderr.write(` ✓ ${t.engineSaved}: ${ENGINE_NAMES[engine]}\n`);
113
+ await chooseModel(args, locale, home, engine);
114
+ if (isApiEngine(engine) && !loadEngineKey(home, engine)) {
115
+ try {
116
+ const key = await promptSecret(` ${t.engineKey} (${ENGINE_NAMES[engine]}): `);
117
+ if (key.trim()) {
118
+ saveEngineKey(home, engine, key);
119
+ process.stderr.write(` ✓ ${t.engineKeySaved}\n`);
120
+ }
121
+ else {
122
+ process.stderr.write(` ○ ${t.engineKeyLater} vault-go engine key ${engine}\n`);
123
+ }
124
+ }
125
+ catch {
126
+ process.stderr.write(` ○ ${t.engineKeyLater} vault-go engine key ${engine}\n`);
127
+ }
128
+ }
129
+ return engine;
130
+ }
131
+ async function promptEngine(home, locale) {
132
+ const t = copy[locale];
133
+ process.stderr.write(` ${t.engineHelp}\n`);
134
+ try {
135
+ const listed = await listContextEngines(home, await managedStatus(home));
136
+ for (const [index, engine] of listed.engines.entries()) {
137
+ const mark = engine.available ? `● ${t.detected}` : `○ ${t.available}`;
138
+ process.stderr.write(` ${index + 1}. ${ENGINE_NAMES[engine.id].padEnd(18)} ${mark}\n`);
139
+ }
140
+ }
141
+ catch {
142
+ for (const [index, id] of CONTEXT_ENGINE_IDS.entries()) {
143
+ process.stderr.write(` ${index + 1}. ${ENGINE_NAMES[id]}\n`);
144
+ }
145
+ }
146
+ const answer = await promptText(`${t.choose} [1. ${ENGINE_NAMES['vault-ai-resume']}]: `);
147
+ return answer ? parseEngineChoice(answer) : 'vault-ai-resume';
148
+ }
149
+ async function chooseModel(args, locale, home, engine) {
150
+ const catalog = engineModels(engine);
151
+ if (catalog.length === 0)
152
+ return;
153
+ const t = copy[locale];
154
+ const configured = optionValue(args, '--model');
155
+ const model = configured
156
+ ? parseEngineModel(engine, configured)
157
+ : await promptModel(home, engine, locale);
158
+ selectEngineModel(home, engine, model);
159
+ process.stderr.write(` ✓ ${t.modelSaved}: ${catalog.find((item) => item.id === model)?.label ?? model}\n`);
160
+ }
161
+ async function promptModel(home, engine, locale) {
162
+ const t = copy[locale];
163
+ const catalog = engineModels(engine);
164
+ process.stderr.write(` ${t.modelHelp}\n`);
165
+ for (const [index, item] of catalog.entries()) {
166
+ process.stderr.write(` ${index + 1}. ${item.label}\n`);
167
+ }
168
+ const fallback = selectedEngineModel(home, engine) || catalog[0].id;
169
+ const answer = await promptText(`${t.choose} [${catalog[0].label}]: `);
170
+ return answer ? parseEngineModel(engine, answer) : fallback;
171
+ }
83
172
  export function cliArguments(args) {
84
173
  const remaining = [...args];
85
174
  for (let i = 0; i < remaining.length; i++) {
@@ -170,13 +259,15 @@ export async function runSetup(args) {
170
259
  const t = copy[locale];
171
260
  const local = localCopy[locale];
172
261
  process.env['VAULT_GO_LANG'] = locale;
173
- process.stderr.write(` ${t.subtitle}\n\n [1/4] ${t.auth}\n`);
262
+ process.stderr.write(` ${t.subtitle}\n\n [1/5] ${t.auth}\n`);
174
263
  await ensureAuthentication(vaultHome(), args.includes('--reauth'), args.includes('--api-key'), locale);
175
- process.stderr.write(`\n [2/4] ${t.clients}\n`);
264
+ process.stderr.write(`\n [2/5] ${t.clients}\n`);
176
265
  const clients = await chooseClients(args, locale);
177
266
  if (clients.length === 0)
178
267
  throw new Error(t.none);
179
- process.stderr.write(`\n [3/4] ${t.install}\n`);
268
+ process.stderr.write(`\n [3/5] ${t.engine}\n`);
269
+ await chooseEngine(args, locale, vaultHome());
270
+ process.stderr.write(`\n [4/5] ${t.install}\n`);
180
271
  let failures = 0;
181
272
  for (const client of clients) {
182
273
  try {
@@ -190,7 +281,7 @@ export async function runSetup(args) {
190
281
  process.stderr.write(` ✗ ${t.failed} ${client}: ${message}\n`);
191
282
  }
192
283
  }
193
- process.stderr.write(`\n [4/4] ${local.step}\n`);
284
+ process.stderr.write(`\n [5/5] ${local.step}\n`);
194
285
  try {
195
286
  const result = await installLocal();
196
287
  process.stderr.write(` ✓ ${result.startup ? local.startup : local.manual}\n`);
@@ -213,8 +304,10 @@ Uso:
213
304
  bunx --bun vault-go@latest
214
305
  Abre o login Vault no navegador e instala nos clientes detectados.
215
306
 
216
- bunx --bun vault-go@latest setup [--clients lista] [--reauth]
217
- Executa o assistente de autenticação e instalação.
307
+ bunx --bun vault-go@latest setup [--clients lista] [--engine motor] [--model modelo] [--reauth]
308
+ Executa o assistente de autenticação e instalação. Pergunta o motor
309
+ (Vault AI, Claude, OpenRouter, Gemini ou ChatGPT/Codex) e o modelo
310
+ (Haiku, Sonnet, Opus no Claude).
218
311
 
219
312
  bunx --bun vault-go@latest login [--force] [--api-key]
220
313
  Autentica sem alterar clientes MCP.
@@ -225,9 +318,8 @@ Uso:
225
318
  vault-go local [install|start|open|status|stop|uninstall|serve]
226
319
  Painel local em http://localhost:38850; install ativa início automático no macOS.
227
320
 
228
- vault-go engine list|use <motor>|key openrouter|gemini
229
- Escolhe o motor de contexto: vault-ai-resume, claude-subscription,
230
- openai-subscription, openrouter ou gemini. key grava a chave em prompt oculto.
321
+ vault-go engine list|use <motor> [--model haiku|sonnet|opus]|key openrouter|gemini
322
+ Escolhe o motor e o modelo de contexto. No Claude: haiku, sonnet ou opus.
231
323
 
232
324
  --lang pt|en|es
233
325
  Idioma do assistente (padrão: idioma do sistema).
@@ -276,7 +368,21 @@ export async function runEngine(args) {
276
368
  if (!engine || !CONTEXT_ENGINE_IDS.includes(engine)) {
277
369
  throw new Error(`Motor inválido. Use: ${CONTEXT_ENGINE_IDS.join(', ')}`);
278
370
  }
279
- process.stdout.write(`${selectContextEngine(home, engine)}\n`);
371
+ const selected = parseEngineChoice(engine);
372
+ selectContextEngine(home, selected);
373
+ const modelFlag = optionValue(args, '--model');
374
+ const model = modelFlag
375
+ ? selectEngineModel(home, selected, parseEngineModel(selected, modelFlag))
376
+ : selectedEngineModel(home, selected);
377
+ process.stdout.write(`${JSON.stringify({ selected, ...(model ? { model } : {}) })}\n`);
378
+ return 0;
379
+ }
380
+ if (action === 'model') {
381
+ const engine = selectContextEngine(home, selectedContextEngine(home));
382
+ const value = positional[1] ?? optionValue(args, '--model');
383
+ if (!value)
384
+ throw new Error('Use vault-go engine model haiku|sonnet|opus');
385
+ process.stdout.write(`${selectEngineModel(home, engine, parseEngineModel(engine, value))}\n`);
280
386
  return 0;
281
387
  }
282
388
  if (action === 'key') {
@@ -287,5 +393,5 @@ export async function runEngine(args) {
287
393
  process.stderr.write(`Chave ${engine} gravada com permissão privada.\n`);
288
394
  return 0;
289
395
  }
290
- throw new Error('vault-go engine list|use <motor>|key openrouter|gemini');
396
+ throw new Error('vault-go engine list|use <motor> [--model haiku|sonnet|opus]|model <modelo>|key openrouter|gemini');
291
397
  }
@@ -2,6 +2,13 @@ export declare const CONTEXT_ENGINE_IDS: readonly ["vault-ai-resume", "claude-su
2
2
  export declare const API_ENGINE_IDS: readonly ["openrouter", "gemini"];
3
3
  export type ContextEngineId = (typeof CONTEXT_ENGINE_IDS)[number];
4
4
  export type ApiEngineId = (typeof API_ENGINE_IDS)[number];
5
+ export declare const ENGINE_NAMES: Record<ContextEngineId, string>;
6
+ export interface EngineModelOption {
7
+ id: string;
8
+ label: string;
9
+ aliases?: readonly string[];
10
+ }
11
+ export declare const ENGINE_MODELS: Record<ContextEngineId, readonly EngineModelOption[]>;
5
12
  export interface ContextResult {
6
13
  title: string;
7
14
  content: string;
@@ -17,6 +24,8 @@ export interface EngineAvailability {
17
24
  export interface ContextEngine extends EngineAvailability {
18
25
  id: ContextEngineId;
19
26
  name: string;
27
+ models: EngineModelOption[];
28
+ model?: string;
20
29
  }
21
30
  export interface CommandRequest {
22
31
  command: "claude" | "codex";
@@ -37,6 +46,10 @@ export declare function selectedContextEngine(home: string): ContextEngineId;
37
46
  export declare function selectContextEngine(home: string, engine: unknown): ContextEngineId;
38
47
  export declare function saveEngineKey(home: string, engine: unknown, key: unknown): ApiEngineId;
39
48
  export declare function loadEngineKey(home: string, engine: ApiEngineId, environment?: NodeJS.ProcessEnv): string | undefined;
49
+ export declare function engineModels(engine: ContextEngineId): EngineModelOption[];
50
+ export declare function parseEngineModel(engine: unknown, value: unknown): string;
51
+ export declare function selectedEngineModel(home: string, engine: ContextEngineId): string;
52
+ export declare function selectEngineModel(home: string, engine: unknown, model: unknown): string;
40
53
  export declare function contextEnvironment(source?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
41
54
  /** Never includes process stderr or model input in an error message. */
42
55
  export declare const runContextCommand: CommandRunner;
@@ -45,6 +58,7 @@ export declare function listContextEngines(home: string, managedStatus?: EngineA
45
58
  env?: NodeJS.ProcessEnv;
46
59
  }): Promise<{
47
60
  selected: ContextEngineId;
61
+ model?: string;
48
62
  engines: ContextEngine[];
49
63
  }>;
50
64
  export declare function validateContextResult(value: unknown, engine: ContextEngineId): ContextResult;
@@ -10,17 +10,44 @@ export const CONTEXT_ENGINE_IDS = [
10
10
  "gemini",
11
11
  ];
12
12
  export const API_ENGINE_IDS = ["openrouter", "gemini"];
13
- const ENGINE_NAMES = {
13
+ export const ENGINE_NAMES = {
14
14
  "vault-ai-resume": "Vault AI Resume",
15
15
  "claude-subscription": "Claude",
16
16
  "openai-subscription": "ChatGPT / Codex",
17
17
  openrouter: "OpenRouter",
18
18
  gemini: "Gemini",
19
19
  };
20
+ export const ENGINE_MODELS = {
21
+ "vault-ai-resume": [],
22
+ "claude-subscription": [
23
+ { id: "haiku", label: "Haiku" },
24
+ { id: "sonnet", label: "Sonnet" },
25
+ { id: "opus", label: "Opus" },
26
+ ],
27
+ "openai-subscription": [
28
+ { id: "gpt-5-mini", label: "GPT-5 mini" },
29
+ { id: "gpt-5", label: "GPT-5" },
30
+ { id: "o3", label: "o3" },
31
+ ],
32
+ openrouter: [
33
+ { id: "xiaomi/mimo-v2-flash:free", label: "Free", aliases: ["free"] },
34
+ { id: "anthropic/claude-haiku-4.5", label: "Haiku", aliases: ["haiku"] },
35
+ { id: "anthropic/claude-sonnet-4.5", label: "Sonnet", aliases: ["sonnet"] },
36
+ { id: "anthropic/claude-opus-4.1", label: "Opus", aliases: ["opus"] },
37
+ ],
38
+ gemini: [
39
+ { id: "gemini-flash-latest", label: "Flash", aliases: ["flash"] },
40
+ { id: "gemini-2.5-pro", label: "Pro", aliases: ["pro"] },
41
+ ],
42
+ };
20
43
  const DEFAULT_MODELS = {
44
+ "vault-ai-resume": "",
45
+ "claude-subscription": "haiku",
46
+ "openai-subscription": "gpt-5-mini",
21
47
  openrouter: "xiaomi/mimo-v2-flash:free",
22
48
  gemini: "gemini-flash-latest",
23
49
  };
50
+ const CUSTOM_MODEL = /^[a-zA-Z0-9][a-zA-Z0-9_.:/-]{0,199}$/;
24
51
  const KEY_ENV = {
25
52
  openrouter: ["VAULT_GO_OPENROUTER_API_KEY", "OPENROUTER_API_KEY"],
26
53
  gemini: ["VAULT_GO_GEMINI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY"],
@@ -87,14 +114,61 @@ export function loadEngineKey(home, engine, environment = process.env) {
87
114
  const stored = readJson(secretsPath(home))[engine];
88
115
  return typeof stored === "string" && stored.trim() ? stored.trim() : undefined;
89
116
  }
90
- function engineModel(home, engine) {
117
+ export function engineModels(engine) {
118
+ return ENGINE_MODELS[engine].map(({ id, label }) => ({ id, label }));
119
+ }
120
+ export function parseEngineModel(engine, value) {
121
+ if (!isContextEngine(engine))
122
+ throw new Error("Unknown context engine.");
123
+ const catalog = ENGINE_MODELS[engine];
124
+ if (catalog.length === 0)
125
+ throw new Error("This engine does not select a model.");
126
+ if (typeof value !== "string" || !value.trim())
127
+ throw new Error("Unknown model.");
128
+ const input = value.trim();
129
+ const index = /^\d+$/.test(input) ? Number(input) - 1 : -1;
130
+ const numbered = catalog[index];
131
+ if (numbered)
132
+ return numbered.id;
133
+ const lower = input.toLowerCase();
134
+ const match = catalog.find((item) => item.id.toLowerCase() === lower ||
135
+ item.label.toLowerCase() === lower ||
136
+ item.aliases?.some((alias) => alias.toLowerCase() === lower));
137
+ if (match)
138
+ return match.id;
139
+ if ((engine === "openrouter" || engine === "gemini") && CUSTOM_MODEL.test(input))
140
+ return input;
141
+ throw new Error("Unknown model.");
142
+ }
143
+ export function selectedEngineModel(home, engine) {
91
144
  const models = readJson(settingsPath(home)).models;
92
145
  const configured = models && typeof models === "object" && !Array.isArray(models)
93
146
  ? models[engine]
94
147
  : undefined;
95
- return typeof configured === "string" && configured.trim()
96
- ? configured.trim().slice(0, 200)
97
- : DEFAULT_MODELS[engine];
148
+ if (typeof configured === "string" && configured.trim()) {
149
+ try {
150
+ return parseEngineModel(engine, configured);
151
+ }
152
+ catch {
153
+ return DEFAULT_MODELS[engine];
154
+ }
155
+ }
156
+ return DEFAULT_MODELS[engine];
157
+ }
158
+ export function selectEngineModel(home, engine, model) {
159
+ if (!isContextEngine(engine))
160
+ throw new Error("Unknown context engine.");
161
+ const resolved = parseEngineModel(engine, model);
162
+ const current = readJson(settingsPath(home));
163
+ const models = current.models && typeof current.models === "object" && !Array.isArray(current.models)
164
+ ? { ...current.models }
165
+ : {};
166
+ models[engine] = resolved;
167
+ writePrivateJson(settingsPath(home), { ...current, engine, models });
168
+ return resolved;
169
+ }
170
+ function engineModel(home, engine) {
171
+ return selectedEngineModel(home, engine);
98
172
  }
99
173
  export function contextEnvironment(source = process.env) {
100
174
  const env = {};
@@ -252,22 +326,24 @@ export async function listContextEngines(home, managedStatus = {
252
326
  checkSubscription("claude-subscription", cwd, runner, env),
253
327
  checkSubscription("openai-subscription", cwd, runner, env),
254
328
  ]);
329
+ const selected = selectedContextEngine(home);
330
+ const withModels = (id, availability) => ({
331
+ id,
332
+ name: ENGINE_NAMES[id],
333
+ ...availability,
334
+ models: engineModels(id),
335
+ ...(engineModels(id).length > 0 ? { model: selectedEngineModel(home, id) } : {}),
336
+ });
337
+ const selectedModel = selectedEngineModel(home, selected);
255
338
  return {
256
- selected: selectedContextEngine(home),
339
+ selected,
340
+ ...(selectedModel ? { model: selectedModel } : {}),
257
341
  engines: [
258
- { id: "vault-ai-resume", name: ENGINE_NAMES["vault-ai-resume"], ...managedStatus },
259
- { id: "claude-subscription", name: ENGINE_NAMES["claude-subscription"], ...claude },
260
- { id: "openai-subscription", name: ENGINE_NAMES["openai-subscription"], ...codex },
261
- {
262
- id: "openrouter",
263
- name: ENGINE_NAMES.openrouter,
264
- ...apiAvailability(home, "openrouter", options.env),
265
- },
266
- {
267
- id: "gemini",
268
- name: ENGINE_NAMES.gemini,
269
- ...apiAvailability(home, "gemini", options.env),
270
- },
342
+ withModels("vault-ai-resume", managedStatus),
343
+ withModels("claude-subscription", claude),
344
+ withModels("openai-subscription", codex),
345
+ withModels("openrouter", apiAvailability(home, "openrouter", options.env)),
346
+ withModels("gemini", apiAvailability(home, "gemini", options.env)),
271
347
  ],
272
348
  };
273
349
  }
@@ -469,6 +545,8 @@ export async function generateContext(home, engine, text, options = {}) {
469
545
  args = [
470
546
  "--safe-mode",
471
547
  "-p",
548
+ "--model",
549
+ engineModel(home, engine),
472
550
  "--tools",
473
551
  "",
474
552
  "--strict-mcp-config",
@@ -498,6 +576,8 @@ export async function generateContext(home, engine, text, options = {}) {
498
576
  "-c",
499
577
  'forced_login_method="chatgpt"',
500
578
  "-c",
579
+ `model=${JSON.stringify(engineModel(home, engine))}`,
580
+ "-c",
501
581
  'approval_policy="never"',
502
582
  "-c",
503
583
  'web_search="disabled"',
@@ -5,7 +5,7 @@ export function dashboardHtml() {
5
5
 
6
6
  /* Same identity tokens and glass surfaces as the Vault portal. */
7
7
  :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}}
8
- .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}@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="https://vault.resolveup.com.br/app" 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 client</h2><p data-i18n="stepClientBody">Configure Vault MCP in Codex, Cursor or another compatible client.</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">Create your first memory</h2><p data-i18n="firstBody">Connect Vault MCP to your AI client, then ask it to save context. Your memories will appear here after a cloud refresh.</p><code id="mcp-command" style="display:block;margin-top:12px;font-size:12px;overflow-wrap:anywhere"></code><p data-i18n="rememberPrompt" style="margin-top:10px"></p></div><div class="actions"><a class="button" href="https://vault.resolveup.com.br/mcp" target="_blank" rel="noopener noreferrer" data-i18n="setupMcp">Set up MCP ↗</a><a class="link" href="https://vault.resolveup.com.br/app/memory" 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-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>`;
8
+ .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}@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="https://vault.resolveup.com.br/app" 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 client</h2><p data-i18n="stepClientBody">Configure Vault MCP in Codex, Cursor or another compatible client.</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">Create your first memory</h2><p data-i18n="firstBody">Connect Vault MCP to your AI client, then ask it to save context. Your memories will appear here after a cloud refresh.</p><code id="mcp-command" style="display:block;margin-top:12px;font-size:12px;overflow-wrap:anywhere"></code><p data-i18n="rememberPrompt" style="margin-top:10px"></p></div><div class="actions"><a class="button" href="https://vault.resolveup.com.br/mcp" target="_blank" rel="noopener noreferrer" data-i18n="setupMcp">Set up MCP ↗</a><a class="link" href="https://vault.resolveup.com.br/app/memory" 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
9
  }
10
10
  export function dashboardScript() {
11
11
  return `"use strict";
@@ -146,6 +146,19 @@ export function dashboardScript() {
146
146
  label.append(input,name,node('p','',engineReason(engine)));if(engine.billing)label.append(node('p','',engineBilling(engine)));target.append(label);
147
147
  }
148
148
  text('engine-message',engineMessage?t(engineMessage):!engines?t('engineLoading'):'');
149
+ const models=el('model-options');models.replaceChildren();models.setAttribute('aria-label',t('contextEngine'));
150
+ const current=array(engines&&engines.engines).find(engine=>engine.id===engines.selected);
151
+ for(const item of array(current&&current.models)) {
152
+ const label=node('label','engine-option'),input=node('input',''),name=node('strong','',scalar(item.label)||scalar(item.id));
153
+ input.type='radio';input.name='context-model';input.value=item.id;input.checked=(current.model||engines.model)===item.id;input.disabled=engineBusy||!!contextJob||contextStarting;
154
+ input.addEventListener('change',async()=>{
155
+ if(!input.checked||engineBusy)return;const epoch=sessionEpoch;engineBusy=true;engineMessage='';renderEngines();
156
+ try {const result=await request('/api/context/model',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({engine:current.id,model:item.id})});if(result&&!sessionRequired&&epoch===sessionEpoch&&engines){current.model=item.id;engines.model=item.id;engineMessage='engineSaved';}}
157
+ catch {if(epoch===sessionEpoch)engineMessage='engineFailed';}
158
+ finally{engineBusy=false;render();}
159
+ });
160
+ label.append(input,name);models.append(label);
161
+ }
149
162
  const keys=el('engine-keys');keys.replaceChildren();
150
163
  if(engines) for(const provider of ['openrouter','gemini']) {
151
164
  const wrap=node('div',''),label=node('label','',t(provider==='openrouter'?'openrouterKey':'geminiKey')),input=node('input',''),button=node('button','button',t('saveKey'));
@@ -12,7 +12,7 @@ import { resolveLocale } from "./locale.js";
12
12
  import { callbackPage } from "./callback-page.js";
13
13
  import { privateStore } from "./private-store.js";
14
14
  import { localDevice } from "./local-device.js";
15
- import { generateContext, isApiEngine, isContextEngine, listContextEngines, saveEngineKey, selectContextEngine, selectedContextEngine, } from "./context-engines.js";
15
+ import { generateContext, isApiEngine, isContextEngine, listContextEngines, parseEngineModel, saveEngineKey, selectContextEngine, selectEngineModel, selectedContextEngine, } from "./context-engines.js";
16
16
  export const LOCAL_PORT = 38850;
17
17
  const safeEqual = (a, b) => a.length === b.length &&
18
18
  /^[A-Za-z0-9_-]{43}$/.test(a) &&
@@ -467,6 +467,21 @@ export async function startLocalService(options = {}) {
467
467
  json(200, { selected: data.engine });
468
468
  return;
469
469
  }
470
+ if (url.pathname === "/api/context/model" && req.method === "PUT") {
471
+ const data = (await readBody(req));
472
+ const engine = isContextEngine(data.engine)
473
+ ? data.engine
474
+ : selectedContextEngine(home);
475
+ try {
476
+ const model = selectEngineModel(home, engine, parseEngineModel(engine, data.model));
477
+ engineCache = undefined;
478
+ json(200, { selected: engine, model });
479
+ }
480
+ catch {
481
+ json(400, { error: "invalid_model" });
482
+ }
483
+ return;
484
+ }
470
485
  if (url.pathname === "/api/context/secret" && req.method === "PUT") {
471
486
  const data = (await readBody(req));
472
487
  if (!isApiEngine(data.provider) || typeof data.key !== "string") {
package/dist/locale.d.ts CHANGED
@@ -11,6 +11,14 @@ export declare const copy: {
11
11
  openFailed: string;
12
12
  connected: string;
13
13
  key: string;
14
+ engine: string;
15
+ engineHelp: string;
16
+ engineSaved: string;
17
+ engineKey: string;
18
+ engineKeySaved: string;
19
+ engineKeyLater: string;
20
+ modelHelp: string;
21
+ modelSaved: string;
14
22
  choose: string;
15
23
  detected: string;
16
24
  available: string;
@@ -42,6 +50,14 @@ export declare const copy: {
42
50
  openFailed: string;
43
51
  connected: string;
44
52
  key: string;
53
+ engine: string;
54
+ engineHelp: string;
55
+ engineSaved: string;
56
+ engineKey: string;
57
+ engineKeySaved: string;
58
+ engineKeyLater: string;
59
+ modelHelp: string;
60
+ modelSaved: string;
45
61
  choose: string;
46
62
  detected: string;
47
63
  available: string;
@@ -73,6 +89,14 @@ export declare const copy: {
73
89
  openFailed: string;
74
90
  connected: string;
75
91
  key: string;
92
+ engine: string;
93
+ engineHelp: string;
94
+ engineSaved: string;
95
+ engineKey: string;
96
+ engineKeySaved: string;
97
+ engineKeyLater: string;
98
+ modelHelp: string;
99
+ modelSaved: string;
76
100
  choose: string;
77
101
  detected: string;
78
102
  available: string;
package/dist/locale.js CHANGED
@@ -20,6 +20,14 @@ export const copy = {
20
20
  openFailed: "Não foi possível abrir automaticamente. Use o endereço acima.",
21
21
  connected: "Conta conectada",
22
22
  key: "Chave de API",
23
+ engine: "Escolher motor de contexto",
24
+ engineHelp: "Como o Vault prepara memórias: Claude, OpenRouter, Gemini ou Vault AI.",
25
+ engineSaved: "Motor selecionado",
26
+ engineKey: "Cole a chave (não será exibida). Enter para pular",
27
+ engineKeySaved: "Chave gravada com permissão privada.",
28
+ engineKeyLater: "Sem chave ainda. Depois execute:",
29
+ modelHelp: "Haiku é mais rápido; Sonnet é o equilíbrio; Opus é o mais capaz.",
30
+ modelSaved: "Modelo selecionado",
23
31
  choose: "Números ou nomes, separados por vírgula",
24
32
  detected: "detectado",
25
33
  available: "disponível",
@@ -51,6 +59,14 @@ export const copy = {
51
59
  openFailed: "Could not open automatically. Use the address above.",
52
60
  connected: "Account connected",
53
61
  key: "API key",
62
+ engine: "Choose context engine",
63
+ engineHelp: "How Vault prepares memories: Claude, OpenRouter, Gemini, or Vault AI.",
64
+ engineSaved: "Engine selected",
65
+ engineKey: "Paste the API key (it will not be shown). Enter to skip",
66
+ engineKeySaved: "Key stored with private file permissions.",
67
+ engineKeyLater: "No key yet. Later run:",
68
+ modelHelp: "Haiku is faster; Sonnet is the balance; Opus is the most capable.",
69
+ modelSaved: "Model selected",
54
70
  choose: "Numbers or names, separated by commas",
55
71
  detected: "detected",
56
72
  available: "available",
@@ -82,6 +98,14 @@ export const copy = {
82
98
  openFailed: "No se pudo abrir automáticamente. Usa la dirección anterior.",
83
99
  connected: "Cuenta conectada",
84
100
  key: "Clave de API",
101
+ engine: "Elegir motor de contexto",
102
+ engineHelp: "Cómo Vault prepara memorias: Claude, OpenRouter, Gemini o Vault AI.",
103
+ engineSaved: "Motor seleccionado",
104
+ engineKey: "Pega la clave (no se mostrará). Enter para omitir",
105
+ engineKeySaved: "Clave guardada con permisos privados.",
106
+ engineKeyLater: "Aún no hay clave. Después ejecuta:",
107
+ modelHelp: "Haiku es más rápido; Sonnet es el equilibrio; Opus es el más capaz.",
108
+ modelSaved: "Modelo seleccionado",
85
109
  choose: "Números o nombres, separados por comas",
86
110
  detected: "detectado",
87
111
  available: "disponible",
package/dist/server.js CHANGED
@@ -2,7 +2,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { z } from 'zod';
3
3
  import { VaultCloudClient } from './cloud.js';
4
4
  import { getStatus, vaultHome } from './config.js';
5
- import { CONTEXT_ENGINE_IDS, generateContext, listContextEngines, selectContextEngine, selectedContextEngine, } from './context-engines.js';
5
+ import { CONTEXT_ENGINE_IDS, generateContext, listContextEngines, parseEngineModel, selectContextEngine, selectEngineModel, selectedContextEngine, selectedEngineModel, } from './context-engines.js';
6
6
  import packageJson from '../package.json' with { type: 'json' };
7
7
  export const VERSION = packageJson.version;
8
8
  function jsonResult(value) {
@@ -70,10 +70,19 @@ export function createVaultGoServer(home = vaultHome(), cloud = new VaultCloudCl
70
70
  }, async () => run(async () => listContextEngines(home, await managedEngineStatus(cloud))));
71
71
  server.registerTool('vault_go_engine_select', {
72
72
  title: 'Escolher motor de contexto',
73
- description: 'Define o motor local: vault-ai-resume, claude-subscription, openai-subscription, openrouter ou gemini. Chaves de API não são aceitas por esta ferramenta.',
74
- inputSchema: { engine: z.enum(CONTEXT_ENGINE_IDS) },
73
+ description: 'Define o motor local e, opcionalmente, o modelo (no Claude: haiku, sonnet ou opus). Chaves de API não são aceitas por esta ferramenta.',
74
+ inputSchema: {
75
+ engine: z.enum(CONTEXT_ENGINE_IDS),
76
+ model: z.string().trim().min(1).max(200).optional(),
77
+ },
75
78
  annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false },
76
- }, async ({ engine }) => jsonResult({ selected: selectContextEngine(home, engine) }));
79
+ }, async ({ engine, model }) => {
80
+ selectContextEngine(home, engine);
81
+ const selectedModel = model
82
+ ? selectEngineModel(home, engine, parseEngineModel(engine, model))
83
+ : selectedEngineModel(home, engine);
84
+ return jsonResult({ selected: engine, ...(selectedModel ? { model: selectedModel } : {}) });
85
+ });
77
86
  server.registerTool('vault_go_generate_context', {
78
87
  title: 'Gerar contexto',
79
88
  description: 'Gera título, conteúdo, fatos e conceitos com o motor selecionado (Claude, OpenRouter, Gemini ou Vault AI). Não persiste memória automaticamente.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vault-go",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Servidor MCP universal com autenticação e instalação multi-cliente para a plataforma Vault.",
5
5
  "type": "module",
6
6
  "bin": {