vault-go 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/auth.d.ts +8 -1
- package/dist/auth.js +102 -0
- package/dist/cli.js +9 -18
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## Login no navegador
|
|
2
|
+
|
|
3
|
+
`bunx --bun vault-go@latest` abre o autenticador do AI Vault Memory no navegador. E-mail, senha e MFA são informados apenas no portal. Ao concluir, o terminal recebe um código de uso único por `127.0.0.1` e o troca com PKCE; tokens não aparecem na URL nem no console. O listener fecha ao concluir ou após cinco minutos.
|
|
4
|
+
|
|
5
|
+
Para trocar a conta: `bunx --bun vault-go@latest login --force`. Para chave de API explícita: `bunx --bun vault-go@latest login --force --api-key`. Se a abertura automática falhar, use o endereço exibido no mesmo computador. Requer uma sessão local com navegador para o retorno loopback.
|
|
6
|
+
|
|
1
7
|
# vault-go
|
|
2
8
|
|
|
3
9
|
Servidor MCP em Bun para pesquisar, contextualizar e registrar memória na
|
|
@@ -59,7 +65,7 @@ bunx --bun vault-go@latest
|
|
|
59
65
|
O assistente:
|
|
60
66
|
|
|
61
67
|
1. valida uma sessão existente em `~/.memvault`;
|
|
62
|
-
2. se necessário,
|
|
68
|
+
2. se necessário, abre o login do AI Vault Memory no navegador (chave de API somente com `--api-key`)
|
|
63
69
|
`vg_live_...`;
|
|
64
70
|
3. detecta os clientes instalados;
|
|
65
71
|
4. permite escolher onde registrar o MCP;
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
export interface AuthenticationResult {
|
|
2
2
|
email: string;
|
|
3
|
-
method: 'password' | 'api-key' | 'existing';
|
|
3
|
+
method: 'password' | 'browser' | 'api-key' | 'existing';
|
|
4
4
|
}
|
|
5
5
|
type Fetcher = typeof fetch;
|
|
6
6
|
export declare function authenticateWithPassword(email: string, password: string, requestMfa: () => Promise<string>, home: string, fetcher?: Fetcher): Promise<AuthenticationResult>;
|
|
7
7
|
export declare function authenticateWithApiKey(apiKey: string, home: string, fetcher?: Fetcher): Promise<AuthenticationResult>;
|
|
8
8
|
export declare function validateExistingAuthentication(home: string, fetcher?: Fetcher): Promise<AuthenticationResult | null>;
|
|
9
|
+
export declare function openVaultBrowser(url: string): Promise<void>;
|
|
10
|
+
export declare function authenticateWithBrowser(home: string, options?: {
|
|
11
|
+
fetcher?: Fetcher;
|
|
12
|
+
openBrowser?: (url: string) => Promise<void>;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
report?: (message: string) => void;
|
|
15
|
+
}): Promise<AuthenticationResult>;
|
|
9
16
|
export {};
|
package/dist/auth.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { randomBytes, createHash, timingSafeEqual } from 'node:crypto';
|
|
3
|
+
import { execFile } from 'node:child_process';
|
|
1
4
|
import { loadConfig, loadTokens, saveConfig, saveTokens, } from './config.js';
|
|
2
5
|
function endpoint(home, path) {
|
|
3
6
|
const apiUrl = new URL(loadConfig(home).apiUrl);
|
|
@@ -10,6 +13,7 @@ async function errorMessage(response) {
|
|
|
10
13
|
async function accountForToken(token, home, fetcher) {
|
|
11
14
|
const response = await fetcher(endpoint(home, '/me'), {
|
|
12
15
|
headers: { authorization: `Bearer ${token}` },
|
|
16
|
+
redirect: 'error',
|
|
13
17
|
signal: AbortSignal.timeout(10_000),
|
|
14
18
|
});
|
|
15
19
|
if (response.status === 401 || response.status === 403)
|
|
@@ -97,3 +101,101 @@ export async function validateExistingAuthentication(home, fetcher = fetch) {
|
|
|
97
101
|
persist(tokens, account.email, home);
|
|
98
102
|
return { email: account.email, method: 'existing' };
|
|
99
103
|
}
|
|
104
|
+
export function openVaultBrowser(url) {
|
|
105
|
+
const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'rundll32' : 'xdg-open';
|
|
106
|
+
const args = process.platform === 'win32' ? ['url.dll,FileProtocolHandler', url] : [url];
|
|
107
|
+
return new Promise((resolve, reject) => { execFile(command, args, { timeout: 10_000 }, error => error ? reject(error) : resolve()); });
|
|
108
|
+
}
|
|
109
|
+
export async function authenticateWithBrowser(home, options = {}) {
|
|
110
|
+
const fetcher = options.fetcher ?? fetch;
|
|
111
|
+
const report = options.report ?? (message => process.stderr.write(message + '\n'));
|
|
112
|
+
const start = endpoint(home, '/auth/oauth/resolveup/start');
|
|
113
|
+
if (start.protocol !== 'https:' && !(start.protocol === 'http:' && ['127.0.0.1', 'localhost', '[::1]'].includes(start.hostname)))
|
|
114
|
+
throw new Error('A autenticação Vault requer HTTPS.');
|
|
115
|
+
const verifier = randomBytes(32).toString('base64url');
|
|
116
|
+
const state = randomBytes(32).toString('base64url');
|
|
117
|
+
let port = 0;
|
|
118
|
+
let claim;
|
|
119
|
+
let rejectClaim;
|
|
120
|
+
let accepted = false;
|
|
121
|
+
const received = new Promise((resolve, reject) => { claim = resolve; rejectClaim = reject; });
|
|
122
|
+
// A timeout can occur while the browser opener is still running.
|
|
123
|
+
void received.catch(() => { });
|
|
124
|
+
const server = createServer((req, res) => {
|
|
125
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
126
|
+
res.setHeader('Referrer-Policy', 'no-referrer');
|
|
127
|
+
res.setHeader('Content-Security-Policy', "default-src 'none'; frame-ancestors 'none'");
|
|
128
|
+
let url;
|
|
129
|
+
try {
|
|
130
|
+
url = new URL(req.url ?? '/', `http://127.0.0.1:${port}`);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
res.writeHead(400).end();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (req.method !== 'GET' || req.headers.host !== `127.0.0.1:${port}` || url.pathname !== '/callback') {
|
|
137
|
+
res.writeHead(404).end();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const incoming = url.searchParams.get('state') ?? '';
|
|
141
|
+
const validState = /^[A-Za-z0-9_-]{43}$/.test(incoming) && timingSafeEqual(Buffer.from(incoming), Buffer.from(state));
|
|
142
|
+
const code = url.searchParams.get('code');
|
|
143
|
+
if (!validState || url.searchParams.getAll('state').length !== 1 || accepted) {
|
|
144
|
+
res.writeHead(400).end('Retorno de autenticação inválido.');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (url.searchParams.has('error')) {
|
|
148
|
+
accepted = true;
|
|
149
|
+
res.writeHead(400).end('Login não concluído. Volte ao terminal.');
|
|
150
|
+
rejectClaim?.(new Error('Login cancelado ou recusado no Vault.'));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!code || !/^[A-Za-z0-9_-]{43}$/.test(code) || url.searchParams.getAll('code').length !== 1) {
|
|
154
|
+
res.writeHead(400).end('Código inválido.');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
accepted = true;
|
|
158
|
+
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>');
|
|
160
|
+
claim?.(code);
|
|
161
|
+
});
|
|
162
|
+
let timer;
|
|
163
|
+
try {
|
|
164
|
+
await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); });
|
|
165
|
+
const address = server.address();
|
|
166
|
+
if (!address || typeof address === 'string')
|
|
167
|
+
throw new Error('Não foi possível iniciar o retorno local.');
|
|
168
|
+
port = address.port;
|
|
169
|
+
start.searchParams.set('mobile_challenge', createHash('sha256').update(verifier).digest('base64url'));
|
|
170
|
+
start.searchParams.set('mobile_state', state);
|
|
171
|
+
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}`);
|
|
175
|
+
try {
|
|
176
|
+
await (options.openBrowser ?? openVaultBrowser)(start.href);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
report('Não foi possível abrir o navegador automaticamente. Use o endereço acima.');
|
|
180
|
+
}
|
|
181
|
+
const code = await received;
|
|
182
|
+
const response = await fetcher(endpoint(home, '/auth/mobile/exchange'), {
|
|
183
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
184
|
+
body: JSON.stringify({ code, verifier }), redirect: 'error', signal: AbortSignal.timeout(15_000),
|
|
185
|
+
});
|
|
186
|
+
if (!response.ok)
|
|
187
|
+
throw new Error('Não foi possível concluir o login do Vault. Tente novamente.');
|
|
188
|
+
const tokens = normalizeTokens(await response.json());
|
|
189
|
+
const account = await accountForToken(tokens.accessToken, home, fetcher);
|
|
190
|
+
if (!account)
|
|
191
|
+
throw new Error('O Vault não confirmou a conta autenticada.');
|
|
192
|
+
persist(tokens, account.email, home);
|
|
193
|
+
return { email: account.email, method: 'browser' };
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
if (timer)
|
|
197
|
+
clearTimeout(timer);
|
|
198
|
+
server.closeAllConnections();
|
|
199
|
+
await new Promise(resolve => server.close(() => resolve()));
|
|
200
|
+
}
|
|
201
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { authenticateWithApiKey,
|
|
1
|
+
import { authenticateWithApiKey, authenticateWithBrowser, validateExistingAuthentication, } from './auth.js';
|
|
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';
|
|
@@ -24,7 +24,7 @@ function optionValue(args, name) {
|
|
|
24
24
|
const prefix = `${name}=`;
|
|
25
25
|
return args.find((argument) => argument.startsWith(prefix))?.slice(prefix.length);
|
|
26
26
|
}
|
|
27
|
-
async function ensureAuthentication(home, force = false) {
|
|
27
|
+
async function ensureAuthentication(home, force = false, apiKeyMode = false) {
|
|
28
28
|
if (!force) {
|
|
29
29
|
const existing = await validateExistingAuthentication(home);
|
|
30
30
|
if (existing) {
|
|
@@ -32,21 +32,12 @@ async function ensureAuthentication(home, force = false) {
|
|
|
32
32
|
return existing;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
process.stderr.write(' 2. Chave de API (vg_live_...)\n');
|
|
38
|
-
const method = (await promptText('Escolha [1]: ')) || '1';
|
|
39
|
-
if (method === '2') {
|
|
40
|
-
const apiKey = await promptSecret('Chave de API: ');
|
|
41
|
-
const result = await authenticateWithApiKey(apiKey, home);
|
|
35
|
+
if (apiKeyMode) {
|
|
36
|
+
const result = await authenticateWithApiKey(await promptSecret('Chave de API: '), home);
|
|
42
37
|
process.stderr.write(`✓ Chave validada para ${result.email}.\n`);
|
|
43
38
|
return result;
|
|
44
39
|
}
|
|
45
|
-
|
|
46
|
-
throw new Error('Método de autenticação inválido.');
|
|
47
|
-
const email = await promptText('E-mail: ');
|
|
48
|
-
const password = await promptSecret('Senha: ');
|
|
49
|
-
const result = await authenticateWithPassword(email, password, () => promptSecret('Código MFA ou backup: '), home);
|
|
40
|
+
const result = await authenticateWithBrowser(home);
|
|
50
41
|
process.stderr.write(`✓ Login realizado como ${result.email}.\n`);
|
|
51
42
|
return result;
|
|
52
43
|
}
|
|
@@ -73,7 +64,7 @@ export async function runLogin(args) {
|
|
|
73
64
|
if (!process.stdin.isTTY || !process.stderr.isTTY) {
|
|
74
65
|
throw new Error('O login requer um terminal interativo.');
|
|
75
66
|
}
|
|
76
|
-
await ensureAuthentication(vaultHome(), args.includes('--force'));
|
|
67
|
+
await ensureAuthentication(vaultHome(), args.includes('--force'), args.includes('--api-key'));
|
|
77
68
|
return 0;
|
|
78
69
|
}
|
|
79
70
|
export async function runSetup(args) {
|
|
@@ -81,7 +72,7 @@ export async function runSetup(args) {
|
|
|
81
72
|
throw new Error('O setup requer um terminal interativo.');
|
|
82
73
|
}
|
|
83
74
|
process.stderr.write('vault-go — configuração MCP universal\n');
|
|
84
|
-
await ensureAuthentication(vaultHome(), args.includes('--reauth'));
|
|
75
|
+
await ensureAuthentication(vaultHome(), args.includes('--reauth'), args.includes('--api-key'));
|
|
85
76
|
const clients = await chooseClients(args);
|
|
86
77
|
if (clients.length === 0)
|
|
87
78
|
throw new Error('Nenhum cliente MCP selecionado.');
|
|
@@ -113,12 +104,12 @@ Servidor MCP universal para a plataforma Vault.
|
|
|
113
104
|
|
|
114
105
|
Uso:
|
|
115
106
|
bunx --bun vault-go@latest
|
|
116
|
-
|
|
107
|
+
Abre o login Vault no navegador e instala nos clientes detectados.
|
|
117
108
|
|
|
118
109
|
bunx --bun vault-go@latest setup [--clients lista] [--reauth]
|
|
119
110
|
Executa o assistente de autenticação e instalação.
|
|
120
111
|
|
|
121
|
-
bunx --bun vault-go@latest login [--force]
|
|
112
|
+
bunx --bun vault-go@latest login [--force] [--api-key]
|
|
122
113
|
Autentica sem alterar clientes MCP.
|
|
123
114
|
|
|
124
115
|
bunx --bun vault-go@latest serve
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vault-go",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
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": {
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
},
|
|
59
59
|
"repository": {
|
|
60
60
|
"type": "git",
|
|
61
|
-
"url": "git+https://github.com/
|
|
61
|
+
"url": "git+https://github.com/resolveup-cloud/vault-go.git"
|
|
62
62
|
},
|
|
63
63
|
"bugs": {
|
|
64
|
-
"url": "https://github.com/
|
|
64
|
+
"url": "https://github.com/resolveup-cloud/vault-go/issues"
|
|
65
65
|
},
|
|
66
66
|
"homepage": "https://vault.resolveup.com.br",
|
|
67
67
|
"keywords": [
|