@puddle-code/cli 0.2.3 → 0.2.6
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 +2 -13
- package/README.md +33 -23
- package/dist/host-control.mjs +40 -2
- package/dist/index.js +541 -313
- package/dist/install-cli.sh +167 -0
- package/dist/install.sh +7 -10
- package/dist/public/assets/{App-JZgRWo4t.js → App-CzuHShdO.js} +2 -2
- package/dist/public/assets/{Dashboard-DNWLZwxT.js → Dashboard-B5qWtmwb.js} +1 -1
- package/dist/public/assets/{EditorTabClose-CdpMpKYq.js → EditorTabClose-uxuNJdgx.js} +1 -1
- package/dist/public/assets/FindWidget-lUpiYZJG.js +1 -0
- package/dist/public/assets/{LazyTerminal-DVeI2Lkl.js → LazyTerminal-BYbPpGEI.js} +2 -2
- package/dist/public/assets/{ModelRefcount-ALfiNB9K.js → ModelRefcount-N0Xh_Y-_.js} +1 -1
- package/dist/public/assets/{NewProjectDialog-RHBHLIQe.js → NewProjectDialog-D3bNK3u_.js} +1 -1
- package/dist/public/assets/PaneEditorBody-DrSiMzrp.js +553 -0
- package/dist/public/assets/{PdfViewer-DZrh-S3Z.js → PdfViewer-BhqrWfmb.js} +2 -2
- package/dist/public/assets/{SettingsDialog-D-yMKqY9.js → SettingsDialog-CamnF91I.js} +2 -2
- package/dist/public/assets/Terminal-Ds17sCF_.js +60 -0
- package/dist/public/assets/Workspace-B6mgQyIM.css +1 -0
- package/dist/public/assets/{Workspace-DhFU_Ltc.js → Workspace-BmtA0kYQ.js} +6 -6
- package/dist/public/assets/{abnfDiagram-VCTEODGH-Bb_QGl6J.js → abnfDiagram-VCTEODGH-B4ZtDUXi.js} +1 -1
- package/dist/public/assets/architecture-7GRP2DOG-D0iH6Lxl.js +1 -0
- package/dist/public/assets/{architectureDiagram-5GKGNRK7-CF6YHpo7.js → architectureDiagram-5GKGNRK7-Cyzuyzwl.js} +1 -1
- package/dist/public/assets/{blockDiagram-I7D4REHJ-DgUouS8v.js → blockDiagram-I7D4REHJ-BN00JnNl.js} +1 -1
- package/dist/public/assets/{buffer-store-DcARHjQ8.js → buffer-store--4yU4YqM.js} +2 -2
- package/dist/public/assets/{button-CyO-gAMJ.js → button-DehrlYw7.js} +1 -1
- package/dist/public/assets/{c4Diagram-7LVT6UL2-CFhvq4-y.js → c4Diagram-7LVT6UL2-DpUKodBy.js} +1 -1
- package/dist/public/assets/channel-DT2izo70.js +1 -0
- package/dist/public/assets/{chunk-2E4U76K2-CskNiADZ.js → chunk-2E4U76K2-BksaqXZU.js} +1 -1
- package/dist/public/assets/{chunk-4HAMMTFA-DY74ei7y.js → chunk-4HAMMTFA-D01k6w0A.js} +1 -1
- package/dist/public/assets/{chunk-75Z2AOVW-sHPItT4L.js → chunk-75Z2AOVW-DJJjslyX.js} +1 -1
- package/dist/public/assets/{chunk-CLGD4ZFX-BbZdl00J.js → chunk-CLGD4ZFX-CX1B9i6H.js} +1 -1
- package/dist/public/assets/{chunk-DU6HZSFF-BZvHUJoN.js → chunk-DU6HZSFF-Bx2ELCn-.js} +1 -1
- package/dist/public/assets/{chunk-F27PBJKO-C-FJqBSD.js → chunk-F27PBJKO-CCDhODx8.js} +1 -1
- package/dist/public/assets/{chunk-GMAD6QVW-DPkzF1Ic.js → chunk-GMAD6QVW-C1evXXZi.js} +1 -1
- package/dist/public/assets/{chunk-GVQU2GXP-Batc2xZS.js → chunk-GVQU2GXP-C1iphgLK.js} +1 -1
- package/dist/public/assets/{chunk-IMKFNOWR-CAMx72vu.js → chunk-IMKFNOWR-5wMdkmsQ.js} +1 -1
- package/dist/public/assets/{chunk-L3NEJ4N5-CuIGdGy-.js → chunk-L3NEJ4N5-B-aXfMpF.js} +1 -1
- package/dist/public/assets/{chunk-OSK3NFVY-uNp8Mp7C.js → chunk-OSK3NFVY-Bm2MaFOc.js} +1 -1
- package/dist/public/assets/{chunk-P2QGCYS3-CoRPhFtM.js → chunk-P2QGCYS3-H02JaAun.js} +1 -1
- package/dist/public/assets/{chunk-POPQ4Y6H-DMOCBqAw.js → chunk-POPQ4Y6H-Dmm46Beg.js} +1 -1
- package/dist/public/assets/{chunk-PWAF6VOD-DOQkLdyp.js → chunk-PWAF6VOD-CRt8Ete5.js} +1 -1
- package/dist/public/assets/{chunk-SHT3W25Y-BJJe_uau.js → chunk-SHT3W25Y-w4aNK54y.js} +1 -1
- package/dist/public/assets/{chunk-SVP7TREG-D5B1jrjj.js → chunk-SVP7TREG-Cl3QpMDs.js} +1 -1
- package/dist/public/assets/{chunk-TICWLB2K-C9NafHGC.js → chunk-TICWLB2K-BpBbot9h.js} +1 -1
- package/dist/public/assets/{chunk-TLUHSLCS-Ctl_RxEM.js → chunk-TLUHSLCS-BOnIB6P6.js} +2 -2
- package/dist/public/assets/classDiagram-ZZMXUADV-BSshJUxJ.js +1 -0
- package/dist/public/assets/classDiagram-v2-VYDZK3BY-BSshJUxJ.js +1 -0
- package/dist/public/assets/{context-menu-BuiHQTu6.js → context-menu-BT88Vgia.js} +1 -1
- package/dist/public/assets/{cssMode-DozYXR6g.js → cssMode-gXT_K4po.js} +1 -1
- package/dist/public/assets/{cynefin-OW5HDTMX-gj0lEe4i.js → cynefin-OW5HDTMX-CJakxaH8.js} +1 -1
- package/dist/public/assets/{cynefinDiagram-5FMLGOSQ-lYCffbhY.js → cynefinDiagram-5FMLGOSQ-BJ_fVnC9.js} +1 -1
- package/dist/public/assets/{dagre-GXQ25YYZ-DyYVAAw7.js → dagre-GXQ25YYZ-CjflcbuS.js} +1 -1
- package/dist/public/assets/{diagram-S7CK7UJ4-Dop5OEsZ.js → diagram-S7CK7UJ4-g8pKIWrY.js} +1 -1
- package/dist/public/assets/{diagram-UQ7AKVKN-B5qxPK0m.js → diagram-UQ7AKVKN-Dm5Pg4rY.js} +1 -1
- package/dist/public/assets/{diagram-VSXAHHWV-zvsxaxBC.js → diagram-VSXAHHWV-DV5CVAXg.js} +1 -1
- package/dist/public/assets/{diagram-VX7I27RA-S7nRhoRV.js → diagram-VX7I27RA-Be9qCk-h.js} +1 -1
- package/dist/public/assets/{diagram-Z3DM3KII-C_jkP2wb.js → diagram-Z3DM3KII-C3-xDrxs.js} +1 -1
- package/dist/public/assets/{dialog-D6I1fiX4.js → dialog-CAMTxigV.js} +1 -1
- package/dist/public/assets/{disclosure-Dn2Pws0i.js → disclosure-DvM7-z38.js} +1 -1
- package/dist/public/assets/dist-3tmIo7Ja.js +1 -0
- package/dist/public/assets/{dist-Dt0LZb5E.js → dist-sLs_LTiG.js} +1 -1
- package/dist/public/assets/{ebnfDiagram-PWID7BFC-B1OmWQdM.js → ebnfDiagram-PWID7BFC-Bq92n7k-.js} +1 -1
- package/dist/public/assets/{erDiagram-RLTQ6QDP-DJKky3Wx.js → erDiagram-RLTQ6QDP-C5I2bKnS.js} +1 -1
- package/dist/public/assets/eventmodeling-NTZA5JFV-CWxfOZuS.js +1 -0
- package/dist/public/assets/flowDiagram-HODETNUW-ddOOBXlS.js +1 -0
- package/dist/public/assets/{ganttDiagram-EL5Y4UJY-qEYH72Jx.js → ganttDiagram-EL5Y4UJY-qUaBgbbK.js} +1 -1
- package/dist/public/assets/{gitGraph-4MIJSDKK-DlVqGPr_.js → gitGraph-4MIJSDKK-CUfoq3Sa.js} +1 -1
- package/dist/public/assets/{gitGraphDiagram-WWUBYQGX-DjfCMlWr.js → gitGraphDiagram-WWUBYQGX-kj34Cchu.js} +1 -1
- package/dist/public/assets/{hotkeys-Bx0QQoOi.js → hotkeys-DbIEec2f.js} +1 -1
- package/dist/public/assets/{htmlMode-DlKAkkNW.js → htmlMode-C_uC6TDH.js} +1 -1
- package/dist/public/assets/index-BLx1AR0-.js +82 -0
- package/dist/public/assets/index-uKreLbru.css +2 -0
- package/dist/public/assets/{info-A6RAGUB7-CH2cc1Sk.js → info-A6RAGUB7-C938EzS-.js} +1 -1
- package/dist/public/assets/{infoDiagram-27XIBGKW-QLQ5mJz6.js → infoDiagram-27XIBGKW-BoTxIjdn.js} +1 -1
- package/dist/public/assets/input-DXYRmAFr.js +1 -0
- package/dist/public/assets/{ishikawaDiagram-5VMMS53U-Dgd8STMm.js → ishikawaDiagram-5VMMS53U-j98L6THV.js} +1 -1
- package/dist/public/assets/{journeyDiagram-3NMN7TZE-BaIBwMwN.js → journeyDiagram-3NMN7TZE-DI-XCC2F.js} +1 -1
- package/dist/public/assets/{jsonMode-B-xhmxpv.js → jsonMode-BQ3590Ds.js} +1 -1
- package/dist/public/assets/{kanban-definition-UXKFOSKX-DUwkSFm5.js → kanban-definition-UXKFOSKX-BCTvFJTJ.js} +1 -1
- package/dist/public/assets/{line-DBd--km9.js → line-hE2Okp1W.js} +1 -1
- package/dist/public/assets/{media-refresh-store-C2PF6Nl9.js → media-refresh-store-CxqhHl8x.js} +1 -1
- package/dist/public/assets/{mermaid-parser.core-CH9r0Plq.js → mermaid-parser.core-n0Wb6T7Q.js} +3 -3
- package/dist/public/assets/{mermaid.core-Dx4uf1Og.js → mermaid.core-Cb0bw4D8.js} +3 -3
- package/dist/public/assets/{mindmap-definition-YA3MSWOX-DFlzr0Rk.js → mindmap-definition-YA3MSWOX-92LM1OuK.js} +1 -1
- package/dist/public/assets/{notification-permission-BJ5Ee1QS.js → notification-permission-CG2r3Oi8.js} +1 -1
- package/dist/public/assets/{packet-AYTQ26CC-BoGgl5A0.js → packet-AYTQ26CC-_HBkwHYp.js} +1 -1
- package/dist/public/assets/{pdf-CIIeLqAE.js → pdf-DABk-fGs.js} +1 -1
- package/dist/public/assets/{pegDiagram-XKGWAZYB-dBubzdqe.js → pegDiagram-XKGWAZYB-B-fG_WHS.js} +1 -1
- package/dist/public/assets/{pie-WAS4IAKB-C8Ujmmnq.js → pie-WAS4IAKB-ByxJto7v.js} +1 -1
- package/dist/public/assets/{pieDiagram-E7YTZNPT-BarO3dDM.js → pieDiagram-E7YTZNPT-Ci8ZSq4L.js} +1 -1
- package/dist/public/assets/{project-abbrev-BlBz9tcM.js → project-abbrev-BU2DBpdq.js} +1 -1
- package/dist/public/assets/{quadrantDiagram-AXDQQJYC-Bq7mziYZ.js → quadrantDiagram-AXDQQJYC-B97Yoxpb.js} +1 -1
- package/dist/public/assets/{radar-RG4KPBEZ-CSOiAaDJ.js → radar-RG4KPBEZ-BT2qdAqW.js} +1 -1
- package/dist/public/assets/{railroad-74A4TZTK-B72WpbJE.js → railroad-74A4TZTK-Buny6l3B.js} +1 -1
- package/dist/public/assets/railroad-abnf-HS5TGJTU-C_aUf3vj.js +1 -0
- package/dist/public/assets/railroad-ebnf-LZEXJU2U-BGUVD_Qq.js +1 -0
- package/dist/public/assets/railroad-peg-WCYAUIDC-CGdTjBGv.js +1 -0
- package/dist/public/assets/{railroadDiagram-O6MQD6OU-CdpFpJTq.js → railroadDiagram-O6MQD6OU-BktpI6J7.js} +1 -1
- package/dist/public/assets/{requirementDiagram-BXWQKSXE-D5gAf9X3.js → requirementDiagram-BXWQKSXE-DYy_YKmd.js} +1 -1
- package/dist/public/assets/{sankeyDiagram-P5KCCOFB-CWGxwWQO.js → sankeyDiagram-P5KCCOFB-BeKH_sFV.js} +1 -1
- package/dist/public/assets/{sequenceDiagram-WJ2MYXX4-D4OU6YFv.js → sequenceDiagram-WJ2MYXX4-DD8okFGM.js} +1 -1
- package/dist/public/assets/{stateDiagram-D77RDMKH-DL34KHS6.js → stateDiagram-D77RDMKH-Bfk3E3Oa.js} +1 -1
- package/dist/public/assets/stateDiagram-v2-MP3YSRHH-DCg8-XDy.js +1 -0
- package/dist/public/assets/{swimlanes-42K2YHIH-ClnyergN.js → swimlanes-42K2YHIH-OVNrzhfn.js} +1 -1
- package/dist/public/assets/swimlanesDiagram-VR7AAH4N-DAqvbP32.js +8 -0
- package/dist/public/assets/{switch-JLAneJz1.js → switch-CN9NW-du.js} +1 -1
- package/dist/public/assets/{timeline-definition-24CTP7MA-Bouu1PMd.js → timeline-definition-24CTP7MA-C78a8EdU.js} +1 -1
- package/dist/public/assets/{treeView-Q6P3EWNA-OLbwx3G_.js → treeView-Q6P3EWNA-Cl5tCoga.js} +1 -1
- package/dist/public/assets/{treemap-WGGIJYW6-BWtOLy1c.js → treemap-WGGIJYW6-4YVcjSz9.js} +1 -1
- package/dist/public/assets/{tsMode-BChcsbnx.js → tsMode-CmGZ7MCT.js} +1 -1
- package/dist/public/assets/{use-ui-state-xRIsbDTY.js → use-ui-state-CzR7YpWb.js} +1 -1
- package/dist/public/assets/{vennDiagram-4TSXK5OY-B7Iv-Yti.js → vennDiagram-4TSXK5OY-Lih9MwBs.js} +1 -1
- package/dist/public/assets/{wardley-WFR3VGLG-bREi8xGw.js → wardley-WFR3VGLG-CcU7I6bt.js} +1 -1
- package/dist/public/assets/{wardleyDiagram-VM6X3IG4-BCZ7iNOW.js → wardleyDiagram-VM6X3IG4-DxHrszj8.js} +1 -1
- package/dist/public/assets/{ws-BzWidrUz.js → ws-C-kvwpeD.js} +1 -1
- package/dist/public/assets/{xychartDiagram-S5SC5T6Z-eLBpE0yW.js → xychartDiagram-S5SC5T6Z-DpLvvdIG.js} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/public/preview.html +18 -0
- package/package.json +1 -1
- package/dist/public/assets/PaneEditorBody-BnTDt5LG.js +0 -553
- package/dist/public/assets/Terminal-CoQpOpFQ.js +0 -60
- package/dist/public/assets/Workspace-klU-NMuZ.css +0 -1
- package/dist/public/assets/architecture-7GRP2DOG-DR2uP7PV.js +0 -1
- package/dist/public/assets/channel-CUVxklBM.js +0 -1
- package/dist/public/assets/classDiagram-ZZMXUADV-Cgl1n0Dz.js +0 -1
- package/dist/public/assets/classDiagram-v2-VYDZK3BY-Cgl1n0Dz.js +0 -1
- package/dist/public/assets/dist-tn5vjhJE.js +0 -1
- package/dist/public/assets/eventmodeling-NTZA5JFV-mkLPvEKM.js +0 -1
- package/dist/public/assets/flowDiagram-HODETNUW-O3VTLiVc.js +0 -1
- package/dist/public/assets/index-Ctw2P0rY.css +0 -2
- package/dist/public/assets/index-DWTNflld.js +0 -82
- package/dist/public/assets/input-CWrHR6Ba.js +0 -1
- package/dist/public/assets/railroad-abnf-HS5TGJTU-6aqbSR4_.js +0 -1
- package/dist/public/assets/railroad-ebnf-LZEXJU2U-DSe-edrj.js +0 -1
- package/dist/public/assets/railroad-peg-WCYAUIDC-CxQjyuGw.js +0 -1
- package/dist/public/assets/stateDiagram-v2-MP3YSRHH-C86Q0rld.js +0 -1
- package/dist/public/assets/swimlanesDiagram-VR7AAH4N-TVdovaZm.js +0 -8
- package/dist/public/assets/use-find-controls-DEA0SpBh.js +0 -1
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:
|
|
3928
|
+
var PROTOCOL_VERSION = { major: 21, minor: 2 };
|
|
3929
3929
|
|
|
3930
3930
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
3931
3931
|
var external_exports = {};
|
|
@@ -18459,6 +18459,8 @@ var profileId = external_exports.string().regex(/^[0-9a-f]{10}$/);
|
|
|
18459
18459
|
// ../shared/src/api/version.ts
|
|
18460
18460
|
var versionResponseSchema = external_exports.object({
|
|
18461
18461
|
version: external_exports.string(),
|
|
18462
|
+
/** Added by the host connector; absent on direct daemon responses and older connectors. */
|
|
18463
|
+
remote_image_uploads: external_exports.boolean().optional(),
|
|
18462
18464
|
protocol: external_exports.object({
|
|
18463
18465
|
major: external_exports.number().int().nonnegative(),
|
|
18464
18466
|
minor: external_exports.number().int().nonnegative()
|
|
@@ -19386,6 +19388,11 @@ var fileResponseSchema = external_exports.object({
|
|
|
19386
19388
|
size: external_exports.number().int().nonnegative(),
|
|
19387
19389
|
mtime_ms: external_exports.number()
|
|
19388
19390
|
});
|
|
19391
|
+
var MAX_PREVIEW_ASSET_BYTES = 8 * 1024 * 1024;
|
|
19392
|
+
var previewAssetResponseSchema = external_exports.object({
|
|
19393
|
+
mime: external_exports.string().min(1).max(128),
|
|
19394
|
+
data: external_exports.string().max(4 * Math.ceil(MAX_PREVIEW_ASSET_BYTES / 3))
|
|
19395
|
+
});
|
|
19389
19396
|
var putFileRequestSchema = external_exports.object({
|
|
19390
19397
|
content: external_exports.string(),
|
|
19391
19398
|
/** Optimistic-concurrency guard: reject the write if the file changed since this mtime. */
|
|
@@ -19914,6 +19921,36 @@ var browserAuthorityStateSchema = external_exports.object({
|
|
|
19914
19921
|
proxies: external_exports.array(proxyRecordSchema)
|
|
19915
19922
|
});
|
|
19916
19923
|
|
|
19924
|
+
// ../shared/src/remote/image-upload.ts
|
|
19925
|
+
var IMAGE_UPLOAD_POLICY = {
|
|
19926
|
+
imageBytes: 4 * 1024 * 1024,
|
|
19927
|
+
chunkBytes: 64 * 1024,
|
|
19928
|
+
idleMs: 6e4,
|
|
19929
|
+
lifetimeMs: 5 * 6e4
|
|
19930
|
+
};
|
|
19931
|
+
var imageUploadRequestSchema = external_exports.discriminatedUnion("action", [
|
|
19932
|
+
external_exports.object({
|
|
19933
|
+
action: external_exports.literal("start"),
|
|
19934
|
+
upload: external_exports.string().uuid(),
|
|
19935
|
+
mime: pasteImageMimeSchema,
|
|
19936
|
+
size: external_exports.number().int().positive().max(IMAGE_UPLOAD_POLICY.imageBytes)
|
|
19937
|
+
}).strict(),
|
|
19938
|
+
external_exports.object({
|
|
19939
|
+
action: external_exports.literal("chunk"),
|
|
19940
|
+
upload: external_exports.string().uuid(),
|
|
19941
|
+
offset: external_exports.number().int().nonnegative().max(IMAGE_UPLOAD_POLICY.imageBytes),
|
|
19942
|
+
data: external_exports.string().min(4).max(4 * Math.ceil(IMAGE_UPLOAD_POLICY.chunkBytes / 3)).regex(/^[A-Za-z0-9+/]+={0,2}$/).refine((data) => {
|
|
19943
|
+
const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
|
|
19944
|
+
return data.length % 4 === 0 && data.length / 4 * 3 - padding <= IMAGE_UPLOAD_POLICY.chunkBytes;
|
|
19945
|
+
}, "Image chunk is too large or malformed")
|
|
19946
|
+
}).strict(),
|
|
19947
|
+
external_exports.object({ action: external_exports.literal("finish"), upload: external_exports.string().uuid() }).strict(),
|
|
19948
|
+
external_exports.object({ action: external_exports.literal("cancel"), upload: external_exports.string().uuid() }).strict()
|
|
19949
|
+
]);
|
|
19950
|
+
var imageUploadProgressSchema = external_exports.object({
|
|
19951
|
+
received: external_exports.number().int().nonnegative().max(IMAGE_UPLOAD_POLICY.imageBytes)
|
|
19952
|
+
});
|
|
19953
|
+
|
|
19917
19954
|
// ../shared/src/remote/service.ts
|
|
19918
19955
|
var remoteHostSchema = external_exports.object({
|
|
19919
19956
|
id: remoteIdSchema,
|
|
@@ -19938,6 +19975,7 @@ var remoteLoginStateSchema = external_exports.object({
|
|
|
19938
19975
|
var registerHostRequestSchema = external_exports.object({ label: remoteLabelSchema }).strict();
|
|
19939
19976
|
var registrationResponseSchema = external_exports.object({ host: remoteIdSchema, code: remoteSecretSchema, expires: external_exports.number() }).strict();
|
|
19940
19977
|
var redeemRegistrationSchema = external_exports.object({ code: remoteSecretSchema }).strict();
|
|
19978
|
+
var unregisterHostRequestSchema = external_exports.object({ credential: remoteSecretSchema }).strict();
|
|
19941
19979
|
var desktopRegistrationSchema = external_exports.object({
|
|
19942
19980
|
challenge: remoteSecretSchema,
|
|
19943
19981
|
label: remoteLabelSchema,
|
|
@@ -20001,7 +20039,7 @@ var LocalTransport = class {
|
|
|
20001
20039
|
kind = "local";
|
|
20002
20040
|
label = "this machine";
|
|
20003
20041
|
exec(command, opts = {}) {
|
|
20004
|
-
return new Promise((
|
|
20042
|
+
return new Promise((resolve6) => {
|
|
20005
20043
|
const child = spawn("sh", ["-c", command], { stdio: ["pipe", "pipe", "pipe"] });
|
|
20006
20044
|
let stdout = "";
|
|
20007
20045
|
let stderr = "";
|
|
@@ -20018,11 +20056,11 @@ var LocalTransport = class {
|
|
|
20018
20056
|
child.stderr.on("data", (chunk) => stderr += chunk.toString());
|
|
20019
20057
|
child.on("error", (err) => {
|
|
20020
20058
|
if (timer) clearTimeout(timer);
|
|
20021
|
-
|
|
20059
|
+
resolve6({ code: -1, stdout, stderr: stderr + String(err) });
|
|
20022
20060
|
});
|
|
20023
20061
|
child.on("close", (code) => {
|
|
20024
20062
|
if (timer) clearTimeout(timer);
|
|
20025
|
-
|
|
20063
|
+
resolve6({ code: code ?? -1, stdout, stderr });
|
|
20026
20064
|
});
|
|
20027
20065
|
child.stdin.on("error", () => {
|
|
20028
20066
|
});
|
|
@@ -20116,7 +20154,7 @@ var SshTransport = class {
|
|
|
20116
20154
|
* live control socket behind.
|
|
20117
20155
|
*/
|
|
20118
20156
|
open() {
|
|
20119
|
-
return new Promise((
|
|
20157
|
+
return new Promise((resolve6, reject) => {
|
|
20120
20158
|
const child = spawn2(this.ssh, this.args(this.host, "true"), {
|
|
20121
20159
|
// A GUI has no useful inherited terminal; OpenSSH calls its askpass
|
|
20122
20160
|
// helper instead. The terminal CLI keeps byte-for-byte inheritance.
|
|
@@ -20128,7 +20166,7 @@ var SshTransport = class {
|
|
|
20128
20166
|
(err) => reject(new CliError("ssh_unreachable", `could not run ${this.ssh}: ${err.message}`))
|
|
20129
20167
|
);
|
|
20130
20168
|
child.on("close", (code) => {
|
|
20131
|
-
if (code === 0)
|
|
20169
|
+
if (code === 0) resolve6();
|
|
20132
20170
|
else {
|
|
20133
20171
|
reject(
|
|
20134
20172
|
new CliError(
|
|
@@ -20144,13 +20182,13 @@ var SshTransport = class {
|
|
|
20144
20182
|
/** Whether the master connection is still alive (-O check). */
|
|
20145
20183
|
isAlive() {
|
|
20146
20184
|
if (!this.hasControlMaster) return Promise.resolve(true);
|
|
20147
|
-
return new Promise((
|
|
20185
|
+
return new Promise((resolve6) => {
|
|
20148
20186
|
const child = spawn2(this.ssh, this.args("-O", "check", this.host), {
|
|
20149
20187
|
stdio: "ignore",
|
|
20150
20188
|
env: this.spawnEnv()
|
|
20151
20189
|
});
|
|
20152
|
-
child.on("error", () =>
|
|
20153
|
-
child.on("close", (code) =>
|
|
20190
|
+
child.on("error", () => resolve6(false));
|
|
20191
|
+
child.on("close", (code) => resolve6(code === 0));
|
|
20154
20192
|
});
|
|
20155
20193
|
}
|
|
20156
20194
|
/**
|
|
@@ -20163,18 +20201,18 @@ var SshTransport = class {
|
|
|
20163
20201
|
*/
|
|
20164
20202
|
cancelForward(localPort, remotePort) {
|
|
20165
20203
|
if (!this.hasControlMaster) return Promise.resolve();
|
|
20166
|
-
return new Promise((
|
|
20204
|
+
return new Promise((resolve6) => {
|
|
20167
20205
|
const spec = `${localPort}:127.0.0.1:${remotePort}`;
|
|
20168
20206
|
const child = spawn2(this.ssh, this.args("-O", "cancel", "-L", spec, this.host), {
|
|
20169
20207
|
stdio: "ignore",
|
|
20170
20208
|
env: this.spawnEnv()
|
|
20171
20209
|
});
|
|
20172
|
-
child.on("error", () =>
|
|
20173
|
-
child.on("close", () =>
|
|
20210
|
+
child.on("error", () => resolve6());
|
|
20211
|
+
child.on("close", () => resolve6());
|
|
20174
20212
|
});
|
|
20175
20213
|
}
|
|
20176
20214
|
exec(command, opts = {}) {
|
|
20177
|
-
return new Promise((
|
|
20215
|
+
return new Promise((resolve6) => {
|
|
20178
20216
|
const child = spawn2(this.ssh, this.args(this.host, "--", `sh -c ${shellQuote(command)}`), {
|
|
20179
20217
|
stdio: ["pipe", "pipe", "pipe"],
|
|
20180
20218
|
env: this.spawnEnv()
|
|
@@ -20194,11 +20232,11 @@ var SshTransport = class {
|
|
|
20194
20232
|
child.stderr.on("data", (chunk) => stderr += chunk.toString());
|
|
20195
20233
|
child.on("error", (err) => {
|
|
20196
20234
|
if (timer) clearTimeout(timer);
|
|
20197
|
-
|
|
20235
|
+
resolve6({ code: -1, stdout, stderr: stderr + String(err) });
|
|
20198
20236
|
});
|
|
20199
20237
|
child.on("close", (code) => {
|
|
20200
20238
|
if (timer) clearTimeout(timer);
|
|
20201
|
-
|
|
20239
|
+
resolve6({ code: code ?? -1, stdout, stderr });
|
|
20202
20240
|
});
|
|
20203
20241
|
child.stdin.on("error", () => {
|
|
20204
20242
|
});
|
|
@@ -20218,11 +20256,11 @@ var SshTransport = class {
|
|
|
20218
20256
|
});
|
|
20219
20257
|
let stdout = "";
|
|
20220
20258
|
let stderr = "";
|
|
20221
|
-
const result = new Promise((
|
|
20259
|
+
const result = new Promise((resolve6) => {
|
|
20222
20260
|
child.stdout.on("data", (chunk) => stdout += chunk.toString());
|
|
20223
20261
|
child.stderr.on("data", (chunk) => stderr += chunk.toString());
|
|
20224
|
-
child.on("error", (err) =>
|
|
20225
|
-
child.on("close", (code) =>
|
|
20262
|
+
child.on("error", (err) => resolve6({ code: -1, stdout, stderr: stderr + String(err) }));
|
|
20263
|
+
child.on("close", (code) => resolve6({ code: code ?? -1, stdout, stderr }));
|
|
20226
20264
|
});
|
|
20227
20265
|
return {
|
|
20228
20266
|
result,
|
|
@@ -20237,14 +20275,14 @@ var SshTransport = class {
|
|
|
20237
20275
|
}
|
|
20238
20276
|
async copyTo(localPath, destPath) {
|
|
20239
20277
|
await this.exec(`mkdir -p $(dirname ${destPath})`, { timeoutMs: 15e3 });
|
|
20240
|
-
await new Promise((
|
|
20278
|
+
await new Promise((resolve6, reject) => {
|
|
20241
20279
|
const child = spawn2(this.scp, [...this.controlArgs, localPath, `${this.host}:${destPath}`], {
|
|
20242
20280
|
stdio: ["ignore", "ignore", "inherit"],
|
|
20243
20281
|
env: this.spawnEnv()
|
|
20244
20282
|
});
|
|
20245
20283
|
child.on("error", reject);
|
|
20246
20284
|
child.on("close", (code) => {
|
|
20247
|
-
if (code === 0)
|
|
20285
|
+
if (code === 0) resolve6();
|
|
20248
20286
|
else reject(new CliError("ssh_unreachable", `scp to ${this.host} failed (exit ${code})`));
|
|
20249
20287
|
});
|
|
20250
20288
|
});
|
|
@@ -20345,7 +20383,7 @@ async function registrationCode() {
|
|
|
20345
20383
|
const readline = createInterface({ input: process.stdin, output: silent, terminal: true });
|
|
20346
20384
|
try {
|
|
20347
20385
|
return remoteSecretSchema.parse(
|
|
20348
|
-
await new Promise((
|
|
20386
|
+
await new Promise((resolve6) => readline.question("", resolve6))
|
|
20349
20387
|
);
|
|
20350
20388
|
} finally {
|
|
20351
20389
|
readline.close();
|
|
@@ -20469,6 +20507,8 @@ running in the background once ready, so the terminal may close (--foreground
|
|
|
20469
20507
|
to stay attached; Ctrl-C then stops the cockpit). refresh stops a target's
|
|
20470
20508
|
cockpit (even a wedged one) and runs the full launch flow again \u2014 tunnel,
|
|
20471
20509
|
daemon restart if needed \u2014 keeping the old UI port so open tabs survive.
|
|
20510
|
+
An older host protocol prompts before updating its daemon; --no-upgrade
|
|
20511
|
+
aborts before prompting. Without a terminal, upgrade the daemon explicitly first.
|
|
20472
20512
|
list shows running cockpits; kill stops one. Sessions keep running under a
|
|
20473
20513
|
persistent supervisor; an SSH-attached daemon keeps their state for the next launch.
|
|
20474
20514
|
|
|
@@ -20478,7 +20518,7 @@ AppImage placed where you say (default ~/puddle), then opened in the file
|
|
|
20478
20518
|
manager. Already installed and no @version \u2192 nothing changes.
|
|
20479
20519
|
upgrade moves components to the newest release (or the named @version),
|
|
20480
20520
|
installing any that are missing; with no component it covers everything
|
|
20481
|
-
installed on the target \u2014 the CLI last,
|
|
20521
|
+
installed on the target \u2014 the CLI last, using its installation channel. cli and desktop are
|
|
20482
20522
|
client-machine artefacts: user@host targets the daemon only. remove
|
|
20483
20523
|
uninstalls a component after confirmation; 'daemon' stops it (interrupting
|
|
20484
20524
|
its sessions), unregisters the supervisor, and keeps ~/.puddle's data \u2014
|
|
@@ -20665,7 +20705,7 @@ function parseArgs(argv) {
|
|
|
20665
20705
|
throw new CliError(
|
|
20666
20706
|
"bad_arguments",
|
|
20667
20707
|
"install takes one of: daemon | desktop (with an optional @version)",
|
|
20668
|
-
spec?.what === "cli" ? "the CLI
|
|
20708
|
+
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"
|
|
20669
20709
|
);
|
|
20670
20710
|
}
|
|
20671
20711
|
return {
|
|
@@ -20742,6 +20782,182 @@ function argvFor(command) {
|
|
|
20742
20782
|
return argv;
|
|
20743
20783
|
}
|
|
20744
20784
|
|
|
20785
|
+
// src/cli/daemon-upgrade-prompt.ts
|
|
20786
|
+
import { randomUUID } from "node:crypto";
|
|
20787
|
+
|
|
20788
|
+
// src/lib/handshake.ts
|
|
20789
|
+
init_types();
|
|
20790
|
+
var CLI_UPGRADE_COMMAND = "puddle upgrade cli";
|
|
20791
|
+
function decideHandshake(cli, daemon) {
|
|
20792
|
+
if (daemon.major === cli.major) return { kind: "proceed" };
|
|
20793
|
+
if (daemon.major < cli.major) return { kind: "upgrade-daemon" };
|
|
20794
|
+
return { kind: "refuse-cli-outdated", command: CLI_UPGRADE_COMMAND };
|
|
20795
|
+
}
|
|
20796
|
+
function daemonUpgradeMessage(request) {
|
|
20797
|
+
const { host, daemon, clientProtocol, liveSessions } = request;
|
|
20798
|
+
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.
|
|
20799
|
+
|
|
20800
|
+
Updating the host daemon will interrupt ${liveSessions} live session(s). Their saved state remains and they can resume after the update.`;
|
|
20801
|
+
}
|
|
20802
|
+
function verifyDaemonVersion(daemon) {
|
|
20803
|
+
const decision = decideHandshake(PROTOCOL_VERSION, daemon.protocol);
|
|
20804
|
+
if (decision.kind === "proceed") return daemon;
|
|
20805
|
+
if (decision.kind === "refuse-cli-outdated") {
|
|
20806
|
+
throw new CliError(
|
|
20807
|
+
"cli_outdated",
|
|
20808
|
+
`the daemon speaks protocol ${daemon.protocol.major}; this client speaks ${PROTOCOL_VERSION.major}`,
|
|
20809
|
+
`update the desktop app, or update the CLI: ${decision.command}`
|
|
20810
|
+
);
|
|
20811
|
+
}
|
|
20812
|
+
throw new CliError(
|
|
20813
|
+
"protocol_mismatch",
|
|
20814
|
+
`the daemon speaks protocol ${daemon.protocol.major}; this client speaks ${PROTOCOL_VERSION.major}`,
|
|
20815
|
+
"reconnect to update the host daemon"
|
|
20816
|
+
);
|
|
20817
|
+
}
|
|
20818
|
+
async function runHandshake(opts) {
|
|
20819
|
+
const { version: daemon, liveSessions } = opts.inspection;
|
|
20820
|
+
if (decideHandshake(PROTOCOL_VERSION, daemon.protocol).kind !== "upgrade-daemon") {
|
|
20821
|
+
return verifyDaemonVersion(daemon);
|
|
20822
|
+
}
|
|
20823
|
+
if (opts.noUpgrade) {
|
|
20824
|
+
throw new CliError(
|
|
20825
|
+
"upgrade_failed",
|
|
20826
|
+
`the daemon speaks protocol ${daemon.protocol.major} (client: ${PROTOCOL_VERSION.major}) and --no-upgrade is set`,
|
|
20827
|
+
`re-run without --no-upgrade to update it (${liveSessions} live session(s) would be interrupted and can be resumed)`
|
|
20828
|
+
);
|
|
20829
|
+
}
|
|
20830
|
+
const request = {
|
|
20831
|
+
host: opts.host,
|
|
20832
|
+
daemon,
|
|
20833
|
+
clientProtocol: PROTOCOL_VERSION,
|
|
20834
|
+
liveSessions
|
|
20835
|
+
};
|
|
20836
|
+
if (!await opts.confirmDaemonUpgrade?.(request)) {
|
|
20837
|
+
throw new CliError(
|
|
20838
|
+
"upgrade_failed",
|
|
20839
|
+
`the daemon update on ${opts.host} was not approved`,
|
|
20840
|
+
"launch again to approve the update, or run puddle upgrade daemon with the host as an optional argument"
|
|
20841
|
+
);
|
|
20842
|
+
}
|
|
20843
|
+
const latest = await opts.reinspect?.();
|
|
20844
|
+
if (latest && decideHandshake(PROTOCOL_VERSION, latest.version.protocol).kind !== "upgrade-daemon") {
|
|
20845
|
+
return verifyDaemonVersion(latest.version);
|
|
20846
|
+
}
|
|
20847
|
+
const logger = opts.logger ?? silentLogger;
|
|
20848
|
+
logger.info(
|
|
20849
|
+
`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`
|
|
20850
|
+
);
|
|
20851
|
+
try {
|
|
20852
|
+
return verifyDaemonVersion(await opts.upgradeDaemon());
|
|
20853
|
+
} catch (err) {
|
|
20854
|
+
if (err instanceof CliError && ["cli_outdated", "protocol_mismatch"].includes(err.code)) {
|
|
20855
|
+
throw new CliError(
|
|
20856
|
+
"upgrade_failed",
|
|
20857
|
+
`the daemon protocol is still incompatible after updating: ${err.message}`,
|
|
20858
|
+
"inspect the daemon logs: puddle logs"
|
|
20859
|
+
);
|
|
20860
|
+
}
|
|
20861
|
+
throw err;
|
|
20862
|
+
}
|
|
20863
|
+
}
|
|
20864
|
+
|
|
20865
|
+
// src/cli/prompt.ts
|
|
20866
|
+
init_types();
|
|
20867
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
20868
|
+
async function ask(question, def) {
|
|
20869
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return def;
|
|
20870
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
20871
|
+
try {
|
|
20872
|
+
const answer = await new Promise(
|
|
20873
|
+
(resolve6) => rl.question(`${question} [${def}] `, resolve6)
|
|
20874
|
+
);
|
|
20875
|
+
return answer.trim() === "" ? def : answer.trim();
|
|
20876
|
+
} finally {
|
|
20877
|
+
rl.close();
|
|
20878
|
+
}
|
|
20879
|
+
}
|
|
20880
|
+
async function confirm(question, opts = {}) {
|
|
20881
|
+
if (opts.skip === true) return true;
|
|
20882
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
20883
|
+
throw new CliError(
|
|
20884
|
+
"bad_arguments",
|
|
20885
|
+
"refusing to confirm without a terminal",
|
|
20886
|
+
"pass --yes to proceed non-interactively"
|
|
20887
|
+
);
|
|
20888
|
+
}
|
|
20889
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
20890
|
+
try {
|
|
20891
|
+
const answer = await new Promise(
|
|
20892
|
+
(resolve6) => rl.question(`${question} [y/N] `, resolve6)
|
|
20893
|
+
);
|
|
20894
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
20895
|
+
} finally {
|
|
20896
|
+
rl.close();
|
|
20897
|
+
}
|
|
20898
|
+
}
|
|
20899
|
+
|
|
20900
|
+
// src/cli/daemon-upgrade-prompt.ts
|
|
20901
|
+
var REQUEST = "puddle:confirm-daemon-upgrade";
|
|
20902
|
+
var RESPONSE = "puddle:daemon-upgrade-answer";
|
|
20903
|
+
async function askToUpgrade(message) {
|
|
20904
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
20905
|
+
return confirm(`${message}
|
|
20906
|
+
|
|
20907
|
+
Update and connect?`);
|
|
20908
|
+
}
|
|
20909
|
+
var confirmDaemonUpgrade = async (request) => {
|
|
20910
|
+
const message = daemonUpgradeMessage(request);
|
|
20911
|
+
if (!process.send || !process.connected) return askToUpgrade(message);
|
|
20912
|
+
return requestDaemonUpgradeApproval(message, process);
|
|
20913
|
+
};
|
|
20914
|
+
function requestDaemonUpgradeApproval(message, channel) {
|
|
20915
|
+
if (!channel.send || !channel.connected) return Promise.resolve(false);
|
|
20916
|
+
const id = randomUUID();
|
|
20917
|
+
return new Promise((resolve6) => {
|
|
20918
|
+
const finish = (approved) => {
|
|
20919
|
+
channel.off("message", answered);
|
|
20920
|
+
channel.off("disconnect", disconnected);
|
|
20921
|
+
resolve6(approved);
|
|
20922
|
+
};
|
|
20923
|
+
const answered = (value) => {
|
|
20924
|
+
if (!value || typeof value !== "object") return;
|
|
20925
|
+
const response = value;
|
|
20926
|
+
if (response.t === RESPONSE && response.id === id && typeof response.approved === "boolean")
|
|
20927
|
+
finish(response.approved);
|
|
20928
|
+
};
|
|
20929
|
+
const disconnected = () => finish(false);
|
|
20930
|
+
channel.on("message", answered);
|
|
20931
|
+
channel.once("disconnect", disconnected);
|
|
20932
|
+
channel.send({ t: REQUEST, id, message }, (err) => {
|
|
20933
|
+
if (err) finish(false);
|
|
20934
|
+
});
|
|
20935
|
+
});
|
|
20936
|
+
}
|
|
20937
|
+
function relayDaemonUpgradePrompt(child, ask2 = askToUpgrade) {
|
|
20938
|
+
let active = true;
|
|
20939
|
+
let pending = false;
|
|
20940
|
+
const receive = (value) => {
|
|
20941
|
+
if (pending || !value || typeof value !== "object") return;
|
|
20942
|
+
const request = value;
|
|
20943
|
+
if (request.t !== REQUEST || typeof request.id !== "string" || typeof request.message !== "string")
|
|
20944
|
+
return;
|
|
20945
|
+
pending = true;
|
|
20946
|
+
void ask2(request.message).catch(() => false).then((approved) => {
|
|
20947
|
+
pending = false;
|
|
20948
|
+
if (active && child.connected)
|
|
20949
|
+
child.send({ t: RESPONSE, id: request.id, approved }, () => {
|
|
20950
|
+
});
|
|
20951
|
+
});
|
|
20952
|
+
};
|
|
20953
|
+
child.on("message", receive);
|
|
20954
|
+
return () => {
|
|
20955
|
+
active = false;
|
|
20956
|
+
child.off("message", receive);
|
|
20957
|
+
if (child.connected) child.disconnect();
|
|
20958
|
+
};
|
|
20959
|
+
}
|
|
20960
|
+
|
|
20745
20961
|
// ../shared/src/node/security.ts
|
|
20746
20962
|
import { createHash, randomBytes } from "node:crypto";
|
|
20747
20963
|
import {
|
|
@@ -20829,14 +21045,14 @@ async function listenPrivate(path, onConnection) {
|
|
|
20829
21045
|
privatePath(path);
|
|
20830
21046
|
const before = lstatSync(path);
|
|
20831
21047
|
if (!before.isSocket()) throw new Error("Puddle control path is not a socket");
|
|
20832
|
-
const stale = await new Promise((
|
|
21048
|
+
const stale = await new Promise((resolve6, reject) => {
|
|
20833
21049
|
const probe = connect(path);
|
|
20834
21050
|
probe.once("connect", () => {
|
|
20835
21051
|
probe.destroy();
|
|
20836
|
-
|
|
21052
|
+
resolve6(false);
|
|
20837
21053
|
});
|
|
20838
21054
|
probe.once("error", (err) => {
|
|
20839
|
-
if (err.code === "ECONNREFUSED" || err.code === "ENOENT")
|
|
21055
|
+
if (err.code === "ECONNREFUSED" || err.code === "ENOENT") resolve6(true);
|
|
20840
21056
|
else reject(err);
|
|
20841
21057
|
});
|
|
20842
21058
|
});
|
|
@@ -20849,11 +21065,11 @@ async function listenPrivate(path, onConnection) {
|
|
|
20849
21065
|
}
|
|
20850
21066
|
}
|
|
20851
21067
|
const server = createServer(onConnection);
|
|
20852
|
-
await new Promise((
|
|
21068
|
+
await new Promise((resolve6, reject) => {
|
|
20853
21069
|
server.once("error", reject);
|
|
20854
21070
|
server.listen(path, () => {
|
|
20855
21071
|
server.off("error", reject);
|
|
20856
|
-
|
|
21072
|
+
resolve6();
|
|
20857
21073
|
});
|
|
20858
21074
|
});
|
|
20859
21075
|
if (process.platform !== "win32") chmodSync(path, 384);
|
|
@@ -20967,7 +21183,7 @@ var HostControlClient = class {
|
|
|
20967
21183
|
}
|
|
20968
21184
|
this.channel = channel;
|
|
20969
21185
|
this.until = performance.now() + CONNECTION_POLICY.leaseMs;
|
|
20970
|
-
await new Promise((
|
|
21186
|
+
await new Promise((resolve6, reject) => {
|
|
20971
21187
|
let pendingAt = null;
|
|
20972
21188
|
let initial = true;
|
|
20973
21189
|
const send = (message, sentAt = performance.now()) => {
|
|
@@ -20998,8 +21214,8 @@ var HostControlClient = class {
|
|
|
20998
21214
|
this.status = "protocol_mismatch";
|
|
20999
21215
|
reject(
|
|
21000
21216
|
this.error(
|
|
21001
|
-
"
|
|
21002
|
-
`Host protocol ${message.version.protocol.major}
|
|
21217
|
+
"protocol_mismatch",
|
|
21218
|
+
`Host protocol ${message.version.protocol.major} does not match client protocol ${PROTOCOL_VERSION.major}`
|
|
21003
21219
|
)
|
|
21004
21220
|
);
|
|
21005
21221
|
channel.destroy();
|
|
@@ -21020,7 +21236,7 @@ var HostControlClient = class {
|
|
|
21020
21236
|
if (this.channel !== channel) return;
|
|
21021
21237
|
this.status = "ready";
|
|
21022
21238
|
this.emit();
|
|
21023
|
-
|
|
21239
|
+
resolve6();
|
|
21024
21240
|
}).catch(() => channel.destroy());
|
|
21025
21241
|
}
|
|
21026
21242
|
},
|
|
@@ -21117,7 +21333,7 @@ async function inspectLegacyHost(home) {
|
|
|
21117
21333
|
};
|
|
21118
21334
|
try {
|
|
21119
21335
|
const version2 = versionResponseSchema.parse(await get("/api/version"));
|
|
21120
|
-
const sessions = sessionSchema.array().parse(await get("/api/sessions"));
|
|
21336
|
+
const sessions = sessionSchema.pick({ status: true }).array().parse(await get("/api/sessions"));
|
|
21121
21337
|
return hostInspectionSchema.parse({
|
|
21122
21338
|
t: "inspection",
|
|
21123
21339
|
port,
|
|
@@ -21141,7 +21357,7 @@ async function inspectHostLocally(home) {
|
|
|
21141
21357
|
}
|
|
21142
21358
|
const socket = connect2(path);
|
|
21143
21359
|
try {
|
|
21144
|
-
const grant = await new Promise((
|
|
21360
|
+
const grant = await new Promise((resolve6, reject) => {
|
|
21145
21361
|
socket.setTimeout(5e3, () => socket.destroy(new Error("Host inspection timed out")));
|
|
21146
21362
|
socket.once("error", reject);
|
|
21147
21363
|
socket.once("close", () => reject(new Error("Host control closed")));
|
|
@@ -21151,7 +21367,7 @@ async function inspectHostLocally(home) {
|
|
|
21151
21367
|
const message = controlResponseSchema.parse(value);
|
|
21152
21368
|
if (message.t !== "authority" || !message.token)
|
|
21153
21369
|
throw new Error("Host authority unavailable");
|
|
21154
|
-
|
|
21370
|
+
resolve6(message);
|
|
21155
21371
|
},
|
|
21156
21372
|
() => socket.destroy(new Error("Invalid host control response"))
|
|
21157
21373
|
);
|
|
@@ -21162,7 +21378,7 @@ async function inspectHostLocally(home) {
|
|
|
21162
21378
|
signal: AbortSignal.timeout(3e3)
|
|
21163
21379
|
});
|
|
21164
21380
|
if (!response.ok) throw new Error("Host inspection rejected");
|
|
21165
|
-
const sessions = sessionSchema.array().parse(await response.json());
|
|
21381
|
+
const sessions = sessionSchema.pick({ status: true }).array().parse(await response.json());
|
|
21166
21382
|
return hostInspectionSchema.parse({
|
|
21167
21383
|
t: "inspection",
|
|
21168
21384
|
port: grant.port,
|
|
@@ -21264,9 +21480,9 @@ async function acquireAuthority(transport) {
|
|
|
21264
21480
|
|
|
21265
21481
|
// src/cli/run.ts
|
|
21266
21482
|
init_attach();
|
|
21267
|
-
import { existsSync as
|
|
21268
|
-
import { dirname as
|
|
21269
|
-
import { fileURLToPath as
|
|
21483
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
21484
|
+
import { dirname as dirname12, join as join17 } from "node:path";
|
|
21485
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
21270
21486
|
|
|
21271
21487
|
// src/lib/browser.ts
|
|
21272
21488
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -21309,7 +21525,7 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
21309
21525
|
import { dirname as dirname4, join as join5 } from "node:path";
|
|
21310
21526
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
21311
21527
|
function cliVersion() {
|
|
21312
|
-
if (true) return "0.2.
|
|
21528
|
+
if (true) return "0.2.6";
|
|
21313
21529
|
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
21314
21530
|
for (const candidate of [
|
|
21315
21531
|
join5(here, "..", "..", "package.json"),
|
|
@@ -21552,7 +21768,7 @@ function swapScript(staged2, opts) {
|
|
|
21552
21768
|
}
|
|
21553
21769
|
async function findInstalledDesktopApp(opts = {}) {
|
|
21554
21770
|
if ((opts.platform ?? process.platform) !== "darwin") return null;
|
|
21555
|
-
const { readFile:
|
|
21771
|
+
const { readFile: readFile3 } = await import("node:fs/promises");
|
|
21556
21772
|
const { homedir: homedir3 } = await import("node:os");
|
|
21557
21773
|
const systemApplications = opts.systemApplicationsDir ?? "/Applications";
|
|
21558
21774
|
const home = opts.homeDir ?? homedir3();
|
|
@@ -21561,7 +21777,7 @@ async function findInstalledDesktopApp(opts = {}) {
|
|
|
21561
21777
|
join6(home, "Applications", "Puddle.app")
|
|
21562
21778
|
]) {
|
|
21563
21779
|
try {
|
|
21564
|
-
const plist = await
|
|
21780
|
+
const plist = await readFile3(join6(appPath, "Contents/Info.plist"), "utf8");
|
|
21565
21781
|
const match = /<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/.exec(
|
|
21566
21782
|
plist
|
|
21567
21783
|
);
|
|
@@ -21589,12 +21805,12 @@ async function desktopAppInstallPath(opts = {}) {
|
|
|
21589
21805
|
return join6(userApplications, "Puddle.app");
|
|
21590
21806
|
}
|
|
21591
21807
|
function isDesktopAppRunning(appPath) {
|
|
21592
|
-
return new Promise((
|
|
21808
|
+
return new Promise((resolve6) => {
|
|
21593
21809
|
const child = spawn4("/usr/bin/pgrep", ["-f", `${appPath}/Contents/MacOS/`], {
|
|
21594
21810
|
stdio: "ignore"
|
|
21595
21811
|
});
|
|
21596
|
-
child.on("error", () =>
|
|
21597
|
-
child.on("exit", (code) =>
|
|
21812
|
+
child.on("error", () => resolve6(false));
|
|
21813
|
+
child.on("exit", (code) => resolve6(code === 0));
|
|
21598
21814
|
});
|
|
21599
21815
|
}
|
|
21600
21816
|
function fileSha256(path) {
|
|
@@ -21602,12 +21818,12 @@ function fileSha256(path) {
|
|
|
21602
21818
|
return pipeline(createReadStream(path), hash2).then(() => hash2.digest("hex"));
|
|
21603
21819
|
}
|
|
21604
21820
|
function run(command, args) {
|
|
21605
|
-
return new Promise((
|
|
21821
|
+
return new Promise((resolve6, reject) => {
|
|
21606
21822
|
const child = spawn4(command, args, { stdio: "ignore" });
|
|
21607
21823
|
child.on("error", reject);
|
|
21608
21824
|
child.on(
|
|
21609
21825
|
"exit",
|
|
21610
|
-
(code) => code === 0 ?
|
|
21826
|
+
(code) => code === 0 ? resolve6() : reject(new CliError("not_installed", `${command} exited with ${code ?? "signal"}`))
|
|
21611
21827
|
);
|
|
21612
21828
|
});
|
|
21613
21829
|
}
|
|
@@ -21685,7 +21901,10 @@ var RELEASE_PROTOCOLS = {
|
|
|
21685
21901
|
"0.2.0": { major: 19, minor: 0 },
|
|
21686
21902
|
"0.2.1": { major: 19, minor: 0 },
|
|
21687
21903
|
"0.2.2": { major: 20, minor: 0 },
|
|
21688
|
-
"0.2.3": { major: 20, minor: 0 }
|
|
21904
|
+
"0.2.3": { major: 20, minor: 0 },
|
|
21905
|
+
"0.2.4": { major: 21, minor: 2 },
|
|
21906
|
+
"0.2.5": { major: 21, minor: 2 },
|
|
21907
|
+
"0.2.6": { major: 21, minor: 2 }
|
|
21689
21908
|
};
|
|
21690
21909
|
function validProtocol(value) {
|
|
21691
21910
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -21798,7 +22017,7 @@ function formatComponentVersions(components) {
|
|
|
21798
22017
|
import { join as join12 } from "node:path";
|
|
21799
22018
|
|
|
21800
22019
|
// src/lib/attached-daemon.ts
|
|
21801
|
-
import { randomUUID } from "node:crypto";
|
|
22020
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
21802
22021
|
|
|
21803
22022
|
// src/lib/bootstrap.ts
|
|
21804
22023
|
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "node:fs";
|
|
@@ -21888,22 +22107,22 @@ function readInstallScript() {
|
|
|
21888
22107
|
// src/lib/net.ts
|
|
21889
22108
|
import { connect as connect4, createServer as createServer2 } from "node:net";
|
|
21890
22109
|
function findFreePort() {
|
|
21891
|
-
return new Promise((
|
|
22110
|
+
return new Promise((resolve6, reject) => {
|
|
21892
22111
|
const srv = createServer2();
|
|
21893
22112
|
srv.once("error", reject);
|
|
21894
22113
|
srv.listen(0, "127.0.0.1", () => {
|
|
21895
22114
|
const address = srv.address();
|
|
21896
22115
|
const port = typeof address === "object" && address !== null ? address.port : 0;
|
|
21897
|
-
srv.close(() =>
|
|
22116
|
+
srv.close(() => resolve6(port));
|
|
21898
22117
|
});
|
|
21899
22118
|
});
|
|
21900
22119
|
}
|
|
21901
22120
|
function tcpListening(port, timeoutMs = 500) {
|
|
21902
|
-
return new Promise((
|
|
22121
|
+
return new Promise((resolve6) => {
|
|
21903
22122
|
const sock = connect4({ host: "127.0.0.1", port });
|
|
21904
22123
|
const done = (ok) => {
|
|
21905
22124
|
sock.destroy();
|
|
21906
|
-
|
|
22125
|
+
resolve6(ok);
|
|
21907
22126
|
};
|
|
21908
22127
|
sock.setTimeout(timeoutMs, () => done(false));
|
|
21909
22128
|
sock.once("connect", () => done(true));
|
|
@@ -21939,35 +22158,50 @@ init_types();
|
|
|
21939
22158
|
async function ensureDaemon(transport, opts) {
|
|
21940
22159
|
const logger = opts.logger ?? silentLogger;
|
|
21941
22160
|
const startTimeoutMs = opts.startTimeoutMs ?? (opts.attachedFallback === void 0 ? 2e4 : 5e3);
|
|
22161
|
+
let mismatch;
|
|
21942
22162
|
try {
|
|
21943
22163
|
const authority = await acquireAuthority(transport);
|
|
21944
22164
|
return { port: authority.port, authority, bootstrapped: false, daemonLifetime: "persistent" };
|
|
21945
22165
|
} catch (err) {
|
|
21946
|
-
if (err instanceof CliError && err.code === "
|
|
22166
|
+
if (err instanceof CliError && err.code === "protocol_mismatch") mismatch = err;
|
|
21947
22167
|
}
|
|
21948
22168
|
const inspection = await inspectHost(transport).catch((err) => {
|
|
21949
|
-
if (err instanceof CliError) throw err;
|
|
22169
|
+
if (err instanceof CliError || mismatch) throw err;
|
|
21950
22170
|
return null;
|
|
21951
22171
|
});
|
|
22172
|
+
if (!inspection && mismatch) throw mismatch;
|
|
22173
|
+
const install = async () => {
|
|
22174
|
+
await installDaemon(transport, opts);
|
|
22175
|
+
return waitAfterInstall(transport, opts, logger, true, startTimeoutMs);
|
|
22176
|
+
};
|
|
21952
22177
|
if (inspection) {
|
|
21953
|
-
|
|
21954
|
-
|
|
21955
|
-
|
|
21956
|
-
|
|
21957
|
-
|
|
21958
|
-
|
|
21959
|
-
|
|
21960
|
-
|
|
21961
|
-
|
|
22178
|
+
let replacement;
|
|
22179
|
+
try {
|
|
22180
|
+
await runHandshake({
|
|
22181
|
+
host: transport.label,
|
|
22182
|
+
inspection,
|
|
22183
|
+
noUpgrade: opts.noUpgrade,
|
|
22184
|
+
confirmDaemonUpgrade: opts.confirmDaemonUpgrade,
|
|
22185
|
+
reinspect: () => inspectHost(transport),
|
|
22186
|
+
logger,
|
|
22187
|
+
upgradeDaemon: async () => {
|
|
22188
|
+
replacement = await install();
|
|
22189
|
+
return replacement.authority.version;
|
|
22190
|
+
}
|
|
22191
|
+
});
|
|
22192
|
+
if (replacement) return replacement;
|
|
22193
|
+
const authority = await acquireAuthority(transport);
|
|
22194
|
+
return { port: authority.port, authority, bootstrapped: false, daemonLifetime: "persistent" };
|
|
22195
|
+
} catch (err) {
|
|
22196
|
+
replacement?.authority.close();
|
|
22197
|
+
await replacement?.lease?.stop();
|
|
22198
|
+
throw err;
|
|
21962
22199
|
}
|
|
21963
|
-
|
|
21964
|
-
|
|
21965
|
-
);
|
|
21966
|
-
} else if (await installedVersion(transport) !== null) {
|
|
22200
|
+
}
|
|
22201
|
+
if (await installedVersion(transport) !== null) {
|
|
21967
22202
|
logger.info(`restarting puddled on ${transport.label}`);
|
|
21968
22203
|
}
|
|
21969
|
-
|
|
21970
|
-
return waitAfterInstall(transport, opts, logger, true, startTimeoutMs);
|
|
22204
|
+
return install();
|
|
21971
22205
|
}
|
|
21972
22206
|
async function waitAfterInstall(transport, opts, logger, bootstrapped, timeoutMs) {
|
|
21973
22207
|
try {
|
|
@@ -22003,7 +22237,7 @@ async function waitForStartedDaemon(transport, timeoutMs, signal) {
|
|
|
22003
22237
|
}
|
|
22004
22238
|
return { port: authority.port, authority };
|
|
22005
22239
|
} catch (err) {
|
|
22006
|
-
if (err instanceof CliError && err.code === "
|
|
22240
|
+
if (err instanceof CliError && err.code === "protocol_mismatch") throw err;
|
|
22007
22241
|
}
|
|
22008
22242
|
await sleep(500);
|
|
22009
22243
|
}
|
|
@@ -22016,23 +22250,6 @@ function startTimeout(transport) {
|
|
|
22016
22250
|
transport.kind === "ssh" ? `inspect it with: puddle logs ${transport.label}` : "inspect it with: puddle logs"
|
|
22017
22251
|
);
|
|
22018
22252
|
}
|
|
22019
|
-
function makeUpgrader(transport, client, opts, lease) {
|
|
22020
|
-
return async () => {
|
|
22021
|
-
await installDaemon(transport, opts);
|
|
22022
|
-
await lease?.ensureRunning();
|
|
22023
|
-
const fresh = await waitForStartedDaemon(transport, 3e4);
|
|
22024
|
-
client.setAuthority(fresh.authority);
|
|
22025
|
-
const deadline = Date.now() + 3e4;
|
|
22026
|
-
while (Date.now() < deadline) {
|
|
22027
|
-
if (await client.responds()) return;
|
|
22028
|
-
await sleep(500);
|
|
22029
|
-
}
|
|
22030
|
-
throw new CliError(
|
|
22031
|
-
"daemon_start_timeout",
|
|
22032
|
-
`puddled did not come back after updating on ${transport.label}`
|
|
22033
|
-
);
|
|
22034
|
-
};
|
|
22035
|
-
}
|
|
22036
22253
|
|
|
22037
22254
|
// src/lib/attached-daemon.ts
|
|
22038
22255
|
init_types();
|
|
@@ -22095,7 +22312,7 @@ var AttachedDaemon = class {
|
|
|
22095
22312
|
}
|
|
22096
22313
|
async launchAndWait(preferredPort) {
|
|
22097
22314
|
const portFlag = preferredPort === void 0 ? "" : ` --port ${preferredPort}`;
|
|
22098
|
-
const leasePidPath = `${hostPaths.home}/attached-${
|
|
22315
|
+
const leasePidPath = `${hostPaths.home}/attached-${randomUUID2()}.pid`;
|
|
22099
22316
|
this.leasePidPath = leasePidPath;
|
|
22100
22317
|
const command = this.ssh.runAttached(
|
|
22101
22318
|
`printf '%s\\n' "$$" > ${leasePidPath}; exec ${hostPaths.current}/puddled${portFlag} >> ${hostPaths.logs}/puddled.out.log 2>&1`
|
|
@@ -22191,10 +22408,6 @@ var DaemonClient = class {
|
|
|
22191
22408
|
this.port = port;
|
|
22192
22409
|
this.authority = authority;
|
|
22193
22410
|
}
|
|
22194
|
-
setAuthority(authority) {
|
|
22195
|
-
this.authority.close();
|
|
22196
|
-
this.authority = authority;
|
|
22197
|
-
}
|
|
22198
22411
|
setPort(port) {
|
|
22199
22412
|
this.port = port;
|
|
22200
22413
|
}
|
|
@@ -22236,21 +22449,6 @@ var DaemonClient = class {
|
|
|
22236
22449
|
async liveSessions() {
|
|
22237
22450
|
return (await this.sessions()).filter((s) => LIVE.has(s.status));
|
|
22238
22451
|
}
|
|
22239
|
-
async liveSessionCount() {
|
|
22240
|
-
return (await this.sessions()).filter((s) => LIVE.has(s.status)).length;
|
|
22241
|
-
}
|
|
22242
|
-
/** Readiness requires an authenticated response; rejection never counts as ready. */
|
|
22243
|
-
async responds() {
|
|
22244
|
-
try {
|
|
22245
|
-
const res = await fetch(`http://127.0.0.1:${this.port}/api/version`, {
|
|
22246
|
-
headers: { authorization: `Bearer ${this.authority.credential()}` },
|
|
22247
|
-
signal: AbortSignal.timeout(2e3)
|
|
22248
|
-
});
|
|
22249
|
-
return res.ok;
|
|
22250
|
-
} catch {
|
|
22251
|
-
return false;
|
|
22252
|
-
}
|
|
22253
|
-
}
|
|
22254
22452
|
};
|
|
22255
22453
|
async function readDaemonPort(transport) {
|
|
22256
22454
|
const runtime = await transport.readFile(hostPaths.runtime);
|
|
@@ -22272,51 +22470,8 @@ async function readDaemonPort(transport) {
|
|
|
22272
22470
|
return 7434;
|
|
22273
22471
|
}
|
|
22274
22472
|
|
|
22275
|
-
// src/lib/handshake.ts
|
|
22276
|
-
init_types();
|
|
22277
|
-
var CLI_UPGRADE_COMMAND = "npm install -g @puddle-code/cli";
|
|
22278
|
-
function decideHandshake(cli, daemon) {
|
|
22279
|
-
if (daemon.major === cli.major) return { kind: "proceed" };
|
|
22280
|
-
if (daemon.major < cli.major) return { kind: "upgrade-daemon" };
|
|
22281
|
-
return { kind: "refuse-cli-outdated", command: CLI_UPGRADE_COMMAND };
|
|
22282
|
-
}
|
|
22283
|
-
async function runHandshake(opts) {
|
|
22284
|
-
const logger = opts.logger ?? silentLogger;
|
|
22285
|
-
const daemon = await opts.client.version();
|
|
22286
|
-
const decision = decideHandshake(PROTOCOL_VERSION, daemon.protocol);
|
|
22287
|
-
if (decision.kind === "proceed") return daemon;
|
|
22288
|
-
if (decision.kind === "refuse-cli-outdated") {
|
|
22289
|
-
throw new CliError(
|
|
22290
|
-
"cli_outdated",
|
|
22291
|
-
`the daemon speaks protocol ${daemon.protocol.major}; this CLI speaks ${PROTOCOL_VERSION.major}`,
|
|
22292
|
-
`update the CLI: ${decision.command}`
|
|
22293
|
-
);
|
|
22294
|
-
}
|
|
22295
|
-
const liveSessions = await opts.client.liveSessionCount().catch(() => 0);
|
|
22296
|
-
if (opts.noUpgrade) {
|
|
22297
|
-
throw new CliError(
|
|
22298
|
-
"upgrade_failed",
|
|
22299
|
-
`the daemon speaks protocol ${daemon.protocol.major} (CLI: ${PROTOCOL_VERSION.major}) and --no-upgrade is set`,
|
|
22300
|
-
`re-run without --no-upgrade to update it (${liveSessions} live session(s) would be interrupted and can be resumed)`
|
|
22301
|
-
);
|
|
22302
|
-
}
|
|
22303
|
-
logger.info(
|
|
22304
|
-
`updating puddled (protocol ${daemon.protocol.major} \u2192 ${PROTOCOL_VERSION.major}) \u2014 ${liveSessions} live session(s) will be interrupted and can be resumed`
|
|
22305
|
-
);
|
|
22306
|
-
await opts.upgradeDaemon({ from: daemon.version, liveSessions });
|
|
22307
|
-
const after = await opts.client.version();
|
|
22308
|
-
if (after.protocol.major !== PROTOCOL_VERSION.major) {
|
|
22309
|
-
throw new CliError(
|
|
22310
|
-
"upgrade_failed",
|
|
22311
|
-
`the daemon still speaks protocol ${after.protocol.major} after updating`,
|
|
22312
|
-
"inspect the daemon logs: puddle logs"
|
|
22313
|
-
);
|
|
22314
|
-
}
|
|
22315
|
-
return after;
|
|
22316
|
-
}
|
|
22317
|
-
|
|
22318
22473
|
// src/lib/serve/ui-server.ts
|
|
22319
|
-
import { randomUUID as
|
|
22474
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
22320
22475
|
import { createServer as createServer3 } from "node:http";
|
|
22321
22476
|
|
|
22322
22477
|
// src/lib/auth/launcher.ts
|
|
@@ -22352,7 +22507,7 @@ async function startLauncher(home, identity, invite) {
|
|
|
22352
22507
|
path,
|
|
22353
22508
|
close: async () => {
|
|
22354
22509
|
for (const socket of sockets) socket.destroy();
|
|
22355
|
-
await new Promise((
|
|
22510
|
+
await new Promise((resolve6) => server.close(() => resolve6()));
|
|
22356
22511
|
}
|
|
22357
22512
|
};
|
|
22358
22513
|
}
|
|
@@ -22362,14 +22517,14 @@ async function requestInvitation(home, path) {
|
|
|
22362
22517
|
const stored = readPrivateJson(secretFile(home, path));
|
|
22363
22518
|
pipeSecret = stored.secret;
|
|
22364
22519
|
} else privatePath(path);
|
|
22365
|
-
return new Promise((
|
|
22520
|
+
return new Promise((resolve6, reject) => {
|
|
22366
22521
|
const socket = connect5(path);
|
|
22367
22522
|
socket.setTimeout(5e3, () => socket.destroy(new Error("Launcher did not answer")));
|
|
22368
22523
|
jsonLines(
|
|
22369
22524
|
socket,
|
|
22370
22525
|
(value) => {
|
|
22371
22526
|
const message = launcherResponseSchema.parse(value);
|
|
22372
|
-
|
|
22527
|
+
resolve6(message.url);
|
|
22373
22528
|
socket.end();
|
|
22374
22529
|
},
|
|
22375
22530
|
() => socket.destroy(new Error("Invalid launcher reply"))
|
|
@@ -22589,7 +22744,7 @@ function writeStore(file2, store) {
|
|
|
22589
22744
|
renameSync3(tmp, file2);
|
|
22590
22745
|
}
|
|
22591
22746
|
function readBody(req) {
|
|
22592
|
-
return new Promise((
|
|
22747
|
+
return new Promise((resolve6, reject) => {
|
|
22593
22748
|
let size = 0;
|
|
22594
22749
|
const chunks = [];
|
|
22595
22750
|
req.on("data", (chunk) => {
|
|
@@ -22601,7 +22756,7 @@ function readBody(req) {
|
|
|
22601
22756
|
}
|
|
22602
22757
|
chunks.push(chunk);
|
|
22603
22758
|
});
|
|
22604
|
-
req.on("end", () =>
|
|
22759
|
+
req.on("end", () => resolve6(Buffer.concat(chunks).toString("utf8")));
|
|
22605
22760
|
req.on("error", reject);
|
|
22606
22761
|
});
|
|
22607
22762
|
}
|
|
@@ -23188,7 +23343,7 @@ async function startUiServer(opts) {
|
|
|
23188
23343
|
const tracker = new ProxySocketTracker();
|
|
23189
23344
|
const serveStatic = createStaticHandler(opts.assetsDir);
|
|
23190
23345
|
const handleRemoteAccess = remoteAccessHandler(opts.remoteAccess);
|
|
23191
|
-
const nonce =
|
|
23346
|
+
const nonce = randomUUID3();
|
|
23192
23347
|
let origin = "";
|
|
23193
23348
|
let proxyOrigin = "";
|
|
23194
23349
|
let browsers;
|
|
@@ -23326,8 +23481,8 @@ async function startUiServer(opts) {
|
|
|
23326
23481
|
browsers.close();
|
|
23327
23482
|
bridge.close();
|
|
23328
23483
|
tracker.destroyAll();
|
|
23329
|
-
await new Promise((
|
|
23330
|
-
server.close(() =>
|
|
23484
|
+
await new Promise((resolve6) => {
|
|
23485
|
+
server.close(() => resolve6());
|
|
23331
23486
|
server.closeAllConnections();
|
|
23332
23487
|
});
|
|
23333
23488
|
}
|
|
@@ -23337,15 +23492,15 @@ async function listen(server, startPort, strict, avoidPort) {
|
|
|
23337
23492
|
for (let probe = 0; probe < MAX_PORT_PROBES; probe += 1) {
|
|
23338
23493
|
const port = startPort + probe;
|
|
23339
23494
|
if (!strict && port === avoidPort) continue;
|
|
23340
|
-
const ok = await new Promise((
|
|
23495
|
+
const ok = await new Promise((resolve6, rejectListen) => {
|
|
23341
23496
|
const onError = (err) => {
|
|
23342
23497
|
server.off("listening", onListening);
|
|
23343
|
-
if (err.code === "EADDRINUSE")
|
|
23498
|
+
if (err.code === "EADDRINUSE") resolve6(false);
|
|
23344
23499
|
else rejectListen(err);
|
|
23345
23500
|
};
|
|
23346
23501
|
const onListening = () => {
|
|
23347
23502
|
server.off("error", onError);
|
|
23348
|
-
|
|
23503
|
+
resolve6(true);
|
|
23349
23504
|
};
|
|
23350
23505
|
server.once("error", onError);
|
|
23351
23506
|
server.once("listening", onListening);
|
|
@@ -23626,7 +23781,12 @@ async function connectRemote(opts) {
|
|
|
23626
23781
|
let uiResource;
|
|
23627
23782
|
let endpointResource;
|
|
23628
23783
|
try {
|
|
23629
|
-
const bootstrap = {
|
|
23784
|
+
const bootstrap = {
|
|
23785
|
+
tarball: opts.tarball,
|
|
23786
|
+
logger,
|
|
23787
|
+
noUpgrade: opts.noUpgrade,
|
|
23788
|
+
confirmDaemonUpgrade: opts.confirmDaemonUpgrade
|
|
23789
|
+
};
|
|
23630
23790
|
const attachedDaemon = new AttachedDaemon(ssh, logger);
|
|
23631
23791
|
const endpoint = await ensureDaemon(ssh, {
|
|
23632
23792
|
...bootstrap,
|
|
@@ -23667,12 +23827,7 @@ async function connectRemote(opts) {
|
|
|
23667
23827
|
logger
|
|
23668
23828
|
});
|
|
23669
23829
|
oauthForwardResource = oauthForward;
|
|
23670
|
-
const daemon = await
|
|
23671
|
-
client,
|
|
23672
|
-
noUpgrade: opts.noUpgrade,
|
|
23673
|
-
upgradeDaemon: makeUpgrader(ssh, client, bootstrap, endpoint.lease),
|
|
23674
|
-
logger
|
|
23675
|
-
});
|
|
23830
|
+
const daemon = verifyDaemonVersion(await client.version());
|
|
23676
23831
|
const avoidPort = await readDaemonPort(new LocalTransport());
|
|
23677
23832
|
endpoint.authority.setVerifier(async () => {
|
|
23678
23833
|
const generation = endpoint.authority.generation;
|
|
@@ -23695,7 +23850,7 @@ async function connectRemote(opts) {
|
|
|
23695
23850
|
watchTunnel(replacement);
|
|
23696
23851
|
await previous?.close();
|
|
23697
23852
|
}
|
|
23698
|
-
await client.version();
|
|
23853
|
+
verifyDaemonVersion(await client.version());
|
|
23699
23854
|
});
|
|
23700
23855
|
const ui = await startUiServer({
|
|
23701
23856
|
authority: endpoint.authority,
|
|
@@ -23897,24 +24052,26 @@ init_types();
|
|
|
23897
24052
|
async function startLocal(opts) {
|
|
23898
24053
|
const logger = opts.logger ?? silentLogger;
|
|
23899
24054
|
const transport = new LocalTransport();
|
|
23900
|
-
const bootstrap = {
|
|
23901
|
-
|
|
24055
|
+
const bootstrap = {
|
|
24056
|
+
tarball: opts.tarball,
|
|
24057
|
+
logger,
|
|
24058
|
+
noUpgrade: opts.noUpgrade,
|
|
24059
|
+
confirmDaemonUpgrade: opts.confirmDaemonUpgrade
|
|
24060
|
+
};
|
|
24061
|
+
let endpoint;
|
|
23902
24062
|
try {
|
|
23903
|
-
|
|
23904
|
-
const
|
|
23905
|
-
|
|
23906
|
-
|
|
23907
|
-
upgradeDaemon: makeUpgrader(transport, client, bootstrap),
|
|
23908
|
-
logger
|
|
23909
|
-
});
|
|
24063
|
+
endpoint = await ensureDaemon(transport, bootstrap);
|
|
24064
|
+
const authority = endpoint.authority;
|
|
24065
|
+
const client = new DaemonClient(endpoint.port, authority);
|
|
24066
|
+
const daemon = verifyDaemonVersion(await client.version());
|
|
23910
24067
|
let currentUi = void 0;
|
|
23911
|
-
|
|
23912
|
-
client.setPort(
|
|
23913
|
-
await client.version();
|
|
23914
|
-
currentUi?.setTarget({ host: "127.0.0.1", port:
|
|
24068
|
+
authority.setVerifier(async () => {
|
|
24069
|
+
client.setPort(authority.port);
|
|
24070
|
+
verifyDaemonVersion(await client.version());
|
|
24071
|
+
currentUi?.setTarget({ host: "127.0.0.1", port: authority.port });
|
|
23915
24072
|
});
|
|
23916
24073
|
const ui = await startUiServer({
|
|
23917
|
-
authority
|
|
24074
|
+
authority,
|
|
23918
24075
|
identity: "local",
|
|
23919
24076
|
refreshId: opts.refreshId,
|
|
23920
24077
|
assetsDir: opts.assetsDir,
|
|
@@ -23943,12 +24100,12 @@ async function startLocal(opts) {
|
|
|
23943
24100
|
},
|
|
23944
24101
|
async stop() {
|
|
23945
24102
|
await ui.close();
|
|
23946
|
-
|
|
24103
|
+
authority.close();
|
|
23947
24104
|
transport.dispose();
|
|
23948
24105
|
}
|
|
23949
24106
|
};
|
|
23950
24107
|
} catch (err) {
|
|
23951
|
-
endpoint
|
|
24108
|
+
endpoint?.authority.close();
|
|
23952
24109
|
transport.dispose();
|
|
23953
24110
|
throw err;
|
|
23954
24111
|
}
|
|
@@ -23978,12 +24135,170 @@ async function statusReport(client) {
|
|
|
23978
24135
|
// src/cli/run.ts
|
|
23979
24136
|
init_types();
|
|
23980
24137
|
|
|
23981
|
-
// src/cli/manage.ts
|
|
24138
|
+
// src/cli/manage-cli.ts
|
|
24139
|
+
init_types();
|
|
23982
24140
|
import { spawn as spawn7 } from "node:child_process";
|
|
23983
|
-
import {
|
|
23984
|
-
|
|
24141
|
+
import { dirname as dirname10 } from "node:path";
|
|
24142
|
+
|
|
24143
|
+
// src/lib/standalone-cli.ts
|
|
24144
|
+
init_types();
|
|
24145
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
24146
|
+
import { existsSync as existsSync5, lstatSync as lstatSync2, readFileSync as readFileSync9, realpathSync as realpathSync2 } from "node:fs";
|
|
24147
|
+
import { mkdir as mkdir2, readFile as readFile2, rm as rm2, unlink } from "node:fs/promises";
|
|
24148
|
+
import { dirname as dirname9, isAbsolute, join as join15, resolve as resolve5 } from "node:path";
|
|
24149
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
24150
|
+
function standaloneInstallation(entryFile = fileURLToPath4(import.meta.url), execPath = process.execPath) {
|
|
24151
|
+
const release = resolve5(dirname9(entryFile), "..");
|
|
24152
|
+
if (!existsSync5(join15(release, "STANDALONE"))) return null;
|
|
24153
|
+
try {
|
|
24154
|
+
const root = resolve5(release, "..", "..");
|
|
24155
|
+
const record2 = JSON.parse(readFileSync9(join15(root, "installation.json"), "utf8"));
|
|
24156
|
+
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))
|
|
24157
|
+
throw new Error("Installation metadata does not match this CLI");
|
|
24158
|
+
return { root, release, launcher: record2.launcher };
|
|
24159
|
+
} catch {
|
|
24160
|
+
throw new CliError(
|
|
24161
|
+
"not_installed",
|
|
24162
|
+
"this standalone archive is not an installer-managed CLI",
|
|
24163
|
+
"run the public install.sh to install the CLI before using upgrade or remove"
|
|
24164
|
+
);
|
|
24165
|
+
}
|
|
24166
|
+
}
|
|
24167
|
+
function upgradeStandaloneCli(installation, version2, repo) {
|
|
24168
|
+
const script = readFileSync9(join15(installation.release, "cli/install-cli.sh"), "utf8");
|
|
24169
|
+
const args = [
|
|
24170
|
+
"-s",
|
|
24171
|
+
"--",
|
|
24172
|
+
"--prefix",
|
|
24173
|
+
installation.root,
|
|
24174
|
+
"--bin-dir",
|
|
24175
|
+
dirname9(installation.launcher)
|
|
24176
|
+
];
|
|
24177
|
+
if (version2 !== void 0) args.push("--version", version2);
|
|
24178
|
+
if (repo !== void 0) args.push("--repo", repo);
|
|
24179
|
+
return new Promise((resolve6, reject) => {
|
|
24180
|
+
const child = spawn6("sh", args, { stdio: ["pipe", "inherit", "inherit"] });
|
|
24181
|
+
child.on("error", reject);
|
|
24182
|
+
child.stdin.on("error", (error51) => {
|
|
24183
|
+
if (error51.code !== "EPIPE") reject(error51);
|
|
24184
|
+
});
|
|
24185
|
+
child.stdin.end(script);
|
|
24186
|
+
child.on("exit", (code) => {
|
|
24187
|
+
if (code === 0) resolve6(0);
|
|
24188
|
+
else reject(new CliError("not_installed", `CLI installer exited with ${code ?? "a signal"}`));
|
|
24189
|
+
});
|
|
24190
|
+
});
|
|
24191
|
+
}
|
|
24192
|
+
function standaloneLauncher(root) {
|
|
24193
|
+
const quoted = `'${join15(root, "current/puddle").replaceAll("'", "'\\''")}'`;
|
|
24194
|
+
return `#!/bin/sh
|
|
24195
|
+
# puddle standalone launcher
|
|
24196
|
+
exec ${quoted} "$@"
|
|
24197
|
+
`;
|
|
24198
|
+
}
|
|
24199
|
+
async function removeStandaloneCli(installation) {
|
|
24200
|
+
const lock = join15(installation.root, ".install-lock");
|
|
24201
|
+
try {
|
|
24202
|
+
await mkdir2(lock);
|
|
24203
|
+
} catch {
|
|
24204
|
+
throw new CliError("already_running", "another CLI installation operation is running");
|
|
24205
|
+
}
|
|
24206
|
+
try {
|
|
24207
|
+
if (lstatSync2(installation.launcher).isSymbolicLink() || await readFile2(installation.launcher, "utf8") !== standaloneLauncher(installation.root))
|
|
24208
|
+
throw new CliError("not_installed", "the CLI launcher was replaced; refusing removal");
|
|
24209
|
+
await unlink(installation.launcher);
|
|
24210
|
+
await rm2(installation.root, { recursive: true, force: true });
|
|
24211
|
+
} finally {
|
|
24212
|
+
await rm2(lock, { recursive: true, force: true });
|
|
24213
|
+
}
|
|
24214
|
+
}
|
|
24215
|
+
|
|
24216
|
+
// src/cli/manage-cli.ts
|
|
24217
|
+
function upgradeCli(version2, logger) {
|
|
24218
|
+
const standalone = standaloneInstallation();
|
|
24219
|
+
if (standalone) {
|
|
24220
|
+
logger.info(`upgrading standalone CLI ${cliVersion()} to ${version2 ?? "the latest release"}`);
|
|
24221
|
+
return upgradeStandaloneCli(standalone, version2, repoSlug());
|
|
24222
|
+
}
|
|
24223
|
+
const spec = `@puddle-code/cli@${version2 ?? "latest"}`;
|
|
24224
|
+
logger.info(`puddle CLI ${cliVersion()} \u2014 asking npm for ${version2 ?? "the latest release"}`);
|
|
24225
|
+
return new Promise((resolve6, reject) => {
|
|
24226
|
+
const child = spawn7("npm", ["install", "-g", spec], { stdio: "inherit" });
|
|
24227
|
+
child.on(
|
|
24228
|
+
"error",
|
|
24229
|
+
() => reject(
|
|
24230
|
+
new CliError(
|
|
24231
|
+
"not_installed",
|
|
24232
|
+
"npm is not on PATH",
|
|
24233
|
+
`the CLI is installed and upgraded via npm: npm install -g ${spec}`
|
|
24234
|
+
)
|
|
24235
|
+
)
|
|
24236
|
+
);
|
|
24237
|
+
child.on("exit", (code) => {
|
|
24238
|
+
if (code === 0) {
|
|
24239
|
+
logger.info("done \u2014 `puddle --version` shows the installed version");
|
|
24240
|
+
resolve6(0);
|
|
24241
|
+
} else {
|
|
24242
|
+
reject(new CliError("not_installed", `npm install exited with ${code ?? "a signal"}`));
|
|
24243
|
+
}
|
|
24244
|
+
});
|
|
24245
|
+
});
|
|
24246
|
+
}
|
|
24247
|
+
async function removeCli(yes, logger) {
|
|
24248
|
+
const standalone = standaloneInstallation();
|
|
24249
|
+
if (standalone) {
|
|
24250
|
+
const proceed2 = await confirm(
|
|
24251
|
+
`Remove the standalone CLI at ${standalone.root} and its launcher in ${dirname10(standalone.launcher)}? Close its cockpits first; daemons and their sessions are untouched.`,
|
|
24252
|
+
{ skip: yes }
|
|
24253
|
+
);
|
|
24254
|
+
if (!proceed2) {
|
|
24255
|
+
logger.info("nothing removed");
|
|
24256
|
+
return 0;
|
|
24257
|
+
}
|
|
24258
|
+
await removeStandaloneCli(standalone);
|
|
24259
|
+
logger.info("removed the standalone CLI \u2014 daemon installations and data are untouched");
|
|
24260
|
+
return 0;
|
|
24261
|
+
}
|
|
24262
|
+
const managed = await new Promise((resolve6) => {
|
|
24263
|
+
const child = spawn7("npm", ["ls", "-g", "@puddle-code/cli", "--depth=0"], { stdio: "ignore" });
|
|
24264
|
+
child.on("error", () => resolve6(false));
|
|
24265
|
+
child.on("exit", (code) => resolve6(code === 0));
|
|
24266
|
+
});
|
|
24267
|
+
if (!managed) {
|
|
24268
|
+
throw new CliError(
|
|
24269
|
+
"not_installed",
|
|
24270
|
+
"the puddle CLI is not an npm global install on this machine",
|
|
24271
|
+
"a repo checkout or linked dev build is removed the way it was installed"
|
|
24272
|
+
);
|
|
24273
|
+
}
|
|
24274
|
+
const proceed = await confirm(
|
|
24275
|
+
`Remove the puddle CLI (npm uninstall -g @puddle-code/cli)? Running cockpits keep running until killed; daemons and their sessions are untouched.`,
|
|
24276
|
+
{ skip: yes }
|
|
24277
|
+
);
|
|
24278
|
+
if (!proceed) {
|
|
24279
|
+
logger.info("nothing removed");
|
|
24280
|
+
return 0;
|
|
24281
|
+
}
|
|
24282
|
+
return new Promise((resolve6, reject) => {
|
|
24283
|
+
const child = spawn7("npm", ["uninstall", "-g", "@puddle-code/cli"], { stdio: "inherit" });
|
|
24284
|
+
child.on("error", () => reject(new CliError("not_installed", "npm is not on PATH")));
|
|
24285
|
+
child.on("exit", (code) => {
|
|
24286
|
+
if (code === 0) {
|
|
24287
|
+
logger.info("removed \u2014 daemons keep running (puddle remove daemon uninstalls one)");
|
|
24288
|
+
resolve6(0);
|
|
24289
|
+
} else {
|
|
24290
|
+
reject(new CliError("not_installed", `npm uninstall exited with ${code ?? "a signal"}`));
|
|
24291
|
+
}
|
|
24292
|
+
});
|
|
24293
|
+
});
|
|
24294
|
+
}
|
|
24295
|
+
|
|
24296
|
+
// src/cli/manage.ts
|
|
24297
|
+
import { spawn as spawn9 } from "node:child_process";
|
|
24298
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
24299
|
+
import { chmod as chmod2, copyFile, mkdir as mkdir3, rename, rm as rm3 } from "node:fs/promises";
|
|
23985
24300
|
import { homedir as homedir2 } from "node:os";
|
|
23986
|
-
import { join as
|
|
24301
|
+
import { join as join16 } from "node:path";
|
|
23987
24302
|
|
|
23988
24303
|
// src/lib/releases.ts
|
|
23989
24304
|
init_types();
|
|
@@ -24116,9 +24431,9 @@ async function upgradeDaemon(transport, opts = {}) {
|
|
|
24116
24431
|
}
|
|
24117
24432
|
|
|
24118
24433
|
// src/cli/detach.ts
|
|
24119
|
-
import { spawn as
|
|
24120
|
-
import { closeSync as closeSync2, mkdirSync as mkdirSync6, openSync as openSync2, readFileSync as
|
|
24121
|
-
import { dirname as
|
|
24434
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
24435
|
+
import { closeSync as closeSync2, mkdirSync as mkdirSync6, openSync as openSync2, readFileSync as readFileSync10 } from "node:fs";
|
|
24436
|
+
import { dirname as dirname11 } from "node:path";
|
|
24122
24437
|
init_types();
|
|
24123
24438
|
var COCKPIT_CHILD_ENV = "PUDDLE_COCKPIT_CHILD";
|
|
24124
24439
|
var READY_TIMEOUT_MS = 3e5;
|
|
@@ -24157,12 +24472,12 @@ async function launchDetached(opts) {
|
|
|
24157
24472
|
}
|
|
24158
24473
|
if (target !== "local") await new SshTransport(target).open();
|
|
24159
24474
|
const logFile = cockpitLogPath(target);
|
|
24160
|
-
mkdirSync6(
|
|
24475
|
+
mkdirSync6(dirname11(logFile), { recursive: true });
|
|
24161
24476
|
const fd = openSync2(logFile, "w");
|
|
24162
24477
|
const argv = opts.argv ?? process.argv.slice(2);
|
|
24163
|
-
const child =
|
|
24478
|
+
const child = spawn8(process.execPath, [process.argv[1] ?? "", ...argv], {
|
|
24164
24479
|
detached: true,
|
|
24165
|
-
stdio: ["ignore", fd, fd],
|
|
24480
|
+
stdio: ["ignore", fd, fd, "ipc"],
|
|
24166
24481
|
env: { ...process.env, [COCKPIT_CHILD_ENV]: "1" }
|
|
24167
24482
|
});
|
|
24168
24483
|
closeSync2(fd);
|
|
@@ -24171,7 +24486,13 @@ async function launchDetached(opts) {
|
|
|
24171
24486
|
throw new CliError("cockpit_launch_failed", "could not spawn the background cockpit process");
|
|
24172
24487
|
}
|
|
24173
24488
|
logger.info(`starting the cockpit for ${target} in the background\u2026`);
|
|
24174
|
-
const
|
|
24489
|
+
const stopPromptRelay = relayDaemonUpgradePrompt(child);
|
|
24490
|
+
let record2;
|
|
24491
|
+
try {
|
|
24492
|
+
record2 = await followStartup(target, child.pid, logFile);
|
|
24493
|
+
} finally {
|
|
24494
|
+
stopPromptRelay();
|
|
24495
|
+
}
|
|
24175
24496
|
if (record2 === null || record2.status !== "ready") {
|
|
24176
24497
|
if (record2?.status === "error") {
|
|
24177
24498
|
logger.warn(record2.message ?? "the cockpit failed to start");
|
|
@@ -24194,7 +24515,7 @@ async function followStartup(target, childPid, logFile) {
|
|
|
24194
24515
|
let offset = 0;
|
|
24195
24516
|
const relay = () => {
|
|
24196
24517
|
try {
|
|
24197
|
-
const buf =
|
|
24518
|
+
const buf = readFileSync10(logFile);
|
|
24198
24519
|
if (buf.length > offset) {
|
|
24199
24520
|
process.stderr.write(buf.subarray(offset));
|
|
24200
24521
|
offset = buf.length;
|
|
@@ -24219,7 +24540,7 @@ async function followStartup(target, childPid, logFile) {
|
|
|
24219
24540
|
}
|
|
24220
24541
|
function spawnDetachedRefresh(target, argv, refreshId) {
|
|
24221
24542
|
const logFile = cockpitLogPath(target);
|
|
24222
|
-
mkdirSync6(
|
|
24543
|
+
mkdirSync6(dirname11(logFile), { recursive: true });
|
|
24223
24544
|
const fd = openSync2(logFile, "a");
|
|
24224
24545
|
const env = {
|
|
24225
24546
|
...process.env,
|
|
@@ -24227,7 +24548,7 @@ function spawnDetachedRefresh(target, argv, refreshId) {
|
|
|
24227
24548
|
PUDDLE_INTERNAL_REFRESH: "1"
|
|
24228
24549
|
};
|
|
24229
24550
|
delete env[COCKPIT_CHILD_ENV];
|
|
24230
|
-
const child =
|
|
24551
|
+
const child = spawn8(process.execPath, [process.argv[1] ?? "", ...argv], {
|
|
24231
24552
|
detached: true,
|
|
24232
24553
|
stdio: ["ignore", fd, fd],
|
|
24233
24554
|
env
|
|
@@ -24260,41 +24581,6 @@ async function presentInvitation(record2, noBrowser, logger) {
|
|
|
24260
24581
|
else openBrowser(url2);
|
|
24261
24582
|
}
|
|
24262
24583
|
|
|
24263
|
-
// src/cli/prompt.ts
|
|
24264
|
-
init_types();
|
|
24265
|
-
import { createInterface as createInterface2 } from "node:readline";
|
|
24266
|
-
async function ask(question, def) {
|
|
24267
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) return def;
|
|
24268
|
-
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
24269
|
-
try {
|
|
24270
|
-
const answer = await new Promise(
|
|
24271
|
-
(resolve5) => rl.question(`${question} [${def}] `, resolve5)
|
|
24272
|
-
);
|
|
24273
|
-
return answer.trim() === "" ? def : answer.trim();
|
|
24274
|
-
} finally {
|
|
24275
|
-
rl.close();
|
|
24276
|
-
}
|
|
24277
|
-
}
|
|
24278
|
-
async function confirm(question, opts = {}) {
|
|
24279
|
-
if (opts.skip === true) return true;
|
|
24280
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
24281
|
-
throw new CliError(
|
|
24282
|
-
"bad_arguments",
|
|
24283
|
-
"refusing to confirm without a terminal",
|
|
24284
|
-
"pass --yes to proceed non-interactively"
|
|
24285
|
-
);
|
|
24286
|
-
}
|
|
24287
|
-
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
24288
|
-
try {
|
|
24289
|
-
const answer = await new Promise(
|
|
24290
|
-
(resolve5) => rl.question(`${question} [y/N] `, resolve5)
|
|
24291
|
-
);
|
|
24292
|
-
return /^y(es)?$/i.test(answer.trim());
|
|
24293
|
-
} finally {
|
|
24294
|
-
rl.close();
|
|
24295
|
-
}
|
|
24296
|
-
}
|
|
24297
|
-
|
|
24298
24584
|
// src/cli/manage.ts
|
|
24299
24585
|
function assertLocal(what, host, verb) {
|
|
24300
24586
|
if (host === void 0 || host === "local") return;
|
|
@@ -24403,31 +24689,6 @@ async function runUpgrade(cmd, logger) {
|
|
|
24403
24689
|
else if (process.platform === "darwin") logger.info("no desktop app installed \u2014 skipping");
|
|
24404
24690
|
return upgradeCli(cmd.version, logger);
|
|
24405
24691
|
}
|
|
24406
|
-
function upgradeCli(version2, logger) {
|
|
24407
|
-
const spec = `@puddle-code/cli@${version2 ?? "latest"}`;
|
|
24408
|
-
logger.info(`puddle CLI ${cliVersion()} \u2014 asking npm for ${version2 ?? "the latest release"}`);
|
|
24409
|
-
return new Promise((resolve5, reject) => {
|
|
24410
|
-
const child = spawn7("npm", ["install", "-g", spec], { stdio: "inherit" });
|
|
24411
|
-
child.on(
|
|
24412
|
-
"error",
|
|
24413
|
-
() => reject(
|
|
24414
|
-
new CliError(
|
|
24415
|
-
"not_installed",
|
|
24416
|
-
"npm is not on PATH",
|
|
24417
|
-
`the CLI is installed and upgraded via npm: npm install -g ${spec}`
|
|
24418
|
-
)
|
|
24419
|
-
)
|
|
24420
|
-
);
|
|
24421
|
-
child.on("exit", (code) => {
|
|
24422
|
-
if (code === 0) {
|
|
24423
|
-
logger.info("done \u2014 `puddle --version` shows the installed version");
|
|
24424
|
-
resolve5(0);
|
|
24425
|
-
} else {
|
|
24426
|
-
reject(new CliError("not_installed", `npm install exited with ${code ?? "a signal"}`));
|
|
24427
|
-
}
|
|
24428
|
-
});
|
|
24429
|
-
});
|
|
24430
|
-
}
|
|
24431
24692
|
async function setDesktop(opts, logger) {
|
|
24432
24693
|
if (process.platform === "linux") {
|
|
24433
24694
|
if (opts.verb !== "install") {
|
|
@@ -24499,22 +24760,22 @@ async function installDesktopAppImage(version2, logger) {
|
|
|
24499
24760
|
);
|
|
24500
24761
|
}
|
|
24501
24762
|
const dir = expandHome2(await ask("Where should the AppImage be stored?", "~/puddle"));
|
|
24502
|
-
const target =
|
|
24503
|
-
if (
|
|
24763
|
+
const target = join16(dir, "Puddle.AppImage");
|
|
24764
|
+
if (existsSync6(target) && version2 === void 0) {
|
|
24504
24765
|
logger.info(`${target} already exists \u2014 the app updates itself in-app (its update toast)`);
|
|
24505
24766
|
return 0;
|
|
24506
24767
|
}
|
|
24507
24768
|
logger.info(`installing Puddle ${update.version} to ${target}`);
|
|
24508
24769
|
const staged2 = await stageDesktopUpdate(update, { logger });
|
|
24509
|
-
await
|
|
24510
|
-
await
|
|
24770
|
+
await mkdir3(dir, { recursive: true });
|
|
24771
|
+
await rm3(target, { force: true });
|
|
24511
24772
|
try {
|
|
24512
24773
|
await rename(staged2.stagedPath, target);
|
|
24513
24774
|
} catch {
|
|
24514
24775
|
await copyFile(staged2.stagedPath, target);
|
|
24515
24776
|
await chmod2(target, 493);
|
|
24516
24777
|
}
|
|
24517
|
-
await
|
|
24778
|
+
await rm3(staged2.dir, { recursive: true, force: true });
|
|
24518
24779
|
const protocol = componentProtocolForVersion(update.version);
|
|
24519
24780
|
recordDesktopInstallation({
|
|
24520
24781
|
path: target,
|
|
@@ -24522,14 +24783,14 @@ async function installDesktopAppImage(version2, logger) {
|
|
|
24522
24783
|
...protocol ? { protocol } : {}
|
|
24523
24784
|
});
|
|
24524
24785
|
logger.info(`installed \u2014 run it directly (${target}) or add it to your launcher`);
|
|
24525
|
-
const opener =
|
|
24786
|
+
const opener = spawn9("xdg-open", [dir], { stdio: "ignore", detached: true });
|
|
24526
24787
|
opener.on("error", () => void 0);
|
|
24527
24788
|
opener.unref();
|
|
24528
24789
|
return 0;
|
|
24529
24790
|
}
|
|
24530
24791
|
function expandHome2(path) {
|
|
24531
24792
|
if (path === "~") return homedir2();
|
|
24532
|
-
return path.startsWith("~/") ?
|
|
24793
|
+
return path.startsWith("~/") ? join16(homedir2(), path.slice(2)) : path;
|
|
24533
24794
|
}
|
|
24534
24795
|
async function runRemove(cmd, logger) {
|
|
24535
24796
|
switch (cmd.what) {
|
|
@@ -24543,40 +24804,6 @@ async function runRemove(cmd, logger) {
|
|
|
24543
24804
|
return removeDaemonCmd(cmd, logger);
|
|
24544
24805
|
}
|
|
24545
24806
|
}
|
|
24546
|
-
async function removeCli(yes, logger) {
|
|
24547
|
-
const managed = await new Promise((resolve5) => {
|
|
24548
|
-
const child = spawn7("npm", ["ls", "-g", "@puddle-code/cli", "--depth=0"], { stdio: "ignore" });
|
|
24549
|
-
child.on("error", () => resolve5(false));
|
|
24550
|
-
child.on("exit", (code) => resolve5(code === 0));
|
|
24551
|
-
});
|
|
24552
|
-
if (!managed) {
|
|
24553
|
-
throw new CliError(
|
|
24554
|
-
"not_installed",
|
|
24555
|
-
"the puddle CLI is not an npm global install on this machine",
|
|
24556
|
-
"a repo checkout or linked dev build is removed the way it was installed"
|
|
24557
|
-
);
|
|
24558
|
-
}
|
|
24559
|
-
const proceed = await confirm(
|
|
24560
|
-
`Remove the puddle CLI (npm uninstall -g @puddle-code/cli)? Running cockpits keep running until killed; daemons and their sessions are untouched.`,
|
|
24561
|
-
{ skip: yes }
|
|
24562
|
-
);
|
|
24563
|
-
if (!proceed) {
|
|
24564
|
-
logger.info("nothing removed");
|
|
24565
|
-
return 0;
|
|
24566
|
-
}
|
|
24567
|
-
return new Promise((resolve5, reject) => {
|
|
24568
|
-
const child = spawn7("npm", ["uninstall", "-g", "@puddle-code/cli"], { stdio: "inherit" });
|
|
24569
|
-
child.on("error", () => reject(new CliError("not_installed", "npm is not on PATH")));
|
|
24570
|
-
child.on("exit", (code) => {
|
|
24571
|
-
if (code === 0) {
|
|
24572
|
-
logger.info("removed \u2014 daemons keep running (puddle remove daemon uninstalls one)");
|
|
24573
|
-
resolve5(0);
|
|
24574
|
-
} else {
|
|
24575
|
-
reject(new CliError("not_installed", `npm uninstall exited with ${code ?? "a signal"}`));
|
|
24576
|
-
}
|
|
24577
|
-
});
|
|
24578
|
-
});
|
|
24579
|
-
}
|
|
24580
24807
|
async function removeDesktop(yes, logger) {
|
|
24581
24808
|
if (process.platform !== "darwin") {
|
|
24582
24809
|
throw new CliError(
|
|
@@ -24601,7 +24828,7 @@ async function removeDesktop(yes, logger) {
|
|
|
24601
24828
|
logger.info("nothing removed");
|
|
24602
24829
|
return 0;
|
|
24603
24830
|
}
|
|
24604
|
-
await
|
|
24831
|
+
await rm3(installed.appPath, { recursive: true, force: true });
|
|
24605
24832
|
await pruneDesktopUpdateCache([]);
|
|
24606
24833
|
logger.info(`deleted ${installed.appPath} (recent hosts kept in ~/.puddle/recent-hosts.json)`);
|
|
24607
24834
|
return 0;
|
|
@@ -24723,9 +24950,9 @@ function timestampedLogger() {
|
|
|
24723
24950
|
|
|
24724
24951
|
// src/cli/run.ts
|
|
24725
24952
|
function assetsDir() {
|
|
24726
|
-
const here =
|
|
24727
|
-
for (const candidate of [
|
|
24728
|
-
if (
|
|
24953
|
+
const here = dirname12(fileURLToPath5(import.meta.url));
|
|
24954
|
+
for (const candidate of [join17(here, "public"), join17(here, "..", "..", "dist", "public")]) {
|
|
24955
|
+
if (existsSync7(join17(candidate, "index.html"))) return candidate;
|
|
24729
24956
|
}
|
|
24730
24957
|
throw new CliError(
|
|
24731
24958
|
"not_installed",
|
|
@@ -24759,7 +24986,7 @@ async function openTarget(host) {
|
|
|
24759
24986
|
};
|
|
24760
24987
|
}
|
|
24761
24988
|
function runUntilInterrupted(stop, farewell) {
|
|
24762
|
-
return new Promise((
|
|
24989
|
+
return new Promise((resolve6) => {
|
|
24763
24990
|
let stopping = false;
|
|
24764
24991
|
process.on("SIGINT", () => {
|
|
24765
24992
|
if (stopping) process.exit(130);
|
|
@@ -24767,10 +24994,10 @@ function runUntilInterrupted(stop, farewell) {
|
|
|
24767
24994
|
process.stderr.write(`
|
|
24768
24995
|
${farewell}
|
|
24769
24996
|
`);
|
|
24770
|
-
void stop().then(
|
|
24997
|
+
void stop().then(resolve6);
|
|
24771
24998
|
});
|
|
24772
24999
|
process.on("SIGTERM", () => {
|
|
24773
|
-
void stop().then(
|
|
25000
|
+
void stop().then(resolve6);
|
|
24774
25001
|
});
|
|
24775
25002
|
});
|
|
24776
25003
|
}
|
|
@@ -25058,6 +25285,7 @@ async function runCockpit(command, target, terminal) {
|
|
|
25058
25285
|
const common = {
|
|
25059
25286
|
...command,
|
|
25060
25287
|
assetsDir: assetsDir(),
|
|
25288
|
+
confirmDaemonUpgrade,
|
|
25061
25289
|
logger,
|
|
25062
25290
|
onRefreshRequest,
|
|
25063
25291
|
refreshId: process.env.PUDDLE_REFRESH_ID
|