vault-go 0.19.1 → 0.21.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 +27 -1
- package/dist/capabilities.js +2 -2
- package/dist/cli.js +8 -5
- package/dist/hook-install.d.ts +7 -2
- package/dist/hook-install.js +71 -15
- package/dist/hooks.js +69 -13
- package/dist/install-ui.d.ts +14 -0
- package/dist/install-ui.js +95 -0
- package/dist/installer.js +11 -1
- package/dist/locale.d.ts +3 -0
- package/dist/locale.js +3 -0
- package/dist/opencode-plugin.d.ts +16 -0
- package/dist/opencode-plugin.js +73 -0
- package/dist/update-control.d.ts +2 -0
- package/dist/update-control.js +19 -2
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -62,6 +62,13 @@ Execute em um terminal:
|
|
|
62
62
|
bunx --bun vault-go@latest
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
A instalação abre com uma animação ASCII do Vault Go e mostra um indicador
|
|
66
|
+
enquanto verifica os motores disponíveis. Em terminais pequenos, a abertura usa
|
|
67
|
+
uma versão compacta. Para desativar, execute `vault-go setup --no-animation` ou
|
|
68
|
+
defina `VAULT_GO_NO_ANIMATION=1`. `NO_COLOR`, `TERM=dumb`, CI e saídas sem terminal
|
|
69
|
+
também usam a apresentação estática, sem códigos ANSI ou espera. O modo MCP
|
|
70
|
+
`serve` não exibe a animação.
|
|
71
|
+
|
|
65
72
|
O assistente:
|
|
66
73
|
|
|
67
74
|
1. valida uma sessão existente em `~/.memvault`;
|
|
@@ -174,7 +181,7 @@ limitados.
|
|
|
174
181
|
|
|
175
182
|
O seletor oferece Codex (Terra, Luna ou Sol pela assinatura ChatGPT), Claude (CLI da assinatura), OpenRouter (chave), Gemini (chave) ou o Vault AI Resume da conta, quando disponível no servidor. Grave chaves com `vault-go engine key openrouter|gemini` ou no painel local; as ferramentas MCP não aceitam segredos.
|
|
176
183
|
|
|
177
|
-
Na instalação, o Vault Go registra hooks no Claude Code, no Codex, no Grok e no
|
|
184
|
+
Na instalação, o Vault Go registra hooks no Claude Code, no Codex, no Grok, no Agy e no Gemini CLI. Eles carregam contexto e capturam os eventos oferecidos pelo cliente; o serviço local (`http://localhost:38850`) gera memórias com o motor escolhido, persistindo no Vault. No OpenCode 1.x, um plugin acrescenta contexto ao sistema antes das chamadas do modelo; esse adaptador não adiciona captura automática.
|
|
178
185
|
|
|
179
186
|
O recurso `vault-go://status` fornece o resumo sanitizado em JSON.
|
|
180
187
|
|
|
@@ -271,6 +278,10 @@ O runtime fica em `~/.memvault/local-runtime`, independente do cache bunx.
|
|
|
271
278
|
`local stop` para o processo; `local uninstall` remove o início automático e
|
|
272
279
|
preserva autenticação, cache e atividade. A atualização automática mantém o
|
|
273
280
|
serviço e os hooks atualizados; clientes MCP abertos precisam reconectar.
|
|
281
|
+
Após validar a nova versão do serviço, a atualização também repara os hooks do
|
|
282
|
+
Gemini e o plugin do OpenCode quando esses clientes já têm o MCP Vault configurado.
|
|
283
|
+
Plugins de terceiros são preservados. Reinicie clientes abertos para carregar
|
|
284
|
+
novos hooks ou o módulo atualizado do plugin.
|
|
274
285
|
|
|
275
286
|
A atividade registra método, rota, duração e resultado das chamadas feitas
|
|
276
287
|
pelo MCP Vault, sem argumentos, consultas ou credenciais. Não captura toda a
|
|
@@ -315,6 +326,21 @@ conforme os eventos oferecidos pelo cliente. O hook anterior à leitura de arqui
|
|
|
315
326
|
é síncrono para conseguir inserir o contexto. `vault_go_session_start_context`
|
|
316
327
|
executa o mesmo leitor, respeitando `contextItems` e `contextMaxChars` da conta.
|
|
317
328
|
|
|
329
|
+
Claude, Codex e Gemini mostram uma confirmação quando o contexto é carregado,
|
|
330
|
+
sem exibir conteúdo da memória nessa confirmação. Worktrees Git reutilizam o
|
|
331
|
+
projeto do repositório principal; se ele ainda não estiver cadastrado, um projeto
|
|
332
|
+
existente do próprio worktree continua legível. Submódulos mantêm seu projeto.
|
|
333
|
+
|
|
334
|
+
O Gemini usa `SessionStart`, `BeforeAgent`, `AfterTool` e `AfterAgent`, conforme
|
|
335
|
+
seu [contrato de hooks](https://geminicli.com/docs/hooks/reference/). Não há
|
|
336
|
+
injeção de histórico de arquivo em `BeforeTool`, pois esse evento não oferece
|
|
337
|
+
esse contrato. O plugin OpenCode usa `experimental.chat.system.transform`
|
|
338
|
+
do [contrato 1.x](https://github.com/anomalyco/opencode/blob/v1.18.31/packages/plugin/src/index.ts),
|
|
339
|
+
com prazo de três segundos e continuação normal em caso de falha. Consultas
|
|
340
|
+
simultâneas da mesma sessão são compartilhadas; respostas de uma credencial
|
|
341
|
+
substituída não são injetadas. Clientes sem esses adaptadores acessam memória
|
|
342
|
+
pelas ferramentas MCP, sem promessa de contexto automático.
|
|
343
|
+
|
|
318
344
|
A captura respeita `automaticCapture`, `captureMode` e a opção de incluir resultados
|
|
319
345
|
de ferramentas. Uma falha ao consultar preferências desativa aquela captura.
|
|
320
346
|
Eventos privados não são enviados; campos sensíveis e marcadores privados são
|
package/dist/capabilities.js
CHANGED
|
@@ -6,7 +6,7 @@ export const CAPABILITIES = {
|
|
|
6
6
|
{ upstream: ['important_workflow'], status: 'available', tools: ['vault_go_workflow'] },
|
|
7
7
|
{ upstream: ['search', 'get_observations', 'timeline'], status: 'partial', tools: ['vault_go_search_index', 'vault_go_observations', 'vault_go_timeline', 'vault_go_feed'], difference: 'UUID identifiers; search requires query; feed provides cursor pagination. No offset/orderBy or automatic timeline anchor.' },
|
|
8
8
|
{ upstream: ['get_tool_uses'], status: 'available', tools: ['vault_go_tool_uses'], requires: 'Engine with POST /v1/events/batch. Historical output is unavailable unless it was captured. Private data is redacted.' },
|
|
9
|
-
{ upstream: ['session_start_context'], status: 'partial', tools: ['vault_go_session_start_context', 'vault_go_context', 'vault_go_file_context'], difference: '
|
|
9
|
+
{ upstream: ['session_start_context'], status: 'partial', tools: ['vault_go_session_start_context', 'vault_go_context', 'vault_go_file_context'], difference: 'Vault SessionStart output and configured budget; canonical Git worktree projects. Native hooks for Claude/Codex/Grok/Agy/Gemini; context-only OpenCode 1.x system plugin. No upstream multi-project chain renderer.' },
|
|
10
10
|
{ upstream: ['smart_search', 'smart_outline', 'smart_unfold'], status: 'partial', tools: ['vault_go_smart_search', 'vault_go_smart_outline', 'vault_go_smart_unfold'], difference: 'Local AST with bounded workspace reads; supported languages are returned by the tools, not all upstream grammars.' },
|
|
11
11
|
{ upstream: ['observation_add', 'observation_record_event', 'observation_search', 'observation_context'], status: 'available', tools: ['vault_go_remember', 'vault_go_event', 'vault_go_search', 'vault_go_context'], requires: 'generate:false requires updated engine and prevents duplicate server processing. summaryMode=off preserves raw memory without invoking a model.' },
|
|
12
12
|
{ upstream: ['automatic updates'], status: 'available', tools: ['vault_go_update_status', 'vault_go_update_check', 'vault_go_update_install', 'vault_go_update_configure'], requires: 'Local worker running, npm installed and official registry reachable. Daily stable-release check; idle activation and runtime rollback. Reconnect MCP clients to use new tool definitions.' },
|
|
@@ -16,7 +16,7 @@ export const CAPABILITIES = {
|
|
|
16
16
|
{ upstream: ['provider selection', 'automatic observation processing'], status: 'available', tools: ['vault_go_engines', 'vault_go_engine_select', 'vault_go_generate_context', 'vault_go_processing_status'], requires: 'Local worker running and selected provider authenticated. Codex Terra/Luna/Sol use ChatGPT subscription CLI. Retried persistence is idempotent.' },
|
|
17
17
|
{ upstream: ['privacy', 'capture modes', 'context settings'], status: 'partial', tools: ['vault_go_preferences', 'vault_go_preferences_update'], difference: 'Built-in capture modes and private-tag redaction; no custom mode inheritance/taxonomy. Preference update requires session authentication.' },
|
|
18
18
|
{ upstream: ['import', 'export', 'cloud sync'], status: 'partial', tools: ['vault_go_import', 'vault_go_export'], difference: 'Vault PostgreSQL persistence; upstream SQLite exports are a different format. Export is a bounded batch, not a complete backup. No offline replicated memory store.' },
|
|
19
|
-
{ upstream: ['Telegram', 'Grok awareness push', 'CCS Align', 'transcript watchers'], status: 'unsupported', tools: [], difference: 'Not ported. Native capture hooks
|
|
19
|
+
{ upstream: ['Telegram', 'Grok awareness push', 'CCS Align', 'transcript watchers'], status: 'unsupported', tools: [], difference: 'Not ported. Native capture hooks target Claude, Codex, Grok, Agy and Gemini. OpenCode has context-only integration; remaining clients use MCP.' },
|
|
20
20
|
{ upstream: ['development/report/presentation skills', 'babysit'], status: 'partial', tools: ['vault_go_modes', 'vault_go_mode'], difference: '20 original Vault workflow prompts, also exposed as tools for clients without prompt support. Requirements and missing runtimes are explicit. Loading a mode does not start background agents or execute external actions.' },
|
|
21
21
|
],
|
|
22
22
|
};
|
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@ import { authenticateWithApiKey, authenticateWithBrowser, validateExistingAuthen
|
|
|
2
2
|
import { vaultHome } from './config.js';
|
|
3
3
|
import { detectMcpClients, installMcpClient, MCP_CLIENTS, parseClientSelection, } from './installer.js';
|
|
4
4
|
import { promptSecret, promptText } from './prompt.js';
|
|
5
|
+
import { playInstallBanner, withInstallProgress } from './install-ui.js';
|
|
5
6
|
import { CONTEXT_ENGINE_IDS, ENGINE_NAMES, engineModels, isApiEngine, listContextEngines, loadEngineKey, parseEngineModel, saveEngineKey, selectContextEngine, selectEngineModel, selectedContextEngine, selectedEngineModel, } from './context-engines.js';
|
|
6
7
|
import { VaultCloudClient } from './cloud.js';
|
|
7
8
|
import { copy, resolveLocale } from './locale.js';
|
|
@@ -111,7 +112,7 @@ async function chooseClients(args, locale) {
|
|
|
111
112
|
async function chooseEngine(args, locale, home) {
|
|
112
113
|
const t = copy[locale];
|
|
113
114
|
const configured = optionValue(args, '--engine');
|
|
114
|
-
const engine = configured ? parseEngineChoice(configured) : await promptEngine(home, locale);
|
|
115
|
+
const engine = configured ? parseEngineChoice(configured) : await promptEngine(home, locale, { disabled: args.includes('--no-animation') });
|
|
115
116
|
selectContextEngine(home, engine);
|
|
116
117
|
process.stderr.write(` ✓ ${t.engineSaved}: ${ENGINE_NAMES[engine]}\n`);
|
|
117
118
|
await chooseModel(args, locale, home, engine);
|
|
@@ -132,11 +133,11 @@ async function chooseEngine(args, locale, home) {
|
|
|
132
133
|
}
|
|
133
134
|
return engine;
|
|
134
135
|
}
|
|
135
|
-
async function promptEngine(home, locale) {
|
|
136
|
+
async function promptEngine(home, locale, ui) {
|
|
136
137
|
const t = copy[locale];
|
|
137
138
|
process.stderr.write(` ${t.engineHelp}\n`);
|
|
138
139
|
try {
|
|
139
|
-
const listed = await listContextEngines(home, await managedStatus(home));
|
|
140
|
+
const listed = await withInstallProgress(t.detectingEngines, async () => listContextEngines(home, await managedStatus(home)), ui);
|
|
140
141
|
for (const [index, engine] of listed.engines.entries()) {
|
|
141
142
|
const mark = engine.available ? `● ${t.detected}` : `○ ${t.available}`;
|
|
142
143
|
process.stderr.write(` ${index + 1}. ${ENGINE_NAMES[engine.id].padEnd(18)} ${mark}\n`);
|
|
@@ -234,6 +235,7 @@ export async function runLocal(args) {
|
|
|
234
235
|
return 0;
|
|
235
236
|
}
|
|
236
237
|
if (command === 'install') {
|
|
238
|
+
await playInstallBanner({ disabled: args.includes('--no-animation') });
|
|
237
239
|
const result = await installLocal();
|
|
238
240
|
process.stderr.write(`${result.startup ? t.startup : t.manual}\n`);
|
|
239
241
|
}
|
|
@@ -258,7 +260,7 @@ export async function runSetup(args) {
|
|
|
258
260
|
if (!process.stdin.isTTY || !process.stderr.isTTY) {
|
|
259
261
|
throw new Error('O setup requer um terminal interativo.');
|
|
260
262
|
}
|
|
261
|
-
|
|
263
|
+
await playInstallBanner({ disabled: args.includes('--no-animation') });
|
|
262
264
|
const locale = await chooseLocale(args);
|
|
263
265
|
const t = copy[locale];
|
|
264
266
|
const local = localCopy[locale];
|
|
@@ -308,7 +310,7 @@ Uso:
|
|
|
308
310
|
bunx --bun vault-go@latest
|
|
309
311
|
Abre o login Vault no navegador e instala nos clientes detectados.
|
|
310
312
|
|
|
311
|
-
bunx --bun vault-go@latest setup [--clients lista] [--engine motor] [--model modelo] [--reauth]
|
|
313
|
+
bunx --bun vault-go@latest setup [--clients lista] [--engine motor] [--model modelo] [--reauth] [--no-animation]
|
|
312
314
|
Executa o assistente de autenticação e instalação. Pergunta o motor
|
|
313
315
|
(Vault AI, Claude, OpenRouter, Gemini ou ChatGPT/Codex) e o modelo
|
|
314
316
|
(Terra, Luna, Sol no Codex; Haiku, Sonnet, Opus no Claude).
|
|
@@ -342,6 +344,7 @@ Variáveis:
|
|
|
342
344
|
VAULT_GO_HOME Diretório de configuração (padrão: ~/.memvault)
|
|
343
345
|
MEMVAULT_CONFIG_DIR Diretório compartilhado com o agente MemVault
|
|
344
346
|
VAULT_GO_API_URL Endpoint alternativo da API Vault
|
|
347
|
+
VAULT_GO_NO_ANIMATION Desativa a animação de instalação (também: --no-animation)
|
|
345
348
|
VAULT_GO_OPENROUTER_API_KEY / OPENROUTER_API_KEY
|
|
346
349
|
VAULT_GO_GEMINI_API_KEY / GEMINI_API_KEY / GOOGLE_API_KEY
|
|
347
350
|
`;
|
package/dist/hook-install.d.ts
CHANGED
|
@@ -9,6 +9,11 @@ export declare const HOOK_EVENTS: {
|
|
|
9
9
|
export declare function quoteShell(value: string): string;
|
|
10
10
|
export declare function hookEntryPath(configHome?: string): string;
|
|
11
11
|
export declare function hookCommand(adapter: string, event: string, configHome?: string): string;
|
|
12
|
-
export type HookClient = "claude" | "codex" | "grok" | "agy";
|
|
12
|
+
export type HookClient = "claude" | "codex" | "grok" | "agy" | "gemini";
|
|
13
13
|
export declare function installClientHooks(client: HookClient, userHome?: string, configHome?: string): void;
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function installOpenCodePlugin(userHome?: string, configHome?: string): void;
|
|
15
|
+
/** Repairs only clients explicitly connected to Vault; never installs a CLI. */
|
|
16
|
+
export declare function repairAdditionalClientHooks(userHome?: string, configHome?: string, report?: typeof reportRepairWarning): void;
|
|
17
|
+
declare function reportRepairWarning(message: string): void;
|
|
18
|
+
export declare function repairClientHooks(userHome?: string, configHome?: string, report?: typeof reportRepairWarning): void;
|
|
19
|
+
export {};
|
package/dist/hook-install.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { vaultHome } from "./config.js";
|
|
6
6
|
function readJsonObject(path) {
|
|
7
7
|
if (!existsSync(path))
|
|
@@ -21,9 +21,12 @@ function readJsonObject(path) {
|
|
|
21
21
|
return parsed;
|
|
22
22
|
}
|
|
23
23
|
function atomicWriteJson(path, value) {
|
|
24
|
+
atomicWriteText(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
25
|
+
}
|
|
26
|
+
function atomicWriteText(path, text) {
|
|
24
27
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
25
28
|
const temporary = join(dirname(path), `.${process.pid}.${Date.now()}.tmp`);
|
|
26
|
-
writeFileSync(temporary,
|
|
29
|
+
writeFileSync(temporary, text, {
|
|
27
30
|
mode: 0o600,
|
|
28
31
|
});
|
|
29
32
|
renameSync(temporary, path);
|
|
@@ -37,6 +40,12 @@ export const HOOK_EVENTS = {
|
|
|
37
40
|
PostToolUse: "observation",
|
|
38
41
|
Stop: "summarize",
|
|
39
42
|
};
|
|
43
|
+
const GEMINI_EVENTS = {
|
|
44
|
+
SessionStart: "context",
|
|
45
|
+
BeforeAgent: "session-init",
|
|
46
|
+
AfterTool: "observation",
|
|
47
|
+
AfterAgent: "summarize",
|
|
48
|
+
};
|
|
40
49
|
export function quoteShell(value) {
|
|
41
50
|
return "'" + value.replaceAll("'", "'\\''") + "'";
|
|
42
51
|
}
|
|
@@ -51,7 +60,7 @@ export function hookCommand(adapter, event, configHome = vaultHome()) {
|
|
|
51
60
|
}
|
|
52
61
|
function hookManifest(adapter, configHome = vaultHome()) {
|
|
53
62
|
return {
|
|
54
|
-
hooks: Object.fromEntries(Object.entries(HOOK_EVENTS).map(([name, event]) => [
|
|
63
|
+
hooks: Object.fromEntries(Object.entries(adapter === "gemini" ? GEMINI_EVENTS : HOOK_EVENTS).map(([name, event]) => [
|
|
55
64
|
name,
|
|
56
65
|
[
|
|
57
66
|
{
|
|
@@ -59,10 +68,15 @@ function hookManifest(adapter, configHome = vaultHome()) {
|
|
|
59
68
|
{
|
|
60
69
|
type: "command",
|
|
61
70
|
command: hookCommand(adapter, event, configHome),
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
...(adapter === "gemini" ? {
|
|
72
|
+
timeout: event === "context" ? 60000 : 20000,
|
|
73
|
+
name: `vault-go:${event}`,
|
|
74
|
+
description: HOOK_OWNER,
|
|
75
|
+
} : {
|
|
76
|
+
timeout: event === "context" ? 60 : 20,
|
|
77
|
+
statusMessage: HOOK_OWNER,
|
|
78
|
+
}),
|
|
79
|
+
...(adapter !== "gemini" && (event === "observation" || event === "summarize")
|
|
66
80
|
? { async: true }
|
|
67
81
|
: {}),
|
|
68
82
|
},
|
|
@@ -100,7 +114,7 @@ export function installClientHooks(client, userHome = homedir(), configHome = va
|
|
|
100
114
|
atomicWriteJson(path, { ...config, "vault-go": agyHookManifest(configHome) });
|
|
101
115
|
return;
|
|
102
116
|
}
|
|
103
|
-
const path = join(userHome, client === "claude" ? ".claude/settings.json" : ".codex/hooks.json");
|
|
117
|
+
const path = join(userHome, client === "claude" ? ".claude/settings.json" : client === "gemini" ? ".gemini/settings.json" : ".codex/hooks.json");
|
|
104
118
|
const config = readJsonObject(path);
|
|
105
119
|
const hooks = {
|
|
106
120
|
...(config.hooks &&
|
|
@@ -110,7 +124,7 @@ export function installClientHooks(client, userHome = homedir(), configHome = va
|
|
|
110
124
|
: {}),
|
|
111
125
|
};
|
|
112
126
|
const desired = hookManifest(client, configHome).hooks;
|
|
113
|
-
for (const event of Object.keys(
|
|
127
|
+
for (const event of Object.keys(desired)) {
|
|
114
128
|
const current = hooks[event];
|
|
115
129
|
const retained = Array.isArray(current)
|
|
116
130
|
? current
|
|
@@ -121,7 +135,8 @@ export function installClientHooks(client, userHome = homedir(), configHome = va
|
|
|
121
135
|
return group;
|
|
122
136
|
const filtered = group.hooks.filter((hook) => !hook ||
|
|
123
137
|
typeof hook !== "object" ||
|
|
124
|
-
hook.statusMessage !== HOOK_OWNER
|
|
138
|
+
(hook.statusMessage !== HOOK_OWNER &&
|
|
139
|
+
!(client === "gemini" && hook.name === `vault-go:${GEMINI_EVENTS[event]}`)));
|
|
125
140
|
return { ...group, hooks: filtered };
|
|
126
141
|
})
|
|
127
142
|
.filter((group) => !group ||
|
|
@@ -133,15 +148,56 @@ export function installClientHooks(client, userHome = homedir(), configHome = va
|
|
|
133
148
|
}
|
|
134
149
|
atomicWriteJson(path, { ...config, hooks });
|
|
135
150
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
151
|
+
const OPENCODE_OWNER = "// Managed by vault-go: automatic memory context";
|
|
152
|
+
export function installOpenCodePlugin(userHome = homedir(), configHome = vaultHome()) {
|
|
153
|
+
const path = join(userHome, ".config", "opencode", "plugins", "vault-go-memory.js");
|
|
154
|
+
if (existsSync(path) && !readFileSync(path, "utf8").startsWith(OPENCODE_OWNER + "\n")) {
|
|
155
|
+
throw new Error(`Refusing to replace an unowned plugin: ${path}`);
|
|
156
|
+
}
|
|
157
|
+
const runtime = pathToFileURL(join(configHome, "local-runtime", "dist", "opencode-plugin.js")).href;
|
|
158
|
+
atomicWriteText(path, `${OPENCODE_OWNER}\nexport default async function(input) {\n try {\n const runtime = await import(${JSON.stringify(runtime)});\n return runtime.createOpenCodePlugin(input, { home: ${JSON.stringify(configHome)} });\n } catch { return {}; }\n}\n`);
|
|
159
|
+
}
|
|
160
|
+
/** Repairs only clients explicitly connected to Vault; never installs a CLI. */
|
|
161
|
+
export function repairAdditionalClientHooks(userHome = homedir(), configHome = vaultHome(), report = reportRepairWarning) {
|
|
162
|
+
repairClient("gemini", () => {
|
|
163
|
+
const gemini = readJsonObject(join(userHome, ".gemini", "settings.json"));
|
|
164
|
+
const server = gemini.mcpServers?.["vault-go"];
|
|
165
|
+
if (server && server.enabled !== false && server.disabled !== true) {
|
|
166
|
+
installClientHooks("gemini", userHome, configHome);
|
|
167
|
+
}
|
|
168
|
+
}, report);
|
|
169
|
+
repairClient("opencode", () => {
|
|
170
|
+
const opencode = readJsonObject(join(userHome, ".config", "opencode", "opencode.json"));
|
|
171
|
+
const server = opencode.mcp?.["vault-go"];
|
|
172
|
+
if (server && server.enabled !== false)
|
|
173
|
+
installOpenCodePlugin(userHome, configHome);
|
|
174
|
+
}, report);
|
|
175
|
+
}
|
|
176
|
+
function reportRepairWarning(message) {
|
|
177
|
+
process.stderr.write(message + "\n");
|
|
178
|
+
}
|
|
179
|
+
function repairClient(client, repair, report) {
|
|
180
|
+
try {
|
|
181
|
+
repair();
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Client configuration failures must not stop another client's repair or reveal its contents.
|
|
185
|
+
try {
|
|
186
|
+
report(`Vault Go: reparo de ${client} pendente; configuração existente preservada.`);
|
|
187
|
+
}
|
|
188
|
+
catch { }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
export function repairClientHooks(userHome = homedir(), configHome = vaultHome(), report = reportRepairWarning) {
|
|
192
|
+
repairAdditionalClientHooks(userHome, configHome, report);
|
|
193
|
+
repairClient("claude", () => installClientHooks("claude", userHome, configHome), report);
|
|
194
|
+
repairClient("codex", () => installClientHooks("codex", userHome, configHome), report);
|
|
139
195
|
if (existsSync(join(userHome, ".grok"))) {
|
|
140
|
-
installClientHooks("grok", userHome, configHome);
|
|
196
|
+
repairClient("grok", () => installClientHooks("grok", userHome, configHome), report);
|
|
141
197
|
}
|
|
142
198
|
if (existsSync(join(userHome, ".gemini", "config")) ||
|
|
143
199
|
existsSync(join(userHome, ".gemini", "antigravity")) ||
|
|
144
200
|
existsSync(join(userHome, ".gemini", "antigravity-cli"))) {
|
|
145
|
-
installClientHooks("agy", userHome, configHome);
|
|
201
|
+
repairClient("agy", () => installClientHooks("agy", userHome, configHome), report);
|
|
146
202
|
}
|
|
147
203
|
}
|
package/dist/hooks.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { basename, dirname, resolve } from "node:path";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
4
4
|
import { VaultCloudClient } from "./cloud.js";
|
|
5
5
|
import { vaultHome } from "./config.js";
|
|
6
6
|
import { enqueueGeneration } from "./hook-queue.js";
|
|
@@ -16,6 +16,14 @@ const record = (value) => value && typeof value === "object" && !Array.isArray(v
|
|
|
16
16
|
: {};
|
|
17
17
|
const string = (...values) => values.find((value) => typeof value === "string" && value.trim()) ||
|
|
18
18
|
"";
|
|
19
|
+
function resolvedPath(path) {
|
|
20
|
+
try {
|
|
21
|
+
return realpathSync(resolve(path));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return resolve(path);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
19
27
|
function root(cwd) {
|
|
20
28
|
const initial = resolve(cwd);
|
|
21
29
|
let dir = initial;
|
|
@@ -26,6 +34,43 @@ function root(cwd) {
|
|
|
26
34
|
}
|
|
27
35
|
return initial;
|
|
28
36
|
}
|
|
37
|
+
function gitMetadata(path) {
|
|
38
|
+
const file = statSync(path);
|
|
39
|
+
if (!file.isFile() || file.size > 4096)
|
|
40
|
+
return "";
|
|
41
|
+
return readFileSync(path, "utf8").trim();
|
|
42
|
+
}
|
|
43
|
+
function projectRoots(workspace) {
|
|
44
|
+
const fallback = [resolvedPath(workspace)];
|
|
45
|
+
try {
|
|
46
|
+
const marker = /^gitdir:\s*(.+)$/.exec(gitMetadata(resolve(workspace, ".git")));
|
|
47
|
+
if (!marker)
|
|
48
|
+
return fallback;
|
|
49
|
+
const gitdir = resolve(workspace, marker[1]);
|
|
50
|
+
const common = gitMetadata(resolve(gitdir, "commondir"));
|
|
51
|
+
// A submodule or separate git directory has no shared worktree metadata.
|
|
52
|
+
if (!common)
|
|
53
|
+
return fallback;
|
|
54
|
+
const shared = resolvedPath(resolve(gitdir, common));
|
|
55
|
+
if (basename(shared) !== ".git" || !statSync(shared).isDirectory())
|
|
56
|
+
return fallback;
|
|
57
|
+
return [...new Set([dirname(shared), ...fallback])];
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Keep existing projects usable if Git metadata is absent or unreadable.
|
|
61
|
+
return fallback;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function canonicalFile(file, workspace, project) {
|
|
65
|
+
const absolute = resolve(workspace, file);
|
|
66
|
+
for (const directory of [workspace, resolvedPath(workspace)]) {
|
|
67
|
+
const path = relative(directory, absolute);
|
|
68
|
+
if (path !== ".." && !path.startsWith("../") && !path.startsWith("..\\") && !isAbsolute(path)) {
|
|
69
|
+
return resolve(project, path);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return absolute;
|
|
73
|
+
}
|
|
29
74
|
export function redact(value) {
|
|
30
75
|
if (typeof value === "string") {
|
|
31
76
|
return value
|
|
@@ -58,7 +103,7 @@ export function normalizeHookInput(adapter, value) {
|
|
|
58
103
|
cwd: string(input.cwd, input.workspace, input.workspaceRoot, workspace),
|
|
59
104
|
tool_name: string(input.tool_name, input.toolName, toolCall.name),
|
|
60
105
|
tool_input: record(input.tool_input ?? input.toolInput ?? toolCall.args),
|
|
61
|
-
last_assistant_message: string(input.last_assistant_message, input.lastAssistantMessage, input.response, input.summary),
|
|
106
|
+
last_assistant_message: string(input.last_assistant_message, input.lastAssistantMessage, input.prompt_response, input.response, input.summary),
|
|
62
107
|
prompt: string(input.prompt, input.user_prompt, input.userPrompt),
|
|
63
108
|
};
|
|
64
109
|
}
|
|
@@ -73,19 +118,24 @@ export function hookOutput(event, context = "", adapter = "claude") {
|
|
|
73
118
|
return { decision: "allow" };
|
|
74
119
|
return {};
|
|
75
120
|
}
|
|
76
|
-
const result =
|
|
121
|
+
const result = adapter === "codex"
|
|
122
|
+
? {}
|
|
123
|
+
: { continue: true, suppressOutput: true };
|
|
77
124
|
const name = event === "context"
|
|
78
125
|
? "SessionStart"
|
|
79
126
|
: event === "file-context"
|
|
80
127
|
? "PreToolUse"
|
|
81
128
|
: event === "session-init"
|
|
82
|
-
? "UserPromptSubmit"
|
|
129
|
+
? adapter === "gemini" ? "BeforeAgent" : "UserPromptSubmit"
|
|
83
130
|
: "";
|
|
84
131
|
if (name && context) {
|
|
85
132
|
result.hookSpecificOutput = {
|
|
86
133
|
hookEventName: name,
|
|
87
134
|
additionalContext: context,
|
|
88
135
|
};
|
|
136
|
+
if (event === "context" && ["claude", "codex", "gemini"].includes(adapter)) {
|
|
137
|
+
result.systemMessage = "Vault Memory: contexto do projeto carregado.";
|
|
138
|
+
}
|
|
89
139
|
}
|
|
90
140
|
return result;
|
|
91
141
|
}
|
|
@@ -130,15 +180,17 @@ export async function executeHook(adapter, event, value, cloud = new VaultCloudC
|
|
|
130
180
|
const sessionId = string(input.session_id);
|
|
131
181
|
if (!reading && !sessionId)
|
|
132
182
|
return hookOutput(event, "", adapter);
|
|
133
|
-
const
|
|
183
|
+
const workspace = root(string(input.cwd, input.workspace) || process.cwd());
|
|
184
|
+
const roots = projectRoots(workspace);
|
|
134
185
|
const projects = await cloud.projects();
|
|
135
186
|
if (!Array.isArray(projects))
|
|
136
187
|
throw new Error("Resposta de projetos inválida");
|
|
137
|
-
let project = projects.find((item) => item &&
|
|
188
|
+
let project = roots.map((cwd) => projects.find((item) => item &&
|
|
138
189
|
typeof item === "object" &&
|
|
139
190
|
typeof item.rootPath === "string" &&
|
|
140
191
|
Boolean(String(item.rootPath).trim()) &&
|
|
141
|
-
|
|
192
|
+
resolvedPath(String(item.rootPath)) === cwd)).find(Boolean);
|
|
193
|
+
const cwd = project ? resolvedPath(String(project.rootPath)) : roots[0];
|
|
142
194
|
if (!project && reading)
|
|
143
195
|
return hookOutput(event, "", adapter);
|
|
144
196
|
if (!project) {
|
|
@@ -154,11 +206,15 @@ export async function executeHook(adapter, event, value, cloud = new VaultCloudC
|
|
|
154
206
|
limit: boundedPreference(prefs.contextItems, 20, 1, 100),
|
|
155
207
|
maxChars: boundedPreference(prefs.contextMaxChars, 24_000, 1_000, 100_000),
|
|
156
208
|
};
|
|
209
|
+
const projectFile = file ? canonicalFile(file, workspace, cwd) : "";
|
|
210
|
+
const files = file
|
|
211
|
+
? [...new Set([file, ...(resolvedPath(workspace) !== cwd ? [projectFile] : [])])]
|
|
212
|
+
: [];
|
|
157
213
|
if (reading) {
|
|
158
214
|
const result = record(event === "file-context"
|
|
159
215
|
? await cloud.fileContext({
|
|
160
216
|
...limits,
|
|
161
|
-
paths: [...new Set([file, resolve(
|
|
217
|
+
paths: [...new Set([file, resolve(workspace, file), projectFile])],
|
|
162
218
|
})
|
|
163
219
|
: await cloud.context(limits));
|
|
164
220
|
return hookOutput(event, string(result.context).slice(0, limits.maxChars), adapter);
|
|
@@ -218,8 +274,8 @@ export async function executeHook(adapter, event, value, cloud = new VaultCloudC
|
|
|
218
274
|
title: toolName ||
|
|
219
275
|
(event === "summarize" ? "Resumo da sessão" : "Solicitação do usuário"),
|
|
220
276
|
content,
|
|
221
|
-
filesRead: !modifies
|
|
222
|
-
filesModified: modifies
|
|
277
|
+
filesRead: !modifies ? files : [],
|
|
278
|
+
filesModified: modifies ? files : [],
|
|
223
279
|
},
|
|
224
280
|
occurredAtEpoch: Date.now(),
|
|
225
281
|
});
|
|
@@ -233,8 +289,8 @@ export async function executeHook(adapter, event, value, cloud = new VaultCloudC
|
|
|
233
289
|
(event === "summarize" ? "Resumo da sessão" : "Solicitação do usuário"),
|
|
234
290
|
kind: event === "summarize" ? "summary" : event === "session-init" ? "prompt" : "observation",
|
|
235
291
|
maxChars: boundedPreference(prefs.summaryMaxChars, 4_000, 256, 16_000),
|
|
236
|
-
filesRead: !modifies
|
|
237
|
-
filesModified: modifies
|
|
292
|
+
filesRead: !modifies ? files : [],
|
|
293
|
+
filesModified: modifies ? files : [],
|
|
238
294
|
});
|
|
239
295
|
}
|
|
240
296
|
if (event === "summarize")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type Terminal = Pick<NodeJS.WriteStream, 'write' | 'isTTY' | 'columns' | 'rows'> & {
|
|
2
|
+
on(event: 'resize', listener: () => void): unknown;
|
|
3
|
+
off(event: 'resize', listener: () => void): unknown;
|
|
4
|
+
};
|
|
5
|
+
export interface InstallUiOptions {
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
stream?: Terminal;
|
|
8
|
+
env?: NodeJS.ProcessEnv;
|
|
9
|
+
}
|
|
10
|
+
/** Original Vault artwork: a turning vault dial and a left-to-right wordmark reveal. */
|
|
11
|
+
export declare function playInstallBanner(options?: InstallUiOptions): Promise<void>;
|
|
12
|
+
/** Use only around silent operations, ending before any prompt or result is printed. */
|
|
13
|
+
export declare function withInstallProgress<T>(label: string, operation: () => Promise<T>, options?: InstallUiOptions): Promise<T>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
+
const RESET = '\x1b[0m';
|
|
3
|
+
const CLEAR_LINE = '\r\x1b[2K';
|
|
4
|
+
const FRAMES = 24;
|
|
5
|
+
const WORDMARK = [
|
|
6
|
+
'V V AAA U U L TTTTT GGGG OOO ',
|
|
7
|
+
'V V A A U U L T G O O',
|
|
8
|
+
'V V AAAAA U U L T G GG O O',
|
|
9
|
+
' V V A A U U L T G G O O',
|
|
10
|
+
' V A A UUU LLLLL T GGG OOO ',
|
|
11
|
+
];
|
|
12
|
+
function terminal(options) {
|
|
13
|
+
// stdout belongs to the MCP transport, including when redirected to a file.
|
|
14
|
+
return options.stream ?? process.stderr;
|
|
15
|
+
}
|
|
16
|
+
function animate(options) {
|
|
17
|
+
const env = options.env ?? process.env;
|
|
18
|
+
return terminal(options).isTTY === true && !options.disabled && !env.CI &&
|
|
19
|
+
env.TERM !== 'dumb' && env.NO_COLOR === undefined && !env.VAULT_GO_NO_ANIMATION;
|
|
20
|
+
}
|
|
21
|
+
function columns(stream) {
|
|
22
|
+
// Leave the final column unused so repainting cannot wrap a line.
|
|
23
|
+
return Math.max(1, (stream.columns || 80) - 1);
|
|
24
|
+
}
|
|
25
|
+
function bannerLines(stream, frame) {
|
|
26
|
+
const width = columns(stream);
|
|
27
|
+
if (width < 46 || (stream.rows || 24) < 16) {
|
|
28
|
+
return [` ${['◇', '◈', '◆', '◈'][Math.floor(frame / 3) % 4]} VAULT GO`.slice(0, width)];
|
|
29
|
+
}
|
|
30
|
+
const wheel = ['+', '×', '✳', '◆'][Math.floor(frame / 3) % 4];
|
|
31
|
+
const art = [
|
|
32
|
+
' .-----------------.',
|
|
33
|
+
' /_________________/|',
|
|
34
|
+
' | .-----------. ||',
|
|
35
|
+
` | | ${wheel} | ||`,
|
|
36
|
+
' | | VAULT | |/',
|
|
37
|
+
" |__'-----------'__|",
|
|
38
|
+
'',
|
|
39
|
+
...WORDMARK.map(row => ` ${row}`),
|
|
40
|
+
];
|
|
41
|
+
const revealed = Math.ceil((frame + 1) / FRAMES * 44);
|
|
42
|
+
return art.map(row => row.slice(0, revealed));
|
|
43
|
+
}
|
|
44
|
+
/** Original Vault artwork: a turning vault dial and a left-to-right wordmark reveal. */
|
|
45
|
+
export async function playInstallBanner(options = {}) {
|
|
46
|
+
const stream = terminal(options);
|
|
47
|
+
if (!animate(options)) {
|
|
48
|
+
stream.write('\n' + ' ◆ VAULT GO'.slice(0, columns(stream)) + '\n\n');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
let resized = false;
|
|
52
|
+
const onResize = () => { resized = true; };
|
|
53
|
+
stream.on('resize', onResize);
|
|
54
|
+
stream.write('\n');
|
|
55
|
+
try {
|
|
56
|
+
for (let frame = 0; frame < FRAMES; frame++) {
|
|
57
|
+
// Stop repainting after a resize: previously drawn lines may have reflowed.
|
|
58
|
+
if (resized)
|
|
59
|
+
break;
|
|
60
|
+
const lines = bannerLines(stream, frame);
|
|
61
|
+
if (frame > 0)
|
|
62
|
+
stream.write(`\x1b[${lines.length}F`);
|
|
63
|
+
stream.write(lines.map((line, row) => `${CLEAR_LINE}${row < 7 ? '\x1b[96m' : '\x1b[95m'}${line}${RESET}\n`).join(''));
|
|
64
|
+
if (frame < FRAMES - 1)
|
|
65
|
+
await delay(45);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
stream.off('resize', onResize);
|
|
70
|
+
// Never hide the cursor or enter an alternate screen: interruption stays safe.
|
|
71
|
+
stream.write('\n');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Use only around silent operations, ending before any prompt or result is printed. */
|
|
75
|
+
export async function withInstallProgress(label, operation, options = {}) {
|
|
76
|
+
if (!animate(options))
|
|
77
|
+
return operation();
|
|
78
|
+
const stream = terminal(options);
|
|
79
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
80
|
+
let frame = 0;
|
|
81
|
+
const draw = () => {
|
|
82
|
+
const line = ` ${frames[frame++ % frames.length]} ${label}`.slice(0, columns(stream));
|
|
83
|
+
stream.write(`${CLEAR_LINE}\x1b[96m${line}${RESET}`);
|
|
84
|
+
};
|
|
85
|
+
draw();
|
|
86
|
+
const timer = setInterval(draw, 80);
|
|
87
|
+
timer.unref();
|
|
88
|
+
try {
|
|
89
|
+
return await operation();
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
clearInterval(timer);
|
|
93
|
+
stream.write(CLEAR_LINE);
|
|
94
|
+
}
|
|
95
|
+
}
|
package/dist/installer.js
CHANGED
|
@@ -4,7 +4,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
import { vaultHome } from './config.js';
|
|
6
6
|
import { stageLocalRuntime } from './local-install.js';
|
|
7
|
-
import { installClientHooks } from './hook-install.js';
|
|
7
|
+
import { installClientHooks, installOpenCodePlugin } from './hook-install.js';
|
|
8
8
|
export const MCP_SERVER_NAME = 'vault-go';
|
|
9
9
|
export const MCP_SERVER_COMMAND = ['bunx', '--bun', 'vault-go@latest', 'serve'];
|
|
10
10
|
export const MCP_CLIENTS = [
|
|
@@ -199,6 +199,11 @@ export function installMcpClient(client, options = {}) {
|
|
|
199
199
|
if (client === 'gemini') {
|
|
200
200
|
const path = join(home, '.gemini', 'settings.json');
|
|
201
201
|
mergeServer(path);
|
|
202
|
+
try {
|
|
203
|
+
stageLocalRuntime(vaultHome());
|
|
204
|
+
}
|
|
205
|
+
catch { /* incomplete installs still write hooks */ }
|
|
206
|
+
installClientHooks('gemini', home);
|
|
202
207
|
return { client, status: 'installed', destination: path };
|
|
203
208
|
}
|
|
204
209
|
if (client === 'vault-desktop') {
|
|
@@ -221,6 +226,11 @@ export function installMcpClient(client, options = {}) {
|
|
|
221
226
|
else
|
|
222
227
|
path = join(home, '.config', 'opencode', 'opencode.json');
|
|
223
228
|
if (client === 'opencode') {
|
|
229
|
+
try {
|
|
230
|
+
stageLocalRuntime(vaultHome());
|
|
231
|
+
}
|
|
232
|
+
catch { /* plugin becomes active when runtime is available */ }
|
|
233
|
+
installOpenCodePlugin(home);
|
|
224
234
|
mergeServer(path, 'mcp', {
|
|
225
235
|
type: 'local',
|
|
226
236
|
command: [...MCP_SERVER_COMMAND],
|
package/dist/locale.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare const copy: {
|
|
|
12
12
|
connected: string;
|
|
13
13
|
key: string;
|
|
14
14
|
engine: string;
|
|
15
|
+
detectingEngines: string;
|
|
15
16
|
engineHelp: string;
|
|
16
17
|
engineSaved: string;
|
|
17
18
|
engineKey: string;
|
|
@@ -51,6 +52,7 @@ export declare const copy: {
|
|
|
51
52
|
connected: string;
|
|
52
53
|
key: string;
|
|
53
54
|
engine: string;
|
|
55
|
+
detectingEngines: string;
|
|
54
56
|
engineHelp: string;
|
|
55
57
|
engineSaved: string;
|
|
56
58
|
engineKey: string;
|
|
@@ -90,6 +92,7 @@ export declare const copy: {
|
|
|
90
92
|
connected: string;
|
|
91
93
|
key: string;
|
|
92
94
|
engine: string;
|
|
95
|
+
detectingEngines: string;
|
|
93
96
|
engineHelp: string;
|
|
94
97
|
engineSaved: string;
|
|
95
98
|
engineKey: string;
|
package/dist/locale.js
CHANGED
|
@@ -21,6 +21,7 @@ export const copy = {
|
|
|
21
21
|
connected: "Conta conectada",
|
|
22
22
|
key: "Chave de API",
|
|
23
23
|
engine: "Escolher motor de contexto",
|
|
24
|
+
detectingEngines: "Verificando motores disponíveis…",
|
|
24
25
|
engineHelp: "Como o Vault prepara memórias: Claude, OpenRouter, Gemini ou Vault AI.",
|
|
25
26
|
engineSaved: "Motor selecionado",
|
|
26
27
|
engineKey: "Cole a chave (não será exibida). Enter para pular",
|
|
@@ -60,6 +61,7 @@ export const copy = {
|
|
|
60
61
|
connected: "Account connected",
|
|
61
62
|
key: "API key",
|
|
62
63
|
engine: "Choose context engine",
|
|
64
|
+
detectingEngines: "Checking available engines…",
|
|
63
65
|
engineHelp: "How Vault prepares memories: Claude, OpenRouter, Gemini, or Vault AI.",
|
|
64
66
|
engineSaved: "Engine selected",
|
|
65
67
|
engineKey: "Paste the API key (it will not be shown). Enter to skip",
|
|
@@ -99,6 +101,7 @@ export const copy = {
|
|
|
99
101
|
connected: "Cuenta conectada",
|
|
100
102
|
key: "Clave de API",
|
|
101
103
|
engine: "Elegir motor de contexto",
|
|
104
|
+
detectingEngines: "Comprobando motores disponibles…",
|
|
102
105
|
engineHelp: "Cómo Vault prepara memorias: Claude, OpenRouter, Gemini o Vault AI.",
|
|
103
106
|
engineSaved: "Motor seleccionado",
|
|
104
107
|
engineKey: "Pega la clave (no se mostrará). Enter para omitir",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type Options = {
|
|
2
|
+
home?: string;
|
|
3
|
+
fetch?: typeof fetch;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
};
|
|
6
|
+
/** OpenCode 1.x plugin contract: context only; capture stays with the user's existing integrations. */
|
|
7
|
+
export declare function createOpenCodePlugin(input: {
|
|
8
|
+
directory: string;
|
|
9
|
+
}, options?: Options): {
|
|
10
|
+
'experimental.chat.system.transform': (request: {
|
|
11
|
+
sessionID?: string;
|
|
12
|
+
}, output: {
|
|
13
|
+
system: string[];
|
|
14
|
+
}) => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { loadConfig, loadTokens, vaultHome } from './config.js';
|
|
3
|
+
import { VaultCloudClient } from './cloud.js';
|
|
4
|
+
import { executeHook } from './hooks.js';
|
|
5
|
+
function owner(home) {
|
|
6
|
+
return createHash('sha256').update(JSON.stringify([
|
|
7
|
+
loadConfig(home).apiUrl,
|
|
8
|
+
process.env.VAULT_GO_TOKEN || loadTokens(home)?.accessToken || '',
|
|
9
|
+
])).digest('hex');
|
|
10
|
+
}
|
|
11
|
+
/** OpenCode 1.x plugin contract: context only; capture stays with the user's existing integrations. */
|
|
12
|
+
export function createOpenCodePlugin(input, options = {}) {
|
|
13
|
+
const home = options.home ?? vaultHome();
|
|
14
|
+
const pending = new Map();
|
|
15
|
+
const empty = () => ({ context: '', owner: '' });
|
|
16
|
+
async function readContext(sessionID) {
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
19
|
+
const boundedFetch = (async (url, init) => {
|
|
20
|
+
controller.signal.throwIfAborted();
|
|
21
|
+
return fetcher(url, {
|
|
22
|
+
...init,
|
|
23
|
+
signal: init?.signal ? AbortSignal.any([controller.signal, init.signal]) : controller.signal,
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
const cloud = new VaultCloudClient(home, boundedFetch, false);
|
|
27
|
+
let timer;
|
|
28
|
+
const load = async () => {
|
|
29
|
+
// Authenticate/refresh before taking the ownership snapshot. No model or capture writes run here.
|
|
30
|
+
const preferences = await cloud.preferences();
|
|
31
|
+
controller.signal.throwIfAborted();
|
|
32
|
+
const requestOwner = owner(home);
|
|
33
|
+
cloud.preferences = async () => preferences;
|
|
34
|
+
const output = await executeHook('opencode', 'context', { cwd: input.directory, session_id: sessionID }, cloud, home);
|
|
35
|
+
const context = output.hookSpecificOutput?.additionalContext;
|
|
36
|
+
if (controller.signal.aborted || requestOwner !== owner(home) || typeof context !== 'string')
|
|
37
|
+
return empty();
|
|
38
|
+
return { context, owner: requestOwner };
|
|
39
|
+
};
|
|
40
|
+
try {
|
|
41
|
+
return await Promise.race([
|
|
42
|
+
load(),
|
|
43
|
+
new Promise(resolve => {
|
|
44
|
+
timer = setTimeout(() => { controller.abort(); resolve(empty()); }, options.timeoutMs ?? 3000);
|
|
45
|
+
}),
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return empty();
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
if (timer)
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
controller.abort();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
'experimental.chat.system.transform': async (request, output) => {
|
|
59
|
+
if (!request.sessionID || !input.directory)
|
|
60
|
+
return;
|
|
61
|
+
const key = JSON.stringify([request.sessionID, owner(home)]);
|
|
62
|
+
let loading = pending.get(key);
|
|
63
|
+
if (!loading) {
|
|
64
|
+
loading = readContext(request.sessionID).finally(() => pending.delete(key));
|
|
65
|
+
pending.set(key, loading);
|
|
66
|
+
}
|
|
67
|
+
const result = await loading;
|
|
68
|
+
if (result.context && result.owner === owner(home) && !output.system.includes(result.context)) {
|
|
69
|
+
output.system.push(result.context);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
package/dist/update-control.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { type UpdateState } from './updater.js';
|
|
|
3
3
|
export declare function activatePreparedUpdate(home: string, state: UpdateState, operations?: {
|
|
4
4
|
stop: (home: string) => Promise<boolean>;
|
|
5
5
|
start: (home: string) => Promise<LocalHealth>;
|
|
6
|
+
repair?: (home: string) => void;
|
|
7
|
+
warn?: (message: string) => void;
|
|
6
8
|
}, options?: {
|
|
7
9
|
automatic?: boolean;
|
|
8
10
|
}): Promise<LocalHealth>;
|
package/dist/update-control.js
CHANGED
|
@@ -2,10 +2,12 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { existsSync, openSync, closeSync, readFileSync, realpathSync, renameSync, rmSync, lstatSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { join, relative, isAbsolute } from 'node:path';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
5
6
|
import { stageLocalRuntime, startLocal, stopLocal } from './local-install.js';
|
|
6
7
|
import { installUpdate, updateStatus } from './updater.js';
|
|
7
8
|
import { vaultHome } from './config.js';
|
|
8
9
|
import { generationQueueStatus } from './hook-queue.js';
|
|
10
|
+
import { repairAdditionalClientHooks } from './hook-install.js';
|
|
9
11
|
function preparedPackage(home, state) {
|
|
10
12
|
if (state.status !== 'ready' || !state.packageRoot || !state.latest)
|
|
11
13
|
throw new Error('No validated update is ready.');
|
|
@@ -18,8 +20,20 @@ function preparedPackage(home, state) {
|
|
|
18
20
|
throw new Error('Invalid prepared update.');
|
|
19
21
|
return root;
|
|
20
22
|
}
|
|
21
|
-
export async function activatePreparedUpdate(home, state, operations = { stop: stopLocal, start: startLocal }, options = {}) {
|
|
23
|
+
export async function activatePreparedUpdate(home, state, operations = { stop: stopLocal, start: startLocal, repair: home => repairAdditionalClientHooks(homedir(), home) }, options = {}) {
|
|
22
24
|
const release = await acquireActivationLock(home);
|
|
25
|
+
const repair = () => {
|
|
26
|
+
try {
|
|
27
|
+
operations.repair?.(home);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
const message = 'Vault Go: runtime atualizado; reparo dos hooks dos clientes pendente.';
|
|
31
|
+
try {
|
|
32
|
+
(operations.warn ?? (warning => { process.stderr.write(warning + '\n'); }))(message);
|
|
33
|
+
}
|
|
34
|
+
catch { }
|
|
35
|
+
}
|
|
36
|
+
};
|
|
23
37
|
try {
|
|
24
38
|
if (options.automatic && !updateStatus(home).enabled)
|
|
25
39
|
throw new Error('Automatic updates are disabled.');
|
|
@@ -29,11 +43,14 @@ export async function activatePreparedUpdate(home, state, operations = { stop: s
|
|
|
29
43
|
const health = await operations.start(home);
|
|
30
44
|
if (health.version !== state.latest)
|
|
31
45
|
throw new Error('Active worker version mismatch.');
|
|
46
|
+
repair();
|
|
32
47
|
return health;
|
|
33
48
|
}
|
|
34
49
|
if (active && compareVersions(active, state.latest) > 0)
|
|
35
50
|
throw new Error('Refusing to activate an older runtime.');
|
|
36
|
-
|
|
51
|
+
const health = await replaceRuntime(home, state, source, operations);
|
|
52
|
+
repair();
|
|
53
|
+
return health;
|
|
37
54
|
}
|
|
38
55
|
finally {
|
|
39
56
|
release();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vault-go",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Servidor MCP universal com autenticação e instalação multi-cliente para a plataforma Vault.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
|
+
"dist/opencode-plugin.js",
|
|
24
|
+
"dist/opencode-plugin.d.ts",
|
|
23
25
|
"dist/activity.js",
|
|
24
26
|
"dist/activity.d.ts",
|
|
25
27
|
"dist/auth.js",
|
|
@@ -46,6 +48,8 @@
|
|
|
46
48
|
"dist/index.d.ts",
|
|
47
49
|
"dist/installer.js",
|
|
48
50
|
"dist/installer.d.ts",
|
|
51
|
+
"dist/install-ui.js",
|
|
52
|
+
"dist/install-ui.d.ts",
|
|
49
53
|
"dist/local-dashboard.js",
|
|
50
54
|
"dist/local-dashboard.d.ts",
|
|
51
55
|
"dist/local-device.js",
|