panopticon-cli 0.6.4 → 0.6.6

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 (311) hide show
  1. package/README.md +2 -2
  2. package/dist/{agents-DfYify9s.js → agents-CfFDs52G.js} +14 -14
  3. package/dist/{agents-DfYify9s.js.map → agents-CfFDs52G.js.map} +1 -1
  4. package/dist/{agents-BKsVoIc9.js → agents-D_2oRFVf.js} +1 -1
  5. package/dist/{archive-planning-BJrZ3tmN.js → archive-planning-D97ziGec.js} +3 -3
  6. package/dist/{archive-planning-BJrZ3tmN.js.map → archive-planning-D97ziGec.js.map} +1 -1
  7. package/dist/{archive-planning-C3m3hfa5.js → archive-planning-DK90wn9Q.js} +1 -1
  8. package/dist/{browser-Cvdznzc0.js → browser-CX7jXfXX.js} +1 -1
  9. package/dist/{browser-Cvdznzc0.js.map → browser-CX7jXfXX.js.map} +1 -1
  10. package/dist/{clean-planning-DvhZAUv4.js → clean-planning-D_lz4aQq.js} +2 -2
  11. package/dist/{clean-planning-DvhZAUv4.js.map → clean-planning-D_lz4aQq.js.map} +1 -1
  12. package/dist/clean-planning-x1S-JdmO.js +2 -0
  13. package/dist/cli/index.js +291 -760
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/{close-issue-Dr7yZmrr.js → close-issue-CaFE0stN.js} +11 -7
  16. package/dist/close-issue-CaFE0stN.js.map +1 -0
  17. package/dist/close-issue-CjcfZI9s.js +2 -0
  18. package/dist/compact-beads-B0_qE1w3.js +2 -0
  19. package/dist/{compact-beads-BCOtIIRl.js → compact-beads-CjFkteSU.js} +2 -2
  20. package/dist/{compact-beads-BCOtIIRl.js.map → compact-beads-CjFkteSU.js.map} +1 -1
  21. package/dist/{config-CRzMQRgA.js → config-BQNKsi9G.js} +2 -2
  22. package/dist/{config-CRzMQRgA.js.map → config-BQNKsi9G.js.map} +1 -1
  23. package/dist/{config-BYgUzQ21.js → config-agyKgF5C.js} +1 -1
  24. package/dist/{config-yaml-BgOACZAB.js → config-yaml-DGbLSMCa.js} +1 -1
  25. package/dist/{config-yaml-BgOACZAB.js.map → config-yaml-DGbLSMCa.js.map} +1 -1
  26. package/dist/{config-yaml-fdyvyL0S.js → config-yaml-Dqt4FWQH.js} +1 -1
  27. package/dist/dashboard/{acceptance-criteria-e5iiHlRx.js → acceptance-criteria-Dk9hhiYj.js} +1 -1
  28. package/dist/dashboard/{acceptance-criteria-e5iiHlRx.js.map → acceptance-criteria-Dk9hhiYj.js.map} +1 -1
  29. package/dist/dashboard/{agent-enrichment-C67LJBgD.js → agent-enrichment-DdO7ZqjI.js} +11 -7
  30. package/dist/dashboard/agent-enrichment-DdO7ZqjI.js.map +1 -0
  31. package/dist/dashboard/{agent-enrichment-Cq0P1cNZ.js → agent-enrichment-dLeGE1fX.js} +1 -1
  32. package/dist/dashboard/{agents-YyO6t5Xa.js → agents-DCpQQ_W5.js} +14 -14
  33. package/dist/dashboard/{agents-YyO6t5Xa.js.map → agents-DCpQQ_W5.js.map} +1 -1
  34. package/dist/dashboard/{agents-BVBVCyat.js → agents-Dgh2TjSp.js} +1 -1
  35. package/dist/dashboard/{archive-planning-h-hAjk0P.js → archive-planning-BmW9UDTr.js} +3 -3
  36. package/dist/dashboard/{archive-planning-h-hAjk0P.js.map → archive-planning-BmW9UDTr.js.map} +1 -1
  37. package/dist/dashboard/{archive-planning-CScs1MOC.js → archive-planning-C3Ebf9yC.js} +1 -1
  38. package/dist/dashboard/{beads-qNB0yAHV.js → beads-Bv-AdX7G.js} +3 -3
  39. package/dist/dashboard/{beads-qNB0yAHV.js.map → beads-Bv-AdX7G.js.map} +1 -1
  40. package/dist/dashboard/{beads-D_FRedEJ.js → beads-By6-X07V.js} +1 -1
  41. package/dist/dashboard/clean-planning-D60L8rPY.js +2 -0
  42. package/dist/dashboard/{clean-planning-qafw99vY.js → clean-planning-VEJu5suh.js} +2 -2
  43. package/dist/dashboard/{clean-planning-qafw99vY.js.map → clean-planning-VEJu5suh.js.map} +1 -1
  44. package/dist/dashboard/close-issue-C2KeSKKJ.js +2 -0
  45. package/dist/dashboard/{close-issue-DfIggeZD.js → close-issue-DtKdsSTm.js} +11 -7
  46. package/dist/dashboard/close-issue-DtKdsSTm.js.map +1 -0
  47. package/dist/dashboard/compact-beads-C7BN5N11.js +2 -0
  48. package/dist/dashboard/{compact-beads-Dt0qTqsC.js → compact-beads-D8Vt3qyv.js} +2 -2
  49. package/dist/dashboard/{compact-beads-Dt0qTqsC.js.map → compact-beads-D8Vt3qyv.js.map} +1 -1
  50. package/dist/dashboard/{config-CUREjHP7.js → config-CDkGjnwy.js} +2 -2
  51. package/dist/dashboard/{config-CUREjHP7.js.map → config-CDkGjnwy.js.map} +1 -1
  52. package/dist/dashboard/{config-BeI3uy-8.js → config-CTXkBATQ.js} +1 -1
  53. package/dist/dashboard/{database-CozA13Wy.js → database-DhqASALP.js} +1 -1
  54. package/dist/dashboard/{database-C0y0hXBx.js → database-cxmQryoh.js} +2 -2
  55. package/dist/dashboard/{database-C0y0hXBx.js.map → database-cxmQryoh.js.map} +1 -1
  56. package/dist/dashboard/{dist-src-oG2iHzgI.js → dist-src-DTm11oQr.js} +1 -1
  57. package/dist/dashboard/{dist-src-oG2iHzgI.js.map → dist-src-DTm11oQr.js.map} +1 -1
  58. package/dist/dashboard/{event-store-D7kLBd07.js → event-store-VWWUmOfn.js} +1 -1
  59. package/dist/dashboard/{event-store-O9q0Gweh.js → event-store-vSmAA3Zp.js} +9 -4
  60. package/dist/dashboard/event-store-vSmAA3Zp.js.map +1 -0
  61. package/dist/dashboard/{factory-BnLdiQW-.js → factory-C8nhLGHB.js} +3 -3
  62. package/dist/dashboard/{factory-BnLdiQW-.js.map → factory-C8nhLGHB.js.map} +1 -1
  63. package/dist/dashboard/{feedback-writer-DyovUANg.js → feedback-writer-CudSe1WK.js} +2 -2
  64. package/dist/dashboard/{feedback-writer-DyovUANg.js.map → feedback-writer-CudSe1WK.js.map} +1 -1
  65. package/dist/dashboard/{feedback-writer-gSUv_W0h.js → feedback-writer-Wgv1cd1r.js} +1 -1
  66. package/dist/dashboard/{git-utils-BJRioREj.js → git-utils-C1m4SwAe.js} +1 -1
  67. package/dist/dashboard/{git-utils-BJRioREj.js.map → git-utils-C1m4SwAe.js.map} +1 -1
  68. package/dist/dashboard/{git-utils-BtCRddq3.js → git-utils-DQI8EYoj.js} +1 -1
  69. package/dist/dashboard/{github-app-XO-LBUGk.js → github-app-DClWjjHr.js} +1 -1
  70. package/dist/dashboard/{github-app-XO-LBUGk.js.map → github-app-DClWjjHr.js.map} +1 -1
  71. package/dist/dashboard/{health-events-db-584nYgJB.js → health-events-db-BMXQfInV.js} +1 -1
  72. package/dist/dashboard/{health-events-db-B3ChzN65.js → health-events-db-Do4NrOhC.js} +2 -2
  73. package/dist/dashboard/{health-events-db-B3ChzN65.js.map → health-events-db-Do4NrOhC.js.map} +1 -1
  74. package/dist/dashboard/{hooks-CKhs3N68.js → hooks-CB4T47NC.js} +1 -1
  75. package/dist/dashboard/{hooks-CErbP8Oq.js → hooks-CjqXOlNb.js} +2 -2
  76. package/dist/dashboard/{hooks-CErbP8Oq.js.map → hooks-CjqXOlNb.js.map} +1 -1
  77. package/dist/dashboard/hume-CA2pftu_.js +3 -0
  78. package/dist/dashboard/{hume-CX_U3Qha.js → hume-JsAlMOJC.js} +2 -2
  79. package/dist/dashboard/{hume-CX_U3Qha.js.map → hume-JsAlMOJC.js.map} +1 -1
  80. package/dist/dashboard/{inspect-agent-B57kGDUV.js → inspect-agent-7eour7EA.js} +3 -3
  81. package/dist/dashboard/{inspect-agent-B57kGDUV.js.map → inspect-agent-7eour7EA.js.map} +1 -1
  82. package/dist/dashboard/{io-yGovuG4U.js → io-CWlFW78i.js} +1 -1
  83. package/dist/dashboard/{io-AJg-mzFi.js → io-DKS6359z.js} +1 -1
  84. package/dist/dashboard/{io-AJg-mzFi.js.map → io-DKS6359z.js.map} +1 -1
  85. package/dist/dashboard/issue-id-vwYJdsf8.js +62 -0
  86. package/dist/dashboard/issue-id-vwYJdsf8.js.map +1 -0
  87. package/dist/dashboard/{issue-service-singleton-DQK42EqH.js → issue-service-singleton-Co__-6kL.js} +1 -1
  88. package/dist/dashboard/{issue-service-singleton-sb2HkB9f.js → issue-service-singleton-Wv4xBm3y.js} +7 -7
  89. package/dist/dashboard/{issue-service-singleton-sb2HkB9f.js.map → issue-service-singleton-Wv4xBm3y.js.map} +1 -1
  90. package/dist/dashboard/{label-cleanup-CZEsbtq9.js → label-cleanup-nVKTmIIW.js} +7 -4
  91. package/dist/dashboard/label-cleanup-nVKTmIIW.js.map +1 -0
  92. package/dist/dashboard/lifecycle-BcUmtkR4.js +7 -0
  93. package/dist/dashboard/{merge-agent-GLtMEsTu.js → merge-agent-CGN3TT0a.js} +1 -1
  94. package/dist/dashboard/{merge-agent-twroFuAh.js → merge-agent-yudQOPZc.js} +148 -46
  95. package/dist/dashboard/merge-agent-yudQOPZc.js.map +1 -0
  96. package/dist/dashboard/{paths-COdEvoXR.js → paths-BDyJ7BiV.js} +19 -2
  97. package/dist/dashboard/{paths-COdEvoXR.js.map → paths-BDyJ7BiV.js.map} +1 -1
  98. package/dist/dashboard/{pipeline-notifier-DM5AHG5Q.js → pipeline-notifier-CCSN-jar.js} +1 -1
  99. package/dist/dashboard/{pipeline-notifier-DM5AHG5Q.js.map → pipeline-notifier-CCSN-jar.js.map} +1 -1
  100. package/dist/dashboard/{plan-utils-BkCIhn3B.js → plan-utils-Bkcsqr_s.js} +3 -3
  101. package/dist/dashboard/{plan-utils-BkCIhn3B.js.map → plan-utils-Bkcsqr_s.js.map} +1 -1
  102. package/dist/dashboard/{prd-draft-D09Afalc.js → prd-draft-BD8oMkZ1.js} +2 -2
  103. package/dist/dashboard/{prd-draft-D09Afalc.js.map → prd-draft-BD8oMkZ1.js.map} +1 -1
  104. package/dist/dashboard/{projection-cache-DQ9zegkK.js → projection-cache-C0EL8s8h.js} +1 -1
  105. package/dist/dashboard/{projection-cache-DQ9zegkK.js.map → projection-cache-C0EL8s8h.js.map} +1 -1
  106. package/dist/dashboard/{projects-DyT3vSy-.js → projects-C5ozxjwP.js} +1 -1
  107. package/dist/dashboard/{projects-Cq3TWdPS.js → projects-CFVl4oHn.js} +25 -13
  108. package/dist/dashboard/projects-CFVl4oHn.js.map +1 -0
  109. package/dist/dashboard/{providers-Ck2sQd_F.js → providers-B5Y4H2Mg.js} +4 -4
  110. package/dist/dashboard/providers-B5Y4H2Mg.js.map +1 -0
  111. package/dist/dashboard/{providers-DVQnDekG.js → providers-csVZVPkE.js} +1 -1
  112. package/dist/dashboard/public/assets/{dist-CCJbQrSB.js → dist-BaQPC-c6.js} +1 -1
  113. package/dist/dashboard/public/assets/index-ByLmYGhW.js +212 -0
  114. package/dist/dashboard/public/assets/index-OEEbThNN.css +1 -0
  115. package/dist/dashboard/public/index.html +2 -2
  116. package/dist/dashboard/rally-6McpKKRa.js +3 -0
  117. package/dist/dashboard/{rally-Cwuae-4C.js → rally-YjFRxIiC.js} +2 -2
  118. package/dist/dashboard/{rally-Cwuae-4C.js.map → rally-YjFRxIiC.js.map} +1 -1
  119. package/dist/dashboard/{rally-api-DSUxm7EO.js → rally-api-C0WqCSkT.js} +1 -1
  120. package/dist/dashboard/{rally-api-DSUxm7EO.js.map → rally-api-C0WqCSkT.js.map} +1 -1
  121. package/dist/dashboard/{rally-api-CEH5KZi4.js → rally-api-DNttdCW4.js} +1 -1
  122. package/dist/dashboard/{remote-BHTTMpJJ.js → remote-Cigqjj3f.js} +2 -2
  123. package/dist/dashboard/{remote-BXo_iIku.js → remote-ObpNZ7hF.js} +2 -2
  124. package/dist/dashboard/{remote-BXo_iIku.js.map → remote-ObpNZ7hF.js.map} +1 -1
  125. package/dist/dashboard/{remote-agents-CTKVhFFY.js → remote-agents-Bf3GuM7t.js} +1 -1
  126. package/dist/dashboard/{remote-agents-C0_0LLNd.js → remote-agents-DFyjT1Le.js} +1 -1
  127. package/dist/dashboard/{remote-agents-C0_0LLNd.js.map → remote-agents-DFyjT1Le.js.map} +1 -1
  128. package/dist/dashboard/{review-status-CK3eBGyb.js → review-status-BtXqWBhS.js} +1 -1
  129. package/dist/dashboard/{review-status-CV55Tl-n.js → review-status-Bymwzh2i.js} +44 -4
  130. package/dist/dashboard/{review-status-CV55Tl-n.js.map → review-status-Bymwzh2i.js.map} +1 -1
  131. package/dist/dashboard/server.js +565 -265
  132. package/dist/dashboard/server.js.map +1 -1
  133. package/dist/dashboard/{settings-CuHV-wcv.js → settings-BHlDG7TK.js} +2 -2
  134. package/dist/dashboard/settings-BHlDG7TK.js.map +1 -0
  135. package/dist/dashboard/settings-XWvDcj-D.js +2 -0
  136. package/dist/dashboard/{shadow-engineering-BUeZunaE.js → shadow-engineering-lIn1W_95.js} +1 -1
  137. package/dist/dashboard/{shadow-engineering-BUeZunaE.js.map → shadow-engineering-lIn1W_95.js.map} +1 -1
  138. package/dist/dashboard/{shadow-state-DHQ-kASN.js → shadow-state-BIexcxkv.js} +1 -1
  139. package/dist/dashboard/{shadow-state-DHQ-kASN.js.map → shadow-state-BIexcxkv.js.map} +1 -1
  140. package/dist/dashboard/{spawn-planning-session-8FFAqLdK.js → spawn-planning-session-33Jf-d5T.js} +6 -6
  141. package/dist/dashboard/{spawn-planning-session-8FFAqLdK.js.map → spawn-planning-session-33Jf-d5T.js.map} +1 -1
  142. package/dist/dashboard/{spawn-planning-session-U0Lqpjen.js → spawn-planning-session-D5hrVdWM.js} +1 -1
  143. package/dist/dashboard/{specialist-context-ColzlmGE.js → specialist-context-DGukHSn8.js} +6 -6
  144. package/dist/dashboard/{specialist-context-ColzlmGE.js.map → specialist-context-DGukHSn8.js.map} +1 -1
  145. package/dist/dashboard/{specialist-logs-BhmDpFIq.js → specialist-logs-CIw4qfTy.js} +1 -1
  146. package/dist/dashboard/{specialists-C6s3U6tX.js → specialists-B_zrayaP.js} +37 -36
  147. package/dist/dashboard/specialists-B_zrayaP.js.map +1 -0
  148. package/dist/dashboard/{specialists-Cny632-T.js → specialists-Cp-PgspS.js} +1 -1
  149. package/dist/dashboard/{test-agent-queue-tqI4VDsu.js → test-agent-queue-ypF_ecHo.js} +4 -4
  150. package/dist/dashboard/{test-agent-queue-tqI4VDsu.js.map → test-agent-queue-ypF_ecHo.js.map} +1 -1
  151. package/dist/dashboard/{tldr-daemon-BNFyS7W_.js → tldr-daemon-B_oLRD8z.js} +2 -2
  152. package/dist/dashboard/{tldr-daemon-BNFyS7W_.js.map → tldr-daemon-B_oLRD8z.js.map} +1 -1
  153. package/dist/dashboard/{tldr-daemon-A6JqC59u.js → tldr-daemon-Cfs0bXTi.js} +1 -1
  154. package/dist/dashboard/{tmux-DYGAVJfb.js → tmux-BzxdKItf.js} +1 -1
  155. package/dist/dashboard/{tmux-IlN1Slv-.js → tmux-LwG0tHhU.js} +2 -2
  156. package/dist/dashboard/{tmux-IlN1Slv-.js.map → tmux-LwG0tHhU.js.map} +1 -1
  157. package/dist/dashboard/{tracker-config-BzNLnmcE.js → tracker-config-BP59uH4V.js} +1 -1
  158. package/dist/dashboard/{tracker-config-CNM_5rEf.js → tracker-config-e7ph1QqT.js} +2 -2
  159. package/dist/dashboard/{tracker-config-CNM_5rEf.js.map → tracker-config-e7ph1QqT.js.map} +1 -1
  160. package/dist/dashboard/{tunnel-D2BkwU7k.js → tunnel-0RzzuXPf.js} +1 -1
  161. package/dist/dashboard/{tunnel-Dub2hiAA.js → tunnel-DldbBPWL.js} +2 -2
  162. package/dist/dashboard/{tunnel-Dub2hiAA.js.map → tunnel-DldbBPWL.js.map} +1 -1
  163. package/dist/dashboard/{types-CWA-o4UN.js → types-RKZjGE5N.js} +1 -1
  164. package/dist/dashboard/{types-CWA-o4UN.js.map → types-RKZjGE5N.js.map} +1 -1
  165. package/dist/dashboard/{vtt-parser-BAXygRf0.js → vtt-parser-99vFekRQ.js} +1 -1
  166. package/dist/dashboard/{vtt-parser-BAXygRf0.js.map → vtt-parser-99vFekRQ.js.map} +1 -1
  167. package/dist/dashboard/{work-agent-prompt-JYq_OugP.js → work-agent-prompt-fCg67nyo.js} +65 -10
  168. package/dist/dashboard/{work-agent-prompt-JYq_OugP.js.map → work-agent-prompt-fCg67nyo.js.map} +1 -1
  169. package/dist/dashboard/{work-type-router-Cxp8_ur2.js → work-type-router-CWVW2Wk_.js} +1 -1
  170. package/dist/dashboard/{work-type-router-Cxp8_ur2.js.map → work-type-router-CWVW2Wk_.js.map} +1 -1
  171. package/dist/dashboard/{work-type-router-Com2amST.js → work-type-router-Di5gCQwh.js} +1 -1
  172. package/dist/dashboard/{workflows-N1UTipYl.js → workflows-BSMipN07.js} +35 -17
  173. package/dist/dashboard/workflows-BSMipN07.js.map +1 -0
  174. package/dist/dashboard/workflows-DaYWQIS2.js +2 -0
  175. package/dist/dashboard/{workspace-config-cmp5_ipD.js → workspace-config-DVDR-Ukh.js} +1 -1
  176. package/dist/dashboard/workspace-config-DVDR-Ukh.js.map +1 -0
  177. package/dist/dashboard/{workspace-manager-CjpWPgzL.js → workspace-manager-BYfzs_t2.js} +1 -1
  178. package/dist/dashboard/{workspace-manager-D_y9ZmW_.js → workspace-manager-C7OfT62A.js} +44 -24
  179. package/dist/dashboard/workspace-manager-C7OfT62A.js.map +1 -0
  180. package/dist/{dns-BKzHm-2q.js → dns-D_aKQJjb.js} +1 -1
  181. package/dist/{dns-DZwOWvVO.js → dns-Yxq4NNS7.js} +1 -1
  182. package/dist/{dns-DZwOWvVO.js.map → dns-Yxq4NNS7.js.map} +1 -1
  183. package/dist/{factory-DFu3IT4r.js → factory-BRBGw6OB.js} +1 -1
  184. package/dist/{factory-DfzczxN1.js → factory-DzsOiZVc.js} +3 -3
  185. package/dist/{factory-DfzczxN1.js.map → factory-DzsOiZVc.js.map} +1 -1
  186. package/dist/{feedback-writer-CwdnOkPO.js → feedback-writer-ygXN5F9N.js} +2 -2
  187. package/dist/{feedback-writer-CwdnOkPO.js.map → feedback-writer-ygXN5F9N.js.map} +1 -1
  188. package/dist/{github-app-CHKwxOeQ.js → github-app-DykduJ0X.js} +1 -1
  189. package/dist/{github-app-CHKwxOeQ.js.map → github-app-DykduJ0X.js.map} +1 -1
  190. package/dist/hume-9nv1VmMV.js +3 -0
  191. package/dist/{hume-DnV-tDsh.js → hume-DoCbph2h.js} +2 -2
  192. package/dist/{hume-DnV-tDsh.js.map → hume-DoCbph2h.js.map} +1 -1
  193. package/dist/index.d.ts +17 -2
  194. package/dist/index.d.ts.map +1 -1
  195. package/dist/index.js +8 -7
  196. package/dist/issue-id-CAcekoIw.js +62 -0
  197. package/dist/issue-id-CAcekoIw.js.map +1 -0
  198. package/dist/{label-cleanup-31ElPqqv.js → label-cleanup-C8R9Rspn.js} +7 -4
  199. package/dist/label-cleanup-C8R9Rspn.js.map +1 -0
  200. package/dist/{manifest-DL0oDbpv.js → manifest-B4ghOD-V.js} +1 -1
  201. package/dist/{manifest-DL0oDbpv.js.map → manifest-B4ghOD-V.js.map} +1 -1
  202. package/dist/{merge-agent-VQH9z9t8.js → merge-agent-DlUiUanN.js} +86 -33
  203. package/dist/merge-agent-DlUiUanN.js.map +1 -0
  204. package/dist/{paths-lMaxrYtT.js → paths-CDJ_HsbN.js} +19 -2
  205. package/dist/{paths-lMaxrYtT.js.map → paths-CDJ_HsbN.js.map} +1 -1
  206. package/dist/{pipeline-notifier-OJ-d3Y60.js → pipeline-notifier-XgDdCdvT.js} +1 -1
  207. package/dist/{pipeline-notifier-OJ-d3Y60.js.map → pipeline-notifier-XgDdCdvT.js.map} +1 -1
  208. package/dist/{projects-CvLepaxC.js → projects-Bk-5QhFQ.js} +25 -13
  209. package/dist/projects-Bk-5QhFQ.js.map +1 -0
  210. package/dist/{projects-DMWmPeIU.js → projects-DhU7rAVN.js} +1 -1
  211. package/dist/{providers-DcCPZ5K4.js → providers-DSU1vfQF.js} +4 -4
  212. package/dist/providers-DSU1vfQF.js.map +1 -0
  213. package/dist/rally-DdPvGa-w.js +3 -0
  214. package/dist/{rally-uUUZXp1h.js → rally-Dy00NElU.js} +1 -1
  215. package/dist/{rally-uUUZXp1h.js.map → rally-Dy00NElU.js.map} +1 -1
  216. package/dist/{remote-CkLBqLJc.js → remote-CYiOJg0q.js} +2 -2
  217. package/dist/{remote-CkLBqLJc.js.map → remote-CYiOJg0q.js.map} +1 -1
  218. package/dist/{remote-agents-C5Bd2fgt.js → remote-agents-CZXrUF4f.js} +1 -1
  219. package/dist/{remote-agents-C5Bd2fgt.js.map → remote-agents-CZXrUF4f.js.map} +1 -1
  220. package/dist/{remote-agents-BTzD-wMQ.js → remote-agents-ycHHVsgf.js} +1 -1
  221. package/dist/{remote-workspace-Dxghqiti.js → remote-workspace-CA33UuVI.js} +4 -4
  222. package/dist/{remote-workspace-Dxghqiti.js.map → remote-workspace-CA33UuVI.js.map} +1 -1
  223. package/dist/{review-status-2TdtHNcs.js → review-status-D6H2WOw8.js} +1 -1
  224. package/dist/{review-status-Bm1bWNEa.js → review-status-DEDvCKMP.js} +44 -4
  225. package/dist/{review-status-Bm1bWNEa.js.map → review-status-DEDvCKMP.js.map} +1 -1
  226. package/dist/{tracker-C_62ukEq.js → settings-BcWPTrua.js} +7 -199
  227. package/dist/settings-BcWPTrua.js.map +1 -0
  228. package/dist/shadow-state-BZzxfEGw.js +2 -0
  229. package/dist/{shadow-state-CFFHf05M.js → shadow-state-CE3dQfll.js} +1 -1
  230. package/dist/{shadow-state-CFFHf05M.js.map → shadow-state-CE3dQfll.js.map} +1 -1
  231. package/dist/{specialist-context-BdNFsfMG.js → specialist-context-BAUWL1Fl.js} +6 -6
  232. package/dist/{specialist-context-BdNFsfMG.js.map → specialist-context-BAUWL1Fl.js.map} +1 -1
  233. package/dist/{specialist-logs-CLztE_bE.js → specialist-logs-DQKKQV9B.js} +1 -1
  234. package/dist/{specialists-aUoUVWsN.js → specialists-Bfb9ATzw.js} +1 -1
  235. package/dist/{specialists-DEKqgkxp.js → specialists-D7Kj5o6s.js} +35 -34
  236. package/dist/specialists-D7Kj5o6s.js.map +1 -0
  237. package/dist/sync-DMfgd389.js +693 -0
  238. package/dist/sync-DMfgd389.js.map +1 -0
  239. package/dist/sync-TL6y-8K6.js +2 -0
  240. package/dist/{tldr-daemon-BCEFPItr.js → tldr-daemon-CFx4LXAl.js} +2 -2
  241. package/dist/{tldr-daemon-BCEFPItr.js.map → tldr-daemon-CFx4LXAl.js.map} +1 -1
  242. package/dist/{tldr-daemon-xBAx4cBE.js → tldr-daemon-D_EooADG.js} +1 -1
  243. package/dist/{tmux-DN6H886Y.js → tmux-CBtui_Cl.js} +1 -1
  244. package/dist/{tmux-CKdNxxJx.js → tmux-D6Ah4I8z.js} +2 -2
  245. package/dist/{tmux-CKdNxxJx.js.map → tmux-D6Ah4I8z.js.map} +1 -1
  246. package/dist/tracker-BhYYvU3p.js +198 -0
  247. package/dist/tracker-BhYYvU3p.js.map +1 -0
  248. package/dist/{tracker-utils-CVU2W1sX.js → tracker-utils-ChQyut8w.js} +34 -12
  249. package/dist/tracker-utils-ChQyut8w.js.map +1 -0
  250. package/dist/{traefik-DHgBoWXX.js → traefik-C80EbDu_.js} +4 -4
  251. package/dist/{traefik-DHgBoWXX.js.map → traefik-C80EbDu_.js.map} +1 -1
  252. package/dist/{traefik-BR-edbZv.js → traefik-CgHl7Bge.js} +1 -1
  253. package/dist/{tunnel-BZO9Q5oe.js → tunnel-DXOJ1wMM.js} +1 -1
  254. package/dist/{tunnel-Bl1qNSyQ.js → tunnel-DzXEPwIc.js} +2 -2
  255. package/dist/{tunnel-Bl1qNSyQ.js.map → tunnel-DzXEPwIc.js.map} +1 -1
  256. package/dist/{types-DewGdaIP.js → types-BhJj1SP1.js} +1 -1
  257. package/dist/{types-DewGdaIP.js.map → types-BhJj1SP1.js.map} +1 -1
  258. package/dist/{work-type-router-CS2BB1vS.js → work-type-router-CHjciPyS.js} +3 -3
  259. package/dist/{work-type-router-CS2BB1vS.js.map → work-type-router-CHjciPyS.js.map} +1 -1
  260. package/dist/{workspace-config-CNXOpKuj.js → workspace-config-fUafvYMp.js} +1 -1
  261. package/dist/workspace-config-fUafvYMp.js.map +1 -0
  262. package/dist/workspace-manager-B9jS4Dsq.js +3 -0
  263. package/dist/{workspace-manager-CncdZkIy.js → workspace-manager-DuLhnzJV.js} +112 -27
  264. package/dist/workspace-manager-DuLhnzJV.js.map +1 -0
  265. package/package.json +2 -1
  266. package/scripts/post-merge-deploy.sh +25 -5
  267. package/scripts/record-cost-event.js +57 -7
  268. package/scripts/record-cost-event.js.map +1 -1
  269. package/skills/pan-help/SKILL.md +1 -1
  270. package/skills/pan-sync/SKILL.md +6 -6
  271. package/skills/workspace-add-repo/skill.md +46 -0
  272. package/templates/claude-md/sections/warnings.md +15 -2
  273. package/dist/clean-planning-sZXvy3Y5.js +0 -2
  274. package/dist/close-issue-Dml437qV.js +0 -2
  275. package/dist/close-issue-Dr7yZmrr.js.map +0 -1
  276. package/dist/compact-beads-iu218JcO.js +0 -2
  277. package/dist/dashboard/agent-enrichment-C67LJBgD.js.map +0 -1
  278. package/dist/dashboard/clean-planning-DCu3cOTu.js +0 -2
  279. package/dist/dashboard/close-issue-DfIggeZD.js.map +0 -1
  280. package/dist/dashboard/close-issue-DwdwYtar.js +0 -2
  281. package/dist/dashboard/compact-beads-DXY2fK2s.js +0 -2
  282. package/dist/dashboard/event-store-O9q0Gweh.js.map +0 -1
  283. package/dist/dashboard/hume-MZndNDVU.js +0 -3
  284. package/dist/dashboard/label-cleanup-CZEsbtq9.js.map +0 -1
  285. package/dist/dashboard/lifecycle-ZTYdrr2O.js +0 -7
  286. package/dist/dashboard/merge-agent-twroFuAh.js.map +0 -1
  287. package/dist/dashboard/projects-Cq3TWdPS.js.map +0 -1
  288. package/dist/dashboard/providers-Ck2sQd_F.js.map +0 -1
  289. package/dist/dashboard/public/assets/index-CpSmB2ts.css +0 -1
  290. package/dist/dashboard/public/assets/index-yarWhi0M.js +0 -214
  291. package/dist/dashboard/rally-CQ1OBJrJ.js +0 -3
  292. package/dist/dashboard/settings-CuHV-wcv.js.map +0 -1
  293. package/dist/dashboard/settings-DMeGBRsk.js +0 -2
  294. package/dist/dashboard/specialists-C6s3U6tX.js.map +0 -1
  295. package/dist/dashboard/workflows-B2ARUpOa.js +0 -2
  296. package/dist/dashboard/workflows-N1UTipYl.js.map +0 -1
  297. package/dist/dashboard/workspace-config-cmp5_ipD.js.map +0 -1
  298. package/dist/dashboard/workspace-manager-D_y9ZmW_.js.map +0 -1
  299. package/dist/hume-BjmwmJ9E.js +0 -3
  300. package/dist/label-cleanup-31ElPqqv.js.map +0 -1
  301. package/dist/merge-agent-VQH9z9t8.js.map +0 -1
  302. package/dist/projects-CvLepaxC.js.map +0 -1
  303. package/dist/providers-DcCPZ5K4.js.map +0 -1
  304. package/dist/rally-DR9x8--6.js +0 -3
  305. package/dist/shadow-state-p3jpGRPJ.js +0 -2
  306. package/dist/specialists-DEKqgkxp.js.map +0 -1
  307. package/dist/tracker-C_62ukEq.js.map +0 -1
  308. package/dist/tracker-utils-CVU2W1sX.js.map +0 -1
  309. package/dist/workspace-config-CNXOpKuj.js.map +0 -1
  310. package/dist/workspace-manager-CncdZkIy.js.map +0 -1
  311. package/dist/workspace-manager-Cx0r2Jnv.js +0 -3
@@ -1,4 +1,5 @@
1
- import { g as loadProjectsConfig, p as init_projects, s as getIssuePrefix } from "./projects-CvLepaxC.js";
1
+ import { i as parseIssueId, n as extractPrefix, r as init_issue_id, t as extractNumber } from "./issue-id-CAcekoIw.js";
2
+ import { g as loadProjectsConfig, p as init_projects, s as getIssuePrefix } from "./projects-Bk-5QhFQ.js";
2
3
  import { existsSync, readFileSync } from "fs";
3
4
  import { join } from "path";
4
5
  import { homedir } from "os";
@@ -10,6 +11,7 @@ import { homedir } from "os";
10
11
  * Eliminates hardcoded prefix checks like `issueId.startsWith('PAN-')`.
11
12
  */
12
13
  init_projects();
14
+ init_issue_id();
13
15
  /**
14
16
  * Parse GitHub repos from GITHUB_REPOS env var and projects.yaml.
15
17
  * Priority: GITHUB_REPOS env var first, then auto-derive from projects.yaml.
@@ -45,10 +47,12 @@ function parseGitHubRepos() {
45
47
  return repos;
46
48
  }
47
49
  /**
48
- * Extract the prefix from an issue ID (e.g., "CLI" from "CLI-1", "PAN" from "PAN-42").
50
+ * Extract the prefix from an issue ID (e.g., "CLI" from "CLI-1", "PAN" from "PAN-42", "F" from "F29698").
51
+ * Uses unified parser to support standard, Rally, and custom formats.
52
+ * @deprecated Use extractPrefix from issue-id.ts for unified parsing
49
53
  */
50
54
  function extractIssuePrefix(issueId) {
51
- return issueId.split("-")[0].toUpperCase();
55
+ return extractPrefix(issueId) ?? issueId.split("-")[0].toUpperCase();
52
56
  }
53
57
  /**
54
58
  * Resolve an issue ID to its GitHub repo config, or determine it's not a GitHub issue.
@@ -60,8 +64,8 @@ function resolveGitHubIssue(issueId) {
60
64
  const prefix = extractIssuePrefix(issueId);
61
65
  const repos = parseGitHubRepos();
62
66
  for (const repoConfig of repos) if (repoConfig.prefix === prefix) {
63
- const number = parseInt(issueId.split("-")[1], 10);
64
- if (!isNaN(number)) return {
67
+ const number = extractNumber(issueId);
68
+ if (number !== null) return {
65
69
  isGitHub: true,
66
70
  ...repoConfig,
67
71
  number
@@ -80,18 +84,36 @@ function isGitHubIssue(issueId) {
80
84
  *
81
85
  * Resolution order:
82
86
  * 1. GitHub — prefix matches a configured github_repo project
83
- * 2. Rally — prefix matches a project with rally_project but no linear_team / github_repo
87
+ * 2. Rally — prefix matches a project with rally_project or tracker: 'rally'
84
88
  * 3. Linear — fallback (matches linear_team or unknown prefix)
85
89
  */
86
90
  function resolveTrackerType(issueId) {
87
91
  if (resolveGitHubIssue(issueId).isGitHub) return "github";
88
- const prefix = extractIssuePrefix(issueId);
92
+ const parsed = parseIssueId(issueId);
93
+ if (!parsed) return "linear";
89
94
  try {
90
95
  const { projects } = loadProjectsConfig();
91
- for (const [key, project] of Object.entries(projects)) if ((getIssuePrefix(project) || key.toUpperCase().replace(/-/g, ""))?.toUpperCase() === prefix) {
92
- if (project.github_repo) return "github";
93
- if (project.rally_project) return "rally";
94
- return "linear";
96
+ for (const [key, project] of Object.entries(projects)) {
97
+ const singlePrefix = getIssuePrefix(project);
98
+ if (singlePrefix?.toUpperCase() === parsed.prefix) {
99
+ if (project.tracker) return project.tracker;
100
+ if (project.github_repo) return "github";
101
+ if (project.rally_project) return "rally";
102
+ return "linear";
103
+ }
104
+ if (project.issue_prefixes?.some((p) => p.toUpperCase() === parsed.prefix)) {
105
+ if (project.tracker) return project.tracker;
106
+ if (project.rally_project) return "rally";
107
+ return "linear";
108
+ }
109
+ if (!singlePrefix && !project.issue_prefixes) {
110
+ if (key.toUpperCase().replace(/-/g, "") === parsed.prefix) {
111
+ if (project.tracker) return project.tracker;
112
+ if (project.github_repo) return "github";
113
+ if (project.rally_project) return "rally";
114
+ return "linear";
115
+ }
116
+ }
95
117
  }
96
118
  } catch {}
97
119
  return "linear";
@@ -99,4 +121,4 @@ function resolveTrackerType(issueId) {
99
121
  //#endregion
100
122
  export { resolveGitHubIssue as n, resolveTrackerType as r, isGitHubIssue as t };
101
123
 
102
- //# sourceMappingURL=tracker-utils-CVU2W1sX.js.map
124
+ //# sourceMappingURL=tracker-utils-ChQyut8w.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracker-utils-ChQyut8w.js","names":[],"sources":["../src/lib/tracker-utils.ts"],"sourcesContent":["/**\n * Shared tracker utilities for resolving issue IDs to their tracker type\n * (GitHub or Linear) based on GITHUB_REPOS configuration.\n *\n * Eliminates hardcoded prefix checks like `issueId.startsWith('PAN-')`.\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { loadProjectsConfig, getIssuePrefix } from './projects.js';\nimport { extractPrefix, extractNumber, parseIssueId } from './issue-id.js';\n\nexport interface GitHubRepoConfig {\n owner: string;\n repo: string;\n prefix: string;\n}\n\nexport interface GitHubIssueResolution {\n isGitHub: true;\n owner: string;\n repo: string;\n prefix: string;\n number: number;\n}\n\nexport interface NonGitHubResolution {\n isGitHub: false;\n}\n\nexport type IssueResolution = GitHubIssueResolution | NonGitHubResolution;\n\n/**\n * Parse GitHub repos from GITHUB_REPOS env var and projects.yaml.\n * Priority: GITHUB_REPOS env var first, then auto-derive from projects.yaml.\n * Format for env var: \"owner/repo:PREFIX,owner2/repo2:PREFIX2\"\n */\nexport function parseGitHubRepos(): GitHubRepoConfig[] {\n const repos: GitHubRepoConfig[] = [];\n\n // 1. Check GITHUB_REPOS env var\n const envFile = join(homedir(), '.panopticon.env');\n if (existsSync(envFile)) {\n const content = readFileSync(envFile, 'utf-8');\n const reposMatch = content.match(/GITHUB_REPOS=(.+)/);\n if (reposMatch) {\n repos.push(...reposMatch[1].trim().split(',').map(r => {\n const [repoPath, prefix] = r.trim().split(':');\n const [owner, repo] = (repoPath || '').split('/');\n return { owner: owner || '', repo: repo || '', prefix: (prefix || '').toUpperCase() };\n }).filter(r => r.owner && r.repo && r.prefix));\n }\n }\n\n // 2. Auto-derive from projects.yaml (if no explicit GITHUB_REPOS)\n if (repos.length === 0) {\n try {\n const { projects } = loadProjectsConfig();\n for (const [key, project] of Object.entries(projects)) {\n if (project.github_repo) {\n const [owner, repo] = project.github_repo.split('/');\n // Derive prefix: linear_team if set, otherwise uppercase project key\n const prefix = getIssuePrefix(project) || key.toUpperCase().replace(/-/g, '');\n if (owner && repo && prefix) {\n repos.push({ owner, repo, prefix: prefix.toUpperCase() });\n }\n }\n }\n } catch { /* ignore */ }\n }\n\n return repos;\n}\n\n/**\n * Extract the prefix from an issue ID (e.g., \"CLI\" from \"CLI-1\", \"PAN\" from \"PAN-42\", \"F\" from \"F29698\").\n * Uses unified parser to support standard, Rally, and custom formats.\n * @deprecated Use extractPrefix from issue-id.ts for unified parsing\n */\nexport function extractIssuePrefix(issueId: string): string {\n return extractPrefix(issueId) ?? issueId.split('-')[0].toUpperCase();\n}\n\n/**\n * Resolve an issue ID to its GitHub repo config, or determine it's not a GitHub issue.\n *\n * Checks the issue prefix against all prefixes configured in GITHUB_REPOS.\n * Returns the matching repo config with parsed issue number, or { isGitHub: false }.\n */\nexport function resolveGitHubIssue(issueId: string): IssueResolution {\n const prefix = extractIssuePrefix(issueId);\n const repos = parseGitHubRepos();\n\n for (const repoConfig of repos) {\n if (repoConfig.prefix === prefix) {\n const number = extractNumber(issueId);\n if (number !== null) {\n return { isGitHub: true, ...repoConfig, number };\n }\n }\n }\n\n return { isGitHub: false };\n}\n\n/**\n * Check if an issue ID belongs to a GitHub-tracked project.\n */\nexport function isGitHubIssue(issueId: string): boolean {\n return resolveGitHubIssue(issueId).isGitHub;\n}\n\nexport type TrackerTypeResolution = 'github' | 'rally' | 'linear' | 'gitlab';\n\n/**\n * Resolve the tracker type for an issue ID by checking projects.yaml configuration.\n *\n * Resolution order:\n * 1. GitHub — prefix matches a configured github_repo project\n * 2. Rally — prefix matches a project with rally_project or tracker: 'rally'\n * 3. Linear — fallback (matches linear_team or unknown prefix)\n */\nexport function resolveTrackerType(issueId: string): TrackerTypeResolution {\n // Check GitHub first (existing logic)\n if (resolveGitHubIssue(issueId).isGitHub) {\n return 'github';\n }\n\n // Check if the issue prefix matches a project with explicit tracker type\n const parsed = parseIssueId(issueId);\n if (!parsed) {\n return 'linear'; // default for unparseable IDs\n }\n\n try {\n const { projects } = loadProjectsConfig();\n for (const [key, project] of Object.entries(projects)) {\n // Check single issue_prefix\n const singlePrefix = getIssuePrefix(project);\n if (singlePrefix?.toUpperCase() === parsed.prefix) {\n if (project.tracker) return project.tracker;\n if (project.github_repo) return 'github';\n if (project.rally_project) return 'rally';\n return 'linear';\n }\n\n // Check issue_prefixes array (multiple prefixes per project)\n if (project.issue_prefixes?.some(p => p.toUpperCase() === parsed.prefix)) {\n if (project.tracker) return project.tracker;\n if (project.rally_project) return 'rally';\n return 'linear';\n }\n\n // Derive prefix from project key for projects without explicit prefixes\n if (!singlePrefix && !project.issue_prefixes) {\n const derivedPrefix = key.toUpperCase().replace(/-/g, '');\n if (derivedPrefix === parsed.prefix) {\n if (project.tracker) return project.tracker;\n if (project.github_repo) return 'github';\n if (project.rally_project) return 'rally';\n return 'linear';\n }\n }\n }\n } catch { /* ignore config errors */ }\n\n // Default to Linear for unknown prefixes\n return 'linear';\n}\n"],"mappings":";;;;;;;;;;;;eAUmE;eACQ;;;;;;AA2B3E,SAAgB,mBAAuC;CACrD,MAAM,QAA4B,EAAE;CAGpC,MAAM,UAAU,KAAK,SAAS,EAAE,kBAAkB;AAClD,KAAI,WAAW,QAAQ,EAAE;EAEvB,MAAM,aADU,aAAa,SAAS,QAAQ,CACnB,MAAM,oBAAoB;AACrD,MAAI,WACF,OAAM,KAAK,GAAG,WAAW,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,KAAI,MAAK;GACrD,MAAM,CAAC,UAAU,UAAU,EAAE,MAAM,CAAC,MAAM,IAAI;GAC9C,MAAM,CAAC,OAAO,SAAS,YAAY,IAAI,MAAM,IAAI;AACjD,UAAO;IAAE,OAAO,SAAS;IAAI,MAAM,QAAQ;IAAI,SAAS,UAAU,IAAI,aAAa;IAAE;IACrF,CAAC,QAAO,MAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC;;AAKlD,KAAI,MAAM,WAAW,EACnB,KAAI;EACF,MAAM,EAAE,aAAa,oBAAoB;AACzC,OAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,SAAS,CACnD,KAAI,QAAQ,aAAa;GACvB,MAAM,CAAC,OAAO,QAAQ,QAAQ,YAAY,MAAM,IAAI;GAEpD,MAAM,SAAS,eAAe,QAAQ,IAAI,IAAI,aAAa,CAAC,QAAQ,MAAM,GAAG;AAC7E,OAAI,SAAS,QAAQ,OACnB,OAAM,KAAK;IAAE;IAAO;IAAM,QAAQ,OAAO,aAAa;IAAE,CAAC;;SAIzD;AAGV,QAAO;;;;;;;AAQT,SAAgB,mBAAmB,SAAyB;AAC1D,QAAO,cAAc,QAAQ,IAAI,QAAQ,MAAM,IAAI,CAAC,GAAG,aAAa;;;;;;;;AAStE,SAAgB,mBAAmB,SAAkC;CACnE,MAAM,SAAS,mBAAmB,QAAQ;CAC1C,MAAM,QAAQ,kBAAkB;AAEhC,MAAK,MAAM,cAAc,MACvB,KAAI,WAAW,WAAW,QAAQ;EAChC,MAAM,SAAS,cAAc,QAAQ;AACrC,MAAI,WAAW,KACb,QAAO;GAAE,UAAU;GAAM,GAAG;GAAY;GAAQ;;AAKtD,QAAO,EAAE,UAAU,OAAO;;;;;AAM5B,SAAgB,cAAc,SAA0B;AACtD,QAAO,mBAAmB,QAAQ,CAAC;;;;;;;;;;AAarC,SAAgB,mBAAmB,SAAwC;AAEzE,KAAI,mBAAmB,QAAQ,CAAC,SAC9B,QAAO;CAIT,MAAM,SAAS,aAAa,QAAQ;AACpC,KAAI,CAAC,OACH,QAAO;AAGT,KAAI;EACF,MAAM,EAAE,aAAa,oBAAoB;AACzC,OAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,SAAS,EAAE;GAErD,MAAM,eAAe,eAAe,QAAQ;AAC5C,OAAI,cAAc,aAAa,KAAK,OAAO,QAAQ;AACjD,QAAI,QAAQ,QAAS,QAAO,QAAQ;AACpC,QAAI,QAAQ,YAAa,QAAO;AAChC,QAAI,QAAQ,cAAe,QAAO;AAClC,WAAO;;AAIT,OAAI,QAAQ,gBAAgB,MAAK,MAAK,EAAE,aAAa,KAAK,OAAO,OAAO,EAAE;AACxE,QAAI,QAAQ,QAAS,QAAO,QAAQ;AACpC,QAAI,QAAQ,cAAe,QAAO;AAClC,WAAO;;AAIT,OAAI,CAAC,gBAAgB,CAAC,QAAQ;QACN,IAAI,aAAa,CAAC,QAAQ,MAAM,GAAG,KACnC,OAAO,QAAQ;AACnC,SAAI,QAAQ,QAAS,QAAO,QAAQ;AACpC,SAAI,QAAQ,YAAa,QAAO;AAChC,SAAI,QAAQ,cAAe,QAAO;AAClC,YAAO;;;;SAIP;AAGR,QAAO"}
@@ -1,6 +1,6 @@
1
- import { B as TRAEFIK_CERTS_DIR, H as TRAEFIK_DYNAMIC_DIR, L as SOURCE_TRAEFIK_TEMPLATES, V as TRAEFIK_DIR, W as init_paths } from "./paths-lMaxrYtT.js";
2
- import { a as init_config, o as loadConfig } from "./config-CRzMQRgA.js";
3
- import { g as loadProjectsConfig, p as init_projects } from "./projects-CvLepaxC.js";
1
+ import { B as TRAEFIK_CERTS_DIR, G as init_paths, H as TRAEFIK_DYNAMIC_DIR, L as SOURCE_TRAEFIK_TEMPLATES, V as TRAEFIK_DIR } from "./paths-CDJ_HsbN.js";
2
+ import { a as init_config, o as loadConfig } from "./config-BQNKsi9G.js";
3
+ import { g as loadProjectsConfig, p as init_projects } from "./projects-Bk-5QhFQ.js";
4
4
  import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "fs";
5
5
  import { join } from "path";
6
6
  import { execSync } from "child_process";
@@ -146,4 +146,4 @@ function cleanupStaleTlsSections() {
146
146
  //#endregion
147
147
  export { generateTlsConfig as a, generatePanopticonTraefikConfig as i, cleanupTemplateFiles as n, ensureProjectCerts as r, cleanupStaleTlsSections as t };
148
148
 
149
- //# sourceMappingURL=traefik-DHgBoWXX.js.map
149
+ //# sourceMappingURL=traefik-C80EbDu_.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"traefik-DHgBoWXX.js","names":[],"sources":["../src/lib/traefik.ts"],"sourcesContent":["/**\n * Traefik Configuration Generator\n *\n * Generates the Panopticon dashboard Traefik routing config\n * from a template, substituting values from config.toml.\n * Also generates TLS certificate configuration from discovered certs.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, readdirSync } from 'fs';\nimport { join, basename } from 'path';\nimport { execSync } from 'child_process';\nimport { TRAEFIK_DYNAMIC_DIR, TRAEFIK_CERTS_DIR, TRAEFIK_DIR, SOURCE_TRAEFIK_TEMPLATES } from './paths.js';\nimport { loadConfig } from './config.js';\nimport { loadProjectsConfig } from './projects.js';\n\n/**\n * Generate panopticon.yml from template using current config values.\n * Safe to call multiple times (idempotent).\n * Returns true if file was written, false if template not found.\n */\nexport function generatePanopticonTraefikConfig(): boolean {\n const templatePath = join(SOURCE_TRAEFIK_TEMPLATES, 'dynamic', 'panopticon.yml.template');\n if (!existsSync(templatePath)) {\n return false;\n }\n\n const config = loadConfig();\n const placeholders: Record<string, string> = {\n TRAEFIK_DOMAIN: config.traefik?.domain || 'pan.localhost',\n DASHBOARD_PORT: String(config.dashboard.port),\n DASHBOARD_API_PORT: String(config.dashboard.api_port),\n };\n\n let content = readFileSync(templatePath, 'utf-8');\n for (const [key, value] of Object.entries(placeholders)) {\n content = content.replace(new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, 'g'), value);\n }\n\n mkdirSync(TRAEFIK_DYNAMIC_DIR, { recursive: true });\n const outputPath = join(TRAEFIK_DYNAMIC_DIR, 'panopticon.yml');\n writeFileSync(outputPath, content, 'utf-8');\n return true;\n}\n\n/**\n * Remove any accidentally-copied .template files from the runtime Traefik dir.\n * Called after copyDirectoryRecursive in pan install.\n */\nexport function cleanupTemplateFiles(): void {\n const copiedTemplate = join(TRAEFIK_DYNAMIC_DIR, 'panopticon.yml.template');\n if (existsSync(copiedTemplate)) {\n unlinkSync(copiedTemplate);\n }\n}\n\n/**\n * Generate tls.yml from all discovered certificate files in the certs directory.\n *\n * Traefik v3 ignores `tls:` sections when they appear in the same dynamic config\n * file as `http:` routers/services. This function creates a dedicated tls.yml file\n * that Traefik's file provider will pick up separately.\n *\n * The first cert found (pan.localhost) is used as the default certificate.\n * All certs are listed in the certificates array for SNI matching.\n *\n * Safe to call multiple times (idempotent).\n * Returns true if file was written, false if no certs found.\n */\nexport function generateTlsConfig(): boolean {\n if (!existsSync(TRAEFIK_CERTS_DIR)) {\n return false;\n }\n\n // Scan for cert files (exclude -key.pem files)\n const files = readdirSync(TRAEFIK_CERTS_DIR);\n const certFiles = files.filter(f => f.endsWith('.pem') && !f.endsWith('-key.pem'));\n\n if (certFiles.length === 0) {\n return false;\n }\n\n // Pair each cert with its key file\n const certPairs: Array<{ certFile: string; keyFile: string }> = [];\n for (const certFile of certFiles) {\n const keyFile = certFile.replace('.pem', '-key.pem');\n if (files.includes(keyFile)) {\n certPairs.push({\n certFile: `/etc/traefik/certs/${certFile}`,\n keyFile: `/etc/traefik/certs/${keyFile}`,\n });\n }\n }\n\n if (certPairs.length === 0) {\n return false;\n }\n\n // Use the pan.localhost cert as default, fall back to first cert\n const defaultCert = certPairs.find(p => p.certFile.includes('pan.localhost')) || certPairs[0];\n\n // Build YAML content\n let yaml = '# Auto-generated TLS configuration — do not edit manually\\n';\n yaml += '# Generated by: pan up / pan install\\n';\n yaml += '# Traefik v3 requires TLS config in a separate dynamic config file\\n\\n';\n yaml += 'tls:\\n';\n yaml += ' stores:\\n';\n yaml += ' default:\\n';\n yaml += ' defaultCertificate:\\n';\n yaml += ` certFile: ${defaultCert.certFile}\\n`;\n yaml += ` keyFile: ${defaultCert.keyFile}\\n`;\n yaml += ' certificates:\\n';\n for (const pair of certPairs) {\n yaml += ` - certFile: ${pair.certFile}\\n`;\n yaml += ` keyFile: ${pair.keyFile}\\n`;\n }\n\n mkdirSync(TRAEFIK_DYNAMIC_DIR, { recursive: true });\n const outputPath = join(TRAEFIK_DYNAMIC_DIR, 'tls.yml');\n writeFileSync(outputPath, yaml, 'utf-8');\n return true;\n}\n\n/**\n * Ensure wildcard certificates exist for all registered projects that have DNS domains.\n *\n * Scans projects.yaml for projects with workspace.dns.domain, and generates\n * mkcert wildcard certs for any that don't already have certs in the Traefik\n * certs directory.\n *\n * Returns array of domains that had certs generated.\n */\nexport function ensureProjectCerts(): string[] {\n // Check mkcert is available\n try {\n execSync('which mkcert', { stdio: 'pipe' });\n } catch {\n return [];\n }\n\n const projectsConfig = loadProjectsConfig();\n const generated: string[] = [];\n\n for (const [, project] of Object.entries(projectsConfig.projects)) {\n const domain = project.workspace?.dns?.domain;\n if (!domain) continue;\n\n const certFile = join(TRAEFIK_CERTS_DIR, `_wildcard.${domain}.pem`);\n const keyFile = join(TRAEFIK_CERTS_DIR, `_wildcard.${domain}-key.pem`);\n\n if (existsSync(certFile) && existsSync(keyFile)) {\n continue;\n }\n\n // Generate cert for this project's domain\n mkdirSync(TRAEFIK_CERTS_DIR, { recursive: true });\n try {\n execSync(\n `mkcert -cert-file \"${certFile}\" -key-file \"${keyFile}\" \"${domain}\" \"*.${domain}\" 2>/dev/null`,\n { stdio: 'pipe' }\n );\n generated.push(domain);\n } catch {\n // mkcert failed — skip this domain\n }\n }\n\n return generated;\n}\n\n/**\n * Remove stale `tls:` sections from runtime config files.\n *\n * Traefik v3 ignores tls: in static config (traefik.yml) and in dynamic\n * config files that also contain http: routers. This function strips those\n * dead sections to avoid confusion.\n *\n * Called during `pan up` to clean up configs from older Panopticon versions.\n */\nexport function cleanupStaleTlsSections(): void {\n // Clean static config (traefik.yml)\n const staticConfig = join(TRAEFIK_DIR, 'traefik.yml');\n if (existsSync(staticConfig)) {\n const content = readFileSync(staticConfig, 'utf-8');\n // Remove tls: section at the end of the file\n const cleaned = content.replace(/\\n# TLS Configuration\\ntls:\\n(?: .*\\n)*/g, '\\n');\n if (cleaned !== content) {\n writeFileSync(staticConfig, cleaned, 'utf-8');\n }\n }\n\n // Clean dynamic panopticon.yml (regenerated from template, but also clean runtime copy)\n const dynamicConfig = join(TRAEFIK_DYNAMIC_DIR, 'panopticon.yml');\n if (existsSync(dynamicConfig)) {\n const content = readFileSync(dynamicConfig, 'utf-8');\n // Remove standalone tls: section (not nested under http: routers)\n const cleaned = content.replace(/\\ntls:\\n (?:stores|certificates):\\n(?: .*\\n)*/g, '\\n');\n if (cleaned !== content) {\n writeFileSync(dynamicConfig, cleaned, 'utf-8');\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"traefik-C80EbDu_.js","names":[],"sources":["../src/lib/traefik.ts"],"sourcesContent":["/**\n * Traefik Configuration Generator\n *\n * Generates the Panopticon dashboard Traefik routing config\n * from a template, substituting values from config.toml.\n * Also generates TLS certificate configuration from discovered certs.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, readdirSync } from 'fs';\nimport { join, basename } from 'path';\nimport { execSync } from 'child_process';\nimport { TRAEFIK_DYNAMIC_DIR, TRAEFIK_CERTS_DIR, TRAEFIK_DIR, SOURCE_TRAEFIK_TEMPLATES } from './paths.js';\nimport { loadConfig } from './config.js';\nimport { loadProjectsConfig } from './projects.js';\n\n/**\n * Generate panopticon.yml from template using current config values.\n * Safe to call multiple times (idempotent).\n * Returns true if file was written, false if template not found.\n */\nexport function generatePanopticonTraefikConfig(): boolean {\n const templatePath = join(SOURCE_TRAEFIK_TEMPLATES, 'dynamic', 'panopticon.yml.template');\n if (!existsSync(templatePath)) {\n return false;\n }\n\n const config = loadConfig();\n const placeholders: Record<string, string> = {\n TRAEFIK_DOMAIN: config.traefik?.domain || 'pan.localhost',\n DASHBOARD_PORT: String(config.dashboard.port),\n DASHBOARD_API_PORT: String(config.dashboard.api_port),\n };\n\n let content = readFileSync(templatePath, 'utf-8');\n for (const [key, value] of Object.entries(placeholders)) {\n content = content.replace(new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, 'g'), value);\n }\n\n mkdirSync(TRAEFIK_DYNAMIC_DIR, { recursive: true });\n const outputPath = join(TRAEFIK_DYNAMIC_DIR, 'panopticon.yml');\n writeFileSync(outputPath, content, 'utf-8');\n return true;\n}\n\n/**\n * Remove any accidentally-copied .template files from the runtime Traefik dir.\n * Called after copyDirectoryRecursive in pan install.\n */\nexport function cleanupTemplateFiles(): void {\n const copiedTemplate = join(TRAEFIK_DYNAMIC_DIR, 'panopticon.yml.template');\n if (existsSync(copiedTemplate)) {\n unlinkSync(copiedTemplate);\n }\n}\n\n/**\n * Generate tls.yml from all discovered certificate files in the certs directory.\n *\n * Traefik v3 ignores `tls:` sections when they appear in the same dynamic config\n * file as `http:` routers/services. This function creates a dedicated tls.yml file\n * that Traefik's file provider will pick up separately.\n *\n * The first cert found (pan.localhost) is used as the default certificate.\n * All certs are listed in the certificates array for SNI matching.\n *\n * Safe to call multiple times (idempotent).\n * Returns true if file was written, false if no certs found.\n */\nexport function generateTlsConfig(): boolean {\n if (!existsSync(TRAEFIK_CERTS_DIR)) {\n return false;\n }\n\n // Scan for cert files (exclude -key.pem files)\n const files = readdirSync(TRAEFIK_CERTS_DIR);\n const certFiles = files.filter(f => f.endsWith('.pem') && !f.endsWith('-key.pem'));\n\n if (certFiles.length === 0) {\n return false;\n }\n\n // Pair each cert with its key file\n const certPairs: Array<{ certFile: string; keyFile: string }> = [];\n for (const certFile of certFiles) {\n const keyFile = certFile.replace('.pem', '-key.pem');\n if (files.includes(keyFile)) {\n certPairs.push({\n certFile: `/etc/traefik/certs/${certFile}`,\n keyFile: `/etc/traefik/certs/${keyFile}`,\n });\n }\n }\n\n if (certPairs.length === 0) {\n return false;\n }\n\n // Use the pan.localhost cert as default, fall back to first cert\n const defaultCert = certPairs.find(p => p.certFile.includes('pan.localhost')) || certPairs[0];\n\n // Build YAML content\n let yaml = '# Auto-generated TLS configuration — do not edit manually\\n';\n yaml += '# Generated by: pan up / pan install\\n';\n yaml += '# Traefik v3 requires TLS config in a separate dynamic config file\\n\\n';\n yaml += 'tls:\\n';\n yaml += ' stores:\\n';\n yaml += ' default:\\n';\n yaml += ' defaultCertificate:\\n';\n yaml += ` certFile: ${defaultCert.certFile}\\n`;\n yaml += ` keyFile: ${defaultCert.keyFile}\\n`;\n yaml += ' certificates:\\n';\n for (const pair of certPairs) {\n yaml += ` - certFile: ${pair.certFile}\\n`;\n yaml += ` keyFile: ${pair.keyFile}\\n`;\n }\n\n mkdirSync(TRAEFIK_DYNAMIC_DIR, { recursive: true });\n const outputPath = join(TRAEFIK_DYNAMIC_DIR, 'tls.yml');\n writeFileSync(outputPath, yaml, 'utf-8');\n return true;\n}\n\n/**\n * Ensure wildcard certificates exist for all registered projects that have DNS domains.\n *\n * Scans projects.yaml for projects with workspace.dns.domain, and generates\n * mkcert wildcard certs for any that don't already have certs in the Traefik\n * certs directory.\n *\n * Returns array of domains that had certs generated.\n */\nexport function ensureProjectCerts(): string[] {\n // Check mkcert is available\n try {\n execSync('which mkcert', { stdio: 'pipe' });\n } catch {\n return [];\n }\n\n const projectsConfig = loadProjectsConfig();\n const generated: string[] = [];\n\n for (const [, project] of Object.entries(projectsConfig.projects)) {\n const domain = project.workspace?.dns?.domain;\n if (!domain) continue;\n\n const certFile = join(TRAEFIK_CERTS_DIR, `_wildcard.${domain}.pem`);\n const keyFile = join(TRAEFIK_CERTS_DIR, `_wildcard.${domain}-key.pem`);\n\n if (existsSync(certFile) && existsSync(keyFile)) {\n continue;\n }\n\n // Generate cert for this project's domain\n mkdirSync(TRAEFIK_CERTS_DIR, { recursive: true });\n try {\n execSync(\n `mkcert -cert-file \"${certFile}\" -key-file \"${keyFile}\" \"${domain}\" \"*.${domain}\" 2>/dev/null`,\n { stdio: 'pipe' }\n );\n generated.push(domain);\n } catch {\n // mkcert failed — skip this domain\n }\n }\n\n return generated;\n}\n\n/**\n * Remove stale `tls:` sections from runtime config files.\n *\n * Traefik v3 ignores tls: in static config (traefik.yml) and in dynamic\n * config files that also contain http: routers. This function strips those\n * dead sections to avoid confusion.\n *\n * Called during `pan up` to clean up configs from older Panopticon versions.\n */\nexport function cleanupStaleTlsSections(): void {\n // Clean static config (traefik.yml)\n const staticConfig = join(TRAEFIK_DIR, 'traefik.yml');\n if (existsSync(staticConfig)) {\n const content = readFileSync(staticConfig, 'utf-8');\n // Remove tls: section at the end of the file\n const cleaned = content.replace(/\\n# TLS Configuration\\ntls:\\n(?: .*\\n)*/g, '\\n');\n if (cleaned !== content) {\n writeFileSync(staticConfig, cleaned, 'utf-8');\n }\n }\n\n // Clean dynamic panopticon.yml (regenerated from template, but also clean runtime copy)\n const dynamicConfig = join(TRAEFIK_DYNAMIC_DIR, 'panopticon.yml');\n if (existsSync(dynamicConfig)) {\n const content = readFileSync(dynamicConfig, 'utf-8');\n // Remove standalone tls: section (not nested under http: routers)\n const cleaned = content.replace(/\\ntls:\\n (?:stores|certificates):\\n(?: .*\\n)*/g, '\\n');\n if (cleaned !== content) {\n writeFileSync(dynamicConfig, cleaned, 'utf-8');\n }\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { a as generateTlsConfig, i as generatePanopticonTraefikConfig, r as ensureProjectCerts, t as cleanupStaleTlsSections } from "./traefik-DHgBoWXX.js";
1
+ import { a as generateTlsConfig, i as generatePanopticonTraefikConfig, r as ensureProjectCerts, t as cleanupStaleTlsSections } from "./traefik-C80EbDu_.js";
2
2
  export { cleanupStaleTlsSections, ensureProjectCerts, generatePanopticonTraefikConfig, generateTlsConfig };
@@ -1,3 +1,3 @@
1
- import { n as init_tunnel, r as removeTunnelIngress } from "./tunnel-Bl1qNSyQ.js";
1
+ import { n as init_tunnel, r as removeTunnelIngress } from "./tunnel-DzXEPwIc.js";
2
2
  init_tunnel();
3
3
  export { removeTunnelIngress };
@@ -1,5 +1,5 @@
1
1
  import { t as __esmMin } from "./chunk-ruWRV7i3.js";
2
- import { n as init_workspace_config, r as replacePlaceholders } from "./workspace-config-CNXOpKuj.js";
2
+ import { n as init_workspace_config, r as replacePlaceholders } from "./workspace-config-fUafvYMp.js";
3
3
  import { readFileSync } from "fs";
4
4
  import { resolve } from "path";
5
5
  import { homedir } from "os";
@@ -204,4 +204,4 @@ var init_tunnel = __esmMin((() => {
204
204
  //#endregion
205
205
  export { init_tunnel as n, removeTunnelIngress as r, addTunnelIngress as t };
206
206
 
207
- //# sourceMappingURL=tunnel-Bl1qNSyQ.js.map
207
+ //# sourceMappingURL=tunnel-DzXEPwIc.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tunnel-Bl1qNSyQ.js","names":[],"sources":["../src/lib/tunnel.ts"],"sourcesContent":["/**\n * Cloudflare Tunnel Management\n *\n * Manages tunnel ingress rules and DNS CNAME records for workspace lifecycle.\n * Called during workspace create (addTunnelIngress) and workspace remove/deep-wipe (removeTunnelIngress).\n */\n\nimport { readFileSync } from 'fs';\nimport { resolve } from 'path';\nimport { homedir } from 'os';\nimport { TunnelConfig, TunnelHostname, TemplatePlaceholders, replacePlaceholders } from './workspace-config.js';\n\nexport interface TunnelResult {\n success: boolean;\n steps: string[];\n}\n\ninterface CloudflareCredentials {\n apiToken: string;\n accountId: string;\n zoneId: string;\n}\n\ninterface CloudflareIngressRule {\n service: string;\n hostname?: string;\n originRequest?: Record<string, unknown>;\n}\n\ninterface CloudflareTunnelConfig {\n config: {\n ingress: CloudflareIngressRule[];\n };\n}\n\nconst CF_API = 'https://api.cloudflare.com/client/v4';\nconst FETCH_TIMEOUT = 10_000;\n\n/**\n * Read API token from Cloudflare cert.pem file.\n * The cert.pem contains a PEM-wrapped base64 JSON blob with { zoneID, accountID, apiToken }.\n */\nfunction readCloudflareCredentials(certPath: string): CloudflareCredentials | null {\n try {\n const resolvedPath = certPath.replace(/^~/, homedir());\n const pem = readFileSync(resolve(resolvedPath), 'utf-8');\n // Strip PEM headers/trailers and decode\n const b64 = pem\n .split('\\n')\n .filter(line => !line.startsWith('-----'))\n .join('');\n const json = JSON.parse(Buffer.from(b64, 'base64').toString('utf-8'));\n return {\n apiToken: json.apiToken,\n accountId: json.accountID,\n zoneId: json.zoneID,\n };\n } catch (err) {\n return null;\n }\n}\n\n/**\n * Make an authenticated Cloudflare API request.\n */\nasync function cfFetch(\n path: string,\n apiToken: string,\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',\n body?: unknown,\n): Promise<{ ok: boolean; data: any; errors?: any[] }> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT);\n\n try {\n const resp = await fetch(`${CF_API}${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${apiToken}`,\n 'Content-Type': 'application/json',\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n const json = await resp.json() as any;\n return { ok: json.success !== false, data: json.result, errors: json.errors };\n } catch (err: any) {\n return { ok: false, data: null, errors: [{ message: err.message }] };\n } finally {\n clearTimeout(timeout);\n }\n}\n\n/**\n * Resolve hostnames from config, replacing template placeholders.\n */\nfunction resolveHostnames(\n hostnames: TunnelHostname[],\n placeholders: TemplatePlaceholders,\n): Array<{ hostname: string; httpHostHeader?: string; noTlsVerify: boolean }> {\n return hostnames.map(h => ({\n hostname: replacePlaceholders(h.pattern, placeholders),\n httpHostHeader: h.http_host_header ? replacePlaceholders(h.http_host_header, placeholders) : undefined,\n noTlsVerify: h.no_tls_verify !== false, // default true\n }));\n}\n\n/**\n * Add tunnel ingress rules and DNS CNAME records for a workspace.\n * Called during workspace creation.\n */\nexport async function addTunnelIngress(\n config: TunnelConfig,\n placeholders: TemplatePlaceholders,\n): Promise<TunnelResult> {\n const steps: string[] = [];\n let allOk = true;\n\n // Read credentials\n const creds = readCloudflareCredentials(config.credentials_file);\n if (!creds) {\n return { success: false, steps: ['[tunnel] Failed to read Cloudflare credentials from ' + config.credentials_file] };\n }\n steps.push('[tunnel] Read Cloudflare credentials');\n\n const resolved = resolveHostnames(config.hostnames, placeholders);\n\n // Get current tunnel configuration\n const tunnelPath = `/accounts/${creds.accountId}/cfd_tunnel/${config.tunnel_id}/configurations`;\n const current = await cfFetch(tunnelPath, creds.apiToken);\n if (!current.ok) {\n return { success: false, steps: [...steps, `[tunnel] Failed to get tunnel config: ${JSON.stringify(current.errors)}`] };\n }\n\n const tunnelConfig: CloudflareTunnelConfig = current.data;\n const ingress: CloudflareIngressRule[] = tunnelConfig.config?.ingress || [];\n steps.push(`[tunnel] Current tunnel config has ${ingress.length} ingress rules`);\n\n // Add new ingress rules (before the catch-all)\n let modified = false;\n for (const h of resolved) {\n // Skip if rule already exists\n if (ingress.some(r => r.hostname === h.hostname)) {\n steps.push(`[tunnel] Ingress rule for ${h.hostname} already exists, skipping`);\n continue;\n }\n\n const originRequest: Record<string, unknown> = {};\n if (h.noTlsVerify) originRequest.noTLSVerify = true;\n if (h.httpHostHeader) originRequest.httpHostHeader = h.httpHostHeader;\n\n const newRule: CloudflareIngressRule = {\n service: config.service_target,\n hostname: h.hostname,\n originRequest: Object.keys(originRequest).length > 0 ? originRequest : undefined,\n };\n\n // Insert before the last rule (catch-all has no hostname)\n const catchAllIdx = ingress.findIndex(r => !r.hostname);\n if (catchAllIdx >= 0) {\n ingress.splice(catchAllIdx, 0, newRule);\n } else {\n ingress.push(newRule);\n }\n modified = true;\n steps.push(`[tunnel] Added ingress rule for ${h.hostname}`);\n }\n\n // Push updated tunnel config\n if (modified) {\n const putResult = await cfFetch(tunnelPath, creds.apiToken, 'PUT', {\n config: { ingress },\n });\n if (!putResult.ok) {\n steps.push(`[tunnel] Failed to update tunnel config: ${JSON.stringify(putResult.errors)}`);\n allOk = false;\n } else {\n steps.push('[tunnel] Updated tunnel ingress configuration');\n }\n }\n\n // Create DNS CNAME records\n for (const h of resolved) {\n const dnsResult = await cfFetch(\n `/zones/${creds.zoneId}/dns_records`,\n creds.apiToken,\n 'POST',\n {\n type: 'CNAME',\n name: h.hostname,\n content: `${config.tunnel_id}.cfargotunnel.com`,\n proxied: true,\n },\n );\n if (!dnsResult.ok) {\n const errMsg = dnsResult.errors?.map((e: any) => e.message).join(', ') || 'unknown error';\n // Record already exists is not a failure\n if (errMsg.includes('already exists') || errMsg.includes('already been taken')) {\n steps.push(`[tunnel] DNS CNAME for ${h.hostname} already exists`);\n } else {\n steps.push(`[tunnel] Failed to create DNS CNAME for ${h.hostname}: ${errMsg}`);\n allOk = false;\n }\n } else {\n steps.push(`[tunnel] Created DNS CNAME: ${h.hostname} → ${config.tunnel_id}.cfargotunnel.com`);\n }\n }\n\n return { success: allOk, steps };\n}\n\n/**\n * Remove tunnel ingress rules and DNS CNAME records for a workspace.\n * Called during workspace removal and deep-wipe.\n */\nexport async function removeTunnelIngress(\n config: TunnelConfig,\n placeholders: TemplatePlaceholders,\n): Promise<TunnelResult> {\n const steps: string[] = [];\n let allOk = true;\n\n // Read credentials\n const creds = readCloudflareCredentials(config.credentials_file);\n if (!creds) {\n return { success: false, steps: ['[tunnel] Failed to read Cloudflare credentials from ' + config.credentials_file] };\n }\n steps.push('[tunnel] Read Cloudflare credentials');\n\n const resolved = resolveHostnames(config.hostnames, placeholders);\n const hostnameSet = new Set(resolved.map(h => h.hostname));\n\n // Get current tunnel configuration\n const tunnelPath = `/accounts/${creds.accountId}/cfd_tunnel/${config.tunnel_id}/configurations`;\n const current = await cfFetch(tunnelPath, creds.apiToken);\n if (!current.ok) {\n steps.push(`[tunnel] Failed to get tunnel config: ${JSON.stringify(current.errors)}`);\n // Continue to attempt DNS cleanup even if tunnel config read fails\n allOk = false;\n } else {\n const tunnelConfig: CloudflareTunnelConfig = current.data;\n const ingress: CloudflareIngressRule[] = tunnelConfig.config?.ingress || [];\n const originalCount = ingress.length;\n\n // Filter out matching ingress rules\n const filtered = ingress.filter(r => !r.hostname || !hostnameSet.has(r.hostname));\n\n if (filtered.length < originalCount) {\n const putResult = await cfFetch(tunnelPath, creds.apiToken, 'PUT', {\n config: { ingress: filtered },\n });\n if (!putResult.ok) {\n steps.push(`[tunnel] Failed to update tunnel config: ${JSON.stringify(putResult.errors)}`);\n allOk = false;\n } else {\n steps.push(`[tunnel] Removed ${originalCount - filtered.length} ingress rule(s)`);\n }\n } else {\n steps.push('[tunnel] No matching ingress rules found to remove');\n }\n }\n\n // Remove DNS CNAME records\n for (const h of resolved) {\n // Find the DNS record\n const listResult = await cfFetch(\n `/zones/${creds.zoneId}/dns_records?name=${encodeURIComponent(h.hostname)}&type=CNAME`,\n creds.apiToken,\n );\n if (!listResult.ok) {\n steps.push(`[tunnel] Failed to look up DNS record for ${h.hostname}: ${JSON.stringify(listResult.errors)}`);\n allOk = false;\n continue;\n }\n\n const records = Array.isArray(listResult.data) ? listResult.data : [];\n if (records.length === 0) {\n steps.push(`[tunnel] No DNS CNAME record found for ${h.hostname}`);\n continue;\n }\n\n for (const record of records) {\n const delResult = await cfFetch(\n `/zones/${creds.zoneId}/dns_records/${record.id}`,\n creds.apiToken,\n 'DELETE',\n );\n if (!delResult.ok) {\n steps.push(`[tunnel] Failed to delete DNS record ${record.id} for ${h.hostname}: ${JSON.stringify(delResult.errors)}`);\n allOk = false;\n } else {\n steps.push(`[tunnel] Deleted DNS CNAME for ${h.hostname}`);\n }\n }\n }\n\n return { success: allOk, steps };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tunnel-DzXEPwIc.js","names":[],"sources":["../src/lib/tunnel.ts"],"sourcesContent":["/**\n * Cloudflare Tunnel Management\n *\n * Manages tunnel ingress rules and DNS CNAME records for workspace lifecycle.\n * Called during workspace create (addTunnelIngress) and workspace remove/deep-wipe (removeTunnelIngress).\n */\n\nimport { readFileSync } from 'fs';\nimport { resolve } from 'path';\nimport { homedir } from 'os';\nimport { TunnelConfig, TunnelHostname, TemplatePlaceholders, replacePlaceholders } from './workspace-config.js';\n\nexport interface TunnelResult {\n success: boolean;\n steps: string[];\n}\n\ninterface CloudflareCredentials {\n apiToken: string;\n accountId: string;\n zoneId: string;\n}\n\ninterface CloudflareIngressRule {\n service: string;\n hostname?: string;\n originRequest?: Record<string, unknown>;\n}\n\ninterface CloudflareTunnelConfig {\n config: {\n ingress: CloudflareIngressRule[];\n };\n}\n\nconst CF_API = 'https://api.cloudflare.com/client/v4';\nconst FETCH_TIMEOUT = 10_000;\n\n/**\n * Read API token from Cloudflare cert.pem file.\n * The cert.pem contains a PEM-wrapped base64 JSON blob with { zoneID, accountID, apiToken }.\n */\nfunction readCloudflareCredentials(certPath: string): CloudflareCredentials | null {\n try {\n const resolvedPath = certPath.replace(/^~/, homedir());\n const pem = readFileSync(resolve(resolvedPath), 'utf-8');\n // Strip PEM headers/trailers and decode\n const b64 = pem\n .split('\\n')\n .filter(line => !line.startsWith('-----'))\n .join('');\n const json = JSON.parse(Buffer.from(b64, 'base64').toString('utf-8'));\n return {\n apiToken: json.apiToken,\n accountId: json.accountID,\n zoneId: json.zoneID,\n };\n } catch (err) {\n return null;\n }\n}\n\n/**\n * Make an authenticated Cloudflare API request.\n */\nasync function cfFetch(\n path: string,\n apiToken: string,\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',\n body?: unknown,\n): Promise<{ ok: boolean; data: any; errors?: any[] }> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT);\n\n try {\n const resp = await fetch(`${CF_API}${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${apiToken}`,\n 'Content-Type': 'application/json',\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n const json = await resp.json() as any;\n return { ok: json.success !== false, data: json.result, errors: json.errors };\n } catch (err: any) {\n return { ok: false, data: null, errors: [{ message: err.message }] };\n } finally {\n clearTimeout(timeout);\n }\n}\n\n/**\n * Resolve hostnames from config, replacing template placeholders.\n */\nfunction resolveHostnames(\n hostnames: TunnelHostname[],\n placeholders: TemplatePlaceholders,\n): Array<{ hostname: string; httpHostHeader?: string; noTlsVerify: boolean }> {\n return hostnames.map(h => ({\n hostname: replacePlaceholders(h.pattern, placeholders),\n httpHostHeader: h.http_host_header ? replacePlaceholders(h.http_host_header, placeholders) : undefined,\n noTlsVerify: h.no_tls_verify !== false, // default true\n }));\n}\n\n/**\n * Add tunnel ingress rules and DNS CNAME records for a workspace.\n * Called during workspace creation.\n */\nexport async function addTunnelIngress(\n config: TunnelConfig,\n placeholders: TemplatePlaceholders,\n): Promise<TunnelResult> {\n const steps: string[] = [];\n let allOk = true;\n\n // Read credentials\n const creds = readCloudflareCredentials(config.credentials_file);\n if (!creds) {\n return { success: false, steps: ['[tunnel] Failed to read Cloudflare credentials from ' + config.credentials_file] };\n }\n steps.push('[tunnel] Read Cloudflare credentials');\n\n const resolved = resolveHostnames(config.hostnames, placeholders);\n\n // Get current tunnel configuration\n const tunnelPath = `/accounts/${creds.accountId}/cfd_tunnel/${config.tunnel_id}/configurations`;\n const current = await cfFetch(tunnelPath, creds.apiToken);\n if (!current.ok) {\n return { success: false, steps: [...steps, `[tunnel] Failed to get tunnel config: ${JSON.stringify(current.errors)}`] };\n }\n\n const tunnelConfig: CloudflareTunnelConfig = current.data;\n const ingress: CloudflareIngressRule[] = tunnelConfig.config?.ingress || [];\n steps.push(`[tunnel] Current tunnel config has ${ingress.length} ingress rules`);\n\n // Add new ingress rules (before the catch-all)\n let modified = false;\n for (const h of resolved) {\n // Skip if rule already exists\n if (ingress.some(r => r.hostname === h.hostname)) {\n steps.push(`[tunnel] Ingress rule for ${h.hostname} already exists, skipping`);\n continue;\n }\n\n const originRequest: Record<string, unknown> = {};\n if (h.noTlsVerify) originRequest.noTLSVerify = true;\n if (h.httpHostHeader) originRequest.httpHostHeader = h.httpHostHeader;\n\n const newRule: CloudflareIngressRule = {\n service: config.service_target,\n hostname: h.hostname,\n originRequest: Object.keys(originRequest).length > 0 ? originRequest : undefined,\n };\n\n // Insert before the last rule (catch-all has no hostname)\n const catchAllIdx = ingress.findIndex(r => !r.hostname);\n if (catchAllIdx >= 0) {\n ingress.splice(catchAllIdx, 0, newRule);\n } else {\n ingress.push(newRule);\n }\n modified = true;\n steps.push(`[tunnel] Added ingress rule for ${h.hostname}`);\n }\n\n // Push updated tunnel config\n if (modified) {\n const putResult = await cfFetch(tunnelPath, creds.apiToken, 'PUT', {\n config: { ingress },\n });\n if (!putResult.ok) {\n steps.push(`[tunnel] Failed to update tunnel config: ${JSON.stringify(putResult.errors)}`);\n allOk = false;\n } else {\n steps.push('[tunnel] Updated tunnel ingress configuration');\n }\n }\n\n // Create DNS CNAME records\n for (const h of resolved) {\n const dnsResult = await cfFetch(\n `/zones/${creds.zoneId}/dns_records`,\n creds.apiToken,\n 'POST',\n {\n type: 'CNAME',\n name: h.hostname,\n content: `${config.tunnel_id}.cfargotunnel.com`,\n proxied: true,\n },\n );\n if (!dnsResult.ok) {\n const errMsg = dnsResult.errors?.map((e: any) => e.message).join(', ') || 'unknown error';\n // Record already exists is not a failure\n if (errMsg.includes('already exists') || errMsg.includes('already been taken')) {\n steps.push(`[tunnel] DNS CNAME for ${h.hostname} already exists`);\n } else {\n steps.push(`[tunnel] Failed to create DNS CNAME for ${h.hostname}: ${errMsg}`);\n allOk = false;\n }\n } else {\n steps.push(`[tunnel] Created DNS CNAME: ${h.hostname} → ${config.tunnel_id}.cfargotunnel.com`);\n }\n }\n\n return { success: allOk, steps };\n}\n\n/**\n * Remove tunnel ingress rules and DNS CNAME records for a workspace.\n * Called during workspace removal and deep-wipe.\n */\nexport async function removeTunnelIngress(\n config: TunnelConfig,\n placeholders: TemplatePlaceholders,\n): Promise<TunnelResult> {\n const steps: string[] = [];\n let allOk = true;\n\n // Read credentials\n const creds = readCloudflareCredentials(config.credentials_file);\n if (!creds) {\n return { success: false, steps: ['[tunnel] Failed to read Cloudflare credentials from ' + config.credentials_file] };\n }\n steps.push('[tunnel] Read Cloudflare credentials');\n\n const resolved = resolveHostnames(config.hostnames, placeholders);\n const hostnameSet = new Set(resolved.map(h => h.hostname));\n\n // Get current tunnel configuration\n const tunnelPath = `/accounts/${creds.accountId}/cfd_tunnel/${config.tunnel_id}/configurations`;\n const current = await cfFetch(tunnelPath, creds.apiToken);\n if (!current.ok) {\n steps.push(`[tunnel] Failed to get tunnel config: ${JSON.stringify(current.errors)}`);\n // Continue to attempt DNS cleanup even if tunnel config read fails\n allOk = false;\n } else {\n const tunnelConfig: CloudflareTunnelConfig = current.data;\n const ingress: CloudflareIngressRule[] = tunnelConfig.config?.ingress || [];\n const originalCount = ingress.length;\n\n // Filter out matching ingress rules\n const filtered = ingress.filter(r => !r.hostname || !hostnameSet.has(r.hostname));\n\n if (filtered.length < originalCount) {\n const putResult = await cfFetch(tunnelPath, creds.apiToken, 'PUT', {\n config: { ingress: filtered },\n });\n if (!putResult.ok) {\n steps.push(`[tunnel] Failed to update tunnel config: ${JSON.stringify(putResult.errors)}`);\n allOk = false;\n } else {\n steps.push(`[tunnel] Removed ${originalCount - filtered.length} ingress rule(s)`);\n }\n } else {\n steps.push('[tunnel] No matching ingress rules found to remove');\n }\n }\n\n // Remove DNS CNAME records\n for (const h of resolved) {\n // Find the DNS record\n const listResult = await cfFetch(\n `/zones/${creds.zoneId}/dns_records?name=${encodeURIComponent(h.hostname)}&type=CNAME`,\n creds.apiToken,\n );\n if (!listResult.ok) {\n steps.push(`[tunnel] Failed to look up DNS record for ${h.hostname}: ${JSON.stringify(listResult.errors)}`);\n allOk = false;\n continue;\n }\n\n const records = Array.isArray(listResult.data) ? listResult.data : [];\n if (records.length === 0) {\n steps.push(`[tunnel] No DNS CNAME record found for ${h.hostname}`);\n continue;\n }\n\n for (const record of records) {\n const delResult = await cfFetch(\n `/zones/${creds.zoneId}/dns_records/${record.id}`,\n creds.apiToken,\n 'DELETE',\n );\n if (!delResult.ok) {\n steps.push(`[tunnel] Failed to delete DNS record ${record.id} for ${h.hostname}: ${JSON.stringify(delResult.errors)}`);\n allOk = false;\n } else {\n steps.push(`[tunnel] Deleted DNS CNAME for ${h.hostname}`);\n }\n }\n }\n\n return { success: allOk, steps };\n}\n"],"mappings":"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"}
@@ -52,4 +52,4 @@ function getLinearApiKey() {
52
52
  //#endregion
53
53
  export { stepSkipped as i, stepFailed as n, stepOk as r, getLinearApiKey as t };
54
54
 
55
- //# sourceMappingURL=types-DewGdaIP.js.map
55
+ //# sourceMappingURL=types-BhJj1SP1.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-DewGdaIP.js","names":[],"sources":["../src/lib/lifecycle/types.ts"],"sourcesContent":["/**\n * Shared types for lifecycle operations.\n *\n * Every atomic operation returns a StepResult. Workflows compose\n * multiple operations and return a WorkflowResult.\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\nexport interface StepResult {\n step: string;\n success: boolean;\n skipped: boolean; // true if operation was a no-op (idempotent)\n error?: string;\n details?: string[]; // human-readable log of what was done\n}\n\nexport interface WorkflowResult {\n workflow: 'approve' | 'close' | 'close-out' | 'deep-wipe';\n issueId: string;\n success: boolean; // true only if ALL non-skipped steps succeeded\n steps: StepResult[];\n duration: number; // ms\n}\n\n/** Context shared across lifecycle operations */\nexport interface LifecycleContext {\n issueId: string;\n projectPath: string;\n /** Project name (for Docker compose project naming + placeholders) */\n projectName?: string;\n /** GitHub issue metadata (populated for PAN- issues) */\n github?: {\n owner: string;\n repo: string;\n number: number;\n };\n /** Rally configuration (populated for Rally-tracked issues) */\n rally?: {\n apiKey: string;\n server?: string;\n workspace?: string;\n project?: string;\n };\n}\n\n/** Options for teardown-workspace */\nexport interface TeardownOptions {\n /** Delete feature branches (local + remote). Default: false */\n deleteBranches?: boolean;\n /** Skip Docker container cleanup. Default: false */\n skipDocker?: boolean;\n /** Delete workspace directory (worktree + files). Default: true */\n deleteWorkspace?: boolean;\n /** Clear beads for this issue from project root. Default: false.\n * Only set to true for destructive wipe — normal completion should preserve beads. */\n clearBeads?: boolean;\n /** Project-specific workspace config for tunnel/Hume cleanup */\n workspaceConfig?: {\n tunnel?: any;\n hume?: any;\n dns?: { domain?: string };\n };\n /** Project name (for Docker compose project naming + placeholders) */\n projectName?: string;\n}\n\n/** Options for archive-planning */\nexport interface ArchiveOptions {\n /** Push git commits to remote after archiving. Default: true */\n pushToRemote?: boolean;\n}\n\n/** Options for the approve workflow */\nexport interface ApproveOptions {\n /** Skip the merge step (e.g. if already merged). Default: false */\n skipMerge?: boolean;\n /** Skip beads compaction. Default: false */\n skipBeadsCompaction?: boolean;\n}\n\n/** Options for the deep-wipe workflow */\n/** Progress event emitted during deep-wipe. */\nexport interface DeepWipeProgress {\n step: number;\n total: number;\n label: string;\n detail: string;\n status: 'active' | 'complete' | 'error';\n}\n\nexport interface DeepWipeOptions {\n /** Delete workspace directory. Default: true */\n deleteWorkspace?: boolean;\n /** Delete git branches (local + remote). Default: true */\n deleteBranches?: boolean;\n /** Reset issue to backlog/open state. Default: true */\n resetIssue?: boolean;\n /** Project-specific workspace config for tunnel/Hume cleanup */\n workspaceConfig?: {\n tunnel?: any;\n hume?: any;\n dns?: { domain?: string };\n };\n /** Project name (for Docker compose project naming + placeholders) */\n projectName?: string;\n /** Optional callback for streaming progress events to the client. */\n onProgress?: (event: DeepWipeProgress) => void;\n}\n\n/** Helper to create a successful step result */\nexport function stepOk(step: string, details?: string[]): StepResult {\n return { step, success: true, skipped: false, details };\n}\n\n/** Helper to create a skipped step result */\nexport function stepSkipped(step: string, details?: string[]): StepResult {\n return { step, success: true, skipped: true, details };\n}\n\n/** Helper to create a failed step result */\nexport function stepFailed(step: string, error: string, details?: string[]): StepResult {\n return { step, success: false, skipped: false, error, details };\n}\n\n/**\n * Get LINEAR_API_KEY from environment or .panopticon.env.\n * Shared across lifecycle modules.\n */\nexport function getLinearApiKey(): string | null {\n if (process.env.LINEAR_API_KEY) return process.env.LINEAR_API_KEY;\n const envFile = join(homedir(), '.panopticon.env');\n if (existsSync(envFile)) {\n const content = readFileSync(envFile, 'utf-8');\n const match = content.match(/LINEAR_API_KEY=(.+)/);\n if (match) return match[1].trim();\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;AAiHA,SAAgB,OAAO,MAAc,SAAgC;AACnE,QAAO;EAAE;EAAM,SAAS;EAAM,SAAS;EAAO;EAAS;;;AAIzD,SAAgB,YAAY,MAAc,SAAgC;AACxE,QAAO;EAAE;EAAM,SAAS;EAAM,SAAS;EAAM;EAAS;;;AAIxD,SAAgB,WAAW,MAAc,OAAe,SAAgC;AACtF,QAAO;EAAE;EAAM,SAAS;EAAO,SAAS;EAAO;EAAO;EAAS;;;;;;AAOjE,SAAgB,kBAAiC;AAC/C,KAAI,QAAQ,IAAI,eAAgB,QAAO,QAAQ,IAAI;CACnD,MAAM,UAAU,KAAK,SAAS,EAAE,kBAAkB;AAClD,KAAI,WAAW,QAAQ,EAAE;EAEvB,MAAM,QADU,aAAa,SAAS,QAAQ,CACxB,MAAM,sBAAsB;AAClD,MAAI,MAAO,QAAO,MAAM,GAAG,MAAM;;AAEnC,QAAO"}
1
+ {"version":3,"file":"types-BhJj1SP1.js","names":[],"sources":["../src/lib/lifecycle/types.ts"],"sourcesContent":["/**\n * Shared types for lifecycle operations.\n *\n * Every atomic operation returns a StepResult. Workflows compose\n * multiple operations and return a WorkflowResult.\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\nexport interface StepResult {\n step: string;\n success: boolean;\n skipped: boolean; // true if operation was a no-op (idempotent)\n error?: string;\n details?: string[]; // human-readable log of what was done\n}\n\nexport interface WorkflowResult {\n workflow: 'approve' | 'close' | 'close-out' | 'deep-wipe';\n issueId: string;\n success: boolean; // true only if ALL non-skipped steps succeeded\n steps: StepResult[];\n duration: number; // ms\n}\n\n/** Context shared across lifecycle operations */\nexport interface LifecycleContext {\n issueId: string;\n projectPath: string;\n /** Project name (for Docker compose project naming + placeholders) */\n projectName?: string;\n /** GitHub issue metadata (populated for PAN- issues) */\n github?: {\n owner: string;\n repo: string;\n number: number;\n };\n /** Rally configuration (populated for Rally-tracked issues) */\n rally?: {\n apiKey: string;\n server?: string;\n workspace?: string;\n project?: string;\n };\n}\n\n/** Options for teardown-workspace */\nexport interface TeardownOptions {\n /** Delete feature branches (local + remote). Default: false */\n deleteBranches?: boolean;\n /** Skip Docker container cleanup. Default: false */\n skipDocker?: boolean;\n /** Delete workspace directory (worktree + files). Default: true */\n deleteWorkspace?: boolean;\n /** Clear beads for this issue from project root. Default: false.\n * Only set to true for destructive wipe — normal completion should preserve beads. */\n clearBeads?: boolean;\n /** Project-specific workspace config for tunnel/Hume cleanup */\n workspaceConfig?: {\n tunnel?: any;\n hume?: any;\n dns?: { domain?: string };\n };\n /** Project name (for Docker compose project naming + placeholders) */\n projectName?: string;\n}\n\n/** Options for archive-planning */\nexport interface ArchiveOptions {\n /** Push git commits to remote after archiving. Default: true */\n pushToRemote?: boolean;\n}\n\n/** Options for the approve workflow */\nexport interface ApproveOptions {\n /** Skip the merge step (e.g. if already merged). Default: false */\n skipMerge?: boolean;\n /** Skip beads compaction. Default: false */\n skipBeadsCompaction?: boolean;\n}\n\n/** Options for the deep-wipe workflow */\n/** Progress event emitted during deep-wipe. */\nexport interface DeepWipeProgress {\n step: number;\n total: number;\n label: string;\n detail: string;\n status: 'active' | 'complete' | 'error';\n}\n\nexport interface DeepWipeOptions {\n /** Delete workspace directory. Default: true */\n deleteWorkspace?: boolean;\n /** Delete git branches (local + remote). Default: true */\n deleteBranches?: boolean;\n /** Reset issue to backlog/open state. Default: true */\n resetIssue?: boolean;\n /** Project-specific workspace config for tunnel/Hume cleanup */\n workspaceConfig?: {\n tunnel?: any;\n hume?: any;\n dns?: { domain?: string };\n };\n /** Project name (for Docker compose project naming + placeholders) */\n projectName?: string;\n /** Optional callback for streaming progress events to the client. */\n onProgress?: (event: DeepWipeProgress) => void;\n}\n\n/** Helper to create a successful step result */\nexport function stepOk(step: string, details?: string[]): StepResult {\n return { step, success: true, skipped: false, details };\n}\n\n/** Helper to create a skipped step result */\nexport function stepSkipped(step: string, details?: string[]): StepResult {\n return { step, success: true, skipped: true, details };\n}\n\n/** Helper to create a failed step result */\nexport function stepFailed(step: string, error: string, details?: string[]): StepResult {\n return { step, success: false, skipped: false, error, details };\n}\n\n/**\n * Get LINEAR_API_KEY from environment or .panopticon.env.\n * Shared across lifecycle modules.\n */\nexport function getLinearApiKey(): string | null {\n if (process.env.LINEAR_API_KEY) return process.env.LINEAR_API_KEY;\n const envFile = join(homedir(), '.panopticon.env');\n if (existsSync(envFile)) {\n const content = readFileSync(envFile, 'utf-8');\n const match = content.match(/LINEAR_API_KEY=(.+)/);\n if (match) return match[1].trim();\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;AAiHA,SAAgB,OAAO,MAAc,SAAgC;AACnE,QAAO;EAAE;EAAM,SAAS;EAAM,SAAS;EAAO;EAAS;;;AAIzD,SAAgB,YAAY,MAAc,SAAgC;AACxE,QAAO;EAAE;EAAM,SAAS;EAAM,SAAS;EAAM;EAAS;;;AAIxD,SAAgB,WAAW,MAAc,OAAe,SAAgC;AACtF,QAAO;EAAE;EAAM,SAAS;EAAO,SAAS;EAAO;EAAO;EAAS;;;;;;AAOjE,SAAgB,kBAAiC;AAC/C,KAAI,QAAQ,IAAI,eAAgB,QAAO,QAAQ,IAAI;CACnD,MAAM,UAAU,KAAK,SAAS,EAAE,kBAAkB;AAClD,KAAI,WAAW,QAAQ,EAAE;EAEvB,MAAM,QADU,aAAa,SAAS,QAAQ,CACxB,MAAM,sBAAsB;AAClD,MAAI,MAAO,QAAO,MAAM,GAAG,MAAM;;AAEnC,QAAO"}
@@ -1,6 +1,6 @@
1
1
  import { t as __esmMin } from "./chunk-ruWRV7i3.js";
2
- import { W as init_paths, t as AGENTS_DIR } from "./paths-lMaxrYtT.js";
3
- import { a as getModelCapability, i as MODEL_CAPABILITIES, n as init_config_yaml, o as init_model_capabilities, r as loadConfig } from "./config-yaml-BgOACZAB.js";
2
+ import { G as init_paths, t as AGENTS_DIR } from "./paths-CDJ_HsbN.js";
3
+ import { a as getModelCapability, i as MODEL_CAPABILITIES, n as init_config_yaml, o as init_model_capabilities, r as loadConfig } from "./config-yaml-DGbLSMCa.js";
4
4
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
5
5
  import { join } from "path";
6
6
  //#region src/lib/hooks.ts
@@ -926,4 +926,4 @@ var init_work_type_router = __esmMin((() => {
926
926
  //#endregion
927
927
  export { generateFixedPointPrompt as a, popFromHook as c, clearHook as i, pushToHook as l, init_work_type_router as n, initHook as o, checkHook as r, init_hooks as s, getModelId as t, sendMail as u };
928
928
 
929
- //# sourceMappingURL=work-type-router-CS2BB1vS.js.map
929
+ //# sourceMappingURL=work-type-router-CHjciPyS.js.map