Skip to content

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.

Terminal window
npm install askdb
npx askdb --help

For global install:

Terminal window
npm install -g askdb
askdb --help

To 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).

FlagDescription
-h, --helpPrint 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, --versionPrint 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.

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:

  1. A lockfile in that directory or a parent: pnpm-lock.yaml, bun.lock or bun.lockb, yarn.lock, then package-lock.json. An existing lockfile always wins.
  2. The nearest package.json that declares a manager: its packageManager field ([email protected]), then devEngines.packageManager (pnpm init writes it).
  3. The tool that ran init: pnpm dlx, yarn dlx, npx or bunx (from npm_config_user_agent).
  4. 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>.

Terminal window
npx askdb@latest init # wizard in a terminal
npx askdb@latest init --yes # non-interactive, Postgres + OpenAI defaults

General flags

FlagDescription
-y, --yesAccept defaults without prompts.
--interactiveForce wizard mode; errors if stdin/stdout are not TTYs.
--no-interactiveNever prompt; use defaults or flags.
--path <file>Write the config to a custom path. Default askdb.config.ts.
-f, --forceOverwrite an existing file.
--skip-installOnly write the file; don’t install packages.

Database flags

FlagDescription
--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

FlagDescription
--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).

FlagDescription
--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).

FlagDescription
--studio-executeEnable 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-executeDisable 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: ….

Reads your database (or a Prisma schema file) and writes an AskDB schema artifact to disk.

Terminal window
askdb introspect \
--url "$DATABASE_URL" \
--out ./my-app.schema \
--schema-id my-app
FlagDescription
--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.
--printPrint 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:

Terminal window
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.

Terminal window
askdb introspect templates --engine postgres > askdb-templates.sql

Prints 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.

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.

Terminal window
askdb enrich --schema ./my-app.schema --port 5600

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.

Terminal window
askdb studio --schema ./my-app.schema
FlagDescription
-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, --versionPrint 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.

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.

Terminal window
askdb bundle ./my-app.schema --out ./my-app.schema.bundle.json

Generates SQL from a natural-language question against a loaded schema artifact.

Terminal window
askdb ask \
--schema ./my-app.schema \
--question "Top 5 customers by lifetime value"
FlagDescription
-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.
--explainAfter the SQL, print a JSON block describing the read-only checks it ran.
--omit-sensitive-from-promptExclude 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.

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.

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.

askdb ask, askdb introspect, askdb rag index, and askdb rag query all accept the same structured-logging flags:

FlagDescription
-v, --verboseEmit 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-stdoutMirror 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.

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.

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.

FlagFalls back to
[schema-dir] (positional, index and query)introspection.outputDir
--store memory|file|pgvectorrag.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|airag.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].

  • mock: a deterministic lexical hash, 64 dimensions unless --dimensions. It needs no API key, so it’s the one to use in CI.
  • ai: the ai.embedding model, with the key from its connection in ai.providerConfig. It needs rag.embedder: "ai", since that’s the only setting under which AskDB resolves ai.embedding, so --embedder ai can’t override rag.embedder: "mock". --embedder-model overrides ai.embedding.model, and --dimensions requests a width. --embedder-model with the mock embedder 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.

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-store requires --dimensions: it has no embedding model to ask.
  • index and query use --dimensions, else ai.embedding.dimensions (a size to request), else the model’s own width (mock: 64). For a pgvector store with neither set, index first 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.

Provisions the pgvector extension, table, and indexes for @askdb/rag idempotently. Safe to run in CI and deploy steps.

Terminal window
askdb rag setup-store --pg-url "$DATABASE_URL" --dimensions 1536 # text-embedding-3-small's width
FlagDescription
--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.

Chunks and embeds a schema artifact into the store — the CLI equivalent of buildSchemaIndex.

Terminal window
askdb rag index ./my-app.schema --store pgvector
FlagDescription
[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-tablepgvector connection and table. The table is provisioned (idempotently) and its width checked before indexing.
--forceRe-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.

Runs a similarity query against an indexed store — useful for checking what retrieval would hand the model.

Terminal window
askdb rag query ./my-app.schema --question "customer signups" -k 8
FlagDescription
--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-tableAs 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.

CodeMeaning
0Success, including --help and --version.
1Any 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.