@farthershore/cli 0.34.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,121 @@
1
+ # Changelog — @farthershore/cli
2
+
3
+ All notable changes to the `farthershore` CLI are documented here. The CLI
4
+ versions independently from the SDKs. Pre-1.0: minor versions may include
5
+ breaking changes. Releases before 0.35.0 are recorded only in the git history.
6
+
7
+ ## [0.36.0] - 2026-10-13
8
+
9
+ ### Changed
10
+
11
+ - **Breaking:** `farthershore create api` is now `farthershore backend scaffold`
12
+ (same flags), so it no longer reads like `farthershore api`. The top-level
13
+ `create` group is gone, and its error codes are renamed from `CREATE_API_*`
14
+ to `BACKEND_SCAFFOLD_*` (for example `BACKEND_SCAFFOLD_EXISTS`).
15
+ - Every command calls the `@farthershore/client` operation surface directly;
16
+ command output and flags are unchanged.
17
+
18
+ ### Removed
19
+
20
+ - **Breaking:** the local stdio MCP server and its `farthershore-mcp` binary
21
+ are gone. MCP launch configurations that run `farthershore-mcp` (or
22
+ `npx -p @farthershore/cli farthershore-mcp`) stop working on upgrade. Use
23
+ `farthershore code run` or `farthershore api` from an agent until the hosted
24
+ MCP server ships.
25
+
26
+ ## [0.35.0] - 2026-10-12
27
+
28
+ ### Added
29
+
30
+ - `farthershore api <endpoint>`: an authenticated request to any platform API
31
+ path, like `gh api`. `-X` sets the method; `-f` / `-F` add string or typed
32
+ fields (query parameters for GET, a JSON body otherwise); `--input` sends a
33
+ JSON file; `-H` adds headers (the auth, surface and organization headers are
34
+ refused); `--paginate` / `--max-pages` follow offset or cursor pages; `-i`
35
+ prints the status and headers; `--dry-run` appends `dry_run=true`. The
36
+ `{business}`, `{org}` / `{organization}` and `{env}` placeholders are filled
37
+ from the current context. Absolute URLs and other hosts are refused.
38
+ - `farthershore code search|describe|types|run`: code mode over the typed
39
+ operation surface.
40
+ - `code search` and `code describe` find operations and print their
41
+ metadata; `code types` prints `.d.ts` declarations for `fs` or one
42
+ namespace.
43
+ - `code run` runs a JavaScript/TypeScript script (a file, `-` for stdin, or
44
+ `-e`) with `fs`, `args`, `paginate` and `FartherShoreError` in scope. The
45
+ script runs in a worker with no credential and an empty environment, and
46
+ every call is checked by the CLI process.
47
+ - Read-only by default. `--allow-writes` permits non-destructive writes;
48
+ destructive and one-time-secret operations also need `--yes`.
49
+ `{ dryRun: true }` on a preview-capable write runs as a read.
50
+ Contract-class (managed-by-code) writes are always refused.
51
+ - Budgets: `--max-calls` (200), `--max-writes` (20), `--timeout` (60s), and
52
+ at most 4 calls in flight. A breach stops the script
53
+ (`CODE_MODE_BUDGET_EXCEEDED`), terminates the worker and cancels in-flight
54
+ requests.
55
+ - One-time secrets come back as `{ redacted: true, last4 }` unless
56
+ `--reveal-secrets` is passed; every result also goes through the secret
57
+ redaction backstop.
58
+
59
+ ### Changed
60
+
61
+ - The CLI and the MCP server now run on `@farthershore/client`: the shared
62
+ transport, credential sources, typed errors and namespaced operation
63
+ surface. `--yes`, `--dry-run` and the MCP `destructiveHint` are derived from
64
+ each operation's spec. Requests carry
65
+ `x-fs-client: @farthershore/client/<version> farthershore-cli/<version>`.
66
+ Errors carry `param` when the server names a field.
67
+
68
+ ### Breaking
69
+
70
+ - These commands now require `--yes`: `business rollback`,
71
+ `frontend rollback`, `backend tokens rotate`, `webhook rotate`,
72
+ `token rotate` (maker token), `persona rotate`, `persona delete`,
73
+ `business rbac disable`, `consumer migrate-latest`, `consumer rbac disable`
74
+ and `consumer rbac roles delete`. A `--dry-run` of a preview-capable
75
+ operation never needs `--yes`. Their MCP tools now set
76
+ `destructiveHint: true`.
77
+ - Consumer RBAC writes have one operation key per verb instead of sharing
78
+ `consumer.roles.assign`: `consumer.roles.assign`, `consumer.rbac.enable`,
79
+ `consumer.rbac.disable`, `consumer.role.create`, `consumer.role.update` and
80
+ `consumer.role.delete`. Only `consumer.rbac.disable` and
81
+ `consumer.role.delete` are destructive, so `consumer rbac assign`,
82
+ `enable`, `roles create` and `roles update` run without `--yes`.
83
+ `consumer rbac roles create` is no longer retried automatically.
84
+ - `promo-code archive` no longer requires `--yes`, and its MCP tool's
85
+ `destructiveHint` is now `false`.
86
+ - `--dry-run` on an operation without a preview is refused locally with
87
+ `DRY_RUN_NOT_SUPPORTED`; nothing is sent.
88
+ - The retired request-key vocabulary is gone. `operations list` no longer
89
+ reports an `intrinsic_replay` retry kind (`service-account approve` is a
90
+ `convergent_write`), and the retry contract no longer carries
91
+ `same_key_replay`, `keyRequired` or `liveWhenFieldPresent`. CLI and MCP
92
+ help describe the retry rule plainly: reads retry; writes retry only on a
93
+ 429 or a server-marked `retryable` + `safe_to_repeat` answer; anything else
94
+ is `OUTCOME_UNKNOWN` (exit 5) and names the read to reconcile with.
95
+
96
+ ### Fixed
97
+
98
+ - `workflow-control replay` and `rollback-to-config` call Core at
99
+ `/agent/v1/replay` and `/agent/v1/rollback-to-config`. They always returned
100
+ 404 before.
101
+ - A 401 on a cached exchanged token drops it, re-exchanges once and resends
102
+ once, so a long-lived MCP server recovers after a grant change or session
103
+ invalidation instead of failing for up to an hour. The refresh deadline
104
+ follows the token's lifetime rather than a possibly skewed local clock, and
105
+ concurrent first requests share one token exchange, which retries like a
106
+ read.
107
+ - The MCP server uses the CLI's organization (`--organization`, the linked
108
+ directory, or `auth organization use`) and API URL, and sends
109
+ `x-fs-surface: mcp`. It could act in a different organization before.
110
+ - POST endpoints that only read (analytics query, manifest compile, previews,
111
+ the builder bootstrap) and dry runs retry like reads instead of reporting
112
+ `OUTCOME_UNKNOWN`.
113
+ - Transport failures have stable codes instead of `INTERNAL_ERROR`:
114
+ `NETWORK_ERROR` and `TIMEOUT` (retryable for reads, exit 5),
115
+ `REQUEST_ABORTED`, `OUTCOME_UNKNOWN` for a write with no response, and
116
+ `RESPONSE_UNREADABLE` for a 2xx whose body could not be read. The original
117
+ failure is kept as `cause`. The attempt timeout now also covers the body
118
+ read.
119
+ - `farthershore api` never follows a redirect: a 3xx is reported as
120
+ `REDIRECT_NOT_FOLLOWED` with `details.location`, so headers never reach
121
+ another host.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Create and operate [FartherShore](https://farthershore.com) software businesses
4
4
  from the terminal.
5
5
 
6
- **Status: `0.34.0`.** Pre-1.0 — the command surface is stable but may still
6
+ **Status: `0.36.0`.** Pre-1.0 — the command surface is stable but may still
7
7
  change between minor versions. Pin an exact version in CI.
8
8
 
9
9
  FartherShore lets you define a software business as code, then sells, meters,
@@ -15,25 +15,18 @@ This CLI is the tool layer around that repo: provision a managed business repo,
15
15
  validate it locally before you push, inspect publication state, and run
16
16
  day-to-day operations (consumers, secrets, backends, webhooks, promo codes,
17
17
  usage). It is designed to be safe for both humans and agents — every command
18
- takes `--format json` for non-interactive scripting, and the MCP server exposes
19
- the explicitly agent-operable subset of the CLI surface.
18
+ takes `--format json` for non-interactive scripting.
20
19
 
21
20
  ## Install
22
21
 
23
22
  ```bash
24
- npm install -g @farthershore/cli@0.34.0
23
+ npm install -g @farthershore/cli@0.36.0
25
24
  ```
26
25
 
27
26
  Or run it directly with npx:
28
27
 
29
28
  ```bash
30
- npx -p @farthershore/cli@0.34.0 farthershore --help
31
- ```
32
-
33
- The package also ships an MCP stdio server for agents:
34
-
35
- ```bash
36
- npx -p @farthershore/cli@0.34.0 farthershore-mcp
29
+ npx -p @farthershore/cli@0.36.0 farthershore --help
37
30
  ```
38
31
 
39
32
  ## Authentication
@@ -209,17 +202,17 @@ before pagination; the CLI does not sort or filter a partial page locally.
209
202
  Each command rejects fields outside its endpoint-specific allowlist shown by
210
203
  `--help`.
211
204
 
212
- ### `farthershore create`
205
+ ### `farthershore backend scaffold`
213
206
 
214
- Create local project components inside a managed builder repo. `create api`
215
- scaffolds a starter backend into `api/` by downloading the public Node template
216
- tarball, then patches the repo root `.gitignore`.
207
+ Scaffold a starter backend into `api/` inside a managed builder repo, by
208
+ downloading the public Node template tarball, then patch the repo root
209
+ `.gitignore`. It runs locally and needs no login.
217
210
 
218
211
  ```bash
219
- farthershore create api
220
- farthershore create api --path ../weather-api
221
- farthershore create api --force
222
- farthershore create api --node --ref v0.1.0 --format json
212
+ farthershore backend scaffold
213
+ farthershore backend scaffold --path ../weather-api
214
+ farthershore backend scaffold --force
215
+ farthershore backend scaffold --node --ref v0.1.0 --format json
223
216
  ```
224
217
 
225
218
  `--node` is the default language today: Node (TypeScript + Express). `--ref`
@@ -274,7 +267,7 @@ farthershore business create weather-api \
274
267
  --format json
275
268
 
276
269
  farthershore business publish weather-api --format json
277
- farthershore business rollback weather-api wfe_previous --reason "bad release" --format json
270
+ farthershore business rollback weather-api wfe_previous --reason "bad release" --yes --format json
278
271
  ```
279
272
 
280
273
  Deleting a business is **stage only** — the command is not registered in the
@@ -321,13 +314,13 @@ and requires the exact `business:rollback` permission.
321
314
  `rbacEnabled` flag. `business rbac enable` / `disable` toggle that flag (like
322
315
  `docsEnabled`, it is platform-owned and never repo-authored); once enabled,
323
316
  subscriber organizations manage roles in their portal. Both toggles are
324
- idempotent. Subscriber role and assignment state is kept server-side on
317
+ safe to repeat. Subscriber role and assignment state is kept server-side on
325
318
  disable, so re-enabling is a pure flag flip.
326
319
 
327
320
  ```bash
328
321
  farthershore business rbac weather-api --format json
329
322
  farthershore business rbac enable weather-api --format json
330
- farthershore business rbac disable weather-api --format json
323
+ farthershore business rbac disable weather-api --yes --format json
331
324
  ```
332
325
 
333
326
  `business rbac subjects list` lists the custom permission vocabulary: subjects
@@ -385,8 +378,7 @@ default, and `--org <id>` overrides it for one command.
385
378
 
386
379
  Create and administer organization-owned MakerTokens. These commands require a
387
380
  signed-in user CLI session (`farthershore login`); an existing MakerToken cannot
388
- list, mint, approve, rotate, revoke, or otherwise administer credentials. The
389
- entire group is CLI-only and is deliberately absent from `farthershore-mcp`.
381
+ list, mint, approve, rotate, revoke, or otherwise administer credentials.
390
382
 
391
383
  ```bash
392
384
  # Exact permissions a human may delegate — read this before choosing --permissions.
@@ -406,7 +398,7 @@ farthershore token approve <approvalId> --org \
406
398
  farthershore token deny <approvalId> --org --format json
407
399
  farthershore token update <tokenId> --org \
408
400
  --permissions '["business:read"]' --format json
409
- farthershore token rotate <tokenId> --org --format json
401
+ farthershore token rotate <tokenId> --org --yes --format json
410
402
  farthershore token revoke <tokenId> --org --yes --format json
411
403
  ```
412
404
 
@@ -470,8 +462,8 @@ never print `plaintext`. ACTIVE creates
470
462
  and rotations print the one-time key once. If one of these commands times out,
471
463
  run `service-account list` before trying again.
472
464
 
473
- This group is direct CLI-only and deliberately has no MCP projection: the MCP
474
- operation registry carries builder principals, not portal consumer sessions.
465
+ This group is registered directly rather than through the operation registry:
466
+ that registry carries builder principals, not portal consumer sessions.
475
467
 
476
468
  ### `farthershore limit`
477
469
 
@@ -522,9 +514,18 @@ portal renders locally exactly as it serves in production. Pass `--mock` to
522
514
  inject `{ mock: true }` instead of Core/Clerk config for local page inspection
523
515
  with placeholder data and auth bypassed.
524
516
 
517
+ A build is published only when its output ships the SDK's managed sign-in.
518
+ A refused build never replaces what is live; `frontend status` shows the
519
+ reason, and `builds[].diagnostics[].code` is `FRONTEND_SIGN_IN_MISSING` (mount
520
+ `<FartherShoreRoot>` and render `<FsSignIn />`) or `FRONTEND_SDK_TOO_OLD`
521
+ (upgrade `@farthershore/farthershore-js`). `--wait` exits `BUILD_FAILED` with
522
+ that code in `details.reasonCode`. Mock mode is for this local preview only: a
523
+ hosted portal ignores it, and always uses the platform's sign-in.
524
+
525
525
  ```bash
526
526
  farthershore frontend status weather-api --format json
527
- farthershore frontend rollback weather-api --release-id <hash> --format json
527
+ farthershore frontend status weather-api --wait --ref <commitSha> --format json
528
+ farthershore frontend rollback weather-api --release-id <hash> --yes --format json
528
529
 
529
530
  # Local preview (shells out to vite; needs a frontend/ project)
530
531
  farthershore frontend dev # hot-reloading dev server, mock mode by default
@@ -570,8 +571,8 @@ farthershore persona bootstrap weather-api --env preview --plan pro \
570
571
  --subscriber-id <subscriberId> --account-role member --role viewer \
571
572
  --format json
572
573
 
573
- farthershore persona rotate weather-api <personaId> --env preview --format json
574
- farthershore persona delete weather-api <personaId> --env preview --format json
574
+ farthershore persona rotate weather-api <personaId> --env preview --yes --format json
575
+ farthershore persona delete weather-api <personaId> --env preview --yes --format json
575
576
  ```
576
577
 
577
578
  Bootstrap and rotation return a key once. `login` sends the 60-second,
@@ -657,7 +658,7 @@ farthershore backend bind weather-api api \
657
658
  --origin-url https://api-pr-42.example.com \
658
659
  --format json
659
660
  farthershore backend tokens create weather-api --backend <backendId> --format json
660
- farthershore backend tokens rotate weather-api <tokenId> --format json
661
+ farthershore backend tokens rotate weather-api <tokenId> --yes --format json
661
662
  farthershore backend tokens revoke weather-api <tokenId> --yes --format json
662
663
  ```
663
664
 
@@ -707,7 +708,7 @@ farthershore webhook update weather-api <webhookId> --disable --format json
707
708
  farthershore webhook test weather-api <webhookId> --format json
708
709
  farthershore webhook trigger weather-api <webhookId> --type payment.failed --format json
709
710
  farthershore webhook deliveries weather-api <webhookId> --limit 20 --format json
710
- farthershore webhook rotate weather-api <webhookId> --format json
711
+ farthershore webhook rotate weather-api <webhookId> --yes --format json
711
712
  farthershore webhook delete weather-api <webhookId> --yes --format json
712
713
  ```
713
714
 
@@ -734,8 +735,8 @@ farthershore webhook listen weather-api --forward-to localhost:3000/webhooks \
734
735
  ```
735
736
 
736
737
  `listen` needs `cloudflared` — bundled as an optional
737
- `@farthershore/cloudflared-<platform>` dependency, or on your PATH. It has no
738
- MCP tool (long-running, process-owning). A listener killed hard leaves an
738
+ `@farthershore/cloudflared-<platform>` dependency, or on your PATH. A listener
739
+ killed hard leaves an
739
740
  endpoint with a `*.trycloudflare.com` URL; `webhook delete --yes` removes it.
740
741
 
741
742
  ### `farthershore promo-code`
@@ -763,18 +764,18 @@ Use `--all-plans` to clear plan scoping.
763
764
  Install or refresh the platform-verified skills bundle together. The platform
764
765
  pins this immutable release, and CI plus a daily repository check compare it to
765
766
  GitHub's latest stable release so the guidance cannot silently drift.
766
- This command requires `@farthershore/cli` 0.34.0 or newer.
767
+ This command requires `@farthershore/cli` 0.36.0 or newer.
767
768
 
768
769
  ```bash
769
770
  set -euo pipefail
770
771
  CLI_VERSION=$(farthershore --version)
771
- printf '%s' "$CLI_VERSION" | node -e 'let v=""; process.stdin.on("data", c => v += c).on("end", () => { const m = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:\+[0-9A-Za-z.-]+)?$/.exec(v.trim()); if (!m || !(+m[1] > 0 || +m[2] > 34 || (+m[2] === 34 && +m[3] >= 0))) { console.error("Stop: CLI 0.34.0 or newer is required; ask the human to upgrade."); process.exitCode = 1; } })'
772
+ printf '%s' "$CLI_VERSION" | node -e 'let v=""; process.stdin.on("data", c => v += c).on("end", () => { const m = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:\+[0-9A-Za-z.-]+)?$/.exec(v.trim()); if (!m || !(+m[1] > 0 || +m[2] > 36 || (+m[2] === 36 && +m[3] >= 0))) { console.error("Stop: CLI 0.36.0 or newer is required; ask the human to upgrade."); process.exitCode = 1; } })'
772
773
  farthershore skills install --help
773
774
  farthershore skills install
774
775
  ```
775
776
 
776
777
  If either prerequisite fails, stop and ask the human to run
777
- `npm install -g @farthershore/cli@0.34.0`, then repeat the checks.
778
+ `npm install -g @farthershore/cli@0.36.0`, then repeat the checks.
778
779
  Do not install or upgrade global tooling autonomously.
779
780
 
780
781
  The Farther Shore CLI downloads the exact installer archive and verifies this
@@ -789,6 +790,144 @@ Print the machine-readable action boundary: which operations are repo-authored
789
790
  farthershore operations list --format json
790
791
  ```
791
792
 
793
+ ### `farthershore api`
794
+
795
+ Send a raw authenticated request to any platform API endpoint and print the
796
+ response, like `gh api`. Use it for endpoints a typed command does not cover
797
+ yet. Every endpoint is described in the platform's OpenAPI document
798
+ (`farthershore api /openapi.json`).
799
+
800
+ ```bash
801
+ farthershore api /businesses
802
+ farthershore api 'businesses/{business}/environments' --paginate
803
+ farthershore api -X PATCH 'businesses/{business}' -f displayName='Weather API'
804
+ farthershore api 'management/businesses/{business}/backends' -f name=api -f originUrl=https://api.example.com -F isDefault=true --dry-run
805
+ farthershore api -X POST 'businesses/{business}/promo-codes' --input promo.json
806
+ farthershore api 'builder/organizations/{org}/members' --include
807
+ farthershore api /businesses --format json | jq '.[].id'
808
+ ```
809
+
810
+ - **Endpoint.** A path on the configured API (`--api-url` /
811
+ `FARTHERSHORE_API_URL`). Absolute URLs and other hosts are refused, so the
812
+ credential never leaves the platform, and redirects are never followed: a
813
+ 3xx answer is an error (`REDIRECT_NOT_FOLLOWED`), so the credential and `-H`
814
+ headers never reach another location. Placeholders in the path and its
815
+ query are filled from the current context: `{business}` from
816
+ `--business`, `FARTHERSHORE_BUSINESS` or the linked directory; `{org}` /
817
+ `{organization}` from the selected organization; `{env}` from `--env`,
818
+ `FARTHERSHORE_ENV` or the linked environment.
819
+ - **Method.** `-X, --method`; default `GET`, or `POST` when fields or `--input`
820
+ are given.
821
+ - **Fields.** `-f key=value` sends a string; `-F key=value` converts `true`,
822
+ `false`, `null` and numbers, and reads `@file` (or `@-` for stdin). Nest with
823
+ `key[sub]=value`, append to an array with `key[]=value`. `GET` fields become
824
+ query parameters; otherwise they form the JSON body.
825
+ - **`--input <file|->`** sends a JSON body as-is (not combinable with fields).
826
+ - **`-H 'Name: value'`** adds a header. `Authorization`, `x-fs-surface`,
827
+ `x-fs-client` and the organization headers belong to the CLI and are refused.
828
+ - **`--paginate`** follows offset (`pagination.hasMore`) and cursor
829
+ (`nextCursor`) pages and prints one object with the lists concatenated;
830
+ `--max-pages <n>` (default 100) bounds it.
831
+ - **`-i, --include`** prints the HTTP status and response headers first.
832
+ - **`--dry-run`** appends `dry_run=true`.
833
+
834
+ The body is printed as-is (pretty in a terminal, compact with `--format json`),
835
+ never wrapped in the CLI envelope; there is no `--jq`, so pipe to `jq`. Errors
836
+ print the error envelope and use the exit codes below. Reads retry on transient
837
+ failures; a write is never repeated: a 5xx or lost answer exits `5` and its
838
+ error has `outcomeUnknown: true` (the `code` may be the server's own, e.g.
839
+ `TEARDOWN_FAILED`; key on `outcomeUnknown`), so read current state before
840
+ sending it again. `-i` also prints the status line and headers of a failed
841
+ answer, before the error envelope.
842
+
843
+ ### `farthershore code` (code mode)
844
+
845
+ Code mode lets you (or an agent) find platform operations and run a script
846
+ against a typed `fs` client, instead of chaining one command per call. A
847
+ script can loop, filter and join results, and only its return value comes
848
+ back — the intermediate data never has to pass through a transcript.
849
+
850
+ **Agent quickstart: search → describe → types → run.**
851
+
852
+ ```bash
853
+ # 1. Find operations (ranked by name, path, summary and description).
854
+ farthershore code search runtime token --format json
855
+
856
+ # 2. Read one: description, TypeScript signature, safety flags, reconcile
857
+ # read, required permission and a runnable example.
858
+ farthershore code describe runtimeTokens.revoke
859
+
860
+ # 3. Load the types you need (or the whole API) before writing the script.
861
+ farthershore code types runtimeTokens > fs.d.ts
862
+
863
+ # 4. Run it. Reads only, unless you opt in.
864
+ farthershore code run -e '
865
+ const businesses = await fs.businesses.list();
866
+ return businesses.filter((b) => b.status === args.status).map((b) => b.name);
867
+ ' --arg status=DRAFT
868
+ ```
869
+
870
+ - **`code search <query...>`** — `--limit <n>` (default 20),
871
+ `--side-effect read|write`, `--namespace <ns>`. Each hit shows the path
872
+ (`fs.runtimeTokens.revoke`), a one-line summary and its flags (read/write,
873
+ destructive, one-time secret, preview, paginated).
874
+ - **`code describe <path|opKey>`** — accepts `runtimeTokens.revoke`,
875
+ `fs.runtimeTokens.revoke` or the opKey `runtime_token.revoke`.
876
+ - **`code types [namespace]`** — a self-contained `.d.ts` for `fs`, generated
877
+ from the same tables `code run` enforces, with JSDoc from the operation
878
+ specs.
879
+ - **`code run [file|-]` / `-e '<code>'`** — the script is the body of an async
880
+ function: `await` freely and `return` the result (or `export default` a value
881
+ or an async function of `{ fs, args }`). `.ts` files (or `--ts`) have their
882
+ types stripped by Node; only erasable syntax is supported. `--arg key=value`
883
+ (repeatable) and `--args-json '{…}'` fill `args`. The return value is
884
+ printed as JSON on stdout; `console.*` goes to stderr, or into
885
+ `{ result, logs }` with `--format json`. `code run` needs Node.js 22.8 or
886
+ newer (`CODE_MODE_UNSUPPORTED_NODE` otherwise); TypeScript needs 22.13.
887
+
888
+ In scope inside a script: `fs` (every method, plus `fs.call(opKey, input,
889
+ options)`), `args`, `paginate(method, input)` / `collect(method, input)`,
890
+ `refs.business(ref)` / `refs.environment(businessId, ref)`, `poll({ check,
891
+ until, deadlineMs })`, `console`, `setTimeout` / `clearTimeout` and
892
+ `FartherShoreError`. Nothing else: no `require`, `import()`, `process`,
893
+ `fetch`, filesystem or network.
894
+
895
+ **Policy** (checked in the CLI process for every call, not in the script):
896
+
897
+ | Call | Needs |
898
+ | ------------------------------------------------------- | ----------------------------------------------------------- |
899
+ | read | nothing |
900
+ | write | `--allow-writes` (else `CODE_MODE_WRITE_NOT_ALLOWED`) |
901
+ | destructive or one-time-secret write | `--allow-writes --yes` (else `CODE_MODE_APPROVAL_REQUIRED`) |
902
+ | preview (`{ dryRun: true }` on a method marked preview) | nothing — it changes nothing |
903
+ | managed-as-code (contract) write | always refused |
904
+
905
+ One-time secrets (minted or rotated credentials) come back to the script as
906
+ `{ redacted: true, last4 }` unless you pass `--reveal-secrets`; a redacted
907
+ secret cannot be shown again, so rotate with `--reveal-secrets` when you need
908
+ the value. In every other result, credential-shaped strings (tokens, API keys,
909
+ JWTs) and known secret fields are masked before the script sees them.
910
+
911
+ **Budgets:** `--max-calls` (200), `--max-writes` (20), `--timeout` (60s; `60`,
912
+ `60s`, `500ms`, `2m`) and at most 4 calls in flight. Exceeding one stops the
913
+ script with `CODE_MODE_BUDGET_EXCEEDED` / `CODE_MODE_TIMEOUT`; Ctrl-C stops it
914
+ too. Stopping terminates the script and cancels its in-flight requests.
915
+
916
+ **Exit codes:** `0` success; `1` script error, refusal, budget or timeout; an
917
+ uncaught operation error keeps the CLI codes below (`2` input, `3` auth, `5`
918
+ server or `OUTCOME_UNKNOWN` — the error names the read that reconciles it).
919
+
920
+ **Isolation and threat model.** The script runs in a worker thread that never
921
+ receives the credential, the CLI config or the environment (it starts with an
922
+ empty one); its only capability is a message channel to the CLI process,
923
+ which applies the policy above and makes the authenticated request. Requests
924
+ are marked `code-mode` in the client header for audit. The script itself runs
925
+ in a fresh JavaScript context with only the language built-ins. This keeps
926
+ credentials out of script memory and gates every side effect. It is **not** a
927
+ sandbox for hostile code — Node's `vm` is not a security boundary — so run
928
+ scripts you would run yourself. The hosted MCP will run scripts in an
929
+ isolated hosted runtime instead.
930
+
792
931
  ## Errors and exit codes
793
932
 
794
933
  In `--format json` mode, every command prints a single canonical envelope to
@@ -802,14 +941,31 @@ bracketed code is stable across releases — quote it in support threads.
802
941
  Exit codes are a branchable taxonomy so a script can tell failure classes apart
803
942
  without parsing text:
804
943
 
805
- | Exit | Meaning |
806
- | ---- | -------------------------------------------------------------- |
807
- | `0` | Success |
808
- | `2` | Validation error (HTTP 400/422) — fix your input |
809
- | `3` | Auth / permission error (401/403) — re-authenticate |
810
- | `4` | Conflict / managed-by-code (409) — change it in the repo |
811
- | `5` | Server error (5xx) — retry only when `error.retryable` is true |
812
- | `1` | Any other failure (local error, 402/404, argument errors, …) |
944
+ | Exit | Meaning |
945
+ | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
946
+ | `0` | Success |
947
+ | `2` | Validation error (HTTP 400/422) — fix your input |
948
+ | `3` | Auth / permission error (401/403) — re-authenticate |
949
+ | `4` | `MANAGED_BY_CODE` (409 with that code only) — the contract is managed as code; change it in the repo and push |
950
+ | `5` | Server error (5xx), or `OUTCOME_UNKNOWN` — a write whose outcome is unknown; never repeat it blindly (see below) |
951
+ | `1` | Any other failure: local errors, 402/404, argument errors, and every other 409 (`CONFLICT`, `RELEASE_PENDING_ACCEPTANCE`, `ROLLBACK_IN_FLIGHT`, …) |
952
+
953
+ A 409 other than `MANAGED_BY_CODE` means the server refused a repeat or a
954
+ conflicting change (an existing name, a release still being applied, a rollback
955
+ already running). Wait or reconcile; do not edit `business/` for it.
956
+
957
+ Exit `5` covers two different cases. Branch on `error.retryDisposition`:
958
+
959
+ - **`reconcile_before_repeat`.** The write may or may not have run. Run the
960
+ read named in `error.details.reconcile` (also listed per command by
961
+ `farthershore operations list`) and repeat only if that read shows it did
962
+ not happen. The error can retain a server code such as `UPSTREAM_TIMEOUT`;
963
+ a lost response uses `OUTCOME_UNKNOWN`. Neither code alone decides whether
964
+ repeating the write is safe.
965
+ - **`safe_to_repeat`.** Retry only when `error.retryable` is also true.
966
+
967
+ The CLI retries reads automatically. It retries a write only on 429, or when
968
+ the server marks the failure `retryable` with a `safe_to_repeat` verdict.
813
969
 
814
970
  ## CI and agent usage
815
971
 
@@ -819,20 +975,6 @@ MakerToken through `FARTHERSHORE_TOKEN`. Device login waits for human approval;
819
975
  `farthershore validate --local-only` need no network or auth and are safe to
820
976
  run in CI.
821
977
 
822
- ## MCP server
823
-
824
- The MCP server (`farthershore-mcp`) projects its tools from the same operation
825
- registry that defines the CLI commands. Every MCP tool has a CLI sibling, but
826
- not every CLI command is exposed through MCP. Tool names use the `fs_*` prefix
827
- and cover only operations explicitly marked agent-operable in that registry.
828
- Organization, role, member, invitation, and org MakerToken administration
829
- requires a live-user CLI session and is not projected into MCP; audit logs,
830
- notifications, and apply timelines are also CLI-only.
831
-
832
- ```bash
833
- farthershore-mcp
834
- ```
835
-
836
978
  ## Learn more
837
979
 
838
980
  - Platform documentation: [farthershore.com](https://farthershore.com)