npx skills add ...
npx skills add manaflow-ai/cmux --skill cmux-custom-sidebar
Build a custom cmux sidebar from a plain-language request. Use when the user asks for a custom sidebar, a sidebar that shows their workspaces/tabs/PRs/clock, a vibe-coded sidebar, or anything involving files in ~/.config/cmux/sidebars/. Covers authoring the interpreted SwiftUI-style file, enabling the beta flag, selecting it, and iterating with hot reload.
npx skills add manaflow-ai/cmux --skill cmux-custom-sidebar
cmux renders custom sidebars from a small SwiftUI-style file at runtime: no Xcode, no build step, no signing. The file hot-reloads on save, binds to live cmux state (workspaces, tabs, git, PRs, clock), and runs real cmux commands on tap.
The person asking is describing a result ("a sidebar that shows my workspaces and lets me jump between them"), not an implementation. Make the engineering decisions for them; do not ask them about SwiftUI, files, or syntax.
This skill is the workflow summary. Read the complete authoring contract (every supported view, modifier, language feature, and data field) before writing a non-trivial sidebar:
customSidebars.beta.enabled). If a written sidebar does not appear in the picker, check this first.~/.config/cmux/sidebars/<name>.swift. The name becomes the menu label; use short kebab-case. The file is a single SwiftUI-style view expression (no struct, no var body, no imports). A .json variant exists for static layouts; prefer .swift for anything dynamic.cmux sidebar reload forces it). Verify rows show real data and taps do the right thing before declaring it done.workspaces context instead of hard-coding text, so the sidebar stays correct on its own.cmux(...) action on tap. A list that only displays text is rarely what they wanted.Reorderable for workspace-like lists; it gives persisted drag-and-drop reordering for free..prefix(20), filter/sort before rendering). The sidebar re-evaluates about once a second.workspaces: id, title, selected, pinned, index, directory, ports + portCount, unread, tabs + tabCount; when present also description, color, branch + dirty, pr / prs ({number, label, url, status, stale, branch}), progress ({value, label}), latestMessage, latestPrompt, latestAt, remote ({target, state, connected}).workspaces[i].tabs: id, title, focused, pinned; plus directory, branch + dirty, ports when available.clock: {time, hour, minute, second, weekday, epoch}.workspaceCount, selectedTitle, selectedId, unreadTotal.Optional fields are omitted when absent; guard with if let b = w.branch { ... } or w.pr != nil ? ... : ....
A button or .onTapGesture body calls cmux("<method>", param: value), dispatched through the same surface as the CLI. Common methods: workspace.select (workspace_id), surface.focus (surface_id), workspace.reorder (workspace_id + index). openURL("https://...") opens links. Full command surface: cmux docs api.
Containers: stacks (including lazy), Group, List, Section, grids, ViewThatFits, ScrollView, HSplitView (two resizable columns). Content: Text, Label, Image(systemName:), Button (title and label form), Menu, ProgressView, Gauge, Spacer, Divider, shapes, gradients via .background. Modifiers: full typography set, colors as hex strings or tokens, .padding/.frame/layout, .background/.overlay/.mask/.contextMenu with arbitrary nested views, shadows/borders/opacity/effects, .onTapGesture, .help, .disabled. Language: let, user func helpers, for/ForEach, if/else, ternary, string interpolation, arithmetic, array methods (filter/map/sorted/prefix), string and number formatting.
Not yet supported (write the natural Swift anyway; it degrades gracefully): @State and input controls (TextField, Toggle, Slider, Picker), custom struct/View definitions, navigation (sheet/popover), AsyncImage. Two-way editing does not work yet; taps that run cmux(...) do.
~/.config/cmux/sidebars/.cmux sidebar validate <name>. Errors show inline in the sidebar with the failing location; a broken save keeps the last working render on screen, so re-save after fixing.Button(action: { cmux(...) }) { ... } or add .onTapGesture { cmux(...) }.Reorderable(data, move: "workspace.reorder"), not List/.onMove/.draggable.