@tonbo/cli 0.14.1 → 0.15.0
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/dist/bin/tonbo.js +1158 -54
- package/package.json +1 -1
package/dist/bin/tonbo.js
CHANGED
|
@@ -574,6 +574,13 @@ var managementOperations = {
|
|
|
574
574
|
"query": [],
|
|
575
575
|
"idempotencyKey": false
|
|
576
576
|
},
|
|
577
|
+
"leaveYieldOrganization": {
|
|
578
|
+
"method": "POST",
|
|
579
|
+
"path": "/yield/leave",
|
|
580
|
+
"pathParams": [],
|
|
581
|
+
"query": [],
|
|
582
|
+
"idempotencyKey": false
|
|
583
|
+
},
|
|
577
584
|
"recordYieldMetric": {
|
|
578
585
|
"method": "POST",
|
|
579
586
|
"path": "/yield/metrics",
|
|
@@ -600,6 +607,13 @@ var managementOperations = {
|
|
|
600
607
|
"query": [],
|
|
601
608
|
"idempotencyKey": false
|
|
602
609
|
},
|
|
610
|
+
"createYieldPickup": {
|
|
611
|
+
"method": "POST",
|
|
612
|
+
"path": "/yield/pickups",
|
|
613
|
+
"pathParams": [],
|
|
614
|
+
"query": [],
|
|
615
|
+
"idempotencyKey": false
|
|
616
|
+
},
|
|
603
617
|
"listYieldQuestions": {
|
|
604
618
|
"method": "GET",
|
|
605
619
|
"path": "/yield/questions",
|
|
@@ -794,6 +808,16 @@ var managementOperations = {
|
|
|
794
808
|
"idempotencyKey": false,
|
|
795
809
|
"cli": "tonbo machine sleep"
|
|
796
810
|
},
|
|
811
|
+
"wakeMachine": {
|
|
812
|
+
"method": "POST",
|
|
813
|
+
"path": "/machines/{name}/wake",
|
|
814
|
+
"pathParams": [
|
|
815
|
+
"name"
|
|
816
|
+
],
|
|
817
|
+
"query": [],
|
|
818
|
+
"idempotencyKey": false,
|
|
819
|
+
"cli": "tonbo machine wake"
|
|
820
|
+
},
|
|
797
821
|
"listMachineActivations": {
|
|
798
822
|
"method": "GET",
|
|
799
823
|
"path": "/machines/{name}/activations",
|
|
@@ -924,6 +948,101 @@ var managementOperations = {
|
|
|
924
948
|
"cli": "tonbo machine file list",
|
|
925
949
|
"cell": true
|
|
926
950
|
},
|
|
951
|
+
"listMachineProcesses": {
|
|
952
|
+
"method": "GET",
|
|
953
|
+
"path": "/machines/{name}/processes",
|
|
954
|
+
"pathParams": [
|
|
955
|
+
"name"
|
|
956
|
+
],
|
|
957
|
+
"query": [],
|
|
958
|
+
"idempotencyKey": false,
|
|
959
|
+
"cli": "tonbo machine ps",
|
|
960
|
+
"cell": true
|
|
961
|
+
},
|
|
962
|
+
"startMachineProcess": {
|
|
963
|
+
"method": "POST",
|
|
964
|
+
"path": "/machines/{name}/processes",
|
|
965
|
+
"pathParams": [
|
|
966
|
+
"name"
|
|
967
|
+
],
|
|
968
|
+
"query": [],
|
|
969
|
+
"idempotencyKey": true,
|
|
970
|
+
"cli": "tonbo machine exec --detach",
|
|
971
|
+
"cell": true
|
|
972
|
+
},
|
|
973
|
+
"getMachineProcess": {
|
|
974
|
+
"method": "GET",
|
|
975
|
+
"path": "/machines/{name}/processes/{process}",
|
|
976
|
+
"pathParams": [
|
|
977
|
+
"name",
|
|
978
|
+
"process"
|
|
979
|
+
],
|
|
980
|
+
"query": [],
|
|
981
|
+
"idempotencyKey": false,
|
|
982
|
+
"cli": "tonbo machine wait",
|
|
983
|
+
"cell": true
|
|
984
|
+
},
|
|
985
|
+
"killMachineProcess": {
|
|
986
|
+
"method": "DELETE",
|
|
987
|
+
"path": "/machines/{name}/processes/{process}",
|
|
988
|
+
"pathParams": [
|
|
989
|
+
"name",
|
|
990
|
+
"process"
|
|
991
|
+
],
|
|
992
|
+
"query": [],
|
|
993
|
+
"idempotencyKey": false,
|
|
994
|
+
"cli": "tonbo machine kill",
|
|
995
|
+
"cell": true
|
|
996
|
+
},
|
|
997
|
+
"readMachineProcessOutput": {
|
|
998
|
+
"method": "GET",
|
|
999
|
+
"path": "/machines/{name}/processes/{process}/output",
|
|
1000
|
+
"pathParams": [
|
|
1001
|
+
"name",
|
|
1002
|
+
"process"
|
|
1003
|
+
],
|
|
1004
|
+
"query": [
|
|
1005
|
+
"offset"
|
|
1006
|
+
],
|
|
1007
|
+
"idempotencyKey": false,
|
|
1008
|
+
"cli": "tonbo machine output",
|
|
1009
|
+
"cell": true
|
|
1010
|
+
},
|
|
1011
|
+
"listMachineHolds": {
|
|
1012
|
+
"method": "GET",
|
|
1013
|
+
"path": "/machines/{name}/holds",
|
|
1014
|
+
"pathParams": [
|
|
1015
|
+
"name"
|
|
1016
|
+
],
|
|
1017
|
+
"query": [],
|
|
1018
|
+
"idempotencyKey": false,
|
|
1019
|
+
"cli": "tonbo machine hold ls",
|
|
1020
|
+
"cell": true
|
|
1021
|
+
},
|
|
1022
|
+
"setMachineHold": {
|
|
1023
|
+
"method": "PUT",
|
|
1024
|
+
"path": "/machines/{name}/holds/{hold}",
|
|
1025
|
+
"pathParams": [
|
|
1026
|
+
"name",
|
|
1027
|
+
"hold"
|
|
1028
|
+
],
|
|
1029
|
+
"query": [],
|
|
1030
|
+
"idempotencyKey": false,
|
|
1031
|
+
"cli": "tonbo machine hold set",
|
|
1032
|
+
"cell": true
|
|
1033
|
+
},
|
|
1034
|
+
"releaseMachineHold": {
|
|
1035
|
+
"method": "DELETE",
|
|
1036
|
+
"path": "/machines/{name}/holds/{hold}",
|
|
1037
|
+
"pathParams": [
|
|
1038
|
+
"name",
|
|
1039
|
+
"hold"
|
|
1040
|
+
],
|
|
1041
|
+
"query": [],
|
|
1042
|
+
"idempotencyKey": false,
|
|
1043
|
+
"cli": "tonbo machine hold rm",
|
|
1044
|
+
"cell": true
|
|
1045
|
+
},
|
|
927
1046
|
"issueSshCertificate": {
|
|
928
1047
|
"method": "POST",
|
|
929
1048
|
"path": "/ssh/certificates",
|
|
@@ -1059,6 +1178,182 @@ var managementOperations = {
|
|
|
1059
1178
|
"cli": "tonbo stream usage"
|
|
1060
1179
|
}
|
|
1061
1180
|
};
|
|
1181
|
+
var plannedManagementOperations = {
|
|
1182
|
+
"listBudgets": {
|
|
1183
|
+
"method": "GET",
|
|
1184
|
+
"path": "/budgets",
|
|
1185
|
+
"pathParams": [],
|
|
1186
|
+
"query": [],
|
|
1187
|
+
"idempotencyKey": false,
|
|
1188
|
+
"cli": "tonbo budget list"
|
|
1189
|
+
},
|
|
1190
|
+
"createBudget": {
|
|
1191
|
+
"method": "POST",
|
|
1192
|
+
"path": "/budgets",
|
|
1193
|
+
"pathParams": [],
|
|
1194
|
+
"query": [],
|
|
1195
|
+
"idempotencyKey": true,
|
|
1196
|
+
"cli": "tonbo budget add"
|
|
1197
|
+
},
|
|
1198
|
+
"getBudget": {
|
|
1199
|
+
"method": "GET",
|
|
1200
|
+
"path": "/budgets/{budget_id}",
|
|
1201
|
+
"pathParams": [
|
|
1202
|
+
"budget_id"
|
|
1203
|
+
],
|
|
1204
|
+
"query": [],
|
|
1205
|
+
"idempotencyKey": false,
|
|
1206
|
+
"cli": "tonbo budget show"
|
|
1207
|
+
},
|
|
1208
|
+
"changeBudget": {
|
|
1209
|
+
"method": "PATCH",
|
|
1210
|
+
"path": "/budgets/{budget_id}",
|
|
1211
|
+
"pathParams": [
|
|
1212
|
+
"budget_id"
|
|
1213
|
+
],
|
|
1214
|
+
"query": [],
|
|
1215
|
+
"idempotencyKey": false,
|
|
1216
|
+
"cli": "tonbo budget set"
|
|
1217
|
+
},
|
|
1218
|
+
"getMachineActivation": {
|
|
1219
|
+
"method": "GET",
|
|
1220
|
+
"path": "/machines/{name}/activations/{activation}",
|
|
1221
|
+
"pathParams": [
|
|
1222
|
+
"name",
|
|
1223
|
+
"activation"
|
|
1224
|
+
],
|
|
1225
|
+
"query": [
|
|
1226
|
+
"cursor",
|
|
1227
|
+
"limit"
|
|
1228
|
+
],
|
|
1229
|
+
"idempotencyKey": false,
|
|
1230
|
+
"cli": "tonbo machine activation show"
|
|
1231
|
+
},
|
|
1232
|
+
"getMachineUsage": {
|
|
1233
|
+
"method": "GET",
|
|
1234
|
+
"path": "/machines/{name}/usage",
|
|
1235
|
+
"pathParams": [
|
|
1236
|
+
"name"
|
|
1237
|
+
],
|
|
1238
|
+
"query": [
|
|
1239
|
+
"from",
|
|
1240
|
+
"to",
|
|
1241
|
+
"cursor",
|
|
1242
|
+
"limit"
|
|
1243
|
+
],
|
|
1244
|
+
"idempotencyKey": false,
|
|
1245
|
+
"cli": "tonbo machine usage"
|
|
1246
|
+
},
|
|
1247
|
+
"setMachineProcessDeadline": {
|
|
1248
|
+
"method": "PUT",
|
|
1249
|
+
"path": "/machines/{machine}/processes/{process}/deadline",
|
|
1250
|
+
"pathParams": [
|
|
1251
|
+
"machine",
|
|
1252
|
+
"process"
|
|
1253
|
+
],
|
|
1254
|
+
"query": [],
|
|
1255
|
+
"idempotencyKey": false,
|
|
1256
|
+
"cli": "tonbo machine extend",
|
|
1257
|
+
"cell": true
|
|
1258
|
+
},
|
|
1259
|
+
"signalMachineProcess": {
|
|
1260
|
+
"method": "POST",
|
|
1261
|
+
"path": "/machines/{machine}/processes/{process}/signal",
|
|
1262
|
+
"pathParams": [
|
|
1263
|
+
"machine",
|
|
1264
|
+
"process"
|
|
1265
|
+
],
|
|
1266
|
+
"query": [],
|
|
1267
|
+
"idempotencyKey": false,
|
|
1268
|
+
"cli": "tonbo machine signal",
|
|
1269
|
+
"cell": true
|
|
1270
|
+
},
|
|
1271
|
+
"createMachineDirectory": {
|
|
1272
|
+
"method": "PUT",
|
|
1273
|
+
"path": "/machines/{machine}/files/directory",
|
|
1274
|
+
"pathParams": [
|
|
1275
|
+
"machine"
|
|
1276
|
+
],
|
|
1277
|
+
"query": [
|
|
1278
|
+
"path",
|
|
1279
|
+
"parents",
|
|
1280
|
+
"mode",
|
|
1281
|
+
"user"
|
|
1282
|
+
],
|
|
1283
|
+
"idempotencyKey": true,
|
|
1284
|
+
"cli": "tonbo machine file mkdir",
|
|
1285
|
+
"cell": true
|
|
1286
|
+
},
|
|
1287
|
+
"renameMachineFile": {
|
|
1288
|
+
"method": "POST",
|
|
1289
|
+
"path": "/machines/{machine}/files/rename",
|
|
1290
|
+
"pathParams": [
|
|
1291
|
+
"machine"
|
|
1292
|
+
],
|
|
1293
|
+
"query": [
|
|
1294
|
+
"user"
|
|
1295
|
+
],
|
|
1296
|
+
"idempotencyKey": true,
|
|
1297
|
+
"cli": "tonbo machine file mv",
|
|
1298
|
+
"cell": true
|
|
1299
|
+
},
|
|
1300
|
+
"getMachineFileArchive": {
|
|
1301
|
+
"method": "GET",
|
|
1302
|
+
"path": "/machines/{machine}/files/archive",
|
|
1303
|
+
"pathParams": [
|
|
1304
|
+
"machine"
|
|
1305
|
+
],
|
|
1306
|
+
"query": [
|
|
1307
|
+
"path",
|
|
1308
|
+
"format",
|
|
1309
|
+
"user"
|
|
1310
|
+
],
|
|
1311
|
+
"idempotencyKey": false,
|
|
1312
|
+
"cli": "tonbo machine file archive",
|
|
1313
|
+
"cell": true
|
|
1314
|
+
},
|
|
1315
|
+
"readMachineLogs": {
|
|
1316
|
+
"method": "GET",
|
|
1317
|
+
"path": "/machines/{machine}/logs",
|
|
1318
|
+
"pathParams": [
|
|
1319
|
+
"machine"
|
|
1320
|
+
],
|
|
1321
|
+
"query": [
|
|
1322
|
+
"activation",
|
|
1323
|
+
"cursor",
|
|
1324
|
+
"since",
|
|
1325
|
+
"limit",
|
|
1326
|
+
"follow"
|
|
1327
|
+
],
|
|
1328
|
+
"idempotencyKey": false,
|
|
1329
|
+
"cli": "tonbo machine logs",
|
|
1330
|
+
"cell": true
|
|
1331
|
+
},
|
|
1332
|
+
"listSshCertificates": {
|
|
1333
|
+
"method": "GET",
|
|
1334
|
+
"path": "/ssh/certificates",
|
|
1335
|
+
"pathParams": [],
|
|
1336
|
+
"query": [
|
|
1337
|
+
"member",
|
|
1338
|
+
"cursor",
|
|
1339
|
+
"limit"
|
|
1340
|
+
],
|
|
1341
|
+
"idempotencyKey": false,
|
|
1342
|
+
"cli": "tonbo ssh certificate list",
|
|
1343
|
+
"cell": true
|
|
1344
|
+
},
|
|
1345
|
+
"revokeSshCertificate": {
|
|
1346
|
+
"method": "DELETE",
|
|
1347
|
+
"path": "/ssh/certificates/{certificate}",
|
|
1348
|
+
"pathParams": [
|
|
1349
|
+
"certificate"
|
|
1350
|
+
],
|
|
1351
|
+
"query": [],
|
|
1352
|
+
"idempotencyKey": false,
|
|
1353
|
+
"cli": "tonbo ssh certificate revoke",
|
|
1354
|
+
"cell": true
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1062
1357
|
var accountRoles = [
|
|
1063
1358
|
"owner",
|
|
1064
1359
|
"admin",
|
|
@@ -1673,6 +1968,7 @@ var machinePortAccess = [
|
|
|
1673
1968
|
var machineTimings = {
|
|
1674
1969
|
"pollAfterSeconds": 2,
|
|
1675
1970
|
"sleepPollAfterSeconds": 5,
|
|
1971
|
+
"wakePollAfterSeconds": 2,
|
|
1676
1972
|
"wakeHoldSeconds": 25,
|
|
1677
1973
|
"earlyReclaimSeconds": 120
|
|
1678
1974
|
};
|
|
@@ -1745,6 +2041,9 @@ async function requestJson(fetcher, url, init = {}) {
|
|
|
1745
2041
|
|
|
1746
2042
|
// ../../packages/tonbo-cli-core/dist/src/api.js
|
|
1747
2043
|
var DEFAULT_API_ORIGIN = "https://api.tonbo.dev";
|
|
2044
|
+
function managementOperation(operationId) {
|
|
2045
|
+
return operationId in managementOperations ? managementOperations[operationId] : plannedManagementOperations[operationId];
|
|
2046
|
+
}
|
|
1748
2047
|
var DEFAULT_LOGIN_HINT2 = credentialsContract.reader.login_hint;
|
|
1749
2048
|
var ExchangeRefusal = class extends HttpError {
|
|
1750
2049
|
};
|
|
@@ -1789,7 +2088,7 @@ var ManagementApi = class {
|
|
|
1789
2088
|
}
|
|
1790
2089
|
}
|
|
1791
2090
|
async requestWith(operationId, key, { params = {}, query = {}, body, idempotencyKey, ifMatch }, seen) {
|
|
1792
|
-
const operation =
|
|
2091
|
+
const operation = managementOperation(operationId);
|
|
1793
2092
|
const path4 = operation.path.replace(/\{([^}]+)\}/g, (_, name) => {
|
|
1794
2093
|
const value = params[name];
|
|
1795
2094
|
if (value === void 0)
|
|
@@ -1855,6 +2154,27 @@ var ManagementApi = class {
|
|
|
1855
2154
|
idempotencyKey
|
|
1856
2155
|
}).catch(unconfirmed("Agent creation", idempotencyKey, "the same name, role and region"));
|
|
1857
2156
|
}
|
|
2157
|
+
/** The account's budgets (planned: 404 until budgets are served). */
|
|
2158
|
+
listBudgets(key) {
|
|
2159
|
+
return this.call("listBudgets", key);
|
|
2160
|
+
}
|
|
2161
|
+
getBudget(key, budgetId) {
|
|
2162
|
+
return this.call("getBudget", key, { params: { budget_id: budgetId } });
|
|
2163
|
+
}
|
|
2164
|
+
/** A replay with the same idempotency key returns the budget the first
|
|
2165
|
+
* request created. */
|
|
2166
|
+
createBudget(key, request, idempotencyKey) {
|
|
2167
|
+
return this.call("createBudget", key, {
|
|
2168
|
+
body: withoutUndefined(request),
|
|
2169
|
+
idempotencyKey
|
|
2170
|
+
}).catch(unconfirmed("Budget creation", idempotencyKey, "the same name, limit and members"));
|
|
2171
|
+
}
|
|
2172
|
+
changeBudget(key, budgetId, change) {
|
|
2173
|
+
return this.call("changeBudget", key, {
|
|
2174
|
+
params: { budget_id: budgetId },
|
|
2175
|
+
body: withoutUndefined(change)
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
1858
2178
|
listApiKeys(key, page) {
|
|
1859
2179
|
return this.page("listApiKeys", key, page);
|
|
1860
2180
|
}
|
|
@@ -2074,6 +2394,18 @@ var ManagementApi = class {
|
|
|
2074
2394
|
});
|
|
2075
2395
|
return { machine: answer.data, retryAfterSeconds: retryAfterSeconds(headers.retryAfter) };
|
|
2076
2396
|
}
|
|
2397
|
+
/** With `etag`, only the Machine that ETag names, at that revision, is
|
|
2398
|
+
* woken (machine-v2.json `etag.if_match.wake` is optional). A 202 carries
|
|
2399
|
+
* the `Retry-After` to read the Machine again after (machine-v2.json
|
|
2400
|
+
* `wake.poll_after_seconds`); a 200 (already awake) carries none. A
|
|
2401
|
+
* refused wake throws the API's problem. */
|
|
2402
|
+
async wakeMachine(key, name, etag) {
|
|
2403
|
+
const headers = { retryAfter: null };
|
|
2404
|
+
const answer = await this.request("wakeMachine", key, { params: { name }, ...etag ? { ifMatch: etag } : {} }, (response) => {
|
|
2405
|
+
headers.retryAfter = response.headers.get("retry-after");
|
|
2406
|
+
});
|
|
2407
|
+
return { machine: answer.data, retryAfterSeconds: retryAfterSeconds(headers.retryAfter) };
|
|
2408
|
+
}
|
|
2077
2409
|
/** The Machine being deleted or the device forgotten, with its kind; the
|
|
2078
2410
|
* Machine is null when the API answered 204, whose `X-Tonbo-Machine-Kind`
|
|
2079
2411
|
* says whether a name was released (`managed`: its create never reached its
|
|
@@ -2144,11 +2476,42 @@ import os4 from "node:os";
|
|
|
2144
2476
|
import { promisify as promisify2 } from "node:util";
|
|
2145
2477
|
|
|
2146
2478
|
// ../../packages/tonbo-cli-core/dist/src/callback-page.js
|
|
2147
|
-
var
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
]
|
|
2479
|
+
var LOGO_PIXELS = [
|
|
2480
|
+
[0, 5],
|
|
2481
|
+
[5, 0],
|
|
2482
|
+
[5, 5],
|
|
2483
|
+
[5, 10],
|
|
2484
|
+
[5, 15],
|
|
2485
|
+
[10, 5],
|
|
2486
|
+
[10, 15],
|
|
2487
|
+
[21, 10],
|
|
2488
|
+
[21, 15],
|
|
2489
|
+
[26, 5],
|
|
2490
|
+
[26, 15],
|
|
2491
|
+
[31, 5],
|
|
2492
|
+
[31, 10],
|
|
2493
|
+
[42, 5],
|
|
2494
|
+
[42, 10],
|
|
2495
|
+
[42, 15],
|
|
2496
|
+
[47, 5],
|
|
2497
|
+
[52, 10],
|
|
2498
|
+
[52, 15],
|
|
2499
|
+
[63, 0],
|
|
2500
|
+
[63, 5],
|
|
2501
|
+
[63, 10],
|
|
2502
|
+
[63, 15],
|
|
2503
|
+
[68, 5],
|
|
2504
|
+
[68, 15],
|
|
2505
|
+
[73, 5],
|
|
2506
|
+
[73, 10],
|
|
2507
|
+
[84, 5],
|
|
2508
|
+
[84, 10],
|
|
2509
|
+
[89, 5],
|
|
2510
|
+
[89, 15],
|
|
2511
|
+
[94, 10],
|
|
2512
|
+
[94, 15]
|
|
2513
|
+
];
|
|
2514
|
+
var LOGO = `<svg class="wordmark" aria-hidden="true" viewBox="0 0 99 20" width="99" height="20"><g fill="currentColor" shape-rendering="crispEdges">${LOGO_PIXELS.map(([x, y]) => `<rect x="${x}" y="${y}" width="5" height="5"/>`).join("")}</g></svg>`;
|
|
2152
2515
|
var OUTCOMES = {
|
|
2153
2516
|
complete: {
|
|
2154
2517
|
status: 200,
|
|
@@ -2185,7 +2548,7 @@ function renderCallbackPage(outcome) {
|
|
|
2185
2548
|
--ink: #1d1b17;
|
|
2186
2549
|
--rule: #c9c5ba;
|
|
2187
2550
|
--quiet: #5c564a;
|
|
2188
|
-
--signal: #
|
|
2551
|
+
--signal: #e5673e;
|
|
2189
2552
|
}
|
|
2190
2553
|
@media (prefers-color-scheme: dark) {
|
|
2191
2554
|
:root {
|
|
@@ -2193,7 +2556,7 @@ function renderCallbackPage(outcome) {
|
|
|
2193
2556
|
--ink: #f3f2ee;
|
|
2194
2557
|
--rule: #3a362e;
|
|
2195
2558
|
--quiet: #a49e8f;
|
|
2196
|
-
--signal: #
|
|
2559
|
+
--signal: #e5673e;
|
|
2197
2560
|
}
|
|
2198
2561
|
}
|
|
2199
2562
|
* { box-sizing: border-box; }
|
|
@@ -2209,21 +2572,9 @@ function renderCallbackPage(outcome) {
|
|
|
2209
2572
|
-webkit-font-smoothing: antialiased;
|
|
2210
2573
|
}
|
|
2211
2574
|
header { padding: 28px 0; }
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
what makes the rows meet without seams. Iosevka is not available here,
|
|
2215
|
-
so it falls back to the platform monospace. */
|
|
2216
|
-
pre.wordmark {
|
|
2217
|
-
margin: 0;
|
|
2218
|
-
font-family: 'Iosevka', ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
|
|
2219
|
-
font-size: 8px;
|
|
2220
|
-
font-weight: 400;
|
|
2221
|
-
line-height: 10px;
|
|
2222
|
-
letter-spacing: 0;
|
|
2223
|
-
white-space: pre;
|
|
2575
|
+
svg.wordmark {
|
|
2576
|
+
display: block;
|
|
2224
2577
|
color: var(--signal);
|
|
2225
|
-
transform: scaleY(0.8);
|
|
2226
|
-
transform-origin: left top;
|
|
2227
2578
|
}
|
|
2228
2579
|
main {
|
|
2229
2580
|
display: flex;
|
|
@@ -2254,7 +2605,7 @@ function renderCallbackPage(outcome) {
|
|
|
2254
2605
|
</style>
|
|
2255
2606
|
</head>
|
|
2256
2607
|
<body>
|
|
2257
|
-
<header
|
|
2608
|
+
<header>${LOGO}</header>
|
|
2258
2609
|
<main>
|
|
2259
2610
|
<h1>${title}</h1>
|
|
2260
2611
|
<p>${body}</p>
|
|
@@ -2663,6 +3014,14 @@ var SessionClient = class _SessionClient {
|
|
|
2663
3014
|
}
|
|
2664
3015
|
return bearer;
|
|
2665
3016
|
}
|
|
3017
|
+
/** Drops every token of `accountId` this process keeps, and its bearer. */
|
|
3018
|
+
forget(accountId) {
|
|
3019
|
+
for (const id of [...this.tokens.keys()])
|
|
3020
|
+
if (id.endsWith(` ${accountId}`))
|
|
3021
|
+
this.tokens.delete(id);
|
|
3022
|
+
this.bearers.delete(accountId);
|
|
3023
|
+
this.workspaces.delete(accountId);
|
|
3024
|
+
}
|
|
2666
3025
|
/** A token of `accountId` for `audience`, kept until a minute before it expires. */
|
|
2667
3026
|
async token(audience, accountId) {
|
|
2668
3027
|
const id = `${audience} ${accountId}`;
|
|
@@ -2877,6 +3236,29 @@ var LoginStore = class {
|
|
|
2877
3236
|
}
|
|
2878
3237
|
return accounts;
|
|
2879
3238
|
}
|
|
3239
|
+
/** Forgets an account the person no longer belongs to (they left it, or
|
|
3240
|
+
* were removed): the login's cache of names is read again without it, it
|
|
3241
|
+
* is no longer the default account, and this process's tokens of it are
|
|
3242
|
+
* dropped. The CLI login itself stays: it still reaches every other
|
|
3243
|
+
* account. Without a CLI login there is nothing to forget. */
|
|
3244
|
+
async forgetAccount(accountId) {
|
|
3245
|
+
const client = await this.session().catch(() => null);
|
|
3246
|
+
if (!client)
|
|
3247
|
+
return;
|
|
3248
|
+
await this.refreshAccounts(client).catch(() => void 0);
|
|
3249
|
+
const latest = await this.sessions.load();
|
|
3250
|
+
if (!latest || latest.session.id !== client.stored.session.id)
|
|
3251
|
+
return;
|
|
3252
|
+
const { [accountId]: _forgotten, ...accounts } = latest.accounts ?? {};
|
|
3253
|
+
const saved = {
|
|
3254
|
+
...latest,
|
|
3255
|
+
accounts,
|
|
3256
|
+
default_account_id: latest.default_account_id === accountId ? null : latest.default_account_id ?? null
|
|
3257
|
+
};
|
|
3258
|
+
await this.sessions.save(saved);
|
|
3259
|
+
client.stored = saved;
|
|
3260
|
+
client.forget(accountId);
|
|
3261
|
+
}
|
|
2880
3262
|
/** The id of the account `nameOrId` names, looked up again once when the cache misses. */
|
|
2881
3263
|
async resolve(client, nameOrId) {
|
|
2882
3264
|
if (UUID2.test(nameOrId))
|
|
@@ -3604,7 +3986,7 @@ var TerminalProgress = class {
|
|
|
3604
3986
|
};
|
|
3605
3987
|
|
|
3606
3988
|
// src/commands.ts
|
|
3607
|
-
import { randomUUID as
|
|
3989
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
3608
3990
|
|
|
3609
3991
|
// src/agent-name.ts
|
|
3610
3992
|
var AGENT_NAME_RULE = agentNameContract.$defs.name;
|
|
@@ -3619,6 +4001,209 @@ function validateAgentName(name) {
|
|
|
3619
4001
|
}
|
|
3620
4002
|
}
|
|
3621
4003
|
|
|
4004
|
+
// src/budget.ts
|
|
4005
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
4006
|
+
var BUDGETS_NOT_AVAILABLE = "Budgets are not available yet.";
|
|
4007
|
+
function parseDollars(value) {
|
|
4008
|
+
const match = /^\$?([0-9]+)(?:\.([0-9]{1,2}))?$/.exec(value.trim());
|
|
4009
|
+
if (!match) throw new Error(`${value} is not an amount in dollars, such as $50 or 12.50.`);
|
|
4010
|
+
return Number(match[1]) * 1e6 + Number((match[2] ?? "").padEnd(2, "0")) * 1e4;
|
|
4011
|
+
}
|
|
4012
|
+
var isAmount = (value) => /^\$?[0-9]+(?:\.[0-9]{1,2})?$/.test(value.trim());
|
|
4013
|
+
function unavailable(error) {
|
|
4014
|
+
if (error instanceof HttpError && error.status === 404 && error.code !== "budget_not_found")
|
|
4015
|
+
return new HttpError(BUDGETS_NOT_AVAILABLE, 404, error.body, error.requestId);
|
|
4016
|
+
return error;
|
|
4017
|
+
}
|
|
4018
|
+
async function budgets(deps, key) {
|
|
4019
|
+
return deps.api.listBudgets(key).catch((error) => {
|
|
4020
|
+
throw unavailable(error);
|
|
4021
|
+
});
|
|
4022
|
+
}
|
|
4023
|
+
async function resolveBudget(deps, key, budget) {
|
|
4024
|
+
const all = await budgets(deps, key);
|
|
4025
|
+
const found = all.find((entry) => entry.id === budget || entry.name === budget);
|
|
4026
|
+
if (!found) throw new Error(`No budget of this account is ${budget}. See \`tonbo budget list\`.`);
|
|
4027
|
+
return found;
|
|
4028
|
+
}
|
|
4029
|
+
function usedText(budget) {
|
|
4030
|
+
if (budget.limit_micros === void 0) return "\u2014";
|
|
4031
|
+
const used = (budget.settled_micros ?? 0) + (budget.committed_micros ?? 0);
|
|
4032
|
+
return `${usd(used)} of ${usd(budget.limit_micros)}`;
|
|
4033
|
+
}
|
|
4034
|
+
function budgetFields(budget) {
|
|
4035
|
+
const rows = [
|
|
4036
|
+
["ID", budget.id],
|
|
4037
|
+
["Name", budget.name ?? "\u2014"],
|
|
4038
|
+
["Scope", budget.scope],
|
|
4039
|
+
["Stage", budget.stage]
|
|
4040
|
+
];
|
|
4041
|
+
if (budget.limit_micros !== void 0) {
|
|
4042
|
+
rows.push(["Limit", usd(budget.limit_micros)]);
|
|
4043
|
+
rows.push(["Charged", usd(budget.settled_micros ?? 0)]);
|
|
4044
|
+
rows.push(["Committed", usd(budget.committed_micros ?? 0)]);
|
|
4045
|
+
}
|
|
4046
|
+
if (budget.principals?.length) rows.push(["Covers", budget.principals.join(", ")]);
|
|
4047
|
+
if (budget.write_pace)
|
|
4048
|
+
rows.push([
|
|
4049
|
+
"Writes paced",
|
|
4050
|
+
`${budget.write_pace.units_per_second}/s (floor ${budget.write_pace.floor_units_per_second}/s)`
|
|
4051
|
+
]);
|
|
4052
|
+
rows.push(["Resets", budget.resets_at]);
|
|
4053
|
+
rows.push(["Raise at", budget.raise_url]);
|
|
4054
|
+
return fields(rows);
|
|
4055
|
+
}
|
|
4056
|
+
async function budgetListCommand(deps, options) {
|
|
4057
|
+
const all = await budgets(deps, await deps.auth.apiKey());
|
|
4058
|
+
print(
|
|
4059
|
+
deps,
|
|
4060
|
+
options.json,
|
|
4061
|
+
all,
|
|
4062
|
+
all.length ? table(
|
|
4063
|
+
["ID", "NAME", "SCOPE", "STAGE", "USED", "RESETS"],
|
|
4064
|
+
all.map((budget) => [
|
|
4065
|
+
budget.id,
|
|
4066
|
+
budget.name ?? "\u2014",
|
|
4067
|
+
budget.scope,
|
|
4068
|
+
budget.stage,
|
|
4069
|
+
usedText(budget),
|
|
4070
|
+
budget.resets_at
|
|
4071
|
+
])
|
|
4072
|
+
) : "No budgets. An owner or admin sets the account budget in the console; `tonbo budget add` sets one for you or an Agent you created."
|
|
4073
|
+
);
|
|
4074
|
+
}
|
|
4075
|
+
async function budgetShowCommand(deps, budget, options) {
|
|
4076
|
+
const found = await resolveBudget(deps, await deps.auth.apiKey(), budget);
|
|
4077
|
+
print(deps, options.json, found, budgetFields(found));
|
|
4078
|
+
}
|
|
4079
|
+
async function sendToConsole(deps, url, why) {
|
|
4080
|
+
deps.notice(`${why} An owner or admin makes it in the console: ${url}`);
|
|
4081
|
+
if (deps.interactive() && deps.openUrl) await deps.openUrl(url).catch(() => void 0);
|
|
4082
|
+
}
|
|
4083
|
+
async function consoleOr(deps, error) {
|
|
4084
|
+
if (error instanceof HttpError && error.code === "budget_raise_in_console") {
|
|
4085
|
+
const body = error.body;
|
|
4086
|
+
if (typeof body?.raise_url === "string") {
|
|
4087
|
+
await sendToConsole(deps, body.raise_url, "Only the console makes this change.");
|
|
4088
|
+
throw new HttpError(error.message, error.status, error.body, error.requestId);
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
throw unavailable(error);
|
|
4092
|
+
}
|
|
4093
|
+
async function budgetAddCommand(deps, name, options) {
|
|
4094
|
+
const key = await deps.auth.apiKey();
|
|
4095
|
+
const limit = parseDollars(options.limit);
|
|
4096
|
+
const principals = options.member?.length ? await Promise.all(options.member.map((member) => resolveMemberSubject(deps, key, member))) : [(await deps.api.getAccount(key)).member.subject];
|
|
4097
|
+
const created = await deps.api.createBudget(key, { scope: "principals", name, limit_micros: limit, principals }, randomUUID3()).catch((error) => consoleOr(deps, error));
|
|
4098
|
+
print(
|
|
4099
|
+
deps,
|
|
4100
|
+
options.json,
|
|
4101
|
+
created,
|
|
4102
|
+
`Created budget ${created.name ?? name}: ${usd(limit)} a month, covering ${principals.join(", ")}.`
|
|
4103
|
+
);
|
|
4104
|
+
}
|
|
4105
|
+
async function budgetSetCommand(deps, budget, options) {
|
|
4106
|
+
if (options.limit === void 0 && !options.add?.length)
|
|
4107
|
+
throw new Error("Nothing to change: pass --limit, --add or both.");
|
|
4108
|
+
const key = await deps.auth.apiKey();
|
|
4109
|
+
const found = await resolveBudget(deps, key, budget);
|
|
4110
|
+
const limit = options.limit === void 0 ? void 0 : parseDollars(options.limit);
|
|
4111
|
+
if (limit !== void 0 && (found.limit_micros === void 0 || limit >= found.limit_micros)) {
|
|
4112
|
+
await sendToConsole(
|
|
4113
|
+
deps,
|
|
4114
|
+
found.raise_url,
|
|
4115
|
+
found.limit_micros === void 0 ? "This budget is not yours to change here." : `${usd(limit)} is not below the budget's ${usd(found.limit_micros)}: the CLI only lowers a budget.`
|
|
4116
|
+
);
|
|
4117
|
+
throw new Error("The budget was not changed.");
|
|
4118
|
+
}
|
|
4119
|
+
const add = options.add?.length ? await Promise.all(options.add.map((member) => resolveMemberSubject(deps, key, member))) : void 0;
|
|
4120
|
+
const changed = await deps.api.changeBudget(key, found.id, { limit_micros: limit, add_principals: add }).catch((error) => consoleOr(deps, error));
|
|
4121
|
+
print(deps, options.json, changed, budgetFields(changed));
|
|
4122
|
+
}
|
|
4123
|
+
async function attachBudget(deps, key, subject, value, name, created) {
|
|
4124
|
+
try {
|
|
4125
|
+
if (isAmount(value)) {
|
|
4126
|
+
const limit = parseDollars(value);
|
|
4127
|
+
const budget = await deps.api.createBudget(
|
|
4128
|
+
key,
|
|
4129
|
+
{ scope: "principals", name, limit_micros: limit, principals: [subject] },
|
|
4130
|
+
randomUUID3()
|
|
4131
|
+
).catch((error) => consoleOr(deps, error));
|
|
4132
|
+
deps.notice(`Budget ${budget.name ?? name}: ${usd(limit)} a month for ${subject}.`);
|
|
4133
|
+
return;
|
|
4134
|
+
}
|
|
4135
|
+
const found = await resolveBudget(deps, key, value);
|
|
4136
|
+
await deps.api.changeBudget(key, found.id, { add_principals: [subject] }).catch((error) => consoleOr(deps, error));
|
|
4137
|
+
deps.notice(`Budget ${found.name ?? found.id} now covers ${subject}.`);
|
|
4138
|
+
} catch (error) {
|
|
4139
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
4140
|
+
throw new Error(`${created} was created, but its budget was not set: ${reason}`);
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
function accountBudgetFields(account) {
|
|
4144
|
+
const budgets2 = account.budgets;
|
|
4145
|
+
if (!budgets2) return [];
|
|
4146
|
+
const line = (budget) => budget ? `${budget.name ?? budget.id} \xB7 ${budget.stage} \xB7 ${usedText(budget)} \xB7 resets ${budget.resets_at}` : "none";
|
|
4147
|
+
const rows = [
|
|
4148
|
+
["Account budget", line(budgets2.account)],
|
|
4149
|
+
["Your budget", line(budgets2.caller)]
|
|
4150
|
+
];
|
|
4151
|
+
const pace = budgets2.account?.write_pace;
|
|
4152
|
+
if (pace)
|
|
4153
|
+
rows.push([
|
|
4154
|
+
"Writes paced",
|
|
4155
|
+
`${pace.units_per_second}/s across regions (floor ${pace.floor_units_per_second}/s)`
|
|
4156
|
+
]);
|
|
4157
|
+
return rows;
|
|
4158
|
+
}
|
|
4159
|
+
function refusalBudget(body) {
|
|
4160
|
+
const budget = body?.budget;
|
|
4161
|
+
if (!budget || typeof budget !== "object") return null;
|
|
4162
|
+
const record = budget;
|
|
4163
|
+
return typeof record.id === "string" && typeof record.raise_url === "string" ? record : null;
|
|
4164
|
+
}
|
|
4165
|
+
function budgetHint(error) {
|
|
4166
|
+
const budget = refusalBudget(error.body);
|
|
4167
|
+
const name = budget ? budget.name ?? budget.id : "a budget";
|
|
4168
|
+
switch (error.code) {
|
|
4169
|
+
case "budget_exhausted":
|
|
4170
|
+
return `The budget ${name} is used up${budget?.retry_at ? ` until ${budget.retry_at}` : ""}: stop, and try again only after then. A person can raise it in the console${budget ? ` at ${budget.raise_url}` : ""}.`;
|
|
4171
|
+
case "budget_slowdown":
|
|
4172
|
+
return `The budget ${name} is close to its limit, so only a few of its Machines may be awake at once: sleep another of its Machines${budget?.largest_shape_that_fits ? `, or set this one's shape to ${budget.largest_shape_that_fits}` : ""}, then wake it again. A person can raise it${budget ? ` at ${budget.raise_url}` : " in the console"}.`;
|
|
4173
|
+
case "budget_scope":
|
|
4174
|
+
return "Your budget confines you to the Machines you created: work on one of those, or ask a person to.";
|
|
4175
|
+
case "budget_raise_in_console": {
|
|
4176
|
+
const url = error.body?.raise_url;
|
|
4177
|
+
return `Only the console makes this change${typeof url === "string" ? `: ${url}` : "."}`;
|
|
4178
|
+
}
|
|
4179
|
+
default:
|
|
4180
|
+
return null;
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
function budgetHeaderLine(header) {
|
|
4184
|
+
const values = /* @__PURE__ */ new Map();
|
|
4185
|
+
for (const member of header.split(",")) {
|
|
4186
|
+
const [name, ...rest] = member.trim().split("=");
|
|
4187
|
+
if (!name) continue;
|
|
4188
|
+
values.set(name.trim(), rest.join("=").trim().replace(/^"|"$/g, ""));
|
|
4189
|
+
}
|
|
4190
|
+
const used = values.get("used");
|
|
4191
|
+
const percent = used === void 0 ? "" : `, ${Math.round(Number(used) * 100)}% used`;
|
|
4192
|
+
return `Budget: ${values.get("scope") === "account" ? "the account budget" : "your budget"} is at ${values.get("stage") ?? "notice"}${percent}; it resets ${values.get("resets") ?? "on the 1st"}.`;
|
|
4193
|
+
}
|
|
4194
|
+
function noticingBudget(fetcher, notice) {
|
|
4195
|
+
let said = false;
|
|
4196
|
+
return async (input, init) => {
|
|
4197
|
+
const response = await fetcher(input, init);
|
|
4198
|
+
const header = response.headers.get("tonbo-budget");
|
|
4199
|
+
if (header && !said) {
|
|
4200
|
+
said = true;
|
|
4201
|
+
notice(budgetHeaderLine(header));
|
|
4202
|
+
}
|
|
4203
|
+
return response;
|
|
4204
|
+
};
|
|
4205
|
+
}
|
|
4206
|
+
|
|
3622
4207
|
// src/commands.ts
|
|
3623
4208
|
function print(deps, json, data, human) {
|
|
3624
4209
|
deps.output(json ? JSON.stringify(data) : human);
|
|
@@ -3661,7 +4246,7 @@ async function agentCreateCommand(deps, name, options = {}) {
|
|
|
3661
4246
|
const agent = await deps.api.createAgent(
|
|
3662
4247
|
key,
|
|
3663
4248
|
{ name, region, ...options.role === void 0 ? {} : { role: options.role } },
|
|
3664
|
-
options.idempotencyKey ??
|
|
4249
|
+
options.idempotencyKey ?? randomUUID4()
|
|
3665
4250
|
);
|
|
3666
4251
|
print(
|
|
3667
4252
|
deps,
|
|
@@ -3674,6 +4259,15 @@ async function agentCreateCommand(deps, name, options = {}) {
|
|
|
3674
4259
|
`Region: ${agent.region}`
|
|
3675
4260
|
].join("\n")
|
|
3676
4261
|
);
|
|
4262
|
+
if (options.budget !== void 0)
|
|
4263
|
+
await attachBudget(
|
|
4264
|
+
deps,
|
|
4265
|
+
key,
|
|
4266
|
+
`agent.${agent.id}`,
|
|
4267
|
+
options.budget,
|
|
4268
|
+
agent.name,
|
|
4269
|
+
`The Agent ${agent.name}`
|
|
4270
|
+
);
|
|
3677
4271
|
}
|
|
3678
4272
|
async function accountShowCommand(deps, options = {}) {
|
|
3679
4273
|
const account = await deps.api.getAccount(await deps.auth.apiKey());
|
|
@@ -3687,7 +4281,8 @@ async function accountShowCommand(deps, options = {}) {
|
|
|
3687
4281
|
["Kind", account.kind],
|
|
3688
4282
|
["Your role", account.member.role],
|
|
3689
4283
|
["Your subject", account.member.subject],
|
|
3690
|
-
["Bucket limit", String(account.limits.buckets)]
|
|
4284
|
+
["Bucket limit", String(account.limits.buckets)],
|
|
4285
|
+
...accountBudgetFields(account)
|
|
3691
4286
|
])
|
|
3692
4287
|
);
|
|
3693
4288
|
}
|
|
@@ -3892,6 +4487,15 @@ async function apiKeyCreateCommand(deps, name, options = {}) {
|
|
|
3892
4487
|
deps.notice(
|
|
3893
4488
|
"Do not set TONBO_API_KEY in your own shell: it overrides your `tonbo login`, for this CLI too."
|
|
3894
4489
|
);
|
|
4490
|
+
if (options.budget !== void 0)
|
|
4491
|
+
await attachBudget(
|
|
4492
|
+
deps,
|
|
4493
|
+
key,
|
|
4494
|
+
created.member,
|
|
4495
|
+
options.budget,
|
|
4496
|
+
created.name,
|
|
4497
|
+
`The API key ${created.name}`
|
|
4498
|
+
);
|
|
3895
4499
|
}
|
|
3896
4500
|
async function apiKeyShowCommand(deps, keyId, options = {}) {
|
|
3897
4501
|
const apiKey = await deps.api.getApiKey(await deps.auth.apiKey(), keyId);
|
|
@@ -3950,7 +4554,7 @@ async function bucketCreateCommand(deps, name, options = {}) {
|
|
|
3950
4554
|
const bucket = await deps.api.createBucket(
|
|
3951
4555
|
await deps.auth.apiKey(),
|
|
3952
4556
|
{ name, region: options.region },
|
|
3953
|
-
options.idempotencyKey ??
|
|
4557
|
+
options.idempotencyKey ?? randomUUID4()
|
|
3954
4558
|
);
|
|
3955
4559
|
print(
|
|
3956
4560
|
deps,
|
|
@@ -4081,7 +4685,7 @@ async function databaseCreateCommand(deps, name, options = {}) {
|
|
|
4081
4685
|
const database = await deps.api.createDatabase(
|
|
4082
4686
|
await deps.auth.apiKey(),
|
|
4083
4687
|
{ name, region: options.region, expires_at: expiresAt },
|
|
4084
|
-
options.idempotencyKey ??
|
|
4688
|
+
options.idempotencyKey ?? randomUUID4()
|
|
4085
4689
|
);
|
|
4086
4690
|
print(
|
|
4087
4691
|
deps,
|
|
@@ -4269,7 +4873,7 @@ async function streamCreateCommand(deps, name, options = {}) {
|
|
|
4269
4873
|
content_type: options.contentType,
|
|
4270
4874
|
expires_at: expiresAt
|
|
4271
4875
|
},
|
|
4272
|
-
options.idempotencyKey ??
|
|
4876
|
+
options.idempotencyKey ?? randomUUID4()
|
|
4273
4877
|
);
|
|
4274
4878
|
print(
|
|
4275
4879
|
deps,
|
|
@@ -4493,11 +5097,11 @@ async function accountUseCommand(deps, account, options = {}) {
|
|
|
4493
5097
|
import { Help } from "commander";
|
|
4494
5098
|
|
|
4495
5099
|
// src/machine.ts
|
|
4496
|
-
import { randomUUID as
|
|
5100
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
4497
5101
|
import { readFileSync } from "node:fs";
|
|
4498
5102
|
|
|
4499
5103
|
// src/volume.ts
|
|
4500
|
-
import { randomUUID as
|
|
5104
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
4501
5105
|
var VOLUME_DURABILITY_LINK = "https://tonbo.dev/docs/machines/guarantees/data#volume";
|
|
4502
5106
|
var VOLUME_ERROR_HINTS = {
|
|
4503
5107
|
payment_required: "Add credit at <billing>, then create the Volume again.",
|
|
@@ -4595,7 +5199,7 @@ async function volumeCreateCommand(deps, name, options = {}) {
|
|
|
4595
5199
|
const volume = await deps.api.createVolume(
|
|
4596
5200
|
key,
|
|
4597
5201
|
{ name, region, size_gib: sizeGib },
|
|
4598
|
-
options.idempotencyKey ??
|
|
5202
|
+
options.idempotencyKey ?? randomUUID5()
|
|
4599
5203
|
);
|
|
4600
5204
|
print(
|
|
4601
5205
|
deps,
|
|
@@ -4662,7 +5266,8 @@ var MACHINE_ERROR_HINTS = {
|
|
|
4662
5266
|
machine_kind_mismatch: "That Machine is one of your computers: `tonbo machine show` and `tonbo machine delete` serve it, and nothing else does.",
|
|
4663
5267
|
volumes_not_offered: "Volumes are not offered to this account in the Machine's region yet; nothing was created or changed.",
|
|
4664
5268
|
secret_env_conflict: "Use another secret key or remove the non-secret env variable with that name.",
|
|
4665
|
-
secret_limit: "Reduce the number or byte size of this Machine's secrets."
|
|
5269
|
+
secret_limit: "Reduce the number or byte size of this Machine's secrets.",
|
|
5270
|
+
wake_unavailable: "The wake was refused for now; nothing woke. Wake it again after a while; `tonbo machine show` says why it is blocked."
|
|
4666
5271
|
};
|
|
4667
5272
|
var MACHINE_IMAGE_FAILURE_HINTS = {
|
|
4668
5273
|
image_unavailable: "The registry no longer serves that image publicly, or served other bytes for it: publish it again, or use another image",
|
|
@@ -4694,7 +5299,7 @@ function ifMatchHeader(value) {
|
|
|
4694
5299
|
}
|
|
4695
5300
|
function explainMachineError(error) {
|
|
4696
5301
|
if (!(error instanceof HttpError) || error.code === null) return error;
|
|
4697
|
-
const hint = MACHINE_ERROR_HINTS[error.code];
|
|
5302
|
+
const hint = budgetHint(error) ?? MACHINE_ERROR_HINTS[error.code];
|
|
4698
5303
|
if (!hint) return error;
|
|
4699
5304
|
return new HttpError(`${error.message}
|
|
4700
5305
|
${hint}`, error.status, error.body, error.requestId);
|
|
@@ -4724,7 +5329,13 @@ function portText(port) {
|
|
|
4724
5329
|
function machinePortsText(machine) {
|
|
4725
5330
|
return machine.ports.length ? machine.ports.map(portText).join(", ") : "none";
|
|
4726
5331
|
}
|
|
4727
|
-
var {
|
|
5332
|
+
var {
|
|
5333
|
+
earlyReclaimSeconds,
|
|
5334
|
+
pollAfterSeconds,
|
|
5335
|
+
sleepPollAfterSeconds,
|
|
5336
|
+
wakePollAfterSeconds,
|
|
5337
|
+
wakeHoldSeconds
|
|
5338
|
+
} = machineTimings;
|
|
4728
5339
|
function machineState(machine) {
|
|
4729
5340
|
if (machine.failure)
|
|
4730
5341
|
return `${machine.state}: ${machine.failure.code}: ${machine.failure.message}`;
|
|
@@ -5226,7 +5837,7 @@ async function machineCreateCommand(deps, name, command, options = {}) {
|
|
|
5226
5837
|
...database ? { database: database.name } : {},
|
|
5227
5838
|
...options.database === false ? { database: null } : {}
|
|
5228
5839
|
},
|
|
5229
|
-
options.idempotencyKey ??
|
|
5840
|
+
options.idempotencyKey ?? randomUUID6()
|
|
5230
5841
|
);
|
|
5231
5842
|
} catch (error) {
|
|
5232
5843
|
if (error instanceof HttpError && error.code === "name_taken")
|
|
@@ -5266,11 +5877,16 @@ async function machineShowCommand(deps, name, options = {}) {
|
|
|
5266
5877
|
const key = await deps.auth.apiKey();
|
|
5267
5878
|
const { machine, etag } = await deps.api.getMachineWithEtag(key, name);
|
|
5268
5879
|
const lastEnd = options.json || machine.kind === "byoc" || machine.state !== "active" ? null : await lastActivationEnd(deps, key, machine.name);
|
|
5880
|
+
const processes = options.json || machine.kind === "byoc" || machine.state !== "active" || !deps.processes ? null : await deps.processes(key, machine.region, machine.name);
|
|
5881
|
+
const processLines = processes ? [
|
|
5882
|
+
...processes.running.map((line) => `Background process: ${line}`),
|
|
5883
|
+
...processes.last ? [`Last background process: ${processes.last}`] : []
|
|
5884
|
+
] : [];
|
|
5269
5885
|
print(
|
|
5270
5886
|
deps,
|
|
5271
5887
|
options.json,
|
|
5272
5888
|
machine,
|
|
5273
|
-
anyMachineText(machine, await billingUrl(deps), etag, lastEnd)
|
|
5889
|
+
[anyMachineText(machine, await billingUrl(deps), etag, lastEnd), ...processLines].join("\n")
|
|
5274
5890
|
);
|
|
5275
5891
|
});
|
|
5276
5892
|
}
|
|
@@ -5318,9 +5934,14 @@ function appliesText(name, change) {
|
|
|
5318
5934
|
lines.push(
|
|
5319
5935
|
change.delete_after_seconds === null ? "The Machine is no longer deleted for being asleep." : "Delete-after applies now; `tonbo machine show` says when."
|
|
5320
5936
|
);
|
|
5321
|
-
const later = [
|
|
5322
|
-
|
|
5323
|
-
|
|
5937
|
+
const later = [
|
|
5938
|
+
change.image,
|
|
5939
|
+
change.command,
|
|
5940
|
+
change.shape,
|
|
5941
|
+
change.env,
|
|
5942
|
+
change.volumes,
|
|
5943
|
+
change.database
|
|
5944
|
+
].some((field) => field !== void 0);
|
|
5324
5945
|
if (later) {
|
|
5325
5946
|
lines.push(
|
|
5326
5947
|
`The change applies at the next activation. To apply it now, run \`tonbo machine sleep ${name}\`; the next request boots the new spec.`
|
|
@@ -5370,6 +5991,8 @@ async function machineUpdateCommand(deps, name, command, options = {}) {
|
|
|
5370
5991
|
if (options.volume === false) change.volumes = [];
|
|
5371
5992
|
else if (typeof options.volume === "string")
|
|
5372
5993
|
change.volumes = [(await resolveVolumeBinding(deps, key, options.volume)).binding];
|
|
5994
|
+
if (options.database === false) change.database = null;
|
|
5995
|
+
else if (typeof options.database === "string") change.database = options.database;
|
|
5373
5996
|
if (options.discardSnapshot) change.discard_snapshot = true;
|
|
5374
5997
|
if (Object.values(change).every((value) => value === void 0))
|
|
5375
5998
|
throw new Error("Nothing to change: pass at least one option or a command.");
|
|
@@ -5431,6 +6054,42 @@ async function machineSleepCommand(deps, name, options = {}) {
|
|
|
5431
6054
|
);
|
|
5432
6055
|
});
|
|
5433
6056
|
}
|
|
6057
|
+
function wokenText(machine, waking) {
|
|
6058
|
+
if (!waking) return `Machine ${machine.name} is awake.`;
|
|
6059
|
+
return `Machine ${machine.name} is waking; \`tonbo machine show ${machine.name}\` says when it is awake.`;
|
|
6060
|
+
}
|
|
6061
|
+
async function machineWakeCommand(deps, name, options = {}) {
|
|
6062
|
+
await machineCommand(async () => {
|
|
6063
|
+
const timeout = waitTimeout(options);
|
|
6064
|
+
const key = await deps.auth.apiKey();
|
|
6065
|
+
const { machine, retryAfterSeconds: retryAfterSeconds2 } = await deps.api.wakeMachine(
|
|
6066
|
+
key,
|
|
6067
|
+
name,
|
|
6068
|
+
ifMatchHeader(options.ifMatch)
|
|
6069
|
+
);
|
|
6070
|
+
const waking = retryAfterSeconds2 !== null;
|
|
6071
|
+
if (!options.wait || !waking) {
|
|
6072
|
+
print(deps, options.json, machine, wokenText(machine, waking));
|
|
6073
|
+
return;
|
|
6074
|
+
}
|
|
6075
|
+
const { machine: awake, waited } = await waitForMachine(
|
|
6076
|
+
deps,
|
|
6077
|
+
key,
|
|
6078
|
+
name,
|
|
6079
|
+
retryAfterSeconds2 ?? wakePollAfterSeconds,
|
|
6080
|
+
wakePollAfterSeconds,
|
|
6081
|
+
timeout,
|
|
6082
|
+
(read) => read.status.power === "awake" || read.state !== "active"
|
|
6083
|
+
);
|
|
6084
|
+
if (!awake)
|
|
6085
|
+
throw new Error(
|
|
6086
|
+
`Machine ${name} is not awake after ${waited} s; \`tonbo machine show ${name}\` says why.`
|
|
6087
|
+
);
|
|
6088
|
+
if (awake.status.power !== "awake")
|
|
6089
|
+
throw new Error(`Machine ${name} is ${awake.state}; it did not wake.`);
|
|
6090
|
+
print(deps, options.json, awake, `Machine ${awake.name} is awake (after ${waited} s).`);
|
|
6091
|
+
});
|
|
6092
|
+
}
|
|
5434
6093
|
async function machineDeleteCommand(deps, name, options = {}) {
|
|
5435
6094
|
await machineCommand(async () => {
|
|
5436
6095
|
const key = await deps.auth.apiKey();
|
|
@@ -5676,6 +6335,34 @@ var COMMAND_HELP = {
|
|
|
5676
6335
|
"--idempotency-key": "a new UUID"
|
|
5677
6336
|
}
|
|
5678
6337
|
},
|
|
6338
|
+
"tonbo budget list": {
|
|
6339
|
+
about: [
|
|
6340
|
+
"Lists the account's monthly spend budgets, the account budget first, with each one's stage and what it has used of its limit this UTC month. You see a budget's figures with `usage.read`, or when it covers you; of any other budget only its stage, reset and console link."
|
|
6341
|
+
],
|
|
6342
|
+
examples: ["tonbo budget list"]
|
|
6343
|
+
},
|
|
6344
|
+
"tonbo budget show": {
|
|
6345
|
+
about: [
|
|
6346
|
+
"Prints one budget: its limit, what was charged and what is committed this month, whom it covers, its stage, when it resets (00:00 UTC on the 1st) and its page in the console."
|
|
6347
|
+
],
|
|
6348
|
+
examples: ["tonbo budget show pi-agent"],
|
|
6349
|
+
arguments: { budget: "the budget's name or id (`bdg_\u2026`)" }
|
|
6350
|
+
},
|
|
6351
|
+
"tonbo budget add": {
|
|
6352
|
+
about: [
|
|
6353
|
+
"Creates a budget over you, or over Agents you created, which counts the compute and outbound data of the Machines they create. Only the console sets the account budget or covers another member."
|
|
6354
|
+
],
|
|
6355
|
+
examples: ["tonbo budget add pi-agent --limit 50 --member pi-agent"],
|
|
6356
|
+
arguments: { name: "the budget's name, unique in the account" },
|
|
6357
|
+
defaults: { "--member": "yourself" }
|
|
6358
|
+
},
|
|
6359
|
+
"tonbo budget set": {
|
|
6360
|
+
about: [
|
|
6361
|
+
"Lowers the budget that covers you, or adds Agents you created to it. Raising a limit, renaming, removing and every change to another budget are the console's: the command prints the budget's page and opens it instead."
|
|
6362
|
+
],
|
|
6363
|
+
examples: ["tonbo budget set pi-agent --limit 25"],
|
|
6364
|
+
arguments: { budget: "the budget's name or id (`bdg_\u2026`)" }
|
|
6365
|
+
},
|
|
5679
6366
|
"tonbo api-key list": {
|
|
5680
6367
|
about: [
|
|
5681
6368
|
"Shows your keys, or every key of the account for an owner or admin, with the last four characters of each secret, whether it is read-only, what it reaches of buckets, databases and streams, and when it expires and was last used."
|
|
@@ -5913,7 +6600,7 @@ var COMMAND_HELP = {
|
|
|
5913
6600
|
"tonbo machine update": {
|
|
5914
6601
|
about: [
|
|
5915
6602
|
"Changes only what its options name. It reads the Machine and sends the ETag it read, or `--if-match`'s, so a change someone made in between is refused with `precondition_failed` rather than overwritten; a Machine whose creation failed is refused with `machine_failed`. `--env` and `--unset-env` change single variables; `--port` replaces every port, and the current default port stays the default while it is still listed.",
|
|
5916
|
-
"Ports and `--sleep-after` apply at once. The image, command, shape and
|
|
6603
|
+
"Ports and `--sleep-after` apply at once. The image, command, shape, env, Volume and database apply at the next boot, and the command says how to apply them now: `tonbo machine sleep`, after which the next request boots the new spec. Naming the same tag again resolves its digest again. `--database <name>` binds another database of the account in the Machine's region, which no other Machine binds, and `--no-database` binds none; the database it leaves is kept. A Machine created with its own database keeps it.",
|
|
5917
6604
|
"`--delete-after` applies at once and keeps the clock, which counts from the end of the Machine's last activation (or its creation), so a lower value can delete a Machine that has already slept that long; `--no-delete-after` turns it off. `tonbo machine show` prints when it will be deleted."
|
|
5918
6605
|
],
|
|
5919
6606
|
examples: ["tonbo machine update web --env MODE=staging --sleep-after 300"],
|
|
@@ -5929,11 +6616,13 @@ var COMMAND_HELP = {
|
|
|
5929
6616
|
"tonbo machine exec": {
|
|
5930
6617
|
about: [
|
|
5931
6618
|
"Runs one command in the Machine's running activation through its region's cell origin, waking the Machine first when it is asleep, and streams its stdout and stderr here as they come; a call that woke the Machine prints its hourly ceiling on stderr. The command runs without a shell, as the image's User, and lives exactly as long as the call: there is no terminal and no streamed stdin, and `--stdin` sends at most 1 MiB read before the call.",
|
|
5932
|
-
"The CLI exits with the command's status: 128 plus the signal's number when a signal ended it, 124 at `--timeout`, 125 when it was refused, killed or passed 64 MiB of output, 126 or 127 when it could not run, and 255 when the connection ended first and the outcome is unknown. `--json` prints the answer's document instead, with the first and last 512 KiB of each stream."
|
|
6619
|
+
"The CLI exits with the command's status: 128 plus the signal's number when a signal ended it, 124 at `--timeout`, 125 when it was refused, killed or passed 64 MiB of output, 126 or 127 when it could not run, and 255 when the connection ended first and the outcome is unknown. `--json` prints the answer's document instead, with the first and last 512 KiB of each stream.",
|
|
6620
|
+
"`--detach` starts the command in the background instead, prints its process id and returns at once: it runs whatever happens to this call, keeps the Machine awake until it ends, and is ended at `--timeout` (default 3600, at most 21600). Read it with `tonbo machine output`, wait for it with `tonbo machine wait`, list them with `tonbo machine ps` and end one with `tonbo machine kill`. A budget or funding stop, or a sleep you ask for, still ends it."
|
|
5933
6621
|
],
|
|
5934
6622
|
examples: [
|
|
5935
6623
|
"tonbo machine exec web -- ls -la /app",
|
|
5936
|
-
"tonbo machine exec web --timeout 60 -- npm test"
|
|
6624
|
+
"tonbo machine exec web --timeout 60 -- npm test",
|
|
6625
|
+
"tonbo machine exec web --detach -- npm run build"
|
|
5937
6626
|
],
|
|
5938
6627
|
arguments: {
|
|
5939
6628
|
name: "the Machine's name",
|
|
@@ -5941,6 +6630,37 @@ var COMMAND_HELP = {
|
|
|
5941
6630
|
},
|
|
5942
6631
|
defaults: { "--timeout": "600", "--cwd": "the image's WorkingDir" }
|
|
5943
6632
|
},
|
|
6633
|
+
"tonbo machine ps": {
|
|
6634
|
+
about: [
|
|
6635
|
+
"Lists the Machine's background processes, newest first: those running and those that ended in the last 24 hours, with how each ended. It never wakes the Machine."
|
|
6636
|
+
],
|
|
6637
|
+
examples: ["tonbo machine ps web"],
|
|
6638
|
+
arguments: { name: "the Machine's name" }
|
|
6639
|
+
},
|
|
6640
|
+
"tonbo machine wait": {
|
|
6641
|
+
about: [
|
|
6642
|
+
"Waits until a background process ends, prints how it ended and exits with its status as `tonbo machine exec` would: its exit code, 128 plus a signal's number, 124 at its timeout, 125 when it was killed and 255 when its outcome is unknown. It never wakes the Machine."
|
|
6643
|
+
],
|
|
6644
|
+
examples: ["tonbo machine wait web 019a1f2e5a6b7c8d9e0f1a2b3c4d5e6f"],
|
|
6645
|
+
arguments: { name: "the Machine's name", process: "the process id `exec --detach` printed" }
|
|
6646
|
+
},
|
|
6647
|
+
"tonbo machine output": {
|
|
6648
|
+
about: [
|
|
6649
|
+
"Prints a background process's stdout and stderr together, as it wrote them, from the last 16 MiB the Machine keeps. `--follow` keeps printing until the process ends. The output is gone once the Machine sleeps. It never wakes the Machine."
|
|
6650
|
+
],
|
|
6651
|
+
examples: [
|
|
6652
|
+
"tonbo machine output web 019a1f2e5a6b7c8d9e0f1a2b3c4d5e6f",
|
|
6653
|
+
"tonbo machine output web 019a1f2e5a6b7c8d9e0f1a2b3c4d5e6f --follow"
|
|
6654
|
+
],
|
|
6655
|
+
arguments: { name: "the Machine's name", process: "the process id `exec --detach` printed" }
|
|
6656
|
+
},
|
|
6657
|
+
"tonbo machine kill": {
|
|
6658
|
+
about: [
|
|
6659
|
+
"Ends a background process: SIGTERM to everything it started, then a kill 5 seconds later. It never wakes the Machine."
|
|
6660
|
+
],
|
|
6661
|
+
examples: ["tonbo machine kill web 019a1f2e5a6b7c8d9e0f1a2b3c4d5e6f"],
|
|
6662
|
+
arguments: { name: "the Machine's name", process: "the process id `exec --detach` printed" }
|
|
6663
|
+
},
|
|
5944
6664
|
"tonbo machine file read": {
|
|
5945
6665
|
about: [
|
|
5946
6666
|
"Prints one regular file of the Machine, waking it when it is asleep. A file is gone when the Machine sleeps, so a read that woke it sees only its image."
|
|
@@ -5979,6 +6699,30 @@ var COMMAND_HELP = {
|
|
|
5979
6699
|
examples: ["tonbo machine file delete web /app/tmp --recursive"],
|
|
5980
6700
|
arguments: { name: "the Machine's name", path: "the absolute path in the Machine" }
|
|
5981
6701
|
},
|
|
6702
|
+
"tonbo machine hold set": {
|
|
6703
|
+
about: [
|
|
6704
|
+
"Keeps the Machine awake until at least now plus `--for` (1 hour by default, at most 1 hour). Running it again only moves the end later, so a script can repeat it while its work runs. It never wakes an asleep Machine, and it ends when the Machine sleeps, stops or runs out of credit or budget. An awake Machine is billed as usual. A program inside the Machine sets its own holds on `/run/tonbo-hold.sock`, with no key."
|
|
6705
|
+
],
|
|
6706
|
+
examples: ["tonbo machine hold set web build --for 30m"],
|
|
6707
|
+
arguments: {
|
|
6708
|
+
name: "the Machine's name",
|
|
6709
|
+
hold: "a name you choose for the hold, such as build"
|
|
6710
|
+
}
|
|
6711
|
+
},
|
|
6712
|
+
"tonbo machine hold rm": {
|
|
6713
|
+
about: [
|
|
6714
|
+
"Releases one hold set from outside the Machine, whether or not it existed. The Machine sleeps `sleep_after` seconds later unless something else keeps it awake."
|
|
6715
|
+
],
|
|
6716
|
+
examples: ["tonbo machine hold rm web build"],
|
|
6717
|
+
arguments: { name: "the Machine's name", hold: "the hold's name" }
|
|
6718
|
+
},
|
|
6719
|
+
"tonbo machine hold ls": {
|
|
6720
|
+
about: [
|
|
6721
|
+
"Lists the holds of the Machine's current activation, those set from outside and those a program inside set, with when each lapses. An asleep Machine has none."
|
|
6722
|
+
],
|
|
6723
|
+
examples: ["tonbo machine hold ls web"],
|
|
6724
|
+
arguments: { name: "the Machine's name" }
|
|
6725
|
+
},
|
|
5982
6726
|
"tonbo machine sleep": {
|
|
5983
6727
|
about: [
|
|
5984
6728
|
"Puts an awake Machine to sleep now, and says so when it is already asleep or still creating; its next request boots it on its latest spec. With `--wait` the CLI reads the Machine until it is asleep or `--timeout` passes."
|
|
@@ -5987,6 +6731,14 @@ var COMMAND_HELP = {
|
|
|
5987
6731
|
arguments: NAME("Machine"),
|
|
5988
6732
|
defaults: { "--timeout": String(DEFAULT_WAIT_SECONDS) }
|
|
5989
6733
|
},
|
|
6734
|
+
"tonbo machine wake": {
|
|
6735
|
+
about: [
|
|
6736
|
+
"Wakes an asleep Machine now, without a request to it, and says so when it is already awake. The wake passes the same checks as any other and is billed the same: it needs credit, room on a host and a budget that allows it, and a refused wake says why. With `--wait` the CLI reads the Machine until it is awake or `--timeout` passes."
|
|
6737
|
+
],
|
|
6738
|
+
examples: ["tonbo machine wake web --wait"],
|
|
6739
|
+
arguments: NAME("Machine"),
|
|
6740
|
+
defaults: { "--timeout": String(DEFAULT_WAIT_SECONDS) }
|
|
6741
|
+
},
|
|
5990
6742
|
"tonbo machine delete": {
|
|
5991
6743
|
about: [
|
|
5992
6744
|
"Deletes the Machine with its disk and logs; a Volume or database it binds stays. Its name is free for a new Machine at once. At a terminal it asks you to type the Machine's name first, and sends the ETag it read then.",
|
|
@@ -6165,7 +6917,7 @@ function applyHelp(program) {
|
|
|
6165
6917
|
|
|
6166
6918
|
// src/machine-cell.ts
|
|
6167
6919
|
import { readFile as readFile3, writeFile } from "node:fs/promises";
|
|
6168
|
-
import { randomUUID as
|
|
6920
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
6169
6921
|
var EXEC_EXIT_CODES = {
|
|
6170
6922
|
timeout: 124,
|
|
6171
6923
|
output_limit: 125,
|
|
@@ -6318,6 +7070,191 @@ async function machineExecCommand(deps, name, command, options = {}) {
|
|
|
6318
7070
|
deps.notice(`The command ended: ${ended.reason}.`);
|
|
6319
7071
|
exitWith(deps, execExitCode(ended));
|
|
6320
7072
|
}
|
|
7073
|
+
function processEndText(process2) {
|
|
7074
|
+
const end = process2.end;
|
|
7075
|
+
if (!end) return "-";
|
|
7076
|
+
if (end.reason === "exited") return `exited ${end.exit_code ?? 0}`;
|
|
7077
|
+
if (end.reason === "signaled") return `signaled ${end.signal ?? ""}`.trim();
|
|
7078
|
+
return end.reason;
|
|
7079
|
+
}
|
|
7080
|
+
function processExitCode(process2) {
|
|
7081
|
+
const end = process2.end;
|
|
7082
|
+
if (!end) return 0;
|
|
7083
|
+
if (end.reason === "exited" || end.reason === "signaled" || end.reason === "timeout")
|
|
7084
|
+
return execExitCode({ reason: end.reason, exit_code: end.exit_code, signal: end.signal });
|
|
7085
|
+
return end.reason === "lost" ? EXEC_EXIT_CODES.lost : EXEC_EXIT_CODES.killed;
|
|
7086
|
+
}
|
|
7087
|
+
async function machineProcessStartCommand(deps, name, command, options = {}) {
|
|
7088
|
+
if (command.length === 0)
|
|
7089
|
+
throw new Error(
|
|
7090
|
+
"Name the command after --, such as `tonbo machine exec web --detach -- npm run build`."
|
|
7091
|
+
);
|
|
7092
|
+
const body = { command };
|
|
7093
|
+
if (options.cwd) body.cwd = options.cwd;
|
|
7094
|
+
if (options.env?.length) body.env = parseEnv2(options.env);
|
|
7095
|
+
if (options.timeout !== void 0) body.timeout_seconds = options.timeout;
|
|
7096
|
+
if (options.expectActivation) body.expect_activation = options.expectActivation;
|
|
7097
|
+
try {
|
|
7098
|
+
const key = await deps.auth.apiKey();
|
|
7099
|
+
const region = await regionOf(deps, key, name);
|
|
7100
|
+
if (options.stdin) body.stdin = Buffer.from(await readAllStdin(deps)).toString("base64");
|
|
7101
|
+
const response = await cellApi(deps).fetch("startMachineProcess", key, region, {
|
|
7102
|
+
params: { name },
|
|
7103
|
+
headers: {
|
|
7104
|
+
"content-type": "application/json",
|
|
7105
|
+
"idempotency-key": options.idempotencyKey ?? randomUUID7()
|
|
7106
|
+
},
|
|
7107
|
+
body: JSON.stringify(body)
|
|
7108
|
+
});
|
|
7109
|
+
const { data } = await response.json();
|
|
7110
|
+
if (response.headers.get("tonbo-woke") === "true") {
|
|
7111
|
+
const ceiling = response.headers.get("tonbo-ceiling-usd-micros-per-hour");
|
|
7112
|
+
noticeWake(
|
|
7113
|
+
deps,
|
|
7114
|
+
{
|
|
7115
|
+
activation: data.activation,
|
|
7116
|
+
woke: true,
|
|
7117
|
+
ceiling_usd_micros_per_hour: ceiling === null ? null : Number(ceiling)
|
|
7118
|
+
},
|
|
7119
|
+
name
|
|
7120
|
+
);
|
|
7121
|
+
}
|
|
7122
|
+
print(
|
|
7123
|
+
deps,
|
|
7124
|
+
options.json,
|
|
7125
|
+
data,
|
|
7126
|
+
response.status === 200 ? `${data.process} (already started under this key, ${data.state})` : data.process
|
|
7127
|
+
);
|
|
7128
|
+
if (!options.json && response.status !== 200)
|
|
7129
|
+
deps.notice(
|
|
7130
|
+
`Started; it keeps ${name} awake until it ends or at ${data.deadline}. Read it with \`tonbo machine output ${name} ${data.process}\`, wait for it with \`tonbo machine wait ${name} ${data.process}\`.`
|
|
7131
|
+
);
|
|
7132
|
+
} catch (error) {
|
|
7133
|
+
throw explainMachineError(error);
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
7136
|
+
async function readProcess(deps, key, region, name, id) {
|
|
7137
|
+
return cellApi(deps).json("getMachineProcess", key, region, {
|
|
7138
|
+
params: { name, process: id }
|
|
7139
|
+
});
|
|
7140
|
+
}
|
|
7141
|
+
function processFields(process2) {
|
|
7142
|
+
return fields([
|
|
7143
|
+
["process", process2.process],
|
|
7144
|
+
["state", process2.state],
|
|
7145
|
+
["command", (process2.argv_preview ?? []).join(" ") || "-"],
|
|
7146
|
+
["started", process2.started_at],
|
|
7147
|
+
["deadline", process2.deadline],
|
|
7148
|
+
["ended", process2.ended_at ?? "-"],
|
|
7149
|
+
["end", processEndText(process2)],
|
|
7150
|
+
["activation", process2.activation]
|
|
7151
|
+
]);
|
|
7152
|
+
}
|
|
7153
|
+
async function machineProcessListCommand(deps, name, options = {}) {
|
|
7154
|
+
try {
|
|
7155
|
+
const key = await deps.auth.apiKey();
|
|
7156
|
+
const region = await regionOf(deps, key, name);
|
|
7157
|
+
const processes = await cellApi(deps).json(
|
|
7158
|
+
"listMachineProcesses",
|
|
7159
|
+
key,
|
|
7160
|
+
region,
|
|
7161
|
+
{
|
|
7162
|
+
params: { name }
|
|
7163
|
+
}
|
|
7164
|
+
);
|
|
7165
|
+
print(
|
|
7166
|
+
deps,
|
|
7167
|
+
options.json,
|
|
7168
|
+
processes,
|
|
7169
|
+
processes.length === 0 ? `${name} has no background processes.` : table(
|
|
7170
|
+
["PROCESS", "STATE", "STARTED", "END", "COMMAND"],
|
|
7171
|
+
processes.map((process2) => [
|
|
7172
|
+
process2.process,
|
|
7173
|
+
process2.state,
|
|
7174
|
+
process2.started_at,
|
|
7175
|
+
processEndText(process2),
|
|
7176
|
+
(process2.argv_preview ?? []).join(" ")
|
|
7177
|
+
])
|
|
7178
|
+
)
|
|
7179
|
+
);
|
|
7180
|
+
} catch (error) {
|
|
7181
|
+
throw explainMachineError(error);
|
|
7182
|
+
}
|
|
7183
|
+
}
|
|
7184
|
+
async function machineProcessWaitCommand(deps, name, id, options = {}) {
|
|
7185
|
+
const sleep = options.sleep ?? ((ms) => new Promise((done) => setTimeout(done, ms)));
|
|
7186
|
+
try {
|
|
7187
|
+
const key = await deps.auth.apiKey();
|
|
7188
|
+
const region = await regionOf(deps, key, name);
|
|
7189
|
+
let process2 = await readProcess(deps, key, region, name, id);
|
|
7190
|
+
while (process2.state === "running") {
|
|
7191
|
+
await sleep(2e3);
|
|
7192
|
+
process2 = await readProcess(deps, key, region, name, id);
|
|
7193
|
+
}
|
|
7194
|
+
print(deps, options.json, process2, processFields(process2));
|
|
7195
|
+
exitWith(deps, processExitCode(process2));
|
|
7196
|
+
} catch (error) {
|
|
7197
|
+
throw explainMachineError(error);
|
|
7198
|
+
}
|
|
7199
|
+
}
|
|
7200
|
+
async function machineProcessOutputCommand(deps, name, id, options = {}) {
|
|
7201
|
+
const sleep = options.sleep ?? ((ms) => new Promise((done) => setTimeout(done, ms)));
|
|
7202
|
+
try {
|
|
7203
|
+
const key = await deps.auth.apiKey();
|
|
7204
|
+
const region = await regionOf(deps, key, name);
|
|
7205
|
+
let offset = options.offset ?? 0;
|
|
7206
|
+
for (; ; ) {
|
|
7207
|
+
const response = await cellApi(deps).fetch("readMachineProcessOutput", key, region, {
|
|
7208
|
+
params: { name, process: id },
|
|
7209
|
+
query: { offset: String(offset) }
|
|
7210
|
+
});
|
|
7211
|
+
const bytes2 = new Uint8Array(await response.arrayBuffer());
|
|
7212
|
+
const start = Number(response.headers.get("tonbo-output-start") ?? offset);
|
|
7213
|
+
if (start > offset)
|
|
7214
|
+
deps.notice(`(${start - offset} bytes before offset ${start} were dropped from the ring)`);
|
|
7215
|
+
if (bytes2.length) writeBytes(deps, "stdout", bytes2);
|
|
7216
|
+
offset = Number(response.headers.get("tonbo-output-next-offset") ?? start + bytes2.length);
|
|
7217
|
+
const ended = response.headers.get("tonbo-process-state") === "ended";
|
|
7218
|
+
if (start + bytes2.length < offset) continue;
|
|
7219
|
+
if (!options.follow || ended) break;
|
|
7220
|
+
await sleep(1e3);
|
|
7221
|
+
}
|
|
7222
|
+
} catch (error) {
|
|
7223
|
+
throw explainMachineError(error);
|
|
7224
|
+
}
|
|
7225
|
+
}
|
|
7226
|
+
async function machineProcessKillCommand(deps, name, id, options = {}) {
|
|
7227
|
+
try {
|
|
7228
|
+
const key = await deps.auth.apiKey();
|
|
7229
|
+
const region = await regionOf(deps, key, name);
|
|
7230
|
+
const process2 = await cellApi(deps).json("killMachineProcess", key, region, {
|
|
7231
|
+
params: { name, process: id }
|
|
7232
|
+
});
|
|
7233
|
+
print(
|
|
7234
|
+
deps,
|
|
7235
|
+
options.json,
|
|
7236
|
+
process2,
|
|
7237
|
+
process2.state === "ended" ? `${id} had already ended: ${processEndText(process2)}.` : `Killing ${id}; it ends within a few seconds.`
|
|
7238
|
+
);
|
|
7239
|
+
} catch (error) {
|
|
7240
|
+
throw explainMachineError(error);
|
|
7241
|
+
}
|
|
7242
|
+
}
|
|
7243
|
+
async function machineProcessSummary(cell, key, region, name) {
|
|
7244
|
+
try {
|
|
7245
|
+
const processes = await cell.json("listMachineProcesses", key, region, {
|
|
7246
|
+
params: { name }
|
|
7247
|
+
});
|
|
7248
|
+
const line = (process2) => `${process2.process} ${(process2.argv_preview ?? []).join(" ")} (${process2.state === "running" ? `until ${process2.deadline}` : processEndText(process2)})`;
|
|
7249
|
+
const last = processes.find((process2) => process2.state === "ended");
|
|
7250
|
+
return {
|
|
7251
|
+
running: processes.filter((process2) => process2.state === "running").map(line),
|
|
7252
|
+
last: last ? line(last) : null
|
|
7253
|
+
};
|
|
7254
|
+
} catch {
|
|
7255
|
+
return null;
|
|
7256
|
+
}
|
|
7257
|
+
}
|
|
6321
7258
|
async function machineFileReadCommand(deps, name, path4, options = {}) {
|
|
6322
7259
|
try {
|
|
6323
7260
|
const key = await deps.auth.apiKey();
|
|
@@ -6348,7 +7285,7 @@ async function machineFileWriteCommand(deps, name, path4, options = {}) {
|
|
|
6348
7285
|
"if-none-match": options.create ? "*" : void 0,
|
|
6349
7286
|
"tonbo-file-mode": options.mode,
|
|
6350
7287
|
"tonbo-expect-activation": options.expectActivation,
|
|
6351
|
-
"idempotency-key":
|
|
7288
|
+
"idempotency-key": randomUUID7()
|
|
6352
7289
|
},
|
|
6353
7290
|
body: new Uint8Array(bytes2)
|
|
6354
7291
|
});
|
|
@@ -6431,7 +7368,7 @@ async function machineFileDeleteCommand(deps, name, path4, options = {}) {
|
|
|
6431
7368
|
query: { path: path4, recursive: options.recursive ? "true" : void 0 },
|
|
6432
7369
|
headers: {
|
|
6433
7370
|
"tonbo-expect-activation": options.expectActivation,
|
|
6434
|
-
"idempotency-key":
|
|
7371
|
+
"idempotency-key": randomUUID7()
|
|
6435
7372
|
}
|
|
6436
7373
|
});
|
|
6437
7374
|
print(deps, options.json, { path: path4 }, `Deleted ${path4}.`);
|
|
@@ -6439,6 +7376,76 @@ async function machineFileDeleteCommand(deps, name, path4, options = {}) {
|
|
|
6439
7376
|
throw explainMachineError(error);
|
|
6440
7377
|
}
|
|
6441
7378
|
}
|
|
7379
|
+
var HOLD_MAX_SECONDS = 3600;
|
|
7380
|
+
function parseHoldDuration(value) {
|
|
7381
|
+
const match = /^(\d+)\s*([smh]?)$/.exec(value.trim());
|
|
7382
|
+
const unit = { "": 1, s: 1, m: 60, h: 3600 }[match?.[2] ?? ""] ?? 1;
|
|
7383
|
+
const seconds = match ? Number(match[1]) * unit : Number.NaN;
|
|
7384
|
+
if (!Number.isSafeInteger(seconds) || seconds < 1 || seconds > HOLD_MAX_SECONDS)
|
|
7385
|
+
throw new Error(`--for ${value} is not between 1 second and 1 hour, such as 90s, 45m or 1h.`);
|
|
7386
|
+
return seconds;
|
|
7387
|
+
}
|
|
7388
|
+
async function machineHoldSetCommand(deps, name, hold, options = {}) {
|
|
7389
|
+
try {
|
|
7390
|
+
const key = await deps.auth.apiKey();
|
|
7391
|
+
const region = await regionOf(deps, key, name);
|
|
7392
|
+
const placed = await cellApi(deps).json("setMachineHold", key, region, {
|
|
7393
|
+
params: { name, hold },
|
|
7394
|
+
headers: {
|
|
7395
|
+
"content-type": "application/json",
|
|
7396
|
+
"tonbo-expect-activation": options.expectActivation
|
|
7397
|
+
},
|
|
7398
|
+
body: JSON.stringify({ seconds: options.for ?? HOLD_MAX_SECONDS })
|
|
7399
|
+
});
|
|
7400
|
+
print(
|
|
7401
|
+
deps,
|
|
7402
|
+
options.json,
|
|
7403
|
+
placed,
|
|
7404
|
+
`${name} stays awake until ${placed.until} (hold ${placed.hold}). Run this again before then to extend it; \`tonbo machine hold rm ${name} ${placed.hold}\` lets it sleep. An awake Machine is billed as usual.`
|
|
7405
|
+
);
|
|
7406
|
+
} catch (error) {
|
|
7407
|
+
throw explainMachineError(error);
|
|
7408
|
+
}
|
|
7409
|
+
}
|
|
7410
|
+
async function machineHoldRmCommand(deps, name, hold, options = {}) {
|
|
7411
|
+
try {
|
|
7412
|
+
const key = await deps.auth.apiKey();
|
|
7413
|
+
const region = await regionOf(deps, key, name);
|
|
7414
|
+
await cellApi(deps).fetch("releaseMachineHold", key, region, { params: { name, hold } });
|
|
7415
|
+
print(
|
|
7416
|
+
deps,
|
|
7417
|
+
options.json,
|
|
7418
|
+
{ hold },
|
|
7419
|
+
`Released ${hold}. ${name} sleeps once nothing else keeps it awake.`
|
|
7420
|
+
);
|
|
7421
|
+
} catch (error) {
|
|
7422
|
+
throw explainMachineError(error);
|
|
7423
|
+
}
|
|
7424
|
+
}
|
|
7425
|
+
async function machineHoldLsCommand(deps, name, options = {}) {
|
|
7426
|
+
try {
|
|
7427
|
+
const key = await deps.auth.apiKey();
|
|
7428
|
+
const region = await regionOf(deps, key, name);
|
|
7429
|
+
const holds = await cellApi(deps).json("listMachineHolds", key, region, {
|
|
7430
|
+
params: { name }
|
|
7431
|
+
});
|
|
7432
|
+
print(
|
|
7433
|
+
deps,
|
|
7434
|
+
options.json,
|
|
7435
|
+
holds,
|
|
7436
|
+
holds.length === 0 ? `${name} has no holds.` : table(
|
|
7437
|
+
["HOLD", "SET FROM", "UNTIL"],
|
|
7438
|
+
holds.map((entry) => [
|
|
7439
|
+
entry.hold,
|
|
7440
|
+
entry.source === "guest" ? "inside the Machine" : "outside (API or CLI)",
|
|
7441
|
+
entry.until
|
|
7442
|
+
])
|
|
7443
|
+
)
|
|
7444
|
+
);
|
|
7445
|
+
} catch (error) {
|
|
7446
|
+
throw explainMachineError(error);
|
|
7447
|
+
}
|
|
7448
|
+
}
|
|
6442
7449
|
|
|
6443
7450
|
// src/prompt.ts
|
|
6444
7451
|
import select from "@inquirer/select";
|
|
@@ -6709,8 +7716,9 @@ function createDependencies(selection = {}) {
|
|
|
6709
7716
|
loginOrigin: loginOrigin(api),
|
|
6710
7717
|
account
|
|
6711
7718
|
});
|
|
7719
|
+
const notice = (text) => console.error(text);
|
|
6712
7720
|
return {
|
|
6713
|
-
api: new ManagementApi(fetch, api),
|
|
7721
|
+
api: new ManagementApi(noticingBudget(fetch, notice), api),
|
|
6714
7722
|
ask: terminalAsk,
|
|
6715
7723
|
auth: new AuthClient(
|
|
6716
7724
|
credentials,
|
|
@@ -6726,7 +7734,9 @@ function createDependencies(selection = {}) {
|
|
|
6726
7734
|
credentials,
|
|
6727
7735
|
interactive: () => process.stdin.isTTY === true && process.stderr.isTTY === true,
|
|
6728
7736
|
output: (text) => console.log(text),
|
|
6729
|
-
notice
|
|
7737
|
+
notice,
|
|
7738
|
+
openUrl: openBrowser,
|
|
7739
|
+
processes: (key, region, name) => machineProcessSummary(new CellApi(fetch), key, region, name),
|
|
6730
7740
|
progress: new TerminalProgress(process.stderr),
|
|
6731
7741
|
select: terminalSelect
|
|
6732
7742
|
};
|
|
@@ -6740,7 +7750,7 @@ function parseWhole(value) {
|
|
|
6740
7750
|
return Number(value);
|
|
6741
7751
|
}
|
|
6742
7752
|
function operationOptions(command, operationId) {
|
|
6743
|
-
const query =
|
|
7753
|
+
const query = managementOperation(operationId).query;
|
|
6744
7754
|
if (query.includes("limit"))
|
|
6745
7755
|
command.option("--limit <count>", "the most items to return", parseLimit);
|
|
6746
7756
|
if (query.includes("cursor"))
|
|
@@ -6749,6 +7759,10 @@ function operationOptions(command, operationId) {
|
|
|
6749
7759
|
}
|
|
6750
7760
|
var ifMatchHelp = (action) => `${action} only the Machine at the ETag \`tonbo machine show\` printed (with or without its quotes)`;
|
|
6751
7761
|
var repeated = (value, previous = []) => [...previous, value];
|
|
7762
|
+
var budgetOption = (whom) => new Option(
|
|
7763
|
+
"--budget <budget>",
|
|
7764
|
+
`put ${whom} under a monthly budget: an amount in dollars such as 50 creates one, a budget's name adds to it`
|
|
7765
|
+
).hideHelp();
|
|
6752
7766
|
var roleOption = (description) => new Option("--role <role>", description).choices(accountRoles);
|
|
6753
7767
|
function machineSpecOptions(command, update) {
|
|
6754
7768
|
const later = update ? "; applies at the next boot" : "";
|
|
@@ -6856,7 +7870,7 @@ function createProgram(makeDependencies = createDependencies) {
|
|
|
6856
7870
|
).option(
|
|
6857
7871
|
"--idempotency-key <key>",
|
|
6858
7872
|
"reuse the key of an unconfirmed creation to get the Agent it created"
|
|
6859
|
-
),
|
|
7873
|
+
).addOption(budgetOption("the Agent")),
|
|
6860
7874
|
"createAgent"
|
|
6861
7875
|
).action(
|
|
6862
7876
|
async (name, options) => agentCreateCommand(dependencies(), name, options)
|
|
@@ -6891,7 +7905,7 @@ function createProgram(makeDependencies = createDependencies) {
|
|
|
6891
7905
|
repeated
|
|
6892
7906
|
).addOption(
|
|
6893
7907
|
new Option("--expires-in <period>", "when the key expires").choices(apiKeyExpirations)
|
|
6894
|
-
),
|
|
7908
|
+
).addOption(budgetOption("the key's member")),
|
|
6895
7909
|
"createApiKey"
|
|
6896
7910
|
).action(
|
|
6897
7911
|
async (name, options) => apiKeyCreateCommand(dependencies(), name, options)
|
|
@@ -6905,6 +7919,37 @@ function createProgram(makeDependencies = createDependencies) {
|
|
|
6905
7919
|
).action(
|
|
6906
7920
|
async (keyId, options) => apiKeyRevokeCommand(dependencies(), keyId, options)
|
|
6907
7921
|
);
|
|
7922
|
+
const budget = program.command("budget", { hidden: true }).description("the account's monthly spend budgets");
|
|
7923
|
+
operationOptions(
|
|
7924
|
+
budget.command("list").description("list the budgets, the account budget first"),
|
|
7925
|
+
"listBudgets"
|
|
7926
|
+
).action(async (options) => budgetListCommand(dependencies(), options));
|
|
7927
|
+
operationOptions(
|
|
7928
|
+
budget.command("show <budget>").description("show one budget, by its name or id"),
|
|
7929
|
+
"getBudget"
|
|
7930
|
+
).action(
|
|
7931
|
+
async (name, options) => budgetShowCommand(dependencies(), name, options)
|
|
7932
|
+
);
|
|
7933
|
+
operationOptions(
|
|
7934
|
+
budget.command("add <name>").description("create a budget for you or Agents you created").requiredOption("--limit <dollars>", "the monthly limit in dollars, such as 50").option(
|
|
7935
|
+
"--member <member>",
|
|
7936
|
+
"a member to cover: you, or an Agent you created (subject, id or name); repeat for several",
|
|
7937
|
+
repeated
|
|
7938
|
+
),
|
|
7939
|
+
"createBudget"
|
|
7940
|
+
).action(
|
|
7941
|
+
async (name, options) => budgetAddCommand(dependencies(), name, options)
|
|
7942
|
+
);
|
|
7943
|
+
operationOptions(
|
|
7944
|
+
budget.command("set <budget>").description("lower the budget that covers you, or add Agents you created to it").option("--limit <dollars>", "a lower monthly limit; a raise opens the console").option(
|
|
7945
|
+
"--add <member>",
|
|
7946
|
+
"an Agent you created to cover too (subject, id or name); repeat for several",
|
|
7947
|
+
repeated
|
|
7948
|
+
),
|
|
7949
|
+
"changeBudget"
|
|
7950
|
+
).action(
|
|
7951
|
+
async (name, options) => budgetSetCommand(dependencies(), name, options)
|
|
7952
|
+
);
|
|
6908
7953
|
const bucket = program.command("bucket").description("the account's Streams buckets");
|
|
6909
7954
|
operationOptions(bucket.command("list").description("list the buckets"), "listBuckets").action(
|
|
6910
7955
|
async (options) => bucketListCommand(dependencies(), options)
|
|
@@ -7116,7 +8161,10 @@ function createProgram(makeDependencies = createDependencies) {
|
|
|
7116
8161
|
machineSpecOptions(
|
|
7117
8162
|
machine.command("update <name> [command...]").description(
|
|
7118
8163
|
"change a Machine; [command...] after -- replaces its command at the next boot"
|
|
7119
|
-
).option("--image <reference>", "a new image, named as for create; applies at the next boot").option("--image-command", "run the image's own CMD again at the next boot").option("--unset-env <KEY>", "remove an environment variable; repeat for several", repeated).option("--clear-ports", "remove every port").option("--if-match <etag>", ifMatchHelp("change")).option("--discard-snapshot", "apply a change that discards the Machine's snapshot").option("--no-volume", "remove the Machine's Volume at the next boot; the Volume is kept").option(
|
|
8164
|
+
).option("--image <reference>", "a new image, named as for create; applies at the next boot").option("--image-command", "run the image's own CMD again at the next boot").option("--unset-env <KEY>", "remove an environment variable; repeat for several", repeated).option("--clear-ports", "remove every port").option("--if-match <etag>", ifMatchHelp("change")).option("--discard-snapshot", "apply a change that discards the Machine's snapshot").option("--no-volume", "remove the Machine's Volume at the next boot; the Volume is kept").option(
|
|
8165
|
+
"--database <name>",
|
|
8166
|
+
"bind the account's database <name> of the Machine's region instead; applies at the next wake"
|
|
8167
|
+
).option("--no-database", "bind no database from the next wake; the database is kept").option("--no-delete-after", "never delete the Machine for being asleep"),
|
|
7120
8168
|
true
|
|
7121
8169
|
),
|
|
7122
8170
|
"updateMachine"
|
|
@@ -7126,13 +8174,16 @@ function createProgram(makeDependencies = createDependencies) {
|
|
|
7126
8174
|
operationOptions(
|
|
7127
8175
|
machine.command("exec <name> [command...]").description(
|
|
7128
8176
|
"run a command in a Machine, waking it first, and print its output; exits with its status"
|
|
8177
|
+
).option(
|
|
8178
|
+
"--detach",
|
|
8179
|
+
"start it in the background and print its id at once; it keeps the Machine awake until it ends or its timeout"
|
|
7129
8180
|
).option("--cwd <path>", "the working directory, an absolute path").option(
|
|
7130
8181
|
"--env <KEY=VALUE>",
|
|
7131
8182
|
"set an environment variable for the command; repeat for several",
|
|
7132
8183
|
repeated
|
|
7133
8184
|
).option(
|
|
7134
8185
|
"--timeout <seconds>",
|
|
7135
|
-
"kill the command after this many seconds (default 600, at most 3600)",
|
|
8186
|
+
"kill the command after this many seconds (default 600, at most 3600; with --detach default 3600, at most 21600)",
|
|
7136
8187
|
parseWhole
|
|
7137
8188
|
).option("--stdin", "send this process's stdin to the command (at most 1 MiB)").option(
|
|
7138
8189
|
"--expect-activation <activation>",
|
|
@@ -7143,7 +8194,31 @@ function createProgram(makeDependencies = createDependencies) {
|
|
|
7143
8194
|
),
|
|
7144
8195
|
"execMachineCommand"
|
|
7145
8196
|
).action(
|
|
7146
|
-
async (name, command, options) => machineExecCommand(dependencies(), name, command, options)
|
|
8197
|
+
async (name, command, options) => options.detach ? machineProcessStartCommand(dependencies(), name, command, options) : machineExecCommand(dependencies(), name, command, options)
|
|
8198
|
+
);
|
|
8199
|
+
operationOptions(
|
|
8200
|
+
machine.command("ps <name>").description("list a Machine's background processes, running and recently ended"),
|
|
8201
|
+
"listMachineProcesses"
|
|
8202
|
+
).action(
|
|
8203
|
+
async (name, options) => machineProcessListCommand(dependencies(), name, options)
|
|
8204
|
+
);
|
|
8205
|
+
operationOptions(
|
|
8206
|
+
machine.command("wait <name> <process>").description("wait for a background process to end; exits with its status"),
|
|
8207
|
+
"getMachineProcess"
|
|
8208
|
+
).action(
|
|
8209
|
+
async (name, id, options) => machineProcessWaitCommand(dependencies(), name, id, options)
|
|
8210
|
+
);
|
|
8211
|
+
operationOptions(
|
|
8212
|
+
machine.command("output <name> <process>").description("print a background process's output, stdout and stderr together").option("--offset <bytes>", "start at this byte offset", parseWhole).option("--follow", "keep printing new output until the process ends"),
|
|
8213
|
+
"readMachineProcessOutput"
|
|
8214
|
+
).action(
|
|
8215
|
+
async (name, id, options) => machineProcessOutputCommand(dependencies(), name, id, options)
|
|
8216
|
+
);
|
|
8217
|
+
operationOptions(
|
|
8218
|
+
machine.command("kill <name> <process>").description("end a background process: SIGTERM, then a kill after 5 seconds"),
|
|
8219
|
+
"killMachineProcess"
|
|
8220
|
+
).action(
|
|
8221
|
+
async (name, id, options) => machineProcessKillCommand(dependencies(), name, id, options)
|
|
7147
8222
|
);
|
|
7148
8223
|
const file = machine.command("file").description("read, write, stat, list and delete a Machine's files");
|
|
7149
8224
|
operationOptions(
|
|
@@ -7176,12 +8251,41 @@ function createProgram(makeDependencies = createDependencies) {
|
|
|
7176
8251
|
).action(
|
|
7177
8252
|
async (name, path4, options) => machineFileDeleteCommand(dependencies(), name, path4, options)
|
|
7178
8253
|
);
|
|
8254
|
+
const hold = machine.command("hold").description("keep a Machine awake for work that has no open connection");
|
|
8255
|
+
operationOptions(
|
|
8256
|
+
hold.command("set <name> <hold>").description("keep a Machine awake until a hold lapses; never wakes it").option(
|
|
8257
|
+
"--for <duration>",
|
|
8258
|
+
"how long from now, such as 90s, 45m or 1h (at most 1h)",
|
|
8259
|
+
parseHoldDuration
|
|
8260
|
+
).option("--expect-activation <activation>", "hold only this activation"),
|
|
8261
|
+
"setMachineHold"
|
|
8262
|
+
).action(
|
|
8263
|
+
async (name, holdName, options) => machineHoldSetCommand(dependencies(), name, holdName, options)
|
|
8264
|
+
);
|
|
8265
|
+
operationOptions(
|
|
8266
|
+
hold.command("rm <name> <hold>").description("release a hold set from outside the Machine"),
|
|
8267
|
+
"releaseMachineHold"
|
|
8268
|
+
).action(
|
|
8269
|
+
async (name, holdName, options) => machineHoldRmCommand(dependencies(), name, holdName, options)
|
|
8270
|
+
);
|
|
8271
|
+
operationOptions(
|
|
8272
|
+
hold.command("ls <name>").description("list what holds a Machine awake"),
|
|
8273
|
+
"listMachineHolds"
|
|
8274
|
+
).action(
|
|
8275
|
+
async (name, options) => machineHoldLsCommand(dependencies(), name, options)
|
|
8276
|
+
);
|
|
7179
8277
|
operationOptions(
|
|
7180
8278
|
machine.command("sleep <name>").description("put a Machine to sleep now; its next request boots it on its latest spec").option("--if-match <etag>", ifMatchHelp("put to sleep")).option("--wait", "wait until the Machine is asleep").option("--timeout <seconds>", "how long --wait waits, in seconds", parseWhole),
|
|
7181
8279
|
"sleepMachine"
|
|
7182
8280
|
).action(
|
|
7183
8281
|
async (name, options) => machineSleepCommand(dependencies(), name, options)
|
|
7184
8282
|
);
|
|
8283
|
+
operationOptions(
|
|
8284
|
+
machine.command("wake <name>").description("wake an asleep Machine now, without a request to it").option("--if-match <etag>", ifMatchHelp("wake")).option("--wait", "wait until the Machine is awake").option("--timeout <seconds>", "how long --wait waits, in seconds", parseWhole),
|
|
8285
|
+
"wakeMachine"
|
|
8286
|
+
).action(
|
|
8287
|
+
async (name, options) => machineWakeCommand(dependencies(), name, options)
|
|
8288
|
+
);
|
|
7185
8289
|
operationOptions(
|
|
7186
8290
|
machine.command("delete <name>").description("delete a Machine with its disk and logs, or forget one of your computers").option("--yes", "delete without asking; required when stdin is not a terminal").option("--if-match <etag>", ifMatchHelp("delete")),
|
|
7187
8291
|
"deleteMachine"
|