# Kickoff prompt: extract a feature into a plugin Paste everything below the line into a fresh agent, replacing `` with the feature's name (`photos`, `jellyfin`, `email`, `soulseek`, `wallet`, …). Nothing else needs to be said first. --- You are extracting **``** out of the Officer platform and into a standalone plugin, the way `offscale` and `music` were extracted before it. Work in `/home/pastilhas/officer-dev/platform`. ## Read these first, in this order, before touching anything 1. **`plugins/EXTRACTING-A-PLUGIN.md`** — the runbook. It is the authority: the rules, the order that worked, the verification table, and a list of traps that each cost real time once. Read the section "What changed on 2026-08-16" carefully; it contradicts an earlier instruction in the same file, and the contradiction is deliberate. 2. **`plugins/example/README.md`** — what a plugin IS, stated as conventions, each one verified against `src/servers/plugins/discover.ts` rather than remembered. 3. **`src/servers/plugins/`** — the system itself. `discover.ts` (what a directory must contain), `manifest.ts` (the contract and `mountPrefix`), `mount.ts`, `install.ts`, `schema.ts`, `ecosystem.ts`. When this prompt and that code disagree, the code is right. 4. **`CLAUDE.md`** — house rules for the whole repository. Style, the permission model, and why `assertPermissionTotality` refuses to boot the server. 5. If `` holds credentials: **`docs/secret-store.md`**. Then read the two plugins that already did this. Both carry a `PLUGIN.md` recording their own extraction: `gitea.officer.dev/plugins/music` and `gitea.officer.dev/plugins/offscale`. Music is the harder case and the more useful one, because its boundary was dictated by imports pointing the wrong way. ## What "done" means `` is a public repository that a stranger's Officer can install from the marketplace, and the platform contains no trace of it beyond dated history. Specifically: - its own repo on `gitea.officer.dev/plugins/`, public, cloning anonymously over https - a `manifest.ts`, and `api/`, `db/`, `sidecar/`, `web/` as it needs them - a README that works as both documentation and shop window - a row in the marketplace catalogue, and a matching entry in `BUNDLED` in `src/servers/plugins/marketplace.ts` - installs, enables, disables, uninstalls and reinstalls on a live server, verified by you, not reasoned about - `bunx tsgo` clean, `bun test` with no new failures against the baseline you recorded before starting ## How to work **Map before you move.** The first task is not a file move — it is finding every line of platform code that names ``, and every import pointing INTO it from `src/`. A plugin may import platform code (`@@/…`); the reverse cannot happen. Anything in `src/` that reaches into `` and cannot move decides where the boundary goes. Music's boundary was chosen by two such imports, found by reading the graph rather than by reasoning about what music "is". Report what you find before you start moving. **Ask before deciding anything that is not mechanical.** The owner would rather answer a question than review a wrong assumption. Say what you would do and why, and wait, when: a piece cannot cleanly move; a permission's shape is not obvious; the split would change behaviour for a member; or the feature wants something the plugin contract does not offer (a global overlay, a websocket, a component export — the answers are no, not yet, and no, and the runbook says why). **Verify on the live server, and purge between attempts.** Uninstall deliberately keeps the directory and the tables, so a second install is not a fresh one. Use the Marketplace screen's "Delete … and all its data", then install from the catalogue. That is the only run that resembles a stranger's machine. **Do not restart the platform yourself.** Hand the owner the command. `pm2 restart officer` breaks their chat session, and they will run it when they are ready. **Report honestly.** Say what you verified and what you did not. "I did not test the member path" is useful; a claim you did not check is not. If you assert a behaviour, name the file and line, or the command whose output you read. ## When you get stuck Blockers go in writing, not into a workaround. Write them down, say plainly that you are blocked and on what, and move to the next piece. The runbook's "Still open, platform-wide" section lists problems that are already known — check there before treating one as new. ## Finishing Commit and push both repositories — the plugin's and the platform's — including if it went badly. A reverted commit with an explanatory message is worth more than a clean history that hides an abandoned approach. Commit messages: simple lowercase, no prefixes, and say what moved, what it found, and what is still open. Then update `plugins/EXTRACTING-A-PLUGIN.md`: add what this extraction taught, and correct anything it told you that turned out to be wrong. The runbook is the thing that makes the next one cheaper, and it is only worth that if each extraction leaves it more accurate than it found it. ## Colour is part of the job The feature you are extracting almost certainly hardcodes Tailwind palette classes — `text-emerald-400`, `bg-zinc-800`, `border-red-500/30`. Replace them with semantic tokens as you go: `text-success`, `bg-muted`, `border-destructive`, `bg-busy/15`. They follow the theme; a palette number does not. Read `src/workspaces/theme/README.md` for the contract and `src/workspaces/components/Data/tone.ts` for the closed set of state tones. `bun run check:theme` counts what is left. Do it during the extraction rather than after. You are already rewriting every import in these files, and once the directory leaves the platform the ratchet stops covering it.