@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,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes following JSON-RPC 2.0 conventions
|
|
3
|
+
* -32xxx: Standard JSON-RPC errors
|
|
4
|
+
* -31xxx: Authentication errors
|
|
5
|
+
* -30xxx: Resource errors
|
|
6
|
+
* -29xxx: Network errors
|
|
7
|
+
*/
|
|
8
|
+
export declare const ErrorCodes: {
|
|
9
|
+
readonly AUTH_REQUIRED: -31001;
|
|
10
|
+
readonly AUTH_EXPIRED: -31002;
|
|
11
|
+
readonly AUTH_INVALID_SCOPE: -31003;
|
|
12
|
+
readonly AUTH_INVALID_KEY: -31004;
|
|
13
|
+
readonly ENTRY_NOT_FOUND: -30001;
|
|
14
|
+
readonly BOOK_NOT_FOUND: -30002;
|
|
15
|
+
readonly INDEX_STALE: -30003;
|
|
16
|
+
readonly FILE_NOT_FOUND: -30004;
|
|
17
|
+
readonly INVALID_PATH: -30005;
|
|
18
|
+
readonly INVALID_PARAMS: -32602;
|
|
19
|
+
readonly INVALID_REQUEST: -32600;
|
|
20
|
+
readonly API_UNAVAILABLE: -29001;
|
|
21
|
+
readonly NETWORK_TIMEOUT: -29002;
|
|
22
|
+
readonly API_ERROR: -29003;
|
|
23
|
+
};
|
|
24
|
+
export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
|
|
25
|
+
/**
|
|
26
|
+
* Custom error class for Librarian MCP
|
|
27
|
+
* Includes error code and retryable flag for AI agents
|
|
28
|
+
*/
|
|
29
|
+
export declare class LibrarianError extends Error {
|
|
30
|
+
readonly code: ErrorCode;
|
|
31
|
+
readonly retryable: boolean;
|
|
32
|
+
constructor(code: ErrorCode, message: string, retryable?: boolean);
|
|
33
|
+
/**
|
|
34
|
+
* Convert to JSON-RPC compatible error object
|
|
35
|
+
*/
|
|
36
|
+
toJSON(): {
|
|
37
|
+
error: string;
|
|
38
|
+
code: ErrorCode;
|
|
39
|
+
retryable: boolean;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export declare function authRequired(): LibrarianError;
|
|
43
|
+
export declare function authExpired(): LibrarianError;
|
|
44
|
+
export declare function entryNotFound(path: string): LibrarianError;
|
|
45
|
+
export declare function bookNotFound(slug: string): LibrarianError;
|
|
46
|
+
export declare function apiError(message: string): LibrarianError;
|
|
47
|
+
export declare function networkTimeout(): LibrarianError;
|
|
48
|
+
export declare function invalidParams(message: string): LibrarianError;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Error Code Definitions
|
|
3
|
+
// JSON-RPC compatible error codes for MCP
|
|
4
|
+
// ============================================================================
|
|
5
|
+
/**
|
|
6
|
+
* Error codes following JSON-RPC 2.0 conventions
|
|
7
|
+
* -32xxx: Standard JSON-RPC errors
|
|
8
|
+
* -31xxx: Authentication errors
|
|
9
|
+
* -30xxx: Resource errors
|
|
10
|
+
* -29xxx: Network errors
|
|
11
|
+
*/
|
|
12
|
+
export const ErrorCodes = {
|
|
13
|
+
// Auth errors (-31xxx)
|
|
14
|
+
AUTH_REQUIRED: -31001,
|
|
15
|
+
AUTH_EXPIRED: -31002,
|
|
16
|
+
AUTH_INVALID_SCOPE: -31003,
|
|
17
|
+
AUTH_INVALID_KEY: -31004,
|
|
18
|
+
// Resource errors (-30xxx)
|
|
19
|
+
ENTRY_NOT_FOUND: -30001,
|
|
20
|
+
BOOK_NOT_FOUND: -30002,
|
|
21
|
+
INDEX_STALE: -30003,
|
|
22
|
+
FILE_NOT_FOUND: -30004,
|
|
23
|
+
INVALID_PATH: -30005,
|
|
24
|
+
// Validation errors (-32xxx - JSON-RPC standard)
|
|
25
|
+
INVALID_PARAMS: -32602,
|
|
26
|
+
INVALID_REQUEST: -32600,
|
|
27
|
+
// Network errors (-29xxx)
|
|
28
|
+
API_UNAVAILABLE: -29001,
|
|
29
|
+
NETWORK_TIMEOUT: -29002,
|
|
30
|
+
API_ERROR: -29003,
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Custom error class for Librarian MCP
|
|
34
|
+
* Includes error code and retryable flag for AI agents
|
|
35
|
+
*/
|
|
36
|
+
export class LibrarianError extends Error {
|
|
37
|
+
code;
|
|
38
|
+
retryable;
|
|
39
|
+
constructor(code, message, retryable = false) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = 'LibrarianError';
|
|
42
|
+
this.code = code;
|
|
43
|
+
this.retryable = retryable;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Convert to JSON-RPC compatible error object
|
|
47
|
+
*/
|
|
48
|
+
toJSON() {
|
|
49
|
+
return {
|
|
50
|
+
error: this.message,
|
|
51
|
+
code: this.code,
|
|
52
|
+
retryable: this.retryable,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Helper Functions for Common Errors
|
|
58
|
+
// ============================================================================
|
|
59
|
+
export function authRequired() {
|
|
60
|
+
return new LibrarianError(ErrorCodes.AUTH_REQUIRED, 'Authentication required. Use auth({ action: "login" }) to connect.', false);
|
|
61
|
+
}
|
|
62
|
+
export function authExpired() {
|
|
63
|
+
return new LibrarianError(ErrorCodes.AUTH_EXPIRED, 'API key has expired. Use auth({ action: "login" }) to get a new key.', false);
|
|
64
|
+
}
|
|
65
|
+
export function entryNotFound(path) {
|
|
66
|
+
return new LibrarianError(ErrorCodes.ENTRY_NOT_FOUND, `Entry not found: ${path}`, false);
|
|
67
|
+
}
|
|
68
|
+
export function bookNotFound(slug) {
|
|
69
|
+
return new LibrarianError(ErrorCodes.BOOK_NOT_FOUND, `Book not found: ${slug}`, false);
|
|
70
|
+
}
|
|
71
|
+
export function apiError(message) {
|
|
72
|
+
return new LibrarianError(ErrorCodes.API_ERROR, message, true // Network errors are typically retryable
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
export function networkTimeout() {
|
|
76
|
+
return new LibrarianError(ErrorCodes.NETWORK_TIMEOUT, 'Request timed out. Please try again.', true);
|
|
77
|
+
}
|
|
78
|
+
export function invalidParams(message) {
|
|
79
|
+
return new LibrarianError(ErrorCodes.INVALID_PARAMS, message, false);
|
|
80
|
+
}
|
|
@@ -146,14 +146,14 @@ export declare const ImportedLibrarySchema: z.ZodObject<{
|
|
|
146
146
|
name: string;
|
|
147
147
|
entry_count: number;
|
|
148
148
|
purchased_at: string;
|
|
149
|
-
sync_preference?: "manual" | "auto" | "pinned" | undefined;
|
|
150
149
|
last_synced?: string | undefined;
|
|
150
|
+
sync_preference?: "pinned" | "manual" | "auto" | undefined;
|
|
151
151
|
}, {
|
|
152
152
|
name: string;
|
|
153
153
|
entry_count: number;
|
|
154
154
|
purchased_at: string;
|
|
155
|
-
sync_preference?: "manual" | "auto" | "pinned" | undefined;
|
|
156
155
|
last_synced?: string | undefined;
|
|
156
|
+
sync_preference?: "pinned" | "manual" | "auto" | undefined;
|
|
157
157
|
}>;
|
|
158
158
|
export type ImportedLibrary = z.infer<typeof ImportedLibrarySchema>;
|
|
159
159
|
export declare const StateResultSchema: z.ZodObject<{
|
|
@@ -192,14 +192,14 @@ export declare const StateResultSchema: z.ZodObject<{
|
|
|
192
192
|
name: string;
|
|
193
193
|
entry_count: number;
|
|
194
194
|
purchased_at: string;
|
|
195
|
-
sync_preference?: "manual" | "auto" | "pinned" | undefined;
|
|
196
195
|
last_synced?: string | undefined;
|
|
196
|
+
sync_preference?: "pinned" | "manual" | "auto" | undefined;
|
|
197
197
|
}, {
|
|
198
198
|
name: string;
|
|
199
199
|
entry_count: number;
|
|
200
200
|
purchased_at: string;
|
|
201
|
-
sync_preference?: "manual" | "auto" | "pinned" | undefined;
|
|
202
201
|
last_synced?: string | undefined;
|
|
202
|
+
sync_preference?: "pinned" | "manual" | "auto" | undefined;
|
|
203
203
|
}>, "many">;
|
|
204
204
|
}, "strip", z.ZodTypeAny, {
|
|
205
205
|
entries: {
|
|
@@ -215,8 +215,8 @@ export declare const StateResultSchema: z.ZodObject<{
|
|
|
215
215
|
name: string;
|
|
216
216
|
entry_count: number;
|
|
217
217
|
purchased_at: string;
|
|
218
|
-
sync_preference?: "manual" | "auto" | "pinned" | undefined;
|
|
219
218
|
last_synced?: string | undefined;
|
|
219
|
+
sync_preference?: "pinned" | "manual" | "auto" | undefined;
|
|
220
220
|
}[];
|
|
221
221
|
}, {
|
|
222
222
|
entries: {
|
|
@@ -232,8 +232,8 @@ export declare const StateResultSchema: z.ZodObject<{
|
|
|
232
232
|
name: string;
|
|
233
233
|
entry_count: number;
|
|
234
234
|
purchased_at: string;
|
|
235
|
-
sync_preference?: "manual" | "auto" | "pinned" | undefined;
|
|
236
235
|
last_synced?: string | undefined;
|
|
236
|
+
sync_preference?: "pinned" | "manual" | "auto" | undefined;
|
|
237
237
|
}[];
|
|
238
238
|
}>;
|
|
239
239
|
export type StateResult = z.infer<typeof StateResultSchema>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface SensitiveFinding {
|
|
2
|
+
entry: string;
|
|
3
|
+
file?: string;
|
|
4
|
+
matches: string[];
|
|
5
|
+
}
|
|
6
|
+
interface ScannableEntry {
|
|
7
|
+
title: string;
|
|
8
|
+
content: string;
|
|
9
|
+
intent?: string;
|
|
10
|
+
context?: string;
|
|
11
|
+
reasoning?: string;
|
|
12
|
+
example?: string;
|
|
13
|
+
originalPath?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Scan entries for sensitive data patterns.
|
|
17
|
+
* Returns findings grouped by entry.
|
|
18
|
+
*/
|
|
19
|
+
export declare function scanForSensitiveData(entries: ScannableEntry[]): SensitiveFinding[];
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Sensitive Data Scanner
|
|
3
|
+
// Shared module for scanning entries before they leave the user's machine.
|
|
4
|
+
// Used by: library_publish (mandatory), audit tool (on-demand)
|
|
5
|
+
// ============================================================================
|
|
6
|
+
const SENSITIVE_PATTERNS = [
|
|
7
|
+
// API keys and tokens
|
|
8
|
+
{ pattern: /sk_(live|test)_[a-zA-Z0-9]{10,}/g, label: 'Stripe secret key' },
|
|
9
|
+
{ pattern: /whsec_[a-zA-Z0-9]{10,}/g, label: 'Stripe webhook secret' },
|
|
10
|
+
{ pattern: /tvk_[a-zA-Z0-9]{20,}/g, label: 'Telvok API key' },
|
|
11
|
+
{ pattern: /ghp_[a-zA-Z0-9]{36}/g, label: 'GitHub personal access token' },
|
|
12
|
+
{ pattern: /xoxb-[a-zA-Z0-9-]+/g, label: 'Slack bot token' },
|
|
13
|
+
{ pattern: /eyJ[a-zA-Z0-9_-]{20,}\.[a-zA-Z0-9_-]{20,}\.[a-zA-Z0-9_-]{20,}/g, label: 'JWT token' },
|
|
14
|
+
{ pattern: /AKIA[A-Z0-9]{16}/g, label: 'AWS access key' },
|
|
15
|
+
{ pattern: /npm_[a-zA-Z0-9]{36}/g, label: 'npm token' },
|
|
16
|
+
// Credentials in assignments
|
|
17
|
+
{ pattern: /password\s*[:=]\s*['"][^'"]+['"]/gi, label: 'password value' },
|
|
18
|
+
{ pattern: /secret\s*[:=]\s*['"][^'"]+['"]/gi, label: 'secret value' },
|
|
19
|
+
{ pattern: /api[_-]?key\s*[:=]\s*['"][^'"]+['"]/gi, label: 'API key value' },
|
|
20
|
+
// Personal data
|
|
21
|
+
{ pattern: /\b[a-zA-Z0-9._%+-]+@(?!example\.com)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b/g, label: 'email address' },
|
|
22
|
+
// Connection strings with credentials
|
|
23
|
+
{ pattern: /:\/\/[^:]+:[^@]+@[^/\s]+/g, label: 'URL with embedded credentials' },
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Scan entries for sensitive data patterns.
|
|
27
|
+
* Returns findings grouped by entry.
|
|
28
|
+
*/
|
|
29
|
+
export function scanForSensitiveData(entries) {
|
|
30
|
+
const findings = [];
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const textToScan = [
|
|
33
|
+
entry.title,
|
|
34
|
+
entry.content,
|
|
35
|
+
entry.intent,
|
|
36
|
+
entry.context,
|
|
37
|
+
entry.reasoning,
|
|
38
|
+
entry.example,
|
|
39
|
+
].filter(Boolean).join('\n');
|
|
40
|
+
const matches = [];
|
|
41
|
+
for (const { pattern, label } of SENSITIVE_PATTERNS) {
|
|
42
|
+
pattern.lastIndex = 0;
|
|
43
|
+
if (pattern.test(textToScan)) {
|
|
44
|
+
matches.push(label);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (matches.length > 0) {
|
|
48
|
+
findings.push({
|
|
49
|
+
entry: entry.title,
|
|
50
|
+
file: entry.originalPath,
|
|
51
|
+
matches,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return findings;
|
|
56
|
+
}
|
|
@@ -9,11 +9,11 @@ export declare function getLibraryPath(): string;
|
|
|
9
9
|
export declare function getLocalPath(libraryPath: string): string;
|
|
10
10
|
/**
|
|
11
11
|
* Get the imported entries path.
|
|
12
|
-
* @deprecated Use getPackagesPath for
|
|
12
|
+
* @deprecated Use getPackagesPath for library content
|
|
13
13
|
*/
|
|
14
14
|
export declare function getImportedPath(libraryPath: string): string;
|
|
15
15
|
/**
|
|
16
|
-
* Get the packages path (
|
|
16
|
+
* Get the packages path (library content from others).
|
|
17
17
|
*/
|
|
18
18
|
export declare function getPackagesPath(libraryPath: string): string;
|
|
19
19
|
/**
|
package/dist/library/storage.js
CHANGED
|
@@ -17,13 +17,13 @@ export function getLocalPath(libraryPath) {
|
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Get the imported entries path.
|
|
20
|
-
* @deprecated Use getPackagesPath for
|
|
20
|
+
* @deprecated Use getPackagesPath for library content
|
|
21
21
|
*/
|
|
22
22
|
export function getImportedPath(libraryPath) {
|
|
23
23
|
return path.join(libraryPath, 'imported');
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* Get the packages path (
|
|
26
|
+
* Get the packages path (library content from others).
|
|
27
27
|
*/
|
|
28
28
|
export function getPackagesPath(libraryPath) {
|
|
29
29
|
return path.join(libraryPath, 'packages');
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get embedding for a text string.
|
|
3
|
+
* Returns a 384-dimensional normalized vector.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getEmbedding(text: string): Promise<number[]>;
|
|
6
|
+
/**
|
|
7
|
+
* Check if embeddings are available (model can load).
|
|
8
|
+
*/
|
|
9
|
+
export declare function isEmbeddingAvailable(): Promise<boolean>;
|
|
10
|
+
/**
|
|
11
|
+
* Calculate cosine similarity between two vectors.
|
|
12
|
+
* Since vectors are normalized, this is just the dot product.
|
|
13
|
+
*/
|
|
14
|
+
export declare function cosineSimilarity(a: number[], b: number[]): number;
|
|
15
|
+
/**
|
|
16
|
+
* Split text into chunks at sentence boundaries.
|
|
17
|
+
* Aims for ~500 chars per chunk to preserve semantic meaning.
|
|
18
|
+
*/
|
|
19
|
+
export declare function chunkText(text: string, maxChars?: number): string[];
|
|
20
|
+
export declare const EMBEDDING_MODEL_ID = "Xenova/all-MiniLM-L6-v2";
|
|
21
|
+
export declare const EMBEDDING_DIMENSION = 384;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { pipeline, env } from '@huggingface/transformers';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { getLibraryPath } from './storage.js';
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Configuration
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Cache model in .librarian/models
|
|
8
|
+
env.allowRemoteModels = true;
|
|
9
|
+
const MODEL_ID = 'Xenova/all-MiniLM-L6-v2';
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Embedding Generation
|
|
12
|
+
// ============================================================================
|
|
13
|
+
let embedder = null;
|
|
14
|
+
/**
|
|
15
|
+
* Get embedding for a text string.
|
|
16
|
+
* Returns a 384-dimensional normalized vector.
|
|
17
|
+
*/
|
|
18
|
+
export async function getEmbedding(text) {
|
|
19
|
+
if (!embedder) {
|
|
20
|
+
// Set local model path on first call
|
|
21
|
+
const libraryPath = getLibraryPath();
|
|
22
|
+
env.localModelPath = path.join(libraryPath, 'models');
|
|
23
|
+
embedder = await pipeline('feature-extraction', MODEL_ID);
|
|
24
|
+
}
|
|
25
|
+
const result = await embedder(text, { pooling: 'mean', normalize: true });
|
|
26
|
+
return Array.from(result.data);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if embeddings are available (model can load).
|
|
30
|
+
*/
|
|
31
|
+
export async function isEmbeddingAvailable() {
|
|
32
|
+
try {
|
|
33
|
+
await getEmbedding('test');
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Similarity Calculation
|
|
42
|
+
// ============================================================================
|
|
43
|
+
/**
|
|
44
|
+
* Calculate cosine similarity between two vectors.
|
|
45
|
+
* Since vectors are normalized, this is just the dot product.
|
|
46
|
+
*/
|
|
47
|
+
export function cosineSimilarity(a, b) {
|
|
48
|
+
if (a.length !== b.length) {
|
|
49
|
+
throw new Error('Vectors must have same dimension');
|
|
50
|
+
}
|
|
51
|
+
return a.reduce((sum, val, i) => sum + val * b[i], 0);
|
|
52
|
+
}
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Text Chunking
|
|
55
|
+
// ============================================================================
|
|
56
|
+
/**
|
|
57
|
+
* Split text into chunks at sentence boundaries.
|
|
58
|
+
* Aims for ~500 chars per chunk to preserve semantic meaning.
|
|
59
|
+
*/
|
|
60
|
+
export function chunkText(text, maxChars = 500) {
|
|
61
|
+
// Split at sentence boundaries (. ! ? followed by whitespace)
|
|
62
|
+
const sentences = text.split(/(?<=[.!?])\s+/);
|
|
63
|
+
const chunks = [];
|
|
64
|
+
let current = '';
|
|
65
|
+
for (const sentence of sentences) {
|
|
66
|
+
// If adding this sentence exceeds limit and we have content, start new chunk
|
|
67
|
+
if ((current + ' ' + sentence).length > maxChars && current.trim()) {
|
|
68
|
+
chunks.push(current.trim());
|
|
69
|
+
current = sentence;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
current = current ? current + ' ' + sentence : sentence;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Don't forget the last chunk
|
|
76
|
+
if (current.trim()) {
|
|
77
|
+
chunks.push(current.trim());
|
|
78
|
+
}
|
|
79
|
+
// If no chunks created (e.g., no sentence boundaries), return original text
|
|
80
|
+
return chunks.length > 0 ? chunks : [text];
|
|
81
|
+
}
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// Constants
|
|
84
|
+
// ============================================================================
|
|
85
|
+
export const EMBEDDING_MODEL_ID = MODEL_ID;
|
|
86
|
+
export const EMBEDDING_DIMENSION = 384;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { LibraryEntry } from './schemas.js';
|
|
2
|
+
export declare class LibraryManager {
|
|
3
|
+
private libraryPath;
|
|
4
|
+
constructor();
|
|
5
|
+
/**
|
|
6
|
+
* Initialize the library directory structure.
|
|
7
|
+
*/
|
|
8
|
+
initialize(): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Get all entries from local library.
|
|
11
|
+
*/
|
|
12
|
+
getLocalEntries(): Promise<LibraryEntry[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Get all entries from imported libraries.
|
|
15
|
+
*/
|
|
16
|
+
getImportedEntries(): Promise<LibraryEntry[]>;
|
|
17
|
+
/**
|
|
18
|
+
* Get all archived entries.
|
|
19
|
+
*/
|
|
20
|
+
getArchivedEntries(): Promise<LibraryEntry[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Query entries by topic.
|
|
23
|
+
*/
|
|
24
|
+
queryByTopic(topic: string): Promise<LibraryEntry[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Record a new entry to local library.
|
|
27
|
+
*/
|
|
28
|
+
record(topics: string[], content: string): Promise<{
|
|
29
|
+
entry: LibraryEntry;
|
|
30
|
+
path: string;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Archive an entry (move to archived/).
|
|
34
|
+
*/
|
|
35
|
+
archive(entryId: string): Promise<{
|
|
36
|
+
success: boolean;
|
|
37
|
+
message: string;
|
|
38
|
+
}>;
|
|
39
|
+
private readEntriesFromPath;
|
|
40
|
+
private findEntryById;
|
|
41
|
+
private fileExists;
|
|
42
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
import { glob } from 'glob';
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
6
|
+
import { getLibraryPath, getLocalPath, getImportedPath, getArchivedPath, } from './storage.js';
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Library Manager
|
|
9
|
+
// ============================================================================
|
|
10
|
+
export class LibraryManager {
|
|
11
|
+
libraryPath;
|
|
12
|
+
constructor() {
|
|
13
|
+
this.libraryPath = getLibraryPath();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Initialize the library directory structure.
|
|
17
|
+
*/
|
|
18
|
+
async initialize() {
|
|
19
|
+
const dirs = [
|
|
20
|
+
getLocalPath(this.libraryPath),
|
|
21
|
+
getImportedPath(this.libraryPath),
|
|
22
|
+
getArchivedPath(this.libraryPath),
|
|
23
|
+
];
|
|
24
|
+
for (const dir of dirs) {
|
|
25
|
+
await fs.mkdir(dir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get all entries from local library.
|
|
30
|
+
*/
|
|
31
|
+
async getLocalEntries() {
|
|
32
|
+
const localPath = getLocalPath(this.libraryPath);
|
|
33
|
+
return this.readEntriesFromPath(localPath, 'local');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get all entries from imported libraries.
|
|
37
|
+
*/
|
|
38
|
+
async getImportedEntries() {
|
|
39
|
+
const importedPath = getImportedPath(this.libraryPath);
|
|
40
|
+
return this.readEntriesFromPath(importedPath, 'imported');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get all archived entries.
|
|
44
|
+
*/
|
|
45
|
+
async getArchivedEntries() {
|
|
46
|
+
const archivedPath = getArchivedPath(this.libraryPath);
|
|
47
|
+
return this.readEntriesFromPath(archivedPath, 'archived');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Query entries by topic.
|
|
51
|
+
*/
|
|
52
|
+
async queryByTopic(topic) {
|
|
53
|
+
const [local, imported] = await Promise.all([
|
|
54
|
+
this.getLocalEntries(),
|
|
55
|
+
this.getImportedEntries(),
|
|
56
|
+
]);
|
|
57
|
+
const allEntries = [...local, ...imported];
|
|
58
|
+
const searchTerm = topic.toLowerCase();
|
|
59
|
+
return allEntries.filter(entry => entry.topics.some(t => t.toLowerCase().includes(searchTerm)) ||
|
|
60
|
+
entry.content.toLowerCase().includes(searchTerm));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Record a new entry to local library.
|
|
64
|
+
*/
|
|
65
|
+
async record(topics, content) {
|
|
66
|
+
const localPath = getLocalPath(this.libraryPath);
|
|
67
|
+
await fs.mkdir(localPath, { recursive: true });
|
|
68
|
+
const id = uuidv4();
|
|
69
|
+
const created = new Date().toISOString();
|
|
70
|
+
const entry = {
|
|
71
|
+
id,
|
|
72
|
+
topics,
|
|
73
|
+
content,
|
|
74
|
+
created,
|
|
75
|
+
source: 'local',
|
|
76
|
+
origin: 'manual',
|
|
77
|
+
};
|
|
78
|
+
// Generate filename
|
|
79
|
+
const slug = topics[0]
|
|
80
|
+
.toLowerCase()
|
|
81
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
82
|
+
.replace(/^-|-$/g, '');
|
|
83
|
+
const timestamp = created.slice(0, 10);
|
|
84
|
+
let filename = `${slug}-${timestamp}.md`;
|
|
85
|
+
let filePath = path.join(localPath, filename);
|
|
86
|
+
// Handle collisions
|
|
87
|
+
let counter = 1;
|
|
88
|
+
while (await this.fileExists(filePath)) {
|
|
89
|
+
filename = `${slug}-${timestamp}-${counter}.md`;
|
|
90
|
+
filePath = path.join(localPath, filename);
|
|
91
|
+
counter++;
|
|
92
|
+
}
|
|
93
|
+
// Write file
|
|
94
|
+
const frontmatter = {
|
|
95
|
+
id,
|
|
96
|
+
topics,
|
|
97
|
+
created,
|
|
98
|
+
source: 'manual',
|
|
99
|
+
};
|
|
100
|
+
const fileContent = matter.stringify(content, frontmatter);
|
|
101
|
+
await fs.writeFile(filePath, fileContent, 'utf-8');
|
|
102
|
+
return {
|
|
103
|
+
entry,
|
|
104
|
+
path: path.relative(this.libraryPath, filePath),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Archive an entry (move to archived/).
|
|
109
|
+
*/
|
|
110
|
+
async archive(entryId) {
|
|
111
|
+
const localPath = getLocalPath(this.libraryPath);
|
|
112
|
+
const archivedPath = getArchivedPath(this.libraryPath);
|
|
113
|
+
// Find the entry
|
|
114
|
+
const found = await this.findEntryById(localPath, entryId);
|
|
115
|
+
if (!found) {
|
|
116
|
+
return { success: false, message: `Entry not found: ${entryId}` };
|
|
117
|
+
}
|
|
118
|
+
await fs.mkdir(archivedPath, { recursive: true });
|
|
119
|
+
const filename = path.basename(found.filePath);
|
|
120
|
+
const newPath = path.join(archivedPath, filename);
|
|
121
|
+
await fs.rename(found.filePath, newPath);
|
|
122
|
+
return {
|
|
123
|
+
success: true,
|
|
124
|
+
message: `Archived to ${path.relative(this.libraryPath, newPath)}`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// Private Helpers
|
|
129
|
+
// ============================================================================
|
|
130
|
+
async readEntriesFromPath(dirPath, source) {
|
|
131
|
+
const entries = [];
|
|
132
|
+
try {
|
|
133
|
+
const files = await glob(path.join(dirPath, '**/*.md'), { nodir: true });
|
|
134
|
+
for (const filePath of files) {
|
|
135
|
+
try {
|
|
136
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
137
|
+
const { data, content: body } = matter(content);
|
|
138
|
+
let topics;
|
|
139
|
+
if (Array.isArray(data.topics)) {
|
|
140
|
+
topics = data.topics;
|
|
141
|
+
}
|
|
142
|
+
else if (data.topic) {
|
|
143
|
+
topics = [data.topic];
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
topics = ['general'];
|
|
147
|
+
}
|
|
148
|
+
entries.push({
|
|
149
|
+
id: data.id || uuidv4(),
|
|
150
|
+
topics,
|
|
151
|
+
content: body.trim(),
|
|
152
|
+
created: data.created || new Date().toISOString(),
|
|
153
|
+
source,
|
|
154
|
+
origin: data.source,
|
|
155
|
+
imported_from: data.imported_from,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Skip unreadable files
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Directory doesn't exist
|
|
165
|
+
}
|
|
166
|
+
return entries;
|
|
167
|
+
}
|
|
168
|
+
async findEntryById(dirPath, entryId) {
|
|
169
|
+
try {
|
|
170
|
+
const files = await glob(path.join(dirPath, '**/*.md'), { nodir: true });
|
|
171
|
+
for (const filePath of files) {
|
|
172
|
+
try {
|
|
173
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
174
|
+
const { data, content: body } = matter(content);
|
|
175
|
+
if (data.id === entryId) {
|
|
176
|
+
let topics;
|
|
177
|
+
if (Array.isArray(data.topics)) {
|
|
178
|
+
topics = data.topics;
|
|
179
|
+
}
|
|
180
|
+
else if (data.topic) {
|
|
181
|
+
topics = [data.topic];
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
topics = ['general'];
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
entry: {
|
|
188
|
+
id: data.id,
|
|
189
|
+
topics,
|
|
190
|
+
content: body.trim(),
|
|
191
|
+
created: data.created || new Date().toISOString(),
|
|
192
|
+
source: 'local',
|
|
193
|
+
origin: data.source,
|
|
194
|
+
},
|
|
195
|
+
filePath,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// Skip unreadable files
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// Directory doesn't exist
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
async fileExists(filePath) {
|
|
210
|
+
try {
|
|
211
|
+
await fs.access(filePath);
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ParseResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a Cursor Memory Bank folder (.cursor-memory/).
|
|
4
|
+
*
|
|
5
|
+
* Cursor Memory Bank typically contains:
|
|
6
|
+
* - activeContext.md - Current working context
|
|
7
|
+
* - progress.md - Progress log
|
|
8
|
+
* - projectBrief.md - Project overview
|
|
9
|
+
* - systemPatterns.md - System patterns
|
|
10
|
+
* - decisionLog.md - Decision history
|
|
11
|
+
* - techStack.md - Technology stack info
|
|
12
|
+
*
|
|
13
|
+
* Can also contain JSON files and subdirectories.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseCursorMemory(dirPath: string): Promise<ParseResult>;
|