imsg-mcp 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@
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.10.0](https://github.com/george43g/imsg-mcp/compare/v1.9.0...v1.10.0) (2026-07-21)
7
+
8
+
9
+ ### Features
10
+
11
+ * **core:** media-intel interpretation service, cache & providers ([d6441de](https://github.com/george43g/imsg-mcp/commit/d6441de0248336f9a1fe04a709a8541c13476527))
12
+
6
13
  # [1.9.0](https://github.com/george43g/imsg-mcp/compare/v1.8.0...v1.9.0) (2026-07-21)
7
14
 
8
15
 
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { createInterface } from "node:readline";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import { Command } from "commander";
6
6
  import { c as checkLocalAccess, f as formatAccessReport, J as IMPLEMENTED_TYPES, K as ANALYTIC_INFO, w as installShutdownHandlers, x as registerCleanup, L as looksLikeThreadSlug } from "./shutdown-CQ9wzrxA.js";
7
- import { A as APP_VERSION, t as toYaml } from "./meta-BrYyl7Ls.js";
7
+ import { A as APP_VERSION, t as toYaml } from "./meta-D6ZUMB_B.js";
8
8
  import { spawn } from "node:child_process";
9
9
  import { join, dirname } from "node:path";
10
10
  function distRoot() {
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import Database from "better-sqlite3";
12
12
  import { n as normalizedPhoneVariants, h as hasNativeModule, I as IMessageDB, r as rankFuzzy } from "./imessage-db-vrzopRGT.js";
13
13
  import { parseUserDate } from "./date-parse-DJXMfq3a.js";
14
14
  import { streamExport } from "./exportStream-DgPVRZTZ.js";
15
- import { r as renderAnalyticText, A as APP_VERSION, a as APP_NAME } from "./meta-BrYyl7Ls.js";
15
+ import { r as renderAnalyticText, A as APP_VERSION, a as APP_NAME } from "./meta-D6ZUMB_B.js";
16
16
  import { randomUUID } from "node:crypto";
17
17
  import { z } from "zod";
18
18
  import { m as minMessageId, r as resolveRecipient, d as defaultCountryFromEnv, i as installWatchdog, n as noteActivity, a as readWatchdogState } from "./watchdog-Cl5PoHhW.js";
@@ -155,11 +155,11 @@ function yamlScalar(s) {
155
155
  return needsQuote ? `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"` : s;
156
156
  }
157
157
  const APP_NAME = "imsg-mcp";
158
- const APP_VERSION = "1.9.0";
158
+ const APP_VERSION = "1.10.0";
159
159
  export {
160
160
  APP_VERSION as A,
161
161
  APP_NAME as a,
162
162
  renderAnalyticText as r,
163
163
  toYaml as t
164
164
  };
165
- //# sourceMappingURL=meta-BrYyl7Ls.js.map
165
+ //# sourceMappingURL=meta-D6ZUMB_B.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"meta-BrYyl7Ls.js","sources":["../src/analytics-render.ts","../src/meta.ts"],"sourcesContent":["/**\n * Human-readable text rendering for every chat_analytics type, shared by the\n * MCP tool output and the CLI/console. Keeping it in one place means the agent\n * (via the tool's text block) and a person (via `imsg analytics …`) see the\n * same summary, and adding an analytic only needs one renderer.\n */\n\nimport type {\n AnalyticType,\n DoubleTextResult,\n HeatmapResult,\n RelationshipScore,\n ResponseTimeStats,\n StreakResult,\n TapbackResult,\n WrappedResult,\n} from \"./analytics.js\";\n\n/** ms → compact human duration (e.g. 4m, 2h, 3d). */\nfunction humanDuration(ms: number): string {\n if (!Number.isFinite(ms) || ms < 0) return \"—\";\n const s = Math.round(ms / 1000);\n if (s < 60) return `${s}s`;\n const m = Math.round(s / 60);\n if (m < 60) return `${m}m`;\n const h = Math.round(m / 60);\n if (h < 24) return `${h}h`;\n return `${Math.round(h / 24)}d`;\n}\n\nconst TOP = 20;\n\nfunction renderStreaks(rows: StreakResult[]): string {\n if (rows.length === 0) return \"(no streaks in window)\";\n return rows\n .slice(0, TOP)\n .map((r, i) => {\n const span =\n r.longestStreakStart && r.longestStreakEnd\n ? ` (${r.longestStreakStart}→${r.longestStreakEnd})`\n : \"\";\n const cur = r.currentStreakDays > 0 ? `, current ${r.currentStreakDays}d` : \"\";\n return `${String(i + 1).padStart(2)}. ${r.contact} — longest ${r.longestStreakDays}d${span}${cur}`;\n })\n .join(\"\\n\");\n}\n\nfunction renderDoubleTexts(rows: DoubleTextResult[]): string {\n if (rows.length === 0) return \"(no double-texts in window)\";\n return rows\n .slice(0, TOP)\n .map(\n (r, i) =>\n `${String(i + 1).padStart(2)}. ${r.contact} — you ${r.doubleTextsFromMe}×, them ${r.doubleTextsFromThem}×`,\n )\n .join(\"\\n\");\n}\n\nfunction renderResponseTimes(rows: ResponseTimeStats[]): string {\n if (rows.length === 0) return \"(no response-time data in window)\";\n return rows\n .slice(0, TOP)\n .map(\n (r, i) =>\n `${String(i + 1).padStart(2)}. ${r.contact} — median ${humanDuration(r.medianMs)}, p95 ${humanDuration(r.p95Ms)}, mean ${humanDuration(r.meanMs)} (n=${r.count})`,\n )\n .join(\"\\n\");\n}\n\nconst DOW = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nconst HEAT = \" ░▒▓█\";\n\nfunction renderHeatmap(result: HeatmapResult): string {\n if (result.total === 0) return \"(no activity in window)\";\n const max = Math.max(...result.grid.flat());\n const header = ` ${Array.from({ length: 24 }, (_, h) => String(h % 10)).join(\"\")}`;\n const rows = result.grid.map((row, dow) => {\n const cells = row\n .map((v) => {\n if (v === 0) return HEAT[0];\n const level = Math.min(HEAT.length - 1, 1 + Math.floor((v / max) * (HEAT.length - 2)));\n return HEAT[level];\n })\n .join(\"\");\n return `${DOW[dow]} ${cells}`;\n });\n return `${header}\\n${rows.join(\"\\n\")}\\n(${result.total} messages; each column = hour 0–23)`;\n}\n\nfunction renderTapbacks(rows: TapbackResult[]): string {\n if (rows.length === 0) return \"(no tapbacks in window)\";\n return rows\n .slice(0, TOP)\n .map((r, i) => {\n const parts = [\n r.heart && `❤️${r.heart}`,\n r.thumbsUp && `👍${r.thumbsUp}`,\n r.thumbsDown && `👎${r.thumbsDown}`,\n r.haha && `😂${r.haha}`,\n r.exclaim && `‼️${r.exclaim}`,\n r.question && `❓${r.question}`,\n r.emoji && `🙂${r.emoji}`,\n ].filter(Boolean);\n return `${String(i + 1).padStart(2)}. ${r.contact} — ${parts.join(\" \")} (total ${r.total})`;\n })\n .join(\"\\n\");\n}\n\nfunction renderWrapped(w: WrappedResult): string {\n const lines = [\n `Window: ${w.windowStart} → ${w.windowEnd}`,\n `Sent: ${w.totalSent.toLocaleString()} Received: ${w.totalReceived.toLocaleString()} Reactions: ${w.totalReactions.toLocaleString()}`,\n w.peakDay ? `Peak day: ${w.peakDay.date} (${w.peakDay.count} messages)` : \"Peak day: —\",\n w.longestStreakContact\n ? `Longest streak: ${w.longestStreakDays}d with ${w.longestStreakContact}`\n : `Longest streak: ${w.longestStreakDays}d`,\n \"\",\n \"Top contacts:\",\n ...w.topContacts\n .slice(0, 10)\n .map(\n (c, i) =>\n `${String(i + 1).padStart(2)}. ${c.contact} — ${c.total} (↑${c.sent} / ↓${c.received})`,\n ),\n ];\n return lines.join(\"\\n\");\n}\n\nfunction renderLeaderboard(rows: RelationshipScore[]): string {\n if (rows.length === 0) return \"(no ranked relationships in window)\";\n const max = rows[0]?.score || 0;\n return rows\n .slice(0, TOP)\n .map((r, i) => {\n const rel = max > 0 ? Math.round((r.score / max) * 100) : 0;\n return `${String(i + 1).padStart(2)}. ${r.contact} — ${r.total} msgs, ${Math.round(\n r.reciprocity * 100,\n )}% reciprocity, last ${Math.round(r.daysSinceLast)}d ago (score ${rel})`;\n })\n .join(\"\\n\");\n}\n\n/** Render any analytic's `data` payload as human-readable text. */\nexport function renderAnalyticText(type: AnalyticType, data: unknown): string {\n switch (type) {\n case \"messaging_streaks\":\n return renderStreaks(\n (data as { streaks?: StreakResult[] }).streaks ?? (data as StreakResult[]),\n );\n case \"double_texts\":\n return renderDoubleTexts(\n (data as { doubleTexts?: DoubleTextResult[] }).doubleTexts ?? (data as DoubleTextResult[]),\n );\n case \"response_time_stats\":\n return renderResponseTimes(\n (data as { responseTimes?: ResponseTimeStats[] }).responseTimes ??\n (data as ResponseTimeStats[]),\n );\n case \"daily_heatmap\":\n return renderHeatmap(data as HeatmapResult);\n case \"tapback_summary\":\n return renderTapbacks(\n (data as { tapbacks?: TapbackResult[] }).tapbacks ?? (data as TapbackResult[]),\n );\n case \"year_in_review_wrapped\":\n return renderWrapped(data as WrappedResult);\n case \"relationship_leaderboard\":\n return renderLeaderboard((data as { leaderboard?: RelationshipScore[] }).leaderboard ?? []);\n }\n}\n\n/**\n * Minimal YAML serializer for analytic output (`--yaml`). Zero-dep on purpose;\n * handles the shapes analytics produce — nested objects, arrays, strings,\n * numbers, booleans, null. Not a general-purpose emitter.\n */\nexport function toYaml(value: unknown, indent = 0): string {\n const pad = \" \".repeat(indent);\n if (value === null || value === undefined) return \"null\";\n if (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n if (typeof value === \"string\") return yamlScalar(value);\n if (Array.isArray(value)) {\n if (value.length === 0) return \"[]\";\n return value\n .map((item) => {\n const rendered = toYaml(item, indent + 1);\n if (isComposite(item)) {\n // Nested block: put the first key inline after \"- \", rest indented.\n return `${pad}-\\n${rendered}`;\n }\n return `${pad}- ${rendered}`;\n })\n .join(\"\\n\");\n }\n if (typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>);\n if (entries.length === 0) return \"{}\";\n return entries\n .map(([k, v]) => {\n if (isComposite(v)) {\n return `${pad}${k}:\\n${toYaml(v, indent + 1)}`;\n }\n return `${pad}${k}: ${toYaml(v, indent)}`;\n })\n .join(\"\\n\");\n }\n return yamlScalar(String(value));\n}\n\nfunction isComposite(v: unknown): boolean {\n return typeof v === \"object\" && v !== null && (Array.isArray(v) ? v.length > 0 : true);\n}\n\nfunction yamlScalar(s: string): string {\n // Conservatively quote any string a YAML parser could misread as a non-string.\n // Critically this includes phone numbers (\"+15550000119\" would otherwise\n // parse as the integer 15550000119, dropping the \"+\") and keyword-ish values.\n const needsQuote =\n s === \"\" ||\n /^\\s|\\s$/.test(s) || // leading/trailing whitespace\n /[\\n\"\\\\]/.test(s) || // control / quote / backslash\n /^[-?:,[\\]{}&*!|>%@`#'\"]/.test(s) || // starts with a YAML indicator\n /:(\\s|$)|\\s#/.test(s) || // \": \" (mapping) or \" #\" (comment) in structural position\n /^[+-]?(\\d|\\.\\d)/.test(s) || // number-ish, incl. \"+61…\" phone numbers\n /^(true|false|yes|no|on|off|null|~)$/i.test(s) || // YAML keywords\n /^[+-]?\\.?(inf|nan)$/i.test(s);\n return needsQuote\n ? `\"${s.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \"\\\\n\")}\"`\n : s;\n}\n","// Injected by Vite `define` from package.json at build/test time.\ndeclare const __APP_VERSION__: string;\n\nexport const APP_NAME = \"imsg-mcp\";\n// Fallback covers running un-built source without Vite (e.g. plain tsx).\nexport const APP_VERSION = typeof __APP_VERSION__ !== \"undefined\" ? __APP_VERSION__ : \"0.0.0-dev\";\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"meta-D6ZUMB_B.js","sources":["../src/analytics-render.ts","../src/meta.ts"],"sourcesContent":["/**\n * Human-readable text rendering for every chat_analytics type, shared by the\n * MCP tool output and the CLI/console. Keeping it in one place means the agent\n * (via the tool's text block) and a person (via `imsg analytics …`) see the\n * same summary, and adding an analytic only needs one renderer.\n */\n\nimport type {\n AnalyticType,\n DoubleTextResult,\n HeatmapResult,\n RelationshipScore,\n ResponseTimeStats,\n StreakResult,\n TapbackResult,\n WrappedResult,\n} from \"./analytics.js\";\n\n/** ms → compact human duration (e.g. 4m, 2h, 3d). */\nfunction humanDuration(ms: number): string {\n if (!Number.isFinite(ms) || ms < 0) return \"—\";\n const s = Math.round(ms / 1000);\n if (s < 60) return `${s}s`;\n const m = Math.round(s / 60);\n if (m < 60) return `${m}m`;\n const h = Math.round(m / 60);\n if (h < 24) return `${h}h`;\n return `${Math.round(h / 24)}d`;\n}\n\nconst TOP = 20;\n\nfunction renderStreaks(rows: StreakResult[]): string {\n if (rows.length === 0) return \"(no streaks in window)\";\n return rows\n .slice(0, TOP)\n .map((r, i) => {\n const span =\n r.longestStreakStart && r.longestStreakEnd\n ? ` (${r.longestStreakStart}→${r.longestStreakEnd})`\n : \"\";\n const cur = r.currentStreakDays > 0 ? `, current ${r.currentStreakDays}d` : \"\";\n return `${String(i + 1).padStart(2)}. ${r.contact} — longest ${r.longestStreakDays}d${span}${cur}`;\n })\n .join(\"\\n\");\n}\n\nfunction renderDoubleTexts(rows: DoubleTextResult[]): string {\n if (rows.length === 0) return \"(no double-texts in window)\";\n return rows\n .slice(0, TOP)\n .map(\n (r, i) =>\n `${String(i + 1).padStart(2)}. ${r.contact} — you ${r.doubleTextsFromMe}×, them ${r.doubleTextsFromThem}×`,\n )\n .join(\"\\n\");\n}\n\nfunction renderResponseTimes(rows: ResponseTimeStats[]): string {\n if (rows.length === 0) return \"(no response-time data in window)\";\n return rows\n .slice(0, TOP)\n .map(\n (r, i) =>\n `${String(i + 1).padStart(2)}. ${r.contact} — median ${humanDuration(r.medianMs)}, p95 ${humanDuration(r.p95Ms)}, mean ${humanDuration(r.meanMs)} (n=${r.count})`,\n )\n .join(\"\\n\");\n}\n\nconst DOW = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nconst HEAT = \" ░▒▓█\";\n\nfunction renderHeatmap(result: HeatmapResult): string {\n if (result.total === 0) return \"(no activity in window)\";\n const max = Math.max(...result.grid.flat());\n const header = ` ${Array.from({ length: 24 }, (_, h) => String(h % 10)).join(\"\")}`;\n const rows = result.grid.map((row, dow) => {\n const cells = row\n .map((v) => {\n if (v === 0) return HEAT[0];\n const level = Math.min(HEAT.length - 1, 1 + Math.floor((v / max) * (HEAT.length - 2)));\n return HEAT[level];\n })\n .join(\"\");\n return `${DOW[dow]} ${cells}`;\n });\n return `${header}\\n${rows.join(\"\\n\")}\\n(${result.total} messages; each column = hour 0–23)`;\n}\n\nfunction renderTapbacks(rows: TapbackResult[]): string {\n if (rows.length === 0) return \"(no tapbacks in window)\";\n return rows\n .slice(0, TOP)\n .map((r, i) => {\n const parts = [\n r.heart && `❤️${r.heart}`,\n r.thumbsUp && `👍${r.thumbsUp}`,\n r.thumbsDown && `👎${r.thumbsDown}`,\n r.haha && `😂${r.haha}`,\n r.exclaim && `‼️${r.exclaim}`,\n r.question && `❓${r.question}`,\n r.emoji && `🙂${r.emoji}`,\n ].filter(Boolean);\n return `${String(i + 1).padStart(2)}. ${r.contact} — ${parts.join(\" \")} (total ${r.total})`;\n })\n .join(\"\\n\");\n}\n\nfunction renderWrapped(w: WrappedResult): string {\n const lines = [\n `Window: ${w.windowStart} → ${w.windowEnd}`,\n `Sent: ${w.totalSent.toLocaleString()} Received: ${w.totalReceived.toLocaleString()} Reactions: ${w.totalReactions.toLocaleString()}`,\n w.peakDay ? `Peak day: ${w.peakDay.date} (${w.peakDay.count} messages)` : \"Peak day: —\",\n w.longestStreakContact\n ? `Longest streak: ${w.longestStreakDays}d with ${w.longestStreakContact}`\n : `Longest streak: ${w.longestStreakDays}d`,\n \"\",\n \"Top contacts:\",\n ...w.topContacts\n .slice(0, 10)\n .map(\n (c, i) =>\n `${String(i + 1).padStart(2)}. ${c.contact} — ${c.total} (↑${c.sent} / ↓${c.received})`,\n ),\n ];\n return lines.join(\"\\n\");\n}\n\nfunction renderLeaderboard(rows: RelationshipScore[]): string {\n if (rows.length === 0) return \"(no ranked relationships in window)\";\n const max = rows[0]?.score || 0;\n return rows\n .slice(0, TOP)\n .map((r, i) => {\n const rel = max > 0 ? Math.round((r.score / max) * 100) : 0;\n return `${String(i + 1).padStart(2)}. ${r.contact} — ${r.total} msgs, ${Math.round(\n r.reciprocity * 100,\n )}% reciprocity, last ${Math.round(r.daysSinceLast)}d ago (score ${rel})`;\n })\n .join(\"\\n\");\n}\n\n/** Render any analytic's `data` payload as human-readable text. */\nexport function renderAnalyticText(type: AnalyticType, data: unknown): string {\n switch (type) {\n case \"messaging_streaks\":\n return renderStreaks(\n (data as { streaks?: StreakResult[] }).streaks ?? (data as StreakResult[]),\n );\n case \"double_texts\":\n return renderDoubleTexts(\n (data as { doubleTexts?: DoubleTextResult[] }).doubleTexts ?? (data as DoubleTextResult[]),\n );\n case \"response_time_stats\":\n return renderResponseTimes(\n (data as { responseTimes?: ResponseTimeStats[] }).responseTimes ??\n (data as ResponseTimeStats[]),\n );\n case \"daily_heatmap\":\n return renderHeatmap(data as HeatmapResult);\n case \"tapback_summary\":\n return renderTapbacks(\n (data as { tapbacks?: TapbackResult[] }).tapbacks ?? (data as TapbackResult[]),\n );\n case \"year_in_review_wrapped\":\n return renderWrapped(data as WrappedResult);\n case \"relationship_leaderboard\":\n return renderLeaderboard((data as { leaderboard?: RelationshipScore[] }).leaderboard ?? []);\n }\n}\n\n/**\n * Minimal YAML serializer for analytic output (`--yaml`). Zero-dep on purpose;\n * handles the shapes analytics produce — nested objects, arrays, strings,\n * numbers, booleans, null. Not a general-purpose emitter.\n */\nexport function toYaml(value: unknown, indent = 0): string {\n const pad = \" \".repeat(indent);\n if (value === null || value === undefined) return \"null\";\n if (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n if (typeof value === \"string\") return yamlScalar(value);\n if (Array.isArray(value)) {\n if (value.length === 0) return \"[]\";\n return value\n .map((item) => {\n const rendered = toYaml(item, indent + 1);\n if (isComposite(item)) {\n // Nested block: put the first key inline after \"- \", rest indented.\n return `${pad}-\\n${rendered}`;\n }\n return `${pad}- ${rendered}`;\n })\n .join(\"\\n\");\n }\n if (typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>);\n if (entries.length === 0) return \"{}\";\n return entries\n .map(([k, v]) => {\n if (isComposite(v)) {\n return `${pad}${k}:\\n${toYaml(v, indent + 1)}`;\n }\n return `${pad}${k}: ${toYaml(v, indent)}`;\n })\n .join(\"\\n\");\n }\n return yamlScalar(String(value));\n}\n\nfunction isComposite(v: unknown): boolean {\n return typeof v === \"object\" && v !== null && (Array.isArray(v) ? v.length > 0 : true);\n}\n\nfunction yamlScalar(s: string): string {\n // Conservatively quote any string a YAML parser could misread as a non-string.\n // Critically this includes phone numbers (\"+15550000119\" would otherwise\n // parse as the integer 15550000119, dropping the \"+\") and keyword-ish values.\n const needsQuote =\n s === \"\" ||\n /^\\s|\\s$/.test(s) || // leading/trailing whitespace\n /[\\n\"\\\\]/.test(s) || // control / quote / backslash\n /^[-?:,[\\]{}&*!|>%@`#'\"]/.test(s) || // starts with a YAML indicator\n /:(\\s|$)|\\s#/.test(s) || // \": \" (mapping) or \" #\" (comment) in structural position\n /^[+-]?(\\d|\\.\\d)/.test(s) || // number-ish, incl. \"+61…\" phone numbers\n /^(true|false|yes|no|on|off|null|~)$/i.test(s) || // YAML keywords\n /^[+-]?\\.?(inf|nan)$/i.test(s);\n return needsQuote\n ? `\"${s.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \"\\\\n\")}\"`\n : s;\n}\n","// Injected by Vite `define` from package.json at build/test time.\ndeclare const __APP_VERSION__: string;\n\nexport const APP_NAME = \"imsg-mcp\";\n// Fallback covers running un-built source without Vite (e.g. plain tsx).\nexport const APP_VERSION = typeof __APP_VERSION__ !== \"undefined\" ? __APP_VERSION__ : \"0.0.0-dev\";\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imsg-mcp",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "MCP server, CLI, and TUI for reading and sending iMessage / SMS on macOS — lets AI agents text humans, fetch conversation history, and stream-export entire chats. Includes a self-healing watchdog and an optional Rust acceleration module.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",