@dylanpiercey/work 0.0.120 → 0.0.122
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -8
- package/bin/work.mjs +380 -41
- package/dist/_B1lVb01g.js +2 -0
- package/dist/{_DEBFGwto.js → _BPX2_wNT.js} +20 -18
- package/dist/{_BZhGz-eY.js → _BdBrOaZF.js} +95 -94
- package/dist/_Bjh5JEm7.js +2 -0
- package/dist/_CfwTkTbL.js +352 -0
- package/dist/{_DE2RUdbF.js → _Cr71Xd0d.js} +8 -3
- package/dist/_DJyHc1w-.js +1739 -0
- package/dist/_Dpa8WFuv.js +2 -0
- package/dist/index.mjs +4136 -4140
- package/dist/public/assets/{_z3QzE4ht2.js → _22Wep3Qj2.js} +1 -1
- package/dist/public/assets/{_DdYfAoJz.js → _4C4ZKftZ.js} +1 -1
- package/dist/public/assets/_4C4ZKftZ.js.br +0 -0
- package/dist/public/assets/{_h6HS-Zb_2.js → _5waGGcsH2.js} +1 -1
- package/dist/public/assets/_5waGGcsH2.js.br +0 -0
- package/dist/public/assets/{_BqNoqWIY.js → _6yLwnUth.js} +1 -1
- package/dist/public/assets/_6yLwnUth.js.br +0 -0
- package/dist/public/assets/{_BuF6uiIm.js → _8ixfpSLV.js} +1 -1
- package/dist/public/assets/_8ixfpSLV.js.br +0 -0
- package/dist/public/assets/{_BEPt7EWk.js → _9NNG2Bg-.js} +1 -1
- package/dist/public/assets/_9NNG2Bg-.js.br +0 -0
- package/dist/public/assets/{_D8lg2J6H.js → _9wp1Weya.js} +1 -1
- package/dist/public/assets/_9wp1Weya.js.br +0 -0
- package/dist/public/assets/{_BoXfCUcF.js → _B80Me7dX.js} +1 -1
- package/dist/public/assets/{_BbE6iGBd.js → _BJVUTWAN.js} +1 -1
- package/dist/public/assets/_BJVUTWAN.js.br +0 -0
- package/dist/public/assets/{_CihMY3_B2.js → _BO5eXuo12.js} +1 -1
- package/dist/public/assets/_BO5eXuo12.js.br +0 -0
- package/dist/public/assets/{_CwZCc-fE2.js → _BO6TG3u42.js} +1 -1
- package/dist/public/assets/_BO6TG3u42.js.br +0 -0
- package/dist/public/assets/{_D4V7vTBe.js → _BO_carFG.js} +1 -1
- package/dist/public/assets/_BO_carFG.js.br +0 -0
- package/dist/public/assets/{_D11ZFURg.js → _BPjnrd1A.js} +1 -1
- package/dist/public/assets/_BPjnrd1A.js.br +0 -0
- package/dist/public/assets/{_DRKLaQtq.js → _BQfrm6Hv.js} +1 -1
- package/dist/public/assets/_BQfrm6Hv.js.br +0 -0
- package/dist/public/assets/{_CwtAB8Ju2.js → _BYOrCsb12.js} +1 -1
- package/dist/public/assets/_BYOrCsb12.js.br +0 -0
- package/dist/public/assets/{_CIio55Q2.js → _BcA9iBrt.js} +1 -1
- package/dist/public/assets/_BcA9iBrt.js.br +0 -0
- package/dist/public/assets/{_Du-i6H8r.js → _BcnKn75V.js} +24 -21
- package/dist/public/assets/_BcnKn75V.js.br +0 -0
- package/dist/public/assets/{_CJ_9Hl5d.js → _BeYy7LJv.js} +1 -1
- package/dist/public/assets/_BeYy7LJv.js.br +0 -0
- package/dist/public/assets/{_DBXfsrRd.js → _BfzwZXGo.js} +1 -1
- package/dist/public/assets/_BfzwZXGo.js.br +0 -0
- package/dist/public/assets/{_BfZMmKaW.js → _Bgq0Djxf.js} +1 -1
- package/dist/public/assets/_Bgq0Djxf.js.br +0 -0
- package/dist/public/assets/{_zJkCtRhb.js → _BgxHsV2q.js} +1 -1
- package/dist/public/assets/_BgxHsV2q.js.br +0 -0
- package/dist/public/assets/{_p1VLQxPw.js → _Bhf_wksG.js} +1 -1
- package/dist/public/assets/_Bhf_wksG.js.br +0 -0
- package/dist/public/assets/{_CrPK-ZOH2.js → _Bkd9T8qO2.js} +1 -1
- package/dist/public/assets/_Bkd9T8qO2.js.br +0 -0
- package/dist/public/assets/{_CYLlpjQc.js → _BllRu2dt.js} +1 -1
- package/dist/public/assets/_BllRu2dt.js.br +0 -0
- package/dist/public/assets/{_DWctbEWg.js → _Buu8_cyc.js} +1 -1
- package/dist/public/assets/_Buu8_cyc.js.br +0 -0
- package/dist/public/assets/{_F8phZWNM2.js → _BvJHLrwP2.js} +1 -1
- package/dist/public/assets/_BvJHLrwP2.js.br +0 -0
- package/dist/public/assets/{_wc-pkVQS.js → _BvTWp2QQ.js} +1 -1
- package/dist/public/assets/_BvTWp2QQ.js.br +0 -0
- package/dist/public/assets/{_CkBv_F0f.js → _BxfzYLyp.js} +1 -1
- package/dist/public/assets/_BxfzYLyp.js.br +0 -0
- package/dist/public/assets/{_BzULaZGs.js → _Bz61WBUI.js} +1 -1
- package/dist/public/assets/_Bz61WBUI.js.br +0 -0
- package/dist/public/assets/{_Cv-4N4xQ.js → _C0Wcm1DA.js} +1 -1
- package/dist/public/assets/_C0Wcm1DA.js.br +0 -0
- package/dist/public/assets/{_BUmLRB7c2.js → _C5fkGs032.js} +1 -1
- package/dist/public/assets/_C5fkGs032.js.br +0 -0
- package/dist/public/assets/{_BEOZghNJ2.js → _CKAWtCKg2.js} +1 -1
- package/dist/public/assets/_CKAWtCKg2.js.br +0 -0
- package/dist/public/assets/{_BFgJoTOo.js → _CLM87vGj.js} +1 -1
- package/dist/public/assets/_CLM87vGj.js.br +0 -0
- package/dist/public/assets/{_B6bSumz1.js → _CM8zmLXB.js} +1 -1
- package/dist/public/assets/_CM8zmLXB.js.br +0 -0
- package/dist/public/assets/{_8hF31A2G.js → _CNBki2Aq.js} +1 -1
- package/dist/public/assets/_CNBki2Aq.js.br +0 -0
- package/dist/public/assets/{_CQ-tqbUq.js → _CNVk64eD.js} +1 -1
- package/dist/public/assets/_CNVk64eD.js.br +0 -0
- package/dist/public/assets/{_BHgE7f-_2.js → _CPCFHsPT2.js} +1 -1
- package/dist/public/assets/_CPCFHsPT2.js.br +0 -0
- package/dist/public/assets/{_BLgtdDQm.js → _CW70CETb.js} +1 -1
- package/dist/public/assets/_CW70CETb.js.br +0 -0
- package/dist/public/assets/{_BCKhXQct2.js → _CWU_IJjz2.js} +1 -1
- package/dist/public/assets/_CWU_IJjz2.js.br +0 -0
- package/dist/public/assets/{_BaiP7ogH.js → _ClA-9QLW.js} +1 -1
- package/dist/public/assets/_ClA-9QLW.js.br +0 -0
- package/dist/public/assets/{_dBVoJwVC.js → _CorgOnjQ.js} +1 -1
- package/dist/public/assets/_CorgOnjQ.js.br +0 -0
- package/dist/public/assets/{_DdnCyOPs.js → _CpByFq0N.js} +1 -1
- package/dist/public/assets/_CpByFq0N.js.br +0 -0
- package/dist/public/assets/{_fR2ilomn.js → _CqEc7KwE.js} +1 -1
- package/dist/public/assets/_CqEc7KwE.js.br +0 -0
- package/dist/public/assets/{_BtmFYBHk.js → _CqgcFjOQ.js} +1 -1
- package/dist/public/assets/_CqgcFjOQ.js.br +0 -0
- package/dist/public/assets/{_CeECfdVo2.js → _CrlXwiFN2.js} +1 -1
- package/dist/public/assets/_CrlXwiFN2.js.br +0 -0
- package/dist/public/assets/{_FEbRnVo7.js → _CtuCeJaB.js} +1 -1
- package/dist/public/assets/_CtuCeJaB.js.br +0 -0
- package/dist/public/assets/{_DfpMH3So2.js → _D1zxC16I2.js} +1 -1
- package/dist/public/assets/_D1zxC16I2.js.br +0 -0
- package/dist/public/assets/{_Cvly93xo2.js → _D2SIC9vH2.js} +1 -1
- package/dist/public/assets/_D2SIC9vH2.js.br +0 -0
- package/dist/public/assets/{_-3OmHyNU.js → _D9QYQO8L.js} +1 -1
- package/dist/public/assets/_D9QYQO8L.js.br +0 -0
- package/dist/public/assets/{_CzMjLSTK.js → _DDNq8klN.js} +1 -1
- package/dist/public/assets/_DDNq8klN.js.br +0 -0
- package/dist/public/assets/{_BIsQjQ872.js → _DL2HExUi2.js} +1 -1
- package/dist/public/assets/_DL2HExUi2.js.br +0 -0
- package/dist/public/assets/{_QuFTKxMO2.js → _DLbcflUX2.js} +1 -1
- package/dist/public/assets/_DLbcflUX2.js.br +0 -0
- package/dist/public/assets/{_N0Ea8Q_R.js → _DMzElmqz.js} +1 -1
- package/dist/public/assets/_DMzElmqz.js.br +0 -0
- package/dist/public/assets/{_cS3NYZ_K2.js → _DOa36sDf2.js} +1 -1
- package/dist/public/assets/_DOa36sDf2.js.br +0 -0
- package/dist/public/assets/{_DDag_nXJ.js → _DWZLw9u5.js} +1 -1
- package/dist/public/assets/_DWZLw9u5.js.br +0 -0
- package/dist/public/assets/{_DiaYv9e1.js → _DY5MuJqh.js} +1 -1
- package/dist/public/assets/_DY5MuJqh.js.br +0 -0
- package/dist/public/assets/{_D2xWjoK-2.js → _DZXcx3AV2.js} +1 -1
- package/dist/public/assets/_DZXcx3AV2.js.br +0 -0
- package/dist/public/assets/{_BPcKgx9L.js → _D_5rjG0o.js} +1 -1
- package/dist/public/assets/_D_5rjG0o.js.br +0 -0
- package/dist/public/assets/{_Ca811EeR.js → _DbEs3COY.js} +1 -1
- package/dist/public/assets/_DbEs3COY.js.br +0 -0
- package/dist/public/assets/{_BrTLGsp82.js → _DegNdi0y2.js} +1 -1
- package/dist/public/assets/_DegNdi0y2.js.br +0 -0
- package/dist/public/assets/{_B_MP9UES.js → _DfLxwQXd.js} +1 -1
- package/dist/public/assets/_DfLxwQXd.js.br +0 -0
- package/dist/public/assets/{_BilqQTfw.js → _DwTIenq9.js} +1 -1
- package/dist/public/assets/_DwTIenq9.js.br +0 -0
- package/dist/public/assets/{_0_ngOEbL.js → _Dwsfrcqr.js} +1 -1
- package/dist/public/assets/_Dwsfrcqr.js.br +0 -0
- package/dist/public/assets/{_uJTU0UZd2.js → _DyZPE8LD2.js} +1 -1
- package/dist/public/assets/_DyZPE8LD2.js.br +0 -0
- package/dist/public/assets/{_D2be5yUI.js → _MEclBKIH.js} +1 -1
- package/dist/public/assets/_MEclBKIH.js.br +0 -0
- package/dist/public/assets/{_DhLWIgCs.js → _Tpid4lIw.js} +1 -1
- package/dist/public/assets/_Tpid4lIw.js.br +0 -0
- package/dist/public/assets/{_cdbANa0O.js → _W9aOkr2v.js} +1 -1
- package/dist/public/assets/_W9aOkr2v.js.br +0 -0
- package/dist/public/assets/{_BDRc66NB2.js → _Y8lHXkX62.js} +1 -1
- package/dist/public/assets/_Y8lHXkX62.js.br +0 -0
- package/dist/public/assets/{_GdcmEEuJ.js → _Z4spekbX.js} +1 -1
- package/dist/public/assets/_Z4spekbX.js.br +0 -0
- package/dist/public/assets/{_CeVHIZWy2.js → _a18bdHwu2.js} +1 -1
- package/dist/public/assets/_a18bdHwu2.js.br +0 -0
- package/dist/public/assets/{_DkYHMy0q2.js → _hO1aV02w2.js} +1 -1
- package/dist/public/assets/{_DZgx_rAt.js → _ih_22Lwc.js} +1 -1
- package/dist/public/assets/_ih_22Lwc.js.br +0 -0
- package/dist/public/assets/{_CG2uZvAG.js → _kQ849tt6.js} +1 -1
- package/dist/public/assets/_kQ849tt6.js.br +0 -0
- package/dist/public/assets/{_Ces5jihM.js → _lo4uGYhv.js} +1 -1
- package/dist/public/assets/_lo4uGYhv.js.br +0 -0
- package/dist/public/assets/{_Brws7C1N.js → _sWtKlZZ5.js} +6 -6
- package/dist/public/assets/_sWtKlZZ5.js.br +0 -0
- package/dist/public/assets/{_BGHYY00K2.js → _xvmiPi6J2.js} +1 -1
- package/dist/public/assets/_xvmiPi6J2.js.br +0 -0
- package/dist/public/assets/app-page-9ukuDxSW.css +1 -0
- package/dist/public/assets/app-page-9ukuDxSW.css.br +0 -0
- package/dist/public/assets/diff-view-BmyKvz_f.css +1 -0
- package/dist/public/assets/diff-view-BmyKvz_f.css.br +0 -0
- package/dist/public/assets/new-PmY_tc2O.js +1 -0
- package/dist/public/assets/settings-PmY_tc2O.js +1 -0
- package/dist/public/assets/w-PmY_tc2O.js +1 -0
- package/dist/public/assets/w._-DL1tdfaH.js +1 -0
- package/dist/public/assets/w._-PmY_tc2O.js +1 -0
- package/lib/review/markdown.mjs +6 -2
- package/lib/setup/catalog.d.mts +3 -1
- package/lib/setup/catalog.mjs +11 -2
- package/lib/setup/doctor.mjs +96 -20
- package/lib/setup/install.d.mts +9 -3
- package/lib/setup/install.mjs +68 -39
- package/lib/share/manifest.d.mts +27 -0
- package/lib/share/manifest.mjs +144 -0
- package/package.json +6 -2
- package/scripts/migrate.mjs +101 -30
- package/dist/_BIDn_RW1.js +0 -2
- package/dist/public/assets/_-3OmHyNU.js.br +0 -0
- package/dist/public/assets/_0_ngOEbL.js.br +0 -0
- package/dist/public/assets/_8hF31A2G.js.br +0 -0
- package/dist/public/assets/_B6bSumz1.js.br +0 -0
- package/dist/public/assets/_BCKhXQct2.js.br +0 -0
- package/dist/public/assets/_BDRc66NB2.js.br +0 -0
- package/dist/public/assets/_BEOZghNJ2.js.br +0 -0
- package/dist/public/assets/_BEPt7EWk.js.br +0 -0
- package/dist/public/assets/_BFgJoTOo.js.br +0 -0
- package/dist/public/assets/_BGHYY00K2.js.br +0 -0
- package/dist/public/assets/_BHgE7f-_2.js.br +0 -0
- package/dist/public/assets/_BIsQjQ872.js.br +0 -0
- package/dist/public/assets/_BLgtdDQm.js.br +0 -0
- package/dist/public/assets/_BPcKgx9L.js.br +0 -0
- package/dist/public/assets/_BUmLRB7c2.js.br +0 -0
- package/dist/public/assets/_B_MP9UES.js.br +0 -0
- package/dist/public/assets/_BaiP7ogH.js.br +0 -0
- package/dist/public/assets/_BbE6iGBd.js.br +0 -0
- package/dist/public/assets/_BfZMmKaW.js.br +0 -0
- package/dist/public/assets/_BilqQTfw.js.br +0 -0
- package/dist/public/assets/_BqNoqWIY.js.br +0 -0
- package/dist/public/assets/_BrTLGsp82.js.br +0 -0
- package/dist/public/assets/_Brws7C1N.js.br +0 -0
- package/dist/public/assets/_BtmFYBHk.js.br +0 -0
- package/dist/public/assets/_BuF6uiIm.js.br +0 -0
- package/dist/public/assets/_BzULaZGs.js.br +0 -0
- package/dist/public/assets/_CG2uZvAG.js.br +0 -0
- package/dist/public/assets/_CIio55Q2.js.br +0 -0
- package/dist/public/assets/_CJ_9Hl5d.js.br +0 -0
- package/dist/public/assets/_CQ-tqbUq.js.br +0 -0
- package/dist/public/assets/_CYLlpjQc.js.br +0 -0
- package/dist/public/assets/_Ca811EeR.js.br +0 -0
- package/dist/public/assets/_CeECfdVo2.js.br +0 -0
- package/dist/public/assets/_CeVHIZWy2.js.br +0 -0
- package/dist/public/assets/_Ces5jihM.js.br +0 -7
- package/dist/public/assets/_CihMY3_B2.js.br +0 -0
- package/dist/public/assets/_CkBv_F0f.js.br +0 -0
- package/dist/public/assets/_CrPK-ZOH2.js.br +0 -0
- package/dist/public/assets/_Cv-4N4xQ.js.br +0 -0
- package/dist/public/assets/_Cvly93xo2.js.br +0 -0
- package/dist/public/assets/_CwZCc-fE2.js.br +0 -0
- package/dist/public/assets/_CwtAB8Ju2.js.br +0 -0
- package/dist/public/assets/_CzMjLSTK.js.br +0 -0
- package/dist/public/assets/_D11ZFURg.js.br +0 -0
- package/dist/public/assets/_D2be5yUI.js.br +0 -0
- package/dist/public/assets/_D2xWjoK-2.js.br +0 -0
- package/dist/public/assets/_D4V7vTBe.js.br +0 -0
- package/dist/public/assets/_D8lg2J6H.js.br +0 -0
- package/dist/public/assets/_DBXfsrRd.js.br +0 -0
- package/dist/public/assets/_DDag_nXJ.js.br +0 -0
- package/dist/public/assets/_DRKLaQtq.js.br +0 -0
- package/dist/public/assets/_DWctbEWg.js.br +0 -3
- package/dist/public/assets/_DZgx_rAt.js.br +0 -0
- package/dist/public/assets/_DdYfAoJz.js.br +0 -0
- package/dist/public/assets/_DdnCyOPs.js.br +0 -0
- package/dist/public/assets/_DfpMH3So2.js.br +0 -0
- package/dist/public/assets/_DhLWIgCs.js.br +0 -0
- package/dist/public/assets/_DiaYv9e1.js.br +0 -0
- package/dist/public/assets/_Du-i6H8r.js.br +0 -0
- package/dist/public/assets/_F8phZWNM2.js.br +0 -0
- package/dist/public/assets/_FEbRnVo7.js.br +0 -0
- package/dist/public/assets/_GdcmEEuJ.js.br +0 -0
- package/dist/public/assets/_N0Ea8Q_R.js.br +0 -0
- package/dist/public/assets/_QuFTKxMO2.js.br +0 -0
- package/dist/public/assets/_cS3NYZ_K2.js.br +0 -0
- package/dist/public/assets/_cdbANa0O.js.br +0 -0
- package/dist/public/assets/_dBVoJwVC.js.br +0 -0
- package/dist/public/assets/_fR2ilomn.js.br +0 -0
- package/dist/public/assets/_h6HS-Zb_2.js.br +0 -0
- package/dist/public/assets/_p1VLQxPw.js.br +0 -0
- package/dist/public/assets/_uJTU0UZd2.js.br +0 -0
- package/dist/public/assets/_wc-pkVQS.js.br +0 -0
- package/dist/public/assets/_zJkCtRhb.js.br +0 -0
- package/dist/public/assets/app-page-DgsAA8Om.css +0 -1
- package/dist/public/assets/app-page-DgsAA8Om.css.br +0 -0
- package/dist/public/assets/diff-view-BqwTpf5k.css +0 -1
- package/dist/public/assets/diff-view-BqwTpf5k.css.br +0 -0
- package/dist/public/assets/new-cBa61wMi.js +0 -1
- package/dist/public/assets/settings-cBa61wMi.js +0 -1
- package/dist/public/assets/w-cBa61wMi.js +0 -1
- package/dist/public/assets/w._-B9llVTHr.js +0 -1
- package/dist/public/assets/w._-cBa61wMi.js +0 -1
package/lib/setup/install.mjs
CHANGED
|
@@ -10,17 +10,20 @@ import { agentById, toolById } from "./catalog.mjs";
|
|
|
10
10
|
import { setupPath, which } from "./doctor.mjs";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Run a command, streaming each output line to `log`.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* scope is the directory they run in, such as project-scope skill installs.
|
|
13
|
+
* Run a command, streaming each output line to `log`. stdout/err are
|
|
14
|
+
* captured line by line and `input` (if any) is written to stdin before it
|
|
15
|
+
* closes. `cwd` matters for tools whose scope is the directory they run in,
|
|
16
|
+
* such as project-scope skill installs.
|
|
18
17
|
*/
|
|
19
|
-
export function runStreaming(
|
|
18
|
+
export function runStreaming(
|
|
19
|
+
cmd,
|
|
20
|
+
args,
|
|
21
|
+
{ log = () => {}, input, env, cwd, timeout = 15 * 60_000 } = {},
|
|
22
|
+
) {
|
|
20
23
|
return new Promise((resolve, reject) => {
|
|
21
24
|
const child = spawn(cmd, args, {
|
|
22
25
|
cwd,
|
|
23
|
-
stdio:
|
|
26
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
24
27
|
env: { ...process.env, ...env, PATH: setupPath() },
|
|
25
28
|
});
|
|
26
29
|
const lines = [];
|
|
@@ -29,7 +32,10 @@ export function runStreaming(cmd, args, { log = () => {}, interactive = false, i
|
|
|
29
32
|
buf += String(chunk);
|
|
30
33
|
let i;
|
|
31
34
|
while ((i = buf.search(/\r?\n/)) >= 0) {
|
|
32
|
-
const line = buf
|
|
35
|
+
const line = buf
|
|
36
|
+
.slice(0, i)
|
|
37
|
+
.replace(/\x1b\[[0-9;]*[A-Za-z]/g, "")
|
|
38
|
+
.trimEnd();
|
|
33
39
|
buf = buf.slice(i + (buf[i] === "\r" ? 2 : 1));
|
|
34
40
|
if (line) {
|
|
35
41
|
lines.push(line);
|
|
@@ -39,10 +45,8 @@ export function runStreaming(cmd, args, { log = () => {}, interactive = false, i
|
|
|
39
45
|
};
|
|
40
46
|
child.stdout?.on("data", onData);
|
|
41
47
|
child.stderr?.on("data", onData);
|
|
42
|
-
if (child.stdin)
|
|
43
|
-
|
|
44
|
-
child.stdin.end();
|
|
45
|
-
}
|
|
48
|
+
if (input) child.stdin.write(input);
|
|
49
|
+
child.stdin.end();
|
|
46
50
|
const timer = setTimeout(() => child.kill("SIGTERM"), timeout);
|
|
47
51
|
child.on("error", (err) => {
|
|
48
52
|
clearTimeout(timer);
|
|
@@ -52,7 +56,12 @@ export function runStreaming(cmd, args, { log = () => {}, interactive = false, i
|
|
|
52
56
|
clearTimeout(timer);
|
|
53
57
|
if (buf.trim()) onData("\n");
|
|
54
58
|
if (code === 0) resolve(lines);
|
|
55
|
-
else
|
|
59
|
+
else
|
|
60
|
+
reject(
|
|
61
|
+
new Error(
|
|
62
|
+
`${cmd} ${args.join(" ")} exited ${code}${lines.length ? `: ${lines[lines.length - 1]}` : ""}`,
|
|
63
|
+
),
|
|
64
|
+
);
|
|
56
65
|
});
|
|
57
66
|
});
|
|
58
67
|
}
|
|
@@ -62,7 +71,10 @@ export function runStreaming(cmd, args, { log = () => {}, interactive = false, i
|
|
|
62
71
|
* stream into a login shell: the URL is pinned in the catalog and the
|
|
63
72
|
* script runs in the setup PATH, not the user's rc. */
|
|
64
73
|
async function runRemoteScript(url, shell, opts) {
|
|
65
|
-
const res = await fetch(url, {
|
|
74
|
+
const res = await fetch(url, {
|
|
75
|
+
redirect: "follow",
|
|
76
|
+
signal: AbortSignal.timeout(60_000),
|
|
77
|
+
});
|
|
66
78
|
if (!res.ok) throw new Error(`${url} → ${res.status}`);
|
|
67
79
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "work-install-"));
|
|
68
80
|
const file = path.join(dir, "install.sh");
|
|
@@ -78,18 +90,25 @@ export async function installMise(opts = {}) {
|
|
|
78
90
|
if (which("mise")) return;
|
|
79
91
|
opts.log?.("installing mise");
|
|
80
92
|
await runRemoteScript("https://mise.run", "sh", opts);
|
|
81
|
-
if (!which("mise"))
|
|
93
|
+
if (!which("mise"))
|
|
94
|
+
throw new Error("mise install finished but binary not found");
|
|
82
95
|
}
|
|
83
96
|
|
|
84
97
|
function miseName(idOrName) {
|
|
85
98
|
const t = toolById(idOrName);
|
|
86
|
-
if (t && !t.mise)
|
|
99
|
+
if (t && !t.mise)
|
|
100
|
+
throw new Error(
|
|
101
|
+
`${t.label} is a system package. Install it with the system package manager`,
|
|
102
|
+
);
|
|
87
103
|
return t ? t.mise : idOrName;
|
|
88
104
|
}
|
|
89
105
|
|
|
90
106
|
/** `mise use -g name@version` — installs (or re-pins) a global tool. Accepts
|
|
91
107
|
* catalog ids or any mise registry short name (Settings search). */
|
|
92
|
-
export async function installTool(
|
|
108
|
+
export async function installTool(
|
|
109
|
+
idOrName,
|
|
110
|
+
{ version = "latest", ...opts } = {},
|
|
111
|
+
) {
|
|
93
112
|
await installMise(opts);
|
|
94
113
|
const name = miseName(idOrName);
|
|
95
114
|
opts.log?.(`mise use -g ${name}@${version}`);
|
|
@@ -119,39 +138,49 @@ export async function installAgent(id, opts = {}) {
|
|
|
119
138
|
if (!a) throw new Error(`unknown agent: ${id}`);
|
|
120
139
|
opts.log?.(`installing ${a.label} (${a.install.url})`);
|
|
121
140
|
await runRemoteScript(a.install.url, a.install.shell, opts);
|
|
122
|
-
if (!a.bins.some((b) => which(b)))
|
|
141
|
+
if (!a.bins.some((b) => which(b)))
|
|
142
|
+
throw new Error(
|
|
143
|
+
`${a.label} installer finished but ${a.bins[0]} is not on PATH`,
|
|
144
|
+
);
|
|
123
145
|
}
|
|
124
146
|
|
|
125
|
-
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* (
|
|
131
|
-
*
|
|
132
|
-
|
|
133
|
-
*/
|
|
134
|
-
export async function authGithub(host, { interactive = false, ...opts } = {}) {
|
|
147
|
+
/** Shared gh device flow: sign in / refresh without opening a browser.
|
|
148
|
+
* `work` usually runs on a remote box, so gh's attempt to launch one only
|
|
149
|
+
* produces "Failed opening a web browser" noise. Server-side we press Enter
|
|
150
|
+
* for gh and the code and URL flow out through `log` for the UI to show —
|
|
151
|
+
* gh keeps polling until the user finishes in their own browser
|
|
152
|
+
* (BROWSER=true suppresses the launch attempt; GH_PROMPT_DISABLED stops the
|
|
153
|
+
* prompt waiting on stdin we already fed). */
|
|
154
|
+
async function ghDeviceFlow(host, args, opts = {}) {
|
|
135
155
|
if (!which("gh")) throw new Error("gh is not installed");
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
156
|
+
return runStreaming("gh", args, {
|
|
157
|
+
...opts,
|
|
158
|
+
input: "\n",
|
|
159
|
+
env: { ...opts.env, BROWSER: "true", GH_PROMPT_DISABLED: "1" },
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Sign a GitHub host in with gh's device flow — see ghDeviceFlow. */
|
|
164
|
+
export async function authGithub(host, opts = {}) {
|
|
165
|
+
return ghDeviceFlow(
|
|
166
|
+
host,
|
|
167
|
+
["auth", "login", "--hostname", host, "--web", "--git-protocol", "https"],
|
|
168
|
+
opts,
|
|
169
|
+
);
|
|
140
170
|
}
|
|
141
171
|
|
|
142
172
|
/** Re-authorize an existing gh login (device flow). Needed after an org
|
|
143
173
|
* enables SAML: visiting the X-GitHub-SSO link only signs the browser into
|
|
144
174
|
* the IdP, while `auth refresh` re-issues the OAuth token with org SSO. */
|
|
145
|
-
export async function refreshGithub(host,
|
|
146
|
-
|
|
147
|
-
const args = ["auth", "refresh", "--hostname", host];
|
|
148
|
-
const env = { ...opts.env, BROWSER: "true" };
|
|
149
|
-
if (interactive) return runStreaming("gh", args, { ...opts, env, interactive: true });
|
|
150
|
-
return runStreaming("gh", args, { ...opts, input: "\n", env: { ...env, GH_PROMPT_DISABLED: "1" } });
|
|
175
|
+
export async function refreshGithub(host, opts = {}) {
|
|
176
|
+
return ghDeviceFlow(host, ["auth", "refresh", "--hostname", host], opts);
|
|
151
177
|
}
|
|
152
178
|
|
|
153
179
|
/** Sign out of a GitHub host (drops gh's token; the host stays listed). */
|
|
154
180
|
export async function logoutGithub(host, opts = {}) {
|
|
155
181
|
if (!which("gh")) throw new Error("gh is not installed");
|
|
156
|
-
return runStreaming("gh", ["auth", "logout", "--hostname", host], {
|
|
182
|
+
return runStreaming("gh", ["auth", "logout", "--hostname", host], {
|
|
183
|
+
...opts,
|
|
184
|
+
env: { GH_PROMPT_DISABLED: "1" },
|
|
185
|
+
});
|
|
157
186
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface RepoRow {
|
|
2
|
+
full_name: string;
|
|
3
|
+
worktree: string;
|
|
4
|
+
base_branch: string;
|
|
5
|
+
default_branch: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** `owner/name`, `owner/name@base`, or `{ repo, dir?, base? }`. */
|
|
9
|
+
export type RepoEntry = string | { repo: string; dir?: string; base?: string };
|
|
10
|
+
|
|
11
|
+
export interface Invite {
|
|
12
|
+
name: string;
|
|
13
|
+
branch: string;
|
|
14
|
+
/** `[host/]owner/name` per repo. */
|
|
15
|
+
repos: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A repo row as the manifest writes it. */
|
|
19
|
+
export function encodeRepo(r: RepoRow): RepoEntry;
|
|
20
|
+
/** The row for a manifest entry (or an older object form); null if not one.
|
|
21
|
+
* `base_branch`/`default_branch` are empty when the file did not pin them. */
|
|
22
|
+
export function decodeRepo(entry: unknown): RepoRow | null;
|
|
23
|
+
/** The pastable invite for a workspace (Manage's copy button, `work share`). */
|
|
24
|
+
export function formatInvite(ws: Invite): string;
|
|
25
|
+
/** Parse an invite, or a one-workspace backup. Throws with a reason a
|
|
26
|
+
* person can act on. */
|
|
27
|
+
export function parseInvite(text: string): Invite;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// The backup manifest's workspace entry is the one shape a workspace has
|
|
2
|
+
// outside the database: what `work export` writes, what Manage's copy
|
|
3
|
+
// button puts on the clipboard, what New → Join and `work join` read. An
|
|
4
|
+
// invite is that entry without the machine-specific parts (id, created,
|
|
5
|
+
// archived). Plain ESM with no dependencies, shared by the browser, the
|
|
6
|
+
// server and the CLI so there is exactly one format:
|
|
7
|
+
//
|
|
8
|
+
// {
|
|
9
|
+
// "name": "persisted pages",
|
|
10
|
+
// "branch": "dpiercey-ws-persisted-pages-xh8764",
|
|
11
|
+
// "repos": ["marko-js/marko", "DylanPiercey/persisted-pages-scratch"]
|
|
12
|
+
// }
|
|
13
|
+
//
|
|
14
|
+
// A repo is `owner/name` (`host/owner/name` off github.com), `owner/name@base`
|
|
15
|
+
// when the workspace's base is not the remote's default branch, and an
|
|
16
|
+
// object `{ repo, dir?, base? }` only when its directory is not the repo's
|
|
17
|
+
// name. A join ignores the base — the shared branch is the base.
|
|
18
|
+
|
|
19
|
+
const BRANCH_RE = /^[\w./+@-]+$/;
|
|
20
|
+
// `owner/name`, or `host/owner/name` — a host has a dot in it.
|
|
21
|
+
const REPO_RE = /^(?:[\w-]+(?:\.[\w-]+)+\/)?[\w.-]+\/[\w.-]+$/;
|
|
22
|
+
// One path segment: a repo's directory in the workspace.
|
|
23
|
+
const DIR_RE = /^(?!\.{1,2}$)[\w.-]+$/;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {{ full_name: string, worktree: string, base_branch: string, default_branch: string }} RepoRow
|
|
27
|
+
* @typedef {string | { repo: string, dir?: string, base?: string }} RepoEntry
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** @param {RepoRow} r @returns {RepoEntry} */
|
|
31
|
+
export function encodeRepo(r) {
|
|
32
|
+
const base = r.base_branch !== r.default_branch ? r.base_branch : undefined;
|
|
33
|
+
// `@` in the base would split wrong on the way back: object form then.
|
|
34
|
+
if (r.worktree === r.full_name.split("/").at(-1) && !base?.includes("@")) {
|
|
35
|
+
return base ? `${r.full_name}@${base}` : r.full_name;
|
|
36
|
+
}
|
|
37
|
+
const dir = r.worktree !== r.full_name.split("/").at(-1);
|
|
38
|
+
return {
|
|
39
|
+
repo: r.full_name,
|
|
40
|
+
...(dir && { dir: r.worktree }),
|
|
41
|
+
...(base && { base }),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The row for an entry, or null when it is not one. The remote's default
|
|
47
|
+
* branch is never in the file: `base_branch`/`default_branch` come back
|
|
48
|
+
* empty when not pinned and the server fills them from the clone.
|
|
49
|
+
* @param {unknown} entry @returns {RepoRow | null}
|
|
50
|
+
*/
|
|
51
|
+
export function decodeRepo(entry) {
|
|
52
|
+
let full = "";
|
|
53
|
+
let dir;
|
|
54
|
+
let base;
|
|
55
|
+
if (typeof entry === "string") {
|
|
56
|
+
const at = entry.lastIndexOf("@");
|
|
57
|
+
full = at > 0 ? entry.slice(0, at) : entry;
|
|
58
|
+
base = at > 0 ? entry.slice(at + 1) : undefined;
|
|
59
|
+
} else if (entry && typeof entry === "object") {
|
|
60
|
+
const o = /** @type {Record<string, unknown>} */ (entry);
|
|
61
|
+
// Format-1 rows and API shapes spell the name differently.
|
|
62
|
+
full = String(o.repo ?? o.full_name ?? o.fullName ?? "");
|
|
63
|
+
dir = typeof o.dir === "string" ? o.dir : undefined;
|
|
64
|
+
if (typeof o.worktree === "string") dir = o.worktree;
|
|
65
|
+
base = typeof o.base === "string" ? o.base : undefined;
|
|
66
|
+
if (typeof o.base_branch === "string") base = o.base_branch;
|
|
67
|
+
} else return null;
|
|
68
|
+
full = full.trim();
|
|
69
|
+
if (!REPO_RE.test(full)) return null;
|
|
70
|
+
// A host is case-insensitive; the configured list is lowercase.
|
|
71
|
+
const parts = full.split("/");
|
|
72
|
+
if (parts.length === 3)
|
|
73
|
+
full = [parts[0].toLowerCase(), parts[1], parts[2]].join("/");
|
|
74
|
+
if (base !== undefined && !BRANCH_RE.test(base)) return null;
|
|
75
|
+
if (dir !== undefined && !DIR_RE.test(dir)) return null;
|
|
76
|
+
return {
|
|
77
|
+
full_name: full,
|
|
78
|
+
worktree: dir || full.split("/").at(-1),
|
|
79
|
+
base_branch: base || "",
|
|
80
|
+
default_branch:
|
|
81
|
+
entry &&
|
|
82
|
+
typeof entry === "object" &&
|
|
83
|
+
typeof entry.default_branch === "string"
|
|
84
|
+
? entry.default_branch
|
|
85
|
+
: base || "",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The invite for a workspace: its manifest entry, pared to what a join
|
|
91
|
+
* needs. Repos carry no base — the joiner's base is the branch.
|
|
92
|
+
* @param {{ name: string, branch: string, repos: string[] }} ws
|
|
93
|
+
* @returns {string}
|
|
94
|
+
*/
|
|
95
|
+
export function formatInvite(ws) {
|
|
96
|
+
return (
|
|
97
|
+
JSON.stringify(
|
|
98
|
+
{ name: ws.name, branch: ws.branch, repos: ws.repos },
|
|
99
|
+
null,
|
|
100
|
+
1,
|
|
101
|
+
) + "\n"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Read an invite: a workspace entry, or a whole backup that holds one
|
|
107
|
+
* workspace. Throws with a reason a person can act on.
|
|
108
|
+
* @param {string} text
|
|
109
|
+
* @returns {{ name: string, branch: string, repos: string[] }}
|
|
110
|
+
*/
|
|
111
|
+
export function parseInvite(text) {
|
|
112
|
+
const raw = String(text ?? "").trim();
|
|
113
|
+
if (!raw) throw new Error("paste a workspace invite");
|
|
114
|
+
let obj;
|
|
115
|
+
try {
|
|
116
|
+
obj = JSON.parse(raw);
|
|
117
|
+
} catch {
|
|
118
|
+
throw new Error("not an invite (expected JSON)");
|
|
119
|
+
}
|
|
120
|
+
if (!obj || typeof obj !== "object") throw new Error("not an invite");
|
|
121
|
+
if (Array.isArray(obj.workspaces)) {
|
|
122
|
+
if (obj.workspaces.length !== 1)
|
|
123
|
+
throw new Error(
|
|
124
|
+
obj.workspaces.length
|
|
125
|
+
? "this is a whole backup — restore it from Settings, or paste one workspace entry"
|
|
126
|
+
: "this backup holds no workspaces",
|
|
127
|
+
);
|
|
128
|
+
obj = obj.workspaces[0];
|
|
129
|
+
if (!obj || typeof obj !== "object") throw new Error("not an invite");
|
|
130
|
+
}
|
|
131
|
+
const branch = String(obj.branch ?? "").trim();
|
|
132
|
+
if (!branch) throw new Error("the invite has no branch");
|
|
133
|
+
if (!BRANCH_RE.test(branch)) throw new Error(`not a branch name: ${branch}`);
|
|
134
|
+
const entries = Array.isArray(obj.repos) ? obj.repos : [];
|
|
135
|
+
const repos = [];
|
|
136
|
+
for (const e of entries) {
|
|
137
|
+
const r = decodeRepo(e);
|
|
138
|
+
if (!r) throw new Error(`not a repo: ${JSON.stringify(e)}`);
|
|
139
|
+
if (!repos.includes(r.full_name)) repos.push(r.full_name);
|
|
140
|
+
}
|
|
141
|
+
if (!repos.length) throw new Error("the invite lists no repos");
|
|
142
|
+
const name = String(obj.name ?? "").trim();
|
|
143
|
+
return { name: name || branch, branch, repos };
|
|
144
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dylanpiercey/work",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.122",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Web GUI for agentic dev workspaces",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
"postinstall": "chmod +x node_modules/node-pty/prebuilds/*/spawn-helper node_modules/node-pty/build/Release/spawn-helper 2>/dev/null || true",
|
|
30
30
|
"build": "node scripts/agents-catalog.mjs && marko-run build src/entry.ts",
|
|
31
31
|
"check": "node scripts/agents-catalog.mjs && mtc",
|
|
32
|
-
"preview": "PORT=7171 node dist/index.mjs"
|
|
32
|
+
"preview": "PORT=7171 node dist/index.mjs",
|
|
33
|
+
"format": "prettier . -w",
|
|
34
|
+
"format:check": "prettier --check ."
|
|
33
35
|
},
|
|
34
36
|
"dependencies": {
|
|
35
37
|
"node-pty": "^1.1.0"
|
|
@@ -56,6 +58,8 @@
|
|
|
56
58
|
"iconoir": "^7.12.1",
|
|
57
59
|
"marko": "^6.3.43",
|
|
58
60
|
"playwright": "^1.62.1",
|
|
61
|
+
"prettier": "^3.9.6",
|
|
62
|
+
"prettier-plugin-marko": "^4.1.0",
|
|
59
63
|
"serve-static": "^2.2.1",
|
|
60
64
|
"typescript": "^6.0.3",
|
|
61
65
|
"valibot": "^1.4.2",
|
package/scripts/migrate.mjs
CHANGED
|
@@ -79,7 +79,8 @@ for (const { path: wsPath } of db
|
|
|
79
79
|
noted++;
|
|
80
80
|
} catch {}
|
|
81
81
|
}
|
|
82
|
-
if (noted)
|
|
82
|
+
if (noted)
|
|
83
|
+
console.log(`==> migrate: noted the shared stash in ${noted} AGENTS.md`);
|
|
83
84
|
|
|
84
85
|
// The review is commit-to-commit by default; fold that into the stash
|
|
85
86
|
// bullet rather than appending another one. Keep in sync with writeAgentsMd.
|
|
@@ -98,7 +99,10 @@ for (const { path: wsPath } of db
|
|
|
98
99
|
restated++;
|
|
99
100
|
} catch {}
|
|
100
101
|
}
|
|
101
|
-
if (restated)
|
|
102
|
+
if (restated)
|
|
103
|
+
console.log(
|
|
104
|
+
`==> migrate: restated the stash bullet in ${restated} AGENTS.md`,
|
|
105
|
+
);
|
|
102
106
|
|
|
103
107
|
// Same rewrite-on-create-only problem: existing AGENTS.md never learn to
|
|
104
108
|
// rebase after a squash-merge. Keep in sync with writeAgentsMd.
|
|
@@ -119,7 +123,8 @@ for (const { path: wsPath } of db
|
|
|
119
123
|
rebased++;
|
|
120
124
|
} catch {}
|
|
121
125
|
}
|
|
122
|
-
if (rebased)
|
|
126
|
+
if (rebased)
|
|
127
|
+
console.log(`==> migrate: noted rebase-after-merge in ${rebased} AGENTS.md`);
|
|
123
128
|
|
|
124
129
|
// Branch rules: stay on the workspace branch; reset it after it lands;
|
|
125
130
|
// topic lanes only when that branch must stay open. Replaces both the
|
|
@@ -173,7 +178,9 @@ for (const { path: wsPath } of db
|
|
|
173
178
|
} catch {}
|
|
174
179
|
}
|
|
175
180
|
if (restatedBranches) {
|
|
176
|
-
console.log(
|
|
181
|
+
console.log(
|
|
182
|
+
`==> migrate: restated branch rules in ${restatedBranches} AGENTS.md`,
|
|
183
|
+
);
|
|
177
184
|
}
|
|
178
185
|
|
|
179
186
|
// Default review base/algorithm changed (workspace branch vs origin/<base>,
|
|
@@ -229,13 +236,19 @@ try {
|
|
|
229
236
|
const fullName = `${owner}/${repoDir.slice(0, -4)}`;
|
|
230
237
|
const script =
|
|
231
238
|
`#!/bin/sh\n` +
|
|
232
|
-
`# installed by work
|
|
239
|
+
`# installed by work: signals ref updates to the app server.\n` +
|
|
233
240
|
`[ "$1" = "committed" ] || exit 0\n` +
|
|
234
|
-
`
|
|
235
|
-
`
|
|
241
|
+
`refs=$(cat)\n` +
|
|
242
|
+
`case "$refs" in *"refs/remotes/origin/"*|*" refs/heads/"*) ;; *) exit 0 ;; esac\n` +
|
|
243
|
+
`printf '%s\\n' "$refs" | curl -sf -m 2 -X POST --data-binary @- "http://127.0.0.1:${appPort}` +
|
|
236
244
|
`/api/repo-pushed?repo=${encodeURIComponent(fullName)}&token=${hookTokenValue}" >/dev/null 2>&1 &\n` +
|
|
237
245
|
`exit 0\n`;
|
|
238
|
-
const file = path.join(
|
|
246
|
+
const file = path.join(
|
|
247
|
+
ownerDir,
|
|
248
|
+
repoDir,
|
|
249
|
+
"hooks",
|
|
250
|
+
"reference-transaction",
|
|
251
|
+
);
|
|
239
252
|
try {
|
|
240
253
|
if (fs.readFileSync(file, "utf8") === script) continue;
|
|
241
254
|
} catch {}
|
|
@@ -247,7 +260,8 @@ try {
|
|
|
247
260
|
}
|
|
248
261
|
}
|
|
249
262
|
} catch {}
|
|
250
|
-
if (hooked)
|
|
263
|
+
if (hooked)
|
|
264
|
+
console.log(`==> migrate: installed push hook in ${hooked} cache repo(s)`);
|
|
251
265
|
|
|
252
266
|
// Lane branches used to accumulate in the bare caches: nothing deleted one
|
|
253
267
|
// when its PR finished, only removing the repo or the whole workspace did,
|
|
@@ -260,24 +274,37 @@ const laneWorkspaces = db
|
|
|
260
274
|
.prepare("SELECT id, branch FROM workspaces")
|
|
261
275
|
.all()
|
|
262
276
|
.map((w) => ({ ...w, repos: [] }));
|
|
263
|
-
for (const row of db
|
|
264
|
-
|
|
277
|
+
for (const row of db
|
|
278
|
+
.prepare("SELECT workspace_id, full_name FROM repos")
|
|
279
|
+
.all()) {
|
|
280
|
+
laneWorkspaces
|
|
281
|
+
.find((w) => w.id === row.workspace_id)
|
|
282
|
+
?.repos.push(row.full_name);
|
|
265
283
|
}
|
|
266
284
|
const ownsRef = (ref, prefix) => ref === prefix || ref.startsWith(`${prefix}-`);
|
|
267
285
|
const gitIn = (dir, args) =>
|
|
268
|
-
execFileSync("git", ["-C", dir, ...args], {
|
|
286
|
+
execFileSync("git", ["-C", dir, ...args], {
|
|
287
|
+
encoding: "utf8",
|
|
288
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
289
|
+
});
|
|
269
290
|
|
|
270
291
|
// Candidate lanes: owned by a workspace branch, not the branch itself, not
|
|
271
292
|
// claimed by another workspace whose branch extends this one's prefix, and
|
|
272
293
|
// not checked out in a worktree.
|
|
273
294
|
const candidates = [];
|
|
274
295
|
for (const ws of laneWorkspaces) {
|
|
275
|
-
const others = laneWorkspaces
|
|
296
|
+
const others = laneWorkspaces
|
|
297
|
+
.filter((w) => w.id !== ws.id)
|
|
298
|
+
.map((w) => w.branch);
|
|
276
299
|
for (const fullName of ws.repos) {
|
|
277
300
|
const bare = path.join(cacheHost, `${fullName}.git`);
|
|
278
301
|
let refs, checkedOut;
|
|
279
302
|
try {
|
|
280
|
-
refs = gitIn(bare, [
|
|
303
|
+
refs = gitIn(bare, [
|
|
304
|
+
"for-each-ref",
|
|
305
|
+
"--format=%(refname:short) %(objectname)",
|
|
306
|
+
"refs/heads/",
|
|
307
|
+
])
|
|
281
308
|
.split("\n")
|
|
282
309
|
.filter(Boolean)
|
|
283
310
|
.map((line) => line.split(" "));
|
|
@@ -292,7 +319,8 @@ for (const ws of laneWorkspaces) {
|
|
|
292
319
|
}
|
|
293
320
|
for (const [lane, oid] of refs) {
|
|
294
321
|
if (lane === ws.branch || !ownsRef(lane, ws.branch)) continue;
|
|
295
|
-
if (others.some((b) => ownsRef(lane, b)) || checkedOut.has(lane))
|
|
322
|
+
if (others.some((b) => ownsRef(lane, b)) || checkedOut.has(lane))
|
|
323
|
+
continue;
|
|
296
324
|
candidates.push({ bare, fullName, lane, oid });
|
|
297
325
|
}
|
|
298
326
|
}
|
|
@@ -332,26 +360,30 @@ for (let i = 0; i < candidates.length; i += 40) {
|
|
|
332
360
|
// The lane has to still be sitting on the finished PR's head: anything
|
|
333
361
|
// further along is work that PR never carried, and deleting it here
|
|
334
362
|
// would be the only copy.
|
|
335
|
-
if (!nodes.some((p) => p.state !== "OPEN" && p.headRefOid === c.oid))
|
|
363
|
+
if (!nodes.some((p) => p.state !== "OPEN" && p.headRefOid === c.oid))
|
|
364
|
+
return;
|
|
336
365
|
try {
|
|
337
366
|
gitIn(c.bare, ["branch", "-D", c.lane]);
|
|
338
367
|
pruned++;
|
|
339
368
|
} catch {}
|
|
340
369
|
});
|
|
341
370
|
}
|
|
342
|
-
if (pruned)
|
|
371
|
+
if (pruned)
|
|
372
|
+
console.log(`==> migrate: pruned ${pruned} finished lane branch(es)`);
|
|
343
373
|
|
|
344
374
|
// Existing AGENTS.md never learn about the `work` CLI read commands. Keep in
|
|
345
375
|
// sync with writeAgentsMd.
|
|
346
376
|
const WORK_CLI_BULLET =
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
377
|
+
"- **Ask `work` for facts:** the `work` CLI answers from the same data as\n" +
|
|
378
|
+
" the app: `work workspace` (this workspace: repos, base branches, open PRs,\n" +
|
|
379
|
+
" sessions, running procs/ports), `work repos`, `work procs`,\n" +
|
|
380
|
+
" `work workspaces`, `work doctor` (installed tools / agents / GitHub\n" +
|
|
381
|
+
" sign-in), `work usage` (agent plan quotas). Add `--json` for machine\n" +
|
|
382
|
+
" output. Prefer these over guessing from the filesystem or calling GitHub.\n";
|
|
353
383
|
let cliNoted = 0;
|
|
354
|
-
for (const { path: wsPath } of db
|
|
384
|
+
for (const { path: wsPath } of db
|
|
385
|
+
.prepare("SELECT path FROM workspaces")
|
|
386
|
+
.all()) {
|
|
355
387
|
const file = path.join(wsPath, "AGENTS.md");
|
|
356
388
|
try {
|
|
357
389
|
const text = fs.readFileSync(file, "utf8");
|
|
@@ -360,7 +392,8 @@ for (const { path: wsPath } of db.prepare("SELECT path FROM workspaces").all())
|
|
|
360
392
|
cliNoted++;
|
|
361
393
|
} catch {}
|
|
362
394
|
}
|
|
363
|
-
if (cliNoted)
|
|
395
|
+
if (cliNoted)
|
|
396
|
+
console.log(`==> migrate: pointed ${cliNoted} AGENTS.md at the work CLI`);
|
|
364
397
|
|
|
365
398
|
// Existing AGENTS.md never learn that a repo's own agents file is required
|
|
366
399
|
// reading. Splice it in after the repo list (ahead of the stash bullet), so
|
|
@@ -369,17 +402,25 @@ const REPO_AGENTS_BULLET =
|
|
|
369
402
|
"- **Repo agents files:** you **MUST** read a repo's `AGENTS.md` (or\n" +
|
|
370
403
|
" `CLAUDE.md`) before doing any work in it. It is not loaded for you.\n";
|
|
371
404
|
let repoNoted = 0;
|
|
372
|
-
for (const { path: wsPath } of db
|
|
405
|
+
for (const { path: wsPath } of db
|
|
406
|
+
.prepare("SELECT path FROM workspaces")
|
|
407
|
+
.all()) {
|
|
373
408
|
const file = path.join(wsPath, "AGENTS.md");
|
|
374
409
|
try {
|
|
375
410
|
const text = fs.readFileSync(file, "utf8");
|
|
376
411
|
const at = text.indexOf("- **No `git stash`:**");
|
|
377
412
|
if (at < 0 || text.includes("**Repo agents files:**")) continue;
|
|
378
|
-
fs.writeFileSync(
|
|
413
|
+
fs.writeFileSync(
|
|
414
|
+
file,
|
|
415
|
+
text.slice(0, at) + REPO_AGENTS_BULLET + text.slice(at),
|
|
416
|
+
);
|
|
379
417
|
repoNoted++;
|
|
380
418
|
} catch {}
|
|
381
419
|
}
|
|
382
|
-
if (repoNoted)
|
|
420
|
+
if (repoNoted)
|
|
421
|
+
console.log(
|
|
422
|
+
`==> migrate: required repo agents files in ${repoNoted} AGENTS.md`,
|
|
423
|
+
);
|
|
383
424
|
|
|
384
425
|
// Per-workspace UI prefs were never swept on delete; they ride every SSR
|
|
385
426
|
// document via allPrefs(). Prune orphans once — runtime delete handles new ones.
|
|
@@ -387,7 +428,10 @@ if (repoNoted) console.log(`==> migrate: required repo agents files in ${repoNot
|
|
|
387
428
|
const wr = new DatabaseSync(dbPath);
|
|
388
429
|
try {
|
|
389
430
|
const live = new Set(
|
|
390
|
-
wr
|
|
431
|
+
wr
|
|
432
|
+
.prepare("SELECT id FROM workspaces")
|
|
433
|
+
.all()
|
|
434
|
+
.map((r) => r.id),
|
|
391
435
|
);
|
|
392
436
|
const prefixes = ["ui.tab:", "ui.diff-filter:", "ui.diff-pos:"];
|
|
393
437
|
let pruned = 0;
|
|
@@ -400,7 +444,34 @@ if (repoNoted) console.log(`==> migrate: required repo agents files in ${repoNot
|
|
|
400
444
|
pruned++;
|
|
401
445
|
}
|
|
402
446
|
}
|
|
403
|
-
if (pruned)
|
|
447
|
+
if (pruned)
|
|
448
|
+
console.log(`==> migrate: pruned ${pruned} orphaned ui pref(s)`);
|
|
449
|
+
} finally {
|
|
450
|
+
wr.close();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// The review's two-way toggle became a three-way scope pref. Fold the old
|
|
455
|
+
// value into the new key once and drop it, so runtime never carries legacy
|
|
456
|
+
// handling — settings are plain rows, so this is the whole upgrade.
|
|
457
|
+
{
|
|
458
|
+
const wr = new DatabaseSync(dbPath);
|
|
459
|
+
try {
|
|
460
|
+
const legacy = wr
|
|
461
|
+
.prepare("SELECT value FROM settings WHERE key = 'diff-uncommitted'")
|
|
462
|
+
.get();
|
|
463
|
+
if (legacy) {
|
|
464
|
+
const existing = wr
|
|
465
|
+
.prepare("SELECT value FROM settings WHERE key = 'diff-scope'")
|
|
466
|
+
.get();
|
|
467
|
+
if (!existing) {
|
|
468
|
+
wr.prepare(
|
|
469
|
+
"INSERT INTO settings (key, value) VALUES ('diff-scope', ?)",
|
|
470
|
+
).run(legacy.value === "1" ? "working" : "committed");
|
|
471
|
+
}
|
|
472
|
+
wr.prepare("DELETE FROM settings WHERE key = 'diff-uncommitted'").run();
|
|
473
|
+
console.log("==> migrate: folded diff-uncommitted into diff-scope");
|
|
474
|
+
}
|
|
404
475
|
} finally {
|
|
405
476
|
wr.close();
|
|
406
477
|
}
|
package/dist/_BIDn_RW1.js
DELETED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|