@erdoai/cli 0.80.0 → 0.82.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 +149 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -342,13 +342,18 @@ var ErdoClient = class {
|
|
|
342
342
|
);
|
|
343
343
|
}
|
|
344
344
|
// Creates the container — a sub-account under the manager's MCC plus the
|
|
345
|
-
// delegated connection inside the managed org. `
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
345
|
+
// delegated connection inside the managed org. `customerId` switches the call
|
|
346
|
+
// from creating an account to adopting one that already sits directly under
|
|
347
|
+
// the manager, which is the only way to attach a sub-account somebody made by
|
|
348
|
+
// hand: Google Ads cannot delete an account, so creating a second one beside
|
|
349
|
+
// it is permanent. `replaceCurrentAccount` is the one input with consequences:
|
|
350
|
+
// provisioning refuses outright when the org is already running campaigns in
|
|
351
|
+
// another account, and passing this accepts that those campaigns are left
|
|
352
|
+
// behind, so the key is sent only when it is asked for rather than as a
|
|
353
|
+
// `false` the server has to read.
|
|
350
354
|
provisionManagedOrganizationAdsContainer(slug, input) {
|
|
351
355
|
const body = {};
|
|
356
|
+
if (input.customerId) body.customer_id = input.customerId;
|
|
352
357
|
if (input.descriptiveName) body.descriptive_name = input.descriptiveName;
|
|
353
358
|
if (input.currencyCode) body.currency_code = input.currencyCode;
|
|
354
359
|
if (input.timeZone) body.time_zone = input.timeZone;
|
|
@@ -508,6 +513,32 @@ var ErdoClient = class {
|
|
|
508
513
|
{ session_id: sessionID, ...widget ? { widget } : {} }
|
|
509
514
|
);
|
|
510
515
|
}
|
|
516
|
+
// Text conversations on those same numbers. A conversation is one Erdo number
|
|
517
|
+
// and one person, for as long as they keep texting — so it is addressed by its
|
|
518
|
+
// session id rather than by a message id, and narrowed by the two numbers
|
|
519
|
+
// rather than by an agent slug and a direction.
|
|
520
|
+
listSMSConversations(params) {
|
|
521
|
+
const q = new URLSearchParams();
|
|
522
|
+
if (params?.phone_number) q.set("phone_number", params.phone_number);
|
|
523
|
+
if (params?.address) q.set("address", params.address);
|
|
524
|
+
if (params?.limit !== void 0) q.set("limit", String(params.limit));
|
|
525
|
+
if (params?.cursor) q.set("cursor", params.cursor);
|
|
526
|
+
const qs = q.toString();
|
|
527
|
+
return this.request(
|
|
528
|
+
"GET",
|
|
529
|
+
`/v1/voice/sms-conversations${qs ? `?${qs}` : ""}`
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
getSMSConversation(sessionID, params) {
|
|
533
|
+
const q = new URLSearchParams();
|
|
534
|
+
if (params?.limit !== void 0) q.set("limit", String(params.limit));
|
|
535
|
+
if (params?.cursor) q.set("cursor", params.cursor);
|
|
536
|
+
const qs = q.toString();
|
|
537
|
+
return this.request(
|
|
538
|
+
"GET",
|
|
539
|
+
`/v1/voice/sms-conversations/${encodeURIComponent(sessionID)}${qs ? `?${qs}` : ""}`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
511
542
|
// Run a read-only HogQL query against the org's page-analytics events. Rows are
|
|
512
543
|
// positional per columns; enabled:false means page analytics is off for the org
|
|
513
544
|
// (not zero traffic). A rejected query surfaces PostHog's message as the error.
|
|
@@ -2127,6 +2158,9 @@ function printAdsContainer(c) {
|
|
|
2127
2158
|
managed.command("ads-container <orgSlug>").description(
|
|
2128
2159
|
"Read the Google Ads account a managed org runs its campaigns in, or --provision one under your manager account"
|
|
2129
2160
|
).option("--provision", "create the account and the org's delegated google_ads connection instead of reading them").option(
|
|
2161
|
+
"--adopt <customerId>",
|
|
2162
|
+
"with --provision: connect an existing client account already sitting directly under your manager account instead of creating a new one \u2014 for a sub-account created by hand in the Google Ads UI"
|
|
2163
|
+
).option(
|
|
2130
2164
|
"--replace-current-account",
|
|
2131
2165
|
"with --provision: go ahead even though the org already runs campaigns in another account. Google Ads cannot move a campaign, so those campaigns stay behind and have to be rebuilt"
|
|
2132
2166
|
).option("--name <descriptiveName>", "display name for the new account (default: the org's name and slug)").option("--currency <code>", "ISO currency code for the new account, e.g. USD").option("--time-zone <tz>", "IANA time zone for the new account, e.g. America/New_York").action(
|
|
@@ -2135,6 +2169,16 @@ managed.command("ads-container <orgSlug>").description(
|
|
|
2135
2169
|
if (opts.replaceCurrentAccount && !opts.provision) {
|
|
2136
2170
|
fail(new Error("--replace-current-account only applies with --provision"));
|
|
2137
2171
|
}
|
|
2172
|
+
if (opts.adopt && !opts.provision) {
|
|
2173
|
+
fail(new Error("--adopt only applies with --provision"));
|
|
2174
|
+
}
|
|
2175
|
+
if (opts.adopt && (opts.name || opts.currency || opts.timeZone)) {
|
|
2176
|
+
fail(
|
|
2177
|
+
new Error(
|
|
2178
|
+
"--adopt takes an account that already exists, so --name, --currency and --time-zone do not apply \u2014 they describe an account being created"
|
|
2179
|
+
)
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
2138
2182
|
const api = new ErdoClient();
|
|
2139
2183
|
if (!opts.provision) {
|
|
2140
2184
|
try {
|
|
@@ -2152,13 +2196,14 @@ managed.command("ads-container <orgSlug>").description(
|
|
|
2152
2196
|
return;
|
|
2153
2197
|
}
|
|
2154
2198
|
const c = await api.provisionManagedOrganizationAdsContainer(orgSlug, {
|
|
2199
|
+
customerId: opts.adopt,
|
|
2155
2200
|
descriptiveName: opts.name,
|
|
2156
2201
|
currencyCode: opts.currency,
|
|
2157
2202
|
timeZone: opts.timeZone,
|
|
2158
2203
|
replaceCurrentAccount: opts.replaceCurrentAccount
|
|
2159
2204
|
});
|
|
2160
2205
|
console.log(
|
|
2161
|
-
c.already_provisioned ? `Google Ads container already existed for ${c.org_slug}` : `Created Google Ads container for ${c.org_slug}`
|
|
2206
|
+
c.already_provisioned ? `Google Ads container already existed for ${c.org_slug}` : c.adopted ? `Adopted Google Ads account ${c.customer_id} as the container for ${c.org_slug}` : `Created Google Ads container for ${c.org_slug}`
|
|
2162
2207
|
);
|
|
2163
2208
|
printAdsContainer(c);
|
|
2164
2209
|
const leftBehind = c.previous_campaign_count ?? 0;
|
|
@@ -4846,7 +4891,7 @@ sentEmailsCmd.command("get <emailID>").description("Read one sent email, includi
|
|
|
4846
4891
|
fail(e);
|
|
4847
4892
|
}
|
|
4848
4893
|
});
|
|
4849
|
-
var voiceCmd = program.command("voice").description("Read voice agent phone calls and website widget conversations");
|
|
4894
|
+
var voiceCmd = program.command("voice").description("Read a voice agent's phone calls, text conversations and website widget conversations");
|
|
4850
4895
|
function contactLabel(contact) {
|
|
4851
4896
|
if (!contact) return "-";
|
|
4852
4897
|
const name = [contact.first_name, contact.last_name].filter(Boolean).join(" ");
|
|
@@ -4992,6 +5037,103 @@ widgetConversationsCmd.command("get <sessionID>").description(
|
|
|
4992
5037
|
fail(e);
|
|
4993
5038
|
}
|
|
4994
5039
|
});
|
|
5040
|
+
var voiceSMSCmd = voiceCmd.command("sms").description("Text conversations on a voice agent's own number");
|
|
5041
|
+
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(
|
|
5042
|
+
async (opts) => {
|
|
5043
|
+
try {
|
|
5044
|
+
const res = await new ErdoClient().listSMSConversations({
|
|
5045
|
+
phone_number: opts.phoneNumber,
|
|
5046
|
+
address: opts.address,
|
|
5047
|
+
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
5048
|
+
cursor: opts.cursor
|
|
5049
|
+
});
|
|
5050
|
+
if (opts.json) {
|
|
5051
|
+
print(res);
|
|
5052
|
+
return;
|
|
5053
|
+
}
|
|
5054
|
+
const conversations = res.conversations ?? [];
|
|
5055
|
+
if (conversations.length === 0) {
|
|
5056
|
+
console.log("No SMS conversations match those filters.");
|
|
5057
|
+
return;
|
|
5058
|
+
}
|
|
5059
|
+
printAlignedTable(
|
|
5060
|
+
["session id", "phone", "address", "msgs", "last inbound", "last outbound", "summary"],
|
|
5061
|
+
conversations.map((c) => [
|
|
5062
|
+
c.session_id,
|
|
5063
|
+
c.phone_number,
|
|
5064
|
+
// An opted-out conversation is marked on the person, since that is
|
|
5065
|
+
// what the flag governs: nothing may text THEM until they send START.
|
|
5066
|
+
c.suppressed ? `${c.address} (suppressed)` : c.address,
|
|
5067
|
+
c.message_count,
|
|
5068
|
+
c.last_inbound_at ?? "",
|
|
5069
|
+
c.last_outbound_at ?? "",
|
|
5070
|
+
c.summary
|
|
5071
|
+
])
|
|
5072
|
+
);
|
|
5073
|
+
process.stderr.write(`showing ${conversations.length} conversation(s)
|
|
5074
|
+
`);
|
|
5075
|
+
if (res.next_cursor) {
|
|
5076
|
+
process.stderr.write(`more available \u2014 re-run with --cursor ${res.next_cursor}
|
|
5077
|
+
`);
|
|
5078
|
+
}
|
|
5079
|
+
} catch (e) {
|
|
5080
|
+
fail(e);
|
|
5081
|
+
}
|
|
5082
|
+
}
|
|
5083
|
+
);
|
|
5084
|
+
voiceSMSCmd.command("get <sessionID>").description("Read one SMS conversation: its metadata, then every text oldest first").option("--limit <n>", "texts per page (default 50, max 200)").option("--cursor <cursor>", "next_cursor from the preceding page of texts").option("--json", "print the raw JSON result instead of a table").action(
|
|
5085
|
+
async (sessionID, opts) => {
|
|
5086
|
+
try {
|
|
5087
|
+
const res = await new ErdoClient().getSMSConversation(sessionID, {
|
|
5088
|
+
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
5089
|
+
cursor: opts.cursor
|
|
5090
|
+
});
|
|
5091
|
+
if (opts.json) {
|
|
5092
|
+
print(res);
|
|
5093
|
+
return;
|
|
5094
|
+
}
|
|
5095
|
+
const c = res.conversation;
|
|
5096
|
+
printAlignedTable(
|
|
5097
|
+
["session id", "phone", "address", "msgs", "last inbound", "last outbound", "summary"],
|
|
5098
|
+
[
|
|
5099
|
+
[
|
|
5100
|
+
c.session_id,
|
|
5101
|
+
c.phone_number,
|
|
5102
|
+
c.suppressed ? `${c.address} (suppressed)` : c.address,
|
|
5103
|
+
c.message_count,
|
|
5104
|
+
c.last_inbound_at ?? "",
|
|
5105
|
+
c.last_outbound_at ?? "",
|
|
5106
|
+
c.summary
|
|
5107
|
+
]
|
|
5108
|
+
]
|
|
5109
|
+
);
|
|
5110
|
+
const messages = res.messages ?? [];
|
|
5111
|
+
if (messages.length === 0) {
|
|
5112
|
+
console.log("\nNo texts have been recorded in this conversation yet.");
|
|
5113
|
+
return;
|
|
5114
|
+
}
|
|
5115
|
+
console.log("");
|
|
5116
|
+
printAlignedTable(
|
|
5117
|
+
["at", "direction", "status", "media", "body"],
|
|
5118
|
+
messages.map((m) => [
|
|
5119
|
+
m.created_at,
|
|
5120
|
+
m.direction,
|
|
5121
|
+
m.status,
|
|
5122
|
+
m.num_media > 0 ? m.num_media : "",
|
|
5123
|
+
m.body
|
|
5124
|
+
])
|
|
5125
|
+
);
|
|
5126
|
+
process.stderr.write(`showing ${messages.length} text(s), oldest first
|
|
5127
|
+
`);
|
|
5128
|
+
if (res.next_cursor) {
|
|
5129
|
+
process.stderr.write(`more available \u2014 re-run with --cursor ${res.next_cursor}
|
|
5130
|
+
`);
|
|
5131
|
+
}
|
|
5132
|
+
} catch (e) {
|
|
5133
|
+
fail(e);
|
|
5134
|
+
}
|
|
5135
|
+
}
|
|
5136
|
+
);
|
|
4995
5137
|
var datasetsCmd = program.command("datasets").description("Datasets");
|
|
4996
5138
|
datasetsCmd.command("list").description("List datasets").option(
|
|
4997
5139
|
"--class <class>",
|