---
title: "Extension manifest reference · intentic API"
description: "Every field of intentic-extension.json: views, viewers, commands, settings, capability cards, processes, listeners, agent plugins, the backend, permissions."
url: "https://intentic.dev/api/manifest/"
updated: "2026-08-12"
---

Build

# Manifest reference

Every field of intentic-extension.json. The manifest is both the approval dialog the owner reads and the gate the host enforces, so what's here is exactly what your extension can do.

Every field group, and what it's for. If you know the shape you want but not its name, scan the right-hand column.

| Section | What it covers |
| --- | --- |
| [Top level](#top-level) | Identity, the API range you build against, how you're listed. |
| [`permissions`](#permissions) | The daemon routes each half may call: the allowlist a reviewer reads. |
| [`server`](#backend) | The backend half: its own route namespace, and its reach into the daemon. |
| [`contributes.views`](#contributes-views) | Surfaces in the sidebar, the rail and the workspace. |
| [`contributes.files`](#contributes-files) | Files your extension owns, and who may read them. |
| [`contributes.viewers`](#contributes-viewers) | Custom renderers for a file type the editor opens. |
| [`contributes.documents`](#contributes-documents) | A document family that opens as a tab beside the code. |
| [`contributes.commands`](#contributes-commands) | Actions in the command palette, and their keybindings. |
| [`contributes.settings`](#contributes-settings) | Typed configuration, rendered as a settings form for you. |
| [`contributes.capabilities`](#contributes-capabilities) | A connector: its catalog entry, credential fields and env. |
| [`contributes.processes`](#contributes-processes-and-contributes-listener) | Long-running processes, and the events that wake an agent. |
| [`contributes.agent`](#contributes-agent-and-contributes-bin) | Agent plugins with skills and MCP servers, plus CLIs on its PATH. |
| [`contributes.environment`](#contributes-environment) | Packages and toolchains baked into the sandbox image. |
| [When each takes effect](#when-each-contribution-takes-effect) | Which contributions are live on install, and which need a rebuild. |

## Top level

| Field | Required | Meaning |
| --- | --- | --- |
| `publisher` | yes | Lowercase slug. With `name` it derives the extension id, `acme.incidents`. |
| `name` | yes | Lowercase slug, unique within the publisher. |
| `version` | yes | Your semver, for display. The *code* identity is always the installed commit sha. |
| `engines.intentic` | yes | Semver range over the host's extension-API version, checked before activation. Current API: **2.1.0**. |
| `category` | no | The section your extension sits under in the sandbox's Extensions tab: `work`, `workspace`, `connections`, `knowledge` or `sandbox`. Anything else (or nothing) lists it under *Other*; it installs and runs either way. |
| `logo` | no | A [simple-icons](https://simpleicons.org/) slug such as `"linear"` or `"notion"`, drawn wherever your extension is listed. Add `"/<hex>"` (`"github/f5f5f5"`) to force a colour for a mark that would otherwise vanish against the surface. |
| `icon` | no | A glyph from the app's own icon set, drawn when there is no brand to use, and when an offline sandbox can't reach the icon CDN. Neither field is required and neither can break the page: a slug that 404s, a glyph this build has never heard of, or nothing at all all fall through to your extension's initials. |
| `entry` | no | Checkout-relative path of the prebuilt single-file ESM bundle exporting `activate()`. Omit for an extension with no UI. |
| `server` | no | Checkout-relative path of the prebuilt node ESM bundle exporting `activateServer()`: the extension's [backend half](#backend). Omit for an extension with no backend. |
| `permissions.sandbox` | no | The daemon routes the **UI half** may call. Omit if it calls none. |
| `permissions.daemon` | no | The daemon routes the **backend half** may call. Omit if it calls none. |
| `contributes` | no | Everything below. |

## permissions

Each entry is `"<METHOD> <path-glob>"`, where `*` matches exactly one path segment: one grammar for both halves, because the install dialog, the gate and the usage ledger all read it. The two lists are separate because the halves run as different principals: the UI acts with the owner's session, the backend with a token minted for exactly this extension, and a grant to one must never quietly widen the other.

- `sandbox`: the routes the **UI half** may call. Every door in `api.sandbox` is checked against it, so a call to anything not covered throws. That includes the typed `rpc` client, which resolves to a method and a concrete path and is gated on that exactly as `request` and `json` are. This constrains use of the published extension API; a browser bundle still shares the app's DOM, storage, globals and network access.
- `daemon`: the routes the **backend half** may call through `api.daemon`, enforced daemon-side against its minted token.

A route allowlist

```json
"permissions": {
 "sandbox": ["GET /panels", "POST /panels/*/start", "GET /memory/file"],
 "daemon": ["GET /capabilities/*/connection"]
}
```

Keep both as narrow as the feature needs. These lists are what a reviewer reads to understand declared daemon API use, not a complete confinement boundary for browser or process code. The [HTTP API](https://intentic.dev/api/http/) page is what the routes are. An extension's *own* namespace, `/x/<id>/…`, needs no entry on either list: its backend is its own code from the same approved checkout.

## server: the backend half

A manifest `server` entry names a prebuilt, self-contained node ESM bundle exporting `activateServer(api, context)`. That is the extension's backend: real server code of its own, for the features a browser bundle can't carry: a client library for somebody else's API, work that must outlive the tab, state assembled from workspace files.

- **Its own route namespace.** The backend serves `/x/<id>/…`, proxied by the daemon through its ordinary auth, so it does not receive the caller's session credential. It does receive its own extension token and workspace root for daemon work; that reach is part of the source audit. The extension's UI calls its own namespace freely because both halves are code from the same approved checkout.
- **One supervised host process.** Every enabled backend runs in the daemon's single backend-host process, not in the daemon itself. Loaded code can't be unloaded, so the off switch, an update and a live edit all work the same way: the host restarts. The restart *is* the reload.
- **Declared reach back into the daemon.** The backend's calls into the daemon's own routes go through `api.daemon` and are gated by `permissions.daemon`, with a per-extension token: the same honesty rule as the UI half, enforced against a different principal.
- **Self-contained by construction.** The installed checkout has no `node_modules` and the host provides no import map, so everything except node builtins must be bundled in.

The backend API surface includes `routes.mount`, `api.daemon` and `workspaceRoot`. It is documented on the [Host API](https://intentic.dev/api/host/#backend) page. A backend requires `engines.intentic` of `^2.1.0` or wider.

## contributes.views

| Field | Meaning |
| --- | --- |
| `id` | Kebab-case slug; must match the id your code registers. |
| `label` | The view family's human name. |
| `surface` | `rail` (always-visible left column), `directory` (a per-repo panel in the Workspace tree), or `sandbox` (a tab on the Sandbox hub). |
| `badge` | Whether the tile may show a count. Without it the host drops any badge you register. |

Pick the surface by what the view is *about*. The rail is a place people act from and its slots are permanent, so earn one. A view about the box itself (its logs, its consumption) belongs on the Sandbox hub, where things are inspected occasionally rather than worked in.

## contributes.files

An intentic workspace is file-first: the agent edits files directly, out of band from every HTTP route. Declaring the files behind your view is what gets you into the daemon's change push instead of polling for it.

| Field | Meaning |
| --- | --- |
| `path` | Workspace-root-relative, forward-slash, matched by **prefix**, not a glob. Keep a trailing slash on directories so `.intentic/drafts/` can't match a sibling file. |
| `invalidates` | The query keys those contents feed: the first parts of your own `api.sandbox.key(...)` keys. At least one. |

Narrow paths matter. A broad prefix costs every connected browser a refetch on every matching write, and a write-heavy path turns that into a request storm.

## contributes.viewers

A renderer for a file type. The host resolves the open file, fetches its content, and renders your component with it.

| Field | Meaning |
| --- | --- |
| `id` | Kebab-case slug, matched to your runtime registration. |
| `extensions` | Bare file extensions, no dot: `["docx", "xlsx"]`. |
| `fetch` | How the host hands you the file. `"text"` is decoded utf8, as a `text` prop. `"blob"` is the whole file's bytes, as `blob`, for a format that must be parsed end to end. `"url"` is a streaming URL to point an element at, as `src`, for audio and video, which range-read gigabytes and should never be held in memory. The host mints a credentialed URL and hands the URL to your component; treat it as a short-lived bearer value and never log or persist it. |

## contributes.documents

A per-directory document family: your provider marks the Workspace-tree rows it can explain with an icon, and the host opens your component as a tab beside the code. This is how the commit graph sits on every repository row, and how a package's architecture page sits on the packages that have one.

| Field | Meaning |
| --- | --- |
| `id` | Kebab-case slug, matched to your runtime registration. |
| `label` | The family's human name, shown in the install dialog. |

Only the id and label are declared. A viewer takes files over and a command can claim a global shortcut, so those are approved field by field. A document provider takes nothing over: it adds an icon to rows it has something for, and every such row is evidence the owner can see. So the manifest gates *whether* you may mark up the tree at all, and the per-row wording stays with your provider, which is the only thing that knows what it found.

## contributes.commands

| Field | Meaning |
| --- | --- |
| `command` | Dotted slug with at least two segments, as in `incidents.acknowledge`. |
| `title` | What the command palette shows. |
| `icon` | Optional glyph from the host's icon set. |
| `keybinding` | Optional global shortcut in chord notation, as in `"Mod+Shift+K"`, where `Mod` is ⌘ on Apple and Ctrl elsewhere. |

A global shortcut is consequential, so like the title it's declared here and the host binds only what was approved.

## contributes.settings

Typed settings the host renders into the Settings page and persists daemon-side, shared across all the owner's browsers.

| Field | Meaning |
| --- | --- |
| `key` | Slug you read with `api.settings.get(key)`. |
| `type` | `boolean`, `string`, `number`, or `enum`. |
| `title`, `description` | Label and help text. |
| `default`, `enum` | Initial value; the choices, for `enum`. |
| `secret` | Masks the value and strips it from reads, so a set secret round-trips as "still set", never as itself. |
| `env` | Injects the stored value into the **agent's** shell under this name, every turn. How a credential reaches the agent's CLI tools. |

Settings live on the daemon keyed by `publisher.name`, so they outlive a re-clone, an update, or a remove and re-add.

## contributes.capabilities

A whole third-party integration expressed as data, with no code. It produces a real capability card in the app: a "+" tile, a config form, credentials stored as secrets, environment variables in the agent's shell, and a `SKILL.md` cheatsheet that teaches the agent to use the tool.

You contribute a **card**, never a handler. The machinery that acts on a capability lives in the daemon, because some of it grants real privilege: putting the sandbox on a private network, running it privileged, changing what an agent may do on somebody's personal computer. So the four kinds below are the ones whose daemon-side behaviour is entirely generic over your data; a manifest naming any other kind is rejected at install.

A CLI connector

```json
"capabilities": [
 {
 "id": "linear",
 "kind": "cli",
 "catalog": {
 "name": "Linear",
 "logo": "linear",
 "description": "Read and file issues from the agent's shell.",
 "category": "Project management",
 "guide": {
 "url": "https://linear.app/settings/api",
 "linkLabel": "Create a personal API key",
 "steps": ["Open Settings → API", "Create a personal key", "Paste it here"]
 }
 },
 "fields": [{ "key": "apiKey", "label": "API key", "secret": true }],
 "env": { "LINEAR_API_KEY": "${apiKey}" },
 "skill": "skills/linear/SKILL.md"
 }
]
```

| Field | Meaning |
| --- | --- |
| `id` | Slug the card is keyed by, unique within its `kind`. It becomes the card's URL and the hidden field tying each connection back to it. |
| `kind` | `"cli"` a command-line tool the agent drives; `"browser"` a site the agent acts on as the user; `"host"` an operating-system guide for a connected computer; `"agent"` a preset for an outside chat agent (fields only). |
| `catalog` | The card: `name`, `description` (one line: the grid clamps it at two), `category`, an optional brand `logo` or fallback `icon`, a `hint` paragraph the form prints in full and the catalog search reads, and a `guide` walking the user through creating the credential. |
| `fields` | The config form: `key`, `label`, and flags for `secret`, `optional`, `multiline`, `options`, and a `when` gate. A `boolean` field renders as a quiet opt-in switch; `hint` puts a line under one control; `value` bakes a fixed value in without rendering anything; `rebuild` chips a field whose value only lands at the next image rebuild; `totp` marks an authenticator seed: see below. |
| `loginUrl`, `homeUrl` | `browser` only, both optional: where the sign-in window opens, and where the connected profile opens once it has one. A card either pins them or declares a field that supplies them: leaving both out is what the one generic "browser session" card does, so users can connect a site nobody shipped a card for. |
| `env` | Environment variables for the agent, templated over the fields: `${field}` substitutes, `${field:uri}` percent-encodes. |
| `skill` | Path to the `SKILL.md` that teaches the agent the tool. Two placeholders are substituted when a user adds it: `${id}` becomes the name they chose, and `${tools}` becomes the built-in note for that kind (how to drive the shared browser, what a connected computer's tools are). |
| `fragment` | Optional image fragment installing the client binary. `cli` only. |

This contribution point covers the most ground: one entry plus two files adds an integration to the product.

One field flag carries a security rule. A field marked `totp` (declared with `secret`) holds the seed an authenticator app would hold. So unlike an ordinary secret it never enters the agent's environment either. The daemon mints the six-digit codes on demand and only those cross, each dead within its period; an `env` template that references a totp field fails to parse rather than handing the agent the second factor itself.

## contributes.processes and contributes.listener

A process is a long-lived background program the daemon supervises. Pair it with a listener and you have a realtime event source that can wake the agent, exactly how the Discord and IMAP integrations work.

A gateway process with a listener

```json
"processes": [
 { "name": "gateway", "command": "node dist/gateway.js", "port": "auto", "autoStart": true }
],
"listener": {
 "provider": "linear",
 "events": [
 { "type": "issue_created", "label": "Issues created" },
 { "type": "comment_added", "label": "Comments added" }
 ],
 "automation": {
 "label": "Linear",
 "channel": { "label": "Team ID (optional)", "placeholder": "all teams" },
 "starterPrompt": "Linear events arrived. Triage each issue or comment and use the Linear capability when a response or update is needed."
 }
}
```

| Field | Meaning |
| --- | --- |
| `processes[].name` | Slug you address it by; undeclared names are refused. |
| `processes[].command`, `cwd` | What to run, and where relative to the checkout. |
| `processes[].port` | `"auto"` assigns a free port and injects it as `PORT`. |
| `processes[].preview` | Exposes the port on a tunneled preview hostname. |
| `processes[].autoStart` | Launch on install and on daemon boot. |
| `listener.provider` | The slug automation triggers fire on. |
| `listener.events` | The event ids the daemon accepts and the labels the automation editor shows for them. |
| `listener.automation.label` | The source name shown in the automation editor. |
| `listener.automation.mentionLabel` | Optional wording for a mention-only filter when addressed messages have provider-specific semantics. |
| `listener.automation.channel` | The label and placeholder for the optional channel, mailbox, chat, or equivalent source filter. |
| `listener.automation.starterPrompt` | The initial prompt that teaches a new automation how to interpret this provider's event payload. |

The daemon holds no connection to your provider; your process does. It gets a provider-scoped control surface to reconcile state, dispatch a wake, and report status or failure.

## contributes.agent and contributes.bin

| Field | Meaning |
| --- | --- |
| `agent.path` | A checkout-relative directory that is a Claude Code plugin: skills, subagents, hooks, commands, `.mcp.json`. Handed to the agent's plugin loader every turn. Omit `path` to use the checkout root. |
| `bin` | A checkout-relative directory of executables prepended to the **agent's** PATH each turn. How you ship it a command-line tool. |

These two are how an extension changes what the agent can *do* rather than what the owner can see. Both are composed per turn, so they take effect from the next one, with no reload.

## contributes.environment

A Dockerfile fragment baked into the sandbox image overlay, for when your tool has to actually be installed: a database client, a media encoder, a language runtime.

| Field | Meaning |
| --- | --- |
| `fragment` | Checkout-relative path to a file containing **only** `RUN` and `ENV` instructions. |

`FROM` and privileged directives are rejected. An extension can install tools but can't claim container privileges or swap the base image. The owner approves the composed overlay and rebuilds out of band, so this is the one contribution that applies on the next image rebuild rather than immediately.

## When each contribution takes effect

| Contribution | Applies |
| --- | --- |
| `views`, `viewers`, `documents`, `commands`, `files`, `settings`, `processes`, `capabilities`, `listener` | Immediately |
| `server` | Immediately: the backend host restarts with the new code |
| `agent`, `bin` | Next agent turn |
| `environment` | Next image rebuild |

The Extensions tab states this per extension, so nobody has to guess why a change hasn't landed yet.

More in Build

[Previous ← Build an extension](https://intentic.dev/api/build/)[Next Host API →](https://intentic.dev/api/host/)
