@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.
Files changed (262) hide show
  1. package/README.md +35 -8
  2. package/bin/work.mjs +380 -41
  3. package/dist/_B1lVb01g.js +2 -0
  4. package/dist/{_DEBFGwto.js → _BPX2_wNT.js} +20 -18
  5. package/dist/{_BZhGz-eY.js → _BdBrOaZF.js} +95 -94
  6. package/dist/_Bjh5JEm7.js +2 -0
  7. package/dist/_CfwTkTbL.js +352 -0
  8. package/dist/{_DE2RUdbF.js → _Cr71Xd0d.js} +8 -3
  9. package/dist/_DJyHc1w-.js +1739 -0
  10. package/dist/_Dpa8WFuv.js +2 -0
  11. package/dist/index.mjs +4136 -4140
  12. package/dist/public/assets/{_z3QzE4ht2.js → _22Wep3Qj2.js} +1 -1
  13. package/dist/public/assets/{_DdYfAoJz.js → _4C4ZKftZ.js} +1 -1
  14. package/dist/public/assets/_4C4ZKftZ.js.br +0 -0
  15. package/dist/public/assets/{_h6HS-Zb_2.js → _5waGGcsH2.js} +1 -1
  16. package/dist/public/assets/_5waGGcsH2.js.br +0 -0
  17. package/dist/public/assets/{_BqNoqWIY.js → _6yLwnUth.js} +1 -1
  18. package/dist/public/assets/_6yLwnUth.js.br +0 -0
  19. package/dist/public/assets/{_BuF6uiIm.js → _8ixfpSLV.js} +1 -1
  20. package/dist/public/assets/_8ixfpSLV.js.br +0 -0
  21. package/dist/public/assets/{_BEPt7EWk.js → _9NNG2Bg-.js} +1 -1
  22. package/dist/public/assets/_9NNG2Bg-.js.br +0 -0
  23. package/dist/public/assets/{_D8lg2J6H.js → _9wp1Weya.js} +1 -1
  24. package/dist/public/assets/_9wp1Weya.js.br +0 -0
  25. package/dist/public/assets/{_BoXfCUcF.js → _B80Me7dX.js} +1 -1
  26. package/dist/public/assets/{_BbE6iGBd.js → _BJVUTWAN.js} +1 -1
  27. package/dist/public/assets/_BJVUTWAN.js.br +0 -0
  28. package/dist/public/assets/{_CihMY3_B2.js → _BO5eXuo12.js} +1 -1
  29. package/dist/public/assets/_BO5eXuo12.js.br +0 -0
  30. package/dist/public/assets/{_CwZCc-fE2.js → _BO6TG3u42.js} +1 -1
  31. package/dist/public/assets/_BO6TG3u42.js.br +0 -0
  32. package/dist/public/assets/{_D4V7vTBe.js → _BO_carFG.js} +1 -1
  33. package/dist/public/assets/_BO_carFG.js.br +0 -0
  34. package/dist/public/assets/{_D11ZFURg.js → _BPjnrd1A.js} +1 -1
  35. package/dist/public/assets/_BPjnrd1A.js.br +0 -0
  36. package/dist/public/assets/{_DRKLaQtq.js → _BQfrm6Hv.js} +1 -1
  37. package/dist/public/assets/_BQfrm6Hv.js.br +0 -0
  38. package/dist/public/assets/{_CwtAB8Ju2.js → _BYOrCsb12.js} +1 -1
  39. package/dist/public/assets/_BYOrCsb12.js.br +0 -0
  40. package/dist/public/assets/{_CIio55Q2.js → _BcA9iBrt.js} +1 -1
  41. package/dist/public/assets/_BcA9iBrt.js.br +0 -0
  42. package/dist/public/assets/{_Du-i6H8r.js → _BcnKn75V.js} +24 -21
  43. package/dist/public/assets/_BcnKn75V.js.br +0 -0
  44. package/dist/public/assets/{_CJ_9Hl5d.js → _BeYy7LJv.js} +1 -1
  45. package/dist/public/assets/_BeYy7LJv.js.br +0 -0
  46. package/dist/public/assets/{_DBXfsrRd.js → _BfzwZXGo.js} +1 -1
  47. package/dist/public/assets/_BfzwZXGo.js.br +0 -0
  48. package/dist/public/assets/{_BfZMmKaW.js → _Bgq0Djxf.js} +1 -1
  49. package/dist/public/assets/_Bgq0Djxf.js.br +0 -0
  50. package/dist/public/assets/{_zJkCtRhb.js → _BgxHsV2q.js} +1 -1
  51. package/dist/public/assets/_BgxHsV2q.js.br +0 -0
  52. package/dist/public/assets/{_p1VLQxPw.js → _Bhf_wksG.js} +1 -1
  53. package/dist/public/assets/_Bhf_wksG.js.br +0 -0
  54. package/dist/public/assets/{_CrPK-ZOH2.js → _Bkd9T8qO2.js} +1 -1
  55. package/dist/public/assets/_Bkd9T8qO2.js.br +0 -0
  56. package/dist/public/assets/{_CYLlpjQc.js → _BllRu2dt.js} +1 -1
  57. package/dist/public/assets/_BllRu2dt.js.br +0 -0
  58. package/dist/public/assets/{_DWctbEWg.js → _Buu8_cyc.js} +1 -1
  59. package/dist/public/assets/_Buu8_cyc.js.br +0 -0
  60. package/dist/public/assets/{_F8phZWNM2.js → _BvJHLrwP2.js} +1 -1
  61. package/dist/public/assets/_BvJHLrwP2.js.br +0 -0
  62. package/dist/public/assets/{_wc-pkVQS.js → _BvTWp2QQ.js} +1 -1
  63. package/dist/public/assets/_BvTWp2QQ.js.br +0 -0
  64. package/dist/public/assets/{_CkBv_F0f.js → _BxfzYLyp.js} +1 -1
  65. package/dist/public/assets/_BxfzYLyp.js.br +0 -0
  66. package/dist/public/assets/{_BzULaZGs.js → _Bz61WBUI.js} +1 -1
  67. package/dist/public/assets/_Bz61WBUI.js.br +0 -0
  68. package/dist/public/assets/{_Cv-4N4xQ.js → _C0Wcm1DA.js} +1 -1
  69. package/dist/public/assets/_C0Wcm1DA.js.br +0 -0
  70. package/dist/public/assets/{_BUmLRB7c2.js → _C5fkGs032.js} +1 -1
  71. package/dist/public/assets/_C5fkGs032.js.br +0 -0
  72. package/dist/public/assets/{_BEOZghNJ2.js → _CKAWtCKg2.js} +1 -1
  73. package/dist/public/assets/_CKAWtCKg2.js.br +0 -0
  74. package/dist/public/assets/{_BFgJoTOo.js → _CLM87vGj.js} +1 -1
  75. package/dist/public/assets/_CLM87vGj.js.br +0 -0
  76. package/dist/public/assets/{_B6bSumz1.js → _CM8zmLXB.js} +1 -1
  77. package/dist/public/assets/_CM8zmLXB.js.br +0 -0
  78. package/dist/public/assets/{_8hF31A2G.js → _CNBki2Aq.js} +1 -1
  79. package/dist/public/assets/_CNBki2Aq.js.br +0 -0
  80. package/dist/public/assets/{_CQ-tqbUq.js → _CNVk64eD.js} +1 -1
  81. package/dist/public/assets/_CNVk64eD.js.br +0 -0
  82. package/dist/public/assets/{_BHgE7f-_2.js → _CPCFHsPT2.js} +1 -1
  83. package/dist/public/assets/_CPCFHsPT2.js.br +0 -0
  84. package/dist/public/assets/{_BLgtdDQm.js → _CW70CETb.js} +1 -1
  85. package/dist/public/assets/_CW70CETb.js.br +0 -0
  86. package/dist/public/assets/{_BCKhXQct2.js → _CWU_IJjz2.js} +1 -1
  87. package/dist/public/assets/_CWU_IJjz2.js.br +0 -0
  88. package/dist/public/assets/{_BaiP7ogH.js → _ClA-9QLW.js} +1 -1
  89. package/dist/public/assets/_ClA-9QLW.js.br +0 -0
  90. package/dist/public/assets/{_dBVoJwVC.js → _CorgOnjQ.js} +1 -1
  91. package/dist/public/assets/_CorgOnjQ.js.br +0 -0
  92. package/dist/public/assets/{_DdnCyOPs.js → _CpByFq0N.js} +1 -1
  93. package/dist/public/assets/_CpByFq0N.js.br +0 -0
  94. package/dist/public/assets/{_fR2ilomn.js → _CqEc7KwE.js} +1 -1
  95. package/dist/public/assets/_CqEc7KwE.js.br +0 -0
  96. package/dist/public/assets/{_BtmFYBHk.js → _CqgcFjOQ.js} +1 -1
  97. package/dist/public/assets/_CqgcFjOQ.js.br +0 -0
  98. package/dist/public/assets/{_CeECfdVo2.js → _CrlXwiFN2.js} +1 -1
  99. package/dist/public/assets/_CrlXwiFN2.js.br +0 -0
  100. package/dist/public/assets/{_FEbRnVo7.js → _CtuCeJaB.js} +1 -1
  101. package/dist/public/assets/_CtuCeJaB.js.br +0 -0
  102. package/dist/public/assets/{_DfpMH3So2.js → _D1zxC16I2.js} +1 -1
  103. package/dist/public/assets/_D1zxC16I2.js.br +0 -0
  104. package/dist/public/assets/{_Cvly93xo2.js → _D2SIC9vH2.js} +1 -1
  105. package/dist/public/assets/_D2SIC9vH2.js.br +0 -0
  106. package/dist/public/assets/{_-3OmHyNU.js → _D9QYQO8L.js} +1 -1
  107. package/dist/public/assets/_D9QYQO8L.js.br +0 -0
  108. package/dist/public/assets/{_CzMjLSTK.js → _DDNq8klN.js} +1 -1
  109. package/dist/public/assets/_DDNq8klN.js.br +0 -0
  110. package/dist/public/assets/{_BIsQjQ872.js → _DL2HExUi2.js} +1 -1
  111. package/dist/public/assets/_DL2HExUi2.js.br +0 -0
  112. package/dist/public/assets/{_QuFTKxMO2.js → _DLbcflUX2.js} +1 -1
  113. package/dist/public/assets/_DLbcflUX2.js.br +0 -0
  114. package/dist/public/assets/{_N0Ea8Q_R.js → _DMzElmqz.js} +1 -1
  115. package/dist/public/assets/_DMzElmqz.js.br +0 -0
  116. package/dist/public/assets/{_cS3NYZ_K2.js → _DOa36sDf2.js} +1 -1
  117. package/dist/public/assets/_DOa36sDf2.js.br +0 -0
  118. package/dist/public/assets/{_DDag_nXJ.js → _DWZLw9u5.js} +1 -1
  119. package/dist/public/assets/_DWZLw9u5.js.br +0 -0
  120. package/dist/public/assets/{_DiaYv9e1.js → _DY5MuJqh.js} +1 -1
  121. package/dist/public/assets/_DY5MuJqh.js.br +0 -0
  122. package/dist/public/assets/{_D2xWjoK-2.js → _DZXcx3AV2.js} +1 -1
  123. package/dist/public/assets/_DZXcx3AV2.js.br +0 -0
  124. package/dist/public/assets/{_BPcKgx9L.js → _D_5rjG0o.js} +1 -1
  125. package/dist/public/assets/_D_5rjG0o.js.br +0 -0
  126. package/dist/public/assets/{_Ca811EeR.js → _DbEs3COY.js} +1 -1
  127. package/dist/public/assets/_DbEs3COY.js.br +0 -0
  128. package/dist/public/assets/{_BrTLGsp82.js → _DegNdi0y2.js} +1 -1
  129. package/dist/public/assets/_DegNdi0y2.js.br +0 -0
  130. package/dist/public/assets/{_B_MP9UES.js → _DfLxwQXd.js} +1 -1
  131. package/dist/public/assets/_DfLxwQXd.js.br +0 -0
  132. package/dist/public/assets/{_BilqQTfw.js → _DwTIenq9.js} +1 -1
  133. package/dist/public/assets/_DwTIenq9.js.br +0 -0
  134. package/dist/public/assets/{_0_ngOEbL.js → _Dwsfrcqr.js} +1 -1
  135. package/dist/public/assets/_Dwsfrcqr.js.br +0 -0
  136. package/dist/public/assets/{_uJTU0UZd2.js → _DyZPE8LD2.js} +1 -1
  137. package/dist/public/assets/_DyZPE8LD2.js.br +0 -0
  138. package/dist/public/assets/{_D2be5yUI.js → _MEclBKIH.js} +1 -1
  139. package/dist/public/assets/_MEclBKIH.js.br +0 -0
  140. package/dist/public/assets/{_DhLWIgCs.js → _Tpid4lIw.js} +1 -1
  141. package/dist/public/assets/_Tpid4lIw.js.br +0 -0
  142. package/dist/public/assets/{_cdbANa0O.js → _W9aOkr2v.js} +1 -1
  143. package/dist/public/assets/_W9aOkr2v.js.br +0 -0
  144. package/dist/public/assets/{_BDRc66NB2.js → _Y8lHXkX62.js} +1 -1
  145. package/dist/public/assets/_Y8lHXkX62.js.br +0 -0
  146. package/dist/public/assets/{_GdcmEEuJ.js → _Z4spekbX.js} +1 -1
  147. package/dist/public/assets/_Z4spekbX.js.br +0 -0
  148. package/dist/public/assets/{_CeVHIZWy2.js → _a18bdHwu2.js} +1 -1
  149. package/dist/public/assets/_a18bdHwu2.js.br +0 -0
  150. package/dist/public/assets/{_DkYHMy0q2.js → _hO1aV02w2.js} +1 -1
  151. package/dist/public/assets/{_DZgx_rAt.js → _ih_22Lwc.js} +1 -1
  152. package/dist/public/assets/_ih_22Lwc.js.br +0 -0
  153. package/dist/public/assets/{_CG2uZvAG.js → _kQ849tt6.js} +1 -1
  154. package/dist/public/assets/_kQ849tt6.js.br +0 -0
  155. package/dist/public/assets/{_Ces5jihM.js → _lo4uGYhv.js} +1 -1
  156. package/dist/public/assets/_lo4uGYhv.js.br +0 -0
  157. package/dist/public/assets/{_Brws7C1N.js → _sWtKlZZ5.js} +6 -6
  158. package/dist/public/assets/_sWtKlZZ5.js.br +0 -0
  159. package/dist/public/assets/{_BGHYY00K2.js → _xvmiPi6J2.js} +1 -1
  160. package/dist/public/assets/_xvmiPi6J2.js.br +0 -0
  161. package/dist/public/assets/app-page-9ukuDxSW.css +1 -0
  162. package/dist/public/assets/app-page-9ukuDxSW.css.br +0 -0
  163. package/dist/public/assets/diff-view-BmyKvz_f.css +1 -0
  164. package/dist/public/assets/diff-view-BmyKvz_f.css.br +0 -0
  165. package/dist/public/assets/new-PmY_tc2O.js +1 -0
  166. package/dist/public/assets/settings-PmY_tc2O.js +1 -0
  167. package/dist/public/assets/w-PmY_tc2O.js +1 -0
  168. package/dist/public/assets/w._-DL1tdfaH.js +1 -0
  169. package/dist/public/assets/w._-PmY_tc2O.js +1 -0
  170. package/lib/review/markdown.mjs +6 -2
  171. package/lib/setup/catalog.d.mts +3 -1
  172. package/lib/setup/catalog.mjs +11 -2
  173. package/lib/setup/doctor.mjs +96 -20
  174. package/lib/setup/install.d.mts +9 -3
  175. package/lib/setup/install.mjs +68 -39
  176. package/lib/share/manifest.d.mts +27 -0
  177. package/lib/share/manifest.mjs +144 -0
  178. package/package.json +6 -2
  179. package/scripts/migrate.mjs +101 -30
  180. package/dist/_BIDn_RW1.js +0 -2
  181. package/dist/public/assets/_-3OmHyNU.js.br +0 -0
  182. package/dist/public/assets/_0_ngOEbL.js.br +0 -0
  183. package/dist/public/assets/_8hF31A2G.js.br +0 -0
  184. package/dist/public/assets/_B6bSumz1.js.br +0 -0
  185. package/dist/public/assets/_BCKhXQct2.js.br +0 -0
  186. package/dist/public/assets/_BDRc66NB2.js.br +0 -0
  187. package/dist/public/assets/_BEOZghNJ2.js.br +0 -0
  188. package/dist/public/assets/_BEPt7EWk.js.br +0 -0
  189. package/dist/public/assets/_BFgJoTOo.js.br +0 -0
  190. package/dist/public/assets/_BGHYY00K2.js.br +0 -0
  191. package/dist/public/assets/_BHgE7f-_2.js.br +0 -0
  192. package/dist/public/assets/_BIsQjQ872.js.br +0 -0
  193. package/dist/public/assets/_BLgtdDQm.js.br +0 -0
  194. package/dist/public/assets/_BPcKgx9L.js.br +0 -0
  195. package/dist/public/assets/_BUmLRB7c2.js.br +0 -0
  196. package/dist/public/assets/_B_MP9UES.js.br +0 -0
  197. package/dist/public/assets/_BaiP7ogH.js.br +0 -0
  198. package/dist/public/assets/_BbE6iGBd.js.br +0 -0
  199. package/dist/public/assets/_BfZMmKaW.js.br +0 -0
  200. package/dist/public/assets/_BilqQTfw.js.br +0 -0
  201. package/dist/public/assets/_BqNoqWIY.js.br +0 -0
  202. package/dist/public/assets/_BrTLGsp82.js.br +0 -0
  203. package/dist/public/assets/_Brws7C1N.js.br +0 -0
  204. package/dist/public/assets/_BtmFYBHk.js.br +0 -0
  205. package/dist/public/assets/_BuF6uiIm.js.br +0 -0
  206. package/dist/public/assets/_BzULaZGs.js.br +0 -0
  207. package/dist/public/assets/_CG2uZvAG.js.br +0 -0
  208. package/dist/public/assets/_CIio55Q2.js.br +0 -0
  209. package/dist/public/assets/_CJ_9Hl5d.js.br +0 -0
  210. package/dist/public/assets/_CQ-tqbUq.js.br +0 -0
  211. package/dist/public/assets/_CYLlpjQc.js.br +0 -0
  212. package/dist/public/assets/_Ca811EeR.js.br +0 -0
  213. package/dist/public/assets/_CeECfdVo2.js.br +0 -0
  214. package/dist/public/assets/_CeVHIZWy2.js.br +0 -0
  215. package/dist/public/assets/_Ces5jihM.js.br +0 -7
  216. package/dist/public/assets/_CihMY3_B2.js.br +0 -0
  217. package/dist/public/assets/_CkBv_F0f.js.br +0 -0
  218. package/dist/public/assets/_CrPK-ZOH2.js.br +0 -0
  219. package/dist/public/assets/_Cv-4N4xQ.js.br +0 -0
  220. package/dist/public/assets/_Cvly93xo2.js.br +0 -0
  221. package/dist/public/assets/_CwZCc-fE2.js.br +0 -0
  222. package/dist/public/assets/_CwtAB8Ju2.js.br +0 -0
  223. package/dist/public/assets/_CzMjLSTK.js.br +0 -0
  224. package/dist/public/assets/_D11ZFURg.js.br +0 -0
  225. package/dist/public/assets/_D2be5yUI.js.br +0 -0
  226. package/dist/public/assets/_D2xWjoK-2.js.br +0 -0
  227. package/dist/public/assets/_D4V7vTBe.js.br +0 -0
  228. package/dist/public/assets/_D8lg2J6H.js.br +0 -0
  229. package/dist/public/assets/_DBXfsrRd.js.br +0 -0
  230. package/dist/public/assets/_DDag_nXJ.js.br +0 -0
  231. package/dist/public/assets/_DRKLaQtq.js.br +0 -0
  232. package/dist/public/assets/_DWctbEWg.js.br +0 -3
  233. package/dist/public/assets/_DZgx_rAt.js.br +0 -0
  234. package/dist/public/assets/_DdYfAoJz.js.br +0 -0
  235. package/dist/public/assets/_DdnCyOPs.js.br +0 -0
  236. package/dist/public/assets/_DfpMH3So2.js.br +0 -0
  237. package/dist/public/assets/_DhLWIgCs.js.br +0 -0
  238. package/dist/public/assets/_DiaYv9e1.js.br +0 -0
  239. package/dist/public/assets/_Du-i6H8r.js.br +0 -0
  240. package/dist/public/assets/_F8phZWNM2.js.br +0 -0
  241. package/dist/public/assets/_FEbRnVo7.js.br +0 -0
  242. package/dist/public/assets/_GdcmEEuJ.js.br +0 -0
  243. package/dist/public/assets/_N0Ea8Q_R.js.br +0 -0
  244. package/dist/public/assets/_QuFTKxMO2.js.br +0 -0
  245. package/dist/public/assets/_cS3NYZ_K2.js.br +0 -0
  246. package/dist/public/assets/_cdbANa0O.js.br +0 -0
  247. package/dist/public/assets/_dBVoJwVC.js.br +0 -0
  248. package/dist/public/assets/_fR2ilomn.js.br +0 -0
  249. package/dist/public/assets/_h6HS-Zb_2.js.br +0 -0
  250. package/dist/public/assets/_p1VLQxPw.js.br +0 -0
  251. package/dist/public/assets/_uJTU0UZd2.js.br +0 -0
  252. package/dist/public/assets/_wc-pkVQS.js.br +0 -0
  253. package/dist/public/assets/_zJkCtRhb.js.br +0 -0
  254. package/dist/public/assets/app-page-DgsAA8Om.css +0 -1
  255. package/dist/public/assets/app-page-DgsAA8Om.css.br +0 -0
  256. package/dist/public/assets/diff-view-BqwTpf5k.css +0 -1
  257. package/dist/public/assets/diff-view-BqwTpf5k.css.br +0 -0
  258. package/dist/public/assets/new-cBa61wMi.js +0 -1
  259. package/dist/public/assets/settings-cBa61wMi.js +0 -1
  260. package/dist/public/assets/w-cBa61wMi.js +0 -1
  261. package/dist/public/assets/w._-B9llVTHr.js +0 -1
  262. package/dist/public/assets/w._-cBa61wMi.js +0 -1
@@ -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
- * `interactive` hands the terminal over (CLI prompts such as gh's device
15
- * flow); otherwise stdout/err are captured line by line and `input` (if
16
- * any) is written to stdin before it closes. `cwd` matters for tools whose
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(cmd, args, { log = () => {}, interactive = false, input, env, cwd, timeout = 15 * 60_000 } = {}) {
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: interactive ? "inherit" : ["pipe", "pipe", "pipe"],
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.slice(0, i).replace(/\x1b\[[0-9;]*[A-Za-z]/g, "").trimEnd();
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
- if (input) child.stdin.write(input);
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 reject(new Error(`${cmd} ${args.join(" ")} exited ${code}${lines.length ? `: ${lines[lines.length - 1]}` : ""}`));
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, { redirect: "follow", signal: AbortSignal.timeout(60_000) });
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")) throw new Error("mise install finished but binary not found");
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) throw new Error(`${t.label} is a system package. Install it with the system package manager`);
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(idOrName, { version = "latest", ...opts } = {}) {
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))) throw new Error(`${a.label} installer finished but ${a.bins[0]} is not on PATH`);
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
- * Sign in to a GitHub host with gh's browser device flow, never opening a
127
- * browser: `work` usually runs on a remote box, so gh's attempt to launch
128
- * one only produces "Failed opening a web browser" noise. Interactive (CLI)
129
- * lets gh own the terminal (it prints the code + URL and polls); otherwise
130
- * (server) we press Enter for it and the code and URL flow out through
131
- * `log` for the UI to show gh keeps polling until the user finishes in
132
- * their own browser.
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
- const args = ["auth", "login", "--hostname", host, "--web", "--git-protocol", "https"];
137
- const env = { ...opts.env, BROWSER: "true" };
138
- if (interactive) return runStreaming("gh", args, { ...opts, env, interactive: true });
139
- return runStreaming("gh", args, { ...opts, input: "\n", env: { ...env, GH_PROMPT_DISABLED: "1" } });
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, { interactive = false, ...opts } = {}) {
146
- if (!which("gh")) throw new Error("gh is not installed");
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], { ...opts, env: { GH_PROMPT_DISABLED: "1" } });
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.120",
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",
@@ -79,7 +79,8 @@ for (const { path: wsPath } of db
79
79
  noted++;
80
80
  } catch {}
81
81
  }
82
- if (noted) console.log(`==> migrate: noted the shared stash in ${noted} AGENTS.md`);
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) console.log(`==> migrate: restated the stash bullet in ${restated} AGENTS.md`);
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) console.log(`==> migrate: noted rebase-after-merge in ${rebased} AGENTS.md`);
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(`==> migrate: restated branch rules in ${restatedBranches} AGENTS.md`);
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 signals remote-ref updates to the app server.\n` +
239
+ `# installed by work: signals ref updates to the app server.\n` +
233
240
  `[ "$1" = "committed" ] || exit 0\n` +
234
- `grep -q "refs/remotes/origin/" || exit 0\n` +
235
- `curl -sf -m 2 -X POST "http://127.0.0.1:${appPort}` +
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(ownerDir, repoDir, "hooks", "reference-transaction");
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) console.log(`==> migrate: installed push hook in ${hooked} cache repo(s)`);
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.prepare("SELECT workspace_id, full_name FROM repos").all()) {
264
- laneWorkspaces.find((w) => w.id === row.workspace_id)?.repos.push(row.full_name);
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], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
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.filter((w) => w.id !== ws.id).map((w) => w.branch);
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, ["for-each-ref", "--format=%(refname:short) %(objectname)", "refs/heads/"])
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)) continue;
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)) return;
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) console.log(`==> migrate: pruned ${pruned} finished lane branch(es)`);
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
- '- **Ask `work` for facts:** the `work` CLI answers from the same data as\n' +
348
- ' the app: `work workspace` (this workspace: repos, base branches, open PRs,\n' +
349
- ' sessions, running procs/ports), `work repos`, `work procs`,\n' +
350
- ' `work workspaces`, `work doctor` (installed tools / agents / GitHub\n' +
351
- ' sign-in), `work usage` (agent plan quotas). Add `--json` for machine\n' +
352
- ' output. Prefer these over guessing from the filesystem or calling GitHub.\n';
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.prepare("SELECT path FROM workspaces").all()) {
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) console.log(`==> migrate: pointed ${cliNoted} AGENTS.md at the work CLI`);
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.prepare("SELECT path FROM workspaces").all()) {
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(file, text.slice(0, at) + REPO_AGENTS_BULLET + text.slice(at));
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) console.log(`==> migrate: required repo agents files in ${repoNoted} AGENTS.md`);
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.prepare("SELECT id FROM workspaces").all().map((r) => r.id),
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) console.log(`==> migrate: pruned ${pruned} orphaned ui pref(s)`);
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
@@ -1,2 +0,0 @@
1
- import { f as notifySessionsChanged } from "./_DE2RUdbF.js";
2
- export { notifySessionsChanged };
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file