@dylanpiercey/work 0.1.0 → 0.2.0
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 +45 -5
- package/bin/work.mjs +449 -10
- package/dist/_BO3_WJb5.js +169 -0
- package/dist/{_Bq7to522.js → _CzOrFOdN.js} +527 -56
- package/dist/_DiOv9AKj.js +2 -0
- package/dist/index.mjs +8916 -7891
- package/dist/public/assets/{_Dytf99zx.js → _3hcTpTUc.js} +1 -1
- package/dist/public/assets/_3hcTpTUc.js.br +0 -0
- package/dist/public/assets/_88jVvZ6C.js +1 -0
- package/dist/public/assets/{_DrIgZJzN.js → _B0FtZL3L.js} +1 -1
- package/dist/public/assets/_B0FtZL3L.js.br +0 -0
- package/dist/public/assets/{_C0_s45qg.js → _B0rj5kOY.js} +1 -1
- package/dist/public/assets/_B0rj5kOY.js.br +0 -0
- package/dist/public/assets/{_BNW5Jiec.js → _B3aw0q8p.js} +1 -1
- package/dist/public/assets/_B3aw0q8p.js.br +0 -0
- package/dist/public/assets/{_Bhx319Hi.js → _B42XfAog.js} +1 -1
- package/dist/public/assets/_B42XfAog.js.br +0 -0
- package/dist/public/assets/_B5Ih3Lwt.js +1 -0
- package/dist/public/assets/_B6jh-_o_.js +1 -0
- package/dist/public/assets/_B8FHVXDZ.js +1 -0
- package/dist/public/assets/_BBJYNk03.js +1 -0
- package/dist/public/assets/{_D4q8XSMI.js → _BBk5_vEh.js} +1 -1
- package/dist/public/assets/_BBk5_vEh.js.br +0 -0
- package/dist/public/assets/_BCS_gQxR.js +15 -0
- package/dist/public/assets/_BCS_gQxR.js.br +0 -0
- package/dist/public/assets/{_BvXp6HpX.js → _BDBxSbjY.js} +1 -1
- package/dist/public/assets/_BDBxSbjY.js.br +0 -0
- package/dist/public/assets/_BDkqB224.js +1 -0
- package/dist/public/assets/{_SGDxiwE1.js → _BEtl4C9q.js} +1 -1
- package/dist/public/assets/_BEtl4C9q.js.br +0 -0
- package/dist/public/assets/{_DDl8KSWJ.js → _BJZwk-Pf.js} +1 -1
- package/dist/public/assets/_BJZwk-Pf.js.br +0 -0
- package/dist/public/assets/{_CpjSCB1I.js → _BM8qop28.js} +1 -1
- package/dist/public/assets/_BM8qop28.js.br +0 -0
- package/dist/public/assets/_BNL2e5az.js +1 -0
- package/dist/public/assets/{_CEXHZd73.js → _BODl5Jdc.js} +1 -1
- package/dist/public/assets/_BODl5Jdc.js.br +0 -0
- package/dist/public/assets/{_De7P3jUH.js → _BOe3voZI.js} +1 -1
- package/dist/public/assets/_BOe3voZI.js.br +0 -0
- package/dist/public/assets/{_eoa1EOU-.js → _BPW-OoaM.js} +1 -1
- package/dist/public/assets/_BPW-OoaM.js.br +0 -0
- package/dist/public/assets/{_BE_Jyvqd.js → _BSHUa-H0.js} +1 -1
- package/dist/public/assets/_BSHUa-H0.js.br +0 -0
- package/dist/public/assets/_BTXURtlK.js +1 -0
- package/dist/public/assets/{_Bp8-V8yl.js → _BU4lH-F3.js} +1 -1
- package/dist/public/assets/_BU4lH-F3.js.br +0 -0
- package/dist/public/assets/{_wcshV0sZ.js → _BVGSrb9C.js} +1 -1
- package/dist/public/assets/_BVGSrb9C.js.br +0 -0
- package/dist/public/assets/{_CTINBWaG.js → _BXyDScMX.js} +1 -1
- package/dist/public/assets/_BXyDScMX.js.br +0 -0
- package/dist/public/assets/{_Bm4SHrNp.js → _BZXtyNwD.js} +1 -1
- package/dist/public/assets/_BZXtyNwD.js.br +0 -0
- package/dist/public/assets/{_DfI982PI.js → _B_iUZxSk.js} +1 -1
- package/dist/public/assets/_B_iUZxSk.js.br +0 -0
- package/dist/public/assets/_B_klvcQs.js +1 -0
- package/dist/public/assets/{_CHuLFr1f.js → _BaNsw5pr.js} +1 -1
- package/dist/public/assets/_BaNsw5pr.js.br +0 -0
- package/dist/public/assets/_BbPq34k8.js +1 -0
- package/dist/public/assets/{_CAkPJLeF.js → _BfDaM2KU.js} +1 -1
- package/dist/public/assets/{_CAkPJLeF.js.br → _BfDaM2KU.js.br} +0 -0
- package/dist/public/assets/_Bk1naXp4.js +1 -0
- package/dist/public/assets/{_BOGkzMx9.js → _BkNKadGd.js} +1 -1
- package/dist/public/assets/_BkNKadGd.js.br +0 -0
- package/dist/public/assets/_BkztnqUW.js +1 -0
- package/dist/public/assets/_BkztnqUW.js.br +0 -0
- package/dist/public/assets/_BlNBDTvP.js +1 -0
- package/dist/public/assets/_BlNBDTvP.js.br +0 -0
- package/dist/public/assets/_BlUmx1wN.js +1 -0
- package/dist/public/assets/_Ble8b9gd.js +1 -0
- package/dist/public/assets/{_BDa-iwuj.js → _BpEvAl9E.js} +1 -1
- package/dist/public/assets/_BpEvAl9E.js.br +0 -0
- package/dist/public/assets/_Bqyvdrqf.js +1 -0
- package/dist/public/assets/_BsA2bpua.js +1 -0
- package/dist/public/assets/_BsA2bpua.js.br +0 -0
- package/dist/public/assets/_BsAVia13.js +1 -0
- package/dist/public/assets/_BtxLmMRq.js +1 -0
- package/dist/public/assets/{_ClldMY37.js → _Byfemjjt.js} +1 -1
- package/dist/public/assets/_Byfemjjt.js.br +0 -0
- package/dist/public/assets/{_MCwIJAuJ.js → _Bz94qqXL.js} +1 -1
- package/dist/public/assets/_Bz94qqXL.js.br +0 -0
- package/dist/public/assets/_C-HIZYIZ.js +1 -0
- package/dist/public/assets/{_D4quFMVQ.js → _C-UGjoFi.js} +1 -1
- package/dist/public/assets/_C-UGjoFi.js.br +0 -0
- package/dist/public/assets/{_wXDh-Z2N.js → _C-jXvDAe.js} +1 -1
- package/dist/public/assets/_C-jXvDAe.js.br +0 -0
- package/dist/public/assets/{_Ct3RF80L.js → _C2b1kmvc.js} +1 -1
- package/dist/public/assets/_C2b1kmvc.js.br +0 -0
- package/dist/public/assets/{_CIb7xw_1.js → _C3PIXMJF.js} +1 -1
- package/dist/public/assets/_C3PIXMJF.js.br +0 -0
- package/dist/public/assets/_C6eXJBpX.js +1 -0
- package/dist/public/assets/{_C654p96Z.js → _C7Xd05b1.js} +1 -1
- package/dist/public/assets/_C7Xd05b1.js.br +0 -0
- package/dist/public/assets/_C8jPP8tm.js +1 -0
- package/dist/public/assets/{_CamWDaoG.js → _CB9BGKKP.js} +1 -1
- package/dist/public/assets/_CB9BGKKP.js.br +0 -0
- package/dist/public/assets/{_bcsuBP7T.js → _CD44YUtO.js} +1 -1
- package/dist/public/assets/_CD44YUtO.js.br +0 -0
- package/dist/public/assets/{_D6kktUFn.js → _CEXagH_p.js} +1 -1
- package/dist/public/assets/_CEXagH_p.js.br +0 -0
- package/dist/public/assets/_CFOiLJdu.js +1 -0
- package/dist/public/assets/_CFOiLJdu.js.br +0 -0
- package/dist/public/assets/{_D1uRW1qz.js → _CGrClLsS.js} +1 -1
- package/dist/public/assets/_CGrClLsS.js.br +0 -0
- package/dist/public/assets/_CHJuzFIS.js +1 -0
- package/dist/public/assets/{_DC-78WeM.js → _CJ7DGNLC.js} +1 -1
- package/dist/public/assets/_CJ7DGNLC.js.br +0 -0
- package/dist/public/assets/_CJG7LF4o.js +1 -0
- package/dist/public/assets/_CK0tdQ6M.js +1591 -0
- package/dist/public/assets/_CK0tdQ6M.js.br +0 -0
- package/dist/public/assets/{_CMSBfO6u.js → _CKcab_6c.js} +1 -1
- package/dist/public/assets/_CKcab_6c.js.br +0 -0
- package/dist/public/assets/{_8G2Mv7jG.js → _CKpSd2DN.js} +1 -1
- package/dist/public/assets/_CKpSd2DN.js.br +0 -0
- package/dist/public/assets/{_zSAfqXcm.js → _CKy2HduV.js} +1 -1
- package/dist/public/assets/_CKy2HduV.js.br +0 -0
- package/dist/public/assets/{_BUvqweu0.js → _CLikwAg5.js} +1 -1
- package/dist/public/assets/_CLikwAg5.js.br +0 -0
- package/dist/public/assets/{_CxAPL49a.js → _CM-AIl7T.js} +1 -1
- package/dist/public/assets/_CM-AIl7T.js.br +0 -0
- package/dist/public/assets/_CMwrJ1ju.js +1 -0
- package/dist/public/assets/_CMwrJ1ju.js.br +0 -0
- package/dist/public/assets/_CQG-EbMi.js +1 -0
- package/dist/public/assets/_CUl36Gcx.js +1 -0
- package/dist/public/assets/{_BwP07uVS.js → _CUq2djIT.js} +1 -1
- package/dist/public/assets/_CUq2djIT.js.br +0 -0
- package/dist/public/assets/{_DFWOMF-B.js → _CWSGdqC2.js} +1 -1
- package/dist/public/assets/_CWSGdqC2.js.br +0 -0
- package/dist/public/assets/_CXK3AUqn.js +1 -0
- package/dist/public/assets/{_d5u7EI97.js → _CYYRiJuo.js} +1 -1
- package/dist/public/assets/_CYYRiJuo.js.br +0 -0
- package/dist/public/assets/_CYr8GcJI.js +1 -0
- package/dist/public/assets/{_8dvK2NDJ.js → _CaFhYr8X.js} +1 -1
- package/dist/public/assets/_CaFhYr8X.js.br +0 -0
- package/dist/public/assets/_CbzGfu_I.js +1 -0
- package/dist/public/assets/{_CZ_OWtIT.js → _Cd85Xtd1.js} +1 -1
- package/dist/public/assets/_Cd85Xtd1.js.br +0 -0
- package/dist/public/assets/_Ch-bwiFD.js +1 -0
- package/dist/public/assets/_ClGAsZdo.js +1 -0
- package/dist/public/assets/_ClGAsZdo.js.br +0 -0
- package/dist/public/assets/_ClhDmYWO.js +1 -0
- package/dist/public/assets/_ClhDmYWO.js.br +0 -0
- package/dist/public/assets/{_9kX9D09B.js → _Cm85V2m3.js} +1 -1
- package/dist/public/assets/_Cm85V2m3.js.br +0 -0
- package/dist/public/assets/_Cp4ZjGeg.js +1 -0
- package/dist/public/assets/{_C_37_B6u.js → _CsRnEvC3.js} +1 -1
- package/dist/public/assets/_CsRnEvC3.js.br +0 -0
- package/dist/public/assets/_CtFo1511.js +1 -0
- package/dist/public/assets/{_DcMIlvz-.js → _CtIpOiCk.js} +1 -1
- package/dist/public/assets/_CtIpOiCk.js.br +0 -0
- package/dist/public/assets/{_D4rOX-Fa.js → _CwOgQx7H.js} +1 -1
- package/dist/public/assets/_CwOgQx7H.js.br +0 -0
- package/dist/public/assets/_CwtVecVY.js +1 -0
- package/dist/public/assets/_Cxuln-KJ.js +1 -0
- package/dist/public/assets/_CyYNC4Ow.js +1 -0
- package/dist/public/assets/{_Dd6X3e7w.js → _Cy_g0woP.js} +1 -1
- package/dist/public/assets/_Cy_g0woP.js.br +0 -0
- package/dist/public/assets/{_COcePo2y.js → _CzDRlITT.js} +1 -1
- package/dist/public/assets/_CzDRlITT.js.br +0 -0
- package/dist/public/assets/_Czq9fyjZ.js +1 -0
- package/dist/public/assets/{_B582D2rg.js → _D1Rh4MKD.js} +1 -1
- package/dist/public/assets/_D2edy8B-.js +1 -0
- package/dist/public/assets/_D2edy8B-.js.br +0 -0
- package/dist/public/assets/_D2pdXUO3.js +1 -0
- package/dist/public/assets/_D4M5BFEU.js +1 -0
- package/dist/public/assets/{_D0aoEFRx.js → _D749nUfQ.js} +1 -1
- package/dist/public/assets/_D749nUfQ.js.br +0 -0
- package/dist/public/assets/_D7mcWce0.js +2 -0
- package/dist/public/assets/_D7mcWce0.js.br +0 -0
- package/dist/public/assets/{_BpoeUr5j.js → _D81sIY4I.js} +1 -1
- package/dist/public/assets/_D81sIY4I.js.br +0 -0
- package/dist/public/assets/{_D9sS4J_w.js → _D8Opg0DA.js} +1 -1
- package/dist/public/assets/_D8Opg0DA.js.br +0 -0
- package/dist/public/assets/{_B7GwDeKK.js → _DCJrZQ6J.js} +1 -1
- package/dist/public/assets/_DEPHQNx4.js +1 -0
- package/dist/public/assets/_DEtYVOe1.js +1 -0
- package/dist/public/assets/_DJ4gN7dQ.js +1 -0
- package/dist/public/assets/_DJ9NEjwG.js +1 -0
- package/dist/public/assets/{_xyw_DS5P.js → _DJUeUZKF.js} +1 -1
- package/dist/public/assets/_DJUeUZKF.js.br +0 -0
- package/dist/public/assets/_DLZswhbS.js +1 -0
- package/dist/public/assets/{_BywcP-As.js → _DMEtxMrc.js} +1 -1
- package/dist/public/assets/_DMEtxMrc.js.br +0 -0
- package/dist/public/assets/{_DqhphdU4.js → _DOr6ubI-.js} +1 -1
- package/dist/public/assets/_DOr6ubI-.js.br +0 -0
- package/dist/public/assets/{_ch_yXAap.js → _DQu5M2GV.js} +1 -1
- package/dist/public/assets/_DQu5M2GV.js.br +0 -0
- package/dist/public/assets/_DUvup9ew.js +1 -0
- package/dist/public/assets/{_CEJUcePr.js → _DWrhUk5T.js} +1 -1
- package/dist/public/assets/_DWrhUk5T.js.br +0 -0
- package/dist/public/assets/{_Bv3qNE1b.js → _DXOx4zKx.js} +1 -1
- package/dist/public/assets/_D_1b7pob.js +1 -0
- package/dist/public/assets/_Daap2skI.js +1 -0
- package/dist/public/assets/{_CUXajOKj.js → _DcktT8yN.js} +1 -1
- package/dist/public/assets/_DcktT8yN.js.br +0 -0
- package/dist/public/assets/{_D2un443j.js → _Ddc9Y9a_.js} +1 -1
- package/dist/public/assets/_Ddc9Y9a_.js.br +0 -0
- package/dist/public/assets/{_BBWaxDkO.js → _Dfjlzo9W.js} +1 -1
- package/dist/public/assets/_Dfjlzo9W.js.br +0 -0
- package/dist/public/assets/_Dh30YzVM.js +1 -0
- package/dist/public/assets/_DhohfuPc.js +1 -0
- package/dist/public/assets/_DjQz8h-q.js +1 -0
- package/dist/public/assets/_DjX1BGKn.js +1 -0
- package/dist/public/assets/_DjiaxzEs.js +1 -0
- package/dist/public/assets/_Dkg-QEnz.js +1 -0
- package/dist/public/assets/{_Bx1CW-sF.js → _Dl46-33v.js} +1 -1
- package/dist/public/assets/_Dl46-33v.js.br +0 -0
- package/dist/public/assets/_DlvIY6aE.js +1 -0
- package/dist/public/assets/{_DxEm-Gwj.js → _Dmi6AQP3.js} +1 -1
- package/dist/public/assets/_Dmi6AQP3.js.br +0 -0
- package/dist/public/assets/{_BbbOvNRH.js → _Dmj0vHNs.js} +1 -1
- package/dist/public/assets/{_BbbOvNRH.js.br → _Dmj0vHNs.js.br} +0 -0
- package/dist/public/assets/{_DSVLaMWS.js → _DnYboxB9.js} +1 -1
- package/dist/public/assets/_DnYboxB9.js.br +0 -0
- package/dist/public/assets/{_Cz5BPBG8.js → _Do6MGycg.js} +1 -1
- package/dist/public/assets/_Do6MGycg.js.br +0 -0
- package/dist/public/assets/{_DkCaEf_h.js → _DtZ-vebM.js} +1 -1
- package/dist/public/assets/_DtZ-vebM.js.br +0 -0
- package/dist/public/assets/_DuNisSjT.js +1 -0
- package/dist/public/assets/_Dvk-TlMq.js +1 -0
- package/dist/public/assets/_Dvk-TlMq.js.br +0 -0
- package/dist/public/assets/_DwdnhMjL.js +1 -0
- package/dist/public/assets/_DwydmxSi.js +1 -0
- package/dist/public/assets/_DzgHDUu0.js +1 -0
- package/dist/public/assets/{_Dp3IZdH8.js → _G-e2JHHS.js} +1 -1
- package/dist/public/assets/_G-e2JHHS.js.br +0 -0
- package/dist/public/assets/_IOS0xG1B.js +1 -0
- package/dist/public/assets/_IiZnk_jc.js +1 -0
- package/dist/public/assets/{_DaMVRI8f.js → _KYdxhRVL.js} +1 -1
- package/dist/public/assets/_KYdxhRVL.js.br +0 -0
- package/dist/public/assets/_LY1sEEvJ.js +1 -0
- package/dist/public/assets/_MATnTPAL.js +1 -0
- package/dist/public/assets/_MATnTPAL.js.br +0 -0
- package/dist/public/assets/_OVl4e0V3.js +1 -0
- package/dist/public/assets/{_Bf8WYPfh.js → _Sv1HjkeK.js} +1 -1
- package/dist/public/assets/_Sv1HjkeK.js.br +0 -0
- package/dist/public/assets/_Syg4UyQU.js +86 -0
- package/dist/public/assets/_Syg4UyQU.js.br +0 -0
- package/dist/public/assets/_TJCslr2g.js +1 -0
- package/dist/public/assets/_UcXY-7XH.js +2 -0
- package/dist/public/assets/_UcXY-7XH.js.br +0 -0
- package/dist/public/assets/{_j2tDSMvo.js → _VM9JbUGP.js} +1 -1
- package/dist/public/assets/_VM9JbUGP.js.br +0 -0
- package/dist/public/assets/_YOjtJWMK.js +1 -0
- package/dist/public/assets/_YOjtJWMK.js.br +10 -0
- package/dist/public/assets/_YPt61819.js +1 -0
- package/dist/public/assets/{_DbvaGqOl.js → _Zh4-3iG-.js} +1 -1
- package/dist/public/assets/_Zh4-3iG-.js.br +0 -0
- package/dist/public/assets/{_D4dJ2qEE.js → _ZptG1LQC.js} +1 -1
- package/dist/public/assets/_brnakAIx.js +1 -0
- package/dist/public/assets/_bsxJ1YDE.js +1 -0
- package/dist/public/assets/_f5wPpUkq.js +1 -0
- package/dist/public/assets/_fPqLhj85.js +1 -0
- package/dist/public/assets/_g3oXW6NQ.js +1 -0
- package/dist/public/assets/_iHPPeFV7.js +1 -0
- package/dist/public/assets/_iJwEKWn1.js +1 -0
- package/dist/public/assets/_jtFrcV5d.js +1 -0
- package/dist/public/assets/{_Bv3e3rg8.js → _kQ9qYC7a.js} +1 -1
- package/dist/public/assets/_kQ9qYC7a.js.br +0 -0
- package/dist/public/assets/_kQki_UDN.js +1 -0
- package/dist/public/assets/_lRRU15CX.js +1 -0
- package/dist/public/assets/_lRRU15CX.js.br +0 -0
- package/dist/public/assets/_mKx7UwEC.js +1 -0
- package/dist/public/assets/_nnKb_fyX.js +6 -0
- package/dist/public/assets/_nnKb_fyX.js.br +0 -0
- package/dist/public/assets/_p44jCyhe.js +1 -0
- package/dist/public/assets/_pAx6IPLY.js +1 -0
- package/dist/public/assets/_psqSIrEM.js +1 -0
- package/dist/public/assets/{_CyBCIknf.js → _w_IRrfdL.js} +1 -1
- package/dist/public/assets/_w_IRrfdL.js.br +0 -0
- package/dist/public/assets/_wxSNlWQ_.js +1 -0
- package/dist/public/assets/_xPBJn0Hx.js +1 -0
- package/dist/public/assets/_xPBJn0Hx.js.br +0 -0
- package/dist/public/assets/_yP2G9NCR.js +1 -0
- package/dist/public/assets/app-shell-CVYlktwa.css +1 -0
- package/dist/public/assets/app-shell-CVYlktwa.css.br +0 -0
- package/dist/public/assets/files-view-SzdFekvc.js +2 -0
- package/dist/public/assets/manage-workspace-DIY2p0qY.js +2 -0
- package/dist/public/assets/new-dBLsQZ5Y.js +1 -0
- package/dist/public/assets/new-workspace-D6YmZcwM.js +2 -0
- package/dist/public/assets/review-view-Dr5gLqyl.js +2 -0
- package/dist/public/assets/settings-dBLsQZ5Y.js +1 -0
- package/dist/public/assets/settings-page-COg15zf5.js +2 -0
- package/dist/public/assets/styles-DqimleOa.css +1 -0
- package/dist/public/assets/styles-DqimleOa.css.br +0 -0
- package/dist/public/assets/w-dBLsQZ5Y.js +1 -0
- package/dist/public/assets/w._-BbJyr8oS.js +1 -0
- package/dist/public/assets/w._-Bbhvn-OV.js +1 -0
- package/dist/public/assets/w._-dBLsQZ5Y.js +1 -0
- package/dist/rtc-helper.mjs +1170 -0
- package/lib/preview/media.mjs +5 -4
- package/lib/setup/doctor.d.mts +23 -1
- package/lib/setup/doctor.mjs +75 -9
- package/lib/setup/firewall.d.mts +76 -0
- package/lib/setup/firewall.mjs +598 -0
- package/lib/setup/tailscale.d.mts +53 -0
- package/lib/setup/tailscale.mjs +179 -0
- package/package.json +3 -2
- package/dist/_CkwTsHKB.js +0 -2
- package/dist/public/assets/_1RHVsAcK.js +0 -1
- package/dist/public/assets/_4CShjaLP.js +0 -1
- package/dist/public/assets/_68JtNDO1.js +0 -1
- package/dist/public/assets/_8G2Mv7jG.js.br +0 -0
- package/dist/public/assets/_8dvK2NDJ.js.br +0 -0
- package/dist/public/assets/_94L2SIJF.js +0 -1
- package/dist/public/assets/_9AnDb3YH.js +0 -1
- package/dist/public/assets/_9kX9D09B.js.br +0 -0
- package/dist/public/assets/_9lErVegn.js +0 -1
- package/dist/public/assets/_B0oyGUU5.js +0 -1
- package/dist/public/assets/_B0oyGUU5.js.br +0 -0
- package/dist/public/assets/_B1mPnLlp.js +0 -1
- package/dist/public/assets/_B4fi2SSA.js +0 -1
- package/dist/public/assets/_B7Qx25UP.js +0 -1
- package/dist/public/assets/_B7b8EVjA.js +0 -1
- package/dist/public/assets/_BBWaxDkO.js.br +0 -0
- package/dist/public/assets/_BDa-iwuj.js.br +0 -0
- package/dist/public/assets/_BEKHKNmM.js +0 -1
- package/dist/public/assets/_BE_Jyvqd.js.br +0 -0
- package/dist/public/assets/_BFU-kJkk.js +0 -1
- package/dist/public/assets/_BIJ2O2oU.js +0 -1
- package/dist/public/assets/_BJ59XAZ9.js +0 -1
- package/dist/public/assets/_BKqRYfOI.js +0 -15
- package/dist/public/assets/_BKqRYfOI.js.br +0 -0
- package/dist/public/assets/_BKr9Dzz1.js +0 -1
- package/dist/public/assets/_BN7xvxS_.js +0 -1
- package/dist/public/assets/_BNQTVBlH.js +0 -1
- package/dist/public/assets/_BNW5Jiec.js.br +0 -0
- package/dist/public/assets/_BOGkzMx9.js.br +0 -0
- package/dist/public/assets/_BUEpqQ_m.js +0 -1
- package/dist/public/assets/_BUvqweu0.js.br +0 -0
- package/dist/public/assets/_BZKB4fhr.js +0 -1
- package/dist/public/assets/_BZKB4fhr.js.br +0 -0
- package/dist/public/assets/_BZo9BjDF.js +0 -1
- package/dist/public/assets/_BdUNnSlC.js +0 -1
- package/dist/public/assets/_Bf8WYPfh.js.br +0 -0
- package/dist/public/assets/_Bhx319Hi.js.br +0 -0
- package/dist/public/assets/_Bm4SHrNp.js.br +0 -0
- package/dist/public/assets/_Bmrqq8Ro.js +0 -2
- package/dist/public/assets/_Bmrqq8Ro.js.br +0 -0
- package/dist/public/assets/_BnHZ6Npj.js +0 -1
- package/dist/public/assets/_BoDxRJLS.js +0 -1
- package/dist/public/assets/_BoDxRJLS.js.br +0 -0
- package/dist/public/assets/_BoPk1gXL.js +0 -1
- package/dist/public/assets/_Bo_Ru664.js +0 -1
- package/dist/public/assets/_Bp8-V8yl.js.br +0 -0
- package/dist/public/assets/_BpoeUr5j.js.br +0 -0
- package/dist/public/assets/_Bq2G_Tid.js +0 -1
- package/dist/public/assets/_BsyoLB_D.js +0 -1
- package/dist/public/assets/_Bv3e3rg8.js.br +0 -0
- package/dist/public/assets/_BvXp6HpX.js.br +0 -0
- package/dist/public/assets/_BwP07uVS.js.br +0 -0
- package/dist/public/assets/_Bx1CW-sF.js.br +0 -0
- package/dist/public/assets/_BywcP-As.js.br +0 -0
- package/dist/public/assets/_Bzgtwt3G.js +0 -1
- package/dist/public/assets/_Bzgtwt3G.js.br +0 -0
- package/dist/public/assets/_C0_s45qg.js.br +0 -0
- package/dist/public/assets/_C0ePgF6b.js +0 -1
- package/dist/public/assets/_C0oVtTqO.js +0 -1
- package/dist/public/assets/_C0oVtTqO.js.br +0 -0
- package/dist/public/assets/_C654p96Z.js.br +0 -0
- package/dist/public/assets/_C8_1Y2-z.js +0 -1
- package/dist/public/assets/_C8_1Y2-z.js.br +0 -0
- package/dist/public/assets/_C8v1AZFP.js +0 -1
- package/dist/public/assets/_CB0D5cRH.js +0 -1
- package/dist/public/assets/_CD05JUTh.js +0 -1
- package/dist/public/assets/_CEJUcePr.js.br +0 -0
- package/dist/public/assets/_CEXHZd73.js.br +0 -0
- package/dist/public/assets/_CHcUMVR7.js +0 -1
- package/dist/public/assets/_CHuLFr1f.js.br +0 -0
- package/dist/public/assets/_CIb7xw_1.js.br +0 -0
- package/dist/public/assets/_CMSBfO6u.js.br +0 -0
- package/dist/public/assets/_CN7GPyBo.js +0 -1
- package/dist/public/assets/_COcePo2y.js.br +0 -0
- package/dist/public/assets/_CTINBWaG.js.br +0 -0
- package/dist/public/assets/_CTWrnAbz.js +0 -1
- package/dist/public/assets/_CUXajOKj.js.br +0 -0
- package/dist/public/assets/_CVru3vzC.js +0 -1
- package/dist/public/assets/_CYjE__ml.js +0 -1
- package/dist/public/assets/_CYjE__ml.js.br +0 -0
- package/dist/public/assets/_CZ_OWtIT.js.br +0 -0
- package/dist/public/assets/_C_37_B6u.js.br +0 -0
- package/dist/public/assets/_C_CIQL7J.js +0 -1
- package/dist/public/assets/_C_CIQL7J.js.br +0 -0
- package/dist/public/assets/_C_WdpYbK.js +0 -1
- package/dist/public/assets/_CaiH3VN9.js +0 -1
- package/dist/public/assets/_CamWDaoG.js.br +0 -0
- package/dist/public/assets/_CdDElA0V.js +0 -1
- package/dist/public/assets/_CfnsClzZ.js +0 -1
- package/dist/public/assets/_Cgo1fRhJ.js +0 -1
- package/dist/public/assets/_Cgo1fRhJ.js.br +0 -0
- package/dist/public/assets/_CjhGrlf3.js +0 -1
- package/dist/public/assets/_CjlbL4C_.js +0 -1
- package/dist/public/assets/_CjogTUXK.js +0 -1
- package/dist/public/assets/_ClldMY37.js.br +0 -0
- package/dist/public/assets/_CmFq6Gtp.js +0 -1
- package/dist/public/assets/_CoAPOKiP.js +0 -1
- package/dist/public/assets/_CoUUgh9P.js +0 -1
- package/dist/public/assets/_CpjSCB1I.js.br +0 -0
- package/dist/public/assets/_Ct3RF80L.js.br +0 -0
- package/dist/public/assets/_CtYy5eZs.js +0 -1
- package/dist/public/assets/_CxAPL49a.js.br +0 -0
- package/dist/public/assets/_CyBCIknf.js.br +0 -0
- package/dist/public/assets/_Cz5BPBG8.js.br +0 -0
- package/dist/public/assets/_D-eEFXaI.js +0 -1
- package/dist/public/assets/_D0aoEFRx.js.br +0 -0
- package/dist/public/assets/_D1uRW1qz.js.br +0 -0
- package/dist/public/assets/_D2oIr2T2.js +0 -1
- package/dist/public/assets/_D2un443j.js.br +0 -0
- package/dist/public/assets/_D3MPx4-f.js +0 -1
- package/dist/public/assets/_D4q8XSMI.js.br +0 -0
- package/dist/public/assets/_D4quFMVQ.js.br +0 -0
- package/dist/public/assets/_D4rOX-Fa.js.br +0 -0
- package/dist/public/assets/_D5edrSyA.js +0 -1
- package/dist/public/assets/_D6kktUFn.js.br +0 -0
- package/dist/public/assets/_D80-qtVY.js +0 -1
- package/dist/public/assets/_D9-3EVA4.js +0 -1
- package/dist/public/assets/_D9sS4J_w.js.br +0 -0
- package/dist/public/assets/_DBLpxTDi.js +0 -1
- package/dist/public/assets/_DByHQ7nW.js +0 -1591
- package/dist/public/assets/_DByHQ7nW.js.br +0 -0
- package/dist/public/assets/_DC-78WeM.js.br +0 -0
- package/dist/public/assets/_DDl8KSWJ.js.br +0 -0
- package/dist/public/assets/_DFWOMF-B.js.br +0 -0
- package/dist/public/assets/_DKb-rt9n.js +0 -1
- package/dist/public/assets/_DMnSJCF_.js +0 -1
- package/dist/public/assets/_DOOIlrPh.js +0 -1
- package/dist/public/assets/_DPW-0yKP.js +0 -1
- package/dist/public/assets/_DR3BmvWU.js +0 -1
- package/dist/public/assets/_DSVLaMWS.js.br +0 -0
- package/dist/public/assets/_DYNGNKLk.js +0 -1
- package/dist/public/assets/_DZnEgCbj.js +0 -1
- package/dist/public/assets/_DaMVRI8f.js.br +0 -0
- package/dist/public/assets/_DbvaGqOl.js.br +0 -0
- package/dist/public/assets/_DcMIlvz-.js.br +0 -0
- package/dist/public/assets/_Dd6X3e7w.js.br +0 -0
- package/dist/public/assets/_De7P3jUH.js.br +0 -0
- package/dist/public/assets/_DeiFEAar.js +0 -1
- package/dist/public/assets/_DfI982PI.js.br +0 -0
- package/dist/public/assets/_DfZ6emcw.js +0 -1
- package/dist/public/assets/_Dj1lZ1pa.js +0 -86
- package/dist/public/assets/_Dj1lZ1pa.js.br +0 -0
- package/dist/public/assets/_DkCaEf_h.js.br +0 -0
- package/dist/public/assets/_DonAis0J.js +0 -1
- package/dist/public/assets/_Dp3IZdH8.js.br +0 -0
- package/dist/public/assets/_DqYih4xk.js +0 -1
- package/dist/public/assets/_DqYih4xk.js.br +0 -0
- package/dist/public/assets/_DqhphdU4.js.br +0 -0
- package/dist/public/assets/_DrIgZJzN.js.br +0 -0
- package/dist/public/assets/_Dt4hHR05.js +0 -1
- package/dist/public/assets/_DtTbTraA.js +0 -1
- package/dist/public/assets/_DwMxAJKP.js +0 -1
- package/dist/public/assets/_DxEm-Gwj.js.br +0 -0
- package/dist/public/assets/_Dytf99zx.js.br +0 -0
- package/dist/public/assets/_EAgv5jR9.js +0 -1
- package/dist/public/assets/_Iecpd0kS.js +0 -1
- package/dist/public/assets/_Iecpd0kS.js.br +0 -0
- package/dist/public/assets/_MCwIJAuJ.js.br +0 -0
- package/dist/public/assets/_NM79Lu3L.js +0 -6
- package/dist/public/assets/_NM79Lu3L.js.br +0 -0
- package/dist/public/assets/_QpNKKuS9.js +0 -1
- package/dist/public/assets/_SGDxiwE1.js.br +0 -0
- package/dist/public/assets/_TA8pE7z9.js +0 -1
- package/dist/public/assets/_bE4sNDeU.js +0 -1
- package/dist/public/assets/_bcsuBP7T.js.br +0 -0
- package/dist/public/assets/_cdcAU4yY.js +0 -1
- package/dist/public/assets/_ch_yXAap.js.br +0 -0
- package/dist/public/assets/_d5u7EI97.js.br +0 -0
- package/dist/public/assets/_eoa1EOU-.js.br +0 -0
- package/dist/public/assets/_j2tDSMvo.js.br +0 -0
- package/dist/public/assets/_jNhIFgbc.js +0 -1
- package/dist/public/assets/_kDYiTaag.js +0 -1
- package/dist/public/assets/_lNMVVbUx.js +0 -1
- package/dist/public/assets/_nwJ7Hu3S.js +0 -1
- package/dist/public/assets/_tmDtUACa.js +0 -1
- package/dist/public/assets/_tmDtUACa.js.br +0 -0
- package/dist/public/assets/_wXDh-Z2N.js.br +0 -0
- package/dist/public/assets/_wcshV0sZ.js.br +0 -0
- package/dist/public/assets/_x-NwqgCn.js +0 -1
- package/dist/public/assets/_xyw_DS5P.js.br +0 -0
- package/dist/public/assets/_zSAfqXcm.js.br +0 -0
- package/dist/public/assets/app-shell-ChQoVC5b.css +0 -1
- package/dist/public/assets/app-shell-ChQoVC5b.css.br +0 -0
- package/dist/public/assets/files-view-CLtLp_BW.js +0 -2
- package/dist/public/assets/files-view-CNHQapdI.css +0 -1
- package/dist/public/assets/files-view-CNHQapdI.css.br +0 -0
- package/dist/public/assets/group.css.ts-BG82htKb.css +0 -1
- package/dist/public/assets/group.css.ts-BG82htKb.css.br +0 -0
- package/dist/public/assets/icon-folder-DJOHuEpD.css +0 -1
- package/dist/public/assets/icon-folder-DJOHuEpD.css.br +0 -0
- package/dist/public/assets/icon-search-DsJuFXUJ.css +0 -1
- package/dist/public/assets/icon-search-DsJuFXUJ.css.br +0 -0
- package/dist/public/assets/manage-workspace-NBcFDyrB.js +0 -2
- package/dist/public/assets/manage-workspace-f_Np32TD.css +0 -1
- package/dist/public/assets/modal-view-KVNBpNYi.css +0 -1
- package/dist/public/assets/modal-view-KVNBpNYi.css.br +0 -0
- package/dist/public/assets/new-DHI77GvL.js +0 -1
- package/dist/public/assets/new-workspace-BFE-HpD_.css +0 -1
- package/dist/public/assets/new-workspace-BFE-HpD_.css.br +0 -0
- package/dist/public/assets/new-workspace-BRzimdd5.js +0 -2
- package/dist/public/assets/port-chip-CUUIDOVM.css +0 -1
- package/dist/public/assets/port-chip-CUUIDOVM.css.br +0 -0
- package/dist/public/assets/prompt-dialog-BGs6RXOV.css +0 -1
- package/dist/public/assets/prompt-dialog-BGs6RXOV.css.br +0 -0
- package/dist/public/assets/quick-open-Gx4so1bl.css +0 -1
- package/dist/public/assets/quick-open-Gx4so1bl.css.br +0 -0
- package/dist/public/assets/repo-picker-CUhd0vJN.css +0 -1
- package/dist/public/assets/repo-picker-CUhd0vJN.css.br +0 -4
- package/dist/public/assets/review-view-DFEK9VBa.js +0 -2
- package/dist/public/assets/review-view-deUY3OE5.css +0 -1
- package/dist/public/assets/review-view-deUY3OE5.css.br +0 -0
- package/dist/public/assets/section-CziSOSBR.css +0 -1
- package/dist/public/assets/section-CziSOSBR.css.br +0 -0
- package/dist/public/assets/settings-DHI77GvL.js +0 -1
- package/dist/public/assets/settings-page-OOtPOzIn.js +0 -2
- package/dist/public/assets/settings-page-wZhd7FLi.css +0 -1
- package/dist/public/assets/settings-page-wZhd7FLi.css.br +0 -0
- package/dist/public/assets/skill-row-BoCyFIXg.css +0 -1
- package/dist/public/assets/sso-auth-DV76tTpE.css +0 -1
- package/dist/public/assets/sso-auth-DV76tTpE.css.br +0 -0
- package/dist/public/assets/w-DHI77GvL.js +0 -1
- package/dist/public/assets/w._-DHI77GvL.js +0 -1
- package/dist/public/assets/w._-DsTgq3-C.js +0 -1
- package/dist/public/assets/w._-igEOSPR-.js +0 -1
package/README.md
CHANGED
|
@@ -80,13 +80,50 @@ from then on. A refused request gets a page saying which setting it needs.
|
|
|
80
80
|
Everything (pages, API, and the `/term` websockets) serves from the single
|
|
81
81
|
app port, so route the whole hostname to it.
|
|
82
82
|
|
|
83
|
+
**Tailscale** is the zero-config way. On a machine logged in to Tailscale
|
|
84
|
+
(1.52 or newer), `work setup` offers to run `tailscale serve --bg <port>`,
|
|
85
|
+
which puts work at `https://<machine>.<tailnet>.ts.net/` for your devices on
|
|
86
|
+
the tailnet alone (never Funnel, which would publish it to the internet),
|
|
87
|
+
and adds that name to `WORK_ALLOWED_HOSTS`: open it on a phone signed in to
|
|
88
|
+
the same tailnet. `work setup --tailscale` does it without asking and
|
|
89
|
+
`--no-tailscale` skips the question; `--yes` alone leaves Tailscale's config
|
|
90
|
+
as it is. A serve config already pointing at work only has its name
|
|
91
|
+
allowed, and `work doctor` says which of these holds.
|
|
92
|
+
|
|
93
|
+
**Direct connections.** Behind a proxy, a tunnel or a relay, terminals
|
|
94
|
+
move to a direct WebRTC connection to this machine when it is measurably
|
|
95
|
+
faster than the path through the proxy (Settings → Terminal → Direct
|
|
96
|
+
connection: Auto by default, Always, or Off, one setting for every device). Pages,
|
|
97
|
+
the API and sign-in still go through your proxy; the direct path is set up
|
|
98
|
+
over the app's authenticated events socket, carries terminal traffic
|
|
99
|
+
alone, encrypted (DTLS), to UDP port `WORK_RTC_PORT` (by default the app's
|
|
100
|
+
port number), and ends when that login does. That port is held by a helper
|
|
101
|
+
process of work's own, only while direct connections are in use: it
|
|
102
|
+
starts at the first one and exits ten minutes after the last. Proxy-side inspection or
|
|
103
|
+
logging does not see terminal traffic that goes direct: turn the feature
|
|
104
|
+
off (per device in Settings, or `WORK_RTC=off` for the machine) where it
|
|
105
|
+
must. On the LAN the direct path needs nothing more; from another network
|
|
106
|
+
(a phone on cellular) the UDP port has to be reachable through your router.
|
|
107
|
+
Settings → Terminal can allow work to open it on the router when needed
|
|
108
|
+
(UPnP, off by default): only after a device failed to connect without it,
|
|
109
|
+
leased, and given back once unused. A host firewall has to let the port
|
|
110
|
+
in too: `work doctor` says when one likely drops it (firewalld's
|
|
111
|
+
FedoraServer zone does, for one), with the command that admits it, and
|
|
112
|
+
`work setup` offers to add that rule, named `work` so it is removed as one
|
|
113
|
+
(`--firewall` adds it without asking, `--no-firewall` skips the question).
|
|
114
|
+
macOS's firewall admits programs rather than ports, so there it is the
|
|
115
|
+
node the service runs that `work doctor` checks and `work setup` lets in. `WORK_RTC_STUN` names the STUN servers that find this machine's
|
|
116
|
+
public address (Cloudflare's by default; empty for none).
|
|
117
|
+
|
|
83
118
|
**Opening workspace ports remotely.** The port chips in the sidebar link to
|
|
84
119
|
`http://<host>:<port>`, which only works where the machine is reachable
|
|
85
|
-
directly
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
120
|
+
directly — under a tailnet name too, since `tailscale serve` has no
|
|
121
|
+
wildcard names: there a chip reaches a dev server that listens beyond
|
|
122
|
+
loopback (`--host`), and one on loopback says so. Through a tunnel, route
|
|
123
|
+
a wildcard hostname to the app port as well and work proxies it — HTTP and
|
|
124
|
+
websockets, so HMR works — to `localhost:<port>`. `WORK_PORT_HOSTS` picks
|
|
125
|
+
the hostname shape the chips link to; `sub` is always accepted, `dash` only
|
|
126
|
+
when picked, and only for a name of three labels or more:
|
|
90
127
|
|
|
91
128
|
- `sub` (default): `https://3000.work.example.com`. Needs a wildcard
|
|
92
129
|
`*.work.example.com` route and a certificate that covers it (your own
|
|
@@ -147,6 +184,9 @@ targeting that branch.
|
|
|
147
184
|
| `WORK_PORT_HOSTS` | `sub` | Port-chip hostname shape: `sub` / `dash` (see above) |
|
|
148
185
|
| `WORK_USER` / `WORK_HOST` | current user/host | Branch-name prefix parts |
|
|
149
186
|
| `WORK_BRANCH_PREFIX` | `<user>-<host>` | Whole branch-name prefix, overriding the parts |
|
|
187
|
+
| `WORK_RTC` | on | `off` turns direct connections off (see Access) |
|
|
188
|
+
| `WORK_RTC_PORT` | the app port | UDP port of direct connections |
|
|
189
|
+
| `WORK_RTC_STUN` | Cloudflare's STUN | Their STUN servers, comma-separated (empty: none) |
|
|
150
190
|
|
|
151
191
|
The usage panel (and `work usage`) reads plan/quota windows straight from each
|
|
152
192
|
agent CLI's own credentials on disk — Claude, Codex, Cursor, Grok, and Copilot
|
package/bin/work.mjs
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
// work CLI — ships with the npm package (no dependencies; plain node).
|
|
3
3
|
//
|
|
4
4
|
// work setup install missing tools, sign in, install/refresh the background service
|
|
5
|
+
// (--tailscale serves work on the tailnet with `tailscale serve`, which
|
|
6
|
+
// it otherwise offers when Tailscale is logged in; --no-tailscale skips that;
|
|
7
|
+
// --firewall adds a `work` rule for direct connections' UDP port to a host
|
|
8
|
+
// firewall that likely drops it, or on macOS lets the service's node in,
|
|
9
|
+
// which it otherwise offers; --no-firewall skips that)
|
|
5
10
|
// work doctor report machine readiness (--json, --fix to install what's missing)
|
|
6
11
|
// work start run the server in the foreground
|
|
7
12
|
// work update upgrade the npm package and refresh the service
|
|
@@ -26,8 +31,23 @@ import { execFileSync, spawnSync } from "node:child_process";
|
|
|
26
31
|
import fs from "node:fs";
|
|
27
32
|
import os from "node:os";
|
|
28
33
|
import path from "node:path";
|
|
34
|
+
import readline from "node:readline/promises";
|
|
35
|
+
import tty from "node:tty";
|
|
29
36
|
import { fileURLToPath } from "node:url";
|
|
30
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
doctor,
|
|
39
|
+
formatDoctor,
|
|
40
|
+
setupPath,
|
|
41
|
+
tailscaleProbe,
|
|
42
|
+
tilde,
|
|
43
|
+
which,
|
|
44
|
+
} from "../lib/setup/doctor.mjs";
|
|
45
|
+
import { allowHost, stateText } from "../lib/setup/tailscale.mjs";
|
|
46
|
+
import {
|
|
47
|
+
applyPlan,
|
|
48
|
+
firewallProbe,
|
|
49
|
+
serviceNode,
|
|
50
|
+
} from "../lib/setup/firewall.mjs";
|
|
31
51
|
import { installTool, repairGhHelpers } from "../lib/setup/install.mjs";
|
|
32
52
|
import { bridgeCommands, bridgeIfNeeded } from "../lib/setup/bridge.mjs";
|
|
33
53
|
import { dialHost, listenStream } from "../lib/setup/listen.mjs";
|
|
@@ -638,17 +658,24 @@ function until(iso) {
|
|
|
638
658
|
|
|
639
659
|
async function setup() {
|
|
640
660
|
await bridge();
|
|
641
|
-
|
|
661
|
+
const d = await doctor(doctorOpts());
|
|
662
|
+
await fix(d);
|
|
663
|
+
// Before the service is written: a tailnet name to answer goes into the
|
|
664
|
+
// WORK_ALLOWED_HOSTS it carries.
|
|
665
|
+
const tailnet = await tailscale(d.tailscale);
|
|
666
|
+
await firewall();
|
|
642
667
|
|
|
643
668
|
const running = installService();
|
|
644
669
|
const port = serviceEnv().PORT;
|
|
645
|
-
maybeTunnel(port);
|
|
670
|
+
if (!tailnet) maybeTunnel(port);
|
|
646
671
|
if (running) {
|
|
647
672
|
if (await waitForServer()) log(`done — http://localhost:${port}`);
|
|
648
673
|
else
|
|
649
674
|
warn(
|
|
650
675
|
`service started but nothing answered on port ${port} within 30s — check the logs`,
|
|
651
676
|
);
|
|
677
|
+
if (tailnet)
|
|
678
|
+
log(`on your phone and other devices on the tailnet: ${tailnet}`);
|
|
652
679
|
} else {
|
|
653
680
|
// A fresh install's shell may not have mise on PATH yet — name the shim.
|
|
654
681
|
const shim = path.join(
|
|
@@ -725,28 +752,195 @@ async function update() {
|
|
|
725
752
|
"@dylanpiercey/work@latest",
|
|
726
753
|
]);
|
|
727
754
|
// Re-exec the (possibly new) CLI to finish with fresh code.
|
|
728
|
-
sh("work", [
|
|
755
|
+
sh("work", [
|
|
756
|
+
"setup",
|
|
757
|
+
...(yes ? ["--yes"] : []),
|
|
758
|
+
...args.filter((a) =>
|
|
759
|
+
["--tailscale", "--no-tailscale", "--firewall", "--no-firewall"].includes(
|
|
760
|
+
a,
|
|
761
|
+
),
|
|
762
|
+
),
|
|
763
|
+
]);
|
|
729
764
|
}
|
|
730
765
|
|
|
731
766
|
/** Machine readiness from lib/setup — the same probe the Settings page runs. */
|
|
732
767
|
async function doctorCmd() {
|
|
733
|
-
let d = await doctor();
|
|
768
|
+
let d = await doctor(doctorOpts());
|
|
734
769
|
if (args.includes("--fix")) {
|
|
735
770
|
await fix(d);
|
|
736
|
-
d = await doctor();
|
|
771
|
+
d = await doctor(doctorOpts());
|
|
737
772
|
}
|
|
773
|
+
const installs = await workInstalls();
|
|
738
774
|
if (args.includes("--json")) {
|
|
739
|
-
console.log(JSON.stringify(d, null, 2));
|
|
775
|
+
console.log(JSON.stringify({ ...d, work: installs }, null, 2));
|
|
740
776
|
} else {
|
|
741
777
|
console.log(formatDoctor(d));
|
|
742
778
|
const access = accessNote();
|
|
743
779
|
if (access) warn(access);
|
|
780
|
+
for (const note of installNotes(installs)) warn(note);
|
|
744
781
|
}
|
|
745
782
|
// exitCode, not exit(): a pipe on macOS is written asynchronously, and
|
|
746
783
|
// exiting at once can cut the JSON short for whatever reads it.
|
|
747
784
|
if (!d.ready) process.exitCode = 1;
|
|
748
785
|
}
|
|
749
786
|
|
|
787
|
+
/**
|
|
788
|
+
* This `work`, every other one on PATH (the first is what `work` runs),
|
|
789
|
+
* each with its version, and the version the running service reports
|
|
790
|
+
* (null when none answers). A package manager's global left behind — a
|
|
791
|
+
* Homebrew node's under mise's — shadows the one kept up to date, and the
|
|
792
|
+
* doctor it runs is then old code describing a machine it no longer knows.
|
|
793
|
+
*/
|
|
794
|
+
async function workInstalls() {
|
|
795
|
+
const self = realpathOr(fileURLToPath(import.meta.url));
|
|
796
|
+
const onPath = [];
|
|
797
|
+
const seen = new Set();
|
|
798
|
+
for (const dir of (process.env.PATH || "").split(path.delimiter)) {
|
|
799
|
+
if (!dir) continue;
|
|
800
|
+
const file = path.join(dir, "work");
|
|
801
|
+
let real;
|
|
802
|
+
try {
|
|
803
|
+
if (!fs.statSync(file).isFile()) continue;
|
|
804
|
+
real = fs.realpathSync(file);
|
|
805
|
+
} catch {
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
if (seen.has(real)) continue;
|
|
809
|
+
seen.add(real);
|
|
810
|
+
onPath.push({ path: file, real, version: installVersion(real, file) });
|
|
811
|
+
}
|
|
812
|
+
let service = null;
|
|
813
|
+
try {
|
|
814
|
+
const res = await fetch(`${serverUrl()}/api/boot`, {
|
|
815
|
+
signal: AbortSignal.timeout(2000),
|
|
816
|
+
});
|
|
817
|
+
if (res.ok) service = (await res.json()).version ?? null;
|
|
818
|
+
} catch {}
|
|
819
|
+
return { self: { path: self, version: pkg.version }, onPath, service };
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function realpathOr(file) {
|
|
823
|
+
try {
|
|
824
|
+
return fs.realpathSync(file);
|
|
825
|
+
} catch {
|
|
826
|
+
return file;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/** An install's version: an npm global's bin links into the package, whose
|
|
831
|
+
* package.json says; a shim (mise's) is asked. */
|
|
832
|
+
function installVersion(real, file) {
|
|
833
|
+
let dir = path.dirname(real);
|
|
834
|
+
for (let i = 0; i < 3; i++) {
|
|
835
|
+
try {
|
|
836
|
+
const p = JSON.parse(fs.readFileSync(path.join(dir, "package.json")));
|
|
837
|
+
if (p.name === "@dylanpiercey/work") return p.version ?? null;
|
|
838
|
+
} catch {}
|
|
839
|
+
dir = path.dirname(dir);
|
|
840
|
+
}
|
|
841
|
+
const out = spawnSync(file, ["version"], { encoding: "utf8", timeout: 5000 });
|
|
842
|
+
return /^\d+\.\d+\.\d+\S*/.exec((out.stdout || "").trim())?.[0] ?? null;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/** How to remove an install: the npm beside the node whose global it is. */
|
|
846
|
+
function removeHint(real) {
|
|
847
|
+
const m =
|
|
848
|
+
/^(.*)\/lib\/node_modules\/@dylanpiercey\/work\/bin\/work\.mjs$/.exec(real);
|
|
849
|
+
return m
|
|
850
|
+
? `${m[1]}/bin/npm uninstall -g @dylanpiercey/work`
|
|
851
|
+
: `remove ${real}`;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function newest(versions) {
|
|
855
|
+
const parts = (v) => (v || "0").split(/[.-]/).map((n) => Number(n) || 0);
|
|
856
|
+
return versions.reduce((a, b) => {
|
|
857
|
+
const [x, y] = [parts(a), parts(b)];
|
|
858
|
+
for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] > y[i] ? a : b;
|
|
859
|
+
return a;
|
|
860
|
+
}, null);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/** The doctor's warnings about which `work` this is. */
|
|
864
|
+
function installNotes({ self, onPath, service }) {
|
|
865
|
+
const notes = [];
|
|
866
|
+
if (service && service !== self.version) {
|
|
867
|
+
const theirs = onPath.find((w) => w.version === service);
|
|
868
|
+
notes.push(
|
|
869
|
+
`this work is ${self.version} (${tilde(self.path)}), but the running service is ${service}, so what this doctor says is an older or newer release's${theirs ? `: the service's is ${tilde(theirs.path)}` : ""}`,
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
if (onPath.length > 1) {
|
|
873
|
+
// A stray is one at another release than the service's (or, with no
|
|
874
|
+
// service answering, than the newest here).
|
|
875
|
+
const current = service ?? newest(onPath.map((w) => w.version));
|
|
876
|
+
const lines = onPath.map(
|
|
877
|
+
(w, i) =>
|
|
878
|
+
` ${tilde(w.path)} → ${w.version ?? "unknown version"}${i === 0 ? " (what \`work\` runs)" : ""}${w.version === current ? "" : `\n remove: ${removeHint(w.real)}`}`,
|
|
879
|
+
);
|
|
880
|
+
notes.push(`more than one work is on PATH:\n${lines.join("\n")}`);
|
|
881
|
+
}
|
|
882
|
+
return notes;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/** What the doctor's Tailscale probe checks against: the port and allowed
|
|
886
|
+
* names the service runs with (or will, after `work setup`). */
|
|
887
|
+
function doctorOpts() {
|
|
888
|
+
const env = serviceEnv();
|
|
889
|
+
return {
|
|
890
|
+
port: env.PORT,
|
|
891
|
+
allowedHosts: env.WORK_ALLOWED_HOSTS || "",
|
|
892
|
+
rtcPort: env.WORK_RTC_PORT || "",
|
|
893
|
+
listen: env.WORK_LISTEN,
|
|
894
|
+
rtc: env.WORK_RTC || "",
|
|
895
|
+
firewall: serviceFirewall(),
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* The host firewall as the service meets it. macOS's admits programs, so
|
|
901
|
+
* the node that counts is the one the service runs, which need not be this
|
|
902
|
+
* shell's: asked of the running server first (`GET /api/direct`, its own
|
|
903
|
+
* reading), else read here for the node its definition runs
|
|
904
|
+
* (`serviceNode`), else for this one's, which the result says
|
|
905
|
+
* (`nodeFrom`). `fresh` skips the server's few minutes of keeping it.
|
|
906
|
+
*/
|
|
907
|
+
async function serviceFirewall({ fresh = false } = {}) {
|
|
908
|
+
const env = serviceEnv();
|
|
909
|
+
try {
|
|
910
|
+
const res = await fetch(
|
|
911
|
+
`${serverUrl()}/api/direct${fresh ? "?fresh=1" : ""}`,
|
|
912
|
+
{ signal: AbortSignal.timeout(3000) },
|
|
913
|
+
);
|
|
914
|
+
const live = res.ok ? await res.json() : null;
|
|
915
|
+
if (live && "firewall" in live)
|
|
916
|
+
return (
|
|
917
|
+
live.firewall && {
|
|
918
|
+
...live.firewall,
|
|
919
|
+
node: live.firewall.node ?? live.node,
|
|
920
|
+
nodeFrom: "service",
|
|
921
|
+
}
|
|
922
|
+
);
|
|
923
|
+
} catch {}
|
|
924
|
+
let definition = null;
|
|
925
|
+
try {
|
|
926
|
+
definition = fs.readFileSync(serviceFile(), "utf8");
|
|
927
|
+
} catch {}
|
|
928
|
+
const fromUnit =
|
|
929
|
+
definition &&
|
|
930
|
+
serviceNode(definition, { PATH: installedServiceEnv().PATH || "" });
|
|
931
|
+
const node = fromUnit || process.execPath;
|
|
932
|
+
const f = await firewallProbe({
|
|
933
|
+
rtcPort: env.WORK_RTC_PORT,
|
|
934
|
+
appPort: env.PORT,
|
|
935
|
+
listen: env.WORK_LISTEN,
|
|
936
|
+
rtc: env.WORK_RTC,
|
|
937
|
+
node,
|
|
938
|
+
});
|
|
939
|
+
return (
|
|
940
|
+
f && { ...f, node: f.node ?? node, nodeFrom: fromUnit ? "unit" : "shell" }
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
|
|
750
944
|
/** Said when the service listens beyond this machine with nothing declared
|
|
751
945
|
* to answer there: every other machine is refused (server/access.ts), which
|
|
752
946
|
* is safe, but likely not what the listening was for. */
|
|
@@ -956,6 +1150,9 @@ function serviceEnv() {
|
|
|
956
1150
|
"WORK_ALLOWED_HOSTS",
|
|
957
1151
|
"WORK_LISTEN",
|
|
958
1152
|
"WORK_TRUSTED_PROXIES",
|
|
1153
|
+
"WORK_RTC",
|
|
1154
|
+
"WORK_RTC_PORT",
|
|
1155
|
+
"WORK_RTC_STUN",
|
|
959
1156
|
]) {
|
|
960
1157
|
// `in` rather than a truthy check, so `WORK_ALLOWED_HOSTS= work setup`
|
|
961
1158
|
// can deliberately clear a setting the installed service still carries.
|
|
@@ -1312,8 +1509,250 @@ function maybeTunnel(port = appPort) {
|
|
|
1312
1509
|
if (yes || have("cloudflared")) return;
|
|
1313
1510
|
console.log(`
|
|
1314
1511
|
To reach work from your phone or other devices, put it behind something
|
|
1315
|
-
that terminates TLS and authenticates, e.g.
|
|
1512
|
+
that terminates TLS and authenticates, e.g. Tailscale (\`work setup
|
|
1513
|
+
--tailscale\` runs \`tailscale serve\` for you) or a Cloudflare Tunnel with
|
|
1316
1514
|
Access (https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/)
|
|
1317
|
-
pointing at http://localhost:${port}
|
|
1318
|
-
|
|
1515
|
+
pointing at http://localhost:${port}. The server itself has no auth of its
|
|
1516
|
+
own.`);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
/**
|
|
1520
|
+
* `tailscale serve` in front of work: https://<machine>.<tailnet>.ts.net/,
|
|
1521
|
+
* TLS and the tailnet's own sign-in in the local daemon, proxied to work's
|
|
1522
|
+
* port, so requests arrive from this machine under that name — which goes
|
|
1523
|
+
* into WORK_ALLOWED_HOSTS. Tailnet only, never Funnel: Funnel publishes to
|
|
1524
|
+
* the internet, and work has no login of its own. Tailscale's config is
|
|
1525
|
+
* changed only on an explicit yes, the prompt's or `--tailscale`; `--yes`
|
|
1526
|
+
* is how the installer, `work update` and the app's own update run setup
|
|
1527
|
+
* unattended, and none of them asked for it. A serve config already
|
|
1528
|
+
* pointing at work only has its name allowed. Returns the URL work is
|
|
1529
|
+
* served at, or null.
|
|
1530
|
+
*/
|
|
1531
|
+
async function tailscale(ts) {
|
|
1532
|
+
if (args.includes("--no-tailscale")) return null;
|
|
1533
|
+
const asked = args.includes("--tailscale");
|
|
1534
|
+
const port = serviceEnv().PORT;
|
|
1535
|
+
if (!ts?.installed) {
|
|
1536
|
+
if (asked)
|
|
1537
|
+
warn(
|
|
1538
|
+
"Tailscale is not installed: https://tailscale.com/download, then work setup --tailscale",
|
|
1539
|
+
);
|
|
1540
|
+
return null;
|
|
1541
|
+
}
|
|
1542
|
+
if (ts.serve) {
|
|
1543
|
+
if (!allowTailnet(ts.serve)) return null;
|
|
1544
|
+
if (ts.running) return servedAt(ts.serve.url);
|
|
1545
|
+
// A serve config outlives a logout, and serves again once it is up.
|
|
1546
|
+
log(
|
|
1547
|
+
`Tailscale is ${stateText(ts)}: work is at ${ts.serve.url} once it is up`,
|
|
1548
|
+
);
|
|
1549
|
+
return null;
|
|
1550
|
+
}
|
|
1551
|
+
if (!ts.running) {
|
|
1552
|
+
if (asked)
|
|
1553
|
+
warn(
|
|
1554
|
+
`Tailscale is ${stateText(ts)}: ${ts.state === null ? "start tailscaled" : "run `tailscale up`"}, then work setup --tailscale`,
|
|
1555
|
+
);
|
|
1556
|
+
return null;
|
|
1557
|
+
}
|
|
1558
|
+
const input = yes ? null : terminal();
|
|
1559
|
+
try {
|
|
1560
|
+
const refusal = serveRefusal(ts, port);
|
|
1561
|
+
if (refusal) {
|
|
1562
|
+
if (asked || input) warn(refusal);
|
|
1563
|
+
return null;
|
|
1564
|
+
}
|
|
1565
|
+
const target = serveTarget(port);
|
|
1566
|
+
const command = `tailscale serve --bg ${target}`;
|
|
1567
|
+
if (!asked) {
|
|
1568
|
+
if (!input) {
|
|
1569
|
+
log(
|
|
1570
|
+
`Tailscale is logged in: \`work setup --tailscale\` serves work at https://${ts.name}/ on your tailnet`,
|
|
1571
|
+
);
|
|
1572
|
+
return null;
|
|
1573
|
+
}
|
|
1574
|
+
console.log(`
|
|
1575
|
+
Tailscale is logged in here as ${ts.name}.
|
|
1576
|
+
\`${command}\` puts work at https://${ts.name}/
|
|
1577
|
+
for your devices on the tailnet (only them: never Funnel), proxied by the
|
|
1578
|
+
local Tailscale daemon to port ${port}.${ts.https === false ? "\nHTTPS certificates are off on your tailnet: tailscale prints a link that\nturns them on." : ""}`);
|
|
1579
|
+
if (!(await confirm(input, `Run \`${command}\`? [y/N] `))) {
|
|
1580
|
+
log(
|
|
1581
|
+
"left Tailscale as it was (work setup --tailscale serves work later; --no-tailscale skips this question)",
|
|
1582
|
+
);
|
|
1583
|
+
return null;
|
|
1584
|
+
}
|
|
1585
|
+
} else if (ts.https === false && !input) {
|
|
1586
|
+
// tailscale serve would wait, unattended, for someone to follow the
|
|
1587
|
+
// link it prints.
|
|
1588
|
+
warn(
|
|
1589
|
+
"HTTPS certificates are off on your tailnet: turn them on (https://login.tailscale.com/admin/dns), then run work setup --tailscale",
|
|
1590
|
+
);
|
|
1591
|
+
return null;
|
|
1592
|
+
}
|
|
1593
|
+
log(command);
|
|
1594
|
+
spawnSync(which("tailscale"), ["serve", "--bg", target], {
|
|
1595
|
+
stdio: "inherit",
|
|
1596
|
+
});
|
|
1597
|
+
} finally {
|
|
1598
|
+
if (input && input !== process.stdin) input.destroy();
|
|
1599
|
+
}
|
|
1600
|
+
// Its exit status is no proof: it exits 0 having only printed how to
|
|
1601
|
+
// turn HTTPS on, when it does not wait for that.
|
|
1602
|
+
const after = await tailscaleProbe({ port, allowedHosts: "" });
|
|
1603
|
+
if (!after.serve) {
|
|
1604
|
+
warn(
|
|
1605
|
+
"tailscale serve did not serve work (see above); once it does, run work setup again",
|
|
1606
|
+
);
|
|
1607
|
+
return null;
|
|
1608
|
+
}
|
|
1609
|
+
return allowTailnet(after.serve) ? servedAt(after.serve.url) : null;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/**
|
|
1613
|
+
* A rule in the host firewall for direct connections' UDP port (and the
|
|
1614
|
+
* app's TCP port too when the service listens beyond this machine), named
|
|
1615
|
+
* `work` so it is found and removed as one: a firewalld service enabled in
|
|
1616
|
+
* the active zone, or ufw rules with that comment (lib/setup/firewall.mjs);
|
|
1617
|
+
* on macOS, whose firewall admits programs, the service's node let in
|
|
1618
|
+
* (`serviceFirewall`; "Block all incoming connections" is only said).
|
|
1619
|
+
* Added with sudo, and only on an explicit yes, the prompt's or
|
|
1620
|
+
* `--firewall`: `--yes` is how the installer, `work update` and the app's
|
|
1621
|
+
* own update run setup unattended, and none of them asked for it. What it
|
|
1622
|
+
* runs is printed as it runs.
|
|
1623
|
+
*/
|
|
1624
|
+
async function firewall() {
|
|
1625
|
+
if (args.includes("--no-firewall")) return;
|
|
1626
|
+
const asked = args.includes("--firewall");
|
|
1627
|
+
const env = serviceEnv();
|
|
1628
|
+
const f = await serviceFirewall();
|
|
1629
|
+
if (!f) {
|
|
1630
|
+
if (asked && env.WORK_RTC !== "off")
|
|
1631
|
+
log("no active host firewall: nothing to add");
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
if (f.admits) {
|
|
1635
|
+
if (asked) log(`${f.what} already admits UDP ${f.port}`);
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
const drops = `${f.what} ${f.exact ? "drops" : "likely drops"} UDP ${f.port}${f.why ? ` (${f.why})` : ""}, which direct connections from your other devices use`;
|
|
1639
|
+
if (!f.plan.length) {
|
|
1640
|
+
if (asked || !yes) warn(`${drops}: ${f.fix}`);
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
// macOS's firewall admits a program: the service's node, as found.
|
|
1644
|
+
const adds =
|
|
1645
|
+
f.kind === "macos" ? `let ${f.node} in` : "add a rule named `work`";
|
|
1646
|
+
if (f.kind === "macos" && f.nodeFrom === "shell")
|
|
1647
|
+
warn(
|
|
1648
|
+
`neither a running service nor its definition said which node it runs: this is for this shell's, ${f.node}`,
|
|
1649
|
+
);
|
|
1650
|
+
const input = yes || asked ? null : terminal();
|
|
1651
|
+
try {
|
|
1652
|
+
if (!asked) {
|
|
1653
|
+
if (!input) {
|
|
1654
|
+
log(`${drops}: \`work setup --firewall\` can ${adds}`);
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
console.log(`
|
|
1658
|
+
${drops}.
|
|
1659
|
+
\`work setup\` can ${adds} for it, with sudo:
|
|
1660
|
+
${f.plan.map((cmd) => cmd.join(" ")).join("\n ")}`);
|
|
1661
|
+
if (!(await confirm(input, "Add it? [y/N] "))) {
|
|
1662
|
+
log(
|
|
1663
|
+
"left the firewall as it was (work setup --firewall adds it later; --no-firewall skips this question)",
|
|
1664
|
+
);
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
} finally {
|
|
1669
|
+
if (input && input !== process.stdin) input.destroy();
|
|
1670
|
+
}
|
|
1671
|
+
const applied = applyPlan(f.plan, log);
|
|
1672
|
+
if (!applied.ok) {
|
|
1673
|
+
warn(
|
|
1674
|
+
`\`${applied.failed?.join(" ")}\` failed, so the rule may be part-made: run the rest of\n ${f.plan.map((c) => c.join(" ")).join("\n ")}`,
|
|
1675
|
+
);
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
const after = await serviceFirewall({ fresh: true });
|
|
1679
|
+
if (after?.admits) log(`${after.what} admits UDP ${after.port}`);
|
|
1680
|
+
else warn(`${f.what} still looks shut to UDP ${f.port}: see work doctor`);
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
function servedAt(url) {
|
|
1684
|
+
log(`Tailscale serves work at ${url} (tailnet only)`);
|
|
1685
|
+
return url;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
/** Why `tailscale serve --bg <port>` is not run here, or null. */
|
|
1689
|
+
function serveRefusal(ts, port) {
|
|
1690
|
+
if (!ts.serveCli)
|
|
1691
|
+
return `tailscale ${ts.version || "(unknown version)"} predates \`tailscale serve --bg <port>\` (1.52): update Tailscale, then run work setup --tailscale`;
|
|
1692
|
+
// It would replace whatever serves the name's root now.
|
|
1693
|
+
if (ts.rootTaken)
|
|
1694
|
+
return `https://${ts.name}/ already serves ${ts.rootTaken}, which work leaves alone. To serve work beside it: tailscale serve --bg --https=8443 ${port}, then work setup`;
|
|
1695
|
+
if (!serveTarget(port))
|
|
1696
|
+
return `work listens on ${serviceEnv().WORK_LISTEN} alone, and tailscale serve proxies to loopback: WORK_LISTEN= work setup --tailscale`;
|
|
1697
|
+
return null;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
/** What `tailscale serve` proxies to: work's port on loopback, where
|
|
1701
|
+
* requests are this machine's (server/access.ts). Null when work listens
|
|
1702
|
+
* on one other address only, which loopback does not reach. */
|
|
1703
|
+
function serveTarget(port) {
|
|
1704
|
+
const listen = serviceEnv().WORK_LISTEN;
|
|
1705
|
+
if (listen === "::1") return `http://[::1]:${port}`;
|
|
1706
|
+
return ["127.0.0.1", "0.0.0.0", "::", "*"].includes(listen)
|
|
1707
|
+
? String(port)
|
|
1708
|
+
: null;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/** Put the name tailscale serve serves work under in WORK_ALLOWED_HOSTS,
|
|
1712
|
+
* for installService() to write — or, when Funnel publishes that name to
|
|
1713
|
+
* the internet, take it out, so work refuses the internet there. False
|
|
1714
|
+
* then. */
|
|
1715
|
+
function allowTailnet(serve) {
|
|
1716
|
+
const current = serviceEnv().WORK_ALLOWED_HOSTS || "";
|
|
1717
|
+
const next = allowHost(current, serve.host, !serve.funnel);
|
|
1718
|
+
if (next !== current) {
|
|
1719
|
+
process.env.WORK_ALLOWED_HOSTS = next;
|
|
1720
|
+
log(
|
|
1721
|
+
`${serve.funnel ? "refusing" : "allowing"} ${serve.host} (WORK_ALLOWED_HOSTS=${next})`,
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
if (serve.funnel) {
|
|
1725
|
+
warn(
|
|
1726
|
+
`Tailscale Funnel publishes ${serve.url} to the internet, and work has no login, so work refuses that name. Turn Funnel off (tailscale funnel --https=${new URL(serve.url).port || 443} off), then run work setup again`,
|
|
1727
|
+
);
|
|
1728
|
+
return false;
|
|
1729
|
+
}
|
|
1730
|
+
return true;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
/** Where to ask the user something: stdin when it is a terminal, else the
|
|
1734
|
+
* controlling terminal, since the curl installer pipes its script into the
|
|
1735
|
+
* shell this runs under. Null with no terminal at all. */
|
|
1736
|
+
function terminal() {
|
|
1737
|
+
if (process.stdin.isTTY) return process.stdin;
|
|
1738
|
+
try {
|
|
1739
|
+
return new tty.ReadStream(fs.openSync("/dev/tty", "r"));
|
|
1740
|
+
} catch {
|
|
1741
|
+
return null;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
/** A yes/no question, no unless answered yes. Line-buffered by the
|
|
1746
|
+
* terminal itself, so Ctrl-C stops setup as it would anywhere else. */
|
|
1747
|
+
async function confirm(input, question) {
|
|
1748
|
+
const rl = readline.createInterface({
|
|
1749
|
+
input,
|
|
1750
|
+
output: process.stdout,
|
|
1751
|
+
terminal: false,
|
|
1752
|
+
});
|
|
1753
|
+
try {
|
|
1754
|
+
return /^y(es)?$/i.test((await rl.question(question)).trim());
|
|
1755
|
+
} finally {
|
|
1756
|
+
rl.close();
|
|
1757
|
+
}
|
|
1319
1758
|
}
|