@getdial/cli 0.29.0 → 0.31.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 +49 -2
- package/dist/commands/billing.js +3 -0
- package/dist/commands/call/send.js +1 -0
- package/dist/commands/message/send.js +1 -0
- package/dist/commands/number/purchase.js +1 -0
- package/dist/commands/number/set.js +2 -0
- package/dist/commands/typing/start.js +20 -0
- package/dist/commands/typing/stop.js +20 -0
- package/dist/lib/ops/auth.js +25 -0
- package/dist/lib/ops/calls.js +3 -3
- package/dist/lib/ops/messages.js +5 -4
- package/dist/lib/ops/numbers.js +5 -1
- package/dist/lib/ops/typing.js +16 -0
- package/dist/mcp/schemas.js +1 -0
- package/dist/mcp/tools/index.js +4 -0
- package/dist/mcp/tools/place-call.js +6 -0
- package/dist/mcp/tools/purchase-number.js +2 -0
- package/dist/mcp/tools/send-message.js +6 -0
- package/dist/mcp/tools/set-number-properties.js +3 -1
- package/dist/mcp/tools/start-typing.js +29 -0
- package/dist/mcp/tools/stop-typing.js +27 -0
- package/package.json +1 -1
- package/skills.tar.gz +0 -0
package/dist/cli.js
CHANGED
|
@@ -16,6 +16,8 @@ import { runNumberSet } from "./commands/number/set.js";
|
|
|
16
16
|
import { runMessageSend } from "./commands/message/send.js";
|
|
17
17
|
import { runMessageReply } from "./commands/message/reply.js";
|
|
18
18
|
import { runMessageList } from "./commands/message/list.js";
|
|
19
|
+
import { runTypingStart } from "./commands/typing/start.js";
|
|
20
|
+
import { runTypingStop } from "./commands/typing/stop.js";
|
|
19
21
|
import { runCallSend } from "./commands/call/send.js";
|
|
20
22
|
import { runCallList } from "./commands/call/list.js";
|
|
21
23
|
import { runCallGet } from "./commands/call/get.js";
|
|
@@ -102,6 +104,7 @@ number
|
|
|
102
104
|
.requiredOption("--inbound-instruction <text>", "system prompt for inbound calls to this number")
|
|
103
105
|
.requiredOption("--explicit-programmatic-consent <text>", "required attestation that the account holder consented to provisioning this number programmatically (stored on the number)")
|
|
104
106
|
.option("--inbound-voice-gender <male|female>", "voice gender for inbound calls (default: female; pass male to override)")
|
|
107
|
+
.option("--inbound-language <bcp47>", "language tag inbound calls are pinned to (default: detect from the caller's country prefix, alongside en-US)")
|
|
105
108
|
.option("--area-code <code>", "preferred US area code (only US numbers can be provisioned; ignored with --include-imessage)")
|
|
106
109
|
.option("--include-imessage", "provision an iMessage number (pay-as-you-go only; provisioned asynchronously — poll `dial number list` until ready)")
|
|
107
110
|
.option("--json", "machine-readable output")
|
|
@@ -109,6 +112,7 @@ number
|
|
|
109
112
|
inboundInstruction: opts.inboundInstruction,
|
|
110
113
|
explicitProgrammaticConsent: opts.explicitProgrammaticConsent,
|
|
111
114
|
inboundVoiceGender: opts.inboundVoiceGender,
|
|
115
|
+
inboundLanguage: opts.inboundLanguage,
|
|
112
116
|
areaCode: opts.areaCode,
|
|
113
117
|
includeImessage: !!opts.includeImessage,
|
|
114
118
|
json: !!opts.json,
|
|
@@ -118,6 +122,7 @@ number
|
|
|
118
122
|
.description("Update a number's properties (at least one flag). PATCH /api/v1/numbers/<id>.")
|
|
119
123
|
.option("--inbound-instruction <text>", "new system prompt for inbound calls to this number")
|
|
120
124
|
.option("--inbound-voice-gender <male|female>", 'voice gender for inbound calls; pass "" to clear (reverts to the default, female)')
|
|
125
|
+
.option("--inbound-language <bcp47>", 'language tag inbound calls are pinned to; pass "" to clear (reverts to detecting from the caller\'s country prefix)')
|
|
121
126
|
.option("--nickname <text>", 'human-readable label for the number, e.g. "Support line"; pass "" to clear')
|
|
122
127
|
.option("--max-call-duration <seconds>", "call duration cap for this number, in seconds, applied as a hard ceiling to both inbound and outbound calls (the smallest of the per-number, account, and per-call caps wins)", (v) => {
|
|
123
128
|
const n = parseInt(v, 10);
|
|
@@ -141,6 +146,7 @@ number
|
|
|
141
146
|
number: numberArg,
|
|
142
147
|
inboundInstruction: opts.inboundInstruction,
|
|
143
148
|
inboundVoiceGender: opts.inboundVoiceGender,
|
|
149
|
+
inboundLanguage: opts.inboundLanguage,
|
|
144
150
|
nickname: opts.nickname,
|
|
145
151
|
maxCallDurationSeconds,
|
|
146
152
|
json: !!opts.json,
|
|
@@ -151,7 +157,8 @@ const message = program
|
|
|
151
157
|
.description("Send an SMS, optionally with media (MMS). POST /api/v1/messages.")
|
|
152
158
|
.option("--to <e164>", "destination phone number, E.164 (e.g. +14155551234)")
|
|
153
159
|
.option("--body <text>", "message body")
|
|
154
|
-
.option("--from-number
|
|
160
|
+
.option("--from-number <ref>", "number to send from: id, owned E.164, or nickname (defaults to onboard's number; exclusive with --from-number-id)")
|
|
161
|
+
.option("--from-number-id <id>", "phoneNumberId to send from (defaults to onboard's number; exclusive with --from-number)")
|
|
155
162
|
.option("--media <path-or-url>", "media attachment: local file path (uploaded) or public http(s) URL (repeatable, max 10)", (v, prev = []) => [...prev, v], [])
|
|
156
163
|
.option("--force-audio-file", "send an audio attachment as a regular file attachment instead of an iMessage voice message")
|
|
157
164
|
.option("--json", "machine-readable output")
|
|
@@ -167,6 +174,7 @@ const message = program
|
|
|
167
174
|
process.exit(await runMessageSend({
|
|
168
175
|
to: opts.to,
|
|
169
176
|
body: opts.body,
|
|
177
|
+
fromNumber: opts.fromNumber,
|
|
170
178
|
fromNumberId: opts.fromNumberId,
|
|
171
179
|
media: opts.media,
|
|
172
180
|
forceAudioFile: !!opts.forceAudioFile,
|
|
@@ -204,6 +212,43 @@ message
|
|
|
204
212
|
since: opts.since,
|
|
205
213
|
json: !!opts.json,
|
|
206
214
|
})));
|
|
215
|
+
const typing = program
|
|
216
|
+
.command("typing")
|
|
217
|
+
.description("Show or clear a typing indicator. iMessage numbers display it; SMS numbers ignore it. POST /api/v1/typing.");
|
|
218
|
+
typing
|
|
219
|
+
.command("start")
|
|
220
|
+
.description("Show a typing indicator to a recipient, as if composing a message from your number.")
|
|
221
|
+
.option("--to-number <e164>", "recipient phone number, E.164 (e.g. +14155551234)")
|
|
222
|
+
.option("--from-number <ref>", "number the indicator appears from: id, owned E.164, or nickname (defaults to onboard's number)")
|
|
223
|
+
.option("--json", "machine-readable output")
|
|
224
|
+
.action(async (opts) => {
|
|
225
|
+
if (!opts.toNumber) {
|
|
226
|
+
console.error("error: --to-number is required. Use `dial typing start --help` for usage.");
|
|
227
|
+
process.exit(2);
|
|
228
|
+
}
|
|
229
|
+
process.exit(await runTypingStart({
|
|
230
|
+
toNumber: opts.toNumber,
|
|
231
|
+
fromNumber: opts.fromNumber,
|
|
232
|
+
json: !!opts.json,
|
|
233
|
+
}));
|
|
234
|
+
});
|
|
235
|
+
typing
|
|
236
|
+
.command("stop")
|
|
237
|
+
.description("Clear a typing indicator previously shown with `typing start`.")
|
|
238
|
+
.option("--to-number <e164>", "recipient phone number, E.164 (e.g. +14155551234)")
|
|
239
|
+
.option("--from-number <ref>", "number the indicator appears from: id, owned E.164, or nickname (defaults to onboard's number)")
|
|
240
|
+
.option("--json", "machine-readable output")
|
|
241
|
+
.action(async (opts) => {
|
|
242
|
+
if (!opts.toNumber) {
|
|
243
|
+
console.error("error: --to-number is required. Use `dial typing stop --help` for usage.");
|
|
244
|
+
process.exit(2);
|
|
245
|
+
}
|
|
246
|
+
process.exit(await runTypingStop({
|
|
247
|
+
toNumber: opts.toNumber,
|
|
248
|
+
fromNumber: opts.fromNumber,
|
|
249
|
+
json: !!opts.json,
|
|
250
|
+
}));
|
|
251
|
+
});
|
|
207
252
|
const call = program
|
|
208
253
|
.command("call")
|
|
209
254
|
.description("Place an outbound voice call. POST /api/v1/calls.")
|
|
@@ -213,7 +258,8 @@ const call = program
|
|
|
213
258
|
.option("--voice-gender <male|female>", "voice gender for the agent (default: female; pass male to override)")
|
|
214
259
|
.option("--transfer-to <e164>", "forward-to number, E.164: the agent waits for a real human (riding out hold/IVR) then cold-transfers the call here")
|
|
215
260
|
.option("--idempotency-key <key>", "unique key (e.g. a UUID) making the placement idempotent: re-running with the same key returns the already-placed call instead of dialing again")
|
|
216
|
-
.option("--from-number
|
|
261
|
+
.option("--from-number <ref>", "number to call from: id, owned E.164, or nickname (defaults to onboard's number; exclusive with --from-number-id)")
|
|
262
|
+
.option("--from-number-id <id>", "phoneNumberId to call from (defaults to onboard's number; exclusive with --from-number)")
|
|
217
263
|
.option("--max-call-duration <seconds>", "maximum call duration cap (seconds); call is terminated when this limit is reached", (v) => {
|
|
218
264
|
const n = parseInt(v, 10);
|
|
219
265
|
if (!Number.isInteger(n) || n <= 0 || String(n) !== v.trim()) {
|
|
@@ -235,6 +281,7 @@ const call = program
|
|
|
235
281
|
voiceGender: opts.voiceGender,
|
|
236
282
|
transferTo: opts.transferTo,
|
|
237
283
|
idempotencyKey: opts.idempotencyKey,
|
|
284
|
+
fromNumber: opts.fromNumber,
|
|
238
285
|
fromNumberId: opts.fromNumberId,
|
|
239
286
|
maxCallDurationSeconds: opts.maxCallDuration,
|
|
240
287
|
json: !!opts.json,
|
package/dist/commands/billing.js
CHANGED
|
@@ -19,6 +19,9 @@ export async function runBilling(opts) {
|
|
|
19
19
|
else {
|
|
20
20
|
console.log(`plan: pay-as-you-go`);
|
|
21
21
|
}
|
|
22
|
+
if (billing.numbersReleaseAt) {
|
|
23
|
+
console.log(`at risk: balance is negative — all numbers release ${billing.numbersReleaseAt} for non-payment (top up to keep them)`);
|
|
24
|
+
}
|
|
22
25
|
if (billing.numbers.length > 0) {
|
|
23
26
|
console.log(`numbers:`);
|
|
24
27
|
for (const n of billing.numbers) {
|
|
@@ -10,6 +10,7 @@ export async function runCallSend(opts) {
|
|
|
10
10
|
voiceGender: opts.voiceGender,
|
|
11
11
|
transferTo: opts.transferTo,
|
|
12
12
|
idempotencyKey: opts.idempotencyKey,
|
|
13
|
+
fromNumber: opts.fromNumber,
|
|
13
14
|
fromNumberId: opts.fromNumberId,
|
|
14
15
|
maxCallDurationSeconds: opts.maxCallDurationSeconds,
|
|
15
16
|
});
|
|
@@ -7,6 +7,7 @@ export async function runNumberPurchase(opts) {
|
|
|
7
7
|
inboundInstruction: opts.inboundInstruction,
|
|
8
8
|
explicitProgrammaticConsent: opts.explicitProgrammaticConsent,
|
|
9
9
|
inboundVoiceGender: opts.inboundVoiceGender,
|
|
10
|
+
inboundLanguage: opts.inboundLanguage,
|
|
10
11
|
areaCode: opts.areaCode,
|
|
11
12
|
includeImessage: opts.includeImessage,
|
|
12
13
|
});
|
|
@@ -7,6 +7,7 @@ export async function runNumberSet(opts) {
|
|
|
7
7
|
number: opts.number,
|
|
8
8
|
inboundInstruction: opts.inboundInstruction,
|
|
9
9
|
...(opts.inboundVoiceGender !== undefined ? { inboundVoiceGender: opts.inboundVoiceGender } : {}),
|
|
10
|
+
...(opts.inboundLanguage !== undefined ? { inboundLanguage: opts.inboundLanguage } : {}),
|
|
10
11
|
...(opts.nickname !== undefined ? { nickname: opts.nickname } : {}),
|
|
11
12
|
...(opts.maxCallDurationSeconds !== undefined ? { maxCallDurationSeconds: opts.maxCallDurationSeconds } : {}),
|
|
12
13
|
});
|
|
@@ -20,6 +21,7 @@ export async function runNumberSet(opts) {
|
|
|
20
21
|
console.log(` nickname: ${n.nickname ?? ""}`);
|
|
21
22
|
console.log(` inbound instruction: ${n.inboundInstruction ?? ""}`);
|
|
22
23
|
console.log(` inbound voice gender: ${n.inboundVoiceGender ?? ""}`);
|
|
24
|
+
console.log(` inbound language: ${n.inboundLanguage ?? ""}`);
|
|
23
25
|
}
|
|
24
26
|
return 0;
|
|
25
27
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { setTyping } from "../../lib/ops/typing.js";
|
|
2
|
+
import { isDialError } from "../../lib/ops/errors.js";
|
|
3
|
+
import { printDialError } from "../../lib/cli-error.js";
|
|
4
|
+
export async function runTypingStart(opts) {
|
|
5
|
+
try {
|
|
6
|
+
const result = await setTyping({ toNumber: opts.toNumber, value: true, fromNumber: opts.fromNumber });
|
|
7
|
+
if (opts.json) {
|
|
8
|
+
console.log(JSON.stringify(result));
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
console.log(`typing indicator shown to ${opts.toNumber} (iMessage numbers only — SMS numbers ignore it).`);
|
|
12
|
+
}
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
if (isDialError(e))
|
|
17
|
+
return printDialError(opts.json, e);
|
|
18
|
+
throw e;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { setTyping } from "../../lib/ops/typing.js";
|
|
2
|
+
import { isDialError } from "../../lib/ops/errors.js";
|
|
3
|
+
import { printDialError } from "../../lib/cli-error.js";
|
|
4
|
+
export async function runTypingStop(opts) {
|
|
5
|
+
try {
|
|
6
|
+
const result = await setTyping({ toNumber: opts.toNumber, value: false, fromNumber: opts.fromNumber });
|
|
7
|
+
if (opts.json) {
|
|
8
|
+
console.log(JSON.stringify(result));
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
console.log(`typing indicator cleared for ${opts.toNumber}.`);
|
|
12
|
+
}
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
if (isDialError(e))
|
|
17
|
+
return printDialError(opts.json, e);
|
|
18
|
+
throw e;
|
|
19
|
+
}
|
|
20
|
+
}
|
package/dist/lib/ops/auth.js
CHANGED
|
@@ -16,3 +16,28 @@ export function requireFromNumberId(auth, override) {
|
|
|
16
16
|
}
|
|
17
17
|
return id;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a flexible from-number ref (id, owned E.164, or nickname): explicit
|
|
21
|
+
* override, else the saved default number id (an id is a valid ref), else throw.
|
|
22
|
+
*/
|
|
23
|
+
export function requireFromNumber(auth, override) {
|
|
24
|
+
const ref = override ?? auth.phoneNumberId;
|
|
25
|
+
if (!ref) {
|
|
26
|
+
throw new DialError("no_from_number", "No default phoneNumberId in auth. Pass --from-number <id|E.164|nickname>.");
|
|
27
|
+
}
|
|
28
|
+
return ref;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Pick the from-number selector field for send/call requests. `--from-number`
|
|
32
|
+
* (flexible ref) and `--from-number-id` (id only) are mutually exclusive —
|
|
33
|
+
* both given fails fast here, before any request; neither falls back to the
|
|
34
|
+
* saved default id via the legacy field.
|
|
35
|
+
*/
|
|
36
|
+
export function resolveFromSelector(auth, opts) {
|
|
37
|
+
if (opts.fromNumber && opts.fromNumberId) {
|
|
38
|
+
throw new DialError("from_number_conflict", "Provide only one of --from-number and --from-number-id.");
|
|
39
|
+
}
|
|
40
|
+
if (opts.fromNumber)
|
|
41
|
+
return { fromNumber: opts.fromNumber };
|
|
42
|
+
return { fromNumberId: requireFromNumberId(auth, opts.fromNumberId) };
|
|
43
|
+
}
|
package/dist/lib/ops/calls.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { apiGet, apiPost } from "../api.js";
|
|
2
|
-
import { requireAuth,
|
|
2
|
+
import { requireAuth, resolveFromSelector } from "./auth.js";
|
|
3
3
|
import { DialError } from "./errors.js";
|
|
4
4
|
export async function placeCall(opts) {
|
|
5
5
|
const auth = requireAuth();
|
|
6
|
-
const
|
|
6
|
+
const from = resolveFromSelector(auth, opts);
|
|
7
7
|
const res = await apiPost("/api/v1/calls", {
|
|
8
8
|
to: opts.to,
|
|
9
|
-
|
|
9
|
+
...from,
|
|
10
10
|
outboundInstruction: opts.outboundInstruction,
|
|
11
11
|
...(opts.language && { language: opts.language }),
|
|
12
12
|
// Omitted → the server uses the default voice gender (female).
|
package/dist/lib/ops/messages.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { basename, extname } from "node:path";
|
|
3
3
|
import { apiGet, apiPost, apiPostMultipart, ApiFormData } from "../api.js";
|
|
4
|
-
import { requireAuth,
|
|
4
|
+
import { requireAuth, resolveFromSelector } from "./auth.js";
|
|
5
5
|
import { DialError } from "./errors.js";
|
|
6
6
|
export const MAX_MEDIA_ITEMS = 10;
|
|
7
7
|
// File extensions the API accepts for uploads, mapped to their MIME type
|
|
@@ -46,7 +46,7 @@ function readMediaFile(path) {
|
|
|
46
46
|
}
|
|
47
47
|
export async function sendMessage(opts) {
|
|
48
48
|
const auth = requireAuth();
|
|
49
|
-
const
|
|
49
|
+
const from = resolveFromSelector(auth, opts);
|
|
50
50
|
const media = opts.media ?? [];
|
|
51
51
|
if (media.length > MAX_MEDIA_ITEMS) {
|
|
52
52
|
throw new DialError("too_much_media", `at most ${MAX_MEDIA_ITEMS} media items are allowed per message (got ${media.length})`);
|
|
@@ -61,7 +61,7 @@ export async function sendMessage(opts) {
|
|
|
61
61
|
res = await apiPost("/api/v1/messages", {
|
|
62
62
|
to: opts.to,
|
|
63
63
|
...(opts.body ? { body: opts.body } : {}),
|
|
64
|
-
|
|
64
|
+
...from,
|
|
65
65
|
...(media.length ? { mediaUrls: media } : {}),
|
|
66
66
|
...(opts.forceAudioFile ? { forceAudioFile: true } : {}),
|
|
67
67
|
}, auth.apiKey);
|
|
@@ -71,7 +71,8 @@ export async function sendMessage(opts) {
|
|
|
71
71
|
form.set("to", opts.to);
|
|
72
72
|
if (opts.body)
|
|
73
73
|
form.set("body", opts.body);
|
|
74
|
-
|
|
74
|
+
for (const [field, value] of Object.entries(from))
|
|
75
|
+
form.set(field, value);
|
|
75
76
|
if (opts.forceAudioFile)
|
|
76
77
|
form.set("forceAudioFile", "true");
|
|
77
78
|
for (const item of media) {
|
package/dist/lib/ops/numbers.js
CHANGED
|
@@ -16,6 +16,8 @@ export async function purchaseNumber(opts) {
|
|
|
16
16
|
};
|
|
17
17
|
if (opts.inboundVoiceGender)
|
|
18
18
|
body.inboundVoiceGender = opts.inboundVoiceGender;
|
|
19
|
+
if (opts.inboundLanguage)
|
|
20
|
+
body.inboundLanguage = opts.inboundLanguage;
|
|
19
21
|
// iMessage numbers ignore areaCode, so only send it for standard numbers.
|
|
20
22
|
if (opts.includeImessage)
|
|
21
23
|
body.capabilities = ["sms", "call", "imessage"];
|
|
@@ -33,12 +35,14 @@ export async function setNumberProperties(opts) {
|
|
|
33
35
|
// Empty string clears the override → send null (the enum API rejects "").
|
|
34
36
|
if (opts.inboundVoiceGender !== undefined)
|
|
35
37
|
body.inboundVoiceGender = opts.inboundVoiceGender || null;
|
|
38
|
+
if (opts.inboundLanguage !== undefined)
|
|
39
|
+
body.inboundLanguage = opts.inboundLanguage || null;
|
|
36
40
|
if (opts.nickname !== undefined)
|
|
37
41
|
body.nickname = opts.nickname;
|
|
38
42
|
if (opts.maxCallDurationSeconds !== undefined)
|
|
39
43
|
body.maxCallDurationSeconds = opts.maxCallDurationSeconds;
|
|
40
44
|
if (Object.keys(body).length === 0) {
|
|
41
|
-
throw new DialError("bad_request", "Provide at least one property to update (inboundInstruction, inboundVoiceGender, nickname, or maxCallDurationSeconds).");
|
|
45
|
+
throw new DialError("bad_request", "Provide at least one property to update (inboundInstruction, inboundVoiceGender, inboundLanguage, nickname, or maxCallDurationSeconds).");
|
|
42
46
|
}
|
|
43
47
|
const auth = requireAuth();
|
|
44
48
|
// The REST API keys numbers by id; the CLI/tool takes the E.164 number for ergonomics,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { apiPost } from "../api.js";
|
|
2
|
+
import { requireAuth, requireFromNumber } from "./auth.js";
|
|
3
|
+
import { DialError } from "./errors.js";
|
|
4
|
+
/**
|
|
5
|
+
* Show or clear a typing indicator (POST /api/v1/typing). iMessage numbers
|
|
6
|
+
* display it; standard numbers have no typing concept and the server silently
|
|
7
|
+
* no-ops, so calling this unconditionally is safe.
|
|
8
|
+
*/
|
|
9
|
+
export async function setTyping(opts) {
|
|
10
|
+
const auth = requireAuth();
|
|
11
|
+
const fromNumber = requireFromNumber(auth, opts.fromNumber);
|
|
12
|
+
const res = await apiPost("/api/v1/typing", { toNumber: opts.toNumber, value: opts.value, fromNumber }, auth.apiKey);
|
|
13
|
+
if (!res.ok)
|
|
14
|
+
throw new DialError("typing_failed", res.error, res.status);
|
|
15
|
+
return res.data;
|
|
16
|
+
}
|
package/dist/mcp/schemas.js
CHANGED
|
@@ -26,6 +26,7 @@ export const phoneNumberSchema = z
|
|
|
26
26
|
country: z.string().optional(),
|
|
27
27
|
inboundInstruction: z.string().nullable().optional(),
|
|
28
28
|
inboundVoiceGender: z.string().nullable().optional().describe('Voice gender for inbound calls ("male"/"female"); null → female (the default)'),
|
|
29
|
+
inboundLanguage: z.string().nullable().optional().describe("BCP-47 language tag inbound calls are pinned to; null → detected from the caller's country prefix per call"),
|
|
29
30
|
})
|
|
30
31
|
.passthrough();
|
|
31
32
|
export const messageSchema = z
|
package/dist/mcp/tools/index.js
CHANGED
|
@@ -3,6 +3,8 @@ import { purchaseNumberTool } from "./purchase-number.js";
|
|
|
3
3
|
import { setNumberPropertiesTool } from "./set-number-properties.js";
|
|
4
4
|
import { sendMessageTool } from "./send-message.js";
|
|
5
5
|
import { replyToMessageTool } from "./reply-to-message.js";
|
|
6
|
+
import { startTypingTool } from "./start-typing.js";
|
|
7
|
+
import { stopTypingTool } from "./stop-typing.js";
|
|
6
8
|
import { listMessagesTool } from "./list-messages.js";
|
|
7
9
|
import { placeCallTool } from "./place-call.js";
|
|
8
10
|
import { listCallsTool } from "./list-calls.js";
|
|
@@ -25,6 +27,8 @@ export const tools = [
|
|
|
25
27
|
setNumberPropertiesTool,
|
|
26
28
|
sendMessageTool,
|
|
27
29
|
replyToMessageTool,
|
|
30
|
+
startTypingTool,
|
|
31
|
+
stopTypingTool,
|
|
28
32
|
listMessagesTool,
|
|
29
33
|
placeCallTool,
|
|
30
34
|
listCallsTool,
|
|
@@ -9,6 +9,11 @@ const inputSchema = {
|
|
|
9
9
|
voiceGender: z.enum(["male", "female"]).optional().describe("Voice gender for the agent; the default is female"),
|
|
10
10
|
transferTo: z.string().optional().describe("Forward-to number, E.164: the agent waits for a real human (riding out hold/IVR) then cold-transfers the call here. Must differ from `to` and the from number."),
|
|
11
11
|
idempotencyKey: z.string().optional().describe("Unique key (e.g. a UUID) making the placement idempotent: retrying with the same key returns the already-placed call instead of dialing again"),
|
|
12
|
+
fromNumber: z
|
|
13
|
+
.string()
|
|
14
|
+
.min(1)
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Number to call from: a phone number id, one of your numbers in E.164, or a nickname. Exclusive with fromNumberId; omit both to use your primary number"),
|
|
12
17
|
fromNumberId: z.string().optional().describe("Number id to call from; defaults to your primary number"),
|
|
13
18
|
maxCallDurationSeconds: z.number().int().positive().optional().describe("Maximum call duration cap (seconds); the call is terminated when this limit is reached"),
|
|
14
19
|
};
|
|
@@ -30,6 +35,7 @@ export const placeCallTool = {
|
|
|
30
35
|
voiceGender: args.voiceGender,
|
|
31
36
|
transferTo: args.transferTo,
|
|
32
37
|
idempotencyKey: args.idempotencyKey,
|
|
38
|
+
fromNumber: args.fromNumber,
|
|
33
39
|
fromNumberId: args.fromNumberId,
|
|
34
40
|
maxCallDurationSeconds: args.maxCallDurationSeconds,
|
|
35
41
|
});
|
|
@@ -6,6 +6,7 @@ const inputSchema = {
|
|
|
6
6
|
inboundInstruction: z.string().min(1).describe("System prompt for inbound calls to this number"),
|
|
7
7
|
explicitProgrammaticConsent: z.string().min(1).max(2000).describe("Required attestation (max 2000 chars) that the account holder consented to provisioning this number programmatically; stored on the number"),
|
|
8
8
|
inboundVoiceGender: z.enum(["male", "female"]).optional().describe("Voice gender for inbound calls to this number; the default is female"),
|
|
9
|
+
inboundLanguage: z.string().optional().describe("BCP-47 language tag pinning inbound calls to this number to one language (e.g. es-ES); omitted → the language is detected from the caller's country prefix on each call (plus en-US)"),
|
|
9
10
|
areaCode: z.string().optional().describe("Preferred US area code; omitted → any available US number. Only US numbers can be provisioned at this time. Ignored for iMessage numbers"),
|
|
10
11
|
includeImessage: z.boolean().optional().describe('Provision an iMessage number (pay-as-you-go only; provisioned asynchronously — poll List Numbers until setupStatus is "ready")'),
|
|
11
12
|
};
|
|
@@ -23,6 +24,7 @@ export const purchaseNumberTool = {
|
|
|
23
24
|
inboundInstruction: args.inboundInstruction,
|
|
24
25
|
explicitProgrammaticConsent: args.explicitProgrammaticConsent,
|
|
25
26
|
inboundVoiceGender: args.inboundVoiceGender,
|
|
27
|
+
inboundLanguage: args.inboundLanguage,
|
|
26
28
|
areaCode: args.areaCode,
|
|
27
29
|
includeImessage: args.includeImessage,
|
|
28
30
|
}),
|
|
@@ -5,6 +5,11 @@ import { messageSchema } from "../schemas.js";
|
|
|
5
5
|
const inputSchema = {
|
|
6
6
|
to: z.string().min(7).describe("Destination phone number, E.164 (e.g. +14155550123)"),
|
|
7
7
|
body: z.string().optional().describe("Message body; optional when mediaUrls is given (media-only send)"),
|
|
8
|
+
fromNumber: z
|
|
9
|
+
.string()
|
|
10
|
+
.min(1)
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Number to send from: a phone number id, one of your numbers in E.164, or a nickname. Exclusive with fromNumberId; omit both to use your primary number"),
|
|
8
13
|
fromNumberId: z.string().optional().describe("Number id to send from; defaults to your primary number"),
|
|
9
14
|
mediaUrls: z
|
|
10
15
|
.array(z.string().url())
|
|
@@ -29,6 +34,7 @@ export const sendMessageTool = {
|
|
|
29
34
|
message: await sendMessage({
|
|
30
35
|
to: args.to,
|
|
31
36
|
body: args.body,
|
|
37
|
+
fromNumber: args.fromNumber,
|
|
32
38
|
fromNumberId: args.fromNumberId,
|
|
33
39
|
media: args.mediaUrls,
|
|
34
40
|
forceAudioFile: args.forceAudioFile,
|
|
@@ -6,6 +6,7 @@ const inputSchema = {
|
|
|
6
6
|
number: z.string().min(7).describe("The E.164 phone number to update (e.g. +14155550123)"),
|
|
7
7
|
inboundInstruction: z.string().min(1).optional().describe("New system prompt for inbound calls to this number"),
|
|
8
8
|
inboundVoiceGender: z.enum(["male", "female"]).optional().describe("Voice gender for inbound calls to this number; the default is female"),
|
|
9
|
+
inboundLanguage: z.string().optional().describe("BCP-47 language tag pinning inbound calls to this number to one language (e.g. es-ES). Pass an empty string to clear it (reverts to detecting the language from the caller's country prefix per call)."),
|
|
9
10
|
nickname: z.string().max(100).optional().describe('Human-readable label for the number, e.g. "Support line". Pass an empty string to clear it.'),
|
|
10
11
|
maxCallDurationSeconds: z.number().int().positive().nullable().optional().describe("Call duration cap for this number, in seconds, applied as a hard ceiling to both inbound and outbound calls (the smallest of the per-number, account, and per-call caps wins). Pass null to clear the cap; omit to leave it unchanged."),
|
|
11
12
|
};
|
|
@@ -13,7 +14,7 @@ export const setNumberPropertiesTool = {
|
|
|
13
14
|
name: "set_number_properties",
|
|
14
15
|
config: {
|
|
15
16
|
title: "Set Number Properties",
|
|
16
|
-
description: "Update a phone number's properties: its inbound instruction (the system prompt for inbound calls) and/or its nickname. Provide at least one.",
|
|
17
|
+
description: "Update a phone number's properties: its inbound instruction (the system prompt for inbound calls), inbound voice gender, inbound language, and/or its nickname. Provide at least one.",
|
|
17
18
|
inputSchema,
|
|
18
19
|
outputSchema: { number: phoneNumberSchema },
|
|
19
20
|
annotations: { openWorldHint: true },
|
|
@@ -23,6 +24,7 @@ export const setNumberPropertiesTool = {
|
|
|
23
24
|
number: args.number,
|
|
24
25
|
inboundInstruction: args.inboundInstruction,
|
|
25
26
|
...(args.inboundVoiceGender !== undefined ? { inboundVoiceGender: args.inboundVoiceGender } : {}),
|
|
27
|
+
...(args.inboundLanguage !== undefined ? { inboundLanguage: args.inboundLanguage } : {}),
|
|
26
28
|
...(args.nickname !== undefined ? { nickname: args.nickname } : {}),
|
|
27
29
|
...(args.maxCallDurationSeconds !== undefined ? { maxCallDurationSeconds: args.maxCallDurationSeconds } : {}),
|
|
28
30
|
}),
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { jsonResult } from "../result.js";
|
|
3
|
+
import { setTyping } from "../../lib/ops/typing.js";
|
|
4
|
+
const inputSchema = {
|
|
5
|
+
toNumber: z.string().min(7).describe("Recipient phone number, E.164 (e.g. +14155550123)"),
|
|
6
|
+
fromNumber: z
|
|
7
|
+
.string()
|
|
8
|
+
.min(1)
|
|
9
|
+
.describe("Number the indicator appears from: a phone number id, one of your numbers in E.164, or a nickname"),
|
|
10
|
+
};
|
|
11
|
+
export const startTypingTool = {
|
|
12
|
+
name: "start_typing",
|
|
13
|
+
config: {
|
|
14
|
+
title: "Start typing indicator",
|
|
15
|
+
description: "Show a typing indicator to the recipient, as if someone were composing a message from your number. " +
|
|
16
|
+
"iMessage numbers display it; standard (SMS) numbers have no typing concept and silently ignore it, " +
|
|
17
|
+
"so this is safe to call unconditionally before a send. Fire-and-forget and free. " +
|
|
18
|
+
"Delivering a message or reaction clears the indicator natively on the recipient's device — " +
|
|
19
|
+
"call start_typing again after a send to keep composing, and stop_typing when you stop without sending.",
|
|
20
|
+
inputSchema,
|
|
21
|
+
outputSchema: { ok: z.boolean() },
|
|
22
|
+
annotations: { openWorldHint: true },
|
|
23
|
+
},
|
|
24
|
+
run: async (args) => jsonResult(await setTyping({
|
|
25
|
+
toNumber: args.toNumber,
|
|
26
|
+
fromNumber: args.fromNumber,
|
|
27
|
+
value: true,
|
|
28
|
+
})),
|
|
29
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { jsonResult } from "../result.js";
|
|
3
|
+
import { setTyping } from "../../lib/ops/typing.js";
|
|
4
|
+
const inputSchema = {
|
|
5
|
+
toNumber: z.string().min(7).describe("Recipient phone number, E.164 (e.g. +14155550123)"),
|
|
6
|
+
fromNumber: z
|
|
7
|
+
.string()
|
|
8
|
+
.min(1)
|
|
9
|
+
.describe("Number the indicator appears from: a phone number id, one of your numbers in E.164, or a nickname"),
|
|
10
|
+
};
|
|
11
|
+
export const stopTypingTool = {
|
|
12
|
+
name: "stop_typing",
|
|
13
|
+
config: {
|
|
14
|
+
title: "Stop typing indicator",
|
|
15
|
+
description: "Clear a typing indicator previously shown with start_typing. Delivering a message or reaction " +
|
|
16
|
+
"already clears it natively on the recipient's device — call this when you stop composing " +
|
|
17
|
+
"without sending. Standard (SMS) numbers silently ignore it.",
|
|
18
|
+
inputSchema,
|
|
19
|
+
outputSchema: { ok: z.boolean() },
|
|
20
|
+
annotations: { openWorldHint: true },
|
|
21
|
+
},
|
|
22
|
+
run: async (args) => jsonResult(await setTyping({
|
|
23
|
+
toNumber: args.toNumber,
|
|
24
|
+
fromNumber: args.fromNumber,
|
|
25
|
+
value: false,
|
|
26
|
+
})),
|
|
27
|
+
};
|
package/package.json
CHANGED
package/skills.tar.gz
CHANGED
|
Binary file
|