@puddle-code/cli 0.0.13 → 0.0.14
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 +45 -6
- package/README.md +4 -4
- package/dist/index.js +446 -166
- package/dist/public/assets/{Dashboard-T4hbzT6P.js → Dashboard-Ct5exI8s.js} +1 -1
- package/dist/public/assets/{EditorTabClose-DxU5M-9J.js → EditorTabClose-BhFjRoCZ.js} +1 -1
- package/dist/public/assets/{LazyTerminal-Dpk5-lcY.js → LazyTerminal-B0xYs2ul.js} +2 -2
- package/dist/public/assets/{ModelRefcount-BGB1nvG5.js → ModelRefcount-BY5Ul0p3.js} +1 -1
- package/dist/public/assets/{PaneEditorBody-DX0u3ISF.js → PaneEditorBody-DLMtDiov.js} +1 -1
- package/dist/public/assets/{SettingsDialog-58D8eQOa.js → SettingsDialog-WhQXNCmS.js} +1 -1
- package/dist/public/assets/{Terminal-DlXSwAG0.js → Terminal-DA8zjtmU.js} +1 -1
- package/dist/public/assets/{Workspace-B_eXJKAN.js → Workspace-B0FDl771.js} +2 -2
- package/dist/public/assets/{buffer-store-zsL8CNKc.js → buffer-store-BHJ82Nh3.js} +2 -2
- package/dist/public/assets/{context-menu-C8MwVsBG.js → context-menu--H5GoapF.js} +1 -1
- package/dist/public/assets/{cssMode-Dzho6NMm.js → cssMode-Ctq00nXG.js} +1 -1
- package/dist/public/assets/{htmlMode-pj_l07tD.js → htmlMode-pYCLPiZ_.js} +1 -1
- package/dist/public/assets/index-CjgsCY-C.css +2 -0
- package/dist/public/assets/{index-oAv85qtL.js → index-RvCLpi9M.js} +4 -4
- package/dist/public/assets/{jsonMode-Dx2T0LuI.js → jsonMode-Ch5UticT.js} +1 -1
- package/dist/public/assets/{session-seed-C8M00YNU.js → session-seed-DBxHPYBK.js} +1 -1
- package/dist/public/assets/ssh-mode-eNTVum_C.js +1 -0
- package/dist/public/assets/{tsMode-DXWeq4NK.js → tsMode-CeXrzlPn.js} +1 -1
- package/dist/public/index.html +3 -3
- package/package.json +1 -1
- package/dist/public/assets/index-DK5CyY2G.css +0 -2
- package/dist/public/assets/ssh-mode-BdHO6hP9.js +0 -1
package/dist/index.js
CHANGED
|
@@ -2307,8 +2307,8 @@ var require_websocket = __commonJS({
|
|
|
2307
2307
|
var http = __require("http");
|
|
2308
2308
|
var net = __require("net");
|
|
2309
2309
|
var tls = __require("tls");
|
|
2310
|
-
var { randomBytes, createHash } = __require("crypto");
|
|
2311
|
-
var { Duplex, Readable } = __require("stream");
|
|
2310
|
+
var { randomBytes, createHash: createHash2 } = __require("crypto");
|
|
2311
|
+
var { Duplex, Readable: Readable2 } = __require("stream");
|
|
2312
2312
|
var { URL: URL2 } = __require("url");
|
|
2313
2313
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
2314
2314
|
var Receiver2 = require_receiver();
|
|
@@ -2975,7 +2975,7 @@ var require_websocket = __commonJS({
|
|
|
2975
2975
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
2976
2976
|
return;
|
|
2977
2977
|
}
|
|
2978
|
-
const digest =
|
|
2978
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
2979
2979
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
2980
2980
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
2981
2981
|
return;
|
|
@@ -3344,7 +3344,7 @@ var require_websocket_server = __commonJS({
|
|
|
3344
3344
|
var EventEmitter = __require("events");
|
|
3345
3345
|
var http = __require("http");
|
|
3346
3346
|
var { Duplex } = __require("stream");
|
|
3347
|
-
var { createHash } = __require("crypto");
|
|
3347
|
+
var { createHash: createHash2 } = __require("crypto");
|
|
3348
3348
|
var extension2 = require_extension();
|
|
3349
3349
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
3350
3350
|
var subprotocol2 = require_subprotocol();
|
|
@@ -3651,7 +3651,7 @@ var require_websocket_server = __commonJS({
|
|
|
3651
3651
|
);
|
|
3652
3652
|
}
|
|
3653
3653
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
3654
|
-
const digest =
|
|
3654
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
3655
3655
|
const headers = [
|
|
3656
3656
|
"HTTP/1.1 101 Switching Protocols",
|
|
3657
3657
|
"Upgrade: websocket",
|
|
@@ -3908,10 +3908,8 @@ init_types();
|
|
|
3908
3908
|
var USAGE = `puddle \u2014 self-hosted orchestrator for CLI coding agents
|
|
3909
3909
|
|
|
3910
3910
|
usage:
|
|
3911
|
-
puddle
|
|
3912
|
-
[--tarball <path>]
|
|
3913
|
-
puddle connect user@host [--port <local>] [--remote-port <p>] [--foreground]
|
|
3914
|
-
[--no-browser] [--no-upgrade] [--tarball <path>]
|
|
3911
|
+
puddle launch [local | user@host] [--port <p>] [--remote-port <p>]
|
|
3912
|
+
[--foreground] [--no-browser] [--no-upgrade] [--tarball <path>]
|
|
3915
3913
|
puddle refresh [local | user@host] [--port <p>] [--remote-port <p>]
|
|
3916
3914
|
[--foreground] [--no-browser] [--no-upgrade] [--tarball <path>]
|
|
3917
3915
|
puddle list
|
|
@@ -3919,17 +3917,18 @@ usage:
|
|
|
3919
3917
|
puddle status [user@host]
|
|
3920
3918
|
puddle attach [user@host] <session> [--term <id>]
|
|
3921
3919
|
puddle logs [user@host] [session] [--term <id>] [-f|--follow]
|
|
3922
|
-
puddle upgrade [user@host]
|
|
3920
|
+
puddle upgrade <cli | daemon | desktop> [user@host]
|
|
3923
3921
|
puddle --version | --help
|
|
3924
3922
|
|
|
3925
|
-
|
|
3926
|
-
machine (installing it under ~/.puddle if
|
|
3927
|
-
an SSH host through one tunnel.
|
|
3928
|
-
ready, so the terminal may close (--foreground
|
|
3929
|
-
stops the cockpit). refresh stops a target's
|
|
3930
|
-
runs the full
|
|
3931
|
-
keeping the old UI port so open tabs survive.
|
|
3932
|
-
kill stops one \u2014 sessions keep running on the
|
|
3923
|
+
launch serves the cockpit at http://localhost:7433 against the daemon on this
|
|
3924
|
+
machine when no host (or 'local') is given \u2014 installing it under ~/.puddle if
|
|
3925
|
+
needed \u2014 and does the same for an SSH host through one tunnel. It keeps
|
|
3926
|
+
running in the background once ready, so the terminal may close (--foreground
|
|
3927
|
+
to stay attached; Ctrl-C then stops the cockpit). refresh stops a target's
|
|
3928
|
+
cockpit (even a wedged one) and runs the full launch flow again \u2014 tunnel,
|
|
3929
|
+
daemon restart if needed \u2014 keeping the old UI port so open tabs survive.
|
|
3930
|
+
list shows running cockpits; kill stops one \u2014 sessions keep running on the
|
|
3931
|
+
host either way.`;
|
|
3933
3932
|
function parseArgs(argv) {
|
|
3934
3933
|
const [cmd, ...rest] = argv;
|
|
3935
3934
|
if (cmd === void 0 || cmd === "help" || cmd === "--help" || cmd === "-h")
|
|
@@ -3977,40 +3976,10 @@ function parseArgs(argv) {
|
|
|
3977
3976
|
}
|
|
3978
3977
|
};
|
|
3979
3978
|
switch (cmd) {
|
|
3980
|
-
case "
|
|
3981
|
-
if (positionals.length > 0) {
|
|
3982
|
-
throw new CliError(
|
|
3983
|
-
"bad_arguments",
|
|
3984
|
-
`start takes no positional arguments (got '${positionals[0]}')`
|
|
3985
|
-
);
|
|
3986
|
-
}
|
|
3987
|
-
const port = intFlag("--port");
|
|
3988
|
-
const preferPort = intFlag("--prefer-port");
|
|
3989
|
-
const tarball = strFlag("--tarball");
|
|
3990
|
-
expect(
|
|
3991
|
-
"--port",
|
|
3992
|
-
"--prefer-port",
|
|
3993
|
-
"--no-browser",
|
|
3994
|
-
"--no-upgrade",
|
|
3995
|
-
"--tarball",
|
|
3996
|
-
"--foreground"
|
|
3997
|
-
);
|
|
3998
|
-
return {
|
|
3999
|
-
cmd: "start",
|
|
4000
|
-
...port !== void 0 ? { port } : {},
|
|
4001
|
-
...preferPort !== void 0 ? { preferPort } : {},
|
|
4002
|
-
...tarball !== void 0 ? { tarball } : {},
|
|
4003
|
-
noBrowser: flags.has("--no-browser"),
|
|
4004
|
-
noUpgrade: flags.has("--no-upgrade"),
|
|
4005
|
-
foreground: flags.has("--foreground")
|
|
4006
|
-
};
|
|
4007
|
-
}
|
|
4008
|
-
case "connect": {
|
|
4009
|
-
const host = positionals[0];
|
|
4010
|
-
if (host === void 0)
|
|
4011
|
-
throw new CliError("bad_arguments", "connect needs a host: puddle connect user@host");
|
|
3979
|
+
case "launch": {
|
|
4012
3980
|
if (positionals.length > 1)
|
|
4013
|
-
throw new CliError("bad_arguments", "
|
|
3981
|
+
throw new CliError("bad_arguments", "launch takes at most one target");
|
|
3982
|
+
const target = positionals[0] ?? "local";
|
|
4014
3983
|
const port = intFlag("--port");
|
|
4015
3984
|
const preferPort = intFlag("--prefer-port");
|
|
4016
3985
|
const remotePort = intFlag("--remote-port");
|
|
@@ -4024,9 +3993,12 @@ function parseArgs(argv) {
|
|
|
4024
3993
|
"--tarball",
|
|
4025
3994
|
"--foreground"
|
|
4026
3995
|
);
|
|
3996
|
+
if (target === "local" && remotePort !== void 0) {
|
|
3997
|
+
throw new CliError("bad_arguments", "--remote-port only applies to an SSH target");
|
|
3998
|
+
}
|
|
4027
3999
|
return {
|
|
4028
|
-
cmd: "
|
|
4029
|
-
|
|
4000
|
+
cmd: "launch",
|
|
4001
|
+
target,
|
|
4030
4002
|
...port !== void 0 ? { port } : {},
|
|
4031
4003
|
...preferPort !== void 0 ? { preferPort } : {},
|
|
4032
4004
|
...remotePort !== void 0 ? { remotePort } : {},
|
|
@@ -4036,6 +4008,14 @@ function parseArgs(argv) {
|
|
|
4036
4008
|
foreground: flags.has("--foreground")
|
|
4037
4009
|
};
|
|
4038
4010
|
}
|
|
4011
|
+
// The pre-unification verbs, kept only to say what replaced them.
|
|
4012
|
+
case "start":
|
|
4013
|
+
throw new CliError("bad_arguments", `'start' is now: puddle launch`);
|
|
4014
|
+
case "connect":
|
|
4015
|
+
throw new CliError(
|
|
4016
|
+
"bad_arguments",
|
|
4017
|
+
`'connect' is now: puddle launch ${positionals[0] ?? "user@host"}`
|
|
4018
|
+
);
|
|
4039
4019
|
case "refresh": {
|
|
4040
4020
|
if (positionals.length > 1)
|
|
4041
4021
|
throw new CliError("bad_arguments", "refresh takes at most one target");
|
|
@@ -4120,24 +4100,29 @@ function parseArgs(argv) {
|
|
|
4120
4100
|
return first.includes("@") ? { cmd: "logs", host: first, follow, ...term !== void 0 ? { term } : {} } : { cmd: "logs", session: first, follow, ...term !== void 0 ? { term } : {} };
|
|
4121
4101
|
}
|
|
4122
4102
|
case "upgrade": {
|
|
4123
|
-
|
|
4124
|
-
|
|
4103
|
+
const [what, host, extra] = positionals;
|
|
4104
|
+
if (what !== "cli" && what !== "daemon" && what !== "desktop") {
|
|
4105
|
+
throw new CliError(
|
|
4106
|
+
"bad_arguments",
|
|
4107
|
+
"upgrade needs a subject: puddle upgrade <cli | daemon | desktop> [user@host]",
|
|
4108
|
+
what !== void 0 && what.includes("@") ? `to upgrade the daemon on ${what}: puddle upgrade daemon ${what}` : void 0
|
|
4109
|
+
);
|
|
4110
|
+
}
|
|
4111
|
+
if (extra !== void 0)
|
|
4112
|
+
throw new CliError("bad_arguments", "upgrade takes at most a subject + host");
|
|
4125
4113
|
expect();
|
|
4126
|
-
|
|
4127
|
-
return { cmd: "upgrade", ...host !== void 0 ? { host } : {} };
|
|
4114
|
+
return { cmd: "upgrade", what, ...host !== void 0 ? { host } : {} };
|
|
4128
4115
|
}
|
|
4129
4116
|
default:
|
|
4130
4117
|
throw new CliError("bad_arguments", `unknown command '${cmd}'`, "see: puddle --help");
|
|
4131
4118
|
}
|
|
4132
4119
|
}
|
|
4133
4120
|
function argvFor(command) {
|
|
4134
|
-
const argv = [
|
|
4135
|
-
if (command.
|
|
4121
|
+
const argv = ["launch"];
|
|
4122
|
+
if (command.target !== "local") argv.push(command.target);
|
|
4136
4123
|
if (command.port !== void 0) argv.push("--port", String(command.port));
|
|
4137
4124
|
if (command.preferPort !== void 0) argv.push("--prefer-port", String(command.preferPort));
|
|
4138
|
-
if (command.
|
|
4139
|
-
argv.push("--remote-port", String(command.remotePort));
|
|
4140
|
-
}
|
|
4125
|
+
if (command.remotePort !== void 0) argv.push("--remote-port", String(command.remotePort));
|
|
4141
4126
|
if (command.tarball !== void 0) argv.push("--tarball", command.tarball);
|
|
4142
4127
|
if (command.noBrowser) argv.push("--no-browser");
|
|
4143
4128
|
if (command.noUpgrade) argv.push("--no-upgrade");
|
|
@@ -4147,16 +4132,288 @@ function argvFor(command) {
|
|
|
4147
4132
|
|
|
4148
4133
|
// src/cli/run.ts
|
|
4149
4134
|
init_attach();
|
|
4135
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
4150
4136
|
import { existsSync as existsSync3 } from "node:fs";
|
|
4151
|
-
import { dirname as dirname6, join as
|
|
4137
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
4152
4138
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
4153
4139
|
|
|
4154
|
-
// src/lib/
|
|
4140
|
+
// src/lib/desktop-update.ts
|
|
4141
|
+
import { createHash } from "node:crypto";
|
|
4155
4142
|
import { spawn } from "node:child_process";
|
|
4143
|
+
import { createReadStream, createWriteStream } from "node:fs";
|
|
4144
|
+
import { chmod, mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
4145
|
+
import { join as join3 } from "node:path";
|
|
4146
|
+
import { Readable } from "node:stream";
|
|
4147
|
+
import { pipeline } from "node:stream/promises";
|
|
4148
|
+
|
|
4149
|
+
// src/lib/paths.ts
|
|
4150
|
+
import { homedir } from "node:os";
|
|
4151
|
+
import { join } from "node:path";
|
|
4152
|
+
function clientHome(env = process.env) {
|
|
4153
|
+
return env.PUDDLE_HOME ?? join(homedir(), ".puddle");
|
|
4154
|
+
}
|
|
4155
|
+
var HOST_HOME = '"${PUDDLE_HOME:-$HOME/.puddle}"';
|
|
4156
|
+
var hostPaths = {
|
|
4157
|
+
home: HOST_HOME,
|
|
4158
|
+
token: `${HOST_HOME}/token`,
|
|
4159
|
+
config: `${HOST_HOME}/config.json`,
|
|
4160
|
+
runtime: `${HOST_HOME}/runtime.json`,
|
|
4161
|
+
current: `${HOST_HOME}/bin/current`,
|
|
4162
|
+
cache: `${HOST_HOME}/cache`,
|
|
4163
|
+
logs: `${HOST_HOME}/logs`
|
|
4164
|
+
};
|
|
4165
|
+
|
|
4166
|
+
// src/lib/desktop-update.ts
|
|
4167
|
+
init_types();
|
|
4168
|
+
|
|
4169
|
+
// src/lib/version.ts
|
|
4170
|
+
import { readFileSync } from "node:fs";
|
|
4171
|
+
import { dirname, join as join2 } from "node:path";
|
|
4172
|
+
import { fileURLToPath } from "node:url";
|
|
4173
|
+
function cliVersion() {
|
|
4174
|
+
if (true) return "0.0.14";
|
|
4175
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
4176
|
+
for (const candidate of [
|
|
4177
|
+
join2(here, "..", "..", "package.json"),
|
|
4178
|
+
join2(here, "..", "package.json")
|
|
4179
|
+
]) {
|
|
4180
|
+
try {
|
|
4181
|
+
const pkg = JSON.parse(readFileSync(candidate, "utf8"));
|
|
4182
|
+
if (pkg.version !== void 0) return pkg.version;
|
|
4183
|
+
} catch {
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
return "0.0.0";
|
|
4187
|
+
}
|
|
4188
|
+
function pinnedDaemonVersion() {
|
|
4189
|
+
return cliVersion();
|
|
4190
|
+
}
|
|
4191
|
+
function repoSlug(env = process.env) {
|
|
4192
|
+
if (env.PUDDLE_REPO !== void 0 && env.PUDDLE_REPO !== "") return env.PUDDLE_REPO;
|
|
4193
|
+
if (true) return "PerceptronV/puddle-code";
|
|
4194
|
+
return void 0;
|
|
4195
|
+
}
|
|
4196
|
+
|
|
4197
|
+
// src/lib/desktop-update.ts
|
|
4198
|
+
function isNewerVersion(candidate, current) {
|
|
4199
|
+
const parse3 = (v) => {
|
|
4200
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(v.trim());
|
|
4201
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
4202
|
+
};
|
|
4203
|
+
const a = parse3(candidate);
|
|
4204
|
+
const b = parse3(current);
|
|
4205
|
+
if (a === null || b === null) return false;
|
|
4206
|
+
for (let i = 0; i < 3; i++) {
|
|
4207
|
+
if (a[i] !== b[i]) return a[i] > b[i];
|
|
4208
|
+
}
|
|
4209
|
+
return false;
|
|
4210
|
+
}
|
|
4211
|
+
function pickDesktopAsset(assets, platform, arch) {
|
|
4212
|
+
const suffix = platform === "darwin" ? "-mac.zip" : platform === "linux" ? ".AppImage" : null;
|
|
4213
|
+
if (suffix === null) return null;
|
|
4214
|
+
return assets.find(
|
|
4215
|
+
(a) => a.name.endsWith(suffix) && a.name.includes("-arm64") === (arch === "arm64")
|
|
4216
|
+
) ?? null;
|
|
4217
|
+
}
|
|
4218
|
+
function parseSums(text) {
|
|
4219
|
+
const sums = /* @__PURE__ */ new Map();
|
|
4220
|
+
for (const line of text.split("\n")) {
|
|
4221
|
+
const match = /^([0-9a-f]{64})\s+\*?(.+?)\s*$/.exec(line.trim());
|
|
4222
|
+
if (match) sums.set(match[2], match[1]);
|
|
4223
|
+
}
|
|
4224
|
+
return sums;
|
|
4225
|
+
}
|
|
4226
|
+
async function checkForDesktopUpdate(currentVersion, opts = {}) {
|
|
4227
|
+
const slug2 = repoSlug();
|
|
4228
|
+
if (slug2 === void 0) return null;
|
|
4229
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
4230
|
+
const response = await fetchFn(`https://api.github.com/repos/${slug2}/releases/latest`, {
|
|
4231
|
+
headers: { accept: "application/vnd.github+json", "user-agent": "puddle-desktop" }
|
|
4232
|
+
});
|
|
4233
|
+
if (!response.ok) {
|
|
4234
|
+
throw new CliError("not_installed", `release lookup failed (HTTP ${response.status})`);
|
|
4235
|
+
}
|
|
4236
|
+
const release = await response.json();
|
|
4237
|
+
const version2 = (release.tag_name ?? "").replace(/^v/, "");
|
|
4238
|
+
if (!isNewerVersion(version2, currentVersion)) return null;
|
|
4239
|
+
const assets = release.assets ?? [];
|
|
4240
|
+
const asset = pickDesktopAsset(
|
|
4241
|
+
assets,
|
|
4242
|
+
opts.platform ?? process.platform,
|
|
4243
|
+
opts.arch ?? process.arch
|
|
4244
|
+
);
|
|
4245
|
+
const sums = assets.find((a) => a.name === "SHA256SUMS");
|
|
4246
|
+
if (asset === null || sums === void 0) return null;
|
|
4247
|
+
return {
|
|
4248
|
+
version: version2,
|
|
4249
|
+
asset: { name: asset.name, url: asset.browser_download_url },
|
|
4250
|
+
sumsUrl: sums.browser_download_url
|
|
4251
|
+
};
|
|
4252
|
+
}
|
|
4253
|
+
async function stageDesktopUpdate(update, opts = {}) {
|
|
4254
|
+
const logger = opts.logger ?? silentLogger;
|
|
4255
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
4256
|
+
const dir = join3(opts.cacheDir ?? join3(clientHome(), "cache", "desktop"), update.version);
|
|
4257
|
+
await rm(dir, { recursive: true, force: true });
|
|
4258
|
+
await mkdir(dir, { recursive: true });
|
|
4259
|
+
try {
|
|
4260
|
+
logger.info(`downloading ${update.asset.name}`);
|
|
4261
|
+
const archive = join3(dir, update.asset.name);
|
|
4262
|
+
const response = await fetchFn(update.asset.url, {
|
|
4263
|
+
headers: { "user-agent": "puddle-desktop" }
|
|
4264
|
+
});
|
|
4265
|
+
if (!response.ok || response.body === null) {
|
|
4266
|
+
throw new CliError("not_installed", `download failed (HTTP ${response.status})`);
|
|
4267
|
+
}
|
|
4268
|
+
await pipeline(
|
|
4269
|
+
Readable.fromWeb(response.body),
|
|
4270
|
+
createWriteStream(archive)
|
|
4271
|
+
);
|
|
4272
|
+
const sumsResponse = await fetchFn(update.sumsUrl, {
|
|
4273
|
+
headers: { "user-agent": "puddle-desktop" }
|
|
4274
|
+
});
|
|
4275
|
+
if (!sumsResponse.ok) {
|
|
4276
|
+
throw new CliError("not_installed", `SHA256SUMS fetch failed (HTTP ${sumsResponse.status})`);
|
|
4277
|
+
}
|
|
4278
|
+
const expected = parseSums(await sumsResponse.text()).get(update.asset.name);
|
|
4279
|
+
if (expected === void 0) {
|
|
4280
|
+
throw new CliError("not_installed", `${update.asset.name} is not in SHA256SUMS`);
|
|
4281
|
+
}
|
|
4282
|
+
const actual = await fileSha256(archive);
|
|
4283
|
+
if (actual !== expected) {
|
|
4284
|
+
throw new CliError(
|
|
4285
|
+
"not_installed",
|
|
4286
|
+
`checksum mismatch for ${update.asset.name}`,
|
|
4287
|
+
`expected ${expected}, got ${actual}`
|
|
4288
|
+
);
|
|
4289
|
+
}
|
|
4290
|
+
if (update.asset.name.endsWith(".zip")) {
|
|
4291
|
+
const extractDir = join3(dir, "extract");
|
|
4292
|
+
await run("/usr/bin/ditto", ["-x", "-k", archive, extractDir]);
|
|
4293
|
+
const bundle = (await readdir(extractDir)).find((name) => name.endsWith(".app"));
|
|
4294
|
+
if (bundle === void 0) {
|
|
4295
|
+
throw new CliError("not_installed", "the update zip contains no .app bundle");
|
|
4296
|
+
}
|
|
4297
|
+
logger.info(`staged ${update.version} at ${join3(extractDir, bundle)}`);
|
|
4298
|
+
return {
|
|
4299
|
+
version: update.version,
|
|
4300
|
+
kind: "mac-app",
|
|
4301
|
+
stagedPath: join3(extractDir, bundle),
|
|
4302
|
+
dir
|
|
4303
|
+
};
|
|
4304
|
+
}
|
|
4305
|
+
await chmod(archive, 493);
|
|
4306
|
+
logger.info(`staged ${update.version} at ${archive}`);
|
|
4307
|
+
return { version: update.version, kind: "appimage", stagedPath: archive, dir };
|
|
4308
|
+
} catch (e) {
|
|
4309
|
+
await rm(dir, { recursive: true, force: true });
|
|
4310
|
+
throw e;
|
|
4311
|
+
}
|
|
4312
|
+
}
|
|
4313
|
+
async function applyDesktopUpdate(staged, opts) {
|
|
4314
|
+
const logger = opts.logger ?? silentLogger;
|
|
4315
|
+
const script = swapScript(staged, opts);
|
|
4316
|
+
const scriptPath = join3(staged.dir, "apply.sh");
|
|
4317
|
+
await writeFile(scriptPath, script, { mode: 493 });
|
|
4318
|
+
logger.info(`applying ${staged.version} via ${scriptPath}`);
|
|
4319
|
+
if (opts.detach ?? true) {
|
|
4320
|
+
const child = spawn("/bin/sh", [scriptPath], { detached: true, stdio: "ignore" });
|
|
4321
|
+
child.unref();
|
|
4322
|
+
return;
|
|
4323
|
+
}
|
|
4324
|
+
await run("/bin/sh", [scriptPath]);
|
|
4325
|
+
}
|
|
4326
|
+
var q = (s) => `'${s.replaceAll("'", `'\\''`)}'`;
|
|
4327
|
+
function swapScript(staged, opts) {
|
|
4328
|
+
const wait = opts.waitPid === void 0 ? [] : [
|
|
4329
|
+
`i=0`,
|
|
4330
|
+
`while kill -0 ${opts.waitPid} 2>/dev/null; do`,
|
|
4331
|
+
` i=$((i+1)); [ "$i" -gt 600 ] && exit 1`,
|
|
4332
|
+
` sleep 0.1`,
|
|
4333
|
+
`done`
|
|
4334
|
+
];
|
|
4335
|
+
if (staged.kind === "mac-app") {
|
|
4336
|
+
return [
|
|
4337
|
+
`#!/bin/sh`,
|
|
4338
|
+
`# puddle desktop update ${staged.version} \u2014 swap once the app exits`,
|
|
4339
|
+
...wait,
|
|
4340
|
+
`target=${q(opts.targetPath)}`,
|
|
4341
|
+
`staged=${q(staged.stagedPath)}`,
|
|
4342
|
+
`rm -rf "$target.old"`,
|
|
4343
|
+
`mv "$target" "$target.old" || exit 1`,
|
|
4344
|
+
`if mv "$staged" "$target" 2>/dev/null || /usr/bin/ditto "$staged" "$target"; then`,
|
|
4345
|
+
// Defence in depth: the normal path never quarantines (node's fetch is
|
|
4346
|
+
// not a quarantine-opted-in app, and ditto only propagates what the
|
|
4347
|
+
// zip already carries), but if the bundle ever acquires the attribute
|
|
4348
|
+
// the swap must not resurrect the Gatekeeper prompt.
|
|
4349
|
+
` /usr/bin/xattr -dr com.apple.quarantine "$target" 2>/dev/null`,
|
|
4350
|
+
` rm -rf "$target.old" ${q(staged.dir)}`,
|
|
4351
|
+
...opts.relaunch ? [` open "$target"`] : [],
|
|
4352
|
+
`else`,
|
|
4353
|
+
` mv "$target.old" "$target"`,
|
|
4354
|
+
` exit 1`,
|
|
4355
|
+
`fi`,
|
|
4356
|
+
``
|
|
4357
|
+
].join("\n");
|
|
4358
|
+
}
|
|
4359
|
+
return [
|
|
4360
|
+
`#!/bin/sh`,
|
|
4361
|
+
`# puddle desktop update ${staged.version} \u2014 swap once the app exits`,
|
|
4362
|
+
...wait,
|
|
4363
|
+
`target=${q(opts.targetPath)}`,
|
|
4364
|
+
`cp -f ${q(staged.stagedPath)} "$target" || exit 1`,
|
|
4365
|
+
`chmod +x "$target"`,
|
|
4366
|
+
`rm -rf ${q(staged.dir)}`,
|
|
4367
|
+
...opts.relaunch ? [`"$target" >/dev/null 2>&1 &`] : [],
|
|
4368
|
+
``
|
|
4369
|
+
].join("\n");
|
|
4370
|
+
}
|
|
4371
|
+
async function findInstalledDesktopApp() {
|
|
4372
|
+
if (process.platform !== "darwin") return null;
|
|
4373
|
+
const { readFile } = await import("node:fs/promises");
|
|
4374
|
+
const { homedir: homedir2 } = await import("node:os");
|
|
4375
|
+
for (const appPath of ["/Applications/Puddle.app", join3(homedir2(), "Applications/Puddle.app")]) {
|
|
4376
|
+
try {
|
|
4377
|
+
const plist = await readFile(join3(appPath, "Contents/Info.plist"), "utf8");
|
|
4378
|
+
const match = /<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/.exec(
|
|
4379
|
+
plist
|
|
4380
|
+
);
|
|
4381
|
+
if (match) return { appPath, version: match[1] };
|
|
4382
|
+
} catch {
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
return null;
|
|
4386
|
+
}
|
|
4387
|
+
function isDesktopAppRunning(appPath) {
|
|
4388
|
+
return new Promise((resolve3) => {
|
|
4389
|
+
const child = spawn("/usr/bin/pgrep", ["-f", `${appPath}/Contents/MacOS/`], {
|
|
4390
|
+
stdio: "ignore"
|
|
4391
|
+
});
|
|
4392
|
+
child.on("error", () => resolve3(false));
|
|
4393
|
+
child.on("exit", (code) => resolve3(code === 0));
|
|
4394
|
+
});
|
|
4395
|
+
}
|
|
4396
|
+
function fileSha256(path) {
|
|
4397
|
+
const hash2 = createHash("sha256");
|
|
4398
|
+
return pipeline(createReadStream(path), hash2).then(() => hash2.digest("hex"));
|
|
4399
|
+
}
|
|
4400
|
+
function run(command, args) {
|
|
4401
|
+
return new Promise((resolve3, reject) => {
|
|
4402
|
+
const child = spawn(command, args, { stdio: "ignore" });
|
|
4403
|
+
child.on("error", reject);
|
|
4404
|
+
child.on(
|
|
4405
|
+
"exit",
|
|
4406
|
+
(code) => code === 0 ? resolve3() : reject(new CliError("not_installed", `${command} exited with ${code ?? "signal"}`))
|
|
4407
|
+
);
|
|
4408
|
+
});
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4411
|
+
// src/lib/browser.ts
|
|
4412
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
4156
4413
|
function openBrowser(url2, platform = process.platform) {
|
|
4157
4414
|
const command = platform === "darwin" ? { bin: "open", args: [url2] } : platform === "win32" ? { bin: "cmd", args: ["/c", "start", "", url2] } : { bin: "xdg-open", args: [url2] };
|
|
4158
4415
|
try {
|
|
4159
|
-
const child =
|
|
4416
|
+
const child = spawn2(command.bin, command.args, { stdio: "ignore", detached: true });
|
|
4160
4417
|
child.on("error", () => {
|
|
4161
4418
|
});
|
|
4162
4419
|
child.unref();
|
|
@@ -4167,7 +4424,7 @@ function openBrowser(url2, platform = process.platform) {
|
|
|
4167
4424
|
}
|
|
4168
4425
|
|
|
4169
4426
|
// src/lib/connect.ts
|
|
4170
|
-
import { join as
|
|
4427
|
+
import { join as join6 } from "node:path";
|
|
4171
4428
|
|
|
4172
4429
|
// ../shared/src/protocol.ts
|
|
4173
4430
|
var PROTOCOL_VERSION = { major: 10, minor: 1 };
|
|
@@ -19537,23 +19794,6 @@ function sleep(ms) {
|
|
|
19537
19794
|
return new Promise((r) => setTimeout(r, ms));
|
|
19538
19795
|
}
|
|
19539
19796
|
|
|
19540
|
-
// src/lib/paths.ts
|
|
19541
|
-
import { homedir } from "node:os";
|
|
19542
|
-
import { join } from "node:path";
|
|
19543
|
-
function clientHome(env = process.env) {
|
|
19544
|
-
return env.PUDDLE_HOME ?? join(homedir(), ".puddle");
|
|
19545
|
-
}
|
|
19546
|
-
var HOST_HOME = '"${PUDDLE_HOME:-$HOME/.puddle}"';
|
|
19547
|
-
var hostPaths = {
|
|
19548
|
-
home: HOST_HOME,
|
|
19549
|
-
token: `${HOST_HOME}/token`,
|
|
19550
|
-
config: `${HOST_HOME}/config.json`,
|
|
19551
|
-
runtime: `${HOST_HOME}/runtime.json`,
|
|
19552
|
-
current: `${HOST_HOME}/bin/current`,
|
|
19553
|
-
cache: `${HOST_HOME}/cache`,
|
|
19554
|
-
logs: `${HOST_HOME}/logs`
|
|
19555
|
-
};
|
|
19556
|
-
|
|
19557
19797
|
// src/lib/daemon-client.ts
|
|
19558
19798
|
init_types();
|
|
19559
19799
|
var LIVE = /* @__PURE__ */ new Set(["starting", "running", "waiting_input"]);
|
|
@@ -19652,39 +19892,9 @@ async function waitForToken(transport, timeoutMs) {
|
|
|
19652
19892
|
|
|
19653
19893
|
// src/lib/bootstrap.ts
|
|
19654
19894
|
import { existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
19655
|
-
import { dirname as dirname2, join as
|
|
19895
|
+
import { dirname as dirname2, join as join4, resolve } from "node:path";
|
|
19656
19896
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
19657
19897
|
init_types();
|
|
19658
|
-
|
|
19659
|
-
// src/lib/version.ts
|
|
19660
|
-
import { readFileSync } from "node:fs";
|
|
19661
|
-
import { dirname, join as join2 } from "node:path";
|
|
19662
|
-
import { fileURLToPath } from "node:url";
|
|
19663
|
-
function cliVersion() {
|
|
19664
|
-
if (true) return "0.0.13";
|
|
19665
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
19666
|
-
for (const candidate of [
|
|
19667
|
-
join2(here, "..", "..", "package.json"),
|
|
19668
|
-
join2(here, "..", "package.json")
|
|
19669
|
-
]) {
|
|
19670
|
-
try {
|
|
19671
|
-
const pkg = JSON.parse(readFileSync(candidate, "utf8"));
|
|
19672
|
-
if (pkg.version !== void 0) return pkg.version;
|
|
19673
|
-
} catch {
|
|
19674
|
-
}
|
|
19675
|
-
}
|
|
19676
|
-
return "0.0.0";
|
|
19677
|
-
}
|
|
19678
|
-
function pinnedDaemonVersion() {
|
|
19679
|
-
return cliVersion();
|
|
19680
|
-
}
|
|
19681
|
-
function repoSlug(env = process.env) {
|
|
19682
|
-
if (env.PUDDLE_REPO !== void 0 && env.PUDDLE_REPO !== "") return env.PUDDLE_REPO;
|
|
19683
|
-
if (true) return "PerceptronV/puddle-code";
|
|
19684
|
-
return void 0;
|
|
19685
|
-
}
|
|
19686
|
-
|
|
19687
|
-
// src/lib/bootstrap.ts
|
|
19688
19898
|
async function installedVersion(transport) {
|
|
19689
19899
|
const result = await transport.exec(`readlink ${hostPaths.current}`, { timeoutMs: 15e3 });
|
|
19690
19900
|
if (result.code !== 0) return null;
|
|
@@ -19754,9 +19964,9 @@ function lastLines(text, count = 3) {
|
|
|
19754
19964
|
function readInstallScript() {
|
|
19755
19965
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
19756
19966
|
const candidates = [
|
|
19757
|
-
|
|
19967
|
+
join4(here, "install.sh"),
|
|
19758
19968
|
// dist/index.js → dist/install.sh
|
|
19759
|
-
|
|
19969
|
+
join4(here, "..", "..", "..", "..", "scripts", "install.sh")
|
|
19760
19970
|
// src/lib/ → repo root
|
|
19761
19971
|
];
|
|
19762
19972
|
for (const candidate of candidates) {
|
|
@@ -20004,8 +20214,8 @@ function recoverProxiedPath(referer, requestUrl) {
|
|
|
20004
20214
|
}
|
|
20005
20215
|
|
|
20006
20216
|
// src/lib/serve/static.ts
|
|
20007
|
-
import { createReadStream, existsSync as existsSync2, statSync } from "node:fs";
|
|
20008
|
-
import { extname, join as
|
|
20217
|
+
import { createReadStream as createReadStream2, existsSync as existsSync2, statSync } from "node:fs";
|
|
20218
|
+
import { extname, join as join5, normalize, resolve as resolve2, sep } from "node:path";
|
|
20009
20219
|
var MIME = {
|
|
20010
20220
|
".html": "text/html; charset=utf-8",
|
|
20011
20221
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -20033,14 +20243,14 @@ function createStaticHandler(rootDir) {
|
|
|
20033
20243
|
res.writeHead(404, { "content-type": "text/plain" }).end("not found");
|
|
20034
20244
|
return;
|
|
20035
20245
|
}
|
|
20036
|
-
const requested = normalize(
|
|
20246
|
+
const requested = normalize(join5(root, decoded));
|
|
20037
20247
|
const candidate = requested.startsWith(root + sep) || requested === root ? requested : root;
|
|
20038
20248
|
const isFile = existsSync2(candidate) && statSync(candidate).isFile();
|
|
20039
20249
|
if (!isFile && extname(pathname) !== "") {
|
|
20040
20250
|
res.writeHead(404, { "content-type": "text/plain" }).end("not found");
|
|
20041
20251
|
return;
|
|
20042
20252
|
}
|
|
20043
|
-
const file2 = isFile ? candidate :
|
|
20253
|
+
const file2 = isFile ? candidate : join5(root, "index.html");
|
|
20044
20254
|
if (!existsSync2(file2)) {
|
|
20045
20255
|
res.writeHead(404, { "content-type": "text/plain" }).end("not found");
|
|
20046
20256
|
return;
|
|
@@ -20053,7 +20263,7 @@ function createStaticHandler(rootDir) {
|
|
|
20053
20263
|
res.end();
|
|
20054
20264
|
return;
|
|
20055
20265
|
}
|
|
20056
|
-
|
|
20266
|
+
createReadStream2(file2).pipe(res);
|
|
20057
20267
|
};
|
|
20058
20268
|
}
|
|
20059
20269
|
|
|
@@ -20318,7 +20528,7 @@ async function listen(server, startPort, strict, avoidPort) {
|
|
|
20318
20528
|
}
|
|
20319
20529
|
|
|
20320
20530
|
// src/lib/tunnel.ts
|
|
20321
|
-
import { spawn as
|
|
20531
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
20322
20532
|
init_types();
|
|
20323
20533
|
var RECONNECT_INITIAL_MS = 500;
|
|
20324
20534
|
var RECONNECT_MAX_MS = 1e4;
|
|
@@ -20342,7 +20552,7 @@ async function openTunnel(ssh, remotePort, opts = {}) {
|
|
|
20342
20552
|
let healthTimer = null;
|
|
20343
20553
|
const spawnForward = async (port) => {
|
|
20344
20554
|
if (stopping) return false;
|
|
20345
|
-
const child =
|
|
20555
|
+
const child = spawn3(
|
|
20346
20556
|
sshBinary,
|
|
20347
20557
|
ssh.args("-N", "-L", `${port}:127.0.0.1:${remotePort}`, ssh.host),
|
|
20348
20558
|
{ stdio: ["ignore", "ignore", "inherit"] }
|
|
@@ -20465,7 +20675,7 @@ async function openTunnel(ssh, remotePort, opts = {}) {
|
|
|
20465
20675
|
}
|
|
20466
20676
|
|
|
20467
20677
|
// src/lib/transport/local.ts
|
|
20468
|
-
import { spawn as
|
|
20678
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
20469
20679
|
import { copyFileSync, mkdirSync as mkdirSync2, readFileSync as readFileSync4 } from "node:fs";
|
|
20470
20680
|
import { dirname as dirname4 } from "node:path";
|
|
20471
20681
|
var LocalTransport = class {
|
|
@@ -20473,7 +20683,7 @@ var LocalTransport = class {
|
|
|
20473
20683
|
label = "this machine";
|
|
20474
20684
|
exec(command, opts = {}) {
|
|
20475
20685
|
return new Promise((resolve3) => {
|
|
20476
|
-
const child =
|
|
20686
|
+
const child = spawn4("sh", ["-c", command], { stdio: ["pipe", "pipe", "pipe"] });
|
|
20477
20687
|
let stdout = "";
|
|
20478
20688
|
let stderr = "";
|
|
20479
20689
|
let timer;
|
|
@@ -20520,7 +20730,7 @@ function expandHome(path) {
|
|
|
20520
20730
|
}
|
|
20521
20731
|
|
|
20522
20732
|
// src/lib/transport/ssh.ts
|
|
20523
|
-
import { spawn as
|
|
20733
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
20524
20734
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
20525
20735
|
init_types();
|
|
20526
20736
|
var SshTransport = class {
|
|
@@ -20567,7 +20777,7 @@ var SshTransport = class {
|
|
|
20567
20777
|
*/
|
|
20568
20778
|
open() {
|
|
20569
20779
|
return new Promise((resolve3, reject) => {
|
|
20570
|
-
const child =
|
|
20780
|
+
const child = spawn5(this.ssh, this.args(this.host, "true"), { stdio: "inherit" });
|
|
20571
20781
|
child.on(
|
|
20572
20782
|
"error",
|
|
20573
20783
|
(err) => reject(new CliError("ssh_unreachable", `could not run ${this.ssh}: ${err.message}`))
|
|
@@ -20590,7 +20800,7 @@ var SshTransport = class {
|
|
|
20590
20800
|
isAlive() {
|
|
20591
20801
|
if (!this.hasControlMaster) return Promise.resolve(true);
|
|
20592
20802
|
return new Promise((resolve3) => {
|
|
20593
|
-
const child =
|
|
20803
|
+
const child = spawn5(this.ssh, this.args("-O", "check", this.host), { stdio: "ignore" });
|
|
20594
20804
|
child.on("error", () => resolve3(false));
|
|
20595
20805
|
child.on("close", (code) => resolve3(code === 0));
|
|
20596
20806
|
});
|
|
@@ -20607,7 +20817,7 @@ var SshTransport = class {
|
|
|
20607
20817
|
if (!this.hasControlMaster) return Promise.resolve();
|
|
20608
20818
|
return new Promise((resolve3) => {
|
|
20609
20819
|
const spec = `${localPort}:127.0.0.1:${remotePort}`;
|
|
20610
|
-
const child =
|
|
20820
|
+
const child = spawn5(this.ssh, this.args("-O", "cancel", "-L", spec, this.host), {
|
|
20611
20821
|
stdio: "ignore"
|
|
20612
20822
|
});
|
|
20613
20823
|
child.on("error", () => resolve3());
|
|
@@ -20616,7 +20826,7 @@ var SshTransport = class {
|
|
|
20616
20826
|
}
|
|
20617
20827
|
exec(command, opts = {}) {
|
|
20618
20828
|
return new Promise((resolve3) => {
|
|
20619
|
-
const child =
|
|
20829
|
+
const child = spawn5(this.ssh, this.args(this.host, "--", `sh -c ${shellQuote(command)}`), {
|
|
20620
20830
|
stdio: ["pipe", "pipe", "pipe"]
|
|
20621
20831
|
});
|
|
20622
20832
|
let stdout = "";
|
|
@@ -20651,7 +20861,7 @@ var SshTransport = class {
|
|
|
20651
20861
|
async copyTo(localPath, destPath) {
|
|
20652
20862
|
await this.exec(`mkdir -p $(dirname ${destPath})`, { timeoutMs: 15e3 });
|
|
20653
20863
|
await new Promise((resolve3, reject) => {
|
|
20654
|
-
const child =
|
|
20864
|
+
const child = spawn5(this.scp, [...this.controlArgs, localPath, `${this.host}:${destPath}`], {
|
|
20655
20865
|
stdio: ["ignore", "ignore", "inherit"]
|
|
20656
20866
|
});
|
|
20657
20867
|
child.on("error", reject);
|
|
@@ -20711,7 +20921,7 @@ async function connectRemote(opts) {
|
|
|
20711
20921
|
...opts.onRefreshRequest !== void 0 ? { control: { token: endpoint.token, onRefresh: opts.onRefreshRequest } } : {},
|
|
20712
20922
|
// The store lives on the CLIENT machine — every cockpit here shares it,
|
|
20713
20923
|
// whichever remote daemon each one drives.
|
|
20714
|
-
localSync: { token: endpoint.token, file:
|
|
20924
|
+
localSync: { token: endpoint.token, file: join6(clientHome(), "local-sync.json") }
|
|
20715
20925
|
});
|
|
20716
20926
|
tunnel.onPortChange((port) => ui.setTarget({ host: "127.0.0.1", port }));
|
|
20717
20927
|
const eventCbs = /* @__PURE__ */ new Set();
|
|
@@ -20762,18 +20972,18 @@ async function showLogs(transport, opts) {
|
|
|
20762
20972
|
|
|
20763
20973
|
// src/lib/registry.ts
|
|
20764
20974
|
import { mkdirSync as mkdirSync4, readdirSync, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
20765
|
-
import { join as
|
|
20975
|
+
import { join as join7 } from "node:path";
|
|
20766
20976
|
function cockpitsDir(env) {
|
|
20767
|
-
return
|
|
20977
|
+
return join7(clientHome(env), "cockpits");
|
|
20768
20978
|
}
|
|
20769
20979
|
function slug(target) {
|
|
20770
20980
|
return target.replace(/[^A-Za-z0-9@._-]/g, "_");
|
|
20771
20981
|
}
|
|
20772
20982
|
function cockpitRecordPath(target) {
|
|
20773
|
-
return
|
|
20983
|
+
return join7(cockpitsDir(), `${slug(target)}.json`);
|
|
20774
20984
|
}
|
|
20775
20985
|
function cockpitLogPath(target) {
|
|
20776
|
-
return
|
|
20986
|
+
return join7(clientHome(), "logs", `cockpit-${slug(target)}.log`);
|
|
20777
20987
|
}
|
|
20778
20988
|
function writeCockpitRecord(record2) {
|
|
20779
20989
|
mkdirSync4(cockpitsDir(), { recursive: true, mode: 448 });
|
|
@@ -20802,7 +21012,7 @@ function listCockpitRecords() {
|
|
|
20802
21012
|
for (const name of names) {
|
|
20803
21013
|
if (!name.endsWith(".json")) continue;
|
|
20804
21014
|
try {
|
|
20805
|
-
records.push(JSON.parse(readFileSync5(
|
|
21015
|
+
records.push(JSON.parse(readFileSync5(join7(cockpitsDir(), name), "utf8")));
|
|
20806
21016
|
} catch {
|
|
20807
21017
|
}
|
|
20808
21018
|
}
|
|
@@ -20834,7 +21044,7 @@ async function checkCockpit(record2) {
|
|
|
20834
21044
|
}
|
|
20835
21045
|
|
|
20836
21046
|
// src/lib/start.ts
|
|
20837
|
-
import { join as
|
|
21047
|
+
import { join as join8 } from "node:path";
|
|
20838
21048
|
init_types();
|
|
20839
21049
|
async function startLocal(opts) {
|
|
20840
21050
|
const logger = opts.logger ?? silentLogger;
|
|
@@ -20857,7 +21067,7 @@ async function startLocal(opts) {
|
|
|
20857
21067
|
// never squat the daemon's own port
|
|
20858
21068
|
target: { host: "127.0.0.1", port: endpoint.port },
|
|
20859
21069
|
...opts.onRefreshRequest !== void 0 ? { control: { token: endpoint.token, onRefresh: opts.onRefreshRequest } } : {},
|
|
20860
|
-
localSync: { token: endpoint.token, file:
|
|
21070
|
+
localSync: { token: endpoint.token, file: join8(clientHome(), "local-sync.json") }
|
|
20861
21071
|
});
|
|
20862
21072
|
const eventCbs = /* @__PURE__ */ new Set();
|
|
20863
21073
|
return {
|
|
@@ -20919,7 +21129,7 @@ async function upgradeDaemon(transport, opts = {}) {
|
|
|
20919
21129
|
}
|
|
20920
21130
|
|
|
20921
21131
|
// src/cli/detach.ts
|
|
20922
|
-
import { spawn as
|
|
21132
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
20923
21133
|
import { closeSync, mkdirSync as mkdirSync5, openSync, readFileSync as readFileSync6 } from "node:fs";
|
|
20924
21134
|
import { dirname as dirname5 } from "node:path";
|
|
20925
21135
|
init_types();
|
|
@@ -20965,7 +21175,7 @@ async function launchDetached(opts) {
|
|
|
20965
21175
|
mkdirSync5(dirname5(logFile), { recursive: true });
|
|
20966
21176
|
const fd = openSync(logFile, "w");
|
|
20967
21177
|
const argv = opts.argv ?? process.argv.slice(2);
|
|
20968
|
-
const child =
|
|
21178
|
+
const child = spawn6(process.execPath, [process.argv[1] ?? "", ...argv], {
|
|
20969
21179
|
detached: true,
|
|
20970
21180
|
stdio: ["ignore", fd, fd],
|
|
20971
21181
|
env: { ...process.env, [COCKPIT_CHILD_ENV]: "1" }
|
|
@@ -21031,7 +21241,7 @@ function spawnDetachedRefresh(target, argv) {
|
|
|
21031
21241
|
const fd = openSync(logFile, "a");
|
|
21032
21242
|
const env = { ...process.env };
|
|
21033
21243
|
delete env[COCKPIT_CHILD_ENV];
|
|
21034
|
-
const child =
|
|
21244
|
+
const child = spawn6(process.execPath, [process.argv[1] ?? "", ...argv], {
|
|
21035
21245
|
detached: true,
|
|
21036
21246
|
stdio: ["ignore", fd, fd],
|
|
21037
21247
|
env
|
|
@@ -21085,8 +21295,8 @@ function timestampedLogger() {
|
|
|
21085
21295
|
// src/cli/run.ts
|
|
21086
21296
|
function assetsDir() {
|
|
21087
21297
|
const here = dirname6(fileURLToPath3(import.meta.url));
|
|
21088
|
-
for (const candidate of [
|
|
21089
|
-
if (existsSync3(
|
|
21298
|
+
for (const candidate of [join9(here, "public"), join9(here, "..", "..", "dist", "public")]) {
|
|
21299
|
+
if (existsSync3(join9(candidate, "index.html"))) return candidate;
|
|
21090
21300
|
}
|
|
21091
21301
|
throw new CliError(
|
|
21092
21302
|
"not_installed",
|
|
@@ -21102,7 +21312,7 @@ async function openTarget(host) {
|
|
|
21102
21312
|
throw new CliError(
|
|
21103
21313
|
"not_installed",
|
|
21104
21314
|
`no puddle daemon is set up on ${transport.label}`,
|
|
21105
|
-
host === void 0 ? "run: puddle
|
|
21315
|
+
host === void 0 ? "run: puddle launch" : `run: puddle launch ${host}`
|
|
21106
21316
|
);
|
|
21107
21317
|
}
|
|
21108
21318
|
const daemonPort = await readDaemonPort(transport);
|
|
@@ -21141,7 +21351,7 @@ ${farewell}
|
|
|
21141
21351
|
});
|
|
21142
21352
|
});
|
|
21143
21353
|
}
|
|
21144
|
-
async function
|
|
21354
|
+
async function run2(command) {
|
|
21145
21355
|
const logger = terminalLogger();
|
|
21146
21356
|
switch (command.cmd) {
|
|
21147
21357
|
case "help":
|
|
@@ -21151,9 +21361,8 @@ async function run(command) {
|
|
|
21151
21361
|
process.stdout.write(`puddle ${cliVersion()}
|
|
21152
21362
|
`);
|
|
21153
21363
|
return 0;
|
|
21154
|
-
case "
|
|
21155
|
-
|
|
21156
|
-
const target = command.cmd === "connect" ? command.host : "local";
|
|
21364
|
+
case "launch": {
|
|
21365
|
+
const target = command.target;
|
|
21157
21366
|
if (!command.foreground && !isCockpitChild()) {
|
|
21158
21367
|
return launchDetached({ target, noBrowser: command.noBrowser, logger });
|
|
21159
21368
|
}
|
|
@@ -21203,11 +21412,11 @@ async function run(command) {
|
|
|
21203
21412
|
noUpgrade: command.noUpgrade,
|
|
21204
21413
|
foreground: command.foreground
|
|
21205
21414
|
};
|
|
21206
|
-
const next =
|
|
21207
|
-
cmd: "
|
|
21208
|
-
|
|
21415
|
+
const next = {
|
|
21416
|
+
cmd: "launch",
|
|
21417
|
+
target,
|
|
21209
21418
|
...shared,
|
|
21210
|
-
...command.remotePort !== void 0 ? { remotePort: command.remotePort } : {}
|
|
21419
|
+
...target !== "local" && command.remotePort !== void 0 ? { remotePort: command.remotePort } : {}
|
|
21211
21420
|
};
|
|
21212
21421
|
if (!command.foreground && !isCockpitChild()) {
|
|
21213
21422
|
return launchDetached({
|
|
@@ -21370,18 +21579,88 @@ async function run(command) {
|
|
|
21370
21579
|
}
|
|
21371
21580
|
}
|
|
21372
21581
|
case "upgrade": {
|
|
21373
|
-
const
|
|
21374
|
-
if (
|
|
21375
|
-
|
|
21376
|
-
|
|
21377
|
-
|
|
21378
|
-
|
|
21379
|
-
|
|
21380
|
-
|
|
21582
|
+
const local = command.host === void 0 || command.host === "local";
|
|
21583
|
+
if (command.what === "daemon") {
|
|
21584
|
+
const transport = local ? new LocalTransport() : new SshTransport(command.host);
|
|
21585
|
+
if (transport instanceof SshTransport) await transport.open();
|
|
21586
|
+
try {
|
|
21587
|
+
const result = await upgradeDaemon(transport, { logger });
|
|
21588
|
+
logger.info(
|
|
21589
|
+
`puddled ${result.from ?? "(unmanaged)"} \u2192 ${result.to} on ${transport.label}`
|
|
21590
|
+
);
|
|
21591
|
+
return 0;
|
|
21592
|
+
} finally {
|
|
21593
|
+
transport.dispose();
|
|
21594
|
+
}
|
|
21381
21595
|
}
|
|
21596
|
+
if (!local) {
|
|
21597
|
+
throw new CliError(
|
|
21598
|
+
"bad_arguments",
|
|
21599
|
+
`puddle upgrade ${command.what} runs on the machine it upgrades`,
|
|
21600
|
+
`ssh into ${command.host} and run it there \u2014 user@host targets the daemon only`
|
|
21601
|
+
);
|
|
21602
|
+
}
|
|
21603
|
+
return command.what === "cli" ? upgradeCli(logger) : upgradeDesktop(logger);
|
|
21382
21604
|
}
|
|
21383
21605
|
}
|
|
21384
21606
|
}
|
|
21607
|
+
function upgradeCli(logger) {
|
|
21608
|
+
logger.info(`puddle CLI ${cliVersion()} \u2014 asking npm for the latest release`);
|
|
21609
|
+
return new Promise((resolve3, reject) => {
|
|
21610
|
+
const child = spawn7("npm", ["install", "-g", "@puddle-code/cli@latest"], {
|
|
21611
|
+
stdio: "inherit"
|
|
21612
|
+
});
|
|
21613
|
+
child.on(
|
|
21614
|
+
"error",
|
|
21615
|
+
() => reject(
|
|
21616
|
+
new CliError(
|
|
21617
|
+
"not_installed",
|
|
21618
|
+
"npm is not on PATH",
|
|
21619
|
+
"the CLI is installed and upgraded via npm: npm install -g @puddle-code/cli@latest"
|
|
21620
|
+
)
|
|
21621
|
+
)
|
|
21622
|
+
);
|
|
21623
|
+
child.on("exit", (code) => {
|
|
21624
|
+
if (code === 0) {
|
|
21625
|
+
logger.info("done \u2014 `puddle --version` shows the installed version");
|
|
21626
|
+
resolve3(0);
|
|
21627
|
+
} else {
|
|
21628
|
+
reject(new CliError("not_installed", `npm install exited with ${code ?? "a signal"}`));
|
|
21629
|
+
}
|
|
21630
|
+
});
|
|
21631
|
+
});
|
|
21632
|
+
}
|
|
21633
|
+
async function upgradeDesktop(logger) {
|
|
21634
|
+
const installed = await findInstalledDesktopApp();
|
|
21635
|
+
if (installed === null) {
|
|
21636
|
+
throw new CliError(
|
|
21637
|
+
"not_installed",
|
|
21638
|
+
"no Puddle.app in /Applications or ~/Applications",
|
|
21639
|
+
process.platform === "linux" ? "AppImages carry no fixed path \u2014 update from the app itself (its banner knows $APPIMAGE)" : void 0
|
|
21640
|
+
);
|
|
21641
|
+
}
|
|
21642
|
+
if (await isDesktopAppRunning(installed.appPath)) {
|
|
21643
|
+
throw new CliError(
|
|
21644
|
+
"already_running",
|
|
21645
|
+
"Puddle is running \u2014 use its update banner, or quit it and rerun"
|
|
21646
|
+
);
|
|
21647
|
+
}
|
|
21648
|
+
const update = await checkForDesktopUpdate(installed.version);
|
|
21649
|
+
if (update === null) {
|
|
21650
|
+
logger.info(`Puddle ${installed.version} is already the latest release`);
|
|
21651
|
+
return 0;
|
|
21652
|
+
}
|
|
21653
|
+
logger.info(`Puddle ${installed.version} \u2192 ${update.version}`);
|
|
21654
|
+
const staged = await stageDesktopUpdate(update, { logger });
|
|
21655
|
+
await applyDesktopUpdate(staged, {
|
|
21656
|
+
targetPath: installed.appPath,
|
|
21657
|
+
detach: false,
|
|
21658
|
+
relaunch: false,
|
|
21659
|
+
logger
|
|
21660
|
+
});
|
|
21661
|
+
logger.info(`Puddle ${update.version} installed at ${installed.appPath}`);
|
|
21662
|
+
return 0;
|
|
21663
|
+
}
|
|
21385
21664
|
async function runCockpit(command, target, terminal) {
|
|
21386
21665
|
const detached = isCockpitChild();
|
|
21387
21666
|
const logger = detached ? timestampedLogger() : terminal;
|
|
@@ -21413,14 +21692,15 @@ async function runCockpit(command, target, terminal) {
|
|
|
21413
21692
|
"--no-browser",
|
|
21414
21693
|
// the requesting tab reloads itself; no second tab
|
|
21415
21694
|
...command.port !== void 0 ? ["--port", String(command.port)] : [],
|
|
21416
|
-
...command.
|
|
21695
|
+
...command.remotePort !== void 0 ? ["--remote-port", String(command.remotePort)] : [],
|
|
21417
21696
|
...command.tarball !== void 0 ? ["--tarball", command.tarball] : [],
|
|
21418
21697
|
...command.noUpgrade ? ["--no-upgrade"] : []
|
|
21419
21698
|
]);
|
|
21420
21699
|
};
|
|
21421
21700
|
let cockpit;
|
|
21422
21701
|
try {
|
|
21423
|
-
|
|
21702
|
+
const common = { ...command, assetsDir: assetsDir(), logger, onRefreshRequest };
|
|
21703
|
+
cockpit = command.target === "local" ? await startLocal(common) : await connectRemote({ ...common, host: command.target });
|
|
21424
21704
|
} catch (err) {
|
|
21425
21705
|
if (err instanceof CliError) {
|
|
21426
21706
|
writeCockpitRecord({
|
|
@@ -21441,13 +21721,13 @@ async function runCockpit(command, target, terminal) {
|
|
|
21441
21721
|
browserUrl: cockpit.browserUrl,
|
|
21442
21722
|
nonce: cockpit.nonce
|
|
21443
21723
|
});
|
|
21444
|
-
const arrow = command.
|
|
21724
|
+
const arrow = command.target === "local" ? "" : ` \u2192 ${command.target}`;
|
|
21445
21725
|
logger.info(`puddle cockpit at ${cockpit.origin}${arrow} (daemon ${cockpit.daemon.version})`);
|
|
21446
21726
|
if (!detached) {
|
|
21447
21727
|
if (!command.noBrowser) openBrowser(cockpit.browserUrl);
|
|
21448
21728
|
logger.info(`open: ${cockpit.browserUrl}`);
|
|
21449
21729
|
}
|
|
21450
|
-
const where = command.
|
|
21730
|
+
const where = command.target === "local" ? "this machine" : command.target;
|
|
21451
21731
|
await runUntilInterrupted(async () => {
|
|
21452
21732
|
await cockpit.stop();
|
|
21453
21733
|
removeCockpitRecord(target);
|
|
@@ -21462,7 +21742,7 @@ function pad(text, width) {
|
|
|
21462
21742
|
init_types();
|
|
21463
21743
|
try {
|
|
21464
21744
|
const command = parseArgs(process.argv.slice(2));
|
|
21465
|
-
process.exit(await
|
|
21745
|
+
process.exit(await run2(command));
|
|
21466
21746
|
} catch (err) {
|
|
21467
21747
|
if (err instanceof CliError) {
|
|
21468
21748
|
process.stderr.write(`puddle: ${err.message}
|