@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.
- package/dist/{_DgDsZuoQ.js → _BDyG-m5v.js} +1248 -158
- package/dist/{_8t6K8Jk8.js → _a4E7JVTq.js} +65 -65
- package/dist/{_D3qriis-.js → _hclnI8dX.js} +2 -2
- package/dist/_wbad_ZHd.js +2 -0
- package/dist/index.mjs +155 -915
- package/dist/public/assets/{_DSg_vc6H2.js → _29x9Ht-Q2.js} +1 -1
- package/dist/public/assets/_29x9Ht-Q2.js.br +0 -0
- package/dist/public/assets/{_BejV602Y.js → _A-SnIf68.js} +1 -1
- package/dist/public/assets/_A-SnIf68.js.br +0 -0
- package/dist/public/assets/{_AeL166wR2.js → _B-PT04Ok2.js} +1 -1
- package/dist/public/assets/_B-PT04Ok2.js.br +0 -0
- package/dist/public/assets/{_B3wjms9s2.js → _BGsHhxfZ2.js} +1 -1
- package/dist/public/assets/_BGsHhxfZ2.js.br +0 -0
- package/dist/public/assets/{_BZNMfrgb.js → _BN-ThWaS.js} +1 -1
- package/dist/public/assets/_BN-ThWaS.js.br +0 -0
- package/dist/public/assets/{_DZss8Wq02.js → _BTjgy5c62.js} +1 -1
- package/dist/public/assets/_BTjgy5c62.js.br +0 -0
- package/dist/public/assets/{_Dp1TBNWN.js → _BU_ZD020.js} +1 -1
- package/dist/public/assets/_BU_ZD020.js.br +0 -0
- package/dist/public/assets/{_Cn0DEemU.js → _BUc_Vl5e.js} +1 -1
- package/dist/public/assets/_BUc_Vl5e.js.br +0 -0
- package/dist/public/assets/{_C-wce-RA.js → _BYAOEh2-.js} +1 -1
- package/dist/public/assets/_BYAOEh2-.js.br +0 -0
- package/dist/public/assets/{_CiElF6r4.js → _BhRT35mT.js} +20 -20
- package/dist/public/assets/_BhRT35mT.js.br +0 -0
- package/dist/public/assets/{_Dbf-xJ6k.js → _BmDB0rQV.js} +1 -1
- package/dist/public/assets/_BmDB0rQV.js.br +0 -0
- package/dist/public/assets/{_BFfBgn79.js → _Bpr7lRdd.js} +1 -1
- package/dist/public/assets/_Bpr7lRdd.js.br +0 -0
- package/dist/public/assets/{_CGVLhh2k.js → _Bud2gpcs.js} +1 -1
- package/dist/public/assets/_Bud2gpcs.js.br +0 -0
- package/dist/public/assets/{_Lot9I7Im.js → _Bx-jImeN.js} +1 -1
- package/dist/public/assets/_Bx-jImeN.js.br +0 -0
- package/dist/public/assets/{_USmNZPRb.js → _ByMM8N1r.js} +1 -1
- package/dist/public/assets/_ByMM8N1r.js.br +0 -0
- package/dist/public/assets/{_CgwQS6LN2.js → _C-JDWYNo2.js} +1 -1
- package/dist/public/assets/_C-JDWYNo2.js.br +0 -0
- package/dist/public/assets/{_dAXKSDYL.js → _C-ifLHsW.js} +1 -1
- package/dist/public/assets/_C-ifLHsW.js.br +0 -0
- package/dist/public/assets/{_BPmmCSLY.js → _C0xjFWIM.js} +1 -1
- package/dist/public/assets/_C0xjFWIM.js.br +0 -0
- package/dist/public/assets/{_HO-Qb8pn.js → _C1RzE6Mp.js} +1 -1
- package/dist/public/assets/_C1RzE6Mp.js.br +0 -0
- package/dist/public/assets/{_Ek1T1FMw.js → _C6Eavzmt.js} +1 -1
- package/dist/public/assets/_C6Eavzmt.js.br +0 -0
- package/dist/public/assets/{_CWVKprFe2.js → _C6iBocvM2.js} +1 -1
- package/dist/public/assets/_C6iBocvM2.js.br +0 -0
- package/dist/public/assets/{_DAun3Lt7.js → _C7CzRI13.js} +1 -1
- package/dist/public/assets/_C7CzRI13.js.br +0 -0
- package/dist/public/assets/{_uyFxI_FF2.js → _C9CdATEd2.js} +1 -1
- package/dist/public/assets/_C9CdATEd2.js.br +0 -0
- package/dist/public/assets/{_BqrygBOe.js → _CE-w6G87.js} +1 -1
- package/dist/public/assets/{_CzxBqeDO.js → _CHhO4fFL.js} +1 -1
- package/dist/public/assets/_CHhO4fFL.js.br +0 -0
- package/dist/public/assets/{_Bn6Hn06S.js → _CIkNdjAJ.js} +1 -1
- package/dist/public/assets/_CIkNdjAJ.js.br +0 -0
- package/dist/public/assets/{_Bd-WyHvg.js → _CJDpwABS.js} +1 -1
- package/dist/public/assets/_CJDpwABS.js.br +0 -0
- package/dist/public/assets/{_B3VIvUTs2.js → _CKSRE8zv2.js} +1 -1
- package/dist/public/assets/_CKSRE8zv2.js.br +0 -0
- package/dist/public/assets/{_CTdFL4wj.js → _CMkaOgq5.js} +1 -1
- package/dist/public/assets/_CMkaOgq5.js.br +0 -0
- package/dist/public/assets/{_DWdZCyBL2.js → _CQ_0JcVs2.js} +1 -1
- package/dist/public/assets/_CQ_0JcVs2.js.br +0 -0
- package/dist/public/assets/{_Dcav7tzL.js → _CQeNDnCp.js} +6 -6
- package/dist/public/assets/_CQeNDnCp.js.br +0 -0
- package/dist/public/assets/{_CdHoxy6P.js → _CSfPA_mo.js} +1 -1
- package/dist/public/assets/_CSfPA_mo.js.br +0 -0
- package/dist/public/assets/{_BztBrnhM.js → _CYjQ_rQU.js} +1 -1
- package/dist/public/assets/_CYjQ_rQU.js.br +0 -0
- package/dist/public/assets/{_BPH6VN8T2.js → _CdB7VHwX2.js} +1 -1
- package/dist/public/assets/_CdB7VHwX2.js.br +0 -0
- package/dist/public/assets/{_C6jdreqj.js → _CdWuqeZK.js} +1 -1
- package/dist/public/assets/_CdWuqeZK.js.br +0 -0
- package/dist/public/assets/{_CgENI0zF2.js → _Ce-R03cZ2.js} +1 -1
- package/dist/public/assets/_Ce-R03cZ2.js.br +0 -0
- package/dist/public/assets/{_BltuSqY6.js → _Ce2B5H6R.js} +1 -1
- package/dist/public/assets/_Ce2B5H6R.js.br +0 -0
- package/dist/public/assets/{_BKRausc72.js → _ChkPwLnS2.js} +1 -1
- package/dist/public/assets/_ChkPwLnS2.js.br +0 -0
- package/dist/public/assets/{_BqfMkwYX.js → _CiYb_Bp1.js} +1 -1
- package/dist/public/assets/_CiYb_Bp1.js.br +0 -0
- package/dist/public/assets/{_CLGWdoAJ.js → _CkABEOAv.js} +1 -1
- package/dist/public/assets/_CkABEOAv.js.br +0 -0
- package/dist/public/assets/{_BPW0wOMI2.js → _CkNCn2942.js} +1 -1
- package/dist/public/assets/_CkNCn2942.js.br +0 -0
- package/dist/public/assets/{_DwNswmCf.js → _CnaMcooO.js} +1 -1
- package/dist/public/assets/_CnaMcooO.js.br +0 -0
- package/dist/public/assets/{_C_9OeUA2.js → _CpByMJ40.js} +1 -1
- package/dist/public/assets/_CpByMJ40.js.br +0 -0
- package/dist/public/assets/{_DTSUTR0_2.js → _CtowbQCb2.js} +1 -1
- package/dist/public/assets/_CtowbQCb2.js.br +0 -0
- package/dist/public/assets/{_DBiZo1op2.js → _D-P8DqvK2.js} +1 -1
- package/dist/public/assets/{_DbEPPsY1.js → _D6MZcQKP.js} +1 -1
- package/dist/public/assets/_D6MZcQKP.js.br +0 -0
- package/dist/public/assets/{_9aJgLz6G2.js → _D7mRLhwX2.js} +1 -1
- package/dist/public/assets/_D7mRLhwX2.js.br +0 -0
- package/dist/public/assets/{_DNdrj0-M2.js → _D99GO9JW2.js} +1 -1
- package/dist/public/assets/_D99GO9JW2.js.br +0 -0
- package/dist/public/assets/{_DqBuHT5A.js → _DC8LVwVY.js} +1 -1
- package/dist/public/assets/_DC8LVwVY.js.br +0 -0
- package/dist/public/assets/{_BUeHx4zC2.js → _DESY84xu2.js} +1 -1
- package/dist/public/assets/_DESY84xu2.js.br +0 -0
- package/dist/public/assets/{_CvMvIBy42.js → _DJARroah2.js} +1 -1
- package/dist/public/assets/_DJARroah2.js.br +0 -0
- package/dist/public/assets/{_CpnXaqgd2.js → _DK6p9z1j2.js} +1 -1
- package/dist/public/assets/_DK6p9z1j2.js.br +0 -0
- package/dist/public/assets/{_CeVU1yJL.js → _DKHAhfGs.js} +1 -1
- package/dist/public/assets/_DKHAhfGs.js.br +0 -0
- package/dist/public/assets/{_CbgTB_-a.js → _DQSUDhad.js} +1 -1
- package/dist/public/assets/_DQSUDhad.js.br +0 -0
- package/dist/public/assets/{_C-LtCarQ2.js → _DSWOPuxC2.js} +1 -1
- package/dist/public/assets/{_BR_6IGfk.js → _DSiaWqg3.js} +1 -1
- package/dist/public/assets/_DSiaWqg3.js.br +0 -0
- package/dist/public/assets/{_DXCHOWYL.js → _DcXafbQI.js} +1 -1
- package/dist/public/assets/_DcXafbQI.js.br +0 -0
- package/dist/public/assets/{_QQHXmR7d.js → _DcfF8nxm.js} +1 -1
- package/dist/public/assets/_DcfF8nxm.js.br +0 -0
- package/dist/public/assets/{_Du4JDj5_.js → _DgqFUsuu.js} +1 -1
- package/dist/public/assets/_DgqFUsuu.js.br +0 -0
- package/dist/public/assets/{_B0h_gNbx.js → _Dh1jAyYX.js} +1 -1
- package/dist/public/assets/_Dh1jAyYX.js.br +0 -0
- package/dist/public/assets/{_UgQ3KVU7.js → _DngVnDg5.js} +1 -1
- package/dist/public/assets/_DngVnDg5.js.br +0 -0
- package/dist/public/assets/{_DeMAtQzd.js → _Dnrrwurs.js} +1 -1
- package/dist/public/assets/_Dnrrwurs.js.br +0 -0
- package/dist/public/assets/{_DPDIk03W2.js → _DoN89b6G2.js} +1 -1
- package/dist/public/assets/_DoN89b6G2.js.br +0 -0
- package/dist/public/assets/{_B_OHURbh2.js → _DswD_yru2.js} +1 -1
- package/dist/public/assets/_DswD_yru2.js.br +0 -0
- package/dist/public/assets/{_COmAhQWL.js → _DynCQhpp.js} +1 -1
- package/dist/public/assets/_DynCQhpp.js.br +0 -0
- package/dist/public/assets/{_DtE0g-o1.js → _EA4z6Ocg.js} +1 -1
- package/dist/public/assets/_EA4z6Ocg.js.br +0 -0
- package/dist/public/assets/{_BYrRgI_G.js → _Mggy21Fi.js} +1 -1
- package/dist/public/assets/_Mggy21Fi.js.br +0 -0
- package/dist/public/assets/{_B5mDTFiX.js → _QfF14R_b.js} +1 -1
- package/dist/public/assets/_QfF14R_b.js.br +0 -0
- package/dist/public/assets/{__DTZmGl8.js → _T__665hy.js} +1 -1
- package/dist/public/assets/_T__665hy.js.br +0 -0
- package/dist/public/assets/{_DToVjT46.js → _UJESRnqQ.js} +1 -1
- package/dist/public/assets/_UJESRnqQ.js.br +0 -0
- package/dist/public/assets/{_CS1j3Hb2.js → _YuhWYT9L.js} +1 -1
- package/dist/public/assets/_YuhWYT9L.js.br +0 -0
- package/dist/public/assets/{_BZK8bxUc.js → _YxDFo3_J.js} +1 -1
- package/dist/public/assets/_YxDFo3_J.js.br +0 -0
- package/dist/public/assets/{_BEEgRpqj.js → _bYuFEVvN.js} +1 -1
- package/dist/public/assets/_bYuFEVvN.js.br +0 -0
- package/dist/public/assets/{_D2vmNrtA.js → _mn9C4e-7.js} +1 -1
- package/dist/public/assets/_mn9C4e-7.js.br +0 -0
- package/dist/public/assets/{_BUteNftu.js → _srUTg489.js} +1 -1
- package/dist/public/assets/_srUTg489.js.br +0 -0
- package/dist/public/assets/{_CXSLFN1Q2.js → _ynuKZgua2.js} +1 -1
- package/dist/public/assets/_ynuKZgua2.js.br +0 -0
- package/dist/public/assets/{app-page-Bv2S_Tip.css → app-page-B0jusuKZ.css} +1 -1
- package/dist/public/assets/app-page-B0jusuKZ.css.br +0 -0
- package/dist/public/assets/diff-view-CXzNOw49.css +1 -0
- package/dist/public/assets/diff-view-CXzNOw49.css.br +0 -0
- package/dist/public/assets/new-DEf08lXy.js +1 -0
- package/dist/public/assets/settings-DEf08lXy.js +1 -0
- package/dist/public/assets/w-DEf08lXy.js +1 -0
- package/dist/public/assets/w._-C3AKQvSa.js +1 -0
- package/dist/public/assets/w._-DEf08lXy.js +1 -0
- package/lib/setup/agents.json +66 -0
- package/lib/setup/catalog.d.mts +2 -0
- package/lib/setup/catalog.mjs +9 -10
- package/package.json +3 -3
- package/dist/_xZfpJdQE.js +0 -2
- package/dist/public/assets/_9aJgLz6G2.js.br +0 -0
- package/dist/public/assets/_AeL166wR2.js.br +0 -0
- package/dist/public/assets/_B0h_gNbx.js.br +0 -0
- package/dist/public/assets/_B3VIvUTs2.js.br +0 -0
- package/dist/public/assets/_B3wjms9s2.js.br +0 -0
- package/dist/public/assets/_B5mDTFiX.js.br +0 -0
- package/dist/public/assets/_BEEgRpqj.js.br +0 -0
- package/dist/public/assets/_BFfBgn79.js.br +0 -0
- package/dist/public/assets/_BKRausc72.js.br +0 -0
- package/dist/public/assets/_BPH6VN8T2.js.br +0 -0
- package/dist/public/assets/_BPW0wOMI2.js.br +0 -0
- package/dist/public/assets/_BPmmCSLY.js.br +0 -0
- package/dist/public/assets/_BR_6IGfk.js.br +0 -0
- package/dist/public/assets/_BUeHx4zC2.js.br +0 -0
- package/dist/public/assets/_BUteNftu.js.br +0 -0
- package/dist/public/assets/_BYrRgI_G.js.br +0 -0
- package/dist/public/assets/_BZK8bxUc.js.br +0 -0
- package/dist/public/assets/_BZNMfrgb.js.br +0 -0
- package/dist/public/assets/_B_OHURbh2.js.br +0 -0
- package/dist/public/assets/_Bd-WyHvg.js.br +0 -0
- package/dist/public/assets/_BejV602Y.js.br +0 -0
- package/dist/public/assets/_BltuSqY6.js.br +0 -0
- package/dist/public/assets/_Bn6Hn06S.js.br +0 -0
- package/dist/public/assets/_BqfMkwYX.js.br +0 -0
- package/dist/public/assets/_BztBrnhM.js.br +0 -0
- package/dist/public/assets/_C-wce-RA.js.br +0 -0
- package/dist/public/assets/_C6jdreqj.js.br +0 -0
- package/dist/public/assets/_CGVLhh2k.js.br +0 -0
- package/dist/public/assets/_CLGWdoAJ.js.br +0 -0
- package/dist/public/assets/_COmAhQWL.js.br +0 -0
- package/dist/public/assets/_CS1j3Hb2.js.br +0 -0
- package/dist/public/assets/_CTdFL4wj.js.br +0 -0
- package/dist/public/assets/_CWVKprFe2.js.br +0 -0
- package/dist/public/assets/_CXSLFN1Q2.js.br +0 -0
- package/dist/public/assets/_C_9OeUA2.js.br +0 -0
- package/dist/public/assets/_CbgTB_-a.js.br +0 -0
- package/dist/public/assets/_CdHoxy6P.js.br +0 -0
- package/dist/public/assets/_CeVU1yJL.js.br +0 -0
- package/dist/public/assets/_CgENI0zF2.js.br +0 -0
- package/dist/public/assets/_CgwQS6LN2.js.br +0 -0
- package/dist/public/assets/_CiElF6r4.js.br +0 -0
- package/dist/public/assets/_Cn0DEemU.js.br +0 -0
- package/dist/public/assets/_CpnXaqgd2.js.br +0 -0
- package/dist/public/assets/_CvMvIBy42.js.br +0 -0
- package/dist/public/assets/_CzxBqeDO.js.br +0 -0
- package/dist/public/assets/_D2vmNrtA.js.br +0 -0
- package/dist/public/assets/_DAun3Lt7.js.br +0 -0
- package/dist/public/assets/_DNdrj0-M2.js.br +0 -0
- package/dist/public/assets/_DPDIk03W2.js.br +0 -0
- package/dist/public/assets/_DSg_vc6H2.js.br +0 -0
- package/dist/public/assets/_DTSUTR0_2.js.br +0 -0
- package/dist/public/assets/_DToVjT46.js.br +0 -0
- package/dist/public/assets/_DWdZCyBL2.js.br +0 -0
- package/dist/public/assets/_DXCHOWYL.js.br +0 -0
- package/dist/public/assets/_DZss8Wq02.js.br +0 -0
- package/dist/public/assets/_DbEPPsY1.js.br +0 -0
- package/dist/public/assets/_Dbf-xJ6k.js.br +0 -0
- package/dist/public/assets/_Dcav7tzL.js.br +0 -0
- package/dist/public/assets/_DeMAtQzd.js.br +0 -0
- package/dist/public/assets/_Dp1TBNWN.js.br +0 -0
- package/dist/public/assets/_DqBuHT5A.js.br +0 -0
- package/dist/public/assets/_DtE0g-o1.js.br +0 -0
- package/dist/public/assets/_Du4JDj5_.js.br +0 -0
- package/dist/public/assets/_DwNswmCf.js.br +0 -0
- package/dist/public/assets/_Ek1T1FMw.js.br +0 -0
- package/dist/public/assets/_HO-Qb8pn.js.br +0 -0
- package/dist/public/assets/_Lot9I7Im.js.br +0 -0
- package/dist/public/assets/_QQHXmR7d.js.br +0 -0
- package/dist/public/assets/_USmNZPRb.js.br +0 -0
- package/dist/public/assets/_UgQ3KVU7.js.br +0 -0
- package/dist/public/assets/__DTZmGl8.js.br +0 -0
- package/dist/public/assets/_dAXKSDYL.js.br +0 -0
- package/dist/public/assets/_uyFxI_FF2.js.br +0 -0
- package/dist/public/assets/app-page-Bv2S_Tip.css.br +0 -0
- package/dist/public/assets/diff-view-rtQ04a8n.css +0 -1
- package/dist/public/assets/diff-view-rtQ04a8n.css.br +0 -0
- package/dist/public/assets/new-ECSvnCXB.js +0 -1
- package/dist/public/assets/settings-ECSvnCXB.js +0 -1
- package/dist/public/assets/w-ECSvnCXB.js +0 -1
- package/dist/public/assets/w._-CEJj3Icr.js +0 -1
- 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
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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("./
|
|
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/
|
|
6458
|
-
/**
|
|
6459
|
-
*
|
|
6460
|
-
*
|
|
6461
|
-
*
|
|
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
|
-
/**
|
|
6471
|
-
*
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
const
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
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
|
|
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).
|
|
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:
|
|
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
|
|
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
|
|
6523
|
-
/** `-c` overrides that install work's state hooks
|
|
6524
|
-
function
|
|
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,
|
|
6528
|
-
const command =
|
|
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 = `${
|
|
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
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
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(
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
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 (
|
|
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 (
|
|
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 {
|
|
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 };
|