Cletus plugin kit: rules for coding agents, specification, host API, style guide, worker SDK and cletus-plugin tool
This commit is contained in:
commit
9b19d3a8be
41 files changed
+4121
No files matched your search
@@ -0,0 +1,283 @@
|
||||
# Cletus plugin specification
|
||||
|
||||
Draft 1, 2026-11-10. Host API version 2.
|
||||
|
||||
## Parts of a plugin
|
||||
|
||||
| Part | Language | Runs | Required |
|
||||
| --- | --- | --- | --- |
|
||||
| Worker | Go | as its own process, started and stopped by Cletus | yes |
|
||||
| Chat tools | Go, in the worker | when Cletus's AI calls them, in chat or background runs | no |
|
||||
| Screen | TypeScript (Svelte 5 supported) | in a sandboxed frame inside the Cletus window | no |
|
||||
| Services | Go, in the worker | when another plugin calls them through the host | no |
|
||||
|
||||
The worker does the work. The screen shows it. The host (Cletus) sits
|
||||
between them and everything else: the user's files and shared folders,
|
||||
background agent runs, other plugins. The plugin never holds the
|
||||
user's Cletus key or any organization credential.
|
||||
|
||||
## The repository
|
||||
|
||||
```
|
||||
plugin.json identity, permissions, tools, services
|
||||
CHANGELOG.md one entry per version, written for users
|
||||
go.mod, go.sum the worker module (CGO_ENABLED=0)
|
||||
vendor/ go mod vendor output; the build has no network
|
||||
cmd/worker/ the worker's main package
|
||||
internal/ the rest of the worker
|
||||
testdata/ synthetic files for tests and `cletus-plugin run`
|
||||
docs/plan.md purpose, permissions and dependencies, each with a reason
|
||||
docs/guide.md the user guide, shown on the store listing
|
||||
listing/ listing.json for the store
|
||||
```
|
||||
|
||||
Eidonix builds the worker for macOS (arm64) and Windows (amd64) with the Go
|
||||
version the SDK pins, with `CGO_ENABLED=0`. `cletus-plugin check` builds the
|
||||
same two targets locally.
|
||||
|
||||
## plugin.json
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "acme.gauge-check",
|
||||
"name": "Gauge Check",
|
||||
"description": "Check measured gauges against the drawing's tolerances.",
|
||||
"version": "1.2.0",
|
||||
"min_app_version": "0.5.0",
|
||||
"host_api": 2,
|
||||
"color": "teal",
|
||||
"permissions": ["files", "workspace.read"],
|
||||
"file_types": [".gauge"],
|
||||
"opens_folders": false,
|
||||
"tools": [
|
||||
{"name": "gauge_check_run", "mutating": false},
|
||||
{"name": "gauge_check_save_report", "mutating": true, "requires_approval": true}
|
||||
],
|
||||
"provides": {},
|
||||
"uses": ["cad-viewer:cad.open"],
|
||||
"dependencies": []
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Rules |
|
||||
| --- | --- |
|
||||
| `id` | `<publisher>.<name>`. Eidonix assigns your publisher id. `<name>`: lowercase letters, digits, hyphens. Never changes. |
|
||||
| `name`, `description` | Shown in the store and the app. Plain words. No claims about Eidonix or other companies. |
|
||||
| `version` | Semantic version, raised on every submission. Never reused. |
|
||||
| `min_app_version` | The oldest Cletus with every host method you call (`host-api.md` lists each method's version). |
|
||||
| `host_api` | `2`. |
|
||||
| `color` | One of `red orange amber yellow green teal blue indigo violet pink grey`. The app maps it to each theme. |
|
||||
| `permissions` | See "Permissions". Undeclared host methods are refused. |
|
||||
| `file_types` | Extensions your screen opens (with the screen toolchain) ("Open in …"). Claim your own formats. Common formats (`.xlsx`, `.pdf`, `.step`) are allowed when the plugin really works on them; the user picks which plugin opens a file. |
|
||||
| `opens_folders` | `true` if your screen opens a whole folder. |
|
||||
| `tools` | Every chat tool. `mutating: true` if it writes anything anywhere. `requires_approval: true` makes Cletus ask the user each time. Must match what the worker reports. |
|
||||
| `ui`, `ui_host_api` | The screen and the SDK modules it uses. Defined with the screen toolchain; leave them out until then. |
|
||||
| `provides`, `uses` | Services offered to other plugins, and theirs you call. See "Services". |
|
||||
| `dependencies` | Plugins that must be installed for yours to work (for example `cad-viewer`). The user installs them; nothing is bundled. |
|
||||
|
||||
## Permissions
|
||||
|
||||
Shown to the organization's admin when they turn your plugin on, and to the
|
||||
user at install, in the plain words below. An update that adds a permission
|
||||
is reviewed again and must be approved again by each organization.
|
||||
|
||||
**App** means Cletus refuses calls you didn't declare. **Review** means the
|
||||
worker could technically do it, so review checks that the code does only
|
||||
what `docs/plan.md` says.
|
||||
|
||||
| Permission | Plain words | Grants | Enforced by |
|
||||
| --- | --- | --- | --- |
|
||||
| (always) | | Chat tools, own data folder, own screen, events, dialogs, published data links | app |
|
||||
| `files` | Open and save files you choose | `host.files.*`: files the user opened, dropped or picked, and the working folder within its write rules | app + review |
|
||||
| `files.computer` | Read and write other files on this computer | Direct access to the folders `docs/plan.md` names | review |
|
||||
| `workspace.read` | Read files in shared folders you're a member of | `host.workspace.folders / list / fetch / read` | app |
|
||||
| `workspace.write` | Add, change and delete files in shared folders | `host.workspace.put / delete / create / rename` | app |
|
||||
| `agents` | Run Cletus in the background | `host.agent.*` | app |
|
||||
| `chat.handoff` | Send work to chat | the screen's `handoffToChat` | app |
|
||||
| `identity` | See your name and organization | `host.auth.whoami` | app |
|
||||
| `net:<domain>` | Connect to <domain> | Direct connections from the worker to that host and its subdomains; at most 16 | review |
|
||||
| `net.configured` | Connect to devices and servers you set up in this plugin | Direct connections to addresses the user enters in the plugin (controllers, databases, instruments) | review |
|
||||
| `processes` | Run programs on this computer | Starting the programs `docs/plan.md` names | review |
|
||||
|
||||
Permissions for the planned host services (AI models, vision, speech,
|
||||
camera and microphone, saved connections, a secret store) will be added with
|
||||
them. Until then, a plugin that needs a password asks the user each time and
|
||||
keeps it in memory only.
|
||||
|
||||
**Never granted**, with or without review: the user's Cletus key or session;
|
||||
organization or provider credentials; vault keys or vault contents; other
|
||||
plugins' data; Cletus's settings and stored secrets; the user's chat
|
||||
history and memory; the commands of Cletus Code or System Administration;
|
||||
an organization's managed libraries.
|
||||
|
||||
## The worker
|
||||
|
||||
Cletus starts the worker on first use and stops it when the plugin is
|
||||
disabled, updated or uninstalled, or the user signs out. It restarts a
|
||||
crashed worker on the next call. The worker gets:
|
||||
|
||||
- `CLETUS_PLUGIN_ID`: your plugin id;
|
||||
- `CLETUS_PLUGIN_DATA`: its **data folder**, per signed-in account, kept
|
||||
across updates and uninstalls, and also its working directory. Settings,
|
||||
caches, history and job state go there (`pluginsdk.DataDir()`);
|
||||
- a few standard variables (`HOME`, `PATH`, temp folders, locale). Don't
|
||||
rely on any other variable;
|
||||
- standard input and output for messages, standard error for its log
|
||||
(`worker.log` in the data folder; never log user data).
|
||||
|
||||
The SDK's `pluginsdk.Serve` implements the protocol below. You register
|
||||
handlers.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
err := pluginsdk.Serve(pluginsdk.Handlers{
|
||||
Tools: []pluginsdk.Tool{countLines}, // chat tools
|
||||
UI: map[string]pluginsdk.Func{"check": check}, // answers ui.check
|
||||
Services: map[string]pluginsdk.ServiceFunc{"gauge.check": gauge}, // answers svc.gauge.check
|
||||
Busy: jobs.Running,
|
||||
})
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var countLines = pluginsdk.Tool{
|
||||
Name: "gauge_check_count_lines",
|
||||
Description: "Count the lines in a text file. Returns the count.",
|
||||
Parameters: map[string]any{"type": "object", "properties": map[string]any{"path": map[string]any{"type": "string"}}, "required": []string{"path"}},
|
||||
Run: func(ctx context.Context, h *pluginsdk.Host, call pluginsdk.ToolCall) pluginsdk.ToolResult {
|
||||
var args struct{ Path string `json:"path"` }
|
||||
if err := call.Decode(&args); err != nil {
|
||||
return pluginsdk.Fail("%v", err)
|
||||
}
|
||||
path, err := h.ResolvePath(ctx, args.Path) // host.files.resolve
|
||||
if err != nil {
|
||||
return pluginsdk.Fail("%v", err)
|
||||
}
|
||||
// … read path …
|
||||
return pluginsdk.Text("%s has %d lines.", path, n)
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
`h.Call(ctx, method, params, &out)` makes any call in `host-api.md`;
|
||||
`ResolvePath`, `CanWrite`, `Event` and `Publish` are typed shortcuts.
|
||||
`pluginsdk.NewTestHost` answers host calls in unit tests. The template that
|
||||
`cletus-plugin new` writes shows all of this working.
|
||||
|
||||
### Protocol
|
||||
|
||||
One JSON object per line in each direction, at most 64 MiB per line.
|
||||
|
||||
```json
|
||||
{"id": "7", "method": "tools.invoke", "params": {}}
|
||||
{"id": "7", "result": {}}
|
||||
{"id": "7", "error": {"message": "The drawing has no tolerance table."}}
|
||||
```
|
||||
|
||||
A host call made while handling request `7` carries `"parent_id": "7"`.
|
||||
Cancellation is `{"method": "$/cancel", "params": "7"}`: stop, and reply
|
||||
with an error. Stop promptly: a worker that ignores cancellation may be
|
||||
stopped.
|
||||
|
||||
| Method the worker answers | Params | Result |
|
||||
| --- | --- | --- |
|
||||
| `plugin.handshake` | none | `{"api_version": 1}` |
|
||||
| `plugin.shutdown` | none | `{"ok": true}`, then exit |
|
||||
| `plugin.busy` | none | `true` while work must not be interrupted; Cletus then won't update or stop the plugin |
|
||||
| `tools.list` | none | `[{"def": <function definition>, "mutating": bool, "requires_approval": bool}]` |
|
||||
| `tools.invoke` | `{"name", "args", "working_dir", "edit_mode"}` (`args` is JSON text) | `{"Text": "…"}` or `{"Error": "…"}`; `"Images": [base64]` optional |
|
||||
| `ui.<name>` | the screen's arguments, as a JSON array | any JSON; answer within 60 seconds (start longer work as a job and report it with events) |
|
||||
| `svc.<service>.<method>` | `{"caller", "mutating", "args"}` | must match the declared `result` schema |
|
||||
|
||||
### Chat tools
|
||||
|
||||
The AI reads your tool definitions and results, so write them for it:
|
||||
|
||||
- One tool does one thing. Name it `<plugin short name>_<verb>`.
|
||||
- The description says what it does, when to use it, and what it returns.
|
||||
- Results are short and factual. A failure says why and what to call next.
|
||||
Never try a different method silently.
|
||||
- When `edit_mode` is false, the user has Cletus read-only: a mutating tool
|
||||
asks `host.files.can_write` for each path and refuses if the answer is no.
|
||||
|
||||
The same tools run in background agent runs, where nobody watches. Don't open
|
||||
dialogs from a tool.
|
||||
|
||||
## The screen
|
||||
|
||||
**The screen toolchain (the `@cletus/plugin-sdk` package, its build and a
|
||||
preview host) arrives in a later SDK release.** Until then, build the worker
|
||||
and chat tools; a plugin with tools only is complete and publishable. Design
|
||||
your screen with the rules below so it drops in when the toolchain ships.
|
||||
|
||||
- The screen runs in a sandboxed frame with no network access. It cannot see
|
||||
the Cletus window or other plugins.
|
||||
- It calls only its own worker: `call('check', arg)` reaches the worker's
|
||||
`ui.check` method, with the arguments as a JSON array.
|
||||
- Events from the worker (`host.event`) reach the screen only under your own
|
||||
`<id>:` prefix.
|
||||
- Large data (meshes, images, tables) is published by the worker
|
||||
(`host.data.publish`) and fetched by the screen from the returned link.
|
||||
Every other request is blocked.
|
||||
- Settings: up to 512 KiB, stored by the host.
|
||||
- WebGL, WebGPU, Web Workers, canvas, clipboard writes and downloads of your
|
||||
published data work. The camera and microphone don't.
|
||||
- Cletus draws the menu bar and the ← Chat button above the screen.
|
||||
- Colours, density and layout follow `style-guide.md`.
|
||||
|
||||
## Services
|
||||
|
||||
Plugins share no code. They call each other's declared services through the
|
||||
host, which checks both sides.
|
||||
|
||||
```json
|
||||
"provides": {
|
||||
"gauge": {"methods": {"check": {
|
||||
"description": "Check a gauge file against its drawing.",
|
||||
"params": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"], "additionalProperties": false},
|
||||
"result": {"type": "object"},
|
||||
"mutating": false,
|
||||
"long": false
|
||||
}}},
|
||||
"screen": {"methods": {"open": {"params": {"type": "object"}, "result": {"type": "null"}}}}
|
||||
},
|
||||
"uses": ["cad-viewer:cad.open", "acme.fixtures:fixture.*"]
|
||||
```
|
||||
|
||||
- Services are lowercase and may be dotted (`gauge.batch`). Methods are
|
||||
`[a-z][a-z0-9_]*`. A use is `<plugin>:<service>.<method>` or
|
||||
`<plugin>:<service>.*`.
|
||||
- `params` and `result` are JSON schemas (draft 2020-12 or draft-07, at most
|
||||
16 KiB, no remote `$ref`). The host checks arguments and results against
|
||||
them.
|
||||
- `screen.open` is reserved. Provide it if other plugins may open your screen.
|
||||
Its `params` is the payload your `open()` receives.
|
||||
- Calls are limited to 5 per second after a burst of 20 per pair, 1 MiB of
|
||||
arguments, and 2 minutes (15 minutes for methods marked `long`).
|
||||
- A mutating method linked to a tool that needs approval is refused when
|
||||
called from another plugin, because the user can't be asked.
|
||||
- Services between your own plugins work the same way. Services of Eidonix
|
||||
plugins are not open to publishers yet; they will be listed in
|
||||
`host-api.md` when they are.
|
||||
|
||||
## Versions and data
|
||||
|
||||
- Users choose when to update and can go back to any earlier version. Keep
|
||||
every file you write readable after a downgrade, or refuse it with a plain
|
||||
message naming the version that wrote it.
|
||||
- Uninstalling keeps the data folder. Reinstalling picks it up.
|
||||
- `min_app_version` protects users on older Cletus releases. Raise it
|
||||
whenever you use a newer host method.
|
||||
|
||||
## Limits
|
||||
|
||||
| Limit | Value |
|
||||
| --- | --- |
|
||||
| Message | 64 MiB per line |
|
||||
| Service call | 1 MiB arguments; 2 minutes, or 15 if `long` |
|
||||
| Background agent runs | 4 queued or running per plugin |
|
||||
| Published data link | 1 hour at most |
|
||||
| Screen storage | 512 KiB |
|
||||
| `workspace.read` | 2 MiB per call (use `fetch` for larger files) |
|
||||
| Package | 768 MiB download, 3 GiB unpacked, 20,000 files |
|
||||
Reference in new issue
Block a user