git:20260719.32cb290 to git:20260724.6548f64

1 added, 1 removed. Audit A to A.

---
name: author-extension
description: Write a new yolop extension end-to-end — scaffold, implement, install, verify, and enable it — when the user asks yolop to add a capability, integrate with something, or "build an extension" for a need that no installed extension covers.
metadata:
internal: true
user-invocable: true
---
# Author an extension
Goal: turn a capability request ("integrate with X", "block Y", "add a tool
that Z") into a working, installed yolop extension — built by yolop itself.
An extension is a capability package served over YEP (the yolop extension
protocol): a `plugin.json` manifest plus a capability server that speaks
- newline-delimited JSON-RPC over stdio. See [`specs/extensions.md`](../../../specs/extensions.md).
+ newline-delimited JSON-RPC over stdio. See [`knowledge/specs/extensions.md`](../../../knowledge/specs/extensions.md).
## When to use
Use this when the user wants a *new, persistent* capability and no installed
extension provides it — something worth keeping across sessions, not a one-off
shell command. Signals: "make yourself a tool for…", "add an extension that…",
"integrate yolop with…", "from now on, block/allow…".
If an installed extension already covers it, use that instead
(`list_extensions`). If the need is a single throwaway action, just do it — do
not author an extension for it.
## What an extension can contribute
Declare only what you need; a manifest must contribute at least one of:
- **tools** — functions the model can call (the model sees name + schema).
- **hooks** — `pre_tool_use` (can **block** a tool call, e.g. deny git) or
`post_tool_use` (observe-only).
- **prompt** — a static system-prompt contribution.
- **mcpServers** — contributed MCP servers (declare in the manifest directly).
- **status** — a status-bar field the server updates live by pushing
`status/changed` (e.g. a counter). Scaffold with `status: true`; the generated
server gets an `emit_status(text)` helper (empty text clears the field). Shows
in the inline and full-screen TUIs; a no-op in `--print`/ACP.
- **skills** — a `skills/` directory of `SKILL.md` files, mounted read-only for
the enabled extension (same discovery as workspace/global skills). Scaffold
with `skills: true` to get a starter `skills/<name>/SKILL.md`.
- **commands** — slash commands, registered namespaced as `/<ext>:<cmd>` in the
palette and dispatched to the server over `command/execute` (the result
message is shown to the user). Scaffold with `commands: ["name", …]`; the
generated server gets a `handle_command(name, arguments)` seam.
- **ui_ask** — declare `ui_ask: true`, then the server may send a `ui/ask`
request (`{prompt, placeholder?}`) to prompt the user for a typed answer
mid-turn; the TUI shows an overlay and returns `{answer, cancelled}`. Refused
in `--print`/ACP. (No scaffold-template helper yet — send the request by hand
from your server.)
Not yet available: providers.
## The loop
1. **Scaffold.** `scaffold_extension name=<name> [description=…] [language=python|typescript|rust]`
with the facets you need — `tools=[…]`, `hooks=[…]`, and/or `prompt=…`.
This writes a package (manifest + server source) with the handler bodies
stubbed. `python` and `typescript` (a dependency-free Node.js server) are
single-file and need no build step; `rust` emits a `serde_json`-only crate.
Pick the one whose toolchain the environment has; Python is the default.
For `rust`, the scaffold result includes a `build` command — run it after
editing to compile the binary into `bin/` before installing. The zero-build
templates skip that.
2. **Implement.** Open the generated server (the tool result prints its path)
and fill in the `handle_*` bodies:
- `handle_tool(name, args)` — return the tool result dict.
- `handle_hook(event, tool_name, args)` — return `{}` to allow, or
`{"block": True, "reason": "…"}` to deny (pre_tool_use only). The server
receives *every* subscribed tool call, so gate on `tool_name`/`args`.
- Keep stdout for protocol JSON only; log to stderr.
Edit only the marked handler bodies — leave the protocol plumbing alone.
3. **Install.** `install_extension source=<package dir>` — copies the package
into the store and pins it. Installing runs third-party code; since *you*
authored it here, the sharp edge is enabling — see step 5.
4. **Verify.** `doctor_extension name=<name>` — spawns the server, runs the
handshake, and checks the served tools/prompt against the manifest. Fix any
`fail`/`warn` before enabling. If the server won't spawn, check the shebang
and that the command is executable.
5. **Enable (ask first).** Show the user the contribution summary and ask
before `enable_extension name=<name>` — enabling adds it to the harness and
runs its server every session. In the interactive TUI it is **applied to the
running session immediately** (activated on the live agent), so its
tools/prompt/hooks are available on the **next turn** — no restart — and it's
persisted for future sessions. In `--print`/ACP there's no live session to
mutate, so it only takes effect next run.
6. **Iterate live (already-enabled extensions).** Once an extension is enabled
and its server has run this session, `reload_extension name=<name>` restarts
that server in place so edits to its *implementation* take effect
immediately — no yolop restart. This is the fast inner loop for fixing a
handler you authored: edit the server, `reload_extension`, call the tool
again. The manifest is the session's approval boundary, so a *surface*
change (adding a tool, changing a schema) still needs a restart, and reload
can never widen the grant.
## Acceptance check
The loop is proven when yolop, unaided, produces an extension that actually
works — e.g. a `pre_tool_use` hook that blocks any `git` command, or a tool
that returns a computed value — installed, doctor-green, and effective after a
restart. The end-to-end deny path is covered by
`scaffolded_extension_blocks_git_end_to_end` in `src/extensions/mod.rs`.
## Notes
- Names: ascii letters, digits, `-`, `_`. The dir name must equal the manifest
`name`.
- Do not hardcode absolute paths in the manifest — the package is copied on
install. The scaffold resolves the server via the package's `bin/` on PATH.
- Iterate by editing the server and re-running `doctor_extension`; no reinstall
is needed while working from the scaffolded dir, but re-run `install_extension`
to pick up edits into the store.