vault-go 0.1.0 → 0.3.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 +64 -62
- package/dist/cloud.d.ts +36 -0
- package/dist/cloud.js +121 -0
- package/dist/config.d.ts +10 -7
- package/dist/config.js +40 -36
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/server.d.ts +3 -2
- package/dist/server.js +139 -99
- package/package.json +10 -8
- package/dist/files.d.ts +0 -25
- package/dist/files.js +0 -161
package/README.md
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
# vault-go
|
|
2
2
|
|
|
3
|
-
Servidor MCP
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Servidor MCP em Bun para pesquisar, contextualizar e registrar memória na
|
|
4
|
+
plataforma Vault. A persistência fica no PostgreSQL do serviço Rust executado
|
|
5
|
+
no EasyPanel; este pacote não cria banco local.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Pré-requisito
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Instale o [Bun](https://bun.sh/) e confirme:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
|
|
12
|
+
bun --version
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Instalação
|
|
15
|
+
## Instalação MCP
|
|
16
|
+
|
|
17
|
+
Execução direta:
|
|
16
18
|
|
|
17
19
|
```sh
|
|
18
|
-
|
|
19
|
-
vault-go --version
|
|
20
|
+
bunx --bun vault-go@latest
|
|
20
21
|
```
|
|
21
22
|
|
|
22
23
|
Configuração MCP genérica:
|
|
@@ -25,21 +26,23 @@ Configuração MCP genérica:
|
|
|
25
26
|
{
|
|
26
27
|
"mcpServers": {
|
|
27
28
|
"vault-go": {
|
|
28
|
-
"command": "
|
|
29
|
-
"args": ["
|
|
29
|
+
"command": "bunx",
|
|
30
|
+
"args": ["--bun", "vault-go@latest"]
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
Por padrão, o MCP reutiliza `~/.memvault/config.json` e
|
|
37
|
+
`~/.memvault/auth.json`, criados pelo comando `memvault login`. Também é
|
|
38
|
+
possível apontar outro diretório:
|
|
36
39
|
|
|
37
40
|
```json
|
|
38
41
|
{
|
|
39
42
|
"mcpServers": {
|
|
40
43
|
"vault-go": {
|
|
41
|
-
"command": "
|
|
42
|
-
"args": ["
|
|
44
|
+
"command": "bunx",
|
|
45
|
+
"args": ["--bun", "vault-go@latest"],
|
|
43
46
|
"env": {
|
|
44
47
|
"VAULT_GO_HOME": "/caminho/privado/.memvault"
|
|
45
48
|
}
|
|
@@ -48,70 +51,69 @@ Para usar outro diretório local:
|
|
|
48
51
|
}
|
|
49
52
|
```
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- `vault_go_list_files`: lista arquivos textuais monitorados.
|
|
55
|
-
- `vault_go_search`: busca texto e retorna linha/trecho.
|
|
56
|
-
- `vault_go_read_file`: lê até 1 MiB dentro dos caminhos permitidos.
|
|
57
|
-
- `vault_go_write_note`: cria ou acrescenta Markdown em `mcp-notes`.
|
|
58
|
-
- `vault_go_cloud_health`: testa a API pública sem enviar tokens ou conteúdo.
|
|
59
|
-
|
|
60
|
-
O recurso `vault-go://status` fornece o mesmo resumo sanitizado em formato JSON.
|
|
61
|
-
|
|
62
|
-
## Integração com a nuvem
|
|
54
|
+
Para ambientes efêmeros, `VAULT_GO_TOKEN` aceita um access token e
|
|
55
|
+
`VAULT_GO_API_URL` troca o endpoint. Prefira o login normal, porque ele permite
|
|
56
|
+
renovação automática da sessão.
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
cofre. Ao criar a primeira nota, ele adiciona `~/.memvault/mcp-notes` aos
|
|
66
|
-
caminhos monitorados. O agente local existente:
|
|
67
|
-
|
|
68
|
-
1. detecta a nota;
|
|
69
|
-
2. cifra o conteúdo no dispositivo;
|
|
70
|
-
3. envia somente o blob cifrado para `vault.resolveup.com.br`;
|
|
71
|
-
4. sincroniza o arquivo com os outros dispositivos autorizados.
|
|
72
|
-
|
|
73
|
-
Mantenha o agente em execução para sincronização contínua:
|
|
58
|
+
## Ferramentas MCP
|
|
74
59
|
|
|
75
|
-
|
|
76
|
-
|
|
60
|
+
- `vault_go_status`: conexão e autenticação sem expor credenciais.
|
|
61
|
+
- `vault_go_cloud_health`: disponibilidade da plataforma.
|
|
62
|
+
- `vault_go_projects` e `vault_go_project_create`: projetos da conta.
|
|
63
|
+
- `vault_go_session_start` e `vault_go_session_end`: ciclo de sessão.
|
|
64
|
+
- `vault_go_event`: eventos de prompt, ferramenta e resultado.
|
|
65
|
+
- `vault_go_remember`: observações, decisões, descobertas e resumos.
|
|
66
|
+
- `vault_go_search`: busca textual com ranking e trechos.
|
|
67
|
+
- `vault_go_timeline`: contexto anterior e posterior a uma memória.
|
|
68
|
+
- `vault_go_context`: contexto progressivo com limite de caracteres.
|
|
69
|
+
- `vault_go_file_context`: histórico ligado aos arquivos lidos ou modificados.
|
|
70
|
+
- `vault_go_stats`: contagens isoladas da conta.
|
|
71
|
+
- `vault_go_forget`: exclusão explícita de uma memória.
|
|
72
|
+
|
|
73
|
+
O recurso `vault-go://status` fornece o resumo sanitizado em JSON.
|
|
74
|
+
|
|
75
|
+
## Fluxo integrado
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
Cliente MCP -> vault-go (Bun) -> API autenticada (Bun)
|
|
79
|
+
-> engine interno (Rust)
|
|
80
|
+
-> PostgreSQL no EasyPanel
|
|
77
81
|
```
|
|
78
82
|
|
|
83
|
+
O engine não possui rota pública. A API valida o JWT do usuário, substitui-o
|
|
84
|
+
por uma credencial interna e informa ao engine a conta autenticada. Todas as
|
|
85
|
+
consultas aplicam esse identificador, impedindo leitura entre contas.
|
|
86
|
+
|
|
79
87
|
## Segurança
|
|
80
88
|
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
- Usa HTTPS para health remoto; HTTP só é aceito em localhost.
|
|
88
|
-
- Não escreve logs no `stdout`, que é reservado ao protocolo MCP.
|
|
89
|
+
- Não retorna access token nem refresh token em ferramentas ou recursos.
|
|
90
|
+
- Aceita HTTPS; HTTP somente em localhost.
|
|
91
|
+
- Renova o access token usando o refresh token local com permissão privada.
|
|
92
|
+
- Limita tamanhos, listas e janelas nos schemas das ferramentas.
|
|
93
|
+
- Reserva `stdout` exclusivamente para o protocolo MCP.
|
|
94
|
+
- Não persiste memória nem cria banco no dispositivo.
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
de autorizar chamadas de leitura ou escrita.
|
|
96
|
+
Instale o MCP apenas em clientes confiáveis: o cliente conectado pode pedir
|
|
97
|
+
busca, gravação e exclusão de memórias dentro da conta autenticada.
|
|
93
98
|
|
|
94
99
|
## Desenvolvimento
|
|
95
100
|
|
|
96
101
|
```sh
|
|
97
102
|
npm install
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
bun run typecheck
|
|
104
|
+
bun test src
|
|
105
|
+
bun run build
|
|
100
106
|
npm run pack:check
|
|
101
107
|
```
|
|
102
108
|
|
|
103
|
-
Requer Node.js `>= 20`.
|
|
104
|
-
|
|
105
109
|
## Publicação automática
|
|
106
110
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
atualiza a versão, publica no npm e cria a release e a tag no GitHub:
|
|
111
|
+
Pushes em `main` passam por CI. Commits convencionais acionam release
|
|
112
|
+
semântica, publicação no npm, tag e GitHub Release:
|
|
110
113
|
|
|
111
|
-
- `fix:`
|
|
112
|
-
- `feat:`
|
|
113
|
-
- `feat!:` ou `BREAKING CHANGE:`
|
|
114
|
-
- `docs:`, `test:`, `chore:` e `ci:` não publicam uma nova versão.
|
|
114
|
+
- `fix:` gera patch;
|
|
115
|
+
- `feat:` gera minor;
|
|
116
|
+
- `feat!:` ou `BREAKING CHANGE:` gera major.
|
|
115
117
|
|
|
116
|
-
O
|
|
117
|
-
|
|
118
|
+
O segredo `NPM_TOKEN` existe apenas no GitHub Actions e nunca deve ser salvo no
|
|
119
|
+
repositório ou em logs.
|
package/dist/cloud.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface VaultMemoryApi {
|
|
2
|
+
health(): Promise<unknown>;
|
|
3
|
+
projects(): Promise<unknown>;
|
|
4
|
+
createProject(input: Record<string, unknown>): Promise<unknown>;
|
|
5
|
+
startSession(input: Record<string, unknown>): Promise<unknown>;
|
|
6
|
+
endSession(id: string): Promise<unknown>;
|
|
7
|
+
event(input: Record<string, unknown>): Promise<unknown>;
|
|
8
|
+
remember(input: Record<string, unknown>): Promise<unknown>;
|
|
9
|
+
forget(id: string): Promise<unknown>;
|
|
10
|
+
search(input: Record<string, unknown>): Promise<unknown>;
|
|
11
|
+
timeline(input: Record<string, unknown>): Promise<unknown>;
|
|
12
|
+
context(input: Record<string, unknown>): Promise<unknown>;
|
|
13
|
+
fileContext(input: Record<string, unknown>): Promise<unknown>;
|
|
14
|
+
stats(): Promise<unknown>;
|
|
15
|
+
}
|
|
16
|
+
export declare class VaultCloudClient implements VaultMemoryApi {
|
|
17
|
+
private readonly home;
|
|
18
|
+
private readonly cloudFetch;
|
|
19
|
+
constructor(home?: string, cloudFetch?: typeof fetch);
|
|
20
|
+
health(): Promise<unknown>;
|
|
21
|
+
projects(): Promise<unknown>;
|
|
22
|
+
createProject(input: Record<string, unknown>): Promise<unknown>;
|
|
23
|
+
startSession(input: Record<string, unknown>): Promise<unknown>;
|
|
24
|
+
endSession(id: string): Promise<unknown>;
|
|
25
|
+
event(input: Record<string, unknown>): Promise<unknown>;
|
|
26
|
+
remember(input: Record<string, unknown>): Promise<unknown>;
|
|
27
|
+
forget(id: string): Promise<unknown>;
|
|
28
|
+
search(input: Record<string, unknown>): Promise<unknown>;
|
|
29
|
+
timeline(input: Record<string, unknown>): Promise<unknown>;
|
|
30
|
+
context(input: Record<string, unknown>): Promise<unknown>;
|
|
31
|
+
fileContext(input: Record<string, unknown>): Promise<unknown>;
|
|
32
|
+
stats(): Promise<unknown>;
|
|
33
|
+
private endpoint;
|
|
34
|
+
private accessToken;
|
|
35
|
+
private request;
|
|
36
|
+
}
|
package/dist/cloud.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { loadConfig, loadTokens, saveTokens, validateApiUrl, vaultHome, } from './config.js';
|
|
2
|
+
export class VaultCloudClient {
|
|
3
|
+
home;
|
|
4
|
+
cloudFetch;
|
|
5
|
+
constructor(home = vaultHome(), cloudFetch = globalThis.fetch) {
|
|
6
|
+
this.home = home;
|
|
7
|
+
this.cloudFetch = cloudFetch;
|
|
8
|
+
}
|
|
9
|
+
async health() {
|
|
10
|
+
return this.request('/health', { authenticated: false });
|
|
11
|
+
}
|
|
12
|
+
async projects() {
|
|
13
|
+
return this.request('/memory/projects');
|
|
14
|
+
}
|
|
15
|
+
async createProject(input) {
|
|
16
|
+
return this.request('/memory/projects', { method: 'POST', body: input });
|
|
17
|
+
}
|
|
18
|
+
async startSession(input) {
|
|
19
|
+
return this.request('/memory/sessions/start', { method: 'POST', body: input });
|
|
20
|
+
}
|
|
21
|
+
async endSession(id) {
|
|
22
|
+
return this.request(`/memory/sessions/${encodeURIComponent(id)}/end`, { method: 'POST' });
|
|
23
|
+
}
|
|
24
|
+
async event(input) {
|
|
25
|
+
return this.request('/memory/events', { method: 'POST', body: input });
|
|
26
|
+
}
|
|
27
|
+
async remember(input) {
|
|
28
|
+
return this.request('/memory/memories', { method: 'POST', body: input });
|
|
29
|
+
}
|
|
30
|
+
async forget(id) {
|
|
31
|
+
await this.request(`/memory/memories/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
32
|
+
return { deleted: true, id };
|
|
33
|
+
}
|
|
34
|
+
async search(input) {
|
|
35
|
+
return this.request('/memory/search', { method: 'POST', body: input });
|
|
36
|
+
}
|
|
37
|
+
async timeline(input) {
|
|
38
|
+
return this.request('/memory/timeline', { method: 'POST', body: input });
|
|
39
|
+
}
|
|
40
|
+
async context(input) {
|
|
41
|
+
return this.request('/memory/context', { method: 'POST', body: input });
|
|
42
|
+
}
|
|
43
|
+
async fileContext(input) {
|
|
44
|
+
return this.request('/memory/files/context', { method: 'POST', body: input });
|
|
45
|
+
}
|
|
46
|
+
async stats() {
|
|
47
|
+
return this.request('/memory/stats');
|
|
48
|
+
}
|
|
49
|
+
endpoint(path) {
|
|
50
|
+
const base = validateApiUrl(loadConfig(this.home).apiUrl);
|
|
51
|
+
const prefix = base.pathname.replace(/\/$/u, '');
|
|
52
|
+
return new URL(`${prefix}${path}`, base.origin);
|
|
53
|
+
}
|
|
54
|
+
async accessToken(forceRefresh = false) {
|
|
55
|
+
const environmentToken = process.env['VAULT_GO_TOKEN'];
|
|
56
|
+
if (environmentToken)
|
|
57
|
+
return environmentToken;
|
|
58
|
+
const tokens = loadTokens(this.home);
|
|
59
|
+
if (!tokens) {
|
|
60
|
+
throw new Error('Vault não autenticado. Execute `memvault login` antes de iniciar o MCP.');
|
|
61
|
+
}
|
|
62
|
+
const valid = !forceRefresh &&
|
|
63
|
+
(!tokens.expiresAt || Date.now() < tokens.expiresAt - 60_000);
|
|
64
|
+
if (valid)
|
|
65
|
+
return tokens.accessToken;
|
|
66
|
+
if (!tokens.refreshToken)
|
|
67
|
+
throw new Error('Sessão expirada. Execute `memvault login` novamente.');
|
|
68
|
+
const response = await this.cloudFetch(this.endpoint('/auth/refresh'), {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'content-type': 'application/json' },
|
|
71
|
+
body: JSON.stringify({ refreshToken: tokens.refreshToken }),
|
|
72
|
+
signal: AbortSignal.timeout(10_000),
|
|
73
|
+
});
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
throw new Error('Sessão expirada. Execute `memvault login` novamente.');
|
|
76
|
+
}
|
|
77
|
+
const refreshed = (await response.json());
|
|
78
|
+
const stored = {
|
|
79
|
+
...refreshed,
|
|
80
|
+
expiresAt: Date.now() + (refreshed.expiresIn ?? 900) * 1000,
|
|
81
|
+
};
|
|
82
|
+
saveTokens(stored, this.home);
|
|
83
|
+
return stored.accessToken;
|
|
84
|
+
}
|
|
85
|
+
async request(path, options = {}) {
|
|
86
|
+
const headers = new Headers({ accept: 'application/json' });
|
|
87
|
+
if (options.body)
|
|
88
|
+
headers.set('content-type', 'application/json');
|
|
89
|
+
if (options.authenticated !== false) {
|
|
90
|
+
headers.set('authorization', `Bearer ${await this.accessToken(options.retried)}`);
|
|
91
|
+
}
|
|
92
|
+
const request = {
|
|
93
|
+
method: options.method ?? 'GET',
|
|
94
|
+
headers,
|
|
95
|
+
signal: AbortSignal.timeout(15_000),
|
|
96
|
+
};
|
|
97
|
+
if (options.body)
|
|
98
|
+
request.body = JSON.stringify(options.body);
|
|
99
|
+
const response = await this.cloudFetch(this.endpoint(path), request);
|
|
100
|
+
if (response.status === 401 && options.authenticated !== false && !options.retried) {
|
|
101
|
+
return this.request(path, { ...options, retried: true });
|
|
102
|
+
}
|
|
103
|
+
const text = await response.text();
|
|
104
|
+
const payload = text ? safeJson(text) : null;
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
const message = payload && typeof payload === 'object' && 'error' in payload
|
|
107
|
+
? String(payload.error)
|
|
108
|
+
: `Vault respondeu HTTP ${response.status}`;
|
|
109
|
+
throw new Error(message);
|
|
110
|
+
}
|
|
111
|
+
return payload;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function safeJson(text) {
|
|
115
|
+
try {
|
|
116
|
+
return JSON.parse(text);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return text;
|
|
120
|
+
}
|
|
121
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -6,20 +6,23 @@ export interface VaultConfig {
|
|
|
6
6
|
intervalSeconds: number;
|
|
7
7
|
watchPaths: string[];
|
|
8
8
|
}
|
|
9
|
+
export interface StoredTokens {
|
|
10
|
+
accessToken: string;
|
|
11
|
+
refreshToken?: string;
|
|
12
|
+
expiresIn?: number;
|
|
13
|
+
expiresAt?: number;
|
|
14
|
+
}
|
|
9
15
|
export interface VaultStatus {
|
|
10
|
-
vaultHome: string;
|
|
11
16
|
apiUrl: string;
|
|
12
17
|
configured: boolean;
|
|
13
18
|
authenticated: boolean;
|
|
14
|
-
|
|
15
|
-
notesPath: string;
|
|
19
|
+
account?: string;
|
|
16
20
|
}
|
|
17
21
|
export declare function vaultHome(environment?: NodeJS.ProcessEnv): string;
|
|
18
22
|
export declare function configPath(home?: string): string;
|
|
19
23
|
export declare function authPath(home?: string): string;
|
|
20
|
-
export declare function notesPath(home?: string): string;
|
|
21
|
-
export declare function ensurePrivateDirectory(directory: string): void;
|
|
22
24
|
export declare function loadConfig(home?: string): VaultConfig;
|
|
23
|
-
export declare function
|
|
24
|
-
export declare function
|
|
25
|
+
export declare function loadTokens(home?: string): StoredTokens | null;
|
|
26
|
+
export declare function saveTokens(tokens: StoredTokens, home?: string): void;
|
|
25
27
|
export declare function getStatus(home?: string): VaultStatus;
|
|
28
|
+
export declare function validateApiUrl(value: string): URL;
|
package/dist/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chmodSync, existsSync,
|
|
1
|
+
import { chmodSync, existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
export const DEFAULT_API_URL = 'https://vault.resolveup.com.br/api';
|
|
@@ -8,7 +8,9 @@ const DEFAULT_CONFIG = {
|
|
|
8
8
|
watchPaths: [],
|
|
9
9
|
};
|
|
10
10
|
export function vaultHome(environment = process.env) {
|
|
11
|
-
const configured = environment['VAULT_GO_HOME'] ??
|
|
11
|
+
const configured = environment['VAULT_GO_HOME'] ??
|
|
12
|
+
environment['MEMVAULT_CONFIG_DIR'] ??
|
|
13
|
+
environment['MEMVAULT_HOME'];
|
|
12
14
|
return resolve(configured || join(homedir(), '.memvault'));
|
|
13
15
|
}
|
|
14
16
|
export function configPath(home = vaultHome()) {
|
|
@@ -17,59 +19,61 @@ export function configPath(home = vaultHome()) {
|
|
|
17
19
|
export function authPath(home = vaultHome()) {
|
|
18
20
|
return join(home, 'auth.json');
|
|
19
21
|
}
|
|
20
|
-
export function notesPath(home = vaultHome()) {
|
|
21
|
-
return join(home, 'mcp-notes');
|
|
22
|
-
}
|
|
23
|
-
export function ensurePrivateDirectory(directory) {
|
|
24
|
-
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
25
|
-
chmodSync(directory, 0o700);
|
|
26
|
-
}
|
|
27
22
|
export function loadConfig(home = vaultHome()) {
|
|
28
23
|
const file = configPath(home);
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
const environmentApiUrl = process.env['VAULT_GO_API_URL'];
|
|
25
|
+
if (!existsSync(file)) {
|
|
26
|
+
return {
|
|
27
|
+
...DEFAULT_CONFIG,
|
|
28
|
+
apiUrl: environmentApiUrl || DEFAULT_API_URL,
|
|
29
|
+
watchPaths: [],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
31
32
|
try {
|
|
32
33
|
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
33
|
-
const watchPaths = Array.isArray(parsed.watchPaths)
|
|
34
|
-
? parsed.watchPaths.filter((entry) => typeof entry === 'string')
|
|
35
|
-
: [];
|
|
36
34
|
return {
|
|
37
35
|
...DEFAULT_CONFIG,
|
|
38
36
|
...parsed,
|
|
39
|
-
apiUrl: parsed.apiUrl || DEFAULT_API_URL,
|
|
40
|
-
watchPaths,
|
|
37
|
+
apiUrl: environmentApiUrl || parsed.apiUrl || DEFAULT_API_URL,
|
|
38
|
+
watchPaths: Array.isArray(parsed.watchPaths) ? parsed.watchPaths : [],
|
|
41
39
|
};
|
|
42
40
|
}
|
|
43
41
|
catch {
|
|
44
42
|
return { ...DEFAULT_CONFIG, watchPaths: [] };
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
|
-
export function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
export function loadTokens(home = vaultHome()) {
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(readFileSync(authPath(home), 'utf8'));
|
|
48
|
+
return typeof parsed.accessToken === 'string' ? parsed : null;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
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 });
|
|
52
58
|
renameSync(temporary, destination);
|
|
53
59
|
chmodSync(destination, 0o600);
|
|
54
60
|
}
|
|
55
|
-
export function ensureNotesPath(home = vaultHome()) {
|
|
56
|
-
const directory = notesPath(home);
|
|
57
|
-
ensurePrivateDirectory(directory);
|
|
58
|
-
const config = loadConfig(home);
|
|
59
|
-
const normalized = new Set(config.watchPaths.map((entry) => resolve(entry)));
|
|
60
|
-
if (!normalized.has(resolve(directory))) {
|
|
61
|
-
saveConfig({ ...config, watchPaths: [...config.watchPaths, directory] }, home);
|
|
62
|
-
}
|
|
63
|
-
return directory;
|
|
64
|
-
}
|
|
65
61
|
export function getStatus(home = vaultHome()) {
|
|
66
62
|
const config = loadConfig(home);
|
|
67
|
-
|
|
68
|
-
vaultHome: home,
|
|
63
|
+
const status = {
|
|
69
64
|
apiUrl: config.apiUrl,
|
|
70
65
|
configured: existsSync(configPath(home)),
|
|
71
|
-
authenticated:
|
|
72
|
-
watchPaths: config.watchPaths.map((entry) => resolve(entry)),
|
|
73
|
-
notesPath: notesPath(home),
|
|
66
|
+
authenticated: Boolean(process.env['VAULT_GO_TOKEN']) || loadTokens(home) !== null,
|
|
74
67
|
};
|
|
68
|
+
if (config.email)
|
|
69
|
+
status.account = config.email;
|
|
70
|
+
return status;
|
|
71
|
+
}
|
|
72
|
+
export function validateApiUrl(value) {
|
|
73
|
+
const url = new URL(value);
|
|
74
|
+
const local = url.hostname === 'localhost' || url.hostname === '127.0.0.1';
|
|
75
|
+
if (url.protocol !== 'https:' && !(local && url.protocol === 'http:')) {
|
|
76
|
+
throw new Error('A API deve usar HTTPS, exceto em localhost.');
|
|
77
|
+
}
|
|
78
|
+
return url;
|
|
75
79
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
2
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { createVaultGoServer, VERSION } from './server.js';
|
|
4
4
|
const argument = process.argv[2];
|
|
@@ -7,7 +7,7 @@ if (argument === '--version' || argument === '-v') {
|
|
|
7
7
|
process.exit(0);
|
|
8
8
|
}
|
|
9
9
|
if (argument === '--help' || argument === '-h') {
|
|
10
|
-
process.stdout.write(`vault-go ${VERSION}\n\nServidor MCP
|
|
10
|
+
process.stdout.write(`vault-go ${VERSION}\n\nServidor MCP cloud por stdio, executado com Bun.\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`);
|
|
11
11
|
process.exit(0);
|
|
12
12
|
}
|
|
13
13
|
async function main() {
|
package/dist/server.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
|
|
3
|
-
export declare
|
|
2
|
+
import { type VaultMemoryApi } from './cloud.js';
|
|
3
|
+
export declare const VERSION: string;
|
|
4
|
+
export declare function createVaultGoServer(home?: string, cloud?: VaultMemoryApi): McpServer;
|
package/dist/server.js
CHANGED
|
@@ -1,137 +1,177 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import { VaultCloudClient } from './cloud.js';
|
|
4
|
+
import { getStatus, vaultHome } from './config.js';
|
|
5
|
+
import packageJson from '../package.json' with { type: 'json' };
|
|
6
|
+
export const VERSION = packageJson.version;
|
|
6
7
|
function jsonResult(value) {
|
|
7
|
-
const
|
|
8
|
+
const structuredContent = value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
9
|
+
? value
|
|
10
|
+
: { value };
|
|
8
11
|
return {
|
|
9
|
-
content: [{ type: 'text', text }],
|
|
10
|
-
structuredContent
|
|
12
|
+
content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
13
|
+
structuredContent,
|
|
11
14
|
};
|
|
12
15
|
}
|
|
13
16
|
function errorResult(error) {
|
|
14
17
|
const message = error instanceof Error ? error.message : 'Erro inesperado.';
|
|
15
|
-
return {
|
|
16
|
-
content: [{ type: 'text', text: message }],
|
|
17
|
-
isError: true,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
function rootsFor(home) {
|
|
21
|
-
const config = loadConfig(home);
|
|
22
|
-
return [...config.watchPaths, notesPath(home)];
|
|
18
|
+
return { content: [{ type: 'text', text: message }], isError: true };
|
|
23
19
|
}
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
async function run(operation) {
|
|
21
|
+
try {
|
|
22
|
+
return jsonResult(await operation());
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return errorResult(error);
|
|
29
26
|
}
|
|
30
|
-
return url;
|
|
31
27
|
}
|
|
32
|
-
|
|
28
|
+
const uuid = z.string().uuid();
|
|
29
|
+
export function createVaultGoServer(home = vaultHome(), cloud = new VaultCloudClient(home)) {
|
|
33
30
|
const server = new McpServer({ name: 'vault-go', version: VERSION });
|
|
34
31
|
server.registerTool('vault_go_status', {
|
|
35
32
|
title: 'Status do Vault',
|
|
36
|
-
description: 'Mostra
|
|
33
|
+
description: 'Mostra conexão e autenticação sem revelar credenciais.',
|
|
37
34
|
inputSchema: {},
|
|
38
35
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
39
36
|
}, async () => jsonResult(getStatus(home)));
|
|
40
|
-
server.registerTool('
|
|
41
|
-
title: '
|
|
42
|
-
description: '
|
|
37
|
+
server.registerTool('vault_go_cloud_health', {
|
|
38
|
+
title: 'Verificar nuvem do Vault',
|
|
39
|
+
description: 'Verifica a disponibilidade da plataforma Vault.',
|
|
40
|
+
inputSchema: {},
|
|
41
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
42
|
+
}, async () => run(() => cloud.health()));
|
|
43
|
+
server.registerTool('vault_go_projects', {
|
|
44
|
+
title: 'Listar projetos',
|
|
45
|
+
description: 'Lista os projetos de memória da conta autenticada.',
|
|
46
|
+
inputSchema: {},
|
|
47
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
48
|
+
}, async () => run(() => cloud.projects()));
|
|
49
|
+
server.registerTool('vault_go_project_create', {
|
|
50
|
+
title: 'Criar projeto',
|
|
51
|
+
description: 'Cria um projeto de memória no Vault.',
|
|
43
52
|
inputSchema: {
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
name: z.string().trim().min(1).max(200),
|
|
54
|
+
rootPath: z.string().trim().max(2_000).optional(),
|
|
46
55
|
},
|
|
47
|
-
annotations: { readOnlyHint:
|
|
48
|
-
}, async (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
57
|
+
}, async (input) => run(() => cloud.createProject(input)));
|
|
58
|
+
server.registerTool('vault_go_session_start', {
|
|
59
|
+
title: 'Iniciar sessão',
|
|
60
|
+
description: 'Inicia ou recupera uma sessão de trabalho no Vault.',
|
|
61
|
+
inputSchema: {
|
|
62
|
+
projectId: uuid,
|
|
63
|
+
externalSessionId: z.string().trim().max(500).optional(),
|
|
64
|
+
platformSource: z.string().trim().min(1).max(100).default('mcp'),
|
|
65
|
+
},
|
|
66
|
+
annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: true },
|
|
67
|
+
}, async (input) => run(() => cloud.startSession(input)));
|
|
68
|
+
server.registerTool('vault_go_session_end', {
|
|
69
|
+
title: 'Encerrar sessão',
|
|
70
|
+
description: 'Marca uma sessão como concluída.',
|
|
71
|
+
inputSchema: { id: uuid },
|
|
72
|
+
annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: true },
|
|
73
|
+
}, async ({ id }) => run(() => cloud.endSession(id)));
|
|
74
|
+
server.registerTool('vault_go_event', {
|
|
75
|
+
title: 'Registrar evento',
|
|
76
|
+
description: 'Registra um evento de prompt, ferramenta ou resultado na sessão.',
|
|
77
|
+
inputSchema: {
|
|
78
|
+
projectId: uuid,
|
|
79
|
+
sessionId: uuid.optional(),
|
|
80
|
+
sourceAdapter: z.string().trim().min(1).max(100).default('mcp'),
|
|
81
|
+
sourceEventId: z.string().trim().max(500).optional(),
|
|
82
|
+
eventType: z.string().trim().min(1).max(100),
|
|
83
|
+
payload: z.record(z.unknown()).default({}),
|
|
84
|
+
occurredAtEpoch: z.number().int().positive().optional(),
|
|
85
|
+
},
|
|
86
|
+
annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: true },
|
|
87
|
+
}, async (input) => run(() => cloud.event(input)));
|
|
88
|
+
server.registerTool('vault_go_remember', {
|
|
89
|
+
title: 'Registrar memória',
|
|
90
|
+
description: 'Persiste uma observação, decisão, descoberta ou resumo no Vault.',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
projectId: uuid,
|
|
93
|
+
sessionId: uuid.optional(),
|
|
94
|
+
kind: z.string().trim().min(1).max(100).default('observation'),
|
|
95
|
+
memoryType: z.string().trim().min(1).max(100).default('discovery'),
|
|
96
|
+
title: z.string().trim().max(500).optional(),
|
|
97
|
+
content: z.string().trim().min(1).max(200_000),
|
|
98
|
+
facts: z.array(z.string().trim().min(1).max(2_000)).max(200).default([]),
|
|
99
|
+
concepts: z.array(z.string().trim().min(1).max(500)).max(200).default([]),
|
|
100
|
+
filesRead: z.array(z.string().trim().min(1).max(2_000)).max(500).default([]),
|
|
101
|
+
filesModified: z.array(z.string().trim().min(1).max(2_000)).max(500).default([]),
|
|
102
|
+
},
|
|
103
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
104
|
+
}, async (input) => run(() => cloud.remember(input)));
|
|
105
|
+
server.registerTool('vault_go_forget', {
|
|
106
|
+
title: 'Excluir memória',
|
|
107
|
+
description: 'Exclui uma memória da conta autenticada.',
|
|
108
|
+
inputSchema: { id: uuid },
|
|
109
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
110
|
+
}, async ({ id }) => run(() => cloud.forget(id)));
|
|
57
111
|
server.registerTool('vault_go_search', {
|
|
58
112
|
title: 'Pesquisar no Vault',
|
|
59
|
-
description: 'Pesquisa
|
|
113
|
+
description: 'Pesquisa memórias persistidas na plataforma Vault.',
|
|
60
114
|
inputSchema: {
|
|
61
|
-
query: z.string().trim().min(1).max(
|
|
62
|
-
|
|
115
|
+
query: z.string().trim().min(1).max(1_000),
|
|
116
|
+
projectId: uuid.optional(),
|
|
117
|
+
kind: z.string().trim().max(100).optional(),
|
|
118
|
+
memoryType: z.string().trim().max(100).optional(),
|
|
119
|
+
limit: z.number().int().min(1).max(200).default(20),
|
|
63
120
|
},
|
|
64
|
-
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint:
|
|
65
|
-
}, async (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
catch (error) {
|
|
70
|
-
return errorResult(error);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
server.registerTool('vault_go_read_file', {
|
|
74
|
-
title: 'Ler arquivo do Vault',
|
|
75
|
-
description: 'Lê um arquivo textual permitido, limitado a 1 MiB e sem atravessar os caminhos monitorados.',
|
|
121
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
122
|
+
}, async (input) => run(() => cloud.search(input)));
|
|
123
|
+
server.registerTool('vault_go_timeline', {
|
|
124
|
+
title: 'Abrir linha do tempo',
|
|
125
|
+
description: 'Retorna memórias anteriores e posteriores a uma memória âncora.',
|
|
76
126
|
inputSchema: {
|
|
77
|
-
|
|
127
|
+
anchorId: uuid,
|
|
128
|
+
before: z.number().int().min(1).max(100).default(5),
|
|
129
|
+
after: z.number().int().min(1).max(100).default(5),
|
|
78
130
|
},
|
|
79
|
-
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint:
|
|
80
|
-
}, async (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
catch (error) {
|
|
85
|
-
return errorResult(error);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
server.registerTool('vault_go_write_note', {
|
|
89
|
-
title: 'Registrar nota no Vault',
|
|
90
|
-
description: 'Cria ou acrescenta uma nota Markdown em mcp-notes, monitorada pelo agente de sincronização.',
|
|
131
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
132
|
+
}, async (input) => run(() => cloud.timeline(input)));
|
|
133
|
+
server.registerTool('vault_go_context', {
|
|
134
|
+
title: 'Montar contexto',
|
|
135
|
+
description: 'Monta contexto progressivo, limitado e pronto para injeção no cliente.',
|
|
91
136
|
inputSchema: {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
137
|
+
projectId: uuid,
|
|
138
|
+
query: z.string().trim().max(1_000).optional(),
|
|
139
|
+
limit: z.number().int().min(1).max(100).default(20),
|
|
140
|
+
maxChars: z.number().int().min(1_000).max(100_000).default(24_000),
|
|
95
141
|
},
|
|
96
|
-
annotations: { readOnlyHint:
|
|
97
|
-
}, async (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
142
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
143
|
+
}, async (input) => run(() => cloud.context(input)));
|
|
144
|
+
server.registerTool('vault_go_file_context', {
|
|
145
|
+
title: 'Contexto de arquivos',
|
|
146
|
+
description: 'Recupera memórias anteriores associadas a um ou mais arquivos do projeto.',
|
|
147
|
+
inputSchema: {
|
|
148
|
+
projectId: uuid,
|
|
149
|
+
paths: z.array(z.string().trim().min(1).max(2_000)).min(1).max(20),
|
|
150
|
+
limit: z.number().int().min(1).max(100).default(20),
|
|
151
|
+
maxChars: z.number().int().min(1_000).max(100_000).default(16_000),
|
|
152
|
+
},
|
|
153
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
154
|
+
}, async (input) => run(() => cloud.fileContext(input)));
|
|
155
|
+
server.registerTool('vault_go_stats', {
|
|
156
|
+
title: 'Estatísticas do Vault',
|
|
157
|
+
description: 'Mostra contagens da conta autenticada.',
|
|
109
158
|
inputSchema: {},
|
|
110
159
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
111
|
-
}, async () =>
|
|
112
|
-
try {
|
|
113
|
-
const status = getStatus(home);
|
|
114
|
-
const base = validateApiUrl(status.apiUrl);
|
|
115
|
-
const healthUrl = new URL(`${base.pathname.replace(/\/$/u, '')}/health`, base.origin);
|
|
116
|
-
const response = await fetch(healthUrl, {
|
|
117
|
-
headers: { Accept: 'application/json' },
|
|
118
|
-
signal: AbortSignal.timeout(10_000),
|
|
119
|
-
});
|
|
120
|
-
const body = await response.text();
|
|
121
|
-
return jsonResult({ ok: response.ok, status: response.status, url: healthUrl.href, body });
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
return errorResult(error);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
160
|
+
}, async () => run(() => cloud.stats()));
|
|
127
161
|
server.registerResource('vault-go-status', 'vault-go://status', {
|
|
128
162
|
title: 'Status do Vault Go',
|
|
129
|
-
description: 'Resumo sanitizado da
|
|
163
|
+
description: 'Resumo sanitizado da conexão cloud.',
|
|
130
164
|
mimeType: 'application/json',
|
|
131
165
|
}, async (uri) => {
|
|
132
166
|
const status = getStatus(home);
|
|
133
167
|
return {
|
|
134
|
-
contents: [
|
|
168
|
+
contents: [
|
|
169
|
+
{
|
|
170
|
+
uri: uri.href,
|
|
171
|
+
mimeType: 'application/json',
|
|
172
|
+
text: JSON.stringify(status, null, 2),
|
|
173
|
+
},
|
|
174
|
+
],
|
|
135
175
|
};
|
|
136
176
|
});
|
|
137
177
|
return server;
|
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.3.0",
|
|
4
|
+
"description": "Servidor MCP Bun para pesquisar e registrar memória na plataforma Vault.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"vault-go": "dist/index.js",
|
|
@@ -26,21 +26,22 @@
|
|
|
26
26
|
"dist/server.d.ts",
|
|
27
27
|
"dist/config.js",
|
|
28
28
|
"dist/config.d.ts",
|
|
29
|
-
"dist/
|
|
30
|
-
"dist/
|
|
29
|
+
"dist/cloud.js",
|
|
30
|
+
"dist/cloud.d.ts",
|
|
31
31
|
"README.md",
|
|
32
32
|
"LICENSE"
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
35
|
-
"clean": "
|
|
36
|
-
"build": "
|
|
35
|
+
"clean": "bun -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
36
|
+
"build": "bun run clean && tsc -p tsconfig.json",
|
|
37
37
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
38
|
-
"test": "
|
|
38
|
+
"test": "bun run build && bun test src",
|
|
39
39
|
"pack:check": "npm pack --dry-run",
|
|
40
40
|
"release": "semantic-release",
|
|
41
|
-
"prepublishOnly": "
|
|
41
|
+
"prepublishOnly": "bun test src && npm run pack:check"
|
|
42
42
|
},
|
|
43
43
|
"engines": {
|
|
44
|
+
"bun": ">=1.3.0",
|
|
44
45
|
"node": ">=20"
|
|
45
46
|
},
|
|
46
47
|
"publishConfig": {
|
|
@@ -74,6 +75,7 @@
|
|
|
74
75
|
"@semantic-release/github": "12.0.9",
|
|
75
76
|
"@semantic-release/npm": "13.1.5",
|
|
76
77
|
"@semantic-release/release-notes-generator": "14.1.1",
|
|
78
|
+
"@types/bun": "^1.3.0",
|
|
77
79
|
"@types/node": "^22.15.0",
|
|
78
80
|
"semantic-release": "25.0.8",
|
|
79
81
|
"typescript": "^5.9.3"
|
package/dist/files.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export declare const MAX_READ_BYTES = 1048576;
|
|
2
|
-
export interface VaultFile {
|
|
3
|
-
path: string;
|
|
4
|
-
root: string;
|
|
5
|
-
relativePath: string;
|
|
6
|
-
size: number;
|
|
7
|
-
modifiedAt: string;
|
|
8
|
-
}
|
|
9
|
-
export interface SearchMatch {
|
|
10
|
-
path: string;
|
|
11
|
-
relativePath: string;
|
|
12
|
-
line: number;
|
|
13
|
-
snippet: string;
|
|
14
|
-
}
|
|
15
|
-
export declare function resolveAllowedFile(input: string, roots: string[]): string;
|
|
16
|
-
export declare function listVaultFiles(roots: string[], options?: {
|
|
17
|
-
query?: string;
|
|
18
|
-
limit?: number;
|
|
19
|
-
}): VaultFile[];
|
|
20
|
-
export declare function readVaultFile(input: string, roots: string[]): {
|
|
21
|
-
path: string;
|
|
22
|
-
text: string;
|
|
23
|
-
};
|
|
24
|
-
export declare function searchVaultFiles(roots: string[], query: string, limit?: number): SearchMatch[];
|
|
25
|
-
export declare function writeVaultNote(notesRoot: string, title: string, content: string, mode?: 'create' | 'append'): string;
|
package/dist/files.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { appendFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, writeFileSync, } from 'node:fs';
|
|
2
|
-
import { basename, extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
3
|
-
export const MAX_READ_BYTES = 1_048_576;
|
|
4
|
-
const DEFAULT_LIMIT = 50;
|
|
5
|
-
const MAX_LIMIT = 200;
|
|
6
|
-
const TEXT_EXTENSIONS = new Set([
|
|
7
|
-
'.md',
|
|
8
|
-
'.mdx',
|
|
9
|
-
'.txt',
|
|
10
|
-
'.json',
|
|
11
|
-
'.jsonl',
|
|
12
|
-
'.yaml',
|
|
13
|
-
'.yml',
|
|
14
|
-
'.toml',
|
|
15
|
-
'.csv',
|
|
16
|
-
'.log',
|
|
17
|
-
]);
|
|
18
|
-
const IGNORED_DIRECTORIES = new Set(['.git', 'node_modules', 'dist', '.cache']);
|
|
19
|
-
function boundedLimit(limit) {
|
|
20
|
-
if (!limit || !Number.isFinite(limit))
|
|
21
|
-
return DEFAULT_LIMIT;
|
|
22
|
-
return Math.min(MAX_LIMIT, Math.max(1, Math.trunc(limit)));
|
|
23
|
-
}
|
|
24
|
-
function isTextFile(file) {
|
|
25
|
-
return TEXT_EXTENSIONS.has(extname(file).toLowerCase());
|
|
26
|
-
}
|
|
27
|
-
function normalizedRoots(roots) {
|
|
28
|
-
return [...new Set(roots.map((root) => resolve(root)))].filter((root) => {
|
|
29
|
-
try {
|
|
30
|
-
return statSync(root).isDirectory();
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
function isInside(candidate, root) {
|
|
38
|
-
return candidate === root || candidate.startsWith(`${root}${sep}`);
|
|
39
|
-
}
|
|
40
|
-
export function resolveAllowedFile(input, roots) {
|
|
41
|
-
const availableRoots = normalizedRoots(roots);
|
|
42
|
-
const candidates = isAbsolute(input)
|
|
43
|
-
? [resolve(input)]
|
|
44
|
-
: availableRoots.map((root) => resolve(root, input));
|
|
45
|
-
for (const candidate of candidates) {
|
|
46
|
-
if (!existsSync(candidate))
|
|
47
|
-
continue;
|
|
48
|
-
const actual = realpathSync(candidate);
|
|
49
|
-
if (!lstatSync(actual).isFile())
|
|
50
|
-
continue;
|
|
51
|
-
if (availableRoots.some((root) => isInside(actual, realpathSync(root))))
|
|
52
|
-
return actual;
|
|
53
|
-
}
|
|
54
|
-
throw new Error('Arquivo inexistente ou fora dos caminhos monitorados.');
|
|
55
|
-
}
|
|
56
|
-
export function listVaultFiles(roots, options = {}) {
|
|
57
|
-
const limit = boundedLimit(options.limit);
|
|
58
|
-
const query = options.query?.trim().toLocaleLowerCase();
|
|
59
|
-
const results = [];
|
|
60
|
-
for (const root of normalizedRoots(roots)) {
|
|
61
|
-
const stack = [root];
|
|
62
|
-
while (stack.length > 0 && results.length < limit) {
|
|
63
|
-
const current = stack.pop();
|
|
64
|
-
if (!current)
|
|
65
|
-
break;
|
|
66
|
-
const entries = readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
67
|
-
for (const entry of entries) {
|
|
68
|
-
if (entry.name.startsWith('.'))
|
|
69
|
-
continue;
|
|
70
|
-
const absolute = join(current, entry.name);
|
|
71
|
-
if (entry.isSymbolicLink())
|
|
72
|
-
continue;
|
|
73
|
-
if (entry.isDirectory()) {
|
|
74
|
-
if (!IGNORED_DIRECTORIES.has(entry.name))
|
|
75
|
-
stack.push(absolute);
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (!entry.isFile() || !isTextFile(entry.name))
|
|
79
|
-
continue;
|
|
80
|
-
const relativePath = relative(root, absolute);
|
|
81
|
-
if (query && !relativePath.toLocaleLowerCase().includes(query))
|
|
82
|
-
continue;
|
|
83
|
-
const stats = statSync(absolute);
|
|
84
|
-
results.push({
|
|
85
|
-
path: absolute,
|
|
86
|
-
root,
|
|
87
|
-
relativePath,
|
|
88
|
-
size: stats.size,
|
|
89
|
-
modifiedAt: stats.mtime.toISOString(),
|
|
90
|
-
});
|
|
91
|
-
if (results.length >= limit)
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (results.length >= limit)
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
return results;
|
|
99
|
-
}
|
|
100
|
-
export function readVaultFile(input, roots) {
|
|
101
|
-
const file = resolveAllowedFile(input, roots);
|
|
102
|
-
const size = statSync(file).size;
|
|
103
|
-
if (size > MAX_READ_BYTES) {
|
|
104
|
-
throw new Error(`Arquivo excede o limite de ${MAX_READ_BYTES} bytes.`);
|
|
105
|
-
}
|
|
106
|
-
if (!isTextFile(file))
|
|
107
|
-
throw new Error('Tipo de arquivo não permitido para leitura textual.');
|
|
108
|
-
return { path: file, text: readFileSync(file, 'utf8') };
|
|
109
|
-
}
|
|
110
|
-
export function searchVaultFiles(roots, query, limit) {
|
|
111
|
-
const normalizedQuery = query.trim().toLocaleLowerCase();
|
|
112
|
-
if (!normalizedQuery)
|
|
113
|
-
throw new Error('A consulta não pode ser vazia.');
|
|
114
|
-
const maxResults = boundedLimit(limit);
|
|
115
|
-
const files = listVaultFiles(roots, { limit: MAX_LIMIT });
|
|
116
|
-
const matches = [];
|
|
117
|
-
for (const file of files) {
|
|
118
|
-
if (file.size > MAX_READ_BYTES)
|
|
119
|
-
continue;
|
|
120
|
-
const lines = readFileSync(file.path, 'utf8').split(/\r?\n/u);
|
|
121
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
122
|
-
const line = lines[index] ?? '';
|
|
123
|
-
if (!line.toLocaleLowerCase().includes(normalizedQuery))
|
|
124
|
-
continue;
|
|
125
|
-
matches.push({
|
|
126
|
-
path: file.path,
|
|
127
|
-
relativePath: file.relativePath,
|
|
128
|
-
line: index + 1,
|
|
129
|
-
snippet: line.trim().slice(0, 500),
|
|
130
|
-
});
|
|
131
|
-
if (matches.length >= maxResults)
|
|
132
|
-
return matches;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return matches;
|
|
136
|
-
}
|
|
137
|
-
function safeSlug(title) {
|
|
138
|
-
const slug = title
|
|
139
|
-
.normalize('NFKD')
|
|
140
|
-
.replace(/[\u0300-\u036f]/gu, '')
|
|
141
|
-
.toLocaleLowerCase()
|
|
142
|
-
.replace(/[^a-z0-9]+/gu, '-')
|
|
143
|
-
.replace(/^-+|-+$/gu, '')
|
|
144
|
-
.slice(0, 80);
|
|
145
|
-
return slug || `note-${Date.now()}`;
|
|
146
|
-
}
|
|
147
|
-
export function writeVaultNote(notesRoot, title, content, mode = 'create') {
|
|
148
|
-
mkdirSync(notesRoot, { recursive: true, mode: 0o700 });
|
|
149
|
-
const file = join(notesRoot, `${safeSlug(title)}.md`);
|
|
150
|
-
const body = `# ${title.trim()}\n\n${content.trim()}\n`;
|
|
151
|
-
if (mode === 'create' && existsSync(file)) {
|
|
152
|
-
throw new Error(`A nota ${basename(file)} já existe; use mode=append.`);
|
|
153
|
-
}
|
|
154
|
-
if (mode === 'append' && existsSync(file)) {
|
|
155
|
-
appendFileSync(file, `\n${content.trim()}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
writeFileSync(file, body, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
159
|
-
}
|
|
160
|
-
return file;
|
|
161
|
-
}
|