Summary
Deliver the primary CLI surface — traceroot sql "<query>" — plus the API-client methods and CSV renderer it composes.
Context
- Lives in the TypeScript CLI repo (
traceroot-cli): Commander.js + native fetch + Vitest. Existing client (src/api/client.ts) does bearer auth + key scrubbing; extend with POST. No CSV lib — hand-roll (RFC-4180).
- Analytical export framing: results are metric/dimension columns only; blobs are excluded.
- Can develop against the frozen public SQL API contract before backend merge.
Implementation
src/api/client.ts — types SqlColumn/SqlQueryRequest/SqlQueryResponse/SqlSchemaResponse; sqlQuery(body) (POST /api/v1/public/sql) + sqlSchema() (GET) reusing header construction + key scrubbing (generalize request<T> to method+body).
src/render/csv.ts — renderCsv(headers, rows): RFC-4180 quoting for ,/"/newline; null/undefined → empty; objects JSON-stringified; trailing \n.
src/commands/sql.ts — registerSql(program): create the sql group, register the schema subcommand before the default [query] action (so sql schema resolves as a subcommand).
src/commands/sql/query.ts — registerSqlQuery + runSqlQuery: optional [query], -f/--file, --csv, --output <file>, --max-rows <n> (forwarded to the request body so the truncation hint is honest), reuse global --json. Exactly one of positional/--file required (else error); --json+--csv is an error. Output: table (default) / JSON line / CSV; --output writes any format to a file. On truncated in table/CSV mode, stderr warning (mentions --max-rows/LIMIT); JSON mode relies on the truncated field. Wrap file read/write errors as a readable CliError. Error/exit handling follows the CLI's existing conventions (the established reportError/CliError path) — do not introduce a new numeric exit-code taxonomy unless the CLI already defines one. Surface failures as readable messages: validation rejections print the server detail; resource-cap messages add a LIMIT/time-filter hint.
- Register the group in
src/commands/index.ts.
Test plan
tests/api/sql-client.test.ts: sqlQuery POSTs to the right URL with bearer auth; key scrubbing preserved; params serialized into the body.
tests/render/csv.test.ts: header+rows; comma/quote/newline escaping; null/undefined → empty.
tests/commands/sql-query.test.ts (via runSqlQuery + fake client + StringSink): default table; --json line; --csv; --json+--csv rejects; positional+--file and neither reject; --output writes file; truncated warns on stderr (table/CSV) but not JSON. One runCli surface test (unknown flag is rejected per the CLI's normal error handling).
Acceptance criteria
Summary
Deliver the primary CLI surface —
traceroot sql "<query>"— plus the API-client methods and CSV renderer it composes.Context
traceroot-cli): Commander.js + nativefetch+ Vitest. Existing client (src/api/client.ts) does bearer auth + key scrubbing; extend with POST. No CSV lib — hand-roll (RFC-4180).Implementation
src/api/client.ts— typesSqlColumn/SqlQueryRequest/SqlQueryResponse/SqlSchemaResponse;sqlQuery(body)(POST/api/v1/public/sql) +sqlSchema()(GET) reusing header construction + key scrubbing (generalizerequest<T>to method+body).src/render/csv.ts—renderCsv(headers, rows): RFC-4180 quoting for,/"/newline; null/undefined → empty; objects JSON-stringified; trailing\n.src/commands/sql.ts—registerSql(program): create thesqlgroup, register theschemasubcommand before the default[query]action (sosql schemaresolves as a subcommand).src/commands/sql/query.ts—registerSqlQuery+runSqlQuery: optional[query],-f/--file,--csv,--output <file>,--max-rows <n>(forwarded to the request body so the truncation hint is honest), reuse global--json. Exactly one of positional/--filerequired (else error);--json+--csvis an error. Output: table (default) / JSON line / CSV;--outputwrites any format to a file. Ontruncatedin table/CSV mode, stderr warning (mentions--max-rows/LIMIT); JSON mode relies on thetruncatedfield. Wrap file read/write errors as a readableCliError. Error/exit handling follows the CLI's existing conventions (the establishedreportError/CliErrorpath) — do not introduce a new numeric exit-code taxonomy unless the CLI already defines one. Surface failures as readable messages: validation rejections print the serverdetail; resource-cap messages add aLIMIT/time-filter hint.src/commands/index.ts.Test plan
tests/api/sql-client.test.ts:sqlQueryPOSTs to the right URL with bearer auth; key scrubbing preserved; params serialized into the body.tests/render/csv.test.ts: header+rows; comma/quote/newline escaping; null/undefined → empty.tests/commands/sql-query.test.ts(viarunSqlQuery+ fake client +StringSink): default table;--jsonline;--csv;--json+--csvrejects; positional+--fileand neither reject;--outputwrites file;truncatedwarns on stderr (table/CSV) but not JSON. OnerunClisurface test (unknown flag is rejected per the CLI's normal error handling).Acceptance criteria
traceroot sql "<query>"prints a table by default;--jsonone JSON line;--csvRFC-4180;--output <file>writes any format;-f/--filereads the query;--max-rows <n>forwarded.--filerequired;--json+--csvis an error; truncation warns on stderr (table/CSV), self-describing in JSON.LIMIT/time-filter hint.sqlQuery/sqlSchemaPOST/GET with bearer auth; key scrubbing preserved;renderCsvhandles commas/quotes/newlines/null.npm run buildclean; no regressions.