@getdial/cli 0.39.0 → 0.40.1

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
@@ -47,6 +47,22 @@ function parsePositiveInteger(value) {
47
47
  }
48
48
  return parsed;
49
49
  }
50
+ /**
51
+ * `--calling <on|off>` → boolean.
52
+ *
53
+ * One flag taking an explicit value, rather than a `--calling`/`--no-calling`
54
+ * pair: it reads the same in a script and in a skill, and it cannot be confused
55
+ * with "leave unchanged" (which is what omitting the flag means). Anything other
56
+ * than the two spellings is an error, never quietly read as off.
57
+ */
58
+ function parseCalling(value) {
59
+ const normalized = value.trim().toLowerCase();
60
+ if (normalized === "on")
61
+ return true;
62
+ if (normalized === "off")
63
+ return false;
64
+ throw new InvalidArgumentError(`must be "on" or "off", got: ${value}`);
65
+ }
50
66
  program
51
67
  .name("dial")
52
68
  .description("Dial CLI — set up your account and run the listen service.")
@@ -166,6 +182,7 @@ number
166
182
  .option("--area-code <code>", "preferred US area code (only US numbers can be provisioned; ignored with --include-imessage)")
167
183
  .option("--include-imessage", "provision an iMessage number (pay-as-you-go only; provisioned asynchronously — poll `dial number list` until ready)")
168
184
  .option("--whatsapp", "also connect WhatsApp to the new line (beta, enabled per account). Requires --include-imessage: WhatsApp is a channel on an iMessage line")
185
+ .option("--calling <on|off>", 'whether calling is switched on for the new number (default: on). "off" provisions a messaging-only line with no window in which it answers a call', parseCalling)
169
186
  .option("--json", "machine-readable output")
170
187
  .action(async (opts) => process.exit(await runNumberPurchase({
171
188
  inboundInstruction: opts.inboundInstruction,
@@ -175,6 +192,7 @@ number
175
192
  areaCode: opts.areaCode,
176
193
  includeImessage: !!opts.includeImessage,
177
194
  whatsapp: !!opts.whatsapp,
195
+ callingEnabled: opts.calling,
178
196
  json: !!opts.json,
179
197
  })));
180
198
  number
@@ -203,6 +221,7 @@ number
203
221
  .option("--avatar <path-or-url>", "iMessage avatar photo (iMessage numbers only): a local image file (jpeg/png/gif/webp, max 5 MB) to upload, or a public image URL to fetch. Replaces the current photo; photos can't be removed")
204
222
  .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")
205
223
  .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)")
224
+ .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)
206
225
  .option("--json", "machine-readable output")
207
226
  .action(async (numberArg, opts) => {
208
227
  let maxCallDurationSeconds;
@@ -224,6 +243,7 @@ number
224
243
  avatar: opts.avatar,
225
244
  whatsappName: opts.whatsappName,
226
245
  whatsappAvatar: opts.whatsappAvatar,
246
+ callingEnabled: opts.calling,
227
247
  json: !!opts.json,
228
248
  }));
229
249
  });
@@ -15,7 +15,11 @@ export async function runNumberList(opts) {
15
15
  for (const n of numbers) {
16
16
  const tag = n.id === defaultNumberId ? " (default)" : "";
17
17
  const nickname = n.nickname ? ` "${n.nickname}"` : "";
18
- console.log(`${n.number} id=${n.id} ${n.country}${nickname}${tag}`);
18
+ // Marked only when off. `--json` carries callingEnabled either way, but
19
+ // this line is hand-built, so without this the switch would be invisible
20
+ // in the CLI's default output.
21
+ const calling = n.callingEnabled === false ? " calling:off" : "";
22
+ console.log(`${n.number} id=${n.id} ${n.country}${nickname}${calling}${tag}`);
19
23
  }
20
24
  return 0;
21
25
  }
@@ -17,6 +17,7 @@ export async function runNumberPurchase(opts) {
17
17
  inboundLanguage: opts.inboundLanguage,
18
18
  areaCode: opts.areaCode,
19
19
  includeImessage: opts.includeImessage,
20
+ callingEnabled: opts.callingEnabled,
20
21
  whatsapp: opts.whatsapp,
21
22
  });
22
23
  if (opts.json) {
@@ -27,6 +28,10 @@ export async function runNumberPurchase(opts) {
27
28
  console.log(` number: ${n.number}`);
28
29
  console.log(` id: ${n.id}`);
29
30
  console.log(` country: ${n.country}`);
31
+ // Only worth a line when it isn't the default — a messaging-only line is
32
+ // a surprising thing to discover later.
33
+ if (n.callingEnabled === false)
34
+ console.log(` calling: off (messaging only)`);
30
35
  // iMessage numbers provision asynchronously: the number is returned right
31
36
  // away in setupStatus "provisioning". Tell the user to poll before using it.
32
37
  if (opts.includeImessage) {
@@ -17,6 +17,7 @@ export async function runNumberSet(opts) {
17
17
  ...(opts.firstName !== undefined ? { firstName: opts.firstName } : {}),
18
18
  ...(opts.lastName !== undefined ? { lastName: opts.lastName } : {}),
19
19
  ...(opts.avatar !== undefined ? { avatar: opts.avatar } : {}),
20
+ ...(opts.callingEnabled !== undefined ? { callingEnabled: opts.callingEnabled } : {}),
20
21
  ...(opts.whatsappName !== undefined ? { whatsappName: opts.whatsappName } : {}),
21
22
  ...(opts.whatsappAvatar !== undefined ? { whatsappAvatar: opts.whatsappAvatar } : {}),
22
23
  });
@@ -31,6 +32,9 @@ export async function runNumberSet(opts) {
31
32
  console.log(` inbound instruction: ${n.inboundInstruction ?? ""}`);
32
33
  console.log(` inbound voice gender: ${n.inboundVoiceGender ?? ""}`);
33
34
  console.log(` inbound language: ${n.inboundLanguage ?? ""}`);
35
+ // Always printed, not only when it changed: "is calling on?" is the
36
+ // question someone runs this command to settle.
37
+ console.log(` calling: ${n.callingEnabled === false ? "off" : "on"}`);
34
38
  const hasIdentity = n.firstName != null || n.lastName != null || n.avatarUrl != null;
35
39
  if (hasIdentity) {
36
40
  console.log(` display name: ${[n.firstName, n.lastName].filter(Boolean).join(" ")}`);
@@ -46,6 +46,10 @@ export async function purchaseNumber(opts) {
46
46
  body.inboundVoiceGender = opts.inboundVoiceGender;
47
47
  if (opts.inboundLanguage)
48
48
  body.inboundLanguage = opts.inboundLanguage;
49
+ // Only sent when asked for, so the server's default (on) stays the one place
50
+ // that decides what an unspecified number does.
51
+ if (opts.callingEnabled !== undefined)
52
+ body.callingEnabled = opts.callingEnabled;
49
53
  // iMessage numbers ignore areaCode, so only send it for standard numbers.
50
54
  if (opts.includeImessage)
51
55
  body.capabilities = ["sms", "call", "imessage"];
@@ -117,6 +121,8 @@ export async function setNumberProperties(opts) {
117
121
  body.firstName = opts.firstName;
118
122
  if (opts.lastName !== undefined)
119
123
  body.lastName = opts.lastName;
124
+ if (opts.callingEnabled !== undefined)
125
+ body.callingEnabled = opts.callingEnabled;
120
126
  if (opts.whatsappName !== undefined)
121
127
  body.whatsappName = opts.whatsappName;
122
128
  // A URL avatar goes in the JSON body; a local file forces multipart (below).
@@ -131,7 +137,7 @@ export async function setNumberProperties(opts) {
131
137
  if (opts.whatsappAvatar !== undefined && !whatsappAvatarFile)
132
138
  body.whatsappAvatarUrl = opts.whatsappAvatar;
133
139
  if (Object.keys(body).length === 0 && !avatarFile && !whatsappAvatarFile) {
134
- throw new DialError("bad_request", "Provide at least one property to update (inboundInstruction, inboundVoiceGender, inboundLanguage, nickname, maxCallDurationSeconds, firstName, lastName, avatar, whatsappName, or whatsappAvatar).");
140
+ throw new DialError("bad_request", "Provide at least one property to update (inboundInstruction, inboundVoiceGender, inboundLanguage, nickname, maxCallDurationSeconds, calling, firstName, lastName, avatar, whatsappName, or whatsappAvatar).");
135
141
  }
136
142
  const auth = maybeAuth();
137
143
  // The REST API keys numbers by id; the CLI/tool takes the E.164 number for ergonomics,
@@ -38,6 +38,10 @@ export const phoneNumberSchema = z
38
38
  .nullable()
39
39
  .optional()
40
40
  .describe("BCP-47 language tag inbound calls are pinned to; null → detected from the caller's country prefix per call"),
41
+ callingEnabled: z
42
+ .boolean()
43
+ .optional()
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"),
41
45
  firstName: z
42
46
  .string()
43
47
  .nullable()
@@ -25,6 +25,10 @@ const inputSchema = {
25
25
  .boolean()
26
26
  .optional()
27
27
  .describe('Provision an iMessage number (pay-as-you-go only; provisioned asynchronously — poll List Numbers until setupStatus is "ready")'),
28
+ callingEnabled: z
29
+ .boolean()
30
+ .optional()
31
+ .describe("Whether calling is switched on for the new number; omitted → true. Pass false for a messaging-only line: inbound calls are never connected and place_call from it fails, with no window in which it answers a call. Changeable later with set_number_properties."),
28
32
  };
29
33
  export const purchaseNumberTool = {
30
34
  name: "purchase_number",
@@ -43,6 +47,7 @@ export const purchaseNumberTool = {
43
47
  inboundLanguage: args.inboundLanguage,
44
48
  areaCode: args.areaCode,
45
49
  includeImessage: args.includeImessage,
50
+ callingEnabled: args.callingEnabled,
46
51
  }),
47
52
  }),
48
53
  };
@@ -38,7 +38,7 @@ const inputSchema = {
38
38
  forceAudioFile: z
39
39
  .boolean()
40
40
  .optional()
41
- .describe("Send an audio attachment as a regular file attachment instead of an iMessage voice message. No effect on standard numbers or non-audio media."),
41
+ .describe("Send a lone audio attachment as a regular file instead of a voice message — an iMessage voice message on iMessage numbers, a WhatsApp voice note on WhatsApp numbers. No effect on standard numbers or non-audio media."),
42
42
  };
43
43
  export const sendMessageTool = {
44
44
  name: "send_message",
@@ -47,7 +47,8 @@ export const sendMessageTool = {
47
47
  description: "Send a message from one of your Dial numbers — to a phone number, or into a group conversation — " +
48
48
  "optionally with media attachments (MMS). On an iMessage number, a single audio attachment is " +
49
49
  "delivered as a voice message unless forceAudioFile is true. " +
50
- "Address it with exactly one of to or groupId. WhatsApp sends are text-only.",
50
+ "Address it with exactly one of to or groupId. A WhatsApp number sends text and a single " +
51
+ "attachment (a caption is allowed only with an image or video).",
51
52
  inputSchema,
52
53
  outputSchema: { message: messageSchema },
53
54
  annotations: { openWorldHint: true },
@@ -53,12 +53,16 @@ const inputSchema = {
53
53
  .url()
54
54
  .optional()
55
55
  .describe("Public image URL to set as the number's WhatsApp avatar (the server downloads it). Square jpeg or png between 192x192 and 640x640 (not resized). WhatsApp-ready numbers only."),
56
+ callingEnabled: z
57
+ .boolean()
58
+ .optional()
59
+ .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."),
56
60
  };
57
61
  export const setNumberPropertiesTool = {
58
62
  name: "set_number_properties",
59
63
  config: {
60
64
  title: "Set Number Properties",
61
- description: "Update a phone number's properties: its inbound instruction (the system prompt for inbound calls), inbound voice gender, inbound language, nickname, and — for iMessage numbers — its display identity (firstName, lastName, avatarUrl), and for WhatsApp-ready numbers its WhatsApp identity (whatsappName, whatsappAvatarUrl). Provide at least one.",
65
+ 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 — for iMessage numbers — its display identity (firstName, lastName, avatarUrl), and for WhatsApp-ready numbers its WhatsApp identity (whatsappName, whatsappAvatarUrl). Provide at least one.",
62
66
  inputSchema,
63
67
  outputSchema: { number: phoneNumberSchema },
64
68
  annotations: { openWorldHint: true },
@@ -79,6 +83,9 @@ export const setNumberPropertiesTool = {
79
83
  : {}),
80
84
  ...(args.firstName !== undefined ? { firstName: args.firstName } : {}),
81
85
  ...(args.whatsappName !== undefined ? { whatsappName: args.whatsappName } : {}),
86
+ ...(args.callingEnabled !== undefined
87
+ ? { callingEnabled: args.callingEnabled }
88
+ : {}),
82
89
  ...(args.whatsappAvatarUrl !== undefined
83
90
  ? { whatsappAvatar: args.whatsappAvatarUrl }
84
91
  : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getdial/cli",
3
- "version": "0.39.0",
3
+ "version": "0.40.1",
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