@telvok/librarian-mcp 1.5.4 → 2.3.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/sensitive-scanner.d.ts +20 -0
- package/dist/library/sensitive-scanner.js +56 -0
- 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 +104 -54
- package/dist/tools/adopt.d.ts +1 -0
- package/dist/tools/adopt.js +37 -10
- package/dist/tools/audit.d.ts +27 -0
- package/dist/tools/audit.js +126 -0
- 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 +117 -0
- package/dist/tools/library-publish.js +447 -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,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;
|