Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.altnautica.com/llms.txt

Use this file to discover all available pages before exploring further.

The host runs the bridge; plugins consume two flavors of message:
  • Events: pushed from the host, no response expected (config.changed, theme.changed, telemetry.<topic>).
  • Responses: returned by the host in reply to a plugin request.
Plugins themselves only subscribe to events and send requests.

Events the host pushes

MethodCapabilityArgsWhen
theme.changedtheme.useThemeRecord<string, string> of CSS variablesInitial mount; whenever the host theme changes.
config.changednonethe plugin’s full config objectOperator saves a new config under Settings -> Plugins.
telemetry.<topic>telemetry.subscribe.<topic>topic-specific payloadAfter the plugin called telemetry.subscribe.
lifecycle.ticknone{ uptimeMs }Once a minute while the plugin is running.
recording.startednone{ recordingId, startedAtMs }Operator starts a recording.
recording.stoppednone{ recordingId, durationMs }Operator stops a recording.
Subscribe via ctx.client.on("config.changed", handler) or via the typed wrappers on ctx.config.onChange, ctx.theme.onChange, etc.

Requests the plugin can send

Every RPC carries { method, capability, args }. The host re-resolves the required capability from method and args and checks the granted set. The plugin never has to compute the capability id itself.
MethodCapabilityArgsReturns
telemetry.subscribetelemetry.subscribe.<topic>{ topic }{ ok }
telemetry.unsubscribederived{ topic }{ ok }
command.sendcommand.send{ command, args }host-defined
mission.readmission.read{ missionId }mission body
mission.writemission.write{ missionId, payload }{ ok, version }
notification.publishui.slot.notification{ channelId, severity, title, body, meta }{ ok }
recording.markrecording.write{ label, meta }{ ok }
event.publishevent.publish{ topic, payload }{ ok }
event.subscribeevent.subscribe{ topic }{ ok }

Wire shape

interface RpcEnvelope {
  id: string;
  type: "request" | "response" | "event";
  method: string;
  capability: string;
  args: unknown;
  version: 1;
  error?: { code: string; message: string };
}
The plugin never builds these by hand. The SDK’s PluginClient generates the id, attaches the protocol version, and waits for the correlated response.

Error envelopes

When the host rejects a request it returns a response with error: { code, message }. The SDK throws a HostError whose code field carries the machine-readable error code:
CodeMeaning
permission_deniedThe required capability is not in the granted set.
method_unknownThe method is not in the host’s registry.
schema_invalidArgs failed schema validation.
handler_unsetThe host knows the method but no handler is wired.
handler_errorThe host’s handler threw. The message is the thrown error’s message.
Plugins should branch on code, not on message. Messages are for log lines.

Theming

theme.changed arrives once on mount and again on every theme toggle. The payload is a Record<string, string> of CSS variables. Apply them by walking the entries:
ctx.theme.onChange((vars) => {
  for (const [key, value] of Object.entries(vars)) {
    document.documentElement.style.setProperty(key, value);
  }
});
The SDK does not auto-apply because plugins choose how granular they want to be (full root, scoped to a panel, or ignored).

i18n

Plugin authors ship a locale bundle in their archive; the host streams the active bundle to the plugin on mount. The SDK formats keys with ctx.i18n.t(key, params):
ctx.i18n.t("anomaly.cellLow", { voltage: 3.4 });
// "Cell low: 3.4 V"
Missing keys fall back to the key itself; missing parameters render the {name} placeholder so it is visible in QA.