@erdoai/cli 0.126.0 → 0.127.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 +41 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1257,10 +1257,22 @@ var ErdoClient = class {
1257
1257
  }
1258
1258
  // The captures the platform refused to resolve: two leads sharing a contact
1259
1259
  // detail, or a submission whose email and phone point at different leads.
1260
- listLeadMergeCandidates(datasetSlug, limit) {
1261
- const qs = limit ? `?limit=${encodeURIComponent(String(limit))}` : "";
1260
+ listLeadMergeCandidates(datasetSlug, limit, traffic) {
1261
+ const query = new URLSearchParams();
1262
+ if (limit !== void 0) query.set("limit", String(limit));
1263
+ if (traffic !== void 0) query.set("traffic", traffic);
1264
+ const qs = query.size ? `?${query}` : "";
1262
1265
  return this.request("GET", `/v1/datasets/${encodeURIComponent(datasetSlug)}/lead-candidates${qs}`);
1263
1266
  }
1267
+ resolveLeadCapture(datasetSlug, captureID, input) {
1268
+ if (!["apply_to_lead", "dismiss"].includes(input.action) || typeof input.reason !== "string" || !input.reason.trim()) {
1269
+ throw new Error("A resolution action and reason are required.");
1270
+ }
1271
+ if (input.apply && (!input.operation_key || !input.expected_snapshot)) {
1272
+ throw new Error("Apply requires operation_key and expected_snapshot from the exact preview.");
1273
+ }
1274
+ return this.request("POST", `/v1/datasets/${encodeURIComponent(datasetSlug)}/lead-captures/${encodeURIComponent(captureID)}/resolve`, input);
1275
+ }
1264
1276
  // Not reversible in practice: the absorbed lead's row values are combined into
1265
1277
  // the survivor's and its row is gone. It needs no idempotency key — the pair it
1266
1278
  // joins identifies it, so a retry answers "already_merged".
@@ -6749,7 +6761,7 @@ datasetsCmd.command("create <name>").description(
6749
6761
  });
6750
6762
  datasetsCmd.command("ensure <purpose>").description(
6751
6763
  "Create or reuse the one dataset for a purpose and verify its declared producer contract. Refuses legacy splits and incompatible populated datasets instead of choosing silently."
6752
- ).requiredOption("--schema <file>", "JSON file containing { columns: [...] } or a columns array").option("--name <name>", "dataset name when creation is required").option("--description <text>", "dataset description when creation is required").option("--purpose-description <text>", "what writes and reads this purpose").option("--preferred-dataset <slug>", "existing bound dataset slug or id to adopt").option("--email-column <name>", "for a leads purpose: which declared column holds the email (default email)").option("--phone-column <name>", "for a leads purpose: which declared column holds the phone (default phone)").action(async (purpose, opts) => {
6764
+ ).requiredOption("--schema <file>", "JSON file containing { columns: [...] } or a columns array").option("--name <name>", "dataset name when creation is required").option("--description <text>", "dataset description when creation is required").option("--purpose-description <text>", "what writes and reads this purpose").option("--preferred-dataset <slug>", "existing bound dataset slug or id to adopt").option("--reviewed-optional-addition <name>", "producer-reviewed optional column to add atomically; repeat for multiple columns", (value, previous = []) => [...previous, value], []).option("--email-column <name>", "for a leads purpose: which declared column holds the email (default email)").option("--phone-column <name>", "for a leads purpose: which declared column holds the phone (default phone)").action(async (purpose, opts) => {
6753
6765
  try {
6754
6766
  const declaredSchema = readSchemaFile(opts.schema);
6755
6767
  const result = await new ErdoClient().ensureDataset({
@@ -6758,6 +6770,7 @@ datasetsCmd.command("ensure <purpose>").description(
6758
6770
  ...opts.description ? { description: opts.description } : {},
6759
6771
  ...opts.purposeDescription ? { purpose_description: opts.purposeDescription } : {},
6760
6772
  ...opts.preferredDataset ? { preferred_dataset: opts.preferredDataset } : {},
6773
+ ...opts.reviewedOptionalAddition.length ? { reviewed_optional_additions: opts.reviewedOptionalAddition } : {},
6761
6774
  ...opts.emailColumn ? { email_column: opts.emailColumn } : {},
6762
6775
  ...opts.phoneColumn ? { phone_column: opts.phoneColumn } : {},
6763
6776
  declared_schema: declaredSchema
@@ -7392,10 +7405,10 @@ leadsCmd.command("get <dataset> <lead>").description(
7392
7405
  }
7393
7406
  });
7394
7407
  leadsCmd.command("candidates <dataset>").description(
7395
- "List the captures the platform refused to resolve \u2014 where two leads share a contact detail, or a submission's email and phone point at different leads. The platform records the candidates instead of guessing, and this is the queue a person drains with `leads merge`."
7396
- ).option("-l, --limit <n>", "maximum captures to return (default 50, maximum 200)", (v) => parseInt(v, 10)).option("--json", "print the raw JSON result instead of a table").action(async (dataset, opts) => {
7408
+ "List unresolved refused submissions and their candidate leads. Inspect the saved evidence before previewing an apply-to-lead or dismissal decision with leads resolve-capture."
7409
+ ).option("-l, --limit <n>", "maximum captures to return (default 50, maximum 200)", (v) => parseInt(v, 10)).addOption(new Option2("--traffic <kind>", "include real submissions, independently trusted tests, or both").choices(["real", "test", "all"])).option("--json", "print the raw JSON result instead of a table").action(async (dataset, opts) => {
7397
7410
  try {
7398
- const res = await new ErdoClient().listLeadMergeCandidates(dataset, opts.limit);
7411
+ const res = await new ErdoClient().listLeadMergeCandidates(dataset, opts.limit, opts.traffic);
7399
7412
  if (opts.json) {
7400
7413
  print(res);
7401
7414
  return;
@@ -7413,8 +7426,10 @@ leadsCmd.command("candidates <dataset>").description(
7413
7426
  }
7414
7427
  const rows = [];
7415
7428
  for (const item of candidates) {
7429
+ if (!item.candidates?.length) rows.push([item.capture.id, item.capture.created_at, item.capture.resolution, "No candidate", "", "", ""]);
7416
7430
  for (const candidate of item.candidates ?? []) {
7417
7431
  rows.push([
7432
+ item.capture.id,
7418
7433
  item.capture.created_at,
7419
7434
  item.capture.resolution,
7420
7435
  candidate.canonical_lead_id,
@@ -7424,15 +7439,29 @@ leadsCmd.command("candidates <dataset>").description(
7424
7439
  ]);
7425
7440
  }
7426
7441
  }
7427
- printAlignedTable(["captured", "why", "lead", "emails", "phones", "settled"], rows);
7442
+ printAlignedTable(["receipt", "captured", "why", "lead", "emails", "phones", "settled"], rows);
7428
7443
  console.log(
7429
7444
  `
7430
- Merge two of them with: erdo leads merge ${res.dataset_slug} <survivor> --absorb <lead>`
7445
+ Review saved submission evidence with: erdo leads resolve-capture ${res.dataset_slug} <receipt> --input <decision.json>`
7431
7446
  );
7432
7447
  } catch (e) {
7433
7448
  fail(e);
7434
7449
  }
7435
7450
  });
7451
+ leadsCmd.command("resolve-capture <dataset> <receipt>").description("Preview a refused submission against a candidate lead, or record an audited dismissal. Applying preserves the original receipt and does not merge identities, report a conversion, or replay arrival automations.").requiredOption("--input <file>", "JSON decision: action, target_lead_id for apply_to_lead, reason; applying also requires expected_snapshot and operation_key").option("--apply", "confirm the exact preview; without this flag the decision only previews").action(async (dataset, receipt, opts) => {
7452
+ try {
7453
+ const input = JSON.parse(readFileSync4(opts.input, "utf8"));
7454
+ if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("--input must contain a JSON object");
7455
+ if (Object.hasOwn(input, "apply")) throw new Error("Use the explicit --apply flag; do not put apply in the decision file");
7456
+ for (const key of Object.keys(input)) {
7457
+ if (!["action", "target_lead_id", "reason", "expected_snapshot", "operation_key"].includes(key)) throw new Error(`Unknown decision field: ${key}`);
7458
+ }
7459
+ const result = await new ErdoClient().resolveLeadCapture(dataset, receipt, { ...input, apply: Boolean(opts.apply) });
7460
+ print(result);
7461
+ } catch (e) {
7462
+ fail(e);
7463
+ }
7464
+ });
7436
7465
  leadsCmd.command("merge <dataset> <survivor>").description(
7437
7466
  "Merge two leads into one. The absorbed lead stops being its own lead: its contact evidence moves onto the survivor, and the two dataset rows become one row \u2014 a value the absorbed row carries fills a blank in the survivor's row, and never replaces one the survivor already holds unless you name the column with --overwrite-column. This cannot be undone: the absorbed row's values are combined away and only the pointer survives, so an id already handed out keeps resolving. Read both leads with `leads get` before running it."
7438
7467
  ).requiredOption("--absorb <lead>", "the lead that stops being its own lead (UUID or lead reference)").option(
@@ -7907,6 +7936,10 @@ must not affect priority ${rb.priority.forbidden_factors.join(", ")}`);
7907
7936
  }
7908
7937
  }
7909
7938
  if (rb.flow) printLeadPlaybookFlow(rb.flow);
7939
+ if (rb.call_plan?.length) {
7940
+ console.log("\ncalls to a lead who has not answered (enforced)");
7941
+ for (const line of rb.call_plan) console.log(` ${line}`);
7942
+ }
7910
7943
  if (rb.limits?.length) {
7911
7944
  console.log("\nlimits Erdo applied");
7912
7945
  for (const l of rb.limits) console.log(` ${l.kind} \u2192 ${l.mode}: ${l.reason}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.126.0",
3
+ "version": "0.127.0",
4
4
  "description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {