All docs

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=0 turns them off — Overview → Turning plugins on and off). GET /api/features answers pluginsEnabled and pluginHostEnabled.
  • The cuttle CLI reaching that server (cuttle status). Inside a Cuttle session it is already authenticated; elsewhere set CCC_BASE_URL and CCC_TOKEN.
  • To load a plugin for development you must be an admin of the server (403 admin_required otherwise). On a no-login desktop install you are.
  • A project to try it in: cuttle projects list.

The loop

StepCommandWhat it does
1. Developcuttle 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 locallycuttle 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. Packagecuttle plugins pack <dir> [-o file]Writes <name>-<version>.cuttleplugin and prints its size and SHA-256.
5. Installsee Installing a pluginBrowse catalog, file import, CLI, or the dev load above.
6. SharePublishingHost 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.spawn and providers.use:<runner> (__pty__ for the Claude Code terminal), or every run is refused. Give sessions.spawn a cap; denying Bash is the recommended default.
  • Agent files carry only description, prompt, tools, disallowedTools, model, maxTurns, effort and skills.
  • Host code (contributes.host) runs only with CUTTLE_PLUGIN_HOST=1 on the server. With it off, actions answer "plugin code is turned off" and onWebhook automations answer 503.
  • A dev reload that asks for a new permission or loosens the cap waits for cuttle plugins update <id> --approve; the previous load keeps running. An invalid save keeps the last good load and prints the errors.
  • Bump version for 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):

WayWhereNotes
BrowsePlugins → Browse → InstallThe 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 filePlugins → Import plugin file…, or drop a .cuttleplugin on the pageShows every permission before anything is installed.
CLIcuttle plugins install <file> [--yes-required] [--grant p]… [--project id]… [--org]; cuttle plugins install <pluginId> --from-indexWithout --yes-required it only shows what the plugin asks for. --org installs for the whole organization (admins).
Developmentcuttle 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:

  1. 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).
  2. 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).
  3. The plugin-developer row appears with the Plugin Developer chip and the prompt disappears. A toast offers New session; pick plugin-developer as 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:

  1. cuttle orchestrator init — it becomes an orchestrator; every session it spawns folds under its row in the Sessions panel (orchestrators.md).
  2. Plans the plugin in plugin-plan.md (id, contributions, the permissions each needs and why, the session cap) and scaffolds it with cuttle plugins new.
  3. 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.
  4. Integrates the parts, runs cuttle plugins validate, then loads the plugin with cuttle plugins dev <dir> --once --yes-required --project $CCC_PROJECT_ID and proves each contribution landed (cuttle plugins list, the automations list, a session with the new agent).
  5. 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-developer row → ⋯ → 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 CLI

To 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, the plugin-dev skill with its reference files in the extracted tree, and the catalog pin (index.json SHA-256/size against dist/, dist/ against the source directory). Changing the plugin: re-pack it into dist/ and update index.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=1 source, a spawn with agent: "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 / get read the project's agents.json only, so they do not show plugin agents; use cuttle sessions spawn --agent or GET …/agents?merged=1.