@phnx-labs/agents-cli 1.22.37 → 1.22.39

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 (65) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +8 -8
  3. package/dist/bin/agents +0 -0
  4. package/dist/bootstrap.js +3 -2
  5. package/dist/commands/artifacts-setup.d.ts +53 -0
  6. package/dist/commands/{setup-share.js → artifacts-setup.js} +59 -13
  7. package/dist/commands/artifacts.d.ts +18 -0
  8. package/dist/commands/artifacts.js +58 -0
  9. package/dist/commands/browser.js +2 -0
  10. package/dist/commands/config.js +31 -1
  11. package/dist/commands/exec.js +2 -2
  12. package/dist/commands/models.js +67 -0
  13. package/dist/commands/setup.js +5 -5
  14. package/dist/commands/share.d.ts +20 -7
  15. package/dist/commands/share.js +74 -75
  16. package/dist/commands/ssh.js +156 -8
  17. package/dist/lib/browser/hygiene.d.ts +90 -0
  18. package/dist/lib/browser/hygiene.js +146 -0
  19. package/dist/lib/browser/ipc.js +12 -0
  20. package/dist/lib/browser/service.d.ts +75 -1
  21. package/dist/lib/browser/service.js +201 -11
  22. package/dist/lib/browser/types.d.ts +44 -1
  23. package/dist/lib/config-keys.d.ts +11 -3
  24. package/dist/lib/config-keys.js +22 -3
  25. package/dist/lib/config-machine-keys.js +1 -0
  26. package/dist/lib/device-config.d.ts +34 -0
  27. package/dist/lib/device-config.js +96 -0
  28. package/dist/lib/devices/pool.d.ts +56 -0
  29. package/dist/lib/devices/pool.js +85 -0
  30. package/dist/lib/exec.d.ts +4 -1
  31. package/dist/lib/exec.js +8 -2
  32. package/dist/lib/git.d.ts +1 -1
  33. package/dist/lib/git.js +1 -1
  34. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  35. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  36. package/dist/lib/migrate.d.ts +36 -0
  37. package/dist/lib/migrate.js +107 -0
  38. package/dist/lib/routines.d.ts +3 -1
  39. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  40. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  41. package/dist/lib/share/analytics.js +1 -1
  42. package/dist/lib/share/capture.d.ts +1 -1
  43. package/dist/lib/share/capture.js +3 -3
  44. package/dist/lib/share/config.d.ts +2 -2
  45. package/dist/lib/share/config.js +5 -5
  46. package/dist/lib/share/delete.js +3 -3
  47. package/dist/lib/share/provision.js +3 -3
  48. package/dist/lib/share/publish.d.ts +1 -1
  49. package/dist/lib/share/publish.js +3 -3
  50. package/dist/lib/share/worker-template.d.ts +12 -1
  51. package/dist/lib/share/worker-template.js +13 -2
  52. package/dist/lib/smart-launch.d.ts +33 -3
  53. package/dist/lib/smart-launch.js +61 -6
  54. package/dist/lib/startup/command-registry.d.ts +10 -2
  55. package/dist/lib/startup/command-registry.js +16 -7
  56. package/dist/lib/tmux/orphan-reap.d.ts +15 -19
  57. package/dist/lib/tmux/orphan-reap.js +15 -21
  58. package/dist/lib/tmux/session.js +4 -3
  59. package/dist/lib/triggers/handlers.js +10 -0
  60. package/dist/lib/triggers/webhook.js +10 -0
  61. package/dist/lib/types.d.ts +2 -2
  62. package/package.json +1 -1
  63. package/dist/commands/set.d.ts +0 -15
  64. package/dist/commands/set.js +0 -79
  65. package/dist/commands/setup-share.d.ts +0 -17
package/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.39
4
+
5
+ - **The auto-detected `default` browser profile no longer sits in the fleet-shared `agents.yaml`, which was wedging `agents repos pull user` fleet-wide (RUSH-2161).** `browser` is a central key because named profiles a user creates are real fleet config, but the one `default` entry inside it is machine-local: its `binary` is an OS-specific path and its endpoint is a locally chosen free port. `createProfile`/`updateProfile` have routed that entry to the per-device file since 1.22.38 (`isMachineLocalProfile`), but nothing removed the copy older versions had already written into the shared file, and `serializeCentral` could not — it deletes whole device-scoped KEYS, and `browser` is not one. So the stale entry stayed committed and every box rewrote it with its own browser. Measured across three boxes all running 1.22.38: zion `chrome` + `/Applications/Google Chrome.app/...`, yosemite-s1 `brave` + `/opt/brave.com/brave/brave`, mark-1 the same shape, each with an empty `deviceBrowser`. Because the pull refuses when an incoming change touches a locally modified path, a permanently dirty `agents.yaml` blocked fleet config sync outright — those boxes sat 5, 8 and 79 commits behind, so merged config (a `notify.owner` block, device roles) reached zero machines. A new migration moves the entry into `devices/<machine>/agents.yaml` and deletes it from central, writing the device file first so a crash cannot lose the profile and keeping an existing device entry when one is already there. Central is edited through a YAML `Document`, so the hand-written comments in the committed file survive rather than being flattened by a re-stringify — which would have re-created the same churn. Named profiles are untouched. Source: `apps/cli/src/lib/migrate.ts`, `apps/cli/src/bootstrap.ts`.
6
+
7
+ - **`agents config set devices.<name>.tmux off` turns the interactive tmux wrap off for one machine (RUSH-2620).** Interactive `agents run` wraps the harness in the shared-socket tmux session so every agent gets an addressable `%pane` — that is what lets `agents sessions --active` tell co-located agents apart and `agents focus` re-attach without forking. Until now the only ways out were per-run (`--raw` / `--no-tmux` / `--disable-tmux`) or the `AGENTS_NO_TMUX=1` env var, so a box whose tmux is broken needed the flag retyped on every launch. The new key is machine-local by design (a broken tmux is a property of one machine): it never enters the fleet-shared `agents.yaml` and is refused for a peer. Unset still means wrap. `agents devices config <name> tmux.enabled off` sets the same key. Source: `apps/cli/src/lib/device-config.ts` (`isTmuxEnabled`), `apps/cli/src/lib/exec.ts` (`shouldWrapInTmux`).
8
+
9
+ - **`agents devices role <name> worker` marks which boxes agents run on, and `--device auto` follows it.** Roles are fleet-wide (`worker` / `personal`), stored in the shared `fleet.devices.<name>.config.role` block of `~/.agents/agents.yaml`, so a mark set on any box travels with `agents repo push/pull` — the per-device files under `~/.agents/devices/` are written only by the machine they name, so they could never carry a fleet-wide statement about a different box. Marking ANY device `worker` turns automatic placement into an allowlist: `agents run --device auto`, `agents teams add --device auto`, and the AGI EXT launch commands then pick only from the marked workers. A device marked `personal` (a machine you sit at) is never picked automatically, under any mode, and a paired cockpit stays excluded through its existing registry `control` role. When roles leave the pool empty, `--device auto` fails loud naming the fix instead of quietly running on the local machine — through `agents ssh auto` and the `--host auto` passthrough too, not just `agents run`. Nothing marked = today's behavior, every online device. Widen it back with `agents config set auto.pool all`. `agents devices role` with no arguments prints who is marked what and exactly which devices `--device auto` would consider; `agents devices list` tags marked rows and `--json` carries `role` plus an `autoPool` boolean per device. Source: `apps/cli/src/lib/devices/pool.ts`, `apps/cli/src/lib/device-config.ts`, `apps/cli/src/lib/smart-launch.ts`.
10
+
11
+ ## 1.22.38
12
+
13
+ - Webhook `stateTo` triggers and handlers now fire only on the delivery that actually moved a Linear issue INTO that state, not on every later update while it still sits there. Previously a `stateTo: Plan` handler re-matched on any subsequent `Issue/update` — a label edit, an assignee change, a description touch — because it checked the issue's *current* state instead of the transition, which accumulated 11 duplicate plan comments on one issue. It now additionally requires the delivery's `updatedFrom` to record a state change. (RUSH-2539)
14
+
15
+ - **A new release home base no longer needs the provisioning profile hand-copied over (RUSH-2541).** `apps/cli/bin/embedded.provisionprofile` became a committed, tracked file in commit `2567004b4` (RUSH-2535), but `release.sh`'s home-base seed step and `signing-home-base-probe.sh`'s preflight both still checked only the home base's own on-disk working tree -- so a legitimately new home base (or one whose local checkout simply predated that commit) still reported "unprovisioned" or died at the sign step with a misleading "generate at developer.apple.com" message, even though `git fetch origin` (which both already run) had the file the whole time. Both now recover the blob from the freshly fetched `origin/<default>` ref when it is absent on disk, and the seed step fails loud with the correct recovery guidance (recover from git history; do not regenerate at Apple's portal) instead of limping forward on a warning. Source: `apps/cli/scripts/release.sh`, `apps/cli/scripts/signing-home-base-probe.sh`, `apps/cli/scripts/build-keychain-helper.sh`.
16
+
17
+ - **`release.sh`'s home-base provisionprofile recovery no longer dies silently under `set -e` on a freshly bootstrapped checkout (RUSH-2541 follow-up).** The `DEFAULT_BRANCH="$(git symbolic-ref ...)"` assignment added to recover `embedded.provisionprofile` from `origin/<default>` ran under `home_base_wt_snippet`'s own `set -euo pipefail`; `git symbolic-ref` returns non-zero whenever `refs/remotes/origin/HEAD` is unset, which is the normal state of a checkout bootstrapped via `init && remote add && fetch` rather than `clone` -- plausibly a brand-new fleet home base, exactly the box this feature targets. Under `set -e` the bare failing assignment tripped errexit at that line, killing the whole home-base phase with zero output before even the "main" fallback on the next line ran. Guarded with `|| true`, matching the pattern `assert_signing_home_base` already established for the identical anti-pattern. Source: `apps/cli/scripts/release.sh`.
18
+
19
+ - **Top-level `agents set` is removed — use `agents models set` (RUSH-2579).** `agents set claude@2.1.220 --model opus-5` moves to `agents models set claude@2.1.220 --model opus-5`, unchanged otherwise: same selector forms (`<agent>@<version>`, `<agent>:*`), same `--model`/`--mode` flags, same underlying `run.defaults` store (`agents config get run.<agent@version>.model` still reads it). `agents set` now reports `unknown command`. Source: `apps/cli/src/commands/models.ts`, `apps/cli/src/lib/startup/command-registry.ts`.
20
+
21
+ - **BREAKING: `agents share` moved under a new `agents artifacts` group (RUSH-2580).**
22
+ `artifacts` is the noun and `share` the action on it, so the surface now reads
23
+ noun-then-action like the rest of the CLI. The whole subtree moved down one
24
+ level — `agents artifacts share <file>` publishes, and
25
+ `agents artifacts share list|delete|analytics|join|status|update` are unchanged
26
+ under it. The two provisioning doors collapsed into one:
27
+ `agents artifacts setup` replaces both `agents share setup` (flag-driven) and
28
+ `agents setup share` (the wizard). It runs the wizard only when no endpoint
29
+ flag is typed on a TTY; type any of `--bundle`/`--worker`/`--bucket`/
30
+ `--account`/`--token`/`--domain`/`--analytics-token`, or run non-interactively,
31
+ and it provisions directly with what you named. The top-level `agents share` group and the
32
+ `agents setup share` subcommand no longer exist; `share` is retired from
33
+ distance-1 auto-correct, so a stale invocation fails loudly instead of running a
34
+ neighbouring command. `agents unshare <targets...>` is unchanged and stays
35
+ top-level. Source: `apps/cli/src/commands/artifacts.ts`,
36
+ `apps/cli/src/commands/artifacts-setup.ts`, `apps/cli/src/commands/share.ts`,
37
+ `apps/cli/src/commands/setup.ts`, `apps/cli/src/lib/startup/command-registry.ts`.
38
+
39
+ ### Fixed
40
+
41
+ - The daemon orphan reaper no longer terminates live agents after a tmux server restart. A missing tmux session is now treated as unknown; helper processes are reaped only when their pane owner is present and confirmed dead, or a harness-specific rule proves their declared spawner exited.
42
+
43
+ - **`agents browser` stops leaving duplicate and orphan tabs behind (RUSH-2622).**
44
+ The `about:blank` that a bare `agents browser start` opens is now registered on
45
+ the task, so `done`/`stop` actually closes it — it never was, which made every
46
+ bare start leak one tab permanently. `agents browser start --url` now reclaims
47
+ a tab that an abandoned task is still holding on that exact URL instead of
48
+ opening a duplicate; a tab held by a live task, or one you opened yourself, is
49
+ never taken, and the new `--fresh` skips the reclaim entirely. Tasks also carry
50
+ a `lastActionAt` stamp in `tasks.json` now. Source:
51
+ `apps/cli/src/lib/browser/service.ts`, `apps/cli/src/lib/browser/types.ts`.
52
+
3
53
  ## 1.22.37
4
54
 
5
55
  - **`agents.yaml`'s five writers now emit identical bytes, closing the rest of the sync-blocking drift (RUSH-2505).**
package/README.md CHANGED
@@ -1158,14 +1158,14 @@ Sources: a command's stdout (`--watch` / `--poll`), an HTTP endpoint (`--poll-ht
1158
1158
 
1159
1159
  ```bash
1160
1160
  # Publish an HTML artifact to a public link on your own Cloudflare R2 (~$0).
1161
- agents share setup # once: provision bucket + Worker on your CF
1162
- agents share plan.html --slug fleet --expire 30d # → https://<base>/fleet
1163
- agents share plan.html --json # URL object for plan-render hooks
1164
- agents share status # show the endpoint
1165
- agents unshare fleet # take a published link (+ its OG cover) down
1161
+ agents artifacts setup # once: provision bucket + Worker on your CF
1162
+ agents artifacts share plan.html --slug fleet --expire 30d # → https://<base>/fleet
1163
+ agents artifacts share plan.html --json # URL object for plan-render hooks
1164
+ agents artifacts share status # show the endpoint
1165
+ agents unshare fleet # take a published link (+ its OG cover) down
1166
1166
  ```
1167
1167
 
1168
- `agents share` closes the loop: an agent makes work (a plan, a viz, a report),
1168
+ `agents artifacts share` closes the loop: an agent makes work (a plan, a viz, a report),
1169
1169
  publishes it, and you open the link to see it. `setup` reads a Cloudflare API token
1170
1170
  from your `cloudflare.com` secrets bundle (or `--token`), creates an R2 bucket, uploads
1171
1171
  a tiny Worker, and enables the free `*.workers.dev` subdomain (or maps `--domain
@@ -1175,12 +1175,12 @@ the Worker (its R2 binding does the put, so the client needs no S3 keys); reads
1175
1175
  this is effectively free.
1176
1176
 
1177
1177
  **Fleet mode:** provision one endpoint, then every fleet / cloud / ephemeral agent
1178
- publishes through it with a shared write token — `agents share join <baseUrl>` uses an
1178
+ publishes through it with a shared write token — `agents artifacts share join <baseUrl>` uses an
1179
1179
  existing endpoint with no provisioning. `--expire 30d|12h|<date>` auto-expires a link.
1180
1180
  `--json` emits `{ url, coverUrl, expiresAt }` so plan-render automation can publish the
1181
1181
  rendered HTML and post the returned link without scraping terminal text.
1182
1182
 
1183
- `agents share delete <targets...>` (alias `agents unshare`) takes a page down — pass a
1183
+ `agents artifacts share delete <targets...>` (alias `agents unshare`) takes a page down — pass a
1184
1184
  full URL, `<user>/<slug>`, or a bare slug (resolved against your own namespace); several
1185
1185
  targets at once are fine. It also deletes the sibling `<slug>.png` OG cover by default
1186
1186
  (`--keep-cover` opts out) and verifies the page actually 404s before reporting success —
package/dist/bin/agents CHANGED
Binary file
package/dist/bootstrap.js CHANGED
@@ -254,6 +254,7 @@ Run and dispatch:
254
254
  logs [id] Show a run's log — host-dispatch task or session; -f to follow
255
255
  browser Automate a browser — navigate, click, screenshot, console, network
256
256
  pty Drive interactive terminal programs (REPLs, TUIs) via a persistent PTY session
257
+ artifacts Publish what an agent made (plans, reports, visuals) to a shareable link
257
258
 
258
259
  Observe (read the fleet — no store merge; aliases point at the real readers):
259
260
  feed / inbox Needs-you inbox (open blocks waiting on you)
@@ -1094,7 +1095,7 @@ const SETUP_EXEMPT_COMMANDS = new Set(['setup', 'help', 'uninstall']);
1094
1095
  //
1095
1096
  // Skipped for --help/--version (RUSH-2454): pure documentation paths must not
1096
1097
  // load any migration graph. Loaded from migrate-fold.js (leaf: fs + createLink),
1097
- // not migrate.js, so a real command pays only the fold hop unless the v19
1098
+ // not migrate.js, so a real command pays only the fold hop unless the v20
1098
1099
  // sentinel is missing and runMigration() is required below.
1099
1100
  if (process.env.AGENTS_SKIP_MIGRATION !== '1' && !helpOrVersionRequested) {
1100
1101
  try {
@@ -1129,7 +1130,7 @@ if (process.env.AGENTS_SKIP_MIGRATION !== '1' && !helpOrVersionRequested) {
1129
1130
  // Bumping the suffix re-runs migrations for every user; binary releases that
1130
1131
  // don't change the schema must NOT re-run (they would destroy user content
1131
1132
  // when migration steps overlap with user-authored paths). See issue #20.
1132
- const sentinelValue = 'v19';
1133
+ const sentinelValue = 'v20';
1133
1134
  let needRun = true;
1134
1135
  try {
1135
1136
  if (fs.existsSync(sentinel) && fs.readFileSync(sentinel, 'utf-8').trim() === sentinelValue) {
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `agents artifacts setup` — configure the artifact share endpoint (Cloudflare
3
+ * R2 + Worker) that backs `agents artifacts share`.
4
+ *
5
+ * One door, two modes, because it replaces two commands that used to be
6
+ * separate (RUSH-2580): the interactive wizard formerly at `agents setup share`
7
+ * and the flag-driven provisioner formerly at `agents share setup`. On a TTY
8
+ * with no provisioning flags TYPED it runs the wizard (provision or join, and
9
+ * offers a Worker update when an endpoint already exists); with any provisioning
10
+ * flag typed, or on a non-TTY, it provisions directly. Both modes call the same
11
+ * `runShareProvision` / `runShareJoin` in commands/share.ts, so there is a
12
+ * single source of truth for provisioning.
13
+ *
14
+ * "Typed", not "present", is load-bearing — see {@link isDirectProvisionRequest}.
15
+ *
16
+ * Idempotent: re-running shows the current endpoint and offers to reconfigure.
17
+ */
18
+ import type { Command } from 'commander';
19
+ /**
20
+ * Interactive share setup. Returns true if the user configured (or already had)
21
+ * an endpoint, false if they skipped. Never throws on user cancel — callers
22
+ * (the `agents setup` hub) rely on that to keep the fresh-machine flow going.
23
+ */
24
+ export declare function runShareWizard(): Promise<boolean>;
25
+ /** Parsed `agents artifacts setup` options — the retired `agents share setup` flag set. */
26
+ interface ArtifactsSetupOpts {
27
+ bundle: string;
28
+ worker: string;
29
+ bucket: string;
30
+ account?: string;
31
+ token?: string;
32
+ domain?: string;
33
+ analyticsToken?: string;
34
+ }
35
+ /** The provisioning flags, by their commander option-value keys. */
36
+ declare const PROVISION_FLAGS: readonly ["bundle", "worker", "bucket", "account", "token", "domain", "analyticsToken"];
37
+ /**
38
+ * True when the invocation named a Cloudflare endpoint detail, i.e. the caller
39
+ * is driving the old `agents share setup` provisioning path rather than asking
40
+ * for the wizard.
41
+ *
42
+ * `--bundle`/`--worker`/`--bucket` carry commander defaults, so their mere
43
+ * PRESENCE proves nothing — the question is whether the user typed them, which
44
+ * only `wasTyped` (backed by commander's `getOptionValueSource`) can answer.
45
+ * Reading presence alone would classify `agents artifacts setup --bundle
46
+ * cloudflare-work` as a wizard run and then silently provision against the
47
+ * DEFAULT bundle, i.e. someone else's Cloudflare account. The other four flags
48
+ * have no default, so presence is proof for them.
49
+ */
50
+ export declare function isDirectProvisionRequest(opts: ArtifactsSetupOpts, wasTyped?: (flag: (typeof PROVISION_FLAGS)[number]) => boolean): boolean;
51
+ /** Register `agents artifacts setup` under the parent `artifacts` command. */
52
+ export declare function registerArtifactsSetupCommand(artifactsCmd: Command): void;
53
+ export {};
@@ -1,13 +1,22 @@
1
1
  /**
2
- * `agents setup share` — interactive wizard to configure the `agents share`
3
- * endpoint (Cloudflare R2 + Worker). A friendly front door over the existing
4
- * `agents share setup` (provision) and `agents share join` flows, reusing their
5
- * exact logic so there is a single source of truth for provisioning.
2
+ * `agents artifacts setup` — configure the artifact share endpoint (Cloudflare
3
+ * R2 + Worker) that backs `agents artifacts share`.
4
+ *
5
+ * One door, two modes, because it replaces two commands that used to be
6
+ * separate (RUSH-2580): the interactive wizard formerly at `agents setup share`
7
+ * and the flag-driven provisioner formerly at `agents share setup`. On a TTY
8
+ * with no provisioning flags TYPED it runs the wizard (provision or join, and
9
+ * offers a Worker update when an endpoint already exists); with any provisioning
10
+ * flag typed, or on a non-TTY, it provisions directly. Both modes call the same
11
+ * `runShareProvision` / `runShareJoin` in commands/share.ts, so there is a
12
+ * single source of truth for provisioning.
13
+ *
14
+ * "Typed", not "present", is load-bearing — see {@link isDirectProvisionRequest}.
6
15
  *
7
16
  * Idempotent: re-running shows the current endpoint and offers to reconfigure.
8
17
  */
9
18
  import chalk from 'chalk';
10
- import { DEFAULT_BUCKET_NAME, DEFAULT_CF_BUNDLE, DEFAULT_WORKER_NAME, readShareConfig, } from '../lib/share/config.js';
19
+ import { DEFAULT_BUCKET_NAME, DEFAULT_CF_BUNDLE, DEFAULT_SHARE_DOMAIN, DEFAULT_WORKER_NAME, readShareConfig, } from '../lib/share/config.js';
11
20
  import { runShareProvision, runShareJoin, runShareUpdate, shareTemplateStatus } from './share.js';
12
21
  import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
13
22
  /**
@@ -17,8 +26,9 @@ import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
17
26
  */
18
27
  export async function runShareWizard() {
19
28
  if (!isInteractiveTerminal()) {
20
- console.error(chalk.red('agents setup share needs an interactive terminal. ' +
21
- 'Non-interactively, use `agents share setup` (provision) or `agents share join [url]`.'));
29
+ console.error(chalk.red('The artifact share wizard needs an interactive terminal. ' +
30
+ 'Non-interactively, use `agents artifacts setup --account <id> --token <t>` (provision) ' +
31
+ 'or `agents artifacts share join <baseUrl> --token <t>`.'));
22
32
  return false;
23
33
  }
24
34
  const existing = readShareConfig();
@@ -94,13 +104,49 @@ export async function runShareWizard() {
94
104
  await runShareJoin(baseUrl.trim());
95
105
  return true;
96
106
  }
97
- /** Register `agents setup share` under the parent `setup` command. */
98
- export function registerSetupShareCommand(setupCmd) {
99
- setupCmd
100
- .command('share')
101
- .description('Configure the `agents share` endpoint (Cloudflare R2 + Worker) — provision your own or join one.')
102
- .action(async () => {
107
+ /** The provisioning flags, by their commander option-value keys. */
108
+ const PROVISION_FLAGS = ['bundle', 'worker', 'bucket', 'account', 'token', 'domain', 'analyticsToken'];
109
+ /**
110
+ * True when the invocation named a Cloudflare endpoint detail, i.e. the caller
111
+ * is driving the old `agents share setup` provisioning path rather than asking
112
+ * for the wizard.
113
+ *
114
+ * `--bundle`/`--worker`/`--bucket` carry commander defaults, so their mere
115
+ * PRESENCE proves nothing — the question is whether the user typed them, which
116
+ * only `wasTyped` (backed by commander's `getOptionValueSource`) can answer.
117
+ * Reading presence alone would classify `agents artifacts setup --bundle
118
+ * cloudflare-work` as a wizard run and then silently provision against the
119
+ * DEFAULT bundle, i.e. someone else's Cloudflare account. The other four flags
120
+ * have no default, so presence is proof for them.
121
+ */
122
+ export function isDirectProvisionRequest(opts, wasTyped = () => false) {
123
+ if (opts.account || opts.token || opts.domain || opts.analyticsToken)
124
+ return true;
125
+ return PROVISION_FLAGS.some((flag) => wasTyped(flag));
126
+ }
127
+ /** Register `agents artifacts setup` under the parent `artifacts` command. */
128
+ export function registerArtifactsSetupCommand(artifactsCmd) {
129
+ artifactsCmd
130
+ .command('setup')
131
+ .description('Provision (or join) the Cloudflare R2 + Worker endpoint that backs `agents artifacts share`.')
132
+ .option('--bundle <name>', 'secrets bundle holding the Cloudflare API token', DEFAULT_CF_BUNDLE)
133
+ .option('--worker <name>', 'Worker name', DEFAULT_WORKER_NAME)
134
+ .option('--bucket <name>', 'R2 bucket name', DEFAULT_BUCKET_NAME)
135
+ .option('--account <id>', 'Cloudflare account id (else read from the bundle / prompt)')
136
+ .option('--token <t>', 'Cloudflare API token (else read from the --bundle)')
137
+ .option('--domain <host>', `custom domain to map (default: ${DEFAULT_SHARE_DOMAIN}; workers.dev if zone is not visible)`)
138
+ .option('--analytics-token <token>', 'Cloudflare Web Analytics token to inject into published HTML pages')
139
+ .action(async (opts, cmd) => {
103
140
  try {
141
+ // Named endpoint details, or no terminal to prompt on, means provision
142
+ // directly — the wizard has nothing to ask that was not already given,
143
+ // and it would provision against its own hardcoded defaults, dropping
144
+ // whatever the caller named.
145
+ const wasTyped = (flag) => cmd.getOptionValueSource(flag) === 'cli';
146
+ if (isDirectProvisionRequest(opts, wasTyped) || !isInteractiveTerminal()) {
147
+ await runShareProvision(opts);
148
+ return;
149
+ }
104
150
  await runShareWizard();
105
151
  }
106
152
  catch (err) {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `agents artifacts` — the group that owns everything an agent publishes for a
3
+ * human to open: HTML plans, reports, rendered visuals, screenshots.
4
+ *
5
+ * RUSH-2580 nested the former top-level `agents share` here. `artifacts` is the
6
+ * noun, `share` is the action on it, so the surface reads noun-then-action like
7
+ * the rest of the CLI:
8
+ *
9
+ * agents artifacts share <file> publish
10
+ * agents artifacts share list|delete|analytics|join|status|update
11
+ * agents artifacts setup provision (or join) the endpoint
12
+ *
13
+ * The subtree itself lives in commands/share.ts (publish + its subcommands) and
14
+ * commands/artifacts-setup.ts (the provisioning door); this module only assembles
15
+ * the group, so there is one place that says what `agents artifacts` contains.
16
+ */
17
+ import type { Command } from 'commander';
18
+ export declare function registerArtifactsCommands(program: Command): void;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * `agents artifacts` — the group that owns everything an agent publishes for a
3
+ * human to open: HTML plans, reports, rendered visuals, screenshots.
4
+ *
5
+ * RUSH-2580 nested the former top-level `agents share` here. `artifacts` is the
6
+ * noun, `share` is the action on it, so the surface reads noun-then-action like
7
+ * the rest of the CLI:
8
+ *
9
+ * agents artifacts share <file> publish
10
+ * agents artifacts share list|delete|analytics|join|status|update
11
+ * agents artifacts setup provision (or join) the endpoint
12
+ *
13
+ * The subtree itself lives in commands/share.ts (publish + its subcommands) and
14
+ * commands/artifacts-setup.ts (the provisioning door); this module only assembles
15
+ * the group, so there is one place that says what `agents artifacts` contains.
16
+ */
17
+ import { setHelpSections } from '../lib/help.js';
18
+ import { registerShareCommands, registerUnshareCommand } from './share.js';
19
+ import { registerArtifactsSetupCommand } from './artifacts-setup.js';
20
+ export function registerArtifactsCommands(program) {
21
+ const artifactsCmd = program
22
+ .command('artifacts')
23
+ .description('Publish agent-made artifacts (plans, reports, visuals) to your own Cloudflare R2 and get a shareable link (~$0).');
24
+ registerArtifactsSetupCommand(artifactsCmd);
25
+ registerShareCommands(artifactsCmd);
26
+ setHelpSections(artifactsCmd, {
27
+ examples: `
28
+ # One-time: provision your own endpoint (or join a teammate's)
29
+ agents artifacts setup
30
+ agents artifacts share join https://share.agents-cli.sh
31
+
32
+ # Publish — auto OG cover, default 30d expiry, shareable link
33
+ agents artifacts share ./out/plan.html
34
+
35
+ # What's live in your namespace, and taking one down
36
+ agents artifacts share list
37
+ agents unshare my-plan-a1b2
38
+
39
+ # Endpoint health, and pushing a new Worker template to it
40
+ agents artifacts share status
41
+ agents artifacts share update
42
+ `,
43
+ notes: `
44
+ agents artifacts share is the publish action; agents artifacts setup is the
45
+ one-time endpoint provisioning. It runs the interactive wizard only when you
46
+ type NO endpoint flag on a TTY; type any of --bundle/--worker/--bucket/
47
+ --account/--token/--domain/--analytics-token, or run non-interactively, and it
48
+ provisions directly with what you named.
49
+
50
+ agents unshare <targets...> stays a top-level alias of
51
+ agents artifacts share delete.
52
+
53
+ Full reference: apps/cli/docs/share.md.
54
+ `,
55
+ });
56
+ // Top-level, deliberately: see registerUnshareCommand in commands/share.ts.
57
+ registerUnshareCommand(program);
58
+ }
@@ -699,6 +699,7 @@ function registerTaskCommands(browser) {
699
699
  .option(TASK_OPTION_FLAG, 'Task name (auto-generated if omitted)')
700
700
  .option('-e, --endpoint <name>', 'Endpoint preset (defaults to the profile\'s default)')
701
701
  .option('-u, --url <url>', 'Open URL in first tab')
702
+ .option('--fresh', 'Always open a new tab, skipping the reclaim of a tab an abandoned task is holding on that URL')
702
703
  .option('--no-skills', 'Skip auto-discovery of site-specific SKILL.md from ~/.agents/skills/browser/domain-skills/')
703
704
  .option('--record', 'Start recording right after the tab opens (shorthand for `agents browser record start` as a follow-up)')
704
705
  .option('--fps <n>', 'Recording frames per second (with --record; 1–30, default 5)', (v) => parseInt(v, 10))
@@ -791,6 +792,7 @@ function registerTaskCommands(browser) {
791
792
  url: opts.url,
792
793
  endpoint: opts.endpoint,
793
794
  skipDomainSkill: opts.skills === false,
795
+ fresh: opts.fresh === true,
794
796
  // Forward the caller's identity: the browser daemon is shared, so it
795
797
  // cannot resolve who/which-run called `start`. `resolveActor()` runs
796
798
  // here in the CLI (the caller's process); `$AGENT_LAUNCH_ID` is the
@@ -41,12 +41,17 @@ function parseValue(key, parsed, raw) {
41
41
  return raw.trim();
42
42
  case 'usage':
43
43
  return raw.trim();
44
+ case 'auto':
45
+ return raw.trim();
44
46
  case 'browser':
45
47
  return raw.trim();
46
48
  case 'project':
47
49
  return raw.trim();
48
50
  case 'device': {
49
- switch (parsed.property) {
51
+ const property = parsed.property;
52
+ switch (property) {
53
+ case 'role':
54
+ return raw.trim();
50
55
  case 'max-agents':
51
56
  if (!/^\d+$/.test(raw.trim())) {
52
57
  throw new Error(`Config key '${key}' expects an integer, got '${raw}'.`);
@@ -55,6 +60,7 @@ function parseValue(key, parsed, raw) {
55
60
  case 'scheduler':
56
61
  case 'daemon':
57
62
  case 'watchdog':
63
+ case 'tmux':
58
64
  case 'browser.remote-control':
59
65
  return parseBool(raw, key);
60
66
  case 'notes':
@@ -62,6 +68,13 @@ function parseValue(key, parsed, raw) {
62
68
  case 'browser.profile':
63
69
  return raw.trim();
64
70
  }
71
+ // A device property with no arm above used to fall out of the switch and
72
+ // return `undefined`, so the write failed downstream with "expects a
73
+ // boolean, got undefined" instead of naming the real gap. The `never`
74
+ // binding makes adding a DeviceConfigProperty without a parse rule a
75
+ // compile error rather than a runtime mystery.
76
+ const unhandled = property;
77
+ throw new Error(`Config key '${key}' has no parse rule for device property '${String(unhandled)}'.`);
65
78
  }
66
79
  }
67
80
  }
@@ -93,6 +106,10 @@ function setConfig(parsed, value) {
93
106
  setConfigValue('usage.primary-host', value);
94
107
  return;
95
108
  }
109
+ case 'auto': {
110
+ setConfigValue('auto.pool', value);
111
+ return;
112
+ }
96
113
  case 'browser': {
97
114
  // Device-local default lives in the central fleet.devices.<name>.config
98
115
  // block (same store `agents devices config` / getConfigValue use). Bare
@@ -137,6 +154,11 @@ function unsetConfig(parsed) {
137
154
  unsetConfigValue('usage.primary-host');
138
155
  return had;
139
156
  }
157
+ case 'auto': {
158
+ const had = getConfigValue('auto.pool').value !== undefined;
159
+ unsetConfigValue('auto.pool');
160
+ return had;
161
+ }
140
162
  case 'browser': {
141
163
  const target = parsed.device ? { device: parsed.device } : undefined;
142
164
  const had = getConfigValue('browser.profile', target).value !== undefined;
@@ -173,6 +195,8 @@ function getConfig(parsed) {
173
195
  return getConfigValue('interactive.host').value;
174
196
  case 'usage':
175
197
  return getConfigValue('usage.primary-host').value;
198
+ case 'auto':
199
+ return getConfigValue('auto.pool').value;
176
200
  case 'browser': {
177
201
  return getConfigValue('browser.profile', parsed.device ? { device: parsed.device } : undefined).value;
178
202
  }
@@ -231,6 +255,9 @@ function* listCentralConfigEntries() {
231
255
  if (meta.config?.usagePrimaryHost !== undefined) {
232
256
  yield { key: 'usage.primary-host', value: meta.config.usagePrimaryHost, hint: 'config.usagePrimaryHost' };
233
257
  }
258
+ if (meta.config?.autoPool !== undefined) {
259
+ yield { key: 'auto.pool', value: meta.config.autoPool, hint: 'config.autoPool' };
260
+ }
234
261
  if (meta.projectRoot !== undefined) {
235
262
  yield { key: 'project.root', value: meta.projectRoot, hint: 'devices.<self>.projectRoot' };
236
263
  }
@@ -262,6 +289,9 @@ function* listDeviceConfigEntries(device) {
262
289
  case 'watchdog.enabled':
263
290
  key = `${prefix}watchdog`;
264
291
  break;
292
+ case 'tmux.enabled':
293
+ key = `${prefix}tmux`;
294
+ break;
265
295
  case 'browser.remote-control':
266
296
  key = `${prefix}browser.remote-control`;
267
297
  break;
@@ -557,7 +557,7 @@ export function registerRunCommand(program) {
557
557
  .option('--terminal [backend]', "Open this run in a real terminal tab instead of here. Without a value the terminal is detected from your live sessions (`agents sessions --active` host), so it lands where you already work — Ghostty for a Ghostty user, iTerm for an iTerm user. Name one to force it: iterm | ghostty | terminal | tmux | vscodium-agent. This is how the menu bar's New Session opens.")
558
558
  .option('--verbose', 'Show detailed execution logs')
559
559
  .option('--raw', 'Interactive runs on macOS/Linux launch inside a shared tmux session (for %pane addressing + re-attach). Pass --raw to spawn the agent directly instead. Also disabled by AGENTS_NO_TMUX=1.')
560
- .option('--no-tmux', 'Spawn the agent directly instead of wrapping it in the shared tmux session. Same effect as --raw / AGENTS_NO_TMUX=1. Use this to see the agent\'s full startup output when a launch is failing.')
560
+ .option('--no-tmux', 'Spawn the agent directly instead of wrapping it in the shared tmux session. Same effect as --raw / AGENTS_NO_TMUX=1. Use this to see the agent\'s full startup output when a launch is failing; to turn the wrap off for every run on this machine, set `agents config set devices.<name>.tmux off`.')
561
561
  .option('--disable-tmux', 'Alias for --no-tmux.')
562
562
  .option('--timeout <duration>', 'Kill the agent after this duration (e.g., 30m, 1h, 2h30m)')
563
563
  .option('--fallback <agents>', 'Comma-separated agents to try on rate-limit failure. Each entry accepts an optional @version pin (e.g., codex@0.116.0,antigravity). The primary runs first; if it exits with a rate-limit error, the next agent picks up via /continue handoff.')
@@ -2699,7 +2699,7 @@ export function registerRunCommand(program) {
2699
2699
  // Profile carries provider auth; secrets bundles carry user-defined
2700
2700
  // values; --env is the per-invocation override. The share token is
2701
2701
  // best-effort: if it is not already in env or an unlocked bundle, unrelated
2702
- // runs keep working, and `agents share` itself still fails loudly on use.
2702
+ // runs keep working, and `agents artifacts share` itself still fails loudly on use.
2703
2703
  const hasOverrides = profileEnv || accountEnv || autoShareEnv || options.secrets.length > 0 || userEnv;
2704
2704
  const env = hasOverrides
2705
2705
  ? { ...(profileEnv ?? {}), ...(accountEnv ?? {}), ...(autoShareEnv ?? {}), ...secretsEnv, ...(userEnv ?? {}) }
@@ -16,6 +16,8 @@ import { setTierOverride, clearTierOverride, listTierOverrides } from '../lib/mo
16
16
  import { getModelPricing } from '../lib/pricing/index.js';
17
17
  import { terminalWidth, truncateToWidth, stringWidth } from '../lib/session/width.js';
18
18
  import { wrapJoined } from './inspect.js';
19
+ import { setHelpSections } from '../lib/help.js';
20
+ import { formatRunDefaultEntry, listRunDefaults, parseRunDefaultSelector, setRunDefault, } from '../lib/run-defaults.js';
19
21
  const MODEL_CAPABLE_AGENTS = ['claude', 'codex', 'opencode', 'cursor', 'openclaw', 'antigravity', 'kimi', 'grok', 'droid', 'pi'];
20
22
  /**
21
23
  * Agents that don't necessarily install under ~/.agents/versions (cursor ships
@@ -75,6 +77,71 @@ export function registerModelsCommand(program) {
75
77
  console.log(chalk.gray(' Permission modes (--mode plan|edit|auto|skip): `agents modes <agent>`'));
76
78
  }
77
79
  });
80
+ // `models set` — the ergonomic setter for per-agent/version run defaults. It
81
+ // reads and writes the same store as `agents config set run.<agent@version>.*`
82
+ // (agents.yaml -> run.defaults), so the two stay consistent — `set` is just the
83
+ // short front door, nested here because `models` owns model/mode concerns.
84
+ const set = models
85
+ .command('set [selector]')
86
+ .description('Set the default model/mode an agent version uses for `agents run`')
87
+ .option('--model <model>', 'Default model or model alias, forwarded via --model')
88
+ .option('--mode <mode>', "Default mode: plan, edit, auto, skip. 'full' accepted as alias for skip.")
89
+ .action((selector, options) => {
90
+ try {
91
+ const hasFlags = options.model !== undefined || options.mode !== undefined;
92
+ if (!selector) {
93
+ if (hasFlags) {
94
+ throw new Error('Selector is required when passing --model/--mode. Example: agents models set claude@2.1.220 --model opus-5');
95
+ }
96
+ const entries = listRunDefaults();
97
+ if (entries.length === 0) {
98
+ console.log(chalk.gray('No agent defaults configured.'));
99
+ console.log(chalk.gray('Set one with: agents models set claude@2.1.220 --model opus-5'));
100
+ return;
101
+ }
102
+ console.log(chalk.bold('Agent Defaults\n'));
103
+ for (const entry of entries) {
104
+ console.log(` ${formatRunDefaultEntry(entry)}`);
105
+ }
106
+ return;
107
+ }
108
+ if (!hasFlags) {
109
+ const parsed = parseRunDefaultSelector(selector);
110
+ const entry = listRunDefaults().find((e) => e.selector === parsed.selector);
111
+ if (!entry || (!entry.defaults.mode && !entry.defaults.model)) {
112
+ console.log(chalk.gray(`No default set for ${parsed.selector}.`));
113
+ console.log(chalk.gray(`Set one with: agents models set ${selector} --model <model>`));
114
+ return;
115
+ }
116
+ console.log(` ${formatRunDefaultEntry(entry)}`);
117
+ return;
118
+ }
119
+ const entry = setRunDefault(selector, {
120
+ ...(options.mode !== undefined ? { mode: options.mode } : {}),
121
+ ...(options.model !== undefined ? { model: options.model } : {}),
122
+ });
123
+ console.log(chalk.green('Set default:'));
124
+ console.log(` ${formatRunDefaultEntry(entry)}`);
125
+ }
126
+ catch (err) {
127
+ console.error(chalk.red(err.message));
128
+ process.exit(1);
129
+ }
130
+ });
131
+ setHelpSections(set, {
132
+ examples: `
133
+ agents models set claude@2.1.220 --model opus-5
134
+ agents models set 'claude:*' --mode auto --model opus
135
+ agents models set claude@2.1.220
136
+ agents models set
137
+ `,
138
+ notes: `
139
+ Selectors use <agent>@<version> or <agent>:<version>; * matches all versions.
140
+ Exact selectors override wildcard selectors field by field.
141
+ Writes the same store as 'agents config set run.<agent@version>.*'. Explicit flags on
142
+ 'agents run' always win over configured defaults.
143
+ `,
144
+ });
78
145
  // Override subcommands. These WRITE agents.yaml so the user never hand-edits it;
79
146
  // resolution is exact `<agent>@<version>` over `<agent>` over the auto guess.
80
147
  const tierDeprecation = chalk.yellow('Deprecation: `agents models tier` is replaced by `agents config set run.<agent@version>.tier.<tier>`.');
@@ -20,7 +20,7 @@ import { ensureShimCurrent, switchHomeFileSymlinks, isShimsInPath, addShimsToPat
20
20
  import { setHelpSections } from '../lib/help.js';
21
21
  import { registerSetupBrowserCommand, runBrowserWizard } from './setup-browser.js';
22
22
  import { registerSetupComputerCommand, runComputerWizard } from './setup-computer.js';
23
- import { registerSetupShareCommand, runShareWizard } from './setup-share.js';
23
+ import { runShareWizard } from './artifacts-setup.js';
24
24
  import { registerSetupMineCommand } from './setup-mine.js';
25
25
  import { registerSetupSecretsCommand } from './setup-secrets.js';
26
26
  import { registerSetupFleetCommand } from './setup-fleet.js';
@@ -355,10 +355,11 @@ export function registerSetupCommand(program) {
355
355
  .description('Set up agents-cli, or re-open the capability onboarding hub.')
356
356
  .option('-f, --force', 'Re-run setup even if ~/.agents/.system/ already exists (use with caution)')
357
357
  .option('--no-system-repo', 'Skip cloning the system repo (you must populate ~/.agents/.system/ yourself)');
358
- // Capability subcommands: `agents setup browser|computer|share|mine|secrets|fleet`.
358
+ // Capability subcommands: `agents setup browser|computer|mine|secrets|fleet`.
359
+ // Share/artifact publishing is set up by `agents artifacts setup` (RUSH-2580);
360
+ // the hub below still offers it as a phase via runShareWizard.
359
361
  registerSetupBrowserCommand(setupCmd);
360
362
  registerSetupComputerCommand(setupCmd);
361
- registerSetupShareCommand(setupCmd);
362
363
  registerSetupMineCommand(setupCmd);
363
364
  registerSetupSecretsCommand(setupCmd);
364
365
  registerSetupFleetCommand(setupCmd);
@@ -386,7 +387,6 @@ export function registerSetupCommand(program) {
386
387
  # Set up a specific capability on its own
387
388
  agents setup browser
388
389
  agents setup computer
389
- agents setup share
390
390
  agents setup secrets
391
391
  agents setup fleet
392
392
  agents setup watchdog
@@ -401,7 +401,7 @@ export function registerSetupCommand(program) {
401
401
  Capability setup can also be run any time on its own:
402
402
  agents setup browser # detect a browser + create the default profile
403
403
  agents setup computer # install the signed macOS helper + grant permissions
404
- agents setup share # provision or join a Cloudflare share endpoint
404
+ agents artifacts setup # provision or join a Cloudflare share endpoint
405
405
  agents setup secrets # choose secrets backend/policy defaults + import
406
406
  agents setup fleet # discover Tailscale devices + configure SSH access
407
407
  agents setup watchdog # choose which devices run the daemon watchdog pass