@dylanpiercey/work 0.0.72 → 0.0.74

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 (249) hide show
  1. package/dist/{_BfXE8Kjn.js → _BDyG-m5v.js} +1225 -317
  2. package/dist/{_DXDmuFjC.js → _C4T3WOc-.js} +327 -293
  3. package/dist/{_Dq5wZ9y2.js → _JZKScK_6.js} +2 -2
  4. package/dist/_wbad_ZHd.js +2 -0
  5. package/dist/index.mjs +152 -939
  6. package/dist/public/assets/{_BLZtgkbJ2.js → _0_BxD6tA2.js} +1 -1
  7. package/dist/public/assets/_0_BxD6tA2.js.br +0 -0
  8. package/dist/public/assets/{_sUX0JPgf2.js → _B-ZLatX02.js} +1 -1
  9. package/dist/public/assets/_B-ZLatX02.js.br +0 -0
  10. package/dist/public/assets/{_CivjiEX92.js → _B-vJ063K2.js} +1 -1
  11. package/dist/public/assets/_B-vJ063K2.js.br +0 -0
  12. package/dist/public/assets/{_Drt_09zp.js → _B3-kV650.js} +1 -1
  13. package/dist/public/assets/_B3-kV650.js.br +0 -0
  14. package/dist/public/assets/{_-G0Dx3t52.js → _B6cp2Tnt2.js} +1 -1
  15. package/dist/public/assets/_B6cp2Tnt2.js.br +0 -0
  16. package/dist/public/assets/{_CTQouXRe.js → _B7qLfWk7.js} +1 -1
  17. package/dist/public/assets/_B7qLfWk7.js.br +0 -0
  18. package/dist/public/assets/{_DmLXUxIk.js → _B8yL5yfH.js} +1 -1
  19. package/dist/public/assets/_B8yL5yfH.js.br +0 -0
  20. package/dist/public/assets/{_DHelzj06.js → _BAM8hQx8.js} +1 -1
  21. package/dist/public/assets/_BAM8hQx8.js.br +0 -0
  22. package/dist/public/assets/{_ChkNS16f.js → _BCELU8uk.js} +1 -1
  23. package/dist/public/assets/_BCELU8uk.js.br +0 -0
  24. package/dist/public/assets/{_ckRann09.js → _BEsGJwj1.js} +1 -1
  25. package/dist/public/assets/_BEsGJwj1.js.br +0 -0
  26. package/dist/public/assets/{_C5VLAw4k2.js → _BIlkaSXj2.js} +1 -1
  27. package/dist/public/assets/_BIlkaSXj2.js.br +0 -0
  28. package/dist/public/assets/{_B7vYPsWj.js → _BRKdH0Xv.js} +1 -1
  29. package/dist/public/assets/_BRKdH0Xv.js.br +0 -0
  30. package/dist/public/assets/{_CuFX3MHy2.js → _BRrshe202.js} +1 -1
  31. package/dist/public/assets/{_CNClKCWp.js → _BSnpBPhR.js} +1 -1
  32. package/dist/public/assets/_BSnpBPhR.js.br +0 -0
  33. package/dist/public/assets/{_Csmr2y3G.js → _Bf8W6hR7.js} +1 -1
  34. package/dist/public/assets/_Bf8W6hR7.js.br +0 -0
  35. package/dist/public/assets/{_BE8lov_p2.js → _BjS-vjVo2.js} +1 -1
  36. package/dist/public/assets/_BjS-vjVo2.js.br +0 -0
  37. package/dist/public/assets/{_DjVNZ4eK2.js → _BsMtWEvw2.js} +1 -1
  38. package/dist/public/assets/_BsMtWEvw2.js.br +0 -0
  39. package/dist/public/assets/{_BSSf-PYx.js → _Bsi-MUb-.js} +1 -1
  40. package/dist/public/assets/_Bsi-MUb-.js.br +0 -0
  41. package/dist/public/assets/{_BpJS3XpY2.js → _C4OOpxns2.js} +1 -1
  42. package/dist/public/assets/_C4OOpxns2.js.br +0 -0
  43. package/dist/public/assets/{_BqE48K5c.js → _C4bcAkxw.js} +20 -20
  44. package/dist/public/assets/_C4bcAkxw.js.br +0 -0
  45. package/dist/public/assets/{_BkrDGdOx2.js → _CEjOmogC2.js} +1 -1
  46. package/dist/public/assets/_CEjOmogC2.js.br +0 -0
  47. package/dist/public/assets/{_BBWg58GJ2.js → _CHUg0ksH2.js} +1 -1
  48. package/dist/public/assets/_CHUg0ksH2.js.br +0 -0
  49. package/dist/public/assets/{_LdAm0jkh.js → _CJmkmGNg.js} +1 -1
  50. package/dist/public/assets/_CJmkmGNg.js.br +0 -0
  51. package/dist/public/assets/{_CfjckiVM2.js → _CKkkXYf42.js} +1 -1
  52. package/dist/public/assets/_CKkkXYf42.js.br +0 -0
  53. package/dist/public/assets/{_Y-mhaywp2.js → _CL6hsAgf2.js} +1 -1
  54. package/dist/public/assets/_CL6hsAgf2.js.br +0 -0
  55. package/dist/public/assets/{_UxrF5q9P.js → _CNHWN4ne.js} +1 -1
  56. package/dist/public/assets/_CNHWN4ne.js.br +0 -0
  57. package/dist/public/assets/{_vWJVVrmZ.js → _COQdreVN.js} +1 -1
  58. package/dist/public/assets/_COQdreVN.js.br +0 -0
  59. package/dist/public/assets/{_COZ91m9B.js → _CRzcId75.js} +1 -1
  60. package/dist/public/assets/_CRzcId75.js.br +0 -0
  61. package/dist/public/assets/{_snCcB5_5.js → _CSSWCImt.js} +1 -1
  62. package/dist/public/assets/_CSSWCImt.js.br +0 -0
  63. package/dist/public/assets/{_RF9fz9Iu.js → _CTIj6y6q.js} +1 -1
  64. package/dist/public/assets/_CTIj6y6q.js.br +0 -0
  65. package/dist/public/assets/{_D5nVZek7.js → _CVQsXn4K.js} +1 -1
  66. package/dist/public/assets/_CVQsXn4K.js.br +0 -0
  67. package/dist/public/assets/{_DX2Uab-U.js → _CY8DnUYp.js} +1 -1
  68. package/dist/public/assets/_CY8DnUYp.js.br +0 -0
  69. package/dist/public/assets/{_C82GEkZ4.js → _CbnLiunX.js} +1 -1
  70. package/dist/public/assets/_CbnLiunX.js.br +0 -0
  71. package/dist/public/assets/{_BGhBZpWT.js → _Ckz3mCrC.js} +1 -1
  72. package/dist/public/assets/_Ckz3mCrC.js.br +0 -0
  73. package/dist/public/assets/{_DAi07M7i.js → _Cl6zL1rb.js} +1 -1
  74. package/dist/public/assets/_Cl6zL1rb.js.br +0 -0
  75. package/dist/public/assets/{_CH2m75uw.js → _CmATrSbQ.js} +1 -1
  76. package/dist/public/assets/_CmATrSbQ.js.br +0 -0
  77. package/dist/public/assets/{_C41DPP-z2.js → _Cq4B-czG2.js} +1 -1
  78. package/dist/public/assets/_Cq4B-czG2.js.br +0 -0
  79. package/dist/public/assets/{_Dx6kLICJ.js → _CqJy9zqf.js} +1 -1
  80. package/dist/public/assets/_CqJy9zqf.js.br +0 -0
  81. package/dist/public/assets/{_BXzRffYj.js → _Cv5cB1Gc.js} +1 -1
  82. package/dist/public/assets/_Cv5cB1Gc.js.br +0 -0
  83. package/dist/public/assets/{_B61wm226.js → _CvqFUYec.js} +1 -1
  84. package/dist/public/assets/_CvqFUYec.js.br +0 -0
  85. package/dist/public/assets/{_f4WTM4Kz.js → _Cy4Qu_lY.js} +1 -1
  86. package/dist/public/assets/_Cy4Qu_lY.js.br +0 -0
  87. package/dist/public/assets/{_B_OOMZsL.js → _Cy6s-DjW.js} +1 -1
  88. package/dist/public/assets/_Cy6s-DjW.js.br +0 -0
  89. package/dist/public/assets/{_D1XJzdSj.js → _Cymp0KkN.js} +1 -1
  90. package/dist/public/assets/_Cymp0KkN.js.br +0 -0
  91. package/dist/public/assets/{_Bohi4mUp.js → _CzvbtXgZ.js} +1 -1
  92. package/dist/public/assets/_CzvbtXgZ.js.br +0 -0
  93. package/dist/public/assets/{_KFiFSJT8.js → _D-wyWmAu.js} +1 -1
  94. package/dist/public/assets/_D-wyWmAu.js.br +0 -0
  95. package/dist/public/assets/{_BRzgmRS2.js → _DAQlNNDT.js} +1 -1
  96. package/dist/public/assets/_DAQlNNDT.js.br +0 -0
  97. package/dist/public/assets/{_CV60RHGa.js → _DCLWmEuS.js} +1 -1
  98. package/dist/public/assets/_DCLWmEuS.js.br +0 -0
  99. package/dist/public/assets/{_CnIE6_qB2.js → _DHth5jay2.js} +1 -1
  100. package/dist/public/assets/_DHth5jay2.js.br +0 -0
  101. package/dist/public/assets/{_B7vLPjLl.js → _DI_ZoCy4.js} +1 -1
  102. package/dist/public/assets/_DI_ZoCy4.js.br +0 -0
  103. package/dist/public/assets/{_Clv6arvL.js → _DPPdU-Av.js} +1 -1
  104. package/dist/public/assets/_DPPdU-Av.js.br +0 -0
  105. package/dist/public/assets/{_Wxr28jKW.js → _DPfjt9fQ.js} +1 -1
  106. package/dist/public/assets/_DPfjt9fQ.js.br +0 -0
  107. package/dist/public/assets/{_BdVRHt6-.js → _DTZJyM9w.js} +1 -1
  108. package/dist/public/assets/_DTZJyM9w.js.br +0 -0
  109. package/dist/public/assets/{_B-xEEN4l2.js → _DXCkImvC2.js} +1 -1
  110. package/dist/public/assets/{_DFAhtXzm.js → _DdSK0SaL.js} +1 -1
  111. package/dist/public/assets/_DdSK0SaL.js.br +0 -0
  112. package/dist/public/assets/{_B0-ns0BU2.js → _Dim3Qa8e2.js} +1 -1
  113. package/dist/public/assets/_Dim3Qa8e2.js.br +0 -0
  114. package/dist/public/assets/{_DPL2ZCgQ.js → _DncKjJb4.js} +1 -1
  115. package/dist/public/assets/_DncKjJb4.js.br +0 -0
  116. package/dist/public/assets/{_wgItEK0c.js → _DsjVSWqF.js} +1 -1
  117. package/dist/public/assets/_DsjVSWqF.js.br +0 -0
  118. package/dist/public/assets/{_DshD8Ycc.js → _DwvzTp2-.js} +1 -1
  119. package/dist/public/assets/_DwvzTp2-.js.br +0 -0
  120. package/dist/public/assets/{_C-uc9hBA2.js → _DxrYm2uA2.js} +1 -1
  121. package/dist/public/assets/_DxrYm2uA2.js.br +0 -0
  122. package/dist/public/assets/{_B7OMjNs8.js → _DztmqTsm.js} +1 -1
  123. package/dist/public/assets/_DztmqTsm.js.br +0 -0
  124. package/dist/public/assets/{_DpENgSEx.js → _ETZIHvA2.js} +1 -1
  125. package/dist/public/assets/_ETZIHvA2.js.br +0 -0
  126. package/dist/public/assets/{_BKTem58K.js → _FgVKeYCS.js} +1 -1
  127. package/dist/public/assets/_FgVKeYCS.js.br +0 -0
  128. package/dist/public/assets/{_B5x1OTTu.js → _JL1loQ5W.js} +1 -1
  129. package/dist/public/assets/_JL1loQ5W.js.br +0 -0
  130. package/dist/public/assets/{_CWUF1FpI.js → _QP0MFlyi.js} +1 -1
  131. package/dist/public/assets/_QP0MFlyi.js.br +0 -0
  132. package/dist/public/assets/{_DvcpKhXL.js → _RZIVk7xV.js} +1 -1
  133. package/dist/public/assets/_RZIVk7xV.js.br +0 -0
  134. package/dist/public/assets/{_Bj5u_Vnk2.js → _RjnwisyN2.js} +1 -1
  135. package/dist/public/assets/_RjnwisyN2.js.br +0 -0
  136. package/dist/public/assets/{_BOQrg7gU2.js → _U11SdqZG2.js} +1 -1
  137. package/dist/public/assets/_U11SdqZG2.js.br +0 -0
  138. package/dist/public/assets/{_QPczAdMm2.js → _XSVpb5jO2.js} +1 -1
  139. package/dist/public/assets/_XSVpb5jO2.js.br +0 -0
  140. package/dist/public/assets/{_CifL_Zbh.js → _fDUiWXKb.js} +1 -1
  141. package/dist/public/assets/_fDUiWXKb.js.br +0 -0
  142. package/dist/public/assets/{_Dixad7y-2.js → _kVKsguoi2.js} +1 -1
  143. package/dist/public/assets/_kVKsguoi2.js.br +0 -0
  144. package/dist/public/assets/{_CYgKydU62.js → _o_bMNXwQ2.js} +1 -1
  145. package/dist/public/assets/_o_bMNXwQ2.js.br +0 -0
  146. package/dist/public/assets/{_DpCy3kYU.js → _tP5Jh-Ws.js} +45 -45
  147. package/dist/public/assets/_tP5Jh-Ws.js.br +0 -0
  148. package/dist/public/assets/{_c9sBllWt.js → _tpRUSSso.js} +1 -1
  149. package/dist/public/assets/_tpRUSSso.js.br +0 -0
  150. package/dist/public/assets/{_D-SthL18.js → _uEb-xDmI.js} +1 -1
  151. package/dist/public/assets/{_BUs7NMrG.js → _uKsdufG5.js} +1 -1
  152. package/dist/public/assets/_uKsdufG5.js.br +0 -0
  153. package/dist/public/assets/{_BTXq-nPh2.js → _vs0DFS9r2.js} +1 -1
  154. package/dist/public/assets/_vs0DFS9r2.js.br +0 -0
  155. package/dist/public/assets/{app-page-CAw2YLYv.css → app-page-KK8yJIbR.css} +1 -1
  156. package/dist/public/assets/app-page-KK8yJIbR.css.br +0 -0
  157. package/dist/public/assets/diff-view-TbHko8ib.css +1 -0
  158. package/dist/public/assets/diff-view-TbHko8ib.css.br +0 -0
  159. package/dist/public/assets/new-OSO__hUI.js +1 -0
  160. package/dist/public/assets/settings-OSO__hUI.js +1 -0
  161. package/dist/public/assets/w-OSO__hUI.js +1 -0
  162. package/dist/public/assets/w._-BVUaVdRa.js +1 -0
  163. package/dist/public/assets/w._-OSO__hUI.js +1 -0
  164. package/lib/setup/agents.json +66 -0
  165. package/lib/setup/catalog.d.mts +2 -0
  166. package/lib/setup/catalog.mjs +9 -10
  167. package/package.json +3 -3
  168. package/dist/_DLxkSyzt.js +0 -2
  169. package/dist/public/assets/_-G0Dx3t52.js.br +0 -0
  170. package/dist/public/assets/_B0-ns0BU2.js.br +0 -0
  171. package/dist/public/assets/_B5x1OTTu.js.br +0 -0
  172. package/dist/public/assets/_B61wm226.js.br +0 -0
  173. package/dist/public/assets/_B7OMjNs8.js.br +0 -0
  174. package/dist/public/assets/_B7vLPjLl.js.br +0 -0
  175. package/dist/public/assets/_B7vYPsWj.js.br +0 -0
  176. package/dist/public/assets/_BBWg58GJ2.js.br +0 -0
  177. package/dist/public/assets/_BE8lov_p2.js.br +0 -0
  178. package/dist/public/assets/_BGhBZpWT.js.br +0 -0
  179. package/dist/public/assets/_BKTem58K.js.br +0 -0
  180. package/dist/public/assets/_BLZtgkbJ2.js.br +0 -0
  181. package/dist/public/assets/_BOQrg7gU2.js.br +0 -0
  182. package/dist/public/assets/_BRzgmRS2.js.br +0 -0
  183. package/dist/public/assets/_BSSf-PYx.js.br +0 -0
  184. package/dist/public/assets/_BTXq-nPh2.js.br +0 -0
  185. package/dist/public/assets/_BUs7NMrG.js.br +0 -0
  186. package/dist/public/assets/_BXzRffYj.js.br +0 -0
  187. package/dist/public/assets/_B_OOMZsL.js.br +0 -0
  188. package/dist/public/assets/_BdVRHt6-.js.br +0 -0
  189. package/dist/public/assets/_Bj5u_Vnk2.js.br +0 -0
  190. package/dist/public/assets/_BkrDGdOx2.js.br +0 -0
  191. package/dist/public/assets/_Bohi4mUp.js.br +0 -0
  192. package/dist/public/assets/_BpJS3XpY2.js.br +0 -0
  193. package/dist/public/assets/_BqE48K5c.js.br +0 -0
  194. package/dist/public/assets/_C-uc9hBA2.js.br +0 -0
  195. package/dist/public/assets/_C41DPP-z2.js.br +0 -0
  196. package/dist/public/assets/_C5VLAw4k2.js.br +0 -0
  197. package/dist/public/assets/_C82GEkZ4.js.br +0 -0
  198. package/dist/public/assets/_CH2m75uw.js.br +0 -0
  199. package/dist/public/assets/_CNClKCWp.js.br +0 -0
  200. package/dist/public/assets/_COZ91m9B.js.br +0 -0
  201. package/dist/public/assets/_CTQouXRe.js.br +0 -0
  202. package/dist/public/assets/_CV60RHGa.js.br +0 -0
  203. package/dist/public/assets/_CWUF1FpI.js.br +0 -0
  204. package/dist/public/assets/_CYgKydU62.js.br +0 -0
  205. package/dist/public/assets/_CfjckiVM2.js.br +0 -0
  206. package/dist/public/assets/_ChkNS16f.js.br +0 -0
  207. package/dist/public/assets/_CifL_Zbh.js.br +0 -0
  208. package/dist/public/assets/_CivjiEX92.js.br +0 -0
  209. package/dist/public/assets/_Clv6arvL.js.br +0 -0
  210. package/dist/public/assets/_CnIE6_qB2.js.br +0 -0
  211. package/dist/public/assets/_Csmr2y3G.js.br +0 -0
  212. package/dist/public/assets/_D1XJzdSj.js.br +0 -0
  213. package/dist/public/assets/_D5nVZek7.js.br +0 -0
  214. package/dist/public/assets/_DAi07M7i.js.br +0 -10
  215. package/dist/public/assets/_DFAhtXzm.js.br +0 -0
  216. package/dist/public/assets/_DHelzj06.js.br +0 -0
  217. package/dist/public/assets/_DPL2ZCgQ.js.br +0 -5
  218. package/dist/public/assets/_DX2Uab-U.js.br +0 -0
  219. package/dist/public/assets/_Dixad7y-2.js.br +0 -0
  220. package/dist/public/assets/_DjVNZ4eK2.js.br +0 -0
  221. package/dist/public/assets/_DmLXUxIk.js.br +0 -0
  222. package/dist/public/assets/_DpCy3kYU.js.br +0 -0
  223. package/dist/public/assets/_DpENgSEx.js.br +0 -0
  224. package/dist/public/assets/_Drt_09zp.js.br +0 -0
  225. package/dist/public/assets/_DshD8Ycc.js.br +0 -0
  226. package/dist/public/assets/_DvcpKhXL.js.br +0 -0
  227. package/dist/public/assets/_Dx6kLICJ.js.br +0 -0
  228. package/dist/public/assets/_KFiFSJT8.js.br +0 -0
  229. package/dist/public/assets/_LdAm0jkh.js.br +0 -0
  230. package/dist/public/assets/_QPczAdMm2.js.br +0 -0
  231. package/dist/public/assets/_RF9fz9Iu.js.br +0 -0
  232. package/dist/public/assets/_UxrF5q9P.js.br +0 -0
  233. package/dist/public/assets/_Wxr28jKW.js.br +0 -0
  234. package/dist/public/assets/_Y-mhaywp2.js.br +0 -0
  235. package/dist/public/assets/_c9sBllWt.js.br +0 -0
  236. package/dist/public/assets/_ckRann09.js.br +0 -0
  237. package/dist/public/assets/_f4WTM4Kz.js.br +0 -0
  238. package/dist/public/assets/_sUX0JPgf2.js.br +0 -0
  239. package/dist/public/assets/_snCcB5_5.js.br +0 -0
  240. package/dist/public/assets/_vWJVVrmZ.js.br +0 -0
  241. package/dist/public/assets/_wgItEK0c.js.br +0 -0
  242. package/dist/public/assets/app-page-CAw2YLYv.css.br +0 -0
  243. package/dist/public/assets/diff-view-DCB49VYM.css +0 -1
  244. package/dist/public/assets/diff-view-DCB49VYM.css.br +0 -0
  245. package/dist/public/assets/new-C9qRFMyu.js +0 -1
  246. package/dist/public/assets/settings-C9qRFMyu.js +0 -1
  247. package/dist/public/assets/w-C9qRFMyu.js +0 -1
  248. package/dist/public/assets/w._-C9qRFMyu.js +0 -1
  249. package/dist/public/assets/w._-phpTDxP7.js +0 -1
@@ -149,6 +149,64 @@ function ensureColumn(table, column, decl) {
149
149
  ensureColumn("review_comments", "head", "TEXT");
150
150
  ensureColumn("review_comments", "snippet", "TEXT");
151
151
  //#endregion
152
+ //#region lib/setup/agents.json
153
+ var agents_default = [
154
+ {
155
+ "id": "claude",
156
+ "label": "Claude",
157
+ "bins": ["claude"],
158
+ "install": {
159
+ "url": "https://claude.ai/install.sh",
160
+ "shell": "bash"
161
+ },
162
+ "icon": "M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z"
163
+ },
164
+ {
165
+ "id": "codex",
166
+ "label": "Codex",
167
+ "bins": ["codex"],
168
+ "install": {
169
+ "url": "https://chatgpt.com/codex/install.sh",
170
+ "shell": "sh"
171
+ },
172
+ "icon": "M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z",
173
+ "reauth": "codex login"
174
+ },
175
+ {
176
+ "id": "copilot",
177
+ "label": "Copilot",
178
+ "bins": ["copilot"],
179
+ "install": {
180
+ "url": "https://gh.io/copilot-install",
181
+ "shell": "bash"
182
+ },
183
+ "icon": "M19.245 5.364c1.322 1.36 1.877 3.216 2.11 5.817.622 0 1.2.135 1.592.654l.73.964c.21.278.323.61.323.955v2.62c0 .339-.173.669-.453.868C20.239 19.602 16.157 21.5 12 21.5c-4.6 0-9.205-2.583-11.547-4.258-.28-.2-.452-.53-.453-.868v-2.62c0-.345.113-.679.321-.956l.73-.963c.392-.517.974-.654 1.593-.654l.029-.297c.25-2.446.81-4.213 2.082-5.52 2.461-2.54 5.71-2.851 7.146-2.864h.198c1.436.013 4.685.323 7.146 2.864zm-7.244 4.328c-.284 0-.613.016-.962.05-.123.447-.305.85-.57 1.108-1.05 1.023-2.316 1.18-2.994 1.18-.638 0-1.306-.13-1.851-.464-.516.165-1.012.403-1.044.996a65.882 65.882 0 00-.063 2.884l-.002.48c-.002.563-.005 1.126-.013 1.69.002.326.204.63.51.765 2.482 1.102 4.83 1.657 6.99 1.657 2.156 0 4.504-.555 6.985-1.657a.854.854 0 00.51-.766c.03-1.682.006-3.372-.076-5.053-.031-.596-.528-.83-1.046-.996-.546.333-1.212.464-1.85.464-.677 0-1.942-.157-2.993-1.18-.266-.258-.447-.661-.57-1.108-.32-.032-.64-.049-.96-.05zm-2.525 4.013c.539 0 .976.426.976.95v1.753c0 .525-.437.95-.976.95a.964.964 0 01-.976-.95v-1.752c0-.525.437-.951.976-.951zm5 0c.539 0 .976.426.976.95v1.753c0 .525-.437.95-.976.95a.964.964 0 01-.976-.95v-1.752c0-.525.437-.951.976-.951zM7.635 5.087c-1.05.102-1.935.438-2.385.906-.975 1.037-.765 3.668-.21 4.224.405.394 1.17.657 1.995.657h.09c.649-.013 1.785-.176 2.73-1.11.435-.41.705-1.433.675-2.47-.03-.834-.27-1.52-.63-1.813-.39-.336-1.275-.482-2.265-.394zm6.465.394c-.36.292-.6.98-.63 1.813-.03 1.037.24 2.06.675 2.47.968.957 2.136 1.104 2.776 1.11h.044c.825 0 1.59-.263 1.995-.657.555-.556.765-3.187-.21-4.224-.45-.468-1.335-.804-2.385-.906-.99-.088-1.875.058-2.265.394zM12 7.615c-.24 0-.525.015-.84.044.03.16.045.336.06.526l-.001.159a2.94 2.94 0 01-.014.25c.225-.022.425-.027.612-.028h.366c.187 0 .387.006.612.028-.015-.146-.015-.277-.015-.409.015-.19.03-.365.06-.526a9.29 9.29 0 00-.84-.044z",
184
+ "reauth": "gh auth login"
185
+ },
186
+ {
187
+ "id": "cursor",
188
+ "label": "Cursor",
189
+ "bins": ["cursor-agent"],
190
+ "install": {
191
+ "url": "https://cursor.com/install",
192
+ "shell": "bash"
193
+ },
194
+ "icon": "M22.106 5.68L12.5.135a.998.998 0 00-.998 0L1.893 5.68a.84.84 0 00-.419.726v11.186c0 .3.16.577.42.727l9.607 5.547a.999.999 0 00.998 0l9.608-5.547a.84.84 0 00.42-.727V6.407a.84.84 0 00-.42-.726zm-.603 1.176L12.228 22.92c-.063.108-.228.064-.228-.061V12.34a.59.59 0 00-.295-.51l-9.11-5.26c-.107-.062-.063-.228.062-.228h18.55c.264 0 .428.286.296.514z",
195
+ "reauth": "cursor-agent login"
196
+ },
197
+ {
198
+ "id": "grok",
199
+ "label": "Grok",
200
+ "bins": ["grok"],
201
+ "install": {
202
+ "url": "https://x.ai/cli/install.sh",
203
+ "shell": "bash"
204
+ },
205
+ "icon": "M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815",
206
+ "reauth": "grok"
207
+ }
208
+ ];
209
+ //#endregion
152
210
  //#region lib/setup/catalog.mjs
153
211
  /** Machine tools. `required` ones gate readiness; the rest are the utilities
154
212
  * agents reach for constantly (mirrors DylanPiercey/start). Static binaries via
@@ -238,62 +296,19 @@ var TOOLS = [
238
296
  mise: "pnpm"
239
297
  }
240
298
  ];
241
- /** Coding agent CLIs. Installed via each vendor's own installer (not npm) so
242
- * self-update works. `bins` lists the executable names a family answers to;
243
- * numeric suffixes (`claude1`) are discovered on top of these. */
244
- var AGENTS = [
245
- {
246
- id: "claude",
247
- label: "Claude",
248
- bins: ["claude"],
249
- install: {
250
- url: "https://claude.ai/install.sh",
251
- shell: "bash"
252
- }
253
- },
254
- {
255
- id: "codex",
256
- label: "Codex",
257
- bins: ["codex"],
258
- install: {
259
- url: "https://chatgpt.com/codex/install.sh",
260
- shell: "sh"
261
- }
262
- },
263
- {
264
- id: "copilot",
265
- label: "Copilot",
266
- bins: ["copilot"],
267
- install: {
268
- url: "https://gh.io/copilot-install",
269
- shell: "bash"
270
- }
271
- },
272
- {
273
- id: "cursor",
274
- label: "Cursor",
275
- bins: ["cursor-agent"],
276
- install: {
277
- url: "https://cursor.com/install",
278
- shell: "bash"
279
- }
280
- },
281
- {
282
- id: "grok",
283
- label: "Grok",
284
- bins: ["grok"],
285
- install: {
286
- url: "https://x.ai/cli/install.sh",
287
- shell: "bash"
288
- }
289
- }
290
- ];
299
+ /** Coding agent CLIs one folder each under src/agents/, whose meta.json is
300
+ * the family's whole description (id, label, bins, vendor installer, icon).
301
+ * agents.json beside this file is those metas collected by
302
+ * scripts/agents-catalog.mjs a build artifact (`pnpm build`/`check`
303
+ * write it; it is not committed), because src/ is not published and this
304
+ * file runs under plain node in the CLI. */
305
+ var AGENTS$1 = agents_default;
291
306
  var DEFAULT_GITHUB_HOST = "github.com";
292
307
  function toolById(id) {
293
308
  return TOOLS.find((t) => t.id === id) || null;
294
309
  }
295
310
  function agentById(id) {
296
- return AGENTS.find((a) => a.id === id) || null;
311
+ return AGENTS$1.find((a) => a.id === id) || null;
297
312
  }
298
313
  /**
299
314
  * Agent *instances* are the family id plus an optional numeric suffix —
@@ -315,7 +330,7 @@ function agentCommand(id) {
315
330
  return parsed ? `${parsed.family.bins[0]}${parsed.suffix}` : null;
316
331
  }
317
332
  /** "Claude" / "Claude 2". */
318
- function agentLabel(id) {
333
+ function agentLabel$1(id) {
319
334
  const parsed = parseAgentId(id);
320
335
  return parsed ? `${parsed.family.label}${parsed.suffix ? ` ${parsed.suffix}` : ""}` : id;
321
336
  }
@@ -414,7 +429,7 @@ function discoverAgents() {
414
429
  continue;
415
430
  }
416
431
  for (const name of names) {
417
- const hit = AGENTS.find((a) => a.bins.some((b) => name === b || name.startsWith(b) && /^\d+$/.test(name.slice(b.length))));
432
+ const hit = AGENTS$1.find((a) => a.bins.some((b) => name === b || name.startsWith(b) && /^\d+$/.test(name.slice(b.length))));
418
433
  if (!hit || seen.has(name)) continue;
419
434
  const full = path.join(dir, name);
420
435
  try {
@@ -431,7 +446,7 @@ function discoverAgents() {
431
446
  });
432
447
  }
433
448
  }
434
- return AGENTS.map((a) => {
449
+ return AGENTS$1.map((a) => {
435
450
  const instances = [...seen.values()].filter((i) => i.family === a).map(({ id, command, path: p }) => ({
436
451
  id,
437
452
  command,
@@ -557,7 +572,7 @@ function agentChoices() {
557
572
  const out = [];
558
573
  for (const family of discoverAgents()) for (const inst of family.instances) out.push({
559
574
  id: inst.id,
560
- label: agentLabel(inst.id),
575
+ label: agentLabel$1(inst.id),
561
576
  family: family.id,
562
577
  command: inst.command,
563
578
  enabled: !off.has(inst.id)
@@ -6324,110 +6339,6 @@ var scraped = /* @__PURE__ */ new Map();
6324
6339
  var reasons = /* @__PURE__ */ new Map();
6325
6340
  var summaries = /* @__PURE__ */ new Map();
6326
6341
  var viewers = /* @__PURE__ */ new Map();
6327
- /** Human-readable one-liner mined from an agent CLI's hook payload.
6328
- * Claude's Stop hook carries the agent's actual final message
6329
- * (last_assistant_message), as does Codex's Stop hook; the legacy notify payload spells it
6330
- * last-assistant-message; Claude Notifications carry a type. Anything
6331
- * else falls back to terminal scraping. */
6332
- function summaryFromHookPayload(payload) {
6333
- if (!payload || typeof payload !== "object") return void 0;
6334
- const p = payload;
6335
- const text = typeof p.last_assistant_message === "string" && p.last_assistant_message || typeof p["last-assistant-message"] === "string" && p["last-assistant-message"] || typeof p.lastAssistantMessage === "string" && p.lastAssistantMessage || typeof p.agent_message === "string" && p.agent_message || "";
6336
- if (text) return cleanSummary(text);
6337
- const kind = notificationType(p);
6338
- if (kind) {
6339
- const message = typeof p.message === "string" ? p.message.trim() : "";
6340
- return message ? cleanSummary(message) : NOTIFICATION_LABELS[kind];
6341
- }
6342
- if (typeof p.errorDetails === "string" && p.errorDetails.trim()) return cleanSummary(p.errorDetails);
6343
- if (typeof p.error === "string" && p.error.trim()) return cleanSummary(p.error);
6344
- if (p.hook_event_name === "PermissionRequest") {
6345
- const input = p.tool_input;
6346
- const what = (input && typeof input.command === "string" ? input.command : "") || (typeof p.tool_name === "string" ? p.tool_name : "");
6347
- return what ? cleanSummary(`Approve: ${what}`) : "Waiting for permission";
6348
- }
6349
- }
6350
- var NOTIFICATION_LABELS = {
6351
- permission_prompt: "Waiting for permission",
6352
- idle_prompt: "Waiting for your reply",
6353
- agent_needs_input: "Waiting for input",
6354
- elicitation_dialog: "Asking a question",
6355
- elicitation_url_dialog: "Needs a browser sign-in"
6356
- };
6357
- /** Notification hooks are one channel for several kinds of event. Only some
6358
- * mean the agent is blocked on the user; Copilot also routes shell and
6359
- * inbox chatter through it. Those the state endpoint must drop, not paint
6360
- * red — the notification is neither "needs input" nor "done". */
6361
- var CHATTER_NOTIFICATIONS = /* @__PURE__ */ new Set([
6362
- "shell_completed",
6363
- "shell_detached_completed",
6364
- "new_inbox_message",
6365
- "instruction_discovered",
6366
- "factory_completed",
6367
- "unclassified"
6368
- ]);
6369
- function ignoreHookPayload(payload) {
6370
- if (!payload || typeof payload !== "object") return false;
6371
- const p = payload;
6372
- const kind = notificationType(p);
6373
- if (kind && CHATTER_NOTIFICATIONS.has(kind)) return true;
6374
- if (typeof p.subagentType === "string" && p.subagentType) return true;
6375
- if (p.hookEventName === "stop" && typeof p.reason === "string" && p.reason !== "end_turn") return true;
6376
- return false;
6377
- }
6378
- /** `notification_type` (Claude, Copilot) or `notificationType` (Grok). */
6379
- function notificationType(p) {
6380
- const kind = p.notification_type ?? p.notificationType;
6381
- return typeof kind === "string" ? kind : void 0;
6382
- }
6383
- /** Copilot's Stop payload names its transcript but not the last message;
6384
- * the last non-empty `assistant.message` in that JSONL is it. Reads the
6385
- * tail only — transcripts grow for the life of a session. */
6386
- function summaryFromTranscript(payload) {
6387
- if (!payload || typeof payload !== "object") return void 0;
6388
- const file = payload.transcript_path;
6389
- if (typeof file !== "string" || !file.endsWith("events.jsonl")) return void 0;
6390
- try {
6391
- const fd = fs.openSync(file, "r");
6392
- try {
6393
- const size = fs.fstatSync(fd).size;
6394
- const len = Math.min(size, 65536);
6395
- const buf = Buffer.alloc(len);
6396
- fs.readSync(fd, buf, 0, len, size - len);
6397
- const lines = buf.toString("utf8").split("\n");
6398
- for (let i = lines.length - 1; i >= 0; i--) {
6399
- const line = lines[i];
6400
- if (!line.includes("\"assistant.message\"")) continue;
6401
- try {
6402
- const ev = JSON.parse(line);
6403
- const content = ev.type === "assistant.message" ? ev.data?.content : void 0;
6404
- if (typeof content === "string" && content.trim()) return cleanSummary(content);
6405
- } catch {}
6406
- }
6407
- } finally {
6408
- fs.closeSync(fd);
6409
- }
6410
- } catch {}
6411
- }
6412
- /** Notifications that mean the agent is nudging about an idle session
6413
- * rather than blocking on one. The hook cannot tell them apart when it
6414
- * fires — it reports every notification the same way — so the kind has to
6415
- * come out of the payload, or a session that merely sat there reads as
6416
- * stuck and paints the nav red. */
6417
- var NUDGE_NOTIFICATIONS = /* @__PURE__ */ new Set(["idle_prompt"]);
6418
- /** The reason a hook's `waiting` actually carries, given its payload. */
6419
- function reasonFromHookPayload(payload, reported) {
6420
- if (reported !== "attention" || !payload || typeof payload !== "object") return reported;
6421
- const kind = notificationType(payload);
6422
- return kind && NUDGE_NOTIFICATIONS.has(kind) ? "done" : reported;
6423
- }
6424
- /** First meaningful line(s) of an agent message, markdown chrome stripped,
6425
- * sized for a push notification. */
6426
- function cleanSummary(text) {
6427
- const flat = text.replace(/```[\s\S]*?```/g, " ").split("\n").map((line) => line.replace(/^#{1,6}\s+/, "").replace(/^\s*[-*>]\s+/, "").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/`([^`]+)`/g, "$1").trim()).filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
6428
- if (!flat) return void 0;
6429
- return flat.length > 180 ? `${flat.slice(0, 177)}…` : flat;
6430
- }
6431
6342
  function setAgentPhase(sessionId, phase, reason, summary) {
6432
6343
  if (phase === "waiting" && reason === "done") {
6433
6344
  if (summary) summaries.set(sessionId, summary);
@@ -6509,110 +6420,803 @@ function publishChange(sessionId) {
6509
6420
  const row = get("SELECT workspace_id FROM sessions WHERE id = ?", sessionId);
6510
6421
  if (row?.workspace_id) {
6511
6422
  const workspaceId = row.workspace_id;
6512
- import("./_DLxkSyzt.js").then((m) => m.notifySessionsChanged(workspaceId));
6423
+ import("./_wbad_ZHd.js").then((m) => m.notifySessionsChanged(workspaceId));
6513
6424
  return;
6514
6425
  }
6515
6426
  publish("sessions.changed");
6516
6427
  }
6517
6428
  //#endregion
6518
- //#region src/server/agent-flags.ts
6519
- /** Agent-state reporting via launch-time flags nothing is written to the
6520
- * workspace or the user's global config. Claude takes an inline settings
6521
- * JSON (--settings accepts a JSON string); Codex takes lifecycle hooks via
6522
- * `-c` overrides, trusted through a `hooks.state` entry in the same
6523
- * override layer (see codexHookFlags); Copilot mounts a plugin directory
6524
- * that lives in work's own data dir (`--plugin-dir`, see copilotPluginDir).
6525
- * Grok has no flag at all: its hook file also lives in work's data dir, and
6526
- * the one thing written outside it is a line naming that file in Grok's
6527
- * hook registry `~/.grok/hooks-paths` (see grokHooksRegistered). Cursor
6528
- * exposes no hook mechanism, so its sessions get working/idle from pane
6529
- * activity (see scraper.ts) and nothing else — a stopped turn and a
6530
- * question look the same. */
6429
+ //#region src/agents/hooks.ts
6430
+ /** Shell command a CLI hook runs to report a state: POSTs the hook's own
6431
+ * stdin JSON to the app, which mines it for a notification summary. `exit 0`
6432
+ * always a state report must never block the agent. Session and port
6433
+ * come from the env tmux gave the pane (see sessions.ts). */
6531
6434
  function stateCurl(state, reason) {
6532
6435
  const qs = `session=$WORK_SESSION_ID&state=${state}` + (reason ? `&reason=${reason}` : "");
6533
6436
  return `[ -n "$WORK_SESSION_ID" ] && curl -sf -m 3 -X POST "http://127.0.0.1:\${WORK_APP_PORT:-${config.appPort}}/api/agent-state?${qs}" -H 'content-type: application/json' --data-binary @- >/dev/null 2>&1; exit 0`;
6534
6437
  }
6535
- /** Whether the agent says when it *starts* working, not just when it stops.
6536
- * Claude and Codex announce it through hooks; believing the screen over that
6537
- * costs them the state their own hook just reported. */
6538
- function reportsWorking(agent) {
6539
- const family = agentFamily(agent);
6540
- return family === "claude" || family === "codex" || family === "copilot" || family === "grok" && grokHooksOk;
6438
+ /** Claude-format hook groups (`{matcher?, hooks:[{type:"command",command}]}`)
6439
+ * for a hook map the JSON shape Claude, Copilot plugins and Grok all read. */
6440
+ function claudeStyleHooks(map, extra = {}) {
6441
+ const hooks = {};
6442
+ for (const [event, state, reason, matcher] of map) hooks[event] = [{
6443
+ ...matcher ? { matcher } : {},
6444
+ hooks: [{
6445
+ type: "command",
6446
+ command: stateCurl(state, reason),
6447
+ ...extra
6448
+ }]
6449
+ }];
6450
+ return hooks;
6541
6451
  }
6542
- /** Whether Grok's hook registry took work's hook file — without it Grok
6543
- * reports nothing and the screen has to speak for it. */
6544
- var grokHooksOk = false;
6545
- /** Grok hook events → state. Payloads are camelCase (hookEventName,
6546
- * notificationType, lastAssistantMessage); agent-state.ts reads both
6547
- * spellings. Notification is matched to the two kinds that mean a prompt is
6548
- * on screen; Stop fires on shutdown too, with a reason the endpoint uses to
6549
- * tell a finished turn from a closed session. */
6550
- var GROK_HOOKS = [
6452
+ var NOTIFICATION_LABELS = {
6453
+ permission_prompt: "Waiting for permission",
6454
+ idle_prompt: "Waiting for your reply",
6455
+ agent_needs_input: "Waiting for input",
6456
+ elicitation_dialog: "Asking a question",
6457
+ elicitation_url_dialog: "Needs a browser sign-in"
6458
+ };
6459
+ /** Notifications that mean the agent is nudging about an idle session
6460
+ * rather than blocking on one: the hook reports every notification the
6461
+ * same way, so the kind has to come out of the payload, or a session that
6462
+ * merely sat there reads as stuck and paints the nav red. */
6463
+ var NUDGE_NOTIFICATIONS = /* @__PURE__ */ new Set(["idle_prompt"]);
6464
+ function str$1(v) {
6465
+ return typeof v === "string" ? v.trim() : "";
6466
+ }
6467
+ /** `notification_type` (Claude, Copilot) or `notificationType` (Grok). */
6468
+ function notificationType(p) {
6469
+ return str$1(p.notification_type) || str$1(p.notificationType);
6470
+ }
6471
+ /** Read a Claude-format payload: last message on Stop, the CLI's own
6472
+ * notification line (or a label for its type) on Notification, and the
6473
+ * idle nudge downgraded to done. Families that speak this dialect start
6474
+ * here and add their own quirks. */
6475
+ function readClaudeStyle(p, reported) {
6476
+ const kind = notificationType(p);
6477
+ const reason = reported === "attention" && NUDGE_NOTIFICATIONS.has(kind) ? "done" : reported;
6478
+ const text = str$1(p.last_assistant_message) || str$1(p["last-assistant-message"]) || str$1(p.lastAssistantMessage);
6479
+ if (text) return {
6480
+ reason,
6481
+ summary: cleanSummary(text)
6482
+ };
6483
+ if (kind) return {
6484
+ reason,
6485
+ summary: cleanSummary(str$1(p.message)) ?? NOTIFICATION_LABELS[kind]
6486
+ };
6487
+ return { reason };
6488
+ }
6489
+ /** First meaningful line(s) of an agent message, markdown chrome stripped,
6490
+ * sized for a push notification. */
6491
+ function cleanSummary(text) {
6492
+ const flat = text.replace(/```[\s\S]*?```/g, " ").split("\n").map((line) => line.replace(/^#{1,6}\s+/, "").replace(/^\s*[-*>]\s+/, "").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/`([^`]+)`/g, "$1").trim()).filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
6493
+ if (!flat) return void 0;
6494
+ return flat.length > 180 ? `${flat.slice(0, 177)}…` : flat;
6495
+ }
6496
+ var meta_default$4 = {
6497
+ id: "claude",
6498
+ label: "Claude",
6499
+ bins: ["claude"],
6500
+ install: {
6501
+ "url": "https://claude.ai/install.sh",
6502
+ "shell": "bash"
6503
+ },
6504
+ icon: "M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z"
6505
+ };
6506
+ var meta_default$3 = {
6507
+ id: "codex",
6508
+ label: "Codex",
6509
+ bins: ["codex"],
6510
+ install: {
6511
+ "url": "https://chatgpt.com/codex/install.sh",
6512
+ "shell": "sh"
6513
+ },
6514
+ icon: "M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z",
6515
+ reauth: "codex login"
6516
+ };
6517
+ var meta_default$2 = {
6518
+ id: "copilot",
6519
+ label: "Copilot",
6520
+ bins: ["copilot"],
6521
+ install: {
6522
+ "url": "https://gh.io/copilot-install",
6523
+ "shell": "bash"
6524
+ },
6525
+ icon: "M19.245 5.364c1.322 1.36 1.877 3.216 2.11 5.817.622 0 1.2.135 1.592.654l.73.964c.21.278.323.61.323.955v2.62c0 .339-.173.669-.453.868C20.239 19.602 16.157 21.5 12 21.5c-4.6 0-9.205-2.583-11.547-4.258-.28-.2-.452-.53-.453-.868v-2.62c0-.345.113-.679.321-.956l.73-.963c.392-.517.974-.654 1.593-.654l.029-.297c.25-2.446.81-4.213 2.082-5.52 2.461-2.54 5.71-2.851 7.146-2.864h.198c1.436.013 4.685.323 7.146 2.864zm-7.244 4.328c-.284 0-.613.016-.962.05-.123.447-.305.85-.57 1.108-1.05 1.023-2.316 1.18-2.994 1.18-.638 0-1.306-.13-1.851-.464-.516.165-1.012.403-1.044.996a65.882 65.882 0 00-.063 2.884l-.002.48c-.002.563-.005 1.126-.013 1.69.002.326.204.63.51.765 2.482 1.102 4.83 1.657 6.99 1.657 2.156 0 4.504-.555 6.985-1.657a.854.854 0 00.51-.766c.03-1.682.006-3.372-.076-5.053-.031-.596-.528-.83-1.046-.996-.546.333-1.212.464-1.85.464-.677 0-1.942-.157-2.993-1.18-.266-.258-.447-.661-.57-1.108-.32-.032-.64-.049-.96-.05zm-2.525 4.013c.539 0 .976.426.976.95v1.753c0 .525-.437.95-.976.95a.964.964 0 01-.976-.95v-1.752c0-.525.437-.951.976-.951zm5 0c.539 0 .976.426.976.95v1.753c0 .525-.437.95-.976.95a.964.964 0 01-.976-.95v-1.752c0-.525.437-.951.976-.951zM7.635 5.087c-1.05.102-1.935.438-2.385.906-.975 1.037-.765 3.668-.21 4.224.405.394 1.17.657 1.995.657h.09c.649-.013 1.785-.176 2.73-1.11.435-.41.705-1.433.675-2.47-.03-.834-.27-1.52-.63-1.813-.39-.336-1.275-.482-2.265-.394zm6.465.394c-.36.292-.6.98-.63 1.813-.03 1.037.24 2.06.675 2.47.968.957 2.136 1.104 2.776 1.11h.044c.825 0 1.59-.263 1.995-.657.555-.556.765-3.187-.21-4.224-.45-.468-1.335-.804-2.385-.906-.99-.088-1.875.058-2.265.394zM12 7.615c-.24 0-.525.015-.84.044.03.16.045.336.06.526l-.001.159a2.94 2.94 0 01-.014.25c.225-.022.425-.027.612-.028h.366c.187 0 .387.006.612.028-.015-.146-.015-.277-.015-.409.015-.19.03-.365.06-.526a9.29 9.29 0 00-.84-.044z",
6526
+ reauth: "gh auth login"
6527
+ };
6528
+ var meta_default$1 = {
6529
+ id: "cursor",
6530
+ label: "Cursor",
6531
+ bins: ["cursor-agent"],
6532
+ install: {
6533
+ "url": "https://cursor.com/install",
6534
+ "shell": "bash"
6535
+ },
6536
+ icon: "M22.106 5.68L12.5.135a.998.998 0 00-.998 0L1.893 5.68a.84.84 0 00-.419.726v11.186c0 .3.16.577.42.727l9.607 5.547a.999.999 0 00.998 0l9.608-5.547a.84.84 0 00.42-.727V6.407a.84.84 0 00-.42-.726zm-.603 1.176L12.228 22.92c-.063.108-.228.064-.228-.061V12.34a.59.59 0 00-.295-.51l-9.11-5.26c-.107-.062-.063-.228.062-.228h18.55c.264 0 .428.286.296.514z",
6537
+ reauth: "cursor-agent login"
6538
+ };
6539
+ var meta_default = {
6540
+ id: "grok",
6541
+ label: "Grok",
6542
+ bins: ["grok"],
6543
+ install: {
6544
+ "url": "https://x.ai/cli/install.sh",
6545
+ "shell": "bash"
6546
+ },
6547
+ icon: "M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815",
6548
+ reauth: "grok"
6549
+ };
6550
+ //#endregion
6551
+ //#region src/agents/meta.ts
6552
+ /** Alphabetical, which is also the order every picker shows. */
6553
+ var AGENT_METAS = [
6554
+ meta_default$4,
6555
+ meta_default$3,
6556
+ meta_default$2,
6557
+ meta_default$1,
6558
+ meta_default
6559
+ ];
6560
+ var byId = new Map(AGENT_METAS.map((m) => [m.id, m]));
6561
+ function agentMeta(family) {
6562
+ return byId.get(family);
6563
+ }
6564
+ /** Agent *instances* are the family id plus an optional numeric suffix —
6565
+ * `claude`, `claude1`, `claude2` — one per binary on PATH (`claude1`,
6566
+ * `cursor-agent2`, …). This is the canonical id everywhere: sessions,
6567
+ * settings, usage rows (`~/.claude-2` reports as `claude2`), icons. */
6568
+ function parseAgent(id) {
6569
+ const m = /^([a-z]+?)(\d*)$/.exec(id || "");
6570
+ const family = m && byId.get(m[1]);
6571
+ return family ? {
6572
+ family,
6573
+ suffix: m[2]
6574
+ } : null;
6575
+ }
6576
+ /** Family id of an instance — `shell` (or unknown) passes through, which is
6577
+ * what icons and pickers want. */
6578
+ function agentFamily(id) {
6579
+ return parseAgent(id)?.family.id ?? id;
6580
+ }
6581
+ /** Numeric suffix to badge on the icon; empty for the primary instance. */
6582
+ function agentBadge(id) {
6583
+ return parseAgent(id)?.suffix ?? "";
6584
+ }
6585
+ /** "Claude 2" / "Shell". */
6586
+ function agentLabel(id) {
6587
+ const p = parseAgent(id);
6588
+ if (!p) return id === "shell" ? "Shell" : id;
6589
+ return `${p.family.label}${p.suffix ? ` ${p.suffix}` : ""}`;
6590
+ }
6591
+ /** Instance id for a running command (`cursor-agent2` → `cursor2`), or null. */
6592
+ function agentFromCommand(command) {
6593
+ for (const meta of AGENT_METAS) for (const bin of meta.bins) {
6594
+ if (command === bin) return meta.id;
6595
+ if (command.startsWith(bin) && /^\d+$/.test(command.slice(bin.length))) return `${meta.id}${command.slice(bin.length)}`;
6596
+ }
6597
+ return null;
6598
+ }
6599
+ //#endregion
6600
+ //#region src/server/usage/shared.ts
6601
+ var HOME = os.homedir();
6602
+ var obj = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
6603
+ var arr = (value) => Array.isArray(value) ? value : [];
6604
+ var str = (value) => typeof value === "string" ? value : "";
6605
+ /** Numeric coercion that refuses NaN/Infinity — a provider sending "n/a"
6606
+ * must read as "no number", not as a percentage that renders a bar. */
6607
+ function num(value) {
6608
+ if (value == null || value === "") return null;
6609
+ const n = typeof value === "number" ? value : Number(value);
6610
+ return Number.isFinite(n) ? n : null;
6611
+ }
6612
+ function readJson(file) {
6613
+ try {
6614
+ return obj(JSON.parse(fs.readFileSync(file, "utf8")));
6615
+ } catch {
6616
+ return null;
6617
+ }
6618
+ }
6619
+ /** True if an epoch (seconds or ms) or ISO-8601 expiry is in the past. */
6620
+ function expired(value) {
6621
+ if (value == null) return false;
6622
+ if (typeof value === "string") {
6623
+ const t = Date.parse(value.replace(/\.\d+/, ""));
6624
+ return Number.isFinite(t) && t < Date.now();
6625
+ }
6626
+ const n = num(value);
6627
+ if (n == null) return false;
6628
+ return (n > 1e11 ? n : n * 1e3) < Date.now();
6629
+ }
6630
+ function isoFromEpoch(value) {
6631
+ const n = num(value);
6632
+ if (n == null || n <= 0) return null;
6633
+ const ms = n > 1e11 ? n : n * 1e3;
6634
+ return new Date(ms).toISOString();
6635
+ }
6636
+ /** `p•••@g•••.com` — enough to tell two accounts apart, and the page is
6637
+ * screenshotted into PRs and chat, so the address itself doesn't belong. */
6638
+ function maskEmail(email) {
6639
+ if (!email || !email.includes("@")) return email || "—";
6640
+ const [local, domain] = email.split("@");
6641
+ if (!local || !domain) return email;
6642
+ const tld = domain.split(".").pop() || "";
6643
+ return `${local[0]}•••@${domain[0]}•••.${tld}`;
6644
+ }
6645
+ /** A fetch that failed, carrying a reason fit to show on the row.
6646
+ *
6647
+ * A stale number with no explanation is what makes a usage table untrustworthy
6648
+ * — "why is claude2 from yesterday?" deserves an answer next to the number. */
6649
+ var FetchFailed = class extends Error {
6650
+ reason;
6651
+ constructor(reason) {
6652
+ super(reason);
6653
+ this.reason = reason;
6654
+ }
6655
+ };
6656
+ function httpReason(status) {
6657
+ if (status === 401 || status === 403) return "auth expired";
6658
+ if (status === 429) return "rate limited or auth expired";
6659
+ if (status >= 500) return `provider error (HTTP ${status})`;
6660
+ return `HTTP ${status}`;
6661
+ }
6662
+ var TIMEOUT_MS = 15e3;
6663
+ /** JSON over HTTPS, with every failure mode collapsed into a FetchFailed
6664
+ * whose reason is worth showing to a human. */
6665
+ async function fetchJson(url, init = {}) {
6666
+ let res;
6667
+ try {
6668
+ res = await fetch(url, {
6669
+ ...init,
6670
+ signal: AbortSignal.timeout(TIMEOUT_MS)
6671
+ });
6672
+ } catch (err) {
6673
+ const name = err?.name;
6674
+ throw new FetchFailed(name === "TimeoutError" || name === "AbortError" ? "timed out" : "network unreachable");
6675
+ }
6676
+ if (!res.ok) throw new FetchFailed(httpReason(res.status));
6677
+ try {
6678
+ return await res.json();
6679
+ } catch {
6680
+ throw new FetchFailed("unreadable response");
6681
+ }
6682
+ }
6683
+ var cacheDir = path.join(config.dataDir, "usage");
6684
+ function readCache(key) {
6685
+ const blob = readJson(path.join(cacheDir, `${key}.json`));
6686
+ if (!blob) return null;
6687
+ const payload = blob.payload;
6688
+ const at = num(blob.fetched_at);
6689
+ if (!payload || typeof payload !== "object" || at == null) return null;
6690
+ return {
6691
+ payload: obj(payload),
6692
+ ageMs: Math.max(0, Date.now() - at)
6693
+ };
6694
+ }
6695
+ function writeCache(key, payload) {
6696
+ try {
6697
+ fs.mkdirSync(cacheDir, { recursive: true });
6698
+ fs.writeFileSync(path.join(cacheDir, `${key}.json`), JSON.stringify({
6699
+ fetched_at: Date.now(),
6700
+ payload
6701
+ }));
6702
+ } catch {}
6703
+ }
6704
+ /** Live fetch, falling back to the last good payload on disk.
6705
+ *
6706
+ * There is no "cache is fresh enough, skip the fetch" branch: the page's own
6707
+ * 60s stale-while-revalidate (server/usage/index.ts) already decides how often
6708
+ * this runs, and a second TTL underneath it only makes the numbers older than
6709
+ * the page claims. */
6710
+ async function loadPayload(key, live) {
6711
+ let reason;
6712
+ try {
6713
+ const data = await live();
6714
+ if (data) {
6715
+ writeCache(key, data);
6716
+ return {
6717
+ payload: data,
6718
+ source: "live"
6719
+ };
6720
+ }
6721
+ } catch (err) {
6722
+ reason = err instanceof FetchFailed ? err.reason : "fetch failed";
6723
+ }
6724
+ const disk = readCache(key);
6725
+ if (disk) return {
6726
+ payload: disk.payload,
6727
+ source: `stale-cache ${Math.round(disk.ageMs / 1e3)}s`,
6728
+ reason
6729
+ };
6730
+ return {
6731
+ payload: null,
6732
+ source: "missing",
6733
+ reason
6734
+ };
6735
+ }
6736
+ /** How to get a fresh token back, per provider (meta.json `reauth`). */
6737
+ var reauthHint = (key) => agentMeta(key)?.reauth;
6738
+ /** Never signed in (or the CLI's credentials went missing) — not an outage.
6739
+ * Distinct from a failed request: nothing was ever sent, so blaming the
6740
+ * network would send you debugging the wrong thing. */
6741
+ /** Row note for a CLI signed in with an API key: metered by the bill, not
6742
+ * by plan windows, so the empty meters are the whole story. */
6743
+ var API_KEY_NOTE = "API key — pay per use, no plan limits";
6744
+ function noAuthReason(key) {
6745
+ const hint = reauthHint(key);
6746
+ return hint ? `not signed in (run \`${hint}\`)` : "no credentials on disk";
6747
+ }
6748
+ function blankRow(agent, source = "missing") {
6749
+ return {
6750
+ agent,
6751
+ plan: agent,
6752
+ account: "—",
6753
+ five_h: null,
6754
+ five_h_resets: null,
6755
+ seven_d: null,
6756
+ seven_d_resets: null,
6757
+ fable: null,
6758
+ fable_resets: null,
6759
+ source
6760
+ };
6761
+ }
6762
+ /** Say *why* a row is showing last-known numbers, on the row itself.
6763
+ *
6764
+ * `tokenExpired` is the local credential check: true/false where an expiry is
6765
+ * readable off disk, undefined where it isn't. It beats guessing from the
6766
+ * status code — a dead token comes back 429 or 403 as readily as 401 — and
6767
+ * knowing the token is *good* is what lets a 429 be reported as plain
6768
+ * throttling instead of sending you to re-authenticate for nothing. */
6769
+ function markStale(row, key, tokenExpired, reason) {
6770
+ if (!/^(stale|claude\.json|missing)/.test(row.source)) return row;
6771
+ let why = reason ?? (tokenExpired ? "auth expired" : void 0);
6772
+ if (!why) why = "couldn't reach provider";
6773
+ if (why.startsWith("rate limited") && tokenExpired === false) why = "rate limited — retry shortly";
6774
+ else if (/^(auth expired|rate limited)/.test(why)) {
6775
+ const hint = reauthHint(key);
6776
+ why += hint ? ` (run \`${hint}\`)` : " (sign in again)";
6777
+ }
6778
+ return {
6779
+ ...row,
6780
+ stale_reason: why
6781
+ };
6782
+ }
6783
+ //#endregion
6784
+ //#region src/agents/claude/usage.ts
6785
+ /** Every Claude account this machine can read.
6786
+ *
6787
+ * Numbered dirs (~/.claude-1, -2, …) keep metadata at <dir>/.claude.json; the
6788
+ * stock single-account install keeps it at ~/.claude.json with only
6789
+ * credentials under ~/.claude/. The numbered ones are discovered rather than
6790
+ * assumed — hardcoding a count is a guess about one machine's setup. */
6791
+ function claudeAccounts() {
6792
+ const candidates = [...numberedDirs().map((n) => ({
6793
+ suffix: n,
6794
+ dir: path.join(HOME, `.claude-${n}`),
6795
+ meta: path.join(HOME, `.claude-${n}`, ".claude.json"),
6796
+ label: `claude${n}`
6797
+ })), {
6798
+ suffix: "",
6799
+ dir: path.join(HOME, ".claude"),
6800
+ meta: path.join(HOME, ".claude.json"),
6801
+ label: "claude"
6802
+ }];
6803
+ const out = [];
6804
+ for (const candidate of candidates) {
6805
+ const meta = readJson(candidate.meta);
6806
+ if (!meta) continue;
6807
+ const oauth = obj(meta.oauthAccount);
6808
+ out.push({
6809
+ ...candidate,
6810
+ email: str(oauth.emailAddress).trim(),
6811
+ plan: str(oauth.organizationRateLimitTier || oauth.organizationType)
6812
+ });
6813
+ }
6814
+ return out;
6815
+ }
6816
+ /** `.claude-<n>` directories in $HOME, in numeric order. */
6817
+ function numberedDirs() {
6818
+ try {
6819
+ return fs.readdirSync(HOME).map((entry) => /^\.claude-(\d+)$/.exec(entry)?.[1]).filter((n) => !!n).sort((a, b) => Number(a) - Number(b));
6820
+ } catch {
6821
+ return [];
6822
+ }
6823
+ }
6824
+ var credentials = (account) => readJson(path.join(account.dir, ".credentials.json"));
6825
+ /** Why an account has no live data — an expired token is the common case
6826
+ * and only that account's own CLI can refresh it. */
6827
+ function credentialNote(account) {
6828
+ const cred = credentials(account);
6829
+ if (!cred) return "no credentials — run: claude";
6830
+ if (expired(obj(cred.claudeAiOauth).expiresAt)) return "token expired — use this Claude account to refresh";
6831
+ return "usage unavailable";
6832
+ }
6833
+ function tokenExpired(account) {
6834
+ const cred = credentials(account);
6835
+ return cred ? expired(obj(cred.claudeAiOauth).expiresAt) : false;
6836
+ }
6837
+ async function fetchUsage$3(account) {
6838
+ const token = str(obj(credentials(account)?.claudeAiOauth).accessToken);
6839
+ if (!token) return null;
6840
+ return obj(await fetchJson("https://api.anthropic.com/api/oauth/usage", { headers: {
6841
+ Authorization: `Bearer ${token}`,
6842
+ "anthropic-beta": "oauth-2025-04-20",
6843
+ "User-Agent": "claude-cli",
6844
+ Accept: "application/json"
6845
+ } }));
6846
+ }
6847
+ /** The CLI's own last-seen utilization, written into .claude.json. Older and
6848
+ * coarser than the API, but it survives an expired token. */
6849
+ function claudeJsonFallback(account) {
6850
+ const cached = readJson(account.meta)?.cachedUsageUtilization;
6851
+ return cached && typeof cached === "object" ? obj(cached) : null;
6852
+ }
6853
+ /** The payload nests windows under `utilization` on some plans and inlines
6854
+ * them on others. */
6855
+ function utilRoot(data) {
6856
+ const util = obj(data.utilization);
6857
+ return "five_hour" in util || "limits" in util ? util : data;
6858
+ }
6859
+ function window$2(data, key) {
6860
+ const win = obj(utilRoot(data)[key]);
6861
+ const used = num(win.utilization);
6862
+ return [used == null ? null : Math.trunc(used), str(win.resets_at) || null];
6863
+ }
6864
+ /** Fable gets its own weekly cap, listed as a model-scoped limit rather than
6865
+ * a top-level window. */
6866
+ function fableWindow(data) {
6867
+ for (const entry of arr(utilRoot(data).limits)) {
6868
+ const limit = obj(entry);
6869
+ if (limit.kind !== "weekly_scoped") continue;
6870
+ const model = obj(obj(limit.scope).model);
6871
+ if (!str(model.display_name || model.id).toLowerCase().includes("fable")) continue;
6872
+ const pct = num(limit.percent);
6873
+ return [pct == null ? null : Math.trunc(pct), str(limit.resets_at) || null];
6874
+ }
6875
+ return [null, null];
6876
+ }
6877
+ function parseRow$4(account, data, source) {
6878
+ const [five, fiveResets] = data ? window$2(data, "five_hour") : [null, null];
6879
+ const [seven, sevenResets] = data ? window$2(data, "seven_day") : [null, null];
6880
+ const [fable, fableResets] = data ? fableWindow(data) : [null, null];
6881
+ const row = {
6882
+ agent: account.label,
6883
+ plan: account.plan || "claude",
6884
+ account: maskEmail(account.email),
6885
+ five_h: five,
6886
+ five_h_resets: fiveResets,
6887
+ seven_d: seven,
6888
+ seven_d_resets: sevenResets,
6889
+ fable,
6890
+ fable_resets: fableResets,
6891
+ source
6892
+ };
6893
+ if (five == null && seven == null && fable == null) row.note = credentialNote(account);
6894
+ return row;
6895
+ }
6896
+ /** No OAuth session but an API key in play (helper, an approved key, or
6897
+ * the env): pay-as-you-go, nothing to meter. */
6898
+ function apiKeyMode$1(account) {
6899
+ if (str(obj(credentials(account)?.claudeAiOauth).accessToken)) return false;
6900
+ const settings = readJson(path.join(account.dir, "settings.json"));
6901
+ const approved = obj(readJson(account.meta)?.customApiKeyResponses).approved;
6902
+ return !!str(settings?.apiKeyHelper) || Array.isArray(approved) && approved.length > 0 || !!process.env.ANTHROPIC_API_KEY;
6903
+ }
6904
+ async function claudeRow(account) {
6905
+ if (apiKeyMode$1(account)) return {
6906
+ ...blankRow(account.label, "api-key"),
6907
+ plan: "API key",
6908
+ note: API_KEY_NOTE
6909
+ };
6910
+ const loaded = await loadPayload(`claude${account.suffix}`, () => fetchUsage$3(account));
6911
+ const fallback = loaded.payload ? null : claudeJsonFallback(account);
6912
+ return markStale(parseRow$4(account, loaded.payload ?? fallback, fallback ? "claude.json" : loaded.source), account.label, tokenExpired(account), loaded.reason);
6913
+ }
6914
+ /** Claude Code takes its settings inline (`--settings` accepts a JSON
6915
+ * string), so the hooks travel in argv and nothing is written anywhere.
6916
+ * Its Notification hook fires for permission prompts and questions —
6917
+ * and for the idle nudge, which readClaudeStyle downgrades. */
6918
+ var claude = {
6919
+ meta: meta_default$4,
6920
+ launchFlags: () => ["--settings", JSON.stringify({ hooks: claudeStyleHooks([
6921
+ ["UserPromptSubmit", "working"],
6922
+ ["PreToolUse", "working"],
6923
+ [
6924
+ "Stop",
6925
+ "waiting",
6926
+ "done"
6927
+ ],
6928
+ [
6929
+ "Notification",
6930
+ "waiting",
6931
+ "attention"
6932
+ ]
6933
+ ]) })],
6934
+ reportsWorking: () => true,
6935
+ envPrefixes: [
6936
+ "CLAUDE",
6937
+ "CLAUDECODE",
6938
+ "AI_AGENT"
6939
+ ],
6940
+ readHook: readClaudeStyle,
6941
+ usage: () => Promise.all(claudeAccounts().map((account) => claudeRow(account)))
6942
+ };
6943
+ //#endregion
6944
+ //#region src/agents/codex/usage.ts
6945
+ var AUTH$2 = path.join(HOME, ".codex", "auth.json");
6946
+ /** Live rate-limit windows from the endpoint the Codex CLI itself uses. */
6947
+ async function fetchUsage$2() {
6948
+ const tokens = obj(readJson(AUTH$2)?.tokens);
6949
+ const token = str(tokens.access_token);
6950
+ if (!token) return null;
6951
+ return obj(await fetchJson("https://chatgpt.com/backend-api/wham/usage", { headers: {
6952
+ Authorization: `Bearer ${token}`,
6953
+ "chatgpt-account-id": str(tokens.account_id),
6954
+ "User-Agent": "codex-cli",
6955
+ Accept: "application/json"
6956
+ } }));
6957
+ }
6958
+ /** (used percent, reset) for whichever window is `seconds` long — the two
6959
+ * windows arrive as primary/secondary rather than named 5h / 7d. */
6960
+ function window$1(rateLimit, seconds) {
6961
+ for (const key of ["primary_window", "secondary_window"]) {
6962
+ const win = obj(rateLimit[key]);
6963
+ if (num(win.limit_window_seconds) !== seconds) continue;
6964
+ const used = num(win.used_percent);
6965
+ return [used == null ? null : Math.trunc(used), isoFromEpoch(win.reset_at)];
6966
+ }
6967
+ return [null, null];
6968
+ }
6969
+ function parseRow$3(payload, source) {
6970
+ const rateLimit = obj(payload.rate_limit);
6971
+ const [five, fiveResets] = window$1(rateLimit, 18e3);
6972
+ const [seven, sevenResets] = window$1(rateLimit, 604800);
6973
+ return {
6974
+ agent: "codex",
6975
+ plan: str(payload.plan_type) || "codex",
6976
+ account: maskEmail(str(payload.email)),
6977
+ five_h: five,
6978
+ five_h_resets: fiveResets,
6979
+ seven_d: seven,
6980
+ seven_d_resets: sevenResets,
6981
+ fable: null,
6982
+ fable_resets: null,
6983
+ source
6984
+ };
6985
+ }
6986
+ /** Signed in with an API key rather than a ChatGPT plan: pay-as-you-go,
6987
+ * no rate-limit windows to draw. */
6988
+ function apiKeyMode() {
6989
+ const auth = readJson(AUTH$2);
6990
+ return !!auth && (str(auth.auth_mode) === "apikey" || !obj(auth.tokens).access_token && !!auth.OPENAI_API_KEY);
6991
+ }
6992
+ async function codexRow() {
6993
+ if (apiKeyMode()) return {
6994
+ ...blankRow("codex", "api-key"),
6995
+ plan: "API key",
6996
+ note: API_KEY_NOTE
6997
+ };
6998
+ const { payload, source, reason } = await loadPayload("codex", fetchUsage$2);
6999
+ const missingAuth = fs.existsSync(AUTH$2) ? void 0 : noAuthReason("codex");
7000
+ return markStale(payload ? parseRow$3(payload, source) : {
7001
+ ...blankRow("codex", source),
7002
+ note: missingAuth ?? "usage unavailable"
7003
+ }, "codex", void 0, missingAuth ?? reason);
7004
+ }
7005
+ //#endregion
7006
+ //#region src/agents/codex/index.ts
7007
+ var meta$3 = meta_default$3;
7008
+ /** Codex hook events → state. PermissionRequest fires just before the
7009
+ * approval prompt (PostToolUse clears it once the approved call has run,
7010
+ * or the next PreToolUse does if a reviewer auto-decided). */
7011
+ var HOOKS$2 = claudeStyleHooks([
6551
7012
  ["UserPromptSubmit", "working"],
6552
7013
  ["PreToolUse", "working"],
6553
7014
  ["PostToolUse", "working"],
6554
7015
  [
6555
- "Notification",
7016
+ "PermissionRequest",
6556
7017
  "waiting",
6557
- "attention",
6558
- "permission_prompt|elicitation_dialog"
7018
+ "attention"
6559
7019
  ],
6560
7020
  [
6561
7021
  "Stop",
6562
7022
  "waiting",
6563
7023
  "done"
6564
- ],
6565
- [
6566
- "StopCancelled",
6567
- "waiting",
6568
- "done"
6569
- ],
6570
- [
6571
- "StopFailure",
6572
- "waiting",
6573
- "attention"
6574
7024
  ]
6575
- ];
6576
- /** Grok discovers hooks from `~/.grok/hooks-paths`: one absolute path per
6577
- * line, the registry its own `/hooks-add` command appends to. work's hook
6578
- * file is kept in the data dir the commands read the port from env, so
6579
- * the file never needs rewriting and registered once. Returns false when
6580
- * the registry cannot be written (hooks then simply don't report). */
6581
- function grokHooksRegistered() {
6582
- const file = path.join(config.dataDir, "grok-hooks.json");
6583
- const hooks = {};
6584
- for (const [event, state, reason, matcher] of GROK_HOOKS) hooks[event] = [{
6585
- ...matcher ? { matcher } : {},
7025
+ ]);
7026
+ /** Codex only runs hooks it trusts. Trust is a hash of the normalized hook
7027
+ * kept in config (`hooks.state.<key>.trusted_hash`), and both the hooks and
7028
+ * that state are read from the `-c` override layer too so the launch flags
7029
+ * can carry their own trust without touching ~/.codex/config.toml.
7030
+ * Mirrors codex-rs/hooks/src/engine/discovery.rs (hook_hash, hook_key,
7031
+ * normalized Command config: timeout defaults to 600, async false, None
7032
+ * fields omitted) and codex-rs/config/src/fingerprint.rs (sha256 of
7033
+ * key-sorted JSON). scripts/smoke.mjs pins it against a hash Codex wrote. */
7034
+ function codexHookTrustHash(event, command) {
7035
+ const identity = {
7036
+ event_name: hookKeyLabel(event),
6586
7037
  hooks: [{
6587
- type: "command",
6588
- command: stateCurl(state, reason),
6589
- timeout: 5
7038
+ async: false,
7039
+ command,
7040
+ timeout: 600,
7041
+ type: "command"
6590
7042
  }]
6591
- }];
6592
- writeIfChanged(file, JSON.stringify({ hooks }, null, 2));
6593
- const registry = path.join(process.env.HOME || "", ".grok", "hooks-paths");
7043
+ };
7044
+ return "sha256:" + createHash("sha256").update(JSON.stringify(identity)).digest("hex");
7045
+ }
7046
+ /** `PermissionRequest` → `permission_request` (codex-rs/hooks/src/lib.rs). */
7047
+ function hookKeyLabel(event) {
7048
+ return event.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
7049
+ }
7050
+ /** Synthetic path Codex gives the `-c` override layer when keying hook state. */
7051
+ var SESSION_FLAGS_PATH = "/<session-flags>/config.toml";
7052
+ /** `-c` overrides that install work's state hooks and trust them. */
7053
+ function launchFlags() {
7054
+ const flags = [];
7055
+ const state = [];
7056
+ for (const [event, groups] of Object.entries(HOOKS$2)) {
7057
+ const command = groups[0].hooks[0].command;
7058
+ flags.push("-c", `hooks.${event}=[{hooks=[{type="command",command=${JSON.stringify(command)}}]}]`);
7059
+ const key = `${SESSION_FLAGS_PATH}:${hookKeyLabel(event)}:0:0`;
7060
+ state.push(`${JSON.stringify(key)}={trusted_hash=${JSON.stringify(codexHookTrustHash(event, command))}}`);
7061
+ }
7062
+ flags.push("-c", `hooks.state={${state.join(",")}}`);
7063
+ return flags;
7064
+ }
7065
+ function readHook$2(p, reported) {
7066
+ const read = readClaudeStyle(p, reported);
7067
+ if (!read.summary && p.hook_event_name === "PermissionRequest") {
7068
+ const input = p.tool_input;
7069
+ const what = str$1(input?.command) || str$1(p.tool_name);
7070
+ read.summary = what ? cleanSummary(`Approve: ${what}`) : "Waiting for permission";
7071
+ }
7072
+ return read;
7073
+ }
7074
+ var codex = {
7075
+ meta: meta$3,
7076
+ launchFlags,
7077
+ reportsWorking: () => true,
7078
+ envPrefixes: ["CODEX_"],
7079
+ pathDirs: ["~/.codex/bin"],
7080
+ readHook: readHook$2,
7081
+ usage: () => codexRow().then((row) => [row])
7082
+ };
7083
+ //#endregion
7084
+ //#region src/agents/copilot/usage.ts
7085
+ var USER_API = "https://api.github.com/copilot_internal/user";
7086
+ var HOSTS = path.join(process.env.XDG_CONFIG_HOME || path.join(HOME, ".config"), "gh", "hosts.yml");
7087
+ var SESSION_DB = path.join(HOME, ".copilot", "session-store.db");
7088
+ /** github.com's `oauth_token` straight out of gh's own hosts.yml.
7089
+ *
7090
+ * Deliberately not a YAML parse (no dependency): track the current top-level
7091
+ * host key by indentation and take the first token beneath github.com. */
7092
+ function hostsToken() {
7093
+ let text;
6594
7094
  try {
6595
- let current = "";
7095
+ text = fs.readFileSync(HOSTS, "utf8");
7096
+ } catch {
7097
+ return null;
7098
+ }
7099
+ let host = "";
7100
+ for (const line of text.split("\n")) {
7101
+ if (line[0]?.trim() && line.trimEnd().endsWith(":")) {
7102
+ host = line.trim().replace(/:$/, "");
7103
+ continue;
7104
+ }
7105
+ if (host !== "github.com") continue;
7106
+ const match = /^\s+oauth_token:\s*(\S+)/.exec(line);
7107
+ if (match) return match[1] ?? null;
7108
+ }
7109
+ return null;
7110
+ }
7111
+ /** The Copilot CLI keeps its own auth in the OS keyring, so reuse the token
7112
+ * gh manages. Copilot entitlement is account-scoped, so any valid token for
7113
+ * the entitled user reads the quota. */
7114
+ async function token() {
7115
+ for (const key of ["GH_TOKEN", "GITHUB_TOKEN"]) {
7116
+ const value = process.env[key]?.trim();
7117
+ if (value) return value;
7118
+ }
7119
+ const fromHosts = hostsToken();
7120
+ if (fromHosts) return fromHosts;
7121
+ try {
7122
+ return await run$1("gh", ["auth", "token"]) || null;
7123
+ } catch {
7124
+ return null;
7125
+ }
7126
+ }
7127
+ async function fetchUsage$1(auth) {
7128
+ if (!auth) return null;
7129
+ const data = obj(await fetchJson(USER_API, { headers: {
7130
+ Authorization: `token ${auth}`,
7131
+ "User-Agent": "GitHubCopilotCLI",
7132
+ Accept: "application/json"
7133
+ } }));
7134
+ return data.quota_snapshots == null ? null : data;
7135
+ }
7136
+ /** Billing period start = one month before the quota reset date. */
7137
+ function periodStart(resetIso) {
7138
+ const reset = new Date(resetIso);
7139
+ if (Number.isNaN(reset.getTime())) return null;
7140
+ const year = reset.getUTCMonth() === 0 ? reset.getUTCFullYear() - 1 : reset.getUTCFullYear();
7141
+ const month = reset.getUTCMonth() === 0 ? 12 : reset.getUTCMonth();
7142
+ const day = Math.min(reset.getUTCDate(), 28);
7143
+ return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
7144
+ }
7145
+ /** Credits this machine's Copilot CLI has spent in the current period.
7146
+ *
7147
+ * GitHub's own quota snapshot is batched — it can still read `credits_used: 0`
7148
+ * minutes after a session that plainly spent credits — so the CLI's local
7149
+ * ledger is the only number that moves in real time. It sees this machine
7150
+ * only, which is why the caller floors it with upstream rather than trusting
7151
+ * it alone. */
7152
+ function localCredits(start) {
7153
+ if (!start || !fs.existsSync(SESSION_DB)) return null;
7154
+ try {
7155
+ const db = new DatabaseSync(SESSION_DB, { readOnly: true });
6596
7156
  try {
6597
- current = fs.readFileSync(registry, "utf8");
6598
- } catch {}
6599
- if (!current.split("\n").some((line) => line.trim() === file)) {
6600
- fs.mkdirSync(path.dirname(registry), { recursive: true });
6601
- fs.appendFileSync(registry, `${current && !current.endsWith("\n") ? "\n" : ""}${file}\n`);
7157
+ const nano = num(db.prepare("select sum(total_nano_aiu) as nano from assistant_usage_events where created_at >= ?").get(start)?.nano);
7158
+ return nano == null ? 0 : nano / 1e9;
7159
+ } finally {
7160
+ db.close();
6602
7161
  }
6603
- grokHooksOk = true;
6604
7162
  } catch {
6605
- grokHooksOk = false;
7163
+ return null;
6606
7164
  }
6607
- return grokHooksOk;
6608
7165
  }
7166
+ function parseRow$2(payload, source) {
7167
+ const premium = obj(obj(payload.quota_snapshots).premium_interactions);
7168
+ const reset = str(payload.quota_reset_date_utc || payload.quota_reset_date) || null;
7169
+ let pct = null;
7170
+ let note;
7171
+ if (!Object.keys(premium).length || !premium.has_quota) note = "no premium quota";
7172
+ else if (premium.unlimited) note = "unlimited premium requests";
7173
+ else {
7174
+ const remaining = num(premium.percent_remaining);
7175
+ if (remaining != null) pct = 100 - remaining;
7176
+ const entitlement = num(premium.entitlement) ?? 0;
7177
+ const used = reset ? localCredits(periodStart(reset)) : null;
7178
+ if (entitlement > 0 && used != null) {
7179
+ const localPct = 100 * used / entitlement;
7180
+ if (pct == null || localPct > pct) {
7181
+ pct = localPct;
7182
+ source = `${source}+local`;
7183
+ }
7184
+ }
7185
+ }
7186
+ if (pct != null) pct = Math.round(pct * 1e3) / 1e3;
7187
+ const row = {
7188
+ agent: "copilot",
7189
+ plan: str(payload.copilot_plan) || "copilot",
7190
+ account: str(payload.login) || "—",
7191
+ five_h: null,
7192
+ five_h_resets: null,
7193
+ seven_d: pct,
7194
+ seven_d_resets: pct == null ? null : reset,
7195
+ seven_d_label: "month",
7196
+ fable: null,
7197
+ fable_resets: null,
7198
+ source
7199
+ };
7200
+ if (note) row.note = note;
7201
+ return row;
7202
+ }
7203
+ async function copilotRow() {
7204
+ const auth = await token();
7205
+ const { payload, source, reason } = await loadPayload("copilot", () => fetchUsage$1(auth));
7206
+ const missingAuth = auth ? void 0 : noAuthReason("copilot");
7207
+ return markStale(payload ? parseRow$2(payload, source) : {
7208
+ ...blankRow("copilot", source),
7209
+ note: missingAuth ?? "usage unavailable"
7210
+ }, "copilot", void 0, missingAuth ?? reason);
7211
+ }
7212
+ //#endregion
7213
+ //#region src/agents/copilot/index.ts
7214
+ var meta$2 = meta_default$2;
6609
7215
  /** Copilot hook events (Claude-format names, which make it emit Claude's
6610
- * snake_case payloads: hook_event_name, notification_type…) → state.
6611
- * Notification rather than PermissionRequest for "needs input": the latter
6612
- * fires before Copilot's own allow rules, the former only once a prompt is
6613
- * actually on screen. Its Stop payload has no last message; the server
6614
- * reads it from transcript_path (see agent-state.ts). */
6615
- var COPILOT_HOOKS = [
7216
+ * snake_case payloads) → state. Notification rather than PermissionRequest
7217
+ * for "needs input": the latter fires before Copilot's own allow rules, the
7218
+ * former only once a prompt is actually on screen. */
7219
+ var HOOKS$1 = claudeStyleHooks([
6616
7220
  ["UserPromptSubmit", "working"],
6617
7221
  ["PreToolUse", "working"],
6618
7222
  ["PostToolUse", "working"],
@@ -6626,25 +7230,20 @@ var COPILOT_HOOKS = [
6626
7230
  "waiting",
6627
7231
  "done"
6628
7232
  ]
6629
- ];
7233
+ ]);
6630
7234
  /** Copilot has no flag or env for inline hooks; the file-free option is a
6631
7235
  * plugin directory passed with `--plugin-dir`, which loads without a trust
6632
7236
  * prompt. The plugin is two files kept under work's data dir and rewritten
6633
7237
  * only when their content changes (a stale hook would report to the wrong
6634
- * port after a config change). Returns the directory. */
6635
- function copilotPluginDir() {
7238
+ * port after a config change). */
7239
+ function pluginDir() {
6636
7240
  const dir = path.join(config.dataDir, "copilot-plugin");
6637
- const hooks = {};
6638
- for (const [event, state, reason] of COPILOT_HOOKS) hooks[event] = [{ hooks: [{
6639
- type: "command",
6640
- command: stateCurl(state, reason)
6641
- }] }];
6642
7241
  writeIfChanged(path.join(dir, ".claude-plugin", "plugin.json"), JSON.stringify({
6643
7242
  name: "work-session-state",
6644
7243
  version: "1",
6645
7244
  description: "Reports this session's working / needs-input / done state to work."
6646
7245
  }, null, 2));
6647
- writeIfChanged(path.join(dir, "hooks", "hooks.json"), JSON.stringify({ hooks }, null, 2));
7246
+ writeIfChanged(path.join(dir, "hooks", "hooks.json"), JSON.stringify({ hooks: HOOKS$1 }, null, 2));
6648
7247
  return dir;
6649
7248
  }
6650
7249
  function writeIfChanged(file, content) {
@@ -6654,89 +7253,401 @@ function writeIfChanged(file, content) {
6654
7253
  fs.mkdirSync(path.dirname(file), { recursive: true });
6655
7254
  fs.writeFileSync(file, content);
6656
7255
  }
6657
- /** Codex hook events the state each reports. Same shape as Claude's:
6658
- * a prompt or a tool call means working, Stop means the turn is done,
6659
- * PermissionRequest means it is about to ask (fires just before the
6660
- * approval prompt; PostToolUse clears it once the approved call has run,
6661
- * or the next PreToolUse does if a reviewer auto-decided). */
6662
- var CODEX_HOOKS = [
7256
+ /** Copilot routes shell and inbox chatter through the same Notification
7257
+ * hook; those are neither "needs input" nor "done" and must be dropped. */
7258
+ var CHATTER = /* @__PURE__ */ new Set([
7259
+ "shell_completed",
7260
+ "shell_detached_completed",
7261
+ "new_inbox_message",
7262
+ "instruction_discovered",
7263
+ "factory_completed",
7264
+ "unclassified"
7265
+ ]);
7266
+ function readHook$1(p, reported) {
7267
+ if (CHATTER.has(notificationType(p))) return { ignore: true };
7268
+ const read = readClaudeStyle(p, reported);
7269
+ if (!read.summary && reported === "done") read.summary = lastMessageFromTranscript(p);
7270
+ return read;
7271
+ }
7272
+ /** The last non-empty `assistant.message` in Copilot's events.jsonl. Reads
7273
+ * the tail only — transcripts grow for the life of a session. */
7274
+ function lastMessageFromTranscript(p) {
7275
+ const file = p.transcript_path;
7276
+ if (typeof file !== "string" || !file.endsWith("events.jsonl")) return void 0;
7277
+ try {
7278
+ const fd = fs.openSync(file, "r");
7279
+ try {
7280
+ const size = fs.fstatSync(fd).size;
7281
+ const len = Math.min(size, 65536);
7282
+ const buf = Buffer.alloc(len);
7283
+ fs.readSync(fd, buf, 0, len, size - len);
7284
+ const lines = buf.toString("utf8").split("\n");
7285
+ for (let i = lines.length - 1; i >= 0; i--) {
7286
+ if (!lines[i].includes("\"assistant.message\"")) continue;
7287
+ try {
7288
+ const ev = JSON.parse(lines[i]);
7289
+ const content = ev.type === "assistant.message" ? ev.data?.content : void 0;
7290
+ if (typeof content === "string" && content.trim()) return cleanSummary(content);
7291
+ } catch {}
7292
+ }
7293
+ } finally {
7294
+ fs.closeSync(fd);
7295
+ }
7296
+ } catch {}
7297
+ }
7298
+ var copilot = {
7299
+ meta: meta$2,
7300
+ launchFlags: () => ["--plugin-dir", pluginDir()],
7301
+ reportsWorking: () => true,
7302
+ envPrefixes: ["COPILOT_"],
7303
+ readHook: readHook$1,
7304
+ usage: () => copilotRow().then((row) => [row])
7305
+ };
7306
+ //#endregion
7307
+ //#region src/agents/cursor/usage.ts
7308
+ var AUTH$1 = path.join(HOME, ".config", "cursor", "auth.json");
7309
+ var CLI_CONFIG = path.join(HOME, ".cursor", "cli-config.json");
7310
+ var API = "https://api2.cursor.sh";
7311
+ var WWW = "https://cursor.com";
7312
+ function cursorAuth() {
7313
+ const auth = readJson(AUTH$1);
7314
+ if (!auth) return null;
7315
+ const token = str(auth.accessToken || auth.access_token);
7316
+ if (!token) return null;
7317
+ let sub = "";
7318
+ try {
7319
+ sub = str(obj(JSON.parse(Buffer.from(str(token.split(".")[1]), "base64url").toString())).sub);
7320
+ } catch {}
7321
+ return {
7322
+ token,
7323
+ sub,
7324
+ email: str(obj(readJson(CLI_CONFIG)?.authInfo).email).trim()
7325
+ };
7326
+ }
7327
+ var dashboardPost = async (token, method) => obj(await fetchJson(`${API}/aiserver.v1.DashboardService/${method}`, {
7328
+ method: "POST",
7329
+ body: "{}",
7330
+ headers: {
7331
+ Authorization: `Bearer ${token}`,
7332
+ "Content-Type": "application/json",
7333
+ Accept: "application/json",
7334
+ "Connect-Protocol-Version": "1",
7335
+ "User-Agent": "cursor-agent"
7336
+ }
7337
+ }));
7338
+ /** Enterprise/team monthly pool, which the dashboard service leaves empty —
7339
+ * it is only readable with the web session cookie. */
7340
+ async function usageSummary(auth) {
7341
+ if (!auth.sub) return null;
7342
+ const cookie = `WorkosCursorSessionToken=${encodeURIComponent(`${auth.sub}::${auth.token}`)}`;
7343
+ return obj(await fetchJson(`${WWW}/api/usage-summary`, { headers: {
7344
+ Cookie: cookie,
7345
+ Accept: "application/json",
7346
+ "User-Agent": "Mozilla/5.0"
7347
+ } }));
7348
+ }
7349
+ /** Prefer the dashboard's planUsage (Pro/Ultra); fall back to the web
7350
+ * summary for Enterprise/team accounts, where planUsage comes back empty.
7351
+ * Each call is optional on its own, so one failing endpoint must not hide a
7352
+ * number another would have produced — but if nothing lands, the first
7353
+ * failure is rethrown so the row can say why. */
7354
+ async function fetchUsage(auth) {
7355
+ let failure;
7356
+ const soft = async (load) => {
7357
+ try {
7358
+ return await load();
7359
+ } catch (err) {
7360
+ failure ??= err;
7361
+ return null;
7362
+ }
7363
+ };
7364
+ const [period, planInfo] = await Promise.all([soft(() => dashboardPost(auth.token, "GetCurrentPeriodUsage")), soft(() => dashboardPost(auth.token, "GetPlanInfo"))]);
7365
+ const planUsage = obj(obj(period).planUsage);
7366
+ const summary = num(planUsage.limit) ? null : await soft(() => usageSummary(auth));
7367
+ if (!Object.keys(planUsage).length && !summary) {
7368
+ if (failure) throw failure;
7369
+ return null;
7370
+ }
7371
+ return {
7372
+ period: obj(period),
7373
+ plan_info: obj(obj(planInfo).planInfo),
7374
+ summary
7375
+ };
7376
+ }
7377
+ function parseRow$1(payload, auth, source) {
7378
+ const period = obj(payload.period);
7379
+ const planInfo = obj(payload.plan_info);
7380
+ const summary = obj(payload.summary);
7381
+ let pct = num(obj(period.planUsage).totalPercentUsed);
7382
+ let resets = isoFromEpoch(period.billingCycleEnd);
7383
+ if (pct == null) {
7384
+ const overall = obj(obj(summary.individualUsage).overall);
7385
+ const used = num(overall.used);
7386
+ const limit = num(overall.limit);
7387
+ if (used != null && limit) pct = Math.round(100 * used / limit);
7388
+ resets = str(summary.billingCycleEnd) || resets;
7389
+ }
7390
+ return {
7391
+ agent: "cursor",
7392
+ plan: str(planInfo.planName || summary.membershipType) || "cursor",
7393
+ account: auth?.email ? maskEmail(auth.email) : "—",
7394
+ five_h: null,
7395
+ five_h_resets: null,
7396
+ seven_d: pct == null ? null : Math.trunc(pct),
7397
+ seven_d_resets: resets,
7398
+ seven_d_label: "month",
7399
+ fable: null,
7400
+ fable_resets: null,
7401
+ source
7402
+ };
7403
+ }
7404
+ async function cursorRow() {
7405
+ const auth = cursorAuth();
7406
+ const { payload, source, reason } = await loadPayload("cursor", () => auth ? fetchUsage(auth) : Promise.resolve(null));
7407
+ const missingAuth = auth ? void 0 : noAuthReason("cursor");
7408
+ return markStale(payload ? parseRow$1(payload, auth, source) : {
7409
+ ...blankRow("cursor", source),
7410
+ note: missingAuth ?? "usage unavailable"
7411
+ }, "cursor", void 0, missingAuth ?? reason);
7412
+ }
7413
+ /** Cursor's plugin-dir hooks can't carry stop/prompt events (those load only
7414
+ * from ~/.cursor/hooks.json) and it has no permission-prompt hook at all,
7415
+ * so its sessions get working/idle from pane activity (see scraper.ts) and
7416
+ * nothing else — a stopped turn and a question look the same. */
7417
+ var cursor = {
7418
+ meta: meta_default$1,
7419
+ usage: () => cursorRow().then((row) => [row])
7420
+ };
7421
+ //#endregion
7422
+ //#region src/agents/grok/usage.ts
7423
+ var AUTH = path.join(HOME, ".grok", "auth.json");
7424
+ var LOG = path.join(HOME, ".grok", "logs", "unified.jsonl");
7425
+ /** The newest usable entry in ~/.grok/auth.json, which is keyed by scope
7426
+ * rather than being a single credential. */
7427
+ function grokAuth() {
7428
+ const auth = readJson(AUTH);
7429
+ if (!auth) return null;
7430
+ const entry = Object.values(auth).map(obj).filter((entry) => str(entry.key) || str(entry.access_token)).sort((a, b) => str(b.create_time || b.expires_at).localeCompare(str(a.create_time || a.expires_at)))[0];
7431
+ if (!entry) return null;
7432
+ return {
7433
+ token: str(entry.key) || str(entry.access_token),
7434
+ email: str(entry.email),
7435
+ expiresAt: entry.expires_at
7436
+ };
7437
+ }
7438
+ /** Weekly credit pool from the Grok Build CLI's own proxy. */
7439
+ async function fetchBilling(token) {
7440
+ const data = obj(await fetchJson("https://cli-chat-proxy.grok.com/v1/billing?format=credits", { headers: {
7441
+ Authorization: `Bearer ${token}`,
7442
+ Accept: "application/json",
7443
+ "User-Agent": "grok-cli",
7444
+ "x-grok-client-mode": "cli"
7445
+ } }));
7446
+ return data.config ? data : null;
7447
+ }
7448
+ /** Fallback: the last credits config the CLI logged, normalized to the live
7449
+ * API's shape. Reads only the tail — this log grows without bound. */
7450
+ function billingFromLogs() {
7451
+ let chunk;
7452
+ try {
7453
+ const { size } = fs.statSync(LOG);
7454
+ const start = Math.max(0, size - 512e3);
7455
+ const fd = fs.openSync(LOG, "r");
7456
+ try {
7457
+ const buf = Buffer.alloc(size - start);
7458
+ fs.readSync(fd, buf, 0, buf.length, start);
7459
+ chunk = buf.toString("utf8");
7460
+ } finally {
7461
+ fs.closeSync(fd);
7462
+ }
7463
+ } catch {
7464
+ return null;
7465
+ }
7466
+ let latest = null;
7467
+ for (const line of chunk.split("\n")) {
7468
+ if (!line.includes("billing: fetched credits config")) continue;
7469
+ try {
7470
+ const entry = obj(JSON.parse(line));
7471
+ if (entry.msg === "billing: fetched credits config") latest = entry;
7472
+ } catch {}
7473
+ }
7474
+ if (!latest) return null;
7475
+ const ctx = obj(latest.ctx);
7476
+ const cfg = obj(ctx.config);
7477
+ if (!Object.keys(cfg).length) return null;
7478
+ return {
7479
+ config: cfg,
7480
+ subscriptionTier: ctx.subscriptionTier
7481
+ };
7482
+ }
7483
+ function parseRow(payload, auth, source) {
7484
+ const cfg = payload.config ? obj(payload.config) : payload;
7485
+ const pct = num(cfg.creditUsagePercent);
7486
+ let productPct = null;
7487
+ for (const entry of arr(cfg.productUsage)) {
7488
+ const usage = obj(entry);
7489
+ if (!str(usage.product).toLowerCase().includes("grok")) continue;
7490
+ productPct = num(usage.usagePercent);
7491
+ break;
7492
+ }
7493
+ const used = pct ?? productPct;
7494
+ const period = obj(cfg.currentPeriod);
7495
+ return {
7496
+ agent: "grok",
7497
+ plan: str(payload.subscriptionTier || cfg.subscriptionTier) || "Grok Build",
7498
+ account: auth?.email ? maskEmail(auth.email) : "—",
7499
+ five_h: null,
7500
+ five_h_resets: null,
7501
+ seven_d: used == null ? null : Math.trunc(used),
7502
+ seven_d_resets: str(period.end || cfg.billingPeriodEnd) || null,
7503
+ seven_d_label: /MONTH/i.test(str(period.type)) ? "month" : "7d",
7504
+ fable: null,
7505
+ fable_resets: null,
7506
+ source
7507
+ };
7508
+ }
7509
+ async function grokRow() {
7510
+ const auth = grokAuth();
7511
+ const loaded = await loadPayload("grok", () => auth?.token ? fetchBilling(auth.token) : Promise.resolve(null));
7512
+ const fromLog = loaded.payload ? null : billingFromLogs();
7513
+ const payload = loaded.payload ?? fromLog;
7514
+ const source = fromLog ? "unified.jsonl" : loaded.source;
7515
+ const missingAuth = auth?.token ? void 0 : noAuthReason("grok");
7516
+ return markStale(payload ? parseRow(payload, auth, source) : {
7517
+ ...blankRow("grok", source),
7518
+ note: missingAuth ?? "usage unavailable"
7519
+ }, "grok", expired(auth?.expiresAt), missingAuth ?? loaded.reason);
7520
+ }
7521
+ //#endregion
7522
+ //#region src/agents/grok/index.ts
7523
+ var meta = meta_default;
7524
+ /** Grok hook events → state. Payloads are camelCase (hookEventName,
7525
+ * notificationType, lastAssistantMessage). Notification is matched to the
7526
+ * two kinds that mean a prompt is on screen; Stop fires on shutdown too,
7527
+ * with a reason readHook uses to tell a finished turn from a closed
7528
+ * session. */
7529
+ var HOOKS = claudeStyleHooks([
6663
7530
  ["UserPromptSubmit", "working"],
6664
7531
  ["PreToolUse", "working"],
6665
7532
  ["PostToolUse", "working"],
6666
7533
  [
6667
- "PermissionRequest",
7534
+ "Notification",
6668
7535
  "waiting",
6669
- "attention"
7536
+ "attention",
7537
+ "permission_prompt|elicitation_dialog"
6670
7538
  ],
6671
7539
  [
6672
7540
  "Stop",
6673
7541
  "waiting",
6674
7542
  "done"
7543
+ ],
7544
+ [
7545
+ "StopCancelled",
7546
+ "waiting",
7547
+ "done"
7548
+ ],
7549
+ [
7550
+ "StopFailure",
7551
+ "waiting",
7552
+ "attention"
6675
7553
  ]
6676
- ];
6677
- /** Codex only runs hooks it trusts. Trust is a hash of the normalized hook
6678
- * kept in config (`hooks.state.<key>.trusted_hash`), and both the hooks and
6679
- * that state are read from the `-c` override layer too — so the launch flags
6680
- * can carry their own trust without touching ~/.codex/config.toml.
6681
- * Mirrors codex-rs/hooks/src/engine/discovery.rs (hook_hash, hook_key,
6682
- * normalized Command config: timeout defaults to 600, async false, None
6683
- * fields omitted) and codex-rs/config/src/fingerprint.rs (sha256 of
6684
- * key-sorted JSON). Verified against real trusted_hash entries. */
6685
- function codexHookTrustHash(event, command) {
6686
- const identity = {
6687
- event_name: codexHookKeyLabel(event),
6688
- hooks: [{
6689
- async: false,
6690
- command,
6691
- timeout: 600,
6692
- type: "command"
6693
- }]
6694
- };
6695
- return "sha256:" + createHash("sha256").update(JSON.stringify(identity)).digest("hex");
6696
- }
6697
- /** `PermissionRequest` `permission_request` (codex-rs/hooks/src/lib.rs). */
6698
- function codexHookKeyLabel(event) {
6699
- return event.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
7554
+ ], { timeout: 5 });
7555
+ /** Whether Grok's hook registry took work's hook file without it Grok
7556
+ * reports nothing and the screen has to speak for it. */
7557
+ var registered = false;
7558
+ /** Grok has no flag for hooks. It discovers them from `~/.grok/hooks-paths`:
7559
+ * one absolute path per line, the registry its own `/hooks-add` command
7560
+ * appends to. work's hook file is kept in the data dir — the commands read
7561
+ * the port from env, so the file never needs rewriting — and named there
7562
+ * once. The one line in that registry is the only thing work writes
7563
+ * outside its own dir for any agent. */
7564
+ function registerHooks() {
7565
+ const file = path.join(config.dataDir, "grok-hooks.json");
7566
+ writeIfChanged(file, JSON.stringify({ hooks: HOOKS }, null, 2));
7567
+ const registry = path.join(process.env.HOME || "", ".grok", "hooks-paths");
7568
+ try {
7569
+ let current = "";
7570
+ try {
7571
+ current = fs.readFileSync(registry, "utf8");
7572
+ } catch {}
7573
+ if (!current.split("\n").some((line) => line.trim() === file)) {
7574
+ fs.mkdirSync(path.dirname(registry), { recursive: true });
7575
+ fs.appendFileSync(registry, `${current && !current.endsWith("\n") ? "\n" : ""}${file}\n`);
7576
+ }
7577
+ registered = true;
7578
+ } catch {
7579
+ registered = false;
7580
+ }
7581
+ return registered;
6700
7582
  }
6701
- /** Synthetic path Codex gives the `-c` override layer when keying hook state. */
6702
- var CODEX_SESSION_FLAGS_PATH = "/<session-flags>/config.toml";
6703
- /** `-c` overrides that install work's state hooks in a Codex session. */
6704
- function codexHookFlags() {
6705
- const flags = [];
6706
- const state = [];
6707
- for (const [event, st, reason] of CODEX_HOOKS) {
6708
- const command = stateCurl(st, reason);
6709
- flags.push("-c", `hooks.${event}=[{hooks=[{type="command",command=${JSON.stringify(command)}}]}]`);
6710
- const key = `${CODEX_SESSION_FLAGS_PATH}:${codexHookKeyLabel(event)}:0:0`;
6711
- state.push(`${JSON.stringify(key)}={trusted_hash=${JSON.stringify(codexHookTrustHash(event, command))}}`);
7583
+ function readHook(p, reported) {
7584
+ if (str$1(p.subagentType)) return { ignore: true };
7585
+ if (p.hookEventName === "stop" && str$1(p.reason) && p.reason !== "end_turn") return { ignore: true };
7586
+ const read = readClaudeStyle(p, reported);
7587
+ if (!read.summary) {
7588
+ const err = str$1(p.errorDetails) || str$1(p.error);
7589
+ if (err) read.summary = cleanSummary(err);
6712
7590
  }
6713
- flags.push("-c", `hooks.state={${state.join(",")}}`);
6714
- return flags;
7591
+ return read;
6715
7592
  }
6716
- /** Family of an instance id (`claude2` → `claude`); flags and hooks are
6717
- * per family, every numbered copy behaves the same. */
6718
- function agentFamily(agent) {
6719
- return agent ? parseAgentId(agent)?.family.id : void 0;
7593
+ //#endregion
7594
+ //#region src/agents/index.ts
7595
+ /** Every agent family work integrates with, one folder each. Order is the
7596
+ * order Settings and pickers show; the folder's meta.json describes it,
7597
+ * index.ts drives it, usage.ts meters it. */
7598
+ var AGENTS = [
7599
+ claude,
7600
+ codex,
7601
+ copilot,
7602
+ cursor,
7603
+ {
7604
+ meta,
7605
+ launchFlags: () => {
7606
+ registerHooks();
7607
+ return [];
7608
+ },
7609
+ reportsWorking: () => registered,
7610
+ promptAsArgv: true,
7611
+ envPrefixes: ["GROK_"],
7612
+ pathDirs: ["~/.grok/bin"],
7613
+ readHook,
7614
+ usage: () => grokRow().then((row) => [row])
7615
+ }
7616
+ ];
7617
+ var byFamily = new Map(AGENTS.map((a) => [a.meta.id, a]));
7618
+ /** Integration for an instance id (`claude2` → claude), if any. */
7619
+ function agentFor(id) {
7620
+ return id ? byFamily.get(agentFamily(id)) : void 0;
6720
7621
  }
6721
7622
  /** Extra argv for an agent CLI so it reports working/waiting state. */
6722
- function agentLaunchFlags(agent) {
6723
- const family = agentFamily(agent);
6724
- if (family === "claude") {
6725
- const hook = (state, reason) => [{ hooks: [{
6726
- type: "command",
6727
- command: stateCurl(state, reason)
6728
- }] }];
6729
- return ["--settings", JSON.stringify({ hooks: {
6730
- UserPromptSubmit: hook("working"),
6731
- PreToolUse: hook("working"),
6732
- Stop: hook("waiting", "done"),
6733
- Notification: hook("waiting", "attention")
6734
- } })];
6735
- }
6736
- if (family === "codex") return codexHookFlags();
6737
- if (family === "copilot") return ["--plugin-dir", copilotPluginDir()];
6738
- if (family === "grok") grokHooksRegistered();
6739
- return [];
7623
+ function agentLaunchFlags(id) {
7624
+ return agentFor(id)?.launchFlags?.() ?? [];
7625
+ }
7626
+ /** Whether the agent says when it *starts* working, not just when it stops
7627
+ * — believing the screen over that costs it the state its own hook just
7628
+ * reported, so the pane scraper leaves these alone. */
7629
+ function reportsWorking(id) {
7630
+ return agentFor(id)?.reportsWorking?.() ?? false;
7631
+ }
7632
+ /** Cold launch takes the first prompt as argv rather than a paste. */
7633
+ function promptAsArgv(id) {
7634
+ return agentFor(id)?.promptAsArgv ?? false;
7635
+ }
7636
+ /** Env var prefixes no fresh pane may inherit (the app may itself be
7637
+ * running inside one of these agents). */
7638
+ function agentEnvPrefixes() {
7639
+ return AGENTS.flatMap((a) => a.envPrefixes ?? []);
7640
+ }
7641
+ /** Install dirs to put on a spawned pane's PATH (`~` = home). */
7642
+ function agentPathDirs(home) {
7643
+ return AGENTS.flatMap((a) => (a.pathDirs ?? []).map((d) => d.replace(/^~/, home)));
7644
+ }
7645
+ /** What a hook payload the session's CLI POSTed means. Unknown families
7646
+ * (or an unreadable body) keep the reported reason and carry no summary. */
7647
+ function readHookPayload(id, payload, reported) {
7648
+ if (!payload || typeof payload !== "object") return { reason: reported };
7649
+ const read = agentFor(id)?.readHook;
7650
+ return read ? read(payload, reported) : { reason: reported };
6740
7651
  }
6741
7652
  //#endregion
6742
7653
  //#region src/server/sessions.ts
@@ -6829,7 +7740,7 @@ async function sendToSession(id, text, size) {
6829
7740
  "-t",
6830
7741
  name
6831
7742
  ], env).then(() => true, () => false)) {
6832
- if (agentFamily(session.agent) === "grok") {
7743
+ if (promptAsArgv(session.agent)) {
6833
7744
  await tmux(newSessionArgs(session, wsPath, "-d", text, size), env);
6834
7745
  markTmuxStarted(id);
6835
7746
  return;
@@ -7038,17 +7949,14 @@ function shellQuote(word) {
7038
7949
  * inherit this env, and a service manager's PATH is usually minimal. */
7039
7950
  function cleanEnv() {
7040
7951
  const env = {};
7952
+ const agentPrefix = new RegExp(`^(${agentEnvPrefixes().join("|")})`);
7041
7953
  for (const [key, value] of Object.entries(process.env)) {
7042
7954
  if (value === void 0) continue;
7043
- if (/^(CLAUDE|CLAUDECODE|AI_AGENT|CODEX_|GROK_|COPILOT_)/.test(key)) continue;
7955
+ if (agentPrefix.test(key)) continue;
7044
7956
  env[key] = value;
7045
7957
  }
7046
7958
  const home = process.env.HOME || "";
7047
- const bins = [
7048
- `${home}/.local/bin`,
7049
- `${home}/.grok/bin`,
7050
- `${home}/.codex/bin`
7051
- ];
7959
+ const bins = [`${home}/.local/bin`, ...agentPathDirs(home)];
7052
7960
  const parts = (env.PATH || "").split(":");
7053
7961
  env.PATH = [...parts, ...bins.filter((bin) => !parts.includes(bin))].join(":");
7054
7962
  if (!/utf-?8/i.test(env.LC_ALL || env.LC_CTYPE || env.LANG || "")) {
@@ -7085,4 +7993,4 @@ function sleep(ms) {
7085
7993
  return new Promise((resolve) => setTimeout(resolve, ms));
7086
7994
  }
7087
7995
  //#endregion
7088
- export { all as $, saveSubscription as A, preferredAgent as B, setAgentPhase as C, summaryFromTranscript as D, summaryFromHookPayload as E, agentChoices as F, ghKnownHosts as G, setPref as H, allPrefs as I, which as J, miseVersions as K, disabledAgents as L, subscribe as M, subscriberCount as N, deleteSubscription as O, subscriberJoins as P, toolById as Q, enabledAgents as R, reasonFromHookPayload as S, setSessionViewers as T, setSetting as U, setDisabledAgents as V, doctor as W, TOOLS as X, DEFAULT_GITHUB_HOST as Y, agentById as Z, tmuxName as _, ensureTmuxServer as a, git as at, clearAgentPhase as b, killWorkspaceSessions as c, config as ct, newSessionArgs as d, db as et, notifySessionsChanged as f, sessionStates as g, sendToSession as h, disposeSessions as i, gh as it, publish as j, pushPublicKey as k, listSessions as l, renameSession as m, clearTmuxStarted as n, workspacePath as nt, getSession as o, run$1 as ot, panePids as p, setupPath as q, createSession as r, TMUX_SOCKET as rt, killSession as s, tmux as st, cleanEnv as t, get as tt, markTmuxStarted as u, tmuxWasStarted as v, setScrapedPhase as w, ignoreHookPayload as x, reportsWorking as y, getSetting as z };
7996
+ export { DEFAULT_GITHUB_HOST as $, setScrapedPhase as A, allPrefs as B, agentBadge as C, agentMeta as D, agentLabel as E, publish as F, setDisabledAgents as G, enabledAgents as H, subscribe as I, doctor as J, setPref as K, subscriberCount as L, deleteSubscription as M, pushPublicKey as N, clearAgentPhase as O, saveSubscription as P, which as Q, subscriberJoins as R, blankRow as S, agentFromCommand as T, getSetting as U, disabledAgents as V, preferredAgent as W, miseVersions as X, ghKnownHosts as Y, setupPath as Z, tmuxName as _, ensureTmuxServer as a, get as at, readHookPayload as b, killWorkspaceSessions as c, gh as ct, newSessionArgs as d, tmux as dt, TOOLS as et, notifySessionsChanged as f, config as ft, sessionStates as g, sendToSession as h, disposeSessions as i, db as it, setSessionViewers as j, setAgentPhase as k, listSessions as l, git as lt, renameSession as m, clearTmuxStarted as n, toolById as nt, getSession as o, workspacePath as ot, panePids as p, setSetting as q, createSession as r, all as rt, killSession as s, TMUX_SOCKET as st, cleanEnv as t, agentById as tt, markTmuxStarted as u, run$1 as ut, tmuxWasStarted as v, agentFamily as w, reportsWorking as x, AGENTS as y, agentChoices as z };