@agimon-ai/doompi-config 0.0.1-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 (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs +1 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs +1 -0
  5. package/dist/_virtual/_rolldown/runtime.cjs +1 -0
  6. package/dist/atomicJson.cjs +2 -0
  7. package/dist/atomicJson.cjs.map +1 -0
  8. package/dist/atomicJson.d.cts +6 -0
  9. package/dist/atomicJson.d.cts.map +1 -0
  10. package/dist/atomicJson.d.mts +6 -0
  11. package/dist/atomicJson.d.mts.map +1 -0
  12. package/dist/atomicJson.mjs +2 -0
  13. package/dist/atomicJson.mjs.map +1 -0
  14. package/dist/config/piConfig.cjs +2 -0
  15. package/dist/config/piConfig.cjs.map +1 -0
  16. package/dist/config/piConfig.d.cts +7 -0
  17. package/dist/config/piConfig.d.cts.map +1 -0
  18. package/dist/config/piConfig.d.mts +7 -0
  19. package/dist/config/piConfig.d.mts.map +1 -0
  20. package/dist/config/piConfig.mjs +2 -0
  21. package/dist/config/piConfig.mjs.map +1 -0
  22. package/dist/config/schema.cjs +0 -0
  23. package/dist/config/schema.d.cts +33 -0
  24. package/dist/config/schema.d.cts.map +1 -0
  25. package/dist/config/schema.d.mts +33 -0
  26. package/dist/config/schema.d.mts.map +1 -0
  27. package/dist/config/schema.mjs +1 -0
  28. package/dist/config.cjs +2 -0
  29. package/dist/config.cjs.map +1 -0
  30. package/dist/config.d.cts +24 -0
  31. package/dist/config.d.cts.map +1 -0
  32. package/dist/config.d.mts +24 -0
  33. package/dist/config.d.mts.map +1 -0
  34. package/dist/config.mjs +2 -0
  35. package/dist/config.mjs.map +1 -0
  36. package/dist/configWriter.cjs +2 -0
  37. package/dist/configWriter.cjs.map +1 -0
  38. package/dist/configWriter.d.cts +52 -0
  39. package/dist/configWriter.d.cts.map +1 -0
  40. package/dist/configWriter.d.mts +52 -0
  41. package/dist/configWriter.d.mts.map +1 -0
  42. package/dist/configWriter.mjs +2 -0
  43. package/dist/configWriter.mjs.map +1 -0
  44. package/dist/container.cjs +2 -0
  45. package/dist/container.cjs.map +1 -0
  46. package/dist/container.d.cts +12 -0
  47. package/dist/container.d.cts.map +1 -0
  48. package/dist/container.d.mts +12 -0
  49. package/dist/container.d.mts.map +1 -0
  50. package/dist/container.mjs +2 -0
  51. package/dist/container.mjs.map +1 -0
  52. package/dist/domains.cjs +2 -0
  53. package/dist/domains.cjs.map +1 -0
  54. package/dist/domains.d.cts +75 -0
  55. package/dist/domains.d.cts.map +1 -0
  56. package/dist/domains.d.mts +75 -0
  57. package/dist/domains.d.mts.map +1 -0
  58. package/dist/domains.mjs +2 -0
  59. package/dist/domains.mjs.map +1 -0
  60. package/dist/extensions/doom.cjs +2 -0
  61. package/dist/extensions/doom.cjs.map +1 -0
  62. package/dist/extensions/doom.d.cts +8 -0
  63. package/dist/extensions/doom.d.cts.map +1 -0
  64. package/dist/extensions/doom.d.mts +8 -0
  65. package/dist/extensions/doom.d.mts.map +1 -0
  66. package/dist/extensions/doom.mjs +2 -0
  67. package/dist/extensions/doom.mjs.map +1 -0
  68. package/dist/extensions/pi.cjs +2 -0
  69. package/dist/extensions/pi.cjs.map +1 -0
  70. package/dist/extensions/pi.d.cts +8 -0
  71. package/dist/extensions/pi.d.cts.map +1 -0
  72. package/dist/extensions/pi.d.mts +8 -0
  73. package/dist/extensions/pi.d.mts.map +1 -0
  74. package/dist/extensions/pi.mjs +2 -0
  75. package/dist/extensions/pi.mjs.map +1 -0
  76. package/dist/harnessState.cjs +2 -0
  77. package/dist/harnessState.cjs.map +1 -0
  78. package/dist/harnessState.d.cts +50 -0
  79. package/dist/harnessState.d.cts.map +1 -0
  80. package/dist/harnessState.d.mts +50 -0
  81. package/dist/harnessState.d.mts.map +1 -0
  82. package/dist/harnessState.mjs +2 -0
  83. package/dist/harnessState.mjs.map +1 -0
  84. package/dist/harnessStore.cjs +2 -0
  85. package/dist/harnessStore.cjs.map +1 -0
  86. package/dist/harnessStore.d.cts +102 -0
  87. package/dist/harnessStore.d.cts.map +1 -0
  88. package/dist/harnessStore.d.mts +102 -0
  89. package/dist/harnessStore.d.mts.map +1 -0
  90. package/dist/harnessStore.mjs +2 -0
  91. package/dist/harnessStore.mjs.map +1 -0
  92. package/dist/index.cjs +1 -0
  93. package/dist/index.d.cts +16 -0
  94. package/dist/index.d.mts +16 -0
  95. package/dist/index.mjs +1 -0
  96. package/dist/init.cjs +73 -0
  97. package/dist/init.cjs.map +1 -0
  98. package/dist/init.d.cts +33 -0
  99. package/dist/init.d.cts.map +1 -0
  100. package/dist/init.d.mts +33 -0
  101. package/dist/init.d.mts.map +1 -0
  102. package/dist/init.mjs +73 -0
  103. package/dist/init.mjs.map +1 -0
  104. package/dist/layeredConfig.cjs +2 -0
  105. package/dist/layeredConfig.cjs.map +1 -0
  106. package/dist/layeredConfig.d.cts +49 -0
  107. package/dist/layeredConfig.d.cts.map +1 -0
  108. package/dist/layeredConfig.d.mts +49 -0
  109. package/dist/layeredConfig.d.mts.map +1 -0
  110. package/dist/layeredConfig.mjs +2 -0
  111. package/dist/layeredConfig.mjs.map +1 -0
  112. package/dist/majorModes.cjs +2 -0
  113. package/dist/majorModes.cjs.map +1 -0
  114. package/dist/majorModes.d.cts +73 -0
  115. package/dist/majorModes.d.cts.map +1 -0
  116. package/dist/majorModes.d.mts +73 -0
  117. package/dist/majorModes.d.mts.map +1 -0
  118. package/dist/majorModes.mjs +2 -0
  119. package/dist/majorModes.mjs.map +1 -0
  120. package/dist/pi.cjs +1 -0
  121. package/dist/pi.d.cts +2 -0
  122. package/dist/pi.d.mts +2 -0
  123. package/dist/pi.mjs +1 -0
  124. package/dist/piContext.cjs +2 -0
  125. package/dist/piContext.cjs.map +1 -0
  126. package/dist/piContext.d.cts +19 -0
  127. package/dist/piContext.d.cts.map +1 -0
  128. package/dist/piContext.d.mts +19 -0
  129. package/dist/piContext.d.mts.map +1 -0
  130. package/dist/piContext.mjs +2 -0
  131. package/dist/piContext.mjs.map +1 -0
  132. package/dist/profiles.cjs +4 -0
  133. package/dist/profiles.cjs.map +1 -0
  134. package/dist/profiles.d.cts +35 -0
  135. package/dist/profiles.d.cts.map +1 -0
  136. package/dist/profiles.d.mts +35 -0
  137. package/dist/profiles.d.mts.map +1 -0
  138. package/dist/profiles.mjs +4 -0
  139. package/dist/profiles.mjs.map +1 -0
  140. package/dist/types.cjs +2 -0
  141. package/dist/types.cjs.map +1 -0
  142. package/dist/types.d.cts +132 -0
  143. package/dist/types.d.cts.map +1 -0
  144. package/dist/types.d.mts +132 -0
  145. package/dist/types.d.mts.map +1 -0
  146. package/dist/types.mjs +2 -0
  147. package/dist/types.mjs.map +1 -0
  148. package/package.json +150 -0
  149. package/skills/doom-pi-config/SKILL.md +26 -0
  150. package/skills/doom-pi-config/agents/openai.yaml +4 -0
  151. package/skills/doom-pi-config/references/config-contract.md +43 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vuong Ngo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # @agimon-ai/doompi-config
2
+
3
+ Shared Doom configuration loading and dependency injection.
4
+
5
+ Loads and validates `.doom/`: major modes and their layers, domains, profiles, and the hook
6
+ registry. Owns the resolved harness state file that every other reader derives from.
7
+
8
+ Part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi), a coding agent that loads
9
+ only the skills and tools you name. This package is composed by Doompi and is not usually
10
+ installed on its own.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install @agimon-ai/doompi-config
16
+ ```
17
+
18
+ ## How it loads
19
+
20
+ Doompi loads this unconditionally. It is core, not a layer, so it does not appear in
21
+ `.doom/modes.yaml`: making it optional would only create broken configurations.
22
+
23
+ Extension entry: `@agimon-ai/doompi-config/pi`
24
+
25
+ ## Notes
26
+
27
+ This is the package that defines the config contract. Changing the shape of `.doom/*.yaml`
28
+ starts here.
29
+
30
+ ## Entry points
31
+
32
+ 18 subpaths are declared in the `exports` field of `package.json`.
33
+
34
+ ## License
35
+
36
+ MIT
@@ -0,0 +1 @@
1
+ function e(e,t,n,r){var i=arguments.length,a=i<3?t:r===null?r=Object.getOwnPropertyDescriptor(t,n):r,o;if(typeof Reflect==`object`&&typeof Reflect.decorate==`function`)a=Reflect.decorate(e,t,n,r);else for(var s=e.length-1;s>=0;s--)(o=e[s])&&(a=(i<3?o(a):i>3?o(t,n,a):o(t,n))||a);return i>3&&a&&Object.defineProperty(t,n,a),a}exports.default=e;
@@ -0,0 +1 @@
1
+ function e(e,t,n,r){var i=arguments.length,a=i<3?t:r===null?r=Object.getOwnPropertyDescriptor(t,n):r,o;if(typeof Reflect==`object`&&typeof Reflect.decorate==`function`)a=Reflect.decorate(e,t,n,r);else for(var s=e.length-1;s>=0;s--)(o=e[s])&&(a=(i<3?o(a):i>3?o(t,n,a):o(t,n))||a);return i>3&&a&&Object.defineProperty(t,n,a),a}export{e as default};
@@ -0,0 +1 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));exports.__toESM=s;
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("node:crypto");function i(e,i){t.default.mkdirSync(n.default.dirname(e),{mode:448,recursive:!0});let a=`${e}.${process.pid}.${(0,r.randomUUID)()}.tmp`;try{t.default.writeFileSync(a,`${JSON.stringify(i,null,2)}\n`,{mode:384}),t.default.renameSync(a,e)}catch(e){throw t.default.rmSync(a,{force:!0}),e}}exports.writePrivateAtomicJson=i;
2
+ //# sourceMappingURL=atomicJson.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomicJson.cjs","names":["path","randomUUID"],"sources":["../src/atomicJson.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * Private atomic JSON writes.\n *\n * Write to a uniquely named temporary file in the destination directory, then\n * rename onto the target. Rename is atomic within a filesystem, so a reader\n * sees either the previous file or the complete new one and never a partial\n * write. The temporary name carries the pid and a random suffix, so two\n * processes writing the same target cannot collide on it.\n *\n * This prevents torn reads. It does NOT prevent lost updates: two writers doing\n * read-modify-write still clobber each other, because the rename only publishes\n * what the caller already computed. Harness state avoids that by giving every\n * process a file it owns rather than by locking.\n *\n * AVOID:\n * - Treating this as a lock\n * - Leaving the temporary file behind when the write fails\n */\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst TEMPORARY_SUFFIX = '.tmp';\n\n/** Writes JSON readable by its owner only, replacing the target atomically. */\nexport function writePrivateAtomicJson(filePath: string, value: unknown): void {\n fs.mkdirSync(path.dirname(filePath), { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}${TEMPORARY_SUFFIX}`;\n try {\n fs.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\\n`, { mode: PRIVATE_FILE_MODE });\n fs.renameSync(temporaryPath, filePath);\n } catch (error) {\n fs.rmSync(temporaryPath, { force: true });\n throw error;\n }\n}\n"],"mappings":"0OA4BA,SAAgB,EAAuB,EAAkB,EAAsB,CAC7E,EAAA,QAAG,UAAUA,EAAAA,QAAK,QAAQ,CAAQ,EAAG,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EACtF,IAAM,EAAgB,GAAG,EAAS,GAAG,QAAQ,IAAI,IAAA,EAAGC,EAAAA,WAAAA,CAAW,QAC/D,GAAI,CACF,EAAA,QAAG,cAAc,EAAe,GAAG,KAAK,UAAU,EAAO,KAAM,CAAC,EAAE,IAAK,CAAE,KAAM,GAAkB,CAAC,EAClG,EAAA,QAAG,WAAW,EAAe,CAAQ,CACvC,OAAS,EAAO,CAEd,MADA,EAAA,QAAG,OAAO,EAAe,CAAE,MAAO,EAAK,CAAC,EAClC,CACR,CACF"}
@@ -0,0 +1,6 @@
1
+ //#region src/atomicJson.d.ts
2
+ /** Writes JSON readable by its owner only, replacing the target atomically. */
3
+ declare function writePrivateAtomicJson(filePath: string, value: unknown): void;
4
+ //#endregion
5
+ export { writePrivateAtomicJson };
6
+ //# sourceMappingURL=atomicJson.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomicJson.d.cts","names":[],"sources":["../src/atomicJson.ts"],"mappings":";;iBA4BgB,uBAAuB,kBAAkB"}
@@ -0,0 +1,6 @@
1
+ //#region src/atomicJson.d.ts
2
+ /** Writes JSON readable by its owner only, replacing the target atomically. */
3
+ declare function writePrivateAtomicJson(filePath: string, value: unknown): void;
4
+ //#endregion
5
+ export { writePrivateAtomicJson };
6
+ //# sourceMappingURL=atomicJson.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomicJson.d.mts","names":[],"sources":["../src/atomicJson.ts"],"mappings":";;iBA4BgB,uBAAuB,kBAAkB"}
@@ -0,0 +1,2 @@
1
+ import e from"node:fs";import t from"node:path";import{randomUUID as n}from"node:crypto";function r(r,i){e.mkdirSync(t.dirname(r),{mode:448,recursive:!0});let a=`${r}.${process.pid}.${n()}.tmp`;try{e.writeFileSync(a,`${JSON.stringify(i,null,2)}\n`,{mode:384}),e.renameSync(a,r)}catch(t){throw e.rmSync(a,{force:!0}),t}}export{r as writePrivateAtomicJson};
2
+ //# sourceMappingURL=atomicJson.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomicJson.mjs","names":[],"sources":["../src/atomicJson.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * Private atomic JSON writes.\n *\n * Write to a uniquely named temporary file in the destination directory, then\n * rename onto the target. Rename is atomic within a filesystem, so a reader\n * sees either the previous file or the complete new one and never a partial\n * write. The temporary name carries the pid and a random suffix, so two\n * processes writing the same target cannot collide on it.\n *\n * This prevents torn reads. It does NOT prevent lost updates: two writers doing\n * read-modify-write still clobber each other, because the rename only publishes\n * what the caller already computed. Harness state avoids that by giving every\n * process a file it owns rather than by locking.\n *\n * AVOID:\n * - Treating this as a lock\n * - Leaving the temporary file behind when the write fails\n */\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst TEMPORARY_SUFFIX = '.tmp';\n\n/** Writes JSON readable by its owner only, replacing the target atomically. */\nexport function writePrivateAtomicJson(filePath: string, value: unknown): void {\n fs.mkdirSync(path.dirname(filePath), { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}${TEMPORARY_SUFFIX}`;\n try {\n fs.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\\n`, { mode: PRIVATE_FILE_MODE });\n fs.renameSync(temporaryPath, filePath);\n } catch (error) {\n fs.rmSync(temporaryPath, { force: true });\n throw error;\n }\n}\n"],"mappings":"yFA4BA,SAAgB,EAAuB,EAAkB,EAAsB,CAC7E,EAAG,UAAU,EAAK,QAAQ,CAAQ,EAAG,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EACtF,IAAM,EAAgB,GAAG,EAAS,GAAG,QAAQ,IAAI,GAAG,EAAW,QAC/D,GAAI,CACF,EAAG,cAAc,EAAe,GAAG,KAAK,UAAU,EAAO,KAAM,CAAC,EAAE,IAAK,CAAE,KAAM,GAAkB,CAAC,EAClG,EAAG,WAAW,EAAe,CAAQ,CACvC,OAAS,EAAO,CAEd,MADA,EAAG,OAAO,EAAe,CAAE,MAAO,EAAK,CAAC,EAClC,CACR,CACF"}
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:os");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);let i=require("@earendil-works/pi-coding-agent");const a=`settings.json`;function o(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function s(e,t){let n={...e};for(let[e,r]of Object.entries(t)){let t=n[e];n[e]=o(t)&&o(r)?s(t,r):r}return n}function c(e){if(!t.default.existsSync(e))return{};let n;try{n=JSON.parse(t.default.readFileSync(e,`utf8`))}catch(t){throw Error(`Could not parse Pi settings at ${e}`,{cause:t})}if(!o(n))throw Error(`Pi settings at ${e} must be a JSON object`);return n}function l(e){return e.reduce((e,t)=>s(e,c(t)),{})}function u(e){return typeof e==`function`?e():e}function d(e,t=n.default.homedir(),o=t===n.default.homedir()?(0,i.getAgentDir)():r.default.join(t,i.CONFIG_DIR_NAME,`agent`)){return{canonicalUser:r.default.join(o,a),canonicalProject:r.default.join(e,i.CONFIG_DIR_NAME,a),legacyUser:[r.default.join(t,i.CONFIG_DIR_NAME,a)],legacyProject:[r.default.join(e,`pi`,a)]}}function f(e){let t=d(e.repoRoot,e.homeDirectory),n=[...t.legacyUser,...e.userConfigPaths??[],t.canonicalUser],r=[...t.legacyProject,...e.projectConfigPaths??[],t.canonicalProject],i=e.defaults??{},a=l(n),o=u(e.isProjectTrusted)?l(r):{};return[i,a,e.overlay??{},o,e.environment??{},e.cli??{},e.programmatic??{}].reduce(s,{})}exports.loadPiConfig=f,exports.piConfigPaths=d;
2
+ //# sourceMappingURL=piConfig.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"piConfig.cjs","names":["fs","os","getAgentDir","path","CONFIG_DIR_NAME"],"sources":["../../src/config/piConfig.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { CONFIG_DIR_NAME, getAgentDir } from '@earendil-works/pi-coding-agent';\nimport type { PiConfig, PiConfigLoadOptions, PiConfigPaths } from './schema.ts';\n\nconst SETTINGS_FILE_NAME = 'settings.json';\nconst LEGACY_CONFIG_DIR_NAME = 'pi';\n\nfunction isObject(value: unknown): value is PiConfig {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction mergeConfig(base: PiConfig, override: PiConfig): PiConfig {\n const result: PiConfig = { ...base };\n for (const [key, value] of Object.entries(override)) {\n const current = result[key];\n result[key] = isObject(current) && isObject(value) ? mergeConfig(current, value) : value;\n }\n return result;\n}\n\nfunction readConfig(filePath: string): PiConfig {\n if (!fs.existsSync(filePath)) return {};\n let parsed: unknown;\n try {\n parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n } catch (error) {\n throw new Error(`Could not parse Pi settings at ${filePath}`, { cause: error });\n }\n if (!isObject(parsed)) throw new Error(`Pi settings at ${filePath} must be a JSON object`);\n return parsed;\n}\n\nfunction readConfigPaths(paths: readonly string[]): PiConfig {\n return paths.reduce<PiConfig>((config, filePath) => mergeConfig(config, readConfig(filePath)), {});\n}\n\nfunction projectIsTrusted(value: PiConfigLoadOptions['isProjectTrusted']): boolean {\n return typeof value === 'function' ? value() : value;\n}\n\nexport function piConfigPaths(\n repoRoot: string,\n homeDirectory = os.homedir(),\n agentDirectory = homeDirectory === os.homedir() ? getAgentDir() : path.join(homeDirectory, CONFIG_DIR_NAME, 'agent'),\n): PiConfigPaths {\n return {\n canonicalUser: path.join(agentDirectory, SETTINGS_FILE_NAME),\n canonicalProject: path.join(repoRoot, CONFIG_DIR_NAME, SETTINGS_FILE_NAME),\n legacyUser: [path.join(homeDirectory, CONFIG_DIR_NAME, SETTINGS_FILE_NAME)],\n legacyProject: [path.join(repoRoot, LEGACY_CONFIG_DIR_NAME, SETTINGS_FILE_NAME)],\n };\n}\n\nexport function loadPiConfig(options: PiConfigLoadOptions): PiConfig {\n const paths = piConfigPaths(options.repoRoot, options.homeDirectory);\n const userPaths = [...paths.legacyUser, ...(options.userConfigPaths ?? []), paths.canonicalUser];\n const projectPaths = [...paths.legacyProject, ...(options.projectConfigPaths ?? []), paths.canonicalProject];\n const defaults = options.defaults ?? {};\n const user = readConfigPaths(userPaths);\n const trustedProject = projectIsTrusted(options.isProjectTrusted) ? readConfigPaths(projectPaths) : {};\n return [\n defaults,\n user,\n options.overlay ?? {},\n trustedProject,\n options.environment ?? {},\n options.cli ?? {},\n options.programmatic ?? {},\n ].reduce(mergeConfig, {});\n}\n"],"mappings":"ySAMA,MAAM,EAAqB,gBAG3B,SAAS,EAAS,EAAmC,CACnD,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,SAAS,EAAY,EAAgB,EAA8B,CACjE,IAAM,EAAmB,CAAE,GAAG,CAAK,EACnC,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAQ,EAAG,CACnD,IAAM,EAAU,EAAO,GACvB,EAAO,GAAO,EAAS,CAAO,GAAK,EAAS,CAAK,EAAI,EAAY,EAAS,CAAK,EAAI,CACrF,CACA,OAAO,CACT,CAEA,SAAS,EAAW,EAA4B,CAC9C,GAAI,CAACA,EAAAA,QAAG,WAAW,CAAQ,EAAG,MAAO,CAAC,EACtC,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAMA,EAAAA,QAAG,aAAa,EAAU,MAAM,CAAC,CACvD,OAAS,EAAO,CACd,MAAU,MAAM,kCAAkC,IAAY,CAAE,MAAO,CAAM,CAAC,CAChF,CACA,GAAI,CAAC,EAAS,CAAM,EAAG,MAAU,MAAM,kBAAkB,EAAS,uBAAuB,EACzF,OAAO,CACT,CAEA,SAAS,EAAgB,EAAoC,CAC3D,OAAO,EAAM,QAAkB,EAAQ,IAAa,EAAY,EAAQ,EAAW,CAAQ,CAAC,EAAG,CAAC,CAAC,CACnG,CAEA,SAAS,EAAiB,EAAyD,CACjF,OAAO,OAAO,GAAU,WAAa,EAAM,EAAI,CACjD,CAEA,SAAgB,EACd,EACA,EAAgBC,EAAAA,QAAG,QAAQ,EAC3B,EAAiB,IAAkBA,EAAAA,QAAG,QAAQ,GAAA,EAAIC,EAAAA,YAAAA,CAAY,EAAIC,EAAAA,QAAK,KAAK,EAAeC,EAAAA,gBAAiB,OAAO,EACpG,CACf,MAAO,CACL,cAAeD,EAAAA,QAAK,KAAK,EAAgB,CAAkB,EAC3D,iBAAkBA,EAAAA,QAAK,KAAK,EAAUC,EAAAA,gBAAiB,CAAkB,EACzE,WAAY,CAACD,EAAAA,QAAK,KAAK,EAAeC,EAAAA,gBAAiB,CAAkB,CAAC,EAC1E,cAAe,CAACD,EAAAA,QAAK,KAAK,EAAU,KAAwB,CAAkB,CAAC,CACjF,CACF,CAEA,SAAgB,EAAa,EAAwC,CACnE,IAAM,EAAQ,EAAc,EAAQ,SAAU,EAAQ,aAAa,EAC7D,EAAY,CAAC,GAAG,EAAM,WAAY,GAAI,EAAQ,iBAAmB,CAAC,EAAI,EAAM,aAAa,EACzF,EAAe,CAAC,GAAG,EAAM,cAAe,GAAI,EAAQ,oBAAsB,CAAC,EAAI,EAAM,gBAAgB,EACrG,EAAW,EAAQ,UAAY,CAAC,EAChC,EAAO,EAAgB,CAAS,EAChC,EAAiB,EAAiB,EAAQ,gBAAgB,EAAI,EAAgB,CAAY,EAAI,CAAC,EACrG,MAAO,CACL,EACA,EACA,EAAQ,SAAW,CAAC,EACpB,EACA,EAAQ,aAAe,CAAC,EACxB,EAAQ,KAAO,CAAC,EAChB,EAAQ,cAAgB,CAAC,CAC3B,CAAC,CAAC,OAAO,EAAa,CAAC,CAAC,CAC1B"}
@@ -0,0 +1,7 @@
1
+ import { PiConfig, PiConfigLoadOptions, PiConfigPaths } from "./schema.cjs";
2
+ //#region src/config/piConfig.d.ts
3
+ declare function piConfigPaths(repoRoot: string, homeDirectory?: string, agentDirectory?: string): PiConfigPaths;
4
+ declare function loadPiConfig(options: PiConfigLoadOptions): PiConfig;
5
+ //#endregion
6
+ export { loadPiConfig, piConfigPaths };
7
+ //# sourceMappingURL=piConfig.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"piConfig.d.cts","names":[],"sources":["../../src/config/piConfig.ts"],"mappings":";;iBA0CgB,cACd,kBACA,wBACA,0BACC;iBASa,aAAa,SAAS,sBAAsB"}
@@ -0,0 +1,7 @@
1
+ import { PiConfig, PiConfigLoadOptions, PiConfigPaths } from "./schema.mjs";
2
+ //#region src/config/piConfig.d.ts
3
+ declare function piConfigPaths(repoRoot: string, homeDirectory?: string, agentDirectory?: string): PiConfigPaths;
4
+ declare function loadPiConfig(options: PiConfigLoadOptions): PiConfig;
5
+ //#endregion
6
+ export { loadPiConfig, piConfigPaths };
7
+ //# sourceMappingURL=piConfig.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"piConfig.d.mts","names":[],"sources":["../../src/config/piConfig.ts"],"mappings":";;iBA0CgB,cACd,kBACA,wBACA,0BACC;iBASa,aAAa,SAAS,sBAAsB"}
@@ -0,0 +1,2 @@
1
+ import e from"node:fs";import t from"node:os";import n from"node:path";import{CONFIG_DIR_NAME as r,getAgentDir as i}from"@earendil-works/pi-coding-agent";const a=`settings.json`;function o(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function s(e,t){let n={...e};for(let[e,r]of Object.entries(t)){let t=n[e];n[e]=o(t)&&o(r)?s(t,r):r}return n}function c(t){if(!e.existsSync(t))return{};let n;try{n=JSON.parse(e.readFileSync(t,`utf8`))}catch(e){throw Error(`Could not parse Pi settings at ${t}`,{cause:e})}if(!o(n))throw Error(`Pi settings at ${t} must be a JSON object`);return n}function l(e){return e.reduce((e,t)=>s(e,c(t)),{})}function u(e){return typeof e==`function`?e():e}function d(e,o=t.homedir(),s=o===t.homedir()?i():n.join(o,r,`agent`)){return{canonicalUser:n.join(s,a),canonicalProject:n.join(e,r,a),legacyUser:[n.join(o,r,a)],legacyProject:[n.join(e,`pi`,a)]}}function f(e){let t=d(e.repoRoot,e.homeDirectory),n=[...t.legacyUser,...e.userConfigPaths??[],t.canonicalUser],r=[...t.legacyProject,...e.projectConfigPaths??[],t.canonicalProject],i=e.defaults??{},a=l(n),o=u(e.isProjectTrusted)?l(r):{};return[i,a,e.overlay??{},o,e.environment??{},e.cli??{},e.programmatic??{}].reduce(s,{})}export{f as loadPiConfig,d as piConfigPaths};
2
+ //# sourceMappingURL=piConfig.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"piConfig.mjs","names":[],"sources":["../../src/config/piConfig.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { CONFIG_DIR_NAME, getAgentDir } from '@earendil-works/pi-coding-agent';\nimport type { PiConfig, PiConfigLoadOptions, PiConfigPaths } from './schema.ts';\n\nconst SETTINGS_FILE_NAME = 'settings.json';\nconst LEGACY_CONFIG_DIR_NAME = 'pi';\n\nfunction isObject(value: unknown): value is PiConfig {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction mergeConfig(base: PiConfig, override: PiConfig): PiConfig {\n const result: PiConfig = { ...base };\n for (const [key, value] of Object.entries(override)) {\n const current = result[key];\n result[key] = isObject(current) && isObject(value) ? mergeConfig(current, value) : value;\n }\n return result;\n}\n\nfunction readConfig(filePath: string): PiConfig {\n if (!fs.existsSync(filePath)) return {};\n let parsed: unknown;\n try {\n parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n } catch (error) {\n throw new Error(`Could not parse Pi settings at ${filePath}`, { cause: error });\n }\n if (!isObject(parsed)) throw new Error(`Pi settings at ${filePath} must be a JSON object`);\n return parsed;\n}\n\nfunction readConfigPaths(paths: readonly string[]): PiConfig {\n return paths.reduce<PiConfig>((config, filePath) => mergeConfig(config, readConfig(filePath)), {});\n}\n\nfunction projectIsTrusted(value: PiConfigLoadOptions['isProjectTrusted']): boolean {\n return typeof value === 'function' ? value() : value;\n}\n\nexport function piConfigPaths(\n repoRoot: string,\n homeDirectory = os.homedir(),\n agentDirectory = homeDirectory === os.homedir() ? getAgentDir() : path.join(homeDirectory, CONFIG_DIR_NAME, 'agent'),\n): PiConfigPaths {\n return {\n canonicalUser: path.join(agentDirectory, SETTINGS_FILE_NAME),\n canonicalProject: path.join(repoRoot, CONFIG_DIR_NAME, SETTINGS_FILE_NAME),\n legacyUser: [path.join(homeDirectory, CONFIG_DIR_NAME, SETTINGS_FILE_NAME)],\n legacyProject: [path.join(repoRoot, LEGACY_CONFIG_DIR_NAME, SETTINGS_FILE_NAME)],\n };\n}\n\nexport function loadPiConfig(options: PiConfigLoadOptions): PiConfig {\n const paths = piConfigPaths(options.repoRoot, options.homeDirectory);\n const userPaths = [...paths.legacyUser, ...(options.userConfigPaths ?? []), paths.canonicalUser];\n const projectPaths = [...paths.legacyProject, ...(options.projectConfigPaths ?? []), paths.canonicalProject];\n const defaults = options.defaults ?? {};\n const user = readConfigPaths(userPaths);\n const trustedProject = projectIsTrusted(options.isProjectTrusted) ? readConfigPaths(projectPaths) : {};\n return [\n defaults,\n user,\n options.overlay ?? {},\n trustedProject,\n options.environment ?? {},\n options.cli ?? {},\n options.programmatic ?? {},\n ].reduce(mergeConfig, {});\n}\n"],"mappings":"0JAMA,MAAM,EAAqB,gBAG3B,SAAS,EAAS,EAAmC,CACnD,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,SAAS,EAAY,EAAgB,EAA8B,CACjE,IAAM,EAAmB,CAAE,GAAG,CAAK,EACnC,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAQ,EAAG,CACnD,IAAM,EAAU,EAAO,GACvB,EAAO,GAAO,EAAS,CAAO,GAAK,EAAS,CAAK,EAAI,EAAY,EAAS,CAAK,EAAI,CACrF,CACA,OAAO,CACT,CAEA,SAAS,EAAW,EAA4B,CAC9C,GAAI,CAAC,EAAG,WAAW,CAAQ,EAAG,MAAO,CAAC,EACtC,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,EAAG,aAAa,EAAU,MAAM,CAAC,CACvD,OAAS,EAAO,CACd,MAAU,MAAM,kCAAkC,IAAY,CAAE,MAAO,CAAM,CAAC,CAChF,CACA,GAAI,CAAC,EAAS,CAAM,EAAG,MAAU,MAAM,kBAAkB,EAAS,uBAAuB,EACzF,OAAO,CACT,CAEA,SAAS,EAAgB,EAAoC,CAC3D,OAAO,EAAM,QAAkB,EAAQ,IAAa,EAAY,EAAQ,EAAW,CAAQ,CAAC,EAAG,CAAC,CAAC,CACnG,CAEA,SAAS,EAAiB,EAAyD,CACjF,OAAO,OAAO,GAAU,WAAa,EAAM,EAAI,CACjD,CAEA,SAAgB,EACd,EACA,EAAgB,EAAG,QAAQ,EAC3B,EAAiB,IAAkB,EAAG,QAAQ,EAAI,EAAY,EAAI,EAAK,KAAK,EAAe,EAAiB,OAAO,EACpG,CACf,MAAO,CACL,cAAe,EAAK,KAAK,EAAgB,CAAkB,EAC3D,iBAAkB,EAAK,KAAK,EAAU,EAAiB,CAAkB,EACzE,WAAY,CAAC,EAAK,KAAK,EAAe,EAAiB,CAAkB,CAAC,EAC1E,cAAe,CAAC,EAAK,KAAK,EAAU,KAAwB,CAAkB,CAAC,CACjF,CACF,CAEA,SAAgB,EAAa,EAAwC,CACnE,IAAM,EAAQ,EAAc,EAAQ,SAAU,EAAQ,aAAa,EAC7D,EAAY,CAAC,GAAG,EAAM,WAAY,GAAI,EAAQ,iBAAmB,CAAC,EAAI,EAAM,aAAa,EACzF,EAAe,CAAC,GAAG,EAAM,cAAe,GAAI,EAAQ,oBAAsB,CAAC,EAAI,EAAM,gBAAgB,EACrG,EAAW,EAAQ,UAAY,CAAC,EAChC,EAAO,EAAgB,CAAS,EAChC,EAAiB,EAAiB,EAAQ,gBAAgB,EAAI,EAAgB,CAAY,EAAI,CAAC,EACrG,MAAO,CACL,EACA,EACA,EAAQ,SAAW,CAAC,EACpB,EACA,EAAQ,aAAe,CAAC,EACxB,EAAQ,KAAO,CAAC,EAChB,EAAQ,cAAgB,CAAC,CAC3B,CAAC,CAAC,OAAO,EAAa,CAAC,CAAC,CAC1B"}
File without changes
@@ -0,0 +1,33 @@
1
+ //#region src/config/schema.d.ts
2
+ type PiConfig = Record<string, unknown>;
3
+ interface DoomAdapterSettings {
4
+ projectTrust?: 'ask' | 'always' | 'never';
5
+ }
6
+ interface PiConfigPaths {
7
+ canonicalUser: string;
8
+ canonicalProject: string;
9
+ legacyUser: readonly string[];
10
+ legacyProject: readonly string[];
11
+ }
12
+ interface PiConfigLoadOptions {
13
+ repoRoot: string;
14
+ homeDirectory?: string;
15
+ defaults?: PiConfig;
16
+ overlay?: PiConfig;
17
+ userConfigPaths?: readonly string[];
18
+ projectConfigPaths?: readonly string[];
19
+ programmatic?: PiConfig;
20
+ environment?: PiConfig;
21
+ cli?: PiConfig;
22
+ isProjectTrusted: boolean | (() => boolean);
23
+ }
24
+ interface ConfigAdapterRegistrationOptions {
25
+ defaults?: PiConfig;
26
+ programmatic?: PiConfig;
27
+ environment?: PiConfig;
28
+ cli?: PiConfig;
29
+ homeDirectory?: string;
30
+ }
31
+ //#endregion
32
+ export { ConfigAdapterRegistrationOptions, DoomAdapterSettings, PiConfig, PiConfigLoadOptions, PiConfigPaths };
33
+ //# sourceMappingURL=schema.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.cts","names":[],"sources":["../../src/config/schema.ts"],"mappings":";KAAY,WAAW;UAEN;EACf;;UAGe;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA,WAAW;EACX,UAAU;EACV;EACA;EACA,eAAe;EACf,cAAc;EACd,MAAM;EACN;;UAGe;EACf,WAAW;EACX,eAAe;EACf,cAAc;EACd,MAAM;EACN"}
@@ -0,0 +1,33 @@
1
+ //#region src/config/schema.d.ts
2
+ type PiConfig = Record<string, unknown>;
3
+ interface DoomAdapterSettings {
4
+ projectTrust?: 'ask' | 'always' | 'never';
5
+ }
6
+ interface PiConfigPaths {
7
+ canonicalUser: string;
8
+ canonicalProject: string;
9
+ legacyUser: readonly string[];
10
+ legacyProject: readonly string[];
11
+ }
12
+ interface PiConfigLoadOptions {
13
+ repoRoot: string;
14
+ homeDirectory?: string;
15
+ defaults?: PiConfig;
16
+ overlay?: PiConfig;
17
+ userConfigPaths?: readonly string[];
18
+ projectConfigPaths?: readonly string[];
19
+ programmatic?: PiConfig;
20
+ environment?: PiConfig;
21
+ cli?: PiConfig;
22
+ isProjectTrusted: boolean | (() => boolean);
23
+ }
24
+ interface ConfigAdapterRegistrationOptions {
25
+ defaults?: PiConfig;
26
+ programmatic?: PiConfig;
27
+ environment?: PiConfig;
28
+ cli?: PiConfig;
29
+ homeDirectory?: string;
30
+ }
31
+ //#endregion
32
+ export { ConfigAdapterRegistrationOptions, DoomAdapterSettings, PiConfig, PiConfigLoadOptions, PiConfigPaths };
33
+ //# sourceMappingURL=schema.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.mts","names":[],"sources":["../../src/config/schema.ts"],"mappings":";KAAY,WAAW;UAEN;EACf;;UAGe;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA,WAAW;EACX,UAAU;EACV;EACA;EACA,eAAe;EACf,cAAc;EACd,MAAM;EACN;;UAGe;EACf,WAAW;EACX,eAAe;EACf,cAAc;EACd,MAAM;EACN"}
@@ -0,0 +1 @@
1
+ export{};
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:os");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);let i=require("yaml");const a=`.doom`,o=`config.yaml`,s=`auto`,c=[`modes`,`projectTrust`,`editor`,`voice`,`selection`],l=[`majorMode`,`domains`,`profile`],ee=[`planning`],u=[`main`,`subagents`,`plansDirectory`],d=[`model`,`thinking`],f=[`command`],p=[`engine`,`language`,`recorder`,`adapters`,`autoCapture`],te=[`binary`,`device`],m=`whisper-cpp`,h=`openai-whisper`,g=`mlx-whisper`,_=[m,h,g],v=[`binary`,`model`],ne=[`path`,`id`],re=[`model`,`startPhrases`,`stopPhrases`,`tts`],y=[`engine`,`voice`,`rate`],b=/^[^/\s]+\/[^/\s]+(?:\/[^/\s]+)*$/u,x=/[\p{P}\p{S}]+/gu,S=[`off`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`],C=[s,m,h,g],w=[`macos-say`],T=new Set(S),E=new Set([`ask`,`always`,`never`]),D=new Set(C),O=new Set(w);function k(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function A(e,t,n,r){let i=Object.keys(e).filter(e=>!t.includes(e));if(i.length>0)throw Error(`Doom config at ${r} has unsupported ${n} field(s): ${i.join(`, `)}`)}function j(e,t,n){if(e!==void 0){if(typeof e!=`string`||e.trim().length===0)throw Error(`Doom config at ${n} requires ${t} to be a non-empty string`);return e.trim()}}function M(e){for(let t of e){let e=t.codePointAt(0);if(e!==void 0&&(e<=31||e===127))return!0}return!1}function N(e,t,n,r){let i=j(e,t,n);if(i){if(i.length>r)throw Error(`Doom config at ${n} requires ${t} to be at most ${r} characters`);if(M(i))throw Error(`Doom config at ${n} does not allow control characters in ${t}`);return i}}function P(e,t){let n=`voice.autoCapture.model`,r=j(e,n,t);if(!r||!b.test(r))throw Error(`Doom config at ${t} requires ${n} in provider/model-id form`);return r}function F(e){return e.normalize(`NFKC`).toLocaleLowerCase(`en-US`).replace(x,` `).replace(/\s+/gu,` `).trim()}function I(e,t,n){if(e===void 0)return;if(!Array.isArray(e)||e.some(e=>typeof e!=`string`))throw Error(`Doom config at ${n} requires ${t} to be an array of strings`);if(e.length>16)throw Error(`Doom config at ${n} requires ${t} to contain at most 16 phrases`);let r=e.map(e=>e.trim()),i=new Set;for(let e of r){if(e.length===0)throw Error(`Doom config at ${n} requires ${t} to contain non-empty strings`);if(e.length>64)throw Error(`Doom config at ${n} requires ${t} phrases to be at most 64 characters`);if(M(e))throw Error(`Doom config at ${n} does not allow control characters in ${t}`);let r=F(e);if(!r)throw Error(`Doom config at ${n} requires ${t} to contain words`);if(i.has(r))throw Error(`Doom config at ${n} does not allow normalized duplicates in ${t}`);i.add(r)}return r}function L(e,t){let n=`voice.autoCapture.tts`;if(!k(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);if(A(e,y,n,t),typeof e.engine!=`string`||!O.has(e.engine))throw Error(`Doom config at ${t} requires ${n}.engine to be one of: ${[...O].join(`, `)}`);let r=N(e.voice,`${n}.voice`,t,64),i=e.rate;if(i!==void 0&&(typeof i!=`number`||!Number.isInteger(i)||i<80||i>500))throw Error(`Doom config at ${t} requires ${n}.rate to be an integer from 80 to 500`);return{engine:e.engine,...r?{voice:r}:{},...i===void 0?{}:{rate:i}}}function R(e,t){if(e===void 0)return;let n=`voice.autoCapture`;if(!k(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);A(e,re,n,t);let r=I(e.startPhrases,`${n}.startPhrases`,t),i=I(e.stopPhrases,`${n}.stopPhrases`,t),a=new Set((r??[]).map(F));if((i??[]).some(e=>a.has(F(e))))throw Error(`Doom config at ${t} does not allow a phrase in both startPhrases and stopPhrases`);return{model:P(e.model,t),...r?{startPhrases:r}:{},...i?{stopPhrases:i}:{},tts:L(e.tts,t)}}function z(e,t,n){if(e===void 0)return;if(!k(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);A(e,d,t,n);let r=e.thinking;if(r!==void 0&&(typeof r!=`string`||!T.has(r)))throw Error(`Doom config at ${n} requires ${t}.thinking to be one of: ${[...T].join(`, `)}`);let i=j(e.model,`${t}.model`,n);return{...i?{model:i}:{},...r?{thinking:r}:{}}}function B(e,t,n=`modes.planning`){if(!k(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);return A(e,u,n,t),{main:z(e.main,`${n}.main`,t),subagents:z(e.subagents,`${n}.subagents`,t),plansDirectory:j(e.plansDirectory,`${n}.plansDirectory`,t)}}function V(e,t,n,r){if(!k(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);A(e,ne,t,n);let i=j(e.path,`${t}.path`,n),a=j(e.id,`${t}.id`,n);if(!!i==!!a)throw Error(`Doom config at ${n} requires exactly one of ${t}.path or ${t}.id`);if(a&&!r)throw Error(`Doom config at ${n} does not support ${t}.id for whisper-cpp`);return{...i?{path:i}:{},...a?{id:a}:{}}}function H(e,t,n,r){if(e===void 0)return;if(!k(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);A(e,v,t,n);let i=j(e.binary,`${t}.binary`,n);return{...i?{binary:i}:{},model:V(e.model,`${t}.model`,n,r)}}function U(e,t){if(e===void 0)return;if(!k(e))throw Error(`Doom config at ${t} requires voice to be an object`);A(e,p,`voice`,t);let n={};if(e.engine!==void 0){if(typeof e.engine!=`string`||!D.has(e.engine))throw Error(`Doom config at ${t} requires voice.engine to be one of: ${[...D].join(`, `)}`);n.engine=e.engine}let r=j(e.language,`voice.language`,t);if(r&&(n.language=r),e.recorder!==void 0){if(!k(e.recorder))throw Error(`Doom config at ${t} requires voice.recorder to be an object`);A(e.recorder,te,`voice.recorder`,t),n.recorder={};let r=j(e.recorder.binary,`voice.recorder.binary`,t),i=j(e.recorder.device,`voice.recorder.device`,t);r&&(n.recorder.binary=r),i&&(n.recorder.device=i)}if(e.adapters!==void 0){if(!k(e.adapters))throw Error(`Doom config at ${t} requires voice.adapters to be an object`);A(e.adapters,_,`voice.adapters`,t),n.adapters={[m]:H(e.adapters[m],`voice.adapters.${m}`,t,!1),[h]:H(e.adapters[h],`voice.adapters.${h}`,t,!0),[g]:H(e.adapters[g],`voice.adapters.${g}`,t,!0)}}let i=R(e.autoCapture,t);return i&&(n.autoCapture=i),n}function W(e,t){if(e===void 0)return;if(!k(e))throw Error(`Doom config at ${t} requires selection to be an object`);A(e,l,`selection`,t);let n=e.domains;if(n!==void 0&&(!Array.isArray(n)||n.some(e=>typeof e!=`string`)))throw Error(`Doom config at ${t} requires selection.domains to be an array of strings`);return{majorMode:j(e.majorMode,`selection.majorMode`,t),domains:n,profile:j(e.profile,`selection.profile`,t)}}function G(e,t){if(e!==void 0){if(!k(e))throw Error(`Doom config at ${t} requires editor to be an object`);return A(e,f,`editor`,t),{command:j(e.command,`editor.command`,t)}}}function K(e,t){let n;try{n=(0,i.parse)(e)??{}}catch(e){throw Error(`Could not parse Doom config at ${t}`,{cause:e})}if(!k(n))throw Error(`Doom config at ${t} must be a YAML object`);A(n,c,`root`,t);let r;if(n.modes!==void 0){if(!k(n.modes))throw Error(`Doom config at ${t} requires modes to be an object`);A(n.modes,ee,`modes`,t),r=n.modes.planning===void 0?{}:{planning:B(n.modes.planning,t)}}let a=n.projectTrust===void 0?`ask`:n.projectTrust;if(typeof a!=`string`||!E.has(a))throw Error(`Doom config at ${t} projectTrust must be ask, always, or never`);return{modes:r,projectTrust:a,editor:G(n.editor,t),voice:U(n.voice,t),selection:W(n.selection,t)}}function q(e){return t.default.existsSync(e)?K(t.default.readFileSync(e,`utf8`),e):{projectTrust:`ask`}}function J(e,t){return e||t?{...e,...t}:void 0}function Y(e,t){if(t.voice?.autoCapture)throw Error(`Repository Doom config voice.autoCapture is global-only; configure it in ~/.pi/.doom/config.yaml`);let n=e.modes?.planning,r=t.modes?.planning,i=n||r?{main:J(n?.main,r?.main),subagents:J(n?.subagents,r?.subagents),plansDirectory:r?.plansDirectory??n?.plansDirectory}:void 0,a=ie(e.voice,t.voice);return{modes:i?{planning:i}:void 0,projectTrust:t.projectTrust,editor:e.editor,voice:a,selection:e.selection||t.selection?{...e.selection,...t.selection}:void 0}}function X(e,t){if(!(!e&&!t))return{...e,...t,model:t?.model??e.model}}function ie(e,t){if(!(!e&&!t))return{...e,...t,recorder:e?.recorder||t?.recorder?{...e?.recorder,...t?.recorder}:void 0,adapters:e?.adapters||t?.adapters?{[m]:X(e?.adapters?.[m],t?.adapters?.[m]),[h]:X(e?.adapters?.[h],t?.adapters?.[h]),[g]:X(e?.adapters?.[g],t?.adapters?.[g])}:void 0,...e?.autoCapture?{autoCapture:e.autoCapture}:{}}}function ae(e){return{engine:e.engine??s,language:e.language??`auto`,recorder:{...e.recorder,device:e.recorder?.device??`none:default`},adapters:e.adapters??{},...e.autoCapture?{autoCapture:{...e.autoCapture,startPhrases:[...e.autoCapture.startPhrases??[]],stopPhrases:[...e.autoCapture.stopPhrases??[]],tts:{...e.autoCapture.tts}}}:{}}}function Z(e=n.default.homedir()){return r.default.join(e,`.pi`,a)}function Q(e=n.default.homedir()){return r.default.join(Z(e),o)}function oe(e,t,i=n.default.homedir()){if(!e)return r.default.join(i,`.pi`,`plans`);if(e===`~`)return i;if(e.startsWith(`~/`))return r.default.resolve(i,e.slice(2));if(e.startsWith(`~`))throw Error(`Doom planning plansDirectory supports only '~' or '~/' home aliases`);return r.default.isAbsolute(e)?r.default.normalize(e):r.default.resolve(t,e)}function $(e){return r.default.join(e,a,o)}function se(e,t=n.default.homedir()){return Y(q(Q(t)),q($(e)))}exports.DOOM_PLANNING_THINKING_LEVELS=S,exports.DOOM_VOICE_ENGINES=C,exports.DOOM_VOICE_TTS_ENGINES=w,exports.globalDoomConfigDirectory=Z,exports.globalDoomConfigPath=Q,exports.loadDoomConfig=se,exports.mergeDoomConfigs=Y,exports.parseDoomConfig=K,exports.parsePlanningModeConfig=B,exports.repositoryDoomConfigPath=$,exports.resolvePlanningPlansDirectory=oe,exports.resolveVoiceConfig=ae;
2
+ //# sourceMappingURL=config.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.cjs","names":["parseYaml","fs","os","path"],"sources":["../src/config.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport type {\n DoomConfig,\n DoomSelectionConfig,\n EditorConfig,\n PlanningAgentConfig,\n PlanningModeConfig,\n PlanningThinkingLevel,\n ProjectTrust,\n ResolvedVoiceConfig,\n VoiceAdapterConfig,\n VoiceAutoCaptureConfig,\n VoiceConfig,\n VoiceEngine,\n VoiceModelConfig,\n VoiceTtsConfig,\n VoiceTtsEngine,\n} from './types.ts';\n\nconst DOOM_DIR = '.doom';\nconst CONFIG_FILE = 'config.yaml';\nconst GLOBAL_CONFIG_DIR = '.pi';\nconst PLANS_DIR = 'plans';\nconst DEFAULT_PROJECT_TRUST: ProjectTrust = 'ask';\nconst DEFAULT_VOICE_ENGINE: VoiceEngine = 'auto';\nconst DEFAULT_VOICE_LANGUAGE = 'auto';\nconst DEFAULT_RECORDER_DEVICE = 'none:default';\nconst ROOT_KEYS = ['modes', 'projectTrust', 'editor', 'voice', 'selection'] as const;\nconst SELECTION_KEYS = ['majorMode', 'domains', 'profile'] as const;\nconst MODE_KEYS = ['planning'] as const;\nconst PLANNING_KEYS = ['main', 'subagents', 'plansDirectory'] as const;\nconst AGENT_KEYS = ['model', 'thinking'] as const;\nconst EDITOR_KEYS = ['command'] as const;\nconst VOICE_KEYS = ['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 = ['model', 'startPhrases', 'stopPhrases', 'tts'] as const;\nconst TTS_KEYS = ['engine', 'voice', 'rate'] as const;\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_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 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}\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)\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 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 startKeys = new Set((startPhrases ?? []).map(normalizeControlPhrase));\n if ((stopPhrases ?? []).some((phrase) => startKeys.has(normalizeControlPhrase(phrase))))\n throw new Error(`Doom config at ${filePath} does not allow a phrase in both startPhrases and stopPhrases`);\n return {\n model: parseModelReference(value.model, filePath),\n ...(startPhrases ? { startPhrases } : {}),\n ...(stopPhrases ? { stopPhrases } : {}),\n tts: parseTtsConfig(value.tts, filePath),\n };\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 = value.thinking;\n if (\n thinking !== undefined &&\n (typeof thinking !== 'string' || !THINKING_VALUES.has(thinking as PlanningThinkingLevel))\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.thinking to be one of: ${[...THINKING_VALUES].join(', ')}`,\n );\n }\n const model = optionalString(value.model, `${location}.model`, filePath);\n return { ...(model ? { model } : {}), ...(thinking ? { thinking: thinking as PlanningThinkingLevel } : {}) };\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.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}\nexport function parseDoomConfig(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 ? {} : { planning: parsePlanningModeConfig(parsed.modes.planning, filePath) };\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 projectTrust: trust as ProjectTrust,\n editor: parseEditor(parsed.editor, filePath),\n voice: parseVoice(parsed.voice, filePath),\n selection: parseSelection(parsed.selection, filePath),\n };\n}\nfunction readConfig(filePath: string): DoomConfig {\n return fs.existsSync(filePath)\n ? parseDoomConfig(fs.readFileSync(filePath, 'utf8'), filePath)\n : { projectTrust: DEFAULT_PROJECT_TRUST };\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?.autoCapture)\n throw new Error('Repository Doom config voice.autoCapture is global-only; configure it in ~/.pi/.doom/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 voice = mergeVoice(globalConfig.voice, repositoryConfig.voice);\n return {\n modes: planning ? { planning } : undefined,\n projectTrust: repositoryConfig.projectTrust,\n editor: globalConfig.editor,\n voice,\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 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}\nexport function resolveVoiceConfig(config: VoiceConfig): ResolvedVoiceConfig {\n return {\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 tts: { ...config.autoCapture.tts },\n },\n }\n : {}),\n };\n}\nexport function globalDoomConfigDirectory(homeDirectory = os.homedir()): string {\n return path.join(homeDirectory, GLOBAL_CONFIG_DIR, DOOM_DIR);\n}\nexport function globalDoomConfigPath(homeDirectory = os.homedir()): string {\n return path.join(globalDoomConfigDirectory(homeDirectory), CONFIG_FILE);\n}\nexport function resolvePlanningPlansDirectory(\n configured: string | undefined,\n repoRoot: string,\n homeDirectory = os.homedir(),\n): string {\n if (!configured) return path.join(homeDirectory, GLOBAL_CONFIG_DIR, PLANS_DIR);\n if (configured === '~') return homeDirectory;\n if (configured.startsWith('~/')) return path.resolve(homeDirectory, configured.slice(2));\n if (configured.startsWith('~')) {\n throw new Error(\"Doom planning plansDirectory supports only '~' or '~/' home aliases\");\n }\n return path.isAbsolute(configured) ? path.normalize(configured) : path.resolve(repoRoot, configured);\n}\nexport function repositoryDoomConfigPath(repoRoot: string): string {\n return path.join(repoRoot, DOOM_DIR, CONFIG_FILE);\n}\nexport function loadDoomConfig(repoRoot: string, homeDirectory = os.homedir()): DoomConfig {\n return mergeDoomConfigs(\n readConfig(globalDoomConfigPath(homeDirectory)),\n readConfig(repositoryDoomConfigPath(repoRoot)),\n );\n}\n"],"mappings":"6QAsBA,MAAM,EAAW,QACX,EAAc,cAId,EAAoC,OAGpC,EAAY,CAAC,QAAS,eAAgB,SAAU,QAAS,WAAW,EACpE,EAAiB,CAAC,YAAa,UAAW,SAAS,EACnD,GAAY,CAAC,UAAU,EACvB,EAAgB,CAAC,OAAQ,YAAa,gBAAgB,EACtD,EAAa,CAAC,QAAS,UAAU,EACjC,EAAc,CAAC,SAAS,EACxB,EAAa,CAAC,SAAU,WAAY,WAAY,WAAY,aAAa,EACzE,GAAgB,CAAC,SAAU,QAAQ,EACnC,EAAqB,cACrB,EAAwB,iBACxB,EAAqB,cAErB,EAAgB,CAAC,EAAoB,EAAuB,CAAkB,EAC9E,EAAe,CAAC,SAAU,OAAO,EACjC,GAAa,CAAC,OAAQ,IAAI,EAC1B,GAAoB,CAAC,QAAS,eAAgB,cAAe,KAAK,EAClE,EAAW,CAAC,SAAU,QAAS,MAAM,EAMrC,EAA0B,oCAC1B,EAA6B,kBAQtB,EAAgC,CAAC,MAAO,UAAW,MAAO,SAAU,OAAQ,QAAS,KAAK,EAC1F,EAAqB,CAChC,EACA,EACA,EACA,CACF,EACa,EAAyB,CAAC,WAAoB,EAErD,EAAkB,IAAI,IAA2B,CAA6B,EAC9E,EAAe,IAAI,IAAkB,CAAC,MAAO,SAAU,OAAO,CAAC,EAC/D,EAAgB,IAAI,IAAiB,CAAkB,EACvD,EAAoB,IAAI,IAAoB,CAAsB,EAGxE,SAAS,EAAS,EAAuC,CACvD,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CACA,SAAS,EAAW,EAAqB,EAA4B,EAAkB,EAAwB,CAC7G,IAAM,EAAU,OAAO,KAAK,CAAK,CAAC,CAAC,OAAQ,GAAQ,CAAC,EAAQ,SAAS,CAAG,CAAC,EACzE,GAAI,EAAQ,OAAS,EACnB,MAAU,MAAM,kBAAkB,EAAS,mBAAmB,EAAS,aAAa,EAAQ,KAAK,IAAI,GAAG,CAC5G,CACA,SAAS,EAAe,EAAgB,EAAkB,EAAsC,CAC1F,OAAU,IAAA,GACd,IAAI,OAAO,GAAU,UAAY,EAAM,KAAK,CAAC,CAAC,SAAW,EACvD,MAAU,MAAM,kBAAkB,EAAS,YAAY,EAAS,0BAA0B,EAC5F,OAAO,EAAM,KAAK,CAD0E,CAE9F,CACA,SAAS,EAAqB,EAAwB,CACpD,IAAK,IAAM,KAAa,EAAO,CAC7B,IAAM,EAAY,EAAU,YAAY,CAAC,EACzC,GAAI,IAAc,IAAA,KAAc,GAAa,IAAQ,IAAc,KAAO,MAAO,EACnF,CACA,MAAO,EACT,CACA,SAAS,EACP,EACA,EACA,EACA,EACoB,CACpB,IAAM,EAAO,EAAe,EAAO,EAAU,CAAQ,EAChD,KACL,IAAI,EAAK,OAAS,EAChB,MAAU,MAAM,kBAAkB,EAAS,YAAY,EAAS,iBAAiB,EAAc,YAAY,EAC7G,GAAI,EAAqB,CAAI,EAC3B,MAAU,MAAM,kBAAkB,EAAS,wCAAwC,GAAU,EAC/F,OAAO,CAHsG,CAI/G,CACA,SAAS,EAAoB,EAAgB,EAA0B,CACrE,IAAM,EAAW,0BACX,EAAQ,EAAe,EAAO,EAAU,CAAQ,EACtD,GAAI,CAAC,GAAS,CAAC,EAAwB,KAAK,CAAK,EAC/C,MAAU,MAAM,kBAAkB,EAAS,YAAY,EAAS,2BAA2B,EAC7F,OAAO,CACT,CACA,SAAS,EAAuB,EAAuB,CACrD,OAAO,EACJ,UAAU,MAAM,CAAC,CACjB,kBAAkB,OAAO,CAAC,CAC1B,QAAQ,EAA4B,GAAG,CAAC,CACxC,QAAQ,QAAS,GAAG,CAAC,CACrB,KAAK,CACV,CACA,SAAS,EAAoB,EAAgB,EAAkB,EAAwC,CACrG,GAAI,IAAU,IAAA,GAAW,OACzB,GAAI,CAAC,MAAM,QAAQ,CAAK,GAAK,EAAM,KAAM,GAAW,OAAO,GAAW,QAAQ,EAC5E,MAAU,MAAM,kBAAkB,EAAS,YAAY,EAAS,2BAA2B,EAC7F,GAAI,EAAM,OAAS,GACjB,MAAU,MACR,kBAAkB,EAAS,YAAY,EAAS,+BAClD,EACF,IAAM,EAAU,EAAM,IAAK,GAAW,EAAO,KAAK,CAAC,EAC7C,EAAa,IAAI,IACvB,IAAK,IAAM,KAAU,EAAS,CAC5B,GAAI,EAAO,SAAW,EACpB,MAAU,MAAM,kBAAkB,EAAS,YAAY,EAAS,8BAA8B,EAChG,GAAI,EAAO,OAAS,GAClB,MAAU,MACR,kBAAkB,EAAS,YAAY,EAAS,qCAClD,EACF,GAAI,EAAqB,CAAM,EAC7B,MAAU,MAAM,kBAAkB,EAAS,wCAAwC,GAAU,EAC/F,IAAM,EAAM,EAAuB,CAAM,EACzC,GAAI,CAAC,EAAK,MAAU,MAAM,kBAAkB,EAAS,YAAY,EAAS,kBAAkB,EAC5F,GAAI,EAAW,IAAI,CAAG,EACpB,MAAU,MAAM,kBAAkB,EAAS,2CAA2C,GAAU,EAClG,EAAW,IAAI,CAAG,CACpB,CACA,OAAO,CACT,CACA,SAAS,EAAe,EAAgB,EAAkC,CACxE,IAAM,EAAW,wBACjB,GAAI,CAAC,EAAS,CAAK,EAAG,MAAU,MAAM,kBAAkB,EAAS,YAAY,EAAS,iBAAiB,EAEvG,GADA,EAAW,EAAO,EAAU,EAAU,CAAQ,EAC1C,OAAO,EAAM,QAAW,UAAY,CAAC,EAAkB,IAAI,EAAM,MAAwB,EAC3F,MAAU,MACR,kBAAkB,EAAS,YAAY,EAAS,wBAAwB,CAAC,GAAG,CAAiB,CAAC,CAAC,KAAK,IAAI,GAC1G,EACF,IAAM,EAAQ,EAAsB,EAAM,MAAO,GAAG,EAAS,QAAS,EAAU,EAAoB,EAC9F,EAAO,EAAM,KACnB,GACE,IAAS,IAAA,KACR,OAAO,GAAS,UAAY,CAAC,OAAO,UAAU,CAAI,GAAK,EAAO,IAAgB,EAAO,KAEtF,MAAU,MACR,kBAAkB,EAAS,YAAY,EAAS,sCAClD,EAEF,MAAO,CACL,OAAQ,EAAM,OACd,GAAI,EAAQ,CAAE,OAAM,EAAI,CAAC,EACzB,GAAI,IAAS,IAAA,GAAY,CAAC,EAAI,CAAE,MAAK,CACvC,CACF,CACA,SAAS,EAAiB,EAAgB,EAAsD,CAC9F,GAAI,IAAU,IAAA,GAAW,OACzB,IAAM,EAAW,oBACjB,GAAI,CAAC,EAAS,CAAK,EAAG,MAAU,MAAM,kBAAkB,EAAS,YAAY,EAAS,iBAAiB,EACvG,EAAW,EAAO,GAAmB,EAAU,CAAQ,EACvD,IAAM,EAAe,EAAoB,EAAM,aAAc,GAAG,EAAS,eAAgB,CAAQ,EAC3F,EAAc,EAAoB,EAAM,YAAa,GAAG,EAAS,cAAe,CAAQ,EACxF,EAAY,IAAI,KAAK,GAAgB,CAAC,EAAA,CAAG,IAAI,CAAsB,CAAC,EAC1E,IAAK,GAAe,CAAC,EAAA,CAAG,KAAM,GAAW,EAAU,IAAI,EAAuB,CAAM,CAAC,CAAC,EACpF,MAAU,MAAM,kBAAkB,EAAS,8DAA8D,EAC3G,MAAO,CACL,MAAO,EAAoB,EAAM,MAAO,CAAQ,EAChD,GAAI,EAAe,CAAE,cAAa,EAAI,CAAC,EACvC,GAAI,EAAc,CAAE,aAAY,EAAI,CAAC,EACrC,IAAK,EAAe,EAAM,IAAK,CAAQ,CACzC,CACF,CACA,SAAS,EAAW,EAAgB,EAAkB,EAAmD,CACvG,GAAI,IAAU,IAAA,GAAW,OACzB,GAAI,CAAC,EAAS,CAAK,EAAG,MAAU,MAAM,kBAAkB,EAAS,YAAY,EAAS,iBAAiB,EACvG,EAAW,EAAO,EAAY,EAAU,CAAQ,EAChD,IAAM,EAAW,EAAM,SACvB,GACE,IAAa,IAAA,KACZ,OAAO,GAAa,UAAY,CAAC,EAAgB,IAAI,CAAiC,GAEvF,MAAU,MACR,kBAAkB,EAAS,YAAY,EAAS,0BAA0B,CAAC,GAAG,CAAe,CAAC,CAAC,KAAK,IAAI,GAC1G,EAEF,IAAM,EAAQ,EAAe,EAAM,MAAO,GAAG,EAAS,QAAS,CAAQ,EACvE,MAAO,CAAE,GAAI,EAAQ,CAAE,OAAM,EAAI,CAAC,EAAI,GAAI,EAAW,CAAY,UAAkC,EAAI,CAAC,CAAG,CAC7G,CACA,SAAgB,EACd,EACA,EACA,EAAW,iBACS,CACpB,GAAI,CAAC,EAAS,CAAK,EAAG,MAAU,MAAM,kBAAkB,EAAS,YAAY,EAAS,iBAAiB,EAEvG,OADA,EAAW,EAAO,EAAe,EAAU,CAAQ,EAC5C,CACL,KAAM,EAAW,EAAM,KAAM,GAAG,EAAS,OAAQ,CAAQ,EACzD,UAAW,EAAW,EAAM,UAAW,GAAG,EAAS,YAAa,CAAQ,EACxE,eAAgB,EAAe,EAAM,eAAgB,GAAG,EAAS,iBAAkB,CAAQ,CAC7F,CACF,CACA,SAAS,EAAW,EAAgB,EAAkB,EAAkB,EAAoC,CAC1G,GAAI,CAAC,EAAS,CAAK,EAAG,MAAU,MAAM,kBAAkB,EAAS,YAAY,EAAS,iBAAiB,EACvG,EAAW,EAAO,GAAY,EAAU,CAAQ,EAChD,IAAM,EAAY,EAAe,EAAM,KAAM,GAAG,EAAS,OAAQ,CAAQ,EACnE,EAAK,EAAe,EAAM,GAAI,GAAG,EAAS,KAAM,CAAQ,EAC9D,GAAI,EAAQ,GAAe,EAAQ,EACjC,MAAU,MAAM,kBAAkB,EAAS,2BAA2B,EAAS,WAAW,EAAS,IAAI,EACzG,GAAI,GAAM,CAAC,EAAS,MAAU,MAAM,kBAAkB,EAAS,oBAAoB,EAAS,oBAAoB,EAChH,MAAO,CAAE,GAAI,EAAY,CAAE,KAAM,CAAU,EAAI,CAAC,EAAI,GAAI,EAAK,CAAE,IAAG,EAAI,CAAC,CAAG,CAC5E,CACA,SAAS,EACP,EACA,EACA,EACA,EACgC,CAChC,GAAI,IAAU,IAAA,GAAW,OACzB,GAAI,CAAC,EAAS,CAAK,EAAG,MAAU,MAAM,kBAAkB,EAAS,YAAY,EAAS,iBAAiB,EACvG,EAAW,EAAO,EAAc,EAAU,CAAQ,EAClD,IAAM,EAAS,EAAe,EAAM,OAAQ,GAAG,EAAS,SAAU,CAAQ,EAC1E,MAAO,CAAE,GAAI,EAAS,CAAE,QAAO,EAAI,CAAC,EAAI,MAAO,EAAW,EAAM,MAAO,GAAG,EAAS,QAAS,EAAU,CAAO,CAAE,CACjH,CACA,SAAS,EAAW,EAAgB,EAA2C,CAC7E,GAAI,IAAU,IAAA,GAAW,OACzB,GAAI,CAAC,EAAS,CAAK,EAAG,MAAU,MAAM,kBAAkB,EAAS,gCAAgC,EACjG,EAAW,EAAO,EAAY,QAAS,CAAQ,EAC/C,IAAM,EAAsB,CAAC,EAC7B,GAAI,EAAM,SAAW,IAAA,GAAW,CAC9B,GAAI,OAAO,EAAM,QAAW,UAAY,CAAC,EAAc,IAAI,EAAM,MAAqB,EACpF,MAAU,MACR,kBAAkB,EAAS,uCAAuC,CAAC,GAAG,CAAa,CAAC,CAAC,KAAK,IAAI,GAChG,EACF,EAAO,OAAS,EAAM,MACxB,CACA,IAAM,EAAW,EAAe,EAAM,SAAU,iBAAkB,CAAQ,EAE1E,GADI,IAAU,EAAO,SAAW,GAC5B,EAAM,WAAa,IAAA,GAAW,CAChC,GAAI,CAAC,EAAS,EAAM,QAAQ,EAC1B,MAAU,MAAM,kBAAkB,EAAS,yCAAyC,EACtF,EAAW,EAAM,SAAU,GAAe,iBAAkB,CAAQ,EACpE,EAAO,SAAW,CAAC,EACnB,IAAM,EAAS,EAAe,EAAM,SAAS,OAAQ,wBAAyB,CAAQ,EAChF,EAAS,EAAe,EAAM,SAAS,OAAQ,wBAAyB,CAAQ,EAClF,IAAQ,EAAO,SAAS,OAAS,GACjC,IAAQ,EAAO,SAAS,OAAS,EACvC,CACA,GAAI,EAAM,WAAa,IAAA,GAAW,CAChC,GAAI,CAAC,EAAS,EAAM,QAAQ,EAC1B,MAAU,MAAM,kBAAkB,EAAS,yCAAyC,EACtF,EAAW,EAAM,SAAU,EAAe,iBAAkB,CAAQ,EACpE,EAAO,SAAW,EACf,GAAqB,EACpB,EAAM,SAAS,GACf,kBAAkB,IAClB,EACA,EACF,GACC,GAAwB,EACvB,EAAM,SAAS,GACf,kBAAkB,IAClB,EACA,EACF,GACC,GAAqB,EACpB,EAAM,SAAS,GACf,kBAAkB,IAClB,EACA,EACF,CACF,CACF,CACA,IAAM,EAAc,EAAiB,EAAM,YAAa,CAAQ,EAEhE,OADI,IAAa,EAAO,YAAc,GAC/B,CACT,CACA,SAAS,EAAe,EAAgB,EAAmD,CACzF,GAAI,IAAU,IAAA,GAAW,OACzB,GAAI,CAAC,EAAS,CAAK,EAAG,MAAU,MAAM,kBAAkB,EAAS,oCAAoC,EACrG,EAAW,EAAO,EAAgB,YAAa,CAAQ,EACvD,IAAM,EAAU,EAAM,QACtB,GAAI,IAAY,IAAA,KAAc,CAAC,MAAM,QAAQ,CAAO,GAAK,EAAQ,KAAM,GAAS,OAAO,GAAS,QAAQ,GACtG,MAAU,MAAM,kBAAkB,EAAS,sDAAsD,EAEnG,MAAO,CACL,UAAW,EAAe,EAAM,UAAW,sBAAuB,CAAQ,EAEjE,UACT,QAAS,EAAe,EAAM,QAAS,oBAAqB,CAAQ,CACtE,CACF,CACA,SAAS,EAAY,EAAgB,EAA4C,CAC3E,OAAU,IAAA,GACd,IAAI,CAAC,EAAS,CAAK,EAAG,MAAU,MAAM,kBAAkB,EAAS,iCAAiC,EAElG,OADA,EAAW,EAAO,EAAa,SAAU,CAAQ,EAC1C,CAAE,QAAS,EAAe,EAAM,QAAS,iBAAkB,CAAQ,CAAE,CAFsB,CAGpG,CACA,SAAgB,EAAgB,EAAiB,EAA8B,CAC7E,IAAI,EACJ,GAAI,CACF,GAAA,EAASA,EAAAA,MAAAA,CAAU,CAAO,GAAK,CAAC,CAClC,OAAS,EAAO,CACd,MAAU,MAAM,kCAAkC,IAAY,CAAE,MAAO,CAAM,CAAC,CAChF,CACA,GAAI,CAAC,EAAS,CAAM,EAAG,MAAU,MAAM,kBAAkB,EAAS,uBAAuB,EACzF,EAAW,EAAQ,EAAW,OAAQ,CAAQ,EAC9C,IAAI,EACJ,GAAI,EAAO,QAAU,IAAA,GAAW,CAC9B,GAAI,CAAC,EAAS,EAAO,KAAK,EAAG,MAAU,MAAM,kBAAkB,EAAS,gCAAgC,EACxG,EAAW,EAAO,MAAO,GAAW,QAAS,CAAQ,EACrD,EACE,EAAO,MAAM,WAAa,IAAA,GAAY,CAAC,EAAI,CAAE,SAAU,EAAwB,EAAO,MAAM,SAAU,CAAQ,CAAE,CACpH,CACA,IAAM,EAAQ,EAAO,eAAiB,IAAA,GAAY,MAAwB,EAAO,aACjF,GAAI,OAAO,GAAU,UAAY,CAAC,EAAa,IAAI,CAAqB,EACtE,MAAU,MAAM,kBAAkB,EAAS,4CAA4C,EACzF,MAAO,CACL,QACA,aAAc,EACd,OAAQ,EAAY,EAAO,OAAQ,CAAQ,EAC3C,MAAO,EAAW,EAAO,MAAO,CAAQ,EACxC,UAAW,EAAe,EAAO,UAAW,CAAQ,CACtD,CACF,CACA,SAAS,EAAW,EAA8B,CAChD,OAAOC,EAAAA,QAAG,WAAW,CAAQ,EACzB,EAAgBA,EAAAA,QAAG,aAAa,EAAU,MAAM,EAAG,CAAQ,EAC3D,CAAE,aAAc,KAAsB,CAC5C,CACA,SAAS,EACP,EACA,EACiC,CACjC,OAAO,GAAe,EAAkB,CAAE,GAAG,EAAa,GAAG,CAAgB,EAAI,IAAA,EACnF,CACA,SAAgB,EAAiB,EAA0B,EAA0C,CACnG,GAAI,EAAiB,OAAO,YAC1B,MAAU,MAAM,kGAAkG,EACpH,IAAM,EAAiB,EAAa,OAAO,SACrC,EAAqB,EAAiB,OAAO,SAC7C,EACJ,GAAkB,EACd,CACE,KAAM,EAAW,GAAgB,KAAM,GAAoB,IAAI,EAC/D,UAAW,EAAW,GAAgB,UAAW,GAAoB,SAAS,EAC9E,eAAgB,GAAoB,gBAAkB,GAAgB,cACxE,EACA,IAAA,GACA,EAAQ,GAAW,EAAa,MAAO,EAAiB,KAAK,EACnE,MAAO,CACL,MAAO,EAAW,CAAE,UAAS,EAAI,IAAA,GACjC,aAAc,EAAiB,aAC/B,OAAQ,EAAa,OACrB,QAEA,UACE,EAAa,WAAa,EAAiB,UACvC,CAAE,GAAG,EAAa,UAAW,GAAG,EAAiB,SAAU,EAC3D,IAAA,EACR,CACF,CACA,SAAS,EACP,EACA,EACgC,CAC5B,MAAC,GAAe,CAAC,GACrB,MAAO,CAAE,GAAG,EAAa,GAAG,EAAiB,MAAO,GAAiB,OAAS,EAAa,KAAM,CACnG,CACA,SAAS,GAAW,EAA2B,EAAwD,CACjG,MAAC,GAAe,CAAC,GACrB,MAAO,CACL,GAAG,EACH,GAAG,EACH,SACE,GAAa,UAAY,GAAiB,SACtC,CAAE,GAAG,GAAa,SAAU,GAAG,GAAiB,QAAS,EACzD,IAAA,GACN,SACE,GAAa,UAAY,GAAiB,SACtC,EACG,GAAqB,EACpB,GAAa,WAAW,GACxB,GAAiB,WAAW,EAC9B,GACC,GAAwB,EACvB,GAAa,WAAW,GACxB,GAAiB,WAAW,EAC9B,GACC,GAAqB,EACpB,GAAa,WAAW,GACxB,GAAiB,WAAW,EAC9B,CACF,EACA,IAAA,GACN,GAAI,GAAa,YAAc,CAAE,YAAa,EAAY,WAAY,EAAI,CAAC,CAC7E,CACF,CACA,SAAgB,GAAmB,EAA0C,CAC3E,MAAO,CACL,OAAQ,EAAO,QAAU,EACzB,SAAU,EAAO,UAAY,OAC7B,SAAU,CAAE,GAAG,EAAO,SAAU,OAAQ,EAAO,UAAU,QAAU,cAAwB,EAC3F,SAAU,EAAO,UAAY,CAAC,EAC9B,GAAI,EAAO,YACP,CACE,YAAa,CACX,GAAG,EAAO,YACV,aAAc,CAAC,GAAI,EAAO,YAAY,cAAgB,CAAC,CAAE,EACzD,YAAa,CAAC,GAAI,EAAO,YAAY,aAAe,CAAC,CAAE,EACvD,IAAK,CAAE,GAAG,EAAO,YAAY,GAAI,CACnC,CACF,EACA,CAAC,CACP,CACF,CACA,SAAgB,EAA0B,EAAgBC,EAAAA,QAAG,QAAQ,EAAW,CAC9E,OAAOC,EAAAA,QAAK,KAAK,EAAe,MAAmB,CAAQ,CAC7D,CACA,SAAgB,EAAqB,EAAgBD,EAAAA,QAAG,QAAQ,EAAW,CACzE,OAAOC,EAAAA,QAAK,KAAK,EAA0B,CAAa,EAAG,CAAW,CACxE,CACA,SAAgB,GACd,EACA,EACA,EAAgBD,EAAAA,QAAG,QAAQ,EACnB,CACR,GAAI,CAAC,EAAY,OAAOC,EAAAA,QAAK,KAAK,EAAe,MAAmB,OAAS,EAC7E,GAAI,IAAe,IAAK,OAAO,EAC/B,GAAI,EAAW,WAAW,IAAI,EAAG,OAAOA,EAAAA,QAAK,QAAQ,EAAe,EAAW,MAAM,CAAC,CAAC,EACvF,GAAI,EAAW,WAAW,GAAG,EAC3B,MAAU,MAAM,qEAAqE,EAEvF,OAAOA,EAAAA,QAAK,WAAW,CAAU,EAAIA,EAAAA,QAAK,UAAU,CAAU,EAAIA,EAAAA,QAAK,QAAQ,EAAU,CAAU,CACrG,CACA,SAAgB,EAAyB,EAA0B,CACjE,OAAOA,EAAAA,QAAK,KAAK,EAAU,EAAU,CAAW,CAClD,CACA,SAAgB,GAAe,EAAkB,EAAgBD,EAAAA,QAAG,QAAQ,EAAe,CACzF,OAAO,EACL,EAAW,EAAqB,CAAa,CAAC,EAC9C,EAAW,EAAyB,CAAQ,CAAC,CAC/C,CACF"}
@@ -0,0 +1,24 @@
1
+ import { DoomConfig, PlanningModeConfig, ResolvedVoiceConfig, VoiceConfig } from "./types.cjs";
2
+ //#region src/config.d.ts
3
+ /**
4
+ * The accepted values, in the order a picker should offer them.
5
+ *
6
+ * Exported because the config UI has to offer exactly what the parser accepts,
7
+ * and a second hand-written list in the contributing package would drift. The
8
+ * validating Sets below are built from these for the same reason.
9
+ */
10
+ declare const DOOM_PLANNING_THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
11
+ declare const DOOM_VOICE_ENGINES: readonly ["auto", "whisper-cpp", "openai-whisper", "mlx-whisper"];
12
+ declare const DOOM_VOICE_TTS_ENGINES: readonly ["macos-say"];
13
+ declare function parsePlanningModeConfig(value: unknown, filePath: string, location?: string): PlanningModeConfig;
14
+ declare function parseDoomConfig(content: string, filePath: string): DoomConfig;
15
+ declare function mergeDoomConfigs(globalConfig: DoomConfig, repositoryConfig: DoomConfig): DoomConfig;
16
+ declare function resolveVoiceConfig(config: VoiceConfig): ResolvedVoiceConfig;
17
+ declare function globalDoomConfigDirectory(homeDirectory?: string): string;
18
+ declare function globalDoomConfigPath(homeDirectory?: string): string;
19
+ declare function resolvePlanningPlansDirectory(configured: string | undefined, repoRoot: string, homeDirectory?: string): string;
20
+ declare function repositoryDoomConfigPath(repoRoot: string): string;
21
+ declare function loadDoomConfig(repoRoot: string, homeDirectory?: string): DoomConfig;
22
+ //#endregion
23
+ export { DOOM_PLANNING_THINKING_LEVELS, DOOM_VOICE_ENGINES, DOOM_VOICE_TTS_ENGINES, globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, mergeDoomConfigs, parseDoomConfig, parsePlanningModeConfig, repositoryDoomConfigPath, resolvePlanningPlansDirectory, resolveVoiceConfig };
24
+ //# sourceMappingURL=config.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.cts","names":[],"sources":["../src/config.ts"],"mappings":";;;;;;;;;cA6Da;cACA;cAMA;iBA8IG,wBACd,gBACA,kBACA,oBACC;iBAyGa,gBAAgB,iBAAiB,mBAAmB;iBAsCpD,iBAAiB,cAAc,YAAY,kBAAkB,aAAa;iBA8D1E,mBAAmB,QAAQ,cAAc;iBAkBzC,0BAA0B;iBAG1B,qBAAqB;iBAGrB,8BACd,gCACA,kBACA;iBAUc,yBAAyB;iBAGzB,eAAe,kBAAkB,yBAA+B"}
@@ -0,0 +1,24 @@
1
+ import { DoomConfig, PlanningModeConfig, ResolvedVoiceConfig, VoiceConfig } from "./types.mjs";
2
+ //#region src/config.d.ts
3
+ /**
4
+ * The accepted values, in the order a picker should offer them.
5
+ *
6
+ * Exported because the config UI has to offer exactly what the parser accepts,
7
+ * and a second hand-written list in the contributing package would drift. The
8
+ * validating Sets below are built from these for the same reason.
9
+ */
10
+ declare const DOOM_PLANNING_THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
11
+ declare const DOOM_VOICE_ENGINES: readonly ["auto", "whisper-cpp", "openai-whisper", "mlx-whisper"];
12
+ declare const DOOM_VOICE_TTS_ENGINES: readonly ["macos-say"];
13
+ declare function parsePlanningModeConfig(value: unknown, filePath: string, location?: string): PlanningModeConfig;
14
+ declare function parseDoomConfig(content: string, filePath: string): DoomConfig;
15
+ declare function mergeDoomConfigs(globalConfig: DoomConfig, repositoryConfig: DoomConfig): DoomConfig;
16
+ declare function resolveVoiceConfig(config: VoiceConfig): ResolvedVoiceConfig;
17
+ declare function globalDoomConfigDirectory(homeDirectory?: string): string;
18
+ declare function globalDoomConfigPath(homeDirectory?: string): string;
19
+ declare function resolvePlanningPlansDirectory(configured: string | undefined, repoRoot: string, homeDirectory?: string): string;
20
+ declare function repositoryDoomConfigPath(repoRoot: string): string;
21
+ declare function loadDoomConfig(repoRoot: string, homeDirectory?: string): DoomConfig;
22
+ //#endregion
23
+ export { DOOM_PLANNING_THINKING_LEVELS, DOOM_VOICE_ENGINES, DOOM_VOICE_TTS_ENGINES, globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, mergeDoomConfigs, parseDoomConfig, parsePlanningModeConfig, repositoryDoomConfigPath, resolvePlanningPlansDirectory, resolveVoiceConfig };
24
+ //# sourceMappingURL=config.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.mts","names":[],"sources":["../src/config.ts"],"mappings":";;;;;;;;;cA6Da;cACA;cAMA;iBA8IG,wBACd,gBACA,kBACA,oBACC;iBAyGa,gBAAgB,iBAAiB,mBAAmB;iBAsCpD,iBAAiB,cAAc,YAAY,kBAAkB,aAAa;iBA8D1E,mBAAmB,QAAQ,cAAc;iBAkBzC,0BAA0B;iBAG1B,qBAAqB;iBAGrB,8BACd,gCACA,kBACA;iBAUc,yBAAyB;iBAGzB,eAAe,kBAAkB,yBAA+B"}
@@ -0,0 +1,2 @@
1
+ import e from"node:fs";import t from"node:os";import n from"node:path";import{parse as r}from"yaml";const i=`.doom`,a=`config.yaml`,o=`auto`,s=[`modes`,`projectTrust`,`editor`,`voice`,`selection`],c=[`majorMode`,`domains`,`profile`],ee=[`planning`],te=[`main`,`subagents`,`plansDirectory`],ne=[`model`,`thinking`],l=[`command`],u=[`engine`,`language`,`recorder`,`adapters`,`autoCapture`],d=[`binary`,`device`],f=`whisper-cpp`,p=`openai-whisper`,m=`mlx-whisper`,h=[f,p,m],g=[`binary`,`model`],_=[`path`,`id`],v=[`model`,`startPhrases`,`stopPhrases`,`tts`],y=[`engine`,`voice`,`rate`],b=/^[^/\s]+\/[^/\s]+(?:\/[^/\s]+)*$/u,x=/[\p{P}\p{S}]+/gu,S=[`off`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`],C=[o,f,p,m],w=[`macos-say`],T=new Set(S),E=new Set([`ask`,`always`,`never`]),D=new Set(C),O=new Set(w);function k(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function A(e,t,n,r){let i=Object.keys(e).filter(e=>!t.includes(e));if(i.length>0)throw Error(`Doom config at ${r} has unsupported ${n} field(s): ${i.join(`, `)}`)}function j(e,t,n){if(e!==void 0){if(typeof e!=`string`||e.trim().length===0)throw Error(`Doom config at ${n} requires ${t} to be a non-empty string`);return e.trim()}}function M(e){for(let t of e){let e=t.codePointAt(0);if(e!==void 0&&(e<=31||e===127))return!0}return!1}function N(e,t,n,r){let i=j(e,t,n);if(i){if(i.length>r)throw Error(`Doom config at ${n} requires ${t} to be at most ${r} characters`);if(M(i))throw Error(`Doom config at ${n} does not allow control characters in ${t}`);return i}}function P(e,t){let n=`voice.autoCapture.model`,r=j(e,n,t);if(!r||!b.test(r))throw Error(`Doom config at ${t} requires ${n} in provider/model-id form`);return r}function F(e){return e.normalize(`NFKC`).toLocaleLowerCase(`en-US`).replace(x,` `).replace(/\s+/gu,` `).trim()}function I(e,t,n){if(e===void 0)return;if(!Array.isArray(e)||e.some(e=>typeof e!=`string`))throw Error(`Doom config at ${n} requires ${t} to be an array of strings`);if(e.length>16)throw Error(`Doom config at ${n} requires ${t} to contain at most 16 phrases`);let r=e.map(e=>e.trim()),i=new Set;for(let e of r){if(e.length===0)throw Error(`Doom config at ${n} requires ${t} to contain non-empty strings`);if(e.length>64)throw Error(`Doom config at ${n} requires ${t} phrases to be at most 64 characters`);if(M(e))throw Error(`Doom config at ${n} does not allow control characters in ${t}`);let r=F(e);if(!r)throw Error(`Doom config at ${n} requires ${t} to contain words`);if(i.has(r))throw Error(`Doom config at ${n} does not allow normalized duplicates in ${t}`);i.add(r)}return r}function L(e,t){let n=`voice.autoCapture.tts`;if(!k(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);if(A(e,y,n,t),typeof e.engine!=`string`||!O.has(e.engine))throw Error(`Doom config at ${t} requires ${n}.engine to be one of: ${[...O].join(`, `)}`);let r=N(e.voice,`${n}.voice`,t,64),i=e.rate;if(i!==void 0&&(typeof i!=`number`||!Number.isInteger(i)||i<80||i>500))throw Error(`Doom config at ${t} requires ${n}.rate to be an integer from 80 to 500`);return{engine:e.engine,...r?{voice:r}:{},...i===void 0?{}:{rate:i}}}function R(e,t){if(e===void 0)return;let n=`voice.autoCapture`;if(!k(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);A(e,v,n,t);let r=I(e.startPhrases,`${n}.startPhrases`,t),i=I(e.stopPhrases,`${n}.stopPhrases`,t),a=new Set((r??[]).map(F));if((i??[]).some(e=>a.has(F(e))))throw Error(`Doom config at ${t} does not allow a phrase in both startPhrases and stopPhrases`);return{model:P(e.model,t),...r?{startPhrases:r}:{},...i?{stopPhrases:i}:{},tts:L(e.tts,t)}}function z(e,t,n){if(e===void 0)return;if(!k(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);A(e,ne,t,n);let r=e.thinking;if(r!==void 0&&(typeof r!=`string`||!T.has(r)))throw Error(`Doom config at ${n} requires ${t}.thinking to be one of: ${[...T].join(`, `)}`);let i=j(e.model,`${t}.model`,n);return{...i?{model:i}:{},...r?{thinking:r}:{}}}function B(e,t,n=`modes.planning`){if(!k(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);return A(e,te,n,t),{main:z(e.main,`${n}.main`,t),subagents:z(e.subagents,`${n}.subagents`,t),plansDirectory:j(e.plansDirectory,`${n}.plansDirectory`,t)}}function V(e,t,n,r){if(!k(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);A(e,_,t,n);let i=j(e.path,`${t}.path`,n),a=j(e.id,`${t}.id`,n);if(!!i==!!a)throw Error(`Doom config at ${n} requires exactly one of ${t}.path or ${t}.id`);if(a&&!r)throw Error(`Doom config at ${n} does not support ${t}.id for whisper-cpp`);return{...i?{path:i}:{},...a?{id:a}:{}}}function H(e,t,n,r){if(e===void 0)return;if(!k(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);A(e,g,t,n);let i=j(e.binary,`${t}.binary`,n);return{...i?{binary:i}:{},model:V(e.model,`${t}.model`,n,r)}}function U(e,t){if(e===void 0)return;if(!k(e))throw Error(`Doom config at ${t} requires voice to be an object`);A(e,u,`voice`,t);let n={};if(e.engine!==void 0){if(typeof e.engine!=`string`||!D.has(e.engine))throw Error(`Doom config at ${t} requires voice.engine to be one of: ${[...D].join(`, `)}`);n.engine=e.engine}let r=j(e.language,`voice.language`,t);if(r&&(n.language=r),e.recorder!==void 0){if(!k(e.recorder))throw Error(`Doom config at ${t} requires voice.recorder to be an object`);A(e.recorder,d,`voice.recorder`,t),n.recorder={};let r=j(e.recorder.binary,`voice.recorder.binary`,t),i=j(e.recorder.device,`voice.recorder.device`,t);r&&(n.recorder.binary=r),i&&(n.recorder.device=i)}if(e.adapters!==void 0){if(!k(e.adapters))throw Error(`Doom config at ${t} requires voice.adapters to be an object`);A(e.adapters,h,`voice.adapters`,t),n.adapters={[f]:H(e.adapters[f],`voice.adapters.${f}`,t,!1),[p]:H(e.adapters[p],`voice.adapters.${p}`,t,!0),[m]:H(e.adapters[m],`voice.adapters.${m}`,t,!0)}}let i=R(e.autoCapture,t);return i&&(n.autoCapture=i),n}function W(e,t){if(e===void 0)return;if(!k(e))throw Error(`Doom config at ${t} requires selection to be an object`);A(e,c,`selection`,t);let n=e.domains;if(n!==void 0&&(!Array.isArray(n)||n.some(e=>typeof e!=`string`)))throw Error(`Doom config at ${t} requires selection.domains to be an array of strings`);return{majorMode:j(e.majorMode,`selection.majorMode`,t),domains:n,profile:j(e.profile,`selection.profile`,t)}}function G(e,t){if(e!==void 0){if(!k(e))throw Error(`Doom config at ${t} requires editor to be an object`);return A(e,l,`editor`,t),{command:j(e.command,`editor.command`,t)}}}function K(e,t){let n;try{n=r(e)??{}}catch(e){throw Error(`Could not parse Doom config at ${t}`,{cause:e})}if(!k(n))throw Error(`Doom config at ${t} must be a YAML object`);A(n,s,`root`,t);let i;if(n.modes!==void 0){if(!k(n.modes))throw Error(`Doom config at ${t} requires modes to be an object`);A(n.modes,ee,`modes`,t),i=n.modes.planning===void 0?{}:{planning:B(n.modes.planning,t)}}let a=n.projectTrust===void 0?`ask`:n.projectTrust;if(typeof a!=`string`||!E.has(a))throw Error(`Doom config at ${t} projectTrust must be ask, always, or never`);return{modes:i,projectTrust:a,editor:G(n.editor,t),voice:U(n.voice,t),selection:W(n.selection,t)}}function q(t){return e.existsSync(t)?K(e.readFileSync(t,`utf8`),t):{projectTrust:`ask`}}function J(e,t){return e||t?{...e,...t}:void 0}function Y(e,t){if(t.voice?.autoCapture)throw Error(`Repository Doom config voice.autoCapture is global-only; configure it in ~/.pi/.doom/config.yaml`);let n=e.modes?.planning,r=t.modes?.planning,i=n||r?{main:J(n?.main,r?.main),subagents:J(n?.subagents,r?.subagents),plansDirectory:r?.plansDirectory??n?.plansDirectory}:void 0,a=re(e.voice,t.voice);return{modes:i?{planning:i}:void 0,projectTrust:t.projectTrust,editor:e.editor,voice:a,selection:e.selection||t.selection?{...e.selection,...t.selection}:void 0}}function X(e,t){if(!(!e&&!t))return{...e,...t,model:t?.model??e.model}}function re(e,t){if(!(!e&&!t))return{...e,...t,recorder:e?.recorder||t?.recorder?{...e?.recorder,...t?.recorder}:void 0,adapters:e?.adapters||t?.adapters?{[f]:X(e?.adapters?.[f],t?.adapters?.[f]),[p]:X(e?.adapters?.[p],t?.adapters?.[p]),[m]:X(e?.adapters?.[m],t?.adapters?.[m])}:void 0,...e?.autoCapture?{autoCapture:e.autoCapture}:{}}}function ie(e){return{engine:e.engine??o,language:e.language??`auto`,recorder:{...e.recorder,device:e.recorder?.device??`none:default`},adapters:e.adapters??{},...e.autoCapture?{autoCapture:{...e.autoCapture,startPhrases:[...e.autoCapture.startPhrases??[]],stopPhrases:[...e.autoCapture.stopPhrases??[]],tts:{...e.autoCapture.tts}}}:{}}}function Z(e=t.homedir()){return n.join(e,`.pi`,i)}function Q(e=t.homedir()){return n.join(Z(e),a)}function ae(e,r,i=t.homedir()){if(!e)return n.join(i,`.pi`,`plans`);if(e===`~`)return i;if(e.startsWith(`~/`))return n.resolve(i,e.slice(2));if(e.startsWith(`~`))throw Error(`Doom planning plansDirectory supports only '~' or '~/' home aliases`);return n.isAbsolute(e)?n.normalize(e):n.resolve(r,e)}function $(e){return n.join(e,i,a)}function oe(e,n=t.homedir()){return Y(q(Q(n)),q($(e)))}export{S as DOOM_PLANNING_THINKING_LEVELS,C as DOOM_VOICE_ENGINES,w as DOOM_VOICE_TTS_ENGINES,Z as globalDoomConfigDirectory,Q as globalDoomConfigPath,oe as loadDoomConfig,Y as mergeDoomConfigs,K as parseDoomConfig,B as parsePlanningModeConfig,$ as repositoryDoomConfigPath,ae as resolvePlanningPlansDirectory,ie as resolveVoiceConfig};
2
+ //# sourceMappingURL=config.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.mjs","names":["parseYaml"],"sources":["../src/config.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport type {\n DoomConfig,\n DoomSelectionConfig,\n EditorConfig,\n PlanningAgentConfig,\n PlanningModeConfig,\n PlanningThinkingLevel,\n ProjectTrust,\n ResolvedVoiceConfig,\n VoiceAdapterConfig,\n VoiceAutoCaptureConfig,\n VoiceConfig,\n VoiceEngine,\n VoiceModelConfig,\n VoiceTtsConfig,\n VoiceTtsEngine,\n} from './types.ts';\n\nconst DOOM_DIR = '.doom';\nconst CONFIG_FILE = 'config.yaml';\nconst GLOBAL_CONFIG_DIR = '.pi';\nconst PLANS_DIR = 'plans';\nconst DEFAULT_PROJECT_TRUST: ProjectTrust = 'ask';\nconst DEFAULT_VOICE_ENGINE: VoiceEngine = 'auto';\nconst DEFAULT_VOICE_LANGUAGE = 'auto';\nconst DEFAULT_RECORDER_DEVICE = 'none:default';\nconst ROOT_KEYS = ['modes', 'projectTrust', 'editor', 'voice', 'selection'] as const;\nconst SELECTION_KEYS = ['majorMode', 'domains', 'profile'] as const;\nconst MODE_KEYS = ['planning'] as const;\nconst PLANNING_KEYS = ['main', 'subagents', 'plansDirectory'] as const;\nconst AGENT_KEYS = ['model', 'thinking'] as const;\nconst EDITOR_KEYS = ['command'] as const;\nconst VOICE_KEYS = ['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 = ['model', 'startPhrases', 'stopPhrases', 'tts'] as const;\nconst TTS_KEYS = ['engine', 'voice', 'rate'] as const;\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_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 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}\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)\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 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 startKeys = new Set((startPhrases ?? []).map(normalizeControlPhrase));\n if ((stopPhrases ?? []).some((phrase) => startKeys.has(normalizeControlPhrase(phrase))))\n throw new Error(`Doom config at ${filePath} does not allow a phrase in both startPhrases and stopPhrases`);\n return {\n model: parseModelReference(value.model, filePath),\n ...(startPhrases ? { startPhrases } : {}),\n ...(stopPhrases ? { stopPhrases } : {}),\n tts: parseTtsConfig(value.tts, filePath),\n };\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 = value.thinking;\n if (\n thinking !== undefined &&\n (typeof thinking !== 'string' || !THINKING_VALUES.has(thinking as PlanningThinkingLevel))\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.thinking to be one of: ${[...THINKING_VALUES].join(', ')}`,\n );\n }\n const model = optionalString(value.model, `${location}.model`, filePath);\n return { ...(model ? { model } : {}), ...(thinking ? { thinking: thinking as PlanningThinkingLevel } : {}) };\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.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}\nexport function parseDoomConfig(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 ? {} : { planning: parsePlanningModeConfig(parsed.modes.planning, filePath) };\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 projectTrust: trust as ProjectTrust,\n editor: parseEditor(parsed.editor, filePath),\n voice: parseVoice(parsed.voice, filePath),\n selection: parseSelection(parsed.selection, filePath),\n };\n}\nfunction readConfig(filePath: string): DoomConfig {\n return fs.existsSync(filePath)\n ? parseDoomConfig(fs.readFileSync(filePath, 'utf8'), filePath)\n : { projectTrust: DEFAULT_PROJECT_TRUST };\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?.autoCapture)\n throw new Error('Repository Doom config voice.autoCapture is global-only; configure it in ~/.pi/.doom/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 voice = mergeVoice(globalConfig.voice, repositoryConfig.voice);\n return {\n modes: planning ? { planning } : undefined,\n projectTrust: repositoryConfig.projectTrust,\n editor: globalConfig.editor,\n voice,\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 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}\nexport function resolveVoiceConfig(config: VoiceConfig): ResolvedVoiceConfig {\n return {\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 tts: { ...config.autoCapture.tts },\n },\n }\n : {}),\n };\n}\nexport function globalDoomConfigDirectory(homeDirectory = os.homedir()): string {\n return path.join(homeDirectory, GLOBAL_CONFIG_DIR, DOOM_DIR);\n}\nexport function globalDoomConfigPath(homeDirectory = os.homedir()): string {\n return path.join(globalDoomConfigDirectory(homeDirectory), CONFIG_FILE);\n}\nexport function resolvePlanningPlansDirectory(\n configured: string | undefined,\n repoRoot: string,\n homeDirectory = os.homedir(),\n): string {\n if (!configured) return path.join(homeDirectory, GLOBAL_CONFIG_DIR, PLANS_DIR);\n if (configured === '~') return homeDirectory;\n if (configured.startsWith('~/')) return path.resolve(homeDirectory, configured.slice(2));\n if (configured.startsWith('~')) {\n throw new Error(\"Doom planning plansDirectory supports only '~' or '~/' home aliases\");\n }\n return path.isAbsolute(configured) ? path.normalize(configured) : path.resolve(repoRoot, configured);\n}\nexport function repositoryDoomConfigPath(repoRoot: string): string {\n return path.join(repoRoot, DOOM_DIR, CONFIG_FILE);\n}\nexport function loadDoomConfig(repoRoot: string, homeDirectory = os.homedir()): DoomConfig {\n return mergeDoomConfigs(\n readConfig(globalDoomConfigPath(homeDirectory)),\n readConfig(repositoryDoomConfigPath(repoRoot)),\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./config.cjs");let n=require("node:path");n=e.__toESM(n,1);let r=require("yaml"),i=require("node:crypto"),a=require("node:fs/promises");a=e.__toESM(a,1);async function o(e){try{return await a.default.readFile(e,`utf8`)}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return``;throw e}}function s(e){if(typeof e!=`object`||!e)return!1;let t=e.items;return Array.isArray(t)&&t.length===0}function c(e,t){for(let n=t.length-1;n>0;--n){let r=t.slice(0,n);if(e.hasIn(r)){if(!s(e.getIn(r,!0)))return;e.deleteIn(r)}}}async function l(e,s,l={}){if(s.length===0)return;let u=await o(e),d=(0,r.parseDocument)(u,{keepSourceTokens:!0});if(d.errors.length>0)throw Error(`Could not parse Doom config at ${e}`);for(let e of s){let t=[...e.keyPath];if(e.value!==void 0){d.setIn(t,e.value);continue}d.hasIn(t)&&(d.deleteIn(t),l.prune!==!1&&c(d,t))}let f=d.toString();t.parseDoomConfig(f,e),await a.default.mkdir(n.default.dirname(e),{mode:448,recursive:!0});let p=`${e}.${process.pid}.${(0,i.randomUUID)()}.tmp`;try{await a.default.writeFile(p,f,{encoding:`utf8`,mode:384}),await a.default.rename(p,e)}catch(e){throw await a.default.rm(p,{force:!0}),e}}async function u(e,t,n){await l(e,[{keyPath:t,value:n}])}async function d(e,t){await l(e,[{keyPath:t}])}exports.setDoomConfigValue=u,exports.unsetDoomConfigValue=d,exports.writeDoomConfigValues=l;
2
+ //# sourceMappingURL=configWriter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configWriter.cjs","names":["fs","parseDocument","path","randomUUID"],"sources":["../src/configWriter.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';\nimport { parseDocument } from 'yaml';\nimport { parseDoomConfig } from './config.ts';\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\n\nexport interface DoomConfigEdit {\n /** Key path into the document, e.g. `['voice', 'adapters', 'mlx-whisper', 'model', 'id']`. */\n readonly keyPath: readonly string[];\n /** The value to write. `undefined` removes the key. */\n readonly value?: 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\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 await fs.mkdir(path.dirname(filePath), { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await fs.writeFile(temporaryPath, output, { encoding: 'utf8', mode: PRIVATE_FILE_MODE });\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(filePath: string, keyPath: readonly string[], value: string): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath, value }]);\n}\n\nexport async function unsetDoomConfigValue(filePath: string, keyPath: readonly string[]): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath }]);\n}\n"],"mappings":"2RAkDA,eAAe,EAAc,EAAmC,CAC9D,GAAI,CACF,OAAO,MAAMA,EAAAA,QAAG,SAAS,EAAU,MAAM,CAC3C,OAAS,EAAO,CACd,GAAI,aAAiB,OAAS,SAAU,GAAS,EAAM,OAAS,SAAU,MAAO,GACjF,MAAM,CACR,CACF,CAEA,SAAS,EAAkB,EAAyB,CAClD,GAAsB,OAAO,GAAU,WAAnC,EAA6C,MAAO,GACxD,IAAM,EAAS,EAA8B,MAC7C,OAAO,MAAM,QAAQ,CAAK,GAAK,EAAM,SAAW,CAClD,CAGA,SAAS,EAAoB,EAA4C,EAAkC,CACzG,IAAK,IAAI,EAAQ,EAAQ,OAAS,EAAG,EAAQ,EAAG,IAAY,CAC1D,IAAM,EAAW,EAAQ,MAAM,EAAG,CAAK,EAClC,KAAS,MAAM,CAAQ,EAC5B,IAAI,CAAC,EAAkB,EAAS,MAAM,EAAU,EAAI,CAAC,EAAG,OACxD,EAAS,SAAS,CAAQ,CAD8B,CAE1D,CACF,CASA,eAAsB,EACpB,EACA,EACA,EAAkC,CAAC,EACpB,CACf,GAAI,EAAM,SAAW,EAAG,OACxB,IAAM,EAAU,MAAM,EAAc,CAAQ,EAItC,GAAA,EAAWC,EAAAA,cAAAA,CAAc,EAAS,CAAE,iBAAkB,EAAK,CAAC,EAClE,GAAI,EAAS,OAAO,OAAS,EAAG,MAAU,MAAM,kCAAkC,GAAU,EAE5F,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAU,CAAC,GAAG,EAAK,OAAO,EAChC,GAAI,EAAK,QAAU,IAAA,GAAW,CAC5B,EAAS,MAAM,EAAS,EAAK,KAAK,EAClC,QACF,CAGK,EAAS,MAAM,CAAO,IAC3B,EAAS,SAAS,CAAO,EACrB,EAAQ,QAAU,IAAO,EAAoB,EAAU,CAAO,EACpE,CAEA,IAAM,EAAS,EAAS,SAAS,EACjC,EAAA,gBAAgB,EAAQ,CAAQ,EAEhC,MAAMD,EAAAA,QAAG,MAAME,EAAAA,QAAK,QAAQ,CAAQ,EAAG,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EACxF,IAAM,EAAgB,GAAG,EAAS,GAAG,QAAQ,IAAI,IAAA,EAAGC,EAAAA,WAAAA,CAAW,EAAE,MACjE,GAAI,CACF,MAAMH,EAAAA,QAAG,UAAU,EAAe,EAAQ,CAAE,SAAU,OAAQ,KAAM,GAAkB,CAAC,EACvF,MAAMA,EAAAA,QAAG,OAAO,EAAe,CAAQ,CACzC,OAAS,EAAO,CAEd,MADA,MAAMA,EAAAA,QAAG,GAAG,EAAe,CAAE,MAAO,EAAK,CAAC,EACpC,CACR,CACF,CAEA,eAAsB,EAAmB,EAAkB,EAA4B,EAA8B,CACnH,MAAM,EAAsB,EAAU,CAAC,CAAE,UAAS,OAAM,CAAC,CAAC,CAC5D,CAEA,eAAsB,EAAqB,EAAkB,EAA2C,CACtG,MAAM,EAAsB,EAAU,CAAC,CAAE,SAAQ,CAAC,CAAC,CACrD"}