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.
- package/README.md +12 -0
- package/dist/agent-cost.d.ts +11 -0
- package/dist/agent-cost.js +116 -0
- package/dist/auth.js +29 -2
- package/dist/capabilities.d.ts +30 -24
- package/dist/capabilities.js +7 -6
- package/dist/capture-privacy.js +1 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +16 -1
- package/dist/cloud.js +98 -47
- package/dist/config.d.ts +2 -0
- package/dist/config.js +15 -1
- package/dist/crystal-memory-mirror.d.ts +14 -1
- package/dist/crystal-memory-mirror.js +24 -15
- package/dist/crystal-tools.d.ts +24 -1
- package/dist/crystal-tools.js +30 -5
- package/dist/grok-awareness.js +54 -1
- package/dist/hook-install.d.ts +4 -0
- package/dist/hook-install.js +41 -1
- package/dist/hook-queue.d.ts +18 -0
- package/dist/hook-queue.js +115 -3
- package/dist/hook-runner.js +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +17 -8
- package/dist/index.js +5 -1
- package/dist/installer.js +8 -1
- package/dist/legacy-brand.d.ts +2 -0
- package/dist/legacy-brand.js +5 -0
- package/dist/local-service.js +11 -0
- package/dist/memory-archive.d.ts +25 -0
- package/dist/memory-archive.js +65 -0
- package/dist/modes.d.ts +1 -1
- package/dist/modes.js +3 -1
- package/dist/opencode-plugin.d.ts +29 -1
- package/dist/opencode-plugin.js +36 -1
- package/dist/server.js +66 -0
- package/dist/skill-download.d.ts +6 -0
- package/dist/skill-download.js +48 -0
- package/dist/skill-library.d.ts +8 -0
- package/dist/skill-library.js +32 -0
- package/dist/skill-local.d.ts +7 -0
- package/dist/skill-local.js +44 -7
- package/dist/skill-tools.js +13 -26
- package/dist/skills-cli.d.ts +31 -0
- package/dist/skills-cli.js +90 -0
- package/dist/transcript-config.d.ts +1 -1
- package/dist/transcript-config.js +4 -3
- package/dist/transcript-parser.js +1 -1
- package/dist/transcript-tools.js +1 -1
- 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,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
|
+
}
|
package/dist/skill-local.d.ts
CHANGED
|
@@ -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;
|
package/dist/skill-local.js
CHANGED
|
@@ -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(
|
|
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
|
|
160
|
-
|
|
161
|
-
|
|
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}`);
|
package/dist/skill-tools.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 =
|
|
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'
|
|
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
|
|
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
|
+
}
|
|
@@ -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
|
-
|
|
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;
|
package/dist/transcript-tools.js
CHANGED
|
@@ -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.
|
|
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": {
|