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,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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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 |
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in new issue
Block a user