foldrun 0.8.0 → 0.9.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/docs/api.md CHANGED
@@ -16,8 +16,8 @@ refuses — there is no separate "browser API". The exceptions carry their own
16
16
  proof instead of a key: a token or signature (`/s/<token>`, `/api/hooks`,
17
17
  `/api/inbox`, `/api/events`, `/api/approve`, `/api/unsubscribe`, `/api/git`, `/api/billing/stripe`,
18
18
  `/api/billing/confirm`), the sign-in and recovery steps (`/api/auth/signup`,
19
- `login`, `forgot`, `reset`, `verify`, `mfa/verify`, `/api/cli/login`,
20
- `/api/cli/authorize` GET, `/api/oauth/callback`), `/api/healthz`, `/api/status`, `/api/status/history`, `/api/version` and `/api/openapi.json`.
19
+ `login`, `forgot`, `reset`, `verify`, `mfa/verify`, `session`, `/api/cli/login`,
20
+ `/api/cli/login/<id>`, `/api/cli/authorize` GET, `/api/oauth/callback`), `/api/healthz`, `/api/status`, `/api/status/history`, `/api/version` and `/api/openapi.json`.
21
21
 
22
22
  ```
23
23
  Authorization: Bearer <api-key> # machines. Settings → API keys
@@ -65,7 +65,7 @@ api workspaces/$WS/flows/publish/run -X POST # start a flow
65
65
 
66
66
  | Route | Methods | |
67
67
  |---|---|---|
68
- | `/api/workspaces` | GET, POST | list; create (`name`, `template`, `demo`, `files`) |
68
+ | `/api/workspaces` | GET, POST | list; create (`name`, plus one of: `template: true` — blank, just an AGENTS.md, what the dashboard sends; `starter: true` — the example researcher, writer and publish flow; `demo: true`; `files`) |
69
69
  | `/api/workspaces/<ws>` | GET, PATCH, DELETE | read; rename or re-describe (`name`, `description`), or set its defaults (`timezone`, `budget`, `notify` — null clears one, edited in AGENTS.md in place); delete |
70
70
  | `/api/workspaces/<ws>/vocabulary` | GET | which document kinds this workspace admits — its `docs` list is what `[[ ]]` resolves: knowledge, memory, `state/` and `storage/` files, plus the folders themselves |
71
71
 
@@ -113,12 +113,18 @@ swap happen in one step, so nothing can slip in between. Absent, no check.
113
113
  Never touched by a deploy: `runs/`, `state/`, `secrets.json`, and any memory an
114
114
  agent wrote that the push does not mention.
115
115
 
116
+ | Route | Methods | |
117
+ |---|---|---|
118
+ | `/api/workspaces/<ws>/deploy` | POST | deploy the whole workspace from a list of files — checked as a whole first, refused with `422` and `issues` when it does not check out. `dryRun`, `force`, `expectRemoved` below. Idempotent |
119
+ | `/api/workspaces/<ws>/runtimes` | GET | the `runtime:` builds a deploy started, each with its `state` — below |
120
+
116
121
  `GET /api/workspaces/<ws>/runtimes` answers what a deploy started on the
117
122
  platform: every distinct `runtime:` the workspace's agents need
118
- (`fingerprint`, `agents`, `python`, `packages`, `npm`, and `rejected` for
119
- entries that are not requirements), each with the `state` of the build the
123
+ (`fingerprint`, `agents`, `python`, `packages`, `npm`, `system` — the Debian
124
+ packages it apt-installs — and `rejected` for entries that are not
125
+ requirements), each with the `state` of the build the
120
126
  platform ran for it — `pending`, `building`, `ready`, or `failed` with the
121
- installer's own `error`. `foldrun deploy` polls it after a deploy.
127
+ installer's `error`, its cause on the first line. `foldrun deploy` polls it after a deploy.
122
128
 
123
129
  ## Flows
124
130
 
@@ -144,7 +150,11 @@ everywhere else a run is live.
144
150
  | Route | Methods | |
145
151
  |---|---|---|
146
152
  | `/api/workspaces/<ws>/agents` | GET, POST | list; create (`name`) |
153
+ | `/api/workspaces/<ws>/agents/<agent>` | PATCH | edit one line of an agent's frontmatter in place, as the flow canvas does: `{ list: { key, name, action } }` — `key` one of `subagents`, `agents`, `tools`; `action` `add` or `remove` — or `{ set: { key: "description", value } }`. Only that key's lines change — comments, key order and prose stay. Answers `{ ok, changed }`; 404 for an unknown agent. `workspace:write` |
147
154
  | `/api/workspaces/<ws>/agents/<agent>/run` | POST | run one agent alone (`task`; `test: true` for a test run) |
155
+ | `/api/workspaces/<ws>/agents/import` | GET, POST | copy an agent in from another workspace of the account. GET lists what could be imported (every other workspace you can read, with its agents). POST `{ from, agent, as? }` copies `agents/<agent>/` — its agent.md and own skills/, scripts/, knowledge/; never memory/, outputs/, state/ — and answers `{ name, files, warnings }`; `warnings` names tools, skills, scripts and secrets the copy uses that this workspace lacks. 409 on a name clash (pass `as`), 404 for an unknown workspace or agent, 403 when `from` is outside your key's scope. Idempotent |
156
+ | `/api/workspaces/<ws>/export` | GET | this workspace, one flow or one agent as a `.zip` — `?kind=flow&name=<flow>` (the flow, every flow it runs as a step, every agent those steps and their colleagues name, and the workspace tools, skills and scripts they grant), `?kind=agent&name=<agent>` (its folder and what it grants), or neither for the whole authored tree. Never memory/, state/, outputs/, storage, runs or a secret's value; `foldrun-package.json` inside says what it is. Any role that can read the workspace |
157
+ | `/api/workspaces/<ws>/import` | POST | take a package in; the body is the `.zip` (`content-type: application/zip`, at most 20 MB). `?dryRun=1` answers `{ kind, name, from, creates, added, overwritten, unchanged, needs: { secrets, tools, skills, scripts, agents, flows } }` plus `skipped` (entries it will not write: memory, state, anything outside the authored folders) and writes nothing. Without it, applies as one revision; 409 when it would replace a file with different text unless `?overwrite=1`, or when a file cannot be written there (all or nothing). Never deletes. A `Content-Length` is required (411 without). A workspace package into a name that does not exist makes that workspace (`workspace:manage`, the plan's workspace count). 400 for anything not a package, or one holding memory/, state/ or outputs/. Idempotent |
148
158
  | `/api/workspaces/<ws>/evals` | GET, POST | list; create (`name`) |
149
159
  | `/api/workspaces/<ws>/evals/<eval>/run` | POST | run one eval |
150
160
  | `/api/workspaces/<ws>/tools/<tool>/test` | POST | exercise one tool (`args`, `path`). An http tool with an `operations:` allowlist is probed only with a GET to one of its operations (no `path`: the first GET without parameters), and fails if the allowlist resolves nothing, as a run does |
@@ -162,22 +172,22 @@ paying for the steps around it.
162
172
  | `/api/workspaces/<ws>/runs/<id>/stop` | POST | destroys the sandbox, not just the record |
163
173
  | `/api/workspaces/<ws>/runs/<id>/rerun` | POST | again — `step` to start partway, or `agent` to start at the first step that agent runs (one of the two is required). Takes `?wait=true&timeout=` like a flow start |
164
174
  | `/api/workspaces/<ws>/runs/bulk` | POST | one action on many runs at once. `action` is `stop` or `rerun`; the runs are named by any of `runIds`, `status`, `flow`, `since`, `until`, AND-ed together, and a call naming none of them is refused rather than treated as "everything". `from` picks the step a rerun starts at, `dryRun: true` answers with what would be touched and touches nothing. At most 100 runs per call — past that it answers `422` with the count, because a filter matching more than that is almost certainly the wrong filter. Each run is its own outcome: the reply carries `matched`, `succeeded`, `failed` and a `runs` array, so one run that finished a second ago does not abort the other thirty |
165
- | `/api/workspaces/<ws>/runs/<id>/approve` | POST | release a run parked on a human |
175
+ | `/api/workspaces/<ws>/runs/<id>/approve` | POST | release a run parked on a human: `decision` (`approve` or `reject`), `note`, `reason`, `step` (one of several waiting). `payload` (a string, or JSON sent as its text, up to 20000 characters) releases a step parked on `wait: event` with that payload, as its event URL would, recorded as released by the caller — 400 with `reject` or when no such step is waiting. `run:start`. Idempotent |
166
176
  | `/api/workspaces/<ws>/runs/<id>/answer` | POST | answer a question an agent asked mid-step (`tools: [ask]`): `{question: <id>, answer}`. The agent is waiting in a live sandbox and gets it within a second or two. First answer wins — a second is `409` with the answer that stands; also `409` when the agent stopped waiting (its ask timed out) or its step is no longer running. `404` when the question is not this run's or has expired |
167
177
  | `/api/workspaces/<ws>/runs/<id>/message` | POST | say something to a running agent: `{text, step?}` (at most 4 000 characters). It reaches the model after its next tool call, as context — it never changes the step's tools. `step` is the step's index in the run; left out, the only running step. With several running (a parallel group) and no `step`, `400` listing them in `running`, so a sibling never takes a message meant for another. `409` unless the run, and that step, are running. Replies `{ok, step, message}` |
168
178
  | `/api/workspaces/<ws>/runs/<id>/archive` | GET | one file the run archived from an agent's `outputs/` — `agent` and `path` name it; the run page's Out column links here, and plays a recording or shows a picture inline. Served as bytes, never as a page; a `Range` header gets `206` with that slice, which is how a video seeks |
169
179
  | `/api/workspaces/<ws>/runs/<id>/live` | GET | the live view: with no query, `{ agents: { <agent>: <last frame at> } }`; with `agent=`, the newest frame that agent's browser streamed as `image/jpeg` (or `204` when there is none), and `x-live-url`, `x-live-at`, `x-live-ended`, `x-live-engine` headers. Frames arrive from `web action=browse` through the step's egress lease and expire five minutes after the last |
170
180
  | `/api/workspaces/<ws>/runs/<id>/promote` | POST | save a completed run as a regression case in `evals/<target>-regressions.md` — `evalName` to pick the file, `caseName` to name the case, `expect` (assertion lines, e.g. `["contains: $34"]`) to replace the default judge. Returns `{ ok, file, created, caseName }` |
171
- | `/api/workspaces/<ws>/triggers?since=` | GET | why nothing happened: one row per flow saying how often its trigger fired, how often that became a run, and what the difference was — a duplicate delivery, a throttled burst, a debounced burst still being waited out, a quarantined flow, an `overlap: skip`, a fire missed while the platform was down. Plus every decision newest-first, and the hook/inbox delivery log, which answers the separate question of whether a request arrived and was authentic. `since` is days, default 7 |
181
+ | `/api/workspaces/<ws>/triggers?days=` | GET | why nothing happened: one row per flow saying how often its trigger fired, how often that became a run, and what the difference was — a duplicate delivery, a throttled burst, a debounced burst still being waited out, a quarantined flow, an `overlap: skip`, a fire missed while the platform was down. Plus every decision newest-first, and the hook/inbox delivery log, which answers the separate question of whether a request arrived and was authentic. `days` 1–365, default 7 (`since=`, its older name, still works — deprecated) |
172
182
  | `/api/workspaces/<ws>/notify/deliveries` | GET | the outbound webhook log: every `notify:` url delivery from this workspace, newest first — `{ deliveries, total, limit, offset, endpoint }`. A delivery is `{ id, event, status, destination, attempts, attemptLog, lastStatusCode, lastError, nextAttemptAt, createdAt, updatedAt, body }`: `id` is the `X-Foldrun-Delivery` header; `status` is `pending` (a retry is due at `nextAttemptAt`), `delivered`, `failed` (gave up after six attempts) or `skipped` (the endpoint was switched off); each attempt is `{ n, at, statusCode, durationMs, response (first 500 characters), error, manual? }`. `body` is what was sent, except that a gate's `approveUrl`/`rejectUrl` (top level and in `links`) read `[redacted]` — they decide without a login, and this log is readable by every role; the receiver still gets them, on a redelivery too. `?status=`, `?event=`, `?limit=` (50, at most 200), `?offset=`. `endpoint` is `{ failingSince, lastSuccessAt, lastFailureAt, disabledAt, disabledReason }` or null. Kept 30 days |
173
183
  | `/api/workspaces/<ws>/notify/deliveries/<id>/redeliver` | POST | send one delivery again now — the same id and body, signed afresh, to the URL `notify:` names today. `{ ok, attempt, delivery }`, always `200` when the delivery exists (whether the receiver took it is the answer); `404` when it does not. An accepted redelivery switches a disabled endpoint back on. Needs the permission that runs |
174
184
  | `/api/workspaces/<ws>/notify/test` | POST | sends one real notification to whatever this workspace's `notify:` resolves to, and answers `{ ok, destination, detail }` — including the provider's own words when it failed, which are what name the problem. Always `200`: the request was handled, and the result is the answer. Needs the permission that runs, because it sends real mail |
175
- | `/api/workspaces/<ws>/observe?since=` | GET | the observability report the Observe page renders — per-agent failures and retries, per-tool calls/errors/latency, per-flow duration, day series, recent failures. `since` in days, default 30 |
185
+ | `/api/workspaces/<ws>/observe?days=` | GET | the observability report the Observe page renders — per-agent failures and retries, per-tool calls/errors/latency, per-flow duration, day series, recent failures. `days` 1–365, default 30 (`since=`, its older name, still works — deprecated) |
176
186
  | `/api/workspaces/<ws>/history?path=&id=&limit=` | GET | every change to the workspace, newest first — `path` narrows to one file, `id` returns one revision with its before/after and a line diff, `limit` 1–500, default 100. A deploy's revision id is its commit |
177
187
  | `/git/<tenant>/<ws>.git` | git | the workspace as a git remote — `git clone`, `git push` (a push to main deploys and runs the evals; a push to any other branch deploys a **preview** workspace named `<ws>-preview-<branch>` from that branch's tree and runs its evals there — the scheduler never fires a preview's flows, a preview reads its source's secrets, and deleting the branch deletes the preview). A push while runs are in flight is accepted but **not** applied yet: git warns at push time, and the scheduler applies it when those runs finish. HTTP Basic: any username, an API key as the password |
178
188
  | `/git/<tenant>/_library.git` | git | the account library as a git remote — a push replaces the shelf |
179
189
  | `/api/workspaces/<ws>/repo?branch=` | GET, POST | GET: branches, tags, mirror settings; `?branch=` a branch's net change against main with diffs. POST `{action}`: `deploy` a `ref` (a rollback is a new commit on main), `merge` a `branch` into main and deploy, `delete-branch`, `mirror` (set `url`, https, credentials via the MIRROR_TOKEN secret), `mirror-now` |
180
- | `/api/workspaces/<ws>/restore` | POST | put the workspace's source back as it was at a point in its history. `to`: a commit or its prefix, a branch or tag, an ISO date/time (the last commit on main at or before it) or an age (`90m`, `12h`, `3d`, `2w`). `{ to, dryRun: true }` answers `{ to: { sha, at, by, message }, from, added, updated, removed, files: [{ path, change, diff }], blockedBy, noop }` and writes nothing. The restore needs `confirm` set to the workspace's name (400 without) and takes the dry run's `expectRemoved`; it deploys that commit's tree as a new commit on main (undo: restore to `from`), keeps runs, `state/`, storage, secrets and agent-written memory, and is refused 409 while runs are in flight or when it would remove something not in `expectRemoved`. `workspace:write`; audited as `workspace.restored` |
190
+ | `/api/workspaces/<ws>/restore` | POST | put the workspace's source back as it was at a point in its history. `to`: a commit or its prefix, a branch or tag, an ISO date/time (the last commit on main at or before it) or an age (`90m`, `12h`, `3d`, `2w`). `{ to, dryRun: true }` answers `{ to: { sha, at, by, message }, from, added, updated, removed, files: [{ path, change, diff }], blockedBy, noop, issues, warnings }` and writes nothing; `issues` are the deploy checks that would refuse that tree, `warnings` the ones it would deploy with. The restore needs `confirm` set to the workspace's name (400 without) and takes the dry run's `expectRemoved`; it deploys that commit's tree as a new commit on main (undo: restore to `from`), keeps runs, `state/`, storage, secrets and agent-written memory, and is refused 409 while runs are in flight or when it would remove something not in `expectRemoved`. `workspace:write`; audited as `workspace.restored` |
181
191
 
182
192
  `status` is one of `queued`, `running`, `awaiting-approval`, `completed`,
183
193
  `failed`.
@@ -279,7 +289,9 @@ its header and requires a URL it can GET for itself.
279
289
 
280
290
  ## Secrets
281
291
 
282
- `GET`, `PUT`, `DELETE /api/secrets`
292
+ | Route | Methods | |
293
+ |---|---|---|
294
+ | `/api/secrets?workspace=` | GET, PUT, DELETE | the vault: list which secrets exist (never a value); set one (`name` plus one shape, below); delete one (`name`). `workspace` names a workspace's own store; unsaid is the account's, which a key scoped to some workspaces may not touch. PUT is idempotent |
283
295
 
284
296
  `PUT` takes `name` plus exactly one shape: `value` (a string), `file` (a PEM,
285
297
  a cert), `oauth2`, `service_account`, `ssh`, or `api`. They go to the vault;
@@ -296,15 +308,17 @@ Reads return which secrets exist, never their values.
296
308
  | `/api/team` | GET, POST | members with their roles and workspace scope; mint an invite — `role` (viewer \| editor \| admin, default editor), `workspaces` (names, or omit for all), `email` (bound into the invite: only that address can accept it; sent through the account's `RESEND_API_KEY` unless `send: false`). Returns `path` (the invite's relative URL), `expires`, `sent` and any `sendError` either way; the full link is in the mail |
297
309
  | `/api/team/members/<id>` | PATCH, DELETE | change a member's role (`role`) and/or which workspaces they may open (`workspaces`: names, or null for all); remove them |
298
310
  | `/api/team/transfer` | POST | hand the account to a member (`to`) — owner only; you become an admin |
299
- | `/api/usage/history?days=` | GET | charged spend by flow, by agent and by workspace over time, read from the ledger rather than the run records — so it survives runs being pruned and reports what was billed rather than what was consumed. Buckets by week over three weeks, by day under. Each series carries its periods, its total, and the change between the last two COMPLETE periods, so a partial week never reads as a collapse. `unattributedUsd` is charges from before a line recorded its flow |
311
+ | `/api/usage/history?days=` | GET | charged spend by flow, by agent and by workspace over time, read from the ledger rather than the run records — so it survives runs being pruned and reports what was billed rather than what was consumed. Buckets by week over three weeks, by day under. Each series carries its periods, its total, and the change between the last two COMPLETE periods, so a partial week never reads as a collapse. `unattributedUsd` is charges from before a line recorded its flow. A caller scoped to some workspaces sees only the charges in those; `days` 1–365, default 56 |
312
+ | `/api/account/model` | GET, PUT, DELETE | the account's own model key — what every step runs on unless an agent, workspace or the account's AGENTS.md names a `provider:`. GET: `{ set, provider, baseUrl, format, keyHint, runsOn, platformKeyAllowed }` — never the key, only its last four characters; `runsOn` is `account` (its own key), `platform` (the platform's credential, for the operator's accounts) or `none` (runs are refused until a key is set). PUT `{ provider, apiKey, baseUrl?, format? }` (needs `secrets:write`): `provider` is a preset name (anthropic, openrouter, openai, groq, … — see providers) or `custom` with an https `baseUrl` and a `format` (anthropic or openai); a Claude login token (sk-ant-oat…) is refused for any account not allowed the platform's credential. Stored as the account secrets `FOLDRUN_MODEL_PROVIDER`, `FOLDRUN_MODEL_API_KEY`, `FOLDRUN_MODEL_BASE_URL`, `FOLDRUN_MODEL_FORMAT`. DELETE removes them. Honours Idempotency-Key on PUT; audited as model.key_set / model.key_removed |
300
313
  | `/api/account/providers` | GET, POST | GET: the model providers this account's files actually use — each one's preset name, where it is declared, the model a check would send — plus the last daily health check. POST forces one now, which spends one minimal request at each distinct endpoint, and needs the permission that runs |
301
- | `/api/usage` | GET | what has been spent, by meter |
314
+ | `/api/usage` | GET | what has been spent, by meter. A key or member scoped to some workspaces sees those workspaces, and totals over them; the ledger figures are the account's, as the wallet's are |
302
315
  | `/api/approvals` | GET | every gate waiting on a person, across workspaces — one entry per waiting **step** (`workspace`, `runId`, `flow`, `step`, `agent`, `question`, `asked`, `waitingSince`), oldest first. By step status, not run status: a decided gate leaves the list at once, before the worker resumes the run. `wait: event` steps are not listed; no button answers them. Beside it, `questions`: every question an agent is asking mid-step in a running run (`kind: "question"`, `id`, `workspace`, `runId`, `flow`, `agent`, `question`, `options?`, `askedAt`) — answered with `…/runs/<id>/answer` |
303
316
  | `/api/account/export` | GET | the owner's copy of what the platform holds about the account, as one JSON file (`foldrun-<account>-export.json`): `workspaces` (with their flows), `members` (email, role, scope, since), `runs` (an index — id, workspace, flow, status, times, headline) and the whole `ledger` (each line's charge; the platform's provider cost is left out). `billing:manage`, so the owner; audited as `account.exported`. The markdown itself comes from `foldrun pull` or the source API |
304
317
  | `/api/account/close` | POST | the owner asks to close the account — in person, never a key, with `confirm` set to the account's name and an optional `reason`. Not a delete: the account is suspended at once (runs in flight finish, sign-in and the export still work), `closeRequested` is set on its admin state, and the super admin is mailed to settle the last invoice and any refund. Returns `{ ok, suspended, notified }`; 409 when already asked. Hosted installs only |
305
318
  | `/api/account/backups` | GET | what is backed up for this account: `policy` (`schedule`, `keepLocal`, `keepOffsiteDays`, `encrypted`, `covers`, `excludes`), `last`, `backups` (each nightly archive that holds this account, newest first: `id`, `at`, `bytes` (the encrypted object), `sha256` (the archive inside the encryption — what a decrypt must reproduce), `kinds` — files, database, keys — `verified`, `offsite`, `keepDays`, `encrypted` (sealed with age before it left the server; false for archives taken before October 2026), `recipient` (the key's fingerprint, `sha256:<16 hex>`, or null)), `requests` (earlier restore requests) and `workspaces` (`name`, `head`, `history`, `lastChange`) — what a restore can reach. See [Backups and restore](backups-and-restore.md) |
306
319
  | `/api/account/backups/requests` | GET, POST | ask the platform team to restore what an account cannot restore itself. POST `{ what, target, workspace?, backupId?, note? }` (`what` is runs, state, storage or everything) records the request, mails the operator and audits it as `backup.restore_requested`; answers 201 `{ ok, request }` with `notified` saying whether the mail went. Restores nothing. `workspace:manage`, and a member scoped to some workspaces must name one. GET lists this account's requests |
307
320
  | `/api/account/api` | GET, PATCH | how this account meets the API — GET `{ version: { current, pinned, effective, supported, supportMonths }, limits: { plan, windowSeconds, perCredential: { read, write }, perAccount: { read, write } } }`; PATCH `{ version: "YYYY-MM-DD" \| null }` pins the version requests without a `Foldrun-Version` header get (null: always current). `keys:manage`. See [api-usage](api-usage.md) |
321
+ | `/api/account/flags` | GET | the feature flags as they stand for this account — `{ flags: [{ name, description, owner, default, on, source, rollout }] }`, `source` being what decided it: `account` (an override for this account), `global` (the install-wide setting), `rollout` (a percentage of accounts, by a stable hash of account and flag; `rollout` is the percentage) or `default`. Read-only, every role; only the super admin changes a flag. See [Feature flags](feature-flags.md) |
308
322
  | `/api/openapi.json` | GET | this API as an OpenAPI 3.1 document — every route and method, bearer auth, the rate-limit, version and Idempotency-Key headers, and request/response schemas for runs, flow runs, deploy, source, secrets, keys and billing. No key needed. Generated from the route files by `scripts/gen-openapi.mjs`; the dashboard renders it at Docs → API reference, `foldrun api spec` saves it |
309
323
  | `/api/account` | GET, PATCH | the account's defaults from its AGENTS.md frontmatter — `timezone`, `budget` (a USD cap — a number for a month, `"60/day"`, `"60/week"`, or `{ usd, period }`; null is no limit), `notify` (`{ url?, email?, events }`); PATCH sets the keys sent, null clears one, and edits the file in place |
310
324
  | `/api/audit?since&until&action&actor&workspace&limit&cursor&format` | GET | the account's audit log, newest first — the caller's account only, whatever a header, query or body names. The owner and unscoped admins (people, or `admin` keys); `audit:read`, and refused to a member or key scoped to some workspaces, because the log is the whole account's. `since`/`until`: `7d`, `24h`, `30m` or a date; `action`: exact (`apikey.created`) or a family (`apikey`); `actor`: exact (an email, `key:<label>`, `support:<email>`); `workspace`: entries about one; `limit` 1–1000 (100; 1000 for CSV); `cursor`: the `next` of the page before. JSON `{ entries: [{ id, at, actor, action, subject, detail }], next, retention }`; `format=csv` downloads the same rows (`x-next-cursor` header when there are more). Records sign-ins and refusals, sign-outs, session revokes, password and email changes, MFA on/off, key mint/rotate/revoke, invites, joins, role/scope changes, removals, ownership transfers, secrets set/deleted (by name — never a value), deploys, workspace deletes, plan and billing changes, export and close requests, and `support.viewed`/`support.ended`. The super admin's own levers (`operator.*`) are the platform's record and not listed. Kept for the life of the account |
@@ -347,7 +361,7 @@ person: a key names an account, never a who.
347
361
  | `/api/billing/card` | POST | save a card |
348
362
  | `/api/billing/details` | GET, PUT | who the invoices are made out to — GET `{ details, stripe, customer, tax }` for anyone who can see billing; PUT (the owner) `legalName` (required), `email`, `abn` (checked against the ABN rule), `address` `{ line1, line2, city, state, postcode, country }` (two-letter country) or `null`. Every problem comes back at once as `errors` with a `400`. Kept first, then sent to the Stripe customer (name, address, the ABN as its `au_abn` tax id): answers `synced`, and `syncError` with Stripe's words when Stripe refused — the details stand either way |
349
363
  | `/api/billing/portal` | POST | a Stripe Billing Portal session for the account's customer (the owner): answers `url`. Optional `returnTo`, a `/dashboard/…` path to come back to. `501` without Stripe |
350
- | `/api/billing/statement?month=&format=` | GET | one month of the ledger as a statement. JSON by default; `format=csv` downloads it with the month, the account and the spent and added totals in its header. `month` is `YYYY-MM`, default the current month. What the customer sees is the charge; the platform's provider cost is not on it |
364
+ | `/api/billing/statement?month=&format=` | GET | one month of the ledger as a statement. JSON by default; `format=csv` downloads it with the month, the account and the spent and added totals in its header. `month` is `YYYY-MM`, default the current month. What the customer sees is the charge; the platform's provider cost is not on it. The whole account's money, so refused (403) to a member or key scoped to some workspaces, as the audit log is |
351
365
  | `/api/billing/wallet` | GET, PUT | the balance summary (burn, runway, daily spend, month to date, the suggested top-up), the refill rule and the low-balance email — GET `{ enabled, stripe, summary, cardSaved, autoTopUp, email, dunning }`, PUT `autoTopUp`, `email`. `dunning.status` is `none`, `grace` (a plan invoice failed; runs go on until `graceUntil`) or `suspended` (new runs paused until it is paid), with `payUrl`, `amountDue` (minor units) and `currency` |
352
366
  | `/api/billing/plans` | GET | the catalogue (four plans: fee, credits, per-credit price, runs at once, workspaces, lane), where this account stands (plan, status, this cycle's credits and what is left, when it resets), the balance in dollars and credits, and any overdraft the platform has allowed |
353
367
  | `/api/billing/subscribe` | POST, DELETE | POST `plan` — starter, creator, pro or scale. With no subscription yet, answers `url`: a Stripe Checkout in subscription mode; the return trip starts the first cycle. With a live one, changes it at Stripe at once, prorated (an upgrade is invoiced today and the bigger bundle lands with it; a downgrade comes back as credit on the next invoice), and answers `changed: true`. POST `resume: true` takes a pending cancellation off — nothing is charged and no cycle moves. DELETE ends the plan with the current cycle: the credits stand until then, and credits bought on top are kept |
@@ -431,5 +445,6 @@ signed-in person, never by a key — mints an ordinary API key at their role.
431
445
  | `/api/inbox/<tenant>/<ws>/<flow>` | POST | the token as for a hook (`Authorization: Bearer`, `X-Foldrun-Token`, or `?token=`). An inbound email for a `trigger: email` flow — JSON (`from`, `to`, `subject`, `text` or `html`; Resend's `data` wrapper understood) or an inbound-parse form (Mailgun, SendGrid). Same token as the hook |
432
446
  | `/api/events/<tenant>/<ws>/<id>?token=` | POST | release a run parked on `wait: event`; the body becomes the step's event payload. The token is derived from the run id, and the URL is printed in the waiting step's trace. `409` when the run is not waiting on an event |
433
447
  | `/api/approve/<tenant>/<ws>/<id>?token=` | GET, POST | decide a run parked on a human, from the link in its notification. GET shows what is waiting and two buttons — it never decides, because inbox link-checkers follow GETs. POST `decision` (`approve` or `reject`) and an optional `note`, form-encoded or JSON; answers JSON when `Accept` prefers it. The token is derived from the run id under the install key, not stored, so a key rotation kills every link. Needs `FOLDRUN_PUBLIC_URL` to be minted at all |
448
+ | `/api/workspaces/<ws>/hooks/<flow>` | GET | read the flow's webhook URL without changing it: `{ ok, flow, path, url }` — `path` carries the token, `url` is it made absolute with `FOLDRUN_PUBLIC_URL` (null when that is unset). `workspace:manage`, as rotate and the settings page that shows it, since the URL is a credential. `404` for no such flow, `409` for a flow that is not `trigger: webhook` |
434
449
  | `/api/workspaces/<ws>/hooks/<flow>/rotate` | POST | new token, old URLs stop working |
435
450
  | `/api/git/<tenant>/<ws>?branch=&dir=` | POST | GitHub push webhook — HMAC-signed, fetches the tarball at that commit and deploys it |
@@ -98,6 +98,10 @@ per-seat fee would tax the approver the safety story depends on.
98
98
  **Bring your own model key** and tokens cost no credits at all — the model
99
99
  is billed to your own provider account — so a plan then buys sandbox time
100
100
  and the platform, which is most of what a BYOK customer's credits go on.
101
+ A step counts as BYOK when it ran on a key of the account's own (Settings →
102
+ Model, or a `provider:` block); the run page says which credential each step
103
+ used. On a hosted platform every customer account brings one
104
+ ([providers](providers.md#your-accounts-own-model-key)).
101
105
 
102
106
  **Going into minus.** Ordinarily an empty balance starts no run. The
103
107
  platform can allow an account to run below zero — to a floor, or with none —
package/docs/cli.md CHANGED
@@ -33,8 +33,10 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
33
33
  | | |
34
34
  |---|---|
35
35
  | `foldrun init [dir]` | create an account folder with one workspace in it — `--workspace <name>` names it, `--flat` makes the older single-folder shape |
36
- | `foldrun new <name>` | another workspace in this account |
36
+ | `foldrun new <name>` | another workspace in this account — blank: an `AGENTS.md` and empty `agents/` and `flows/`. `--starter` adds the example researcher, writer and `publish` flow; `--from <template>` copies a template |
37
37
  | `foldrun agent new <name>` | one more agent inside a workspace, with the frontmatter the format requires — and every key explained beside it. Also `foldrun flow new <name>`, whose first step names an agent this workspace already has, and `foldrun tool new <name>`, which writes a `transport: script` folder with its program in a file beside the definition (`--transport script\|http\|mcp`, `--language javascript\|python\|bash`). In an account with more than one workspace, `--to <name>` says which; inside one, it is already known. The templates are the same ones the dashboard's New button writes, so what is scaffolded is what `foldrun check` accepts |
38
+ | `foldrun agent new <name> --platform` | the dashboard's **New agent** on the DEPLOYED workspace (`POST …/agents { name, description, model }`): the platform writes its own template as a revision, and nothing is written in this folder. `--to <workspace>` (or the folder you stand in), `--description "…"`, `--model fast\|default\|max`. Without `--platform`, `--to` keeps naming a workspace of this account folder. Also `flow new <name> --platform` (`POST …/flows { name, pattern }`, `--pattern pipeline\|review-loop\|fan-out\|debate\|router`, default `pipeline`) and `tool new <name> --platform` (`POST …/assets { kind: "tools" }` — a `script` tool as here unless `--transport`/`--template` says otherwise; `--template` also takes a gallery entry, `--language`). `foldrun pull` brings them down |
39
+ | `foldrun agent ls` | the agents deployed in a workspace — name, model, description (`GET …/agents`); `flow ls` the flows and how each fires (`GET …/flows`). `--to <workspace>`, `--json` for the list as the platform returns it |
38
40
  | `foldrun guide` | the coding-agent rules, copied from what Next.js does: a short managed block in the account's `AGENTS.md`, between `<!-- BEGIN:foldrun-agent-rules -->` markers, telling a coding agent (Claude Code, Cursor, Codex) to read `foldrun docs` before it writes an agent, flow or tool, and a `CLAUDE.md` that imports it with `@AGENTS.md`. `foldrun init` writes them, `foldrun pull` refreshes them, and `foldrun check` re-adds them when a coding agent runs it and they are missing or old — as `next dev` does. Only the block is rewritten, in place; text around it is kept. The runtime strips the block, so the agents that run in the account never see it; the rest of `AGENTS.md` stays their shared context. `--check` exits 1 when the block is missing or old (for CI), `--print` writes it to stdout |
39
41
  | `foldrun docs [page]` | these docs, from the copy that ships inside the CLI — the way Next.js ships `node_modules/next/dist/docs` — so a coding agent reads the version it is driving, offline. No page lists them; `foldrun docs flows` prints one; `--path` prints the folder. Design records and the platform operator's routes are left out |
40
42
  | `foldrun check [dir]` | validate agents, flows, tools, evals, knowledge — offline. At an account root: every workspace, then the shared library. Includes each agent's colleagues: an `agents:` or `subagents:` name that is not an agent, a sub-agent with no `description:`, a sub-agent that would hold none of its tools inside its parent's. Signed in, it also asks the platform's open `/api/version` which Chrome its runner image has, and warns when a pinned `web.browse.user_agent:` claims another major ([why](tools#how-this-agents-browser-presents-itself)); it warns about the old `web.browse.session:` key too (now `vendor_session:`) |
@@ -42,12 +44,15 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
42
44
  | `foldrun check --to <ws>` | validate the copy that is DEPLOYED, rather than the folder in front of you — a workspace edited through the API, by an agent or by `foldrun source put`, has had no way to be checked short of spending a run. The files are fetched into a temporary folder and the ordinary check runs over them, so there is one copy of the rules; the output names the workspace and the platform it checked |
43
45
  | `foldrun agent run <name>` | run one agent once on a platform, with no flow file: a flow of one step. `--task "…"` is the instruction and is required. Prints the run id and how to follow it; `--wait` holds on (in 25-second pieces, as `invoke` does) and then prints the same report `foldrun report` would; `--test` makes it a [test run](runs#test-runs) |
44
46
  | `foldrun agent link <agent>` | add to an agent's team, in its own frontmatter — `--subagent <worker>` (`subagents:`, a worker it delegates to), `--consult <agent>` (`agents:`, a colleague it asks) or `--can-ask` (`ask` in `tools:`, so it may ask you mid-step). The edit is core's, the one the canvas's docks make: only the lines of that one list change, flow style stays flow style, comments and the prose stay byte for byte. A sub-agent without a `description:` needs one, because the model picks a sub-agent by it — asked for at a terminal, `--description "…"` otherwise, and written into the worker's file first. Says which flows use the agent ("used by 3 flows"), since the change reaches all of them. Prints the diff, and refuses an edit `foldrun check` would call an error; `--dry-run` writes nothing. In this folder by default (`--workspace <name>` in an account with several); `--to <workspace>` edits the deployed copy through `PATCH /api/workspaces/<ws>/agents/<name>`. In this folder it re-reads each file just before writing — after the description prompt, after `check` — and refuses, writing nothing to either file, when one changed since it was read |
47
+ | `foldrun agent import <workspace>/<agent>` | copy an agent you already have in another workspace into this one: its `agent.md` and own `skills/`, `scripts/`, `knowledge/` — never `memory/`, `outputs/` or `state/`. Into the workspace you stand in, or `--workspace <name>`; `--as <name>` imports it under another name (and rewrites `name:`), which a name clash requires. `--to <workspace>` does it on the platform (`POST /api/workspaces/<ws>/agents/import`) and lists the tools, skills, scripts and secrets the copy uses that the workspace lacks. See [Importing an agent](workspaces#importing-an-agent-you-already-have) |
48
+ | `foldrun agent import --list` | what could be imported into a deployed workspace: every other workspace this key may read, and each one's agents with their descriptions (`GET …/agents/import`). Imports nothing. `--to <workspace>`, `--json` |
45
49
  | `foldrun agent unlink <agent>` | take one away: `--subagent <name>`, `--consult <name>` or `--can-ask`. An emptied list is written `key: []`, not deleted. In this folder, refused the same way, nothing written, when the file changed since it was read |
46
50
  | `foldrun flow add <flow> <pattern>` | one palette block from the [flow canvas](flows#editing-on-the-canvas), as the same markdown edit the canvas writes (core's `flow-patterns`): `chain` (alias `step`: `--agent <name>`, `--instruction "…"`, `--after <n>` or `--before <n>`, default last), `parallel` (`--agent`, `--group <n>`), `router` (`--agent <triage>`, `--cases BUG=bugs,DOCS=docs`, `--else <agent>`, `--after`), `fan-out` (`--step`, `--each lines\|items\|"rows of <path>"`, `--max <n>`), `loop` (alias `evaluator`: `--step`, `--loop <n>` default 3, `--until <MARKER>` default APPROVED, `--judge "<claim>"` adds `verify: judge:`), `approval` (`--step`; the `!` marker, `approve: true` on a `?` step), `ask` (`--step`, `--question "…"`), `wait` (`--step`, `--wait 30m\|event`), `rescue` (`--step`, `--on-fail <agent>`), `subflow` (`--flow <other>`, `--after`). `--step` is the step's place in the file (1 = the first step line) or the agent it runs; `--off` takes a step pattern back off. Prints the diff, runs the rules `check` runs over the result and refuses — writing nothing — an edit that would add an error; one that was there before does not block it. `--dry-run` shows the diff and writes nothing. In this folder by default; `--to <workspace>` edits the deployed copy through the canvas's own route (`PATCH …/flows/<flow>` with `{ edit }`), and the sha256 of the file it read as `expect`: one edited meanwhile is refused (`409`), nothing written. In this folder it re-reads the file just before writing — after the y/N, after `check` — and refuses the same way, writing nothing, when it changed since it was read |
47
51
  | `foldrun flow rm-step <flow> --step <n\|agent>` | delete one step, as the canvas's × does (core's `removeStep`): the step line and its indented options go, the groups after an emptied one renumber, prose and the agent's file are untouched. Says what else changes first (`case:`/`when:` steps that will route on the step before, the renumbering), prints the diff, refuses one `foldrun check` would call a new error, then asks — no terminal needs `--yes`. `--step` is the step's place in the file (1 = the first step line) or the agent it runs. `--dry-run` writes nothing; `--to <workspace>` sends the canvas's `PATCH …/flows/<flow>` with `{ edit: { op: "remove", step } }`, and the sha256 of the file it read as `expect`: one edited meanwhile is refused (`409`), nothing written. In this folder it re-reads the file just before writing — after the y/N, after `check` — and refuses the same way, writing nothing, when it changed since it was read |
48
52
  | `foldrun flow dup-step <flow> --step <n\|agent>` | duplicate one step, as the canvas's Duplicate (Cmd/Ctrl+D) does (core's `duplicateStep`): a copy of the step line — its `?`/`!` marker and every indented option — goes directly under it with the same group number, so the copy runs in parallel with the original; nothing else moves. Prints the diff and refuses one `foldrun check` would call a new error; additive, so it does not ask. `--dry-run` writes nothing; `--to <workspace>` sends `PATCH …/flows/<flow>` with `{ edit: { op: "duplicate", step } }`, and the sha256 of the file it read as `expect`: one edited meanwhile is refused (`409`), nothing written. In this folder it re-reads the file just before writing — after the y/N, after `check` — and refuses the same way, writing nothing, when it changed since it was read |
49
53
  | `foldrun flow show <flow>` | the flow as the canvas draws it, in a terminal: the trigger, then one block per group — parallel steps listed under their group number — each step with its chips (`approve`, `optional`, `when:`, `case:`/`else`, `each:` ×max, `loop … until`, `verify:`, `retry`, `wait`, `ask:`, `on-fail →`, `model`), and under it its agent's team: `↳ sub-agent`, `· consults`, `may ask you`. What `foldrun check` says about a step or its agent is printed on it, red for an error; exits 1 when there is one. Plain text, no colour, when the output is not a terminal. `--to <workspace>` shows the deployed copy |
50
54
  | `foldrun flow run <flow>` | start a flow on the platform — the same command as `invoke`, spelled the way `agent run` is. `foldrun run` runs HERE; `flow run`/`invoke` run a flow on the platform; `agent run` runs one agent there |
55
+ | `foldrun flow hook <flow>` | the flow's webhook URL as it is now (`GET …/hooks/<flow>`), changing nothing — `--json` prints `{ flow, url, path }`. Needs `workspace:manage`, like the settings page that shows it; the URL is a credential |
51
56
  | `foldrun flow rotate-hook <flow>` | a new webhook URL for the flow (`POST …/hooks/<flow>/rotate`). The old URL stops working the moment it returns, so it asks first — `--yes` with no terminal. Prints the new URL, which is itself a credential |
52
57
  | `foldrun flow set <flow> --step <n\|agent> key=value …` | the step editor on the Flows page, from a terminal (core's `updateFlowStep`, and `updateFlowStepInstruction` for `instruction=`): `model`, `effort`, `retry`, `timeout`, `verify`, `when`, `case`, `else`, `loop`, `until`, `each`, `max`, `limits` — only the keys named are touched, `key=` clears one. `each=` takes `lines` only; the other fan-outs are `flow add … fan-out`. A value the rewriter would drop or clamp is refused, nothing written: `effort` one of low, medium, high, xhigh, max (or an alias core reads, e.g. `deep`); `timeout` a whole number of seconds (`timeout=900`, not `15m`); `retry` 0–5, `loop` 1–5, `max` 1–20, whole numbers. Shows the diff, refuses an edit `check` would call a new error, `--dry-run` writes nothing; `--to <workspace>` sends the editor's `PATCH …/flows/<flow> { step, options }` / `{ step, instruction }` with `expect` |
53
58
  | `foldrun flow trigger <flow> manual\|webhook\|schedule` | the trigger picker on a flow card (core's `setFlowTrigger`): only the `trigger:`, `schedule:` and `timezone:` lines change. `--schedule "0 9 * * 1"` (5 fields, or `@daily`) and `--timezone <zone>` go with `schedule`. Diff, checked, `--dry-run`; `--to` sends `PATCH { trigger }` |
@@ -58,6 +63,7 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
58
63
  | `foldrun tool test <name>` | exercise one tool, alone — no model, no run, no flow. The real thing runs: the HTTP request against the declared `base:`, the script with the workspace's secrets in its environment, the MCP handshake. Prints the transport, whether it exited cleanly, how long it took, everything the tool printed, and any secret it needed by NAME and never by value. Its arguments are `key=value` pairs, one per argument the tool declares, and `--path` is an http tool's probe path. It waits five minutes by default, because a tool that crawls takes as long as it takes — `--timeout <seconds>` waits longer, and a test given up on says so rather than claiming the tool failed |
59
64
  | `foldrun run <target>` | run an agent or a flow HERE, on this machine (`name`, or `flow:name`) — `--test` for a [test run](runs#test-runs). On a platform: `flow run`/`invoke` for a flow, `agent run` for one agent |
60
65
  | `foldrun eval [name]` | one eval, or all of them. `--to <workspace>` runs the DEPLOYED workspace's evals on the platform (`POST …/evals/<name>/run`), waiting for each verdict, and prints the same lines; exits 1 when a case fails |
66
+ | `foldrun eval new <name>` | the Evals page's New, on the deployed workspace (`POST …/evals { name }`): an eval whose `agent:` is the workspace's first agent and whose case is a placeholder — edit it (`source cat`/`put`), then `foldrun eval <name> --to <workspace>` runs it. `--to <workspace>` |
61
67
  | `foldrun promote <run-id>` | keep a finished run as a regression case: its task and what the next run must still say go into `evals/<target>-regressions.md` (or `--eval <name>`), appended when the file exists. `--expect "contains: $34"` — repeatable, eval syntax — replaces the default judge line that holds the next run to the recorded conclusion; `--case <name>` names the case |
62
68
  | `foldrun probe <model>` | can this model hold a tool loop here? A live check |
63
69
  | `foldrun logs [run-id]` | recent runs in one workspace, or one run's whole event trail. `--local` reads this machine's store even when signed in; `foldrun runs --local` is the same command and prints the same thing |
@@ -67,9 +73,9 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
67
73
  | `foldrun report <run-id> get <agent>/<path>` | download one file the run archived — whatever that agent left in `outputs/`, copied under the run when it ended. Writes it beside you under its own name; `--file <path>` names somewhere else (`-` for stdout), `--force` overwrites |
68
74
  | `foldrun report <run-id> live` | the run page's live browser view: which agents' browsers have posted a frame, and when; `--agent <name>` saves the newest frame as a JPEG (`--file <path>`, `-` for stdout) and prints the page's address, the engine and whether the browser has closed (`GET …/runs/<id>/live`) |
69
75
  | `foldrun approvals` | every gate waiting on a person, anywhere in the account: which workspace, flow and run, which step and agent, the question, how long it has been waiting and which files under `storage/` the gate previews — and, first, every question an agent is asking mid-step, with its choices. `--to <workspace>` narrows it |
70
- | `foldrun approve <run-id>` | release a waiting gate. Approving is outward — the step behind it publishes, sends or posts — so it prints what it is about to release and asks you to type the run id back, unless `--yes`. `--note "…"` is read by the agent as guidance, `--step <n>` decides one gate of several |
76
+ | `foldrun approve <run-id>` | release a waiting gate. Approving is outward — the step behind it publishes, sends or posts — so it prints what it is about to release and asks you to type the run id back, unless `--yes`. `--note "…"` is read by the agent as guidance, `--step <n>` decides one gate of several. `--payload <json\|text\|@file>` releases a step parked on `wait: event` with that body instead, as its event URL would — the trace names you, not "an external event" |
71
77
  | `foldrun reject <run-id>` | refuse one, failing the step and the run with it. `--note` is the reason, and it goes on the trace |
72
- | `foldrun stop <run-id>` | kill a run in flight: the queued job is dropped and the sandbox the current step is spending in is destroyed. It prints which step is running and what has been spent, then asks you to type the run id back unless `--yes`. Finished steps keep their results, and costs already incurred stay on the bill. A run that has already finished is said so, not stopped. With no run id and a filter — `--status running,queued`, `--since 2h`, `--flow <name>` — it stops every run that matches (`POST …/runs/bulk`), in `--to <workspace>` or across the account: it lists what matches first, asks, and then stops exactly the runs it listed, so one that starts in between is not swept up. `--dry-run` lists and touches nothing. The route refuses more than 100 at once |
78
+ | `foldrun stop <run-id>` | kill a run in flight: the queued job is dropped and the sandbox the current step is spending in is destroyed. It prints which step is running and what has been spent, then asks you to type the run id back unless `--yes`. Finished steps keep their results, and costs already incurred stay on the bill. A run that has already finished is said so, not stopped. With no run id and a filter — `--status running,queued`, `--since 2h`, `--until 1h` (started before then: a span ago, or a date), `--flow <name>` — it stops every run that matches (`POST …/runs/bulk`), in `--to <workspace>` or across the account: it lists what matches first, asks, and then stops exactly the runs it listed, so one that starts in between is not swept up. `--dry-run` lists and touches nothing. The route refuses more than 100 at once |
73
79
  | `foldrun answer <run-id> "…"` | answer the question an agent is asking mid-step (`tools: [ask]`). `--option <n>` picks one of the choices it offered; `--question <id>` one of several. The agent is waiting in its sandbox and carries on at once. `foldrun approvals` lists what is being asked |
74
80
  | `foldrun message <run-id> "…"` | say something to a running agent. It reaches the model after its next tool call, as context — it changes none of the step's tools. `--step <n>` picks one when a parallel group has several running. Platform only |
75
81
  | `foldrun secrets set NAME` | store a secret — also `ls`, `rm`, and `status` (OAuth grant health) |
@@ -80,19 +86,25 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
80
86
  | `foldrun deploy [dir]` | push this account into an installation — every workspace and the shared library. `foldrun deploy <workspace>` pushes one. Never removes a workspace the platform has and the folder does not. It first prints where it is going and as whom (`deploying to <url> as <profile> (<email>)`); a deploy that would delete files the folder no longer has lists them and asks — what runs wrote (`storage/`, `state/`, the trigger log) is never deleted, so never listed. The confirmed list goes with the deploy; if the platform would now remove anything else it refuses and the CLI asks again. With no terminal (a script, a CI job, a coding agent) it refuses unless `--yes`. A deploy that only adds or changes files asks nothing |
81
87
  | `foldrun help <command>` | that command's usage and options. `foldrun <command> --help` and `-h` print the same and never run the command |
82
88
  | `foldrun pull [workspace]` | bring the platform's account down into this folder — refuses to overwrite a locally edited file, and names every one it would have taken (`--force` takes them) |
89
+ | `foldrun export [workspace]` | a deployed workspace as a `.zip` — agents, flows, tools, skills, scripts, evals, knowledge; never memory, state, storage or a secret's value. `--flow <name>` exports that flow with every flow and agent it runs and the tools, skills and scripts they grant; `--agent <name>` that agent and what it grants. Written under the name the platform gives it (`<ws>.zip`, `<ws>-flow-<name>.zip`); `--file <path>` (`-` for stdout), `--force` to replace (`GET …/export`) |
90
+ | `foldrun import <file.zip>` | take a package in: first shows what it adds, what is identical, what it would replace and what it still needs (secrets, tools, skills, scripts, agents, flows), then asks. Never deletes; replacing a file needs `--overwrite`. `--to <workspace>` (a name that does not exist makes it, from a workspace package), `--dry-run` previews only (`--json` for the plan), `--yes` with no terminal (`POST …/import`) |
83
91
  | `foldrun status [workspace]` | per workspace: what is added, changed and only-on-the-platform, plus which files moved there since your last deploy from here. Reads only |
84
92
  | `foldrun status --platform` | is the platform up: overall, each component (dashboard and API, run workers, scheduler, model providers, email, storage) and any incident or maintenance the operator posted, with its latest update. Reads the open `/api/status` — no key needed; the signed-in platform, `--url`, or app.foldrun.io. `--json` prints the answer as is. Exits 1, saying *status unavailable — the platform may be down*, when it does not answer. Plain `foldrun status` opens with the same one-line verdict |
85
- | `foldrun workspaces` | what exists here, what exists on the platform, which are both — also `new <name>`, and `rm <name>` (locally; `--platform --yes` deletes it there) |
93
+ | `foldrun status --platform --history` | what the status page draws as bars: each component's uptime over the window and a mark per day — up, degraded or maintenance, an outage, or no samples — then the incidents and maintenance in it, from the open `GET /api/status/history?days=<n>`. `--days 1-90` (default 90), `--json` |
94
+ | `foldrun workspaces` | what exists here, what exists on the platform, which are both — also `new <name>`, and `rm <name>` (locally; `--platform --yes` deletes it there). `--json`: `{ platform, workspaces: [{ name, here, platform }], error? }`, exit 1 when the platform could not be read |
95
+ | `foldrun workspaces show <name>` | what is deployed in one workspace (`GET /api/workspaces/<name>`): its agents with model and description, its flows and how each fires, and its files counted by top-level folder. `--json` for the document |
96
+ | `foldrun workspaces new <name> --platform` | the dashboard's **Create workspace**: blank — an `AGENTS.md` and nothing to delete (`POST /api/workspaces { name, template: true }`) — or `--starter` for the example researcher, writer and publish flow (`{ name, starter: true }`). The plan's workspace count applies. Without `--platform` it makes the folder here, as `foldrun new` does |
86
97
  | `foldrun workspaces demo` | **Try the demo pipeline**: makes the `demo-pipeline` workspace on the platform — a four-step flow with a parallel pair, an approval gate and a pretend CRM tool (`POST /api/workspaces { demo: true }`) |
87
- | `foldrun library` | the account's shared library on the platform, as the Library page shows it: `ls [skills\|tools\|scripts\|knowledge]`, `cat <kind>/<path>`, `put <kind>/<path>` (from `--file`, else stdin), `rm <kind>/<path>` (asks). `deploy` still pushes the whole of it |
98
+ | `foldrun library` | the account's shared library on the platform, as the Library page shows it: `ls [skills\|tools\|scripts\|knowledge\|memory]`, `cat <kind>/<path>`, `put <kind>/<path>` (from `--file`, else stdin), `new <kind>/<name>` (the Library page's New, `POST /api/library/<kind> { name, template, language }`: the platform's template — `--template` a transport or a gallery entry, `--language` for a script tool), `rm <kind>/<path>` (asks). `deploy` still pushes the whole of it |
88
99
  | `foldrun find <words>` | the dashboard's ⌘K search: workspaces, agents, flows, tools, skills, knowledge, memory and runs across the account, ranked the same way (`GET /api/search`; `--json`) |
89
100
  | `foldrun invoke <flow>` | start a flow on a running platform — `--watch` follows its trace here; `--test` makes it a [test run](runs#test-runs) `--once <key>` sends the key as the run's idempotency key, so a CI step that retries after a dropped response is answered with the run it already started instead of paying for a second. `--tag <t>`, repeatable, labels the run (the API's `tags`). `--inputs <name>` sends a saved input set as the task — a case of `evals/<flow>-inputs.md` or of an eval that runs the flow, as the platform has it (see [Run with saved inputs](flows#run-with-saved-inputs)). `foldrun flow run <flow>` is the same command |
90
- | `foldrun source <verb>` | one workspace file on a platform: `ls [dir]`, `cat <path>`, `put <path>` (from `--file`, else stdin; `--message` goes on the revision), `mv <from> <to>`, `rm <path>` — the editor's own door, so every write is a revision. For a whole tree, `deploy` |
101
+ | `foldrun source <verb>` | one workspace file on a platform: `ls [dir]`, `cat <path>`, `put <path>` (from `--file`, else stdin; `--message` goes on the revision), `new <kind> <name>`, `mv <from> <to>`, `rm <path>` — the editor's own door, so every write is a revision. `new` is the dashboard's New button for any document (`POST …/assets { kind, name, agent?, template?, language? }`): `tools`, `knowledge`, `memory`, `skills`, `scripts` (and `agents`, `flows`, `evals`), the template written by the platform; `--agent <name>` puts a knowledge, memory, skill or script document inside that agent. For a whole tree, `deploy` |
91
102
  | `foldrun open [page]` | the dashboard for this workspace, in the browser (`runs`, `agents`, `graph`, `repo`…) |
92
103
  | `foldrun login` | sign this machine in from the browser — no key to copy |
93
104
  | `foldrun login <site> --url <address>` | a browser window to sign in to a site by hand; the session is stored for an agent |
94
105
  | `foldrun logout` | forget this machine's key, and revoke it if `login` made it |
95
106
  | `foldrun whoami` | who you are on the platform: account, role, which workspaces |
107
+ | `foldrun team` | who is in the account (`GET /api/team`): each member's email, role (the owner marked), the workspaces they may open — a scoped key sees only the ones it shares — and when they joined. `--json`. Read-only on purpose: inviting, changing a role, removing someone and transferring the account are a signed-in person's, in Settings → Team, and the platform refuses them from a key |
96
108
  | `foldrun onboarding` | the account's getting-started steps — workspace, agent, a run, a run that completes, a secret, a schedule or webhook, a teammate, billing where the plan asks for it, the CLI — each worked out from what is really there, with the next one first and where to do it (`--json`). The dashboard card is hidden per person from the dashboard |
97
109
  | `foldrun preferences` | your own settings on the platform — today the theme (`system`, `light`, `dark`), kept with you on every browser; `preferences set theme dark` changes it (`/api/me/preferences`; a key acts for whoever minted it) |
98
110
  | `foldrun api spec` | the platform's OpenAPI 3.1 document, from `GET /api/openapi.json` (no key needed) — to stdout, or `--out <file>`. Every other command sends `Foldrun-Version` (the API version this CLI was built for), an `Idempotency-Key` on each write (reused on its own retry), and waits out one `429` for `Retry-After` before saying so. See [Using the API](api-usage) |
@@ -104,15 +116,16 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
104
116
  | `foldrun runtimes` | the environments a deployed workspace's agents need — each distinct `runtime:` (an agent's own merged with its tools'), the packages in it, which agents use it, and whether the platform has built it: ready, building, or failed with the installer's own error. `foldrun deploy` prints the same thing after a deploy and waits up to two minutes for builds in progress (`--no-runtimes` skips the wait); this asks again later, and `--wait` holds on while any is building. Exits 1 when one has failed. `--to <workspace>` from anywhere |
105
117
  | `foldrun schedule` | every flow in the account whose `trigger:` is a clock: workspace, flow, the cron line, its timezone, how many steps, whether the scheduler can parse it, and the next three times it fires. The times are the point — `0 5 1-7 * 5` reads as "the first Friday" and fires eight times in twenty-eight days, because day-of-month and day-of-week are OR'd. Exits 1 if any line is unparseable, since an invalid schedule errors nowhere and simply never runs. `--to <workspace>` narrows it |
106
118
  | `foldrun schedule tick` | Settings' **Run scheduler now**: fire whatever is due this minute and print which flows went (`POST /api/schedule`). They were due anyway; this only makes them go now |
107
- | `foldrun rerun <run-id>` | the same flow again, from a step: `--from <n>` as the flow file numbers them, or `--agent <name>` for the first step that agent runs. Earlier steps are recorded as skipped. This is the debug loop from the terminal: fix the file, `deploy`, `rerun --from` the step that failed. `--wait` follows the new run like `invoke --wait`; `--to <workspace>` from anywhere. With no run id and a filter (`--status failed`, `--since 24h`, `--flow <name>`) it reruns every match from `--from <n>` (default 1), listing and asking first as a bulk `stop` does; `--dry-run` lists only |
119
+ | `foldrun rerun <run-id>` | the same flow again, from a step: `--from <n>` as the flow file numbers them, or `--agent <name>` for the first step that agent runs. Earlier steps are recorded as skipped. This is the debug loop from the terminal: fix the file, `deploy`, `rerun --from` the step that failed. `--wait` follows the new run like `invoke --wait`; `--to <workspace>` from anywhere. With no run id and a filter (`--status failed`, `--since 24h`, `--until 2h`, `--flow <name>`) it reruns every match from `--from <n>` (default 1), listing and asking first as a bulk `stop` does; `--dry-run` lists only |
108
120
  | `foldrun triggers` | why nothing ran. One row per flow that fired in the window: its trigger, how many fires became runs, when it last ran, and every reason for the difference with a count — a duplicate delivery, a throttled or debounced burst, a fire the platform slept through, an `overlap: skip`, a flow switched off by `disable_after:`. A switched-off flow is a non-zero exit; one successful run clears it. `--since <days>` (default 7), `--to <workspace>` |
109
121
  | `foldrun observe` | where the account fails, retries and spends: one row per workspace — runs, failed and what share, steps, cost — then the recent failures in their own words. `--to <workspace>` prints that workspace in full, as the Observe page does: per flow (runs, failed, p50/p95 duration, cost), per agent (failed, retried, skipped, p95), per tool (calls, errors, p95 latency, which agents use it). `--since <days>` (default 30), `--json` for the document |
110
122
  | `foldrun usage` | what the account consumed — runs, steps, tokens, compute, storage, per workspace — and what it was charged over `--days <n>` (default 56) from the ledger, per workspace with the change against the period before. `--json` for both documents |
111
123
  | `foldrun billing` | the account's balance, whether billing is on at all, what the platform is currently waiving, and the recent ledger entries with what each was for — a run names its workspace, flow and run id; an adjustment carries its own note. `--limit <n>` shows more. `statement [YYYY-MM]` is a month of the ledger (this month by default) — `--csv` for the file an accountant wants, to stdout or `--file <path>`. `wallet` is the burn, the runway and auto top-up; `wallet set auto-top-up --threshold <usd> --amount <usd>` turns it on (a refill of 5–500), `set auto-top-up off` turns it off, `set email <address>` is where receipts go. `details` is who invoices are made out to; `details set --name "…" --abn … --address "line1, city, state, postcode, AU" --email …` changes it, merged with what is there. `portal` prints a link to Stripe's billing portal. Changing the wallet, the details, and the portal are the account owner's; anyone else is told so |
112
124
  | `foldrun billing plans` | the Plans page: every plan with its monthly fee, credits, runs at once and workspaces, which one the account is on and this cycle's credits left (`GET /api/billing/plans`). `billing plan <id>` chooses one — a first plan answers with Stripe's checkout page, a change on a live plan is prorated at Stripe at once (asks first; `--yes`); `plan cancel` stops it at the end of the cycle, `plan resume` undoes that until then (`POST`/`DELETE /api/billing/subscribe`) |
113
125
  | `foldrun billing top-up <usd>` | the Payment page's top-up: Stripe's checkout page for that amount, opened in the browser (`--no-browser`, or no terminal, prints it). The credit lands when Stripe confirms the payment. `billing card` is the same for saving a card for auto top-up. Both need `billing:manage` |
126
+ | `foldrun billing credit <usd>` | put credit on the balance by hand (`POST /api/billing { usd, note }`, `--note "…"` on the ledger line) — only on an install without Stripe, a self-hosted or dev platform; once billing is live the platform refuses it and top-ups go through `billing top-up`. Needs `billing:manage` |
114
127
  | `foldrun gallery` | the tools the platform ships to every account (`web`), and for each whether this account keeps its own copy and whether that copy still matches. `pull` lays the shelf down under `~/.foldrun/gallery/<host>/` for offline runs (`foldrun run` and `foldrun eval` refresh it themselves before each run); `upgrade <tool>` replaces the account's copy with the current gallery version, the old one a revision back |
115
- | `foldrun account` | the account's own defaults — the AGENTS.md frontmatter every workspace under it inherits: `timezone`, `notify`, `budget`, `concurrency`. `foldrun account set timezone Australia/Sydney`, `set notify email you@example.com` (`--events failed,awaiting-approval,completed`), `set notify url https://…`, `set budget 60/month`, `set concurrency 4`, and `clear <key>` to unset one. `set notify` merges against what is there, because the platform replaces the whole block. `providers` lists the model providers the account's files use and the last daily key check — `--check` asks now, and a dead key is a non-zero exit. `export` downloads everything the platform holds about the account — workspaces, runs, members and the whole ledger — as one JSON file (owner only; `--file <path>`, `--force`). Singular — `accounts` below is the list of logins on this machine |
128
+ | `foldrun account` | the account's own defaults — the AGENTS.md frontmatter every workspace under it inherits: `timezone`, `notify`, `budget`, `concurrency`. `foldrun account set timezone Australia/Sydney`, `set notify email you@example.com` (`--events failed,awaiting-approval,completed`), `set notify url https://…`, `set budget 60/month`, `set concurrency 4`, and `clear <key>` to unset one. `set notify` merges against what is there, because the platform replaces the whole block. `providers` lists the model providers the account's files use and the last daily key check — `--check` asks now, and a dead key is a non-zero exit. `model` is the account's own model key, what every step runs on unless a `provider:` block names another: `foldrun account model` shows it (never the key, only its last four; exit 1 when there is none and runs are refused), `model set <provider> --key <api key>` sets it — a preset name (anthropic, openrouter, openai, groq, …) or `custom --base-url https://… --format anthropic|openai`, `--key -` reads the key from stdin — and `model remove` removes it; a Claude login token is refused for any account not allowed the platform's own credential. `export` downloads everything the platform holds about the account — workspaces, runs, members and the whole ledger — as one JSON file (owner only; `--file <path>`, `--force`). Singular — `accounts` below is the list of logins on this machine |
116
129
  | `foldrun workspace` | one deployed workspace's own settings (`--to <workspace>`): description, timezone, budget and notify, read from its `AGENTS.md` — unset means the account's. `set timezone <tz>`, `set budget 20/week`, `set notify email <address>` / `set notify url <https://…>` (`--events`; merged with what the file says), `set description "…"`, `set name <new>` — a rename changes every webhook URL, so it asks first and names the flows whose URLs changed — and `clear <key>`. Singular — `workspaces` is the list |
117
130
  | `foldrun workspace vocabulary` | what the dashboard's editor completes as you type in a workspace (`--to`): agents, flows, skills, tools, secrets (names only), scripts, document types and linkable documents (`GET …/vocabulary`; `--json`) |
118
131
  | `foldrun notify test` | send one test notification to whatever the workspace's `notify:` resolves to, and print what happened — the provider's own words when it failed ("domain not verified"). Exits 1 when it did not go. `--to <workspace>` |
@@ -120,12 +133,13 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
120
133
  | `foldrun webhooks redeliver <id>` | send one delivery again now, with the same `X-Foldrun-Delivery` id; prints the attempt and exits 1 when it was refused. `--to <workspace>` |
121
134
  | `foldrun notifications` | what mail you get, category by category: `always` for the required ones (security, invites, billing, account), `on`/`off` for the rest, and the workspaces where run alerts or approvals differ. `--json` |
122
135
  | `foldrun notifications set <category> on\|off` | turn one category off, or back on — account-wide, or for one workspace with `--to <workspace>` (`run-alerts`, `approvals`). A required category is refused with the reason |
136
+ | `foldrun flags` | the feature flags for this account: each one `on` or `off` and why — `account` (set for this account), `global` (set for every account), `rollout` (a share of accounts, with its percentage) or `default` — and what it does. Read-only: only the platform's super admin changes a flag, in the console. `--json` for the API's answer as it came. See [Feature flags](feature-flags.md) |
123
137
  | `foldrun history [path]` | every change to a deployed workspace, newest first: revision id, when, who, the message and the files. A path narrows it to one file; `--id <revision>` prints that revision as diffs; `--limit <n>`; `--to <workspace>` |
124
138
  | `foldrun history restore <path> --id <revision>` | the History drawer's **Restore this version**: the file is rewritten with that revision's text as a new revision, `restored <rev> from history`, so the restore is itself undone the same way. Shows the diff against the file now and asks (`--yes`, `--dry-run`); sent with `ifMatch`, so a file that changed since is refused (`--to`) |
125
139
  | `foldrun repo ls` | a workspace's repository: main, its branches and tags with their last commit, and the mirror. `repo diff <branch>` is what a branch changes against main; `repo deploy <commit\|branch\|tag>` makes that tree live (a rollback is a new commit on main, itself undoable); `repo merge <branch>` merges into main and deploys. Deploy and merge ask first (`--yes` with no terminal) and print the check issues or the run in flight that refused them |
126
140
  | `foldrun repo rm-branch <branch>` | delete a branch (asks; `--yes`). `repo mirror <git-url>` sends every push to main there too, with the workspace's `MIRROR_TOKEN` (`repo mirror off` stops it); `repo mirror-now` pushes main now and prints what the remote said (`--to`) |
127
141
  | `foldrun restore <workspace> --to <commit\|time\|age>` | put a workspace's source back as it was then — `--to` is a commit or its prefix, a branch or tag, a date/time (the last commit at or before it) or an age (`90m`, `12h`, `3d`, `2w`). Always shows the change first (`--dry-run`: the full diff and nothing else); the restore asks for the workspace's name typed back (`--yes` with no terminal) and sends the removals it showed, so a file added in between refuses it. A new commit on main, so it prints the `foldrun restore … --to <from>` that undoes it. Runs, `state/`, storage and secrets are not touched. See [Backups and restore](backups-and-restore.md) |
128
- | `foldrun backups` | how the account is backed up: the schedule, how many are kept, the last snapshot (verified? copied off the server? encrypted?), the snapshots that hold the account, each marked encrypted or not, each workspace's last change, and earlier restore requests (`--limit`, `--json`). `backups request --what runs\|state\|storage\|everything --at "<when>" [--to <workspace>] [--backup <id>] [--note "…"]` asks the platform team for a restore from a snapshot — recorded and emailed; nothing is restored until they do it |
142
+ | `foldrun backups` | how the account is backed up: the schedule, how many are kept, the last snapshot (verified? copied off the server? encrypted?), the snapshots that hold the account, each marked encrypted or not, each workspace's last change, and earlier restore requests (`--limit`, `--json`). `backups request --what runs\|state\|storage\|everything --at "<when>" [--to <workspace>] [--backup <id>] [--note "…"]` asks the platform team for a restore from a snapshot — recorded and emailed; nothing is restored until they do it. `backups requests` lists every request this account has made, and where each stands (`GET /api/account/backups/requests`, `--json`) |
129
143
  | `foldrun accounts` | every account signed in on this machine, and which one a bare command acts as. `foldrun profiles` is the same command |
130
144
  | `foldrun use <name>` | act as one of them from here on. `foldrun switch <name>` is the same command |
131
145
  | `foldrun keys ls` | the account's API keys, each with its role and scope, when it was created, when it was last used and from where (a /24 or /48), and when it expires — also `create <label>` (`--expires 30d\|90d\|365d\|never`), `rotate <id>` and `revoke <id>` |
@@ -139,13 +153,15 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
139
153
  | `--workspace <dir>` | the workspace folder (default `.`). On `init` it names the first workspace instead |
140
154
  | `--flat` | `init`: the single-folder shape — `agents/` and `flows/` at the root, no account around them |
141
155
  | `--from <template>` | `init`: start from a shipped template, e.g. `templates/hello` |
156
+ | `--starter` | `new`, `workspaces new`: include the example researcher, writer and `publish` flow — a new workspace is otherwise blank |
157
+ | `--as <name>` | `agent import`: the name the copy gets in this workspace (rewrites its `name:`); `storage put`: the path one uploaded file lands at |
142
158
  | `--transport <k>` | `tool new`: `script` (the default — a folder with its program beside it), `http` or `mcp` |
143
- | `--language <l>` | `tool new`: the script's language — `javascript`, `python` or `bash` |
159
+ | `--language <l>` | `tool new`, `source new`, `library new`: the script's language — `javascript`, `python` or `bash` |
144
160
  | `--task "<text>"` | the instruction for a manual run |
145
161
  | `--test` | `run`, `invoke`, `agent run`: a test run — every step runs, nothing outward happens, `state/` and `storage/` are left alone, and the run page lists what would have happened. See [Test runs](runs#test-runs) |
146
162
  | `--follow` | `logs`: keep tailing a live run — locally, or on the platform with `--url` |
147
163
  | `--status <s>` | `runs`, and a bulk `stop` or `rerun`: only these statuses, comma-separated — `failed`, `completed`, `awaiting-approval`, `running`, `queued` |
148
- | `--since <span>` | `runs`, and a bulk `stop` or `rerun`: only runs started within `24h`, `7d`, `90m`, `2w`. `observe` and `triggers` take a number of days instead |
164
+ | `--since <span>` | `runs`, and a bulk `stop` or `rerun`: only runs started within `24h`, `7d`, `90m`, `2w`. `observe` and `triggers` take a number of days instead (sent as `?days=`). `--until <span\|date>` on a bulk `stop` or `rerun`: only runs started before then |
149
165
  | `--since <span>` (audit) | `audit`: entries from `24h`, `7d` (default), `30d` ago or a date; `--until <date>` ends the window |
150
166
  | `--action <a>` | `audit`: one action (`apikey.created`) or a family (`apikey`, `secret`, `member`, `support`) |
151
167
  | `--actor <who>` | `audit`: one person or key — an email, `key:<label>`, `support:<email>` |
@@ -164,15 +180,16 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
164
180
  | `--csv` (audit) | `audit`: the log as CSV, for an auditor — `--file <path>` saves it |
165
181
  | `--threshold`, `--amount` | `billing wallet set auto-top-up`: refill `--amount` (5–500 USD) when the balance falls below `--threshold` |
166
182
  | `--name`, `--abn`, `--address`, `--email` | `billing details set`: the legal name, the ABN, `"line1, city, state, postcode, AU"` (the last part is the two-letter country), the receipt email |
167
- | `--days <n>` | `usage`: the window the charges cover (default 56) |
183
+ | `--days <n>` | `usage`: the window the charges cover (default 56). `status --platform --history`: 1–90 days (default 90) |
168
184
  | `--id <revision>` | `history`: one revision, as diffs |
169
185
  | `--engine <e>` | `login <site>`: the browser to sign in with — `chrome` (default; it signs in with Chromium, so `chromium` is the same), `firefox` or `safari` (`webkit`). Not `lightpanda` or `obscura`, which keep no profile to sign in with — the full list is [Engines](tools#engines) |
170
186
  | `--new-client` | `connect`: enter a new OAuth client even when one is saved for the secret |
171
187
  | `--limit <n>` | `runs`, `billing`, `history`: how many rows, newest first (`runs` default 20, `billing` 15). Each workspace is asked for its own newest `n` before the merge, so one busy desk cannot crowd out a quiet one |
172
188
  | `--step <n>` | `approve`, `reject`: decide one gate, numbered as `report` prints it. Without it, every step of that run which is waiting |
173
- | `--note "<text>"` | `approve`: guidance the approved step reads in its prompt — "approve, but skip the Sydney batch". `reject`: the reason, recorded on the trace |
189
+ | `--payload <json\|text\|@file>` | `approve`: release a step waiting on `wait: event` with this body (`POST …/approve` `{ payload }`). JSON is sent as JSON, anything else as text; `@file` reads a file |
190
+ | `--note "<text>"` | `approve`: guidance the approved step reads in its prompt — "approve, but skip the Sydney batch". `reject`: the reason, recorded on the trace. `billing credit`: the ledger line's note |
174
191
  | `--yes` | skip the confirmation, deliberately: `approve`, `stop`, `deploy` (when it would delete files), and a bulk `stop` or `rerun`, `flow rotate-hook`, `flow draft`, `history restore`, `restore`, `billing plan`, `repo deploy`/`merge`, `workspace set name`, and every delete — `runs rm`, `storage rm`, `storage unshare`, `source rm`, `secrets rm`, `keys revoke`, `workspaces rm`. Required when there is no terminal to ask; without it nothing is deleted |
175
- | `--json` | `report`: the raw run record instead of the report. `observe`, `usage`: the raw document |
192
+ | `--json` | `report`: the raw run record instead of the report. `observe`, `usage`, `workspaces`, `workspaces show`, `agent ls`, `flow ls`, `agent import --list`, `backups requests`, `status --platform --history`, `team`: the raw document |
176
193
  | `--events <a,b>` | `account set notify`: which events are notified on — `failed`, `awaiting-approval`, `completed` |
177
194
  | `--watch` | `invoke`: follow the run's trace here as it happens; the exit code is the run's |
178
195
  | `--print` | `open`: print the URL, do not open it |
@@ -192,6 +209,9 @@ claude.ai or Claude Code there, nor any MCP configuration on disk.
192
209
  | `--force` | `deploy`: deploy while runs are in flight. `pull`, `storage get`: overwrite local files |
193
210
  | `--no-runtimes` | `deploy`: do not wait for the workspace's environments to be built, or print them |
194
211
  | `--platform` / `--yes` | `workspaces rm`: delete it on the platform, said twice because it cannot be undone |
212
+ | `--platform` | `workspaces new`, `agent new`, `flow new`, `tool new`: make it on the platform, not in this folder. `status`: the platform's own health (`--history` for the daily record) |
213
+ | `--description`, `--model`, `--pattern`, `--template` | `agent new --platform`: its description and model tier. `flow new --platform`: the shape to start from. `tool new --platform`, `source new`, `library new`: a transport or gallery entry to start from |
214
+ | `--list` | `agent import`: what could be imported, importing nothing |
195
215
  | `--path <p>` | `tool test`: the path an http tool should probe, appended to its `base:` — a path that escapes the base is refused, the same confinement the agent gets |
196
216
  | `--timeout <s>` | how many seconds one request to the platform may take — or `FOLDRUN_TIMEOUT`. Default 30, and 300 for `tool test`, where the tool itself is the thing being waited on |
197
217
  | `--local` | `deploy`, `secrets`, `logs`: the installation on this machine, even when signed in |
@@ -85,12 +85,13 @@ api workspaces/$WS/flows/<flow>/run -X POST # start one
85
85
  | `/runs/<id>/stream` | GET | live events, server-sent |
86
86
  | `/runs/<id>/stop` · `/rerun` · `/approve` | POST | kill it; again from a step; release a gate |
87
87
  | `/flows` · `/flows/<f>` · `/flows/<f>/run` | GET POST · POST DELETE PATCH · POST | list, create, edit, start |
88
- | `/agents` · `/agents/<a>/run` | GET POST · POST | list agents, run one |
88
+ | `/agents` · `/agents/<a>/run` · `/agents/import` | GET POST · POST · GET POST | list agents, run one, copy one in from another workspace |
89
89
  | `/evals` · `/evals/<e>/run` | GET POST · POST | list evals, run one |
90
90
  | `/tools/<t>/test` | POST | exercise one tool alone |
91
91
  | `/storage` · `/storage/download` · `/storage/upload-url` · `/storage/preview` · `/shares` | GET POST PUT DELETE · GET · POST · GET · POST GET DELETE | files agents produced and people uploaded, any type; a file parsed for reading; public links to them |
92
92
  | `/triggers?since=` | GET | why nothing ran: fired vs started per flow, with reasons |
93
93
  | `/hooks/<f>/rotate` | POST | new webhook token for a flow |
94
+ | `/export` · `/import` | GET · POST | the workspace, a flow (`?kind=flow&name=`) or an agent as a .zip; take one in (`?dryRun=1` first, `?overwrite=1` to replace) |
94
95
  | `/restore` | POST | the workspace's source back to a point in its history — `dryRun` first, then `confirm` |
95
96
  | `/notify/deliveries` · `/notify/deliveries/<id>/redeliver` | GET · POST | every notify: webhook sent and each attempt; send one again |
96
97
  | `/api/secrets` · `/api/schedule` · `/api/account` · `/api/keys` | account-level | the vault, the clock, defaults, API keys |
@@ -193,6 +194,7 @@ provider: {} # BYOK: your own model credential
193
194
  permissionMode: plan # plan first, act once approved
194
195
  runtime: # only if scripts need packages
195
196
  packages: [requests]
197
+ system: [ffmpeg] # Debian packages, apt-installed before the step
196
198
  ---
197
199
 
198
200
  You research a topic and report what you found...
@@ -202,8 +204,12 @@ Only `name` and the prose are required. Every other field widens or narrows
202
204
  what the agent can reach, and the default is narrow.
203
205
 
204
206
  `runtime:` installs pip/npm packages in the sandbox each agent runs in. Pin
205
- versions the normal way (`pandas>=2`, `lodash@^4`). Python and Node are
206
- supported; anything else should ship as a committed binary.
207
+ versions the normal way (`pandas>=2`, `lodash@^4`). `system:` adds Debian
208
+ packages — a program (`ffmpeg`), a library's headers for a pip package that
209
+ compiles (`libcairo2-dev`, `python3-dev`), `build-essential` — installed with
210
+ apt before the step starts and cached, so later runs only unpack them. An agent that
211
+ finds it needs something mid-run installs it itself with the `install` tool
212
+ (apt, pip or npm), which every agent has; declaring is only a speed-up.
207
213
 
208
214
  Link documents to each other with `[[wikilinks]]` — the name, not the path.
209
215
 
package/docs/deploying.md CHANGED
@@ -68,7 +68,11 @@ is its commit.
68
68
 
69
69
  A rollback is a deploy of an earlier ref (`POST …/repo {action: deploy, ref}`
70
70
  or the History page), and it lands as a **new commit on `main`** — history
71
- moves forward, never rewrites.
71
+ moves forward, never rewrites. **Preview** beside "Deploy this commit" (and
72
+ "Preview deploy" beside a branch's "Merge & deploy" on the Repository page)
73
+ shows first what it would change, add and remove, with diffs, and any run in
74
+ flight that would hold it — the restore dry run
75
+ (`POST …/restore {to, dryRun: true}`), which is the same operation.
72
76
 
73
77
  ## Mirroring
74
78
 
package/docs/faq.md CHANGED
@@ -13,8 +13,11 @@ with a team.
13
13
  **Which models does it use?** Whatever you point it at. `model:` takes a
14
14
  tier — `fast`, `default`, `max` — and the platform resolves the tier to a
15
15
  model. Bring your own key for any provider that speaks the Anthropic or the
16
- Chat Completions API, per account or per workspace, and the platform bills
17
- you nothing for those tokens. See [Providers](providers).
16
+ Chat Completions API — once for the whole account in Settings → Model
17
+ (`foldrun account model set`), or per workspace or agent with a `provider:`
18
+ block — and the platform bills you nothing for those tokens. On a hosted
19
+ platform every account brings its own API key; Claude login tokens are not
20
+ accepted. See [Providers](providers).
18
21
 
19
22
  **Can agents reach the internet?** Yes — the public internet, as themselves,
20
23
  with the secrets they were granted. Not private networks, not the cloud
@@ -0,0 +1,63 @@
1
+ # Feature flags
2
+
3
+ Some changes ship dark: the code is deployed, and whether an account gets
4
+ the new behaviour is a decision made afterwards, by the platform team, one
5
+ account or a share of accounts at a time. That decision is a feature flag.
6
+
7
+ An account cannot change its flags. It can see them: **Settings → Feature
8
+ flags** in the dashboard, `foldrun flags` in a terminal, or
9
+ `GET /api/account/flags`. Each flag says whether it is on for this account
10
+ and why.
11
+
12
+ ## What decides a flag
13
+
14
+ The flags themselves are code — a name, what it changes, who owns it, a
15
+ default, and sometimes a rollout percentage. A name the code does not
16
+ register is not a flag; asking for one is a 404. What the platform's super
17
+ admin sets on top is checked in this order, and the first that has an
18
+ answer wins:
19
+
20
+ | | source | |
21
+ |---|---|---|
22
+ | 1 | `account` | this account's own override — on or off whatever else says |
23
+ | 2 | `global` | the install-wide setting — on or off for every account without an override |
24
+ | 3 | `rollout` | a percentage of accounts. Each account falls in a fixed bucket from 0 to 99, a hash of the account and the flag's names, so the same account gets the same answer on every server and every day, and raising the percentage only adds accounts — nobody who had it loses it |
25
+ | 4 | `default` | what the code says when nothing else does |
26
+
27
+ A change is seen at once on the server that made it, and on every other
28
+ within ten seconds.
29
+
30
+ ## The example
31
+
32
+ `example-flag` is registered, off by default, and read by nothing: turning
33
+ it on changes no behaviour. It is there to try the console, the API and the
34
+ CLI against without risk.
35
+
36
+ ```sh
37
+ foldrun flags # every flag, on or off for this account, and why
38
+ foldrun flags --json
39
+ ```
40
+
41
+ ```json
42
+ { "flags": [ { "name": "example-flag", "description": "The registered example. …",
43
+ "owner": "platform", "default": false, "on": false,
44
+ "source": "default", "rollout": null } ] }
45
+ ```
46
+
47
+ ## Running the platform yourself
48
+
49
+ On a hosted install the super admin changes flags at **/admin → Flags**:
50
+ the install-wide on/off and rollout, and an override per account. The same
51
+ is `PUT`/`DELETE /api/admin/flags/<flag>` and
52
+ `/api/admin/flags/<flag>/accounts/<account>` (see the API reference's
53
+ super admin section). Every change is on the platform's audit as
54
+ `operator.flag.global`, `operator.flag.global_cleared`,
55
+ `operator.flag.account` or `operator.flag.account_cleared`, with who made
56
+ it and what it was before. The settings are rows in Postgres
57
+ (`feature_flags`), or `<data>/.feature-flags.json` without a database; an
58
+ account's overrides go when the account is deleted, and their audit stays.
59
+
60
+ A flag is added by a line in `foldrun-platform/src/flags.ts` and asked with
61
+ `flagOn(account, "<name>")`. Retire it by deleting the line and the code
62
+ that asks, once it is on for everyone; a row left behind for a name the
63
+ code no longer registers is ignored.