Plugin docs
Tutorial: build a Cuttle plugin
Build a plugin step by step: scaffold it, load it for development, change it while it runs, and pack it for other people.
You will build Standup, a plugin that adds a summary-writing skill, a weekday automation that uses it, and a signed webhook that runs the same agent on demand. Along the way you load it for development, change it while it runs, approve a new permission, and pack it for other people.
It takes about 20 minutes. Everything happens on your own Cuttle server; no agent needs to finish a run for any step to work.
Reference while you go: Manifest (every field), Permissions (what each permission allows).
0. Before you start
A Cuttle server with plugins on (the default;
CUTTLE_PLUGINS=0turns them off, see Overview → Turning plugins on and off). Check:curl -s "${CCC_BASE_URL:-http://localhost:5101}/api/features" | grep -o '"pluginsEnabled":[a-z]*' # "pluginsEnabled":true- The
cuttleCLI, signed in to that server (cuttle statusanswers). The commands below useCCC_BASE_URLwhen you set it, andhttp://localhost:5101otherwise. - To be an admin. Loading a development plugin is admin-only; on the desktop app (no login) you are the admin.
A project to try it in. Note its id:
cuttle projects list export PROJECT=<the project's id>
1. Scaffold
cuttle plugins new acme.standup --dir standupThe id is publisher.name: lowercase, one dot. new writes a plugin that already validates:
standup/
cuttle-plugin.json the manifest
skills/summary.md the standup-summary skill
README.md shown on the plugin's page
CHANGELOG.md its Changelog tabOpen standup/cuttle-plugin.json. The parts that matter:
permissions.required— what the plugin cannot work without:skills.contribute(its skill joins sessions),automations.contribute(it adds an automation),sessions.spawn(that automation runs an agent) andproviders.use:__pty__(on the Claude Code terminal). A plugin that starts sessions must name the runner inproviders.use, or every run is refused.- The
caponsessions.spawn— limits on the sessions it starts:runners: ["__pty__"]andtools.deny: ["Bash"], so its agent can read but has no shell. The person installing it can tighten this, never loosen it. contributes.configuration— two settings:dailySummary(a switch, off by default) andsummaryCron(when to run).contributes.automations— Daily summary, a cron automation whose schedule is${config:summaryCron}and which exists but stays off untilenabledWhen: "config.dailySummary"is true.
Check it the way the server will:
cuttle plugins validate standup2. Load it for development
In a second terminal (leave it running):
cuttle plugins dev standup --yes-required --project "$PROJECT"Without --yes-required it prints the permission list and stops: read it once. With it, it grants the required permissions and enables the plugin in your project, then watches the directory: every save is validated and reloaded.
Open Plugins in Cuttle (in the sidebar it sits under More destinations, the ⋯ button). Standup is listed with a Development badge. Its page shows the permissions you granted (Permissions tab) and the automation it added (Contributions tab), marked Off in settings.
From the CLI:
cuttle plugins list
cuttle automations list --project "$PROJECT" --source plugin:acme.standupThe automation carries the plugin's source: on Automations it has a Standup chip, the source filter can show only it, and it has no Edit. It is configured through the plugin's settings instead.
3. Turn the automation on
On the plugin's page open Settings. Daily summary is labelled Synced (it follows you to your other devices). Switch it on, change When to write it if you like, and press Save.
Back on Contributions, Daily summary now says On, and on Automations it is enabled with that schedule. Changing the schedule later updates the same automation in place.
To try it now instead of waiting for 09:00, use Run Now on the automation, or:
cuttle automations run <automation id>The run starts an agent session in your project, inside the plugin's limits (no Bash). In the Sessions panel the session carries a Standup chip. Automation sessions are hidden there by default: use Show hidden sessions to see it.
4. Change the skill while it runs
Edit standup/skills/summary.md, for example add a step:
4. End with one line: "Next standup: <tomorrow's date>".Save. The dev terminal prints reloaded acme.standup 0.1.0 …. The next session in the project gets the new text. On the project's page, Skills & agents lists standup-summary with the plugin's chip, and the project's Context page shows the skill file the next session reads, new line included. Sessions that were already running keep what they started with.
Nothing was written into your project: the skill is read from the plugin's package. To make your own editable copy, use Duplicate to customize on it.
5. Add a signed webhook
Let other tools ask for a summary. In standup/cuttle-plugin.json, add a permission to permissions.required:
{ "id": "webhooks.receive", "reason": "Other tools can ask for a summary" }and a second automation to contributes.automations:
{
"id": "summary-on-request",
"name": "Summary on request",
"type": "webhook",
"webhookAuth": "hmac-sha256",
"runner": "__pty__",
"prompt": "Use the standup-summary skill to summarize this project now."
}Also bump "version" to "0.2.0". Save.
This version asks for something new, so it does not apply by itself. The dev terminal says the change waits for your approval, and the previous load keeps running. The plugin page says Update 0.1.0 → 0.2.0 asks for more access (webhooks.receive) with Review update, which lists only what is new. Approve it there, or:
cuttle plugins update acme.standup --approveOn Contributions, Summary on request shows its webhook URL and "Secret set · rotate to reveal a new one". Press Rotate, confirm, and copy the secret: it is shown once. Then send a signed request, the way GitHub signs its webhooks:
URL=<the webhook URL from the Contributions tab>
SECRET=<the secret you copied>
BODY='{"reason":"tutorial"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -s -o /dev/null -w '%{http_code}\n' -X POST "$URL" \
-H 'Content-Type: application/json' -H "X-Hub-Signature-256: sha256=$SIG" -d "$BODY"
# 202The automation's history gets a run. The same request with a changed body or a wrong secret answers 401 and runs nothing.
6. Pause it, then share it
Switch Enabled off at the top of the plugin's page. If sessions it started are still running, a confirmation lists them and can stop them too; leave that unticked. Both automations show Paused; the webhook answers 409 and Run Now refuses, but nothing about them is changed. Switch it back on and they are exactly as before.
Stop the dev terminal (Ctrl-C). The Development installation stays. Pack the plugin:
cuttle plugins pack standupIt writes standup-0.2.0.cuttleplugin and prints its SHA-256. That file is what you share. Anyone installs it on their own server with Plugins → Import plugin file… (the install sheet lists every permission) or the commands below. On your server the Development installation already holds the id, so there these commands stop with "already installed": cuttle plugins update <file> it instead, or uninstall first (step 7).
cuttle plugins install standup-0.2.0.cuttleplugin # shows the permissions
cuttle plugins install standup-0.2.0.cuttleplugin --yes-required --project <project id>To list it in a plugin index for Browse, see Publishing.
7. Clean up
cuttle plugins uninstall acme.standup --mode removeremove archives the plugin's automations (their run history stays); --mode keep would turn them into automations you own instead.
Where next
- More contribution points — agents, pipelines, tool (MCP) servers, runners, per-project settings: Manifest.
- Code that transforms webhook deliveries or adds buttons to the plugin page runs in the sandboxed plugin host.
- The two plugins in
examples/plugins/are complete versions of all of this: the Automation Suite (cron and webhook automations, a pipeline, host code) and ComfyUI (a tool server and an agent).