vault-go 0.30.0 → 0.31.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.
Files changed (50) hide show
  1. package/README.md +12 -0
  2. package/dist/agent-cost.d.ts +11 -0
  3. package/dist/agent-cost.js +116 -0
  4. package/dist/auth.js +29 -2
  5. package/dist/capabilities.d.ts +30 -24
  6. package/dist/capabilities.js +7 -6
  7. package/dist/capture-privacy.js +1 -1
  8. package/dist/cli.d.ts +2 -1
  9. package/dist/cli.js +16 -1
  10. package/dist/cloud.js +98 -47
  11. package/dist/config.d.ts +2 -0
  12. package/dist/config.js +15 -1
  13. package/dist/crystal-memory-mirror.d.ts +14 -1
  14. package/dist/crystal-memory-mirror.js +24 -15
  15. package/dist/crystal-tools.d.ts +24 -1
  16. package/dist/crystal-tools.js +30 -5
  17. package/dist/grok-awareness.js +54 -1
  18. package/dist/hook-install.d.ts +4 -0
  19. package/dist/hook-install.js +41 -1
  20. package/dist/hook-queue.d.ts +18 -0
  21. package/dist/hook-queue.js +115 -3
  22. package/dist/hook-runner.js +1 -1
  23. package/dist/hooks.d.ts +1 -1
  24. package/dist/hooks.js +17 -8
  25. package/dist/index.js +5 -1
  26. package/dist/installer.js +8 -1
  27. package/dist/legacy-brand.d.ts +2 -0
  28. package/dist/legacy-brand.js +5 -0
  29. package/dist/local-service.js +11 -0
  30. package/dist/memory-archive.d.ts +25 -0
  31. package/dist/memory-archive.js +65 -0
  32. package/dist/modes.d.ts +1 -1
  33. package/dist/modes.js +3 -1
  34. package/dist/opencode-plugin.d.ts +29 -1
  35. package/dist/opencode-plugin.js +36 -1
  36. package/dist/server.js +66 -0
  37. package/dist/skill-download.d.ts +6 -0
  38. package/dist/skill-download.js +48 -0
  39. package/dist/skill-library.d.ts +8 -0
  40. package/dist/skill-library.js +32 -0
  41. package/dist/skill-local.d.ts +7 -0
  42. package/dist/skill-local.js +44 -7
  43. package/dist/skill-tools.js +13 -26
  44. package/dist/skills-cli.d.ts +31 -0
  45. package/dist/skills-cli.js +90 -0
  46. package/dist/transcript-config.d.ts +1 -1
  47. package/dist/transcript-config.js +4 -3
  48. package/dist/transcript-parser.js +1 -1
  49. package/dist/transcript-tools.js +1 -1
  50. package/package.json +13 -1
package/dist/server.js CHANGED
@@ -14,6 +14,8 @@ import { createProjectScope } from './project-scope.js';
14
14
  import { VaultCloudClient } from './cloud.js';
15
15
  import { getStatus, vaultHome } from './config.js';
16
16
  import { createPrivateBackup, restorePrivateBackup } from './backup.js';
17
+ import { estimateAgentCost } from './agent-cost.js';
18
+ import { flattenArchive, groupArchive, recordsFromExport } from './memory-archive.js';
17
19
  import { CONTEXT_ENGINE_IDS, generateContext, listContextEngines, parseEngineModel, selectContextEngine, selectEngineModel, selectedContextEngine, selectedEngineModel, } from './context-engines.js';
18
20
  import packageJson from '../package.json' with { type: 'json' };
19
21
  export const VERSION = packageJson.version;
@@ -486,6 +488,70 @@ export function createVaultGoServer(home = vaultHome(), cloud = new VaultCloudCl
486
488
  },
487
489
  annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: true },
488
490
  }, async (input) => run(() => cloud.importMemories(input)));
491
+ const archiveRecord = z.object({
492
+ sourceId: z.string().trim().min(1).max(500),
493
+ kind: z.string().trim().min(1).max(100),
494
+ content: z.string().trim().min(1).max(200_000),
495
+ title: z.string().trim().max(500).optional(),
496
+ sessionId: z.string().uuid().optional(),
497
+ memoryType: z.string().trim().min(1).max(100).optional(),
498
+ facts: z.array(z.string().trim().min(1).max(2_000)).max(200).optional(),
499
+ concepts: z.array(z.string().trim().min(1).max(500)).max(200).optional(),
500
+ filesRead: z.array(z.string().trim().min(1).max(2_000)).max(500).optional(),
501
+ filesModified: z.array(z.string().trim().min(1).max(2_000)).max(500).optional(),
502
+ occurredAt: z.string().trim().max(40).optional(),
503
+ });
504
+ const archiveGroup = z.array(archiveRecord).max(1_000);
505
+ server.registerTool('vault_go_archive_export', {
506
+ title: 'Exportar arquivo em quatro grupos',
507
+ description: 'Exporta memórias da conta e as separa em observations, sessions, summaries e prompts.',
508
+ inputSchema: {
509
+ projectId: uuid.optional(),
510
+ limit: z.number().int().min(1).max(1_000).default(500),
511
+ },
512
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
513
+ }, async (input) => run(async () => groupArchive(recordsFromExport(await cloud.exportMemories(input)))));
514
+ server.registerTool('vault_go_archive_import', {
515
+ title: 'Importar arquivo em quatro grupos',
516
+ description: 'Importa observations, sessions, summaries e prompts para um projeto existente.',
517
+ inputSchema: {
518
+ projectId: uuid,
519
+ observations: archiveGroup.default([]),
520
+ sessions: archiveGroup.default([]),
521
+ summaries: archiveGroup.default([]),
522
+ prompts: archiveGroup.default([]),
523
+ },
524
+ annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: true },
525
+ }, async (input) => run(() => {
526
+ const records = flattenArchive({
527
+ version: 1,
528
+ format: 'vault-memory-groups',
529
+ observations: input.observations,
530
+ sessions: input.sessions,
531
+ summaries: input.summaries,
532
+ prompts: input.prompts,
533
+ }).map(record => ({
534
+ sourceId: record.sourceId,
535
+ kind: record.kind,
536
+ content: record.content,
537
+ memoryType: record.memoryType ?? 'discovery',
538
+ facts: record.facts ?? [],
539
+ concepts: record.concepts ?? [],
540
+ filesRead: record.filesRead ?? [],
541
+ filesModified: record.filesModified ?? [],
542
+ ...(record.title ? { title: record.title } : {}),
543
+ ...(record.sessionId ? { sessionId: record.sessionId } : {}),
544
+ }));
545
+ if (!records.length)
546
+ throw new Error('O arquivo não tem registros.');
547
+ return cloud.importMemories({ projectId: input.projectId, records });
548
+ }));
549
+ server.registerTool('vault_go_agent_cost_report', {
550
+ title: 'Estimar custo de transcripts',
551
+ description: 'Lê usage em JSONL locais e devolve tokens e um valor estimado. Sem usage, o valor fica indisponível.',
552
+ inputSchema: { root: z.string().trim().min(1).max(2_000) },
553
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
554
+ }, async ({ root }) => run(async () => estimateAgentCost(root)));
489
555
  server.registerTool('vault_go_embedding_status', {
490
556
  title: 'Status da busca semântica',
491
557
  description: 'Mostra provedor, modelo e fila de vetores sem revelar a chave.',
@@ -0,0 +1,6 @@
1
+ import type { VaultMemoryApi } from './cloud.js';
2
+ export declare function downloadSkillArchive(cloud: VaultMemoryApi, id: string, versionId?: string, fetcher?: typeof fetch): Promise<{
3
+ bytes: Uint8Array;
4
+ sha256: string;
5
+ versionId: string;
6
+ }>;
@@ -0,0 +1,48 @@
1
+ const MAX_ARCHIVE_BYTES = 40 * 1024 * 1024;
2
+ function record(value) {
3
+ if (!value || typeof value !== 'object' || Array.isArray(value))
4
+ throw new Error('Resposta de download inválida.');
5
+ return value;
6
+ }
7
+ export async function downloadSkillArchive(cloud, id, versionId, fetcher = globalThis.fetch) {
8
+ if (!cloud.skillDownloadGrant)
9
+ throw new Error('Este Vault não oferece download de skills.');
10
+ const grant = record(await cloud.skillDownloadGrant(id, versionId));
11
+ const link = record(grant.grant);
12
+ const version = record(grant.version);
13
+ if (typeof link.url !== 'string' || typeof version.id !== 'string' || typeof version.archiveSha256 !== 'string')
14
+ throw new Error('Resposta de download inválida.');
15
+ const url = new URL(link.url);
16
+ if (url.protocol !== 'https:')
17
+ throw new Error('Download inseguro da skill: HTTPS é obrigatório.');
18
+ const response = await fetcher(url, { redirect: 'error', signal: AbortSignal.timeout(60_000) });
19
+ if (!response.ok)
20
+ throw new Error(`Download da skill falhou (HTTP ${response.status}).`);
21
+ const declared = Number(response.headers.get('content-length'));
22
+ if (declared > MAX_ARCHIVE_BYTES) {
23
+ await response.body?.cancel();
24
+ throw new Error('Pacote acima de 40 MiB.');
25
+ }
26
+ if (!response.body)
27
+ throw new Error('Download vazio da skill.');
28
+ const reader = response.body.getReader();
29
+ const chunks = [];
30
+ let total = 0;
31
+ try {
32
+ for (;;) {
33
+ const next = await reader.read();
34
+ if (next.done)
35
+ break;
36
+ total += next.value.byteLength;
37
+ if (total > MAX_ARCHIVE_BYTES) {
38
+ await reader.cancel();
39
+ throw new Error('Pacote acima de 40 MiB.');
40
+ }
41
+ chunks.push(next.value);
42
+ }
43
+ }
44
+ finally {
45
+ reader.releaseLock();
46
+ }
47
+ return { bytes: Buffer.concat(chunks, total), sha256: version.archiveSha256, versionId: version.id };
48
+ }
@@ -0,0 +1,8 @@
1
+ import type { VaultMemoryApi } from './cloud.js';
2
+ export type AccountSkillSummary = {
3
+ id: string;
4
+ name: string;
5
+ folderPath?: string | null;
6
+ [key: string]: unknown;
7
+ };
8
+ export declare function listAllSkillSummaries(cloud: VaultMemoryApi, query?: Record<string, unknown>): Promise<AccountSkillSummary[]>;
@@ -0,0 +1,32 @@
1
+ export async function listAllSkillSummaries(cloud, query = {}) {
2
+ if (!cloud.skills)
3
+ throw new Error('Este Vault não oferece a biblioteca de skills.');
4
+ const output = [];
5
+ const cursors = new Set();
6
+ let cursor;
7
+ for (let page = 0; page < 20; page++) {
8
+ const response = await cloud.skills({ ...query, limit: 100, ...(cursor ? { cursor } : {}) });
9
+ if (!response || typeof response !== 'object' || Array.isArray(response))
10
+ throw new Error('Resposta inválida da biblioteca de skills.');
11
+ const body = response;
12
+ if (!Array.isArray(body.skills))
13
+ throw new Error('Resposta inválida da biblioteca de skills.');
14
+ for (const item of body.skills) {
15
+ if (!item || typeof item !== 'object' || Array.isArray(item))
16
+ throw new Error('Resposta inválida da biblioteca de skills.');
17
+ const skill = item;
18
+ if (typeof skill.id !== 'string' || typeof skill.name !== 'string' ||
19
+ (skill.folderPath !== null && skill.folderPath !== undefined && typeof skill.folderPath !== 'string')) {
20
+ throw new Error('Resposta inválida da biblioteca de skills.');
21
+ }
22
+ output.push(skill);
23
+ }
24
+ if (body.nextCursor == null)
25
+ return output;
26
+ if (typeof body.nextCursor !== 'string' || !body.nextCursor || cursors.has(body.nextCursor))
27
+ throw new Error('Paginação inválida da biblioteca de skills.');
28
+ cursor = body.nextCursor;
29
+ cursors.add(cursor);
30
+ }
31
+ throw new Error('Biblioteca de skills excedeu o limite de páginas.');
32
+ }
@@ -2,6 +2,8 @@ export declare const SKILL_MARKER = ".vault-skill.json";
2
2
  export declare const SKILL_FILE_MAX_BYTES: number;
3
3
  export declare const SKILL_MAX_FILES = 512;
4
4
  export declare const SKILL_TOTAL_MAX_BYTES: number;
5
+ /** Caminhos do manifesto de bundle (várias skills num tar): o Vault e o herdado. */
6
+ export declare const SKILL_BUNDLE_MANIFEST_PATHS: readonly string[];
5
7
  export declare class SkillLocalError extends Error {
6
8
  code: string;
7
9
  constructor(code: string, message: string);
@@ -23,6 +25,11 @@ export interface InstalledSkill {
23
25
  skillId: string;
24
26
  versionId: string;
25
27
  }
28
+ export declare function inspectSkillDestination(root: string, dirName: string, skillId: string): {
29
+ status: 'missing' | 'managed' | 'conflict';
30
+ path: string;
31
+ reason?: 'unmanaged' | 'foreign';
32
+ };
26
33
  /** name/description do frontmatter; valida só o formato mínimo (o servidor valida o resto). */
27
34
  export declare function frontmatter(md: string): {
28
35
  name: string;
@@ -6,6 +6,7 @@ import { createHash, randomBytes } from 'node:crypto';
6
6
  import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
7
7
  import { dirname, join, relative, resolve, sep } from 'node:path';
8
8
  import { gunzipSync } from 'node:zlib';
9
+ import { LEGACY_SKILL_BUNDLE_MANIFEST_PATH } from './legacy-brand.js';
9
10
  export const SKILL_MARKER = '.vault-skill.json';
10
11
  export const SKILL_FILE_MAX_BYTES = 4 * 1024 * 1024;
11
12
  export const SKILL_MAX_FILES = 512;
@@ -13,6 +14,8 @@ export const SKILL_TOTAL_MAX_BYTES = 32 * 1024 * 1024;
13
14
  const EXPANDED_MAX_BYTES = 40 * 1024 * 1024;
14
15
  const IGNORED = new Set(['.git', 'node_modules', '.DS_Store', SKILL_MARKER]);
15
16
  const NAME = /^[a-z0-9][a-z0-9.-]{0,63}$/;
17
+ /** Caminhos do manifesto de bundle (várias skills num tar): o Vault e o herdado. */
18
+ export const SKILL_BUNDLE_MANIFEST_PATHS = ['dev.vault.skill-sharing/manifest.json', LEGACY_SKILL_BUNDLE_MANIFEST_PATH];
16
19
  export class SkillLocalError extends Error {
17
20
  code;
18
21
  constructor(code, message) {
@@ -21,6 +24,40 @@ export class SkillLocalError extends Error {
21
24
  }
22
25
  }
23
26
  const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex');
27
+ export function inspectSkillDestination(root, dirName, skillId) {
28
+ if (!NAME.test(dirName) || dirName.includes('..'))
29
+ throw new SkillLocalError('skill_install_invalid', `Nome de pasta inválido: ${dirName}`);
30
+ const path = join(resolve(root), dirName);
31
+ let target;
32
+ try {
33
+ target = lstatSync(path);
34
+ }
35
+ catch (error) {
36
+ if (error.code === 'ENOENT')
37
+ return { status: 'missing', path };
38
+ throw error;
39
+ }
40
+ if (!target.isDirectory() || target.isSymbolicLink())
41
+ return { status: 'conflict', path, reason: 'unmanaged' };
42
+ const markerPath = join(path, SKILL_MARKER);
43
+ try {
44
+ const marker = lstatSync(markerPath);
45
+ if (!marker.isFile() || marker.isSymbolicLink())
46
+ return { status: 'conflict', path, reason: 'unmanaged' };
47
+ const data = JSON.parse(readFileSync(markerPath, 'utf8'));
48
+ if (!data || typeof data !== 'object' || Array.isArray(data))
49
+ return { status: 'conflict', path, reason: 'unmanaged' };
50
+ const skillIdInMarker = data.skillId;
51
+ return skillIdInMarker === skillId
52
+ ? { status: 'managed', path }
53
+ : { status: 'conflict', path, reason: typeof skillIdInMarker === 'string' ? 'foreign' : 'unmanaged' };
54
+ }
55
+ catch (error) {
56
+ if (error.code === 'ENOENT' || error instanceof SyntaxError)
57
+ return { status: 'conflict', path, reason: 'unmanaged' };
58
+ throw error;
59
+ }
60
+ }
24
61
  /** name/description do frontmatter; valida só o formato mínimo (o servidor valida o resto). */
25
62
  export function frontmatter(md) {
26
63
  const block = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(md)?.[1];
@@ -122,7 +159,7 @@ export function readTarGz(archive) {
122
159
  function verifiedFiles(entries) {
123
160
  const integrity = (detail) => new SkillLocalError('skill_install_integrity', `Falha de integridade do pacote: ${detail}`);
124
161
  const byPath = new Map(entries.map(e => [e.path, e]));
125
- if (byPath.has('dev.orca.skill-sharing/manifest.json'))
162
+ if (SKILL_BUNDLE_MANIFEST_PATHS.some(path => byPath.has(path)))
126
163
  throw new SkillLocalError('skill_install_unsupported', 'Pacotes com várias skills (bundle) devem ser instalados pelo Vault Desktop');
127
164
  const raw = byPath.get('manifest.json');
128
165
  if (!raw)
@@ -150,16 +187,16 @@ function verifiedFiles(entries) {
150
187
  });
151
188
  }
152
189
  export function installSkillArchive(input) {
153
- if (!NAME.test(input.dirName) || input.dirName.includes('..'))
154
- throw new SkillLocalError('skill_install_invalid', `Nome de pasta inválido: ${input.dirName}`);
155
190
  if (sha256(input.bytes) !== input.expectedSha256)
156
191
  throw new SkillLocalError('skill_install_integrity', 'Falha de integridade do pacote: sha256');
157
192
  const files = verifiedFiles(readTarGz(input.bytes));
158
193
  const root = resolve(input.root);
159
- const target = join(root, input.dirName);
160
- if (existsSync(target) && !existsSync(join(target, SKILL_MARKER))) {
161
- throw new SkillLocalError('skill_install_conflict', `${target} já existe e não foi instalada pelo Vault; nada foi alterado`);
162
- }
194
+ const destination = inspectSkillDestination(root, input.dirName, input.skillId);
195
+ const target = destination.path;
196
+ if (destination.status === 'conflict')
197
+ throw new SkillLocalError('skill_install_conflict', destination.reason === 'foreign'
198
+ ? `${target} pertence a outra skill Vault; nada foi alterado`
199
+ : `${target} já existe e não foi instalada pelo Vault; nada foi alterado`);
163
200
  mkdirSync(root, { recursive: true, mode: 0o700 });
164
201
  const token = `${process.pid}-${randomBytes(4).toString('hex')}`;
165
202
  const staging = join(root, `.${input.dirName}.vault-tmp-${token}`);
@@ -7,6 +7,8 @@ import { findWorkspaceProject } from './hooks.js';
7
7
  import { run } from './mcp-result.js';
8
8
  import { clip, skillContextSection } from './skill-context.js';
9
9
  import { frontmatter, installedSkills, installSkillArchive, readSourceDir } from './skill-local.js';
10
+ import { downloadSkillArchive } from './skill-download.js';
11
+ import { listAllSkillSummaries } from './skill-library.js';
10
12
  export { skillContextSection };
11
13
  export const SKILL_TEXT_MAX = 64 * 1024;
12
14
  const SOURCE = 'biblioteca privada de skills do usuário (Vault)';
@@ -39,16 +41,7 @@ export function folderByPath(folders, path) {
39
41
  return folders.find(f => f.path.toLowerCase() === normalized.toLowerCase());
40
42
  }
41
43
  async function listAll(cloud, query) {
42
- const out = [];
43
- let cursor;
44
- for (let i = 0; i < 20; i++) {
45
- const result = record(await api(cloud, 'skills')({ ...query, limit: 100, ...(cursor ? { cursor } : {}) }));
46
- out.push(...(Array.isArray(result.skills) ? result.skills : []));
47
- if (typeof result.nextCursor !== 'string')
48
- break;
49
- cursor = result.nextCursor;
50
- }
51
- return out;
44
+ return await listAllSkillSummaries(cloud, query);
52
45
  }
53
46
  /** Aceita o id da skill, `pasta/sub/nome` ou só `nome` (quando for único). */
54
47
  export async function resolveSkill(cloud, ref) {
@@ -82,7 +75,6 @@ export async function currentProject(cloud, cwd) {
82
75
  return null;
83
76
  }
84
77
  }
85
- const ARCHIVE_MAX_BYTES = 40 * 1024 * 1024;
86
78
  export const SKILL_TARGETS = ['claude', 'grok', 'agents'];
87
79
  export function targetRoot(target, userHome = homedir()) {
88
80
  return target === 'agents' ? join(userHome, '.agents', 'skills') : join(userHome, `.${target}`, 'skills');
@@ -301,17 +293,7 @@ export function registerSkillTools(server, home, cloud, options = {}) {
301
293
  const fetcher = options.fetch ?? globalThis.fetch;
302
294
  const targets = z.array(z.enum(SKILL_TARGETS)).min(1).max(3).default(['claude']);
303
295
  async function download(id, versionId) {
304
- const grant = record(await api(cloud, 'skillDownloadGrant')(id, versionId));
305
- const url = record(grant.grant).url, version = record(grant.version);
306
- if (typeof url !== 'string' || typeof version.archiveSha256 !== 'string' || typeof version.id !== 'string')
307
- throw new Error('Resposta de download inválida.');
308
- const response = await fetcher(url, { redirect: 'error', signal: AbortSignal.timeout(60_000) });
309
- if (!response.ok)
310
- throw new Error(`Download da skill falhou (HTTP ${response.status}).`);
311
- const bytes = new Uint8Array(await response.arrayBuffer());
312
- if (bytes.length > ARCHIVE_MAX_BYTES)
313
- throw new Error('Pacote acima de 40 MiB.');
314
- return { bytes, sha256: version.archiveSha256, versionId: version.id };
296
+ return downloadSkillArchive(cloud, id, versionId, fetcher);
315
297
  }
316
298
  async function installInto(id, name, roots, versionId) {
317
299
  const archive = await download(id, versionId);
@@ -335,7 +317,7 @@ export function registerSkillTools(server, home, cloud, options = {}) {
335
317
  inputSchema: { currentProject: z.boolean().optional(), projectId: z.string().trim().min(1).max(128).optional(), targets },
336
318
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
337
319
  }, async (input) => run(async () => {
338
- const roots = SKILL_TARGETS.map(t => targetRoot(t, userHome));
320
+ const roots = input.targets.map(t => targetRoot(t, userHome));
339
321
  const installed = installedSkills(roots);
340
322
  const updated = [], missing = [], added = [];
341
323
  for (const item of installed) {
@@ -357,11 +339,16 @@ export function registerSkillTools(server, home, cloud, options = {}) {
357
339
  if (input.currentProject && !projectId)
358
340
  throw new Error('Nenhum projeto do Vault corresponde ao diretório atual.');
359
341
  if (projectId) {
360
- const have = new Set(installedSkills(roots).map(i => i.skillId));
361
- const linked = (await listAll(cloud, { projectId })).filter(s => s.scope === 'project' && !have.has(s.id));
342
+ const have = new Set(installedSkills(roots).map(i => `${i.skillId}\0${i.root}`));
343
+ const linked = (await listAll(cloud, { projectId })).filter(s => s.scope === 'project');
362
344
  for (const skill of linked) {
363
- const done = await installInto(skill.id, skill.name, input.targets.map(t => targetRoot(t, userHome)));
345
+ const missingRoots = roots.filter(root => !have.has(`${skill.id}\0${root}`));
346
+ if (!missingRoots.length)
347
+ continue;
348
+ const done = await installInto(skill.id, skill.name, missingRoots);
364
349
  added.push(...done.map(d => ({ skill: skill.name, path: d.path, versionId: d.versionId })));
350
+ for (const root of missingRoots)
351
+ have.add(`${skill.id}\0${root}`);
365
352
  }
366
353
  }
367
354
  return { updated, installed: added, missing };
@@ -0,0 +1,31 @@
1
+ import type { VaultMemoryApi } from './cloud.js';
2
+ type Target = 'agents' | 'claude' | 'grok';
3
+ type ListedSkill = {
4
+ id: string;
5
+ name: string;
6
+ folderPath: string | null;
7
+ };
8
+ type InstalledSkillResult = {
9
+ name: string;
10
+ target: Target;
11
+ path: string;
12
+ versionId: string;
13
+ };
14
+ type SkippedSkillResult = {
15
+ name: string;
16
+ target: Target;
17
+ path: string;
18
+ reason: 'unmanaged' | 'foreign';
19
+ };
20
+ export type SkillsCommandResult = {
21
+ skills: ListedSkill[];
22
+ installed: InstalledSkillResult[];
23
+ skipped: SkippedSkillResult[];
24
+ };
25
+ type Dependencies = {
26
+ cloud: VaultMemoryApi;
27
+ userHome?: string;
28
+ fetcher?: typeof fetch;
29
+ };
30
+ export declare function executeSkillsCommand(args: string[], dependencies: Dependencies): Promise<SkillsCommandResult>;
31
+ export {};
@@ -0,0 +1,90 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ import { inspectSkillDestination, installSkillArchive } from './skill-local.js';
4
+ import { downloadSkillArchive } from './skill-download.js';
5
+ import { listAllSkillSummaries } from './skill-library.js';
6
+ async function listSkills(cloud) {
7
+ return (await listAllSkillSummaries(cloud)).map(skill => ({ id: skill.id, name: skill.name, folderPath: skill.folderPath ?? null }));
8
+ }
9
+ function parse(args) {
10
+ const action = args[0] ?? 'list';
11
+ if (action !== 'list' && action !== 'install')
12
+ throw new Error('Use vault-go skills list ou vault-go skills install --skill <nome>|--all.');
13
+ let all = false;
14
+ let target = 'agents';
15
+ const refs = [];
16
+ for (let index = 1; index < args.length; index++) {
17
+ const flag = args[index];
18
+ if (flag === '--all') {
19
+ all = true;
20
+ continue;
21
+ }
22
+ if (flag === '--skill' || flag === '--target') {
23
+ const value = args[++index];
24
+ if (!value || value.startsWith('--'))
25
+ throw new Error(`${flag} exige um valor.`);
26
+ if (flag === '--skill')
27
+ refs.push(value);
28
+ else if (value === 'agents' || value === 'claude' || value === 'grok')
29
+ target = value;
30
+ else
31
+ throw new Error('--target deve ser agents, claude ou grok.');
32
+ continue;
33
+ }
34
+ throw new Error(`Opção desconhecida: ${flag}`);
35
+ }
36
+ if (action === 'list' && (all || refs.length || target !== 'agents'))
37
+ throw new Error('vault-go skills list não aceita opções de instalação.');
38
+ if (action === 'install' && (all === (refs.length > 0)))
39
+ throw new Error('Use --all ou pelo menos um --skill.');
40
+ return { action, all, refs, target };
41
+ }
42
+ function select(skills, all, refs) {
43
+ const selected = all ? skills : refs.map(ref => {
44
+ const matches = skills.filter(skill => [skill.id, skill.name, `${skill.folderPath ? `${skill.folderPath}/` : ''}${skill.name}`].some(value => value.toLowerCase() === ref.toLowerCase()));
45
+ if (!matches.length)
46
+ throw new Error(`Skill não encontrada: ${ref}`);
47
+ if (matches.length > 1)
48
+ throw new Error(`Skill ambígua: ${ref}. Informe pasta/nome.`);
49
+ return matches[0];
50
+ });
51
+ const unique = [...new Map(selected.map(skill => [skill.id, skill])).values()];
52
+ const names = new Set();
53
+ for (const skill of unique) {
54
+ const name = skill.name.toLowerCase();
55
+ if (names.has(name))
56
+ throw new Error(`Mais de uma skill usa a pasta ${skill.name}; selecione uma por vez.`);
57
+ names.add(name);
58
+ }
59
+ return unique;
60
+ }
61
+ export async function executeSkillsCommand(args, dependencies) {
62
+ const command = parse(args);
63
+ const skills = await listSkills(dependencies.cloud);
64
+ if (command.action === 'list')
65
+ return { skills, installed: [], skipped: [] };
66
+ if (!dependencies.cloud.skillDownloadGrant)
67
+ throw new Error('Este Vault não oferece download de skills.');
68
+ const selected = select(skills, command.all, command.refs);
69
+ const root = command.target === 'agents'
70
+ ? join(dependencies.userHome ?? homedir(), '.agents', 'skills')
71
+ : join(dependencies.userHome ?? homedir(), `.${command.target}`, 'skills');
72
+ const fetcher = dependencies.fetcher ?? globalThis.fetch;
73
+ const installed = [];
74
+ const skipped = [];
75
+ const planned = selected.flatMap(skill => {
76
+ const destination = inspectSkillDestination(root, skill.name, skill.id);
77
+ if (destination.status !== 'conflict')
78
+ return [skill];
79
+ if (!command.all)
80
+ throw new Error(`${destination.path} já existe; skill ${skill.name} não foi instalada.`);
81
+ skipped.push({ name: skill.name, target: command.target, path: destination.path, reason: destination.reason });
82
+ return [];
83
+ });
84
+ for (const skill of planned) {
85
+ const downloaded = await downloadSkillArchive(dependencies.cloud, skill.id, undefined, fetcher);
86
+ const result = installSkillArchive({ bytes: downloaded.bytes, expectedSha256: downloaded.sha256, dirName: skill.name, root, skillId: skill.id, versionId: downloaded.versionId });
87
+ installed.push({ name: skill.name, target: command.target, path: result.path, versionId: downloaded.versionId });
88
+ }
89
+ return { skills: [], installed, skipped };
90
+ }
@@ -1,4 +1,4 @@
1
- export type TranscriptAdapter = 'claude' | 'codex';
1
+ export type TranscriptAdapter = 'claude' | 'codex' | 'cursor';
2
2
  export interface TranscriptTarget {
3
3
  id: string;
4
4
  adapter: TranscriptAdapter;
@@ -44,10 +44,11 @@ function load(home) {
44
44
  const value = readPrivateJson(home, 'transcript-watchers.json', 100_000);
45
45
  if (value?.version !== 1 || value.owner !== captureOwner(home) || !Array.isArray(value.targets))
46
46
  return { version: 1, owner: captureOwner(home), targets: [] };
47
- return { version: 1, owner: value.owner, targets: value.targets.filter(t => t && typeof t.id === 'string' && ['claude', 'codex'].includes(t.adapter) && typeof t.root === 'string' && isAbsolute(t.root) && typeof t.workspace === 'string' && isAbsolute(t.workspace) && typeof t.since === 'number').slice(0, 20) };
47
+ return { version: 1, owner: value.owner, targets: value.targets.filter(t => t && typeof t.id === 'string' && ['claude', 'codex', 'cursor'].includes(t.adapter) && typeof t.root === 'string' && isAbsolute(t.root) && typeof t.workspace === 'string' && isAbsolute(t.workspace) && typeof t.since === 'number').slice(0, 20) };
48
48
  }
49
49
  export function defaultTranscriptRoot(adapter, userHome = homedir()) {
50
- return join(userHome, adapter === 'claude' ? '.claude/projects' : '.codex/sessions');
50
+ const folder = adapter === 'claude' ? '.claude/projects' : adapter === 'cursor' ? '.cursor/projects' : '.codex/sessions';
51
+ return join(userHome, folder);
51
52
  }
52
53
  export function transcriptStatus(home) {
53
54
  const config = load(home);
@@ -64,7 +65,7 @@ export function transcriptCaptureEnabled(home, adapter, cwd) {
64
65
  return load(home).targets.some(t => t.enabled && t.adapter === adapter && t.workspace === workspace);
65
66
  }
66
67
  export function configureTranscriptWatcher(home, input) {
67
- if (!['claude', 'codex'].includes(input.adapter) || !isAbsolute(input.workspace))
68
+ if (!['claude', 'codex', 'cursor'].includes(input.adapter) || !isAbsolute(input.workspace))
68
69
  throw new Error('Adaptador ou workspace inválido. Use um caminho absoluto.');
69
70
  if (input.enabled && input.consent !== true)
70
71
  throw new Error('Ativar exige consent=true para capturar transcripts deste workspace.');
@@ -63,7 +63,7 @@ export function parseTranscript(adapter, value, previous, offset) {
63
63
  if (cwd)
64
64
  state.cwd = cwd;
65
65
  }
66
- if (adapter === 'claude') {
66
+ if (adapter === 'claude' || adapter === 'cursor') {
67
67
  scope(text(row.sessionId), text(row.cwd));
68
68
  if (row.private === true || row.isPrivate === true || row.is_private === true) {
69
69
  state.private = true;
@@ -8,7 +8,7 @@ export function registerTranscriptTools(server, home, cloud) {
8
8
  server.registerTool('vault_go_transcripts_status', { description: 'Watchers opt-in de Claude/Codex neste dispositivo e conta. Requer automaticCapture para enviar eventos; geração usa o motor já escolhido.', inputSchema: {}, annotations: { readOnlyHint: true, openWorldHint: false } }, () => run(async () => transcriptStatus(home)));
9
9
  server.registerTool('vault_go_transcripts_configure', {
10
10
  description: 'Ativa/desativa captura persistente de um workspace com consentimento explícito. Por padrão ignora histórico existente; fromStart importa histórico. Hooks nativos preservam contexto e cedem escrita ao watcher enquanto ativo. O worker local precisa estar em execução.',
11
- inputSchema: { adapter: z.enum(['claude', 'codex']), workspace: path, root: path.optional(), enabled: z.boolean(), consent: z.boolean().default(false), fromStart: z.boolean().default(false) },
11
+ inputSchema: { adapter: z.enum(['claude', 'codex', 'cursor']), workspace: path, root: path.optional(), enabled: z.boolean(), consent: z.boolean().default(false), fromStart: z.boolean().default(false) },
12
12
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
13
13
  }, input => run(async () => { const { root, ...rest } = input; return configureTranscriptWatcher(home, { ...rest, ...(root === undefined ? {} : { root }) }); }));
14
14
  server.registerTool('vault_go_transcripts_poll', { description: 'Processa um lote limitado dos watchers já consentidos. Retomada e idempotência persistem entre reinícios; não ativa captura nem muda o motor.', inputSchema: {}, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true } }, () => run(() => pollTranscripts(home, cloud)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vault-go",
3
- "version": "0.30.0",
3
+ "version": "0.31.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": {
@@ -30,8 +30,16 @@
30
30
  "dist/skill-context.d.ts",
31
31
  "dist/skill-local.js",
32
32
  "dist/skill-local.d.ts",
33
+ "dist/legacy-brand.js",
34
+ "dist/legacy-brand.d.ts",
33
35
  "dist/skill-tools.js",
34
36
  "dist/skill-tools.d.ts",
37
+ "dist/skill-download.js",
38
+ "dist/skill-download.d.ts",
39
+ "dist/skill-library.js",
40
+ "dist/skill-library.d.ts",
41
+ "dist/skills-cli.js",
42
+ "dist/skills-cli.d.ts",
35
43
  "dist/activity.js",
36
44
  "dist/activity.d.ts",
37
45
  "dist/auth.js",
@@ -139,6 +147,10 @@
139
147
  "dist/awareness-tools.d.ts",
140
148
  "dist/skill-install.js",
141
149
  "dist/skill-install.d.ts",
150
+ "dist/memory-archive.js",
151
+ "dist/memory-archive.d.ts",
152
+ "dist/agent-cost.js",
153
+ "dist/agent-cost.d.ts",
142
154
  "grammars/"
143
155
  ],
144
156
  "scripts": {