@astris-labs/cli 0.1.3 → 0.2.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 CHANGED
@@ -30,7 +30,11 @@ Requests go to `https://api.astris.run`.
30
30
 
31
31
  ## Commands
32
32
 
33
- v0 commands are `deploy`, `status`, `env`, `domains`, `restart`, `diagnose`, `database`, `storage`, `compose`, `list`, `logs`, `usage`, `plans`, `whoami`, `next`, `login`, `logout`, `app`, `preflight`, and `export`.
33
+ v0 commands are `deploy`, `status`, `env`, `domains`, `restart`, `diagnose`, `database`, `storage`, `compose`, `list`, `project`, `logs`, `usage`, `plans`, `billing`, `whoami`, `next`, `login`, `logout`, `app`, `preflight`, `export`, `mcp`, and `new`.
34
+
35
+ ### mcp
36
+
37
+ `astris mcp` is the stdio bridge an editor runs. Each line of stdin is one JSON-RPC message and is posted to `https://api.astris.run/mcp`. The key is the one `astris login` saved, or `ASTRIS_API_KEY` when that variable is set. A message with no id gets no reply line. Each reply is one JSON line on stdout. Logs go to stderr.
34
38
 
35
39
  `--json` writes one JSON object to stdout. Without `--json`, success is text on stdout and an error is a sentence on stderr.
36
40
 
@@ -53,13 +57,15 @@ Optional flags are `--build-path`, `--dockerfile`, `--port`, `--build`, `--publi
53
57
 
54
58
  `--app <id> --hook` prints the push-to-deploy hook URL of that application, one line. The URL is a credential: keep it private. `--hook` needs `--app`; without it the sentence is `Send --app set to the application id.` `--hook` follows the same `--app` rules as a redeploy, so the flags that rule refuses are refused with it.
55
59
 
60
+ `--wait` makes the command wait until the deploy it queued ends, then print `done`, `error`, or `cancelled` and nothing else. A deploy still running when the wait stops prints `still running, check status`. A refusal while polling prints that status's sentence. `--hook` with `--wait` is refused. A create that prints a deploy key queues no deploy, so it does not wait.
61
+
56
62
  `--build nixpacks` builds with Nixpacks. `--build dockerfile` builds from the Dockerfile named by `--dockerfile`; without `--build`, a `--dockerfile` value alone means a Dockerfile build. `--build static` serves static files; with it, `--publish-directory <dir>` names the directory to publish, relative to the build, and `--spa` serves it as a single-page application. `--spa` takes no value, and `--spa=false` is refused.
57
63
 
58
64
  `--kind web` is the default kind. `--kind worker` runs `--command` as the container's process. `--kind cron` runs `--command` on `--schedule`, five space-separated fields. A worker or a cron app cannot use `--build static`. `--app` with any of `--build`, `--publish-directory`, `--spa`, `--kind`, `--command`, or `--schedule` is refused and makes no request; the create flags `--project`, `--name`, `--git-url`, `--branch`, `--port`, `--dockerfile`, and `--build-path` are still ignored with `--app`.
59
65
 
60
66
  ### status
61
67
 
62
- `astris status --app <id>` prints the status and the hostnames.
68
+ `astris status --app <id>` prints the status and the hostnames. When the check is answering or not answering, the next line is `check: answering since <since>` or `check: not answering since <since>`, and `<since>` is the string the API sent. A null since drops that part, so the line is `check: answering` or `check: not answering`. `--json` adds `check` as `{state, since}`. No usable check, or a state of unknown, prints no check line and adds no `check` key. The exit code stays 0.
63
69
 
64
70
  ### env
65
71
 
@@ -75,6 +81,10 @@ Optional flags are `--build-path`, `--dockerfile`, `--port`, `--build`, `--publi
75
81
 
76
82
  `astris domains verify --app <id> --id <domainId>` asks Astris to check the DNS records. It prints the status the API returned, and the reason on the next line when the check failed. A missing or malformed `--id` makes no request.
77
83
 
84
+ `astris domains status --app <id> --id <domainId>` calls `GET /v1/apps/:id/domains/:domainId` and sends no body. It reads the domain and changes nothing. Human output is the hostname, the status, and the certificate word (`issued`, `pending`, `failed`, or `not_applicable`), one per line. `--json` prints one JSON object `{"application_id":"<id>","domain":{"id":"<domainId>","hostname":"<host>","kind":"<kind>","status":"<status>","certificate":"<certificate>"}}`. A missing or malformed `--app` or `--id` makes no request.
85
+
86
+ `astris domains remove --app <id> --id <domainId> --yes` calls `DELETE /v1/apps/:id/domains/:domainId` and sends no body. Without `--yes` it makes no request and says the domain and its hostname are removed and that `--yes` confirms. A repeated `--id` is refused and makes no request. Human output is `Domain removed.` `--json` prints one JSON object `{"application_id":"<id>","domain_id":"<domainId>","removed":true}`. A hostname the API will not let go is refused with the API's own sentence, and nothing is removed.
87
+
78
88
  ### restart
79
89
 
80
90
  `astris restart --app <id>` restarts the application.
@@ -85,25 +95,31 @@ Optional flags are `--build-path`, `--dockerfile`, `--port`, `--build`, `--publi
85
95
 
86
96
  ### database
87
97
 
88
- `astris database create --project <id> --kind <kind> --name <name>` calls `POST /v1/projects/:id/databases` with `kind` and `name`. `kind` is `postgres` or `redis`. The password is printed on this command only.
98
+ `astris database create --project <id> --kind <kind> --name <name>` calls `POST /v1/projects/:id/databases` with `kind` and `name`. `kind` is `postgres` or `redis`. A `--kind` that is neither is refused before any request with `Astris offers Postgres and Redis databases. Set kind to postgres or redis.` The password is printed on this command only.
89
99
 
90
100
  `astris database status --id <id>` calls `GET /v1/databases/:id`. It does not print the password.
91
101
 
92
102
  `astris database backup --id <id>` calls `POST /v1/databases/:id/backups` and sends no body. Human output is `Backup run.` `--json` prints one JSON object `{"database_id":"<id>","backup":"run"}`.
93
103
 
104
+ `astris database backups --id <id>` calls `GET /v1/databases/:id/backups` and sends no body. Human output is one line per backup: the key, the size in bytes, and the last modified time, in the order the API returned. An empty list prints nothing. `--json` prints one JSON object `{"database_id":"<id>","backups":[{"key":"<key>","size_bytes":<bytes>,"last_modified":"<time>"}]}`. A Redis database is refused with the API's own sentence.
105
+
94
106
  `astris database delete --id <id> --yes` calls `DELETE /v1/databases/:id` and sends no body. Without `--yes` it makes no request and says the database and its data are removed and that `--yes` confirms. A repeated `--id` is refused and makes no request. Human output is `Database removed.` `--json` prints one JSON object `{"database_id":"<id>","removed":true}`.
95
107
 
96
108
  ### compose
97
109
 
98
110
  `astris compose list` calls `GET /v1/compose`. Human output is one line per stack in the order the API returned: the id, the name, and the project name. An empty list prints nothing. `--json` prints one JSON object `{"compose":[...]}` of the stack rows.
99
111
 
100
- `astris compose deploy --id <id>` calls `POST /v1/compose/:id/deploy` and sends no body. Human output is the stack id, project id, name, git URL, branch, compose path, memory limit, created_at, and updated_at, one per line. `--json` prints one JSON object `{"compose":{...}}`.
112
+ `astris compose deploy --id <id>` calls `POST /v1/compose/:id/deploy` and sends no body. In the private preview the API refuses every redeploy, so the command prints the API's sentence and exits 1. Otherwise human output is the stack id, project id, name, git URL, branch, compose path, memory limit, created_at, and updated_at, one per line. `--json` prints one JSON object `{"compose":{...}}`.
101
113
 
102
114
  `astris compose start --id <id>` calls `POST /v1/compose/:id/start` and sends no body. Human output is the status the API returned, then the same stack fields as deploy, one per line. `--json` prints one JSON object `{"compose":{...},"status":"<status>"}`.
103
115
 
104
116
  `astris compose stop --id <id>` calls `POST /v1/compose/:id/stop` and sends no body. Human output matches start. `--json` prints one JSON object `{"compose":{...},"status":"<status>"}`.
105
117
 
106
- Without `--id`, or with an id that is not a stack id, deploy, start, and stop make no request and say `Send --id set to the compose stack id.`
118
+ `astris compose status --id <id>` calls `GET /v1/compose/:id` and sends no body. It reads the stack and changes nothing. Human output matches start. `--json` prints one JSON object `{"compose":{...},"status":"<status>"}`.
119
+
120
+ `astris compose remove --id <id> --confirm <name>` calls `DELETE /v1/compose/:id` with `{"confirm":"<name>"}`. The route compares the name with the stack and answers 400 when it differs; the command sends what was typed and reads nothing first. Without `--confirm`, or with an empty one, it makes no request and says that removing a stack deletes its volumes and all their data and that `--confirm` takes the stack's exact name. Human output is `Compose stack removed.` `--json` prints one JSON object `{"compose_id":"<id>","removed":true}`.
121
+
122
+ Without `--id`, or with an id that is not a stack id, deploy, start, stop, status, and remove make no request and say `Send --id set to the compose stack id.`
107
123
 
108
124
  ### storage
109
125
 
@@ -113,26 +129,48 @@ Without `--id`, or with an id that is not a stack id, deploy, start, and stop ma
113
129
 
114
130
  `astris storage show --id <id>` calls `GET /v1/buckets/:id`. Human output is the id, the name, the endpoint, the bucket, the access key id, the status, the created_at, and the updated_at, one per line; a missing access key id skips its line. It never prints the secret key. `--json` prints one JSON object `{"bucket":{...}}`.
115
131
 
132
+ `astris storage rotate --id <id>` calls `POST /v1/buckets/:id/key` and sends no body. The route revokes the old key and issues a new one. Human output is the bucket id, the name, the endpoint, the bucket, the access key id, and the new secret key, one value per line; the secret key is printed on this command only, so save it now. `--json` prints one JSON object of the response body: the bucket row and the credentials. A missing or malformed `--id` is refused and makes no request.
133
+
116
134
  `astris storage delete --id <id> --yes` calls `DELETE /v1/buckets/:id` and sends no body. Without `--yes` it makes no request and says the bucket and its objects are removed and that `--yes` confirms. A repeated `--id` is refused and makes no request. A bucket that still holds objects is refused with `That bucket is not empty. Remove its objects, then try again.` Human output is `Bucket removed.` `--json` prints one JSON object `{"bucket_id":"<id>","removed":true}`.
117
135
 
118
- When storage is not configured, all four commands print `Storage is not available right now. Try again later.` When the account has reached its bucket limit, `create` prints the API's message, `This account has reached its bucket safety limit. Contact support.` When new buckets cannot be made at all, `create` prints the API's message, `New storage buckets are not available right now.` Other refusals reuse the shared sentences below, and on a plan refusal `create` prints the API's own message.
136
+ When storage is not configured, all five commands print `Storage is not available right now. Try again later.` When the account has reached its bucket limit, `create` prints the API's message, `This account has reached its bucket limit. Delete a bucket you no longer need, then try again.` When new buckets cannot be made at all, `create` prints the API's message, `New storage buckets are not available right now.` Other refusals reuse the shared sentences below, and on a plan refusal `create` prints the API's own message.
119
137
 
120
138
  ### list
121
139
 
122
140
  `astris list apps` calls `GET /v1/apps`. `astris list databases` calls `GET /v1/databases`. `astris list projects` calls `GET /v1/projects`. Human output is one line per row, in the order the API returned. An app line is the id, the name, and the project name. A database line is the id, the kind, the name, and the project name. A project line is the id and the name. For apps and databases `--json` prints the response body. For projects `--json` prints an object whose `projects` rows each carry `id`, `name`, and `created_at`.
123
141
 
142
+ `astris list projects --id <id>` calls `GET /v1/projects/:id`, the one project read. Human output is the id and the name on one line. `--json` prints one JSON object `{"project":{"id":"<id>","name":"<name>","created_at":"<created_at>"}}`. With `apps` or `databases`, or with an `--id` that is not a project id, the command is refused and makes no request.
143
+
144
+ ### project
145
+
146
+ `astris project create <name>` calls `POST /v1/projects` with `{ "name": "<name>" }`. The name is trimmed. A missing name, a name that is empty after trim, and an extra word are refused and make no request. Human output is the new project id. `--json` prints the response body. A refusal prints the API's message, or its error word when the body has no message, and exits 1. A key bound to a project is refused with the API's error word `project_scope`.
147
+
148
+ `astris project list` calls `GET /v1/projects`. Human output is one line per project, in the order the API returned: the id and the name. An empty list prints nothing. `--json` prints the response body. A refusal prints the API's message, or its error word when the body has no message, and exits 1. An extra word is refused and makes no request.
149
+
150
+ `astris project delete --id <id> --yes` calls `DELETE /v1/projects/:id` and sends no body. Without `--yes` it makes no request and says the project is removed and that `--yes` confirms. A repeated `--id` is refused and makes no request. Human output is `Project removed.` `--json` prints one JSON object `{"project_id":"<id>","removed":true}`. A project that still holds an application, a database, or a compose stack is refused with the API's own sentence, and nothing is removed.
151
+
124
152
  ### logs
125
153
 
126
- `astris logs <app-id>` calls `GET /v1/apps/:id/logs`. `--source runtime` or `--source deploy` selects the log. Omit `--source` and the route uses runtime. `--tail <n>` sets how many lines, at most 500. Omit `--tail` and the route uses its own default. Human output is the log text. `--json` prints the response body.
154
+ `astris logs <app-id>` calls `GET /v1/apps/:id/logs`. `--source runtime` or `--source deploy` selects the log. Omit `--source` and the route uses runtime. `--tail <n>` sets how many lines, at most 500. Omit `--tail` and the route uses its own default. `--deployment <n>` sets which deploy to read, 1 to 5. It needs `--source deploy`; `--source runtime` without it, or a value outside 1 to 5, is refused and makes no request. Omit `--deployment` and the route uses 1. Human output is the log text. `--json` prints the response body.
127
155
 
128
156
  ### usage
129
157
 
130
- `astris usage` calls `GET /v1/usage`. It takes no flags besides `--json`. `--json` prints the response body. When `plan` is null, human output is `Plan: none` and `RAM used: <n> MiB`. When `plan` is not null, human output is `Plan: <name>`, `RAM used: <n> MiB of <pool> MiB`, and `RAM left: <n> MiB`. CPU and disk are not shown.
158
+ `astris usage` calls `GET /v1/usage`. It takes no flags besides `--json`. `--json` prints the response body. When `plan` is null, human output is `Plan: none`, `RAM used: <n> MiB`, `CPU used: <n> mCPU`, and `Disk used: <n> GB`, with no left line and no ` of `. When `plan` is not null, human output is `Plan: <name>`, `RAM used: <n> MiB of <pool> MiB`, `RAM left: <n> MiB`, `CPU used: <n> mCPU of <pool> mCPU`, `CPU left: <n> mCPU`, `Disk used: <n> GB of <pool> GB`, and `Disk left: <n> GB`. CPU and disk are the share admission counts for this account's applications. A database or a compose stack adds nothing to those sums. An application is counted even when it was saved before CPU limits existed.
131
159
 
132
160
  ### plans
133
161
 
134
162
  `astris plans` calls `GET /v1/plans`. It takes no flags besides `--json`. `--json` prints the response body. Human output is one line per plan, in the order the route returns them, with no header: `<name>: RAM <ram_mb> MiB, CPU <cpu_milli> mCPU, disk <disk_gb> GB`.
135
163
 
164
+ A plan whose `on_sale` is `false` ends with ` (not on sale yet)`; a response without `on_sale` prints no marker.
165
+
166
+ ### billing
167
+
168
+ `astris billing checkout --plan <name>` calls `POST /v1/billing/checkout` with `{"plan":"<name>"}` and prints the Checkout URL on one line. `<name>`, after trim, is `starter`, `growth`, `pro`, `scale`, or `business`. `scale` and `business` are sent to the route. The command prints the route's sentence and does not decide which plans are on sale. A missing `--plan`, a repeated `--plan`, or any other name is refused before any request. `--json` prints the response body.
169
+
170
+ `astris billing portal` calls `POST /v1/billing/portal` and sends no body. It prints the Customer Portal URL on one line. `--json` prints the response body.
171
+
172
+ A refusal is one sentence and exits 1. With `--json`, that sentence is one object `{"error":"<sentence>"}` on stdout.
173
+
136
174
  ### whoami
137
175
 
138
176
  `astris whoami` calls `GET /v1/me`. It takes no flags besides `--json`. `--json` prints the account id, email, status, plan, and created_at, and the key id, name, and key_prefix. Human output is three lines: `Account: <email>`, `Plan: <plan>` (`Plan: none` when plan is null), and `Key: <name> (<key_prefix>)`.
@@ -153,9 +191,23 @@ When storage is not configured, all four commands print `Storage is not availabl
153
191
 
154
192
  `astris app remove --app <id> --confirm <name>` calls `GET /v1/apps/<id>` and then `DELETE /v1/apps/<id>`. `--confirm` must equal the application name exactly; without `--confirm` the sentence is `Send --confirm set to the application name.` A different name is refused with `The name does not match the application.` and nothing is deleted. Human output is `App removed.` `--json` prints one JSON object `{"app_id":"<id>","removed":true}`.
155
193
 
194
+ `astris app start --app <id>` calls `POST /v1/apps/<id>/start` and sends no body. Human output is `App started.` `--json` prints one JSON object `{"app_id":"<id>","started":true}`. A missing or malformed `--app` makes no request.
195
+
196
+ `astris app stop --app <id>` calls `POST /v1/apps/<id>/stop` and sends no body. Human output is `App stopped.` `--json` prints one JSON object `{"app_id":"<id>","stopped":true}`. A missing or malformed `--app` makes no request.
197
+
198
+ ### new
199
+
200
+ `astris new <template> [dir]` writes a starter template into `<dir>`, or into `./<template>` when no directory is given. The directory must be new or empty. Otherwise the command writes nothing and prints `That directory is not empty. Choose a new or empty directory.` The command needs no key and makes no request: the templates are inside the package.
201
+
202
+ The templates today are `fastapi-postgres`, `go-dockerfile`, `nextjs-postgres` and `node-worker-cron`. A name that is not a template, or no name, prints `Name one of these templates: <names>.` A flag or a third word is refused with `Those arguments were refused. Check the flags and send them again.`
203
+
204
+ After the files are written, the command prints `Wrote <n> files to <dir>.` and the next steps: push the folder to a Git repository of your own, `astris deploy`, `astris database create`, then `astris env` and `astris deploy --app`.
205
+
206
+ Run from a checkout of this repository, the command has no templates and prints `This install has no templates. Run the CLI from npm: npx @astris-labs/cli new <template>.` `npm pack` and `npm publish` build `templates.json` from the repository's `templates/` folder first and remove it after.
207
+
156
208
  ### preflight
157
209
 
158
- `astris preflight [dir]` calls `POST /v1/preflight` with the files it finds under `dir` (the current directory when `dir` is omitted) and prints the verdict and one line per check with its reason. It sends no file or folder whose name starts with a dot (so no .env file and nothing under .git), nothing under node_modules, dist, build, out or coverage, no binary file, and nothing outside dir. Files the request cannot hold are left out and counted on stderr. `--json` prints one JSON object of the response body.
210
+ `astris preflight [dir]` calls `POST /v1/preflight` with the files it finds under `dir` (the current directory when `dir` is omitted) and prints the verdict and one line per check with its reason. It sends a root file named `.env` or starting with `.env.` (so `.env`, `.env.local`, `.env.example`, `.env.sample` and `.env.template`) when the root `.gitignore` does not ignore it, so a `.env` the builder committed to Git is posted and the Secrets check can catch a leaked key. It reads only the root `.gitignore`, once, before the walk; a nested `.gitignore` and the global ignore file are not read. A `.env` in a subfolder and `.envrc` are never sent. With no root `.gitignore`, nothing is ignored and a root `.env` is sent. A `.gitignore` the client cannot read as a plain file fails closed, so a root `.env` is treated as ignored and is not sent. It sends no other file or folder whose name starts with a dot (so nothing under `.git`), nothing under node_modules, dist, build, out or coverage, no binary file, and nothing outside dir. Files the request cannot hold are left out and counted on stderr. `--json` prints one JSON object of the response body.
159
211
 
160
212
  ### export
161
213
 
@@ -166,6 +218,8 @@ When storage is not configured, all four commands print `Storage is not availabl
166
218
 
167
219
  Exit code 0 means the command succeeded. Any error exits with a non-zero code. With `--json`, an error that happens after the key check is one object `{"error":"<sentence>"}` on stdout. A missing key and an unknown command write their sentence to stderr.
168
220
 
221
+ With `--wait`, `done` exits 0; `error`, `cancelled`, and `still running, check status` exit 1.
222
+
169
223
  `astris preflight` exits 0 when the verdict is `pass` or `fixable`, 1 when it is `refuse`, and 2 when it could not run. `astris export` exits 0 when the server accepts the request and 2 when it could not run.
170
224
 
171
225
  ## Not in this version
@@ -176,23 +230,27 @@ Exit code 0 means the command succeeded. Any error exits with a non-zero code. W
176
230
 
177
231
  Some arguments are refused on this machine, before any request.
178
232
 
179
- The command name is checked first. The words `help`, `--help`, and `-h` print the usage and the command names, exit 0, and make no request. `deploy`, `status`, `env`, `domains`, `restart`, `logs`, `rollback`, `diagnose`, `database`, `storage`, `compose`, `list`, `usage`, `plans`, `whoami`, `next`, `login`, `logout`, `app`, `preflight`, and `export` are known. Any other name writes `Unknown command.` and makes no request.
233
+ The command name is checked first. The words `help`, `--help`, and `-h` print the usage and the command names, exit 0, and make no request. `deploy`, `status`, `env`, `domains`, `restart`, `logs`, `rollback`, `diagnose`, `database`, `storage`, `compose`, `list`, `project`, `usage`, `plans`, `billing`, `whoami`, `next`, `login`, `logout`, `app`, `preflight`, `export`, and `new` are known. Any other name writes `Unknown command.` and makes no request.
180
234
 
181
235
  Login asks for the key on a terminal and reads it from stdin otherwise. Logout deletes that one file. Every other known command uses this sentence: When `ASTRIS_API_KEY` is set to a non-empty value, that value is the key, and the saved file is used only when the variable is missing or empty. A missing or empty variable and a missing or empty file write `ASTRIS_API_KEY is not set` to stderr and make no request. That is also true when `--json` is set.
182
236
 
183
237
  `login` and `logout` take no arguments. An argument is refused, and `logout` does not delete the file.
184
238
 
185
- After the key, `deploy`, `status`, `env`, `domains`, `restart`, `diagnose`, `database`, `storage`, `compose`, and `next` refuse a flag they do not know, a flag with no value, and a bare word that is not a flag. The sentence is `Those arguments were refused. Check the flags and send them again.`
239
+ `mcp` reads the key with the function the other commands use: a non-blank `ASTRIS_API_KEY` is the key; otherwise the saved file, trimmed. A missing key writes `Run astris login.` to stderr, exits 1, and makes no request. `mcp` takes no arguments and ignores any. Blank lines are skipped. A line that is not JSON gets a `Parse error` reply on stdout and no request. Every other line is posted unchanged, as the request body, to `https://api.astris.run/mcp`, with the key as a Bearer token. The command exits 0 when stdin ends.
240
+
241
+ After the key, `deploy`, `status`, `env`, `domains`, `restart`, `diagnose`, `database`, `storage`, `compose`, `project`, and `next` refuse a flag they do not know, a flag with no value, and a bare word that is not a flag. The sentence is `Those arguments were refused. Check the flags and send them again.`
242
+
243
+ `diagnose` requires `--app` set to an application id, the same check as `status`. `database` takes one word, `create`, `status`, `backup`, `backups`, or `delete`. `create` requires `--project`, `--kind`, and `--name`. `--project` is a project id. `--kind` is exactly `postgres` or `redis`. `status`, `backup`, and `backups` each require `--id` set to a database id. `delete` requires `--id` set to a database id and `--yes`, and a repeated `--id` is refused with `Those arguments were refused. Check the flags and send them again.` and makes no request.
186
244
 
187
- `diagnose` requires `--app` set to an application id, the same check as `status`. `database` takes one word, `create`, `status`, `backup`, or `delete`. `create` requires `--project`, `--kind`, and `--name`. `--project` is a project id. `--kind` is exactly `postgres` or `redis`. `status` and `backup` each require `--id` set to a database id. `backup` is accepted. `delete` requires `--id` set to a database id and `--yes`, and a repeated `--id` is refused with `Those arguments were refused. Check the flags and send them again.` and makes no request. The plural `backups` is refused and makes no request.
245
+ `storage` takes one word, `create`, `list`, `show`, `rotate`, or `delete`. `create` requires `--name`. `show`, `rotate`, and `delete` each require `--id` set to a bucket id, and a `--id` that is not a bucket id makes no request. `delete` requires `--yes`, and a repeated `--id` is refused with `Those arguments were refused. Check the flags and send them again.` and makes no request. `list` takes no word, so `storage list buckets` is refused and makes no request.
188
246
 
189
- `storage` takes one word, `create`, `list`, `show`, or `delete`. `create` requires `--name`. `show` and `delete` each require `--id` set to a bucket id, and a `--id` that is not a bucket id makes no request. `delete` requires `--yes`, and a repeated `--id` is refused with `Those arguments were refused. Check the flags and send them again.` and makes no request. `list` takes no word, so `storage list buckets` is refused and makes no request.
247
+ `billing` takes one word, `checkout` or `portal`. `checkout` requires one `--plan` whose value, after trim, is `starter`, `growth`, `pro`, `scale`, or `business`. A missing `--plan`, a repeated `--plan`, and any other name are refused and make no request. `portal` takes no flag besides `--json`.
190
248
 
191
- `compose` takes one word, `list`, `deploy`, `start`, or `stop`. `deploy`, `start`, and `stop` each require `--id` set to a compose stack id, and a repeated `--id` is refused with `Those arguments were refused. Check the flags and send them again.` and makes no request. `list` rejects `--id`.
249
+ `compose` takes one word, `list`, `deploy`, `start`, `stop`, `status`, or `remove`. `deploy`, `start`, `stop`, and `status` each require `--id` set to a compose stack id, and a repeated `--id` is refused with `Those arguments were refused. Check the flags and send them again.` and makes no request. `remove` requires that `--id` and a `--confirm` that is not empty; without `--confirm` it makes no request. `list` rejects `--id`.
192
250
 
193
251
  `next` takes no word and no flag besides `--json`.
194
252
 
195
- `domains` binds with no word, or verifies with the one word `verify`. `--hostname` with `--slug` is refused. An empty `--hostname`, or one containing a space, is refused before any request. Other hostname rules are left to the API. `verify` requires `--app` set to an application id and `--id` set to a domain id; a missing or non-UUID `--id`, and a word other than `verify`, make no request.
253
+ `domains` binds with no word, verifies with the one word `verify`, reads with the one word `status`, or removes with the one word `remove`. `--hostname` with `--slug` is refused. An empty `--hostname`, or one containing a space, is refused before any request. Other hostname rules are left to the API. `verify` and `status` each require `--app` set to an application id and `--id` set to a domain id; a missing or non-UUID `--id`, and a word other than `verify`, `status`, or `remove`, make no request. `remove` requires the same two ids and `--yes`, and a repeated `--id` is refused with `Those arguments were refused. Check the flags and send them again.` and makes no request.
196
254
 
197
255
  `--app` is refused unless the value, after trim, is an id of eight, four, four, four, and twelve hexadecimal digits, with hyphens, in either case. The sentence is `Send --app set to the application id.` `status`, `env`, `domains`, and `restart` require that id. `deploy` requires it when `--app` is present or when `--hook` is sent (`--hook` without `--app` is refused with `Send --app set to the application id.`), and then refuses `--build`, `--publish-directory`, `--spa`, `--kind`, `--command`, and `--schedule`; the other create flags are ignored.
198
256
 
@@ -202,6 +260,6 @@ After the key, `deploy`, `status`, `env`, `domains`, `restart`, `diagnose`, `dat
202
260
 
203
261
  `rollback` is refused after the key check. The sentence is `rollback is not available in this version.` It makes no request.
204
262
 
205
- `logs` takes one application id. With no id, or an id that is not an application id, the sentence is `Send the application id.` `list` takes one word, `apps`, `databases`, or `projects`. A missing word or any other word is refused. Neither command makes a request in those cases.
263
+ `logs` takes one application id. With no id, or an id that is not an application id, the sentence is `Send the application id.` `list` takes one word, `apps`, `databases`, or `projects`. A missing word or any other word is refused. Only `projects` takes `--id`. `project` takes one word, `create`, `list`, or `delete`. `create` takes the project name as the next word; a missing name, a name that is empty after trim, and an extra word are refused and make no request. `list` takes no further word; an extra word is refused and makes no request. `delete` requires `--id` set to a project id and `--yes`; a missing or non-UUID `--id`, a repeated `--id`, and any other word are refused and make no request. None of those commands makes a request in those cases.
206
264
 
207
265
  Ownership, names, limits, and what an account is allowed to do are rules in the API. This CLI does not decide them.
package/package.json CHANGED
@@ -1,12 +1,17 @@
1
1
  {
2
2
  "name": "@astris-labs/cli",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
+ "mcpName": "run.astris/astris",
4
5
  "description": "The Astris CLI, a command-line client of the Astris API.",
5
6
  "repository": {
6
7
  "type": "git",
7
8
  "url": "git+https://github.com/astris-labs/astris-api.git",
8
9
  "directory": "cli"
9
10
  },
11
+ "scripts": {
12
+ "prepack": "node src/new.mjs --bundle",
13
+ "postpack": "node src/new.mjs --unbundle"
14
+ },
10
15
  "bin": {
11
16
  "astris": "./src/main.mjs"
12
17
  },
@@ -15,6 +20,9 @@
15
20
  "src/client.mjs",
16
21
  "src/commands.mjs",
17
22
  "src/main.mjs",
23
+ "src/mcp-bridge.mjs",
24
+ "src/new.mjs",
25
+ "templates.json",
18
26
  "README.md"
19
27
  ],
20
28
  "engines": {
package/src/client.mjs CHANGED
@@ -18,6 +18,22 @@ function swallow(promise) {
18
18
  promise.catch(() => undefined);
19
19
  }
20
20
 
21
+ function retryAfterSecondsOf(headers) {
22
+ if (!headers) return null;
23
+ let raw;
24
+ if (typeof headers.get === "function") {
25
+ raw = headers.get("retry-after");
26
+ } else if (typeof headers === "object") {
27
+ raw = headers["retry-after"] ?? headers["Retry-After"];
28
+ }
29
+ if (typeof raw !== "string") return null;
30
+ const trimmed = raw.trim();
31
+ if (!/^[0-9]+$/.test(trimmed)) return null;
32
+ const seconds = Number(trimmed);
33
+ if (seconds < 1 || seconds > 3600) return null;
34
+ return seconds;
35
+ }
36
+
21
37
  export async function astrisFetch({ key, baseUrl, path, method, body, fetchImpl }) {
22
38
  const url = baseUrl + path;
23
39
  const headers = { Authorization: `Bearer ${key}` };
@@ -54,14 +70,15 @@ export async function astrisFetch({ key, baseUrl, path, method, body, fetchImpl
54
70
  }
55
71
  swallow(reading);
56
72
  const text = await Promise.race([reading, timeout]);
73
+ const retryAfterSeconds = retryAfterSecondsOf(response?.headers);
57
74
 
58
75
  if (text === "") {
59
- return { status: response.status, body: null };
76
+ return { status: response.status, body: null, retryAfterSeconds };
60
77
  }
61
78
  try {
62
- return { status: response.status, body: JSON.parse(text) };
79
+ return { status: response.status, body: JSON.parse(text), retryAfterSeconds };
63
80
  } catch {
64
- return { status: response.status, body: null };
81
+ return { status: response.status, body: null, retryAfterSeconds };
65
82
  }
66
83
  } catch (err) {
67
84
  throw new Error(redact(failureMessage(err), key));