284 lines
13 KiB
Markdown
284 lines
13 KiB
Markdown
# 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 |
|