Get started free
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.

SectionWhat it covers
Top levelIdentity, the API range you build against, how you're listed.
permissionsThe daemon routes each half may call: the allowlist a reviewer reads.
serverThe backend half: its own route namespace, and its reach into the daemon.
contributes.viewsSurfaces in the sidebar, the rail and the workspace.
contributes.filesFiles your extension owns, and who may read them.
contributes.viewersCustom renderers for a file type the editor opens.
contributes.documentsA document family that opens as a tab beside the code.
contributes.commandsActions in the command palette, and their keybindings.
contributes.settingsTyped configuration, rendered as a settings form for you.
contributes.capabilitiesA connector: its catalog entry, credential fields and env.
contributes.processesLong-running processes, and the events that wake an agent.
contributes.agentAgent plugins with skills and MCP servers, plus CLIs on its PATH.
contributes.environmentPackages and toolchains baked into the sandbox image.
When each takes effectWhich contributions are live on install, and which need a rebuild.

Top level

FieldRequiredMeaning
publisheryesLowercase slug. With name it derives the extension id, acme.incidents.
nameyesLowercase slug, unique within the publisher.
versionyesYour semver, for display. The code identity is always the installed commit sha.
engines.intenticyesSemver range over the host's extension-API version, checked before activation. Current API: 2.1.0.
categorynoThe 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.
logonoA simple-icons 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.
iconnoA 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.
entrynoCheckout-relative path of the prebuilt single-file ESM bundle exporting activate(). Omit for an extension with no UI.
servernoCheckout-relative path of the prebuilt node ESM bundle exporting activateServer(): the extension's backend half. Omit for an extension with no backend.
permissions.sandboxnoThe daemon routes the UI half may call. Omit if it calls none.
permissions.daemonnoThe daemon routes the backend half may call. Omit if it calls none.
contributesnoEverything 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
"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 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 page. A backend requires engines.intentic of ^2.1.0 or wider.

contributes.views

FieldMeaning
idKebab-case slug; must match the id your code registers.
labelThe view family's human name.
surfacerail (always-visible left column), directory (a per-repo panel in the Workspace tree), or sandbox (a tab on the Sandbox hub).
badgeWhether 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.

FieldMeaning
pathWorkspace-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.
invalidatesThe 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.

FieldMeaning
idKebab-case slug, matched to your runtime registration.
extensionsBare file extensions, no dot: ["docx", "xlsx"].
fetchHow 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.

FieldMeaning
idKebab-case slug, matched to your runtime registration.
labelThe 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

FieldMeaning
commandDotted slug with at least two segments, as in incidents.acknowledge.
titleWhat the command palette shows.
iconOptional glyph from the host's icon set.
keybindingOptional 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.

FieldMeaning
keySlug you read with api.settings.get(key).
typeboolean, string, number, or enum.
title, descriptionLabel and help text.
default, enumInitial value; the choices, for enum.
secretMasks the value and strips it from reads, so a set secret round-trips as "still set", never as itself.
envInjects 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
"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"
    }
]
FieldMeaning
idSlug 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).
catalogThe 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.
fieldsThe 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, homeUrlbrowser 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.
envEnvironment variables for the agent, templated over the fields: ${field} substitutes, ${field:uri} percent-encodes.
skillPath 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).
fragmentOptional 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
"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."
    }
}
FieldMeaning
processes[].nameSlug you address it by; undeclared names are refused.
processes[].command, cwdWhat to run, and where relative to the checkout.
processes[].port"auto" assigns a free port and injects it as PORT.
processes[].previewExposes the port on a tunneled preview hostname.
processes[].autoStartLaunch on install and on daemon boot.
listener.providerThe slug automation triggers fire on.
listener.eventsThe event ids the daemon accepts and the labels the automation editor shows for them.
listener.automation.labelThe source name shown in the automation editor.
listener.automation.mentionLabelOptional wording for a mention-only filter when addressed messages have provider-specific semantics.
listener.automation.channelThe label and placeholder for the optional channel, mailbox, chat, or equivalent source filter.
listener.automation.starterPromptThe 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

FieldMeaning
agent.pathA 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.
binA 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.

FieldMeaning
fragmentCheckout-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

ContributionApplies
views, viewers, documents, commands, files, settings, processes, capabilities, listenerImmediately
serverImmediately: the backend host restarts with the new code
agent, binNext agent turn
environmentNext image rebuild

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

Type to search every page, in the docs and the API reference.