@telvok/librarian-mcp 2.4.0 → 2.4.1
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.
|
@@ -5,9 +5,6 @@
|
|
|
5
5
|
// ============================================================================
|
|
6
6
|
import * as fs from 'fs/promises';
|
|
7
7
|
import * as path from 'path';
|
|
8
|
-
import * as crypto from 'crypto';
|
|
9
|
-
import { execSync } from 'child_process';
|
|
10
|
-
import { platform } from 'os';
|
|
11
8
|
import { glob } from 'glob';
|
|
12
9
|
import matter from 'gray-matter';
|
|
13
10
|
import { loadApiKey } from './auth.js';
|
|
@@ -67,7 +64,7 @@ If the user says "publish my entries" — call library_publish() with NO args to
|
|
|
67
64
|
description: { type: 'string', description: 'Book description. Only provide after user writes or approves it.' },
|
|
68
65
|
tags: { type: 'array', items: { type: 'string' }, description: 'Topic tags (max 10).' },
|
|
69
66
|
license: { type: 'string', enum: ['open', 'open_attributed', 'personal'] },
|
|
70
|
-
|
|
67
|
+
confirm: { type: 'boolean', description: 'Set to true ONLY after showing the summary to the user and they say yes.' },
|
|
71
68
|
},
|
|
72
69
|
required: [],
|
|
73
70
|
},
|
|
@@ -267,72 +264,25 @@ If the user says "publish my entries" — call library_publish() with NO args to
|
|
|
267
264
|
wizardState.tags = input.tags;
|
|
268
265
|
wizardState.license = input.license || 'personal';
|
|
269
266
|
wizardState.step = 'confirm';
|
|
270
|
-
// Generate confirmation code
|
|
271
|
-
const confirmCode = crypto.randomBytes(3).toString('hex').toUpperCase(); // 6 chars
|
|
272
267
|
const priceDisplay = wizardState.pricing.type === 'open'
|
|
273
268
|
? 'Free (download)'
|
|
274
269
|
: `$${(wizardState.pricing.price_cents / 100).toFixed(2)}/${wizardState.pricing.type === 'subscription' ? 'mo' : 'once'}`;
|
|
275
|
-
// Try native dialog on macOS
|
|
276
|
-
let confirmMethod = 'code';
|
|
277
|
-
if (platform() === 'darwin') {
|
|
278
|
-
try {
|
|
279
|
-
const dialogText = [
|
|
280
|
-
`Publish "${wizardState.name}" to Telvok?`,
|
|
281
|
-
``,
|
|
282
|
-
`${wizardState.selectedEntries.length} entries — ${priceDisplay}`,
|
|
283
|
-
wizardState.description ? `"${wizardState.description}"` : '',
|
|
284
|
-
``,
|
|
285
|
-
`This action publishes to the marketplace.`,
|
|
286
|
-
].filter(Boolean).join('\\n');
|
|
287
|
-
const result = execSync(`osascript -e 'display dialog "${dialogText}" buttons {"Cancel", "Publish"} default button "Cancel" with title "Telvok Publish"'`, { encoding: 'utf-8', timeout: 120000 });
|
|
288
|
-
if (result.includes('Publish')) {
|
|
289
|
-
confirmMethod = 'dialog_confirmed';
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
catch {
|
|
293
|
-
// User clicked Cancel or osascript failed — fall through to code method
|
|
294
|
-
confirmMethod = 'dialog_cancelled';
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
if (confirmMethod === 'dialog_confirmed') {
|
|
298
|
-
// User confirmed via native dialog — publish immediately
|
|
299
|
-
return await executePublish(wizardState);
|
|
300
|
-
}
|
|
301
|
-
if (confirmMethod === 'dialog_cancelled') {
|
|
302
|
-
wizardState = null;
|
|
303
|
-
return {
|
|
304
|
-
success: false,
|
|
305
|
-
message: 'Publish cancelled. Run library_publish() to start over.',
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
// Fallback: confirmation code
|
|
309
|
-
// Store the code in wizard state for validation
|
|
310
|
-
wizardState.confirm_code = confirmCode;
|
|
311
270
|
return {
|
|
312
271
|
success: true,
|
|
313
272
|
step: 'confirm',
|
|
314
|
-
message: `📋 PUBLISH SUMMARY\n\n Name: ${wizardState.name}\n Entries: ${wizardState.selectedEntries.length}\n Pricing: ${priceDisplay}${wizardState.description ? `\n Description: ${wizardState.description}` : ''}${wizardState.tags?.length ? `\n Tags: ${wizardState.tags.join(', ')}` : ''}
|
|
315
|
-
ask_user:
|
|
273
|
+
message: `📋 PUBLISH SUMMARY\n\n Name: ${wizardState.name}\n Entries: ${wizardState.selectedEntries.length}\n Pricing: ${priceDisplay}${wizardState.description ? `\n Description: ${wizardState.description}` : ''}${wizardState.tags?.length ? `\n Tags: ${wizardState.tags.join(', ')}` : ''}`,
|
|
274
|
+
ask_user: 'Show this summary to the user. Ask: "Publish this? (yes/no)"',
|
|
316
275
|
};
|
|
317
276
|
}
|
|
318
277
|
// ======================================================================
|
|
319
|
-
// STEP 5:
|
|
278
|
+
// STEP 5: User confirms → Publish
|
|
320
279
|
// ======================================================================
|
|
321
280
|
if (wizardState.step === 'confirm') {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
return {
|
|
325
|
-
success: false,
|
|
326
|
-
step: 'confirm',
|
|
327
|
-
message: 'Confirmation code required. The user must type the code shown in the summary.',
|
|
328
|
-
ask_user: 'Please type the confirmation code to publish.',
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
if (input.confirm_code.toUpperCase() !== stored?.toUpperCase()) {
|
|
281
|
+
if (!input.confirm) {
|
|
282
|
+
wizardState = null;
|
|
332
283
|
return {
|
|
333
284
|
success: false,
|
|
334
|
-
|
|
335
|
-
message: `Wrong code. Expected: ${stored}. Got: ${input.confirm_code}. Try again or run library_publish() to start over.`,
|
|
285
|
+
message: 'Publish cancelled. Run library_publish() to start over.',
|
|
336
286
|
};
|
|
337
287
|
}
|
|
338
288
|
return await executePublish(wizardState);
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
2
|
// Marketplace Unpublish Tool
|
|
3
|
-
// Two-step: preview →
|
|
3
|
+
// Two-step: preview → user confirms → delete
|
|
4
4
|
// ============================================================================
|
|
5
|
-
import * as crypto from 'crypto';
|
|
6
|
-
import { execSync } from 'child_process';
|
|
7
|
-
import { platform } from 'os';
|
|
8
5
|
import { loadApiKey } from './auth.js';
|
|
9
6
|
const TELVOK_API_URL = process.env.TELVOK_API_URL || 'https://telvok.com';
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Pending State — preview stores slug, expires 5 min
|
|
9
|
+
// ============================================================================
|
|
10
|
+
let pendingSlug = null;
|
|
11
|
+
let pendingCreated = 0;
|
|
10
12
|
const EXPIRY_MS = 5 * 60 * 1000;
|
|
11
|
-
let pending = null;
|
|
12
13
|
function clearExpired() {
|
|
13
|
-
if (
|
|
14
|
-
|
|
14
|
+
if (pendingSlug && Date.now() - pendingCreated > EXPIRY_MS) {
|
|
15
|
+
pendingSlug = null;
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
// ============================================================================
|
|
@@ -25,19 +26,16 @@ export const libraryUnpublishTool = {
|
|
|
25
26
|
TWO-STEP FLOW:
|
|
26
27
|
|
|
27
28
|
Step 1: Call with just slug. Shows book details and what will be deleted.
|
|
28
|
-
|
|
29
|
-
On other platforms, a confirmation code is returned that the user must type back.
|
|
29
|
+
Step 2: Call again with slug + confirm: true ONLY after the user says yes.
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
DO NOT set confirm: true without the user explicitly saying yes.
|
|
32
|
+
Show the preview and ask "Delete this book? (yes/no)" first.
|
|
32
33
|
|
|
33
34
|
RESTRICTIONS:
|
|
34
35
|
- Cannot unpublish books with active purchases
|
|
35
36
|
- Deletion is PERMANENT — all entries are removed from marketplace
|
|
36
37
|
|
|
37
|
-
Use my_books() first to see your published books and their slugs
|
|
38
|
-
|
|
39
|
-
DO NOT decide to unpublish without the user explicitly asking.
|
|
40
|
-
Show the preview details and wait for user confirmation.`,
|
|
38
|
+
Use my_books() first to see your published books and their slugs.`,
|
|
41
39
|
inputSchema: {
|
|
42
40
|
type: 'object',
|
|
43
41
|
properties: {
|
|
@@ -45,15 +43,15 @@ Show the preview details and wait for user confirmation.`,
|
|
|
45
43
|
type: 'string',
|
|
46
44
|
description: 'Book slug (from my_books output)',
|
|
47
45
|
},
|
|
48
|
-
|
|
49
|
-
type: '
|
|
50
|
-
description: '
|
|
46
|
+
confirm: {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
description: 'Set to true ONLY after showing preview to user and they say yes.',
|
|
51
49
|
},
|
|
52
50
|
},
|
|
53
51
|
required: ['slug'],
|
|
54
52
|
},
|
|
55
53
|
async handler(args) {
|
|
56
|
-
const { slug,
|
|
54
|
+
const { slug, confirm } = (args || {});
|
|
57
55
|
clearExpired();
|
|
58
56
|
if (!slug || typeof slug !== 'string') {
|
|
59
57
|
return { success: false, message: 'slug is required. Use my_books() to see your published books.' };
|
|
@@ -63,26 +61,20 @@ Show the preview details and wait for user confirmation.`,
|
|
|
63
61
|
return { success: false, message: 'Not authenticated. Run auth({ action: "login" }) first.' };
|
|
64
62
|
}
|
|
65
63
|
// ========================================================================
|
|
66
|
-
// CONFIRM STEP — user
|
|
64
|
+
// CONFIRM STEP — user said yes
|
|
67
65
|
// ========================================================================
|
|
68
|
-
if (
|
|
69
|
-
if (
|
|
66
|
+
if (confirm) {
|
|
67
|
+
if (pendingSlug !== slug) {
|
|
70
68
|
return {
|
|
71
69
|
success: false,
|
|
72
70
|
message: 'No pending unpublish for this slug. Call library_unpublish({ slug }) first to preview.',
|
|
73
71
|
};
|
|
74
72
|
}
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
success: false,
|
|
78
|
-
message: `Wrong code. Expected: ${pending.confirmCode}. Got: ${confirm_code}. Try again.`,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
pending = null;
|
|
73
|
+
pendingSlug = null;
|
|
82
74
|
return await executeUnpublish(slug, apiKey);
|
|
83
75
|
}
|
|
84
76
|
// ========================================================================
|
|
85
|
-
// PREVIEW STEP — fetch book details, show
|
|
77
|
+
// PREVIEW STEP — fetch book details, show to user
|
|
86
78
|
// ========================================================================
|
|
87
79
|
const res = await fetch(`${TELVOK_API_URL}/api/my-books`, {
|
|
88
80
|
headers: { 'Authorization': `Bearer ${apiKey}` },
|
|
@@ -101,38 +93,13 @@ Show the preview details and wait for user confirmation.`,
|
|
|
101
93
|
const bookName = book.name || slug;
|
|
102
94
|
const entriesCount = book.entries_count || book.entry_count || 0;
|
|
103
95
|
const pricing = book.pricing_type || book.pricing || 'unknown';
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
const dialogText = [
|
|
108
|
-
`Permanently unpublish "${bookName}"?`,
|
|
109
|
-
``,
|
|
110
|
-
`${entriesCount} entries — ${pricing}`,
|
|
111
|
-
``,
|
|
112
|
-
`This removes the book from the marketplace.`,
|
|
113
|
-
`This action cannot be undone.`,
|
|
114
|
-
].join('\\n');
|
|
115
|
-
const result = execSync(`osascript -e 'display dialog "${dialogText}" buttons {"Cancel", "Delete"} default button "Cancel" with title "Telvok Unpublish" with icon caution'`, { encoding: 'utf-8', timeout: 120000 });
|
|
116
|
-
if (result.includes('Delete')) {
|
|
117
|
-
return await executeUnpublish(slug, apiKey);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
// User clicked Cancel or osascript failed
|
|
122
|
-
return {
|
|
123
|
-
success: false,
|
|
124
|
-
message: 'Unpublish cancelled.',
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
// Fallback: confirmation code
|
|
129
|
-
const confirmCode = crypto.randomBytes(3).toString('hex').toUpperCase();
|
|
130
|
-
pending = { slug, bookName, entriesCount, pricing, confirmCode, created: Date.now() };
|
|
96
|
+
pendingSlug = slug;
|
|
97
|
+
pendingCreated = Date.now();
|
|
131
98
|
return {
|
|
132
99
|
success: true,
|
|
133
100
|
preview: true,
|
|
134
|
-
message: `⚠️ UNPUBLISH PREVIEW\n\n Book: ${bookName}\n Slug: ${slug}\n Entries: ${entriesCount}\n Pricing: ${pricing}\n\n This action is PERMANENT
|
|
135
|
-
ask_user:
|
|
101
|
+
message: `⚠️ UNPUBLISH PREVIEW\n\n Book: ${bookName}\n Slug: ${slug}\n Entries: ${entriesCount}\n Pricing: ${pricing}\n\n This action is PERMANENT.`,
|
|
102
|
+
ask_user: 'Show this to the user. Ask: "Delete this book from the marketplace? (yes/no)"',
|
|
136
103
|
book: { slug, name: bookName, entries_count: entriesCount, pricing },
|
|
137
104
|
};
|
|
138
105
|
},
|