@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.
Files changed (2) hide show
  1. package/dist/index.js +149 -7
  2. 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. `replaceCurrentAccount` is the
346
- // one input with consequences: provisioning refuses outright when the org is
347
- // already running campaigns in another account, and passing this accepts that
348
- // those campaigns are left behind, so the key is sent only when it is asked
349
- // for rather than as a `false` the server has to read.
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>",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.80.0",
3
+ "version": "0.82.0",
4
4
  "description": "Erdo CLI \u2014 drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {