@farthershore/cli 0.34.0 → 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.34.0`.** 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.34.0
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.34.0 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.34.0 farthershore-mcp
36
+ npx -p @farthershore/cli@0.35.0 farthershore-mcp
37
37
  ```
38
38
 
39
39
  ## Authentication
@@ -274,7 +274,7 @@ farthershore business create weather-api \
274
274
  --format json
275
275
 
276
276
  farthershore business publish weather-api --format json
277
- farthershore business rollback weather-api wfe_previous --reason "bad release" --format json
277
+ farthershore business rollback weather-api wfe_previous --reason "bad release" --yes --format json
278
278
  ```
279
279
 
280
280
  Deleting a business is **stage only** — the command is not registered in the
@@ -321,13 +321,13 @@ and requires the exact `business:rollback` permission.
321
321
  `rbacEnabled` flag. `business rbac enable` / `disable` toggle that flag (like
322
322
  `docsEnabled`, it is platform-owned and never repo-authored); once enabled,
323
323
  subscriber organizations manage roles in their portal. Both toggles are
324
- 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
325
325
  disable, so re-enabling is a pure flag flip.
326
326
 
327
327
  ```bash
328
328
  farthershore business rbac weather-api --format json
329
329
  farthershore business rbac enable weather-api --format json
330
- farthershore business rbac disable weather-api --format json
330
+ farthershore business rbac disable weather-api --yes --format json
331
331
  ```
332
332
 
333
333
  `business rbac subjects list` lists the custom permission vocabulary: subjects
@@ -406,7 +406,7 @@ farthershore token approve <approvalId> --org \
406
406
  farthershore token deny <approvalId> --org --format json
407
407
  farthershore token update <tokenId> --org \
408
408
  --permissions '["business:read"]' --format json
409
- farthershore token rotate <tokenId> --org --format json
409
+ farthershore token rotate <tokenId> --org --yes --format json
410
410
  farthershore token revoke <tokenId> --org --yes --format json
411
411
  ```
412
412
 
@@ -524,7 +524,7 @@ with placeholder data and auth bypassed.
524
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 rollback weather-api --release-id <hash> --yes --format json
528
528
 
529
529
  # Local preview (shells out to vite; needs a frontend/ project)
530
530
  farthershore frontend dev # hot-reloading dev server, mock mode by default
@@ -570,8 +570,8 @@ farthershore persona bootstrap weather-api --env preview --plan pro \
570
570
  --subscriber-id <subscriberId> --account-role member --role viewer \
571
571
  --format json
572
572
 
573
- farthershore persona rotate weather-api <personaId> --env preview --format json
574
- 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
575
575
  ```
576
576
 
577
577
  Bootstrap and rotation return a key once. `login` sends the 60-second,
@@ -657,7 +657,7 @@ farthershore backend bind weather-api api \
657
657
  --origin-url https://api-pr-42.example.com \
658
658
  --format json
659
659
  farthershore backend tokens create weather-api --backend <backendId> --format json
660
- farthershore backend tokens rotate weather-api <tokenId> --format json
660
+ farthershore backend tokens rotate weather-api <tokenId> --yes --format json
661
661
  farthershore backend tokens revoke weather-api <tokenId> --yes --format json
662
662
  ```
663
663
 
@@ -707,7 +707,7 @@ farthershore webhook update weather-api <webhookId> --disable --format json
707
707
  farthershore webhook test weather-api <webhookId> --format json
708
708
  farthershore webhook trigger weather-api <webhookId> --type payment.failed --format json
709
709
  farthershore webhook deliveries weather-api <webhookId> --limit 20 --format json
710
- farthershore webhook rotate weather-api <webhookId> --format json
710
+ farthershore webhook rotate weather-api <webhookId> --yes --format json
711
711
  farthershore webhook delete weather-api <webhookId> --yes --format json
712
712
  ```
713
713
 
@@ -763,18 +763,18 @@ Use `--all-plans` to clear plan scoping.
763
763
  Install or refresh the platform-verified skills bundle together. The platform
764
764
  pins this immutable release, and CI plus a daily repository check compare it to
765
765
  GitHub's latest stable release so the guidance cannot silently drift.
766
- This command requires `@farthershore/cli` 0.34.0 or newer.
766
+ This command requires `@farthershore/cli` 0.35.0 or newer.
767
767
 
768
768
  ```bash
769
769
  set -euo pipefail
770
770
  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; } })'
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; } })'
772
772
  farthershore skills install --help
773
773
  farthershore skills install
774
774
  ```
775
775
 
776
776
  If either prerequisite fails, stop and ask the human to run
777
- `npm install -g @farthershore/cli@0.34.0`, then repeat the checks.
777
+ `npm install -g @farthershore/cli@0.35.0`, then repeat the checks.
778
778
  Do not install or upgrade global tooling autonomously.
779
779
 
780
780
  The Farther Shore CLI downloads the exact installer archive and verifies this
@@ -789,6 +789,143 @@ Print the machine-readable action boundary: which operations are repo-authored
789
789
  farthershore operations list --format json
790
790
  ```
791
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
+
792
929
  ## Errors and exit codes
793
930
 
794
931
  In `--format json` mode, every command prints a single canonical envelope to
@@ -802,14 +939,31 @@ bracketed code is stable across releases — quote it in support threads.
802
939
  Exit codes are a branchable taxonomy so a script can tell failure classes apart
803
940
  without parsing text:
804
941
 
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, …) |
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.
813
967
 
814
968
  ## CI and agent usage
815
969
 
@@ -829,6 +983,11 @@ Organization, role, member, invitation, and org MakerToken administration
829
983
  requires a live-user CLI session and is not projected into MCP; audit logs,
830
984
  notifications, and apply timelines are also CLI-only.
831
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
+
832
991
  ```bash
833
992
  farthershore-mcp
834
993
  ```