Building extensions
Choose the tools, views, and service parts your package needs.
fez's rule is simple: if a feature can live behind a seam, it must. Core grows only when a new seam is needed — never a new feature. Extensions are how everything user-facing ships, and this section is the contract you build them against.
The surface model
A fez extension is one npm package with optional attachment
points, declared under the fez key in package.json. Executable parts are
bundled files installed under ~/.fez/; their hosts load them with an injected
API. Declarative GUI parts are JSON rendered by the host. Skill definitions
and background flags configure how a package attaches rather than providing
another executable bundle.
| Part | Installs to | Loaded by |
|---|---|---|
headless | ~/.fez/extensions/ | the TUI; permitted background parts also load in the desktop worker or optional sentinel |
gui | ~/.fez/packages/<name>/ on the current desktop | declarative host rendering or the declared GUI runtime; legacy CLI installs may also stage gui-extensions/ |
miner | ~/.fez/miners/ | the mining harness |
relay | ~/.fez/relay-extensions/ | a relay started with --extensions |
workspace | ~/.fez/workspace-providers/ | the agent runtime, for repo: personas |
skill | settings.json → mcpServers | agents at spawn (an MCP server definition) |
background: true | settings.json allowlist | opts scheduled tasks into the desktop worker or optional sentinel |
Plus npm's own bin map (→ ~/.fez/bin/) and fez.personas for shipping
persona packs — @fezchat/score-studio is a
complete extension that ships only a persona.
One fez install places every part; each host picks up its own. In the current
source implementation, the desktop selects a GUI runtime from the manifest and its separate
fez-background process loads permitted background headless parts. Headless
commands still belong to the TUI; desktop commands need a gui part.
Choose a GUI runtime before building a desktop part. Declarative data covers standard settings and themes; isolated child views cover custom UI. An omitted runtime selects the legacy shared webview.
The contract package
npm install -D @fezchat/extension-apiimport type { FezExtensionAPI } from "@fezchat/extension-api/headless";
import type { GuiExtensionApi } from "@fezchat/extension-api/gui";
import type { RelayExtensionAPI } from "@fezchat/extension-api/relay";
import type { WorkspaceProvider } from "@fezchat/extension-api/workspace";It is types only — every export erases at bundle time, so your shipped
part carries zero runtime dependency on fez. (Some first-party packages
instead carry a hand-written structural mirror in src/api-types.ts; a
conformance eval keeps every mirror honest against the host. For new
extensions, use the package.)
The published contract is a deliberate subset of what hosts implement; a few host-only registration seams (harnesses, themes, system-prompt sections) are noted on each surface's page.
Which surface do I need?
- A slash command or a scheduled job → headless
- A card under messages, a panel, a view over a doc → gui
- Serving HTTP from the relay (git, media) → relay
- Giving a
repo:persona its checkout → workspace - A tool agents call → the
skillpart (an MCP server your package ships — see the manifest)
Worked examples in the repo: packages/fez-polls (headless + gui + skill
sharing one vote-logic module), packages/fez-git (all four surfaces plus
two binaries), packages/fez-score-studio (personas only, no code).
Start with the dev loop: fez create,
fez link, publish.