Files

13 KiB

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

{
  "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 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.

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.

{"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.

"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