Skip to content

Changelog

What changed in the forculus CLI and in each SDK, from one release to the next, newest first. The platform itself changes without releases, and the docs say what it does today.

forculus, from npm, Homebrew or the install script. Its docs.

Changed:

  • forculus schedule off says when it cancelled a run that was waiting to go, from forculus schedule run or because it was due. That run never happened, and nothing said so. forculus audit says it too.

Fixed:

  • forculus webhook deliveries times each delivery by when its event came, the order it lists them in. It gave the last try’s time, so an older event tried again read as newer than the ones above it. One that took more than one try says how many.
  • forculus webhook deliveries lists events in the order they happened, newest first, each with the time it happened. Events we picked up in the same minute came in any order, all with the time we picked them up, up to a minute late. --json has each one’s event_at, and the dashboard’s list uses the same time.
  • forculus webhook list, webhook add and webhook edit say “all events” for a webhook that gets every event, and how many for one that gets more than three. They printed every name on one line, all 158 for a webhook added without --event. --json still has every name. webhook edit says only what it changed, not the whole row after it.
  • Signed in to staging, the CLI’s links and examples are staging’s. webhook add, audit-stream add, log-drain add, a redirect forculus curl explains, forculus uninstall and forculus mcp install pointed at production’s docs, and the preview a viewer is asked for was on production’s apps domain. The docs are on the site of the API in use, and the preview’s example on the apps domain that API names.
  • forculus curl --help and the docs say it exits 0 whatever the app answers, a 404 or a 502 included, as curl does, and that --fail exits 22 on a 4xx or 5xx.
  • forculus audit and forculus watch name a preview reached through the apps domain, as forculus curl --preview reaches one, as they name one opened at its own address: on a preview of hello (p-0a1b2c3d), with the path the preview saw. It read as a request to the live app, with the apps domain’s path.

Added:

  • forculus curl <workspace>/<app> --preview <version> sends the request to one of the app’s previews, as you, through the apps domain. It also takes the preview’s address, for someone the app is shared with who can’t list its versions. forculus open --preview <version> prints the preview’s address. The MCP server’s call_app takes preview, as a number or a string. The agent plugin’s preview skill uses it, so update the CLI before the plugin.

Changed:

  • A container deploy and forculus dev --docker give the image they build a name of their own, and remove it by that name once they’re done. They removed it by its id, which deletes the image whatever single name it has by then: two deploys of the same code at once could delete the image the other was about to save, or one you had named since the build began. Docker now keeps an image for as long as anything else names it.
  • forculus deploy refuses --port, --instance-type and --instances for a Worker or a static site, before anything is built or uploaded. It deployed without them and said nothing. --dry-run refuses them too, and so does the MCP server’s deploy_app, by its own names for them.
  • forculus deploy refuses --main and --compatibility-date for a container or a static site the same way. It dropped them without a word too.
  • When --kind was given, a flag for another kind says to change --kind, not to add it. Adding a second one failed.
  • forculus detect, --dry-run and the start of a deploy print size: and containers: after port: when forculus.json sets container.instance_type or container.instances.
  • --dry-run and the start of a deploy give the port, size and count that --port, --instance-type and --instances set over forculus.json’s. They gave the file’s, which the deploy didn’t use.
  • The MCP server’s deploy_app schema gives instances as 1 to 20, the numbers it takes. It said 0 to 65535.
  • forculus scale says so on a line of its own when the platform rolled the live version out again because the last deploy didn’t finish. Otherwise a scale rolls nothing out, with any CLI: the containers running keep running, even after an earlier push failed.
  • forculus scale --help says what a scale changes: nothing rolls out unless the last deploy didn’t finish, and containers past the new number stop within a few minutes.
  • --help splits the sentences it joined with a semicolon.
  • What forculus mcp’s tools change is in the audit log as via: mcp, not cli. A forculus command an agent runs in a shell is still cli. The server says so in its User-Agent, forculus/<version> mcp, which marks the entry and proves nothing. forculus watch shows it as (mcp).
  • forculus deploy --dry-run shows the entry and the compatibility date that --main and --compatibility-date set. forculus detect and the dry run print main: and compatibility date: lines for a Worker when something sets them.
  • A kind from forculus.json reads because forculus.json says it's a container, where it said you said. With a kind given, the first line still names the framework detection found.
  • forculus import --env-file reads a quoted value over more than one line whole, and leaves it out as Not set. It stopped at the second line, or read it as a variable of its own.
  • forculus import says The directory <dir> already has files in it rather than starting with the directory’s name, which was given a capital. A dry run says why it stops there too.
  • forculus kv put and forculus kv rm say a change can take a minute or so to show everywhere. kv rm said the key was gone while kv get still read it.
  • forculus mcp install cursor names a Homebrew install by its opt path, which lasts through brew upgrade, not the version’s own Cellar path.
  • forculus dev --as webhook gives a delivery id and a signed time only for Stripe, Slack and Standard Webhooks, as the platform does. GitHub, Shopify and HMAC-SHA256 deliveries had both.
  • The MCP server: list_cli_sessions gives times as RFC 3339, workspace_insights takes hour, day, week and month for its window, query_database names run_sql for a write, delete_kv_key and set_kv_value say a change takes a minute or so to show, a missing key names list_kv_keys, and a directory outside the session’s says what to do for an upload, an import or a clone. A folder the client names as the plugin’s is never deployed from, whichever root reaches it.
  • forculus env set and env remove on a container app, and the MCP server’s set_env, say the container takes about five minutes to restart with the change. They said a minute or two, and it took five and a half on production every time.

Fixed:

  • forculus curl printed 200 OK for a stream cut off because your access to the app ended. It now says the answer was cut off and exits with code 1. Only a request sent as you is checked this way: one sent with a bypass token, and any other HTTP client, can still see a stream the platform cut as one that finished.
  • forculus curl told someone refused with a 404 to run forculus app list on the app’s workspace, whether or not they were in it. Someone outside it is pointed at forculus app list --shared instead.
  • forculus deploy of a container app whose build took more than about 15 minutes failed with “the push token was refused”, since the token was made before the build. It now asks for a new one when the build has left less than 10 minutes of it, and once more if the registry refuses it partway through the push.
  • forculus deploy refuses an image whose programs are all for another architecture, as from a FROM --platform=linux/arm64 line, before it’s pushed. BuildKit marks such an image as built for linux/amd64, so it went live and couldn’t run.
  • forculus dockerfile and the MCP server’s generate_dockerfile say a directory that deploys as a Worker or a static site isn’t a container app. They planned every directory as a container, so a Worker was told to write a Dockerfile, and a package.json Worker got a Node Dockerfile no deploy would use. Both take a kind (--kind, kind) to plan one as a container anyway.
  • A Bun project whose entry calls Bun.serve, with no start script, is detected as a container that runs on Bun. Its TypeScript entry made it a Worker, which Cloudflare refused with “Bun is not defined”. A typed call like Bun.serve<{ user: string }>(...) counts too. A Worker with a Bun server beside it for local work, in a file package.json doesn’t name, stays a Worker.
  • An image forculus deploy refuses before the push, for a key no deploy may carry, is sent to the platform to be kept. It’s listed by forculus scans, and the audit log, webhooks and Slack have an app.deploy_blocked for it, as for any refused deploy. The deploy prints Scan: refused and the scan’s id, as a Worker’s does. Nothing kept or logged the refusal.
  • The MCP server’s set_env refuses an argument it doesn’t take, like values or env, and names it and the ones it takes. It dropped them and said only that there was nothing to set.

Added:

  • forculus deploy --instances <n> runs a container app on that many containers, each request going to one of them. forculus.json takes it as container.instances. forculus scale <workspace>/<app> <n> changes the number without a deploy, and forculus app show gives it. The MCP server’s deploy takes instances.
  • forculus workspace show says how many containers an app may run on the plan, and the container units its container apps share.
  • forculus workspace create --region eu makes a workspace that keeps its apps’ databases, files, containers and archived audit log in the EU, where we offer it. The EU isn’t open on forculuslabs.tech yet, so for now it’s refused. It can’t change later, and apps in an EU workspace need Enterprise. If the platform made it somewhere else, the command says so and fails. forculus workspace show says where a workspace keeps its data, and the MCP server’s create_workspace takes region.

Changed:

  • forculus app show prints a containers: line for a container app, between driver: and visibility:.
  • forculus workspace show prints a data kept in: line after plan:, and containers per app: and container units: lines with the plan’s limits.
  • forculus audit describes a change to how many containers an app runs as now runs 3 containers.
  • When forculus.json or a flag names a size or a number of containers, forculus deploy of a container app checks the workspace’s container units before it builds, and stops there when they’re spent, rather than after the push.
  • container.instances in forculus.json stops CLI 0.1.11 and older with an error that starts forculus.json isn't valid: unknown field `instances` . Upgrade every CLI that deploys the app before adding it, the GitHub deploy action’s cli-version included.
  • A container refusal past what Business runs now says to write to sales@forculuslabs.tech. CLI 0.1.11 and older add “Upgrade the workspace at” and the Billing link after it, which a Business workspace can’t do itself: an Enterprise contract is the way past Business.

Added:

  • forculus preview end <workspace>/<app> --pull-request <n> ends the preview a pull request’s GitHub run put up. The deploy action runs it when the pull request closes, with the credential its run traded for. The MCP server has it as end_preview.
  • forculus deploy --preview with a pull request’s credential puts up that pull request’s preview, and says which pull request it’s for. forculus versions and forculus audit name the pull request beside its previews.
  • forculus policy shows and sets a workspace’s publish policy: who besides its owners may open an app to the whole workspace or an email domain, share outside the workspace, open public paths, make bypass tokens, mint agent credentials and connect AI clients. forculus policy breaches lists what was done before a rule by someone it doesn’t name, and forculus policy end ends it. The MCP server has publish_policy, which only reads.
  • Every deploy is scanned before it goes live, for keys left in what it sends and for packages with published vulnerabilities. forculus deploy sends the packages its lock files pin (names and versions only) and the project’s .forculus-allow, reads a container image’s layers and config for keys before it pushes, and prints what the scan found under the app’s address.
  • forculus scans <workspace>/<app> lists an app’s scans, refused deploys among them, and --version <n> or --id <id> prints one whole. forculus versions marks a version whose scan found something, and forculus audit says what each scan found.
  • forculus workspace scan-policy <workspace> shows whether deploys with findings warn or are refused, and --secrets block or --vulnerabilities block turns refusing on. Turning it off is done in the dashboard.
  • The MCP server has deploy_scans, and a deploy’s answer says what its scan found and what to do about it.
  • forculus log-drain sends an app’s logs, or every app’s in a workspace, to an HTTPS endpoint as signed JSON lines, Datadog, Grafana Loki or an S3-compatible bucket. test sends a test line, failed and retry list and send again the batches that failed for a day, and pause, resume, level and remove change one. The MCP server has list_log_drains and set_log_drain, which pauses, resumes or changes the level of a drain but never adds one.
  • forculus insights <workspace> shows who opens a workspace’s apps: people, opens and when each app was last opened, the apps nobody opened in 30, 60 or 90 days, and apps whose owners have all left. --app narrows it to one app, --days picks 7, 30, 90 or 365, and --csv apps or --csv people prints a CSV. The MCP server has it as usage_insights, which only reads.
  • "kv": true and "queue": {"path": "/jobs/queue"} in forculus.json give an app on Team or Business a key-value store and a queue of its own, made by the next deploy and bound as KV and QUEUE. forculus kv status, ls, get, put and rm reach its keys, and forculus queue status and send its queue. The MCP server has app_stores, list_kv_keys, get_kv_value, set_kv_value, delete_kv_key and send_to_queue.
  • forculus db query <workspace>/<app> "<statement>" runs one statement on an app’s database and prints a page of the rows as a table. It’s read only unless you add --write, takes --limit and --offset to page, and --csv prints up to 10,000 rows as CSV, or saves them with -o. The MCP server has query_database, which only reads, and list_files, which lists an app’s files.
  • The MCP server has a tool for nearly everything the CLI does. An agent can now share an app with a domain or a group, answer requests for access, look after groups, read members, custom roles, single sign-on and SCIM, read and test webhooks and audit log streams, set an app’s AI budget and outbound list, pause, start and run schedules, put a preview live, read and change an app’s files and database, export and restore a database, restore a deleted workspace, read storage and traffic, list connected AI clients and revoke an agent’s credential. Anything that deletes or replaces data takes what it changes named twice, and so does turning an app’s outbound list off. query_database runs only a statement that reads. No tool changes who signs in, adds a member or a role, changes a group that gives a role, or sends the workspace’s events somewhere new.
  • The MCP server’s show_workspace reads a workspace’s plan, limits and allowed email domains, list_cli_sessions lists your CLI sessions without where they signed in from, and generate_dockerfile shows the Dockerfile a container deploy would build with, writing nothing.
  • forculus sso <workspace> --status and forculus scim <workspace> --status show how a workspace signs in and whether its SCIM token works, for its owners. Changing either stays in the browser.
  • forculus dev --as takes a persona as well as an email: owner, editor, viewer, agent, token, scheduler, webhook (with --provider and --pattern), outsider and nobody. Each gets the token the platform would mint for that kind of caller, so an app’s checks for agents, bypass tokens, scheduled jobs and webhooks run locally. An outsider gets the platform’s 404 and never reaches the app, and a webhook reaches only the path its pattern opens. The page at /.forculus has a button for each.
  • forculus dev --docker runs a container app in the image a deploy would build, published on 127.0.0.1 only. A project that is only a Dockerfile runs this way without the flag.
  • A build for Windows on Arm. The PowerShell install script and the npm package install it on an arm64 PC, which until now got the x86-64 build and ran it under emulation.
  • forculus import brings a project over from Vercel, Netlify or Replit, or from any git repository, as a new app: forculus import vercel:<project> acme/app, netlify:<site>, replit or the repository’s address. It reads the project’s settings and production variables with a token you paste, used for that import only and never saved, clones the code, writes a forculus.json, makes the app, sets its variables and secrets, deploys it and adds its scheduled calls. It shows all of that and asks first, and names what can’t come along with what to do instead. --dry-run only shows it. The MCP server has it as import_app, which never takes a token or a key from the conversation.
  • forculus postgres connects a Postgres database you already have, like Neon, Supabase or RDS, and gives it to apps, which query it through the platform and never hold its password. add, rotate, update, check, remove, grant, revoke, show and list. The URL or the password is read at a hidden prompt or from stdin, never from a flag. Team connects up to 3 and Business up to 10. The MCP server has list_postgres, check_postgres, grant_postgres, revoke_postgres, remove_postgres and app_postgres. Connecting a database and changing its password stay yours, at the prompt.
  • forculus network lets an Enterprise workspace’s apps reach services inside the company’s network, through a connector you run there, and lists the fixed addresses its apps send from. add prints the command that runs a network’s connector, service add puts a host and its ports on it, and grant and revoke choose which apps reach it.
  • forculus ai key set takes keys for Google Gemini, Mistral, Groq, Cohere, Azure OpenAI (with --endpoint) and Amazon Bedrock (with --region), beside Anthropic and OpenAI. forculus env set refuses a Groq or Bedrock key as a plain variable, as it does the others.
  • FORCULUS_DEBUG=1 prints the connection’s own error when a request gets no answer, for support.

Changed:

  • forculus dev sets FORCULUS_DEV=1 for the app it runs. The SDKs from 0.3.0 take its http issuer only with it, and the platform never sets it.
  • --role is now only for an email, an agent or a token. --as viewer replaces --as you@example.com --role viewer, which still works.
  • The /.forculus page and the outsider’s page can’t be framed by another site.
  • An app that takes the connection and closes it without answering, as a container does while it starts, gets a 502 that says so rather than an empty reply.
  • forculus dev --docker removes its container and image however it stops, an error included, and waits for the container to go before it removes the image.
  • A wrangler config that binds D1, KV or a queue under a name the platform doesn’t give is refused with everything else it asks for, in one message, rather than on its own.
  • forculus app delete warns by the terms the app’s owners agreed to. A deleted app is kept for 30 days only when you and every owner of the app and its workspace have accepted the 5 October terms, or from 4 November, and is otherwise deleted for good as before. A delete you were told is kept goes through with --kept, and nothing is deleted if that no longer holds. With Recently deleted full, it says only --for-good goes through. If it can’t read the app, it says so rather than warning that the delete is final, and an app that isn’t there is said to be missing.
  • forculus app delete --for-good deletes an app already in Recently deleted, and a deleted app says it can be deleted for good too. forculus workspace delete --for-good opens the page that deletes a deleted workspace for good. The MCP server’s delete_app takes for_good and kept.
  • A first deploy that fails takes back the app it made, by its id, and deletes it for good, so the same deploy can run again. If the app changed in between, it’s left as it is and the deploy says why.
  • forculus deploy to an app that was deleted says so and until when it can be restored, rather than offering --create. A deploy cut off by its workspace being deleted says it isn’t live, and what to delete after a restore. A directory given before the app is told the app goes first.
  • forculus workspace members marks who waits for the identity provider after a restore, and forculus whoami says where you’re held. forculus audit says when someone held was let back in.
  • forculus db restore that may have run says the bookmark that undoes it. When the platform won’t say where a restore goes, having been asked too often that hour, it asks as it used to rather than saying only to wait, since --yes still restores.
  • forculus files rm, get and put keep a leading ./ in a file’s name: rm ./secret deleted secret. get and put refuse it, as any dot part. forculus files get --out writes into a device or a pipe as it is, so /dev/null and /dev/stdout work.
  • A request that gets no answer says plainly why: a proxy that refused it, nothing listening (on Windows too), or a request that couldn’t be made at all, which isn’t tried again.
  • The line naming a request for support goes under a message of more than one line, not after it.
  • The dashboard opens in a browser only for someone at a terminal, never under --json or in a script.
  • forculus app list says 1 person for one person outside the workspace, not 1 people. forculus access on an app shared with nobody says so and who can still open it, where it printed nothing.
  • The MCP server asks again before set_outbound_list turns an app’s list off, marks export_database as one to ask about, since the database pauses while it runs, and names its own tools for the CLI’s commands in limits and refusals.

Added:

  • forculus billing show <workspace> prints the plan, what it costs, when it renews and this month’s requests against the allowance. On Enterprise it prints the contract’s terms and the invoices Stripe sent, each with a link to pay it. forculus billing open opens the Billing tab, since paying and changing the plan stay in the browser. The MCP server has the same as billing_status, which only reads.
  • forculus sessions list and forculus sessions revoke <id> list and end your CLI sessions. forculus logout --everywhere ends every one of them, this one too, and disconnects every AI client you connected. Bypass tokens and agent credentials you made keep working after you sign out: add --tokens to revoke them too.
  • forculus connections list and forculus connections disconnect list and disconnect the AI clients connected to apps as you, or to an app you manage.
  • forculus agent rename renames an agent. The MCP server has it as rename_agent.
  • forculus webhook edit changes which events a webhook or Slack channel gets, or turns it on or off.
  • forculus egress usage <workspace>/<app> shows the hosts an app sent requests to over the last 30 days, with what its list refused. The MCP server has it as outbound_usage.
  • forculus sso, forculus scim and forculus workspace export open the dashboard’s page for each. A CLI session can’t change who signs in to a workspace or carry it off.
  • forculus app deleted and forculus app restore list and restore the apps a workspace keeps for 30 days after they’re deleted, where the platform keeps deleted apps. forculus workspace deleted and forculus workspace restore do the same for workspaces. The MCP server has list_deleted_apps and restore_app.
  • forculus db restore --to puts an app’s database back as it was at a moment in the last 30 days, and prints a bookmark that undoes it.
  • forculus role makes, changes and deletes a workspace’s custom roles, and gives them to members or groups. forculus workspace add --custom-role gives one as the member joins.
  • forculus audit-stream sends a workspace’s audit log to Splunk, Datadog, an HTTPS collector or an S3-compatible bucket, and retries failed batches. The MCP server has list_audit_streams, which only reads.

Changed:

  • forculus app list shows owners and admins, under each app, who can open it, what it sends requests to, who made it and when it was last opened.
  • forculus workspace show and forculus workspace usage know Enterprise: what its plan allows, and its usage against what the contract includes each calendar month.
  • forculus app delete says whether the app can be restored and until when. --for-good skips the 30 days.
  • forculus deploy takes a worker or container section in forculus.json as the kind it names, and stops on a section for another kind than the one deployed, which it used to drop without a word. An index.html beside a Worker’s entry is no longer taken for a static site: the deploy asks which you meant.
  • The rewrite printed for an old flat forculus.json keeps its kind. Without it, a Worker with an index.html went up as a static site serving its own code.
  • A static site never uploads forculus.json or a wrangler config from its top, and stops on TypeScript source, which a site would only serve for anyone to read.
  • A failure on our side names its request, for support to find it.
  • forculus curl takes the file the MCP server’s create_bypass_token writes as it is, in --token or FORCULUS_TOKEN: its # lines are skipped. Anything else that isn’t one token is said plainly, not as builder error: failed to parse header value.
  • forculus schedule list says when a schedule last ran late, and how many runs that one stood in for.
  • forculus rollback to a preview says the version is live, not live again. So does the MCP server’s rollback, which said the app was back on it.
  • forculus secret set refuses a host no app’s request reaches, ours or one under .internal, .local or .localhost, before it asks for the value.
  • forculus db export --out - writes to stdout, not to a file named -.
  • forculus files get downloads a file whose name has a space or a % in it, which it could list but not fetch. A file that isn’t there is no_such_file, not a missing app, a static site says it keeps no files, and --out - writes to stdout.
  • forculus files put names the value that can’t be a file’s name, and says so when its two names are the wrong way round.
  • forculus workspace domains <workspace> with no list shows the domains set now.
  • forculus app list with no workspace lists the apps of the only one you’re in.
  • forculus whoami and forculus workspace show say when Free’s apps are paused because we can’t count their usage for a while, and that it’s ours to fix.
  • forculus audit and the audit CSV mark what the identity provider did over SCIM, as the dashboard does.
  • forculus ai key remove asks first, and takes --yes where there’s no terminal to ask at. AI budgets are printed with their thousands grouped.
  • forculus deploy finds a Worker in worker.js, worker.mjs and their src/ copies, and in the main or module package.json names when that file has a default export.
  • forculus mcp install cursor from npm says the command it wrote is npm’s own copy, which runs without Node and stays put through updates. From npx, pnpm dlx or bunx it stops, since their cache is cleared.
  • Every failure under --json is JSON with a code: a command line the CLI can’t read is invalid_argument, and one that had none is failed, not null.
  • Prose is wrapped to 80 columns at a terminal. Piped or saved, it’s as before.
  • forculus app public-paths prints, under each path, the address to send its requests to. The app’s own address asks everyone to sign in, public paths included, so a 401 there read as the path not being open. The MCP server’s list_public_paths has it as url.
  • forculus token list says when each token ends, or ended.
  • forculus deploy of an app that isn’t there, by someone whose role can’t make apps, says a workspace owner or admin has to make it. It offered --create, which was then refused.
  • forculus webhook list says why a webhook is off, and when one that’s still on is failing.
  • forculus audit names the webhook a webhook entry is about and a database restore’s undo bookmark, and names an agent’s request by the name it had when it made it.
  • forculus curl prints only the error when an answer stops short, or a stream stops because your session ended, in place of the status. It printed 200 OK first, over an error and an exit code of 1.
  • forculus promote of a version that failed keeps Cloudflare’s reason on one line, and the message ends with a full stop.
  • forculus schedule off on a schedule that’s already off, and on on one that’s already on, say so and change nothing. One we turned off says why and that it has to be added again.
  • forculus webhook test exits with 1 when the test event wasn’t delivered.
  • forculus webhook add --name and forculus webhook rename refuse a name another webhook or Slack channel in the workspace already has, in any case.
  • forculus workspace usage sizes its table the way its storage line, the plans and the dashboard do, in units of 1,024, so the same files no longer read 1.2 GB in one and 1.3 GB in the other. A database or files not measured yet say not measured, not 0 B.
  • --help ends every help text of more than one sentence with a full stop.
  • forculus deploy to a name that can’t be one quotes it as you typed it. A name of lowercase letters alone came back with a capital.
  • forculus share <app> @resend.dev is refused: nobody has an inbox at a mail testing service’s domain, and a share with it reached every test account.

Added:

  • forculus webhook add --name gives a webhook or Slack channel a name, like “deploys to ops”, so you can tell apart several that go to the same site. forculus webhook rename names one later, or takes its name off, and forculus webhook list shows the name beside its address.
  • forculus workspace usage shows what the workspace stores against its plan, split into site files, databases and app files, and each app’s files in the table. When the workspace is over, it says what that stops and how to make room.
  • forculus ai grant and forculus ai show say when an app’s list of hosts is on and leaves out the provider’s, so its requests to Anthropic or OpenAI would be refused, and give the forculus egress allow command that fixes it.
  • forculus audit and forculus watch mark a request made to a preview, with the preview’s name, like p-0a1b2c3d. Before, only the host in --json told it apart from the live app.
  • forculus access <app> ends with viewer everyone in acme when everyone in the workspace can open the app, and --json has visibility. It listed only the people it was shared with.

Changed:

  • forculus group delete needs --yes. Deleting a group takes away every app’s share with it, and the only way back is to make the group again and share each app again. Group messages call a group “the ops group” throughout.
  • forculus deploy of a static site checks its _headers and _redirects before uploading anything. A rule the platform would refuse stopped the deploy only after the changed files had gone up and counted toward your storage.
  • forculus promote of a version that failed says it didn’t deploy and why. It said to run forculus rollback, which then refused it. A version that’s already live, or still going up, says so too.
  • forculus deploy to a workspace or app name that can’t be one, like acme/My_App, stops straight away and says what a name can hold, before looking at your project. Yun looks sad only when the platform turns a deploy down, not over a typo or something that isn’t there.
  • forculus files rm of a name the app doesn’t have says nothing was deleted, rather than “Deleted”. forculus files ls says its times are UTC.
  • forculus webhook test on a Slack channel that Slack answered 404, 410 or 403 says what that means in Slack: the webhook or its channel is gone, the channel is archived, or Slack won’t let it post there.
  • Sharing an app you can’t see says there’s no such app you can see and how to list the ones you can. It said only the app’s owners and admins could share it. Unsharing someone the app isn’t shared with no longer starts “not found:”, and points to forculus access <app>.
  • forculus schedule add and the MCP server’s add_schedule with a shortcut like @daily say shortcuts aren’t read and give the five fields to write instead. They said the schedule had one field.
  • The MCP server’s rollback, used to put a preview live, says the app is now on that version, not back on it: a preview was never live.
  • forculus agent create says what to run next with the agent’s name, as the other agent commands do, rather than its id.
  • forculus deploy --preview says a preview answers until the app’s next preview, whoever puts it up, not until “your” next one.
  • --help fits an 80-column terminal. Each paragraph was one line, up to 241 columns, which the terminal broke again mid-word.
  • forculus schedule on for a schedule we turned off, because whoever added it can no longer edit the app or for single sign-on, says to add it again. It said the schedule would run, and the platform turned it off again a minute later.
  • forculus audit says whether a schedule was turned on or off.
  • forculus audit names the agent an app was granted to, or a credential was minted for or revoked from, as release-bot (ag_…), the way it names an agent it made or ended.
  • forculus audit says the role a member or a grant had before a change, as bo@example.com as admin, was owner, and which limit a plan.limit entry hit, as audit_export, not on free.
  • forculus group add says when the person is already in the group, rather than that it added them, and names the group as “the finance group”.
  • forculus deploy of an app that isn’t there, without --create, gives the command with --create that makes it.
  • forculus curl writes the body out as it arrives, so an event stream shows each event as it’s sent. An answer that stops short, or a stream that stops because your session ended, is an error that says so. It printed 200 OK and exited 0.

Added:

  • forculus share <app> @acme.com shares an app with everyone whose address is exactly at that domain, as viewers. You can share only the domain your own address is at, and never a free mail provider’s or a public suffix.
  • forculus share <app> --group <name> shares an app with one of the workspace’s groups, as viewers. forculus group creates, lists and deletes groups and adds and removes their people, who needn’t be in the workspace.
  • forculus unshare takes @<domain> and --group the same way. When someone you unshare can still open the app through their domain, a group or the whole workspace, it says which.
  • forculus access <app> lists the domains and groups an app is shared with, after the people.
  • forculus workspace remove says which apps the person’s domain still lets them open.
  • forculus mcp’s app_status shows the domains and groups an app is shared with, and set_access names what still lets someone in after their access is removed.
  • forculus domain add, list and remove give an app a custom domain, like app.example.com. add prints the records to add at your DNS provider: a CNAME that points the name at us, and a TXT record that proves it’s yours. The domain works once we’ve seen both and its certificate is issued. The MCP server has the same as add_domain, list_domains and remove_domain.
  • forculus agent create|list|grant|revoke|token|end: agents are callers of a workspace’s own, each reaching only the apps granted to it with fca_ credentials of its own, and named as itself in the audit log. --json on each.
  • The MCP server has list_agents, create_agent, set_agent_access and end_agent. None of them mints a credential, so a secret never passes through an agent’s conversation.
  • forculus audit, watch and top name an agent as agent:<id>.
  • forculus egress list|allow|deny|on|off <workspace>/<app> holds an app to a list of hosts it may send requests to. allow takes a host like api.stripe.com or a pattern like "*.stripe.com". Only workspace owners and admins can add a host or turn the list off.
  • forculus ai shares the workspace’s Anthropic or OpenAI key with apps, each up to a monthly budget: forculus ai key set <workspace> anthropic stores it (pasted at a hidden prompt or piped in), forculus ai grant <workspace>/<app> anthropic --budget 25 lets an app use it, and forculus ai keys, show and revoke list, show and take it away.
  • forculus webhook add|list|remove|test sends a workspace’s events to a URL of yours, signed in the Forculus-Signature header, or with --slack to a Slack channel, whose URL you paste at the prompt or pipe in so it stays out of your shell history. add shows the signing secret once, and test sends a test event now and says how it went. For owners and admins.
  • forculus deploy --preview puts a Worker app or a static site up at a preview address of its own, p-<eight characters>--<app>, and leaves the live version as it is. Only people the app is shared with can open it, for 7 days or until the next preview. forculus versions shows each preview’s address and whether it still answers.
  • forculus promote <app> <version> puts a preview live, as it is.
  • forculus schedule add|list|remove|run calls a path on an app on a schedule, as five cron fields in UTC. Each run is signed as the scheduler: kind service, with a sub that starts with scheduler:.
  • forculus schedule on and off turn a schedule on and off. One we turned off because its maker can no longer edit the app, or for single sign-on, is added again instead.
  • The MCP server’s deploy_app takes preview: true, and list_schedules, add_schedule and remove_schedule manage an app’s schedules.
  • forculus files ls|get|put|rm <workspace>/<app> lists, downloads, uploads and deletes the files an app keeps, where the platform has storage on. Owners can do all four, admins can list and delete. get saves under the file’s own name, or --out, or --stdout, and won’t replace a file that’s already there unless you pass --force. put takes up to 25 MB, and - reads stdin. For get and put, a name with anything that would need encoding is refused before anything is sent. rm takes any name the app wrote, spaces and all, so you can always make room. Only a name with a . or .. part can’t be deleted this way. ls shows control characters and anything else outside plain ASCII in a name escaped, so a name an app wrote can’t act on your terminal.
  • forculus deploy takes a deploy credential from FORCULUS_DEPLOY_TOKEN and uses it in place of a login. A GitHub Actions run gets one by trading its OIDC token, which the deploy action in actions/deploy does for you, so nothing is stored in the repository. The credential deploys the one app it was made for and nothing else, so --create isn’t taken with it. Nothing the CLI starts (your build, wrangler, docker) gets FORCULUS_DEPLOY_TOKEN in its environment, so a build can’t bake it into the app. FORCULUS_DEPLOY_TOKEN_FILE names a file holding it instead, which the deploy reads and deletes before it starts anything, so the credential is in no process’s environment at all. The GitHub action hands it over that way. A file that doesn’t hold a deploy credential, or is a link, is left alone and the deploy stops.
  • forculus versions names the GitHub Actions run that made a version, beside the person whose permissions it had.
  • forculus app mcp <workspace>/<app> on lets AI clients such as Claude and Cursor connect to an app that is an MCP server. Each signs in as someone the app is shared with, who approves it in a browser, and reaches only that app, as them. off disconnects every one of them, from its next request, and turning it on again brings none back. forculus app show says when AI clients can connect.
  • forculus access <app> ends with how many people are waiting for access, when you can share the app and anyone is, and points to forculus access <app> requests.
  • forculus mcp install cursor puts FORCULUS_CONFIG_DIR in the server’s env when the config directory isn’t the default one, from the environment or --config-dir, and says which. Cursor doesn’t have your terminal’s environment, so the server it started read the default config’s session instead. That directory holds the platform you signed in to, so FORCULUS_API isn’t written.
  • Yun, our cloud, says hi over forculus --help and a bare forculus, after you sign in, after forculus init writes a template and at an app’s first deploy. Yun naps when you sign out or nobody is signed in, and looks a little sad when the platform turns a deploy down. Yun goes to stderr, and only when stdout and stderr are both a terminal: never with --json, NO_COLOR or CI set. FORCULUS_NO_MASCOT=1 turns Yun off. The MCP server never draws Yun.

Changed:

  • forculus deploy ends with “Version 3 (…) is live.” and the app’s address, rather than “Deployed version 3 (…).”. For an app that’s taken down it says the version is saved, but not served until the takedown is lifted.
  • forculus share, forculus access approve and the MCP server’s set_access say someone can now use the app “as a viewer” or “as an editor”, rather than “as viewer”.
  • forculus deploy --dry-run ends with “That was a dry run, so nothing was deployed.”, and signing in with no workspace yet says to make one, a little shorter.
  • forculus access <app> --json now answers {"grants": [...], "domains": [...], "groups": [...]} rather than a bare list of people, so the domains and groups an app is shared with show too. People are under grants, as before.
  • In forculus audit --json, app.database_exec and app.database_exec_refused entries from 3 October 2026 on have a keyed hash of the statement in sha256, and say which key made it in hash, like "hmac-v1:k1". Before, sha256 was a plain SHA-256 of the statement, with no hash, so an old entry never matches a new one, and hashing a statement yourself won’t find it. Entries whose hash differs never match either.
  • A forculus deploy whose connection drops before the platform answers looks for the new version, as it does for a deploy that takes too long, instead of failing with what the TLS library said.
  • A deploy that made its app and then failed deletes the app again only once the platform says none of its versions is live or on its way. When the deploy may have gone through, or nobody can tell, the app is kept and the error says so, with forculus versions to see whether it went live. That error is one sentence for a deploy that timed out or that a gateway gave up on too. It said where to look twice, with the connection’s own error in between. A deploy that failed before it sent its version, uploading files or pushing an image, deletes the app straight away as before: nothing that could go live had left your computer.
  • A deploy whose connection drops stops looking for its version after 20 seconds when none has turned up, rather than 3 minutes: a drop can come before the deploy reached the platform.
  • A deploy that made its app no longer tries to delete it after a failure when the app is already gone, or out of your reach. The error said to delete an app that wasn’t there. One out of your reach is still there, and the error says so.
  • A version on its way live is looked at every second for its first 15 seconds, then every 5, so a deploy that goes live a moment after the platform answers says so a moment later.
  • forculus rollback, forculus env set and forculus env remove whose connection drops, or that the platform doesn’t answer in time, say the change may have gone through and how to check, instead of what the TLS library said. So do the agent’s rollback and set_env.
  • forculus access approve <app> <id> and deny <app> <id> for a request that’s already decided, or was never there, say so, or that there’s no open request with that id, and how to list the open ones (forculus access requests <app>). They said the app wasn’t found and offered to create it. deny names the person it turned down, not the request’s id.
  • forculus deploy --create that can’t delete the app it made after a failed deploy no longer puts a full stop after the platform’s reason when it already ends in one, a ?, a ! or an ellipsis, inside closing quotes or brackets or not. A reason ending in a colon gets a full stop in its place, one ending in a link is followed by a new line rather than running on into how to delete the app, and no reason at all leaves no stray stop.
  • forculus deploy says “Deploying 1 file”, not “1 files”, and a workspace at a plan’s limit of one app “has 1 app”.
  • forculus init names your workspace in the deploy command it ends with when you’re signed in and in only one. Otherwise it still says <workspace>.
  • forculus audit --csv prints the oldest entry first, the same order as the dashboard’s download.
  • forculus audit without --app, refused to an app’s owner, says to try --app with the app’s name: an owner can read the log of the apps they own. forculus watch <workspace> says to watch <workspace>/<app>, and the MCP server’s audit_log says to pass app.
  • The static-site template from forculus init has a 404.html. A path with no page gets it, with a 404 status, instead of the front page with a 200.
  • forculus curl says so when a redirect came back with no Location. The gateway now drops every redirect that would leave the app’s own /<workspace>/<app>, to another site too, so a token never follows one onto another app. An app that hands a script a URL somewhere else, like a signed download link, returns it in the body instead.
  • Taking away something that isn’t there says what’s missing rather than that the app or the workspace is: sharing with a group that doesn’t exist, unsharing a group or a domain the app isn’t shared with, removing a custom domain, a schedule or an outbound host the app doesn’t have, adding to or removing from a group, revoking an agent’s credential that’s already revoked, and taking an app from an agent that never had it.
  • forculus agent end asks before it ends an agent for good, and with no terminal to ask at needs --yes. The agent commands call an agent by its name, as you typed it, and say “credentials”, not “tokens”.
  • A viewer’s forculus deploy, or --preview, says who can deploy the app before it detects anything. Granting an app to an agent without the role for it says who can.
  • forculus deploy --json --preview puts the preview’s address in url and the live app’s in live_url, the way the MCP server’s deploy_app does.
  • forculus promote of a preview pushed out of the newest 20 versions says it’s too old to promote.
  • forculus audit names an agent and the credential it used, the scheduler by its schedule, and whose request for access was approved or turned down.
  • forculus schedule list says off (maker_cannot_edit) for a schedule turned off because its maker can no longer edit the app, and off (sso_required) for one single sign-on turned off.
  • forculus access requests keeps the line breaks in a note.
  • forculus share <app> *.acme.com says it isn’t a domain, and forculus share <app> acme.com says to put @ in front. Both said it wasn’t a valid email address. The usage line of share and unshare shows --group, and unshare says the address as the platform keeps it.
  • forculus curl says a refusal came from Forculus rather than “our edge”, and names your workspace in what to run next. forculus webhook add --slack names it too, not <workspace>.
  • forculus ai show says “1 request”, not “1 requests”.

Security:

  • With FORCULUS_DEPLOY_TOKEN set, forculus deploy sends it to the API FORCULUS_API names, or to https://api.forculuslabs.tech, never to the API this machine is signed in to.
  • Nothing the CLI starts gets ACTIONS_ID_TOKEN_REQUEST_TOKEN or ACTIONS_ID_TOKEN_REQUEST_URL, so a build’s dependencies can’t ask GitHub for a token of their own in a deploy job.
  • A deploy now stops on a file that holds one of our own tokens (a deploy credential, a bypass token, a session or a push token), not only a CLI session.

Upgrading:

  • On a network, or behind a proxy, that cuts a request after 5 seconds with nothing back, the platform now answers a deploy from 0.1.2 or later in time, and the CLI follows the rest. 0.1.0 and 0.1.1 still wait in the request for a first site to go live, so there they can lose the connection and delete the app they made a moment before. Upgrade if you’re on either.

Added:

  • forculus uninstall takes the CLI off your computer. It revokes the bypass tokens your agent made here and deletes their secret files, ends the session, and deletes the config directory, leaving anything in it the CLI didn’t write. If the install script put forculus there, it deletes the program and takes out the lines the script added to your shell’s startup files, and nothing else. For Homebrew, npm, cargo and Windows it prints what to run. It takes forculus out of the project’s .cursor/mcp.json and says how to take it out of other agents. It asks first, or takes --yes. --keep-config keeps your settings. Your apps and workspaces aren’t touched.
  • forculus init <template> [dir] starts an app from a template, ready to deploy: static-site, worker, internal-tool (a Worker with a database), express or fastapi. It writes into ./<template> unless you name a directory, never replaces a file that’s already there, and ends with the forculus deploy command to run next. forculus init on its own lists the templates.
  • forculus mcp has list_templates and create_from_template, so an agent can go from a sentence to a deployed app. A template is written only where a deploy could read from, by the same rules as deploy_app, and never over a file. Neither needs anyone signed in.
  • forculus access <app> requests lists the open requests for access to an app, with what each person wrote. forculus access <app> approve <id> shares the app with them as a viewer and emails them its link. deny turns a request down without telling them.
  • forculus audit --csv prints the log as CSV, with the same columns as the dashboard’s download. A cell that a spreadsheet would read as a formula is written so it reads as text.

Added:

  • forculus mcp install cursor sets Cursor up for the project you run it in. It writes the project’s .cursor/mcp.json with this forculus’s full path, so Cursor finds it even when it was opened from the Dock, which doesn’t have your terminal’s PATH. Running it again changes nothing. Other servers in the file are kept, and a file that isn’t valid JSON is left alone.

Changed:

  • Every error reads as a sentence. The CLI’s own words start with a capital, every message ends with a full stop, and a name at the start, like a workspace’s, is never capitalised.
  • forculus deploy and forculus detect no longer take from in a string, a comment or a regular expression for an import. A Worker with "comes from" in it was refused for a package called + that it never imported.
  • forculus logs --version 3 finds version 3’s lines when version 4 wrote a lot in its first minutes, and refuses a version the app never had. A read with no lines says “in the last hour”, not “since 1h”, and how far back the plan lets you read, not how long it keeps lines: every plan’s lines are kept 7 days. A viewer is told once who can read the logs.
  • forculus app show lists the paths anyone can reach without signing in, and which of them have no signature check. --json has them as public_paths.
  • forculus audit puts (deleted) after a deleted app’s name on every entry about it, and forculus watch does on requests too. An env removal reads removed, not unset, and an allowed request reads access allow, without (allowed).
  • forculus curl says plainly that a 401 or 403 without our edge’s X-Forculus-From header came from your app.
  • forculus db export says “Exported the database of acme/notes to acme-notes.sql (48,213 bytes)”, with the size grouped by thousands.
  • forculus workspace usage says storage over the limit in GB-months, the unit it’s priced in, rounded as the Billing tab rounds it. A meter we haven’t priced yet says that’s ours to fix, the usage limit rule is said once in plain words, and requests with no CPU measured say only their CPU time is free. A limit lowered partway through an hour is said to still count that hour against the higher one, and apps paused at the default $0 limit are said to have run out of the plan’s allowances, not its usage.
  • forculus workspace usage on a subscription made at an older price says it keeps the prices it started with, usage prices included, until the workspace changes plan. It showed $199 with nothing to say why the pricing page says $215.
  • forculus workspace usage --previous for a short time on Free before a plan began says when it was, to the minute, instead of calling it last month. An empty table says no app used anything, not that the workspace has no apps.
  • --driver takes worker and container as well, and its help says which kinds each runs.
  • forculus detect calls a JavaScript monorepo’s root that, not a workspace.

Agents:

  • Refusals read as sentences, with a capital and a full stop, and never capitalise a workspace’s name.
  • set_visibility says who can reach the app, as the CLI does.
  • call_app, send_to_app and set_env take headers and variables as an object of names and values too, and any other shape is refused with both shapes shown.
  • app_logs refuses a version the app never had, finds a version’s lines behind the next one’s, and says when a short page has older lines to read on to. Someone who can’t read the logs is told who can, as forculus logs says it, not the platform’s “You don’t have permission to do that.”
  • app_status lists the paths anyone can reach without signing in.
  • call_app says a 401 or 403 without our edge’s mark came from the app.
  • workspace_usage says storage in GB-months and names a short time on Free before a plan as that. It says a subscription made at an older price keeps it, and words the usage limit as the CLI does.

Changed:

  • After a deploy or rollback that replaces a live Worker version, forculus says the old version may answer some requests for a minute or two, not up to a minute. env set and env remove say the same of when the live code sees the change.
  • forculus logs labels each line with the version that wrote it, not the one that was live, so --version shows the lines that version wrote. A line we can’t tell, such as a container’s own output, still gets the version that was live when it was written.
  • forculus app list on an empty workspace tells an owner or admin it has no apps yet, and tells a member there may be apps not shared with them.
  • forculus rollback to a version the app doesn’t have says the app has no such version, and points at forculus versions.
  • forculus workspace usage shows the default usage limit as $0 a month, as the dashboard does, and says whether usage before the billing month began in its first hour was on Free, and so on no invoice, or on the last month’s invoice.

Agents:

  • rollback, deploy_app and set_env say a replaced version may answer for a minute or two.
  • app_logs gives each line the version that wrote it, and its version filter means lines that version wrote.
  • rollback to a missing version says the app has no such version.
  • workspace_usage says the default usage limit is $0 a month, and whether usage in the billing month’s first hour was on Free or on the last month’s invoice.

Changed:

  • A container deploy removes the image it built from Docker however it ends: pushed, failed, or stopped with Ctrl-C. It says when the image is up and it’s waiting for the platform.
  • Ctrl-C before a deploy’s version is sent says nothing was deployed, deletes an app --create made for it, and exits 130.
  • forculus detect, forculus dockerfile and deploy --dry-run no longer say a container has a Dockerfile when it has none. The dry run refuses what the deploy would, and detect exits 1 and gives the reason in --json as problem.
  • forculus --json logs lists lines oldest first, like the text. --follow looks back a minute, since Cloudflare can take half of one to make a line readable.
  • Not being logged in has a --json code: not_logged_in.
  • forculus secret set checks the name, --host, --header and --format before it asks for the value, and names the flag that’s wrong. It tells a header that isn’t one from one that can’t carry a secret, suggests the hostname for a host with a port, and gives these errors the --json code invalid_argument.
  • forculus rollback to the version already live says so.
  • forculus token list and forculus app list line up their columns, a small site’s size is <1 KiB, and forculus unshare says plainly when the app isn’t shared with someone.
  • An app the platform took down says so in app show, app list, deploy and env set: a deploy is kept but not served until it’s lifted. A deploy says why once, before it builds.
  • forculus token revoke and forculus app delete delete the files the MCP’s create_bypass_token wrote the tokens’ secrets to, as the MCP’s own tools do. Only files for the platform you’re signed in to go.
  • A workspace member at the plan’s app limit is told to ask an owner or admin, with no billing link, as the platform now tells them.
  • An app that isn’t found says it may not be shared with you, and offers --create only when no app has the name.
  • forculus audit --app finds a name’s deleted apps too, and marks them (deleted).
  • forculus workspace usage shows what a plan switch holds apart from the old plan, and --previous on a workspace made after that period says it has nothing to show.

Agents:

  • revoke_bypass_token and delete_app delete the files create_bypass_token wrote the tokens’ secrets to. create_bypass_token writes no file for an id that isn’t a token id, and revokes that token.
  • A missing app points at deploy_app with create_if_missing, list_apps and whoami, and delete_app on one says it may be deleted already. set_access says once that someone has to join the workspace, and call_app answers every platform 404 as an error.
  • A refusal starts with a capital, and one a member can’t act on says to tell the person rather than offer an upgrade.

Security:

  • Names can’t hide characters any more. forculus refuses bidi controls, zero-width and every other invisible character in workspace, app and token names, and prints any stored before without them. A zero-width joiner stays only where it changes what shows, as in an emoji or a script that joins its letters, so one name can’t pass for another. The England, Scotland and Wales flags are allowed. forculus audit and forculus watch strip control and bidi characters from what they print.
  • An email address with a hidden character in it is shown with that character escaped, like ceo\u{200b}@acme.com, wherever the CLI and the MCP tools print one, so it can’t pass for another address.
  • A bypass token given to forculus curl goes only to the apps of the platform FORCULUS_API names, or the one you’re signed in to. Before, with no login it went to production’s apps whatever FORCULUS_API said. When the CLI doesn’t know where that platform’s apps are, it doesn’t send the token and says to set --apps-url.
  • An app that isn’t shared with you answers 404, the same as no app, so a refusal no longer tells you it exists.
  • forculus audit and forculus watch show a refused or unknown bearer token as an unknown token, never as the platform.
  • forculus curl refuses gateway paths that aren’t UTF-8, and NUL.

New:

  • forculus workspace usage shows this period’s requests, CPU time, database rows and storage, what they cost past the plan, and where Free stops. --previous shows the last period. The MCP server has the same as the read-only workspace_usage tool.
  • forculus workspace usage says your plan and what it costs a month, and when a switch to another plan is still going through. Apps deleted in the period stay in its table, marked (deleted). When a period began partway through an hour, it says why the apps can add up to more than the meters.
  • forculus workspace usage and forculus whoami say when a Free workspace’s apps are paused for the month.
  • forculus logs leaves the platform’s own lines out unless you pass --platform, and reads a container’s log levels.
  • forculus watch names the app on each line.
  • forculus login says what to do when the sign-in page emails you a code and it doesn’t come: check spam, ask for a new one, then write to support@forculuslabs.tech.
  • When the platform refuses you an app in a workspace you own or run, forculus curl and call_app say the app is there and give the forculus share command that lets you in.
  • Deploys: bun and deno apps, VitePress sites built from the root their script builds, uv and poetry apps run from where their packages went, and chained start commands. A deploy that can’t build or start is refused before it uploads, and a failed docker build shows the step’s output.

Changed:

  • A deploy follows its version until it’s live and says which version it replaced. If Ctrl-C stops the wait, it says whether the version went live, failed or is still going. deploy_app now says too that the old version can answer for a moment longer.
  • A container deploy removes the image it built from Docker once it’s pushed. A failed push leaves it.
  • Python images install packages before copying your code, so an edit no longer reinstalls everything and pushes every layer again. A requirements.txt that installs local wheels, or uses --find-links or --no-index, builds.
  • forculus audit names deleted apps on every entry, and --app takes a deleted app’s name. It names the recipient, the statement and the rollback, says whose version the platform put back and which, and what a refused removal would have taken.
  • forculus watch --denies and forculus top ask the platform for refusals alone, so a busy workspace’s refusals no longer go missing. Neither skips entries that reached the log together.
  • forculus watch --tail N counts only the lines it prints, and --tail 0 prints none.
  • forculus curl starts the status on its own line when stdout and stderr share a pipe, on Windows too.
  • A refusal from the platform says so, with its cause. A 502 after dispatch no longer blames the app. A 404 says the app may not be shared with you, and who to ask.
  • forculus env set on a static site says the change comes with its next deploy.
  • A deploy refused for the plan’s app count suggests an upgrade only to a plan with room.
  • create_bypass_token takes any token name the platform takes, such as Café ci.
  • forculus token list says a token opens nothing once its maker no longer has an account.
  • The audit log’s archive paging is followed in forculus audit and the MCP tools.

Security:

  • forculus curl and the MCP tools call_app and send_to_app refuse any path that climbs out of the app they name (.., %2e%2e, ..%2f and the other spellings). Before, one app’s path could reach another app you can open.
  • forculus mcp refuses to deploy from a hidden directory anywhere in the path, the project root included.

New:

  • forculus logs, and the app_logs tool, read an app’s recent output.
  • forculus db exec runs one statement on an app’s database.
  • forculus app list --shared lists apps people have shared with you. list_apps includes them.
  • Container redeploys roll out instead of only patching the running container.

Agents:

  • deploy_app answers within 45 seconds, or the wait_seconds it’s given, up to 240. A deploy still going carries on in the server, and the new deploy_status waits for it. Codex stops a call after five minutes, and a plugin can’t give it longer.
  • Started in a plugin’s own directory, as Codex and every Agent Plugins client start it, forculus mcp never deploys from there. It goes by PWD, the directory the client was started from, and otherwise says to set FORCULUS_MCP_ROOTS.

Deploys:

  • A plain Worker can import npm packages. It’s bundled with wrangler, like TypeScript.
  • A static site’s own 404.html is served for paths with no file.
  • SvelteKit and Astro apps no longer fail on Cloudflare’s disabled default cache. A SvelteKit app without the Cloudflare adapter is told which adapter to add.
  • Go and Rust images carry the project’s files beside the binary. Go builds use Go 1.27 and fetch a newer toolchain when go.mod asks for one.
  • Generated container images run under tini, so they stop cleanly.
  • A failed Docker build shows Docker’s last lines. An app that --create made is deleted again when its deploy fails.
  • The wrangler config is checked before the build, and the plan’s app count before a --create build.

Smaller:

  • Tokens default to 30 days, the most a CLI session gives them. token list says when a token’s maker caps what it can do.
  • forculus audit lines say who did what to which app.
  • Clearer messages for env changes, ports, credentials in a build, and the terms to accept.
  • MCP tools have titles, name each other rather than CLI commands, and mark app output as data in structured results too.

First release, so there is nothing to upgrade from.

@forculus/sdk on npm. Its docs.

  • requireUser and verifyIdentity refuse a token whose aud is a list, even one holding the app’s id. The edge writes one string, and requireWebhook already refused a list.
  • An issuer or key set on http is refused unless env.FORCULUS_DEV is "1", and even then only on this machine (127.0.0.1, localhost or [::1], and for the keys host.docker.internal too). forculus dev sets it for the app it runs, from CLI 0.1.11. The platform’s issuer and keys are https and it never sets FORCULUS_DEV, so a deployed app that still points at forculus dev refuses everyone, and asks nobody for keys.
  • envFromProcess() reads FORCULUS_DEV with the rest.
  • An app that checked tokens against keys of its own over http, outside forculus dev, is now refused. That is why this is 0.3.0.
  • A caller has agentName: the name of the workspace’s agent that made the request, or null for a person, a bypass token or a scheduled job. isAgent(user) says whether an agent called. Decide what an agent may do by sub, which is agent:<agent id>. The name can change.
  • An agent’s identity token with no name, an empty one, or an empty agent id is refused, as a person’s with no email is. The platform always sends one.
  • User has a new required field, so code that builds a User by hand (in tests, say) needs agentName: null. That is why this is 0.2.0.
  • The README is rewritten in plain words. Nothing in the SDK’s behaviour changed.
  • The Express adapter’s 401 and 403 carry Cache-Control: no-store, as toResponse() already did.
  • A 403 says forbidden: this needs editor as plain text, the same body every SDK answers, rather than forbidden.

The first version on npm. 0.1.0 was tagged but its publish failed, so it was never released. The code is the same as 0.1.0 below.

First release, so there is nothing to upgrade from.

  • requireUser and verifyIdentity check the identity token the platform sends with a person’s or a bypass token’s request, with Express, Hono, Next and node:http adapters.
  • requireWebhook and verifyWebhook check the token it sends with a webhook delivery whose signature it verified, and name the provider. The adapters are requireWebhook for Express and Next, webhook and getWebhook for Hono, and requireWebhookFrom for Node’s own headers. Each kind of token is refused where the other is expected.

forculus on PyPI. Its docs.

  • require_user and verify_identity refuse a token whose aud is a list, even one holding the app’s id. The edge writes one string, and require_webhook already refused a list.
  • A request with two X-Forculus-Identity headers is refused, as the Node SDK refuses it. It read the first.
  • An issuer or key set on http is refused unless FORCULUS_DEV is 1, and even then only on this machine (127.0.0.1, localhost or [::1], and for the keys host.docker.internal too). forculus dev sets it for the app it runs, from CLI 0.1.11. The platform’s issuer and keys are https and it never sets FORCULUS_DEV, so a deployed app that still points at forculus dev refuses everyone, and asks nobody for keys.
  • Config has dev, which Config.from_env reads from FORCULUS_DEV. A Config with an http issuer or key set raises Unauthorized when it’s made, unless dev is true. That is why this is 0.3.0.
  • A Config made with an empty app id, issuer or key set URL raises Unauthorized saying they must be set, as Config.from_env does, rather than that they must use https.
  • User has agent_name: the name of the workspace’s agent that made the request, or None for a person, a bypass token or a scheduled job. user.is_agent says whether an agent called. Decide what an agent may do by sub, which is agent:<agent id>. The name can change.
  • An agent’s identity token with no name, an empty one, or an empty agent id is refused, as a person’s with no email is. The platform always sends one.
  • agent_name defaults to None, so code that builds a User by hand keeps working.
  • The README is rewritten in plain words. Nothing in the SDK’s behaviour changed.
  • A 403 says forbidden: this needs editor as plain text, the same body every SDK answers: Flask and Django said forbidden: editor as HTML. FastAPI’s is that text as its JSON detail.

First release, so there is nothing to upgrade from.

  • require_user and verify_identity check the identity token the platform sends with a person’s or a bypass token’s request, with Flask, FastAPI, Django, Streamlit and Gradio integrations.
  • require_webhook and verify_webhook check the token it sends with a webhook delivery whose signature it verified, and name the provider. The integrations are webhook_required and get_webhook for Flask, the webhook dependency for FastAPI, and webhook_required for Django, which exempts its view from the CSRF check. forculus.aio has both calls. Each kind of token is refused where the other is expected.

the forculuslabs.tech/sdk module. Its docs.

  • RequireUser and Verify refuse a token whose aud is a list, even one holding the app’s id. The edge writes one string, and RequireWebhook already refused a list.
  • A request with two X-Forculus-Identity headers is refused, as the Node SDK refuses it. It read the first.
  • An issuer or key set on http is refused unless FORCULUS_DEV is 1, and even then only on this machine (127.0.0.1, localhost or [::1], and for the keys host.docker.internal too). forculus dev sets it for the app it runs, from CLI 0.1.11. The platform’s issuer and keys are https and it never sets FORCULUS_DEV, so a deployed app that still points at forculus dev refuses everyone, and asks nobody for keys.
  • Config has Dev, which ConfigFromEnv reads from FORCULUS_DEV. NewVerifier returns an error for an http issuer or key set unless Dev is true. That is why this is 0.3.0.
  • User has AgentName: the name of the workspace’s agent that made the request, or empty for a person, a bypass token or a scheduled job. user.IsAgent() says whether an agent called. Decide what an agent may do by Sub, which is agent:<agent id>. The name can change.
  • User marshals agent_name too, null when empty, as the other SDKs write it.
  • An agent’s identity token with no name, an empty one, or an empty agent id is refused, as a person’s with no email is. The platform always sends one.
  • A User written as an unkeyed literal (User{"user", ...}) no longer compiles. Name the fields.
  • The README is rewritten in plain words. Nothing in the SDK’s behaviour changed.
  • Requires answers its 403, forbidden: this needs editor, with no newline after it, the same body every SDK answers.

First release, so there is nothing to upgrade from.

  • RequireUser, Verify, Middleware and Requires check the identity token the platform sends with a person’s or a bypass token’s request.
  • RequireWebhook, VerifyWebhook and WebhookMiddleware (with WebhookFrom) check the token it sends with a webhook delivery whose signature it verified, and name the provider. Each kind of token is refused where the other is expected.
  • User and Webhook marshal to JSON with the names the Python and Rust SDKs use: kind, sub, email, role and workspace_id, and provider, pattern, delivery, signed_at and workspace_id. An empty email or delivery id is null, and signed_at is unix seconds or null.

the forculus crate. Its docs.

  • require_user and verify refuse a token whose aud is a list, even one holding the app’s id. The edge writes one string, and require_webhook already refused a list.
  • A request with two X-Forculus-Identity headers is refused, as the Node SDK refuses it. It read the first.
  • An issuer or key set on http is refused unless FORCULUS_DEV is 1, and even then only on this machine (127.0.0.1, localhost or [::1], and for the keys host.docker.internal too). forculus dev sets it for the app it runs, from CLI 0.1.11. The platform’s issuer and keys are https and it never sets FORCULUS_DEV, so a deployed app that still points at forculus dev refuses everyone, and asks nobody for keys.
  • Config has a new public field, dev, which Config::from_env reads from FORCULUS_DEV. Verifier::new refuses an http issuer or key set unless it’s true. A Config { .. } literal needs dev: false. Config is Default now, so ..Default::default() works too. That is why this is 0.3.0.
  • User has agent_name: the name of the workspace’s agent that made the request, or None for a person, a bypass token or a scheduled job. user.is_agent() says whether an agent called. Decide what an agent may do by sub, which is agent:<agent id>. The name can change.
  • An agent’s identity token with no name, an empty one, or an empty agent id is refused, as a person’s with no email is. The platform always sends one.
  • User has a new public field, so a User { .. } literal needs agent_name: None. That is why this is 0.2.0.
  • The README is rewritten in plain words. Nothing in the SDK’s behaviour changed.

First release, so there is nothing to upgrade from.

  • Verifier::require_user and Verifier::verify check the identity token the platform sends with a person’s or a bypass token’s request. The axum feature adds the Forculus layer and the User extractor.
  • Verifier::require_webhook and Verifier::verify_webhook check the token it sends with a webhook delivery whose signature it verified, and name the Provider. The axum feature adds the ForculusWebhook layer and the Webhook extractor. Each kind of token is refused where the other is expected.

@forculus/client on npm. Its docs.

  • A share or a new member’s reply says which day’s budget stopped the invitation email, when one did: budget_spent is workspace, sender or platform, beside invited: "over_budget".
  • A share in the role the person already had says unchanged: true. Nothing changed and nothing was logged.
  • First release. A client for the Forculus API: workspaces, members, apps, versions, deploys of Workers and static sites, sharing, bypass tokens, the audit log, secrets and config values.
  • Signs in with a CLI session: token, FORCULUS_TOKEN, or the one forculus login saved.
  • Tries reads again after a failure on our side or a 429, and never sends a change or a deploy twice.
  • Sends a deploy’s lock files and .forculus-allow for its scan, when given them as scan.
  • Sends a saved CLI session only to the API it was made by.
  • Asks for the app’s key-value storage and queue with a Worker deploy, as forculus.json does: kv and queue.

forculus-client on PyPI. Its docs.

  • A share or a new member’s reply says which day’s budget stopped the invitation email, when one did: budget_spent is workspace, sender or platform, beside invited: "over_budget".
  • A share in the role the person already had says unchanged: True. Nothing changed and nothing was logged.
  • First release. A client for the Forculus API: workspaces, members, apps, versions, deploys of Workers and static sites, sharing, bypass tokens, the audit log, secrets and config values.
  • Trusts what the operating system trusts, through truststore, its one dependency.
  • Signs in with a CLI session: token, FORCULUS_TOKEN, or the one forculus login saved.
  • Tries reads again after a failure on our side or a 429, and never sends a change or a deploy twice.
  • Sends a deploy’s lock files and .forculus-allow for its scan, when given them as scan.
  • Sends a saved CLI session only to the API it was made by.
  • Asks for the app’s key-value storage and queue with a Worker deploy, as forculus.json does: kv and queue.