Imported from trickleai/capy-app-dev (
skills/capy-app-dev/SKILL.md). Install upstream withnpx skills add trickleai/capy-app-dev --skill capy-app-dev. Copyright stays with the author.
capy-app-dev
Use this skill when an agent needs to create, initialize, build, deploy, or check the status of an app on the capy app platform from inside a sandbox.
Trigger Conditions
Trigger this skill when the user intent is to build, modify, or ship an application, for example:
- Build me a web app
- Create a dashboard / admin panel / landing page that should be previewable
- Start from a scaffold or app template and deploy it
- Update an existing app, rebuild it, and refresh the preview URL
- Build an app that needs a database, persistent storage, D1, Drizzle, SQL, saved user content, or data that must still exist after refresh/redeploy
Do not wait for the user to explicitly say "deploy". The agent should decide to create and deploy when the task is clearly an application-development workflow. When the user explicitly asks for a database or persistence, treat D1-backed persistence as required, not optional.
Preconditions
CAPY_SECRET— preferred token (sandbox environments)CAPY_AUTH_TOKENorMANAGEMENT_API_TOKEN— fallback tokensCAPY_USER_ID— required forcreateonly whenCAPY_SECRETis not setCAPY_API_URL— optional; defaults tohttps://api.happycapy.host; set only for non-production testing- CLI source:
https://github.com/trickleai/capy-app-dev.git - Default scaffold:
https://github.com/trickleai/capy-scaffold-default.git
Agent Workflow
- Decide whether to use the default scaffold or clone a task-specific template repository.
- If the task starts from scratch, use the default scaffold.
- If the task already has a repo or template requirement, clone/copy that source instead.
- If the user requires a database, implement the app against Cloudflare D1 via
c.env.DB, define the schema with Drizzle, and generate migrations. - Generate or choose a unique app name and create the remote app record before deploy.
- Build the project, then deploy the build output through the platform API.
deploynow requires a-m "<message>"describing the change (see "Writing good messages" below); it is mandatory and enforced locally.deployalso auto-saves the project source as a versioned snapshot before uploading the build, so every deployed version has a matching, recoverable source snapshot — you do not need a separatesavebeforedeploy. Always also commit and git-tag each deploy (see Command Workflow step 5) — the git tag is a session-local mirror that complements the persistent server-side snapshot (see "Two version records" below). Do this automatically, without waiting for the user to ask. deployis preview-only — it never goes live on its own, not even the first time. To make a version live you must callpublish(see Command Workflow step 6). For a normal "build and ship it" request, deploy then publish. If the user only wants to preview/review first, deploy and hand back the preview URL without publishing.- Verify the deployment result and app status show database metadata when D1 was required.
- Return the preview URL (and, once published, the live URL) and deployment status to the user.
Command Workflow
- Download the CLI (latest release):
mkdir -p .capy-cli
curl -fsSL \
https://github.com/trickleai/capy-app-dev/releases/latest/download/capy-app-dev.js \
-o .capy-cli/index.js
The CLI lives in .capy-cli/ (not dist/) so that npm run build — which
clears and repopulates dist/ — does not overwrite it.
- Create the remote app record:
node .capy-cli/index.js create <app-name>
This writes .capy-app.json in the current directory.
- Initialize the default scaffold if the project has not been created yet:
node .capy-cli/index.js init
By default init fetches the public default scaffold repository. For local development, CAPY_DEFAULT_SCAFFOLD_PATH can point at a local checkout instead.
The default scaffold is expected to emit a self-contained client index.html so preview deployments remain usable even if platform asset responses have incorrect MIME types for module JS or CSS.
- Install dependencies and build the app:
npm install
npm run db:generate # when src/server/db/schema.ts changed
npm run build
The bundled CLI package and default scaffold both include a local .npmrc with include=dev, so you should not need to override NODE_ENV just to install build dependencies.
If the user asked for a database, do not skip npm run db:generate after schema work.
-
Deploy the build output. Always follow this three-step sequence so every deploy is a recoverable version (this is mandatory, not optional — it is how the platform's rollback stays usable; see "Versioned deploy workflow"):
a. Commit the working tree first. Init a repo on the very first deploy:
git init -q # first deploy only git add -A && git commit -q -m "deploy: <short description>"b. Deploy (a
-mmessage is required — see "Writing good messages"):node .capy-cli/index.js deploy -m "<what changed and why>" --json(Deploys from
./distby default; use--dir <path>for another output dir.)deployfirst auto-saves the project source as a snapshot (using the same-mmessage), then uploads the build. The source snapshot is best-effort: if the code API is unavailable it is skipped and the deploy still proceeds. An empty/whitespace-mis rejected locally (MISSING_MESSAGE, exit 2) before any network call.Check whether the snapshot was actually saved. Because the save is best-effort, a deploy can succeed with no recoverable source snapshot (e.g. the code API timed out). The
--jsonresult makes this explicit:snapshotSaved: true+snapshotId: "asnap_…"→ this version has a recoverable source snapshot.snapshotSaved: false+snapshotId: null+snapshotError: "<reason>"→ the deploy went live but this version has no source snapshot. Do not assume you canrestoreback to it. Re-runsave -m "<same message>"to record one (in text mode the CLI prints aWarning: … WITHOUT a source snapshotline for the same reason).
c. Tag the commit with the returned
deployId:git tag "v-<deployId>" # deployId from the deploy --json outputDo these three every time you deploy — no exceptions. It keeps the sandbox's local code (and
.capy-app.json, including itsenv) aligned with each server-side version, so a laterrollbackcan be mirrored locally.deployis preview-only. It uploads to the preview slot and returns apreviewUrl;publishedisfalse. The live URL is NOT updated — this is true for every deploy, including the first one. Go live withpublish(next step). -
Publish to make a version live.
deployonly produces a preview; the live URL changes only when you publish:node .capy-cli/index.js publish # publish the latest preview code node .capy-cli/index.js publish <deployId> # publish specific version code node .capy-cli/index.js publish <deployId> --with-data --yes # code, then D1 restoreData restore is destructive and requires both an explicit
deployIdand--yes. If restore fails after code publication, the target code remains live; inspect the partial-failure details and verify D1 state before retrying. Run all publish operations for the same app serially; concurrent same-app publishes are unsupported.For a normal "build and ship" request, run
publishright afterdeploy. Skip it only when the user explicitly wants to preview/review before going live. -
Check the current remote status:
node .capy-cli/index.js status
When D1 is required, prefer node .capy-cli/index.js deploy --json and node .capy-cli/index.js status --json so the agent can verify that database.id and database.name were returned.
- List the account's apps:
node .capy-cli/index.js list # active apps only
node .capy-cli/index.js list --all # include suspended/deleted rows too
Ownership is scoped to the caller's account.
- Delete the app (destructive — requires explicit confirmation):
node .capy-cli/index.js delete --yes # soft-delete cwd app
node .capy-cli/index.js delete <appName> --yes # soft-delete named app
node .capy-cli/index.js delete --hard --yes # hard-delete cwd app
node .capy-cli/index.js delete <appName> --hard --yes # hard-delete named app
Omit appName to use the current directory's .capy-app.json. Provide appName
to delete an owned app from any directory without reading local project config.
Both forms require explicit confirmation.
Soft-delete (--yes only): removes the deployed worker and routing (URL stops
serving immediately), but preserves the registry record, app name, and D1 data.
The app name is locked and cannot be reused.
Hard-delete (--hard --yes): irreversibly removes ALL resources — all version
scripts, KV routing, the D1 database and its data, deployment history, env vars,
and the registry row. The app name is released for reuse. D1 data is permanently
lost and cannot be recovered. Both flags are required; --hard alone exits with
CONFIRMATION_REQUIRED and makes no network call.
Managing secrets
Env vars are plain text (visible in the Cloudflare dashboard) — non-sensitive config only, no secrets. Values must be strings; a non-string value in .capy-app.json's env fails with INVALID_PROJECT_CONFIG.
Note: The
envcommand is a deprecated alias forsecret. Usesecretfor all new workflows.
Two ways to set them:
Option A — via .capy-app.json (applied on next deploy):
{
"appName": "my-app",
"url": "https://my-app.happycapy.host",
"env": { "APP_TITLE": "My App", "MODE": "production" }
}
Option B — directly against the registry (snapshotted into the worker's bindings at the next deploy or publish):
node .capy-cli/index.js secret list # show stored vars (NAME + value)
node .capy-cli/index.js secret set APP_TITLE "Hi" # upsert one var
node .capy-cli/index.js secret unset APP_TITLE # remove one var
Persistence is accumulate/merge. A deploy overwrites keys it sends and keeps any previously-stored keys that are omitted. secret unset is the supported way to remove a var — omitting a key from .capy-app.json does not remove it.
Env vars are snapshotted into the worker's bindings at deploy time. The currently-live worker only sees the env that was active when it was deployed — secret set and .capy-app.json edits do not hot-update the running worker; they take effect only on the next deploy (or publish).
secret set/secret unset also mirror the change into .capy-app.json so a later deploy won't overwrite with a stale local value.
Versioned deploy workflow
capy-app uses a preview-first, two-slot deploy model. Every app has two fixed
worker slots: a live slot (served at the app's main URL) and a preview
slot (served at previewUrl). deploy only ever writes the preview slot;
publish copies a version into the live slot.
- deploy — uploads the new version to the preview slot only. This is
always preview-only — including the first-ever deploy (
publishedisfalse, the live URL is not created/changed). Accessible atpreviewUrl. To go live you mustpublish. - publish [deployId] — publishes a version's code to the live slot. Omit
deployIdto publish the latest preview; pass an explicitdeployIdto publish a specific version.publish <deployId> --with-data --yespublishes that code and then destructively restores its D1 bookmark. A restore failure does not roll live code back; verify D1 state before retrying. - rollback <deployId> — re-deploys a previous version into the preview
slot for review. It never changes live code or D1 data. Requires an explicit
deployId(find one withversions); publish afterward to make it live. - versions — lists all deployment versions with their status, reachable URL (or no URL for superseded versions), and timestamp.
Publish operations for one app must run serially. Concurrent same-app publishes are unsupported until the platform has a durable per-app operation lease.
node .capy-cli/index.js deploy -m "add checkout page" # preview-only (always, incl. first deploy)
node .capy-cli/index.js publish # publish latest preview code to live
node .capy-cli/index.js publish abc123 # publish specific version code to live
node .capy-cli/index.js publish abc123 --with-data --yes # publish code, then restore D1
node .capy-cli/index.js rollback abc123 # stage abc123 in preview; live/data unchanged
node .capy-cli/index.js publish # then publish the staged version to live
node .capy-cli/index.js versions # list all versions
Two version records: server snapshot + local git tag (they coexist)
There are two complementary records of each version — keep both, they serve different purposes and do not replace each other:
| Server-side source snapshot | Local git tag (v-<deployId>) |
|
|---|---|---|
| Created by | deploy auto-saves it (and save) |
you git commit + git tag on each deploy |
| Lifetime | persistent — the durable source of truth, survives across sandboxes | session-local — lives only in this sandbox's git repo |
| Purpose | authoritative "what source produced this version"; queryable/restorable server-side | quick local convenience to check the working tree back out |
| Findable by | its -m message (why detailed messages matter) |
the deployId in the tag name |
Because you commit + git tag "v-<deployId>" on every deploy (step 5 of the
Command Workflow), each server-side version has a matching local tag. rollback
re-deploys the version into the preview slot server-side and does not
touch sandbox code, so after rolling back, check out the tag to bring the local
project back in step (then publish when you're ready to make it live):
node .capy-cli/index.js rollback <deployId> --json
git checkout "v-<deployId>"
node .capy-cli/index.js publish # make the rolled-back version live
Notes:
- The sandbox git repo is session-local — tags do not persist across sandboxes.
.capy-app.json(including itsenvblock) is tracked by git, sogit checkout v-<deployId>also restores that version's local env config — and becausesecret set/secret unsetmirror into.capy-app.json, the local file is a faithful record to check out. A laterdeploystill applies the accumulate/merge semantics above (it re-applies the checked-out env over the server's stored vars), so this re-applies that version's env rather than resetting the server to an exact snapshot.- Redeploying after a rollback uploads the current working tree, which supersedes the staged rollback version. Roll back to preview and verify it, then explicitly publish that deploy ID to switch live. For an urgent known-good recovery, publish the deploy ID directly; afterward, fix forward and deploy again.
Saving project source (save)
save backs up the entire project source tree to the backend, independent
of deploy. It is content-addressed and incremental: unchanged files are not
re-uploaded, and each save records a versioned snapshot. deploy runs this
same save automatically (see Command Workflow step 5), so an explicit save
is only needed to snapshot a checkpoint that you are not deploying.
save requires a -m "<message>" (like deploy). An empty/whitespace
message is rejected locally (MISSING_MESSAGE, exit 2) — the message is the
only human-readable handle for finding and rolling back to a version later.
node .capy-cli/index.js save -m "extract auth into its own module" # snapshot the workspace (cwd)
node .capy-cli/index.js save -m "wip: checkout page markup" --dir ./app --json # a specific dir, JSON
- Walks the workspace and skips ignored paths (dependency/install dirs like
node_modules, VCS like.git, caches, OS junk — build outputs such asdist/buildare NOT ignored). The ignore list is fetched from the server (GET .../code/ignore) so it stays in sync; a built-in fallback is used if that call fails. - Flow: build a manifest → ask the server which blobs are missing → upload only those → commit (reconciles the stored tree to match the workspace + records a snapshot with the message).
savedoes not deploy — it only stores source. Deploy stays a separate step.
Browsing and restoring source versions (snapshots / restore)
Together with save/deploy's auto-save, these two commands close the source
version-control loop: save → list → restore.
node .capy-cli/index.js snapshots # list source snapshots, newest-first
node .capy-cli/index.js snapshots --json # machine-readable
node .capy-cli/index.js restore <snapshotId> --yes # revert the workspace to that snapshot
snapshots lists every recorded source version (id, timestamp, file count,
and the -m message). The message is how you decide which version to go back
to — this is why detailed messages matter.
restore <snapshotId> is a true revert of the local workspace to that
snapshot: it reverts the server-side source tree to the snapshot, then downloads
that tree over your working directory — overwriting changed files and deleting
files added since the snapshot. Because it can discard uncommitted work it is
destructive and requires --yes (without it: CONFIRMATION_REQUIRED, no
network call). It skips ignored paths (node_modules, .git, …) and never
touches .capy-app.json, so dependencies and project identity survive.
restore does not deploy — after it, your local code matches the snapshot;
run deploy -m "..." to ship that version live. A typical recovery flow:
node .capy-cli/index.js snapshots # find the good version by its message
node .capy-cli/index.js restore asnap_abc123 --yes # bring the workspace back to it
node .capy-cli/index.js deploy -m "revert to pre-refactor checkout" # ship it
Writing good messages
The -m message is the only thing an agent (or human) has later to tell
versions apart and decide which one to roll back to — there is no diff viewer in
the recovery path, just the list of messages. Write it for a reader who has to
choose a version months from now:
- State what changed and why, specifically. Name the feature/area touched.
- One concrete change per message. If you did several unrelated things, that
is a sign to
save/deployin smaller steps. - Never use vague placeholders like
update,wip,fix,changes,stuff,., or the empty string — they make every version look identical and the version list useless.
| ❌ Avoid | ✅ Prefer |
|---|---|
update |
add dark-mode toggle to settings page |
fix |
fix cart total ignoring discount codes |
wip |
wip: checkout form validation (email + card) |
changes to db |
add orders table + migration 0004 |
Machine-readable output
Append --json to any command (create, init, deploy, status, list,
delete, publish, rollback, versions, save, snapshots, restore, secret list/set/unset) for structured output.
Notes
- The CLI does not expose Cloudflare credentials to the sandbox.
deploydepends only on the build output contract anddeploy.json, not on the default scaffold.- If
deploy.jsoncontainsdatabase.migrations, the platform manages D1 creation and incremental migrations automatically. - Database requests require real D1-backed persistence via
c.env.DB. Ensure the build output includesdeploy.json.database.migrations, the server code actually reads and writes throughc.env.DB, and the post-deploy status confirmsdatabase.idexists. - After changing the default scaffold schema, run
npm run db:generatebeforenpm run build. - If
.capy-app.jsonis missing, runcreatebeforedeployorstatus.
Handling errors
Every command exits non-zero on failure; --json emits { "success": false, "error": { "code", "message" } }. Branch on error.code, not message text.
| Code | HTTP | Action |
|---|---|---|
APP_QUOTA_EXCEEDED |
402 | Plan limit reached. Do not retry or rename. Tell the user to upgrade their plan or delete an unused app to free a slot. |
APP_NAME_TAKEN |
409 | Name in use. Pick a different name and retry create. |
CONFIRMATION_REQUIRED |
— | Destructive command called without required confirmation flag. delete → add --yes. delete --hard → add --hard --yes. publish <deployId> --with-data → add --yes. restore → add --yes (it overwrites the local workspace). |
MISSING_MESSAGE |
— | save/deploy called without a non-empty -m "<message>". Add a specific message describing the change (see "Writing good messages"). No network call is made. |
SNAPSHOT_NOT_FOUND |
404 | restore was given a snapshot id that does not exist for this app. Run snapshots to list valid ids. |
MISSING_PROJECT_CONFIG |
— | .capy-app.json not found. Run create first. |
INVALID_PROJECT_CONFIG |
— | .capy-app.json is malformed (e.g. env is not an object of string values). Fix the file, then retry. |