fez
Extension API

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 "needs gh" 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: true is an allowlist entry, not a default — without it neither background host loads your headless part. It's distinct from the background permission: you need the part flag to be loaded by the desktop worker or sentinel and the permission for registerScheduledTask to work.
  • parts.skill merges into the user's settings.json mcpServers. 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.personas ships a persona pack: every .md in the directory is validated before anything installs (one bad file rejects the pack), pack defaults merge 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 create stamps the scaffolding fez's own version.

GUI runtime

The example above shows the legacy GUI shape. Current source also accepts:

fez.guiRuntimeUse
declarativeA JSON GUI part for standard settings or themes
isolated-settingsA custom settings bundle in a native child webview
isolated-pageA document-view bundle with guiContributions.page
isolatedCustom 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.

PermissionGrantsSensitive
read:channelsread messages; the client object
read:dmsunwrap the user's DMs
read:agentsagent roster and activity
publishpost messages, reactions, docs as the user
signsign, encrypt, and decrypt with the user's key, without publishing
commandsslash commands, input handlers
uipanels, cards, themes, navigation
backgroundscheduled tasks while the user is away
system-promptstanding instructions to all the user's agents
personasread and edit agent personas
processesrun its own programs on the user's machine
notificationssend native notifications
network:relayHTTP 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.

On this page