@dylanpiercey/work 0.0.71 → 0.0.73

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/{_DgDsZuoQ.js → _BDyG-m5v.js} +1248 -158
  2. package/dist/{_8t6K8Jk8.js → _a4E7JVTq.js} +65 -65
  3. package/dist/{_D3qriis-.js → _hclnI8dX.js} +2 -2
  4. package/dist/_wbad_ZHd.js +2 -0
  5. package/dist/index.mjs +155 -915
  6. package/dist/public/assets/{_DSg_vc6H2.js → _29x9Ht-Q2.js} +1 -1
  7. package/dist/public/assets/_29x9Ht-Q2.js.br +0 -0
  8. package/dist/public/assets/{_BejV602Y.js → _A-SnIf68.js} +1 -1
  9. package/dist/public/assets/_A-SnIf68.js.br +0 -0
  10. package/dist/public/assets/{_AeL166wR2.js → _B-PT04Ok2.js} +1 -1
  11. package/dist/public/assets/_B-PT04Ok2.js.br +0 -0
  12. package/dist/public/assets/{_B3wjms9s2.js → _BGsHhxfZ2.js} +1 -1
  13. package/dist/public/assets/_BGsHhxfZ2.js.br +0 -0
  14. package/dist/public/assets/{_BZNMfrgb.js → _BN-ThWaS.js} +1 -1
  15. package/dist/public/assets/_BN-ThWaS.js.br +0 -0
  16. package/dist/public/assets/{_DZss8Wq02.js → _BTjgy5c62.js} +1 -1
  17. package/dist/public/assets/_BTjgy5c62.js.br +0 -0
  18. package/dist/public/assets/{_Dp1TBNWN.js → _BU_ZD020.js} +1 -1
  19. package/dist/public/assets/_BU_ZD020.js.br +0 -0
  20. package/dist/public/assets/{_Cn0DEemU.js → _BUc_Vl5e.js} +1 -1
  21. package/dist/public/assets/_BUc_Vl5e.js.br +0 -0
  22. package/dist/public/assets/{_C-wce-RA.js → _BYAOEh2-.js} +1 -1
  23. package/dist/public/assets/_BYAOEh2-.js.br +0 -0
  24. package/dist/public/assets/{_CiElF6r4.js → _BhRT35mT.js} +20 -20
  25. package/dist/public/assets/_BhRT35mT.js.br +0 -0
  26. package/dist/public/assets/{_Dbf-xJ6k.js → _BmDB0rQV.js} +1 -1
  27. package/dist/public/assets/_BmDB0rQV.js.br +0 -0
  28. package/dist/public/assets/{_BFfBgn79.js → _Bpr7lRdd.js} +1 -1
  29. package/dist/public/assets/_Bpr7lRdd.js.br +0 -0
  30. package/dist/public/assets/{_CGVLhh2k.js → _Bud2gpcs.js} +1 -1
  31. package/dist/public/assets/_Bud2gpcs.js.br +0 -0
  32. package/dist/public/assets/{_Lot9I7Im.js → _Bx-jImeN.js} +1 -1
  33. package/dist/public/assets/_Bx-jImeN.js.br +0 -0
  34. package/dist/public/assets/{_USmNZPRb.js → _ByMM8N1r.js} +1 -1
  35. package/dist/public/assets/_ByMM8N1r.js.br +0 -0
  36. package/dist/public/assets/{_CgwQS6LN2.js → _C-JDWYNo2.js} +1 -1
  37. package/dist/public/assets/_C-JDWYNo2.js.br +0 -0
  38. package/dist/public/assets/{_dAXKSDYL.js → _C-ifLHsW.js} +1 -1
  39. package/dist/public/assets/_C-ifLHsW.js.br +0 -0
  40. package/dist/public/assets/{_BPmmCSLY.js → _C0xjFWIM.js} +1 -1
  41. package/dist/public/assets/_C0xjFWIM.js.br +0 -0
  42. package/dist/public/assets/{_HO-Qb8pn.js → _C1RzE6Mp.js} +1 -1
  43. package/dist/public/assets/_C1RzE6Mp.js.br +0 -0
  44. package/dist/public/assets/{_Ek1T1FMw.js → _C6Eavzmt.js} +1 -1
  45. package/dist/public/assets/_C6Eavzmt.js.br +0 -0
  46. package/dist/public/assets/{_CWVKprFe2.js → _C6iBocvM2.js} +1 -1
  47. package/dist/public/assets/_C6iBocvM2.js.br +0 -0
  48. package/dist/public/assets/{_DAun3Lt7.js → _C7CzRI13.js} +1 -1
  49. package/dist/public/assets/_C7CzRI13.js.br +0 -0
  50. package/dist/public/assets/{_uyFxI_FF2.js → _C9CdATEd2.js} +1 -1
  51. package/dist/public/assets/_C9CdATEd2.js.br +0 -0
  52. package/dist/public/assets/{_BqrygBOe.js → _CE-w6G87.js} +1 -1
  53. package/dist/public/assets/{_CzxBqeDO.js → _CHhO4fFL.js} +1 -1
  54. package/dist/public/assets/_CHhO4fFL.js.br +0 -0
  55. package/dist/public/assets/{_Bn6Hn06S.js → _CIkNdjAJ.js} +1 -1
  56. package/dist/public/assets/_CIkNdjAJ.js.br +0 -0
  57. package/dist/public/assets/{_Bd-WyHvg.js → _CJDpwABS.js} +1 -1
  58. package/dist/public/assets/_CJDpwABS.js.br +0 -0
  59. package/dist/public/assets/{_B3VIvUTs2.js → _CKSRE8zv2.js} +1 -1
  60. package/dist/public/assets/_CKSRE8zv2.js.br +0 -0
  61. package/dist/public/assets/{_CTdFL4wj.js → _CMkaOgq5.js} +1 -1
  62. package/dist/public/assets/_CMkaOgq5.js.br +0 -0
  63. package/dist/public/assets/{_DWdZCyBL2.js → _CQ_0JcVs2.js} +1 -1
  64. package/dist/public/assets/_CQ_0JcVs2.js.br +0 -0
  65. package/dist/public/assets/{_Dcav7tzL.js → _CQeNDnCp.js} +6 -6
  66. package/dist/public/assets/_CQeNDnCp.js.br +0 -0
  67. package/dist/public/assets/{_CdHoxy6P.js → _CSfPA_mo.js} +1 -1
  68. package/dist/public/assets/_CSfPA_mo.js.br +0 -0
  69. package/dist/public/assets/{_BztBrnhM.js → _CYjQ_rQU.js} +1 -1
  70. package/dist/public/assets/_CYjQ_rQU.js.br +0 -0
  71. package/dist/public/assets/{_BPH6VN8T2.js → _CdB7VHwX2.js} +1 -1
  72. package/dist/public/assets/_CdB7VHwX2.js.br +0 -0
  73. package/dist/public/assets/{_C6jdreqj.js → _CdWuqeZK.js} +1 -1
  74. package/dist/public/assets/_CdWuqeZK.js.br +0 -0
  75. package/dist/public/assets/{_CgENI0zF2.js → _Ce-R03cZ2.js} +1 -1
  76. package/dist/public/assets/_Ce-R03cZ2.js.br +0 -0
  77. package/dist/public/assets/{_BltuSqY6.js → _Ce2B5H6R.js} +1 -1
  78. package/dist/public/assets/_Ce2B5H6R.js.br +0 -0
  79. package/dist/public/assets/{_BKRausc72.js → _ChkPwLnS2.js} +1 -1
  80. package/dist/public/assets/_ChkPwLnS2.js.br +0 -0
  81. package/dist/public/assets/{_BqfMkwYX.js → _CiYb_Bp1.js} +1 -1
  82. package/dist/public/assets/_CiYb_Bp1.js.br +0 -0
  83. package/dist/public/assets/{_CLGWdoAJ.js → _CkABEOAv.js} +1 -1
  84. package/dist/public/assets/_CkABEOAv.js.br +0 -0
  85. package/dist/public/assets/{_BPW0wOMI2.js → _CkNCn2942.js} +1 -1
  86. package/dist/public/assets/_CkNCn2942.js.br +0 -0
  87. package/dist/public/assets/{_DwNswmCf.js → _CnaMcooO.js} +1 -1
  88. package/dist/public/assets/_CnaMcooO.js.br +0 -0
  89. package/dist/public/assets/{_C_9OeUA2.js → _CpByMJ40.js} +1 -1
  90. package/dist/public/assets/_CpByMJ40.js.br +0 -0
  91. package/dist/public/assets/{_DTSUTR0_2.js → _CtowbQCb2.js} +1 -1
  92. package/dist/public/assets/_CtowbQCb2.js.br +0 -0
  93. package/dist/public/assets/{_DBiZo1op2.js → _D-P8DqvK2.js} +1 -1
  94. package/dist/public/assets/{_DbEPPsY1.js → _D6MZcQKP.js} +1 -1
  95. package/dist/public/assets/_D6MZcQKP.js.br +0 -0
  96. package/dist/public/assets/{_9aJgLz6G2.js → _D7mRLhwX2.js} +1 -1
  97. package/dist/public/assets/_D7mRLhwX2.js.br +0 -0
  98. package/dist/public/assets/{_DNdrj0-M2.js → _D99GO9JW2.js} +1 -1
  99. package/dist/public/assets/_D99GO9JW2.js.br +0 -0
  100. package/dist/public/assets/{_DqBuHT5A.js → _DC8LVwVY.js} +1 -1
  101. package/dist/public/assets/_DC8LVwVY.js.br +0 -0
  102. package/dist/public/assets/{_BUeHx4zC2.js → _DESY84xu2.js} +1 -1
  103. package/dist/public/assets/_DESY84xu2.js.br +0 -0
  104. package/dist/public/assets/{_CvMvIBy42.js → _DJARroah2.js} +1 -1
  105. package/dist/public/assets/_DJARroah2.js.br +0 -0
  106. package/dist/public/assets/{_CpnXaqgd2.js → _DK6p9z1j2.js} +1 -1
  107. package/dist/public/assets/_DK6p9z1j2.js.br +0 -0
  108. package/dist/public/assets/{_CeVU1yJL.js → _DKHAhfGs.js} +1 -1
  109. package/dist/public/assets/_DKHAhfGs.js.br +0 -0
  110. package/dist/public/assets/{_CbgTB_-a.js → _DQSUDhad.js} +1 -1
  111. package/dist/public/assets/_DQSUDhad.js.br +0 -0
  112. package/dist/public/assets/{_C-LtCarQ2.js → _DSWOPuxC2.js} +1 -1
  113. package/dist/public/assets/{_BR_6IGfk.js → _DSiaWqg3.js} +1 -1
  114. package/dist/public/assets/_DSiaWqg3.js.br +0 -0
  115. package/dist/public/assets/{_DXCHOWYL.js → _DcXafbQI.js} +1 -1
  116. package/dist/public/assets/_DcXafbQI.js.br +0 -0
  117. package/dist/public/assets/{_QQHXmR7d.js → _DcfF8nxm.js} +1 -1
  118. package/dist/public/assets/_DcfF8nxm.js.br +0 -0
  119. package/dist/public/assets/{_Du4JDj5_.js → _DgqFUsuu.js} +1 -1
  120. package/dist/public/assets/_DgqFUsuu.js.br +0 -0
  121. package/dist/public/assets/{_B0h_gNbx.js → _Dh1jAyYX.js} +1 -1
  122. package/dist/public/assets/_Dh1jAyYX.js.br +0 -0
  123. package/dist/public/assets/{_UgQ3KVU7.js → _DngVnDg5.js} +1 -1
  124. package/dist/public/assets/_DngVnDg5.js.br +0 -0
  125. package/dist/public/assets/{_DeMAtQzd.js → _Dnrrwurs.js} +1 -1
  126. package/dist/public/assets/_Dnrrwurs.js.br +0 -0
  127. package/dist/public/assets/{_DPDIk03W2.js → _DoN89b6G2.js} +1 -1
  128. package/dist/public/assets/_DoN89b6G2.js.br +0 -0
  129. package/dist/public/assets/{_B_OHURbh2.js → _DswD_yru2.js} +1 -1
  130. package/dist/public/assets/_DswD_yru2.js.br +0 -0
  131. package/dist/public/assets/{_COmAhQWL.js → _DynCQhpp.js} +1 -1
  132. package/dist/public/assets/_DynCQhpp.js.br +0 -0
  133. package/dist/public/assets/{_DtE0g-o1.js → _EA4z6Ocg.js} +1 -1
  134. package/dist/public/assets/_EA4z6Ocg.js.br +0 -0
  135. package/dist/public/assets/{_BYrRgI_G.js → _Mggy21Fi.js} +1 -1
  136. package/dist/public/assets/_Mggy21Fi.js.br +0 -0
  137. package/dist/public/assets/{_B5mDTFiX.js → _QfF14R_b.js} +1 -1
  138. package/dist/public/assets/_QfF14R_b.js.br +0 -0
  139. package/dist/public/assets/{__DTZmGl8.js → _T__665hy.js} +1 -1
  140. package/dist/public/assets/_T__665hy.js.br +0 -0
  141. package/dist/public/assets/{_DToVjT46.js → _UJESRnqQ.js} +1 -1
  142. package/dist/public/assets/_UJESRnqQ.js.br +0 -0
  143. package/dist/public/assets/{_CS1j3Hb2.js → _YuhWYT9L.js} +1 -1
  144. package/dist/public/assets/_YuhWYT9L.js.br +0 -0
  145. package/dist/public/assets/{_BZK8bxUc.js → _YxDFo3_J.js} +1 -1
  146. package/dist/public/assets/_YxDFo3_J.js.br +0 -0
  147. package/dist/public/assets/{_BEEgRpqj.js → _bYuFEVvN.js} +1 -1
  148. package/dist/public/assets/_bYuFEVvN.js.br +0 -0
  149. package/dist/public/assets/{_D2vmNrtA.js → _mn9C4e-7.js} +1 -1
  150. package/dist/public/assets/_mn9C4e-7.js.br +0 -0
  151. package/dist/public/assets/{_BUteNftu.js → _srUTg489.js} +1 -1
  152. package/dist/public/assets/_srUTg489.js.br +0 -0
  153. package/dist/public/assets/{_CXSLFN1Q2.js → _ynuKZgua2.js} +1 -1
  154. package/dist/public/assets/_ynuKZgua2.js.br +0 -0
  155. package/dist/public/assets/{app-page-Bv2S_Tip.css → app-page-B0jusuKZ.css} +1 -1
  156. package/dist/public/assets/app-page-B0jusuKZ.css.br +0 -0
  157. package/dist/public/assets/diff-view-CXzNOw49.css +1 -0
  158. package/dist/public/assets/diff-view-CXzNOw49.css.br +0 -0
  159. package/dist/public/assets/new-DEf08lXy.js +1 -0
  160. package/dist/public/assets/settings-DEf08lXy.js +1 -0
  161. package/dist/public/assets/w-DEf08lXy.js +1 -0
  162. package/dist/public/assets/w._-C3AKQvSa.js +1 -0
  163. package/dist/public/assets/w._-DEf08lXy.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/_xZfpJdQE.js +0 -2
  169. package/dist/public/assets/_9aJgLz6G2.js.br +0 -0
  170. package/dist/public/assets/_AeL166wR2.js.br +0 -0
  171. package/dist/public/assets/_B0h_gNbx.js.br +0 -0
  172. package/dist/public/assets/_B3VIvUTs2.js.br +0 -0
  173. package/dist/public/assets/_B3wjms9s2.js.br +0 -0
  174. package/dist/public/assets/_B5mDTFiX.js.br +0 -0
  175. package/dist/public/assets/_BEEgRpqj.js.br +0 -0
  176. package/dist/public/assets/_BFfBgn79.js.br +0 -0
  177. package/dist/public/assets/_BKRausc72.js.br +0 -0
  178. package/dist/public/assets/_BPH6VN8T2.js.br +0 -0
  179. package/dist/public/assets/_BPW0wOMI2.js.br +0 -0
  180. package/dist/public/assets/_BPmmCSLY.js.br +0 -0
  181. package/dist/public/assets/_BR_6IGfk.js.br +0 -0
  182. package/dist/public/assets/_BUeHx4zC2.js.br +0 -0
  183. package/dist/public/assets/_BUteNftu.js.br +0 -0
  184. package/dist/public/assets/_BYrRgI_G.js.br +0 -0
  185. package/dist/public/assets/_BZK8bxUc.js.br +0 -0
  186. package/dist/public/assets/_BZNMfrgb.js.br +0 -0
  187. package/dist/public/assets/_B_OHURbh2.js.br +0 -0
  188. package/dist/public/assets/_Bd-WyHvg.js.br +0 -0
  189. package/dist/public/assets/_BejV602Y.js.br +0 -0
  190. package/dist/public/assets/_BltuSqY6.js.br +0 -0
  191. package/dist/public/assets/_Bn6Hn06S.js.br +0 -0
  192. package/dist/public/assets/_BqfMkwYX.js.br +0 -0
  193. package/dist/public/assets/_BztBrnhM.js.br +0 -0
  194. package/dist/public/assets/_C-wce-RA.js.br +0 -0
  195. package/dist/public/assets/_C6jdreqj.js.br +0 -0
  196. package/dist/public/assets/_CGVLhh2k.js.br +0 -0
  197. package/dist/public/assets/_CLGWdoAJ.js.br +0 -0
  198. package/dist/public/assets/_COmAhQWL.js.br +0 -0
  199. package/dist/public/assets/_CS1j3Hb2.js.br +0 -0
  200. package/dist/public/assets/_CTdFL4wj.js.br +0 -0
  201. package/dist/public/assets/_CWVKprFe2.js.br +0 -0
  202. package/dist/public/assets/_CXSLFN1Q2.js.br +0 -0
  203. package/dist/public/assets/_C_9OeUA2.js.br +0 -0
  204. package/dist/public/assets/_CbgTB_-a.js.br +0 -0
  205. package/dist/public/assets/_CdHoxy6P.js.br +0 -0
  206. package/dist/public/assets/_CeVU1yJL.js.br +0 -0
  207. package/dist/public/assets/_CgENI0zF2.js.br +0 -0
  208. package/dist/public/assets/_CgwQS6LN2.js.br +0 -0
  209. package/dist/public/assets/_CiElF6r4.js.br +0 -0
  210. package/dist/public/assets/_Cn0DEemU.js.br +0 -0
  211. package/dist/public/assets/_CpnXaqgd2.js.br +0 -0
  212. package/dist/public/assets/_CvMvIBy42.js.br +0 -0
  213. package/dist/public/assets/_CzxBqeDO.js.br +0 -0
  214. package/dist/public/assets/_D2vmNrtA.js.br +0 -0
  215. package/dist/public/assets/_DAun3Lt7.js.br +0 -0
  216. package/dist/public/assets/_DNdrj0-M2.js.br +0 -0
  217. package/dist/public/assets/_DPDIk03W2.js.br +0 -0
  218. package/dist/public/assets/_DSg_vc6H2.js.br +0 -0
  219. package/dist/public/assets/_DTSUTR0_2.js.br +0 -0
  220. package/dist/public/assets/_DToVjT46.js.br +0 -0
  221. package/dist/public/assets/_DWdZCyBL2.js.br +0 -0
  222. package/dist/public/assets/_DXCHOWYL.js.br +0 -0
  223. package/dist/public/assets/_DZss8Wq02.js.br +0 -0
  224. package/dist/public/assets/_DbEPPsY1.js.br +0 -0
  225. package/dist/public/assets/_Dbf-xJ6k.js.br +0 -0
  226. package/dist/public/assets/_Dcav7tzL.js.br +0 -0
  227. package/dist/public/assets/_DeMAtQzd.js.br +0 -0
  228. package/dist/public/assets/_Dp1TBNWN.js.br +0 -0
  229. package/dist/public/assets/_DqBuHT5A.js.br +0 -0
  230. package/dist/public/assets/_DtE0g-o1.js.br +0 -0
  231. package/dist/public/assets/_Du4JDj5_.js.br +0 -0
  232. package/dist/public/assets/_DwNswmCf.js.br +0 -0
  233. package/dist/public/assets/_Ek1T1FMw.js.br +0 -0
  234. package/dist/public/assets/_HO-Qb8pn.js.br +0 -0
  235. package/dist/public/assets/_Lot9I7Im.js.br +0 -0
  236. package/dist/public/assets/_QQHXmR7d.js.br +0 -0
  237. package/dist/public/assets/_USmNZPRb.js.br +0 -0
  238. package/dist/public/assets/_UgQ3KVU7.js.br +0 -0
  239. package/dist/public/assets/__DTZmGl8.js.br +0 -0
  240. package/dist/public/assets/_dAXKSDYL.js.br +0 -0
  241. package/dist/public/assets/_uyFxI_FF2.js.br +0 -0
  242. package/dist/public/assets/app-page-Bv2S_Tip.css.br +0 -0
  243. package/dist/public/assets/diff-view-rtQ04a8n.css +0 -1
  244. package/dist/public/assets/diff-view-rtQ04a8n.css.br +0 -0
  245. package/dist/public/assets/new-ECSvnCXB.js +0 -1
  246. package/dist/public/assets/settings-ECSvnCXB.js +0 -1
  247. package/dist/public/assets/w-ECSvnCXB.js +0 -1
  248. package/dist/public/assets/w._-CEJj3Icr.js +0 -1
  249. package/dist/public/assets/w._-ECSvnCXB.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,49 +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.agent_message === "string" && p.agent_message || "";
6336
- if (text) return cleanSummary(text);
6337
- if (typeof p.notification_type === "string") return NOTIFICATION_LABELS[p.notification_type];
6338
- if (p.hook_event_name === "PermissionRequest") {
6339
- const input = p.tool_input;
6340
- const what = (input && typeof input.command === "string" ? input.command : "") || (typeof p.tool_name === "string" ? p.tool_name : "");
6341
- return what ? cleanSummary(`Approve: ${what}`) : "Waiting for permission";
6342
- }
6343
- }
6344
- var NOTIFICATION_LABELS = {
6345
- permission_prompt: "Waiting for permission",
6346
- idle_prompt: "Waiting for your reply",
6347
- agent_needs_input: "Waiting for input",
6348
- elicitation_dialog: "Asking a question",
6349
- elicitation_url_dialog: "Needs a browser sign-in"
6350
- };
6351
- /** Notifications that mean the agent is nudging about an idle session
6352
- * rather than blocking on one. The hook cannot tell them apart when it
6353
- * fires — it reports every notification the same way — so the kind has to
6354
- * come out of the payload, or a session that merely sat there reads as
6355
- * stuck and paints the nav red. */
6356
- var NUDGE_NOTIFICATIONS = /* @__PURE__ */ new Set(["idle_prompt"]);
6357
- /** The reason a hook's `waiting` actually carries, given its payload. */
6358
- function reasonFromHookPayload(payload, reported) {
6359
- if (reported !== "attention" || !payload || typeof payload !== "object") return reported;
6360
- const kind = payload.notification_type;
6361
- return typeof kind === "string" && NUDGE_NOTIFICATIONS.has(kind) ? "done" : reported;
6362
- }
6363
- /** First meaningful line(s) of an agent message, markdown chrome stripped,
6364
- * sized for a push notification. */
6365
- function cleanSummary(text) {
6366
- 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();
6367
- if (!flat) return void 0;
6368
- return flat.length > 180 ? `${flat.slice(0, 177)}…` : flat;
6369
- }
6370
6342
  function setAgentPhase(sessionId, phase, reason, summary) {
6371
6343
  if (phase === "waiting" && reason === "done") {
6372
6344
  if (summary) summaries.set(sessionId, summary);
@@ -6448,38 +6420,595 @@ function publishChange(sessionId) {
6448
6420
  const row = get("SELECT workspace_id FROM sessions WHERE id = ?", sessionId);
6449
6421
  if (row?.workspace_id) {
6450
6422
  const workspaceId = row.workspace_id;
6451
- import("./_xZfpJdQE.js").then((m) => m.notifySessionsChanged(workspaceId));
6423
+ import("./_wbad_ZHd.js").then((m) => m.notifySessionsChanged(workspaceId));
6452
6424
  return;
6453
6425
  }
6454
6426
  publish("sessions.changed");
6455
6427
  }
6456
6428
  //#endregion
6457
- //#region src/server/agent-flags.ts
6458
- /** Agent-state reporting via launch-time flags nothing is written to the
6459
- * workspace or the user's global config. Claude takes an inline settings
6460
- * JSON (--settings accepts a JSON string); Codex takes lifecycle hooks via
6461
- * `-c` overrides, trusted through a `hooks.state` entry in the same
6462
- * override layer (see codexHookFlags). Grok, Cursor and Copilot expose no
6463
- * file-free hook mechanism, so their sessions get working/idle from pane
6464
- * activity (see scraper.ts) and nothing else — a stopped turn and a
6465
- * 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). */
6466
6434
  function stateCurl(state, reason) {
6467
6435
  const qs = `session=$WORK_SESSION_ID&state=${state}` + (reason ? `&reason=${reason}` : "");
6468
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`;
6469
6437
  }
6470
- /** Whether the agent says when it *starts* working, not just when it stops.
6471
- * Claude and Codex announce it through hooks; believing the screen over that
6472
- * costs them the state their own hook just reported. */
6473
- function reportsWorking(agent) {
6474
- const family = agentFamily(agent);
6475
- return family === "claude" || family === "codex";
6476
- }
6477
- /** Codex hook events → the state each reports. Same shape as Claude's:
6478
- * a prompt or a tool call means working, Stop means the turn is done,
6479
- * PermissionRequest means it is about to ask (fires just before the
6480
- * approval prompt; PostToolUse clears it once the approved call has run,
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;
6451
+ }
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,
6481
7010
  * or the next PreToolUse does if a reviewer auto-decided). */
6482
- var CODEX_HOOKS = [
7011
+ var HOOKS$2 = claudeStyleHooks([
6483
7012
  ["UserPromptSubmit", "working"],
6484
7013
  ["PreToolUse", "working"],
6485
7014
  ["PostToolUse", "working"],
@@ -6493,7 +7022,7 @@ var CODEX_HOOKS = [
6493
7022
  "waiting",
6494
7023
  "done"
6495
7024
  ]
6496
- ];
7025
+ ]);
6497
7026
  /** Codex only runs hooks it trusts. Trust is a hash of the normalized hook
6498
7027
  * kept in config (`hooks.state.<key>.trusted_hash`), and both the hooks and
6499
7028
  * that state are read from the `-c` override layer too — so the launch flags
@@ -6501,10 +7030,10 @@ var CODEX_HOOKS = [
6501
7030
  * Mirrors codex-rs/hooks/src/engine/discovery.rs (hook_hash, hook_key,
6502
7031
  * normalized Command config: timeout defaults to 600, async false, None
6503
7032
  * fields omitted) and codex-rs/config/src/fingerprint.rs (sha256 of
6504
- * key-sorted JSON). Verified against real trusted_hash entries. */
7033
+ * key-sorted JSON). scripts/smoke.mjs pins it against a hash Codex wrote. */
6505
7034
  function codexHookTrustHash(event, command) {
6506
7035
  const identity = {
6507
- event_name: codexHookKeyLabel(event),
7036
+ event_name: hookKeyLabel(event),
6508
7037
  hooks: [{
6509
7038
  async: false,
6510
7039
  command,
@@ -6515,46 +7044,610 @@ function codexHookTrustHash(event, command) {
6515
7044
  return "sha256:" + createHash("sha256").update(JSON.stringify(identity)).digest("hex");
6516
7045
  }
6517
7046
  /** `PermissionRequest` → `permission_request` (codex-rs/hooks/src/lib.rs). */
6518
- function codexHookKeyLabel(event) {
7047
+ function hookKeyLabel(event) {
6519
7048
  return event.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
6520
7049
  }
6521
7050
  /** Synthetic path Codex gives the `-c` override layer when keying hook state. */
6522
- var CODEX_SESSION_FLAGS_PATH = "/<session-flags>/config.toml";
6523
- /** `-c` overrides that install work's state hooks in a Codex session. */
6524
- function codexHookFlags() {
7051
+ var SESSION_FLAGS_PATH = "/<session-flags>/config.toml";
7052
+ /** `-c` overrides that install work's state hooks and trust them. */
7053
+ function launchFlags() {
6525
7054
  const flags = [];
6526
7055
  const state = [];
6527
- for (const [event, st, reason] of CODEX_HOOKS) {
6528
- const command = stateCurl(st, reason);
7056
+ for (const [event, groups] of Object.entries(HOOKS$2)) {
7057
+ const command = groups[0].hooks[0].command;
6529
7058
  flags.push("-c", `hooks.${event}=[{hooks=[{type="command",command=${JSON.stringify(command)}}]}]`);
6530
- const key = `${CODEX_SESSION_FLAGS_PATH}:${codexHookKeyLabel(event)}:0:0`;
7059
+ const key = `${SESSION_FLAGS_PATH}:${hookKeyLabel(event)}:0:0`;
6531
7060
  state.push(`${JSON.stringify(key)}={trusted_hash=${JSON.stringify(codexHookTrustHash(event, command))}}`);
6532
7061
  }
6533
7062
  flags.push("-c", `hooks.state={${state.join(",")}}`);
6534
7063
  return flags;
6535
7064
  }
6536
- /** Family of an instance id (`claude2` → `claude`); flags and hooks are
6537
- * per family, every numbered copy behaves the same. */
6538
- function agentFamily(agent) {
6539
- return agent ? parseAgentId(agent)?.family.id : void 0;
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;
7094
+ try {
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 });
7156
+ try {
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();
7161
+ }
7162
+ } catch {
7163
+ return null;
7164
+ }
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;
7215
+ /** Copilot hook events (Claude-format names, which make it emit Claude's
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([
7220
+ ["UserPromptSubmit", "working"],
7221
+ ["PreToolUse", "working"],
7222
+ ["PostToolUse", "working"],
7223
+ [
7224
+ "Notification",
7225
+ "waiting",
7226
+ "attention"
7227
+ ],
7228
+ [
7229
+ "Stop",
7230
+ "waiting",
7231
+ "done"
7232
+ ]
7233
+ ]);
7234
+ /** Copilot has no flag or env for inline hooks; the file-free option is a
7235
+ * plugin directory passed with `--plugin-dir`, which loads without a trust
7236
+ * prompt. The plugin is two files kept under work's data dir and rewritten
7237
+ * only when their content changes (a stale hook would report to the wrong
7238
+ * port after a config change). */
7239
+ function pluginDir() {
7240
+ const dir = path.join(config.dataDir, "copilot-plugin");
7241
+ writeIfChanged(path.join(dir, ".claude-plugin", "plugin.json"), JSON.stringify({
7242
+ name: "work-session-state",
7243
+ version: "1",
7244
+ description: "Reports this session's working / needs-input / done state to work."
7245
+ }, null, 2));
7246
+ writeIfChanged(path.join(dir, "hooks", "hooks.json"), JSON.stringify({ hooks: HOOKS$1 }, null, 2));
7247
+ return dir;
7248
+ }
7249
+ function writeIfChanged(file, content) {
7250
+ try {
7251
+ if (fs.readFileSync(file, "utf8") === content) return;
7252
+ } catch {}
7253
+ fs.mkdirSync(path.dirname(file), { recursive: true });
7254
+ fs.writeFileSync(file, content);
7255
+ }
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([
7530
+ ["UserPromptSubmit", "working"],
7531
+ ["PreToolUse", "working"],
7532
+ ["PostToolUse", "working"],
7533
+ [
7534
+ "Notification",
7535
+ "waiting",
7536
+ "attention",
7537
+ "permission_prompt|elicitation_dialog"
7538
+ ],
7539
+ [
7540
+ "Stop",
7541
+ "waiting",
7542
+ "done"
7543
+ ],
7544
+ [
7545
+ "StopCancelled",
7546
+ "waiting",
7547
+ "done"
7548
+ ],
7549
+ [
7550
+ "StopFailure",
7551
+ "waiting",
7552
+ "attention"
7553
+ ]
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;
7582
+ }
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);
7590
+ }
7591
+ return read;
7592
+ }
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;
6540
7621
  }
6541
7622
  /** Extra argv for an agent CLI so it reports working/waiting state. */
6542
- function agentLaunchFlags(agent) {
6543
- const family = agentFamily(agent);
6544
- if (family === "claude") {
6545
- const hook = (state, reason) => [{ hooks: [{
6546
- type: "command",
6547
- command: stateCurl(state, reason)
6548
- }] }];
6549
- return ["--settings", JSON.stringify({ hooks: {
6550
- UserPromptSubmit: hook("working"),
6551
- PreToolUse: hook("working"),
6552
- Stop: hook("waiting", "done"),
6553
- Notification: hook("waiting", "attention")
6554
- } })];
6555
- }
6556
- if (family === "codex") return codexHookFlags();
6557
- 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 };
6558
7651
  }
6559
7652
  //#endregion
6560
7653
  //#region src/server/sessions.ts
@@ -6647,7 +7740,7 @@ async function sendToSession(id, text, size) {
6647
7740
  "-t",
6648
7741
  name
6649
7742
  ], env).then(() => true, () => false)) {
6650
- if (agentFamily(session.agent) === "grok") {
7743
+ if (promptAsArgv(session.agent)) {
6651
7744
  await tmux(newSessionArgs(session, wsPath, "-d", text, size), env);
6652
7745
  markTmuxStarted(id);
6653
7746
  return;
@@ -6856,17 +7949,14 @@ function shellQuote(word) {
6856
7949
  * inherit this env, and a service manager's PATH is usually minimal. */
6857
7950
  function cleanEnv() {
6858
7951
  const env = {};
7952
+ const agentPrefix = new RegExp(`^(${agentEnvPrefixes().join("|")})`);
6859
7953
  for (const [key, value] of Object.entries(process.env)) {
6860
7954
  if (value === void 0) continue;
6861
- if (/^(CLAUDE|CLAUDECODE|AI_AGENT|CODEX_|GROK_|COPILOT_)/.test(key)) continue;
7955
+ if (agentPrefix.test(key)) continue;
6862
7956
  env[key] = value;
6863
7957
  }
6864
7958
  const home = process.env.HOME || "";
6865
- const bins = [
6866
- `${home}/.local/bin`,
6867
- `${home}/.grok/bin`,
6868
- `${home}/.codex/bin`
6869
- ];
7959
+ const bins = [`${home}/.local/bin`, ...agentPathDirs(home)];
6870
7960
  const parts = (env.PATH || "").split(":");
6871
7961
  env.PATH = [...parts, ...bins.filter((bin) => !parts.includes(bin))].join(":");
6872
7962
  if (!/utf-?8/i.test(env.LC_ALL || env.LC_CTYPE || env.LANG || "")) {
@@ -6903,4 +7993,4 @@ function sleep(ms) {
6903
7993
  return new Promise((resolve) => setTimeout(resolve, ms));
6904
7994
  }
6905
7995
  //#endregion
6906
- export { get as $, subscribe as A, setPref as B, setScrapedPhase as C, pushPublicKey as D, deleteSubscription as E, disabledAgents as F, setupPath as G, doctor as H, enabledAgents as I, TOOLS as J, which as K, getSetting as L, subscriberJoins as M, agentChoices as N, saveSubscription as O, allPrefs as P, db as Q, preferredAgent as R, setAgentPhase as S, summaryFromHookPayload as T, ghKnownHosts as U, setSetting as V, miseVersions as W, toolById as X, agentById as Y, all as Z, tmuxName as _, ensureTmuxServer as a, tmux as at, clearAgentPhase as b, killWorkspaceSessions as c, newSessionArgs as d, workspacePath as et, notifySessionsChanged as f, sessionStates as g, sendToSession as h, disposeSessions as i, run$1 as it, subscriberCount as j, publish as k, listSessions as l, renameSession as m, clearTmuxStarted as n, gh as nt, getSession as o, config as ot, panePids as p, DEFAULT_GITHUB_HOST as q, createSession as r, git as rt, killSession as s, cleanEnv as t, TMUX_SOCKET as tt, markTmuxStarted as u, tmuxWasStarted as v, setSessionViewers as w, reasonFromHookPayload as x, reportsWorking as y, setDisabledAgents 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 };