Plugin System
Boo has an extensible plugin architecture for sidebar panels, status bar segments, and terminal lifecycle hooks. A plugin can contribute a sidebar tab, a status bar segment, or both — a sidebar tab is not required.
Plugin Types
- Built-in — Swift classes in
Boo/Plugins/conforming toBooPluginProtocol - External — JavaScript in
~/.boo/plugins/with aplugin.jsonmanifest, hot-loaded at runtime
Built-in Plugins
| Plugin | ID | Description |
|---|---|---|
| Files | file-tree-local, file-tree-remote | File explorer with local FSEvents and remote browsing |
| Git | git-panel | Branch, staged/unstaged/untracked files, ahead/behind counts |
| Agents | agents | Agent Center for Claude Code, Codex, OpenCode, and AI CLI sessions |
| Snippets | snippets | Saved code snippets with syntax highlighting and quick insertion |
| Docker | docker | Container list with exec, logs, start/stop/restart |
| Bookmarks | bookmarks | Saved directory shortcuts with per-host namespacing |
| System | system-info | CPU, memory, disk — reference example for plugin patterns |
| Debug | debug | Live lifecycle event log and terminal state inspector |
Plugin Lifecycle
Every terminal state change triggers a plugin cycle with a PluginCycleReason:
| Reason | Triggered by |
|---|---|
.focusChanged | Pane focus moved |
.cwdChanged | Working directory changed |
.titleChanged | Terminal title changed |
.processChanged | Foreground process changed |
.remoteSessionChanged | Remote session started or ended |
.workspaceSwitched | Active workspace switched |
The cycle phases (total budget: <16ms):
- Enrich — Plugins write shared data to a mutable
EnrichmentContext - Freeze — Produces an immutable
TerminalContextsnapshot - React — Plugins receive the frozen context
- Evaluate — When-clauses determine visibility; status bar content is collected
- Rebuild — Sidebar and status bar are updated
Call onRequestCycleRerun?() when plugin data changes externally (e.g., Docker events, git commit) to trigger a refresh.
TerminalContext
Immutable snapshot passed to all plugin methods:
| Field | Type | Description |
|---|---|---|
terminalID | UUID | Active terminal identifier |
cwd | String | Current working directory |
remoteSession | RemoteSessionType? | Remote session info, if active |
remoteCwd | String? | Remote working directory |
environmentLabel | String | Display label for the terminal session |
gitContext | GitContext? | Branch, dirty state, file counts |
processName | String | Foreground process name |
paneCount | Int | Panes in workspace |
tabCount | Int | Tabs in active pane |
isRemote | Bool | Computed: remoteSession != nil |
GitContext fields: branch, repoRoot, isDirty, changedFileCount, stagedCount, aheadCount, behindCount, lastCommitShort.
PluginActions
Unified action dispatch for plugins:
pluginActions.exec("make build") // Send command to terminal
pluginActions.cd(to: "/some/path") // cd + Enter
pluginActions.pastePath("/foo/bar") // Insert path without executing
pluginActions.copy("text") // Copy to clipboard
pluginActions.reveal("/some/file") // Show in Finder
pluginActions.openInEditor("/file") // Open with default app
pluginActions.openDirectoryInNewTab?("/some/dir") // Open dir in new tab
pluginActions.openDirectoryInNewPane?("/some/dir") // Open dir in new paneAlso exposes closures: sendToTerminal, openDirectoryInNewTab, openDirectoryInNewPane, displayImageInTerminal, isTerminalBusy, registerMultiContentTab, openMultiContentTab, setAgentSessionID, getAgentSessionID.
Capabilities
Manifest capability flags that control where a plugin appears:
| Flag | Description |
|---|---|
capabilities.sidebarTab | Contribute a dedicated full-height sidebar tab (Swift plugins) |
capabilities.statusBarSegment | Contribute a segment to the status bar (can be the sole capability) |
External JS plugins automatically get a sidebar tab — no capabilities field needed in plugin.json. Use statusBarSegment: true only when adding a status bar segment.
See Plugin Development for makeSidebarTab details.
When-Clauses
Control plugin visibility via the when manifest field:
| Expression | Meaning |
|---|---|
null | Always visible |
"git.active" | Only in git repos |
"!remote" | Only local sessions |
"remote" | Only remote sessions |
"env.ssh" | SSH or Mosh |
"env.docker" | Docker sessions |
"env.container" | Any container/VM/device shell |
"env.type == 'kubectl'" | Specific tool match |
"git.active && !remote" | Combined conditions |
Operators: &&, ||, !, ==, !=, ().
DSL Actions
Interactive elements dispatch these actions:
| Action | Parameters | Effect |
|---|---|---|
"cd" | path | Change terminal dir |
"open" | path | Open in $EDITOR |
"exec" | command | Send command to terminal |
"copy" | text or path | Copy to clipboard |
"reveal" | path | Open in Finder |
"url" | url or text | Opens URL in browser |
"newTab" | path (optional) | Opens new terminal tab |
"newPane" | path (optional) | Opens new split pane |
"paste" | text | Inserts text at cursor |
"notification" | text, title (optional) | Shows notification |
Plugin Settings
Plugins declare settings in their manifest. The Settings window renders controls automatically.
AppSettings.shared.pluginBool("my-plugin", "myKey", default: true)
AppSettings.shared.pluginString("my-plugin", "myKey", default: "")
AppSettings.shared.pluginDouble("my-plugin", "myKey", default: 12.0)| Plugin | Key | Type | Default | Description |
|---|---|---|---|---|
| Files | showHiddenFiles | Bool | false | Show hidden files |
| Files | showIcons | Bool | true | Show file icons |
| Files | showPath | Bool | true | Show path above tree |
| Files | showProcess | Bool | true | Show running process |
| Files | showFilterBar | Bool | true | Show filter bar (/ focuses it) |
| Files | gitDecorations | Bool | true | Show git status badges |
| Files | dimIgnored | Bool | true | Dim git-ignored files |
| Files | editorFilePatterns | String | built-in list | Comma-separated patterns that open in the editor |
| Files | markdownOpenMode | String | preview | preview, builtInEditor, editor (terminal), external |
| Files | imageOpenMode | String | imageViewer | imageViewer, kitty, external |
| Files | textOpenMode | String | editor | editor, terminalEditor, external |
| Files | htmlOpenInBrowser | Bool | false | Open HTML files in a browser tab |
| Files | pdfOpenInBrowser | Bool | true | Open PDF files in a browser tab |
| Git | showBranch | Bool | true | Show branch in status bar |
| Git | diffTool | String | "" | External diff command, {file} placeholder |
| Docker | socketPath | String | "" | Docker socket path (empty = auto-detect) |
| System | statusBarMemory | Bool | false | Show memory in status bar |
| System | statusBarDisk | Bool | false | Show disk in status bar |
| System | statusBarCPU | Bool | false | Show CPU in status bar |
| System | statusBarBattery | Bool | false | Show battery in status bar |
| Debug | maxEntries | Double | 200 | Max log entries |
The Agents plugin uses its own custom settings page instead of manifest-rendered controls.
Status Bar
Built-in segments: Environment, Git Branch, Path, Process (left); File Tree toggle, Pane Info, Clock (right).
Sidebar plugins automatically get toggle icons in the status bar — no manual registration needed. Toggle segments in Settings → Status Bar.
Menu Contributions
External plugins contribute items to the "Plugins" menu bar via menus in plugin.json. See Plugin Development for details.
Swift plugins implement menuContributions() -> [PluginMenuItem] and handleMenuAction(_ action: String, context: TerminalContext) on BooPluginProtocol.
External Plugins
JavaScript plugins run in-process via JavaScriptCore. See the Plugin Development guide.
