Plugin docs
Developing a Cuttle plugin — by hand or with the Plugin Developer agent
From idea to an installable package: develop, test, pack, install and share a plugin, by hand or with the Plugin Developer agent.
How a plugin goes from idea to something other people install, and how to have Cuttle do most of it: the Plugin Developer plugin in the built-in catalog (cuttle.plugin, examples/plugins/plugin) adds a plugin-developer agent that orchestrates child sessions to build the parts, then validates, loads and packs the result. Tutorial walks one plugin through the loop step by step; Manifest and Permissions are the references. Every command below is the real cuttle plugins surface — cuttle plugins <command> --help is authoritative when the two disagree.
Before you start
- A Cuttle server with plugins on (the default;
CUTTLE_PLUGINS=0turns them off — Overview → Turning plugins on and off).GET /api/featuresanswerspluginsEnabledandpluginHostEnabled. - The
cuttleCLI reaching that server (cuttle status). Inside a Cuttle session it is already authenticated; elsewhere setCCC_BASE_URLandCCC_TOKEN. - To load a plugin for development you must be an admin of the server (
403 admin_requiredotherwise). On a no-login desktop install you are. - A project to try it in:
cuttle projects list.
The loop
| Step | Command | What it does | |
|---|---|---|---|
| 1. Develop | cuttle plugins new <publisher>.<name> [--dir d] | Scaffolds a manifest, a skill, a weekday automation (off until its setting is on), README and CHANGELOG. It already passes validate. | |
| 2. Check | `cuttle plugins validate <dir \ | file>` | The server's own import checks, offline: schema, implied permissions, every referenced path, pipeline graphs. |
| 3. Test locally | cuttle plugins dev <dir> --yes-required --project <id> | Loads the directory as a Development installation and reloads it on every save. Without --yes-required it prints the permission list and stops. --grant <optional> adds optional permissions; --once loads once and exits. | |
| 4. Package | cuttle plugins pack <dir> [-o file] | Writes <name>-<version>.cuttleplugin and prints its size and SHA-256. | |
| 5. Install | see Installing a plugin | Browse catalog, file import, CLI, or the dev load above. | |
| 6. Share | Publishing | Host the package anywhere HTTPS and list it in a plugin index for Browse. |
Things that bite, all enforced at import or spawn:
- Skill names and agent keys must start with
<name>-(the part of the id after the dot). Every contribution implies permissions the manifest must list as required (Manifest → Implied permissions). - A plugin that starts sessions needs
sessions.spawnandproviders.use:<runner>(__pty__for the Claude Code terminal), or every run is refused. Givesessions.spawnacap; denyingBashis the recommended default. - Agent files carry only
description,prompt,tools,disallowedTools,model,maxTurns,effortandskills. - Host code (
contributes.host) runs only withCUTTLE_PLUGIN_HOST=1on the server. With it off, actions answer "plugin code is turned off" andonWebhookautomations answer503. - A
devreload that asks for a new permission or loosens the cap waits forcuttle plugins update <id> --approve; the previous load keeps running. An invalid save keeps the last good load and prints the errors. - Bump
versionfor every package you share: a same-version package with other contents is refused at install unless confirmed (409 confirm_version).
Where the contributions show while testing
- Plugins (
/plugins, in the navigation under More destinations): the installation with a Development badge; its page's Permissions, Contributions and Settings tabs. - Project → Skills & agents: contributed skills and agents after the project's own, with the plugin's chip and Duplicate to customize.
- Automations / Pipelines: the contributed rows with the plugin's chip, read-only;
cuttle automations list -p <id> --source plugin:<pluginId>. - A new session in the project gets the skills, agents and tool servers; sessions already running keep what they started with.
Installing a plugin
Four ways in, one install path (the same preview, permission sheet and registry; Overview → Registry, import and install):
| Way | Where | Notes |
|---|---|---|
| Browse | Plugins → Browse → Install | The plugin index (CUTTLE_PLUGIN_INDEX_URL; unset, the built-in catalog of the three example plugins). The package must hash to the index's SHA-256. |
| Import a file | Plugins → Import plugin file…, or drop a .cuttleplugin on the page | Shows every permission before anything is installed. |
| CLI | cuttle plugins install <file> [--yes-required] [--grant p]… [--project id]… [--org]; cuttle plugins install <pluginId> --from-index | Without --yes-required it only shows what the plugin asks for. --org installs for the whole organization (admins). |
| Development | cuttle plugins dev <dir> | Admin only; does not sync to other devices. |
After installing: enable it per project (the plugin page's Enabled in checklist, or cuttle plugins projects <id> --project <projectId>…), review optional permissions (cuttle plugins grants <id> [--grant p] [--revoke p]), narrow the session cap if you like (cuttle plugins cap <id> …), and configure it on its Settings tab. Uninstall with cuttle plugins uninstall <id> --mode remove|keep.
The Plugin Developer plugin
cuttle.plugin ships in the built-in catalog (examples/plugins/plugin, packaged as examples/plugins/dist/plugin-1.0.0.cuttleplugin, listed in examples/plugins/index.json) next to Automation Suite and ComfyUI. It contributes one agent, plugin-developer, and one skill, plugin-dev (the author loop above, with plugin-dev/references/manifest.md, permissions.md and testing.md beside it in the package, read only at the step that needs them). It asks for agents.contribute and skills.contribute, nothing else — see Why no sessions.spawn.
Enabling it for a project
The shortest path, from the project you want the plugin for:
- Project page → Skills & agents → Agents: while Plugin Developer is not enabled there, the panel shows Build a plugin for this project with Install Plugin Developer (not installed yet) or Enable Plugin Developer here (installed, not enabled in this project).
- Install opens the ordinary install sheet — the server downloads the package from the catalog and checks its SHA-256; you see both permissions and this project is pre-selected (tick others if you like) — and Install grants them. Enable … here adds this project to the installation's projects in one click (its permissions were granted when it was installed).
The
plugin-developerrow appears with the Plugin Developer chip and the prompt disappears. A toast offers New session; pickplugin-developeras the agent there, or:cuttle sessions spawn -p <projectId> --agent plugin-developer --name "changelog plugin" \ --prompt "Build a plugin that adds a changelog-writer skill and agent."
The other ways in are the usual ones: Plugins → Browse (the Plugin Developer card; Browse sorts the catalog by name) → Install → tick projects; cuttle plugins install cuttle.plugin --from-index --yes-required --project <id>; or later cuttle plugins projects cuttle.plugin --project <id>. The prompt is hidden while plugins are off (CUTTLE_PLUGINS=0), when the configured index does not list cuttle.plugin, once the plugin is enabled in that project, and when you have disabled the installation yourself. Component: apps/web/src/components/project/plugin-developer-prompt.tsx (pluginDeveloperOffer in apps/web/src/lib/plugins.ts decides what to offer).
What the agent does
Its prompt (examples/plugins/plugin/agents/plugin-developer.json) is the contract, in order:
cuttle orchestrator init— it becomes an orchestrator; every session it spawns folds under its row in the Sessions panel (orchestrators.md).- Plans the plugin in
plugin-plan.md(id, contributions, the permissions each needs and why, the session cap) and scaffolds it withcuttle plugins new. - Spawns one child session per separable part — manifest + skills/agents, host code or an MCP server, tests and validation, README/CHANGELOG — each with a brief naming the files it owns, and waits with
cuttle sessions wait … --all(never polling). It answers you first whenever you write to it. - Integrates the parts, runs
cuttle plugins validate, then loads the plugin withcuttle plugins dev <dir> --once --yes-required --project $CCC_PROJECT_IDand proves each contribution landed (cuttle plugins list, the automations list, a session with the new agent). - Packs it (
cuttle plugins pack) and reports the package, its SHA-256, how to install it elsewhere, and what it did not verify (for example host code on a server where the host is off).
Its skills: plugin-dev (from the plugin) plus the built-in orchestrator and cuttle. What it needs: a provider that can run sessions with a shell (it runs the cuttle CLI), admin rights on the server for the dev load, and plugins on. Its child sessions cost what any session costs; keep the idea small for a first run.
Why no sessions.spawn
The agent runs in your session: the cuttle CLI there carries your session's auth, so the children it spawns are your sessions, not the plugin's. sessions.spawn and its cap govern sessions whose origin_source is plugin:<id> — those a plugin's automations, pipelines or credential start (Permissions → The session cap) — and this plugin contributes none of those, so declaring it would show a critical permission nothing uses. Like every plugin agent it is subject to the project's session access policy and the user's own rights.
Layers
Agents merge plugin → org → project → repo, later layers winning by key (loadMergedAgents, apps/server/src/services/session-context-service.ts). A project, organization or other plugin agent named plugin-developer replaces this one; Duplicate to customize on its row copies it into the project's agents.json to edit (the row then shows shadowed). Nothing is written into any agents.json by enabling the plugin; pickers report its source as plugin:cuttle.plugin (GET /api/projects/:id/agents?merged=1).
The agent bundle (plugin-developer-agent.zip)
The agent and its skills can be downloaded as a bundle to read, to hand to a developer, or to use on a Cuttle without the plugin:
- UI: Project → Skills & agents → the
plugin-developerrow → ⋯ → Download (the same action every agent row has). - HTTP:
GET /api/projects/<projectId>/agents/plugin-developer/export(any read access, in a project the plugin is enabled in; from a shell with a personal access token:curl -H "Authorization: Bearer $CCC_TOKEN" -o plugin-developer-agent.zip "$CCC_BASE_URL/api/projects/<projectId>/agents/plugin-developer/export").
Contents — the same export every agent gets; a skill's companion files (<skill>/… beside the skill file on disk, whether plugin, built-in or project) ride along:
plugin-developer-agent/
agent.md definition, settings, the full prompt, skill list
skills/plugin-dev.md the author loop
skills/plugin-dev/references/manifest.md
skills/plugin-dev/references/permissions.md
skills/plugin-dev/references/testing.md
skills/orchestrator.md child sessions, wait, steer
skills/cuttle.md the cuttle CLITo use it on another Cuttle, install the plugin there (Browse, or examples/plugins/dist/plugin-1.0.0.cuttleplugin via Import plugin file…). Without the plugin, drop the skill into the project and create the agent from the prompt in agent.md:
cuttle skills write plugin-dev.md -p <projectId> --file skills/plugin-dev.md
# the reference files are read by path: keep skills/plugin-dev/references/ next to the skill
cuttle agents set plugin-developer -p <projectId> -d "Builds a Cuttle plugin from an idea" \
--prompt-file prompt.md --skills plugin-dev.md,orchestrator.md,cuttle.md(prompt.md is the Prompt section of agent.md; orchestrator and cuttle are built-in skills on every Cuttle, so they need no copy.)
Tests
apps/server/src/services/plugins/examples.test.ts— installs the shipped package through the real routes: the agent's key, skills and prompt steps, theplugin-devskill with its reference files in the extracted tree, and the catalog pin (index.jsonSHA-256/size againstdist/,dist/against the source directory). Changing the plugin: re-pack it intodist/and updateindex.json; the test prints the exact command.apps/server/src/routes/filesystem.plugin-contributions.test.ts→ "exports a plugin agent as a bundle …" — the export zip carries a plugin skill and its companion files.apps/web/src/lib/plugins.test.ts→pluginDeveloperOffer— when the project page offers install, enable, or nothing.tests/e2e/suites/98-plugin-developer-discovery.spec.ts— Browse leads with it; the project-page prompt → install sheet (both permissions, this project pre-selected, nothing installed before Install) → the row with the plugin chip,?merged=1source, a spawn withagent: "plugin-developer", the bundle download; the one-click enable in a second project.
Known limits
- The agent's own end-to-end run (plan → children → validate → dev load → pack) is exercised by its prompt and the CLI commands it uses, not by an automated test: the e2e suite runs without a model. Drive it once on a small idea after changing its prompt.
- The project-page prompt is on the desktop Skills & agents panel; on a phone use Plugins → Browse.
cuttle agents list/getread the project'sagents.jsononly, so they do not show plugin agents; usecuttle sessions spawn --agentorGET …/agents?merged=1.