vault-go 0.20.0 → 0.22.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 +58 -9
- package/dist/awareness-tools.d.ts +4 -0
- package/dist/awareness-tools.js +38 -0
- package/dist/backup.d.ts +41 -0
- package/dist/backup.js +117 -0
- package/dist/capabilities.d.ts +218 -25
- package/dist/capabilities.js +46 -16
- package/dist/capture-privacy.d.ts +1 -0
- package/dist/capture-privacy.js +19 -0
- package/dist/ccs-align.d.ts +122 -0
- package/dist/ccs-align.js +376 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +52 -0
- package/dist/cloud.d.ts +15 -2
- package/dist/cloud.js +62 -4
- package/dist/context-chain.d.ts +37 -0
- package/dist/context-chain.js +98 -0
- package/dist/context-engines.d.ts +6 -0
- package/dist/context-engines.js +28 -8
- package/dist/crystal-tools.d.ts +9 -0
- package/dist/crystal-tools.js +106 -0
- package/dist/custom-modes.d.ts +59 -0
- package/dist/custom-modes.js +249 -0
- package/dist/deployment-verification.d.ts +19 -0
- package/dist/deployment-verification.js +171 -0
- package/dist/grok-awareness.d.ts +51 -0
- package/dist/grok-awareness.js +244 -0
- package/dist/hook-install.d.ts +7 -2
- package/dist/hook-install.js +71 -15
- package/dist/hook-queue.d.ts +6 -2
- package/dist/hook-queue.js +23 -4
- package/dist/hooks.d.ts +4 -3
- package/dist/hooks.js +83 -32
- package/dist/index.js +9 -1
- package/dist/installer.js +11 -1
- package/dist/knowledge-agent.d.ts +39 -0
- package/dist/knowledge-agent.js +143 -0
- package/dist/knowledge-tools.d.ts +3 -0
- package/dist/knowledge-tools.js +19 -0
- package/dist/local-service.js +6 -1
- package/dist/mode-integrations.d.ts +9 -0
- package/dist/mode-integrations.js +149 -0
- package/dist/modes.js +2 -2
- package/dist/opencode-plugin.d.ts +16 -0
- package/dist/opencode-plugin.js +73 -0
- package/dist/parity-tools.js +4 -4
- package/dist/server.js +56 -8
- package/dist/smart-code.d.ts +12 -16
- package/dist/smart-code.js +139 -28
- package/dist/telegram.d.ts +39 -0
- package/dist/telegram.js +138 -0
- package/dist/transcript-config.d.ts +41 -0
- package/dist/transcript-config.js +97 -0
- package/dist/transcript-parser.d.ts +24 -0
- package/dist/transcript-parser.js +118 -0
- package/dist/transcript-tools.d.ts +3 -0
- package/dist/transcript-tools.js +21 -0
- package/dist/transcript-watchers.d.ts +13 -0
- package/dist/transcript-watchers.js +311 -0
- package/dist/update-control.d.ts +2 -0
- package/dist/update-control.js +19 -2
- package/docs/ast-and-awareness.md +72 -0
- package/docs/mode-integrations.md +74 -0
- package/docs/parity-deployment-review.md +60 -0
- package/docs/transcripts-and-context.md +75 -0
- package/package.json +62 -2
package/dist/smart-code.js
CHANGED
|
@@ -1,26 +1,77 @@
|
|
|
1
|
-
import { Lang, parse } from '@ast-grep/napi';
|
|
1
|
+
import { Lang, parse, registerDynamicLanguage } from '@ast-grep/napi';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import { readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
-
import { basename, extname, isAbsolute, relative, resolve, sep } from 'node:path';
|
|
4
|
-
|
|
5
|
-
{
|
|
6
|
-
{
|
|
7
|
-
{
|
|
8
|
-
{
|
|
9
|
-
{
|
|
4
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
|
+
const BUILTIN_LANGUAGES = [
|
|
6
|
+
{ name: 'JavaScript', parser: Lang.JavaScript, extensions: ['.js', '.jsx', '.mjs', '.cjs'] },
|
|
7
|
+
{ name: 'TypeScript', parser: Lang.TypeScript, extensions: ['.ts', '.mts', '.cts'] },
|
|
8
|
+
{ name: 'Tsx', parser: Lang.Tsx, extensions: ['.tsx'] },
|
|
9
|
+
{ name: 'Html', parser: Lang.Html, extensions: ['.html', '.htm'] },
|
|
10
|
+
{ name: 'Css', parser: Lang.Css, extensions: ['.css'] },
|
|
10
11
|
];
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
['.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
]
|
|
12
|
+
const DYNAMIC_LANGUAGES = [
|
|
13
|
+
{ name: 'Python', parser: 'python', extensions: ['.py', '.pyw'], packageName: '@ast-grep/lang-python' },
|
|
14
|
+
{ name: 'Go', parser: 'go', extensions: ['.go'], packageName: '@ast-grep/lang-go' },
|
|
15
|
+
{ name: 'Rust', parser: 'rust', extensions: ['.rs'], packageName: '@ast-grep/lang-rust' },
|
|
16
|
+
{ name: 'Ruby', parser: 'ruby', extensions: ['.rb'], packageName: '@ast-grep/lang-ruby' },
|
|
17
|
+
{ name: 'Java', parser: 'java', extensions: ['.java'], packageName: '@ast-grep/lang-java' },
|
|
18
|
+
{ name: 'C', parser: 'c', extensions: ['.c', '.h'], packageName: '@ast-grep/lang-c' },
|
|
19
|
+
{ name: 'Cpp', parser: 'cpp', extensions: ['.cpp', '.cc', '.cxx', '.hpp', '.hh'], packageName: '@ast-grep/lang-cpp' },
|
|
20
|
+
{ name: 'Kotlin', parser: 'kotlin', extensions: ['.kt', '.kts'], packageName: '@ast-grep/lang-kotlin' },
|
|
21
|
+
{ name: 'Swift', parser: 'swift', extensions: ['.swift'], packageName: '@ast-grep/lang-swift' },
|
|
22
|
+
{ name: 'Php', parser: 'php', extensions: ['.php'], packageName: '@ast-grep/lang-php' },
|
|
23
|
+
{ name: 'Lua', parser: 'lua', extensions: ['.lua'], packageName: '@ast-grep/lang-lua' },
|
|
24
|
+
{ name: 'Scala', parser: 'scala', extensions: ['.scala', '.sc'], packageName: '@ast-grep/lang-scala' },
|
|
25
|
+
{ name: 'Bash', parser: 'bash', extensions: ['.sh', '.bash', '.zsh'], packageName: '@ast-grep/lang-bash' },
|
|
26
|
+
{ name: 'Haskell', parser: 'haskell', extensions: ['.hs'], packageName: '@ast-grep/lang-haskell' },
|
|
27
|
+
{ name: 'Toml', parser: 'toml', extensions: ['.toml'], packageName: '@ast-grep/lang-toml' },
|
|
28
|
+
{ name: 'Yaml', parser: 'yaml', extensions: ['.yml', '.yaml'], packageName: '@ast-grep/lang-yaml' },
|
|
29
|
+
{ name: 'Sql', parser: 'sql', extensions: ['.sql'], packageName: '@ast-grep/lang-sql' },
|
|
30
|
+
{ name: 'Markdown', parser: 'markdown', extensions: ['.md', '.mdx'], packageName: '@ast-grep/lang-markdown' },
|
|
31
|
+
{ name: 'Zig', parser: 'zig', extensions: ['.zig'], packageName: '@tree-sitter-grammars/tree-sitter-zig', binding: '@tree-sitter-grammars+tree-sitter-zig' },
|
|
32
|
+
{ name: 'Scss', parser: 'scss', extensions: ['.scss'], packageName: 'tree-sitter-scss', binding: 'tree-sitter-scss' },
|
|
33
|
+
];
|
|
34
|
+
// Register the complete allowlisted set once. No path from a workspace/config is loaded as executable grammar code.
|
|
35
|
+
const grammarRequire = createRequire(import.meta.url);
|
|
36
|
+
const unavailable = new Set();
|
|
37
|
+
const registrations = {};
|
|
38
|
+
for (const language of DYNAMIC_LANGUAGES) {
|
|
39
|
+
try {
|
|
40
|
+
const grammar = language.binding ? {
|
|
41
|
+
libraryPath: join(dirname(grammarRequire.resolve(`${language.packageName}/package.json`)), 'prebuilds', `${process.platform}-${process.arch}`, `${language.binding}.node`),
|
|
42
|
+
extensions: language.extensions.map(ext => ext.slice(1)), languageSymbol: `tree_sitter_${language.parser}`, expandoChar: '_',
|
|
43
|
+
} : grammarRequire(language.packageName);
|
|
44
|
+
statSync(grammar.libraryPath);
|
|
45
|
+
registrations[language.parser] = { ...grammar, libraryPath: grammar.libraryPath };
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
unavailable.add(language.name);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
registerDynamicLanguage(registrations);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
for (const language of DYNAMIC_LANGUAGES)
|
|
56
|
+
unavailable.add(language.name);
|
|
57
|
+
}
|
|
58
|
+
for (const language of DYNAMIC_LANGUAGES) {
|
|
59
|
+
if (!unavailable.has(language.name)) {
|
|
60
|
+
try {
|
|
61
|
+
parse(language.parser, '').root();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
unavailable.add(language.name);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const LANGUAGE_BY_EXTENSION = new Map([...BUILTIN_LANGUAGES, ...DYNAMIC_LANGUAGES].flatMap(language => language.extensions.map(extension => [extension, language])));
|
|
69
|
+
export const SUPPORTED_LANGUAGES = [...BUILTIN_LANGUAGES, ...DYNAMIC_LANGUAGES].filter(language => !unavailable.has(language.name)).map(language => ({ language: language.name, extensions: language.extensions }));
|
|
70
|
+
export function smartLanguageStatus() {
|
|
71
|
+
return [
|
|
72
|
+
...[...BUILTIN_LANGUAGES, ...DYNAMIC_LANGUAGES].map(language => ({ language: language.name, extensions: language.extensions, available: !unavailable.has(language.name), parser: 'tree-sitter/ast-grep', package: language.packageName ?? '@ast-grep/napi', ...(unavailable.has(language.name) ? { reason: 'No compatible installed prebuild; no regex fallback.' } : {}) })),
|
|
73
|
+
];
|
|
74
|
+
}
|
|
24
75
|
export const SMART_CODE_LIMITS = Object.freeze({
|
|
25
76
|
maxFileBytes: 1_048_576,
|
|
26
77
|
maxOutlineSymbols: 1_000,
|
|
@@ -39,6 +90,8 @@ const DENIED_DIRECTORIES = new Set(['.git', 'node_modules', 'build', 'dist', 'co
|
|
|
39
90
|
const TRUNCATION_SUFFIX = '\n… [truncated]';
|
|
40
91
|
function languageFor(filePath) {
|
|
41
92
|
const language = LANGUAGE_BY_EXTENSION.get(extname(filePath).toLowerCase());
|
|
93
|
+
if (language && unavailable.has(language.name))
|
|
94
|
+
throw new Error(`AST grammar unavailable for ${language.name} on ${process.platform}/${process.arch}. Install a compatible prebuild; no regex fallback is used.`);
|
|
42
95
|
if (!language) {
|
|
43
96
|
const extensions = [...LANGUAGE_BY_EXTENSION.keys()].join(', ');
|
|
44
97
|
throw new Error(`Unsupported language for "${filePath}". Supported extensions: ${extensions}`);
|
|
@@ -106,8 +159,14 @@ function sourceLocation(node) {
|
|
|
106
159
|
endColumn: range.end.column + 1,
|
|
107
160
|
};
|
|
108
161
|
}
|
|
109
|
-
const IDENTIFIER_KINDS = new Set(['identifier', 'type_identifier', 'property_identifier', 'private_property_identifier']);
|
|
162
|
+
const IDENTIFIER_KINDS = new Set(['identifier', 'type_identifier', 'property_identifier', 'private_property_identifier', 'field_identifier', 'simple_identifier', 'name', 'word', 'variable', 'constant']);
|
|
110
163
|
function directIdentifier(node) {
|
|
164
|
+
const named = node.field('name');
|
|
165
|
+
if (named)
|
|
166
|
+
return named.text();
|
|
167
|
+
const declarator = node.field('declarator');
|
|
168
|
+
if (declarator)
|
|
169
|
+
return directIdentifier(declarator) ?? (IDENTIFIER_KINDS.has(String(declarator.kind())) ? declarator.text() : null);
|
|
111
170
|
const identifier = node.namedChildren().find((child) => IDENTIFIER_KINDS.has(String(child.kind())));
|
|
112
171
|
return identifier?.text() ?? null;
|
|
113
172
|
}
|
|
@@ -124,12 +183,54 @@ function symbolDescriptor(node) {
|
|
|
124
183
|
type_alias_declaration: 'type',
|
|
125
184
|
enum_declaration: 'enum',
|
|
126
185
|
internal_module: 'namespace',
|
|
186
|
+
function_definition: 'function', function_item: 'function', function_signature_item: 'function', function: 'function',
|
|
187
|
+
class_definition: 'class', class_specifier: 'class', object_definition: 'class', object_declaration: 'class',
|
|
188
|
+
method_declaration: 'method', method: 'method', singleton_method: 'method', constructor_declaration: 'method',
|
|
189
|
+
struct_item: 'struct', struct_specifier: 'struct', enum_item: 'enum', enum_specifier: 'enum',
|
|
190
|
+
trait_item: 'trait', trait_definition: 'trait', trait_declaration: 'trait', protocol_declaration: 'interface',
|
|
191
|
+
type_spec: 'type', type_item: 'type', type_synomym: 'type', type_synonym: 'type', newtype: 'type', data_type: 'type',
|
|
192
|
+
class: 'class', module: 'namespace', function_statement: 'function', mixin_statement: 'mixin',
|
|
127
193
|
};
|
|
128
194
|
const fixedKind = fixedKinds[syntaxKind];
|
|
129
195
|
if (fixedKind) {
|
|
130
196
|
const name = directIdentifier(node);
|
|
131
197
|
return name ? { name, kind: fixedKind } : null;
|
|
132
198
|
}
|
|
199
|
+
if (syntaxKind === 'impl_item') {
|
|
200
|
+
const type = node.field('type');
|
|
201
|
+
return type ? { name: type.text(), kind: 'impl' } : null;
|
|
202
|
+
}
|
|
203
|
+
if (syntaxKind === 'variable_declaration' && node.namedChildren().some(c => ['struct_declaration', 'enum_declaration', 'union_declaration'].includes(String(c.kind())))) {
|
|
204
|
+
const name = directIdentifier(node);
|
|
205
|
+
return name ? { name, kind: 'type' } : null;
|
|
206
|
+
}
|
|
207
|
+
if (syntaxKind === 'table' || syntaxKind === 'table_array_element') {
|
|
208
|
+
const key = node.namedChildren().find(c => ['bare_key', 'dotted_key', 'quoted_key'].includes(String(c.kind())));
|
|
209
|
+
return key ? { name: key.text(), kind: 'section' } : null;
|
|
210
|
+
}
|
|
211
|
+
if (syntaxKind === 'block_mapping_pair') {
|
|
212
|
+
const key = node.field('key');
|
|
213
|
+
return key ? { name: key.text(), kind: 'property' } : null;
|
|
214
|
+
}
|
|
215
|
+
if (['create_table', 'create_view', 'create_function'].includes(syntaxKind)) {
|
|
216
|
+
const name = node.namedChildren().find(c => String(c.kind()) === 'object_reference');
|
|
217
|
+
return name ? { name: name.text(), kind: syntaxKind === 'create_function' ? 'function' : 'class' } : null;
|
|
218
|
+
}
|
|
219
|
+
if (syntaxKind === 'section') {
|
|
220
|
+
const heading = node.namedChildren().find(c => ['atx_heading', 'setext_heading'].includes(String(c.kind())));
|
|
221
|
+
const name = heading?.field('heading_content') ?? heading?.namedChildren().find(c => ['inline', 'paragraph'].includes(String(c.kind())));
|
|
222
|
+
return name ? { name: name.text().trim(), kind: 'section' } : null;
|
|
223
|
+
}
|
|
224
|
+
if (syntaxKind === 'fenced_code_block') {
|
|
225
|
+
const info = node.namedChildren().find(c => String(c.kind()) === 'info_string');
|
|
226
|
+
return { name: info?.text() ?? 'code', kind: 'code' };
|
|
227
|
+
}
|
|
228
|
+
if (syntaxKind === 'minus_metadata')
|
|
229
|
+
return { name: 'frontmatter', kind: 'metadata' };
|
|
230
|
+
if (syntaxKind === 'link_reference_definition') {
|
|
231
|
+
const name = node.namedChildren().find(c => String(c.kind()) === 'link_label');
|
|
232
|
+
return name ? { name: name.text(), kind: 'reference' } : null;
|
|
233
|
+
}
|
|
133
234
|
if (syntaxKind === 'variable_declarator' || syntaxKind === 'public_field_definition') {
|
|
134
235
|
const children = node.namedChildren();
|
|
135
236
|
const value = children.at(-1);
|
|
@@ -153,7 +254,7 @@ function symbolDescriptor(node) {
|
|
|
153
254
|
}
|
|
154
255
|
return null;
|
|
155
256
|
}
|
|
156
|
-
const DIRECT_BODY_KINDS = new Set(['statement_block', 'class_body', 'interface_body', 'enum_body', 'block']);
|
|
257
|
+
const DIRECT_BODY_KINDS = new Set(['statement_block', 'class_body', 'interface_body', 'enum_body', 'block', 'body_statement', 'compound_statement', 'field_declaration_list', 'enumerator_list', 'function_body', 'template_body', 'declaration_list', 'match', 'column_definitions']);
|
|
157
258
|
function functionValueBody(node) {
|
|
158
259
|
if (!['variable_declarator', 'public_field_definition'].includes(String(node.kind())))
|
|
159
260
|
return null;
|
|
@@ -166,10 +267,22 @@ function functionValueBody(node) {
|
|
|
166
267
|
return candidate;
|
|
167
268
|
}
|
|
168
269
|
function signatureFor(node, descriptor) {
|
|
270
|
+
if (String(node.kind()) === 'section')
|
|
271
|
+
return node.namedChildren().find(c => ['atx_heading', 'setext_heading'].includes(String(c.kind())))?.text().trim() ?? descriptor.name;
|
|
272
|
+
if (String(node.kind()) === 'fenced_code_block')
|
|
273
|
+
return `\`\`\`${descriptor.name === 'code' ? '' : descriptor.name}`;
|
|
274
|
+
if (String(node.kind()) === 'minus_metadata')
|
|
275
|
+
return '---frontmatter---';
|
|
276
|
+
if (String(node.kind()) === 'table')
|
|
277
|
+
return `[${descriptor.name}]`;
|
|
278
|
+
if (String(node.kind()) === 'table_array_element')
|
|
279
|
+
return `[[${descriptor.name}]]`;
|
|
280
|
+
if (String(node.kind()) === 'block_mapping_pair')
|
|
281
|
+
return `${descriptor.name}:`;
|
|
169
282
|
if (String(node.kind()) === 'element') {
|
|
170
283
|
return node.namedChildren().find((child) => String(child.kind()) === 'start_tag')?.text() ?? descriptor.name;
|
|
171
284
|
}
|
|
172
|
-
const body = node.namedChildren().find((child) => DIRECT_BODY_KINDS.has(String(child.kind()))) ?? functionValueBody(node);
|
|
285
|
+
const body = node.field('body') ?? node.namedChildren().find((child) => DIRECT_BODY_KINDS.has(String(child.kind()))) ?? functionValueBody(node);
|
|
173
286
|
if (!body)
|
|
174
287
|
return node.text().trim();
|
|
175
288
|
const nodeText = node.text();
|
|
@@ -177,9 +290,6 @@ function signatureFor(node, descriptor) {
|
|
|
177
290
|
const bodyOffset = nodeText.lastIndexOf(bodyText);
|
|
178
291
|
if (bodyOffset < 0)
|
|
179
292
|
return nodeText.trim();
|
|
180
|
-
const trailingText = nodeText.slice(bodyOffset + bodyText.length).trim();
|
|
181
|
-
if (trailingText !== '' && trailingText !== ';')
|
|
182
|
-
return nodeText.trim();
|
|
183
293
|
return nodeText.slice(0, bodyOffset).trimEnd();
|
|
184
294
|
}
|
|
185
295
|
function extractSymbols(root) {
|
|
@@ -325,7 +435,8 @@ function discoverFiles(searchPath, workspace, filePattern) {
|
|
|
325
435
|
return pattern.test(patternHasDirectory ? relativePath : basename(relativePath));
|
|
326
436
|
};
|
|
327
437
|
const addFile = (file, outputPath) => {
|
|
328
|
-
|
|
438
|
+
const language = LANGUAGE_BY_EXTENSION.get(extname(file).toLowerCase());
|
|
439
|
+
if (!language || unavailable.has(language.name) || !accepts(outputPath))
|
|
329
440
|
return;
|
|
330
441
|
const stats = statSync(file);
|
|
331
442
|
if (stats.size > SMART_CODE_LIMITS.maxFileBytes) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Buffer } from 'node:buffer';
|
|
2
|
+
export interface TelegramConfiguration {
|
|
3
|
+
token: string;
|
|
4
|
+
chatId: string;
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
triggerTypes: string[];
|
|
7
|
+
triggerConcepts: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface TelegramStatus {
|
|
10
|
+
configured: boolean;
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
chatId?: string;
|
|
13
|
+
triggerTypes: string[];
|
|
14
|
+
triggerConcepts: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface TelegramObservation {
|
|
17
|
+
id: string;
|
|
18
|
+
type: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
subtitle?: string;
|
|
21
|
+
project?: string;
|
|
22
|
+
concepts?: string[];
|
|
23
|
+
narrative?: string;
|
|
24
|
+
facts?: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface TelegramRuntimeOptions {
|
|
27
|
+
key?: Buffer;
|
|
28
|
+
fetcher?: typeof fetch;
|
|
29
|
+
}
|
|
30
|
+
export declare function configureTelegram(home: string, input: TelegramConfiguration, key?: Buffer): TelegramStatus;
|
|
31
|
+
export declare function telegramStatus(home: string, key?: Buffer): TelegramStatus;
|
|
32
|
+
export declare function disableTelegram(home: string, key?: Buffer): TelegramStatus;
|
|
33
|
+
export declare function sendTelegramTest(home: string, options?: TelegramRuntimeOptions): Promise<{
|
|
34
|
+
sent: true;
|
|
35
|
+
}>;
|
|
36
|
+
export declare function notifyTelegramObservation(home: string, observation: TelegramObservation, options?: TelegramRuntimeOptions): Promise<{
|
|
37
|
+
sent: boolean;
|
|
38
|
+
reason?: 'not-configured' | 'disabled' | 'no-trigger-match';
|
|
39
|
+
}>;
|
package/dist/telegram.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { captureOwner } from './transcript-config.js';
|
|
4
|
+
import { redact } from './capture-privacy.js';
|
|
5
|
+
import { privateStore } from './private-store.js';
|
|
6
|
+
const STORE_NAME = 'telegram.json';
|
|
7
|
+
const MAX_TRIGGERS = 32;
|
|
8
|
+
const MAX_FIELD = 500;
|
|
9
|
+
function validateTrigger(value) {
|
|
10
|
+
const normalized = value.trim().toLowerCase();
|
|
11
|
+
if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(normalized))
|
|
12
|
+
throw new Error('Invalid Telegram trigger.');
|
|
13
|
+
return normalized;
|
|
14
|
+
}
|
|
15
|
+
function triggers(values) {
|
|
16
|
+
if (!Array.isArray(values) || values.length > MAX_TRIGGERS)
|
|
17
|
+
throw new Error('Too many Telegram triggers.');
|
|
18
|
+
return [...new Set(values.map(validateTrigger))];
|
|
19
|
+
}
|
|
20
|
+
function validate(input) {
|
|
21
|
+
if (!/^\d{5,15}:[A-Za-z0-9_-]{10,100}$/.test(input.token))
|
|
22
|
+
throw new Error('Invalid Telegram bot token.');
|
|
23
|
+
if (!/^-?\d{1,20}$/.test(input.chatId))
|
|
24
|
+
throw new Error('Invalid Telegram chat identifier.');
|
|
25
|
+
const triggerTypes = triggers(input.triggerTypes);
|
|
26
|
+
const triggerConcepts = triggers(input.triggerConcepts);
|
|
27
|
+
if (input.enabled && triggerTypes.length === 0 && triggerConcepts.length === 0) {
|
|
28
|
+
throw new Error('Enabled Telegram alerts require at least one trigger.');
|
|
29
|
+
}
|
|
30
|
+
return { ...input, chatId: input.chatId.trim(), triggerTypes, triggerConcepts };
|
|
31
|
+
}
|
|
32
|
+
function withStore(home, key, operation) {
|
|
33
|
+
const store = privateStore(home, key);
|
|
34
|
+
try {
|
|
35
|
+
return operation(store);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
store.close();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function readConfiguration(home, key) {
|
|
42
|
+
if (!existsSync(join(home, STORE_NAME)))
|
|
43
|
+
return null;
|
|
44
|
+
return withStore(home, key, store => {
|
|
45
|
+
const value = store.read(STORE_NAME);
|
|
46
|
+
if (!value || !captureOwner(home) || value.owner !== captureOwner(home))
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
return validate(value);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function publicStatus(value) {
|
|
57
|
+
if (!value)
|
|
58
|
+
return { configured: false, enabled: false, triggerTypes: [], triggerConcepts: [] };
|
|
59
|
+
return {
|
|
60
|
+
configured: true,
|
|
61
|
+
enabled: value.enabled,
|
|
62
|
+
chatId: value.chatId,
|
|
63
|
+
triggerTypes: value.triggerTypes,
|
|
64
|
+
triggerConcepts: value.triggerConcepts,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function configureTelegram(home, input, key) {
|
|
68
|
+
const owner = captureOwner(home);
|
|
69
|
+
if (!owner)
|
|
70
|
+
throw new Error('Conecte sua conta Vault antes de configurar alertas.');
|
|
71
|
+
const configuration = validate(input);
|
|
72
|
+
withStore(home, key, store => store.write(STORE_NAME, { ...configuration, owner }));
|
|
73
|
+
return publicStatus(configuration);
|
|
74
|
+
}
|
|
75
|
+
export function telegramStatus(home, key) {
|
|
76
|
+
return publicStatus(readConfiguration(home, key));
|
|
77
|
+
}
|
|
78
|
+
export function disableTelegram(home, key) {
|
|
79
|
+
const current = readConfiguration(home, key);
|
|
80
|
+
if (!current)
|
|
81
|
+
return publicStatus(null);
|
|
82
|
+
return configureTelegram(home, { ...current, enabled: false }, key);
|
|
83
|
+
}
|
|
84
|
+
function bounded(value, fallback) {
|
|
85
|
+
const normalized = String(redact(value ?? '')).replace(/\s+/g, ' ').trim();
|
|
86
|
+
return (normalized || fallback).slice(0, MAX_FIELD);
|
|
87
|
+
}
|
|
88
|
+
function observationText(observation) {
|
|
89
|
+
return [
|
|
90
|
+
'Vault observation',
|
|
91
|
+
`Type: ${bounded(observation.type, 'unknown')}`,
|
|
92
|
+
`Title: ${bounded(observation.title, 'Untitled')}`,
|
|
93
|
+
...(observation.subtitle ? [`Subtitle: ${bounded(observation.subtitle, '')}`] : []),
|
|
94
|
+
...(observation.project ? [`Project: ${bounded(observation.project, '')}`] : []),
|
|
95
|
+
`ID: ${bounded(observation.id, 'unknown')}`,
|
|
96
|
+
].join('\n').slice(0, 3_500);
|
|
97
|
+
}
|
|
98
|
+
async function send(configuration, text, fetcher) {
|
|
99
|
+
let response;
|
|
100
|
+
try {
|
|
101
|
+
response = await fetcher(`https://api.telegram.org/bot${configuration.token}/sendMessage`, {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
redirect: 'error',
|
|
104
|
+
headers: { 'content-type': 'application/json' },
|
|
105
|
+
body: JSON.stringify({ chat_id: configuration.chatId, text: text.slice(0, 3_500), disable_web_page_preview: true }),
|
|
106
|
+
signal: AbortSignal.timeout(10_000),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
throw new Error('Telegram request failed.');
|
|
111
|
+
}
|
|
112
|
+
if (!response.ok)
|
|
113
|
+
throw new Error(`Telegram request failed with status ${response.status}.`);
|
|
114
|
+
}
|
|
115
|
+
export async function sendTelegramTest(home, options = {}) {
|
|
116
|
+
const configuration = readConfiguration(home, options.key);
|
|
117
|
+
if (!configuration)
|
|
118
|
+
throw new Error('Telegram is not configured.');
|
|
119
|
+
if (!configuration.enabled)
|
|
120
|
+
throw new Error('Telegram alerts are disabled.');
|
|
121
|
+
await send(configuration, 'Vault Go Telegram test. Alerts are configured.', options.fetcher ?? fetch);
|
|
122
|
+
return { sent: true };
|
|
123
|
+
}
|
|
124
|
+
export async function notifyTelegramObservation(home, observation, options = {}) {
|
|
125
|
+
const configuration = readConfiguration(home, options.key);
|
|
126
|
+
if (!configuration)
|
|
127
|
+
return { sent: false, reason: 'not-configured' };
|
|
128
|
+
if (!configuration.enabled)
|
|
129
|
+
return { sent: false, reason: 'disabled' };
|
|
130
|
+
const type = observation.type.trim().toLowerCase();
|
|
131
|
+
const concepts = (observation.concepts ?? []).map(value => value.trim().toLowerCase());
|
|
132
|
+
const matches = configuration.triggerTypes.includes(type)
|
|
133
|
+
|| concepts.some(concept => configuration.triggerConcepts.includes(concept));
|
|
134
|
+
if (!matches)
|
|
135
|
+
return { sent: false, reason: 'no-trigger-match' };
|
|
136
|
+
await send(configuration, observationText(observation), options.fetcher ?? fetch);
|
|
137
|
+
return { sent: true };
|
|
138
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type TranscriptAdapter = 'claude' | 'codex';
|
|
2
|
+
export interface TranscriptTarget {
|
|
3
|
+
id: string;
|
|
4
|
+
adapter: TranscriptAdapter;
|
|
5
|
+
root: string;
|
|
6
|
+
workspace: string;
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
fromStart: boolean;
|
|
9
|
+
since: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function captureOwner(home: string): string;
|
|
12
|
+
export declare function captureCredential(home: string): string;
|
|
13
|
+
export declare function workspaceRoot(path: string): string;
|
|
14
|
+
export declare function privateJson(home: string, name: string, value: unknown): void;
|
|
15
|
+
export declare function readPrivateJson(home: string, name: string, maxBytes?: number): unknown;
|
|
16
|
+
export declare function defaultTranscriptRoot(adapter: TranscriptAdapter, userHome?: string): string;
|
|
17
|
+
export declare function transcriptStatus(home: string): {
|
|
18
|
+
lastError?: string;
|
|
19
|
+
lastScan?: number;
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
targets: TranscriptTarget[];
|
|
22
|
+
requiresAutomaticCapture: boolean;
|
|
23
|
+
defaultFromStart: boolean;
|
|
24
|
+
};
|
|
25
|
+
export declare function transcriptTargets(home: string): TranscriptTarget[];
|
|
26
|
+
export declare function transcriptCaptureEnabled(home: string, adapter: string, cwd: string): boolean;
|
|
27
|
+
export declare function configureTranscriptWatcher(home: string, input: {
|
|
28
|
+
adapter: TranscriptAdapter;
|
|
29
|
+
workspace: string;
|
|
30
|
+
root?: string;
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
consent?: boolean;
|
|
33
|
+
fromStart?: boolean;
|
|
34
|
+
}): {
|
|
35
|
+
lastError?: string;
|
|
36
|
+
lastScan?: number;
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
targets: TranscriptTarget[];
|
|
39
|
+
requiresAutomaticCapture: boolean;
|
|
40
|
+
defaultFromStart: boolean;
|
|
41
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { loadConfig, loadTokens } from './config.js';
|
|
6
|
+
export function captureOwner(home) {
|
|
7
|
+
const config = loadConfig(home), token = process.env['VAULT_GO_TOKEN'] || loadTokens(home)?.accessToken;
|
|
8
|
+
if (!token)
|
|
9
|
+
return '';
|
|
10
|
+
return createHash('sha256').update(JSON.stringify([config.apiUrl, process.env['VAULT_GO_TOKEN'] || config.email || token])).digest('hex');
|
|
11
|
+
}
|
|
12
|
+
export function captureCredential(home) {
|
|
13
|
+
return createHash('sha256').update(process.env['VAULT_GO_TOKEN'] || loadTokens(home)?.accessToken || '').digest('hex');
|
|
14
|
+
}
|
|
15
|
+
export function workspaceRoot(path) {
|
|
16
|
+
let current = resolve(path);
|
|
17
|
+
const initial = current;
|
|
18
|
+
while (dirname(current) !== current) {
|
|
19
|
+
if (existsSync(join(current, '.git')))
|
|
20
|
+
return current;
|
|
21
|
+
current = dirname(current);
|
|
22
|
+
}
|
|
23
|
+
return initial;
|
|
24
|
+
}
|
|
25
|
+
export function privateJson(home, name, value) {
|
|
26
|
+
mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
27
|
+
const destination = join(home, name), temp = `${destination}.${randomUUID()}.tmp`;
|
|
28
|
+
writeFileSync(temp, JSON.stringify(value) + '\n', { mode: 0o600 });
|
|
29
|
+
renameSync(temp, destination);
|
|
30
|
+
chmodSync(destination, 0o600);
|
|
31
|
+
}
|
|
32
|
+
export function readPrivateJson(home, name, maxBytes = 8_000_000) {
|
|
33
|
+
try {
|
|
34
|
+
const file = join(home, name);
|
|
35
|
+
if (statSync(file).size > maxBytes)
|
|
36
|
+
return null;
|
|
37
|
+
return JSON.parse(readFileSync(file, 'utf8'));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function load(home) {
|
|
44
|
+
const value = readPrivateJson(home, 'transcript-watchers.json', 100_000);
|
|
45
|
+
if (value?.version !== 1 || value.owner !== captureOwner(home) || !Array.isArray(value.targets))
|
|
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) };
|
|
48
|
+
}
|
|
49
|
+
export function defaultTranscriptRoot(adapter, userHome = homedir()) {
|
|
50
|
+
return join(userHome, adapter === 'claude' ? '.claude/projects' : '.codex/sessions');
|
|
51
|
+
}
|
|
52
|
+
export function transcriptStatus(home) {
|
|
53
|
+
const config = load(home);
|
|
54
|
+
const state = readPrivateJson(home, 'transcript-checkpoints.json');
|
|
55
|
+
const current = state?.owner === config.owner ? state : null;
|
|
56
|
+
return { enabled: config.targets.some(t => t.enabled), targets: config.targets, requiresAutomaticCapture: true, defaultFromStart: false,
|
|
57
|
+
...(typeof current?.lastScan === 'number' ? { lastScan: current.lastScan } : {}),
|
|
58
|
+
...(current?.lastError === 'transcript_capture_failed' ? { lastError: current.lastError } : {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function transcriptTargets(home) { return load(home).targets; }
|
|
62
|
+
export function transcriptCaptureEnabled(home, adapter, cwd) {
|
|
63
|
+
const workspace = workspaceRoot(cwd);
|
|
64
|
+
return load(home).targets.some(t => t.enabled && t.adapter === adapter && t.workspace === workspace);
|
|
65
|
+
}
|
|
66
|
+
export function configureTranscriptWatcher(home, input) {
|
|
67
|
+
if (!['claude', 'codex'].includes(input.adapter) || !isAbsolute(input.workspace))
|
|
68
|
+
throw new Error('Adaptador ou workspace inválido. Use um caminho absoluto.');
|
|
69
|
+
if (input.enabled && input.consent !== true)
|
|
70
|
+
throw new Error('Ativar exige consent=true para capturar transcripts deste workspace.');
|
|
71
|
+
const owner = captureOwner(home);
|
|
72
|
+
if (!owner)
|
|
73
|
+
throw new Error('Autentique a conta antes de configurar captura.');
|
|
74
|
+
const workspace = workspaceRoot(resolve(input.workspace)), config = load(home);
|
|
75
|
+
const existing = config.targets.find(t => t.adapter === input.adapter && t.workspace === workspace);
|
|
76
|
+
if (!input.enabled) {
|
|
77
|
+
if (existing)
|
|
78
|
+
existing.enabled = false;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const root = input.root ?? defaultTranscriptRoot(input.adapter);
|
|
82
|
+
if (!isAbsolute(root) || !statSync(root).isDirectory())
|
|
83
|
+
throw new Error('A raiz de transcripts deve ser um diretório absoluto.');
|
|
84
|
+
if (!statSync(workspace).isDirectory())
|
|
85
|
+
throw new Error('Workspace inexistente.');
|
|
86
|
+
const target = { id: existing?.id ?? randomUUID(), adapter: input.adapter, root: realpathSync(root), workspace, enabled: true, fromStart: input.fromStart === true, since: existing?.enabled ? existing.since : Date.now() };
|
|
87
|
+
if (existing)
|
|
88
|
+
Object.assign(existing, target);
|
|
89
|
+
else {
|
|
90
|
+
if (config.targets.length >= 20)
|
|
91
|
+
throw new Error('Limite de 20 watchers.');
|
|
92
|
+
config.targets.push(target);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
privateJson(home, 'transcript-watchers.json', { ...config, owner });
|
|
96
|
+
return transcriptStatus(home);
|
|
97
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { HookEvent } from './hooks.js';
|
|
2
|
+
import type { TranscriptAdapter } from './transcript-config.js';
|
|
3
|
+
type Row = Record<string, unknown>;
|
|
4
|
+
export interface TranscriptState {
|
|
5
|
+
session: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
private: boolean;
|
|
8
|
+
pending: Record<string, {
|
|
9
|
+
name: string;
|
|
10
|
+
input: Row;
|
|
11
|
+
timestamp: string;
|
|
12
|
+
}>;
|
|
13
|
+
summarized?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface TranscriptEvent {
|
|
16
|
+
event: HookEvent;
|
|
17
|
+
input: Row;
|
|
18
|
+
}
|
|
19
|
+
export declare const emptyTranscriptState: () => TranscriptState;
|
|
20
|
+
export declare function parseTranscript(adapter: TranscriptAdapter, value: unknown, previous: TranscriptState, offset: number): {
|
|
21
|
+
state: TranscriptState;
|
|
22
|
+
events: TranscriptEvent[];
|
|
23
|
+
};
|
|
24
|
+
export {};
|