@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,39 @@
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
+ description: string;
13
+ inputSchema: {
14
+ type: "object";
15
+ properties: {
16
+ slug: {
17
+ type: string;
18
+ description: string;
19
+ };
20
+ rating: {
21
+ type: string;
22
+ description: string;
23
+ minimum: number;
24
+ maximum: number;
25
+ };
26
+ title: {
27
+ type: string;
28
+ description: string;
29
+ };
30
+ comment: {
31
+ type: string;
32
+ description: string;
33
+ };
34
+ };
35
+ required: string[];
36
+ };
37
+ handler(args: unknown): Promise<RateBookResult>;
38
+ };
39
+ export default rateBookTool;
@@ -0,0 +1,139 @@
1
+ // ============================================================================
2
+ // Rate Book Tool
3
+ // Rate a book you've purchased on the Telvok marketplace
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
+ description: `Rate a book you've purchased from the Telvok marketplace.
13
+
14
+ Share your experience to help other agents find quality content.
15
+ Rating scale: 1 (poor) to 5 (excellent).
16
+
17
+ Requires authentication and purchase.
18
+
19
+ Examples:
20
+ - rate_book({ slug: "react-best-practices", rating: 5 })
21
+ - rate_book({ slug: "auth-patterns", rating: 4, title: "Very helpful", comment: "Saved hours on token refresh logic" })`,
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ slug: {
26
+ type: 'string',
27
+ description: 'Book slug to rate',
28
+ },
29
+ rating: {
30
+ type: 'number',
31
+ description: 'Rating from 1 to 5',
32
+ minimum: 1,
33
+ maximum: 5,
34
+ },
35
+ title: {
36
+ type: 'string',
37
+ description: 'Optional review title',
38
+ },
39
+ comment: {
40
+ type: 'string',
41
+ description: 'Optional review comment',
42
+ },
43
+ },
44
+ required: ['slug', 'rating'],
45
+ },
46
+ async handler(args) {
47
+ const { slug, rating, title, comment } = args;
48
+ // Validation
49
+ if (!slug || typeof slug !== 'string') {
50
+ return {
51
+ success: false,
52
+ message: 'Book slug is required',
53
+ };
54
+ }
55
+ if (typeof rating !== 'number' || rating < 1 || rating > 5) {
56
+ return {
57
+ success: false,
58
+ message: 'Rating must be a number between 1 and 5',
59
+ };
60
+ }
61
+ // Check authentication
62
+ const apiKey = await loadApiKey();
63
+ if (!apiKey) {
64
+ return {
65
+ success: false,
66
+ message: 'Not authenticated. Run auth({ action: "login" }) to connect your Telvok account first.',
67
+ };
68
+ }
69
+ try {
70
+ const response = await fetch(`${TELVOK_API_URL}/api/reviews`, {
71
+ method: 'POST',
72
+ headers: {
73
+ 'Authorization': `Bearer ${apiKey}`,
74
+ 'Content-Type': 'application/json',
75
+ },
76
+ body: JSON.stringify({
77
+ slug,
78
+ rating: Math.round(rating),
79
+ title: title?.trim() || undefined,
80
+ comment: comment?.trim() || undefined,
81
+ }),
82
+ });
83
+ const data = await response.json();
84
+ if (!response.ok) {
85
+ // Handle specific error cases with user-friendly messages
86
+ if (response.status === 404) {
87
+ return {
88
+ success: false,
89
+ message: `Book '${slug}' not found. Check the slug with my_books().`,
90
+ };
91
+ }
92
+ if (response.status === 400 && data.error?.includes('own')) {
93
+ return {
94
+ success: false,
95
+ message: 'Cannot review your own book.',
96
+ };
97
+ }
98
+ if (response.status === 409) {
99
+ return {
100
+ success: false,
101
+ message: 'You\'ve already reviewed this book.',
102
+ };
103
+ }
104
+ if (response.status === 403) {
105
+ return {
106
+ success: false,
107
+ message: `You haven't purchased '${slug}'. Buy it first with marketplace_buy().`,
108
+ };
109
+ }
110
+ return {
111
+ success: false,
112
+ message: data.error || `Rating failed: HTTP ${response.status}`,
113
+ };
114
+ }
115
+ const stars = '\u2605'.repeat(Math.round(rating)) + '\u2606'.repeat(5 - Math.round(rating));
116
+ const verifiedBadge = data.is_verified_purchase ? ' - Verified Purchase' : '';
117
+ return {
118
+ success: true,
119
+ message: `Rated '${slug}' ${stars} (${Math.round(rating)}/5)${verifiedBadge}`,
120
+ review: {
121
+ id: data.id,
122
+ rating: data.rating,
123
+ is_verified_purchase: data.is_verified_purchase,
124
+ },
125
+ };
126
+ }
127
+ catch (error) {
128
+ const message = error instanceof Error ? error.message : String(error);
129
+ return {
130
+ success: false,
131
+ message: `Rating failed: ${message}`,
132
+ };
133
+ }
134
+ },
135
+ };
136
+ // ============================================================================
137
+ // Export
138
+ // ============================================================================
139
+ export default rateBookTool;
@@ -0,0 +1,23 @@
1
+ export interface RebuildResult {
2
+ success: boolean;
3
+ indexed: number;
4
+ skipped: number;
5
+ errors: string[];
6
+ stats: {
7
+ entryCount: number;
8
+ chunkCount: number;
9
+ modelId: string;
10
+ rebuilt: string;
11
+ };
12
+ message: string;
13
+ }
14
+ export declare const rebuildIndexTool: {
15
+ name: string;
16
+ description: string;
17
+ inputSchema: {
18
+ type: "object";
19
+ properties: {};
20
+ required: never[];
21
+ };
22
+ handler(_args: unknown): Promise<RebuildResult>;
23
+ };
@@ -0,0 +1,107 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { glob } from 'glob';
4
+ import matter from 'gray-matter';
5
+ import { getLibraryPath, getLocalPath, getImportedPath, getPackagesPath } from '../library/storage.js';
6
+ import { saveIndex, addToIndex, getIndexStats } from '../library/vector-index.js';
7
+ // ============================================================================
8
+ // Tool Definition
9
+ // ============================================================================
10
+ export const rebuildIndexTool = {
11
+ name: 'rebuild_index',
12
+ description: `Rebuild the semantic search index for all library entries.
13
+
14
+ Use this when semantic search returns poor results, when user reports search seems broken, or after manually adding entries outside the normal workflow.
15
+
16
+ Use this after:
17
+ - Upgrading to v1.2.0 (existing entries need embeddings)
18
+ - Importing memories from other tools
19
+ - If semantic search seems broken
20
+
21
+ Reads all .md entries and generates embeddings. May take a minute on first run.`,
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {},
25
+ required: [],
26
+ },
27
+ async handler(_args) {
28
+ const libraryPath = getLibraryPath();
29
+ const localPath = getLocalPath(libraryPath);
30
+ const importedPath = getImportedPath(libraryPath);
31
+ const packagesPath = getPackagesPath(libraryPath);
32
+ // Create a fresh index
33
+ const index = {
34
+ version: 1,
35
+ rebuilt: '',
36
+ modelId: '',
37
+ entries: [],
38
+ };
39
+ let indexed = 0;
40
+ let skipped = 0;
41
+ const errors = [];
42
+ // Collect all .md files from all directories
43
+ const allDirs = [localPath, importedPath, packagesPath];
44
+ const allFiles = [];
45
+ for (const dir of allDirs) {
46
+ try {
47
+ const files = await glob(path.join(dir, '**/*.md'), { nodir: true });
48
+ allFiles.push(...files);
49
+ }
50
+ catch {
51
+ // Directory doesn't exist, skip
52
+ }
53
+ }
54
+ // Process each file
55
+ for (const filePath of allFiles) {
56
+ try {
57
+ const content = await fs.readFile(filePath, 'utf-8');
58
+ const { data, content: body } = matter(content);
59
+ // Skip empty files
60
+ if (!body.trim()) {
61
+ skipped++;
62
+ continue;
63
+ }
64
+ // Extract title
65
+ let title = data.title;
66
+ if (!title) {
67
+ const headingMatch = body.match(/^#\s+(.+)$/m);
68
+ if (headingMatch) {
69
+ title = headingMatch[1].trim();
70
+ }
71
+ else {
72
+ title = path.basename(filePath, '.md').replace(/-/g, ' ');
73
+ }
74
+ }
75
+ // Build full content for embedding
76
+ const fullContent = [
77
+ title,
78
+ data.intent || '',
79
+ body.trim(),
80
+ data.context || '',
81
+ ].filter(Boolean).join('\n\n');
82
+ const relativePath = path.relative(libraryPath, filePath);
83
+ await addToIndex(index, relativePath, title, fullContent);
84
+ indexed++;
85
+ }
86
+ catch (error) {
87
+ const relativePath = path.relative(libraryPath, filePath);
88
+ errors.push(`${relativePath}: ${error instanceof Error ? error.message : String(error)}`);
89
+ skipped++;
90
+ }
91
+ }
92
+ // Save the index
93
+ await saveIndex(index);
94
+ const stats = getIndexStats(index);
95
+ const message = indexed > 0
96
+ ? `Rebuilt index with ${indexed} entries (${stats.chunkCount} chunks). ${skipped} skipped.`
97
+ : 'No entries found to index.';
98
+ return {
99
+ success: indexed > 0 || allFiles.length === 0,
100
+ indexed,
101
+ skipped,
102
+ errors,
103
+ stats,
104
+ message,
105
+ };
106
+ },
107
+ };
@@ -0,0 +1,40 @@
1
+ export interface RecordResult {
2
+ success: boolean;
3
+ path: string;
4
+ title: string;
5
+ }
6
+ export declare const recordTool: {
7
+ name: string;
8
+ description: string;
9
+ inputSchema: {
10
+ type: "object";
11
+ properties: {
12
+ insight: {
13
+ type: string;
14
+ description: string;
15
+ };
16
+ intent: {
17
+ type: string;
18
+ description: string;
19
+ };
20
+ reasoning: {
21
+ type: string;
22
+ description: string;
23
+ };
24
+ context: {
25
+ type: string;
26
+ description: string;
27
+ };
28
+ example: {
29
+ type: string;
30
+ description: string;
31
+ };
32
+ title: {
33
+ type: string;
34
+ description: string;
35
+ };
36
+ };
37
+ required: string[];
38
+ };
39
+ handler(args: unknown): Promise<RecordResult>;
40
+ };
@@ -0,0 +1,205 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { getLibraryPath, getLocalPath } from '../library/storage.js';
4
+ import { loadIndex, saveIndex, addToIndex } from '../library/vector-index.js';
5
+ // ============================================================================
6
+ // Tool Definition
7
+ // ============================================================================
8
+ export const recordTool = {
9
+ name: 'record',
10
+ description: `Use this tool proactively throughout every conversation.
11
+
12
+ CALL THIS IMMEDIATELY when you:
13
+ - Solve a problem (before moving to the next thing)
14
+ - Make a decision (capture the reasoning NOW)
15
+ - Hit a gotcha (it WILL come up again)
16
+ - Learn something non-obvious (context dies fast)
17
+
18
+ Do NOT wait for the user to ask. Do NOT batch multiple insights.
19
+ One insight = one record() call. Call it the moment you notice it.
20
+
21
+ Quality bar: "I wish we knew this yesterday"
22
+
23
+ Good entries:
24
+ - "Stripe retries webhooks but doesn't dedupe - always check idempotency key"
25
+ - "Clock skew between services - add 30s buffer to token validation"
26
+ - "GitHub org names are squattable even if you own the domain"
27
+
28
+ Not worth recording:
29
+ - Generic docs (search exists)
30
+ - Temporary hacks
31
+ - Stuff that'll change next week
32
+
33
+ Examples:
34
+
35
+ Quick:
36
+ - record({ insight: "Stripe webhooks need idempotency checks" })
37
+
38
+ Rich:
39
+ - record({
40
+ intent: "Setting up GitHub org for Telvok",
41
+ insight: "GitHub org names are first-come-first-served regardless of domain ownership",
42
+ context: "GitHub, npm, branding",
43
+ reasoning: "We owned telvok.com but someone squatted telvok org years ago",
44
+ example: "Had to use telvokdev instead of telvok"
45
+ })`,
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ insight: {
50
+ type: 'string',
51
+ description: 'What did we learn? The knowledge worth keeping.',
52
+ },
53
+ intent: {
54
+ type: 'string',
55
+ description: 'What were we trying to accomplish?',
56
+ },
57
+ reasoning: {
58
+ type: 'string',
59
+ description: 'Why does this work? Why this over alternatives?',
60
+ },
61
+ context: {
62
+ type: 'string',
63
+ description: "Topic, area, or when this applies (e.g., 'auth', 'payments', 'only on Windows')",
64
+ },
65
+ example: {
66
+ type: 'string',
67
+ description: 'Code snippet or concrete illustration',
68
+ },
69
+ title: {
70
+ type: 'string',
71
+ description: 'Entry title. Auto-generated from insight if not provided.',
72
+ },
73
+ },
74
+ required: ['insight'],
75
+ },
76
+ async handler(args) {
77
+ const { insight, intent, reasoning, context, example, title: providedTitle } = args;
78
+ if (!insight) {
79
+ throw new Error('insight is required');
80
+ }
81
+ const libraryPath = getLibraryPath();
82
+ const localPath = getLocalPath(libraryPath);
83
+ // Ensure local directory exists
84
+ await fs.mkdir(localPath, { recursive: true });
85
+ // Generate title
86
+ const title = providedTitle || generateTitle(insight, intent);
87
+ // Generate slug for filename
88
+ const slug = slugify(title);
89
+ const created = new Date().toISOString();
90
+ // Handle filename collisions
91
+ let filename = `${slug}.md`;
92
+ let filePath = path.join(localPath, filename);
93
+ let counter = 1;
94
+ while (await fileExists(filePath)) {
95
+ filename = `${slug}-${counter}.md`;
96
+ filePath = path.join(localPath, filename);
97
+ counter++;
98
+ }
99
+ // Build frontmatter
100
+ const frontmatterLines = ['---'];
101
+ if (intent) {
102
+ frontmatterLines.push(`intent: "${escapeYaml(intent)}"`);
103
+ }
104
+ if (context) {
105
+ frontmatterLines.push(`context: "${escapeYaml(context)}"`);
106
+ }
107
+ frontmatterLines.push(`created: "${created}"`);
108
+ frontmatterLines.push(`updated: "${created}"`);
109
+ frontmatterLines.push('source: "local"');
110
+ frontmatterLines.push('hits: 0');
111
+ frontmatterLines.push('last_hit: null');
112
+ frontmatterLines.push('---');
113
+ // Build body
114
+ const bodyLines = [];
115
+ bodyLines.push(`# ${title}`);
116
+ bodyLines.push('');
117
+ bodyLines.push(insight);
118
+ if (reasoning) {
119
+ bodyLines.push('');
120
+ bodyLines.push('## Reasoning');
121
+ bodyLines.push('');
122
+ bodyLines.push(reasoning);
123
+ }
124
+ if (example) {
125
+ bodyLines.push('');
126
+ bodyLines.push('## Example');
127
+ bodyLines.push('');
128
+ // Detect if it looks like code
129
+ if (example.includes('\n') || example.includes('{') || example.includes('(')) {
130
+ bodyLines.push('```');
131
+ bodyLines.push(example);
132
+ bodyLines.push('```');
133
+ }
134
+ else {
135
+ bodyLines.push('```');
136
+ bodyLines.push(example);
137
+ bodyLines.push('```');
138
+ }
139
+ }
140
+ // Combine and write
141
+ const fileContent = frontmatterLines.join('\n') + '\n\n' + bodyLines.join('\n') + '\n';
142
+ await fs.writeFile(filePath, fileContent, 'utf-8');
143
+ const relativePath = path.relative(libraryPath, filePath);
144
+ // Add to vector index for semantic search
145
+ try {
146
+ const index = await loadIndex();
147
+ // Combine all text for embedding
148
+ const fullContent = [
149
+ title,
150
+ intent || '',
151
+ insight,
152
+ reasoning || '',
153
+ example || '',
154
+ context || '',
155
+ ].filter(Boolean).join('\n\n');
156
+ await addToIndex(index, relativePath, title, fullContent);
157
+ await saveIndex(index);
158
+ }
159
+ catch (embeddingError) {
160
+ // Don't fail the record operation if embedding fails
161
+ // Entry is still saved and searchable via keywords
162
+ console.error('Failed to add embedding:', embeddingError);
163
+ }
164
+ return {
165
+ success: true,
166
+ path: relativePath,
167
+ title,
168
+ };
169
+ },
170
+ };
171
+ // ============================================================================
172
+ // Helper Functions
173
+ // ============================================================================
174
+ function generateTitle(insight, intent) {
175
+ // Try to extract from first sentence of insight
176
+ const firstSentence = insight.split(/[.!?\n]/)[0].trim();
177
+ if (firstSentence.length <= 60) {
178
+ return firstSentence;
179
+ }
180
+ // If insight is too long, try intent
181
+ if (intent && intent.length <= 60) {
182
+ return intent;
183
+ }
184
+ // Truncate insight
185
+ return firstSentence.slice(0, 57) + '...';
186
+ }
187
+ function slugify(text) {
188
+ return text
189
+ .toLowerCase()
190
+ .replace(/[^a-z0-9]+/g, '-')
191
+ .replace(/^-+|-+$/g, '')
192
+ .slice(0, 50);
193
+ }
194
+ function escapeYaml(text) {
195
+ return text.replace(/"/g, '\\"').replace(/\n/g, ' ');
196
+ }
197
+ async function fileExists(filePath) {
198
+ try {
199
+ await fs.access(filePath);
200
+ return true;
201
+ }
202
+ catch {
203
+ return false;
204
+ }
205
+ }
@@ -0,0 +1,35 @@
1
+ interface SellerAnalyticsResult {
2
+ success: boolean;
3
+ message: string;
4
+ overview?: {
5
+ revenue: string;
6
+ downloads: number;
7
+ hits: number;
8
+ avgRating: string;
9
+ libraryCount: number;
10
+ };
11
+ books?: Array<{
12
+ name: string;
13
+ revenue: string;
14
+ downloads: number;
15
+ hits: number;
16
+ rating: string;
17
+ }>;
18
+ recent_reviews?: Array<{
19
+ book: string;
20
+ rating: string;
21
+ title: string;
22
+ reviewer: string;
23
+ }>;
24
+ }
25
+ export declare const sellerAnalyticsTool: {
26
+ name: string;
27
+ description: string;
28
+ inputSchema: {
29
+ type: "object";
30
+ properties: {};
31
+ required: never[];
32
+ };
33
+ handler(): Promise<SellerAnalyticsResult>;
34
+ };
35
+ export default sellerAnalyticsTool;
@@ -0,0 +1,102 @@
1
+ // ============================================================================
2
+ // Seller Analytics Tool
3
+ // View your seller analytics - revenue, downloads, hits, ratings
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 sellerAnalyticsTool = {
11
+ name: 'seller_analytics',
12
+ description: `View your seller analytics - revenue, downloads, hits, and ratings for your published books.
13
+
14
+ Shows:
15
+ - Overview: Total revenue (after 15% platform fee), downloads, hits, average rating
16
+ - Per-book breakdown: Revenue, downloads, hits, rating for each book
17
+ - Recent reviews: Latest reviews on your books
18
+
19
+ Requires authentication. Run auth({ action: "login" }) first if not connected.
20
+
21
+ Examples:
22
+ - seller_analytics() - View all your analytics`,
23
+ inputSchema: {
24
+ type: 'object',
25
+ properties: {},
26
+ required: [],
27
+ },
28
+ async handler() {
29
+ // Check authentication
30
+ const apiKey = await loadApiKey();
31
+ if (!apiKey) {
32
+ return {
33
+ success: false,
34
+ message: 'Not authenticated. Run auth({ action: "login" }) to connect your Telvok account first.',
35
+ };
36
+ }
37
+ try {
38
+ const response = await fetch(`${TELVOK_API_URL}/api/seller/analytics`, {
39
+ method: 'GET',
40
+ headers: {
41
+ 'Authorization': `Bearer ${apiKey}`,
42
+ },
43
+ });
44
+ const data = await response.json();
45
+ if (!response.ok) {
46
+ return {
47
+ success: false,
48
+ message: data.error || `Failed to fetch analytics: HTTP ${response.status}`,
49
+ };
50
+ }
51
+ // Check if user has any libraries
52
+ if (data.overview.libraryCount === 0) {
53
+ return {
54
+ success: true,
55
+ message: 'You haven\'t published any books yet. Use marketplace_publish() to publish your first book.',
56
+ overview: {
57
+ revenue: '$0.00',
58
+ downloads: 0,
59
+ hits: 0,
60
+ avgRating: 'N/A',
61
+ libraryCount: 0,
62
+ },
63
+ books: [],
64
+ recent_reviews: [],
65
+ };
66
+ }
67
+ // Format for terminal display
68
+ return {
69
+ success: true,
70
+ message: `Analytics for ${data.overview.libraryCount} book${data.overview.libraryCount === 1 ? '' : 's'}`,
71
+ overview: {
72
+ revenue: `$${data.overview.revenue.toFixed(2)}`,
73
+ downloads: data.overview.downloads,
74
+ hits: data.overview.hits,
75
+ avgRating: data.overview.avgRating ? `${data.overview.avgRating}/5` : 'No ratings yet',
76
+ libraryCount: data.overview.libraryCount,
77
+ },
78
+ books: data.byLibrary.map(b => ({
79
+ name: b.name,
80
+ revenue: `$${b.revenue.toFixed(2)}`,
81
+ downloads: b.downloads,
82
+ hits: b.hits,
83
+ rating: b.rating ? `${b.rating}/5` : 'No ratings',
84
+ })),
85
+ recent_reviews: data.recentReviews.slice(0, 5).map(r => ({
86
+ book: r.library.name,
87
+ rating: `${r.rating}/5`,
88
+ title: r.title || '(no title)',
89
+ reviewer: r.reviewer?.name || 'Anonymous',
90
+ })),
91
+ };
92
+ }
93
+ catch (error) {
94
+ const message = error instanceof Error ? error.message : String(error);
95
+ throw new Error(`Failed to fetch analytics: ${message}`);
96
+ }
97
+ },
98
+ };
99
+ // ============================================================================
100
+ // Export
101
+ // ============================================================================
102
+ export default sellerAnalyticsTool;