vault-go 0.16.0 → 0.17.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 +4 -1
- package/dist/cli.js +2 -2
- package/dist/hook-install.d.ts +14 -0
- package/dist/hook-install.js +148 -0
- package/dist/hook-runner.d.ts +2 -0
- package/dist/hook-runner.js +9 -0
- package/dist/hooks.d.ts +2 -1
- package/dist/hooks.js +44 -17
- package/dist/installer.d.ts +1 -4
- package/dist/installer.js +78 -71
- package/dist/local-install.js +10 -4
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -76,6 +76,9 @@ Clientes suportados:
|
|
|
76
76
|
|
|
77
77
|
- Codex e ChatGPT Desktop local;
|
|
78
78
|
- Claude Code e Claude Desktop;
|
|
79
|
+
- Grok (config.toml nativo e hooks em `~/.grok/hooks`);
|
|
80
|
+
- Agy / Antigravity CLI e IDE;
|
|
81
|
+
- Gemini CLI;
|
|
79
82
|
- Cursor e Windsurf;
|
|
80
83
|
- VS Code com GitHub Copilot;
|
|
81
84
|
- GitHub Copilot CLI;
|
|
@@ -170,7 +173,7 @@ limitados.
|
|
|
170
173
|
|
|
171
174
|
O seletor de motor segue o mesmo modelo do claude-mem: Claude (CLI da assinatura), OpenRouter (chave), Gemini (chave) ou o Vault AI Resume da conta. Grave chaves com `vault-go engine key openrouter|gemini` ou no painel local; as ferramentas MCP não aceitam segredos.
|
|
172
175
|
|
|
173
|
-
Na instalação, o Vault Go registra hooks no Claude Code e no
|
|
176
|
+
Na instalação, o Vault Go registra hooks no Claude Code, no Codex, no Grok e no Agy. Eles injetam contexto no início da sessão, capturam prompts e ferramentas, e o serviço local (`http://localhost:38850`) gera memórias com o motor escolhido — o mesmo ciclo do claude-mem, persistindo no Vault.
|
|
174
177
|
|
|
175
178
|
O recurso `vault-go://status` fornece o resumo sanitizado em JSON.
|
|
176
179
|
|
package/dist/cli.js
CHANGED
|
@@ -324,8 +324,8 @@ Uso:
|
|
|
324
324
|
Escolhe o motor e o modelo de contexto. No Claude: haiku, sonnet ou opus.
|
|
325
325
|
|
|
326
326
|
vault-go hook <adaptador> <evento>
|
|
327
|
-
Usado pelos hooks do Claude
|
|
328
|
-
e enfileira geração com o motor escolhido.
|
|
327
|
+
Usado pelos hooks do Claude, Codex, Grok e Agy: injeta contexto,
|
|
328
|
+
captura atividade e enfileira geração com o motor escolhido.
|
|
329
329
|
|
|
330
330
|
--lang pt|en|es
|
|
331
331
|
Idioma do assistente (padrão: idioma do sistema).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const HOOK_OWNER = "Vault Go: captura e contexto";
|
|
2
|
+
export declare const HOOK_EVENTS: {
|
|
3
|
+
readonly SessionStart: "context";
|
|
4
|
+
readonly UserPromptSubmit: "session-init";
|
|
5
|
+
readonly PreToolUse: "file-context";
|
|
6
|
+
readonly PostToolUse: "observation";
|
|
7
|
+
readonly Stop: "summarize";
|
|
8
|
+
};
|
|
9
|
+
export declare function quoteShell(value: string): string;
|
|
10
|
+
export declare function hookEntryPath(configHome?: string): string;
|
|
11
|
+
export declare function hookCommand(adapter: string, event: string, configHome?: string): string;
|
|
12
|
+
export type HookClient = "claude" | "codex" | "grok" | "agy";
|
|
13
|
+
export declare function installClientHooks(client: HookClient, userHome?: string, configHome?: string): void;
|
|
14
|
+
export declare function repairClientHooks(userHome?: string, configHome?: string): void;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { vaultHome } from "./config.js";
|
|
6
|
+
function readJsonObject(path) {
|
|
7
|
+
if (!existsSync(path))
|
|
8
|
+
return {};
|
|
9
|
+
let parsed;
|
|
10
|
+
try {
|
|
11
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
throw new Error(`JSON inválido em ${path}; o arquivo não foi alterado.`, {
|
|
15
|
+
cause: error,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
19
|
+
throw new Error(`Configuração inválida em ${path}; o arquivo não foi alterado.`);
|
|
20
|
+
}
|
|
21
|
+
return parsed;
|
|
22
|
+
}
|
|
23
|
+
function atomicWriteJson(path, value) {
|
|
24
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
25
|
+
const temporary = join(dirname(path), `.${process.pid}.${Date.now()}.tmp`);
|
|
26
|
+
writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, {
|
|
27
|
+
mode: 0o600,
|
|
28
|
+
});
|
|
29
|
+
renameSync(temporary, path);
|
|
30
|
+
chmodSync(path, 0o600);
|
|
31
|
+
}
|
|
32
|
+
export const HOOK_OWNER = "Vault Go: captura e contexto";
|
|
33
|
+
export const HOOK_EVENTS = {
|
|
34
|
+
SessionStart: "context",
|
|
35
|
+
UserPromptSubmit: "session-init",
|
|
36
|
+
PreToolUse: "file-context",
|
|
37
|
+
PostToolUse: "observation",
|
|
38
|
+
Stop: "summarize",
|
|
39
|
+
};
|
|
40
|
+
export function quoteShell(value) {
|
|
41
|
+
return "'" + value.replaceAll("'", "'\\''") + "'";
|
|
42
|
+
}
|
|
43
|
+
export function hookEntryPath(configHome = vaultHome()) {
|
|
44
|
+
const staged = join(configHome, "local-runtime", "dist", "hook-runner.js");
|
|
45
|
+
if (existsSync(staged))
|
|
46
|
+
return staged;
|
|
47
|
+
return join(dirname(fileURLToPath(import.meta.url)), "hook-runner.js");
|
|
48
|
+
}
|
|
49
|
+
export function hookCommand(adapter, event, configHome = vaultHome()) {
|
|
50
|
+
return `${quoteShell(process.execPath)} ${quoteShell(hookEntryPath(configHome))} ${adapter} ${event}`;
|
|
51
|
+
}
|
|
52
|
+
function hookManifest(adapter, configHome = vaultHome()) {
|
|
53
|
+
return {
|
|
54
|
+
hooks: Object.fromEntries(Object.entries(HOOK_EVENTS).map(([name, event]) => [
|
|
55
|
+
name,
|
|
56
|
+
[
|
|
57
|
+
{
|
|
58
|
+
hooks: [
|
|
59
|
+
{
|
|
60
|
+
type: "command",
|
|
61
|
+
command: hookCommand(adapter, event, configHome),
|
|
62
|
+
timeout: event === "context" ? 60 : 20,
|
|
63
|
+
statusMessage: HOOK_OWNER,
|
|
64
|
+
...(event === "observation" ||
|
|
65
|
+
event === "summarize" ||
|
|
66
|
+
event === "file-context"
|
|
67
|
+
? { async: true }
|
|
68
|
+
: {}),
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
])),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function agyHandler(event, configHome = vaultHome()) {
|
|
77
|
+
return {
|
|
78
|
+
type: "command",
|
|
79
|
+
command: hookCommand("agy", event, configHome),
|
|
80
|
+
timeout: event === "context" ? 60 : 20,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function agyHookManifest(configHome = vaultHome()) {
|
|
84
|
+
return {
|
|
85
|
+
PreInvocation: [agyHandler("context", configHome)],
|
|
86
|
+
PreToolUse: [{ matcher: "*", hooks: [agyHandler("file-context", configHome)] }],
|
|
87
|
+
PostToolUse: [
|
|
88
|
+
{ matcher: "*", hooks: [agyHandler("observation", configHome)] },
|
|
89
|
+
],
|
|
90
|
+
Stop: [agyHandler("summarize", configHome)],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export function installClientHooks(client, userHome = homedir(), configHome = vaultHome()) {
|
|
94
|
+
if (client === "grok") {
|
|
95
|
+
atomicWriteJson(join(userHome, ".grok", "hooks", "vault-go.json"), hookManifest("grok", configHome));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (client === "agy") {
|
|
99
|
+
const path = join(userHome, ".gemini", "config", "hooks.json");
|
|
100
|
+
const config = readJsonObject(path);
|
|
101
|
+
atomicWriteJson(path, { ...config, "vault-go": agyHookManifest(configHome) });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const path = join(userHome, client === "claude" ? ".claude/settings.json" : ".codex/hooks.json");
|
|
105
|
+
const config = readJsonObject(path);
|
|
106
|
+
const hooks = {
|
|
107
|
+
...(config.hooks &&
|
|
108
|
+
typeof config.hooks === "object" &&
|
|
109
|
+
!Array.isArray(config.hooks)
|
|
110
|
+
? config.hooks
|
|
111
|
+
: {}),
|
|
112
|
+
};
|
|
113
|
+
const desired = hookManifest(client, configHome).hooks;
|
|
114
|
+
for (const event of Object.keys(HOOK_EVENTS)) {
|
|
115
|
+
const current = hooks[event];
|
|
116
|
+
const retained = Array.isArray(current)
|
|
117
|
+
? current
|
|
118
|
+
.map((group) => {
|
|
119
|
+
if (!group ||
|
|
120
|
+
typeof group !== "object" ||
|
|
121
|
+
!Array.isArray(group.hooks))
|
|
122
|
+
return group;
|
|
123
|
+
const filtered = group.hooks.filter((hook) => !hook ||
|
|
124
|
+
typeof hook !== "object" ||
|
|
125
|
+
hook.statusMessage !== HOOK_OWNER);
|
|
126
|
+
return { ...group, hooks: filtered };
|
|
127
|
+
})
|
|
128
|
+
.filter((group) => !group ||
|
|
129
|
+
typeof group !== "object" ||
|
|
130
|
+
!Array.isArray(group.hooks) ||
|
|
131
|
+
group.hooks.length > 0)
|
|
132
|
+
: [];
|
|
133
|
+
hooks[event] = [...retained, ...(desired[event] || [])];
|
|
134
|
+
}
|
|
135
|
+
atomicWriteJson(path, { ...config, hooks });
|
|
136
|
+
}
|
|
137
|
+
export function repairClientHooks(userHome = homedir(), configHome = vaultHome()) {
|
|
138
|
+
installClientHooks("claude", userHome, configHome);
|
|
139
|
+
installClientHooks("codex", userHome, configHome);
|
|
140
|
+
if (existsSync(join(userHome, ".grok"))) {
|
|
141
|
+
installClientHooks("grok", userHome, configHome);
|
|
142
|
+
}
|
|
143
|
+
if (existsSync(join(userHome, ".gemini", "config")) ||
|
|
144
|
+
existsSync(join(userHome, ".gemini", "antigravity")) ||
|
|
145
|
+
existsSync(join(userHome, ".gemini", "antigravity-cli"))) {
|
|
146
|
+
installClientHooks("agy", userHome, configHome);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { HOOK_EVENTS, runHook } from "./hooks.js";
|
|
3
|
+
const adapter = process.argv[2] ?? "auto";
|
|
4
|
+
const event = process.argv[3];
|
|
5
|
+
if (!event || !HOOK_EVENTS.includes(event)) {
|
|
6
|
+
process.stderr.write("Uso: hook-runner <adaptador> context|session-init|observation|file-context|summarize\n");
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
await runHook(adapter, event);
|
package/dist/hooks.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { type VaultMemoryApi } from "./cloud.js";
|
|
|
2
2
|
export declare const HOOK_EVENTS: readonly ["context", "session-init", "observation", "file-context", "summarize"];
|
|
3
3
|
export type HookEvent = (typeof HOOK_EVENTS)[number];
|
|
4
4
|
type RecordValue = Record<string, unknown>;
|
|
5
|
-
export declare function
|
|
5
|
+
export declare function normalizeHookInput(adapter: string, value: unknown): RecordValue;
|
|
6
|
+
export declare function hookOutput(event: HookEvent, context?: string, adapter?: string): RecordValue;
|
|
6
7
|
export declare function executeHook(adapter: string, event: HookEvent, value: unknown, cloud?: VaultMemoryApi, home?: string): Promise<RecordValue>;
|
|
7
8
|
export declare function readHookInput(stream?: NodeJS.ReadableStream): Promise<unknown>;
|
|
8
9
|
export declare function runHook(adapter: string, event: string): Promise<void>;
|
package/dist/hooks.js
CHANGED
|
@@ -45,7 +45,34 @@ function redact(value) {
|
|
|
45
45
|
}
|
|
46
46
|
return value;
|
|
47
47
|
}
|
|
48
|
-
export function
|
|
48
|
+
export function normalizeHookInput(adapter, value) {
|
|
49
|
+
const input = record(value);
|
|
50
|
+
const toolCall = record(input.toolCall);
|
|
51
|
+
const workspaces = Array.isArray(input.workspacePaths)
|
|
52
|
+
? input.workspacePaths
|
|
53
|
+
: [];
|
|
54
|
+
const workspace = workspaces.find((item) => typeof item === "string" && Boolean(item.trim()));
|
|
55
|
+
return {
|
|
56
|
+
...input,
|
|
57
|
+
session_id: string(input.session_id, input.sessionId, input.conversation_id, input.conversationId, input.thread_id),
|
|
58
|
+
cwd: string(input.cwd, input.workspace, input.workspaceRoot, workspace),
|
|
59
|
+
tool_name: string(input.tool_name, input.toolName, toolCall.name),
|
|
60
|
+
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),
|
|
62
|
+
prompt: string(input.prompt, input.user_prompt, input.userPrompt),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function hookOutput(event, context = "", adapter = "claude") {
|
|
66
|
+
if (adapter === "agy") {
|
|
67
|
+
if (event === "context" || event === "session-init") {
|
|
68
|
+
return context
|
|
69
|
+
? { injectSteps: [{ ephemeralMessage: context }] }
|
|
70
|
+
: {};
|
|
71
|
+
}
|
|
72
|
+
if (event === "file-context")
|
|
73
|
+
return { decision: "allow" };
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
49
76
|
const result = { continue: true, suppressOutput: true };
|
|
50
77
|
const name = event === "context"
|
|
51
78
|
? "SessionStart"
|
|
@@ -79,29 +106,29 @@ async function preferences(cloud) {
|
|
|
79
106
|
};
|
|
80
107
|
}
|
|
81
108
|
export async function executeHook(adapter, event, value, cloud = new VaultCloudClient(), home = vaultHome()) {
|
|
82
|
-
const input =
|
|
83
|
-
const toolName = string(input.tool_name
|
|
109
|
+
const input = normalizeHookInput(adapter, value);
|
|
110
|
+
const toolName = string(input.tool_name);
|
|
84
111
|
if (input.private === true ||
|
|
85
112
|
input.is_private === true ||
|
|
86
113
|
/vault[_-]go|vault[_-]mem/.test(toolName)) {
|
|
87
|
-
return hookOutput(event);
|
|
114
|
+
return hookOutput(event, "", adapter);
|
|
88
115
|
}
|
|
89
116
|
const prefs = await preferences(cloud);
|
|
90
117
|
const reading = event === "context" || event === "file-context";
|
|
91
118
|
if (!reading && prefs.automaticCapture !== true)
|
|
92
|
-
return hookOutput(event);
|
|
93
|
-
const tool = record(input.tool_input
|
|
94
|
-
const file = string(tool.file_path, tool.path, tool.notebook_path);
|
|
119
|
+
return hookOutput(event, "", adapter);
|
|
120
|
+
const tool = record(input.tool_input);
|
|
121
|
+
const file = string(tool.file_path, tool.path, tool.notebook_path, tool.AbsolutePath, tool.Path, tool.filePath);
|
|
95
122
|
const modifies = /write|edit|patch|create|delete|move|rename/i.test(toolName);
|
|
96
123
|
if (!reading && prefs.captureMode === "focused" && event !== "summarize")
|
|
97
|
-
return hookOutput(event);
|
|
124
|
+
return hookOutput(event, "", adapter);
|
|
98
125
|
if (event === "observation" && prefs.captureMode === "balanced" && !modifies)
|
|
99
|
-
return hookOutput(event);
|
|
126
|
+
return hookOutput(event, "", adapter);
|
|
100
127
|
if (event === "file-context" && !file)
|
|
101
|
-
return hookOutput(event);
|
|
102
|
-
const sessionId = string(input.session_id
|
|
128
|
+
return hookOutput(event, "", adapter);
|
|
129
|
+
const sessionId = string(input.session_id);
|
|
103
130
|
if (!reading && !sessionId)
|
|
104
|
-
return hookOutput(event);
|
|
131
|
+
return hookOutput(event, "", adapter);
|
|
105
132
|
const cwd = root(string(input.cwd, input.workspace) || process.cwd());
|
|
106
133
|
const projects = await cloud.projects();
|
|
107
134
|
if (!Array.isArray(projects))
|
|
@@ -110,7 +137,7 @@ export async function executeHook(adapter, event, value, cloud = new VaultCloudC
|
|
|
110
137
|
typeof item === "object" &&
|
|
111
138
|
resolve(String(item.rootPath || "")) === cwd);
|
|
112
139
|
if (!project && reading)
|
|
113
|
-
return hookOutput(event);
|
|
140
|
+
return hookOutput(event, "", adapter);
|
|
114
141
|
if (!project) {
|
|
115
142
|
project = record(await cloud.createProject({
|
|
116
143
|
name: (basename(cwd) || "workspace").padEnd(3, "_").slice(0, 80),
|
|
@@ -131,7 +158,7 @@ export async function executeHook(adapter, event, value, cloud = new VaultCloudC
|
|
|
131
158
|
paths: [...new Set([file, resolve(cwd, file)])],
|
|
132
159
|
})
|
|
133
160
|
: await cloud.context(limits));
|
|
134
|
-
return hookOutput(event, string(result.context).slice(0, 24_000));
|
|
161
|
+
return hookOutput(event, string(result.context).slice(0, 24_000), adapter);
|
|
135
162
|
}
|
|
136
163
|
const session = record(await cloud.startSession({
|
|
137
164
|
projectId: project.id,
|
|
@@ -140,8 +167,8 @@ export async function executeHook(adapter, event, value, cloud = new VaultCloudC
|
|
|
140
167
|
}));
|
|
141
168
|
if (typeof session.id !== "string")
|
|
142
169
|
throw new Error("Sessão inválida");
|
|
143
|
-
const prompt = string(input.prompt
|
|
144
|
-
const response = string(input.last_assistant_message
|
|
170
|
+
const prompt = string(input.prompt);
|
|
171
|
+
const response = string(input.last_assistant_message);
|
|
145
172
|
const rawContent = event === "session-init"
|
|
146
173
|
? prompt
|
|
147
174
|
: event === "summarize"
|
|
@@ -195,7 +222,7 @@ export async function executeHook(adapter, event, value, cloud = new VaultCloudC
|
|
|
195
222
|
}
|
|
196
223
|
if (event === "summarize")
|
|
197
224
|
await cloud.endSession(session.id);
|
|
198
|
-
return hookOutput(event);
|
|
225
|
+
return hookOutput(event, "", adapter);
|
|
199
226
|
}
|
|
200
227
|
export async function readHookInput(stream = process.stdin) {
|
|
201
228
|
const chunks = [];
|
package/dist/installer.d.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
export declare const MCP_SERVER_NAME = "vault-go";
|
|
2
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"];
|
|
3
|
+
export declare const MCP_CLIENTS: readonly ["codex", "claude", "claude-desktop", "cursor", "vscode", "copilot", "windsurf", "roo", "opencode", "grok", "agy", "gemini"];
|
|
4
4
|
export type McpClient = (typeof MCP_CLIENTS)[number];
|
|
5
|
-
export declare function quoteShell(value: string): string;
|
|
6
|
-
export declare function hookEntryPath(configHome?: string): string;
|
|
7
|
-
export declare function hookCommand(adapter: string, event: string, configHome?: string): string;
|
|
8
5
|
export interface DetectedClient {
|
|
9
6
|
client: McpClient;
|
|
10
7
|
detected: boolean;
|
package/dist/installer.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync, } from 'node:fs';
|
|
2
2
|
import { homedir, platform } from 'node:os';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
4
|
import { spawnSync } from 'node:child_process';
|
|
6
5
|
import { vaultHome } from './config.js';
|
|
7
6
|
import { stageLocalRuntime } from './local-install.js';
|
|
7
|
+
import { installClientHooks } 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 = [
|
|
@@ -17,76 +17,14 @@ export const MCP_CLIENTS = [
|
|
|
17
17
|
'windsurf',
|
|
18
18
|
'roo',
|
|
19
19
|
'opencode',
|
|
20
|
+
'grok',
|
|
21
|
+
'agy',
|
|
22
|
+
'gemini',
|
|
20
23
|
];
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
UserPromptSubmit: 'session-init',
|
|
25
|
-
PreToolUse: 'file-context',
|
|
26
|
-
PostToolUse: 'observation',
|
|
27
|
-
Stop: 'summarize',
|
|
24
|
+
const CLIENT_ALIASES = {
|
|
25
|
+
antigravity: 'agy',
|
|
26
|
+
'gemini-cli': 'gemini',
|
|
28
27
|
};
|
|
29
|
-
export function quoteShell(value) {
|
|
30
|
-
return "'" + value.replaceAll("'", "'\\''") + "'";
|
|
31
|
-
}
|
|
32
|
-
export function hookEntryPath(configHome = vaultHome()) {
|
|
33
|
-
const staged = join(configHome, 'local-runtime', 'dist', 'index.js');
|
|
34
|
-
if (existsSync(staged))
|
|
35
|
-
return staged;
|
|
36
|
-
return join(dirname(fileURLToPath(import.meta.url)), 'index.js');
|
|
37
|
-
}
|
|
38
|
-
export function hookCommand(adapter, event, configHome = vaultHome()) {
|
|
39
|
-
return `${quoteShell(process.execPath)} ${quoteShell(hookEntryPath(configHome))} hook ${adapter} ${event}`;
|
|
40
|
-
}
|
|
41
|
-
function hookManifest(adapter, configHome = vaultHome()) {
|
|
42
|
-
return {
|
|
43
|
-
hooks: Object.fromEntries(Object.entries(HOOK_EVENTS).map(([name, event]) => [
|
|
44
|
-
name,
|
|
45
|
-
[
|
|
46
|
-
{
|
|
47
|
-
hooks: [
|
|
48
|
-
{
|
|
49
|
-
type: 'command',
|
|
50
|
-
command: hookCommand(adapter, event, configHome),
|
|
51
|
-
timeout: event === 'context' ? 60 : 20,
|
|
52
|
-
statusMessage: HOOK_OWNER,
|
|
53
|
-
...(event === 'observation' || event === 'summarize' || event === 'file-context'
|
|
54
|
-
? { async: true }
|
|
55
|
-
: {}),
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
])),
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
function installClientHooks(client, userHome, configHome = vaultHome()) {
|
|
64
|
-
try {
|
|
65
|
-
stageLocalRuntime(configHome);
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
/* tests and incomplete installs still write the hook command */
|
|
69
|
-
}
|
|
70
|
-
const path = join(userHome, client === 'claude' ? '.claude/settings.json' : '.codex/hooks.json');
|
|
71
|
-
const config = readJsonObject(path);
|
|
72
|
-
const hooks = { ...(config.hooks && typeof config.hooks === 'object' && !Array.isArray(config.hooks) ? config.hooks : {}) };
|
|
73
|
-
const desired = hookManifest(client, configHome).hooks;
|
|
74
|
-
for (const event of Object.keys(HOOK_EVENTS)) {
|
|
75
|
-
const current = hooks[event];
|
|
76
|
-
const retained = Array.isArray(current)
|
|
77
|
-
? current
|
|
78
|
-
.map((group) => {
|
|
79
|
-
if (!group || typeof group !== 'object' || !Array.isArray(group.hooks))
|
|
80
|
-
return group;
|
|
81
|
-
const filtered = group.hooks.filter((hook) => !hook || typeof hook !== 'object' || hook.statusMessage !== HOOK_OWNER);
|
|
82
|
-
return { ...group, hooks: filtered };
|
|
83
|
-
})
|
|
84
|
-
.filter((group) => !group || typeof group !== 'object' || !Array.isArray(group.hooks) || group.hooks.length > 0)
|
|
85
|
-
: [];
|
|
86
|
-
hooks[event] = [...retained, ...(desired[event] || [])];
|
|
87
|
-
}
|
|
88
|
-
atomicWriteJson(path, { ...config, hooks });
|
|
89
|
-
}
|
|
90
28
|
const runCommand = (command, args) => spawnSync(command, [...args], { stdio: 'ignore' });
|
|
91
29
|
function readJsonObject(path) {
|
|
92
30
|
if (!existsSync(path))
|
|
@@ -104,12 +42,37 @@ function readJsonObject(path) {
|
|
|
104
42
|
return parsed;
|
|
105
43
|
}
|
|
106
44
|
function atomicWriteJson(path, value) {
|
|
45
|
+
atomicWriteText(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
46
|
+
}
|
|
47
|
+
function atomicWriteText(path, text) {
|
|
107
48
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
108
49
|
const temporary = join(dirname(path), `.${process.pid}.${Date.now()}.tmp`);
|
|
109
|
-
writeFileSync(temporary,
|
|
50
|
+
writeFileSync(temporary, text.endsWith('\n') ? text : `${text}\n`, { mode: 0o600 });
|
|
110
51
|
renameSync(temporary, path);
|
|
111
52
|
chmodSync(path, 0o600);
|
|
112
53
|
}
|
|
54
|
+
function formatTomlValue(value) {
|
|
55
|
+
if (typeof value === 'boolean')
|
|
56
|
+
return value ? 'true' : 'false';
|
|
57
|
+
if (typeof value === 'string')
|
|
58
|
+
return JSON.stringify(value);
|
|
59
|
+
return `[${value.map((item) => JSON.stringify(item)).join(', ')}]`;
|
|
60
|
+
}
|
|
61
|
+
function upsertTomlTable(path, table, assignments) {
|
|
62
|
+
const previous = existsSync(path) ? readFileSync(path, 'utf8') : '';
|
|
63
|
+
const body = Object.entries(assignments)
|
|
64
|
+
.map(([key, value]) => `${key} = ${formatTomlValue(value)}`)
|
|
65
|
+
.join('\n');
|
|
66
|
+
const block = `[${table}]\n${body}\n`;
|
|
67
|
+
const escaped = table.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
68
|
+
const pattern = new RegExp(`(^|\\r?\\n)\\[${escaped}\\](?:\\r?\\n(?!\\[).*)*`);
|
|
69
|
+
const next = pattern.test(previous)
|
|
70
|
+
? previous.replace(pattern, (match) => `${/^\r?\n/.exec(match)?.[0] ?? ''}${block}`)
|
|
71
|
+
: previous.trimEnd()
|
|
72
|
+
? `${previous.trimEnd()}\n\n${block}`
|
|
73
|
+
: block;
|
|
74
|
+
atomicWriteText(path, next);
|
|
75
|
+
}
|
|
113
76
|
function standardServer() {
|
|
114
77
|
return {
|
|
115
78
|
command: MCP_SERVER_COMMAND[0],
|
|
@@ -156,6 +119,10 @@ export function installMcpClient(client, options = {}) {
|
|
|
156
119
|
const runner = options.runner ?? runCommand;
|
|
157
120
|
if (client === 'codex') {
|
|
158
121
|
const result = installViaCli(client, 'codex', ['mcp', 'get', MCP_SERVER_NAME], ['mcp', 'add', MCP_SERVER_NAME, '--', ...MCP_SERVER_COMMAND], runner);
|
|
122
|
+
try {
|
|
123
|
+
stageLocalRuntime(vaultHome());
|
|
124
|
+
}
|
|
125
|
+
catch { /* incomplete installs still write hooks */ }
|
|
159
126
|
installClientHooks('codex', home);
|
|
160
127
|
return result;
|
|
161
128
|
}
|
|
@@ -171,6 +138,10 @@ export function installMcpClient(client, options = {}) {
|
|
|
171
138
|
'--',
|
|
172
139
|
...MCP_SERVER_COMMAND,
|
|
173
140
|
], runner);
|
|
141
|
+
try {
|
|
142
|
+
stageLocalRuntime(vaultHome());
|
|
143
|
+
}
|
|
144
|
+
catch { /* incomplete installs still write hooks */ }
|
|
174
145
|
installClientHooks('claude', home);
|
|
175
146
|
return result;
|
|
176
147
|
}
|
|
@@ -186,6 +157,36 @@ export function installMcpClient(client, options = {}) {
|
|
|
186
157
|
}
|
|
187
158
|
return { client, status: 'installed', destination: 'VS Code user profile' };
|
|
188
159
|
}
|
|
160
|
+
if (client === 'grok') {
|
|
161
|
+
const destination = join(home, '.grok', 'config.toml');
|
|
162
|
+
upsertTomlTable(destination, 'mcp_servers.vault-go', {
|
|
163
|
+
command: MCP_SERVER_COMMAND[0],
|
|
164
|
+
args: MCP_SERVER_COMMAND.slice(1),
|
|
165
|
+
enabled: true,
|
|
166
|
+
});
|
|
167
|
+
try {
|
|
168
|
+
stageLocalRuntime(vaultHome());
|
|
169
|
+
}
|
|
170
|
+
catch { /* incomplete installs still write hooks */ }
|
|
171
|
+
installClientHooks('grok', home);
|
|
172
|
+
return { client, status: 'installed', destination };
|
|
173
|
+
}
|
|
174
|
+
if (client === 'agy') {
|
|
175
|
+
const cliPath = join(home, '.gemini', 'config', 'mcp_config.json');
|
|
176
|
+
mergeServer(cliPath);
|
|
177
|
+
mergeServer(join(home, '.gemini', 'antigravity', 'mcp_config.json'));
|
|
178
|
+
try {
|
|
179
|
+
stageLocalRuntime(vaultHome());
|
|
180
|
+
}
|
|
181
|
+
catch { /* incomplete installs still write hooks */ }
|
|
182
|
+
installClientHooks('agy', home);
|
|
183
|
+
return { client, status: 'installed', destination: cliPath };
|
|
184
|
+
}
|
|
185
|
+
if (client === 'gemini') {
|
|
186
|
+
const path = join(home, '.gemini', 'settings.json');
|
|
187
|
+
mergeServer(path);
|
|
188
|
+
return { client, status: 'installed', destination: path };
|
|
189
|
+
}
|
|
189
190
|
let path;
|
|
190
191
|
if (client === 'claude-desktop')
|
|
191
192
|
path = claudeDesktopPath(home);
|
|
@@ -230,6 +231,9 @@ export function detectMcpClients(home = homedir()) {
|
|
|
230
231
|
['windsurf', ['windsurf'], [join(home, '.codeium', 'windsurf')]],
|
|
231
232
|
['roo', [], []],
|
|
232
233
|
['opencode', ['opencode'], [join(home, '.config', 'opencode')]],
|
|
234
|
+
['grok', ['grok'], [join(home, '.grok')]],
|
|
235
|
+
['agy', ['agy'], [join(home, '.gemini', 'antigravity-cli'), join(home, '.gemini', 'config'), join(home, '.gemini', 'antigravity')]],
|
|
236
|
+
['gemini', ['gemini'], [join(home, '.gemini', 'settings.json')]],
|
|
233
237
|
];
|
|
234
238
|
return definitions.map(([client, commands, paths]) => {
|
|
235
239
|
const command = commands.find((candidate) => Bun.which(candidate));
|
|
@@ -256,7 +260,10 @@ export function parseClientSelection(value) {
|
|
|
256
260
|
const normalized = value.trim().toLowerCase();
|
|
257
261
|
if (normalized === 'all' || normalized === 'todos')
|
|
258
262
|
return [...MCP_CLIENTS];
|
|
259
|
-
const selected = [...new Set(normalized.split(',').map((item) =>
|
|
263
|
+
const selected = [...new Set(normalized.split(',').map((item) => {
|
|
264
|
+
const trimmed = item.trim();
|
|
265
|
+
return CLIENT_ALIASES[trimmed] ?? trimmed;
|
|
266
|
+
}).filter(Boolean))];
|
|
260
267
|
const invalid = selected.filter((item) => !MCP_CLIENTS.includes(item));
|
|
261
268
|
if (invalid.length > 0)
|
|
262
269
|
throw new Error(`Clientes MCP desconhecidos: ${invalid.join(', ')}`);
|
package/dist/local-install.js
CHANGED
|
@@ -51,13 +51,10 @@ export function stageLocalRuntime(home = vaultHome(), source = dirname(fileURLTo
|
|
|
51
51
|
mkdirSync(target, { recursive: true, mode: 0o700 });
|
|
52
52
|
chmodSync(root, 0o700);
|
|
53
53
|
chmodSync(target, 0o700);
|
|
54
|
-
const excluded = new Set(["server.js", "installer.js", "cli.js"]);
|
|
55
54
|
if (!existsSync(join(source, "local-worker.js")))
|
|
56
55
|
throw new Error("Local worker is missing. Build or reinstall vault-go first.");
|
|
57
56
|
for (const file of readdirSync(source)) {
|
|
58
|
-
if (!file.endsWith(".js") ||
|
|
59
|
-
file.endsWith(".test.js") ||
|
|
60
|
-
excluded.has(file))
|
|
57
|
+
if (!file.endsWith(".js") || file.endsWith(".test.js"))
|
|
61
58
|
continue;
|
|
62
59
|
const destination = join(target, file);
|
|
63
60
|
copyFileSync(join(source, file), `${destination}.tmp`);
|
|
@@ -76,6 +73,8 @@ export function stageLocalRuntime(home = vaultHome(), source = dirname(fileURLTo
|
|
|
76
73
|
version: manifest.version,
|
|
77
74
|
type: "module",
|
|
78
75
|
}), { mode: 0o600 });
|
|
76
|
+
if (!existsSync(join(target, "hook-runner.js")))
|
|
77
|
+
throw new Error("Hook runner is missing. Build or reinstall vault-go first.");
|
|
79
78
|
return join(target, "local-worker.js");
|
|
80
79
|
}
|
|
81
80
|
export async function startLocal(home = vaultHome()) {
|
|
@@ -218,6 +217,13 @@ export async function replaceLaunchAgent(domain, path, prepare, operations) {
|
|
|
218
217
|
}
|
|
219
218
|
export async function installLocal(home = vaultHome()) {
|
|
220
219
|
const worker = stageLocalRuntime(home);
|
|
220
|
+
try {
|
|
221
|
+
const { repairClientHooks } = await import("./hook-install.js");
|
|
222
|
+
repairClientHooks(homedir(), home);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
/* settings files may not exist yet */
|
|
226
|
+
}
|
|
221
227
|
if (process.platform !== "darwin") {
|
|
222
228
|
await startLocal(home);
|
|
223
229
|
return { startup: false };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vault-go",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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": {
|
|
@@ -34,8 +34,12 @@
|
|
|
34
34
|
"dist/config.d.ts",
|
|
35
35
|
"dist/context-engines.js",
|
|
36
36
|
"dist/context-engines.d.ts",
|
|
37
|
+
"dist/hook-install.js",
|
|
38
|
+
"dist/hook-install.d.ts",
|
|
37
39
|
"dist/hook-queue.js",
|
|
38
40
|
"dist/hook-queue.d.ts",
|
|
41
|
+
"dist/hook-runner.js",
|
|
42
|
+
"dist/hook-runner.d.ts",
|
|
39
43
|
"dist/hooks.js",
|
|
40
44
|
"dist/hooks.d.ts",
|
|
41
45
|
"dist/index.js",
|
|
@@ -99,7 +103,10 @@
|
|
|
99
103
|
"codex",
|
|
100
104
|
"claude",
|
|
101
105
|
"copilot",
|
|
102
|
-
"cursor"
|
|
106
|
+
"cursor",
|
|
107
|
+
"grok",
|
|
108
|
+
"opencode",
|
|
109
|
+
"antigravity"
|
|
103
110
|
],
|
|
104
111
|
"author": "Gutierrez Henrique",
|
|
105
112
|
"license": "MIT",
|