vault-go 0.15.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -170,6 +170,8 @@ limitados.
170
170
 
171
171
  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
172
 
173
+ Na instalação, o Vault Go registra hooks no Claude Code e no Codex. 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
+
173
175
  O recurso `vault-go://status` fornece o resumo sanitizado em JSON.
174
176
 
175
177
  Para reduzir o uso de contexto, prefira a recuperação em três camadas:
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type McpClient } from './installer.js';
2
2
  import { type ContextEngineId } from './context-engines.js';
3
- export type CliMode = 'help' | 'version' | 'setup' | 'login' | 'serve' | 'local' | 'engine' | 'invalid';
3
+ export type CliMode = 'help' | 'version' | 'setup' | 'login' | 'serve' | 'local' | 'engine' | 'hook' | 'invalid';
4
4
  export declare function resolveCliMode(argument: string | undefined, stdinIsTTY: boolean, stderrIsTTY: boolean): CliMode;
5
5
  export declare function parseEngineChoice(value: string): ContextEngineId;
6
6
  export declare function parseNumberedClients(value: string): McpClient[];
package/dist/cli.js CHANGED
@@ -21,6 +21,8 @@ export function resolveCliMode(argument, stdinIsTTY, stderrIsTTY) {
21
21
  return 'local';
22
22
  if (argument === 'engine')
23
23
  return 'engine';
24
+ if (argument === 'hook')
25
+ return 'hook';
24
26
  if (argument === undefined)
25
27
  return stdinIsTTY && stderrIsTTY ? 'setup' : 'serve';
26
28
  return 'invalid';
@@ -321,6 +323,10 @@ Uso:
321
323
  vault-go engine list|use <motor> [--model haiku|sonnet|opus]|key openrouter|gemini
322
324
  Escolhe o motor e o modelo de contexto. No Claude: haiku, sonnet ou opus.
323
325
 
326
+ vault-go hook <adaptador> <evento>
327
+ Usado pelos hooks do Claude/Codex: injeta contexto, captura atividade
328
+ e enfileira geração com o motor escolhido.
329
+
324
330
  --lang pt|en|es
325
331
  Idioma do assistente (padrão: idioma do sistema).
326
332
 
package/dist/cloud.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export interface VaultMemoryApi {
2
2
  observerStatus?(): Promise<unknown>;
3
3
  observerGenerate?(text: string, signal?: AbortSignal): Promise<unknown>;
4
+ preferences?(): Promise<unknown>;
4
5
  health(): Promise<unknown>;
5
6
  projects(): Promise<unknown>;
6
7
  createProject(input: Record<string, unknown>): Promise<unknown>;
@@ -38,6 +39,7 @@ export declare class VaultCloudClient implements VaultMemoryApi {
38
39
  constructor(home?: string, cloudFetch?: typeof fetch, journal?: boolean);
39
40
  health(): Promise<unknown>;
40
41
  observerStatus(): Promise<unknown>;
42
+ preferences(): Promise<unknown>;
41
43
  observerGenerate(text: string, signal?: AbortSignal): Promise<unknown>;
42
44
  registerDevice(input: Record<string, unknown>): Promise<unknown>;
43
45
  rotateDevice(id: string): Promise<unknown>;
package/dist/cloud.js CHANGED
@@ -13,6 +13,20 @@ export class VaultCloudClient {
13
13
  return this.request('/health', { authenticated: false });
14
14
  }
15
15
  async observerStatus() { return this.request('/memory/observer/status'); }
16
+ async preferences() {
17
+ try {
18
+ return await this.request('/preferences');
19
+ }
20
+ catch {
21
+ return {
22
+ automaticCapture: true,
23
+ captureMode: 'balanced',
24
+ includeToolResults: false,
25
+ contextItems: 20,
26
+ contextMaxChars: 24_000,
27
+ };
28
+ }
29
+ }
16
30
  async observerGenerate(text, signal) {
17
31
  return this.request('/memory/observer/generate', { method: 'POST', body: { text }, timeoutMs: 60_000, ...(signal ? { signal } : {}) });
18
32
  }
@@ -0,0 +1,13 @@
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 declare function installClientHooks(client: "claude" | "codex", userHome?: string, configHome?: string): void;
13
+ export declare function repairClientHooks(userHome?: string, configHome?: string): void;
@@ -0,0 +1,113 @@
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
+ export function installClientHooks(client, userHome = homedir(), configHome = vaultHome()) {
77
+ const path = join(userHome, client === "claude" ? ".claude/settings.json" : ".codex/hooks.json");
78
+ const config = readJsonObject(path);
79
+ const hooks = {
80
+ ...(config.hooks &&
81
+ typeof config.hooks === "object" &&
82
+ !Array.isArray(config.hooks)
83
+ ? config.hooks
84
+ : {}),
85
+ };
86
+ const desired = hookManifest(client, configHome).hooks;
87
+ for (const event of Object.keys(HOOK_EVENTS)) {
88
+ const current = hooks[event];
89
+ const retained = Array.isArray(current)
90
+ ? current
91
+ .map((group) => {
92
+ if (!group ||
93
+ typeof group !== "object" ||
94
+ !Array.isArray(group.hooks))
95
+ return group;
96
+ const filtered = group.hooks.filter((hook) => !hook ||
97
+ typeof hook !== "object" ||
98
+ hook.statusMessage !== HOOK_OWNER);
99
+ return { ...group, hooks: filtered };
100
+ })
101
+ .filter((group) => !group ||
102
+ typeof group !== "object" ||
103
+ !Array.isArray(group.hooks) ||
104
+ group.hooks.length > 0)
105
+ : [];
106
+ hooks[event] = [...retained, ...(desired[event] || [])];
107
+ }
108
+ atomicWriteJson(path, { ...config, hooks });
109
+ }
110
+ export function repairClientHooks(userHome = homedir(), configHome = vaultHome()) {
111
+ installClientHooks("claude", userHome, configHome);
112
+ installClientHooks("codex", userHome, configHome);
113
+ }
@@ -0,0 +1,13 @@
1
+ import { VaultCloudClient } from "./cloud.js";
2
+ import { generateContext } from "./context-engines.js";
3
+ export interface GenerationJob {
4
+ id: string;
5
+ projectId: string;
6
+ sessionId?: string;
7
+ text: string;
8
+ title?: string;
9
+ kind: "observation" | "summary" | "prompt";
10
+ }
11
+ export declare function enqueueGeneration(home: string, job: GenerationJob): void;
12
+ export declare function takeGeneration(home: string): GenerationJob | undefined;
13
+ export declare function processGenerationQueue(home?: string, cloud?: VaultCloudClient, generate?: typeof generateContext): Promise<boolean>;
@@ -0,0 +1,72 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync } from "node:fs";
2
+ import { writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { VaultCloudClient } from "./cloud.js";
5
+ import { vaultHome } from "./config.js";
6
+ import { generateContext, selectedContextEngine, } from "./context-engines.js";
7
+ function queuePath(home) {
8
+ return join(home, "generation-queue.json");
9
+ }
10
+ function readQueue(home) {
11
+ try {
12
+ const value = JSON.parse(readFileSync(queuePath(home), "utf8"));
13
+ return Array.isArray(value) ? value : [];
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ }
19
+ function writeQueue(home, jobs) {
20
+ mkdirSync(home, { recursive: true, mode: 0o700 });
21
+ const destination = queuePath(home);
22
+ const temporary = destination + "." + process.pid + ".tmp";
23
+ writeFileSync(temporary, JSON.stringify(jobs.slice(0, 50)) + "\n", { mode: 0o600 });
24
+ chmodSync(temporary, 0o600);
25
+ renameSync(temporary, destination);
26
+ chmodSync(destination, 0o600);
27
+ }
28
+ export function enqueueGeneration(home, job) {
29
+ if (!job.text.trim() || job.text.length > 16_000)
30
+ return;
31
+ const jobs = readQueue(home);
32
+ if (jobs.some((item) => item.id === job.id))
33
+ return;
34
+ writeQueue(home, [...jobs, job]);
35
+ }
36
+ export function takeGeneration(home) {
37
+ const jobs = readQueue(home);
38
+ const next = jobs[0];
39
+ if (!next)
40
+ return;
41
+ writeQueue(home, jobs.slice(1));
42
+ return next;
43
+ }
44
+ export async function processGenerationQueue(home = vaultHome(), cloud = new VaultCloudClient(home), generate = generateContext) {
45
+ const job = takeGeneration(home);
46
+ if (!job)
47
+ return false;
48
+ const engine = selectedContextEngine(home);
49
+ try {
50
+ const result = await generate(home, engine, job.text.slice(0, 16_000), {
51
+ ...(cloud.observerGenerate
52
+ ? {
53
+ managedGenerate: (text, options) => cloud.observerGenerate(text, options.signal),
54
+ }
55
+ : {}),
56
+ });
57
+ await cloud.remember({
58
+ projectId: job.projectId,
59
+ ...(job.sessionId ? { sessionId: job.sessionId } : {}),
60
+ kind: job.kind === "summary" ? "summary" : "observation",
61
+ memoryType: "discovery",
62
+ title: result.title || job.title,
63
+ content: result.content,
64
+ facts: result.facts,
65
+ concepts: result.concepts,
66
+ });
67
+ }
68
+ catch {
69
+ // Drop poison items so the watcher keeps draining.
70
+ }
71
+ return existsSync(queuePath(home)) && readQueue(home).length > 0;
72
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -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);
@@ -0,0 +1,9 @@
1
+ import { type VaultMemoryApi } from "./cloud.js";
2
+ export declare const HOOK_EVENTS: readonly ["context", "session-init", "observation", "file-context", "summarize"];
3
+ export type HookEvent = (typeof HOOK_EVENTS)[number];
4
+ type RecordValue = Record<string, unknown>;
5
+ export declare function hookOutput(event: HookEvent, context?: string): RecordValue;
6
+ export declare function executeHook(adapter: string, event: HookEvent, value: unknown, cloud?: VaultMemoryApi, home?: string): Promise<RecordValue>;
7
+ export declare function readHookInput(stream?: NodeJS.ReadableStream): Promise<unknown>;
8
+ export declare function runHook(adapter: string, event: string): Promise<void>;
9
+ export {};
package/dist/hooks.js ADDED
@@ -0,0 +1,227 @@
1
+ import { createHash } from "node:crypto";
2
+ import { basename, dirname, resolve } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { VaultCloudClient } from "./cloud.js";
5
+ import { vaultHome } from "./config.js";
6
+ import { enqueueGeneration } from "./hook-queue.js";
7
+ export const HOOK_EVENTS = [
8
+ "context",
9
+ "session-init",
10
+ "observation",
11
+ "file-context",
12
+ "summarize",
13
+ ];
14
+ const record = (value) => value && typeof value === "object" && !Array.isArray(value)
15
+ ? value
16
+ : {};
17
+ const string = (...values) => values.find((value) => typeof value === "string" && value.trim()) ||
18
+ "";
19
+ function root(cwd) {
20
+ const initial = resolve(cwd);
21
+ let dir = initial;
22
+ while (dirname(dir) !== dir) {
23
+ if (existsSync(resolve(dir, ".git")))
24
+ return dir;
25
+ dir = dirname(dir);
26
+ }
27
+ return initial;
28
+ }
29
+ function redact(value) {
30
+ if (typeof value === "string") {
31
+ return value
32
+ .replace(/<private>[\s\S]*?<\/private>/gi, "[redacted]")
33
+ .replace(/(api[_-]?key|secret|token|password)\s*[:=]\s*['"]?[^\s'"]+/gi, "$1=[redacted]");
34
+ }
35
+ if (Array.isArray(value))
36
+ return value.map(redact);
37
+ if (value && typeof value === "object") {
38
+ const output = {};
39
+ for (const [key, nested] of Object.entries(value)) {
40
+ output[key] = /api[_-]?key|secret|token|password|authorization/i.test(key)
41
+ ? "[redacted]"
42
+ : redact(nested);
43
+ }
44
+ return output;
45
+ }
46
+ return value;
47
+ }
48
+ export function hookOutput(event, context = "") {
49
+ const result = { continue: true, suppressOutput: true };
50
+ const name = event === "context"
51
+ ? "SessionStart"
52
+ : event === "file-context"
53
+ ? "PreToolUse"
54
+ : event === "session-init"
55
+ ? "UserPromptSubmit"
56
+ : "";
57
+ if (name && context) {
58
+ result.hookSpecificOutput = {
59
+ hookEventName: name,
60
+ additionalContext: context,
61
+ };
62
+ }
63
+ return result;
64
+ }
65
+ async function preferences(cloud) {
66
+ try {
67
+ if (cloud.preferences)
68
+ return record(await cloud.preferences());
69
+ }
70
+ catch {
71
+ /* use defaults */
72
+ }
73
+ return {
74
+ automaticCapture: true,
75
+ captureMode: "balanced",
76
+ includeToolResults: false,
77
+ contextItems: 20,
78
+ contextMaxChars: 24_000,
79
+ };
80
+ }
81
+ export async function executeHook(adapter, event, value, cloud = new VaultCloudClient(), home = vaultHome()) {
82
+ const input = record(value);
83
+ const toolName = string(input.tool_name, input.toolName);
84
+ if (input.private === true ||
85
+ input.is_private === true ||
86
+ /vault[_-]go|vault[_-]mem/.test(toolName)) {
87
+ return hookOutput(event);
88
+ }
89
+ const prefs = await preferences(cloud);
90
+ const reading = event === "context" || event === "file-context";
91
+ if (!reading && prefs.automaticCapture !== true)
92
+ return hookOutput(event);
93
+ const tool = record(input.tool_input ?? input.toolInput);
94
+ const file = string(tool.file_path, tool.path, tool.notebook_path);
95
+ const modifies = /write|edit|patch|create|delete|move|rename/i.test(toolName);
96
+ if (!reading && prefs.captureMode === "focused" && event !== "summarize")
97
+ return hookOutput(event);
98
+ if (event === "observation" && prefs.captureMode === "balanced" && !modifies)
99
+ return hookOutput(event);
100
+ if (event === "file-context" && !file)
101
+ return hookOutput(event);
102
+ const sessionId = string(input.session_id, input.sessionId, input.thread_id, input.conversation_id);
103
+ if (!reading && !sessionId)
104
+ return hookOutput(event);
105
+ const cwd = root(string(input.cwd, input.workspace) || process.cwd());
106
+ const projects = await cloud.projects();
107
+ if (!Array.isArray(projects))
108
+ throw new Error("Resposta de projetos inválida");
109
+ let project = projects.find((item) => item &&
110
+ typeof item === "object" &&
111
+ resolve(String(item.rootPath || "")) === cwd);
112
+ if (!project && reading)
113
+ return hookOutput(event);
114
+ if (!project) {
115
+ project = record(await cloud.createProject({
116
+ name: (basename(cwd) || "workspace").padEnd(3, "_").slice(0, 80),
117
+ rootPath: cwd,
118
+ }));
119
+ }
120
+ if (typeof project.id !== "string")
121
+ throw new Error("Projeto inválido");
122
+ const limits = {
123
+ projectId: project.id,
124
+ limit: 20,
125
+ maxChars: 24_000,
126
+ };
127
+ if (reading) {
128
+ const result = record(event === "file-context"
129
+ ? await cloud.fileContext({
130
+ ...limits,
131
+ paths: [...new Set([file, resolve(cwd, file)])],
132
+ })
133
+ : await cloud.context(limits));
134
+ return hookOutput(event, string(result.context).slice(0, 24_000));
135
+ }
136
+ const session = record(await cloud.startSession({
137
+ projectId: project.id,
138
+ externalSessionId: sessionId,
139
+ platformSource: adapter,
140
+ }));
141
+ if (typeof session.id !== "string")
142
+ throw new Error("Sessão inválida");
143
+ const prompt = string(input.prompt, input.user_prompt);
144
+ const response = string(input.last_assistant_message, input.response, input.summary);
145
+ const rawContent = event === "session-init"
146
+ ? prompt
147
+ : event === "summarize"
148
+ ? response
149
+ : JSON.stringify(redact({
150
+ toolName,
151
+ toolInput: tool,
152
+ }));
153
+ const content = String(redact(rawContent));
154
+ if (content.trim()) {
155
+ const eventType = event === "session-init"
156
+ ? "user_prompt"
157
+ : event === "summarize"
158
+ ? "session_summary"
159
+ : "tool_use";
160
+ const sourceEventId = createHash("sha256")
161
+ .update(JSON.stringify([
162
+ adapter,
163
+ event,
164
+ cwd,
165
+ sessionId,
166
+ input.tool_use_id,
167
+ input.turn_id,
168
+ input.timestamp,
169
+ ]))
170
+ .digest("hex");
171
+ await cloud.event({
172
+ projectId: project.id,
173
+ sessionId: session.id,
174
+ sourceAdapter: adapter,
175
+ sourceEventId,
176
+ eventType,
177
+ payload: {
178
+ title: toolName ||
179
+ (event === "summarize" ? "Resumo da sessão" : "Solicitação do usuário"),
180
+ content,
181
+ filesRead: !modifies && file ? [file] : [],
182
+ filesModified: modifies && file ? [file] : [],
183
+ },
184
+ occurredAtEpoch: Date.now(),
185
+ });
186
+ enqueueGeneration(home, {
187
+ id: sourceEventId,
188
+ projectId: project.id,
189
+ sessionId: session.id,
190
+ text: content.slice(0, 16_000),
191
+ title: toolName ||
192
+ (event === "summarize" ? "Resumo da sessão" : "Solicitação do usuário"),
193
+ kind: event === "summarize" ? "summary" : event === "session-init" ? "prompt" : "observation",
194
+ });
195
+ }
196
+ if (event === "summarize")
197
+ await cloud.endSession(session.id);
198
+ return hookOutput(event);
199
+ }
200
+ export async function readHookInput(stream = process.stdin) {
201
+ const chunks = [];
202
+ let size = 0;
203
+ for await (const chunk of stream) {
204
+ const buffer = Buffer.from(chunk);
205
+ size += buffer.length;
206
+ if (size > 1_000_000)
207
+ throw new Error("Hook excede o limite de entrada");
208
+ chunks.push(buffer);
209
+ }
210
+ return JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
211
+ }
212
+ export async function runHook(adapter, event) {
213
+ if (!HOOK_EVENTS.includes(event)) {
214
+ throw new Error("Evento de hook desconhecido.");
215
+ }
216
+ if (event === "context") {
217
+ try {
218
+ const { startLocal } = await import("./local-install.js");
219
+ await startLocal();
220
+ }
221
+ catch {
222
+ /* dashboard already running or unavailable */
223
+ }
224
+ }
225
+ const result = await executeHook(adapter === "auto" ? "claude" : adapter, event, await readHookInput());
226
+ process.stdout.write(JSON.stringify(result) + "\n");
227
+ }
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { cliArguments, helpText, resolveCliMode, runEngine, runLocal, runLogin, runSetup } from './cli.js';
4
+ import { HOOK_EVENTS, runHook } from './hooks.js';
4
5
  import { createVaultGoServer, VERSION } from './server.js';
5
6
  const { argument, options } = cliArguments(process.argv.slice(2));
6
7
  const mode = resolveCliMode(argument, process.stdin.isTTY === true, process.stderr.isTTY === true);
@@ -32,6 +33,15 @@ async function main() {
32
33
  process.exitCode = await runEngine(options);
33
34
  return;
34
35
  }
36
+ if (mode === 'hook') {
37
+ const adapter = options.find((item) => !item.startsWith('--')) ?? 'auto';
38
+ const event = options.filter((item) => !item.startsWith('--'))[1];
39
+ if (!event || !HOOK_EVENTS.includes(event)) {
40
+ throw new Error('Uso: vault-go hook <adaptador> context|session-init|observation|file-context|summarize');
41
+ }
42
+ await runHook(adapter, event);
43
+ return;
44
+ }
35
45
  const server = createVaultGoServer();
36
46
  const transport = new StdioServerTransport();
37
47
  await server.connect(transport);
package/dist/installer.js CHANGED
@@ -2,6 +2,9 @@ import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync
2
2
  import { homedir, platform } from 'node:os';
3
3
  import { dirname, join, resolve } from 'node:path';
4
4
  import { spawnSync } from 'node:child_process';
5
+ import { vaultHome } from './config.js';
6
+ import { stageLocalRuntime } from './local-install.js';
7
+ import { installClientHooks } from './hook-install.js';
5
8
  export const MCP_SERVER_NAME = 'vault-go';
6
9
  export const MCP_SERVER_COMMAND = ['bunx', '--bun', 'vault-go@latest', 'serve'];
7
10
  export const MCP_CLIENTS = [
@@ -83,10 +86,16 @@ export function installMcpClient(client, options = {}) {
83
86
  const cwd = options.cwd ?? process.cwd();
84
87
  const runner = options.runner ?? runCommand;
85
88
  if (client === 'codex') {
86
- return installViaCli(client, 'codex', ['mcp', 'get', MCP_SERVER_NAME], ['mcp', 'add', MCP_SERVER_NAME, '--', ...MCP_SERVER_COMMAND], runner);
89
+ const result = installViaCli(client, 'codex', ['mcp', 'get', MCP_SERVER_NAME], ['mcp', 'add', MCP_SERVER_NAME, '--', ...MCP_SERVER_COMMAND], runner);
90
+ try {
91
+ stageLocalRuntime(vaultHome());
92
+ }
93
+ catch { /* incomplete installs still write hooks */ }
94
+ installClientHooks('codex', home);
95
+ return result;
87
96
  }
88
97
  if (client === 'claude') {
89
- return installViaCli(client, 'claude', ['mcp', 'get', MCP_SERVER_NAME], [
98
+ const result = installViaCli(client, 'claude', ['mcp', 'get', MCP_SERVER_NAME], [
90
99
  'mcp',
91
100
  'add',
92
101
  '--scope',
@@ -97,6 +106,12 @@ export function installMcpClient(client, options = {}) {
97
106
  '--',
98
107
  ...MCP_SERVER_COMMAND,
99
108
  ], runner);
109
+ try {
110
+ stageLocalRuntime(vaultHome());
111
+ }
112
+ catch { /* incomplete installs still write hooks */ }
113
+ installClientHooks('claude', home);
114
+ return result;
100
115
  }
101
116
  if (client === 'vscode') {
102
117
  const payload = JSON.stringify({
@@ -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(["index.js", "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 };
@@ -12,6 +12,7 @@ import { resolveLocale } from "./locale.js";
12
12
  import { callbackPage } from "./callback-page.js";
13
13
  import { privateStore } from "./private-store.js";
14
14
  import { localDevice } from "./local-device.js";
15
+ import { processGenerationQueue } from "./hook-queue.js";
15
16
  import { generateContext, isApiEngine, isContextEngine, listContextEngines, parseEngineModel, saveEngineKey, selectContextEngine, selectEngineModel, selectedContextEngine, } from "./context-engines.js";
16
17
  export const LOCAL_PORT = 38850;
17
18
  const safeEqual = (a, b) => a.length === b.length &&
@@ -258,6 +259,11 @@ export async function startLocalService(options = {}) {
258
259
  connected = true;
259
260
  cloudError = undefined;
260
261
  storage?.write(cache, { owner, projects, memories, updatedAt });
262
+ if (!generating && !options.cloud) {
263
+ generating = processGenerationQueue(home, accountCloud).finally(() => {
264
+ generating = null;
265
+ });
266
+ }
261
267
  }
262
268
  catch {
263
269
  if (requestOwner === identity()) {
@@ -271,6 +277,7 @@ export async function startLocalService(options = {}) {
271
277
  })();
272
278
  return syncing;
273
279
  }
280
+ let generating = null;
274
281
  const server = createServer(async (req, res) => {
275
282
  res.setHeader("Cache-Control", "no-store");
276
283
  res.setHeader("Referrer-Policy", "no-referrer");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vault-go",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
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,6 +34,14 @@
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",
39
+ "dist/hook-queue.js",
40
+ "dist/hook-queue.d.ts",
41
+ "dist/hook-runner.js",
42
+ "dist/hook-runner.d.ts",
43
+ "dist/hooks.js",
44
+ "dist/hooks.d.ts",
37
45
  "dist/index.js",
38
46
  "dist/index.d.ts",
39
47
  "dist/installer.js",