@erdoai/cli 0.140.1 → 0.141.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 +87 -13
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1217,6 +1217,15 @@ var ErdoClient = class {
1217
1217
  const qs = q.toString();
1218
1218
  return this.request("GET", `/v1/datasets${qs ? `?${qs}` : ""}`);
1219
1219
  }
1220
+ getDatasetRowActions(slug, opts) {
1221
+ const q = new URLSearchParams({ dataset: slug });
1222
+ if (opts?.include_receipts) q.set("include_receipts", "true");
1223
+ if (opts?.include_unresolved) q.set("include_unresolved", "true");
1224
+ return this.request("GET", `/v1/dataset-row-actions?${q.toString()}`);
1225
+ }
1226
+ setDatasetRowAction(input) {
1227
+ return this.request("PUT", "/v1/dataset-row-actions", input, false);
1228
+ }
1220
1229
  listDatasetUserPermissions(slug) {
1221
1230
  return this.request("GET", `/v1/datasets/${encodeURIComponent(slug)}/permissions/users`);
1222
1231
  }
@@ -2184,6 +2193,40 @@ async function promptLine(question) {
2184
2193
  }
2185
2194
  }
2186
2195
 
2196
+ // src/integration-credential-input.ts
2197
+ var PROMPT_FIELD = /^[A-Za-z0-9_.-]+$/;
2198
+ async function resolveIntegrationCredentials(options) {
2199
+ if (options.fromConnection && (options.credentialArgs.length || options.promptFields.length)) {
2200
+ throw new Error("pass either credentials or --from-connection, not both \u2014 otherwise the connection would hold a credential you did not choose");
2201
+ }
2202
+ if (options.promptFields.length && !options.interactive) {
2203
+ throw new Error("--prompt-credential requires both standard input and standard output to be terminals");
2204
+ }
2205
+ const credentials = /* @__PURE__ */ new Map();
2206
+ const seen = /* @__PURE__ */ new Set();
2207
+ const claim = (field) => {
2208
+ if (seen.has(field)) throw new Error("duplicate credential field");
2209
+ seen.add(field);
2210
+ };
2211
+ for (const value of options.credentialArgs) {
2212
+ const separator = value.indexOf("=");
2213
+ if (separator < 1) throw new Error("invalid --credential; expected key=value");
2214
+ const field = value.slice(0, separator);
2215
+ claim(field);
2216
+ credentials.set(field, value.slice(separator + 1));
2217
+ }
2218
+ for (const field of options.promptFields) {
2219
+ if (!PROMPT_FIELD.test(field)) throw new Error("prompt credential field must contain only letters, digits, dots, hyphens and underscores");
2220
+ claim(field);
2221
+ }
2222
+ for (const field of options.promptFields) {
2223
+ const value = await options.prompt(field);
2224
+ if (!value.trim()) throw new Error("prompted credential cannot be empty");
2225
+ credentials.set(field, value);
2226
+ }
2227
+ return seen.size ? Object.fromEntries(credentials) : void 0;
2228
+ }
2229
+
2187
2230
  // src/integrations.ts
2188
2231
  function formatIntegrationListRow(i) {
2189
2232
  const provided = i.provided_by_organization_slug ? ` provided by ${i.provided_by_organization_slug}` : "";
@@ -2450,6 +2493,19 @@ function readJSONObject(file, label) {
2450
2493
  }
2451
2494
  return parsed;
2452
2495
  }
2496
+ async function readJSONInput(value, label) {
2497
+ if (value !== "-") return readJSONObject(value, label);
2498
+ let parsed;
2499
+ try {
2500
+ parsed = JSON.parse(await readAllStdin());
2501
+ } catch (error) {
2502
+ throw new Error(`${label} must contain valid JSON: ${error instanceof Error ? error.message : String(error)}`);
2503
+ }
2504
+ if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
2505
+ throw new Error(`${label} must contain one JSON object`);
2506
+ }
2507
+ return parsed;
2508
+ }
2453
2509
  function requireOutreachProject() {
2454
2510
  if (!process.env.ERDO_PROJECT?.trim()) {
2455
2511
  throw new Error(
@@ -6961,6 +7017,29 @@ voiceAgentsCmd.command("whatsapp-replies <slug>").description("Turn a voice agen
6961
7017
  }
6962
7018
  });
6963
7019
  var datasetsCmd = program.command("datasets").description("Datasets");
7020
+ var datasetRowActionsCmd = datasetsCmd.command("row-actions").description("Read and replace actions triggered by rows arriving in a dataset");
7021
+ datasetRowActionsCmd.command("get <slug>").description("Read the row-action declarations and optional bounded diagnostics for a dataset").option("--include-receipts", "include bounded provider receipt diagnostics").option("--include-unresolved", "include bounded unresolved lookup diagnostics").action(async (slug, opts) => {
7022
+ try {
7023
+ print(await new ErdoClient().getDatasetRowActions(slug, {
7024
+ ...opts.includeReceipts ? { include_receipts: true } : {},
7025
+ ...opts.includeUnresolved ? { include_unresolved: true } : {}
7026
+ }));
7027
+ } catch (e) {
7028
+ fail(e);
7029
+ }
7030
+ });
7031
+ datasetRowActionsCmd.command("set").description("Replace one row-action declaration from a JSON file, or from stdin with --file -").requiredOption("-f, --file <path>", "one JSON declaration object; use - to read exactly one object from stdin").action(async (opts) => {
7032
+ try {
7033
+ const input = await readJSONInput(opts.file, "--file");
7034
+ const dataset = typeof input.dataset === "string" ? input.dataset.trim() : "";
7035
+ const name = typeof input.name === "string" ? input.name.trim() : "";
7036
+ if (!dataset) throw new Error("--file dataset must be a non-empty string");
7037
+ if (!name) throw new Error("--file name must be a non-empty string");
7038
+ print(await new ErdoClient().setDatasetRowAction(input));
7039
+ } catch (e) {
7040
+ fail(e);
7041
+ }
7042
+ });
6964
7043
  var datasetPermissionsCmd = datasetsCmd.command("permissions").description("Manage dataset user permissions");
6965
7044
  datasetPermissionsCmd.command("list <slug>").option("--json", "output raw JSON").action(async (slug, opts) => {
6966
7045
  try {
@@ -8545,7 +8624,7 @@ integrationsCmd.command("apps [query]").description("Search connectable apps (na
8545
8624
  fail(e);
8546
8625
  }
8547
8626
  });
8548
- integrationsCmd.command("connect <app>").description("Connect an app \u2014 pass its API key or other credentials with -c, or omit them to get a browser connect URL for OAuth apps").option("-c, --credential <key=value...>", "credential field, e.g. -c api_key=\u2026 (repeatable); rejected by OAuth apps, which have no credential to pass", (v, acc) => acc.concat(v), []).option("-n, --name <name>", "display name for the connection").option("--rotate", "replace the credentials of the app's existing connection instead of adding a second one \u2014 for a key that was rotated at the provider; requires -c").option("--share", "share the connection with the whole organization instead of keeping it private to you \u2014 for org-level credentials teammates and agents should see").option(
8627
+ integrationsCmd.command("connect <app>").description("Connect an app \u2014 pass credentials with -c or --prompt-credential, or omit them to get a browser connect URL for OAuth apps").option("-c, --credential <key=value...>", "credential field, e.g. -c api_key=\u2026 (repeatable); rejected by OAuth apps, which have no credential to pass", (v, acc) => acc.concat(v), []).option("--prompt-credential <field>", "prompt securely for a credential field (repeatable; interactive terminals only)", (v, acc) => acc.concat(v), []).option("-n, --name <name>", "display name for the connection").option("--rotate", "replace the credentials of the app's existing connection instead of adding a second one \u2014 for a key that was rotated at the provider; requires -c or --prompt-credential").option("--share", "share the connection with the whole organization instead of keeping it private to you \u2014 for org-level credentials teammates and agents should see").option(
8549
8628
  "--provide-to-managed",
8550
8629
  "let the organizations you manage use this connection as if it were their own \u2014 for a vendor key your agency pays for. Key-authenticated native integrations only, connected in a manager organization"
8551
8630
  ).option(
@@ -8553,9 +8632,6 @@ integrationsCmd.command("connect <app>").description("Connect an app \u2014 pass
8553
8632
  "reuse a credential you verifiably supplied \u2014 find its connection id in column 5 of `erdo integrations list --org <that-org>`. The credential is copied inside Erdo and never shown. Legacy keys without supplier records require an authenticated credential update"
8554
8633
  ).action(async (app, opts) => {
8555
8634
  try {
8556
- if (opts.fromConnection && opts.credential.length) {
8557
- fail(new Error("pass either -c or --from-connection, not both \u2014 otherwise the connection would hold a credential you did not choose"));
8558
- }
8559
8635
  if (opts.fromConnection && opts.rotate) {
8560
8636
  fail(
8561
8637
  new Error(
@@ -8563,15 +8639,13 @@ integrationsCmd.command("connect <app>").description("Connect an app \u2014 pass
8563
8639
  )
8564
8640
  );
8565
8641
  }
8566
- let credentials;
8567
- if (opts.credential.length) {
8568
- credentials = {};
8569
- for (const c of opts.credential) {
8570
- const eq = c.indexOf("=");
8571
- if (eq < 1) fail(new Error(`invalid --credential ${JSON.stringify(c)}; expected key=value`));
8572
- credentials[c.slice(0, eq)] = c.slice(eq + 1);
8573
- }
8574
- }
8642
+ const credentials = await resolveIntegrationCredentials({
8643
+ credentialArgs: opts.credential,
8644
+ fromConnection: opts.fromConnection,
8645
+ promptFields: opts.promptCredential,
8646
+ interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
8647
+ prompt: (field) => password({ message: `Credential ${field}:`, mask: "*" })
8648
+ });
8575
8649
  const res = await new ErdoClient().connectIntegration({
8576
8650
  app,
8577
8651
  name: opts.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.140.1",
3
+ "version": "0.141.0",
4
4
  "description": "Erdo CLI \u2014 drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {