1
0
Fork
You've already forked SparkShell
0
SparkShell with Bubbles
  • Go 98.2%
  • Go Template 1.2%
  • Makefile 0.6%
2026年07月07日 16:31:02 -07:00
internal Add quick chips 2026年07月07日 16:31:02 -07:00
.gitignore polishing two pane view 2026年07月01日 22:20:50 -07:00
DESIGN.md sprksh: docs + Phase 0/1/2 (instrumented PTY shell, bubbles, UI) 2026年07月01日 14:30:31 -07:00
go.mod coloring, bat 2026年07月01日 22:55:56 -07:00
go.sum polishing two pane view 2026年07月01日 22:20:50 -07:00
LICENSE.md BSD 2 2026年07月01日 23:38:56 -07:00
main.go polishing two pane view 2026年07月01日 22:20:50 -07:00
Makefile feat(ask): [?] ask an LLM about a bubble 2026年07月02日 23:17:24 -07:00
PLAN.md feat(bubbles): sticky [e] edits, user notes, and [p] pasteboard path 2026年07月02日 14:25:26 -07:00
preliminary-plan.md sprksh: docs + Phase 0/1/2 (instrumented PTY shell, bubbles, UI) 2026年07月01日 14:30:31 -07:00
README.md feat(ui): drag-to-select and copy on-screen text in the shell pane 2026年07月02日 17:20:06 -07:00
STATUS.md feat(ui): drag-to-select and copy on-screen text in the shell pane 2026年07月02日 17:20:06 -07:00

sprksh — Sparkle Shell

A mouse- and keyboard-driven terminal wrapper that slices your shell session into structured, revisitable bubbles — one card per command — while still letting full-screen apps take over the terminal when they need to.

sprksh wraps a normal, persistent zsh session and makes it more legible. Every batch command (ls, git status, make, ...) becomes a bubble: a card showing the directory, the command, its output, and its exit code — that you can scroll back to, re-run, cd into, or open in an editor/pager long after it scrolled off. Full-screen apps (nvim, htop, less) are not emulated; they transparently own the real terminal until they exit.

It's built in Go on Bubble Tea, and the author runs it as a daily driver.

Status: feature-complete for single-session daily use and currently in a QC interval. See STATUS.md for the detailed checklist and PLAN.md for the roadmap; DESIGN.md records the original design rationale.


What it looks like

A side-by-side split: a live shell pane on the left, the bubbles pane on the right.

┌ shell ─────────────────────────┐┌ Bubbles [3-4 of 4] ───────────────┐
│ ~/dev/sprksh $ go build ./... ││┌ dir: ~/dev/sprksh ───────── ✓ 0 ┐ │
│ ~/dev/sprksh $ ▮ │││ $ go build ./... │ │
│ │││ (no output) │ │
│ ││└─────────────────────────────────┘ │
│ ││┌ dir: ~/dev/sprksh ───────── ✗ 1 ┐ │
│ │││ $ go test ./... │ │
│ │││ --- FAIL: TestFoo ... │ │
│ │││ [go] cd [R] rerun [x] pop ... │ │
│ ││└─────────────────────────────────┘ │
└─────────────────────────────────┘└─────────────────────────────────────┘
 sprksh · focus:shell · ~/dev/sprksh · ^o switch · ^l resize · ^q quit · ...

The focused card shows its action row: [go] cd [R] rerun [x] pop [e] edit [b] bat [c] copy [p] path.

Quick start

Requirements:

  • Go 1.26+ and zsh (the instrumented shell; bash is a later add).
  • Optional per-action tools: bat for [b] (quick syntax-highlighted peek), $EDITOR (defaults to nvim) for [e], and a clipboard tool for [p] (pbcopy on macOS; wl-copy / xclip / xsel on Linux).

Build and run:

# GOPATH must point somewhere writable; this repo uses a git-ignored .gopath/.
export GOPATH=$PWD/.gopath
go run . # run in place
# — or —
go build -o sprksh . # produce a binary
./sprksh

To install it on your PATH (run outside any restricted sandbox):

GOBIN=$HOME/.local/bin GOPATH=$PWD/.gopath go install .
# ensure ~/.local/bin is on PATH

Keybindings & mouse

Global

Key Action
ctrl+o Toggle focus between the shell and bubbles panes
ctrl+l Enter split-resize mode (see below); shadows the shell's clear
ctrl+q Quit sprksh (the wrapped session ends)

When the shell pane is focused, all other keys go straight to the PTY, so your shell, ZLE, and key bindings behave normally.

Split-resize mode (after ctrl+l, a toast shows the keys for ~5s):

Key Action
[ Shrink the shell pane
] Grow the shell pane
= Reset to a 50/50 split
any Exit the mode

Bubbles pane (when focused):

Key Action
/k, /j Move the selection
g cd the shell into the bubble's directory
R Re-run the bubble's command in the shell
x Pop (remove) the bubble
e Edit the persisted bubble in $EDITOR — trim output / add a note; saved edits stick
b Page the bubble's full record through bat (read-only)
c Copy the bubble's command into the shell prompt (edit + run yourself)
p Flush to disk and copy the bubble's .bubble file path to the clipboard
/ Start editing the filter footer (see below)

Filter footer (always visible at the bottom of the bubbles pane):

Key Action
/ Start editing (begins on the path: field)
Tab Toggle between the path: and cmd: fields
type Filter live — case-insensitive substring on cwd / command
ctrl+u Clear the active field
Esc/Enter Stop editing (the filters stay applied)

Both fields combine with AND; an empty field matches everything.

Mouse

  • Click either pane to focus it; click a bubble to select it.
  • Click the [go] / [R] / [x] / [e] / [b] / [c] / [p] labels on the focused card to trigger those actions directly.
  • Drag inside the shell pane to select a rectangular block of on-screen text; the highlight is clamped to the pane (blocked at the divider and borders), and releasing copies it to the clipboard — trailing whitespace trimmed, with a "selection copied" toast. This is how you grab visible output despite the live pane having no scrollback.
  • Drag the vertical divider to resize the split live.
  • Wheel over the bubbles pane to move the selection.

How it works (as built)

Persistent, instrumented shell

Each session is one long-lived zsh -i under a PTY — not a fresh zsh -c "..." per command — so cd, export, aliases, functions, and activated virtualenvs persist exactly as in a normal terminal.

To slice that continuous byte stream into per-command blocks without disturbing the shell's statefulness, sprksh injects shell integration via a generated ZDOTDIR: a temp .zshrc that first sources your real config, then appends precmd/preexec hooks emitting the documented OSC 133 semantic-prompt markers (A prompt-start, B input-start, C output-start, D;<exit> finished) plus OSC 7 (cwd) and OSC 633;E (the exact command line). These are the same markers iTerm2, WezTerm, and VS Code use. Every C ... D region becomes a Block{cwd, cmd, output, exit} → a bubble.

Limitation: prompt frameworks that fully rewrite PS1 each render (e.g. Powerlevel10k) may drop the appended B marker; A/C/D still fire from the hooks, so blocks and exit codes stay correct.

Two classes of command

  • Batch / streaming output is captured into a bubble. Rendering needs only light SGR (color/attribute) handling — never cursor addressing.
  • Full-screen / interactive apps are detected by their alt-screen escape (ESC[?1049h) and escalated: sprksh suspends its UI, drops the real terminal into raw mode, and relays bytes both ways (forwarding SIGWINCH) until the app leaves the alt screen — then restores its UI. The app runs at native fidelity; sprksh is momentarily just a pipe. This works even for apps launched over ssh, because the escape is in-band.

The live shell pane

The left pane is driven by a real VT emulator (vt10x) fed the OSC-stripped stream and kept the same size as the PTY. (This is for rendering the shell pane correctly — ZLE redraws, tab-completion menus — not for emulating full-screen apps, which still escalate.) The pane renders in color by walking the emulator's cells and emitting SGR spans, and shows a focus-aware cursor: a solid reverse-video block when the shell is focused, an underline when it isn't.

Bubbles that persist

Completed bubbles are written to an on-disk, content-addressed store at $XDG_DATA_HOME/sprksh/bubbles (default ~/.local/share/sprksh/bubbles), one plain-text file per bubble:

cwd: /abs/path
cmd: go build ./...
exit: 0
notes: {
optional free-form note, may span lines
}
<full, SGR-stripped output>
  • The filename is a hash of the content address (cwd + cmd + exit, plus the note when one is present — output is excluded), so re-running a command updates the one file for it with the most recent output and jumps its card to the most-recent position, rather than forking a new card. The pane stays 1:1 with the store. Adding a note is the deliberate exception: because a non-empty note folds into the address, it forks a new, uniquely-named record — the way you preserve a significant bubble long-term. Note-less bubbles keep their historic hash, so upgrading triggers no mass rename.
  • Notes are a free-form, multi-line annotation rendered on the card. [e] opens the bubble with an empty notes: { ... } scaffold; type inside the braces (or write a single bare notes: your text line — both parse). The notes: block is only written when non-empty, so note-less files stay byte-identical to before.
  • Files are plain text — auditable, grep-friendly, and reusable outside sprksh (a future "scriptlet" direction).
  • Writes are deferred to quit. The pane makes no disk I/O per command; instead it flushes the whole set once on quit (ctrl+q or the shell exiting), writing the current bubbles and reconciling the store (popped bubbles' files are removed). This keeps mid-session disk churn at zero. The tradeoff: a hard kill (SIGKILL / closing the terminal) loses this session's bubble changes — accepted, since reducing churn is the point.
  • On launch the pane rehydrates from the store (deduping by content address, most-recent wins, so a pre-change store migrates cleanly on the next quit).
  • [e] opens the persisted bubble (in store format) in $EDITOR; on editor exit the file is re-parsed and the in-memory bubble replaced, so trimming output or editing a note sticks. [b] opens the same record read-only through bat. Both are self-injected commands whose own blocks are dropped ([b] suppressed, [e] consumed by the read-back), and [go] cds are suppressed too — so peeking, editing, or navigating never creates a spurious bubble. [c] only types a command into the prompt, so what you run becomes a normal bubble.
  • [p] flushes the pane to disk and copies the selected bubble's .bubble file path to the system clipboard (best-effort; a transient toast confirms it). Run something, hit [p], and hand the path to a tool — or to Claude — to read the full record, output and note, straight off disk.

Rehydrated bubbles render monochrome (the store keeps plain text); the current session's live bubbles keep color until the next launch.

Layout

The as-built layout is a single shell pane beside the bubbles pane, with a draggable / key-adjustable split ratio. The bubbles pane carries a two-line filter footer (path / command, live substring). Multiple shell panes and stack/T arrangements are deferred (see STATUS.md). Live-pane scrollback is intentionally not pursued — the bubbles already hold each command's complete output, so [e]/[b] cover "see what scrolled past."

Roadmap

The next substantial idea is remote / ssh-aware bubbles — parked as a study cycle in Phase 6 of STATUS.md pending the QC interval. The insight: because the marker protocol is in-band and transport-agnostic, a remote shell that emits the same OSC 133 markers would produce bubbles over ssh with no ssh-specific code; the real work is giving bubbles a host/session identity so [go]/[R] route correctly.

Development notes

  • Module: github.com/nporcino/sprksh. Key deps: bubbletea, lipgloss, creack/pty, hinshun/vt10x, mattn/go-runewidth.
  • Build/test inside the safety-cage sandbox with a writable GOPATH: GOPATH=$PWD/.gopath go build ./... && go vet ./....
  • Most unit tests run in the sandbox. Tests that allocate a real PTY (e.g. TestInstrumentedShellPersistsCwd) and all interactive keyboard/mouse checks must run in a real terminal outside the sandbox — see the "How to verify" section of STATUS.md.

Layout of the code

Path Responsibility
main.go Entry point: start the shell, wire up the Bubble Tea program
internal/shell/ Instrumented PTY zsh: ZDOTDIR generation, OSC scanner, block builder, alt-screen escalation
internal/ansi/ Light SGR sanitize/strip (color only, no emulation)
internal/bubble/ Bubble card view + the bubbles pane (selection, actions, hit-testing)
internal/store/ Content-addressed on-disk bubble store (Format/Parse)
internal/clip/ Best-effort system clipboard copy (the [p] action)
internal/passthrough/ Raw terminal relay for full-screen escalation
internal/ui/ Top-level model: layout, focus routing, live pane render, status bar

License

Copyright 2026, Nick Porcino BSD 2-Clause