@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
@@ -0,0 +1,387 @@
1
+ // ============================================================================
2
+ // Marketplace Publish Tool
3
+ // Publish local entries as a book on Telvok 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 { loadApiKey } from './auth.js';
10
+ import { getLibraryPath, getLocalPath } from '../library/storage.js';
11
+ const TELVOK_API_URL = process.env.TELVOK_API_URL || 'https://telvok.com';
12
+ // ============================================================================
13
+ // Tool Definition
14
+ // ============================================================================
15
+ export const libraryPublishTool = {
16
+ name: 'library_publish',
17
+ title: 'Publish Book',
18
+ description: `Publish local entries as a book on Telvok marketplace.
19
+
20
+ USE THIS TOOL WHEN:
21
+ - User wants to share/sell their recorded knowledge
22
+ - User says "publish", "share", or "sell" their entries
23
+ - Creating a book from .librarian/local/ entries
24
+
25
+ ALWAYS use preview: true first to show what will be published.
26
+
27
+ TRIGGER PATTERNS:
28
+ - "Publish my entries" → library_publish({ name: "...", pricing: { type: "open" }, preview: true })
29
+ - "Sell my knowledge" → library_publish({ name: "...", pricing: { type: "one_time", price_cents: 500 }, preview: true })
30
+ - After preview approval → add attestation and consumption, remove preview
31
+
32
+ Required for actual publish: name, pricing, consumption, attestation (all true).
33
+
34
+ Examples:
35
+ - Preview: library_publish({ name: "My Book", pricing: { type: "open" }, preview: true })
36
+ - Publish: library_publish({ name: "My Book", pricing: { type: "open" }, consumption: "download", attestation: { original_work: true, no_secrets: true, terms_accepted: true } })`,
37
+ inputSchema: {
38
+ type: 'object',
39
+ properties: {
40
+ name: {
41
+ type: 'string',
42
+ description: 'Book title (3-100 characters)',
43
+ },
44
+ description: {
45
+ type: 'string',
46
+ description: 'Short description of the book (optional, max 500 chars)',
47
+ },
48
+ pricing: {
49
+ type: 'object',
50
+ properties: {
51
+ type: {
52
+ type: 'string',
53
+ enum: ['open', 'one_time', 'subscription'],
54
+ description: 'Pricing model',
55
+ },
56
+ price_cents: {
57
+ type: 'number',
58
+ description: 'Price in cents (required for paid, min 100 = $1.00)',
59
+ },
60
+ },
61
+ required: ['type'],
62
+ description: 'Pricing configuration',
63
+ },
64
+ consumption: {
65
+ type: 'string',
66
+ enum: ['inline', 'reference', 'download'],
67
+ description: 'How buyers access content. download only for free books.',
68
+ },
69
+ attestation: {
70
+ type: 'object',
71
+ properties: {
72
+ original_work: { type: 'boolean', description: 'Confirm this is original work' },
73
+ no_secrets: { type: 'boolean', description: 'Confirm no secrets/credentials' },
74
+ terms_accepted: { type: 'boolean', description: 'Accept library terms' },
75
+ },
76
+ required: ['original_work', 'no_secrets', 'terms_accepted'],
77
+ description: 'Required confirmations before publishing',
78
+ },
79
+ preview: {
80
+ type: 'boolean',
81
+ description: 'If true, show what would be published without publishing',
82
+ },
83
+ entries: {
84
+ type: 'array',
85
+ items: { type: 'string' },
86
+ description: 'Specific entry filenames to include (omit for all local/)',
87
+ },
88
+ tags: {
89
+ type: 'array',
90
+ items: { type: 'string' },
91
+ description: 'Category/topic tags (max 10)',
92
+ },
93
+ license: {
94
+ type: 'string',
95
+ enum: ['open', 'open_attributed', 'personal'],
96
+ description: 'License type (default: personal)',
97
+ },
98
+ },
99
+ required: ['name', 'pricing'],
100
+ },
101
+ async handler(args) {
102
+ const { name, description, pricing, consumption, attestation, preview, entries: entryFilter, tags, license } = args;
103
+ // Validate name
104
+ if (!name || typeof name !== 'string' || name.trim().length < 3) {
105
+ throw new Error('Book name is required (minimum 3 characters)');
106
+ }
107
+ if (name.trim().length > 100) {
108
+ throw new Error('Book name must be 100 characters or less');
109
+ }
110
+ // Validate pricing
111
+ if (!pricing || !pricing.type) {
112
+ throw new Error('Pricing type is required');
113
+ }
114
+ if (!['open', 'one_time', 'subscription'].includes(pricing.type)) {
115
+ throw new Error('Pricing type must be: open, one_time, or subscription');
116
+ }
117
+ if (pricing.type !== 'open' && (!pricing.price_cents || pricing.price_cents < 100)) {
118
+ throw new Error('Paid books require price_cents >= 100 ($1.00)');
119
+ }
120
+ if (pricing.price_cents && pricing.price_cents > 100000) {
121
+ throw new Error('Price cannot exceed $1000.00 (100000 cents)');
122
+ }
123
+ // Validate description length
124
+ if (description && description.length > 500) {
125
+ throw new Error('Description must be 500 characters or less');
126
+ }
127
+ // Validate tags count
128
+ if (tags && tags.length > 10) {
129
+ throw new Error('Maximum 10 tags allowed');
130
+ }
131
+ // Collect entries from local/ (needed for preview and publish)
132
+ const collectedEntries = await collectLocalEntries(entryFilter);
133
+ if (collectedEntries.length === 0) {
134
+ return {
135
+ success: false,
136
+ message: 'No entries found in .librarian/local/. Use record() to create entries first.',
137
+ };
138
+ }
139
+ // Format pricing display
140
+ const pricingDisplay = pricing.type === 'open'
141
+ ? 'Free'
142
+ : `$${((pricing.price_cents || 0) / 100).toFixed(2)}`;
143
+ // Handle preview mode - return summary without publishing
144
+ if (preview) {
145
+ return {
146
+ success: true,
147
+ preview: true,
148
+ message: `Preview of "${name.trim()}" - NOT published`,
149
+ summary: {
150
+ name: name.trim(),
151
+ pricing: { type: pricing.type, display: pricingDisplay },
152
+ entries_count: collectedEntries.length,
153
+ entries: collectedEntries.map(e => ({
154
+ title: e.title,
155
+ file: path.basename(e.originalPath),
156
+ })),
157
+ },
158
+ next_steps: 'To publish, add consumption type and attestation fields.',
159
+ };
160
+ }
161
+ // Validate consumption type (required for actual publish)
162
+ if (!consumption) {
163
+ return {
164
+ success: false,
165
+ message: 'Consumption type required. Choose how buyers access your content:',
166
+ options: {
167
+ inline: 'Content returned in API responses (best for small entries)',
168
+ reference: 'README + pointers to entries (best for larger books)',
169
+ download: 'Download to local library (only for free/open books)',
170
+ },
171
+ };
172
+ }
173
+ if (!['inline', 'reference', 'download'].includes(consumption)) {
174
+ return {
175
+ success: false,
176
+ message: 'Invalid consumption type. Must be: inline, reference, or download',
177
+ };
178
+ }
179
+ if (consumption === 'download' && pricing.type !== 'open') {
180
+ return {
181
+ success: false,
182
+ message: 'Download is only for free books. Paid content uses inline or reference.',
183
+ next_steps: "Use pricing.type: 'open' for download, or consumption: 'inline'/'reference' for paid.",
184
+ };
185
+ }
186
+ // Validate attestation (required for actual publish)
187
+ if (!attestation) {
188
+ return {
189
+ success: false,
190
+ message: 'Attestation required. Please confirm:',
191
+ required: {
192
+ original_work: 'This is my original work or I have rights to publish',
193
+ no_secrets: 'Contains no secrets, credentials, or sensitive data',
194
+ terms_accepted: 'I accept the Telvok library terms',
195
+ },
196
+ };
197
+ }
198
+ const failedAttestations = [];
199
+ if (!attestation.original_work)
200
+ failedAttestations.push('original_work');
201
+ if (!attestation.no_secrets)
202
+ failedAttestations.push('no_secrets');
203
+ if (!attestation.terms_accepted)
204
+ failedAttestations.push('terms_accepted');
205
+ if (failedAttestations.length > 0) {
206
+ return {
207
+ success: false,
208
+ message: `All attestation fields must be true to publish. Failed: ${failedAttestations.join(', ')}`,
209
+ };
210
+ }
211
+ // Check authentication
212
+ const apiKey = await loadApiKey();
213
+ if (!apiKey) {
214
+ return {
215
+ success: false,
216
+ message: 'Not authenticated. Run auth({ action: "login" }) to connect your Telvok account first.',
217
+ };
218
+ }
219
+ // Format entries for API
220
+ const apiEntries = collectedEntries.map(e => ({
221
+ title: e.title,
222
+ content: e.content,
223
+ intent: e.intent,
224
+ context: e.context,
225
+ reasoning: e.reasoning,
226
+ example: e.example,
227
+ }));
228
+ // Build request body
229
+ const requestBody = {
230
+ name: name.trim(),
231
+ description: description?.trim(),
232
+ pricing,
233
+ consumption,
234
+ entries: apiEntries,
235
+ tags: tags || [],
236
+ license_type: license || 'personal',
237
+ attestation,
238
+ };
239
+ try {
240
+ const response = await fetch(`${TELVOK_API_URL}/api/publish`, {
241
+ method: 'POST',
242
+ headers: {
243
+ 'Authorization': `Bearer ${apiKey}`,
244
+ 'Content-Type': 'application/json',
245
+ },
246
+ body: JSON.stringify(requestBody),
247
+ });
248
+ const data = await response.json();
249
+ if (!response.ok) {
250
+ // Handle Stripe Connect requirement
251
+ if (data.error === 'stripe_connect_required') {
252
+ return {
253
+ success: false,
254
+ message: `Stripe Connect required to sell paid content.\n\nComplete setup at: ${data.setup_url}`,
255
+ setup_url: data.setup_url,
256
+ };
257
+ }
258
+ // Handle validation errors
259
+ if (data.error === 'validation_error') {
260
+ const details = Object.entries(data.details || {})
261
+ .map(([k, v]) => ` - ${k}: ${v}`)
262
+ .join('\n');
263
+ return {
264
+ success: false,
265
+ message: `Validation failed:\n${details}`,
266
+ };
267
+ }
268
+ return {
269
+ success: false,
270
+ message: data.error || `Publish failed: HTTP ${response.status}`,
271
+ };
272
+ }
273
+ return {
274
+ success: true,
275
+ message: data.message || `Published "${data.book?.name}" with ${data.entries_count} entries`,
276
+ book: data.book,
277
+ entries_count: data.entries_count,
278
+ };
279
+ }
280
+ catch (error) {
281
+ const message = error instanceof Error ? error.message : String(error);
282
+ throw new Error(`Publish failed: ${message}`);
283
+ }
284
+ },
285
+ };
286
+ // ============================================================================
287
+ // Entry Collection
288
+ // ============================================================================
289
+ async function collectLocalEntries(filter) {
290
+ const libraryPath = getLibraryPath();
291
+ const localPath = getLocalPath(libraryPath);
292
+ const entries = [];
293
+ try {
294
+ const files = await glob(path.join(localPath, '**/*.md'), { nodir: true });
295
+ for (const filePath of files) {
296
+ const filename = path.basename(filePath);
297
+ // If filter specified, only include matching files
298
+ if (filter && filter.length > 0) {
299
+ const matchesFilter = filter.some(f => filename === f ||
300
+ filename === f + '.md' ||
301
+ filePath.endsWith(f) ||
302
+ filePath.endsWith(f + '.md'));
303
+ if (!matchesFilter)
304
+ continue;
305
+ }
306
+ try {
307
+ const content = await fs.readFile(filePath, 'utf-8');
308
+ const parsed = parseEntryFile(content, filePath);
309
+ if (parsed) {
310
+ entries.push({
311
+ ...parsed,
312
+ originalPath: filePath,
313
+ });
314
+ }
315
+ }
316
+ catch {
317
+ // Skip files that can't be parsed
318
+ }
319
+ }
320
+ }
321
+ catch {
322
+ // No local directory yet
323
+ }
324
+ return entries;
325
+ }
326
+ function parseEntryFile(content, filePath) {
327
+ const { data: frontmatter, content: body } = matter(content);
328
+ const trimmedBody = body.trim();
329
+ if (!trimmedBody)
330
+ return null;
331
+ // Extract title from frontmatter, H1, or filename
332
+ let title = frontmatter.title;
333
+ if (!title) {
334
+ const headingMatch = trimmedBody.match(/^#\s+(.+)$/m);
335
+ if (headingMatch) {
336
+ title = headingMatch[1].trim();
337
+ }
338
+ else {
339
+ // Use filename as title, converting hyphens to spaces
340
+ title = path.basename(filePath, '.md')
341
+ .replace(/-/g, ' ')
342
+ .replace(/\b\w/g, l => l.toUpperCase());
343
+ }
344
+ }
345
+ // Extract sections from body
346
+ const sections = extractSections(trimmedBody);
347
+ return {
348
+ title,
349
+ content: sections.main || trimmedBody,
350
+ intent: frontmatter.intent || undefined,
351
+ context: frontmatter.context || undefined,
352
+ reasoning: sections.reasoning,
353
+ example: sections.example,
354
+ };
355
+ }
356
+ function extractSections(body) {
357
+ const result = {
358
+ main: body,
359
+ };
360
+ // Find ## Reasoning section
361
+ const reasoningMatch = body.match(/##\s*Reasoning\s*\n([\s\S]*?)(?=##|$)/i);
362
+ if (reasoningMatch) {
363
+ result.reasoning = reasoningMatch[1].trim();
364
+ }
365
+ // Find ## Example section
366
+ const exampleMatch = body.match(/##\s*Example\s*\n([\s\S]*?)(?=##|$)/i);
367
+ if (exampleMatch) {
368
+ result.example = exampleMatch[1].trim();
369
+ }
370
+ // Main content is everything after title until first ## section
371
+ const mainMatch = body.match(/^#\s+.+\n\n?([\s\S]*?)(?=##|$)/);
372
+ if (mainMatch) {
373
+ result.main = mainMatch[1].trim();
374
+ }
375
+ else {
376
+ // If no H1 header, take content before first ## section
377
+ const beforeSections = body.match(/^([\s\S]*?)(?=##)/);
378
+ if (beforeSections) {
379
+ result.main = beforeSections[1].trim();
380
+ }
381
+ }
382
+ return result;
383
+ }
384
+ // ============================================================================
385
+ // Export
386
+ // ============================================================================
387
+ export default libraryPublishTool;
@@ -0,0 +1,110 @@
1
+ interface BookResult {
2
+ slug: string;
3
+ name: string;
4
+ description: string;
5
+ pricing: 'open' | 'one_time' | 'subscription';
6
+ price: string;
7
+ entries: number;
8
+ rating: number | null;
9
+ tags: string[];
10
+ total_hits: number;
11
+ }
12
+ interface SearchResult {
13
+ books: BookResult[];
14
+ total: number;
15
+ message: string;
16
+ }
17
+ export declare const librarySearchTool: {
18
+ name: string;
19
+ title: string;
20
+ description: string;
21
+ inputSchema: {
22
+ type: "object";
23
+ properties: {
24
+ query: {
25
+ type: string;
26
+ description: string;
27
+ };
28
+ filters: {
29
+ type: string;
30
+ properties: {
31
+ pricing: {
32
+ type: string;
33
+ enum: string[];
34
+ description: string;
35
+ };
36
+ tags: {
37
+ type: string;
38
+ items: {
39
+ type: string;
40
+ };
41
+ description: string;
42
+ };
43
+ min_rating: {
44
+ type: string;
45
+ description: string;
46
+ };
47
+ };
48
+ description: string;
49
+ };
50
+ limit: {
51
+ type: string;
52
+ description: string;
53
+ };
54
+ };
55
+ required: never[];
56
+ };
57
+ outputSchema: {
58
+ type: "object";
59
+ properties: {
60
+ books: {
61
+ type: string;
62
+ items: {
63
+ type: string;
64
+ properties: {
65
+ slug: {
66
+ type: string;
67
+ };
68
+ name: {
69
+ type: string;
70
+ };
71
+ description: {
72
+ type: string;
73
+ };
74
+ pricing: {
75
+ type: string;
76
+ enum: string[];
77
+ };
78
+ price: {
79
+ type: string;
80
+ };
81
+ entries: {
82
+ type: string;
83
+ };
84
+ rating: {
85
+ type: string;
86
+ };
87
+ tags: {
88
+ type: string;
89
+ items: {
90
+ type: string;
91
+ };
92
+ };
93
+ total_hits: {
94
+ type: string;
95
+ };
96
+ };
97
+ };
98
+ };
99
+ total: {
100
+ type: string;
101
+ };
102
+ message: {
103
+ type: string;
104
+ };
105
+ };
106
+ required: string[];
107
+ };
108
+ handler(args: unknown): Promise<SearchResult>;
109
+ };
110
+ export {};
@@ -0,0 +1,132 @@
1
+ // ============================================================================
2
+ // Library Search Tool
3
+ // Search for books in the Telvok library
4
+ // ============================================================================
5
+ const TELVOK_API_URL = process.env.TELVOK_API_URL || 'https://telvok.com';
6
+ // ============================================================================
7
+ // Tool Definition
8
+ // ============================================================================
9
+ export const librarySearchTool = {
10
+ name: 'library_search',
11
+ title: 'Search Marketplace',
12
+ description: `Search Telvok marketplace for knowledge books.
13
+
14
+ USE THIS TOOL WHEN:
15
+ - brief() returns no results and marketplace might have relevant content
16
+ - User explicitly asks to find/search marketplace books
17
+ - Looking for domain expertise we don't have locally
18
+
19
+ DO NOT USE when brief() already found useful local entries.
20
+
21
+ TRIGGER PATTERNS:
22
+ - brief() found nothing → library_search({ query: "<same topic>" })
23
+ - "Find books about X" → library_search({ query: "X" })
24
+ - "What's in the marketplace?" → library_search({})
25
+ - "Show free books" → library_search({ filters: { pricing: "open" } })
26
+
27
+ Filters: pricing ("open"/"one_time"/"subscription"), tags, min_rating (0-5)
28
+
29
+ Examples:
30
+ - library_search({ query: "react hooks" })
31
+ - library_search({ filters: { pricing: "open" } })`,
32
+ inputSchema: {
33
+ type: 'object',
34
+ properties: {
35
+ query: {
36
+ type: 'string',
37
+ description: 'Search terms to find books',
38
+ },
39
+ filters: {
40
+ type: 'object',
41
+ properties: {
42
+ pricing: {
43
+ type: 'string',
44
+ enum: ['open', 'one_time', 'subscription'],
45
+ description: 'Filter by pricing type',
46
+ },
47
+ tags: {
48
+ type: 'array',
49
+ items: { type: 'string' },
50
+ description: 'Filter by tags',
51
+ },
52
+ min_rating: {
53
+ type: 'number',
54
+ description: 'Minimum quality rating (0-5)',
55
+ },
56
+ },
57
+ description: 'Optional filters to narrow results',
58
+ },
59
+ limit: {
60
+ type: 'number',
61
+ description: 'Maximum results to return (default: 10)',
62
+ },
63
+ },
64
+ required: [], // Query is optional - empty returns all books (browse mode)
65
+ },
66
+ outputSchema: {
67
+ type: 'object',
68
+ properties: {
69
+ books: {
70
+ type: 'array',
71
+ items: {
72
+ type: 'object',
73
+ properties: {
74
+ slug: { type: 'string' },
75
+ name: { type: 'string' },
76
+ description: { type: 'string' },
77
+ pricing: { type: 'string', enum: ['open', 'one_time', 'subscription'] },
78
+ price: { type: 'string' },
79
+ entries: { type: 'number' },
80
+ rating: { type: 'number' },
81
+ tags: { type: 'array', items: { type: 'string' } },
82
+ total_hits: { type: 'number' },
83
+ },
84
+ },
85
+ },
86
+ total: { type: 'number' },
87
+ message: { type: 'string' },
88
+ },
89
+ required: ['books', 'total', 'message'],
90
+ },
91
+ async handler(args) {
92
+ const { query = '', filters, limit = 10 } = args;
93
+ try {
94
+ const response = await fetch(`${TELVOK_API_URL}/api/search`, {
95
+ method: 'POST',
96
+ headers: { 'Content-Type': 'application/json' },
97
+ body: JSON.stringify({ query, filters, limit }),
98
+ });
99
+ if (!response.ok) {
100
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }));
101
+ throw new Error(error.error || `Search failed: HTTP ${response.status}`);
102
+ }
103
+ const data = await response.json();
104
+ // Format results for agent consumption
105
+ const books = data.books || [];
106
+ const total = data.total || 0;
107
+ if (books.length === 0) {
108
+ const queryMsg = query ? `for "${query}"` : 'matching your filters';
109
+ return {
110
+ books: [],
111
+ total: 0,
112
+ message: `No books found ${queryMsg}. Try different search terms or filters.`,
113
+ };
114
+ }
115
+ // Build helpful summary
116
+ const summary = books.map((b, i) => {
117
+ const helpedText = b.total_hits > 0 ? `Helped ${b.total_hits} agent${b.total_hits === 1 ? '' : 's'}` : `${b.entries} entries`;
118
+ return `${i + 1}. **${b.name}** (${b.price}) - ${helpedText}\n ${b.description?.slice(0, 100)}${b.description?.length > 100 ? '...' : ''}`;
119
+ }).join('\n');
120
+ const queryMsg = query ? `for "${query}"` : 'in library';
121
+ return {
122
+ books,
123
+ total,
124
+ message: `Found ${total} book(s) ${queryMsg}:\n\n${summary}\n\nUse library_buy({ slug: "..." }) to purchase a book.`,
125
+ };
126
+ }
127
+ catch (error) {
128
+ const message = error instanceof Error ? error.message : String(error);
129
+ throw new Error(`Library search failed: ${message}`);
130
+ }
131
+ },
132
+ };
@@ -5,6 +5,7 @@ export interface MarkHitResult {
5
5
  }
6
6
  export declare const markHitTool: {
7
7
  name: string;
8
+ title: string;
8
9
  description: string;
9
10
  inputSchema: {
10
11
  type: "object";