Cletus plugin kit: rules for coding agents, specification, host API, style guide, worker SDK and cletus-plugin tool

This commit is contained in:
Eidonix committed 2026-11-10 19:50:09 -05:00
commit 9b19d3a8be
41 files changed
+4121

No files matched your search

+82
View File
@@ -0,0 +1,82 @@
# Host API reference (version 1)
Every method a worker may call, with the permission it needs. A worker calls
them only while handling a request (`parent_id`). Errors return
`{"error": {"message"}}` as a plain sentence; show it or pass it on, don't
parse it.
Only the methods listed here exist. More host services (the organization's
AI models, vision, speech, camera and microphone, saved connections and a
secret store) are planned and will be added to this reference when they are
released. Don't call a method that isn't listed.
## Always available
| Method | Params | Result |
| --- | --- | --- |
| `host.auth.has_capability` | `{capability}` | `{granted}`: whether the signed-in user has a Cletus feature |
| `host.data.publish` | `{path, contentType, ttl}` | `{token, url, expiresAt}`: a link your screen can fetch for an immutable file in your data folder; `ttl` is 1 to 3600 seconds, default 300 |
| `host.data.revoke` | `{token}` | releases a link |
| `host.data.leases` | `{path}` | the number of live links to a file |
| `host.data.remove` | `{path}` | deletes a file in your data folder unless a link holds it (never check, then delete) |
| `host.event` | `{name, args}` | sends an event to your screen; `name` starts with `<your id>:` |
| `host.screen.open` | `{id, payload}` | opens your screen, or another plugin's screen that it offers as `screen.open` and that you `use` |
| `host.dialog.open` | `{Title, DefaultDirectory, Filters: [{DisplayName, Pattern}]}` | the chosen path, or `""` |
| `host.dialog.folder` | as `open` | the chosen folder, or `""` |
| `host.dialog.save` | `{Title, DefaultDirectory, DefaultFilename, Filters}` | the chosen path, or `""` |
| `host.dialog.message` | `{Type, Title, Message, Buttons}` | the button pressed |
| `host.plugin.call` | `{target, service, method, args}` | the provider's result; see "Services" in the specification |
Dialogs and screens are refused in background runs, where nobody is
watching.
## `files`
| Method | Params | Result |
| --- | --- | --- |
| `host.files.resolve` | `{path}` | the real path (a string) of a file the user opened, dropped or picked, or one in the working folder; an error otherwise. Resolve every path before reading it. |
| `host.files.can_write` | `{path}` | `true` or `false` under the user's current write rules (read-only mode, background-run limits) |
## `workspace.read`, `workspace.write`
Shared folders. Keys are full: `ws:<uuid>/files/<path>`. Vault (end-to-end
encrypted) folders and anything outside `files/` are never visible.
| Method | Permission | Params | Result |
| --- | --- | --- | --- |
| `host.workspace.folders` | read | `{}` | `[{id, name, role, personal?}]`; `role` is `owner`, `member` or `viewer` |
| `host.workspace.list` | read | `{workspace, prefix}` | `[{key, size, etag, modified}]`, at most 1000; `workspace` is an id or an exact folder name |
| `host.workspace.fetch` | read | `{key}` | `{path, etag}`: a local cached copy (unchanged files aren't downloaded again) |
| `host.workspace.read` | read | `{key}` | `{data, etag, size}`: the bytes as base64, in memory, at most 2 MiB |
| `host.workspace.put` | write | `{workspace, key, path}` or `{workspace, key, data}` | `{key, size, etag}`; `key` is relative to `files/`; owner or member only |
| `host.workspace.delete` | write | `{key}` | `{key}`; deleting a missing key succeeds |
| `host.workspace.create` | write | `{name}` | `{id, name, role}`: a new folder the user owns |
## `agents`
| Method | Params | Result |
| --- | --- | --- |
| `host.agent.start` | `{playbook_slug?, prompt, paths, label}` | `{run_id}` |
| `host.agent.status` | `{run_id}` | `{run_id, state, steps, last_message, error, label, report}` |
| `host.agent.cancel` | `{run_id}` | the status after the cancel |
A run is a separate Cletus conversation over files the user chose (`paths`,
at most 64, each passing `host.files.resolve`). Give a playbook, a prompt,
or both. It can use your plugin's tools and writes only inside the folders
in `paths`. `state` is `queued`, `running`, `done`, `failed` or `cancelled`.
Events `<id>:agent-progress` and `<id>:agent-done` carry the status; key
your screen's state by `run_id`. At most 4 runs per plugin are queued or
running.
## `identity`
| Method | Params | Result |
| --- | --- | --- |
| `host.auth.whoami` | `{}` | `{key_id, name, org, org_id}`. `key_id` is the person's identity; names are unique only within an organization. |
## Screen SDK
Arrives with the screen toolchain in a later SDK release. It will offer
calls to your own worker, your events, navigation, confirm dialogs,
storage, clipboard writes, the theme, and the host's components (icons,
side panel, status bar, context menu, the standard 3D viewport look).
+73
View File
@@ -0,0 +1,73 @@
# Patterns
How to build common kinds of plugin on this contract. Each pattern says what
works today and what waits for a later release.
## Chat tools over your own files and formats
Works today. The smallest useful plugin and the fastest to review.
- Tools that read a format Cletus doesn't understand (an instrument export, a
machine log, a proprietary report) and return a short, typed summary.
- `files` permission; every path through `host.files.resolve`.
- Large results are written to a file next to the input (after
`host.files.can_write`) and the tool returns its path and a summary.
## Long computations and batch jobs
Works today.
- A tool or service that starts a job returns a job id at once. Separate
tools report `status`, return `results` and `cancel`.
- Job state lives in the data folder, so a restarted worker can report it.
- `plugin.busy` answers `true` while a job runs, so Cletus doesn't stop the
worker for an update.
- Results are written in the folder the user chose, never elsewhere.
## Shared reference data
Works today.
- Reference data a team shares (standards, tables, templates) lives in a
shared folder the user picks (`workspace.read`), never inside the plugin.
- The worker fetches it with `host.workspace.fetch`, which caches unchanged
files.
- Writing results back for the team needs `workspace.write`.
## Reading documents in the background
Works today.
- `agents`: `host.agent.start` runs Cletus over the files the user chose,
with a playbook or a prompt. The run can call your plugin's own tools.
- Poll `host.agent.status`, or wait for `<id>:agent-done`.
## Connecting machines, databases and services
Works today, with direct network access.
- `net.configured` for addresses the user enters (controllers, databases,
instruments), or `net:<domain>` for a fixed service.
- Pure-Go drivers only.
- Read-only by default. Every write is a separate tool marked `mutating` and
`requires_approval`, and `docs/plan.md` says what it changes.
- Until the secret store ships, ask for passwords when needed and keep them
in memory only.
## Libraries written in C or C++
Works today.
- Compile them to WebAssembly from source in your repository and run them
with [wazero](https://wazero.io) inside the worker.
- The build stays pure Go, and review sees the library source.
## With a later release
- **Screens:** 2D and 3D screens in a sandboxed frame. The worker computes
and publishes data (`host.data.publish`); the screen draws it.
- **The organization's AI, vision and speech:** summaries, image reading,
transcription.
- **Camera and microphone** through the host.
- **Saved connections and a secret store.**
- **Services of Eidonix plugins**, such as opening a file in CAD Viewer.
+55
View File
@@ -0,0 +1,55 @@
# Review checklist
The store's reviewers walk this list for every submission. Walk it yourself
first. Anything marked **(auto)** is also checked by `cletus-plugin check`.
## Identity and listing
- [ ] `id` starts with your publisher id; `version` is new and matches the tag. **(auto)**
- [ ] Name, description and guide are accurate and plain. No
claims about Eidonix or other companies, no imitation of their screens.
- [ ] `CHANGELOG.md` describes this version for users.
## Permissions
- [ ] Every declared permission is used, and every use is declared. **(auto, for host calls)**
- [ ] `docs/plan.md` gives a reason for each permission that a user would accept.
- [ ] `files.computer`, `net:*`, `net.configured` and `processes`: the code
does only what `docs/plan.md` says (folders, hosts, addresses typed by
the user, named programs).
- [ ] Data leaves the machine only where the description says so.
## Code
- [ ] Builds from source with no network, `CGO_ENABLED=0`, no prebuilt
binaries, vendored dependencies. **(auto)**
- [ ] No code loaded, generated or evaluated at run time. **(auto, flags for review)**
- [ ] No obfuscation; any `unsafe`, `reflect`, `syscall`, `os/exec`, `net`
or direct file access is explained and matches the permissions. **(auto flags)**
- [ ] No behaviour that depends on the date, machine, user or test mode.
- [ ] Paths come from the host or the data folder; nothing reads Cletus's
settings, keys, history, vaults or other plugins' data.
- [ ] Secrets only through `host.secrets`; none in code, files or logs. **(auto scan)**
- [ ] No telemetry. Logs carry no user data.
- [ ] Dependencies: licences allow distribution; no known serious
vulnerability. **(auto)**
- [ ] Handlers honour cancellation; long jobs have progress and cancel;
`plugin.busy` is truthful.
- [ ] Files written stay readable across downgrades, or are refused with a
plain message.
## Behaviour (the recorded run)
Reviewers run the plugin under a recording host that logs every host call,
file opened, connection attempted and program started.
- [ ] Everything in the recording matches the permissions and `docs/plan.md`.
- [ ] Chat tools do one thing each, are marked `mutating` correctly, and
fail with a reason and a next step.
- [ ] A screen, if any, follows `style-guide.md` in all three themes: tokens only,
density, no layout shift, Esc, plain errors.
## Tests
- [ ] Go tests cover every tool, service method and `ui.*` method, and pass. **(auto)**
- [ ] Fixtures are synthetic.
+283
View File
@@ -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 |
+134
View File
@@ -0,0 +1,134 @@
# Style guide
Cletus is a professional tool for engineers, analysts and shop-floor teams.
A plugin should feel like part of it: calm, dense, exact, and never in the
way. Review checks this guide.
## 1. Principles
- **It just works.** Values that can be computed are computed. Nothing
waits for a button press that could have been automatic.
- **Model the tool users already know.** A CAD screen behaves like the CAD
viewers your users use; a forming screen like the forming tools they use.
Keep familiar mouse controls and names.
- **Nothing jumps.** No layout shift from messages, panels or loading.
- **Professional density.** Small type, tight rows, icons. Never big
friendly buttons.
- **Simple surface, depth underneath.** Show what most people need; put the
rest one click away, not on screen.
## 2. Words
- Plain words, short sentences. Write for a manufacturing engineer, not a
programmer.
- No internal codes, ids, hashes or stack traces in the interface. A
technical detail appears only when it adds to the plain sentence, behind
"Details".
- Errors say what happened and what to do: "The drawing has no tolerance
table. Add one, or enter tolerances under Settings." Not "Error 422" or
"Something went wrong".
- Sentence case for labels and buttons ("Save report", not "Save Report").
- Units always shown beside the value and the input (`10 mm`).
- Inexact values carry `~` with the reason on hover. Exact values carry no
mark.
## 3. Theme
- Use the host's tokens (CSS variables) for every colour. Never hard-code a
colour, and never assume the theme is dark.
- Cletus has three base themes (Amber, which is dark; Dark; Light) and
customer variants. Test all three.
- Engineering and data-dense screens set `"theme": "tool"`. Style controls
with the semantic tokens:
- toggled buttons: `--pressed-fill / -text / -border / -indicator`
- primary buttons: `--primary-*`
- the active tab: `--tab-indicator`
- density: `--tool-font` (12 px), `--tool-row` (22 px), `--tool-pad` (6 px)
- The accent colour is for thin indicators and small marks. No large filled
accent areas.
- One surface per card. No boxes in boxes.
- Judge theming by intent (readable, consistent, calm), not by pixels.
## 4. Layout
- **One job per region.**
- The left rail holds the thing being worked on (a model tree, a
document list, jobs).
- The right rail holds tools, in one collapsible `SidePanel` that floats
over the content and never resizes it. Collapsed, it is a slim icon
rail.
- The top toolbar holds view controls only.
- A tool's sub-tools appear as a small strip at the top of the content only
while that tool is active.
- A fixed-height status bar shows the selection, progress with cancel, and
dismissable failures. Errors never push content around.
- Floating cards (properties, details) open only when asked for, are movable
and closable, and never open on a click or right-click.
- Nothing a dialog opens may land under it. A setting a dialog needs appears
inside that dialog.
- Settings sit behind a gear at the bottom right, not on screen-switching
bars.
- Icons on every toolbar button (host `Icon` set), with the name and
shortcut in the tooltip.
## 5. Behaviour
- **Esc** stops what is running or closes the innermost thing open (a menu,
a live measurement, a panel). It never discards saved work.
- Every job over a second shows progress, an estimate where possible, and
cancel.
- Instant values compute on selection with "Computing…" in place. A failure
is a muted "unavailable" with the reason on hover, not a red banner.
- Selection is easy to clear: empty click, Esc, × on the status-bar chip,
right-click → Clear selection. Hover shows exactly what a click will pick.
- Right-click shows only a menu. Menus are compact and grouped, like
Cletus's own.
- A resize changes the aspect only. Never re-fit or re-zoom the user's view.
- Anything placed on the content (pins, labels, annotations) has a visible
way to remove it.
- Files dropped on chat only attach to chat. Your plugin opens a file only
when the user chooses "Open in <your plugin>" or drops it on your screen.
## 6. 3D and 2D viewports
- Use `sdk_viewport` for backgrounds and selection so every viewport looks
the same.
- Selection: a soft tint on faces and bodies; the full accent only on
selected edges and outlines. Never flood a part in the accent colour.
- Offer "Set orbit centre here" (trackpads have no middle button).
- Labels stay inside the viewport and hide or point off-screen when their
anchor is hidden. Never draw over panels or toolbars.
## 7. Chat tools
- One tool, one operation. No hidden retries or fallbacks inside a tool: fail
with the reason and the suggested next call, and let the AI decide.
- Results are short, factual and stable: the same input gives the same
text.
- Name tools `<short plugin name>_<verb>[_<object>]` (`gauge_check_run`).
- Descriptions say when to use the tool and when not to.
## 8. Go
- `gofmt`, `go vet`, `staticcheck` clean. Standard library first.
- Small packages under `internal/`, named for what they do. No `util`,
`common` or `helpers`.
- Errors wrap with context (`fmt.Errorf("read tolerance table: %w", err)`)
inside the worker; the message that reaches the user is a plain sentence.
- Every blocking call takes `ctx`. No goroutine without a way to stop it.
- Comments say why, not what. Exported names have doc comments.
## 9. TypeScript and Svelte
- Svelte 5 runes. An `$effect` never reads and writes the same `$state`.
- Strict TypeScript. No `any` without a comment.
- Components small and named for what they show.
- No network calls; data comes from your worker or published links.
## 10. Tests
- Every tool, service method and `ui.*` method has a Go test.
- With screens (a later release): every screen feature gets a browser test
that checks it changes what the user sees. Don't compare pixels.
- Fixtures are synthetic. Reproduce real-world failures with made-up files
that have the same shape.
+101
View File
@@ -0,0 +1,101 @@
# Submitting a plugin
**Submissions open with a later release.** This is how they will work, so
you can prepare now.
Plugins reach users only through the Eidonix plugin store. Cletus refuses
any plugin Eidonix has not signed. There is no other way to install one.
## 1. Become a publisher
Ask Eidonix for a publisher account. Eidonix checks:
- your company (registration documents);
- your web domain: you add a DNS TXT record
`_cletus-publisher.<your domain>` with the value Eidonix gives you;
- a signed publisher agreement (confidentiality, review, withdrawal).
You receive:
- your **publisher id** (the first half of every plugin id: `acme.…`);
- an account on `git.eidonix.com`, with a private repository for each plugin
(`plugin-submissions/acme-gauge-check`). Only you and Eidonix's review
team can see it;
- a place to register the SSH or GPG key you sign tags with.
Users see your verified company name and domain on every listing and
install screen.
## 2. Build and check
Develop wherever you like. Before submitting:
```sh
cletus-plugin check # the store's automated checks, locally
```
## 3. Submit
1. Push your code to your plugin's repository on `git.eidonix.com`.
2. Push a **signed tag** `v<version>` that matches `version` in
`plugin.json`. The tag is the submission.
Unsigned tags, tags signed by an unregistered key, and versions that were
used before are ignored.
Also include `listing/listing.json`:
```json
{
"summary": "Check measured gauges against the drawing's tolerances.",
"category": "Quality",
"visibility": "store",
"organizations": [],
"support": "support@acme.com",
"price": "free"
}
```
- `visibility` is `"store"` (every organization can find it) or
`"organizations"`: only the organizations listed, by name. Use that for a
plugin made for one customer.
- `price`: `"free"`, or `"paid"`. Paid plugins are arranged with Eidonix
directly for now; the store does not take payments yet.
## 4. What happens next
Progress shows as a status on your tag, and in an issue opened for each
submission in your repository. Review comments go in that issue.
| Status | Meaning |
| --- | --- |
| Building | Eidonix is building from your tag. |
| Checks failed | The build or an automated check failed. The issue says which; fix and submit a higher version. |
| In review | A reviewer is reading the code and running the plugin. |
| Changes requested | Fix what the issue lists and submit a higher version. |
| Approved | Waiting for Eidonix to sign and publish. |
| Published | Available in the store (or to the organizations listed). |
| Rejected | Not publishable. The issue says why. |
The first review of a plugin reads all of it. Updates are reviewed as the
difference from the last published version, so small, focused releases go
through faster. An update that adds a permission is reviewed in full, and
organizations must approve it again before their users get it.
## 5. After publishing
- Users choose when to update and may go back to earlier versions. Every
published version stays available unless withdrawn.
- You can withdraw a version or the plugin (ask in your repository's
issues). Withdrawn versions stop being offered; installed copies keep
working unless Eidonix revokes them.
- Eidonix revokes a version, plugin or publisher that is harmful or breaks
the agreement. A revoked plugin stops on every machine. Users keep the
files it saved.
- Problems users report reach you through your repository's issues.
## Your source code
- Only the built plugin is distributed. Your source is not published.
- How Eidonix may use, store and retain your source is set out in the
publisher agreement, which you sign before your first submission.