@erdoai/cli 0.53.0 → 0.54.1

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.
Files changed (3) hide show
  1. package/README.md +9 -0
  2. package/dist/index.js +42 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -61,6 +61,15 @@ erdo datasets fetch acme.leads --filter <name> # opt into a saved filter
61
61
  The table is named **`data`** — for file datasets (CSV, Excel, and anything
62
62
  written by an event pipeline) that is the name regardless of the dataset's slug
63
63
  or resource key, and it is not guessable, so it is the first thing to get right.
64
+ The columns aren't guessable either, so look before you write SQL:
65
+
66
+ ```bash
67
+ erdo datasets schema acme.leads # column names and types
68
+ ```
69
+
70
+ `datasets schema` reads the stored table itself, so it shows the columns that
71
+ are actually there — the ones `fetch --sql` can reference — rather than a
72
+ declared schema that may lag what recent writes added.
64
73
  Database and warehouse datasets are queried through their real table names, which
65
74
  come from the dataset's schema. The SQL dialect is DuckDB. A dataset's default
66
75
  filters apply on every read; `--filter <name>` adds a saved filter on top, and
package/dist/index.js CHANGED
@@ -1339,6 +1339,14 @@ function timedOutMessage(threadID) {
1339
1339
  function print(value) {
1340
1340
  console.log(JSON.stringify(value, null, 2));
1341
1341
  }
1342
+ function printAlignedTable(columns, rows) {
1343
+ const cell = (v) => v === null || v === void 0 ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
1344
+ const widths = columns.map((c, i) => Math.max(c.length, ...rows.map((r) => cell(r[i]).length), 0));
1345
+ const line = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
1346
+ console.log(line(columns));
1347
+ console.log(widths.map((w) => "-".repeat(w)).join(" "));
1348
+ for (const r of rows) console.log(line(columns.map((_, i) => cell(r[i]))));
1349
+ }
1342
1350
  var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
1343
1351
  var collect = (v, acc) => {
1344
1352
  acc.push(v);
@@ -2463,6 +2471,12 @@ agentCmd.command("threads").description("List your threads").action(async () =>
2463
2471
  fail(e);
2464
2472
  }
2465
2473
  });
2474
+ function contentBlockLabel(c) {
2475
+ const type = c.ui_content_type || c.content_type;
2476
+ const payload = c.content;
2477
+ const toolName = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.name === "string" ? payload.name : void 0;
2478
+ return toolName ? `${type}: ${toolName}` : type;
2479
+ }
2466
2480
  agentCmd.command("messages <threadId>").description("Show a thread's messages (role + text)").option("-v, --verbose", "print full content for non-text entries (tool calls, results, etc.) instead of a placeholder").action(async (threadId, opts) => {
2467
2481
  try {
2468
2482
  const { messages } = await new ErdoClient().getThreadMessages(threadId);
@@ -2477,10 +2491,10 @@ agentCmd.command("messages <threadId>").description("Show a thread's messages (r
2477
2491
  if (c.content_type === "text") {
2478
2492
  console.log(c.content);
2479
2493
  } else if (opts.verbose) {
2480
- console.log(`\x1B[2m[${c.content_type}]\x1B[0m`);
2494
+ console.log(`\x1B[2m[${contentBlockLabel(c)}]\x1B[0m`);
2481
2495
  console.log(typeof c.content === "string" ? c.content : JSON.stringify(c.content, null, 2));
2482
2496
  } else {
2483
- console.log(`\x1B[2m[${c.content_type}]\x1B[0m`);
2497
+ console.log(`\x1B[2m[${contentBlockLabel(c)}]\x1B[0m`);
2484
2498
  }
2485
2499
  }
2486
2500
  }
@@ -3444,7 +3458,7 @@ datasetsCmd.command("fetch <slug>").description(
3444
3458
  "Read rows from a dataset \u2014 the deterministic read, and the one to use for anything mechanical or scripted. Returns {columns, rows, row_count}."
3445
3459
  ).option(
3446
3460
  "-q, --sql <query>",
3447
- "DuckDB SQL shaping the rows. File datasets (including anything an event pipeline writes) are queried as a table named `data`, whatever the dataset's slug; database and warehouse datasets use their real table names from the schema."
3461
+ "DuckDB SQL shaping the rows. File datasets (including anything an event pipeline writes) are queried as a table named `data`, whatever the dataset's slug; database and warehouse datasets use their real table names from the schema. `erdo datasets schema <slug>` lists the columns you can reference."
3448
3462
  ).option("-l, --limit <n>", "max rows to return", (v) => parseInt(v, 10)).option(
3449
3463
  "-f, --filter <name>",
3450
3464
  "opt into a saved filter by name (repeatable); additive on top of the dataset's default filters (see: erdo datasets filter list <slug>)",
@@ -3463,6 +3477,29 @@ datasetsCmd.command("fetch <slug>").description(
3463
3477
  fail(e);
3464
3478
  }
3465
3479
  });
3480
+ datasetsCmd.command("schema <slug>").alias("describe").description(
3481
+ "Show a dataset's physical columns \u2014 the names and types you can reference in `datasets fetch --sql`, read from the stored `data` table itself (DuckDB DESCRIBE) so they are what is actually there, not what was declared. For a database or warehouse dataset, DESCRIBE its real table names instead: `datasets fetch <slug> --sql 'DESCRIBE <table>'`."
3482
+ ).option("--json", "print the raw JSON result instead of a table").action(async (slug, opts) => {
3483
+ try {
3484
+ const res = await new ErdoClient().fetchDatasetContents(slug, { sql_query: "DESCRIBE data" });
3485
+ if (opts.json) {
3486
+ print(res);
3487
+ return;
3488
+ }
3489
+ const nameIdx = res.columns.indexOf("column_name");
3490
+ const typeIdx = res.columns.indexOf("column_type");
3491
+ if (nameIdx === -1 || typeIdx === -1) {
3492
+ printAlignedTable(res.columns, res.rows);
3493
+ return;
3494
+ }
3495
+ printAlignedTable(
3496
+ ["column", "type"],
3497
+ res.rows.map((r) => [r[nameIdx], r[typeIdx]])
3498
+ );
3499
+ } catch (e) {
3500
+ fail(e);
3501
+ }
3502
+ });
3466
3503
  var MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
3467
3504
  datasetsCmd.command("upload <file>").description("Upload a file (CSV, Excel, JSON, ...) and create a dataset from it").option("-n, --name <name>", "display name for the dataset (defaults to the filename)").option("-d, --description <text>", "description shown to agents analyzing the dataset").action(async (file, opts) => {
3468
3505
  try {
@@ -3519,14 +3556,6 @@ datasetsCmd.command("configure-integration <dataset-id>").description("Set an in
3519
3556
  fail(e);
3520
3557
  }
3521
3558
  });
3522
- function printAnalyticsTable(columns, rows) {
3523
- const cell = (v) => v === null || v === void 0 ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
3524
- const widths = columns.map((c, i) => Math.max(c.length, ...rows.map((r) => cell(r[i]).length), 0));
3525
- const line = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
3526
- console.log(line(columns));
3527
- console.log(widths.map((w) => "-".repeat(w)).join(" "));
3528
- for (const r of rows) console.log(line(columns.map((_, i) => cell(r[i]))));
3529
- }
3530
3559
  var analytics = program.command("analytics").description("Page analytics \u2014 what is tracking your published pages, and how they perform with real visitors");
3531
3560
  analytics.command("query <hogql>").description("Run a read-only HogQL query against this org's page-analytics events").option("--json", "print the raw JSON result instead of a table").action(async (hogql, opts) => {
3532
3561
  try {
@@ -3539,7 +3568,7 @@ analytics.command("query <hogql>").description("Run a read-only HogQL query agai
3539
3568
  console.log("Page analytics is not enabled for this organization \u2014 turn it on in the Erdo app, then query real traffic here.");
3540
3569
  return;
3541
3570
  }
3542
- printAnalyticsTable(res.columns, res.rows);
3571
+ printAlignedTable(res.columns, res.rows);
3543
3572
  if (res.truncated) console.log(`
3544
3573
  (rows truncated at the cap \u2014 aggregate further or add a tighter filter/LIMIT)`);
3545
3574
  } catch (e) {
@@ -3570,7 +3599,7 @@ analytics.command("tracking").description("Show which analytics destinations thi
3570
3599
  rows.push([d.kind, d.enabled ? "on" : "configured, off", d.public_id ?? "", d.provider ?? "", ""]);
3571
3600
  }
3572
3601
  }
3573
- printAnalyticsTable(["kind", "status", "public id", "provider", "what it does"], rows);
3602
+ printAlignedTable(["kind", "status", "public id", "provider", "what it does"], rows);
3574
3603
  console.log(`
3575
3604
  session replay input masking: ${res.mask_inputs ? "on" : "OFF \u2014 form values are recorded"}`);
3576
3605
  console.log(`consent: ${res.consent}${res.consent === "required" ? " (recording waits on a consent banner, so thin volume may be the gate, not the traffic)" : ""}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.53.0",
3
+ "version": "0.54.1",
4
4
  "description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {