@farthershore/cli 0.33.6 → 0.35.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,103 @@
1
+ # Changelog — @farthershore/cli
2
+
3
+ All notable changes to the `farthershore` CLI and its MCP server
4
+ (`farthershore-mcp`) are documented here. The CLI versions independently from
5
+ the SDKs. Pre-1.0: minor versions may include breaking changes. Releases
6
+ before 0.35.0 are recorded only in the git history.
7
+
8
+ ## [0.35.0] - 2026-10-12
9
+
10
+ ### Added
11
+
12
+ - `farthershore api <endpoint>`: an authenticated request to any platform API
13
+ path, like `gh api`. `-X` sets the method; `-f` / `-F` add string or typed
14
+ fields (query parameters for GET, a JSON body otherwise); `--input` sends a
15
+ JSON file; `-H` adds headers (the auth, surface and organization headers are
16
+ refused); `--paginate` / `--max-pages` follow offset or cursor pages; `-i`
17
+ prints the status and headers; `--dry-run` appends `dry_run=true`. The
18
+ `{business}`, `{org}` / `{organization}` and `{env}` placeholders are filled
19
+ from the current context. Absolute URLs and other hosts are refused.
20
+ - `farthershore code search|describe|types|run`: code mode over the typed
21
+ operation surface.
22
+ - `code search` and `code describe` find operations and print their
23
+ metadata; `code types` prints `.d.ts` declarations for `fs` or one
24
+ namespace.
25
+ - `code run` runs a JavaScript/TypeScript script (a file, `-` for stdin, or
26
+ `-e`) with `fs`, `args`, `paginate` and `FartherShoreError` in scope. The
27
+ script runs in a worker with no credential and an empty environment, and
28
+ every call is checked by the CLI process.
29
+ - Read-only by default. `--allow-writes` permits non-destructive writes;
30
+ destructive and one-time-secret operations also need `--yes`.
31
+ `{ dryRun: true }` on a preview-capable write runs as a read.
32
+ Contract-class (managed-by-code) writes are always refused.
33
+ - Budgets: `--max-calls` (200), `--max-writes` (20), `--timeout` (60s), and
34
+ at most 4 calls in flight. A breach stops the script
35
+ (`CODE_MODE_BUDGET_EXCEEDED`), terminates the worker and cancels in-flight
36
+ requests.
37
+ - One-time secrets come back as `{ redacted: true, last4 }` unless
38
+ `--reveal-secrets` is passed; every result also goes through the secret
39
+ redaction backstop.
40
+
41
+ ### Changed
42
+
43
+ - The CLI and the MCP server now run on `@farthershore/client`: the shared
44
+ transport, credential sources, typed errors and namespaced operation
45
+ surface. `--yes`, `--dry-run` and the MCP `destructiveHint` are derived from
46
+ each operation's spec. Requests carry
47
+ `x-fs-client: @farthershore/client/<version> farthershore-cli/<version>`.
48
+ Errors carry `param` when the server names a field.
49
+
50
+ ### Breaking
51
+
52
+ - These commands now require `--yes`: `business rollback`,
53
+ `frontend rollback`, `backend tokens rotate`, `webhook rotate`,
54
+ `token rotate` (maker token), `persona rotate`, `persona delete`,
55
+ `business rbac disable`, `consumer migrate-latest`, `consumer rbac disable`
56
+ and `consumer rbac roles delete`. A `--dry-run` of a preview-capable
57
+ operation never needs `--yes`. Their MCP tools now set
58
+ `destructiveHint: true`.
59
+ - Consumer RBAC writes have one operation key per verb instead of sharing
60
+ `consumer.roles.assign`: `consumer.roles.assign`, `consumer.rbac.enable`,
61
+ `consumer.rbac.disable`, `consumer.role.create`, `consumer.role.update` and
62
+ `consumer.role.delete`. Only `consumer.rbac.disable` and
63
+ `consumer.role.delete` are destructive, so `consumer rbac assign`,
64
+ `enable`, `roles create` and `roles update` run without `--yes`.
65
+ `consumer rbac roles create` is no longer retried automatically.
66
+ - `promo-code archive` no longer requires `--yes`, and its MCP tool's
67
+ `destructiveHint` is now `false`.
68
+ - `--dry-run` on an operation without a preview is refused locally with
69
+ `DRY_RUN_NOT_SUPPORTED`; nothing is sent.
70
+ - The retired request-key vocabulary is gone. `operations list` no longer
71
+ reports an `intrinsic_replay` retry kind (`service-account approve` is a
72
+ `convergent_write`), and the retry contract no longer carries
73
+ `same_key_replay`, `keyRequired` or `liveWhenFieldPresent`. CLI and MCP
74
+ help describe the retry rule plainly: reads retry; writes retry only on a
75
+ 429 or a server-marked `retryable` + `safe_to_repeat` answer; anything else
76
+ is `OUTCOME_UNKNOWN` (exit 5) and names the read to reconcile with.
77
+
78
+ ### Fixed
79
+
80
+ - `workflow-control replay` and `rollback-to-config` call Core at
81
+ `/agent/v1/replay` and `/agent/v1/rollback-to-config`. They always returned
82
+ 404 before.
83
+ - A 401 on a cached exchanged token drops it, re-exchanges once and resends
84
+ once, so a long-lived MCP server recovers after a grant change or session
85
+ invalidation instead of failing for up to an hour. The refresh deadline
86
+ follows the token's lifetime rather than a possibly skewed local clock, and
87
+ concurrent first requests share one token exchange, which retries like a
88
+ read.
89
+ - The MCP server uses the CLI's organization (`--organization`, the linked
90
+ directory, or `auth organization use`) and API URL, and sends
91
+ `x-fs-surface: mcp`. It could act in a different organization before.
92
+ - POST endpoints that only read (analytics query, manifest compile, previews,
93
+ the builder bootstrap) and dry runs retry like reads instead of reporting
94
+ `OUTCOME_UNKNOWN`.
95
+ - Transport failures have stable codes instead of `INTERNAL_ERROR`:
96
+ `NETWORK_ERROR` and `TIMEOUT` (retryable for reads, exit 5),
97
+ `REQUEST_ABORTED`, `OUTCOME_UNKNOWN` for a write with no response, and
98
+ `RESPONSE_UNREADABLE` for a 2xx whose body could not be read. The original
99
+ failure is kept as `cause`. The attempt timeout now also covers the body
100
+ read.
101
+ - `farthershore api` never follows a redirect: a 3xx is reported as
102
+ `REDIRECT_NOT_FOLLOWED` with `details.location`, so headers never reach
103
+ 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.33.6`.** Pre-1.0 — the command surface is stable but may still
6
+ **Status: `0.35.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,
@@ -21,19 +21,19 @@ the explicitly agent-operable subset of the CLI surface.
21
21
  ## Install
22
22
 
23
23
  ```bash
24
- npm install -g @farthershore/cli@0.33.6
24
+ npm install -g @farthershore/cli@0.35.0
25
25
  ```
26
26
 
27
27
  Or run it directly with npx:
28
28
 
29
29
  ```bash
30
- npx -p @farthershore/cli@0.33.6 farthershore --help
30
+ npx -p @farthershore/cli@0.35.0 farthershore --help
31
31
  ```
32
32
 
33
33
  The package also ships an MCP stdio server for agents:
34
34
 
35
35
  ```bash
36
- npx -p @farthershore/cli@0.33.6 farthershore-mcp
36
+ npx -p @farthershore/cli@0.35.0 farthershore-mcp
37
37
  ```
38
38
 
39
39
  ## Authentication
@@ -87,12 +87,11 @@ credentials into the managed repository.
87
87
  Create a managed business repo, author the business, validate it, and push:
88
88
 
89
89
  ```bash
90
- # 1. Create the managed repo. Human output is exactly its URL.
91
- REPO_URL=$(farthershore business create weather-api \
92
- --idempotency-key <persisted-business-create-attempt-key>)
90
+ # 1. Create the managed repo. The command prints its URL.
91
+ farthershore business create weather-api
93
92
 
94
93
  # 2. Clone it and author business/ from scratch.
95
- git clone "$REPO_URL"
94
+ git clone <printed-repository-url>
96
95
  cd weather-api
97
96
  # ...author business/ with @farthershore/business...
98
97
 
@@ -246,9 +245,8 @@ Run the same verdict the GitHub `farthershore/validate` PR check produces, befor
246
245
  you push. It builds the manifest twice and compares the IR hash (the determinism
247
246
  check the build runner enforces), then — when you pass `--business` — submits the
248
247
  IR for a server verdict that returns compile diagnostics, the semantic diff, and
249
- an economic-change verdict. The server may retain a diagnostic compilation
250
- record, but validation never accepts or publishes business state, so it is safe
251
- to repeat and does not take an idempotency key.
248
+ an economic-change verdict. Validation never accepts or publishes business
249
+ state, so it is safe to repeat.
252
250
 
253
251
  ```bash
254
252
  farthershore validate # local build + determinism only
@@ -273,11 +271,10 @@ farthershore business subdomain-check weather-api --format json
273
271
 
274
272
  farthershore business create weather-api \
275
273
  --display-name "Weather API" \
276
- --idempotency-key <persisted-business-create-attempt-key> \
277
274
  --format json
278
275
 
279
- farthershore business publish weather-api --format json --idempotency-key <persisted-business-publish-attempt-key>
280
- farthershore business rollback weather-api wfe_previous --reason "bad release" --format json --idempotency-key <persisted-business-rollback-attempt-key>
276
+ farthershore business publish weather-api --format json
277
+ farthershore business rollback weather-api wfe_previous --reason "bad release" --yes --format json
281
278
  ```
282
279
 
283
280
  Deleting a business is **stage only** — the command is not registered in the
@@ -324,13 +321,13 @@ and requires the exact `business:rollback` permission.
324
321
  `rbacEnabled` flag. `business rbac enable` / `disable` toggle that flag (like
325
322
  `docsEnabled`, it is platform-owned and never repo-authored); once enabled,
326
323
  subscriber organizations manage roles in their portal. Both toggles are
327
- idempotent. Subscriber role and assignment state is kept server-side on
324
+ safe to repeat. Subscriber role and assignment state is kept server-side on
328
325
  disable, so re-enabling is a pure flag flip.
329
326
 
330
327
  ```bash
331
328
  farthershore business rbac weather-api --format json
332
329
  farthershore business rbac enable weather-api --format json
333
- farthershore business rbac disable weather-api --format json
330
+ farthershore business rbac disable weather-api --yes --format json
334
331
  ```
335
332
 
336
333
  `business rbac subjects list` lists the custom permission vocabulary: subjects
@@ -377,7 +374,7 @@ farthershore organization role list <organizationId> --filter builtIn=true --sor
377
374
  farthershore organization members <organizationId> --filter roleKey=auditor --sort email:asc --search person --format json
378
375
 
379
376
  # Primary owner only; hands ultimate authority to another existing member.
380
- farthershore organization ownership-transfer <organizationId> <newOwnerUserId> --yes --format json --idempotency-key <persisted-organization-ownership-transfer-attempt-key>
377
+ farthershore organization ownership-transfer <organizationId> <newOwnerUserId> --yes --format json
381
378
  ```
382
379
 
383
380
  Selecting the default organization is a local credential concern, not an API
@@ -399,20 +396,17 @@ farthershore token permission-catalog --org --format json
399
396
  farthershore token create deploy-bot --org \
400
397
  --permissions '["business:read","business:publish"]' \
401
398
  --businesses business_1,business_2 \
402
- --idempotency-key org-token-create-2026-08-11 \
403
399
  --format json
404
400
 
405
401
  farthershore token list --org --format json
406
402
  farthershore token approvals --org --format json
407
- farthershore token approve <approvalId> --org --format json --idempotency-key <persisted-token-approve-attempt-key>
403
+ farthershore token approve <approvalId> --org --format json
408
404
  farthershore token approve <approvalId> --org \
409
- --idempotency-key <persisted-token-approve-attempt-key> \
410
405
  --permissions '["business:read"]' --format json # trim on approval
411
406
  farthershore token deny <approvalId> --org --format json
412
407
  farthershore token update <tokenId> --org \
413
- --permissions '["business:read"]' \
414
- --idempotency-key org-token-update-2026-08-11 --format json
415
- farthershore token rotate <tokenId> --org --format json --idempotency-key <persisted-token-rotate-attempt-key>
408
+ --permissions '["business:read"]' --format json
409
+ farthershore token rotate <tokenId> --org --yes --format json
416
410
  farthershore token revoke <tokenId> --org --yes --format json
417
411
  ```
418
412
 
@@ -423,10 +417,7 @@ exactly once; store it immediately. Human output follows the same rule: it
423
417
  prints plaintext only for an ACTIVE one-time-secret response and says explicitly
424
418
  when a PENDING request minted nothing.
425
419
 
426
- Create and update require `--idempotency-key`. Persist the key before the first
427
- dispatch and reuse it only when repeating that exact logical request after an
428
- interrupted invocation. The CLI never generates a key: a process-local key
429
- would be lost across the restart where retry safety matters most.
420
+ If a create or update times out, run `token list --org` before trying again.
430
421
 
431
422
  ### `farthershore stripe`
432
423
 
@@ -462,26 +453,22 @@ export FARTHERSHORE_SUBSCRIBER_ORGANIZATION_ID='<subscriber organization UUID>'
462
453
  farthershore service-account list <businessId> --format json
463
454
  farthershore service-account create <businessId> deploy-bot \
464
455
  --permissions '["route:read","route:write"]' \
465
- --scopes '["production"]' \
466
- --idempotency-key sa-create-2026-08-11 --format json
456
+ --scopes '["production"]' --format json
467
457
  farthershore service-account update <businessId> <serviceAccountId> \
468
- --permissions '["route:read"]' \
469
- --idempotency-key sa-update-2026-08-11 --format json
458
+ --permissions '["route:read"]' --format json
470
459
  farthershore service-account approvals <businessId> --format json
471
460
  farthershore service-account approve <businessId> <approvalId> \
472
461
  --permissions '["route:read"]' --format json # optional trim
473
462
  farthershore service-account deny <businessId> <approvalId> --format json
474
- farthershore service-account rotate <businessId> <serviceAccountId> --format json --idempotency-key <persisted-service-account-rotate-attempt-key>
463
+ farthershore service-account rotate <businessId> <serviceAccountId> --format json
475
464
  farthershore service-account revoke <businessId> <serviceAccountId> --yes --format json
476
465
  ```
477
466
 
478
467
  Pass `--environment-id` and `--subscriber-organization` to override the two
479
468
  context environment variables for one command. PENDING create/update responses
480
469
  never print `plaintext`. ACTIVE creates
481
- and rotations print the one-time key once. Create, update, and rotate require a
482
- caller-persisted key, just like one-time-secret org-token operations. A replayed
483
- success includes `meta.idempotency.replayed: true`; follow it with a list read
484
- before reporting current state.
470
+ and rotations print the one-time key once. If one of these commands times out,
471
+ run `service-account list` before trying again.
485
472
 
486
473
  This group is direct CLI-only and deliberately has no MCP projection: the MCP
487
474
  operation registry carries builder principals, not portal consumer sessions.
@@ -537,7 +524,7 @@ with placeholder data and auth bypassed.
537
524
 
538
525
  ```bash
539
526
  farthershore frontend status weather-api --format json
540
- farthershore frontend rollback weather-api --release-id <hash> --format json
527
+ farthershore frontend rollback weather-api --release-id <hash> --yes --format json
541
528
 
542
529
  # Local preview (shells out to vite; needs a frontend/ project)
543
530
  farthershore frontend dev # hot-reloading dev server, mock mode by default
@@ -573,7 +560,7 @@ organization composes those permissions into its own product roles.
573
560
 
574
561
  ```bash
575
562
  # First persona creates and owns a temporary subscriber workspace.
576
- farthershore persona bootstrap weather-api --env preview --plan pro --format json --idempotency-key <persisted-persona-bootstrap-attempt-key>
563
+ farthershore persona bootstrap weather-api --env preview --plan pro --format json
577
564
  farthershore persona list weather-api --env preview --format json
578
565
  farthershore persona login weather-api <ownerPersonaId> --env preview --format json
579
566
 
@@ -581,10 +568,10 @@ farthershore persona login weather-api <ownerPersonaId> --env preview --format j
581
568
  # created roles/defaults through the normal customer RBAC surface.
582
569
  farthershore persona bootstrap weather-api --env preview --plan pro \
583
570
  --subscriber-id <subscriberId> --account-role member --role viewer \
584
- --format json --idempotency-key <persisted-member-bootstrap-attempt-key>
571
+ --format json
585
572
 
586
- farthershore persona rotate weather-api <personaId> --env preview --format json --idempotency-key <persisted-persona-rotate-attempt-key>
587
- farthershore persona delete weather-api <personaId> --env preview --format json
573
+ farthershore persona rotate weather-api <personaId> --env preview --yes --format json
574
+ farthershore persona delete weather-api <personaId> --env preview --yes --format json
588
575
  ```
589
576
 
590
577
  Bootstrap and rotation return a key once. `login` sends the 60-second,
@@ -642,11 +629,11 @@ compiled integration that references it.
642
629
  ```bash
643
630
  farthershore variables list weather-api --format json
644
631
  printf '%s' "$SENTRY_AUTH_TOKEN" | farthershore variables set weather-api \
645
- SENTRY_AUTH_TOKEN --format json --idempotency-key <persisted-sentry-variable-attempt-key>
632
+ SENTRY_AUTH_TOKEN --format json
646
633
  printf '%s' "$POSTHOG_PROJECT_KEY" | farthershore variables set weather-api \
647
- FS_PUBLIC_POSTHOG_KEY --format json --idempotency-key <persisted-posthog-variable-attempt-key>
648
- farthershore variables revoke weather-api POSTHOG_SECRET --yes --format json --idempotency-key <persisted-variables-revoke-attempt-key>
649
- farthershore variables rm weather-api POSTHOG_SECRET --yes --format json --idempotency-key <persisted-variables-rm-attempt-key>
634
+ FS_PUBLIC_POSTHOG_KEY --format json
635
+ farthershore variables revoke weather-api POSTHOG_SECRET --yes --format json
636
+ farthershore variables rm weather-api POSTHOG_SECRET --yes --format json
650
637
  ```
651
638
 
652
639
  There is no builder-managed public-variable surface and values are never
@@ -664,14 +651,13 @@ farthershore backend list weather-api --format json
664
651
  farthershore backend create weather-api \
665
652
  --name "Production API" \
666
653
  --origin-url https://api.example.com \
667
- --idempotency-key <persisted-backend-create-attempt-key> \
668
654
  --format json
669
655
  farthershore backend bind weather-api api \
670
656
  --env preview-42 \
671
657
  --origin-url https://api-pr-42.example.com \
672
658
  --format json
673
- farthershore backend tokens create weather-api --backend <backendId> --format json --idempotency-key <persisted-backend-tokens-create-attempt-key>
674
- farthershore backend tokens rotate weather-api <tokenId> --format json --idempotency-key <persisted-backend-tokens-rotate-attempt-key>
659
+ farthershore backend tokens create weather-api --backend <backendId> --format json
660
+ farthershore backend tokens rotate weather-api <tokenId> --yes --format json
675
661
  farthershore backend tokens revoke weather-api <tokenId> --yes --format json
676
662
  ```
677
663
 
@@ -716,13 +702,12 @@ farthershore webhook list weather-api --format json
716
702
  farthershore webhook create weather-api \
717
703
  --url https://hooks.example.com/farthershore \
718
704
  --events subscription.created,payment.failed \
719
- --idempotency-key <persisted-webhook-create-attempt-key> \
720
705
  --format json
721
706
  farthershore webhook update weather-api <webhookId> --disable --format json
722
- farthershore webhook test weather-api <webhookId> --idempotency-key <persisted-test-attempt-key> --format json
723
- farthershore webhook trigger weather-api <webhookId> --type payment.failed --idempotency-key <persisted-trigger-attempt-key> --format json
707
+ farthershore webhook test weather-api <webhookId> --format json
708
+ farthershore webhook trigger weather-api <webhookId> --type payment.failed --format json
724
709
  farthershore webhook deliveries weather-api <webhookId> --limit 20 --format json
725
- farthershore webhook rotate weather-api <webhookId> --format json --idempotency-key <persisted-webhook-rotate-attempt-key>
710
+ farthershore webhook rotate weather-api <webhookId> --yes --format json
726
711
  farthershore webhook delete weather-api <webhookId> --yes --format json
727
712
  ```
728
713
 
@@ -761,10 +746,8 @@ Manage checkout promo codes (operational state, not part of the manifest).
761
746
  farthershore promo-code list weather-api --format json
762
747
  farthershore promo-code create weather-api \
763
748
  --code LAUNCH25 --kind percent_off --percent 25 --duration-months 3 \
764
- --idempotency-key <persisted-promo-code-create-attempt-key> \
765
749
  --plan <planId> --format json
766
750
  farthershore promo-code create weather-api \
767
- --idempotency-key <persisted-promo-code-create-attempt-key> \
768
751
  --code SAVE500 --kind amount_off --amount-cents 500 --duration-months 1 --format json
769
752
  farthershore promo-code archive weather-api <promoCodeId> --format json
770
753
  farthershore promo-code reactivate weather-api <promoCodeId> --format json
@@ -780,18 +763,18 @@ Use `--all-plans` to clear plan scoping.
780
763
  Install or refresh the platform-verified skills bundle together. The platform
781
764
  pins this immutable release, and CI plus a daily repository check compare it to
782
765
  GitHub's latest stable release so the guidance cannot silently drift.
783
- This command requires `@farthershore/cli` 0.33.6 or newer.
766
+ This command requires `@farthershore/cli` 0.35.0 or newer.
784
767
 
785
768
  ```bash
786
769
  set -euo pipefail
787
770
  CLI_VERSION=$(farthershore --version)
788
- 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] > 33 || (+m[2] === 33 && +m[3] >= 6))) { console.error("Stop: CLI 0.33.6 or newer is required; ask the human to upgrade."); process.exitCode = 1; } })'
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] > 35 || (+m[2] === 35 && +m[3] >= 0))) { console.error("Stop: CLI 0.35.0 or newer is required; ask the human to upgrade."); process.exitCode = 1; } })'
789
772
  farthershore skills install --help
790
773
  farthershore skills install
791
774
  ```
792
775
 
793
776
  If either prerequisite fails, stop and ask the human to run
794
- `npm install -g @farthershore/cli@0.33.6`, then repeat the checks.
777
+ `npm install -g @farthershore/cli@0.35.0`, then repeat the checks.
795
778
  Do not install or upgrade global tooling autonomously.
796
779
 
797
780
  The Farther Shore CLI downloads the exact installer archive and verifies this
@@ -806,6 +789,143 @@ Print the machine-readable action boundary: which operations are repo-authored
806
789
  farthershore operations list --format json
807
790
  ```
808
791
 
792
+ ### `farthershore api`
793
+
794
+ Send a raw authenticated request to any platform API endpoint and print the
795
+ response, like `gh api`. Use it for endpoints a typed command does not cover
796
+ yet. Every endpoint is described in the platform's OpenAPI document
797
+ (`farthershore api /openapi.json`).
798
+
799
+ ```bash
800
+ farthershore api /businesses
801
+ farthershore api 'businesses/{business}/environments' --paginate
802
+ farthershore api -X PATCH 'businesses/{business}' -f displayName='Weather API'
803
+ farthershore api 'management/businesses/{business}/backends' -f name=api -f originUrl=https://api.example.com -F isDefault=true --dry-run
804
+ farthershore api -X POST 'businesses/{business}/promo-codes' --input promo.json
805
+ farthershore api 'builder/organizations/{org}/members' --include
806
+ farthershore api /businesses --format json | jq '.[].id'
807
+ ```
808
+
809
+ - **Endpoint.** A path on the configured API (`--api-url` /
810
+ `FARTHERSHORE_API_URL`). Absolute URLs and other hosts are refused, so the
811
+ credential never leaves the platform, and redirects are never followed: a
812
+ 3xx answer is an error (`REDIRECT_NOT_FOLLOWED`), so the credential and `-H`
813
+ headers never reach another location. Placeholders in the path and its
814
+ query are filled from the current context: `{business}` from
815
+ `--business`, `FARTHERSHORE_BUSINESS` or the linked directory; `{org}` /
816
+ `{organization}` from the selected organization; `{env}` from `--env`,
817
+ `FARTHERSHORE_ENV` or the linked environment.
818
+ - **Method.** `-X, --method`; default `GET`, or `POST` when fields or `--input`
819
+ are given.
820
+ - **Fields.** `-f key=value` sends a string; `-F key=value` converts `true`,
821
+ `false`, `null` and numbers, and reads `@file` (or `@-` for stdin). Nest with
822
+ `key[sub]=value`, append to an array with `key[]=value`. `GET` fields become
823
+ query parameters; otherwise they form the JSON body.
824
+ - **`--input <file|->`** sends a JSON body as-is (not combinable with fields).
825
+ - **`-H 'Name: value'`** adds a header. `Authorization`, `x-fs-surface`,
826
+ `x-fs-client` and the organization headers belong to the CLI and are refused.
827
+ - **`--paginate`** follows offset (`pagination.hasMore`) and cursor
828
+ (`nextCursor`) pages and prints one object with the lists concatenated;
829
+ `--max-pages <n>` (default 100) bounds it.
830
+ - **`-i, --include`** prints the HTTP status and response headers first.
831
+ - **`--dry-run`** appends `dry_run=true`.
832
+
833
+ The body is printed as-is (pretty in a terminal, compact with `--format json`),
834
+ never wrapped in the CLI envelope; there is no `--jq`, so pipe to `jq`. Errors
835
+ print the error envelope and use the exit codes below. Reads retry on transient
836
+ failures; a write is never repeated: a 5xx or lost answer exits `5` and its
837
+ error has `outcomeUnknown: true` (the `code` may be the server's own, e.g.
838
+ `TEARDOWN_FAILED`; key on `outcomeUnknown`), so read current state before
839
+ sending it again. `-i` also prints the status line and headers of a failed
840
+ answer, before the error envelope.
841
+
842
+ ### `farthershore code` (code mode)
843
+
844
+ Code mode lets you (or an agent) find platform operations and run a script
845
+ against a typed `fs` client, instead of chaining one command per call. A
846
+ script can loop, filter and join results, and only its return value comes
847
+ back — the intermediate data never has to pass through a transcript.
848
+
849
+ **Agent quickstart: search → describe → types → run.**
850
+
851
+ ```bash
852
+ # 1. Find operations (ranked by name, path, summary and description).
853
+ farthershore code search runtime token --format json
854
+
855
+ # 2. Read one: description, TypeScript signature, safety flags, reconcile
856
+ # read, required permission and a runnable example.
857
+ farthershore code describe runtimeTokens.revoke
858
+
859
+ # 3. Load the types you need (or the whole API) before writing the script.
860
+ farthershore code types runtimeTokens > fs.d.ts
861
+
862
+ # 4. Run it. Reads only, unless you opt in.
863
+ farthershore code run -e '
864
+ const businesses = await fs.businesses.list();
865
+ return businesses.filter((b) => b.status === args.status).map((b) => b.name);
866
+ ' --arg status=DRAFT
867
+ ```
868
+
869
+ - **`code search <query...>`** — `--limit <n>` (default 20),
870
+ `--side-effect read|write`, `--namespace <ns>`. Each hit shows the path
871
+ (`fs.runtimeTokens.revoke`), a one-line summary and its flags (read/write,
872
+ destructive, one-time secret, preview, paginated).
873
+ - **`code describe <path|opKey>`** — accepts `runtimeTokens.revoke`,
874
+ `fs.runtimeTokens.revoke` or the opKey `runtime_token.revoke`.
875
+ - **`code types [namespace]`** — a self-contained `.d.ts` for `fs`, generated
876
+ from the same tables `code run` enforces, with JSDoc from the operation
877
+ specs.
878
+ - **`code run [file|-]` / `-e '<code>'`** — the script is the body of an async
879
+ function: `await` freely and `return` the result (or `export default` a value
880
+ or an async function of `{ fs, args }`). `.ts` files (or `--ts`) have their
881
+ types stripped by Node; only erasable syntax is supported. `--arg key=value`
882
+ (repeatable) and `--args-json '{…}'` fill `args`. The return value is
883
+ printed as JSON on stdout; `console.*` goes to stderr, or into
884
+ `{ result, logs }` with `--format json`. `code run` needs Node.js 22.8 or
885
+ newer (`CODE_MODE_UNSUPPORTED_NODE` otherwise); TypeScript needs 22.13.
886
+
887
+ In scope inside a script: `fs` (every method, plus `fs.call(opKey, input,
888
+ options)`), `args`, `paginate(method, input)` / `collect(method, input)`,
889
+ `refs.business(ref)` / `refs.environment(businessId, ref)`, `poll({ check,
890
+ until, deadlineMs })`, `console`, `setTimeout` / `clearTimeout` and
891
+ `FartherShoreError`. Nothing else: no `require`, `import()`, `process`,
892
+ `fetch`, filesystem or network.
893
+
894
+ **Policy** (checked in the CLI process for every call, not in the script):
895
+
896
+ | Call | Needs |
897
+ | ------------------------------------------------------- | ----------------------------------------------------------- |
898
+ | read | nothing |
899
+ | write | `--allow-writes` (else `CODE_MODE_WRITE_NOT_ALLOWED`) |
900
+ | destructive or one-time-secret write | `--allow-writes --yes` (else `CODE_MODE_APPROVAL_REQUIRED`) |
901
+ | preview (`{ dryRun: true }` on a method marked preview) | nothing — it changes nothing |
902
+ | managed-as-code (contract) write | always refused |
903
+
904
+ One-time secrets (minted or rotated credentials) come back to the script as
905
+ `{ redacted: true, last4 }` unless you pass `--reveal-secrets`; a redacted
906
+ secret cannot be shown again, so rotate with `--reveal-secrets` when you need
907
+ the value. Other results pass the same credential masking as the MCP server.
908
+
909
+ **Budgets:** `--max-calls` (200), `--max-writes` (20), `--timeout` (60s; `60`,
910
+ `60s`, `500ms`, `2m`) and at most 4 calls in flight. Exceeding one stops the
911
+ script with `CODE_MODE_BUDGET_EXCEEDED` / `CODE_MODE_TIMEOUT`; Ctrl-C stops it
912
+ too. Stopping terminates the script and cancels its in-flight requests.
913
+
914
+ **Exit codes:** `0` success; `1` script error, refusal, budget or timeout; an
915
+ uncaught operation error keeps the CLI codes below (`2` input, `3` auth, `5`
916
+ server or `OUTCOME_UNKNOWN` — the error names the read that reconciles it).
917
+
918
+ **Isolation and threat model.** The script runs in a worker thread that never
919
+ receives the credential, the CLI config or the environment (it starts with an
920
+ empty one); its only capability is a message channel to the CLI process,
921
+ which applies the policy above and makes the authenticated request. Requests
922
+ are marked `code-mode` in the client header for audit. The script itself runs
923
+ in a fresh JavaScript context with only the language built-ins. This keeps
924
+ credentials out of script memory and gates every side effect. It is **not** a
925
+ sandbox for hostile code — Node's `vm` is not a security boundary — so run
926
+ scripts you would run yourself. The hosted MCP will run scripts in an
927
+ isolated hosted runtime instead.
928
+
809
929
  ## Errors and exit codes
810
930
 
811
931
  In `--format json` mode, every command prints a single canonical envelope to
@@ -819,14 +939,31 @@ bracketed code is stable across releases — quote it in support threads.
819
939
  Exit codes are a branchable taxonomy so a script can tell failure classes apart
820
940
  without parsing text:
821
941
 
822
- | Exit | Meaning |
823
- | ---- | -------------------------------------------------------------- |
824
- | `0` | Success |
825
- | `2` | Validation error (HTTP 400/422) — fix your input |
826
- | `3` | Auth / permission error (401/403) — re-authenticate |
827
- | `4` | Conflict / managed-by-code (409) — change it in the repo |
828
- | `5` | Server error (5xx) — retry only when `error.retryable` is true |
829
- | `1` | Any other failure (local error, 402/404, argument errors, …) |
942
+ | Exit | Meaning |
943
+ | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
944
+ | `0` | Success |
945
+ | `2` | Validation error (HTTP 400/422) — fix your input |
946
+ | `3` | Auth / permission error (401/403) — re-authenticate |
947
+ | `4` | `MANAGED_BY_CODE` (409 with that code only) — the contract is managed as code; change it in the repo and push |
948
+ | `5` | Server error (5xx), or `OUTCOME_UNKNOWN` — a write whose outcome is unknown; never repeat it blindly (see below) |
949
+ | `1` | Any other failure: local errors, 402/404, argument errors, and every other 409 (`CONFLICT`, `RELEASE_PENDING_ACCEPTANCE`, `ROLLBACK_IN_FLIGHT`, …) |
950
+
951
+ A 409 other than `MANAGED_BY_CODE` means the server refused a repeat or a
952
+ conflicting change (an existing name, a release still being applied, a rollback
953
+ already running). Wait or reconcile; do not edit `business/` for it.
954
+
955
+ Exit `5` covers two different cases. Branch on `error.retryDisposition`:
956
+
957
+ - **`reconcile_before_repeat`.** The write may or may not have run. Run the
958
+ read named in `error.details.reconcile` (also listed per command by
959
+ `farthershore operations list`) and repeat only if that read shows it did
960
+ not happen. The error can retain a server code such as `UPSTREAM_TIMEOUT`;
961
+ a lost response uses `OUTCOME_UNKNOWN`. Neither code alone decides whether
962
+ repeating the write is safe.
963
+ - **`safe_to_repeat`.** Retry only when `error.retryable` is also true.
964
+
965
+ The CLI retries reads automatically. It retries a write only on 429, or when
966
+ the server marks the failure `retryable` with a `safe_to_repeat` verdict.
830
967
 
831
968
  ## CI and agent usage
832
969
 
@@ -846,6 +983,11 @@ Organization, role, member, invitation, and org MakerToken administration
846
983
  requires a live-user CLI session and is not projected into MCP; audit logs,
847
984
  notifications, and apply timelines are also CLI-only.
848
985
 
986
+ The server resolves its target exactly as the CLI does: the stored login,
987
+ `FARTHERSHORE_API_URL` (else the configured API URL), and the organization
988
+ linked to its working directory, else the one chosen with
989
+ `farthershore auth organization use`.
990
+
849
991
  ```bash
850
992
  farthershore-mcp
851
993
  ```