@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.
- package/README.md +512 -253
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +23 -0
- package/dist/commands/action/create.d.ts +21 -0
- package/dist/commands/action/create.js +126 -0
- package/dist/commands/action/delete.d.ts +17 -0
- package/dist/commands/action/delete.js +78 -0
- package/dist/commands/action/index.d.ts +15 -0
- package/dist/commands/action/index.js +107 -0
- package/dist/commands/action/list.d.ts +14 -0
- package/dist/commands/action/list.js +89 -0
- package/dist/commands/action/run.d.ts +19 -0
- package/dist/commands/action/run.js +179 -0
- package/dist/commands/action/show.d.ts +15 -0
- package/dist/commands/action/show.js +47 -0
- package/dist/commands/action/update.d.ts +22 -0
- package/dist/commands/action/update.js +168 -0
- package/dist/commands/agent/index.d.ts +13 -0
- package/dist/commands/agent/index.js +131 -0
- package/dist/commands/agent/list.d.ts +7 -0
- package/dist/commands/agent/list.js +126 -0
- package/dist/commands/agent/login.d.ts +16 -0
- package/dist/commands/agent/login.js +146 -0
- package/dist/commands/agent/rebuild.d.ts +18 -0
- package/dist/commands/agent/rebuild.js +133 -0
- package/dist/commands/agent/restart.d.ts +17 -0
- package/dist/commands/agent/restart.js +116 -0
- package/dist/commands/agent/shell.d.ts +23 -0
- package/dist/commands/agent/shell.js +378 -0
- package/dist/commands/agent/staff/add.d.ts +15 -0
- package/dist/commands/agent/staff/add.js +281 -0
- package/dist/commands/agent/staff/index.d.ts +14 -0
- package/dist/commands/agent/staff/index.js +90 -0
- package/dist/commands/agent/staff/list.d.ts +7 -0
- package/dist/commands/agent/staff/list.js +90 -0
- package/dist/commands/agent/staff/remove.d.ts +16 -0
- package/dist/commands/agent/staff/remove.js +137 -0
- package/dist/commands/agent/status.d.ts +17 -0
- package/dist/commands/agent/status.js +139 -0
- package/dist/commands/agent/temp/cleanup.d.ts +23 -0
- package/dist/commands/agent/temp/cleanup.js +388 -0
- package/dist/commands/agent/temp/index.d.ts +14 -0
- package/dist/commands/agent/temp/index.js +82 -0
- package/dist/commands/agent/temp/list.d.ts +7 -0
- package/dist/commands/agent/temp/list.js +108 -0
- package/dist/commands/agent/themes/add-names.d.ts +10 -0
- package/dist/commands/agent/themes/add-names.js +67 -0
- package/dist/commands/agent/themes/create.d.ts +13 -0
- package/dist/commands/agent/themes/create.js +66 -0
- package/dist/commands/agent/themes/index.d.ts +9 -0
- package/dist/commands/agent/themes/index.js +194 -0
- package/dist/commands/agent/themes/list.d.ts +6 -0
- package/dist/commands/agent/themes/list.js +41 -0
- package/dist/commands/agent/themes/set.d.ts +12 -0
- package/dist/commands/agent/themes/set.js +77 -0
- package/dist/commands/agent/visit.d.ts +16 -0
- package/dist/commands/agent/visit.js +88 -0
- package/dist/commands/autocomplete/setup.d.ts +14 -0
- package/dist/commands/autocomplete/setup.js +154 -0
- package/dist/commands/board/index.d.ts +17 -0
- package/dist/commands/board/index.js +255 -0
- package/dist/commands/board/watch.d.ts +13 -0
- package/dist/commands/board/watch.js +52 -0
- package/dist/commands/branch/create.d.ts +50 -0
- package/dist/commands/branch/create.js +624 -0
- package/dist/commands/branch/index.d.ts +13 -0
- package/dist/commands/branch/index.js +50 -0
- package/dist/commands/branch/list.d.ts +17 -0
- package/dist/commands/branch/list.js +120 -0
- package/dist/commands/branch/validate.d.ts +15 -0
- package/dist/commands/branch/validate.js +73 -0
- package/dist/commands/commit.d.ts +71 -0
- package/dist/commands/commit.js +499 -0
- package/dist/commands/docker/clean.d.ts +13 -0
- package/dist/commands/docker/clean.js +224 -0
- package/dist/commands/docker/index.d.ts +19 -0
- package/dist/commands/docker/index.js +274 -0
- package/dist/commands/docker/list.d.ts +16 -0
- package/dist/commands/docker/list.js +200 -0
- package/dist/commands/docker/logs.d.ts +14 -0
- package/dist/commands/docker/logs.js +118 -0
- package/dist/commands/docker/prune.d.ts +14 -0
- package/dist/commands/docker/prune.js +211 -0
- package/dist/commands/docker/restart.d.ts +14 -0
- package/dist/commands/docker/restart.js +129 -0
- package/dist/commands/docker/shell.d.ts +14 -0
- package/dist/commands/docker/shell.js +103 -0
- package/dist/commands/docker/start.d.ts +12 -0
- package/dist/commands/docker/start.js +92 -0
- package/dist/commands/docker/status.d.ts +7 -0
- package/dist/commands/docker/status.js +40 -0
- package/dist/commands/docker/stop.d.ts +14 -0
- package/dist/commands/docker/stop.js +134 -0
- package/dist/commands/docker/sync.d.ts +15 -0
- package/dist/commands/docker/sync.js +112 -0
- package/dist/commands/epic/activate.d.ts +13 -0
- package/dist/commands/epic/activate.js +118 -0
- package/dist/commands/epic/archive.d.ts +14 -0
- package/dist/commands/epic/archive.js +132 -0
- package/dist/commands/epic/create.d.ts +15 -0
- package/dist/commands/epic/create.js +137 -0
- package/dist/commands/epic/index.d.ts +13 -0
- package/dist/commands/epic/index.js +88 -0
- package/dist/commands/epic/link/block.d.ts +14 -0
- package/dist/commands/epic/link/block.js +79 -0
- package/dist/commands/epic/link/duplicates.d.ts +14 -0
- package/dist/commands/epic/link/duplicates.js +66 -0
- package/dist/commands/epic/link/index.d.ts +19 -0
- package/dist/commands/epic/link/index.js +242 -0
- package/dist/commands/epic/link/relates.d.ts +14 -0
- package/dist/commands/epic/link/relates.js +66 -0
- package/dist/commands/epic/link/remove.d.ts +16 -0
- package/dist/commands/epic/link/remove.js +89 -0
- package/dist/commands/epic/list.d.ts +11 -0
- package/dist/commands/epic/list.js +87 -0
- package/dist/commands/epic/move.d.ts +15 -0
- package/dist/commands/epic/move.js +184 -0
- package/dist/commands/epic/progress.d.ts +16 -0
- package/dist/commands/epic/progress.js +166 -0
- package/dist/commands/epic/project.d.ts +15 -0
- package/dist/commands/epic/project.js +219 -0
- package/dist/commands/epic/reorder.d.ts +21 -0
- package/dist/commands/epic/reorder.js +160 -0
- package/dist/commands/epic/spec.d.ts +15 -0
- package/dist/commands/epic/spec.js +191 -0
- package/dist/commands/epic/ticket.d.ts +18 -0
- package/dist/commands/epic/ticket.js +291 -0
- package/dist/commands/epic/view.d.ts +13 -0
- package/dist/commands/epic/view.js +117 -0
- package/dist/commands/execution/index.d.ts +13 -0
- package/dist/commands/execution/index.js +70 -0
- package/dist/commands/execution/list.d.ts +15 -0
- package/dist/commands/execution/list.js +144 -0
- package/dist/commands/execution/logs.d.ts +18 -0
- package/dist/commands/execution/logs.js +161 -0
- package/dist/commands/execution/stop.d.ts +22 -0
- package/dist/commands/execution/stop.js +248 -0
- package/dist/commands/gh/index.d.ts +9 -0
- package/dist/commands/gh/index.js +53 -0
- package/dist/commands/gh/login.d.ts +6 -0
- package/dist/commands/gh/login.js +57 -0
- package/dist/commands/gh/status.d.ts +6 -0
- package/dist/commands/gh/status.js +48 -0
- package/dist/commands/gh/token.d.ts +6 -0
- package/dist/commands/gh/token.js +59 -0
- package/dist/commands/init.d.ts +26 -0
- package/dist/commands/init.js +200 -0
- package/dist/commands/phase/create.d.ts +22 -0
- package/dist/commands/phase/create.js +123 -0
- package/dist/commands/phase/delete.d.ts +17 -0
- package/dist/commands/phase/delete.js +73 -0
- package/dist/commands/phase/list.d.ts +12 -0
- package/dist/commands/phase/list.js +76 -0
- package/dist/commands/phase/move.d.ts +17 -0
- package/dist/commands/phase/move.js +115 -0
- package/dist/commands/phase/template/apply.d.ts +17 -0
- package/dist/commands/phase/template/apply.js +106 -0
- package/dist/commands/phase/template/create.d.ts +16 -0
- package/dist/commands/phase/template/create.js +58 -0
- package/dist/commands/phase/template/delete.d.ts +17 -0
- package/dist/commands/phase/template/delete.js +98 -0
- package/dist/commands/phase/template/index.d.ts +15 -0
- package/dist/commands/phase/template/index.js +128 -0
- package/dist/commands/phase/template/list.d.ts +16 -0
- package/dist/commands/phase/template/list.js +95 -0
- package/dist/commands/phase/template/update.d.ts +17 -0
- package/dist/commands/phase/template/update.js +89 -0
- package/dist/commands/phase/update.d.ts +23 -0
- package/dist/commands/phase/update.js +174 -0
- package/dist/commands/pmo/init.d.ts +25 -0
- package/dist/commands/pmo/init.js +341 -0
- package/dist/commands/pr/create.d.ts +17 -0
- package/dist/commands/pr/create.js +242 -0
- package/dist/commands/pr/index.d.ts +9 -0
- package/dist/commands/pr/index.js +68 -0
- package/dist/commands/pr/link.d.ts +14 -0
- package/dist/commands/pr/link.js +212 -0
- package/dist/commands/pr/status.d.ts +12 -0
- package/dist/commands/pr/status.js +161 -0
- package/dist/commands/project/archive.d.ts +17 -0
- package/dist/commands/project/archive.js +83 -0
- package/dist/commands/project/create.d.ts +22 -0
- package/dist/commands/project/create.js +143 -0
- package/dist/commands/project/delete.d.ts +17 -0
- package/dist/commands/project/delete.js +128 -0
- package/dist/commands/project/index.d.ts +13 -0
- package/dist/commands/project/index.js +64 -0
- package/dist/commands/project/list.d.ts +14 -0
- package/dist/commands/project/list.js +96 -0
- package/dist/commands/project/spec.d.ts +18 -0
- package/dist/commands/project/spec.js +216 -0
- package/dist/commands/project/unarchive.d.ts +15 -0
- package/dist/commands/project/unarchive.js +35 -0
- package/dist/commands/project/view.d.ts +16 -0
- package/dist/commands/project/view.js +94 -0
- package/dist/commands/repo/add.d.ts +21 -0
- package/dist/commands/repo/add.js +118 -0
- package/dist/commands/repo/index.d.ts +13 -0
- package/dist/commands/repo/index.js +114 -0
- package/dist/commands/repo/list.d.ts +13 -0
- package/dist/commands/repo/list.js +96 -0
- package/dist/commands/repo/remove.d.ts +23 -0
- package/dist/commands/repo/remove.js +217 -0
- package/dist/commands/repo/view.d.ts +15 -0
- package/dist/commands/repo/view.js +99 -0
- package/dist/commands/session/attach.d.ts +40 -0
- package/dist/commands/session/attach.js +307 -0
- package/dist/commands/session/index.d.ts +13 -0
- package/dist/commands/session/index.js +64 -0
- package/dist/commands/session/list.d.ts +21 -0
- package/dist/commands/session/list.js +181 -0
- package/dist/commands/spec/create.d.ts +19 -0
- package/dist/commands/spec/create.js +130 -0
- package/dist/commands/spec/index.d.ts +13 -0
- package/dist/commands/spec/index.js +68 -0
- package/dist/commands/spec/link/depends.d.ts +14 -0
- package/dist/commands/spec/link/depends.js +64 -0
- package/dist/commands/spec/link/duplicates.d.ts +14 -0
- package/dist/commands/spec/link/duplicates.js +63 -0
- package/dist/commands/spec/link/index.d.ts +19 -0
- package/dist/commands/spec/link/index.js +200 -0
- package/dist/commands/spec/link/relates.d.ts +14 -0
- package/dist/commands/spec/link/relates.js +63 -0
- package/dist/commands/spec/link/remove.d.ts +16 -0
- package/dist/commands/spec/link/remove.js +94 -0
- package/dist/commands/spec/list.d.ts +12 -0
- package/dist/commands/spec/list.js +75 -0
- package/dist/commands/spec/plan.d.ts +15 -0
- package/dist/commands/spec/plan.js +108 -0
- package/dist/commands/spec/ticket.d.ts +18 -0
- package/dist/commands/spec/ticket.js +160 -0
- package/dist/commands/spec/view.d.ts +15 -0
- package/dist/commands/spec/view.js +163 -0
- package/dist/commands/status/create.d.ts +21 -0
- package/dist/commands/status/create.js +140 -0
- package/dist/commands/status/delete.d.ts +13 -0
- package/dist/commands/status/delete.js +77 -0
- package/dist/commands/status/index.d.ts +14 -0
- package/dist/commands/status/index.js +91 -0
- package/dist/commands/status/list.d.ts +12 -0
- package/dist/commands/status/list.js +93 -0
- package/dist/commands/status/move.d.ts +14 -0
- package/dist/commands/status/move.js +120 -0
- package/dist/commands/status/update.d.ts +20 -0
- package/dist/commands/status/update.js +180 -0
- package/dist/commands/template/delete.d.ts +15 -0
- package/dist/commands/template/delete.js +142 -0
- package/dist/commands/template/index.d.ts +10 -0
- package/dist/commands/template/index.js +64 -0
- package/dist/commands/template/list.d.ts +18 -0
- package/dist/commands/template/list.js +157 -0
- package/dist/commands/template/phase/apply.d.ts +14 -0
- package/dist/commands/template/phase/apply.js +41 -0
- package/dist/commands/template/phase/create.d.ts +12 -0
- package/dist/commands/template/phase/create.js +29 -0
- package/dist/commands/template/phase/delete.d.ts +13 -0
- package/dist/commands/template/phase/delete.js +34 -0
- package/dist/commands/template/phase/index.d.ts +10 -0
- package/dist/commands/template/phase/index.js +62 -0
- package/dist/commands/template/phase/list.d.ts +11 -0
- package/dist/commands/template/phase/list.js +34 -0
- package/dist/commands/template/phase/update.d.ts +13 -0
- package/dist/commands/template/phase/update.js +35 -0
- package/dist/commands/template/ticket/apply.d.ts +17 -0
- package/dist/commands/template/ticket/apply.js +58 -0
- package/dist/commands/template/ticket/delete.d.ts +13 -0
- package/dist/commands/template/ticket/delete.js +34 -0
- package/dist/commands/template/ticket/index.d.ts +10 -0
- package/dist/commands/template/ticket/index.js +62 -0
- package/dist/commands/template/ticket/list.d.ts +11 -0
- package/dist/commands/template/ticket/list.js +34 -0
- package/dist/commands/template/ticket/save.d.ts +13 -0
- package/dist/commands/template/ticket/save.js +35 -0
- package/dist/commands/ticket/bulk.d.ts +13 -0
- package/dist/commands/ticket/bulk.js +145 -0
- package/dist/commands/ticket/complete.d.ts +16 -0
- package/dist/commands/ticket/complete.js +170 -0
- package/dist/commands/ticket/create.d.ts +22 -0
- package/dist/commands/ticket/create.js +390 -0
- package/dist/commands/ticket/delete.d.ts +16 -0
- package/dist/commands/ticket/delete.js +178 -0
- package/dist/commands/ticket/edit.d.ts +27 -0
- package/dist/commands/ticket/edit.js +322 -0
- package/dist/commands/ticket/epic.d.ts +20 -0
- package/dist/commands/ticket/epic.js +333 -0
- package/dist/commands/ticket/index.d.ts +13 -0
- package/dist/commands/ticket/index.js +103 -0
- package/dist/commands/ticket/link/block.d.ts +14 -0
- package/dist/commands/ticket/link/block.js +94 -0
- package/dist/commands/ticket/link/duplicates.d.ts +14 -0
- package/dist/commands/ticket/link/duplicates.js +93 -0
- package/dist/commands/ticket/link/index.d.ts +19 -0
- package/dist/commands/ticket/link/index.js +239 -0
- package/dist/commands/ticket/link/relates.d.ts +14 -0
- package/dist/commands/ticket/link/relates.js +93 -0
- package/dist/commands/ticket/link/remove.d.ts +16 -0
- package/dist/commands/ticket/link/remove.js +128 -0
- package/dist/commands/ticket/list.d.ts +24 -0
- package/dist/commands/ticket/list.js +431 -0
- package/dist/commands/ticket/move.d.ts +18 -0
- package/dist/commands/ticket/move.js +212 -0
- package/dist/commands/ticket/project.d.ts +18 -0
- package/dist/commands/ticket/project.js +254 -0
- package/dist/commands/ticket/reassign.d.ts +19 -0
- package/dist/commands/ticket/reassign.js +279 -0
- package/dist/commands/ticket/spec.d.ts +18 -0
- package/dist/commands/ticket/spec.js +259 -0
- package/dist/commands/ticket/status.d.ts +13 -0
- package/dist/commands/ticket/status.js +87 -0
- package/dist/commands/ticket/template/apply.d.ts +25 -0
- package/dist/commands/ticket/template/apply.js +249 -0
- package/dist/commands/ticket/template/create.d.ts +19 -0
- package/dist/commands/ticket/template/create.js +210 -0
- package/dist/commands/ticket/template/delete.d.ts +17 -0
- package/dist/commands/ticket/template/delete.js +92 -0
- package/dist/commands/ticket/template/index.d.ts +15 -0
- package/dist/commands/ticket/template/index.js +118 -0
- package/dist/commands/ticket/template/list.d.ts +16 -0
- package/dist/commands/ticket/template/list.js +110 -0
- package/dist/commands/ticket/template/save.d.ts +14 -0
- package/dist/commands/ticket/template/save.js +110 -0
- package/dist/commands/ticket/update.d.ts +18 -0
- package/dist/commands/ticket/update.js +325 -0
- package/dist/commands/ticket/view.d.ts +13 -0
- package/dist/commands/ticket/view.js +80 -0
- package/dist/commands/whoami.d.ts +9 -0
- package/dist/commands/whoami.js +103 -0
- package/dist/commands/work/complete.d.ts +13 -0
- package/dist/commands/work/complete.js +121 -0
- package/dist/commands/work/index.d.ts +13 -0
- package/dist/commands/work/index.js +70 -0
- package/dist/commands/work/ready.d.ts +24 -0
- package/dist/commands/work/ready.js +290 -0
- package/dist/commands/work/revise.d.ts +19 -0
- package/dist/commands/work/revise.js +377 -0
- package/dist/commands/work/spawn-all.d.ts +17 -0
- package/dist/commands/work/spawn-all.js +58 -0
- package/dist/commands/work/spawn.d.ts +29 -0
- package/dist/commands/work/spawn.js +728 -0
- package/dist/commands/work/start.d.ts +39 -0
- package/dist/commands/work/start.js +1393 -0
- package/dist/commands/work/watch.d.ts +31 -0
- package/dist/commands/work/watch.js +359 -0
- package/dist/commands/workflow/create.d.ts +18 -0
- package/dist/commands/workflow/create.js +119 -0
- package/dist/commands/workflow/delete.d.ts +17 -0
- package/dist/commands/workflow/delete.js +119 -0
- package/dist/commands/workflow/index.d.ts +15 -0
- package/dist/commands/workflow/index.js +75 -0
- package/dist/commands/workflow/list.d.ts +15 -0
- package/dist/commands/workflow/list.js +75 -0
- package/dist/commands/workflow/switch.d.ts +13 -0
- package/dist/commands/workflow/switch.js +117 -0
- package/dist/commands/workflow/view.d.ts +16 -0
- package/dist/commands/workflow/view.js +114 -0
- package/dist/commands/workspace/add.d.ts +12 -0
- package/dist/commands/workspace/add.js +74 -0
- package/dist/commands/workspace/list.d.ts +9 -0
- package/dist/commands/workspace/list.js +153 -0
- package/dist/commands/workspace/remove.d.ts +13 -0
- package/dist/commands/workspace/remove.js +98 -0
- package/dist/commands/workspace/use.d.ts +12 -0
- package/dist/commands/workspace/use.js +111 -0
- package/dist/hooks/init.d.ts +11 -0
- package/dist/hooks/init.js +57 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/agents/commands.d.ts +189 -0
- package/dist/lib/agents/commands.js +893 -0
- package/dist/lib/agents/index.d.ts +54 -0
- package/dist/lib/agents/index.js +382 -0
- package/dist/lib/branch/index.d.ts +120 -0
- package/dist/lib/branch/index.js +334 -0
- package/dist/lib/colors.d.ts +94 -0
- package/dist/lib/colors.js +68 -0
- package/dist/lib/commands/docker-command.d.ts +21 -0
- package/dist/lib/commands/docker-command.js +27 -0
- package/dist/lib/database/index.d.ts +176 -0
- package/dist/lib/database/index.js +581 -0
- package/dist/lib/docker/resolve.d.ts +38 -0
- package/dist/lib/docker/resolve.js +175 -0
- package/dist/lib/execution/config.d.ts +150 -0
- package/dist/lib/execution/config.js +541 -0
- package/dist/lib/execution/devcontainer.d.ts +85 -0
- package/dist/lib/execution/devcontainer.js +594 -0
- package/dist/lib/execution/index.d.ts +10 -0
- package/dist/lib/execution/index.js +10 -0
- package/dist/lib/execution/runners.d.ts +53 -0
- package/dist/lib/execution/runners.js +1182 -0
- package/dist/lib/execution/spawner.d.ts +85 -0
- package/dist/lib/execution/spawner.js +548 -0
- package/dist/lib/execution/storage.d.ts +159 -0
- package/dist/lib/execution/storage.js +425 -0
- package/dist/lib/execution/types.d.ts +145 -0
- package/dist/lib/execution/types.js +157 -0
- package/dist/lib/init/index.d.ts +75 -0
- package/dist/lib/init/index.js +355 -0
- package/dist/lib/machine-config.d.ts +170 -0
- package/dist/lib/machine-config.js +386 -0
- package/dist/lib/pmo/base-command.d.ts +195 -0
- package/dist/lib/pmo/base-command.js +319 -0
- package/dist/lib/pmo/create-spec-folders.d.ts +43 -0
- package/dist/lib/pmo/create-spec-folders.js +64 -0
- package/dist/lib/pmo/epic-files.d.ts +56 -0
- package/dist/lib/pmo/epic-files.js +195 -0
- package/dist/lib/pmo/find-pmo.d.ts +14 -0
- package/dist/lib/pmo/find-pmo.js +172 -0
- package/dist/lib/pmo/index.d.ts +109 -0
- package/dist/lib/pmo/index.js +501 -0
- package/dist/lib/pmo/markdown.d.ts +31 -0
- package/dist/lib/pmo/markdown.js +245 -0
- package/dist/lib/pmo/pmo-context.d.ts +27 -0
- package/dist/lib/pmo/pmo-context.js +44 -0
- package/dist/lib/pmo/schema.d.ts +82 -0
- package/dist/lib/pmo/schema.js +531 -0
- package/dist/lib/pmo/spec-parser.d.ts +25 -0
- package/dist/lib/pmo/spec-parser.js +205 -0
- package/dist/lib/pmo/spec-types.d.ts +43 -0
- package/dist/lib/pmo/spec-types.js +7 -0
- package/dist/lib/pmo/storage/actions.d.ts +34 -0
- package/dist/lib/pmo/storage/actions.js +177 -0
- package/dist/lib/pmo/storage/base.d.ts +47 -0
- package/dist/lib/pmo/storage/base.js +858 -0
- package/dist/lib/pmo/storage/dependencies.d.ts +61 -0
- package/dist/lib/pmo/storage/dependencies.js +267 -0
- package/dist/lib/pmo/storage/epics.d.ts +46 -0
- package/dist/lib/pmo/storage/epics.js +243 -0
- package/dist/lib/pmo/storage/helpers.d.ts +33 -0
- package/dist/lib/pmo/storage/helpers.js +148 -0
- package/dist/lib/pmo/storage/index.d.ts +186 -0
- package/dist/lib/pmo/storage/index.js +689 -0
- package/dist/lib/pmo/storage/phases.d.ts +65 -0
- package/dist/lib/pmo/storage/phases.js +392 -0
- package/dist/lib/pmo/storage/projects.d.ts +79 -0
- package/dist/lib/pmo/storage/projects.js +303 -0
- package/dist/lib/pmo/storage/specs.d.ts +77 -0
- package/dist/lib/pmo/storage/specs.js +389 -0
- package/dist/lib/pmo/storage/statuses.d.ts +63 -0
- package/dist/lib/pmo/storage/statuses.js +404 -0
- package/dist/lib/pmo/storage/subtasks.d.ts +37 -0
- package/dist/lib/pmo/storage/subtasks.js +184 -0
- package/dist/lib/pmo/storage/templates.d.ts +40 -0
- package/dist/lib/pmo/storage/templates.js +210 -0
- package/dist/lib/pmo/storage/tickets.d.ts +57 -0
- package/dist/lib/pmo/storage/tickets.js +453 -0
- package/dist/lib/pmo/storage/types.d.ts +200 -0
- package/dist/lib/pmo/storage/types.js +5 -0
- package/dist/lib/pmo/storage/views.d.ts +44 -0
- package/dist/lib/pmo/storage/views.js +355 -0
- package/dist/lib/pmo/storage-sqlite.d.ts +7 -0
- package/dist/lib/pmo/storage-sqlite.js +7 -0
- package/dist/lib/pmo/sync-manager.d.ts +92 -0
- package/dist/lib/pmo/sync-manager.js +229 -0
- package/dist/lib/pmo/types.d.ts +710 -0
- package/dist/lib/pmo/types.js +108 -0
- package/dist/lib/pmo/utils.d.ts +122 -0
- package/dist/lib/pmo/utils.js +174 -0
- package/dist/lib/pmo/watcher.d.ts +43 -0
- package/dist/lib/pmo/watcher.js +208 -0
- package/dist/lib/pr/index.d.ts +150 -0
- package/dist/lib/pr/index.js +483 -0
- package/dist/lib/prompt-json.d.ts +231 -0
- package/dist/lib/prompt-json.js +213 -0
- package/dist/lib/repos/index.d.ts +81 -0
- package/dist/lib/repos/index.js +679 -0
- package/dist/lib/styles.d.ts +98 -0
- package/dist/lib/styles.js +195 -0
- package/dist/lib/themes.d.ts +128 -0
- package/dist/lib/themes.js +301 -0
- package/dist/lib/ui/BoardUI.d.ts +21 -0
- package/dist/lib/ui/BoardUI.js +85 -0
- package/dist/lib/ui/ClaimTicketUI.d.ts +17 -0
- package/dist/lib/ui/ClaimTicketUI.js +64 -0
- package/dist/lib/ui/CreateTicketUI.d.ts +13 -0
- package/dist/lib/ui/CreateTicketUI.js +101 -0
- package/dist/lib/workspace.d.ts +66 -0
- package/dist/lib/workspace.js +204 -0
- package/oclif.manifest.json +10593 -0
- package/package.json +103 -56
- package/LICENSE +0 -21
- package/dist/bin/prlt.d.ts +0 -11
- package/dist/bin/prlt.d.ts.map +0 -1
- package/dist/bin/prlt.js +0 -144
- package/dist/bin/prlt.js.map +0 -1
- package/dist/lib/config/index.d.ts +0 -14
- package/dist/lib/config/index.d.ts.map +0 -1
- package/dist/lib/config/index.js +0 -142
- package/dist/lib/config/index.js.map +0 -1
- package/dist/lib/config/upgrade.d.ts +0 -2
- package/dist/lib/config/upgrade.d.ts.map +0 -1
- package/dist/lib/config/upgrade.js +0 -248
- package/dist/lib/config/upgrade.js.map +0 -1
- package/dist/lib/themes/index.d.ts +0 -8
- package/dist/lib/themes/index.d.ts.map +0 -1
- package/dist/lib/themes/index.js +0 -80
- package/dist/lib/themes/index.js.map +0 -1
- package/dist/lib/utils/helpers.d.ts +0 -4
- package/dist/lib/utils/helpers.d.ts.map +0 -1
- package/dist/lib/utils/helpers.js +0 -39
- package/dist/lib/utils/helpers.js.map +0 -1
- package/dist/lib/utils/logger.d.ts +0 -4
- package/dist/lib/utils/logger.d.ts.map +0 -1
- package/dist/lib/utils/logger.js +0 -28
- package/dist/lib/utils/logger.js.map +0 -1
- package/dist/lib/workspace/index.d.ts +0 -13
- package/dist/lib/workspace/index.d.ts.map +0 -1
- package/dist/lib/workspace/index.js +0 -116
- package/dist/lib/workspace/index.js.map +0 -1
- package/dist/lib/worktree/index.d.ts +0 -7
- package/dist/lib/worktree/index.d.ts.map +0 -1
- package/dist/lib/worktree/index.js +0 -362
- package/dist/lib/worktree/index.js.map +0 -1
- package/dist/lib/worktree/migrate.d.ts +0 -2
- package/dist/lib/worktree/migrate.d.ts.map +0 -1
- package/dist/lib/worktree/migrate.js +0 -214
- package/dist/lib/worktree/migrate.js.map +0 -1
- package/dist/lib/worktree/repair.d.ts +0 -3
- package/dist/lib/worktree/repair.d.ts.map +0 -1
- package/dist/lib/worktree/repair.js +0 -320
- package/dist/lib/worktree/repair.js.map +0 -1
- package/dist/types/index.d.ts +0 -57
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -3
- 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
|
+
}
|