imsg-mcp 1.3.0 → 1.3.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/CHANGELOG.md +17 -0
- package/dist/cli.js +7 -7
- package/dist/cli.js.map +1 -1
- package/dist/{exportFormats-CWWiy5uz.js → exportFormats-wytSyBTp.js} +6 -3
- package/dist/exportFormats-wytSyBTp.js.map +1 -0
- package/dist/{exportStream-B86sO7Mg.js → exportStream-BCoZYUUZ.js} +2 -2
- package/dist/{exportStream-B86sO7Mg.js.map → exportStream-BCoZYUUZ.js.map} +1 -1
- package/dist/{imessage-db-C7w6mu1Q.js → imessage-db-Cz8MIIpG.js} +7 -2
- package/dist/imessage-db-Cz8MIIpG.js.map +1 -0
- package/dist/index.js +27 -11
- package/dist/index.js.map +1 -1
- package/dist/{meta-D3NoTAjA.js → meta-DzE7CSrZ.js} +2 -2
- package/dist/meta-DzE7CSrZ.js.map +1 -0
- package/dist/{setup-B2HejfMT.js → setup-BuXuKAHA.js} +2 -2
- package/dist/{setup-B2HejfMT.js.map → setup-BuXuKAHA.js.map} +1 -1
- package/dist/{shutdown-CikCSZru.js → shutdown-pPAPSnk3.js} +40 -45
- package/dist/shutdown-pPAPSnk3.js.map +1 -0
- package/dist/tui.js +16 -12
- package/dist/tui.js.map +1 -1
- package/dist/{watchdog-CnbBZnfg.js → watchdog-Cbgm050b.js} +2 -2
- package/dist/{watchdog-CnbBZnfg.js.map → watchdog-Cbgm050b.js.map} +1 -1
- package/package.json +3 -2
- package/dist/exportFormats-CWWiy5uz.js.map +0 -1
- package/dist/imessage-db-C7w6mu1Q.js.map +0 -1
- package/dist/meta-D3NoTAjA.js.map +0 -1
- package/dist/shutdown-CikCSZru.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and follows [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [1.3.2](https://github.com/george43g/imsg-mcp/compare/v1.3.1...v1.3.2) (2026-07-12)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **send:** route on the thread's real service — AppleScript can't detect wrong-service sends ([ff30b7a](https://github.com/george43g/imsg-mcp/commit/ff30b7a9a5fa2837a0e20278d61ba20f27db1780))
|
|
12
|
+
|
|
13
|
+
## [1.3.1](https://github.com/george43g/imsg-mcp/compare/v1.3.0...v1.3.1) (2026-07-12)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **cli:** report the real version — inject from package.json at build ([f2d2245](https://github.com/george43g/imsg-mcp/commit/f2d2245ef0e204b25c8d91375bc58656926a3d26))
|
|
19
|
+
* **export:** deterministic key order in NDJSON/JSON message lines ([43cf729](https://github.com/george43g/imsg-mcp/commit/43cf729aad3a6651768f2d1511b06485f48b913f))
|
|
20
|
+
* **release:** pack .mcpb after version bump; sync manifest.json version ([c93c503](https://github.com/george43g/imsg-mcp/commit/c93c5031f9112ed122b04ff508b27bd7d1f19700))
|
|
21
|
+
* **tui:** paint dev-stats immediately when the panel opens ([734257c](https://github.com/george43g/imsg-mcp/commit/734257caa3bedc4ca4a49f354295cfbf61a84de5))
|
|
22
|
+
|
|
6
23
|
# [1.3.0](https://github.com/george43g/imsg-mcp/compare/v1.2.0...v1.3.0) (2026-07-12)
|
|
7
24
|
|
|
8
25
|
|
package/dist/cli.js
CHANGED
|
@@ -3,10 +3,10 @@ import { existsSync, realpathSync } from "node:fs";
|
|
|
3
3
|
import { createInterface } from "node:readline";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import { c as checkLocalAccess, f as formatAccessReport, q as installShutdownHandlers, r as registerCleanup, E as looksLikeThreadSlug } from "./shutdown-
|
|
6
|
+
import { c as checkLocalAccess, f as formatAccessReport, q as installShutdownHandlers, r as registerCleanup, E as looksLikeThreadSlug } from "./shutdown-pPAPSnk3.js";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import { join, dirname } from "node:path";
|
|
9
|
-
import { A as APP_VERSION } from "./meta-
|
|
9
|
+
import { A as APP_VERSION } from "./meta-DzE7CSrZ.js";
|
|
10
10
|
function distRoot() {
|
|
11
11
|
return dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
}
|
|
@@ -58,7 +58,7 @@ class LocalMcpClient {
|
|
|
58
58
|
const response = await this.call("initialize", {
|
|
59
59
|
protocolVersion: "2024-11-05",
|
|
60
60
|
capabilities: {},
|
|
61
|
-
clientInfo: { name: "imsg", version:
|
|
61
|
+
clientInfo: { name: "imsg", version: APP_VERSION }
|
|
62
62
|
});
|
|
63
63
|
if (response.result) {
|
|
64
64
|
this.proc.stdin.write(
|
|
@@ -357,9 +357,9 @@ async function runExportCommand(target, opts) {
|
|
|
357
357
|
const { existsSync: existsSync2, mkdirSync, copyFileSync, statSync } = await import("node:fs");
|
|
358
358
|
const { homedir } = await import("node:os");
|
|
359
359
|
const { dirname: dirname2, join: join2, isAbsolute, resolve } = await import("node:path");
|
|
360
|
-
const { getContactsDbPaths, getImsgDbPath, getSlugsDbPath } = await import("./shutdown-
|
|
361
|
-
const { IMessageDB } = await import("./imessage-db-
|
|
362
|
-
const { streamExport } = await import("./exportStream-
|
|
360
|
+
const { getContactsDbPaths, getImsgDbPath, getSlugsDbPath } = await import("./shutdown-pPAPSnk3.js").then((n) => n.V);
|
|
361
|
+
const { IMessageDB } = await import("./imessage-db-Cz8MIIpG.js").then((n) => n.i);
|
|
362
|
+
const { streamExport } = await import("./exportStream-BCoZYUUZ.js");
|
|
363
363
|
const { parseUserDate } = await import("./dateParse-DJXMfq3a.js");
|
|
364
364
|
const format = normalizeFormat(opts.format ?? "md");
|
|
365
365
|
const ext = extForFormat(format);
|
|
@@ -557,7 +557,7 @@ program.command("tui").description("Launch the read-only terminal UI").option("-
|
|
|
557
557
|
});
|
|
558
558
|
program.command("export <target>").description("Export a conversation to a file (md/csv/json/ndjson)").option("-f, --format <fmt>", "Output format: md (default), csv, json, ndjson", "md").option("--since <date>", "Earliest date (ISO or relative, e.g. '3 months ago')").option("--until <date>", "Latest date (ISO or relative)").option("-o, --output <path>", "Output path (default: ~/imsg-export-<target>-<YYYY-MM-DD>.<ext>)").option("--include-attachments", "Copy attachments next to the export").option("--attachments-dir <path>", "Where to copy attachments (default: <output>.attachments/)").option("--page-size <n>", "Messages per DB page (100-5000)", "1000").action(runExportCommand);
|
|
559
559
|
program.command("setup").description("Autodetect DB paths and emit an MCP host config snippet").option("-w, --write <host>", 'Write into a host config: "claude" or "cursor"').option("-r, --runtime <runtime>", 'Runtime command: "npx" (default), "bunx", or "global"').option("--print-only", "Just print the snippet (default behaviour)").action(async (opts) => {
|
|
560
|
-
const { probeMachine, buildMcpSnippet, writeHostConfig } = await import("./setup-
|
|
560
|
+
const { probeMachine, buildMcpSnippet, writeHostConfig } = await import("./setup-BuXuKAHA.js");
|
|
561
561
|
const report = probeMachine();
|
|
562
562
|
if (!report.imsgDb.readable) {
|
|
563
563
|
log(`✗ Messages DB is not readable: ${report.imsgDb.path}`, "err");
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sources":["../src/mcp-client.ts","../src/cli.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport interface ToolCallResult {\n content?: { type: string; text?: string }[];\n isError?: boolean;\n}\n\nfunction distRoot(): string {\n return dirname(fileURLToPath(import.meta.url));\n}\n\nexport class LocalMcpClient {\n private proc: ReturnType<typeof spawn>;\n private requestId = 0;\n private pending = new Map<\n number,\n { resolve: (value: unknown) => void; reject: (error: Error) => void }\n >();\n private outBuf = \"\";\n private closed = false;\n\n constructor(private readonly onStderr?: (line: string) => void) {\n const entry = join(distRoot(), \"cli.js\");\n if (!existsSync(entry)) {\n throw new Error(\"dist/cli.js not found. Run `pnpm build` first.\");\n }\n\n this.proc = spawn(process.execPath, [entry, \"mcp\"], {\n cwd: process.cwd(),\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n this.proc.stderr!.on(\"data\", (chunk: Buffer) => {\n this.onStderr?.(chunk.toString(\"utf8\"));\n });\n\n this.proc.stdout!.on(\"data\", (chunk: Buffer) => {\n this.outBuf += chunk.toString(\"utf8\");\n const lines = this.outBuf.split(\"\\n\");\n this.outBuf = lines.pop() ?? \"\";\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const message = JSON.parse(line) as { id?: number };\n if (message.id != null && this.pending.has(message.id)) {\n const pending = this.pending.get(message.id);\n this.pending.delete(message.id);\n pending?.resolve(message);\n }\n } catch {\n // Ignore non-JSON lines on stdout.\n }\n }\n });\n\n // Handle unexpected child exit — reject all pending requests\n this.proc.on(\"exit\", (code) => {\n if (this.closed) return;\n for (const [, p] of this.pending) {\n p.reject(new Error(`MCP child exited unexpectedly with code ${code}`));\n }\n this.pending.clear();\n });\n }\n\n async start(): Promise<void> {\n const response = (await this.call(\"initialize\", {\n protocolVersion: \"2024-11-05\",\n capabilities: {},\n clientInfo: { name: \"imsg\", version: \"1.0.0\" },\n })) as { result?: unknown };\n\n if (response.result) {\n this.proc.stdin!.write(\n `${JSON.stringify({ jsonrpc: \"2.0\", method: \"notifications/initialized\", params: {} })}\\n`,\n );\n }\n }\n\n async callTool(name: string, args: object, timeoutMs = 30_000): Promise<ToolCallResult> {\n const response = (await this.call(\"tools/call\", { name, arguments: args }, timeoutMs)) as {\n result?: ToolCallResult;\n error?: { message: string };\n };\n if (response.error) {\n throw new Error(response.error.message);\n }\n return response.result ?? {};\n }\n\n async listTools(timeoutMs = 15_000): Promise<{ name: string; description?: string }[]> {\n const response = (await this.call(\"tools/list\", {}, timeoutMs)) as {\n result?: { tools?: { name: string; description?: string }[] };\n error?: { message: string };\n };\n if (response.error) {\n throw new Error(response.error.message);\n }\n return response.result?.tools ?? [];\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n\n // Close stdin to signal the child\n this.proc.stdin!.end();\n\n // Give child 2s to exit gracefully, then force kill\n const killTimer = setTimeout(() => {\n if (!this.proc.killed) {\n this.proc.kill(\"SIGKILL\");\n }\n }, 2000);\n killTimer.unref();\n\n this.proc.on(\"exit\", () => clearTimeout(killTimer));\n this.proc.kill(\"SIGTERM\");\n\n // Reject any remaining pending requests\n for (const [, p] of this.pending) {\n p.reject(new Error(\"MCP client closed\"));\n }\n this.pending.clear();\n }\n\n private call(method: string, params: object, timeoutMs = 15_000): Promise<unknown> {\n return new Promise((resolve, reject) => {\n if (this.closed) {\n reject(new Error(\"MCP client is closed\"));\n return;\n }\n\n const id = ++this.requestId;\n this.pending.set(id, { resolve, reject });\n this.proc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`);\n\n const timer = setTimeout(() => {\n if (!this.pending.has(id)) return;\n this.pending.delete(id);\n reject(new Error(`Request timeout after ${timeoutMs}ms.`));\n }, timeoutMs);\n timer.unref(); // Don't prevent process exit\n\n const original = this.pending.get(id);\n if (!original) return;\n this.pending.set(id, {\n resolve: (value) => {\n clearTimeout(timer);\n original.resolve(value);\n },\n reject: (error) => {\n clearTimeout(timer);\n original.reject(error);\n },\n });\n });\n }\n}\n","import { realpathSync } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport { pathToFileURL } from \"node:url\";\nimport { Command } from \"commander\";\nimport { checkLocalAccess, formatAccessReport } from \"./access-check.js\";\nimport { LocalMcpClient } from \"./mcp-client.js\";\nimport { APP_VERSION } from \"./meta.js\";\nimport { installShutdownHandlers, registerCleanup } from \"./shutdown.js\";\nimport { looksLikeThreadSlug } from \"./thread-slug.js\";\n\n// ── Colour helpers ─────────────────────────────────────────────────────\n\nconst color = {\n dim: (v: string) => `\\x1b[2m${v}\\x1b[0m`,\n cyan: (v: string) => `\\x1b[36m${v}\\x1b[0m`,\n green: (v: string) => `\\x1b[32m${v}\\x1b[0m`,\n yellow: (v: string) => `\\x1b[33m${v}\\x1b[0m`,\n red: (v: string) => `\\x1b[31m${v}\\x1b[0m`,\n};\n\nfunction log(message: string, style: \"dim\" | \"ok\" | \"warn\" | \"err\" = \"dim\") {\n const fn =\n style === \"ok\"\n ? color.green\n : style === \"warn\"\n ? color.yellow\n : style === \"err\"\n ? color.red\n : color.dim;\n console.log(fn(message));\n}\n\n// ── MCP client helpers ─────────────────────────────────────────────────\n\nasync function withClient<T>(run: (client: LocalMcpClient) => Promise<T>): Promise<T> {\n const client = new LocalMcpClient((line) => process.stderr.write(color.dim(`[server] ${line}`)));\n try {\n await client.start();\n return await run(client);\n } finally {\n client.close();\n }\n}\n\nasync function printToolResult(\n client: LocalMcpClient,\n name: string,\n args: object,\n timeoutMs?: number,\n) {\n const result = await client.callTool(name, args, timeoutMs);\n const text = result.content?.[0]?.text ?? JSON.stringify(result, null, 2);\n if (result.isError) throw new Error(text);\n console.log(text);\n}\n\n// ── Interactive console ────────────────────────────────────────────────\n\nfunction parseConsoleInput(line: string): { cmd: string; args: string[] } {\n const parts: string[] = [];\n let current = \"\";\n let quote: string | null = null;\n\n for (const char of line) {\n if ((char === '\"' || char === \"'\") && quote == null) {\n quote = char;\n continue;\n }\n if (char === quote) {\n quote = null;\n continue;\n }\n if (char === \" \" && quote == null) {\n if (current) {\n parts.push(current);\n current = \"\";\n }\n continue;\n }\n current += char;\n }\n if (current) parts.push(current);\n return { cmd: parts[0]?.toLowerCase() ?? \"\", args: parts.slice(1) };\n}\n\nasync function runConsoleCommand(\n cmd: string,\n args: string[],\n client: LocalMcpClient,\n): Promise<void> {\n switch (cmd) {\n case \"conversations\":\n case \"list\":\n await printToolResult(client, \"list_conversations\", { limit: Number(args[0] ?? 20) });\n return;\n case \"messages\":\n case \"msg\": {\n const first = args[0];\n const firstIsLimit = first != null && /^\\d+$/.test(first);\n const chatIdentifier = firstIsLimit ? undefined : first;\n const limit = Number(firstIsLimit ? first : (args[1] ?? 20));\n await printToolResult(client, \"get_messages\", {\n ...(chatIdentifier\n ? looksLikeThreadSlug(chatIdentifier)\n ? { threadSlug: chatIdentifier }\n : { chatIdentifier }\n : {}),\n limit,\n });\n return;\n }\n case \"unread\":\n await printToolResult(\n client,\n \"get_unread_messages\",\n args[0] ? { limit: Number(args[0]) } : {},\n );\n return;\n case \"search\":\n if (!args[0]) throw new Error(\"Usage: search <query> [limit]\");\n await printToolResult(client, \"search_messages\", {\n query: args[0],\n limit: Number(args[1] ?? 20),\n });\n return;\n case \"wait\":\n if (!args[0]) throw new Error(\"Usage: wait <chat> [timeoutSeconds]\");\n await printToolResult(\n client,\n \"wait_for_reply\",\n {\n ...(looksLikeThreadSlug(args[0]) ? { threadSlug: args[0] } : { chatIdentifier: args[0] }),\n timeoutSeconds: Number(args[1] ?? 60),\n pollIntervalSeconds: 5,\n },\n 70_000,\n );\n return;\n case \"send\":\n if (args.length < 2) throw new Error(\"Usage: send <target> <message>\");\n await printToolResult(client, \"send_message\", {\n ...(looksLikeThreadSlug(args[0]) ? { threadSlug: args[0] } : { recipient: args[0] }),\n message: args.slice(1).join(\" \"),\n });\n return;\n case \"contacts\": {\n const verb = args[0];\n if (verb === \"list\" || verb === undefined) {\n await printToolResult(client, \"list_contacts\", {\n limit: Number(args[1] ?? 20),\n offset: Number(args[2] ?? 0),\n });\n } else if (verb === \"search\") {\n if (!args[1]) throw new Error(\"Usage: contacts search <query> [limit]\");\n await printToolResult(client, \"search_contacts\", {\n query: args[1],\n limit: Number(args[2] ?? 20),\n });\n } else if (verb === \"resolve\") {\n if (!args[1]) throw new Error(\"Usage: contacts resolve <handle>\");\n await printToolResult(client, \"resolve_handle\", { handle: args[1] });\n } else if (verb === \"show\") {\n if (!args[1]) throw new Error(\"Usage: contacts show <handle-or-id>\");\n const params = /^\\d+$/.test(args[1]) ? { id: Number(args[1]) } : { handle: args[1] };\n await printToolResult(client, \"get_contact\", params);\n } else {\n throw new Error(`Unknown contacts verb: ${verb}. Use list|search|resolve|show.`);\n }\n return;\n }\n case \"logs\":\n await printToolResult(client, \"get_logs\", args[0] ? { tail: Number(args[0]) } : {});\n return;\n case \"last-error\":\n case \"lasterror\":\n await printToolResult(client, \"get_last_send_error\", {});\n return;\n case \"tools\":\n for (const tool of await client.listTools()) {\n console.log(`${tool.name}${tool.description ? ` - ${tool.description}` : \"\"}`);\n }\n return;\n case \"raw\":\n if (!args[0]) throw new Error(\"Usage: raw '<json>'\");\n {\n const parsed = JSON.parse(args.join(\" \")) as { name?: string; arguments?: object };\n if (!parsed.name)\n throw new Error('Expected JSON like {\"name\":\"tool_name\",\"arguments\":{...}}.');\n await printToolResult(client, parsed.name, parsed.arguments ?? {});\n }\n return;\n case \"tui\": {\n const { runTui } = await import(\"./tui/index.js\");\n await runTui();\n return;\n }\n case \"help\":\n case \"?\":\n console.log(CONSOLE_HELP);\n return;\n case \"quit\":\n case \"exit\":\n process.exit(0);\n return;\n default:\n throw new Error(`Unknown command: ${cmd}. Type \"help\" for available commands.`);\n }\n}\n\nconst CONSOLE_HELP = `\nAvailable commands:\n conversations [n] List recent conversations (default 20)\n messages [chat] [n] Show recent messages\n unread [n] Show unread messages\n search <query> [n] Search messages\n wait <chat> [secs] Wait for a reply (default 60s)\n send <target> <msg> Send a message\n contacts [verb] Contacts: list [n] [offset] | search <q> [n] | resolve <handle> | show <handle-or-id>\n logs [tail] Show server debug logs\n last-error Show last send failure\n tools List available MCP tools\n raw <json> Send raw JSON-RPC to tools/call\n tui Launch the read-only TUI\n help Show this help\n quit Exit\n`.trim();\n\nasync function runInteractiveConsole(): Promise<void> {\n log(\"Starting local MCP server...\", \"dim\");\n const client = new LocalMcpClient((line) => process.stderr.write(color.dim(`[server] ${line}`)));\n await client.start();\n log(\"Console ready.\\n\", \"ok\");\n // Show the full help text on launch so the user doesn't have to type `help`\n // to discover what's available.\n console.log(CONSOLE_HELP);\n console.log(\"\");\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n const prompt = () =>\n rl.question(color.cyan(\"imsg> \"), async (line) => {\n const trimmed = line.trim();\n if (!trimmed) {\n prompt();\n return;\n }\n\n const { cmd, args } = parseConsoleInput(trimmed);\n try {\n await runConsoleCommand(cmd, args, client);\n } catch (error) {\n log(error instanceof Error ? error.message : String(error), \"err\");\n }\n console.log(\"\");\n prompt();\n });\n\n prompt();\n\n installShutdownHandlers();\n registerCleanup(() => client.close());\n}\n\n// ── Export command ─────────────────────────────────────────────────────\n\ninterface ExportOpts {\n format?: string;\n since?: string;\n until?: string;\n output?: string;\n includeAttachments?: boolean;\n attachmentsDir?: string;\n pageSize?: string;\n}\n\nfunction normalizeFormat(raw: string): \"markdown\" | \"csv\" | \"json\" | \"ndjson\" {\n const v = raw.toLowerCase();\n if (v === \"md\" || v === \"markdown\") return \"markdown\";\n if (v === \"csv\") return \"csv\";\n if (v === \"json\") return \"json\";\n if (v === \"ndjson\") return \"ndjson\";\n throw new Error(`Unknown format: ${raw}. Expected md, csv, json, or ndjson.`);\n}\n\nfunction extForFormat(fmt: \"markdown\" | \"csv\" | \"json\" | \"ndjson\"): string {\n return fmt === \"markdown\" ? \"md\" : fmt;\n}\n\nfunction sanitizeForFilename(s: string): string {\n return s.replace(/[^A-Za-z0-9._~-]+/g, \"_\").replace(/^_+|_+$/g, \"\") || \"chat\";\n}\n\nexport async function runExportCommand(target: string, opts: ExportOpts): Promise<void> {\n const { existsSync, mkdirSync, copyFileSync, statSync } = await import(\"node:fs\");\n const { homedir } = await import(\"node:os\");\n const { dirname, join, isAbsolute, resolve } = await import(\"node:path\");\n const { getContactsDbPaths, getImsgDbPath, getSlugsDbPath } = await import(\"./config.js\");\n const { IMessageDB } = await import(\"./imessage-db.js\");\n const { streamExport } = await import(\"./exportStream.js\");\n const { parseUserDate } = await import(\"./tui/dateParse.js\");\n\n const format = normalizeFormat(opts.format ?? \"md\");\n const ext = extForFormat(format);\n\n const pageSize = Number(opts.pageSize ?? \"1000\");\n if (!Number.isFinite(pageSize) || pageSize < 100 || pageSize > 5000) {\n throw new Error(\"--page-size must be between 100 and 5000.\");\n }\n\n const since = opts.since ? parseUserDate(opts.since) : null;\n if (opts.since && !since) throw new Error(`Could not parse --since: ${opts.since}`);\n const until = opts.until ? parseUserDate(opts.until) : null;\n if (opts.until && !until) throw new Error(`Could not parse --until: ${opts.until}`);\n\n const db = new IMessageDB(getImsgDbPath(), getContactsDbPaths(), getSlugsDbPath());\n try {\n // Resolve target → chatIdentifier + display slug for filename\n let chatIdentifier = target;\n let displayHandle = target;\n if (looksLikeThreadSlug(target)) {\n const rec = db.getSlugRecord(target);\n if (!rec) throw new Error(`Unknown thread slug: ${target}`);\n chatIdentifier = rec.chatIdentifier;\n displayHandle = rec.slug;\n }\n\n // Default output path: ~/imsg-export-<sanitized>-<YYYY-MM-DD>.<ext>\n let outputPath: string;\n if (opts.output) {\n outputPath = isAbsolute(opts.output) ? opts.output : resolve(opts.output);\n } else {\n const today = new Date().toISOString().slice(0, 10);\n outputPath = join(\n homedir(),\n `imsg-export-${sanitizeForFilename(displayHandle)}-${today}.${ext}`,\n );\n }\n\n const parent = dirname(outputPath);\n if (!existsSync(parent)) mkdirSync(parent, { recursive: true });\n\n log(`Exporting ${displayHandle} → ${outputPath} (${format})`, \"dim\");\n\n const result = await streamExport({\n db,\n chatIdentifier,\n format,\n outputPath,\n since,\n until,\n pageSize,\n });\n\n let attachmentSummary = \"\";\n if (opts.includeAttachments) {\n const dir = opts.attachmentsDir\n ? isAbsolute(opts.attachmentsDir)\n ? opts.attachmentsDir\n : resolve(opts.attachmentsDir)\n : `${outputPath}.attachments`;\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n const filters: Parameters<typeof db.searchAttachments>[0] = {\n chatIdentifier,\n limit: 0,\n };\n if (since) filters.sinceMs = since.getTime();\n if (until) filters.untilMs = until.getTime();\n const attachments = db.searchAttachments(filters);\n\n let copied = 0;\n let totalBytes = 0;\n const seen = new Set<string>();\n for (const a of attachments) {\n if (!a.filename) continue;\n const src = a.filename.replace(/^~/, homedir());\n if (!existsSync(src)) continue;\n const baseName = a.transferName || src.split(\"/\").pop() || `att-${a.rowId}`;\n let destName = `${a.rowId}-${sanitizeForFilename(baseName)}`;\n if (seen.has(destName)) destName = `${a.rowId}-${Date.now()}-${baseName}`;\n seen.add(destName);\n const dest = join(dir, destName);\n try {\n copyFileSync(src, dest);\n copied++;\n totalBytes += statSync(dest).size;\n } catch (err) {\n log(\n ` warn: copy failed for ${src}: ${err instanceof Error ? err.message : String(err)}`,\n \"warn\",\n );\n }\n }\n attachmentSummary = `\\nAttachments: ${copied} file(s), ${(totalBytes / 1024).toFixed(1)} KB → ${dir}`;\n }\n\n log(\n [\n \"\",\n `✓ Exported ${result.count} message(s) to ${result.savedTo}`,\n ` Format: ${format}`,\n ` Range: ${result.oldest?.toISOString() ?? \"(none)\"} → ${result.newest?.toISOString() ?? \"(none)\"}`,\n ` Size: ${(result.sizeBytes / 1024).toFixed(1)} KB${attachmentSummary}`,\n ].join(\"\\n\"),\n \"ok\",\n );\n } finally {\n await db.close();\n }\n}\n\n// ── CLI program (Commander) ────────────────────────────────────────────\n\nconst program = new Command()\n .name(\"imsg\")\n .version(APP_VERSION, \"-v, --version\")\n .description(\"CLI for the imsg-mcp iMessage MCP server\")\n .addHelpText(\n \"after\",\n `\nNotes:\n Use \"imsg doctor\" on a new machine.\n Run the MCP stdio server with \"imsg mcp\".\n Launch the read-only TUI with \"imsg tui\".`,\n );\n\nprogram\n .command(\"mcp\")\n .description(\"Run the MCP stdio server\")\n .action(async () => {\n const { runMcpServer } = await import(\"./index.js\");\n await runMcpServer();\n });\n\nprogram\n .command(\"cli\")\n .alias(\"console\")\n .description(\"Launch interactive console\")\n .action(runInteractiveConsole);\n\nprogram\n .command(\"doctor\")\n .description(\"Check local permissions and database access\")\n .action(async () => {\n const report = await checkLocalAccess();\n console.log(formatAccessReport(report));\n process.exitCode = report.ok ? 0 : 1;\n });\n\nprogram\n .command(\"conversations\")\n .alias(\"list\")\n .description(\"List recent conversations\")\n .argument(\"[limit]\", \"Number of conversations\", \"20\")\n .action(async (limit: string) => {\n await withClient((c) => printToolResult(c, \"list_conversations\", { limit: Number(limit) }));\n });\n\nprogram\n .command(\"messages\")\n .alias(\"msg\")\n .description(\"Show recent messages from a conversation\")\n .argument(\"[chat]\", \"Phone number, email, or thread slug\")\n .argument(\"[limit]\", \"Number of messages\", \"20\")\n .action(async (chat: string | undefined, limit: string) => {\n const firstIsLimit = chat != null && /^\\d+$/.test(chat);\n const chatIdentifier = firstIsLimit ? undefined : chat;\n const finalLimit = Number(firstIsLimit ? chat : limit);\n await withClient((c) =>\n printToolResult(c, \"get_messages\", {\n ...(chatIdentifier\n ? looksLikeThreadSlug(chatIdentifier)\n ? { threadSlug: chatIdentifier }\n : { chatIdentifier }\n : {}),\n limit: finalLimit,\n }),\n );\n });\n\nprogram\n .command(\"unread\")\n .description(\"Show unread messages across all conversations\")\n .argument(\"[limit]\", \"Number of messages\", \"100\")\n .action(async (limit: string) => {\n await withClient((c) => printToolResult(c, \"get_unread_messages\", { limit: Number(limit) }));\n });\n\nprogram\n .command(\"search\")\n .description(\"Search messages by text content\")\n .argument(\"<query>\", \"Search query\")\n .argument(\"[limit]\", \"Number of results\", \"20\")\n .action(async (query: string, limit: string) => {\n await withClient((c) => printToolResult(c, \"search_messages\", { query, limit: Number(limit) }));\n });\n\nprogram\n .command(\"wait\")\n .description(\"Wait for a reply in a conversation\")\n .argument(\"<chat>\", \"Phone number, email, or thread slug\")\n .argument(\"[timeout]\", \"Timeout in seconds\", \"60\")\n .action(async (chat: string, timeout: string) => {\n await withClient((c) =>\n printToolResult(\n c,\n \"wait_for_reply\",\n {\n ...(looksLikeThreadSlug(chat) ? { threadSlug: chat } : { chatIdentifier: chat }),\n timeoutSeconds: Number(timeout),\n pollIntervalSeconds: 5,\n },\n 70_000,\n ),\n );\n });\n\nprogram\n .command(\"send\")\n .description(\"Send a message via Messages.app\")\n .argument(\n \"<target>\",\n \"Recipient: thread slug, E.164 phone (+61401990797), local phone (0401 990 797), iMessage email, or contact name\",\n )\n .argument(\"<message...>\", \"Message text\")\n .action(async (target: string, messageParts: string[]) => {\n await withClient((c) =>\n printToolResult(c, \"send_message\", {\n ...(looksLikeThreadSlug(target) ? { threadSlug: target } : { recipient: target }),\n message: messageParts.join(\" \"),\n }),\n );\n });\n\nconst contactsCommand = program\n .command(\"contacts\")\n .description(\"Search and inspect macOS contacts (read-only)\");\n\ncontactsCommand\n .command(\"list\")\n .description(\"List contacts, sorted by name\")\n .argument(\"[limit]\", \"Number of contacts\", \"20\")\n .argument(\"[offset]\", \"Skip this many (pagination)\", \"0\")\n .action(async (limit: string, offset: string) => {\n await withClient((c) =>\n printToolResult(c, \"list_contacts\", { limit: Number(limit), offset: Number(offset) }),\n );\n });\n\ncontactsCommand\n .command(\"search\")\n .description(\"Search contacts by name, phone, or email\")\n .argument(\"<query>\", \"Substring to match\")\n .argument(\"[limit]\", \"Number of results\", \"20\")\n .action(async (query: string, limit: string) => {\n await withClient((c) => printToolResult(c, \"search_contacts\", { query, limit: Number(limit) }));\n });\n\ncontactsCommand\n .command(\"resolve\")\n .description(\"Resolve a phone number or email to its contact name\")\n .argument(\"<handle>\", \"Phone number or email\")\n .action(async (handle: string) => {\n await withClient((c) => printToolResult(c, \"resolve_handle\", { handle }));\n });\n\ncontactsCommand\n .command(\"show\")\n .description(\"Show a contact's handles and the thread slug for each\")\n .argument(\"<handleOrId>\", \"Phone, email, or numeric contact id\")\n .action(async (handleOrId: string) => {\n const args = /^\\d+$/.test(handleOrId) ? { id: Number(handleOrId) } : { handle: handleOrId };\n await withClient((c) => printToolResult(c, \"get_contact\", args));\n });\n\nprogram\n .command(\"logs\")\n .description(\"Show server debug logs\")\n .argument(\"[tail]\", \"Show only last N lines\")\n .action(async (tail: string | undefined) => {\n await withClient((c) => printToolResult(c, \"get_logs\", tail ? { tail: Number(tail) } : {}));\n });\n\nprogram\n .command(\"last-error\")\n .description(\"Show last send_message failure details\")\n .action(async () => {\n await withClient((c) => printToolResult(c, \"get_last_send_error\", {}));\n });\n\nprogram\n .command(\"tools\")\n .description(\"List available MCP tools\")\n .action(async () => {\n await withClient(async (c) => {\n for (const tool of await c.listTools()) {\n console.log(`${tool.name}${tool.description ? ` - ${tool.description}` : \"\"}`);\n }\n });\n });\n\nprogram\n .command(\"raw\")\n .description(\"Send raw JSON-RPC params to tools/call\")\n .argument(\"<json>\", 'JSON like {\"name\":\"tool_name\",\"arguments\":{...}}')\n .action(async (json: string) => {\n const parsed = JSON.parse(json) as { name?: string; arguments?: object };\n if (!parsed.name) throw new Error('Expected JSON like {\"name\":\"tool_name\",\"arguments\":{...}}.');\n await withClient((c) => printToolResult(c, parsed.name!, parsed.arguments ?? {}));\n });\n\nprogram\n .command(\"tui\")\n .description(\"Launch the read-only terminal UI\")\n .option(\"--theme <theme>\", 'TUI theme: \"safe\" or \"powerline\"')\n .option(\"--accent <hex>\", \"TUI accent color as #RRGGBB\")\n .action(async () => {\n const { runTui } = await import(\"./tui/index.js\");\n await runTui();\n });\n\n// ── Export ───────────────────────────────────────────────────────────────\n\nprogram\n .command(\"export <target>\")\n .description(\"Export a conversation to a file (md/csv/json/ndjson)\")\n .option(\"-f, --format <fmt>\", \"Output format: md (default), csv, json, ndjson\", \"md\")\n .option(\"--since <date>\", \"Earliest date (ISO or relative, e.g. '3 months ago')\")\n .option(\"--until <date>\", \"Latest date (ISO or relative)\")\n .option(\"-o, --output <path>\", \"Output path (default: ~/imsg-export-<target>-<YYYY-MM-DD>.<ext>)\")\n .option(\"--include-attachments\", \"Copy attachments next to the export\")\n .option(\"--attachments-dir <path>\", \"Where to copy attachments (default: <output>.attachments/)\")\n .option(\"--page-size <n>\", \"Messages per DB page (100-5000)\", \"1000\")\n .action(runExportCommand);\n\n// ── Setup ────────────────────────────────────────────────────────────────\n\nprogram\n .command(\"setup\")\n .description(\"Autodetect DB paths and emit an MCP host config snippet\")\n .option(\"-w, --write <host>\", 'Write into a host config: \"claude\" or \"cursor\"')\n .option(\"-r, --runtime <runtime>\", 'Runtime command: \"npx\" (default), \"bunx\", or \"global\"')\n .option(\"--print-only\", \"Just print the snippet (default behaviour)\")\n .action(async (opts: { write?: string; runtime?: string; printOnly?: boolean }) => {\n const { probeMachine, buildMcpSnippet, writeHostConfig } = await import(\"./setup.js\");\n const report = probeMachine();\n\n if (!report.imsgDb.readable) {\n log(`✗ Messages DB is not readable: ${report.imsgDb.path}`, \"err\");\n log(` ${report.imsgDb.error ?? \"\"}`, \"err\");\n log(\n \" Grant Full Disk Access to the running app: System Settings → Privacy & Security → Full Disk Access\",\n \"warn\",\n );\n process.exitCode = 1;\n return;\n }\n\n log(`✓ Messages DB readable: ${report.imsgDb.path}`, \"ok\");\n log(\n `✓ Address Book: ${report.contactsDbs.length} source(s), ${report.contactsDbs.filter((p) => p.readable).length} readable`,\n \"ok\",\n );\n log(\n ` slugs.db: ${report.slugsDb.path} ${report.slugsDb.exists ? \"(exists)\" : \"(will be created on first run)\"}`,\n );\n\n const runtime = opts.runtime === \"bunx\" || opts.runtime === \"global\" ? opts.runtime : \"npx\";\n const snippet = buildMcpSnippet(report, { runtime });\n\n if (opts.write) {\n if (opts.write !== \"claude\" && opts.write !== \"cursor\") {\n log(`✗ unknown host: ${opts.write} (expected \"claude\" or \"cursor\")`, \"err\");\n process.exitCode = 1;\n return;\n }\n const result = writeHostConfig(opts.write, report, { runtime });\n log(\n `✓ wrote ${opts.write} config to ${result.path}${result.replaced ? \" (replaced existing imessage entry)\" : \"\"}`,\n \"ok\",\n );\n log(` backup of any prior file at ${result.path}.bak`);\n return;\n }\n\n log(\"--- snippet ---\", \"dim\");\n process.stdout.write(snippet);\n });\n\n// ── Config ───────────────────────────────────────────────────────────────\n\nconst configCmd = program\n .command(\"config\")\n .description(\"Manage TUI settings (theme, accent color)\");\n\nconfigCmd\n .command(\"show\")\n .description(\"Print resolved TUI settings and where each value came from\")\n .action(async () => {\n const { resolveTuiConfig, defaultTuiConfigPath } = await import(\"./tui-config.js\");\n const cfg = resolveTuiConfig();\n log(\n `config file : ${cfg.configPath ?? `(none — defaults; would write to ${defaultTuiConfigPath()})`}`,\n );\n log(`theme : ${cfg.theme} (from ${cfg.origin.theme})`);\n log(`accentColor : ${cfg.accentColor} (from ${cfg.origin.accentColor})`);\n if (cfg.theme === \"powerline\") {\n log(\" — powerline theme requires a Nerd Font (https://www.nerdfonts.com)\", \"warn\");\n }\n for (const w of cfg.warnings) log(w, \"warn\");\n });\n\nconfigCmd\n .command(\"edit\")\n .description(\"Open the TUI config file in $EDITOR (creates it if missing)\")\n .action(async () => {\n const { defaultTuiConfigPath, findTuiConfigPath, writeTuiConfig, DEFAULT_TUI_CONFIG } =\n await import(\"./tui-config.js\");\n const path = findTuiConfigPath() ?? defaultTuiConfigPath();\n const { existsSync } = await import(\"node:fs\");\n if (!existsSync(path)) {\n writeTuiConfig(DEFAULT_TUI_CONFIG, path);\n log(`created ${path}`, \"ok\");\n }\n const editor = process.env.EDITOR ?? \"vi\";\n const { spawn } = await import(\"node:child_process\");\n const child = spawn(editor, [path], { stdio: \"inherit\" });\n await new Promise<void>((resolve, reject) => {\n child.on(\"exit\", (code) =>\n code === 0 ? resolve() : reject(new Error(`editor exited ${code}`)),\n );\n child.on(\"error\", reject);\n });\n });\n\nprogram.action(() => {\n program.outputHelp();\n});\n\n// Only auto-parse when invoked as a script (not when imported from tests).\nconst invokedAsScript = (() => {\n try {\n const entry = process.argv[1];\n if (!entry) return false;\n return import.meta.url === pathToFileURL(realpathSync(entry)).href;\n } catch {\n return false;\n }\n})();\n\nif (invokedAsScript) {\n program.parseAsync(process.argv).catch((error) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n });\n}\n"],"names":["existsSync","dirname","join","spawn"],"mappings":";;;;;;;;;AAUA,SAAS,WAAmB;AAC1B,SAAO,QAAQ,cAAc,YAAY,GAAG,CAAC;AAC/C;AAEO,MAAM,eAAe;AAAA,EAU1B,YAA6B,UAAmC;AAAnC,SAAA,WAAA;AAC3B,UAAM,QAAQ,KAAK,SAAA,GAAY,QAAQ;AACvC,QAAI,CAAC,WAAW,KAAK,GAAG;AACtB,YAAM,IAAI,MAAM,gDAAgD;AAAA,IAClE;AAEA,SAAK,OAAO,MAAM,QAAQ,UAAU,CAAC,OAAO,KAAK,GAAG;AAAA,MAClD,KAAK,QAAQ,IAAA;AAAA,MACb,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAAA,CAC/B;AAED,SAAK,KAAK,OAAQ,GAAG,QAAQ,CAAC,UAAkB;AAC9C,WAAK,WAAW,MAAM,SAAS,MAAM,CAAC;AAAA,IACxC,CAAC;AAED,SAAK,KAAK,OAAQ,GAAG,QAAQ,CAAC,UAAkB;AAC9C,WAAK,UAAU,MAAM,SAAS,MAAM;AACpC,YAAM,QAAQ,KAAK,OAAO,MAAM,IAAI;AACpC,WAAK,SAAS,MAAM,IAAA,KAAS;AAC7B,iBAAW,QAAQ,OAAO;AACxB,YAAI,CAAC,KAAK,OAAQ;AAClB,YAAI;AACF,gBAAM,UAAU,KAAK,MAAM,IAAI;AAC/B,cAAI,QAAQ,MAAM,QAAQ,KAAK,QAAQ,IAAI,QAAQ,EAAE,GAAG;AACtD,kBAAM,UAAU,KAAK,QAAQ,IAAI,QAAQ,EAAE;AAC3C,iBAAK,QAAQ,OAAO,QAAQ,EAAE;AAC9B,qBAAS,QAAQ,OAAO;AAAA,UAC1B;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF,CAAC;AAGD,SAAK,KAAK,GAAG,QAAQ,CAAC,SAAS;AAC7B,UAAI,KAAK,OAAQ;AACjB,iBAAW,CAAA,EAAG,CAAC,KAAK,KAAK,SAAS;AAChC,UAAE,OAAO,IAAI,MAAM,2CAA2C,IAAI,EAAE,CAAC;AAAA,MACvE;AACA,WAAK,QAAQ,MAAA;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAnDQ;AAAA,EACA,YAAY;AAAA,EACZ,8BAAc,IAAA;AAAA,EAId,SAAS;AAAA,EACT,SAAS;AAAA,EA8CjB,MAAM,QAAuB;AAC3B,UAAM,WAAY,MAAM,KAAK,KAAK,cAAc;AAAA,MAC9C,iBAAiB;AAAA,MACjB,cAAc,CAAA;AAAA,MACd,YAAY,EAAE,MAAM,QAAQ,SAAS,QAAA;AAAA,IAAQ,CAC9C;AAED,QAAI,SAAS,QAAQ;AACnB,WAAK,KAAK,MAAO;AAAA,QACf,GAAG,KAAK,UAAU,EAAE,SAAS,OAAO,QAAQ,6BAA6B,QAAQ,CAAA,GAAI,CAAC;AAAA;AAAA,MAAA;AAAA,IAE1F;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,MAAc,MAAc,YAAY,KAAiC;AACtF,UAAM,WAAY,MAAM,KAAK,KAAK,cAAc,EAAE,MAAM,WAAW,KAAA,GAAQ,SAAS;AAIpF,QAAI,SAAS,OAAO;AAClB,YAAM,IAAI,MAAM,SAAS,MAAM,OAAO;AAAA,IACxC;AACA,WAAO,SAAS,UAAU,CAAA;AAAA,EAC5B;AAAA,EAEA,MAAM,UAAU,YAAY,MAA2D;AACrF,UAAM,WAAY,MAAM,KAAK,KAAK,cAAc,CAAA,GAAI,SAAS;AAI7D,QAAI,SAAS,OAAO;AAClB,YAAM,IAAI,MAAM,SAAS,MAAM,OAAO;AAAA,IACxC;AACA,WAAO,SAAS,QAAQ,SAAS,CAAA;AAAA,EACnC;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AAGd,SAAK,KAAK,MAAO,IAAA;AAGjB,UAAM,YAAY,WAAW,MAAM;AACjC,UAAI,CAAC,KAAK,KAAK,QAAQ;AACrB,aAAK,KAAK,KAAK,SAAS;AAAA,MAC1B;AAAA,IACF,GAAG,GAAI;AACP,cAAU,MAAA;AAEV,SAAK,KAAK,GAAG,QAAQ,MAAM,aAAa,SAAS,CAAC;AAClD,SAAK,KAAK,KAAK,SAAS;AAGxB,eAAW,CAAA,EAAG,CAAC,KAAK,KAAK,SAAS;AAChC,QAAE,OAAO,IAAI,MAAM,mBAAmB,CAAC;AAAA,IACzC;AACA,SAAK,QAAQ,MAAA;AAAA,EACf;AAAA,EAEQ,KAAK,QAAgB,QAAgB,YAAY,MAA0B;AACjF,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,KAAK,QAAQ;AACf,eAAO,IAAI,MAAM,sBAAsB,CAAC;AACxC;AAAA,MACF;AAEA,YAAM,KAAK,EAAE,KAAK;AAClB,WAAK,QAAQ,IAAI,IAAI,EAAE,SAAS,QAAQ;AACxC,WAAK,KAAK,MAAO,MAAM,GAAG,KAAK,UAAU,EAAE,SAAS,OAAO,IAAI,QAAQ,OAAA,CAAQ,CAAC;AAAA,CAAI;AAEpF,YAAM,QAAQ,WAAW,MAAM;AAC7B,YAAI,CAAC,KAAK,QAAQ,IAAI,EAAE,EAAG;AAC3B,aAAK,QAAQ,OAAO,EAAE;AACtB,eAAO,IAAI,MAAM,yBAAyB,SAAS,KAAK,CAAC;AAAA,MAC3D,GAAG,SAAS;AACZ,YAAM,MAAA;AAEN,YAAM,WAAW,KAAK,QAAQ,IAAI,EAAE;AACpC,UAAI,CAAC,SAAU;AACf,WAAK,QAAQ,IAAI,IAAI;AAAA,QACnB,SAAS,CAAC,UAAU;AAClB,uBAAa,KAAK;AAClB,mBAAS,QAAQ,KAAK;AAAA,QACxB;AAAA,QACA,QAAQ,CAAC,UAAU;AACjB,uBAAa,KAAK;AAClB,mBAAS,OAAO,KAAK;AAAA,QACvB;AAAA,MAAA,CACD;AAAA,IACH,CAAC;AAAA,EACH;AACF;ACrJA,MAAM,QAAQ;AAAA,EACZ,KAAK,CAAC,MAAc,UAAU,CAAC;AAAA,EAC/B,MAAM,CAAC,MAAc,WAAW,CAAC;AAAA,EACjC,OAAO,CAAC,MAAc,WAAW,CAAC;AAAA,EAClC,QAAQ,CAAC,MAAc,WAAW,CAAC;AAAA,EACnC,KAAK,CAAC,MAAc,WAAW,CAAC;AAClC;AAEA,SAAS,IAAI,SAAiB,QAAuC,OAAO;AAC1E,QAAM,KACJ,UAAU,OACN,MAAM,QACN,UAAU,SACR,MAAM,SACN,UAAU,QACR,MAAM,MACN,MAAM;AAChB,UAAQ,IAAI,GAAG,OAAO,CAAC;AACzB;AAIA,eAAe,WAAc,KAAyD;AACpF,QAAM,SAAS,IAAI,eAAe,CAAC,SAAS,QAAQ,OAAO,MAAM,MAAM,IAAI,YAAY,IAAI,EAAE,CAAC,CAAC;AAC/F,MAAI;AACF,UAAM,OAAO,MAAA;AACb,WAAO,MAAM,IAAI,MAAM;AAAA,EACzB,UAAA;AACE,WAAO,MAAA;AAAA,EACT;AACF;AAEA,eAAe,gBACb,QACA,MACA,MACA,WACA;AACA,QAAM,SAAS,MAAM,OAAO,SAAS,MAAM,MAAM,SAAS;AAC1D,QAAM,OAAO,OAAO,UAAU,CAAC,GAAG,QAAQ,KAAK,UAAU,QAAQ,MAAM,CAAC;AACxE,MAAI,OAAO,QAAS,OAAM,IAAI,MAAM,IAAI;AACxC,UAAQ,IAAI,IAAI;AAClB;AAIA,SAAS,kBAAkB,MAA+C;AACxE,QAAM,QAAkB,CAAA;AACxB,MAAI,UAAU;AACd,MAAI,QAAuB;AAE3B,aAAW,QAAQ,MAAM;AACvB,SAAK,SAAS,OAAO,SAAS,QAAQ,SAAS,MAAM;AACnD,cAAQ;AACR;AAAA,IACF;AACA,QAAI,SAAS,OAAO;AAClB,cAAQ;AACR;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,MAAM;AACjC,UAAI,SAAS;AACX,cAAM,KAAK,OAAO;AAClB,kBAAU;AAAA,MACZ;AACA;AAAA,IACF;AACA,eAAW;AAAA,EACb;AACA,MAAI,QAAS,OAAM,KAAK,OAAO;AAC/B,SAAO,EAAE,KAAK,MAAM,CAAC,GAAG,YAAA,KAAiB,IAAI,MAAM,MAAM,MAAM,CAAC,EAAA;AAClE;AAEA,eAAe,kBACb,KACA,MACA,QACe;AACf,UAAQ,KAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,YAAM,gBAAgB,QAAQ,sBAAsB,EAAE,OAAO,OAAO,KAAK,CAAC,KAAK,EAAE,GAAG;AACpF;AAAA,IACF,KAAK;AAAA,IACL,KAAK,OAAO;AACV,YAAM,QAAQ,KAAK,CAAC;AACpB,YAAM,eAAe,SAAS,QAAQ,QAAQ,KAAK,KAAK;AACxD,YAAM,iBAAiB,eAAe,SAAY;AAClD,YAAM,QAAQ,OAAO,eAAe,QAAS,KAAK,CAAC,KAAK,EAAG;AAC3D,YAAM,gBAAgB,QAAQ,gBAAgB;AAAA,QAC5C,GAAI,iBACA,oBAAoB,cAAc,IAChC,EAAE,YAAY,eAAA,IACd,EAAE,eAAA,IACJ,CAAA;AAAA,QACJ;AAAA,MAAA,CACD;AACD;AAAA,IACF;AAAA,IACA,KAAK;AACH,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA,KAAK,CAAC,IAAI,EAAE,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,CAAA;AAAA,MAAC;AAE1C;AAAA,IACF,KAAK;AACH,UAAI,CAAC,KAAK,CAAC,EAAG,OAAM,IAAI,MAAM,+BAA+B;AAC7D,YAAM,gBAAgB,QAAQ,mBAAmB;AAAA,QAC/C,OAAO,KAAK,CAAC;AAAA,QACb,OAAO,OAAO,KAAK,CAAC,KAAK,EAAE;AAAA,MAAA,CAC5B;AACD;AAAA,IACF,KAAK;AACH,UAAI,CAAC,KAAK,CAAC,EAAG,OAAM,IAAI,MAAM,qCAAqC;AACnE,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,UACE,GAAI,oBAAoB,KAAK,CAAC,CAAC,IAAI,EAAE,YAAY,KAAK,CAAC,MAAM,EAAE,gBAAgB,KAAK,CAAC,EAAA;AAAA,UACrF,gBAAgB,OAAO,KAAK,CAAC,KAAK,EAAE;AAAA,UACpC,qBAAqB;AAAA,QAAA;AAAA,QAEvB;AAAA,MAAA;AAEF;AAAA,IACF,KAAK;AACH,UAAI,KAAK,SAAS,EAAG,OAAM,IAAI,MAAM,gCAAgC;AACrE,YAAM,gBAAgB,QAAQ,gBAAgB;AAAA,QAC5C,GAAI,oBAAoB,KAAK,CAAC,CAAC,IAAI,EAAE,YAAY,KAAK,CAAC,MAAM,EAAE,WAAW,KAAK,CAAC,EAAA;AAAA,QAChF,SAAS,KAAK,MAAM,CAAC,EAAE,KAAK,GAAG;AAAA,MAAA,CAChC;AACD;AAAA,IACF,KAAK,YAAY;AACf,YAAM,OAAO,KAAK,CAAC;AACnB,UAAI,SAAS,UAAU,SAAS,QAAW;AACzC,cAAM,gBAAgB,QAAQ,iBAAiB;AAAA,UAC7C,OAAO,OAAO,KAAK,CAAC,KAAK,EAAE;AAAA,UAC3B,QAAQ,OAAO,KAAK,CAAC,KAAK,CAAC;AAAA,QAAA,CAC5B;AAAA,MACH,WAAW,SAAS,UAAU;AAC5B,YAAI,CAAC,KAAK,CAAC,EAAG,OAAM,IAAI,MAAM,wCAAwC;AACtE,cAAM,gBAAgB,QAAQ,mBAAmB;AAAA,UAC/C,OAAO,KAAK,CAAC;AAAA,UACb,OAAO,OAAO,KAAK,CAAC,KAAK,EAAE;AAAA,QAAA,CAC5B;AAAA,MACH,WAAW,SAAS,WAAW;AAC7B,YAAI,CAAC,KAAK,CAAC,EAAG,OAAM,IAAI,MAAM,kCAAkC;AAChE,cAAM,gBAAgB,QAAQ,kBAAkB,EAAE,QAAQ,KAAK,CAAC,GAAG;AAAA,MACrE,WAAW,SAAS,QAAQ;AAC1B,YAAI,CAAC,KAAK,CAAC,EAAG,OAAM,IAAI,MAAM,qCAAqC;AACnE,cAAM,SAAS,QAAQ,KAAK,KAAK,CAAC,CAAC,IAAI,EAAE,IAAI,OAAO,KAAK,CAAC,CAAC,EAAA,IAAM,EAAE,QAAQ,KAAK,CAAC,EAAA;AACjF,cAAM,gBAAgB,QAAQ,eAAe,MAAM;AAAA,MACrD,OAAO;AACL,cAAM,IAAI,MAAM,0BAA0B,IAAI,iCAAiC;AAAA,MACjF;AACA;AAAA,IACF;AAAA,IACA,KAAK;AACH,YAAM,gBAAgB,QAAQ,YAAY,KAAK,CAAC,IAAI,EAAE,MAAM,OAAO,KAAK,CAAC,CAAC,EAAA,IAAM,CAAA,CAAE;AAClF;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,YAAM,gBAAgB,QAAQ,uBAAuB,EAAE;AACvD;AAAA,IACF,KAAK;AACH,iBAAW,QAAQ,MAAM,OAAO,UAAA,GAAa;AAC3C,gBAAQ,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,cAAc,MAAM,KAAK,WAAW,KAAK,EAAE,EAAE;AAAA,MAC/E;AACA;AAAA,IACF,KAAK;AACH,UAAI,CAAC,KAAK,CAAC,EAAG,OAAM,IAAI,MAAM,qBAAqB;AACnD;AACE,cAAM,SAAS,KAAK,MAAM,KAAK,KAAK,GAAG,CAAC;AACxC,YAAI,CAAC,OAAO;AACV,gBAAM,IAAI,MAAM,4DAA4D;AAC9E,cAAM,gBAAgB,QAAQ,OAAO,MAAM,OAAO,aAAa,EAAE;AAAA,MACnE;AACA;AAAA,IACF,KAAK,OAAO;AACV,YAAM,EAAE,OAAA,IAAW,MAAM,OAAO,UAAgB;AAChD,YAAM,OAAA;AACN;AAAA,IACF;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AACH,cAAQ,IAAI,YAAY;AACxB;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,cAAQ,KAAK,CAAC;AACd;AAAA,IACF;AACE,YAAM,IAAI,MAAM,oBAAoB,GAAG,uCAAuC;AAAA,EAAA;AAEpF;AAEA,MAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBnB,KAAA;AAEF,eAAe,wBAAuC;AACpD,MAAI,gCAAgC,KAAK;AACzC,QAAM,SAAS,IAAI,eAAe,CAAC,SAAS,QAAQ,OAAO,MAAM,MAAM,IAAI,YAAY,IAAI,EAAE,CAAC,CAAC;AAC/F,QAAM,OAAO,MAAA;AACb,MAAI,oBAAoB,IAAI;AAG5B,UAAQ,IAAI,YAAY;AACxB,UAAQ,IAAI,EAAE;AAEd,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,QAAQ;AAE3E,QAAM,SAAS,MACb,GAAG,SAAS,MAAM,KAAK,QAAQ,GAAG,OAAO,SAAS;AAChD,UAAM,UAAU,KAAK,KAAA;AACrB,QAAI,CAAC,SAAS;AACZ,aAAA;AACA;AAAA,IACF;AAEA,UAAM,EAAE,KAAK,SAAS,kBAAkB,OAAO;AAC/C,QAAI;AACF,YAAM,kBAAkB,KAAK,MAAM,MAAM;AAAA,IAC3C,SAAS,OAAO;AACd,UAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,KAAK;AAAA,IACnE;AACA,YAAQ,IAAI,EAAE;AACd,WAAA;AAAA,EACF,CAAC;AAEH,SAAA;AAEA,0BAAA;AACA,kBAAgB,MAAM,OAAO,OAAO;AACtC;AAcA,SAAS,gBAAgB,KAAqD;AAC5E,QAAM,IAAI,IAAI,YAAA;AACd,MAAI,MAAM,QAAQ,MAAM,WAAY,QAAO;AAC3C,MAAI,MAAM,MAAO,QAAO;AACxB,MAAI,MAAM,OAAQ,QAAO;AACzB,MAAI,MAAM,SAAU,QAAO;AAC3B,QAAM,IAAI,MAAM,mBAAmB,GAAG,sCAAsC;AAC9E;AAEA,SAAS,aAAa,KAAqD;AACzE,SAAO,QAAQ,aAAa,OAAO;AACrC;AAEA,SAAS,oBAAoB,GAAmB;AAC9C,SAAO,EAAE,QAAQ,sBAAsB,GAAG,EAAE,QAAQ,YAAY,EAAE,KAAK;AACzE;AAEA,eAAsB,iBAAiB,QAAgB,MAAiC;AACtF,QAAM,EAAE,YAAAA,aAAY,WAAW,cAAc,aAAa,MAAM,OAAO,SAAS;AAChF,QAAM,EAAE,QAAA,IAAY,MAAM,OAAO,SAAS;AAC1C,QAAM,EAAE,SAAAC,UAAS,MAAAC,OAAM,YAAY,YAAY,MAAM,OAAO,WAAW;AACvE,QAAM,EAAE,oBAAoB,eAAe,mBAAmB,MAAM,OAAO,wBAAa,EAAA,KAAA,OAAA,EAAA,CAAA;AACxF,QAAM,EAAE,WAAA,IAAe,MAAM,OAAO,2BAAkB,EAAA,KAAA,OAAA,EAAA,CAAA;AACtD,QAAM,EAAE,aAAA,IAAiB,MAAM,OAAO,4BAAmB;AACzD,QAAM,EAAE,cAAA,IAAkB,MAAM,OAAO,yBAAoB;AAE3D,QAAM,SAAS,gBAAgB,KAAK,UAAU,IAAI;AAClD,QAAM,MAAM,aAAa,MAAM;AAE/B,QAAM,WAAW,OAAO,KAAK,YAAY,MAAM;AAC/C,MAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,WAAW,OAAO,WAAW,KAAM;AACnE,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AAEA,QAAM,QAAQ,KAAK,QAAQ,cAAc,KAAK,KAAK,IAAI;AACvD,MAAI,KAAK,SAAS,CAAC,MAAO,OAAM,IAAI,MAAM,4BAA4B,KAAK,KAAK,EAAE;AAClF,QAAM,QAAQ,KAAK,QAAQ,cAAc,KAAK,KAAK,IAAI;AACvD,MAAI,KAAK,SAAS,CAAC,MAAO,OAAM,IAAI,MAAM,4BAA4B,KAAK,KAAK,EAAE;AAElF,QAAM,KAAK,IAAI,WAAW,cAAA,GAAiB,mBAAA,GAAsB,gBAAgB;AACjF,MAAI;AAEF,QAAI,iBAAiB;AACrB,QAAI,gBAAgB;AACpB,QAAI,oBAAoB,MAAM,GAAG;AAC/B,YAAM,MAAM,GAAG,cAAc,MAAM;AACnC,UAAI,CAAC,IAAK,OAAM,IAAI,MAAM,wBAAwB,MAAM,EAAE;AAC1D,uBAAiB,IAAI;AACrB,sBAAgB,IAAI;AAAA,IACtB;AAGA,QAAI;AACJ,QAAI,KAAK,QAAQ;AACf,mBAAa,WAAW,KAAK,MAAM,IAAI,KAAK,SAAS,QAAQ,KAAK,MAAM;AAAA,IAC1E,OAAO;AACL,YAAM,6BAAY,KAAA,GAAO,cAAc,MAAM,GAAG,EAAE;AAClD,mBAAaA;AAAA,QACX,QAAA;AAAA,QACA,eAAe,oBAAoB,aAAa,CAAC,IAAI,KAAK,IAAI,GAAG;AAAA,MAAA;AAAA,IAErE;AAEA,UAAM,SAASD,SAAQ,UAAU;AACjC,QAAI,CAACD,YAAW,MAAM,aAAa,QAAQ,EAAE,WAAW,MAAM;AAE9D,QAAI,aAAa,aAAa,MAAM,UAAU,KAAK,MAAM,KAAK,KAAK;AAEnE,UAAM,SAAS,MAAM,aAAa;AAAA,MAChC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAED,QAAI,oBAAoB;AACxB,QAAI,KAAK,oBAAoB;AAC3B,YAAM,MAAM,KAAK,iBACb,WAAW,KAAK,cAAc,IAC5B,KAAK,iBACL,QAAQ,KAAK,cAAc,IAC7B,GAAG,UAAU;AACjB,UAAI,CAACA,YAAW,GAAG,aAAa,KAAK,EAAE,WAAW,MAAM;AAExD,YAAM,UAAsD;AAAA,QAC1D;AAAA,QACA,OAAO;AAAA,MAAA;AAET,UAAI,MAAO,SAAQ,UAAU,MAAM,QAAA;AACnC,UAAI,MAAO,SAAQ,UAAU,MAAM,QAAA;AACnC,YAAM,cAAc,GAAG,kBAAkB,OAAO;AAEhD,UAAI,SAAS;AACb,UAAI,aAAa;AACjB,YAAM,2BAAW,IAAA;AACjB,iBAAW,KAAK,aAAa;AAC3B,YAAI,CAAC,EAAE,SAAU;AACjB,cAAM,MAAM,EAAE,SAAS,QAAQ,MAAM,SAAS;AAC9C,YAAI,CAACA,YAAW,GAAG,EAAG;AACtB,cAAM,WAAW,EAAE,gBAAgB,IAAI,MAAM,GAAG,EAAE,IAAA,KAAS,OAAO,EAAE,KAAK;AACzE,YAAI,WAAW,GAAG,EAAE,KAAK,IAAI,oBAAoB,QAAQ,CAAC;AAC1D,YAAI,KAAK,IAAI,QAAQ,EAAG,YAAW,GAAG,EAAE,KAAK,IAAI,KAAK,IAAA,CAAK,IAAI,QAAQ;AACvE,aAAK,IAAI,QAAQ;AACjB,cAAM,OAAOE,MAAK,KAAK,QAAQ;AAC/B,YAAI;AACF,uBAAa,KAAK,IAAI;AACtB;AACA,wBAAc,SAAS,IAAI,EAAE;AAAA,QAC/B,SAAS,KAAK;AACZ;AAAA,YACE,2BAA2B,GAAG,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,YACnF;AAAA,UAAA;AAAA,QAEJ;AAAA,MACF;AACA,0BAAoB;AAAA,eAAkB,MAAM,cAAc,aAAa,MAAM,QAAQ,CAAC,CAAC,SAAS,GAAG;AAAA,IACrG;AAEA;AAAA,MACE;AAAA,QACE;AAAA,QACA,cAAc,OAAO,KAAK,kBAAkB,OAAO,OAAO;AAAA,QAC1D,aAAa,MAAM;AAAA,QACnB,YAAY,OAAO,QAAQ,YAAA,KAAiB,QAAQ,MAAM,OAAO,QAAQ,YAAA,KAAiB,QAAQ;AAAA,QAClG,YAAY,OAAO,YAAY,MAAM,QAAQ,CAAC,CAAC,MAAM,iBAAiB;AAAA,MAAA,EACtE,KAAK,IAAI;AAAA,MACX;AAAA,IAAA;AAAA,EAEJ,UAAA;AACE,UAAM,GAAG,MAAA;AAAA,EACX;AACF;AAIA,MAAM,UAAU,IAAI,UACjB,KAAK,MAAM,EACX,QAAQ,aAAa,eAAe,EACpC,YAAY,0CAA0C,EACtD;AAAA,EACC;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAKF;AAEF,QACG,QAAQ,KAAK,EACb,YAAY,0BAA0B,EACtC,OAAO,YAAY;AAClB,QAAM,EAAE,aAAA,IAAiB,MAAM,OAAO,YAAY;AAClD,QAAM,aAAA;AACR,CAAC;AAEH,QACG,QAAQ,KAAK,EACb,MAAM,SAAS,EACf,YAAY,4BAA4B,EACxC,OAAO,qBAAqB;AAE/B,QACG,QAAQ,QAAQ,EAChB,YAAY,6CAA6C,EACzD,OAAO,YAAY;AAClB,QAAM,SAAS,MAAM,iBAAA;AACrB,UAAQ,IAAI,mBAAmB,MAAM,CAAC;AACtC,UAAQ,WAAW,OAAO,KAAK,IAAI;AACrC,CAAC;AAEH,QACG,QAAQ,eAAe,EACvB,MAAM,MAAM,EACZ,YAAY,2BAA2B,EACvC,SAAS,WAAW,2BAA2B,IAAI,EACnD,OAAO,OAAO,UAAkB;AAC/B,QAAM,WAAW,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,EAAE,OAAO,OAAO,KAAK,EAAA,CAAG,CAAC;AAC5F,CAAC;AAEH,QACG,QAAQ,UAAU,EAClB,MAAM,KAAK,EACX,YAAY,0CAA0C,EACtD,SAAS,UAAU,qCAAqC,EACxD,SAAS,WAAW,sBAAsB,IAAI,EAC9C,OAAO,OAAO,MAA0B,UAAkB;AACzD,QAAM,eAAe,QAAQ,QAAQ,QAAQ,KAAK,IAAI;AACtD,QAAM,iBAAiB,eAAe,SAAY;AAClD,QAAM,aAAa,OAAO,eAAe,OAAO,KAAK;AACrD,QAAM;AAAA,IAAW,CAAC,MAChB,gBAAgB,GAAG,gBAAgB;AAAA,MACjC,GAAI,iBACA,oBAAoB,cAAc,IAChC,EAAE,YAAY,eAAA,IACd,EAAE,eAAA,IACJ,CAAA;AAAA,MACJ,OAAO;AAAA,IAAA,CACR;AAAA,EAAA;AAEL,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,+CAA+C,EAC3D,SAAS,WAAW,sBAAsB,KAAK,EAC/C,OAAO,OAAO,UAAkB;AAC/B,QAAM,WAAW,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,EAAE,OAAO,OAAO,KAAK,EAAA,CAAG,CAAC;AAC7F,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,iCAAiC,EAC7C,SAAS,WAAW,cAAc,EAClC,SAAS,WAAW,qBAAqB,IAAI,EAC7C,OAAO,OAAO,OAAe,UAAkB;AAC9C,QAAM,WAAW,CAAC,MAAM,gBAAgB,GAAG,mBAAmB,EAAE,OAAO,OAAO,OAAO,KAAK,EAAA,CAAG,CAAC;AAChG,CAAC;AAEH,QACG,QAAQ,MAAM,EACd,YAAY,oCAAoC,EAChD,SAAS,UAAU,qCAAqC,EACxD,SAAS,aAAa,sBAAsB,IAAI,EAChD,OAAO,OAAO,MAAc,YAAoB;AAC/C,QAAM;AAAA,IAAW,CAAC,MAChB;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,QACE,GAAI,oBAAoB,IAAI,IAAI,EAAE,YAAY,SAAS,EAAE,gBAAgB,KAAA;AAAA,QACzE,gBAAgB,OAAO,OAAO;AAAA,QAC9B,qBAAqB;AAAA,MAAA;AAAA,MAEvB;AAAA,IAAA;AAAA,EACF;AAEJ,CAAC;AAEH,QACG,QAAQ,MAAM,EACd,YAAY,iCAAiC,EAC7C;AAAA,EACC;AAAA,EACA;AACF,EACC,SAAS,gBAAgB,cAAc,EACvC,OAAO,OAAO,QAAgB,iBAA2B;AACxD,QAAM;AAAA,IAAW,CAAC,MAChB,gBAAgB,GAAG,gBAAgB;AAAA,MACjC,GAAI,oBAAoB,MAAM,IAAI,EAAE,YAAY,WAAW,EAAE,WAAW,OAAA;AAAA,MACxE,SAAS,aAAa,KAAK,GAAG;AAAA,IAAA,CAC/B;AAAA,EAAA;AAEL,CAAC;AAEH,MAAM,kBAAkB,QACrB,QAAQ,UAAU,EAClB,YAAY,+CAA+C;AAE9D,gBACG,QAAQ,MAAM,EACd,YAAY,+BAA+B,EAC3C,SAAS,WAAW,sBAAsB,IAAI,EAC9C,SAAS,YAAY,+BAA+B,GAAG,EACvD,OAAO,OAAO,OAAe,WAAmB;AAC/C,QAAM;AAAA,IAAW,CAAC,MAChB,gBAAgB,GAAG,iBAAiB,EAAE,OAAO,OAAO,KAAK,GAAG,QAAQ,OAAO,MAAM,GAAG;AAAA,EAAA;AAExF,CAAC;AAEH,gBACG,QAAQ,QAAQ,EAChB,YAAY,0CAA0C,EACtD,SAAS,WAAW,oBAAoB,EACxC,SAAS,WAAW,qBAAqB,IAAI,EAC7C,OAAO,OAAO,OAAe,UAAkB;AAC9C,QAAM,WAAW,CAAC,MAAM,gBAAgB,GAAG,mBAAmB,EAAE,OAAO,OAAO,OAAO,KAAK,EAAA,CAAG,CAAC;AAChG,CAAC;AAEH,gBACG,QAAQ,SAAS,EACjB,YAAY,qDAAqD,EACjE,SAAS,YAAY,uBAAuB,EAC5C,OAAO,OAAO,WAAmB;AAChC,QAAM,WAAW,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,EAAE,OAAA,CAAQ,CAAC;AAC1E,CAAC;AAEH,gBACG,QAAQ,MAAM,EACd,YAAY,uDAAuD,EACnE,SAAS,gBAAgB,qCAAqC,EAC9D,OAAO,OAAO,eAAuB;AACpC,QAAM,OAAO,QAAQ,KAAK,UAAU,IAAI,EAAE,IAAI,OAAO,UAAU,EAAA,IAAM,EAAE,QAAQ,WAAA;AAC/E,QAAM,WAAW,CAAC,MAAM,gBAAgB,GAAG,eAAe,IAAI,CAAC;AACjE,CAAC;AAEH,QACG,QAAQ,MAAM,EACd,YAAY,wBAAwB,EACpC,SAAS,UAAU,wBAAwB,EAC3C,OAAO,OAAO,SAA6B;AAC1C,QAAM,WAAW,CAAC,MAAM,gBAAgB,GAAG,YAAY,OAAO,EAAE,MAAM,OAAO,IAAI,EAAA,IAAM,CAAA,CAAE,CAAC;AAC5F,CAAC;AAEH,QACG,QAAQ,YAAY,EACpB,YAAY,wCAAwC,EACpD,OAAO,YAAY;AAClB,QAAM,WAAW,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAA,CAAE,CAAC;AACvE,CAAC;AAEH,QACG,QAAQ,OAAO,EACf,YAAY,0BAA0B,EACtC,OAAO,YAAY;AAClB,QAAM,WAAW,OAAO,MAAM;AAC5B,eAAW,QAAQ,MAAM,EAAE,UAAA,GAAa;AACtC,cAAQ,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,cAAc,MAAM,KAAK,WAAW,KAAK,EAAE,EAAE;AAAA,IAC/E;AAAA,EACF,CAAC;AACH,CAAC;AAEH,QACG,QAAQ,KAAK,EACb,YAAY,wCAAwC,EACpD,SAAS,UAAU,kDAAkD,EACrE,OAAO,OAAO,SAAiB;AAC9B,QAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,CAAC,OAAO,KAAM,OAAM,IAAI,MAAM,4DAA4D;AAC9F,QAAM,WAAW,CAAC,MAAM,gBAAgB,GAAG,OAAO,MAAO,OAAO,aAAa,CAAA,CAAE,CAAC;AAClF,CAAC;AAEH,QACG,QAAQ,KAAK,EACb,YAAY,kCAAkC,EAC9C,OAAO,mBAAmB,kCAAkC,EAC5D,OAAO,kBAAkB,6BAA6B,EACtD,OAAO,YAAY;AAClB,QAAM,EAAE,OAAA,IAAW,MAAM,OAAO,UAAgB;AAChD,QAAM,OAAA;AACR,CAAC;AAIH,QACG,QAAQ,iBAAiB,EACzB,YAAY,sDAAsD,EAClE,OAAO,sBAAsB,kDAAkD,IAAI,EACnF,OAAO,kBAAkB,sDAAsD,EAC/E,OAAO,kBAAkB,+BAA+B,EACxD,OAAO,uBAAuB,kEAAkE,EAChG,OAAO,yBAAyB,qCAAqC,EACrE,OAAO,4BAA4B,4DAA4D,EAC/F,OAAO,mBAAmB,mCAAmC,MAAM,EACnE,OAAO,gBAAgB;AAI1B,QACG,QAAQ,OAAO,EACf,YAAY,yDAAyD,EACrE,OAAO,sBAAsB,gDAAgD,EAC7E,OAAO,2BAA2B,uDAAuD,EACzF,OAAO,gBAAgB,4CAA4C,EACnE,OAAO,OAAO,SAAoE;AACjF,QAAM,EAAE,cAAc,iBAAiB,oBAAoB,MAAM,OAAO,qBAAY;AACpF,QAAM,SAAS,aAAA;AAEf,MAAI,CAAC,OAAO,OAAO,UAAU;AAC3B,QAAI,kCAAkC,OAAO,OAAO,IAAI,IAAI,KAAK;AACjE,QAAI,KAAK,OAAO,OAAO,SAAS,EAAE,IAAI,KAAK;AAC3C;AAAA,MACE;AAAA,MACA;AAAA,IAAA;AAEF,YAAQ,WAAW;AACnB;AAAA,EACF;AAEA,MAAI,2BAA2B,OAAO,OAAO,IAAI,IAAI,IAAI;AACzD;AAAA,IACE,mBAAmB,OAAO,YAAY,MAAM,eAAe,OAAO,YAAY,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM;AAAA,IAC9G;AAAA,EAAA;AAEF;AAAA,IACE,eAAe,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ,SAAS,aAAa,gCAAgC;AAAA,EAAA;AAG7G,QAAM,UAAU,KAAK,YAAY,UAAU,KAAK,YAAY,WAAW,KAAK,UAAU;AACtF,QAAM,UAAU,gBAAgB,QAAQ,EAAE,SAAS;AAEnD,MAAI,KAAK,OAAO;AACd,QAAI,KAAK,UAAU,YAAY,KAAK,UAAU,UAAU;AACtD,UAAI,mBAAmB,KAAK,KAAK,oCAAoC,KAAK;AAC1E,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,UAAM,SAAS,gBAAgB,KAAK,OAAO,QAAQ,EAAE,SAAS;AAC9D;AAAA,MACE,WAAW,KAAK,KAAK,cAAc,OAAO,IAAI,GAAG,OAAO,WAAW,wCAAwC,EAAE;AAAA,MAC7G;AAAA,IAAA;AAEF,QAAI,iCAAiC,OAAO,IAAI,MAAM;AACtD;AAAA,EACF;AAEA,MAAI,mBAAmB,KAAK;AAC5B,UAAQ,OAAO,MAAM,OAAO;AAC9B,CAAC;AAIH,MAAM,YAAY,QACf,QAAQ,QAAQ,EAChB,YAAY,2CAA2C;AAE1D,UACG,QAAQ,MAAM,EACd,YAAY,4DAA4D,EACxE,OAAO,YAAY;AAClB,QAAM,EAAE,kBAAkB,yBAAyB,MAAM,OAAO,0BAAiB;AACjF,QAAM,MAAM,iBAAA;AACZ;AAAA,IACE,iBAAiB,IAAI,cAAc,oCAAoC,qBAAA,CAAsB,GAAG;AAAA,EAAA;AAElG,MAAI,iBAAiB,IAAI,KAAK,WAAW,IAAI,OAAO,KAAK,GAAG;AAC5D,MAAI,iBAAiB,IAAI,WAAW,WAAW,IAAI,OAAO,WAAW,GAAG;AACxE,MAAI,IAAI,UAAU,aAAa;AAC7B,QAAI,wEAAwE,MAAM;AAAA,EACpF;AACA,aAAW,KAAK,IAAI,SAAU,KAAI,GAAG,MAAM;AAC7C,CAAC;AAEH,UACG,QAAQ,MAAM,EACd,YAAY,6DAA6D,EACzE,OAAO,YAAY;AAClB,QAAM,EAAE,sBAAsB,mBAAmB,gBAAgB,uBAC/D,MAAM,OAAO,0BAAiB;AAChC,QAAM,OAAO,kBAAA,KAAuB,qBAAA;AACpC,QAAM,EAAE,YAAAF,YAAA,IAAe,MAAM,OAAO,SAAS;AAC7C,MAAI,CAACA,YAAW,IAAI,GAAG;AACrB,mBAAe,oBAAoB,IAAI;AACvC,QAAI,WAAW,IAAI,IAAI,IAAI;AAAA,EAC7B;AACA,QAAM,SAAS,QAAQ,IAAI,UAAU;AACrC,QAAM,EAAE,OAAAG,OAAA,IAAU,MAAM,OAAO,oBAAoB;AACnD,QAAM,QAAQA,OAAM,QAAQ,CAAC,IAAI,GAAG,EAAE,OAAO,WAAW;AACxD,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAM;AAAA,MAAG;AAAA,MAAQ,CAAC,SAChB,SAAS,IAAI,QAAA,IAAY,OAAO,IAAI,MAAM,iBAAiB,IAAI,EAAE,CAAC;AAAA,IAAA;AAEpE,UAAM,GAAG,SAAS,MAAM;AAAA,EAC1B,CAAC;AACH,CAAC;AAEH,QAAQ,OAAO,MAAM;AACnB,UAAQ,WAAA;AACV,CAAC;AAGD,MAAM,mBAAmB,MAAM;AAC7B,MAAI;AACF,UAAM,QAAQ,QAAQ,KAAK,CAAC;AAC5B,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,YAAY,QAAQ,cAAc,aAAa,KAAK,CAAC,EAAE;AAAA,EAChE,QAAQ;AACN,WAAO;AAAA,EACT;AACF,GAAA;AAEA,IAAI,iBAAiB;AACnB,UAAQ,WAAW,QAAQ,IAAI,EAAE,MAAM,CAAC,UAAU;AAChD,YAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AACpE,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;"}
|
|
1
|
+
{"version":3,"file":"cli.js","sources":["../src/mcp-client.ts","../src/cli.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { APP_VERSION } from \"./meta.js\";\n\nexport interface ToolCallResult {\n content?: { type: string; text?: string }[];\n isError?: boolean;\n}\n\nfunction distRoot(): string {\n return dirname(fileURLToPath(import.meta.url));\n}\n\nexport class LocalMcpClient {\n private proc: ReturnType<typeof spawn>;\n private requestId = 0;\n private pending = new Map<\n number,\n { resolve: (value: unknown) => void; reject: (error: Error) => void }\n >();\n private outBuf = \"\";\n private closed = false;\n\n constructor(private readonly onStderr?: (line: string) => void) {\n const entry = join(distRoot(), \"cli.js\");\n if (!existsSync(entry)) {\n throw new Error(\"dist/cli.js not found. Run `pnpm build` first.\");\n }\n\n this.proc = spawn(process.execPath, [entry, \"mcp\"], {\n cwd: process.cwd(),\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n this.proc.stderr!.on(\"data\", (chunk: Buffer) => {\n this.onStderr?.(chunk.toString(\"utf8\"));\n });\n\n this.proc.stdout!.on(\"data\", (chunk: Buffer) => {\n this.outBuf += chunk.toString(\"utf8\");\n const lines = this.outBuf.split(\"\\n\");\n this.outBuf = lines.pop() ?? \"\";\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const message = JSON.parse(line) as { id?: number };\n if (message.id != null && this.pending.has(message.id)) {\n const pending = this.pending.get(message.id);\n this.pending.delete(message.id);\n pending?.resolve(message);\n }\n } catch {\n // Ignore non-JSON lines on stdout.\n }\n }\n });\n\n // Handle unexpected child exit — reject all pending requests\n this.proc.on(\"exit\", (code) => {\n if (this.closed) return;\n for (const [, p] of this.pending) {\n p.reject(new Error(`MCP child exited unexpectedly with code ${code}`));\n }\n this.pending.clear();\n });\n }\n\n async start(): Promise<void> {\n const response = (await this.call(\"initialize\", {\n protocolVersion: \"2024-11-05\",\n capabilities: {},\n clientInfo: { name: \"imsg\", version: APP_VERSION },\n })) as { result?: unknown };\n\n if (response.result) {\n this.proc.stdin!.write(\n `${JSON.stringify({ jsonrpc: \"2.0\", method: \"notifications/initialized\", params: {} })}\\n`,\n );\n }\n }\n\n async callTool(name: string, args: object, timeoutMs = 30_000): Promise<ToolCallResult> {\n const response = (await this.call(\"tools/call\", { name, arguments: args }, timeoutMs)) as {\n result?: ToolCallResult;\n error?: { message: string };\n };\n if (response.error) {\n throw new Error(response.error.message);\n }\n return response.result ?? {};\n }\n\n async listTools(timeoutMs = 15_000): Promise<{ name: string; description?: string }[]> {\n const response = (await this.call(\"tools/list\", {}, timeoutMs)) as {\n result?: { tools?: { name: string; description?: string }[] };\n error?: { message: string };\n };\n if (response.error) {\n throw new Error(response.error.message);\n }\n return response.result?.tools ?? [];\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n\n // Close stdin to signal the child\n this.proc.stdin!.end();\n\n // Give child 2s to exit gracefully, then force kill\n const killTimer = setTimeout(() => {\n if (!this.proc.killed) {\n this.proc.kill(\"SIGKILL\");\n }\n }, 2000);\n killTimer.unref();\n\n this.proc.on(\"exit\", () => clearTimeout(killTimer));\n this.proc.kill(\"SIGTERM\");\n\n // Reject any remaining pending requests\n for (const [, p] of this.pending) {\n p.reject(new Error(\"MCP client closed\"));\n }\n this.pending.clear();\n }\n\n private call(method: string, params: object, timeoutMs = 15_000): Promise<unknown> {\n return new Promise((resolve, reject) => {\n if (this.closed) {\n reject(new Error(\"MCP client is closed\"));\n return;\n }\n\n const id = ++this.requestId;\n this.pending.set(id, { resolve, reject });\n this.proc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`);\n\n const timer = setTimeout(() => {\n if (!this.pending.has(id)) return;\n this.pending.delete(id);\n reject(new Error(`Request timeout after ${timeoutMs}ms.`));\n }, timeoutMs);\n timer.unref(); // Don't prevent process exit\n\n const original = this.pending.get(id);\n if (!original) return;\n this.pending.set(id, {\n resolve: (value) => {\n clearTimeout(timer);\n original.resolve(value);\n },\n reject: (error) => {\n clearTimeout(timer);\n original.reject(error);\n },\n });\n });\n }\n}\n","import { realpathSync } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport { pathToFileURL } from \"node:url\";\nimport { Command } from \"commander\";\nimport { checkLocalAccess, formatAccessReport } from \"./access-check.js\";\nimport { LocalMcpClient } from \"./mcp-client.js\";\nimport { APP_VERSION } from \"./meta.js\";\nimport { installShutdownHandlers, registerCleanup } from \"./shutdown.js\";\nimport { looksLikeThreadSlug } from \"./thread-slug.js\";\n\n// ── Colour helpers ─────────────────────────────────────────────────────\n\nconst color = {\n dim: (v: string) => `\\x1b[2m${v}\\x1b[0m`,\n cyan: (v: string) => `\\x1b[36m${v}\\x1b[0m`,\n green: (v: string) => `\\x1b[32m${v}\\x1b[0m`,\n yellow: (v: string) => `\\x1b[33m${v}\\x1b[0m`,\n red: (v: string) => `\\x1b[31m${v}\\x1b[0m`,\n};\n\nfunction log(message: string, style: \"dim\" | \"ok\" | \"warn\" | \"err\" = \"dim\") {\n const fn =\n style === \"ok\"\n ? color.green\n : style === \"warn\"\n ? color.yellow\n : style === \"err\"\n ? color.red\n : color.dim;\n console.log(fn(message));\n}\n\n// ── MCP client helpers ─────────────────────────────────────────────────\n\nasync function withClient<T>(run: (client: LocalMcpClient) => Promise<T>): Promise<T> {\n const client = new LocalMcpClient((line) => process.stderr.write(color.dim(`[server] ${line}`)));\n try {\n await client.start();\n return await run(client);\n } finally {\n client.close();\n }\n}\n\nasync function printToolResult(\n client: LocalMcpClient,\n name: string,\n args: object,\n timeoutMs?: number,\n) {\n const result = await client.callTool(name, args, timeoutMs);\n const text = result.content?.[0]?.text ?? JSON.stringify(result, null, 2);\n if (result.isError) throw new Error(text);\n console.log(text);\n}\n\n// ── Interactive console ────────────────────────────────────────────────\n\nfunction parseConsoleInput(line: string): { cmd: string; args: string[] } {\n const parts: string[] = [];\n let current = \"\";\n let quote: string | null = null;\n\n for (const char of line) {\n if ((char === '\"' || char === \"'\") && quote == null) {\n quote = char;\n continue;\n }\n if (char === quote) {\n quote = null;\n continue;\n }\n if (char === \" \" && quote == null) {\n if (current) {\n parts.push(current);\n current = \"\";\n }\n continue;\n }\n current += char;\n }\n if (current) parts.push(current);\n return { cmd: parts[0]?.toLowerCase() ?? \"\", args: parts.slice(1) };\n}\n\nasync function runConsoleCommand(\n cmd: string,\n args: string[],\n client: LocalMcpClient,\n): Promise<void> {\n switch (cmd) {\n case \"conversations\":\n case \"list\":\n await printToolResult(client, \"list_conversations\", { limit: Number(args[0] ?? 20) });\n return;\n case \"messages\":\n case \"msg\": {\n const first = args[0];\n const firstIsLimit = first != null && /^\\d+$/.test(first);\n const chatIdentifier = firstIsLimit ? undefined : first;\n const limit = Number(firstIsLimit ? first : (args[1] ?? 20));\n await printToolResult(client, \"get_messages\", {\n ...(chatIdentifier\n ? looksLikeThreadSlug(chatIdentifier)\n ? { threadSlug: chatIdentifier }\n : { chatIdentifier }\n : {}),\n limit,\n });\n return;\n }\n case \"unread\":\n await printToolResult(\n client,\n \"get_unread_messages\",\n args[0] ? { limit: Number(args[0]) } : {},\n );\n return;\n case \"search\":\n if (!args[0]) throw new Error(\"Usage: search <query> [limit]\");\n await printToolResult(client, \"search_messages\", {\n query: args[0],\n limit: Number(args[1] ?? 20),\n });\n return;\n case \"wait\":\n if (!args[0]) throw new Error(\"Usage: wait <chat> [timeoutSeconds]\");\n await printToolResult(\n client,\n \"wait_for_reply\",\n {\n ...(looksLikeThreadSlug(args[0]) ? { threadSlug: args[0] } : { chatIdentifier: args[0] }),\n timeoutSeconds: Number(args[1] ?? 60),\n pollIntervalSeconds: 5,\n },\n 70_000,\n );\n return;\n case \"send\":\n if (args.length < 2) throw new Error(\"Usage: send <target> <message>\");\n await printToolResult(client, \"send_message\", {\n ...(looksLikeThreadSlug(args[0]) ? { threadSlug: args[0] } : { recipient: args[0] }),\n message: args.slice(1).join(\" \"),\n });\n return;\n case \"contacts\": {\n const verb = args[0];\n if (verb === \"list\" || verb === undefined) {\n await printToolResult(client, \"list_contacts\", {\n limit: Number(args[1] ?? 20),\n offset: Number(args[2] ?? 0),\n });\n } else if (verb === \"search\") {\n if (!args[1]) throw new Error(\"Usage: contacts search <query> [limit]\");\n await printToolResult(client, \"search_contacts\", {\n query: args[1],\n limit: Number(args[2] ?? 20),\n });\n } else if (verb === \"resolve\") {\n if (!args[1]) throw new Error(\"Usage: contacts resolve <handle>\");\n await printToolResult(client, \"resolve_handle\", { handle: args[1] });\n } else if (verb === \"show\") {\n if (!args[1]) throw new Error(\"Usage: contacts show <handle-or-id>\");\n const params = /^\\d+$/.test(args[1]) ? { id: Number(args[1]) } : { handle: args[1] };\n await printToolResult(client, \"get_contact\", params);\n } else {\n throw new Error(`Unknown contacts verb: ${verb}. Use list|search|resolve|show.`);\n }\n return;\n }\n case \"logs\":\n await printToolResult(client, \"get_logs\", args[0] ? { tail: Number(args[0]) } : {});\n return;\n case \"last-error\":\n case \"lasterror\":\n await printToolResult(client, \"get_last_send_error\", {});\n return;\n case \"tools\":\n for (const tool of await client.listTools()) {\n console.log(`${tool.name}${tool.description ? ` - ${tool.description}` : \"\"}`);\n }\n return;\n case \"raw\":\n if (!args[0]) throw new Error(\"Usage: raw '<json>'\");\n {\n const parsed = JSON.parse(args.join(\" \")) as { name?: string; arguments?: object };\n if (!parsed.name)\n throw new Error('Expected JSON like {\"name\":\"tool_name\",\"arguments\":{...}}.');\n await printToolResult(client, parsed.name, parsed.arguments ?? {});\n }\n return;\n case \"tui\": {\n const { runTui } = await import(\"./tui/index.js\");\n await runTui();\n return;\n }\n case \"help\":\n case \"?\":\n console.log(CONSOLE_HELP);\n return;\n case \"quit\":\n case \"exit\":\n process.exit(0);\n return;\n default:\n throw new Error(`Unknown command: ${cmd}. Type \"help\" for available commands.`);\n }\n}\n\nconst CONSOLE_HELP = `\nAvailable commands:\n conversations [n] List recent conversations (default 20)\n messages [chat] [n] Show recent messages\n unread [n] Show unread messages\n search <query> [n] Search messages\n wait <chat> [secs] Wait for a reply (default 60s)\n send <target> <msg> Send a message\n contacts [verb] Contacts: list [n] [offset] | search <q> [n] | resolve <handle> | show <handle-or-id>\n logs [tail] Show server debug logs\n last-error Show last send failure\n tools List available MCP tools\n raw <json> Send raw JSON-RPC to tools/call\n tui Launch the read-only TUI\n help Show this help\n quit Exit\n`.trim();\n\nasync function runInteractiveConsole(): Promise<void> {\n log(\"Starting local MCP server...\", \"dim\");\n const client = new LocalMcpClient((line) => process.stderr.write(color.dim(`[server] ${line}`)));\n await client.start();\n log(\"Console ready.\\n\", \"ok\");\n // Show the full help text on launch so the user doesn't have to type `help`\n // to discover what's available.\n console.log(CONSOLE_HELP);\n console.log(\"\");\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n const prompt = () =>\n rl.question(color.cyan(\"imsg> \"), async (line) => {\n const trimmed = line.trim();\n if (!trimmed) {\n prompt();\n return;\n }\n\n const { cmd, args } = parseConsoleInput(trimmed);\n try {\n await runConsoleCommand(cmd, args, client);\n } catch (error) {\n log(error instanceof Error ? error.message : String(error), \"err\");\n }\n console.log(\"\");\n prompt();\n });\n\n prompt();\n\n installShutdownHandlers();\n registerCleanup(() => client.close());\n}\n\n// ── Export command ─────────────────────────────────────────────────────\n\ninterface ExportOpts {\n format?: string;\n since?: string;\n until?: string;\n output?: string;\n includeAttachments?: boolean;\n attachmentsDir?: string;\n pageSize?: string;\n}\n\nfunction normalizeFormat(raw: string): \"markdown\" | \"csv\" | \"json\" | \"ndjson\" {\n const v = raw.toLowerCase();\n if (v === \"md\" || v === \"markdown\") return \"markdown\";\n if (v === \"csv\") return \"csv\";\n if (v === \"json\") return \"json\";\n if (v === \"ndjson\") return \"ndjson\";\n throw new Error(`Unknown format: ${raw}. Expected md, csv, json, or ndjson.`);\n}\n\nfunction extForFormat(fmt: \"markdown\" | \"csv\" | \"json\" | \"ndjson\"): string {\n return fmt === \"markdown\" ? \"md\" : fmt;\n}\n\nfunction sanitizeForFilename(s: string): string {\n return s.replace(/[^A-Za-z0-9._~-]+/g, \"_\").replace(/^_+|_+$/g, \"\") || \"chat\";\n}\n\nexport async function runExportCommand(target: string, opts: ExportOpts): Promise<void> {\n const { existsSync, mkdirSync, copyFileSync, statSync } = await import(\"node:fs\");\n const { homedir } = await import(\"node:os\");\n const { dirname, join, isAbsolute, resolve } = await import(\"node:path\");\n const { getContactsDbPaths, getImsgDbPath, getSlugsDbPath } = await import(\"./config.js\");\n const { IMessageDB } = await import(\"./imessage-db.js\");\n const { streamExport } = await import(\"./exportStream.js\");\n const { parseUserDate } = await import(\"./tui/dateParse.js\");\n\n const format = normalizeFormat(opts.format ?? \"md\");\n const ext = extForFormat(format);\n\n const pageSize = Number(opts.pageSize ?? \"1000\");\n if (!Number.isFinite(pageSize) || pageSize < 100 || pageSize > 5000) {\n throw new Error(\"--page-size must be between 100 and 5000.\");\n }\n\n const since = opts.since ? parseUserDate(opts.since) : null;\n if (opts.since && !since) throw new Error(`Could not parse --since: ${opts.since}`);\n const until = opts.until ? parseUserDate(opts.until) : null;\n if (opts.until && !until) throw new Error(`Could not parse --until: ${opts.until}`);\n\n const db = new IMessageDB(getImsgDbPath(), getContactsDbPaths(), getSlugsDbPath());\n try {\n // Resolve target → chatIdentifier + display slug for filename\n let chatIdentifier = target;\n let displayHandle = target;\n if (looksLikeThreadSlug(target)) {\n const rec = db.getSlugRecord(target);\n if (!rec) throw new Error(`Unknown thread slug: ${target}`);\n chatIdentifier = rec.chatIdentifier;\n displayHandle = rec.slug;\n }\n\n // Default output path: ~/imsg-export-<sanitized>-<YYYY-MM-DD>.<ext>\n let outputPath: string;\n if (opts.output) {\n outputPath = isAbsolute(opts.output) ? opts.output : resolve(opts.output);\n } else {\n const today = new Date().toISOString().slice(0, 10);\n outputPath = join(\n homedir(),\n `imsg-export-${sanitizeForFilename(displayHandle)}-${today}.${ext}`,\n );\n }\n\n const parent = dirname(outputPath);\n if (!existsSync(parent)) mkdirSync(parent, { recursive: true });\n\n log(`Exporting ${displayHandle} → ${outputPath} (${format})`, \"dim\");\n\n const result = await streamExport({\n db,\n chatIdentifier,\n format,\n outputPath,\n since,\n until,\n pageSize,\n });\n\n let attachmentSummary = \"\";\n if (opts.includeAttachments) {\n const dir = opts.attachmentsDir\n ? isAbsolute(opts.attachmentsDir)\n ? opts.attachmentsDir\n : resolve(opts.attachmentsDir)\n : `${outputPath}.attachments`;\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n const filters: Parameters<typeof db.searchAttachments>[0] = {\n chatIdentifier,\n limit: 0,\n };\n if (since) filters.sinceMs = since.getTime();\n if (until) filters.untilMs = until.getTime();\n const attachments = db.searchAttachments(filters);\n\n let copied = 0;\n let totalBytes = 0;\n const seen = new Set<string>();\n for (const a of attachments) {\n if (!a.filename) continue;\n const src = a.filename.replace(/^~/, homedir());\n if (!existsSync(src)) continue;\n const baseName = a.transferName || src.split(\"/\").pop() || `att-${a.rowId}`;\n let destName = `${a.rowId}-${sanitizeForFilename(baseName)}`;\n if (seen.has(destName)) destName = `${a.rowId}-${Date.now()}-${baseName}`;\n seen.add(destName);\n const dest = join(dir, destName);\n try {\n copyFileSync(src, dest);\n copied++;\n totalBytes += statSync(dest).size;\n } catch (err) {\n log(\n ` warn: copy failed for ${src}: ${err instanceof Error ? err.message : String(err)}`,\n \"warn\",\n );\n }\n }\n attachmentSummary = `\\nAttachments: ${copied} file(s), ${(totalBytes / 1024).toFixed(1)} KB → ${dir}`;\n }\n\n log(\n [\n \"\",\n `✓ Exported ${result.count} message(s) to ${result.savedTo}`,\n ` Format: ${format}`,\n ` Range: ${result.oldest?.toISOString() ?? \"(none)\"} → ${result.newest?.toISOString() ?? \"(none)\"}`,\n ` Size: ${(result.sizeBytes / 1024).toFixed(1)} KB${attachmentSummary}`,\n ].join(\"\\n\"),\n \"ok\",\n );\n } finally {\n await db.close();\n }\n}\n\n// ── CLI program (Commander) ────────────────────────────────────────────\n\nconst program = new Command()\n .name(\"imsg\")\n .version(APP_VERSION, \"-v, --version\")\n .description(\"CLI for the imsg-mcp iMessage MCP server\")\n .addHelpText(\n \"after\",\n `\nNotes:\n Use \"imsg doctor\" on a new machine.\n Run the MCP stdio server with \"imsg mcp\".\n Launch the read-only TUI with \"imsg tui\".`,\n );\n\nprogram\n .command(\"mcp\")\n .description(\"Run the MCP stdio server\")\n .action(async () => {\n const { runMcpServer } = await import(\"./index.js\");\n await runMcpServer();\n });\n\nprogram\n .command(\"cli\")\n .alias(\"console\")\n .description(\"Launch interactive console\")\n .action(runInteractiveConsole);\n\nprogram\n .command(\"doctor\")\n .description(\"Check local permissions and database access\")\n .action(async () => {\n const report = await checkLocalAccess();\n console.log(formatAccessReport(report));\n process.exitCode = report.ok ? 0 : 1;\n });\n\nprogram\n .command(\"conversations\")\n .alias(\"list\")\n .description(\"List recent conversations\")\n .argument(\"[limit]\", \"Number of conversations\", \"20\")\n .action(async (limit: string) => {\n await withClient((c) => printToolResult(c, \"list_conversations\", { limit: Number(limit) }));\n });\n\nprogram\n .command(\"messages\")\n .alias(\"msg\")\n .description(\"Show recent messages from a conversation\")\n .argument(\"[chat]\", \"Phone number, email, or thread slug\")\n .argument(\"[limit]\", \"Number of messages\", \"20\")\n .action(async (chat: string | undefined, limit: string) => {\n const firstIsLimit = chat != null && /^\\d+$/.test(chat);\n const chatIdentifier = firstIsLimit ? undefined : chat;\n const finalLimit = Number(firstIsLimit ? chat : limit);\n await withClient((c) =>\n printToolResult(c, \"get_messages\", {\n ...(chatIdentifier\n ? looksLikeThreadSlug(chatIdentifier)\n ? { threadSlug: chatIdentifier }\n : { chatIdentifier }\n : {}),\n limit: finalLimit,\n }),\n );\n });\n\nprogram\n .command(\"unread\")\n .description(\"Show unread messages across all conversations\")\n .argument(\"[limit]\", \"Number of messages\", \"100\")\n .action(async (limit: string) => {\n await withClient((c) => printToolResult(c, \"get_unread_messages\", { limit: Number(limit) }));\n });\n\nprogram\n .command(\"search\")\n .description(\"Search messages by text content\")\n .argument(\"<query>\", \"Search query\")\n .argument(\"[limit]\", \"Number of results\", \"20\")\n .action(async (query: string, limit: string) => {\n await withClient((c) => printToolResult(c, \"search_messages\", { query, limit: Number(limit) }));\n });\n\nprogram\n .command(\"wait\")\n .description(\"Wait for a reply in a conversation\")\n .argument(\"<chat>\", \"Phone number, email, or thread slug\")\n .argument(\"[timeout]\", \"Timeout in seconds\", \"60\")\n .action(async (chat: string, timeout: string) => {\n await withClient((c) =>\n printToolResult(\n c,\n \"wait_for_reply\",\n {\n ...(looksLikeThreadSlug(chat) ? { threadSlug: chat } : { chatIdentifier: chat }),\n timeoutSeconds: Number(timeout),\n pollIntervalSeconds: 5,\n },\n 70_000,\n ),\n );\n });\n\nprogram\n .command(\"send\")\n .description(\"Send a message via Messages.app\")\n .argument(\n \"<target>\",\n \"Recipient: thread slug, E.164 phone (+61401990797), local phone (0401 990 797), iMessage email, or contact name\",\n )\n .argument(\"<message...>\", \"Message text\")\n .action(async (target: string, messageParts: string[]) => {\n await withClient((c) =>\n printToolResult(c, \"send_message\", {\n ...(looksLikeThreadSlug(target) ? { threadSlug: target } : { recipient: target }),\n message: messageParts.join(\" \"),\n }),\n );\n });\n\nconst contactsCommand = program\n .command(\"contacts\")\n .description(\"Search and inspect macOS contacts (read-only)\");\n\ncontactsCommand\n .command(\"list\")\n .description(\"List contacts, sorted by name\")\n .argument(\"[limit]\", \"Number of contacts\", \"20\")\n .argument(\"[offset]\", \"Skip this many (pagination)\", \"0\")\n .action(async (limit: string, offset: string) => {\n await withClient((c) =>\n printToolResult(c, \"list_contacts\", { limit: Number(limit), offset: Number(offset) }),\n );\n });\n\ncontactsCommand\n .command(\"search\")\n .description(\"Search contacts by name, phone, or email\")\n .argument(\"<query>\", \"Substring to match\")\n .argument(\"[limit]\", \"Number of results\", \"20\")\n .action(async (query: string, limit: string) => {\n await withClient((c) => printToolResult(c, \"search_contacts\", { query, limit: Number(limit) }));\n });\n\ncontactsCommand\n .command(\"resolve\")\n .description(\"Resolve a phone number or email to its contact name\")\n .argument(\"<handle>\", \"Phone number or email\")\n .action(async (handle: string) => {\n await withClient((c) => printToolResult(c, \"resolve_handle\", { handle }));\n });\n\ncontactsCommand\n .command(\"show\")\n .description(\"Show a contact's handles and the thread slug for each\")\n .argument(\"<handleOrId>\", \"Phone, email, or numeric contact id\")\n .action(async (handleOrId: string) => {\n const args = /^\\d+$/.test(handleOrId) ? { id: Number(handleOrId) } : { handle: handleOrId };\n await withClient((c) => printToolResult(c, \"get_contact\", args));\n });\n\nprogram\n .command(\"logs\")\n .description(\"Show server debug logs\")\n .argument(\"[tail]\", \"Show only last N lines\")\n .action(async (tail: string | undefined) => {\n await withClient((c) => printToolResult(c, \"get_logs\", tail ? { tail: Number(tail) } : {}));\n });\n\nprogram\n .command(\"last-error\")\n .description(\"Show last send_message failure details\")\n .action(async () => {\n await withClient((c) => printToolResult(c, \"get_last_send_error\", {}));\n });\n\nprogram\n .command(\"tools\")\n .description(\"List available MCP tools\")\n .action(async () => {\n await withClient(async (c) => {\n for (const tool of await c.listTools()) {\n console.log(`${tool.name}${tool.description ? ` - ${tool.description}` : \"\"}`);\n }\n });\n });\n\nprogram\n .command(\"raw\")\n .description(\"Send raw JSON-RPC params to tools/call\")\n .argument(\"<json>\", 'JSON like {\"name\":\"tool_name\",\"arguments\":{...}}')\n .action(async (json: string) => {\n const parsed = JSON.parse(json) as { name?: string; arguments?: object };\n if (!parsed.name) throw new Error('Expected JSON like {\"name\":\"tool_name\",\"arguments\":{...}}.');\n await withClient((c) => printToolResult(c, parsed.name!, parsed.arguments ?? {}));\n });\n\nprogram\n .command(\"tui\")\n .description(\"Launch the read-only terminal UI\")\n .option(\"--theme <theme>\", 'TUI theme: \"safe\" or \"powerline\"')\n .option(\"--accent <hex>\", \"TUI accent color as #RRGGBB\")\n .action(async () => {\n const { runTui } = await import(\"./tui/index.js\");\n await runTui();\n });\n\n// ── Export ───────────────────────────────────────────────────────────────\n\nprogram\n .command(\"export <target>\")\n .description(\"Export a conversation to a file (md/csv/json/ndjson)\")\n .option(\"-f, --format <fmt>\", \"Output format: md (default), csv, json, ndjson\", \"md\")\n .option(\"--since <date>\", \"Earliest date (ISO or relative, e.g. '3 months ago')\")\n .option(\"--until <date>\", \"Latest date (ISO or relative)\")\n .option(\"-o, --output <path>\", \"Output path (default: ~/imsg-export-<target>-<YYYY-MM-DD>.<ext>)\")\n .option(\"--include-attachments\", \"Copy attachments next to the export\")\n .option(\"--attachments-dir <path>\", \"Where to copy attachments (default: <output>.attachments/)\")\n .option(\"--page-size <n>\", \"Messages per DB page (100-5000)\", \"1000\")\n .action(runExportCommand);\n\n// ── Setup ────────────────────────────────────────────────────────────────\n\nprogram\n .command(\"setup\")\n .description(\"Autodetect DB paths and emit an MCP host config snippet\")\n .option(\"-w, --write <host>\", 'Write into a host config: \"claude\" or \"cursor\"')\n .option(\"-r, --runtime <runtime>\", 'Runtime command: \"npx\" (default), \"bunx\", or \"global\"')\n .option(\"--print-only\", \"Just print the snippet (default behaviour)\")\n .action(async (opts: { write?: string; runtime?: string; printOnly?: boolean }) => {\n const { probeMachine, buildMcpSnippet, writeHostConfig } = await import(\"./setup.js\");\n const report = probeMachine();\n\n if (!report.imsgDb.readable) {\n log(`✗ Messages DB is not readable: ${report.imsgDb.path}`, \"err\");\n log(` ${report.imsgDb.error ?? \"\"}`, \"err\");\n log(\n \" Grant Full Disk Access to the running app: System Settings → Privacy & Security → Full Disk Access\",\n \"warn\",\n );\n process.exitCode = 1;\n return;\n }\n\n log(`✓ Messages DB readable: ${report.imsgDb.path}`, \"ok\");\n log(\n `✓ Address Book: ${report.contactsDbs.length} source(s), ${report.contactsDbs.filter((p) => p.readable).length} readable`,\n \"ok\",\n );\n log(\n ` slugs.db: ${report.slugsDb.path} ${report.slugsDb.exists ? \"(exists)\" : \"(will be created on first run)\"}`,\n );\n\n const runtime = opts.runtime === \"bunx\" || opts.runtime === \"global\" ? opts.runtime : \"npx\";\n const snippet = buildMcpSnippet(report, { runtime });\n\n if (opts.write) {\n if (opts.write !== \"claude\" && opts.write !== \"cursor\") {\n log(`✗ unknown host: ${opts.write} (expected \"claude\" or \"cursor\")`, \"err\");\n process.exitCode = 1;\n return;\n }\n const result = writeHostConfig(opts.write, report, { runtime });\n log(\n `✓ wrote ${opts.write} config to ${result.path}${result.replaced ? \" (replaced existing imessage entry)\" : \"\"}`,\n \"ok\",\n );\n log(` backup of any prior file at ${result.path}.bak`);\n return;\n }\n\n log(\"--- snippet ---\", \"dim\");\n process.stdout.write(snippet);\n });\n\n// ── Config ───────────────────────────────────────────────────────────────\n\nconst configCmd = program\n .command(\"config\")\n .description(\"Manage TUI settings (theme, accent color)\");\n\nconfigCmd\n .command(\"show\")\n .description(\"Print resolved TUI settings and where each value came from\")\n .action(async () => {\n const { resolveTuiConfig, defaultTuiConfigPath } = await import(\"./tui-config.js\");\n const cfg = resolveTuiConfig();\n log(\n `config file : ${cfg.configPath ?? `(none — defaults; would write to ${defaultTuiConfigPath()})`}`,\n );\n log(`theme : ${cfg.theme} (from ${cfg.origin.theme})`);\n log(`accentColor : ${cfg.accentColor} (from ${cfg.origin.accentColor})`);\n if (cfg.theme === \"powerline\") {\n log(\" — powerline theme requires a Nerd Font (https://www.nerdfonts.com)\", \"warn\");\n }\n for (const w of cfg.warnings) log(w, \"warn\");\n });\n\nconfigCmd\n .command(\"edit\")\n .description(\"Open the TUI config file in $EDITOR (creates it if missing)\")\n .action(async () => {\n const { defaultTuiConfigPath, findTuiConfigPath, writeTuiConfig, DEFAULT_TUI_CONFIG } =\n await import(\"./tui-config.js\");\n const path = findTuiConfigPath() ?? defaultTuiConfigPath();\n const { existsSync } = await import(\"node:fs\");\n if (!existsSync(path)) {\n writeTuiConfig(DEFAULT_TUI_CONFIG, path);\n log(`created ${path}`, \"ok\");\n }\n const editor = process.env.EDITOR ?? \"vi\";\n const { spawn } = await import(\"node:child_process\");\n const child = spawn(editor, [path], { stdio: \"inherit\" });\n await new Promise<void>((resolve, reject) => {\n child.on(\"exit\", (code) =>\n code === 0 ? resolve() : reject(new Error(`editor exited ${code}`)),\n );\n child.on(\"error\", reject);\n });\n });\n\nprogram.action(() => {\n program.outputHelp();\n});\n\n// Only auto-parse when invoked as a script (not when imported from tests).\nconst invokedAsScript = (() => {\n try {\n const entry = process.argv[1];\n if (!entry) return false;\n return import.meta.url === pathToFileURL(realpathSync(entry)).href;\n } catch {\n return false;\n }\n})();\n\nif (invokedAsScript) {\n program.parseAsync(process.argv).catch((error) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n });\n}\n"],"names":["existsSync","dirname","join","spawn"],"mappings":"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"}
|
|
@@ -79,12 +79,15 @@ function toJSON(messages, header) {
|
|
|
79
79
|
return JSON.stringify(data, null, 2);
|
|
80
80
|
}
|
|
81
81
|
function toNDJSONLine(m) {
|
|
82
|
-
|
|
82
|
+
const entry = {
|
|
83
83
|
...m,
|
|
84
84
|
date: m.date.toISOString(),
|
|
85
85
|
dateRead: m.dateRead?.toISOString() ?? null,
|
|
86
86
|
dateDelivered: m.dateDelivered?.toISOString() ?? null
|
|
87
|
-
}
|
|
87
|
+
};
|
|
88
|
+
return JSON.stringify(
|
|
89
|
+
Object.fromEntries(Object.entries(entry).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0))
|
|
90
|
+
);
|
|
88
91
|
}
|
|
89
92
|
function extensionFor(format) {
|
|
90
93
|
switch (format) {
|
|
@@ -105,4 +108,4 @@ export {
|
|
|
105
108
|
extensionFor as e,
|
|
106
109
|
toJSON as t
|
|
107
110
|
};
|
|
108
|
-
//# sourceMappingURL=exportFormats-
|
|
111
|
+
//# sourceMappingURL=exportFormats-wytSyBTp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exportFormats-wytSyBTp.js","sources":["../src/tui/exportFormats.ts"],"sourcesContent":["/**\n * Format converters for exporting messages from the TUI.\n *\n * All three formats accept the same Message[] + optional header info.\n * Keep these pure (no fs / no React) — the modal + MCP export tool both\n * call into them.\n */\nimport type { Message } from \"../types.js\";\n\nexport interface ExportHeader {\n thread: string;\n participants?: string[];\n serviceType?: string;\n}\n\nfunction fmtDate(d: Date): string {\n return d\n .toISOString()\n .replace(\"T\", \" \")\n .replace(/\\.\\d+Z$/, \"\");\n}\n\nfunction senderLabel(m: Message): string {\n if (m.isFromMe) return \"Me\";\n return m.displayName ?? m.handle;\n}\n\n/** Markdown — human-readable, suitable for sharing or further processing. */\nexport function toMarkdown(messages: Message[], header: ExportHeader): string {\n const lines: string[] = [];\n lines.push(`# ${header.thread}`);\n lines.push(\"\");\n if (header.participants && header.participants.length > 0) {\n lines.push(`**Participants**: ${header.participants.join(\", \")}`);\n }\n if (header.serviceType) {\n lines.push(`**Service**: ${header.serviceType}`);\n }\n lines.push(`**Exported**: ${fmtDate(new Date())} (${messages.length} messages)`);\n lines.push(\"\");\n lines.push(\"---\");\n lines.push(\"\");\n\n for (const m of messages) {\n if (m.isReply && m.replyTo?.replyToText) {\n lines.push(`> ${m.replyTo.replyToText.replace(/\\n/g, \"\\n> \")}`);\n }\n const ts = `[${fmtDate(m.date)}]`;\n const sender = senderLabel(m);\n const text = m.text ?? (m.hasAttachments ? \"*(attachment)*\" : \"*(no text)*\");\n const flags: string[] = [];\n if (m.isEdited) flags.push(\"edited\");\n if (m.hasAttachments) flags.push(\"📎\");\n const flagStr = flags.length > 0 ? ` _(${flags.join(\", \")})_` : \"\";\n lines.push(`**${ts}** ${sender}: ${text}${flagStr}`);\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\n/** CSV — for spreadsheet import. RFC 4180-ish quoting. */\nexport function toCSV(messages: Message[]): string {\n const escapeCsv = (v: string | null | undefined): string => {\n if (v == null) return \"\";\n if (/[\",\\n\\r]/.test(v)) {\n return `\"${v.replace(/\"/g, '\"\"')}\"`;\n }\n return v;\n };\n\n const lines: string[] = [];\n lines.push(\n \"id,date,sender,handle,is_from_me,is_read,is_reply,reply_to_text,text,has_attachments\",\n );\n\n for (const m of messages) {\n const row = [\n String(m.id),\n m.date.toISOString(),\n escapeCsv(senderLabel(m)),\n escapeCsv(m.handle),\n m.isFromMe ? \"1\" : \"0\",\n m.isRead ? \"1\" : \"0\",\n m.isReply ? \"1\" : \"0\",\n escapeCsv(m.replyTo?.replyToText ?? \"\"),\n escapeCsv(m.text ?? \"\"),\n m.hasAttachments ? \"1\" : \"0\",\n ];\n lines.push(row.join(\",\"));\n }\n\n return lines.join(\"\\n\");\n}\n\n/** JSON — full message objects, indented, dates as ISO strings. */\nexport function toJSON(messages: Message[], header?: ExportHeader): string {\n const data = {\n ...(header ?? {}),\n exportedAt: new Date().toISOString(),\n count: messages.length,\n messages: messages.map((m) => ({\n ...m,\n date: m.date.toISOString(),\n dateRead: m.dateRead?.toISOString() ?? null,\n dateDelivered: m.dateDelivered?.toISOString() ?? null,\n })),\n };\n return JSON.stringify(data, null, 2);\n}\n\n/** NDJSON — newline-delimited JSON, ideal for streaming exports. */\nexport function toNDJSONLine(m: Message): string {\n const entry: Record<string, unknown> = {\n ...m,\n date: m.date.toISOString(),\n dateRead: m.dateRead?.toISOString() ?? null,\n dateDelivered: m.dateDelivered?.toISOString() ?? null,\n };\n // Sort top-level keys so the same data always serializes to the same bytes.\n // Message objects come from different construction paths (TS parser, native\n // parser, cache hydration) whose property insertion order can differ, which\n // made re-exports of identical data diff at the byte level.\n return JSON.stringify(\n Object.fromEntries(Object.entries(entry).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))),\n );\n}\n\n/** Choose extension for a given format. */\nexport function extensionFor(format: \"markdown\" | \"csv\" | \"json\" | \"ndjson\"): string {\n switch (format) {\n case \"markdown\":\n return \"md\";\n case \"csv\":\n return \"csv\";\n case \"json\":\n return \"json\";\n case \"ndjson\":\n return \"ndjson\";\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createWriteStream, statSync } from "node:fs";
|
|
2
|
-
import { b as toMarkdown, a as toCSV, c as toNDJSONLine } from "./exportFormats-
|
|
2
|
+
import { b as toMarkdown, a as toCSV, c as toNDJSONLine } from "./exportFormats-wytSyBTp.js";
|
|
3
3
|
function writeAndDrain(stream, chunk) {
|
|
4
4
|
return new Promise((resolve, reject) => {
|
|
5
5
|
if (stream.write(chunk)) {
|
|
@@ -131,4 +131,4 @@ async function streamExport(opts) {
|
|
|
131
131
|
export {
|
|
132
132
|
streamExport
|
|
133
133
|
};
|
|
134
|
-
//# sourceMappingURL=exportStream-
|
|
134
|
+
//# sourceMappingURL=exportStream-BCoZYUUZ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exportStream-
|
|
1
|
+
{"version":3,"file":"exportStream-BCoZYUUZ.js","sources":["../src/exportStream.ts"],"sourcesContent":["/**\n * Streaming exporter — writes a conversation to a file in pages so a 100k\n * message history doesn't blow up memory. Used by the `export_messages`\n * MCP tool.\n *\n * Strategy:\n * 1. Walk history chronologically with a stable `(date, ROWID)` cursor.\n * 2. Each page is formatted and written immediately to a writable stream.\n * 3. For JSON we emit a top-level `{ ..., messages: [...] }` shape, opening\n * the array, comma-separating page entries, then closing — that way we\n * never hold the full array in memory.\n * 4. NDJSON is the simplest streaming option (one JSON object per line) —\n * recommended for large exports.\n */\n\nimport { createWriteStream, statSync } from \"node:fs\";\nimport type { IMessageDB, MessageExportCursor, UnmergedSiblingChat } from \"./imessage-db.js\";\nimport { toCSV, toMarkdown, toNDJSONLine } from \"./tui/exportFormats.js\";\n\nexport interface ExportOptions {\n db: IMessageDB;\n chatIdentifier: string;\n format: \"markdown\" | \"csv\" | \"json\" | \"ndjson\";\n outputPath: string;\n since: Date | null;\n until: Date | null;\n pageSize: number;\n signal?: AbortSignal;\n}\n\nexport interface ExportResult {\n savedTo: string;\n count: number;\n oldest: Date | null;\n newest: Date | null;\n sizeBytes: number;\n unmergedSiblings: UnmergedSiblingChat[];\n}\n\n/** Type guard — Node ESM filesystem stream */\ntype WritableStream = ReturnType<typeof createWriteStream>;\n\nfunction writeAndDrain(stream: WritableStream, chunk: string): Promise<void> {\n return new Promise((resolve, reject) => {\n if (stream.write(chunk)) {\n resolve();\n return;\n }\n // Back-pressure path: register both listeners and ensure either side\n // also removes the OTHER. Pre-fix only the firing side cleaned up\n // (via .once), so each back-pressure tick leaked a stale \"error\"\n // listener — exporting a 26k-msg thread triggered the MaxListeners\n // warning around the 11th tick.\n const onDrain = () => {\n stream.removeListener(\"error\", onError);\n resolve();\n };\n const onError = (err: Error) => {\n stream.removeListener(\"drain\", onDrain);\n reject(err);\n };\n stream.once(\"drain\", onDrain);\n stream.once(\"error\", onError);\n });\n}\n\nfunction endStream(stream: WritableStream): Promise<void> {\n return new Promise((resolve, reject) => {\n stream.end((err?: Error | null) => (err ? reject(err) : resolve()));\n });\n}\n\nexport async function streamExport(opts: ExportOptions): Promise<ExportResult> {\n const { db, chatIdentifier, format, outputPath, since, until, pageSize, signal } = opts;\n\n const stream = createWriteStream(outputPath, { encoding: \"utf8\" });\n\n let count = 0;\n let oldest: Date | null = null;\n let newest: Date | null = null;\n let cursor: MessageExportCursor | null = null;\n\n try {\n if (signal?.aborted) throw new Error(\"Export cancelled by client.\");\n\n // Header\n if (format === \"markdown\") {\n const conv = await db.findChatByHandle(chatIdentifier);\n const name = conv?.displayName ?? conv?.rawIdentifier ?? chatIdentifier;\n await writeAndDrain(stream, `# ${name}\\n\\n`);\n if (conv?.threadSlug) await writeAndDrain(stream, `**Slug**: \\`${conv.threadSlug}\\`\\n`);\n if (since) await writeAndDrain(stream, `**Since**: ${since.toISOString()}\\n`);\n if (until) await writeAndDrain(stream, `**Until**: ${until.toISOString()}\\n`);\n await writeAndDrain(stream, `**Exported**: ${new Date().toISOString()}\\n\\n---\\n\\n`);\n } else if (format === \"csv\") {\n await writeAndDrain(\n stream,\n \"id,date,sender,handle,is_from_me,is_read,is_reply,reply_to_text,text,has_attachments\\n\",\n );\n } else if (format === \"json\") {\n await writeAndDrain(\n stream,\n `{\\n \"chatIdentifier\": ${JSON.stringify(chatIdentifier)},\\n \"exportedAt\": ${JSON.stringify(new Date().toISOString())},\\n \"messages\": [\\n`,\n );\n }\n // ndjson: no header\n\n let firstJson = true;\n\n // Page through history in chronological order. The export-specific DB\n // method applies date bounds in SQL and dedupes merged chat rows before\n // LIMIT so every normal message gets one chance to be exported.\n while (true) {\n if (signal?.aborted) throw new Error(\"Export cancelled by client.\");\n const page = await db.getMessagesForChatExportPage(chatIdentifier, pageSize, {\n includeReactionDetails: true,\n afterCursor: cursor,\n since,\n until,\n });\n if (page.messages.length === 0) break;\n\n // Track running oldest/newest for the result\n for (const m of page.messages) {\n if (oldest == null || m.date < oldest) oldest = m.date;\n if (newest == null || m.date > newest) newest = m.date;\n }\n\n // Format and write\n if (page.messages.length > 0) {\n if (format === \"markdown\") {\n await writeAndDrain(\n stream,\n toMarkdown(page.messages, { thread: chatIdentifier }).split(\"\\n\").slice(6).join(\"\\n\"),\n );\n // ^ slice(6) skips the per-page header (6 lines without participants) — we wrote it once above\n } else if (format === \"csv\") {\n // toCSV emits its own header; skip the first line on each page\n const csvBody = toCSV(page.messages).split(\"\\n\").slice(1).join(\"\\n\");\n await writeAndDrain(stream, `${csvBody}\\n`);\n } else if (format === \"json\") {\n for (const m of page.messages) {\n if (!firstJson) await writeAndDrain(stream, \",\\n\");\n await writeAndDrain(stream, ` ${toNDJSONLine(m)}`);\n firstJson = false;\n }\n } else if (format === \"ndjson\") {\n for (const m of page.messages) {\n await writeAndDrain(stream, `${toNDJSONLine(m)}\\n`);\n }\n }\n }\n count += page.messages.length;\n\n cursor = page.nextCursor;\n if (!cursor || page.rawCount < pageSize) break; // last page\n\n // Yield so other tasks (e.g. health_check) can run\n await new Promise((r) => setImmediate(r));\n }\n\n // Footer\n if (format === \"json\") {\n await writeAndDrain(stream, `\\n ],\\n \"count\": ${count}\\n}\\n`);\n }\n\n await endStream(stream);\n const sizeBytes = (() => {\n try {\n return statSync(outputPath).size;\n } catch {\n return 0;\n }\n })();\n\n const unmergedSiblings = db.findUnmergedSiblingChats(chatIdentifier);\n\n return { savedTo: outputPath, count, oldest, newest, sizeBytes, unmergedSiblings };\n } catch (error) {\n stream.destroy();\n throw error;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import Database from "better-sqlite3";
|
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { existsSync, readdirSync, mkdirSync } from "node:fs";
|
|
7
|
-
import { O as OBJECT_REPLACEMENT_CHAR, p as perf, J as isGroupGuid, K as isGroupChatIdentifier, L as generateThreadSlug, T as Tables, M as AssociatedMessageType, N as macAutoTimestampToDate, P as isReactionType, Q as MAC_EPOCH_OFFSET, R as NANOS_PER_SECOND, S as parseAssociatedMessageGuid$1, U as macTimestampToDate$1 } from "./shutdown-
|
|
7
|
+
import { O as OBJECT_REPLACEMENT_CHAR, p as perf, J as isGroupGuid, K as isGroupChatIdentifier, L as generateThreadSlug, T as Tables, M as AssociatedMessageType, N as macAutoTimestampToDate, P as isReactionType, Q as MAC_EPOCH_OFFSET, R as NANOS_PER_SECOND, S as parseAssociatedMessageGuid$1, U as macTimestampToDate$1 } from "./shutdown-pPAPSnk3.js";
|
|
8
8
|
import { parseBuffer } from "bplist-parser";
|
|
9
9
|
function normalizePhoneNumber(phone) {
|
|
10
10
|
const digits = phone.replace(/\D/g, "");
|
|
@@ -1051,6 +1051,11 @@ class IMessageDB {
|
|
|
1051
1051
|
* the same merged chats dozens of times; without this, every page re-scanned
|
|
1052
1052
|
* each chat's full reaction set — O(pages × chats) — which dominated big
|
|
1053
1053
|
* exports and spiked event-loop lag.
|
|
1054
|
+
*
|
|
1055
|
+
* Invalidation contract: entries expire via their per-entry TTL on read, and
|
|
1056
|
+
* the whole map is cleared only in scheduleBackgroundRefresh() — every read
|
|
1057
|
+
* path that can observe new messages schedules that refresh, so there is no
|
|
1058
|
+
* other clear site. Add one if you introduce a read path that bypasses it.
|
|
1054
1059
|
*/
|
|
1055
1060
|
cachedReactionsByChat = /* @__PURE__ */ new Map();
|
|
1056
1061
|
backgroundSyncNeeded = true;
|
|
@@ -2657,4 +2662,4 @@ export {
|
|
|
2657
2662
|
imessageDb as i,
|
|
2658
2663
|
normalizedPhoneVariants as n
|
|
2659
2664
|
};
|
|
2660
|
-
//# sourceMappingURL=imessage-db-
|
|
2665
|
+
//# sourceMappingURL=imessage-db-Cz8MIIpG.js.map
|