@erdoai/cli 0.117.1 → 0.119.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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +115 -4
  3. package/package.json +8 -8
package/README.md CHANGED
@@ -11,7 +11,7 @@ npx @erdoai/cli --help # or run without installing
11
11
  erdo update # self-update to the latest published version
12
12
  ```
13
13
 
14
- Requires Node.js 18+. User-facing install docs: https://docs.erdo.ai/cli
14
+ Requires Node.js 22.13+. User-facing install docs: https://docs.erdo.ai/cli
15
15
 
16
16
  When a newer version is published, every command prints one line on **stderr**
17
17
  naming the installed version and `erdo update`. It is on stderr so stdout stays
package/dist/index.js CHANGED
@@ -1075,6 +1075,25 @@ var ErdoClient = class {
1075
1075
  throw err;
1076
1076
  }
1077
1077
  }
1078
+ // The thread owns the current invocation identity. Approval resumes create a
1079
+ // new invocation while deliberately reusing the original agent run, so the
1080
+ // run list is not an authoritative source for cancellation.
1081
+ getThreadCompletionStatus(threadID) {
1082
+ return this.request(
1083
+ "GET",
1084
+ `/v1/threads/${encodeURIComponent(threadID)}/completion-status`
1085
+ );
1086
+ }
1087
+ // cancelThreadInvocation names the invocation being stopped. The thread
1088
+ // service refuses (HTTP 400, "the run already changed") when the thread has
1089
+ // moved on to another invocation, so a replacement run is never cancelled.
1090
+ cancelThreadInvocation(threadID, invocationID) {
1091
+ return this.request(
1092
+ "POST",
1093
+ `/v1/threads/${encodeURIComponent(threadID)}/cancel-invocation`,
1094
+ { invocation_id: invocationID }
1095
+ );
1096
+ }
1078
1097
  sendMessage(threadID, input) {
1079
1098
  return this.request(
1080
1099
  "POST",
@@ -1149,6 +1168,13 @@ var ErdoClient = class {
1149
1168
  const qs = q.toString();
1150
1169
  return this.request("GET", `/v1/datasets-purposes${qs ? `?${qs}` : ""}`);
1151
1170
  }
1171
+ // Reads the declared write contract through the slug-addressed fetch route's
1172
+ // declared_schema view. That route is the one a dataset-scoped API key can
1173
+ // reach (and is confined to its own datasets there); the dataset-details
1174
+ // route is not, because its response also names other datasets.
1175
+ getDatasetDeclaredSchema(slug) {
1176
+ return this.request("POST", `/v1/datasets/${encodeURIComponent(slug)}/fetch`, { view: "declared_schema" });
1177
+ }
1152
1178
  // Create an empty dataset with its declared columns. The server refuses a
1153
1179
  // create without a schema unless the purpose's columns are platform-defined.
1154
1180
  createDataset(input) {
@@ -1583,6 +1609,15 @@ var ErdoClient = class {
1583
1609
  );
1584
1610
  }
1585
1611
  // --- knowledge ---
1612
+ getKnowledge(id) {
1613
+ return this.request("GET", `/v1/knowledge/${encodeURIComponent(id)}`);
1614
+ }
1615
+ updateKnowledge(id, input) {
1616
+ return this.request("POST", `/v1/knowledge/${encodeURIComponent(id)}/actions`, {
1617
+ action: "update",
1618
+ ...input
1619
+ });
1620
+ }
1586
1621
  listKnowledge(visibility, options) {
1587
1622
  const params = new URLSearchParams();
1588
1623
  if (visibility) params.set("visibility", visibility);
@@ -1719,6 +1754,7 @@ var ErdoClient = class {
1719
1754
  // what the evidence said afterwards. Referenced by slug, never UUID.
1720
1755
  listDecisions(opts) {
1721
1756
  const params = new URLSearchParams();
1757
+ if (opts.organization_slug) params.set("organization_slug", opts.organization_slug);
1722
1758
  if (opts.workstream_slug) params.set("workstream_slug", opts.workstream_slug);
1723
1759
  if (opts.source) params.set("source", opts.source);
1724
1760
  if (opts.decision_class) params.set("decision_class", opts.decision_class);
@@ -3974,6 +4010,24 @@ agentCmd.command("send <threadId> <message>").description("Send a message to a t
3974
4010
  fail(e);
3975
4011
  }
3976
4012
  });
4013
+ agentCmd.command("cancel <threadId>").description("Request cancellation of a thread's active invocation; read the run to confirm it has stopped").option("--invocation <id>", "invocation to cancel (default: the thread's current invocation)").action(async (threadId, opts) => {
4014
+ try {
4015
+ const client = new ErdoClient();
4016
+ let invocationID = opts.invocation;
4017
+ if (!invocationID) {
4018
+ const current = await client.getThreadCompletionStatus(threadId);
4019
+ invocationID = current.invocation_id;
4020
+ if (!invocationID) throw new Error(`thread ${threadId} has no invocation to cancel`);
4021
+ }
4022
+ const result = await client.cancelThreadInvocation(threadId, invocationID);
4023
+ if (!result.success) {
4024
+ throw new Error("cancellation could not be confirmed; inspect the thread's latest run and retry");
4025
+ }
4026
+ console.log(JSON.stringify(result, null, 2));
4027
+ } catch (e) {
4028
+ fail(e);
4029
+ }
4030
+ });
3977
4031
  agentCmd.command("wait <threadId>").description("Re-attach to a thread's latest run and wait for it to finish; prints the answer").action(async (threadId) => {
3978
4032
  try {
3979
4033
  const client = new ErdoClient();
@@ -4254,7 +4308,7 @@ function renderReportEffect(e) {
4254
4308
  console.log(` outcome: ${e.outcome_label}`);
4255
4309
  }
4256
4310
  }
4257
- decisionsCmd.command("list").description("List decisions, newest first").option("--workstream <slug>", "only decisions filed under this workstream/Strategy").option(
4311
+ decisionsCmd.command("list").description("List decisions, newest first").option("--organization <slug>", "read an authorized client organization (default: active organization)").option("--workstream <slug>", "only decisions filed under this workstream/Strategy").option(
4258
4312
  "--source <source>",
4259
4313
  "producer family: approval | escalation | engine_gate | allocator | experiment | workstream_commitment"
4260
4314
  ).option("--class <class>", "decision class, e.g. paid_media.ad_group.pause").option("--subject-kind <kind>", "only decisions about this kind of subject").option("--subject-ref <id>", "only decisions about this exact subject").option(
@@ -4267,6 +4321,7 @@ decisionsCmd.command("list").description("List decisions, newest first").option(
4267
4321
  async (opts) => {
4268
4322
  try {
4269
4323
  const res = await new ErdoClient().listDecisions({
4324
+ organization_slug: opts.organization,
4270
4325
  workstream_slug: opts.workstream,
4271
4326
  source: opts.source,
4272
4327
  decision_class: opts.class,
@@ -6747,10 +6802,36 @@ Read one with: erdo datasets fetch ${res.dataset_slug} --revision <id> --sql "SE
6747
6802
  }
6748
6803
  });
6749
6804
  datasetsCmd.command("schema <slug>").alias("describe").description(
6750
- "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>'`."
6751
- ).option("--json", "print the raw JSON result instead of a table").action(async (slug, opts) => {
6805
+ "Show physical columns by default, or the declared write contract with --declared. Physical columns are 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>'`."
6806
+ ).option("--json", "print the raw JSON result instead of a table").option("--declared", "read the declared write contract (column names, type hints, required flags, semantic roles) instead of the physical columns").action(async (slug, opts) => {
6752
6807
  try {
6753
- const res = await new ErdoClient().fetchDatasetContents(slug, { sql_query: "DESCRIBE data" });
6808
+ const client = new ErdoClient();
6809
+ if (opts.declared) {
6810
+ const res2 = await client.getDatasetDeclaredSchema(slug);
6811
+ if (!res2 || typeof res2 !== "object" || res2.view !== "declared_schema")
6812
+ throw new Error("Invalid dataset schema response: the server did not answer the declared_schema view");
6813
+ const schema = res2.declared_schema;
6814
+ if (schema != null && (!Array.isArray(schema.columns) || schema.columns.some((column) => !column || typeof column.name !== "string" || !column.name.trim())))
6815
+ throw new Error("Invalid dataset schema response: declared columns are malformed");
6816
+ if (opts.json) {
6817
+ print(res2);
6818
+ return;
6819
+ }
6820
+ if (schema == null) {
6821
+ console.log(`${slug} has no declared column contract. Use datasets schema ${slug} to inspect physical columns.`);
6822
+ return;
6823
+ }
6824
+ if (!schema.columns.length) {
6825
+ console.log(`${slug}'s declared column contract contains no columns.`);
6826
+ return;
6827
+ }
6828
+ printAlignedTable(
6829
+ ["column", "type", "required", "semantic role", "description"],
6830
+ schema.columns.map((column) => [column.name, column.type || "", column.required ? "yes" : "no", column.semantic_role || "", column.description || ""])
6831
+ );
6832
+ return;
6833
+ }
6834
+ const res = await client.fetchDatasetContents(slug, { sql_query: "DESCRIBE data" });
6754
6835
  if (opts.json) {
6755
6836
  print(res);
6756
6837
  return;
@@ -8142,6 +8223,36 @@ pipelinesCmd.command("executions <slug>").description("Show an event pipeline's
8142
8223
  }
8143
8224
  });
8144
8225
  var knowledgeCmd = program.command("knowledge").description("Knowledge objects");
8226
+ knowledgeCmd.command("get <id>").description("Get one full typed Knowledge object, including links and related objects").action(async (id) => {
8227
+ try {
8228
+ print(await new ErdoClient().getKnowledge(id));
8229
+ } catch (e) {
8230
+ fail(e);
8231
+ }
8232
+ });
8233
+ knowledgeCmd.command("update <id>").description("Update a Knowledge object while preserving fields omitted from the command").option("--body <value>", "new body text, or @file; pass an empty value to clear the body").option("--config <file>", "JSON object file containing the complete replacement config, or @file").option("--title <title>", "new title").action(async (id, opts) => {
8234
+ try {
8235
+ if (opts.title === void 0 && opts.body === void 0 && opts.config === void 0) {
8236
+ throw new Error("provide at least one of --title, --body, or --config");
8237
+ }
8238
+ if (opts.title !== void 0 && !opts.title.trim()) {
8239
+ throw new Error("title must not be empty");
8240
+ }
8241
+ const replacementConfig = opts.config === void 0 ? void 0 : readJSONObject(opts.config.startsWith("@") ? opts.config.slice(1) : opts.config, "--config");
8242
+ const replacementBody = opts.body === void 0 ? void 0 : readMaybeFile(opts.body) ?? "";
8243
+ const client = new ErdoClient();
8244
+ const current = await client.getKnowledge(id);
8245
+ const object = current.object;
8246
+ print(await client.updateKnowledge(id, {
8247
+ title: opts.title ?? object.title,
8248
+ body_markdown: replacementBody ?? object.body_markdown,
8249
+ config_json: replacementConfig ?? object.config_json,
8250
+ expected_updated_at: object.updated_at
8251
+ }));
8252
+ } catch (e) {
8253
+ fail(e);
8254
+ }
8255
+ });
8145
8256
  knowledgeCmd.command("list").description("List knowledge objects").option(
8146
8257
  "--public",
8147
8258
  "only entries opted into anonymous external surfaces (website widgets)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.117.1",
3
+ "version": "0.119.0",
4
4
  "description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,7 +11,7 @@
11
11
  "scripts/postinstall.mjs"
12
12
  ],
13
13
  "engines": {
14
- "node": ">=18"
14
+ "node": "^22.13.0 || >=23.5.0"
15
15
  },
16
16
  "publishConfig": {
17
17
  "access": "public",
@@ -41,14 +41,14 @@
41
41
  "prepublishOnly": "npm run build"
42
42
  },
43
43
  "dependencies": {
44
- "@inquirer/prompts": "^7.5.0",
45
- "commander": "^12.1.0"
44
+ "@inquirer/prompts": "^8.7.3",
45
+ "commander": "^15.0.0"
46
46
  },
47
47
  "devDependencies": {
48
- "@types/node": "^22.0.0",
49
- "tsup": "^8.0.0",
50
- "tsx": "^4.0.0",
51
- "typescript": "^5.7.0"
48
+ "@types/node": "^26.6.4",
49
+ "tsup": "^8.5.1",
50
+ "tsx": "^4.23.15",
51
+ "typescript": "^7.0.2"
52
52
  },
53
53
  "overrides": {
54
54
  "esbuild": "^0.28.1"