@andespindola/brainlink 1.0.5 → 1.0.6

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 (51) hide show
  1. package/README.md +8 -0
  2. package/dist/application/add-note.js +2 -2
  3. package/dist/application/build-context.js +16 -10
  4. package/dist/application/canonical-context-links.js +44 -5
  5. package/dist/application/check-package-update.js +105 -0
  6. package/dist/application/frontend/client/chunk-fetch.js +236 -0
  7. package/dist/application/frontend/client/controls.js +178 -0
  8. package/dist/application/frontend/client/elements.js +122 -0
  9. package/dist/application/frontend/client/input.js +202 -0
  10. package/dist/application/frontend/client/node-details.js +191 -0
  11. package/dist/application/frontend/client/rendering.js +296 -0
  12. package/dist/application/frontend/client/scope-theme.js +114 -0
  13. package/dist/application/frontend/client/spatial.js +98 -0
  14. package/dist/application/frontend/client/storage.js +215 -0
  15. package/dist/application/frontend/client/upload.js +90 -0
  16. package/dist/application/frontend/client/worker-bootstrap.js +147 -0
  17. package/dist/application/frontend/client-js.js +24 -1837
  18. package/dist/application/frontend/client-render-worker-js.js +1 -1
  19. package/dist/application/index-vault-phases.js +189 -0
  20. package/dist/application/index-vault.js +44 -165
  21. package/dist/cli/commands/write/dedupe-commands.js +59 -0
  22. package/dist/cli/commands/write/index-commands.js +205 -0
  23. package/dist/cli/commands/write/link-commands.js +68 -0
  24. package/dist/cli/commands/write/note-commands.js +146 -0
  25. package/dist/cli/commands/write/server-commands.js +553 -0
  26. package/dist/cli/commands/write/shared.js +35 -0
  27. package/dist/cli/commands/write/vault-lifecycle-commands.js +270 -0
  28. package/dist/cli/commands/write-commands.js +12 -1303
  29. package/dist/cli/main.js +39 -3
  30. package/dist/domain/context.js +39 -3
  31. package/dist/domain/embeddings.js +31 -5
  32. package/dist/domain/graph-contexts.js +62 -57
  33. package/dist/domain/graph-layout/cauliflower-layout.js +116 -0
  34. package/dist/domain/graph-layout/collisions.js +100 -0
  35. package/dist/domain/graph-layout/hierarchy.js +135 -0
  36. package/dist/domain/graph-layout/metrics.js +111 -0
  37. package/dist/domain/graph-layout/segments.js +76 -0
  38. package/dist/domain/graph-layout/star-layout.js +110 -0
  39. package/dist/domain/graph-layout.js +4 -625
  40. package/dist/infrastructure/config.js +6 -0
  41. package/dist/infrastructure/file-index.js +13 -4
  42. package/dist/infrastructure/semantic-prefilter.js +24 -0
  43. package/dist/mcp/server.js +7 -0
  44. package/dist/mcp/tool-guard.js +29 -0
  45. package/dist/mcp/tools/maintenance-tools.js +409 -0
  46. package/dist/mcp/tools/read-tools.js +504 -0
  47. package/dist/mcp/tools/shared.js +216 -0
  48. package/dist/mcp/tools/write-tools.js +247 -0
  49. package/dist/mcp/tools.js +3 -1357
  50. package/docs/QUICKSTART.md +4 -0
  51. package/package.json +2 -2
@@ -0,0 +1,205 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ import { indexVault, indexVaultWithOptions } from '../../../application/index-vault.js';
4
+ import { startVaultWatcher } from '../../../application/watch-vault.js';
5
+ import { loadBrainlinkConfig } from '../../../infrastructure/config.js';
6
+ import { parsePositiveInteger, print, resolveOptions } from '../../runtime.js';
7
+ import { formatBytes } from './shared.js';
8
+ const formatMs = (value) => Number.isFinite(value) && value != null ? `${value.toFixed(value >= 100 ? 0 : 1)}ms` : 'n/a';
9
+ const benchEventLabel = (event) => `${event.phase}:${event.status}`;
10
+ const printBenchRealtimeEvent = (json, event) => {
11
+ print(json, {
12
+ event: 'bench-progress',
13
+ ...event
14
+ }, () => `[bench] ${benchEventLabel(event)} ${event.message} (${formatMs(event.elapsedMs)})`);
15
+ };
16
+ const printBenchSummary = (json, trigger, vault, result) => {
17
+ print(json, {
18
+ event: 'bench-result',
19
+ trigger,
20
+ vault,
21
+ result
22
+ }, () => {
23
+ const packs = result.packs;
24
+ const compression = packs?.compression;
25
+ const savedPercent = compression && compression.inputBytes > 0
26
+ ? `${((1 - compression.ratio) * 100).toFixed(1)}%`
27
+ : 'n/a';
28
+ return [
29
+ `[bench] trigger=${trigger}`,
30
+ `documents=${result.documentCount} chunks=${result.chunkCount} links=${result.linkCount}`,
31
+ `changedDocuments=${result.changedDocumentCount ?? 0} totalElapsed=${formatMs(result.elapsedMs)}`,
32
+ `packsRebuilt=${packs?.rebuilt ? 'yes' : 'no'} reason=${packs?.reason ?? 'n/a'}`,
33
+ packs?.rebuilt
34
+ ? `packCount=${packs.packCount ?? 0} packDuration=${formatMs(packs.durationMs)} input=${formatBytes(compression?.inputBytes)} output=${formatBytes(compression?.outputBytes)} saved=${savedPercent}`
35
+ : 'packCompression=n/a'
36
+ ].join('\n');
37
+ });
38
+ };
39
+ const benchHistoryPath = (vaultPath) => join(vaultPath, '.brainlink', 'benchmarks', 'latest.json');
40
+ const readBenchHistory = async (vaultPath) => {
41
+ try {
42
+ const parsed = JSON.parse(await readFile(benchHistoryPath(vaultPath), 'utf8'));
43
+ if (typeof parsed.elapsedMs !== 'number' || typeof parsed.timestamp !== 'string') {
44
+ return null;
45
+ }
46
+ return {
47
+ elapsedMs: parsed.elapsedMs,
48
+ timestamp: parsed.timestamp,
49
+ ...(typeof parsed.compressionRatio === 'number' ? { compressionRatio: parsed.compressionRatio } : {})
50
+ };
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ };
56
+ const writeBenchHistory = async (vaultPath, result) => {
57
+ await mkdir(dirname(benchHistoryPath(vaultPath)), { recursive: true });
58
+ const payload = {
59
+ elapsedMs: result.elapsedMs ?? 0,
60
+ timestamp: new Date().toISOString(),
61
+ ...(typeof result.packs?.compression?.ratio === 'number' ? { compressionRatio: result.packs.compression.ratio } : {})
62
+ };
63
+ await writeFile(benchHistoryPath(vaultPath), `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
64
+ };
65
+ const evaluateBenchGuardrails = (config, result, baseline) => {
66
+ const compressionRatio = result.packs?.compression?.ratio;
67
+ const compressionSavingsPercent = typeof compressionRatio === 'number' ? Math.max(0, (1 - compressionRatio) * 100) : undefined;
68
+ const compressionPass = compressionSavingsPercent != null
69
+ ? compressionSavingsPercent >= config.searchPack.guardrailMinSavingsPercent
70
+ : undefined;
71
+ const latencyRegressionPercent = baseline && baseline.elapsedMs > 0 && typeof result.elapsedMs === 'number'
72
+ ? ((result.elapsedMs - baseline.elapsedMs) / baseline.elapsedMs) * 100
73
+ : undefined;
74
+ const latencyPass = latencyRegressionPercent != null
75
+ ? latencyRegressionPercent <= config.searchPack.guardrailMaxLatencyRegressionPercent
76
+ : undefined;
77
+ return {
78
+ ...(compressionSavingsPercent != null ? { compressionSavingsPercent } : {}),
79
+ ...(compressionPass != null ? { compressionPass } : {}),
80
+ ...(latencyRegressionPercent != null ? { latencyRegressionPercent } : {}),
81
+ ...(latencyPass != null ? { latencyPass } : {})
82
+ };
83
+ };
84
+ const printBenchGuardrails = (json, vault, config, guardrails) => {
85
+ print(json, {
86
+ event: 'bench-guardrails',
87
+ vault,
88
+ thresholds: {
89
+ minSavingsPercent: config.searchPack.guardrailMinSavingsPercent,
90
+ maxLatencyRegressionPercent: config.searchPack.guardrailMaxLatencyRegressionPercent
91
+ },
92
+ guardrails
93
+ }, () => {
94
+ const savings = guardrails.compressionSavingsPercent;
95
+ const latency = guardrails.latencyRegressionPercent;
96
+ return [
97
+ '[bench] guardrails',
98
+ `minSavings=${config.searchPack.guardrailMinSavingsPercent.toFixed(1)}% maxLatencyRegression=${config.searchPack.guardrailMaxLatencyRegressionPercent.toFixed(1)}%`,
99
+ `compressionSavings=${savings != null ? `${savings.toFixed(2)}%` : 'n/a'} pass=${guardrails.compressionPass != null ? (guardrails.compressionPass ? 'yes' : 'no') : 'n/a'}`,
100
+ `latencyRegression=${latency != null ? `${latency.toFixed(2)}%` : 'n/a'} pass=${guardrails.latencyPass != null ? (guardrails.latencyPass ? 'yes' : 'no') : 'n/a'}`
101
+ ].join('\n');
102
+ });
103
+ };
104
+ export const registerIndexCommands = (program) => {
105
+ program
106
+ .command('index')
107
+ .option('-v, --vault <vault>', 'vault directory')
108
+ .option('--full', 'force a complete reindex from Markdown source without reusing unchanged index entries')
109
+ .option('--json', 'print machine-readable JSON')
110
+ .description('index markdown notes, links, tags and chunks')
111
+ .action(async (options) => {
112
+ const resolved = await resolveOptions(options);
113
+ const result = await indexVault(resolved.vault, {
114
+ full: options.full === true
115
+ });
116
+ print(options.json, result, () => options.full === true
117
+ ? `Fully reindexed ${result.documentCount} documents, ${result.chunkCount} chunks and ${result.linkCount} links`
118
+ : `Indexed ${result.documentCount} documents, ${result.chunkCount} chunks and ${result.linkCount} links`);
119
+ });
120
+ program
121
+ .command('bench')
122
+ .option('-v, --vault <vault>', 'vault directory')
123
+ .option('-w, --watch', 'watch markdown changes and re-run benchmark in realtime')
124
+ .option('--debounce <ms>', 'watch debounce in milliseconds', '350')
125
+ .option('--json', 'print machine-readable JSON events')
126
+ .description('benchmark indexing in realtime, including compressed pack behavior')
127
+ .action(async (options) => {
128
+ const resolved = await resolveOptions(options);
129
+ const config = await loadBrainlinkConfig();
130
+ const emitProgress = (event) => {
131
+ printBenchRealtimeEvent(options.json, event);
132
+ };
133
+ const printBenchError = (error) => {
134
+ const message = error instanceof Error ? error.message : String(error);
135
+ print(options.json, { event: 'bench-error', message }, () => `[bench] error ${message}`);
136
+ };
137
+ const runAndPrint = async (trigger) => {
138
+ const baseline = await readBenchHistory(resolved.vault);
139
+ const result = await indexVaultWithOptions(resolved.vault, {
140
+ onProgress: emitProgress
141
+ });
142
+ printBenchSummary(options.json, trigger, resolved.vault, result);
143
+ const guardrails = evaluateBenchGuardrails(config, result, baseline);
144
+ printBenchGuardrails(options.json, resolved.vault, config, guardrails);
145
+ await writeBenchHistory(resolved.vault, result);
146
+ return result;
147
+ };
148
+ if (!options.watch) {
149
+ await runAndPrint('manual');
150
+ return;
151
+ }
152
+ const debounceMs = parsePositiveInteger(options.debounce ?? '350', 350);
153
+ await runAndPrint('manual');
154
+ print(options.json, {
155
+ event: 'bench-watching',
156
+ vault: resolved.vault,
157
+ debounceMs
158
+ }, () => `[bench] watching ${resolved.vault} (debounce=${debounceMs}ms)`);
159
+ const watcher = startVaultWatcher({
160
+ vaultPath: resolved.vault,
161
+ debounceMs,
162
+ onProgress: emitProgress,
163
+ onIndex: (result) => {
164
+ printBenchSummary(options.json, 'watch', resolved.vault, result);
165
+ },
166
+ onError: printBenchError
167
+ });
168
+ await new Promise((resolveSignal) => {
169
+ const shutdown = () => {
170
+ watcher.close();
171
+ resolveSignal();
172
+ };
173
+ process.once('SIGINT', shutdown);
174
+ process.once('SIGTERM', shutdown);
175
+ });
176
+ });
177
+ program
178
+ .command('watch')
179
+ .option('-v, --vault <vault>', 'vault directory')
180
+ .option('--json', 'print machine-readable JSON events')
181
+ .description('watch markdown files and reindex on changes')
182
+ .action(async (options) => {
183
+ const resolved = await resolveOptions(options);
184
+ const initial = await indexVault(resolved.vault);
185
+ const watcher = startVaultWatcher({
186
+ vaultPath: resolved.vault,
187
+ onIndex: (result) => {
188
+ print(options.json, { event: 'indexed', result }, () => `Indexed ${result.documentCount} documents, ${result.chunkCount} chunks and ${result.linkCount} links`);
189
+ },
190
+ onError: (error) => {
191
+ const message = error instanceof Error ? error.message : String(error);
192
+ print(options.json, { event: 'error', message }, () => message);
193
+ }
194
+ });
195
+ print(options.json, { event: 'watching', vault: resolved.vault, initial }, () => `Watching ${resolved.vault}`);
196
+ process.once('SIGINT', () => {
197
+ watcher.close();
198
+ process.exit(0);
199
+ });
200
+ process.once('SIGTERM', () => {
201
+ watcher.close();
202
+ process.exit(0);
203
+ });
204
+ });
205
+ };
@@ -0,0 +1,68 @@
1
+ import { indexVault } from '../../../application/index-vault.js';
2
+ import { migrateContextLinks } from '../../../application/migrate-context-links.js';
3
+ import { canonicalizeContextLinks } from '../../../application/canonical-context-links.js';
4
+ import { parsePositiveInteger, print, resolveOptions } from '../../runtime.js';
5
+ export const registerLinkCommands = (program) => {
6
+ program
7
+ .command('migrate-context-links')
8
+ .option('-v, --vault <vault>', 'vault directory')
9
+ .option('-a, --agent <agent>', 'agent memory namespace')
10
+ .option('-l, --limit <limit>', 'maximum context links to add per note', '5')
11
+ .option('--dry-run', 'preview context-link migration without writing files')
12
+ .option('--no-index', 'skip reindexing after migration')
13
+ .option('--json', 'print machine-readable JSON')
14
+ .description('add concise Context Links sections from existing wiki-link mentions')
15
+ .action(async (options) => {
16
+ const resolved = await resolveOptions(options);
17
+ const result = await migrateContextLinks(resolved.vault, {
18
+ dryRun: options.dryRun === true,
19
+ limit: parsePositiveInteger(options.limit ?? '5', 5),
20
+ agentId: resolved.agent
21
+ });
22
+ const shouldIndex = options.index !== false && !result.dryRun && result.changed > 0;
23
+ const index = shouldIndex ? await indexVault(resolved.vault, { full: true }) : undefined;
24
+ print(options.json, {
25
+ vault: resolved.vault,
26
+ agent: resolved.agent ?? 'shared',
27
+ ...result,
28
+ ...(index ? { index } : {})
29
+ }, () => {
30
+ const mode = result.dryRun ? 'Previewed' : 'Migrated';
31
+ const indexMessage = index
32
+ ? ` Fully reindexed ${index.documentCount} documents, ${index.chunkCount} chunks and ${index.linkCount} context links.`
33
+ : '';
34
+ return `${mode} ${result.scanned} notes: changed=${result.changed}, skipped=${result.skipped}, limit=${result.limit}.${indexMessage}`;
35
+ });
36
+ });
37
+ program
38
+ .command('canonicalize-context-links')
39
+ .option('-v, --vault <vault>', 'vault directory')
40
+ .option('-a, --agent <agent>', 'agent memory namespace')
41
+ .option('--dry-run', 'preview canonical context links without writing files')
42
+ .option('--no-create-hubs', 'do not create missing context hub notes')
43
+ .option('--no-index', 'skip reindexing after canonicalization')
44
+ .option('--json', 'print machine-readable JSON')
45
+ .description('ensure notes have canonical Context Links to their inferred context hubs')
46
+ .action(async (options) => {
47
+ const resolved = await resolveOptions(options);
48
+ const result = await canonicalizeContextLinks(resolved.vault, {
49
+ dryRun: options.dryRun === true,
50
+ agentId: resolved.agent,
51
+ createMissingHubs: options.createHubs !== false
52
+ });
53
+ const shouldIndex = options.index !== false && !result.dryRun && result.changed > 0;
54
+ const index = shouldIndex ? await indexVault(resolved.vault, { full: true }) : undefined;
55
+ print(options.json, {
56
+ vault: resolved.vault,
57
+ agent: resolved.agent ?? 'shared',
58
+ ...result,
59
+ ...(index ? { index } : {})
60
+ }, () => {
61
+ const mode = result.dryRun ? 'Previewed' : 'Canonicalized';
62
+ const indexMessage = index
63
+ ? ` Fully reindexed ${index.documentCount} documents, ${index.chunkCount} chunks and ${index.linkCount} context links.`
64
+ : '';
65
+ return `${mode} ${result.scanned} notes: changed=${result.changed}, createdHubs=${result.createdHubs}, skipped=${result.skipped}.${indexMessage}`;
66
+ });
67
+ });
68
+ };
@@ -0,0 +1,146 @@
1
+ import { relative, resolve } from 'node:path';
2
+ import { addNoteWithMetadata } from '../../../application/add-note.js';
3
+ import { deleteNote } from '../../../application/delete-note.js';
4
+ import { scanDuplicateNotes } from '../../../application/dedupe-notes.js';
5
+ import { importFile } from '../../../application/import-file.js';
6
+ import { indexVault } from '../../../application/index-vault.js';
7
+ import { ensureVault } from '../../../infrastructure/file-system-vault.js';
8
+ import { addVolatileMemory, clearVolatileMemory } from '../../../infrastructure/volatile-memory.js';
9
+ import { parsePositiveInteger, print, resolveOptions } from '../../runtime.js';
10
+ import { resolveAddContent } from './shared.js';
11
+ export const registerNoteCommands = (program) => {
12
+ program
13
+ .command('volatile')
14
+ .option('-c, --content <content>', 'temporary memory content to add')
15
+ .option('--ttl <minutes>', 'time-to-live in minutes', '240')
16
+ .option('--tag <tag...>', 'volatile memory tag')
17
+ .option('-v, --vault <vault>', 'vault directory')
18
+ .option('-a, --agent <agent>', 'agent memory namespace')
19
+ .option('--clear', 'clear volatile memory for the current agent namespace')
20
+ .option('--json', 'print machine-readable JSON')
21
+ .description('add or clear temporary agent-decided memory')
22
+ .action(async (options) => {
23
+ const resolved = await resolveOptions(options);
24
+ if (options.clear) {
25
+ const cleared = await clearVolatileMemory(resolved.vault, resolved.agent);
26
+ print(options.json, { cleared, agent: resolved.agent ?? 'shared' }, () => `Cleared ${cleared} volatile memories.`);
27
+ return;
28
+ }
29
+ if (!options.content || options.content.trim().length === 0) {
30
+ throw new Error('Use --content to add volatile memory, or --clear to remove it.');
31
+ }
32
+ const entry = await addVolatileMemory(resolved.vault, options.content, resolved.agent ?? 'shared', parsePositiveInteger(options.ttl ?? '240', 240), options.tag ?? []);
33
+ print(options.json, { entry }, () => `Stored volatile memory until ${entry.expiresAt}.`);
34
+ });
35
+ program
36
+ .command('add')
37
+ .argument('<title>', 'note title')
38
+ .option('-c, --content <content>', 'markdown content')
39
+ .option('-f, --content-file <contentFile>', 'read markdown content from a file')
40
+ .option('-v, --vault <vault>', 'vault directory')
41
+ .option('-a, --agent <agent>', 'agent memory namespace')
42
+ .option('--allow-sensitive', 'allow writing content that looks like a secret')
43
+ .option('--no-auto-context-links', 'skip canonical Context Links for this note')
44
+ .option('--no-auto-index', 'skip reindexing after add')
45
+ .option('--json', 'print machine-readable JSON')
46
+ .description('add a markdown note to the vault')
47
+ .action(async (title, options) => {
48
+ const resolved = await resolveOptions(options);
49
+ const content = resolveAddContent(options);
50
+ const added = await addNoteWithMetadata(resolved.vault, title, content, resolved.agent, {
51
+ allowSensitive: Boolean(options.allowSensitive),
52
+ autoContextLinks: options.autoContextLinks !== false && resolved.config.autoCanonicalContextLinks
53
+ });
54
+ const shouldAutoIndex = options.autoIndex !== false && resolved.config.autoIndexOnWrite;
55
+ const index = shouldAutoIndex ? await indexVault(resolved.vault) : undefined;
56
+ const absoluteVaultPath = await ensureVault(resolved.vault);
57
+ const focusPath = added.path.startsWith(absoluteVaultPath)
58
+ ? relative(absoluteVaultPath, added.path).replaceAll('\\', '/')
59
+ : added.path.includes('agents/')
60
+ ? added.path.slice(added.path.indexOf('agents/')).replaceAll('\\', '/')
61
+ : undefined;
62
+ const possibleDuplicates = await scanDuplicateNotes(resolved.vault, {
63
+ agentId: resolved.agent,
64
+ focusPath,
65
+ limit: 5,
66
+ minSemanticScore: 0.92,
67
+ includeSemantic: true
68
+ });
69
+ print(options.json, {
70
+ title,
71
+ agent: resolved.agent ?? 'shared',
72
+ path: added.path,
73
+ writeConnectivity: {
74
+ autoLinked: added.autoLinked,
75
+ linkTarget: added.linkTarget,
76
+ context: added.context,
77
+ hubCreated: added.hubCreated,
78
+ guaranteedEdge: added.autoLinked
79
+ },
80
+ possibleDuplicates,
81
+ ...(index ? { index } : {})
82
+ }, () => {
83
+ const duplicateMessage = possibleDuplicates.length > 0
84
+ ? `\nPotential duplicates: ${possibleDuplicates.length}. Use "blink dedupe --json" or "blink dedupe-resolve".`
85
+ : '';
86
+ const linkMessage = added.autoLinked ? ` Linked to [[${added.linkTarget}]].` : '';
87
+ return `Created note at ${added.path}.${linkMessage}${duplicateMessage}`;
88
+ });
89
+ });
90
+ program
91
+ .command('import-file')
92
+ .argument('<file>', 'file to convert and import into the vault')
93
+ .option('-t, --title <title>', 'note title; defaults to the source filename')
94
+ .option('-v, --vault <vault>', 'vault directory')
95
+ .option('-a, --agent <agent>', 'agent memory namespace')
96
+ .option('--allow-sensitive', 'allow writing converted content that looks like a secret')
97
+ .option('--no-auto-context-links', 'skip canonical Context Links for this imported note')
98
+ .option('--no-auto-index', 'skip reindexing after import')
99
+ .option('--json', 'print machine-readable JSON')
100
+ .description('convert a document with Docling and import it as a Markdown note')
101
+ .action(async (file, options) => {
102
+ const resolved = await resolveOptions(options);
103
+ const result = await importFile({
104
+ vaultPath: resolved.vault,
105
+ filePath: resolve(file),
106
+ title: options.title,
107
+ agentId: resolved.agent,
108
+ allowSensitive: Boolean(options.allowSensitive),
109
+ autoContextLinks: options.autoContextLinks !== false && resolved.config.autoCanonicalContextLinks,
110
+ autoIndex: options.autoIndex !== false && resolved.config.autoIndexOnWrite
111
+ });
112
+ print(options.json, {
113
+ vault: resolved.vault,
114
+ agent: resolved.agent ?? 'shared',
115
+ ...result
116
+ }, () => {
117
+ const linkMessage = result.writeConnectivity.autoLinked ? ` Linked to [[${result.writeConnectivity.linkTarget}]].` : '';
118
+ const indexMessage = result.index ? ` Indexed ${result.index.documentCount} documents.` : '';
119
+ return `Imported ${result.sourceName} as "${result.title}" at ${result.path}.${linkMessage}${indexMessage}`;
120
+ });
121
+ });
122
+ program
123
+ .command('delete-note')
124
+ .option('-v, --vault <vault>', 'vault directory')
125
+ .option('-a, --agent <agent>', 'agent memory namespace when deleting by title')
126
+ .option('--title <title>', 'note title to delete')
127
+ .option('--path <path>', 'vault-relative or absolute markdown note path to delete')
128
+ .option('--yes', 'confirm note deletion')
129
+ .option('--no-auto-index', 'skip reindexing after delete')
130
+ .option('--json', 'print machine-readable JSON')
131
+ .description('delete a Markdown note from the vault after explicit confirmation')
132
+ .action(async (options) => {
133
+ const resolved = await resolveOptions(options);
134
+ const result = await deleteNote(resolved.vault, {
135
+ title: options.title,
136
+ path: options.path,
137
+ agentId: resolved.agent,
138
+ confirm: Boolean(options.yes),
139
+ autoIndex: options.autoIndex !== false
140
+ });
141
+ print(options.json, {
142
+ vault: resolved.vault,
143
+ ...result
144
+ }, () => `Deleted note ${result.relativePath}.`);
145
+ });
146
+ };