@erdoai/cli 0.141.0 → 0.144.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 +152 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -50,7 +50,7 @@ function describeRefreshWrites(strategy, mode, key, liveMode) {
50
50
  }
51
51
 
52
52
  // src/index.ts
53
- import { readFileSync as readFileSync4 } from "fs";
53
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
54
54
  import { basename } from "path";
55
55
  import { password, select } from "@inquirer/prompts";
56
56
  import { Command as Command2, Option as Option2 } from "commander";
@@ -699,6 +699,20 @@ var ErdoClient = class {
699
699
  provisionVoiceCallerName() {
700
700
  return this.request("POST", "/v1/voice/caller-name");
701
701
  }
702
+ getVoiceBrandedCalling() {
703
+ return this.request("GET", "/v1/voice/branded-calling");
704
+ }
705
+ setVoiceBrandedCalling(body) {
706
+ return this.request("PUT", "/v1/voice/branded-calling", body);
707
+ }
708
+ getVoiceBrandedCallingLOA() {
709
+ return this.request("GET", "/v1/voice/branded-calling/loa");
710
+ }
711
+ // Filing Branded Calling is billed per branded call, so this files the same
712
+ // approval-gated action the chat agent files.
713
+ provisionVoiceBrandedCalling() {
714
+ return this.request("POST", "/v1/voice/branded-calling/provision");
715
+ }
702
716
  // Turn an agent's answering of texts on or off. Only the switch changes: the
703
717
  // agent's persona, number and calls are untouched, and texts keep arriving and
704
718
  // stay readable either way.
@@ -6749,7 +6763,7 @@ voiceNumbersCmd.command("list").description("List the organization's numbers wit
6749
6763
  } else {
6750
6764
  const callable = new Set(res.person_call_numbers ?? []);
6751
6765
  printAlignedTable(
6752
- ["number", "agent", "kind", "calls", "status", "waiting on", "reason", "caller name", "shows", "attest."],
6766
+ ["number", "agent", "kind", "calls", "status", "waiting on", "reason", "caller name", "shows", "attest.", "brand"],
6753
6767
  numbers.map((n) => [
6754
6768
  n.address,
6755
6769
  n.agent_ref ?? "",
@@ -6760,7 +6774,8 @@ voiceNumbersCmd.command("list").description("List the organization's numbers wit
6760
6774
  n.reason ?? "",
6761
6775
  n.caller_name_attachment ?? "",
6762
6776
  describeObservedCallerName(n.caller_name_observed, n.observed_caller_name),
6763
- n.call_attestation ?? ""
6777
+ n.call_attestation ?? "",
6778
+ n.branded_calling_assignment ?? ""
6764
6779
  ])
6765
6780
  );
6766
6781
  process.stderr.write(`showing ${numbers.length} number(s)
@@ -6776,6 +6791,7 @@ voiceNumbersCmd.command("list").description("List the organization's numbers wit
6776
6791
  }
6777
6792
  printStaleCallerNameNote(numbers);
6778
6793
  printCallAttestationNote(res.call_attestation);
6794
+ printBrandedCallingNote(res.branded_calling);
6779
6795
  } catch (e) {
6780
6796
  fail(e);
6781
6797
  }
@@ -6835,6 +6851,16 @@ function printCallAttestationNote(attestation) {
6835
6851
  console.log("Complete the business profile with: erdo org business-profile set --help, or in Settings \u2192 Business.");
6836
6852
  }
6837
6853
  }
6854
+ function printBrandedCallingNote(branded) {
6855
+ if (!branded || branded.status === "approved") return;
6856
+ console.log("");
6857
+ const waiting = branded.next_step ? ` (waiting on ${branded.next_step})` : "";
6858
+ console.log(`Branded calling: ${branded.status}${waiting}.`);
6859
+ if (branded.reason) console.log(branded.reason);
6860
+ if (branded.missing_inputs && branded.missing_inputs.length > 0) {
6861
+ console.log(`Still needed: ${branded.missing_inputs.join(", ")} \u2014 erdo voice branded-calling set --help`);
6862
+ }
6863
+ }
6838
6864
  function describeObservedCallerName(observed, name) {
6839
6865
  switch (observed) {
6840
6866
  case "ours":
@@ -6955,6 +6981,129 @@ voiceCallerNameCmd.command("provision").description("Register the organization's
6955
6981
  fail(e);
6956
6982
  }
6957
6983
  });
6984
+ var voiceBrandedCallingCmd = voiceCmd.command("branded-calling").description("The brand (display name, logo, call reason) mobile phones show when the organization's numbers call");
6985
+ function printBrandedCallingStatus(status) {
6986
+ printAlignedTable(
6987
+ ["field", "value"],
6988
+ [
6989
+ ["status", status.status],
6990
+ ["waiting on", status.next_step ?? ""],
6991
+ ["reason", status.reason ?? status.not_registerable_reason ?? ""],
6992
+ ["display name", status.inputs.display_name || ""],
6993
+ ["legal name", status.legal_name ?? ""],
6994
+ ["call reason", status.inputs.call_reason || ""],
6995
+ ["logo", status.logo_url ?? "not uploaded"],
6996
+ ["signed LOA", status.loa ? status.loa.filename : "not uploaded \u2014 erdo voice branded-calling loa"],
6997
+ ["registered as", status.registered_display_name ?? ""],
6998
+ ["price", `$${status.price_per_call_usd} per call whose brand is delivered`]
6999
+ ]
7000
+ );
7001
+ if (status.numbers.length > 0) {
7002
+ console.log("");
7003
+ printAlignedTable(
7004
+ ["number", "agent", "assignment", "note"],
7005
+ status.numbers.map((n) => [
7006
+ n.phone_number,
7007
+ n.agent_slug ?? "",
7008
+ n.assignment,
7009
+ n.not_eligible_reason ?? n.assign_error ?? ""
7010
+ ])
7011
+ );
7012
+ }
7013
+ if (status.missing_inputs && status.missing_inputs.length > 0) {
7014
+ console.log("");
7015
+ console.log(`Still needed: ${status.missing_inputs.join(", ")} \u2014 erdo voice branded-calling set --help`);
7016
+ }
7017
+ if (status.missing_profile_fields && status.missing_profile_fields.length > 0) {
7018
+ console.log(`The business profile still needs: ${status.missing_profile_fields.join(", ")}`);
7019
+ }
7020
+ if (status.out_of_date) {
7021
+ console.log("");
7022
+ console.log(
7023
+ "The details changed since they were filed. Register again with: erdo voice branded-calling provision (it replaces the registration; calls show no brand until the new one is approved)."
7024
+ );
7025
+ }
7026
+ }
7027
+ function fileAsBase64(path) {
7028
+ return path ? readFileSync4(path).toString("base64") : void 0;
7029
+ }
7030
+ voiceBrandedCallingCmd.command("get").description("Show the brand, what it still needs, and each number's part in it").option("--json", "print the raw JSON result instead of a table").action(async (opts) => {
7031
+ try {
7032
+ const res = await new ErdoClient().getVoiceBrandedCalling();
7033
+ if (opts.json) {
7034
+ print(res);
7035
+ return;
7036
+ }
7037
+ printBrandedCallingStatus(res);
7038
+ } catch (e) {
7039
+ fail(e);
7040
+ }
7041
+ });
7042
+ voiceBrandedCallingCmd.command("set").description(
7043
+ "Store the brand's details (admins only). Omitted options keep their values. Nothing reaches Twilio until erdo voice branded-calling provision is approved."
7044
+ ).option(
7045
+ "--display-name <name>",
7046
+ "shown on the phone, at most 35 characters; defaults to the legal name. Twilio only accepts the legal name or a name legally registered to the business, and rejects the registration otherwise"
7047
+ ).option("--call-reason <text>", 'the line under the name, at most 64 characters, e.g. "Your viewing request"').option("--use-case <category>", "CUSTOMER_CARE, ACCOUNT_NOTIFICATION, MARKETING, MIXED, \u2026 (see erdo voice branded-calling get --json)").option("--use-case-description <text>", "what the calls are for").option("--consent-description <text>", "how the people called agreed to be called").option("--daily-calls <n>", "calls expected per day").option("--employees <n>", "number of employees").option("--logo <file>", "the logo: PNG, JPG, WebP, GIF or BMP, at least 128 px; converted to the 256x256 BMP Twilio takes").option("--loa-file <file>", "the signed letter of authorization (PDF, JPG or PNG) \u2014 get it pre-filled with: erdo voice branded-calling loa").option("--json", "print the raw JSON result instead of a table").action(
7048
+ async (opts) => {
7049
+ try {
7050
+ const res = await new ErdoClient().setVoiceBrandedCalling({
7051
+ display_name: opts.displayName,
7052
+ call_reason: opts.callReason,
7053
+ use_case_category: opts.useCase,
7054
+ use_case_description: opts.useCaseDescription,
7055
+ consent_description: opts.consentDescription,
7056
+ daily_call_volume: opts.dailyCalls ? Number(opts.dailyCalls) : void 0,
7057
+ employee_count: opts.employees ? Number(opts.employees) : void 0,
7058
+ logo_base64: fileAsBase64(opts.logo),
7059
+ loa_base64: fileAsBase64(opts.loaFile),
7060
+ loa_filename: opts.loaFile ? basename(opts.loaFile) : void 0
7061
+ });
7062
+ if (opts.json) {
7063
+ print(res);
7064
+ return;
7065
+ }
7066
+ printBrandedCallingStatus(res);
7067
+ } catch (e) {
7068
+ fail(e);
7069
+ }
7070
+ }
7071
+ );
7072
+ voiceBrandedCallingCmd.command("loa").description("Download the letter of authorization pre-filled from the business profile, for somebody at the business to sign (admins only)").option("--out <file>", "where to write the PDF", "branded-calling-letter-of-authorization.pdf").action(async (opts) => {
7073
+ try {
7074
+ const res = await new ErdoClient().getVoiceBrandedCallingLOA();
7075
+ writeFileSync3(opts.out, Buffer.from(res.content, "base64"));
7076
+ console.log(`Wrote ${opts.out}. Tick the authorization, sign and date it, then upload it with:`);
7077
+ console.log(` erdo voice branded-calling set --loa-file <signed file>`);
7078
+ } catch (e) {
7079
+ fail(e);
7080
+ }
7081
+ });
7082
+ voiceBrandedCallingCmd.command("provision").description("File Branded Calling for every number with A attestation (approval-gated; Twilio charges per branded call)").option("--json", "print the raw JSON result instead of a summary").action(async (opts) => {
7083
+ try {
7084
+ const res = await new ErdoClient().provisionVoiceBrandedCalling();
7085
+ if (opts.json) {
7086
+ print(res);
7087
+ return;
7088
+ }
7089
+ if (res.status === "pending_approval") {
7090
+ console.log("Nothing has been registered yet \u2014 an approval card was filed and awaits a decision.");
7091
+ if (res.action_display) console.log(`action: ${res.action_display}`);
7092
+ if (res.approval_request_id) {
7093
+ console.log(`approval request: ${res.approval_request_id}`);
7094
+ console.log(` erdo approvals show ${res.approval_request_id}`);
7095
+ }
7096
+ } else {
7097
+ console.log(`Branded calling ${res.status}.`);
7098
+ if (res.registration !== void 0) print(res.registration);
7099
+ }
7100
+ process.stderr.write(
7101
+ "Twilio reviews it in about 7 business days; re-read it with: erdo voice branded-calling get\n"
7102
+ );
7103
+ } catch (e) {
7104
+ fail(e);
7105
+ }
7106
+ });
6958
7107
  var voiceAgentsCmd = voiceCmd.command("agents").description("List the organization's concierges and change settings on one");
6959
7108
  voiceAgentsCmd.command("list", { isDefault: true }).description(
6960
7109
  "List the organization's concierges: the voice agents that answer its website widget, its phone and its texts. The id is what the lead playbook stores as its linked concierge (`erdo leads playbook set --agent-id`). Archived agents are not listed."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.141.0",
3
+ "version": "0.144.0",
4
4
  "description": "Erdo CLI \u2014 drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {