@puddle-code/cli 0.2.4 → 0.2.7
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/CHANGELOG.md +4 -14
- package/README.md +33 -23
- package/dist/host-control.mjs +7 -4
- package/dist/index.js +508 -315
- package/dist/install-cli.sh +167 -0
- package/dist/install.sh +7 -10
- package/dist/public/assets/{App-C2IQg_6K.js → App-BkCGcO4p.js} +2 -2
- package/dist/public/assets/{Dashboard-DUFOyL1_.js → Dashboard-BOK1fw_Q.js} +1 -1
- package/dist/public/assets/{EditorTabClose-C3wpMhYL.js → EditorTabClose-CzMQj4jF.js} +1 -1
- package/dist/public/assets/{FindWidget-Dt1gfA70.js → FindWidget-6LifDkeS.js} +1 -1
- package/dist/public/assets/{LazyTerminal-B9HzmRBx.js → LazyTerminal-Beep0VuJ.js} +2 -2
- package/dist/public/assets/{ModelRefcount-CxzQ3w05.js → ModelRefcount-F2Tf2V5f.js} +1 -1
- package/dist/public/assets/{NewProjectDialog-Cq_1po_h.js → NewProjectDialog-CoI17sqS.js} +1 -1
- package/dist/public/assets/{PaneEditorBody-zS2BI5Fn.js → PaneEditorBody-57BHgi2R.js} +4 -4
- package/dist/public/assets/{PdfViewer-ckjWE_TR.js → PdfViewer-T-uYvXEd.js} +2 -2
- package/dist/public/assets/SettingsDialog-CkBdXZyv.js +8 -0
- package/dist/public/assets/{Terminal-BqbjwcMi.js → Terminal-CJMmk0kO.js} +1 -1
- package/dist/public/assets/{Workspace-BVDgHkML.js → Workspace-0P5NTRzC.js} +6 -6
- package/dist/public/assets/{abnfDiagram-VCTEODGH-JZdwYDGF.js → abnfDiagram-VCTEODGH-Dl2GTGCZ.js} +1 -1
- package/dist/public/assets/architecture-7GRP2DOG-UXEF_VQt.js +1 -0
- package/dist/public/assets/{architectureDiagram-5GKGNRK7-CYoRlLvs.js → architectureDiagram-5GKGNRK7-TLkiib9Z.js} +1 -1
- package/dist/public/assets/{blockDiagram-I7D4REHJ-DjPQ4-5b.js → blockDiagram-I7D4REHJ-CFdDK-5H.js} +1 -1
- package/dist/public/assets/{buffer-store-GUPJI8Wy.js → buffer-store-Pex0dRS2.js} +2 -2
- package/dist/public/assets/{button-CKes3Qwq.js → button-IwQVXqQs.js} +1 -1
- package/dist/public/assets/{c4Diagram-7LVT6UL2-BvwPGpk0.js → c4Diagram-7LVT6UL2-9M5y_7Rr.js} +1 -1
- package/dist/public/assets/channel-D3NmDqec.js +1 -0
- package/dist/public/assets/{chunk-2E4U76K2-DcsuTdxj.js → chunk-2E4U76K2-BEq-rPTx.js} +1 -1
- package/dist/public/assets/{chunk-4HAMMTFA-BGEaVxkg.js → chunk-4HAMMTFA-DSsufXZj.js} +1 -1
- package/dist/public/assets/{chunk-75Z2AOVW-BOvbyaVc.js → chunk-75Z2AOVW-CUihRphW.js} +1 -1
- package/dist/public/assets/{chunk-CLGD4ZFX-8fG7spZS.js → chunk-CLGD4ZFX-8FntHoH6.js} +1 -1
- package/dist/public/assets/{chunk-DU6HZSFF-C94gDWXH.js → chunk-DU6HZSFF-BhI6mdvA.js} +1 -1
- package/dist/public/assets/{chunk-F27PBJKO-CZD-A2b3.js → chunk-F27PBJKO-CflkfzDR.js} +1 -1
- package/dist/public/assets/{chunk-GMAD6QVW-C7KK3kiY.js → chunk-GMAD6QVW-C6sQnCBM.js} +1 -1
- package/dist/public/assets/{chunk-GVQU2GXP-CDFKc0sW.js → chunk-GVQU2GXP-BTEA9yBL.js} +1 -1
- package/dist/public/assets/{chunk-IMKFNOWR-BWQiE1Cp.js → chunk-IMKFNOWR-BTN8pZaT.js} +1 -1
- package/dist/public/assets/{chunk-L3NEJ4N5-DqP3WOXV.js → chunk-L3NEJ4N5-CPMRHY2b.js} +1 -1
- package/dist/public/assets/{chunk-OSK3NFVY-BlmYDJRN.js → chunk-OSK3NFVY-D62Fb29m.js} +1 -1
- package/dist/public/assets/{chunk-P2QGCYS3-v7eQ-UQT.js → chunk-P2QGCYS3-B_AAuki5.js} +1 -1
- package/dist/public/assets/{chunk-POPQ4Y6H-D_gxItFL.js → chunk-POPQ4Y6H-C-wGGj3u.js} +1 -1
- package/dist/public/assets/{chunk-PWAF6VOD-Ds90M0Eb.js → chunk-PWAF6VOD-DElrnMgG.js} +1 -1
- package/dist/public/assets/{chunk-SHT3W25Y-B8oepiFw.js → chunk-SHT3W25Y-D4tV8JU2.js} +1 -1
- package/dist/public/assets/{chunk-SVP7TREG-DjFxplBp.js → chunk-SVP7TREG-CFj2bSX9.js} +1 -1
- package/dist/public/assets/{chunk-TICWLB2K-GEeYOgw4.js → chunk-TICWLB2K-B-5yvDQM.js} +1 -1
- package/dist/public/assets/{chunk-TLUHSLCS-CEnN9wOB.js → chunk-TLUHSLCS-CpBFfaGS.js} +2 -2
- package/dist/public/assets/classDiagram-ZZMXUADV-8X6mwi75.js +1 -0
- package/dist/public/assets/classDiagram-v2-VYDZK3BY-8X6mwi75.js +1 -0
- package/dist/public/assets/{context-menu-CmSSwq3B.js → context-menu-Bh2z-xpu.js} +1 -1
- package/dist/public/assets/{cssMode-BEaREsxj.js → cssMode-iZ32bnYe.js} +1 -1
- package/dist/public/assets/{cynefin-OW5HDTMX-C_sl2wZ-.js → cynefin-OW5HDTMX-D5BzXyX_.js} +1 -1
- package/dist/public/assets/{cynefinDiagram-5FMLGOSQ-CPpFUPl3.js → cynefinDiagram-5FMLGOSQ-CdbgNH0t.js} +1 -1
- package/dist/public/assets/{dagre-GXQ25YYZ-BryapOat.js → dagre-GXQ25YYZ-B7zDDwyp.js} +1 -1
- package/dist/public/assets/{diagram-S7CK7UJ4-dIdCar-k.js → diagram-S7CK7UJ4-WMtT-mtt.js} +1 -1
- package/dist/public/assets/{diagram-UQ7AKVKN-EcAWwfjf.js → diagram-UQ7AKVKN-DtvvWbC3.js} +1 -1
- package/dist/public/assets/{diagram-VSXAHHWV-Dpl4gvkd.js → diagram-VSXAHHWV-DBLLhbuk.js} +1 -1
- package/dist/public/assets/{diagram-VX7I27RA-CJtemv3e.js → diagram-VX7I27RA-6sDOwqqU.js} +1 -1
- package/dist/public/assets/{diagram-Z3DM3KII-CPSnQEgD.js → diagram-Z3DM3KII-giEr0RfN.js} +1 -1
- package/dist/public/assets/{dialog-Dam8WdmS.js → dialog-ClHoWaGa.js} +1 -1
- package/dist/public/assets/{disclosure-Ex-68BU4.js → disclosure-Ch2qCUT3.js} +1 -1
- package/dist/public/assets/{dist-ByY6lqkl.js → dist-C1IA4PaM.js} +1 -1
- package/dist/public/assets/{dist-CxMnSex-.js → dist-ujnACQRA.js} +1 -1
- package/dist/public/assets/{ebnfDiagram-PWID7BFC-uXsUC0ll.js → ebnfDiagram-PWID7BFC-CDfLo0uv.js} +1 -1
- package/dist/public/assets/{erDiagram-RLTQ6QDP-D5vJ6ZLT.js → erDiagram-RLTQ6QDP-z5xLMSlR.js} +1 -1
- package/dist/public/assets/eventmodeling-NTZA5JFV-CzmQ0jM3.js +1 -0
- package/dist/public/assets/flowDiagram-HODETNUW-_tJIQrw7.js +1 -0
- package/dist/public/assets/{ganttDiagram-EL5Y4UJY-C6yTyvpx.js → ganttDiagram-EL5Y4UJY-C2poetEB.js} +1 -1
- package/dist/public/assets/{gitGraph-4MIJSDKK-C7nVbcZ1.js → gitGraph-4MIJSDKK-D40KkO3l.js} +1 -1
- package/dist/public/assets/{gitGraphDiagram-WWUBYQGX-DByBzF_5.js → gitGraphDiagram-WWUBYQGX-B4839p8c.js} +1 -1
- package/dist/public/assets/{hotkeys-MSGaeY2m.js → hotkeys-CY98l7MF.js} +1 -1
- package/dist/public/assets/{htmlMode-DirKEo5j.js → htmlMode-XXBiFVzW.js} +1 -1
- package/dist/public/assets/{index-C1a3rCk2.js → index-D0Lm0Z_9.js} +2 -2
- package/dist/public/assets/index-uKreLbru.css +2 -0
- package/dist/public/assets/{info-A6RAGUB7-BCEGc_J2.js → info-A6RAGUB7-R6D6gcdH.js} +1 -1
- package/dist/public/assets/{infoDiagram-27XIBGKW-DDVeVGK9.js → infoDiagram-27XIBGKW-CQMe7eLM.js} +1 -1
- package/dist/public/assets/{input-i-kiT55N.js → input-BUZTRTzW.js} +1 -1
- package/dist/public/assets/{ishikawaDiagram-5VMMS53U-CTZvKcdI.js → ishikawaDiagram-5VMMS53U-hWJnyGf_.js} +1 -1
- package/dist/public/assets/{journeyDiagram-3NMN7TZE-CwFCP_8e.js → journeyDiagram-3NMN7TZE-DRy7w8VL.js} +1 -1
- package/dist/public/assets/{jsonMode-BLMO7T1W.js → jsonMode-B4KgbobV.js} +1 -1
- package/dist/public/assets/{kanban-definition-UXKFOSKX-BCdCN6ov.js → kanban-definition-UXKFOSKX-Dvv0eWiD.js} +1 -1
- package/dist/public/assets/{line-e7ifLbsv.js → line-K2RkIT6o.js} +1 -1
- package/dist/public/assets/{media-refresh-store-d6Ihz_Yj.js → media-refresh-store-OAgBTFYO.js} +1 -1
- package/dist/public/assets/{mermaid-parser.core-DfmtQ5si.js → mermaid-parser.core-B36v6vc8.js} +3 -3
- package/dist/public/assets/{mermaid.core-B0Ceo892.js → mermaid.core-kqdkh9ua.js} +3 -3
- package/dist/public/assets/{mindmap-definition-YA3MSWOX-BKWZ0hGZ.js → mindmap-definition-YA3MSWOX-CykDXfif.js} +1 -1
- package/dist/public/assets/{notification-permission-DThcB8rO.js → notification-permission-B9hBLJAo.js} +1 -1
- package/dist/public/assets/{packet-AYTQ26CC-C_WSBR7W.js → packet-AYTQ26CC-BDu33u4C.js} +1 -1
- package/dist/public/assets/{pdf-CW6jyF4i.js → pdf-BTD1hhPr.js} +1 -1
- package/dist/public/assets/{pegDiagram-XKGWAZYB-CQw-vxyv.js → pegDiagram-XKGWAZYB-DWdbGFng.js} +1 -1
- package/dist/public/assets/{pie-WAS4IAKB-CyAolSkk.js → pie-WAS4IAKB-CeKUt-Xf.js} +1 -1
- package/dist/public/assets/{pieDiagram-E7YTZNPT-fEikc7gQ.js → pieDiagram-E7YTZNPT-D94ZuRp2.js} +1 -1
- package/dist/public/assets/{project-abbrev-0K47Phcy.js → project-abbrev-E6FngLNw.js} +1 -1
- package/dist/public/assets/protocol-support-C3qZDXsO.js +1 -0
- package/dist/public/assets/{quadrantDiagram-AXDQQJYC-BLZF9CN6.js → quadrantDiagram-AXDQQJYC-DXkTGcPo.js} +1 -1
- package/dist/public/assets/{radar-RG4KPBEZ-DZj09Z3T.js → radar-RG4KPBEZ-C1wtnYrQ.js} +1 -1
- package/dist/public/assets/{railroad-74A4TZTK-DrrM4RhH.js → railroad-74A4TZTK-Dx_D-zM9.js} +1 -1
- package/dist/public/assets/railroad-abnf-HS5TGJTU-CBfnoAOv.js +1 -0
- package/dist/public/assets/railroad-ebnf-LZEXJU2U-0kuF9A11.js +1 -0
- package/dist/public/assets/railroad-peg-WCYAUIDC-DCe0oM6p.js +1 -0
- package/dist/public/assets/{railroadDiagram-O6MQD6OU-BjnjeLPo.js → railroadDiagram-O6MQD6OU-DVuwN3bC.js} +1 -1
- package/dist/public/assets/{requirementDiagram-BXWQKSXE-CJnHoVjp.js → requirementDiagram-BXWQKSXE-nX3JtB1N.js} +1 -1
- package/dist/public/assets/{sankeyDiagram-P5KCCOFB-BFBAycgU.js → sankeyDiagram-P5KCCOFB-DvErANKF.js} +1 -1
- package/dist/public/assets/{sequenceDiagram-WJ2MYXX4-IgIUmdp2.js → sequenceDiagram-WJ2MYXX4-BruH0Mma.js} +1 -1
- package/dist/public/assets/{stateDiagram-D77RDMKH-C8ragRoI.js → stateDiagram-D77RDMKH-BMiYrZ8Y.js} +1 -1
- package/dist/public/assets/stateDiagram-v2-MP3YSRHH-CF86bnO8.js +1 -0
- package/dist/public/assets/{swimlanes-42K2YHIH-mm0BJE0h.js → swimlanes-42K2YHIH-CK3jGfyc.js} +1 -1
- package/dist/public/assets/swimlanesDiagram-VR7AAH4N-COC62XcR.js +8 -0
- package/dist/public/assets/{switch-TfZl8YQu.js → switch-BrFrNOcn.js} +1 -1
- package/dist/public/assets/{timeline-definition-24CTP7MA-CLtACXsf.js → timeline-definition-24CTP7MA-DHf_pf2_.js} +1 -1
- package/dist/public/assets/{treeView-Q6P3EWNA-B_s8YnCD.js → treeView-Q6P3EWNA-C5uB7_Kg.js} +1 -1
- package/dist/public/assets/{treemap-WGGIJYW6-CiyeynoG.js → treemap-WGGIJYW6-BGzjaDxJ.js} +1 -1
- package/dist/public/assets/{tsMode-BJ9DHdAB.js → tsMode-C39Hmal5.js} +1 -1
- package/dist/public/assets/{use-ui-state-2bNBLDPU.js → use-ui-state-D5kpevK-.js} +1 -1
- package/dist/public/assets/{vennDiagram-4TSXK5OY-C9rcT-1u.js → vennDiagram-4TSXK5OY-9mb8KINT.js} +1 -1
- package/dist/public/assets/{wardley-WFR3VGLG-D_hqUvOM.js → wardley-WFR3VGLG-DW5VvBTA.js} +1 -1
- package/dist/public/assets/{wardleyDiagram-VM6X3IG4-Dfn7mGAc.js → wardleyDiagram-VM6X3IG4-DCM5g6RC.js} +1 -1
- package/dist/public/assets/{ws-CDXqi1pN.js → ws-BZ4-LssP.js} +1 -1
- package/dist/public/assets/{xychartDiagram-S5SC5T6Z-vwVJPUe2.js → xychartDiagram-S5SC5T6Z-Bks8gKZc.js} +1 -1
- package/dist/public/index.html +2 -2
- package/package.json +1 -1
- package/dist/public/assets/SettingsDialog-Cb2fdV1D.js +0 -8
- package/dist/public/assets/architecture-7GRP2DOG-RMEcXYv9.js +0 -1
- package/dist/public/assets/channel-BheYsxDc.js +0 -1
- package/dist/public/assets/classDiagram-ZZMXUADV-CDDLBiV5.js +0 -1
- package/dist/public/assets/classDiagram-v2-VYDZK3BY-CDDLBiV5.js +0 -1
- package/dist/public/assets/eventmodeling-NTZA5JFV-WlMCgowE.js +0 -1
- package/dist/public/assets/flowDiagram-HODETNUW-DJ99l88N.js +0 -1
- package/dist/public/assets/index-Ctw2P0rY.css +0 -2
- package/dist/public/assets/protocol-support-C1NCVjbV.js +0 -1
- package/dist/public/assets/railroad-abnf-HS5TGJTU-lXe6u3FO.js +0 -1
- package/dist/public/assets/railroad-ebnf-LZEXJU2U-C7R4jvUV.js +0 -1
- package/dist/public/assets/railroad-peg-WCYAUIDC-B24HGcGJ.js +0 -1
- package/dist/public/assets/stateDiagram-v2-MP3YSRHH-AU0bFC0Z.js +0 -1
- package/dist/public/assets/swimlanesDiagram-VR7AAH4N-cBVuNXB5.js +0 -8
package/dist/index.js
CHANGED
|
@@ -3756,7 +3756,7 @@ var init_wrapper = __esm({
|
|
|
3756
3756
|
|
|
3757
3757
|
// src/lib/ws-client.ts
|
|
3758
3758
|
function connectGateway(port, authority) {
|
|
3759
|
-
return new Promise((
|
|
3759
|
+
return new Promise((resolve6, reject) => {
|
|
3760
3760
|
const ws = new wrapper_default(`ws://127.0.0.1:${port}/ws`, {
|
|
3761
3761
|
headers: { host: `localhost:${port}` }
|
|
3762
3762
|
});
|
|
@@ -3781,7 +3781,7 @@ function connectGateway(port, authority) {
|
|
|
3781
3781
|
}
|
|
3782
3782
|
if (parsed.t === "authenticated") {
|
|
3783
3783
|
clearTimeout(timer);
|
|
3784
|
-
|
|
3784
|
+
resolve6({
|
|
3785
3785
|
send(message) {
|
|
3786
3786
|
if (resource.valid() && ws.readyState === wrapper_default.OPEN)
|
|
3787
3787
|
ws.send(JSON.stringify(message));
|
|
@@ -3855,7 +3855,7 @@ async function attachSession(opts) {
|
|
|
3855
3855
|
rows: streams.stdout.rows ?? 24
|
|
3856
3856
|
});
|
|
3857
3857
|
gateway.send({ t: "attach", session: session.id, term, ...dims2() });
|
|
3858
|
-
return new Promise((
|
|
3858
|
+
return new Promise((resolve6) => {
|
|
3859
3859
|
let settled = false;
|
|
3860
3860
|
const finish = (outcome) => {
|
|
3861
3861
|
if (settled) return;
|
|
@@ -3864,7 +3864,7 @@ async function attachSession(opts) {
|
|
|
3864
3864
|
stdin.removeListener("data", onStdin);
|
|
3865
3865
|
unResize?.();
|
|
3866
3866
|
gateway.close();
|
|
3867
|
-
|
|
3867
|
+
resolve6(outcome);
|
|
3868
3868
|
};
|
|
3869
3869
|
gateway.onMessage((message) => {
|
|
3870
3870
|
switch (message.t) {
|
|
@@ -3925,7 +3925,7 @@ import { createInterface } from "node:readline";
|
|
|
3925
3925
|
import { Writable } from "node:stream";
|
|
3926
3926
|
|
|
3927
3927
|
// ../shared/src/protocol.ts
|
|
3928
|
-
var PROTOCOL_VERSION = { major: 21, minor:
|
|
3928
|
+
var PROTOCOL_VERSION = { major: 21, minor: 3 };
|
|
3929
3929
|
|
|
3930
3930
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
3931
3931
|
var external_exports = {};
|
|
@@ -18754,6 +18754,7 @@ var agentTypeSchema = external_exports.object({
|
|
|
18754
18754
|
var repoSchema = external_exports.object({
|
|
18755
18755
|
id: rowId,
|
|
18756
18756
|
path: external_exports.string(),
|
|
18757
|
+
/** Empty follows the branch currently checked out at path, resolved for each new session. */
|
|
18757
18758
|
default_base_branch: external_exports.string(),
|
|
18758
18759
|
onboarding_notes: external_exports.string().nullable(),
|
|
18759
18760
|
fetch_enabled: external_exports.boolean(),
|
|
@@ -18764,7 +18765,8 @@ var repoWithOrphansSchema = repoSchema.extend({
|
|
|
18764
18765
|
});
|
|
18765
18766
|
var createRepoRequestSchema = external_exports.object({
|
|
18766
18767
|
path: external_exports.string().min(1),
|
|
18767
|
-
|
|
18768
|
+
/** Empty follows the clone's current branch; omitted snapshots it at registration. */
|
|
18769
|
+
default_base_branch: external_exports.string().optional(),
|
|
18768
18770
|
onboarding_notes: external_exports.string().nullable().optional(),
|
|
18769
18771
|
fetch_enabled: external_exports.boolean().optional()
|
|
18770
18772
|
});
|
|
@@ -18805,7 +18807,8 @@ var repoWorktreesResponseSchema = external_exports.object({
|
|
|
18805
18807
|
orphan_branches: external_exports.array(orphanBranchSchema)
|
|
18806
18808
|
});
|
|
18807
18809
|
var patchRepoRequestSchema = external_exports.object({
|
|
18808
|
-
|
|
18810
|
+
/** Empty follows the clone's current branch; omitted leaves the setting unchanged. */
|
|
18811
|
+
default_base_branch: external_exports.string().optional(),
|
|
18809
18812
|
onboarding_notes: external_exports.string().nullable().optional(),
|
|
18810
18813
|
fetch_enabled: external_exports.boolean().optional()
|
|
18811
18814
|
});
|
|
@@ -20039,7 +20042,7 @@ var LocalTransport = class {
|
|
|
20039
20042
|
kind = "local";
|
|
20040
20043
|
label = "this machine";
|
|
20041
20044
|
exec(command, opts = {}) {
|
|
20042
|
-
return new Promise((
|
|
20045
|
+
return new Promise((resolve6) => {
|
|
20043
20046
|
const child = spawn("sh", ["-c", command], { stdio: ["pipe", "pipe", "pipe"] });
|
|
20044
20047
|
let stdout = "";
|
|
20045
20048
|
let stderr = "";
|
|
@@ -20056,11 +20059,11 @@ var LocalTransport = class {
|
|
|
20056
20059
|
child.stderr.on("data", (chunk) => stderr += chunk.toString());
|
|
20057
20060
|
child.on("error", (err) => {
|
|
20058
20061
|
if (timer) clearTimeout(timer);
|
|
20059
|
-
|
|
20062
|
+
resolve6({ code: -1, stdout, stderr: stderr + String(err) });
|
|
20060
20063
|
});
|
|
20061
20064
|
child.on("close", (code) => {
|
|
20062
20065
|
if (timer) clearTimeout(timer);
|
|
20063
|
-
|
|
20066
|
+
resolve6({ code: code ?? -1, stdout, stderr });
|
|
20064
20067
|
});
|
|
20065
20068
|
child.stdin.on("error", () => {
|
|
20066
20069
|
});
|
|
@@ -20154,7 +20157,7 @@ var SshTransport = class {
|
|
|
20154
20157
|
* live control socket behind.
|
|
20155
20158
|
*/
|
|
20156
20159
|
open() {
|
|
20157
|
-
return new Promise((
|
|
20160
|
+
return new Promise((resolve6, reject) => {
|
|
20158
20161
|
const child = spawn2(this.ssh, this.args(this.host, "true"), {
|
|
20159
20162
|
// A GUI has no useful inherited terminal; OpenSSH calls its askpass
|
|
20160
20163
|
// helper instead. The terminal CLI keeps byte-for-byte inheritance.
|
|
@@ -20166,7 +20169,7 @@ var SshTransport = class {
|
|
|
20166
20169
|
(err) => reject(new CliError("ssh_unreachable", `could not run ${this.ssh}: ${err.message}`))
|
|
20167
20170
|
);
|
|
20168
20171
|
child.on("close", (code) => {
|
|
20169
|
-
if (code === 0)
|
|
20172
|
+
if (code === 0) resolve6();
|
|
20170
20173
|
else {
|
|
20171
20174
|
reject(
|
|
20172
20175
|
new CliError(
|
|
@@ -20182,13 +20185,13 @@ var SshTransport = class {
|
|
|
20182
20185
|
/** Whether the master connection is still alive (-O check). */
|
|
20183
20186
|
isAlive() {
|
|
20184
20187
|
if (!this.hasControlMaster) return Promise.resolve(true);
|
|
20185
|
-
return new Promise((
|
|
20188
|
+
return new Promise((resolve6) => {
|
|
20186
20189
|
const child = spawn2(this.ssh, this.args("-O", "check", this.host), {
|
|
20187
20190
|
stdio: "ignore",
|
|
20188
20191
|
env: this.spawnEnv()
|
|
20189
20192
|
});
|
|
20190
|
-
child.on("error", () =>
|
|
20191
|
-
child.on("close", (code) =>
|
|
20193
|
+
child.on("error", () => resolve6(false));
|
|
20194
|
+
child.on("close", (code) => resolve6(code === 0));
|
|
20192
20195
|
});
|
|
20193
20196
|
}
|
|
20194
20197
|
/**
|
|
@@ -20201,18 +20204,18 @@ var SshTransport = class {
|
|
|
20201
20204
|
*/
|
|
20202
20205
|
cancelForward(localPort, remotePort) {
|
|
20203
20206
|
if (!this.hasControlMaster) return Promise.resolve();
|
|
20204
|
-
return new Promise((
|
|
20207
|
+
return new Promise((resolve6) => {
|
|
20205
20208
|
const spec = `${localPort}:127.0.0.1:${remotePort}`;
|
|
20206
20209
|
const child = spawn2(this.ssh, this.args("-O", "cancel", "-L", spec, this.host), {
|
|
20207
20210
|
stdio: "ignore",
|
|
20208
20211
|
env: this.spawnEnv()
|
|
20209
20212
|
});
|
|
20210
|
-
child.on("error", () =>
|
|
20211
|
-
child.on("close", () =>
|
|
20213
|
+
child.on("error", () => resolve6());
|
|
20214
|
+
child.on("close", () => resolve6());
|
|
20212
20215
|
});
|
|
20213
20216
|
}
|
|
20214
20217
|
exec(command, opts = {}) {
|
|
20215
|
-
return new Promise((
|
|
20218
|
+
return new Promise((resolve6) => {
|
|
20216
20219
|
const child = spawn2(this.ssh, this.args(this.host, "--", `sh -c ${shellQuote(command)}`), {
|
|
20217
20220
|
stdio: ["pipe", "pipe", "pipe"],
|
|
20218
20221
|
env: this.spawnEnv()
|
|
@@ -20232,11 +20235,11 @@ var SshTransport = class {
|
|
|
20232
20235
|
child.stderr.on("data", (chunk) => stderr += chunk.toString());
|
|
20233
20236
|
child.on("error", (err) => {
|
|
20234
20237
|
if (timer) clearTimeout(timer);
|
|
20235
|
-
|
|
20238
|
+
resolve6({ code: -1, stdout, stderr: stderr + String(err) });
|
|
20236
20239
|
});
|
|
20237
20240
|
child.on("close", (code) => {
|
|
20238
20241
|
if (timer) clearTimeout(timer);
|
|
20239
|
-
|
|
20242
|
+
resolve6({ code: code ?? -1, stdout, stderr });
|
|
20240
20243
|
});
|
|
20241
20244
|
child.stdin.on("error", () => {
|
|
20242
20245
|
});
|
|
@@ -20256,11 +20259,11 @@ var SshTransport = class {
|
|
|
20256
20259
|
});
|
|
20257
20260
|
let stdout = "";
|
|
20258
20261
|
let stderr = "";
|
|
20259
|
-
const result = new Promise((
|
|
20262
|
+
const result = new Promise((resolve6) => {
|
|
20260
20263
|
child.stdout.on("data", (chunk) => stdout += chunk.toString());
|
|
20261
20264
|
child.stderr.on("data", (chunk) => stderr += chunk.toString());
|
|
20262
|
-
child.on("error", (err) =>
|
|
20263
|
-
child.on("close", (code) =>
|
|
20265
|
+
child.on("error", (err) => resolve6({ code: -1, stdout, stderr: stderr + String(err) }));
|
|
20266
|
+
child.on("close", (code) => resolve6({ code: code ?? -1, stdout, stderr }));
|
|
20264
20267
|
});
|
|
20265
20268
|
return {
|
|
20266
20269
|
result,
|
|
@@ -20275,14 +20278,14 @@ var SshTransport = class {
|
|
|
20275
20278
|
}
|
|
20276
20279
|
async copyTo(localPath, destPath) {
|
|
20277
20280
|
await this.exec(`mkdir -p $(dirname ${destPath})`, { timeoutMs: 15e3 });
|
|
20278
|
-
await new Promise((
|
|
20281
|
+
await new Promise((resolve6, reject) => {
|
|
20279
20282
|
const child = spawn2(this.scp, [...this.controlArgs, localPath, `${this.host}:${destPath}`], {
|
|
20280
20283
|
stdio: ["ignore", "ignore", "inherit"],
|
|
20281
20284
|
env: this.spawnEnv()
|
|
20282
20285
|
});
|
|
20283
20286
|
child.on("error", reject);
|
|
20284
20287
|
child.on("close", (code) => {
|
|
20285
|
-
if (code === 0)
|
|
20288
|
+
if (code === 0) resolve6();
|
|
20286
20289
|
else reject(new CliError("ssh_unreachable", `scp to ${this.host} failed (exit ${code})`));
|
|
20287
20290
|
});
|
|
20288
20291
|
});
|
|
@@ -20383,7 +20386,7 @@ async function registrationCode() {
|
|
|
20383
20386
|
const readline = createInterface({ input: process.stdin, output: silent, terminal: true });
|
|
20384
20387
|
try {
|
|
20385
20388
|
return remoteSecretSchema.parse(
|
|
20386
|
-
await new Promise((
|
|
20389
|
+
await new Promise((resolve6) => readline.question("", resolve6))
|
|
20387
20390
|
);
|
|
20388
20391
|
} finally {
|
|
20389
20392
|
readline.close();
|
|
@@ -20507,6 +20510,8 @@ running in the background once ready, so the terminal may close (--foreground
|
|
|
20507
20510
|
to stay attached; Ctrl-C then stops the cockpit). refresh stops a target's
|
|
20508
20511
|
cockpit (even a wedged one) and runs the full launch flow again \u2014 tunnel,
|
|
20509
20512
|
daemon restart if needed \u2014 keeping the old UI port so open tabs survive.
|
|
20513
|
+
An older host protocol prompts before updating its daemon; --no-upgrade
|
|
20514
|
+
aborts before prompting. Without a terminal, upgrade the daemon explicitly first.
|
|
20510
20515
|
list shows running cockpits; kill stops one. Sessions keep running under a
|
|
20511
20516
|
persistent supervisor; an SSH-attached daemon keeps their state for the next launch.
|
|
20512
20517
|
|
|
@@ -20516,7 +20521,7 @@ AppImage placed where you say (default ~/puddle), then opened in the file
|
|
|
20516
20521
|
manager. Already installed and no @version \u2192 nothing changes.
|
|
20517
20522
|
upgrade moves components to the newest release (or the named @version),
|
|
20518
20523
|
installing any that are missing; with no component it covers everything
|
|
20519
|
-
installed on the target \u2014 the CLI last,
|
|
20524
|
+
installed on the target \u2014 the CLI last, using its installation channel. cli and desktop are
|
|
20520
20525
|
client-machine artefacts: user@host targets the daemon only. remove
|
|
20521
20526
|
uninstalls a component after confirmation; 'daemon' stops it (interrupting
|
|
20522
20527
|
its sessions), unregisters the supervisor, and keeps ~/.puddle's data \u2014
|
|
@@ -20703,7 +20708,7 @@ function parseArgs(argv) {
|
|
|
20703
20708
|
throw new CliError(
|
|
20704
20709
|
"bad_arguments",
|
|
20705
20710
|
"install takes one of: daemon | desktop (with an optional @version)",
|
|
20706
|
-
spec?.what === "cli" ? "the CLI
|
|
20711
|
+
spec?.what === "cli" ? "install the CLI with the public install.sh or npm; update it with puddle upgrade cli" : "e.g. puddle install daemon@v0.0.32 user@host"
|
|
20707
20712
|
);
|
|
20708
20713
|
}
|
|
20709
20714
|
return {
|
|
@@ -20780,6 +20785,182 @@ function argvFor(command) {
|
|
|
20780
20785
|
return argv;
|
|
20781
20786
|
}
|
|
20782
20787
|
|
|
20788
|
+
// src/cli/daemon-upgrade-prompt.ts
|
|
20789
|
+
import { randomUUID } from "node:crypto";
|
|
20790
|
+
|
|
20791
|
+
// src/lib/handshake.ts
|
|
20792
|
+
init_types();
|
|
20793
|
+
var CLI_UPGRADE_COMMAND = "puddle upgrade cli";
|
|
20794
|
+
function decideHandshake(cli, daemon) {
|
|
20795
|
+
if (daemon.major === cli.major) return { kind: "proceed" };
|
|
20796
|
+
if (daemon.major < cli.major) return { kind: "upgrade-daemon" };
|
|
20797
|
+
return { kind: "refuse-cli-outdated", command: CLI_UPGRADE_COMMAND };
|
|
20798
|
+
}
|
|
20799
|
+
function daemonUpgradeMessage(request) {
|
|
20800
|
+
const { host, daemon, clientProtocol, liveSessions } = request;
|
|
20801
|
+
return `${host} runs puddled ${daemon.version} (protocol ${daemon.protocol.major}.${daemon.protocol.minor}). This client uses protocol ${clientProtocol.major}.${clientProtocol.minor} and needs the same protocol major.
|
|
20802
|
+
|
|
20803
|
+
Updating the host daemon will interrupt ${liveSessions} live session(s). Their saved state remains and they can resume after the update.`;
|
|
20804
|
+
}
|
|
20805
|
+
function verifyDaemonVersion(daemon) {
|
|
20806
|
+
const decision = decideHandshake(PROTOCOL_VERSION, daemon.protocol);
|
|
20807
|
+
if (decision.kind === "proceed") return daemon;
|
|
20808
|
+
if (decision.kind === "refuse-cli-outdated") {
|
|
20809
|
+
throw new CliError(
|
|
20810
|
+
"cli_outdated",
|
|
20811
|
+
`the daemon speaks protocol ${daemon.protocol.major}; this client speaks ${PROTOCOL_VERSION.major}`,
|
|
20812
|
+
`update the desktop app, or update the CLI: ${decision.command}`
|
|
20813
|
+
);
|
|
20814
|
+
}
|
|
20815
|
+
throw new CliError(
|
|
20816
|
+
"protocol_mismatch",
|
|
20817
|
+
`the daemon speaks protocol ${daemon.protocol.major}; this client speaks ${PROTOCOL_VERSION.major}`,
|
|
20818
|
+
"reconnect to update the host daemon"
|
|
20819
|
+
);
|
|
20820
|
+
}
|
|
20821
|
+
async function runHandshake(opts) {
|
|
20822
|
+
const { version: daemon, liveSessions } = opts.inspection;
|
|
20823
|
+
if (decideHandshake(PROTOCOL_VERSION, daemon.protocol).kind !== "upgrade-daemon") {
|
|
20824
|
+
return verifyDaemonVersion(daemon);
|
|
20825
|
+
}
|
|
20826
|
+
if (opts.noUpgrade) {
|
|
20827
|
+
throw new CliError(
|
|
20828
|
+
"upgrade_failed",
|
|
20829
|
+
`the daemon speaks protocol ${daemon.protocol.major} (client: ${PROTOCOL_VERSION.major}) and --no-upgrade is set`,
|
|
20830
|
+
`re-run without --no-upgrade to update it (${liveSessions} live session(s) would be interrupted and can be resumed)`
|
|
20831
|
+
);
|
|
20832
|
+
}
|
|
20833
|
+
const request = {
|
|
20834
|
+
host: opts.host,
|
|
20835
|
+
daemon,
|
|
20836
|
+
clientProtocol: PROTOCOL_VERSION,
|
|
20837
|
+
liveSessions
|
|
20838
|
+
};
|
|
20839
|
+
if (!await opts.confirmDaemonUpgrade?.(request)) {
|
|
20840
|
+
throw new CliError(
|
|
20841
|
+
"upgrade_failed",
|
|
20842
|
+
`the daemon update on ${opts.host} was not approved`,
|
|
20843
|
+
"launch again to approve the update, or run puddle upgrade daemon with the host as an optional argument"
|
|
20844
|
+
);
|
|
20845
|
+
}
|
|
20846
|
+
const latest = await opts.reinspect?.();
|
|
20847
|
+
if (latest && decideHandshake(PROTOCOL_VERSION, latest.version.protocol).kind !== "upgrade-daemon") {
|
|
20848
|
+
return verifyDaemonVersion(latest.version);
|
|
20849
|
+
}
|
|
20850
|
+
const logger = opts.logger ?? silentLogger;
|
|
20851
|
+
logger.info(
|
|
20852
|
+
`updating puddled on ${opts.host} (protocol ${daemon.protocol.major} \u2192 ${PROTOCOL_VERSION.major}) \u2014 ${liveSessions} live session(s) will be interrupted and can be resumed`
|
|
20853
|
+
);
|
|
20854
|
+
try {
|
|
20855
|
+
return verifyDaemonVersion(await opts.upgradeDaemon());
|
|
20856
|
+
} catch (err) {
|
|
20857
|
+
if (err instanceof CliError && ["cli_outdated", "protocol_mismatch"].includes(err.code)) {
|
|
20858
|
+
throw new CliError(
|
|
20859
|
+
"upgrade_failed",
|
|
20860
|
+
`the daemon protocol is still incompatible after updating: ${err.message}`,
|
|
20861
|
+
"inspect the daemon logs: puddle logs"
|
|
20862
|
+
);
|
|
20863
|
+
}
|
|
20864
|
+
throw err;
|
|
20865
|
+
}
|
|
20866
|
+
}
|
|
20867
|
+
|
|
20868
|
+
// src/cli/prompt.ts
|
|
20869
|
+
init_types();
|
|
20870
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
20871
|
+
async function ask(question, def) {
|
|
20872
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return def;
|
|
20873
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
20874
|
+
try {
|
|
20875
|
+
const answer = await new Promise(
|
|
20876
|
+
(resolve6) => rl.question(`${question} [${def}] `, resolve6)
|
|
20877
|
+
);
|
|
20878
|
+
return answer.trim() === "" ? def : answer.trim();
|
|
20879
|
+
} finally {
|
|
20880
|
+
rl.close();
|
|
20881
|
+
}
|
|
20882
|
+
}
|
|
20883
|
+
async function confirm(question, opts = {}) {
|
|
20884
|
+
if (opts.skip === true) return true;
|
|
20885
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
20886
|
+
throw new CliError(
|
|
20887
|
+
"bad_arguments",
|
|
20888
|
+
"refusing to confirm without a terminal",
|
|
20889
|
+
"pass --yes to proceed non-interactively"
|
|
20890
|
+
);
|
|
20891
|
+
}
|
|
20892
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
20893
|
+
try {
|
|
20894
|
+
const answer = await new Promise(
|
|
20895
|
+
(resolve6) => rl.question(`${question} [y/N] `, resolve6)
|
|
20896
|
+
);
|
|
20897
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
20898
|
+
} finally {
|
|
20899
|
+
rl.close();
|
|
20900
|
+
}
|
|
20901
|
+
}
|
|
20902
|
+
|
|
20903
|
+
// src/cli/daemon-upgrade-prompt.ts
|
|
20904
|
+
var REQUEST = "puddle:confirm-daemon-upgrade";
|
|
20905
|
+
var RESPONSE = "puddle:daemon-upgrade-answer";
|
|
20906
|
+
async function askToUpgrade(message) {
|
|
20907
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
20908
|
+
return confirm(`${message}
|
|
20909
|
+
|
|
20910
|
+
Update and connect?`);
|
|
20911
|
+
}
|
|
20912
|
+
var confirmDaemonUpgrade = async (request) => {
|
|
20913
|
+
const message = daemonUpgradeMessage(request);
|
|
20914
|
+
if (!process.send || !process.connected) return askToUpgrade(message);
|
|
20915
|
+
return requestDaemonUpgradeApproval(message, process);
|
|
20916
|
+
};
|
|
20917
|
+
function requestDaemonUpgradeApproval(message, channel) {
|
|
20918
|
+
if (!channel.send || !channel.connected) return Promise.resolve(false);
|
|
20919
|
+
const id = randomUUID();
|
|
20920
|
+
return new Promise((resolve6) => {
|
|
20921
|
+
const finish = (approved) => {
|
|
20922
|
+
channel.off("message", answered);
|
|
20923
|
+
channel.off("disconnect", disconnected);
|
|
20924
|
+
resolve6(approved);
|
|
20925
|
+
};
|
|
20926
|
+
const answered = (value) => {
|
|
20927
|
+
if (!value || typeof value !== "object") return;
|
|
20928
|
+
const response = value;
|
|
20929
|
+
if (response.t === RESPONSE && response.id === id && typeof response.approved === "boolean")
|
|
20930
|
+
finish(response.approved);
|
|
20931
|
+
};
|
|
20932
|
+
const disconnected = () => finish(false);
|
|
20933
|
+
channel.on("message", answered);
|
|
20934
|
+
channel.once("disconnect", disconnected);
|
|
20935
|
+
channel.send({ t: REQUEST, id, message }, (err) => {
|
|
20936
|
+
if (err) finish(false);
|
|
20937
|
+
});
|
|
20938
|
+
});
|
|
20939
|
+
}
|
|
20940
|
+
function relayDaemonUpgradePrompt(child, ask2 = askToUpgrade) {
|
|
20941
|
+
let active = true;
|
|
20942
|
+
let pending = false;
|
|
20943
|
+
const receive = (value) => {
|
|
20944
|
+
if (pending || !value || typeof value !== "object") return;
|
|
20945
|
+
const request = value;
|
|
20946
|
+
if (request.t !== REQUEST || typeof request.id !== "string" || typeof request.message !== "string")
|
|
20947
|
+
return;
|
|
20948
|
+
pending = true;
|
|
20949
|
+
void ask2(request.message).catch(() => false).then((approved) => {
|
|
20950
|
+
pending = false;
|
|
20951
|
+
if (active && child.connected)
|
|
20952
|
+
child.send({ t: RESPONSE, id: request.id, approved }, () => {
|
|
20953
|
+
});
|
|
20954
|
+
});
|
|
20955
|
+
};
|
|
20956
|
+
child.on("message", receive);
|
|
20957
|
+
return () => {
|
|
20958
|
+
active = false;
|
|
20959
|
+
child.off("message", receive);
|
|
20960
|
+
if (child.connected) child.disconnect();
|
|
20961
|
+
};
|
|
20962
|
+
}
|
|
20963
|
+
|
|
20783
20964
|
// ../shared/src/node/security.ts
|
|
20784
20965
|
import { createHash, randomBytes } from "node:crypto";
|
|
20785
20966
|
import {
|
|
@@ -20867,14 +21048,14 @@ async function listenPrivate(path, onConnection) {
|
|
|
20867
21048
|
privatePath(path);
|
|
20868
21049
|
const before = lstatSync(path);
|
|
20869
21050
|
if (!before.isSocket()) throw new Error("Puddle control path is not a socket");
|
|
20870
|
-
const stale = await new Promise((
|
|
21051
|
+
const stale = await new Promise((resolve6, reject) => {
|
|
20871
21052
|
const probe = connect(path);
|
|
20872
21053
|
probe.once("connect", () => {
|
|
20873
21054
|
probe.destroy();
|
|
20874
|
-
|
|
21055
|
+
resolve6(false);
|
|
20875
21056
|
});
|
|
20876
21057
|
probe.once("error", (err) => {
|
|
20877
|
-
if (err.code === "ECONNREFUSED" || err.code === "ENOENT")
|
|
21058
|
+
if (err.code === "ECONNREFUSED" || err.code === "ENOENT") resolve6(true);
|
|
20878
21059
|
else reject(err);
|
|
20879
21060
|
});
|
|
20880
21061
|
});
|
|
@@ -20887,11 +21068,11 @@ async function listenPrivate(path, onConnection) {
|
|
|
20887
21068
|
}
|
|
20888
21069
|
}
|
|
20889
21070
|
const server = createServer(onConnection);
|
|
20890
|
-
await new Promise((
|
|
21071
|
+
await new Promise((resolve6, reject) => {
|
|
20891
21072
|
server.once("error", reject);
|
|
20892
21073
|
server.listen(path, () => {
|
|
20893
21074
|
server.off("error", reject);
|
|
20894
|
-
|
|
21075
|
+
resolve6();
|
|
20895
21076
|
});
|
|
20896
21077
|
});
|
|
20897
21078
|
if (process.platform !== "win32") chmodSync(path, 384);
|
|
@@ -21005,7 +21186,7 @@ var HostControlClient = class {
|
|
|
21005
21186
|
}
|
|
21006
21187
|
this.channel = channel;
|
|
21007
21188
|
this.until = performance.now() + CONNECTION_POLICY.leaseMs;
|
|
21008
|
-
await new Promise((
|
|
21189
|
+
await new Promise((resolve6, reject) => {
|
|
21009
21190
|
let pendingAt = null;
|
|
21010
21191
|
let initial = true;
|
|
21011
21192
|
const send = (message, sentAt = performance.now()) => {
|
|
@@ -21036,8 +21217,8 @@ var HostControlClient = class {
|
|
|
21036
21217
|
this.status = "protocol_mismatch";
|
|
21037
21218
|
reject(
|
|
21038
21219
|
this.error(
|
|
21039
|
-
"
|
|
21040
|
-
`Host protocol ${message.version.protocol.major}
|
|
21220
|
+
"protocol_mismatch",
|
|
21221
|
+
`Host protocol ${message.version.protocol.major} does not match client protocol ${PROTOCOL_VERSION.major}`
|
|
21041
21222
|
)
|
|
21042
21223
|
);
|
|
21043
21224
|
channel.destroy();
|
|
@@ -21058,7 +21239,7 @@ var HostControlClient = class {
|
|
|
21058
21239
|
if (this.channel !== channel) return;
|
|
21059
21240
|
this.status = "ready";
|
|
21060
21241
|
this.emit();
|
|
21061
|
-
|
|
21242
|
+
resolve6();
|
|
21062
21243
|
}).catch(() => channel.destroy());
|
|
21063
21244
|
}
|
|
21064
21245
|
},
|
|
@@ -21155,7 +21336,7 @@ async function inspectLegacyHost(home) {
|
|
|
21155
21336
|
};
|
|
21156
21337
|
try {
|
|
21157
21338
|
const version2 = versionResponseSchema.parse(await get("/api/version"));
|
|
21158
|
-
const sessions = sessionSchema.array().parse(await get("/api/sessions"));
|
|
21339
|
+
const sessions = sessionSchema.pick({ status: true }).array().parse(await get("/api/sessions"));
|
|
21159
21340
|
return hostInspectionSchema.parse({
|
|
21160
21341
|
t: "inspection",
|
|
21161
21342
|
port,
|
|
@@ -21179,7 +21360,7 @@ async function inspectHostLocally(home) {
|
|
|
21179
21360
|
}
|
|
21180
21361
|
const socket = connect2(path);
|
|
21181
21362
|
try {
|
|
21182
|
-
const grant = await new Promise((
|
|
21363
|
+
const grant = await new Promise((resolve6, reject) => {
|
|
21183
21364
|
socket.setTimeout(5e3, () => socket.destroy(new Error("Host inspection timed out")));
|
|
21184
21365
|
socket.once("error", reject);
|
|
21185
21366
|
socket.once("close", () => reject(new Error("Host control closed")));
|
|
@@ -21189,7 +21370,7 @@ async function inspectHostLocally(home) {
|
|
|
21189
21370
|
const message = controlResponseSchema.parse(value);
|
|
21190
21371
|
if (message.t !== "authority" || !message.token)
|
|
21191
21372
|
throw new Error("Host authority unavailable");
|
|
21192
|
-
|
|
21373
|
+
resolve6(message);
|
|
21193
21374
|
},
|
|
21194
21375
|
() => socket.destroy(new Error("Invalid host control response"))
|
|
21195
21376
|
);
|
|
@@ -21200,7 +21381,7 @@ async function inspectHostLocally(home) {
|
|
|
21200
21381
|
signal: AbortSignal.timeout(3e3)
|
|
21201
21382
|
});
|
|
21202
21383
|
if (!response.ok) throw new Error("Host inspection rejected");
|
|
21203
|
-
const sessions = sessionSchema.array().parse(await response.json());
|
|
21384
|
+
const sessions = sessionSchema.pick({ status: true }).array().parse(await response.json());
|
|
21204
21385
|
return hostInspectionSchema.parse({
|
|
21205
21386
|
t: "inspection",
|
|
21206
21387
|
port: grant.port,
|
|
@@ -21302,9 +21483,9 @@ async function acquireAuthority(transport) {
|
|
|
21302
21483
|
|
|
21303
21484
|
// src/cli/run.ts
|
|
21304
21485
|
init_attach();
|
|
21305
|
-
import { existsSync as
|
|
21306
|
-
import { dirname as
|
|
21307
|
-
import { fileURLToPath as
|
|
21486
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
21487
|
+
import { dirname as dirname12, join as join17 } from "node:path";
|
|
21488
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
21308
21489
|
|
|
21309
21490
|
// src/lib/browser.ts
|
|
21310
21491
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -21347,7 +21528,7 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
21347
21528
|
import { dirname as dirname4, join as join5 } from "node:path";
|
|
21348
21529
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
21349
21530
|
function cliVersion() {
|
|
21350
|
-
if (true) return "0.2.
|
|
21531
|
+
if (true) return "0.2.7";
|
|
21351
21532
|
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
21352
21533
|
for (const candidate of [
|
|
21353
21534
|
join5(here, "..", "..", "package.json"),
|
|
@@ -21590,7 +21771,7 @@ function swapScript(staged2, opts) {
|
|
|
21590
21771
|
}
|
|
21591
21772
|
async function findInstalledDesktopApp(opts = {}) {
|
|
21592
21773
|
if ((opts.platform ?? process.platform) !== "darwin") return null;
|
|
21593
|
-
const { readFile:
|
|
21774
|
+
const { readFile: readFile3 } = await import("node:fs/promises");
|
|
21594
21775
|
const { homedir: homedir3 } = await import("node:os");
|
|
21595
21776
|
const systemApplications = opts.systemApplicationsDir ?? "/Applications";
|
|
21596
21777
|
const home = opts.homeDir ?? homedir3();
|
|
@@ -21599,7 +21780,7 @@ async function findInstalledDesktopApp(opts = {}) {
|
|
|
21599
21780
|
join6(home, "Applications", "Puddle.app")
|
|
21600
21781
|
]) {
|
|
21601
21782
|
try {
|
|
21602
|
-
const plist = await
|
|
21783
|
+
const plist = await readFile3(join6(appPath, "Contents/Info.plist"), "utf8");
|
|
21603
21784
|
const match = /<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/.exec(
|
|
21604
21785
|
plist
|
|
21605
21786
|
);
|
|
@@ -21627,12 +21808,12 @@ async function desktopAppInstallPath(opts = {}) {
|
|
|
21627
21808
|
return join6(userApplications, "Puddle.app");
|
|
21628
21809
|
}
|
|
21629
21810
|
function isDesktopAppRunning(appPath) {
|
|
21630
|
-
return new Promise((
|
|
21811
|
+
return new Promise((resolve6) => {
|
|
21631
21812
|
const child = spawn4("/usr/bin/pgrep", ["-f", `${appPath}/Contents/MacOS/`], {
|
|
21632
21813
|
stdio: "ignore"
|
|
21633
21814
|
});
|
|
21634
|
-
child.on("error", () =>
|
|
21635
|
-
child.on("exit", (code) =>
|
|
21815
|
+
child.on("error", () => resolve6(false));
|
|
21816
|
+
child.on("exit", (code) => resolve6(code === 0));
|
|
21636
21817
|
});
|
|
21637
21818
|
}
|
|
21638
21819
|
function fileSha256(path) {
|
|
@@ -21640,12 +21821,12 @@ function fileSha256(path) {
|
|
|
21640
21821
|
return pipeline(createReadStream(path), hash2).then(() => hash2.digest("hex"));
|
|
21641
21822
|
}
|
|
21642
21823
|
function run(command, args) {
|
|
21643
|
-
return new Promise((
|
|
21824
|
+
return new Promise((resolve6, reject) => {
|
|
21644
21825
|
const child = spawn4(command, args, { stdio: "ignore" });
|
|
21645
21826
|
child.on("error", reject);
|
|
21646
21827
|
child.on(
|
|
21647
21828
|
"exit",
|
|
21648
|
-
(code) => code === 0 ?
|
|
21829
|
+
(code) => code === 0 ? resolve6() : reject(new CliError("not_installed", `${command} exited with ${code ?? "signal"}`))
|
|
21649
21830
|
);
|
|
21650
21831
|
});
|
|
21651
21832
|
}
|
|
@@ -21724,7 +21905,10 @@ var RELEASE_PROTOCOLS = {
|
|
|
21724
21905
|
"0.2.1": { major: 19, minor: 0 },
|
|
21725
21906
|
"0.2.2": { major: 20, minor: 0 },
|
|
21726
21907
|
"0.2.3": { major: 20, minor: 0 },
|
|
21727
|
-
"0.2.4": { major: 21, minor: 2 }
|
|
21908
|
+
"0.2.4": { major: 21, minor: 2 },
|
|
21909
|
+
"0.2.5": { major: 21, minor: 2 },
|
|
21910
|
+
"0.2.6": { major: 21, minor: 2 },
|
|
21911
|
+
"0.2.7": { major: 21, minor: 3 }
|
|
21728
21912
|
};
|
|
21729
21913
|
function validProtocol(value) {
|
|
21730
21914
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -21837,7 +22021,7 @@ function formatComponentVersions(components) {
|
|
|
21837
22021
|
import { join as join12 } from "node:path";
|
|
21838
22022
|
|
|
21839
22023
|
// src/lib/attached-daemon.ts
|
|
21840
|
-
import { randomUUID } from "node:crypto";
|
|
22024
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
21841
22025
|
|
|
21842
22026
|
// src/lib/bootstrap.ts
|
|
21843
22027
|
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "node:fs";
|
|
@@ -21927,22 +22111,22 @@ function readInstallScript() {
|
|
|
21927
22111
|
// src/lib/net.ts
|
|
21928
22112
|
import { connect as connect4, createServer as createServer2 } from "node:net";
|
|
21929
22113
|
function findFreePort() {
|
|
21930
|
-
return new Promise((
|
|
22114
|
+
return new Promise((resolve6, reject) => {
|
|
21931
22115
|
const srv = createServer2();
|
|
21932
22116
|
srv.once("error", reject);
|
|
21933
22117
|
srv.listen(0, "127.0.0.1", () => {
|
|
21934
22118
|
const address = srv.address();
|
|
21935
22119
|
const port = typeof address === "object" && address !== null ? address.port : 0;
|
|
21936
|
-
srv.close(() =>
|
|
22120
|
+
srv.close(() => resolve6(port));
|
|
21937
22121
|
});
|
|
21938
22122
|
});
|
|
21939
22123
|
}
|
|
21940
22124
|
function tcpListening(port, timeoutMs = 500) {
|
|
21941
|
-
return new Promise((
|
|
22125
|
+
return new Promise((resolve6) => {
|
|
21942
22126
|
const sock = connect4({ host: "127.0.0.1", port });
|
|
21943
22127
|
const done = (ok) => {
|
|
21944
22128
|
sock.destroy();
|
|
21945
|
-
|
|
22129
|
+
resolve6(ok);
|
|
21946
22130
|
};
|
|
21947
22131
|
sock.setTimeout(timeoutMs, () => done(false));
|
|
21948
22132
|
sock.once("connect", () => done(true));
|
|
@@ -21978,35 +22162,50 @@ init_types();
|
|
|
21978
22162
|
async function ensureDaemon(transport, opts) {
|
|
21979
22163
|
const logger = opts.logger ?? silentLogger;
|
|
21980
22164
|
const startTimeoutMs = opts.startTimeoutMs ?? (opts.attachedFallback === void 0 ? 2e4 : 5e3);
|
|
22165
|
+
let mismatch;
|
|
21981
22166
|
try {
|
|
21982
22167
|
const authority = await acquireAuthority(transport);
|
|
21983
22168
|
return { port: authority.port, authority, bootstrapped: false, daemonLifetime: "persistent" };
|
|
21984
22169
|
} catch (err) {
|
|
21985
|
-
if (err instanceof CliError && err.code === "
|
|
22170
|
+
if (err instanceof CliError && err.code === "protocol_mismatch") mismatch = err;
|
|
21986
22171
|
}
|
|
21987
22172
|
const inspection = await inspectHost(transport).catch((err) => {
|
|
21988
|
-
if (err instanceof CliError) throw err;
|
|
22173
|
+
if (err instanceof CliError || mismatch) throw err;
|
|
21989
22174
|
return null;
|
|
21990
22175
|
});
|
|
22176
|
+
if (!inspection && mismatch) throw mismatch;
|
|
22177
|
+
const install = async () => {
|
|
22178
|
+
await installDaemon(transport, opts);
|
|
22179
|
+
return waitAfterInstall(transport, opts, logger, true, startTimeoutMs);
|
|
22180
|
+
};
|
|
21991
22181
|
if (inspection) {
|
|
21992
|
-
|
|
21993
|
-
|
|
21994
|
-
|
|
21995
|
-
|
|
21996
|
-
|
|
21997
|
-
|
|
21998
|
-
|
|
21999
|
-
|
|
22000
|
-
|
|
22182
|
+
let replacement;
|
|
22183
|
+
try {
|
|
22184
|
+
await runHandshake({
|
|
22185
|
+
host: transport.label,
|
|
22186
|
+
inspection,
|
|
22187
|
+
noUpgrade: opts.noUpgrade,
|
|
22188
|
+
confirmDaemonUpgrade: opts.confirmDaemonUpgrade,
|
|
22189
|
+
reinspect: () => inspectHost(transport),
|
|
22190
|
+
logger,
|
|
22191
|
+
upgradeDaemon: async () => {
|
|
22192
|
+
replacement = await install();
|
|
22193
|
+
return replacement.authority.version;
|
|
22194
|
+
}
|
|
22195
|
+
});
|
|
22196
|
+
if (replacement) return replacement;
|
|
22197
|
+
const authority = await acquireAuthority(transport);
|
|
22198
|
+
return { port: authority.port, authority, bootstrapped: false, daemonLifetime: "persistent" };
|
|
22199
|
+
} catch (err) {
|
|
22200
|
+
replacement?.authority.close();
|
|
22201
|
+
await replacement?.lease?.stop();
|
|
22202
|
+
throw err;
|
|
22001
22203
|
}
|
|
22002
|
-
|
|
22003
|
-
|
|
22004
|
-
);
|
|
22005
|
-
} else if (await installedVersion(transport) !== null) {
|
|
22204
|
+
}
|
|
22205
|
+
if (await installedVersion(transport) !== null) {
|
|
22006
22206
|
logger.info(`restarting puddled on ${transport.label}`);
|
|
22007
22207
|
}
|
|
22008
|
-
|
|
22009
|
-
return waitAfterInstall(transport, opts, logger, true, startTimeoutMs);
|
|
22208
|
+
return install();
|
|
22010
22209
|
}
|
|
22011
22210
|
async function waitAfterInstall(transport, opts, logger, bootstrapped, timeoutMs) {
|
|
22012
22211
|
try {
|
|
@@ -22042,7 +22241,7 @@ async function waitForStartedDaemon(transport, timeoutMs, signal) {
|
|
|
22042
22241
|
}
|
|
22043
22242
|
return { port: authority.port, authority };
|
|
22044
22243
|
} catch (err) {
|
|
22045
|
-
if (err instanceof CliError && err.code === "
|
|
22244
|
+
if (err instanceof CliError && err.code === "protocol_mismatch") throw err;
|
|
22046
22245
|
}
|
|
22047
22246
|
await sleep(500);
|
|
22048
22247
|
}
|
|
@@ -22055,23 +22254,6 @@ function startTimeout(transport) {
|
|
|
22055
22254
|
transport.kind === "ssh" ? `inspect it with: puddle logs ${transport.label}` : "inspect it with: puddle logs"
|
|
22056
22255
|
);
|
|
22057
22256
|
}
|
|
22058
|
-
function makeUpgrader(transport, client, opts, lease) {
|
|
22059
|
-
return async () => {
|
|
22060
|
-
await installDaemon(transport, opts);
|
|
22061
|
-
await lease?.ensureRunning();
|
|
22062
|
-
const fresh = await waitForStartedDaemon(transport, 3e4);
|
|
22063
|
-
client.setAuthority(fresh.authority);
|
|
22064
|
-
const deadline = Date.now() + 3e4;
|
|
22065
|
-
while (Date.now() < deadline) {
|
|
22066
|
-
if (await client.responds()) return;
|
|
22067
|
-
await sleep(500);
|
|
22068
|
-
}
|
|
22069
|
-
throw new CliError(
|
|
22070
|
-
"daemon_start_timeout",
|
|
22071
|
-
`puddled did not come back after updating on ${transport.label}`
|
|
22072
|
-
);
|
|
22073
|
-
};
|
|
22074
|
-
}
|
|
22075
22257
|
|
|
22076
22258
|
// src/lib/attached-daemon.ts
|
|
22077
22259
|
init_types();
|
|
@@ -22134,7 +22316,7 @@ var AttachedDaemon = class {
|
|
|
22134
22316
|
}
|
|
22135
22317
|
async launchAndWait(preferredPort) {
|
|
22136
22318
|
const portFlag = preferredPort === void 0 ? "" : ` --port ${preferredPort}`;
|
|
22137
|
-
const leasePidPath = `${hostPaths.home}/attached-${
|
|
22319
|
+
const leasePidPath = `${hostPaths.home}/attached-${randomUUID2()}.pid`;
|
|
22138
22320
|
this.leasePidPath = leasePidPath;
|
|
22139
22321
|
const command = this.ssh.runAttached(
|
|
22140
22322
|
`printf '%s\\n' "$$" > ${leasePidPath}; exec ${hostPaths.current}/puddled${portFlag} >> ${hostPaths.logs}/puddled.out.log 2>&1`
|
|
@@ -22230,10 +22412,6 @@ var DaemonClient = class {
|
|
|
22230
22412
|
this.port = port;
|
|
22231
22413
|
this.authority = authority;
|
|
22232
22414
|
}
|
|
22233
|
-
setAuthority(authority) {
|
|
22234
|
-
this.authority.close();
|
|
22235
|
-
this.authority = authority;
|
|
22236
|
-
}
|
|
22237
22415
|
setPort(port) {
|
|
22238
22416
|
this.port = port;
|
|
22239
22417
|
}
|
|
@@ -22275,21 +22453,6 @@ var DaemonClient = class {
|
|
|
22275
22453
|
async liveSessions() {
|
|
22276
22454
|
return (await this.sessions()).filter((s) => LIVE.has(s.status));
|
|
22277
22455
|
}
|
|
22278
|
-
async liveSessionCount() {
|
|
22279
|
-
return (await this.sessions()).filter((s) => LIVE.has(s.status)).length;
|
|
22280
|
-
}
|
|
22281
|
-
/** Readiness requires an authenticated response; rejection never counts as ready. */
|
|
22282
|
-
async responds() {
|
|
22283
|
-
try {
|
|
22284
|
-
const res = await fetch(`http://127.0.0.1:${this.port}/api/version`, {
|
|
22285
|
-
headers: { authorization: `Bearer ${this.authority.credential()}` },
|
|
22286
|
-
signal: AbortSignal.timeout(2e3)
|
|
22287
|
-
});
|
|
22288
|
-
return res.ok;
|
|
22289
|
-
} catch {
|
|
22290
|
-
return false;
|
|
22291
|
-
}
|
|
22292
|
-
}
|
|
22293
22456
|
};
|
|
22294
22457
|
async function readDaemonPort(transport) {
|
|
22295
22458
|
const runtime = await transport.readFile(hostPaths.runtime);
|
|
@@ -22311,51 +22474,8 @@ async function readDaemonPort(transport) {
|
|
|
22311
22474
|
return 7434;
|
|
22312
22475
|
}
|
|
22313
22476
|
|
|
22314
|
-
// src/lib/handshake.ts
|
|
22315
|
-
init_types();
|
|
22316
|
-
var CLI_UPGRADE_COMMAND = "npm install -g @puddle-code/cli";
|
|
22317
|
-
function decideHandshake(cli, daemon) {
|
|
22318
|
-
if (daemon.major === cli.major) return { kind: "proceed" };
|
|
22319
|
-
if (daemon.major < cli.major) return { kind: "upgrade-daemon" };
|
|
22320
|
-
return { kind: "refuse-cli-outdated", command: CLI_UPGRADE_COMMAND };
|
|
22321
|
-
}
|
|
22322
|
-
async function runHandshake(opts) {
|
|
22323
|
-
const logger = opts.logger ?? silentLogger;
|
|
22324
|
-
const daemon = await opts.client.version();
|
|
22325
|
-
const decision = decideHandshake(PROTOCOL_VERSION, daemon.protocol);
|
|
22326
|
-
if (decision.kind === "proceed") return daemon;
|
|
22327
|
-
if (decision.kind === "refuse-cli-outdated") {
|
|
22328
|
-
throw new CliError(
|
|
22329
|
-
"cli_outdated",
|
|
22330
|
-
`the daemon speaks protocol ${daemon.protocol.major}; this CLI speaks ${PROTOCOL_VERSION.major}`,
|
|
22331
|
-
`update the CLI: ${decision.command}`
|
|
22332
|
-
);
|
|
22333
|
-
}
|
|
22334
|
-
const liveSessions = await opts.client.liveSessionCount().catch(() => 0);
|
|
22335
|
-
if (opts.noUpgrade) {
|
|
22336
|
-
throw new CliError(
|
|
22337
|
-
"upgrade_failed",
|
|
22338
|
-
`the daemon speaks protocol ${daemon.protocol.major} (CLI: ${PROTOCOL_VERSION.major}) and --no-upgrade is set`,
|
|
22339
|
-
`re-run without --no-upgrade to update it (${liveSessions} live session(s) would be interrupted and can be resumed)`
|
|
22340
|
-
);
|
|
22341
|
-
}
|
|
22342
|
-
logger.info(
|
|
22343
|
-
`updating puddled (protocol ${daemon.protocol.major} \u2192 ${PROTOCOL_VERSION.major}) \u2014 ${liveSessions} live session(s) will be interrupted and can be resumed`
|
|
22344
|
-
);
|
|
22345
|
-
await opts.upgradeDaemon({ from: daemon.version, liveSessions });
|
|
22346
|
-
const after = await opts.client.version();
|
|
22347
|
-
if (after.protocol.major !== PROTOCOL_VERSION.major) {
|
|
22348
|
-
throw new CliError(
|
|
22349
|
-
"upgrade_failed",
|
|
22350
|
-
`the daemon still speaks protocol ${after.protocol.major} after updating`,
|
|
22351
|
-
"inspect the daemon logs: puddle logs"
|
|
22352
|
-
);
|
|
22353
|
-
}
|
|
22354
|
-
return after;
|
|
22355
|
-
}
|
|
22356
|
-
|
|
22357
22477
|
// src/lib/serve/ui-server.ts
|
|
22358
|
-
import { randomUUID as
|
|
22478
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
22359
22479
|
import { createServer as createServer3 } from "node:http";
|
|
22360
22480
|
|
|
22361
22481
|
// src/lib/auth/launcher.ts
|
|
@@ -22391,7 +22511,7 @@ async function startLauncher(home, identity, invite) {
|
|
|
22391
22511
|
path,
|
|
22392
22512
|
close: async () => {
|
|
22393
22513
|
for (const socket of sockets) socket.destroy();
|
|
22394
|
-
await new Promise((
|
|
22514
|
+
await new Promise((resolve6) => server.close(() => resolve6()));
|
|
22395
22515
|
}
|
|
22396
22516
|
};
|
|
22397
22517
|
}
|
|
@@ -22401,14 +22521,14 @@ async function requestInvitation(home, path) {
|
|
|
22401
22521
|
const stored = readPrivateJson(secretFile(home, path));
|
|
22402
22522
|
pipeSecret = stored.secret;
|
|
22403
22523
|
} else privatePath(path);
|
|
22404
|
-
return new Promise((
|
|
22524
|
+
return new Promise((resolve6, reject) => {
|
|
22405
22525
|
const socket = connect5(path);
|
|
22406
22526
|
socket.setTimeout(5e3, () => socket.destroy(new Error("Launcher did not answer")));
|
|
22407
22527
|
jsonLines(
|
|
22408
22528
|
socket,
|
|
22409
22529
|
(value) => {
|
|
22410
22530
|
const message = launcherResponseSchema.parse(value);
|
|
22411
|
-
|
|
22531
|
+
resolve6(message.url);
|
|
22412
22532
|
socket.end();
|
|
22413
22533
|
},
|
|
22414
22534
|
() => socket.destroy(new Error("Invalid launcher reply"))
|
|
@@ -22628,7 +22748,7 @@ function writeStore(file2, store) {
|
|
|
22628
22748
|
renameSync3(tmp, file2);
|
|
22629
22749
|
}
|
|
22630
22750
|
function readBody(req) {
|
|
22631
|
-
return new Promise((
|
|
22751
|
+
return new Promise((resolve6, reject) => {
|
|
22632
22752
|
let size = 0;
|
|
22633
22753
|
const chunks = [];
|
|
22634
22754
|
req.on("data", (chunk) => {
|
|
@@ -22640,7 +22760,7 @@ function readBody(req) {
|
|
|
22640
22760
|
}
|
|
22641
22761
|
chunks.push(chunk);
|
|
22642
22762
|
});
|
|
22643
|
-
req.on("end", () =>
|
|
22763
|
+
req.on("end", () => resolve6(Buffer.concat(chunks).toString("utf8")));
|
|
22644
22764
|
req.on("error", reject);
|
|
22645
22765
|
});
|
|
22646
22766
|
}
|
|
@@ -23227,7 +23347,7 @@ async function startUiServer(opts) {
|
|
|
23227
23347
|
const tracker = new ProxySocketTracker();
|
|
23228
23348
|
const serveStatic = createStaticHandler(opts.assetsDir);
|
|
23229
23349
|
const handleRemoteAccess = remoteAccessHandler(opts.remoteAccess);
|
|
23230
|
-
const nonce =
|
|
23350
|
+
const nonce = randomUUID3();
|
|
23231
23351
|
let origin = "";
|
|
23232
23352
|
let proxyOrigin = "";
|
|
23233
23353
|
let browsers;
|
|
@@ -23365,8 +23485,8 @@ async function startUiServer(opts) {
|
|
|
23365
23485
|
browsers.close();
|
|
23366
23486
|
bridge.close();
|
|
23367
23487
|
tracker.destroyAll();
|
|
23368
|
-
await new Promise((
|
|
23369
|
-
server.close(() =>
|
|
23488
|
+
await new Promise((resolve6) => {
|
|
23489
|
+
server.close(() => resolve6());
|
|
23370
23490
|
server.closeAllConnections();
|
|
23371
23491
|
});
|
|
23372
23492
|
}
|
|
@@ -23376,15 +23496,15 @@ async function listen(server, startPort, strict, avoidPort) {
|
|
|
23376
23496
|
for (let probe = 0; probe < MAX_PORT_PROBES; probe += 1) {
|
|
23377
23497
|
const port = startPort + probe;
|
|
23378
23498
|
if (!strict && port === avoidPort) continue;
|
|
23379
|
-
const ok = await new Promise((
|
|
23499
|
+
const ok = await new Promise((resolve6, rejectListen) => {
|
|
23380
23500
|
const onError = (err) => {
|
|
23381
23501
|
server.off("listening", onListening);
|
|
23382
|
-
if (err.code === "EADDRINUSE")
|
|
23502
|
+
if (err.code === "EADDRINUSE") resolve6(false);
|
|
23383
23503
|
else rejectListen(err);
|
|
23384
23504
|
};
|
|
23385
23505
|
const onListening = () => {
|
|
23386
23506
|
server.off("error", onError);
|
|
23387
|
-
|
|
23507
|
+
resolve6(true);
|
|
23388
23508
|
};
|
|
23389
23509
|
server.once("error", onError);
|
|
23390
23510
|
server.once("listening", onListening);
|
|
@@ -23665,7 +23785,12 @@ async function connectRemote(opts) {
|
|
|
23665
23785
|
let uiResource;
|
|
23666
23786
|
let endpointResource;
|
|
23667
23787
|
try {
|
|
23668
|
-
const bootstrap = {
|
|
23788
|
+
const bootstrap = {
|
|
23789
|
+
tarball: opts.tarball,
|
|
23790
|
+
logger,
|
|
23791
|
+
noUpgrade: opts.noUpgrade,
|
|
23792
|
+
confirmDaemonUpgrade: opts.confirmDaemonUpgrade
|
|
23793
|
+
};
|
|
23669
23794
|
const attachedDaemon = new AttachedDaemon(ssh, logger);
|
|
23670
23795
|
const endpoint = await ensureDaemon(ssh, {
|
|
23671
23796
|
...bootstrap,
|
|
@@ -23706,12 +23831,7 @@ async function connectRemote(opts) {
|
|
|
23706
23831
|
logger
|
|
23707
23832
|
});
|
|
23708
23833
|
oauthForwardResource = oauthForward;
|
|
23709
|
-
const daemon = await
|
|
23710
|
-
client,
|
|
23711
|
-
noUpgrade: opts.noUpgrade,
|
|
23712
|
-
upgradeDaemon: makeUpgrader(ssh, client, bootstrap, endpoint.lease),
|
|
23713
|
-
logger
|
|
23714
|
-
});
|
|
23834
|
+
const daemon = verifyDaemonVersion(await client.version());
|
|
23715
23835
|
const avoidPort = await readDaemonPort(new LocalTransport());
|
|
23716
23836
|
endpoint.authority.setVerifier(async () => {
|
|
23717
23837
|
const generation = endpoint.authority.generation;
|
|
@@ -23734,7 +23854,7 @@ async function connectRemote(opts) {
|
|
|
23734
23854
|
watchTunnel(replacement);
|
|
23735
23855
|
await previous?.close();
|
|
23736
23856
|
}
|
|
23737
|
-
await client.version();
|
|
23857
|
+
verifyDaemonVersion(await client.version());
|
|
23738
23858
|
});
|
|
23739
23859
|
const ui = await startUiServer({
|
|
23740
23860
|
authority: endpoint.authority,
|
|
@@ -23936,24 +24056,26 @@ init_types();
|
|
|
23936
24056
|
async function startLocal(opts) {
|
|
23937
24057
|
const logger = opts.logger ?? silentLogger;
|
|
23938
24058
|
const transport = new LocalTransport();
|
|
23939
|
-
const bootstrap = {
|
|
23940
|
-
|
|
24059
|
+
const bootstrap = {
|
|
24060
|
+
tarball: opts.tarball,
|
|
24061
|
+
logger,
|
|
24062
|
+
noUpgrade: opts.noUpgrade,
|
|
24063
|
+
confirmDaemonUpgrade: opts.confirmDaemonUpgrade
|
|
24064
|
+
};
|
|
24065
|
+
let endpoint;
|
|
23941
24066
|
try {
|
|
23942
|
-
|
|
23943
|
-
const
|
|
23944
|
-
|
|
23945
|
-
|
|
23946
|
-
upgradeDaemon: makeUpgrader(transport, client, bootstrap),
|
|
23947
|
-
logger
|
|
23948
|
-
});
|
|
24067
|
+
endpoint = await ensureDaemon(transport, bootstrap);
|
|
24068
|
+
const authority = endpoint.authority;
|
|
24069
|
+
const client = new DaemonClient(endpoint.port, authority);
|
|
24070
|
+
const daemon = verifyDaemonVersion(await client.version());
|
|
23949
24071
|
let currentUi = void 0;
|
|
23950
|
-
|
|
23951
|
-
client.setPort(
|
|
23952
|
-
await client.version();
|
|
23953
|
-
currentUi?.setTarget({ host: "127.0.0.1", port:
|
|
24072
|
+
authority.setVerifier(async () => {
|
|
24073
|
+
client.setPort(authority.port);
|
|
24074
|
+
verifyDaemonVersion(await client.version());
|
|
24075
|
+
currentUi?.setTarget({ host: "127.0.0.1", port: authority.port });
|
|
23954
24076
|
});
|
|
23955
24077
|
const ui = await startUiServer({
|
|
23956
|
-
authority
|
|
24078
|
+
authority,
|
|
23957
24079
|
identity: "local",
|
|
23958
24080
|
refreshId: opts.refreshId,
|
|
23959
24081
|
assetsDir: opts.assetsDir,
|
|
@@ -23982,12 +24104,12 @@ async function startLocal(opts) {
|
|
|
23982
24104
|
},
|
|
23983
24105
|
async stop() {
|
|
23984
24106
|
await ui.close();
|
|
23985
|
-
|
|
24107
|
+
authority.close();
|
|
23986
24108
|
transport.dispose();
|
|
23987
24109
|
}
|
|
23988
24110
|
};
|
|
23989
24111
|
} catch (err) {
|
|
23990
|
-
endpoint
|
|
24112
|
+
endpoint?.authority.close();
|
|
23991
24113
|
transport.dispose();
|
|
23992
24114
|
throw err;
|
|
23993
24115
|
}
|
|
@@ -24017,12 +24139,170 @@ async function statusReport(client) {
|
|
|
24017
24139
|
// src/cli/run.ts
|
|
24018
24140
|
init_types();
|
|
24019
24141
|
|
|
24020
|
-
// src/cli/manage.ts
|
|
24142
|
+
// src/cli/manage-cli.ts
|
|
24143
|
+
init_types();
|
|
24021
24144
|
import { spawn as spawn7 } from "node:child_process";
|
|
24022
|
-
import {
|
|
24023
|
-
|
|
24145
|
+
import { dirname as dirname10 } from "node:path";
|
|
24146
|
+
|
|
24147
|
+
// src/lib/standalone-cli.ts
|
|
24148
|
+
init_types();
|
|
24149
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
24150
|
+
import { existsSync as existsSync5, lstatSync as lstatSync2, readFileSync as readFileSync9, realpathSync as realpathSync2 } from "node:fs";
|
|
24151
|
+
import { mkdir as mkdir2, readFile as readFile2, rm as rm2, unlink } from "node:fs/promises";
|
|
24152
|
+
import { dirname as dirname9, isAbsolute, join as join15, resolve as resolve5 } from "node:path";
|
|
24153
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
24154
|
+
function standaloneInstallation(entryFile = fileURLToPath4(import.meta.url), execPath = process.execPath) {
|
|
24155
|
+
const release = resolve5(dirname9(entryFile), "..");
|
|
24156
|
+
if (!existsSync5(join15(release, "STANDALONE"))) return null;
|
|
24157
|
+
try {
|
|
24158
|
+
const root = resolve5(release, "..", "..");
|
|
24159
|
+
const record2 = JSON.parse(readFileSync9(join15(root, "installation.json"), "utf8"));
|
|
24160
|
+
if (readFileSync9(join15(release, "STANDALONE"), "utf8").trim() !== "puddle-cli" || readFileSync9(join15(root, "INSTALLATION"), "utf8").trim() !== "puddle-cli" || dirname9(release) !== join15(root, "versions") || realpathSync2(release) !== release || realpathSync2(execPath) !== join15(release, "bin/node") || record2.kind !== "puddle-cli" || typeof record2.launcher !== "string" || !isAbsolute(record2.launcher))
|
|
24161
|
+
throw new Error("Installation metadata does not match this CLI");
|
|
24162
|
+
return { root, release, launcher: record2.launcher };
|
|
24163
|
+
} catch {
|
|
24164
|
+
throw new CliError(
|
|
24165
|
+
"not_installed",
|
|
24166
|
+
"this standalone archive is not an installer-managed CLI",
|
|
24167
|
+
"run the public install.sh to install the CLI before using upgrade or remove"
|
|
24168
|
+
);
|
|
24169
|
+
}
|
|
24170
|
+
}
|
|
24171
|
+
function upgradeStandaloneCli(installation, version2, repo) {
|
|
24172
|
+
const script = readFileSync9(join15(installation.release, "cli/install-cli.sh"), "utf8");
|
|
24173
|
+
const args = [
|
|
24174
|
+
"-s",
|
|
24175
|
+
"--",
|
|
24176
|
+
"--prefix",
|
|
24177
|
+
installation.root,
|
|
24178
|
+
"--bin-dir",
|
|
24179
|
+
dirname9(installation.launcher)
|
|
24180
|
+
];
|
|
24181
|
+
if (version2 !== void 0) args.push("--version", version2);
|
|
24182
|
+
if (repo !== void 0) args.push("--repo", repo);
|
|
24183
|
+
return new Promise((resolve6, reject) => {
|
|
24184
|
+
const child = spawn6("sh", args, { stdio: ["pipe", "inherit", "inherit"] });
|
|
24185
|
+
child.on("error", reject);
|
|
24186
|
+
child.stdin.on("error", (error51) => {
|
|
24187
|
+
if (error51.code !== "EPIPE") reject(error51);
|
|
24188
|
+
});
|
|
24189
|
+
child.stdin.end(script);
|
|
24190
|
+
child.on("exit", (code) => {
|
|
24191
|
+
if (code === 0) resolve6(0);
|
|
24192
|
+
else reject(new CliError("not_installed", `CLI installer exited with ${code ?? "a signal"}`));
|
|
24193
|
+
});
|
|
24194
|
+
});
|
|
24195
|
+
}
|
|
24196
|
+
function standaloneLauncher(root) {
|
|
24197
|
+
const quoted = `'${join15(root, "current/puddle").replaceAll("'", "'\\''")}'`;
|
|
24198
|
+
return `#!/bin/sh
|
|
24199
|
+
# puddle standalone launcher
|
|
24200
|
+
exec ${quoted} "$@"
|
|
24201
|
+
`;
|
|
24202
|
+
}
|
|
24203
|
+
async function removeStandaloneCli(installation) {
|
|
24204
|
+
const lock = join15(installation.root, ".install-lock");
|
|
24205
|
+
try {
|
|
24206
|
+
await mkdir2(lock);
|
|
24207
|
+
} catch {
|
|
24208
|
+
throw new CliError("already_running", "another CLI installation operation is running");
|
|
24209
|
+
}
|
|
24210
|
+
try {
|
|
24211
|
+
if (lstatSync2(installation.launcher).isSymbolicLink() || await readFile2(installation.launcher, "utf8") !== standaloneLauncher(installation.root))
|
|
24212
|
+
throw new CliError("not_installed", "the CLI launcher was replaced; refusing removal");
|
|
24213
|
+
await unlink(installation.launcher);
|
|
24214
|
+
await rm2(installation.root, { recursive: true, force: true });
|
|
24215
|
+
} finally {
|
|
24216
|
+
await rm2(lock, { recursive: true, force: true });
|
|
24217
|
+
}
|
|
24218
|
+
}
|
|
24219
|
+
|
|
24220
|
+
// src/cli/manage-cli.ts
|
|
24221
|
+
function upgradeCli(version2, logger) {
|
|
24222
|
+
const standalone = standaloneInstallation();
|
|
24223
|
+
if (standalone) {
|
|
24224
|
+
logger.info(`upgrading standalone CLI ${cliVersion()} to ${version2 ?? "the latest release"}`);
|
|
24225
|
+
return upgradeStandaloneCli(standalone, version2, repoSlug());
|
|
24226
|
+
}
|
|
24227
|
+
const spec = `@puddle-code/cli@${version2 ?? "latest"}`;
|
|
24228
|
+
logger.info(`puddle CLI ${cliVersion()} \u2014 asking npm for ${version2 ?? "the latest release"}`);
|
|
24229
|
+
return new Promise((resolve6, reject) => {
|
|
24230
|
+
const child = spawn7("npm", ["install", "-g", spec], { stdio: "inherit" });
|
|
24231
|
+
child.on(
|
|
24232
|
+
"error",
|
|
24233
|
+
() => reject(
|
|
24234
|
+
new CliError(
|
|
24235
|
+
"not_installed",
|
|
24236
|
+
"npm is not on PATH",
|
|
24237
|
+
`the CLI is installed and upgraded via npm: npm install -g ${spec}`
|
|
24238
|
+
)
|
|
24239
|
+
)
|
|
24240
|
+
);
|
|
24241
|
+
child.on("exit", (code) => {
|
|
24242
|
+
if (code === 0) {
|
|
24243
|
+
logger.info("done \u2014 `puddle --version` shows the installed version");
|
|
24244
|
+
resolve6(0);
|
|
24245
|
+
} else {
|
|
24246
|
+
reject(new CliError("not_installed", `npm install exited with ${code ?? "a signal"}`));
|
|
24247
|
+
}
|
|
24248
|
+
});
|
|
24249
|
+
});
|
|
24250
|
+
}
|
|
24251
|
+
async function removeCli(yes, logger) {
|
|
24252
|
+
const standalone = standaloneInstallation();
|
|
24253
|
+
if (standalone) {
|
|
24254
|
+
const proceed2 = await confirm(
|
|
24255
|
+
`Remove the standalone CLI at ${standalone.root} and its launcher in ${dirname10(standalone.launcher)}? Close its cockpits first; daemons and their sessions are untouched.`,
|
|
24256
|
+
{ skip: yes }
|
|
24257
|
+
);
|
|
24258
|
+
if (!proceed2) {
|
|
24259
|
+
logger.info("nothing removed");
|
|
24260
|
+
return 0;
|
|
24261
|
+
}
|
|
24262
|
+
await removeStandaloneCli(standalone);
|
|
24263
|
+
logger.info("removed the standalone CLI \u2014 daemon installations and data are untouched");
|
|
24264
|
+
return 0;
|
|
24265
|
+
}
|
|
24266
|
+
const managed = await new Promise((resolve6) => {
|
|
24267
|
+
const child = spawn7("npm", ["ls", "-g", "@puddle-code/cli", "--depth=0"], { stdio: "ignore" });
|
|
24268
|
+
child.on("error", () => resolve6(false));
|
|
24269
|
+
child.on("exit", (code) => resolve6(code === 0));
|
|
24270
|
+
});
|
|
24271
|
+
if (!managed) {
|
|
24272
|
+
throw new CliError(
|
|
24273
|
+
"not_installed",
|
|
24274
|
+
"the puddle CLI is not an npm global install on this machine",
|
|
24275
|
+
"a repo checkout or linked dev build is removed the way it was installed"
|
|
24276
|
+
);
|
|
24277
|
+
}
|
|
24278
|
+
const proceed = await confirm(
|
|
24279
|
+
`Remove the puddle CLI (npm uninstall -g @puddle-code/cli)? Running cockpits keep running until killed; daemons and their sessions are untouched.`,
|
|
24280
|
+
{ skip: yes }
|
|
24281
|
+
);
|
|
24282
|
+
if (!proceed) {
|
|
24283
|
+
logger.info("nothing removed");
|
|
24284
|
+
return 0;
|
|
24285
|
+
}
|
|
24286
|
+
return new Promise((resolve6, reject) => {
|
|
24287
|
+
const child = spawn7("npm", ["uninstall", "-g", "@puddle-code/cli"], { stdio: "inherit" });
|
|
24288
|
+
child.on("error", () => reject(new CliError("not_installed", "npm is not on PATH")));
|
|
24289
|
+
child.on("exit", (code) => {
|
|
24290
|
+
if (code === 0) {
|
|
24291
|
+
logger.info("removed \u2014 daemons keep running (puddle remove daemon uninstalls one)");
|
|
24292
|
+
resolve6(0);
|
|
24293
|
+
} else {
|
|
24294
|
+
reject(new CliError("not_installed", `npm uninstall exited with ${code ?? "a signal"}`));
|
|
24295
|
+
}
|
|
24296
|
+
});
|
|
24297
|
+
});
|
|
24298
|
+
}
|
|
24299
|
+
|
|
24300
|
+
// src/cli/manage.ts
|
|
24301
|
+
import { spawn as spawn9 } from "node:child_process";
|
|
24302
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
24303
|
+
import { chmod as chmod2, copyFile, mkdir as mkdir3, rename, rm as rm3 } from "node:fs/promises";
|
|
24024
24304
|
import { homedir as homedir2 } from "node:os";
|
|
24025
|
-
import { join as
|
|
24305
|
+
import { join as join16 } from "node:path";
|
|
24026
24306
|
|
|
24027
24307
|
// src/lib/releases.ts
|
|
24028
24308
|
init_types();
|
|
@@ -24155,9 +24435,9 @@ async function upgradeDaemon(transport, opts = {}) {
|
|
|
24155
24435
|
}
|
|
24156
24436
|
|
|
24157
24437
|
// src/cli/detach.ts
|
|
24158
|
-
import { spawn as
|
|
24159
|
-
import { closeSync as closeSync2, mkdirSync as mkdirSync6, openSync as openSync2, readFileSync as
|
|
24160
|
-
import { dirname as
|
|
24438
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
24439
|
+
import { closeSync as closeSync2, mkdirSync as mkdirSync6, openSync as openSync2, readFileSync as readFileSync10 } from "node:fs";
|
|
24440
|
+
import { dirname as dirname11 } from "node:path";
|
|
24161
24441
|
init_types();
|
|
24162
24442
|
var COCKPIT_CHILD_ENV = "PUDDLE_COCKPIT_CHILD";
|
|
24163
24443
|
var READY_TIMEOUT_MS = 3e5;
|
|
@@ -24196,12 +24476,12 @@ async function launchDetached(opts) {
|
|
|
24196
24476
|
}
|
|
24197
24477
|
if (target !== "local") await new SshTransport(target).open();
|
|
24198
24478
|
const logFile = cockpitLogPath(target);
|
|
24199
|
-
mkdirSync6(
|
|
24479
|
+
mkdirSync6(dirname11(logFile), { recursive: true });
|
|
24200
24480
|
const fd = openSync2(logFile, "w");
|
|
24201
24481
|
const argv = opts.argv ?? process.argv.slice(2);
|
|
24202
|
-
const child =
|
|
24482
|
+
const child = spawn8(process.execPath, [process.argv[1] ?? "", ...argv], {
|
|
24203
24483
|
detached: true,
|
|
24204
|
-
stdio: ["ignore", fd, fd],
|
|
24484
|
+
stdio: ["ignore", fd, fd, "ipc"],
|
|
24205
24485
|
env: { ...process.env, [COCKPIT_CHILD_ENV]: "1" }
|
|
24206
24486
|
});
|
|
24207
24487
|
closeSync2(fd);
|
|
@@ -24210,7 +24490,13 @@ async function launchDetached(opts) {
|
|
|
24210
24490
|
throw new CliError("cockpit_launch_failed", "could not spawn the background cockpit process");
|
|
24211
24491
|
}
|
|
24212
24492
|
logger.info(`starting the cockpit for ${target} in the background\u2026`);
|
|
24213
|
-
const
|
|
24493
|
+
const stopPromptRelay = relayDaemonUpgradePrompt(child);
|
|
24494
|
+
let record2;
|
|
24495
|
+
try {
|
|
24496
|
+
record2 = await followStartup(target, child.pid, logFile);
|
|
24497
|
+
} finally {
|
|
24498
|
+
stopPromptRelay();
|
|
24499
|
+
}
|
|
24214
24500
|
if (record2 === null || record2.status !== "ready") {
|
|
24215
24501
|
if (record2?.status === "error") {
|
|
24216
24502
|
logger.warn(record2.message ?? "the cockpit failed to start");
|
|
@@ -24233,7 +24519,7 @@ async function followStartup(target, childPid, logFile) {
|
|
|
24233
24519
|
let offset = 0;
|
|
24234
24520
|
const relay = () => {
|
|
24235
24521
|
try {
|
|
24236
|
-
const buf =
|
|
24522
|
+
const buf = readFileSync10(logFile);
|
|
24237
24523
|
if (buf.length > offset) {
|
|
24238
24524
|
process.stderr.write(buf.subarray(offset));
|
|
24239
24525
|
offset = buf.length;
|
|
@@ -24258,7 +24544,7 @@ async function followStartup(target, childPid, logFile) {
|
|
|
24258
24544
|
}
|
|
24259
24545
|
function spawnDetachedRefresh(target, argv, refreshId) {
|
|
24260
24546
|
const logFile = cockpitLogPath(target);
|
|
24261
|
-
mkdirSync6(
|
|
24547
|
+
mkdirSync6(dirname11(logFile), { recursive: true });
|
|
24262
24548
|
const fd = openSync2(logFile, "a");
|
|
24263
24549
|
const env = {
|
|
24264
24550
|
...process.env,
|
|
@@ -24266,7 +24552,7 @@ function spawnDetachedRefresh(target, argv, refreshId) {
|
|
|
24266
24552
|
PUDDLE_INTERNAL_REFRESH: "1"
|
|
24267
24553
|
};
|
|
24268
24554
|
delete env[COCKPIT_CHILD_ENV];
|
|
24269
|
-
const child =
|
|
24555
|
+
const child = spawn8(process.execPath, [process.argv[1] ?? "", ...argv], {
|
|
24270
24556
|
detached: true,
|
|
24271
24557
|
stdio: ["ignore", fd, fd],
|
|
24272
24558
|
env
|
|
@@ -24299,41 +24585,6 @@ async function presentInvitation(record2, noBrowser, logger) {
|
|
|
24299
24585
|
else openBrowser(url2);
|
|
24300
24586
|
}
|
|
24301
24587
|
|
|
24302
|
-
// src/cli/prompt.ts
|
|
24303
|
-
init_types();
|
|
24304
|
-
import { createInterface as createInterface2 } from "node:readline";
|
|
24305
|
-
async function ask(question, def) {
|
|
24306
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) return def;
|
|
24307
|
-
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
24308
|
-
try {
|
|
24309
|
-
const answer = await new Promise(
|
|
24310
|
-
(resolve5) => rl.question(`${question} [${def}] `, resolve5)
|
|
24311
|
-
);
|
|
24312
|
-
return answer.trim() === "" ? def : answer.trim();
|
|
24313
|
-
} finally {
|
|
24314
|
-
rl.close();
|
|
24315
|
-
}
|
|
24316
|
-
}
|
|
24317
|
-
async function confirm(question, opts = {}) {
|
|
24318
|
-
if (opts.skip === true) return true;
|
|
24319
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
24320
|
-
throw new CliError(
|
|
24321
|
-
"bad_arguments",
|
|
24322
|
-
"refusing to confirm without a terminal",
|
|
24323
|
-
"pass --yes to proceed non-interactively"
|
|
24324
|
-
);
|
|
24325
|
-
}
|
|
24326
|
-
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
24327
|
-
try {
|
|
24328
|
-
const answer = await new Promise(
|
|
24329
|
-
(resolve5) => rl.question(`${question} [y/N] `, resolve5)
|
|
24330
|
-
);
|
|
24331
|
-
return /^y(es)?$/i.test(answer.trim());
|
|
24332
|
-
} finally {
|
|
24333
|
-
rl.close();
|
|
24334
|
-
}
|
|
24335
|
-
}
|
|
24336
|
-
|
|
24337
24588
|
// src/cli/manage.ts
|
|
24338
24589
|
function assertLocal(what, host, verb) {
|
|
24339
24590
|
if (host === void 0 || host === "local") return;
|
|
@@ -24442,31 +24693,6 @@ async function runUpgrade(cmd, logger) {
|
|
|
24442
24693
|
else if (process.platform === "darwin") logger.info("no desktop app installed \u2014 skipping");
|
|
24443
24694
|
return upgradeCli(cmd.version, logger);
|
|
24444
24695
|
}
|
|
24445
|
-
function upgradeCli(version2, logger) {
|
|
24446
|
-
const spec = `@puddle-code/cli@${version2 ?? "latest"}`;
|
|
24447
|
-
logger.info(`puddle CLI ${cliVersion()} \u2014 asking npm for ${version2 ?? "the latest release"}`);
|
|
24448
|
-
return new Promise((resolve5, reject) => {
|
|
24449
|
-
const child = spawn7("npm", ["install", "-g", spec], { stdio: "inherit" });
|
|
24450
|
-
child.on(
|
|
24451
|
-
"error",
|
|
24452
|
-
() => reject(
|
|
24453
|
-
new CliError(
|
|
24454
|
-
"not_installed",
|
|
24455
|
-
"npm is not on PATH",
|
|
24456
|
-
`the CLI is installed and upgraded via npm: npm install -g ${spec}`
|
|
24457
|
-
)
|
|
24458
|
-
)
|
|
24459
|
-
);
|
|
24460
|
-
child.on("exit", (code) => {
|
|
24461
|
-
if (code === 0) {
|
|
24462
|
-
logger.info("done \u2014 `puddle --version` shows the installed version");
|
|
24463
|
-
resolve5(0);
|
|
24464
|
-
} else {
|
|
24465
|
-
reject(new CliError("not_installed", `npm install exited with ${code ?? "a signal"}`));
|
|
24466
|
-
}
|
|
24467
|
-
});
|
|
24468
|
-
});
|
|
24469
|
-
}
|
|
24470
24696
|
async function setDesktop(opts, logger) {
|
|
24471
24697
|
if (process.platform === "linux") {
|
|
24472
24698
|
if (opts.verb !== "install") {
|
|
@@ -24538,22 +24764,22 @@ async function installDesktopAppImage(version2, logger) {
|
|
|
24538
24764
|
);
|
|
24539
24765
|
}
|
|
24540
24766
|
const dir = expandHome2(await ask("Where should the AppImage be stored?", "~/puddle"));
|
|
24541
|
-
const target =
|
|
24542
|
-
if (
|
|
24767
|
+
const target = join16(dir, "Puddle.AppImage");
|
|
24768
|
+
if (existsSync6(target) && version2 === void 0) {
|
|
24543
24769
|
logger.info(`${target} already exists \u2014 the app updates itself in-app (its update toast)`);
|
|
24544
24770
|
return 0;
|
|
24545
24771
|
}
|
|
24546
24772
|
logger.info(`installing Puddle ${update.version} to ${target}`);
|
|
24547
24773
|
const staged2 = await stageDesktopUpdate(update, { logger });
|
|
24548
|
-
await
|
|
24549
|
-
await
|
|
24774
|
+
await mkdir3(dir, { recursive: true });
|
|
24775
|
+
await rm3(target, { force: true });
|
|
24550
24776
|
try {
|
|
24551
24777
|
await rename(staged2.stagedPath, target);
|
|
24552
24778
|
} catch {
|
|
24553
24779
|
await copyFile(staged2.stagedPath, target);
|
|
24554
24780
|
await chmod2(target, 493);
|
|
24555
24781
|
}
|
|
24556
|
-
await
|
|
24782
|
+
await rm3(staged2.dir, { recursive: true, force: true });
|
|
24557
24783
|
const protocol = componentProtocolForVersion(update.version);
|
|
24558
24784
|
recordDesktopInstallation({
|
|
24559
24785
|
path: target,
|
|
@@ -24561,14 +24787,14 @@ async function installDesktopAppImage(version2, logger) {
|
|
|
24561
24787
|
...protocol ? { protocol } : {}
|
|
24562
24788
|
});
|
|
24563
24789
|
logger.info(`installed \u2014 run it directly (${target}) or add it to your launcher`);
|
|
24564
|
-
const opener =
|
|
24790
|
+
const opener = spawn9("xdg-open", [dir], { stdio: "ignore", detached: true });
|
|
24565
24791
|
opener.on("error", () => void 0);
|
|
24566
24792
|
opener.unref();
|
|
24567
24793
|
return 0;
|
|
24568
24794
|
}
|
|
24569
24795
|
function expandHome2(path) {
|
|
24570
24796
|
if (path === "~") return homedir2();
|
|
24571
|
-
return path.startsWith("~/") ?
|
|
24797
|
+
return path.startsWith("~/") ? join16(homedir2(), path.slice(2)) : path;
|
|
24572
24798
|
}
|
|
24573
24799
|
async function runRemove(cmd, logger) {
|
|
24574
24800
|
switch (cmd.what) {
|
|
@@ -24582,40 +24808,6 @@ async function runRemove(cmd, logger) {
|
|
|
24582
24808
|
return removeDaemonCmd(cmd, logger);
|
|
24583
24809
|
}
|
|
24584
24810
|
}
|
|
24585
|
-
async function removeCli(yes, logger) {
|
|
24586
|
-
const managed = await new Promise((resolve5) => {
|
|
24587
|
-
const child = spawn7("npm", ["ls", "-g", "@puddle-code/cli", "--depth=0"], { stdio: "ignore" });
|
|
24588
|
-
child.on("error", () => resolve5(false));
|
|
24589
|
-
child.on("exit", (code) => resolve5(code === 0));
|
|
24590
|
-
});
|
|
24591
|
-
if (!managed) {
|
|
24592
|
-
throw new CliError(
|
|
24593
|
-
"not_installed",
|
|
24594
|
-
"the puddle CLI is not an npm global install on this machine",
|
|
24595
|
-
"a repo checkout or linked dev build is removed the way it was installed"
|
|
24596
|
-
);
|
|
24597
|
-
}
|
|
24598
|
-
const proceed = await confirm(
|
|
24599
|
-
`Remove the puddle CLI (npm uninstall -g @puddle-code/cli)? Running cockpits keep running until killed; daemons and their sessions are untouched.`,
|
|
24600
|
-
{ skip: yes }
|
|
24601
|
-
);
|
|
24602
|
-
if (!proceed) {
|
|
24603
|
-
logger.info("nothing removed");
|
|
24604
|
-
return 0;
|
|
24605
|
-
}
|
|
24606
|
-
return new Promise((resolve5, reject) => {
|
|
24607
|
-
const child = spawn7("npm", ["uninstall", "-g", "@puddle-code/cli"], { stdio: "inherit" });
|
|
24608
|
-
child.on("error", () => reject(new CliError("not_installed", "npm is not on PATH")));
|
|
24609
|
-
child.on("exit", (code) => {
|
|
24610
|
-
if (code === 0) {
|
|
24611
|
-
logger.info("removed \u2014 daemons keep running (puddle remove daemon uninstalls one)");
|
|
24612
|
-
resolve5(0);
|
|
24613
|
-
} else {
|
|
24614
|
-
reject(new CliError("not_installed", `npm uninstall exited with ${code ?? "a signal"}`));
|
|
24615
|
-
}
|
|
24616
|
-
});
|
|
24617
|
-
});
|
|
24618
|
-
}
|
|
24619
24811
|
async function removeDesktop(yes, logger) {
|
|
24620
24812
|
if (process.platform !== "darwin") {
|
|
24621
24813
|
throw new CliError(
|
|
@@ -24640,7 +24832,7 @@ async function removeDesktop(yes, logger) {
|
|
|
24640
24832
|
logger.info("nothing removed");
|
|
24641
24833
|
return 0;
|
|
24642
24834
|
}
|
|
24643
|
-
await
|
|
24835
|
+
await rm3(installed.appPath, { recursive: true, force: true });
|
|
24644
24836
|
await pruneDesktopUpdateCache([]);
|
|
24645
24837
|
logger.info(`deleted ${installed.appPath} (recent hosts kept in ~/.puddle/recent-hosts.json)`);
|
|
24646
24838
|
return 0;
|
|
@@ -24762,9 +24954,9 @@ function timestampedLogger() {
|
|
|
24762
24954
|
|
|
24763
24955
|
// src/cli/run.ts
|
|
24764
24956
|
function assetsDir() {
|
|
24765
|
-
const here =
|
|
24766
|
-
for (const candidate of [
|
|
24767
|
-
if (
|
|
24957
|
+
const here = dirname12(fileURLToPath5(import.meta.url));
|
|
24958
|
+
for (const candidate of [join17(here, "public"), join17(here, "..", "..", "dist", "public")]) {
|
|
24959
|
+
if (existsSync7(join17(candidate, "index.html"))) return candidate;
|
|
24768
24960
|
}
|
|
24769
24961
|
throw new CliError(
|
|
24770
24962
|
"not_installed",
|
|
@@ -24798,7 +24990,7 @@ async function openTarget(host) {
|
|
|
24798
24990
|
};
|
|
24799
24991
|
}
|
|
24800
24992
|
function runUntilInterrupted(stop, farewell) {
|
|
24801
|
-
return new Promise((
|
|
24993
|
+
return new Promise((resolve6) => {
|
|
24802
24994
|
let stopping = false;
|
|
24803
24995
|
process.on("SIGINT", () => {
|
|
24804
24996
|
if (stopping) process.exit(130);
|
|
@@ -24806,10 +24998,10 @@ function runUntilInterrupted(stop, farewell) {
|
|
|
24806
24998
|
process.stderr.write(`
|
|
24807
24999
|
${farewell}
|
|
24808
25000
|
`);
|
|
24809
|
-
void stop().then(
|
|
25001
|
+
void stop().then(resolve6);
|
|
24810
25002
|
});
|
|
24811
25003
|
process.on("SIGTERM", () => {
|
|
24812
|
-
void stop().then(
|
|
25004
|
+
void stop().then(resolve6);
|
|
24813
25005
|
});
|
|
24814
25006
|
});
|
|
24815
25007
|
}
|
|
@@ -25097,6 +25289,7 @@ async function runCockpit(command, target, terminal) {
|
|
|
25097
25289
|
const common = {
|
|
25098
25290
|
...command,
|
|
25099
25291
|
assetsDir: assetsDir(),
|
|
25292
|
+
confirmDaemonUpgrade,
|
|
25100
25293
|
logger,
|
|
25101
25294
|
onRefreshRequest,
|
|
25102
25295
|
refreshId: process.env.PUDDLE_REFRESH_ID
|