- Rust 78.7%
- HTML 21%
- Handlebars 0.2%
| docs | ||
| examples | ||
| src | ||
| tests | ||
| .envrc | ||
| .gitignore | ||
| about.hbs | ||
| about.toml | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| devenv.lock | ||
| devenv.nix | ||
| devenv.yaml | ||
| LICENSE | ||
| README.md | ||
| THIRD_PARTY_LICENSES.html | ||
Barback
Barback is a source-agnostic terminal workbench for Stations.
A Station is a named work source that Barback can show in the terminal UI. The project is still early: Barback can discover and load Station config at startup, execute configured Station list commands, parse JSON Lines output, map arbitrary JSON object fields into stable display roles, render a header Station selector with browseable Items and Details panes in the TUI, and surface visible warning/error states without compiling in source-specific logic.
Current status
Barback currently provides:
- a terminal UI with a Catppuccin Mocha-styled header Station selector (
< {Station name} >) and circle indicators, two-pane Items/Details browse area, compact bottom status/help bar, diagnostics, selection, and muted/help text through an internal render-only theme seam - startup loading for the initially selected Station before the first terminal draw
- keyboard navigation for header Station selection (
h/lor left/right arrow keys), item selection (j/kor up/down arrow keys), detail scrolling (u/d,PageUp/PageDown,g/G), lowercaserlist refresh, uppercaseRselected-detail refresh, and quit keys - visible empty, no-usable-row, warning, error, and missing-detail states in the TUI
- a public typed config loader for Station TOML
- deterministic global/local Station merge behavior
- strict TOML schema parsing for known config fields
- CLI startup discovery and loading before the terminal UI opens
- a public Station list runtime API that executes configured shell command strings
- JSON Lines parsing for object rows with malformed-row warnings and empty/no-usable-row states
- source-agnostic field mapping from arbitrary JSON object keys into stable
id,title,subtitle, and detail roles - selected-item detail resolution from configured row fields or an optional fixture-testable view command
- bounded Station-scoped diagnostics for successful stderr, malformed rows, detail warnings, and non-zero command exits
- selected-Station list refresh that reruns the configured list command in the background, preserves the last successful rows/selection/detail when refresh fails, surfaces the refresh failure as a visible error banner, shows successful stderr as a warning, and clears prior refresh errors after a successful refresh
- selected-item detail loading, detail-pane scrolling, and uppercase
Rdetail refresh that run configured view commands in the background, hydrate from an in-memory detail cache when possible, keep stale detail text visible while refreshing, and keep bounded detail diagnostics visible without blanking the Details pane - selected-item template/action preview opened with
p, navigated withj/kor arrow keys, confirmed withEnterfor action entries, cancelled withcwhile an action is active, acknowledged withEnter/Escafter results, and closed withEscwhen no result popup is visible - a source-agnostic non-interactive action command contract that renders configured commands from selected-item templates/outputs, supports
stdin = "none" | "template" | "outputs.<key>", executes inside the background Runtime seam with a singleton active-action lock, cancellation, and the default action timeout, and returns typed process status plus bounded stdout/stderr captures
Barback's Catppuccin Mocha styling is currently built in through an internal render seam. M002 does not expose custom or user-configurable themes. The final M003 selected-item workflow is covered by deterministic fixture-backed Product Flow tests, and the Manual UAT runbook provides real-terminal validation without live services.
Barback intentionally still keeps command execution simple: configured list, view, and non-interactive action commands run through the local shell as single command strings, with no retries, pagination controls, or non-shell execution yet. Slow list/detail/action commands no longer block normal terminal drawing while they are running. Selection changes do not cancel in-flight list or detail work; when successful background work completes, Barback stores the result in process-local cache. Returning to a Station or item whose request is still pending reuses that pending request instead of starting duplicate command work. Action entries can be launched from the preview popup, one action may run at a time, active actions can be cancelled, action commands use the runtime default timeout, and completed action outcomes remain visible in a persistent result popup until acknowledged with Enter or Esc.
Product Flow and browser controls
Reader action after this section: create or reuse Station TOML, run cargo run, navigate the Station browser, and understand what the UI is proving when it shows loading, cache, stale, warning, or error states.
The running browser is a Catppuccin Mocha-styled two-pane Station view. The header shows the selected Station as < {Station name} > with adjacent circle indicators. The left Items pane shows JSONL rows mapped through the Station field config. The right Details pane shows the selected item detail, either from mapped row detail fields or from the configured view command. The compact footer carries help, loading, stale cache, warning, and error text so the main panes do not disappear during background work.
Keyboard controls:
| Area | Keys | Effect |
|---|---|---|
| Station selector | h/l, left/right arrows |
Move to the previous or next Station and load its Items. |
| Items | j/k, up/down arrows |
Move the selected item and request its Details. |
| Details scroll | u/d, PageUp/PageDown, g/G |
Scroll the visible Details text by line, page, top, or bottom. |
| Refresh | r reload |
Rerun the selected Station list command in the background. |
| Refresh | R detail |
Rerun the selected item's detail command in the background, even when cached detail text exists. |
| Preview | p |
Open the selected item’s Templates / Actions preview popup. |
| Preview | j/k, up/down arrows |
Move between preview entries while the popup is open without moving the underlying item selection. |
| Preview | Enter |
Run the highlighted action entry when its referenced template renders successfully; template-only entries remain non-runnable. |
| Action | c |
Request cancellation for the active action. |
| Action Result | Enter, Esc |
Acknowledge the persistent action result popup and return to the underlying browser/preview state. |
| Quit | q, Ctrl-C |
Leave the terminal UI. |
The preview popup renders the selected Station item’s configured template body and named outputs using the same selected-item context as detail view-command rendering, and shows bounded render errors in the popup when rendering fails. Template-only entries are non-runnable display entries: pressing Enter on them remains fail-closed and does not execute a command. Action entries render their referenced template in the same popup; pressing Enter on a runnable action starts the action through the Runtime seam, disables duplicate launches while it is active, and leaves normal browser navigation responsive. While an action is active, press c to request cancellation. When the action completes, fails, times out, is cancelled, or is rejected because another action is active, Barback shows a topmost persistent Action Result popup with typed status details and bounded stdout/stderr where available. Acknowledge that result with Enter or Esc to return to the underlying preview/browser state.
Barback caches only in memory inside the current process. A successful list load stores that Station's Items snapshot, even if the user selected another Station before it finished. A successful detail load stores that item's Details snapshot, even if the user selected another item before it finished. These caches are not written to disk, shared across runs, or invalidated by external source changes. Returning to an already completed Station or item hydrates from cache without duplicate command work; returning while the original request is still pending reuses the pending request. During lowercase r list refresh and uppercase R detail refresh, stale cached content remains visible and scrollable while the newer command runs. If refresh fails, Barback keeps the stale Items or Details on screen and surfaces bounded, redacted diagnostics in the footer instead of exposing full shell command strings, oversized output, credentials, or local paths.
Automated product proof is fixture-backed rather than live-service-backed. The focused M003 Product Flow checks build temporary Station config against tracked local fixture scripts, prove JSONL remains the Station item contract, exercise startup loading, Station/item navigation, detail scrolling, stale cache preservation during failed list/detail refreshes, inline and Markdown-backed template previews, template-only no-op confirmation, non-interactive runtime actions, singleton suppression, cancellation, timeout, persistent Action Result acknowledgement, bounded diagnostics, compact footer behavior, and Catppuccin styling. They do not call GitHub, Jira, network services, credentials, or developer-local config.
Configuration format
Barback config is TOML. Define Stations with [[stations]] tables. Each Station needs an id, a display name, and a list command string under [stations.list]. Optional [stations.fields] and [stations.view] sections describe how source-shaped JSON rows become Barback display roles and how selected-item details can be resolved lazily.
[[stations]]
id = "pull-requests"
name = "Pull Requests"
[stations.list]
command = "gh pr list --json number,title,author,body --jq '.[] | @json'"
[stations.fields]
id = "number"
title = "title"
subtitle = "author"
detail = ["body"]
[stations.view]
command = "gh pr view {{ item.number }} --json body --jq .body"
[[stations]]
id = "issues"
name = "Issues"
[stations.list]
command = "gh issue list --json number,title,author --jq '.[] | @json'"
Fields
| Field | Required | Meaning |
|---|---|---|
id |
Yes | Stable Station identifier used for merging global and local config. |
name |
Yes | Human-readable name shown in Barback UI surfaces. |
list.command |
Yes | Shell command string Barback stores and passes to the Station list runtime. The command should emit one JSON object per stdout line. |
fields.id |
No | JSON object key used as the stable item id. Defaults to id when omitted. |
fields.title |
No | JSON object key used as the item title. Defaults to title when omitted. |
fields.subtitle |
No | JSON object key used as optional subtitle text. Omitted unless configured. |
fields.detail |
No | Ordered list of JSON object keys joined with blank lines for row-backed selected-item details. |
view.command |
No | Shell command string used instead of row-backed detail fields when resolving a selected item. |
templates |
No | Strict catalog of prompt/action templates available to callers. Each entry needs id, name, and exactly one of inline body or Markdown file. |
actions |
No | Strict catalog of non-interactive actions tied to a template. Each action needs id, name, template, command, and optional stdin/shell. |
Unknown fields are rejected. This is intentional: a typo in config should fail visibly instead of being ignored.
Station templates
Stations can define a deterministic template catalog for callers that need rendered prompt/action text from the selected Station item and optional detail text. Templates use the same strict MiniJinja selected-item context as view.command: selected row fields are available at the top level and under item, Station metadata is under station, detail text is detail, and shell_quote is available for shell-safe arguments.
Inline templates live directly in TOML and may declare named outputs below [stations.templates.outputs]:
[[stations.templates]]
id = "pr-summary"
name = "PR Summary"
body = "Review PR #{{ item.number }}: {{ item.title }}\n\n{{ detail }}"
[stations.templates.outputs]
title = "PR #{{ item.number }}: {{ item.title }}"
url = "{{ item.url }}"
Markdown-backed templates use file, resolved relative to the config file that declares it unless the path is absolute. File-backed outputs must be declared in TOML frontmatter inside the Markdown file, not in the Station TOML entry:
[[stations.templates]]
id = "pr-review"
name = "PR Review Markdown"
file = "templates/pr-review.md"
---
[outputs]
title = "PR #{{ item.number }}: {{ item.title }}"
url = "{{ item.url }}"
---
# Review PR #{{ item.number }}
{{ detail }}
Named outputs are returned in deterministic key order. Config-load diagnostics include the config path, Station id, template id/path field, and concise reason, but they do not dump full Markdown/frontmatter/body content. Render diagnostics distinguish template body failures from named output failures without exposing the full template source or rendered output.
Station template entries are visible from the selected item’s preview popup. Press p in the TUI to open the popup, use j/k or the up/down arrows to move between preview entries, and use Esc to close it when no result popup is visible. The popup renders template bodies plus named outputs for template-only entries, and it renders the referenced template for action entries before execution. Template-only entries remain non-runnable/fail-closed; Enter on a valid action entry launches that action.
Station actions
Stations can define a strict action catalog for non-interactive local command execution by Barback runtime callers. Actions reference a configured template by id, render a command with the selected-item context plus template and outputs.<key> values, and select stdin from the rendered template body, a rendered named output, or no stdin:
[[stations.actions]]
id = "copy-pr-summary"
name = "Copy PR Summary"
template = "pr-summary"
command = "pbcopy"
stdin = "template"
[[stations.actions]]
id = "create-pr-from-review"
name = "Create PR From Review"
template = "pr-review"
command = "gh pr create --body {{ outputs.pr_body|shell_quote }}"
stdin = "none"
[[stations.actions]]
id = "fish-notify"
name = "Fish Notify"
template = "pr-summary"
command = "set subject {{ outputs.title|shell_quote }}; printf '%s\n' $subject"
stdin = "none"
shell = "fish"
stdin defaults to template when omitted. Valid values are template, none, or outputs.<key>. shell defaults to sh; valid values are sh and fish. Prefer stdin for multiline prompt bodies or rendered outputs; reserve command interpolation for short shell arguments and wrap arbitrary values with shell_quote, for example {{ outputs.url|shell_quote }} or {{ outputs.pr_body|shell_quote }}. Command rendering uses the same strict MiniJinja environment and deterministic POSIX shell_quote filter as Station templates, plus template for the rendered body and outputs for rendered named outputs. Missing fields, null selected-item values, malformed command templates, missing output references, or invalid shell names fail before any process spawn. Subprocess results return typed status (success, non-zero exit, spawn failure, stdin-write failure, timeout, or cancellation) with bounded stdout/stderr captures. Diagnostics include action/template context but do not dump full commands, prompts, rendered bodies, outputs, local fixture paths, credentials, or tokens. In the TUI, action entries launch from the selected-item preview popup, the runtime enforces a single active action at a time, active actions can be cancelled with c, the default action timeout applies, and completed or rejected outcomes display a persistent Action Result popup until acknowledged with Enter or Esc.
Template/action examples in this repository are parse-only/local fixtures. Automated tests verify that tracked examples parse and file paths resolve, but they do not execute gh, call live services, or require credentials.
Example configs
A checked-in GitHub CLI Station example lives at examples/gh-stations.toml. Treat this file as a canonical example of external Station config shape: it demonstrates gh-backed list commands, field mapping, and an optional view command without making GitHub a compiled-in Barback source.
The example file is not the same as a repository-local .barback.toml. Barback discovers .barback.toml from your current working directory at startup for local use, while examples/gh-stations.toml is documentation and parse-only test input. Automated tests verify that the example parses as config, but they do not execute its gh commands, call Jira/GitHub, or require live credentials.
Field mapping and details
Station list commands emit arbitrary JSON objects, not source-specific Rust structs. Barback maps configured keys into stable roles:
- strings render as-is
- numbers and booleans render with their display text
- arrays and objects render as compact JSON
- missing or
nullmapped display fields are absent
Selected-item details resolve in two modes:
- If
[stations.view].commandis configured, Barback renders it as a strict MiniJinja selected-item template and executes the rendered result as one platform shell command string. Selected row fields are available both at the top level ({{number}}) for backward-compatible configs and under theitemnamespace ({{ item.number }}) for collision-safe configs. Station metadata is available as{{ station.id }}/{{ station.name }}, andshell_quoteprovides deterministic POSIX single-quoting for placeholder values that may contain shell metacharacters. Stdout is treated as plain detail text, not JSON. Successful stderr is returned as a bounded warning. Missing ornullselected-item values return a structured Station-scoped detail error and skip shell execution instead of panicking. - If no view command is configured, Barback joins configured
fields.detailrow values in order. Missing ornulldetail fields produce Station-scoped warnings; non-string JSON values render using the same display conversion as mapped fields.
View-command rendering is intentionally limited to the selected-item MiniJinja context and the shell_quote filter. Barback does not add retries, timeouts, cancellation, pagination, or non-shell execution in this slice. Because rendered view commands run through the local shell, Station authors should quote placeholders appropriately for their command and data shape, for example {{ item.key|shell_quote }} when an arbitrary string becomes one shell argument. Public diagnostics include Station id/name, render/command failure summaries, status, and bounded stdout/stderr snippets, but they do not include the full configured shell command string or template body.
Global and local config
Barback's config model supports two sources:
- a global config file for Stations you want available everywhere
- a project-local config file for Stations specific to one repository
At startup, Barback discovers and loads both paths before opening the terminal UI:
- global config: the OS-standard Barback config directory, as resolved by
directories::ProjectDirs, with aconfig.tomlfile - project-local config:
.barback.tomlin the current working directory
Tests and library callers can still pass explicit paths into the config loader so they do not read developer-local config.
When both sources define Stations, Barback merges them by id:
- global Stations are loaded first
- a local Station with the same
idreplaces the global Station at the same position - local Stations with new
ids append after the global list in local-file order
Example:
# Global config
[[stations]]
id = "pull-requests"
name = "Pull Requests"
[stations.list]
command = "gh pr list --json number,title --jq '.[] | @json'"
[[stations]]
id = "inbox"
name = "Inbox"
[stations.list]
command = "tool inbox --jsonl"
# Project-local config
[[stations]]
id = "pull-requests"
name = "Repository PRs"
[stations.list]
command = "gh pr list --search 'repo:owner/project' --json number,title --jq '.[] | @json'"
[[stations]]
id = "release-blockers"
name = "Release Blockers"
[stations.list]
command = "tool blockers --jsonl"
Merged result:
pull-requestsfrom the project-local config, in the original global positioninboxfrom the global configrelease-blockersfrom the project-local config
Diagnostics
Config load errors include the source path that failed. Invalid TOML and unknown fields are returned as errors instead of being silently ignored. When config discovery finds an invalid present config file, Barback returns that error before opening the terminal UI.
Diagnostics are deliberately concise. They should identify the broken source and parser problem without dumping the full config file or exposing secret-bearing command strings.
On lowercase r list refresh, Barback reruns the selected Station's configured list command in the background. A failed list refresh leaves the last successful Items and Details panes visible, keeps the previous selection when possible, and reports the current refresh failure in the compact bottom status/help bar as a bounded error with a stale-cache signal. A successful list refresh replaces stale rows with the latest command output, clears any previous refresh error, and keeps successful stderr visible as a bounded warning in that bar.
On uppercase R selected-detail refresh, Barback reruns the selected item's configured view command in the background even when cached detail content exists. While that refresh is in flight, the Details pane keeps the stale detail text visible with a refresh signal. The same Details pane scroll controls (u/d, PageUp/PageDown, g/G) continue to operate on the visible stale text and diagnostics. A successful detail refresh replaces the stale text and updates the in-memory detail cache; a failed detail refresh keeps the stale text visible and surfaces bounded detail diagnostics instead of blanking the pane. These diagnostics remain source-agnostic: they describe Station context and bounded stdout/stderr snippets without exposing the full configured shell command string.
For selected-item actions, Barback surfaces completion through the persistent Action Result popup rather than a transient footer line. The popup distinguishes success, non-zero exit, spawn failure, stdin-write failure, timeout, cancellation, and singleton rejection; includes Station/item/action/template metadata; and shows bounded stdout/stderr captures for completed process outcomes. It intentionally omits full command strings, rendered prompt/template bodies, rendered outputs, fixture paths, credentials, and tokens. The popup stays topmost until acknowledged with Enter or Esc.
Development
Run the test suite with:
cargo nextest run
Run the config-focused tests with:
cargo nextest run config
Run the Station list runtime tests with:
cargo nextest run station_runtime
Run the Station field/detail tests with:
cargo nextest run --test station_details
Run the checked-in example-config parse proof with:
cargo nextest run --test example_config
Run the fixture-backed App/TUI browse proof with:
cargo nextest run --test e2e_fixture_browse
Run the focused M003 selected-item product-flow proof with:
cargo test --test e2e_fixture_browse m003 -- --nocapture
The config tests use temporary files and public APIs. Runtime and detail tests use tracked fixture scripts under tests/fixtures/bin/ instead of live tools. The fixture-backed E2E tests build temporary global/local TOML files that point at those tracked scripts, proving merge order, command execution, JSONL parsing, field mapping, details, warnings/errors, Station switching, refresh behavior, stale-cache preservation, item/detail scrolling, inline and Markdown-backed template rendering, named outputs, runtime action success and failure statuses, singleton suppression, cancellation, timeout, Action Result acknowledgement, compact footer rendering, and Catppuccin style presence without reading developer-local config. They do not call live GitHub/Jira services or require credentials. The focused M003 product-flow tests are the automated no-live-service regression gate for the current selected-item workflow; the Manual UAT runbook is the human real-terminal validation pass.