@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.
- package/dist/index.js +41 -8
- 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
|
|
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
|
|
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
|
-
|
|
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}`);
|