aicodeman 1.47.0 → 1.49.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 (361) hide show
  1. package/README.md +15 -3
  2. package/dist/cli-notify.d.ts +34 -0
  3. package/dist/cli-notify.d.ts.map +1 -0
  4. package/dist/cli-notify.js +100 -0
  5. package/dist/cli-notify.js.map +1 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +16 -0
  8. package/dist/cli.js.map +1 -1
  9. package/dist/config/cli-registry/patterns.d.ts +1 -1
  10. package/dist/config/cli-registry/patterns.d.ts.map +1 -1
  11. package/dist/config/cli-registry/patterns.js +4 -0
  12. package/dist/config/cli-registry/patterns.js.map +1 -1
  13. package/dist/config/cli-registry/schema.d.ts +7 -0
  14. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  15. package/dist/config/cli-registry/schema.js +67 -3
  16. package/dist/config/cli-registry/schema.js.map +1 -1
  17. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  18. package/dist/config/cli-registry/stock.js +114 -31
  19. package/dist/config/cli-registry/stock.js.map +1 -1
  20. package/dist/config/cli-registry/types.d.ts +51 -21
  21. package/dist/config/cli-registry/types.d.ts.map +1 -1
  22. package/dist/config/foreign-tmux.d.ts +48 -0
  23. package/dist/config/foreign-tmux.d.ts.map +1 -0
  24. package/dist/config/foreign-tmux.js +48 -0
  25. package/dist/config/foreign-tmux.js.map +1 -0
  26. package/dist/config/path-probe.d.ts +6 -0
  27. package/dist/config/path-probe.d.ts.map +1 -1
  28. package/dist/config/path-probe.js +6 -0
  29. package/dist/config/path-probe.js.map +1 -1
  30. package/dist/config-backup.d.ts +1 -1
  31. package/dist/config-backup.d.ts.map +1 -1
  32. package/dist/config-backup.js +2 -0
  33. package/dist/config-backup.js.map +1 -1
  34. package/dist/custom-model-hosts.d.ts +32 -8
  35. package/dist/custom-model-hosts.d.ts.map +1 -1
  36. package/dist/custom-model-hosts.js +10 -0
  37. package/dist/custom-model-hosts.js.map +1 -1
  38. package/dist/custom-model-injection-apply.d.ts +21 -5
  39. package/dist/custom-model-injection-apply.d.ts.map +1 -1
  40. package/dist/custom-model-injection-apply.js +121 -27
  41. package/dist/custom-model-injection-apply.js.map +1 -1
  42. package/dist/custom-model-injection.d.ts +6 -0
  43. package/dist/custom-model-injection.d.ts.map +1 -1
  44. package/dist/custom-model-injection.js +92 -10
  45. package/dist/custom-model-injection.js.map +1 -1
  46. package/dist/discord-bridge.d.ts +338 -0
  47. package/dist/discord-bridge.d.ts.map +1 -0
  48. package/dist/discord-bridge.js +1256 -0
  49. package/dist/discord-bridge.js.map +1 -0
  50. package/dist/docker-hosts.d.ts +8 -1
  51. package/dist/docker-hosts.d.ts.map +1 -1
  52. package/dist/docker-hosts.js +5 -5
  53. package/dist/docker-hosts.js.map +1 -1
  54. package/dist/edit-history-local-source.d.ts +83 -0
  55. package/dist/edit-history-local-source.d.ts.map +1 -0
  56. package/dist/edit-history-local-source.js +198 -0
  57. package/dist/edit-history-local-source.js.map +1 -0
  58. package/dist/edit-history-parse.d.ts +114 -0
  59. package/dist/edit-history-parse.d.ts.map +1 -0
  60. package/dist/edit-history-parse.js +312 -0
  61. package/dist/edit-history-parse.js.map +1 -0
  62. package/dist/edit-history-store.d.ts +209 -0
  63. package/dist/edit-history-store.d.ts.map +1 -0
  64. package/dist/edit-history-store.js +599 -0
  65. package/dist/edit-history-store.js.map +1 -0
  66. package/dist/foreign-tmux-discovery.d.ts +140 -0
  67. package/dist/foreign-tmux-discovery.d.ts.map +1 -0
  68. package/dist/foreign-tmux-discovery.js +366 -0
  69. package/dist/foreign-tmux-discovery.js.map +1 -0
  70. package/dist/foreign-tmux.d.ts +381 -0
  71. package/dist/foreign-tmux.d.ts.map +1 -0
  72. package/dist/foreign-tmux.js +775 -0
  73. package/dist/foreign-tmux.js.map +1 -0
  74. package/dist/git-clone.d.ts +6 -1
  75. package/dist/git-clone.d.ts.map +1 -1
  76. package/dist/git-clone.js +79 -14
  77. package/dist/git-clone.js.map +1 -1
  78. package/dist/git-fetch-scheduler.d.ts +2 -0
  79. package/dist/git-fetch-scheduler.d.ts.map +1 -1
  80. package/dist/git-fetch-scheduler.js +1 -1
  81. package/dist/git-fetch-scheduler.js.map +1 -1
  82. package/dist/git-fetch.d.ts +1 -1
  83. package/dist/git-fetch.d.ts.map +1 -1
  84. package/dist/git-workspace-status.d.ts +2 -2
  85. package/dist/git-workspace-status.d.ts.map +1 -1
  86. package/dist/hooks-config.d.ts +2 -1
  87. package/dist/hooks-config.d.ts.map +1 -1
  88. package/dist/hooks-config.js +2 -1
  89. package/dist/hooks-config.js.map +1 -1
  90. package/dist/image-watcher.js +4 -4
  91. package/dist/image-watcher.js.map +1 -1
  92. package/dist/live-worktree.d.ts +53 -0
  93. package/dist/live-worktree.d.ts.map +1 -0
  94. package/dist/live-worktree.js +97 -0
  95. package/dist/live-worktree.js.map +1 -0
  96. package/dist/mux-interface.d.ts +22 -2
  97. package/dist/mux-interface.d.ts.map +1 -1
  98. package/dist/reboot-restore.d.ts +1 -1
  99. package/dist/reboot-restore.d.ts.map +1 -1
  100. package/dist/reboot-restore.js +9 -0
  101. package/dist/reboot-restore.js.map +1 -1
  102. package/dist/remote-files.d.ts +150 -5
  103. package/dist/remote-files.d.ts.map +1 -1
  104. package/dist/remote-files.js +505 -7
  105. package/dist/remote-files.js.map +1 -1
  106. package/dist/remote-hosts.d.ts +15 -9
  107. package/dist/remote-hosts.d.ts.map +1 -1
  108. package/dist/remote-hosts.js +42 -20
  109. package/dist/remote-hosts.js.map +1 -1
  110. package/dist/session-cli-registry-bridge.d.ts.map +1 -1
  111. package/dist/session-cli-registry-bridge.js +23 -8
  112. package/dist/session-cli-registry-bridge.js.map +1 -1
  113. package/dist/session-env-clamp.d.ts +1 -1
  114. package/dist/session-env-clamp.d.ts.map +1 -1
  115. package/dist/session-env-clamp.js +4 -3
  116. package/dist/session-env-clamp.js.map +1 -1
  117. package/dist/session.d.ts +161 -24
  118. package/dist/session.d.ts.map +1 -1
  119. package/dist/session.js +337 -37
  120. package/dist/session.js.map +1 -1
  121. package/dist/tmux-manager.d.ts +114 -12
  122. package/dist/tmux-manager.d.ts.map +1 -1
  123. package/dist/tmux-manager.js +318 -41
  124. package/dist/tmux-manager.js.map +1 -1
  125. package/dist/types/discord.d.ts +55 -0
  126. package/dist/types/discord.d.ts.map +1 -0
  127. package/dist/types/discord.js +18 -0
  128. package/dist/types/discord.js.map +1 -0
  129. package/dist/types/foreign-tmux.d.ts +101 -0
  130. package/dist/types/foreign-tmux.d.ts.map +1 -0
  131. package/dist/types/foreign-tmux.js +27 -0
  132. package/dist/types/foreign-tmux.js.map +1 -0
  133. package/dist/types/index.d.ts +3 -0
  134. package/dist/types/index.d.ts.map +1 -1
  135. package/dist/types/index.js +3 -0
  136. package/dist/types/index.js.map +1 -1
  137. package/dist/types/session.d.ts +119 -2
  138. package/dist/types/session.d.ts.map +1 -1
  139. package/dist/types/session.js.map +1 -1
  140. package/dist/types/update.d.ts +4 -1
  141. package/dist/types/update.d.ts.map +1 -1
  142. package/dist/web/case-path.d.ts +2 -0
  143. package/dist/web/case-path.d.ts.map +1 -1
  144. package/dist/web/case-path.js +3 -3
  145. package/dist/web/case-path.js.map +1 -1
  146. package/dist/web/edit-history-sync.d.ts +178 -0
  147. package/dist/web/edit-history-sync.d.ts.map +1 -0
  148. package/dist/web/edit-history-sync.js +461 -0
  149. package/dist/web/edit-history-sync.js.map +1 -0
  150. package/dist/web/launch-defaults.d.ts +1 -1
  151. package/dist/web/launch-defaults.js +2 -2
  152. package/dist/web/launch-defaults.js.map +1 -1
  153. package/dist/web/linked-cases-writer.d.ts +43 -0
  154. package/dist/web/linked-cases-writer.d.ts.map +1 -0
  155. package/dist/web/linked-cases-writer.js +107 -0
  156. package/dist/web/linked-cases-writer.js.map +1 -0
  157. package/dist/web/paste-image-gc.d.ts +10 -1
  158. package/dist/web/paste-image-gc.d.ts.map +1 -1
  159. package/dist/web/paste-image-gc.js +10 -2
  160. package/dist/web/paste-image-gc.js.map +1 -1
  161. package/dist/web/ports/session-port.d.ts +13 -0
  162. package/dist/web/ports/session-port.d.ts.map +1 -1
  163. package/dist/web/public/admin-ui.js.gz +0 -0
  164. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  165. package/dist/web/public/{app.fe8a6b4c.js → app.9ea37070.js} +6 -6
  166. package/dist/web/public/app.9ea37070.js.br +0 -0
  167. package/dist/web/public/app.9ea37070.js.gz +0 -0
  168. package/dist/web/public/approvals-ui.js.gz +0 -0
  169. package/dist/web/public/{constants.affcdde6.js → constants.782170e1.js} +114 -25
  170. package/dist/web/public/constants.782170e1.js.br +0 -0
  171. package/dist/web/public/constants.782170e1.js.gz +0 -0
  172. package/dist/web/public/cron-ui.js.gz +0 -0
  173. package/dist/web/public/edit-history-ui.js +643 -0
  174. package/dist/web/public/edit-history-ui.js.br +0 -0
  175. package/dist/web/public/edit-history-ui.js.gz +0 -0
  176. package/dist/web/public/entrance-animations.js.gz +0 -0
  177. package/dist/web/public/foreign-sessions.js +476 -0
  178. package/dist/web/public/foreign-sessions.js.br +0 -0
  179. package/dist/web/public/foreign-sessions.js.gz +0 -0
  180. package/dist/web/public/git-status-ui.js +5 -3
  181. package/dist/web/public/git-status-ui.js.br +0 -0
  182. package/dist/web/public/git-status-ui.js.gz +0 -0
  183. package/dist/web/public/header-divider.eb2d2463.js.gz +0 -0
  184. package/dist/web/public/home-sessions.js.gz +0 -0
  185. package/dist/web/public/host-wake-ui.js.gz +0 -0
  186. package/dist/web/public/i18n.a5e82c95.js +2 -0
  187. package/dist/web/public/i18n.a5e82c95.js.br +0 -0
  188. package/dist/web/public/i18n.a5e82c95.js.gz +0 -0
  189. package/dist/web/public/image-input.a5d0f8f3.js.gz +0 -0
  190. package/dist/web/public/index.html +183 -18
  191. package/dist/web/public/index.html.br +0 -0
  192. package/dist/web/public/index.html.gz +0 -0
  193. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  194. package/dist/web/public/{keyboard-accessory.ff64fafc.js → keyboard-accessory.b941676c.js} +75 -2
  195. package/dist/web/public/keyboard-accessory.b941676c.js.br +0 -0
  196. package/dist/web/public/keyboard-accessory.b941676c.js.gz +0 -0
  197. package/dist/web/public/mobile-handlers.f7c3f692.js.gz +0 -0
  198. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  199. package/dist/web/public/mobile-overview.js +10 -0
  200. package/dist/web/public/mobile-overview.js.br +0 -0
  201. package/dist/web/public/mobile-overview.js.gz +0 -0
  202. package/dist/web/public/mobile.67c8eb5b.css +1 -0
  203. package/dist/web/public/mobile.67c8eb5b.css.br +0 -0
  204. package/dist/web/public/mobile.67c8eb5b.css.gz +0 -0
  205. package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
  206. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  207. package/dist/web/public/{panels-ui.4a56ee63.js → panels-ui.8bcd4473.js} +5 -5
  208. package/dist/web/public/panels-ui.8bcd4473.js.br +0 -0
  209. package/dist/web/public/panels-ui.8bcd4473.js.gz +0 -0
  210. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  211. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  212. package/dist/web/public/readmymind-ui.js.gz +0 -0
  213. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  214. package/dist/web/public/{respawn-ui.d7adcf6a.js → respawn-ui.304ba0d4.js} +1 -1
  215. package/dist/web/public/respawn-ui.304ba0d4.js.br +0 -0
  216. package/dist/web/public/respawn-ui.304ba0d4.js.gz +0 -0
  217. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  218. package/dist/web/public/security-notices.json +1 -21
  219. package/dist/web/public/session-lineage.js.gz +0 -0
  220. package/dist/web/public/{session-ui.426fb671.js → session-ui.a7c8c095.js} +30 -30
  221. package/dist/web/public/session-ui.a7c8c095.js.br +0 -0
  222. package/dist/web/public/session-ui.a7c8c095.js.gz +0 -0
  223. package/dist/web/public/settings-ui.18f92052.js +106 -0
  224. package/dist/web/public/settings-ui.18f92052.js.br +0 -0
  225. package/dist/web/public/settings-ui.18f92052.js.gz +0 -0
  226. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  227. package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
  228. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  229. package/dist/web/public/styles.6a718331.css +1 -0
  230. package/dist/web/public/styles.6a718331.css.br +0 -0
  231. package/dist/web/public/styles.6a718331.css.gz +0 -0
  232. package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
  233. package/dist/web/public/sw.js +2 -2
  234. package/dist/web/public/sw.js.br +0 -0
  235. package/dist/web/public/sw.js.gz +0 -0
  236. package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
  237. package/dist/web/public/tab-rail-resize.b5cdceaf.js.gz +0 -0
  238. package/dist/web/public/terminal-keycode229-recovery.98843069.js.gz +0 -0
  239. package/dist/web/public/terminal-split.js.gz +0 -0
  240. package/dist/web/public/terminal-tile.js +2 -2
  241. package/dist/web/public/terminal-tile.js.br +0 -0
  242. package/dist/web/public/terminal-tile.js.gz +0 -0
  243. package/dist/web/public/terminal-ui.a7883640.js +4 -0
  244. package/dist/web/public/terminal-ui.a7883640.js.br +0 -0
  245. package/dist/web/public/terminal-ui.a7883640.js.gz +0 -0
  246. package/dist/web/public/tile-grid.js.gz +0 -0
  247. package/dist/web/public/ultracode-panel.js.gz +0 -0
  248. package/dist/web/public/ultracode-windows.js.gz +0 -0
  249. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  250. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  251. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  252. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  253. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  254. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  255. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  256. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  257. package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.gz +0 -0
  258. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.gz +0 -0
  259. package/dist/web/public/vendor/xterm.css.gz +0 -0
  260. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  261. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  262. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  263. package/dist/web/public/webview-tabs.js.gz +0 -0
  264. package/dist/web/public/whats-new.json +9 -8
  265. package/dist/web/routes/case-routes.d.ts.map +1 -1
  266. package/dist/web/routes/case-routes.js +165 -50
  267. package/dist/web/routes/case-routes.js.map +1 -1
  268. package/dist/web/routes/config-backup-routes.d.ts +5 -1
  269. package/dist/web/routes/config-backup-routes.d.ts.map +1 -1
  270. package/dist/web/routes/config-backup-routes.js +4 -1
  271. package/dist/web/routes/config-backup-routes.js.map +1 -1
  272. package/dist/web/routes/custom-model-routes.d.ts +7 -2
  273. package/dist/web/routes/custom-model-routes.d.ts.map +1 -1
  274. package/dist/web/routes/custom-model-routes.js +51 -6
  275. package/dist/web/routes/custom-model-routes.js.map +1 -1
  276. package/dist/web/routes/discord-routes.d.ts +25 -0
  277. package/dist/web/routes/discord-routes.d.ts.map +1 -0
  278. package/dist/web/routes/discord-routes.js +206 -0
  279. package/dist/web/routes/discord-routes.js.map +1 -0
  280. package/dist/web/routes/edit-history-routes.d.ts +43 -0
  281. package/dist/web/routes/edit-history-routes.d.ts.map +1 -0
  282. package/dist/web/routes/edit-history-routes.js +107 -0
  283. package/dist/web/routes/edit-history-routes.js.map +1 -0
  284. package/dist/web/routes/file-routes.d.ts.map +1 -1
  285. package/dist/web/routes/file-routes.js +273 -102
  286. package/dist/web/routes/file-routes.js.map +1 -1
  287. package/dist/web/routes/git-status-routes.d.ts.map +1 -1
  288. package/dist/web/routes/git-status-routes.js +7 -2
  289. package/dist/web/routes/git-status-routes.js.map +1 -1
  290. package/dist/web/routes/index.d.ts +2 -0
  291. package/dist/web/routes/index.d.ts.map +1 -1
  292. package/dist/web/routes/index.js +2 -0
  293. package/dist/web/routes/index.js.map +1 -1
  294. package/dist/web/routes/mux-routes.d.ts +10 -3
  295. package/dist/web/routes/mux-routes.d.ts.map +1 -1
  296. package/dist/web/routes/mux-routes.js +59 -1
  297. package/dist/web/routes/mux-routes.js.map +1 -1
  298. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  299. package/dist/web/routes/ralph-routes.js +22 -0
  300. package/dist/web/routes/ralph-routes.js.map +1 -1
  301. package/dist/web/routes/readmymind-routes.d.ts.map +1 -1
  302. package/dist/web/routes/readmymind-routes.js +3 -2
  303. package/dist/web/routes/readmymind-routes.js.map +1 -1
  304. package/dist/web/routes/respawn-routes.d.ts.map +1 -1
  305. package/dist/web/routes/respawn-routes.js +24 -0
  306. package/dist/web/routes/respawn-routes.js.map +1 -1
  307. package/dist/web/routes/session-routes.d.ts +21 -1
  308. package/dist/web/routes/session-routes.d.ts.map +1 -1
  309. package/dist/web/routes/session-routes.js +338 -55
  310. package/dist/web/routes/session-routes.js.map +1 -1
  311. package/dist/web/routes/system-routes.d.ts.map +1 -1
  312. package/dist/web/routes/system-routes.js +4 -2
  313. package/dist/web/routes/system-routes.js.map +1 -1
  314. package/dist/web/schemas.d.ts +92 -2
  315. package/dist/web/schemas.d.ts.map +1 -1
  316. package/dist/web/schemas.js +111 -10
  317. package/dist/web/schemas.js.map +1 -1
  318. package/dist/web/self-update.d.ts +9 -0
  319. package/dist/web/self-update.d.ts.map +1 -1
  320. package/dist/web/self-update.js +19 -3
  321. package/dist/web/self-update.js.map +1 -1
  322. package/dist/web/sensitive-path.d.ts.map +1 -1
  323. package/dist/web/sensitive-path.js +15 -0
  324. package/dist/web/sensitive-path.js.map +1 -1
  325. package/dist/web/server.d.ts +56 -0
  326. package/dist/web/server.d.ts.map +1 -1
  327. package/dist/web/server.js +285 -17
  328. package/dist/web/server.js.map +1 -1
  329. package/dist/web/session-wait-registry.d.ts +13 -0
  330. package/dist/web/session-wait-registry.d.ts.map +1 -1
  331. package/dist/web/session-wait-registry.js +16 -8
  332. package/dist/web/session-wait-registry.js.map +1 -1
  333. package/package.json +2 -2
  334. package/skills/codeman/SKILL.md +20 -4
  335. package/dist/web/public/app.fe8a6b4c.js.br +0 -0
  336. package/dist/web/public/app.fe8a6b4c.js.gz +0 -0
  337. package/dist/web/public/constants.affcdde6.js.br +0 -0
  338. package/dist/web/public/constants.affcdde6.js.gz +0 -0
  339. package/dist/web/public/i18n.a618ef4e.js +0 -2
  340. package/dist/web/public/i18n.a618ef4e.js.br +0 -0
  341. package/dist/web/public/i18n.a618ef4e.js.gz +0 -0
  342. package/dist/web/public/keyboard-accessory.ff64fafc.js.br +0 -0
  343. package/dist/web/public/keyboard-accessory.ff64fafc.js.gz +0 -0
  344. package/dist/web/public/mobile.1bbd2d67.css +0 -1
  345. package/dist/web/public/mobile.1bbd2d67.css.br +0 -0
  346. package/dist/web/public/mobile.1bbd2d67.css.gz +0 -0
  347. package/dist/web/public/panels-ui.4a56ee63.js.br +0 -0
  348. package/dist/web/public/panels-ui.4a56ee63.js.gz +0 -0
  349. package/dist/web/public/respawn-ui.d7adcf6a.js.br +0 -0
  350. package/dist/web/public/respawn-ui.d7adcf6a.js.gz +0 -0
  351. package/dist/web/public/session-ui.426fb671.js.br +0 -0
  352. package/dist/web/public/session-ui.426fb671.js.gz +0 -0
  353. package/dist/web/public/settings-ui.a191154e.js +0 -106
  354. package/dist/web/public/settings-ui.a191154e.js.br +0 -0
  355. package/dist/web/public/settings-ui.a191154e.js.gz +0 -0
  356. package/dist/web/public/styles.4f0c67a0.css +0 -1
  357. package/dist/web/public/styles.4f0c67a0.css.br +0 -0
  358. package/dist/web/public/styles.4f0c67a0.css.gz +0 -0
  359. package/dist/web/public/terminal-ui.7e787829.js +0 -4
  360. package/dist/web/public/terminal-ui.7e787829.js.br +0 -0
  361. package/dist/web/public/terminal-ui.7e787829.js.gz +0 -0
@@ -15,6 +15,8 @@ import { ApiErrorCode, createErrorResponse, getErrorMessage } from '../../types.
15
15
  import { CreateCaseSchema, CloneCaseSchema, ClonePreflightSchema, LinkCaseSchema, CaseOrderSchema, CaseDefaultGroupSchema, RenameCaseSchema, RemoteCaseLinkSchema, RemoteHostSchema, DockerCaseLinkSchema, DockerCaseAdoptSchema, DockerAdoptPreflightSchema, DockerBrowseSchema, DockerHostSchema, DockerExportSchema, DockerImportSchema, DockerQuickCreateSchema, } from '../schemas.js';
16
16
  import { exportDockerCase, importDockerBundle, listDockerExports, exportBundleName } from '../../docker-export.js';
17
17
  import { cloneRepository, isGitAvailable, isSafeGitRef, parseGitRepositoryUrl, probeGitRemote, } from '../../git-clone.js';
18
+ import { isPathWithin } from '../../git-worktrees.js';
19
+ import { defaultDockerWorkspaces } from './git-status-routes.js';
18
20
  import { generateClaudeMd } from '../../templates/claude-md.js';
19
21
  import { prepareNewCasePath } from '../case-path.js';
20
22
  import { CaseRenameError, isCaseSessionStarting, moveCaseFolder, moveClaudeHistory, renameInOrder, siblingPath, withCaseLock, } from '../../case-rename.js';
@@ -23,6 +25,7 @@ import { readAgentCaseMarker } from '../../agent-case-marker.js';
23
25
  import { settingsWriteBlocker, writeHooksConfig } from '../../hooks-config.js';
24
26
  import { canAccessOwned, getAuthUser, isAdmin, isWorkingDirAllowed, ownerFor, requireAdmin, resolveCasesDir, SETTINGS_PATH, validatePathWithinBase, parseBody, readJsonConfig, } from '../route-helpers.js';
25
27
  import { assertSettingsWritable, mutateSettingsFile, SettingsWriteRefusedError, settingsWriteErrorResponse, } from '../settings-writer.js';
28
+ import { LinkedCasesWriteRefusedError, linkedCasesPath, mutateLinkedCases } from '../linked-cases-writer.js';
26
29
  import { isMultiUserMode } from '../../config/multiuser.js';
27
30
  import { SseEvent } from '../sse-events.js';
28
31
  import { ownerLayoutKey } from '../../tab-layout-persistence.js';
@@ -31,7 +34,6 @@ import { dataPath, getDataDir } from '../../config/instance.js';
31
34
  import { checkDockerAvailable, checkDockerImagePresent, checkDockerTmuxAvailable, ensureAgentBaseImage, DEFAULT_AGENT_IMAGE, dockerContainerName, dockerDisplayPath, probeAdoptableContainer, classifyAdoptContainerConflict, listDockerContainers, browseInContainer, dockerAdoptProbeModes, readDockerCases, readDockerHosts, removeDockerContainer, toSessionDocker, writeDockerCases, writeDockerHosts, } from '../../docker-hosts.js';
32
35
  import { buildDockerRemoveCommand } from '../../tmux-manager.js';
33
36
  import { checkRemoteTmuxAvailable, listRemoteCodemanSessions, readRemoteCases, readRemoteHosts, remoteDisplayPath, writeRemoteCases, writeRemoteHosts, } from '../../remote-hosts.js';
34
- const LINKED_CASES_FILE = dataPath('linked-cases.json');
35
37
  const CODEMAN_CONFIG_DIR = getDataDir();
36
38
  const SAFE_CASE_NAME = /^[a-zA-Z0-9_-]+$/;
37
39
  const DOCKER_EXPORTS_DIR = dataPath('docker-exports');
@@ -54,6 +56,8 @@ const APP_VERSION = (() => {
54
56
  * `/api/cases/link` is admin-only for).
55
57
  */
56
58
  const LOCAL_CLONE_ADMIN_ONLY = 'Cloning from a local path is admin-only in multi-user mode. Use a repository URL instead.';
59
+ /** Refusal text for a clone at or inside a Docker case workspace (git would run its hooks on the host). */
60
+ const DOCKER_CLONE_REFUSAL = 'Cloning is not available into a folder inside a Docker case workspace: a container can write there, and git would run the new repository’s hooks and filters on this machine. Clone outside the workspace and link it, or clone from inside the container.';
57
61
  /**
58
62
  * Whether a clone or preflight must run with git's credential helpers cleared:
59
63
  * a non-admin in multi-user mode. Every user's git runs as the one server
@@ -106,10 +110,19 @@ function agentCreatedInfo(marker) {
106
110
  ...(marker.mode ? { mode: marker.mode } : {}),
107
111
  };
108
112
  }
109
- /** Read and parse linked-cases.json, returning empty object on missing/invalid file. */
113
+ /**
114
+ * Read and parse linked-cases.json, returning empty object on missing/invalid file. For reads only:
115
+ * every write goes through `mutateLinkedCases()`, which re-reads the file inside its own critical
116
+ * section and refuses one that does not parse.
117
+ */
110
118
  async function readLinkedCases() {
111
- return readJsonConfig(LINKED_CASES_FILE, 'linked cases', {});
119
+ return readJsonConfig(linkedCasesPath(), 'linked cases', {});
112
120
  }
121
+ /** A conflict thrown from inside a `mutateLinkedCases()` callback: answered 409, nothing written. */
122
+ function linkedConflict(message) {
123
+ return Object.assign(new Error(message), { conflict: true });
124
+ }
125
+ const isLinkedConflict = (err) => err.conflict === true;
113
126
  /**
114
127
  * Resolve a case name to its directory path, checking linked cases first, then the
115
128
  * user's case space (per-user in multi-user mode, the shared CASES_DIR otherwise).
@@ -418,17 +431,12 @@ export function registerCaseRoutes(app, ctx) {
418
431
  made.push(join(casePath, 'CLAUDE.md'));
419
432
  made.push(join(casePath, '.claude')); // before the write, so a half-written one is undone too
420
433
  await writeHooksConfig(casePath);
421
- const codemanDir = getDataDir();
422
- if (!existsSync(codemanDir))
423
- mkdirSync(codemanDir, { recursive: true });
424
- // Re-read right before writing, so a case linked since the check above is not dropped. This only
425
- // narrows the window: like POST /api/cases/link, the registry write is not serialized, and two
426
- // requests that both read before either writes can still lose one entry.
427
- const fresh = await readLinkedCases();
428
- if (fresh[name])
429
- throw Object.assign(new Error(`Case "${name}" was just linked`), { conflict: true });
430
- fresh[name] = casePath;
431
- await fs.writeFile(LINKED_CASES_FILE, JSON.stringify(fresh, null, 2));
434
+ // The check above ran before the scaffold; a case linked since then wins and this call is undone.
435
+ await mutateLinkedCases((current) => {
436
+ if (current[name])
437
+ throw linkedConflict(`Case "${name}" was just linked`);
438
+ return { ...current, [name]: casePath };
439
+ });
432
440
  ctx.broadcast(SseEvent.CaseCreated, { name, path: casePath });
433
441
  return { success: true, data: { case: { name, path: casePath } } };
434
442
  }
@@ -437,10 +445,14 @@ export function registerCaseRoutes(app, ctx) {
437
445
  // entries inside the empty folder the user picked; never anything else.
438
446
  for (const p of made.reverse())
439
447
  await fs.rm(p, { recursive: true, force: true }).catch(() => undefined);
440
- if (err.conflict) {
448
+ if (isLinkedConflict(err)) {
441
449
  reply.code(409);
442
450
  return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, getErrorMessage(err));
443
451
  }
452
+ if (err instanceof LinkedCasesWriteRefusedError) {
453
+ reply.code(409);
454
+ return createErrorResponse(ApiErrorCode.CONFLICT, err.message);
455
+ }
444
456
  reply.code(500);
445
457
  return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
446
458
  }
@@ -519,7 +531,8 @@ export function registerCaseRoutes(app, ctx) {
519
531
  * request died mid-clone still sees the case appear over SSE when git finishes.
520
532
  *
521
533
  * Deliberately NOT admin-gated in multi-user mode: unlike `/api/cases/link`,
522
- * this writes only inside the caller's own `resolveCasesDir`. Two things would
534
+ * this writes only inside the caller's own `resolveCasesDir`, EXCEPT with a `path` (a custom folder,
535
+ * registered as a linked case in the shared registry), which is admin-only like Link Existing. Two things would
523
536
  * otherwise read through that boundary: a `local`-transport source (refused for
524
537
  * non-admins) and the server account's git credential helpers, which every user
525
538
  * shares (cleared for non-admins, see `cloneWithoutCredentialHelpers`).
@@ -531,7 +544,7 @@ export function registerCaseRoutes(app, ctx) {
531
544
  * user's machine the moment a session starts in the case.
532
545
  */
533
546
  app.post('/api/cases/clone', async (req, reply) => {
534
- const { name, repository, ref, shallow, description } = parseBody(CloneCaseSchema, req.body);
547
+ const { name, repository, ref, shallow, description, path: customPath } = parseBody(CloneCaseSchema, req.body);
535
548
  const user = getAuthUser(req);
536
549
  const parsed = parseGitRepositoryUrl(repository);
537
550
  if (!parsed.cloneable)
@@ -546,8 +559,52 @@ export function registerCaseRoutes(app, ctx) {
546
559
  if (!isGitAvailable()) {
547
560
  return createErrorResponse(ApiErrorCode.OPERATION_FAILED, 'git is not installed on this machine (or not on the server’s PATH).');
548
561
  }
562
+ // A custom folder writes outside the cases directory and registers the path in the shared,
563
+ // ownerless linked-cases registry, so it carries the same bar as POST /api/cases/link.
564
+ if (customPath !== undefined) {
565
+ const denied = adminOnly(req, reply);
566
+ if (denied)
567
+ return denied;
568
+ }
549
569
  const casesDir = resolveCasesDir(user);
550
- const casePath = validatePathWithinBase(name, casesDir);
570
+ let casePath;
571
+ let intoEmptyDir = false;
572
+ if (customPath !== undefined) {
573
+ const prepared = await prepareNewCasePath(customPath, {
574
+ home: homedir(),
575
+ dataDir: getDataDir(),
576
+ casesDirs: [...new Set([casesDir, resolveCasesDir()])],
577
+ casesDirAdvice: 'clone there without a custom folder',
578
+ });
579
+ if (!prepared.ok) {
580
+ reply.code(prepared.code === 'UNREACHABLE'
581
+ ? 422
582
+ : prepared.code === 'NOT_FOUND'
583
+ ? 404
584
+ : prepared.code === 'EXISTS'
585
+ ? 409
586
+ : 400);
587
+ return createErrorResponse(prepared.code === 'UNREACHABLE'
588
+ ? ApiErrorCode.OPERATION_FAILED
589
+ : prepared.code === 'NOT_FOUND'
590
+ ? ApiErrorCode.NOT_FOUND
591
+ : prepared.code === 'EXISTS'
592
+ ? ApiErrorCode.ALREADY_EXISTS
593
+ : ApiErrorCode.INVALID_INPUT, prepared.code === 'EXISTS'
594
+ ? `${customPath} already has files in it. The clone target must be empty or not exist yet.`
595
+ : prepared.reason);
596
+ }
597
+ intoEmptyDir = prepared.existedEmpty;
598
+ casePath = prepared.path;
599
+ // The same folder listed as a local case in the cases dir would make deleting it remove files.
600
+ if (existsSync(join(casesDir, name))) {
601
+ reply.code(409);
602
+ return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, 'A case with this name already exists in codeman-cases.');
603
+ }
604
+ }
605
+ else {
606
+ casePath = validatePathWithinBase(name, casesDir);
607
+ }
551
608
  if (!casePath)
552
609
  return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Invalid case path');
553
610
  // Reject a duplicate name across EVERY case kind before invoking git, so a
@@ -556,22 +613,52 @@ export function registerCaseRoutes(app, ctx) {
556
613
  const linkedCases = await readLinkedCases();
557
614
  const dockerCases = await readDockerCases(CODEMAN_CONFIG_DIR);
558
615
  const remoteCases = await readRemoteCases(CODEMAN_CONFIG_DIR);
559
- if (existsSync(casePath) ||
616
+ const linkedAs = Object.entries(linkedCases).find(([, p]) => p === casePath)?.[0];
617
+ if (linkedAs && customPath !== undefined) {
618
+ reply.code(409);
619
+ return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, `That folder is already the case "${linkedAs}"`);
620
+ }
621
+ if (
622
+ // A custom folder was judged by prepareNewCasePath (an existing empty one is fine).
623
+ (customPath === undefined && existsSync(casePath)) ||
560
624
  linkedCases[name] ||
561
625
  dockerCases.some((item) => item.name === name) ||
562
626
  remoteCases.some((item) => item.name === name)) {
563
627
  return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, 'Case already exists');
564
628
  }
565
- // git creates the leaf, not necessarily the case space above it.
629
+ // git creates the leaf, not necessarily the case space above it (a custom folder's parent
630
+ // already exists: prepareNewCasePath refused a missing one).
631
+ if (customPath === undefined) {
632
+ try {
633
+ mkdirSync(casesDir, { recursive: true });
634
+ }
635
+ catch (err) {
636
+ return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
637
+ }
638
+ }
639
+ // git runs the new repository's hooks and filters on THIS machine once the clone finishes, and a container
640
+ // with the workspace bind-mounted can write both while it fetches. Like git status, fetch and worktrees,
641
+ // never clone at or inside a Docker case workspace. Fails closed when the Docker case list cannot be read.
566
642
  try {
567
- mkdirSync(casesDir, { recursive: true });
643
+ // Probed first (#516): a case's workspace can sit on a mount that stopped answering, and an unbounded
644
+ // realpath there would hold this request and a threadpool worker. Such a root is judged as spelled.
645
+ const roots = await Promise.all((await defaultDockerWorkspaces()).map(async (root) => (await probePathKind(root, { pastCap: true })) === 'unknown'
646
+ ? resolve(root)
647
+ : fs.realpath(root).catch(() => resolve(root))));
648
+ // A custom folder is already symlink-resolved; the default one is judged through its real cases dir.
649
+ const dest = customPath !== undefined ? casePath : join(await fs.realpath(casesDir).catch(() => casesDir), name);
650
+ if (roots.some((root) => isPathWithin(dest, root))) {
651
+ return createErrorResponse(ApiErrorCode.INVALID_INPUT, DOCKER_CLONE_REFUSAL);
652
+ }
568
653
  }
569
- catch (err) {
570
- return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
654
+ catch {
655
+ reply.code(503);
656
+ return createErrorResponse(ApiErrorCode.OPERATION_FAILED, 'Could not read the Docker case list, so Codeman cannot tell whether that folder is a Docker case workspace. Try again.');
571
657
  }
572
658
  const clone = await cloneRepository({
573
659
  repository: parsed.repository,
574
660
  destination: casePath,
661
+ ...(intoEmptyDir ? { intoEmptyDir: true } : {}),
575
662
  withoutCredentialHelpers: cloneWithoutCredentialHelpers(req),
576
663
  ...(ref ? { ref } : {}),
577
664
  ...(shallow ? { shallow: true } : {}),
@@ -628,6 +715,24 @@ export function registerCaseRoutes(app, ctx) {
628
715
  // problem, rather than deleting a tree the user just waited for.
629
716
  warnings.push(`Case scaffolding was incomplete: ${getErrorMessage(err)}`);
630
717
  }
718
+ if (customPath !== undefined) {
719
+ // The name was checked before git ran, which can take minutes. Checked again where the entry is
720
+ // written, so of two clones that claimed one name only the first to finish links it.
721
+ try {
722
+ await mutateLinkedCases((current) => {
723
+ if (current[name])
724
+ throw linkedConflict(`Case "${name}" was linked while the clone ran`);
725
+ return { ...current, [name]: casePath };
726
+ });
727
+ }
728
+ catch (err) {
729
+ // The clone itself succeeded: keep the folder (the user waited for it) and say where it is.
730
+ const conflict = isLinkedConflict(err);
731
+ const refused = err instanceof LinkedCasesWriteRefusedError;
732
+ reply.code(conflict || refused ? 409 : 500);
733
+ return createErrorResponse(conflict ? ApiErrorCode.ALREADY_EXISTS : refused ? ApiErrorCode.CONFLICT : ApiErrorCode.OPERATION_FAILED, `Cloned into ${casePath} but could not register the case (${getErrorMessage(err)}). Use Link Existing to add it.`);
734
+ }
735
+ }
631
736
  ctx.broadcast(SseEvent.CaseCreated, { name, path: casePath });
632
737
  return {
633
738
  success: true,
@@ -1301,24 +1406,21 @@ export function registerCaseRoutes(app, ctx) {
1301
1406
  if (existsSync(casePath)) {
1302
1407
  return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, 'A case with this name already exists in codeman-cases.');
1303
1408
  }
1304
- // Load existing linked cases
1305
- const linkedCases = await readLinkedCases();
1306
- // Check if name is already linked
1307
- if (linkedCases[name]) {
1308
- return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, `Case "${name}" is already linked to ${linkedCases[name]}`);
1309
- }
1310
- // Save the linked case
1311
- linkedCases[name] = expandedPath;
1409
+ // The name check and the write are one step, so two links of one name cannot both succeed.
1312
1410
  try {
1313
- const codemanDir = getDataDir();
1314
- if (!existsSync(codemanDir)) {
1315
- mkdirSync(codemanDir, { recursive: true });
1316
- }
1317
- await fs.writeFile(LINKED_CASES_FILE, JSON.stringify(linkedCases, null, 2));
1411
+ await mutateLinkedCases((current) => {
1412
+ if (current[name])
1413
+ throw linkedConflict(`Case "${name}" is already linked to ${current[name]}`);
1414
+ return { ...current, [name]: expandedPath };
1415
+ });
1318
1416
  ctx.broadcast(SseEvent.CaseLinked, { name, path: expandedPath });
1319
1417
  return { success: true, data: { case: { name, path: expandedPath } } };
1320
1418
  }
1321
1419
  catch (err) {
1420
+ if (isLinkedConflict(err))
1421
+ return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, getErrorMessage(err));
1422
+ if (err instanceof LinkedCasesWriteRefusedError)
1423
+ return createErrorResponse(ApiErrorCode.CONFLICT, err.message);
1322
1424
  return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
1323
1425
  }
1324
1426
  });
@@ -1388,14 +1490,22 @@ export function registerCaseRoutes(app, ctx) {
1388
1490
  // must not unlink one either; skip so control falls through to their local delete.
1389
1491
  const linkedCases = await readLinkedCases();
1390
1492
  if (linkedCases[name] && (!isMultiUserMode() || isAdmin(req))) {
1391
- delete linkedCases[name];
1392
1493
  try {
1393
- await fs.writeFile(LINKED_CASES_FILE, JSON.stringify(linkedCases, null, 2));
1494
+ // Removes this one entry from the registry as it is now, keeping any linked since the read above.
1495
+ await mutateLinkedCases((current) => {
1496
+ if (!Object.hasOwn(current, name))
1497
+ return undefined;
1498
+ const next = { ...current };
1499
+ delete next[name];
1500
+ return next;
1501
+ });
1394
1502
  ctx.broadcast(SseEvent.CaseDeleted, { name, type: 'unlinked' });
1395
1503
  await forgetCaseDefault();
1396
1504
  return { success: true, data: { name } };
1397
1505
  }
1398
1506
  catch (err) {
1507
+ if (err instanceof LinkedCasesWriteRefusedError)
1508
+ return createErrorResponse(ApiErrorCode.CONFLICT, err.message);
1399
1509
  return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
1400
1510
  }
1401
1511
  }
@@ -1564,7 +1674,6 @@ export function registerCaseRoutes(app, ctx) {
1564
1674
  }
1565
1675
  if (moveFolder)
1566
1676
  await moveCaseFolder(oldPath, newPath);
1567
- const originalLinks = isLinked ? await readLinkedCases() : undefined;
1568
1677
  // The settings.json values this rename replaced, set only once it wrote them. A failure puts back
1569
1678
  // these keys alone, so a settings save that landed in between is kept.
1570
1679
  let settingsUndo;
@@ -1585,17 +1694,20 @@ export function registerCaseRoutes(app, ctx) {
1585
1694
  settingsUndo = Object.fromEntries(keys.map((k) => [k, before[k]]));
1586
1695
  }
1587
1696
  }
1588
- if (originalLinks) {
1589
- const links = { ...originalLinks };
1590
- delete links[name];
1591
- links[newName] = newPath;
1592
- try {
1593
- await fs.writeFile(LINKED_CASES_FILE, JSON.stringify(links, null, 2));
1594
- }
1595
- catch (err) {
1596
- await fs.writeFile(LINKED_CASES_FILE, JSON.stringify(originalLinks, null, 2)).catch(() => { });
1597
- throw err;
1598
- }
1697
+ if (isLinked) {
1698
+ // Checked again where the entry is written: the checks above ran before the sessions were
1699
+ // stopped and the folder moved. A failed write leaves the registry as it was.
1700
+ await mutateLinkedCases((current) => {
1701
+ if (current[newName])
1702
+ throw new CaseRenameError('EXISTS', `A case named "${newName}" already exists`);
1703
+ if (current[name] !== oldPath) {
1704
+ throw new CaseRenameError('BUSY', `Case "${name}" was changed while it was being renamed`);
1705
+ }
1706
+ const links = { ...current };
1707
+ delete links[name];
1708
+ links[newName] = newPath;
1709
+ return links;
1710
+ });
1599
1711
  }
1600
1712
  }
1601
1713
  catch (err) {
@@ -1642,6 +1754,9 @@ export function registerCaseRoutes(app, ctx) {
1642
1754
  : ApiErrorCode.OPERATION_FAILED;
1643
1755
  return fail(status, code, err.message, { renameCode: err.code, stoppedSessions: stopped });
1644
1756
  }
1757
+ if (err instanceof LinkedCasesWriteRefusedError) {
1758
+ return fail(409, ApiErrorCode.CONFLICT, err.message, { stoppedSessions: stopped });
1759
+ }
1645
1760
  return fail(500, ApiErrorCode.OPERATION_FAILED, getErrorMessage(err), { stoppedSessions: stopped });
1646
1761
  }
1647
1762
  });