↑ ↓ to move · Enter to open · Esc to close Something not working? Troubleshooting
    On this page

    Search

    The suv search command opens an interactive search TUI that lets you find, filter, and reuse commands from your recorded shell history. It supports literal-word matching with a forgiving fallback that ignores separators, a view of every run or of identical commands grouped, a Smart rank that learns which commands you run often, which usually fail and what you usually run next, a scrollable detail pane, and a rich set of keyboard shortcuts.

    Search for docker logs and return the selected command to the prompt without running it. Recorded in v0.5.0 with fictional data.

    Basic Usage

    Launch the interactive search TUI:

    suv search

    Start typing to filter your history. Use the arrow keys to navigate, and press Enter (labelled Use) to put the selected command on your prompt for review when you opened search with Ctrl+R, or to print it when you ran suv search directly. Enter never runs anything.

    How Matching Works

    Each word you type must appear as a literal substring in the searched field (in any order) — so results always contain what you typed. Matching is case-insensitive. If nothing matches as typed, a terms query is tried once more with separators ignored, so hiveagent finds hive-agent (see below). Case is folded as described in Upper and lower case. Results are ranked by match quality: the query as a prefix first, then as a contiguous phrase, then your words in order, then in any order. Recall and search show your own (human-typed) commands by default; press Ctrl+A to include AI-agent commands. See How results are ranked for the full ordering.

    Since 0.4.0, database-level substring queries on the command field have an FTS5 trigram index, and since 0.5.1 every terms and literal query actually uses it: before, SQLite never handed those queries to the index, so each one scanned the whole history. On the 100,000-entry benchmark corpus (Apple M4 Max, BENCHMARKS.md) most judged queries now return in well under a millisecond instead of 10–40 ms, with the same results; fuzzy is about twice as fast, and prefix mode fell from 15 ms to 70 µs. Since 0.4.2, text you type or edit inside the open TUI takes the same path: every matching mode narrows candidates in the database, over your whole eligible history, so how old a match is never decides whether it is found. The query passed with -q/--query (which the Ctrl+R widget uses for any text already on your prompt) now goes through that same pipeline instead of a separate one.

    Matching Modes and Scopes

    Matching decides which commands are eligible, scope decides where to look, and ranking decides only the order. They are three separate controls, and changing one never changes what another does.

    Matching — --match, or Ctrl+X in the search UI: terms (the default — every whitespace-separated word must appear as a substring, in any order), literal (the whole query exactly as typed, spaces and punctuation included), prefix (the command starts with the query), and fuzzy (the query's letters appear in order, gaps allowed, so gco finds git checkout). Words are ANDed, never ORed; there is no quoting syntax and punctuation is never stripped, so git-push is one word — with one exception, the fallback below.

    When nothing matches: separators ignored

    Since 0.5.1, when a terms query matches nothing in the current scope and filters, it is asked again with separators ignored — whitespace, -, _, ., / and : are dropped from both your words and the commands. hiveagent then finds hive-agent, hive_agent, hive.agent, hive/agent and hive:agent, and hive-agent finds hiveagent. Every word must still appear.

    • The results title says ignoring punctuation (-_./:), the status row says Match terms (loose), and the matched text, separators inside it included, is underlined as usual.
    • It never mixes with exact matches: if anything matches as typed, only that is shown, so the count and every page come from one rule.
    • literal, prefix and fuzzy have no fallback. The MCP search_commands tool falls back the same way for its whole query and says so in its response.

    Upper and lower case

    Every mode ignores case. Since 0.5.1, terms (for a word with a non-ASCII letter in it) and fuzzy fold Unicode case rather than only lowercasing: ÉCHO finds écho, σ finds ΟΔΟΣ, and straße finds STRASSE. A word typed in plain ASCII is looked up in the search index, which folds ASCII case only, so STRASSE does not find straße; literal and prefix are answered by SQLite's LIKE, which folds ASCII only too. There are no language-specific rules: Turkish İ folds to i plus a combining dot, and dotless ı matches only itself.

    Scope — --scope, Ctrl+P to cycle, Ctrl+R to reset: all (default), directory (commands run in exactly the current directory, not its subdirectories — --here is the same thing), workspace (anywhere under the nearest enclosing Git repository; a linked worktree is its own workspace and a nested repository wins over its parent), and session (the current shell session). A scope that cannot apply here — no repository, no session — says so on stderr and falls back explicitly; it never silently widens.

    suv search --compact draws the same UI inline under your prompt instead of taking over the screen, so the output you were reading stays visible. All three have config keys — search.match_mode, search.scope and search.compact — that set the default for every recall, and all default to the behaviour of earlier releases. Since 0.5.0 you can set them on the Search tab of suv settings (Default Match Mode, Starting Scope, Compact Recall) as well as in config.toml.

    Fixed in 0.4.2: up to and including 0.4.1, typing or editing a query inside the open search TUI first loaded the newest 5,000 entries that passed your filters and matched your text against only those, so an older command could be missing from the results even though it was still stored. Matching and counting now finish in the database across your whole eligible history. At most 5,000 matches are ordered by relevance — that caps the ranking work, not how much history was searched, and not how much of it you can reach. The result count is the real number of matches and every page of it opens; pages past the ranked window come back newest first, because a relevance order computed from only part of a result set would be arbitrary there.

    Reading the Results

    Why a row matched

    The characters your query matched are underlined and emphasised in every row, over the usual syntax colours: every occurrence of each word in terms mode, the whole query in literal, the start in prefix, and the characters the subsequence rule walked in fuzzy. Highlighting applies when you search command text, the default field.

    Executions and Commands

    Search shows the same results in two views, and Ctrl+U switches between them:

    • Commands (where recall opens) — identical commands share one row, with a Last used age and a Runs count (for example 26×) of how many runs matched, so a command you ran fifty times does not push every other candidate down the list. A command is grouped only with exactly the same text: whitespace, flags and case are never normalised.
    • Executions — every recorded run, with its time, directory and outcome. On a wide terminal the columns are Time, Command, Path and Status, plus Ran by when agent commands are shown; each run's session and duration are in the detail pane.

    The results title says which view you are in, counts in its units, and names the key that switches: Executions 1-50 of 558 · ^U group, or Commands 1-50 of 139 · ^U every run. The command you had selected stays selected across the switch, on whichever page it lands, and going back to every run selects its most recent run. If that command was deleted meanwhile, a message in the footer says so instead of putting another command under the cursor.

    Since 0.5.0 recall opens in Commands unless your config.toml says otherwise: set show_unique_by_default = false in its [search] section, or turn off Start in Commands View in suv settings, to open in Executions. A value already in your config — including one suv settings wrote when you saved it — is kept. suv search -u opens in Commands either way.

    Long commands and hidden whitespace

    A row too long for the list ends in …; the selected row wraps so every character of it is on screen. Because commands are grouped only by exact text, whitespace you could not otherwise see is drawn: · for a leading or trailing space (a long run shows its first markers and how many more, such as +4), ⇥ for each tab, ↵ for each line break, and · for any other non-space whitespace. Runs of spaces inside a command are kept. The markers change only what is drawn, not what is stored, what Enter puts on your prompt, or how commands are grouped.

    Inspect the exact text

    When a command holds anything that cannot be read off its text — a tab, a line break, an edge space, or a character that draws as nothing, such as a zero-width space, a bidi override or a byte-order mark — the detail pane adds a Raw line that spells it out with escapes (for example "printf·'a\t\tb'" or \u{200b}), with every space drawn as · so none can hide at a line break.

    Press Ctrl+V to open the same raw view for the highlighted command in a scrollable overlay, however long it is: every space drawn as ·, every hidden character escaped, a line per line break, and its length in characters and bytes. Scroll with the arrow keys or PageUp / PageDown; Enter or Esc closes it without accepting the command.

    Since 0.5.0 each query runs on a background thread with its own read-only connection. A keystroke that changes the query interrupts the search still running, and results that arrive for an older query are discarded. While a slow search runs, the results title shows searching…, and an empty list says Searching… rather than explaining an earlier query's empty result. Enter, navigation, paging, and mode or scope changes first wait for the results of what is typed now, so the command you accept always matches the query on screen. Esc leaves at once.

    The row between the search box and the results shows the current Scope, Match mode, whether Agents are shown, and the Rank (Smart or Recent), followed by a badge for each active filter. An active filter always stays visible: on a narrow terminal the Rank segment gives way first. The search box holds only what you typed; whether results are grouped is shown in the results title.

    The filter panel (Ctrl+F) refuses what it cannot read. Since 0.5.1, pressing Enter with a date it cannot read (last tuesday), a tag that does not exist, or an exit code that is not a whole number applies nothing: the filters already applied stay, the panel stays open on that field, and a red line names the accepted date formats, the existing tags, or the rule. Editing the field clears the message; a blank field still clears its filter. The --after and --before flags likewise stop with could not read --after "last tuesday" as a date and exit 1 instead of showing everything.

    The footer keeps to the core keys at any width — use (Enter), navigate, filter (Ctrl+F), detail (Tab), mode (Ctrl+X) and scope (Ctrl+P), plus quit and help. Press ? to list every shortcut; all of them work whether or not the footer shows them.

    Command-Line Flags

    You can pre-filter results before the TUI opens by passing flags:

    Flag Description Example
    -q / --query Pre-fill the search query suv search -q "docker build"
    -u / --unique Start in the Commands view even when your config opens Executions suv search -u
    --after Show commands after a date or relative time suv search --after "3 days ago"
    --before Show commands before a date or relative time suv search --before "2024-01-01"
    --tag Filter by session tag suv search --tag deploy
    --exit-code Filter by exit code (0 = success, 1+ = failure) suv search --exit-code 1
    --executor Filter by executor (e.g., claude-code, cursor, user) suv search --executor claude-code
    --here Only show commands run in the current directory suv search --here
    --field Search one field: command, cwd, session, or executor suv search --field cwd
    --failed Show only commands that exited non-zero suv search --failed
    --include-agents Include AI-agent / CI / script commands (hidden by default) suv search --include-agents

    Keyboard Shortcuts

    These shortcuts are available inside the search TUI:

    Key Action
    Type any text Substring search / filter results
    Up / Down Navigate through results
    Tab Toggle detail preview pane
    Shift+Tab Move focus into the detail pane to scroll it, when its contents don't fit (see Detail Preview Pane); Esc, Shift+Tab or any other key returns to the list
    Enter Use: put the highlighted command on your prompt (from Ctrl+R) or print it (from suv search). It never runs the command.
    Esc Exit search without selecting, at once, even while a search is running. Ctrl+C does the same in one press
    Ctrl+X Cycle the matching mode (terms, literal, prefix, fuzzy)
    Ctrl+P / Ctrl+R Cycle the scope / reset it to all history
    Ctrl+S Toggle Smart rank (directory, frecency, outcome and sequence boosts for typed queries; on by default) and Recent rank
    Ctrl+L Toggle directory filter (show only current directory)
    Ctrl+E Toggle failed-only filter (commands that exited non-zero)
    Ctrl+A Toggle showing AI-agent / CI / script commands (hidden by default)
    Ctrl+U Switch between Executions (every run) and Commands (identical commands grouped); the selected command stays selected
    Ctrl+V Inspect the highlighted command's exact text in a scrollable raw view (see Inspect the exact text)
    Ctrl+F Open filter panel (date, exit code, executor, tag)
    Ctrl+OToggle bookmarks-only results
    PageUp / PageDownMove the selection 10 rows up / down
    Home / EndFirst / last result on the current page
    Ctrl+B Toggle bookmark on the highlighted command
    Ctrl+N Add or edit a note on the highlighted command
    Ctrl+T Tag the current session
    Ctrl+K Open the timeline of the session the highlighted command ran in, the same one suv sessions shows (since 0.5.3). Esc comes back to recall where you left it, and Ctrl+C closes both. Compact recall has too few rows for a timeline and says so instead.
    Ctrl+Y Copy the highlighted command to clipboard
    Ctrl+D Delete the highlighted entry
    Ctrl+G Go to a specific page number
    Left / Right Previous / next page of results
    F1 / ? Show help overlay with all shortcuts (the footer shows only the core keys)

    Smart Mode

    Smart mode is on by default (context_boost = true under [search]). Press Ctrl+S to toggle it; the status row shows the current ranking in its Rank segment (Smart or Recent), and the selected command stays selected. For a terms or fuzzy query, Smart rank multiplies each match's score, within its match tier, by four boosts:

    • This directory — entries whose recorded directory is exactly your current working directory (cwd_boost_percent). Commands run in subdirectories or parent directories are not boosted, and the directory column is highlighted for rows recorded in the current directory.
    • Frecency (since 0.5.1) — how often and how recently you ran that exact command. Each run counts 16 in its first hour, 8 that day, 4 for three days, 1 for a week, 0.5 for a month and 0.25 after that; the boost is the fourth root of one plus the sum, so twenty runs a day or two ago (×3.0) beat one run five minutes ago (×2.0) without burying it. It is the same definition the MCP suggest_next tool ranks by.
    • Outcome (since 0.5.1) — a command that usually fails, more than half of at least three runs with a known exit status, is scaled down, to a quarter when it has never succeeded. One failure among many successes changes nothing, and a run stopped with Ctrl+C (exit 130) is not a failure.
    • Sequence (since 0.5.1) — what you usually run next after the command you just ran in this shell. Recall looks at the newest 500 runs of that command in your history and, for each, at the next command recorded in the same session within 10 minutes; a command seen there at least twice is boosted by up to ×3, in proportion to how often it came next. So after git add -A, git commit comes before the git status you run far more often. It needs the shell hook's session ($SUVADU_SESSION_ID). Turn off this boost alone with Sequence Boost in suv settings or:
      [search]
      sequence_boost = false

    Frecency, outcomes and sequences count every run of that exact text in your history, not only the matching ones; agent runs count only while agent commands are shown. No boost lifts a command over a closer textual match, and matching, counts and paging are the same in both ranks. Recent rank leaves the four boosts out and orders exactly as earlier releases did. With an empty query, and in literal and prefix modes, results are listed newest first in both ranks. To see only commands from the current directory instead of boosting them, use the directory filter (Ctrl+L or --here).

    The statistics are read on the first keystroke of a recall session and reused after that. Measured on an Apple M4 Max with a 100,000-run history (BENCHMARKS.md), that first keystroke costs 11 ms more for a one-letter query in the Commands view; later keystrokes cost what they did before.

    How results are ranked

    When you type a query in the TUI, each candidate entry is scored and sorted by these keys, in order:

    1. Literal-token requirement. In terms mode, entries where any typed word is missing as a literal substring are dropped, even if they would match as a fuzzy subsequence (for example, gco does not match git checkout; use fuzzy for that). The separator fallback applies the same rule with separators removed.
    2. Match tier. Query as a prefix of the field, then the query as a contiguous substring, then all words in query order, then all words in any order. A better tier always ranks first, regardless of boosts.
    3. Boosted matcher score. Within a tier, entries are ordered by a fuzzy-matcher score (from the nucleo matcher) adjusted by:
      • Long-command penalty — fields longer than length_threshold characters (default 80) have their score scaled by √(threshold ÷ length).
      • Human boost — entries from a human terminal or with an unknown executor have their score raised by human_boost_percent (default 33%).
      • Directory boost — in Smart mode only, entries from exactly the current directory have their score raised by cwd_boost_percent (default 50%).
      • Frecency, outcome and sequence — in Smart mode only, the multipliers described in Smart Mode.
    4. Interactive tiebreak. On an equal score, commands from a terminal, an IDE terminal, or an unknown executor rank above agent, bot, CI, and script commands.

    Remaining ties go to the newest run (Executions) or the most-run command (Commands). The three scoring values can be changed in Settings. This ranking applies only to queries typed in the TUI; see the known limitation above for which entries are considered.

    Detail Preview Pane

    Press Tab to toggle the detail preview pane. When visible, it shows full metadata for the highlighted command, leading with where it ran and how it ended, so those are what you see when the pane has only a few rows below the results:

    • Full command text
    • Raw — only when the command holds whitespace or characters that cannot be read off its text; see Inspect the exact text
    • Path, Exit and Time — the working directory, exit code, and date and time of the run. In the Commands view these are the latest matching run's, labelled Last path, Last exit and Last run, followed by Runs, the number of runs that matched
    • Duration
    • Session ID and tag
    • Branch — since 0.5.1, the git branch the command started on (or the short hash of a detached HEAD), when it was recorded inside a repository; see Git branch capture
    • Executor (user, claude-code, cursor, etc.)
    • Bookmark and note status
    • Output — since 0.5.1, for a failed command whose output was kept, the end of what it printed. Only commands run by an agent integration or suv wrap with Keep Failure Output on have it; commands typed in your shell never do. See Failure output
    • Last time this failed, you fixed it with … — since 0.5.1, for a failed command, what fixed it before, most often first: Last time this failed, you fixed it with: npm install, then reran it (3×). It is worked out from your history when the command is selected, never stored; see below

    This is useful when multiple similar commands appear in results and you need to identify the exact one you want.

    When the pane's contents don't fit, press Shift+Tab to move focus into it, then scroll with Up/Down, PageUp/PageDown and Home/End (or j/k/g/G in vim Normal mode). A scrollbar sits outside the pane's border and the title shows the rows in view, such as 12–32/67. Esc, Shift+Tab or any other key returns to the list.

    How "you fixed it with" is worked out

    Nothing new is recorded for it. When you select a failed command, Suvadu takes the newest 50 failures of exactly that command (any exit status other than 0 and Ctrl+C's 130) and, after each one, looks at the next 20 commands of the same session, started within 15 minutes, in the same directory, skipping other failures:

    • a successful command similar to the failed one is its fix — the same program with the same first non-flag argument (cargo build → cargo build --release), or a small edit of the line (gti status → git status);
    • if that is the failed command again, unchanged, then a setup step that passed before it — the same program (npm install before npm test) or a package install — is the fix; with no setup step it passed when you reran it unchanged;
    • looking around in between (cd, ls, cat, git status/diff/log/show, an editor) is skipped, and any other successful command ends the search without a fix.

    Fixes are counted across those failures and the most common is shown. It is an inference from what ran next, never an observed cause. Each lookup took 0.6–0.7 ms on a 100,000-command history on an Apple M4 Max (BENCHMARKS.md), once per selected failed command. The MCP what_failed and learn_from_failures tools add the same answer as a fixed before (inferred) line.

    Vim Bindings

    Enable vim-style modal navigation with vim_mode = true in your config (suv settings → Search tab), or in config.toml:

    [search]
    vim_mode = true

    When enabled, the search TUI starts in Insert mode (typing searches as usual). Press Esc to switch to Normal mode for navigation:

    Key Normal Mode
    j / k Navigate down / up
    Ctrl+D / Ctrl+U Half-page scroll down / up
    g / G Jump to first / last entry
    h / l Previous / next page
    / or i Switch to Insert mode (search)
    Enter Use: put the command on your prompt (never runs it)
    Tab Toggle detail pane (with the pane focused by Shift+Tab, j/k/g/G scroll it)
    q or Esc Quit

    The current mode is shown in the footer as NORMAL or INSERT. All Ctrl+key shortcuts (filters, bookmarks, notes, etc.) work in both modes.

    Note: Ctrl+U and Ctrl+D only remap to half-page scroll in Normal mode. In Insert mode, they keep their original behavior (switching between Executions and Commands, and delete), which is why the results title offers ^U only outside Normal mode. All other Ctrl shortcuts work in both modes.

    Examples

    Find failed commands from the last 3 days

    suv search --exit-code 1 --after "3 days ago"

    Opens the search TUI pre-filtered to show only commands that exited with code 1, from the last 3 days.

    See what an AI agent ran in the current directory

    suv search --executor claude-code --here

    Filters to commands executed by Claude Code in your current working directory. Useful for reviewing what an AI agent did in a project.

    Search with a pre-filled query

    suv search -q "git rebase"

    Opens the TUI with "git rebase" already typed into the search box. The first list comes from a database query for that phrase across all recorded history, newest first.

    Start with identical commands grouped, whatever the config says

    suv search -u

    Opens in the Commands view — each distinct command text is one row, with when it last ran and how many runs matched — even if show_unique_by_default = false makes Executions your default. Press Ctrl+U to see every run instead.

    Filter by session tag

    suv search --tag deploy

    Shows only commands from sessions tagged "deploy". Tags can be applied with suv tag associate <name> or Ctrl+T in the search TUI.