@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
@@ -2,19 +2,27 @@ import * as fs from 'fs/promises';
2
2
  import * as path from 'path';
3
3
  import matter from 'gray-matter';
4
4
  import { getLibraryPath } from '../library/storage.js';
5
+ import { loadApiKey } from './auth.js';
6
+ const TELVOK_API_URL = process.env.TELVOK_API_URL || 'https://telvok.com';
5
7
  // ============================================================================
6
8
  // Tool Definition
7
9
  // ============================================================================
8
10
  export const markHitTool = {
9
11
  name: 'mark_hit',
10
- description: `Mark a library entry as helpful - call this when knowledge from the library helped solve a problem.
12
+ title: 'Mark Entry as Helpful',
13
+ description: `CALL THIS when an entry from brief() actually helped. Fire and forget.
11
14
 
12
- When an entry from brief() actually helped you complete a task or make a decision,
13
- call mark_hit() on it. This helps the library learn which entries are most useful.
15
+ USE THIS TOOL WHEN:
16
+ - A brief() result helped you solve the problem
17
+ - You used information from an entry to make a decision
18
+ - An entry saved you from making a mistake
14
19
 
15
- Entries with more hits bubble up in future brief() results.
20
+ DO NOT SKIP - this is how the library learns what's useful.
21
+ Entries with more hits rank higher in future searches.
16
22
 
17
- Fire and forget - call it and move on.
23
+ TRIGGER PATTERNS:
24
+ - Used entry from brief() → mark_hit({ path: "<path from brief result>" })
25
+ - Entry prevented a mistake → mark_hit({ path: "..." })
18
26
 
19
27
  Example:
20
28
  - mark_hit({ path: "local/stripe-webhooks-need-idempotency.md" })`,
@@ -33,6 +41,48 @@ Example:
33
41
  if (!entryPath) {
34
42
  throw new Error('path is required');
35
43
  }
44
+ // Handle cloud entries (from purchased books via brief())
45
+ // These have paths like "cloud:book-slug"
46
+ if (entryPath.startsWith('cloud:')) {
47
+ const bookSlug = entryPath.slice(6); // Remove "cloud:" prefix
48
+ if (!bookSlug) {
49
+ throw new Error('Invalid cloud entry path');
50
+ }
51
+ // Get API key for authenticated request
52
+ const apiKey = await loadApiKey();
53
+ if (!apiKey) {
54
+ // Can't track hit without auth, but don't fail - fire and forget
55
+ return {
56
+ success: true,
57
+ path: entryPath,
58
+ hits: 1, // We don't know actual count for cloud entries
59
+ };
60
+ }
61
+ // POST hit to server with auth
62
+ try {
63
+ await fetch(`${TELVOK_API_URL}/api/hits`, {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Content-Type': 'application/json',
67
+ 'Authorization': `Bearer ${apiKey}`,
68
+ },
69
+ body: JSON.stringify({
70
+ hits: [{
71
+ slug: bookSlug,
72
+ timestamp: new Date().toISOString(),
73
+ }],
74
+ }),
75
+ });
76
+ }
77
+ catch {
78
+ // Silently ignore server errors - fire and forget
79
+ }
80
+ return {
81
+ success: true,
82
+ path: entryPath,
83
+ hits: 1, // We don't track local count for cloud entries
84
+ };
85
+ }
36
86
  const libraryPath = getLibraryPath();
37
87
  // Resolve the full path
38
88
  let fullPath;
@@ -62,6 +112,34 @@ Example:
62
112
  const updatedContent = matter.stringify(body, data);
63
113
  // Write back
64
114
  await fs.writeFile(fullPath, updatedContent, 'utf-8');
115
+ // Sync to server if this is an imported/packages entry
116
+ const normalizedPath = entryPath.replace(/\\/g, '/');
117
+ if (normalizedPath.startsWith('packages/') || normalizedPath.includes('/packages/')) {
118
+ try {
119
+ // Extract library slug from path: packages/{slug}/entry.md
120
+ const parts = normalizedPath.split('/');
121
+ const packagesIndex = parts.indexOf('packages');
122
+ if (packagesIndex !== -1 && parts.length > packagesIndex + 1) {
123
+ const librarySlug = parts[packagesIndex + 1];
124
+ // POST to server - fire and forget
125
+ fetch(`${TELVOK_API_URL}/api/hits`, {
126
+ method: 'POST',
127
+ headers: { 'Content-Type': 'application/json' },
128
+ body: JSON.stringify({
129
+ hits: [{
130
+ slug: librarySlug,
131
+ timestamp: new Date().toISOString(),
132
+ }],
133
+ }),
134
+ }).catch(() => {
135
+ // Silently ignore server sync errors - local update already succeeded
136
+ });
137
+ }
138
+ }
139
+ catch {
140
+ // Ignore errors - local update already succeeded
141
+ }
142
+ }
65
143
  return {
66
144
  success: true,
67
145
  path: entryPath,
@@ -0,0 +1,51 @@
1
+ interface PublishedBook {
2
+ slug: string;
3
+ name: string;
4
+ entries_count: number;
5
+ pricing: {
6
+ type: string;
7
+ display: string;
8
+ };
9
+ created_at: string;
10
+ url: string;
11
+ }
12
+ interface PurchasedBook {
13
+ slug: string;
14
+ name: string;
15
+ author: string;
16
+ entries_count: number;
17
+ pricing: {
18
+ type: string;
19
+ display: string;
20
+ };
21
+ purchased_at: string;
22
+ status: string;
23
+ }
24
+ interface MyBooksResult {
25
+ success: boolean;
26
+ message: string;
27
+ published?: PublishedBook[];
28
+ purchased?: PurchasedBook[];
29
+ summary?: {
30
+ published_count: number;
31
+ purchased_count: number;
32
+ };
33
+ }
34
+ export declare const myBooksTool: {
35
+ name: string;
36
+ title: string;
37
+ description: string;
38
+ inputSchema: {
39
+ type: "object";
40
+ properties: {
41
+ filter: {
42
+ type: string;
43
+ enum: string[];
44
+ description: string;
45
+ };
46
+ };
47
+ required: never[];
48
+ };
49
+ handler(args: unknown): Promise<MyBooksResult>;
50
+ };
51
+ export default myBooksTool;
@@ -0,0 +1,115 @@
1
+ // ============================================================================
2
+ // My Books Tool
3
+ // View published and purchased books from Telvok library
4
+ // ============================================================================
5
+ import { loadApiKey } from './auth.js';
6
+ const TELVOK_API_URL = process.env.TELVOK_API_URL || 'https://telvok.com';
7
+ // ============================================================================
8
+ // Tool Definition
9
+ // ============================================================================
10
+ export const myBooksTool = {
11
+ name: 'my_books',
12
+ title: 'View My Books',
13
+ description: `View your published and purchased books.
14
+
15
+ USE THIS TOOL WHEN:
16
+ - User asks "what books do I have" or "show my library"
17
+ - Need to find a book slug for sync(), rate_book(), or other operations
18
+ - Checking what content user owns
19
+
20
+ Shows published (created) and purchased (bought/claimed) books.
21
+
22
+ TRIGGER PATTERNS:
23
+ - "Show my books" → my_books()
24
+ - "What have I published?" → my_books({ filter: "published" })
25
+ - "What books did I buy?" → my_books({ filter: "purchased" })
26
+ - Need book slugs → my_books()
27
+
28
+ Examples:
29
+ - my_books() - Show all your books
30
+ - my_books({ filter: "published" }) - Only your published books
31
+ - my_books({ filter: "purchased" }) - Only books you've bought`,
32
+ inputSchema: {
33
+ type: 'object',
34
+ properties: {
35
+ filter: {
36
+ type: 'string',
37
+ enum: ['all', 'published', 'purchased'],
38
+ description: 'Filter results (default: all)',
39
+ },
40
+ },
41
+ required: [],
42
+ },
43
+ async handler(args) {
44
+ const { filter = 'all' } = (args || {});
45
+ // Validate filter
46
+ if (filter && !['all', 'published', 'purchased'].includes(filter)) {
47
+ return {
48
+ success: false,
49
+ message: 'Invalid filter. Must be: all, published, or purchased',
50
+ };
51
+ }
52
+ // Check authentication
53
+ const apiKey = await loadApiKey();
54
+ if (!apiKey) {
55
+ return {
56
+ success: false,
57
+ message: 'Not authenticated. Run auth({ action: "login" }) to connect your Telvok account first.',
58
+ };
59
+ }
60
+ try {
61
+ const url = new URL(`${TELVOK_API_URL}/api/my-books`);
62
+ if (filter && filter !== 'all') {
63
+ url.searchParams.set('filter', filter);
64
+ }
65
+ const response = await fetch(url.toString(), {
66
+ method: 'GET',
67
+ headers: {
68
+ 'Authorization': `Bearer ${apiKey}`,
69
+ },
70
+ });
71
+ const data = await response.json();
72
+ if (!response.ok) {
73
+ return {
74
+ success: false,
75
+ message: data.error || `Failed to fetch books: HTTP ${response.status}`,
76
+ };
77
+ }
78
+ // Format output message
79
+ const publishedCount = data.published?.length || 0;
80
+ const purchasedCount = data.purchased?.length || 0;
81
+ let message = '';
82
+ if (filter === 'published') {
83
+ message = publishedCount === 0
84
+ ? 'You haven\'t published any books yet.'
85
+ : `You have ${publishedCount} published book${publishedCount === 1 ? '' : 's'}.`;
86
+ }
87
+ else if (filter === 'purchased') {
88
+ message = purchasedCount === 0
89
+ ? 'You haven\'t purchased any books yet.'
90
+ : `You have ${purchasedCount} purchased book${purchasedCount === 1 ? '' : 's'}.`;
91
+ }
92
+ else {
93
+ const total = publishedCount + purchasedCount;
94
+ message = total === 0
95
+ ? 'No books yet. Publish with library_publish() or browse with library_search().'
96
+ : `${publishedCount} published, ${purchasedCount} purchased.`;
97
+ }
98
+ return {
99
+ success: true,
100
+ message,
101
+ published: data.published,
102
+ purchased: data.purchased,
103
+ summary: data.summary,
104
+ };
105
+ }
106
+ catch (error) {
107
+ const message = error instanceof Error ? error.message : String(error);
108
+ throw new Error(`Failed to fetch books: ${message}`);
109
+ }
110
+ },
111
+ };
112
+ // ============================================================================
113
+ // Export
114
+ // ============================================================================
115
+ export default myBooksTool;
@@ -0,0 +1,43 @@
1
+ interface BountyItem {
2
+ id: string;
3
+ title: string;
4
+ description: string | null;
5
+ amount: string;
6
+ amount_cents: number;
7
+ status: string;
8
+ created_at: string;
9
+ expires_at: string;
10
+ claimed_at: string | null;
11
+ submitted_at: string | null;
12
+ completed_at: string | null;
13
+ }
14
+ interface MyBountiesResult {
15
+ success: boolean;
16
+ message: string;
17
+ created: BountyItem[];
18
+ claimed: BountyItem[];
19
+ summary?: {
20
+ total_created: number;
21
+ total_claimed: number;
22
+ pending_approval: number;
23
+ in_progress: number;
24
+ };
25
+ }
26
+ export declare const myBountiesTool: {
27
+ name: string;
28
+ title: string;
29
+ description: string;
30
+ inputSchema: {
31
+ type: "object";
32
+ properties: {
33
+ role: {
34
+ type: string;
35
+ enum: string[];
36
+ description: string;
37
+ };
38
+ };
39
+ required: never[];
40
+ };
41
+ handler(args: unknown): Promise<MyBountiesResult>;
42
+ };
43
+ export {};
@@ -0,0 +1,126 @@
1
+ // ============================================================================
2
+ // My Bounties Tool
3
+ // View your created and claimed bounties
4
+ // ============================================================================
5
+ import { loadApiKey } from './auth.js';
6
+ const TELVOK_API_URL = process.env.TELVOK_API_URL || 'https://telvok.com';
7
+ // ============================================================================
8
+ // Tool Definition
9
+ // ============================================================================
10
+ export const myBountiesTool = {
11
+ name: 'my_bounties',
12
+ title: 'View My Bounties',
13
+ description: `View your bounty activity and pending actions.
14
+
15
+ USE THIS TOOL WHEN:
16
+ - User asks "what bounties do I have"
17
+ - Checking status of created or claimed bounties
18
+ - User needs to see pending approvals or submissions
19
+
20
+ Shows bounties you created (as buyer) and claimed (as seller).
21
+
22
+ TRIGGER PATTERNS:
23
+ - "Show my bounties" → my_bounties()
24
+ - "What bounties am I working on?" → my_bounties({ role: "claimer" })
25
+ - "Check bounty status" → my_bounties()
26
+
27
+ Examples:
28
+ - my_bounties() - Show all your bounties
29
+ - my_bounties({ role: "creator" }) - Only bounties you created
30
+ - my_bounties({ role: "claimer" }) - Only bounties you claimed`,
31
+ inputSchema: {
32
+ type: 'object',
33
+ properties: {
34
+ role: {
35
+ type: 'string',
36
+ enum: ['creator', 'claimer', 'all'],
37
+ description: 'Filter by role (default: all)',
38
+ },
39
+ },
40
+ required: [],
41
+ },
42
+ async handler(args) {
43
+ const { role = 'all' } = (args || {});
44
+ // Check authentication
45
+ const apiKey = await loadApiKey();
46
+ if (!apiKey) {
47
+ return {
48
+ success: false,
49
+ message: 'Not authenticated. Run auth({ action: "login" }) to connect your Telvok account first.',
50
+ created: [],
51
+ claimed: [],
52
+ };
53
+ }
54
+ try {
55
+ const params = new URLSearchParams();
56
+ if (role !== 'all') {
57
+ params.set('role', role);
58
+ }
59
+ const response = await fetch(`${TELVOK_API_URL}/api/my-bounties?${params}`, {
60
+ method: 'GET',
61
+ headers: {
62
+ 'Authorization': `Bearer ${apiKey}`,
63
+ 'Content-Type': 'application/json',
64
+ },
65
+ });
66
+ const data = await response.json();
67
+ if (!response.ok) {
68
+ return {
69
+ success: false,
70
+ message: data.error || `Failed to fetch bounties: HTTP ${response.status}`,
71
+ created: [],
72
+ claimed: [],
73
+ };
74
+ }
75
+ const created = data.created || [];
76
+ const claimed = data.claimed || [];
77
+ const summary = data.summary;
78
+ // Build summary message
79
+ const parts = [];
80
+ if (created.length > 0) {
81
+ parts.push(`**Bounties You Created (${created.length}):**`);
82
+ created.forEach((b, i) => {
83
+ const statusEmoji = b.status === 'submitted' ? '⏳' : b.status === 'completed' ? '✅' : '📝';
84
+ parts.push(`${i + 1}. ${statusEmoji} ${b.title} (${b.amount}) - ${b.status}`);
85
+ });
86
+ }
87
+ if (claimed.length > 0) {
88
+ if (parts.length > 0)
89
+ parts.push('');
90
+ parts.push(`**Bounties You Claimed (${claimed.length}):**`);
91
+ claimed.forEach((b, i) => {
92
+ const statusEmoji = b.status === 'submitted' ? '⏳' : b.status === 'completed' ? '✅' : '🔨';
93
+ parts.push(`${i + 1}. ${statusEmoji} ${b.title} (${b.amount}) - ${b.status}`);
94
+ });
95
+ }
96
+ if (parts.length === 0) {
97
+ parts.push('No bounties found. Create one with bounty_create() or browse with bounty_list().');
98
+ }
99
+ // Add action hints
100
+ if (summary?.pending_approval > 0) {
101
+ parts.push('');
102
+ parts.push(`💡 You have ${summary.pending_approval} submission(s) awaiting your approval.`);
103
+ }
104
+ if (summary?.in_progress > 0) {
105
+ parts.push('');
106
+ parts.push(`💡 You have ${summary.in_progress} claimed bounty(s) to complete.`);
107
+ }
108
+ return {
109
+ success: true,
110
+ created,
111
+ claimed,
112
+ summary,
113
+ message: parts.join('\n'),
114
+ };
115
+ }
116
+ catch (error) {
117
+ const message = error instanceof Error ? error.message : String(error);
118
+ return {
119
+ success: false,
120
+ message: `Failed to fetch bounties: ${message}`,
121
+ created: [],
122
+ claimed: [],
123
+ };
124
+ }
125
+ },
126
+ };
@@ -0,0 +1,40 @@
1
+ interface RateBookResult {
2
+ success: boolean;
3
+ message: string;
4
+ review?: {
5
+ id: string;
6
+ rating: number;
7
+ is_verified_purchase: boolean;
8
+ };
9
+ }
10
+ export declare const rateBookTool: {
11
+ name: string;
12
+ title: string;
13
+ description: string;
14
+ inputSchema: {
15
+ type: "object";
16
+ properties: {
17
+ slug: {
18
+ type: string;
19
+ description: string;
20
+ };
21
+ rating: {
22
+ type: string;
23
+ description: string;
24
+ minimum: number;
25
+ maximum: number;
26
+ };
27
+ title: {
28
+ type: string;
29
+ description: string;
30
+ };
31
+ comment: {
32
+ type: string;
33
+ description: string;
34
+ };
35
+ };
36
+ required: string[];
37
+ };
38
+ handler(args: unknown): Promise<RateBookResult>;
39
+ };
40
+ export default rateBookTool;
@@ -0,0 +1,147 @@
1
+ // ============================================================================
2
+ // Rate Book Tool
3
+ // Rate a book you've purchased on the Telvok library
4
+ // ============================================================================
5
+ import { loadApiKey } from './auth.js';
6
+ const TELVOK_API_URL = process.env.TELVOK_API_URL || 'https://telvok.com';
7
+ // ============================================================================
8
+ // Tool Definition
9
+ // ============================================================================
10
+ export const rateBookTool = {
11
+ name: 'rate_book',
12
+ title: 'Rate a Book',
13
+ description: `Rate a purchased book to help others find quality content.
14
+
15
+ USE THIS TOOL WHEN:
16
+ - A purchased book was helpful (or not) - share the experience
17
+ - User says "rate that book" or "leave a review"
18
+ - After using a book's knowledge successfully
19
+
20
+ Rating scale: 1 (poor) to 5 (excellent). Requires purchase.
21
+
22
+ TRIGGER PATTERNS:
23
+ - Book helped solve problem → rate_book({ slug: "...", rating: 5 })
24
+ - "Rate that book 4 stars" → rate_book({ slug: "...", rating: 4 })
25
+ - "Leave a review" → rate_book({ slug: "...", rating: X, comment: "..." })
26
+
27
+ Examples:
28
+ - rate_book({ slug: "react-best-practices", rating: 5 })
29
+ - rate_book({ slug: "auth-patterns", rating: 4, title: "Very helpful", comment: "Saved hours on token refresh logic" })`,
30
+ inputSchema: {
31
+ type: 'object',
32
+ properties: {
33
+ slug: {
34
+ type: 'string',
35
+ description: 'Book slug to rate',
36
+ },
37
+ rating: {
38
+ type: 'number',
39
+ description: 'Rating from 1 to 5',
40
+ minimum: 1,
41
+ maximum: 5,
42
+ },
43
+ title: {
44
+ type: 'string',
45
+ description: 'Optional review title',
46
+ },
47
+ comment: {
48
+ type: 'string',
49
+ description: 'Optional review comment',
50
+ },
51
+ },
52
+ required: ['slug', 'rating'],
53
+ },
54
+ async handler(args) {
55
+ const { slug, rating, title, comment } = args;
56
+ // Validation
57
+ if (!slug || typeof slug !== 'string') {
58
+ return {
59
+ success: false,
60
+ message: 'Book slug is required',
61
+ };
62
+ }
63
+ if (typeof rating !== 'number' || rating < 1 || rating > 5) {
64
+ return {
65
+ success: false,
66
+ message: 'Rating must be a number between 1 and 5',
67
+ };
68
+ }
69
+ // Check authentication
70
+ const apiKey = await loadApiKey();
71
+ if (!apiKey) {
72
+ return {
73
+ success: false,
74
+ message: 'Not authenticated. Run auth({ action: "login" }) to connect your Telvok account first.',
75
+ };
76
+ }
77
+ try {
78
+ const response = await fetch(`${TELVOK_API_URL}/api/reviews`, {
79
+ method: 'POST',
80
+ headers: {
81
+ 'Authorization': `Bearer ${apiKey}`,
82
+ 'Content-Type': 'application/json',
83
+ },
84
+ body: JSON.stringify({
85
+ slug,
86
+ rating: Math.round(rating),
87
+ title: title?.trim() || undefined,
88
+ comment: comment?.trim() || undefined,
89
+ }),
90
+ });
91
+ const data = await response.json();
92
+ if (!response.ok) {
93
+ // Handle specific error cases with user-friendly messages
94
+ if (response.status === 404) {
95
+ return {
96
+ success: false,
97
+ message: `Book '${slug}' not found. Check the slug with my_books().`,
98
+ };
99
+ }
100
+ if (response.status === 400 && data.error?.includes('own')) {
101
+ return {
102
+ success: false,
103
+ message: 'Cannot review your own book.',
104
+ };
105
+ }
106
+ if (response.status === 409) {
107
+ return {
108
+ success: false,
109
+ message: 'You\'ve already reviewed this book.',
110
+ };
111
+ }
112
+ if (response.status === 403) {
113
+ return {
114
+ success: false,
115
+ message: `You haven't purchased '${slug}'. Buy it first with library_buy().`,
116
+ };
117
+ }
118
+ return {
119
+ success: false,
120
+ message: data.error || `Rating failed: HTTP ${response.status}`,
121
+ };
122
+ }
123
+ const stars = '\u2605'.repeat(Math.round(rating)) + '\u2606'.repeat(5 - Math.round(rating));
124
+ const verifiedBadge = data.is_verified_purchase ? ' - Verified Purchase' : '';
125
+ return {
126
+ success: true,
127
+ message: `Rated '${slug}' ${stars} (${Math.round(rating)}/5)${verifiedBadge}`,
128
+ review: {
129
+ id: data.id,
130
+ rating: data.rating,
131
+ is_verified_purchase: data.is_verified_purchase,
132
+ },
133
+ };
134
+ }
135
+ catch (error) {
136
+ const message = error instanceof Error ? error.message : String(error);
137
+ return {
138
+ success: false,
139
+ message: `Rating failed: ${message}`,
140
+ };
141
+ }
142
+ },
143
+ };
144
+ // ============================================================================
145
+ // Export
146
+ // ============================================================================
147
+ export default rateBookTool;
@@ -13,6 +13,7 @@ export interface RebuildResult {
13
13
  }
14
14
  export declare const rebuildIndexTool: {
15
15
  name: string;
16
+ title: string;
16
17
  description: string;
17
18
  inputSchema: {
18
19
  type: "object";