End-to-end via CLI/MCP
A complete agent-run lifecycle — create, define, add a plan, and publish CronCloud — entirely from the CLI with a maker token, no browser.
A complete agent-run lifecycle — create, define, add a plan, and publish CronCloud — entirely from the CLI with a maker token, no browser.
This is the whole job, start to finish, the way an agent runs it: create a
product, author its contract as a decorated @Product class, add a plan, and
publish — using only the farthershore CLI and a maker
token. The single browser-only step (Stripe
Connect) is a status poll, not a blocker the agent performs.
We use CronCloud (managed cron jobs) as the running product.
export FARTHERSHORE_TOKEN=mk_xxx
farthershore auth whoami --format json # confirm org + scopes before starting
product create provisions a managed GitHub repo and the editable frontend/
starter, then returns clone-URL + bootstrap instructions. It is a dual
operation and CLI-only (it needs a workstation to scaffold the repo).
farthershore product create \
--name croncloud \
--display-name "CronCloud" \
--description "Managed cron jobs" \
--origin https://api.example.com \
--repo-owner acme --repo-name croncloud \
--format json
--origin is the builder-owned URL Farther Shore calls for customer-facing
actions. --repo-owner must be a connected GitHub account. The product starts as
a DRAFT:
{
"schema_version": 1,
"ok": true,
"op": "product.create",
"data": { "result": { "id": "prod_…", "name": "croncloud", "status": "DRAFT", "repoUrl": "https://github.com/acme/croncloud" } }
}
No GitHub connection yet? farthershore connect github --format json reports
status the agent can poll until the user finishes the browser OAuth. Connect
flows are intentionally browser-only.
Clone the repo and edit product/product.config.ts. The product's plans, routes,
meters, and capabilities are the contract — authored as one decorated
@Product class (the only authoring surface; there is no YAML). This is the
canonical CronCloud shape:
import {
Capability,
Feature,
Meter,
Plan,
Product,
Requests,
Resource,
capabilityGrant,
} from "@farthershore/product";
@Product({
name: "croncloud",
origin: "https://api.example.com",
displayName: "CronCloud",
description: "Managed cron jobs",
billOn4xx: false,
})
class CronCloud {
@Requests()
requests!: unknown;
@Meter("compute", { display: "Compute", unit: "ms" })
compute!: unknown;
@Resource("cron_jobs", { display: "Cron jobs", countSource: "action_inferred" })
cronJobs!: unknown;
@Capability("managed-cron", {
title: "Managed Cron Jobs",
includesFeatures: ["cron-jobs"],
})
managedCron!: unknown;
@Feature("cron-jobs", {
description: "Cron job CRUD",
plans: ["starter", "pro"],
actions: [
{
id: "cron-job.create",
kind: "mutation",
: ,
: { : , : },
},
{
: ,
: ,
: ,
: { : , : , : },
: { : , : },
: ,
},
],
: {
: {},
: { : },
: { : },
},
})
cronJobsFeature!: ;
(, {
: ,
: { : , : , : },
: [(, { : { : } })],
: { : { : , : , : } },
})
starter!: ;
(, {
: ,
: { : , : , : },
: [(, { : { : } })],
: { : { : , : , : } },
})
pro!: ;
}
;
Then validate locally. farthershore build executes the decorated class and
emits a Manifest IR envelope (manifest-ir.json) — it runs the same manifest
gates the GitHub bot enforces (e.g. every plan needs at least one rate-limit
rule), so a green local build means the push will compile and apply. The
remaining publish gates (a verified Stripe connection, an origin) are checked
server-side at publish time.
cd product && npm install # once
farthershore build --format json # from the repo root
Because both plans above are defined in the manifest, the change goes live when
you git push — the GitHub bot compiles and applies it. An out-of-band
plan create against this published product would return MANAGED_BY_CODE
(exit 4); see operation classes.
During the DRAFT window — before the first publish — the API still accepts
contractual writes so an agent can seed the repo without a push. This is the one
time plan create works directly:
farthershore plan create croncloud \
--key starter --name "Starter" --recurring-fee-cents 2900 --format json
farthershore plan create croncloud \
--key pro --name "Pro" --recurring-fee-cents 19900 --format json
farthershore plan list croncloud --format json
After publish, the manifest is canonical — edit @Plan and push instead.
Publishing requires a verified Stripe connection. The flow is browser-only; the CLI reports status so the agent can poll and hand the URL to the user:
farthershore connect stripe croncloud --format json
# poll until: { "data": { "status": "verified" } }
product publish is a dual operation — an agent may publish its own
product. It enforces the same gates as the dashboard: at least one plan, an
origin, and a verified Stripe connection.
farthershore product publish croncloud --format json
{ "schema_version": 1, "ok": true, "op": "product.publish", "data": { "status": "ACTIVE" } }
If a gate fails, the envelope is ok: false with the actionable code —
MISSING_ORIGIN (a 400, exit 2), or one of the billing gates
STRIPE_NOT_CONNECTED / STRIPE_NOT_VERIFIED / BILLING_TAX_NOT_ENROLLED
(a 402 the agent surfaces to the user, since connecting Stripe is browser-only).
Branch on the exit code and the
code, not on the message text.
product status is an operate read — it reflects the deployed runtime state,
not just what was applied:
farthershore product status croncloud --format json
farthershore usage summary croncloud --format json
To exercise the live gateway end-to-end, bootstrap a test persona (test-strategy environments only) and call a route with the minted key:
farthershore persona bootstrap croncloud --env preview --plan pro --format json
# → returns an fsk_test_* key; call POST https://croncloud.…/v1/cron-jobs with it
A hosted agent without a shell drives the operate/dual steps as fs_*
tools — fs_product_publish, fs_product_status, fs_usage_read,
fs_persona_bootstrap, fs_runtime_token_mint. The CLI-only steps (create,
build, and the contract edits) run in the agent's workstation. See
the MCP page for the full tool/command map.
If CronCloud's logic runs on your own server, mint a runtime token and verify
requests with @farthershore/backend:
farthershore backend create croncloud \
--name "Production API" --origin-url https://api.example.com --format json
farthershore backend tokens create croncloud --backend <backendId> --format json
# → returns fsrt_… ONCE; deploy it as FS_RUNTIME_TOKEN
import { fartherShore, withUsage } from "@farthershore/backend";
const fs = fartherShore.initFromEnv();
export async function POST(request: Request) {
const url = new URL(request.url);
const body = await request.clone().arrayBuffer();
await fs.verifyRequest({
method: request.method,
path: url.pathname,
query: url.search,
headers: request.headers,
body: new Uint8Array(body),
});
const result = await createCronJob(await request.json());
return withUsage(request, Response.json(result), { compute: result.elapsedMs });
}
That is the complete loop: create with the CLI, author the contract as code, seed and publish, then operate at runtime — every step copy-pasteable, no browser in the hot path.