Skip to content

Segments and layout

Settings live in ~/.config/opencode-cockpit/config.json for every project, <project>/.cockpit.json for one, and on the plugin entry itself, which beats both.

{
"statusline": {
"surface": "bottom",
"separator": "",
"segments": [
"git.diff",
{ "type": "context", "style": "gradient", "width": 16 },
{ "type": "cost", "color": "#e8b923" },
"diagnostics"
]
}
}

A segment is a built-in’s name, or that name with settings. An unknown name is skipped rather than fatal: a config written against a newer version costs you a segment, not the line.

NameShowsSettings
cwdfolder, relative to the worktreemaxWidth
git.branchcurrent branch, dimmed on the default branch
session.diff+150 / -30 — what this session changed, not the working tree
modelclaude-opus-5full
contexthow full the window isstyle, width, warnAt, dangerAt
tokens78.5k tok
costsession spendcurrency, showZero
todo3/7 todoshowComplete
session.statusworking, or a retry and its countdown
session.timeelapsedcoarse
diagnosticsunhealthy LSP and MCP servers
versionthe bay’s version
textliteral textvalue
commanda shell command’s outputname, row

Every one also takes prefix, suffix, priority, color and icon.

What this session changed — the same files OpenCode lists in its own sidebar, tracked through the session’s snapshots. Not git status: a file you edited by hand was never part of the session and will not appear, however dirty the tree is.

Both questions are worth asking, and they are different questions — “what have I changed here” is not “what has the agent changed this turn”. The working tree needs a command, because a built-in that shelled out would stop being a pure function of the snapshot, which is what makes every one of them testable without a filesystem:

{
"statusline": {
"modules": ["<examples/bottom.ts>"],
"commands": { "tree": { "run": "git diff --shortstat", "intervalMs": 5000 } },
"segments": [
{ "type": "session.diff", "prefix": "session " },
{ "type": "worktree", "prefix": "tree " }
]
}
}

git diff --shortstat prints 3 files changed, 12 insertions(+), 4 deletions(-), far too long for a line — the worktree segment in examples/bottom.ts reads that and draws 3f +12 -4.

session.diff also answers to git.diff, its old and more misleading name.

The sidebar you see is not one panel — each block is an internal plugin, and tui.json can switch any of them off:

~/.config/opencode/tui.json
{
"plugin": ["opencode-cockpit"],
"plugin_enabled": { "internal:sidebar-context": false }
}

That removes OpenCode’s own Context / tokens / % used / spent block, leaving the space to a sidebar line of your own. It is the honest way to avoid the same figure twice: rather than this bay staying quiet about what the host says, you turn off the half you would rather not read.

PluginWhat it draws
internal:sidebar-contexttokens, context percentage, spend
internal:sidebar-filesfiles this session changed
internal:sidebar-todothe todo list
internal:sidebar-lsplanguage-server status
internal:sidebar-mcpMCP server status
internal:sidebar-footerthe path and version at the bottom
internal:home-footer, internal:home-tipsthe home screen’s furniture
internal:notificationstoasts

api.plugins.list() prints the current set, so the list above can be checked rather than trusted.

Four styles, because a context meter is the segment people care most about.

styleDraws
percent39% ctx
bara plain bar with end caps
gradienta bar whose every cell is coloured by the level it stands for, green through amber to red
splitone bar coloured by what fills it — cache, fresh input, output

split is the one worth knowing about: a session that is mostly re-reading its own cache looks different from one that is mostly new input, and that difference is invisible in a percentage.

{ "type": "context", "style": "split", "width": 12, "warnAt": 0.7, "dangerAt": 0.9 }

Both hide themselves where no context window was declared. A percentage needs a denominator.

{
"statusline": {
"lines": [
{ "surface": "bottom", "segments": ["git.diff", "todo", "session.time"] },
{ "surface": "sidebar", "segments": ["context", "cost"] }
]
}
}

Two lines on the same surface stack, which is how a two-row statusline is written.

Each line takes its own settings:

SettingDefault
separator" · " across, nothing down
stackvertical in the sidebar, horizontal elsewhere
maxRows8, vertical only
iconson
paddingLeft / Right / Top / Bottomper surface, to line up with OpenCode’s own content

color takes a tone name or a literal. A tone follows whatever theme you run; a literal does not.

Tones: text, muted, accent, success, warning, error, info, and background, panel, border for drawing against the window’s own surfaces.

Prefer a tone. A statusline in someone else’s palette is the first thing that makes a plugin look bolted on.

On by default, in single-width glyphs — an emoji is two cells wide in most terminals and one in a few, which is exactly what shears a fixed-width line. Turn them off with "icons": false, globally or per line, or set your own per segment with "icon": "»".