--- name: autokap-init description: Equip a repository with autokap — write captures/config.mjs, adapter.mjs and scope.mjs by reading the project's own migrations, cron routes, workers and triggers, then create the demo account. Use when the user says "set up autokap", "install autokap", "/autokap init", when captures/scope.mjs is still the empty template, or when a world or shot task cannot proceed because the perimeter is not declared. --- # autokap-init — equipping a project Installing the plugin dropped a doctrine. It dropped no perimeter, no inertia, no adapter and no world. **`init` is the real installation, and it is the one session that needs the user's attention.** Budget: about 40 minutes of conversation on a repository you have never seen. If it is heading well past that, you are rebuilding the hosted service that died — stop and simplify. ## Step 0 — an image before anything else Run this first, and do not skip it: ``` npx autokap init ``` It probes the repo, writes the scaffold, and creates one shot of the app's public landing page. Then, with the dev server running: ``` npx autokap run home ``` **Get that image on screen before you ask for a single credential.** A public page must produce a picture before auth or data have been touched. If the user has to answer questions before seeing anything work, they will not come back — that is the entire post-mortem of the product this replaces. If Playwright is missing, say exactly: `npm i -D playwright && npx playwright install chromium`. ## Step 1 — the perimeter (`captures/scope.mjs`, `SCOPE`) Read the project's migrations or schema. For every table a demo screenshot could need, declare: - `writable: true` if seeds write it, `false` if it only serves as an anchor. - `anchors.owner` — the column pointing at the identity (`owner_id`, `user_id`, `created_by`). - `anchors.parents` — for tables with no identity column of their own, the columns pointing at a table that is already proven demo. - `userRefs` — **every** column pointing at a user, not just the owner. This is what stops a demo row being assigned to a real person. Miss one and a real customer gets a fake ticket. Two rules the engine enforces, so get them right the first time: - **Declaration order is dependency order.** A parent above its children. - **A table with no anchor is refused at load.** If a table genuinely has nothing pointing at the demo world (a global lookup table), it does not belong in SCOPE — read it, never write it. Do not declare tables "just in case". The perimeter is meant to be narrow, and widening it later is a diff the user will see, which is exactly the point. ## Step 2 — inertia (`SCOPE[*].inert`) **This is the step nobody else does, and the one that shows up on a bill.** A demo row is perfectly legitimate as far as the schema is concerned. That is the problem: a cron will claim it. Inertia is not derived from the schema. It is derived from the code. Go and read: - cron routes (`app/api/cron/**`, `vercel.json`, scheduled functions) - background workers and queue consumers - database triggers and scheduled jobs in the migrations - anything named `drain`, `sweep`, `requeue`, `retry`, `stuck`, `pending`, `poll` For each, ask: *which rows does it pick up?* Then declare the condition that keeps a demo row invisible to it: ```js agent_runs: { writable: true, anchors: { parents: [{ column: "issue_id", table: "issues" }] }, inert: { status: { notIn: ["queued", "running"], why: "the drain cron claims `queued`, and requeueStuckRuns restarts anything " + "`running` for more than 6 minutes — the agent would actually run: " + "sandbox, billed LLM calls, a write to a repository.", }, }, }, ``` Predicates: `notNull`, `isNull`, `in`, `notIn`, `equals`. **`why` is mandatory** and the engine refuses to load without it. Name the job and what it would cost. In six months that sentence is the only way to know whether the constraint still holds. Report what you found to the user in plain words: *"your hourly AI pass picks up posts where `analyzed_at` is null, so demo posts must have it set, or a model gets called on our fake data and you get charged."* ## Step 3 — entitlements (`ENTITLEMENTS`) An account on the free plan photographs paywalls. Find the lever the product already has for granting a right — an admin override column, a plan id, a flag — and declare it. ```js export const ENTITLEMENTS = { billing_accounts: { columns: { admin_override_plan_id: "" }, why: "the free plan puts agents and pull requests behind a plan gate; " + "no Stripe row is created.", }, }; ``` The table must also be declared in `SCOPE` with `writable: true` — granting a right is a write like any other, and it goes through a plan like any other: [autokap-world](../autokap-world/SKILL.md) calls `planEntitlements(world, plan)`, shows the description, and applies it after a clear answer. Declaring the right here does not grant it. **Never a simulated payment state.** No fake Stripe rows, no invented subscription records. If the only way to grant the right is to fake a payment, say so and stop: that is a product decision, not yours. ## Step 4 — the adapter (`captures/adapter.mjs`) About thirty lines. Read how the repo already talks to its database and write the same thing: ```js export const mode = "direct"; // or "indirect" export async function select(table, { where, columns, limit }) // → rows export async function count(table, { where }) // → number export async function insert(table, rows) // → rows export async function update(table, { where }, patch) // → rows export async function remove(table, { where }) // → number ``` `where` is a simple equality (`{ id: "…" }`) or `{ column: { in: [...] } }`. Nothing else. Do not add a `sql()`, a `rpc()` or a `query()` helper — the engine refuses to load an adapter that exports anything outside the contract, because an escape hatch that exists is an escape hatch that gets used. Optional, and worth having: `findUsers({ emailPattern })` so the demo family is found without scanning the users table, plus `createUser` / `deleteUser`. Absent, those capabilities become manual. A Supabase reference implementation ships at `templates/adapter.supabase.mjs` in the autokap package. Copy it, adapt it, do not import it. ## Step 5 — direct or indirect Ask the user, plainly: > Do you have a service key for this database that I can use — one that bypasses > row-level security? If yes, I can create the demo data myself and check my own > work. If no, I will write scripts and you will run them. | | direct | indirect | |---|---|---| | Who writes | the engine | the user, running the file | | `plan.apply()` | executes | refuses | | `plan.emit()` | — | writes `world/seed/NNN-….sql` to run | | Re-read before modifying | yes | impossible | | Blast radius | measured | not measurable | | Demo account created by | `adapter.createUser()` | **by hand** — you say what to create, the user pastes back the id | There is no third mode where you create the data by driving the UI. It is tempting and it is a trap: learning an unknown interface is expensive exploration that does not compound, while the schema is already in the repo. In indirect mode, say clearly that the guard rails protect nothing — there is no hand to hold back. The safety comes from the user reading the file. ## Step 6 — the demo account The identity pattern is the only thing that says which account is the demo account. Everything anchors on it. ```js export const IDENTITY = { emailPattern: /^autokap-demo(\+[a-z0-9-]+)?@example\.com$/, }; ``` Direct mode with `createUser`: create the account and its family. Otherwise: tell the user exactly what to create through their own signup screen, and have them paste the ids into `IDENTITY.knownIds`. Then write `captures/world/world.md`: who the family is, what they own, what it is all for. That file is the readable registry, and in indirect mode it is the only known version of the world. ## Step 7 — hand over Show the user: - what the perimeter now allows, table by table, in one sentence each - every inertia constraint you found and what it costs if it is wrong - `npx autokap run` to replay everything - `npx autokap status` to see what drifted Offer `npx autokap init --vendor` if they use an agent that is not covered by Agent Plugins or Claude Code: it copies the skills into `.claude/skills/` and depends on no ecosystem at all. ## Hard rules - Never run migrations, never reset, never truncate, never reseed globally. - Never write through the application's HTTP API. Routes fire assignment, notifications, analytics, emails and billing. Write to the database. - Never put a service key anywhere but `.env`. - If you cannot determine an anchor for a table, leave it out. An unanchored table is not a scoping puzzle, it is a table that has no business being written. Related: [autokap-world](../autokap-world/SKILL.md) creates the data, [autokap-shot](../autokap-shot/SKILL.md) takes the pictures.