@proletariat/cli 0.2.0 → 0.3.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 (526) hide show
  1. package/README.md +512 -253
  2. package/bin/dev.cmd +3 -0
  3. package/bin/dev.js +5 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +23 -0
  6. package/dist/commands/action/create.d.ts +21 -0
  7. package/dist/commands/action/create.js +126 -0
  8. package/dist/commands/action/delete.d.ts +17 -0
  9. package/dist/commands/action/delete.js +78 -0
  10. package/dist/commands/action/index.d.ts +15 -0
  11. package/dist/commands/action/index.js +107 -0
  12. package/dist/commands/action/list.d.ts +14 -0
  13. package/dist/commands/action/list.js +89 -0
  14. package/dist/commands/action/run.d.ts +19 -0
  15. package/dist/commands/action/run.js +179 -0
  16. package/dist/commands/action/show.d.ts +15 -0
  17. package/dist/commands/action/show.js +47 -0
  18. package/dist/commands/action/update.d.ts +22 -0
  19. package/dist/commands/action/update.js +168 -0
  20. package/dist/commands/agent/index.d.ts +13 -0
  21. package/dist/commands/agent/index.js +131 -0
  22. package/dist/commands/agent/list.d.ts +7 -0
  23. package/dist/commands/agent/list.js +126 -0
  24. package/dist/commands/agent/login.d.ts +16 -0
  25. package/dist/commands/agent/login.js +146 -0
  26. package/dist/commands/agent/rebuild.d.ts +18 -0
  27. package/dist/commands/agent/rebuild.js +133 -0
  28. package/dist/commands/agent/restart.d.ts +17 -0
  29. package/dist/commands/agent/restart.js +116 -0
  30. package/dist/commands/agent/shell.d.ts +23 -0
  31. package/dist/commands/agent/shell.js +378 -0
  32. package/dist/commands/agent/staff/add.d.ts +15 -0
  33. package/dist/commands/agent/staff/add.js +281 -0
  34. package/dist/commands/agent/staff/index.d.ts +14 -0
  35. package/dist/commands/agent/staff/index.js +90 -0
  36. package/dist/commands/agent/staff/list.d.ts +7 -0
  37. package/dist/commands/agent/staff/list.js +90 -0
  38. package/dist/commands/agent/staff/remove.d.ts +16 -0
  39. package/dist/commands/agent/staff/remove.js +137 -0
  40. package/dist/commands/agent/status.d.ts +17 -0
  41. package/dist/commands/agent/status.js +139 -0
  42. package/dist/commands/agent/temp/cleanup.d.ts +23 -0
  43. package/dist/commands/agent/temp/cleanup.js +388 -0
  44. package/dist/commands/agent/temp/index.d.ts +14 -0
  45. package/dist/commands/agent/temp/index.js +82 -0
  46. package/dist/commands/agent/temp/list.d.ts +7 -0
  47. package/dist/commands/agent/temp/list.js +108 -0
  48. package/dist/commands/agent/themes/add-names.d.ts +10 -0
  49. package/dist/commands/agent/themes/add-names.js +67 -0
  50. package/dist/commands/agent/themes/create.d.ts +13 -0
  51. package/dist/commands/agent/themes/create.js +66 -0
  52. package/dist/commands/agent/themes/index.d.ts +9 -0
  53. package/dist/commands/agent/themes/index.js +194 -0
  54. package/dist/commands/agent/themes/list.d.ts +6 -0
  55. package/dist/commands/agent/themes/list.js +41 -0
  56. package/dist/commands/agent/themes/set.d.ts +12 -0
  57. package/dist/commands/agent/themes/set.js +77 -0
  58. package/dist/commands/agent/visit.d.ts +16 -0
  59. package/dist/commands/agent/visit.js +88 -0
  60. package/dist/commands/autocomplete/setup.d.ts +14 -0
  61. package/dist/commands/autocomplete/setup.js +154 -0
  62. package/dist/commands/board/index.d.ts +17 -0
  63. package/dist/commands/board/index.js +255 -0
  64. package/dist/commands/board/watch.d.ts +13 -0
  65. package/dist/commands/board/watch.js +52 -0
  66. package/dist/commands/branch/create.d.ts +50 -0
  67. package/dist/commands/branch/create.js +624 -0
  68. package/dist/commands/branch/index.d.ts +13 -0
  69. package/dist/commands/branch/index.js +50 -0
  70. package/dist/commands/branch/list.d.ts +17 -0
  71. package/dist/commands/branch/list.js +120 -0
  72. package/dist/commands/branch/validate.d.ts +15 -0
  73. package/dist/commands/branch/validate.js +73 -0
  74. package/dist/commands/commit.d.ts +71 -0
  75. package/dist/commands/commit.js +499 -0
  76. package/dist/commands/docker/clean.d.ts +13 -0
  77. package/dist/commands/docker/clean.js +224 -0
  78. package/dist/commands/docker/index.d.ts +19 -0
  79. package/dist/commands/docker/index.js +274 -0
  80. package/dist/commands/docker/list.d.ts +16 -0
  81. package/dist/commands/docker/list.js +200 -0
  82. package/dist/commands/docker/logs.d.ts +14 -0
  83. package/dist/commands/docker/logs.js +118 -0
  84. package/dist/commands/docker/prune.d.ts +14 -0
  85. package/dist/commands/docker/prune.js +211 -0
  86. package/dist/commands/docker/restart.d.ts +14 -0
  87. package/dist/commands/docker/restart.js +129 -0
  88. package/dist/commands/docker/shell.d.ts +14 -0
  89. package/dist/commands/docker/shell.js +103 -0
  90. package/dist/commands/docker/start.d.ts +12 -0
  91. package/dist/commands/docker/start.js +92 -0
  92. package/dist/commands/docker/status.d.ts +7 -0
  93. package/dist/commands/docker/status.js +40 -0
  94. package/dist/commands/docker/stop.d.ts +14 -0
  95. package/dist/commands/docker/stop.js +134 -0
  96. package/dist/commands/docker/sync.d.ts +15 -0
  97. package/dist/commands/docker/sync.js +112 -0
  98. package/dist/commands/epic/activate.d.ts +13 -0
  99. package/dist/commands/epic/activate.js +118 -0
  100. package/dist/commands/epic/archive.d.ts +14 -0
  101. package/dist/commands/epic/archive.js +132 -0
  102. package/dist/commands/epic/create.d.ts +15 -0
  103. package/dist/commands/epic/create.js +137 -0
  104. package/dist/commands/epic/index.d.ts +13 -0
  105. package/dist/commands/epic/index.js +88 -0
  106. package/dist/commands/epic/link/block.d.ts +14 -0
  107. package/dist/commands/epic/link/block.js +79 -0
  108. package/dist/commands/epic/link/duplicates.d.ts +14 -0
  109. package/dist/commands/epic/link/duplicates.js +66 -0
  110. package/dist/commands/epic/link/index.d.ts +19 -0
  111. package/dist/commands/epic/link/index.js +242 -0
  112. package/dist/commands/epic/link/relates.d.ts +14 -0
  113. package/dist/commands/epic/link/relates.js +66 -0
  114. package/dist/commands/epic/link/remove.d.ts +16 -0
  115. package/dist/commands/epic/link/remove.js +89 -0
  116. package/dist/commands/epic/list.d.ts +11 -0
  117. package/dist/commands/epic/list.js +87 -0
  118. package/dist/commands/epic/move.d.ts +15 -0
  119. package/dist/commands/epic/move.js +184 -0
  120. package/dist/commands/epic/progress.d.ts +16 -0
  121. package/dist/commands/epic/progress.js +166 -0
  122. package/dist/commands/epic/project.d.ts +15 -0
  123. package/dist/commands/epic/project.js +219 -0
  124. package/dist/commands/epic/reorder.d.ts +21 -0
  125. package/dist/commands/epic/reorder.js +160 -0
  126. package/dist/commands/epic/spec.d.ts +15 -0
  127. package/dist/commands/epic/spec.js +191 -0
  128. package/dist/commands/epic/ticket.d.ts +18 -0
  129. package/dist/commands/epic/ticket.js +291 -0
  130. package/dist/commands/epic/view.d.ts +13 -0
  131. package/dist/commands/epic/view.js +117 -0
  132. package/dist/commands/execution/index.d.ts +13 -0
  133. package/dist/commands/execution/index.js +70 -0
  134. package/dist/commands/execution/list.d.ts +15 -0
  135. package/dist/commands/execution/list.js +144 -0
  136. package/dist/commands/execution/logs.d.ts +18 -0
  137. package/dist/commands/execution/logs.js +161 -0
  138. package/dist/commands/execution/stop.d.ts +22 -0
  139. package/dist/commands/execution/stop.js +248 -0
  140. package/dist/commands/gh/index.d.ts +9 -0
  141. package/dist/commands/gh/index.js +53 -0
  142. package/dist/commands/gh/login.d.ts +6 -0
  143. package/dist/commands/gh/login.js +57 -0
  144. package/dist/commands/gh/status.d.ts +6 -0
  145. package/dist/commands/gh/status.js +48 -0
  146. package/dist/commands/gh/token.d.ts +6 -0
  147. package/dist/commands/gh/token.js +59 -0
  148. package/dist/commands/init.d.ts +26 -0
  149. package/dist/commands/init.js +200 -0
  150. package/dist/commands/phase/create.d.ts +22 -0
  151. package/dist/commands/phase/create.js +123 -0
  152. package/dist/commands/phase/delete.d.ts +17 -0
  153. package/dist/commands/phase/delete.js +73 -0
  154. package/dist/commands/phase/list.d.ts +12 -0
  155. package/dist/commands/phase/list.js +76 -0
  156. package/dist/commands/phase/move.d.ts +17 -0
  157. package/dist/commands/phase/move.js +115 -0
  158. package/dist/commands/phase/template/apply.d.ts +17 -0
  159. package/dist/commands/phase/template/apply.js +106 -0
  160. package/dist/commands/phase/template/create.d.ts +16 -0
  161. package/dist/commands/phase/template/create.js +58 -0
  162. package/dist/commands/phase/template/delete.d.ts +17 -0
  163. package/dist/commands/phase/template/delete.js +98 -0
  164. package/dist/commands/phase/template/index.d.ts +15 -0
  165. package/dist/commands/phase/template/index.js +128 -0
  166. package/dist/commands/phase/template/list.d.ts +16 -0
  167. package/dist/commands/phase/template/list.js +95 -0
  168. package/dist/commands/phase/template/update.d.ts +17 -0
  169. package/dist/commands/phase/template/update.js +89 -0
  170. package/dist/commands/phase/update.d.ts +23 -0
  171. package/dist/commands/phase/update.js +174 -0
  172. package/dist/commands/pmo/init.d.ts +25 -0
  173. package/dist/commands/pmo/init.js +341 -0
  174. package/dist/commands/pr/create.d.ts +17 -0
  175. package/dist/commands/pr/create.js +242 -0
  176. package/dist/commands/pr/index.d.ts +9 -0
  177. package/dist/commands/pr/index.js +68 -0
  178. package/dist/commands/pr/link.d.ts +14 -0
  179. package/dist/commands/pr/link.js +212 -0
  180. package/dist/commands/pr/status.d.ts +12 -0
  181. package/dist/commands/pr/status.js +161 -0
  182. package/dist/commands/project/archive.d.ts +17 -0
  183. package/dist/commands/project/archive.js +83 -0
  184. package/dist/commands/project/create.d.ts +22 -0
  185. package/dist/commands/project/create.js +143 -0
  186. package/dist/commands/project/delete.d.ts +17 -0
  187. package/dist/commands/project/delete.js +128 -0
  188. package/dist/commands/project/index.d.ts +13 -0
  189. package/dist/commands/project/index.js +64 -0
  190. package/dist/commands/project/list.d.ts +14 -0
  191. package/dist/commands/project/list.js +96 -0
  192. package/dist/commands/project/spec.d.ts +18 -0
  193. package/dist/commands/project/spec.js +216 -0
  194. package/dist/commands/project/unarchive.d.ts +15 -0
  195. package/dist/commands/project/unarchive.js +35 -0
  196. package/dist/commands/project/view.d.ts +16 -0
  197. package/dist/commands/project/view.js +94 -0
  198. package/dist/commands/repo/add.d.ts +21 -0
  199. package/dist/commands/repo/add.js +118 -0
  200. package/dist/commands/repo/index.d.ts +13 -0
  201. package/dist/commands/repo/index.js +114 -0
  202. package/dist/commands/repo/list.d.ts +13 -0
  203. package/dist/commands/repo/list.js +96 -0
  204. package/dist/commands/repo/remove.d.ts +23 -0
  205. package/dist/commands/repo/remove.js +217 -0
  206. package/dist/commands/repo/view.d.ts +15 -0
  207. package/dist/commands/repo/view.js +99 -0
  208. package/dist/commands/session/attach.d.ts +40 -0
  209. package/dist/commands/session/attach.js +307 -0
  210. package/dist/commands/session/index.d.ts +13 -0
  211. package/dist/commands/session/index.js +64 -0
  212. package/dist/commands/session/list.d.ts +21 -0
  213. package/dist/commands/session/list.js +181 -0
  214. package/dist/commands/spec/create.d.ts +19 -0
  215. package/dist/commands/spec/create.js +130 -0
  216. package/dist/commands/spec/index.d.ts +13 -0
  217. package/dist/commands/spec/index.js +68 -0
  218. package/dist/commands/spec/link/depends.d.ts +14 -0
  219. package/dist/commands/spec/link/depends.js +64 -0
  220. package/dist/commands/spec/link/duplicates.d.ts +14 -0
  221. package/dist/commands/spec/link/duplicates.js +63 -0
  222. package/dist/commands/spec/link/index.d.ts +19 -0
  223. package/dist/commands/spec/link/index.js +200 -0
  224. package/dist/commands/spec/link/relates.d.ts +14 -0
  225. package/dist/commands/spec/link/relates.js +63 -0
  226. package/dist/commands/spec/link/remove.d.ts +16 -0
  227. package/dist/commands/spec/link/remove.js +94 -0
  228. package/dist/commands/spec/list.d.ts +12 -0
  229. package/dist/commands/spec/list.js +75 -0
  230. package/dist/commands/spec/plan.d.ts +15 -0
  231. package/dist/commands/spec/plan.js +108 -0
  232. package/dist/commands/spec/ticket.d.ts +18 -0
  233. package/dist/commands/spec/ticket.js +160 -0
  234. package/dist/commands/spec/view.d.ts +15 -0
  235. package/dist/commands/spec/view.js +163 -0
  236. package/dist/commands/status/create.d.ts +21 -0
  237. package/dist/commands/status/create.js +140 -0
  238. package/dist/commands/status/delete.d.ts +13 -0
  239. package/dist/commands/status/delete.js +77 -0
  240. package/dist/commands/status/index.d.ts +14 -0
  241. package/dist/commands/status/index.js +91 -0
  242. package/dist/commands/status/list.d.ts +12 -0
  243. package/dist/commands/status/list.js +93 -0
  244. package/dist/commands/status/move.d.ts +14 -0
  245. package/dist/commands/status/move.js +120 -0
  246. package/dist/commands/status/update.d.ts +20 -0
  247. package/dist/commands/status/update.js +180 -0
  248. package/dist/commands/template/delete.d.ts +15 -0
  249. package/dist/commands/template/delete.js +142 -0
  250. package/dist/commands/template/index.d.ts +10 -0
  251. package/dist/commands/template/index.js +64 -0
  252. package/dist/commands/template/list.d.ts +18 -0
  253. package/dist/commands/template/list.js +157 -0
  254. package/dist/commands/template/phase/apply.d.ts +14 -0
  255. package/dist/commands/template/phase/apply.js +41 -0
  256. package/dist/commands/template/phase/create.d.ts +12 -0
  257. package/dist/commands/template/phase/create.js +29 -0
  258. package/dist/commands/template/phase/delete.d.ts +13 -0
  259. package/dist/commands/template/phase/delete.js +34 -0
  260. package/dist/commands/template/phase/index.d.ts +10 -0
  261. package/dist/commands/template/phase/index.js +62 -0
  262. package/dist/commands/template/phase/list.d.ts +11 -0
  263. package/dist/commands/template/phase/list.js +34 -0
  264. package/dist/commands/template/phase/update.d.ts +13 -0
  265. package/dist/commands/template/phase/update.js +35 -0
  266. package/dist/commands/template/ticket/apply.d.ts +17 -0
  267. package/dist/commands/template/ticket/apply.js +58 -0
  268. package/dist/commands/template/ticket/delete.d.ts +13 -0
  269. package/dist/commands/template/ticket/delete.js +34 -0
  270. package/dist/commands/template/ticket/index.d.ts +10 -0
  271. package/dist/commands/template/ticket/index.js +62 -0
  272. package/dist/commands/template/ticket/list.d.ts +11 -0
  273. package/dist/commands/template/ticket/list.js +34 -0
  274. package/dist/commands/template/ticket/save.d.ts +13 -0
  275. package/dist/commands/template/ticket/save.js +35 -0
  276. package/dist/commands/ticket/bulk.d.ts +13 -0
  277. package/dist/commands/ticket/bulk.js +145 -0
  278. package/dist/commands/ticket/complete.d.ts +16 -0
  279. package/dist/commands/ticket/complete.js +170 -0
  280. package/dist/commands/ticket/create.d.ts +22 -0
  281. package/dist/commands/ticket/create.js +390 -0
  282. package/dist/commands/ticket/delete.d.ts +16 -0
  283. package/dist/commands/ticket/delete.js +178 -0
  284. package/dist/commands/ticket/edit.d.ts +27 -0
  285. package/dist/commands/ticket/edit.js +322 -0
  286. package/dist/commands/ticket/epic.d.ts +20 -0
  287. package/dist/commands/ticket/epic.js +333 -0
  288. package/dist/commands/ticket/index.d.ts +13 -0
  289. package/dist/commands/ticket/index.js +103 -0
  290. package/dist/commands/ticket/link/block.d.ts +14 -0
  291. package/dist/commands/ticket/link/block.js +94 -0
  292. package/dist/commands/ticket/link/duplicates.d.ts +14 -0
  293. package/dist/commands/ticket/link/duplicates.js +93 -0
  294. package/dist/commands/ticket/link/index.d.ts +19 -0
  295. package/dist/commands/ticket/link/index.js +239 -0
  296. package/dist/commands/ticket/link/relates.d.ts +14 -0
  297. package/dist/commands/ticket/link/relates.js +93 -0
  298. package/dist/commands/ticket/link/remove.d.ts +16 -0
  299. package/dist/commands/ticket/link/remove.js +128 -0
  300. package/dist/commands/ticket/list.d.ts +24 -0
  301. package/dist/commands/ticket/list.js +431 -0
  302. package/dist/commands/ticket/move.d.ts +18 -0
  303. package/dist/commands/ticket/move.js +212 -0
  304. package/dist/commands/ticket/project.d.ts +18 -0
  305. package/dist/commands/ticket/project.js +254 -0
  306. package/dist/commands/ticket/reassign.d.ts +19 -0
  307. package/dist/commands/ticket/reassign.js +279 -0
  308. package/dist/commands/ticket/spec.d.ts +18 -0
  309. package/dist/commands/ticket/spec.js +259 -0
  310. package/dist/commands/ticket/status.d.ts +13 -0
  311. package/dist/commands/ticket/status.js +87 -0
  312. package/dist/commands/ticket/template/apply.d.ts +25 -0
  313. package/dist/commands/ticket/template/apply.js +249 -0
  314. package/dist/commands/ticket/template/create.d.ts +19 -0
  315. package/dist/commands/ticket/template/create.js +210 -0
  316. package/dist/commands/ticket/template/delete.d.ts +17 -0
  317. package/dist/commands/ticket/template/delete.js +92 -0
  318. package/dist/commands/ticket/template/index.d.ts +15 -0
  319. package/dist/commands/ticket/template/index.js +118 -0
  320. package/dist/commands/ticket/template/list.d.ts +16 -0
  321. package/dist/commands/ticket/template/list.js +110 -0
  322. package/dist/commands/ticket/template/save.d.ts +14 -0
  323. package/dist/commands/ticket/template/save.js +110 -0
  324. package/dist/commands/ticket/update.d.ts +18 -0
  325. package/dist/commands/ticket/update.js +325 -0
  326. package/dist/commands/ticket/view.d.ts +13 -0
  327. package/dist/commands/ticket/view.js +80 -0
  328. package/dist/commands/whoami.d.ts +9 -0
  329. package/dist/commands/whoami.js +103 -0
  330. package/dist/commands/work/complete.d.ts +13 -0
  331. package/dist/commands/work/complete.js +121 -0
  332. package/dist/commands/work/index.d.ts +13 -0
  333. package/dist/commands/work/index.js +70 -0
  334. package/dist/commands/work/ready.d.ts +24 -0
  335. package/dist/commands/work/ready.js +290 -0
  336. package/dist/commands/work/revise.d.ts +19 -0
  337. package/dist/commands/work/revise.js +377 -0
  338. package/dist/commands/work/spawn-all.d.ts +17 -0
  339. package/dist/commands/work/spawn-all.js +58 -0
  340. package/dist/commands/work/spawn.d.ts +29 -0
  341. package/dist/commands/work/spawn.js +728 -0
  342. package/dist/commands/work/start.d.ts +39 -0
  343. package/dist/commands/work/start.js +1393 -0
  344. package/dist/commands/work/watch.d.ts +31 -0
  345. package/dist/commands/work/watch.js +359 -0
  346. package/dist/commands/workflow/create.d.ts +18 -0
  347. package/dist/commands/workflow/create.js +119 -0
  348. package/dist/commands/workflow/delete.d.ts +17 -0
  349. package/dist/commands/workflow/delete.js +119 -0
  350. package/dist/commands/workflow/index.d.ts +15 -0
  351. package/dist/commands/workflow/index.js +75 -0
  352. package/dist/commands/workflow/list.d.ts +15 -0
  353. package/dist/commands/workflow/list.js +75 -0
  354. package/dist/commands/workflow/switch.d.ts +13 -0
  355. package/dist/commands/workflow/switch.js +117 -0
  356. package/dist/commands/workflow/view.d.ts +16 -0
  357. package/dist/commands/workflow/view.js +114 -0
  358. package/dist/commands/workspace/add.d.ts +12 -0
  359. package/dist/commands/workspace/add.js +74 -0
  360. package/dist/commands/workspace/list.d.ts +9 -0
  361. package/dist/commands/workspace/list.js +153 -0
  362. package/dist/commands/workspace/remove.d.ts +13 -0
  363. package/dist/commands/workspace/remove.js +98 -0
  364. package/dist/commands/workspace/use.d.ts +12 -0
  365. package/dist/commands/workspace/use.js +111 -0
  366. package/dist/hooks/init.d.ts +11 -0
  367. package/dist/hooks/init.js +57 -0
  368. package/dist/index.d.ts +1 -0
  369. package/dist/index.js +1 -0
  370. package/dist/lib/agents/commands.d.ts +189 -0
  371. package/dist/lib/agents/commands.js +893 -0
  372. package/dist/lib/agents/index.d.ts +54 -0
  373. package/dist/lib/agents/index.js +382 -0
  374. package/dist/lib/branch/index.d.ts +120 -0
  375. package/dist/lib/branch/index.js +334 -0
  376. package/dist/lib/colors.d.ts +94 -0
  377. package/dist/lib/colors.js +68 -0
  378. package/dist/lib/commands/docker-command.d.ts +21 -0
  379. package/dist/lib/commands/docker-command.js +27 -0
  380. package/dist/lib/database/index.d.ts +176 -0
  381. package/dist/lib/database/index.js +581 -0
  382. package/dist/lib/docker/resolve.d.ts +38 -0
  383. package/dist/lib/docker/resolve.js +175 -0
  384. package/dist/lib/execution/config.d.ts +150 -0
  385. package/dist/lib/execution/config.js +541 -0
  386. package/dist/lib/execution/devcontainer.d.ts +85 -0
  387. package/dist/lib/execution/devcontainer.js +594 -0
  388. package/dist/lib/execution/index.d.ts +10 -0
  389. package/dist/lib/execution/index.js +10 -0
  390. package/dist/lib/execution/runners.d.ts +53 -0
  391. package/dist/lib/execution/runners.js +1182 -0
  392. package/dist/lib/execution/spawner.d.ts +85 -0
  393. package/dist/lib/execution/spawner.js +548 -0
  394. package/dist/lib/execution/storage.d.ts +159 -0
  395. package/dist/lib/execution/storage.js +425 -0
  396. package/dist/lib/execution/types.d.ts +145 -0
  397. package/dist/lib/execution/types.js +157 -0
  398. package/dist/lib/init/index.d.ts +75 -0
  399. package/dist/lib/init/index.js +355 -0
  400. package/dist/lib/machine-config.d.ts +170 -0
  401. package/dist/lib/machine-config.js +386 -0
  402. package/dist/lib/pmo/base-command.d.ts +195 -0
  403. package/dist/lib/pmo/base-command.js +319 -0
  404. package/dist/lib/pmo/create-spec-folders.d.ts +43 -0
  405. package/dist/lib/pmo/create-spec-folders.js +64 -0
  406. package/dist/lib/pmo/epic-files.d.ts +56 -0
  407. package/dist/lib/pmo/epic-files.js +195 -0
  408. package/dist/lib/pmo/find-pmo.d.ts +14 -0
  409. package/dist/lib/pmo/find-pmo.js +172 -0
  410. package/dist/lib/pmo/index.d.ts +109 -0
  411. package/dist/lib/pmo/index.js +501 -0
  412. package/dist/lib/pmo/markdown.d.ts +31 -0
  413. package/dist/lib/pmo/markdown.js +245 -0
  414. package/dist/lib/pmo/pmo-context.d.ts +27 -0
  415. package/dist/lib/pmo/pmo-context.js +44 -0
  416. package/dist/lib/pmo/schema.d.ts +82 -0
  417. package/dist/lib/pmo/schema.js +531 -0
  418. package/dist/lib/pmo/spec-parser.d.ts +25 -0
  419. package/dist/lib/pmo/spec-parser.js +205 -0
  420. package/dist/lib/pmo/spec-types.d.ts +43 -0
  421. package/dist/lib/pmo/spec-types.js +7 -0
  422. package/dist/lib/pmo/storage/actions.d.ts +34 -0
  423. package/dist/lib/pmo/storage/actions.js +177 -0
  424. package/dist/lib/pmo/storage/base.d.ts +47 -0
  425. package/dist/lib/pmo/storage/base.js +858 -0
  426. package/dist/lib/pmo/storage/dependencies.d.ts +61 -0
  427. package/dist/lib/pmo/storage/dependencies.js +267 -0
  428. package/dist/lib/pmo/storage/epics.d.ts +46 -0
  429. package/dist/lib/pmo/storage/epics.js +243 -0
  430. package/dist/lib/pmo/storage/helpers.d.ts +33 -0
  431. package/dist/lib/pmo/storage/helpers.js +148 -0
  432. package/dist/lib/pmo/storage/index.d.ts +186 -0
  433. package/dist/lib/pmo/storage/index.js +689 -0
  434. package/dist/lib/pmo/storage/phases.d.ts +65 -0
  435. package/dist/lib/pmo/storage/phases.js +392 -0
  436. package/dist/lib/pmo/storage/projects.d.ts +79 -0
  437. package/dist/lib/pmo/storage/projects.js +303 -0
  438. package/dist/lib/pmo/storage/specs.d.ts +77 -0
  439. package/dist/lib/pmo/storage/specs.js +389 -0
  440. package/dist/lib/pmo/storage/statuses.d.ts +63 -0
  441. package/dist/lib/pmo/storage/statuses.js +404 -0
  442. package/dist/lib/pmo/storage/subtasks.d.ts +37 -0
  443. package/dist/lib/pmo/storage/subtasks.js +184 -0
  444. package/dist/lib/pmo/storage/templates.d.ts +40 -0
  445. package/dist/lib/pmo/storage/templates.js +210 -0
  446. package/dist/lib/pmo/storage/tickets.d.ts +57 -0
  447. package/dist/lib/pmo/storage/tickets.js +453 -0
  448. package/dist/lib/pmo/storage/types.d.ts +200 -0
  449. package/dist/lib/pmo/storage/types.js +5 -0
  450. package/dist/lib/pmo/storage/views.d.ts +44 -0
  451. package/dist/lib/pmo/storage/views.js +355 -0
  452. package/dist/lib/pmo/storage-sqlite.d.ts +7 -0
  453. package/dist/lib/pmo/storage-sqlite.js +7 -0
  454. package/dist/lib/pmo/sync-manager.d.ts +92 -0
  455. package/dist/lib/pmo/sync-manager.js +229 -0
  456. package/dist/lib/pmo/types.d.ts +710 -0
  457. package/dist/lib/pmo/types.js +108 -0
  458. package/dist/lib/pmo/utils.d.ts +122 -0
  459. package/dist/lib/pmo/utils.js +174 -0
  460. package/dist/lib/pmo/watcher.d.ts +43 -0
  461. package/dist/lib/pmo/watcher.js +208 -0
  462. package/dist/lib/pr/index.d.ts +150 -0
  463. package/dist/lib/pr/index.js +483 -0
  464. package/dist/lib/prompt-json.d.ts +231 -0
  465. package/dist/lib/prompt-json.js +213 -0
  466. package/dist/lib/repos/index.d.ts +81 -0
  467. package/dist/lib/repos/index.js +679 -0
  468. package/dist/lib/styles.d.ts +98 -0
  469. package/dist/lib/styles.js +195 -0
  470. package/dist/lib/themes.d.ts +128 -0
  471. package/dist/lib/themes.js +301 -0
  472. package/dist/lib/ui/BoardUI.d.ts +21 -0
  473. package/dist/lib/ui/BoardUI.js +85 -0
  474. package/dist/lib/ui/ClaimTicketUI.d.ts +17 -0
  475. package/dist/lib/ui/ClaimTicketUI.js +64 -0
  476. package/dist/lib/ui/CreateTicketUI.d.ts +13 -0
  477. package/dist/lib/ui/CreateTicketUI.js +101 -0
  478. package/dist/lib/workspace.d.ts +66 -0
  479. package/dist/lib/workspace.js +204 -0
  480. package/oclif.manifest.json +10593 -0
  481. package/package.json +103 -56
  482. package/LICENSE +0 -21
  483. package/dist/bin/prlt.d.ts +0 -11
  484. package/dist/bin/prlt.d.ts.map +0 -1
  485. package/dist/bin/prlt.js +0 -144
  486. package/dist/bin/prlt.js.map +0 -1
  487. package/dist/lib/config/index.d.ts +0 -14
  488. package/dist/lib/config/index.d.ts.map +0 -1
  489. package/dist/lib/config/index.js +0 -142
  490. package/dist/lib/config/index.js.map +0 -1
  491. package/dist/lib/config/upgrade.d.ts +0 -2
  492. package/dist/lib/config/upgrade.d.ts.map +0 -1
  493. package/dist/lib/config/upgrade.js +0 -248
  494. package/dist/lib/config/upgrade.js.map +0 -1
  495. package/dist/lib/themes/index.d.ts +0 -8
  496. package/dist/lib/themes/index.d.ts.map +0 -1
  497. package/dist/lib/themes/index.js +0 -80
  498. package/dist/lib/themes/index.js.map +0 -1
  499. package/dist/lib/utils/helpers.d.ts +0 -4
  500. package/dist/lib/utils/helpers.d.ts.map +0 -1
  501. package/dist/lib/utils/helpers.js +0 -39
  502. package/dist/lib/utils/helpers.js.map +0 -1
  503. package/dist/lib/utils/logger.d.ts +0 -4
  504. package/dist/lib/utils/logger.d.ts.map +0 -1
  505. package/dist/lib/utils/logger.js +0 -28
  506. package/dist/lib/utils/logger.js.map +0 -1
  507. package/dist/lib/workspace/index.d.ts +0 -13
  508. package/dist/lib/workspace/index.d.ts.map +0 -1
  509. package/dist/lib/workspace/index.js +0 -116
  510. package/dist/lib/workspace/index.js.map +0 -1
  511. package/dist/lib/worktree/index.d.ts +0 -7
  512. package/dist/lib/worktree/index.d.ts.map +0 -1
  513. package/dist/lib/worktree/index.js +0 -362
  514. package/dist/lib/worktree/index.js.map +0 -1
  515. package/dist/lib/worktree/migrate.d.ts +0 -2
  516. package/dist/lib/worktree/migrate.d.ts.map +0 -1
  517. package/dist/lib/worktree/migrate.js +0 -214
  518. package/dist/lib/worktree/migrate.js.map +0 -1
  519. package/dist/lib/worktree/repair.d.ts +0 -3
  520. package/dist/lib/worktree/repair.d.ts.map +0 -1
  521. package/dist/lib/worktree/repair.js +0 -320
  522. package/dist/lib/worktree/repair.js.map +0 -1
  523. package/dist/types/index.d.ts +0 -57
  524. package/dist/types/index.d.ts.map +0 -1
  525. package/dist/types/index.js +0 -3
  526. package/dist/types/index.js.map +0 -1
@@ -0,0 +1,1182 @@
1
+ /**
2
+ * Execution Runners
3
+ *
4
+ * Implementations for each execution environment (devcontainer, host, docker, vm).
5
+ */
6
+ import { spawn, execSync } from 'node:child_process';
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import * as os from 'node:os';
10
+ import { DEFAULT_EXECUTION_CONFIG, } from './types.js';
11
+ // =============================================================================
12
+ // Terminal Title Helpers
13
+ // =============================================================================
14
+ /**
15
+ * Build a unified name for tmux sessions, window names, and tab titles.
16
+ * Format: "{ticketId}-{action}-{agentName}"
17
+ * Example: "TKT-347-implement-altman"
18
+ */
19
+ export function buildSessionName(context) {
20
+ // Sanitize action name: replace spaces and special chars with hyphens for shell safety
21
+ const action = (context.actionName || 'work').replace(/\s+/g, '-');
22
+ const agent = context.agentName || 'agent';
23
+ return `${context.ticketId}-${action}-${agent}`;
24
+ }
25
+ // Legacy aliases for backwards compatibility
26
+ function buildWindowTitle(context) {
27
+ return buildSessionName(context);
28
+ }
29
+ function buildTmuxWindowName(context) {
30
+ return buildSessionName(context);
31
+ }
32
+ /**
33
+ * Generate shell commands to set the terminal tab/window title.
34
+ * Uses ANSI escape sequences that work across most terminal emulators.
35
+ *
36
+ * \033]0;Title\007 - Sets both window and tab title (most compatible)
37
+ * \033]1;Title\007 - Sets tab title only (iTerm2, some others)
38
+ * \033]2;Title\007 - Sets window title only
39
+ */
40
+ function getSetTitleCommands(title) {
41
+ // Escape any special characters in the title
42
+ const safeTitle = title.replace(/[\\'"]/g, '');
43
+ return `
44
+ # Set terminal tab/window title
45
+ echo -ne "\\033]0;${safeTitle}\\007"
46
+ echo -ne "\\033]1;${safeTitle}\\007"
47
+ `;
48
+ }
49
+ // =============================================================================
50
+ // Executor Commands
51
+ // =============================================================================
52
+ function getExecutorCommand(executor, prompt, skipPermissions = true) {
53
+ switch (executor) {
54
+ case 'claude-code':
55
+ if (skipPermissions) {
56
+ // Skip permissions - agent runs autonomously without prompting
57
+ // Note: NO -p flag - we want interactive mode for streaming output in terminal
58
+ return { cmd: 'claude', args: ['--dangerously-skip-permissions', prompt] };
59
+ }
60
+ // Manual mode - will prompt for each action (still interactive, no -p)
61
+ return { cmd: 'claude', args: [prompt] };
62
+ case 'codex':
63
+ return { cmd: 'codex', args: ['--prompt', prompt] };
64
+ case 'aider':
65
+ return { cmd: 'aider', args: ['--message', prompt] };
66
+ case 'custom':
67
+ // Custom executor should be configured
68
+ return { cmd: 'echo', args: ['Custom executor not configured'] };
69
+ default:
70
+ if (skipPermissions) {
71
+ // Note: NO -p flag - we want interactive mode for streaming output
72
+ return { cmd: 'claude', args: ['--dangerously-skip-permissions', prompt] };
73
+ }
74
+ return { cmd: 'claude', args: [prompt] };
75
+ }
76
+ }
77
+ function buildPrompt(context) {
78
+ let prompt = '';
79
+ // For revisions, lead with the PR feedback
80
+ if (context.isRevision && context.prFeedback) {
81
+ prompt += `# Revision: Address PR Feedback\n\n`;
82
+ prompt += context.prFeedback;
83
+ prompt += `\n\n---\n\n`;
84
+ prompt += `## Original Ticket Context\n\n`;
85
+ }
86
+ // Action instruction (what the agent should do) - START HOOK
87
+ if (context.actionPrompt) {
88
+ prompt += `# Action: ${context.actionName || 'Work'}\n\n`;
89
+ prompt += context.actionPrompt;
90
+ prompt += `\n\n---\n\n`;
91
+ }
92
+ // TICKET CONTENT
93
+ prompt += `# Ticket: ${context.ticketId}\n\n`;
94
+ prompt += `**Title:** ${context.ticketTitle}\n\n`;
95
+ if (context.ticketPriority) {
96
+ prompt += `**Priority:** ${context.ticketPriority}\n`;
97
+ }
98
+ if (context.ticketCategory) {
99
+ prompt += `**Category:** ${context.ticketCategory}\n`;
100
+ }
101
+ if (context.epicTitle) {
102
+ prompt += `**Epic:** ${context.epicTitle}\n`;
103
+ }
104
+ if (context.specId) {
105
+ prompt += `**Spec:** ${context.specId}${context.specTitle ? ` - ${context.specTitle}` : ''}\n`;
106
+ }
107
+ if (context.ticketDescription) {
108
+ prompt += `\n## Description\n\n${context.ticketDescription}\n`;
109
+ }
110
+ if (context.ticketSubtasks && context.ticketSubtasks.length > 0) {
111
+ prompt += `\n## Subtasks\n\n`;
112
+ for (const subtask of context.ticketSubtasks) {
113
+ const checkbox = subtask.done ? '[x]' : '[ ]';
114
+ prompt += `- ${checkbox} ${subtask.title}\n`;
115
+ }
116
+ }
117
+ // Note: Branch setup (fetch + checkout/create) is now handled programmatically
118
+ // in work/start.ts before the agent spawns, so no prompt instructions needed
119
+ // END HOOK - Action-specific completion instructions
120
+ prompt += `\n---\n\n## When Complete\n\n`;
121
+ // For revisions, use the revision-specific end prompt
122
+ if (context.isRevision) {
123
+ prompt += `After addressing the feedback:\n`;
124
+ prompt += `1. Commit your changes using \`prlt commit "your message"\`\n`;
125
+ prompt += `2. Push your changes: \`git push\`\n`;
126
+ prompt += `\nThe PR will be updated automatically.`;
127
+ }
128
+ else if (context.actionEndPrompt) {
129
+ // Use action-specific end prompt, replacing {{TICKET_ID}} placeholder
130
+ let endPrompt = context.actionEndPrompt.replace(/\{\{TICKET_ID\}\}/g, context.ticketId);
131
+ // Also handle the PR flag placeholder if present
132
+ if (endPrompt.includes('--pr')) {
133
+ // Replace --pr with appropriate flag based on createPR setting
134
+ if (!context.createPR) {
135
+ endPrompt = endPrompt.replace(/--pr/g, '--no-pr');
136
+ }
137
+ }
138
+ prompt += endPrompt;
139
+ }
140
+ else {
141
+ // Fallback to default completion instructions (for custom actions without end_prompt)
142
+ if (context.modifiesCode) {
143
+ prompt += `1. **Commit your work** in each repository directory you modified:\n`;
144
+ prompt += ` \`\`\`bash\n`;
145
+ prompt += ` cd /workspace/<repo-name>\n`;
146
+ prompt += ` git add -A\n`;
147
+ prompt += ` prlt commit "describe your change"\n`;
148
+ prompt += ` git push\n`;
149
+ prompt += ` \`\`\`\n`;
150
+ prompt += ` This formats your commit as a conventional commit with the ticket ID.\n`;
151
+ prompt += `\n2. **Mark work as ready** by running:\n`;
152
+ const prFlag = context.createPR ? ' --pr' : ' --no-pr';
153
+ prompt += ` \`\`\`bash\n prlt work ready ${context.ticketId}${prFlag}\n \`\`\`\n`;
154
+ if (context.createPR) {
155
+ prompt += ` This moves the ticket to review and creates a pull request.\n`;
156
+ }
157
+ else {
158
+ prompt += ` This moves the ticket to review.\n`;
159
+ }
160
+ prompt += `\n**IMPORTANT:** Use the global \`prlt\` command (just type \`prlt\`). Do NOT use \`./bin/run.js\` or any local path.`;
161
+ }
162
+ else {
163
+ // Non-code-modifying action without custom end_prompt
164
+ prompt += `When you have completed the task, provide a summary of what you did.`;
165
+ }
166
+ }
167
+ return prompt;
168
+ }
169
+ // =============================================================================
170
+ // Host Runner - Host execution with tmux session persistence
171
+ // =============================================================================
172
+ /**
173
+ * Run command on the host machine with tmux session for persistence.
174
+ * Supports multiple terminal emulators on macOS.
175
+ *
176
+ * Architecture (same as devcontainer):
177
+ * - Always creates a host tmux session for session persistence
178
+ * - displayMode controls whether to open a terminal tab attached to the session
179
+ * - User can reattach with `prlt session attach` if tab is closed
180
+ */
181
+ export async function runHost(context, executor, config, displayMode = 'terminal') {
182
+ // Session name: {ticketId}-{action} (e.g., TKT-347-implement)
183
+ const sessionName = buildTmuxWindowName(context);
184
+ const windowTitle = buildWindowTitle(context);
185
+ const prompt = buildPrompt(context);
186
+ // Terminal - use sandboxed setting
187
+ const skipPermissions = !config.sandboxed;
188
+ const { cmd } = getExecutorCommand(executor, prompt, skipPermissions);
189
+ // Write command to temp script to avoid shell escaping issues
190
+ // Use HQ .proletariat/scripts if available, otherwise fallback to home dir
191
+ const baseDir = context.hqPath
192
+ ? path.join(context.hqPath, '.proletariat', 'scripts')
193
+ : path.join(os.homedir(), '.proletariat', 'scripts');
194
+ fs.mkdirSync(baseDir, { recursive: true });
195
+ const timestamp = Date.now();
196
+ const scriptPath = path.join(baseDir, `exec-${context.ticketId}-${timestamp}.sh`);
197
+ const promptPath = path.join(baseDir, `prompt-${context.ticketId}-${timestamp}.txt`);
198
+ // Write prompt to separate file to avoid any shell escaping issues
199
+ fs.writeFileSync(promptPath, prompt, { mode: 0o644 });
200
+ // Build flags based on config
201
+ const permissionsFlag = skipPermissions ? '--dangerously-skip-permissions ' : '';
202
+ // outputMode: 'print' adds -p flag (final result only), 'interactive' shows streaming UI
203
+ const printFlag = config.outputMode === 'print' ? '-p ' : '';
204
+ // Build script that runs claude and keeps shell open after completion
205
+ const setTitleCmds = getSetTitleCommands(windowTitle);
206
+ const scriptContent = `#!/bin/bash
207
+ # Auto-generated script for ticket ${context.ticketId}
208
+ SCRIPT_PATH="${scriptPath}"
209
+ PROMPT_PATH="${promptPath}"
210
+ ${setTitleCmds}
211
+ echo "🚀 Starting: ${sessionName}"
212
+ echo ""
213
+ cd "${context.worktreePath}"
214
+ ${cmd} ${permissionsFlag}${printFlag}"$(cat "$PROMPT_PATH")"
215
+
216
+ # Clean up script and prompt files
217
+ rm -f "$SCRIPT_PATH" "$PROMPT_PATH"
218
+
219
+ echo ""
220
+ echo "✅ Agent work complete. Press Enter to close or run more commands."
221
+ exec $SHELL
222
+ `;
223
+ fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 });
224
+ try {
225
+ // Check if tmux is available
226
+ execSync('which tmux', { stdio: 'pipe' });
227
+ const terminalApp = config.terminal.app;
228
+ // Step 1: Create host tmux session (detached)
229
+ // Enable mouse mode for native scrolling
230
+ const tmuxCmd = `tmux new-session -d -s "${sessionName}" -n "${sessionName}" "${scriptPath}" \\; set-option -g mouse on \\; set-option -g set-titles on \\; set-option -g set-titles-string "#{window_name}"`;
231
+ try {
232
+ execSync(tmuxCmd, { stdio: 'pipe' });
233
+ }
234
+ catch (error) {
235
+ return {
236
+ success: false,
237
+ error: `Failed to create tmux session: ${error instanceof Error ? error.message : error}`,
238
+ };
239
+ }
240
+ // Step 2: Open terminal tab attached to tmux session (unless background mode)
241
+ if (displayMode === 'background') {
242
+ return {
243
+ success: true,
244
+ sessionId: sessionName,
245
+ };
246
+ }
247
+ // NOTE: Don't use tmux -CC here. While -CC gives native iTerm scrolling,
248
+ // it also causes iTerm to create new windows for tmux sessions.
249
+ // Regular tmux attach inside an iTerm tab works well with mouse mode enabled.
250
+ // User can reattach with `prlt session attach` which offers -CC option.
251
+ // Use clear before attach to ensure clean display
252
+ const attachCmd = `clear && tmux attach -t \\"${sessionName}\\"`;
253
+ switch (terminalApp) {
254
+ case 'iTerm':
255
+ // iTerm2 - new tab in current window
256
+ // Write the tmux attach command directly (no script file needed)
257
+ execSync(`osascript -e '
258
+ tell application "iTerm"
259
+ activate
260
+ if (count of windows) = 0 then
261
+ create window with default profile
262
+ delay 0.3
263
+ tell current session of current window
264
+ set name to "${windowTitle}"
265
+ write text "${attachCmd}"
266
+ end tell
267
+ else
268
+ tell current window
269
+ set newTab to (create tab with default profile)
270
+ delay 0.3
271
+ tell current session of newTab
272
+ set name to "${windowTitle}"
273
+ write text "${attachCmd}"
274
+ end tell
275
+ end tell
276
+ end if
277
+ end tell
278
+ '`);
279
+ break;
280
+ case 'Ghostty':
281
+ // Ghostty - use osascript to open new tab and run command
282
+ execSync(`osascript -e '
283
+ tell application "Ghostty"
284
+ activate
285
+ end tell
286
+ tell application "System Events"
287
+ tell process "Ghostty"
288
+ keystroke "t" using command down
289
+ delay 0.3
290
+ keystroke "${attachCmd}"
291
+ keystroke return
292
+ end tell
293
+ end tell
294
+ '`);
295
+ break;
296
+ case 'WezTerm':
297
+ // WezTerm - use wezterm cli to spawn new tab
298
+ execSync(`wezterm cli spawn --new-window -- bash -c '${attachCmd}'`);
299
+ break;
300
+ case 'Kitty':
301
+ // Kitty - use kitten to open new tab
302
+ execSync(`kitty @ launch --type=tab -- bash -c '${attachCmd}'`);
303
+ break;
304
+ case 'Alacritty':
305
+ // Alacritty doesn't have native tab support, opens new window
306
+ execSync(`osascript -e '
307
+ tell application "Alacritty"
308
+ activate
309
+ end tell
310
+ tell application "System Events"
311
+ tell process "Alacritty"
312
+ keystroke "n" using command down
313
+ delay 0.3
314
+ keystroke "${attachCmd}"
315
+ keystroke return
316
+ end tell
317
+ end tell
318
+ '`);
319
+ break;
320
+ case 'Terminal':
321
+ default:
322
+ // macOS Terminal.app - new tab
323
+ execSync(`osascript -e '
324
+ tell application "Terminal"
325
+ activate
326
+ tell application "System Events"
327
+ tell process "Terminal"
328
+ keystroke "t" using command down
329
+ end tell
330
+ end tell
331
+ delay 0.3
332
+ do script "${attachCmd}" in front window
333
+ end tell
334
+ '`);
335
+ break;
336
+ }
337
+ return {
338
+ success: true,
339
+ sessionId: sessionName,
340
+ };
341
+ }
342
+ catch (error) {
343
+ return {
344
+ success: false,
345
+ error: error instanceof Error ? error.message : `Failed to start host tmux session`,
346
+ };
347
+ }
348
+ }
349
+ // =============================================================================
350
+ // Docker Status Check
351
+ // =============================================================================
352
+ /**
353
+ * Check if Docker daemon is running.
354
+ * Returns true if Docker is available and responsive.
355
+ * Uses retry logic to handle slow Docker Desktop startup.
356
+ */
357
+ export function isDockerRunning() {
358
+ const maxRetries = 3;
359
+ const timeout = 10000; // 10 seconds
360
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
361
+ try {
362
+ execSync('docker info', { stdio: 'pipe', timeout });
363
+ return true;
364
+ }
365
+ catch (err) {
366
+ console.debug(`[runners:docker] Docker check attempt ${attempt}/${maxRetries} failed:`, err);
367
+ if (attempt === maxRetries) {
368
+ return false;
369
+ }
370
+ // Brief pause before retry
371
+ }
372
+ }
373
+ return false;
374
+ }
375
+ // =============================================================================
376
+ // Devcontainer Runner
377
+ // =============================================================================
378
+ /**
379
+ * Clean up old prompt files from the worktree.
380
+ * This is called before writing a new prompt file to prevent accumulation
381
+ * of stale prompt files from failed or interrupted executions.
382
+ */
383
+ function cleanupOldPromptFiles(worktreePath, ticketId) {
384
+ try {
385
+ const files = fs.readdirSync(worktreePath);
386
+ const pattern = ticketId
387
+ ? new RegExp(`^\\.prlt-prompt-${ticketId}-\\d+\\.txt$`)
388
+ : /^\.prlt-prompt-.*\.txt$/;
389
+ for (const file of files) {
390
+ if (pattern.test(file)) {
391
+ try {
392
+ fs.unlinkSync(path.join(worktreePath, file));
393
+ }
394
+ catch (err) {
395
+ console.debug(`[runners:cleanup] Failed to delete ${file}:`, err);
396
+ }
397
+ }
398
+ }
399
+ }
400
+ catch (err) {
401
+ console.debug(`[runners:cleanup] Failed to read directory ${worktreePath}:`, err);
402
+ }
403
+ }
404
+ /**
405
+ * Write prompt to a file inside the worktree so the container can access it.
406
+ * Returns the path to the prompt file (relative to worktree for container access).
407
+ * Cleans up old prompt files for the same ticket before writing.
408
+ */
409
+ function writePromptFile(context) {
410
+ // Clean up old prompt files for this ticket before creating a new one
411
+ cleanupOldPromptFiles(context.worktreePath, context.ticketId);
412
+ const prompt = buildPrompt(context);
413
+ const filename = `.prlt-prompt-${context.ticketId}-${Date.now()}.txt`;
414
+ const hostPath = path.join(context.worktreePath, filename);
415
+ fs.writeFileSync(hostPath, prompt, { mode: 0o644 });
416
+ // Container mounts agentDir at /workspace
417
+ // If worktreePath is a subdirectory of agentDir, we need the relative path
418
+ // e.g., agentDir=/agents/altman, worktreePath=/agents/altman/textdeck
419
+ // -> containerPath=/workspace/textdeck/.prlt-prompt-....txt
420
+ const relativePath = path.relative(context.agentDir, context.worktreePath);
421
+ const containerPath = relativePath
422
+ ? `/workspace/${relativePath}/${filename}`
423
+ : `/workspace/${filename}`;
424
+ return { hostPath, containerPath };
425
+ }
426
+ /**
427
+ * Build the command to run Claude inside the container.
428
+ * Uses devcontainer exec which handles user context and working directory automatically.
429
+ * Uses a prompt file to avoid shell escaping issues.
430
+ */
431
+ /**
432
+ * Get the container ID for a devcontainer workspace.
433
+ */
434
+ function getDevcontainerContainerId(agentDir) {
435
+ try {
436
+ // devcontainer up outputs JSON with container ID
437
+ const result = execSync(`devcontainer up --workspace-folder "${agentDir}" 2>/dev/null | tail -1`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
438
+ const json = JSON.parse(result.trim());
439
+ return json.containerId || null;
440
+ }
441
+ catch (err) {
442
+ console.debug('[runners:devcontainer] devcontainer up failed, trying docker ps fallback:', err);
443
+ try {
444
+ const containerId = execSync(`docker ps -q --filter "label=devcontainer.local_folder=${agentDir}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
445
+ return containerId || null;
446
+ }
447
+ catch (fallbackErr) {
448
+ console.debug('[runners:devcontainer] docker ps fallback also failed:', fallbackErr);
449
+ return null;
450
+ }
451
+ }
452
+ }
453
+ function buildDevcontainerCommand(context, executor, promptFile, containerId, outputMode = 'interactive', sandboxed = true, displayMode = 'terminal') {
454
+ // Get base command (just 'claude' for claude-code)
455
+ let baseCmd;
456
+ switch (executor) {
457
+ case 'claude-code':
458
+ baseCmd = 'claude';
459
+ break;
460
+ case 'codex':
461
+ baseCmd = 'codex';
462
+ break;
463
+ case 'aider':
464
+ baseCmd = 'aider';
465
+ break;
466
+ default:
467
+ baseCmd = 'claude';
468
+ }
469
+ // Calculate the relative path from agentDir to worktreePath for cd
470
+ const relativePath = path.relative(context.agentDir, context.worktreePath);
471
+ const cdCmd = relativePath ? `cd /workspace/${relativePath} && ` : '';
472
+ // Build Claude flags based on output mode and sandboxed setting
473
+ // - interactive: No -p flag, shows streaming UI (watch Claude work in real-time)
474
+ // - print: Uses -p flag, outputs final result only (better for logs/automation)
475
+ const printFlag = outputMode === 'print' ? '-p ' : '';
476
+ // sandboxed=true means safe mode (no --dangerously-skip-permissions)
477
+ // sandboxed=false means danger mode (use --dangerously-skip-permissions)
478
+ const permissionsFlag = !sandboxed ? '--dangerously-skip-permissions ' : '';
479
+ // Build the claude command
480
+ const claudeCmd = `${cdCmd}${baseCmd} ${permissionsFlag}${printFlag}"$(cat ${promptFile})" && rm -f ${promptFile}`;
481
+ // If we have a container ID, use docker exec for streaming
482
+ if (containerId) {
483
+ // Use -it flags only for terminal/foreground modes where a TTY is available
484
+ // Background mode runs without a TTY, so -it flags would cause "not a TTY" error
485
+ const ttyFlags = displayMode === 'background' ? '' : '-it ';
486
+ // Direct mode - run claude directly (tmux setup is handled by runDevcontainerInTmux)
487
+ return `docker exec ${ttyFlags}${containerId} bash -c '${claudeCmd}'`;
488
+ }
489
+ // Fallback to devcontainer exec (no streaming, but works)
490
+ return `devcontainer exec --workspace-folder "${context.agentDir}" bash -c '${claudeCmd}'`;
491
+ }
492
+ /**
493
+ * Copy Claude Code credentials (~/.claude.json) into the agent directory.
494
+ * This makes the subscription credentials available inside the devcontainer
495
+ * since the agent directory is mounted at /workspace.
496
+ */
497
+ function copyClaudeCredentials(agentDir) {
498
+ const sourceFile = path.join(os.homedir(), '.claude.json');
499
+ const destFile = path.join(agentDir, '.claude.json');
500
+ if (fs.existsSync(sourceFile)) {
501
+ try {
502
+ fs.copyFileSync(sourceFile, destFile);
503
+ }
504
+ catch (err) {
505
+ console.debug('[runners:credentials] Failed to copy .claude.json:', err);
506
+ }
507
+ }
508
+ }
509
+ /**
510
+ * Run command inside a devcontainer.
511
+ * Uses the devcontainer CLI to start/exec in a VS Code devcontainer.
512
+ * Provides filesystem isolation - agent can only access mounted worktrees.
513
+ *
514
+ * @param displayMode - How to display output (terminal, foreground, background, tmux)
515
+ * @param sessionManager - How to manage the session inside the container (tmux, direct)
516
+ */
517
+ export async function runDevcontainer(context, executor, config, displayMode = 'terminal', sessionManager = 'direct') {
518
+ // Devcontainer config is in the agent directory, not the worktree
519
+ // (worktree may be a subdirectory like agents/altman/textdeck)
520
+ const devcontainerPath = path.join(context.agentDir, '.devcontainer');
521
+ const devcontainerJson = path.join(devcontainerPath, 'devcontainer.json');
522
+ // Check if devcontainer config exists
523
+ if (!fs.existsSync(devcontainerJson)) {
524
+ return {
525
+ success: false,
526
+ error: `No devcontainer.json found at ${devcontainerPath}. Run 'prlt agent add' to set up the agent with devcontainer config.`,
527
+ };
528
+ }
529
+ try {
530
+ // Check devcontainer CLI is installed
531
+ try {
532
+ execSync('which devcontainer', { stdio: 'pipe' });
533
+ }
534
+ catch (err) {
535
+ console.debug('[runners:devcontainer] devcontainer CLI not found:', err);
536
+ return {
537
+ success: false,
538
+ error: 'devcontainer CLI not found. Install with: npm install -g @devcontainers/cli',
539
+ };
540
+ }
541
+ // Check if Docker is running
542
+ if (!isDockerRunning()) {
543
+ return {
544
+ success: false,
545
+ error: 'Docker is not running. Please start Docker Desktop and try again.',
546
+ };
547
+ }
548
+ // Copy Claude credentials into agent directory so container can access them
549
+ copyClaudeCredentials(context.agentDir);
550
+ // Set environment variables for devcontainer mounts
551
+ // PRLT_HQ_PATH: allows agent to access the HQ database and run `prlt ticket complete`
552
+ // PRLT_PMO_PATH: allows agent to access the PMO (can be anywhere, e.g., /hq/repos/myrepo/pmo)
553
+ // PRLT_REPO_PATH: mounts the entire proletariat repo into the container (until prlt is on npm)
554
+ const env = { ...process.env };
555
+ if (context.hqPath) {
556
+ env.PRLT_HQ_PATH = context.hqPath;
557
+ }
558
+ if (context.pmoPath) {
559
+ env.PRLT_PMO_PATH = context.pmoPath;
560
+ }
561
+ // Ensure GitHub token is available for git push operations
562
+ // Try to get token from gh CLI if not already in environment
563
+ if (!env.GITHUB_TOKEN && !env.GH_TOKEN) {
564
+ try {
565
+ const token = execSync('gh auth token', { encoding: 'utf-8', stdio: 'pipe' }).trim();
566
+ if (token) {
567
+ env.GITHUB_TOKEN = token;
568
+ env.GH_TOKEN = token;
569
+ }
570
+ }
571
+ catch (err) {
572
+ console.debug('[runners:devcontainer] gh auth token failed:', err);
573
+ }
574
+ }
575
+ // Set repo path to the proletariat monorepo (auto-detect from current CLI location)
576
+ // We mount the entire repo so node_modules resolution works correctly
577
+ if (!env.PRLT_REPO_PATH) {
578
+ // Get the directory where this CLI is running from (apps/cli)
579
+ const cliDir = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..', '..');
580
+ // Go up to the monorepo root (repos/proletariat)
581
+ const repoDir = path.resolve(cliDir, '..', '..');
582
+ if (fs.existsSync(path.join(repoDir, 'apps', 'cli', 'bin', 'run.js'))) {
583
+ env.PRLT_REPO_PATH = repoDir;
584
+ }
585
+ }
586
+ // Start or reuse container (devcontainer up is idempotent)
587
+ // Use agentDir as the workspace folder since that's where .devcontainer is
588
+ try {
589
+ execSync(`devcontainer up --workspace-folder "${context.agentDir}"`, {
590
+ stdio: 'pipe',
591
+ env,
592
+ });
593
+ }
594
+ catch (error) {
595
+ return {
596
+ success: false,
597
+ error: `Failed to start devcontainer: ${error instanceof Error ? error.message : error}`,
598
+ };
599
+ }
600
+ // Write prompt to file in worktree (accessible by container)
601
+ const { hostPath: promptHostPath, containerPath: promptFile } = writePromptFile(context);
602
+ // Get container ID for docker exec (enables streaming output with TTY)
603
+ const containerId = getDevcontainerContainerId(context.agentDir);
604
+ // Build the devcontainer exec command (just runs claude directly)
605
+ // tmux session setup is handled by runDevcontainerInTmux, not buildDevcontainerCommand
606
+ const devcontainerCmd = buildDevcontainerCommand(context, executor, promptFile, containerId || undefined, config.outputMode, config.sandboxed, displayMode);
607
+ // Execute based on display mode
608
+ // When sessionManager is 'tmux', always use tmux inside container for session persistence
609
+ // (allows reattach via `prlt session attach` even for background mode)
610
+ let result;
611
+ if (sessionManager === 'tmux') {
612
+ // Use tmux inside container - pass displayMode to control whether to open terminal tab
613
+ // Pass containerId directly to avoid regex extraction issues with devcontainer exec commands
614
+ result = await runDevcontainerInTmux(context, devcontainerCmd, config, displayMode, containerId || undefined);
615
+ }
616
+ else {
617
+ switch (displayMode) {
618
+ case 'background':
619
+ result = await runDevcontainerInBackground(context, devcontainerCmd);
620
+ break;
621
+ case 'terminal':
622
+ default:
623
+ result = await runDevcontainerInTerminal(context, devcontainerCmd, config);
624
+ break;
625
+ }
626
+ }
627
+ // Clean up prompt file if execution failed to start
628
+ // (successful executions clean up the file themselves via the command)
629
+ if (!result.success && fs.existsSync(promptHostPath)) {
630
+ try {
631
+ fs.unlinkSync(promptHostPath);
632
+ }
633
+ catch (err) {
634
+ console.debug('[runners:devcontainer] Failed to cleanup prompt file:', err);
635
+ }
636
+ }
637
+ // Override containerId with the real Docker container ID (not the placeholder)
638
+ if (result.success && containerId) {
639
+ result.containerId = containerId;
640
+ }
641
+ // Set sessionId when using tmux inside the container
642
+ if (result.success && sessionManager === 'tmux') {
643
+ const sessionId = context.ticketId.replace(/[^a-zA-Z0-9-]/g, '-');
644
+ result.sessionId = sessionId;
645
+ // For terminal display mode, verify the tmux session was actually created
646
+ // (terminal spawns asynchronously, so we need to wait and check)
647
+ if (displayMode === 'terminal' && containerId) {
648
+ // Wait for the terminal to execute the script
649
+ await new Promise(resolve => setTimeout(resolve, 3000));
650
+ // Check if tmux session exists inside the container
651
+ try {
652
+ const checkResult = execSync(`docker exec ${containerId} tmux has-session -t ${sessionId} 2>&1`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
653
+ // Session exists - success
654
+ }
655
+ catch (err) {
656
+ console.debug(`[runners:devcontainer] tmux session ${sessionId} not found in container:`, err);
657
+ result.success = false;
658
+ result.error = `Failed to create tmux session "${sessionId}" inside container. Check terminal for errors.`;
659
+ }
660
+ }
661
+ }
662
+ return result;
663
+ }
664
+ catch (error) {
665
+ // Clean up any orphaned prompt files on error
666
+ cleanupOldPromptFiles(context.worktreePath, context.ticketId);
667
+ return {
668
+ success: false,
669
+ error: error instanceof Error ? error.message : 'Failed to run in devcontainer',
670
+ };
671
+ }
672
+ }
673
+ /**
674
+ * Run devcontainer command in a new terminal window.
675
+ * Uses a temp script file to avoid shell escaping issues with complex prompts.
676
+ */
677
+ async function runDevcontainerInTerminal(context, devcontainerCmd, config) {
678
+ if (process.platform !== 'darwin') {
679
+ return {
680
+ success: false,
681
+ error: 'Terminal mode is only supported on macOS. Use background mode instead.',
682
+ };
683
+ }
684
+ const terminalApp = config.terminal.app;
685
+ // Write command to temp script to avoid shell escaping issues
686
+ // Use HQ .proletariat/scripts if available, otherwise fallback to home dir
687
+ const baseDir = context.hqPath
688
+ ? path.join(context.hqPath, '.proletariat', 'scripts')
689
+ : path.join(os.homedir(), '.proletariat', 'scripts');
690
+ fs.mkdirSync(baseDir, { recursive: true });
691
+ const scriptPath = path.join(baseDir, `exec-${context.ticketId}-${Date.now()}.sh`);
692
+ // Build window title for terminal tab
693
+ const windowTitle = buildWindowTitle(context);
694
+ const setTitleCmds = getSetTitleCommands(windowTitle);
695
+ // Write script - run the command directly
696
+ // No auth check needed - if auth is required, Claude will show "Invalid API key"
697
+ // and user can run /login from there
698
+ const scriptContent = `#!/bin/bash
699
+ # Auto-generated script for ticket ${context.ticketId}
700
+ ${setTitleCmds}
701
+ echo "🚀 Starting ticket execution: ${context.ticketId}"
702
+ echo ""
703
+
704
+ # Run the ticket
705
+ ${devcontainerCmd}
706
+
707
+ # Clean up script file
708
+ rm -f "${scriptPath}"
709
+
710
+ # Keep shell open after completion
711
+ exec $SHELL
712
+ `;
713
+ fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 });
714
+ try {
715
+ switch (terminalApp) {
716
+ case 'iTerm':
717
+ // Run script file directly - iTerm will execute it with proper TTY
718
+ execSync(`osascript -e '
719
+ tell application "iTerm"
720
+ activate
721
+ tell current window
722
+ set newTab to (create tab with default profile)
723
+ tell current session of newTab
724
+ write text "${scriptPath}"
725
+ end tell
726
+ end tell
727
+ end tell
728
+ '`);
729
+ break;
730
+ case 'Ghostty':
731
+ // Use source to preserve TTY for docker exec
732
+ execSync(`osascript -e '
733
+ tell application "Ghostty"
734
+ activate
735
+ end tell
736
+ tell application "System Events"
737
+ tell process "Ghostty"
738
+ keystroke "t" using command down
739
+ delay 0.3
740
+ keystroke "source ${scriptPath}"
741
+ keystroke return
742
+ end tell
743
+ end tell
744
+ '`);
745
+ break;
746
+ case 'WezTerm':
747
+ // Use bash -c source to preserve TTY
748
+ execSync(`wezterm cli spawn --new-window -- bash -c 'source ${scriptPath}'`);
749
+ break;
750
+ case 'Kitty':
751
+ // Use bash -c source to preserve TTY
752
+ execSync(`kitty @ launch --type=tab -- bash -c 'source ${scriptPath}'`);
753
+ break;
754
+ case 'Alacritty':
755
+ // Use source to preserve TTY for docker exec
756
+ execSync(`osascript -e '
757
+ tell application "Alacritty"
758
+ activate
759
+ end tell
760
+ tell application "System Events"
761
+ tell process "Alacritty"
762
+ keystroke "n" using command down
763
+ delay 0.3
764
+ keystroke "source ${scriptPath}"
765
+ keystroke return
766
+ end tell
767
+ end tell
768
+ '`);
769
+ break;
770
+ case 'Terminal':
771
+ default:
772
+ // Use source to preserve TTY for docker exec
773
+ execSync(`osascript -e '
774
+ tell application "Terminal"
775
+ activate
776
+ tell application "System Events"
777
+ tell process "Terminal"
778
+ keystroke "t" using command down
779
+ end tell
780
+ end tell
781
+ delay 0.3
782
+ do script "source ${scriptPath}" in front window
783
+ end tell
784
+ '`);
785
+ break;
786
+ }
787
+ return {
788
+ success: true,
789
+ containerId: `devcontainer-${context.agentName}`,
790
+ sessionId: `terminal-${context.ticketId}`,
791
+ };
792
+ }
793
+ catch (error) {
794
+ return {
795
+ success: false,
796
+ error: error instanceof Error ? error.message : `Failed to open ${terminalApp}`,
797
+ };
798
+ }
799
+ }
800
+ /**
801
+ * Run devcontainer command in background, logging to file
802
+ */
803
+ async function runDevcontainerInBackground(context, devcontainerCmd) {
804
+ // Create logs directory
805
+ const logsDir = path.join(os.homedir(), '.proletariat', 'logs');
806
+ fs.mkdirSync(logsDir, { recursive: true });
807
+ const logPath = path.join(logsDir, `work-${context.ticketId}-${Date.now()}.log`);
808
+ const logStream = fs.openSync(logPath, 'w');
809
+ const child = spawn('sh', ['-c', devcontainerCmd], {
810
+ detached: true,
811
+ stdio: ['ignore', logStream, logStream],
812
+ });
813
+ child.unref();
814
+ return {
815
+ success: true,
816
+ pid: child.pid?.toString(),
817
+ containerId: `devcontainer-${context.agentName}`,
818
+ logPath,
819
+ };
820
+ }
821
+ /**
822
+ * Run devcontainer command in tmux session INSIDE the container.
823
+ *
824
+ * Architecture: Container tmux only (simple, no nesting)
825
+ * 1. Start tmux session INSIDE the container (detached) - runs claude
826
+ * 2. Open iTerm tab that attaches directly to the container's tmux
827
+ *
828
+ * Benefits:
829
+ * - Session persists even if you close iTerm tab
830
+ * - No nested tmux = proper scrolling
831
+ * - Can reattach anytime via `prlt session attach`
832
+ * - Sessions tracked in workspace.db
833
+ */
834
+ async function runDevcontainerInTmux(context, devcontainerCmd, config, displayMode = 'terminal', containerId) {
835
+ // Session name: {ticketId}-{action} (e.g., TKT-347-implement)
836
+ const sessionName = buildTmuxWindowName(context);
837
+ const windowTitle = buildWindowTitle(context);
838
+ try {
839
+ // Get container ID - prefer passed value, fallback to extracting from command
840
+ // The devcontainerCmd is like: docker exec [-it] <containerId> bash -c '...'
841
+ // Note: -it flags are optional (not present in background mode)
842
+ let actualContainerId = containerId;
843
+ if (!actualContainerId) {
844
+ const containerIdMatch = devcontainerCmd.match(/docker exec\s+(?:-it\s+)?(\S+)/);
845
+ if (containerIdMatch) {
846
+ actualContainerId = containerIdMatch[1];
847
+ }
848
+ }
849
+ if (!actualContainerId) {
850
+ return {
851
+ success: false,
852
+ error: 'Could not determine container ID for tmux session',
853
+ };
854
+ }
855
+ // Check if tmux is available inside the container
856
+ try {
857
+ execSync(`docker exec ${actualContainerId} which tmux`, { stdio: 'pipe' });
858
+ }
859
+ catch {
860
+ return {
861
+ success: false,
862
+ error: `tmux is not installed in the devcontainer. ` +
863
+ `Add 'tmux' to your devcontainer's Dockerfile (e.g., apt-get install -y tmux) ` +
864
+ `or use the default prlt devcontainer template which includes tmux.`,
865
+ };
866
+ }
867
+ // Step 1: Start tmux session INSIDE the container (detached)
868
+ // Extract the claude command from the devcontainer command
869
+ const cmdMatch = devcontainerCmd.match(/bash -c '(.+)'$/);
870
+ const claudeCmd = cmdMatch ? cmdMatch[1] : devcontainerCmd;
871
+ // Create a script inside the container that runs claude and keeps shell open
872
+ const tmuxScript = `#!/bin/bash
873
+ echo "🚀 Starting: ${sessionName}"
874
+ echo ""
875
+ ${claudeCmd}
876
+ echo ""
877
+ echo "✅ Agent work complete. Press Enter to close or run more commands."
878
+ exec bash
879
+ `;
880
+ const base64Script = Buffer.from(tmuxScript).toString('base64');
881
+ const scriptPath = `/tmp/prlt-${sessionName}.sh`;
882
+ // Write script and start tmux session inside container
883
+ // -n sets the window name (shows in iTerm tab title with -CC mode)
884
+ // sessionName is already ticket-action-agent format
885
+ // Enable mouse mode for native scrolling (trackpad/mouse wheel works without -CC mode)
886
+ // set-titles on + set-titles-string: makes tmux set terminal title to window name
887
+ const setupCmd = `echo ${base64Script} | base64 -d > ${scriptPath} && chmod +x ${scriptPath} && tmux new-session -d -s "${sessionName}" -n "${sessionName}" "${scriptPath}" \\; set-option -g mouse on \\; set-option -g set-titles on \\; set-option -g set-titles-string "#{window_name}"`;
888
+ try {
889
+ execSync(`docker exec ${actualContainerId} bash -c '${setupCmd}'`, { stdio: 'pipe' });
890
+ }
891
+ catch (error) {
892
+ return {
893
+ success: false,
894
+ error: `Failed to start tmux inside container: ${error instanceof Error ? error.message : error}`,
895
+ };
896
+ }
897
+ // Step 2: Open iTerm tab that attaches directly to container's tmux
898
+ // Skip this step for background mode - just return success after tmux session is created
899
+ // User can reattach later with `prlt session attach`
900
+ if (displayMode === 'background') {
901
+ return {
902
+ success: true,
903
+ containerId: actualContainerId,
904
+ sessionId: sessionName, // Container tmux session name for tracking
905
+ };
906
+ }
907
+ // NOTE: We don't use tmux -CC (control mode) here because we're already
908
+ // creating a tab via AppleScript. Using -CC would cause iTerm to create
909
+ // another window for the tmux session (double windows).
910
+ // Users can reattach with `prlt session attach` which uses -CC for native scrolling.
911
+ const attachCmd = `docker exec -it ${actualContainerId} tmux -u attach -t "${sessionName}"`;
912
+ const baseDir = context.hqPath
913
+ ? path.join(context.hqPath, '.proletariat', 'scripts')
914
+ : path.join(os.homedir(), '.proletariat', 'scripts');
915
+ fs.mkdirSync(baseDir, { recursive: true });
916
+ const hostScriptPath = path.join(baseDir, `attach-${sessionName}-${Date.now()}.sh`);
917
+ const setTitleCmds = getSetTitleCommands(windowTitle);
918
+ const hostScript = `#!/bin/bash
919
+ ${setTitleCmds}
920
+ # Attach to container tmux session
921
+ # Session: ${sessionName}
922
+ # Container: ${actualContainerId}
923
+ ${attachCmd}
924
+
925
+ # Clean up
926
+ rm -f "${hostScriptPath}"
927
+ exec $SHELL
928
+ `;
929
+ fs.writeFileSync(hostScriptPath, hostScript, { mode: 0o755 });
930
+ // Open iTerm tab and run the attach script
931
+ const terminalApp = config.terminal.app;
932
+ switch (terminalApp) {
933
+ case 'iTerm':
934
+ // Create new tab in existing window, or create new window if none exists
935
+ // Set tab name via AppleScript for reliable naming
936
+ execSync(`osascript -e '
937
+ tell application "iTerm"
938
+ activate
939
+ if (count of windows) = 0 then
940
+ create window with default profile
941
+ tell current session of current window
942
+ set name to "${windowTitle}"
943
+ write text "${hostScriptPath}"
944
+ end tell
945
+ else
946
+ tell current window
947
+ create tab with default profile
948
+ tell current session
949
+ set name to "${windowTitle}"
950
+ write text "${hostScriptPath}"
951
+ end tell
952
+ end tell
953
+ end if
954
+ end tell
955
+ '`);
956
+ break;
957
+ case 'Ghostty':
958
+ execSync(`osascript -e '
959
+ tell application "Ghostty"
960
+ activate
961
+ end tell
962
+ tell application "System Events"
963
+ tell process "Ghostty"
964
+ keystroke "t" using command down
965
+ delay 0.3
966
+ keystroke "${hostScriptPath}"
967
+ keystroke return
968
+ end tell
969
+ end tell
970
+ '`);
971
+ break;
972
+ case 'Terminal':
973
+ default:
974
+ execSync(`osascript -e '
975
+ tell application "Terminal"
976
+ activate
977
+ tell application "System Events"
978
+ tell process "Terminal"
979
+ keystroke "t" using command down
980
+ end tell
981
+ end tell
982
+ delay 0.3
983
+ do script "${hostScriptPath}" in front window
984
+ end tell
985
+ '`);
986
+ break;
987
+ }
988
+ return {
989
+ success: true,
990
+ containerId: actualContainerId,
991
+ sessionId: sessionName, // Container tmux session name for tracking
992
+ };
993
+ }
994
+ catch (error) {
995
+ return {
996
+ success: false,
997
+ error: error instanceof Error ? error.message : 'Failed to start tmux session in container',
998
+ };
999
+ }
1000
+ }
1001
+ /**
1002
+ * Legacy: Run devcontainer in host-side tmux (kept for non-container modes)
1003
+ */
1004
+ async function runDevcontainerInHostTmux(context, devcontainerCmd, config) {
1005
+ const sessionName = config.tmux.session;
1006
+ const windowName = buildTmuxWindowName(context);
1007
+ try {
1008
+ // Check if tmux is available on host
1009
+ execSync('which tmux', { stdio: 'pipe' });
1010
+ // Write command to temp script
1011
+ const baseDir = context.hqPath
1012
+ ? path.join(context.hqPath, '.proletariat', 'scripts')
1013
+ : path.join(os.homedir(), '.proletariat', 'scripts');
1014
+ fs.mkdirSync(baseDir, { recursive: true });
1015
+ const scriptPath = path.join(baseDir, `exec-${context.ticketId}-${Date.now()}.sh`);
1016
+ const windowTitle = buildWindowTitle(context);
1017
+ const setTitleCmds = getSetTitleCommands(windowTitle);
1018
+ const scriptContent = `#!/bin/bash
1019
+ ${setTitleCmds}
1020
+ echo "🚀 Starting ticket execution: ${context.ticketId}"
1021
+ ${devcontainerCmd}
1022
+ rm -f "${scriptPath}"
1023
+ exec $SHELL
1024
+ `;
1025
+ fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 });
1026
+ // Check if session exists
1027
+ let sessionExists = false;
1028
+ try {
1029
+ execSync(`tmux has-session -t ${sessionName}`, { stdio: 'pipe' });
1030
+ sessionExists = true;
1031
+ }
1032
+ catch (err) {
1033
+ console.debug(`[runners:hostTmux] Session ${sessionName} does not exist:`, err);
1034
+ sessionExists = false;
1035
+ }
1036
+ const targetPane = `${sessionName}:${windowName}`;
1037
+ if (!sessionExists) {
1038
+ execSync(`tmux new-session -d -s ${sessionName} -n "${windowName}"`, { stdio: 'pipe' });
1039
+ }
1040
+ else if (config.tmux.layout === 'window') {
1041
+ // Create new window in existing session (starts with shell)
1042
+ execSync(`tmux new-window -t ${sessionName} -n "${windowName}"`, { stdio: 'pipe' });
1043
+ }
1044
+ else {
1045
+ // Split existing pane (starts with shell)
1046
+ execSync(`tmux split-window -t ${sessionName} -h`, { stdio: 'pipe' });
1047
+ }
1048
+ // Send the script command to the shell - execute directly (not source)
1049
+ // Using exec replaces the shell, ensuring proper TTY passthrough
1050
+ execSync(`tmux send-keys -t "${targetPane}" 'exec ${scriptPath}' Enter`, { stdio: 'pipe' });
1051
+ return {
1052
+ success: true,
1053
+ containerId: `devcontainer-${context.agentName}`,
1054
+ sessionId: `${sessionName}:${windowName}`,
1055
+ };
1056
+ }
1057
+ catch (error) {
1058
+ return {
1059
+ success: false,
1060
+ error: error instanceof Error ? error.message : 'Failed to start tmux session',
1061
+ };
1062
+ }
1063
+ }
1064
+ // =============================================================================
1065
+ // Docker Runner
1066
+ // =============================================================================
1067
+ export async function runDocker(context, executor, config) {
1068
+ const prompt = buildPrompt(context);
1069
+ const containerName = `work-${context.ticketId}-${Date.now()}`;
1070
+ try {
1071
+ // Check if docker is available
1072
+ execSync('which docker', { stdio: 'pipe' });
1073
+ // Check if Docker is running
1074
+ if (!isDockerRunning()) {
1075
+ return {
1076
+ success: false,
1077
+ error: 'Docker is not running. Please start Docker Desktop and try again.',
1078
+ };
1079
+ }
1080
+ // Build docker run command
1081
+ let dockerCmd = `docker run -d --name ${containerName}`;
1082
+ dockerCmd += ` -v "${context.worktreePath}:/workspace"`;
1083
+ dockerCmd += ` -w /workspace`;
1084
+ dockerCmd += ` -e TICKET_ID="${context.ticketId}"`;
1085
+ if (config.docker.network) {
1086
+ dockerCmd += ` --network ${config.docker.network}`;
1087
+ }
1088
+ if (config.docker.memory) {
1089
+ dockerCmd += ` --memory ${config.docker.memory}`;
1090
+ }
1091
+ if (config.docker.cpus) {
1092
+ dockerCmd += ` --cpus ${config.docker.cpus}`;
1093
+ }
1094
+ // Escape prompt for shell
1095
+ const escapedPrompt = prompt.replace(/'/g, "'\\''");
1096
+ dockerCmd += ` ${config.docker.image}`;
1097
+ dockerCmd += ` claude --print '${escapedPrompt}'`;
1098
+ const containerId = execSync(dockerCmd, { encoding: 'utf-8' }).trim();
1099
+ return {
1100
+ success: true,
1101
+ containerId: containerId.substring(0, 12),
1102
+ };
1103
+ }
1104
+ catch (error) {
1105
+ return {
1106
+ success: false,
1107
+ error: error instanceof Error ? error.message : 'Failed to start docker container',
1108
+ };
1109
+ }
1110
+ }
1111
+ // =============================================================================
1112
+ // VM Runner
1113
+ // =============================================================================
1114
+ export async function runVm(context, executor, config, host) {
1115
+ const targetHost = host || config.vm.defaultHost;
1116
+ if (!targetHost) {
1117
+ return {
1118
+ success: false,
1119
+ error: 'No VM host specified. Use --host or configure execution.vm.default_host',
1120
+ };
1121
+ }
1122
+ const prompt = buildPrompt(context);
1123
+ const user = config.vm.user;
1124
+ const keyPath = config.vm.keyPath;
1125
+ const remoteWorkspace = `/workspace/${context.agentName}`;
1126
+ try {
1127
+ // Build SSH options
1128
+ let sshOpts = '';
1129
+ if (keyPath) {
1130
+ sshOpts = `-i "${keyPath}"`;
1131
+ }
1132
+ // Sync worktree to remote
1133
+ if (config.vm.syncMethod === 'rsync') {
1134
+ let rsyncCmd = `rsync -avz`;
1135
+ if (keyPath) {
1136
+ rsyncCmd += ` -e "ssh -i ${keyPath}"`;
1137
+ }
1138
+ rsyncCmd += ` "${context.worktreePath}/" ${user}@${targetHost}:${remoteWorkspace}/`;
1139
+ execSync(rsyncCmd, { stdio: 'pipe' });
1140
+ }
1141
+ else {
1142
+ // Git-based sync: push branch and pull on remote
1143
+ execSync(`git push origin ${context.branch}`, { cwd: context.worktreePath, stdio: 'pipe' });
1144
+ const gitPullCmd = `cd ${remoteWorkspace} && git fetch && git checkout ${context.branch}`;
1145
+ execSync(`ssh ${sshOpts} ${user}@${targetHost} "${gitPullCmd}"`, { stdio: 'pipe' });
1146
+ }
1147
+ // Execute on remote
1148
+ const escapedPrompt = prompt.replace(/'/g, "'\\''");
1149
+ const remoteCmd = `cd ${remoteWorkspace} && claude --print '${escapedPrompt}'`;
1150
+ const sshCmd = `ssh ${sshOpts} ${user}@${targetHost} "nohup ${remoteCmd} > /tmp/work-${context.ticketId}.log 2>&1 &"`;
1151
+ execSync(sshCmd, { stdio: 'pipe' });
1152
+ return {
1153
+ success: true,
1154
+ sessionId: `${targetHost}:${context.ticketId}`,
1155
+ logPath: `/tmp/work-${context.ticketId}.log`,
1156
+ };
1157
+ }
1158
+ catch (error) {
1159
+ return {
1160
+ success: false,
1161
+ error: error instanceof Error ? error.message : 'Failed to execute on VM',
1162
+ };
1163
+ }
1164
+ }
1165
+ // =============================================================================
1166
+ // Runner Dispatcher
1167
+ // =============================================================================
1168
+ export async function runExecution(environment, context, executor, config = DEFAULT_EXECUTION_CONFIG, options) {
1169
+ switch (environment) {
1170
+ case 'devcontainer':
1171
+ return runDevcontainer(context, executor, config, options?.displayMode, options?.sessionManager);
1172
+ case 'host':
1173
+ // Host uses tmux for session persistence (same as devcontainer)
1174
+ return runHost(context, executor, config, options?.displayMode);
1175
+ case 'docker':
1176
+ return runDocker(context, executor, config);
1177
+ case 'vm':
1178
+ return runVm(context, executor, config, options?.host);
1179
+ default:
1180
+ return { success: false, error: `Unknown execution environment: ${environment}` };
1181
+ }
1182
+ }