vault-go 0.10.0 → 0.11.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
@@ -8,7 +8,7 @@ Para trocar a conta: `bunx --bun vault-go@latest login --force`. Para chave de A
8
8
 
9
9
  Servidor MCP em Bun para pesquisar, contextualizar e registrar memória na
10
10
  plataforma Vault. A persistência fica no PostgreSQL do serviço Rust executado
11
- no EasyPanel; este pacote não cria banco local.
11
+ no EasyPanel; o painel local mantém um cache privado das consultas recentes.
12
12
 
13
13
  ## Pré-requisito
14
14
 
@@ -69,7 +69,8 @@ O assistente:
69
69
  `vg_live_...`;
70
70
  3. detecta os clientes instalados;
71
71
  4. permite escolher onde registrar o MCP;
72
- 5. preserva as outras configurações dos clientes.
72
+ 5. preserva as outras configurações dos clientes;
73
+ 6. instala e abre o painel local na porta 38850 (início automático no macOS).
73
74
 
74
75
  Clientes suportados:
75
76
 
@@ -195,7 +196,7 @@ consultas aplicam esse identificador, impedindo leitura entre contas.
195
196
  - Renova o access token usando o refresh token local com permissão privada.
196
197
  - Limita tamanhos, listas e janelas nos schemas das ferramentas.
197
198
  - Reserva `stdout` exclusivamente para o protocolo MCP.
198
- - Não persiste memória nem cria banco no dispositivo.
199
+ - O painel guarda cache de projetos/memórias e metadados de chamadas MCP em arquivos privados locais.
199
200
 
200
201
  Instale o MCP apenas em clientes confiáveis: o cliente conectado pode pedir
201
202
  busca, gravação e exclusão de memórias dentro da conta autenticada.
@@ -221,3 +222,40 @@ semântica, publicação no npm, tag e GitHub Release:
221
222
 
222
223
  O segredo `NPM_TOKEN` existe apenas no GitHub Actions e nunca deve ser salvo no
223
224
  repositório ou em logs.
225
+
226
+
227
+ ## Painel local · localhost:38850
228
+
229
+ ```sh
230
+ bunx --bun vault-go@latest setup --lang pt
231
+ bunx --bun vault-go@latest local install
232
+ bunx --bun vault-go@latest local open
233
+ bunx --bun vault-go@latest local status
234
+ bunx --bun vault-go@latest local stop
235
+ bunx --bun vault-go@latest local uninstall
236
+ ```
237
+
238
+ O assistente e a página de retorno do login suportam `--lang pt|en|es`.
239
+ O painel detecta o idioma do navegador e oferece seletor PT/EN/ES, busca
240
+ local, atividade MCP recente, projetos e memórias consultados na nuvem.
241
+ Atualiza a interface a cada 5 segundos enquanto visível e consulta a nuvem
242
+ a cada 15 segundos. Sem conexão, conserva o último cache da mesma conta.
243
+
244
+ O serviço escuta exclusivamente em 127.0.0.1:38850. `local open` verifica
245
+ uma prova criptográfica do serviço e abre uma sessão privada no navegador;
246
+ o segredo local é removido do fragmento imediatamente e trocado por cookie
247
+ HttpOnly/SameSite=Strict. Não há CORS nem acesso direto da nuvem ao localhost.
248
+ O portal `/app/local` abre o painel da máquina atual.
249
+
250
+ No macOS, `local install` registra um LaunchAgent do usuário. Em Linux e
251
+ Windows, inicia o serviço nesta sessão; após reiniciar, use `local start`.
252
+ O runtime fica em `~/.memvault/local-runtime`, independente do cache bunx.
253
+ `local stop` para o processo; `local uninstall` remove o início automático e
254
+ preserva autenticação, cache e atividade. Execute `local install` novamente
255
+ após atualizar o pacote para atualizar o serviço persistente.
256
+
257
+ A atividade registra método, rota, duração e resultado das chamadas feitas
258
+ pelo MCP Vault, sem argumentos, consultas ou credenciais. Não captura toda a
259
+ atividade da máquina nem importa automaticamente o banco do claude-mem em
260
+ 37701. A sincronização deste painel é de leitura da nuvem; as ferramentas
261
+ MCP continuam responsáveis pelas gravações na API.
@@ -0,0 +1,9 @@
1
+ export interface Activity {
2
+ id: string;
3
+ time: string;
4
+ operation: string;
5
+ ok: boolean;
6
+ durationMs: number;
7
+ }
8
+ export declare function recordActivity(home: string, operation: string, ok: boolean, durationMs: number): void;
9
+ export declare function readActivity(home: string): Activity[];
@@ -0,0 +1,31 @@
1
+ import { appendFileSync, chmodSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ export function recordActivity(home, operation, ok, durationMs) {
5
+ try {
6
+ mkdirSync(home, { recursive: true, mode: 0o700 });
7
+ const file = join(home, 'local-activity.jsonl');
8
+ // Bounded journal contains metadata only: never arguments, content or credentials.
9
+ try {
10
+ if (statSync(file).size > 2 * 1024 * 1024)
11
+ writeFileSync(file, '', { mode: 0o600 });
12
+ }
13
+ catch { }
14
+ appendFileSync(file, JSON.stringify({ id: randomUUID(), time: new Date().toISOString(), operation: operation.slice(0, 180), ok, durationMs: Math.round(durationMs) }) + '\n', { mode: 0o600 });
15
+ chmodSync(file, 0o600);
16
+ }
17
+ catch { /* Observability must never prevent an MCP operation. */ }
18
+ }
19
+ export function readActivity(home) {
20
+ try {
21
+ return readFileSync(join(home, 'local-activity.jsonl'), 'utf8').split('\n').filter(Boolean).slice(-100).flatMap(line => { try {
22
+ return [JSON.parse(line)];
23
+ }
24
+ catch {
25
+ return [];
26
+ } }).reverse();
27
+ }
28
+ catch {
29
+ return [];
30
+ }
31
+ }
package/dist/auth.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type Locale } from './locale.js';
1
2
  export interface AuthenticationResult {
2
3
  email: string;
3
4
  method: 'password' | 'browser' | 'api-key' | 'existing';
@@ -8,6 +9,7 @@ export declare function authenticateWithApiKey(apiKey: string, home: string, fet
8
9
  export declare function validateExistingAuthentication(home: string, fetcher?: Fetcher): Promise<AuthenticationResult | null>;
9
10
  export declare function openVaultBrowser(url: string): Promise<void>;
10
11
  export declare function authenticateWithBrowser(home: string, options?: {
12
+ locale?: Locale;
11
13
  fetcher?: Fetcher;
12
14
  openBrowser?: (url: string) => Promise<void>;
13
15
  timeoutMs?: number;
package/dist/auth.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { callbackPage } from './callback-page.js';
2
+ import { copy, resolveLocale } from './locale.js';
1
3
  import { createServer } from 'node:http';
2
4
  import { randomBytes, createHash, timingSafeEqual } from 'node:crypto';
3
5
  import { execFile } from 'node:child_process';
@@ -107,6 +109,8 @@ export function openVaultBrowser(url) {
107
109
  return new Promise((resolve, reject) => { execFile(command, args, { timeout: 10_000 }, error => error ? reject(error) : resolve()); });
108
110
  }
109
111
  export async function authenticateWithBrowser(home, options = {}) {
112
+ const locale = options.locale ?? resolveLocale();
113
+ const t = copy[locale];
110
114
  const fetcher = options.fetcher ?? fetch;
111
115
  const report = options.report ?? (message => process.stderr.write(message + '\n'));
112
116
  const start = endpoint(home, '/auth/oauth/resolveup/start');
@@ -124,7 +128,7 @@ export async function authenticateWithBrowser(home, options = {}) {
124
128
  const server = createServer((req, res) => {
125
129
  res.setHeader('Cache-Control', 'no-store');
126
130
  res.setHeader('Referrer-Policy', 'no-referrer');
127
- res.setHeader('Content-Security-Policy', "default-src 'none'; frame-ancestors 'none'");
131
+ res.setHeader('Content-Security-Policy', "default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'");
128
132
  let url;
129
133
  try {
130
134
  url = new URL(req.url ?? '/', `http://127.0.0.1:${port}`);
@@ -147,7 +151,7 @@ export async function authenticateWithBrowser(home, options = {}) {
147
151
  if (url.searchParams.has('error')) {
148
152
  accepted = true;
149
153
  res.writeHead(400).end('Login não concluído. Volte ao terminal.');
150
- rejectClaim?.(new Error('Login cancelado ou recusado no Vault.'));
154
+ rejectClaim?.(new Error(t.denied));
151
155
  return;
152
156
  }
153
157
  if (!code || !/^[A-Za-z0-9_-]{43}$/.test(code) || url.searchParams.getAll('code').length !== 1) {
@@ -156,7 +160,7 @@ export async function authenticateWithBrowser(home, options = {}) {
156
160
  }
157
161
  accepted = true;
158
162
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
159
- res.end('<!doctype html><html lang="pt-BR"><title>AI Vault Memory</title><h1>AI Vault Memory</h1><p>Retorno recebido. Volte ao terminal para concluir.</p></html>');
163
+ res.end(callbackPage(locale));
160
164
  claim?.(code);
161
165
  });
162
166
  let timer;
@@ -169,14 +173,14 @@ export async function authenticateWithBrowser(home, options = {}) {
169
173
  start.searchParams.set('mobile_challenge', createHash('sha256').update(verifier).digest('base64url'));
170
174
  start.searchParams.set('mobile_state', state);
171
175
  start.searchParams.set('cli_port', String(port));
172
- timer = setTimeout(() => rejectClaim?.(new Error('Tempo de login esgotado. Execute vault-go login novamente.')), options.timeoutMs ?? 300_000);
173
- report('Abrindo o autenticador do AI Vault Memory no navegador…');
174
- report(`Se o navegador não abrir, acesse neste computador: ${start.href}`);
176
+ timer = setTimeout(() => rejectClaim?.(new Error(t.timeout)), options.timeoutMs ?? 300_000);
177
+ report(t.opening);
178
+ report(`${t.fallback} ${start.href}`);
175
179
  try {
176
180
  await (options.openBrowser ?? openVaultBrowser)(start.href);
177
181
  }
178
182
  catch {
179
- report('Não foi possível abrir o navegador automaticamente. Use o endereço acima.');
183
+ report(t.openFailed);
180
184
  }
181
185
  const code = await received;
182
186
  const response = await fetcher(endpoint(home, '/auth/mobile/exchange'), {
@@ -184,7 +188,7 @@ export async function authenticateWithBrowser(home, options = {}) {
184
188
  body: JSON.stringify({ code, verifier }), redirect: 'error', signal: AbortSignal.timeout(15_000),
185
189
  });
186
190
  if (!response.ok)
187
- throw new Error('Não foi possível concluir o login do Vault. Tente novamente.');
191
+ throw new Error(t.retry);
188
192
  const tokens = normalizeTokens(await response.json());
189
193
  const account = await accountForToken(tokens.accessToken, home, fetcher);
190
194
  if (!account)
@@ -0,0 +1,3 @@
1
+ import { type Locale } from './locale.js';
2
+ /** All interpolated content is from the static locale catalog, never the callback URL. */
3
+ export declare function callbackPage(locale: Locale): string;
@@ -0,0 +1,8 @@
1
+ import { copy } from './locale.js';
2
+ /** All interpolated content is from the static locale catalog, never the callback URL. */
3
+ export function callbackPage(locale) {
4
+ const t = copy[locale];
5
+ return `<!doctype html><html lang="${locale}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="dark"><title>AI Vault Memory · ${t.received}</title><style>
6
+ *{box-sizing:border-box}body{margin:0;min-height:100svh;display:grid;place-items:center;padding:24px;color:#f1f3fc;background:radial-gradient(ellipse at 20% 10%,#38305a60,transparent 50%),radial-gradient(ellipse at 90% 90%,#20394c40,transparent 55%),#080b12;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}.panel{width:min(100%,520px);padding:44px;border:1px solid #cdd8ff26;border-radius:32px;background:linear-gradient(135deg,#272c3edb,#131724ed);box-shadow:0 32px 100px #0008,inset 0 1px #ffffff10;animation:arrive .6s cubic-bezier(.2,.8,.2,1) both}.mark{display:block;margin:0 auto 28px;width:92px;height:92px;filter:drop-shadow(0 8px 25px #9990ff40)}.ribbon{stroke-dasharray:180;animation:draw 1.1s ease both}.ribbon:nth-child(2){animation-delay:.12s}.ribbon:nth-child(3){animation-delay:.24s}.brand{text-align:center;color:#b9b6ff;font-size:10px;letter-spacing:.22em;font-weight:650}h1{text-align:center;font-size:34px;letter-spacing:-.045em;line-height:1.1;margin:16px 0}.intro{text-align:center;color:#acb4cb;font-size:15px;line-height:1.7;margin:0 0 30px}.steps{list-style:none;padding:0;margin:0;border-top:1px solid #cdd8ff20}.steps li{display:flex;align-items:center;gap:14px;padding:15px 0;border-bottom:1px solid #cdd8ff15;font-size:13px;animation:arrive .5s both}.steps li:nth-child(2){animation-delay:.15s}.steps li:nth-child(3){animation-delay:.3s}.step{display:grid;place-items:center;flex-shrink:0;width:28px;height:28px;border:1px solid #b9b6ff40;background:#b9b6ff12;border-radius:9px;color:#c8c5ff;font-size:11px}.return{margin:26px 0 0;padding:16px 18px;background:#b9b6ff14;border:1px solid #b9b6ff30;border-radius:16px;color:#d4d1ff;font-size:14px;line-height:1.6}.privacy{margin:22px 0 0;color:#929db6;font-size:11px;line-height:1.7;text-align:center}@keyframes arrive{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}@keyframes draw{from{stroke-dashoffset:180;opacity:0}to{stroke-dashoffset:0;opacity:1}}@media(max-width:480px){.panel{padding:30px 24px}h1{font-size:30px}}@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important}}@media(forced-colors:active){.panel,.step,.return{border:1px solid CanvasText}svg{stroke:CanvasText}}
7
+ </style></head><body><main class="panel"><svg class="mark" viewBox="0 0 100 100" fill="none" aria-hidden="true"><defs><linearGradient id="glass" x1="10" y1="10" x2="85" y2="90" gradientUnits="userSpaceOnUse"><stop stop-color="#e2edff"/><stop offset=".48" stop-color="#8b88df"/><stop offset="1" stop-color="#c4e6ff"/></linearGradient></defs><g stroke="url(#glass)" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"><path class="ribbon" d="M12 20 50 84 88 20"/><path class="ribbon" d="M25 20 50 63 75 20"/><path class="ribbon" d="M38 20 50 41 62 20"/></g></svg><p class="brand">AI VAULT MEMORY</p><h1>${t.received}</h1><p class="intro">${t.body}</p><ol class="steps"><li><span class="step">01</span>${t.step1}</li><li><span class="step">02</span>${t.step2}</li><li><span class="step">03</span>${t.step3}</li></ol><p class="return">↗ ${t.back}</p><p class="privacy">${t.privacy}</p></main></body></html>`;
8
+ }
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,12 @@
1
- export type CliMode = 'help' | 'version' | 'setup' | 'login' | 'serve' | 'invalid';
1
+ import { type McpClient } from './installer.js';
2
+ export type CliMode = 'help' | 'version' | 'setup' | 'login' | 'serve' | 'local' | 'invalid';
2
3
  export declare function resolveCliMode(argument: string | undefined, stdinIsTTY: boolean, stderrIsTTY: boolean): CliMode;
4
+ export declare function parseNumberedClients(value: string): McpClient[];
5
+ export declare function cliArguments(args: string[]): {
6
+ argument: string | undefined;
7
+ options: string[];
8
+ };
9
+ export declare function runLocal(args: string[]): Promise<number>;
3
10
  export declare function runLogin(args: string[]): Promise<number>;
4
11
  export declare function runSetup(args: string[]): Promise<number>;
5
12
  export declare function helpText(version: string): string;
package/dist/cli.js CHANGED
@@ -2,6 +2,8 @@ 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 { copy, resolveLocale } from './locale.js';
6
+ import { installLocal, localStatus, localUrl, openLocal, startLocal, stopLocal, uninstallLocal } from './local-install.js';
5
7
  export function resolveCliMode(argument, stdinIsTTY, stderrIsTTY) {
6
8
  if (argument === '--help' || argument === '-h')
7
9
  return 'help';
@@ -13,6 +15,8 @@ export function resolveCliMode(argument, stdinIsTTY, stderrIsTTY) {
13
15
  return 'login';
14
16
  if (argument === 'serve')
15
17
  return 'serve';
18
+ if (argument === 'local')
19
+ return 'local';
16
20
  if (argument === undefined)
17
21
  return stdinIsTTY && stderrIsTTY ? 'setup' : 'serve';
18
22
  return 'invalid';
@@ -24,21 +28,22 @@ function optionValue(args, name) {
24
28
  const prefix = `${name}=`;
25
29
  return args.find((argument) => argument.startsWith(prefix))?.slice(prefix.length);
26
30
  }
27
- async function ensureAuthentication(home, force = false, apiKeyMode = false) {
31
+ async function ensureAuthentication(home, force = false, apiKeyMode = false, locale = 'pt') {
32
+ const t = copy[locale];
28
33
  if (!force) {
29
34
  const existing = await validateExistingAuthentication(home);
30
35
  if (existing) {
31
- process.stderr.write(`✓ Autenticado no Vault como ${existing.email}.\n`);
36
+ process.stderr.write(` ✓ ${t.connected}: ${existing.email}\n`);
32
37
  return existing;
33
38
  }
34
39
  }
35
40
  if (apiKeyMode) {
36
- const result = await authenticateWithApiKey(await promptSecret('Chave de API: '), home);
37
- process.stderr.write(`✓ Chave validada para ${result.email}.\n`);
41
+ const result = await authenticateWithApiKey(await promptSecret(`${t.key}: `), home);
42
+ process.stderr.write(` ✓ ${t.connected}: ${result.email}\n`);
38
43
  return result;
39
44
  }
40
- const result = await authenticateWithBrowser(home);
41
- process.stderr.write(`✓ Login realizado como ${result.email}.\n`);
45
+ const result = await authenticateWithBrowser(home, { locale });
46
+ process.stderr.write(` ✓ ${t.connected}: ${result.email}\n`);
42
47
  return result;
43
48
  }
44
49
  function defaultClients() {
@@ -47,55 +52,153 @@ function defaultClients() {
47
52
  return detected.map((item) => item.client);
48
53
  return ['codex', 'claude', 'cursor', 'vscode', 'copilot'];
49
54
  }
50
- async function chooseClients(args) {
55
+ export function parseNumberedClients(value) {
56
+ return parseClientSelection(value.split(',').map(item => {
57
+ const trimmed = item.trim();
58
+ if (!/^\d+$/.test(trimmed))
59
+ return trimmed;
60
+ const client = MCP_CLIENTS[Number(trimmed) - 1];
61
+ if (!client)
62
+ throw new Error(`Invalid assistant number: ${trimmed}`);
63
+ return client;
64
+ }).join(','));
65
+ }
66
+ async function chooseClients(args, locale) {
67
+ const t = copy[locale];
51
68
  const configured = optionValue(args, '--clients');
52
69
  if (configured)
53
- return parseClientSelection(configured);
70
+ return parseNumberedClients(configured);
54
71
  const detected = detectMcpClients();
55
- process.stderr.write('\nClientes MCP disponíveis:\n');
56
72
  for (const item of detected) {
57
- process.stderr.write(` - ${item.client}${item.detected ? ` (detectado: ${item.evidence})` : ''}\n`);
73
+ process.stderr.write(` ${MCP_CLIENTS.indexOf(item.client) + 1}. ${item.client.padEnd(12)} ${item.detected ? `● ${t.detected}` : `○ ${t.available}`}\n`);
58
74
  }
59
75
  const defaults = defaultClients();
60
- const answer = await promptText(`Clientes separados por vírgula [${defaults.join(',')}]: `);
61
- return answer ? parseClientSelection(answer) : defaults;
76
+ const answer = await promptText(`${t.choose} [${defaults.join(',')}]: `);
77
+ return answer ? parseNumberedClients(answer) : defaults;
78
+ }
79
+ export function cliArguments(args) {
80
+ const remaining = [...args];
81
+ for (let i = 0; i < remaining.length; i++) {
82
+ if (remaining[i] === '--lang') {
83
+ if (!remaining[i + 1] || !['pt', 'en', 'es'].includes(remaining[i + 1]))
84
+ throw new Error('--lang: pt, en, es');
85
+ remaining.splice(i, 2);
86
+ i--;
87
+ }
88
+ else if (remaining[i]?.startsWith('--lang=')) {
89
+ if (!['pt', 'en', 'es'].includes(remaining[i].slice(7)))
90
+ throw new Error('--lang: pt, en, es');
91
+ remaining.splice(i, 1);
92
+ i--;
93
+ }
94
+ }
95
+ return { argument: remaining[0], options: args.filter((_, index) => index !== args.indexOf(remaining[0])) };
96
+ }
97
+ async function chooseLocale(args) {
98
+ const configured = optionValue(args, '--lang');
99
+ if (configured)
100
+ return resolveLocale(configured);
101
+ const detected = resolveLocale();
102
+ const answer = await promptText(` 1. Português 2. English 3. Español [${detected}]: `);
103
+ if (!answer)
104
+ return detected;
105
+ const selected = { '1': 'pt', '2': 'en', '3': 'es' }[answer] ?? answer.toLowerCase();
106
+ if (!['pt', 'en', 'es'].includes(selected))
107
+ throw new Error('--lang: pt, en, es');
108
+ return resolveLocale(selected);
109
+ }
110
+ const localCopy = {
111
+ pt: { step: 'Instalar painel local', startup: 'Início automático ativado', manual: 'Início automático indisponível neste sistema. Após reiniciar, execute: vault-go local start', running: 'Painel local disponível', stopped: 'Serviço local parado', removed: 'Início automático removido; dados e credenciais preservados', retry: 'Tente novamente: vault-go local install; vault-go local open' },
112
+ en: { step: 'Install local dashboard', startup: 'Automatic startup enabled', manual: 'Automatic startup is unavailable on this system. After restarting, run: vault-go local start', running: 'Local dashboard available', stopped: 'Local service stopped', removed: 'Automatic startup removed; data and credentials preserved', retry: 'Retry: vault-go local install; vault-go local open' },
113
+ es: { step: 'Instalar panel local', startup: 'Inicio automático activado', manual: 'Inicio automático no disponible en este sistema. Después de reiniciar, ejecuta: vault-go local start', running: 'Panel local disponible', stopped: 'Servicio local detenido', removed: 'Inicio automático eliminado; datos y credenciales conservados', retry: 'Reintenta: vault-go local install; vault-go local open' },
114
+ };
115
+ export async function runLocal(args) {
116
+ const t = localCopy[resolveLocale(optionValue(args, '--lang'))];
117
+ const command = cliArguments(args).argument ?? 'open';
118
+ if (command === 'status') {
119
+ const status = await localStatus();
120
+ process.stdout.write(status ? `${t.running}: ${localUrl} (v${status.version})\n` : `${t.stopped}\n`);
121
+ return status ? 0 : 1;
122
+ }
123
+ if (command === 'stop') {
124
+ await stopLocal();
125
+ process.stderr.write(`${t.stopped}\n`);
126
+ return 0;
127
+ }
128
+ if (command === 'uninstall') {
129
+ await uninstallLocal();
130
+ process.stderr.write(`${t.removed}\n`);
131
+ return 0;
132
+ }
133
+ if (command === 'serve') {
134
+ const { startLocalService } = await import('./local-service.js');
135
+ await startLocalService({ home: vaultHome() });
136
+ process.stderr.write(`${t.running}: ${localUrl}\n`);
137
+ return 0;
138
+ }
139
+ if (command === 'install') {
140
+ const result = await installLocal();
141
+ process.stderr.write(`${result.startup ? t.startup : t.manual}\n`);
142
+ }
143
+ else if (command === 'start')
144
+ await startLocal();
145
+ else if (command === 'open')
146
+ await openLocal();
147
+ else
148
+ throw new Error('vault-go local [install|start|open|status|stop|uninstall|serve]');
149
+ process.stderr.write(`${t.running}: ${localUrl}\n`);
150
+ return 0;
62
151
  }
63
152
  export async function runLogin(args) {
153
+ const locale = resolveLocale(optionValue(args, '--lang'));
64
154
  if (!process.stdin.isTTY || !process.stderr.isTTY) {
65
- throw new Error('O login requer um terminal interativo.');
155
+ throw new Error(copy[locale].interactive);
66
156
  }
67
- await ensureAuthentication(vaultHome(), args.includes('--force'), args.includes('--api-key'));
157
+ await ensureAuthentication(vaultHome(), args.includes('--force'), args.includes('--api-key'), locale);
68
158
  return 0;
69
159
  }
70
160
  export async function runSetup(args) {
71
161
  if (!process.stdin.isTTY || !process.stderr.isTTY) {
72
162
  throw new Error('O setup requer um terminal interativo.');
73
163
  }
74
- process.stderr.write('vault-go — configuração MCP universal\n');
75
- await ensureAuthentication(vaultHome(), args.includes('--reauth'), args.includes('--api-key'));
76
- const clients = await chooseClients(args);
164
+ process.stderr.write('\n ◆ VAULT GO\n ────────────────────────────────────────\n');
165
+ const locale = await chooseLocale(args);
166
+ const t = copy[locale];
167
+ const local = localCopy[locale];
168
+ process.env['VAULT_GO_LANG'] = locale;
169
+ process.stderr.write(` ${t.subtitle}\n\n [1/4] ${t.auth}\n`);
170
+ await ensureAuthentication(vaultHome(), args.includes('--reauth'), args.includes('--api-key'), locale);
171
+ process.stderr.write(`\n [2/4] ${t.clients}\n`);
172
+ const clients = await chooseClients(args, locale);
77
173
  if (clients.length === 0)
78
- throw new Error('Nenhum cliente MCP selecionado.');
79
- process.stderr.write('\nInstalação MCP\n');
174
+ throw new Error(t.none);
175
+ process.stderr.write(`\n [3/4] ${t.install}\n`);
80
176
  let failures = 0;
81
177
  for (const client of clients) {
82
178
  try {
83
179
  const result = installMcpClient(client);
84
180
  const marker = result.status === 'already-installed' ? '=' : '✓';
85
- process.stderr.write(`${marker} ${client}: ${result.destination}\n`);
181
+ process.stderr.write(` ${marker} ${client}: ${result.status === 'already-installed' ? t.already : t.installed}\n ${result.destination}\n`);
86
182
  }
87
183
  catch (error) {
88
184
  failures += 1;
89
185
  const message = error instanceof Error ? error.message : String(error);
90
- process.stderr.write(`✗ ${client}: ${message}\n`);
186
+ process.stderr.write(` ✗ ${t.failed} ${client}: ${message}\n`);
91
187
  }
92
188
  }
93
- if (failures > 0) {
94
- process.stderr.write(`\n${failures} cliente(s) não puderam ser configurados.\n`);
95
- return 1;
189
+ process.stderr.write(`\n [4/4] ${local.step}\n`);
190
+ try {
191
+ const result = await installLocal();
192
+ process.stderr.write(` ✓ ${result.startup ? local.startup : local.manual}\n`);
193
+ await openLocal();
194
+ process.stderr.write(` ✓ ${local.running}: ${localUrl}\n`);
96
195
  }
97
- process.stderr.write('\nSetup concluído. Reinicie os clientes abertos para carregar o Vault.\n');
98
- return 0;
196
+ catch (error) {
197
+ failures++;
198
+ process.stderr.write(` ✗ ${error instanceof Error ? error.message : String(error)}\n ${local.retry}\n`);
199
+ }
200
+ process.stderr.write(`\n ${failures ? `${t.failed}: ${failures}` : `✓ ${t.done}`}\n ${t.restart}\n\n`);
201
+ return failures ? 1 : 0;
99
202
  }
100
203
  export function helpText(version) {
101
204
  return `vault-go ${version}
@@ -115,6 +218,12 @@ Uso:
115
218
  bunx --bun vault-go@latest serve
116
219
  Inicia o servidor MCP por stdio.
117
220
 
221
+ vault-go local [install|start|open|status|stop|uninstall|serve]
222
+ Painel local em http://localhost:38850; install ativa início automático no macOS.
223
+
224
+ --lang pt|en|es
225
+ Idioma do assistente (padrão: idioma do sistema).
226
+
118
227
  Clientes:
119
228
  ${MCP_CLIENTS.join(', ')}
120
229
 
package/dist/cloud.d.ts CHANGED
@@ -32,7 +32,8 @@ export interface VaultMemoryApi {
32
32
  export declare class VaultCloudClient implements VaultMemoryApi {
33
33
  private readonly home;
34
34
  private readonly cloudFetch;
35
- constructor(home?: string, cloudFetch?: typeof fetch);
35
+ private readonly journal;
36
+ constructor(home?: string, cloudFetch?: typeof fetch, journal?: boolean);
36
37
  health(): Promise<unknown>;
37
38
  projects(): Promise<unknown>;
38
39
  createProject(input: Record<string, unknown>): Promise<unknown>;
@@ -65,4 +66,5 @@ export declare class VaultCloudClient implements VaultMemoryApi {
65
66
  private endpoint;
66
67
  private accessToken;
67
68
  private request;
69
+ private performRequest;
68
70
  }
package/dist/cloud.js CHANGED
@@ -1,10 +1,13 @@
1
+ import { recordActivity } from './activity.js';
1
2
  import { loadConfig, loadTokens, saveTokens, validateApiUrl, vaultHome, } from './config.js';
2
3
  export class VaultCloudClient {
3
4
  home;
4
5
  cloudFetch;
5
- constructor(home = vaultHome(), cloudFetch = globalThis.fetch) {
6
+ journal;
7
+ constructor(home = vaultHome(), cloudFetch = globalThis.fetch, journal = true) {
6
8
  this.home = home;
7
9
  this.cloudFetch = cloudFetch;
10
+ this.journal = journal;
8
11
  }
9
12
  async health() {
10
13
  return this.request('/health', { authenticated: false });
@@ -134,6 +137,7 @@ export class VaultCloudClient {
134
137
  }
135
138
  const response = await this.cloudFetch(this.endpoint('/auth/refresh'), {
136
139
  method: 'POST',
140
+ redirect: 'error',
137
141
  headers: { 'content-type': 'application/json' },
138
142
  body: JSON.stringify({ refreshToken: tokens.refreshToken }),
139
143
  signal: AbortSignal.timeout(10_000),
@@ -150,6 +154,19 @@ export class VaultCloudClient {
150
154
  return stored.accessToken;
151
155
  }
152
156
  async request(path, options = {}) {
157
+ const start = Date.now();
158
+ let ok = false;
159
+ try {
160
+ const result = await this.performRequest(path, options);
161
+ ok = true;
162
+ return result;
163
+ }
164
+ finally {
165
+ if (this.journal)
166
+ recordActivity(this.home, (options.method ?? 'GET') + ' ' + path.split('?')[0], ok, Date.now() - start);
167
+ }
168
+ }
169
+ async performRequest(path, options = {}) {
153
170
  const headers = new Headers({ accept: 'application/json' });
154
171
  if (options.body)
155
172
  headers.set('content-type', 'application/json');
@@ -158,6 +175,7 @@ export class VaultCloudClient {
158
175
  }
159
176
  const request = {
160
177
  method: options.method ?? 'GET',
178
+ redirect: 'error',
161
179
  headers,
162
180
  signal: AbortSignal.timeout(15_000),
163
181
  };
@@ -171,7 +189,7 @@ export class VaultCloudClient {
171
189
  }
172
190
  const response = await this.cloudFetch(endpoint, request);
173
191
  if (response.status === 401 && options.authenticated !== false && !options.retried) {
174
- return this.request(path, { ...options, retried: true });
192
+ return this.performRequest(path, { ...options, retried: true });
175
193
  }
176
194
  const text = await response.text();
177
195
  const payload = text ? safeJson(text) : null;
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env bun
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { helpText, resolveCliMode, runLogin, runSetup } from './cli.js';
3
+ import { cliArguments, helpText, resolveCliMode, runLocal, runLogin, runSetup } from './cli.js';
4
4
  import { createVaultGoServer, VERSION } from './server.js';
5
- const argument = process.argv[2];
5
+ const { argument, options } = cliArguments(process.argv.slice(2));
6
6
  const mode = resolveCliMode(argument, process.stdin.isTTY === true, process.stderr.isTTY === true);
7
7
  async function main() {
8
8
  if (mode === 'version') {
@@ -17,11 +17,15 @@ async function main() {
17
17
  throw new Error(`Comando desconhecido: ${argument}\n\n${helpText(VERSION)}`);
18
18
  }
19
19
  if (mode === 'setup') {
20
- process.exitCode = await runSetup(process.argv.slice(3));
20
+ process.exitCode = await runSetup(options);
21
21
  return;
22
22
  }
23
23
  if (mode === 'login') {
24
- process.exitCode = await runLogin(process.argv.slice(3));
24
+ process.exitCode = await runLogin(options);
25
+ return;
26
+ }
27
+ if (mode === 'local') {
28
+ process.exitCode = await runLocal(options);
25
29
  return;
26
30
  }
27
31
  const server = createVaultGoServer();
@@ -0,0 +1,3 @@
1
+ /** Local monitor: no secrets or cloud content are interpolated into this document. */
2
+ export declare function dashboardHtml(): string;
3
+ export declare function dashboardScript(): string;