182 lines
9.9 KiB
Markdown
182 lines
9.9 KiB
Markdown
# 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 is watching
|
|
`/home/pastilhas/officer-dev/platform` and pulling into it as you push. Two processes in one working tree
|
|
is how work gets lost — on 2026-08-20 it nearly did.
|
|
|
|
```
|
|
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`.
|
|
|
|
**Push every commit straight to master**, from the worktree:
|
|
|
|
```
|
|
git push origin HEAD:master
|
|
```
|
|
|
|
The branch is only there to keep your files out of the other checkout. Master is what is being reviewed.
|
|
Push after every task — a reviewer reading five small commits can tell you what went wrong on the second
|
|
one; nobody can do that with one commit at the end.
|
|
|
|
**Your final commit message must contain `ATELIER-COMPLETE` on its own line**, and no earlier commit may
|
|
contain that string. It is the signal that the run is finished and the review can start. Do not use it to
|
|
mean "this part is done".
|
|
|
|
## 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. **`docs/secret-store.md`** — Atelier holds an OpenAI key.
|
|
6. **`src/workspaces/officerdev/APP_CONVENTIONS.md`** and `HOOK_CONVENTIONS.md` — you are writing panels.
|
|
|
|
## 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 for one reason that survives the UI moving into Officer: generation is
|
|
long-running, capped by `ATELIER_MAX_CONCURRENCY`, and holds a credential the platform must not. The UI
|
|
is Officer's; the work is the sidecar's.
|
|
|
|
## 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 is PORTED into Officer. No proxy, no iframe, no sidecar-served UI.** Atelier was a separate
|
|
server because that was the quick answer at the time; this extraction is what undoes that. It becomes part
|
|
of Officer, not something Officer frames.
|
|
|
|
It is smaller than the repository suggests:
|
|
|
|
```
|
|
src/apps/frontend 1,736 lines / 25 files
|
|
6 of those components/ui/* — shadcn Officer ALREADY HAS. Delete, import from @/components/ui.
|
|
real work Generate · Gallery (+ Lightbox) · Collections · Providers ≈1,200 lines
|
|
src/apps/web the htmx half — 8 files, DELETED, not ported
|
|
```
|
|
|
|
So: three panel apps — **Generate**, **Gallery**, **Collections** — under
|
|
`plugins/atelier/web/`, each exporting `appRegistryMetas`, with a `layout.ts` composing them. Follow the
|
|
frontend route conventions in `CLAUDE.md`: selection lives in the URL, rows are real links, no
|
|
`usePanelChannel` for "which thing is open".
|
|
|
|
The fourth feature, **Providers**, is not a panel — see the next decision.
|
|
|
|
**2. Providers become Officer settings, with more than one.** `features/Providers/` is a settings screen,
|
|
and Officer has a settings surface already. It moves there rather than becoming a panel.
|
|
|
|
`src/servers/providers/` is already adapter-shaped — `types.ts`, `openai.ts`, `credentials.ts`, an
|
|
`index.ts` that dispatches. Keep that shape and keep the seam: today OpenAI is the only adapter, and the
|
|
point of moving the settings is that a second one is a new file rather than a new screen. **Do not add a
|
|
second provider in this run** — leave the shape able to take one and say so in `PLUGIN.md`.
|
|
|
|
**3. 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.
|
|
|
|
**4. 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.
|
|
|
|
**5. `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.
|
|
|
|
**6. 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 <paths>`, 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.)
|
|
- **Generated images are served through an authenticated route**, like every other plugin artefact. Every
|
|
image having "a permanent URL" was true of a standalone server with its own `PUBLIC_URL`; inside Officer
|
|
the URL is behind the session. If something needs an image without one — an `<img>` in an email, a
|
|
share link — that is a blocker to write down, not a route to open. See the image-rendering affordance
|
|
in `capabilities/affordances/officer/core/` for why an `<img>` cannot carry a token.
|
|
- 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.
|