| 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 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
A manifest must contribute at least one facet; declare only what you need.
extensions.md defines each one — this is what to pass the
scaffold and what it generates.
| Facet | Scaffold | Generated seam |
|---|
| tools | tools=[…] | handle_tool(name, args) returning the result dict |
| hooks | hooks=[…] | handle_hook(event, tool_name, args) — pre_tool_use can block, post_tool_use observes |
| prompt | prompt=… | a static system-prompt contribution |
| mcpServers | — | declared in the manifest directly |
| status | status: true | emit_status(text); empty text clears the field. TUI-only, a no-op in --print/ACP |
| skills | skills: true | a starter skills/<name>/SKILL.md, mounted read-only when the extension is enabled |
| commands | commands: ["name", …] | handle_command(name, arguments); registered as /<ext>:<cmd> |
| ui_ask | — | declare ui_ask: true and send ui/ask ({prompt, placeholder?}) by hand; refused in --print/ACP |
Not yet available: providers.
The loop
-
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.
-
Implement. Open the generated server (the tool result prints its path)
and fill in the marked handle_* bodies; leave the protocol plumbing alone.
A hook returns {} to allow or {"block": True, "reason": "…"} to deny —
and it receives every subscribed tool call, so gate on tool_name/args.
Keep stdout for protocol JSON only; log to stderr.
-
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.
-
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.
-
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.
-
Iterate live (already-enabled extensions). Once an extension is enabled
and its server has run this session, restarts
that server in place so edits to its take effect
immediately — no yolop restart. This is the fast inner loop for fixing a
handler you authored: edit the server, , call the tool
again. The manifest is the session's approval boundary, so a
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.