@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
|
@@ -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
|
+
};
|