# Autocore Tool & Editor Registry
A small ecosystem primitive that lets external packages contribute **tools and
editors** to an autocore installation — discovered by `autocore-server` (to
supervise the ones that are long-running services) and by `autocore-ide` (to
offer rich, custom editors for module configuration) — without either of them
hard-coding any knowledge of a specific tool.
The first citizen is `labelit-studio`, the web-based vision-configuration
editor for `autocore-labelit`. The mechanism is general: future tools
(an EtherCAT slave-tree editor, an NI channel-grid, diagnostics) register the
same way.
This document is the contract. The implementation lives in
`mechutil::tool_registry` and `mechutil::tool_settings`.
## Design principles
1. **Package-owned manifest, separately-owned settings.** A *manifest*
describes what a tool *is* (executable, what it edits, defaults). It ships
inside the package and is replaced freely on upgrade. *Settings* describe
what an admin *chose* (the actual port, enabled/disabled, bind address).
They live in a different directory, are never shipped by the package, and
therefore survive upgrades. Conflating the two would let `apt upgrade`
silently reset a field-configured port.
2. **dpkg owns the files; no shared-file editing.** A package contributes by
*shipping a manifest file* into a drop-in directory. Install registers it;
uninstall removes it — handled entirely by the package manager's own file
tracking. This replaces the older approach of awk-editing a shared
`config.ini`, which is fragile under concurrent installs and messy to
purge.
3. **One registry, multiple consumers.** `autocore-server` reads it to
supervise service tools. `autocore-ide` reads it to learn which module
domains have a custom editor. A CLI could read it to list installed tools.
None of them needs tool-specific code.
4. **Supervision stays at the autocore tier.** Service tools are supervised by
`autocore-server` (the same machinery that launches modules), not by
systemd. This keeps the forthcoming user-authentication layer at the
autocore tier, decoupled from OS users.
## Filesystem layout
All under the autocore config directory, resolved by
`mechutil::resolve_config_dir()` (`AUTOCORE_CONFIG_DIR` env →
`/srv/autocore/config` → `./target/autocore/config` writable fallback for
unprivileged dev runs — mirrors `resolve_log_dir`):
```text
/srv/autocore/config/
├── config.ini # existing server/module config
├── tools.d/ # MANIFESTS — package-owned (dpkg)
│ ├── labelit-studio.json
│ └── <tool>.json (or <tool>/manifest.json + bundled resources)
└── tool-settings/ # SETTINGS — runtime, survives upgrades
└── labelit-studio.json
```
`tools.d/` accepts either a flat `<tool>.json` or a `<tool>/manifest.json`
inside a per-tool directory (use the directory form when a tool also ships
resources such as an icon or extra schemas).
## The manifest
Contribution metadata. Shipped by the package; read-only at runtime.
```json
{
"schema_version": 1,
"name": "labelit-studio",
"version": "1.1.3",
"description": "Vision-pipeline configuration editor for autocore-labelit",
"executable": "/opt/autocore/bin/modules/labelit-studio",
"serves_http": true,
"ui_path": "/",
"launch": {
"mode": "service",
"default_port": 7878,
"autostart": true,
"args": []
},
"editors": [
{
"target_domain": "labelit",
"target_path": null,
"label": "Vision Editor"
}
]
}
```
Fields:
- `name` — unique tool id (kebab-case). Must match the manifest's file stem.
- `executable` — absolute path to the binary.
- `serves_http` / `ui_path` — whether the tool serves a web UI and where its
root is. Drives the security gate and lets a consumer point a browser /
webview at it.
- `launch.mode` — `"service"` (long-running; `autocore-server` supervises it)
or `"on_demand"` (invoked by the IDE/CLI when needed; never auto-launched).
- `launch.default_port` — default for the HTTP port; the *effective* port
comes from settings.
- `launch.autostart` — default for whether a service tool starts with the
server; the *effective* value comes from settings.
- `launch.args` — extra arguments inserted before the tool's standard args
(mirrors `ModuleConfig.args`).
- `editors[]` — the config editors this tool contributes. One tool can
contribute several. Each binds to a module `target_domain` and an optional
`target_path` (a JSON pointer into that module's config, e.g.
`/robot_calibration`) so a tool can be the whole-section editor *and* a
focused widget for a sub-tree. `null` path = the whole module config.
## Settings (two-tier)
`mechutil::tool_settings::ToolSettings` is what every tool — and the server —
reads and writes. It is deliberately two-tier so the server can read launch
settings *without understanding any tool's internals*:
```jsonc
{
"launch": { // reserved schema; the SERVER reads this
"enabled": true,
"port": 7878,
"bind": "127.0.0.1",
"autostart": true
},
"extra": { // free-form; tool-private, the server never parses it
"last_image_dir": "/home/me/snaps"
}
}
```
- `launch.bind` defaults to `127.0.0.1`. **Until autocore user-auth lands, a
`serves_http` tool must not be bound to a routable address by default** — it
is an unauthenticated config-and-camera surface. Exposing it wider is an
explicit admin choice (and ideally Tailscale-only).
- Settings are created on first read, seeded from the manifest's `launch`
defaults (`open_seeded`). Writes are atomic (temp file + rename).
- `extra` is an arbitrary JSON object; tools store whatever they like there
without breaking the `launch` contract the server depends on.
## API surface (mechutil)
```rust
// paths
mechutil::resolve_config_dir() -> PathBuf
// registry (read + register)
mechutil::tool_registry::{
ToolManifest, EditorContribution, LaunchSpec, LaunchMode,
tools_dir, // <config>/tools.d
list_tools() -> Vec<ToolManifest>, // scan + validate, skip bad ones (logged)
load_tool(name) -> Result<ToolManifest>,
find_editor_for(domain, path) -> Option<(ToolManifest, EditorContribution)>,
register_tool(&ToolManifest) -> Result<()>, // for non-deb tools / dev installs
unregister_tool(name) -> Result<()>,
}
// settings (read/write)
mechutil::tool_settings::{
ToolSettings, LaunchSettings,
settings_dir, // <config>/tool-settings
ToolSettings::open(name) -> Result<ToolSettings>,
ToolSettings::open_seeded(&manifest)-> Result<ToolSettings>, // defaults from manifest
ToolSettings::save(&self) -> Result<()>,
}
```
Debs ship the manifest as a packaged file (dpkg = register/deregister);
`register_tool`/`unregister_tool` exist for tools that aren't deb-packaged and
for tests. Both validate against the manifest rules above.
## Consumers (outside mechutil — specified here, built later)
- **autocore-server** scans `tools.d/` at startup, and for each manifest with
`launch.mode == "service"` whose settings have `enabled && autostart`,
supervises `executable [launch.args] --port <settings.launch.port> --bind
<settings.launch.bind>` using the existing module-supervisor machinery. It
exposes `system.list_tools` (discovery for the IDE) and `system.rescan_tools`
(so an install/uninstall takes effect without a server restart — debs call
it from `postinst`/`postrm`).
- **autocore-ide** consults the registry to decide whether a module domain has
a custom editor. When connected to a target it uses `system.list_tools`;
offline it reads `tools.d/` directly or falls back to a bundled manifest —
the same IPC → bundled → cache chain it already uses for `<module>.schema`.
For `labelit`, finding the `labelit-studio` editor lets the IDE offer "Open
Vision Editor" (point a webview at the target's running studio, or spawn a
local sidecar) instead of only the generic schema form.
## The three config scopes (so nothing gets muddled)
1. **Module operational config** — `modules.labelit.config` in `project.json`.
The thing the editor *edits*. Project data; synced to targets.
2. **Tool manifest** — `tools.d/<tool>.json`. Package metadata describing the
tool to the registry.
3. **Tool settings** — `tool-settings/<tool>.json`. The tool's own runtime
settings (port, bind, private state). Not project data, not package
metadata.
The editors a tool contributes act on scope 1, and compose with the existing
`mechutil::ipc::schema` infrastructure: a module already serves a JSON Schema +
`ui_hints` at `<domain>.schema`; the registry adds "and a richer editor exists
for this domain."
```