@agimon-ai/po-config 0.0.0-alpha.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.
Files changed (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/dist/agentPluginMcp.cjs +322 -0
  4. package/dist/agentPluginMcp.cjs.map +1 -0
  5. package/dist/agentPluginMcp.d.mts +38 -0
  6. package/dist/agentPluginMcp.d.mts.map +1 -0
  7. package/dist/agentPluginMcp.mjs +318 -0
  8. package/dist/agentPluginMcp.mjs.map +1 -0
  9. package/dist/apiContracts.cjs +203 -0
  10. package/dist/apiContracts.cjs.map +1 -0
  11. package/dist/apiContracts.d.mts +53 -0
  12. package/dist/apiContracts.d.mts.map +1 -0
  13. package/dist/apiContracts.mjs +198 -0
  14. package/dist/apiContracts.mjs.map +1 -0
  15. package/dist/atomicJson-1W0WhuFF.mjs +44 -0
  16. package/dist/atomicJson-1W0WhuFF.mjs.map +1 -0
  17. package/dist/atomicJson-Dt2z_DXl.cjs +52 -0
  18. package/dist/atomicJson-Dt2z_DXl.cjs.map +1 -0
  19. package/dist/atomicJson.cjs +3 -0
  20. package/dist/atomicJson.d.mts +2 -0
  21. package/dist/atomicJson.mjs +2 -0
  22. package/dist/config-BUw5kHsx.cjs +219 -0
  23. package/dist/config-BUw5kHsx.cjs.map +1 -0
  24. package/dist/config-CKA07FxG.mjs +162 -0
  25. package/dist/config-CKA07FxG.mjs.map +1 -0
  26. package/dist/config-Cm3EV1jp.d.mts +320 -0
  27. package/dist/config-Cm3EV1jp.d.mts.map +1 -0
  28. package/dist/config-NSHRd6na.mjs +2 -0
  29. package/dist/config-zZz80Mh5.cjs +1 -0
  30. package/dist/config.cjs +24 -0
  31. package/dist/config.d.mts +3 -0
  32. package/dist/config.mjs +3 -0
  33. package/dist/configPolicy--TNX7feH.mjs +718 -0
  34. package/dist/configPolicy--TNX7feH.mjs.map +1 -0
  35. package/dist/configPolicy-DmsZEp0t.cjs +795 -0
  36. package/dist/configPolicy-DmsZEp0t.cjs.map +1 -0
  37. package/dist/configSchema.cjs +0 -0
  38. package/dist/configSchema.d.mts +2 -0
  39. package/dist/configSchema.mjs +1 -0
  40. package/dist/configWriter-BTuinLjs.cjs +150 -0
  41. package/dist/configWriter-BTuinLjs.cjs.map +1 -0
  42. package/dist/configWriter-BgkGaLrf.mjs +130 -0
  43. package/dist/configWriter-BgkGaLrf.mjs.map +1 -0
  44. package/dist/configWriter.cjs +5 -0
  45. package/dist/configWriter.d.mts +2 -0
  46. package/dist/configWriter.mjs +2 -0
  47. package/dist/domains-CBYkuxs5.cjs +727 -0
  48. package/dist/domains-CBYkuxs5.cjs.map +1 -0
  49. package/dist/domains-DZOu9vYM.mjs +633 -0
  50. package/dist/domains-DZOu9vYM.mjs.map +1 -0
  51. package/dist/domains.cjs +19 -0
  52. package/dist/domains.d.mts +3 -0
  53. package/dist/domains.mjs +3 -0
  54. package/dist/extensions/mcp-ui.mjs +101 -0
  55. package/dist/extensions/mcp-ui.mjs.map +1 -0
  56. package/dist/extensions/mcp.cjs +171 -0
  57. package/dist/extensions/mcp.cjs.map +1 -0
  58. package/dist/extensions/mcp.d.mts +5 -0
  59. package/dist/extensions/mcp.d.mts.map +1 -0
  60. package/dist/extensions/mcp.mjs +166 -0
  61. package/dist/extensions/mcp.mjs.map +1 -0
  62. package/dist/extensions/server.cjs +504 -0
  63. package/dist/extensions/server.cjs.map +1 -0
  64. package/dist/extensions/server.d.mts +5 -0
  65. package/dist/extensions/server.d.mts.map +1 -0
  66. package/dist/extensions/server.mjs +499 -0
  67. package/dist/extensions/server.mjs.map +1 -0
  68. package/dist/harnessState-CCyBeutB.cjs +188 -0
  69. package/dist/harnessState-CCyBeutB.cjs.map +1 -0
  70. package/dist/harnessState-Dn_S7fB9.mjs +163 -0
  71. package/dist/harnessState-Dn_S7fB9.mjs.map +1 -0
  72. package/dist/harnessState.cjs +6 -0
  73. package/dist/harnessState.d.mts +2 -0
  74. package/dist/harnessState.mjs +2 -0
  75. package/dist/harnessStore-CJSxdSkV.cjs +343 -0
  76. package/dist/harnessStore-CJSxdSkV.cjs.map +1 -0
  77. package/dist/harnessStore-CltwLB7M.mjs +262 -0
  78. package/dist/harnessStore-CltwLB7M.mjs.map +1 -0
  79. package/dist/harnessStore.cjs +15 -0
  80. package/dist/harnessStore.d.mts +2 -0
  81. package/dist/harnessStore.mjs +2 -0
  82. package/dist/index-BAaTySaM.d.mts +35 -0
  83. package/dist/index-BAaTySaM.d.mts.map +1 -0
  84. package/dist/index-BT29GZx5.d.mts +142 -0
  85. package/dist/index-BT29GZx5.d.mts.map +1 -0
  86. package/dist/index-B_lp0W3U.d.mts +114 -0
  87. package/dist/index-B_lp0W3U.d.mts.map +1 -0
  88. package/dist/index-C6GaYjq-.d.mts +6 -0
  89. package/dist/index-C6GaYjq-.d.mts.map +1 -0
  90. package/dist/index-CIQrKS1A.d.mts +50 -0
  91. package/dist/index-CIQrKS1A.d.mts.map +1 -0
  92. package/dist/index-CRcoVYXz.d.mts +39 -0
  93. package/dist/index-CRcoVYXz.d.mts.map +1 -0
  94. package/dist/index-CVlciCJF.d.mts +40 -0
  95. package/dist/index-CVlciCJF.d.mts.map +1 -0
  96. package/dist/index-CeOk2kqs.d.mts +118 -0
  97. package/dist/index-CeOk2kqs.d.mts.map +1 -0
  98. package/dist/index-Ch7ptOkW.d.mts +54 -0
  99. package/dist/index-Ch7ptOkW.d.mts.map +1 -0
  100. package/dist/index-Cvdt-Y-i.d.mts +66 -0
  101. package/dist/index-Cvdt-Y-i.d.mts.map +1 -0
  102. package/dist/index-DJpG-I9K.d.mts +122 -0
  103. package/dist/index-DJpG-I9K.d.mts.map +1 -0
  104. package/dist/index-DNk4q0Qm.d.mts +81 -0
  105. package/dist/index-DNk4q0Qm.d.mts.map +1 -0
  106. package/dist/index.cjs +121 -0
  107. package/dist/index.d.mts +15 -0
  108. package/dist/index.mjs +15 -0
  109. package/dist/init-C_9nyp3-.cjs +448 -0
  110. package/dist/init-C_9nyp3-.cjs.map +1 -0
  111. package/dist/init-DJ6wm-dr.mjs +415 -0
  112. package/dist/init-DJ6wm-dr.mjs.map +1 -0
  113. package/dist/init.cjs +7 -0
  114. package/dist/init.d.mts +2 -0
  115. package/dist/init.mjs +2 -0
  116. package/dist/layeredConfig-B922ibgn.mjs +67 -0
  117. package/dist/layeredConfig-B922ibgn.mjs.map +1 -0
  118. package/dist/layeredConfig-DdI5ysUf.cjs +94 -0
  119. package/dist/layeredConfig-DdI5ysUf.cjs.map +1 -0
  120. package/dist/layeredConfig.cjs +6 -0
  121. package/dist/layeredConfig.d.mts +2 -0
  122. package/dist/layeredConfig.mjs +2 -0
  123. package/dist/majorModes-CmPLEpMT.mjs +333 -0
  124. package/dist/majorModes-CmPLEpMT.mjs.map +1 -0
  125. package/dist/majorModes-DPhVMuhT.cjs +383 -0
  126. package/dist/majorModes-DPhVMuhT.cjs.map +1 -0
  127. package/dist/majorModes.cjs +10 -0
  128. package/dist/majorModes.d.mts +2 -0
  129. package/dist/majorModes.mjs +2 -0
  130. package/dist/piConfig-Bx26HMb_.d.mts +33 -0
  131. package/dist/piConfig-Bx26HMb_.d.mts.map +1 -0
  132. package/dist/piConfig-CCBdZXQr.cjs +260 -0
  133. package/dist/piConfig-CCBdZXQr.cjs.map +1 -0
  134. package/dist/piConfig-qGyCbcVj.mjs +197 -0
  135. package/dist/piConfig-qGyCbcVj.mjs.map +1 -0
  136. package/dist/piConfig.cjs +12 -0
  137. package/dist/piConfig.d.mts +2 -0
  138. package/dist/piConfig.mjs +2 -0
  139. package/dist/piContext.cjs +18 -0
  140. package/dist/piContext.d.mts +3 -0
  141. package/dist/piContext.mjs +2 -0
  142. package/dist/profiles-CTrUCmIh.d.mts +27 -0
  143. package/dist/profiles-CTrUCmIh.d.mts.map +1 -0
  144. package/dist/profiles-DnF9DBpd.cjs +552 -0
  145. package/dist/profiles-DnF9DBpd.cjs.map +1 -0
  146. package/dist/profiles-fujQKrS0.mjs +483 -0
  147. package/dist/profiles-fujQKrS0.mjs.map +1 -0
  148. package/dist/profiles.cjs +13 -0
  149. package/dist/profiles.d.mts +25 -0
  150. package/dist/profiles.d.mts.map +1 -0
  151. package/dist/profiles.mjs +2 -0
  152. package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
  153. package/dist/selectionSwitch.cjs +70 -0
  154. package/dist/selectionSwitch.cjs.map +1 -0
  155. package/dist/selectionSwitch.d.mts +30 -0
  156. package/dist/selectionSwitch.d.mts.map +1 -0
  157. package/dist/selectionSwitch.mjs +64 -0
  158. package/dist/selectionSwitch.mjs.map +1 -0
  159. package/dist/sessionConfig-Boh4Fdje.cjs +342 -0
  160. package/dist/sessionConfig-Boh4Fdje.cjs.map +1 -0
  161. package/dist/sessionConfig-CtLoc-lO.mjs +240 -0
  162. package/dist/sessionConfig-CtLoc-lO.mjs.map +1 -0
  163. package/dist/types.cjs +9 -0
  164. package/dist/types.d.mts +2 -0
  165. package/dist/types.mjs +2 -0
  166. package/llms.txt +12 -0
  167. package/package.json +206 -0
  168. package/src/prompts/po-author-config/SKILL.md +26 -0
  169. package/src/prompts/po-author-config/agents/openai.yaml +4 -0
  170. package/src/prompts/po-author-config/references/config-contract.md +201 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configPolicy-DmsZEp0t.cjs","names":["parseYaml"],"sources":["../src/services/configPolicy/index.ts"],"sourcesContent":["import { parse as parseYaml } from 'yaml';\n\nimport type {\n AutocompactModeConfig,\n AutocompactOverrideConfig,\n AutocompactThresholdConfig,\n ComputerUseConfig,\n ConfigDiagnostic,\n DoomConfig,\n DoomSelectionConfig,\n EditorConfig,\n LenientParseOptions,\n LenientParseResult,\n PlanningAgentConfig,\n PlanningModeConfig,\n PlanningThinkingLevel,\n ProjectTrust,\n ResolvedVoiceConfig,\n VoiceAdapterConfig,\n VoiceAutoCaptureConfig,\n VoiceConfig,\n VoiceEngine,\n VoiceMode,\n VoiceModelConfig,\n VoiceTtsConfig,\n VoiceTtsEngine,\n} from '../../types/config';\nimport type { PersonaVoiceOverride } from '../../types/profiles';\n\nconst DEFAULT_PROJECT_TRUST: ProjectTrust = 'ask';\nconst DEFAULT_VOICE_MODE: VoiceMode = 'legacy';\nconst DEFAULT_VOICE_ENGINE: VoiceEngine = 'auto';\nconst DEFAULT_VOICE_LANGUAGE = 'auto';\nconst DEFAULT_RECORDER_DEVICE = 'none:default';\nconst ROOT_KEYS = ['modes', 'computerUse', 'projectTrust', 'editor', 'voice', 'selection', 'web', 'native'] as const;\nconst SELECTION_KEYS = ['majorMode', 'domains', 'profile'] as const;\nconst MODE_KEYS = ['planning', 'autocompact'] as const;\nconst PLANNING_KEYS = ['main', 'subagents', 'plansDirectory'] as const;\nconst AGENT_KEYS = ['model', 'thinking'] as const;\nconst AUTOCOMPACT_KEYS = ['enabled', 'model', 'thinking', 'thresholds', 'overrides'] as const;\nconst AUTOCOMPACT_THRESHOLD_KEYS = ['pass1', 'pass2', 'pass3'] as const;\nconst AUTOCOMPACT_OVERRIDE_KEYS = ['model', 'tokens'] as const;\nconst COMPUTER_USE_KEYS = ['enabled'] as const;\n/** A pass that fires below this is noise; one above it never fires before Pi compacts natively. */\nconst MIN_AUTOCOMPACT_RATIO = 0.05;\nconst MAX_AUTOCOMPACT_RATIO = 0.99;\n/**\n * A checkpoint at zero tokens fires before the session has said anything. There is\n * deliberately no ceiling: the package takes the lower of the ratio and the token\n * value, so one larger than the window is inert rather than dangerous.\n */\nconst MIN_AUTOCOMPACT_TOKENS = 1;\nconst EDITOR_KEYS = ['command'] as const;\nconst VOICE_KEYS = ['mode', 'engine', 'language', 'recorder', 'adapters', 'autoCapture'] as const;\nconst RECORDER_KEYS = ['binary', 'device'] as const;\nconst WHISPER_CPP_ENGINE = 'whisper-cpp' as const;\nconst OPENAI_WHISPER_ENGINE = 'openai-whisper' as const;\nconst MLX_WHISPER_ENGINE = 'mlx-whisper' as const;\nconst MACOS_SAY_TTS_ENGINE = 'macos-say' as const;\nconst ADAPTERS_KEYS = [WHISPER_CPP_ENGINE, OPENAI_WHISPER_ENGINE, MLX_WHISPER_ENGINE] as const;\nconst ADAPTER_KEYS = ['binary', 'model'] as const;\nconst MODEL_KEYS = ['path', 'id'] as const;\nconst AUTO_CAPTURE_KEYS = [\n 'model',\n 'startPhrases',\n 'stopPhrases',\n 'composeOpenPhrases',\n 'composeSendPhrases',\n 'composeCancelPhrases',\n 'utteranceIdleMs',\n 'composeUtteranceIdleMs',\n 'composeNudgeMs',\n 'transcriptionTimeoutMs',\n 'tts',\n] as const;\nconst TTS_KEYS = ['engine', 'voice', 'rate'] as const;\nconst DEFAULT_UTTERANCE_IDLE_MS = 3_000;\nconst MIN_UTTERANCE_IDLE_MS = 1_500;\nconst MAX_UTTERANCE_IDLE_MS = 10_000;\n/**\n * The endpoint window used while a composition draft is collecting.\n *\n * Shorter than `utteranceIdleMs` on purpose. A turn produces exactly one transcript, so\n * with the ordinary ~3 s window a short command spoken after a brief pause arrives glued\n * to the end of the preceding sentence and can never be read as a command. Roughly twice\n * the VAD's own 600 ms trailing-silence window: long enough that ordinary speech rhythm\n * does not split a sentence, short enough that a deliberate pause does. Over-splitting is\n * harmless here and only here, because draft segments are joined back together with a\n * space.\n */\nconst DEFAULT_COMPOSE_UTTERANCE_IDLE_MS = 1_200;\nconst MIN_COMPOSE_UTTERANCE_IDLE_MS = 800;\nconst MAX_COMPOSE_UTTERANCE_IDLE_MS = 3_000;\n/** Silence before Voice reminds the user a draft is still open. `0` disables it. */\nconst DEFAULT_COMPOSE_NUDGE_MS = 10_000;\nconst MIN_COMPOSE_NUDGE_MS = 5_000;\nconst MAX_COMPOSE_NUDGE_MS = 60_000;\n/**\n * Defaults kept as lists so the phrases that shipped first keep working.\n *\n * `hey doom` and `that's it` read as speech; `doom prompt` and `doom send` are what\n * existing users have in their fingers. Dropping the originals would break them for no\n * gain, and the cost of carrying both is two extra comparisons per command candidate.\n */\nconst DEFAULT_COMPOSE_OPEN_PHRASES = ['hey doom', 'doom prompt'] as const;\nconst DEFAULT_COMPOSE_SEND_PHRASES = [\"that's it\", 'doom send'] as const;\nconst DEFAULT_COMPOSE_CANCEL_PHRASES = ['doom cancel', 'scratch that'] as const;\nconst DEFAULT_TRANSCRIPTION_TIMEOUT_MS = 120_000;\nconst MIN_TRANSCRIPTION_TIMEOUT_MS = 1_000;\nconst MAX_TRANSCRIPTION_TIMEOUT_MS = 120_000;\nconst MAX_CONTROL_PHRASES = 16;\nconst MAX_CONTROL_PHRASE_LENGTH = 64;\nconst MAX_TTS_VOICE_LENGTH = 64;\nconst MIN_TTS_RATE = 80;\nconst MAX_TTS_RATE = 500;\nconst MODEL_REFERENCE_PATTERN = /^[^/\\s]+\\/[^/\\s]+(?:\\/[^/\\s]+)*$/u;\nconst PHRASE_PUNCTUATION_PATTERN = /[\\p{P}\\p{S}]+/gu;\n/**\n * The accepted values, in the order a picker should offer them.\n *\n * Exported because the config UI has to offer exactly what the parser accepts,\n * and a second hand-written list in the contributing package would drift. The\n * validating Sets below are built from these for the same reason.\n */\nexport const DOOM_PLANNING_THINKING_LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const;\nexport const DOOM_VOICE_MODES = [DEFAULT_VOICE_MODE, 'live'] as const;\nexport const DOOM_VOICE_ENGINES = [\n DEFAULT_VOICE_ENGINE,\n WHISPER_CPP_ENGINE,\n OPENAI_WHISPER_ENGINE,\n MLX_WHISPER_ENGINE,\n] as const;\nexport const DOOM_VOICE_TTS_ENGINES = [MACOS_SAY_TTS_ENGINE] as const;\n\nconst THINKING_VALUES = new Set<PlanningThinkingLevel>(DOOM_PLANNING_THINKING_LEVELS);\nconst TRUST_VALUES = new Set<ProjectTrust>(['ask', 'always', 'never']);\nconst VOICE_MODE_VALUES = new Set<VoiceMode>(DOOM_VOICE_MODES);\nconst ENGINE_VALUES = new Set<VoiceEngine>(DOOM_VOICE_ENGINES);\nconst TTS_ENGINE_VALUES = new Set<VoiceTtsEngine>(DOOM_VOICE_TTS_ENGINES);\ntype ConfigObject = Record<string, unknown>;\n\nfunction isObject(value: unknown): value is ConfigObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n/**\n * Where a lenient parse collects unknown keys instead of throwing.\n *\n * Module scope rather than a parameter threaded through the twelve nested\n * parsers: `assertKeys` is the only reader, parsing is synchronous end to end,\n * and `parseDoomConfig` owns the whole window in a try/finally. Threading would\n * touch every parser to change behaviour in one of them.\n */\nlet unknownKeySink: ConfigDiagnostic[] | undefined;\n\nfunction assertKeys(value: ConfigObject, allowed: readonly string[], location: string, filePath: string): void {\n const unknown = Object.keys(value).filter((key) => !allowed.includes(key));\n if (unknown.length === 0) return;\n if (unknownKeySink !== undefined) {\n // Left on the object rather than deleted: every parser reads the fields it\n // knows by name, so an unknown sibling is already inert.\n for (const key of unknown) {\n unknownKeySink.push({\n filePath,\n path: location === 'root' ? key : `${location}.${key}`,\n message: `unsupported ${location} field`,\n });\n }\n return;\n }\n throw new Error(`Doom config at ${filePath} has unsupported ${location} field(s): ${unknown.join(', ')}`);\n}\nfunction optionalString(value: unknown, location: string, filePath: string): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'string' || value.trim().length === 0)\n throw new Error(`Doom config at ${filePath} requires ${location} to be a non-empty string`);\n return value.trim();\n}\nfunction hasControlCharacters(value: string): boolean {\n for (const character of value) {\n const codePoint = character.codePointAt(0);\n if (codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f)) return true;\n }\n return false;\n}\nfunction boundedOptionalString(\n value: unknown,\n location: string,\n filePath: string,\n maximumLength: number,\n): string | undefined {\n const text = optionalString(value, location, filePath);\n if (!text) return undefined;\n if (text.length > maximumLength)\n throw new Error(`Doom config at ${filePath} requires ${location} to be at most ${maximumLength} characters`);\n if (hasControlCharacters(text))\n throw new Error(`Doom config at ${filePath} does not allow control characters in ${location}`);\n return text;\n}\nfunction parseModelReference(value: unknown, filePath: string): string {\n const location = 'voice.autoCapture.model';\n const model = optionalString(value, location, filePath);\n if (!model || !MODEL_REFERENCE_PATTERN.test(model))\n throw new Error(`Doom config at ${filePath} requires ${location} in provider/model-id form`);\n return model;\n}\nfunction normalizeControlPhrase(value: string): string {\n return value\n .normalize('NFKC')\n .toLocaleLowerCase('en-US')\n .replace(PHRASE_PUNCTUATION_PATTERN, ' ')\n .replace(/\\s+/gu, ' ')\n .trim();\n}\nfunction parseControlPhrases(value: unknown, location: string, filePath: string): string[] | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value) || value.some((phrase) => typeof phrase !== 'string'))\n throw new Error(`Doom config at ${filePath} requires ${location} to be an array of strings`);\n if (value.length > MAX_CONTROL_PHRASES)\n throw new Error(\n `Doom config at ${filePath} requires ${location} to contain at most ${MAX_CONTROL_PHRASES} phrases`,\n );\n const phrases = value.map((phrase) => phrase.trim());\n const normalized = new Set<string>();\n for (const phrase of phrases) {\n if (phrase.length === 0)\n throw new Error(`Doom config at ${filePath} requires ${location} to contain non-empty strings`);\n if (phrase.length > MAX_CONTROL_PHRASE_LENGTH)\n throw new Error(\n `Doom config at ${filePath} requires ${location} phrases to be at most ${MAX_CONTROL_PHRASE_LENGTH} characters`,\n );\n if (hasControlCharacters(phrase))\n throw new Error(`Doom config at ${filePath} does not allow control characters in ${location}`);\n const key = normalizeControlPhrase(phrase);\n if (!key) throw new Error(`Doom config at ${filePath} requires ${location} to contain words`);\n if (normalized.has(key))\n throw new Error(`Doom config at ${filePath} does not allow normalized duplicates in ${location}`);\n normalized.add(key);\n }\n return phrases;\n}\nfunction parseBoundedInteger(\n value: unknown,\n location: string,\n filePath: string,\n minimum: number,\n maximum: number,\n): number | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'number' || !Number.isInteger(value) || value < minimum || value > maximum)\n throw new Error(`Doom config at ${filePath} requires ${location} to be an integer from ${minimum} to ${maximum}`);\n return value;\n}\n/**\n * Rejects a phrase claimed by two control roles at once.\n *\n * Composition commands are adjudicated before stop phrases and before leading\n * start-phrase removal, so a phrase shared with a later role is not ambiguous, it is\n * silently unreachable. Without a screen there is nothing to show which role won.\n */\nfunction assertDisjointPhrases(\n left: readonly string[] | undefined,\n right: readonly string[] | undefined,\n leftName: string,\n rightName: string,\n filePath: string,\n): void {\n const keys = new Set((left ?? []).map(normalizeControlPhrase));\n if ((right ?? []).some((phrase) => keys.has(normalizeControlPhrase(phrase))))\n throw new Error(`Doom config at ${filePath} does not allow a phrase in both ${leftName} and ${rightName}`);\n}\nfunction parseTtsConfig(value: unknown, filePath: string): VoiceTtsConfig {\n const location = 'voice.autoCapture.tts';\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, TTS_KEYS, location, filePath);\n if (typeof value.engine !== 'string' || !TTS_ENGINE_VALUES.has(value.engine as VoiceTtsEngine))\n throw new Error(\n `Doom config at ${filePath} requires ${location}.engine to be one of: ${[...TTS_ENGINE_VALUES].join(', ')}`,\n );\n const voice = boundedOptionalString(value.voice, `${location}.voice`, filePath, MAX_TTS_VOICE_LENGTH);\n const rate = value.rate;\n if (\n rate !== undefined &&\n (typeof rate !== 'number' || !Number.isInteger(rate) || rate < MIN_TTS_RATE || rate > MAX_TTS_RATE)\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.rate to be an integer from ${MIN_TTS_RATE} to ${MAX_TTS_RATE}`,\n );\n }\n return {\n engine: value.engine as VoiceTtsEngine,\n ...(voice ? { voice } : {}),\n ...(rate === undefined ? {} : { rate }),\n };\n}\nfunction parseAutoCapture(value: unknown, filePath: string): VoiceAutoCaptureConfig | undefined {\n if (value === undefined) return undefined;\n const location = 'voice.autoCapture';\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AUTO_CAPTURE_KEYS, location, filePath);\n const startPhrases = parseControlPhrases(value.startPhrases, `${location}.startPhrases`, filePath);\n const stopPhrases = parseControlPhrases(value.stopPhrases, `${location}.stopPhrases`, filePath);\n const composeOpenPhrases = parseControlPhrases(value.composeOpenPhrases, `${location}.composeOpenPhrases`, filePath);\n const composeSendPhrases = parseControlPhrases(value.composeSendPhrases, `${location}.composeSendPhrases`, filePath);\n const composeCancelPhrases = parseControlPhrases(\n value.composeCancelPhrases,\n `${location}.composeCancelPhrases`,\n filePath,\n );\n const utteranceIdleMs = parseBoundedInteger(\n value.utteranceIdleMs,\n `${location}.utteranceIdleMs`,\n filePath,\n MIN_UTTERANCE_IDLE_MS,\n MAX_UTTERANCE_IDLE_MS,\n );\n const composeUtteranceIdleMs = parseBoundedInteger(\n value.composeUtteranceIdleMs,\n `${location}.composeUtteranceIdleMs`,\n filePath,\n MIN_COMPOSE_UTTERANCE_IDLE_MS,\n MAX_COMPOSE_UTTERANCE_IDLE_MS,\n );\n // Zero is the documented off switch, so it is accepted outside the range rather than\n // forcing a separate boolean that could disagree with the interval.\n const composeNudgeMs =\n value.composeNudgeMs === 0\n ? 0\n : parseBoundedInteger(\n value.composeNudgeMs,\n `${location}.composeNudgeMs`,\n filePath,\n MIN_COMPOSE_NUDGE_MS,\n MAX_COMPOSE_NUDGE_MS,\n );\n const transcriptionTimeoutMs = parseBoundedInteger(\n value.transcriptionTimeoutMs,\n `${location}.transcriptionTimeoutMs`,\n filePath,\n MIN_TRANSCRIPTION_TIMEOUT_MS,\n MAX_TRANSCRIPTION_TIMEOUT_MS,\n );\n assertDisjointPhrases(startPhrases, stopPhrases, 'startPhrases', 'stopPhrases', filePath);\n // `composeOpenPhrases` may overlap `startPhrases`: addressing the agent and opening a\n // draft are the same gesture, composition is adjudicated first, and the shipped\n // defaults deliberately share `hey doom`.\n assertDisjointPhrases(\n composeSendPhrases,\n composeCancelPhrases,\n 'composeSendPhrases',\n 'composeCancelPhrases',\n filePath,\n );\n assertDisjointPhrases(composeOpenPhrases, composeSendPhrases, 'composeOpenPhrases', 'composeSendPhrases', filePath);\n assertDisjointPhrases(\n composeOpenPhrases,\n composeCancelPhrases,\n 'composeOpenPhrases',\n 'composeCancelPhrases',\n filePath,\n );\n assertDisjointPhrases(stopPhrases, composeSendPhrases, 'stopPhrases', 'composeSendPhrases', filePath);\n assertDisjointPhrases(stopPhrases, composeCancelPhrases, 'stopPhrases', 'composeCancelPhrases', filePath);\n return {\n model: parseModelReference(value.model, filePath),\n ...(startPhrases ? { startPhrases } : {}),\n ...(stopPhrases ? { stopPhrases } : {}),\n ...(composeOpenPhrases ? { composeOpenPhrases } : {}),\n ...(composeSendPhrases ? { composeSendPhrases } : {}),\n ...(composeCancelPhrases ? { composeCancelPhrases } : {}),\n ...(utteranceIdleMs === undefined ? {} : { utteranceIdleMs }),\n ...(composeUtteranceIdleMs === undefined ? {} : { composeUtteranceIdleMs }),\n ...(composeNudgeMs === undefined ? {} : { composeNudgeMs }),\n ...(transcriptionTimeoutMs === undefined ? {} : { transcriptionTimeoutMs }),\n tts: parseTtsConfig(value.tts, filePath),\n };\n}\nfunction parseThinking(value: unknown, location: string, filePath: string): PlanningThinkingLevel | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'string' || !THINKING_VALUES.has(value as PlanningThinkingLevel)) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.thinking to be one of: ${[...THINKING_VALUES].join(', ')}`,\n );\n }\n return value as PlanningThinkingLevel;\n}\nfunction parseAgent(value: unknown, location: string, filePath: string): PlanningAgentConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AGENT_KEYS, location, filePath);\n const thinking = parseThinking(value.thinking, location, filePath);\n const model = optionalString(value.model, `${location}.model`, filePath);\n return { ...(model ? { model } : {}), ...(thinking ? { thinking } : {}) };\n}\n/**\n * A flag the cockpit can write.\n *\n * The settings page writes strings and nothing else, so `'true'` has to mean\n * what a hand-edited `true` means. Anything else is rejected rather than\n * treated as false, because a typo that silently disables a feature is the\n * worst reading of it.\n */\nfunction parseFlexibleBoolean(value: unknown, location: string, filePath: string): boolean | undefined {\n if (value === undefined) return undefined;\n if (typeof value === 'boolean') return value;\n if (value === 'true') return true;\n if (value === 'false') return false;\n throw new Error(`Doom config at ${filePath} requires ${location} to be true or false`);\n}\n/** Same story as the flag: a number, or the string form the settings page writes. */\nfunction parseFlexibleRatio(value: unknown, location: string, filePath: string): number | undefined {\n if (value === undefined) return undefined;\n const ratio = typeof value === 'string' ? Number(value.trim()) : value;\n if (\n typeof ratio !== 'number' ||\n !Number.isFinite(ratio) ||\n ratio < MIN_AUTOCOMPACT_RATIO ||\n ratio > MAX_AUTOCOMPACT_RATIO\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location} to be a number from ${MIN_AUTOCOMPACT_RATIO} to ${MAX_AUTOCOMPACT_RATIO}`,\n );\n }\n return ratio;\n}\nfunction parseAutocompactThresholds(\n value: unknown,\n location: string,\n filePath: string,\n): AutocompactThresholdConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AUTOCOMPACT_THRESHOLD_KEYS, location, filePath);\n const pass1 = parseFlexibleRatio(value.pass1, `${location}.pass1`, filePath);\n const pass2 = parseFlexibleRatio(value.pass2, `${location}.pass2`, filePath);\n const pass3 = parseFlexibleRatio(value.pass3, `${location}.pass3`, filePath);\n return {\n ...(pass1 === undefined ? {} : { pass1 }),\n ...(pass2 === undefined ? {} : { pass2 }),\n ...(pass3 === undefined ? {} : { pass3 }),\n };\n}\n/** Absolute token counts, unlike the ratios, are only ever written by hand, so no string form is accepted. */\nfunction parseAutocompactTokenCount(value: unknown, location: string, filePath: string): number | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < MIN_AUTOCOMPACT_TOKENS)\n throw new Error(\n `Doom config at ${filePath} requires ${location} to be an integer of at least ${MIN_AUTOCOMPACT_TOKENS}`,\n );\n return value;\n}\nfunction parseAutocompactOverrideEntry(value: unknown, location: string, filePath: string): AutocompactOverrideConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AUTOCOMPACT_OVERRIDE_KEYS, location, filePath);\n const model = optionalString(value.model, `${location}.model`, filePath);\n if (!model) throw new Error(`Doom config at ${filePath} requires ${location}.model`);\n if (!isObject(value.tokens))\n throw new Error(`Doom config at ${filePath} requires ${location}.tokens to be an object`);\n assertKeys(value.tokens, AUTOCOMPACT_THRESHOLD_KEYS, `${location}.tokens`, filePath);\n const pass1 = parseAutocompactTokenCount(value.tokens.pass1, `${location}.tokens.pass1`, filePath);\n const pass2 = parseAutocompactTokenCount(value.tokens.pass2, `${location}.tokens.pass2`, filePath);\n const pass3 = parseAutocompactTokenCount(value.tokens.pass3, `${location}.tokens.pass3`, filePath);\n // An entry that pins nothing is a typo rather than a way to say \"leave this model alone\":\n // omitting the entry already does that, and a silent no-op reads like the override worked.\n if (pass1 === undefined && pass2 === undefined && pass3 === undefined)\n throw new Error(`Doom config at ${filePath} requires one of ${location}.tokens.pass1, pass2, or pass3`);\n return {\n model,\n tokens: {\n ...(pass1 === undefined ? {} : { pass1 }),\n ...(pass2 === undefined ? {} : { pass2 }),\n ...(pass3 === undefined ? {} : { pass3 }),\n },\n };\n}\nfunction parseAutocompactOverrides(\n value: unknown,\n location: string,\n filePath: string,\n): AutocompactOverrideConfig[] | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be a list`);\n return value.map((entry, index) => parseAutocompactOverrideEntry(entry, `${location}[${index}]`, filePath));\n}\nexport function parseAutocompactModeConfig(\n value: unknown,\n filePath: string,\n location = 'modes.autocompact',\n): AutocompactModeConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AUTOCOMPACT_KEYS, location, filePath);\n const enabled = parseFlexibleBoolean(value.enabled, `${location}.enabled`, filePath);\n const model = optionalString(value.model, `${location}.model`, filePath);\n const thinking = parseThinking(value.thinking, location, filePath);\n const thresholds = parseAutocompactThresholds(value.thresholds, `${location}.thresholds`, filePath);\n const overrides = parseAutocompactOverrides(value.overrides, `${location}.overrides`, filePath);\n return {\n ...(enabled === undefined ? {} : { enabled }),\n ...(model ? { model } : {}),\n ...(thinking ? { thinking } : {}),\n ...(thresholds ? { thresholds } : {}),\n ...(overrides ? { overrides } : {}),\n };\n}\nexport function parsePlanningModeConfig(\n value: unknown,\n filePath: string,\n location = 'modes.planning',\n): PlanningModeConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, PLANNING_KEYS, location, filePath);\n return {\n main: parseAgent(value.main, `${location}.main`, filePath),\n subagents: parseAgent(value.subagents, `${location}.subagents`, filePath),\n plansDirectory: optionalString(value.plansDirectory, `${location}.plansDirectory`, filePath),\n };\n}\nfunction parseModel(value: unknown, location: string, filePath: string, allowId: boolean): VoiceModelConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, MODEL_KEYS, location, filePath);\n const modelPath = optionalString(value.path, `${location}.path`, filePath);\n const id = optionalString(value.id, `${location}.id`, filePath);\n if (Boolean(modelPath) === Boolean(id))\n throw new Error(`Doom config at ${filePath} requires exactly one of ${location}.path or ${location}.id`);\n if (id && !allowId) throw new Error(`Doom config at ${filePath} does not support ${location}.id for whisper-cpp`);\n return { ...(modelPath ? { path: modelPath } : {}), ...(id ? { id } : {}) };\n}\nfunction parseAdapter(\n value: unknown,\n location: string,\n filePath: string,\n allowId: boolean,\n): VoiceAdapterConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, ADAPTER_KEYS, location, filePath);\n const binary = optionalString(value.binary, `${location}.binary`, filePath);\n return { ...(binary ? { binary } : {}), model: parseModel(value.model, `${location}.model`, filePath, allowId) };\n}\nfunction parseVoice(value: unknown, filePath: string): VoiceConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires voice to be an object`);\n assertKeys(value, VOICE_KEYS, 'voice', filePath);\n const result: VoiceConfig = {};\n if (value.mode !== undefined) {\n if (typeof value.mode !== 'string' || !VOICE_MODE_VALUES.has(value.mode as VoiceMode))\n throw new Error(\n `Doom config at ${filePath} requires voice.mode to be one of: ${[...VOICE_MODE_VALUES].join(', ')}`,\n );\n result.mode = value.mode as VoiceMode;\n }\n if (value.engine !== undefined) {\n if (typeof value.engine !== 'string' || !ENGINE_VALUES.has(value.engine as VoiceEngine))\n throw new Error(\n `Doom config at ${filePath} requires voice.engine to be one of: ${[...ENGINE_VALUES].join(', ')}`,\n );\n result.engine = value.engine as VoiceEngine;\n }\n const language = optionalString(value.language, 'voice.language', filePath);\n if (language) result.language = language;\n if (value.recorder !== undefined) {\n if (!isObject(value.recorder))\n throw new Error(`Doom config at ${filePath} requires voice.recorder to be an object`);\n assertKeys(value.recorder, RECORDER_KEYS, 'voice.recorder', filePath);\n result.recorder = {};\n const binary = optionalString(value.recorder.binary, 'voice.recorder.binary', filePath);\n const device = optionalString(value.recorder.device, 'voice.recorder.device', filePath);\n if (binary) result.recorder.binary = binary;\n if (device) result.recorder.device = device;\n }\n if (value.adapters !== undefined) {\n if (!isObject(value.adapters))\n throw new Error(`Doom config at ${filePath} requires voice.adapters to be an object`);\n assertKeys(value.adapters, ADAPTERS_KEYS, 'voice.adapters', filePath);\n result.adapters = {\n [WHISPER_CPP_ENGINE]: parseAdapter(\n value.adapters[WHISPER_CPP_ENGINE],\n `voice.adapters.${WHISPER_CPP_ENGINE}`,\n filePath,\n false,\n ),\n [OPENAI_WHISPER_ENGINE]: parseAdapter(\n value.adapters[OPENAI_WHISPER_ENGINE],\n `voice.adapters.${OPENAI_WHISPER_ENGINE}`,\n filePath,\n true,\n ),\n [MLX_WHISPER_ENGINE]: parseAdapter(\n value.adapters[MLX_WHISPER_ENGINE],\n `voice.adapters.${MLX_WHISPER_ENGINE}`,\n filePath,\n true,\n ),\n };\n }\n const autoCapture = parseAutoCapture(value.autoCapture, filePath);\n if (autoCapture) result.autoCapture = autoCapture;\n return result;\n}\nfunction parseSelection(value: unknown, filePath: string): DoomSelectionConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires selection to be an object`);\n assertKeys(value, SELECTION_KEYS, 'selection', filePath);\n const domains = value.domains;\n if (domains !== undefined && (!Array.isArray(domains) || domains.some((name) => typeof name !== 'string'))) {\n throw new Error(`Doom config at ${filePath} requires selection.domains to be an array of strings`);\n }\n return {\n majorMode: optionalString(value.majorMode, 'selection.majorMode', filePath),\n // An empty list is meaningful: it selects no content domains at all.\n domains: domains as string[] | undefined,\n profile: optionalString(value.profile, 'selection.profile', filePath),\n };\n}\nfunction parseEditor(value: unknown, filePath: string): EditorConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires editor to be an object`);\n assertKeys(value, EDITOR_KEYS, 'editor', filePath);\n return { command: optionalString(value.command, 'editor.command', filePath) };\n}\n\n/** `web` and `native` each choose a template, so both read the same `{ template }` shape. */\nfunction parseTemplateChoice(\n value: unknown,\n root: 'web' | 'native',\n filePath: string,\n): DoomConfig['web'] | DoomConfig['native'] {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${root} to be an object`);\n assertKeys(value, ['template'], root, filePath);\n if (value.template === undefined) return {};\n const template = optionalString(value.template, `${root}.template`, filePath);\n if (!template || template.length > 160 || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(template)) {\n throw new Error(`Doom config at ${filePath} requires ${root}.template to be a kebab-case template identifier`);\n }\n return { template };\n}\nfunction parseComputerUse(value: unknown, filePath: string): ComputerUseConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires computerUse to be an object`);\n assertKeys(value, COMPUTER_USE_KEYS, 'computerUse', filePath);\n const enabled = parseFlexibleBoolean(value.enabled, 'computerUse.enabled', filePath);\n return enabled === undefined ? {} : { enabled };\n}\n/**\n * Reads one Doom config file.\n *\n * The two-argument form is the strict contract every harness-launch consumer\n * relies on: an unknown key is a hard error. `po sync` opts into the\n * lenient form so a config written for a different version cannot break a\n * build; `po doctor` is the strict check that still reports those keys.\n * Invalid values for known keys throw in both modes.\n */\nexport function parseDoomConfig(content: string, filePath: string): DoomConfig;\nexport function parseDoomConfig(content: string, filePath: string, options: LenientParseOptions): LenientParseResult;\nexport function parseDoomConfig(\n content: string,\n filePath: string,\n options?: LenientParseOptions,\n): DoomConfig | LenientParseResult {\n if (options?.lenient !== true) return parseStrict(content, filePath);\n const diagnostics: ConfigDiagnostic[] = [];\n const previous = unknownKeySink;\n unknownKeySink = diagnostics;\n try {\n return { config: parseStrict(content, filePath), diagnostics };\n } finally {\n unknownKeySink = previous;\n }\n}\n\nfunction parseStrict(content: string, filePath: string): DoomConfig {\n let parsed: unknown;\n try {\n parsed = parseYaml(content) ?? {};\n } catch (error) {\n throw new Error(`Could not parse Doom config at ${filePath}`, { cause: error });\n }\n if (!isObject(parsed)) throw new Error(`Doom config at ${filePath} must be a YAML object`);\n assertKeys(parsed, ROOT_KEYS, 'root', filePath);\n let modes: DoomConfig['modes'];\n if (parsed.modes !== undefined) {\n if (!isObject(parsed.modes)) throw new Error(`Doom config at ${filePath} requires modes to be an object`);\n assertKeys(parsed.modes, MODE_KEYS, 'modes', filePath);\n modes = {\n ...(parsed.modes.planning === undefined\n ? {}\n : { planning: parsePlanningModeConfig(parsed.modes.planning, filePath) }),\n ...(parsed.modes.autocompact === undefined\n ? {}\n : { autocompact: parseAutocompactModeConfig(parsed.modes.autocompact, filePath) }),\n };\n }\n const trust = parsed.projectTrust === undefined ? DEFAULT_PROJECT_TRUST : parsed.projectTrust;\n if (typeof trust !== 'string' || !TRUST_VALUES.has(trust as ProjectTrust))\n throw new Error(`Doom config at ${filePath} projectTrust must be ask, always, or never`);\n return {\n modes,\n computerUse: parseComputerUse(parsed.computerUse, filePath),\n projectTrust: trust as ProjectTrust,\n editor: parseEditor(parsed.editor, filePath),\n voice: parseVoice(parsed.voice, filePath),\n selection: parseSelection(parsed.selection, filePath),\n web: parseTemplateChoice(parsed.web, 'web', filePath),\n native: parseTemplateChoice(parsed.native, 'native', filePath),\n };\n}\nfunction mergeAgent(\n globalValue?: PlanningAgentConfig,\n repositoryValue?: PlanningAgentConfig,\n): PlanningAgentConfig | undefined {\n return globalValue || repositoryValue ? { ...globalValue, ...repositoryValue } : undefined;\n}\nexport function mergeDoomConfigs(globalConfig: DoomConfig, repositoryConfig: DoomConfig): DoomConfig {\n if (repositoryConfig.voice?.mode)\n throw new Error('Repository Doom config voice.mode is global-only; configure it in ~/.po/config.yaml');\n if (repositoryConfig.voice?.autoCapture)\n throw new Error('Repository Doom config voice.autoCapture is global-only; configure it in ~/.po/config.yaml');\n if (repositoryConfig.computerUse)\n throw new Error('Repository Doom config computerUse is global-only; configure it in ~/.po/config.yaml');\n const globalPlanning = globalConfig.modes?.planning;\n const repositoryPlanning = repositoryConfig.modes?.planning;\n const planning =\n globalPlanning || repositoryPlanning\n ? {\n main: mergeAgent(globalPlanning?.main, repositoryPlanning?.main),\n subagents: mergeAgent(globalPlanning?.subagents, repositoryPlanning?.subagents),\n plansDirectory: repositoryPlanning?.plansDirectory ?? globalPlanning?.plansDirectory,\n }\n : undefined;\n const globalAutocompact = globalConfig.modes?.autocompact;\n const repositoryAutocompact = repositoryConfig.modes?.autocompact;\n // Per key, so a repository can pin the model and inherit the ladder, or the\n // other way round. `overrides` is deliberately not merged that way: the spread\n // below lets a repository list replace the global one whole, because the rules\n // are first-match-wins and interleaving two files a reader cannot see together\n // would decide precedence somewhere neither of them says.\n const autocompact =\n globalAutocompact || repositoryAutocompact\n ? {\n ...globalAutocompact,\n ...repositoryAutocompact,\n ...(globalAutocompact?.thresholds || repositoryAutocompact?.thresholds\n ? { thresholds: { ...globalAutocompact?.thresholds, ...repositoryAutocompact?.thresholds } }\n : {}),\n }\n : undefined;\n const voice = mergeVoice(globalConfig.voice, repositoryConfig.voice);\n return {\n modes:\n planning || autocompact\n ? { ...(planning ? { planning } : {}), ...(autocompact ? { autocompact } : {}) }\n : undefined,\n computerUse: globalConfig.computerUse,\n projectTrust: repositoryConfig.projectTrust,\n editor: globalConfig.editor,\n voice,\n web:\n globalConfig.web || repositoryConfig.web\n ? { template: repositoryConfig.web?.template ?? globalConfig.web?.template }\n : undefined,\n native:\n globalConfig.native || repositoryConfig.native\n ? { template: repositoryConfig.native?.template ?? globalConfig.native?.template }\n : undefined,\n // Per key, so a repository can pin one axis and inherit the rest.\n selection:\n globalConfig.selection || repositoryConfig.selection\n ? { ...globalConfig.selection, ...repositoryConfig.selection }\n : undefined,\n };\n}\nfunction mergeAdapter(\n globalValue?: VoiceAdapterConfig,\n repositoryValue?: VoiceAdapterConfig,\n): VoiceAdapterConfig | undefined {\n if (!globalValue && !repositoryValue) return undefined;\n return { ...globalValue, ...repositoryValue, model: repositoryValue?.model ?? globalValue!.model };\n}\nfunction mergeVoice(globalValue?: VoiceConfig, repositoryValue?: VoiceConfig): VoiceConfig | undefined {\n if (!globalValue && !repositoryValue) return undefined;\n return {\n ...globalValue,\n ...repositoryValue,\n ...(globalValue?.mode ? { mode: globalValue.mode } : {}),\n recorder:\n globalValue?.recorder || repositoryValue?.recorder\n ? { ...globalValue?.recorder, ...repositoryValue?.recorder }\n : undefined,\n adapters:\n globalValue?.adapters || repositoryValue?.adapters\n ? {\n [WHISPER_CPP_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[WHISPER_CPP_ENGINE],\n repositoryValue?.adapters?.[WHISPER_CPP_ENGINE],\n ),\n [OPENAI_WHISPER_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[OPENAI_WHISPER_ENGINE],\n repositoryValue?.adapters?.[OPENAI_WHISPER_ENGINE],\n ),\n [MLX_WHISPER_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[MLX_WHISPER_ENGINE],\n repositoryValue?.adapters?.[MLX_WHISPER_ENGINE],\n ),\n }\n : undefined,\n ...(globalValue?.autoCapture ? { autoCapture: globalValue.autoCapture } : {}),\n };\n}\n/**\n * Resolves voice settings, optionally shadowed by the active profile's override.\n *\n * The override is per field, so a persona that sets only `rate` keeps the voice\n * from config.yaml. `engine` is deliberately not overridable: it selects the\n * synthesiser binary, which is a machine-level fact rather than a persona's.\n *\n * The result is read once when autonomous voice activates, because\n * `VoiceNarrationPlayback.config` is assigned in `activate()` and has no setter.\n * That is sufficient today only because every profile switch reloads the\n * session, which deactivates and reactivates voice. A future in-place profile\n * switch would leave the spoken voice stale until the next activation.\n */\nexport function resolveVoiceConfig(config: VoiceConfig, ttsOverride?: PersonaVoiceOverride): ResolvedVoiceConfig {\n return {\n mode: config.mode ?? DEFAULT_VOICE_MODE,\n engine: config.engine ?? DEFAULT_VOICE_ENGINE,\n language: config.language ?? DEFAULT_VOICE_LANGUAGE,\n recorder: { ...config.recorder, device: config.recorder?.device ?? DEFAULT_RECORDER_DEVICE },\n adapters: config.adapters ?? {},\n ...(config.autoCapture\n ? {\n autoCapture: {\n ...config.autoCapture,\n startPhrases: [...(config.autoCapture.startPhrases ?? [])],\n stopPhrases: [...(config.autoCapture.stopPhrases ?? [])],\n // Unlike start and stop phrases, these default to a non-empty list: without\n // a send phrase a draft could be opened and never submitted.\n composeOpenPhrases: [...(config.autoCapture.composeOpenPhrases ?? DEFAULT_COMPOSE_OPEN_PHRASES)],\n composeSendPhrases: [...(config.autoCapture.composeSendPhrases ?? DEFAULT_COMPOSE_SEND_PHRASES)],\n composeCancelPhrases: [...(config.autoCapture.composeCancelPhrases ?? DEFAULT_COMPOSE_CANCEL_PHRASES)],\n utteranceIdleMs: config.autoCapture.utteranceIdleMs ?? DEFAULT_UTTERANCE_IDLE_MS,\n composeUtteranceIdleMs: config.autoCapture.composeUtteranceIdleMs ?? DEFAULT_COMPOSE_UTTERANCE_IDLE_MS,\n composeNudgeMs: config.autoCapture.composeNudgeMs ?? DEFAULT_COMPOSE_NUDGE_MS,\n transcriptionTimeoutMs: config.autoCapture.transcriptionTimeoutMs ?? DEFAULT_TRANSCRIPTION_TIMEOUT_MS,\n tts: {\n ...config.autoCapture.tts,\n ...(ttsOverride?.voice === undefined ? {} : { voice: ttsOverride.voice }),\n ...(ttsOverride?.rate === undefined ? {} : { rate: ttsOverride.rate }),\n },\n },\n }\n : {}),\n };\n}\n\n/**\n * Which files a key may be written to.\n *\n * `mergeDoomConfigs` above already decides this per field, but it decides it by\n * doing it: `editor` reads only from the global side, `projectTrust` only from\n * the repository side, and repository `voice.mode` or `voice.autoCapture`\n * declarations throw outright. A caller holding a key path has no way to ask.\n * surface offers writes that throw, or worse, writes that land in a file the\n * merge will ignore, so the change appears to do nothing.\n *\n * Declared here rather than beside the writer because the rule is a property of\n * the merge, not of the filesystem, and a test asserts the two agree.\n */\nexport type ConfigKeyScope = 'global' | 'repository' | 'both';\n\n/** Root keys the merge reads from one side only. */\nconst GLOBAL_ONLY_ROOTS: readonly string[] = ['computerUse', 'editor'];\nconst REPOSITORY_ONLY_ROOTS: readonly string[] = ['projectTrust'];\n/** Nested exceptions; a repository declaration of either throws in the merge. */\nconst GLOBAL_ONLY_PATHS: readonly (readonly string[])[] = [\n ['voice', 'mode'],\n ['voice', 'autoCapture'],\n];\n\nfunction startsWith(keyPath: readonly string[], prefix: readonly string[]): boolean {\n return prefix.length <= keyPath.length && prefix.every((segment, index) => keyPath[index] === segment);\n}\n\n/**\n * The scopes a key path accepts. An unknown root answers `both` rather than\n * throwing: the parser is what rejects unsupported keys, and it reports them\n * far better than a scope lookup could.\n */\nexport function configScopeOf(keyPath: readonly string[]): ConfigKeyScope {\n if (keyPath.length === 0) return 'both';\n if (GLOBAL_ONLY_PATHS.some((prefix) => startsWith(keyPath, prefix))) return 'global';\n const root = keyPath[0]!;\n if (GLOBAL_ONLY_ROOTS.includes(root)) return 'global';\n if (REPOSITORY_ONLY_ROOTS.includes(root)) return 'repository';\n return 'both';\n}\n\n/** Every root key the config accepts, so a caller can enumerate without importing the parser's internals. */\nexport function configRootKeys(): readonly string[] {\n return ROOT_KEYS;\n}\n\nconst KEY_SEPARATOR = '.';\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Every key path a parsed document actually sets, as dotted strings.\n *\n * A settings surface needs to tell \"this file set this key\" from \"this file is\n * silent about it\", and the parsed `DoomConfig` cannot answer: an absent file\n * reads as `{ projectTrust: 'ask' }` and `parseDoomConfig` always returns all\n * known root keys, some undefined. So the question is asked of the raw document\n * instead, before any defaulting has happened.\n *\n * Leaves only. A list is a leaf because it is replaced whole, and an empty\n * record sets nothing.\n */\nexport function configLeafKeys(value: unknown, prefix: readonly string[] = []): string[] {\n if (!isPlainRecord(value)) return prefix.length > 0 ? [prefix.join(KEY_SEPARATOR)] : [];\n const keys = Object.keys(value);\n if (keys.length === 0) return prefix.length > 0 ? [prefix.join(KEY_SEPARATOR)] : [];\n return keys.flatMap((key) => configLeafKeys(value[key], [...prefix, key]));\n}\n\n/** The value a key path points at, or undefined when any step is missing. */\nexport function valueAtKeyPath(value: unknown, keyPath: readonly string[]): unknown {\n let current: unknown = value;\n for (const segment of keyPath) {\n if (!isPlainRecord(current)) return undefined;\n current = current[segment];\n }\n return current;\n}\n"],"mappings":"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"}
File without changes
@@ -0,0 +1,2 @@
1
+ import { a as PiConfigPaths, i as PiConfigLoadOptions, n as DoomAdapterSettings, r as PiConfig, t as ConfigAdapterRegistrationOptions } from "./piConfig-Bx26HMb_.mjs";
2
+ export type { ConfigAdapterRegistrationOptions, DoomAdapterSettings, PiConfig, PiConfigLoadOptions, PiConfigPaths };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,150 @@
1
+ const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
2
+ const require_configPolicy = require("./configPolicy-DmsZEp0t.cjs");
3
+ let node_crypto = require("node:crypto");
4
+ let node_path = require("node:path");
5
+ node_path = require_rolldown_runtime.__toESM(node_path, 1);
6
+ let yaml = require("yaml");
7
+ let node_fs_promises = require("node:fs/promises");
8
+ node_fs_promises = require_rolldown_runtime.__toESM(node_fs_promises, 1);
9
+ //#region src/services/configWriter/index.ts
10
+ /**
11
+ * Writes into the global Doom config file, one key path at a time.
12
+ *
13
+ * Extracted from doom-file-edit, which owned the only writer in the repo and had
14
+ * it hard-coded to `editor.command`. Several extensions now edit their own
15
+ * settings from the config panel, and every one of them needs the same four
16
+ * properties: keep the user's comments, never publish a file the parser would
17
+ * reject, never leave a half-written file behind, and apply a set of related
18
+ * edits together.
19
+ *
20
+ * That last one is not a convenience. `voice.adapters.<engine>.model` accepts a
21
+ * path or an id but not both, so switching between them is a set and an unset
22
+ * that must land in the same write, or the file is briefly invalid.
23
+ *
24
+ * Atomicity is the same rename trick as `atomicJson.ts` next door, and carries
25
+ * the same caveat: it prevents torn reads, not lost updates. Two sessions with
26
+ * the panel open still clobber each other, because the rename only publishes
27
+ * what the caller already computed.
28
+ *
29
+ * AVOID:
30
+ * - Treating this as a lock
31
+ * - Writing an empty string; the parser rejects blank values, so clearing a key
32
+ * means removing it
33
+ */
34
+ /**
35
+ * The global file holds machine-wide credentials-adjacent settings and lives in
36
+ * the user's home, so it stays private.
37
+ */
38
+ const PRIVATE_FILE_MODE = 384;
39
+ const PRIVATE_DIRECTORY_MODE = 448;
40
+ /**
41
+ * The repository file is committed to git and read by everyone who checks the
42
+ * project out, so it takes the modes `init.ts` already seeds it with. Writing it
43
+ * private would produce a 0600 file in a working tree, which is both wrong and
44
+ * invisible until someone else clones.
45
+ */
46
+ const REPOSITORY_FILE_MODE = 420;
47
+ const REPOSITORY_DIRECTORY_MODE = 493;
48
+ /** The permissions a scope's file and its parent directory are created with. */
49
+ function modesForScope(scope) {
50
+ return scope === "repository" ? {
51
+ file: REPOSITORY_FILE_MODE,
52
+ directory: REPOSITORY_DIRECTORY_MODE
53
+ } : {
54
+ file: PRIVATE_FILE_MODE,
55
+ directory: PRIVATE_DIRECTORY_MODE
56
+ };
57
+ }
58
+ async function readIfPresent(filePath) {
59
+ try {
60
+ return await node_fs_promises.default.readFile(filePath, "utf8");
61
+ } catch (error) {
62
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return "";
63
+ throw error;
64
+ }
65
+ }
66
+ function isEmptyCollection(value) {
67
+ if (value === null || typeof value !== "object") return false;
68
+ const items = value.items;
69
+ return Array.isArray(items) && items.length === 0;
70
+ }
71
+ /** Drops ancestors of a removed key that are now empty, deepest first. */
72
+ function pruneEmptyAncestors(document, keyPath) {
73
+ for (let depth = keyPath.length - 1; depth > 0; depth -= 1) {
74
+ const ancestor = keyPath.slice(0, depth);
75
+ if (!document.hasIn(ancestor)) continue;
76
+ if (!isEmptyCollection(document.getIn(ancestor, true))) return;
77
+ document.deleteIn(ancestor);
78
+ }
79
+ }
80
+ /**
81
+ * Applies every edit as one document mutation, or throws having written nothing.
82
+ *
83
+ * Validation runs against the serialized result rather than each edit, so a pair
84
+ * of edits only valid together is accepted, and a single edit that breaks an
85
+ * invariant elsewhere is still caught.
86
+ */
87
+ async function writeDoomConfigValues(filePath, edits, options = {}) {
88
+ if (edits.length === 0) return;
89
+ const content = await readIfPresent(filePath);
90
+ const document = (0, yaml.parseDocument)(content, { keepSourceTokens: true });
91
+ if (document.errors.length > 0) throw new Error(`Could not parse Doom config at ${filePath}`);
92
+ for (const edit of edits) {
93
+ const keyPath = [...edit.keyPath];
94
+ if (edit.value !== void 0) {
95
+ document.setIn(keyPath, edit.value);
96
+ continue;
97
+ }
98
+ if (!document.hasIn(keyPath)) continue;
99
+ document.deleteIn(keyPath);
100
+ if (options.prune !== false) pruneEmptyAncestors(document, keyPath);
101
+ }
102
+ const output = document.toString();
103
+ require_configPolicy.parseDoomConfig(output, filePath);
104
+ const modes = modesForScope(options.scope ?? "global");
105
+ await node_fs_promises.default.mkdir(node_path.default.dirname(filePath), {
106
+ mode: modes.directory,
107
+ recursive: true
108
+ });
109
+ const temporaryPath = `${filePath}.${process.pid}.${(0, node_crypto.randomUUID)()}.tmp`;
110
+ try {
111
+ await node_fs_promises.default.writeFile(temporaryPath, output, {
112
+ encoding: "utf8",
113
+ mode: modes.file
114
+ });
115
+ await node_fs_promises.default.rename(temporaryPath, filePath);
116
+ } catch (error) {
117
+ await node_fs_promises.default.rm(temporaryPath, { force: true });
118
+ throw error;
119
+ }
120
+ }
121
+ async function setDoomConfigValue(filePath, keyPath, value, options = {}) {
122
+ await writeDoomConfigValues(filePath, [{
123
+ keyPath,
124
+ value
125
+ }], options);
126
+ }
127
+ async function unsetDoomConfigValue(filePath, keyPath, options = {}) {
128
+ await writeDoomConfigValues(filePath, [{ keyPath }], options);
129
+ }
130
+ //#endregion
131
+ Object.defineProperty(exports, "setDoomConfigValue", {
132
+ enumerable: true,
133
+ get: function() {
134
+ return setDoomConfigValue;
135
+ }
136
+ });
137
+ Object.defineProperty(exports, "unsetDoomConfigValue", {
138
+ enumerable: true,
139
+ get: function() {
140
+ return unsetDoomConfigValue;
141
+ }
142
+ });
143
+ Object.defineProperty(exports, "writeDoomConfigValues", {
144
+ enumerable: true,
145
+ get: function() {
146
+ return writeDoomConfigValues;
147
+ }
148
+ });
149
+
150
+ //# sourceMappingURL=configWriter-BTuinLjs.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configWriter-BTuinLjs.cjs","names":["fs","parseDocument","path","randomUUID"],"sources":["../src/services/configWriter/index.ts"],"sourcesContent":["/**\n * Writes into the global Doom config file, one key path at a time.\n *\n * Extracted from doom-file-edit, which owned the only writer in the repo and had\n * it hard-coded to `editor.command`. Several extensions now edit their own\n * settings from the config panel, and every one of them needs the same four\n * properties: keep the user's comments, never publish a file the parser would\n * reject, never leave a half-written file behind, and apply a set of related\n * edits together.\n *\n * That last one is not a convenience. `voice.adapters.<engine>.model` accepts a\n * path or an id but not both, so switching between them is a set and an unset\n * that must land in the same write, or the file is briefly invalid.\n *\n * Atomicity is the same rename trick as `atomicJson.ts` next door, and carries\n * the same caveat: it prevents torn reads, not lost updates. Two sessions with\n * the panel open still clobber each other, because the rename only publishes\n * what the caller already computed.\n *\n * AVOID:\n * - Treating this as a lock\n * - Writing an empty string; the parser rejects blank values, so clearing a key\n * means removing it\n */\n\nimport { randomUUID } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { parseDocument } from 'yaml';\n\nimport { parseDoomConfig } from '../configPolicy';\n\n/**\n * The global file holds machine-wide credentials-adjacent settings and lives in\n * the user's home, so it stays private.\n */\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\n\n/**\n * The repository file is committed to git and read by everyone who checks the\n * project out, so it takes the modes `init.ts` already seeds it with. Writing it\n * private would produce a 0600 file in a working tree, which is both wrong and\n * invisible until someone else clones.\n */\nconst REPOSITORY_FILE_MODE = 0o644;\nconst REPOSITORY_DIRECTORY_MODE = 0o755;\n\n/** Which file a write is aimed at; the modes follow from it. */\nexport type DoomConfigScope = 'global' | 'repository';\n\n/** The permissions a scope's file and its parent directory are created with. */\nfunction modesForScope(scope: DoomConfigScope): { file: number; directory: number } {\n return scope === 'repository'\n ? { file: REPOSITORY_FILE_MODE, directory: REPOSITORY_DIRECTORY_MODE }\n : { file: PRIVATE_FILE_MODE, directory: PRIVATE_DIRECTORY_MODE };\n}\n\nexport interface DoomConfigEdit {\n /** Key path into the document, e.g. `['voice', 'adapters', 'mlx-whisper', 'model', 'id']`. */\n readonly keyPath: readonly string[];\n /**\n * The value to write. `undefined` removes the key.\n *\n * Not only strings: the config holds numbers (`utteranceIdleMs`) and lists\n * (`startPhrases`), and a panel that edits them as text has to hand back the\n * parsed value or the next load rejects what it wrote.\n */\n readonly value?: string | number | boolean | readonly string[];\n}\n\nexport interface WriteDoomConfigOptions {\n /**\n * Remove ancestors left holding nothing. Defaults to true, and is not\n * cosmetic: an adapter left as `{}` fails validation, because the parser\n * requires a model once the adapter exists at all.\n */\n readonly prune?: boolean;\n /**\n * Which file this is, so the write lands with the right permissions.\n * Defaults to `global`, which is what every caller wrote before the\n * repository file was writable at all.\n */\n readonly scope?: DoomConfigScope;\n}\n\nasync function readIfPresent(filePath: string): Promise<string> {\n try {\n return await fs.readFile(filePath, 'utf8');\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return '';\n throw error;\n }\n}\n\nfunction isEmptyCollection(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const items = (value as { items?: unknown }).items;\n return Array.isArray(items) && items.length === 0;\n}\n\n/** Drops ancestors of a removed key that are now empty, deepest first. */\nfunction pruneEmptyAncestors(document: ReturnType<typeof parseDocument>, keyPath: readonly string[]): void {\n for (let depth = keyPath.length - 1; depth > 0; depth -= 1) {\n const ancestor = keyPath.slice(0, depth);\n if (!document.hasIn(ancestor)) continue;\n if (!isEmptyCollection(document.getIn(ancestor, true))) return;\n document.deleteIn(ancestor);\n }\n}\n\n/**\n * Applies every edit as one document mutation, or throws having written nothing.\n *\n * Validation runs against the serialized result rather than each edit, so a pair\n * of edits only valid together is accepted, and a single edit that breaks an\n * invariant elsewhere is still caught.\n */\nexport async function writeDoomConfigValues(\n filePath: string,\n edits: readonly DoomConfigEdit[],\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n if (edits.length === 0) return;\n const content = await readIfPresent(filePath);\n // Not `content || '{}\\n'`: seeding an absent file with a flow map makes yaml\n // keep that style, so the first write produces `{ editor: { command: ... } }`\n // and every later one grows that one line. An empty source starts a block map.\n const document = parseDocument(content, { keepSourceTokens: true });\n if (document.errors.length > 0) throw new Error(`Could not parse Doom config at ${filePath}`);\n\n for (const edit of edits) {\n const keyPath = [...edit.keyPath];\n if (edit.value !== undefined) {\n document.setIn(keyPath, edit.value);\n continue;\n }\n // deleteIn throws when an ancestor is missing rather than treating the key as\n // already absent, and clearing an unset key is worth supporting.\n if (!document.hasIn(keyPath)) continue;\n document.deleteIn(keyPath);\n if (options.prune !== false) pruneEmptyAncestors(document, keyPath);\n }\n\n const output = document.toString();\n parseDoomConfig(output, filePath);\n\n const modes = modesForScope(options.scope ?? 'global');\n await fs.mkdir(path.dirname(filePath), { mode: modes.directory, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await fs.writeFile(temporaryPath, output, { encoding: 'utf8', mode: modes.file });\n await fs.rename(temporaryPath, filePath);\n } catch (error) {\n await fs.rm(temporaryPath, { force: true });\n throw error;\n }\n}\n\nexport async function setDoomConfigValue(\n filePath: string,\n keyPath: readonly string[],\n value: string,\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath, value }], options);\n}\n\nexport async function unsetDoomConfigValue(\n filePath: string,\n keyPath: readonly string[],\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath }], options);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAM,oBAAoB;AAC1B,MAAM,yBAAyB;;;;;;;AAQ/B,MAAM,uBAAuB;AAC7B,MAAM,4BAA4B;;AAMlC,SAAS,cAAc,OAA6D;CAClF,OAAO,UAAU,eACb;EAAE,MAAM;EAAsB,WAAW;CAA0B,IACnE;EAAE,MAAM;EAAmB,WAAW;CAAuB;AACnE;AA8BA,eAAe,cAAc,UAAmC;CAC9D,IAAI;EACF,OAAO,MAAMA,iBAAAA,QAAG,SAAS,UAAU,MAAM;CAC3C,SAAS,OAAO;EACd,IAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAAU,OAAO;EACjF,MAAM;CACR;AACF;AAEA,SAAS,kBAAkB,OAAyB;CAClD,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO;CACxD,MAAM,QAAS,MAA8B;CAC7C,OAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW;AAClD;;AAGA,SAAS,oBAAoB,UAA4C,SAAkC;CACzG,KAAK,IAAI,QAAQ,QAAQ,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG;EAC1D,MAAM,WAAW,QAAQ,MAAM,GAAG,KAAK;EACvC,IAAI,CAAC,SAAS,MAAM,QAAQ,GAAG;EAC/B,IAAI,CAAC,kBAAkB,SAAS,MAAM,UAAU,IAAI,CAAC,GAAG;EACxD,SAAS,SAAS,QAAQ;CAC5B;AACF;;;;;;;;AASA,eAAsB,sBACpB,UACA,OACA,UAAkC,CAAC,GACpB;CACf,IAAI,MAAM,WAAW,GAAG;CACxB,MAAM,UAAU,MAAM,cAAc,QAAQ;CAI5C,MAAM,YAAA,GAAWC,KAAAA,cAAAA,CAAc,SAAS,EAAE,kBAAkB,KAAK,CAAC;CAClE,IAAI,SAAS,OAAO,SAAS,GAAG,MAAM,IAAI,MAAM,kCAAkC,UAAU;CAE5F,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,CAAC,GAAG,KAAK,OAAO;EAChC,IAAI,KAAK,UAAU,KAAA,GAAW;GAC5B,SAAS,MAAM,SAAS,KAAK,KAAK;GAClC;EACF;EAGA,IAAI,CAAC,SAAS,MAAM,OAAO,GAAG;EAC9B,SAAS,SAAS,OAAO;EACzB,IAAI,QAAQ,UAAU,OAAO,oBAAoB,UAAU,OAAO;CACpE;CAEA,MAAM,SAAS,SAAS,SAAS;CACjC,qBAAA,gBAAgB,QAAQ,QAAQ;CAEhC,MAAM,QAAQ,cAAc,QAAQ,SAAS,QAAQ;CACrD,MAAMD,iBAAAA,QAAG,MAAME,UAAAA,QAAK,QAAQ,QAAQ,GAAG;EAAE,MAAM,MAAM;EAAW,WAAW;CAAK,CAAC;CACjF,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,IAAI,IAAA,GAAGC,YAAAA,WAAAA,CAAW,EAAE;CACjE,IAAI;EACF,MAAMH,iBAAAA,QAAG,UAAU,eAAe,QAAQ;GAAE,UAAU;GAAQ,MAAM,MAAM;EAAK,CAAC;EAChF,MAAMA,iBAAAA,QAAG,OAAO,eAAe,QAAQ;CACzC,SAAS,OAAO;EACd,MAAMA,iBAAAA,QAAG,GAAG,eAAe,EAAE,OAAO,KAAK,CAAC;EAC1C,MAAM;CACR;AACF;AAEA,eAAsB,mBACpB,UACA,SACA,OACA,UAAkC,CAAC,GACpB;CACf,MAAM,sBAAsB,UAAU,CAAC;EAAE;EAAS;CAAM,CAAC,GAAG,OAAO;AACrE;AAEA,eAAsB,qBACpB,UACA,SACA,UAAkC,CAAC,GACpB;CACf,MAAM,sBAAsB,UAAU,CAAC,EAAE,QAAQ,CAAC,GAAG,OAAO;AAC9D"}
@@ -0,0 +1,130 @@
1
+ import { u as parseDoomConfig } from "./configPolicy--TNX7feH.mjs";
2
+ import { randomUUID } from "node:crypto";
3
+ import path from "node:path";
4
+ import { parseDocument } from "yaml";
5
+ import fs from "node:fs/promises";
6
+ //#region src/services/configWriter/index.ts
7
+ /**
8
+ * Writes into the global Doom config file, one key path at a time.
9
+ *
10
+ * Extracted from doom-file-edit, which owned the only writer in the repo and had
11
+ * it hard-coded to `editor.command`. Several extensions now edit their own
12
+ * settings from the config panel, and every one of them needs the same four
13
+ * properties: keep the user's comments, never publish a file the parser would
14
+ * reject, never leave a half-written file behind, and apply a set of related
15
+ * edits together.
16
+ *
17
+ * That last one is not a convenience. `voice.adapters.<engine>.model` accepts a
18
+ * path or an id but not both, so switching between them is a set and an unset
19
+ * that must land in the same write, or the file is briefly invalid.
20
+ *
21
+ * Atomicity is the same rename trick as `atomicJson.ts` next door, and carries
22
+ * the same caveat: it prevents torn reads, not lost updates. Two sessions with
23
+ * the panel open still clobber each other, because the rename only publishes
24
+ * what the caller already computed.
25
+ *
26
+ * AVOID:
27
+ * - Treating this as a lock
28
+ * - Writing an empty string; the parser rejects blank values, so clearing a key
29
+ * means removing it
30
+ */
31
+ /**
32
+ * The global file holds machine-wide credentials-adjacent settings and lives in
33
+ * the user's home, so it stays private.
34
+ */
35
+ const PRIVATE_FILE_MODE = 384;
36
+ const PRIVATE_DIRECTORY_MODE = 448;
37
+ /**
38
+ * The repository file is committed to git and read by everyone who checks the
39
+ * project out, so it takes the modes `init.ts` already seeds it with. Writing it
40
+ * private would produce a 0600 file in a working tree, which is both wrong and
41
+ * invisible until someone else clones.
42
+ */
43
+ const REPOSITORY_FILE_MODE = 420;
44
+ const REPOSITORY_DIRECTORY_MODE = 493;
45
+ /** The permissions a scope's file and its parent directory are created with. */
46
+ function modesForScope(scope) {
47
+ return scope === "repository" ? {
48
+ file: REPOSITORY_FILE_MODE,
49
+ directory: REPOSITORY_DIRECTORY_MODE
50
+ } : {
51
+ file: PRIVATE_FILE_MODE,
52
+ directory: PRIVATE_DIRECTORY_MODE
53
+ };
54
+ }
55
+ async function readIfPresent(filePath) {
56
+ try {
57
+ return await fs.readFile(filePath, "utf8");
58
+ } catch (error) {
59
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return "";
60
+ throw error;
61
+ }
62
+ }
63
+ function isEmptyCollection(value) {
64
+ if (value === null || typeof value !== "object") return false;
65
+ const items = value.items;
66
+ return Array.isArray(items) && items.length === 0;
67
+ }
68
+ /** Drops ancestors of a removed key that are now empty, deepest first. */
69
+ function pruneEmptyAncestors(document, keyPath) {
70
+ for (let depth = keyPath.length - 1; depth > 0; depth -= 1) {
71
+ const ancestor = keyPath.slice(0, depth);
72
+ if (!document.hasIn(ancestor)) continue;
73
+ if (!isEmptyCollection(document.getIn(ancestor, true))) return;
74
+ document.deleteIn(ancestor);
75
+ }
76
+ }
77
+ /**
78
+ * Applies every edit as one document mutation, or throws having written nothing.
79
+ *
80
+ * Validation runs against the serialized result rather than each edit, so a pair
81
+ * of edits only valid together is accepted, and a single edit that breaks an
82
+ * invariant elsewhere is still caught.
83
+ */
84
+ async function writeDoomConfigValues(filePath, edits, options = {}) {
85
+ if (edits.length === 0) return;
86
+ const content = await readIfPresent(filePath);
87
+ const document = parseDocument(content, { keepSourceTokens: true });
88
+ if (document.errors.length > 0) throw new Error(`Could not parse Doom config at ${filePath}`);
89
+ for (const edit of edits) {
90
+ const keyPath = [...edit.keyPath];
91
+ if (edit.value !== void 0) {
92
+ document.setIn(keyPath, edit.value);
93
+ continue;
94
+ }
95
+ if (!document.hasIn(keyPath)) continue;
96
+ document.deleteIn(keyPath);
97
+ if (options.prune !== false) pruneEmptyAncestors(document, keyPath);
98
+ }
99
+ const output = document.toString();
100
+ parseDoomConfig(output, filePath);
101
+ const modes = modesForScope(options.scope ?? "global");
102
+ await fs.mkdir(path.dirname(filePath), {
103
+ mode: modes.directory,
104
+ recursive: true
105
+ });
106
+ const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
107
+ try {
108
+ await fs.writeFile(temporaryPath, output, {
109
+ encoding: "utf8",
110
+ mode: modes.file
111
+ });
112
+ await fs.rename(temporaryPath, filePath);
113
+ } catch (error) {
114
+ await fs.rm(temporaryPath, { force: true });
115
+ throw error;
116
+ }
117
+ }
118
+ async function setDoomConfigValue(filePath, keyPath, value, options = {}) {
119
+ await writeDoomConfigValues(filePath, [{
120
+ keyPath,
121
+ value
122
+ }], options);
123
+ }
124
+ async function unsetDoomConfigValue(filePath, keyPath, options = {}) {
125
+ await writeDoomConfigValues(filePath, [{ keyPath }], options);
126
+ }
127
+ //#endregion
128
+ export { unsetDoomConfigValue as n, writeDoomConfigValues as r, setDoomConfigValue as t };
129
+
130
+ //# sourceMappingURL=configWriter-BgkGaLrf.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configWriter-BgkGaLrf.mjs","names":[],"sources":["../src/services/configWriter/index.ts"],"sourcesContent":["/**\n * Writes into the global Doom config file, one key path at a time.\n *\n * Extracted from doom-file-edit, which owned the only writer in the repo and had\n * it hard-coded to `editor.command`. Several extensions now edit their own\n * settings from the config panel, and every one of them needs the same four\n * properties: keep the user's comments, never publish a file the parser would\n * reject, never leave a half-written file behind, and apply a set of related\n * edits together.\n *\n * That last one is not a convenience. `voice.adapters.<engine>.model` accepts a\n * path or an id but not both, so switching between them is a set and an unset\n * that must land in the same write, or the file is briefly invalid.\n *\n * Atomicity is the same rename trick as `atomicJson.ts` next door, and carries\n * the same caveat: it prevents torn reads, not lost updates. Two sessions with\n * the panel open still clobber each other, because the rename only publishes\n * what the caller already computed.\n *\n * AVOID:\n * - Treating this as a lock\n * - Writing an empty string; the parser rejects blank values, so clearing a key\n * means removing it\n */\n\nimport { randomUUID } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { parseDocument } from 'yaml';\n\nimport { parseDoomConfig } from '../configPolicy';\n\n/**\n * The global file holds machine-wide credentials-adjacent settings and lives in\n * the user's home, so it stays private.\n */\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\n\n/**\n * The repository file is committed to git and read by everyone who checks the\n * project out, so it takes the modes `init.ts` already seeds it with. Writing it\n * private would produce a 0600 file in a working tree, which is both wrong and\n * invisible until someone else clones.\n */\nconst REPOSITORY_FILE_MODE = 0o644;\nconst REPOSITORY_DIRECTORY_MODE = 0o755;\n\n/** Which file a write is aimed at; the modes follow from it. */\nexport type DoomConfigScope = 'global' | 'repository';\n\n/** The permissions a scope's file and its parent directory are created with. */\nfunction modesForScope(scope: DoomConfigScope): { file: number; directory: number } {\n return scope === 'repository'\n ? { file: REPOSITORY_FILE_MODE, directory: REPOSITORY_DIRECTORY_MODE }\n : { file: PRIVATE_FILE_MODE, directory: PRIVATE_DIRECTORY_MODE };\n}\n\nexport interface DoomConfigEdit {\n /** Key path into the document, e.g. `['voice', 'adapters', 'mlx-whisper', 'model', 'id']`. */\n readonly keyPath: readonly string[];\n /**\n * The value to write. `undefined` removes the key.\n *\n * Not only strings: the config holds numbers (`utteranceIdleMs`) and lists\n * (`startPhrases`), and a panel that edits them as text has to hand back the\n * parsed value or the next load rejects what it wrote.\n */\n readonly value?: string | number | boolean | readonly string[];\n}\n\nexport interface WriteDoomConfigOptions {\n /**\n * Remove ancestors left holding nothing. Defaults to true, and is not\n * cosmetic: an adapter left as `{}` fails validation, because the parser\n * requires a model once the adapter exists at all.\n */\n readonly prune?: boolean;\n /**\n * Which file this is, so the write lands with the right permissions.\n * Defaults to `global`, which is what every caller wrote before the\n * repository file was writable at all.\n */\n readonly scope?: DoomConfigScope;\n}\n\nasync function readIfPresent(filePath: string): Promise<string> {\n try {\n return await fs.readFile(filePath, 'utf8');\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return '';\n throw error;\n }\n}\n\nfunction isEmptyCollection(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const items = (value as { items?: unknown }).items;\n return Array.isArray(items) && items.length === 0;\n}\n\n/** Drops ancestors of a removed key that are now empty, deepest first. */\nfunction pruneEmptyAncestors(document: ReturnType<typeof parseDocument>, keyPath: readonly string[]): void {\n for (let depth = keyPath.length - 1; depth > 0; depth -= 1) {\n const ancestor = keyPath.slice(0, depth);\n if (!document.hasIn(ancestor)) continue;\n if (!isEmptyCollection(document.getIn(ancestor, true))) return;\n document.deleteIn(ancestor);\n }\n}\n\n/**\n * Applies every edit as one document mutation, or throws having written nothing.\n *\n * Validation runs against the serialized result rather than each edit, so a pair\n * of edits only valid together is accepted, and a single edit that breaks an\n * invariant elsewhere is still caught.\n */\nexport async function writeDoomConfigValues(\n filePath: string,\n edits: readonly DoomConfigEdit[],\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n if (edits.length === 0) return;\n const content = await readIfPresent(filePath);\n // Not `content || '{}\\n'`: seeding an absent file with a flow map makes yaml\n // keep that style, so the first write produces `{ editor: { command: ... } }`\n // and every later one grows that one line. An empty source starts a block map.\n const document = parseDocument(content, { keepSourceTokens: true });\n if (document.errors.length > 0) throw new Error(`Could not parse Doom config at ${filePath}`);\n\n for (const edit of edits) {\n const keyPath = [...edit.keyPath];\n if (edit.value !== undefined) {\n document.setIn(keyPath, edit.value);\n continue;\n }\n // deleteIn throws when an ancestor is missing rather than treating the key as\n // already absent, and clearing an unset key is worth supporting.\n if (!document.hasIn(keyPath)) continue;\n document.deleteIn(keyPath);\n if (options.prune !== false) pruneEmptyAncestors(document, keyPath);\n }\n\n const output = document.toString();\n parseDoomConfig(output, filePath);\n\n const modes = modesForScope(options.scope ?? 'global');\n await fs.mkdir(path.dirname(filePath), { mode: modes.directory, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await fs.writeFile(temporaryPath, output, { encoding: 'utf8', mode: modes.file });\n await fs.rename(temporaryPath, filePath);\n } catch (error) {\n await fs.rm(temporaryPath, { force: true });\n throw error;\n }\n}\n\nexport async function setDoomConfigValue(\n filePath: string,\n keyPath: readonly string[],\n value: string,\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath, value }], options);\n}\n\nexport async function unsetDoomConfigValue(\n filePath: string,\n keyPath: readonly string[],\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath }], options);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAM,oBAAoB;AAC1B,MAAM,yBAAyB;;;;;;;AAQ/B,MAAM,uBAAuB;AAC7B,MAAM,4BAA4B;;AAMlC,SAAS,cAAc,OAA6D;CAClF,OAAO,UAAU,eACb;EAAE,MAAM;EAAsB,WAAW;CAA0B,IACnE;EAAE,MAAM;EAAmB,WAAW;CAAuB;AACnE;AA8BA,eAAe,cAAc,UAAmC;CAC9D,IAAI;EACF,OAAO,MAAM,GAAG,SAAS,UAAU,MAAM;CAC3C,SAAS,OAAO;EACd,IAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAAU,OAAO;EACjF,MAAM;CACR;AACF;AAEA,SAAS,kBAAkB,OAAyB;CAClD,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO;CACxD,MAAM,QAAS,MAA8B;CAC7C,OAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW;AAClD;;AAGA,SAAS,oBAAoB,UAA4C,SAAkC;CACzG,KAAK,IAAI,QAAQ,QAAQ,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG;EAC1D,MAAM,WAAW,QAAQ,MAAM,GAAG,KAAK;EACvC,IAAI,CAAC,SAAS,MAAM,QAAQ,GAAG;EAC/B,IAAI,CAAC,kBAAkB,SAAS,MAAM,UAAU,IAAI,CAAC,GAAG;EACxD,SAAS,SAAS,QAAQ;CAC5B;AACF;;;;;;;;AASA,eAAsB,sBACpB,UACA,OACA,UAAkC,CAAC,GACpB;CACf,IAAI,MAAM,WAAW,GAAG;CACxB,MAAM,UAAU,MAAM,cAAc,QAAQ;CAI5C,MAAM,WAAW,cAAc,SAAS,EAAE,kBAAkB,KAAK,CAAC;CAClE,IAAI,SAAS,OAAO,SAAS,GAAG,MAAM,IAAI,MAAM,kCAAkC,UAAU;CAE5F,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,CAAC,GAAG,KAAK,OAAO;EAChC,IAAI,KAAK,UAAU,KAAA,GAAW;GAC5B,SAAS,MAAM,SAAS,KAAK,KAAK;GAClC;EACF;EAGA,IAAI,CAAC,SAAS,MAAM,OAAO,GAAG;EAC9B,SAAS,SAAS,OAAO;EACzB,IAAI,QAAQ,UAAU,OAAO,oBAAoB,UAAU,OAAO;CACpE;CAEA,MAAM,SAAS,SAAS,SAAS;CACjC,gBAAgB,QAAQ,QAAQ;CAEhC,MAAM,QAAQ,cAAc,QAAQ,SAAS,QAAQ;CACrD,MAAM,GAAG,MAAM,KAAK,QAAQ,QAAQ,GAAG;EAAE,MAAM,MAAM;EAAW,WAAW;CAAK,CAAC;CACjF,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,IAAI,GAAG,WAAW,EAAE;CACjE,IAAI;EACF,MAAM,GAAG,UAAU,eAAe,QAAQ;GAAE,UAAU;GAAQ,MAAM,MAAM;EAAK,CAAC;EAChF,MAAM,GAAG,OAAO,eAAe,QAAQ;CACzC,SAAS,OAAO;EACd,MAAM,GAAG,GAAG,eAAe,EAAE,OAAO,KAAK,CAAC;EAC1C,MAAM;CACR;AACF;AAEA,eAAsB,mBACpB,UACA,SACA,OACA,UAAkC,CAAC,GACpB;CACf,MAAM,sBAAsB,UAAU,CAAC;EAAE;EAAS;CAAM,CAAC,GAAG,OAAO;AACrE;AAEA,eAAsB,qBACpB,UACA,SACA,UAAkC,CAAC,GACpB;CACf,MAAM,sBAAsB,UAAU,CAAC,EAAE,QAAQ,CAAC,GAAG,OAAO;AAC9D"}
@@ -0,0 +1,5 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_configWriter = require("./configWriter-BTuinLjs.cjs");
3
+ exports.setDoomConfigValue = require_configWriter.setDoomConfigValue;
4
+ exports.unsetDoomConfigValue = require_configWriter.unsetDoomConfigValue;
5
+ exports.writeDoomConfigValues = require_configWriter.writeDoomConfigValues;
@@ -0,0 +1,2 @@
1
+ import { a as unsetDoomConfigValue, i as setDoomConfigValue, n as DoomConfigScope, o as writeDoomConfigValues, r as WriteDoomConfigOptions, t as DoomConfigEdit } from "./index-Cvdt-Y-i.mjs";
2
+ export { type DoomConfigEdit, type DoomConfigScope, type WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues };
@@ -0,0 +1,2 @@
1
+ import { n as unsetDoomConfigValue, r as writeDoomConfigValues, t as setDoomConfigValue } from "./configWriter-BgkGaLrf.mjs";
2
+ export { setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues };