@getdial/cli 0.44.1 → 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 +16 -0
- package/dist/commands/lookup/lookup.js +16 -1
- package/dist/commands/number/list.js +3 -1
- package/dist/commands/number/set.js +4 -0
- package/dist/lib/ops/lookup.js +3 -1
- package/dist/lib/ops/numbers.js +7 -2
- package/dist/mcp/schemas.js +5 -0
- package/dist/mcp/tools/lookup-number.js +10 -1
- package/dist/mcp/tools/set-number-properties.js +7 -1
- package/package.json +1 -1
- package/skills.tar.gz +0 -0
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
|
});
|
|
@@ -4,7 +4,22 @@ import { printDialError } from "../../lib/cli-error.js";
|
|
|
4
4
|
/** Channels in the order they are printed, with the label each one shows as. */
|
|
5
5
|
const CHANNEL_LABELS = {
|
|
6
6
|
imessage: "iMessage",
|
|
7
|
+
whatsapp: "WhatsApp",
|
|
7
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* How one channel's verdict reads.
|
|
11
|
+
*
|
|
12
|
+
* Three states, not two. `null` means Dial did not answer for that channel — today only because the
|
|
13
|
+
* account holds no WhatsApp number of its own — and printing it as `no` would say the number is
|
|
14
|
+
* unreachable, which is the one thing it does not mean. A `?? "no"` here is the whole bug.
|
|
15
|
+
*/
|
|
16
|
+
function verdictOf(supported) {
|
|
17
|
+
if (supported === true)
|
|
18
|
+
return "yes";
|
|
19
|
+
if (supported === false)
|
|
20
|
+
return "no";
|
|
21
|
+
return "unknown";
|
|
22
|
+
}
|
|
8
23
|
export async function runLookup(number, opts) {
|
|
9
24
|
try {
|
|
10
25
|
const result = await lookupNumber(number);
|
|
@@ -17,7 +32,7 @@ export async function runLookup(number, opts) {
|
|
|
17
32
|
// A channel added to the API after this CLI shipped still prints, under its
|
|
18
33
|
// raw key — reporting an unknown channel is better than hiding it.
|
|
19
34
|
const label = CHANNEL_LABELS[channel] ?? channel;
|
|
20
|
-
console.log(` ${label.padEnd(9)} ${supported
|
|
35
|
+
console.log(` ${label.padEnd(9)} ${verdictOf(supported)}`);
|
|
21
36
|
}
|
|
22
37
|
return 0;
|
|
23
38
|
}
|
|
@@ -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
|
-
|
|
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(" ")}`);
|
package/dist/lib/ops/lookup.js
CHANGED
|
@@ -9,7 +9,9 @@ import { DialError } from "./errors.js";
|
|
|
9
9
|
*
|
|
10
10
|
* A lookup Dial could not complete comes back as an error (502), never as a
|
|
11
11
|
* negative verdict — so a `false` here always means the number genuinely is not
|
|
12
|
-
* reachable on that channel, and
|
|
12
|
+
* reachable on that channel, and a `null` always means Dial did not answer for
|
|
13
|
+
* that channel. Neither ever means "we could not find out", and callers can
|
|
14
|
+
* treat all three differently.
|
|
13
15
|
*/
|
|
14
16
|
export async function lookupNumber(number) {
|
|
15
17
|
const auth = maybeAuth();
|
package/dist/lib/ops/numbers.js
CHANGED
|
@@ -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
|
}
|
package/dist/mcp/schemas.js
CHANGED
|
@@ -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()
|
|
@@ -17,13 +17,22 @@ export const lookupNumberTool = {
|
|
|
17
17
|
"are added over time. The answer is point-in-time, not a property of the number — someone who changes " +
|
|
18
18
|
"device or turns the service off stops being reachable — so treat `true` as a strong signal for picking " +
|
|
19
19
|
"a channel rather than a promise that the send will land. A lookup that fails is an error, never a " +
|
|
20
|
-
"`false`, so a `false` always means the number genuinely isn't reachable there."
|
|
20
|
+
"`false` and never a `null`, so a `false` always means the number genuinely isn't reachable there. " +
|
|
21
|
+
"`whatsapp` is `null` when this account has no WhatsApp number of its own: a WhatsApp check is made " +
|
|
22
|
+
"from a WhatsApp line, so it is answered only for accounts that have one. `null` says something about " +
|
|
23
|
+
"the account, never about the number — do not report it as unreachable.",
|
|
21
24
|
inputSchema,
|
|
22
25
|
outputSchema: {
|
|
23
26
|
number: z.string().describe("The number you asked about, normalized to E.164"),
|
|
24
27
|
supports: z
|
|
25
28
|
.object({
|
|
26
29
|
imessage: z.boolean().describe("Whether the number can currently receive iMessage"),
|
|
30
|
+
whatsapp: z
|
|
31
|
+
.boolean()
|
|
32
|
+
.nullable()
|
|
33
|
+
.describe("Whether the number can currently receive WhatsApp: true reachable, false not " +
|
|
34
|
+
"reachable, null this account has no WhatsApp number so the check was not made. " +
|
|
35
|
+
"null is never a failure and never means unreachable."),
|
|
27
36
|
})
|
|
28
37
|
.describe("One key per channel, true when the number can be reached there"),
|
|
29
38
|
},
|
|
@@ -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
package/skills.tar.gz
CHANGED
|
Binary file
|