vault-go 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -180,6 +180,8 @@ Engines and updates: `vault_go_engines`, `vault_go_engine_select`, `vault_go_gen
180
180
 
181
181
  Graph and local code: `vault_go_graph`, `vault_go_graph_link`, `vault_go_graph_link_delete`, `vault_go_graph_presets`, `vault_go_graph_preset_save`, `vault_go_smart_outline`, `vault_go_smart_unfold`, `vault_go_smart_search`, `vault_go_smart_languages`.
182
182
 
183
+ Computer Use by Vault: `vault_go_computer_status`, `vault_go_computer_permissions`, `vault_go_computer_list_apps`, `vault_go_computer_list_windows`, `vault_go_computer_get_app_state`, `vault_go_computer_query`, `vault_go_computer_click`, `vault_go_computer_perform_secondary_action`, `vault_go_computer_scroll`, `vault_go_computer_drag`, `vault_go_computer_type_text`, `vault_go_computer_paste_text`, `vault_go_computer_set_value`, `vault_go_computer_press_key`, `vault_go_computer_hotkey`. They drive local apps through the Vault Desktop native helper (installed and running), so every MCP client shares its permissions; on macOS grant Accessibility and Screen Recording to "Vault Desktop Computer Use" once. `get_app_state` returns the accessibility tree plus the window screenshot as an MCP image; `query` finds controls by text or role without a screenshot. Element indexes share one session per server. Typed text and values travel over stdin, never the process arguments. Set `VAULT_DESKTOP_CLI` to point at a custom CLI path, or `VAULT_GO_COMPUTER_USE=off` to hide the tools. The tool surface follows [open-computer-use](https://github.com/iFurySt/open-computer-use) (MIT).
184
+
183
185
  Workflows: `vault_go_workflow`, `vault_go_modes`, `vault_go_mode`, plus named prompts such as `vault_go_babysit`. Loading a mode gives the agent instructions; it does not start a background job.
184
186
 
185
187
  Optional local integrations (explicit consent): Telegram alerts, custom mode taxonomies, Claude/Codex transcript watchers, multi-project context chains, and Grok awareness logs. See `vault_go_capabilities` for what is configured on this machine.
@@ -110,6 +110,11 @@ export declare const CAPABILITIES: {
110
110
  readonly status: "available";
111
111
  readonly tools: readonly ["vault_go_modes", "vault_go_mode", "vault_go_agent_cost_report"];
112
112
  readonly difference: "Setup writes managed SKILL.md files for Claude, Grok and shared agent skill directories. mem-search uses vault_go_search_index → vault_go_timeline → vault_go_observations → vault_go_tool_uses. babysit is a session loop with gh, not a Vault background daemon. agent-cost-report prices local JSONL usage with a published list rate and reports missing usage as unavailable.";
113
+ }, {
114
+ readonly upstream: readonly ["open-computer-use: list_apps, get_app_state, query, click, drag, scroll, type_text, press_key, set_value, perform_secondary_action"];
115
+ readonly status: "available";
116
+ readonly tools: readonly ["vault_go_computer_status", "vault_go_computer_permissions", "vault_go_computer_list_apps", "vault_go_computer_list_windows", "vault_go_computer_get_app_state", "vault_go_computer_query", "vault_go_computer_click", "vault_go_computer_perform_secondary_action", "vault_go_computer_scroll", "vault_go_computer_drag", "vault_go_computer_type_text", "vault_go_computer_paste_text", "vault_go_computer_set_value", "vault_go_computer_press_key", "vault_go_computer_hotkey"];
117
+ readonly requires: "Computer Use by Vault runs through the installed Vault Desktop helper and its macOS Accessibility and Screen Recording permissions; Vault Desktop must be running. select_text, background SkyLight input and Locked Use are not ported. VAULT_GO_COMPUTER_USE=off hides the tools.";
113
118
  }, {
114
119
  readonly upstream: readonly ["viewer", "statusline"];
115
120
  readonly status: "available";
@@ -234,6 +239,11 @@ export declare function getCapabilities(home: string, cloud: VaultMemoryApi, ver
234
239
  readonly status: "available";
235
240
  readonly tools: readonly ["vault_go_modes", "vault_go_mode", "vault_go_agent_cost_report"];
236
241
  readonly difference: "Setup writes managed SKILL.md files for Claude, Grok and shared agent skill directories. mem-search uses vault_go_search_index → vault_go_timeline → vault_go_observations → vault_go_tool_uses. babysit is a session loop with gh, not a Vault background daemon. agent-cost-report prices local JSONL usage with a published list rate and reports missing usage as unavailable.";
242
+ }, {
243
+ readonly upstream: readonly ["open-computer-use: list_apps, get_app_state, query, click, drag, scroll, type_text, press_key, set_value, perform_secondary_action"];
244
+ readonly status: "available";
245
+ readonly tools: readonly ["vault_go_computer_status", "vault_go_computer_permissions", "vault_go_computer_list_apps", "vault_go_computer_list_windows", "vault_go_computer_get_app_state", "vault_go_computer_query", "vault_go_computer_click", "vault_go_computer_perform_secondary_action", "vault_go_computer_scroll", "vault_go_computer_drag", "vault_go_computer_type_text", "vault_go_computer_paste_text", "vault_go_computer_set_value", "vault_go_computer_press_key", "vault_go_computer_hotkey"];
246
+ readonly requires: "Computer Use by Vault runs through the installed Vault Desktop helper and its macOS Accessibility and Screen Recording permissions; Vault Desktop must be running. select_text, background SkyLight input and Locked Use are not ported. VAULT_GO_COMPUTER_USE=off hides the tools.";
237
247
  }, {
238
248
  readonly upstream: readonly ["viewer", "statusline"];
239
249
  readonly status: "available";
@@ -29,6 +29,7 @@ export const CAPABILITIES = {
29
29
  { upstream: ['transcript watchers'], status: 'available', tools: ['vault_go_transcripts_status', 'vault_go_transcripts_configure', 'vault_go_transcripts_poll'], difference: 'Persistent, consented and resumable watchers read Claude, Codex and Cursor JSONL transcripts. Cursor rows use the Claude message shape. Other transcript formats are not claimed.' },
30
30
  { upstream: ['Grok awareness push'], status: 'available', tools: ['vault_go_grok_awareness_status', 'vault_go_grok_awareness_configure', 'vault_go_grok_awareness_push'], requires: 'Explicit consent, an allowlisted agent UUID and a local agent-data root. Monthly awareness logs are bounded, private and separate from Telegram.' },
31
31
  { upstream: ['development/report/presentation skills', 'babysit', 'mem-search', 'agent-cost-report'], status: 'available', tools: ['vault_go_modes', 'vault_go_mode', 'vault_go_agent_cost_report'], difference: 'Setup writes managed SKILL.md files for Claude, Grok and shared agent skill directories. mem-search uses vault_go_search_index → vault_go_timeline → vault_go_observations → vault_go_tool_uses. babysit is a session loop with gh, not a Vault background daemon. agent-cost-report prices local JSONL usage with a published list rate and reports missing usage as unavailable.' },
32
+ { upstream: ['open-computer-use: list_apps, get_app_state, query, click, drag, scroll, type_text, press_key, set_value, perform_secondary_action'], status: 'available', tools: ['vault_go_computer_status', 'vault_go_computer_permissions', 'vault_go_computer_list_apps', 'vault_go_computer_list_windows', 'vault_go_computer_get_app_state', 'vault_go_computer_query', 'vault_go_computer_click', 'vault_go_computer_perform_secondary_action', 'vault_go_computer_scroll', 'vault_go_computer_drag', 'vault_go_computer_type_text', 'vault_go_computer_paste_text', 'vault_go_computer_set_value', 'vault_go_computer_press_key', 'vault_go_computer_hotkey'], requires: 'Computer Use by Vault runs through the installed Vault Desktop helper and its macOS Accessibility and Screen Recording permissions; Vault Desktop must be running. select_text, background SkyLight input and Locked Use are not ported. VAULT_GO_COMPUTER_USE=off hides the tools.' },
32
33
  { upstream: ['viewer', 'statusline'], status: 'available', tools: [], difference: 'The local worker serves /viewer and /statusline on its loopback port. Semantic search stays on the Vault account index. A second local vector database is not shipped.' },
33
34
  { upstream: ['private skills library'], status: 'available', tools: ['vault_go_skills', 'vault_go_skill_folders', 'vault_go_skill_read', 'vault_go_skill_file', 'vault_go_skill_save', 'vault_go_skill_organize', 'vault_go_skill_install', 'vault_go_skill_sync'], requires: 'Account skill packages organized by folders and linked to projects (/v1/skills). Saving publishes a new immutable version; deleting is only available in the Vault apps. Local install verifies sha256 and manifest digest and never replaces a skill the Vault did not install.' },
34
35
  ],
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { ComputerUseBridge } from './computer-use.js';
3
+ /** Computer Use by Vault: native app control through the Vault Desktop helper (opt out with VAULT_GO_COMPUTER_USE=off). */
4
+ export declare function registerComputerTools(server: McpServer, bridge?: ComputerUseBridge, env?: NodeJS.ProcessEnv): void;
@@ -0,0 +1,135 @@
1
+ import { z } from 'zod';
2
+ import { errorMessage } from './mcp-result.js';
3
+ import { ComputerUseBridge, computerUseEnabled, queryTree } from './computer-use.js';
4
+ const app = z.string().trim().min(1).max(500).describe('Nome do app, bundle ID (preferível, ex.: com.apple.TextEdit) ou pid:N, como em vault_go_computer_list_apps.');
5
+ const elementIndex = z.number().int().min(0).max(1_000_000).describe('Índice do elemento na árvore de acessibilidade do último get_app_state/query.');
6
+ const coordinate = z.number().finite().min(-100_000).max(100_000);
7
+ const observe = {
8
+ windowId: z.number().int().min(0).optional().describe('ID da janela retornado por vault_go_computer_list_windows.'),
9
+ windowIndex: z.number().int().min(0).optional().describe('Índice da janela quando o ID for none.'),
10
+ restoreWindow: z.boolean().optional().describe('Traz o app/janela para frente antes da operação.'),
11
+ screenshot: z.boolean().default(true).describe('Anexa um screenshot da janela depois da operação. false é mais rápido.'),
12
+ session: z.string().trim().min(1).max(200).optional().describe('Namespace dos índices de elementos. Por padrão, um por servidor MCP.'),
13
+ };
14
+ const ACTION = { readOnlyHint: false, destructiveHint: false, openWorldHint: false };
15
+ const READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
16
+ const SAFETY = 'Não envie, compre, apague nem altere contas sem pedido explícito do usuário. Ação não verificada (verification.state=unverified) não é sucesso comprovado.';
17
+ function observeFlags(input, bridge) {
18
+ return {
19
+ app: undefined,
20
+ session: input.session ?? bridge.sessionId,
21
+ 'window-id': input.windowId,
22
+ 'window-index': input.windowIndex,
23
+ 'restore-window': input.restoreWindow,
24
+ 'no-screenshot': input.screenshot === false,
25
+ };
26
+ }
27
+ function toolResult(result) {
28
+ const content = [{ type: 'text', text: JSON.stringify(result.value) }];
29
+ if (result.image)
30
+ content.push({ type: 'image', data: result.image.data, mimeType: result.image.mimeType });
31
+ return { content, structuredContent: result.value };
32
+ }
33
+ async function invoke(operation) {
34
+ try {
35
+ return toolResult(await operation());
36
+ }
37
+ catch (error) {
38
+ return { content: [{ type: 'text', text: errorMessage(error) }], isError: true };
39
+ }
40
+ }
41
+ /** Computer Use by Vault: native app control through the Vault Desktop helper (opt out with VAULT_GO_COMPUTER_USE=off). */
42
+ export function registerComputerTools(server, bridge = new ComputerUseBridge(), env = process.env) {
43
+ if (!computerUseEnabled(env))
44
+ return;
45
+ const action = (command, input, extra, stdin) => invoke(() => bridge.call(command, { ...observeFlags(input, bridge), app: input.app, ...extra }, stdin));
46
+ server.registerTool('vault_go_computer_status', {
47
+ description: 'Computer Use by Vault: verifica se o Vault Desktop e o helper nativo estão disponíveis e quais ações a plataforma suporta. Rode antes da primeira ação; em erro de permissão use vault_go_computer_permissions.',
48
+ inputSchema: {}, annotations: READ,
49
+ }, () => invoke(async () => {
50
+ const result = await bridge.call('capabilities');
51
+ return { ...result, value: { cli: bridge.cliPath(), session: bridge.sessionId, ...result.value } };
52
+ }));
53
+ server.registerTool('vault_go_computer_permissions', {
54
+ description: 'Abre a configuração de permissões do Computer Use by Vault (macOS: Acessibilidade e Gravação de Tela para "Vault Desktop Computer Use") e informa o estado de cada uma.',
55
+ inputSchema: { id: z.enum(['accessibility', 'screenshots']).optional() }, annotations: ACTION,
56
+ }, ({ id }) => invoke(() => bridge.call('permissions', { id })));
57
+ server.registerTool('vault_go_computer_list_apps', {
58
+ description: 'Lista os apps em execução que o Computer Use by Vault pode operar, com bundle ID e pid.',
59
+ inputSchema: {}, annotations: READ,
60
+ }, () => invoke(() => bridge.call('list-apps')));
61
+ server.registerTool('vault_go_computer_list_windows', {
62
+ description: 'Lista as janelas de um app (id, índice, título, posição) para mirar uma janela específica.',
63
+ inputSchema: { app }, annotations: READ,
64
+ }, ({ app: target }) => invoke(() => bridge.call('list-windows', { app: target })));
65
+ server.registerTool('vault_go_computer_get_app_state', {
66
+ description: 'Lê a árvore de acessibilidade da janela de um app e anexa um screenshot. Chame uma vez por turno antes de agir; os números da árvore são os elementIndex das ações e expiram após mudanças na UI.',
67
+ inputSchema: { app, ...observe }, annotations: READ,
68
+ }, (input) => action('get-app-state', input, {}));
69
+ server.registerTool('vault_go_computer_query', {
70
+ description: 'Busca direcionada: encontra controles por texto e/ou papel (button, text field, AXButton) sem screenshot e sem devolver a árvore inteira. Os índices retornados servem direto para click/set_value/scroll na mesma sessão.',
71
+ inputSchema: {
72
+ app,
73
+ text: z.string().trim().max(1_000).optional().describe('Texto no título, descrição, valor ou conteúdo do elemento (sem diferenciar maiúsculas).'),
74
+ role: z.string().trim().max(100).optional().describe('Papel de acessibilidade, ex.: button, text field ou AXButton.'),
75
+ exact: z.boolean().default(false).describe('Exige o nome inteiro em vez de substring.'),
76
+ limit: z.number().int().min(1).max(100).default(20),
77
+ windowId: observe.windowId, windowIndex: observe.windowIndex, session: observe.session,
78
+ },
79
+ annotations: READ,
80
+ }, (input) => invoke(async () => {
81
+ if (!input.text && !input.role)
82
+ throw new Error('Informe text e/ou role.');
83
+ const state = await bridge.call('get-app-state', { ...observeFlags({ ...input, screenshot: false }, bridge), app: input.app });
84
+ const snapshot = (state.value.snapshot ?? {});
85
+ const found = queryTree(typeof snapshot.treeText === 'string' ? snapshot.treeText : '', input);
86
+ return { value: { snapshotId: snapshot.id ?? null, app: snapshot.app ?? null, window: snapshot.window ?? null, ...found, treeTruncated: snapshot.truncation?.truncated === true }, image: null };
87
+ }));
88
+ server.registerTool('vault_go_computer_click', {
89
+ description: `Clica em um elemento (elementIndex) ou em coordenadas da janela (x, y). Suporta clique duplo, botão direito/meio e modificadores atômicos (ex.: CmdOrCtrl+Shift). ${SAFETY}`,
90
+ inputSchema: {
91
+ app, elementIndex: elementIndex.optional(), x: coordinate.optional(), y: coordinate.optional(),
92
+ clickCount: z.number().int().min(1).max(3).optional(), mouseButton: z.enum(['left', 'right', 'middle']).optional(),
93
+ modifiers: z.string().trim().max(100).optional(), ...observe,
94
+ },
95
+ annotations: ACTION,
96
+ }, (input) => action('click', input, { 'element-index': input.elementIndex, x: input.x, y: input.y, 'click-count': input.clickCount, 'mouse-button': input.mouseButton, modifiers: input.modifiers }));
97
+ server.registerTool('vault_go_computer_perform_secondary_action', {
98
+ description: `Executa uma ação secundária de acessibilidade listada no elemento (ex.: "show menu", "increment"). ${SAFETY}`,
99
+ inputSchema: { app, elementIndex, action: z.string().trim().min(1).max(200), ...observe }, annotations: ACTION,
100
+ }, (input) => action('perform-secondary-action', input, { 'element-index': input.elementIndex, action: input.action }));
101
+ server.registerTool('vault_go_computer_scroll', {
102
+ description: 'Rola um elemento ou ponto da janela em páginas (frações permitidas).',
103
+ inputSchema: { app, direction: z.enum(['up', 'down', 'left', 'right']), elementIndex: elementIndex.optional(), x: coordinate.optional(), y: coordinate.optional(), pages: z.number().positive().max(50).optional(), ...observe },
104
+ annotations: ACTION,
105
+ }, (input) => action('scroll', input, { direction: input.direction, 'element-index': input.elementIndex, x: input.x, y: input.y, pages: input.pages }));
106
+ server.registerTool('vault_go_computer_drag', {
107
+ description: `Arrasta entre dois elementos (fromElementIndex/toElementIndex) ou entre coordenadas da janela. ${SAFETY}`,
108
+ inputSchema: {
109
+ app, fromElementIndex: elementIndex.optional(), toElementIndex: elementIndex.optional(),
110
+ fromX: coordinate.optional(), fromY: coordinate.optional(), toX: coordinate.optional(), toY: coordinate.optional(), ...observe,
111
+ },
112
+ annotations: ACTION,
113
+ }, (input) => action('drag', input, { 'from-element-index': input.fromElementIndex, 'to-element-index': input.toElementIndex, 'from-x': input.fromX, 'from-y': input.fromY, 'to-x': input.toX, 'to-y': input.toY }));
114
+ const text = z.string().max(100_000);
115
+ server.registerTool('vault_go_computer_type_text', {
116
+ description: `Digita texto literal no foco atual do app. Prefira set_value em campos editáveis; o texto vai por stdin e não aparece na lista de processos. ${SAFETY}`,
117
+ inputSchema: { app, text, ...observe }, annotations: ACTION,
118
+ }, (input) => action('type-text', input, { 'text-stdin': true }, input.text));
119
+ server.registerTool('vault_go_computer_paste_text', {
120
+ description: `Cola texto exato no foco atual (via área de transferência), útil para textos longos ou com acentos. ${SAFETY}`,
121
+ inputSchema: { app, text, ...observe }, annotations: ACTION,
122
+ }, (input) => action('paste-text', input, { 'text-stdin': true }, input.text));
123
+ server.registerTool('vault_go_computer_set_value', {
124
+ description: `Define o valor de um elemento editável (campo de texto, slider, busca) diretamente pela acessibilidade, com verificação por leitura. ${SAFETY}`,
125
+ inputSchema: { app, elementIndex, value: text, ...observe }, annotations: ACTION,
126
+ }, (input) => action('set-value', input, { 'element-index': input.elementIndex, 'value-stdin': true }, input.value));
127
+ server.registerTool('vault_go_computer_press_key', {
128
+ description: `Pressiona uma tecla única: Return, Escape, Tab, setas, PageUp, Delete. Para combinações use vault_go_computer_hotkey. ${SAFETY}`,
129
+ inputSchema: { app, key: z.string().trim().min(1).max(50), ...observe }, annotations: ACTION,
130
+ }, (input) => action('press-key', input, { key: input.key }));
131
+ server.registerTool('vault_go_computer_hotkey', {
132
+ description: `Pressiona um atalho com modificadores e uma tecla, ex.: CmdOrCtrl+A, CmdOrCtrl+Shift+P. ${SAFETY}`,
133
+ inputSchema: { app, key: z.string().trim().min(1).max(100), ...observe }, annotations: ACTION,
134
+ }, (input) => action('hotkey', input, { key: input.key }));
135
+ }
@@ -0,0 +1,60 @@
1
+ export type CommandOutput = {
2
+ stdout: string;
3
+ stderr: string;
4
+ code: number | null;
5
+ };
6
+ export type CommandRunner = (file: string, args: string[], stdin?: string) => Promise<CommandOutput>;
7
+ export type ComputerUseOptions = {
8
+ env?: NodeJS.ProcessEnv;
9
+ platform?: NodeJS.Platform;
10
+ home?: string;
11
+ exists?: (path: string) => boolean;
12
+ runner?: CommandRunner;
13
+ readFile?: (path: string) => Buffer;
14
+ sessionId?: string;
15
+ };
16
+ export type ComputerImage = {
17
+ data: string;
18
+ mimeType: 'image/png';
19
+ };
20
+ export type ComputerCallResult = {
21
+ value: Record<string, unknown>;
22
+ image: ComputerImage | null;
23
+ };
24
+ export declare function computerUseEnabled(env?: NodeJS.ProcessEnv): boolean;
25
+ /** Finds the Vault Desktop CLI: explicit override, the installed app bundle, then PATH. */
26
+ export declare function resolveDesktopCli(options?: ComputerUseOptions): string | null;
27
+ export declare const defaultRunner: CommandRunner;
28
+ export declare class ComputerUseError extends Error {
29
+ readonly code: string;
30
+ readonly nextSteps: string[];
31
+ constructor(code: string, message: string, nextSteps?: string[]);
32
+ }
33
+ export type Flags = Record<string, string | number | boolean | undefined>;
34
+ /** Runs one `vault-desktop computer <command>` call and returns its JSON result. */
35
+ export declare class ComputerUseBridge {
36
+ readonly sessionId: string;
37
+ private readonly options;
38
+ constructor(options?: ComputerUseOptions);
39
+ cliPath(): string | null;
40
+ call(command: string, flags?: Flags, stdin?: string): Promise<ComputerCallResult>;
41
+ /** Moves screenshot pixels out of the JSON so the client receives an MCP image instead of base64 text. */
42
+ private extractScreenshot;
43
+ }
44
+ export type TreeQuery = {
45
+ text?: string | undefined;
46
+ role?: string | undefined;
47
+ exact?: boolean | undefined;
48
+ limit?: number | undefined;
49
+ };
50
+ export type TreeMatch = {
51
+ index: number;
52
+ depth: number;
53
+ line: string;
54
+ };
55
+ export declare function normalizeRole(role: string): string;
56
+ export declare function queryTree(treeText: string, query: TreeQuery): {
57
+ matches: TreeMatch[];
58
+ truncated: boolean;
59
+ visitedNodes: number;
60
+ };
@@ -0,0 +1,205 @@
1
+ // Computer Use by Vault: bridge from MCP to the Vault Desktop native helper through its CLI.
2
+ // The tool surface follows iFurySt/open-computer-use (MIT); input, permissions and the
3
+ // accessibility runtime stay in Vault Desktop so every client shares one signed helper.
4
+ import { spawn } from 'node:child_process';
5
+ import { randomUUID } from 'node:crypto';
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { homedir } from 'node:os';
8
+ import { delimiter, join } from 'node:path';
9
+ const COMMAND_TIMEOUT_MS = 90_000;
10
+ const MAX_OUTPUT_BYTES = 32 * 1024 * 1024;
11
+ const MAX_SCREENSHOT_BYTES = 20 * 1024 * 1024;
12
+ const MAX_TREE_QUERY_LIMIT = 100;
13
+ const DEFAULT_TREE_QUERY_LIMIT = 20;
14
+ export function computerUseEnabled(env = process.env) {
15
+ const value = env.VAULT_GO_COMPUTER_USE?.trim().toLowerCase();
16
+ return !(value === 'off' || value === '0' || value === 'false' || value === 'disabled');
17
+ }
18
+ /** Finds the Vault Desktop CLI: explicit override, the installed app bundle, then PATH. */
19
+ export function resolveDesktopCli(options = {}) {
20
+ const env = options.env ?? process.env;
21
+ const platform = options.platform ?? process.platform;
22
+ const home = options.home ?? homedir();
23
+ const exists = options.exists ?? existsSync;
24
+ const override = env.VAULT_DESKTOP_CLI?.trim();
25
+ if (override)
26
+ return exists(override) ? override : null;
27
+ const candidates = [];
28
+ if (platform === 'darwin') {
29
+ const bundled = 'Vault Desktop.app/Contents/Resources/bin/vault-desktop';
30
+ candidates.push(join('/Applications', bundled), join(home, 'Applications', bundled));
31
+ }
32
+ if (platform === 'win32' && env.LOCALAPPDATA) {
33
+ const bundled = join('resources', 'bin', 'vault-desktop.exe');
34
+ candidates.push(join(env.LOCALAPPDATA, 'Programs', 'Vault Desktop', bundled), join(env.LOCALAPPDATA, 'Vault Desktop', bundled));
35
+ }
36
+ // Why: only the native .exe launcher on Windows; a .cmd shim would need a shell and let
37
+ // tool arguments such as an app name be interpreted as shell syntax.
38
+ const names = platform === 'win32' ? ['vault-desktop.exe'] : ['vault-desktop', 'vault-ide'];
39
+ for (const dir of (env.PATH ?? '').split(delimiter).filter(Boolean)) {
40
+ for (const name of names)
41
+ candidates.push(join(dir, name));
42
+ }
43
+ return candidates.find((candidate) => exists(candidate)) ?? null;
44
+ }
45
+ export const defaultRunner = (file, args, stdin) => new Promise((resolve, reject) => {
46
+ const child = spawn(file, args, { stdio: ['pipe', 'pipe', 'pipe'], shell: false, windowsHide: true });
47
+ const stdout = [], stderr = [];
48
+ let size = 0;
49
+ const timer = setTimeout(() => child.kill('SIGKILL'), COMMAND_TIMEOUT_MS);
50
+ const collect = (target) => (chunk) => {
51
+ size += chunk.length;
52
+ if (size > MAX_OUTPUT_BYTES)
53
+ child.kill('SIGKILL');
54
+ else
55
+ target.push(chunk);
56
+ };
57
+ child.stdout.on('data', collect(stdout));
58
+ child.stderr.on('data', collect(stderr));
59
+ child.on('error', (error) => { clearTimeout(timer); reject(error); });
60
+ child.on('close', (code) => {
61
+ clearTimeout(timer);
62
+ resolve({ stdout: Buffer.concat(stdout).toString('utf8'), stderr: Buffer.concat(stderr).toString('utf8'), code });
63
+ });
64
+ child.stdin.end(stdin ?? '');
65
+ });
66
+ export class ComputerUseError extends Error {
67
+ code;
68
+ nextSteps;
69
+ constructor(code, message, nextSteps = []) {
70
+ super(nextSteps.length ? `${message}\nPróximos passos: ${nextSteps.join(' ')}` : message);
71
+ this.code = code;
72
+ this.nextSteps = nextSteps;
73
+ }
74
+ }
75
+ /** Runs one `vault-desktop computer <command>` call and returns its JSON result. */
76
+ export class ComputerUseBridge {
77
+ sessionId;
78
+ options;
79
+ constructor(options = {}) {
80
+ this.options = options;
81
+ this.sessionId = options.sessionId ?? `vault-go-${randomUUID().slice(0, 8)}`;
82
+ }
83
+ cliPath() {
84
+ return resolveDesktopCli(this.options);
85
+ }
86
+ async call(command, flags = {}, stdin) {
87
+ const cli = this.cliPath();
88
+ if (!cli) {
89
+ throw new ComputerUseError('desktop_not_found', 'Vault Desktop não foi encontrado nesta máquina.', [
90
+ 'Instale e abra o Vault Desktop (https://vaultmemoryai.com) ou defina VAULT_DESKTOP_CLI com o caminho da CLI vault-desktop.',
91
+ ]);
92
+ }
93
+ const args = ['computer', command, ...flagArgs(flags), '--json'];
94
+ const output = await (this.options.runner ?? defaultRunner)(cli, args, stdin);
95
+ const envelope = parseEnvelope(output);
96
+ if (!envelope.ok) {
97
+ const error = envelope.error ?? {};
98
+ const steps = Array.isArray(error.data?.nextSteps) ? error.data.nextSteps.filter((s) => typeof s === 'string') : [];
99
+ throw new ComputerUseError(typeof error.code === 'string' ? error.code : 'runtime_error', typeof error.message === 'string' ? error.message : 'Falha no Computer Use by Vault.', steps);
100
+ }
101
+ const value = envelope.result && typeof envelope.result === 'object' && !Array.isArray(envelope.result) ? { ...envelope.result } : { value: envelope.result };
102
+ return { value, image: this.extractScreenshot(value) };
103
+ }
104
+ /** Moves screenshot pixels out of the JSON so the client receives an MCP image instead of base64 text. */
105
+ extractScreenshot(value) {
106
+ const screenshot = value.screenshot;
107
+ if (!screenshot || typeof screenshot !== 'object')
108
+ return null;
109
+ const shot = { ...screenshot };
110
+ let data = typeof shot.data === 'string' && shot.data ? shot.data : null;
111
+ if (!data && typeof shot.path === 'string') {
112
+ try {
113
+ const bytes = (this.options.readFile ?? readFileSync)(shot.path);
114
+ if (bytes.length <= MAX_SCREENSHOT_BYTES)
115
+ data = bytes.toString('base64');
116
+ }
117
+ catch {
118
+ data = null;
119
+ }
120
+ }
121
+ delete shot.data;
122
+ delete shot.path;
123
+ shot.attached = data !== null;
124
+ value.screenshot = shot;
125
+ return data ? { data, mimeType: 'image/png' } : null;
126
+ }
127
+ }
128
+ function flagArgs(flags) {
129
+ const args = [];
130
+ for (const [name, value] of Object.entries(flags)) {
131
+ if (value === undefined || value === false)
132
+ continue;
133
+ // Why: `--flag=value` keeps values that start with "--" from being read as flags.
134
+ args.push(value === true ? `--${name}` : `--${name}=${String(value)}`);
135
+ }
136
+ return args;
137
+ }
138
+ function parseEnvelope(output) {
139
+ const text = output.stdout.trim();
140
+ const start = text.indexOf('{');
141
+ if (start >= 0) {
142
+ try {
143
+ const parsed = JSON.parse(text.slice(start));
144
+ if (parsed && typeof parsed === 'object' && typeof parsed.ok === 'boolean')
145
+ return parsed;
146
+ }
147
+ catch {
148
+ // fall through to the stderr message below
149
+ }
150
+ }
151
+ const detail = (output.stderr.trim() || text).split('\n').slice(0, 5).join(' ').slice(0, 500);
152
+ if (/not running|ECONNREFUSED|runtime.*unavailable|could not connect/i.test(detail)) {
153
+ throw new ComputerUseError('desktop_not_running', 'O Vault Desktop não está aberto.', ['Abra o Vault Desktop e tente novamente.']);
154
+ }
155
+ throw new ComputerUseError('runtime_error', `A CLI do Vault Desktop respondeu sem JSON (código ${output.code ?? 'desconhecido'}): ${detail || 'sem saída'}`);
156
+ }
157
+ const TREE_LINE = /^(\s*)(\d+)(?:\s+(.*))?$/;
158
+ const LABELLED_SEGMENT = /,\s*(?:Description|Value|Placeholder|Text|Secondary Actions):\s*/;
159
+ export function normalizeRole(role) {
160
+ return role.trim().replace(/^AX(?=[A-Z])/, '').replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[_-]+/g, ' ').replace(/\s+/g, ' ').toLowerCase();
161
+ }
162
+ export function queryTree(treeText, query) {
163
+ const limit = Math.min(query.limit ?? DEFAULT_TREE_QUERY_LIMIT, MAX_TREE_QUERY_LIMIT);
164
+ const text = query.text?.trim().toLowerCase() ?? '';
165
+ const role = query.role ? normalizeRole(query.role) : '';
166
+ const matches = [];
167
+ let visitedNodes = 0, truncated = false;
168
+ for (const raw of treeText.split('\n')) {
169
+ const parsed = TREE_LINE.exec(raw);
170
+ if (!parsed)
171
+ continue;
172
+ visitedNodes += 1;
173
+ const body = (parsed[3] ?? '').trim();
174
+ if (role && !startsWithWord(body.toLowerCase(), role))
175
+ continue;
176
+ if (text && !matchesText(body, text, query.exact === true))
177
+ continue;
178
+ if (matches.length >= limit) {
179
+ truncated = true;
180
+ break;
181
+ }
182
+ matches.push({ index: Number(parsed[2]), depth: Math.floor((parsed[1] ?? '').replace(/\t/g, ' ').length / 2), line: body });
183
+ }
184
+ return { matches, truncated, visitedNodes };
185
+ }
186
+ function startsWithWord(body, prefix) {
187
+ if (!body.startsWith(prefix))
188
+ return false;
189
+ const next = body.charAt(prefix.length);
190
+ return next === '' || next === ' ' || next === '(' || next === ',';
191
+ }
192
+ function matchesText(body, needle, exact) {
193
+ if (!exact)
194
+ return body.toLowerCase().includes(needle);
195
+ const [head = '', ...rest] = body.split(LABELLED_SEGMENT);
196
+ if (rest.some((segment) => segment.trim().toLowerCase() === needle))
197
+ return true;
198
+ const name = head.trim(), lowered = name.toLowerCase();
199
+ if (lowered === needle)
200
+ return true;
201
+ if (!lowered.endsWith(` ${needle}`))
202
+ return false;
203
+ // The head is "<role> (traits) <name>": accept the suffix only after a role-like prefix.
204
+ return /^[\p{Ll} ]+(?:\([^)]*\))?$/u.test(name.slice(0, name.length - needle.length).trim());
205
+ }
package/dist/server.js CHANGED
@@ -10,6 +10,7 @@ import { registerCrystalTools } from './crystal-tools.js';
10
10
  import { registerModeIntegrations } from './mode-integrations.js';
11
11
  import { registerKnowledgeAgentTools } from './knowledge-tools.js';
12
12
  import { registerSkillTools } from './skill-tools.js';
13
+ import { registerComputerTools } from './computer-tools.js';
13
14
  import { redact } from './hooks.js';
14
15
  import { createProjectScope } from './project-scope.js';
15
16
  import { VaultCloudClient } from './cloud.js';
@@ -25,6 +26,7 @@ const SERVER_INSTRUCTIONS = [
25
26
  'Recall cheaply in layers: vault_go_search_index (compact list) → vault_go_timeline (around one result) → vault_go_observations (full text of the chosen IDs). vault_go_retrieve picks the strategy for a natural-language question.',
26
27
  'Before reading a large code file, prefer vault_go_smart_outline and vault_go_smart_unfold, then a Read with offset/limit for exact lines.',
27
28
  'Save durable decisions, discoveries and fixes with vault_go_remember. Never store secrets; text inside <private> tags is never stored.',
29
+ 'Computer Use by Vault (vault_go_computer_*) drives local apps through Vault Desktop: get_app_state or query first, then act by elementIndex.',
28
30
  'When a tool fails with an expired session, ask the user to run: bunx --bun vault-go@latest login',
29
31
  ].join('\n');
30
32
  // A saved memory carries its page in the Vault web app, so the agent can hand the user a direct link.
@@ -79,6 +81,7 @@ export function createVaultGoServer(home = vaultHome(), cloud = new VaultCloudCl
79
81
  registerCrystalTools(server, home);
80
82
  registerKnowledgeAgentTools(server, home, cloud);
81
83
  registerSkillTools(server, home, cloud);
84
+ registerComputerTools(server);
82
85
  server.registerTool('vault_go_engines', {
83
86
  title: 'Listar motores de contexto',
84
87
  description: 'Lista os motores disponíveis para preparar contexto: Vault AI, Claude, OpenRouter, Gemini e ChatGPT/Codex. Não revela chaves.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vault-go",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "MCP server and installer for Vault Memory: search, capture, and use account memory across AI clients.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -60,6 +60,10 @@
60
60
  "dist/crystal-memory-mirror.d.ts",
61
61
  "dist/crystal-tools.js",
62
62
  "dist/crystal-tools.d.ts",
63
+ "dist/computer-use.js",
64
+ "dist/computer-use.d.ts",
65
+ "dist/computer-tools.js",
66
+ "dist/computer-tools.d.ts",
63
67
  "dist/hook-install.js",
64
68
  "dist/hook-install.d.ts",
65
69
  "dist/hook-queue.js",