Skip to main content

hsql

hsql is your agent's favorite SQL client. It's the headless CLI for Harlequin, and shares the same config and query engine, with an interface optimized for agents, scripts, and automations.

If you already use Harlequin and know about adapters and config files, jump ahead to Running hsql.

Installing hsql

hsql is packaged with Harlequin, so if you already use Harlequin, hsql is already installed.

Otherwise, you can install hsql directly. hsql is a Python program, and there are many ways to install and run it. We strongly recommend using uv:

  1. Install uv. From a POSIX shell, run:

    curl -LsSf https://astral.sh/uv/install.sh | sh
    

    Or using Windows Powershell:

    powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
    
  2. Install hsql as a tool using uv:

    uv tool install hsql
    

    This command will install hsql into an isolated environment and add it to your PATH so you can easily run the executable.

Installing Database Adapters

hsql can connect to dozens of databases using adapter plug-ins. Adapters are distributed as their own Python packages that need to be installed into the same environment as hsql and harlequin.

For a list of known adapters provided either by the Harlequin maintainers or the broader community, see the adapters page.

The adapter docs also include installation instructions (installing an adapter with Harlequin also installs it for hsql). Some adapters can be installed as Harlequin extras, like postgres. If you used uv to install hsql directly, you can add adapter packages using --with:

uv tool install hsql --with harlequin-postgres

You can install multiple extras:

uv tool install hsql --with harlequin-postgres --with harlequin-mysql

Running hsql

Once hsql is installed, you run it from the command line. If you have used psql or the duckdb CLI, hsql will feel familiar, but hsql has the major advantage that is works with most databases and provides the same interface and produces the same output, regardless of the connected database. This means you (and your agent) can learn one tool, instead of several. In your shell, all hsql commands take the same form:

hsql [OPTIONS] [CONN_STR]

where [OPTIONS] is 0 or more pairs of the form --[option-name] [option-value], and [CONN_STR] is 0 or more connection strings. [OPTIONS] are composed of both hsql options and adapter options. For a full list of options, run hsql with the --help option:

hsql --help

Using hsql with DuckDB

hsql defaults to using its DuckDB database adapter, which ships with hsql and includes the full DuckDB in-process database.

Run a query against an in-memory DuckDB session, run hsql and pass in a query with the -c option:

$ hsql -c "select 1"
 1
---
 1
(1 row)

To query one or more DuckDB database files, pass in relative or absolute paths as connection strings (hsql will create DuckDB databases if they do not exist):

$ hsql "path/to/duck.db" -tAc "select count(*) from orders"
42

Using hsql with SQLite and Other Adapters

hsql also ships with a SQLite3 adapter. To use that adapter, you specify the --adapter sqlite option. Like DuckDB, you can open an in-memory SQLite database by omitting the connection string:

$ hsql --adapter sqlite -c "select 'Ted' as author"
 author
--------
 Ted
(1 row)

You can query one or more SQLite database files by passing in their paths as connection strings; note that the --adapter option has a short alias, -a:

$ hsql -a sqlite "path/to/sqlite.db" -c "select * from users"
 id | name
----+---------
 1  | Ted
 2  | Patrick
(2 rows)

Other adapters work the same way; for example, Postgres:

$ hsql -a postgres "postgresql://example.com/postgres:5432" -c "select * from invoices"

Configuring hsql and Using Profiles

hsql supports a number of options for setting the query limit, configuring output formats, and defining connection parameters. Options can be passed as command-line flags, or read from config files. Config files store configurations under separate profiles, so you can easily switch between databases by reading from different profiles with the -P option:

$ hsql -P prod -c "select count(*) from orders" --csv
$ hsql -P dev -c "select * from users" --vertical --limit 5
$ hsql -P warehouse -c "..." --format parquet -o invoices.pq

hsql reads the same config files as Harlequin, and merges them one profile at a time, nearest file first; pass --config-path PATH to read a single file instead, or -P None to skip them entirely.

Keeping Credentials Out of Config Files

A profile's string values can name environment variables, so a config file your team shares — and commits — holds no credentials:

[profiles.prod]
adapter = "postgres"
host = "${MYHOST:-localhost}"  # ${VAR:-default} supplies a default
password = "${MYPASSWORD}"     # ${VAR} is required; hsql exits 2 if it is unset

Write $${ for a literal ${, if a value really does start with one.

Values an adapter declares as secrets are masked wherever hsql prints them — --config show, --info, and error messages — as is the password inside a connection string.

Inspecting and Writing Config Files

Five --config modes work on your config files instead of running SQL, and none of them connects to a database:

  • list-profiles — the names you can pass to -P, with each one's adapter, and which is the default.
  • show — the merged config as TOML, with the file each value came from beside it; --json for JSON.
  • validate — every problem in every discovered file, exiting 2 if it finds any.
  • schema — a JSON Schema for a config file, covering every adapter you have installed; point your editor at it for completion and validation as you type.
  • init — a profile written from the options you pass, prompting for nothing: hsql --config init -P prod -a sqlite ./my.db --limit -1 writes [profiles.prod] into the nearest config file.
$ hsql --config show
default_profile = "dev" # from /home/user/.config/harlequin/harlequin.toml

[profiles.dev] # from /home/user/proj/harlequin.toml, overriding /home/user/.config/harlequin/harlequin.toml
adapter = "postgres"
host = "${MYHOST:-localhost}"
password = "********"
limit = 100

list-profiles and validate are result sets, so --csv, -t, -A and -o work on them as they do on a query.

Data Layouts and File Formats

hsql supports all of the following formats for displaying and writing data:

  • table
  • markdown (alias: md)
  • vertical
  • csv
  • tsv
  • json
  • jsonl (alias: ndjson)
  • parquet
  • orc
  • feather (alias: arrow)
  • none (suppresses output)

You can select a format with the --format <name> or using the shorthand --<name>, so these are equivalent: --format csv, --csv.

Some layouts can present the results from multiple queries. Others will raise an error and exit with code 2 if multiple queries are executed.

Additionally, for any layout, pass --stats to print summary info as JSON to stderr:

$ hsql -c "select 1" --format none  --stats
{"status":"ok","statements":1,"rows":1,"truncated":false,"limit":500,"elapsed_ms":1,"columns":[{"name":"1","type":"#"}]}

Exploring the Catalog

Before you can write a query, you have to know what is in the database. --catalog lists the objects one level below --path, and exits without running SQL:

$ hsql "path/to/duck.db" --catalog
 path | name | query_name | type     | type_label
------+------+------------+----------+------------
 duck | duck | "duck"     | database | db
(1 row)

Every row's path is what lists that object's own children, so you walk down one level at a time:

$ hsql "path/to/duck.db" --catalog --path duck
 path           | name      | query_name         | type   | type_label
----------------+-----------+--------------------+--------+------------
 duck.analytics | analytics | "duck"."analytics" | schema | sch
 duck.main      | main      | "duck"."main"      | schema | sch
(2 rows)

$ hsql "path/to/duck.db" --catalog --path duck.analytics
 path                        | name         | query_name                 | type       | type_label
-----------------------------+--------------+----------------------------+------------+------------
 duck.analytics.customers    | customers    | "analytics"."customers"    | BASE TABLE | t
 duck.analytics.order_totals | order_totals | "analytics"."order_totals" | VIEW       | v
 duck.analytics.orders       | orders       | "analytics"."orders"       | BASE TABLE | t
(3 rows)

$ hsql "path/to/duck.db" --catalog --path duck.analytics.orders
 path                              | name        | query_name    | type          | type_label
-----------------------------------+-------------+---------------+---------------+------------
 duck.analytics.orders.customer_id | customer_id | "customer_id" | BIGINT        | ##
 duck.analytics.orders.id          | id          | "id"          | BIGINT        | ##
 duck.analytics.orders.placed_at   | placed_at   | "placed_at"   | TIMESTAMP     | ts
 duck.analytics.orders.total       | total       | "total"       | DECIMAL(18,2) | #.#
(4 rows)

A listing is rows, so every format and output option applies to it, exactly as they do to a query:

$ hsql "path/to/duck.db" --catalog --path duck.analytics -tA --csv
duck.analytics.customers,customers,"""analytics"".""customers""",BASE TABLE,t
duck.analytics.order_totals,order_totals,"""analytics"".""order_totals""",VIEW,v
duck.analytics.orders,orders,"""analytics"".""orders""",BASE TABLE,t

Searching the Catalog

Walking is the wrong tool for where does orders live and which tables have a customer_id. --catalog-search TERM searches every level of the catalog at once, for objects whose name contains TERM:

$ hsql "path/to/duck.db" --catalog-search customer_id
 path                                    | name        | query_name    | type   | type_label
-----------------------------------------+-------------+---------------+--------+------------
 duck.analytics.order_totals.customer_id | customer_id | "customer_id" | BIGINT | ##
 duck.analytics.orders.customer_id       | customer_id | "customer_id" | BIGINT | ##
 duck.main.staging_events.customer_id    | customer_id | "customer_id" | BIGINT | ##
(3 rows)

--path narrows the search to one subtree, and the output can be laid out like any query:

$ hsql "path/to/duck.db" --catalog-search order --path duck.analytics -tA
duck.analytics.order_totals|order_totals|"analytics"."order_totals"|VIEW|v
duck.analytics.orders|orders|"analytics"."orders"|BASE TABLE|t

Note: not all adapters support catalog search at this time.

Scripting with hsql

hsql can write data to files, either with the -o option or by piping output (hsql only writes data to stdout; other messages go to stderr):

$ hsql -P prod --limit -1 -c "select * from users" --format parquet -o "users.pq"
$ hsql -P prod --limit -1 -c "select * from users" --csv > users.csv

hsql can execute multiple statements in one invocation, and supports several methods for doing so:

  • Pass -c multiple times
  • Include multiple queries, separated by ;, in one -c option
  • Pass one or more .sql files with -f, with multiple statements in each
  • Use --result to define which queries output data to stdout
  • Use --on-error to either stop or continue if one or more queries produces an error.

In other words, this works:

$ hsql -P prod --limit -1 --format md --result all --on-error stop \
    -f ./setup.sql \
    -c "select count(*) from raw_table" \
    -f ./build-models.sql \
    -c "select count(*) from modeled_table" 

hsql's exit codes are meaningful and stable:

  • 0: Success
  • 1: Query error
  • 2: Usage/config error
  • 3: Connection error
  • 4: Timeout
  • 130: Interrupted

You can also use --stats and jq together to error on a truncated query:

hsql --limit -1 -c "select 1" --csv -o data.csv --stats 2>&1 | jq -e '.truncated | not' > /dev/null

Running Safely

Two options bound what an invocation can do, and hsql refuses to run at all if the adapter cannot enforce them.

--read-only (or -r) connects in a mode the database itself refuses writes in:

$ hsql -r "path/to/duck.db" -c "insert into orders values (1)"
hsql: error: Invalid Input Error: Cannot execute statement of type "INSERT" on database "duck" which is attached in read-only mode!

--timeout SECONDS limits the duration of executing and fetching a query, and exits 4 when it runs out:

$ hsql --timeout 0.5 -c "select count(*) from range(100000000000) t(i)"
hsql: error: timed out after 0.5s

Both are also profile keys:

[profiles.agent]
adapter = "postgres"
read_only = true
timeout = 30

hsql refuses to open a connection with an adapter that cannot enforce --read-only or --timeout.

hsql --info reports implements_read_only and implements_cancel for every installed adapter.

Describing hsql to an Agent

hsql --help is written for a person; two flags answer the same questions as JSON, so an agent can find its way around an installation it has never seen. --spec is a machine-readable --help: every option on the command, plus the connection options every installed adapter declares, each with its type, choices, default and help text. --info describes the installation instead — versions, platform, the config files hsql found, the profile that would be active, and what each installed adapter declares it supports — so a script can check that an adapter is there, and what it can do, before depending on it.

$ hsql --info -a sqlite | jq '.adapters.sqlite'
{
  "distribution": "harlequin",
  "version": "2.10.0",
  "capabilities": {
    "implements_cancel": true,
    "implements_catalog_search": true,
    "implements_read_only": true,
    "implements_validate_sql": false
  },
  "error": null
}

Neither connects to a database, both write JSON to stdout, and -a NAME narrows either one to a single adapter, which is much faster than importing them all.

Installing the Agent Skill

hsql ships an Agent Skill that teaches an agent how to drive it: ask before you assume, keep credentials off the command line, read the catalog before writing SQL, consider a warm session, watch the 500-row limit, branch on the exit code, and hand off to harlequin when a human should drive. hsql --skill installs it, with no network and matched to the version you have:

$ hsql --skill -o ~/.claude/skills/hsql/          # for you, in every project
$ hsql --skill -o .claude/skills/hsql/            # for this repo, committed with it
note: wrote 6 files to .claude/skills/hsql: SKILL.md, references/config.md, references/queries.md, references/scripting.md, references/sessions.md, references/troubleshooting.md

With no -o, hsql --skill writes SKILL.md to stdout, so you can read it before you install it.

In Claude Code, the same skill is a plugin in Harlequin's repository:

/plugin marketplace add tconbeer/harlequin
/plugin install hsql@harlequin

Keep Reading at harlequin.sh

Visit harlequin.sh for an overview of features and full documentation.

Getting Help

To view all command-line options for Harlequin and all installed adapters, after installation, simply type:

hsql --help

GitHub Discussions are a good place to ask questions, request features, and say hello.

GitHub Issues are the best place to report bugs.

Sponsoring Harlequin and hsql

Please consider sponsoring Harlequin's author, so he can continue to dedicate time to hsql.

Contributing

Thanks for your interest in Harlequin! Harlequin and hsql are primarily maintained by Ted Conbeer, but he welcomes all contributions!

Please see CONTRIBUTING.md for more information.

Differences from psql

-c, -f, -t, -A and -x mean what they mean in psql, so hsql -tAc "select count(*) from orders" prints a bare number, exactly as it would there. These do not:

psql hsql
-P --pset, an output setting --profile, a config-file profile
Field separator -F --csv, --format tsv, or any other --format
Listing databases -l --catalog
Describing an object \d, \dt --catalog --path, --catalog-search
Stopping on the first error -v ON_ERROR_STOP=1 --on-error stop, which is the default
One transaction -1 write begin and commit in your script
-o a file for query output a file, or a directory that gets one file per result set
Connection flags -h, -p, -U, built in the adapter's, so hsql --help -a postgres lists them
Row limits none 500 rows by default; --limit -1 removes it
Suppressing chatter -q nothing to suppress: stdout is only ever results
Exit codes 1 its own error, 2 connection, 3 script error 1 query error, 2 usage/config, 3 connection, 4 timeout

Metadata

Release files for hsql 2.16.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hsql 2.16.0
File Size Uploaded
hsql-2.16.0.tar.gz 11.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for hsql 2.16.0
File Interpreter ABI Platform
hsql-2.16.0-py3-none-any.whl Python 3 none any Details

Total release size: 20.4 kB

Release files / hsql-2.16.0.tar.gz

Download URL hsql-2.16.0.tar.gz
Size 11.2 kB
Tags Source
SHA-256 checksum
How to use checksums
a819586c7d6fdb18c013bdd5003b3d9f186bc8e8ecd37533f7ece51c3740eaf3
BLAKE2b-256 checksum
How to use checksums
51beb4442beef4017a21d622e1df23b75a4c64ecd4a3540d4ec6e306ecdb2cc0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release files / hsql-2.16.0-py3-none-any.whl

Download URL hsql-2.16.0-py3-none-any.whl
Size 9.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
97a46b6ec6c8e0cdb64f9d3003ea192f61fb5c9537806a806c38d38ea56f0298
BLAKE2b-256 checksum
How to use checksums
e89071203333fb2eaf8999ff798844828b1aeb98f234d1ccac39dbf6e53f62c8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2.16.0 This release

2 release files

2.15.0

2 release files

2.14.0

2 release files

2.12.2

2 release files

2.12.1

2 release files

2.12.0

2 release files

2.11.0

2 release files

2.10.0

2 release files

2.9.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page