atriumatrium

Building an adapter

The SDK v2 declarative manifest spec.

atrium uses SDK v2 adapters, which are largely declarative. Most integration (binary discovery, session discovery, launch composition, launcher UI) lives in adapter.json. Shell scripts are only needed for the one method that stays dynamic — hooks.

SDK v1 (fully script-based: detect_running.sh, extract_session_id.sh, list_recent_sessions.sh, build_resume_command.sh) is deprecated and no longer supported.

Manifest schema

Canonical schema: atrium-adapters/schemas/adapter.schema.json.

{
  "sdkVersion": 2,
  "name": "my-tool",
  "displayName": "My Tool",
  "description": "My AI coding assistant",
  "accent": "#4CAF50",
  "binary": "my-tool",
  "version": "1.0.0",
  "author": "me",
  "icon": "icon.svg",
  "skillInstallPath": "~/.my-tool/skills/atrium/SKILL.md",

  "binaryDiscovery": {
    "commands": ["my-tool"],
    "wellKnownPaths": [
      "~/.npm/bin/my-tool",
      "/usr/local/bin/my-tool",
      "/opt/homebrew/bin/my-tool",
      "~/.local/bin/my-tool"
    ]
  },

  "sessions": {
    "pattern": "~/.my-tool/history/*.json",
    "titleField": "title",
    "idField": "id"
  },

  "launch": {
    "base": ["my-tool"],
    "resumeFlag": ["--resume", "{session_id}"],
    "flagMap": {
      "thinking": ["--thinking"],
      "model": ["--model", "{value}"]
    }
  },

  "launcherOptions": [
    {
      "id": "thinking",
      "kind": "toggle",
      "label": "Thinking mode",
      "default": false
    },
    {
      "id": "model",
      "kind": "select",
      "label": "Model",
      "options": [
        { "label": "Fast", "value": "fast" },
        { "label": "Smart", "value": "smart" }
      ],
      "default": "smart"
    }
  ],

  "hooks": {
    "session-start": "atrium://hooks/my-tool/session-start",
    "session-end": "atrium://hooks/my-tool/session-end"
  },

  "methods": {
    "hooks": { "script": "hooks.sh" }
  }
}

Required fields

FieldTypeNotes
sdkVersionintegerMust be 2.
namekebab-case stringMachine ID. Must match the directory name.
displayNamestringShown in the launcher and Settings → Tools.
descriptionstringOne-liner.
accent#RRGGBBUI accent color for this adapter's panes.
binarystringName of the wrapped CLI tool.
versionsemverAdapter version, independent of the tool's version.
methodsobjectMaps method names to their implementation. In SDK v2 only hooks is typically needed.

Optional fields

FieldTypeNotes
iconfilenameInline brand SVG, loaded at adapter init. See icon.
skillInstallPathpath stringWhere to copy the canonical atrium skill. Tilde-expanded.
authorstring
binaryDiscoveryobjectHow atrium resolves the binary. See below.
sessionsobjectHow atrium lists recent sessions for the resume picker.
launchobjectHow atrium composes the launch command.
launcherOptionsarrayPer-launch UI inputs shown in the launcher.
hooksobjectEvent-name → atrium://hooks/<adapter>/<event> URI map.

minAppVersion

Adapters that depend on manifest fields newer than the oldest atrium version they target should set minAppVersion (semver) to gate themselves. atrium refuses to load an adapter whose minAppVersion exceeds the running app version, surfacing a clear error instead of failing schema validation cryptically. The icon field requires minAppVersion: 0.152.0.

binaryDiscovery

Atrium resolves the binary at launch by trying, in order:

  1. Each entry in commands as a command on the user's $PATH.
  2. Each entry in wellKnownPaths (tilde-expanded, checked for existence and executability).
The first hit wins. If nothing is found, launching fails with a clear error pointing to where the binary is expected.

Include well-known paths for common Node version managers (nvm, fnm, volta) if your tool is installed via npm, and the Homebrew ARM path (/opt/homebrew/bin) for Apple Silicon.

sessions

Describes where the tool stores its session history so atrium can populate the resume picker.

  • pattern — glob relative to the user's home directory.
  • titleField — JSON field inside each session file to use as the human label.
  • idField — JSON field to use as the session ID (passed to resumeFlag).
If your tool doesn't persist sessions or the history is somewhere unusual, omit sessions. The launcher will simply not show a recent-sessions picker.

launch

Composes the command atrium runs in the pane.

  • base — argv prefix, for example ["my-tool"]. atrium substitutes the resolved binary path as argv[0].
  • resumeFlag — template applied when resuming. {session_id} is substituted with the stored session ID.
  • flagMap — maps launcher option IDs to argv fragments. {value} substitutes a select/text option's value.

launcherOptions

Inline UI controls shown in the launcher bar when this adapter is selected. Supported kinds:

  • toggle — boolean switch. Present in argv only when true (via flagMap).
  • select — single-select dropdown with options[] of {label, value}.
  • text — free-form text input.
Each option has an id (referenced by flagMap), label, and optional default. A select option's choices can be bare strings or { value, label } objects — the adapter owns the display label, so the launch-profile editor shows "Claude Fable 5" instead of the raw fable value, and an effort select renders friendly reasoning-effort levels.

suggestedFlags

Alongside options, an adapter can declare a suggestedFlags list that populates the launch-profile editor's "+ Add flag" picker — so a user editing a profile gets that tool's real CLI flags with descriptions rather than typing blind. Each entry is { flag, description?, values? }:

"suggestedFlags": [
  { "flag": "--add-dir", "description": "Additional directories to allow tool access" },
  { "flag": "--fallback-model", "description": "Fallback model on overload" },
  { "flag": "--output-format", "description": "Output format", "values": ["text", "json", "stream-json"] }
]

values advertises a closed set when the CLI has one; omit it for free-form flags.

Prior to SDK v2, launcher options were a separate launcher_options.json file. That form is still supported (reference it via methods.launcher_options = { "static": "launcher_options.json" }) and is where suggestedFlags and labeled choices live for the shipped adapters, but inline is preferred for options themselves.

icon

Adapters can ship a canonical brand SVG via a sibling file in the adapter directory; the manifest's icon field names that file ("icon.svg" is conventional). atrium loads the contents at adapter init and surfaces them through AdapterInfo.iconSvg, so the launcher tiles, Settings → Tools rows, the autocomplete facepile, and the activity sidebar all render the same brand mark.

Sanitizer rules (enforced at load time — failing any of these refuses the adapter):

  • Must have an <svg> root element. No HTML, no raw text, no other root nodes.
  • No <script> elements anywhere in the tree.
  • No <foreignObject> elements (they can host arbitrary HTML and CSS, including scripts).
  • No on*= event-handler attributes (onclick, onload, etc.).
  • No javascript: URIs in any attribute.
  • Hard size cap: 64 KB per file.
The root <svg> also has its width / height stripped and replaced with 100% / 100% so the host can size the icon via CSS. Shapes that use fill="currentColor" recolor against the surrounding text color (typically the adapter's accent); ship hard-coded brand colors only for surfaces that should stay branded regardless of theme.

Uninstalled adapters in the registry render the same SVG — the remote registry payload now embeds each adapter's icon body verbatim — so the icon stays consistent across the install transition. See Adapters → Overview for the installation flow.

Adding icon to an existing adapter is a manifest schema change: bump the adapter's version (minor) and set minAppVersion: 0.152.0. atrium's adapter schema has additionalProperties: false, so older clients that don't know about icon will refuse to load the adapter — the minAppVersion gate gives them a clean error instead.

hooks and hooks.sh

The hooks object lists which events your adapter emits. For each event, atrium registers an atrium://hooks/<adapter>/<event> endpoint.

hooks.sh is invoked once by atrium at install and uninstall time to let the adapter wire its hooks into the tool's own hook system. Contract:

#!/usr/bin/env bash
set -e

case "$1" in
  install)
    # Register this adapter's hooks with the tool.
    # Output JSON to stdout: { "subcommand": "install", "installed": true }
    ;;
  uninstall)
    # Remove the registrations.
    ;;
  status)
    # Report current state.
    ;;
esac

Timeout: 5 seconds for install/uninstall; hooks that exceed the timeout fail the operation but do not crash atrium.

Testing locally

  1. Clone atrium-adapters next to your atrium source tree.
  2. Drop your adapter directory into atrium-adapters/adapters/.
  3. Launch atrium's dev build (pnpm tauri dev). atrium checks the sibling path first in debug builds and will pick up your adapter immediately.
  4. Install it from Settings → Tools.
For a remote-installable adapter, open a PR to the atrium-adapters repository adding your adapter directory and an entry in registry.json.

Migration from SDK v1

If you have an SDK v1 adapter (four shell scripts + a minimal adapter.json), move each script's logic into the equivalent declarative block:

SDK v1 scriptSDK v2 equivalent
detect_running.shHandled automatically via process-tree introspection.
extract_session_id.shsessions.idField + adapter emits the session ID as part of session-start.
list_recent_sessions.shsessions.pattern + titleField + idField.
build_resume_command.shlaunch.resumeFlag.
hooks.sh is still needed for install/uninstall wiring. Everything else becomes JSON.