Skip to content

Epic: bare dirsql opens an interactive REPL #953

Closed
3 of 3 issues completed
Closed

Description

@thekevinbot

Problem

dirsql with no arguments prints a usage error pointing at dirsql server (packages/rust/src/cli/run.rs:296-301). It does not start the server — an earlier belief to the contrary was wrong — but an error is still the wrong default.

sqlite3 sets the expectation: run the binary bare, get an interactive shell. dirsql should do the same — a REPL accepting SQL against the current directory (path-tables, plus named tables when -c is passed).

Current dispatch

invocation behavior
dirsql "<sql>" one-shot query (default mode, synonym dirsql query)
dirsql server HTTP server
dirsql init starter config
dirsql (bare) usage error, exit 2

Slices

Stacked — each PR's base is the branch below it.

Why this is an epic rather than one PR

Slice 3 is the reason. Slices 1–2 reuse the existing JSON array serialization, which is correct for dirsql query and wrong for a REPL — SELECT * FROM './' in a 5k-file repo dumps a 5000-element JSON array at a human. One-shot output is piped to jq; REPL output is read with eyes. Different renderings, and a real product decision rather than a detail of slice 1.

The feature is mostly assembled machinery

run_query (run.rs:308) is three calls: load_state (run.rs:434), report_scan_failures, and execute_query (cli/execute.rs:59). A REPL is a loop around the third. load_state and execute_query need no changes.

Two things fall out for free:

  • Zero new dependencies for slice 1. std::io::IsTerminal is std since 1.70; the workspace is on 1.85 / edition 2024.
  • Zero new flags. Bare dirsql takes no subcommand, so ConfigArgs is already flattened onto the top-level Cli (run.rs:60-63) — -c, --persist, --no-ignore, --on-file apply to the REPL with no parser work. (Confirm args_conflicts_with_subcommands does not bite with no SQL present — one parse test.)

Decisions made

  • One AppState, built once before the loop — not a loop of one-shot queries. Answers the original open question: it is cheaper (no directory re-scan per statement), a smaller diff, and the live watcher keeps the index fresh across statements.
  • No dot-commands. Answers the other open question. The dot prefix exists in sqlite3 to namespace meta-commands against SQL; with no meta-commands there is nothing to namespace. Exit is bare exit / quit / Ctrl-D — unambiguous, since neither is a valid SQL statement. If meta-information is wanted later, it should be queryable as SQL (sqlite_master), which fits dirsql's premise better than a command namespace.
  • Exit code 0 on clean EOF, regardless of per-statement failures (matches interactive sqlite3).
  • Static sample-query banner on TTY start — sample queries only, no live data rows and no scan (see Configless startup walks the entire tree for a provably empty result #986).
  • Table in TTY, JSON in pipes (slice 3).

Open question — owned by #989

Once two output formats exist, can the user pick? .mode is the obvious answer and dot-commands are ruled out. Alternatives: infer-from-TTY-only (no control), or a --format {auto,table,json} flag valid in both modes. Decide before slice 3.

Behavior change

echo "SELECT 1" | dirsql goes from an exit-2 usage error to running the statement. Verified: nothing in the repo asserts the old behavior — only no_subcommand_and_no_sql_selects_neither (run.rs:667) touches it, and it asserts parsing, not the runtime error. Slice 1 carries the required migration fragment.

Testing

The TTY path is e2e-testable — via curtaincall (real PTY + VT100, pytest plugin with auto-waiting locators and snapshot support). Lands in packages/python/tests/e2e/ as e2e tier, since it drives the CLI with nothing mocked.

One caveat: curtaincall declares requires-python = ">=3.12" while dirsql's python package is >=3.10, so it needs an environment marker plus a skip guard. Filed upstream as thekevinscott/curtaincall#26 — its full suite (200 tests, including 74 real-PTY integration tests) passes unmodified on 3.10.20, so the floor looks like a policy default rather than a technical constraint. If that lands, the marker and guard are unnecessary.

Related

Activity

  1. changed the title [-]Bare `dirsql` should open an interactive REPL[/-] [+]Epic: bare `dirsql` opens an interactive REPL[/+] on Aug 20, 2026
  2. thekevinbot commented on Aug 21, 2026

    @thekevinbot
    CollaboratorAuthor

    All three slices landed: #987 via #991, #988 via #998, #989 via #1006 (format control resolved as --format {auto,table,json}). Bare dirsql now opens a REPL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions