@tonbo/cli 0.17.2 → 0.17.3
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 +4 -0
- package/dist/bin/tonbo.js +424 -54
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,6 +30,10 @@ Every command other than `login` and `logout` is named by one operation of the [
|
|
|
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
|
|
|
33
|
+
## Updates
|
|
34
|
+
|
|
35
|
+
Every request to the Management API and to tonbo.dev carries the CLI's release in `Tonbo-CLI-Version`, and every answer names the latest release in `Tonbo-CLI-Latest` and the oldest supported one in `Tonbo-CLI-Minimum`, by the contract [`versions-v1.json`](../../contracts/cli/versions-v1.json): Latest is this package's `version` on main, so merging a release bump moves it with no other change, and Minimum is declared in the contract. When Latest is newer, the command ends with one line on stderr, at most once a day (`update-notice.json` beside the login), never with `--json`, without a terminal, under `CI` or with `TONBO_NO_UPDATE_NOTICE=1`. A release below Minimum is refused with 410 `cli_upgrade_required` on any request; the CLI prints the detail and exits 1. `tonbo update` runs the update of the package manager that installed it (npm, pnpm, bun or yarn), printing the command first, and only prints the command for npx or a path it cannot tell; `--check` only compares. [`src/update.ts`](src/update.ts) holds all of it.
|
|
36
|
+
|
|
33
37
|
## Related design
|
|
34
38
|
|
|
35
39
|
- [ADR-0008: Tonbo CLI onboarding and credential boundaries](../../apps/user-center/docs/decisions/adr-0008-tonbo-cli-onboarding.md)
|
package/dist/bin/tonbo.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
// src/app.ts
|
|
4
4
|
import { Command, InvalidArgumentError, Option } from "commander";
|
|
5
|
-
import { readFileSync as readFileSync2 } from "node:fs";
|
|
6
5
|
|
|
7
6
|
// ../../packages/tonbo-cli-core/dist/src/generated/contracts.js
|
|
8
7
|
var agentNameContract = {
|
|
@@ -1773,6 +1772,102 @@ var loginContract = {
|
|
|
1773
1772
|
]
|
|
1774
1773
|
}
|
|
1775
1774
|
};
|
|
1775
|
+
var cliVersionsContract = {
|
|
1776
|
+
"version": 1,
|
|
1777
|
+
"description": "Which `tonbo` CLI (@tonbo/cli) releases Tonbo supports, and how a CLI learns of a newer one without a request of its own. User Center answers every request to the Management API host, and every request that carries `headers.client`, with `headers.latest` and `headers.minimum`. A request whose `headers.client` is a release below `minimum` is refused with `refusal.code` (contracts/management/problems-v1.json holds its status and title), its detail naming the update; a request without the header, or with one that is not a release, is never refused for its version. @tonbo/cli sends `headers.client` on every request, prints `notice.line` when `headers.latest` is newer than itself, and on the refusal prints its detail and exits 1. @tonbo/yield does not send the header.",
|
|
1778
|
+
"package": "@tonbo/cli",
|
|
1779
|
+
"latest": {
|
|
1780
|
+
"source": "cli/tonbo/package.json",
|
|
1781
|
+
"field": "version",
|
|
1782
|
+
"description": "The release main carries when User Center is deployed. Merging a version change to main is what publishes it (tonbo-cli-release.yml); the same push redeploys User Center, whose deploy waits until npm serves that version before it ships, so `latest` is always installable and follows each release with no bump of its own."
|
|
1783
|
+
},
|
|
1784
|
+
"minimum": "0.12.0",
|
|
1785
|
+
"minimum_reason": "0.12.0 is the oldest release that can sign in: the login of 0.11 and earlier was retired when 0.12 shipped (contracts/iam/access-token-v1.json `cli_session.login.retired`). Raise it only when a change of the API leaves older releases unable to work, and in the same change say so in the CLI's changelog.",
|
|
1786
|
+
"headers": {
|
|
1787
|
+
"client": "Tonbo-CLI-Version",
|
|
1788
|
+
"latest": "Tonbo-CLI-Latest",
|
|
1789
|
+
"minimum": "Tonbo-CLI-Minimum"
|
|
1790
|
+
},
|
|
1791
|
+
"release_pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$",
|
|
1792
|
+
"release_order": "Semantic Versioning 2.0.0 precedence: major, minor and patch compare as numbers, and a release with a pre-release suffix is older than the same release without one; pre-release suffixes compare as text. A value that does not match `release_pattern` is not a release and orders with nothing. `order_examples` are [a, b, the sign of a compared with b, or null when either is not a release]; every implementation runs them.",
|
|
1793
|
+
"order_examples": [
|
|
1794
|
+
[
|
|
1795
|
+
"0.17.2",
|
|
1796
|
+
"0.17.3",
|
|
1797
|
+
-1
|
|
1798
|
+
],
|
|
1799
|
+
[
|
|
1800
|
+
"0.17.3",
|
|
1801
|
+
"0.17.3",
|
|
1802
|
+
0
|
|
1803
|
+
],
|
|
1804
|
+
[
|
|
1805
|
+
"0.18.0",
|
|
1806
|
+
"0.17.9",
|
|
1807
|
+
1
|
|
1808
|
+
],
|
|
1809
|
+
[
|
|
1810
|
+
"0.9.0",
|
|
1811
|
+
"0.12.0",
|
|
1812
|
+
-1
|
|
1813
|
+
],
|
|
1814
|
+
[
|
|
1815
|
+
"1.0.0",
|
|
1816
|
+
"0.99.99",
|
|
1817
|
+
1
|
|
1818
|
+
],
|
|
1819
|
+
[
|
|
1820
|
+
"0.18.0-beta.1",
|
|
1821
|
+
"0.18.0",
|
|
1822
|
+
-1
|
|
1823
|
+
],
|
|
1824
|
+
[
|
|
1825
|
+
"0.18.0-beta.2",
|
|
1826
|
+
"0.18.0-beta.1",
|
|
1827
|
+
1
|
|
1828
|
+
],
|
|
1829
|
+
[
|
|
1830
|
+
"0.17",
|
|
1831
|
+
"0.17.0",
|
|
1832
|
+
null
|
|
1833
|
+
],
|
|
1834
|
+
[
|
|
1835
|
+
"v0.17.2",
|
|
1836
|
+
"0.17.2",
|
|
1837
|
+
null
|
|
1838
|
+
],
|
|
1839
|
+
[
|
|
1840
|
+
"0.17.2 ",
|
|
1841
|
+
"0.17.2",
|
|
1842
|
+
null
|
|
1843
|
+
],
|
|
1844
|
+
[
|
|
1845
|
+
"",
|
|
1846
|
+
"0.12.0",
|
|
1847
|
+
null
|
|
1848
|
+
]
|
|
1849
|
+
],
|
|
1850
|
+
"refusal": {
|
|
1851
|
+
"code": "cli_upgrade_required",
|
|
1852
|
+
"registry": "contracts/management/problems-v1.json"
|
|
1853
|
+
},
|
|
1854
|
+
"update_command": "npm install --global @tonbo/cli@latest",
|
|
1855
|
+
"notice": {
|
|
1856
|
+
"line": "tonbo {latest} is available (you have {current}): run tonbo update",
|
|
1857
|
+
"stream": "stderr",
|
|
1858
|
+
"interval_hours": 24,
|
|
1859
|
+
"state": {
|
|
1860
|
+
"description": "When the CLI last printed the notice: `${XDG_CONFIG_HOME}/tonbo/update-notice.json`, else `${HOME}/.config/tonbo/update-notice.json`, beside the login. A state that cannot be read counts as never; one that cannot be written never fails the command.",
|
|
1861
|
+
"file": "update-notice.json"
|
|
1862
|
+
},
|
|
1863
|
+
"silent_when": [
|
|
1864
|
+
"the command line has --json",
|
|
1865
|
+
"neither stdout nor stderr is a terminal",
|
|
1866
|
+
"CI is set to anything but empty, 0 or false",
|
|
1867
|
+
"TONBO_NO_UPDATE_NOTICE is set to anything but empty or 0"
|
|
1868
|
+
]
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1776
1871
|
var cliSessionContract = {
|
|
1777
1872
|
"prefix": "tbs_",
|
|
1778
1873
|
"grant": "cli_session",
|
|
@@ -2036,7 +2131,7 @@ var ManagementApi = class {
|
|
|
2036
2131
|
}
|
|
2037
2132
|
async requestWith(operationId, key, { params = {}, query = {}, body, idempotencyKey, ifMatch }, seen) {
|
|
2038
2133
|
const operation = managementOperation(operationId);
|
|
2039
|
-
const
|
|
2134
|
+
const path6 = operation.path.replace(/\{([^}]+)\}/g, (_, name) => {
|
|
2040
2135
|
const value = params[name];
|
|
2041
2136
|
if (value === void 0)
|
|
2042
2137
|
throw new Error(`${operationId} needs the path parameter ${name}.`);
|
|
@@ -2060,7 +2155,7 @@ var ManagementApi = class {
|
|
|
2060
2155
|
if (ifMatch)
|
|
2061
2156
|
headers["if-match"] = ifMatch;
|
|
2062
2157
|
const queryString = search.toString();
|
|
2063
|
-
const url = `${this.apiOrigin}${managementApiBasePath}${
|
|
2158
|
+
const url = `${this.apiOrigin}${managementApiBasePath}${path6}${queryString ? `?${queryString}` : ""}`;
|
|
2064
2159
|
try {
|
|
2065
2160
|
const fetcher = seen ? async (input, init) => {
|
|
2066
2161
|
const response = await this.fetcher(input, init);
|
|
@@ -3867,7 +3962,7 @@ var CellApi = class {
|
|
|
3867
3962
|
}
|
|
3868
3963
|
url(operationId, region, request) {
|
|
3869
3964
|
const operation = managementOperations[operationId];
|
|
3870
|
-
const
|
|
3965
|
+
const path6 = operation.path.replace(/\{([^}]+)\}/g, (_, name) => {
|
|
3871
3966
|
const value = request.params[name];
|
|
3872
3967
|
if (value === void 0)
|
|
3873
3968
|
throw new Error(`${operationId} needs the path parameter ${name}.`);
|
|
@@ -3878,7 +3973,7 @@ var CellApi = class {
|
|
|
3878
3973
|
if (value !== void 0)
|
|
3879
3974
|
search.set(name, value);
|
|
3880
3975
|
const query = search.toString();
|
|
3881
|
-
return `${cellOrigin(region)}${managementApiBasePath}${
|
|
3976
|
+
return `${cellOrigin(region)}${managementApiBasePath}${path6}${query ? `?${query}` : ""}`;
|
|
3882
3977
|
}
|
|
3883
3978
|
async init(operationId, key, request) {
|
|
3884
3979
|
const headers = {
|
|
@@ -5571,17 +5666,23 @@ var MACHINE_WAKE_BLOCKED_HINTS = {
|
|
|
5571
5666
|
standing: "The account's Streams standing is not solvent: settle the account at <billing>, and the next request wakes it.",
|
|
5572
5667
|
meter_lag: "Tonbo could not confirm the account's usage in time; the next request tries again.",
|
|
5573
5668
|
crash_backoff: "Its recent wakes failed; see `tonbo machine activations <name>`.",
|
|
5574
|
-
capacity: "No host had room for its shape
|
|
5669
|
+
capacity: "No host had room for its shape: Tonbo is adding a host, <minutes>, and the next request after that wakes it.",
|
|
5575
5670
|
region_closed: "Machines are closed in its region for now; the next request after they reopen wakes it.",
|
|
5576
5671
|
budget_slowdown: "Its budget is close to running out, and this wake would pass the budget's slow-down rate: sleep another Machine of the budget, or lower this one's shape, then wake it again. Machines already awake keep running.",
|
|
5577
5672
|
budget_exhausted: "Its budget is used up for the month: it wakes after the budget resets, or once a person raises it in the console."
|
|
5578
5673
|
};
|
|
5579
5674
|
var MACHINE_ACCOUNT_EGRESS_BLOCKED_HINT = "If this is the free egress cap: 10 GiB a month without purchased credit; buy credit at <billing> to lift it.";
|
|
5675
|
+
function minutesUntil(retryAt, now = Date.now()) {
|
|
5676
|
+
const at = retryAt ? Date.parse(retryAt) : Number.NaN;
|
|
5677
|
+
if (!Number.isFinite(at) || at <= now) return "which takes a few minutes";
|
|
5678
|
+
const minutes = Math.ceil((at - now) / 6e4);
|
|
5679
|
+
return `about ${minutes} minute${minutes === 1 ? "" : "s"}`;
|
|
5680
|
+
}
|
|
5580
5681
|
function conditionText(name, condition, billing) {
|
|
5581
5682
|
switch (condition.type) {
|
|
5582
5683
|
case "wake_blocked": {
|
|
5583
5684
|
const retry = condition.retry_at ? `, retry after ${condition.retry_at}` : "";
|
|
5584
|
-
const hint = MACHINE_WAKE_BLOCKED_HINTS[condition.reason]?.replaceAll("<name>", name).replaceAll("<billing>", billing);
|
|
5685
|
+
const hint = MACHINE_WAKE_BLOCKED_HINTS[condition.reason]?.replaceAll("<name>", name).replaceAll("<billing>", billing).replaceAll("<minutes>", minutesUntil(condition.retry_at));
|
|
5585
5686
|
return `wake blocked (${condition.reason.replaceAll("_", " ")}) since ${condition.since}${retry}.${hint ? ` ${hint}` : ""}`;
|
|
5586
5687
|
}
|
|
5587
5688
|
case "creation_delayed":
|
|
@@ -6396,7 +6497,15 @@ var ENVIRONMENT = [
|
|
|
6396
6497
|
},
|
|
6397
6498
|
{
|
|
6398
6499
|
names: ["XDG_CONFIG_HOME"],
|
|
6399
|
-
description: "Where the login file lives: `$XDG_CONFIG_HOME/tonbo/login.json` when it is set and not empty, else `$HOME/.config/tonbo/login.json`. A login an earlier release saved, `credentials.json` beside it, is read only when there is no `login.json`."
|
|
6500
|
+
description: "Where the login file lives: `$XDG_CONFIG_HOME/tonbo/login.json` when it is set and not empty, else `$HOME/.config/tonbo/login.json`. A login an earlier release saved, `credentials.json` beside it, is read only when there is no `login.json`. `update-notice.json` beside it records when the update notice last printed."
|
|
6501
|
+
},
|
|
6502
|
+
{
|
|
6503
|
+
names: ["TONBO_NO_UPDATE_NOTICE"],
|
|
6504
|
+
description: "Set to `1` (anything but empty or `0`), the CLI never prints that a newer release is available."
|
|
6505
|
+
},
|
|
6506
|
+
{
|
|
6507
|
+
names: ["CI"],
|
|
6508
|
+
description: "Set to anything but empty, `0` or `false`, as CI services set it, the CLI never prints that a newer release is available."
|
|
6400
6509
|
},
|
|
6401
6510
|
{
|
|
6402
6511
|
names: ["XDG_STATE_HOME"],
|
|
@@ -6451,6 +6560,14 @@ var TOPICS = [
|
|
|
6451
6560
|
"`api-key create` takes no idempotency key, because a key's secret is never stored and a replay could not return it; a live key's name is unique, so a blind retry is refused with 409 rather than creating a second key. Every other command reads, or sets a resource to the state it names, so running it again does no more than the first run did; a repeated delete or revoke may be refused because the first one already removed it."
|
|
6452
6561
|
]
|
|
6453
6562
|
},
|
|
6563
|
+
{
|
|
6564
|
+
id: "updates",
|
|
6565
|
+
title: "Updates",
|
|
6566
|
+
text: [
|
|
6567
|
+
"The CLI names its release in a `Tonbo-CLI-Version` header on every request to the Management API and to tonbo.dev, and every answer of the Management API names the latest release in `Tonbo-CLI-Latest` and the oldest one Tonbo supports in `Tonbo-CLI-Minimum`, so the CLI learns of a new release with no request of its own; `curl -sI https://api.tonbo.dev/v1/openapi.json` shows both headers. When the latest is newer, the command ends with `tonbo <latest> is available (you have <current>): run tonbo update` on stderr, at most once a day. It never prints with `--json`, when neither stdout nor stderr is a terminal, under `CI=true`, or with `TONBO_NO_UPDATE_NOTICE=1`. An agent that runs the CLI in a terminal sees it too, and should run `tonbo update` before it goes on.",
|
|
6568
|
+
"`tonbo update` updates the CLI with the package manager that installed it, printing the command before it runs it; nothing is ever updated in the background. A release older than `Tonbo-CLI-Minimum` is refused on every request with 410 `cli_upgrade_required`: the CLI prints the refusal, which names the update, and exits 1. `tonbo update` itself still works then."
|
|
6569
|
+
]
|
|
6570
|
+
},
|
|
6454
6571
|
{
|
|
6455
6572
|
id: "confirmations",
|
|
6456
6573
|
title: "Destructive commands",
|
|
@@ -6489,6 +6606,13 @@ var COMMAND_HELP = {
|
|
|
6489
6606
|
],
|
|
6490
6607
|
examples: ["tonbo logout"]
|
|
6491
6608
|
},
|
|
6609
|
+
"tonbo update": {
|
|
6610
|
+
about: [
|
|
6611
|
+
"Reads the latest release from the `Tonbo-CLI-Latest` header of the Management API's answer, then updates the CLI with the package manager that installed it: `npm install --global @tonbo/cli@latest`, `pnpm add --global @tonbo/cli@latest`, `bun add --global @tonbo/cli@latest` or `yarn global add @tonbo/cli@latest`. It prints the command first, then runs it in this terminal, and exits with 1 if it fails. When this release is already the latest, it says so and runs nothing.",
|
|
6612
|
+
"A CLI run through npx, `pnpm dlx` or bunx runs the release you name each time, so it prints `npx @tonbo/cli@latest` and runs nothing. When it cannot tell how the CLI was installed, as for a project's own dependency or a copy, it prints the command to run and exits 1. `--check` only prints this release and the latest one."
|
|
6613
|
+
],
|
|
6614
|
+
examples: ["tonbo update", "tonbo update --check", "tonbo update --check --json"]
|
|
6615
|
+
},
|
|
6492
6616
|
"tonbo account show": {
|
|
6493
6617
|
about: [
|
|
6494
6618
|
"Prints the account commands act in: its id, name and kind, your member subject and role in it, and its limits."
|
|
@@ -7102,19 +7226,19 @@ function optionDescription(option) {
|
|
|
7102
7226
|
function applyHelp(program) {
|
|
7103
7227
|
const named = /* @__PURE__ */ new Set();
|
|
7104
7228
|
const walk = (command, words2) => {
|
|
7105
|
-
const
|
|
7229
|
+
const path6 = words2.join(" ");
|
|
7106
7230
|
command.configureHelp({ ...command.configureHelp(), optionDescription });
|
|
7107
|
-
const help = COMMAND_HELP[
|
|
7231
|
+
const help = COMMAND_HELP[path6];
|
|
7108
7232
|
if (help) {
|
|
7109
|
-
named.add(
|
|
7233
|
+
named.add(path6);
|
|
7110
7234
|
for (const [name, text] of Object.entries(help.arguments ?? {})) {
|
|
7111
7235
|
const argument = command.registeredArguments.find((entry) => entry.name() === name);
|
|
7112
|
-
if (!argument) throw new Error(`${
|
|
7236
|
+
if (!argument) throw new Error(`${path6} has no argument ${name}`);
|
|
7113
7237
|
argument.description = text;
|
|
7114
7238
|
}
|
|
7115
7239
|
for (const [flag, text] of Object.entries(help.defaults ?? {})) {
|
|
7116
7240
|
const option = command.options.find((entry) => entry.long === flag);
|
|
7117
|
-
if (!option) throw new Error(`${
|
|
7241
|
+
if (!option) throw new Error(`${path6} has no option ${flag}`);
|
|
7118
7242
|
option.defaultValueDescription = text;
|
|
7119
7243
|
}
|
|
7120
7244
|
command.addHelpText("after", commandHelpText(help));
|
|
@@ -7122,8 +7246,8 @@ function applyHelp(program) {
|
|
|
7122
7246
|
for (const child of command.commands) walk(child, [...words2, child.name()]);
|
|
7123
7247
|
};
|
|
7124
7248
|
walk(program, [program.name()]);
|
|
7125
|
-
for (const
|
|
7126
|
-
if (!named.has(
|
|
7249
|
+
for (const path6 of Object.keys(COMMAND_HELP))
|
|
7250
|
+
if (!named.has(path6)) throw new Error(`The help of ${path6} names no command`);
|
|
7127
7251
|
program.addHelpText("after", programHelpText());
|
|
7128
7252
|
return program;
|
|
7129
7253
|
}
|
|
@@ -7498,13 +7622,13 @@ async function machineProcessSummary(cell, key, region, name) {
|
|
|
7498
7622
|
return null;
|
|
7499
7623
|
}
|
|
7500
7624
|
}
|
|
7501
|
-
async function machineFileReadCommand(deps, name,
|
|
7625
|
+
async function machineFileReadCommand(deps, name, path6, options = {}) {
|
|
7502
7626
|
try {
|
|
7503
7627
|
const key = await deps.auth.apiKey();
|
|
7504
7628
|
const region = await regionOf(deps, key, name);
|
|
7505
7629
|
const response = await cellApi(deps).fetch("readMachineFile", key, region, {
|
|
7506
7630
|
params: { name },
|
|
7507
|
-
query: { path:
|
|
7631
|
+
query: { path: path6 },
|
|
7508
7632
|
headers: { range: options.range, "tonbo-expect-activation": options.expectActivation }
|
|
7509
7633
|
});
|
|
7510
7634
|
const bytes2 = new Uint8Array(await response.arrayBuffer());
|
|
@@ -7514,14 +7638,14 @@ async function machineFileReadCommand(deps, name, path5, options = {}) {
|
|
|
7514
7638
|
throw explainMachineError(error);
|
|
7515
7639
|
}
|
|
7516
7640
|
}
|
|
7517
|
-
async function machineFileWriteCommand(deps, name,
|
|
7641
|
+
async function machineFileWriteCommand(deps, name, path6, options = {}) {
|
|
7518
7642
|
try {
|
|
7519
7643
|
const bytes2 = options.from ? new Uint8Array(await readFile4(options.from)) : await readAllStdin(deps);
|
|
7520
7644
|
const key = await deps.auth.apiKey();
|
|
7521
7645
|
const region = await regionOf(deps, key, name);
|
|
7522
7646
|
const written = await cellApi(deps).json("writeMachineFile", key, region, {
|
|
7523
7647
|
params: { name },
|
|
7524
|
-
query: { path:
|
|
7648
|
+
query: { path: path6 },
|
|
7525
7649
|
headers: {
|
|
7526
7650
|
"content-type": "application/octet-stream",
|
|
7527
7651
|
"if-match": options.ifMatch,
|
|
@@ -7543,7 +7667,7 @@ async function machineFileWriteCommand(deps, name, path5, options = {}) {
|
|
|
7543
7667
|
throw explainMachineError(error);
|
|
7544
7668
|
}
|
|
7545
7669
|
}
|
|
7546
|
-
async function machineFileStatCommand(deps, name,
|
|
7670
|
+
async function machineFileStatCommand(deps, name, path6, options = {}) {
|
|
7547
7671
|
try {
|
|
7548
7672
|
const key = await deps.auth.apiKey();
|
|
7549
7673
|
const region = await regionOf(deps, key, name);
|
|
@@ -7553,7 +7677,7 @@ async function machineFileStatCommand(deps, name, path5, options = {}) {
|
|
|
7553
7677
|
region,
|
|
7554
7678
|
{
|
|
7555
7679
|
params: { name },
|
|
7556
|
-
query: { path:
|
|
7680
|
+
query: { path: path6 },
|
|
7557
7681
|
headers: { "tonbo-expect-activation": options.expectActivation }
|
|
7558
7682
|
}
|
|
7559
7683
|
);
|
|
@@ -7572,13 +7696,13 @@ async function machineFileStatCommand(deps, name, path5, options = {}) {
|
|
|
7572
7696
|
throw explainMachineError(error);
|
|
7573
7697
|
}
|
|
7574
7698
|
}
|
|
7575
|
-
async function machineFileListCommand(deps, name,
|
|
7699
|
+
async function machineFileListCommand(deps, name, path6, options = {}) {
|
|
7576
7700
|
try {
|
|
7577
7701
|
const key = await deps.auth.apiKey();
|
|
7578
7702
|
const region = await regionOf(deps, key, name);
|
|
7579
7703
|
const listing = await cellApi(deps).json("listMachineFiles", key, region, {
|
|
7580
7704
|
params: { name },
|
|
7581
|
-
query: { path:
|
|
7705
|
+
query: { path: path6 },
|
|
7582
7706
|
headers: { "tonbo-expect-activation": options.expectActivation }
|
|
7583
7707
|
});
|
|
7584
7708
|
noticeWake(deps, listing.machine, name);
|
|
@@ -7602,19 +7726,19 @@ async function machineFileListCommand(deps, name, path5, options = {}) {
|
|
|
7602
7726
|
throw explainMachineError(error);
|
|
7603
7727
|
}
|
|
7604
7728
|
}
|
|
7605
|
-
async function machineFileDeleteCommand(deps, name,
|
|
7729
|
+
async function machineFileDeleteCommand(deps, name, path6, options = {}) {
|
|
7606
7730
|
try {
|
|
7607
7731
|
const key = await deps.auth.apiKey();
|
|
7608
7732
|
const region = await regionOf(deps, key, name);
|
|
7609
7733
|
await cellApi(deps).fetch("deleteMachineFile", key, region, {
|
|
7610
7734
|
params: { name },
|
|
7611
|
-
query: { path:
|
|
7735
|
+
query: { path: path6, recursive: options.recursive ? "true" : void 0 },
|
|
7612
7736
|
headers: {
|
|
7613
7737
|
"tonbo-expect-activation": options.expectActivation,
|
|
7614
7738
|
"idempotency-key": randomUUID8()
|
|
7615
7739
|
}
|
|
7616
7740
|
});
|
|
7617
|
-
print(deps, options.json, { path:
|
|
7741
|
+
print(deps, options.json, { path: path6 }, `Deleted ${path6}.`);
|
|
7618
7742
|
} catch (error) {
|
|
7619
7743
|
throw explainMachineError(error);
|
|
7620
7744
|
}
|
|
@@ -7730,8 +7854,8 @@ var LEGACY_BEGIN = "# >>> tonbo project ssh >>>";
|
|
|
7730
7854
|
var LEGACY_END = "# <<< tonbo project ssh <<<";
|
|
7731
7855
|
var PROXY_COMMAND = /^\s*proxycommand(\s|=)/i;
|
|
7732
7856
|
var SSH_PROXY = /(^|[\s'"/])ssh-proxy(\s|['"]|$)/;
|
|
7733
|
-
function shown(
|
|
7734
|
-
return
|
|
7857
|
+
function shown(path6, home) {
|
|
7858
|
+
return path6.startsWith(home + sep) ? `~${path6.slice(home.length)}` : path6;
|
|
7735
7859
|
}
|
|
7736
7860
|
function words(text) {
|
|
7737
7861
|
const found = [];
|
|
@@ -7747,15 +7871,15 @@ async function expand(pattern) {
|
|
|
7747
7871
|
let paths = [sep];
|
|
7748
7872
|
for (const part of parts) {
|
|
7749
7873
|
if (!/[*?]/.test(part)) {
|
|
7750
|
-
paths = paths.map((
|
|
7874
|
+
paths = paths.map((path6) => join(path6, part));
|
|
7751
7875
|
continue;
|
|
7752
7876
|
}
|
|
7753
7877
|
const matcher = wildcard(part);
|
|
7754
7878
|
const next = [];
|
|
7755
|
-
for (const
|
|
7756
|
-
const names = await readdir(
|
|
7879
|
+
for (const path6 of paths) {
|
|
7880
|
+
const names = await readdir(path6).catch(() => []);
|
|
7757
7881
|
for (const name of names.sort())
|
|
7758
|
-
if (matcher.test(name) && !name.startsWith(".")) next.push(join(
|
|
7882
|
+
if (matcher.test(name) && !name.startsWith(".")) next.push(join(path6, name));
|
|
7759
7883
|
}
|
|
7760
7884
|
paths = next;
|
|
7761
7885
|
}
|
|
@@ -7765,18 +7889,18 @@ async function scan(home) {
|
|
|
7765
7889
|
const sshDir = join(home, ".ssh");
|
|
7766
7890
|
const files = [];
|
|
7767
7891
|
const seen = /* @__PURE__ */ new Set();
|
|
7768
|
-
const visit = async (
|
|
7769
|
-
if (depth > 16 || seen.has(
|
|
7770
|
-
seen.add(
|
|
7892
|
+
const visit = async (path6, depth) => {
|
|
7893
|
+
if (depth > 16 || seen.has(path6) || !path6.startsWith(sshDir + sep)) return;
|
|
7894
|
+
seen.add(path6);
|
|
7771
7895
|
let text;
|
|
7772
7896
|
try {
|
|
7773
|
-
if (!(await stat2(
|
|
7774
|
-
text = await readFile5(
|
|
7897
|
+
if (!(await stat2(path6)).isFile()) return;
|
|
7898
|
+
text = await readFile5(path6, "utf8");
|
|
7775
7899
|
} catch {
|
|
7776
7900
|
return;
|
|
7777
7901
|
}
|
|
7778
7902
|
const lines = text.split("\n");
|
|
7779
|
-
const file = { path:
|
|
7903
|
+
const file = { path: path6, lines, blocks: [], handEdited: [] };
|
|
7780
7904
|
const includes = [];
|
|
7781
7905
|
for (let index = 0; index < lines.length; index++) {
|
|
7782
7906
|
if (lines[index].trim() === LEGACY_BEGIN) {
|
|
@@ -7915,10 +8039,10 @@ async function ensureKey(directory) {
|
|
|
7915
8039
|
}
|
|
7916
8040
|
return key;
|
|
7917
8041
|
}
|
|
7918
|
-
async function writeAtomic(
|
|
7919
|
-
const temporary = `${
|
|
8042
|
+
async function writeAtomic(path6, data) {
|
|
8043
|
+
const temporary = `${path6}.${process.pid}.tmp`;
|
|
7920
8044
|
await writeFile3(temporary, data, { mode: 384 });
|
|
7921
|
-
await rename5(temporary,
|
|
8045
|
+
await rename5(temporary, path6);
|
|
7922
8046
|
}
|
|
7923
8047
|
function certificatePath(directory, user) {
|
|
7924
8048
|
return join2(directory, `${user}-cert.pub`);
|
|
@@ -8122,33 +8246,255 @@ async function sshCertificateIssueCommand(deps, options) {
|
|
|
8122
8246
|
print(deps, options.json, data, data.certificate);
|
|
8123
8247
|
}
|
|
8124
8248
|
|
|
8249
|
+
// src/update.ts
|
|
8250
|
+
import { execFile as execFile3, spawn as spawn2 } from "node:child_process";
|
|
8251
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
8252
|
+
import { mkdir as mkdir5, readFile as readFile7, realpath as realpath2, rename as rename6, writeFile as writeFile4 } from "node:fs/promises";
|
|
8253
|
+
import path5 from "node:path";
|
|
8254
|
+
import { fileURLToPath } from "node:url";
|
|
8255
|
+
import { promisify as promisify3 } from "node:util";
|
|
8256
|
+
var contract = cliVersionsContract;
|
|
8257
|
+
var CLI_VERSION = JSON.parse(readFileSync2(new URL("../../package.json", import.meta.url), "utf8")).version;
|
|
8258
|
+
var RELEASE = new RegExp(contract.release_pattern);
|
|
8259
|
+
function compareReleases(a, b) {
|
|
8260
|
+
const left = RELEASE.exec(a);
|
|
8261
|
+
const right = RELEASE.exec(b);
|
|
8262
|
+
if (!left || !right) return null;
|
|
8263
|
+
for (let at = 1; at <= 3; at++) {
|
|
8264
|
+
const difference = Number(left[at]) - Number(right[at]);
|
|
8265
|
+
if (difference !== 0) return Math.sign(difference);
|
|
8266
|
+
}
|
|
8267
|
+
const [leftPre, rightPre] = [left[4], right[4]];
|
|
8268
|
+
if (leftPre === rightPre) return 0;
|
|
8269
|
+
if (leftPre === void 0) return 1;
|
|
8270
|
+
if (rightPre === void 0) return -1;
|
|
8271
|
+
return leftPre < rightPre ? -1 : 1;
|
|
8272
|
+
}
|
|
8273
|
+
function latestOf(headers) {
|
|
8274
|
+
const latest = headers.get(contract.headers.latest)?.trim() ?? "";
|
|
8275
|
+
return RELEASE.test(latest) ? latest : null;
|
|
8276
|
+
}
|
|
8277
|
+
function noticeLine(current, latest) {
|
|
8278
|
+
if (latest === null || (compareReleases(current, latest) ?? 0) >= 0) return null;
|
|
8279
|
+
return contract.notice.line.replace("{latest}", latest).replace("{current}", current);
|
|
8280
|
+
}
|
|
8281
|
+
var ReleaseWatch = class {
|
|
8282
|
+
constructor(current = CLI_VERSION, refuse = (message) => {
|
|
8283
|
+
process.stderr.write(`${message}
|
|
8284
|
+
`);
|
|
8285
|
+
process.exit(1);
|
|
8286
|
+
}) {
|
|
8287
|
+
this.current = current;
|
|
8288
|
+
this.refuse = refuse;
|
|
8289
|
+
}
|
|
8290
|
+
latest = null;
|
|
8291
|
+
fetch(fetcher) {
|
|
8292
|
+
return async (input, init) => {
|
|
8293
|
+
const headers = new Headers(
|
|
8294
|
+
init?.headers ?? (input instanceof Request ? input.headers : void 0)
|
|
8295
|
+
);
|
|
8296
|
+
headers.set(contract.headers.client, this.current);
|
|
8297
|
+
const response = await fetcher(input, { ...init, headers });
|
|
8298
|
+
this.latest = latestOf(response.headers) ?? this.latest;
|
|
8299
|
+
if (response.status === 410) {
|
|
8300
|
+
const body = await response.clone().json().catch(() => null);
|
|
8301
|
+
if (body?.code === contract.refusal.code) {
|
|
8302
|
+
const reason2 = [body.detail, body.error].find(
|
|
8303
|
+
(value) => typeof value === "string"
|
|
8304
|
+
);
|
|
8305
|
+
this.refuse(
|
|
8306
|
+
reason2 ?? `This tonbo (${this.current}) is no longer supported. Run \`tonbo update\` (or \`${contract.update_command}\`).`
|
|
8307
|
+
);
|
|
8308
|
+
}
|
|
8309
|
+
}
|
|
8310
|
+
return response;
|
|
8311
|
+
};
|
|
8312
|
+
}
|
|
8313
|
+
};
|
|
8314
|
+
var set = (value) => !!value && value !== "0";
|
|
8315
|
+
function noticeAllowed({ env, argv, stdoutIsTTY, stderrIsTTY }) {
|
|
8316
|
+
if (set(env.TONBO_NO_UPDATE_NOTICE)) return false;
|
|
8317
|
+
if (set(env.CI) && env.CI?.toLowerCase() !== "false") return false;
|
|
8318
|
+
if (argv.includes("--json")) return false;
|
|
8319
|
+
return stdoutIsTTY || stderrIsTTY;
|
|
8320
|
+
}
|
|
8321
|
+
function noticeStatePath(env = process.env) {
|
|
8322
|
+
return path5.join(path5.dirname(defaultLoginPath(env)), contract.notice.state.file);
|
|
8323
|
+
}
|
|
8324
|
+
var INTERVAL_MS = contract.notice.interval_hours * 60 * 60 * 1e3;
|
|
8325
|
+
function noticeDue(state, now) {
|
|
8326
|
+
const at = state && typeof state === "object" ? Date.parse(String(state.notified_at)) : Number.NaN;
|
|
8327
|
+
return !Number.isFinite(at) || at > now || now - at >= INTERVAL_MS;
|
|
8328
|
+
}
|
|
8329
|
+
async function showUpdateNotice(watch, {
|
|
8330
|
+
context,
|
|
8331
|
+
statePath,
|
|
8332
|
+
now = Date.now(),
|
|
8333
|
+
write = (line) => process.stderr.write(`${line}
|
|
8334
|
+
`)
|
|
8335
|
+
}) {
|
|
8336
|
+
const line = noticeLine(watch.current, watch.latest);
|
|
8337
|
+
if (line === null || !noticeAllowed(context)) return;
|
|
8338
|
+
const state = await readFile7(statePath, "utf8").then((text) => JSON.parse(text)).catch(() => null);
|
|
8339
|
+
if (!noticeDue(state, now)) return;
|
|
8340
|
+
write(line);
|
|
8341
|
+
try {
|
|
8342
|
+
await mkdir5(path5.dirname(statePath), { recursive: true, mode: 448 });
|
|
8343
|
+
const temporary = `${statePath}.${process.pid}.tmp`;
|
|
8344
|
+
await writeFile4(
|
|
8345
|
+
temporary,
|
|
8346
|
+
`${JSON.stringify({ version: 1, notified_at: new Date(now).toISOString() })}
|
|
8347
|
+
`,
|
|
8348
|
+
{ mode: 384 }
|
|
8349
|
+
);
|
|
8350
|
+
await rename6(temporary, statePath);
|
|
8351
|
+
} catch {
|
|
8352
|
+
}
|
|
8353
|
+
}
|
|
8354
|
+
function installMethod(location) {
|
|
8355
|
+
const at = location.replaceAll("\\", "/").toLowerCase();
|
|
8356
|
+
if (at.includes("/_npx/") || at.includes("/bunx-") || at.includes("/pnpm/dlx/")) return "npx";
|
|
8357
|
+
if (at.includes("/pnpm/global/") || at.includes("/global/") && at.includes("/.pnpm/"))
|
|
8358
|
+
return "pnpm";
|
|
8359
|
+
if (at.includes("/install/global/node_modules/")) return "bun";
|
|
8360
|
+
if (at.includes("/yarn/global/") || at.includes("/yarn/data/global/")) return "yarn";
|
|
8361
|
+
if (/\/(lib|npm|nodejs)\/node_modules\/@tonbo\/cli\//.test(at)) return "npm";
|
|
8362
|
+
return "standalone";
|
|
8363
|
+
}
|
|
8364
|
+
var PACKAGE = `${contract.package}@latest`;
|
|
8365
|
+
var UPDATE_COMMANDS = {
|
|
8366
|
+
npm: ["npm", "install", "--global", PACKAGE],
|
|
8367
|
+
pnpm: ["pnpm", "add", "--global", PACKAGE],
|
|
8368
|
+
bun: ["bun", "add", "--global", PACKAGE],
|
|
8369
|
+
yarn: ["yarn", "global", "add", PACKAGE]
|
|
8370
|
+
};
|
|
8371
|
+
function updateCommandLine(method) {
|
|
8372
|
+
if (method === "npx") return `npx ${PACKAGE}`;
|
|
8373
|
+
if (method === "standalone") return contract.update_command;
|
|
8374
|
+
return UPDATE_COMMANDS[method].join(" ");
|
|
8375
|
+
}
|
|
8376
|
+
async function readGlobalRoot(manager) {
|
|
8377
|
+
try {
|
|
8378
|
+
const { stdout } = await promisify3(execFile3)(manager, ["root", "-g"], {
|
|
8379
|
+
shell: process.platform === "win32",
|
|
8380
|
+
timeout: 15e3
|
|
8381
|
+
});
|
|
8382
|
+
return await realpath2(stdout.trim());
|
|
8383
|
+
} catch {
|
|
8384
|
+
return null;
|
|
8385
|
+
}
|
|
8386
|
+
}
|
|
8387
|
+
function isInside(location, directory) {
|
|
8388
|
+
const normal = (value) => value.replaceAll("\\", "/").replace(/\/+$/, "").toLowerCase();
|
|
8389
|
+
return normal(location).startsWith(`${normal(directory)}/`);
|
|
8390
|
+
}
|
|
8391
|
+
async function confirmedInstallMethod(location, globalRoot) {
|
|
8392
|
+
const method = installMethod(location);
|
|
8393
|
+
if (method !== "npm" && method !== "pnpm") return method;
|
|
8394
|
+
const root = await globalRoot(method);
|
|
8395
|
+
if (root === null) return "standalone";
|
|
8396
|
+
return isInside(location, method === "pnpm" ? path5.dirname(root) : root) ? method : "standalone";
|
|
8397
|
+
}
|
|
8398
|
+
async function readLatest(origin, fetcher = fetch) {
|
|
8399
|
+
try {
|
|
8400
|
+
const response = await fetcher(`${origin}/v1/openapi.json`, {
|
|
8401
|
+
method: "HEAD",
|
|
8402
|
+
signal: AbortSignal.timeout(1e4)
|
|
8403
|
+
});
|
|
8404
|
+
return latestOf(response.headers);
|
|
8405
|
+
} catch {
|
|
8406
|
+
return null;
|
|
8407
|
+
}
|
|
8408
|
+
}
|
|
8409
|
+
function runCommand(command) {
|
|
8410
|
+
return new Promise((resolve2, reject) => {
|
|
8411
|
+
const child = spawn2(command[0], command.slice(1), {
|
|
8412
|
+
stdio: "inherit",
|
|
8413
|
+
// npm, pnpm and yarn are .cmd scripts on Windows, which only a shell runs.
|
|
8414
|
+
shell: process.platform === "win32"
|
|
8415
|
+
});
|
|
8416
|
+
child.on("error", reject);
|
|
8417
|
+
child.on("exit", (code, signal) => resolve2(code ?? (signal ? 1 : 0)));
|
|
8418
|
+
});
|
|
8419
|
+
}
|
|
8420
|
+
var SCRIPT_LOCATION = fileURLToPath(import.meta.url);
|
|
8421
|
+
async function updateCommand(deps, options) {
|
|
8422
|
+
if (options.json && !options.check) throw new Error("--json goes with --check.");
|
|
8423
|
+
const latest = await deps.latest();
|
|
8424
|
+
const method = await confirmedInstallMethod(deps.location, deps.globalRoot);
|
|
8425
|
+
const command = updateCommandLine(method);
|
|
8426
|
+
const order = latest === null ? null : compareReleases(deps.current, latest);
|
|
8427
|
+
const available = order !== null && order < 0;
|
|
8428
|
+
if (options.check) {
|
|
8429
|
+
if (options.json) {
|
|
8430
|
+
deps.output(
|
|
8431
|
+
JSON.stringify({
|
|
8432
|
+
current: deps.current,
|
|
8433
|
+
latest,
|
|
8434
|
+
update_available: available,
|
|
8435
|
+
install: method,
|
|
8436
|
+
command
|
|
8437
|
+
})
|
|
8438
|
+
);
|
|
8439
|
+
} else if (latest === null) {
|
|
8440
|
+
deps.output(`tonbo ${deps.current}; the latest release could not be read from Tonbo.`);
|
|
8441
|
+
} else if (available) {
|
|
8442
|
+
deps.output(`tonbo ${deps.current}; ${latest} is available: run tonbo update`);
|
|
8443
|
+
} else {
|
|
8444
|
+
deps.output(`tonbo ${deps.current} is the latest release.`);
|
|
8445
|
+
}
|
|
8446
|
+
return;
|
|
8447
|
+
}
|
|
8448
|
+
if (latest !== null && !available) {
|
|
8449
|
+
deps.output(`tonbo ${deps.current} is the latest release.`);
|
|
8450
|
+
return;
|
|
8451
|
+
}
|
|
8452
|
+
if (method === "npx") {
|
|
8453
|
+
deps.notice(
|
|
8454
|
+
`This tonbo runs through a package runner, which runs the release you name each time: run \`${command}\`.`
|
|
8455
|
+
);
|
|
8456
|
+
return;
|
|
8457
|
+
}
|
|
8458
|
+
if (method === "standalone") {
|
|
8459
|
+
deps.notice(
|
|
8460
|
+
`Could not tell how the tonbo at ${deps.location} was installed, so nothing was run. Update it with \`${command}\`, or the way you installed it.`
|
|
8461
|
+
);
|
|
8462
|
+
process.exitCode = 1;
|
|
8463
|
+
return;
|
|
8464
|
+
}
|
|
8465
|
+
deps.notice(`Running: ${command}`);
|
|
8466
|
+
const status = await deps.run(UPDATE_COMMANDS[method]);
|
|
8467
|
+
if (status !== 0)
|
|
8468
|
+
throw new Error(`${command} exited with status ${status}; tonbo was not updated.`);
|
|
8469
|
+
deps.output(`Updated tonbo${latest ? ` to ${latest}` : ""}: run tonbo --version to check.`);
|
|
8470
|
+
}
|
|
8471
|
+
|
|
8125
8472
|
// src/app.ts
|
|
8126
|
-
var
|
|
8127
|
-
readFileSync2(new URL("../../package.json", import.meta.url), "utf8")
|
|
8128
|
-
);
|
|
8473
|
+
var releaseWatch = new ReleaseWatch();
|
|
8129
8474
|
function createDependencies(selection = {}) {
|
|
8130
8475
|
const api = apiOrigin();
|
|
8476
|
+
const fetcher = releaseWatch.fetch(fetch);
|
|
8131
8477
|
const account = selection.account?.trim() || process.env.TONBO_ACCOUNT?.trim() || void 0;
|
|
8132
8478
|
const credentials = new LoginStore({
|
|
8133
|
-
fetcher
|
|
8479
|
+
fetcher,
|
|
8134
8480
|
apiOrigin: api,
|
|
8135
8481
|
loginOrigin: loginOrigin(api),
|
|
8136
8482
|
account
|
|
8137
8483
|
});
|
|
8138
8484
|
const notice = (text) => console.error(text);
|
|
8139
8485
|
return {
|
|
8140
|
-
api: new ManagementApi(noticingBudget(
|
|
8486
|
+
api: new ManagementApi(noticingBudget(fetcher, notice), api),
|
|
8141
8487
|
ask: terminalAsk,
|
|
8142
8488
|
auth: new AuthClient(
|
|
8143
8489
|
credentials,
|
|
8144
|
-
|
|
8490
|
+
fetcher,
|
|
8145
8491
|
loginOrigin(api),
|
|
8146
8492
|
void 0,
|
|
8147
8493
|
void 0,
|
|
8148
8494
|
void 0,
|
|
8149
8495
|
void 0,
|
|
8150
8496
|
process.env,
|
|
8151
|
-
{ client: "tonbo", clientVersion:
|
|
8497
|
+
{ client: "tonbo", clientVersion: CLI_VERSION }
|
|
8152
8498
|
),
|
|
8153
8499
|
credentials,
|
|
8154
8500
|
interactive: () => process.stdin.isTTY === true && process.stderr.isTTY === true,
|
|
@@ -8233,7 +8579,7 @@ function parseUsageSort(value) {
|
|
|
8233
8579
|
}
|
|
8234
8580
|
}
|
|
8235
8581
|
function createProgram(makeDependencies = createDependencies) {
|
|
8236
|
-
const program = new Command().name("tonbo").description("Sign in to Tonbo and manage account resources.").version(
|
|
8582
|
+
const program = new Command().name("tonbo").description("Sign in to Tonbo and manage account resources.").version(CLI_VERSION).option(
|
|
8237
8583
|
"--account <account>",
|
|
8238
8584
|
"the account to act in, by name or id: any account you belong to; else TONBO_ACCOUNT, else the default `tonbo account use` set, else your personal account"
|
|
8239
8585
|
);
|
|
@@ -8247,6 +8593,20 @@ function createProgram(makeDependencies = createDependencies) {
|
|
|
8247
8593
|
const account2 = program.opts().account;
|
|
8248
8594
|
await loginCommand(dependencies(), { ...options, account: account2 });
|
|
8249
8595
|
});
|
|
8596
|
+
program.command("update").description("update this CLI with the package manager that installed it").option("--check", "only print this release and the latest one").option("--json", "with --check, print the two releases and the update command as JSON").action(
|
|
8597
|
+
async (options) => updateCommand(
|
|
8598
|
+
{
|
|
8599
|
+
current: CLI_VERSION,
|
|
8600
|
+
latest: () => readLatest(apiOrigin()),
|
|
8601
|
+
location: SCRIPT_LOCATION,
|
|
8602
|
+
globalRoot: readGlobalRoot,
|
|
8603
|
+
run: runCommand,
|
|
8604
|
+
output: (text) => console.log(text),
|
|
8605
|
+
notice: (text) => console.error(text)
|
|
8606
|
+
},
|
|
8607
|
+
options
|
|
8608
|
+
)
|
|
8609
|
+
);
|
|
8250
8610
|
program.command("logout").description(
|
|
8251
8611
|
"end this computer's CLI login and remove the saved login; Yield's capture here stops until the next login"
|
|
8252
8612
|
).action(async () => logoutCommand(dependencies()));
|
|
@@ -8646,31 +9006,31 @@ function createProgram(makeDependencies = createDependencies) {
|
|
|
8646
9006
|
file.command("read <name> <path>").description("print a file of a Machine, waking it first").option("--output <file>", "write the bytes to this local file instead of stdout").option("--range <range>", "one byte range, such as bytes=0-1023").option("--expect-activation <activation>", "read only in this activation"),
|
|
8647
9007
|
"readMachineFile"
|
|
8648
9008
|
).action(
|
|
8649
|
-
async (name,
|
|
9009
|
+
async (name, path6, options) => machineFileReadCommand(dependencies(), name, path6, options)
|
|
8650
9010
|
);
|
|
8651
9011
|
operationOptions(
|
|
8652
9012
|
file.command("write <name> <path>").description("replace a file of a Machine with stdin or --from, atomically").option("--from <file>", "the local file to send instead of stdin").option("--mode <mode>", "the file's mode, four octal digits such as 0755").option("--if-match <etag>", "replace only the version with this ETag").option("--create", "only create the file; refuse when it exists").option("--expect-activation <activation>", "write only in this activation"),
|
|
8653
9013
|
"writeMachineFile"
|
|
8654
9014
|
).action(
|
|
8655
|
-
async (name,
|
|
9015
|
+
async (name, path6, options) => machineFileWriteCommand(dependencies(), name, path6, options)
|
|
8656
9016
|
);
|
|
8657
9017
|
operationOptions(
|
|
8658
9018
|
file.command("stat <name> <path>").description("show a path's type, size, mode, owner and ETag").option("--expect-activation <activation>", "stat only in this activation"),
|
|
8659
9019
|
"statMachineFile"
|
|
8660
9020
|
).action(
|
|
8661
|
-
async (name,
|
|
9021
|
+
async (name, path6, options) => machineFileStatCommand(dependencies(), name, path6, options)
|
|
8662
9022
|
);
|
|
8663
9023
|
operationOptions(
|
|
8664
9024
|
file.command("list <name> <path>").description("list a directory of a Machine").option("--expect-activation <activation>", "list only in this activation"),
|
|
8665
9025
|
"listMachineFiles"
|
|
8666
9026
|
).action(
|
|
8667
|
-
async (name,
|
|
9027
|
+
async (name, path6, options) => machineFileListCommand(dependencies(), name, path6, options)
|
|
8668
9028
|
);
|
|
8669
9029
|
operationOptions(
|
|
8670
9030
|
file.command("delete <name> <path>").description("delete a file, a symbolic link or an empty directory of a Machine").option("--recursive", "delete a directory and everything in it").option("--expect-activation <activation>", "delete only in this activation"),
|
|
8671
9031
|
"deleteMachineFile"
|
|
8672
9032
|
).action(
|
|
8673
|
-
async (name,
|
|
9033
|
+
async (name, path6, options) => machineFileDeleteCommand(dependencies(), name, path6, options)
|
|
8674
9034
|
);
|
|
8675
9035
|
const hold = machine.command("hold").description("keep a Machine awake for work that has no open connection");
|
|
8676
9036
|
operationOptions(
|
|
@@ -8807,4 +9167,14 @@ try {
|
|
|
8807
9167
|
} catch (error) {
|
|
8808
9168
|
console.error(error instanceof Error ? error.message : error);
|
|
8809
9169
|
if (!process.exitCode) process.exitCode = 1;
|
|
9170
|
+
} finally {
|
|
9171
|
+
await showUpdateNotice(releaseWatch, {
|
|
9172
|
+
context: {
|
|
9173
|
+
env: process.env,
|
|
9174
|
+
argv: process.argv,
|
|
9175
|
+
stdoutIsTTY: process.stdout.isTTY === true,
|
|
9176
|
+
stderrIsTTY: process.stderr.isTTY === true
|
|
9177
|
+
},
|
|
9178
|
+
statePath: noticeStatePath()
|
|
9179
|
+
}).catch(() => void 0);
|
|
8810
9180
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tonbo/cli",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.3",
|
|
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": {
|