imsg-mcp 1.13.0 → 1.14.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.14.0](https://github.com/george43g/imsg-mcp/compare/v1.13.0...v1.14.0) (2026-07-23)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **tui:** settings panel for media-interpretation config ([56f0db0](https://github.com/george43g/imsg-mcp/commit/56f0db01361e558979346d236cfb29b7727a3dae))
|
|
12
|
+
|
|
6
13
|
# [1.13.0](https://github.com/george43g/imsg-mcp/compare/v1.12.0...v1.13.0) (2026-07-22)
|
|
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 { d as checkLocalAccess, f as formatAccessReport, I as IMPLEMENTED_TYPES, A as ANALYTIC_INFO, i as installShutdownHandlers, r as registerCleanup, l as looksLikeThreadSlug } from "./shutdown-DhEgpRwh.js";
|
|
7
|
-
import { A as APP_VERSION, t as toYaml } from "./meta-
|
|
7
|
+
import { A as APP_VERSION, t as toYaml } from "./meta-BS_x0KRa.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-CPCdARwa.js";
|
|
13
13
|
import { parseUserDate } from "./date-parse-DJXMfq3a.js";
|
|
14
14
|
import { streamExport, ExportInterpretGuardError } from "./exportStream-DUPD0lbq.js";
|
|
15
|
-
import { r as renderAnalyticText, A as APP_VERSION, a as APP_NAME } from "./meta-
|
|
15
|
+
import { r as renderAnalyticText, A as APP_VERSION, a as APP_NAME } from "./meta-BS_x0KRa.js";
|
|
16
16
|
import { randomUUID } from "node:crypto";
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
import { a as applyInlineInterpretations, i as imageBlockFromFile, v as videoPosterFrame, m as mediaMetadata, b as refForAttachment, T as TRANSCRIBE_MAX_BYTES, g as getInterpretRuntime, t as transcriptSourceEnum, d as detectTranscriber } from "./media-intel-runtime-C4C2QsM_.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.
|
|
158
|
+
const APP_VERSION = "1.14.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-
|
|
165
|
+
//# sourceMappingURL=meta-BS_x0KRa.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta-CIffb4eN.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-BS_x0KRa.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/dist/tui.js
CHANGED
|
@@ -4,7 +4,7 @@ import { parseArgs } from "node:util";
|
|
|
4
4
|
import { useScreenSize, withFullScreen } from "fullscreen-ink";
|
|
5
5
|
import { x as dispatchAnalytic, I as IMPLEMENTED_TYPES, g as getImsgDbPath, a as getContactsDbPaths, b as getSlugsDbPath, y as sendToChat, z as sendToChatId, B as sendMessageReliable, r as registerCleanup, d as checkLocalAccess, f as formatAccessReport, i as installShutdownHandlers, C as startHeapMonitor, D as logStartup, E as enableFileLogging, F as stopHeapMonitor, G as logShutdown } from "./shutdown-DhEgpRwh.js";
|
|
6
6
|
import { spawnSync, execSync } from "node:child_process";
|
|
7
|
-
import { r as resolveTuiConfig } from "./app-config-2yOG35Fr.js";
|
|
7
|
+
import { l as loadTuiConfig, I as InterpretConfigSchema, a as readCredentials, d as defaultTuiConfigPath, w as writeTuiConfig, r as resolveTuiConfig } from "./app-config-2yOG35Fr.js";
|
|
8
8
|
import { o as onMemorySample, r as readWatchdogState, m as minMessageId, a as resolveRecipient, d as defaultCountryFromEnv, i as installWatchdog } from "./watchdog-C-aZkmhF.js";
|
|
9
9
|
import { existsSync, copyFileSync, mkdirSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { homedir } from "node:os";
|
|
@@ -862,6 +862,7 @@ const SIDEBAR_KEYS = [
|
|
|
862
862
|
["N", "new msg"],
|
|
863
863
|
["Tab", "→msgs"],
|
|
864
864
|
["/", "filter"],
|
|
865
|
+
[",", "settings"],
|
|
865
866
|
["d", "stats"],
|
|
866
867
|
["r", "refresh"],
|
|
867
868
|
PALETTE_HINT,
|
|
@@ -940,6 +941,13 @@ const INFO_KEYS = [
|
|
|
940
941
|
["a", "export all"],
|
|
941
942
|
["Esc/q", "close"]
|
|
942
943
|
];
|
|
944
|
+
const SETTINGS_KEYS = [
|
|
945
|
+
["j/k", "move"],
|
|
946
|
+
["␣", "toggle"],
|
|
947
|
+
["←/→", "adjust"],
|
|
948
|
+
["K/J", "reorder"],
|
|
949
|
+
["Esc/q", "close"]
|
|
950
|
+
];
|
|
943
951
|
function HelpBar({ mode, focus }) {
|
|
944
952
|
const theme = useTheme();
|
|
945
953
|
let keys;
|
|
@@ -948,6 +956,7 @@ function HelpBar({ mode, focus }) {
|
|
|
948
956
|
else if (mode === "filter") keys = FILTER_KEYS;
|
|
949
957
|
else if (mode === "drawer") keys = DRAWER_KEYS;
|
|
950
958
|
else if (mode === "info") keys = INFO_KEYS;
|
|
959
|
+
else if (mode === "settings") keys = SETTINGS_KEYS;
|
|
951
960
|
else if (mode === "select") keys = SELECT_KEYS;
|
|
952
961
|
else if (mode === "export") keys = EXPORT_KEYS;
|
|
953
962
|
else if (mode === "date-jump") keys = DATE_JUMP_KEYS;
|
|
@@ -1643,6 +1652,92 @@ function SendViaModal({ handle, apps }) {
|
|
|
1643
1652
|
}
|
|
1644
1653
|
);
|
|
1645
1654
|
}
|
|
1655
|
+
function SettingsPanel({ rows, cursor, configPath, warnings, width, height }) {
|
|
1656
|
+
const theme = useTheme();
|
|
1657
|
+
const chromeH = 6;
|
|
1658
|
+
const bodyH = Math.max(4, height - chromeH);
|
|
1659
|
+
const start = Math.max(
|
|
1660
|
+
0,
|
|
1661
|
+
Math.min(cursor - Math.floor(bodyH / 2), Math.max(0, rows.length - bodyH))
|
|
1662
|
+
);
|
|
1663
|
+
const end = Math.min(rows.length, start + bodyH);
|
|
1664
|
+
const visible = rows.slice(start, end);
|
|
1665
|
+
const labelW = Math.max(24, Math.floor(width * 0.5));
|
|
1666
|
+
return /* @__PURE__ */ jsxs(
|
|
1667
|
+
Box,
|
|
1668
|
+
{
|
|
1669
|
+
flexDirection: "column",
|
|
1670
|
+
width,
|
|
1671
|
+
height,
|
|
1672
|
+
borderStyle: "single",
|
|
1673
|
+
borderColor: theme.drawer.border,
|
|
1674
|
+
overflow: "hidden",
|
|
1675
|
+
children: [
|
|
1676
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 1, backgroundColor: theme.header.focused.bg, children: /* @__PURE__ */ jsx(Text, { color: theme.header.focused.fg, bold: true, children: "Settings — Media Interpretation" }) }),
|
|
1677
|
+
warnings.length > 0 && /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { color: theme.edited, wrap: "truncate", children: [
|
|
1678
|
+
"⚠ ",
|
|
1679
|
+
warnings[0]
|
|
1680
|
+
] }) }),
|
|
1681
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: visible.map((row, i) => {
|
|
1682
|
+
const realIdx = start + i;
|
|
1683
|
+
const selected = realIdx === cursor;
|
|
1684
|
+
const prev = realIdx > 0 ? rows[realIdx - 1] : void 0;
|
|
1685
|
+
const showHeader = !prev || prev.section !== row.section;
|
|
1686
|
+
const key = `${row.section}-${row.kind}-${row.chain ?? ""}-${row.index ?? realIdx}`;
|
|
1687
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1688
|
+
showHeader && /* @__PURE__ */ jsx(Box, { marginTop: realIdx === 0 ? 0 : 1, children: /* @__PURE__ */ jsxs(Text, { color: theme.help.desc, dimColor: true, children: [
|
|
1689
|
+
"── ",
|
|
1690
|
+
row.section,
|
|
1691
|
+
" ──"
|
|
1692
|
+
] }) }),
|
|
1693
|
+
/* @__PURE__ */ jsxs(Box, { backgroundColor: selected ? theme.sidebar.selected : void 0, children: [
|
|
1694
|
+
/* @__PURE__ */ jsx(Box, { flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { color: selected ? theme.sidebar.selectedFg : theme.drawer.label, children: selected ? "▸ " : row.selectable ? " " : "· " }) }),
|
|
1695
|
+
/* @__PURE__ */ jsx(Box, { width: labelW, children: /* @__PURE__ */ jsx(
|
|
1696
|
+
Text,
|
|
1697
|
+
{
|
|
1698
|
+
color: selected ? theme.sidebar.selectedFg : row.selectable ? theme.drawer.value : theme.help.desc,
|
|
1699
|
+
bold: selected,
|
|
1700
|
+
wrap: "truncate",
|
|
1701
|
+
children: row.label
|
|
1702
|
+
}
|
|
1703
|
+
) }),
|
|
1704
|
+
/* @__PURE__ */ jsx(Box, { flexGrow: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsx(
|
|
1705
|
+
Text,
|
|
1706
|
+
{
|
|
1707
|
+
color: selected ? theme.sidebar.selectedFg : theme.info.value,
|
|
1708
|
+
bold: selected,
|
|
1709
|
+
wrap: "truncate",
|
|
1710
|
+
children: row.value
|
|
1711
|
+
}
|
|
1712
|
+
) })
|
|
1713
|
+
] }),
|
|
1714
|
+
selected && row.hint && /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: theme.help.desc, children: row.hint }) })
|
|
1715
|
+
] }, key);
|
|
1716
|
+
}) }),
|
|
1717
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { color: theme.help.desc, children: [
|
|
1718
|
+
/* @__PURE__ */ jsx(Text, { color: theme.help.key, children: "j/k" }),
|
|
1719
|
+
" move",
|
|
1720
|
+
" ",
|
|
1721
|
+
/* @__PURE__ */ jsx(Text, { color: theme.help.key, children: "␣" }),
|
|
1722
|
+
" toggle",
|
|
1723
|
+
" ",
|
|
1724
|
+
/* @__PURE__ */ jsx(Text, { color: theme.help.key, children: "←/→" }),
|
|
1725
|
+
" adjust",
|
|
1726
|
+
" ",
|
|
1727
|
+
/* @__PURE__ */ jsx(Text, { color: theme.help.key, children: "K/J" }),
|
|
1728
|
+
" reorder",
|
|
1729
|
+
" ",
|
|
1730
|
+
/* @__PURE__ */ jsx(Text, { color: theme.help.key, children: "q" }),
|
|
1731
|
+
" close"
|
|
1732
|
+
] }) }),
|
|
1733
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { color: theme.help.desc, wrap: "truncate", children: [
|
|
1734
|
+
"Saved to ",
|
|
1735
|
+
configPath || "(default config path)"
|
|
1736
|
+
] }) })
|
|
1737
|
+
]
|
|
1738
|
+
}
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1646
1741
|
function matchesConversationFilter(c, queryLower) {
|
|
1647
1742
|
return (c.displayName?.toLowerCase().includes(queryLower) ?? false) || c.chatIdentifier.toLowerCase().includes(queryLower) || c.threadSlug.toLowerCase().includes(queryLower);
|
|
1648
1743
|
}
|
|
@@ -1655,6 +1750,212 @@ function firstFilterMatchIndex(conversations, query) {
|
|
|
1655
1750
|
}
|
|
1656
1751
|
return null;
|
|
1657
1752
|
}
|
|
1753
|
+
const AUTO_ORDER = ["all", "free", "off"];
|
|
1754
|
+
const THRESHOLD_STEP = 5;
|
|
1755
|
+
function cap(s) {
|
|
1756
|
+
return s.length ? s[0].toUpperCase() + s.slice(1) : s;
|
|
1757
|
+
}
|
|
1758
|
+
function linkLabel(link) {
|
|
1759
|
+
if (link === "apple") return "Apple transcript (instant, free)";
|
|
1760
|
+
if (link === "local") return "Local tools (hear/yap/whisper)";
|
|
1761
|
+
if (link.startsWith("provider:")) return `Provider: ${link.slice("provider:".length)}`;
|
|
1762
|
+
return link;
|
|
1763
|
+
}
|
|
1764
|
+
function providerEnabled(interpret, name) {
|
|
1765
|
+
const tag = `provider:${name}`;
|
|
1766
|
+
return interpret.chains.audio.includes(tag) || interpret.chains.image.includes(tag) || interpret.chains.video.includes(tag);
|
|
1767
|
+
}
|
|
1768
|
+
function chainRows(interpret, chain) {
|
|
1769
|
+
const items = interpret.chains[chain];
|
|
1770
|
+
const section = `${cap(chain)} chain`;
|
|
1771
|
+
if (items.length === 0) {
|
|
1772
|
+
return [{ section, selectable: false, kind: "note", label: "(empty — no links)", value: "" }];
|
|
1773
|
+
}
|
|
1774
|
+
return items.map((item, i) => ({
|
|
1775
|
+
section,
|
|
1776
|
+
selectable: true,
|
|
1777
|
+
kind: "chain",
|
|
1778
|
+
chain,
|
|
1779
|
+
index: i,
|
|
1780
|
+
total: items.length,
|
|
1781
|
+
label: linkLabel(item),
|
|
1782
|
+
value: `#${i + 1}`,
|
|
1783
|
+
hint: "K/J reorder"
|
|
1784
|
+
}));
|
|
1785
|
+
}
|
|
1786
|
+
function buildSettingsRows(interpret, keyPresence) {
|
|
1787
|
+
const rows = [];
|
|
1788
|
+
rows.push({
|
|
1789
|
+
section: "General",
|
|
1790
|
+
selectable: true,
|
|
1791
|
+
kind: "auto",
|
|
1792
|
+
label: "Auto interpretation",
|
|
1793
|
+
value: interpret.auto,
|
|
1794
|
+
hint: "␣/←→ cycle (all · free · off)"
|
|
1795
|
+
});
|
|
1796
|
+
rows.push({
|
|
1797
|
+
section: "General",
|
|
1798
|
+
selectable: true,
|
|
1799
|
+
kind: "inline",
|
|
1800
|
+
label: "Inline transcripts in reads",
|
|
1801
|
+
value: interpret.inlineTranscripts ? "on" : "off",
|
|
1802
|
+
hint: "␣ toggle"
|
|
1803
|
+
});
|
|
1804
|
+
rows.push({
|
|
1805
|
+
section: "General",
|
|
1806
|
+
selectable: true,
|
|
1807
|
+
kind: "threshold",
|
|
1808
|
+
label: "Export cloud-confirm threshold",
|
|
1809
|
+
value: String(interpret.exportConfirmThreshold),
|
|
1810
|
+
hint: "←/→ ±5"
|
|
1811
|
+
});
|
|
1812
|
+
rows.push({
|
|
1813
|
+
section: "Sync nudge",
|
|
1814
|
+
selectable: true,
|
|
1815
|
+
kind: "nudge",
|
|
1816
|
+
label: "Attachment sync nudge",
|
|
1817
|
+
value: interpret.nudge.enabled ? "on" : "off",
|
|
1818
|
+
hint: "␣ toggle"
|
|
1819
|
+
});
|
|
1820
|
+
rows.push({
|
|
1821
|
+
section: "Sync nudge",
|
|
1822
|
+
selectable: true,
|
|
1823
|
+
kind: "nudge2",
|
|
1824
|
+
label: "Tier-2 'Sync Now' (Accessibility)",
|
|
1825
|
+
value: interpret.nudge.tier2SyncNow ? "on" : "off",
|
|
1826
|
+
hint: "␣ toggle"
|
|
1827
|
+
});
|
|
1828
|
+
rows.push(...chainRows(interpret, "audio"));
|
|
1829
|
+
rows.push(...chainRows(interpret, "image"));
|
|
1830
|
+
rows.push(...chainRows(interpret, "video"));
|
|
1831
|
+
if (interpret.providers.length === 0) {
|
|
1832
|
+
rows.push({
|
|
1833
|
+
section: "Providers",
|
|
1834
|
+
selectable: false,
|
|
1835
|
+
kind: "note",
|
|
1836
|
+
label: "None configured — run `imsg setup`",
|
|
1837
|
+
value: ""
|
|
1838
|
+
});
|
|
1839
|
+
} else {
|
|
1840
|
+
for (const p of interpret.providers) {
|
|
1841
|
+
const target = p.preset ?? p.baseUrl ?? "custom";
|
|
1842
|
+
const enabled = providerEnabled(interpret, p.name);
|
|
1843
|
+
const hasKey = Boolean(keyPresence[p.name]);
|
|
1844
|
+
rows.push({
|
|
1845
|
+
section: "Providers",
|
|
1846
|
+
selectable: false,
|
|
1847
|
+
kind: "provider",
|
|
1848
|
+
label: `${p.name} (${target})`,
|
|
1849
|
+
value: `${enabled ? "in chain" : "unused"} · ${hasKey ? "key set" : "no key"}`
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
return rows;
|
|
1854
|
+
}
|
|
1855
|
+
function firstSelectableIndex(rows) {
|
|
1856
|
+
const i = rows.findIndex((r) => r.selectable);
|
|
1857
|
+
return i < 0 ? 0 : i;
|
|
1858
|
+
}
|
|
1859
|
+
function lastSelectableIndex(rows) {
|
|
1860
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
1861
|
+
if (rows[i].selectable) return i;
|
|
1862
|
+
}
|
|
1863
|
+
return 0;
|
|
1864
|
+
}
|
|
1865
|
+
function stepSelectable(rows, from, dir) {
|
|
1866
|
+
let j = from + dir;
|
|
1867
|
+
while (j >= 0 && j < rows.length && !rows[j].selectable) j += dir;
|
|
1868
|
+
if (j < 0 || j >= rows.length) return from;
|
|
1869
|
+
return j;
|
|
1870
|
+
}
|
|
1871
|
+
function cycleAuto(cur, dir) {
|
|
1872
|
+
const idx = AUTO_ORDER.indexOf(cur);
|
|
1873
|
+
const at = idx < 0 ? 0 : idx;
|
|
1874
|
+
return AUTO_ORDER[(at + dir + AUTO_ORDER.length) % AUTO_ORDER.length];
|
|
1875
|
+
}
|
|
1876
|
+
function moveChain(interpret, chain, index, dir) {
|
|
1877
|
+
const arr = [...interpret.chains[chain]];
|
|
1878
|
+
const j = index + dir;
|
|
1879
|
+
if (j < 0 || j >= arr.length) return null;
|
|
1880
|
+
const tmp = arr[index];
|
|
1881
|
+
arr[index] = arr[j];
|
|
1882
|
+
arr[j] = tmp;
|
|
1883
|
+
return { ...interpret, chains: { ...interpret.chains, [chain]: arr } };
|
|
1884
|
+
}
|
|
1885
|
+
function applySettingsKey(interpret, row, action) {
|
|
1886
|
+
if (!row || !row.selectable) return null;
|
|
1887
|
+
switch (row.kind) {
|
|
1888
|
+
case "auto": {
|
|
1889
|
+
if (action === "left") return { ...interpret, auto: cycleAuto(interpret.auto, -1) };
|
|
1890
|
+
if (action === "right" || action === "toggle")
|
|
1891
|
+
return { ...interpret, auto: cycleAuto(interpret.auto, 1) };
|
|
1892
|
+
return null;
|
|
1893
|
+
}
|
|
1894
|
+
case "inline": {
|
|
1895
|
+
if (action === "toggle")
|
|
1896
|
+
return { ...interpret, inlineTranscripts: !interpret.inlineTranscripts };
|
|
1897
|
+
if (action === "left") return { ...interpret, inlineTranscripts: false };
|
|
1898
|
+
if (action === "right") return { ...interpret, inlineTranscripts: true };
|
|
1899
|
+
return null;
|
|
1900
|
+
}
|
|
1901
|
+
case "threshold": {
|
|
1902
|
+
if (action === "left")
|
|
1903
|
+
return {
|
|
1904
|
+
...interpret,
|
|
1905
|
+
exportConfirmThreshold: Math.max(0, interpret.exportConfirmThreshold - THRESHOLD_STEP)
|
|
1906
|
+
};
|
|
1907
|
+
if (action === "right")
|
|
1908
|
+
return {
|
|
1909
|
+
...interpret,
|
|
1910
|
+
exportConfirmThreshold: interpret.exportConfirmThreshold + THRESHOLD_STEP
|
|
1911
|
+
};
|
|
1912
|
+
return null;
|
|
1913
|
+
}
|
|
1914
|
+
case "nudge": {
|
|
1915
|
+
if (action === "toggle")
|
|
1916
|
+
return { ...interpret, nudge: { ...interpret.nudge, enabled: !interpret.nudge.enabled } };
|
|
1917
|
+
if (action === "left") return { ...interpret, nudge: { ...interpret.nudge, enabled: false } };
|
|
1918
|
+
if (action === "right") return { ...interpret, nudge: { ...interpret.nudge, enabled: true } };
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1921
|
+
case "nudge2": {
|
|
1922
|
+
if (action === "toggle")
|
|
1923
|
+
return {
|
|
1924
|
+
...interpret,
|
|
1925
|
+
nudge: { ...interpret.nudge, tier2SyncNow: !interpret.nudge.tier2SyncNow }
|
|
1926
|
+
};
|
|
1927
|
+
if (action === "left")
|
|
1928
|
+
return { ...interpret, nudge: { ...interpret.nudge, tier2SyncNow: false } };
|
|
1929
|
+
if (action === "right")
|
|
1930
|
+
return { ...interpret, nudge: { ...interpret.nudge, tier2SyncNow: true } };
|
|
1931
|
+
return null;
|
|
1932
|
+
}
|
|
1933
|
+
case "chain": {
|
|
1934
|
+
if (row.chain == null || row.index == null) return null;
|
|
1935
|
+
if (action === "moveUp") return moveChain(interpret, row.chain, row.index, -1);
|
|
1936
|
+
if (action === "moveDown") return moveChain(interpret, row.chain, row.index, 1);
|
|
1937
|
+
return null;
|
|
1938
|
+
}
|
|
1939
|
+
default:
|
|
1940
|
+
return null;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
function loadSettingsState() {
|
|
1944
|
+
const loaded = loadTuiConfig();
|
|
1945
|
+
const interpret = InterpretConfigSchema.parse(loaded.config.interpret ?? {});
|
|
1946
|
+
const creds = readCredentials();
|
|
1947
|
+
const keyPresence = {};
|
|
1948
|
+
for (const p of interpret.providers) keyPresence[p.name] = Boolean(creds[p.name]);
|
|
1949
|
+
return {
|
|
1950
|
+
interpret,
|
|
1951
|
+
keyPresence,
|
|
1952
|
+
configPath: loaded.source ?? defaultTuiConfigPath(),
|
|
1953
|
+
warnings: loaded.warnings
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
function openSettings(dispatch) {
|
|
1957
|
+
dispatch({ type: "OPEN_SETTINGS", ...loadSettingsState() });
|
|
1958
|
+
}
|
|
1658
1959
|
const hasSelection = (s) => s.selectedModuleIdx == null && s.conversations[s.selectedIdx] != null;
|
|
1659
1960
|
const CORE_COMMANDS = [
|
|
1660
1961
|
// ── Navigation ─────────────────────────────────────────────────────────
|
|
@@ -1928,6 +2229,14 @@ const CORE_COMMANDS = [
|
|
|
1928
2229
|
description: "This window",
|
|
1929
2230
|
run: ({ dispatch }) => dispatch({ type: "OPEN_PALETTE" })
|
|
1930
2231
|
},
|
|
2232
|
+
{
|
|
2233
|
+
id: "core.settings",
|
|
2234
|
+
title: "Settings — media interpretation",
|
|
2235
|
+
category: "App",
|
|
2236
|
+
keybinding: ",",
|
|
2237
|
+
description: "Chains, toggles, providers",
|
|
2238
|
+
run: ({ dispatch }) => openSettings(dispatch)
|
|
2239
|
+
},
|
|
1931
2240
|
{ id: "core.quit", title: "Quit", category: "App", keybinding: "q / Ctrl-C", run: () => {
|
|
1932
2241
|
} }
|
|
1933
2242
|
];
|
|
@@ -3651,7 +3960,12 @@ const initialState = {
|
|
|
3651
3960
|
moduleInstances: [],
|
|
3652
3961
|
selectedModuleIdx: null,
|
|
3653
3962
|
paletteQuery: "",
|
|
3654
|
-
paletteCursor: 0
|
|
3963
|
+
paletteCursor: 0,
|
|
3964
|
+
settingsInterpret: null,
|
|
3965
|
+
settingsKeyPresence: {},
|
|
3966
|
+
settingsCursor: 0,
|
|
3967
|
+
settingsConfigPath: "",
|
|
3968
|
+
settingsWarnings: []
|
|
3655
3969
|
};
|
|
3656
3970
|
function clampMsg(state, idx) {
|
|
3657
3971
|
const total = state.messages.length;
|
|
@@ -3942,6 +4256,23 @@ function reducer(state, action) {
|
|
|
3942
4256
|
return { ...state, paletteCursor: Math.max(0, state.paletteCursor + action.delta) };
|
|
3943
4257
|
case "SET_PALETTE_CURSOR":
|
|
3944
4258
|
return { ...state, paletteCursor: Math.max(0, action.index) };
|
|
4259
|
+
// ── Settings panel ─────────────────────────────────────────────────
|
|
4260
|
+
case "OPEN_SETTINGS":
|
|
4261
|
+
return {
|
|
4262
|
+
...state,
|
|
4263
|
+
mode: "settings",
|
|
4264
|
+
settingsInterpret: action.interpret,
|
|
4265
|
+
settingsKeyPresence: action.keyPresence,
|
|
4266
|
+
settingsConfigPath: action.configPath,
|
|
4267
|
+
settingsWarnings: action.warnings,
|
|
4268
|
+
settingsCursor: 0
|
|
4269
|
+
};
|
|
4270
|
+
case "CLOSE_SETTINGS":
|
|
4271
|
+
return { ...state, mode: "browse" };
|
|
4272
|
+
case "SET_SETTINGS_CURSOR":
|
|
4273
|
+
return { ...state, settingsCursor: Math.max(0, action.index) };
|
|
4274
|
+
case "SET_SETTINGS_INTERPRET":
|
|
4275
|
+
return { ...state, settingsInterpret: action.interpret };
|
|
3945
4276
|
// ── Module instances ───────────────────────────────────────────────
|
|
3946
4277
|
case "OPEN_MODULE_INSTANCE": {
|
|
3947
4278
|
const moduleInstances = [action.instance, ...state.moduleInstances];
|
|
@@ -4070,6 +4401,37 @@ function App() {
|
|
|
4070
4401
|
});
|
|
4071
4402
|
}
|
|
4072
4403
|
}, []);
|
|
4404
|
+
const settingsRows = useMemo(
|
|
4405
|
+
() => state.settingsInterpret ? buildSettingsRows(state.settingsInterpret, state.settingsKeyPresence) : [],
|
|
4406
|
+
[state.settingsInterpret, state.settingsKeyPresence]
|
|
4407
|
+
);
|
|
4408
|
+
const applySettings = useCallback(
|
|
4409
|
+
(action) => {
|
|
4410
|
+
const cur = state.settingsInterpret;
|
|
4411
|
+
if (!cur) return;
|
|
4412
|
+
const row = settingsRows[state.settingsCursor];
|
|
4413
|
+
const next = applySettingsKey(cur, row, action);
|
|
4414
|
+
if (!next) return;
|
|
4415
|
+
try {
|
|
4416
|
+
const loaded = loadTuiConfig();
|
|
4417
|
+
writeTuiConfig({ ...loaded.config, interpret: next }, state.settingsConfigPath);
|
|
4418
|
+
dispatch({ type: "SET_SETTINGS_INTERPRET", interpret: next });
|
|
4419
|
+
if (action === "moveUp") {
|
|
4420
|
+
dispatch({ type: "SET_SETTINGS_CURSOR", index: Math.max(0, state.settingsCursor - 1) });
|
|
4421
|
+
} else if (action === "moveDown") {
|
|
4422
|
+
dispatch({ type: "SET_SETTINGS_CURSOR", index: state.settingsCursor + 1 });
|
|
4423
|
+
}
|
|
4424
|
+
dispatch({ type: "SET_STATUS", status: "Settings saved." });
|
|
4425
|
+
setTimeout(() => dispatch({ type: "SET_STATUS", status: "" }), 2e3);
|
|
4426
|
+
} catch (e) {
|
|
4427
|
+
dispatch({
|
|
4428
|
+
type: "SET_STATUS",
|
|
4429
|
+
status: `Settings write failed: ${e instanceof Error ? e.message : String(e)}`
|
|
4430
|
+
});
|
|
4431
|
+
}
|
|
4432
|
+
},
|
|
4433
|
+
[state.settingsInterpret, state.settingsCursor, state.settingsConfigPath, settingsRows]
|
|
4434
|
+
);
|
|
4073
4435
|
const refreshAll = useCallback(async () => {
|
|
4074
4436
|
dispatch({ type: "SET_LOADING", loading: true, status: "Refreshing..." });
|
|
4075
4437
|
const convs = await imsg.loadConversations();
|
|
@@ -4353,6 +4715,36 @@ function App() {
|
|
|
4353
4715
|
}
|
|
4354
4716
|
return;
|
|
4355
4717
|
}
|
|
4718
|
+
if (state.mode === "settings") {
|
|
4719
|
+
if (key.escape || input === "q") {
|
|
4720
|
+
dispatch({ type: "CLOSE_SETTINGS" });
|
|
4721
|
+
} else if (input === "j" || key.downArrow) {
|
|
4722
|
+
dispatch({
|
|
4723
|
+
type: "SET_SETTINGS_CURSOR",
|
|
4724
|
+
index: stepSelectable(settingsRows, state.settingsCursor, 1)
|
|
4725
|
+
});
|
|
4726
|
+
} else if (input === "k" || key.upArrow) {
|
|
4727
|
+
dispatch({
|
|
4728
|
+
type: "SET_SETTINGS_CURSOR",
|
|
4729
|
+
index: stepSelectable(settingsRows, state.settingsCursor, -1)
|
|
4730
|
+
});
|
|
4731
|
+
} else if (input === "g") {
|
|
4732
|
+
dispatch({ type: "SET_SETTINGS_CURSOR", index: firstSelectableIndex(settingsRows) });
|
|
4733
|
+
} else if (input === "G") {
|
|
4734
|
+
dispatch({ type: "SET_SETTINGS_CURSOR", index: lastSelectableIndex(settingsRows) });
|
|
4735
|
+
} else if (input === " " || key.return) {
|
|
4736
|
+
applySettings("toggle");
|
|
4737
|
+
} else if (input === "h" || key.leftArrow) {
|
|
4738
|
+
applySettings("left");
|
|
4739
|
+
} else if (input === "l" || key.rightArrow) {
|
|
4740
|
+
applySettings("right");
|
|
4741
|
+
} else if (input === "K") {
|
|
4742
|
+
applySettings("moveUp");
|
|
4743
|
+
} else if (input === "J") {
|
|
4744
|
+
applySettings("moveDown");
|
|
4745
|
+
}
|
|
4746
|
+
return;
|
|
4747
|
+
}
|
|
4356
4748
|
if (state.mode === "date-jump") {
|
|
4357
4749
|
if (key.escape) {
|
|
4358
4750
|
dispatch({ type: "EXIT_DATE_JUMP" });
|
|
@@ -4436,6 +4828,10 @@ function App() {
|
|
|
4436
4828
|
openInfoDrawer();
|
|
4437
4829
|
return;
|
|
4438
4830
|
}
|
|
4831
|
+
if (input === "," && !key.ctrl && !key.meta && state.mode === "browse") {
|
|
4832
|
+
openSettings(dispatch);
|
|
4833
|
+
return;
|
|
4834
|
+
}
|
|
4439
4835
|
if (input === "V" && state.focus === "thread" && state.selectedMsgIdx >= 0) {
|
|
4440
4836
|
dispatch({ type: "ENTER_SELECT_MODE" });
|
|
4441
4837
|
return;
|
|
@@ -4769,7 +5165,17 @@ function App() {
|
|
|
4769
5165
|
const selectedModuleDef = selectedModule ? findModule(selectedModule.moduleId) : void 0;
|
|
4770
5166
|
const ModulePane = selectedModuleDef?.Pane;
|
|
4771
5167
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: rows, width: columns, children: [
|
|
4772
|
-
/* @__PURE__ */
|
|
5168
|
+
/* @__PURE__ */ jsx(Box, { flexGrow: 1, height: bodyHeight, children: state.mode === "settings" ? /* @__PURE__ */ jsx(
|
|
5169
|
+
SettingsPanel,
|
|
5170
|
+
{
|
|
5171
|
+
rows: settingsRows,
|
|
5172
|
+
cursor: state.settingsCursor,
|
|
5173
|
+
configPath: state.settingsConfigPath,
|
|
5174
|
+
warnings: state.settingsWarnings,
|
|
5175
|
+
width: columns,
|
|
5176
|
+
height: bodyHeight
|
|
5177
|
+
}
|
|
5178
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4773
5179
|
/* @__PURE__ */ jsx(
|
|
4774
5180
|
Sidebar,
|
|
4775
5181
|
{
|
|
@@ -4844,7 +5250,7 @@ function App() {
|
|
|
4844
5250
|
}
|
|
4845
5251
|
),
|
|
4846
5252
|
state.showDevStats && /* @__PURE__ */ jsx(DevStats, { stats: devStats, width: devStatsWidth })
|
|
4847
|
-
] }),
|
|
5253
|
+
] }) }),
|
|
4848
5254
|
state.mode === "date-jump" && /* @__PURE__ */ jsx(
|
|
4849
5255
|
DateJumpModal,
|
|
4850
5256
|
{
|