@llmnesia/mcp 0.1.1 → 0.1.2
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/README.md +18 -9
- package/dist/{chunk-4CBIA42T.js → chunk-7BW63ZTP.js} +1 -45
- package/dist/chunk-BQM5Q4XX.js +49 -0
- package/dist/cli-CNIF4XT3.js +3877 -0
- package/dist/cli.js +32 -3477
- package/dist/mcp.js +7 -6
- package/package.json +2 -3
- package/server.json +2 -2
- package/dist/chunk-4CBIA42T.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/mcp.js.map +0 -1
package/dist/mcp.js
CHANGED
|
@@ -3,15 +3,17 @@ import { createRequire as __llmnesiaCreateRequire } from "node:module";
|
|
|
3
3
|
const require = __llmnesiaCreateRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
5
|
Corpus,
|
|
6
|
-
VERSION,
|
|
7
|
-
__commonJS,
|
|
8
|
-
__export,
|
|
9
|
-
__toESM,
|
|
10
6
|
drainInbox,
|
|
11
7
|
openSearchIndex,
|
|
12
8
|
pendingInboxFiles,
|
|
13
9
|
resolveCorpusDir
|
|
14
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-7BW63ZTP.js";
|
|
11
|
+
import {
|
|
12
|
+
VERSION,
|
|
13
|
+
__commonJS,
|
|
14
|
+
__export,
|
|
15
|
+
__toESM
|
|
16
|
+
} from "./chunk-BQM5Q4XX.js";
|
|
15
17
|
|
|
16
18
|
// ../node_modules/ajv/dist/compile/codegen/code.js
|
|
17
19
|
var require_code = __commonJS({
|
|
@@ -21536,4 +21538,3 @@ if (isMain) {
|
|
|
21536
21538
|
export {
|
|
21537
21539
|
runServe
|
|
21538
21540
|
};
|
|
21539
|
-
//# sourceMappingURL=mcp.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llmnesia/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"comment:mcpName": "The MCP Registry's ownership marker: it must equal server.json's `name`, and the namespace must match how you authenticate with mcp-publisher. `com.llmnesia/...` requires DNS auth (a TXT record on llmnesia.com) — see docs/MCP_INSTALLER_BOARD.md.",
|
|
@@ -18,10 +18,9 @@
|
|
|
18
18
|
"search",
|
|
19
19
|
"local-first"
|
|
20
20
|
],
|
|
21
|
-
"homepage": "https://llmnesia.com
|
|
21
|
+
"homepage": "https://llmnesia.com",
|
|
22
22
|
"comment:repository": "Deliberately omitted: the source repo is private, and a repository field pointing at it renders as a dead link on the npm page. Add one if a public mirror is ever extracted.",
|
|
23
23
|
"bugs": {
|
|
24
|
-
"url": "https://llmnesia.com/mcp",
|
|
25
24
|
"email": "hello@llmnesia.com"
|
|
26
25
|
},
|
|
27
26
|
"license": "MIT",
|
package/server.json
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"name": "com.llmnesia/mcp",
|
|
4
4
|
"title": "LLMnesia",
|
|
5
5
|
"description": "Search your AI chat history from Claude, Cursor, Codex, and other MCP clients.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.2",
|
|
7
7
|
"packages": [
|
|
8
8
|
{
|
|
9
9
|
"registryType": "npm",
|
|
10
10
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
11
11
|
"identifier": "@llmnesia/mcp",
|
|
12
|
-
"version": "0.1.
|
|
12
|
+
"version": "0.1.2",
|
|
13
13
|
"transport": {
|
|
14
14
|
"type": "stdio"
|
|
15
15
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/corpus.ts","../src/paths.ts","../src/serialize.ts","../src/ingest.ts","../src/searchIndex.ts","../src/sqliteWarning.ts","../src/version.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\n\n/**\n * The corpus format version written into meta.json. Bumped only when the\n * on-disk conversation JSON shape changes in a way the companion must react to\n * (e.g. forcing an index rebuild). The message/conversation records themselves\n * are versioned upstream by @llmnesia/shared-types.\n */\nexport const CORPUS_FORMAT_VERSION = 1;\n\n// Written by packages/native-host/install.mjs. Reading it here means the\n// installer is the one source of truth for the corpus path shared between the\n// extension's native-messaging sink and this MCP server, instead of two\n// independently-set defaults that can silently disagree (see the 2026-07-21\n// incident: a stale LLMNESIA_CORPUS_DIR override in one place and not the\n// other made a \"full re-sync\" write 4690 conversations nobody ever read).\nfunction nativeHostConfigPath(): string {\n return join(homedir(), \".llmnesia\", \"config.json\");\n}\n\nfunction readNativeHostCorpusRoot(): string | undefined {\n try {\n const parsed = JSON.parse(readFileSync(nativeHostConfigPath(), \"utf8\")) as { corpusRoot?: unknown };\n return typeof parsed.corpusRoot === \"string\" ? parsed.corpusRoot : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Resolve the corpus root. Precedence: explicit arg > LLMNESIA_CORPUS_DIR env >\n * the native host's own config (~/.llmnesia/config.json) > the hardcoded\n * default (~/.llmnesia/corpus). Both the extension folder picker and the\n * Claude Desktop MCP env must point at the same path (see PLAN-MCP.md).\n */\nexport function resolveCorpusDir(explicit?: string): string {\n const raw = explicit ?? process.env.LLMNESIA_CORPUS_DIR ?? readNativeHostCorpusRoot() ?? \"~/.llmnesia/corpus\";\n return expandHome(raw);\n}\n\nfunction expandHome(p: string): string {\n if (p === \"~\") {\n return homedir();\n }\n if (p.startsWith(\"~/\") || p.startsWith(\"~\\\\\")) {\n return resolve(homedir(), p.slice(2));\n }\n return resolve(p);\n}\n","import { promises as fs } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport type { ConversationDoc, CorpusConversation, MessageDoc } from \"@llmnesia/shared-types\";\nimport { CORPUS_FORMAT_VERSION } from \"./config.js\";\nimport { CorpusPaths, safeSegment, slugifyDocId } from \"./paths.js\";\nimport { canonicalizeConversation } from \"./serialize.js\";\n\n/**\n * The corpus is plaintext chat history sitting outside the browser's profile.\n * Keep it owner-only so it is no more exposed than the IndexedDB it came from.\n * (Encryption at rest is a separate, larger question: it would have to cover\n * the FTS index too, which holds every word of every message.)\n */\nconst DIR_MODE = 0o700;\nconst FILE_MODE = 0o600;\n\nexport type CorpusMeta = {\n formatVersion: number;\n createdAt: number;\n lastIngestAt: number | null;\n conversationCount: number;\n};\n\nexport type UpsertResult = {\n docId: string;\n /** \"created\" (new file), \"updated\" (newer content replaced), \"skipped\" (stale/no-op). */\n outcome: \"created\" | \"updated\" | \"skipped\";\n};\n\nexport type CorpusStats = {\n conversationCount: number;\n messageCount: number;\n byPlatform: Record<string, number>;\n};\n\n/**\n * The on-disk corpus: one JSON file per conversation, the source of truth. All\n * writes funnel through {@link upsert} so a future index (Phase 2) and the MCP\n * `save_conversation` path (Phase 3) share one code path with LWW-by-updatedAt\n * semantics. Reads are by docId; writes are atomic (temp file + rename).\n */\nexport class Corpus {\n readonly paths: CorpusPaths;\n // Serialize writes within this process so concurrent upserts (inbox drain +\n // MCP save_conversation) don't interleave. Cross-process safety relies on the\n // attended, single-companion v1 model (see PLAN-MCP.md open question 3).\n private writeChain: Promise<unknown> = Promise.resolve();\n\n constructor(root: string) {\n this.paths = new CorpusPaths(root);\n }\n\n /** Create the directory skeleton and meta.json if absent. Idempotent. */\n async init(): Promise<void> {\n await fs.mkdir(this.paths.conversationsDir, { recursive: true, mode: DIR_MODE });\n await fs.mkdir(this.paths.processedDir, { recursive: true, mode: DIR_MODE });\n await fs.mkdir(this.paths.indexDir, { recursive: true, mode: DIR_MODE });\n // mkdir's mode is masked by the process umask, and pre-existing dirs keep\n // their old mode, so tighten explicitly. The corpus is plaintext chat\n // history: it must not be readable by other accounts on the machine.\n await hardenDir(this.paths.root);\n await hardenDir(this.paths.conversationsDir);\n await hardenDir(this.paths.inboxDir);\n await hardenDir(this.paths.processedDir);\n await hardenDir(this.paths.indexDir);\n try {\n await fs.access(this.paths.metaFile);\n } catch {\n const meta: CorpusMeta = {\n formatVersion: CORPUS_FORMAT_VERSION,\n createdAt: Date.now(),\n lastIngestAt: null,\n conversationCount: 0\n };\n await writeFileAtomic(this.paths.metaFile, `${JSON.stringify(meta, null, 2)}\\n`);\n }\n }\n\n async readMeta(): Promise<CorpusMeta | null> {\n try {\n const raw = await fs.readFile(this.paths.metaFile, \"utf8\");\n return JSON.parse(raw) as CorpusMeta;\n } catch {\n return null;\n }\n }\n\n private async writeMeta(patch: Partial<CorpusMeta>): Promise<void> {\n const current = (await this.readMeta()) ?? {\n formatVersion: CORPUS_FORMAT_VERSION,\n createdAt: Date.now(),\n lastIngestAt: null,\n conversationCount: 0\n };\n const next: CorpusMeta = { ...current, ...patch };\n await writeFileAtomic(this.paths.metaFile, `${JSON.stringify(next, null, 2)}\\n`);\n }\n\n /**\n * Insert or replace a conversation, keyed on docId with last-write-wins by\n * `conversation.updatedAt`. A stale write (older updatedAt than what's on\n * disk) is a no-op. Re-writing identical content produces byte-identical\n * output, so replaying a delta is safe.\n */\n async upsert(record: CorpusConversation): Promise<UpsertResult> {\n return this.enqueueWrite(async () => {\n const canonical = canonicalizeConversation(record);\n const { conversation } = canonical;\n const file = this.paths.conversationFile(conversation.platform, conversation.docId);\n\n const existing = await readJsonIfExists<CorpusConversation>(file);\n if (existing) {\n const existingUpdatedAt = existing.conversation.updatedAt ?? 0;\n const incomingUpdatedAt = conversation.updatedAt ?? 0;\n if (incomingUpdatedAt < existingUpdatedAt) {\n return { docId: conversation.docId, outcome: \"skipped\" };\n }\n }\n\n const serialized = `${JSON.stringify(canonical, null, 2)}\\n`;\n if (existing) {\n const existingSerialized = `${JSON.stringify(canonicalizeConversation(existing), null, 2)}\\n`;\n if (existingSerialized === serialized) {\n return { docId: conversation.docId, outcome: \"skipped\" };\n }\n }\n\n await fs.mkdir(dirname(file), { recursive: true, mode: DIR_MODE });\n await writeFileAtomic(file, serialized);\n return { docId: conversation.docId, outcome: existing ? \"updated\" : \"created\" };\n });\n }\n\n /** Read a conversation by docId, scanning platform dirs for the slug file. */\n async get(docId: string): Promise<CorpusConversation | null> {\n const filename = `${slugifyDocId(docId)}.json`;\n for (const platform of await this.listPlatforms()) {\n const candidate = join(this.paths.platformDir(platform), filename);\n const record = await readJsonIfExists<CorpusConversation>(candidate);\n if (record && record.conversation.docId === docId) {\n return record;\n }\n }\n return null;\n }\n\n /** Platform subdirectory names under conversations/. */\n async listPlatforms(): Promise<string[]> {\n try {\n const entries = await fs.readdir(this.paths.conversationsDir, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory()).map((e) => e.name);\n } catch {\n return [];\n }\n }\n\n /** Async-iterate every stored conversation (used by reindex + stats). */\n async *iterate(): AsyncGenerator<CorpusConversation> {\n for (const platform of await this.listPlatforms()) {\n const dir = this.paths.platformDir(platform);\n let files: string[];\n try {\n files = await fs.readdir(dir);\n } catch {\n continue;\n }\n for (const name of files) {\n if (!name.endsWith(\".json\")) {\n continue;\n }\n const record = await readJsonIfExists<CorpusConversation>(join(dir, name));\n if (record) {\n yield record;\n }\n }\n }\n }\n\n async stats(): Promise<CorpusStats> {\n const byPlatform: Record<string, number> = {};\n let conversationCount = 0;\n let messageCount = 0;\n for await (const record of this.iterate()) {\n conversationCount += 1;\n messageCount += record.messages.length;\n const platform = safeSegment(record.conversation.platform);\n byPlatform[platform] = (byPlatform[platform] ?? 0) + 1;\n }\n return { conversationCount, messageCount, byPlatform };\n }\n\n /** Refresh meta.json's conversationCount / lastIngestAt after an ingest run. */\n async touchIngestMeta(): Promise<void> {\n const { conversationCount } = await this.stats();\n await this.writeMeta({ lastIngestAt: Date.now(), conversationCount });\n }\n\n private enqueueWrite<T>(fn: () => Promise<T>): Promise<T> {\n const run = this.writeChain.then(fn, fn);\n // Keep the chain alive even if a write rejects.\n this.writeChain = run.then(\n () => undefined,\n () => undefined\n );\n return run;\n }\n}\n\nasync function readJsonIfExists<T>(file: string): Promise<T | null> {\n try {\n const raw = await fs.readFile(file, \"utf8\");\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n\nasync function writeFileAtomic(file: string, contents: string): Promise<void> {\n const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;\n // rename() preserves the temp file's mode, so it has to be created private:\n // fixing it after the rename would leave a readable window.\n await fs.writeFile(tmp, contents, { encoding: \"utf8\", mode: FILE_MODE });\n await fs.rename(tmp, file);\n}\n\n/**\n * Owner-only. The corpus holds plaintext conversation history, so it should be\n * no more readable than the browser profile it was copied out of.\n */\nasync function hardenDir(dir: string): Promise<void> {\n try {\n await fs.chmod(dir, DIR_MODE);\n } catch {\n // Best effort: a corpus on a filesystem without POSIX modes (exFAT, some\n // network mounts) still works, it just can't be locked down this way.\n }\n}\n\n// Re-export for consumers that only need the type surface.\nexport type { ConversationDoc, CorpusConversation, MessageDoc };\n","import { join } from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport type { Platform } from \"@llmnesia/shared-types\";\n\n/**\n * On-disk layout for a corpus root (see PLAN-MCP.md §3):\n *\n * <root>/\n * meta.json\n * inbox/ NDJSON deltas dropped by the extension\n * processed/ files moved here after successful ingest\n * conversations/\n * <platform>/<slug>.json { conversation, messages: [...] }\n * index/\n * llmnesia.db SQLite FTS5 (derived, disposable)\n */\nexport class CorpusPaths {\n constructor(public readonly root: string) {}\n\n get metaFile(): string {\n return join(this.root, \"meta.json\");\n }\n\n get inboxDir(): string {\n return join(this.root, \"inbox\");\n }\n\n get processedDir(): string {\n return join(this.inboxDir, \"processed\");\n }\n\n get conversationsDir(): string {\n return join(this.root, \"conversations\");\n }\n\n get indexDir(): string {\n return join(this.root, \"index\");\n }\n\n get indexDbFile(): string {\n return join(this.indexDir, \"llmnesia.db\");\n }\n\n /** Directory holding a platform's conversation JSON files. */\n platformDir(platform: Platform | string): string {\n return join(this.conversationsDir, safeSegment(platform));\n }\n\n /** Absolute path to the JSON file backing a given docId. */\n conversationFile(platform: Platform | string, docId: string): string {\n return join(this.platformDir(platform), `${slugifyDocId(docId)}.json`);\n }\n}\n\nconst SLUG_MAX = 120;\n\n/**\n * Filesystem-safe filename for a docId (which is typically `platform:sourceId`\n * and can contain `:`, `/`, and other unsafe characters). Readable slug for\n * common ids; falls back to a hash suffix when the slug would be lossy or too\n * long, so distinct docIds never collide on disk. The canonical docId is always\n * stored inside the file — the filename is only a handle.\n */\nexport function slugifyDocId(docId: string): string {\n // Lossless when the id is already filename-safe and short enough — then the\n // slug uniquely identifies the docId and we can use it bare (readable).\n const lossless = docId.length > 0 && docId.length <= SLUG_MAX && /^[a-zA-Z0-9._-]+$/.test(docId);\n if (lossless) {\n return docId;\n }\n // Otherwise the transform could collide (chars replaced or truncated), so\n // append a hash of the full docId to guarantee uniqueness on disk.\n const hash = createHash(\"sha1\").update(docId).digest(\"hex\").slice(0, 12);\n const readable = docId\n .replace(/[^a-zA-Z0-9._-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, SLUG_MAX - 13);\n return readable.length > 0 ? `${readable}-${hash}` : hash;\n}\n\n/** Sanitize a single path segment (platform name) — no separators, no dots. */\nexport function safeSegment(value: string): string {\n const cleaned = value.replace(/[^a-zA-Z0-9._-]+/g, \"-\").replace(/^[.-]+|[.-]+$/g, \"\");\n return cleaned.length > 0 ? cleaned : \"unknown\";\n}\n","import type { ConversationDoc, CorpusConversation, MessageDoc } from \"@llmnesia/shared-types\";\n\n/**\n * Produce a canonical CorpusConversation: fixed key order, undefined fields\n * dropped, messages sorted by msgIndex. This makes JSON.stringify deterministic\n * regardless of input key order, so re-ingesting an unchanged delta writes\n * byte-identical files (the idempotency guarantee in PLAN-MCP.md §5).\n */\nexport function canonicalizeConversation(record: CorpusConversation): CorpusConversation {\n return {\n conversation: canonicalConversationDoc(record.conversation),\n messages: [...record.messages].sort((a, b) => a.msgIndex - b.msgIndex).map(canonicalMessageDoc)\n };\n}\n\nfunction canonicalConversationDoc(c: ConversationDoc): ConversationDoc {\n const out: Record<string, unknown> = {\n docId: c.docId,\n platform: c.platform,\n title: c.title,\n url: c.url,\n createdAt: c.createdAt,\n contentUpdatedAt: c.contentUpdatedAt,\n indexedAt: c.indexedAt,\n updatedAt: c.updatedAt,\n lastSeenAt: c.lastSeenAt,\n messageCount: c.messageCount,\n preview: c.preview,\n hasImages: c.hasImages,\n pinned: c.pinned,\n indexLevel: c.indexLevel,\n estimatedBytes: c.estimatedBytes\n };\n assignIfDefined(out, \"searchDocLength\", c.searchDocLength);\n assignIfDefined(out, \"contentHash\", c.contentHash);\n assignIfDefined(out, \"truncated\", c.truncated);\n assignIfDefined(out, \"kind\", c.kind);\n assignIfDefined(out, \"sourceId\", c.sourceId);\n assignIfDefined(out, \"baseUrl\", c.baseUrl);\n assignIfDefined(out, \"isDeeplinkable\", c.isDeeplinkable);\n assignIfDefined(out, \"origin\", c.origin);\n assignIfDefined(out, \"accountId\", c.accountId);\n assignIfDefined(out, \"accountLabel\", c.accountLabel);\n if (c.summary) {\n out.summary = {\n text: c.summary.text,\n recipe: c.summary.recipe,\n method: c.summary.method,\n createdAt: c.summary.createdAt\n };\n }\n return out as unknown as ConversationDoc;\n}\n\nfunction canonicalMessageDoc(m: MessageDoc): MessageDoc {\n const out: Record<string, unknown> = {\n msgKey: m.msgKey,\n docId: m.docId,\n msgIndex: m.msgIndex,\n role: m.role,\n text: m.text\n };\n assignIfDefined(out, \"formattedText\", m.formattedText);\n out.createdAt = m.createdAt;\n out.updatedAt = m.updatedAt;\n out.contentHash = m.contentHash;\n assignIfDefined(out, \"platformMessageId\", m.platformMessageId);\n return out as unknown as MessageDoc;\n}\n\nfunction assignIfDefined(target: Record<string, unknown>, key: string, value: unknown): void {\n if (value !== undefined) {\n target[key] = value;\n }\n}\n","import { promises as fs, createReadStream } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { createGunzip } from \"node:zlib\";\nimport { Readable } from \"node:stream\";\nimport type { ConversationDoc, CorpusConversation, MessageDoc, MessageRole } from \"@llmnesia/shared-types\";\nimport type { Corpus, UpsertResult } from \"./corpus.js\";\nimport type { IndexSink } from \"./retriever.js\";\n\nexport type IngestReport = {\n conversations: number;\n created: number;\n updated: number;\n skipped: number;\n messages: number;\n malformedLines: number;\n};\n\nconst EMPTY_REPORT: IngestReport = {\n conversations: 0,\n created: 0,\n updated: 0,\n skipped: 0,\n messages: 0,\n malformedLines: 0\n};\n\n/**\n * Ingest one NDJSON backup file (the exact format emitted by the extension's\n * exportBackupJsonlChunks: a `meta` line, then per conversation a `conversation`\n * line followed by its `message` lines). Reassembles the interleaved lines into\n * CorpusConversation records and upserts each by docId. Supports `.gz`.\n */\nexport async function ingestFile(corpus: Corpus, file: string, index?: IndexSink): Promise<IngestReport> {\n const report: IngestReport = { ...EMPTY_REPORT };\n\n let current: ConversationDoc | null = null;\n let buffered: MessageDoc[] = [];\n\n const flush = async (): Promise<void> => {\n if (!current) {\n return;\n }\n const record: CorpusConversation = { conversation: current, messages: buffered };\n const result = await corpus.upsert(record);\n tally(report, result);\n // Keep the derived index in step with the corpus write. A stale/no-op upsert\n // (\"skipped\") means the index already reflects this conversation.\n if (index && result.outcome !== \"skipped\") {\n index.upsertConversation(record);\n }\n report.conversations += 1;\n report.messages += buffered.length;\n current = null;\n buffered = [];\n };\n\n const rl = createInterface({ input: openLineSource(file), crlfDelay: Infinity });\n for await (const raw of rl) {\n const line = raw.trim();\n if (!line) {\n continue;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n report.malformedLines += 1;\n continue;\n }\n if (typeof parsed !== \"object\" || parsed === null) {\n report.malformedLines += 1;\n continue;\n }\n const record = parsed as Record<string, unknown>;\n const type = record.type;\n\n if (type === \"meta\") {\n continue;\n }\n if (type === \"conversation\") {\n await flush();\n current = coerceConversation(record.conversation);\n buffered = [];\n if (!current) {\n report.malformedLines += 1;\n }\n continue;\n }\n if (type === \"message\" && current) {\n const docId = typeof record.docId === \"string\" ? record.docId : \"\";\n if (docId !== current.docId) {\n report.malformedLines += 1;\n continue;\n }\n const message = coerceMessage(current.docId, record.message);\n if (message) {\n buffered.push(message);\n } else {\n report.malformedLines += 1;\n }\n }\n }\n await flush();\n return report;\n}\n\n/**\n * Names of the NDJSON files waiting in `inbox/`, in ingest (filename) order.\n * Deliberately cheap — one readdir, no corpus init — because every MCP read\n * calls this to decide whether a drain is needed at all.\n */\nexport async function pendingInboxFiles(corpus: Corpus): Promise<string[]> {\n try {\n return (await fs.readdir(corpus.paths.inboxDir, { withFileTypes: true }))\n .filter((e) => e.isFile() && isNdjson(e.name))\n .map((e) => e.name)\n .sort();\n } catch {\n return [];\n }\n}\n\nexport type DrainOptions = {\n /**\n * Called once per file that could not be ingested. The drain continues\n * regardless — this is for reporting, not control flow.\n */\n onFileError?: (name: string, error: unknown) => void;\n};\n\n/**\n * Drain every NDJSON file in `inbox/` (not recursing into `processed/`),\n * ingesting each in filename order and moving successfully-ingested files into\n * `inbox/processed/`. Returns the aggregate report.\n *\n * One bad file does not abandon the ones queued behind it. A backlog is often\n * hundreds of megabytes across dozens of parts, and there is no cross-process\n * lock on `inbox/` — a second drain (another MCP client, or the CLI) can claim\n * a file between our readdir and our move. Aborting the batch there would strand\n * the remaining files until something else happened to trigger a drain, and\n * would skip the {@link Corpus.touchIngestMeta} below, leaving the corpus\n * reporting itself stale even though the data had landed.\n */\nexport async function drainInbox(\n corpus: Corpus,\n index?: IndexSink,\n options: DrainOptions = {}\n): Promise<IngestReport & { files: number; failed: number }> {\n await corpus.init();\n const inbox = corpus.paths.inboxDir;\n const entries = await pendingInboxFiles(corpus);\n\n const total: IngestReport & { files: number; failed: number } = { ...EMPTY_REPORT, files: 0, failed: 0 };\n for (const name of entries) {\n const src = join(inbox, name);\n try {\n const report = await ingestFile(corpus, src, index);\n accumulate(total, report);\n total.files += 1;\n await moveToProcessed(corpus, src, name);\n } catch (error) {\n // Ask the filesystem which kind of failure this was rather than trusting\n // the error code: a source file that vanished was claimed by a concurrent\n // drain (idempotent by docId, so it is being handled), but a missing\n // *destination* raises the same ENOENT and must be reported.\n if (await stillPending(src)) {\n total.failed += 1;\n options.onFileError?.(name, error);\n }\n }\n }\n if (total.files > 0) {\n await corpus.touchIngestMeta();\n }\n return total;\n}\n\n/** Whether the inbox file is still there — i.e. no other drain took it. */\nasync function stillPending(src: string): Promise<boolean> {\n try {\n await fs.access(src);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function moveToProcessed(corpus: Corpus, src: string, name: string): Promise<void> {\n const dest = join(corpus.paths.processedDir, name);\n try {\n await fs.rename(src, dest);\n } catch {\n // Cross-device or name clash: fall back to timestamped copy + unlink.\n const alt = join(corpus.paths.processedDir, `${Date.now()}-${name}`);\n await fs.copyFile(src, alt);\n await fs.unlink(src);\n }\n}\n\nfunction isNdjson(name: string): boolean {\n const lower = name.toLowerCase();\n return lower.endsWith(\".jsonl\") || lower.endsWith(\".ndjson\") || lower.endsWith(\".jsonl.gz\") || lower.endsWith(\".ndjson.gz\");\n}\n\nfunction openLineSource(file: string): Readable {\n const stream = createReadStream(file);\n if (file.toLowerCase().endsWith(\".gz\")) {\n return stream.pipe(createGunzip());\n }\n return stream;\n}\n\nfunction coerceConversation(value: unknown): ConversationDoc | null {\n if (!value || typeof value !== \"object\") {\n return null;\n }\n const c = value as Record<string, unknown>;\n if (typeof c.docId !== \"string\" || !c.docId) {\n return null;\n }\n // Trust the extension's own ConversationDoc shape; it is the authoritative\n // producer. Normalize the `updatedAt` used for LWW so a missing value can't\n // read as newer than a real timestamp.\n const conversation = { ...c } as unknown as ConversationDoc;\n if (typeof conversation.updatedAt !== \"number\") {\n conversation.updatedAt =\n typeof conversation.contentUpdatedAt === \"number\" ? conversation.contentUpdatedAt : 0;\n }\n return conversation;\n}\n\nfunction coerceMessage(docId: string, value: unknown): MessageDoc | null {\n if (!value || typeof value !== \"object\") {\n return null;\n }\n const m = value as Record<string, unknown>;\n const text = typeof m.text === \"string\" ? m.text : \"\";\n if (!text.trim()) {\n return null;\n }\n const msgIndex = typeof m.msgIndex === \"number\" ? m.msgIndex : 0;\n const message: MessageDoc = {\n // The backup line omits msgKey; reconstruct the extension's convention.\n msgKey: `${docId}:${msgIndex}`,\n docId,\n msgIndex,\n role: coerceRole(m.role),\n text,\n createdAt: typeof m.createdAt === \"number\" ? m.createdAt : 0,\n updatedAt: typeof m.updatedAt === \"number\" ? m.updatedAt : 0,\n contentHash: typeof m.contentHash === \"string\" ? m.contentHash : \"\"\n };\n if (typeof m.formattedText === \"string\") {\n message.formattedText = m.formattedText;\n }\n if (typeof m.platformMessageId === \"string\") {\n message.platformMessageId = m.platformMessageId;\n } else if (m.platformMessageId === null) {\n message.platformMessageId = null;\n }\n return message;\n}\n\nfunction coerceRole(value: unknown): MessageRole {\n return value === \"assistant\" || value === \"user\" || value === \"system\" || value === \"unknown\"\n ? value\n : \"unknown\";\n}\n\nfunction tally(report: IngestReport, result: UpsertResult): void {\n if (result.outcome === \"created\") {\n report.created += 1;\n } else if (result.outcome === \"updated\") {\n report.updated += 1;\n } else {\n report.skipped += 1;\n }\n}\n\nfunction accumulate(total: IngestReport, report: IngestReport): void {\n total.conversations += report.conversations;\n total.created += report.created;\n total.updated += report.updated;\n total.skipped += report.skipped;\n total.messages += report.messages;\n total.malformedLines += report.malformedLines;\n}\n","import { promises as fs, chmodSync, closeSync, openSync } from \"node:fs\";\n// node:sqlite rather than better-sqlite3: this package is installed with a bare\n// `npx @llmnesia/mcp install`, and a native addon that has to fall back to\n// node-gyp on any platform without a prebuild is the single most likely way\n// that install fails for someone who will never debug it. Node's built-in\n// SQLite ships with the runtime, so there is nothing to compile. It is\n// unflagged from Node 22.13 (see engines.node in package.json).\nimport { createRequire } from \"node:module\";\nimport \"./sqliteWarning.js\";\nimport type { DatabaseSync, StatementSync } from \"node:sqlite\";\nimport type { CorpusConversation, MessageRole } from \"@llmnesia/shared-types\";\nimport type { Corpus } from \"./corpus.js\";\nimport type { Hit, IndexSink, MatchMode, Retriever, SearchFilters } from \"./retriever.js\";\n\n// Loaded through require() at first use rather than a static ESM import, so\n// that ./sqliteWarning.js has already run. A static `import ... from\n// \"node:sqlite\"` is linked before any module body executes, which means Node\n// emits its ExperimentalWarning before any filter we install could exist.\nconst nodeRequire = createRequire(import.meta.url);\nlet sqlite: typeof import(\"node:sqlite\") | undefined;\nfunction loadSqlite(): typeof import(\"node:sqlite\") {\n return (sqlite ??= nodeRequire(\"node:sqlite\") as typeof import(\"node:sqlite\"));\n}\n\n/** Owner-only; see the note in the SearchIndex constructor. */\nconst DB_FILE_MODE = 0o600;\n\nfunction hardenDbFile(dbPath: string): void {\n try {\n // Creates the file with DB_FILE_MODE if absent; mode is ignored when the\n // file already exists, hence the explicit chmod for pre-existing indexes.\n closeSync(openSync(dbPath, \"a\", DB_FILE_MODE));\n chmodSync(dbPath, DB_FILE_MODE);\n } catch {\n // Best effort: a corpus on a filesystem without POSIX modes still works.\n }\n}\n\n/**\n * Bumped when the index schema or tokenizer changes in a way that requires a\n * rebuild. On open, a stored version mismatch triggers auto-rebuild from the\n * corpus — the index is always derivable and disposable.\n */\nexport const INDEX_FORMAT_VERSION = 1;\n\n/** Synthetic msgIndex used for the per-conversation title/preview FTS row. */\nconst TITLE_ROW_INDEX = -1;\nconst TITLE_ROLE = \"title\";\nconst DEFAULT_LIMIT = 10;\nconst SNIPPET_TOKENS = 12;\n\nconst SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS index_meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS conversations (\n docId TEXT PRIMARY KEY,\n platform TEXT NOT NULL,\n title TEXT NOT NULL,\n url TEXT NOT NULL,\n createdAt INTEGER NOT NULL,\n updatedAt INTEGER NOT NULL,\n messageCount INTEGER NOT NULL,\n preview TEXT NOT NULL,\n pinned INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_conversations_platform ON conversations(platform);\nCREATE INDEX IF NOT EXISTS idx_conversations_createdAt ON conversations(createdAt);\n\nCREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(\n docId UNINDEXED,\n msgIndex UNINDEXED,\n role UNINDEXED,\n text,\n tokenize = 'porter unicode61'\n);\n`;\n\ntype ConversationMetaRow = {\n docId: string;\n platform: string;\n title: string;\n url: string;\n createdAt: number;\n updatedAt: number;\n messageCount: number;\n preview: string;\n pinned: number;\n};\n\ntype SearchRow = ConversationMetaRow & {\n msgIndex: number;\n role: string;\n snippet: string;\n bm25: number;\n};\n\n/** Conversation metadata (no snippet), used by list_recent. */\nexport type RecentConversation = Omit<ConversationMetaRow, \"pinned\"> & { pinned: boolean };\n\n/**\n * The derived search index (SQLite FTS5 + BM25). Owns the DB connection and\n * implements both the write side ({@link IndexSink}) and read side\n * ({@link Retriever}). One FTS row per message, plus a synthetic title/preview\n * row per conversation so title matches surface. Filters resolve against the\n * plain `conversations` table.\n */\nexport class SearchIndex implements Retriever, IndexSink {\n private readonly db: DatabaseSync;\n private readonly stmts: {\n deleteFts: StatementSync;\n insertFts: StatementSync;\n deleteConversation: StatementSync;\n insertConversation: StatementSync;\n };\n\n constructor(dbPath: string) {\n // The FTS index holds every word of every message, so it is the most\n // sensitive file in the corpus. SQLite has no mode option and would create\n // it 0644, so pre-create it owner-only: chmod-ing after the fact would\n // leave a world-readable window. SQLite stamps -wal/-shm from the database\n // file's own mode.\n hardenDbFile(dbPath);\n this.db = new (loadSqlite().DatabaseSync)(dbPath);\n this.db.exec(\"PRAGMA journal_mode = WAL\");\n this.db.exec(\"PRAGMA foreign_keys = OFF\");\n this.db.exec(SCHEMA_SQL);\n this.setMeta(\"format_version\", String(INDEX_FORMAT_VERSION));\n\n this.stmts = {\n deleteFts: this.db.prepare(\"DELETE FROM messages_fts WHERE docId = ?\"),\n insertFts: this.db.prepare(\n \"INSERT INTO messages_fts (docId, msgIndex, role, text) VALUES (?, ?, ?, ?)\"\n ),\n deleteConversation: this.db.prepare(\"DELETE FROM conversations WHERE docId = ?\"),\n insertConversation: this.db.prepare(\n `INSERT INTO conversations\n (docId, platform, title, url, createdAt, updatedAt, messageCount, preview, pinned)\n VALUES (@docId, @platform, @title, @url, @createdAt, @updatedAt, @messageCount, @preview, @pinned)`\n )\n };\n }\n\n /** True when the stored format version is missing or stale — caller rebuilds. */\n needsRebuild(): boolean {\n const stored = this.getMeta(\"format_version\");\n return stored !== String(INDEX_FORMAT_VERSION);\n }\n\n /** Number of indexed conversations. */\n count(): number {\n const row = this.db.prepare(\"SELECT COUNT(*) AS n FROM conversations\").get() as { n: number };\n return row.n;\n }\n\n /** Most-recently-updated conversations (metadata only), newest first. */\n listRecent(n: number): RecentConversation[] {\n const limit = Math.max(1, n);\n const rows = this.db\n .prepare(\n `SELECT docId, platform, title, url, createdAt, updatedAt, messageCount, preview, pinned\n FROM conversations ORDER BY updatedAt DESC, createdAt DESC LIMIT ?`\n )\n .all(limit) as ConversationMetaRow[];\n return rows.map((r) => ({ ...r, pinned: r.pinned === 1 }));\n }\n\n /**\n * Run `fn` inside a transaction, rolling back if it throws. node:sqlite has\n * no transaction() wrapper, so this is the one place BEGIN/COMMIT/ROLLBACK\n * lives for synchronous writes; {@link rebuildFrom} spells it out separately\n * because its body is async and cannot be expressed as a sync callback.\n */\n private transaction<T>(fn: () => T): T {\n this.db.exec(\"BEGIN\");\n try {\n const result = fn();\n this.db.exec(\"COMMIT\");\n return result;\n } catch (err) {\n this.db.exec(\"ROLLBACK\");\n throw err;\n }\n }\n\n upsertConversation(record: CorpusConversation): void {\n this.transaction(() => this.writeRecord(record));\n }\n\n removeConversation(docId: string): void {\n this.transaction(() => {\n this.stmts.deleteFts.run(docId);\n this.stmts.deleteConversation.run(docId);\n });\n }\n\n /**\n * Clear the index and repopulate it from the corpus — proving the corpus is\n * the source of truth. Uses one manual transaction spanning the async file\n * reads, which the sync {@link transaction} helper cannot hold.\n */\n async rebuildFrom(corpus: Corpus): Promise<{ conversations: number }> {\n this.db.exec(\"DELETE FROM messages_fts; DELETE FROM conversations;\");\n let conversations = 0;\n this.db.exec(\"BEGIN\");\n try {\n for await (const record of corpus.iterate()) {\n this.writeRecord(record);\n conversations += 1;\n }\n this.db.exec(\"COMMIT\");\n } catch (err) {\n this.db.exec(\"ROLLBACK\");\n throw err;\n }\n return { conversations };\n }\n\n // Replace a conversation's index rows. Caller supplies the transaction (the\n // transaction() helper for single upserts, or rebuildFrom's manual one).\n private writeRecord(rec: CorpusConversation): void {\n const { conversation, messages } = rec;\n this.stmts.deleteFts.run(conversation.docId);\n this.stmts.deleteConversation.run(conversation.docId);\n\n // Synthetic title/preview row so title matches surface as hits.\n const titleText = [conversation.title, conversation.preview].filter(Boolean).join(\"\\n\");\n if (titleText.trim()) {\n this.stmts.insertFts.run(conversation.docId, TITLE_ROW_INDEX, TITLE_ROLE, titleText);\n }\n for (const message of messages) {\n if (message.text.trim()) {\n this.stmts.insertFts.run(conversation.docId, message.msgIndex, message.role, message.text);\n }\n }\n\n this.stmts.insertConversation.run({\n docId: conversation.docId,\n platform: conversation.platform,\n title: conversation.title,\n url: conversation.url,\n createdAt: conversation.createdAt,\n updatedAt: conversation.updatedAt ?? conversation.contentUpdatedAt ?? 0,\n messageCount: conversation.messageCount ?? messages.length,\n preview: conversation.preview ?? \"\",\n pinned: conversation.pinned ? 1 : 0\n });\n }\n\n search(query: string, filters: SearchFilters = {}): Hit[] {\n const match = toMatchQuery(query, filters.matchMode);\n if (!match) {\n return [];\n }\n const limit = Math.max(1, filters.limit ?? DEFAULT_LIMIT);\n\n const clauses: string[] = [\"messages_fts MATCH @match\"];\n const params: Record<string, unknown> = { match };\n if (filters.platform) {\n clauses.push(\"c.platform = @platform\");\n params.platform = filters.platform;\n }\n if (filters.title) {\n clauses.push(\"c.title LIKE @title ESCAPE '\\\\'\");\n params.title = `%${escapeLike(filters.title)}%`;\n }\n if (typeof filters.dateFrom === \"number\") {\n clauses.push(\"c.createdAt >= @dateFrom\");\n params.dateFrom = filters.dateFrom;\n }\n if (typeof filters.dateTo === \"number\") {\n clauses.push(\"c.createdAt <= @dateTo\");\n params.dateTo = filters.dateTo;\n }\n\n // Fetch matched turns ordered by BM25, then group by conversation in JS,\n // keeping the best (first-seen) snippet per conversation. Over-fetch so a\n // conversation with many matching turns can't crowd others out of `limit`.\n const scanCap = Math.min(2000, Math.max(200, limit * 40));\n const sql = `\n SELECT\n c.docId, c.platform, c.title, c.url, c.createdAt, c.updatedAt,\n c.messageCount, c.preview, c.pinned,\n m.msgIndex AS msgIndex, m.role AS role,\n snippet(messages_fts, 3, '[', ']', '…', ${SNIPPET_TOKENS}) AS snippet,\n bm25(messages_fts) AS bm25\n FROM messages_fts m\n JOIN conversations c ON c.docId = m.docId\n WHERE ${clauses.join(\" AND \")}\n ORDER BY bm25\n LIMIT @scanCap\n `;\n const rows = this.db.prepare(sql).all({ ...params, scanCap }) as SearchRow[];\n\n const seen = new Set<string>();\n const hits: Hit[] = [];\n for (const row of rows) {\n if (seen.has(row.docId)) {\n continue;\n }\n seen.add(row.docId);\n hits.push({\n docId: row.docId,\n platform: row.platform,\n title: row.title,\n url: row.url,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n messageCount: row.messageCount,\n preview: row.preview,\n pinned: row.pinned === 1,\n // BM25 is negative (more negative = better); expose a positive relevance.\n score: -row.bm25,\n snippet: row.snippet,\n matchRole: normalizeMatchRole(row.role),\n matchMsgIndex: row.msgIndex\n });\n if (hits.length >= limit) {\n break;\n }\n }\n return hits;\n }\n\n close(): void {\n this.db.close();\n }\n\n private getMeta(key: string): string | null {\n const row = this.db.prepare(\"SELECT value FROM index_meta WHERE key = ?\").get(key) as\n | { value: string }\n | undefined;\n return row ? row.value : null;\n }\n\n private setMeta(key: string, value: string): void {\n this.db\n .prepare(\"INSERT INTO index_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value\")\n .run(key, value);\n }\n}\n\n/**\n * Open the index for a corpus, rebuilding from disk when it is missing, empty,\n * or its format version is stale. Returns a ready-to-query SearchIndex.\n */\nexport async function openSearchIndex(\n corpus: Corpus,\n options: { autoBuild?: boolean } = {}\n): Promise<SearchIndex> {\n await corpus.init();\n const index = new SearchIndex(corpus.paths.indexDbFile);\n if (options.autoBuild !== false && (index.needsRebuild() || index.count() === 0)) {\n await index.rebuildFrom(corpus);\n }\n return index;\n}\n\n/** Delete the on-disk index database (and its WAL sidecars). */\nexport async function deleteIndexDb(dbFile: string): Promise<void> {\n for (const suffix of [\"\", \"-wal\", \"-shm\"]) {\n await fs.rm(`${dbFile}${suffix}`, { force: true });\n }\n}\n\n/**\n * Turn a free-text user query into a safe FTS5 MATCH expression: split on\n * every run of non-letter-non-digit characters, quote each surviving term\n * (phrase-safe, so no stray FTS syntax char reaches the query), then combine\n * them per `mode`.\n *\n * `any` ORs the terms so partial matches still surface while BM25 ranks fuller\n * matches higher. That is the right default for exploratory search but the\n * wrong tool for \"did I ever ask X\" — every stopword in the query is a live\n * term, so a query like \"how many times have i asked about context length\"\n * admits any conversation containing merely \"context\", and the caller cannot\n * tell a real hit from a stopword hit. `all` and `phrase` exist for that case.\n *\n * `phrase` emits a single FTS5 phrase (`\"a b c\"`), which matches only adjacent\n * tokens in order. Note this runs against `porter`-stemmed tokens, so it\n * matches inflections (\"asked\" finds \"asking\") but not reorderings.\n *\n * Splitting rather than stripping matters: `messages_fts` tokenizes with\n * `unicode61`, whose default separator set is exactly \"not a letter or digit\"\n * — the same boundary this regex uses. The previous version stripped a\n * curated blacklist of characters (quotes, `*`, parens, `:`, `^`, `-`) from\n * each whitespace-delimited token without splitting on them, so a hyphenated\n * single-word query like `xi02-verify-080343453` fused into the query term\n * `xi02verify080343453` — a token that exists nowhere in the index, since\n * the tokenizer had already split the indexed text into `xi02`, `verify`,\n * `080343453` separately. The query silently returned zero results for\n * content that was correctly indexed and present. Any hyphenated real-world\n * term (`gpt-4`, `multi-stage`, a compound noun) hit the same bug; it went\n * unnoticed here because every fixture query paired the hyphenated word with\n * several unhyphenated ones in the same OR, so the other terms carried the\n * match and masked the dead one.\n */\nfunction toMatchQuery(query: string, mode: MatchMode = \"any\"): string | null {\n const terms = query.split(/[^\\p{L}\\p{N}]+/u).filter((t) => t.length > 0);\n if (terms.length === 0) {\n return null;\n }\n if (mode === \"phrase\") {\n // One quoted phrase, not per-term quotes: FTS5 reads the space-separated\n // tokens inside a single pair of quotes as an adjacency constraint.\n return `\"${terms.join(\" \")}\"`;\n }\n return terms.map((t) => `\"${t}\"`).join(mode === \"all\" ? \" AND \" : \" OR \");\n}\n\nfunction escapeLike(value: string): string {\n return value.replace(/[\\\\%_]/g, \"\\\\$&\");\n}\n\nfunction normalizeMatchRole(role: string): MessageRole | \"title\" {\n if (role === \"title\") {\n return \"title\";\n }\n return role === \"assistant\" || role === \"user\" || role === \"system\" ? role : \"unknown\";\n}\n","// Node prints `ExperimentalWarning: SQLite is an experimental feature` the\n// first time node:sqlite is used. For an MCP stdio server, stderr *is* the\n// client's log file, so that line shows up in Claude Desktop / Cursor logs\n// looking like a fault in LLMnesia rather than a note about the Node runtime.\n//\n// Node's own stderr printer is itself a \"warning\" listener, so we can lift it\n// off, drop exactly this one warning, and re-dispatch everything else to the\n// original listeners untouched. Deliberately narrow: any other warning (a real\n// deprecation, an unhandled rejection notice) still prints as normal.\n//\n// Safe to run late: process.emitWarning() defers to the next tick, so a filter\n// installed anywhere in synchronous module evaluation is in place before the\n// warning is actually delivered.\n\nconst INSTALLED = Symbol.for(\"llmnesia.sqliteWarningFilterInstalled\");\n\ntype WarningListener = (warning: Error) => void;\ntype Flagged = typeof globalThis & { [INSTALLED]?: boolean };\n\nfunction isSqliteExperimentalWarning(warning: Error): boolean {\n return warning.name === \"ExperimentalWarning\" && /\\bSQLite\\b/i.test(warning.message);\n}\n\nfunction install(): void {\n const flagged = globalThis as Flagged;\n if (flagged[INSTALLED]) {\n return;\n }\n flagged[INSTALLED] = true;\n\n const previous = process.listeners(\"warning\") as WarningListener[];\n process.removeAllListeners(\"warning\");\n process.on(\"warning\", (warning: Error) => {\n if (isSqliteExperimentalWarning(warning)) {\n return;\n }\n for (const listener of previous) {\n listener.call(process, warning);\n }\n });\n}\n\ninstall();\n","// The published package version, injected at build time by tsup (see\n// `define` in tsup.config.ts). It is baked in rather than read from\n// package.json at runtime because `install` copies only `dist/` into\n// ~/.llmnesia/bin — there is no package.json next to the running code there.\ndeclare const __PKG_VERSION__: string | undefined;\n\nexport const VERSION: string = typeof __PKG_VERSION__ === \"string\" ? __PKG_VERSION__ : \"0.0.0-dev\";\n"],"mappings":"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","names":["join","join","join","fs","join","fs","fs"]}
|