@telvok/librarian-mcp 1.5.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/library/errors.d.ts +48 -0
  2. package/dist/library/errors.js +80 -0
  3. package/dist/library/schemas.d.ts +6 -6
  4. package/dist/library/storage.d.ts +2 -2
  5. package/dist/library/storage.js +2 -2
  6. package/dist/library 2/embeddings.d.ts +21 -0
  7. package/dist/library 2/embeddings.js +86 -0
  8. package/dist/library 2/manager.d.ts +42 -0
  9. package/dist/library 2/manager.js +218 -0
  10. package/dist/library 2/parsers/cursor.d.ts +15 -0
  11. package/dist/library 2/parsers/cursor.js +168 -0
  12. package/dist/library 2/parsers/index.d.ts +6 -0
  13. package/dist/library 2/parsers/index.js +5 -0
  14. package/dist/library 2/parsers/json.d.ts +11 -0
  15. package/dist/library 2/parsers/json.js +95 -0
  16. package/dist/library 2/parsers/jsonl.d.ts +14 -0
  17. package/dist/library 2/parsers/jsonl.js +85 -0
  18. package/dist/library 2/parsers/markdown.d.ts +15 -0
  19. package/dist/library 2/parsers/markdown.js +77 -0
  20. package/dist/library 2/parsers/sqlite.d.ts +8 -0
  21. package/dist/library 2/parsers/sqlite.js +123 -0
  22. package/dist/library 2/parsers/types.d.ts +21 -0
  23. package/dist/library 2/parsers/types.js +4 -0
  24. package/dist/library 2/query.d.ts +26 -0
  25. package/dist/library 2/query.js +104 -0
  26. package/dist/library 2/schemas.d.ts +324 -0
  27. package/dist/library 2/schemas.js +79 -0
  28. package/dist/library 2/storage.d.ts +22 -0
  29. package/dist/library 2/storage.js +36 -0
  30. package/dist/library 2/vector-index.d.ts +55 -0
  31. package/dist/library 2/vector-index.js +160 -0
  32. package/dist/server 2.js +199 -0
  33. package/dist/server.d 2.ts +2 -0
  34. package/dist/server.js +102 -54
  35. package/dist/tools/adopt.d.ts +1 -0
  36. package/dist/tools/adopt.js +37 -10
  37. package/dist/tools/auth.d.ts +69 -0
  38. package/dist/tools/auth.js +379 -0
  39. package/dist/tools/bounty-claim.d.ts +28 -0
  40. package/dist/tools/bounty-claim.js +92 -0
  41. package/dist/tools/bounty-create.d.ts +47 -0
  42. package/dist/tools/bounty-create.js +118 -0
  43. package/dist/tools/bounty-list.d.ts +50 -0
  44. package/dist/tools/bounty-list.js +116 -0
  45. package/dist/tools/bounty-submit.d.ts +34 -0
  46. package/dist/tools/bounty-submit.js +94 -0
  47. package/dist/tools/brief.d.ts +94 -0
  48. package/dist/tools/brief.js +234 -15
  49. package/dist/tools/delete.d.ts +87 -0
  50. package/dist/tools/delete.js +266 -0
  51. package/dist/tools/feedback.d.ts +27 -0
  52. package/dist/tools/feedback.js +98 -0
  53. package/dist/tools/help.d.ts +22 -0
  54. package/dist/tools/help.js +482 -0
  55. package/dist/tools/import-memories.d.ts +1 -0
  56. package/dist/tools/import-memories.js +18 -13
  57. package/dist/tools/index.d.ts +11 -0
  58. package/dist/tools/index.js +12 -0
  59. package/dist/tools/library-buy.d.ts +31 -0
  60. package/dist/tools/library-buy.js +104 -0
  61. package/dist/tools/library-download.d.ts +27 -0
  62. package/dist/tools/library-download.js +177 -0
  63. package/dist/tools/library-publish.d.ts +112 -0
  64. package/dist/tools/library-publish.js +387 -0
  65. package/dist/tools/library-search.d.ts +110 -0
  66. package/dist/tools/library-search.js +132 -0
  67. package/dist/tools/mark-hit.d.ts +1 -0
  68. package/dist/tools/mark-hit.js +83 -5
  69. package/dist/tools/my-books.d.ts +51 -0
  70. package/dist/tools/my-books.js +115 -0
  71. package/dist/tools/my-bounties.d.ts +43 -0
  72. package/dist/tools/my-bounties.js +126 -0
  73. package/dist/tools/rate-book.d.ts +40 -0
  74. package/dist/tools/rate-book.js +147 -0
  75. package/dist/tools/rebuild-index.d.ts +1 -0
  76. package/dist/tools/rebuild-index.js +40 -8
  77. package/dist/tools/record.d.ts +18 -0
  78. package/dist/tools/record.js +30 -26
  79. package/dist/tools/seller-analytics.d.ts +53 -0
  80. package/dist/tools/seller-analytics.js +180 -0
  81. package/dist/tools/sync.d.ts +55 -0
  82. package/dist/tools/sync.js +304 -0
  83. package/dist/tools/unsubscribe.d.ts +48 -0
  84. package/dist/tools/unsubscribe.js +120 -0
  85. package/dist/tools 2/adopt.d.ts +24 -0
  86. package/dist/tools 2/adopt.js +154 -0
  87. package/dist/tools 2/auth.d.ts +35 -0
  88. package/dist/tools 2/auth.js +229 -0
  89. package/dist/tools 2/brief.d.ts +56 -0
  90. package/dist/tools 2/brief.js +414 -0
  91. package/dist/tools 2/help.d.ts +21 -0
  92. package/dist/tools 2/help.js +267 -0
  93. package/dist/tools 2/import-memories.d.ts +32 -0
  94. package/dist/tools 2/import-memories.js +231 -0
  95. package/dist/tools 2/index.d.ts +12 -0
  96. package/dist/tools 2/index.js +12 -0
  97. package/dist/tools 2/mark-hit.d.ts +20 -0
  98. package/dist/tools 2/mark-hit.js +71 -0
  99. package/dist/tools 2/marketplace-buy.d.ts +30 -0
  100. package/dist/tools 2/marketplace-buy.js +97 -0
  101. package/dist/tools 2/marketplace-download.d.ts +26 -0
  102. package/dist/tools 2/marketplace-download.js +160 -0
  103. package/dist/tools 2/marketplace-publish.d.ts +111 -0
  104. package/dist/tools 2/marketplace-publish.js +377 -0
  105. package/dist/tools 2/marketplace-search.d.ts +57 -0
  106. package/dist/tools 2/marketplace-search.js +96 -0
  107. package/dist/tools 2/my-books.d.ts +50 -0
  108. package/dist/tools 2/my-books.js +107 -0
  109. package/dist/tools 2/rate-book.d.ts +39 -0
  110. package/dist/tools 2/rate-book.js +139 -0
  111. package/dist/tools 2/rebuild-index.d.ts +23 -0
  112. package/dist/tools 2/rebuild-index.js +107 -0
  113. package/dist/tools 2/record.d.ts +40 -0
  114. package/dist/tools 2/record.js +205 -0
  115. package/dist/tools 2/seller-analytics.d.ts +35 -0
  116. package/dist/tools 2/seller-analytics.js +102 -0
  117. package/dist/tools 2/sync.d.ts +54 -0
  118. package/dist/tools 2/sync.js +298 -0
  119. package/package.json +1 -1
@@ -4,21 +4,36 @@ import matter from 'gray-matter';
4
4
  import { glob } from 'glob';
5
5
  import { getLibraryPath, getLocalPath, getImportedPath, getPackagesPath } from '../library/storage.js';
6
6
  import { loadIndex, semanticSearch, isIndexStale } from '../library/vector-index.js';
7
+ import { loadApiKey } from './auth.js';
7
8
  // ============================================================================
8
9
  // Tool Definition
9
10
  // ============================================================================
11
+ const TELVOK_API_URL = process.env.TELVOK_API_URL || 'https://telvok.com';
10
12
  export const briefTool = {
11
13
  name: 'brief',
12
- description: `Check what we already know before diving in.
14
+ title: 'Search Knowledge Library',
15
+ description: `CALL THIS PROACTIVELY before thinking through problems, making decisions, or planning.
13
16
 
14
- We've solved problems before. Before thinking through a problem, making
15
- decisions, or planning - brief yourself on what past-us figured out.
16
- Searches intent, insight, context, and examples.
17
+ USE THIS TOOL WHEN:
18
+ - Starting work on ANY topic (check what we already know)
19
+ - A problem feels familiar (we probably solved it before)
20
+ - Making architectural or implementation decisions
21
+ - Debugging issues (past-us likely hit the same gotcha)
22
+ - The user mentions something we've worked on before
23
+
24
+ DO NOT SKIP THIS because you think you know the answer. Our library contains project-specific context, credentials, and decisions that general knowledge doesn't have.
25
+
26
+ TRIGGER PATTERNS:
27
+ - User asks about auth/tokens → brief({ query: "auth" })
28
+ - Starting Stripe work → brief({ query: "stripe" })
29
+ - Any error/debugging → brief({ query: "<error topic>" })
30
+ - User says "remember when..." → brief({ query: "<topic>" })
31
+ - Beginning any task → brief({}) for recent context
17
32
 
18
33
  Examples:
19
34
  - brief({ query: "stripe webhooks" })
20
- - brief({ query: "auth token" })
21
- - brief({}) → returns recent entries`,
35
+ - brief({}) returns recent entries
36
+ - brief({ query: "react", include_library: true }) → also search library`,
22
37
  inputSchema: {
23
38
  type: 'object',
24
39
  properties: {
@@ -31,11 +46,58 @@ Examples:
31
46
  description: 'Max entries to return',
32
47
  default: 5,
33
48
  },
49
+ include_library: {
50
+ type: 'boolean',
51
+ description: 'Also search Telvok library for relevant books',
52
+ default: false,
53
+ },
34
54
  },
35
55
  required: [],
36
56
  },
57
+ outputSchema: {
58
+ type: 'object',
59
+ properties: {
60
+ entries: {
61
+ type: 'array',
62
+ items: {
63
+ type: 'object',
64
+ properties: {
65
+ title: { type: 'string' },
66
+ preview: { type: 'string' },
67
+ path: { type: 'string' },
68
+ hits: { type: 'number' },
69
+ source: { type: 'string', enum: ['local', 'cloud', 'packages'] },
70
+ book_name: { type: 'string' },
71
+ },
72
+ },
73
+ },
74
+ total: { type: 'number' },
75
+ message: { type: 'string' },
76
+ libraryPath: { type: 'string' },
77
+ library: {
78
+ type: 'object',
79
+ properties: {
80
+ books: {
81
+ type: 'array',
82
+ items: {
83
+ type: 'object',
84
+ properties: {
85
+ slug: { type: 'string' },
86
+ name: { type: 'string' },
87
+ description: { type: 'string' },
88
+ pricing: { type: 'string' },
89
+ entries: { type: 'number' },
90
+ },
91
+ },
92
+ },
93
+ total: { type: 'number' },
94
+ },
95
+ },
96
+ },
97
+ required: ['entries', 'total', 'message', 'libraryPath'],
98
+ },
37
99
  async handler(args) {
38
- const { query, limit = 5 } = args;
100
+ const { query, limit = 5, include_library = false } = args;
39
101
  const libraryPath = getLibraryPath();
40
102
  const localPath = getLocalPath(libraryPath);
41
103
  const importedPath = getImportedPath(libraryPath);
@@ -76,11 +138,62 @@ Examples:
76
138
  .filter((e) => e !== undefined);
77
139
  const total = allEntries.length;
78
140
  const entries = allEntries.slice(0, limit);
141
+ // Always fetch cloud content if authenticated (owned/purchased books)
142
+ // Only fetch marketplace if include_library flag is set
143
+ let cloudResult;
144
+ let libraryResult;
145
+ if (query) {
146
+ // Always fetch cloud content from owned books if authenticated
147
+ cloudResult = await fetchCloudContent(query, limit);
148
+ // Only fetch marketplace discovery if explicitly requested
149
+ if (include_library) {
150
+ libraryResult = await fetchMarketplaceResults(query, 5);
151
+ // Filter library to exclude books user already owns
152
+ if (cloudResult.entries.length > 0) {
153
+ const ownedSlugs = new Set(cloudResult.entries.map(e => e.book_slug));
154
+ libraryResult.books = libraryResult.books.filter(b => !ownedSlugs.has(b.slug));
155
+ libraryResult.total = libraryResult.books.length;
156
+ }
157
+ }
158
+ }
159
+ // Convert cloud entries to BriefEntry format and merge
160
+ let finalEntries = [...entries];
161
+ if (cloudResult && cloudResult.entries.length > 0) {
162
+ const cloudBriefEntries = cloudResult.entries.map(ce => ({
163
+ title: ce.title,
164
+ intent: ce.intent,
165
+ context: ce.context,
166
+ preview: ce.insight.length > 100 ? ce.insight.slice(0, 100) + '...' : ce.insight,
167
+ path: `cloud:${ce.book_slug}`, // Virtual path for cloud entries
168
+ created: new Date().toISOString(),
169
+ hits: 0,
170
+ last_hit: null,
171
+ source: 'cloud',
172
+ book_name: ce.book_name,
173
+ book_slug: ce.book_slug,
174
+ }));
175
+ // Interleave cloud entries with local: put cloud first (paid content priority)
176
+ // then fill remaining slots with local entries
177
+ const cloudCount = Math.min(cloudBriefEntries.length, Math.ceil(limit / 2));
178
+ const localCount = limit - cloudCount;
179
+ finalEntries = [
180
+ ...cloudBriefEntries.slice(0, cloudCount),
181
+ ...entries.slice(0, localCount),
182
+ ];
183
+ }
184
+ let message = `Found ${total} local ${total === 1 ? 'entry' : 'entries'} for "${query}" (semantic search).`;
185
+ if (cloudResult && cloudResult.entries.length > 0) {
186
+ message += ` Also found ${cloudResult.total} matching entries from owned books.`;
187
+ }
188
+ if (libraryResult && libraryResult.books.length > 0) {
189
+ message += ` ${libraryResult.total} book(s) available on library.`;
190
+ }
79
191
  return {
80
- entries,
81
- total,
82
- message: `Found ${total} ${total === 1 ? 'entry' : 'entries'} for "${query}" (semantic search).`,
192
+ entries: finalEntries,
193
+ total: finalEntries.length,
194
+ message,
83
195
  libraryPath: localPath,
196
+ library: libraryResult,
84
197
  };
85
198
  }
86
199
  // Fall back to keyword search
@@ -110,7 +223,7 @@ Examples:
110
223
  catch {
111
224
  // No imported files
112
225
  }
113
- // Read packages entries (marketplace content)
226
+ // Read packages entries (library content)
114
227
  try {
115
228
  const packagesFiles = await glob(path.join(packagesPath, '**/*.md'), { nodir: true });
116
229
  for (const filePath of packagesFiles) {
@@ -143,21 +256,64 @@ Examples:
143
256
  const total = allEntries.length;
144
257
  // Apply limit
145
258
  const entries = allEntries.slice(0, limit);
259
+ // Always fetch cloud content if authenticated (owned/purchased books)
260
+ // Only fetch marketplace if include_library flag is set
261
+ let cloudResult;
262
+ let libraryResult;
263
+ if (query) {
264
+ // Always fetch cloud content from owned books if authenticated
265
+ cloudResult = await fetchCloudContent(query, limit);
266
+ // Only fetch marketplace discovery if explicitly requested
267
+ if (include_library) {
268
+ libraryResult = await fetchMarketplaceResults(query, 5);
269
+ // Filter library to exclude books user already owns
270
+ if (cloudResult.entries.length > 0) {
271
+ const ownedSlugs = new Set(cloudResult.entries.map(e => e.book_slug));
272
+ libraryResult.books = libraryResult.books.filter(b => !ownedSlugs.has(b.slug));
273
+ libraryResult.total = libraryResult.books.length;
274
+ }
275
+ }
276
+ }
277
+ // Convert cloud entries to BriefEntry format and merge
278
+ let finalEntries = [...entries];
279
+ if (cloudResult && cloudResult.entries.length > 0) {
280
+ const cloudBriefEntries = cloudResult.entries.map(ce => ({
281
+ title: ce.title,
282
+ intent: ce.intent,
283
+ context: ce.context,
284
+ preview: ce.insight.length > 100 ? ce.insight.slice(0, 100) + '...' : ce.insight,
285
+ path: `cloud:${ce.book_slug}`, // Virtual path for cloud entries
286
+ created: new Date().toISOString(),
287
+ hits: 0,
288
+ last_hit: null,
289
+ source: 'cloud',
290
+ book_name: ce.book_name,
291
+ book_slug: ce.book_slug,
292
+ }));
293
+ finalEntries = [...entries, ...cloudBriefEntries].slice(0, limit);
294
+ }
146
295
  // Build message
147
296
  let message;
148
297
  if (query) {
149
298
  message = total === 0
150
- ? `No entries found for "${query}".`
151
- : `Found ${total} ${total === 1 ? 'entry' : 'entries'} for "${query}".`;
299
+ ? `No local entries found for "${query}".`
300
+ : `Found ${total} local ${total === 1 ? 'entry' : 'entries'} for "${query}".`;
152
301
  }
153
302
  else {
154
303
  message = `${total} ${total === 1 ? 'entry' : 'entries'} in library.`;
155
304
  }
305
+ if (cloudResult && cloudResult.entries.length > 0) {
306
+ message += ` Also found ${cloudResult.total} matching entries from owned books.`;
307
+ }
308
+ if (libraryResult && libraryResult.books.length > 0) {
309
+ message += ` ${libraryResult.total} book(s) available on library.`;
310
+ }
156
311
  return {
157
- entries,
158
- total,
312
+ entries: finalEntries,
313
+ total: finalEntries.length,
159
314
  message,
160
315
  libraryPath: localPath,
316
+ library: libraryResult,
161
317
  };
162
318
  },
163
319
  };
@@ -246,3 +402,66 @@ function rankEntries(entries) {
246
402
  scored.sort((a, b) => b.score - a.score);
247
403
  return scored.map(s => s.entry);
248
404
  }
405
+ async function fetchCloudContent(query, limit) {
406
+ try {
407
+ // Check if authenticated
408
+ const apiKey = await loadApiKey();
409
+ if (!apiKey) {
410
+ return { entries: [], total: 0 };
411
+ }
412
+ const response = await fetch(`${TELVOK_API_URL}/api/library/query`, {
413
+ method: 'POST',
414
+ headers: {
415
+ 'Content-Type': 'application/json',
416
+ 'Authorization': `Bearer ${apiKey}`,
417
+ },
418
+ body: JSON.stringify({ query, limit }),
419
+ });
420
+ if (!response.ok) {
421
+ // Don't fail if cloud query fails
422
+ return { entries: [], total: 0 };
423
+ }
424
+ const data = await response.json();
425
+ return {
426
+ entries: data.entries || [],
427
+ total: data.total || 0,
428
+ };
429
+ }
430
+ catch {
431
+ // Network error - silently return empty results
432
+ return { entries: [], total: 0 };
433
+ }
434
+ }
435
+ // ============================================================================
436
+ // Marketplace Search
437
+ // ============================================================================
438
+ async function fetchMarketplaceResults(query, limit) {
439
+ try {
440
+ const response = await fetch(`${TELVOK_API_URL}/api/search`, {
441
+ method: 'POST',
442
+ headers: { 'Content-Type': 'application/json' },
443
+ body: JSON.stringify({ query, limit }),
444
+ });
445
+ if (!response.ok) {
446
+ // Don't fail the whole brief() if library is down
447
+ return { books: [], total: 0 };
448
+ }
449
+ const data = await response.json();
450
+ const books = (data.books || []).map((b) => ({
451
+ slug: b.slug,
452
+ name: b.name,
453
+ description: b.description,
454
+ pricing: b.pricing,
455
+ price: b.price,
456
+ entries: b.entries,
457
+ }));
458
+ return {
459
+ books,
460
+ total: data.total || 0,
461
+ };
462
+ }
463
+ catch {
464
+ // Network error - silently return empty results
465
+ return { books: [], total: 0 };
466
+ }
467
+ }
@@ -0,0 +1,87 @@
1
+ interface EntryInfo {
2
+ path: string;
3
+ title: string;
4
+ preview: string;
5
+ created?: string;
6
+ }
7
+ interface DeleteResult {
8
+ success: boolean;
9
+ message: string;
10
+ deleted?: {
11
+ path: string;
12
+ title: string;
13
+ };
14
+ matches?: EntryInfo[];
15
+ action_required?: string;
16
+ }
17
+ export declare const deleteTool: {
18
+ name: string;
19
+ title: string;
20
+ description: string;
21
+ inputSchema: {
22
+ type: "object";
23
+ properties: {
24
+ path: {
25
+ type: string;
26
+ description: string;
27
+ };
28
+ query: {
29
+ type: string;
30
+ description: string;
31
+ };
32
+ confirm: {
33
+ type: string;
34
+ description: string;
35
+ };
36
+ };
37
+ required: never[];
38
+ };
39
+ outputSchema: {
40
+ type: "object";
41
+ properties: {
42
+ success: {
43
+ type: string;
44
+ };
45
+ message: {
46
+ type: string;
47
+ };
48
+ deleted: {
49
+ type: string;
50
+ properties: {
51
+ path: {
52
+ type: string;
53
+ };
54
+ title: {
55
+ type: string;
56
+ };
57
+ };
58
+ };
59
+ matches: {
60
+ type: string;
61
+ items: {
62
+ type: string;
63
+ properties: {
64
+ path: {
65
+ type: string;
66
+ };
67
+ title: {
68
+ type: string;
69
+ };
70
+ preview: {
71
+ type: string;
72
+ };
73
+ created: {
74
+ type: string;
75
+ };
76
+ };
77
+ };
78
+ };
79
+ action_required: {
80
+ type: string;
81
+ };
82
+ };
83
+ required: string[];
84
+ };
85
+ handler(args: unknown): Promise<DeleteResult>;
86
+ };
87
+ export default deleteTool;
@@ -0,0 +1,266 @@
1
+ // ============================================================================
2
+ // Delete Tool
3
+ // Delete local entries from the library
4
+ // ============================================================================
5
+ import * as fs from 'fs/promises';
6
+ import * as path from 'path';
7
+ import { glob } from 'glob';
8
+ import matter from 'gray-matter';
9
+ import { getLibraryPath, getLocalPath } from '../library/storage.js';
10
+ import { loadIndex, saveIndex, removeFromIndex } from '../library/vector-index.js';
11
+ // ============================================================================
12
+ // Tool Definition
13
+ // ============================================================================
14
+ export const deleteTool = {
15
+ name: 'delete',
16
+ title: 'Delete Local Entry',
17
+ description: `Delete entries from your local library (.librarian/local/).
18
+
19
+ USE THIS TOOL WHEN:
20
+ - User wants to remove an outdated entry
21
+ - Entry is wrong or no longer relevant
22
+ - Cleaning up test/experimental entries
23
+ - User says "delete", "remove", or "clean up" an entry
24
+
25
+ ONLY deletes from local/ (your own entries). Cannot delete:
26
+ - Imported content (use different management)
27
+ - Purchased/packages content (managed by library)
28
+
29
+ TRIGGER PATTERNS:
30
+ - "Delete that entry" → delete({ path: "local/entry-name.md" })
31
+ - "Remove the auth entry" → delete({ query: "auth" }) // Lists matches first
32
+ - After seeing matches → delete({ path: "local/exact-path.md", confirm: true })
33
+
34
+ Flow:
35
+ 1. With query: Lists matching entries, asks for specific path
36
+ 2. With path + confirm: Deletes the entry
37
+ 3. Without confirm: Shows what will be deleted, asks for confirmation`,
38
+ inputSchema: {
39
+ type: 'object',
40
+ properties: {
41
+ path: {
42
+ type: 'string',
43
+ description: 'Exact path to delete (e.g., "local/entry-name.md")',
44
+ },
45
+ query: {
46
+ type: 'string',
47
+ description: 'Search query to find entries to delete',
48
+ },
49
+ confirm: {
50
+ type: 'boolean',
51
+ description: 'Confirm deletion (required when path is provided)',
52
+ },
53
+ },
54
+ required: [],
55
+ },
56
+ outputSchema: {
57
+ type: 'object',
58
+ properties: {
59
+ success: { type: 'boolean' },
60
+ message: { type: 'string' },
61
+ deleted: {
62
+ type: 'object',
63
+ properties: {
64
+ path: { type: 'string' },
65
+ title: { type: 'string' },
66
+ },
67
+ },
68
+ matches: {
69
+ type: 'array',
70
+ items: {
71
+ type: 'object',
72
+ properties: {
73
+ path: { type: 'string' },
74
+ title: { type: 'string' },
75
+ preview: { type: 'string' },
76
+ created: { type: 'string' },
77
+ },
78
+ },
79
+ },
80
+ action_required: { type: 'string' },
81
+ },
82
+ required: ['success', 'message'],
83
+ },
84
+ async handler(args) {
85
+ const { path: entryPath, query, confirm } = (args || {});
86
+ const libraryPath = getLibraryPath();
87
+ const localPath = getLocalPath(libraryPath);
88
+ // Mode 1: Search by query - list matching entries
89
+ if (query && !entryPath) {
90
+ const matches = await findMatchingEntries(localPath, libraryPath, query);
91
+ if (matches.length === 0) {
92
+ return {
93
+ success: false,
94
+ message: `No entries found matching "${query}" in local library.`,
95
+ };
96
+ }
97
+ if (matches.length === 1) {
98
+ return {
99
+ success: true,
100
+ message: `Found 1 entry matching "${query}":`,
101
+ matches,
102
+ action_required: `To delete, call: delete({ path: "${matches[0].path}", confirm: true })`,
103
+ };
104
+ }
105
+ return {
106
+ success: true,
107
+ message: `Found ${matches.length} entries matching "${query}". Specify which one to delete:`,
108
+ matches,
109
+ action_required: 'Call delete({ path: "local/exact-path.md", confirm: true }) with the specific path.',
110
+ };
111
+ }
112
+ // Mode 2: Delete by path
113
+ if (entryPath) {
114
+ // Normalize path
115
+ let normalizedPath = entryPath;
116
+ if (!normalizedPath.startsWith('local/')) {
117
+ normalizedPath = `local/${normalizedPath}`;
118
+ }
119
+ if (!normalizedPath.endsWith('.md')) {
120
+ normalizedPath += '.md';
121
+ }
122
+ const fullPath = path.resolve(libraryPath, normalizedPath);
123
+ // Prevent path traversal — only allow deleting within local/
124
+ const localDir = path.resolve(libraryPath, 'local');
125
+ if (!fullPath.startsWith(localDir)) {
126
+ return {
127
+ success: false,
128
+ message: 'Invalid path: can only delete entries within local/',
129
+ };
130
+ }
131
+ // Check if file exists
132
+ try {
133
+ await fs.access(fullPath);
134
+ }
135
+ catch {
136
+ return {
137
+ success: false,
138
+ message: `Entry not found: ${normalizedPath}`,
139
+ };
140
+ }
141
+ // Read entry to get title for confirmation
142
+ const content = await fs.readFile(fullPath, 'utf-8');
143
+ const { data: frontmatter, content: body } = matter(content);
144
+ let title = frontmatter.title;
145
+ if (!title) {
146
+ const headingMatch = body.match(/^#\s+(.+)$/m);
147
+ if (headingMatch) {
148
+ title = headingMatch[1].trim();
149
+ }
150
+ else {
151
+ title = path.basename(normalizedPath, '.md').replace(/-/g, ' ');
152
+ }
153
+ }
154
+ // Mode 2a: Show what will be deleted (no confirm)
155
+ if (!confirm) {
156
+ const preview = body.trim().split('\n').slice(0, 3).join('\n');
157
+ return {
158
+ success: true,
159
+ message: `Will delete entry:`,
160
+ matches: [{
161
+ path: normalizedPath,
162
+ title,
163
+ preview: preview.slice(0, 200) + (preview.length > 200 ? '...' : ''),
164
+ created: frontmatter.created,
165
+ }],
166
+ action_required: `Confirm by calling: delete({ path: "${normalizedPath}", confirm: true })`,
167
+ };
168
+ }
169
+ // Mode 2b: Actually delete (with confirm)
170
+ try {
171
+ // Delete the file
172
+ await fs.unlink(fullPath);
173
+ // Remove from vector index
174
+ try {
175
+ const index = await loadIndex();
176
+ removeFromIndex(index, normalizedPath);
177
+ await saveIndex(index);
178
+ }
179
+ catch (indexError) {
180
+ // Don't fail if index update fails - file is already deleted
181
+ console.error('Failed to update index:', indexError);
182
+ }
183
+ return {
184
+ success: true,
185
+ message: `Deleted "${title}" from local library.`,
186
+ deleted: {
187
+ path: normalizedPath,
188
+ title,
189
+ },
190
+ };
191
+ }
192
+ catch (error) {
193
+ const message = error instanceof Error ? error.message : String(error);
194
+ return {
195
+ success: false,
196
+ message: `Failed to delete: ${message}`,
197
+ };
198
+ }
199
+ }
200
+ // No path or query provided
201
+ return {
202
+ success: false,
203
+ message: 'Provide either a path or query to find entries to delete.',
204
+ action_required: 'Use delete({ query: "search term" }) to find entries, or delete({ path: "local/entry.md", confirm: true }) to delete directly.',
205
+ };
206
+ },
207
+ };
208
+ // ============================================================================
209
+ // Helper Functions
210
+ // ============================================================================
211
+ async function findMatchingEntries(localPath, libraryPath, query) {
212
+ const matches = [];
213
+ const queryLower = query.toLowerCase();
214
+ try {
215
+ const files = await glob(path.join(localPath, '**/*.md'), { nodir: true });
216
+ for (const filePath of files) {
217
+ try {
218
+ const content = await fs.readFile(filePath, 'utf-8');
219
+ const { data: frontmatter, content: body } = matter(content);
220
+ // Extract title
221
+ let title = frontmatter.title;
222
+ if (!title) {
223
+ const headingMatch = body.match(/^#\s+(.+)$/m);
224
+ if (headingMatch) {
225
+ title = headingMatch[1].trim();
226
+ }
227
+ else {
228
+ title = path.basename(filePath, '.md').replace(/-/g, ' ');
229
+ }
230
+ }
231
+ // Check if query matches title, content, context, or filename
232
+ const filename = path.basename(filePath, '.md');
233
+ const context = frontmatter.context || '';
234
+ const searchText = [title, body, context, filename].join(' ').toLowerCase();
235
+ if (searchText.includes(queryLower)) {
236
+ const relativePath = path.relative(libraryPath, filePath);
237
+ const preview = body.trim().split('\n').slice(0, 2).join(' ');
238
+ matches.push({
239
+ path: relativePath,
240
+ title,
241
+ preview: preview.slice(0, 150) + (preview.length > 150 ? '...' : ''),
242
+ created: frontmatter.created,
243
+ });
244
+ }
245
+ }
246
+ catch {
247
+ // Skip files that can't be parsed
248
+ }
249
+ }
250
+ }
251
+ catch {
252
+ // No local directory
253
+ }
254
+ // Sort by created date (newest first)
255
+ return matches.sort((a, b) => {
256
+ if (!a.created)
257
+ return 1;
258
+ if (!b.created)
259
+ return -1;
260
+ return new Date(b.created).getTime() - new Date(a.created).getTime();
261
+ });
262
+ }
263
+ // ============================================================================
264
+ // Export
265
+ // ============================================================================
266
+ export default deleteTool;
@@ -0,0 +1,27 @@
1
+ interface FeedbackResult {
2
+ success: boolean;
3
+ message: string;
4
+ feedback_id?: string;
5
+ }
6
+ export declare const feedbackTool: {
7
+ name: string;
8
+ title: string;
9
+ description: string;
10
+ inputSchema: {
11
+ type: "object";
12
+ properties: {
13
+ message: {
14
+ type: string;
15
+ description: string;
16
+ };
17
+ type: {
18
+ type: string;
19
+ enum: string[];
20
+ description: string;
21
+ };
22
+ };
23
+ required: string[];
24
+ };
25
+ handler(args: unknown): Promise<FeedbackResult>;
26
+ };
27
+ export default feedbackTool;