imsg-mcp 1.12.0 → 1.13.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 +9 -0
- package/dist/{app-config-CVixby2Z.js → app-config-2yOG35Fr.js} +44 -2
- package/dist/{app-config-CVixby2Z.js.map → app-config-2yOG35Fr.js.map} +1 -1
- package/dist/cli.js +51 -9
- package/dist/cli.js.map +1 -1
- package/dist/{export-formats-DosZugMa.js → export-formats-B9v-Qh_H.js} +10 -3
- package/dist/export-formats-B9v-Qh_H.js.map +1 -0
- package/dist/{exportStream-Dp5KKwtE.js → exportStream-DUPD0lbq.js} +37 -2
- package/dist/exportStream-DUPD0lbq.js.map +1 -0
- package/dist/{imessage-db-CbI0AYGw.js → imessage-db-CPCdARwa.js} +2 -2
- package/dist/{imessage-db-CbI0AYGw.js.map → imessage-db-CPCdARwa.js.map} +1 -1
- package/dist/index.js +111 -221
- package/dist/index.js.map +1 -1
- package/dist/media-intel-cache-mCgH4z9P.js +94 -0
- package/dist/media-intel-cache-mCgH4z9P.js.map +1 -0
- package/dist/media-intel-runtime-C4C2QsM_.js +594 -0
- package/dist/media-intel-runtime-C4C2QsM_.js.map +1 -0
- package/dist/media-providers-C1LgE0MH.js +156 -0
- package/dist/media-providers-C1LgE0MH.js.map +1 -0
- package/dist/{meta-BA-d51LM.js → meta-CIffb4eN.js} +2 -2
- package/dist/{meta-BA-d51LM.js.map → meta-CIffb4eN.js.map} +1 -1
- package/dist/{setup-Tpk356yq.js → setup-helkmfYX.js} +2 -2
- package/dist/{setup-Tpk356yq.js.map → setup-helkmfYX.js.map} +1 -1
- package/dist/{setup-wizard-Z6_0P-yc.js → setup-wizard-CtDo2a8L.js} +4 -43
- package/dist/setup-wizard-CtDo2a8L.js.map +1 -0
- package/dist/{shutdown-CjDoFsTF.js → shutdown-DhEgpRwh.js} +25 -25
- package/dist/{shutdown-CjDoFsTF.js.map → shutdown-DhEgpRwh.js.map} +1 -1
- package/dist/{tui-config-DWD0O2SW.js → tui-config-CZGBCm9N.js} +6 -4
- package/dist/tui-config-CZGBCm9N.js.map +1 -0
- package/dist/tui.js +154 -6
- package/dist/tui.js.map +1 -1
- package/dist/{watchdog-wY5uccJH.js → watchdog-C-aZkmhF.js} +2 -2
- package/dist/watchdog-C-aZkmhF.js.map +1 -0
- package/package.json +1 -1
- package/dist/export-formats-DosZugMa.js.map +0 -1
- package/dist/exportStream-Dp5KKwtE.js.map +0 -1
- package/dist/setup-wizard-Z6_0P-yc.js.map +0 -1
- package/dist/tui-config-DWD0O2SW.js.map +0 -1
- package/dist/watchdog-wY5uccJH.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
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.13.0](https://github.com/george43g/imsg-mcp/compare/v1.12.0...v1.13.0) (2026-07-22)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **export,cli:** interpret media in exports + `imsg interpret` command ([dcba1da](https://github.com/george43g/imsg-mcp/commit/dcba1dab50f64b41c09f90eeaaa3eb4957093f5a))
|
|
12
|
+
* **mcp:** route get_attachment + get_messages through media-intel ([4f43b99](https://github.com/george43g/imsg-mcp/commit/4f43b99b4ff13e4567dd47e2ad8073bf352635a0))
|
|
13
|
+
* **tui:** voice-note interpretation UX — transcript bubbles, R interpret, f reveal ([55697f7](https://github.com/george43g/imsg-mcp/commit/55697f7670d888b90487efd5207c4eeda33d7d70))
|
|
14
|
+
|
|
6
15
|
# [1.12.0](https://github.com/george43g/imsg-mcp/compare/v1.11.0...v1.12.0) (2026-07-22)
|
|
7
16
|
|
|
8
17
|
|
|
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync } from "n
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { c as getTranscribeCloudConfig } from "./shutdown-DhEgpRwh.js";
|
|
5
6
|
const HEX6 = /^#[0-9a-fA-F]{6}$/;
|
|
6
7
|
const PROVIDER_PRESET_NAMES = [
|
|
7
8
|
"openai",
|
|
@@ -144,6 +145,45 @@ function setCredential(name, key) {
|
|
|
144
145
|
else delete creds[name];
|
|
145
146
|
writeCredentials(creds);
|
|
146
147
|
}
|
|
148
|
+
const ENV_PROVIDER_NAME = "env";
|
|
149
|
+
function resolveInterpretConfig() {
|
|
150
|
+
const loaded = loadTuiConfig();
|
|
151
|
+
const warnings = [...loaded.warnings];
|
|
152
|
+
const block = InterpretConfigSchema.parse(loaded.config.interpret ?? {});
|
|
153
|
+
const creds = readCredentials();
|
|
154
|
+
const providers = block.providers.map((p) => ({
|
|
155
|
+
...p,
|
|
156
|
+
apiKey: creds[p.name]
|
|
157
|
+
}));
|
|
158
|
+
const chains = {
|
|
159
|
+
audio: [...block.chains.audio],
|
|
160
|
+
image: [...block.chains.image],
|
|
161
|
+
video: [...block.chains.video]
|
|
162
|
+
};
|
|
163
|
+
const envCloud = getTranscribeCloudConfig();
|
|
164
|
+
if (envCloud && !providers.some((p) => p.name === ENV_PROVIDER_NAME)) {
|
|
165
|
+
providers.push({
|
|
166
|
+
name: ENV_PROVIDER_NAME,
|
|
167
|
+
baseUrl: envCloud.baseUrl,
|
|
168
|
+
apiKey: envCloud.apiKey,
|
|
169
|
+
models: { transcribe: envCloud.model }
|
|
170
|
+
});
|
|
171
|
+
if (!chains.audio.some((l) => l.startsWith("provider:"))) {
|
|
172
|
+
chains.audio.push(`provider:${ENV_PROVIDER_NAME}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const auto = block.auto;
|
|
176
|
+
return {
|
|
177
|
+
auto,
|
|
178
|
+
chains,
|
|
179
|
+
providers,
|
|
180
|
+
inlineTranscripts: block.inlineTranscripts,
|
|
181
|
+
exportConfirmThreshold: block.exportConfirmThreshold,
|
|
182
|
+
nudge: block.nudge,
|
|
183
|
+
configPath: loaded.source,
|
|
184
|
+
warnings
|
|
185
|
+
};
|
|
186
|
+
}
|
|
147
187
|
function resolveTuiConfig(overrides = {}) {
|
|
148
188
|
const loaded = loadTuiConfig();
|
|
149
189
|
const warnings = [...loaded.warnings];
|
|
@@ -197,15 +237,17 @@ function resolveTuiConfig(overrides = {}) {
|
|
|
197
237
|
export {
|
|
198
238
|
AppConfigSchema as A,
|
|
199
239
|
DEFAULT_TUI_CONFIG as D,
|
|
240
|
+
ENV_PROVIDER_NAME as E,
|
|
200
241
|
InterpretConfigSchema as I,
|
|
201
242
|
readCredentials as a,
|
|
202
|
-
|
|
243
|
+
resolveInterpretConfig as b,
|
|
203
244
|
credentialsPath as c,
|
|
204
245
|
defaultTuiConfigPath as d,
|
|
246
|
+
writeCredentials as e,
|
|
205
247
|
findTuiConfigPath as f,
|
|
206
248
|
loadTuiConfig as l,
|
|
207
249
|
resolveTuiConfig as r,
|
|
208
250
|
setCredential as s,
|
|
209
251
|
writeTuiConfig as w
|
|
210
252
|
};
|
|
211
|
-
//# sourceMappingURL=app-config-
|
|
253
|
+
//# sourceMappingURL=app-config-2yOG35Fr.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-config-CVixby2Z.js","sources":["../src/app-config.ts"],"sourcesContent":["/**\n * Canonical persistent configuration for imsg-mcp — one JSON file that carries\n * both the flat TUI keys (theme / accentColor) AND the `interpret` block that\n * configures media interpretation (Stage 2 core: chains, providers, toggles).\n *\n * Resolution order (first one found wins) — unchanged from the original\n * tui-config module so existing files keep loading:\n * 1. $XDG_CONFIG_HOME/imsg-mcp/config.json\n * 2. $HOME/.config/imsg-mcp/config.json\n * 3. $HOME/.imsg-mcp/config.json (matches the slugs.db location)\n *\n * `src/tui-config.ts` re-exports this module verbatim for back-compat.\n *\n * API keys are NEVER stored in config.json. They live in\n * `~/.imsg-mcp/credentials.json` (chmod 600, `{ \"<providerName>\": \"sk-…\" }`)\n * or the legacy `IMSG_TRANSCRIBE_*` env vars, and are merged into providers only\n * at resolution time (`resolveInterpretConfig`).\n */\n\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { z } from \"zod\";\nimport { getTranscribeCloudConfig } from \"./config.js\";\nimport type { AutoMode, InterpretChains, InterpretConfig } from \"./media-intel.js\";\nimport type { ProviderConfig, ProviderPreset } from \"./media-providers.js\";\n\n// ── Schema ────────────────────────────────────────────────────────────────\n\nconst HEX6 = /^#[0-9a-fA-F]{6}$/;\n\n/** Preset names accepted in the `interpret.providers[].preset` field. Kept as a\n * literal tuple so Zod can build an enum without a runtime import of the\n * providers module (and asserted to stay in sync with `ProviderPreset`). */\nconst PROVIDER_PRESET_NAMES = [\n \"openai\",\n \"groq\",\n \"openrouter\",\n \"cloudflare\",\n \"huggingface\",\n \"ollama\",\n] as const satisfies readonly ProviderPreset[];\n\nconst ProviderModelsSchema = z\n .object({ transcribe: z.string().optional(), vision: z.string().optional() })\n .optional();\n\n/** A provider profile as stored in config.json — NO apiKey (that lives in\n * credentials.json / env and is merged in at resolution time). */\nconst ProviderConfigSchema = z\n .object({\n name: z.string().min(1),\n preset: z.enum(PROVIDER_PRESET_NAMES).optional(),\n baseUrl: z.string().url().optional(),\n accountId: z.string().optional(),\n models: ProviderModelsSchema,\n })\n .refine((p) => Boolean(p.preset) || Boolean(p.baseUrl), {\n message: \"provider needs either a preset or a baseUrl\",\n });\n\nconst ChainsSchema = z\n .object({\n audio: z.array(z.string()).default([\"apple\", \"local\"]),\n image: z.array(z.string()).default([]),\n video: z.array(z.string()).default([]),\n })\n .default({});\n\nconst NudgeSchema = z\n .object({\n enabled: z.boolean().default(true),\n tier2SyncNow: z.boolean().default(false),\n timeoutSeconds: z.number().int().positive().default(30),\n })\n .default({});\n\nexport const InterpretConfigSchema = z.object({\n /** Auto-interpretation gate. \"free\" (default) never makes a paid call without\n * an explicit force; \"all\" runs the full chain incl. cloud; \"off\" disables. */\n auto: z.enum([\"all\", \"free\", \"off\"]).default(\"free\"),\n /** Inline cached transcripts/captions in read surfaces (get_messages, TUI). */\n inlineTranscripts: z.boolean().default(true),\n /** Confirm before an export triggers more than this many uncached cloud calls. */\n exportConfirmThreshold: z.number().int().nonnegative().default(25),\n chains: ChainsSchema,\n providers: z.array(ProviderConfigSchema).default([]),\n nudge: NudgeSchema,\n});\n\nexport const AppConfigSchema = z.object({\n /** Glyph preset. \"safe\" is the universally-renderable default. */\n theme: z.enum([\"safe\", \"powerline\"]).default(\"safe\"),\n /** 6-digit hex color used to derive the whole UI palette. */\n accentColor: z.string().regex(HEX6, \"must be a 6-digit hex like #RRGGBB\").default(\"#1982FC\"),\n /** Media-interpretation config (optional — absent on TUI-only configs). */\n interpret: InterpretConfigSchema.optional(),\n});\n\nexport type AppConfig = z.infer<typeof AppConfigSchema>;\nexport type InterpretConfigInput = z.infer<typeof InterpretConfigSchema>;\n\n/** Back-compat alias — the TUI only ever reads theme/accentColor. */\nexport const TuiConfigSchema = AppConfigSchema;\nexport type TuiConfig = AppConfig;\n\nexport const DEFAULT_TUI_CONFIG: TuiConfig = {\n theme: \"safe\",\n accentColor: \"#1982FC\",\n};\n\n/** Free-first interpret defaults used when the block is absent from config. */\nexport const DEFAULT_INTERPRET_CONFIG: InterpretConfigInput = InterpretConfigSchema.parse({});\n\n// ── Path resolution ───────────────────────────────────────────────────────\n\n/** Candidate paths we try to read from, in order. */\nfunction candidatePaths(): string[] {\n const home = homedir();\n const out: string[] = [];\n if (process.env.XDG_CONFIG_HOME) {\n out.push(join(process.env.XDG_CONFIG_HOME, \"imsg-mcp\", \"config.json\"));\n }\n out.push(join(home, \".config\", \"imsg-mcp\", \"config.json\"));\n out.push(join(home, \".imsg-mcp\", \"config.json\"));\n return out;\n}\n\n/** Where the file actually lives (first existing candidate), or null. */\nexport function findTuiConfigPath(): string | null {\n for (const p of candidatePaths()) {\n if (existsSync(p)) return p;\n }\n return null;\n}\n\n/** Where to write a fresh config file. Returns the *first* candidate so\n * XDG-style wins on a clean machine. */\nexport function defaultTuiConfigPath(): string {\n return candidatePaths()[0] ?? candidatePaths()[1];\n}\n\n// ── Loader ────────────────────────────────────────────────────────────────\n\nexport interface LoadedTuiConfig {\n config: AppConfig;\n /** Path of the file we read from, or `null` if defaults were used. */\n source: string | null;\n /** Human-readable warnings (parse errors, schema errors). Empty when clean. */\n warnings: string[];\n}\n\nexport function loadTuiConfig(): LoadedTuiConfig {\n const warnings: string[] = [];\n const path = findTuiConfigPath();\n if (!path) return { config: { ...DEFAULT_TUI_CONFIG }, source: null, warnings };\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, \"utf8\"));\n } catch (err) {\n warnings.push(\n `config: failed to read ${path} (${err instanceof Error ? err.message : String(err)}); using defaults`,\n );\n return { config: { ...DEFAULT_TUI_CONFIG }, source: path, warnings };\n }\n\n const result = AppConfigSchema.safeParse(parsed);\n if (result.success) return { config: result.data, source: path, warnings };\n\n // Full parse failed. Degrade gracefully: if the flat TUI keys alone are valid,\n // keep them (a malformed `interpret` block should not blow away the theme).\n const tuiOnly = AppConfigSchema.pick({ theme: true, accentColor: true }).safeParse(parsed);\n const issues = result.error.issues\n .map((i) => `${i.path.join(\".\") || \"<root>\"}: ${i.message}`)\n .join(\"; \");\n if (tuiOnly.success) {\n warnings.push(`config: ${path} has invalid fields (${issues}); ignoring them`);\n return { config: { ...tuiOnly.data }, source: path, warnings };\n }\n warnings.push(`config: ${path} has invalid fields (${issues}); using defaults`);\n return { config: { ...DEFAULT_TUI_CONFIG }, source: path, warnings };\n}\n\n// ── Writer (used by `imsg config edit` + `imsg setup`) ────────────────────\n\nexport function writeTuiConfig(config: AppConfig, path = defaultTuiConfigPath()): string {\n // Validate before writing so we never persist garbage.\n const validated = AppConfigSchema.parse(config);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(validated, null, 2)}\\n`);\n return path;\n}\n\n// ── Credentials (chmod 600) ───────────────────────────────────────────────\n\n/** `~/.imsg-mcp/credentials.json` — `{ \"<providerName>\": \"<apiKey>\" }`. */\nexport function credentialsPath(): string {\n return join(homedir(), \".imsg-mcp\", \"credentials.json\");\n}\n\nexport function readCredentials(): Record<string, string> {\n const path = credentialsPath();\n if (!existsSync(path)) return {};\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\"));\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(parsed)) {\n if (typeof v === \"string\") out[k] = v;\n }\n return out;\n }\n } catch {\n // Corrupt credentials file — treat as empty (never throw on read).\n }\n return {};\n}\n\n/** Write the whole credentials map, enforcing 0600 on the file (and 0700 dir). */\nexport function writeCredentials(creds: Record<string, string>): string {\n const path = credentialsPath();\n const dir = dirname(path);\n mkdirSync(dir, { recursive: true });\n try {\n chmodSync(dir, 0o700);\n } catch {\n // Best-effort — a shared dir may already exist with other perms.\n }\n writeFileSync(path, `${JSON.stringify(creds, null, 2)}\\n`, { mode: 0o600 });\n // writeFileSync's `mode` is masked by umask on CREATE and ignored when the\n // file already exists — chmod unconditionally so 0600 is guaranteed.\n chmodSync(path, 0o600);\n return path;\n}\n\n/** Set (or clear, when key is empty) one provider's credential. */\nexport function setCredential(name: string, key: string): void {\n const creds = readCredentials();\n if (key) creds[name] = key;\n else delete creds[name];\n writeCredentials(creds);\n}\n\n// ── Interpret-config resolution (config + credentials + env → service) ─────\n\n/** The env-mapped implicit provider name (legacy IMSG_TRANSCRIBE_* path). */\nexport const ENV_PROVIDER_NAME = \"env\";\n\n/** Interpret config ready for `MediaIntelService`, plus the surfacing toggles\n * that frontends (Stage 4/6/7) read. Providers carry their merged apiKey. */\nexport interface ResolvedInterpretConfig extends InterpretConfig {\n inlineTranscripts: boolean;\n exportConfirmThreshold: number;\n nudge: { enabled: boolean; tier2SyncNow: boolean; timeoutSeconds: number };\n /** Where the config was read from (null = defaults). */\n configPath: string | null;\n warnings: string[];\n}\n\n/**\n * Resolve the full interpret configuration:\n * - start from the config file's `interpret` block (or free-first defaults),\n * - merge API keys from credentials.json into each named provider,\n * - map the legacy `IMSG_TRANSCRIBE_*` env vars to an implicit `env` provider\n * (and append `provider:env` to the audio chain if it has no provider yet),\n * preserving the shipped v1.7.0 cloud-transcription fallback.\n */\nexport function resolveInterpretConfig(): ResolvedInterpretConfig {\n const loaded = loadTuiConfig();\n const warnings = [...loaded.warnings];\n const block = InterpretConfigSchema.parse(loaded.config.interpret ?? {});\n const creds = readCredentials();\n\n const providers: ProviderConfig[] = block.providers.map((p) => ({\n ...p,\n apiKey: creds[p.name],\n }));\n const chains: InterpretChains = {\n audio: [...block.chains.audio],\n image: [...block.chains.image],\n video: [...block.chains.video],\n };\n\n // Legacy env fallback → implicit provider, if not already configured by name.\n const envCloud = getTranscribeCloudConfig();\n if (envCloud && !providers.some((p) => p.name === ENV_PROVIDER_NAME)) {\n providers.push({\n name: ENV_PROVIDER_NAME,\n baseUrl: envCloud.baseUrl,\n apiKey: envCloud.apiKey,\n models: { transcribe: envCloud.model },\n });\n if (!chains.audio.some((l) => l.startsWith(\"provider:\"))) {\n chains.audio.push(`provider:${ENV_PROVIDER_NAME}`);\n }\n }\n\n const auto: AutoMode = block.auto;\n return {\n auto,\n chains,\n providers,\n inlineTranscripts: block.inlineTranscripts,\n exportConfirmThreshold: block.exportConfirmThreshold,\n nudge: block.nudge,\n configPath: loaded.source,\n warnings,\n };\n}\n\n// ── Layered resolution (CLI > env > config > defaults) — TUI theme/accent ──\n\nexport interface TuiConfigOverrides {\n /** From `imsg --theme=...` */\n cliTheme?: string;\n /** From `imsg --accent=...` */\n cliAccent?: string;\n}\n\nexport interface ResolvedTuiConfig {\n theme: TuiConfig[\"theme\"];\n accentColor: string;\n /** Where each value came from, useful for `imsg config show`. */\n origin: {\n theme: \"cli\" | \"env\" | \"config\" | \"default\";\n accentColor: \"cli\" | \"env\" | \"config\" | \"default\";\n };\n /** The on-disk file path that contributed (or `null` if no file). */\n configPath: string | null;\n /** Any warnings raised during loading (malformed file, invalid env, etc). */\n warnings: string[];\n}\n\nexport function resolveTuiConfig(overrides: TuiConfigOverrides = {}): ResolvedTuiConfig {\n const loaded = loadTuiConfig();\n const warnings = [...loaded.warnings];\n\n // Theme precedence: CLI > env > config > default\n let theme: TuiConfig[\"theme\"] = loaded.config.theme;\n let themeOrigin: ResolvedTuiConfig[\"origin\"][\"theme\"] = loaded.source ? \"config\" : \"default\";\n\n if (process.env.IMSG_TUI_THEME) {\n const t = process.env.IMSG_TUI_THEME;\n if (t === \"safe\" || t === \"powerline\") {\n theme = t;\n themeOrigin = \"env\";\n } else {\n warnings.push(`IMSG_TUI_THEME=\"${t}\" is not \"safe\" or \"powerline\"; ignoring`);\n }\n }\n if (overrides.cliTheme !== undefined) {\n if (overrides.cliTheme === \"safe\" || overrides.cliTheme === \"powerline\") {\n theme = overrides.cliTheme;\n themeOrigin = \"cli\";\n } else {\n warnings.push(`--theme=\"${overrides.cliTheme}\" is not \"safe\" or \"powerline\"; ignoring`);\n }\n }\n\n // Accent precedence: CLI > env > config > default\n let accentColor = loaded.config.accentColor;\n let accentOrigin: ResolvedTuiConfig[\"origin\"][\"accentColor\"] = loaded.source\n ? \"config\"\n : \"default\";\n\n if (process.env.IMSG_TUI_ACCENT) {\n if (HEX6.test(process.env.IMSG_TUI_ACCENT)) {\n accentColor = process.env.IMSG_TUI_ACCENT;\n accentOrigin = \"env\";\n } else {\n warnings.push(\n `IMSG_TUI_ACCENT=\"${process.env.IMSG_TUI_ACCENT}\" is not a 6-digit hex; ignoring`,\n );\n }\n }\n if (overrides.cliAccent !== undefined) {\n if (HEX6.test(overrides.cliAccent)) {\n accentColor = overrides.cliAccent;\n accentOrigin = \"cli\";\n } else {\n warnings.push(`--accent=\"${overrides.cliAccent}\" is not a 6-digit hex; ignoring`);\n }\n }\n\n return {\n theme,\n accentColor,\n origin: { theme: themeOrigin, accentColor: accentOrigin },\n configPath: loaded.source,\n warnings,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-config-2yOG35Fr.js","sources":["../src/app-config.ts"],"sourcesContent":["/**\n * Canonical persistent configuration for imsg-mcp — one JSON file that carries\n * both the flat TUI keys (theme / accentColor) AND the `interpret` block that\n * configures media interpretation (Stage 2 core: chains, providers, toggles).\n *\n * Resolution order (first one found wins) — unchanged from the original\n * tui-config module so existing files keep loading:\n * 1. $XDG_CONFIG_HOME/imsg-mcp/config.json\n * 2. $HOME/.config/imsg-mcp/config.json\n * 3. $HOME/.imsg-mcp/config.json (matches the slugs.db location)\n *\n * `src/tui-config.ts` re-exports this module verbatim for back-compat.\n *\n * API keys are NEVER stored in config.json. They live in\n * `~/.imsg-mcp/credentials.json` (chmod 600, `{ \"<providerName>\": \"sk-…\" }`)\n * or the legacy `IMSG_TRANSCRIBE_*` env vars, and are merged into providers only\n * at resolution time (`resolveInterpretConfig`).\n */\n\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { z } from \"zod\";\nimport { getTranscribeCloudConfig } from \"./config.js\";\nimport type { AutoMode, InterpretChains, InterpretConfig } from \"./media-intel.js\";\nimport type { ProviderConfig, ProviderPreset } from \"./media-providers.js\";\n\n// ── Schema ────────────────────────────────────────────────────────────────\n\nconst HEX6 = /^#[0-9a-fA-F]{6}$/;\n\n/** Preset names accepted in the `interpret.providers[].preset` field. Kept as a\n * literal tuple so Zod can build an enum without a runtime import of the\n * providers module (and asserted to stay in sync with `ProviderPreset`). */\nconst PROVIDER_PRESET_NAMES = [\n \"openai\",\n \"groq\",\n \"openrouter\",\n \"cloudflare\",\n \"huggingface\",\n \"ollama\",\n] as const satisfies readonly ProviderPreset[];\n\nconst ProviderModelsSchema = z\n .object({ transcribe: z.string().optional(), vision: z.string().optional() })\n .optional();\n\n/** A provider profile as stored in config.json — NO apiKey (that lives in\n * credentials.json / env and is merged in at resolution time). */\nconst ProviderConfigSchema = z\n .object({\n name: z.string().min(1),\n preset: z.enum(PROVIDER_PRESET_NAMES).optional(),\n baseUrl: z.string().url().optional(),\n accountId: z.string().optional(),\n models: ProviderModelsSchema,\n })\n .refine((p) => Boolean(p.preset) || Boolean(p.baseUrl), {\n message: \"provider needs either a preset or a baseUrl\",\n });\n\nconst ChainsSchema = z\n .object({\n audio: z.array(z.string()).default([\"apple\", \"local\"]),\n image: z.array(z.string()).default([]),\n video: z.array(z.string()).default([]),\n })\n .default({});\n\nconst NudgeSchema = z\n .object({\n enabled: z.boolean().default(true),\n tier2SyncNow: z.boolean().default(false),\n timeoutSeconds: z.number().int().positive().default(30),\n })\n .default({});\n\nexport const InterpretConfigSchema = z.object({\n /** Auto-interpretation gate. \"free\" (default) never makes a paid call without\n * an explicit force; \"all\" runs the full chain incl. cloud; \"off\" disables. */\n auto: z.enum([\"all\", \"free\", \"off\"]).default(\"free\"),\n /** Inline cached transcripts/captions in read surfaces (get_messages, TUI). */\n inlineTranscripts: z.boolean().default(true),\n /** Confirm before an export triggers more than this many uncached cloud calls. */\n exportConfirmThreshold: z.number().int().nonnegative().default(25),\n chains: ChainsSchema,\n providers: z.array(ProviderConfigSchema).default([]),\n nudge: NudgeSchema,\n});\n\nexport const AppConfigSchema = z.object({\n /** Glyph preset. \"safe\" is the universally-renderable default. */\n theme: z.enum([\"safe\", \"powerline\"]).default(\"safe\"),\n /** 6-digit hex color used to derive the whole UI palette. */\n accentColor: z.string().regex(HEX6, \"must be a 6-digit hex like #RRGGBB\").default(\"#1982FC\"),\n /** Media-interpretation config (optional — absent on TUI-only configs). */\n interpret: InterpretConfigSchema.optional(),\n});\n\nexport type AppConfig = z.infer<typeof AppConfigSchema>;\nexport type InterpretConfigInput = z.infer<typeof InterpretConfigSchema>;\n\n/** Back-compat alias — the TUI only ever reads theme/accentColor. */\nexport const TuiConfigSchema = AppConfigSchema;\nexport type TuiConfig = AppConfig;\n\nexport const DEFAULT_TUI_CONFIG: TuiConfig = {\n theme: \"safe\",\n accentColor: \"#1982FC\",\n};\n\n/** Free-first interpret defaults used when the block is absent from config. */\nexport const DEFAULT_INTERPRET_CONFIG: InterpretConfigInput = InterpretConfigSchema.parse({});\n\n// ── Path resolution ───────────────────────────────────────────────────────\n\n/** Candidate paths we try to read from, in order. */\nfunction candidatePaths(): string[] {\n const home = homedir();\n const out: string[] = [];\n if (process.env.XDG_CONFIG_HOME) {\n out.push(join(process.env.XDG_CONFIG_HOME, \"imsg-mcp\", \"config.json\"));\n }\n out.push(join(home, \".config\", \"imsg-mcp\", \"config.json\"));\n out.push(join(home, \".imsg-mcp\", \"config.json\"));\n return out;\n}\n\n/** Where the file actually lives (first existing candidate), or null. */\nexport function findTuiConfigPath(): string | null {\n for (const p of candidatePaths()) {\n if (existsSync(p)) return p;\n }\n return null;\n}\n\n/** Where to write a fresh config file. Returns the *first* candidate so\n * XDG-style wins on a clean machine. */\nexport function defaultTuiConfigPath(): string {\n return candidatePaths()[0] ?? candidatePaths()[1];\n}\n\n// ── Loader ────────────────────────────────────────────────────────────────\n\nexport interface LoadedTuiConfig {\n config: AppConfig;\n /** Path of the file we read from, or `null` if defaults were used. */\n source: string | null;\n /** Human-readable warnings (parse errors, schema errors). Empty when clean. */\n warnings: string[];\n}\n\nexport function loadTuiConfig(): LoadedTuiConfig {\n const warnings: string[] = [];\n const path = findTuiConfigPath();\n if (!path) return { config: { ...DEFAULT_TUI_CONFIG }, source: null, warnings };\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, \"utf8\"));\n } catch (err) {\n warnings.push(\n `config: failed to read ${path} (${err instanceof Error ? err.message : String(err)}); using defaults`,\n );\n return { config: { ...DEFAULT_TUI_CONFIG }, source: path, warnings };\n }\n\n const result = AppConfigSchema.safeParse(parsed);\n if (result.success) return { config: result.data, source: path, warnings };\n\n // Full parse failed. Degrade gracefully: if the flat TUI keys alone are valid,\n // keep them (a malformed `interpret` block should not blow away the theme).\n const tuiOnly = AppConfigSchema.pick({ theme: true, accentColor: true }).safeParse(parsed);\n const issues = result.error.issues\n .map((i) => `${i.path.join(\".\") || \"<root>\"}: ${i.message}`)\n .join(\"; \");\n if (tuiOnly.success) {\n warnings.push(`config: ${path} has invalid fields (${issues}); ignoring them`);\n return { config: { ...tuiOnly.data }, source: path, warnings };\n }\n warnings.push(`config: ${path} has invalid fields (${issues}); using defaults`);\n return { config: { ...DEFAULT_TUI_CONFIG }, source: path, warnings };\n}\n\n// ── Writer (used by `imsg config edit` + `imsg setup`) ────────────────────\n\nexport function writeTuiConfig(config: AppConfig, path = defaultTuiConfigPath()): string {\n // Validate before writing so we never persist garbage.\n const validated = AppConfigSchema.parse(config);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(validated, null, 2)}\\n`);\n return path;\n}\n\n// ── Credentials (chmod 600) ───────────────────────────────────────────────\n\n/** `~/.imsg-mcp/credentials.json` — `{ \"<providerName>\": \"<apiKey>\" }`. */\nexport function credentialsPath(): string {\n return join(homedir(), \".imsg-mcp\", \"credentials.json\");\n}\n\nexport function readCredentials(): Record<string, string> {\n const path = credentialsPath();\n if (!existsSync(path)) return {};\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\"));\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(parsed)) {\n if (typeof v === \"string\") out[k] = v;\n }\n return out;\n }\n } catch {\n // Corrupt credentials file — treat as empty (never throw on read).\n }\n return {};\n}\n\n/** Write the whole credentials map, enforcing 0600 on the file (and 0700 dir). */\nexport function writeCredentials(creds: Record<string, string>): string {\n const path = credentialsPath();\n const dir = dirname(path);\n mkdirSync(dir, { recursive: true });\n try {\n chmodSync(dir, 0o700);\n } catch {\n // Best-effort — a shared dir may already exist with other perms.\n }\n writeFileSync(path, `${JSON.stringify(creds, null, 2)}\\n`, { mode: 0o600 });\n // writeFileSync's `mode` is masked by umask on CREATE and ignored when the\n // file already exists — chmod unconditionally so 0600 is guaranteed.\n chmodSync(path, 0o600);\n return path;\n}\n\n/** Set (or clear, when key is empty) one provider's credential. */\nexport function setCredential(name: string, key: string): void {\n const creds = readCredentials();\n if (key) creds[name] = key;\n else delete creds[name];\n writeCredentials(creds);\n}\n\n// ── Interpret-config resolution (config + credentials + env → service) ─────\n\n/** The env-mapped implicit provider name (legacy IMSG_TRANSCRIBE_* path). */\nexport const ENV_PROVIDER_NAME = \"env\";\n\n/** Interpret config ready for `MediaIntelService`, plus the surfacing toggles\n * that frontends (Stage 4/6/7) read. Providers carry their merged apiKey. */\nexport interface ResolvedInterpretConfig extends InterpretConfig {\n inlineTranscripts: boolean;\n exportConfirmThreshold: number;\n nudge: { enabled: boolean; tier2SyncNow: boolean; timeoutSeconds: number };\n /** Where the config was read from (null = defaults). */\n configPath: string | null;\n warnings: string[];\n}\n\n/**\n * Resolve the full interpret configuration:\n * - start from the config file's `interpret` block (or free-first defaults),\n * - merge API keys from credentials.json into each named provider,\n * - map the legacy `IMSG_TRANSCRIBE_*` env vars to an implicit `env` provider\n * (and append `provider:env` to the audio chain if it has no provider yet),\n * preserving the shipped v1.7.0 cloud-transcription fallback.\n */\nexport function resolveInterpretConfig(): ResolvedInterpretConfig {\n const loaded = loadTuiConfig();\n const warnings = [...loaded.warnings];\n const block = InterpretConfigSchema.parse(loaded.config.interpret ?? {});\n const creds = readCredentials();\n\n const providers: ProviderConfig[] = block.providers.map((p) => ({\n ...p,\n apiKey: creds[p.name],\n }));\n const chains: InterpretChains = {\n audio: [...block.chains.audio],\n image: [...block.chains.image],\n video: [...block.chains.video],\n };\n\n // Legacy env fallback → implicit provider, if not already configured by name.\n const envCloud = getTranscribeCloudConfig();\n if (envCloud && !providers.some((p) => p.name === ENV_PROVIDER_NAME)) {\n providers.push({\n name: ENV_PROVIDER_NAME,\n baseUrl: envCloud.baseUrl,\n apiKey: envCloud.apiKey,\n models: { transcribe: envCloud.model },\n });\n if (!chains.audio.some((l) => l.startsWith(\"provider:\"))) {\n chains.audio.push(`provider:${ENV_PROVIDER_NAME}`);\n }\n }\n\n const auto: AutoMode = block.auto;\n return {\n auto,\n chains,\n providers,\n inlineTranscripts: block.inlineTranscripts,\n exportConfirmThreshold: block.exportConfirmThreshold,\n nudge: block.nudge,\n configPath: loaded.source,\n warnings,\n };\n}\n\n// ── Layered resolution (CLI > env > config > defaults) — TUI theme/accent ──\n\nexport interface TuiConfigOverrides {\n /** From `imsg --theme=...` */\n cliTheme?: string;\n /** From `imsg --accent=...` */\n cliAccent?: string;\n}\n\nexport interface ResolvedTuiConfig {\n theme: TuiConfig[\"theme\"];\n accentColor: string;\n /** Where each value came from, useful for `imsg config show`. */\n origin: {\n theme: \"cli\" | \"env\" | \"config\" | \"default\";\n accentColor: \"cli\" | \"env\" | \"config\" | \"default\";\n };\n /** The on-disk file path that contributed (or `null` if no file). */\n configPath: string | null;\n /** Any warnings raised during loading (malformed file, invalid env, etc). */\n warnings: string[];\n}\n\nexport function resolveTuiConfig(overrides: TuiConfigOverrides = {}): ResolvedTuiConfig {\n const loaded = loadTuiConfig();\n const warnings = [...loaded.warnings];\n\n // Theme precedence: CLI > env > config > default\n let theme: TuiConfig[\"theme\"] = loaded.config.theme;\n let themeOrigin: ResolvedTuiConfig[\"origin\"][\"theme\"] = loaded.source ? \"config\" : \"default\";\n\n if (process.env.IMSG_TUI_THEME) {\n const t = process.env.IMSG_TUI_THEME;\n if (t === \"safe\" || t === \"powerline\") {\n theme = t;\n themeOrigin = \"env\";\n } else {\n warnings.push(`IMSG_TUI_THEME=\"${t}\" is not \"safe\" or \"powerline\"; ignoring`);\n }\n }\n if (overrides.cliTheme !== undefined) {\n if (overrides.cliTheme === \"safe\" || overrides.cliTheme === \"powerline\") {\n theme = overrides.cliTheme;\n themeOrigin = \"cli\";\n } else {\n warnings.push(`--theme=\"${overrides.cliTheme}\" is not \"safe\" or \"powerline\"; ignoring`);\n }\n }\n\n // Accent precedence: CLI > env > config > default\n let accentColor = loaded.config.accentColor;\n let accentOrigin: ResolvedTuiConfig[\"origin\"][\"accentColor\"] = loaded.source\n ? \"config\"\n : \"default\";\n\n if (process.env.IMSG_TUI_ACCENT) {\n if (HEX6.test(process.env.IMSG_TUI_ACCENT)) {\n accentColor = process.env.IMSG_TUI_ACCENT;\n accentOrigin = \"env\";\n } else {\n warnings.push(\n `IMSG_TUI_ACCENT=\"${process.env.IMSG_TUI_ACCENT}\" is not a 6-digit hex; ignoring`,\n );\n }\n }\n if (overrides.cliAccent !== undefined) {\n if (HEX6.test(overrides.cliAccent)) {\n accentColor = overrides.cliAccent;\n accentOrigin = \"cli\";\n } else {\n warnings.push(`--accent=\"${overrides.cliAccent}\" is not a 6-digit hex; ignoring`);\n }\n }\n\n return {\n theme,\n accentColor,\n origin: { theme: themeOrigin, accentColor: accentOrigin },\n configPath: loaded.source,\n warnings,\n };\n}\n"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -3,8 +3,8 @@ 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 {
|
|
7
|
-
import { A as APP_VERSION, t as toYaml } from "./meta-
|
|
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-CIffb4eN.js";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { join, dirname } from "node:path";
|
|
10
10
|
function distRoot() {
|
|
@@ -446,9 +446,9 @@ async function runExportCommand(target, opts) {
|
|
|
446
446
|
const { existsSync: existsSync2, mkdirSync, copyFileSync, statSync } = await import("node:fs");
|
|
447
447
|
const { homedir } = await import("node:os");
|
|
448
448
|
const { dirname: dirname2, join: join2, isAbsolute, resolve } = await import("node:path");
|
|
449
|
-
const { getContactsDbPaths, getImsgDbPath, getSlugsDbPath } = await import("./shutdown-
|
|
450
|
-
const { IMessageDB } = await import("./imessage-db-
|
|
451
|
-
const { streamExport } = await import("./exportStream-
|
|
449
|
+
const { getContactsDbPaths, getImsgDbPath, getSlugsDbPath } = await import("./shutdown-DhEgpRwh.js").then((n) => n.a0);
|
|
450
|
+
const { IMessageDB } = await import("./imessage-db-CPCdARwa.js").then((n) => n.i);
|
|
451
|
+
const { streamExport } = await import("./exportStream-DUPD0lbq.js");
|
|
452
452
|
const { parseUserDate } = await import("./date-parse-DJXMfq3a.js");
|
|
453
453
|
const format = normalizeFormat(opts.format ?? "md");
|
|
454
454
|
const ext = extForFormat(format);
|
|
@@ -694,10 +694,52 @@ program.command("tui").description("Launch the read-only terminal UI").option("-
|
|
|
694
694
|
await runTui();
|
|
695
695
|
});
|
|
696
696
|
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);
|
|
697
|
+
program.command("interpret <rowId>").description("Interpret one attachment (transcribe a voice note / caption an image or video)").option("--force", "Re-run even if a cached result exists (evicts the cache entry first)").action(async (rowIdArg, opts) => {
|
|
698
|
+
const rowId = Number(rowIdArg);
|
|
699
|
+
if (!Number.isInteger(rowId) || rowId <= 0) {
|
|
700
|
+
log(`Invalid attachment rowId: ${rowIdArg}`, "err");
|
|
701
|
+
process.exitCode = 1;
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const { getContactsDbPaths, getImsgDbPath, getSlugsDbPath } = await import("./shutdown-DhEgpRwh.js").then((n) => n.a0);
|
|
705
|
+
const { IMessageDB } = await import("./imessage-db-CPCdARwa.js").then((n) => n.i);
|
|
706
|
+
const { getInterpretRuntime, refForAttachment } = await import("./media-intel-runtime-C4C2QsM_.js").then((n) => n.c);
|
|
707
|
+
const { deleteMediaIntel } = await import("./media-intel-cache-mCgH4z9P.js");
|
|
708
|
+
const db = new IMessageDB(getImsgDbPath(), getContactsDbPaths(), getSlugsDbPath());
|
|
709
|
+
try {
|
|
710
|
+
const rec = db.getAttachmentByRowId(rowId);
|
|
711
|
+
if (!rec) {
|
|
712
|
+
log(`Attachment ROWID ${rowId} not found.`, "err");
|
|
713
|
+
process.exitCode = 1;
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const ref = refForAttachment(rec);
|
|
717
|
+
if (!ref) {
|
|
718
|
+
log(
|
|
719
|
+
`Attachment ${rowId} (${rec.mimeType ?? "?"}) is not interpretable media (audio/image/video).`,
|
|
720
|
+
"warn"
|
|
721
|
+
);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
if (opts.force) deleteMediaIntel(ref.key);
|
|
725
|
+
const r = await getInterpretRuntime().service.interpret(ref, { force: true });
|
|
726
|
+
if (r.status === "done" && r.text) {
|
|
727
|
+
const label = ref.kind === "audio" ? "Transcript" : "Description";
|
|
728
|
+
log(`${label} (${r.source ?? "?"}${r.cached ? ", cached" : ""}):`, "ok");
|
|
729
|
+
console.log(r.text);
|
|
730
|
+
} else if (r.status === "skipped") {
|
|
731
|
+
log(`No chain configured for ${ref.kind}. Run \`imsg setup --interactive\`.`, "warn");
|
|
732
|
+
} else {
|
|
733
|
+
log(`Interpretation produced no text${r.error ? ` (${r.error})` : ""}.`, "warn");
|
|
734
|
+
}
|
|
735
|
+
} finally {
|
|
736
|
+
await db.close();
|
|
737
|
+
}
|
|
738
|
+
});
|
|
697
739
|
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)").option("-i, --interactive", "Configure media interpretation (providers, chains, keys)").action(
|
|
698
740
|
async (opts) => {
|
|
699
741
|
if (opts.interactive) {
|
|
700
|
-
const { runSetupWizard } = await import("./setup-wizard-
|
|
742
|
+
const { runSetupWizard } = await import("./setup-wizard-CtDo2a8L.js");
|
|
701
743
|
try {
|
|
702
744
|
await runSetupWizard();
|
|
703
745
|
} catch (err) {
|
|
@@ -709,7 +751,7 @@ program.command("setup").description("Autodetect DB paths and emit an MCP host c
|
|
|
709
751
|
}
|
|
710
752
|
return;
|
|
711
753
|
}
|
|
712
|
-
const { probeMachine, buildMcpSnippet, writeHostConfig } = await import("./setup-
|
|
754
|
+
const { probeMachine, buildMcpSnippet, writeHostConfig } = await import("./setup-helkmfYX.js");
|
|
713
755
|
const report = probeMachine();
|
|
714
756
|
if (!report.imsgDb.readable) {
|
|
715
757
|
log(`✗ Messages DB is not readable: ${report.imsgDb.path}`, "err");
|
|
@@ -752,7 +794,7 @@ program.command("setup").description("Autodetect DB paths and emit an MCP host c
|
|
|
752
794
|
);
|
|
753
795
|
const configCmd = program.command("config").description("Manage TUI settings (theme, accent color)");
|
|
754
796
|
configCmd.command("show").description("Print resolved TUI settings and where each value came from").action(async () => {
|
|
755
|
-
const { resolveTuiConfig, defaultTuiConfigPath } = await import("./tui-config-
|
|
797
|
+
const { resolveTuiConfig, defaultTuiConfigPath } = await import("./tui-config-CZGBCm9N.js");
|
|
756
798
|
const cfg = resolveTuiConfig();
|
|
757
799
|
log(
|
|
758
800
|
`config file : ${cfg.configPath ?? `(none — defaults; would write to ${defaultTuiConfigPath()})`}`
|
|
@@ -765,7 +807,7 @@ configCmd.command("show").description("Print resolved TUI settings and where eac
|
|
|
765
807
|
for (const w of cfg.warnings) log(w, "warn");
|
|
766
808
|
});
|
|
767
809
|
configCmd.command("edit").description("Open the TUI config file in $EDITOR (creates it if missing)").action(async () => {
|
|
768
|
-
const { defaultTuiConfigPath, findTuiConfigPath, writeTuiConfig, DEFAULT_TUI_CONFIG } = await import("./tui-config-
|
|
810
|
+
const { defaultTuiConfigPath, findTuiConfigPath, writeTuiConfig, DEFAULT_TUI_CONFIG } = await import("./tui-config-CZGBCm9N.js");
|
|
769
811
|
const path = findTuiConfigPath() ?? defaultTuiConfigPath();
|
|
770
812
|
const { existsSync: existsSync2 } = await import("node:fs");
|
|
771
813
|
if (!existsSync2(path)) {
|