@erdoai/cli 0.121.0 → 0.124.0

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 (2) hide show
  1. package/dist/index.js +63 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -186,7 +186,7 @@ async function rawRequest(baseURL, token, orgId, projectId, method, path, body,
186
186
  continue;
187
187
  }
188
188
  if (!retryNetworkFailures) {
189
- throw new ErdoNetworkError(`${netErr.message}. The launch may have started; inspect agent runs before retrying.`, netErr);
189
+ throw new ErdoNetworkError(`${netErr.message}. The request may have succeeded; inspect the affected resource before retrying.`, netErr);
190
190
  }
191
191
  throw netErr;
192
192
  }
@@ -1200,6 +1200,12 @@ var ErdoClient = class {
1200
1200
  updateDatasetSchema(slug, input) {
1201
1201
  return this.request("POST", `/v1/datasets/${encodeURIComponent(slug)}/schema`, input);
1202
1202
  }
1203
+ // Dataset row writes are deliberately never replayed after a network
1204
+ // failure: the server may have committed the rows before the response was
1205
+ // lost, and repeating an append or upsert can change the result.
1206
+ writeDatasetRows(slug, input) {
1207
+ return this.request("POST", `/v1/datasets/${encodeURIComponent(slug)}/rows`, input, false);
1208
+ }
1203
1209
  // The endpoint's field is `question` (QueryDataNaturalLanguageInput). Sending // The endpoint's field is `question` (QueryDataNaturalLanguageInput). Sending
1204
1210
  // `query` made every `erdo datasets query` fail with "question is required".
1205
1211
  //
@@ -1714,6 +1720,7 @@ var ErdoClient = class {
1714
1720
  // --- agent runs ---
1715
1721
  listAgentRuns(opts) {
1716
1722
  const params = new URLSearchParams();
1723
+ if (opts.invocation) params.set("invocation_id", opts.invocation);
1717
1724
  if (opts.agent) params.set("agent_key", opts.agent);
1718
1725
  if (opts.thread) params.set("thread_id", opts.thread);
1719
1726
  if (opts.status) params.set("status", opts.status);
@@ -4184,7 +4191,7 @@ agentCmd.command("messages <threadId>").description("Show a thread's messages (r
4184
4191
  }
4185
4192
  });
4186
4193
  var runsCmd = program.command("runs").description("Inspect agent runs");
4187
- runsCmd.command("list").description("List agent runs (filter by agent OR thread \u2014 mutually exclusive)").option("-a, --agent <key>", "filter by agent key").option("-t, --thread <id>", "filter to a thread").option("-s, --status <status>", "filter by status (running, completed, failed, cancelled, pending)").option("-l, --limit <n>", "max runs", (v) => parseInt(v, 10)).action(async (opts) => {
4194
+ runsCmd.command("list").description("List agent runs; select an exact invocation, optionally constrained by agent OR thread").option("--invocation <id>", "select the exact root run for an invocation").option("-a, --agent <key>", "filter by agent key").option("-t, --thread <id>", "filter to a thread").option("-s, --status <status>", "filter by status (running, completed, failed, cancelled, pending)").option("-l, --limit <n>", "max runs", (v) => parseInt(v, 10)).action(async (opts) => {
4188
4195
  try {
4189
4196
  const { runs } = await new ErdoClient().listAgentRuns(opts);
4190
4197
  for (const r of runs) console.log(`${r.id} ${r.status} ${r.agent_key}`);
@@ -4192,13 +4199,13 @@ runsCmd.command("list").description("List agent runs (filter by agent OR thread
4192
4199
  fail(e);
4193
4200
  }
4194
4201
  });
4195
- runsCmd.command("get <id>").description("Show an agent run (status, output, trace metadata)").option("--resources", "also show the skills, knowledge and entities the run used").option("--steps", "also show the run's iterations, tool calls and sub-agent invocations").action(async (id, opts) => {
4202
+ runsCmd.command("get <id>").description("Show an agent run (status, output, trace metadata)").option("--resources", "also show the skills, knowledge and entities the run used").option("--steps", "also show the run's iterations, tool calls and sub-agent invocations").option("--json", "print the full JSON response, including requested resources and steps").action(async (id, opts) => {
4196
4203
  try {
4197
4204
  const include = [];
4198
4205
  if (opts.resources) include.push("resources");
4199
4206
  if (opts.steps) include.push("steps");
4200
4207
  const detail = await new ErdoClient().getAgentRun(id, include);
4201
- if (include.length === 0) {
4208
+ if (opts.json || include.length === 0) {
4202
4209
  print(detail);
4203
4210
  return;
4204
4211
  }
@@ -6658,6 +6665,53 @@ datasetsCmd.command("purposes").description(
6658
6665
  fail(e);
6659
6666
  }
6660
6667
  });
6668
+ datasetsCmd.command("write <slug>").description(
6669
+ "Write rows to a dataset. Without --key-column rows append. File and writable PostgreSQL datasets support keyed replace or merge. ClickHouse-backed writes always append, even with a key, and refuse merge."
6670
+ ).requiredOption("--rows <json-or-@file>", "JSON array of row objects, or @path to a JSON file").option("--key-column <columns>", "non-blank key column, or comma-separated columns for a composite key").option("--write-mode <replace|merge>", "replace matching fields (files clear omitted fields; PostgreSQL updates batch columns), or merge; either explicit mode requires --key-column").option("--overwrite-columns <csv>", "comma-separated columns merge mode may overwrite").action(
6671
+ async (slug, opts) => {
6672
+ try {
6673
+ const rawRows = readMaybeFile(opts.rows);
6674
+ let parsed;
6675
+ try {
6676
+ parsed = JSON.parse(rawRows ?? "");
6677
+ } catch (error) {
6678
+ throw new Error(`--rows must contain valid JSON: ${error instanceof Error ? error.message : String(error)}`);
6679
+ }
6680
+ if (!Array.isArray(parsed) || parsed.some((row) => row === null || typeof row !== "object" || Array.isArray(row))) {
6681
+ throw new Error("--rows must contain a JSON array of non-null objects");
6682
+ }
6683
+ const writeMode = opts.writeMode;
6684
+ if (writeMode !== void 0 && writeMode !== "replace" && writeMode !== "merge") {
6685
+ throw new Error('--write-mode must be "replace" or "merge"');
6686
+ }
6687
+ if (writeMode !== void 0 && !opts.keyColumn?.trim()) {
6688
+ throw new Error(`--write-mode "${writeMode}" requires a non-blank --key-column`);
6689
+ }
6690
+ if (opts.keyColumn !== void 0 && !opts.keyColumn.split(",").some((column) => column.trim())) {
6691
+ throw new Error("--key-column must name at least one non-blank column");
6692
+ }
6693
+ let overwriteColumns;
6694
+ if (opts.overwriteColumns !== void 0) {
6695
+ if (writeMode !== "merge") {
6696
+ throw new Error("--overwrite-columns requires --write-mode merge");
6697
+ }
6698
+ overwriteColumns = opts.overwriteColumns.split(",").map((column) => column.trim());
6699
+ if (overwriteColumns.some((column) => !column)) {
6700
+ throw new Error("--overwrite-columns must not contain empty column names");
6701
+ }
6702
+ }
6703
+ const result = await new ErdoClient().writeDatasetRows(slug, {
6704
+ rows: parsed,
6705
+ ...opts.keyColumn !== void 0 ? { key_column: opts.keyColumn } : {},
6706
+ ...writeMode !== void 0 ? { write_mode: writeMode } : {},
6707
+ ...overwriteColumns !== void 0 ? { overwrite_columns: overwriteColumns } : {}
6708
+ });
6709
+ print(result);
6710
+ } catch (e) {
6711
+ fail(e);
6712
+ }
6713
+ }
6714
+ );
6661
6715
  function readSchemaFile(path) {
6662
6716
  const parsed = JSON.parse(readFileSync4(path, "utf8"));
6663
6717
  const schema = Array.isArray(parsed) ? { columns: parsed } : parsed;
@@ -7096,7 +7150,7 @@ refreshCmd.command("set <slug>").description(
7096
7150
  ).option(
7097
7151
  "--mode <replace|upsert>",
7098
7152
  "replace overwrites every row with what the recipe returned; upsert merges by --key-column and keeps rows outside the returned window. Omit it and you get upsert when you passed --key-column and replace when you did not, since a key column is only meaningful to a merge"
7099
- ).option("--key-column <c>", "the column an upsert merges on; required with --mode upsert").option(
7153
+ ).option("--key-column <c>", "non-blank column, or comma-separated columns, an upsert merges on; required with --mode upsert").option(
7100
7154
  "--schedule <cron>",
7101
7155
  "a cron expression to run the refresh on a fixed cadence. Omit it and the refresh runs on view instead \u2014 cheaper, because it never runs while nobody is looking"
7102
7156
  ).option("--timezone <tz>", "IANA timezone the cron expression is read in (default UTC)").option(
@@ -7146,6 +7200,10 @@ refreshCmd.command("set <slug>").description(
7146
7200
  fail(new Error("--mode must be replace or upsert"));
7147
7201
  return;
7148
7202
  }
7203
+ if (opts.keyColumn !== void 0 && !opts.keyColumn.split(",").some((column) => column.trim())) {
7204
+ fail(new Error("--key-column must name at least one non-blank column"));
7205
+ return;
7206
+ }
7149
7207
  if (opts.mode === "upsert" && !opts.keyColumn) {
7150
7208
  fail(new Error("--mode upsert needs --key-column <c>: the column the merge keys on"));
7151
7209
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.121.0",
3
+ "version": "0.124.0",
4
4
  "description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {