skydive-cli 0.5.0 → 0.6.0-beta.100
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 +15 -0
- package/dist/js/bin.mjs +867 -89
- package/dist/js/{boot-DD4T-61U.mjs → boot--2bLAufE.mjs} +11404 -9140
- package/dist/js/client-BfwRLRDF.mjs +5 -0
- package/dist/js/{client-Btq6bMzX.mjs → client-Cxk3khQu.mjs} +40 -5
- package/dist/js/{client--k9cjfkX.mjs → client-HdxGBXPK.mjs} +98 -11
- package/dist/js/{daemon-CCgNLD0H.mjs → daemon--Yp4BWJq.mjs} +2 -2
- package/dist/js/{daemon-Do1jU2UF.mjs → daemon-B8c3Z1Te.mjs} +228 -30
- package/dist/js/daemon-client-5ZIZn3qg.mjs +8 -0
- package/dist/js/{daemon-client-Dvad009G.mjs → daemon-client-xaS4xE-L.mjs} +29 -4
- package/dist/js/{install-CtAVvERm.mjs → install-ZUWt-WGX.mjs} +49 -3
- package/dist/js/{print-D_UEjdSw.mjs → print-B_jFXZ1W.mjs} +71 -10
- package/dist/js/{print-Bx8qUC9U.mjs → print-CIzEvzQk.mjs} +2 -2
- package/dist/js/{print-share-Cz0EO2RK.mjs → print-share-Cl9t3Etr.mjs} +1 -1
- package/dist/js/{raw-pty-D5PhKZSl.mjs → raw-pty-B-WKaaE3.mjs} +1 -1
- package/dist/js/raw-pty-BJIs_xjQ.mjs +5 -0
- package/dist/js/{rest-Dc0EEok3.mjs → rest-83NiD7BB.mjs} +1 -1
- package/dist/js/{rest-B9__Zsuk.mjs → rest-YroXUA7G.mjs} +89 -6
- package/package.json +10 -8
- package/dist/js/client-Ct0-JZSS.mjs +0 -5
- package/dist/js/daemon-client-C7nE-lLK.mjs +0 -8
- package/dist/js/raw-pty-Ci2qFR9F.mjs +0 -5
|
@@ -91,7 +91,7 @@ function parseFrame(expectedType, frameBytes) {
|
|
|
91
91
|
const parsed = jsonLen ? JSON.parse(textDecoder.decode(frameBytes.subarray(jsonStart, jsonEnd))) : {};
|
|
92
92
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
93
93
|
json = parsed;
|
|
94
|
-
} catch {
|
|
94
|
+
} catch (_error) {
|
|
95
95
|
return null;
|
|
96
96
|
}
|
|
97
97
|
return {
|
|
@@ -113,6 +113,18 @@ const FRAME = {
|
|
|
113
113
|
RESIZE: 17
|
|
114
114
|
};
|
|
115
115
|
const MAX_INPUT_BYTES = 1 * 1024 * 1024;
|
|
116
|
+
/** Upper bound for an exec's `timeoutMs`; larger asks are clamped to this. */
|
|
117
|
+
const MAX_EXEC_TIMEOUT_MS = 1440 * 60 * 1e3;
|
|
118
|
+
/**
|
|
119
|
+
* The exec budget applied when the client sends no `timeoutMs` of its own.
|
|
120
|
+
* Liberal by design — one-shot execs legitimately run installs, builds, and
|
|
121
|
+
* long data pulls — but not absent: without it, the e2b SDK's own default
|
|
122
|
+
* (60s) silently kills anything longer mid-run with an opaque
|
|
123
|
+
* `deadline_exceeded`, which is far below what the CLI's `--timeout` help
|
|
124
|
+
* promises ("the server default (~10 min) applies"). Callers with a bigger
|
|
125
|
+
* budget send an explicit `timeoutMs` (clamped to {@link MAX_EXEC_TIMEOUT_MS}).
|
|
126
|
+
*/
|
|
127
|
+
const DEFAULT_EXEC_TIMEOUT_MS = 600 * 1e3;
|
|
116
128
|
/** Query params for the upgrade URL, from a spec. Inverse of {@link parseStreamSpec}. */
|
|
117
129
|
function streamSpecToQuery(spec) {
|
|
118
130
|
if (spec.mode === "pty") return {
|
|
@@ -124,7 +136,8 @@ function streamSpecToQuery(spec) {
|
|
|
124
136
|
if (spec.mode === "exec") return {
|
|
125
137
|
agentId: spec.agentId,
|
|
126
138
|
mode: "exec",
|
|
127
|
-
command: spec.command
|
|
139
|
+
command: spec.command,
|
|
140
|
+
...spec.timeoutMs !== void 0 ? { timeoutMs: String(spec.timeoutMs) } : {}
|
|
128
141
|
};
|
|
129
142
|
return {
|
|
130
143
|
agentId: spec.agentId,
|
|
@@ -223,8 +236,23 @@ var SandboxStream = class SandboxStream {
|
|
|
223
236
|
}
|
|
224
237
|
});
|
|
225
238
|
let failure = null;
|
|
239
|
+
ws.on("unexpected-response", (_request, response) => {
|
|
240
|
+
const chunks = [];
|
|
241
|
+
let bytes = 0;
|
|
242
|
+
response.on("data", (chunk) => {
|
|
243
|
+
if (bytes >= MAX_HANDSHAKE_BODY_BYTES) return;
|
|
244
|
+
const remaining = MAX_HANDSHAKE_BODY_BYTES - bytes;
|
|
245
|
+
const kept = Buffer.from(chunk).subarray(0, remaining);
|
|
246
|
+
chunks.push(kept);
|
|
247
|
+
bytes += kept.length;
|
|
248
|
+
});
|
|
249
|
+
response.on("end", () => {
|
|
250
|
+
failure = formatHandshakeRejection(response.statusCode, response.statusMessage, Buffer.concat(chunks).toString("utf8"));
|
|
251
|
+
ws.terminate();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
226
254
|
ws.on("error", (err) => {
|
|
227
|
-
failure
|
|
255
|
+
failure ??= err.message;
|
|
228
256
|
});
|
|
229
257
|
ws.on("close", () => {
|
|
230
258
|
this.closed = true;
|
|
@@ -258,13 +286,20 @@ var SandboxStream = class SandboxStream {
|
|
|
258
286
|
} : {
|
|
259
287
|
mode: "exec",
|
|
260
288
|
agentId: opts.agentId,
|
|
261
|
-
command: opts.command
|
|
289
|
+
command: opts.command,
|
|
290
|
+
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
262
291
|
};
|
|
263
292
|
const url = new URL(`${wsBase(opts.appUrl)}${SANDBOX_STREAM_PATH}`);
|
|
264
293
|
for (const [key, value] of Object.entries(streamSpecToQuery(spec))) url.searchParams.set(key, value);
|
|
265
294
|
return new SandboxStream(new WebSocket(url.toString(), { headers: { authorization: `Bearer ${opts.sessionToken}` } }), opts.onEvent);
|
|
266
295
|
}
|
|
267
296
|
};
|
|
297
|
+
const MAX_HANDSHAKE_BODY_BYTES = 512;
|
|
298
|
+
function formatHandshakeRejection(statusCode, statusMessage, body) {
|
|
299
|
+
const status = [statusCode, statusMessage].filter(Boolean).join(" ");
|
|
300
|
+
const reason = Array.from(body, (char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127 ? " " : char).join("").replace(/\s+/g, " ").trim();
|
|
301
|
+
return reason ? `${status}: ${reason}` : status || "WebSocket upgrade rejected";
|
|
302
|
+
}
|
|
268
303
|
function toBuffer(data) {
|
|
269
304
|
if (Buffer.isBuffer(data)) return data;
|
|
270
305
|
if (Array.isArray(data)) return Buffer.concat(data);
|
|
@@ -272,4 +307,4 @@ function toBuffer(data) {
|
|
|
272
307
|
}
|
|
273
308
|
|
|
274
309
|
//#endregion
|
|
275
|
-
export {
|
|
310
|
+
export { FS_MAX_BLOB_BYTES as a, streamSpecToQuery as i, DEFAULT_EXEC_TIMEOUT_MS as n, decodeFsResponse as o, SANDBOX_STREAM_PATH as r, encodeFsRequest as s, SandboxStream as t };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
|
|
2
3
|
import { a as mintPortalDeviceToken, c as unifyLegacyCliGrants, i as grantPortalAccess, l as startTlsForward, n as fetchPortalDevices, r as findThisDevice } from "./api-BFQ4PQDA.mjs";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import { z } from "zod";
|
|
@@ -160,7 +161,7 @@ function errorMessage(err) {
|
|
|
160
161
|
* the commit time of the built tree is one monotonic clock they share.
|
|
161
162
|
*/
|
|
162
163
|
function portalDaemonBuild() {
|
|
163
|
-
return "
|
|
164
|
+
return "1792017288";
|
|
164
165
|
}
|
|
165
166
|
/**
|
|
166
167
|
* Whether a client carrying `mine` should replace a running daemon carrying
|
|
@@ -186,6 +187,8 @@ function parseStamp(stamp) {
|
|
|
186
187
|
|
|
187
188
|
//#endregion
|
|
188
189
|
//#region ../portal-protocol/src/index.ts
|
|
190
|
+
const CLOSE_SUPERSEDED = 4001;
|
|
191
|
+
const CLOSE_FLAP_COOLDOWN = 4002;
|
|
189
192
|
const MAX_WS_FRAME_BYTES = 16 * 1024 * 1024;
|
|
190
193
|
const T_DATA = 1;
|
|
191
194
|
const T_CTRL = 2;
|
|
@@ -577,14 +580,41 @@ var JobManager = class {
|
|
|
577
580
|
|
|
578
581
|
//#endregion
|
|
579
582
|
//#region ../portal-daemon/src/client.ts
|
|
583
|
+
const INITIAL_BACKOFF_MS = 500;
|
|
584
|
+
const MAX_BACKOFF_MS = 1e4;
|
|
585
|
+
const AUTH_STAND_DOWN_MS = 5 * 6e4;
|
|
586
|
+
const AUTH_STAND_DOWN_MESSAGE = "portal sign-in expired — reopen Skydive Desktop (or run `skydive portal open`) to reconnect";
|
|
587
|
+
/**
|
|
588
|
+
* True when the error means the server could not authenticate us: a token-mint
|
|
589
|
+
* failure (HttpError 401) or a WS upgrade rejected at the auth layer. `ws`
|
|
590
|
+
* reports a rejected upgrade as `Unexpected server response: <status>` with no
|
|
591
|
+
* structured status property, so the status is read from the message. A 429 is
|
|
592
|
+
* deliberately NOT an auth failure — see isRateLimited.
|
|
593
|
+
*/
|
|
594
|
+
function isAuthFailure(err) {
|
|
595
|
+
if (err instanceof HttpError) return err.status === 401;
|
|
596
|
+
return /Unexpected server response: 401\b/.test(errorMessage(err));
|
|
597
|
+
}
|
|
598
|
+
const RATE_LIMIT_STAND_DOWN_MS = 12e4;
|
|
599
|
+
const RATE_LIMIT_STAND_DOWN_MESSAGE = "portal connection rate limited — retrying in 2 minutes";
|
|
600
|
+
function isRateLimited(err) {
|
|
601
|
+
if (err instanceof HttpError) return err.status === 429;
|
|
602
|
+
return /Unexpected server response: 429\b/.test(errorMessage(err));
|
|
603
|
+
}
|
|
580
604
|
const DEFAULT_HEARTBEAT = {
|
|
581
605
|
intervalMs: 15e3,
|
|
582
606
|
maxMissed: 2,
|
|
583
607
|
wakeTickMs: 2e3,
|
|
584
608
|
wakeGapThresholdMs: 1e4,
|
|
585
609
|
netWatchTickMs: 2e3,
|
|
586
|
-
initialBackoffMs:
|
|
587
|
-
maxBackoffMs:
|
|
610
|
+
initialBackoffMs: INITIAL_BACKOFF_MS,
|
|
611
|
+
maxBackoffMs: MAX_BACKOFF_MS,
|
|
612
|
+
supersededBackoffFloorMs: 5e3,
|
|
613
|
+
flapWindowMs: 6e4,
|
|
614
|
+
flapMaxStandDowns: 3,
|
|
615
|
+
flapBackoffMs: 6e4,
|
|
616
|
+
authStandDownMs: AUTH_STAND_DOWN_MS,
|
|
617
|
+
rateLimitStandDownMs: RATE_LIMIT_STAND_DOWN_MS
|
|
588
618
|
};
|
|
589
619
|
/**
|
|
590
620
|
* A cheap fingerprint of the machine's network identity: every non-internal
|
|
@@ -664,11 +694,14 @@ var PortalClient = class {
|
|
|
664
694
|
machineName = "";
|
|
665
695
|
friendlyName = "";
|
|
666
696
|
legacyGrantsChecked = false;
|
|
697
|
+
authRejected = false;
|
|
698
|
+
rateLimited = false;
|
|
667
699
|
wakeWatch = null;
|
|
668
700
|
lastTick = Date.now();
|
|
669
701
|
netWatch = null;
|
|
670
702
|
netFingerprint = networkFingerprint();
|
|
671
703
|
backoffKick = null;
|
|
704
|
+
inStandDownSleep = false;
|
|
672
705
|
dialReason = null;
|
|
673
706
|
disconnectedAt = null;
|
|
674
707
|
handingOff = false;
|
|
@@ -739,6 +772,7 @@ var PortalClient = class {
|
|
|
739
772
|
this.ws.terminate();
|
|
740
773
|
return;
|
|
741
774
|
}
|
|
775
|
+
if (this.inStandDownSleep) return;
|
|
742
776
|
this.kickBackoff();
|
|
743
777
|
}
|
|
744
778
|
kickBackoff() {
|
|
@@ -777,6 +811,8 @@ var PortalClient = class {
|
|
|
777
811
|
if (this.enabled || this.disposed) return;
|
|
778
812
|
this.enabled = true;
|
|
779
813
|
this.error = null;
|
|
814
|
+
this.authRejected = false;
|
|
815
|
+
this.rateLimited = false;
|
|
780
816
|
this.connectLoop();
|
|
781
817
|
}
|
|
782
818
|
disable() {
|
|
@@ -914,29 +950,68 @@ var PortalClient = class {
|
|
|
914
950
|
this.opts.onMachineName(identity.machineName, identity.machineNameSource);
|
|
915
951
|
}
|
|
916
952
|
let backoff = this.heartbeatConfig.initialBackoffMs;
|
|
953
|
+
let standDowns = [];
|
|
917
954
|
while (this.enabled && !this.disposed && !this.handingOff) {
|
|
918
955
|
this.setStatus("connecting");
|
|
956
|
+
let sleepFloor = 0;
|
|
919
957
|
try {
|
|
920
958
|
const token = await this.wsToken();
|
|
921
|
-
|
|
922
|
-
|
|
959
|
+
const { healthy, standDown } = await this.runConnection(token);
|
|
960
|
+
if (standDown) {
|
|
961
|
+
this.setStatus("stand-down");
|
|
962
|
+
const now = Date.now();
|
|
963
|
+
standDowns.push(now);
|
|
964
|
+
standDowns = standDowns.filter((at) => now - at <= this.heartbeatConfig.flapWindowMs);
|
|
965
|
+
if (standDowns.length >= this.heartbeatConfig.flapMaxStandDowns) {
|
|
966
|
+
backoff = this.heartbeatConfig.flapBackoffMs;
|
|
967
|
+
this.setStatus("error", "another portal connection for this machine keeps taking over — if Skydive Desktop or `skydive portal open` is running twice, quit the duplicate; an unstable network can also cause this briefly");
|
|
968
|
+
} else backoff = Math.max(Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs), this.heartbeatConfig.supersededBackoffFloorMs);
|
|
969
|
+
sleepFloor = backoff / 2;
|
|
970
|
+
} else if (healthy) backoff = this.heartbeatConfig.initialBackoffMs;
|
|
971
|
+
else if (this.authRejected) {
|
|
972
|
+
this.setStatus("error", AUTH_STAND_DOWN_MESSAGE);
|
|
973
|
+
this.authRejected = false;
|
|
974
|
+
backoff = this.heartbeatConfig.authStandDownMs;
|
|
975
|
+
sleepFloor = backoff;
|
|
976
|
+
} else if (this.rateLimited) {
|
|
977
|
+
this.setStatus("error", RATE_LIMIT_STAND_DOWN_MESSAGE);
|
|
978
|
+
this.rateLimited = false;
|
|
979
|
+
backoff = this.heartbeatConfig.rateLimitStandDownMs;
|
|
980
|
+
sleepFloor = backoff;
|
|
981
|
+
} else backoff = Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs);
|
|
923
982
|
} catch (err) {
|
|
924
983
|
if (!this.enabled || this.disposed || this.handingOff) break;
|
|
925
|
-
|
|
926
|
-
|
|
984
|
+
if (isAuthFailure(err)) {
|
|
985
|
+
this.setStatus("error", AUTH_STAND_DOWN_MESSAGE);
|
|
986
|
+
backoff = this.heartbeatConfig.authStandDownMs;
|
|
987
|
+
sleepFloor = backoff;
|
|
988
|
+
} else if (isRateLimited(err)) {
|
|
989
|
+
this.setStatus("error", RATE_LIMIT_STAND_DOWN_MESSAGE);
|
|
990
|
+
backoff = this.heartbeatConfig.rateLimitStandDownMs;
|
|
991
|
+
sleepFloor = backoff;
|
|
992
|
+
} else {
|
|
993
|
+
this.setStatus("error", errorMessage(err));
|
|
994
|
+
backoff = Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs);
|
|
995
|
+
}
|
|
927
996
|
}
|
|
928
997
|
if (!this.enabled || this.disposed || this.handingOff) break;
|
|
929
|
-
|
|
998
|
+
this.inStandDownSleep = sleepFloor > 0;
|
|
999
|
+
await this.backoffSleep(sleepFloor + Math.random() * Math.max(backoff - sleepFloor, 0));
|
|
1000
|
+
this.inStandDownSleep = false;
|
|
930
1001
|
}
|
|
931
1002
|
}
|
|
932
1003
|
runConnection(token) {
|
|
933
1004
|
return new Promise((resolve) => {
|
|
934
1005
|
let settled = false;
|
|
935
1006
|
let healthy = false;
|
|
1007
|
+
let standDown = false;
|
|
936
1008
|
const settle = () => {
|
|
937
1009
|
if (settled) return;
|
|
938
1010
|
settled = true;
|
|
939
|
-
resolve(
|
|
1011
|
+
resolve({
|
|
1012
|
+
healthy,
|
|
1013
|
+
standDown
|
|
1014
|
+
});
|
|
940
1015
|
};
|
|
941
1016
|
const ws = new WebSocket(portalWsUrl(this.opts.appUrl, this.machineName, this.friendlyName, {
|
|
942
1017
|
build: portalDaemonBuild(),
|
|
@@ -974,6 +1049,8 @@ var PortalClient = class {
|
|
|
974
1049
|
};
|
|
975
1050
|
ws.on("open", () => {
|
|
976
1051
|
missed = 0;
|
|
1052
|
+
this.authRejected = false;
|
|
1053
|
+
this.rateLimited = false;
|
|
977
1054
|
this.dialReason = null;
|
|
978
1055
|
this.disconnectedAt = null;
|
|
979
1056
|
this.setStatus("connected");
|
|
@@ -987,8 +1064,18 @@ var PortalClient = class {
|
|
|
987
1064
|
});
|
|
988
1065
|
ws.on("error", (err) => {
|
|
989
1066
|
this.error = errorMessage(err);
|
|
1067
|
+
if (isAuthFailure(err)) {
|
|
1068
|
+
this.authRejected = true;
|
|
1069
|
+
this.dialReason ??= "portal credentials rejected; standing down";
|
|
1070
|
+
}
|
|
1071
|
+
if (isRateLimited(err)) {
|
|
1072
|
+
this.rateLimited = true;
|
|
1073
|
+
this.dialReason ??= "portal connection rate limited; standing down";
|
|
1074
|
+
}
|
|
990
1075
|
});
|
|
991
|
-
ws.on("close", (code) => {
|
|
1076
|
+
ws.on("close", (code, reason) => {
|
|
1077
|
+
standDown = code === CLOSE_SUPERSEDED || code === CLOSE_FLAP_COOLDOWN || code === 1e3 && reason.toString() === "superseded";
|
|
1078
|
+
if (standDown) this.dialReason ??= `superseded (code ${code})`;
|
|
992
1079
|
this.dialReason ??= `connection closed (code ${code})`;
|
|
993
1080
|
this.disconnectedAt ??= Date.now();
|
|
994
1081
|
clearInterval(heartbeat);
|
|
@@ -1048,4 +1135,4 @@ function sleep(ms) {
|
|
|
1048
1135
|
}
|
|
1049
1136
|
|
|
1050
1137
|
//#endregion
|
|
1051
|
-
export {
|
|
1138
|
+
export { portalDaemonBuild as a, isNewerBuild as i, PortalClient as n, isRecord as o, RATE_LIMIT_STAND_DOWN_MS as r, resolveMachineIdentity as s, AUTH_STAND_DOWN_MS as t };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./client
|
|
2
|
+
import "./client-HdxGBXPK.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-
|
|
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-B8c3Z1Te.mjs";
|
|
6
6
|
|
|
7
7
|
export { runPortalDaemon };
|