@shardflux/cli 0.18.0-next.4 → 0.18.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +61 -8
  2. package/README.md +92 -33
  3. package/dist/account-args.js +5 -3
  4. package/dist/account-commands.d.ts +4 -1
  5. package/dist/account-commands.js +104 -14
  6. package/dist/account-pool.d.ts +31 -0
  7. package/dist/account-pool.js +313 -0
  8. package/dist/agent-setup.d.ts +38 -0
  9. package/dist/agent-setup.js +116 -0
  10. package/dist/agent-target-commands.js +10 -1
  11. package/dist/agent-targets.d.ts +11 -0
  12. package/dist/agent-targets.js +22 -4
  13. package/dist/args.d.ts +3 -1
  14. package/dist/args.js +50 -5
  15. package/dist/claude-cloud-login.d.ts +11 -0
  16. package/dist/claude-cloud-login.js +49 -39
  17. package/dist/cli-proxy-hub.d.ts +26 -1
  18. package/dist/cli-proxy-hub.js +60 -4
  19. package/dist/commands.d.ts +3 -0
  20. package/dist/commands.js +7 -3
  21. package/dist/conversation-commands.d.ts +5 -0
  22. package/dist/conversation-commands.js +8 -2
  23. package/dist/conversations.d.ts +9 -0
  24. package/dist/desktop-linux.js +6 -5
  25. package/dist/filesystem-commands.d.ts +12 -2
  26. package/dist/filesystem-commands.js +46 -18
  27. package/dist/git-remote-bin.js +0 -0
  28. package/dist/handlers.js +2 -1
  29. package/dist/input.d.ts +4 -2
  30. package/dist/input.js +6 -3
  31. package/dist/main.js +19 -8
  32. package/dist/menubar.js +1 -1
  33. package/dist/t3-asar.d.ts +12 -2
  34. package/dist/t3-asar.js +14 -5
  35. package/dist/t3-launch.js +4 -1
  36. package/dist/t3-linux.d.ts +12 -13
  37. package/dist/t3-linux.js +81 -87
  38. package/dist/t3-mode.d.ts +4 -3
  39. package/dist/t3-mode.js +15 -14
  40. package/dist/t3-version.d.ts +8 -0
  41. package/dist/t3-version.js +42 -0
  42. package/package.json +21 -7
package/CHANGELOG.md CHANGED
@@ -1,17 +1,70 @@
1
1
  # Changelog
2
2
 
3
- ## 0.17.0 (not yet published)
3
+ ## 0.18.0 (not yet published)
4
+
5
+ - A conversation that needs you says why: `shard conversations list` adds it as a fifth column, `shard conversations
6
+ get` prints it after the branch, and `shard agents list` shows it as the session's LAST MESSAGE (one line, cut to
7
+ fit). `--json` carries the API's `question`; `agents list --json` adds it to the session's row.
8
+
9
+ - `shard machine save` and `shard template save` say when the save put software installed under the home into the
10
+ image, linked back at the same paths: `machine: version 4 saved; 1 large tool folder went into the machine image
11
+ (~/.dotnet); they keep working at the same paths`. `--json` carries `moved_to_image`.
12
+ - Setting up pooled accounts creates the hosted CLIProxyAPI hub on codex-connector 5, or upgrades it in place (its
13
+ logins and settings stay), whose VM port runs the account pool router; it gives the hub the API URL and a hub token
14
+ and turns on CLIProxyAPI's rate-limit header passthrough. Hubs without pooled accounts stay as they are.
15
+ - The Shardflux app for Ubuntu 24.04 (x64 and arm64): in a desktop session, bare `shard` opens it instead of the
16
+ terminal onboarding. It ships as the optional `@shardflux/desktop-linux-x64` / `-arm64` package (0.1.0), installs
17
+ with no npm install scripts, adds Shardflux to the app grid and starts at login. It has the Mac app's surfaces: the
18
+ first run, the top-bar panel (conversations, approvals, files, updates; a right click opens a small menu), the
19
+ launcher on Alt+Shift+Space (an X11 shortcut, or a GNOME custom shortcut it adds on Wayland), Settings, device and
20
+ sign-in approvals, notifications with actions, file downloads and self-update. `shard menubar start|stop|status`
21
+ and `--login on|off` manage it on Linux as on macOS. Without the desktop libraries, `shard` keeps the terminal
22
+ onboarding and says how to get the window.
23
+ - `shard agents accounts`: your Claude and ChatGPT/Codex accounts in one list, each with its plan, status (Signed in,
24
+ Low, Limited, Expired), the account threads use, and its usage windows (Claude 5-hour and weekly, Codex primary and
25
+ secondary) with when each resets. `shard agents accounts usage` draws every window as a bar. `shard agents accounts
26
+ add claude|codex` signs in another account in a cloud VM (`--api-key-stdin` adds an API key instead),
27
+ `remove <account>` removes one and `move <account> up|down` changes its place. Accounts are named by
28
+ `claude:<n>`/`codex:<n>`, email, label or id; `shard accounts …` works too. `--json` everywhere (SDK 0.24.0).
29
+ - `shard agents targets --json` carries an `accounts` block: the same list, for the menu bar.
30
+
31
+ ## 0.17.1 (not yet published)
4
32
 
5
- - Linux (Ubuntu 24.04, x64 and arm64): bare `shard` opens the Shardflux app in a graphical session. The optional CPU package installs without npm install scripts, adds Shardflux to the app grid, and starts at login. Headless sessions keep terminal onboarding. The systemd user helper keeps the live twin, files and conversation mirrors running; `shard device requests`, `shard device approve <id>` and `shard device deny <id>` handle local approvals.
6
- - The Ubuntu desktop app includes the custom tray panel, launcher capture and project/model menus, all Settings panes, local device and credential approval panels, notifications with actions, file downloads and packaged-app updates. `shard device approve --always` remembers matching in-project requests; outside-folder requests remain one-time approvals.
7
33
  - T3 Code on Linux: `shard` finds T3 Code installed from its .deb or AppImage (and AppImageLauncher, Gear Lever,
8
34
  flatpak and snap entries), registers the Shardflux provider in it, shows it in onboarding, and opens it from its
9
35
  desktop entry. `shard agents new --in t3`, `shard agents open t3` and `shard agents targets --open-in` work as on
10
36
  macOS: a T3 build with ACP providers starts each thread on its Shardflux provider in a cloud VM of its own, and T3
11
- Code 0.0.45 opens the project VM as an SSH environment. `shard agents targets --json` reports it under `t3`
12
- (`last_used_at`: when T3 last started). The Shardflux provider is also recognized in the latest T3 nightlies on
13
- macOS.
14
-
37
+ Code 0.0.45 opens the project VM as an SSH environment. `shard agents targets --json` reports it under `t3`, and
38
+ `--open-in claude=t3,codex=t3` makes T3 Code where new conversations open. The Shardflux provider is also
39
+ recognized in the latest T3 nightlies on macOS.
40
+ - Setting Shardflux up with a coding agent: `shard setup --guide` prints the one question an agent asks its user first
41
+ (a SaaS platform whose product runs VMs for its customers, or their personal cloud coding agents) and the ordered
42
+ steps of both, with every command; `--json` gives them as a list. Local only, so it never waits. Bare `shard`
43
+ without a terminal prints the same guide instead of the full help (macOS) or the sign-in line (Linux).
44
+ - `shard auth login --browser --no-wait` prints the link and the code and returns; the next `shard auth login
45
+ --browser` waits for that same request while it is valid (`"resumed": true` on its `open` event), so an agent whose
46
+ command is cut off by its tool's time limit runs it again and the person approves one code. The request is kept in
47
+ `browser-login.json` (0600) in the config directory until it is approved, refused or expired.
48
+ - `shard machine open` creates or resumes your machine VM without attaching to it and prints its workspace key: an
49
+ agent installs with `shard ws exec <key>`, writes dotfiles and secret files with `shard files write <key> <path>
50
+ --from -`, then `shard machine save --wait`. `--json` adds the workspace and the next commands.
51
+ - A coding agent's shell (`CLAUDECODE`, `CODEX_SANDBOX`, `CODEX_THREAD_ID`, `CURSOR_AGENT` or `GEMINI_CLI` set) is
52
+ treated as no terminal, also when it is a PTY: bare `shard` prints the guide instead of the command center, `shard
53
+ ./` and `shard machine` do not attach, the Claude cloud sign-in says to run it in the user's own terminal, and
54
+ prompts take their non-terminal path (`signup` without `--accept-terms` stops with `terms_not_accepted`).
55
+ - `shard ./` without a terminal (and without `--json`) is a usage error (exit 2) that says to run it in the user's own
56
+ terminal or to start the conversation detached with `--json --prompt`; before, it printed its help and exited 0.
57
+ - `shard setup` without a session and without a terminal prints the guide and exits 4. Sign-in hints name `shard auth
58
+ login --browser` (and `shard signup` for a new account) instead of a password sign-in. After `shard setup` and
59
+ `shard auth login`, the next step names both paths; `shard setup --json` adds `next_steps: {saas, personal}` (`next`
60
+ is unchanged).
61
+
62
+ ## 0.17.0 (2026-10-10)
63
+
64
+ - Linux (Ubuntu 24.04, x64 and arm64): bare `shard` signs in and connects Claude/Codex in the terminal. The
65
+ systemd user helper keeps the live twin, files and conversation mirrors running; `shard device requests`,
66
+ `shard device approve <id>` and `shard device deny <id>` handle local approvals. Desktop launches open a terminal;
67
+ macOS keeps its extra menu bar app.
15
68
  - A Codex project opens in T3 Code without a Claude sign-in (T3 shows Claude as signed out, and says how to sign in), and `--json` names the T3 Connect sign-in `shard` started for the phone (`t3_link.sign_in`, or `t3_link.needs_login` when this computer has no session).
16
69
  - A conversation settles when its turn ends (`done` or `unresolved` right away) and its VM takes the next message.
17
70
  `shard agents open`, `shard ./ --resume` and ACP reattach to that VM instead of resuming the conversation into a new
@@ -67,7 +120,7 @@
67
120
  active conversation and hears the finished turn the moment the conversation reports it (no polling every second).
68
121
  Conversations that finish together for the same branch land in one go, in the order they finished.
69
122
 
70
- ## 0.16.0 (not yet published)
123
+ ## 0.16.0 (2026-10-10)
71
124
 
72
125
  - Cloud sessions are named for the work: `shard ./` starts the agent with the repository or folder name and the first
73
126
  line of your prompt, so claude.ai, the Claude app and its notifications show `Add CSV export · todo-app`
package/README.md CHANGED
@@ -50,10 +50,18 @@ Bulk transfers batch writes automatically. Active transfers retain their byte po
50
50
 
51
51
  ## Install
52
52
 
53
+ Setting Shardflux up with a coding agent (Claude Code, Codex, Cursor, ...)? See [For AI agents](#for-ai-agents): the
54
+ agent asks one question first, then runs the steps for a SaaS platform or for your personal cloud coding agents.
55
+
53
56
  Linux (Ubuntu 24.04, x64 and arm64) and macOS are supported. On Linux, run `shard` to sign in and
54
57
  connect Claude, Codex or both (0.17.0+), then run `shard ./` in your project to attach Claude Code or Codex in place. Sign-in opens your browser;
55
58
  on a headless terminal, follow the displayed URL and device code. `--accept-terms` accepts the terms notice.
56
59
 
60
+ In a desktop session, `shard` opens the Shardflux app for this (0.18.0+): guided sign-in, a top-bar panel for your
61
+ conversations, a launcher on Alt+Shift+Space and Settings. It installs with the CLI (the optional
62
+ `@shardflux/desktop-linux-x64` or `-arm64` package), appears in the app grid and starts at login;
63
+ `shard menubar start|stop|status` and `--login on|off` manage it as on macOS.
64
+
57
65
  The Linux helper starts automatically after setup as `~/.config/systemd/user/shardflux-helper.service`
58
66
  and restarts after a CLI update. It keeps the live twin, `~/Shardflux` files and conversation history in sync.
59
67
  Use `shard device requests`, `shard device approve <id>` or `shard device deny <id>` for local approvals;
@@ -217,38 +225,62 @@ npm test 2>&1 | tail -n 60 | shard feedback --category bug --workspace acme/demo
217
225
 
218
226
  ## For AI agents
219
227
 
220
- `shard` is built to be driven by an agent: no browser, no prompts when standard input is not a
221
- terminal, `--json` on every command, secrets only on standard input, and documented exit codes.
222
-
223
- 1. **Account (0.9.0+):** show your person the [Terms of Service](https://shardflux.dev/terms) and the
224
- [Privacy Policy](https://shardflux.dev/privacy); once they agree, `shard signup --accept-terms --json` (0.15.0+:
225
- without `--accept-terms` or a terminal to ask on, it stops before sending anything with `terms_not_accepted`, exit 2,
226
- and the two links in `details`). With the Codex CLI signed in with ChatGPT on the
227
- machine it signs up (or signs in) verified by that login; otherwise pass `--email <email>` and the
228
- account works at once on the Free plan. It then runs `shard setup`: an organization, a project and
229
- an API key with every workspace tool, saved in the credentials file. With two-factor authentication
230
- it exits 4 with `mfa_required`: ask the person for the code, then `shard auth mfa --code <code>`.
231
- 2. **Verify the email** (an `--email` signup, to start the trial): `shard verify --json` emails a
232
- 6-digit code; ask the person for it, then `shard verify <code>`. Nothing is signed out.
233
- 3. **Set up again any time:** `shard setup --json` is idempotent. `shard setup --inspect --json --apps claude,codex`
234
- (0.15.0+) checks every connection live (your account, Claude, Codex) and says what to do for any that is not ready;
235
- `--connect` also finishes the Codex connection.
236
- 4. **Work:** `shard exec <key> --template default -- <cmd>` creates the workspace on first use and runs the command
237
- (0.10.0+); later commands reuse it. `shard ws open <key> --template <slug> --json`, `shard files ...` use the saved
238
- key too.
239
- 5. **A paid plan:** `shard billing plans`, then `shard billing upgrade <plan> --json` prints the Stripe
240
- Checkout URL; give it to the person, then `shard billing upgrade <plan> --wait` (or
241
- `shard billing status`) until it is active. The API activates a plan only when Stripe confirms the
242
- payment.
243
- 6. **Sensitive actions** (exports, deletions, two-factor and email changes) need a recent step-up:
244
- `printf '%s\n' "$PASSWORD" | shard auth step-up` (add `--code <totp>` with two-factor
245
- authentication; valid 10 minutes). Without a terminal `shard` does not ask: the command fails with
246
- `step_up_required` (exit 4) and the hint names this command.
247
- 7. **Feedback:** send `shard feedback` as you work, the moment something fails, confuses you or is
248
- missing, or your user asks for an improvement (see [Feedback](#feedback-agents-use-this-while-you-work)).
249
-
250
- What a person does: entering a card in Stripe Checkout, the authenticator app's codes, and (for an
251
- `--email` signup) reading the 6-digit code.
228
+ `shard` is built to be driven by an agent: no prompts and no full-screen views when standard input is not a terminal
229
+ or the shell is a coding agent's (Claude Code, Codex, Cursor, Gemini CLI; 0.17.1+), `--json` on every command, secrets
230
+ only on standard input, and documented exit codes.
231
+
232
+ **Ask your user one question first:** is Shardflux for a **SaaS platform** (their product runs VMs for its own
233
+ customers through the API, SDK or MCP) or for their **personal setup** (their own cloud coding agents: each Claude or
234
+ Codex conversation gets its own VM)? `shard setup --guide` prints both paths with every command, `--json` as a list
235
+ (0.17.1+); so does `shard` in a shell without a terminal. The full walk-through:
236
+ [Set up Shardflux with your coding agent](https://docs.shardflux.dev/guides/agent-setup).
237
+
238
+ 1. **Account, for both.** Show your user the [Terms of Service](https://shardflux.dev/terms) and the
239
+ [Privacy Policy](https://shardflux.dev/privacy); once they agree, `shard signup --accept-terms --json`. With the
240
+ Codex CLI signed in with ChatGPT on the machine it signs up (or signs in) with that login and runs `shard setup`:
241
+ an organization, a project and an API key, saved. Without a Codex login, or for an account they already have:
242
+ `shard auth login --browser --no-wait --json` prints the link and the code for your user and returns (0.17.1+);
243
+ `shard auth login --browser --json` then waits for that same request (run it again if your tool's time limit cuts
244
+ it off), and `shard setup --json` saves the key. With two-factor authentication a password sign-in exits 4 with
245
+ `mfa_required`: ask for the code, then `shard auth mfa --code <code>`.
246
+
247
+ **SaaS platform** (no agent sign-ins, menu bar or machine):
248
+
249
+ 2. `eval "$(shard env)"` exports `SHARDFLUX_API_KEY` for the SDKs, the MCP server and your product's server. Keep it in
250
+ server-side secrets.
251
+ 3. Integrate with `@shardflux/sdk`, the Python SDK `shardflux`, the HTTP API or the MCP server. Your customers'
252
+ workspaces start from a template: `shard templates build template.yaml`. `shard exec <key> --template default --
253
+ <cmd>` tries a workspace from here (0.10.0+).
254
+ 4. A paid plan: `shard billing upgrade <plan> --json` prints the Stripe Checkout URL; give it to your user, then
255
+ `shard billing upgrade <plan> --wait` (or `shard billing status`) until it is active.
256
+
257
+ **Personal setup** (their own cloud coding agents):
258
+
259
+ 2. **Connect Claude and Codex.** Claude: your user runs `shard agents credentials login claude` in their own terminal,
260
+ once (a claude.ai link and a code). Codex: `codex login` on this computer. Then
261
+ `shard setup --inspect --connect --json --apps claude,codex` (0.15.0+) checks both live, copies the Codex login and
262
+ starts the account's Codex remote device.
263
+ 3. **Build the machine** every conversation starts from. Ask your user what it should carry: languages and runtimes,
264
+ CLIs and tools, dotfiles and shell, secrets and credentials, editor and agent config. Then
265
+ `shard machine open --json` (0.17.1+) prints the machine VM's key; install with
266
+ `shard ws exec <key> -- bash -lc '...'`, write dotfiles and secret files with
267
+ `shard files write <key> <path> --from -` (contents on standard input), and `shard machine save --wait --json`.
268
+ 4. **Set up the project.** In its folder, `shard ./ --json --prompt "Set up this project and run its tests"` starts a
269
+ conversation in its own VM (`shard ./ codex --json ...` for Codex); setup installs the dependencies and saves the
270
+ project template. `shard conversations wait <conversation-id> --timeout 90` follows it (run it again while it says
271
+ Still working), and `shard template --json` shows the saved version. Project secrets:
272
+ `shard secrets import --from <hsec|1password|bitwarden|keychain> NAME`.
273
+ 5. **Hand over.** Your user runs `shard ./` in their own terminal. Claude conversations continue on claude.ai and in the
274
+ Claude app; Codex threads in the Codex app, on the web and on the phone.
275
+
276
+ Any time: `shard setup --json` is idempotent, and sensitive actions (exports, deletions, two-factor and email changes)
277
+ need a recent step-up: `printf '%s\n' "$PASSWORD" | shard auth step-up` (without a terminal the command fails with
278
+ `step_up_required`, exit 4, and the hint names this command). Send `shard feedback` as you work, the moment something
279
+ fails, confuses you or is missing, or your user asks for an improvement (see
280
+ [Feedback](#feedback-agents-use-this-while-you-work)).
281
+
282
+ What a person does: agreeing to the terms, approving the browser sign-in, the Claude sign-in in their terminal,
283
+ entering a card in Stripe Checkout and the authenticator app's codes.
252
284
 
253
285
  ## Accounts and sign-in (0.5.0+)
254
286
 
@@ -256,6 +288,7 @@ What a person does: entering a card in Stripe Checkout, the authenticator app's
256
288
  shard signup [--email you@example.com] [--name "Ada Lovelace"] [--no-codex] [--no-setup] [--accept-terms] # 0.9.0+
257
289
  shard verify [<code>] # 0.9.0+: emails a 6-digit code, then confirms it
258
290
  shard auth login --browser [--no-open] # 0.15.0+: approve this computer on the sign-in page
291
+ shard auth login --browser --no-wait # 0.17.1+: print the link and code, return; the next --browser run waits for it
259
292
  shard auth login --codex [--accept-terms] # 0.9.0+: sign in with the Codex login on this machine
260
293
  printf '%s\n' "$PASSWORD" | shard auth register --email you@example.com [--name "Ada Lovelace"] [--accept-terms]
261
294
  shard auth verify-email '<link from the email>' # or just the token in it
@@ -334,6 +367,9 @@ shard auth totp recovery-codes
334
367
  3. an API key with every workspace tool, `computer` included (0.15.1+; `--tool` repeatable to narrow it), named
335
368
  `shard-cli <hostname>` (`--key-name`), saved in the credentials file and checked against the API.
336
369
 
370
+ `shard setup --guide` (0.17.1+) prints how to set Shardflux up with a coding agent: the question to ask first (a SaaS
371
+ platform, or the user's own cloud coding agents) and the ordered steps of both; `--json` gives them as a list.
372
+
337
373
  It is idempotent: a saved key that still works is kept (`--new-key` makes a new one). A saved key from an earlier
338
374
  `shard setup` (every tool but `computer`) is replaced with one that has every tool (0.15.1+); the old key keeps working
339
375
  until you revoke it. It prints what
@@ -1283,9 +1319,13 @@ New project folders are ready to use: `shard ./` and `shard agents new` initiali
1283
1319
  Your **machine** carries credentials, tools, dotfiles, logins and agent config; the project's **template** carries
1284
1320
  toolchains, dependencies, services and project secrets. Both are private filesystem templates and include secrets
1285
1321
  as ordinary files. Browser logins and Claude/Codex config belong to the machine. Set up tools and secrets normally inside the VM;
1286
- your agent uses the same save/rollback commands there. Every agent starts from both. Save or roll back with `shard machine save|rollback` and
1322
+ your agent uses the same save/rollback commands there. An agent working from your computer builds the machine with
1323
+ `shard machine open` (0.17.1+): it creates or resumes the machine VM without attaching and prints its key for
1324
+ `shard ws exec` and `shard files write`. Every agent starts from both. Save or roll back with `shard machine save|rollback` and
1287
1325
  `shard template save|rollback`; changed files reach running agents live, software at the next start.
1288
1326
  `shard machine save --wait` and `shard template save --wait` return once the new version is ready (0.13.3+).
1327
+ Software installed under the home goes into the saved image, linked back at the same path; the save line names those
1328
+ folders, and `--json` carries `moved_to_image` (0.18.0+).
1289
1329
  `shard ./ codex` (or `claude`; `--codex` and `--claude` also work, 0.14.4+) starts that agent directly; `shard agents targets <project>/<agent>` lists each
1290
1330
  connection it configured and the file it is in (0.13.3+).
1291
1331
 
@@ -1321,6 +1361,25 @@ on it with `DISPLAY=:1`, detached so it outlives the command, for example
1321
1361
 
1322
1362
  Volume lists and details (0.14.0+) show `kind`. Project spaces show their measured storage and a platform-managed quota.
1323
1363
 
1364
+ ### Claude and ChatGPT/Codex accounts (0.18.0+)
1365
+
1366
+ `shard agents accounts` lists your Claude and ChatGPT/Codex accounts with their plan, status, the account threads use
1367
+ and how much of each usage window is left:
1368
+
1369
+ ```
1370
+ Claude
1371
+ claude:1 marc@example.com (Max, in use) · Signed in
1372
+ 5-hour 43% (resets in 2h 10m) · Weekly 18% (resets in 4d 8h)
1373
+ ChatGPT / Codex
1374
+ codex:1 marc@example.com (Pro, in use) · Signed in
1375
+ 5-hour 12% (resets in 3h 30m) · Weekly 35% (resets in 4d 23h)
1376
+ ```
1377
+
1378
+ `shard agents accounts usage` shows every window as a bar. `shard agents accounts add claude` (or `codex`) signs in
1379
+ another account in a cloud VM; `pbpaste | shard agents accounts add claude --api-key-stdin` adds an API key.
1380
+ `shard agents accounts move claude:2 up` changes an account's place and `shard agents accounts remove claude:2`
1381
+ removes it. `--json` prints the API's account list.
1382
+
1324
1383
  ## Conversations
1325
1384
 
1326
1385
  From 0.14.0, `shard conversations list|get|send|wait|land|move` manages project conversations. `shard c` is the short form. Conversations retain their branch and transcript as their VM binding changes.
@@ -103,12 +103,13 @@ export const ACCOUNT_COMMANDS = [
103
103
  browser: { type: 'boolean', description: 'Sign in in the browser: open the sign-in page, approve this computer there (0.15.0+). An account created there agrees to the terms on the sign-up page, so --browser needs no --accept-terms.' },
104
104
  open: { type: 'boolean', description: 'With --browser: open the page in the default browser (default; --no-open only prints the link).' },
105
105
  'client-name': { type: 'string', value: '<name>', description: 'With --browser: the name the sign-in page shows for this computer (default "shard CLI on <hostname>").' },
106
+ wait: { type: 'boolean', description: 'With --browser: wait for the approval (default). --no-wait prints the link and the code and returns; the next "shard auth login --browser" waits for that same request while it is valid (0.17.1+).' },
106
107
  email: { type: 'string', value: '<email>', description: 'Your email address, to sign in with a password.' },
107
108
  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, so it needs --accept-terms like shard signup).' },
108
109
  ...ACCEPT_TERMS,
109
110
  ...SECOND_FACTOR,
110
111
  },
111
- examples: ['shard auth login --browser', 'shard auth login --browser --no-open --json', `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'],
112
+ examples: ['shard auth login --browser', 'shard auth login --browser --no-open --json', 'shard auth login --browser --no-wait --json # then: shard auth login --browser --json', `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'],
112
113
  },
113
114
  {
114
115
  path: ['auth', 'mfa'],
@@ -195,11 +196,12 @@ export const ACCOUNT_COMMANDS = [
195
196
  {
196
197
  path: ['setup'],
197
198
  summary: 'Get ready to run workspaces: use (or create) an organization and project, create an API key and save it',
198
- description: 'Needs a session (shard auth login). With no organization it creates "<your name>\'s organization" and a project "Default"; the key gets every workspace tool unless --tool. The key is saved in the credentials file, checked against the API, and every workspace command uses it from then on. Idempotent: a saved key that still works is kept (unless --new-key).',
199
+ description: 'Needs a session (shard auth login). With no organization it creates "<your name>\'s organization" and a project "Default"; the key gets every workspace tool unless --tool. The key is saved in the credentials file, checked against the API, and every workspace command uses it from then on. Idempotent: a saved key that still works is kept (unless --new-key). --guide (0.17.1+) prints the question a coding agent asks its user first (a SaaS platform, or their personal cloud coding agents) and the ordered steps of both, without a network call.',
199
200
  positionals: [],
200
201
  options: {
201
202
  ...ORG,
202
203
  ...PROJECT,
204
+ guide: { type: 'boolean', description: 'Print how to set up Shardflux with a coding agent: the one question to ask your user, then the steps for a SaaS platform and for a personal setup (0.17.1+).' },
203
205
  'prepare-sample': { type: 'boolean', description: 'Copy the Daylight sample app into a new local Git project, "Daylight (sample)", for the first-run demo; prints its path.' },
204
206
  activity: { type: 'boolean', description: 'With --inspect: the current commands, listening ports, sudo installs and browser window of --workspace, without waking it.' },
205
207
  workspace: { type: 'string', value: '<key|conv_id>', description: 'The workspace or conversation to inspect for live onboarding activity.' },
@@ -214,7 +216,7 @@ export const ACCOUNT_COMMANDS = [
214
216
  'new-key': { type: 'boolean', description: 'Create and save a new key even when the saved one works (the old key stays valid until revoked).' },
215
217
  codex: { type: 'boolean', description: 'Set up the account Codex Cloud executor with this machine’s Codex login (default when signed in); --no-codex skips it.' },
216
218
  },
217
- examples: ['shard setup', 'shard setup --json --inspect --apps claude,codex', 'shard setup --org acme --project web --tool exec --tool files'],
219
+ examples: ['shard setup', 'shard setup --guide', 'shard setup --json --inspect --apps claude,codex', 'shard setup --org acme --project web --tool exec --tool files'],
218
220
  },
219
221
  {
220
222
  path: ['env'],
@@ -1,4 +1,5 @@
1
- import type { Handler } from './commands.js';
1
+ import { setupGuide } from './agent-setup.js';
2
+ import type { Ctx, Handler } from './commands.js';
2
3
  export declare const DEFAULT_API_URL = "https://api.shardflux.dev";
3
4
  /** What `shard setup` gave a key before 0.15.1: every tool but `computer`. Such a saved key is replaced with a full one. */
4
5
  export declare function outdatedDefaultKey(permissions: readonly string[]): boolean;
@@ -17,4 +18,6 @@ export declare function exportLine(shell: ShellKind, name: string, value: string
17
18
  export declare function parseTime(text: string, option: string, now?: number): string;
18
19
  /** A spend cap in dollars ("9", "9.5", "9.50", "$9") as minor units (cents); at most two decimals. */
19
20
  export declare function dollarsToMinor(text: string, what: string): number;
21
+ /** The setup guide for this computer: whether a session and an API key are saved (no network call). */
22
+ export declare function agentSetupGuide(ctx: Ctx): ReturnType<typeof setupGuide>;
20
23
  export declare const ACCOUNT_HANDLERS: Record<string, Handler>;
@@ -20,9 +20,13 @@ import { API_KEY_TOOL_PERMISSIONS, CheckoutTimeoutError, Shardflux, ShardfluxAcc
20
20
  import { CLI_VERSION, UUID, parseDuration, parsePositiveInt } from "./args.js";
21
21
  import { openUrl } from "./browser.js";
22
22
  import { defaultClientName, startDeviceAuthorization, waitForDeviceSession } from "./browser-login.js";
23
+ import { setupGuide, setupGuideText } from "./agent-setup.js";
24
+ import { readFileSync, rmSync } from 'node:fs';
25
+ import { join } from 'node:path';
23
26
  import { legalFields, requireTermsAcceptance, showLegalNotice } from "./legal.js";
24
27
  import { fileExists, oneOf, out, parseSlugVersion, principal, readInput, resolveWorkspace, str, strs, waitTimeout } from "./commands.js";
25
- import { AuthConfigError, CheckoutEndedError, InterruptedError, MfaPendingError, NotFoundError, UsageError, describeError } from "./errors.js";
28
+ import { writePrivateFile } from "./credentials.js";
29
+ import { AuthConfigError, CheckoutEndedError, EXIT, InterruptedError, MfaPendingError, NotFoundError, UsageError, describeError } from "./errors.js";
26
30
  import { apiKeyTable, attachmentTable, auditTable, authSessionTable, billingStatusText, dataExportText, egressVersionTable, invitationTable, invoiceTable, isGrantPlan, kv, memberTable, money, orgTable, overagePolicyText, planTable, projectEgressText, projectTable, table, time, toolsText, volumeDetail, volumeTable, workspaceEgressText, WORKSPACE_HEADERS, workspaceRow, } from "./format.js";
27
31
  export const DEFAULT_API_URL = 'https://api.shardflux.dev';
28
32
  const TOTP = /^\d{6}$/;
@@ -39,6 +43,9 @@ export function outdatedDefaultKey(permissions) {
39
43
  const DEFAULT_CHECKOUT_WAIT_MS = 15 * 60_000;
40
44
  const CHECKOUT_POLL_MS = 2_000;
41
45
  const NEXT_WORKSPACE = 'shard ws open demo --template python-node-browser';
46
+ /** After setup (0.17.1+): the next step for each answer to the setup question (shard setup --guide has them all). */
47
+ const NEXT_STEPS = { saas: 'eval "$(shard env)", then the SDK, HTTP API or MCP server', personal: 'shard setup --inspect --connect --apps claude,codex, then shard ./' };
48
+ const nextLines = () => `next, for your product (SDK, API, MCP): ${NEXT_STEPS.saas}\nnext, for your own cloud coding agents: ${NEXT_STEPS.personal}\n`;
42
49
  const SWITCHED_NOTE = 'note: another user was signed in here; their default organization, project and saved API key were removed from this profile (the key stays valid until revoked).\n';
43
50
  const pipe = (cmd) => `printf '%s\\n' "$PASSWORD" | ${cmd}`;
44
51
  function need(values, option, example) {
@@ -274,7 +281,7 @@ function nextAfterSignIn(ctx, user, me) {
274
281
  const hasKey = profile.api_key !== undefined || Boolean(ctx.io.env.SHARDFLUX_API_KEY?.trim());
275
282
  if (!hasKey || !me || me.memberships.length === 0)
276
283
  return 'shard setup (an organization, a project and an API key, saved)';
277
- return NEXT_WORKSPACE;
284
+ return 'shard setup --guide (the steps for your product, or for your own cloud coding agents)';
278
285
  }
279
286
  async function signedInReport(ctx, account, user, session, extra = {}) {
280
287
  let me = null;
@@ -682,7 +689,7 @@ async function runSetup(ctx, values, account) {
682
689
  ]) +
683
690
  envNote +
684
691
  (codex ? 'Codex Cloud is ready. One account executor; no pairing. Its residency is billed as workspace usage.\n' : '') +
685
- `next: ${NEXT_WORKSPACE}\n`,
692
+ nextLines(),
686
693
  data: {
687
694
  organization: org,
688
695
  project,
@@ -691,10 +698,46 @@ async function runSetup(ctx, values, account) {
691
698
  ...(codex ? { codex } : {}),
692
699
  credentials_file: ctx.store.file,
693
700
  next: NEXT_WORKSPACE,
701
+ next_steps: NEXT_STEPS,
694
702
  },
695
703
  };
696
704
  }
697
705
  // ---- browser sign-in (0.15.0+) -----------------------------------------------------------------------------------
706
+ /**
707
+ * A browser sign-in to finish later (0.17.1+): `auth login --browser --no-wait` saves the request (0600, next to the
708
+ * credentials) and returns; the next `auth login --browser` for the same API waits for that request while it is valid,
709
+ * so an agent whose command is cut off by its tool's time limit runs it again and the person approves one code.
710
+ */
711
+ const PENDING_LOGIN_FILE = 'browser-login.json';
712
+ /** A request this close to its expiry is not resumed: a new one gives the person time to approve it. */
713
+ const PENDING_MARGIN_MS = 15_000;
714
+ function pendingLogin(ctx) {
715
+ try {
716
+ const saved = JSON.parse(readFileSync(join(ctx.store.dir, PENDING_LOGIN_FILE), 'utf8'));
717
+ const r = saved.request;
718
+ if (saved.api_url !== ctx.apiUrl || typeof saved.expires_at !== 'number' || !r || typeof r.device_code !== 'string' || typeof r.user_code !== 'string' || typeof r.verification_uri_complete !== 'string' || typeof r.interval !== 'number')
719
+ return null;
720
+ const left = saved.expires_at - Date.now();
721
+ if (left < PENDING_MARGIN_MS)
722
+ return null;
723
+ ctx.addSecret(r.device_code);
724
+ return { device_code: r.device_code, user_code: r.user_code, verification_uri: typeof r.verification_uri === 'string' ? r.verification_uri : r.verification_uri_complete, verification_uri_complete: r.verification_uri_complete, interval: r.interval, expires_in: Math.floor(left / 1000) };
725
+ }
726
+ catch {
727
+ return null;
728
+ }
729
+ }
730
+ function savePendingLogin(ctx, d) {
731
+ writePrivateFile(ctx.store.dir, join(ctx.store.dir, PENDING_LOGIN_FILE), `${JSON.stringify({ api_url: ctx.apiUrl, expires_at: Date.now() + d.expires_in * 1000, request: d })}\n`);
732
+ }
733
+ function clearPendingLogin(ctx) {
734
+ try {
735
+ rmSync(join(ctx.store.dir, PENDING_LOGIN_FILE), { force: true });
736
+ }
737
+ catch {
738
+ // Expires on its own.
739
+ }
740
+ }
698
741
  /** One NDJSON line of `auth login --browser --json` (the Mac app reads them). */
699
742
  function event(ctx, value) {
700
743
  ctx.io.stdout.write(`${JSON.stringify(value)}\n`);
@@ -708,23 +751,49 @@ async function browserSignIn(ctx, values, noticeShown = false) {
708
751
  try {
709
752
  const opts = ctx.accountOptions();
710
753
  const clientName = str(values['client-name'])?.trim() || defaultClientName(ctx.hostname());
711
- const d = await startDeviceAuthorization(opts, clientName, ctx.signal);
754
+ const waits = values.wait !== false;
755
+ // A request saved by --no-wait (0.17.1+) is the one to wait for; otherwise a new one.
756
+ const earlier = pendingLogin(ctx);
757
+ const resumed = earlier !== null;
758
+ const d = earlier ?? (await startDeviceAuthorization(opts, clientName, ctx.signal));
759
+ ctx.addSecret(d.device_code);
760
+ if (!waits && !resumed)
761
+ savePendingLogin(ctx, d);
712
762
  // The terms notice comes before the browser opens: a line here, the URLs in the event for an app to show. No
713
763
  // --accept-terms: an account created in the browser agrees to the terms on the sign-up page, which records it.
714
764
  if (ctx.json) {
715
- event(ctx, { event: 'open', verification_uri_complete: d.verification_uri_complete, user_code: d.user_code, expires_in: d.expires_in, ...legalFields() });
765
+ event(ctx, { event: 'open', verification_uri_complete: d.verification_uri_complete, user_code: d.user_code, expires_in: d.expires_in, ...legalFields(), ...(resumed ? { resumed: true } : {}) });
716
766
  }
717
767
  else if (!noticeShown)
718
768
  showLegalNotice(ctx);
719
- const opened = values.open !== false && openUrl(d.verification_uri_complete, ctx.io.env);
769
+ // A resumed request was opened (or printed) by the command that started it.
770
+ const opened = !resumed && values.open !== false && openUrl(d.verification_uri_complete, ctx.io.env);
720
771
  const minutes = Math.max(1, Math.round(d.expires_in / 60));
721
772
  if (!ctx.json) {
722
- ctx.io.stderr.write(`${opened ? 'Opening your browser to sign in' : 'Open this page to sign in'}: ${d.verification_uri_complete}\n` +
773
+ ctx.io.stderr.write(`${resumed ? 'Continuing the sign-in started earlier' : opened ? 'Opening your browser to sign in' : 'Open this page to sign in'}: ${d.verification_uri_complete}\n` +
723
774
  `Your code: ${d.user_code} (approve only if the page shows the same code)\n` +
724
- `Waiting for the approval in the browser (the code expires in ${minutes} minutes; Ctrl-C stops)...\n`);
775
+ (waits
776
+ ? `Waiting for the approval in the browser (the code expires in ${minutes} minutes; Ctrl-C stops)...\n`
777
+ : `Approve it in the browser, then run shard auth login --browser to finish (the code is valid for ${minutes} minutes).\n`));
778
+ }
779
+ if (!waits) {
780
+ if (ctx.json)
781
+ event(ctx, { event: 'pending', expires_in: d.expires_in, next: 'shard auth login --browser --json' });
782
+ return null;
725
783
  }
726
784
  const sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
727
- const s = await waitForDeviceSession(opts, d, { signal: ctx.signal, sleep });
785
+ let s;
786
+ try {
787
+ s = await waitForDeviceSession(opts, d, { signal: ctx.signal, sleep });
788
+ }
789
+ catch (err) {
790
+ // Refused, expired or unknown: the saved request is done. Interrupted (or cut off): it stays for the next run.
791
+ if (resumed && !ctx.signal.aborted && err instanceof ShardfluxApiError)
792
+ clearPendingLogin(ctx);
793
+ throw err;
794
+ }
795
+ if (resumed)
796
+ clearPendingLogin(ctx);
728
797
  ctx.addSecret(s.session_token);
729
798
  const replaced = ctx.store.profile(ctx.apiUrl).session?.token ?? null;
730
799
  const saved = { token: s.session_token, expires_at: s.session_expires_at || null, user: { id: s.user.id, email: s.user.email }, state: 'active' };
@@ -747,8 +816,8 @@ async function browserSignIn(ctx, values, noticeShown = false) {
747
816
  }
748
817
  /** `shard signup` on a terminal without a Codex login or --email (0.15.0+): the browser sign-in, then setup like signup. */
749
818
  async function browserSignup(ctx, values) {
750
- // signup has shown the notice (and asked) already.
751
- const { account, user } = await browserSignIn(ctx, values, true);
819
+ // signup has shown the notice (and asked) already; it always waits for the approval.
820
+ const { account, user } = (await browserSignIn(ctx, { ...values, wait: true }, true));
752
821
  let text = `Signed in as ${userLine(user)} in the browser.\nsession: saved in ${ctx.store.file}\n`;
753
822
  const data = { status: 'authenticated', user, method: 'browser', credentials_file: ctx.store.file };
754
823
  if (values.setup !== false) {
@@ -773,8 +842,16 @@ function wantsBrowser(ctx, values) {
773
842
  }
774
843
  if (values.browser === false || email || values.codex === true)
775
844
  return false;
845
+ // --no-wait (0.17.1+) is a browser sign-in to finish with the next shard auth login --browser.
846
+ if (values.wait === false)
847
+ return true;
776
848
  return ctx.input.interactive && !ctx.json;
777
849
  }
850
+ /** The setup guide for this computer: whether a session and an API key are saved (no network call). */
851
+ export function agentSetupGuide(ctx) {
852
+ const profile = ctx.store.profile(ctx.apiUrl);
853
+ return setupGuide({ signedIn: ctx.sessionSource() !== null, email: profile.session?.user?.email ?? null, apiKey: profile.api_key !== undefined || Boolean(ctx.io.env.SHARDFLUX_API_KEY?.trim()) });
854
+ }
778
855
  // ---- handlers --------------------------------------------------------------------------------------------------
779
856
  export const ACCOUNT_HANDLERS = {
780
857
  async 'auth register'(ctx, values) {
@@ -826,15 +903,18 @@ export const ACCOUNT_HANDLERS = {
826
903
  if (wantsBrowser(ctx, values)) {
827
904
  if (factorFlags(values) !== null)
828
905
  throw new UsageError('--code and --recovery-code go with --email; in the browser the sign-in page asks for your second factor');
829
- const { account, user, saved } = await browserSignIn(ctx, values);
906
+ const signedIn = await browserSignIn(ctx, values);
907
+ if (!signedIn)
908
+ return 0;
909
+ const { account, user, saved } = signedIn;
830
910
  if (ctx.json) {
831
911
  event(ctx, { event: 'signed_in', email: user.email });
832
912
  return 0;
833
913
  }
834
914
  return signedInReport(ctx, account, user, saved);
835
915
  }
836
- if (values.open !== undefined || str(values['client-name']) !== undefined)
837
- throw new UsageError('--no-open and --client-name go with --browser', 'shard auth login --browser --no-open');
916
+ if (values.open !== undefined || values.wait !== undefined || str(values['client-name']) !== undefined)
917
+ throw new UsageError('--no-open, --no-wait and --client-name go with --browser', 'shard auth login --browser --no-open');
838
918
  if (values.codex === true)
839
919
  return ACCOUNT_HANDLERS.signup(ctx, { ...values, setup: false, codex: true, email: undefined }, []);
840
920
  const given = str(values.email)?.trim();
@@ -1099,6 +1179,16 @@ export const ACCOUNT_HANDLERS = {
1099
1179
  return 0;
1100
1180
  },
1101
1181
  async setup(ctx, values) {
1182
+ if (values.guide === true) {
1183
+ const guide = agentSetupGuide(ctx);
1184
+ out(ctx, setupGuideText(guide), { guide });
1185
+ return 0;
1186
+ }
1187
+ // Not signed in, without a terminal (0.17.1+): the setup question and both paths' steps instead of an error line.
1188
+ if (ctx.sessionSource() === null && !ctx.json && values.inspect !== true && values['prepare-sample'] !== true && !ctx.input.interactive) {
1189
+ ctx.io.stdout.write(`Not signed in to ${ctx.apiUrl} yet.\n\n${setupGuideText(agentSetupGuide(ctx))}`);
1190
+ return EXIT.auth;
1191
+ }
1102
1192
  if (values['prepare-sample'] === true) {
1103
1193
  const path = await prepareOnboardingSample(ctx.store.dir);
1104
1194
  out(ctx, `Daylight is ready at ${path}\n`, { sample_path: path });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `shard agents accounts` (0.18.0+); with --json every write prints the updated `{accounts, pool}` block (the menu bar
3
+ * redraws from it), plus the account it changed.: the organization's Claude and ChatGPT/Codex accounts, with each one's usage
4
+ * windows, reset times, status and threads (docs/progress/account-pool.md). Sign-ins run the provider's own login in a
5
+ * workspace, as `shard agents credentials login claude` does; API keys come from standard input and are write-only.
6
+ */
7
+ import type { AgentAccount, AgentAccountList } from '@shardflux/sdk';
8
+ import type { Ctx, Handler } from './commands.js';
9
+ import { CodedError } from './errors.js';
10
+ import type { HubCloud } from './cli-proxy-hub.js';
11
+ /** "in 2h 10m", "in 4d 8h", "in 41m"; "now" once passed. */
12
+ export declare function resetIn(resetsAt: string | null, now: number): string | null;
13
+ /** Account references: the id, `<provider>:<n>` (its place in the list, from 1), the email or the label. */
14
+ export declare function findAccount(list: AgentAccountList, ref: string): AgentAccount;
15
+ /** The compact list: one row per account, grouped by provider. */
16
+ export declare function formatAccounts(list: AgentAccountList, now: number): string;
17
+ /** The usage view: every window of every account as a bar with its reset time. */
18
+ export declare function formatUsage(list: AgentAccountList, now: number): string;
19
+ /** Codex's device sign-in page, as Codex 0.160.0 prints it (test/fixtures/cloud-agents/codex-login-device-auth-0.160.0.json). */
20
+ export declare function codexLoginUrls(output: string): string[];
21
+ export declare const ACCOUNT_POOL_HANDLERS: Record<string, Handler>;
22
+ /** The `accounts` block of `shard agents targets --json` (0.18.0+): the account list for the menu bar; null when it cannot be read. */
23
+ export declare function targetsAccounts(ctx: Ctx): Promise<AgentAccountList | null>;
24
+ /** The conversation's thread runs on the account pool (its `account.mode`, API with the pool). */
25
+ export declare function conversationPooled(conversation: unknown): boolean;
26
+ /** A pooled thread has no provider login on its session, so claude.ai, Claude Desktop and the Codex app cannot reach it. */
27
+ export declare const pooledNeedsT3: (ref: string) => CodedError;
28
+ /** Test seam: the hub cloud the pool toggle ensures (the SDK's by default). */
29
+ export declare const accountPoolSeams: {
30
+ hubCloud?: (ctx: Ctx) => HubCloud;
31
+ };