@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.
@@ -49,7 +49,7 @@ export declare const libraryPublishTool: {
49
49
  type: string;
50
50
  enum: string[];
51
51
  };
52
- confirm_code: {
52
+ confirm: {
53
53
  type: string;
54
54
  description: string;
55
55
  };
@@ -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
- confirm_code: { type: 'string', description: 'Confirmation code from the final summary. User must type this back.' },
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(', ')}` : ''}\n\n🔑 Confirmation code: ${confirmCode}`,
315
- ask_user: `To publish, the user must type back the code: ${confirmCode}`,
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: Confirm with code → Publish
278
+ // STEP 5: User confirms → Publish
320
279
  // ======================================================================
321
280
  if (wizardState.step === 'confirm') {
322
- const stored = wizardState.confirm_code;
323
- if (!input.confirm_code) {
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
- step: 'confirm',
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);
@@ -9,7 +9,7 @@ export declare const libraryUnpublishTool: {
9
9
  type: string;
10
10
  description: string;
11
11
  };
12
- confirm_code: {
12
+ confirm: {
13
13
  type: string;
14
14
  description: string;
15
15
  };
@@ -1,17 +1,18 @@
1
1
  // ============================================================================
2
2
  // Marketplace Unpublish Tool
3
- // Two-step: preview → osascript dialog / confirmation code → delete
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 (pending && Date.now() - pending.created > EXPIRY_MS) {
14
- pending = null;
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
- On macOS, a native confirmation dialog appears the agent CANNOT bypass it.
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
- Step 2 (non-macOS only): Call again with slug + confirm_code from the user.
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
- confirm_code: {
49
- type: 'string',
50
- description: 'Confirmation code from preview. User must type this back. Only needed on non-macOS.',
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, confirm_code } = (args || {});
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 typed back the code
64
+ // CONFIRM STEP — user said yes
67
65
  // ========================================================================
68
- if (confirm_code) {
69
- if (!pending || pending.slug !== slug) {
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
- if (confirm_code.toUpperCase() !== pending.confirmCode.toUpperCase()) {
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 confirmation
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
- // Try native dialog on macOS
105
- if (platform() === 'darwin') {
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.\n\n🔑 Confirmation code: ${confirmCode}`,
135
- ask_user: `To delete this book from the marketplace, the user must type back the code: ${confirmCode}`,
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
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telvok/librarian-mcp",
3
- "version": "2.4.0",
3
+ "version": "2.4.1",
4
4
  "description": "Knowledge capture MCP server - remember what you learn with AI",
5
5
  "type": "module",
6
6
  "main": "dist/server.js",