Skip to content

atyrode/cli-kit

Repository files navigation

cli-kit

A shared visual layer for Bubble Tea CLIs — one palette, layout primitives, and an opt-in agent PromptBox behind a small capability contract, so every tool built on it looks and feels like it came from the same dev.

Built for and extracted from code (a launcher for oh-my-pi) and the atyrode cockpit in atyrode/dotfiles, which remain its reference consumers.

go get github.com/atyrode/cli-kit

Packages

Package What it gives you Pulls in
clikit (root) palette & styles, layout/panel/meter/workspace primitives, the PromptBox, Run, the capability contract lipgloss, Bubble Tea, bubbles
clikit/omp Ask/Act backends that shell out to a headless oh-my-pi run stdlib only (omp binary needed at runtime)
clikit/ollama an Act backend speaking to a local ollama daemon over loopback HTTP stdlib only (daemon needed at runtime)

The backends are deliberately separate packages: importing the core never buys you a model runner. Nothing in this library installs, launches, or assumes an LLM — a tool that wants the PromptBox opts in by implementing an interface, and a tool that wants a local model opts in by importing clikit/ollama and bringing its own daemon.

The core

  • Palette & styles (palette.go) — colour tokens (CAcc, CBord, …), the MeterRamp, text-presentation glyphs (GWarn/GBroken/GReset), and shared lipgloss styles (StDim, StHead, …).
  • Layout and section helpers (layout.go, meter.go) — PadLeft/Pad, WindowList (a clipped, scrollbar'd column), Scrollbar, meters, and full-width Rule/SeparatedSections boundaries. Widths are terminal cells; empty sections produce no orphan rule.
  • Workspace and panel primitives (workspace.go, panel.go) — WorkspaceNav owns ordered selection, wrapping next/previous navigation, and stable application-defined IDs; Panel, PanelContentWidth, and ClipLines provide shared rounded chrome with terminal-cell clipping and no wrap-driven footer displacement.
  • Footer conventions (palette.go, layout.go) — NewHelp applies the shared key/description/separator palette to Bubble Help, while WrapHelp wraps complete required cues without dropping them. Both remain ANSI-aware through lipgloss width measurement.
  • The PromptBox (promptbox.go) + the consumer contract (contract.go, run.go) + ParseActions (actions.go), the tolerant model-output → action-set parser every Act backend shares.

The consumer contract

A CLI built on cli-kit is a plain Bubble Tea model (tea.Model). It launches through clikit.Run, which mounts capabilities the model opts into by implementing small interfaces — structural, compile-time, à la carte:

Implement Get
(nothing extra) palette + layout + the footer/help conventions
AskableAsker() Asker the PromptBox in Ask mode (read-only Q&A)
CommandableCommander() Commander the PromptBox in Act mode (propose → live-preview → keep/revert)
DocumentedDocs() DocCorpus grounding injected into the backend's system prompt

clikit.Run(app) detects what's implemented and mounts the box behind a toggle key (default ctrl+o). The same key or esc closes it and cancels any in-flight request. Act mode takes precedence over Ask when both are present. Options: WithToggleKey, WithAltScreen, WithMouseCellMotion, WithMessageFilter.

Ask mode

type helpApp struct{ /* your tea.Model */ }

func (helpApp) Asker() clikit.Asker     { return omp.NewAsker(myDocs) }
func (helpApp) Docs() clikit.DocCorpus  { return myDocs }

func main() { _, _ = clikit.Run(helpApp{}, clikit.WithAltScreen()) }

omp.NewAsker shells out to a headless oh-my-pi run (omp -p --mode text --no-session --no-tools --model claude-haiku-4-5 --append-system-prompt <docs> <prompt>) and streams the answer; dismissing the box kills the subprocess. Set Asker.ReplaceSystem for a narrow grounded assistant that must replace the coding-agent prompt and its managed scaffolding. Override the evaluator via Asker.Model.

Act mode

Implement clikit.Commander — two-phase so the box can show the model working:

type Commander interface {
    Propose(ctx context.Context, prompt string) (<-chan string, error) // stream raw output
    Parse(output string) ([]Action, error)                             // completed output → typed actions
}

The box streams Propose, runs Parse, and emits ActionsProposedMsg — the host applies the actions immediately (live preview) and snapshots its prior state. enter emits ActionsConfirmedMsg (keep; it carries the original prompt, so a launcher can forward it), esc emits ActionsRevertedMsg (restore the snapshot). A host whose applied set differs from the parsed proposal (derived or repaired changes) sends AppliedActionsMsg back so the box's "applied" list stays truthful. Apply actions through the same code path as a manual change — the closed Action{Key, Value} set is your tool's agent-facing API surface.

Backends: omp.Commander (headless oh-my-pi turn, scaffolding stripped) or ollama.Commander (local daemon, temperature 0, JSON-constrained output). Both parse with clikit.ParseActions, which tolerates prose around the JSON object.

Local-model residency (Loadable)

A backend that also implements clikit.Loadable (as ollama.Commander does) lets the box offer a load/unload toggle (ctrl+l) and show residency state — the model occupies RAM only when the user chooses, and a one-off suggestion never silently leaves weights resident.

Design notes

The original design record (rationale, the runtime-mutation wall, phasing) is kept as DESIGN-promptbox.md — a historical document; where it disagrees with the code, the code wins.

Building / testing

go build ./... && go test ./...

Plain Go module, no code generation, no CGO. CI runs gofmt, go vet, and the test suite.

License

MIT

About

Shared Bubble Tea + lipgloss toolkit behind atyrode's CLIs — one palette, layout primitives, and an opt-in agent PromptBox

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages