vault-go 0.8.1 → 0.9.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 +33 -13
- package/dist/auth.d.ts +9 -0
- package/dist/auth.js +99 -0
- package/dist/cli.d.ts +5 -4
- package/dist/cli.js +128 -27
- package/dist/cloud.js +5 -4
- package/dist/config.d.ts +1 -0
- package/dist/config.js +17 -7
- package/dist/index.js +26 -19
- package/dist/installer.d.ts +27 -0
- package/dist/installer.js +188 -0
- package/dist/prompt.d.ts +3 -0
- package/dist/prompt.js +51 -0
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -50,23 +50,43 @@ bases, escrita, importação ou exclusão, use o cliente Bun completo abaixo.
|
|
|
50
50
|
|
|
51
51
|
### Opção Bun completa
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
Execute em um terminal:
|
|
54
54
|
|
|
55
55
|
```sh
|
|
56
56
|
bunx --bun vault-go@latest
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
O assistente:
|
|
60
|
+
|
|
61
|
+
1. valida uma sessão existente em `~/.memvault`;
|
|
62
|
+
2. se necessário, solicita login por e-mail, senha e MFA ou uma chave de API
|
|
63
|
+
`vg_live_...`;
|
|
64
|
+
3. detecta os clientes instalados;
|
|
65
|
+
4. permite escolher onde registrar o MCP;
|
|
66
|
+
5. preserva as outras configurações dos clientes.
|
|
67
|
+
|
|
68
|
+
Clientes suportados:
|
|
69
|
+
|
|
70
|
+
- Codex e ChatGPT Desktop local;
|
|
71
|
+
- Claude Code e Claude Desktop;
|
|
72
|
+
- Cursor e Windsurf;
|
|
73
|
+
- VS Code com GitHub Copilot;
|
|
74
|
+
- GitHub Copilot CLI;
|
|
75
|
+
- Roo Code;
|
|
76
|
+
- OpenCode.
|
|
77
|
+
|
|
78
|
+
Comandos explícitos:
|
|
62
79
|
|
|
63
80
|
```sh
|
|
64
|
-
bunx --bun vault-go@latest
|
|
81
|
+
bunx --bun vault-go@latest setup
|
|
82
|
+
bunx --bun vault-go@latest setup --clients codex,claude,copilot
|
|
83
|
+
bunx --bun vault-go@latest login --force
|
|
84
|
+
bunx --bun vault-go@latest serve
|
|
65
85
|
```
|
|
66
86
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
primeira instalação.
|
|
87
|
+
As credenciais são gravadas em `~/.memvault/auth.json` com permissão `0600`;
|
|
88
|
+
o diretório usa `0700`. Senhas e códigos MFA não são persistidos. Reinicie os
|
|
89
|
+
clientes que estavam abertos depois da primeira instalação.
|
|
70
90
|
|
|
71
91
|
Configuração MCP genérica:
|
|
72
92
|
|
|
@@ -75,22 +95,22 @@ Configuração MCP genérica:
|
|
|
75
95
|
"mcpServers": {
|
|
76
96
|
"vault-go": {
|
|
77
97
|
"command": "bunx",
|
|
78
|
-
"args": ["--bun", "vault-go@latest"]
|
|
98
|
+
"args": ["--bun", "vault-go@latest", "serve"]
|
|
79
99
|
}
|
|
80
100
|
}
|
|
81
101
|
}
|
|
82
102
|
```
|
|
83
103
|
|
|
84
|
-
Por padrão,
|
|
85
|
-
`~/.memvault/auth.json`, criados pelo
|
|
86
|
-
|
|
104
|
+
Por padrão, todos os clientes reutilizam `~/.memvault/config.json` e
|
|
105
|
+
`~/.memvault/auth.json`, criados pelo assistente. Também é possível apontar
|
|
106
|
+
outro diretório:
|
|
87
107
|
|
|
88
108
|
```json
|
|
89
109
|
{
|
|
90
110
|
"mcpServers": {
|
|
91
111
|
"vault-go": {
|
|
92
112
|
"command": "bunx",
|
|
93
|
-
"args": ["--bun", "vault-go@latest"],
|
|
113
|
+
"args": ["--bun", "vault-go@latest", "serve"],
|
|
94
114
|
"env": {
|
|
95
115
|
"VAULT_GO_HOME": "/caminho/privado/.memvault"
|
|
96
116
|
}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface AuthenticationResult {
|
|
2
|
+
email: string;
|
|
3
|
+
method: 'password' | 'api-key' | 'existing';
|
|
4
|
+
}
|
|
5
|
+
type Fetcher = typeof fetch;
|
|
6
|
+
export declare function authenticateWithPassword(email: string, password: string, requestMfa: () => Promise<string>, home: string, fetcher?: Fetcher): Promise<AuthenticationResult>;
|
|
7
|
+
export declare function authenticateWithApiKey(apiKey: string, home: string, fetcher?: Fetcher): Promise<AuthenticationResult>;
|
|
8
|
+
export declare function validateExistingAuthentication(home: string, fetcher?: Fetcher): Promise<AuthenticationResult | null>;
|
|
9
|
+
export {};
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { loadConfig, loadTokens, saveConfig, saveTokens, } from './config.js';
|
|
2
|
+
function endpoint(home, path) {
|
|
3
|
+
const apiUrl = new URL(loadConfig(home).apiUrl);
|
|
4
|
+
return new URL(`${apiUrl.pathname.replace(/\/$/u, '')}${path}`, apiUrl.origin);
|
|
5
|
+
}
|
|
6
|
+
async function errorMessage(response) {
|
|
7
|
+
const body = (await response.json().catch(() => ({})));
|
|
8
|
+
return body.error || body.code || `HTTP ${response.status}`;
|
|
9
|
+
}
|
|
10
|
+
async function accountForToken(token, home, fetcher) {
|
|
11
|
+
const response = await fetcher(endpoint(home, '/me'), {
|
|
12
|
+
headers: { authorization: `Bearer ${token}` },
|
|
13
|
+
signal: AbortSignal.timeout(10_000),
|
|
14
|
+
});
|
|
15
|
+
if (response.status === 401 || response.status === 403)
|
|
16
|
+
return null;
|
|
17
|
+
if (!response.ok)
|
|
18
|
+
throw new Error(`Falha ao validar a conta Vault: ${await errorMessage(response)}`);
|
|
19
|
+
const account = (await response.json());
|
|
20
|
+
if (!account.email)
|
|
21
|
+
throw new Error('A API Vault retornou uma conta sem e-mail.');
|
|
22
|
+
return account;
|
|
23
|
+
}
|
|
24
|
+
function persist(tokens, email, home) {
|
|
25
|
+
saveTokens(tokens, home);
|
|
26
|
+
saveConfig({ ...loadConfig(home), email }, home);
|
|
27
|
+
}
|
|
28
|
+
function normalizeTokens(value) {
|
|
29
|
+
if (!value || typeof value !== 'object') {
|
|
30
|
+
throw new Error('A API Vault retornou credenciais inválidas.');
|
|
31
|
+
}
|
|
32
|
+
const tokens = value;
|
|
33
|
+
if (typeof tokens.accessToken !== 'string' || tokens.accessToken.length === 0) {
|
|
34
|
+
throw new Error('A API Vault retornou credenciais sem access token.');
|
|
35
|
+
}
|
|
36
|
+
const normalized = { accessToken: tokens.accessToken };
|
|
37
|
+
if (typeof tokens.refreshToken === 'string')
|
|
38
|
+
normalized.refreshToken = tokens.refreshToken;
|
|
39
|
+
if (typeof tokens.expiresIn === 'number')
|
|
40
|
+
normalized.expiresIn = tokens.expiresIn;
|
|
41
|
+
normalized.expiresAt = Date.now() + (normalized.expiresIn ?? 900) * 1000;
|
|
42
|
+
return normalized;
|
|
43
|
+
}
|
|
44
|
+
export async function authenticateWithPassword(email, password, requestMfa, home, fetcher = fetch) {
|
|
45
|
+
const login = await fetcher(endpoint(home, '/auth/login'), {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: { 'content-type': 'application/json' },
|
|
48
|
+
body: JSON.stringify({ email, password }),
|
|
49
|
+
signal: AbortSignal.timeout(15_000),
|
|
50
|
+
});
|
|
51
|
+
if (!login.ok)
|
|
52
|
+
throw new Error(`Falha no login: ${await errorMessage(login)}`);
|
|
53
|
+
let body = (await login.json());
|
|
54
|
+
if ('twoFactorRequired' in body) {
|
|
55
|
+
const code = await requestMfa();
|
|
56
|
+
const verification = await fetcher(endpoint(home, '/auth/2fa/verify'), {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: { 'content-type': 'application/json' },
|
|
59
|
+
body: JSON.stringify({ ticket: body.ticket, code }),
|
|
60
|
+
signal: AbortSignal.timeout(15_000),
|
|
61
|
+
});
|
|
62
|
+
if (!verification.ok) {
|
|
63
|
+
throw new Error(`Falha na verificação MFA: ${await errorMessage(verification)}`);
|
|
64
|
+
}
|
|
65
|
+
body = (await verification.json());
|
|
66
|
+
}
|
|
67
|
+
const tokens = normalizeTokens(body);
|
|
68
|
+
persist(tokens, email, home);
|
|
69
|
+
return { email, method: 'password' };
|
|
70
|
+
}
|
|
71
|
+
export async function authenticateWithApiKey(apiKey, home, fetcher = fetch) {
|
|
72
|
+
const account = await accountForToken(apiKey, home, fetcher);
|
|
73
|
+
if (!account)
|
|
74
|
+
throw new Error('Chave de API Vault inválida ou sem permissão de leitura.');
|
|
75
|
+
persist({ accessToken: apiKey }, account.email, home);
|
|
76
|
+
return { email: account.email, method: 'api-key' };
|
|
77
|
+
}
|
|
78
|
+
export async function validateExistingAuthentication(home, fetcher = fetch) {
|
|
79
|
+
let tokens = loadTokens(home);
|
|
80
|
+
if (!tokens)
|
|
81
|
+
return null;
|
|
82
|
+
let account = await accountForToken(tokens.accessToken, home, fetcher);
|
|
83
|
+
if (!account && tokens.refreshToken) {
|
|
84
|
+
const refreshed = await fetcher(endpoint(home, '/auth/refresh'), {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: { 'content-type': 'application/json' },
|
|
87
|
+
body: JSON.stringify({ refreshToken: tokens.refreshToken }),
|
|
88
|
+
signal: AbortSignal.timeout(15_000),
|
|
89
|
+
});
|
|
90
|
+
if (refreshed.ok) {
|
|
91
|
+
tokens = normalizeTokens(await refreshed.json());
|
|
92
|
+
account = await accountForToken(tokens.accessToken, home, fetcher);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!account)
|
|
96
|
+
return null;
|
|
97
|
+
persist(tokens, account.email, home);
|
|
98
|
+
return { email: account.email, method: 'existing' };
|
|
99
|
+
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export
|
|
2
|
-
export declare
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function
|
|
1
|
+
export type CliMode = 'help' | 'version' | 'setup' | 'login' | 'serve' | 'invalid';
|
|
2
|
+
export declare function resolveCliMode(argument: string | undefined, stdinIsTTY: boolean, stderrIsTTY: boolean): CliMode;
|
|
3
|
+
export declare function runLogin(args: string[]): Promise<number>;
|
|
4
|
+
export declare function runSetup(args: string[]): Promise<number>;
|
|
5
|
+
export declare function helpText(version: string): string;
|
package/dist/cli.js
CHANGED
|
@@ -1,34 +1,135 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { authenticateWithApiKey, authenticateWithPassword, validateExistingAuthentication, } from './auth.js';
|
|
2
|
+
import { vaultHome } from './config.js';
|
|
3
|
+
import { detectMcpClients, installMcpClient, MCP_CLIENTS, parseClientSelection, } from './installer.js';
|
|
4
|
+
import { promptSecret, promptText } from './prompt.js';
|
|
5
|
+
export function resolveCliMode(argument, stdinIsTTY, stderrIsTTY) {
|
|
6
|
+
if (argument === '--help' || argument === '-h')
|
|
7
|
+
return 'help';
|
|
8
|
+
if (argument === '--version' || argument === '-v')
|
|
9
|
+
return 'version';
|
|
10
|
+
if (argument === 'setup' || argument === 'install' || argument === '--install')
|
|
11
|
+
return 'setup';
|
|
12
|
+
if (argument === 'login' || argument === 'auth')
|
|
13
|
+
return 'login';
|
|
14
|
+
if (argument === 'serve')
|
|
15
|
+
return 'serve';
|
|
16
|
+
if (argument === undefined)
|
|
17
|
+
return stdinIsTTY && stderrIsTTY ? 'setup' : 'serve';
|
|
18
|
+
return 'invalid';
|
|
19
|
+
}
|
|
20
|
+
function optionValue(args, name) {
|
|
21
|
+
const exact = args.indexOf(name);
|
|
22
|
+
if (exact >= 0)
|
|
23
|
+
return args[exact + 1];
|
|
24
|
+
const prefix = `${name}=`;
|
|
25
|
+
return args.find((argument) => argument.startsWith(prefix))?.slice(prefix.length);
|
|
26
|
+
}
|
|
27
|
+
async function ensureAuthentication(home, force = false) {
|
|
28
|
+
if (!force) {
|
|
29
|
+
const existing = await validateExistingAuthentication(home);
|
|
30
|
+
if (existing) {
|
|
31
|
+
process.stderr.write(`✓ Autenticado no Vault como ${existing.email}.\n`);
|
|
32
|
+
return existing;
|
|
33
|
+
}
|
|
7
34
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
process.stderr.write(
|
|
16
|
-
return
|
|
35
|
+
process.stderr.write('\nAutenticação Vault\n');
|
|
36
|
+
process.stderr.write(' 1. E-mail, senha e MFA\n');
|
|
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);
|
|
42
|
+
process.stderr.write(`✓ Chave validada para ${result.email}.\n`);
|
|
43
|
+
return result;
|
|
17
44
|
}
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
45
|
+
if (method !== '1')
|
|
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);
|
|
50
|
+
process.stderr.write(`✓ Login realizado como ${result.email}.\n`);
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
function defaultClients() {
|
|
54
|
+
const detected = detectMcpClients().filter((item) => item.detected);
|
|
55
|
+
if (detected.length > 0)
|
|
56
|
+
return detected.map((item) => item.client);
|
|
57
|
+
return ['codex', 'claude', 'cursor', 'vscode', 'copilot'];
|
|
58
|
+
}
|
|
59
|
+
async function chooseClients(args) {
|
|
60
|
+
const configured = optionValue(args, '--clients');
|
|
61
|
+
if (configured)
|
|
62
|
+
return parseClientSelection(configured);
|
|
63
|
+
const detected = detectMcpClients();
|
|
64
|
+
process.stderr.write('\nClientes MCP disponíveis:\n');
|
|
65
|
+
for (const item of detected) {
|
|
66
|
+
process.stderr.write(` - ${item.client}${item.detected ? ` (detectado: ${item.evidence})` : ''}\n`);
|
|
21
67
|
}
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
68
|
+
const defaults = defaultClients();
|
|
69
|
+
const answer = await promptText(`Clientes separados por vírgula [${defaults.join(',')}]: `);
|
|
70
|
+
return answer ? parseClientSelection(answer) : defaults;
|
|
71
|
+
}
|
|
72
|
+
export async function runLogin(args) {
|
|
73
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY) {
|
|
74
|
+
throw new Error('O login requer um terminal interativo.');
|
|
27
75
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
76
|
+
await ensureAuthentication(vaultHome(), args.includes('--force'));
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
export async function runSetup(args) {
|
|
80
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY) {
|
|
81
|
+
throw new Error('O setup requer um terminal interativo.');
|
|
82
|
+
}
|
|
83
|
+
process.stderr.write('vault-go — configuração MCP universal\n');
|
|
84
|
+
await ensureAuthentication(vaultHome(), args.includes('--reauth'));
|
|
85
|
+
const clients = await chooseClients(args);
|
|
86
|
+
if (clients.length === 0)
|
|
87
|
+
throw new Error('Nenhum cliente MCP selecionado.');
|
|
88
|
+
process.stderr.write('\nInstalação MCP\n');
|
|
89
|
+
let failures = 0;
|
|
90
|
+
for (const client of clients) {
|
|
91
|
+
try {
|
|
92
|
+
const result = installMcpClient(client);
|
|
93
|
+
const marker = result.status === 'already-installed' ? '=' : '✓';
|
|
94
|
+
process.stderr.write(`${marker} ${client}: ${result.destination}\n`);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
failures += 1;
|
|
98
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
99
|
+
process.stderr.write(`✗ ${client}: ${message}\n`);
|
|
100
|
+
}
|
|
31
101
|
}
|
|
32
|
-
|
|
102
|
+
if (failures > 0) {
|
|
103
|
+
process.stderr.write(`\n${failures} cliente(s) não puderam ser configurados.\n`);
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
process.stderr.write('\nSetup concluído. Reinicie os clientes abertos para carregar o Vault.\n');
|
|
33
107
|
return 0;
|
|
34
108
|
}
|
|
109
|
+
export function helpText(version) {
|
|
110
|
+
return `vault-go ${version}
|
|
111
|
+
|
|
112
|
+
Servidor MCP universal para a plataforma Vault.
|
|
113
|
+
|
|
114
|
+
Uso:
|
|
115
|
+
bunx --bun vault-go@latest
|
|
116
|
+
Autentica no Vault e instala nos clientes detectados.
|
|
117
|
+
|
|
118
|
+
bunx --bun vault-go@latest setup [--clients lista] [--reauth]
|
|
119
|
+
Executa o assistente de autenticação e instalação.
|
|
120
|
+
|
|
121
|
+
bunx --bun vault-go@latest login [--force]
|
|
122
|
+
Autentica sem alterar clientes MCP.
|
|
123
|
+
|
|
124
|
+
bunx --bun vault-go@latest serve
|
|
125
|
+
Inicia o servidor MCP por stdio.
|
|
126
|
+
|
|
127
|
+
Clientes:
|
|
128
|
+
${MCP_CLIENTS.join(', ')}
|
|
129
|
+
|
|
130
|
+
Variáveis:
|
|
131
|
+
VAULT_GO_HOME Diretório de configuração (padrão: ~/.memvault)
|
|
132
|
+
MEMVAULT_CONFIG_DIR Diretório compartilhado com o agente MemVault
|
|
133
|
+
VAULT_GO_API_URL Endpoint alternativo da API Vault
|
|
134
|
+
`;
|
|
135
|
+
}
|
package/dist/cloud.js
CHANGED
|
@@ -123,14 +123,15 @@ export class VaultCloudClient {
|
|
|
123
123
|
return environmentToken;
|
|
124
124
|
const tokens = loadTokens(this.home);
|
|
125
125
|
if (!tokens) {
|
|
126
|
-
throw new Error('Vault não autenticado. Execute `
|
|
126
|
+
throw new Error('Vault não autenticado. Execute `bunx --bun vault-go@latest login`.');
|
|
127
127
|
}
|
|
128
128
|
const valid = !forceRefresh &&
|
|
129
129
|
(!tokens.expiresAt || Date.now() < tokens.expiresAt - 60_000);
|
|
130
130
|
if (valid)
|
|
131
131
|
return tokens.accessToken;
|
|
132
|
-
if (!tokens.refreshToken)
|
|
133
|
-
throw new Error('Sessão expirada. Execute `
|
|
132
|
+
if (!tokens.refreshToken) {
|
|
133
|
+
throw new Error('Sessão expirada. Execute `bunx --bun vault-go@latest login --force`.');
|
|
134
|
+
}
|
|
134
135
|
const response = await this.cloudFetch(this.endpoint('/auth/refresh'), {
|
|
135
136
|
method: 'POST',
|
|
136
137
|
headers: { 'content-type': 'application/json' },
|
|
@@ -138,7 +139,7 @@ export class VaultCloudClient {
|
|
|
138
139
|
signal: AbortSignal.timeout(10_000),
|
|
139
140
|
});
|
|
140
141
|
if (!response.ok) {
|
|
141
|
-
throw new Error('Sessão expirada. Execute `
|
|
142
|
+
throw new Error('Sessão expirada. Execute `bunx --bun vault-go@latest login --force`.');
|
|
142
143
|
}
|
|
143
144
|
const refreshed = (await response.json());
|
|
144
145
|
const stored = {
|
package/dist/config.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export declare function configPath(home?: string): string;
|
|
|
23
23
|
export declare function authPath(home?: string): string;
|
|
24
24
|
export declare function loadConfig(home?: string): VaultConfig;
|
|
25
25
|
export declare function loadTokens(home?: string): StoredTokens | null;
|
|
26
|
+
export declare function saveConfig(config: VaultConfig, home?: string): void;
|
|
26
27
|
export declare function saveTokens(tokens: StoredTokens, home?: string): void;
|
|
27
28
|
export declare function getStatus(home?: string): VaultStatus;
|
|
28
29
|
export declare function validateApiUrl(value: string): URL;
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { chmodSync, existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
|
-
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
4
4
|
export const DEFAULT_API_URL = 'https://vault.resolveup.com.br/api';
|
|
5
5
|
const DEFAULT_CONFIG = {
|
|
6
6
|
apiUrl: DEFAULT_API_URL,
|
|
@@ -51,12 +51,22 @@ export function loadTokens(home = vaultHome()) {
|
|
|
51
51
|
return null;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
+
function ensureVaultHome(home) {
|
|
55
|
+
mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
56
|
+
chmodSync(home, 0o700);
|
|
57
|
+
}
|
|
58
|
+
function atomicWrite(path, value, home) {
|
|
59
|
+
ensureVaultHome(home);
|
|
60
|
+
const temporary = join(dirname(path), `.${basename(path)}.${process.pid}.${Date.now()}.tmp`);
|
|
61
|
+
writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
62
|
+
renameSync(temporary, path);
|
|
63
|
+
chmodSync(path, 0o600);
|
|
64
|
+
}
|
|
65
|
+
export function saveConfig(config, home = vaultHome()) {
|
|
66
|
+
atomicWrite(configPath(home), config, home);
|
|
67
|
+
}
|
|
54
68
|
export function saveTokens(tokens, home = vaultHome()) {
|
|
55
|
-
|
|
56
|
-
const temporary = join(dirname(destination), `.auth.${process.pid}.tmp`);
|
|
57
|
-
writeFileSync(temporary, `${JSON.stringify(tokens, null, 2)}\n`, { mode: 0o600 });
|
|
58
|
-
renameSync(temporary, destination);
|
|
59
|
-
chmodSync(destination, 0o600);
|
|
69
|
+
atomicWrite(authPath(home), tokens, home);
|
|
60
70
|
}
|
|
61
71
|
export function getStatus(home = vaultHome()) {
|
|
62
72
|
const config = loadConfig(home);
|
package/dist/index.js
CHANGED
|
@@ -1,28 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import {
|
|
3
|
+
import { helpText, resolveCliMode, runLogin, runSetup } from './cli.js';
|
|
4
4
|
import { createVaultGoServer, VERSION } from './server.js';
|
|
5
5
|
const argument = process.argv[2];
|
|
6
|
-
|
|
7
|
-
process.stdout.write(`${VERSION}\n`);
|
|
8
|
-
process.exit(0);
|
|
9
|
-
}
|
|
10
|
-
if (argument === '--help' || argument === '-h') {
|
|
11
|
-
process.stdout.write(`vault-go ${VERSION}\n\nServidor MCP cloud por stdio, executado com Bun.\n\nUso:\n bunx --bun vault-go@latest Instala no Codex quando executado em um terminal\n bunx --bun vault-go@latest install Instala no Codex explicitamente\n bunx --bun vault-go@latest serve Inicia o servidor MCP por stdio\n\nVariáveis:\n VAULT_GO_HOME Diretório de configuração (padrão: ~/.memvault)\n MEMVAULT_CONFIG_DIR Mesmo diretório usado pelo agente\n`);
|
|
12
|
-
process.exit(0);
|
|
13
|
-
}
|
|
6
|
+
const mode = resolveCliMode(argument, process.stdin.isTTY === true, process.stderr.isTTY === true);
|
|
14
7
|
async function main() {
|
|
8
|
+
if (mode === 'version') {
|
|
9
|
+
process.stdout.write(`${VERSION}\n`);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (mode === 'help') {
|
|
13
|
+
process.stdout.write(helpText(VERSION));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (mode === 'invalid') {
|
|
17
|
+
throw new Error(`Comando desconhecido: ${argument}\n\n${helpText(VERSION)}`);
|
|
18
|
+
}
|
|
19
|
+
if (mode === 'setup') {
|
|
20
|
+
process.exitCode = await runSetup(process.argv.slice(3));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (mode === 'login') {
|
|
24
|
+
process.exitCode = await runLogin(process.argv.slice(3));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
15
27
|
const server = createVaultGoServer();
|
|
16
28
|
const transport = new StdioServerTransport();
|
|
17
29
|
await server.connect(transport);
|
|
18
30
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
25
|
-
process.stderr.write(`vault-go falhou: ${message}\n`);
|
|
26
|
-
process.exitCode = 1;
|
|
27
|
-
});
|
|
28
|
-
}
|
|
31
|
+
main().catch((error) => {
|
|
32
|
+
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
33
|
+
process.stderr.write(`vault-go falhou: ${message}\n`);
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const MCP_SERVER_NAME = "vault-go";
|
|
2
|
+
export declare const MCP_SERVER_COMMAND: readonly ["bunx", "--bun", "vault-go@latest", "serve"];
|
|
3
|
+
export declare const MCP_CLIENTS: readonly ["codex", "claude", "claude-desktop", "cursor", "vscode", "copilot", "windsurf", "roo", "opencode"];
|
|
4
|
+
export type McpClient = (typeof MCP_CLIENTS)[number];
|
|
5
|
+
export interface DetectedClient {
|
|
6
|
+
client: McpClient;
|
|
7
|
+
detected: boolean;
|
|
8
|
+
evidence?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface InstallResult {
|
|
11
|
+
client: McpClient;
|
|
12
|
+
status: 'installed' | 'already-installed';
|
|
13
|
+
destination: string;
|
|
14
|
+
}
|
|
15
|
+
type CommandResult = {
|
|
16
|
+
status: number | null;
|
|
17
|
+
error?: Error;
|
|
18
|
+
};
|
|
19
|
+
type CommandRunner = (command: string, args: readonly string[]) => CommandResult;
|
|
20
|
+
export declare function installMcpClient(client: McpClient, options?: {
|
|
21
|
+
home?: string;
|
|
22
|
+
cwd?: string;
|
|
23
|
+
runner?: CommandRunner;
|
|
24
|
+
}): InstallResult;
|
|
25
|
+
export declare function detectMcpClients(home?: string): DetectedClient[];
|
|
26
|
+
export declare function parseClientSelection(value: string): McpClient[];
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { homedir, platform } from 'node:os';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
export const MCP_SERVER_NAME = 'vault-go';
|
|
6
|
+
export const MCP_SERVER_COMMAND = ['bunx', '--bun', 'vault-go@latest', 'serve'];
|
|
7
|
+
export const MCP_CLIENTS = [
|
|
8
|
+
'codex',
|
|
9
|
+
'claude',
|
|
10
|
+
'claude-desktop',
|
|
11
|
+
'cursor',
|
|
12
|
+
'vscode',
|
|
13
|
+
'copilot',
|
|
14
|
+
'windsurf',
|
|
15
|
+
'roo',
|
|
16
|
+
'opencode',
|
|
17
|
+
];
|
|
18
|
+
const runCommand = (command, args) => spawnSync(command, [...args], { stdio: 'ignore' });
|
|
19
|
+
function readJsonObject(path) {
|
|
20
|
+
if (!existsSync(path))
|
|
21
|
+
return {};
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
throw new Error(`JSON inválido em ${path}; o arquivo não foi alterado.`, { cause: error });
|
|
28
|
+
}
|
|
29
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
30
|
+
throw new Error(`Configuração inválida em ${path}; o arquivo não foi alterado.`);
|
|
31
|
+
}
|
|
32
|
+
return parsed;
|
|
33
|
+
}
|
|
34
|
+
function atomicWriteJson(path, value) {
|
|
35
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
36
|
+
const temporary = join(dirname(path), `.${process.pid}.${Date.now()}.tmp`);
|
|
37
|
+
writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
38
|
+
renameSync(temporary, path);
|
|
39
|
+
chmodSync(path, 0o600);
|
|
40
|
+
}
|
|
41
|
+
function standardServer() {
|
|
42
|
+
return {
|
|
43
|
+
command: MCP_SERVER_COMMAND[0],
|
|
44
|
+
args: MCP_SERVER_COMMAND.slice(1),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function mergeServer(path, rootKey = 'mcpServers', server = standardServer()) {
|
|
48
|
+
const config = readJsonObject(path);
|
|
49
|
+
const existing = config[rootKey];
|
|
50
|
+
if (existing !== undefined && (!existing || typeof existing !== 'object' || Array.isArray(existing))) {
|
|
51
|
+
throw new Error(`Campo ${rootKey} inválido em ${path}; o arquivo não foi alterado.`);
|
|
52
|
+
}
|
|
53
|
+
atomicWriteJson(path, {
|
|
54
|
+
...config,
|
|
55
|
+
[rootKey]: {
|
|
56
|
+
...existing,
|
|
57
|
+
[MCP_SERVER_NAME]: server,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function claudeDesktopPath(home) {
|
|
62
|
+
if (platform() === 'darwin') {
|
|
63
|
+
return join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
64
|
+
}
|
|
65
|
+
if (platform() === 'win32') {
|
|
66
|
+
return join(process.env['APPDATA'] ?? join(home, 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
|
|
67
|
+
}
|
|
68
|
+
return join(home, '.config', 'Claude', 'claude_desktop_config.json');
|
|
69
|
+
}
|
|
70
|
+
function installViaCli(client, command, getArgs, addArgs, runner) {
|
|
71
|
+
const existing = runner(command, getArgs);
|
|
72
|
+
if (existing.status === 0) {
|
|
73
|
+
return { client, status: 'already-installed', destination: `${command} MCP config` };
|
|
74
|
+
}
|
|
75
|
+
const installed = runner(command, addArgs);
|
|
76
|
+
if (installed.error || installed.status !== 0) {
|
|
77
|
+
throw new Error(`Não foi possível configurar ${client}. Confirme que o comando "${command}" está instalado.`, { cause: installed.error });
|
|
78
|
+
}
|
|
79
|
+
return { client, status: 'installed', destination: `${command} MCP config` };
|
|
80
|
+
}
|
|
81
|
+
export function installMcpClient(client, options = {}) {
|
|
82
|
+
const home = options.home ?? homedir();
|
|
83
|
+
const cwd = options.cwd ?? process.cwd();
|
|
84
|
+
const runner = options.runner ?? runCommand;
|
|
85
|
+
if (client === 'codex') {
|
|
86
|
+
return installViaCli(client, 'codex', ['mcp', 'get', MCP_SERVER_NAME], ['mcp', 'add', MCP_SERVER_NAME, '--', ...MCP_SERVER_COMMAND], runner);
|
|
87
|
+
}
|
|
88
|
+
if (client === 'claude') {
|
|
89
|
+
return installViaCli(client, 'claude', ['mcp', 'get', MCP_SERVER_NAME], [
|
|
90
|
+
'mcp',
|
|
91
|
+
'add',
|
|
92
|
+
'--scope',
|
|
93
|
+
'user',
|
|
94
|
+
'--transport',
|
|
95
|
+
'stdio',
|
|
96
|
+
MCP_SERVER_NAME,
|
|
97
|
+
'--',
|
|
98
|
+
...MCP_SERVER_COMMAND,
|
|
99
|
+
], runner);
|
|
100
|
+
}
|
|
101
|
+
if (client === 'vscode') {
|
|
102
|
+
const payload = JSON.stringify({
|
|
103
|
+
name: MCP_SERVER_NAME,
|
|
104
|
+
command: MCP_SERVER_COMMAND[0],
|
|
105
|
+
args: MCP_SERVER_COMMAND.slice(1),
|
|
106
|
+
});
|
|
107
|
+
const installed = runner('code', ['--add-mcp', payload]);
|
|
108
|
+
if (installed.error || installed.status !== 0) {
|
|
109
|
+
throw new Error('Não foi possível configurar VS Code. Confirme que o comando "code" está instalado.');
|
|
110
|
+
}
|
|
111
|
+
return { client, status: 'installed', destination: 'VS Code user profile' };
|
|
112
|
+
}
|
|
113
|
+
let path;
|
|
114
|
+
if (client === 'claude-desktop')
|
|
115
|
+
path = claudeDesktopPath(home);
|
|
116
|
+
else if (client === 'cursor')
|
|
117
|
+
path = join(home, '.cursor', 'mcp.json');
|
|
118
|
+
else if (client === 'copilot')
|
|
119
|
+
path = join(home, '.copilot', 'mcp-config.json');
|
|
120
|
+
else if (client === 'windsurf')
|
|
121
|
+
path = join(home, '.codeium', 'windsurf', 'mcp_config.json');
|
|
122
|
+
else if (client === 'roo')
|
|
123
|
+
path = resolve(cwd, '.roo', 'mcp.json');
|
|
124
|
+
else
|
|
125
|
+
path = join(home, '.config', 'opencode', 'opencode.json');
|
|
126
|
+
if (client === 'opencode') {
|
|
127
|
+
mergeServer(path, 'mcp', {
|
|
128
|
+
type: 'local',
|
|
129
|
+
command: [...MCP_SERVER_COMMAND],
|
|
130
|
+
enabled: true,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
else if (client === 'copilot') {
|
|
134
|
+
mergeServer(path, 'mcpServers', {
|
|
135
|
+
type: 'local',
|
|
136
|
+
...standardServer(),
|
|
137
|
+
env: {},
|
|
138
|
+
tools: ['*'],
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
mergeServer(path);
|
|
143
|
+
}
|
|
144
|
+
return { client, status: 'installed', destination: path };
|
|
145
|
+
}
|
|
146
|
+
export function detectMcpClients(home = homedir()) {
|
|
147
|
+
const definitions = [
|
|
148
|
+
['codex', ['codex'], [join(home, '.codex')]],
|
|
149
|
+
['claude', ['claude'], [join(home, '.claude')]],
|
|
150
|
+
['claude-desktop', [], [claudeDesktopPath(home), '/Applications/Claude.app']],
|
|
151
|
+
['cursor', ['cursor'], [join(home, '.cursor')]],
|
|
152
|
+
['vscode', ['code'], [join(home, '.vscode')]],
|
|
153
|
+
['copilot', ['copilot'], [join(home, '.copilot')]],
|
|
154
|
+
['windsurf', ['windsurf'], [join(home, '.codeium', 'windsurf')]],
|
|
155
|
+
['roo', [], []],
|
|
156
|
+
['opencode', ['opencode'], [join(home, '.config', 'opencode')]],
|
|
157
|
+
];
|
|
158
|
+
return definitions.map(([client, commands, paths]) => {
|
|
159
|
+
const command = commands.find((candidate) => Bun.which(candidate));
|
|
160
|
+
if (command)
|
|
161
|
+
return { client, detected: true, evidence: `comando ${command}` };
|
|
162
|
+
if (client === 'roo') {
|
|
163
|
+
const extensions = join(home, '.vscode', 'extensions');
|
|
164
|
+
try {
|
|
165
|
+
const detected = readdirSync(extensions).some((entry) => entry.toLowerCase().includes('roo-code'));
|
|
166
|
+
if (detected)
|
|
167
|
+
return { client, detected: true, evidence: extensions };
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// VS Code ou Roo não estão instalados.
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const path = paths.find((candidate) => existsSync(candidate));
|
|
174
|
+
return path
|
|
175
|
+
? { client, detected: true, evidence: path }
|
|
176
|
+
: { client, detected: false };
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
export function parseClientSelection(value) {
|
|
180
|
+
const normalized = value.trim().toLowerCase();
|
|
181
|
+
if (normalized === 'all' || normalized === 'todos')
|
|
182
|
+
return [...MCP_CLIENTS];
|
|
183
|
+
const selected = [...new Set(normalized.split(',').map((item) => item.trim()).filter(Boolean))];
|
|
184
|
+
const invalid = selected.filter((item) => !MCP_CLIENTS.includes(item));
|
|
185
|
+
if (invalid.length > 0)
|
|
186
|
+
throw new Error(`Clientes MCP desconhecidos: ${invalid.join(', ')}`);
|
|
187
|
+
return selected;
|
|
188
|
+
}
|
package/dist/prompt.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ReadStream, WriteStream } from 'node:tty';
|
|
2
|
+
export declare function promptText(question: string, input?: NodeJS.ReadableStream, output?: NodeJS.WritableStream): Promise<string>;
|
|
3
|
+
export declare function promptSecret(question: string, input?: ReadStream, output?: WriteStream): Promise<string>;
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
export async function promptText(question, input = process.stdin, output = process.stderr) {
|
|
3
|
+
const readline = createInterface({ input, output, terminal: true });
|
|
4
|
+
try {
|
|
5
|
+
return (await readline.question(question)).trim();
|
|
6
|
+
}
|
|
7
|
+
finally {
|
|
8
|
+
readline.close();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export async function promptSecret(question, input = process.stdin, output = process.stderr) {
|
|
12
|
+
if (!input.isTTY || typeof input.setRawMode !== 'function') {
|
|
13
|
+
throw new Error('A autenticação interativa requer um terminal TTY.');
|
|
14
|
+
}
|
|
15
|
+
output.write(question);
|
|
16
|
+
const wasRaw = input.isRaw;
|
|
17
|
+
const wasPaused = input.isPaused();
|
|
18
|
+
input.setRawMode(true);
|
|
19
|
+
input.resume();
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
let value = '';
|
|
22
|
+
const cleanup = () => {
|
|
23
|
+
input.off('data', onData);
|
|
24
|
+
input.setRawMode(wasRaw);
|
|
25
|
+
if (wasPaused)
|
|
26
|
+
input.pause();
|
|
27
|
+
output.write('\n');
|
|
28
|
+
};
|
|
29
|
+
const onData = (chunk) => {
|
|
30
|
+
for (const character of String(chunk)) {
|
|
31
|
+
if (character === '\u0003') {
|
|
32
|
+
cleanup();
|
|
33
|
+
reject(new Error('Operação cancelada.'));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (character === '\r' || character === '\n') {
|
|
37
|
+
cleanup();
|
|
38
|
+
resolve(value);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (character === '\u007f' || character === '\b') {
|
|
42
|
+
value = value.slice(0, -1);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (character >= ' ')
|
|
46
|
+
value += character;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
input.on('data', onData);
|
|
50
|
+
});
|
|
51
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vault-go",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Servidor MCP
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Servidor MCP universal com autenticação e instalação multi-cliente para a plataforma Vault.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"vault-go": "dist/index.js",
|
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
"dist/index.d.ts",
|
|
25
25
|
"dist/cli.js",
|
|
26
26
|
"dist/cli.d.ts",
|
|
27
|
+
"dist/auth.js",
|
|
28
|
+
"dist/auth.d.ts",
|
|
29
|
+
"dist/installer.js",
|
|
30
|
+
"dist/installer.d.ts",
|
|
31
|
+
"dist/prompt.js",
|
|
32
|
+
"dist/prompt.d.ts",
|
|
27
33
|
"dist/server.js",
|
|
28
34
|
"dist/server.d.ts",
|
|
29
35
|
"dist/config.js",
|
|
@@ -64,7 +70,11 @@
|
|
|
64
70
|
"memory",
|
|
65
71
|
"vault",
|
|
66
72
|
"memvault",
|
|
67
|
-
"knowledge"
|
|
73
|
+
"knowledge",
|
|
74
|
+
"codex",
|
|
75
|
+
"claude",
|
|
76
|
+
"copilot",
|
|
77
|
+
"cursor"
|
|
68
78
|
],
|
|
69
79
|
"author": "Gutierrez Henrique",
|
|
70
80
|
"license": "MIT",
|