TreeMap
무료 오픈소스 디스크 사용량 시각화 도구로, 사각형 트리맵·중복 파일 탐색·디스크 가득 예측·정리 시 파일을 휴지통으로만 이동한다
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Cordis-first desktop environment for persistent conversational bots
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
FrockBot is an experimental application for persistent conversational bots. The hosted WebUI and cloud backend provide the product path.README 전문 보기 ↗FrockBot runs persistent conversational Bots. A Bot holds a conversation, calls tools, remembers things across sessions, runs on a schedule, browses and runs commands on a Computer of its own, and writes code that extends it. It is a Cloudflare application — Workers, Durable Objects, R2, Vectorize and Containers — with one Flutter client on the web and on the phone. Terms are defined in CONTEXT.md; how the system is built is in docs/architecture.md.
The simple deployment profile is FrockBot in your own Cloudflare account: Cloudflare Access sign-in, no billing, the Computer included, and nothing to customise (ADR 0028). It is the same code frockbot.com runs — see The hosted deployment for what the other profile adds.
Every release publishes the simple profile as a prebuilt deploy bundle: the three Workers, the web client, the application artifact and a manifest, installed through the Cloudflare REST API with no wrangler build, no Docker and no Flutter on your machine (docs/deploy-bundles.md).
Or skip the repository: frockbot.com/deploy signs you in with Cloudflare and deploys a pinned release to a workers.dev address in that account, with no domain and nothing built locally, then updates it with one click. How it works is in docs/hosted-deploy.md.
From a checkout, scripts/deploy-bundle.ts installs a release's bundle with an API token:
bun install
export CLOUDFLARE_API_TOKEN=… # Workers Scripts, R2, Vectorize, Containers; Workers Routes on the zone
bun scripts/deploy-bundle.ts deploy --bundle <version> \
--account <account id> --install <name> --hostname <app hostname> \
--access-team <team>.cloudflareaccess.com --access-aud <aud> \
--secrets <secrets.json>
It needs a Cloudflare account on the Workers Paid plan, a zone on it for the hostname, and a Zero Trust team whose Access applications you create yourself. The secrets file holds SPRITES_TOKEN (a Fly Sprites token for the Computer) unless you pass --no-computer-host, plus any optional key below; the internal secrets are minted into the same file, which is their only copy. The script's header lists every flag.
| Key | What it enables |
|---|---|
OPENAI_API_KEY | dictation in the composer |
GEMINI_API_KEY | the voice session on Gemini Live, the default provider |
FCM_SERVICE_ACCOUNT | push sent to FCM yourself; without it, push goes through the relay |
COMPOSIO_API_KEY | Connected apps: a Bot using your Gmail, Slack, Notion and the rest |
COMPOSIO_WEBHOOK_SECRET | Routines that fire on a connected-app event |
BRAVE_SEARCH_API_KEY | web search: a Bot that searches the public web |
DEBUG_TOKEN | the read-only /api/debug operator surface |
No model key is needed. Frock AI runs on the account's own AI binding, where Auto resolves to a concrete Workers AI chat model, so a deployment with no configuration at all still picks a model for a User who chose none.
Nothing is gated. Every line of both profiles is in this repository, and a self-hoster who sets a secret gets what it switches on; the deploy bundle simply never asks for one.
STRIPE_SECRET_KEY, which the deploy bundle never asks for. Set one by hand and billing turns on.flutter build. The release's frockbot.apk is that hosted app, with the hosted origin baked in, so it is not your phone app. Build your own against your own origin (docs/app-updates.md, apps/native/README.md); the update control never appears.assetlinks.json and apple-app-site-association the Worker serves name the apps in the profile's nativeApps, and the simple profile names none, so a client you build and sign yourself has no verified return path on your hostname until you add it there — the profile therefore enables no native sign-in targets, and the web client is the client.POST /api/debug/users/<userId>/features under the deployment's DEBUG_TOKEN.apps/marketing is frockbot.com and is hosted-only.After sign-in, General opens automatically on a device with no saved Bot selection, provided General is active, no Bot link is pending, and no page is open over the shell. This applies to both the browser and native app. Account provisioning and deletion follow the General bootstrap contract.
General's empty conversation offers suggestions to research a topic and recommend an action, plan and complete a project, set up a recurring check, or create a specialist Bot. Research appears when General has Web enabled; recurring checks appear when it has Routines enabled. While capabilities are unavailable or still loading, only the project and specialist suggestions appear. Choose a suggestion to fill the composer, edit the selected placeholder, and send when ready: choosing alone does not start work. You can also type your own message, or use Add a Bot to create another Bot.
bun install
bun run dev
The development launcher builds the client and the application artifact, then starts the local Cloudflare origin on port 8787. Building the client needs the pinned Flutter SDK on PATH; see apps/native/README.md.
The deterministic foundation provider runs without credentials. To use an OpenAI-compatible endpoint:
FROCKBOT_LLM_BASE_URL="https://api.example.com/v1" \
FROCKBOT_LLM_MODEL="model-id" \
FROCKBOT_LLM_API_KEY="..." \
bun run dev
FROCKBOT_LLM_API_KEY is optional for local endpoints. FROCKBOT_LLM_PROVIDER_ID customizes the provider label.
The left sidebar lists the authenticated User's Bots and switches the conversation. Add a Bot creates a Bot with a character, a name and the first thing to say to it; pressing the character in Bot settings opens the picker where its character and colour change. Hidden and archived Bots each fold into their own group at the foot of the list; an archived Bot opens read-only, its conversation kept, with Restore and Delete where the composer would be. A Bot's page — its Computer, its recent Routine firings and the doors of its Plugin panels — is the right panel at wide widths and a pushed page on the phone, opened from the Bot's name in the conversation bar; its settings are one level under it behind the gear, and the Bot's Plugins — what it could run and whether it does, one switch per row, each switch that Bot's own — are a row in those settings, ending with the Bot's setup history: each version of the Plugin code it has written, and whether it ran. The Marketplace opens from the Bot list as one searchable catalog of models and some 1,500 connected apps, from Gmail, Google Calendar and Slack to Shopify and Stripe. Checkboxes under the search box choose Models and Connectors; Installed is the same list limited to what has already been added, for configure and remove. Adding a model provider opens its key form, and a connected provider's card offers Choose a model. From the profile sheet, Settings owns the declared application settings and Models holds the account's default model and the providers already added; each Bot may choose its own model in its settings, and without a choice follows that default — Frock AI's Automatic until one is chosen. Built-in features such as Web and Routines are switched per Bot on that Bot's Plugins page; there is no account-wide switch for them. During an active Turn, Stop records cancellation intent; closing or switching clients does not stop backend work. Delete, on the profile page, deletes the Computer — the next Bot that needs one gets a new, empty one — or, once the person types the address they sign in with, the whole account and everything in it, at once.
@frockbot/providers/ollama-cloud lets each User create multiple named Ollama Cloud Connections with their own write-only API keys. It is disabled until added from the Marketplace. The backend validates and encrypts each credential and discovers that Connection's model catalog; connecting it does not change the platform model. Rotation affects subsequent model effects while already-admitted effects retain their credential lease, and disconnect prevents new leases without cancelling admitted Turns.
@frockbot/providers/frock-ai is the built-in credential-free model path. On a User's first configuration read its User Contribution idempotently installs and enables the Package, creates the ready ambient flock-ai-account Connection, and records @frock/auto as the platform model. On the hosted profile the runtime sends Auto through Cloudflare AI Gateway as dynamic/<FROCK_AI_AUTO_ROUTE> and manual @frock/... ids as workers-ai/@cf/..., behind one narrow streaming adapter; a deployment that names no Gateway takes the AI binding instead. No User secret enters FrockBot state; the Gateway credentials below are deployment configuration, held by the Worker and never by a User.
To attach the built-in Fly Sprites Computer provider Package, provide a Sprites token. The provider sits behind the provider-neutral Computer interface used by generic tools and memory. It provisions one persistent Sprite per User, shared by every Bot that User owns: each Bot receives its own directories and an on-demand Chromium/noVNC desktop slot, and every Bot on the Computer shares the one browser profile at /home/box/chrome-profile, so logins are a User-level asset. There is no separate User storage Sprite. FROCKBOT_SPRITE_NAME optionally selects the base name the User's Sprite name is derived from for standalone development; the hosted backend supplies durable User identity. The deployment's Computer host is registered under the id computer-host, and Fly is what implements it: apps/cloudflare/src/computer-host.ts is the one file that chooses, and nothing above it names an implementation. In a deployed stack the Sprites SDK and SPRITES_TOKEN live in apps/computer-host, which the Bot Durable Object reaches over the COMPUTER_HOST service binding; the app Worker keeps SPRITES_TOKEN only as the answer to "has this deployment a Computer at all".
SPRITES_TOKEN="..." \
FROCKBOT_SPRITE_NAME="frockbot-barebones" \
bun run dev
The client exposes Computer viewer and human-takeover controls: the Computer card (apps/native/packages/frockbot_client/lib/computer/) is the first thing on the Bot's page at every width, drawing the desktop live while the Bot is working and the Bot's last screenshot otherwise, and one press — on the card, the Computer icon in the conversation bar, or a search hit — opens the full-window viewer directly, where Take control and Release control live. The backend's token-routed viewer gateway serves each Bot desktop through the host's public HTTPS URL, and its Bot-scoped takeover lease blocks new process and browser actions while leaving durable Package file operations available. Shells start in /workspaces/<bot-key> with HOME=/home/box. Canonical Memory Markdown does not live on the Computer: the Memory Package is its single writer and writes object storage directly, and the Computer sees Memory roots read-only, so a Turn can read and write Memory with the Computer hibernated.
bun run format:check
bun run typecheck
bun test
bun run build
GitHub Actions runs these checks on pushes to main and on pull requests. Dependabot checks Bun/npm dependencies and GitHub Actions weekly, with one exclusion: wrangler is pinned by patchedDependencies in package.json to the patches/wrangler@<version>.patch that keeps its dev proxy from treating one dropped forwarded request as fatal (cloudflare/workers-sdk#15317). A bump detaches that patch silently, so wrangler is upgraded by hand and the patch re-created with bun patch against the new version.
bun run typecheck runs each package through a pool capped at min(4, cores/2)
rather than starting all 73 at once. One TypeScript 7 process already uses about
3.7 cores, so a higher cap costs memory and buys no wall-clock time. Override it
when you have the headroom:
TYPECHECK_CONCURRENCY=8 bun run typecheck # 0 means unbounded
Every package checks with TypeScript 7. Most declare typescript at ^7.0.2
directly. The rest — applets/sdk and the workspace root — depend on a tool
that embeds the TypeScript compiler API, which TypeScript 7's package does not
ship, so they alias typescript to the
typescript-native-bridge build that keeps the TS 6 JavaScript API while
checking on tsgo 7.0.2. No TypeScript 5 is left in the repo.
typecheck also runs the build-time choosing rules: scripts/check-computer-host-imports.ts keeps the Computer host's implementation behind its one choosing file, scripts/check-auth-package-imports.ts does the same for the auth Package, so a better-auth import cannot reach the Access build or an Access import the hosted one, and scripts/check-payments-package-imports.ts keeps Stripe inside its payments Package and each payments Package to the ledger port.
TypeScript 7 ships no tsserver; its language server lives inside the native
binary. Point your editor's TypeScript LSP command at:
bun scripts/ts-lsp.ts
Without this, editors fall back to whatever tsserver they can find — usually a
globally installed TypeScript 5.x, which is the wrong version and will grow
unbounded across long sessions.
Five layers, each answering a different question. The first four run in CI; the fifth never does.
| Layer | Command | What executes |
|---|---|---|
| unit | bun test | *.test.ts and *.spec.ts under Bun, in every workspace. Pure logic and doubles. |
| workerd | bun run --filter @frockbot/cloudflare test:workerd | test/**/*.workerd.ts in local workerd against a probe Worker, so Durable Objects and their storage are real. Hermetic. |
| integration | bun run --filter @frockbot/cloudflare test:integration | test/integration/**/*.integration.ts — SELF.fetch through the deployed gateway, the Worker Loader, and the built artifact. |
| e2e | bun run --filter @frockbot/cloudflare test:e2e | e2e/**/*.e2e.ts — real Chromium against wrangler dev; the only layer in which the shipped client runs. |
| live | bun run --filter @frockbot/computer-host test:live | The production container image against a real disposable Fly Sprite. Needs Docker and SPRITES_TOKEN; deleted in finally. |
Root bun test covers unit tests; runtime, integration and browser tests use
separate suffixes and commands. Pre-commit formats staged files. Pre-push runs
the fast tier — bun run validate format typecheck unit — reusing a
category's last pass whenever nothing it reads has changed, with a clean code
checkout; the slow tier runs on main after the merge. Run
bun run validate for everything, or bun run validate unit integration to
populate selected receipts while working.
Local validation explains the cache rules, worktree
isolation, and the GitHub configuration the pipeline depends on.
The current slice includes:
docs/notifications.md.The Cloudflare application builds an immutable Dynamic Worker artifact holding the gateway routes and the document that names the client; the client's own payload is the Worker's static assets, content-addressed under /_flutter/<buildHash>/. The gateway loads the User's active userId:applicationHash; the Dynamic Worker forwards authoritative Bot execution through a user-scoped capability backed by one Durable Object per Bot.
bun run --filter @frockbot/cloudflare test
bun run --filter @frockbot/cloudflare typecheck
bun run --filter @frockbot/cloudflare build
Run the hosted WebUI against the local Worker backend:
bun run dev
The command builds and seeds the Dynamic Worker artifact, then starts Wrangler on port 8787. On localhost, 127.0.0.1, or ::1, the sign-in screen includes Continue as local developer; it uses the fixed development identity and does not require Google credentials. The identity is accepted by the backend only when local development authentication is enabled.
For Worker-only development, place the artifact in local R2 before starting Wrangler:
cd apps/cloudflare
bun run artifact:build
bunx wrangler --env development r2 object put \
frockbot-application-artifacts/applications/foundation-v1.mjs \
--file dist/artifacts/foundation-v1.mjs --local
bunx wrangler dev --env development --var ALLOW_DEVELOPMENT_AUTH:true
Then open http://localhost:8787/?as_user=alice. CLI requests may instead send x-frockbot-user-id: alice. These query/header/cookie seams are enabled only by the local ALLOW_DEVELOPMENT_AUTH setting and must be disabled in production.
Better Auth with D1 and Google social login is the hosted profile's auth Package, and the one the tracked source resolves — so it is what wrangler dev and every suite run. A simple deployment builds Cloudflare Access instead and has none of this.
For local Google sign-in in the browser:
cp apps/cloudflare/.dev.vars.example apps/cloudflare/.dev.vars
# Replace every value in .dev.vars with independent development credentials,
# then initialize D1.
cd apps/cloudflare
bunx wrangler d1 migrations apply AUTH_DB --env development --local
bun run dev
Create a Google Web application OAuth client and register this local redirect URI:
http://127.0.0.1:8787/api/auth/callback/google
For production, keep ALLOW_DEVELOPMENT_AUTH unset and configure the GitHub production environment described below. BETTER_AUTH_URL is https://bot.frockbot.com; register https://bot.frockbot.com/api/auth/callback/google with Google. Never commit BETTER_AUTH_SECRET, provider credentials, or OAuth client secrets.
The memory Package has a provider-neutral document-store seam. Memory has one store on every platform: the Memory Package writes canonical Markdown to object storage through WorkspaceFilesV1 and is its only writer; the Computer mirrors Memory roots read-only and never writes one. Cloudflare runtimes use R2 for canonical documents and Vectorize with 768-dimensional embeddings from @cf/baai/bge-base-en-v1.5. Local Cloudflare development selects Wrangler's development environment and uses the remote-only development resources frockbot-memory-files-development and frockbot-memory-development; local application artifacts, D1, and Durable Objects remain isolated in .wrangler/state. The development memory resources are separate from the production names listed in Production deployment.
Memory has two user-private tiers: agent memory belongs to one bot, while global memory is shared by all of that user's bots. Reads and recall check both by default; when the same path exists in both tiers, the agent copy wins. Writes default to the safer agent tier.
Everything from here to Security model is the hosted profile — Tim's frockbot.com deployment: better-auth with Google, billing live, the release pipeline, Shorebird and Sparkle, the marketing site and the admin portal. Both profiles are the same code, and a contributor who is not shipping that deployment can skip the whole section.
Merging integrates; tagging ships. The pipeline has four stages, and the only decision in it is the merge:
check.yml runs the fast tier (format, typecheck, unit tests, the two small package suites) in a couple of minutes. It needs no secret, so a fork's pull request runs it too. Its two jobs, Check and Flutter, are the status checks the main ruleset requires..claude/skills/babysit/SKILL.md, run as /loop /babysit in one session) is the only merger: it squash-merges a green, non-draft pull request without the hold label, and only while main is green. The main-health status, required by the ruleset and kept by scripts/main-health.ts, makes GitHub refuse any other merge onto a red main except a pull request labelled fix-main or a revert. There is no auto-merge. A branch need not be rebased first; the ruleset does not require it to be up to date, because at this merge rate that was a rebase-and-rerun loop. (GitHub's merge queue would prove the combination before landing it, but it is only offered on organization-owned repositories.)main — main.yml runs what a landed change owes, on the merge commit itself: the fast tier again plus the marketing and admin-portal bundles, and — unless the Scope job finds that nothing since the last release tag could have affected them — the Flutter suite, the Cloudflare workerd and integration suites, the application build, and the browser suite across five runners. The excused set is narrow and fails safe; docs/architecture.md has the rule. Green deploys staging when the repository variable DEPLOY_STAGING is true (it is unset until the staging environment is configured), cuts the next patch tag on that revision, and starts release.yml for it. Red ships nothing and stops the line: the babysitter attributes the break to the pull requests that landed since the last green run, reruns a flake once, and fixes or reverts the culprit. A push that touches only docs/** and root Markdown starts no run.Pushing a valid SemVer tag by hand — v0.8.0 for a minor bump, v0.8.0-rc.1 for a prerelease — runs the same release workflow; the automatic cut continues from whatever tag is highest. Build metadata such as +build.1 is rejected because npm does not accept it in package versions. Prereleases use npm's next dist-tag rather than latest. Workspaces outside that list remain private.
Two jobs exist for the other profile rather than for this one. publish-images pushes the Computer host and Plugin build container images to Docker Hub under timoconnellaus, which is what an installer with no Docker pulls; it needs the DOCKERHUB_USERNAME and DOCKERHUB_TOKEN repository secrets, and the production deploy waits on it so every tag it ships is installable. release-assets builds the deploy bundle and github-release attaches it to the Release, because a deployer has neither Flutter nor this repository's build. The bundle is the three Workers prebuilt, with the web client, the application artifact and a manifest, and it is what scripts/deploy-bundle.ts and frockbot.com/deploy install through the Cloudflare API (docs/deploy-bundles.md). Details are in apps/cloudflare/deployment-config/README.md.
Neither leg is finished when it starts, so scripts/ci-watch.ts watches each to a terminal state and reduces it to an exit code — 0 green or landed, 1 failed, 2 still pending:
bun scripts/ci-watch.ts pr 128 # polls until its own checks are green, or names the red check
bun scripts/ci-watch.ts release v0.2.0 # polls until production deployed
bun scripts/ci-watch.ts pr 128 --once # report now and exit, for a caller that paces itself
bun scripts/babysit.ts is the view across all of it at once — main's health and the suspects for a break, what production runs, and the next action for each open pull request — and is what the babysitter reads every tick.
ci-watch names the quiet failures rather than waiting them out: a release whose packages published while Deploy marketing site and admin portal failed, or one that completed without ever running the deploy jobs. A release parked for an approval — which only happens if the production environment is given a required reviewer — is reported as waiting, not failed.
The phone app ships from the same run. Ship Android builds a signed Shorebird patch of apps/native against the newest active Android release and puts it on the staging track as soon as the tag verifies; Promote Android patch moves it to stable once Deploy FrockBot app has succeeded, so the client goes stable together with the server it was built against, and never ahead of it. A tag whose apps/native matches the previous release tag cuts nothing. When Shorebird finds native, asset or plugin changes, the same job cuts a full release with native-update.py release and uploads that APK. Installing it once puts the phone on the baseline later patches target (see apps/native/README.md). Neither Android job is on the web deploy's path, so neither can hold production back.
frockbot.apk on the GitHub release is the sideload. A tag that cut a full release attaches the APK it just built. Any other tag's Fetch Android APK job downloads the newest active Shorebird release and re-signs it with the keystore below; installing that file and opening it picks up the stable patch. The jobs read SHOREBIRD_TOKEN and ANDROID_DEBUG_KEYSTORE_BASE64; the patch private key stays on the patch job.
The jobs read three repository secrets, set once from the machine that holds the originals:
gh secret set SHOREBIRD_TOKEN # an API key from https://console.shorebird.dev
gh secret set SHOREBIRD_PATCH_PRIVATE_KEY < .native-build/updates/shorebird-private.pem
base64 -i ~/.android/debug.keystore | gh secret set ANDROID_DEBUG_KEYSTORE_BASE64
The private key signs patches for the public key baked into every release, and the keystore is the signer the installed app already trusts. Neither is ever generated anew.
Releases publish to npm through GitHub OIDC. There is no NPM_TOKEN, and no registry credential exists in this repository at all: each package names timoconnellaus/frockbot and the workflow file release.yml as its trusted publisher, and npm exchanges the job's OIDC identity for a credential that expires with the job. Provenance attestation comes with it, so --provenance is never passed.
Trusted publishing cannot bootstrap itself. npm will only attach a trusted publisher to a package that already exists, so the very first publication of a name cannot come from a workflow that holds no token. Every new published package needs this once, not just the first one: add a workspace to scripts/npm-publish.ts and declare frockbot.npm in its manifest, and the next tag's publish step fails on that name alone until it has been bootstrapped. Run it from a terminal, before the tag:
bun run bootstrap:npm-trust
It publishes a deprecated 0.0.0 placeholder under any name the registry does not have yet, then configures that package's trusted publisher, showing the plan and asking before it changes anything.
It touches only the packages npm is missing, which is usually one or two, and asks npm nothing at all about the rest. That matters more than it looks: the session behind an npm password expires in minutes, and a pass that interrogated all sixty-odd packages spent it answering prompts about packages that needed nothing, then died before reaching the ones that did. Whether an existing package is trusted is not asked, because a package the registry already has was published by the release workflow, which is only possible if it is.
The exception is a run that published a placeholder and then failed before trusting it. That leaves a package npm has and the workflow still cannot publish, which the default pass now skips. Name it to bootstrap it anyway — the failure says so when it happens:
bun run bootstrap:npm-trust @frockbot/applet-sdk
scripts/bootstrap-npm-trust.sh wraps scripts/bootstrap-npm-trust.ts with the two things that are easy to get wrong by hand. It provisions npm 11.15.0 or later into node_modules/.cache when the installed npm is older, because that is the version npm trust requires — the workflow itself needs only 11.5.1 and checks that before publishing. And it signs in, then leaves the terminal to npm for every call that changes the registry.
That second part is the one worth knowing about. npm demands a one-time password per operation, not per session, so signing in once does not settle it: a run that bootstraps five packages asks for a browser confirmation several times over. npm asks by printing an authentication URL and waiting. Capture that output and the question disappears — npm waits on a prompt nobody was shown, and the eventual failure reads as EOTP with the URL redacted, which looks like a rejected credential rather than an unanswered question. So publishing, deprecating and trusting inherit the terminal, and only the read-only probes whose output the script parses are captured. It follows that this cannot be run through a pipe or an agent session; it needs a real terminal.
A failure in the publish step reporting a 404 from the token exchange means the trusted publisher for that package is missing or misconfigured, not that the package is absent — run the bootstrap to reconcile it, then re-run the release.
Staging deploys from main only when the repository variable DEPLOY_STAGING is true — see Releases. It is unset, so nothing deploys here until the staging environment carries every value below; set it once it does.
Staging isolates everything that holds state or identity — its own D1 database frockbot-auth-staging, its own R2 buckets, its own Vectorize index, its own secrets, and its own Durable Object namespaces, which come free because a namespace belongs to the Worker that declares it. It shares the stateless frockbot-computer-host Worker, which owns only the Sprites credential, so staging exercises the same host production does instead of paying for a second container deployment. The consequence is production's ordering constraint — a change to the host's contract ships with a tag, so staging sees it only once that tag lands.
Unlike production, the staging deploy provisions its own resources. Each step is create-if-absent, so the first deploy creates the D1 database, the two R2 buckets, and the Vectorize index, and every later deploy finds them and moves on. The D1 identifier is resolved at deploy time and handed to bun run deployment:config staging --d1-database-id, so no variable records it.
Staging requires an admin allowlist. FROCKBOT_ADMIN_EMAILS is a required staging secret; the deploy fails without it so an administrator can always sign in and manage access. Staging uses the same beta-access authority as production.
Configure these GitHub staging environment values. They are the same set production uses:
| Type | Name | Purpose |
|---|---|---|
| Secret | CLOUDFLARE_API_TOKEN | Cloudflare token permitted to edit Workers, D1, R2, and Vectorize |
| Secret | CLOUDFLARE_ACCOUNT_ID | Cloudflare account containing the staging resources |
| Variable | BETTER_AUTH_URL | Set to https://staging-bot.frockbot.com |
| Secret | BETTER_AUTH_SECRET | Better Auth secret with at least 32 random characters; distinct from production |
| Secret | GOOGLE_CLIENT_ID | Google Web application OAuth client ID |
| Secret | GOOGLE_CLIENT_SECRET | Google Web application OAuth client secret |
| Secret | FROCKBOT_ADMIN_EMAILS | Required. The only identities that can sign in to staging |
| Secret | SPRITES_TOKEN | Fly Sprites token used only by the backend Computer provider |
| Secret | COMPUTER_HOST_TOKEN | Must equal production's, because staging binds the production host |
| Secret | CREDENTIAL_KEYRING | Versioned AES-GCM keyring; generate a fresh one, never production's |
| Secret | ROUTINE_HOOK_SECRET | HMAC secret for Routine webhook keys; generate it |
| Secret | MACHINE_TOKEN_SECRET | HMAC secret for machine tokens and pairing codes; generate it |
| Secret | FCM_SERVICE_ACCOUNT | Firebase service-account JSON authorizing Android push delivery |
| Secret | WEB_PUSH_VAPID_KEYS | Staging's own VAPID key pair for browser notifications; generate it |
The three generated secrets are openssl rand -hex 32, and CREDENTIAL_KEYRING is the same JSON keyring scripts/setup-production.sh builds. COMPUTER_HOST_TOKEN is the exception that must be copied from production rather than generated, because the host it authenticates against is production's.
Register https://staging-bot.frockbot.com/api/auth/callback/google as an authorized Google redirect URI and https://staging-bot.frockbot.com as an authorized JavaScript origin — either on the production OAuth client or on a separate staging one. The frockbot.com zone must be active in the same Cloudflare account and the deploy token must cover it, since Wrangler creates the custom domain's proxied DNS record on the first deploy.
Once a version tag verifies, release.yml deploys six Cloudflare Workers — marketing, the admin portal, the Plugin build service, the Computer host, the push relay and the app — through the GitHub production environment, and only then publishes the scripts/npm-publish.ts workspaces to npm. Publishing waits on the backend deploy because a package published for a release whose deploy then failed would sit on the registry ahead of a production that never moved. Merging to main deploys nothing — a tag is the only thing that reaches production, so code can be integrated freely and released deliberately:
apps/marketing serves the public marketing site at https://frockbot.com and redirects www.frockbot.com to the apex domain;apps/admin-portal serves the administrative surface at https://admin.frockbot.com; see The admin portal;apps/applet-build is the Plugin build service: an internal Worker with no public route and a Cloudflare Container that type-checks a Plugin's source, bundles it into one module and reads its exports by running it. It deploys before the app because that binding must resolve;apps/computer-host is the shared Computer host: an internal Worker with no public route, a bounded pool of Cloudflare Containers, and the only place SPRITES_TOKEN is used. It deploys before the app because that binding must resolve, and because a stale host would be serving a current app;apps/push-relay is the push relay at https://push.frockbot.com, through which servers without FCM credentials of their own, self-hosted ones included, notify the released apps; it holds FCM_SERVICE_ACCOUNT and nothing else. See the push relay;apps/cloudflare serves the authenticated application and API at https://bot.frockbot.com.The Computer host and the Plugin build service both run Containers, which require the Workers Paid plan; each hosted deploy step builds and pushes its container image from the Dockerfile, so the runner needs Docker (ubuntu-latest has it). The images publish-images pushes to Docker Hub are for the simple profile; switching this deploy to pull them instead is its own later tag, with the previous tag as the rollback, since staging shares the production Computer host.
The app deployment applies remote D1 migrations, uploads the immutable application artifact to R2 under its SHA-256 digest, writes that digest into the generated config as DEFAULT_APPLICATION_HASH, and then deploys the Worker, so each build is content-addressed and never overwrites a previously deployed artifact. Every deployment's generated configuration declares its custom domains, so Cloudflare creates and maintains the required proxied DNS records when the Workers are first deployed.
What each Worker is called, which account it belongs to, which hostnames it answers on and which buckets, index and database it binds is in deployments/hosted.json, not in a tracked wrangler.jsonc. bun run deployment:config hosted writes the configs the deploy reads, and scripts/deployment-config.test.ts proves they are still what production runs; see apps/cloudflare/deployment-config/README.md.
Create the resources deployments/hosted.json names before the first app deployment:
frockbot-auth;frockbot-application-artifacts and frockbot-memory-files;frockbot-memory with 768 cosine dimensions (bunx wrangler vectorize create frockbot-memory --preset @cf/baai/bge-base-en-v1.5).The tracked Wrangler files declare Cloudflare's AI binding for production and development. generate_image uses its native image inference, and the Cloudflare account must have billing for the configured Gateway routes and native models. Frock AI reaches the Gateway over HTTP rather than through the binding: the binding's gateway(...).run() targets the universal endpoint, whose request-shape translation rejects a dynamic/<route> model before inference runs (cloudflare/ai#617), so Auto is only accepted on the Gateway's compat/chat/completions endpoint. Reaching it needs the FROCK_AI_ACCOUNT_ID var and the FROCK_AI_GATEWAY_TOKEN secret, which is the cf-aig-authorization bearer for an authenticated Gateway. Both absent, Frock AI falls back to the binding, which serves manual @frock/... ids and resolves Auto to the concrete Workers AI chat model FROCK_AI_BINDING_AUTO_MODEL names — so a deployment with no Gateway still picks a model for a User who chose none. The browser e2e environment binds AI to a local RPC fake and sets no token, so CI takes that fallback, neither authenticating to Cloudflare nor incurring model usage.
Configure these GitHub production environment values:
| Type | Name | Purpose |
|---|---|---|
| Secret | CLOUDFLARE_API_TOKEN | Cloudflare token permitted to edit Workers, D1, and R2 for the target account |
| Secret | CLOUDFLARE_ACCOUNT_ID | Cloudflare account containing the production resources |
| Variable | BETTER_AUTH_URL | Set to https://bot.frockbot.com |
| Secret | BETTER_AUTH_SECRET | Better Auth secret with at least 32 random characters |
| Secret | GOOGLE_CLIENT_ID | Google Web application OAuth client ID |
| Secret | GOOGLE_CLIENT_SECRET | Google Web application OAuth client secret |
| Secret | FROCKBOT_ADMIN_EMAILS | Comma-separated owner emails allowed to administer deployment policy (optional; warns) |
| Secret | FROCKBOT_ADMIN_USER_IDS | Comma-separated User ids that are admins too, for people with no verified email (optional; warns) |
| Secret | SPRITES_TOKEN | Fly Sprites token used only by the backend Computer provider |
| Secret | COMPUTER_HOST_TOKEN | Shared secret the app Worker presents to the Computer host; generate it |
| Secret | CREDENTIAL_KEYRING | Versioned AES-GCM keyring for per-User Connection credentials |
| Secret | ROUTINE_HOOK_SECRET | HMAC secret every Routine webhook key is signed with; generate it |
| Secret | MACHINE_TOKEN_SECRET | HMAC secret every registered-machine token and pairing code is signed with; generate it |
| Secret | FCM_SERVICE_ACCOUNT | Firebase service-account JSON authorizing push delivery, for the app and the push relay; see docs/notifications.md |
| Secret | WEB_PUSH_VAPID_KEYS | The deployment's VAPID key pair for browser notifications; generate it, never rotate it |
Admission is closed by default. Set FROCKBOT_ADMIN_EMAILS to one or more comma-separated email addresses in the GitHub production environment; those identities are always admitted and may open the operator surface. Administration itself is not in the app: it is the admin portal at admin.frockbot.com, and the same list says who may use it. Every other account needs access from the beta-access authority, and having signed in before is not access; see docs/beta-access.md, including the release step that retired the signups switch.
ROUTINE_HOOK_SECRET is generated too, once, with openssl rand -hex 32 — ./scripts/setup-production.sh does it if the secret is absent and preserves it if it is not. Every Routine webhook key is HMAC-SHA256 over its own claims under this secret, and the gateway verifies that signature before any Durable Object is addressed. Rotating it invalidates every webhook key already handed out, which each Routine's owner then has to re-mint; without it set, the delivery route answers 503 and a webhook Routine is recorded without a key rather than given one nothing could verify.
WEB_PUSH_VAPID_KEYS is generated once too, by ./scripts/setup-production.sh when absent, as {"subject","publicKey","privateKey"} for https://bot.frockbot.com. Every browser that turned notifications on is subscribed to its public key, so replacing it silences them all until each turns notifications on again. For staging, bun scripts/web-push-keys.ts staging prints one for staging's origin.
MACHINE_TOKEN_SECRET is generated the same way and on the same terms. Every registered machine's token and every pairing code is HMAC-SHA256 over its own claims under this secret, verified at the edge before any Durable Object is addressed. Rotating it un-enrols every registered machine, which then has to be paired again; without it set, enrollment and every machine route answer 503 rather than admitting a caller nothing could verify.
Email to and from Bots needs no secret: it is on when the deployment profile names email.domain (bots.frockbot.com for this profile), that domain's Email Routing catch-all sends to the app Worker, and the domain is onboarded for Email Sending. The steps are in apps/cloudflare/deployment-config/README.md.
COMPUTER_HOST_TOKEN is not obtained from anywhere — generate it, once, with openssl rand -hex 32, and add it as a GitHub production secret. It is checked inside the container as well as at the host Worker, because the service binding is not the only route to that port. Rotating it means redeploying both Workers together.
./scripts/setup-production.sh is this profile's wizard: it creates the scoped Cloudflare token, configures the Google OAuth web client, and saves the generated platform secrets as GitHub production environment values. It creates no Cloudflare resource, and it is not the deploy bundle, which installs the simple profile into a deployer's own account. Run it, then add FROCKBOT_ADMIN_EMAILS to the production environment and verify the completed configuration.
The app has no administrative surface. apps/admin-portal is a Worker of its
own at admin.frockbot.com, deployed by release.yml beside the marketing
site, and it is the whole of administration: the admission mode, each account's
access, email invitations, what an account holds, and complimentary credit
(ADR 0028). It holds no state and reaches
the app only through a service binding to the app Worker's AdminEntrypoint,
which no route answers for.
Two things guard it, and the portal checks both itself:
admin.frockbot.com. Its team domain and
audience tag are the production environment's ACCESS_TEAM_DOMAIN and
ACCESS_AUD; until both are set, the deploy step skips and the portal admits
nobody.FROCKBOT_ADMIN_EMAILS, the same secret the app reads. Access says who may
open the page; this says who may administer.A deployment without a portal — a simple one — still turns an account's
features on from the operator surface: POST /api/debug/users/<userId>/features
with the deployment's DEBUG_TOKEN.
Register https://bot.frockbot.com/api/auth/callback/google as an authorized Google redirect URI. The deploy token must include Workers Scripts and Workers Routes edit access, and the frockbot.com zone must be active in the same Cloudflare account. Production deployment intentionally does not create or delete D1, R2, or Vectorize resources.
The runtime's features and registries provide composition and lifecycle ownership, not security isolation. Generated or unreviewed executable plugins must run inside a restricted process, container, or micro-VM; untrusted rich UI must run in a sandboxed frame rather than the trusted WebUI context.
bun run --filter @frockbot/computer-host test:live is the only check that drives a real Sprite, and it needs Docker and SPRITES_TOKEN;app/ The product: `runtime.ts`, the Contribution tables, and one directory per feature
admin/ The deployment's administrative operations, mounted by `AdminEntrypoint`
approvals/ Recording one approval decision inside the Bot Durable Object
audit/ Audited-effect projection and the User's rebuildable audit table
auth/ The two auth Packages behind `AuthPackageV1` — `better-auth/` and `access/` — and what they share
authoring/ The source persistence Bot-authored Plugins use, up to the build boundary
billing/ The account's subscription, its metered usage ledger, and the billing page
cards/ The Bot's half of Cards: an approval, a Plugin's own action, or conversation input
clock/ Reference feature with agent and host contributions
composition/ The User's Composition store and RPCs, and the Bot's mirror of them
connect/ Connected apps: the connection backend, its app tools, and the generated app list
credentials/ Per-User Connection credential encryption and leases
custom-models/ Opt-in Bot model override setting, default-disabled
echo/ Minimal reference feature used by tests and examples
email/ Email to and from Bots: inbound `email()`, usernames, and the sender, Cloudflare Email Service behind the `SEND_EMAIL` binding and `EMAIL_DOMAIN`, both written from a profile's `email` and inert until it names one
evals/ Development-only model evaluations and their grading, run through `bun run eval:greeting`, `eval:conversation`, `eval:turn-start`, `eval:response-review`, `eval:call-review`, `eval:context`, `eval:routine-event` and `eval:dictation-cleanup`
flock/ Durable Bot directory and Bot character avatars
identity/ The agent runtime's identity system-prompt section
image/ generate_image through Cloudflare's AI binding, fenced by the Workspace
isolates/ The authority a Bot isolate member is mounted with, and its grants
machine/ Registered machines: the desktop's signed-in enrolment, queue and ledger
memory/ Bot, User and Group Chat memory: canonical records and Markdown tiers
notifications/ User-visible messages, their unread cursors, and the push outbox
payments/ The two payments Packages behind `PaymentsPackageV1` — `stripe/` and `none/`
plugins/ The deployment catalog and seed states, a Bot's Plugins page, its enable map behind a revision fence, and the `plugin_*` authoring tools with the approval that makes a published Plugin live
routines/ Durable Routines, the alarm scheduler, and the webhook door
search/ Per-User transcript index, search route, and overlay
settings/ Bot, Package, and User settings surfaces
shell/ The Bot Durable Object's state, its Turn, the Composition mount, and the hosted geometry
skills/ Skill catalog, disclosure on demand, managed and Plugin-borne Skills, and the Bot's Workspace seam
subagents/ Subagent Tasks: the parent Bot's task authority, the Durable Object binding, and their records
supervision/ The hosted Jev TurnSupervisor adapter and its decision composition
testkit/ Shared test doubles and harnesses
ui-theme/ The Appearance Package definition; it contributes no code
voice/ Composer dictation and the account-wide voice assistant over the deployment's speech providers
web/ web_search on Brave Search and a bounded, SSRF-classified web_fetch
applets/ The Plugin build contract the app and the build service share
sdk/ Plugin authoring SDK: the declarations-only Plugin entry and the build pipeline the build service runs; published to npm
apps/
admin-portal/ Hosted-only administrative Worker behind its own Access application
applet-build/ Plugin build service Worker and its Node container
cloudflare/ User application loader, Dynamic Worker artifact, the client's web build, and bot state
computer-host/ Shared Computer host Worker and its Node container
marketing/ Public frockbot.com site and static-assets Worker
push-relay/ Hosted-only push relay: servers without FCM credentials notify the released apps through it
native/ The client: the phone app, and the web build the app Worker serves
computer/ The Computer: tools, prompt, state, and the ComputerHostV1 interface
core/ The host interface, its capabilities, the registry, and the shared helpers
host-protocol/ Versioned v1 DTOs and decoders for the Computer host seam
fake/ An in-memory host: the substitution proof, and the suites' fixture
fly/ The production host implementation: Fly Sprites, its runtime and takeover adapter
core/
contracts/ Session, LLM, prompt, auth Package, tool execution, and turn-supervision contracts
durable/ Bot Durable Object admission, log, cursor, scheduling, and the Composition generation store
agent-loop/ Concrete event-sourced durable agent loop and Agent registry
configuration/ Versioned durable User/Bot settings contracts
connection/ Provider-neutral Connection transport result contracts
workspace-store/ Object-storage durable-root store and its generation ledger
secret-shapes/ Declared shapes of the deployment's secrets
protocol/ Commands and events shared across process seams
protocol-schemas/ Generated protocol schemas shared by clients
machine-protocol/ Contracts for registered User machines and their tokens
models/ Model role bindings and the provider-neutral model registry
prompt/ System prompt assembly from Package contributions
tools/ The trusted tool registry and its guards
deployments/ Who each deployment is: `hosted.json`, `staging.json`, and the profile schema
frock-compose/ Frock Compose: the Plugin worker host that loads a User's Plugins as one Dynamic Worker
providers/
openai-compatible/ Shared model transport: request mapping, deadlines, AI SDK decoding
frock-ai/ Built-in credential-free Frock AI model provider
ollama-cloud/ Optional Ollama Cloud model provider
anthropic/ Optional Anthropic (Claude) model provider
foundation/ Deterministic credential-free development provider
scripts/
deploy-bundle.ts Installs a release's deploy bundle into a Cloudflare account through the API
deploy-bundle/ The simple profile the bundle is built from, and fetching a bundle
deployment-config.ts `bun run deployment:config`: `@frockbot/cloudflare`'s generator over this repository's profiles
deployment-config/ The equivalence gate's fixtures (the generator is `apps/cloudflare/deployment-config/`)
npm-publish.ts The workspaces a release publishes to npm
white-label-fixture.ts `bun run build:white-label`: a stub white-label built from the packed packages
setup-production.sh The hosted profile's wizard: GitHub environment secrets
docs/
architecture.md Current system shape
grokbot-parity.md The GrokBot capabilities FrockBot must match
notifications.md Messages, unread state, notifications and icon badges
plan.md The re-orientation plan
jev-supervision-plan.md Jev turn supervision delivery
adr/ The decisions, numbered
The core is MIT; see LICENSE. Commercial add-ons such as FrockBot for Teams are licensed separately and are not in this repository. The FrockBot name and logo are not licensed. Contributors sign a Contributor Licence Agreement once, which lets their work also be licensed commercially; CONTRIBUTING.md explains it.
release.ymlscripts/npm-publish.tsproductionmainproduction