SLOPSHOPPER

probe-proj

MOD-91 probe skills-dir mod

new
A shopper browsing a rack in a slop shop
README

htui

htui is a keyboard-driven terminal app for running a backlog of work through AI coding agents. It keeps your work items, their documents, the runs agents performed and full chat transcripts in one Postgres database, and lets you browse and drive all of it from the terminal, across several projects, repositories and machines.

  • One place for everything. Items, documents, notes, runs, requirements, skills and prompt templates live in Postgres. Credentials live in your OS keyring, never in a config file.
  • Works offline. A local copy of what you browse is kept on each machine, so htui opens instantly and stays readable when the server is out of reach.
  • Talks to real agents. Chat with Claude Code or Google's Antigravity (agy) from inside the app, and replay any recorded conversation later.
  • Drives runs step by step. Start a run on an item, approve or reject each step, retry, cancel and close out, all from the Backlog.
  • Never blocks. Network, database and agent work happen in the background, so the UI stays responsive.

It runs on Windows 10+, Linux and macOS.

Contents

Quick start

Try it without a database, using built-in sample data:

cargo run -p htui -- --demo

The demo opens on the Graphics workspace with a few projects, items, runs, notes and documents to look around. Press ? at any time to see the keys for the screen you are on, and q to quit.

Building from source

You need:

  • Rust 1.98.1. The exact version is pinned in rust-toolchain.toml, so rustup downloads it on your first build.
  • On Linux: the D-Bus development files and pkg-config, which the keyring integration builds against (sudo apt install libdbus-1-dev pkg-config on Debian and Ubuntu).
  • Network access on first use of the concepts index. Nothing is downloaded while building. The embedding model (about 134 MB) is downloaded into your user cache directory the first time you index or search, and checked against a pinned hash. An existing download from an earlier htui is reused. On a machine without network access, place onnx/model.onnx (saved as model.onnx) and tokenizer.json from Xenova/bge-small-en-v1.5 at commit ea104dacec62c0de699686887e3f920caeb4f3e3 in <user cache>/htui/model/bge-small-en-v1.5-ea104dac/ (~/.cache on Linux, ~/Library/Caches on macOS, %LOCALAPPDATA% on Windows): files whose sha256 matches the pins in crates/htui-store/src/model.rs are used and nothing is downloaded.
  • git on your PATH, for running work in repositories.

Then build:

cargo build --release

The binary is target/release/htui (target/release/htui.exe on Windows).

To install it onto your PATH (~/.cargo/bin) instead, use cargo install. htui is not published to crates.io, so install from git or from a checkout:

cargo install --git https://git.mluigi.it/htui.git htui --locked
cargo install --path crates/htui --locked

Toolchain

workspace.package.rust-version in Cargo.toml and clippy.toml both say 1.98, matching the pinned toolchain on purpose: with an exact pin, the only minimum version that is both true and checkable is the one everybody actually builds with. Keep all three in step when you move the pin.

Command-line options

OptionWhat it does
--demoStart with the built-in sample data in memory instead of connecting to a database.
--offlineOpen from the local copy only and never try to connect.
--set-dsnRead a Postgres connection string from standard input, save it in the OS keyring and exit.
--clear-dsnRemove the saved connection string from the OS keyring and exit.
--dsn-stdinRead the connection string for this session from standard input; it is never stored. For a TUI over ssh -t on a machine with no keyring.
--keys <PATH>Read key bindings from this file instead of keys.toml. See Changing keys.
--default-keysIgnore keys.toml for this run and use the default keys.
--print-keysPrint every key binding in force as a commented keys.toml and exit. With an invalid file, print its errors and exit with status 2.
--index-itemsUpdate the search index from the database, then exit. See Search.
--search-items <QUERY>Search items, their documents and requirements, print the results and exit. Exits 3 when the database or the search index cannot be reached, 2 on a refusal (for example no index address or connection string saved) and 1 on any other failure.
--project <SLUG>With --index-items or --search-items: only this project. status and waiting take their own --project, after the subcommand.
--decisionsWith --search-items: only decisions, meaning items closed as done, concluded or rejected, and their documents.
--limit <N>With --search-items: how many results to print, from 1 to 1000 (default 10).
--jsonWith --search-items: print one JSON document instead of lines. See Scripting htui.
--cwd <PATH>With --search-items: only the project whose repository, registered on this machine, contains PATH. Cannot be combined with --project.
--log <PATH>Append logs to a file. Also read from HTUI_LOG; HTUI_LOG_FILTER changes the level (default info). Logs never go to the terminal, since the terminal is the app (htui worker, which has none, logs to standard error without it).
worker [--pool-size N] [--dsn-stdin] [--log PATH]Run this machine's runs with no terminal, so they outlive the TUI: htui worker. Needs the box's executor set to worker in Settings › Boxes; no other option may be given beside it, and --log goes after worker. See docs/htui-worker.md.
provision <DESTINATION> [--dsn-stdin] [--replace-credential] [--log PATH]Install this build as the htui-worker service on another Linux machine over ssh, with the DSN encrypted there by systemd-creds, and set that box's executor to worker. See docs/htui-worker.md.
`status [--json] [--project SLUG \--cwd PATH] [--log PATH]`Print the top bar's facts and exit: the workspace, this machine, and how many runs are working and how many things wait on you. Reads only. See Scripting htui.
`waiting [--json] [--project SLUG \--cwd PATH] [--log PATH]`Print what waits on you, as the waiting-on-you list shows it, with how long each has waited, and exit. Reads only. See Scripting htui.
--help, --versionPrint usage or the version and exit.

Connecting to your database

htui needs Postgres 16 or newer.

Run a local server

The repository includes a compose.yaml that starts Postgres 16 on port 5439, so it does not clash with a Postgres you may already have on 5432:

docker compose up -d
echo postgres://postgres:REDACTED@localhost:5439/htui | htui --set-dsn

The same file also starts Qdrant, which is only needed for Search.

Save your connection string

The connection string (DSN) is stored in your OS keyring: Windows Credential Manager, the macOS Keychain, or the Secret Service on Linux. There is no environment variable or config file for it, so it never ends up in your shell history or process list.

You can save it in either of two ways:

  • From inside the app: open Settings › Connection and press e. The input is masked. Press Enter to save, and htui connects straight away without a restart. A machine with no saved connection opens on this screen automatically.
  • From the command line: run htui --set-dsn, paste the DSN and press Enter. You can also pipe it in, as in the example above. htui --clear-dsn removes it again.

TLS follows whatever your DSN asks for, for example ?sslmode=require.

Online and offline

htui opens from its local copy first and connects in the background. The top bar shows where it stands:

Top barMeaning
memoryRunning with --demo. No server and no local copy.
connectingThe first connection attempt has not answered yet.
onlineConnected. The local copy is kept up to date in the background.
offline · 3mNo connection for three minutes. You can browse what was copied locally, but nothing can be changed and no chat or run can start.

While offline, htui retries every 30 seconds and goes back online on its own. Use --offline to look at the local copy on purpose.

Database upgrades

When a new version of htui needs to upgrade your database, it asks first and never upgrades on its own:

3 schema migrations are pending. Apply them now?

y apply · n / Esc stay offline

Press y to upgrade and go online, or n / Esc to leave the database untouched and keep browsing the local copy. If the database is newer than your copy of htui, htui stays offline and says why on the status line; update htui to connect.

Using htui

Everything happens in a workspace. Press w to switch workspaces. If the database has none yet, create one in Settings › Hierarchy with N.

htui has five tabs. Switch with Tab / Shift+Tab or the number keys 1 to 5.

TabWhat it is for
1 BacklogThe workspace's items, grouped by project, with details on the right. This is also where you drive runs.
2 SkillsYour skill library and prompt templates: browse versions, compare them, edit, import, and attach skills to projects and repositories.
3 RequirementsEach project's requirements, grouped by area: what each one says, which items cite it and how it changed over time. The project's owner adds, amends and withdraws them here.
4 SettingsAgents, workspaces and projects, item kinds, prompt settings, the database connection, the search server, this machine's profile and agent personas.
5 ChatA live conversation with an agent, and replays of recorded ones.

Press ? on any screen for its full list of keys. The ones below are the essentials.

Everywhere

KeyAction
qQuit
Tab / Shift+TabNext / previous tab
1 … 5Go to a tab
wSwitch workspace
?Show or hide the key help
EscClose the open pop-up

Most lists use j / k (or the arrow keys) to move, and h / l (or [ / ]) to switch between sub-tabs or sections.

Changing keys

htui reads your key bindings from keys.toml in its configuration directory (see Where htui keeps its files); --keys <PATH> reads another file instead. The file is optional. List only what you change: a list replaces that action's keys, and [] unbinds it. htui --print-keys prints every action with its keys as a commented keys.toml: start from it, and run it again to check your file.

version = 1

[global]
quit = ["ctrl-y"]

[overlay]
close = ["esc", "f2"]

Within one table, your keys win over the defaults you leave alone: if a key you list is the default of another action in the same table, that action gives it up, and is unbound if it had no other key. htui starts anyway and says so on stderr, with the file's path and line, as in [global] quit = "ctrl-q" takes "ctrl-q" from global.queue, which is now unbound; htui --print-keys marks that action's line (unbound by quit). List the action with another key to keep it.

htui refuses to start on an invalid file and names every bad line, as in keys.toml:5: [global] quit = "ctrl-c": ctrl-c always quits and cannot be bound; htui --default-keys starts with the default keys anyway. Two actions you list with the same key are an error. A default gives way to a key from another table in one case only: a [global] key may take the in-pane editor's abort key (ctrl-x) from [editor] abort, which is then unbound, with the same notice. Otherwise a key shared between a pop-up's keys and [global] help is an error in either direction (such as esc for [global] help, or f1 for [overlay] close). [editor] focus must keep a key, and no other action in any table may use it. ctrl-c always quits and cannot be bound. The [editor] rules are in docs/htui-editor.md.

A table named after a Settings section, a Skills view, [requirements], a Backlog sub-tab ([backlog.runs], [backlog.docs], …), [chat] or a pop-up changes a shared action there alone: [settings.boxes] reload = ["f5"] reloads Boxes on F5, and every other section keeps r. [backlog] holds the Backlog's own keys (filter, new item, queue, …), [queue] the queue overlay's (Ctrl+q: move, reorder, pause, dequeue, open) and [picker] the directory picker's (Settings › Hierarchy, b); [backlog.runs] filter = ["/"] moves the filter key in the Runs sub-tab alone. htui --print-keys prints every table that has keys. To see what a table may change, write a name htui does not know in it: the error lists every action that table has and every shared action it may override. A line copied from --print-keys into such a table, like [settings.agents] next_field, replaces that row, so it stops following the shared [form], [common] or [confirm] line; delete it to let it follow again.

A global key must not be a key that Settings, Skills, Requirements, the Backlog, Chat or a pop-up already uses: [global] quit = ["x"] is refused, because x cancels in Settings › Agents and rejects a step in the Backlog's Runs; so is [global] quit = ["f"], because f opens the Backlog filter. A shared table is checked the same way on every screen that uses it: [list] down = ["G"] is refused, because G jumps to the bottom in Chat, and so is [pane] next_subtab = ["tab"], because Tab moves to the next tab. Some Settings forms also move between fields with Down / Up. A [form] next_field line changes them too, and they keep Down; write [settings.agents] next_field = [...] to replace it in that form alone. While a text field takes typing, the keys the field uses itself (letters, arrows, Enter, Esc) stay the field's. In the same way, keys a screen keeps for itself (a choice's value keys, such as Space and h / l on the Backlog filter's rows, or the digits that answer a permission request, the Requirements priority's m / l / Space / ← / →, or the attachment form's activation Space) cannot be given to an action there: [backlog.filter_form] clear = ["space"] is refused. Tab still leaves a Skills editor with the draft kept. In the Backlog's item form and the Notes and Docs editors, [form] save and [form] external_editor replace Ctrl+s and Ctrl+e.

The row above the status line names the keys of what you are in, and follows your keys.toml; ? lists all of them. Every action with its default keys is in docs/htui-keys.md, the output of htui --default-keys --print-keys.

Backlog

Select an item on the left to see it on the right. The detail pane has seven sub-tabs: Body, Runs, Graph (linked items), Docs, Notes, Prompt and Reqs (the requirements the item cites).

KeyAction
j / k, g / GMove, jump to the first / last row
Enter on a projectFold or unfold the project
h / lPrevious / next detail sub-tab
J / K, PageUp / PageDownScroll the detail pane; in Runs, Graph, Docs and Reqs J / K move the pane's cursor, and in Graph PageUp / PageDown move it a page
f / FFilter the list / clear the filter
N / eNew item / edit the item
mOpen the Graph sub-tab
Q / PQueue or dequeue the item / pause or resume this box's queue
?Every key of the pane you are in

The row above the status line names the keys of the focused sub-tab, then the list's when they fit. Ctrl+q opens this box's queue: j / k move, J / K reorder, P pauses or resumes, Q dequeues and Enter opens the item.

Runs. The Runs sub-tab lists an item's runs and their steps. Move between steps with J / K. If an action isn't allowed right now, the status line says why and nothing happens.

KeyAction
RStart a run on the item
a / xApprove / reject the step (rejecting asks for a note)
rRetry the step
sPick this step as the winner of a fan-out
AAccept the step's output
oRead the step's output document
pContinue the step as a chat
iSend a running step's agent a follow-up; it is sent when the current turn ends
EnterReplay the step's conversation in the Chat tab
cCancel the run
TRetry the run's cleanup
uUnblock the item
CClose out the item: pick how it ends with ← / →, press y, then type the item's key to confirm
vSwitch between the list and the flow view
+ / -, =In the flow view: zoom in / out, fit the whole run

The flow view draws the run under the cursor as a graph. Fan-out candidates sit side by side, the judge goes below them, and a retry follows the attempt it replaces. It uses the same cursor as the list, so J / K and every key above work in both views.

When you close out an item, the picker offers only the endings that fit its status. A done item starts on done and can end as done, concluded, rejected, withdrawn, superseded or duplicate. An open, blocked or failed item starts on withdrawn and can also end as rejected, superseded or duplicate.

Runs need a connection to the database.

Graph. The Graph sub-tab draws the items the selected one links to. J / K and PageUp / PageDown move the cursor, + / - show more or fewer links deep, and Enter re-roots the graph on the item under the cursor.

Docs. J / K move between the item's documents, a writes a new document and v a new version of the one under the cursor; Ctrl+s saves.

Notes. a adds a note to the item; Ctrl+s saves it.

Prompt. The Prompt sub-tab shows exactly what an agent would be sent for this item: the assembled text, its token budget, what was trimmed to fit, and which files were picked as context. n / p switch between the project's templates. Looking at a prompt changes nothing and starts nothing.

Reqs. The Reqs sub-tab lists the requirements the item cites, and how: it addresses a requirement, reserves it for later, or is the item that amends or withdraws it. A citation is marked ! suspect when the requirement has changed since the item cited it.

KeyAction
J / KMove between citations
rRe-confirm a suspect citation against the requirement as it reads now
cCite another requirement of the item's project (Enter picks it, then a addresses or v reserves)
uRemove the citation (asks you to confirm with y)

A citation made by amending or withdrawing a requirement is part of that decision and stays. Citing needs a connection to the database.

Requirements

The left pane lists the workspace's projects, their areas and each area's requirements, with their priority (must or later). Select a requirement to see its full text and rationale, which items cite it (a ! suspect marker means the item cited an older version) and its revision history. Withdrawn requirements stay in the list, dimmed and marked ✕.

KeyAction
j / k, g / GMove, jump to the first / last row
Enter on a project or areaFold or unfold it
J / K, PageUp / PageDownScroll the detail pane
/Filter the list as you type (Enter keeps the filter, Esc clears it)
aAdd an area to the project
nWrite a new requirement in the selected area
eAmend the requirement
WWithdraw the requirement (asks you to type its key to confirm)
rRead the requirements again

In the forms, Tab moves between fields and Ctrl+s saves. An amend or a withdraw names the item that decided it, and that item then cites the requirement.

A project's requirements belong to whoever first added an area or a requirement to it. Anyone can read them, but only the owner can add, amend or withdraw; for everyone else, and while offline, the write keys are greyed out and the status line says why. If someone else changed a requirement while you were amending it, your text is kept and Ctrl+s saves over the new version.

Chat

KeyAction
i / EnterWrite a message (Enter sends, Esc stops writing)
aPick another agent (before the first message)
1 … 9Answer the agent's permission request
Esc EscEnd the conversation
tShow or hide the agent's thinking
j / k, g / GScroll

The header shows the agent, the model and the project. If the agent can't do something, such as ask for permission or propose edits, a banner says so before you start typing.

Chats need a connection to the database. When you quit, htui stops every running agent and waits for it to exit.

Replays open read-only over the Chat tab. Esc closes the replay and brings back your live conversation exactly as it was.

Settings

SectionWhat you do there
AgentsSee which agents work on this machine (r checks again), install an agent's adapter (i), and log in (a).
HierarchyCreate and edit workspaces (N), projects and repositories (n), and tell htui where each repository lives on this machine (b picks a directory, or i detects them).
KindsItem kinds, their phases and step graphs; a phase's persona field binds a persona to it.
PromptPrompt settings such as the token budget.
ConnectionThe database connection string (e edit, c clear) and rebuilding the local copy (R).
QdrantThe search server's address and API key (e edit, c clear).
BoxesThis machine's profile: tags (t), quirks (e), who runs its runs, the TUI or htui worker (w), and a fresh check of its tools (p).
PersonasAgent personas: create (n), edit the fields (e), the body (b) and the permission rules (r), delete (d), and import .md persona files (I). See docs/personas.md.

Deleting in Hierarchy shows what would be removed and asks you to type a confirmation, because it cannot be undone.

Setting up agents

htui comes with three agents:

AgentWhat it is
claudeClaude Code over ACP, the Agent Client Protocol. The recommended choice.
claude-cliClaude Code through its command-line JSON stream. A fallback for when the ACP adapter can't be installed.
agyGoogle Antigravity, through Google's ACP server.

Check Settings › Agents to see which ones work on this machine. n adds an agent and e edits the selected one.

Installing an adapter

Some agents need a separate adapter program. For agy, that is Google's agy_acp_server, which the agy command-line tool does not install. In Settings › Agents, select the agent and press i. Before anything is downloaded, htui shows what it will install: the version, where it comes from, its size, whe

Source 1 files
hooks/register.ts 2 lines
1export function register(on: any, options: any) {}
2