← keelsonCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to keelson
Snapshot Sep 30, 2026 · 23:16 UTC · version 0.6.7
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Deploy and operate web apps on Keelson (keelson.run). Use when the user asks to deploy, publish, or host an app, mentions Keelson or keelson.run, or needs keelson.yaml written or fixed. Covers login, deploy, status, logs, diagnose, secrets, and adapting apps to run on Keelson (SQLite -> managed libSQL, PORT binding).",
"included_files": [
{
"relative_path": "core/DECISION.md",
"size_in_bytes": 80768
},
{
"relative_path": "core/KEELSON_YAML.md",
"size_in_bytes": 57257
},
{
"relative_path": "core/ROUTER.md",
"size_in_bytes": 4004
},
{
"relative_path": "reference/DB_APPLY.md",
"size_in_bytes": 5860
},
{
"relative_path": "reference/LIBSQL_CLIENTS.md",
"size_in_bytes": 15128
},
{
"relative_path": "reference/RPO_CONTRACT.md",
"size_in_bytes": 6115
},
{
"relative_path": "reference/SUPPORT_LEDGER.md",
"size_in_bytes": 37885
},
{
"relative_path": "reference/VERIFICATION.md",
"size_in_bytes": 41484
},
{
"relative_path": "stacks/go.md",
"size_in_bytes": 14100
},
{
"relative_path": "stacks/node.md",
"size_in_bytes": 16120
},
{
"relative_path": "stacks/python.md",
"size_in_bytes": 29372
}
],
"name": "keelson",
"skill_md_contents": "---\nname: keelson\ndescription: Deploy and operate web apps on Keelson (keelson.run). Use when the user asks to deploy, publish, or host an app, mentions Keelson or keelson.run, or needs keelson.yaml written or fixed. Covers login, deploy, status, logs, diagnose, secrets, and adapting apps to run on Keelson (SQLite -> managed libSQL, PORT binding).\nallowed-tools: Bash(keelson:*)\n---\n\n# Keelson CLI\n\nUse `keelson` to authenticate, deploy, configure, inspect, and operate the current Keelson app from the terminal.\n\n## Installing the Keelson CLI\n\nBefore running Keelson commands, check whether `keelson` is available. If it is not installed, use the official installer:\n\n- macOS / Linux: `curl -fsSL https://keelson.dev/install.sh | sh`\n- Windows PowerShell: `irm https://keelson.dev/install.ps1 | iex`\n\n## Installing This Skill\n\nIf this skill came from a plugin marketplace, the AI tool manages its installation. Otherwise, use `keelson install-agent` to install the skill for your AI tool. The legacy `keelson skill install` alias still works.\n\n## Keeping This Skill Current\n\nFor a plugin installation, use the AI tool's plugin update mechanism to update this skill, and run `keelson upgrade` separately to keep the CLI current.\n\nFor a CLI-managed installation, this skill ships inside the `keelson` binary. `keelson login` auto-updates an unmodified installed skill to match the CLI. The CLI signals a stale skill three ways:\n\n- a stderr line like `keelson: installed skill differs from this CLI ...`\n- a `meta.skill_outdated` object (with an `action` field) at the top level of `--json` output. When present, `meta` is a sibling of the command's named data key. List commands use stable object shapes: `apps list` returns `{\"apps\": [...]}` with `--workspace` and `{\"workspaces\": [...]}` without it, `workspaces list` returns `{\"workspaces\": [...]}`, `groups list` returns `{\"groups\": [...]}`, and `doctor` returns `{\"checks\": [...]}`.\n- a `keelson doctor` check named \"Skill freshness ...\" with `code: skill_outdated`\n\nWhen you see any of these, run `keelson install-agent --yes` (updates every installed skill in place) and re-read this file — the CLI you are calling is newer than the instructions you loaded.\n\n## Core Contract\n\nAlways read the deploy spec in this skill directory before deploying. It ships as a bundle, not one file — read it in this order:\n\nWhich document wins when they disagree: for how to operate the CLI (commands,\nflags, JSON shapes) the bundled skill matches the installed binary and wins. For\nwhat the service currently supports (limits, plans, runtimes) the web docs and\nDeploy Spec describe the live service; if the bundle says something newer or\nolder than the web, the CLI is out of date — tell the user to update it\n(`keelson version`) rather than guessing.\n\n1. `core/DECISION.md` — deploy / adapt / refuse, `db.mode` selection, and the definition of a successful deploy.\n2. `core/KEELSON_YAML.md` — required `keelson.yaml` fields, supported runtimes, limits, auth, secrets.\n3. `core/ROUTER.md` — detection signals → the **one** `stacks/<lang>.md` file to read for this app.\n\nThen read only that stack file (`stacks/node.md`, `stacks/python.md`, or `stacks/go.md`); each is self-contained, so do not read the other two. Pull `reference/*` (`VERIFICATION.md`, `LIBSQL_CLIENTS.md`, `SUPPORT_LEDGER.md`, `RPO_CONTRACT.md`) only on demand.\n\nIf this directory still contains a single `DEPLOY_SPEC.md`, it is a pre-2026-07-15 leftover that the CLI could not remove because it was edited locally. It is stale — read the `core/` files above instead, and delete it once you have salvaged any local notes.\n\nWhen building a new app that needs durable data, author it against a libSQL client (`db.mode: libsql`) from the first line — do not write fresh `sqlite3` / `better-sqlite3` file code and adapt it later (see `core/DECISION.md` → Decision Tree).\n\nEvery `keelson.yaml` must explicitly contain `db:` and `db.mode` (`libsql` or\n`none`; legacy `turso` normalizes to `libsql`); there is no\nimplicit `none`. File-SQLite signatures are a hard deploy error unless the\ndatabase is declared as intentionally ephemeral with `db.local_sqlite` (policy,\npaths, and reason). `db.mode: libsql` is the only durable database — no mode\npersists a SQLite file on disk — so an app that writes one either moves to a\nlibSQL client or cannot be deployed (see `core/DECISION.md` → Adaptation\nDecision Function, which also decides when you must ask the user first).\n\nEvery machine-checkable contract this skill declares carries a stable ID (`SD-*`, `DC-*`) that maps to an executable test — see `core/DECISION.md` → Spec–Test Traceability. `scripts/check_spec_test_traceability.py` fails when a declared contract has no test, so a promise made in these docs cannot ship untested. The structured-error contract just below is tracked there as `DC-8`.\n\nKeelson keeps `/__keelson` and everything below it for platform use on every\nHTTP method. On delivery paths that pass through the gateway, GET requests to\n`/health` and `/_health` are also platform routes; HEAD and other methods may\nreach the app. The whole `/api/webhooks/` and `/api/external/` subtrees are\nnon-interactive (credential-authenticated, never a browser session), and the\nedge blocks all methods to `/api/webhooks/email` and `/api/webhooks/email-events`.\n`/assets`, `/files`, `/static`, `/uploads`, the rest of `/api`, `/docs`, and\n`/media` remain app-owned. A Node build that emits a top-level `__keelson`\ndirectory is rejected with `reserved_path_conflict`. See\n`core/KEELSON_YAML.md` → Reserved URL Paths for the complete rules.\n\n### Reserved URL path errors — fix recipes\n\nThese error codes concern conflicts with the `/__keelson` namespace above.\nMatch on the code, apply the fix, redeploy. Do not string-match the\nhuman-readable message.\n\n- **`reserved_path_conflict`** — surfaces from `keelson deploy` / `keelson status --json`\n (`error.code`) when your build output contains a top-level `__keelson`\n directory. Fix: rename that directory (and any route that emits it) to a\n non-reserved name — e.g. put your bundle under `/static` or `/assets` instead\n of `/__keelson`. Keep `assetsDir`/output-dir settings off `__keelson`. Redeploy.\n- **`reserved_path_prefix_container`** — a runtime error from the edge: a request\n to a path your **container** app owns (e.g. `/assets/index-*.js`) came back\n `404` from files-worker with `x-keelson-error: reserved_path_prefix_container`\n and a JSON body `{ error, path, hint, doc }`. Your app is fine — a container\n app serves every route from its own origin, so this path should never reach the\n platform edge. It means an edge route shadowed a path your app owns. Fix: make\n sure your app defines **no** routes under `/__keelson/*`, then redeploy to\n reconverge edge routing. If it persists after a clean redeploy, it is a\n platform routing issue, not an app bug — report it rather than editing the app.\n\nWhen an error carries a `doc` field (structured edge errors) or a `hint`\n(`--json` CLI errors), follow it before trying anything else.\n\nPrefer non-interactive CLI usage:\n\n- Use `--json` for commands whose output you need to parse.\n- Use `--quiet` only when a single machine-readable line is enough.\n- Use `--timeout` and `--retry` for transient network or API failures.\n- Before starting work, run `keelson whoami --json` from the project directory and confirm both `active_workspace` and `active_workspace_source`.\n- Do not use `last_active_workspace_id` to choose the CLI workspace; it is the Console's pinned workspace, not the CLI's active workspace.\n- Workspace resolution depends on the current working directory. After moving to another project directory, run `keelson whoami --json` again before continuing.\n- Use `--workspace <workspace>` with an exact workspace slug, name, or ID when the account has more than one workspace.\n- To avoid repeating `--workspace` for one project, a multi-workspace account can set the optional top-level `workspace` field in `keelson.yaml`; prefer the workspace slug. Do not add it when the account has only one workspace, because the CLI selects that workspace automatically. An explicit `--workspace` overrides the file.\n- If you do not know the exact workspace, run `keelson workspaces list --query <text>` to search workspace slugs and names with a case-insensitive partial match, then pass the exact slug, name, or ID to `--workspace`.\n- The former tenant-named flag, command, config field, and JSON keys remain compatibility aliases. Prefer the workspace names; no removal date is set.\n- Use `--app <slug>` to override the app resolved from the current directory.\n\nWhen a command exits non-zero with `--json`, stdout contains:\n\n```json\n{\"error\":{\"code\":\"example_code\",\"message\":\"Human readable message.\",\"hint\":\"Actionable next step.\",\"retryable\":false}}\n```\n\nSchema shorthand: `{\"error\":{\"code\",\"message\",\"hint\",\"retryable\"}}`.\n\nFollow `error.hint`. If `error.retryable` is `false`, do not rerun the same command unchanged. Unknown `error.code` values are forward-compatible; rely on `message`, `hint`, and `retryable` instead of string-matching stderr.\n\n## App Context\n\nMost app-scoped commands can infer their target from the current directory's `keelson.yaml`. Run them from the project root, or pass `--app <slug>`. Pass `--workspace <workspace>` with an exact workspace slug, name, or ID when the CLI reports multiple workspaces. If a name matches multiple workspaces, use the slug or ID.\n\nFor agent, CI, and scripted workflows, capture explicit identifiers from JSON responses when a later step depends on the exact resource. Do not rely on \"latest\" if concurrent deploys might be running.\n\n## Confirmations (destructive operations)\n\nDestructive operations (deleting an app, restoring a snapshot) require a human to\napprove them in the browser. When one is needed, the command exits non-zero with:\n\n```json\n{\"error\":{\"code\":\"confirmation_required\",\"message\":\"...\",\"confirm_url\":\"https://console.../confirm/<id>\",\"confirmation_id\":\"<id>\",\"retryable\":false}}\n```\n\nHandle it like this:\n\n- **Do not block.** Present `error.confirm_url` to the user and ask them to open\n it and approve. Under `--json` or any non-interactive (non-TTY) shell the CLI\n returns immediately; it never waits for approval, so your tool call will not\n hang.\n- **Approval executes the operation server-side.** Once the user approves in the\n browser, the operation runs automatically — you do **not** re-run the command\n with the confirmation id, and execution does not depend on the CLI process\n staying alive.\n- **Track the outcome via a real read path**, not by blocking:\n - *Delete*: teardown runs in the background. Poll `keelson apps list` — the app\n disappears once teardown finishes. (`keelson status` is deploy/runtime status\n for one app and does not track confirmations or teardown.)\n - *Restore*: acceptance is asynchronous — the CLI/confirmation reports the\n restore was **accepted (`queued`)**; there is no completion-tracking read path\n in the CLI. Treat `queued` as success and re-check the running app's own data.\n - Re-running the original destructive command instead creates a brand-new\n confirmation request — do not use it to poll.\n- **If a delete's teardown fails**, the app stays in the `deleting` state and\n cannot start. Re-approving is rejected — recovery requires an **administrator to\n re-enqueue the teardown**. Tell the user to contact an administrator rather than\n retrying yourself.\n- `--wait` (default only on an interactive TTY) polls until the approved\n operation finishes and prints the result/error; it never re-sends the request.\n Reserve it for humans at a terminal; agents should stay non-blocking.\n\n## Secrets\n\nNever put secret values in argv. Send a single value through stdin, or import a\nlocal env file when setting several values.\n\nFor a new app, put the values in a local env file and run\n`keelson deploy --new --secrets-from-env-file <path>`. The CLI creates the app,\nsets the secrets, and completes the initial deploy in one command; it also\nexcludes that file from the upload artifact. Do not try `keelson secrets set`\nfirst, because the app does not exist yet.\n\nFor an existing app, secret changes are injected at deploy time, so they are not\nactive until a redeploy. Use `keelson deploy --secrets-from-env-file <path>` to\nset several values and deploy in one command, or use the secrets command's apply\noption when the user wants the CLI to queue that redeploy immediately.\n\n## Access Control\n\nEvery authenticated workspace member with **view** permission can reach an app;\nnarrow access with two layers (see `core/KEELSON_YAML.md` → Access Control). Coarse\ncontrol (this group opens the app / only managers see the admin screen) = bind a\ngroup's view/manage permission via the Console or `keelson access set`, then have\nthe app branch on the injected `X-Keelson-User-App-Perms` header (`view` /\n`view,manage`) — **no group key in the app code**. Fine control (three or more\ntiers, arbitrary logic) = read `identity.attributes.groups` (a list of group\nkeys) through the Directory/identity API. Manage groups with `keelson groups ...`\nand app bindings with `keelson access ...`; there is no `groups:`/`access:` block\nin `keelson.yaml`.\n\n## Deploy And Operate\n\nWhen you write `keelson.yaml` for a **new** app, include a top-level\n`description`: 1–2 sentences in the user's language saying who the app is for and\nwhat it does (max 300 characters). It fills the app ledger's \"what is this app\"\nline, and it is applied only while that line is still empty — a console edit is\nnever overwritten. See `core/KEELSON_YAML.md` → App Description.\n\nUse `keelson deploy --ndjson --yes` as the standard way to\nwait for deployment completion. It streams one JSON object per line and ends\nwith `{\"result\":\"success\"|\"failed\",...}`; only `success` exits zero. A failure\nbefore watching starts ends with `{\"error\":{...}}` instead. Do not write your\nown deploy-status polling loop except for the recovery paths in\n`reference/VERIFICATION.md` → Alternative: recover with status. `--json` is for\ncapturing a deploy ID and returns immediately; it cannot be combined with\n`--watch`. Fetch diagnostics when a deploy does not complete successfully.\nRuntime, rollback, access log, deploy history, identity, and workspace-context\ncommands follow the same app-context and structured-error conventions.\n\nWhile a stage is running, the stream may include\n`{\"stage\":\"health_check\",\"status\":\"progress\",…}` records. They report elapsed\ntime and may include a server-provided waiting reason. Do not use progress\nrecords to decide the outcome; only a record containing `result` declares\nsuccess or failure.\n\nAfter a successful deploy, verify the delivered app with\n`keelson app curl -i /`. An unauthenticated `curl` proves nothing about your app:\nit returns 401.\nFor deeper checks (write paths, sub-resources), read\n`reference/VERIFICATION.md`.\n\n## Background Work\n\nApps scale to zero: code runs only while handling a request or during a\nplatform-initiated `cron` execution. Do post-processing that finishes\nwithin a request synchronously, before responding. Work scheduled to run *after*\nyou respond is not guaranteed and must be treated as not running. This is\nunconditional — no `keelson.yaml` setting turns it into a guarantee. For\nscheduled work, declare a `crons` entry — never an in-process scheduler\n(APScheduler, node-cron, `BackgroundTasks`, `setInterval`). Event-driven\nbackground tasks are not currently supported.\nBackground executions run in a separate container: their durable state must live\nin the managed database (`db.mode: libsql`), the `files` SDK, or the `media`\nSDK — never on the local disk, `/data` included. See `core/KEELSON_YAML.md` →\nBackground Work for the full contract.\n\nThe per-app managed libSQL database is provisioned automatically. The agent\nwriting the app also writes the small table/schema needed for cron history,\ncursors, or queues; the user has no database setup work. Never choose a local\nfile merely to avoid asking the user to configure a database.\n\nManaged libSQL enforces a **5-second interactive write transaction budget** and\ndrops idle connections after ~10 seconds — non-negotiable. Before writing any\nbulk insert / seed / backfill / import path, or any schema migration that\ntouches data, read `reference/LIBSQL_CLIENTS.md` → \"Write discipline — the\n5-second interactive-transaction budget\" and its error table. Bulk work is\nsupported (millions of rows in seconds when batched); a single long transaction\nis not (aborted at 5 s and blocks every other writer for that window). The same\nfile lists the four error shapes an agent must recognise —\n`stream not found`, `TRANSACTION_TIMEOUT`, `SQLITE_BUSY`, and constraint\nviolations arriving as bare `ValueError` rather than `IntegrityError` — with the\nrecovery for each.\n\n## Files\n\nThe instance filesystem is entirely ephemeral — a file the app writes to any\nlocal path is gone at scale-to-zero, on redeploy, and between\nthe web instance and a `cron` run. Route every file the app keeps by what it is:\na file the app names and updates (`state.json`, `seen_urls.json`) → the `files`\nSDK (`write` / `read`); uploaded or generated media referenced by ID → the\n`media` SDK (`put` / `get`, served at `/__keelson/media/<id>`); data read and\nwritten on every request → the managed database. See `core/DECISION.md` →\nRecipe: local file I/O for the routing rule and the per-language rewrites.\n\nWhen users need to retrieve a generated file, implement a download endpoint in\nthe app and return it from a normal authenticated app route with the appropriate\n`Content-Type` and `Content-Disposition` headers. There is no supported CLI path\nfor inspecting or mutating a running app's files. Read-only store retrieval and\nConsole download are post-launch work, so do not promise them.\n"
}SHA-256 of public snapshot: 3522bc818e6f48c6e37a44e6a3a0dfb66c29b589c3def3ec1252731428b15cba