vault-go 0.10.1 → 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 +41 -3
- package/dist/activity.d.ts +9 -0
- package/dist/activity.js +31 -0
- package/dist/auth.d.ts +2 -0
- package/dist/auth.js +12 -8
- package/dist/callback-page.d.ts +3 -0
- package/dist/callback-page.js +8 -0
- package/dist/cli.d.ts +8 -1
- package/dist/cli.js +135 -26
- package/dist/cloud.d.ts +3 -1
- package/dist/cloud.js +20 -2
- package/dist/index.js +8 -4
- package/dist/local-dashboard.d.ts +3 -0
- package/dist/local-dashboard.js +91 -0
- package/dist/local-install.d.ts +17 -0
- package/dist/local-install.js +169 -0
- package/dist/local-service.d.ts +16 -0
- package/dist/local-service.js +217 -0
- package/dist/local-worker.d.ts +1 -0
- package/dist/local-worker.js +8 -0
- package/dist/locale.d.ts +97 -0
- package/dist/locale.js +9 -0
- package/package.json +23 -9
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;
|
|
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
|
-
-
|
|
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[];
|
package/dist/activity.js
ADDED
|
@@ -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(
|
|
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(
|
|
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(
|
|
173
|
-
report(
|
|
174
|
-
report(
|
|
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(
|
|
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(
|
|
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,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
|
-
|
|
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(
|
|
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(
|
|
37
|
-
process.stderr.write(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(`
|
|
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(
|
|
61
|
-
return answer ?
|
|
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(
|
|
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('
|
|
75
|
-
await
|
|
76
|
-
const
|
|
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(
|
|
79
|
-
process.stderr.write(
|
|
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(
|
|
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(
|
|
186
|
+
process.stderr.write(` ✗ ${t.failed} ${client}: ${message}\n`);
|
|
91
187
|
}
|
|
92
188
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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(
|
|
20
|
+
process.exitCode = await runSetup(options);
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
if (mode === 'login') {
|
|
24
|
-
process.exitCode = await runLogin(
|
|
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();
|