@getdial/cli 0.38.0 → 0.40.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 +24 -0
- package/dist/commands/number/list.js +5 -1
- package/dist/commands/number/purchase.js +5 -0
- package/dist/commands/number/set.js +10 -0
- package/dist/lib/ops/numbers.js +24 -4
- package/dist/mcp/schemas.js +14 -0
- package/dist/mcp/tools/purchase-number.js +5 -0
- package/dist/mcp/tools/set-number-properties.js +21 -1
- package/package.json +1 -1
- package/skills.tar.gz +0 -0
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
|
|
@@ -201,6 +219,9 @@ number
|
|
|
201
219
|
.option("--first-name <text>", 'iMessage display first name shown beside this number\'s messages (iMessage numbers only); pass "" to clear')
|
|
202
220
|
.option("--last-name <text>", 'iMessage display last name (iMessage numbers only); pass "" to clear')
|
|
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")
|
|
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")
|
|
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)
|
|
204
225
|
.option("--json", "machine-readable output")
|
|
205
226
|
.action(async (numberArg, opts) => {
|
|
206
227
|
let maxCallDurationSeconds;
|
|
@@ -220,6 +241,9 @@ number
|
|
|
220
241
|
firstName: opts.firstName,
|
|
221
242
|
lastName: opts.lastName,
|
|
222
243
|
avatar: opts.avatar,
|
|
244
|
+
whatsappName: opts.whatsappName,
|
|
245
|
+
whatsappAvatar: opts.whatsappAvatar,
|
|
246
|
+
callingEnabled: opts.calling,
|
|
223
247
|
json: !!opts.json,
|
|
224
248
|
}));
|
|
225
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
|
-
|
|
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,9 @@ 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 } : {}),
|
|
21
|
+
...(opts.whatsappName !== undefined ? { whatsappName: opts.whatsappName } : {}),
|
|
22
|
+
...(opts.whatsappAvatar !== undefined ? { whatsappAvatar: opts.whatsappAvatar } : {}),
|
|
20
23
|
});
|
|
21
24
|
if (opts.json) {
|
|
22
25
|
console.log(JSON.stringify({ ok: true, number: n }));
|
|
@@ -29,11 +32,18 @@ export async function runNumberSet(opts) {
|
|
|
29
32
|
console.log(` inbound instruction: ${n.inboundInstruction ?? ""}`);
|
|
30
33
|
console.log(` inbound voice gender: ${n.inboundVoiceGender ?? ""}`);
|
|
31
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"}`);
|
|
32
38
|
const hasIdentity = n.firstName != null || n.lastName != null || n.avatarUrl != null;
|
|
33
39
|
if (hasIdentity) {
|
|
34
40
|
console.log(` display name: ${[n.firstName, n.lastName].filter(Boolean).join(" ")}`);
|
|
35
41
|
console.log(` avatar: ${n.avatarUrl ?? ""}`);
|
|
36
42
|
}
|
|
43
|
+
if (n.whatsappName != null || n.whatsappAvatarUrl != null) {
|
|
44
|
+
console.log(` whatsapp name: ${n.whatsappName ?? ""}`);
|
|
45
|
+
console.log(` whatsapp avatar: ${n.whatsappAvatarUrl ?? ""}`);
|
|
46
|
+
}
|
|
37
47
|
}
|
|
38
48
|
return 0;
|
|
39
49
|
}
|
package/dist/lib/ops/numbers.js
CHANGED
|
@@ -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,14 +121,23 @@ 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;
|
|
126
|
+
if (opts.whatsappName !== undefined)
|
|
127
|
+
body.whatsappName = opts.whatsappName;
|
|
120
128
|
// A URL avatar goes in the JSON body; a local file forces multipart (below).
|
|
121
129
|
// Read + validate the file up front, before any API round-trip, so a bad
|
|
122
130
|
// path or unsupported type fails fast.
|
|
123
131
|
const avatarFile = opts.avatar !== undefined && !isHttpUrl(opts.avatar) ? readAvatarFile(opts.avatar) : null;
|
|
124
132
|
if (opts.avatar !== undefined && !avatarFile)
|
|
125
133
|
body.avatarUrl = opts.avatar;
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
const whatsappAvatarFile = opts.whatsappAvatar !== undefined && !isHttpUrl(opts.whatsappAvatar)
|
|
135
|
+
? readAvatarFile(opts.whatsappAvatar)
|
|
136
|
+
: null;
|
|
137
|
+
if (opts.whatsappAvatar !== undefined && !whatsappAvatarFile)
|
|
138
|
+
body.whatsappAvatarUrl = opts.whatsappAvatar;
|
|
139
|
+
if (Object.keys(body).length === 0 && !avatarFile && !whatsappAvatarFile) {
|
|
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).");
|
|
128
141
|
}
|
|
129
142
|
const auth = maybeAuth();
|
|
130
143
|
// The REST API keys numbers by id; the CLI/tool takes the E.164 number for ergonomics,
|
|
@@ -141,11 +154,18 @@ export async function setNumberProperties(opts) {
|
|
|
141
154
|
// A local avatar file forces a multipart PATCH: every scalar field goes in as a
|
|
142
155
|
// text part alongside the uploaded `avatar` file. Otherwise a plain JSON PATCH.
|
|
143
156
|
let res;
|
|
144
|
-
if (avatarFile) {
|
|
157
|
+
if (avatarFile || whatsappAvatarFile) {
|
|
145
158
|
const form = new ApiFormData();
|
|
146
159
|
for (const [field, value] of Object.entries(body))
|
|
147
160
|
form.set(field, String(value));
|
|
148
|
-
|
|
161
|
+
if (avatarFile) {
|
|
162
|
+
form.append("avatar", new Blob([new Uint8Array(avatarFile.data)], { type: avatarFile.contentType }), avatarFile.name);
|
|
163
|
+
}
|
|
164
|
+
if (whatsappAvatarFile) {
|
|
165
|
+
form.append("whatsappAvatar", new Blob([new Uint8Array(whatsappAvatarFile.data)], {
|
|
166
|
+
type: whatsappAvatarFile.contentType,
|
|
167
|
+
}), whatsappAvatarFile.name);
|
|
168
|
+
}
|
|
149
169
|
res = await apiPatchMultipart(path, form, auth?.apiKey);
|
|
150
170
|
}
|
|
151
171
|
else {
|
package/dist/mcp/schemas.js
CHANGED
|
@@ -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()
|
|
@@ -53,6 +57,16 @@ export const phoneNumberSchema = z
|
|
|
53
57
|
.nullable()
|
|
54
58
|
.optional()
|
|
55
59
|
.describe("URL of the number's iMessage avatar photo; null when unset or not an iMessage number"),
|
|
60
|
+
whatsappName: z
|
|
61
|
+
.string()
|
|
62
|
+
.nullable()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("WhatsApp display name; null on numbers without a WhatsApp track"),
|
|
65
|
+
whatsappAvatarUrl: z
|
|
66
|
+
.string()
|
|
67
|
+
.nullable()
|
|
68
|
+
.optional()
|
|
69
|
+
.describe("URL of the number's WhatsApp avatar; null when unset"),
|
|
56
70
|
})
|
|
57
71
|
.passthrough();
|
|
58
72
|
/**
|
|
@@ -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
|
};
|
|
@@ -44,12 +44,25 @@ const inputSchema = {
|
|
|
44
44
|
.url()
|
|
45
45
|
.optional()
|
|
46
46
|
.describe("Public image URL to set as the number's iMessage avatar photo (the server downloads it). jpeg/png/gif/webp, max 5 MB. iMessage numbers only. The photo can be replaced but not removed."),
|
|
47
|
+
whatsappName: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("WhatsApp display name shown to recipients. 1-25 chars, no reserved verification marks. WhatsApp-ready numbers only; the call blocks until WhatsApp applies it."),
|
|
51
|
+
whatsappAvatarUrl: z
|
|
52
|
+
.string()
|
|
53
|
+
.url()
|
|
54
|
+
.optional()
|
|
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."),
|
|
47
60
|
};
|
|
48
61
|
export const setNumberPropertiesTool = {
|
|
49
62
|
name: "set_number_properties",
|
|
50
63
|
config: {
|
|
51
64
|
title: "Set Number Properties",
|
|
52
|
-
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
|
|
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.",
|
|
53
66
|
inputSchema,
|
|
54
67
|
outputSchema: { number: phoneNumberSchema },
|
|
55
68
|
annotations: { openWorldHint: true },
|
|
@@ -69,6 +82,13 @@ export const setNumberPropertiesTool = {
|
|
|
69
82
|
? { maxCallDurationSeconds: args.maxCallDurationSeconds }
|
|
70
83
|
: {}),
|
|
71
84
|
...(args.firstName !== undefined ? { firstName: args.firstName } : {}),
|
|
85
|
+
...(args.whatsappName !== undefined ? { whatsappName: args.whatsappName } : {}),
|
|
86
|
+
...(args.callingEnabled !== undefined
|
|
87
|
+
? { callingEnabled: args.callingEnabled }
|
|
88
|
+
: {}),
|
|
89
|
+
...(args.whatsappAvatarUrl !== undefined
|
|
90
|
+
? { whatsappAvatar: args.whatsappAvatarUrl }
|
|
91
|
+
: {}),
|
|
72
92
|
...(args.lastName !== undefined ? { lastName: args.lastName } : {}),
|
|
73
93
|
...(args.avatarUrl !== undefined ? { avatar: args.avatarUrl } : {}),
|
|
74
94
|
}),
|
package/package.json
CHANGED
package/skills.tar.gz
CHANGED
|
Binary file
|