@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.
- package/dist/library/errors.d.ts +48 -0
- package/dist/library/errors.js +80 -0
- package/dist/library/schemas.d.ts +6 -6
- package/dist/library/storage.d.ts +2 -2
- package/dist/library/storage.js +2 -2
- package/dist/library 2/embeddings.d.ts +21 -0
- package/dist/library 2/embeddings.js +86 -0
- package/dist/library 2/manager.d.ts +42 -0
- package/dist/library 2/manager.js +218 -0
- package/dist/library 2/parsers/cursor.d.ts +15 -0
- package/dist/library 2/parsers/cursor.js +168 -0
- package/dist/library 2/parsers/index.d.ts +6 -0
- package/dist/library 2/parsers/index.js +5 -0
- package/dist/library 2/parsers/json.d.ts +11 -0
- package/dist/library 2/parsers/json.js +95 -0
- package/dist/library 2/parsers/jsonl.d.ts +14 -0
- package/dist/library 2/parsers/jsonl.js +85 -0
- package/dist/library 2/parsers/markdown.d.ts +15 -0
- package/dist/library 2/parsers/markdown.js +77 -0
- package/dist/library 2/parsers/sqlite.d.ts +8 -0
- package/dist/library 2/parsers/sqlite.js +123 -0
- package/dist/library 2/parsers/types.d.ts +21 -0
- package/dist/library 2/parsers/types.js +4 -0
- package/dist/library 2/query.d.ts +26 -0
- package/dist/library 2/query.js +104 -0
- package/dist/library 2/schemas.d.ts +324 -0
- package/dist/library 2/schemas.js +79 -0
- package/dist/library 2/storage.d.ts +22 -0
- package/dist/library 2/storage.js +36 -0
- package/dist/library 2/vector-index.d.ts +55 -0
- package/dist/library 2/vector-index.js +160 -0
- package/dist/server 2.js +199 -0
- package/dist/server.d 2.ts +2 -0
- package/dist/server.js +102 -54
- package/dist/tools/adopt.d.ts +1 -0
- package/dist/tools/adopt.js +37 -10
- package/dist/tools/auth.d.ts +69 -0
- package/dist/tools/auth.js +379 -0
- package/dist/tools/bounty-claim.d.ts +28 -0
- package/dist/tools/bounty-claim.js +92 -0
- package/dist/tools/bounty-create.d.ts +47 -0
- package/dist/tools/bounty-create.js +118 -0
- package/dist/tools/bounty-list.d.ts +50 -0
- package/dist/tools/bounty-list.js +116 -0
- package/dist/tools/bounty-submit.d.ts +34 -0
- package/dist/tools/bounty-submit.js +94 -0
- package/dist/tools/brief.d.ts +94 -0
- package/dist/tools/brief.js +234 -15
- package/dist/tools/delete.d.ts +87 -0
- package/dist/tools/delete.js +266 -0
- package/dist/tools/feedback.d.ts +27 -0
- package/dist/tools/feedback.js +98 -0
- package/dist/tools/help.d.ts +22 -0
- package/dist/tools/help.js +482 -0
- package/dist/tools/import-memories.d.ts +1 -0
- package/dist/tools/import-memories.js +18 -13
- package/dist/tools/index.d.ts +11 -0
- package/dist/tools/index.js +12 -0
- package/dist/tools/library-buy.d.ts +31 -0
- package/dist/tools/library-buy.js +104 -0
- package/dist/tools/library-download.d.ts +27 -0
- package/dist/tools/library-download.js +177 -0
- package/dist/tools/library-publish.d.ts +112 -0
- package/dist/tools/library-publish.js +387 -0
- package/dist/tools/library-search.d.ts +110 -0
- package/dist/tools/library-search.js +132 -0
- package/dist/tools/mark-hit.d.ts +1 -0
- package/dist/tools/mark-hit.js +83 -5
- package/dist/tools/my-books.d.ts +51 -0
- package/dist/tools/my-books.js +115 -0
- package/dist/tools/my-bounties.d.ts +43 -0
- package/dist/tools/my-bounties.js +126 -0
- package/dist/tools/rate-book.d.ts +40 -0
- package/dist/tools/rate-book.js +147 -0
- package/dist/tools/rebuild-index.d.ts +1 -0
- package/dist/tools/rebuild-index.js +40 -8
- package/dist/tools/record.d.ts +18 -0
- package/dist/tools/record.js +30 -26
- package/dist/tools/seller-analytics.d.ts +53 -0
- package/dist/tools/seller-analytics.js +180 -0
- package/dist/tools/sync.d.ts +55 -0
- package/dist/tools/sync.js +304 -0
- package/dist/tools/unsubscribe.d.ts +48 -0
- package/dist/tools/unsubscribe.js +120 -0
- package/dist/tools 2/adopt.d.ts +24 -0
- package/dist/tools 2/adopt.js +154 -0
- package/dist/tools 2/auth.d.ts +35 -0
- package/dist/tools 2/auth.js +229 -0
- package/dist/tools 2/brief.d.ts +56 -0
- package/dist/tools 2/brief.js +414 -0
- package/dist/tools 2/help.d.ts +21 -0
- package/dist/tools 2/help.js +267 -0
- package/dist/tools 2/import-memories.d.ts +32 -0
- package/dist/tools 2/import-memories.js +231 -0
- package/dist/tools 2/index.d.ts +12 -0
- package/dist/tools 2/index.js +12 -0
- package/dist/tools 2/mark-hit.d.ts +20 -0
- package/dist/tools 2/mark-hit.js +71 -0
- package/dist/tools 2/marketplace-buy.d.ts +30 -0
- package/dist/tools 2/marketplace-buy.js +97 -0
- package/dist/tools 2/marketplace-download.d.ts +26 -0
- package/dist/tools 2/marketplace-download.js +160 -0
- package/dist/tools 2/marketplace-publish.d.ts +111 -0
- package/dist/tools 2/marketplace-publish.js +377 -0
- package/dist/tools 2/marketplace-search.d.ts +57 -0
- package/dist/tools 2/marketplace-search.js +96 -0
- package/dist/tools 2/my-books.d.ts +50 -0
- package/dist/tools 2/my-books.js +107 -0
- package/dist/tools 2/rate-book.d.ts +39 -0
- package/dist/tools 2/rate-book.js +139 -0
- package/dist/tools 2/rebuild-index.d.ts +23 -0
- package/dist/tools 2/rebuild-index.js +107 -0
- package/dist/tools 2/record.d.ts +40 -0
- package/dist/tools 2/record.js +205 -0
- package/dist/tools 2/seller-analytics.d.ts +35 -0
- package/dist/tools 2/seller-analytics.js +102 -0
- package/dist/tools 2/sync.d.ts +54 -0
- package/dist/tools 2/sync.js +298 -0
- package/package.json +1 -1
package/dist/tools/brief.js
CHANGED
|
@@ -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
|
-
|
|
14
|
+
title: 'Search Knowledge Library',
|
|
15
|
+
description: `CALL THIS PROACTIVELY before thinking through problems, making decisions, or planning.
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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({
|
|
21
|
-
- brief({}) →
|
|
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
|
|
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 (
|
|
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;
|