@farthershore/cli 0.33.2 → 0.33.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +105 -48
  2. package/dist/index.js +136 -106
  3. package/dist/mcp.js +84 -57
  4. package/package.json +6 -6
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.2`.** Pre-1.0 — the command surface is stable but may still
6
+ **Status: `0.33.4`.** 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.2
24
+ npm install -g @farthershore/cli@0.33.4
25
25
  ```
26
26
 
27
27
  Or run it directly with npx:
28
28
 
29
29
  ```bash
30
- npx -p @farthershore/cli@0.33.2 farthershore --help
30
+ npx -p @farthershore/cli@0.33.4 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.2 farthershore-mcp
36
+ npx -p @farthershore/cli@0.33.4 farthershore-mcp
37
37
  ```
38
38
 
39
39
  ## Authentication
@@ -88,7 +88,8 @@ Create a managed business repo, author the business, validate it, and push:
88
88
 
89
89
  ```bash
90
90
  # 1. Create the managed repo. Human output is exactly its URL.
91
- REPO_URL=$(farthershore business create weather-api)
91
+ REPO_URL=$(farthershore business create weather-api \
92
+ --idempotency-key <persisted-business-create-attempt-key>)
92
93
 
93
94
  # 2. Clone it and author business/ from scratch.
94
95
  git clone "$REPO_URL"
@@ -219,12 +220,14 @@ farthershore build --format json
219
220
  Run the same verdict the GitHub `farthershore/validate` PR check produces, before
220
221
  you push. It builds the manifest twice and compares the IR hash (the determinism
221
222
  check the build runner enforces), then — when you pass `--business` — submits the
222
- IR for a server dry-run that returns compile diagnostics, the semantic diff, and
223
- an economic-change verdict.
223
+ IR for a server verdict that returns compile diagnostics, the semantic diff, and
224
+ an economic-change verdict. The server may retain a diagnostic compilation
225
+ record, but validation never accepts or publishes business state, so it is safe
226
+ to repeat and does not take an idempotency key.
224
227
 
225
228
  ```bash
226
229
  farthershore validate # local build + determinism only
227
- farthershore validate --business weather-api # + full server verdict
230
+ farthershore validate --business weather-api
228
231
  farthershore validate --local-only --format json
229
232
  ```
230
233
 
@@ -245,10 +248,11 @@ farthershore business subdomain-check weather-api --format json
245
248
 
246
249
  farthershore business create weather-api \
247
250
  --display-name "Weather API" \
251
+ --idempotency-key <persisted-business-create-attempt-key> \
248
252
  --format json
249
253
 
250
- farthershore business publish weather-api --format json
251
- farthershore business rollback weather-api wfe_previous --reason "bad release" --format json
254
+ farthershore business publish weather-api --format json --idempotency-key <persisted-business-publish-attempt-key>
255
+ farthershore business rollback weather-api wfe_previous --reason "bad release" --format json --idempotency-key <persisted-business-rollback-attempt-key>
252
256
  farthershore business delete weather-api --yes --format json
253
257
  ```
254
258
 
@@ -338,7 +342,7 @@ farthershore organization role list <organizationId> --filter builtIn=true --sor
338
342
  farthershore organization members <organizationId> --filter roleKey=auditor --sort email:asc --search person --format json
339
343
 
340
344
  # Primary owner only; hands ultimate authority to another existing member.
341
- farthershore organization ownership-transfer <organizationId> <newOwnerUserId> --yes --format json
345
+ farthershore organization ownership-transfer <organizationId> <newOwnerUserId> --yes --format json --idempotency-key <persisted-organization-ownership-transfer-attempt-key>
342
346
  ```
343
347
 
344
348
  Selecting the default organization is a local credential concern, not an API
@@ -365,14 +369,15 @@ farthershore token create deploy-bot --org \
365
369
 
366
370
  farthershore token list --org --format json
367
371
  farthershore token approvals --org --format json
368
- farthershore token approve <approvalId> --org --format json
372
+ farthershore token approve <approvalId> --org --format json --idempotency-key <persisted-token-approve-attempt-key>
369
373
  farthershore token approve <approvalId> --org \
374
+ --idempotency-key <persisted-token-approve-attempt-key> \
370
375
  --permissions '["business:read"]' --format json # trim on approval
371
376
  farthershore token deny <approvalId> --org --format json
372
377
  farthershore token update <tokenId> --org \
373
378
  --permissions '["business:read"]' \
374
379
  --idempotency-key org-token-update-2026-08-11 --format json
375
- farthershore token rotate <tokenId> --org --format json
380
+ farthershore token rotate <tokenId> --org --format json --idempotency-key <persisted-token-rotate-attempt-key>
376
381
  farthershore token revoke <tokenId> --org --yes --format json
377
382
  ```
378
383
 
@@ -383,9 +388,10 @@ exactly once; store it immediately. Human output follows the same rule: it
383
388
  prints plaintext only for an ACTIVE one-time-secret response and says explicitly
384
389
  when a PENDING request minted nothing.
385
390
 
386
- Create and update accept `--idempotency-key`. Reuse the same key when repeating
387
- the same logical request after an interrupted invocation. If omitted, the CLI
388
- generates a key and keeps it stable across its automatic transient retries.
391
+ Create and update require `--idempotency-key`. Persist the key before the first
392
+ dispatch and reuse it only when repeating that exact logical request after an
393
+ interrupted invocation. The CLI never generates a key: a process-local key
394
+ would be lost across the restart where retry safety matters most.
389
395
 
390
396
  ### `farthershore stripe`
391
397
 
@@ -430,15 +436,17 @@ farthershore service-account approvals <businessId> --format json
430
436
  farthershore service-account approve <businessId> <approvalId> \
431
437
  --permissions '["route:read"]' --format json # optional trim
432
438
  farthershore service-account deny <businessId> <approvalId> --format json
433
- farthershore service-account rotate <businessId> <serviceAccountId> --format json
439
+ farthershore service-account rotate <businessId> <serviceAccountId> --format json --idempotency-key <persisted-service-account-rotate-attempt-key>
434
440
  farthershore service-account revoke <businessId> <serviceAccountId> --yes --format json
435
441
  ```
436
442
 
437
443
  Pass `--environment-id` and `--subscriber-organization` to override the two
438
444
  context environment variables for one command. PENDING create/update responses
439
445
  never print `plaintext`. ACTIVE creates
440
- and rotations print the one-time key once. Create/update idempotency follows the
441
- same explicit-key or generated stable in-flight retry behavior as org tokens.
446
+ and rotations print the one-time key once. Create, update, and rotate require a
447
+ caller-persisted key, just like one-time-secret org-token operations. A replayed
448
+ success includes `meta.idempotency.replayed: true`; follow it with a list read
449
+ before reporting current state.
442
450
 
443
451
  This group is direct CLI-only and deliberately has no MCP projection: the MCP
444
452
  operation registry carries builder principals, not portal consumer sessions.
@@ -484,8 +492,9 @@ farthershore plan list weather-api --format json
484
492
 
485
493
  ### `farthershore frontend`
486
494
 
487
- Managed Frontend Hosting operations plus a local preview. `deploy`, `status`,
488
- and `rollback` operate the hosted build; `preview` runs the generated
495
+ Managed Frontend Hosting status, rollback, and a local preview. Builds are
496
+ Git-triggered: production builds on a GitHub Release and preview environments
497
+ build on pushes to their environment branch. `preview` runs the generated
489
498
  `frontend/`'s `vite dev` with an injected `window.__FS_CONFIG__` shim so the
490
499
  portal renders locally exactly as it serves in production. Pass `--mock` to
491
500
  inject `{ mock: true }` instead of Core/Clerk config for local page inspection
@@ -493,7 +502,6 @@ with placeholder data and auth bypassed.
493
502
 
494
503
  ```bash
495
504
  farthershore frontend status weather-api --format json
496
- farthershore frontend deploy weather-api --format json
497
505
  farthershore frontend rollback weather-api --release-id <hash> --format json
498
506
 
499
507
  # Local preview (shells out to vite; needs a frontend/ project)
@@ -501,18 +509,63 @@ farthershore frontend dev # hot-reloading dev server,
501
509
  farthershore frontend preview # production build served as-is, mock mode by default
502
510
  farthershore frontend dev --live # against the real platform
503
511
  farthershore frontend preview --core-url http://localhost:8787
504
- ```
512
+
513
+ # Local preview signed in as a test persona (hot reload against the real platform)
514
+ farthershore frontend dev --live --business acme --env preview --persona alice
515
+ farthershore frontend dev --live --business acme --env preview --persona persona_123 --port 4321
516
+ ```
517
+
518
+ `--live --business --env --persona` (all three required, not combinable with
519
+ `--mock` or `--core-url`) opens `http://localhost:<port>` (default 5173)
520
+ already signed in as that test persona, exactly as the hosted portal would
521
+ be. The CLI issues a short-lived, origin-bound preview lease from Core and
522
+ holds it only in its own process memory; Vite runs on a private random
523
+ loopback port behind a CLI-owned proxy that attaches the lease to portal
524
+ requests and serves the secret-free sign-in bridge. Nothing secret ever
525
+ reaches the browser URL, DOM, storage, or logs — the browser only ever holds
526
+ the same HttpOnly session cookie the hosted portal uses. The lease renews
527
+ automatically while the command runs and is revoked when it exits (Ctrl-C
528
+ included); a lease that cannot be revoked expires within five minutes. Core
529
+ is always the one selected for the authenticated CLI (`--api-url` /
530
+ `FARTHERSHORE_API_URL`).
505
531
 
506
532
  ### `farthershore persona`
507
533
 
508
- Mint fresh `fsk_test_*` API keys in a test-strategy environment so you can
509
- exercise the gateway end-to-end.
534
+ Create temporary subscriber users in a test-strategy environment so an agent
535
+ can exercise the same customer RBAC, browser session, and gateway paths as a
536
+ human. The Business SDK contributes raw permissions; the subscribing
537
+ organization composes those permissions into its own product roles.
510
538
 
511
539
  ```bash
512
- farthershore persona bootstrap weather-api --env preview --plan pro --format json
540
+ # First persona creates and owns a temporary subscriber workspace.
541
+ farthershore persona bootstrap weather-api --env preview --plan pro --format json --idempotency-key <persisted-persona-bootstrap-attempt-key>
513
542
  farthershore persona list weather-api --env preview --format json
514
- farthershore persona revoke weather-api <personaId> --env preview --format json
515
- ```
543
+ farthershore persona login weather-api <ownerPersonaId> --env preview --format json
544
+
545
+ # Join the same subscriber after its owner has enabled subscriber RBAC and
546
+ # created roles/defaults through the normal customer RBAC surface.
547
+ farthershore persona bootstrap weather-api --env preview --plan pro \
548
+ --subscriber-id <subscriberId> --account-role member --role viewer \
549
+ --format json --idempotency-key <persisted-member-bootstrap-attempt-key>
550
+
551
+ farthershore persona rotate weather-api <personaId> --env preview --format json --idempotency-key <persisted-persona-rotate-attempt-key>
552
+ farthershore persona delete weather-api <personaId> --env preview --format json
553
+ ```
554
+
555
+ Bootstrap and rotation return a key once. `login` sends the 60-second,
556
+ single-use handoff only in the browser URL fragment to the platform-owned
557
+ `/persona-sign-in` bridge. The bridge establishes an HttpOnly browser session
558
+ and returns to the exact original portal path, query, and fragment. CLI output
559
+ contains portal metadata but never the handoff or a session bearer. Product
560
+ role keys must already exist in that subscriber's role catalog; omit `--role` to use its
561
+ configured default. Deletion tears down the temporary user and credential, and
562
+ deleting the environment also removes the remaining persona-owned workspace.
563
+ After an environment-branch apply, existing personas remain available but are
564
+ detached from the superseded subscription. Use `persona login` with the same
565
+ persona id and select a plan from the new branch contract; the CLI does not
566
+ fall back to a production plan. The old raw test key stays invalid. After the
567
+ persona is reattached, run `persona rotate` to mint a replacement key for
568
+ gateway testing; its product roles and explicit scope ceiling are preserved.
516
569
 
517
570
  ### `farthershore usage`
518
571
 
@@ -548,11 +601,11 @@ compiled integration that references it.
548
601
  ```bash
549
602
  farthershore variables list weather-api --format json
550
603
  printf '%s' "$SENTRY_AUTH_TOKEN" | farthershore variables set weather-api \
551
- SENTRY_AUTH_TOKEN --format json
604
+ SENTRY_AUTH_TOKEN --format json --idempotency-key <persisted-sentry-variable-attempt-key>
552
605
  printf '%s' "$POSTHOG_PROJECT_KEY" | farthershore variables set weather-api \
553
- FS_PUBLIC_POSTHOG_KEY --format json
554
- farthershore variables revoke weather-api POSTHOG_SECRET --yes --format json
555
- farthershore variables rm weather-api POSTHOG_SECRET --yes --format json
606
+ FS_PUBLIC_POSTHOG_KEY --format json --idempotency-key <persisted-posthog-variable-attempt-key>
607
+ farthershore variables revoke weather-api POSTHOG_SECRET --yes --format json --idempotency-key <persisted-variables-revoke-attempt-key>
608
+ farthershore variables rm weather-api POSTHOG_SECRET --yes --format json --idempotency-key <persisted-variables-rm-attempt-key>
556
609
  ```
557
610
 
558
611
  There is no builder-managed public-variable surface and values are never
@@ -570,13 +623,14 @@ farthershore backend list weather-api --format json
570
623
  farthershore backend create weather-api \
571
624
  --name "Production API" \
572
625
  --origin-url https://api.example.com \
626
+ --idempotency-key <persisted-backend-create-attempt-key> \
573
627
  --format json
574
628
  farthershore backend bind weather-api api \
575
629
  --env preview-42 \
576
630
  --origin-url https://api-pr-42.example.com \
577
631
  --format json
578
- farthershore backend tokens create weather-api --backend <backendId> --format json
579
- farthershore backend tokens rotate weather-api <tokenId> --format json
632
+ farthershore backend tokens create weather-api --backend <backendId> --format json --idempotency-key <persisted-backend-tokens-create-attempt-key>
633
+ farthershore backend tokens rotate weather-api <tokenId> --format json --idempotency-key <persisted-backend-tokens-rotate-attempt-key>
580
634
  farthershore backend tokens revoke weather-api <tokenId> --yes --format json
581
635
  ```
582
636
 
@@ -621,12 +675,13 @@ farthershore webhook list weather-api --format json
621
675
  farthershore webhook create weather-api \
622
676
  --url https://hooks.example.com/farthershore \
623
677
  --events subscription.created,payment.failed \
678
+ --idempotency-key <persisted-webhook-create-attempt-key> \
624
679
  --format json
625
680
  farthershore webhook update weather-api <webhookId> --disable --format json
626
- farthershore webhook test weather-api <webhookId> --format json
627
- farthershore webhook trigger weather-api <webhookId> --type payment.failed --format json
681
+ farthershore webhook test weather-api <webhookId> --idempotency-key <persisted-test-attempt-key> --format json
682
+ farthershore webhook trigger weather-api <webhookId> --type payment.failed --idempotency-key <persisted-trigger-attempt-key> --format json
628
683
  farthershore webhook deliveries weather-api <webhookId> --limit 20 --format json
629
- farthershore webhook rotate weather-api <webhookId> --format json
684
+ farthershore webhook rotate weather-api <webhookId> --format json --idempotency-key <persisted-webhook-rotate-attempt-key>
630
685
  farthershore webhook delete weather-api <webhookId> --yes --format json
631
686
  ```
632
687
 
@@ -665,8 +720,10 @@ Manage checkout promo codes (operational state, not part of the manifest).
665
720
  farthershore promo-code list weather-api --format json
666
721
  farthershore promo-code create weather-api \
667
722
  --code LAUNCH25 --kind percent_off --percent 25 --duration-months 3 \
723
+ --idempotency-key <persisted-promo-code-create-attempt-key> \
668
724
  --plan <planId> --format json
669
725
  farthershore promo-code create weather-api \
726
+ --idempotency-key <persisted-promo-code-create-attempt-key> \
670
727
  --code SAVE500 --kind amount_off --amount-cents 500 --duration-months 1 --format json
671
728
  farthershore promo-code archive weather-api <promoCodeId> --format json
672
729
  farthershore promo-code reactivate weather-api <promoCodeId> --format json
@@ -682,18 +739,18 @@ Use `--all-plans` to clear plan scoping.
682
739
  Install or refresh the platform-verified skills bundle together. The platform
683
740
  pins this immutable release, and CI plus a daily repository check compare it to
684
741
  GitHub's latest stable release so the guidance cannot silently drift.
685
- This command requires `@farthershore/cli` 0.33.2 or newer.
742
+ This command requires `@farthershore/cli` 0.33.4 or newer.
686
743
 
687
744
  ```bash
688
745
  set -euo pipefail
689
746
  CLI_VERSION=$(farthershore --version)
690
- 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] >= 2))) { console.error("Stop: CLI 0.33.2 or newer is required; ask the human to upgrade."); process.exitCode = 1; } })'
747
+ 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] >= 4))) { console.error("Stop: CLI 0.33.4 or newer is required; ask the human to upgrade."); process.exitCode = 1; } })'
691
748
  farthershore skills install --help
692
749
  farthershore skills install
693
750
  ```
694
751
 
695
752
  If either prerequisite fails, stop and ask the human to run
696
- `npm install -g @farthershore/cli@0.33.2`, then repeat the checks.
753
+ `npm install -g @farthershore/cli@0.33.4`, then repeat the checks.
697
754
  Do not install or upgrade global tooling autonomously.
698
755
 
699
756
  The Farther Shore CLI downloads the exact installer archive and verifies this
@@ -721,14 +778,14 @@ bracketed code is stable across releases — quote it in support threads.
721
778
  Exit codes are a branchable taxonomy so a script can tell failure classes apart
722
779
  without parsing text:
723
780
 
724
- | Exit | Meaning |
725
- | ---- | ------------------------------------------------------------ |
726
- | `0` | Success |
727
- | `2` | Validation error (HTTP 400/422) — fix your input |
728
- | `3` | Auth / permission error (401/403) — re-authenticate |
729
- | `4` | Conflict / managed-by-code (409) — change it in the repo |
730
- | `5` | Server error (5xx) — retry |
731
- | `1` | Any other failure (local error, 402/404, argument errors, …) |
781
+ | Exit | Meaning |
782
+ | ---- | -------------------------------------------------------------- |
783
+ | `0` | Success |
784
+ | `2` | Validation error (HTTP 400/422) — fix your input |
785
+ | `3` | Auth / permission error (401/403) — re-authenticate |
786
+ | `4` | Conflict / managed-by-code (409) — change it in the repo |
787
+ | `5` | Server error (5xx) — retry only when `error.retryable` is true |
788
+ | `1` | Any other failure (local error, 402/404, argument errors, …) |
732
789
 
733
790
  ## CI and agent usage
734
791