@shardflux/cli 0.8.0 → 0.9.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/CHANGELOG.md CHANGED
@@ -1,9 +1,112 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0 — 2026-10-04 (not yet published)
4
+
5
+ - `shard ws hint <workspace>` explicitly stages a parked workspace ahead of a VM tool call, without waiting for restore or resuming a suspended workspace.
6
+
3
7
  Every command and option the README shows is available from the version named here. Breaking changes ship in minor
4
8
  releases and are marked **Breaking**.
5
9
 
6
- ## 0.8.0 (not yet published)
10
+ ## 0.9.0 (not yet published)
11
+
12
+ Needs `@shardflux/sdk` 0.14.0 or later.
13
+
14
+ ### Sign up from an agent
15
+
16
+ - `shard signup [--email <email>] [--name <name>] [--no-codex] [--no-setup] [--tool <tool>...]`: creates the account,
17
+ signs in and runs `shard setup` in one command, with no browser and no password. With the Codex CLI signed in with
18
+ ChatGPT on the machine, Codex refreshes its login (`codex app-server`) and the ID token from `$CODEX_HOME/auth.json`
19
+ verifies the email (the account starts with its trial; an existing account of that identity or email is signed
20
+ in). Without one, `--email` creates an account that works at once on the Free plan. `--json`: `{created, access,
21
+ method, user, setup}`.
22
+ - `shard verify [<code>]`: emails a 6-digit verification code (and asks for it on a terminal), or confirms one.
23
+ Nothing is signed out; an account from `shard signup` gets its trial.
24
+ - `shard auth login --codex` signs in with the Codex login; `shard auth step-up --codex` confirms a sensitive action
25
+ with it instead of the password.
26
+ - After signing in with an unverified email, the next step printed is `shard verify`.
27
+
28
+ ### Workspaces working at once
29
+
30
+ - A command whose workspace call finds every workspace the plan allows to work at once busy is sent again after
31
+ `Retry-After` (the SDK's retries; the call did not run). A refusal that lasts exits 1 with 429 `quota_exceeded`
32
+ (`details.limit` `concurrent_workspaces`) and the hint `every workspace your plan allows to work at once is busy
33
+ and the call did not run (it was already retried); retry in 2 s, or upgrade: shard billing plans`.
34
+ - The API's 403 `quota_exceeded` with `details.limit` `retained_state` (opening a new key or forking while the
35
+ plan's Retained state is used up) hints `delete workspaces you no longer need (shard ws delete <key> --yes), or
36
+ upgrade: shard billing plans`. Neither suggests `shard feedback`.
37
+ - `--json` errors carry `retry_after_seconds` when the refusal has `Retry-After` (e.g. 429 `quota_exceeded`, 429
38
+ `rate_limited`).
39
+ - `shard usage` says `retained state used up: new workspaces and forks are refused (...)` when the Retained state
40
+ allowance's `cap_state` is `storage_blocked` (`enforcement` `storage_block`).
41
+
42
+ ### Inbound ports
43
+
44
+ Additive. Serve a port of a processful workspace at its own private HTTPS URL; a request wakes a parked or suspended
45
+ workspace.
46
+
47
+ - `shard ws port expose <id|key> <port>`: the URL and how requests get in (`--json`: the API's port object
48
+ `{port, url, created_at, callback}`). Idempotent.
49
+ - `shard ws port ls <id|key>`: port, URL, exposure time and callback URL (`--json`: `{ports}`).
50
+ - `shard ws port close <id|key> <port>` (`--json`: `{workspace_id, port, closed}`).
51
+ - `shard ws port token <id|key> <port> [--ttl 1m..24h]`: the port token alone on stdout, how to send it on stderr
52
+ (`--json`: `{token, expires_at, url}`).
53
+ - `shard ws port link <id|key> <port> [--ttl 1m..7d] [--path /...]`: a URL that opens the port in a browser
54
+ (`--json`: `{url, expires_at}`).
55
+ - `shard ws port callback <id|key> <port> [--revoke]`: the port's callback URL for webhooks and OAuth redirects, shown
56
+ once (`--json`: `{url, created_at}`; with `--revoke`: `{workspace_id, port, revoked}`).
57
+ - `ports` is an alias of `port`. A port outside 1-65535, a `--ttl` out of range or not in whole seconds, and a
58
+ `--path` without a leading `/` exit 2 before any request; a file-first workspace is refused before any port
59
+ request. Hints for `port_not_exposed` (expose it first) and `port_limit` (close a port first).
60
+
61
+ ### Memory across platform upgrades
62
+
63
+ - The cold-boot note and `--json --timing` name the new reason `runtime_retired` (the runtime the workspace was
64
+ suspended on was retired after an announced window): `note: workspace <id> resumed from its saved disk; running
65
+ processes were restarted (runtime_retired)`.
66
+
67
+ ### Resize a workspace
68
+
69
+ - `shard ws resize <id|key> [--memory MIB] [--held MIB] [--fixed|--elastic] [--cpu MILLIS] [--disk GIB]
70
+ [--timeout D] [--timing]`: changes the caps of a running, suspended or stopped workspace without a restart
71
+ (`PATCH /v1/workspaces/{id}/caps` through `workspace.resize()`), and waits until the resize has finished. It prints
72
+ per resource the change and when it applies, e.g. `memory 2048 → 6144 MiB now` or `memory 4096 → 8192 MiB
73
+ elastic, held 1024 MiB on resume`, with what was clamped to a limit or applied in part. `--json` prints the result
74
+ (`workspace_id`, `operation_id`, `state`, the `memory` / `cpu` / `disk` named, `caps`, and `operation` when one was
75
+ waited for). No resource flag, `--fixed` with `--elastic`, or `--held` with `--fixed` are usage errors (exit 2) with
76
+ no request. Ctrl-C names the operation, which continues server side.
77
+
78
+ ### Memory hint for commands
79
+
80
+ - `shard ws exec --resource-hint auto|light|heavy` and `shard ws exec-start --resource-hint …` send `resource_hint`
81
+ with the exec start: `heavy` gives an elastic workspace its memory before the command starts, `light` starts it at
82
+ once, `auto` (the default) decides from the command. Without the flag nothing is sent. On a file-first workspace
83
+ `--resource-hint` is a usage error (exit 2).
84
+
85
+ ### Elastic memory
86
+
87
+ - `--memory-held` on `shard ws open` / `shard ws fork` no longer needs `--allocation-mode elastic`: without a mode the
88
+ API decides (with elastic-by-default enabled for the organization a new workspace is elastic; otherwise
89
+ `requires_elastic`). With `--allocation-mode fixed` it stays a usage error.
90
+
91
+ ## 0.8.1 (not yet published)
92
+
93
+ Needs `@shardflux/sdk` 0.13.1 or later.
94
+
95
+ ### Workspaces recover by themselves
96
+
97
+ When the machine a workspace runs on fails, the workspace is suspended, and the next command on it resumes it.
98
+
99
+ - A resume that booted the workspace's own disk (`cold_boot_reason` `host_lost`) prints the cold-boot note with the
100
+ reason in words: `note: workspace <id> resumed from its saved disk; running processes were restarted (the machine it
101
+ ran on failed)`.
102
+ - A resume that restored its newest checkpoint prints `note: workspace <id> resumed from checkpoint <id> (state as of
103
+ <time>) after the machine it ran on failed; changes after that time are not in it` on stderr (also with `--json`,
104
+ `ws resume --wait`, `operations wait` and a command's wake).
105
+ - `--timing --json`: `server.host_lost` (`detected_at`, `restored_from`, `restored_checkpoint_id`, `state_as_of`).
106
+ `--timing` prints the SDK's `restored checkpoint <id> (host_lost; state as of <time>)`.
107
+ - `hostLostNotice()` is exported next to `coldBootNotice()`.
108
+
109
+ ## 0.8.0
7
110
 
8
111
  Needs `@shardflux/sdk` 0.13.0 or later.
9
112
 
package/README.md CHANGED
@@ -37,14 +37,11 @@ Or run it without installing: `npx @shardflux/cli --help`. The unscoped
37
37
 
38
38
  ## Quick start
39
39
 
40
- No account yet? Everything happens in the terminal; a person only opens the emailed link (or hands
41
- it to `shard`) and, for a paid plan, pays in Stripe Checkout.
40
+ No account yet? One command (0.9.0+) creates it, signs you in and saves an API key: no browser, no
41
+ password. With the Codex CLI signed in with ChatGPT on this machine, your email is verified by it.
42
42
 
43
43
  ```sh
44
- printf '%s\n' "$PASSWORD" | shard auth register --email you@example.com --name "Ada Lovelace"
45
- shard auth verify-email 'https://app.shardflux.dev/auth/verify-email#token=...' # the link from the email
46
- printf '%s\n' "$PASSWORD" | shard auth login --email you@example.com
47
- shard setup # organization, project and an API key, saved
44
+ shard signup # or: shard signup --email you@example.com
48
45
  shard workspaces open acme/demo --template python-node-browser
49
46
  shard ws exec acme/demo -- python3 -c 'print(40 + 2)'
50
47
  shard files write acme/demo /home/user/notes.txt --from notes.txt
@@ -111,14 +108,14 @@ npm test 2>&1 | tail -n 60 | shard feedback --category bug --workspace acme/demo
111
108
  `shard` is built to be driven by an agent: no browser, no prompts when standard input is not a
112
109
  terminal, `--json` on every command, secrets only on standard input, and documented exit codes.
113
110
 
114
- 1. **Account:** `printf '%s\n' "$PASSWORD" | shard auth register --email <email>` (answers the same
115
- whether or not the address exists). Ask the person for the link in the verification email and run
116
- `shard auth verify-email '<link>'` (the whole link, quoted).
117
- 2. **Sign in:** `printf '%s\n' "$PASSWORD" | shard auth login --email <email> --json`. With two-factor
118
- authentication it exits 4 with `mfa_required`: ask the person for the code, then
119
- `shard auth mfa --code <code>` (within 10 minutes), or pass `--code` to `login`.
120
- 3. **Set up:** `shard setup --json`: an organization, a project and an API key with every workspace
121
- tool, saved in the credentials file. Idempotent: run it again any time.
111
+ 1. **Account (0.9.0+):** `shard signup --json`. With the Codex CLI signed in with ChatGPT on the
112
+ machine it signs up (or signs in) verified by that login; otherwise pass `--email <email>` and the
113
+ account works at once on the Free plan. It then runs `shard setup`: an organization, a project and
114
+ an API key with every workspace tool, saved in the credentials file. With two-factor authentication
115
+ it exits 4 with `mfa_required`: ask the person for the code, then `shard auth mfa --code <code>`.
116
+ 2. **Verify the email** (an `--email` signup, to start the trial): `shard verify --json` emails a
117
+ 6-digit code; ask the person for it, then `shard verify <code>`. Nothing is signed out.
118
+ 3. **Set up again any time:** `shard setup --json` is idempotent.
122
119
  4. **Work:** `shard ws open <key> --template <slug> --json`, `shard ws exec <key> -- <cmd>`,
123
120
  `shard files ...` use the saved key.
124
121
  5. **A paid plan:** `shard billing plans`, then `shard billing upgrade <plan> --json` prints the Stripe
@@ -132,12 +129,15 @@ terminal, `--json` on every command, secrets only on standard input, and documen
132
129
  7. **Feedback:** send `shard feedback` as you work, the moment something fails, confuses you or is
133
130
  missing, or your user asks for an improvement (see [Feedback](#feedback-agents-use-this-while-you-work)).
134
131
 
135
- What stays human: reading the verification email, entering a card in Stripe Checkout, and the
136
- authenticator app's codes.
132
+ What a person does: entering a card in Stripe Checkout, the authenticator app's codes, and (for an
133
+ `--email` signup) reading the 6-digit code.
137
134
 
138
135
  ## Accounts and sign-in (0.5.0+)
139
136
 
140
137
  ```sh
138
+ shard signup [--email you@example.com] [--name "Ada Lovelace"] [--no-codex] [--no-setup] # 0.9.0+
139
+ shard verify [<code>] # 0.9.0+: emails a 6-digit code, then confirms it
140
+ shard auth login --codex # 0.9.0+: sign in with the Codex login on this machine
141
141
  printf '%s\n' "$PASSWORD" | shard auth register --email you@example.com [--name "Ada Lovelace"]
142
142
  shard auth verify-email '<link from the email>' # or just the token in it
143
143
  shard auth resend-verification # signed in, email not verified yet
@@ -147,6 +147,7 @@ shard auth status # session and saved key,
147
147
  shard auth sessions [--revoke <session-id>] # browser and CLI sessions
148
148
  shard auth logout [--all]
149
149
  printf '%s\n' "$PASSWORD" | shard auth step-up [--code 123456]
150
+ shard auth step-up --codex [--code 123456] # 0.9.0+: confirm with the linked Codex login
150
151
  printf '%s\n%s\n' "$CURRENT" "$NEW" | shard auth password change
151
152
  shard auth password reset --email you@example.com
152
153
  printf '%s\n' "$NEW" | shard auth password reset-confirm '<link from the email>'
@@ -157,6 +158,12 @@ shard auth totp disable --code 123456
157
158
  shard auth totp recovery-codes
158
159
  ```
159
160
 
161
+ - **`shard signup`** (0.9.0+) asks the Codex CLI on this machine to refresh its ChatGPT login
162
+ (`codex app-server`) and sends the ID token from `$CODEX_HOME/auth.json` (default `~/.codex`), never
163
+ Codex's access or refresh token. The account is verified and starts with its trial; an account that
164
+ already uses that ChatGPT identity or email is signed in instead. Without a Codex login, `--email`
165
+ creates an account that works at once on the Free plan; `shard verify` starts the trial.
166
+ `--no-codex` uses `--email` even when Codex is signed in; `--no-setup` skips `shard setup`.
160
167
  - **Passwords never come from arguments** (`--password` is refused). On a terminal `shard` asks without
161
168
  echo (a new password twice); otherwise it reads standard input, one line per secret: `auth password
162
169
  change` reads the current password, then the new one. Emailed links and tokens are single-use
@@ -177,7 +184,7 @@ shard auth totp recovery-codes
177
184
  it from standard input (or asks), sets it, and otherwise prints the `auth password reset-confirm`
178
185
  command to run.
179
186
  - After `auth login`, `shard` prints who you are, whether the email is verified, your organizations
180
- and the next step (`shard setup`, or `shard auth verify-email`).
187
+ and the next step (`shard setup`, or `shard verify`).
181
188
 
182
189
  ## Setup (0.5.0+)
183
190
 
@@ -378,8 +385,8 @@ shows the status; `shard version --check` asks now (`--json`: `{"cli", "sdk", "u
378
385
  | `workspaces list [--state S] [--prefix P] [--lifetime persistent\|session\|any] [--purpose standard\|template_draft\|template_test\|any] [--include-deleted] [--all] [--limit N] [--cursor C]` | One page, or every page with `--all`. The next cursor is printed on stderr. By default only persistent standard workspaces are listed. A file-first workspace's state reads `running (file-first)` (0.5.0+). |
379
386
  | `workspaces get <id\|key>` | One workspace (deleted ones included), with the startup of its template's start commands and services (0.4.0+): a failed step shows its exit code, reason and output tail. Its mode, and a file-first workspace's tree revision (0.5.0+). Its memory (0.8.0+): `fixed 4096 MiB`, or `elastic: promised …, held …, plugged +… MiB`. The template version whose [immutable paths](#immutable-paths-080) it has mounted (0.8.0+): `immutable paths v3 (mounted read-only)`. |
380
387
  | `workspaces inputs <id\|key>` | The workspace's text inputs (0.4.0+). |
381
- | `workspaces exec <id\|key> [--cwd D] [--env K=V]... [--timeout T] [--stdin F\|-] [-v] [--execution-id ID] [--output-limit N] [--burst [--burst-vcpus N] [--burst-memory MIB]] -- <cmd> [args...]` | Runs argv with no shell. Output is streamed (in the start request itself, 0.8.0+) and resumes from byte offsets after dropped connections. `shard` exits with the command's exit code. Ctrl-C cancels the command. `-v` (0.5.0+) then prints the session id and exit status on stderr, and the memory grow the command waited for on an elastic workspace (0.8.0+; `--json`: `memory_grow`). `--cwd` is an absolute path (default `/home/user`); a relative one is refused (`invalid_cwd`, exit 2). A command that could not start (a `--cwd` that is not a directory, a program not on `PATH`) exits 6 with the workspace's reason and a hint (0.5.1+; before, it exited 1 with no output). On a file-first workspace (0.5.0+) the command runs as an execution instead: see [File-first workspaces](#file-first-workspaces). `--burst` (0.8.0+): [burst execution](#burst-execution-080). |
382
- | `workspaces exec-start <id\|key> [--session-id ID] [--cwd D] [--timeout T] [--stdin-open] -- <cmd> [args...]`, `workspaces exec-read <id\|key> <session-id> [--follow]`, `workspaces exec-cancel <id\|key> <session-id> [--grace D]` | Background commands (0.8.0+): start a command and return at once, read its output and state (`--follow` streams it to its exit and exits with its code; while it runs `exec-read` exits 5), stop it. See [Background commands](#background-commands-080). |
388
+ | `workspaces exec <id\|key> [--cwd D] [--env K=V]... [--timeout T] [--stdin F\|-] [-v] [--execution-id ID] [--output-limit N] [--resource-hint auto\|light\|heavy] [--burst [--burst-vcpus N] [--burst-memory MIB]] -- <cmd> [args...]` | Runs argv with no shell. Output is streamed (in the start request itself, 0.8.0+) and resumes from byte offsets after dropped connections. `shard` exits with the command's exit code. Ctrl-C cancels the command. `-v` (0.5.0+) then prints the session id and exit status on stderr, and the memory grow the command waited for on an elastic workspace (0.8.0+; `--json`: `memory_grow`). `--cwd` is an absolute path (default `/home/user`); a relative one is refused (`invalid_cwd`, exit 2). A command that could not start (a `--cwd` that is not a directory, a program not on `PATH`) exits 6 with the workspace's reason and a hint (0.5.1+; before, it exited 1 with no output). On a file-first workspace (0.5.0+) the command runs as an execution instead: see [File-first workspaces](#file-first-workspaces). `--burst` (0.8.0+): [burst execution](#burst-execution-080). `--resource-hint` (0.9.0+): [elastic memory](#elastic-memory-080). |
389
+ | `workspaces exec-start <id\|key> [--session-id ID] [--cwd D] [--timeout T] [--stdin-open] [--resource-hint auto\|light\|heavy] -- <cmd> [args...]`, `workspaces exec-read <id\|key> <session-id> [--follow]`, `workspaces exec-cancel <id\|key> <session-id> [--grace D]` | Background commands (0.8.0+): start a command and return at once, read its output and state (`--follow` streams it to its exit and exits with its code; while it runs `exec-read` exits 5), stop it. See [Background commands](#background-commands-080). |
383
390
  | `executions get <id\|key> <execution-id> [--wait D]` | The result of a file-first workspace's execution (0.5.0+): its output, then what ran. Exits with the command's code, 6 if the execution failed or was lost, 5 while it still runs. |
384
391
  | `files read <id\|key> <path> [--out F]` | Raw bytes to stdout, or to a local file. |
385
392
  | `files write <id\|key> <path> [--from F\|-] [--append] [--no-parents] [--if-revision N]` | Atomic write. The default source is stdin. `--if-revision` (0.5.0+): file-first workspaces only, see [Tree revisions](#tree-revisions). |
@@ -391,6 +398,7 @@ shows the status; `shard version --check` asks now (`--json`: `{"cli", "sdk", "u
391
398
  | `workspaces suspend <id\|key> --durable [--timeout T]` | (0.7.0+) Suspend and wait until the copy is in durable storage. See "Durable storage". |
392
399
  | `workspaces suspend <id\|key> --when-idle D` / `--cancel-when-idle` | (0.5.1+) Suspend once the workspace has been idle for `D` (0 to 1h; `0` = as soon as it is idle, 0.6.1+), or cancel that. See "Suspend when idle". |
393
400
  | `workspaces fork <id\|key> <new-key> [caps] [--allocation-mode fixed\|elastic] [--memory-held N] [--wait]` | Fork into a new key. Without caps the fork keeps the source's caps and memory allocation (0.8.0+). With `--wait` the API holds the fork until the copy runs and returns it ready in the same request (0.8.0+; `--timing` shows one `request` phase marked `(held)`). |
401
+ | `workspaces resize <id\|key> [--memory MIB] [--held MIB] [--fixed\|--elastic] [--cpu MILLIS] [--disk GIB] [--timeout 5m] [--timing]` | Change the memory, CPU and disk of a running or suspended workspace without a restart (0.9.0+). Prints per resource the change and when it applies (`now`, `on resume`, `next start`). See [Resize a workspace](#resize-a-workspace-090). |
394
402
  | `workspaces delete <id\|key> --yes [--wait]` | Delete the workspace. Tool access ends at once. After deletion finishes, the key can open a new workspace ID. |
395
403
  | `workspaces close <id\|key> [--wait]` | End a session workspace now: it is deleted, and the key then opens a new workspace. A persistent workspace is refused (`not_session`). |
396
404
  | `workspaces reset <id\|key> --yes [--wait]` | Layered workspaces: wipe every change and restart on the template. The previous state stays restorable for 7 days. |
@@ -398,6 +406,8 @@ shows the status; `shard version --check` asks now (`--json`: `{"cli", "sdk", "u
398
406
  | `workspaces changes <id\|key> [--path P] [--hash] [--summary] [--all] [--limit N] [--cursor C]` | What a layered, running workspace changed against its template. |
399
407
  | `workspaces sessions <id\|key>` | Attributed agent sessions: label, principal and tokens issued. |
400
408
  | `workspaces secrets <id\|key> [--set NAME]... [--clear]` | Show the secret names bound to the workspace and their status, or replace them. |
409
+ | `workspaces port expose <id\|key> <port>`, `workspaces port ls <id\|key>`, `workspaces port close <id\|key> <port>` | Serve a port of the workspace at its own private HTTPS URL, list the exposed ports, close one (0.9.0+; see [Inbound ports](#inbound-ports-090)). |
410
+ | `workspaces port token <id\|key> <port> [--ttl D]`, `workspaces port link <id\|key> <port> [--ttl D] [--path P]`, `workspaces port callback <id\|key> <port> [--revoke]` | A port token (printed alone), a link that opens the port in a browser, the port's callback URL for webhooks and OAuth redirects (0.9.0+). |
401
411
  | `secrets create <NAME> [--from-file F] [--description T] [--tool T]... [--allow-workspace ID]... [--org ID [--allow-project ID]... [--all-projects]]` | Create a secret. The value comes from standard input or `--from-file`, never from an argument. |
402
412
  | `secrets list [--org ID] [--include-deleted] [--all]`, `secrets get <id\|NAME>` | Secret metadata. Values are never shown. |
403
413
  | `secrets update <id\|NAME> [--description T] [--tool T]... [--allow-workspace ID]... [--any-workspace] [--allow-project ID]... [--all-projects] [--clear-projects]` | Change the description or usage permissions. |
@@ -607,11 +617,11 @@ without waking it (the state at suspension); `files search` then notes it on std
607
617
  refused at once (exit 1, `workspace_not_running`).
608
618
 
609
619
  **Detecting a cold resume (0.6.0+).** A resume restores memory and running processes. After a platform runtime
610
- update, a resume can boot from the saved disk instead of restoring memory (a cold boot), also when a command wakes the
611
- workspace; check `memory_restored`. The command still runs with the files as of the suspend, and processes start
612
- fresh, as after a reboot: start dev servers and background jobs again. `shard` then prints one line on stderr after
613
- the output (also with `--json`, except on a failure, whose stderr is only the error document), whether or not you
614
- pass `--timing`:
620
+ update, or after the machine the workspace ran on failed (0.8.1+), a resume can boot from the saved disk instead of
621
+ restoring memory (a cold boot), also when a command wakes the workspace; check `memory_restored`. The command still runs
622
+ with the files kept, and processes start fresh, as after a reboot: start dev servers and background jobs again. `shard`
623
+ then prints one line on stderr after the output (also with `--json`, except on a failure, whose stderr is only the
624
+ error document), whether or not you pass `--timing`:
615
625
 
616
626
  ```text
617
627
  note: workspace 01a0e5a8-3edd-74ba-b489-d62b8925e342 resumed from its saved disk; running processes were restarted (runtime_changed)
@@ -621,6 +631,12 @@ note: workspace 01a0e5a8-3edd-74ba-b489-d62b8925e342 resumed from its saved disk
621
631
  `cold_boot`, `memory_restored` false, `cold_boot_reason`), and `--timing` shows `resume from cold_boot: processes
622
632
  restarted (runtime_changed)`. The same applies to `operations wait` of such a resume.
623
633
 
634
+ **Workspaces recover by themselves (0.8.1+).** When the machine a workspace runs on fails, the workspace is suspended,
635
+ and the next command on it resumes it; there is nothing extra to run. From its own disk, the note above ends in
636
+ `(the machine it ran on failed)`; from its newest checkpoint, the note names the checkpoint and the time its state is
637
+ from (`state as of <time>`). `--json` carries `result.host_lost` (`detected_at`, `restored_from` `disk` or
638
+ `checkpoint`, `restored_checkpoint_id`, `state_as_of`), and `--timing` adds `host_lost` to `server`.
639
+
624
640
  ## Suspend when idle (0.5.1+)
625
641
 
626
642
  A running workspace is billed while it is awake. When an agent's turn ends, ask for a suspend once the workspace has
@@ -644,19 +660,40 @@ shard ws suspend acme/demo --cancel-when-idle
644
660
 
645
661
  ## Elastic memory (0.8.0+)
646
662
 
647
- Give a workspace a large memory promise while it holds only what it uses. It idles at `--memory-held` (default
648
- 1024 MiB) and grows before each command starts, so builds and test runners see the memory they need from their first
649
- instruction.
663
+ A workspace can hold only the memory its commands use and grow up to `--memory-mib` when they need more; billing counts
664
+ the memory it holds. It idles at `--memory-held` (default 1024 MiB). Package installs, test runners, compilers and
665
+ bundlers get their memory before they start, so they see what they need from their first instruction; everything else
666
+ starts at once and the workspace grows while it runs.
650
667
 
651
668
  ```sh
652
669
  shard ws open acme/demo --template python-node-browser --memory-mib 8192 --allocation-mode elastic
653
670
  shard ws get acme/demo # memory elastic: promised 8192 MiB, held 1024 MiB, plugged +3072 MiB
654
671
  shard ws exec acme/demo -v -- npm test # memory grow delivered 1024 -> 4096 MiB in 175 ms
672
+ shard ws exec acme/demo --resource-hint heavy -- python3 train.py # 0.9.0+: memory first, then the command
655
673
  ```
656
674
 
657
- The layout takes effect at the workspace's next start. `--json` output carries `memory` on the workspace and
675
+ `--allocation-mode fixed` gives a static allocation of `--memory-mib`. `--resource-hint` (0.9.0+) is `auto` (the
676
+ default: decided from the command), `heavy` or `light`. `--json` output carries `memory` on the workspace and
658
677
  `memory_grow` on a command that grew it.
659
678
 
679
+ ## Resize a workspace (0.9.0+)
680
+
681
+ Change the memory, CPU and disk of any workspace, running or suspended, fixed or elastic, without a restart:
682
+
683
+ ```sh
684
+ $ shard ws resize acme/demo --memory 6144 --disk 20
685
+ Resized acme/demo (0f6c…), running:
686
+ memory 2048 → 6144 MiB now
687
+ disk 10 → 20 GiB now
688
+
689
+ $ shard ws resize acme/demo --elastic --held 1024 # switch modes live
690
+ ```
691
+
692
+ Memory changes live, and disks grow online. A suspended workspace gets its new size when it resumes, before its first
693
+ call. CPU changes live within the vCPUs the workspace booted with; a larger CPU size applies at the next start. Each
694
+ resource says when it applies (`now`, `on resume` or `next start`), and the new caps are stored, so every later start
695
+ uses them. `--json` prints the result with `applies_at` per resource and the stored `caps`.
696
+
660
697
  ## Burst execution (0.8.0+)
661
698
 
662
699
  Run one heavy command, such as a cold build or a full test suite, on a larger VM without resizing the workspace:
@@ -689,6 +726,31 @@ shard ws exec-cancel acme/demo build-1 --grace 10s
689
726
  code and output.
690
727
  - `exec-cancel` sends SIGTERM, then SIGKILL after `--grace` (default 5s, at most 60s).
691
728
 
729
+ ## Inbound ports (0.9.0+)
730
+
731
+ Serve a port of a workspace at its own HTTPS URL: a preview of the app your agent is building, a webhook receiver, an
732
+ OAuth redirect, a live view. A request wakes a parked or suspended workspace and is answered once it runs. Every port
733
+ is private: a request carries a port token, a browser link's session, or arrives on the port's callback URL.
734
+
735
+ ```sh
736
+ shard ws exec-start acme/demo -- python3 -m http.server 3000 --bind 0.0.0.0
737
+ shard ws port expose acme/demo 3000 # https://3000-<handle>.shardflux.app
738
+ curl -H "Authorization: Bearer $(shard ws port token acme/demo 3000)" https://3000-<handle>.shardflux.app/
739
+ shard ws port link acme/demo 3000 --path /dashboard # open it in a browser
740
+ shard ws port callback acme/demo 3000 # register <url>webhooks/github with GitHub
741
+ shard ws port close acme/demo 3000
742
+ ```
743
+
744
+ - The server inside the workspace listens on `0.0.0.0`, not only `127.0.0.1`.
745
+ - `expose` is idempotent: an exposed port keeps its URL, tokens, links and callback URL. `port ls` lists them.
746
+ - `token` prints the token alone on stdout (`--ttl` 1m to 24h, default 1h). Send it as `Authorization: Bearer
747
+ <token>`, or as `X-Shardflux-Token` when the app reads `Authorization` itself.
748
+ - `link` prints a URL that starts a browser session for the port (`--ttl` 1m to 7d, default 24h) and lands on
749
+ `--path`. Anyone with the link can open the port until it expires.
750
+ - `callback` prints the callback URL once; a request to `<url><rest>` reaches `/<rest>` without a token. Running it
751
+ again replaces the URL; `--revoke` removes it.
752
+ - `close` stops the port's tokens, links and callback URL at once.
753
+
692
754
  ## Start deadlines
693
755
 
694
756
  A queued open, resume or fork (`capacity_pending`) has a deadline 15 minutes after it was created.
@@ -778,7 +840,7 @@ With `--json`, stdout carries the timing as a `timing` field of the command's JS
778
840
  `workspace_id`, `operation_id`, `total_ms`, `phases` (`phase`, `reason`, `operation_id`, `start_ms`, `duration_ms`),
779
841
  `retries`, `server` (`queued_ms`, `run_ms`, `total_ms`, `start_path`, `resume_path`, `memory_restored` and
780
842
  `cold_boot_reason` (0.6.0+; `memory_restored` is `false` when a resume booted the saved disk, `null` when the API did
781
- not say), `boot_to_ready_ms`, ...) and `outside_server_ms`. It is `null` when the command made no lifecycle call. On a failure the error document on stderr
843
+ not say), `host_lost` (0.8.1+), `boot_to_ready_ms`, ...) and `outside_server_ms`. It is `null` when the command made no lifecycle call. On a failure the error document on stderr
782
844
  carries it: `{"error": {...}, "timing": {...}}`.
783
845
 
784
846
  For live progress events, use the SDK's `onProgress`.
@@ -827,6 +889,10 @@ printf %s "$NEW_KEY" | shard secrets rotate OPENAI_API_KEY
827
889
  - `secrets create`/`get`/`update` print the secret's metadata; `list`, `versions` and `access-log`
828
890
  print `{data, next_cursor}`; `rotate` prints `{secret, version}`; `delete` prints
829
891
  `{deleted, id, name, scope}`; `workspaces secrets` prints `{workspace_id, names, secrets}`.
892
+ - `workspaces port expose` prints the port `{port, url, created_at, callback}`; `port ls` prints `{ports}`;
893
+ `port token` prints `{token, expires_at, url}` (`jq -r .token`); `port link` prints `{url, expires_at}`;
894
+ `port callback` prints `{url, created_at}`; `port close` prints `{workspace_id, port, closed}` and
895
+ `port callback --revoke` prints `{workspace_id, port, revoked}` (0.9.0+).
830
896
  - Account commands (0.5.0+) print the API's objects too: lists are `{data, next_cursor}` (with
831
897
  `default_organization_id`, `default_project_id` or `saved_api_key_id` where a default applies);
832
898
  `auth login`/`auth mfa` print `{status, user, session: {expires_at, credentials_file}, organizations,
@@ -911,3 +977,7 @@ shard ws exec-input qm/project-42 qm-worker-1 --offset 5 --close
911
977
  ```
912
978
 
913
979
  Use the byte offset in each acknowledgement, including partial writes. `ws resume` can recover a failed workspace with its existing ID and disk. A completed delete frees the key; old IDs remain deleted. Protocol-error JSON includes `source`; HTTP 404 alone does not mean a workspace was deleted.
980
+
981
+ ### Prepare an imminent command (0.9.0+)
982
+
983
+ Call `shard ws hint <workspace>` ahead of a tool call to stage a parked workspace while the input is prepared. It returns without waiting for restore. Resume a suspended workspace first. The hint is explicit; ordinary commands add no early hint.
@@ -40,6 +40,30 @@ const VOLUME_WAIT = {
40
40
  timeout: { type: 'string', value: '<duration>', description: 'Give up waiting after this long (default 5m); the operation continues server side.' },
41
41
  };
42
42
  export const ACCOUNT_COMMANDS = [
43
+ // ---- agent signup (0.9.0+) -----------------------------------------------------------------------------
44
+ {
45
+ path: ['signup'],
46
+ summary: 'Create your account and get ready to run workspaces in one command: no browser, no password',
47
+ description: 'Signs up with the Codex login on this machine (ChatGPT): your email is verified by it, so the account starts with its trial, and an account that already uses that ChatGPT identity or email is signed in instead. Without a Codex login, pass --email: the account works at once on the Free plan, and "shard verify" starts the trial. Then it runs "shard setup" (organization, project and an API key, saved) unless --no-setup. Two-factor accounts: pass --code or type it at the prompt.',
48
+ positionals: [],
49
+ options: {
50
+ email: { type: 'string', value: '<email>', description: 'Your email address, used when there is no Codex login (or with --no-codex).' },
51
+ name: { type: 'string', value: '<display name>', description: 'Your name as others see it (setup names the organization after it).' },
52
+ codex: { type: 'boolean', description: 'Use the Codex login on this machine (default; --no-codex signs up with --email).' },
53
+ setup: { type: 'boolean', description: 'Run shard setup afterwards (default; --no-setup skips it).' },
54
+ tool: { type: 'string', multiple: true, value: '<tool>', description: 'Workspace tool the saved API key may use (repeatable; exec, files, pty, process, git, browser). Default: all.' },
55
+ ...SECOND_FACTOR,
56
+ },
57
+ examples: ['shard signup', 'shard signup --email you@example.com', 'shard signup --no-codex --email you@example.com --name "Ada Lovelace"'],
58
+ },
59
+ {
60
+ path: ['verify'],
61
+ summary: 'Verify your email with a 6-digit code: emails the code, then confirm it',
62
+ description: 'Without a code it emails one (valid 15 minutes) and, on a terminal, asks for it; otherwise pass it next: "shard verify <code>". Nothing is signed out and saved API keys keep working; an account from "shard signup" gets its trial.',
63
+ positionals: [{ name: 'code', required: false }],
64
+ options: {},
65
+ examples: ['shard verify', 'shard verify 123456'],
66
+ },
43
67
  // ---- sign-in (auth) ------------------------------------------------------------------------------------
44
68
  {
45
69
  path: ['auth', 'register'],
@@ -66,8 +90,12 @@ export const ACCOUNT_COMMANDS = [
66
90
  summary: 'Sign in: saves a session for this API in the credentials file (the password is read from standard input or a prompt)',
67
91
  description: 'Two-factor accounts: pass --code (or --recovery-code), or type the code at the prompt; without either the pending sign-in is saved and "shard auth mfa --code <code>" completes it within 10 minutes (exit 4 until then). Prints who you are, your organizations and the next step. A session lasts 30 days of use (90 at most). SHARDFLUX_SESSION_TOKEN, when set, is used instead of the saved session.',
68
92
  positionals: [],
69
- options: { email: { type: 'string', value: '<email>', description: 'Your email address (required).' }, ...SECOND_FACTOR },
70
- examples: [`printf '%s\\n' "$PASSWORD" | shard auth login --email you@example.com`, `printf '%s\\n' "$PASSWORD" | shard auth login --email you@example.com --code 123456`],
93
+ options: {
94
+ email: { type: 'string', value: '<email>', description: 'Your email address (required without --codex).' },
95
+ codex: { type: 'boolean', description: 'Sign in with the Codex login on this machine instead of a password (0.9.0+; creates the account when there is none).' },
96
+ ...SECOND_FACTOR,
97
+ },
98
+ examples: [`printf '%s\\n' "$PASSWORD" | shard auth login --email you@example.com`, `printf '%s\\n' "$PASSWORD" | shard auth login --email you@example.com --code 123456`, 'shard auth login --codex'],
71
99
  },
72
100
  {
73
101
  path: ['auth', 'mfa'],
@@ -98,9 +126,9 @@ export const ACCOUNT_COMMANDS = [
98
126
  {
99
127
  path: ['auth', 'step-up'],
100
128
  summary: 'Confirm your password (and code) for sensitive actions: valid 10 minutes',
101
- description: 'Needed for two-factor changes, an email change, data exports and deletions. On a terminal "shard" asks by itself when the API requires it; scripts run this first. The password comes from standard input or a prompt; two-factor accounts add --code (or type it at the prompt).',
129
+ description: 'Needed for two-factor changes, an email change, data exports and deletions. On a terminal "shard" asks by itself when the API requires it; scripts run this first. The password comes from standard input or a prompt; with --codex (0.9.0+) the Codex login linked to the account confirms it instead. Two-factor accounts add --code (or type it at the prompt).',
102
130
  positionals: [],
103
- options: { ...SECOND_FACTOR },
131
+ options: { codex: { type: 'boolean', description: 'Confirm with the Codex login on this machine instead of the password (accounts it signs in to).' }, ...SECOND_FACTOR },
104
132
  examples: [`printf '%s\\n' "$PASSWORD" | shard auth step-up`, `printf '%s\\n' "$PASSWORD" | shard auth step-up --code 123456`],
105
133
  },
106
134
  {