diff --git a/plugins/EXTRACT-ATELIER-PROMPT.md b/plugins/EXTRACT-ATELIER-PROMPT.md new file mode 100644 index 00000000..d93adcf1 --- /dev/null +++ b/plugins/EXTRACT-ATELIER-PROMPT.md @@ -0,0 +1,140 @@ +# Kickoff prompt: extract Atelier into a plugin + +Written 2026-08-20 for an **unattended overnight run**. That changes two things from +`EXTRACT-A-PLUGIN-PROMPT.md`, which assumes the owner is awake: + +- **It does not say "ask and wait".** Nobody is there. Decisions that would normally be questions are + pre-made below, with reasoning, so the run does not stall on them. +- **It assumes you may be restarted.** Commit after every task, so whoever picks this up — including you, + an hour later, with no memory of it — finds a clean tree and a readable history. + +Paste everything below the line into a fresh agent. + +--- + +You are extracting **Atelier** out of `~/officer-dev/dockers/officer-atelier` and into an Officer plugin, +the way `music`, `offscale` and `gitea` were extracted before it. + +**Work in a git worktree, not the main checkout.** Another agent may be working in +`/home/pastilhas/officer-dev/platform` tonight. On 2026-08-20 two agents with bypassed permissions ended +up in one working tree and only avoided losing work because one of them noticed and stopped. Do not +arrange that again: + +``` +git -C /home/pastilhas/officer-dev/platform worktree add /home/pastilhas/officer-dev/atelier-work -b atelier-plugin +``` + +Everything below happens in `~/officer-dev/atelier-work`. + +## Read first, in this order + +1. **`plugins/EXTRACTING-A-PLUGIN.md`** — the runbook and the authority. Read "What changed on + 2026-08-16" carefully; it contradicts an earlier instruction in the same file, deliberately. +2. **`plugins/example/README.md`** — what a plugin is, as conventions. +3. **`src/servers/plugins/`** — `discover.ts`, `manifest.ts`, `mount.ts`, `install.ts`, `ecosystem.ts`. + Where this prompt and that code disagree, **the code is right**. +4. **`CLAUDE.md`** — house rules, the permission model, and why `assertPermissionTotality` refuses to boot. +5. **`plugins/gitea/`** — because of the UI decision below. It solves the same problem. +6. **`docs/secret-store.md`** — Atelier holds an OpenAI key. + +## What Atelier is + +Image generation as a service: a provider-shaped API over OpenAI's image endpoints, storing what it makes +so every image has a permanent URL. Bun · Hono · Postgres/Drizzle · htmx with Hono JSX · a React SPA. + +``` +84 ts/tsx files, ~6,400 lines +tables collections, provider_settings (its own `atelier` database) +routes /generations, /generations/:id, /edits, /adapters, + /:id/items, /:id/items/:generationId, mask +env BUN_PORT, PUBLIC_URL, POSTGRES_URL, ATELIER_DATA_DIR, + OPENAI_API_KEY, ATELIER_MAX_CONCURRENCY +runs as pm2 `officer-atelier`, cwd ~/officer-dev/dockers/officer-atelier +``` + +It is a **sidecar** plugin, not an API-only one, for the two reasons that always decide this: it does +long-running work with a concurrency cap, and it holds a credential the platform must not. + +## Decisions already made — do not re-open these + +Each of these would normally be a question. They are answered so you can keep moving. If you find +evidence that one is wrong, **write it in `BLOCKERS.md`, follow the decision anyway, and carry on** — a +half-applied decision is worse than a wrong one consistently applied. + +**1. The UI: follow gitea, do not port it.** Atelier ships htmx _and_ a React SPA with its own build +(`build-web.ts`, `gen-index.ts`). Rewriting 84 files into panels is not an overnight job and is not the +point of this extraction. Instead: the sidecar keeps serving its own UI, the plugin proxies it +same-origin with `createSidecarProxy`, and a single panel renders it in an iframe. `plugins/gitea` does +exactly this and its `sidecar/rewrite.ts` records the traps. **Read that file before writing any of it.** + +**2. The database: check the rows, then decide.** Atelier uses a separate `atelier` Postgres database, +deliberately — `src/databases/atelier_db/drizzle.config.ts` says why. Plugin tables normally live in +`officer_db` with a prefix. + +- Count the rows in `collections` and `provider_settings` first. +- **If both are empty**, fold them into `officer_db` as `atelier_collections` and + `atelier_provider_settings`, following `plugins/*/db/schema.ts`. Two tables do not earn a second + database. +- **If either has rows**, keep the separate database for now and record the migration as a follow-up in + `BLOCKERS.md`. Losing someone's generated images to a schema decision at 4am is not a trade worth making. + +**3. Artefacts: `$OFFICER_ROOT/data/atelier/`.** Generated images are files, and `ATELIER_DATA_DIR` +currently points wherever its `.env` says. A plugin directory is a git clone that an update replaces, so +artefacts kept there vanish on upgrade with nothing logging it. `plugins/kiosk/artefacts.ts` has the same +problem solved and the comment explaining it. + +**4. `OPENAI_API_KEY` goes in the secret store**, not `.env`. `putSecret`/`getSecret` landed 2026-08-19; +`bun run secret:set atelier-openai-key` puts it there. Read with `getSecret`, **never `getKey`** — `getKey` +mints a random value for an absent purpose, which for a foreign credential means silently authenticating +with a fake. Fall back to env when the store has nothing, so an install that predates this keeps working. + +**5. One permission, `atelier`, `kind: 'app'`.** Image generation is not `execution` — it does not run as +the owner's OS user in the owner's home. + +## How to work + +**Map before you move.** The first task is not a file move. Find every line in the platform that names +atelier, and every import pointing INTO it. A plugin may import platform code (`@@/…`); the reverse cannot +happen. Report what you find before moving anything. + +**Commit after every task, and push.** You may be restarted at any point by a scheduler that has no idea +what you were doing. A clean tree and a commit message are how the next attempt — possibly you — knows +where it is. Commit messages: simple lowercase, no prefixes. + +**Do not restart the platform.** `pm2 restart officer` breaks the owner's chat session. Write the command +down instead; they will run it. + +**Do not `git add -A`.** Add the paths you touched. This has caused two commits to swallow another +agent's work in one day. + +**Format only what you touched** — `bunx prettier --write `, never `bun format`. + +**`bunx tsgo` must be clean** before every commit. Not `tsc`. + +## When you get stuck + +Write it in `BLOCKERS.md` at the worktree root — what you were doing, what stopped you, what you did +instead, and whether it is reversible. Then **move to the next task**. Do not wait, do not invent a +workaround that changes behaviour without saying so, and do not stop the run. + +If invented content ends up anywhere durable — copy, defaults, seed data — mark it `[placeholder]` so it +can be found later with a grep. + +## Done means + +- `plugins/atelier/` with `manifest.ts`, `api/router.ts`, `sidecar/index.ts`, `web/panels.ts`, + `web/layout.ts`, `db/`, `assets/icon.png`, `assets/splash.png`, and its own git repository. +- It installs from the Marketplace onto a machine that has never had it, and the panel renders. +- `bunx tsgo` clean, `bun test` passing. +- A `PLUGIN.md` in the plugin recording what moved, what did not, and why — the way `music` and `offscale` + did. This is the part that is worth most in three months. +- `BLOCKERS.md` listing every decision you made without being able to ask. + +## What you may NOT do + +- No unauthenticated routes. A plugin cannot serve one; `UNPROTECTED_API_PREFIXES` is hardcoded in the + platform. If Atelier needs one, that is a blocker, not something to work around. (`plugins/kiosk` + is stuck on exactly this — read its README before concluding you have found a way.) +- No changes to `src/servers/plugins/` to make Atelier fit. If the contract does not accommodate it, that + is a finding worth more than the workaround. +- No second agent, no second worktree, no background job that writes to this tree.