skydive-cli 0.6.0-beta.34 → 0.6.0-beta.36

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.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { t as PortalClient } from "./client-DCrQwQV7.mjs";
3
+ import "./api-BFQ4PQDA.mjs";
4
+
5
+ export { PortalClient };
@@ -160,7 +160,7 @@ function errorMessage(err) {
160
160
  * the commit time of the built tree is one monotonic clock they share.
161
161
  */
162
162
  function portalDaemonBuild() {
163
- return "1790279396";
163
+ return "1790359164";
164
164
  }
165
165
  /**
166
166
  * Whether a client carrying `mine` should replace a running daemon carrying
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import "./client-BKr7GJE0.mjs";
2
+ import "./client-DCrQwQV7.mjs";
3
3
  import "./tls-cert-Rua2oV7n.mjs";
4
4
  import "./api-BFQ4PQDA.mjs";
5
- import { a as runPortalDaemon, c as stopDaemonForHandoff, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-DZqfbEqh.mjs";
5
+ import { a as runPortalDaemon, c as stopDaemonForHandoff, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-CXnHsea6.mjs";
6
6
 
7
7
  export { runPortalDaemon };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as isRecord, n as isNewerBuild, r as portalDaemonBuild, t as PortalClient } from "./client-BKr7GJE0.mjs";
2
+ import { i as isRecord, n as isNewerBuild, r as portalDaemonBuild, t as PortalClient } from "./client-DCrQwQV7.mjs";
3
3
  import { t as defaultTlsCertSource } from "./tls-cert-Rua2oV7n.mjs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
@@ -66,6 +66,18 @@ function canonicalPortalAppUrl(appUrl) {
66
66
  if (host === "skydive.com" || host === "www.skydive.com" || host === "api.skydive.com") return "https://api.skydive.com";
67
67
  return trimmed;
68
68
  }
69
+ /** Prefix every Windows named pipe lives under (the pipe filesystem root). */
70
+ const WINDOWS_PIPE_PREFIX = "\\\\.\\pipe\\";
71
+ /**
72
+ * Whether a daemon control-socket path is a Windows named pipe rather than a
73
+ * Unix-domain-socket FILE. Pipes have no on-disk file: they cannot be
74
+ * `stat`ed, `unlink`ed, or purged by a tmp cleaner, and the OS frees the name
75
+ * the moment the owning server closes — so every file-based lifecycle step
76
+ * (stale-file cleanup, the socket-ownership watch) must be skipped for them.
77
+ */
78
+ function isNamedPipePath(socketPath) {
79
+ return socketPath.startsWith(WINDOWS_PIPE_PREFIX);
80
+ }
69
81
  /**
70
82
  * Per-user, per-app daemon socket + state paths. Scoped by OS user and a hash
71
83
  * of the CANONICAL appUrl so two logins or two deployments (prod vs a PR
@@ -74,6 +86,12 @@ function canonicalPortalAppUrl(appUrl) {
74
86
  * one. Kept under the OS temp dir because Unix socket paths are
75
87
  * length-limited (~104 bytes on macOS).
76
88
  *
89
+ * On Windows a `node:net` server cannot listen on a filesystem path at all —
90
+ * binding the tmp-dir path fails with EACCES and the daemon dies silently
91
+ * (ANY-7285's Windows facet) — so the socket is a named pipe under
92
+ * `\\.\pipe\`, keyed by the same (user, appUrl-hash) pair. State and log
93
+ * files stay under the temp dir on every platform.
94
+ *
77
95
  * Darwin `sockaddr_un.sun_path` is 104 bytes including NUL (103 usable).
78
96
  * `os.tmpdir()` on macOS is `$TMPDIR` (`/var/folders/<id>/T`, often ~50
79
97
  * chars). Adding `skydive-portal-<user>-<hash>/daemon.sock` overruns that
@@ -84,18 +102,17 @@ function canonicalPortalAppUrl(appUrl) {
84
102
  * sun_path, so they keep the long leaf.
85
103
  */
86
104
  const SUN_PATH_MAX = 103;
87
- function daemonPaths(appUrl) {
105
+ function daemonPaths(appUrl, platform = process.platform) {
88
106
  const parent = os.tmpdir();
89
- const user = safeSegment(os.userInfo().username || "user");
90
- const app = createHash("sha256").update(canonicalPortalAppUrl(appUrl)).digest("hex").slice(0, 12);
91
- const dir = path.join(parent, `skydive-portal-${user}-${app}`);
107
+ const key = `skydive-portal-${safeSegment(os.userInfo().username || "user")}-${createHash("sha256").update(canonicalPortalAppUrl(appUrl)).digest("hex").slice(0, 12)}`;
108
+ const dir = path.join(parent, key);
92
109
  const paths = {
93
110
  dir,
94
- socketPath: path.join(dir, "daemon.sock"),
111
+ socketPath: platform === "win32" ? `${WINDOWS_PIPE_PREFIX}${key}` : path.join(dir, "daemon.sock"),
95
112
  statePath: path.join(dir, "state.json"),
96
113
  logPath: path.join(dir, "daemon.log")
97
114
  };
98
- if (process.platform === "win32" || Buffer.byteLength(paths.socketPath) <= SUN_PATH_MAX) return paths;
115
+ if (platform === "win32" || Buffer.byteLength(paths.socketPath) <= SUN_PATH_MAX) return paths;
99
116
  const short = createHash("sha256").update(`${os.userInfo().username || "user"}\0${canonicalPortalAppUrl(appUrl)}`).digest("hex").slice(0, 12);
100
117
  const shortDir = path.join(parent, `skydive-p-${short}`);
101
118
  return {
@@ -412,8 +429,10 @@ var PortalDaemon = class {
412
429
  resolve();
413
430
  });
414
431
  });
415
- await this.recordSocketIdentity();
416
- this.startSocketWatch();
432
+ if (!isNamedPipePath(this.paths.socketPath)) {
433
+ await this.recordSocketIdentity();
434
+ this.startSocketWatch();
435
+ }
417
436
  }
418
437
  /**
419
438
  * Remember which socket FILE we bound (dev+ino), so the watch can tell "our
@@ -510,6 +529,10 @@ var PortalDaemon = class {
510
529
  this.exit();
511
530
  }
512
531
  async clearStaleSocket() {
532
+ if (isNamedPipePath(this.paths.socketPath)) {
533
+ if (await isDaemonListening(this.paths.socketPath)) throw new Error("daemon already running");
534
+ return;
535
+ }
513
536
  if (!await pathExists(this.paths.socketPath)) return;
514
537
  if (await new Promise((resolve) => {
515
538
  const probe = connect(this.paths.socketPath);
@@ -806,13 +829,19 @@ var PortalDaemon = class {
806
829
  * drains attached clients, who reconnect within ~500ms) and spawn from this
807
830
  * binary. Both spawners apply the same rule, so a host converges on the
808
831
  * newest installed build with at most one bounce.
832
+ *
833
+ * Returns whether a daemon is actually listening when it's done. A spawned
834
+ * daemon that never binds (its process died on startup — e.g. the Windows
835
+ * EACCES bind failure this used to swallow) resolves `false` so callers can
836
+ * SAY so, instead of silently handing the caller a socket nobody serves and
837
+ * leaving the reconnect loop to spin forever with no visible error.
809
838
  */
810
839
  async function ensureDaemonRunning(appUrl) {
811
840
  const { socketPath } = daemonPaths(appUrl);
812
841
  if (await isDaemonListening(socketPath)) {
813
842
  const status = await queryDaemonStatus(appUrl);
814
- if (!status || !isNewerBuild(portalDaemonBuild(), status.build)) return;
815
- if (await stopDaemonForHandoff(appUrl) === "failed") return;
843
+ if (!status || !isNewerBuild(portalDaemonBuild(), status.build)) return true;
844
+ if (await stopDaemonForHandoff(appUrl) === "failed") return true;
816
845
  }
817
846
  const entry = process.argv[1];
818
847
  const args = entry ? [
@@ -826,9 +855,10 @@ async function ensureDaemonRunning(appUrl) {
826
855
  env: process.env
827
856
  }).unref();
828
857
  for (let i = 0; i < 50; i += 1) {
829
- if (await isDaemonListening(socketPath)) return;
858
+ if (await isDaemonListening(socketPath)) return true;
830
859
  await sleep(100);
831
860
  }
861
+ return false;
832
862
  }
833
863
  /**
834
864
  * Entry the CLI calls when launched with PORTAL_DAEMON_FLAG: become the daemon.
@@ -849,7 +879,7 @@ function startPortalDaemon(appUrl) {
849
879
  return new PortalDaemon(appUrl, null).listen();
850
880
  }
851
881
  async function isDaemonListening(socketPath) {
852
- if (!await pathExists(socketPath)) return false;
882
+ if (!isNamedPipePath(socketPath) && !await pathExists(socketPath)) return false;
853
883
  return new Promise((resolve) => {
854
884
  const probe = connect(socketPath);
855
885
  probe.on("connect", () => {
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import "./client-DCrQwQV7.mjs";
3
+ import "./tls-cert-Rua2oV7n.mjs";
4
+ import "./api-BFQ4PQDA.mjs";
5
+ import "./daemon-CXnHsea6.mjs";
6
+ import { t as PortalDaemonClient } from "./daemon-client-n5zNHAld.mjs";
7
+
8
+ export { PortalDaemonClient };
@@ -1,14 +1,21 @@
1
1
  #!/usr/bin/env node
2
- import { d as daemonPaths, f as encodeLine, l as LOCAL_PROTOCOL_VERSION, m as parseDaemonMessage, n as ensureDaemonRunning, p as makeLineParser } from "./daemon-DZqfbEqh.mjs";
2
+ import { d as daemonPaths, f as encodeLine, l as LOCAL_PROTOCOL_VERSION, m as parseDaemonMessage, n as ensureDaemonRunning, p as makeLineParser } from "./daemon-CXnHsea6.mjs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { connect } from "node:net";
5
5
 
6
6
  //#region ../portal-daemon/src/daemon-client.ts
7
+ /** Reconnect delay when an established daemon connection drops. */
8
+ const RECONNECT_DELAY_MS = 500;
9
+ /** Base delay between daemon SPAWN retries (doubles per consecutive failure). */
10
+ const START_RETRY_BASE_MS = 2e3;
11
+ /** Consecutive failed spawns before start() stops respawning. */
12
+ const MAX_START_ATTEMPTS = 3;
7
13
  var PortalDaemonClient = class {
8
14
  sessionId = randomUUID();
9
15
  socket = null;
10
16
  disposed = false;
11
17
  reconnectTimer = null;
18
+ startFailures = 0;
12
19
  lastBind = null;
13
20
  fallbackCwd = null;
14
21
  wantEnabled = false;
@@ -21,7 +28,21 @@ var PortalDaemonClient = class {
21
28
  /** Spawn the daemon if needed and attach. Idempotent. */
22
29
  async start() {
23
30
  if (this.disposed) return;
24
- await ensureDaemonRunning(this.opts.appUrl);
31
+ if (!await ensureDaemonRunning(this.opts.appUrl)) {
32
+ this.startFailures += 1;
33
+ const exhausted = this.startFailures >= MAX_START_ATTEMPTS;
34
+ this.opts.onState({
35
+ status: "error",
36
+ machineName: "",
37
+ friendlyName: "",
38
+ error: exhausted ? "portal daemon failed to start repeatedly; toggle sharing to retry" : "portal daemon failed to start (control socket never came up)",
39
+ grantedAgentIds: [],
40
+ declinedAgentIds: []
41
+ });
42
+ if (!exhausted) this.scheduleReconnect(START_RETRY_BASE_MS * 2 ** (this.startFailures - 1));
43
+ return;
44
+ }
45
+ this.startFailures = 0;
25
46
  this.connect();
26
47
  }
27
48
  connect() {
@@ -74,12 +95,12 @@ var PortalDaemonClient = class {
74
95
  socket.on("close", onClose);
75
96
  socket.on("error", onClose);
76
97
  }
77
- scheduleReconnect() {
98
+ scheduleReconnect(delayMs = RECONNECT_DELAY_MS) {
78
99
  if (this.disposed || this.reconnectTimer) return;
79
100
  this.reconnectTimer = setTimeout(() => {
80
101
  this.reconnectTimer = null;
81
102
  this.start();
82
- }, 500);
103
+ }, delayMs);
83
104
  this.reconnectTimer.unref();
84
105
  }
85
106
  onMessage(msg) {
@@ -109,6 +130,10 @@ var PortalDaemonClient = class {
109
130
  /** Turn machine sharing on (the daemon connects the portal). */
110
131
  enable() {
111
132
  this.wantEnabled = true;
133
+ if (this.startFailures >= MAX_START_ATTEMPTS) {
134
+ this.startFailures = 0;
135
+ this.start();
136
+ }
112
137
  this.send({ t: "enable" });
113
138
  }
114
139
  /** Turn machine sharing off. */
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { c as version$1 } from "./rest-M6NtmtbL.mjs";
3
- import { E as getConfigPath } from "./print-DK-FfPei.mjs";
2
+ import { c as version$1 } from "./rest-CM76e6UD.mjs";
3
+ import { E as getConfigPath } from "./print-BDW65Ro4.mjs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { err, ok } from "neverthrow";
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as errorMessage, i as sendErrorMessage, o as isRecord, t as createRestClient } from "./rest-M6NtmtbL.mjs";
2
+ import { a as errorMessage, i as sendErrorMessage, o as isRecord, t as createRestClient } from "./rest-CM76e6UD.mjs";
3
3
  import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
4
4
  import { i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError } from "./billing-blocked-D3l5kJlX.mjs";
5
5
  import path from "node:path";
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import "./rest-M6NtmtbL.mjs";
3
- import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-DK-FfPei.mjs";
2
+ import "./rest-CM76e6UD.mjs";
3
+ import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-BDW65Ro4.mjs";
4
4
  import "./billing-blocked-D3l5kJlX.mjs";
5
5
 
6
6
  export { messageGet, readStdin, resolveAgent, runPrint };
@@ -14,7 +14,7 @@ import { n as printError } from "./output-C9mb3sUB.mjs";
14
14
  * the reply (and to --json).
15
15
  */
16
16
  async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
17
- const { PortalClient } = await import("./client-Dcw8Wl3E.mjs");
17
+ const { PortalClient } = await import("./client-CM--T5Fy.mjs");
18
18
  const { defaultTlsCertSource } = await import("./tls-cert-BpCaD5AT.mjs");
19
19
  let signalConnected;
20
20
  const connected = new Promise((resolve) => {
@@ -6,7 +6,7 @@ import { createParser } from "eventsource-parser";
6
6
 
7
7
  //#region package.json
8
8
  var name = "skydive-cli";
9
- var version = "0.6.0-beta.34";
9
+ var version = "0.6.0-beta.36";
10
10
 
11
11
  //#endregion
12
12
  //#region src/chat/util.ts
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as sendErrorMessage, n as errorDetail, r as filenameFromContentDisposition, t as createRestClient } from "./rest-M6NtmtbL.mjs";
2
+ import { i as sendErrorMessage, n as errorDetail, r as filenameFromContentDisposition, t as createRestClient } from "./rest-CM76e6UD.mjs";
3
3
  import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
4
4
  import "./billing-blocked-D3l5kJlX.mjs";
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skydive-cli",
3
- "version": "0.6.0-beta.34",
3
+ "version": "0.6.0-beta.36",
4
4
  "description": "Skydive CLI — cloud agents from the command line",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",
@@ -71,9 +71,9 @@
71
71
  },
72
72
  "//optionalDependencies": "The per-platform binary packages (skydive-cli-<os>-<arch>) are INTENTIONALLY not committed here. They are published by release-skydive-cli-binaries.yml and injected — pinned to the exact version being published — into the published launcher's optionalDependencies at release time (scripts/prepare-platform-packages.mjs --pin-version, run in release-skydive-cli.yml). Committing them would make `yarn install` try to resolve versions that only exist post-publish, breaking local dev and CI. npm installs of a published skydive-cli still get them (os/cpu-gated); the launcher (src/launcher.ts) resolves and execs the matching one. Local dev never needs them: it runs the JS bundle or `build:binary` directly.",
73
73
  "optionalDependencies": {
74
- "skydive-cli-darwin-arm64": "0.6.0-beta.34",
75
- "skydive-cli-darwin-x64": "0.6.0-beta.34",
76
- "skydive-cli-linux-x64": "0.6.0-beta.34",
77
- "skydive-cli-linux-arm64": "0.6.0-beta.34"
74
+ "skydive-cli-darwin-arm64": "0.6.0-beta.36",
75
+ "skydive-cli-darwin-x64": "0.6.0-beta.36",
76
+ "skydive-cli-linux-x64": "0.6.0-beta.36",
77
+ "skydive-cli-linux-arm64": "0.6.0-beta.36"
78
78
  }
79
79
  }
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- import { t as PortalClient } from "./client-BKr7GJE0.mjs";
3
- import "./api-BFQ4PQDA.mjs";
4
-
5
- export { PortalClient };
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env node
2
- import "./client-BKr7GJE0.mjs";
3
- import "./tls-cert-Rua2oV7n.mjs";
4
- import "./api-BFQ4PQDA.mjs";
5
- import "./daemon-DZqfbEqh.mjs";
6
- import { t as PortalDaemonClient } from "./daemon-client-pcfcfzGs.mjs";
7
-
8
- export { PortalDaemonClient };