CLI reference
Reference · CLI
The askdb binary covers the full local workflow: scaffolding a project, introspecting a database, authoring schema artifacts, and asking questions. All commands use the same @askdb/core pipeline as the library.
Install
Section titled “Install”npm install askdbnpx askdb --helppnpm add askdbpnpm exec askdb --helpyarn add askdbyarn askdb --helpFor global install:
npm install -g askdbaskdb --helppnpm add -g askdbaskdb --helpyarn global add askdbaskdb --helpTo introspect a Prisma schema file, also install @askdb/prisma, in the same place or in the project you run askdb from. askdb loads it only for --engine prisma, and on pnpm 11 it needs its @prisma/engines build approved (see the Prisma guide).
Help, version, and first run
Section titled “Help, version, and first run”| Flag | Description |
|---|---|
-h, --help | Print usage and the list of commands. Every command also takes --help for its own flags (for example askdb ask --help), and askdb help <command> prints the same text. |
-V, --version | Print the installed askdb version. |
Every command except studio and its alias enrich takes a flag’s value as --flag value or --flag=value, and prints its help on stdout and exits 0 for --help (or -h), wherever it appears on the line.
Commands that don’t read the config work in any directory, including one with no askdb.config.*: --help, --version, help, init, bundle, any command’s --help and --version, introspect templates, rag with no arguments (it prints help), and studio.
ask, introspect, and the rag commands need askdb.config.* (or .config/askdb.*) in the current directory. Without one they print No askdb.config.* or .config/askdb.* found in <cwd>. Run `npx askdb init` to create one. and exit 1.
Commands
Section titled “Commands”askdb init
Section titled “askdb init”Scaffolds a tailored askdb.config.ts and installs only the packages your setup needs. In a TTY it opens a short wizard; in CI or with --yes it runs silently. With --database prisma it installs @askdb/prisma at the version of the askdb running it, plus ai and zod, which Yarn Plug’n’Play needs in the project: ai at the newest major @askdb/core accepts, and zod at core’s range. It installs those, and a database driver, only when your package.json doesn’t list them yet, so a range you chose, such as ai@^6, stays. With pnpm 10.4 or later it approves the @prisma/engines build (pnpm add --allow-build=@prisma/engines). Older pnpm has no --allow-build, so it installs without it.
init installs into the current directory if it has a package.json, or else into the nearest directory above it that has one. It picks the package manager from the first of these that names one:
- A lockfile in that directory or a parent:
pnpm-lock.yaml,bun.lockorbun.lockb,yarn.lock, thenpackage-lock.json. An existing lockfile always wins. - The nearest
package.jsonthat declares a manager: itspackageManagerfield ([email protected]), thendevEngines.packageManager(pnpm initwrites it). - The tool that ran
init:pnpm dlx,yarn dlx,npxorbunx(fromnpm_config_user_agent). - npm.
init doesn’t install at a workspace root, or when it finds no package.json. A workspace root is a directory whose pnpm-workspace.yaml lists at least one package under packages, or whose package.json has workspaces. A pnpm-workspace.yaml that holds only settings, such as pnpm’s minimumReleaseAgeExclude, doesn’t make one. init reads the file as plain block YAML, and one written in another form, such as JSON, counts as a workspace root. Where init doesn’t install, or when the install fails, it prints the command to run yourself, for the package manager it picked and with the @prisma/engines approval when it applies, for example pnpm add --allow-build=@prisma/engines @askdb/config@<version> @askdb/prisma@<version> ai@<range> zod@<range>.
npx askdb@latest init # wizard in a terminalnpx askdb@latest init --yes # non-interactive, Postgres + OpenAI defaultsGeneral flags
| Flag | Description |
|---|---|
-y, --yes | Accept defaults without prompts. |
--interactive | Force wizard mode; errors if stdin/stdout are not TTYs. |
--no-interactive | Never prompt; use defaults or flags. |
--path <file> | Write the config to a custom path. Default askdb.config.ts. |
-f, --force | Overwrite an existing file. |
--skip-install | Only write the file; don’t install packages. |
Database flags
| Flag | Description |
|---|---|
--database <db> | postgres | mysql | sqlite | sqlserver | prisma (default postgres) |
--connection-env <name> | Env var name for the connection URL (default DATABASE_URL; not used with sqlite, which reads --sqlite-file). |
--sqlite-file <path-or-env> | SQLite file path, or an env var name (UPPER_SNAKE_CASE, e.g. SQLITE_FILE) to read the path from. Anything else, such as data.db or ../db/app.db, is written as a literal path. |
--prisma-schema <path> | Path to schema.prisma. |
--schema-out <dir> | Schema output directory (default ./askdb). |
AI flags
| Flag | Description |
|---|---|
--ai-provider <name> | openai | anthropic | google | azure | foundry | gateway (default openai) |
--ai-key-env <name> | Env var name for the API key. |
--ai-model-env <name> | Env var name for the model override. The generated config reads it into ai.language.model. |
RAG flags
Experimental, like the rag block they write: a minor release of askdb may change them (ADR 0030).
| Flag | Description |
|---|---|
--rag-store <name> | file | memory | pgvector (default file) |
--pgvector-env <name> | Env var name for the pgvector connection URL (default PGVECTOR_URL). |
Studio flags
Experimental, like the studio block they write: a minor release of askdb may change them (ADR 0030).
| Flag | Description |
|---|---|
--studio-execute | Enable Studio execute (query playground): writes a studio.execute block with enabled: true. Off by default. Execute reads the same connection env var as introspection: --connection-env, default DATABASE_URL. With sqlite, it reads the same file as --sqlite-file. With prisma, execute targets Postgres and reads DATABASE_URL. |
--no-studio-execute | Disable Studio execute (default). |
init generates only the config branches you selected — SQL Server users won’t see a pg import, SQLite users get a file: field instead of DATABASE_URL, and Prisma users don’t get a live DB driver unless they explicitly enable Studio execute. It does not create a .env; it writes an .env.example (unless one exists) listing every variable the config reads, database variables first. Each variable is an empty NAME= line under a # comment that says what it’s for, and for a connection URL, its format. The SQL Server format leaves out TrustServerCertificate, so the server’s certificate is checked by default. A second comment line says to add TrustServerCertificate=True; for a development server with a self-signed certificate, such as the SQL Server Docker image (see Fix the self-signed certificate error). For azure and foundry the list includes AZURE_RESOURCE_NAME, the subdomain of your https://<name>.openai.azure.com endpoint. Studio’s guided setup writes the same files for the same answers.
init checks the env var names and paths you pass before it writes anything. A name for --connection-env, --ai-key-env, --ai-model-env or --pgvector-env must use uppercase letters, digits and underscores, and not start with a digit (DATABASE_URL, _PRIVATE_URL). A path for --sqlite-file, --prisma-schema or --schema-out must not contain control characters, such as a newline. Otherwise init exits 1 with an error that names the flag, for example --connection-env must be an environment variable name like DATABASE_URL: ….
askdb introspect
Section titled “askdb introspect”Reads your database (or a Prisma schema file) and writes an AskDB schema artifact to disk.
askdb introspect \ --url "$DATABASE_URL" \ --out ./my-app.schema \ --schema-id my-app| Flag | Description |
|---|---|
--url <connstr> | Live database connection string (a file path for SQLite). Falls back to introspection.providerConfig.<engine>.databaseUrl (SQLite: .file) in askdb.config.ts when introspection.provider names the engine. |
--engine <id> | Engine: postgres, mysql, sqlite, sqlserver, prisma. Falls back to introspection.provider, then postgres. |
--prisma-schema <path> | .prisma file or directory. With --engine prisma. |
--from-export <bundle-dir> | Introspect from a previously exported bundle directory instead of a live connection. Postgres only. Mutually exclusive with --url. The directory holds a manifest.json and the result of each query that askdb introspect templates prints. Introspect without a connection walks through it. |
--out <dir> | Output directory for the schema artifact. Falls back to introspection.outputDir in askdb.config.ts. If schema.json there is a symbolic link, the command fails instead of writing to the file the link points at; replace the link with a regular file. |
--schema-id <id> | Identifier embedded in schema.json. Default: the name of the --out or --diff directory without a .schema suffix (./my-app.schema gives my-app), else introspected. |
--schemas <list> | Comma-separated database schemas to include (e.g. public,sales). For MySQL/MariaDB these are databases; each becomes its own namespace. Overrides introspection.schemas in askdb.config.ts. Without either, Postgres and SQL Server read every non-system schema, and MySQL and MariaDB read the database in the connection URL. |
--exclude-schemas <list> | Comma-separated database schemas to skip. |
--tables <list> | Comma-separated table patterns to include, matched against <schema>.<table>: * matches any run of characters, including dots, and ? matches one character. Write public.orders or *.orders, not orders, which matches no table. A pattern that matches nothing emits an askdb.introspect.warning log event (see -v). Repeatable — values accumulate. |
--print | Print the introspection result without writing. |
--diff <existing> | Diff against an existing schema artifact. |
Pass exactly one of --out, --print and --diff. With none of them, --out falls back to introspection.outputDir.
When the source has no tables or views to write, after --schemas, --exclude-schemas and --tables, askdb introspect exits 1 and writes nothing, with --out, --print and --diff alike. An artifact with no tables can’t be loaded, so an existing schema.json and tables/*.md stay as they were. The message names the schema id, the engine and the filters you passed, and lists the likely causes. See the troubleshooting entry.
askdb introspect also accepts the shared logging flags.
Re-introspecting doesn’t change tenant-policy.md, so a renamed or dropped column can leave the policy naming IDs the new schema.json lacks. When the --out directory holds a tenant-policy.md, askdb introspect loads the directory after writing and prints on stderr what no longer matches. Under enforcement: strict that is the error loadSchema() will throw until you update the policy. The exit code stays 0, because the write succeeded. See When the policy and the schema disagree.
Live introspection requires the matching engine driver (pg, mysql2, better-sqlite3, or mssql) in the project running askdb, or supplied in the same one-off command:
pnpm dlx -p askdb -p pg askdb introspect --engine postgres --url "$DATABASE_URL"npx -p askdb -p mssql askdb introspect --engine sqlserver --url "$SQLSERVER_URL"Prisma introspection needs @askdb/prisma the same way: in the project running askdb, or in the one-off command (npx -p askdb -p @askdb/prisma askdb introspect --engine prisma). Without it, askdb introspect --engine prisma exits 1 and names the package to install.
askdb introspect templates
Section titled “askdb introspect templates”askdb introspect templates --engine postgres > askdb-templates.sqlPrints the SQL queries that --from-export reads the results of, each headed by a -- <name> comment. Run them against the database yourself, for example where askdb can’t connect, and save each result in the bundle directory. Each query takes two text[] parameters, $1 (schemas to include) and $2 (schemas to skip), where NULL means no filter: replace them before you run the queries in psql. Introspect without a connection walks through it. Only postgres has templates; another engine exits 1 and names the engines that do. It reads no config, so it works in any directory, and --engine defaults to postgres rather than introspection.provider.
askdb enrich
Section titled “askdb enrich”Experimental: a minor release of askdb may change this command (ADR 0030).
Alias of askdb studio: opens Studio for authoring per-table descriptions, aliases, concepts, and sensitive markers. It takes the same flags as askdb studio: -s, --schema, --port and --host.
askdb enrich --schema ./my-app.schema --port 5600askdb studio
Section titled “askdb studio”Experimental: Studio stays 0.x, so a minor release of askdb may change this command (ADR 0030).
Launches Studio — the browser-based authoring UI — pointed at your schema artifact.
askdb studio --schema ./my-app.schema| Flag | Description |
|---|---|
-s, --schema <path> | Path to the schema artifact. |
--port <n> | Override the listen port (default 5556; or set studio.listen.port in config). |
--host <host> | Override the bind host (default 127.0.0.1; or set studio.listen.host in config). |
-V, --version | Print the installed @askdb/studio version. |
Requires @askdb/studio to be installed (lazy-imported on first use). Studio schema browsing, enrichment, and SQL generation do not require a database driver; the optional Playground execute path requires the matching driver (pg, mysql2, better-sqlite3, or mssql). askdb init --studio-execute installs the correct driver automatically.
askdb studio is the one command that completes setup: run it with no config (or no schema artifact) and Studio opens a guided setup wizard in the browser that scaffolds both. An explicit --schema pointing at a missing directory is an error, since you asked for a specific artifact.
If askdb.config.* exists but fails to load (a syntax error, or its dependencies aren’t installed yet), Studio prints a warning with the load error and starts without the config. The setup wizard never overwrites an existing config file; fix it and restart Studio.
askdb bundle
Section titled “askdb bundle”Experimental: a minor release of askdb may change this command (ADR 0030).
askdb bundle <schema-dir> --out <bundle.json>Both are required. Without either, bundle prints that usage line on stderr and exits 1. askdb bundle --help prints its help on stdout and exits 0.
Writes the entire schema artifact (physical layer + enrichment + tenant policy) as a single JSON file for distribution.
Before it writes anything, it checks the bundle with the loader ask() uses (through @askdb/enrich’s bundleSchemaDirectory()), so it refuses whatever loadSchema() refuses. That includes a file name in the wrong case, such as Tenant-Policy.md, an optional file that exists but can’t be read, an invalid schema.json, front-matter that isn’t YAML or doesn’t parse, a tenant policy that doesn’t fit schema.json, and two table files with the same id. It then exits with status 1 and the loader’s error, and writes nothing: a file already at --out stays as it was. So a bundle it writes always loads. The Schema artifact format describes the bundle. After writing, it prints a Warning: line on stderr for each loader warning, a tenant policy’s included, and still exits 0.
askdb bundle ./my-app.schema --out ./my-app.schema.bundle.jsonaskdb ask
Section titled “askdb ask”Generates SQL from a natural-language question against a loaded schema artifact.
askdb ask \ --schema ./my-app.schema \ --question "Top 5 customers by lifetime value"| Flag | Description |
|---|---|
-s, --schema <path> | Path to the schema artifact. Optional — defaults to the configured introspection.outputDir, or ./askdb/. |
-q, --question <text> | The natural-language question (required). |
--mode <mode> | Operating mode: schema_only or bounded_results. It can only tighten config modes.askdbMode (default schema_only): --mode bounded_results runs schema_only unless the config sets bounded_results. |
--explain | After the SQL, print a JSON block describing the read-only checks it ran. |
--omit-sensitive-from-prompt | Exclude sensitive columns from the prompt entirely. |
--mock-sql <sql> | Deterministic output for tests — bypasses the live model call. Overrides dev.mockSql in askdb.config.ts, which does the same. With neither, askdb ask needs an AI key: without one it prints which key is missing and exits 1. |
askdb ask also accepts the shared logging flags.
Output
Section titled “Output”Stdout has the checked SQL after a -- sql -- line. With --explain, a -- explain -- line and that JSON follow:
-- sql --SELECT customer_id, SUM(total) AS lifetime_value FROM orders GROUP BY customer_id ORDER BY lifetime_value DESC LIMIT 5-- explain --{ … }Everything else goes to stderr, so stdout stays parseable: errors, Note: lines (a dialect note, or a sensitive-scope note), a Warning: line for each loader warning the schema has (a tenant policy’s included), and the sensitive warning. Execution is not part of the CLI.
If the generated SQL references identifiers your schema marks sensitive, askdb ask prints a Warning: line to stderr listing them and still prints the SQL. The check is validateSensitiveReferences from @askdb/core, run by ask() in warn mode; embedding hosts can switch it to strict, rely on bindPreparedQuery() re-running it on a rebind, or call it directly on cached SQL.
Dialect
Section titled “Dialect”You never pass a SQL dialect. It resolves from dialect in askdb.config.ts when set, otherwise from the engine the schema artifact records (its provider), otherwise postgres when the artifact records none. An artifact whose provider isn’t a built-in dialect id fails with DialectNotSupportedError and exit 1; set dialect in the config to use one of the built-in dialects for it. When the config’s dialect and the artifact’s engine differ, the config wins and a Note: line on stderr says so.
Shared logging flags
Section titled “Shared logging flags”askdb ask, askdb introspect, askdb rag index, and askdb rag query all accept the same structured-logging flags:
| Flag | Description |
|---|---|
-v, --verbose | Emit structured JSON log events (level info) to stderr. |
--log-level <level> | trace, debug, info, warn, error, fatal, or silent. Default reads logging.level from askdb.config.ts. |
--log-file <path> | Append structured JSON logs to a file. Default reads logging.logFile. |
--log-stdout | Mirror structured JSON logs to stdout. Default reads logging.logStdout. |
--correlation-id <id> | Tag every log event with a correlation ID of your choosing. Default reads logging.correlationId, else a new UUID. |
askdb rag
Section titled “askdb rag”Experimental: @askdb/rag stays 0.x, so a minor release of askdb may change these commands (ADR 0030).
Chunks, embeds, and queries a schema artifact for retrieval, with three commands: setup-store, index, and query.
It replaces askdb-rag. @askdb/rag no longer ships the askdb-rag binary: run askdb rag from the askdb package. askdb rag reads its store from askdb.config.*, where askdb-rag always defaulted to the file store, so pass --store file or set rag.store to keep that store. See Replace the askdb-rag binary with askdb rag.
Config and flags
Section titled “Config and flags”askdb rag --help and askdb rag --version work without a config. The commands read askdb.config.*, and a flag overrides the setting it falls back to.
| Flag | Falls back to |
|---|---|
[schema-dir] (positional, index and query) | introspection.outputDir |
--store memory|file|pgvector | rag.store |
--file-path <path> | rag.storeConfig.file.basePath, else <schema-dir>/schema |
--pg-url <connstr> | rag.storeConfig.pgvector.databaseUrl, read even when rag.store is another store |
--pg-table <name> | rag.storeConfig.pgvector.table, else askdb_rag_chunks |
--embedder mock|ai | rag.embedder: "ai" selects ai, and "mock" selects mock |
--embedder-model <id> | ai.embedding.model |
--dimensions <n> | See Vector width |
rag.storeConfig.pgvector.indexStrategy sets the index setup-store and index create on pgvector (default hnsw), whatever rag.store is. A store flag for a store the run doesn’t use, such as --file-path when the store is pgvector or --pg-url when it’s the file store, is an error rather than ignored. setup-store provisions only pgvector, whatever rag.store is, so it refuses --store memory, --store file, --file-path and a [schema-dir].
Embedders
Section titled “Embedders”mock: a deterministic lexical hash, 64 dimensions unless--dimensions. It needs no API key, so it’s the one to use in CI.ai: theai.embeddingmodel, with the key from its connection inai.providerConfig. It needsrag.embedder: "ai", since that’s the only setting under which AskDB resolvesai.embedding, so--embedder aican’t overriderag.embedder: "mock".--embedder-modeloverridesai.embedding.model, and--dimensionsrequests a width.--embedder-modelwith themockembedder is an error.
An index built with ai records the embedder id ai-sdk:<provider>:<model>:<dimensions>, ending in default when no width was requested. Studio uses the same id, so either accepts an index the other built.
--embedder openai and --embedder ai-sdk were removed, as the same rag.embedder values were. Each fails with an error naming --embedder ai and ai.embedding: see Replace the removed --embedder values.
--api-key was removed: a key on the command line leaks through shell history and the process list. Set the key on a connection in ai.providerConfig instead.
Vector width
Section titled “Vector width”AskDB assumes no embedding width it doesn’t know. The only width the CLI knows is the mock embedder’s 64, because that embedder is AskDB’s own.
setup-storerequires--dimensions: it has no embedding model to ask.indexandqueryuse--dimensions, elseai.embedding.dimensions(a size to request), else the model’s own width (mock: 64). For a pgvector store with neither set,indexfirst embeds one short text to learn the model’s width.
When a pgvector table already holds vectors of another width, setup-store and index exit 1 before embedding any chunk, and say how to fix it: drop the table and run again, pass --pg-table <name> for a new table, or pass --dimensions <table width> if your embedding model supports that width.
askdb rag setup-store
Section titled “askdb rag setup-store”Provisions the pgvector extension, table, and indexes for @askdb/rag idempotently. Safe to run in CI and deploy steps.
askdb rag setup-store --pg-url "$DATABASE_URL" --dimensions 1536 # text-embedding-3-small's width| Flag | Description |
|---|---|
--dimensions <n> | Vector width of your embedding model, the same value as ai.embedding.dimensions (required). setup-store creates an HNSW index by default, which pgvector supports only up to 2000 dimensions, so for a wider model such as text-embedding-3-large (3072) request 2000 or fewer (OpenAI’s text-embedding-3 models can shorten their vectors), or set rag.storeConfig.pgvector.indexStrategy: "none". |
--pg-url <connstr> | Postgres connection string. Falls back to rag.storeConfig.pgvector.databaseUrl. |
--pg-table <name> | Table name. Falls back to rag.storeConfig.pgvector.table, else askdb_rag_chunks. |
askdb rag index
Section titled “askdb rag index”Chunks and embeds a schema artifact into the store — the CLI equivalent of buildSchemaIndex.
askdb rag index ./my-app.schema --store pgvector| Flag | Description |
|---|---|
[schema-dir] | Schema artifact directory. Falls back to introspection.outputDir. |
--store <id> | memory, file (persists next to the artifact), or pgvector. Falls back to rag.store. |
--embedder <id> | mock or ai. Falls back to rag.embedder. |
--embedder-model <id> | Overrides ai.embedding.model for the ai embedder. |
--dimensions <n> | Vector width: the size the mock embedder writes, or the size the ai embedder requests (falls back to ai.embedding.dimensions). See Vector width. |
--file-path <path> | File store base path; vectors go to <path>.embeddings.{bin,json}. Falls back to rag.storeConfig.file.basePath, else <schema-dir>/schema. |
--pg-url / --pg-table | pgvector connection and table. The table is provisioned (idempotently) and its width checked before indexing. |
--force | Re-embed every chunk, even ones the store already holds. |
Stdout is one JSON object: schemaId, chunksTotal, chunksIndexed (embedded in this run), chunksReused (already in the store), sensitiveExcluded and sensitiveIncluded.
Only chunks the store doesn’t already hold (same id and content hash) are embedded. schema.lock.json records the embedder id, store, and dimensions; a different embedder id (which includes the requested width) re-embeds everything, and a different store gets whatever it doesn’t hold. A different width needs a store with no vectors of the old width: a new or recreated pgvector table, or deleted file-store embeddings files.
askdb rag query
Section titled “askdb rag query”Runs a similarity query against an indexed store — useful for checking what retrieval would hand the model.
askdb rag query ./my-app.schema --question "customer signups" -k 8| Flag | Description |
|---|---|
--question <text> | The query text (required). |
-k, --k <n> | Top-k chunks to return. Default 8. |
--types <list> | Restrict chunk types, comma-separated: table, column, cql, question, concept, relationship, tenant-policy. Any other value is an error. |
[schema-dir], --store, --embedder, --embedder-model, --dimensions, --file-path, --pg-url, --pg-table | As for index, and they must match the index run. query fails if the embedder id or dimensions differ from schema.lock.json, or if the lock records no embedder id. The memory store isn’t supported: it only lives inside one process. |
Stdout is one JSON object: question, k, and results, best match first. Each result has id, score (rounded to 6 decimals), type (the chunk type), schemaId, refs (the schema IDs the chunk is about) and textPreview (the chunk’s first 200 characters).
index and query accept the shared logging flags.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 | Success, including --help and --version. |
1 | Any error: invalid arguments, a missing or broken config, IO, generation, or a SQL check that refused the SQL. |
Every command exits 1 for every error. No command uses another non-zero code. askdb with no command prints its usage to stderr and exits 1.
Errors print to stderr in human form; structured log events (with -v) follow the contract in @askdb/core logging. A usage error is one line naming the problem, such as Unknown option: --x, --out requires a value. or Missing --question <text>., and it’s reported before any config is read. An error repeats a value you passed only when it looks like a name or a number, so a connection string given to the wrong flag isn’t printed back. When a connection fails on several addresses, such as localhost refusing on both ::1 and 127.0.0.1, each address’s error prints on its own line.
An unexpected error, such as a config file that fails to load, prints its message and the hint Set ASKDB_DEBUG=1 to print the stack trace. Set ASKDB_DEBUG to 1 or true to add the stack trace. Any other value, including 0, leaves it off, and the DEBUG variable used by the debug package has no effect.
Read next
Section titled “Read next”© 2026 Yahya Gilany

