@erdoai/cli 0.121.0 → 0.122.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 +61 -4
  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
  //
@@ -4192,13 +4198,13 @@ runsCmd.command("list").description("List agent runs (filter by agent OR thread
4192
4198
  fail(e);
4193
4199
  }
4194
4200
  });
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) => {
4201
+ 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
4202
  try {
4197
4203
  const include = [];
4198
4204
  if (opts.resources) include.push("resources");
4199
4205
  if (opts.steps) include.push("steps");
4200
4206
  const detail = await new ErdoClient().getAgentRun(id, include);
4201
- if (include.length === 0) {
4207
+ if (opts.json || include.length === 0) {
4202
4208
  print(detail);
4203
4209
  return;
4204
4210
  }
@@ -6658,6 +6664,53 @@ datasetsCmd.command("purposes").description(
6658
6664
  fail(e);
6659
6665
  }
6660
6666
  });
6667
+ datasetsCmd.command("write <slug>").description(
6668
+ "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."
6669
+ ).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(
6670
+ async (slug, opts) => {
6671
+ try {
6672
+ const rawRows = readMaybeFile(opts.rows);
6673
+ let parsed;
6674
+ try {
6675
+ parsed = JSON.parse(rawRows ?? "");
6676
+ } catch (error) {
6677
+ throw new Error(`--rows must contain valid JSON: ${error instanceof Error ? error.message : String(error)}`);
6678
+ }
6679
+ if (!Array.isArray(parsed) || parsed.some((row) => row === null || typeof row !== "object" || Array.isArray(row))) {
6680
+ throw new Error("--rows must contain a JSON array of non-null objects");
6681
+ }
6682
+ const writeMode = opts.writeMode;
6683
+ if (writeMode !== void 0 && writeMode !== "replace" && writeMode !== "merge") {
6684
+ throw new Error('--write-mode must be "replace" or "merge"');
6685
+ }
6686
+ if (writeMode !== void 0 && !opts.keyColumn?.trim()) {
6687
+ throw new Error(`--write-mode "${writeMode}" requires a non-blank --key-column`);
6688
+ }
6689
+ if (opts.keyColumn !== void 0 && !opts.keyColumn.split(",").some((column) => column.trim())) {
6690
+ throw new Error("--key-column must name at least one non-blank column");
6691
+ }
6692
+ let overwriteColumns;
6693
+ if (opts.overwriteColumns !== void 0) {
6694
+ if (writeMode !== "merge") {
6695
+ throw new Error("--overwrite-columns requires --write-mode merge");
6696
+ }
6697
+ overwriteColumns = opts.overwriteColumns.split(",").map((column) => column.trim());
6698
+ if (overwriteColumns.some((column) => !column)) {
6699
+ throw new Error("--overwrite-columns must not contain empty column names");
6700
+ }
6701
+ }
6702
+ const result = await new ErdoClient().writeDatasetRows(slug, {
6703
+ rows: parsed,
6704
+ ...opts.keyColumn !== void 0 ? { key_column: opts.keyColumn } : {},
6705
+ ...writeMode !== void 0 ? { write_mode: writeMode } : {},
6706
+ ...overwriteColumns !== void 0 ? { overwrite_columns: overwriteColumns } : {}
6707
+ });
6708
+ print(result);
6709
+ } catch (e) {
6710
+ fail(e);
6711
+ }
6712
+ }
6713
+ );
6661
6714
  function readSchemaFile(path) {
6662
6715
  const parsed = JSON.parse(readFileSync4(path, "utf8"));
6663
6716
  const schema = Array.isArray(parsed) ? { columns: parsed } : parsed;
@@ -7096,7 +7149,7 @@ refreshCmd.command("set <slug>").description(
7096
7149
  ).option(
7097
7150
  "--mode <replace|upsert>",
7098
7151
  "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(
7152
+ ).option("--key-column <c>", "non-blank column, or comma-separated columns, an upsert merges on; required with --mode upsert").option(
7100
7153
  "--schedule <cron>",
7101
7154
  "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
7155
  ).option("--timezone <tz>", "IANA timezone the cron expression is read in (default UTC)").option(
@@ -7146,6 +7199,10 @@ refreshCmd.command("set <slug>").description(
7146
7199
  fail(new Error("--mode must be replace or upsert"));
7147
7200
  return;
7148
7201
  }
7202
+ if (opts.keyColumn !== void 0 && !opts.keyColumn.split(",").some((column) => column.trim())) {
7203
+ fail(new Error("--key-column must name at least one non-blank column"));
7204
+ return;
7205
+ }
7149
7206
  if (opts.mode === "upsert" && !opts.keyColumn) {
7150
7207
  fail(new Error("--mode upsert needs --key-column <c>: the column the merge keys on"));
7151
7208
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.121.0",
3
+ "version": "0.122.0",
4
4
  "description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {