@getdial/cli 0.45.0 → 0.46.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/cli.js CHANGED
@@ -65,6 +65,20 @@ function parseCalling(value) {
65
65
  return false;
66
66
  throw new InvalidArgumentError(`must be "on" or "off", got: ${value}`);
67
67
  }
68
+ /**
69
+ * `--forward-to <e164|off>` → the number as typed, or null to stop forwarding.
70
+ *
71
+ * `off` (any case) or an empty string means "stop forwarding", sent as null.
72
+ * Anything else is sent as-is: the CLI never validates phone numbers locally,
73
+ * so the server's own 400 (invalid E.164, or the number's own number) is the
74
+ * one message the user sees.
75
+ */
76
+ function parseForwardTo(value) {
77
+ const trimmed = value.trim();
78
+ if (trimmed === "" || trimmed.toLowerCase() === "off")
79
+ return null;
80
+ return value;
81
+ }
68
82
  program
69
83
  .name("dial")
70
84
  .description("Dial CLI — set up your account and run the listen service.")
@@ -226,6 +240,7 @@ number
226
240
  .option("--whatsapp-name <text>", "WhatsApp display name shown to recipients (WhatsApp-ready numbers only): 1-25 chars, no reserved marks. The call blocks until WhatsApp applies it")
227
241
  .option("--whatsapp-avatar <path-or-url>", "WhatsApp avatar photo (WhatsApp-ready numbers only): a local image file or public URL. Square jpeg/png between 192x192 and 640x640 (not resized)")
228
242
  .option("--calling <on|off>", "switch calling on or off for this number, both directions: off means inbound calls aren't connected and none can be placed from it (messaging is unaffected)", parseCalling)
243
+ .option("--forward-to <e164|off>", "forward inbound calls to this phone number instead of answering them with the AI voice agent. `off` stops forwarding", parseForwardTo)
229
244
  .option("--json", "machine-readable output")
230
245
  .action(async (numberArg, opts) => {
231
246
  let maxCallDurationSeconds;
@@ -250,6 +265,7 @@ number
250
265
  whatsappName: opts.whatsappName,
251
266
  whatsappAvatar: opts.whatsappAvatar,
252
267
  callingEnabled: opts.calling,
268
+ forwardTo: opts.forwardTo,
253
269
  json: !!opts.json,
254
270
  }));
255
271
  });
@@ -19,7 +19,9 @@ export async function runNumberList(opts) {
19
19
  // this line is hand-built, so without this the switch would be invisible
20
20
  // in the CLI's default output.
21
21
  const calling = n.callingEnabled === false ? " calling:off" : "";
22
- console.log(`${n.number} id=${n.id} ${n.country}${nickname}${calling}${tag}`);
22
+ // Same reasoning: marked only when set, since the default is the AI agent answering.
23
+ const forward = n.forwardTo ? ` forward:${n.forwardTo}` : "";
24
+ console.log(`${n.number} id=${n.id} ${n.country}${nickname}${calling}${forward}${tag}`);
23
25
  }
24
26
  return 0;
25
27
  }
@@ -20,6 +20,7 @@ export async function runNumberSet(opts) {
20
20
  ...(opts.lastName !== undefined ? { lastName: opts.lastName } : {}),
21
21
  ...(opts.avatar !== undefined ? { avatar: opts.avatar } : {}),
22
22
  ...(opts.callingEnabled !== undefined ? { callingEnabled: opts.callingEnabled } : {}),
23
+ ...(opts.forwardTo !== undefined ? { forwardTo: opts.forwardTo } : {}),
23
24
  ...(opts.whatsappName !== undefined ? { whatsappName: opts.whatsappName } : {}),
24
25
  ...(opts.whatsappAvatar !== undefined ? { whatsappAvatar: opts.whatsappAvatar } : {}),
25
26
  });
@@ -37,6 +38,9 @@ export async function runNumberSet(opts) {
37
38
  // Always printed, not only when it changed: "is calling on?" is the
38
39
  // question someone runs this command to settle.
39
40
  console.log(` calling: ${n.callingEnabled === false ? "off" : "on"}`);
41
+ // Printed alongside calling for the same reason: "who picks up?" is the
42
+ // other half of that question.
43
+ console.log(` forward to: ${n.forwardTo ?? "off (AI voice agent answers)"}`);
40
44
  const hasIdentity = n.firstName != null || n.lastName != null || n.avatarUrl != null;
41
45
  if (hasIdentity) {
42
46
  console.log(` display name: ${[n.firstName, n.lastName].filter(Boolean).join(" ")}`);
@@ -127,6 +127,9 @@ export async function setNumberProperties(opts) {
127
127
  body.lastName = opts.lastName;
128
128
  if (opts.callingEnabled !== undefined)
129
129
  body.callingEnabled = opts.callingEnabled;
130
+ // Empty string stops forwarding → send null, the one "off" spelling on the wire.
131
+ if (opts.forwardTo !== undefined)
132
+ body.forwardTo = opts.forwardTo || null;
130
133
  if (opts.whatsappName !== undefined)
131
134
  body.whatsappName = opts.whatsappName;
132
135
  // A URL avatar goes in the JSON body; a local file forces multipart (below).
@@ -141,7 +144,7 @@ export async function setNumberProperties(opts) {
141
144
  if (opts.whatsappAvatar !== undefined && !whatsappAvatarFile)
142
145
  body.whatsappAvatarUrl = opts.whatsappAvatar;
143
146
  if (Object.keys(body).length === 0 && !avatarFile && !whatsappAvatarFile) {
144
- throw new DialError("bad_request", "Provide at least one property to update (inboundInstruction, inboundVoiceGender, inboundLanguage, nickname, maxCallDurationSeconds, calling, channel with name/avatar, firstName, lastName, avatar, whatsappName, or whatsappAvatar).");
147
+ throw new DialError("bad_request", "Provide at least one property to update (inboundInstruction, inboundVoiceGender, inboundLanguage, nickname, maxCallDurationSeconds, calling, forwardTo, channel with name/avatar, firstName, lastName, avatar, whatsappName, or whatsappAvatar).");
145
148
  }
146
149
  const auth = maybeAuth();
147
150
  // The REST API keys numbers by id; the CLI/tool takes the E.164 number for ergonomics,
@@ -160,8 +163,10 @@ export async function setNumberProperties(opts) {
160
163
  let res;
161
164
  if (avatarFile || whatsappAvatarFile) {
162
165
  const form = new ApiFormData();
166
+ // A null (a clear, e.g. `--forward-to off`) goes as an empty text part —
167
+ // multipart has no null, and String(null) would send the word "null".
163
168
  for (const [field, value] of Object.entries(body))
164
- form.set(field, String(value));
169
+ form.set(field, value === null ? "" : String(value));
165
170
  if (avatarFile) {
166
171
  form.append("avatar", new Blob([new Uint8Array(avatarFile.data)], { type: avatarFile.contentType }), avatarFile.name);
167
172
  }
@@ -42,6 +42,11 @@ export const phoneNumberSchema = z
42
42
  .boolean()
43
43
  .optional()
44
44
  .describe("Whether calling is switched on for this number, both directions. false → inbound calls are not connected and place_call from it fails with calling_disabled; messaging is unaffected"),
45
+ forwardTo: z
46
+ .string()
47
+ .nullable()
48
+ .optional()
49
+ .describe("E.164 number inbound calls are forwarded to instead of the AI voice agent answering, or null when the agent answers."),
45
50
  firstName: z
46
51
  .string()
47
52
  .nullable()
@@ -66,12 +66,17 @@ const inputSchema = {
66
66
  .boolean()
67
67
  .optional()
68
68
  .describe("Switch calling on or off for this number, in both directions. false stops inbound calls from being connected (the caller is never answered) and makes place_call from this number fail with calling_disabled (409); messaging on the number is unaffected. Takes effect on the next call — a call already in progress is not ended. Omit to leave it unchanged."),
69
+ forwardTo: z
70
+ .string()
71
+ .nullable()
72
+ .optional()
73
+ .describe("Forward inbound calls to this phone number (E.164) instead of having the AI voice agent answer; the caller is connected when that phone answers, and the call ends if it is busy or doesn't answer. null stops forwarding. Ignored while calling is off. Omitted leaves it unchanged."),
69
74
  };
70
75
  export const setNumberPropertiesTool = {
71
76
  name: "set_number_properties",
72
77
  config: {
73
78
  title: "Set Number Properties",
74
- description: 'Update a phone number\'s properties: its inbound instruction (the system prompt for inbound calls), inbound voice gender, inbound language, nickname, whether calling is switched on at all (callingEnabled), and its display identity. For the display identity prefer `channel` ("imessage", "whatsapp", or "both") with `name`/`avatarUrl` — one call that keeps every channel\'s profile identical. The per-channel fields remain for when the profiles differ: firstName/lastName/avatarUrl for iMessage, whatsappName/whatsappAvatarUrl for WhatsApp-ready numbers. Provide at least one property, and don\'t mix `channel` with the per-channel fields.',
79
+ description: 'Update a phone number\'s properties: its inbound instruction (the system prompt for inbound calls), inbound voice gender, inbound language, nickname, whether calling is switched on at all (callingEnabled), a phone to forward inbound calls to instead of the AI voice agent (forwardTo), and its display identity. For the display identity prefer `channel` ("imessage", "whatsapp", or "both") with `name`/`avatarUrl` — one call that keeps every channel\'s profile identical. The per-channel fields remain for when the profiles differ: firstName/lastName/avatarUrl for iMessage, whatsappName/whatsappAvatarUrl for WhatsApp-ready numbers. Provide at least one property, and don\'t mix `channel` with the per-channel fields.',
75
80
  inputSchema,
76
81
  outputSchema: { number: phoneNumberSchema },
77
82
  annotations: { openWorldHint: true },
@@ -97,6 +102,7 @@ export const setNumberPropertiesTool = {
97
102
  ...(args.callingEnabled !== undefined
98
103
  ? { callingEnabled: args.callingEnabled }
99
104
  : {}),
105
+ ...(args.forwardTo !== undefined ? { forwardTo: args.forwardTo } : {}),
100
106
  ...(args.whatsappAvatarUrl !== undefined
101
107
  ? { whatsappAvatar: args.whatsappAvatarUrl }
102
108
  : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getdial/cli",
3
- "version": "0.45.0",
3
+ "version": "0.46.0",
4
4
  "description": "Dial CLI — install, sign up, and run the local listen service.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/skills.tar.gz CHANGED
Binary file