terminal-pilot 0.0.30 → 0.0.31

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 (328) hide show
  1. package/dist/cli.js +1375 -435
  2. package/dist/cli.js.map +4 -4
  3. package/dist/commands/close-session.js +485 -6
  4. package/dist/commands/close-session.js.map +4 -4
  5. package/dist/commands/create-session.js +485 -6
  6. package/dist/commands/create-session.js.map +4 -4
  7. package/dist/commands/fill.js +485 -6
  8. package/dist/commands/fill.js.map +4 -4
  9. package/dist/commands/get-session.js +485 -6
  10. package/dist/commands/get-session.js.map +4 -4
  11. package/dist/commands/index.js +626 -44
  12. package/dist/commands/index.js.map +4 -4
  13. package/dist/commands/install.js +622 -40
  14. package/dist/commands/install.js.map +4 -4
  15. package/dist/commands/installer.js +68 -17
  16. package/dist/commands/installer.js.map +4 -4
  17. package/dist/commands/list-sessions.js +485 -6
  18. package/dist/commands/list-sessions.js.map +4 -4
  19. package/dist/commands/press-key.js +485 -6
  20. package/dist/commands/press-key.js.map +4 -4
  21. package/dist/commands/read-history.js +485 -6
  22. package/dist/commands/read-history.js.map +4 -4
  23. package/dist/commands/read-screen.js +485 -6
  24. package/dist/commands/read-screen.js.map +4 -4
  25. package/dist/commands/resize.js +485 -6
  26. package/dist/commands/resize.js.map +4 -4
  27. package/dist/commands/screenshot.js +485 -6
  28. package/dist/commands/screenshot.js.map +4 -4
  29. package/dist/commands/send-signal.js +485 -6
  30. package/dist/commands/send-signal.js.map +4 -4
  31. package/dist/commands/type.js +485 -6
  32. package/dist/commands/type.js.map +4 -4
  33. package/dist/commands/uninstall.js +550 -20
  34. package/dist/commands/uninstall.js.map +4 -4
  35. package/dist/commands/wait-for-exit.js +485 -6
  36. package/dist/commands/wait-for-exit.js.map +4 -4
  37. package/dist/commands/wait-for.js +485 -6
  38. package/dist/commands/wait-for.js.map +4 -4
  39. package/dist/testing/cli-repl.js +1375 -435
  40. package/dist/testing/cli-repl.js.map +4 -4
  41. package/dist/testing/qa-cli.js +1385 -445
  42. package/dist/testing/qa-cli.js.map +4 -4
  43. package/node_modules/@poe-code/agent-defs/README.md +35 -0
  44. package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.d.ts +2 -0
  45. package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.js +21 -0
  46. package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.d.ts +2 -0
  47. package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.js +18 -0
  48. package/node_modules/@poe-code/agent-defs/dist/agents/codex.d.ts +2 -0
  49. package/node_modules/@poe-code/agent-defs/dist/agents/codex.js +25 -0
  50. package/node_modules/@poe-code/agent-defs/dist/agents/cursor.d.ts +2 -0
  51. package/node_modules/@poe-code/agent-defs/dist/agents/cursor.js +15 -0
  52. package/node_modules/@poe-code/agent-defs/dist/agents/gemini-cli.d.ts +2 -0
  53. package/node_modules/@poe-code/agent-defs/dist/agents/gemini-cli.js +16 -0
  54. package/node_modules/@poe-code/agent-defs/dist/agents/goose.d.ts +2 -0
  55. package/node_modules/@poe-code/agent-defs/dist/agents/goose.js +16 -0
  56. package/node_modules/@poe-code/agent-defs/dist/agents/index.d.ts +9 -0
  57. package/node_modules/@poe-code/agent-defs/dist/agents/index.js +9 -0
  58. package/node_modules/@poe-code/agent-defs/dist/agents/kimi.d.ts +2 -0
  59. package/node_modules/@poe-code/agent-defs/dist/agents/kimi.js +16 -0
  60. package/node_modules/@poe-code/agent-defs/dist/agents/opencode.d.ts +2 -0
  61. package/node_modules/@poe-code/agent-defs/dist/agents/opencode.js +20 -0
  62. package/node_modules/@poe-code/agent-defs/dist/agents/poe-agent.d.ts +2 -0
  63. package/node_modules/@poe-code/agent-defs/dist/agents/poe-agent.js +14 -0
  64. package/node_modules/@poe-code/agent-defs/dist/index.d.ts +5 -0
  65. package/node_modules/@poe-code/agent-defs/dist/index.js +3 -0
  66. package/node_modules/@poe-code/agent-defs/dist/registry.d.ts +3 -0
  67. package/node_modules/@poe-code/agent-defs/dist/registry.js +45 -0
  68. package/node_modules/@poe-code/agent-defs/dist/specifier.d.ts +7 -0
  69. package/node_modules/@poe-code/agent-defs/dist/specifier.js +40 -0
  70. package/node_modules/@poe-code/agent-defs/dist/types.d.ts +28 -0
  71. package/node_modules/@poe-code/agent-defs/dist/types.js +1 -0
  72. package/node_modules/@poe-code/agent-defs/package.json +20 -0
  73. package/node_modules/@poe-code/agent-skill-config/dist/apply.js +2 -1
  74. package/node_modules/@poe-code/agent-skill-config/dist/bridge-active-skills.d.ts +1 -0
  75. package/node_modules/@poe-code/agent-skill-config/dist/bridge-active-skills.js +37 -18
  76. package/node_modules/@poe-code/agent-skill-config/dist/resolve-skill-reference.js +8 -4
  77. package/node_modules/@poe-code/agent-skill-config/package.json +1 -5
  78. package/node_modules/@poe-code/config-mutations/README.md +55 -0
  79. package/node_modules/@poe-code/config-mutations/dist/error-codes.d.ts +1 -0
  80. package/node_modules/@poe-code/config-mutations/dist/error-codes.js +6 -0
  81. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.d.ts +6 -0
  82. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +829 -0
  83. package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.d.ts +17 -0
  84. package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.js +64 -0
  85. package/node_modules/@poe-code/config-mutations/dist/execution/run-mutations.d.ts +7 -0
  86. package/node_modules/@poe-code/config-mutations/dist/execution/run-mutations.js +39 -0
  87. package/node_modules/@poe-code/config-mutations/dist/formats/index.d.ts +13 -0
  88. package/node_modules/@poe-code/config-mutations/dist/formats/index.js +49 -0
  89. package/node_modules/@poe-code/config-mutations/dist/formats/json.d.ts +32 -0
  90. package/node_modules/@poe-code/config-mutations/dist/formats/json.js +171 -0
  91. package/node_modules/@poe-code/config-mutations/dist/formats/object.d.ts +4 -0
  92. package/node_modules/@poe-code/config-mutations/dist/formats/object.js +27 -0
  93. package/node_modules/@poe-code/config-mutations/dist/formats/toml.d.ts +2 -0
  94. package/node_modules/@poe-code/config-mutations/dist/formats/toml.js +75 -0
  95. package/node_modules/@poe-code/config-mutations/dist/formats/yaml.d.ts +2 -0
  96. package/node_modules/@poe-code/config-mutations/dist/formats/yaml.js +76 -0
  97. package/node_modules/@poe-code/config-mutations/dist/fs-utils.d.ts +18 -0
  98. package/node_modules/@poe-code/config-mutations/dist/fs-utils.js +43 -0
  99. package/node_modules/@poe-code/config-mutations/dist/index.d.ts +8 -0
  100. package/node_modules/@poe-code/config-mutations/dist/index.js +8 -0
  101. package/node_modules/@poe-code/config-mutations/dist/mutations/config-mutation.d.ts +47 -0
  102. package/node_modules/@poe-code/config-mutations/dist/mutations/config-mutation.js +34 -0
  103. package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.d.ts +62 -0
  104. package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.js +55 -0
  105. package/node_modules/@poe-code/config-mutations/dist/mutations/template-mutation.d.ts +40 -0
  106. package/node_modules/@poe-code/config-mutations/dist/mutations/template-mutation.js +32 -0
  107. package/node_modules/@poe-code/config-mutations/dist/template/render.d.ts +6 -0
  108. package/node_modules/@poe-code/config-mutations/dist/template/render.js +8 -0
  109. package/node_modules/@poe-code/config-mutations/dist/testing/format-utils.d.ts +7 -0
  110. package/node_modules/@poe-code/config-mutations/dist/testing/format-utils.js +21 -0
  111. package/node_modules/@poe-code/config-mutations/dist/testing/index.d.ts +3 -0
  112. package/node_modules/@poe-code/config-mutations/dist/testing/index.js +2 -0
  113. package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.d.ts +25 -0
  114. package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.js +194 -0
  115. package/node_modules/@poe-code/config-mutations/dist/types.d.ts +168 -0
  116. package/node_modules/@poe-code/config-mutations/dist/types.js +6 -0
  117. package/node_modules/@poe-code/config-mutations/package.json +29 -0
  118. package/node_modules/@poe-code/frontmatter/README.md +35 -0
  119. package/node_modules/@poe-code/frontmatter/dist/fences.d.ts +25 -0
  120. package/node_modules/@poe-code/frontmatter/dist/fences.js +100 -0
  121. package/node_modules/@poe-code/frontmatter/dist/index.d.ts +3 -0
  122. package/node_modules/@poe-code/frontmatter/dist/index.js +3 -0
  123. package/node_modules/@poe-code/frontmatter/dist/parse.d.ts +20 -0
  124. package/node_modules/@poe-code/frontmatter/dist/parse.js +209 -0
  125. package/node_modules/@poe-code/frontmatter/dist/stringify.d.ts +1 -0
  126. package/node_modules/@poe-code/frontmatter/dist/stringify.js +48 -0
  127. package/node_modules/@poe-code/frontmatter/package.json +25 -0
  128. package/node_modules/toolcraft-design/README.md +160 -0
  129. package/node_modules/toolcraft-design/dist/acp/components.d.ts +12 -0
  130. package/node_modules/toolcraft-design/dist/acp/components.js +137 -0
  131. package/node_modules/toolcraft-design/dist/acp/index.d.ts +3 -0
  132. package/node_modules/toolcraft-design/dist/acp/index.js +2 -0
  133. package/node_modules/toolcraft-design/dist/acp/writer.d.ts +13 -0
  134. package/node_modules/toolcraft-design/dist/acp/writer.js +21 -0
  135. package/node_modules/toolcraft-design/dist/components/browser.d.ts +15 -0
  136. package/node_modules/toolcraft-design/dist/components/browser.js +31 -0
  137. package/node_modules/toolcraft-design/dist/components/catalog.d.ts +26 -0
  138. package/node_modules/toolcraft-design/dist/components/catalog.js +95 -0
  139. package/node_modules/toolcraft-design/dist/components/color.d.ts +31 -0
  140. package/node_modules/toolcraft-design/dist/components/color.js +102 -0
  141. package/node_modules/toolcraft-design/dist/components/command-errors.d.ts +16 -0
  142. package/node_modules/toolcraft-design/dist/components/command-errors.js +23 -0
  143. package/node_modules/toolcraft-design/dist/components/detail-card.d.ts +22 -0
  144. package/node_modules/toolcraft-design/dist/components/detail-card.js +45 -0
  145. package/node_modules/toolcraft-design/dist/components/help-formatter-plain.d.ts +5 -0
  146. package/node_modules/toolcraft-design/dist/components/help-formatter-plain.js +132 -0
  147. package/node_modules/toolcraft-design/dist/components/help-formatter.d.ts +33 -0
  148. package/node_modules/toolcraft-design/dist/components/help-formatter.js +213 -0
  149. package/node_modules/toolcraft-design/dist/components/index.d.ts +16 -0
  150. package/node_modules/toolcraft-design/dist/components/index.js +10 -0
  151. package/node_modules/toolcraft-design/dist/components/logger.d.ts +11 -0
  152. package/node_modules/toolcraft-design/dist/components/logger.js +60 -0
  153. package/node_modules/toolcraft-design/dist/components/symbols.d.ts +12 -0
  154. package/node_modules/toolcraft-design/dist/components/symbols.js +71 -0
  155. package/node_modules/toolcraft-design/dist/components/table.d.ts +15 -0
  156. package/node_modules/toolcraft-design/dist/components/table.js +302 -0
  157. package/node_modules/toolcraft-design/dist/components/template.d.ts +10 -0
  158. package/node_modules/toolcraft-design/dist/components/template.js +437 -0
  159. package/node_modules/toolcraft-design/dist/components/text.d.ts +16 -0
  160. package/node_modules/toolcraft-design/dist/components/text.js +145 -0
  161. package/node_modules/toolcraft-design/dist/dashboard/ansi.d.ts +18 -0
  162. package/node_modules/toolcraft-design/dist/dashboard/ansi.js +343 -0
  163. package/node_modules/toolcraft-design/dist/dashboard/buffer.d.ts +25 -0
  164. package/node_modules/toolcraft-design/dist/dashboard/buffer.js +219 -0
  165. package/node_modules/toolcraft-design/dist/dashboard/components/border.d.ts +9 -0
  166. package/node_modules/toolcraft-design/dist/dashboard/components/border.js +123 -0
  167. package/node_modules/toolcraft-design/dist/dashboard/components/footer.d.ts +8 -0
  168. package/node_modules/toolcraft-design/dist/dashboard/components/footer.js +56 -0
  169. package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.d.ts +12 -0
  170. package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.js +268 -0
  171. package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.d.ts +7 -0
  172. package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.js +107 -0
  173. package/node_modules/toolcraft-design/dist/dashboard/dashboard.d.ts +20 -0
  174. package/node_modules/toolcraft-design/dist/dashboard/dashboard.js +167 -0
  175. package/node_modules/toolcraft-design/dist/dashboard/demo.d.ts +13 -0
  176. package/node_modules/toolcraft-design/dist/dashboard/demo.js +145 -0
  177. package/node_modules/toolcraft-design/dist/dashboard/index.d.ts +8 -0
  178. package/node_modules/toolcraft-design/dist/dashboard/index.js +4 -0
  179. package/node_modules/toolcraft-design/dist/dashboard/keymap.d.ts +8 -0
  180. package/node_modules/toolcraft-design/dist/dashboard/keymap.js +233 -0
  181. package/node_modules/toolcraft-design/dist/dashboard/layout.d.ts +25 -0
  182. package/node_modules/toolcraft-design/dist/dashboard/layout.js +79 -0
  183. package/node_modules/toolcraft-design/dist/dashboard/should-use-dashboard.d.ts +10 -0
  184. package/node_modules/toolcraft-design/dist/dashboard/should-use-dashboard.js +7 -0
  185. package/node_modules/toolcraft-design/dist/dashboard/snapshot.d.ts +10 -0
  186. package/node_modules/toolcraft-design/dist/dashboard/snapshot.js +68 -0
  187. package/node_modules/toolcraft-design/dist/dashboard/store.d.ts +8 -0
  188. package/node_modules/toolcraft-design/dist/dashboard/store.js +51 -0
  189. package/node_modules/toolcraft-design/dist/dashboard/terminal-width.d.ts +4 -0
  190. package/node_modules/toolcraft-design/dist/dashboard/terminal-width.js +71 -0
  191. package/node_modules/toolcraft-design/dist/dashboard/terminal.d.ts +37 -0
  192. package/node_modules/toolcraft-design/dist/dashboard/terminal.js +264 -0
  193. package/node_modules/toolcraft-design/dist/dashboard/types.d.ts +38 -0
  194. package/node_modules/toolcraft-design/dist/dashboard/types.js +1 -0
  195. package/node_modules/toolcraft-design/dist/explorer/actions.d.ts +16 -0
  196. package/node_modules/toolcraft-design/dist/explorer/actions.js +39 -0
  197. package/node_modules/toolcraft-design/dist/explorer/demo.d.ts +13 -0
  198. package/node_modules/toolcraft-design/dist/explorer/demo.js +297 -0
  199. package/node_modules/toolcraft-design/dist/explorer/events.d.ts +67 -0
  200. package/node_modules/toolcraft-design/dist/explorer/events.js +1 -0
  201. package/node_modules/toolcraft-design/dist/explorer/filter.d.ts +10 -0
  202. package/node_modules/toolcraft-design/dist/explorer/filter.js +95 -0
  203. package/node_modules/toolcraft-design/dist/explorer/index.d.ts +8 -0
  204. package/node_modules/toolcraft-design/dist/explorer/index.js +8 -0
  205. package/node_modules/toolcraft-design/dist/explorer/jobs.d.ts +7 -0
  206. package/node_modules/toolcraft-design/dist/explorer/jobs.js +59 -0
  207. package/node_modules/toolcraft-design/dist/explorer/keymap.d.ts +21 -0
  208. package/node_modules/toolcraft-design/dist/explorer/keymap.js +376 -0
  209. package/node_modules/toolcraft-design/dist/explorer/layout.d.ts +20 -0
  210. package/node_modules/toolcraft-design/dist/explorer/layout.js +73 -0
  211. package/node_modules/toolcraft-design/dist/explorer/reducer.d.ts +9 -0
  212. package/node_modules/toolcraft-design/dist/explorer/reducer.js +782 -0
  213. package/node_modules/toolcraft-design/dist/explorer/render/detail.d.ts +4 -0
  214. package/node_modules/toolcraft-design/dist/explorer/render/detail.js +97 -0
  215. package/node_modules/toolcraft-design/dist/explorer/render/footer.d.ts +4 -0
  216. package/node_modules/toolcraft-design/dist/explorer/render/footer.js +73 -0
  217. package/node_modules/toolcraft-design/dist/explorer/render/header.d.ts +4 -0
  218. package/node_modules/toolcraft-design/dist/explorer/render/header.js +49 -0
  219. package/node_modules/toolcraft-design/dist/explorer/render/index.d.ts +8 -0
  220. package/node_modules/toolcraft-design/dist/explorer/render/index.js +53 -0
  221. package/node_modules/toolcraft-design/dist/explorer/render/list.d.ts +4 -0
  222. package/node_modules/toolcraft-design/dist/explorer/render/list.js +116 -0
  223. package/node_modules/toolcraft-design/dist/explorer/render/modal.d.ts +3 -0
  224. package/node_modules/toolcraft-design/dist/explorer/render/modal.js +87 -0
  225. package/node_modules/toolcraft-design/dist/explorer/render/test-fixtures.d.ts +8 -0
  226. package/node_modules/toolcraft-design/dist/explorer/render/test-fixtures.js +156 -0
  227. package/node_modules/toolcraft-design/dist/explorer/render/text.d.ts +12 -0
  228. package/node_modules/toolcraft-design/dist/explorer/render/text.js +81 -0
  229. package/node_modules/toolcraft-design/dist/explorer/runtime.d.ts +2 -0
  230. package/node_modules/toolcraft-design/dist/explorer/runtime.js +340 -0
  231. package/node_modules/toolcraft-design/dist/explorer/runtime.test-helpers.d.ts +50 -0
  232. package/node_modules/toolcraft-design/dist/explorer/runtime.test-helpers.js +101 -0
  233. package/node_modules/toolcraft-design/dist/explorer/state.d.ts +137 -0
  234. package/node_modules/toolcraft-design/dist/explorer/state.js +86 -0
  235. package/node_modules/toolcraft-design/dist/explorer/theme.d.ts +27 -0
  236. package/node_modules/toolcraft-design/dist/explorer/theme.js +29 -0
  237. package/node_modules/toolcraft-design/dist/index.d.ts +50 -0
  238. package/node_modules/toolcraft-design/dist/index.js +43 -0
  239. package/node_modules/toolcraft-design/dist/internal/color-support.d.ts +9 -0
  240. package/node_modules/toolcraft-design/dist/internal/color-support.js +12 -0
  241. package/node_modules/toolcraft-design/dist/internal/output-format.d.ts +6 -0
  242. package/node_modules/toolcraft-design/dist/internal/output-format.js +22 -0
  243. package/node_modules/toolcraft-design/dist/internal/strip-ansi.d.ts +1 -0
  244. package/node_modules/toolcraft-design/dist/internal/strip-ansi.js +50 -0
  245. package/node_modules/toolcraft-design/dist/internal/theme-detect.d.ts +12 -0
  246. package/node_modules/toolcraft-design/dist/internal/theme-detect.js +71 -0
  247. package/node_modules/toolcraft-design/dist/internal/theme-state.d.ts +12 -0
  248. package/node_modules/toolcraft-design/dist/internal/theme-state.js +35 -0
  249. package/node_modules/toolcraft-design/dist/prompts/index.d.ts +91 -0
  250. package/node_modules/toolcraft-design/dist/prompts/index.js +137 -0
  251. package/node_modules/toolcraft-design/dist/prompts/interactive/cancel-symbol.d.ts +2 -0
  252. package/node_modules/toolcraft-design/dist/prompts/interactive/cancel-symbol.js +4 -0
  253. package/node_modules/toolcraft-design/dist/prompts/interactive/confirm.d.ts +9 -0
  254. package/node_modules/toolcraft-design/dist/prompts/interactive/confirm.js +47 -0
  255. package/node_modules/toolcraft-design/dist/prompts/interactive/core.d.ts +55 -0
  256. package/node_modules/toolcraft-design/dist/prompts/interactive/core.js +274 -0
  257. package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.d.ts +20 -0
  258. package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.js +53 -0
  259. package/node_modules/toolcraft-design/dist/prompts/interactive/index.d.ts +6 -0
  260. package/node_modules/toolcraft-design/dist/prompts/interactive/index.js +6 -0
  261. package/node_modules/toolcraft-design/dist/prompts/interactive/keys.d.ts +2 -0
  262. package/node_modules/toolcraft-design/dist/prompts/interactive/keys.js +28 -0
  263. package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.d.ts +13 -0
  264. package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.js +131 -0
  265. package/node_modules/toolcraft-design/dist/prompts/interactive/pagination.d.ts +10 -0
  266. package/node_modules/toolcraft-design/dist/prompts/interactive/pagination.js +52 -0
  267. package/node_modules/toolcraft-design/dist/prompts/interactive/password.d.ts +10 -0
  268. package/node_modules/toolcraft-design/dist/prompts/interactive/password.js +55 -0
  269. package/node_modules/toolcraft-design/dist/prompts/interactive/select.d.ts +18 -0
  270. package/node_modules/toolcraft-design/dist/prompts/interactive/select.js +89 -0
  271. package/node_modules/toolcraft-design/dist/prompts/interactive/test-helpers.d.ts +21 -0
  272. package/node_modules/toolcraft-design/dist/prompts/interactive/test-helpers.js +32 -0
  273. package/node_modules/toolcraft-design/dist/prompts/interactive/text.d.ts +12 -0
  274. package/node_modules/toolcraft-design/dist/prompts/interactive/text.js +60 -0
  275. package/node_modules/toolcraft-design/dist/prompts/interactive/wrap.d.ts +4 -0
  276. package/node_modules/toolcraft-design/dist/prompts/interactive/wrap.js +18 -0
  277. package/node_modules/toolcraft-design/dist/prompts/primitives/cancel.d.ts +2 -0
  278. package/node_modules/toolcraft-design/dist/prompts/primitives/cancel.js +9 -0
  279. package/node_modules/toolcraft-design/dist/prompts/primitives/intro.d.ts +1 -0
  280. package/node_modules/toolcraft-design/dist/prompts/primitives/intro.js +16 -0
  281. package/node_modules/toolcraft-design/dist/prompts/primitives/log.d.ts +18 -0
  282. package/node_modules/toolcraft-design/dist/prompts/primitives/log.js +104 -0
  283. package/node_modules/toolcraft-design/dist/prompts/primitives/note.d.ts +1 -0
  284. package/node_modules/toolcraft-design/dist/prompts/primitives/note.js +39 -0
  285. package/node_modules/toolcraft-design/dist/prompts/primitives/outro.d.ts +1 -0
  286. package/node_modules/toolcraft-design/dist/prompts/primitives/outro.js +16 -0
  287. package/node_modules/toolcraft-design/dist/prompts/primitives/spinner.d.ts +6 -0
  288. package/node_modules/toolcraft-design/dist/prompts/primitives/spinner.js +74 -0
  289. package/node_modules/toolcraft-design/dist/prompts/theme.d.ts +11 -0
  290. package/node_modules/toolcraft-design/dist/prompts/theme.js +15 -0
  291. package/node_modules/toolcraft-design/dist/static/index.d.ts +4 -0
  292. package/node_modules/toolcraft-design/dist/static/index.js +2 -0
  293. package/node_modules/toolcraft-design/dist/static/menu.d.ts +11 -0
  294. package/node_modules/toolcraft-design/dist/static/menu.js +42 -0
  295. package/node_modules/toolcraft-design/dist/static/spinner.d.ts +14 -0
  296. package/node_modules/toolcraft-design/dist/static/spinner.js +52 -0
  297. package/node_modules/toolcraft-design/dist/terminal-markdown/ast.d.ts +92 -0
  298. package/node_modules/toolcraft-design/dist/terminal-markdown/ast.js +1 -0
  299. package/node_modules/toolcraft-design/dist/terminal-markdown/demo-content.d.ts +2 -0
  300. package/node_modules/toolcraft-design/dist/terminal-markdown/demo-content.js +139 -0
  301. package/node_modules/toolcraft-design/dist/terminal-markdown/index.d.ts +6 -0
  302. package/node_modules/toolcraft-design/dist/terminal-markdown/index.js +8 -0
  303. package/node_modules/toolcraft-design/dist/terminal-markdown/parser/block.d.ts +7 -0
  304. package/node_modules/toolcraft-design/dist/terminal-markdown/parser/block.js +1495 -0
  305. package/node_modules/toolcraft-design/dist/terminal-markdown/parser/frontmatter.d.ts +8 -0
  306. package/node_modules/toolcraft-design/dist/terminal-markdown/parser/frontmatter.js +36 -0
  307. package/node_modules/toolcraft-design/dist/terminal-markdown/parser/inline.d.ts +10 -0
  308. package/node_modules/toolcraft-design/dist/terminal-markdown/parser/inline.js +1190 -0
  309. package/node_modules/toolcraft-design/dist/terminal-markdown/parser.d.ts +5 -0
  310. package/node_modules/toolcraft-design/dist/terminal-markdown/parser.js +42 -0
  311. package/node_modules/toolcraft-design/dist/terminal-markdown/renderer.d.ts +6 -0
  312. package/node_modules/toolcraft-design/dist/terminal-markdown/renderer.js +615 -0
  313. package/node_modules/toolcraft-design/dist/terminal-markdown/testing/theme-render-fixture.d.ts +1 -0
  314. package/node_modules/toolcraft-design/dist/terminal-markdown/testing/theme-render-fixture.js +27 -0
  315. package/node_modules/toolcraft-design/dist/tokens/brand.d.ts +5 -0
  316. package/node_modules/toolcraft-design/dist/tokens/brand.js +5 -0
  317. package/node_modules/toolcraft-design/dist/tokens/colors.d.ts +37 -0
  318. package/node_modules/toolcraft-design/dist/tokens/colors.js +85 -0
  319. package/node_modules/toolcraft-design/dist/tokens/index.d.ts +6 -0
  320. package/node_modules/toolcraft-design/dist/tokens/index.js +5 -0
  321. package/node_modules/toolcraft-design/dist/tokens/spacing.d.ts +6 -0
  322. package/node_modules/toolcraft-design/dist/tokens/spacing.js +6 -0
  323. package/node_modules/toolcraft-design/dist/tokens/typography.d.ts +7 -0
  324. package/node_modules/toolcraft-design/dist/tokens/typography.js +8 -0
  325. package/node_modules/toolcraft-design/dist/tokens/widths.d.ts +5 -0
  326. package/node_modules/toolcraft-design/dist/tokens/widths.js +5 -0
  327. package/node_modules/toolcraft-design/package.json +44 -0
  328. package/package.json +15 -5
@@ -0,0 +1,829 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import path from "node:path";
3
+ import { renderTemplate } from "toolcraft-design";
4
+ import { getConfigFormat, detectFormat } from "../formats/index.js";
5
+ import { cloneConfigObject, setConfigEntry } from "../formats/object.js";
6
+ import { resolvePath } from "./path-utils.js";
7
+ import { isNotFound, readFileIfExists, pathExists, createTimestamp } from "../fs-utils.js";
8
+ import { hasOwnErrorCode } from "../error-codes.js";
9
+ // ============================================================================
10
+ // Helper Functions
11
+ // ============================================================================
12
+ function resolveValue(resolver, options) {
13
+ if (typeof resolver === "function") {
14
+ return resolver(options);
15
+ }
16
+ return resolver;
17
+ }
18
+ function createInvalidDocumentBackupPath(targetPath) {
19
+ const ext = targetPath.includes(".") ? targetPath.split(".").pop() : "bak";
20
+ return `${targetPath}.invalid-${createTimestamp()}.${ext}`;
21
+ }
22
+ async function backupInvalidDocument(context, targetPath, content) {
23
+ const baseBackupPath = createInvalidDocumentBackupPath(targetPath);
24
+ let attempt = 0;
25
+ while (true) {
26
+ const backupPath = attempt === 0 ? baseBackupPath : `${baseBackupPath}-${attempt}`;
27
+ await assertRegularWriteTarget(context, backupPath);
28
+ try {
29
+ await context.fs.writeFile(backupPath, content, { encoding: "utf8", flag: "wx" });
30
+ return;
31
+ }
32
+ catch (error) {
33
+ if (!isAlreadyExists(error)) {
34
+ await context.fs.unlink(backupPath).catch(() => undefined);
35
+ throw error;
36
+ }
37
+ attempt += 1;
38
+ }
39
+ }
40
+ }
41
+ function isAlreadyExists(error) {
42
+ return hasOwnErrorCode(error, "EEXIST");
43
+ }
44
+ async function assertRegularWriteTarget(context, targetPath) {
45
+ // Symlinks inside the managed home directory are untrusted: an attacker could
46
+ // plant one to redirect a credential/config write outside it. Symlinks at or
47
+ // above home are legitimate system links (e.g. /tmp -> /private/tmp on macOS,
48
+ // /var -> /private/var) and must not block writes, so bound the walk at home.
49
+ const boundary = path.dirname(path.resolve(context.homeDir));
50
+ let currentPath = path.resolve(targetPath);
51
+ while (currentPath !== boundary) {
52
+ try {
53
+ if ((await context.fs.lstat(currentPath)).isSymbolicLink()) {
54
+ throw new Error(`Refusing mutation write through symbolic link: ${currentPath}`);
55
+ }
56
+ }
57
+ catch (error) {
58
+ if (!isNotFound(error)) {
59
+ throw error;
60
+ }
61
+ }
62
+ const parentPath = path.dirname(currentPath);
63
+ if (parentPath === currentPath) {
64
+ return;
65
+ }
66
+ currentPath = parentPath;
67
+ }
68
+ }
69
+ async function writeAtomically(context, targetPath, content) {
70
+ await assertRegularWriteTarget(context, targetPath);
71
+ for (let attempt = 0; attempt < 10; attempt += 1) {
72
+ const tempPath = `${targetPath}.mutation-tmp-${process.pid}-${randomUUID()}`;
73
+ let tempCreated = false;
74
+ try {
75
+ await assertRegularWriteTarget(context, tempPath);
76
+ await context.fs.writeFile(tempPath, content, { encoding: "utf8", flag: "wx" });
77
+ tempCreated = true;
78
+ await context.fs.rename(tempPath, targetPath);
79
+ tempCreated = false;
80
+ return;
81
+ }
82
+ catch (error) {
83
+ const alreadyExists = isAlreadyExists(error);
84
+ if (tempCreated || !alreadyExists) {
85
+ try {
86
+ await context.fs.unlink(tempPath);
87
+ }
88
+ catch (cleanupError) {
89
+ if (!isNotFound(cleanupError)) {
90
+ void cleanupError;
91
+ }
92
+ }
93
+ }
94
+ if (alreadyExists) {
95
+ continue;
96
+ }
97
+ throw error;
98
+ }
99
+ }
100
+ throw new Error(`Unable to create temporary mutation file for ${targetPath}.`);
101
+ }
102
+ function describeMutation(kind, targetPath) {
103
+ const displayPath = targetPath ?? "target";
104
+ switch (kind) {
105
+ case "ensureDirectory":
106
+ return `Create ${displayPath}`;
107
+ case "removeDirectory":
108
+ return `Remove directory ${displayPath}`;
109
+ case "backup":
110
+ return `Backup ${displayPath}`;
111
+ case "restoreBackup":
112
+ return `Restore ${displayPath}`;
113
+ case "templateWrite":
114
+ return `Write ${displayPath}`;
115
+ case "chmod":
116
+ return `Set permissions on ${displayPath}`;
117
+ case "removeFile":
118
+ return `Remove ${displayPath}`;
119
+ case "configMerge":
120
+ case "configPrune":
121
+ case "configTransform":
122
+ case "templateMergeToml":
123
+ case "templateMergeJson":
124
+ return `Update ${displayPath}`;
125
+ default:
126
+ return "Operation";
127
+ }
128
+ }
129
+ function mutationTargetPath(mutation, options) {
130
+ switch (mutation.kind) {
131
+ case "ensureDirectory":
132
+ case "removeDirectory":
133
+ return resolveValue(mutation.path, options);
134
+ case "removeFile":
135
+ case "chmod":
136
+ case "backup":
137
+ case "restoreBackup":
138
+ case "configMerge":
139
+ case "configPrune":
140
+ case "configTransform":
141
+ case "templateWrite":
142
+ case "templateMergeToml":
143
+ case "templateMergeJson":
144
+ return resolveValue(mutation.target, options);
145
+ default:
146
+ return undefined;
147
+ }
148
+ }
149
+ export function resolveMutationDetails(mutation, context, options) {
150
+ try {
151
+ const rawTarget = mutationTargetPath(mutation, options);
152
+ if (rawTarget === undefined) {
153
+ return {
154
+ kind: mutation.kind,
155
+ label: mutation.label ?? mutation.kind
156
+ };
157
+ }
158
+ try {
159
+ const targetPath = resolvePath(rawTarget, context.homeDir, context.pathMapper);
160
+ return {
161
+ kind: mutation.kind,
162
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
163
+ targetPath
164
+ };
165
+ }
166
+ catch {
167
+ return {
168
+ kind: mutation.kind,
169
+ label: mutation.label ?? describeMutation(mutation.kind, rawTarget),
170
+ targetPath: undefined
171
+ };
172
+ }
173
+ }
174
+ catch {
175
+ return {
176
+ kind: mutation.kind,
177
+ label: mutation.label ?? mutation.kind
178
+ };
179
+ }
180
+ }
181
+ function pruneKeysByPrefix(table, prefix) {
182
+ const result = {};
183
+ for (const [key, value] of Object.entries(table)) {
184
+ if (!key.startsWith(prefix)) {
185
+ setConfigEntry(result, key, value);
186
+ }
187
+ }
188
+ return result;
189
+ }
190
+ function isConfigObject(value) {
191
+ return typeof value === "object" && value !== null && !Array.isArray(value);
192
+ }
193
+ function mergeWithPruneByPrefix(base, patch, pruneByPrefix) {
194
+ const result = cloneConfigObject(base);
195
+ const prefixMap = pruneByPrefix ?? {};
196
+ for (const [key, value] of Object.entries(patch)) {
197
+ if (value === undefined) {
198
+ continue;
199
+ }
200
+ const current = result[key];
201
+ const prefix = prefixMap[key];
202
+ if (isConfigObject(current) && isConfigObject(value)) {
203
+ if (prefix) {
204
+ const pruned = pruneKeysByPrefix(current, prefix);
205
+ setConfigEntry(result, key, mergePrunedConfigObject(pruned, value));
206
+ }
207
+ else {
208
+ setConfigEntry(result, key, mergeWithPruneByPrefix(current, value, prefixMap));
209
+ }
210
+ continue;
211
+ }
212
+ setConfigEntry(result, key, value);
213
+ }
214
+ return result;
215
+ }
216
+ function mergePrunedConfigObject(base, patch) {
217
+ const result = cloneConfigObject(base);
218
+ for (const [key, value] of Object.entries(patch)) {
219
+ if (value === undefined) {
220
+ continue;
221
+ }
222
+ setConfigEntry(result, key, value);
223
+ }
224
+ return result;
225
+ }
226
+ function serializeConfigUpdate(format, rawContent, current, next) {
227
+ if (rawContent !== null && format.serializeUpdate) {
228
+ return format.serializeUpdate(rawContent, current, next);
229
+ }
230
+ return format.serialize(next);
231
+ }
232
+ // ============================================================================
233
+ // Apply Mutation
234
+ // ============================================================================
235
+ export async function applyMutation(mutation, context, options) {
236
+ switch (mutation.kind) {
237
+ case "ensureDirectory":
238
+ return applyEnsureDirectory(mutation, context, options);
239
+ case "removeDirectory":
240
+ return applyRemoveDirectory(mutation, context, options);
241
+ case "removeFile":
242
+ return applyRemoveFile(mutation, context, options);
243
+ case "chmod":
244
+ return applyChmod(mutation, context, options);
245
+ case "backup":
246
+ return applyBackup(mutation, context, options);
247
+ case "restoreBackup":
248
+ return applyRestoreBackup(mutation, context, options);
249
+ case "configMerge":
250
+ return applyConfigMerge(mutation, context, options);
251
+ case "configPrune":
252
+ return applyConfigPrune(mutation, context, options);
253
+ case "configTransform":
254
+ return applyConfigTransform(mutation, context, options);
255
+ case "templateWrite":
256
+ return applyTemplateWrite(mutation, context, options);
257
+ case "templateMergeToml":
258
+ return applyTemplateMerge(mutation, context, options, "toml");
259
+ case "templateMergeJson":
260
+ return applyTemplateMerge(mutation, context, options, "json");
261
+ default: {
262
+ const never = mutation;
263
+ throw new Error(`Unknown mutation kind: ${never.kind}`);
264
+ }
265
+ }
266
+ }
267
+ // ============================================================================
268
+ // File Mutation Handlers
269
+ // ============================================================================
270
+ async function applyEnsureDirectory(mutation, context, options) {
271
+ const rawPath = resolveValue(mutation.path, options);
272
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
273
+ const details = {
274
+ kind: mutation.kind,
275
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
276
+ targetPath
277
+ };
278
+ await assertRegularWriteTarget(context, targetPath);
279
+ const existed = await pathExists(context.fs, targetPath);
280
+ if (!context.dryRun) {
281
+ await context.fs.mkdir(targetPath, { recursive: true });
282
+ }
283
+ return {
284
+ outcome: {
285
+ changed: !existed,
286
+ effect: "mkdir",
287
+ detail: existed ? "noop" : "create"
288
+ },
289
+ details
290
+ };
291
+ }
292
+ async function applyRemoveDirectory(mutation, context, options) {
293
+ const rawPath = resolveValue(mutation.path, options);
294
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
295
+ const details = {
296
+ kind: mutation.kind,
297
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
298
+ targetPath
299
+ };
300
+ const existed = await pathExists(context.fs, targetPath);
301
+ if (!existed) {
302
+ return {
303
+ outcome: { changed: false, effect: "none", detail: "noop" },
304
+ details
305
+ };
306
+ }
307
+ if (typeof context.fs.rm !== "function") {
308
+ return {
309
+ outcome: { changed: false, effect: "none", detail: "noop" },
310
+ details
311
+ };
312
+ }
313
+ if (mutation.force) {
314
+ if (!context.dryRun) {
315
+ await context.fs.rm(targetPath, { recursive: true, force: true });
316
+ }
317
+ return {
318
+ outcome: { changed: true, effect: "delete", detail: "delete" },
319
+ details
320
+ };
321
+ }
322
+ const entries = await context.fs.readdir(targetPath);
323
+ if (entries.length > 0) {
324
+ return {
325
+ outcome: { changed: false, effect: "none", detail: "noop" },
326
+ details
327
+ };
328
+ }
329
+ if (!context.dryRun) {
330
+ await context.fs.rm(targetPath, { recursive: true, force: true });
331
+ }
332
+ return {
333
+ outcome: { changed: true, effect: "delete", detail: "delete" },
334
+ details
335
+ };
336
+ }
337
+ async function applyRemoveFile(mutation, context, options) {
338
+ const rawPath = resolveValue(mutation.target, options);
339
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
340
+ const details = {
341
+ kind: mutation.kind,
342
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
343
+ targetPath
344
+ };
345
+ try {
346
+ const content = await context.fs.readFile(targetPath, "utf8");
347
+ const trimmed = content.trim();
348
+ // Check whenContentMatches guard
349
+ if (mutation.whenContentMatches) {
350
+ mutation.whenContentMatches.lastIndex = 0;
351
+ }
352
+ if (mutation.whenContentMatches && !mutation.whenContentMatches.test(trimmed)) {
353
+ return {
354
+ outcome: { changed: false, effect: "none", detail: "noop" },
355
+ details
356
+ };
357
+ }
358
+ // Check whenEmpty guard
359
+ if (mutation.whenEmpty && trimmed.length > 0) {
360
+ return {
361
+ outcome: { changed: false, effect: "none", detail: "noop" },
362
+ details
363
+ };
364
+ }
365
+ if (!context.dryRun) {
366
+ await context.fs.unlink(targetPath);
367
+ }
368
+ return {
369
+ outcome: { changed: true, effect: "delete", detail: "delete" },
370
+ details
371
+ };
372
+ }
373
+ catch (error) {
374
+ if (isNotFound(error)) {
375
+ return {
376
+ outcome: { changed: false, effect: "none", detail: "noop" },
377
+ details
378
+ };
379
+ }
380
+ throw error;
381
+ }
382
+ }
383
+ async function applyChmod(mutation, context, options) {
384
+ const rawPath = resolveValue(mutation.target, options);
385
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
386
+ const details = {
387
+ kind: mutation.kind,
388
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
389
+ targetPath
390
+ };
391
+ if (typeof context.fs.chmod !== "function") {
392
+ return {
393
+ outcome: { changed: false, effect: "none", detail: "noop" },
394
+ details
395
+ };
396
+ }
397
+ try {
398
+ await assertRegularWriteTarget(context, targetPath);
399
+ const stat = await context.fs.stat(targetPath);
400
+ const currentMode = typeof stat.mode === "number" ? stat.mode & 0o777 : null;
401
+ if (currentMode === mutation.mode) {
402
+ return {
403
+ outcome: { changed: false, effect: "none", detail: "noop" },
404
+ details
405
+ };
406
+ }
407
+ if (!context.dryRun) {
408
+ await context.fs.chmod(targetPath, mutation.mode);
409
+ }
410
+ return {
411
+ outcome: { changed: true, effect: "chmod", detail: "update" },
412
+ details
413
+ };
414
+ }
415
+ catch (error) {
416
+ if (isNotFound(error)) {
417
+ return {
418
+ outcome: { changed: false, effect: "none", detail: "noop" },
419
+ details
420
+ };
421
+ }
422
+ throw error;
423
+ }
424
+ }
425
+ async function applyBackup(mutation, context, options) {
426
+ const rawPath = resolveValue(mutation.target, options);
427
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
428
+ const details = {
429
+ kind: mutation.kind,
430
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
431
+ targetPath
432
+ };
433
+ await assertRegularWriteTarget(context, targetPath);
434
+ if (mutation.once && (await findLatestGeneratedBackup(context.fs, targetPath)) !== null) {
435
+ return {
436
+ outcome: { changed: false, effect: "none", detail: "noop" },
437
+ details
438
+ };
439
+ }
440
+ const content = await readFileIfExists(context.fs, targetPath);
441
+ if (content === null && !mutation.once) {
442
+ return {
443
+ outcome: { changed: false, effect: "none", detail: "noop" },
444
+ details
445
+ };
446
+ }
447
+ if (!context.dryRun) {
448
+ const baseBackupPath = `${targetPath}.backup-${createTimestamp()}${content === null ? ".missing" : ""}`;
449
+ let attempt = 0;
450
+ while (true) {
451
+ const backupPath = attempt === 0 ? baseBackupPath : `${baseBackupPath}-${attempt}`;
452
+ try {
453
+ await assertRegularWriteTarget(context, backupPath);
454
+ await context.fs.writeFile(backupPath, content ?? "", { encoding: "utf8", flag: "wx" });
455
+ break;
456
+ }
457
+ catch (error) {
458
+ if (!isAlreadyExists(error)) {
459
+ await context.fs.unlink(backupPath).catch(() => undefined);
460
+ throw error;
461
+ }
462
+ attempt += 1;
463
+ }
464
+ }
465
+ }
466
+ return {
467
+ outcome: { changed: true, effect: "copy", detail: "backup" },
468
+ details
469
+ };
470
+ }
471
+ async function applyRestoreBackup(mutation, context, options) {
472
+ const rawPath = resolveValue(mutation.target, options);
473
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
474
+ const details = {
475
+ kind: mutation.kind,
476
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
477
+ targetPath
478
+ };
479
+ const backup = await findLatestGeneratedBackup(context.fs, targetPath);
480
+ if (backup === null) {
481
+ return { outcome: { changed: false, effect: "none", detail: "noop" }, details };
482
+ }
483
+ if (!context.dryRun) {
484
+ await assertRegularWriteTarget(context, backup.path);
485
+ if (backup.originallyMissing) {
486
+ try {
487
+ await context.fs.unlink(targetPath);
488
+ }
489
+ catch (error) {
490
+ if (!isNotFound(error)) {
491
+ throw error;
492
+ }
493
+ }
494
+ }
495
+ else {
496
+ const content = await context.fs.readFile(backup.path, "utf8");
497
+ await writeAtomically(context, targetPath, content);
498
+ }
499
+ await context.fs.unlink(backup.path);
500
+ }
501
+ return { outcome: { changed: true, effect: "copy", detail: "restore" }, details };
502
+ }
503
+ async function findLatestGeneratedBackup(fs, targetPath) {
504
+ const separatorIndex = targetPath.lastIndexOf("/");
505
+ const directoryPath = separatorIndex <= 0 ? "/" : targetPath.slice(0, separatorIndex);
506
+ const targetName = targetPath.slice(separatorIndex + 1);
507
+ let entries;
508
+ try {
509
+ entries = await fs.readdir(directoryPath);
510
+ }
511
+ catch (error) {
512
+ if (isNotFound(error)) {
513
+ return null;
514
+ }
515
+ throw error;
516
+ }
517
+ const backupName = entries
518
+ .filter((entry) => isGeneratedBackupName(entry, targetName))
519
+ .sort()
520
+ .at(-1);
521
+ return backupName === undefined
522
+ ? null
523
+ : {
524
+ path: `${directoryPath}/${backupName}`,
525
+ originallyMissing: backupName.endsWith(".missing")
526
+ };
527
+ }
528
+ function isGeneratedBackupName(entry, targetName) {
529
+ const prefix = `${targetName}.backup-`;
530
+ if (!entry.startsWith(prefix)) {
531
+ return false;
532
+ }
533
+ const suffix = entry.slice(prefix.length);
534
+ const timestamp = suffix.slice(0, 24);
535
+ if (timestamp.length !== 24 || timestamp[4] !== "-" || timestamp[7] !== "-" || timestamp[10] !== "T" || timestamp[13] !== "-" || timestamp[16] !== "-" || timestamp[19] !== "-" || timestamp[23] !== "Z") {
536
+ return false;
537
+ }
538
+ const parsedTimestamp = `${timestamp.slice(0, 13)}:${timestamp.slice(14, 16)}:${timestamp.slice(17, 19)}.${timestamp.slice(20)}`;
539
+ if (Number.isNaN(Date.parse(parsedTimestamp))) {
540
+ return false;
541
+ }
542
+ const collisionSuffix = suffix.slice(24);
543
+ if (collisionSuffix.length === 0) {
544
+ return true;
545
+ }
546
+ if (collisionSuffix === ".missing") {
547
+ return true;
548
+ }
549
+ return collisionSuffix[0] === "-" && collisionSuffix.slice(1).length > 0 && [...collisionSuffix.slice(1)].every((character) => character >= "0" && character <= "9");
550
+ }
551
+ // ============================================================================
552
+ // Config Mutation Handlers
553
+ // ============================================================================
554
+ async function applyConfigMerge(mutation, context, options) {
555
+ const rawPath = resolveValue(mutation.target, options);
556
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
557
+ const details = {
558
+ kind: mutation.kind,
559
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
560
+ targetPath
561
+ };
562
+ const formatName = mutation.format ?? detectFormat(rawPath);
563
+ if (!formatName) {
564
+ throw new Error(`Cannot detect config format for "${rawPath}". Provide explicit format option.`);
565
+ }
566
+ const format = getConfigFormat(formatName);
567
+ const rawContent = await readFileIfExists(context.fs, targetPath);
568
+ let preserveContent = rawContent;
569
+ let current;
570
+ try {
571
+ current = rawContent === null ? {} : format.parse(rawContent);
572
+ }
573
+ catch {
574
+ // Invalid file - backup and start fresh
575
+ if (rawContent !== null && !context.dryRun) {
576
+ await backupInvalidDocument(context, targetPath, rawContent);
577
+ }
578
+ current = {};
579
+ preserveContent = null;
580
+ }
581
+ const value = resolveValue(mutation.value, options);
582
+ if (!isConfigObject(value)) {
583
+ throw new Error(`configMerge value must be an object for "${rawPath}".`);
584
+ }
585
+ // Keep prefix pruning on the same proto-safe object-write path as normal merges.
586
+ let merged;
587
+ if (mutation.pruneByPrefix) {
588
+ merged = mergeWithPruneByPrefix(current, value, mutation.pruneByPrefix);
589
+ }
590
+ else {
591
+ merged = format.merge(current, value);
592
+ }
593
+ const serialized = serializeConfigUpdate(format, preserveContent, current, merged);
594
+ const changed = serialized !== rawContent;
595
+ if (changed && !context.dryRun) {
596
+ await writeAtomically(context, targetPath, serialized);
597
+ }
598
+ return {
599
+ outcome: {
600
+ changed,
601
+ effect: changed ? "write" : "none",
602
+ detail: changed ? (rawContent === null ? "create" : "update") : "noop"
603
+ },
604
+ details
605
+ };
606
+ }
607
+ async function applyConfigPrune(mutation, context, options) {
608
+ const rawPath = resolveValue(mutation.target, options);
609
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
610
+ const details = {
611
+ kind: mutation.kind,
612
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
613
+ targetPath
614
+ };
615
+ const rawContent = await readFileIfExists(context.fs, targetPath);
616
+ if (rawContent === null) {
617
+ return {
618
+ outcome: { changed: false, effect: "none", detail: "noop" },
619
+ details
620
+ };
621
+ }
622
+ const formatName = mutation.format ?? detectFormat(rawPath);
623
+ if (!formatName) {
624
+ throw new Error(`Cannot detect config format for "${rawPath}". Provide explicit format option.`);
625
+ }
626
+ const format = getConfigFormat(formatName);
627
+ let current;
628
+ try {
629
+ current = format.parse(rawContent);
630
+ }
631
+ catch {
632
+ // Invalid file - can't prune, leave as-is
633
+ return {
634
+ outcome: { changed: false, effect: "none", detail: "noop" },
635
+ details
636
+ };
637
+ }
638
+ // Check onlyIf guard
639
+ if (mutation.onlyIf && !mutation.onlyIf(current, options)) {
640
+ return {
641
+ outcome: { changed: false, effect: "none", detail: "noop" },
642
+ details
643
+ };
644
+ }
645
+ const shape = resolveValue(mutation.shape, options);
646
+ const { changed, result } = format.prune(current, shape);
647
+ if (!changed) {
648
+ return {
649
+ outcome: { changed: false, effect: "none", detail: "noop" },
650
+ details
651
+ };
652
+ }
653
+ // Delete file if empty
654
+ if (Object.keys(result).length === 0) {
655
+ if (!context.dryRun) {
656
+ await context.fs.unlink(targetPath);
657
+ }
658
+ return {
659
+ outcome: { changed: true, effect: "delete", detail: "delete" },
660
+ details
661
+ };
662
+ }
663
+ const serialized = serializeConfigUpdate(format, rawContent, current, result);
664
+ if (!context.dryRun) {
665
+ await writeAtomically(context, targetPath, serialized);
666
+ }
667
+ return {
668
+ outcome: { changed: true, effect: "write", detail: "update" },
669
+ details
670
+ };
671
+ }
672
+ async function applyConfigTransform(mutation, context, options) {
673
+ const rawPath = resolveValue(mutation.target, options);
674
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
675
+ const details = {
676
+ kind: mutation.kind,
677
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
678
+ targetPath
679
+ };
680
+ const formatName = mutation.format ?? detectFormat(rawPath);
681
+ if (!formatName) {
682
+ throw new Error(`Cannot detect config format for "${rawPath}". Provide explicit format option.`);
683
+ }
684
+ const format = getConfigFormat(formatName);
685
+ const rawContent = await readFileIfExists(context.fs, targetPath);
686
+ let preserveContent = rawContent;
687
+ let current;
688
+ try {
689
+ current = rawContent === null ? {} : format.parse(rawContent);
690
+ }
691
+ catch {
692
+ if (rawContent !== null && !context.dryRun) {
693
+ await backupInvalidDocument(context, targetPath, rawContent);
694
+ }
695
+ current = {};
696
+ preserveContent = null;
697
+ }
698
+ const { content: transformed, changed } = mutation.transform(current, options);
699
+ if (!changed) {
700
+ return {
701
+ outcome: { changed: false, effect: "none", detail: "noop" },
702
+ details
703
+ };
704
+ }
705
+ // Delete file if null
706
+ if (transformed === null) {
707
+ if (rawContent === null) {
708
+ return {
709
+ outcome: { changed: false, effect: "none", detail: "noop" },
710
+ details
711
+ };
712
+ }
713
+ if (!context.dryRun) {
714
+ await context.fs.unlink(targetPath);
715
+ }
716
+ return {
717
+ outcome: { changed: true, effect: "delete", detail: "delete" },
718
+ details
719
+ };
720
+ }
721
+ const serialized = serializeConfigUpdate(format, preserveContent, current, transformed);
722
+ const serializedChanged = serialized !== rawContent;
723
+ if (!serializedChanged) {
724
+ return {
725
+ outcome: { changed: false, effect: "none", detail: "noop" },
726
+ details
727
+ };
728
+ }
729
+ if (!context.dryRun) {
730
+ await writeAtomically(context, targetPath, serialized);
731
+ }
732
+ return {
733
+ outcome: {
734
+ changed: true,
735
+ effect: "write",
736
+ detail: rawContent === null ? "create" : "update"
737
+ },
738
+ details
739
+ };
740
+ }
741
+ // ============================================================================
742
+ // Template Mutation Handlers
743
+ // ============================================================================
744
+ async function applyTemplateWrite(mutation, context, options) {
745
+ if (!context.templates) {
746
+ throw new Error("Template mutations require a templates loader. " +
747
+ "Provide templates function to runMutations context.");
748
+ }
749
+ const rawPath = resolveValue(mutation.target, options);
750
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
751
+ const details = {
752
+ kind: mutation.kind,
753
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
754
+ targetPath
755
+ };
756
+ const template = await context.templates(mutation.templateId);
757
+ const templateContext = mutation.context
758
+ ? resolveValue(mutation.context, options)
759
+ : {};
760
+ const rendered = renderTemplate(template, templateContext);
761
+ const current = await readFileIfExists(context.fs, targetPath);
762
+ const changed = current !== rendered;
763
+ if (changed && !context.dryRun) {
764
+ await writeAtomically(context, targetPath, rendered);
765
+ }
766
+ return {
767
+ outcome: {
768
+ changed,
769
+ effect: changed ? "write" : "none",
770
+ detail: changed ? (current === null ? "create" : "update") : "noop"
771
+ },
772
+ details
773
+ };
774
+ }
775
+ async function applyTemplateMerge(mutation, context, options, formatName) {
776
+ if (!context.templates) {
777
+ throw new Error("Template mutations require a templates loader. " +
778
+ "Provide templates function to runMutations context.");
779
+ }
780
+ const rawPath = resolveValue(mutation.target, options);
781
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
782
+ const details = {
783
+ kind: mutation.kind,
784
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
785
+ targetPath
786
+ };
787
+ const format = getConfigFormat(formatName);
788
+ // Load and render template
789
+ const template = await context.templates(mutation.templateId);
790
+ const templateContext = mutation.context
791
+ ? resolveValue(mutation.context, options)
792
+ : {};
793
+ const rendered = renderTemplate(template, templateContext);
794
+ // Parse rendered template
795
+ let templateDoc;
796
+ try {
797
+ templateDoc = format.parse(rendered);
798
+ }
799
+ catch (error) {
800
+ throw new Error(`Failed to parse rendered template "${mutation.templateId}" as ${formatName.toUpperCase()}: ${error}`, { cause: error });
801
+ }
802
+ // Read and parse existing file
803
+ const rawContent = await readFileIfExists(context.fs, targetPath);
804
+ let current;
805
+ try {
806
+ current = rawContent === null ? {} : format.parse(rawContent);
807
+ }
808
+ catch {
809
+ if (rawContent !== null && !context.dryRun) {
810
+ await backupInvalidDocument(context, targetPath, rawContent);
811
+ }
812
+ current = {};
813
+ }
814
+ // Merge
815
+ const merged = format.merge(current, templateDoc);
816
+ const serialized = format.serialize(merged);
817
+ const changed = serialized !== rawContent;
818
+ if (changed && !context.dryRun) {
819
+ await writeAtomically(context, targetPath, serialized);
820
+ }
821
+ return {
822
+ outcome: {
823
+ changed,
824
+ effect: changed ? "write" : "none",
825
+ detail: changed ? (rawContent === null ? "create" : "update") : "noop"
826
+ },
827
+ details
828
+ };
829
+ }