@tonbo/cli 0.7.2 → 0.8.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/README.md +11 -0
- package/dist/bin/tonbo.js +163 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ Every command other than `login` and `logout` calls one operation of the [Manage
|
|
|
35
35
|
| `tonbo region list` | `listRegions` | `GET /v1/regions` |
|
|
36
36
|
| `tonbo member list [--limit <n>] [--cursor <c>]` | `listMembers` | `GET /v1/members` |
|
|
37
37
|
| `tonbo member update <member> [--role <role>] [--name <name>]` | `updateMember` | `PATCH /v1/members/{subject}` |
|
|
38
|
+
| `tonbo workspace list [--limit <n>] [--cursor <c>]` | `listGitWorkspaces` | `GET /v1/git/workspaces` |
|
|
38
39
|
| `tonbo agent create <name> [--role <role>] [--region <region>] [--idempotency-key <key>]` | `createAgent` | `POST /v1/agents` |
|
|
39
40
|
| `tonbo api-key list [--limit <n>] [--cursor <c>]` | `listApiKeys` | `GET /v1/api-keys` |
|
|
40
41
|
| `tonbo api-key create <name> [--member <member>] [--read-only] [--bucket <name>]… [--database <name>]… [--database-prefix <prefix>]… [--stream <name>]… [--stream-prefix <prefix>]… [--expires-in <period>]` | `createApiKey` | `POST /v1/api-keys` |
|
|
@@ -101,6 +102,14 @@ tonbo agent create my-agent --region us-east-1
|
|
|
101
102
|
|
|
102
103
|
`agent create` creates an Agent identity with a role (`member` unless `--role` names another). Every Agent has a home region, chosen when it is created and never changed afterwards. Without `--region`, Tonbo uses the only region that accepts new Agents; when several do, an interactive `agent create` asks which one to use, and a non-interactive request without `--region` is refused.
|
|
103
104
|
|
|
105
|
+
## Git workspaces
|
|
106
|
+
|
|
107
|
+
```console
|
|
108
|
+
tonbo workspace list
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`workspace list` lists the account's git workspaces, the default first: each one's id, name, whether it is the default, its region and where gitd serves it (`<git origin>/<ws_hex>`, the prefix of every repository URL in it). Every account has a default workspace from its creation. Listing needs only a role on the account; it admits nothing, and every git token is still admitted when it is minted.
|
|
112
|
+
|
|
104
113
|
## API keys
|
|
105
114
|
|
|
106
115
|
```console
|
|
@@ -168,6 +177,8 @@ The image is `tonbo.dev/platform`, the platform image each node release ships, o
|
|
|
168
177
|
|
|
169
178
|
`machine update` changes only what its options name. It reads the Machine and writes the change at the revision it read, or at `--revision`, so a change someone made in between is refused with `revision_mismatch` rather than overwritten. `--env` and `--unset-env` change single variables; `--port` replaces every port, naming a public port again keeps its limits, and the current default port stays the default while it is still listed unless `--default-port` names another. Ports and `--sleep-after` apply at once; image, command, shape and env at the Machine's next boot. `machine sleep` puts an awake Machine to sleep now; its next connection wakes it. `machine delete` deletes the Machine with its disk, database and logs, and its name is free for a new Machine at once.
|
|
170
179
|
|
|
180
|
+
In a personal account, `machine list`, `machine show` and `machine delete` also cover your own computers, which `yield login` registers (Machines of kind `byoc`, which run nothing Tonbo operates). They are served even while Machines are not open in any region. `machine list` prints them in a second table with their system, agent harnesses, when they last reported and the state of their login key; `machine show` adds the hostname and device id, which only you see. `machine delete` forgets a computer: it disappears from the list at once and its name is free once the forget completes. Forgetting does not revoke the computer's key, so a computer whose key is still active registers again at its next start; the command says so and suggests `yield logout` on it or `tonbo api-key revoke` first. An organization lists managed Machines only.
|
|
181
|
+
|
|
171
182
|
A refusal of machine-v2's `errors` prints the server's detail and then what to do next, for example `machine shapes` for `shape_unavailable` or a shorter name for `hostname_too_long`.
|
|
172
183
|
|
|
173
184
|
## Usage
|
package/dist/bin/tonbo.js
CHANGED
|
@@ -486,6 +486,17 @@ var managementOperations = {
|
|
|
486
486
|
"idempotencyKey": false,
|
|
487
487
|
"cli": "tonbo usage show"
|
|
488
488
|
},
|
|
489
|
+
"listGitWorkspaces": {
|
|
490
|
+
"method": "GET",
|
|
491
|
+
"path": "/git/workspaces",
|
|
492
|
+
"pathParams": [],
|
|
493
|
+
"query": [
|
|
494
|
+
"cursor",
|
|
495
|
+
"limit"
|
|
496
|
+
],
|
|
497
|
+
"idempotencyKey": false,
|
|
498
|
+
"cli": "tonbo workspace list"
|
|
499
|
+
},
|
|
489
500
|
"listDatabases": {
|
|
490
501
|
"method": "GET",
|
|
491
502
|
"path": "/databases",
|
|
@@ -1188,7 +1199,7 @@ var ManagementApi = class {
|
|
|
1188
1199
|
page(operationId, key, { limit, cursor } = {}, query = {}) {
|
|
1189
1200
|
return this.request(operationId, key, { query: { ...query, limit, cursor } });
|
|
1190
1201
|
}
|
|
1191
|
-
async request(operationId, key, { params = {}, query = {}, body, idempotencyKey, ifMatch }) {
|
|
1202
|
+
async request(operationId, key, { params = {}, query = {}, body, idempotencyKey, ifMatch }, seen) {
|
|
1192
1203
|
const operation = managementOperations[operationId];
|
|
1193
1204
|
const path2 = operation.path.replace(/\{([^}]+)\}/g, (_, name) => {
|
|
1194
1205
|
const value = params[name];
|
|
@@ -1216,7 +1227,12 @@ var ManagementApi = class {
|
|
|
1216
1227
|
const queryString = search.toString();
|
|
1217
1228
|
const url = `${this.apiOrigin}${managementApiBasePath}${path2}${queryString ? `?${queryString}` : ""}`;
|
|
1218
1229
|
try {
|
|
1219
|
-
|
|
1230
|
+
const fetcher = seen ? async (input, init) => {
|
|
1231
|
+
const response = await this.fetcher(input, init);
|
|
1232
|
+
seen(response);
|
|
1233
|
+
return response;
|
|
1234
|
+
} : this.fetcher;
|
|
1235
|
+
return await requestJson(fetcher, url, {
|
|
1220
1236
|
method: operation.method,
|
|
1221
1237
|
headers,
|
|
1222
1238
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
@@ -1236,6 +1252,9 @@ var ManagementApi = class {
|
|
|
1236
1252
|
listMembers(key, page) {
|
|
1237
1253
|
return this.page("listMembers", key, page);
|
|
1238
1254
|
}
|
|
1255
|
+
listGitWorkspaces(key, page) {
|
|
1256
|
+
return this.page("listGitWorkspaces", key, page);
|
|
1257
|
+
}
|
|
1239
1258
|
updateMember(key, subject, change) {
|
|
1240
1259
|
return this.call("updateMember", key, { params: { subject }, body: change });
|
|
1241
1260
|
}
|
|
@@ -1360,13 +1379,18 @@ var ManagementApi = class {
|
|
|
1360
1379
|
sleepMachine(key, name) {
|
|
1361
1380
|
return this.call("sleepMachine", key, { params: { name } });
|
|
1362
1381
|
}
|
|
1363
|
-
/** The Machine being deleted
|
|
1364
|
-
*
|
|
1382
|
+
/** The Machine being deleted or the device forgotten, with its kind; the
|
|
1383
|
+
* Machine is null when the API answered 204, whose `X-Tonbo-Machine-Kind`
|
|
1384
|
+
* says whether a name was released (`managed`: its create never reached its
|
|
1385
|
+
* region) or a device was forgotten (`byoc`: its region held no row). */
|
|
1365
1386
|
async deleteMachine(key, name) {
|
|
1366
|
-
const
|
|
1367
|
-
|
|
1387
|
+
const headers = { kind: null };
|
|
1388
|
+
const answer = await this.request("deleteMachine", key, { params: { name } }, (response) => {
|
|
1389
|
+
headers.kind = response.headers.get("x-tonbo-machine-kind");
|
|
1368
1390
|
});
|
|
1369
|
-
|
|
1391
|
+
if (answer?.data)
|
|
1392
|
+
return { machine: answer.data, kind: answer.data.kind };
|
|
1393
|
+
return { machine: null, kind: headers.kind === "byoc" ? "byoc" : "managed" };
|
|
1370
1394
|
}
|
|
1371
1395
|
/** The whole answer: the shapes and the default one. */
|
|
1372
1396
|
listMachineShapes(key) {
|
|
@@ -1700,6 +1724,29 @@ async function memberListCommand(deps, options = {}) {
|
|
|
1700
1724
|
) : "No members."
|
|
1701
1725
|
);
|
|
1702
1726
|
}
|
|
1727
|
+
async function workspaceListCommand(deps, options = {}) {
|
|
1728
|
+
const page = await deps.api.listGitWorkspaces(await deps.auth.apiKey(), {
|
|
1729
|
+
limit: options.limit,
|
|
1730
|
+
cursor: options.cursor
|
|
1731
|
+
});
|
|
1732
|
+
printPage(
|
|
1733
|
+
deps,
|
|
1734
|
+
options.json,
|
|
1735
|
+
page,
|
|
1736
|
+
(workspaces) => workspaces.length ? table(
|
|
1737
|
+
["ID", "NAME", "DEFAULT", "REGION", "GIT ORIGIN", "STATE", "CREATED"],
|
|
1738
|
+
workspaces.map((workspace) => [
|
|
1739
|
+
workspace.id,
|
|
1740
|
+
workspace.name ?? "",
|
|
1741
|
+
workspace.default ? "yes" : "",
|
|
1742
|
+
workspace.region,
|
|
1743
|
+
`${workspace.git_origin}/${workspace.ws_hex}`,
|
|
1744
|
+
workspace.state,
|
|
1745
|
+
workspace.created_at
|
|
1746
|
+
])
|
|
1747
|
+
) : "No git workspaces."
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1703
1750
|
async function resolveMemberSubject(deps, key, member) {
|
|
1704
1751
|
if (member.startsWith("agent.")) return member;
|
|
1705
1752
|
const subjects = /* @__PURE__ */ new Set();
|
|
@@ -2298,7 +2345,8 @@ var MACHINE_ERROR_HINTS = {
|
|
|
2298
2345
|
idempotency_conflict: "That idempotency key belongs to another request: leave out --idempotency-key to create a new Machine.",
|
|
2299
2346
|
create_rate_limited: "Too many Machines were created in the last hour; try again later.",
|
|
2300
2347
|
image_pulling: "The Machine's image is still being pulled; the request was not run. Try again shortly; `tonbo machine show` reports the image's state.",
|
|
2301
|
-
image_failed: "The Machine's image could not be pulled; `tonbo machine show` says why. Fix the image, then name it again with `tonbo machine update --image`."
|
|
2348
|
+
image_failed: "The Machine's image could not be pulled; `tonbo machine show` says why. Fix the image, then name it again with `tonbo machine update --image`.",
|
|
2349
|
+
machine_kind_mismatch: "That Machine is one of your computers: `tonbo machine show` and `tonbo machine delete` serve it, and nothing else does."
|
|
2302
2350
|
};
|
|
2303
2351
|
var MACHINE_IMAGE_FAILURE_HINTS = {
|
|
2304
2352
|
image_unavailable: "The registry no longer serves that image publicly, or served other bytes for it: publish it again, or use another image",
|
|
@@ -2381,6 +2429,32 @@ function machineFields(machine) {
|
|
|
2381
2429
|
["Updated", machine.updated_at]
|
|
2382
2430
|
]);
|
|
2383
2431
|
}
|
|
2432
|
+
function keyStateText(machine) {
|
|
2433
|
+
const state = machine.byoc.key_state;
|
|
2434
|
+
if (state === "active") return state;
|
|
2435
|
+
return `${state}: this computer is signed out; \`yield login\` on it signs it in again`;
|
|
2436
|
+
}
|
|
2437
|
+
function deviceFields(machine) {
|
|
2438
|
+
const { byoc } = machine;
|
|
2439
|
+
return fields([
|
|
2440
|
+
["Name", machine.name],
|
|
2441
|
+
["ID", machine.id],
|
|
2442
|
+
["Kind", "byoc (one of your computers)"],
|
|
2443
|
+
["Region", machine.region],
|
|
2444
|
+
...byoc.hostname === void 0 ? [] : [["Hostname", byoc.hostname]],
|
|
2445
|
+
...byoc.device_id === void 0 ? [] : [["Device ID", byoc.device_id]],
|
|
2446
|
+
["System", `${byoc.os}/${byoc.arch}`],
|
|
2447
|
+
["Client", `${byoc.client} ${byoc.client_version}`],
|
|
2448
|
+
["Harnesses", byoc.harnesses.join(", ") || "none"],
|
|
2449
|
+
["Last seen", byoc.last_seen],
|
|
2450
|
+
["Key", keyStateText(machine)],
|
|
2451
|
+
["Created", machine.created_at],
|
|
2452
|
+
["Updated", machine.updated_at]
|
|
2453
|
+
]);
|
|
2454
|
+
}
|
|
2455
|
+
function anyMachineFields(machine) {
|
|
2456
|
+
return machine.kind === "byoc" ? deviceFields(machine) : machineFields(machine);
|
|
2457
|
+
}
|
|
2384
2458
|
function parsePorts(specs, current) {
|
|
2385
2459
|
const accesses = machinePortAccess;
|
|
2386
2460
|
return specs.map((spec) => {
|
|
@@ -2425,23 +2499,41 @@ async function machineListCommand(deps, options = {}) {
|
|
|
2425
2499
|
limit: options.limit,
|
|
2426
2500
|
cursor: options.cursor
|
|
2427
2501
|
});
|
|
2428
|
-
printPage(
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
(
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2502
|
+
printPage(deps, options.json, page, (machines) => {
|
|
2503
|
+
const managed = machines.filter((machine) => machine.kind !== "byoc");
|
|
2504
|
+
const devices = machines.filter((machine) => machine.kind === "byoc");
|
|
2505
|
+
const tables = [];
|
|
2506
|
+
if (managed.length)
|
|
2507
|
+
tables.push(
|
|
2508
|
+
table(
|
|
2509
|
+
["NAME", "REGION", "STATE", "POWER", "SHAPE", "IMAGE", "HTTPS"],
|
|
2510
|
+
managed.map((machine) => [
|
|
2511
|
+
machine.name,
|
|
2512
|
+
machine.region,
|
|
2513
|
+
machine.state,
|
|
2514
|
+
machine.status.power,
|
|
2515
|
+
machine.shape,
|
|
2516
|
+
machine.image.reference,
|
|
2517
|
+
machine.hostnames.https
|
|
2518
|
+
])
|
|
2519
|
+
)
|
|
2520
|
+
);
|
|
2521
|
+
if (devices.length)
|
|
2522
|
+
tables.push(
|
|
2523
|
+
table(
|
|
2524
|
+
["COMPUTER", "REGION", "SYSTEM", "HARNESSES", "LAST SEEN", "KEY"],
|
|
2525
|
+
devices.map((machine) => [
|
|
2526
|
+
machine.name,
|
|
2527
|
+
machine.region,
|
|
2528
|
+
`${machine.byoc.os}/${machine.byoc.arch}`,
|
|
2529
|
+
machine.byoc.harnesses.join(",") || "none",
|
|
2530
|
+
machine.byoc.last_seen,
|
|
2531
|
+
machine.byoc.key_state
|
|
2532
|
+
])
|
|
2533
|
+
)
|
|
2534
|
+
);
|
|
2535
|
+
return tables.length ? tables.join("\n\n") : "No Machines.";
|
|
2536
|
+
});
|
|
2445
2537
|
if (page.unavailable_regions.length)
|
|
2446
2538
|
deps.notice(
|
|
2447
2539
|
`Could not read the Machines in ${page.unavailable_regions.join(", ")}; they are missing from this list.`
|
|
@@ -2488,7 +2580,7 @@ async function machineCreateCommand(deps, name, command, options = {}) {
|
|
|
2488
2580
|
async function machineShowCommand(deps, name, options = {}) {
|
|
2489
2581
|
await machineCommand(async () => {
|
|
2490
2582
|
const machine = await deps.api.getMachine(await deps.auth.apiKey(), name);
|
|
2491
|
-
print(deps, options.json, machine,
|
|
2583
|
+
print(deps, options.json, machine, anyMachineFields(machine));
|
|
2492
2584
|
});
|
|
2493
2585
|
}
|
|
2494
2586
|
async function machineUpdateCommand(deps, name, command, options = {}) {
|
|
@@ -2498,7 +2590,12 @@ async function machineUpdateCommand(deps, name, command, options = {}) {
|
|
|
2498
2590
|
if (options.clearPorts && (options.port?.length || options.defaultPort !== void 0))
|
|
2499
2591
|
throw new Error("Give --port or --clear-ports, not both.");
|
|
2500
2592
|
const key = await deps.auth.apiKey();
|
|
2501
|
-
const
|
|
2593
|
+
const read = await deps.api.getMachine(key, name);
|
|
2594
|
+
if (read.kind === "byoc")
|
|
2595
|
+
throw new Error(
|
|
2596
|
+
`${name} is one of your computers; only \`tonbo machine show\` and \`tonbo machine delete\` serve it.`
|
|
2597
|
+
);
|
|
2598
|
+
const current = read;
|
|
2502
2599
|
const change = {
|
|
2503
2600
|
image: options.image,
|
|
2504
2601
|
shape: options.shape,
|
|
@@ -2553,24 +2650,42 @@ async function machineSleepCommand(deps, name, options = {}) {
|
|
|
2553
2650
|
}
|
|
2554
2651
|
async function machineDeleteCommand(deps, name, options = {}) {
|
|
2555
2652
|
await machineCommand(async () => {
|
|
2653
|
+
const key = await deps.auth.apiKey();
|
|
2556
2654
|
await confirm(
|
|
2557
2655
|
deps,
|
|
2558
2656
|
options.yes,
|
|
2559
2657
|
`delete Machine ${name}`,
|
|
2560
|
-
() =>
|
|
2561
|
-
`Delete Machine ${name} with its disk, database and logs? This cannot be undone. Type the Machine's name to confirm:`
|
|
2562
|
-
),
|
|
2658
|
+
() => deleteQuestion(deps, key, name),
|
|
2563
2659
|
name
|
|
2564
2660
|
);
|
|
2565
|
-
const machine = await deps.api.deleteMachine(
|
|
2566
|
-
print(
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
);
|
|
2661
|
+
const { machine, kind } = await deps.api.deleteMachine(key, name);
|
|
2662
|
+
print(deps, options.json, machine, deletedText(name, machine, kind));
|
|
2663
|
+
if (machine?.kind === "byoc" && machine.byoc.key_state === "active")
|
|
2664
|
+
deps.notice(
|
|
2665
|
+
`Its key is still active, so ${name} registers again at its next start. Run \`yield logout\` on it, or revoke its key with \`tonbo api-key revoke\`, then forget it again.`
|
|
2666
|
+
);
|
|
2572
2667
|
});
|
|
2573
2668
|
}
|
|
2669
|
+
async function deleteQuestion(deps, key, name) {
|
|
2670
|
+
let kind = null;
|
|
2671
|
+
try {
|
|
2672
|
+
kind = (await deps.api.getMachine(key, name)).kind;
|
|
2673
|
+
} catch (error) {
|
|
2674
|
+
if (!(error instanceof HttpError && error.status === 404)) throw error;
|
|
2675
|
+
}
|
|
2676
|
+
if (kind === "byoc")
|
|
2677
|
+
return `Forget your computer ${name}? It disappears from your Machines; its key is not revoked. Type its name to confirm:`;
|
|
2678
|
+
if (kind === "managed")
|
|
2679
|
+
return `Delete Machine ${name} with its disk, database and logs? This cannot be undone. Type the Machine's name to confirm:`;
|
|
2680
|
+
return `Delete Machine ${name}? Type its name to confirm:`;
|
|
2681
|
+
}
|
|
2682
|
+
function deletedText(name, machine, kind) {
|
|
2683
|
+
if (kind === "byoc")
|
|
2684
|
+
return `Forgot your computer ${machine?.name ?? name}; its name is free once the forget completes.`;
|
|
2685
|
+
if (machine)
|
|
2686
|
+
return `Machine ${machine.name} is being deleted; its name is free for a new Machine.`;
|
|
2687
|
+
return `Released the name ${name}; no Machine had been created under it.`;
|
|
2688
|
+
}
|
|
2574
2689
|
async function machineShapesCommand(deps, options = {}) {
|
|
2575
2690
|
await machineCommand(async () => {
|
|
2576
2691
|
const shapes = await deps.api.listMachineShapes(await deps.auth.apiKey());
|
|
@@ -2710,6 +2825,12 @@ function createProgram(dependencies = createDependencies) {
|
|
|
2710
2825
|
program.command("region").description("regions new resources can be created in").command("list").description("list the regions"),
|
|
2711
2826
|
"listRegions"
|
|
2712
2827
|
).action(async (options) => regionListCommand(dependencies(), options));
|
|
2828
|
+
operationOptions(
|
|
2829
|
+
program.command("workspace").description("the account's git workspaces").command("list").description("list the git workspaces, the default first"),
|
|
2830
|
+
"listGitWorkspaces"
|
|
2831
|
+
).action(
|
|
2832
|
+
async (options) => workspaceListCommand(dependencies(), options)
|
|
2833
|
+
);
|
|
2713
2834
|
const member = program.command("member").description("the people and Agents of the account");
|
|
2714
2835
|
operationOptions(
|
|
2715
2836
|
member.command("list").description("list the members and their roles"),
|
|
@@ -2934,7 +3055,7 @@ function createProgram(dependencies = createDependencies) {
|
|
|
2934
3055
|
);
|
|
2935
3056
|
const machine = program.command("machine").description("the account's Machines");
|
|
2936
3057
|
operationOptions(
|
|
2937
|
-
machine.command("list").description("list the Machines in every region"),
|
|
3058
|
+
machine.command("list").description("list the Machines in every region, and your computers in a personal account"),
|
|
2938
3059
|
"listMachines"
|
|
2939
3060
|
).action(
|
|
2940
3061
|
async (options) => machineListCommand(dependencies(), options)
|
|
@@ -2960,7 +3081,7 @@ function createProgram(dependencies = createDependencies) {
|
|
|
2960
3081
|
async (name, command, options) => machineCreateCommand(dependencies(), name, command, options)
|
|
2961
3082
|
);
|
|
2962
3083
|
operationOptions(
|
|
2963
|
-
machine.command("show <name>").description("show a Machine, its hostnames and its state"),
|
|
3084
|
+
machine.command("show <name>").description("show a Machine, its hostnames and its state, or one of your computers"),
|
|
2964
3085
|
"getMachine"
|
|
2965
3086
|
).action(
|
|
2966
3087
|
async (name, options) => machineShowCommand(dependencies(), name, options)
|
|
@@ -2987,7 +3108,9 @@ function createProgram(dependencies = createDependencies) {
|
|
|
2987
3108
|
async (name, options) => machineSleepCommand(dependencies(), name, options)
|
|
2988
3109
|
);
|
|
2989
3110
|
operationOptions(
|
|
2990
|
-
machine.command("delete <name>").description(
|
|
3111
|
+
machine.command("delete <name>").description(
|
|
3112
|
+
"delete a Machine with its disk, database and logs, or forget one of your computers"
|
|
3113
|
+
).option("--yes", "delete without asking; required when stdin is not a terminal"),
|
|
2991
3114
|
"deleteMachine"
|
|
2992
3115
|
).action(
|
|
2993
3116
|
async (name, options) => machineDeleteCommand(dependencies(), name, options)
|