@erdoai/cli 0.88.0 → 0.90.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 +100 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -489,10 +489,13 @@ var ErdoClient = class {
|
|
|
489
489
|
// Phone conversation records: inbound calls to a voice agent's own number and
|
|
490
490
|
// the outbound calls Erdo placed. Website chat widget conversations are a
|
|
491
491
|
// different resource — they live under /v1/voice/widget-conversations.
|
|
492
|
+
// canonical_lead_id narrows to one lead's calls, by its UUID or its lead
|
|
493
|
+
// reference — the same filter every conversation list takes.
|
|
492
494
|
listVoiceCalls(params) {
|
|
493
495
|
const q = new URLSearchParams();
|
|
494
496
|
if (params?.agent) q.set("agent", params.agent);
|
|
495
497
|
if (params?.direction) q.set("direction", params.direction);
|
|
498
|
+
if (params?.canonical_lead_id) q.set("canonical_lead_id", params.canonical_lead_id);
|
|
496
499
|
if (params?.limit !== void 0) q.set("limit", String(params.limit));
|
|
497
500
|
if (params?.offset !== void 0) q.set("offset", String(params.offset));
|
|
498
501
|
if (params?.cursor) q.set("cursor", params.cursor);
|
|
@@ -510,6 +513,7 @@ var ErdoClient = class {
|
|
|
510
513
|
if (params?.widget) q.set("widget", params.widget);
|
|
511
514
|
if (params?.since) q.set("since", params.since);
|
|
512
515
|
if (params?.until) q.set("until", params.until);
|
|
516
|
+
if (params?.canonical_lead_id) q.set("canonical_lead_id", params.canonical_lead_id);
|
|
513
517
|
if (params?.limit !== void 0) q.set("limit", String(params.limit));
|
|
514
518
|
if (params?.offset !== void 0) q.set("offset", String(params.offset));
|
|
515
519
|
if (params?.cursor) q.set("cursor", params.cursor);
|
|
@@ -536,6 +540,7 @@ var ErdoClient = class {
|
|
|
536
540
|
const q = new URLSearchParams();
|
|
537
541
|
if (params?.phone_number) q.set("phone_number", params.phone_number);
|
|
538
542
|
if (params?.address) q.set("address", params.address);
|
|
543
|
+
if (params?.canonical_lead_id) q.set("canonical_lead_id", params.canonical_lead_id);
|
|
539
544
|
if (params?.limit !== void 0) q.set("limit", String(params.limit));
|
|
540
545
|
if (params?.cursor) q.set("cursor", params.cursor);
|
|
541
546
|
const qs = q.toString();
|
|
@@ -1153,6 +1158,23 @@ var ErdoClient = class {
|
|
|
1153
1158
|
`/v1/datasets/${encodeURIComponent(slug)}/refresh/run`
|
|
1154
1159
|
);
|
|
1155
1160
|
}
|
|
1161
|
+
// --- lead identity ---
|
|
1162
|
+
// Preview or perform a dataset's permanent-lead-identity migration: give every
|
|
1163
|
+
// pipeline writer the producer contract, backfill the IDs, and publish the
|
|
1164
|
+
// identity config in one transaction.
|
|
1165
|
+
//
|
|
1166
|
+
// apply:false mutates nothing and is the way to read readiness — `ready`, the
|
|
1167
|
+
// `blockers` holding it up, and how many rows would be assigned an identity.
|
|
1168
|
+
// apply:true needs an operation_key: replaying the same key settles an apply
|
|
1169
|
+
// whose response was lost, while a different key against a migrated dataset is
|
|
1170
|
+
// refused rather than migrating twice.
|
|
1171
|
+
maintainLeadIdentity(slug, body) {
|
|
1172
|
+
return this.request(
|
|
1173
|
+
"POST",
|
|
1174
|
+
`/v1/datasets/${encodeURIComponent(slug)}/lead-identity/maintain`,
|
|
1175
|
+
body
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1156
1178
|
// --- bounded outreach ---
|
|
1157
1179
|
putOutreachBatch(batch, body) {
|
|
1158
1180
|
return this.request(
|
|
@@ -5198,13 +5220,18 @@ function contactLabel(contact) {
|
|
|
5198
5220
|
function leadStatusLabel(outcome) {
|
|
5199
5221
|
return outcome ? outcome : "pending";
|
|
5200
5222
|
}
|
|
5223
|
+
function leadReferenceLabel(reference) {
|
|
5224
|
+
return reference ? reference : "-";
|
|
5225
|
+
}
|
|
5226
|
+
var LEAD_FILTER_HELP = "only this lead's conversations \u2014 its canonical lead id (UUID) or its 22-character lead reference";
|
|
5201
5227
|
var voiceCallsCmd = voiceCmd.command("calls").description("Inbound and outbound phone call records");
|
|
5202
|
-
voiceCallsCmd.command("list").description("List the organization's phone calls, newest first").option("--agent <slug>", "only calls held by this voice agent, by slug").option("--direction <direction>", "only 'inbound' (calls to the agent's number) or 'outbound'").option("--limit <n>", "page size (default 25, max 100)").option("--offset <n>", "rows to skip").option("--cursor <cursor>", "next_cursor from the preceding page (stable paging)").option("--json", "print the raw JSON result instead of a table").action(
|
|
5228
|
+
voiceCallsCmd.command("list").description("List the organization's phone calls, newest first").option("--agent <slug>", "only calls held by this voice agent, by slug").option("--direction <direction>", "only 'inbound' (calls to the agent's number) or 'outbound'").option("--lead <lead>", LEAD_FILTER_HELP).option("--limit <n>", "page size (default 25, max 100)").option("--offset <n>", "rows to skip").option("--cursor <cursor>", "next_cursor from the preceding page (stable paging)").option("--json", "print the raw JSON result instead of a table").action(
|
|
5203
5229
|
async (opts) => {
|
|
5204
5230
|
try {
|
|
5205
5231
|
const res = await new ErdoClient().listVoiceCalls({
|
|
5206
5232
|
agent: opts.agent,
|
|
5207
5233
|
direction: opts.direction,
|
|
5234
|
+
canonical_lead_id: opts.lead,
|
|
5208
5235
|
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
5209
5236
|
offset: opts.offset ? Number(opts.offset) : void 0,
|
|
5210
5237
|
cursor: opts.cursor
|
|
@@ -5229,6 +5256,7 @@ voiceCallsCmd.command("list").description("List the organization's phone calls,
|
|
|
5229
5256
|
"transcript",
|
|
5230
5257
|
"contact",
|
|
5231
5258
|
"lead",
|
|
5259
|
+
"lead ref",
|
|
5232
5260
|
"started",
|
|
5233
5261
|
"summary"
|
|
5234
5262
|
],
|
|
@@ -5242,6 +5270,7 @@ voiceCallsCmd.command("list").description("List the organization's phone calls,
|
|
|
5242
5270
|
call.has_transcript ? "yes" : "no",
|
|
5243
5271
|
contactLabel(call.contact),
|
|
5244
5272
|
leadStatusLabel(call.lead_status),
|
|
5273
|
+
leadReferenceLabel(call.lead_reference),
|
|
5245
5274
|
call.created_at,
|
|
5246
5275
|
call.transcript_summary
|
|
5247
5276
|
])
|
|
@@ -5258,7 +5287,7 @@ voiceCallsCmd.command("list").description("List the organization's phone calls,
|
|
|
5258
5287
|
}
|
|
5259
5288
|
);
|
|
5260
5289
|
voiceCallsCmd.command("get <callID>").description(
|
|
5261
|
-
"Read one phone call in full: transcript, summary, per-turn LLM metrics, and the lead it produced (contact, lead_status, lead_saved_at)"
|
|
5290
|
+
"Read one phone call in full: transcript, summary, per-turn LLM metrics, and the lead it produced (contact, lead_status, lead_saved_at, canonical_lead_id, lead_reference)"
|
|
5262
5291
|
).action(async (callID) => {
|
|
5263
5292
|
try {
|
|
5264
5293
|
print(await new ErdoClient().getVoiceCall(callID));
|
|
@@ -5267,13 +5296,14 @@ voiceCallsCmd.command("get <callID>").description(
|
|
|
5267
5296
|
}
|
|
5268
5297
|
});
|
|
5269
5298
|
var widgetConversationsCmd = voiceCmd.command("widget-conversations").description("Website concierge widget conversations (text, voice and video sessions)");
|
|
5270
|
-
widgetConversationsCmd.command("list").description("List the organization's widget conversations, newest first").option("--widget <handle>", "only this widget's conversations \u2014 its name or public key (vw_...)").option("--since <rfc3339>", "only conversations that started at or after this time (inclusive)").option("--until <rfc3339>", "only conversations that started before this time (exclusive)").option("--limit <n>", "page size (default 25, max 100)").option("--offset <n>", "rows to skip").option("--cursor <cursor>", "next_cursor from the preceding page (stable paging)").option("--json", "print the raw JSON result instead of a table").action(
|
|
5299
|
+
widgetConversationsCmd.command("list").description("List the organization's widget conversations, newest first").option("--widget <handle>", "only this widget's conversations \u2014 its name or public key (vw_...)").option("--since <rfc3339>", "only conversations that started at or after this time (inclusive)").option("--until <rfc3339>", "only conversations that started before this time (exclusive)").option("--lead <lead>", LEAD_FILTER_HELP).option("--limit <n>", "page size (default 25, max 100)").option("--offset <n>", "rows to skip").option("--cursor <cursor>", "next_cursor from the preceding page (stable paging)").option("--json", "print the raw JSON result instead of a table").action(
|
|
5271
5300
|
async (opts) => {
|
|
5272
5301
|
try {
|
|
5273
5302
|
const res = await new ErdoClient().listWidgetConversations({
|
|
5274
5303
|
widget: opts.widget,
|
|
5275
5304
|
since: opts.since,
|
|
5276
5305
|
until: opts.until,
|
|
5306
|
+
canonical_lead_id: opts.lead,
|
|
5277
5307
|
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
5278
5308
|
offset: opts.offset ? Number(opts.offset) : void 0,
|
|
5279
5309
|
cursor: opts.cursor
|
|
@@ -5296,6 +5326,7 @@ widgetConversationsCmd.command("list").description("List the organization's widg
|
|
|
5296
5326
|
"transcript",
|
|
5297
5327
|
"contact",
|
|
5298
5328
|
"lead",
|
|
5329
|
+
"lead ref",
|
|
5299
5330
|
"started",
|
|
5300
5331
|
"summary"
|
|
5301
5332
|
],
|
|
@@ -5307,6 +5338,7 @@ widgetConversationsCmd.command("list").description("List the organization's widg
|
|
|
5307
5338
|
c.has_transcript ? "yes" : "no",
|
|
5308
5339
|
contactLabel(c.contact),
|
|
5309
5340
|
leadStatusLabel(c.lead_status),
|
|
5341
|
+
leadReferenceLabel(c.lead_reference),
|
|
5310
5342
|
c.created_at,
|
|
5311
5343
|
c.transcript_summary
|
|
5312
5344
|
])
|
|
@@ -5325,7 +5357,7 @@ widgetConversationsCmd.command("list").description("List the organization's widg
|
|
|
5325
5357
|
}
|
|
5326
5358
|
);
|
|
5327
5359
|
widgetConversationsCmd.command("get <sessionID>").description(
|
|
5328
|
-
"Read one widget conversation in full: transcript, summary, topics, per-turn LLM metrics, and the lead it produced (contact, lead_status, lead_saved_at)"
|
|
5360
|
+
"Read one widget conversation in full: transcript, summary, topics, per-turn LLM metrics, and the lead it produced (contact, lead_status, lead_saved_at, canonical_lead_id, lead_reference)"
|
|
5329
5361
|
).option("--widget <handle>", "the widget it belongs to \u2014 optional, the session id identifies it").action(async (sessionID, opts) => {
|
|
5330
5362
|
try {
|
|
5331
5363
|
print(await new ErdoClient().getWidgetConversation(sessionID, opts.widget));
|
|
@@ -5334,12 +5366,13 @@ widgetConversationsCmd.command("get <sessionID>").description(
|
|
|
5334
5366
|
}
|
|
5335
5367
|
});
|
|
5336
5368
|
var voiceSMSCmd = voiceCmd.command("sms").description("Text conversations on a voice agent's own number");
|
|
5337
|
-
voiceSMSCmd.command("list").description("List the organization's SMS conversations, most recently active first").option("--phone-number <number>", "only conversations on this Erdo number (any format)").option("--address <number>", "only the conversation with this person (any format)").option("--limit <n>", "page size (default 25, max 100)").option("--cursor <cursor>", "next_cursor from the preceding page (stable paging)").option("--json", "print the raw JSON result instead of a table").action(
|
|
5369
|
+
voiceSMSCmd.command("list").description("List the organization's SMS conversations, most recently active first").option("--phone-number <number>", "only conversations on this Erdo number (any format)").option("--address <number>", "only the conversation with this person (any format)").option("--lead <lead>", LEAD_FILTER_HELP).option("--limit <n>", "page size (default 25, max 100)").option("--cursor <cursor>", "next_cursor from the preceding page (stable paging)").option("--json", "print the raw JSON result instead of a table").action(
|
|
5338
5370
|
async (opts) => {
|
|
5339
5371
|
try {
|
|
5340
5372
|
const res = await new ErdoClient().listSMSConversations({
|
|
5341
5373
|
phone_number: opts.phoneNumber,
|
|
5342
5374
|
address: opts.address,
|
|
5375
|
+
canonical_lead_id: opts.lead,
|
|
5343
5376
|
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
5344
5377
|
cursor: opts.cursor
|
|
5345
5378
|
});
|
|
@@ -5353,7 +5386,7 @@ voiceSMSCmd.command("list").description("List the organization's SMS conversatio
|
|
|
5353
5386
|
return;
|
|
5354
5387
|
}
|
|
5355
5388
|
printAlignedTable(
|
|
5356
|
-
["session id", "phone", "address", "msgs", "last inbound", "last outbound", "summary"],
|
|
5389
|
+
["session id", "phone", "address", "msgs", "last inbound", "last outbound", "lead ref", "summary"],
|
|
5357
5390
|
conversations.map((c) => [
|
|
5358
5391
|
c.session_id,
|
|
5359
5392
|
c.phone_number,
|
|
@@ -5363,6 +5396,7 @@ voiceSMSCmd.command("list").description("List the organization's SMS conversatio
|
|
|
5363
5396
|
c.message_count,
|
|
5364
5397
|
c.last_inbound_at ?? "",
|
|
5365
5398
|
c.last_outbound_at ?? "",
|
|
5399
|
+
leadReferenceLabel(c.lead_reference),
|
|
5366
5400
|
c.summary
|
|
5367
5401
|
])
|
|
5368
5402
|
);
|
|
@@ -5409,6 +5443,9 @@ voiceSMSCmd.command("get <sessionID>").description("Read one SMS conversation: i
|
|
|
5409
5443
|
console.log(`
|
|
5410
5444
|
contact: ${parts.join(" \xB7 ")}`);
|
|
5411
5445
|
}
|
|
5446
|
+
if (c.lead_reference) {
|
|
5447
|
+
console.log(`lead: ${c.lead_reference} (${c.canonical_lead_id})`);
|
|
5448
|
+
}
|
|
5412
5449
|
const messages = res.messages ?? [];
|
|
5413
5450
|
if (messages.length === 0) {
|
|
5414
5451
|
console.log("\nNo texts have been recorded in this conversation yet.");
|
|
@@ -5653,6 +5690,49 @@ datasetsCmd.command("set-purpose <slug> [purpose]").description(
|
|
|
5653
5690
|
fail(e);
|
|
5654
5691
|
}
|
|
5655
5692
|
});
|
|
5693
|
+
var leadIdentityCmd = datasetsCmd.command("lead-identity").description(
|
|
5694
|
+
"Permanent lead identity \u2014 one lead is one row, however many ways they got in touch. Migrating a dataset gives every row a canonical_lead_id, points every pipeline writer at it, and makes the resolver the only thing that decides whether a submission is a new lead or an existing one."
|
|
5695
|
+
);
|
|
5696
|
+
leadIdentityCmd.command("maintain <slug>").description(
|
|
5697
|
+
"Preview or perform the migration for one dataset. Without --apply it mutates nothing and reports readiness: whether the dataset is ready, what blocks it, and how many rows would be assigned an identity. Run the preview first and read the blockers \u2014 apply refuses outright while any consumer is incompatible, unverifiable, queued or running, because the backfill rewrites the file every one of them reads."
|
|
5698
|
+
).option(
|
|
5699
|
+
"--apply",
|
|
5700
|
+
"perform the migration rather than preview it; requires --operation-key"
|
|
5701
|
+
).option(
|
|
5702
|
+
"--operation-key <key>",
|
|
5703
|
+
"the migration receipt. Replaying the same key settles an apply whose response was lost; a different key against an already-migrated dataset is refused rather than migrating it twice."
|
|
5704
|
+
).option("--email-column <column>", "the dataset's email column (detected when omitted)").option("--phone-column <column>", "the dataset's phone column (detected when omitted)").option(
|
|
5705
|
+
"--conversion-id-column <column>",
|
|
5706
|
+
"optional conversion id column; must differ from both contact columns"
|
|
5707
|
+
).option(
|
|
5708
|
+
"--default-calling-code <code>",
|
|
5709
|
+
"calling code a phone written in national form belongs to, so one number written two ways is one lead (default 1)"
|
|
5710
|
+
).action(
|
|
5711
|
+
async (slug, opts) => {
|
|
5712
|
+
try {
|
|
5713
|
+
if (opts.apply && !opts.operationKey) {
|
|
5714
|
+
fail(
|
|
5715
|
+
new Error(
|
|
5716
|
+
"--apply requires --operation-key so an uncertain retry can settle rather than migrating twice"
|
|
5717
|
+
)
|
|
5718
|
+
);
|
|
5719
|
+
return;
|
|
5720
|
+
}
|
|
5721
|
+
print(
|
|
5722
|
+
await new ErdoClient().maintainLeadIdentity(slug, {
|
|
5723
|
+
apply: Boolean(opts.apply),
|
|
5724
|
+
operation_key: opts.operationKey,
|
|
5725
|
+
email_column: opts.emailColumn,
|
|
5726
|
+
phone_column: opts.phoneColumn,
|
|
5727
|
+
conversion_id_column: opts.conversionIdColumn,
|
|
5728
|
+
default_calling_code: opts.defaultCallingCode
|
|
5729
|
+
})
|
|
5730
|
+
);
|
|
5731
|
+
} catch (e) {
|
|
5732
|
+
fail(e);
|
|
5733
|
+
}
|
|
5734
|
+
}
|
|
5735
|
+
);
|
|
5656
5736
|
datasetsCmd.command("query <slug> <question>").description(
|
|
5657
5737
|
"Ask a natural-language question of a dataset \u2014 Erdo writes and runs the SQL, and answers with that SQL alongside the values. It runs an agent, so it is slower and two identical questions can produce two different queries: for a deterministic or scripted read, write the SQL yourself with `datasets fetch --sql`."
|
|
5658
5738
|
).action(async (slug, question) => {
|
|
@@ -6230,8 +6310,20 @@ function printLead(lead) {
|
|
|
6230
6310
|
if (lead.captures?.length) {
|
|
6231
6311
|
console.log("\ncaptures");
|
|
6232
6312
|
printAlignedTable(
|
|
6233
|
-
["when", "producer", "resolution", "conversion"],
|
|
6234
|
-
lead.captures.map((c) => [c.created_at, c.producer, c.resolution, c.conversion_status])
|
|
6313
|
+
["when", "producer", "pipeline", "resolution", "conversion"],
|
|
6314
|
+
lead.captures.map((c) => [c.created_at, c.producer, c.pipeline_id ?? "", c.resolution, c.conversion_status])
|
|
6315
|
+
);
|
|
6316
|
+
}
|
|
6317
|
+
if (lead.refused_captures?.length) {
|
|
6318
|
+
console.log("\nrefused captures (this lead was one of the candidates)");
|
|
6319
|
+
printAlignedTable(
|
|
6320
|
+
["when", "pipeline", "resolution", "candidates"],
|
|
6321
|
+
lead.refused_captures.map((c) => [
|
|
6322
|
+
c.created_at,
|
|
6323
|
+
c.pipeline_id ?? "",
|
|
6324
|
+
c.resolution,
|
|
6325
|
+
(c.candidate_lead_ids ?? []).join(", ")
|
|
6326
|
+
])
|
|
6235
6327
|
);
|
|
6236
6328
|
}
|
|
6237
6329
|
if (lead.row) {
|