@tonbo/cli 0.17.3 → 0.19.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 +1 -1
- package/dist/bin/tonbo.js +131 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ The login file, `login.json`, and the device id it is tied to are shared with [`
|
|
|
26
26
|
|
|
27
27
|
## Commands and the Management API
|
|
28
28
|
|
|
29
|
-
Every command other than `login` and `logout` is named by one operation of the [Management API](../../contracts/management/openapi-v1.yaml) through its `x-tonbo-cli` and calls that operation, plus reads of the same resource (a `--wait`, the Machine a `name_taken` refusal names, the activations `machine show` reads for the last end, or the Volume a `machine create` or `update --volume` names, read for its id and region); `tonbo machine logs`, once it ships, is the named Streams exception, reading the stream at the Machine's `logs.url`.
|
|
29
|
+
Every command other than `login` and `logout` is named by one operation of the [Management API](../../contracts/management/openapi-v1.yaml) through its `x-tonbo-cli` and calls that operation, plus reads of the same resource (a `--wait`, the Machine a `name_taken` refusal names, the activations `machine show` reads for the last end, or the Volume a `machine create` or `update --volume` names, read for its id and region); `tonbo machine logs`, once it ships, is the named Streams exception, reading the stream at the Machine's `logs.url`. `tonbo volume move` is the one command that sends another command's operation: it reads the Volume and both Machines and sends the target's `updateMachine` with `move_from` (the `machine create --move-from` of an existing Machine, docs/design/machines-volume-move.md). Apart from it, the command set and the operations tagged with a command are the same set, which `test/commands.test.ts` checks in both directions. The CLI resolves each request from the operation table of the internal package [`@tonbo/cli-core`](../../packages/tonbo-cli-core) (`src/generated/contracts.ts`, which `pnpm --filter @tonbo/cli-core generate:contracts` derives from the contract). `login`, `logout`, `account list` and `account use` are the named exception: they obtain, choose among and discard what the other commands present as their Bearer credential. What a command may do is what your role in the account it acts in allows; the CLI never decides it.
|
|
30
30
|
|
|
31
31
|
The help lives with the commands: [`src/app.ts`](src/app.ts) defines each command, its arguments and flags with their one-line descriptions, and [`src/help.ts`](src/help.ts) holds the long help, each flag's default, the examples, the shared rules and the environment. [`commands.json`](commands.json) is all of it as `--help` prints it, which the docs' CLI reference renders; `test/commands.test.ts` holds it to the program, and holds every runnable command to its details and example and the environment list to the variables the code reads, so after changing a command run `pnpm --filter @tonbo/cli commands`.
|
|
32
32
|
|
package/dist/bin/tonbo.js
CHANGED
|
@@ -402,6 +402,7 @@ var managementOperations = {
|
|
|
402
402
|
"query": [
|
|
403
403
|
"origin",
|
|
404
404
|
"scope",
|
|
405
|
+
"owners",
|
|
405
406
|
"cursor",
|
|
406
407
|
"limit"
|
|
407
408
|
],
|
|
@@ -2362,7 +2363,8 @@ var ManagementApi = class {
|
|
|
2362
2363
|
return {
|
|
2363
2364
|
machine: answer.data,
|
|
2364
2365
|
location: headers.location,
|
|
2365
|
-
retryAfterSeconds: retryAfterSeconds(headers.retryAfter)
|
|
2366
|
+
retryAfterSeconds: retryAfterSeconds(headers.retryAfter),
|
|
2367
|
+
volumeMove: answer.volume_move ?? null
|
|
2366
2368
|
};
|
|
2367
2369
|
} catch (error) {
|
|
2368
2370
|
if (error instanceof HttpError && error.status < 500)
|
|
@@ -2425,6 +2427,13 @@ var ManagementApi = class {
|
|
|
2425
2427
|
ifMatch: etag
|
|
2426
2428
|
});
|
|
2427
2429
|
}
|
|
2430
|
+
/** An update whose binding names `move_from`: the Machine, and what the
|
|
2431
|
+
* move did, which the answer carries beside it
|
|
2432
|
+
* (docs/design/machines-volume-move.md). */
|
|
2433
|
+
async moveVolume(key, name, etag, change) {
|
|
2434
|
+
const answer = await this.request("updateMachine", key, { params: { name }, body: withoutUndefined(change), ifMatch: etag });
|
|
2435
|
+
return { machine: answer.data, volumeMove: answer.volume_move ?? null };
|
|
2436
|
+
}
|
|
2428
2437
|
/** With `etag`, only the Machine that ETag names, at that revision, is put
|
|
2429
2438
|
* to sleep (machine-v2.json `etag.if_match.sleep` is optional). A 202
|
|
2430
2439
|
* carries the `Retry-After` to read the Machine again after
|
|
@@ -5519,12 +5528,73 @@ async function volumeDeleteCommand(deps, name, options = {}) {
|
|
|
5519
5528
|
);
|
|
5520
5529
|
});
|
|
5521
5530
|
}
|
|
5522
|
-
|
|
5531
|
+
function movedText(move, volume, target) {
|
|
5532
|
+
return [
|
|
5533
|
+
`Moved Volume ${volume} from ${move.from} to ${target}; it attaches at ${target}'s next boot, and ${move.from} boots without it.`,
|
|
5534
|
+
...move.replaced ? [
|
|
5535
|
+
`${target}'s Volume ${move.replaced.name} was unbound and kept; bind it again with \`tonbo machine update <machine> --volume ${move.replaced.name}:<path>\`.`
|
|
5536
|
+
] : []
|
|
5537
|
+
];
|
|
5538
|
+
}
|
|
5539
|
+
async function volumeMoveCommand(deps, name, options = {}) {
|
|
5540
|
+
await volumeCommand(deps, async () => {
|
|
5541
|
+
if (!options.to) throw new Error("Pass --to <machine>.");
|
|
5542
|
+
const to = options.to;
|
|
5543
|
+
const key = await deps.auth.apiKey();
|
|
5544
|
+
const volume = await deps.api.getVolume(key, name);
|
|
5545
|
+
const from = volume.machine?.name ?? null;
|
|
5546
|
+
if (from === to && options.path === void 0) {
|
|
5547
|
+
print(
|
|
5548
|
+
deps,
|
|
5549
|
+
options.json,
|
|
5550
|
+
volume,
|
|
5551
|
+
`Volume ${volume.name} is already on ${to}; nothing to move.`
|
|
5552
|
+
);
|
|
5553
|
+
return;
|
|
5554
|
+
}
|
|
5555
|
+
const { machine: target, etag } = await deps.api.getMachineWithEtag(key, to);
|
|
5556
|
+
if (target.kind === "byoc")
|
|
5557
|
+
throw new Error(
|
|
5558
|
+
`${to} is one of your computers; a Volume moves only to a Machine Tonbo runs.`
|
|
5559
|
+
);
|
|
5560
|
+
if (etag === null) throw new Error(`The API answered ${to} without an ETag.`);
|
|
5561
|
+
const binding = { volume: volume.id };
|
|
5562
|
+
let path6 = options.path;
|
|
5563
|
+
if (from !== null && from !== to) {
|
|
5564
|
+
const { machine: source, etag: sourceTag } = await deps.api.getMachineWithEtag(key, from);
|
|
5565
|
+
const held = source.kind === "managed" ? source.volumes?.find((entry) => entry.volume === volume.id) : void 0;
|
|
5566
|
+
path6 ??= held?.path;
|
|
5567
|
+
binding.move_from = from;
|
|
5568
|
+
if (sourceTag !== null) binding.move_from_etag = sourceTag;
|
|
5569
|
+
}
|
|
5570
|
+
if (path6 !== void 0) binding.path = path6;
|
|
5571
|
+
const { machine, volumeMove: move } = await deps.api.moveVolume(key, to, etag, {
|
|
5572
|
+
volumes: [binding]
|
|
5573
|
+
});
|
|
5574
|
+
print(
|
|
5575
|
+
deps,
|
|
5576
|
+
options.json,
|
|
5577
|
+
machine,
|
|
5578
|
+
move ? movedText(move, volume.name, machine.name).join("\n") : `Bound Volume ${volume.name} to ${machine.name}${path6 === void 0 ? " as its whole disk" : ` at ${path6}`}; it attaches at the next boot.`
|
|
5579
|
+
);
|
|
5580
|
+
});
|
|
5581
|
+
}
|
|
5582
|
+
function parseVolumeSpec(spec) {
|
|
5523
5583
|
const split = spec.indexOf(":");
|
|
5584
|
+
if (split === -1 && spec.length > 0) return { name: spec };
|
|
5524
5585
|
if (split < 1 || split === spec.length - 1)
|
|
5525
|
-
throw new Error(
|
|
5526
|
-
|
|
5527
|
-
|
|
5586
|
+
throw new Error(
|
|
5587
|
+
`--volume ${spec}: give <name> for the Machine's whole disk, or <name>:<path> to mount it at a path, such as data:/data.`
|
|
5588
|
+
);
|
|
5589
|
+
return { name: spec.slice(0, split), path: spec.slice(split + 1) };
|
|
5590
|
+
}
|
|
5591
|
+
async function resolveVolumeBinding(deps, key, spec) {
|
|
5592
|
+
const { name, path: path6 } = parseVolumeSpec(spec);
|
|
5593
|
+
const volume = await deps.api.getVolume(key, name);
|
|
5594
|
+
return {
|
|
5595
|
+
binding: path6 === void 0 ? { volume: volume.id } : { volume: volume.id, path: path6 },
|
|
5596
|
+
region: volume.region
|
|
5597
|
+
};
|
|
5528
5598
|
}
|
|
5529
5599
|
|
|
5530
5600
|
// src/machine.ts
|
|
@@ -5550,6 +5620,13 @@ var MACHINE_ERROR_HINTS = {
|
|
|
5550
5620
|
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`.",
|
|
5551
5621
|
machine_kind_mismatch: "That Machine is one of your computers: `tonbo machine show` and `tonbo machine delete` serve it, and nothing else does.",
|
|
5552
5622
|
volumes_not_offered: "Volumes are not offered to this account in the Machine's region yet; nothing was created or changed.",
|
|
5623
|
+
root_mode_not_offered: "A Volume as the Machine's whole disk is not offered to this account yet; nothing was created or changed. Mount it at a path instead: --volume <name>:<path>.",
|
|
5624
|
+
rebuild_required: "This Machine's Volume is its whole disk, so its image changes only by Rebuild, which keeps /home, /workspace and /root and deletes every other change on the disk. Rebuild is not available yet: keep the image, or create a new Machine with the new image and a new Volume.",
|
|
5625
|
+
volume_mode_change: "The Volume cannot move between being the Machine's whole disk and being mounted at a path; nothing was changed. Bind a new Volume in the mode you want.",
|
|
5626
|
+
volume_attached: "The Machine the message names is awake with the Volume attached; nothing was changed. Put it to sleep with `tonbo machine sleep <machine>`, then run the command again.",
|
|
5627
|
+
volume_holder_changed: "The Volume is not on the Machine you named; nothing was changed. `tonbo volume show <volume>` says which Machine has it: name that one, or use `tonbo volume move`.",
|
|
5628
|
+
volume_image_mismatch: "The Volume is a Machine's whole disk, made against one image; it moves only to a Machine on that same image. Nothing was changed.",
|
|
5629
|
+
volume_rebuild_pending: "The Volume's Rebuild has not finished; move it once it has. Nothing was changed.",
|
|
5553
5630
|
secret_env_conflict: "Use another secret key or remove the non-secret env variable with that name.",
|
|
5554
5631
|
secret_limit: "Reduce the number or byte size of this Machine's secrets.",
|
|
5555
5632
|
wake_unavailable: "The wake was refused for now; nothing woke. Wake it again after a while; `tonbo machine show` says why it is blocked."
|
|
@@ -5812,7 +5889,8 @@ function deleteFields(machine) {
|
|
|
5812
5889
|
}
|
|
5813
5890
|
function volumeText(machine) {
|
|
5814
5891
|
const binding = machine.volumes?.[0];
|
|
5815
|
-
|
|
5892
|
+
if (!binding) return "none";
|
|
5893
|
+
return binding.path === void 0 ? `${binding.volume} (whole disk)` : `${binding.volume} at ${binding.path}`;
|
|
5816
5894
|
}
|
|
5817
5895
|
function machineText(machine, billing, etag = null, lastEnd = null) {
|
|
5818
5896
|
return [machineFields(machine, billing, etag, lastEnd), ...privatePortLines(machine)].join("\n");
|
|
@@ -6117,12 +6195,22 @@ async function waitForCreate(deps, key, machine, name, firstWait, timeout, json)
|
|
|
6117
6195
|
async function machineCreateCommand(deps, name, command, options = {}) {
|
|
6118
6196
|
await machineCommand(async () => {
|
|
6119
6197
|
if (!options.image) throw new Error("Pass --image <reference>.");
|
|
6198
|
+
if (options.moveFrom !== void 0 && typeof options.volume !== "string")
|
|
6199
|
+
throw new Error("--move-from names the Machine that holds the --volume: pass --volume too.");
|
|
6120
6200
|
const timeout = waitTimeout(options);
|
|
6121
6201
|
const env = parseEnv(options.env ?? []);
|
|
6122
6202
|
const secrets = createSecrets(options);
|
|
6123
6203
|
const ports = withDefault(parsePorts(options.port ?? []), options.defaultPort);
|
|
6124
6204
|
const key = await deps.auth.apiKey();
|
|
6125
6205
|
const bound = typeof options.volume === "string" ? await resolveVolumeBinding(deps, key, options.volume) : null;
|
|
6206
|
+
if (bound && options.moveFrom !== void 0) {
|
|
6207
|
+
const { etag } = await deps.api.getMachineWithEtag(key, options.moveFrom);
|
|
6208
|
+
bound.binding = {
|
|
6209
|
+
...bound.binding,
|
|
6210
|
+
move_from: options.moveFrom,
|
|
6211
|
+
...etag === null ? {} : { move_from_etag: etag }
|
|
6212
|
+
};
|
|
6213
|
+
}
|
|
6126
6214
|
const database = typeof options.database === "string" ? await deps.api.getDatabase(key, options.database) : null;
|
|
6127
6215
|
if (database && bound && database.region !== bound.region && !options.region)
|
|
6128
6216
|
throw new Error(
|
|
@@ -6182,7 +6270,11 @@ async function machineCreateCommand(deps, name, command, options = {}) {
|
|
|
6182
6270
|
deps,
|
|
6183
6271
|
options.json,
|
|
6184
6272
|
machine,
|
|
6185
|
-
[
|
|
6273
|
+
[
|
|
6274
|
+
createdText(machine),
|
|
6275
|
+
...created.volumeMove ? movedText(created.volumeMove, options.volume, machine.name) : [],
|
|
6276
|
+
machineText(machine, await billingUrl(deps))
|
|
6277
|
+
].join("\n")
|
|
6186
6278
|
);
|
|
6187
6279
|
});
|
|
6188
6280
|
}
|
|
@@ -6886,11 +6978,15 @@ var COMMAND_HELP = {
|
|
|
6886
6978
|
},
|
|
6887
6979
|
"tonbo machine create": {
|
|
6888
6980
|
about: [
|
|
6889
|
-
"Creates a Machine, a VM booted from an image in one region. It has no database of its own: `--database <name>` binds one of the account's databases of its region, which it then reaches at `TONBO_DATABASE_URL`, and a database binds one Machine at a time; its
|
|
6981
|
+
"Creates a Machine, a VM booted from an image in one region. It has no database of its own: `--database <name>` binds one of the account's databases of its region, which it then reaches at `TONBO_DATABASE_URL`, and a database binds one Machine at a time; its disk can last on a Volume (`--volume <name>` keeps the Machine's whole disk on it, and `--volume <name>:<path>` mounts it at a path instead), and its program can open any other database by URL with a key. The image is a public OCI image with a linux/arm64 variant, from Docker Hub or another public registry (`nginx:1.27`, `ghcr.io/org/app:v1`, or one pinned by digest), or `tonbo.dev/platform`, the platform image. A registry reachable only over IPv6, one named by an IP address, and one that redirects its manifests are refused. The CLI resolves a registry image to its linux/arm64 digest, and the image is pulled before the Machine leaves `creating`.",
|
|
6890
6982
|
"The Machine is served at `https://<name>-<account>.tonbo.sh` on its default port. `--port <port>` serves a private port, which takes a member's token, and `--port <port>:public` an open one, which needs the `machines.public` capability. A command after `--` replaces the image's CMD. `--secret-env KEY=FILE` and `--secret-file KEY=FILE` read initial secrets from local files and store them with the Machine before its first boot; their values are never returned. The CLI warns on stderr when the Machine has no port, since nothing can wake it then, and when an `--env` key looks secret: env values are readable by every member through the API.",
|
|
6983
|
+
"`--move-from <machine>` takes the `--volume` from the Machine that has it now, in the same write as the create: that Machine's binding is removed (its ETag changes) and it boots without the Volume. It is refused while that Machine is awake with the Volume attached (`volume_attached`: `tonbo machine sleep <machine>` first), or when another Machine has the Volume (`volume_holder_changed`); either way nothing is created or changed. Without it, a Volume another Machine has is refused.",
|
|
6891
6984
|
"The answer is the Machine while it is `creating`. With `--wait` the CLI reads it until it is `active` or `failed`, or `--timeout` passes: a failure prints what to do and exits 1, and a timeout says it is still creating and undoes nothing. A creation the network or a 5xx answer did not confirm is sent at most three times with the same idempotency key, each retry after its `Retry-After` (at most 30 s) or a short backoff; a `name_taken` refusal says which Machine holds the name and how old it is, in case it is yours from a lost reply. In a region not open for Machines the creation is refused with `region_not_offered`, and nothing is created."
|
|
6892
6985
|
],
|
|
6893
|
-
examples: [
|
|
6986
|
+
examples: [
|
|
6987
|
+
"tonbo machine create web --image nginx:1.27 --port 80:public --wait",
|
|
6988
|
+
"tonbo machine create agent --image nginx:1.27 --volume data:/workspace --move-from worker"
|
|
6989
|
+
],
|
|
6894
6990
|
arguments: {
|
|
6895
6991
|
name: "the Machine's name, part of its hostname",
|
|
6896
6992
|
command: "the command that replaces the image's CMD, after `--`"
|
|
@@ -6937,7 +7033,7 @@ var COMMAND_HELP = {
|
|
|
6937
7033
|
"tonbo machine update": {
|
|
6938
7034
|
about: [
|
|
6939
7035
|
"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.",
|
|
6940
|
-
"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.",
|
|
7036
|
+
"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. A Machine whose Volume is its whole disk keeps its image: another image is refused with `rebuild_required`, since only Rebuild, which keeps /home, /workspace and /root and deletes every other change on the disk, may move it, and Rebuild is not available yet. `--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.",
|
|
6941
7037
|
"`--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."
|
|
6942
7038
|
],
|
|
6943
7039
|
examples: ["tonbo machine update web --env MODE=staging --sleep-after 300"],
|
|
@@ -7155,6 +7251,15 @@ var COMMAND_HELP = {
|
|
|
7155
7251
|
examples: ["tonbo volume show data"],
|
|
7156
7252
|
arguments: NAME("Volume")
|
|
7157
7253
|
},
|
|
7254
|
+
"tonbo volume move": {
|
|
7255
|
+
about: [
|
|
7256
|
+
"Moves the Volume to the Machine `--to`, taking it from the Machine that has it now in one write: the Volume is unbound there (that Machine's ETag changes and it boots without it) and bound to `--to`, which may be awake and gets it at its next boot. The CLI reads both Machines' ETags first, so a Machine changed since is refused, never overwritten. Without `--path` the Volume keeps the path it has now, or stays a whole disk; a whole disk moves only to a Machine on the same image.",
|
|
7257
|
+
'The Machine that has it must be asleep: an awake one is refused with `volume_attached`, and nothing changes; `tonbo machine sleep <machine>` first. When `--to` already has another Volume, that one is unbound and kept, and the CLI names it. The REST equivalent is `PATCH /v1/machines/<to>` with `If-Match: <its ETag>` and `{"volumes": [{"volume": "<id>", "path": "<path>", "move_from": "<machine>", "move_from_etag": "<its ETag>"}]}`.'
|
|
7258
|
+
],
|
|
7259
|
+
examples: ["tonbo volume move data --to agent --path /workspace"],
|
|
7260
|
+
arguments: NAME("Volume"),
|
|
7261
|
+
defaults: { "--path": "the path the Volume has now, or none (a whole disk)" }
|
|
7262
|
+
},
|
|
7158
7263
|
"tonbo volume delete": {
|
|
7159
7264
|
about: [
|
|
7160
7265
|
"Deletes the Volume and every byte on it; there are no Volume snapshots or backups. It is refused while a Machine has it. At a terminal it asks you to type the Volume's name first."
|
|
@@ -8556,8 +8661,8 @@ function machineSpecOptions(command, update) {
|
|
|
8556
8661
|
"the seconds between SIGTERM and SIGKILL on a planned stop",
|
|
8557
8662
|
parseWhole
|
|
8558
8663
|
).option(
|
|
8559
|
-
"--volume <name:path>",
|
|
8560
|
-
`
|
|
8664
|
+
"--volume <name[:path]>",
|
|
8665
|
+
`keep the Machine's whole disk on the Volume <name> (see tonbo volume list), or with :<path> mount it at <path> instead; a Machine has at most one, of its own region${update ? "; applies at the next boot" : ""}`
|
|
8561
8666
|
).option(
|
|
8562
8667
|
"--delete-after <seconds>",
|
|
8563
8668
|
`delete the Machine once it has been asleep, never woken or failed this long (3600 to 7776000)${update ? "; applies at once, from its last activation's end" : ""}`,
|
|
@@ -8902,6 +9007,9 @@ function createProgram(makeDependencies = createDependencies) {
|
|
|
8902
9007
|
"read a UTF-8 environment secret from FILE; repeat",
|
|
8903
9008
|
repeated
|
|
8904
9009
|
).option("--secret-file <KEY=FILE>", "read binary file secret from FILE; repeat", repeated).option(
|
|
9010
|
+
"--move-from <machine>",
|
|
9011
|
+
"take the --volume from the asleep Machine <machine> that has it, in the same write; that Machine boots without it"
|
|
9012
|
+
).option(
|
|
8905
9013
|
"--idempotency-key <key>",
|
|
8906
9014
|
"reuse the key of an unconfirmed creation to get the Machine it created"
|
|
8907
9015
|
).option("--wait", "wait until the Machine is active, or say why it failed").option("--timeout <seconds>", "how long --wait waits, in seconds", parseWhole),
|
|
@@ -9103,6 +9211,17 @@ function createProgram(makeDependencies = createDependencies) {
|
|
|
9103
9211
|
).action(
|
|
9104
9212
|
async (name, options) => volumeShowCommand(dependencies(), name, options)
|
|
9105
9213
|
);
|
|
9214
|
+
operationOptions(
|
|
9215
|
+
volume.command("move <name>").description(
|
|
9216
|
+
"move a Volume to another Machine of its region, taking it from the asleep Machine that has it in the same write"
|
|
9217
|
+
).requiredOption("--to <machine>", "the Machine that gets the Volume; it may be awake").option(
|
|
9218
|
+
"--path <path>",
|
|
9219
|
+
"mount it at <path> there; without it, it keeps the path it has now, or stays a whole disk"
|
|
9220
|
+
),
|
|
9221
|
+
"updateMachine"
|
|
9222
|
+
).action(
|
|
9223
|
+
async (name, options) => volumeMoveCommand(dependencies(), name, options)
|
|
9224
|
+
);
|
|
9106
9225
|
operationOptions(
|
|
9107
9226
|
volume.command("delete <name>").description("delete a Volume and its data; refused while a Machine has it").option("--yes", "delete without asking; required when stdin is not a terminal"),
|
|
9108
9227
|
"deleteVolume"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tonbo/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "The Tonbo CLI. Compute, disks, databases and streams for AI agents: your agent picks what it needs, sets it up, and knows what it costs, from the command line.",
|
|
5
5
|
"homepage": "https://tonbo.dev",
|
|
6
6
|
"bugs": {
|