# Plugins `EXTRACTING-A-PLUGIN.md` is the runbook for turning a platform feature into a plugin, and `EXTRACT-A-PLUGIN-PROMPT.md` is the way to hand that to a fresh agent. This file is for the concepts a plugin author needs that are not procedure. **This directory holds documentation, not plugins.** Every plugin is its own repository; `plugins//` appears by `git clone` when somebody installs one, and is gitignored here. --- ## Affordances **`docs/affordances.md` is the authority** — what one is, the three tests for whether something qualifies, the file format and how they reach the agent. Read it before writing one. What follows is only the part that is specific to shipping one from a plugin. > An affordance is something the surface on the other end of the conversation offers the agent — declared > by the code that implements it, and read by the agent at runtime. Not what the agent can do; what the > client will honour. A plugin ships an `affordances/` directory of markdown-with-frontmatter in its repository. Installing copies it to `capabilities/affordances///`, uninstalling removes it — the same shape as `assets/` and `publishAssets`, and the namespace comes from where it lands rather than from anything inside the file. **The test that catches most bad ones: can it be fixed instead?** If the answer is yes, fix it. An affordance describing a defect gives that defect a rationale, and the rationale outlives everyone's memory that it was a bug. See the two worked examples in `docs/affordances.md`. Keep it short — every affordance is loaded into every conversation on this machine, including turns that have nothing to do with your plugin. ### A plugin-contributed one, for reference## Notifications — the same shape, for push A plugin can also declare what its push notifications MEAN. Same mechanism, different store: ``` capabilities/notifications/ officerdev/music/ catalogue.yaml ``` ```yaml nowPlaying: title: 'Now playing' route: 'officer://music/{id}' ``` That becomes the code `officerdev/music:nowPlaying`. A push carries the code and integers — never content — and the device renders the title and follows the route. The point is that **adding a notification is a server change and never an app build**, because the mapping is synced to the device rather than compiled into it. **The namespace comes from where the file lands, not from what it says.** Writing `officer/core:test` as a key in your own catalogue produces `/:officer/core:test`, not the core code — the same rule as `mountPrefix`, and pinned by test in `src/servers/notifications/catalogue.test.ts`. An entry needs a `title`; `body`, `target`, `route` and `thread` are optional, `thread` defaults to your namespace, and unknown keys are dropped rather than forwarded to the device. Broken YAML in one plugin's catalogue is skipped without affecting anyone else's. See `docs/push-notifications.md`. ### A plugin-contributed one, for reference This is the gitea plugin's, and it shows what these are for: the agent cannot discover from the filesystem that `tea` is authenticated, or that it should prefer `git` for the things git already does. ```markdown --- name: Gitea description: Use when working with git repositories, or with Gitea issues and pull requests. version: 1 --- For anything git already does — clone, fetch, pull, push, branches, tags — use plain `git`. It talks to the server directly. For anything git does not — pull requests, issues, comments, releases, creating a repository — use `tea`. It is already logged in as the current user, with that user's own permissions. Do not configure it, and do not ask the user for a token. ``` ### On the word `capability` was taken — `CAPABILITIES` is the permission registry. `skill` was taken twice: by `capabilities/skills/` here and by the wider AI ecosystem. `affordance` is the exact term for what an environment offers an actor, and the precision earns the slight awkwardness: rendering an image is not a skill the agent has, it is something the client on the other end permits. Keeping that distinction in the name saves explaining it every time.