@dylanpiercey/work 0.0.119 → 0.0.121
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/_Bjh5JEm7.js +2 -0
- package/dist/_CfwTkTbL.js +352 -0
- package/dist/{_BOIUqihs.js → _CivNK9fo.js} +20 -18
- package/dist/{_DE2RUdbF.js → _Cr71Xd0d.js} +8 -3
- package/dist/_DJyHc1w-.js +1739 -0
- package/dist/{_BIhoUpsW.js → _DnaZVHJ7.js} +95 -94
- package/dist/_Dpa8WFuv.js +2 -0
- package/dist/index.mjs +4301 -4236
- package/dist/public/assets/{_BSsp7Z4i2.js → _-zdUyUYM2.js} +1 -1
- package/dist/public/assets/_-zdUyUYM2.js.br +0 -0
- package/dist/public/assets/{_LUNsNRnJ2.js → _0L2K8sFq2.js} +1 -1
- package/dist/public/assets/_0L2K8sFq2.js.br +0 -0
- package/dist/public/assets/{_BE3wNPJo.js → _42jdam49.js} +1 -1
- package/dist/public/assets/_42jdam49.js.br +0 -0
- package/dist/public/assets/{_CIFN7c1t2.js → _9dJ8BnyZ2.js} +1 -1
- package/dist/public/assets/_9dJ8BnyZ2.js.br +0 -0
- package/dist/public/assets/{_CqDb111T.js → _B03G1Nvs.js} +1 -1
- package/dist/public/assets/_B03G1Nvs.js.br +0 -0
- package/dist/public/assets/{_Dzy9r1lm.js → _B0dZ7_qK.js} +1 -1
- package/dist/public/assets/_B0dZ7_qK.js.br +0 -0
- package/dist/public/assets/{_c1YZLlfE.js → _B1VuWiKK.js} +1 -1
- package/dist/public/assets/_B1VuWiKK.js.br +0 -0
- package/dist/public/assets/{_Bp1b0XWB2.js → _B5nrQZIq2.js} +1 -1
- package/dist/public/assets/_B5nrQZIq2.js.br +0 -0
- package/dist/public/assets/{_Bq2fn_8D.js → _B8ZQ8inF.js} +1 -1
- package/dist/public/assets/_B8ZQ8inF.js.br +0 -0
- package/dist/public/assets/{_CvnxxlPb.js → _B9e51G1K.js} +1 -1
- package/dist/public/assets/_B9e51G1K.js.br +0 -0
- package/dist/public/assets/{_CUjnrp_u2.js → _BH015W2i2.js} +1 -1
- package/dist/public/assets/_BH015W2i2.js.br +0 -0
- package/dist/public/assets/{_B62GzYdT.js → _BHjcfVXB.js} +6 -6
- package/dist/public/assets/_BHjcfVXB.js.br +0 -0
- package/dist/public/assets/{_DMHXo8uX.js → _BNIilCsC.js} +1 -1
- package/dist/public/assets/_BNIilCsC.js.br +0 -0
- package/dist/public/assets/{_szXVEFuj2.js → _BPgL37mi2.js} +1 -1
- package/dist/public/assets/_BPgL37mi2.js.br +0 -0
- package/dist/public/assets/{_Ctuh4IDL2.js → _BR3D4nzj2.js} +1 -1
- package/dist/public/assets/_BR3D4nzj2.js.br +0 -0
- package/dist/public/assets/{_BkTJUq-u.js → _BRSJwfZ3.js} +1 -1
- package/dist/public/assets/{_DS628Dnv.js → _BSFENb4l.js} +1 -1
- package/dist/public/assets/_BSFENb4l.js.br +0 -0
- package/dist/public/assets/{_Btt4k5w3.js → _BSWpz0dK.js} +1 -1
- package/dist/public/assets/_BSWpz0dK.js.br +0 -0
- package/dist/public/assets/{_N0vzdKCD.js → _BUOWar32.js} +1 -1
- package/dist/public/assets/_BUOWar32.js.br +0 -0
- package/dist/public/assets/{_1bmjwuWH.js → _BZWlvMSJ.js} +1 -1
- package/dist/public/assets/_BZWlvMSJ.js.br +0 -0
- package/dist/public/assets/{_BlVrxgss2.js → _B_dMglA92.js} +1 -1
- package/dist/public/assets/_B_dMglA92.js.br +0 -0
- package/dist/public/assets/{_XgOGaUKD.js → _Beb6Lb1w.js} +1 -1
- package/dist/public/assets/_Beb6Lb1w.js.br +0 -0
- package/dist/public/assets/{_CXQ_K2Vh2.js → _BjIMoOV-2.js} +1 -1
- package/dist/public/assets/_BjIMoOV-2.js.br +0 -0
- package/dist/public/assets/{_Dhu480me.js → _BlLuOKRT.js} +1 -1
- package/dist/public/assets/_BlLuOKRT.js.br +0 -0
- package/dist/public/assets/{_BijwdXvp.js → _BlXwius8.js} +1 -1
- package/dist/public/assets/_BlXwius8.js.br +0 -0
- package/dist/public/assets/{_DBJ43cPF.js → _Bmt76eWT.js} +1 -1
- package/dist/public/assets/_Bmt76eWT.js.br +0 -0
- package/dist/public/assets/{_CkuVE2zK.js → _BoS5LU5l.js} +1 -1
- package/dist/public/assets/_BoS5LU5l.js.br +0 -0
- package/dist/public/assets/{_DtLQVzKB2.js → _BsNKabYu2.js} +1 -1
- package/dist/public/assets/_BsNKabYu2.js.br +0 -0
- package/dist/public/assets/{_CEWjw4lq.js → _C0OXw3Vy.js} +1 -1
- package/dist/public/assets/_C0OXw3Vy.js.br +0 -0
- package/dist/public/assets/{_CpRKjPVO2.js → _CBUJNdeq2.js} +1 -1
- package/dist/public/assets/_CBUJNdeq2.js.br +0 -0
- package/dist/public/assets/{_CZzZOY4P2.js → _CCNhM32c2.js} +1 -1
- package/dist/public/assets/_CCNhM32c2.js.br +0 -0
- package/dist/public/assets/{_BgnvPND_.js → _CFBaZNUT.js} +1 -1
- package/dist/public/assets/_CFBaZNUT.js.br +0 -0
- package/dist/public/assets/{_CJT7ujcv2.js → _CJHqzl--2.js} +1 -1
- package/dist/public/assets/_CJHqzl--2.js.br +0 -0
- package/dist/public/assets/{_duTaTgEk2.js → _CMGgQ-4H2.js} +1 -1
- package/dist/public/assets/_CMGgQ-4H2.js.br +0 -0
- package/dist/public/assets/{_5YlmpA6X.js → _CPX0D-my.js} +1 -1
- package/dist/public/assets/_CPX0D-my.js.br +0 -0
- package/dist/public/assets/{_BYYDEuFR2.js → _CRuHDn_O2.js} +1 -1
- package/dist/public/assets/_CRuHDn_O2.js.br +0 -0
- package/dist/public/assets/{_CnKbN1QV.js → _CUOao_Hj.js} +1 -1
- package/dist/public/assets/_CUOao_Hj.js.br +0 -0
- package/dist/public/assets/{_CZejW_fq.js → _CUVw5V4z.js} +1 -1
- package/dist/public/assets/_CUVw5V4z.js.br +0 -0
- package/dist/public/assets/{_DO4n-NwM.js → _CYMGOhCb.js} +1 -1
- package/dist/public/assets/_CYMGOhCb.js.br +0 -0
- package/dist/public/assets/{_ER-_pvil.js → _CZfap_Dj.js} +1 -1
- package/dist/public/assets/_CZfap_Dj.js.br +0 -0
- package/dist/public/assets/{_DYngS949.js → _CbNmSe5k.js} +1 -1
- package/dist/public/assets/_CbNmSe5k.js.br +0 -0
- package/dist/public/assets/{_DKciAPQl.js → _CdtuhZSc.js} +1 -1
- package/dist/public/assets/_CdtuhZSc.js.br +0 -0
- package/dist/public/assets/{_B7w9Jp5h.js → _ClhApln3.js} +1 -1
- package/dist/public/assets/_ClhApln3.js.br +0 -0
- package/dist/public/assets/{_BD9TBsnM2.js → _CvA5IOAW2.js} +1 -1
- package/dist/public/assets/_CvA5IOAW2.js.br +0 -0
- package/dist/public/assets/{_Cp-yobQE2.js → _CwZYmUdt2.js} +1 -1
- package/dist/public/assets/{_MofKWwY9.js → _CxrcWZPE.js} +1 -1
- package/dist/public/assets/_CxrcWZPE.js.br +0 -0
- package/dist/public/assets/{_RhYe7Uqa.js → _D-KjGTSW.js} +24 -21
- package/dist/public/assets/_D-KjGTSW.js.br +0 -0
- package/dist/public/assets/{_BRiOyxeF.js → _D4u4SpiK.js} +1 -1
- package/dist/public/assets/_D4u4SpiK.js.br +0 -0
- package/dist/public/assets/{_D8raMSvy.js → _D6CjH3JN.js} +1 -1
- package/dist/public/assets/_D6CjH3JN.js.br +0 -0
- package/dist/public/assets/{_Cym2lUeD2.js → _D7cQLLGK2.js} +1 -1
- package/dist/public/assets/_D7cQLLGK2.js.br +0 -0
- package/dist/public/assets/{_C8DWM9rr.js → _DBoABI85.js} +1 -1
- package/dist/public/assets/_DBoABI85.js.br +0 -0
- package/dist/public/assets/{_Rf_7ZEPL.js → _DNcd1xtp.js} +1 -1
- package/dist/public/assets/_DNcd1xtp.js.br +0 -0
- package/dist/public/assets/{_Bhwci4Zy.js → _DQGTProt.js} +1 -1
- package/dist/public/assets/_DQGTProt.js.br +0 -0
- package/dist/public/assets/{_B2g0gJmZ.js → _DSIH6_Li.js} +1 -1
- package/dist/public/assets/_DSIH6_Li.js.br +0 -0
- package/dist/public/assets/{_T2999saD2.js → _DXHf4WHs2.js} +1 -1
- package/dist/public/assets/_DXHf4WHs2.js.br +0 -0
- package/dist/public/assets/{_CoStmjbL.js → _D_qDhyn2.js} +1 -1
- package/dist/public/assets/_D_qDhyn2.js.br +0 -0
- package/dist/public/assets/{_CGOYaUuV.js → _DcMfCkmd.js} +1 -1
- package/dist/public/assets/_DcMfCkmd.js.br +0 -0
- package/dist/public/assets/{_Cv9Hgy8P.js → _DgDEdxEu.js} +1 -1
- package/dist/public/assets/_DgDEdxEu.js.br +0 -0
- package/dist/public/assets/{_Bsk9ewWF.js → _DlhDim1D.js} +1 -1
- package/dist/public/assets/_DlhDim1D.js.br +0 -0
- package/dist/public/assets/{_BNQgBkwF.js → _DqLib98x.js} +1 -1
- package/dist/public/assets/_DqLib98x.js.br +0 -0
- package/dist/public/assets/{_l9vJoVeP.js → _Ds0Qa0YX.js} +1 -1
- package/dist/public/assets/_Ds0Qa0YX.js.br +0 -0
- package/dist/public/assets/{_iDn41XPe2.js → _DyUvL09k2.js} +1 -1
- package/dist/public/assets/_DyUvL09k2.js.br +0 -0
- package/dist/public/assets/{_AqmX0pBH2.js → _J6xlcydH2.js} +1 -1
- package/dist/public/assets/_J6xlcydH2.js.br +0 -0
- package/dist/public/assets/{_Dni1s_3Q2.js → _MInulccf2.js} +1 -1
- package/dist/public/assets/{_BsOtzbIC.js → _QMVFINxi.js} +1 -1
- package/dist/public/assets/_QMVFINxi.js.br +0 -0
- package/dist/public/assets/{_CWNv7bdi.js → _S9QQtt68.js} +1 -1
- package/dist/public/assets/_S9QQtt68.js.br +0 -0
- package/dist/public/assets/{_DlZyjzE_.js → _XCJLLGxV.js} +1 -1
- package/dist/public/assets/_XCJLLGxV.js.br +0 -0
- package/dist/public/assets/{_B2LXCPdE.js → _Y-bTh0dW.js} +1 -1
- package/dist/public/assets/_Y-bTh0dW.js.br +0 -0
- package/dist/public/assets/{_zPAwS_-H.js → _Y8TOk1pR.js} +1 -1
- package/dist/public/assets/_Y8TOk1pR.js.br +0 -0
- package/dist/public/assets/{_CzI2QhH4.js → _YetFhXkM.js} +1 -1
- package/dist/public/assets/_YetFhXkM.js.br +0 -0
- package/dist/public/assets/{_O8R4l7yw2.js → _YmJopjX92.js} +1 -1
- package/dist/public/assets/_YmJopjX92.js.br +0 -0
- package/dist/public/assets/{_Du5pjG7G.js → _mbzaOqik.js} +1 -1
- package/dist/public/assets/_mbzaOqik.js.br +0 -0
- package/dist/public/assets/{_qj_i_cqe.js → _rMcfapaa.js} +1 -1
- package/dist/public/assets/_rMcfapaa.js.br +0 -0
- package/dist/public/assets/{_yTOoKAfM2.js → _rRtbbfKJ2.js} +1 -1
- package/dist/public/assets/_rRtbbfKJ2.js.br +0 -0
- package/dist/public/assets/{_BD68UZIf.js → _sSWgnHeU.js} +1 -1
- package/dist/public/assets/_sSWgnHeU.js.br +0 -0
- package/dist/public/assets/{_CegJB2vl.js → _xOeYy_3v.js} +1 -1
- package/dist/public/assets/_xOeYy_3v.js.br +0 -0
- package/dist/public/assets/app-page-BWf0w6W9.css +1 -0
- package/dist/public/assets/app-page-BWf0w6W9.css.br +0 -0
- package/dist/public/assets/diff-view-BZaYYEDK.css +1 -0
- package/dist/public/assets/diff-view-BZaYYEDK.css.br +0 -0
- package/dist/public/assets/new-K-7z0qE8.js +1 -0
- package/dist/public/assets/settings-K-7z0qE8.js +1 -0
- package/dist/public/assets/w-K-7z0qE8.js +1 -0
- package/dist/public/assets/w._-D5d4klUv.js +1 -0
- package/dist/public/assets/w._-K-7z0qE8.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/_1bmjwuWH.js.br +0 -0
- package/dist/public/assets/_5YlmpA6X.js.br +0 -0
- package/dist/public/assets/_AqmX0pBH2.js.br +0 -0
- package/dist/public/assets/_B2LXCPdE.js.br +0 -0
- package/dist/public/assets/_B2g0gJmZ.js.br +0 -0
- package/dist/public/assets/_B62GzYdT.js.br +0 -0
- package/dist/public/assets/_B7w9Jp5h.js.br +0 -0
- package/dist/public/assets/_BD68UZIf.js.br +0 -0
- package/dist/public/assets/_BD9TBsnM2.js.br +0 -0
- package/dist/public/assets/_BE3wNPJo.js.br +0 -0
- package/dist/public/assets/_BNQgBkwF.js.br +0 -0
- package/dist/public/assets/_BRiOyxeF.js.br +0 -0
- package/dist/public/assets/_BSsp7Z4i2.js.br +0 -0
- package/dist/public/assets/_BYYDEuFR2.js.br +0 -0
- package/dist/public/assets/_BgnvPND_.js.br +0 -0
- package/dist/public/assets/_Bhwci4Zy.js.br +0 -0
- package/dist/public/assets/_BijwdXvp.js.br +0 -0
- package/dist/public/assets/_BlVrxgss2.js.br +0 -0
- package/dist/public/assets/_Bp1b0XWB2.js.br +0 -0
- package/dist/public/assets/_Bq2fn_8D.js.br +0 -0
- package/dist/public/assets/_BsOtzbIC.js.br +0 -0
- package/dist/public/assets/_Bsk9ewWF.js.br +0 -0
- package/dist/public/assets/_Btt4k5w3.js.br +0 -0
- package/dist/public/assets/_C8DWM9rr.js.br +0 -0
- package/dist/public/assets/_CEWjw4lq.js.br +0 -0
- package/dist/public/assets/_CGOYaUuV.js.br +0 -0
- package/dist/public/assets/_CIFN7c1t2.js.br +0 -0
- package/dist/public/assets/_CJT7ujcv2.js.br +0 -0
- package/dist/public/assets/_CUjnrp_u2.js.br +0 -0
- package/dist/public/assets/_CWNv7bdi.js.br +0 -0
- package/dist/public/assets/_CXQ_K2Vh2.js.br +0 -0
- package/dist/public/assets/_CZejW_fq.js.br +0 -0
- package/dist/public/assets/_CZzZOY4P2.js.br +0 -0
- package/dist/public/assets/_CegJB2vl.js.br +0 -0
- package/dist/public/assets/_CkuVE2zK.js.br +0 -0
- package/dist/public/assets/_CnKbN1QV.js.br +0 -0
- package/dist/public/assets/_CoStmjbL.js.br +0 -0
- package/dist/public/assets/_CpRKjPVO2.js.br +0 -0
- package/dist/public/assets/_CqDb111T.js.br +0 -0
- package/dist/public/assets/_Ctuh4IDL2.js.br +0 -0
- package/dist/public/assets/_Cv9Hgy8P.js.br +0 -0
- package/dist/public/assets/_CvnxxlPb.js.br +0 -0
- package/dist/public/assets/_Cym2lUeD2.js.br +0 -0
- package/dist/public/assets/_CzI2QhH4.js.br +0 -0
- package/dist/public/assets/_D8raMSvy.js.br +0 -0
- package/dist/public/assets/_DBJ43cPF.js.br +0 -0
- package/dist/public/assets/_DKciAPQl.js.br +0 -0
- package/dist/public/assets/_DMHXo8uX.js.br +0 -0
- package/dist/public/assets/_DO4n-NwM.js.br +0 -0
- package/dist/public/assets/_DS628Dnv.js.br +0 -0
- package/dist/public/assets/_DYngS949.js.br +0 -0
- package/dist/public/assets/_Dhu480me.js.br +0 -0
- package/dist/public/assets/_DlZyjzE_.js.br +0 -0
- package/dist/public/assets/_DtLQVzKB2.js.br +0 -0
- package/dist/public/assets/_Du5pjG7G.js.br +0 -0
- package/dist/public/assets/_Dzy9r1lm.js.br +0 -0
- package/dist/public/assets/_ER-_pvil.js.br +0 -0
- package/dist/public/assets/_LUNsNRnJ2.js.br +0 -0
- package/dist/public/assets/_MofKWwY9.js.br +0 -0
- package/dist/public/assets/_N0vzdKCD.js.br +0 -0
- package/dist/public/assets/_O8R4l7yw2.js.br +0 -0
- package/dist/public/assets/_Rf_7ZEPL.js.br +0 -0
- package/dist/public/assets/_RhYe7Uqa.js.br +0 -0
- package/dist/public/assets/_T2999saD2.js.br +0 -0
- package/dist/public/assets/_XgOGaUKD.js.br +0 -0
- package/dist/public/assets/_c1YZLlfE.js.br +0 -0
- package/dist/public/assets/_duTaTgEk2.js.br +0 -0
- package/dist/public/assets/_iDn41XPe2.js.br +0 -0
- package/dist/public/assets/_l9vJoVeP.js.br +0 -0
- package/dist/public/assets/_qj_i_cqe.js.br +0 -0
- package/dist/public/assets/_szXVEFuj2.js.br +0 -0
- package/dist/public/assets/_yTOoKAfM2.js.br +0 -0
- package/dist/public/assets/_zPAwS_-H.js.br +0 -0
- package/dist/public/assets/app-page-BXbewHHv.css +0 -1
- package/dist/public/assets/app-page-BXbewHHv.css.br +0 -0
- package/dist/public/assets/diff-view-BJ1RAVYB.css +0 -1
- package/dist/public/assets/diff-view-BJ1RAVYB.css.br +0 -0
- package/dist/public/assets/new-DUXTU7jB.js +0 -1
- package/dist/public/assets/settings-DUXTU7jB.js +0 -1
- package/dist/public/assets/w-DUXTU7jB.js +0 -1
- package/dist/public/assets/w._-DUXTU7jB.js +0 -1
- package/dist/public/assets/w._-bwrmfhu0.js +0 -1
package/lib/setup/doctor.mjs
CHANGED
|
@@ -46,7 +46,11 @@ export function setupPath(base = process.env.PATH || "") {
|
|
|
46
46
|
* display (Settings rows, `work doctor`), never re-used to exec. */
|
|
47
47
|
function tilde(p) {
|
|
48
48
|
if (!p) return p;
|
|
49
|
-
return p === home
|
|
49
|
+
return p === home
|
|
50
|
+
? "~"
|
|
51
|
+
: p.startsWith(home + path.sep)
|
|
52
|
+
? "~" + p.slice(home.length)
|
|
53
|
+
: p;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
/** Absolute path of `bin` on setupPath(), or null. */
|
|
@@ -65,8 +69,17 @@ function run(cmd, args, { timeout = 15_000 } = {}) {
|
|
|
65
69
|
execFile(
|
|
66
70
|
cmd,
|
|
67
71
|
args,
|
|
68
|
-
{
|
|
69
|
-
|
|
72
|
+
{
|
|
73
|
+
env: { ...process.env, PATH: setupPath() },
|
|
74
|
+
timeout,
|
|
75
|
+
maxBuffer: 8 << 20,
|
|
76
|
+
},
|
|
77
|
+
(err, stdout, stderr) =>
|
|
78
|
+
resolve({
|
|
79
|
+
ok: !err,
|
|
80
|
+
stdout: String(stdout || ""),
|
|
81
|
+
stderr: String(stderr || ""),
|
|
82
|
+
}),
|
|
70
83
|
);
|
|
71
84
|
});
|
|
72
85
|
}
|
|
@@ -81,7 +94,9 @@ export async function miseVersions() {
|
|
|
81
94
|
const out = {};
|
|
82
95
|
try {
|
|
83
96
|
for (const [name, list] of Object.entries(JSON.parse(stdout))) {
|
|
84
|
-
const active =
|
|
97
|
+
const active =
|
|
98
|
+
list.find((v) => v.installed && v.active) ||
|
|
99
|
+
list.find((v) => v.installed);
|
|
85
100
|
if (active) out[name] = active.version;
|
|
86
101
|
}
|
|
87
102
|
} catch {}
|
|
@@ -92,7 +107,10 @@ export async function miseVersions() {
|
|
|
92
107
|
* source of truth for "which GitHub hosts has this machine signed in to",
|
|
93
108
|
* merged with whatever the caller has configured. */
|
|
94
109
|
export function ghKnownHosts() {
|
|
95
|
-
const file = path.join(
|
|
110
|
+
const file = path.join(
|
|
111
|
+
process.env.GH_CONFIG_DIR || path.join(home, ".config", "gh"),
|
|
112
|
+
"hosts.yml",
|
|
113
|
+
);
|
|
96
114
|
let text = "";
|
|
97
115
|
try {
|
|
98
116
|
text = fs.readFileSync(file, "utf8");
|
|
@@ -130,7 +148,13 @@ export function discoverAgents() {
|
|
|
130
148
|
continue;
|
|
131
149
|
}
|
|
132
150
|
for (const name of names) {
|
|
133
|
-
const hit = AGENTS.find((a) =>
|
|
151
|
+
const hit = AGENTS.find((a) =>
|
|
152
|
+
a.bins.some(
|
|
153
|
+
(b) =>
|
|
154
|
+
name === b ||
|
|
155
|
+
(name.startsWith(b) && /^\d+$/.test(name.slice(b.length))),
|
|
156
|
+
),
|
|
157
|
+
);
|
|
134
158
|
if (!hit || seen.has(name)) continue;
|
|
135
159
|
// Join against the PATH entry, not the realpath — tilde() matches on
|
|
136
160
|
// $HOME, and on hosts where home is a symlink (/home → /var/home)
|
|
@@ -142,28 +166,65 @@ export function discoverAgents() {
|
|
|
142
166
|
continue;
|
|
143
167
|
}
|
|
144
168
|
const bin = hit.bins.find((b) => name.startsWith(b));
|
|
145
|
-
seen.set(name, {
|
|
169
|
+
seen.set(name, {
|
|
170
|
+
family: hit,
|
|
171
|
+
id: `${hit.id}${name.slice(bin.length)}`,
|
|
172
|
+
command: name,
|
|
173
|
+
path: tilde(full),
|
|
174
|
+
});
|
|
146
175
|
}
|
|
147
176
|
}
|
|
148
177
|
return AGENTS.map((a) => {
|
|
149
178
|
const instances = [...seen.values()]
|
|
150
179
|
.filter((i) => i.family === a)
|
|
151
|
-
.map(({ id, command, path: p }) => ({
|
|
152
|
-
|
|
153
|
-
|
|
180
|
+
.map(({ id, command, path: p }) => ({
|
|
181
|
+
id,
|
|
182
|
+
command,
|
|
183
|
+
path: p,
|
|
184
|
+
suffix: id.slice(a.id.length),
|
|
185
|
+
}))
|
|
186
|
+
.sort((x, y) =>
|
|
187
|
+
x.suffix === ""
|
|
188
|
+
? -1
|
|
189
|
+
: y.suffix === ""
|
|
190
|
+
? 1
|
|
191
|
+
: Number(x.suffix) - Number(y.suffix),
|
|
192
|
+
);
|
|
193
|
+
return {
|
|
194
|
+
id: a.id,
|
|
195
|
+
label: a.label,
|
|
196
|
+
installed: instances.length > 0,
|
|
197
|
+
installable: true,
|
|
198
|
+
instances,
|
|
199
|
+
};
|
|
154
200
|
});
|
|
155
201
|
}
|
|
156
202
|
|
|
157
203
|
/** Auth status for one host via gh (which owns the tokens). */
|
|
158
204
|
async function githubHostStatus(host) {
|
|
159
205
|
const isEnterprise = host !== DEFAULT_GITHUB_HOST;
|
|
160
|
-
if (!which("gh"))
|
|
161
|
-
|
|
206
|
+
if (!which("gh"))
|
|
207
|
+
return { host, isEnterprise, authed: false, error: "gh not installed" };
|
|
208
|
+
const { ok, stdout, stderr } = await run("gh", [
|
|
209
|
+
"auth",
|
|
210
|
+
"status",
|
|
211
|
+
"--hostname",
|
|
212
|
+
host,
|
|
213
|
+
]);
|
|
162
214
|
const text = stdout + stderr;
|
|
163
|
-
const user =
|
|
215
|
+
const user =
|
|
216
|
+
text.match(/account (\S+)/)?.[1] ||
|
|
217
|
+
text.match(/Logged in to \S+ as (\S+)/)?.[1];
|
|
164
218
|
return ok
|
|
165
219
|
? { host, isEnterprise, authed: true, user }
|
|
166
|
-
: {
|
|
220
|
+
: {
|
|
221
|
+
host,
|
|
222
|
+
isEnterprise,
|
|
223
|
+
authed: false,
|
|
224
|
+
error: /not logged in/i.test(text)
|
|
225
|
+
? undefined
|
|
226
|
+
: text.trim().split("\n").pop(),
|
|
227
|
+
};
|
|
167
228
|
}
|
|
168
229
|
|
|
169
230
|
/**
|
|
@@ -173,7 +234,11 @@ async function githubHostStatus(host) {
|
|
|
173
234
|
* github.com and any host gh is already signed in to are always included.
|
|
174
235
|
*/
|
|
175
236
|
export async function doctor(opts = {}) {
|
|
176
|
-
const hostSet = new Set([
|
|
237
|
+
const hostSet = new Set([
|
|
238
|
+
DEFAULT_GITHUB_HOST,
|
|
239
|
+
...(opts.hosts || []),
|
|
240
|
+
...ghKnownHosts(),
|
|
241
|
+
]);
|
|
177
242
|
const [versions, github] = await Promise.all([
|
|
178
243
|
miseVersions(),
|
|
179
244
|
Promise.all([...hostSet].map(githubHostStatus)),
|
|
@@ -205,7 +270,10 @@ export async function doctor(opts = {}) {
|
|
|
205
270
|
return {
|
|
206
271
|
ready: missing.length === 0,
|
|
207
272
|
missing,
|
|
208
|
-
mise: {
|
|
273
|
+
mise: {
|
|
274
|
+
installed: versions !== null,
|
|
275
|
+
path: tilde(which("mise")) || undefined,
|
|
276
|
+
},
|
|
209
277
|
tools,
|
|
210
278
|
agents,
|
|
211
279
|
github,
|
|
@@ -216,16 +284,24 @@ export async function doctor(opts = {}) {
|
|
|
216
284
|
export function formatDoctor(d) {
|
|
217
285
|
const mark = (ok) => (ok ? "✓" : "✗");
|
|
218
286
|
const lines = [];
|
|
219
|
-
lines.push(
|
|
287
|
+
lines.push(
|
|
288
|
+
`mise ${mark(d.mise.installed)} ${d.mise.path || "not installed"}`,
|
|
289
|
+
);
|
|
220
290
|
for (const t of d.tools) {
|
|
221
291
|
const tag = t.required ? "" : " (optional)";
|
|
222
|
-
lines.push(
|
|
292
|
+
lines.push(
|
|
293
|
+
`${t.label.padEnd(11)}${mark(t.installed)} ${t.installed ? (t.version ? `v${t.version} ` : "") + (t.path || "") : "missing"}${tag}`,
|
|
294
|
+
);
|
|
223
295
|
}
|
|
224
296
|
for (const h of d.github) {
|
|
225
|
-
lines.push(
|
|
297
|
+
lines.push(
|
|
298
|
+
`${h.host.padEnd(11)}${mark(h.authed)} ${h.authed ? `signed in${h.user ? ` as ${h.user}` : ""}` : h.error || "not signed in"}`,
|
|
299
|
+
);
|
|
226
300
|
}
|
|
227
301
|
for (const a of d.agents) {
|
|
228
|
-
lines.push(
|
|
302
|
+
lines.push(
|
|
303
|
+
`${a.label.padEnd(11)}${mark(a.installed)} ${a.installed ? a.instances.map((i) => `${i.command} (${i.path})`).join(", ") : "not installed"}`,
|
|
304
|
+
);
|
|
229
305
|
}
|
|
230
306
|
lines.push("");
|
|
231
307
|
lines.push(d.ready ? "ready" : `not ready, missing: ${d.missing.join(", ")}`);
|
package/lib/setup/install.d.mts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
export interface RunOpts {
|
|
2
2
|
log?: (line: string) => void;
|
|
3
|
-
interactive?: boolean;
|
|
4
3
|
input?: string;
|
|
5
4
|
env?: Record<string, string>;
|
|
6
5
|
cwd?: string;
|
|
7
6
|
timeout?: number;
|
|
8
7
|
}
|
|
9
|
-
export function runStreaming(
|
|
8
|
+
export function runStreaming(
|
|
9
|
+
cmd: string,
|
|
10
|
+
args: string[],
|
|
11
|
+
opts?: RunOpts,
|
|
12
|
+
): Promise<string[]>;
|
|
10
13
|
export function installMise(opts?: RunOpts): Promise<void>;
|
|
11
|
-
export function installTool(
|
|
14
|
+
export function installTool(
|
|
15
|
+
idOrName: string,
|
|
16
|
+
opts?: RunOpts & { version?: string },
|
|
17
|
+
): Promise<void>;
|
|
12
18
|
export function updateTool(idOrName: string, opts?: RunOpts): Promise<void>;
|
|
13
19
|
export function removeTool(idOrName: string, opts?: RunOpts): Promise<void>;
|
|
14
20
|
export function installAgent(id: string, opts?: RunOpts): Promise<void>;
|
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.121",
|
|
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",
|