@erdoai/cli 0.105.0 → 0.108.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 +176 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -576,6 +576,31 @@ var ErdoClient = class {
|
|
|
576
576
|
`/v1/voice/sms-conversations/${encodeURIComponent(sessionID)}${qs ? `?${qs}` : ""}`
|
|
577
577
|
);
|
|
578
578
|
}
|
|
579
|
+
// WhatsApp conversations on those same numbers — their own resource rather
|
|
580
|
+
// than a channel on the SMS reads, addressed and filtered the same way.
|
|
581
|
+
listWhatsAppConversations(params) {
|
|
582
|
+
const q = new URLSearchParams();
|
|
583
|
+
if (params?.phone_number) q.set("phone_number", params.phone_number);
|
|
584
|
+
if (params?.address) q.set("address", params.address);
|
|
585
|
+
if (params?.canonical_lead_id) q.set("canonical_lead_id", params.canonical_lead_id);
|
|
586
|
+
if (params?.limit !== void 0) q.set("limit", String(params.limit));
|
|
587
|
+
if (params?.cursor) q.set("cursor", params.cursor);
|
|
588
|
+
const qs = q.toString();
|
|
589
|
+
return this.request(
|
|
590
|
+
"GET",
|
|
591
|
+
`/v1/voice/whatsapp-conversations${qs ? `?${qs}` : ""}`
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
getWhatsAppConversation(sessionID, params) {
|
|
595
|
+
const q = new URLSearchParams();
|
|
596
|
+
if (params?.limit !== void 0) q.set("limit", String(params.limit));
|
|
597
|
+
if (params?.cursor) q.set("cursor", params.cursor);
|
|
598
|
+
const qs = q.toString();
|
|
599
|
+
return this.request(
|
|
600
|
+
"GET",
|
|
601
|
+
`/v1/voice/whatsapp-conversations/${encodeURIComponent(sessionID)}${qs ? `?${qs}` : ""}`
|
|
602
|
+
);
|
|
603
|
+
}
|
|
579
604
|
// The organization's business profile: its legal identity, which every A2P
|
|
580
605
|
// registration on the account is checked against. It follows the org
|
|
581
606
|
// convention rather than the voice one because it is the organization's, not
|
|
@@ -5968,6 +5993,127 @@ contact: ${parts.join(" \xB7 ")}`);
|
|
|
5968
5993
|
}
|
|
5969
5994
|
}
|
|
5970
5995
|
);
|
|
5996
|
+
var voiceWhatsAppCmd = voiceCmd.command("whatsapp").description("WhatsApp conversations on a voice agent's own number");
|
|
5997
|
+
voiceWhatsAppCmd.command("list").description("List the organization's WhatsApp 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(
|
|
5998
|
+
async (opts) => {
|
|
5999
|
+
try {
|
|
6000
|
+
const res = await new ErdoClient().listWhatsAppConversations({
|
|
6001
|
+
phone_number: opts.phoneNumber,
|
|
6002
|
+
address: opts.address,
|
|
6003
|
+
canonical_lead_id: opts.lead,
|
|
6004
|
+
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
6005
|
+
cursor: opts.cursor
|
|
6006
|
+
});
|
|
6007
|
+
if (opts.json) {
|
|
6008
|
+
print(res);
|
|
6009
|
+
return;
|
|
6010
|
+
}
|
|
6011
|
+
const conversations = res.conversations ?? [];
|
|
6012
|
+
if (conversations.length === 0) {
|
|
6013
|
+
console.log("No WhatsApp conversations match those filters.");
|
|
6014
|
+
return;
|
|
6015
|
+
}
|
|
6016
|
+
printAlignedTable(
|
|
6017
|
+
["session id", "phone", "address", "name", "msgs", "last inbound", "last outbound", "lead ref", "summary"],
|
|
6018
|
+
conversations.map((c) => [
|
|
6019
|
+
c.session_id,
|
|
6020
|
+
c.phone_number,
|
|
6021
|
+
c.suppressed ? `${c.address} (suppressed)` : c.address,
|
|
6022
|
+
c.display_name ?? "",
|
|
6023
|
+
c.message_count,
|
|
6024
|
+
c.last_inbound_at ?? "",
|
|
6025
|
+
c.last_outbound_at ?? "",
|
|
6026
|
+
leadReferenceLabel(c.lead_reference),
|
|
6027
|
+
c.summary
|
|
6028
|
+
])
|
|
6029
|
+
);
|
|
6030
|
+
process.stderr.write(`showing ${conversations.length} conversation(s)
|
|
6031
|
+
`);
|
|
6032
|
+
if (res.next_cursor) {
|
|
6033
|
+
process.stderr.write(`more available \u2014 re-run with --cursor ${res.next_cursor}
|
|
6034
|
+
`);
|
|
6035
|
+
}
|
|
6036
|
+
} catch (e) {
|
|
6037
|
+
fail(e);
|
|
6038
|
+
}
|
|
6039
|
+
}
|
|
6040
|
+
);
|
|
6041
|
+
function whatsAppMessageText(m) {
|
|
6042
|
+
const parts = [];
|
|
6043
|
+
if (m.body) parts.push(m.body);
|
|
6044
|
+
if (m.location) {
|
|
6045
|
+
const where = [m.location.label, m.location.address].filter(Boolean).join(", ");
|
|
6046
|
+
parts.push(`[location ${m.location.latitude},${m.location.longitude}${where ? ` ${where}` : ""}]`);
|
|
6047
|
+
}
|
|
6048
|
+
if (m.context?.button_text) parts.push(`[tapped "${m.context.button_text}"]`);
|
|
6049
|
+
else if (m.context?.replied_message_id) parts.push(`[reply to ${m.context.replied_message_id}]`);
|
|
6050
|
+
if (m.referral) parts.push(`[from ad${m.referral.Headline ? ` "${m.referral.Headline}"` : ""}]`);
|
|
6051
|
+
if (parts.length === 0 && m.message_type && m.message_type !== "text") parts.push(`[${m.message_type}]`);
|
|
6052
|
+
return parts.join(" ");
|
|
6053
|
+
}
|
|
6054
|
+
voiceWhatsAppCmd.command("get <sessionID>").description("Read one WhatsApp conversation: its metadata, then every message oldest first").option("--limit <n>", "messages per page (default 50, max 200)").option("--cursor <cursor>", "next_cursor from the preceding page of messages").option("--json", "print the raw JSON result instead of a table").action(
|
|
6055
|
+
async (sessionID, opts) => {
|
|
6056
|
+
try {
|
|
6057
|
+
const res = await new ErdoClient().getWhatsAppConversation(sessionID, {
|
|
6058
|
+
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
6059
|
+
cursor: opts.cursor
|
|
6060
|
+
});
|
|
6061
|
+
if (opts.json) {
|
|
6062
|
+
print(res);
|
|
6063
|
+
return;
|
|
6064
|
+
}
|
|
6065
|
+
const c = res.conversation;
|
|
6066
|
+
printAlignedTable(
|
|
6067
|
+
["session id", "phone", "address", "name", "msgs", "last inbound", "last outbound", "summary"],
|
|
6068
|
+
[
|
|
6069
|
+
[
|
|
6070
|
+
c.session_id,
|
|
6071
|
+
c.phone_number,
|
|
6072
|
+
c.suppressed ? `${c.address} (suppressed)` : c.address,
|
|
6073
|
+
c.display_name ?? "",
|
|
6074
|
+
c.message_count,
|
|
6075
|
+
c.last_inbound_at ?? "",
|
|
6076
|
+
c.last_outbound_at ?? "",
|
|
6077
|
+
c.summary
|
|
6078
|
+
]
|
|
6079
|
+
]
|
|
6080
|
+
);
|
|
6081
|
+
const contact = c.contact;
|
|
6082
|
+
if (contact && (contact.name || contact.email || contact.phone || contact.company)) {
|
|
6083
|
+
const parts = [contact.name, contact.email, contact.phone, contact.company].filter(Boolean);
|
|
6084
|
+
console.log(`
|
|
6085
|
+
contact: ${parts.join(" \xB7 ")}`);
|
|
6086
|
+
}
|
|
6087
|
+
if (c.lead_reference) {
|
|
6088
|
+
console.log(`lead: ${c.lead_reference} (${c.canonical_lead_id})`);
|
|
6089
|
+
}
|
|
6090
|
+
const messages = res.messages ?? [];
|
|
6091
|
+
if (messages.length === 0) {
|
|
6092
|
+
console.log("\nNo messages have been recorded in this conversation yet.");
|
|
6093
|
+
return;
|
|
6094
|
+
}
|
|
6095
|
+
console.log("");
|
|
6096
|
+
printAlignedTable(
|
|
6097
|
+
["at", "direction", "status", "media", "message"],
|
|
6098
|
+
messages.map((m) => [
|
|
6099
|
+
m.created_at,
|
|
6100
|
+
m.direction,
|
|
6101
|
+
m.status,
|
|
6102
|
+
m.num_media > 0 ? m.num_media : "",
|
|
6103
|
+
whatsAppMessageText(m)
|
|
6104
|
+
])
|
|
6105
|
+
);
|
|
6106
|
+
process.stderr.write(`showing ${messages.length} message(s), oldest first
|
|
6107
|
+
`);
|
|
6108
|
+
if (res.next_cursor) {
|
|
6109
|
+
process.stderr.write(`more available \u2014 re-run with --cursor ${res.next_cursor}
|
|
6110
|
+
`);
|
|
6111
|
+
}
|
|
6112
|
+
} catch (e) {
|
|
6113
|
+
fail(e);
|
|
6114
|
+
}
|
|
6115
|
+
}
|
|
6116
|
+
);
|
|
5971
6117
|
var voiceNumbersCmd = voiceCmd.command("numbers").description("The organization's phone numbers, their A2P SMS registration and their caller name");
|
|
5972
6118
|
function printRegistrationState(registration) {
|
|
5973
6119
|
if (registration === null || typeof registration !== "object") {
|
|
@@ -6913,7 +7059,7 @@ var leadsCmd = program.command("leads").description(
|
|
|
6913
7059
|
"Read leads in a lead dataset, merge two that are the same person, and run the lead playbook that suggests each lead's next action"
|
|
6914
7060
|
);
|
|
6915
7061
|
leadsCmd.command("get <dataset> <lead>").description(
|
|
6916
|
-
"Read one lead: its permanent canonical_lead_id, the email and phone evidence bound to it, its dataset row, the leads merged into it, and its capture history. <lead> is either the canonical_lead_id UUID or the 22-character lead reference, and an id that has since been merged away resolves to the lead it became \u2014 so an id read off an old capture response still works."
|
|
7062
|
+
"Read one lead: its permanent canonical_lead_id, what it told us (on the form and in every call, chat, text and email reply since), the email and phone evidence bound to it, its dataset row, the leads merged into it, and its capture history. <lead> is either the canonical_lead_id UUID or the 22-character lead reference, and an id that has since been merged away resolves to the lead it became \u2014 so an id read off an old capture response still works."
|
|
6917
7063
|
).option("--json", "print the raw JSON result instead of a summary").action(async (dataset, lead, opts) => {
|
|
6918
7064
|
try {
|
|
6919
7065
|
const res = await new ErdoClient().getLead(dataset, lead);
|
|
@@ -6999,7 +7145,7 @@ leadsCmd.command("merge <dataset> <survivor>").description(
|
|
|
6999
7145
|
console.log(`Already one lead: ${res.absorbed_lead_id} resolves to ${res.lead.canonical_lead_id}.`);
|
|
7000
7146
|
} else {
|
|
7001
7147
|
console.log(
|
|
7002
|
-
`Merged ${res.absorbed_lead_id} into ${res.lead.canonical_lead_id} \u2014 ${res.aliases_moved} contact ${res.aliases_moved === 1 ? "alias" : "aliases"} moved, ${res.rows_combined} ${res.rows_combined === 1 ? "row" : "rows"} combined.`
|
|
7148
|
+
`Merged ${res.absorbed_lead_id} into ${res.lead.canonical_lead_id} \u2014 ${res.aliases_moved} contact ${res.aliases_moved === 1 ? "alias" : "aliases"} and ${res.details_moved ?? 0} ${res.details_moved === 1 ? "detail" : "details"} moved, ${res.rows_combined} ${res.rows_combined === 1 ? "row" : "rows"} combined.`
|
|
7003
7149
|
);
|
|
7004
7150
|
}
|
|
7005
7151
|
printLead(res.lead);
|
|
@@ -7406,6 +7552,24 @@ function printRecordedLeadDecision(a) {
|
|
|
7406
7552
|
console.log(`Recorded ${a.action_kind} on lead ${a.lead_reference}${a.action_kind === "hold" ? until : ""}.`);
|
|
7407
7553
|
if (a.rationale) console.log(a.rationale);
|
|
7408
7554
|
}
|
|
7555
|
+
function leadDetailSource(kind) {
|
|
7556
|
+
switch (kind) {
|
|
7557
|
+
case "form":
|
|
7558
|
+
return "form";
|
|
7559
|
+
case "phone_call":
|
|
7560
|
+
return "call";
|
|
7561
|
+
case "widget_conversation":
|
|
7562
|
+
return "website chat";
|
|
7563
|
+
case "sms":
|
|
7564
|
+
return "text";
|
|
7565
|
+
case "whatsapp":
|
|
7566
|
+
return "WhatsApp";
|
|
7567
|
+
case "email_reply":
|
|
7568
|
+
return "email reply";
|
|
7569
|
+
default:
|
|
7570
|
+
return kind;
|
|
7571
|
+
}
|
|
7572
|
+
}
|
|
7409
7573
|
function printLead(lead) {
|
|
7410
7574
|
console.log(`lead ${lead.canonical_lead_id}`);
|
|
7411
7575
|
console.log(`reference ${lead.reference}`);
|
|
@@ -7419,6 +7583,16 @@ function printLead(lead) {
|
|
|
7419
7583
|
if (lead.absorbed_lead_ids?.length) {
|
|
7420
7584
|
console.log(`absorbed ${lead.absorbed_lead_ids.join(", ")}`);
|
|
7421
7585
|
}
|
|
7586
|
+
if (lead.details?.length) {
|
|
7587
|
+
console.log("\nwhat they told us");
|
|
7588
|
+
printAlignedTable(
|
|
7589
|
+
["question", "answer", "from", "when"],
|
|
7590
|
+
lead.details.flatMap((d) => [
|
|
7591
|
+
[d.label, d.value, leadDetailSource(d.source_kind), d.observed_at],
|
|
7592
|
+
...(d.earlier ?? []).map((e) => ["", ` earlier: ${e.value}`, leadDetailSource(e.source_kind), e.observed_at])
|
|
7593
|
+
])
|
|
7594
|
+
);
|
|
7595
|
+
}
|
|
7422
7596
|
if (lead.aliases?.length) {
|
|
7423
7597
|
console.log("\ncontact evidence");
|
|
7424
7598
|
printAlignedTable(
|