The manifest
The fez key in package.json, and the permission vocabulary.
Everything fez needs to know about your package lives under the fez key in
package.json, next to npm's own fields:
{
"name": "@you/fez-something",
"version": "0.1.0",
"type": "module",
"files": ["dist"],
"bin": { "my-tool": "dist/tool.js" }, // npm's own bin map → ~/.fez/bin
"fez": {
"type": "extension", // extension | integration | agent | persona-pack
"requires": ["gh"], // external binaries — reported, never installed
"permissions": ["commands", "ui", "read:channels"],
"parts": {
"headless": "dist/headless.js",
"gui": "dist/gui.js",
"relay": "dist/relay-part.js",
"workspace": "dist/workspace-part.js",
"miner": "dist/miner.js",
"skill": { "command": "node", "args": ["dist/mcp.js"], "env": { "API_KEY": "" } },
"background": true
},
"personas": { "dir": "personas", "defaults": { "harness": "pi" } }
}
}Field notes:
fez.requires— declared rather than discovered, so "needsgh" is data checkable at install, at startup, and on a second machine. fez never installs these; it reports them. The failure it prevents is the quiet one: an extension that loads, registers, runs, and silently does nothing.parts.background: trueis an allowlist entry, not a default — without it neither background host loads your headless part. It's distinct from thebackgroundpermission: you need the part flag to be loaded by the desktop worker or sentinel and the permission forregisterScheduledTaskto work.parts.skillmerges into the user'ssettings.jsonmcpServers. Env names come from you; values the user filled in are always preserved, and secret values live in the OS keychain, never in listings.fez.personasships a persona pack: every.mdin the directory is validated before anything installs (one bad file rejects the pack), packdefaultsmerge under each persona's own frontmatter, and existing personas are never overwritten — hand-written files outrank pack contents.minFezVersion(0.2.0) — the oldest fez your package promises to work on. A package built against a newer API fails at install, naming both versions — not three layers into someone's afternoon.fez createstamps the scaffolding fez's own version.
GUI runtime
The example above shows the legacy GUI shape. Current source also accepts:
fez.guiRuntime | Use |
|---|---|
declarative | A JSON GUI part for standard settings or themes |
isolated-settings | A custom settings bundle in a native child webview |
isolated-page | A document-view bundle with guiContributions.page |
isolated | Custom navigation, settings, thread, message, or profile contributions |
fez.guiContributions declares the host-visible entry points without executing
the custom UI in the main window. fez.settingsSource supplies a channel-source
settings shortcut. See the GUI contract for exact shapes.
Declare the oldest tested host in fez.minFezVersion. The current source
supports these runtimes, but an older release may ignore a declaration. Test
the packaged app and selected runtime before distributing an extension.
Permissions
Declare what you use. Installing shows the list to the user; the host then narrows the API to the grant. Denied headless operations throw or reject with the extension, operation, and required permission. Registration without a grant warns and skips; UI calls outside the TUI remain inert. Handle errors rather than treating an empty result as permission to continue. GUI runtimes have their own scoped adapters; see each surface's contract.
| Permission | Grants | Sensitive |
|---|---|---|
read:channels | read messages; the client object | |
read:dms | unwrap the user's DMs | ⚠ |
read:agents | agent roster and activity | |
publish | post messages, reactions, docs as the user | ⚠ |
sign | sign, encrypt, and decrypt with the user's key, without publishing | ⚠ |
commands | slash commands, input handlers | |
ui | panels, cards, themes, navigation | |
background | scheduled tasks while the user is away | ⚠ |
system-prompt | standing instructions to all the user's agents | ⚠ |
personas | read and edit agent personas | ⚠ |
processes | run its own programs on the user's machine | ⚠ |
notifications | send native notifications | |
network:relay | HTTP to the workspace's own relay | |
network:<host> | that host (leading dot covers subdomains) | |
network:* | any server on the internet | ⚠ |
Unrecognized ids grant nothing and are flagged at install. A package with
parts but no permissions declaration gets the legacy read-only grant
(read:channels, read:agents, commands, ui) — everything except the
sensitive ones.
The honest framing, straight from the source: this is not a sandbox. A headless extension is a Node module and can work around anything here; the legacy GUI loader also runs trusted code in the main webview. Current isolated GUI runtimes enforce separate host API boundaries, with explicit platform and process limitations. Permission declarations do not turn an arbitrary Node module or legacy GUI bundle into a sandboxed program.
Tools a persona declares
The consumer side of tools lives in persona frontmatter: mcpServers: [name] or [name=npm:@scope/pkg]. Sources are restricted to npm:,
uvx:, pipx:, and http(s):// — a spec can never name an arbitrary
command, because a persona file arrives over the wire from whoever wrote it.
Declaring is a request; installing is the approval.