setsuna-microvm 0.5.2 → 0.5.4
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/LICENSE +32 -32
- package/dist/cli.js +236 -314
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
SemSwitch Setsuna Research Preview Client License
|
|
2
|
-
|
|
3
|
-
Copyright © 2026 SemSwitch, Inc. All rights reserved.
|
|
4
|
-
|
|
5
|
-
The Setsuna client distributed in this package is proprietary software owned by SemSwitch, Inc.
|
|
6
|
-
("SemSwitch"). It is provided for authorized use with the Setsuna Research Preview.
|
|
7
|
-
|
|
8
|
-
1. Grant. Subject to the SemSwitch Terms of Service (https://semswitch.com/terms) and any
|
|
9
|
-
applicable Order or evaluation agreement, an authorized user receives a limited, non-exclusive
|
|
10
|
-
right to install and use the Setsuna client solely to access or evaluate the Setsuna service
|
|
11
|
-
that the user is authorized to use.
|
|
12
|
-
|
|
13
|
-
2. Ownership. The Setsuna client is licensed, not sold. SemSwitch retains all ownership,
|
|
14
|
-
copyright, and other intellectual property rights in the Setsuna client. This license transfers
|
|
15
|
-
no ownership or other intellectual property rights.
|
|
16
|
-
|
|
17
|
-
3. Restrictions. Except where expressly permitted by an applicable agreement with SemSwitch or by
|
|
18
|
-
non-waivable law, no right is granted to redistribute, sublicense, sell, rent, publish, modify,
|
|
19
|
-
create derivative works from, reverse engineer, decompile, or disassemble the Setsuna client.
|
|
20
|
-
|
|
21
|
-
4. Third-party software. The Setsuna client uses third-party software and components that are not
|
|
22
|
-
owned by SemSwitch. They remain governed by their respective licenses and notices, which this
|
|
23
|
-
license does not modify or replace.
|
|
24
|
-
|
|
25
|
-
5. Term. The right to use the Setsuna client ends when the applicable Setsuna authorization ends.
|
|
26
|
-
|
|
27
|
-
6. Agreements. If an applicable signed Order or separately negotiated agreement with SemSwitch
|
|
28
|
-
expressly conflicts with this license, that Order or agreement controls.
|
|
29
|
-
|
|
30
|
-
7. Warranties, liability, and service terms. Warranties, disclaimers, limitations of liability,
|
|
31
|
-
and the terms of the Setsuna service are governed by the applicable agreement and the SemSwitch
|
|
32
|
-
Terms of Service rather than by this license.
|
|
1
|
+
SemSwitch Setsuna Research Preview Client License
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 SemSwitch, Inc. All rights reserved.
|
|
4
|
+
|
|
5
|
+
The Setsuna client distributed in this package is proprietary software owned by SemSwitch, Inc.
|
|
6
|
+
("SemSwitch"). It is provided for authorized use with the Setsuna Research Preview.
|
|
7
|
+
|
|
8
|
+
1. Grant. Subject to the SemSwitch Terms of Service (https://semswitch.com/terms) and any
|
|
9
|
+
applicable Order or evaluation agreement, an authorized user receives a limited, non-exclusive
|
|
10
|
+
right to install and use the Setsuna client solely to access or evaluate the Setsuna service
|
|
11
|
+
that the user is authorized to use.
|
|
12
|
+
|
|
13
|
+
2. Ownership. The Setsuna client is licensed, not sold. SemSwitch retains all ownership,
|
|
14
|
+
copyright, and other intellectual property rights in the Setsuna client. This license transfers
|
|
15
|
+
no ownership or other intellectual property rights.
|
|
16
|
+
|
|
17
|
+
3. Restrictions. Except where expressly permitted by an applicable agreement with SemSwitch or by
|
|
18
|
+
non-waivable law, no right is granted to redistribute, sublicense, sell, rent, publish, modify,
|
|
19
|
+
create derivative works from, reverse engineer, decompile, or disassemble the Setsuna client.
|
|
20
|
+
|
|
21
|
+
4. Third-party software. The Setsuna client uses third-party software and components that are not
|
|
22
|
+
owned by SemSwitch. They remain governed by their respective licenses and notices, which this
|
|
23
|
+
license does not modify or replace.
|
|
24
|
+
|
|
25
|
+
5. Term. The right to use the Setsuna client ends when the applicable Setsuna authorization ends.
|
|
26
|
+
|
|
27
|
+
6. Agreements. If an applicable signed Order or separately negotiated agreement with SemSwitch
|
|
28
|
+
expressly conflicts with this license, that Order or agreement controls.
|
|
29
|
+
|
|
30
|
+
7. Warranties, liability, and service terms. Warranties, disclaimers, limitations of liability,
|
|
31
|
+
and the terms of the Setsuna service are governed by the applicable agreement and the SemSwitch
|
|
32
|
+
Terms of Service rather than by this license.
|
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,9 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// ../setsuna/src/tui/customer/prompt.ts
|
|
13
|
+
function readText(text, now) {
|
|
14
|
+
return typeof text === "function" ? text(now) : text;
|
|
15
|
+
}
|
|
13
16
|
var TUI_CANCEL;
|
|
14
17
|
var init_prompt = __esm({
|
|
15
18
|
"../setsuna/src/tui/customer/prompt.ts"() {
|
|
@@ -76,14 +79,13 @@ function adaptPrompts(clack, guide) {
|
|
|
76
79
|
const { confirm, isCancel, select, text } = clack;
|
|
77
80
|
return {
|
|
78
81
|
async select(options, io) {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
options
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
return isCancel(result) ? TUI_CANCEL : result;
|
|
82
|
+
const redraw = hasTimedText(options) ? setInterval(() => io.output.emit("resize"), 1e3) : void 0;
|
|
83
|
+
try {
|
|
84
|
+
const result = await select(selectOptions(options, guide(options), io));
|
|
85
|
+
return isCancel(result) ? TUI_CANCEL : result;
|
|
86
|
+
} finally {
|
|
87
|
+
if (redraw !== void 0) clearInterval(redraw);
|
|
88
|
+
}
|
|
87
89
|
},
|
|
88
90
|
async text(options, io) {
|
|
89
91
|
const result = await text({
|
|
@@ -106,6 +108,28 @@ function adaptPrompts(clack, guide) {
|
|
|
106
108
|
}
|
|
107
109
|
};
|
|
108
110
|
}
|
|
111
|
+
function selectOptions(options, guide, io) {
|
|
112
|
+
return {
|
|
113
|
+
get message() {
|
|
114
|
+
return readText(options.message, Date.now());
|
|
115
|
+
},
|
|
116
|
+
options: options.options.map((option) => ({
|
|
117
|
+
value: option.value,
|
|
118
|
+
label: option.label,
|
|
119
|
+
get hint() {
|
|
120
|
+
return option.hint === void 0 ? void 0 : readText(option.hint, Date.now());
|
|
121
|
+
}
|
|
122
|
+
})),
|
|
123
|
+
initialValue: options.initialValue,
|
|
124
|
+
signal: options.signal,
|
|
125
|
+
...guide,
|
|
126
|
+
input: io.input,
|
|
127
|
+
output: io.output
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function hasTimedText(options) {
|
|
131
|
+
return typeof options.message === "function" || options.options.some(({ hint }) => typeof hint === "function");
|
|
132
|
+
}
|
|
109
133
|
function adaptSpinner(clack, io, options, guide) {
|
|
110
134
|
const completion = trackInterruption(clack, io, options.onCancel, guide);
|
|
111
135
|
const spinner = clack.spinner({
|
|
@@ -1089,6 +1113,7 @@ function parsePreviewCliArgs(args) {
|
|
|
1089
1113
|
"timeout-ms": { type: "string" },
|
|
1090
1114
|
"request-timeout-ms": { type: "string" },
|
|
1091
1115
|
"configuration-id": { type: "string" },
|
|
1116
|
+
duration: { type: "string" },
|
|
1092
1117
|
attach: { type: "string" },
|
|
1093
1118
|
json: { type: "boolean", default: false },
|
|
1094
1119
|
help: { type: "boolean", short: "h", default: false },
|
|
@@ -1200,14 +1225,14 @@ function hasOnlyKeys(value, allowed) {
|
|
|
1200
1225
|
}
|
|
1201
1226
|
function parseLease(value, expectedConfigurationId) {
|
|
1202
1227
|
const sandboxId = parseLeaseSandboxId(value);
|
|
1203
|
-
if (!isRecord(value) || typeof value.expiresAt !== "string") {
|
|
1228
|
+
if (!isRecord(value) || typeof value.expiresAt !== "string" || !Number.isFinite(Date.parse(value.expiresAt)) || value.duration !== "20m" && value.duration !== "1h" && value.duration !== "2h") {
|
|
1204
1229
|
throw invalidResponse("sandbox lease");
|
|
1205
1230
|
}
|
|
1206
1231
|
const microvmConfiguration = parseMicrovmConfiguration(value.microvmConfiguration);
|
|
1207
1232
|
if (expectedConfigurationId !== void 0 && microvmConfiguration.id !== expectedConfigurationId) {
|
|
1208
1233
|
throw invalidResponse("sandbox lease");
|
|
1209
1234
|
}
|
|
1210
|
-
return { sandboxId, expiresAt: value.expiresAt, microvmConfiguration };
|
|
1235
|
+
return { sandboxId, expiresAt: value.expiresAt, duration: value.duration, microvmConfiguration };
|
|
1211
1236
|
}
|
|
1212
1237
|
function parseLeaseSandboxId(value) {
|
|
1213
1238
|
if (!isRecord(value) || !nonEmptyResponseString(value.sandboxId))
|
|
@@ -1248,6 +1273,36 @@ function isRecord(value) {
|
|
|
1248
1273
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1249
1274
|
}
|
|
1250
1275
|
|
|
1276
|
+
// ../setsuna/src/client-affinity.ts
|
|
1277
|
+
var FrontDoorAffinity = class {
|
|
1278
|
+
#origin;
|
|
1279
|
+
#cookies = /* @__PURE__ */ new Map();
|
|
1280
|
+
constructor(serviceUrl) {
|
|
1281
|
+
this.#origin = serviceUrl.origin;
|
|
1282
|
+
}
|
|
1283
|
+
headersFor(url, headers) {
|
|
1284
|
+
if (url.origin !== this.#origin || this.#cookies.size === 0) return headers;
|
|
1285
|
+
const result = new Headers(headers);
|
|
1286
|
+
result.set("cookie", [...this.#cookies].map(([name, value]) => `${name}=${value}`).join("; "));
|
|
1287
|
+
return result;
|
|
1288
|
+
}
|
|
1289
|
+
capture(url, response) {
|
|
1290
|
+
if (url.origin !== this.#origin) return;
|
|
1291
|
+
if (response.url !== "" && new URL(response.url).origin !== this.#origin) return;
|
|
1292
|
+
for (const header of response.headers.getSetCookie()) {
|
|
1293
|
+
const pair = header.split(";", 1)[0]?.trim() ?? "";
|
|
1294
|
+
const match = /^(ASLBSA|ASLBSACORS)=([\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]*)$/u.exec(
|
|
1295
|
+
pair
|
|
1296
|
+
);
|
|
1297
|
+
if (match === null) continue;
|
|
1298
|
+
const [, name, value] = match;
|
|
1299
|
+
if (name === void 0 || value === void 0) continue;
|
|
1300
|
+
if (value === "") this.#cookies.delete(name);
|
|
1301
|
+
else this.#cookies.set(name, value);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
|
|
1251
1306
|
// ../setsuna/src/client-options.ts
|
|
1252
1307
|
var PREVIEW_SERVICE_URL = "https://api.setsuna.semswitch.com/";
|
|
1253
1308
|
function normalizeServiceUrl(value) {
|
|
@@ -1285,12 +1340,16 @@ function normalizeCreateOptions(options) {
|
|
|
1285
1340
|
throw new TypeError("create options must be an object.");
|
|
1286
1341
|
}
|
|
1287
1342
|
const unsupported = Object.keys(options).find(
|
|
1288
|
-
(key) => key !== "microvmConfigurationId" && key !== "network"
|
|
1343
|
+
(key) => key !== "microvmConfigurationId" && key !== "network" && key !== "duration"
|
|
1289
1344
|
);
|
|
1290
1345
|
if (unsupported !== void 0) throw new TypeError(`Unsupported create option: ${unsupported}.`);
|
|
1346
|
+
const duration = options.duration === void 0 ? "20m" : options.duration;
|
|
1347
|
+
if (duration !== "20m" && duration !== "1h" && duration !== "2h")
|
|
1348
|
+
throw new TypeError("duration must be 20m, 1h, or 2h.");
|
|
1291
1349
|
const microvmConfigurationId = options.microvmConfigurationId === void 0 ? void 0 : nonEmptyString(options.microvmConfigurationId, "microvmConfigurationId");
|
|
1292
1350
|
const network = options.network === void 0 ? void 0 : validateRoutedNetwork(options.network);
|
|
1293
1351
|
return {
|
|
1352
|
+
duration,
|
|
1294
1353
|
...microvmConfigurationId === void 0 ? {} : { microvmConfigurationId },
|
|
1295
1354
|
...network === void 0 ? {} : { network }
|
|
1296
1355
|
};
|
|
@@ -1359,7 +1418,7 @@ function isLoopbackHostname(hostname) {
|
|
|
1359
1418
|
}
|
|
1360
1419
|
|
|
1361
1420
|
// ../setsuna/src/client-request.ts
|
|
1362
|
-
async function requestJson(serviceUrl, defaultTimeoutMs, getAccessToken, path3, options) {
|
|
1421
|
+
async function requestJson(serviceUrl, defaultTimeoutMs, getAccessToken, path3, options, affinity) {
|
|
1363
1422
|
const requestTimeoutMs = options.requestTimeoutMs === void 0 ? defaultTimeoutMs : positiveMilliseconds(options.requestTimeoutMs, "requestTimeoutMs");
|
|
1364
1423
|
const timeoutSignal = AbortSignal.timeout(requestTimeoutMs);
|
|
1365
1424
|
const signal = options.signal === void 0 ? timeoutSignal : AbortSignal.any([options.signal, timeoutSignal]);
|
|
@@ -1376,7 +1435,8 @@ async function requestJson(serviceUrl, defaultTimeoutMs, getAccessToken, path3,
|
|
|
1376
1435
|
requestTimeoutMs,
|
|
1377
1436
|
timeoutSignal,
|
|
1378
1437
|
signal,
|
|
1379
|
-
accessToken
|
|
1438
|
+
accessToken,
|
|
1439
|
+
affinity
|
|
1380
1440
|
);
|
|
1381
1441
|
const responseBody = response.status === 204 ? void 0 : await parseResponseJson(response);
|
|
1382
1442
|
if (!response.ok) throw brokerResponseError(response, responseBody);
|
|
@@ -1417,14 +1477,17 @@ function settleBeforeAbort(pending, signal) {
|
|
|
1417
1477
|
if (signal.aborted) onAbort();
|
|
1418
1478
|
});
|
|
1419
1479
|
}
|
|
1420
|
-
async function fetchService(url, options, requestTimeoutMs, timeoutSignal, signal, accessToken) {
|
|
1480
|
+
async function fetchService(url, options, requestTimeoutMs, timeoutSignal, signal, accessToken, affinity) {
|
|
1421
1481
|
try {
|
|
1422
|
-
|
|
1482
|
+
const headers = requestHeaders(options.body, accessToken);
|
|
1483
|
+
const response = await fetch(url, {
|
|
1423
1484
|
method: options.method,
|
|
1424
|
-
headers:
|
|
1485
|
+
headers: affinity?.headersFor(url, headers) ?? headers,
|
|
1425
1486
|
body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
|
|
1426
1487
|
signal
|
|
1427
1488
|
});
|
|
1489
|
+
affinity?.capture(url, response);
|
|
1490
|
+
return response;
|
|
1428
1491
|
} catch (error) {
|
|
1429
1492
|
throwCancellationFailure(error, requestTimeoutMs, timeoutSignal, options.signal);
|
|
1430
1493
|
throw new SetsunaError("The local Setsuna service could not be reached.", {
|
|
@@ -1559,13 +1622,14 @@ function parseProcessList(value) {
|
|
|
1559
1622
|
if (!Array.isArray(value)) throw invalidResponse2("process list");
|
|
1560
1623
|
return value.map(parseProcessInfo);
|
|
1561
1624
|
}
|
|
1562
|
-
function attachProcessStream(serviceUrl, path3, options, getAccessToken) {
|
|
1625
|
+
function attachProcessStream(serviceUrl, path3, options, getAccessToken, affinity) {
|
|
1563
1626
|
return {
|
|
1564
1627
|
async *[Symbol.asyncIterator]() {
|
|
1565
1628
|
const response = await fetchProcessStream(
|
|
1566
1629
|
new URL(path3, serviceUrl),
|
|
1567
1630
|
options.signal,
|
|
1568
|
-
getAccessToken
|
|
1631
|
+
getAccessToken,
|
|
1632
|
+
affinity
|
|
1569
1633
|
);
|
|
1570
1634
|
if (response.body === null) throw invalidResponse2("process stream");
|
|
1571
1635
|
yield* decodeFrames(response.body, options.signal);
|
|
@@ -1586,15 +1650,17 @@ function processFrameToJson(frame) {
|
|
|
1586
1650
|
}
|
|
1587
1651
|
return frame;
|
|
1588
1652
|
}
|
|
1589
|
-
async function fetchProcessStream(url, signal, getAccessToken) {
|
|
1653
|
+
async function fetchProcessStream(url, signal, getAccessToken, affinity) {
|
|
1590
1654
|
const accessToken = await processAccessToken(getAccessToken, signal);
|
|
1591
1655
|
let response;
|
|
1592
1656
|
try {
|
|
1657
|
+
const headers = accessToken === void 0 ? void 0 : { authorization: `Bearer ${accessToken}` };
|
|
1593
1658
|
response = await fetch(url, {
|
|
1594
1659
|
method: "GET",
|
|
1595
1660
|
signal,
|
|
1596
|
-
...
|
|
1661
|
+
...headers === void 0 ? {} : { headers: affinity?.headersFor(url, headers) ?? headers }
|
|
1597
1662
|
});
|
|
1663
|
+
affinity?.capture(url, response);
|
|
1598
1664
|
} catch (error) {
|
|
1599
1665
|
throw requestError(error, signal);
|
|
1600
1666
|
}
|
|
@@ -1768,11 +1834,13 @@ function reportRunPhase(callback, phase) {
|
|
|
1768
1834
|
|
|
1769
1835
|
// ../setsuna/src/client.ts
|
|
1770
1836
|
var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
|
|
1771
|
-
var
|
|
1837
|
+
var DEFAULT_RUN_REQUEST_TIMEOUT_MS = 6e4 + 12e4 + 6e4;
|
|
1772
1838
|
var SetsunaClient = class {
|
|
1773
1839
|
#serviceUrl;
|
|
1774
1840
|
#requestTimeoutMs;
|
|
1841
|
+
#explicitRequestTimeoutMs;
|
|
1775
1842
|
#getAccessToken;
|
|
1843
|
+
#affinity;
|
|
1776
1844
|
constructor(options = {}) {
|
|
1777
1845
|
if (options.serviceUrl !== void 0 && options.remote !== void 0) {
|
|
1778
1846
|
throw new TypeError("serviceUrl and remote are mutually exclusive.");
|
|
@@ -1786,7 +1854,9 @@ var SetsunaClient = class {
|
|
|
1786
1854
|
}
|
|
1787
1855
|
this.#serviceUrl = new URL(PREVIEW_SERVICE_URL);
|
|
1788
1856
|
this.#getAccessToken = options.remote.getAccessToken;
|
|
1857
|
+
this.#affinity = new FrontDoorAffinity(this.#serviceUrl);
|
|
1789
1858
|
}
|
|
1859
|
+
this.#explicitRequestTimeoutMs = options.requestTimeoutMs;
|
|
1790
1860
|
this.#requestTimeoutMs = positiveMilliseconds(
|
|
1791
1861
|
options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
|
|
1792
1862
|
"requestTimeoutMs"
|
|
@@ -1806,20 +1876,7 @@ var SetsunaClient = class {
|
|
|
1806
1876
|
method: "POST",
|
|
1807
1877
|
body: { ...createOptions }
|
|
1808
1878
|
});
|
|
1809
|
-
|
|
1810
|
-
try {
|
|
1811
|
-
return parseLease(response, createOptions.microvmConfigurationId);
|
|
1812
|
-
} catch (error) {
|
|
1813
|
-
try {
|
|
1814
|
-
await this.#destroy(sandboxId, DEFAULT_CLEANUP_TIMEOUT_MS);
|
|
1815
|
-
} catch (cleanupFailure) {
|
|
1816
|
-
throw new AggregateError(
|
|
1817
|
-
[error, cleanupFailure],
|
|
1818
|
-
"Sandbox lease validation and destruction both failed."
|
|
1819
|
-
);
|
|
1820
|
-
}
|
|
1821
|
-
throw error;
|
|
1822
|
-
}
|
|
1879
|
+
return parseLease(response, createOptions.microvmConfigurationId);
|
|
1823
1880
|
}
|
|
1824
1881
|
async list() {
|
|
1825
1882
|
return parseSandboxList(await this.#request("v1/sandboxes", { method: "GET" }));
|
|
@@ -1832,14 +1889,6 @@ var SetsunaClient = class {
|
|
|
1832
1889
|
})
|
|
1833
1890
|
);
|
|
1834
1891
|
}
|
|
1835
|
-
async renew(sandboxId) {
|
|
1836
|
-
const validatedSandboxId = nonEmptyString(sandboxId, "sandboxId");
|
|
1837
|
-
return parseSandboxStatus(
|
|
1838
|
-
await this.#request(`v1/sandboxes/${encodeURIComponent(validatedSandboxId)}/renew`, {
|
|
1839
|
-
method: "POST"
|
|
1840
|
-
})
|
|
1841
|
-
);
|
|
1842
|
-
}
|
|
1843
1892
|
async execute(sandboxId, command, options = {}) {
|
|
1844
1893
|
const validatedSandboxId = nonEmptyString(sandboxId, "sandboxId");
|
|
1845
1894
|
const validatedCommand = nonEmptyString(command, "command");
|
|
@@ -1876,7 +1925,8 @@ var SetsunaClient = class {
|
|
|
1876
1925
|
this.#serviceUrl,
|
|
1877
1926
|
`${processPath(sandboxId)}/${encodeURIComponent(nonEmptyString(processId, "processId"))}/attach${query}`,
|
|
1878
1927
|
options,
|
|
1879
|
-
this.#getAccessToken
|
|
1928
|
+
this.#getAccessToken,
|
|
1929
|
+
this.#affinity
|
|
1880
1930
|
);
|
|
1881
1931
|
}
|
|
1882
1932
|
async writeProcessStdin(sandboxId, processId, data) {
|
|
@@ -1913,44 +1963,30 @@ var SetsunaClient = class {
|
|
|
1913
1963
|
});
|
|
1914
1964
|
}
|
|
1915
1965
|
async run(command, options = {}) {
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1966
|
+
const unsupported = Object.keys(options).find(
|
|
1967
|
+
(key) => !["microvmConfigurationId", "timeoutMs", "requestTimeoutMs", "signal", "onPhase"].includes(
|
|
1968
|
+
key
|
|
1969
|
+
)
|
|
1920
1970
|
);
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
} catch (cleanupFailure) {
|
|
1941
|
-
if (!executionOutcome.ok) {
|
|
1942
|
-
throw new AggregateError(
|
|
1943
|
-
[executionOutcome.error, cleanupFailure],
|
|
1944
|
-
"Sandbox execution and destruction both failed."
|
|
1945
|
-
);
|
|
1946
|
-
}
|
|
1947
|
-
throw cleanupFailure;
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
if (!executionOutcome.ok) {
|
|
1951
|
-
throw executionOutcome.error;
|
|
1952
|
-
}
|
|
1953
|
-
return executionOutcome.result;
|
|
1971
|
+
if (unsupported !== void 0) throw new TypeError(`Unsupported run option: ${unsupported}.`);
|
|
1972
|
+
const validatedCommand = nonEmptyString(command, "command");
|
|
1973
|
+
const timeoutMs = options.timeoutMs === void 0 ? void 0 : positiveMilliseconds(options.timeoutMs, "timeoutMs");
|
|
1974
|
+
const microvmConfigurationId = options.microvmConfigurationId === void 0 ? void 0 : nonEmptyString(options.microvmConfigurationId, "microvmConfigurationId");
|
|
1975
|
+
reportRunPhase(options.onPhase, "running");
|
|
1976
|
+
const result = parseExecutionResult(
|
|
1977
|
+
await this.#request("v1/run", {
|
|
1978
|
+
method: "POST",
|
|
1979
|
+
body: {
|
|
1980
|
+
command: validatedCommand,
|
|
1981
|
+
...microvmConfigurationId === void 0 ? {} : { microvmConfigurationId },
|
|
1982
|
+
...timeoutMs === void 0 ? {} : { timeoutMs }
|
|
1983
|
+
},
|
|
1984
|
+
signal: options.signal,
|
|
1985
|
+
requestTimeoutMs: options.requestTimeoutMs ?? this.#explicitRequestTimeoutMs ?? Math.max(DEFAULT_RUN_REQUEST_TIMEOUT_MS, (timeoutMs ?? 0) + 12e4)
|
|
1986
|
+
})
|
|
1987
|
+
);
|
|
1988
|
+
reportRunPhase(options.onPhase, "complete");
|
|
1989
|
+
return result;
|
|
1954
1990
|
}
|
|
1955
1991
|
async #request(path3, options) {
|
|
1956
1992
|
return requestJson(
|
|
@@ -1958,7 +1994,8 @@ var SetsunaClient = class {
|
|
|
1958
1994
|
this.#requestTimeoutMs,
|
|
1959
1995
|
this.#getAccessToken,
|
|
1960
1996
|
path3,
|
|
1961
|
-
options
|
|
1997
|
+
options,
|
|
1998
|
+
this.#affinity
|
|
1962
1999
|
);
|
|
1963
2000
|
}
|
|
1964
2001
|
};
|
|
@@ -1979,7 +2016,7 @@ async function runWithSignalCleanup(client, command, options, io, signalRuntime)
|
|
|
1979
2016
|
const exitCode = signal === "SIGINT" ? 130 : 143;
|
|
1980
2017
|
if (interruptedExitCode === void 0) {
|
|
1981
2018
|
interruptedExitCode = exitCode;
|
|
1982
|
-
io.errorOutput.write("Interrupt received;
|
|
2019
|
+
io.errorOutput.write("Interrupt received; Setsuna owns run cleanup.\n");
|
|
1983
2020
|
controller.abort();
|
|
1984
2021
|
return;
|
|
1985
2022
|
}
|
|
@@ -2081,98 +2118,19 @@ function enterRawMode(input) {
|
|
|
2081
2118
|
const wasPaused = input.isPaused();
|
|
2082
2119
|
input.setRawMode(true);
|
|
2083
2120
|
input.resume();
|
|
2084
|
-
let restored
|
|
2121
|
+
let restored;
|
|
2085
2122
|
return () => {
|
|
2086
|
-
|
|
2087
|
-
restored
|
|
2088
|
-
input.setRawMode?.(wasRaw);
|
|
2089
|
-
if (wasPaused) input.pause();
|
|
2123
|
+
restored ??= restoreInput(input, wasRaw, wasPaused);
|
|
2124
|
+
return restored;
|
|
2090
2125
|
};
|
|
2091
2126
|
}
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
now: Date.now,
|
|
2097
|
-
setTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
2098
|
-
clearTimeout: (handle) => clearTimeout(handle)
|
|
2099
|
-
};
|
|
2100
|
-
var ActiveLeaseKeeper = class {
|
|
2101
|
-
#client;
|
|
2102
|
-
#sandboxId;
|
|
2103
|
-
#runtime;
|
|
2104
|
-
#expiresAtMs;
|
|
2105
|
-
#failureHandler;
|
|
2106
|
-
#running = false;
|
|
2107
|
-
#timer;
|
|
2108
|
-
constructor(client, sandboxId, runtime = SYSTEM_RUNTIME) {
|
|
2109
|
-
this.#client = client;
|
|
2110
|
-
this.#sandboxId = sandboxId;
|
|
2111
|
-
this.#runtime = runtime;
|
|
2112
|
-
}
|
|
2113
|
-
async renewNow() {
|
|
2114
|
-
if (this.#running) throw new TypeError("Cannot prime a running lease keeper.");
|
|
2115
|
-
this.#expiresAtMs = await this.#renewExpiry();
|
|
2116
|
-
}
|
|
2117
|
-
start(onFailure) {
|
|
2118
|
-
if (this.#running) throw new TypeError("The lease keeper is already running.");
|
|
2119
|
-
if (this.#expiresAtMs === void 0) {
|
|
2120
|
-
throw new TypeError("The lease keeper must renew the sandbox before it starts.");
|
|
2121
|
-
}
|
|
2122
|
-
this.#running = true;
|
|
2123
|
-
this.#failureHandler = onFailure;
|
|
2124
|
-
try {
|
|
2125
|
-
this.#schedule(this.#expiresAtMs);
|
|
2126
|
-
} catch (error) {
|
|
2127
|
-
this.#running = false;
|
|
2128
|
-
this.#failureHandler = void 0;
|
|
2129
|
-
throw error;
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
stop() {
|
|
2133
|
-
this.#running = false;
|
|
2134
|
-
this.#failureHandler = void 0;
|
|
2135
|
-
if (this.#timer !== void 0) this.#runtime.clearTimeout(this.#timer);
|
|
2136
|
-
this.#timer = void 0;
|
|
2137
|
-
}
|
|
2138
|
-
async #renewExpiry() {
|
|
2139
|
-
const { expiresAt } = await this.#client.renew(this.#sandboxId);
|
|
2140
|
-
const expiresAtMs = Date.parse(expiresAt);
|
|
2141
|
-
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= this.#runtime.now()) {
|
|
2142
|
-
throw new TypeError("Sandbox renewal returned an invalid or expired expiresAt value.");
|
|
2143
|
-
}
|
|
2144
|
-
return expiresAtMs;
|
|
2145
|
-
}
|
|
2146
|
-
#schedule(expiresAtMs) {
|
|
2147
|
-
const remainingMs = expiresAtMs - this.#runtime.now();
|
|
2148
|
-
if (!Number.isFinite(remainingMs) || remainingMs <= 0) {
|
|
2149
|
-
throw new TypeError(
|
|
2150
|
-
"The renewed sandbox lease expired before its next renewal was scheduled."
|
|
2151
|
-
);
|
|
2152
|
-
}
|
|
2153
|
-
const delayMs = Math.min(MAX_TIMER_DELAY_MS, Math.max(1, Math.floor(remainingMs / 2)));
|
|
2154
|
-
this.#timer = this.#runtime.setTimeout(() => {
|
|
2155
|
-
this.#timer = void 0;
|
|
2156
|
-
void this.#renewAndSchedule().catch((error) => this.#fail(error));
|
|
2157
|
-
}, delayMs);
|
|
2158
|
-
}
|
|
2159
|
-
async #renewAndSchedule() {
|
|
2160
|
-
const expiresAtMs = await this.#renewExpiry();
|
|
2161
|
-
if (!this.#running) return;
|
|
2162
|
-
this.#expiresAtMs = expiresAtMs;
|
|
2163
|
-
this.#schedule(expiresAtMs);
|
|
2164
|
-
}
|
|
2165
|
-
#fail(error) {
|
|
2166
|
-
if (!this.#running) return;
|
|
2167
|
-
this.#running = false;
|
|
2168
|
-
const handler = this.#failureHandler;
|
|
2169
|
-
this.#failureHandler = void 0;
|
|
2170
|
-
try {
|
|
2171
|
-
handler?.(error);
|
|
2172
|
-
} catch {
|
|
2173
|
-
}
|
|
2127
|
+
async function restoreInput(input, wasRaw, wasPaused) {
|
|
2128
|
+
if (wasPaused) {
|
|
2129
|
+
input.pause();
|
|
2130
|
+
await new Promise((resolve) => process.nextTick(resolve));
|
|
2174
2131
|
}
|
|
2175
|
-
|
|
2132
|
+
input.setRawMode?.(wasRaw);
|
|
2133
|
+
}
|
|
2176
2134
|
|
|
2177
2135
|
// ../setsuna/src/cli-shell.ts
|
|
2178
2136
|
var DETACH_BYTE = 29;
|
|
@@ -2194,7 +2152,7 @@ async function runShellCliCommand(commandName, commandArguments, attachProcessId
|
|
|
2194
2152
|
);
|
|
2195
2153
|
return true;
|
|
2196
2154
|
}
|
|
2197
|
-
async function runShellCommand(commandArguments, attachProcessId, client, io, terminal
|
|
2155
|
+
async function runShellCommand(commandArguments, attachProcessId, client, io, terminal) {
|
|
2198
2156
|
if (commandArguments.length !== 1 || commandArguments[0] === "") {
|
|
2199
2157
|
throw new TypeError("setsuna shell requires exactly one sandbox ID.");
|
|
2200
2158
|
}
|
|
@@ -2203,21 +2161,9 @@ async function runShellCommand(commandArguments, attachProcessId, client, io, te
|
|
|
2203
2161
|
}
|
|
2204
2162
|
if (attachProcessId === "") throw new TypeError("--attach requires a process ID.");
|
|
2205
2163
|
const sandboxId = commandArguments[0];
|
|
2206
|
-
const
|
|
2207
|
-
await
|
|
2208
|
-
|
|
2209
|
-
const size = terminal.size();
|
|
2210
|
-
const processId = await resolveProcess(
|
|
2211
|
-
client,
|
|
2212
|
-
sandboxId,
|
|
2213
|
-
attachProcessId,
|
|
2214
|
-
size,
|
|
2215
|
-
terminal.term()
|
|
2216
|
-
);
|
|
2217
|
-
await attachTerminal(client, sandboxId, processId, io, terminal, leaseKeeper);
|
|
2218
|
-
} finally {
|
|
2219
|
-
leaseKeeper.stop();
|
|
2220
|
-
}
|
|
2164
|
+
const size = terminal.size();
|
|
2165
|
+
const processId = await resolveProcess(client, sandboxId, attachProcessId, size, terminal.term());
|
|
2166
|
+
await attachTerminal(client, sandboxId, processId, io, terminal);
|
|
2221
2167
|
}
|
|
2222
2168
|
async function resolveProcess(client, sandboxId, attachProcessId, size, localTerm) {
|
|
2223
2169
|
if (attachProcessId === void 0) {
|
|
@@ -2240,21 +2186,12 @@ async function resolveProcess(client, sandboxId, attachProcessId, size, localTer
|
|
|
2240
2186
|
if (process2.state === "running") await client.resizeProcess(sandboxId, attachProcessId, size);
|
|
2241
2187
|
return attachProcessId;
|
|
2242
2188
|
}
|
|
2243
|
-
async function attachTerminal(client, sandboxId, processId, io, terminal
|
|
2189
|
+
async function attachTerminal(client, sandboxId, processId, io, terminal) {
|
|
2244
2190
|
const controller = new AbortController();
|
|
2245
|
-
const session = createAttachedSession(
|
|
2246
|
-
client,
|
|
2247
|
-
sandboxId,
|
|
2248
|
-
processId,
|
|
2249
|
-
io,
|
|
2250
|
-
terminal,
|
|
2251
|
-
controller,
|
|
2252
|
-
() => leaseKeeper.stop()
|
|
2253
|
-
);
|
|
2191
|
+
const session = createAttachedSession(client, sandboxId, processId, io, terminal, controller);
|
|
2254
2192
|
let exitFrame;
|
|
2255
2193
|
let attachError;
|
|
2256
2194
|
try {
|
|
2257
|
-
leaseKeeper.start(session.fail);
|
|
2258
2195
|
for await (const frame of client.attachProcess(sandboxId, processId, {
|
|
2259
2196
|
maxStreamBytes: Number.MAX_SAFE_INTEGER,
|
|
2260
2197
|
signal: controller.signal
|
|
@@ -2281,20 +2218,20 @@ async function attachTerminal(client, sandboxId, processId, io, terminal, leaseK
|
|
|
2281
2218
|
}
|
|
2282
2219
|
if (exitFrame.exitCode !== null) terminal.setExitCode(exitFrame.exitCode);
|
|
2283
2220
|
}
|
|
2284
|
-
function createAttachedSession(client, sandboxId, processId, io, terminal, controller
|
|
2221
|
+
function createAttachedSession(client, sandboxId, processId, io, terminal, controller) {
|
|
2285
2222
|
let detached = false;
|
|
2286
2223
|
let failure;
|
|
2287
2224
|
let cleaned = false;
|
|
2288
2225
|
let inputQueue = Promise.resolve();
|
|
2289
2226
|
let resizeQueue = Promise.resolve();
|
|
2227
|
+
let restored;
|
|
2290
2228
|
const restoreRawMode = terminal.enterRawMode();
|
|
2291
2229
|
const cleanup = () => {
|
|
2292
2230
|
if (cleaned) return;
|
|
2293
2231
|
cleaned = true;
|
|
2294
2232
|
io.input.off("data", onInput);
|
|
2295
2233
|
terminal.offResize(onResize);
|
|
2296
|
-
|
|
2297
|
-
restoreRawMode();
|
|
2234
|
+
restored = restoreRawMode();
|
|
2298
2235
|
};
|
|
2299
2236
|
const fail = (error) => {
|
|
2300
2237
|
if (failure === void 0) failure = error;
|
|
@@ -2339,8 +2276,10 @@ function createAttachedSession(client, sandboxId, processId, io, terminal, contr
|
|
|
2339
2276
|
get failure() {
|
|
2340
2277
|
return failure;
|
|
2341
2278
|
},
|
|
2279
|
+
// Everything the session started has finished, including handing the terminal back.
|
|
2342
2280
|
async settle() {
|
|
2343
2281
|
await Promise.allSettled([inputQueue, resizeQueue]);
|
|
2282
|
+
await restored;
|
|
2344
2283
|
}
|
|
2345
2284
|
};
|
|
2346
2285
|
}
|
|
@@ -2382,11 +2321,12 @@ function isDetachAbort(error) {
|
|
|
2382
2321
|
// ../setsuna/src/cli-interactive-persistent-actions.ts
|
|
2383
2322
|
function createPersistentInteractiveActions(client, io, shellTerminal) {
|
|
2384
2323
|
return {
|
|
2385
|
-
async startMicrovm(microvmConfigurationId) {
|
|
2386
|
-
const lease = await client.create({ microvmConfigurationId });
|
|
2324
|
+
async startMicrovm(microvmConfigurationId, duration) {
|
|
2325
|
+
const lease = await client.create({ microvmConfigurationId, duration });
|
|
2387
2326
|
return {
|
|
2388
2327
|
sandboxId: lease.sandboxId,
|
|
2389
2328
|
expiresAt: lease.expiresAt,
|
|
2329
|
+
duration: lease.duration,
|
|
2390
2330
|
vcpuCount: lease.microvmConfiguration.vcpuCount,
|
|
2391
2331
|
memoryMiB: lease.microvmConfiguration.memoryMiB
|
|
2392
2332
|
};
|
|
@@ -2403,13 +2343,11 @@ function createPersistentInteractiveActions(client, io, shellTerminal) {
|
|
|
2403
2343
|
return (await client.listProcesses(sandboxId)).filter((process2) => process2.state === "running" && process2.terminal !== void 0).map(({ processId, pid }) => ({ processId, pid }));
|
|
2404
2344
|
},
|
|
2405
2345
|
async runPersistentCommand(sandboxId, command, timeoutMs) {
|
|
2406
|
-
await client.renew(sandboxId);
|
|
2407
2346
|
const result = await client.execute(sandboxId, command, {
|
|
2408
2347
|
...timeoutMs === void 0 ? {} : { timeoutMs }
|
|
2409
2348
|
});
|
|
2410
2349
|
return interactiveRunResult(result);
|
|
2411
2350
|
},
|
|
2412
|
-
renewMicrovm: (sandboxId) => client.renew(sandboxId),
|
|
2413
2351
|
destroyMicrovm: (sandboxId) => client.destroy(sandboxId)
|
|
2414
2352
|
};
|
|
2415
2353
|
}
|
|
@@ -2468,19 +2406,9 @@ var DEFAULT_SIGNAL_RUNTIME = {
|
|
|
2468
2406
|
|
|
2469
2407
|
// ../setsuna/src/tui/customer/format.ts
|
|
2470
2408
|
var STANDALONE_BOX = { withGuide: false, width: "auto" };
|
|
2409
|
+
var EXIT_MESSAGE = "Sayonara!";
|
|
2471
2410
|
function formatRunPhase(phase) {
|
|
2472
|
-
|
|
2473
|
-
case "starting-microvm":
|
|
2474
|
-
return "Starting microVM";
|
|
2475
|
-
case "microvm-ready":
|
|
2476
|
-
return "microVM ready";
|
|
2477
|
-
case "running-command":
|
|
2478
|
-
return "Running command";
|
|
2479
|
-
case "destroying-microvm":
|
|
2480
|
-
return "Cleaning up";
|
|
2481
|
-
case "microvm-destroyed":
|
|
2482
|
-
return "microVM destroyed";
|
|
2483
|
-
}
|
|
2411
|
+
return phase === "running" ? "Running on Setsuna" : "Complete";
|
|
2484
2412
|
}
|
|
2485
2413
|
function formatRunResult(result) {
|
|
2486
2414
|
const facts = [`Exit status ${result.exitStatus}`, `${result.durationMs} ms`];
|
|
@@ -2519,6 +2447,10 @@ function formatMicrovmState(microvm, now) {
|
|
|
2519
2447
|
if (microvm.vcpuCount !== void 0 && microvm.memoryMiB !== void 0) {
|
|
2520
2448
|
lines.push(`Compute ${formatMicrovmShape(microvm.vcpuCount, microvm.memoryMiB)}`);
|
|
2521
2449
|
}
|
|
2450
|
+
if (microvm.duration !== void 0) {
|
|
2451
|
+
lines.push(`Duration ${microvm.duration}`);
|
|
2452
|
+
return lines.join("\n");
|
|
2453
|
+
}
|
|
2522
2454
|
const remaining = remainingTime(microvm.expiresAt, now);
|
|
2523
2455
|
lines.push(remaining === void 0 ? "Expired" : `Expires in ${remaining}`);
|
|
2524
2456
|
return lines.join("\n");
|
|
@@ -2634,7 +2566,7 @@ async function ensureSignedIn(io, auth, promptUi, session = {}) {
|
|
|
2634
2566
|
return false;
|
|
2635
2567
|
}
|
|
2636
2568
|
if (choice === "exit") {
|
|
2637
|
-
promptUi.outro(
|
|
2569
|
+
promptUi.outro(EXIT_MESSAGE, io);
|
|
2638
2570
|
return false;
|
|
2639
2571
|
}
|
|
2640
2572
|
status = await signIn(io, auth, promptUi);
|
|
@@ -2779,7 +2711,7 @@ async function promptRunAgain(io, promptUi) {
|
|
|
2779
2711
|
);
|
|
2780
2712
|
if (next === TUI_CANCEL || next === "back") return "back-to-main";
|
|
2781
2713
|
if (next === "exit") {
|
|
2782
|
-
promptUi.outro(
|
|
2714
|
+
promptUi.outro(EXIT_MESSAGE, io);
|
|
2783
2715
|
return "exit-tui";
|
|
2784
2716
|
}
|
|
2785
2717
|
return "prompt-new-command";
|
|
@@ -2834,12 +2766,25 @@ async function startMicrovmMenu(io, actions, promptUi) {
|
|
|
2834
2766
|
const configurations = (await actions.microvmConfigurations()).configurations;
|
|
2835
2767
|
const configuration = await promptForMicrovmConfiguration(io, promptUi, configurations);
|
|
2836
2768
|
if (configuration === null) return;
|
|
2837
|
-
const
|
|
2769
|
+
const duration = await promptUi.select(
|
|
2770
|
+
{
|
|
2771
|
+
message: "Duration",
|
|
2772
|
+
options: [
|
|
2773
|
+
{ value: "20m", label: "20 minutes" },
|
|
2774
|
+
{ value: "1h", label: "1 hour" },
|
|
2775
|
+
{ value: "2h", label: "2 hours" }
|
|
2776
|
+
],
|
|
2777
|
+
initialValue: "20m"
|
|
2778
|
+
},
|
|
2779
|
+
io
|
|
2780
|
+
);
|
|
2781
|
+
if (duration === TUI_CANCEL) return;
|
|
2782
|
+
const sandbox = await startSandbox(io, actions, promptUi, configuration.id, duration);
|
|
2838
2783
|
if (sandbox === void 0) return;
|
|
2839
2784
|
promptUi.box(formatMicrovmState(sandbox, Date.now()), "microVM ready", io, STANDALONE_BOX);
|
|
2840
2785
|
await manageSandbox(io, actions, promptUi, sandbox);
|
|
2841
2786
|
}
|
|
2842
|
-
async function startSandbox(io, actions, promptUi, microvmConfigurationId) {
|
|
2787
|
+
async function startSandbox(io, actions, promptUi, microvmConfigurationId, duration) {
|
|
2843
2788
|
let interrupted = false;
|
|
2844
2789
|
const spinner = promptUi.spinner(io, {
|
|
2845
2790
|
indicator: "timer",
|
|
@@ -2850,7 +2795,7 @@ async function startSandbox(io, actions, promptUi, microvmConfigurationId) {
|
|
|
2850
2795
|
spinner.start("Starting microVM");
|
|
2851
2796
|
let sandbox;
|
|
2852
2797
|
try {
|
|
2853
|
-
sandbox = await actions.startMicrovm(microvmConfigurationId);
|
|
2798
|
+
sandbox = await actions.startMicrovm(microvmConfigurationId, duration);
|
|
2854
2799
|
} catch (error) {
|
|
2855
2800
|
spinner.error(formatTuiError(error));
|
|
2856
2801
|
return void 0;
|
|
@@ -2876,7 +2821,6 @@ async function activeMicrovmsMenu(io, actions, promptUi) {
|
|
|
2876
2821
|
}
|
|
2877
2822
|
}
|
|
2878
2823
|
async function selectSandbox(io, promptUi, sandboxes) {
|
|
2879
|
-
const now = Date.now();
|
|
2880
2824
|
const selected = await promptUi.select(
|
|
2881
2825
|
{
|
|
2882
2826
|
message: "Active microVMs",
|
|
@@ -2884,7 +2828,7 @@ async function selectSandbox(io, promptUi, sandboxes) {
|
|
|
2884
2828
|
...sandboxes.map((sandbox, index) => ({
|
|
2885
2829
|
value: `sandbox:${index}`,
|
|
2886
2830
|
label: sandbox.sandboxId,
|
|
2887
|
-
hint: formatExpiry(sandbox.expiresAt, now)
|
|
2831
|
+
hint: (now) => formatExpiry(sandbox.expiresAt, now)
|
|
2888
2832
|
})),
|
|
2889
2833
|
{ value: "back", label: "Back" }
|
|
2890
2834
|
]
|
|
@@ -2897,15 +2841,14 @@ async function selectSandbox(io, promptUi, sandboxes) {
|
|
|
2897
2841
|
async function manageSandbox(io, actions, promptUi, initial) {
|
|
2898
2842
|
let microvm = initial;
|
|
2899
2843
|
while (true) {
|
|
2900
|
-
const
|
|
2844
|
+
const { sandboxId, expiresAt } = microvm;
|
|
2901
2845
|
const choice = await promptUi.select(
|
|
2902
2846
|
{
|
|
2903
|
-
message: `Manage microVM ${
|
|
2847
|
+
message: (now) => `Manage microVM ${sandboxId} \xB7 ${formatExpiry(expiresAt, now)}`,
|
|
2904
2848
|
options: [
|
|
2905
2849
|
{ value: "open-terminal", label: "Open terminal" },
|
|
2906
2850
|
{ value: "reattach-terminal", label: "Reattach terminal" },
|
|
2907
2851
|
{ value: "run-command", label: "Run command" },
|
|
2908
|
-
{ value: "renew", label: "Renew" },
|
|
2909
2852
|
{ value: "destroy", label: "Destroy microVM" },
|
|
2910
2853
|
{ value: "back", label: "Back" }
|
|
2911
2854
|
],
|
|
@@ -2926,33 +2869,17 @@ async function manageSandbox(io, actions, promptUi, initial) {
|
|
|
2926
2869
|
async function dispatchSandboxChoice(choice, microvm, io, actions, promptUi) {
|
|
2927
2870
|
if (choice === "open-terminal") {
|
|
2928
2871
|
await actions.openTerminal(microvm.sandboxId);
|
|
2929
|
-
return
|
|
2872
|
+
return microvm;
|
|
2930
2873
|
}
|
|
2931
2874
|
if (choice === "reattach-terminal") {
|
|
2932
|
-
|
|
2933
|
-
return
|
|
2875
|
+
await reattachTerminal(io, actions, promptUi, microvm.sandboxId);
|
|
2876
|
+
return microvm;
|
|
2934
2877
|
}
|
|
2935
2878
|
if (choice === "run-command") {
|
|
2936
|
-
|
|
2937
|
-
return ran ? refreshExpiry(actions, microvm) : microvm;
|
|
2938
|
-
}
|
|
2939
|
-
if (choice === "renew") return renewSandbox(io, actions, promptUi, microvm);
|
|
2940
|
-
return await destroySandbox(io, actions, promptUi, microvm.sandboxId) ? void 0 : microvm;
|
|
2941
|
-
}
|
|
2942
|
-
async function renewSandbox(io, actions, promptUi, microvm) {
|
|
2943
|
-
const status = await actions.renewMicrovm(microvm.sandboxId);
|
|
2944
|
-
promptUi.success(`Renewed \xB7 ${formatExpiry(status.expiresAt, Date.now())}`, io);
|
|
2945
|
-
return { ...microvm, expiresAt: status.expiresAt };
|
|
2946
|
-
}
|
|
2947
|
-
async function refreshExpiry(actions, microvm) {
|
|
2948
|
-
try {
|
|
2949
|
-
const current = (await actions.activeMicrovms()).find(
|
|
2950
|
-
({ sandboxId }) => sandboxId === microvm.sandboxId
|
|
2951
|
-
);
|
|
2952
|
-
return current === void 0 ? microvm : { ...microvm, expiresAt: current.expiresAt };
|
|
2953
|
-
} catch {
|
|
2879
|
+
await runPersistentCommand(io, actions, promptUi, microvm.sandboxId);
|
|
2954
2880
|
return microvm;
|
|
2955
2881
|
}
|
|
2882
|
+
return await destroySandbox(io, actions, promptUi, microvm.sandboxId) ? void 0 : microvm;
|
|
2956
2883
|
}
|
|
2957
2884
|
async function reattachTerminal(io, actions, promptUi, sandboxId) {
|
|
2958
2885
|
const processes = await actions.terminalProcesses(sandboxId);
|
|
@@ -3049,10 +2976,10 @@ async function mainMenu(io, actions, promptUi, session) {
|
|
|
3049
2976
|
{
|
|
3050
2977
|
message: "What would you like to do?",
|
|
3051
2978
|
options: [
|
|
3052
|
-
{ value: "start", label: "
|
|
3053
|
-
{ value: "active", label: "Active microVMs", hint: "
|
|
3054
|
-
{ value: "run", label: "
|
|
3055
|
-
{ value: "account", label: "Account"
|
|
2979
|
+
{ value: "start", label: "New microVM", hint: "For interactive or ongoing work" },
|
|
2980
|
+
{ value: "active", label: "Active microVMs", hint: "Resume or manage your microVMs" },
|
|
2981
|
+
{ value: "run", label: "One-off run", hint: "Isolated single-run workload" },
|
|
2982
|
+
{ value: "account", label: "Account" },
|
|
3056
2983
|
{ value: "exit", label: "Exit" }
|
|
3057
2984
|
],
|
|
3058
2985
|
initialValue: "start",
|
|
@@ -3065,7 +2992,7 @@ async function mainMenu(io, actions, promptUi, session) {
|
|
|
3065
2992
|
return "exit";
|
|
3066
2993
|
}
|
|
3067
2994
|
if (choice === "exit") {
|
|
3068
|
-
promptUi.outro(
|
|
2995
|
+
promptUi.outro(EXIT_MESSAGE, io);
|
|
3069
2996
|
return "exit";
|
|
3070
2997
|
}
|
|
3071
2998
|
try {
|
|
@@ -3091,68 +3018,18 @@ async function dispatchMainChoice(choice, io, actions, promptUi, session) {
|
|
|
3091
3018
|
return await accountMenu(io, actions.auth, promptUi, session) === "signed-out" ? "signed-out" : "continue";
|
|
3092
3019
|
}
|
|
3093
3020
|
|
|
3094
|
-
// ../setsuna/src/cli-network-options.ts
|
|
3095
|
-
var NETWORK_OPTION_NAMES = [
|
|
3096
|
-
"network-mode",
|
|
3097
|
-
"host-ip",
|
|
3098
|
-
"guest-ip",
|
|
3099
|
-
"prefix-length",
|
|
3100
|
-
"uplink-interface",
|
|
3101
|
-
"dns-resolvers",
|
|
3102
|
-
"mtu"
|
|
3103
|
-
];
|
|
3104
|
-
function routedNetworkFromCli(parsed) {
|
|
3105
|
-
if (!NETWORK_OPTION_NAMES.some((name) => parsed.values[name] !== void 0)) return void 0;
|
|
3106
|
-
if (parsed.values["network-mode"] !== "routed") {
|
|
3107
|
-
throw new TypeError("--network-mode must be routed.");
|
|
3108
|
-
}
|
|
3109
|
-
const hostIp = required(parsed.values["host-ip"], "--host-ip");
|
|
3110
|
-
const guestIp = required(parsed.values["guest-ip"], "--guest-ip");
|
|
3111
|
-
const prefixLength = integer(parsed.values["prefix-length"], "--prefix-length");
|
|
3112
|
-
if (prefixLength !== 30) throw new TypeError("--prefix-length must be 30.");
|
|
3113
|
-
const uplinkInterface = required(parsed.values["uplink-interface"], "--uplink-interface");
|
|
3114
|
-
const dnsResolvers = parsed.values["dns-resolvers"]?.split(",");
|
|
3115
|
-
const mtu = optionalInteger2(parsed.values.mtu, "--mtu");
|
|
3116
|
-
return {
|
|
3117
|
-
mode: "routed",
|
|
3118
|
-
hostIp,
|
|
3119
|
-
guestIp,
|
|
3120
|
-
prefixLength: 30,
|
|
3121
|
-
uplinkInterface,
|
|
3122
|
-
...dnsResolvers === void 0 ? {} : { dnsResolvers },
|
|
3123
|
-
...mtu === void 0 ? {} : { mtu }
|
|
3124
|
-
};
|
|
3125
|
-
}
|
|
3126
|
-
function required(value, name) {
|
|
3127
|
-
if (value === void 0 || value === "") throw new TypeError(`${name} is required.`);
|
|
3128
|
-
return value;
|
|
3129
|
-
}
|
|
3130
|
-
function integer(value, name) {
|
|
3131
|
-
if (value === void 0 || !/^[0-9]+$/u.test(value)) {
|
|
3132
|
-
throw new TypeError(`${name} must be an integer.`);
|
|
3133
|
-
}
|
|
3134
|
-
const parsed = Number(value);
|
|
3135
|
-
if (!Number.isSafeInteger(parsed)) throw new TypeError(`${name} is too large.`);
|
|
3136
|
-
return parsed;
|
|
3137
|
-
}
|
|
3138
|
-
function optionalInteger2(value, name) {
|
|
3139
|
-
return value === void 0 ? void 0 : integer(value, name);
|
|
3140
|
-
}
|
|
3141
|
-
|
|
3142
3021
|
// ../setsuna/src/cli-commands.ts
|
|
3143
3022
|
async function runRunCommand(commandName, commandArguments, parsed, client, requestTimeoutMs, io, options) {
|
|
3144
3023
|
if (commandName !== "run") throw new TypeError(`Unknown command: ${commandName ?? ""}`);
|
|
3145
3024
|
if (commandArguments.length === 0)
|
|
3146
3025
|
throw new TypeError("setsuna run requires a command after --.");
|
|
3147
|
-
const network = routedNetworkFromCli(parsed);
|
|
3148
3026
|
const result = await runWithSignalCleanup(
|
|
3149
3027
|
client,
|
|
3150
3028
|
commandArguments.map(shellQuote).join(" "),
|
|
3151
3029
|
{
|
|
3152
3030
|
timeoutMs: optionalInteger(parsed.values["timeout-ms"], "--timeout-ms"),
|
|
3153
3031
|
requestTimeoutMs,
|
|
3154
|
-
...parsed.values["configuration-id"] === void 0 ? {} : { microvmConfigurationId: parsed.values["configuration-id"] }
|
|
3155
|
-
...network === void 0 ? {} : { network }
|
|
3032
|
+
...parsed.values["configuration-id"] === void 0 ? {} : { microvmConfigurationId: parsed.values["configuration-id"] }
|
|
3156
3033
|
},
|
|
3157
3034
|
io,
|
|
3158
3035
|
options.signalRuntime ?? DEFAULT_SIGNAL_RUNTIME
|
|
@@ -3295,8 +3172,56 @@ function exactly(arguments_, count, message) {
|
|
|
3295
3172
|
return arguments_;
|
|
3296
3173
|
}
|
|
3297
3174
|
|
|
3175
|
+
// ../setsuna/src/cli-network-options.ts
|
|
3176
|
+
var NETWORK_OPTION_NAMES = [
|
|
3177
|
+
"network-mode",
|
|
3178
|
+
"host-ip",
|
|
3179
|
+
"guest-ip",
|
|
3180
|
+
"prefix-length",
|
|
3181
|
+
"uplink-interface",
|
|
3182
|
+
"dns-resolvers",
|
|
3183
|
+
"mtu"
|
|
3184
|
+
];
|
|
3185
|
+
function routedNetworkFromCli(parsed) {
|
|
3186
|
+
if (!NETWORK_OPTION_NAMES.some((name) => parsed.values[name] !== void 0)) return void 0;
|
|
3187
|
+
if (parsed.values["network-mode"] !== "routed") {
|
|
3188
|
+
throw new TypeError("--network-mode must be routed.");
|
|
3189
|
+
}
|
|
3190
|
+
const hostIp = required(parsed.values["host-ip"], "--host-ip");
|
|
3191
|
+
const guestIp = required(parsed.values["guest-ip"], "--guest-ip");
|
|
3192
|
+
const prefixLength = integer(parsed.values["prefix-length"], "--prefix-length");
|
|
3193
|
+
if (prefixLength !== 30) throw new TypeError("--prefix-length must be 30.");
|
|
3194
|
+
const uplinkInterface = required(parsed.values["uplink-interface"], "--uplink-interface");
|
|
3195
|
+
const dnsResolvers = parsed.values["dns-resolvers"]?.split(",");
|
|
3196
|
+
const mtu = optionalInteger2(parsed.values.mtu, "--mtu");
|
|
3197
|
+
return {
|
|
3198
|
+
mode: "routed",
|
|
3199
|
+
hostIp,
|
|
3200
|
+
guestIp,
|
|
3201
|
+
prefixLength: 30,
|
|
3202
|
+
uplinkInterface,
|
|
3203
|
+
...dnsResolvers === void 0 ? {} : { dnsResolvers },
|
|
3204
|
+
...mtu === void 0 ? {} : { mtu }
|
|
3205
|
+
};
|
|
3206
|
+
}
|
|
3207
|
+
function required(value, name) {
|
|
3208
|
+
if (value === void 0 || value === "") throw new TypeError(`${name} is required.`);
|
|
3209
|
+
return value;
|
|
3210
|
+
}
|
|
3211
|
+
function integer(value, name) {
|
|
3212
|
+
if (value === void 0 || !/^[0-9]+$/u.test(value)) {
|
|
3213
|
+
throw new TypeError(`${name} must be an integer.`);
|
|
3214
|
+
}
|
|
3215
|
+
const parsed = Number(value);
|
|
3216
|
+
if (!Number.isSafeInteger(parsed)) throw new TypeError(`${name} is too large.`);
|
|
3217
|
+
return parsed;
|
|
3218
|
+
}
|
|
3219
|
+
function optionalInteger2(value, name) {
|
|
3220
|
+
return value === void 0 ? void 0 : integer(value, name);
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3298
3223
|
// ../setsuna/src/cli-sandbox-commands.ts
|
|
3299
|
-
var SANDBOX_COMMANDS = /* @__PURE__ */ new Set(["create", "list", "status", "
|
|
3224
|
+
var SANDBOX_COMMANDS = /* @__PURE__ */ new Set(["create", "list", "status", "exec", "destroy"]);
|
|
3300
3225
|
async function runSandboxCommand(commandName, commandArguments, parsed, client, requestTimeoutMs, io, options) {
|
|
3301
3226
|
if (commandName === void 0 || !SANDBOX_COMMANDS.has(commandName)) return false;
|
|
3302
3227
|
if (commandName === "create") {
|
|
@@ -3304,6 +3229,7 @@ async function runSandboxCommand(commandName, commandArguments, parsed, client,
|
|
|
3304
3229
|
const network = routedNetworkFromCli(parsed);
|
|
3305
3230
|
writeLease(
|
|
3306
3231
|
await client.create({
|
|
3232
|
+
duration: parsed.values.duration ?? "20m",
|
|
3307
3233
|
...parsed.values["configuration-id"] === void 0 ? {} : { microvmConfigurationId: parsed.values["configuration-id"] },
|
|
3308
3234
|
...network === void 0 ? {} : { network }
|
|
3309
3235
|
}),
|
|
@@ -3317,9 +3243,6 @@ async function runSandboxCommand(commandName, commandArguments, parsed, client,
|
|
|
3317
3243
|
} else if (commandName === "status") {
|
|
3318
3244
|
const sandboxId = requireSandboxId(commandName, commandArguments);
|
|
3319
3245
|
writeStatus(await client.status(sandboxId), parsed.values.json, io.output, "Sandbox");
|
|
3320
|
-
} else if (commandName === "renew") {
|
|
3321
|
-
const sandboxId = requireSandboxId(commandName, commandArguments);
|
|
3322
|
-
writeStatus(await client.renew(sandboxId), parsed.values.json, io.output, "Renewed sandbox");
|
|
3323
3246
|
} else if (commandName === "destroy") {
|
|
3324
3247
|
const sandboxId = requireSandboxId(commandName, commandArguments);
|
|
3325
3248
|
await client.destroy(sandboxId);
|
|
@@ -3401,7 +3324,7 @@ function writeLease(lease, json, output, verb) {
|
|
|
3401
3324
|
`);
|
|
3402
3325
|
else {
|
|
3403
3326
|
output.write(
|
|
3404
|
-
`${verb} sandbox ${lease.sandboxId} with configuration ${lease.microvmConfiguration.id}; lease expires at ${lease.expiresAt}.
|
|
3327
|
+
`${verb} sandbox ${lease.sandboxId} with configuration ${lease.microvmConfiguration.id}; duration ${lease.duration}, expires at ${lease.expiresAt}.
|
|
3405
3328
|
`
|
|
3406
3329
|
);
|
|
3407
3330
|
}
|
|
@@ -3434,6 +3357,8 @@ function writeDestroyed(sandboxId, json, output) {
|
|
|
3434
3357
|
|
|
3435
3358
|
// ../setsuna/src/cli-client.ts
|
|
3436
3359
|
async function runClientCli(commandName, commandArguments, parsed, authenticated, io, options) {
|
|
3360
|
+
if (parsed.values.duration !== void 0 && commandName !== "create")
|
|
3361
|
+
throw new TypeError("--duration is supported only by setsuna create.");
|
|
3437
3362
|
const requestTimeoutMs = optionalInteger(
|
|
3438
3363
|
parsed.values["request-timeout-ms"],
|
|
3439
3364
|
"--request-timeout-ms"
|
|
@@ -3648,7 +3573,6 @@ function customerSafeSandboxActions(actions) {
|
|
|
3648
3573
|
openTerminal: customerSafe(actions.openTerminal),
|
|
3649
3574
|
terminalProcesses: customerSafe(actions.terminalProcesses),
|
|
3650
3575
|
runPersistentCommand: customerSafe(actions.runPersistentCommand),
|
|
3651
|
-
renewMicrovm: customerSafe(actions.renewMicrovm),
|
|
3652
3576
|
destroyMicrovm: customerSafe(actions.destroyMicrovm)
|
|
3653
3577
|
};
|
|
3654
3578
|
}
|
|
@@ -3677,10 +3601,9 @@ var PREVIEW_HELP = `Usage:
|
|
|
3677
3601
|
setsuna auth logout [--json]
|
|
3678
3602
|
setsuna health [--json]
|
|
3679
3603
|
setsuna configurations [--json]
|
|
3680
|
-
setsuna create [--configuration-id ID] [--request-timeout-ms MS] [--json]
|
|
3604
|
+
setsuna create [--duration 20m|1h|2h] [--configuration-id ID] [--request-timeout-ms MS] [--json]
|
|
3681
3605
|
setsuna list [--request-timeout-ms MS] [--json]
|
|
3682
3606
|
setsuna status SANDBOX_ID [--request-timeout-ms MS] [--json]
|
|
3683
|
-
setsuna renew SANDBOX_ID [--request-timeout-ms MS] [--json]
|
|
3684
3607
|
setsuna exec SANDBOX_ID [--timeout-ms MS] [--request-timeout-ms MS] [--json] -- COMMAND [ARG...]
|
|
3685
3608
|
setsuna destroy SANDBOX_ID [--request-timeout-ms MS] [--json]
|
|
3686
3609
|
setsuna shell SANDBOX_ID [--attach PROCESS_ID] [--request-timeout-ms MS]
|
|
@@ -3697,8 +3620,7 @@ Commands:
|
|
|
3697
3620
|
configurations List available microVM configurations.
|
|
3698
3621
|
create Create a persistent sandbox with a selected or default configuration.
|
|
3699
3622
|
list List active persistent sandboxes.
|
|
3700
|
-
status Inspect an active persistent sandbox
|
|
3701
|
-
renew Extend an active persistent sandbox lease.
|
|
3623
|
+
status Inspect an active persistent sandbox.
|
|
3702
3624
|
exec Execute a command without destroying the persistent sandbox.
|
|
3703
3625
|
destroy Explicitly destroy a persistent sandbox.
|
|
3704
3626
|
shell Open an interactive shell in a persistent sandbox, or reattach to one.
|