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 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
- Para instalar e registrar no Codex, execute em um terminal:
53
+ Execute em um terminal:
54
54
 
55
55
  ```sh
56
56
  bunx --bun vault-go@latest
57
57
  ```
58
58
 
59
- Quando executado diretamente em um terminal, o pacote detecta o modo
60
- interativo e chama `codex mcp add` com os argumentos corretos. O mesmo fluxo
61
- pode ser solicitado explicitamente:
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 install
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
- O `bunx` sozinho baixa o pacote para o cache; o instalador da biblioteca é que
68
- registra o servidor na configuração do Codex. Reinicie o Codex depois da
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, o MCP reutiliza `~/.memvault/config.json` e
85
- `~/.memvault/auth.json`, criados pelo comando `memvault login`. Também é
86
- possível apontar outro diretório:
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 declare const CODEX_MCP_NAME = "vault-go";
2
- export declare const CODEX_MCP_COMMAND: readonly ["bunx", "--bun", "vault-go@latest"];
3
- export declare function shouldInstallFromCli(argument: string | undefined, stdinIsTTY: boolean, stderrIsTTY: boolean): boolean;
4
- export declare function installCodexMcp(): number;
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 { spawnSync } from 'node:child_process';
2
- export const CODEX_MCP_NAME = 'vault-go';
3
- export const CODEX_MCP_COMMAND = ['bunx', '--bun', 'vault-go@latest'];
4
- export function shouldInstallFromCli(argument, stdinIsTTY, stderrIsTTY) {
5
- if (argument === 'install' || argument === 'setup' || argument === '--install') {
6
- return true;
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
- return argument === undefined && stdinIsTTY && stderrIsTTY;
9
- }
10
- export function installCodexMcp() {
11
- const existing = spawnSync('codex', ['mcp', 'get', CODEX_MCP_NAME], {
12
- encoding: 'utf8',
13
- });
14
- if (existing.error && 'code' in existing.error && existing.error.code === 'ENOENT') {
15
- process.stderr.write('vault-go: o comando "codex" não foi encontrado. Instale o Codex CLI e tente novamente.\n');
16
- return 1;
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 (existing.status === 0) {
19
- process.stderr.write(`vault-go: o MCP "${CODEX_MCP_NAME}" já está registrado no Codex.\n`);
20
- return 0;
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
- process.stderr.write('vault-go: registrando o MCP no Codex...\n');
23
- const installed = spawnSync('codex', ['mcp', 'add', CODEX_MCP_NAME, '--', ...CODEX_MCP_COMMAND], { stdio: 'inherit' });
24
- if (installed.error) {
25
- process.stderr.write(`vault-go: não foi possível executar o instalador: ${installed.error.message}\n`);
26
- return 1;
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
- if (installed.status !== 0) {
29
- process.stderr.write('vault-go: o Codex não conseguiu registrar o MCP.\n');
30
- return installed.status ?? 1;
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
- process.stderr.write('vault-go: MCP instalado. Reinicie o Codex para carregá-lo.\n');
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 `memvault login` antes de iniciar o MCP.');
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 `memvault login` novamente.');
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 `memvault login` novamente.');
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
- const destination = authPath(home);
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 { installCodexMcp, shouldInstallFromCli } from './cli.js';
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
- if (argument === '--version' || argument === '-v') {
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
- if (shouldInstallFromCli(argument, process.stdin.isTTY === true, process.stderr.isTTY === true)) {
20
- process.exitCode = installCodexMcp();
21
- }
22
- else {
23
- main().catch((error) => {
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
+ }
@@ -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.8.1",
4
- "description": "Servidor MCP Bun para pesquisar e registrar memória na plataforma Vault.",
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",