Create PluginSAFE
Use sidecar next to CLI agents for diffs, file trees, conversation history, and task management with td
Overview
Use sidecar next to CLI agents for diffs, file trees, conversation history, and task management with td
a69114b893d1OBSERVED · 2026-10-10Host compatibility
What the documentation claims. We have not run a compatibility test.
| Host | Status | Notes |
|---|---|---|
| cursor | mentioned |
What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
---
name: create-plugin
description: >
Create new sidecar plugins implementing the plugin.Plugin interface, rendering
views with Bubble Tea, handling keyboard input via keymap contexts, and
integrating with the app shell (footer hints, event bus, adapters). Use when
creating a new plugin, modifying plugin architecture, or debugging plugin
rendering/lifecycle issues. See references/ for sidebar list and fixed footer
layout details.
---
# Create Plugin
**This skill is the embedded class: a Go package compiled into Sidecar with its own Bubble Tea UI.** If what you want is a tool's data in Sidecar — a searchable tab, rows that open into documents, panes beside a terminal — you almost certainly want a *protocol* plugin instead: an executable in any language answering five JSON methods, which Sidecar renders itself and which ships as a config entry rather than a Sidecar release. Read [docs/guides/active/creating-plugins.md](../../../docs/guides/active/creating-plugins.md) and stop here. Come back only when the screen itself is the point — a board, a queue, a layout the collection-and-document vocabulary cannot express — because everything below costs a release, a keymap pass, and a permanent place in the repository.
## Architecture Overview
- **Bubble Tea model**: `internal/app/model.go` owns the active plugin index, dispatches key events, renders plugin views.
- **Registry**: `internal/plugin/registry.go` stores plugins, handles lifecycle with panic protection, keeps an `unavailable` map when `Init` fails (silent degradation).
- **Plugin contract**: `internal/plugin/plugin.go` defines the interface every plugin must satisfy.
- **Context**: `internal/plugin/context.go` provides `WorkDir`, `ConfigDir`, `Adapters`, `EventBus`, `Logger`, `Epoch`, and `Keymap`.
- **Keymap**: `internal/keymap` maps keys to command IDs. Footer/help reads bindings by context using `Plugin.Commands()` + `Plugin.FocusContext()`.
## Plugin Interface
Every plugin must implement all of these methods:
```go
ID() string // Stable kebab-case identifier
Name() string // Short human label for headers/help
Icon() string // Single-character glyph for tab strip
Init(ctx *Context) error // Lightweight setup; return error to degrade gracefully
Start() tea.Cmd // Kick off async work (non-blocking)
Update(msg tea.Msg) (Plugin, tea.Cmd) // Pure state transition
View(width, height int) string // Render within provided dimensions
IsFocused() bool // Check focus state
SetFocused(bool) // App calls this on tab switch
Commands() []plugin.Command // Footer hints per context
FocusContext() string // Current context name for keymap
Stop() // Idempotent cleanup
```
Optional: implement `Diagnostics() []plugin.Diagnostic` for the diagnostics overlay.
## Lifecycle Order
1. **Registration** (`cmd/sidecar/main.go`): `registry.Register(myplugin.New())`. No work here.
2. **Init**: Detect prerequisites (repos, adapters, env vars). Use `ctx.Logger` for warnings. Return error to degrade gracefully.
3. **Start**: Batch initial commands with `tea.Batch`. Never block.
4. **Update**: Pattern-match on custom `Msg` types and `tea.KeyMsg`. Keep I/O in commands, not directly in Update.
5. **View**: Render only; no side-effects. Honor `width/height`.
6. **Focus/Blur**: `SetFocused` called on tab switch. Pause expensive work when unfocused.
7. **Stop**: Close watchers, timers, channels. Guard with `sync.Once`/flags.
## Epoch Pattern (Stale Message Detection)
When switching projects/worktrees, async operations may deliver stale data. Use the epoch pattern:
### Step 1: Add Epoch to message type
```go
type MyDataLoadedMsg struct {
Epoch uint64
Data string
Err error
}
func (m MyDataLoadedMsg) GetEpoch() uint64 { return m.Epoch }
```
### Step 2: Capture epoch in command creators
```go
func (p *Plugin) loadData() tea.Cmd {
epoch := p.ctx.Epoch // Capture synchronously before closure
return func() tea.Msg {
data, err := fetchData()
return MyDataLoadedMsg{Epoch: epoch, Data: data, Err: err}
}
}
```
### Step 3: Check staleness in Update
```go
case MyDataLoadedMsg:
if plugin.IsStale(p.ctx, msg) {
return p, nil // Discard stale message
}
p.data = msg.Data
```
Apply this to any async message that fetches data from filesystem/external sources or updates project-specific state.
## Keymap, Contexts, and Commands
- Define **contexts** mirroring your view modes (e.g., `git-status`, `git-diff`). Return the active one from `FocusContext()`.
- Expose **commands** with matching contexts via `Commands()`. These power footer hints and help overlay.
- Add default **bindings** in `internal/keymap/bindings.go`.
- Keep command IDs stable (verbs preferred: `open-file`, `toggle-diff-mode`).
### Command structure
```go
plugin.Command{
ID: "stage-file",
Name: "Stage", // Keep 1-2 words max
Category: plugin.CategoryGit,
Priority: 10, // Lower = higher priority; 0 treated as 99
Context: "git-status",
}
```
Categories: `CategoryNavigation`, `CategoryActions`, `CategoryView`, `CategorySearch`, `CategoryEdit`, `CategoryGit`, `CategorySystem`
### Context naming convention
- `plugin-name` for main view
- `plugin-name-detail` for detail/preview
- `plugin-name-modal` for modals
- `plugin-name-search` for search modes
### Dynamic binding registration
```go
func (p *Plugin) Init(ctx *plugin.Context) error {
if ctx.Keymap != nil {
ctx.Keymap.RegisterPluginBinding("g g", "go-to-top", "my-context")
}
return nil
}
```
## Event Bus (Cross-Plugin Communication)
- Subscribe: `ch := ctx.EventBus.Subscribe("topic")` in `Start()`, forward messages into `Update`.
- Publish: `ctx.EventBus.Publish("topic", event.NewEvent(event.TypeRefreshNeeded, "topic", payload))`.
- Best-effort, buffered (size 16), drops when full. Design listeners to be resilientTrust audit
SAFEgrade B · trust 89/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | WARN |
| L1 | Static analysis of the code | NA |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- none-observed
- Network
- none-observed
- Shell
- none-observed
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (2)
.codex/skills
GEMINI.md
Gates applied: no_behavioural_pass.
a69114b893d1full audit observations/trust-audit/skill/marcus__create-plugin.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-10 | a69114b893d1 | SAFE | B | 89 | first audit |
Questions
What does the Create Plugin skill do?
Use sidecar next to CLI agents for diffs, file trees, conversation history, and task management with td
Is Create Plugin safe to install?
The audit found nothing in the source that contradicts what it says it does, and graded it B (89/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean skill reads B.
What can Create Plugin access on my machine?
The audit observed no filesystem, network or shell use at all in its source.
Which assistants does Create Plugin work with?
Its documentation mentions cursor. That is what the text claims, not a compatibility test we ran.
How current is this page?
The grade is for one exact copy of the source (a69114b893d1), read on 2026-10-10. The repository is watched, and a new audit runs when it changes — this is the first audit.