@batadata/cli 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -38,5 +38,17 @@ export declare function branchSetProtected(ref: string | undefined, wantProtecte
38
38
  */
39
39
  export declare function branchCheckout(args: string[]): Promise<void>;
40
40
  export declare function studio(): Promise<void>;
41
+ /**
42
+ * Pull `--branch <ref>` / `--at <ISO-timestamp|LSN>` flags (and their `=` forms)
43
+ * out of the query args and return them plus the remaining args, which join into
44
+ * the SQL string. Keeps `db query` order-independent (flags can sit before or
45
+ * after the SQL) and consistent with how global flags are parsed.
46
+ */
47
+ export declare function parseQueryFlags(args: string[]): {
48
+ branchId?: string;
49
+ at?: string;
50
+ projectId?: string;
51
+ rest: string[];
52
+ };
41
53
  export declare function query(args?: string[]): Promise<void>;
42
54
  export declare function handleDb(args: string[]): Promise<void>;
@@ -499,9 +499,10 @@ export async function studio() {
499
499
  * the SQL string. Keeps `db query` order-independent (flags can sit before or
500
500
  * after the SQL) and consistent with how global flags are parsed.
501
501
  */
502
- function parseQueryFlags(args) {
502
+ export function parseQueryFlags(args) {
503
503
  let branchId;
504
504
  let at;
505
+ let projectId;
505
506
  const rest = [];
506
507
  for (let i = 0; i < args.length; i++) {
507
508
  const arg = args[i];
@@ -517,11 +518,17 @@ function parseQueryFlags(args) {
517
518
  else if (arg.startsWith("--at=")) {
518
519
  at = arg.slice("--at=".length);
519
520
  }
521
+ else if (arg === "--project") {
522
+ projectId = args[++i];
523
+ }
524
+ else if (arg.startsWith("--project=")) {
525
+ projectId = arg.slice("--project=".length);
526
+ }
520
527
  else {
521
528
  rest.push(arg);
522
529
  }
523
530
  }
524
- return { branchId, at, rest };
531
+ return { branchId, at, projectId, rest };
525
532
  }
526
533
  /**
527
534
  * Resolve the branch a query should target: an explicit `--branch <id-or-name>`
@@ -621,7 +628,7 @@ async function runTimeTravelQuery(opts) {
621
628
  }
622
629
  export async function query(args = []) {
623
630
  const jsonMode = isJsonMode();
624
- const { branchId: branchFlag, at, rest } = parseQueryFlags(args);
631
+ const { branchId: branchFlag, at, projectId: projectFlag, rest } = parseQueryFlags(args);
625
632
  // Branch precedence: an explicit --branch wins, else the branch pinned by
626
633
  // `bata db branch checkout` in .batadata/project.json, else the primary.
627
634
  const branchId = resolveBranchId(branchFlag).branchId;
@@ -631,9 +638,10 @@ export async function query(args = []) {
631
638
  }
632
639
  const token = requireToken();
633
640
  const config = loadConfig();
634
- const projectId = resolveProjectId().projectId;
641
+ // Project precedence: --project flag > .batadata link > config default.
642
+ const projectId = resolveProjectId(projectFlag).projectId;
635
643
  if (!projectId) {
636
- emitError("NO_PROJECT", "No default project set.", "Set one with: bata projects info <id>");
644
+ emitError("NO_PROJECT", "No project for db query.", "Pass --project <id>, or run `bata link <project>` to set a default.");
637
645
  }
638
646
  // `--at` is a time-travel query: fork a hidden ephemeral branch AS OF the point
639
647
  // and run the SQL there. Classify the point up front so a bad --at fails fast.
@@ -3,4 +3,24 @@ export declare function ormLabel(q: {
3
3
  model?: string | null;
4
4
  action?: string | null;
5
5
  }): string;
6
+ /**
7
+ * Pull `--branch <ref>` / `--project <id>` (and their `=`-joined forms) out of
8
+ * `schema dump` args. Both flags are VALUE-consuming: a silently-ignored
9
+ * `--project` would target the wrong project (the exact db-query bug this
10
+ * command must not repeat). Remaining positionals are returned in `rest`.
11
+ */
12
+ export declare function parseSchemaDumpArgs(args: string[]): {
13
+ branch?: string;
14
+ projectId?: string;
15
+ rest: string[];
16
+ };
17
+ /**
18
+ * Pull `--project <id>` out of `schema diff` args, leaving the two positional
19
+ * branch refs (`<from> <to>`) in `rest`. Consuming the flag value keeps a
20
+ * `--project` from being mistaken for a branch ref.
21
+ */
22
+ export declare function parseSchemaDiffArgs(args: string[]): {
23
+ projectId?: string;
24
+ rest: string[];
25
+ };
6
26
  export declare function handleSchema(args: string[]): Promise<void>;
@@ -1,9 +1,9 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { api, apiError } from "../api.js";
3
- import { requireToken, isJsonMode } from "../config.js";
3
+ import { requireToken, isJsonMode, loadConfig } from "../config.js";
4
4
  import { colors, log, json, spinner, table, heading } from "../utils/logger.js";
5
- import { emitError } from "../utils/errors.js";
6
- import { resolveProjectId } from "../link.js";
5
+ import { emitError, isRetryable } from "../utils/errors.js";
6
+ import { resolveProjectId, resolveBranchId } from "../link.js";
7
7
  const NOT_IMPLEMENTED_HINT = "Use `bata schema check <file.sql> --fail-on breaking` to gate migrations today.";
8
8
  /**
9
9
  * Unimplemented schema subcommand. Never exits 0 for a no-op: emits the
@@ -178,38 +178,253 @@ function renderReport(v) {
178
178
  log(` ${colors.dim(`· ${n}`)}`);
179
179
  log();
180
180
  }
181
+ /**
182
+ * Pull `--branch <ref>` / `--project <id>` (and their `=`-joined forms) out of
183
+ * `schema dump` args. Both flags are VALUE-consuming: a silently-ignored
184
+ * `--project` would target the wrong project (the exact db-query bug this
185
+ * command must not repeat). Remaining positionals are returned in `rest`.
186
+ */
187
+ export function parseSchemaDumpArgs(args) {
188
+ let branch;
189
+ let projectId;
190
+ const rest = [];
191
+ for (let i = 0; i < args.length; i++) {
192
+ const a = args[i];
193
+ if (a === "--branch")
194
+ branch = args[++i];
195
+ else if (a.startsWith("--branch="))
196
+ branch = a.slice("--branch=".length);
197
+ else if (a === "--project")
198
+ projectId = args[++i];
199
+ else if (a.startsWith("--project="))
200
+ projectId = a.slice("--project=".length);
201
+ else
202
+ rest.push(a);
203
+ }
204
+ return { branch, projectId, rest };
205
+ }
206
+ /**
207
+ * Pull `--project <id>` out of `schema diff` args, leaving the two positional
208
+ * branch refs (`<from> <to>`) in `rest`. Consuming the flag value keeps a
209
+ * `--project` from being mistaken for a branch ref.
210
+ */
211
+ export function parseSchemaDiffArgs(args) {
212
+ let projectId;
213
+ const rest = [];
214
+ for (let i = 0; i < args.length; i++) {
215
+ const a = args[i];
216
+ if (a === "--project")
217
+ projectId = args[++i];
218
+ else if (a.startsWith("--project="))
219
+ projectId = a.slice("--project=".length);
220
+ else
221
+ rest.push(a);
222
+ }
223
+ return { projectId, rest };
224
+ }
225
+ /** List a project's branches (GET /v1/projects/:id). Null on lookup failure. */
226
+ async function listProjectBranches(projectId, token, teamId) {
227
+ const query = {};
228
+ if (teamId)
229
+ query.team_id = teamId;
230
+ const res = await api.get(`/v1/projects/${projectId}`, token, query);
231
+ if (!res.ok)
232
+ return null;
233
+ return res.data.branches ?? [];
234
+ }
235
+ /**
236
+ * Resolve a `--branch <ref>` (id OR name) to a concrete branch id, or the
237
+ * project's primary branch when no ref is given. Agents pass the name they used
238
+ * with `db branch create`, so name resolution is mandatory (id-only 404s).
239
+ * Exits via emitError with BRANCH_NOT_FOUND when nothing matches.
240
+ */
241
+ async function resolveBranchIdForSchema(projectId, token, teamId, ref) {
242
+ const branches = await listProjectBranches(projectId, token, teamId);
243
+ if (!branches) {
244
+ emitError("API_UNAVAILABLE", "Failed to list branches for this project.", "Check the project id and try again.");
245
+ }
246
+ if (ref) {
247
+ const match = branches.find((b) => b.id === ref || b.name === ref);
248
+ if (!match) {
249
+ emitError("BRANCH_NOT_FOUND", `Branch "${ref}" not found in this project.`, "List branches with: bata db branches --json");
250
+ }
251
+ return match.id;
252
+ }
253
+ const primary = branches.find((b) => b.isPrimary) ?? branches[0];
254
+ if (!primary) {
255
+ emitError("BRANCH_NOT_FOUND", "This project has no branches to introspect.", "Create one with: bata db branch create <name>");
256
+ }
257
+ return primary.id;
258
+ }
259
+ /** Shared handling for a failed schema/diff request: retryable → COMPUTE_STARTING (exit 6). */
260
+ function failSchemaRequest(res, fallback) {
261
+ const body = res.data;
262
+ if (isRetryable({ status: res.status, code: body?.code, message: body?.error })) {
263
+ emitError("COMPUTE_STARTING", apiError(res, fallback), "compute is starting; retry in a few seconds");
264
+ }
265
+ const code = res.status === 401 || res.status === 403 ? "INVALID_KEY"
266
+ : res.status === 404 ? "NO_PROJECT"
267
+ : "CLI_ERROR";
268
+ emitError(code, apiError(res, fallback), "");
269
+ }
270
+ async function schemaDump(args) {
271
+ const jsonMode = isJsonMode();
272
+ const { branch: branchFlag, projectId: projectFlag } = parseSchemaDumpArgs(args);
273
+ const token = requireToken();
274
+ const config = loadConfig();
275
+ // Project precedence: --project flag > .batadata link > config default.
276
+ const projectId = resolveProjectId(projectFlag).projectId;
277
+ if (!projectId) {
278
+ emitError("NO_PROJECT", "No project for schema dump.", "Pass --project <id>, or run `bata link <project>` to set a default.");
279
+ }
280
+ // Branch precedence: explicit --branch wins, else the branch pinned by
281
+ // `bata db branch checkout`, else the project's primary branch.
282
+ const branchRef = resolveBranchId(branchFlag).branchId;
283
+ const s = jsonMode ? null : spinner("Introspecting schema");
284
+ const branchId = await resolveBranchIdForSchema(projectId, token, config.defaultTeam, branchRef);
285
+ const res = await api.get(`/v1/schema/${projectId}`, token, { branch_id: branchId });
286
+ s?.stop();
287
+ if (!res.ok)
288
+ failSchemaRequest(res, "Schema introspection failed");
289
+ if (jsonMode) {
290
+ json(res.data); // server payload is the source of truth
291
+ return;
292
+ }
293
+ renderSchema(res.data.branch_id, res.data.schema);
294
+ }
295
+ async function schemaDiff(args) {
296
+ const jsonMode = isJsonMode();
297
+ const { projectId: projectFlag, rest } = parseSchemaDiffArgs(args);
298
+ const [fromRef, toRef] = rest;
299
+ if (!fromRef || !toRef) {
300
+ emitError("MISSING_ARG", "Two branches are required.", "Usage: bata schema diff <from-branch> <to-branch> [--project <id>]");
301
+ }
302
+ const token = requireToken();
303
+ const config = loadConfig();
304
+ const projectId = resolveProjectId(projectFlag).projectId;
305
+ if (!projectId) {
306
+ emitError("NO_PROJECT", "No project for schema diff.", "Pass --project <id>, or run `bata link <project>` to set a default.");
307
+ }
308
+ const s = jsonMode ? null : spinner("Diffing branch schemas");
309
+ const fromId = await resolveBranchIdForSchema(projectId, token, config.defaultTeam, fromRef);
310
+ const toId = await resolveBranchIdForSchema(projectId, token, config.defaultTeam, toRef);
311
+ const res = await api.get(`/v1/schema/${projectId}/diff`, token, { from: fromId, to: toId });
312
+ s?.stop();
313
+ if (!res.ok)
314
+ failSchemaRequest(res, "Schema diff failed");
315
+ if (jsonMode) {
316
+ json(res.data);
317
+ return;
318
+ }
319
+ renderDiff(fromRef, toRef, res.data.diff);
320
+ }
321
+ function nullable(c) {
322
+ return c.isNullable ? "" : colors.dim(" NOT NULL");
323
+ }
324
+ function renderSchema(branchId, schema) {
325
+ heading("Schema");
326
+ log(` ${colors.dim(`branch ${colors.cyan(branchId)} · ${schema.tables.length} table${schema.tables.length === 1 ? "" : "s"}`)}`);
327
+ log();
328
+ if (schema.tables.length === 0) {
329
+ log(` ${colors.dim("No tables in this branch's schema.")}`);
330
+ log();
331
+ return;
332
+ }
333
+ for (const t of schema.tables) {
334
+ log(` ${colors.bold(`${t.schema}.${t.name}`)}`);
335
+ for (const col of t.columns) {
336
+ const pk = col.isPrimaryKey ? colors.yellow(" PK") : "";
337
+ const def = col.defaultValue ? colors.dim(` = ${col.defaultValue}`) : "";
338
+ log(` ${col.name} ${colors.cyan(col.dataType)}${nullable(col)}${pk}${def}`);
339
+ }
340
+ for (const idx of t.indexes) {
341
+ log(` ${colors.dim(`index ${idx.name}`)}`);
342
+ }
343
+ for (const con of t.constraints) {
344
+ const fk = con.foreignTableName ? colors.dim(` -> ${con.foreignTableSchema}.${con.foreignTableName}.${con.foreignColumnName}`) : "";
345
+ log(` ${colors.dim(`constraint ${con.name} (${con.type})`)}${fk}`);
346
+ }
347
+ log();
348
+ }
349
+ }
350
+ function renderDiff(fromRef, toRef, diff) {
351
+ heading("Schema diff");
352
+ log(` ${colors.dim(`${colors.cyan(fromRef)} -> ${colors.cyan(toRef)}`)}`);
353
+ log();
354
+ if (diff.identical) {
355
+ log(` ${colors.green(">")} Schemas are identical.`);
356
+ log();
357
+ return;
358
+ }
359
+ for (const t of diff.addedTables) {
360
+ log(` ${colors.green("+ table")} ${colors.bold(`${t.schema}.${t.name}`)} ${colors.dim(`(${t.columns.length} columns)`)}`);
361
+ }
362
+ for (const t of diff.removedTables) {
363
+ log(` ${colors.red("- table")} ${colors.bold(`${t.schema}.${t.name}`)}`);
364
+ }
365
+ for (const t of diff.changedTables) {
366
+ log(` ${colors.yellow("~ table")} ${colors.bold(`${t.schema}.${t.name}`)}`);
367
+ for (const c of t.addedColumns)
368
+ log(` ${colors.green(`+ column ${c.name} ${c.dataType}`)}`);
369
+ for (const c of t.removedColumns)
370
+ log(` ${colors.red(`- column ${c.name}`)}`);
371
+ for (const c of t.changedColumns) {
372
+ const fields = c.changes.map((ch) => `${ch.field}: ${String(ch.from)} -> ${String(ch.to)}`).join(", ");
373
+ log(` ${colors.yellow(`~ column ${c.name}`)} ${colors.dim(fields)}`);
374
+ }
375
+ for (const i of t.addedIndexes)
376
+ log(` ${colors.green(`+ index ${i.name}`)}`);
377
+ for (const i of t.removedIndexes)
378
+ log(` ${colors.red(`- index ${i.name}`)}`);
379
+ for (const i of t.changedIndexes)
380
+ log(` ${colors.yellow(`~ index ${i.name}`)}`);
381
+ for (const con of t.addedConstraints)
382
+ log(` ${colors.green(`+ constraint ${con.name} (${con.type})`)}`);
383
+ for (const con of t.removedConstraints)
384
+ log(` ${colors.red(`- constraint ${con.name}`)}`);
385
+ for (const con of t.changedConstraints)
386
+ log(` ${colors.yellow(`~ constraint ${con.name}`)}`);
387
+ }
388
+ log();
389
+ }
181
390
  export async function handleSchema(args) {
182
391
  const sub = args[0];
183
392
  switch (sub) {
184
393
  case "check":
185
394
  return schemaCheck(args.slice(1));
395
+ case "dump":
396
+ case "show": // alias — the plan calls it `schema show`; `dump` is the agent-friendly verb
397
+ return schemaDump(args.slice(1));
398
+ case "diff":
399
+ return schemaDiff(args.slice(1));
186
400
  case "init":
187
401
  notImplemented("init");
188
402
  case "push":
189
403
  notImplemented("push");
190
404
  case "pull":
191
405
  notImplemented("pull");
192
- case "diff":
193
- notImplemented("diff");
194
406
  default:
195
407
  log();
196
408
  log(` ${colors.bold("bata schema")} ${colors.dim("— schema safety & management")}`);
197
409
  log();
198
410
  log(` ${colors.dim("Commands:")}`);
199
- log(` ${colors.cyan("check <file|->")} Check a proposed DDL change against live query traffic`);
200
- log(` ${colors.cyan("init")} Initialize schema from existing database ${colors.dim("(coming soon)")}`);
201
- log(` ${colors.cyan("push")} Push schema changes to database ${colors.dim("(coming soon)")}`);
202
- log(` ${colors.cyan("pull")} Pull schema from database ${colors.dim("(coming soon)")}`);
203
- log(` ${colors.cyan("diff")} Show pending schema changes ${colors.dim("(coming soon)")}`);
411
+ log(` ${colors.cyan("check <file|->")} Check a proposed DDL change against live query traffic`);
412
+ log(` ${colors.cyan("dump")} Dump a branch's live schema (tables, columns, indexes, constraints)`);
413
+ log(` ${colors.cyan("diff <from> <to>")} Diff two branches' schemas`);
414
+ log(` ${colors.cyan("init")} Initialize schema from existing database ${colors.dim("(coming soon)")}`);
415
+ log(` ${colors.cyan("push")} Push schema changes to database ${colors.dim("(coming soon)")}`);
416
+ log(` ${colors.cyan("pull")} Pull schema from database ${colors.dim("(coming soon)")}`);
204
417
  log();
205
418
  log(` ${colors.dim("Options:")}`);
206
- log(` ${colors.dim("--branch <id> check against a branch's traffic")}`);
207
- log(` ${colors.dim("--window <1h|24h|7d|30d> corpus window (default 24h)")}`);
419
+ log(` ${colors.dim("--branch <name|id> target a branch (dump; default: primary)")}`);
420
+ log(` ${colors.dim("--project <id> override the linked/default project")}`);
421
+ log(` ${colors.dim("--json machine-readable output (dump/diff)")}`);
422
+ log(` ${colors.dim("--window <1h|24h|7d|30d> check corpus window (default 24h)")}`);
208
423
  log(` ${colors.dim("--fail-on <breaking|risky> exit 2 to gate CI (fail-closed: 'unknown' also trips)")}`);
209
424
  log();
210
425
  log(` ${colors.dim("Examples:")}`);
211
- log(` ${colors.dim("bata schema check migration.sql")}`);
212
- log(` ${colors.dim('echo "ALTER TABLE orders DROP COLUMN status;" | bata schema check - --json')}`);
426
+ log(` ${colors.dim("bata schema dump --branch main --json")}`);
427
+ log(` ${colors.dim("bata schema diff main feature-x --json")}`);
213
428
  log(` ${colors.dim("bata schema check migration.sql --fail-on breaking # CI gate")}`);
214
429
  log();
215
430
  }
@@ -22,3 +22,17 @@ export declare function modelActionLabel(q: {
22
22
  model: string | null;
23
23
  action: string | null;
24
24
  }): string;
25
+ /**
26
+ * Build the /turbine-queries request from `--by-query` flags. Exported + pure so
27
+ * the flag wiring is unit-testable — a flag that parses but is never forwarded
28
+ * (a silently-ignored flag) is an agent-usability bug, so we prove it here.
29
+ *
30
+ * Recognised: `--window <1h|24h|7d|30d>` (default 24h), `--branch <id>`,
31
+ * `--tag <label>` (Lever L2 — filter cost attribution to one query tag).
32
+ */
33
+ export declare function buildByQueryRequest(args: string[]): {
34
+ timeRange: string;
35
+ branch?: string;
36
+ tag?: string;
37
+ query: Record<string, string>;
38
+ };
@@ -197,6 +197,25 @@ export function modelActionLabel(q) {
197
197
  return q.action;
198
198
  return colors.dim("raw");
199
199
  }
200
+ /**
201
+ * Build the /turbine-queries request from `--by-query` flags. Exported + pure so
202
+ * the flag wiring is unit-testable — a flag that parses but is never forwarded
203
+ * (a silently-ignored flag) is an agent-usability bug, so we prove it here.
204
+ *
205
+ * Recognised: `--window <1h|24h|7d|30d>` (default 24h), `--branch <id>`,
206
+ * `--tag <label>` (Lever L2 — filter cost attribution to one query tag).
207
+ */
208
+ export function buildByQueryRequest(args) {
209
+ const timeRange = parseValueFlag(args, "--window") ?? "24h";
210
+ const branch = parseValueFlag(args, "--branch");
211
+ const tag = parseValueFlag(args, "--tag");
212
+ const query = { timeRange, orderBy: "totalTime", limit: "20" };
213
+ if (branch)
214
+ query.branch_id = branch;
215
+ if (tag)
216
+ query.tag = tag;
217
+ return { timeRange, branch, tag, query };
218
+ }
200
219
  async function usageByQuery(args) {
201
220
  const jsonMode = isJsonMode();
202
221
  const token = requireToken();
@@ -205,11 +224,7 @@ async function usageByQuery(args) {
205
224
  if (!projectId) {
206
225
  emitError("NO_PROJECT", "No project for --by-query.", "Pass --project <id>, or run `bata link <project>` to set a default.");
207
226
  }
208
- const timeRange = parseValueFlag(args, "--window") ?? "24h";
209
- const branch = parseValueFlag(args, "--branch");
210
- const query = { timeRange, orderBy: "totalTime", limit: "20" };
211
- if (branch)
212
- query.branch_id = branch;
227
+ const { timeRange, branch, tag, query } = buildByQueryRequest(args);
213
228
  const s = jsonMode ? null : spinner("Attributing compute cost by query");
214
229
  const res = await api.get(`/v1/insights/${projectId}/turbine-queries`, token, query);
215
230
  s?.stop();
@@ -232,6 +247,7 @@ async function usageByQuery(args) {
232
247
  project_id: projectId,
233
248
  window: timeRange,
234
249
  branch_id: branch ?? null,
250
+ tag: tag ?? null,
235
251
  // Carry the basis so a consumer can always see HOW the estimate was made
236
252
  // and that it is off the billing path.
237
253
  cost_basis: {
@@ -245,6 +261,7 @@ async function usageByQuery(args) {
245
261
  fingerprint: q.fingerprint,
246
262
  model: q.model,
247
263
  action: q.action,
264
+ tag: q.tag ?? null,
248
265
  sql_template: q.sqlTemplate,
249
266
  calls: q.calls,
250
267
  mean_time_ms: q.meanTimeMs,
@@ -256,16 +273,22 @@ async function usageByQuery(args) {
256
273
  return;
257
274
  }
258
275
  heading("Cost by query — estimated");
259
- log(` ${colors.dim(`Project ${projectId} · window ${timeRange}${branch ? ` · branch ${branch}` : ""}`)}`);
276
+ log(` ${colors.dim(`Project ${projectId} · window ${timeRange}${branch ? ` · branch ${branch}` : ""}${tag ? ` · tag ${tag}` : ""}`)}`);
260
277
  log();
261
278
  if (rows.length === 0) {
262
- log(` ${colors.dim("No ORM query traffic reported in this window.")}`);
263
- log(` ${colors.dim("Wire up createInsightsReporter() to start attributing cost.")}`);
279
+ if (tag) {
280
+ log(` ${colors.dim(`No ORM query traffic tagged "${tag}" in this window.`)}`);
281
+ }
282
+ else {
283
+ log(` ${colors.dim("No ORM query traffic reported in this window.")}`);
284
+ log(` ${colors.dim("Wire up createInsightsReporter() to start attributing cost.")}`);
285
+ }
264
286
  log();
265
287
  return;
266
288
  }
267
- table(["QUERY", "CALLS", "MEAN", "EST. COST"], rows.map((q) => [
289
+ table(["QUERY", "TAG", "CALLS", "MEAN", "EST. COST"], rows.map((q) => [
268
290
  modelActionLabel(q),
291
+ q.tag ?? colors.dim("—"),
269
292
  q.calls.toLocaleString(),
270
293
  `${q.meanTimeMs}ms`,
271
294
  estCost(q.estimatedCostCents),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@batadata/cli",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "CLI for BataDB — serverless Postgres platform",
5
5
  "bin": {
6
6
  "bata": "./dist/index.js"