foldrun 0.6.0 → 0.7.2

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/bin/foldrun.mjs CHANGED
@@ -6,27 +6,58 @@
6
6
  // point. A framework whose free version is a crippled demo gets no adoption,
7
7
  // and adoption is the only reason a hosted version has customers.
8
8
  //
9
- // foldrun init [dir] scaffold a working workspace
9
+ // foldrun init [dir] scaffold an account: a library, and a workspace in it
10
10
  // foldrun check [dir] validate it — no model calls, no cost
11
11
  // foldrun run <target> run an agent or a flow
12
12
  // foldrun eval [name] run evals
13
13
  // foldrun probe <model> can this model hold a tool loop? (live check)
14
14
  // foldrun logs [run-id] recent runs, or one run's full event trail
15
+ // foldrun runs what has run lately, across the account
16
+ // foldrun report <run-id> one run, whole: every step, what it cost, what it wrote
17
+ // foldrun approvals what is waiting for a person, anywhere in the account
18
+ // foldrun approve <run-id> release a waiting gate (asks first) — reject refuses it
19
+ // foldrun stop <run-id> kill a run in flight
20
+ // foldrun account the account's own defaults — timezone, notify, budget, concurrency
21
+ // foldrun schedule every flow in the account that fires on a clock, and when it fires next
22
+ // foldrun triggers why nothing ran: fired vs started per flow, and each reason
23
+ // foldrun webhooks <verb> deliveries / redeliver <id> — the notify: webhooks a workspace sent, and each attempt
24
+ // foldrun notifications what mail you get, per category — set <category> on|off
25
+ // foldrun backups how the account is backed up, its snapshots — request a restore from one
26
+ // foldrun restore <ws> a workspace's source back to a point in its history (shows the change, asks)
27
+ // foldrun onboarding the account's getting-started steps, the next one first
28
+ // foldrun billing the balance, and what the money went on
29
+ // foldrun gallery the tools the platform ships to every account — list, pull, upgrade <tool>
30
+ // foldrun storage <verb> ls / cat / get / put / rm / share / shares / unshare — what the agents produced, and public links to it
31
+ // foldrun runtimes the environments a workspace's agents need, and whether each is built
15
32
  // foldrun secrets <verb> set / ls / rm — the vault, from the terminal
16
- // foldrun deploy [dir] push a workspace into an installation
33
+ // foldrun new <name> another workspace in this account
34
+ // foldrun agent new <name> one more agent in this workspace — also flow new, tool new
35
+ // foldrun agent run <name> run one agent once on a platform, no flow
36
+ // foldrun tool test <name> exercise one tool alone — no model, no run
37
+ // foldrun deploy [dir] push this account — or one workspace — into an installation
38
+ // foldrun pull bring the platform's account down into this folder
39
+ // foldrun status what differs here from what is deployed
40
+ // foldrun workspaces what exists here, and there
17
41
  // foldrun invoke <flow> start a flow on a running platform
18
42
  // foldrun source <verb> ls / cat / put / mv / rm one workspace file on a platform
19
43
  // foldrun open [page] the dashboard for this workspace
20
44
  // foldrun login sign this machine in from the browser
45
+ // foldrun login <site> a browser window to sign in to a site by hand; the session
46
+ // (cookies, storage, identity) is stored for an agent to wear
21
47
  // foldrun whoami who the platform thinks this terminal is
22
48
  // foldrun doctor what is between this terminal and the platform, checked
23
49
  // foldrun accounts every account signed in here; `use <name>` switches
24
50
  // foldrun keys <verb> ls / create / revoke — the account's API keys
51
+ // foldrun library <verb> ls / cat / put / rm — the account's shared shelf on the platform
52
+ // foldrun find <words> search the account the way the dashboard's ⌘K does
53
+ // foldrun changelog what each release of the platform changed
54
+ // foldrun preferences your own settings on the platform — the theme
25
55
  //
26
56
  // `check` is the one to run in CI: it catches the mistakes that otherwise only
27
57
  // show up as a confidently wrong answer at 3am.
28
58
 
29
59
  import { fileURLToPath } from "node:url";
60
+ import fs from "node:fs";
30
61
  import path from "node:path";
31
62
  import process from "node:process";
32
63
 
@@ -39,47 +70,199 @@ process.on("warning", () => {});
39
70
 
40
71
  const [, , command, ...rest] = process.argv;
41
72
 
73
+ // `foldrun --version`: the package's own number and nothing else — no core,
74
+ // no network, no account. Before every import below, so it stays instant.
75
+ // `foldrun version` is the long form that also asks the platform.
76
+ if (command === "--version" || command === "-v") {
77
+ try {
78
+ console.log(`foldrun ${JSON.parse(fs.readFileSync(path.join(HERE, "../package.json"), "utf8")).version}`);
79
+ } catch {
80
+ console.log("foldrun (unknown version)");
81
+ }
82
+ process.exit(0);
83
+ }
84
+
42
85
  const HELP = `foldrun — agents are just folders
43
86
 
44
- foldrun init [dir] create a workspace you can run immediately
45
- foldrun check [dir] validate agents, flows, tools, evals and knowledge
87
+ foldrun init [dir] create an account folder: AGENTS.md, library/ and workspaces/<name>
88
+ foldrun new <name> another workspace in this account
89
+ foldrun agent new <name> one more agent in this workspace (--to <workspace> in an account)
90
+ foldrun flow new <name> one more flow — its first step names an agent you already have
91
+ foldrun tool new <name> one more tool: a folder with its program beside it (--transport, --language)
92
+ foldrun check [dir] validate every workspace here, and the shared library
93
+ foldrun check --to <ws> validate the DEPLOYED copy instead — fetched from the platform, checked here
94
+ foldrun agent run <name> run one agent once on the platform (--task "…", --to, --wait, --test)
95
+ foldrun agent link <agent> add to its team, in its frontmatter: --subagent <worker>, --consult <agent> or --can-ask (--description for a worker with none)
96
+ foldrun agent unlink <agent> take one away: --subagent <name>, --consult <name> or --can-ask
97
+ foldrun flow add <flow> <pattern> one canvas block as a markdown edit: chain, parallel, router, fan-out, loop, approval, ask, wait, rescue, subflow — diff shown, checked, --dry-run
98
+ foldrun flow rm-step <flow> --step <n|agent> delete one step (its options too); groups renumber, agent file untouched — says what else changes, diff, checked, asks (--yes), --dry-run
99
+ foldrun flow dup-step <flow> --step <n|agent> copy one step (marker and options) directly under it, in parallel with it — diff, checked, --dry-run
100
+ foldrun flow show <flow> the flow as the canvas draws it: trigger, groups, step chips, each agent's team, check problems in place
101
+ foldrun flow run <flow> start a flow on the platform — the same command as invoke
102
+ foldrun flow rotate-hook <flow> a new webhook URL for a flow; the old one stops at once — asks first, --yes means it
103
+ foldrun flow set <flow> --step <n|agent> key=value … the step editor: model, effort, retry, timeout, verify, when, case, else, loop, until, each, max, limits, instruction (key= clears) — diff, checked, --dry-run
104
+ foldrun flow trigger <flow> manual|webhook|schedule the trigger picker (--schedule "<cron>", --timezone <zone>) — diff, checked, --dry-run
105
+ foldrun flow move-step <flow> --step <n|agent> drag a card: --group <g> runs it beside group g, --after <g> gives it its own group after g (0 = first) — diff, checked, --dry-run
106
+ foldrun flow copy-step <flow> --step <n|agent>[,<n>…] the steps' markdown on stdout, as the canvas's Copy
107
+ foldrun flow paste <flow> step markdown from stdin or --file in as new groups (--after <g>, default last) or beside one (--group <g>) — diff, checked, --dry-run
108
+ foldrun flow draft "<what it should do>" Draft with AI on the platform (--to; --flow <name> redrafts one): shows the files and the check, saves on yes (--yes, --dry-run)
109
+ foldrun tool test <name> exercise one tool alone, no model and no run (key=value args, --to)
46
110
  foldrun extract [dir] move single-file script tools into folders (tool.md + run.*)
47
- foldrun run <target> run an agent or flow (target: name, or flow:name)
48
- foldrun eval [name] run one eval, or all of them
111
+ foldrun run <target> run an agent or flow HERE (target: name, or flow:name) — on the platform: flow run / invoke a flow, agent run one agent
112
+ foldrun eval [name] run one eval, or all of them (--to <workspace>: the deployed evals, run on the platform)
113
+ foldrun promote <run-id> keep a finished run as a regression case in evals/ (--eval <name>, --expect "contains: …" repeatable, --case <name>)
49
114
  foldrun probe <model> live check: can this model hold a tool loop here?
50
- foldrun logs [run-id] recent runs, or one run's full event trail
51
- foldrun secrets set NAME store a secret (prompted, never echoed) — also ls, rm, status
52
- foldrun connect NAME OAuth sign-in from the terminal, stored as an auto-refreshing secret
53
- foldrun deploy [dir] push a workspace into an installation
54
- foldrun invoke <flow> start a flow on a running platform (--to <workspace>)
115
+ foldrun logs [run-id] recent runs in one workspace, or one run's full event trail (--local: this machine's)
116
+ foldrun runs what has run lately, across the account — --status, --verdict, --since, --to, --limit (--local prints exactly what logs --local prints)
117
+ foldrun runs rm <run-id> delete a run's record and archived outputs; a live one is stopped first — asks first, --yes means it
118
+ foldrun report <run-id> one run, whole: header, every step, what it wrote and what is still waiting
119
+ foldrun report <run-id> get <agent>/<path> download a file the run archived from that agent's outputs/ (--file <path>|-, --force)
120
+ foldrun report <run-id> live the live browser view: which agents have a frame; --agent <name> saves the newest as a JPEG (--file <path>|-)
121
+ foldrun approvals every gate waiting on a person, with its question (--to <workspace> for one)
122
+ foldrun approve <run-id> release a waiting gate — asks first, --yes means it, --note "…" steers the step
123
+ foldrun reject <run-id> refuse one, with --note as the reason
124
+ foldrun stop <run-id> kill a run in flight — asks first, --yes means it
125
+ foldrun stop --status running [--since 2h] [--flow f] every run a filter matches (runs/bulk): lists them, asks, stops only those (--dry-run, --to)
126
+ foldrun answer <run-id> "…" answer the question an agent is asking mid-step (--option <n> picks a choice)
127
+ foldrun message <run-id> "…" say something to a running agent — it hears it after its next tool call
128
+ foldrun rerun <run-id> run it again from a step (--from <n>) or from an agent's step (--agent <name>); --wait
129
+ foldrun rerun --status failed [--since 24h] [--flow f] rerun every run a filter matches, from --from <n> (default 1) — lists, asks (--dry-run, --to)
130
+ foldrun observe where the account fails, retries and spends, per workspace (--to <workspace> in full, --since <days>, --json)
131
+ foldrun usage what the account consumed, and what it was charged week by week (--days <n>, --json)
132
+ foldrun schedule every flow that fires on a clock, its cron line and the next times (--to <workspace>)
133
+ foldrun schedule tick fire whatever is due now — Settings' "Run scheduler now"
134
+ foldrun guide write the coding-agent rules: a block in AGENTS.md and CLAUDE.md importing it (--check, --print)
135
+ foldrun docs [page] foldrun's docs, from the copy this CLI ships — what a coding agent should read (--path)
136
+ foldrun triggers why nothing ran: per flow, fired vs started and every reason (--since <days>, --to <workspace>)
137
+ foldrun storage <verb> what a workspace produced: ls [prefix], cat <path> (--preview: a PDF, sheet, deck or zip as text), get <path>, put <file|folder>... (--into <folder/>, --as <path> for one file), rm <path> — and share <path> (--ttl <days>, --forever), shares (--all), unshare <token>
138
+ foldrun runtimes the environments a deployed workspace's agents need (python, pip and npm packages) and whether each is built — failed ones with the installer's error (--wait holds on while any is building)
139
+ foldrun billing the account's balance and its recent ledger entries (--limit <n>)
140
+ foldrun billing statement [YYYY-MM] a month of the ledger (--csv for the file, --file <path> to save it)
141
+ foldrun billing wallet burn, runway, auto top-up — also set auto-top-up --threshold <usd> --amount <usd> | off, set email <address>
142
+ foldrun billing details who invoices are made out to — also set --name "…" --abn … --address "line1, city, state, postcode, AU" --email …
143
+ foldrun billing portal a link to Stripe's billing portal (owner only)
144
+ foldrun billing plans the plans, which one the account is on and this cycle's credits — also plan <id>, plan cancel, plan resume (asks first)
145
+ foldrun billing top-up <usd> Stripe's checkout page for adding credit (owner only; --no-browser prints it)
146
+ foldrun billing card Stripe's page for saving a card for auto top-up (owner only)
147
+ foldrun gallery the platform's built-in tools (web) and whether you keep your own copy — also pull (a copy for offline runs), upgrade <tool>
148
+ foldrun account the account's defaults — also set <key> <value>, clear <key>, providers (--check) (singular; accounts lists logins)
149
+ foldrun account export everything the platform holds about the account as one JSON file (owner only; --file <path>, --force)
150
+ foldrun workspace one deployed workspace's settings (--to) — also set <name|description|timezone|budget|notify> <value>, clear <key>
151
+ foldrun workspace vocabulary the names the dashboard's editor completes in a workspace: agents, flows, skills, tools, secrets, scripts, types, documents (--to, --json)
152
+ foldrun notify test send one test notification from a workspace and say what happened (--to <workspace>)
153
+ foldrun webhooks deliveries the notify: webhooks a workspace sent, newest first — status, attempts, last answer (--failed, --status <s>, --event <e>, --limit, --offset, --to)
154
+ foldrun webhooks redeliver <id> send one delivery again now, same X-Foldrun-Delivery id; exits 1 when refused (--to)
155
+ foldrun notifications what mail you get, category by category, and which are always sent (--json)
156
+ foldrun notifications set <category> on|off turn one off or back on — account-wide, or --to <workspace> for run-alerts and approvals
157
+ foldrun history [path] every change to a deployed workspace, newest first (--id <revision> for its diff, --limit, --to)
158
+ foldrun history restore <path> --id <revision> put one file back as that revision left it, as a new revision — diff, asks (--yes, --dry-run, --to)
159
+ foldrun repo ls a workspace's branches and tags — also diff <branch>, deploy <ref>, merge <branch> (both ask first), rm-branch <branch>, mirror <git-url|off>, mirror-now (--to)
160
+ foldrun restore <ws> --to <commit|time|3d> put a workspace's source back as it was then — shows the change, asks for the name typed back (--dry-run: the diff only, --yes)
161
+ foldrun backups how the account is backed up (encrypted?), when it last was, the snapshots it is in — also request --what runs|state|storage|everything --at "<when>" [--to <ws>] [--note]
162
+ foldrun secrets set NAME store a secret (prompted, never echoed) — also ls, rm, status; --kind file|ssh|api|service-account|m2m for the other shapes
163
+ foldrun secrets clients the OAuth clients saved on the platform — also add <name> (--provider or --authorize-url/--token-url, --client-id, --scopes), rm <name>
164
+ foldrun connect NAME OAuth sign-in from the terminal, stored as an auto-refreshing secret — --client <saved> runs it from a saved client through the platform
165
+ foldrun deploy [dir] push the whole account, or deploy <workspace> for one of them
166
+ foldrun pull [workspace] bring the platform's account down here (refuses to clobber; --force overrides)
167
+ foldrun status [workspace] per workspace: what is added, changed or gone since the last deploy
168
+ foldrun status --platform is the platform up: each component, and any incident or maintenance posted (--json)
169
+ foldrun workspaces what exists here and on the platform — also rm <name> (--platform --yes)
170
+ foldrun workspaces demo make the demo pipeline workspace on the platform — "Try the demo pipeline"
171
+ foldrun library the account's shared library on the platform: ls [skills|tools|scripts|knowledge], cat <kind>/<path>, put <kind>/<path> (--file, else stdin), rm <kind>/<path> (asks)
172
+ foldrun find <words> search the account as the dashboard's ⌘K does: workspaces, agents, flows, tools, skills, knowledge, memory, runs (--json)
173
+ foldrun invoke <flow> start a flow on a running platform (--to <workspace>; --once <key> so a retry never starts a second run; --tag <t> repeatable)
55
174
  foldrun source <verb> the files on a platform, one at a time: ls, cat <path>, put <path>, mv, rm (--to <workspace>)
56
175
  foldrun open [page] the dashboard for this workspace, in the browser
176
+ foldrun api spec the platform's OpenAPI 3.1 document (GET /api/openapi.json) — stdout, or --out <file>
177
+ foldrun api version the API version this account's requests get, the versions served, the rate limits — also pin <YYYY-MM-DD>, unpin (admin; --json)
178
+ foldrun changelog the platform's release notes, newest first: features, fixes, docs (--limit <n>, --json)
179
+ foldrun version this CLI's version, its core's, and the platform's: release, API version, component shas — warns when the platform ships a newer CLI (--json)
57
180
 
58
181
  Signing in
59
182
  foldrun login sign this machine in from the browser (--token <key> to skip it)
183
+ foldrun login <site> --url <address>
184
+ open a browser, sign in to that site by hand, and store the
185
+ session: cookies, storage and the browser identity it needs
60
186
  foldrun logout forget this machine's key, and revoke it where allowed
61
187
  foldrun whoami who you are on the platform: account, role, workspaces
188
+ foldrun onboarding the account's getting-started steps, done or not, the next one first (--json)
189
+ foldrun preferences your own settings on the platform — the theme; set theme system|light|dark (a key acts for whoever minted it)
62
190
  foldrun doctor check the path to the platform: node, CLI, account, DNS, a timed /api/healthz
63
191
  foldrun accounts every account signed in on this machine, and which one is active
64
192
  foldrun use <name> act as one of them from here on
65
- foldrun keys ls the account's API keys — also create <label>, revoke <id>
66
- foldrun --help
193
+ foldrun keys ls the account's API keys: created, last used, expires, scope — also create <label> [--expires 90d], rotate <id> [--grace 1h], revoke <id>
194
+ foldrun audit the account's audit log, newest first: sign-ins, keys, invites, secrets by name, deploys, support views (--since 7d, --action, --actor, --to, --all, --csv)
195
+ foldrun --version this CLI's version, offline (\`foldrun version\` also asks the platform)
196
+ foldrun --help everything; \`foldrun <command> --help\`, \`-h\` or \`foldrun help <command>\` for one (never runs it)
67
197
 
68
198
  Options
69
- --workspace <dir> the workspace folder (default: .)
70
- --from <template> start from a shipped template, e.g. templates/hello
199
+ --workspace <dir> the workspace folder (default: .) — on init, the first workspace's name
200
+ --flat init: the old single-folder shape, no account around it
201
+ --from <template> init: start from a shipped template, e.g. templates/hello
202
+ --transport <k> tool new: script (default), http or mcp
203
+ --language <l> tool new: the script's language — javascript, python, bash
71
204
  --task "<text>" the instruction for a manual run
72
205
  --test run, invoke: a test run — nothing outward, state/ untouched, receipts on the run page
73
206
  --follow logs: keep tailing a live run (with --url: on the platform)
207
+ --status <s> runs, stop, rerun: only these statuses, comma-separated (failed, completed, awaiting-approval…)
208
+ --since <span> runs, stop, rerun: only runs started within 24h, 7d, 90m, 2w
209
+ --since <span> audit: entries from 24h, 7d, 30d ago, or a date (default 7d); --until <date> ends the window
210
+ --action <a> audit: one action (apikey.created) or a family (apikey, secret, member, support)
211
+ --actor <who> audit: one person or key — an email, key:<label>, support:<email>
212
+ --since <days> observe, triggers: the window in days (observe default 30, triggers 7)
213
+ --verdict <v> runs: only completed runs whose summary leads with it — good, bad, quiet, blocked (comma-separated)
214
+ --flow <name> stop, rerun: only that flow's runs; flow add: the other flow a subflow step runs; flow draft: the existing flow to redraft
215
+ --agent <name> flow add: the agent a chain, parallel or router step runs; rerun: from the first step that agent runs; report live: whose browser frame
216
+ --instruction "<text>" flow add: what a new step (chain, parallel, router) is told to do
217
+ --after <n> flow add, flow move-step, flow paste: put the new group after the nth (0 = first); --before <n> before it (flow add); default last
218
+ --group <n> flow add, flow move-step, flow paste: the group a step joins, in parallel
219
+ --step <n|agent> flow add, flow rm-step, flow dup-step, flow set, flow move-step, flow copy-step: the step a pattern goes on (or is deleted, copied, edited, moved) — its place in the file (1 = the first) or the agent it runs
220
+ --cases <V=agent,…> flow add: a router's branches, e.g. BUG=bugs,DOCS=docs; --else <agent> when none matches
221
+ --each <e> flow add: lines, items or "rows of <path>"; --max <n> caps it (1-20)
222
+ --loop <n> flow add: extra cycles, 1-5 (default 3); --until <MARKER> ends it (default APPROVED); --judge "<claim>" adds verify: judge:
223
+ --question "<text>" flow add: the ask a person gets before the step; answer: which question of several (its id)
224
+ --wait <span|event> flow add: 90s, 30m, 4h, 3d or event
225
+ --on-fail <agent> flow add: who takes the step over when it fails
226
+ --off flow add: take the pattern off the step instead (fan-out, loop, approval, ask, wait, rescue)
227
+ --schedule "<cron>" flow trigger: the cron line a schedule fires on (5 fields, or @daily); --timezone <zone> the zone it is read in
228
+ --kind <k> secrets set: file (--file <path>), ssh (--host, --user, --port, --key-file or a prompted password), api (--base-url, --header "Name: value" repeatable), service-account (--file <key.json>, --scopes), m2m (--token-url, --client-id, --scopes; secret prompted)
229
+ --client <name> connect: run the consent from that OAuth client saved on the platform (secrets clients lists them)
230
+ --subagent <name> agent link, agent unlink: a worker it delegates to (subagents:)
231
+ --consult <name> agent link, agent unlink: a colleague it asks (agents:)
232
+ --can-ask agent link, agent unlink: ask in its tools — it may ask you mid-step
233
+ --description "<text>" agent link: what a --subagent is for, when its file has no description:
234
+ --eval <name> promote: the eval file to write to (default <target>-regressions)
235
+ --expect "<line>" promote: an assertion in eval syntax (contains: …, judge: …) — repeatable
236
+ --case <name> promote: the case's name
237
+ --tag <t> invoke, flow run: a label on the run — repeatable, sent as tags
238
+ --inputs <set> invoke, flow run: the task is a saved input set (evals/<flow>-inputs.md, or an eval case for the flow)
239
+ --preview storage cat: the platform reads the file and prints what is in it
240
+ --csv billing statement: the CSV file instead of the table
241
+ --csv audit: the log as CSV, for an auditor (--file <path> to save it)
242
+ --threshold / --amount billing wallet set: auto top-up refills --amount (5-500 USD) when the balance falls below --threshold
243
+ --name / --abn / --address / --email billing details set: the invoice's legal name, ABN, "line1, city, state, postcode, AU", receipt email
244
+ --days <n> usage: the window the charges cover (default 56)
245
+ --id <revision> history: one revision in full, as diffs
246
+ --engine <e> login: (with a site) the browser to sign in with — chrome (default: real Google Chrome, Chromium when it is not installed), chromium, firefox or safari
247
+ --new-client connect: enter a new OAuth client even when one is saved for the secret
248
+ --step <n> approve, reject: decide only that step; default is every step that is waiting
249
+ --note "<text>" approve, reject: guidance the agent reads — or the reason for a refusal
250
+ --yes approve, stop, rerun (bulk), flow rotate-hook, flow draft, history restore, billing plan, repo, workspace set name, and every rm/revoke/unshare: skip the confirmation, deliberately (required with no terminal)
251
+ --json report: the raw run record instead of the report; observe, usage, api version, changelog, find, preferences, workspace vocabulary: the raw document; version: {cli, core, platform}
252
+ --events <a,b> account set notify: failed, awaiting-approval, completed
253
+ --failed webhooks deliveries: only the ones that gave up
254
+ --limit <n> runs, billing, history, changelog: how many rows
74
255
  --value "<text>" secrets set: skip the prompt (careful with shell history)
75
- --file <path> source put: the local file to send (default: stdin)
256
+ --out <file> api spec: where to write the OpenAPI document (default: stdout)
257
+ --file <path> source put, library put, flow paste: the local file to send (default: stdin); secrets set --kind file|service-account: the file it holds; storage get, report get, report live, account export, billing statement: where to write it (- for stdout)
76
258
  --message "<why>" source put: recorded on the file's revision
77
259
  --account secrets: account scope instead of the workspace's
78
- --wait invoke: hold on and print the result
260
+ --wait invoke, agent run: hold on and print the result
261
+ --path <p> tool test: the path an http tool should probe, appended to its base:
79
262
  --watch invoke: follow the run's trace here as it happens
80
263
  --print open: print the URL only
81
- --from <n> invoke: start at step n; earlier steps are skipped
82
- --no-browser login, connect: print the address instead of opening it
264
+ --from <n> invoke, flow run, rerun: start at step n; earlier steps are recorded as skipped (a bulk rerun defaults to 1)
265
+ --no-browser login, connect, billing top-up/card/plan: print the address instead of opening it
83
266
  --provider <name> connect: google, github, microsoft or linkedin (fills the URLs)
84
267
  --scopes "<a b c>" connect: space-separated scopes (default: the provider's example)
85
268
  --client-id / --client-secret connect: the OAuth app (prompted if omitted; secret never echoed)
@@ -87,19 +270,26 @@ Options
87
270
  --authorize-url / --token-url connect: a provider with no preset
88
271
  --role <r> keys create: viewer, editor (default) or admin
89
272
  --for <workspace> keys create: a deploy key for one workspace (--access read|write)
273
+ --expires <span> keys create: when it stops working — 30d, 90d, 365d or never (default never)
274
+ --grace <span> keys rotate: keep the old key working for 1h or 24h while you swap the new one in (default 0: revoked now)
90
275
 
91
276
  Platform options (deploy, invoke, secrets, logs, keys)
92
- --to <workspace> workspace on the platform (deploy default: folder name)
277
+ --to <workspace> workspace on the platform (deploy default: folder name); flow add/show, agent link: edit or show the deployed copy instead of this folder
93
278
  --tenant <name> account to deploy into (default: default, local only)
94
279
  --data <dir> the installation's data directory
95
280
  --url <url> a running platform (or FOLDRUN_URL, or where you last signed in)
96
281
  --token <key> API key for --url (or FOLDRUN_TOKEN, or the one from foldrun login)
282
+ --timeout <s> seconds one request may take (or FOLDRUN_TIMEOUT; default 30, tool test 300)
97
283
  FOLDRUN_TIMEOUT=<s> seconds one request to the platform may take (default 30)
98
284
  --profile <name> act as one stored account for this command (see foldrun accounts)
285
+ --quiet leave out the dim "acting as …" line a platform command prints on stderr
99
286
  --local deploy: into the installation on this machine, even when signed in
100
287
  --commit <sha> deploy: record which commit this is
101
- --dry-run deploy: check and report, change nothing
102
- --force deploy: deploy even while runs are in flight
288
+ --dry-run deploy: check and report, change nothing; flow add, flow set, flow trigger, flow move-step, flow paste, flow draft, history restore, agent link, agent unlink: show the diff, write nothing; stop, rerun (bulk): list what matches, touch nothing
289
+ --no-runtimes deploy: do not wait for the workspace's environments to be built
290
+ --force deploy: deploy even while runs are in flight; pull, storage get: overwrite local files
291
+ --yes deploy: allow deleting files this folder no longer has (asked otherwise; required with no terminal)
292
+ --platform --yes workspaces rm: delete it on the platform, deliberately
103
293
 
104
294
  Nothing here needs an account. Set ANTHROPIC_API_KEY to run; init and check
105
295
  work without one. \`foldrun login\` is for the hosted platform, or your own.`;
@@ -115,7 +305,13 @@ if (!command || command === "--help" || command === "-h") {
115
305
  // `--value` as the account's argument and stored an empty secret; `--force
116
306
  // ./dir` swallowed the directory. A flag followed by another flag is also
117
307
  // boolean, so an unlisted switch at least does not eat its neighbour.
118
- const BOOLEAN_FLAGS = new Set(["account", "follow", "force", "oauth2", "wait", "watch", "print", "dry-run", "help", "no-browser", "local", "test"]);
308
+ const BOOLEAN_FLAGS = new Set(["account", "follow", "force", "oauth2", "wait", "watch", "print", "dry-run", "help", "no-browser", "local", "test", "flat", "yes", "platform", "json", "forever", "all", "check", "new-client", "quiet", "off", "can-ask", "preview", "csv", "failed"]);
309
+ // Flags said more than once collect into a list: `--tag a --tag b`.
310
+ const REPEATABLE = new Set(["tag", "expect", "header"]);
311
+ // `--wait` is a switch everywhere but `flow add <flow> wait`, where it takes
312
+ // a span: `--wait 30m`, `--wait event`. Only a value that reads as one is
313
+ // taken, so `invoke x --wait` and friends are as they were.
314
+ const WAIT_VALUE = /^(\d+(\.\d+)?\s*[smhd]|event)$/i;
119
315
  const flags = {};
120
316
  const positional = [];
121
317
  for (let i = 0; i < rest.length; i++) {
@@ -125,51 +321,133 @@ for (let i = 0; i < rest.length; i++) {
125
321
  }
126
322
  const name = rest[i].slice(2);
127
323
  const next = rest[i + 1];
128
- if (BOOLEAN_FLAGS.has(name) || next === undefined || next.startsWith("--")) flags[name] = true;
129
- else flags[name] = rest[++i];
324
+ let value;
325
+ if (name === "wait" && next !== undefined && WAIT_VALUE.test(next)) value = rest[++i];
326
+ else if (BOOLEAN_FLAGS.has(name) || next === undefined || next.startsWith("--")) value = true;
327
+ else value = rest[++i];
328
+ if (REPEATABLE.has(name) && typeof value === "string") flags[name] = [...(Array.isArray(flags[name]) ? flags[name] : []), value];
329
+ else flags[name] = value;
330
+ }
331
+
332
+ // Help, before anything runs. `foldrun deploy --help` used to parse --help
333
+ // into a flag nothing read and then DEPLOY (2026-09-30, to the wrong account).
334
+ // --help or -h anywhere, or `foldrun help <command>`, prints that command's
335
+ // lines and exits 0 — no platform is contacted, no file is touched.
336
+ if (flags.help === true || rest.includes("-h") || command === "help") {
337
+ const which = command === "help" ? positional[0] : command;
338
+ console.log(helpFor(which));
339
+ process.exit(0);
340
+ }
341
+
342
+ /** The lines of HELP about one command: its `foldrun <command>` usage lines
343
+ * and the options whose description names it. The whole text when nothing
344
+ * matches or none was named. */
345
+ function helpFor(which) {
346
+ if (!which) return HELP;
347
+ const esc = which.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
348
+ const usage = new RegExp(`^\\s*foldrun ${esc}(\\s|$)`);
349
+ // "approve, reject: …" names approve too — a comma after the name is
350
+ // part of a list of commands, not the end of it.
351
+ const option = new RegExp(`(^|[;,]\\s*)${esc}([\\s,][^;:]*)?:`);
352
+ const lines = HELP.split("\n");
353
+ const own = lines.filter((l) => usage.test(l));
354
+ const opts = lines.filter((l) => /^\s*--/.test(l) && option.test(l.replace(/^\s*--\S+(\s+(\/\s+)?--\S+)*(\s"?<[^>]+>"?)?\s+/, "")));
355
+ if (!own.length && !opts.length) return HELP;
356
+ return ["", ...own, ...(opts.length ? ["", "Options:", ...opts] : []), "", "`foldrun --help` for everything."].join("\n");
130
357
  }
131
358
 
132
- // `deploy` is the one command that is not about a single folder: it reads a
133
- // source directory and writes into an installation, which has accounts and
134
- // many workspaces. Pinning FOLDRUN_WORKSPACE would collapse that layout to one
135
- // folder and send every deploy to the same place.
136
- const isDeploy = command === "deploy";
359
+ // Commands that are about an ACCOUNT, not one folder: they read or write the
360
+ // account AGENTS.md, the shared library and several workspaces at once, so
361
+ // pinning FOLDRUN_WORKSPACE would collapse the layout to one of them.
362
+ // `deploy` has always been in this set — it writes into an installation,
363
+ // which has accounts and many workspaces.
364
+ const ACCOUNT_WIDE = new Set(["deploy", "pull", "status", "workspaces", "new"]);
137
365
 
138
366
  // `init` and `check` take a directory; `run` and `eval` take the name of a
139
367
  // thing to run, so a directory there would be ambiguous — use --workspace.
140
368
  const takesDir = command === "init" || command === "check" || command === "extract";
141
- const workspace = path.resolve(
142
- flags.workspace ?? (takesDir ? positional.shift() ?? "." : "."),
369
+ // For `init` the flag names the first WORKSPACE, not a directory — the
370
+ // directory it makes is the account. A value that looks like a path is still
371
+ // read as one, so `foldrun init --workspace ./desk` keeps working.
372
+ const flagIsName =
373
+ command === "init" &&
374
+ typeof flags.workspace === "string" &&
375
+ !flags.workspace.includes(path.sep) &&
376
+ !flags.workspace.includes("/") &&
377
+ !fs.existsSync(flags.workspace);
378
+ const here = path.resolve(
379
+ (flagIsName ? undefined : flags.workspace) ?? (takesDir ? positional.shift() ?? "." : "."),
143
380
  );
144
- if (!isDeploy) process.env.FOLDRUN_WORKSPACE = workspace;
381
+
382
+ // Which shape is this? One helper in the core answers, so `check`, `deploy`,
383
+ // `status` and the runtime's own account scope cannot drift apart.
384
+ const { detectLayout, installationDataRoot } = await import("@foldrun/core/layout");
385
+ const layout = detectLayout(here);
386
+ const byName =
387
+ typeof flags.workspace === "string" && layout.workspacesDir && layout.workspaces.includes(flags.workspace)
388
+ ? path.join(layout.workspacesDir, flags.workspace)
389
+ : null;
390
+
391
+ /**
392
+ * The workspace directory this command should act on.
393
+ *
394
+ * At an account root with exactly one workspace there is nothing to ask about
395
+ * — that is the one. With several, a command that needs one says so itself,
396
+ * by name, rather than picking.
397
+ */
398
+ function pinned() {
399
+ // `--workspace blog-desk` inside an account names one of its workspaces, not
400
+ // a directory relative to here. Only when it IS one of them, so a path that
401
+ // happens to look like a name still resolves as a path.
402
+ if (byName) return byName;
403
+ if (layout.workspaceDir && layout.kind !== "empty") return layout.workspaceDir;
404
+ if (layout.kind === "account" && layout.workspacesDir && layout.workspaces.length === 1) {
405
+ return path.join(layout.workspacesDir, layout.workspaces[0]);
406
+ }
407
+ if (layout.kind === "empty") return here;
408
+ return null;
409
+ }
410
+
411
+ const workspace = pinned() ?? here;
412
+ if (!ACCOUNT_WIDE.has(command)) process.env.FOLDRUN_WORKSPACE = workspace;
413
+ // The account scope, decided once here and honoured by the core's
414
+ // singleAccountRoot — so `library/` beside `workspaces/` is what an agent's
415
+ // skills:, tools: and scripts: resolve against, exactly as it is on the
416
+ // platform.
417
+ process.env.FOLDRUN_ACCOUNT ??= layout.accountRoot;
145
418
 
146
419
  // Where the secrets, keys and run store live.
147
420
  //
148
- // A workspace on a laptop keeps them inside itself, in `.foldrun/`. But a
149
- // workspace that belongs to an installation sits at
421
+ // An account on a laptop keeps them at its own root, in `.foldrun/`, so every
422
+ // workspace under it shares one vault — the way workspaces in an account share
423
+ // one vault on the platform. A flat workspace keeps them inside itself, which
424
+ // is where they have always been.
425
+ //
426
+ // A workspace that belongs to an INSTALLATION is a third case: it sits at
150
427
  // `<data>/<tenant>/workspaces/<name>`, and its secrets belong to the tenant,
151
428
  // two levels up — so pointing --workspace at one and defaulting to `.foldrun/`
152
429
  // opened an empty store beside it. Every declared secret came back missing,
153
- // and the error told you to add secrets that were already there.
154
- //
155
- // The layout says which case this is: a parent directory named `workspaces`
156
- // only happens in an installation.
157
- const parent = path.basename(path.dirname(workspace));
158
- const installationRoot =
159
- parent === "workspaces" ? path.resolve(workspace, "..", "..", "..") : null;
430
+ // and the error told you to add secrets that were already there. The shape
431
+ // alone cannot tell an installation from an account folder (they are the same
432
+ // shape); the installation's key file at the data root can.
433
+ const installationRoot = installationDataRoot(layout.accountRoot);
160
434
 
161
- if (isDeploy) {
435
+ if (command === "deploy") {
162
436
  // The destination is an installation, so --data names it outright. Without
163
437
  // one, dataRoot()'s own default applies: data/ at the project root.
164
438
  if (flags.data) process.env.FOLDRUN_DATA = path.resolve(flags.data);
165
439
  } else {
166
- process.env.FOLDRUN_DATA ??= installationRoot ?? path.join(workspace, ".foldrun");
440
+ process.env.FOLDRUN_DATA ??=
441
+ installationRoot ??
442
+ (layout.kind === "flat" || layout.kind === "empty"
443
+ ? path.join(workspace, ".foldrun")
444
+ : path.join(layout.accountRoot, ".foldrun"));
167
445
  }
168
446
 
169
447
  const { run, explain } = await import(path.join(HERE, "../src/commands.mjs"));
170
448
 
171
449
  try {
172
- const code = await run(command, positional, flags, workspace);
450
+ const code = await run(command, positional, flags, workspace, layout);
173
451
  process.exit(code ?? 0);
174
452
  } catch (err) {
175
453
  // The whole chain, not the top: "fetch failed" is the top, and the