@getdial/cli 0.28.0 → 0.30.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/README.md +1 -0
- package/dist/cli.js +64 -2
- package/dist/commands/call/send.js +1 -0
- package/dist/commands/message/reply.js +29 -0
- package/dist/commands/message/send.js +1 -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 +19 -4
- package/dist/lib/ops/typing.js +16 -0
- package/dist/mcp/schemas.js +2 -0
- package/dist/mcp/tools/index.js +6 -0
- package/dist/mcp/tools/place-call.js +6 -0
- package/dist/mcp/tools/reply-to-message.js +31 -0
- package/dist/mcp/tools/send-message.js +6 -0
- 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/README.md
CHANGED
|
@@ -54,6 +54,7 @@ dial wait-for message.received --field to=+14155550123
|
|
|
54
54
|
| `dial number set <number>` | Update a number's inbound instruction. |
|
|
55
55
|
| `dial message` | Send an SMS. |
|
|
56
56
|
| `dial message list` | List recent messages. |
|
|
57
|
+
| `dial message reply` | Reply or react to a message. |
|
|
57
58
|
| `dial call` | Place an outbound AI voice call. |
|
|
58
59
|
| `dial call list` | List recent calls. |
|
|
59
60
|
| `dial call get <id>` | Fetch a single call — status, duration, transcript. |
|
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,10 @@ import { runNumberList } from "./commands/number/list.js";
|
|
|
14
14
|
import { runNumberPurchase } from "./commands/number/purchase.js";
|
|
15
15
|
import { runNumberSet } from "./commands/number/set.js";
|
|
16
16
|
import { runMessageSend } from "./commands/message/send.js";
|
|
17
|
+
import { runMessageReply } from "./commands/message/reply.js";
|
|
17
18
|
import { runMessageList } from "./commands/message/list.js";
|
|
19
|
+
import { runTypingStart } from "./commands/typing/start.js";
|
|
20
|
+
import { runTypingStop } from "./commands/typing/stop.js";
|
|
18
21
|
import { runCallSend } from "./commands/call/send.js";
|
|
19
22
|
import { runCallList } from "./commands/call/list.js";
|
|
20
23
|
import { runCallGet } from "./commands/call/get.js";
|
|
@@ -150,7 +153,8 @@ const message = program
|
|
|
150
153
|
.description("Send an SMS, optionally with media (MMS). POST /api/v1/messages.")
|
|
151
154
|
.option("--to <e164>", "destination phone number, E.164 (e.g. +14155551234)")
|
|
152
155
|
.option("--body <text>", "message body")
|
|
153
|
-
.option("--from-number
|
|
156
|
+
.option("--from-number <ref>", "number to send from: id, owned E.164, or nickname (defaults to onboard's number; exclusive with --from-number-id)")
|
|
157
|
+
.option("--from-number-id <id>", "phoneNumberId to send from (defaults to onboard's number; exclusive with --from-number)")
|
|
154
158
|
.option("--media <path-or-url>", "media attachment: local file path (uploaded) or public http(s) URL (repeatable, max 10)", (v, prev = []) => [...prev, v], [])
|
|
155
159
|
.option("--force-audio-file", "send an audio attachment as a regular file attachment instead of an iMessage voice message")
|
|
156
160
|
.option("--json", "machine-readable output")
|
|
@@ -166,12 +170,31 @@ const message = program
|
|
|
166
170
|
process.exit(await runMessageSend({
|
|
167
171
|
to: opts.to,
|
|
168
172
|
body: opts.body,
|
|
173
|
+
fromNumber: opts.fromNumber,
|
|
169
174
|
fromNumberId: opts.fromNumberId,
|
|
170
175
|
media: opts.media,
|
|
171
176
|
forceAudioFile: !!opts.forceAudioFile,
|
|
172
177
|
json: !!opts.json,
|
|
173
178
|
}));
|
|
174
179
|
});
|
|
180
|
+
message
|
|
181
|
+
.command("reply <messageId>")
|
|
182
|
+
.description("Reply or react to a message. POST /api/v1/messages/:id/reply.")
|
|
183
|
+
.option("--body <text>", "reply text (threads under the target on iMessage numbers)")
|
|
184
|
+
.option("--react <reaction>", "reaction: love|like|dislike|laugh|emphasize|question, or a single emoji")
|
|
185
|
+
.option("--json", "machine-readable output")
|
|
186
|
+
.action(async (messageId, opts) => {
|
|
187
|
+
if ((opts.body === undefined) === (opts.react === undefined)) {
|
|
188
|
+
console.error("error: provide exactly one of --body or --react. Use `dial message reply --help` for usage.");
|
|
189
|
+
process.exit(2);
|
|
190
|
+
}
|
|
191
|
+
process.exit(await runMessageReply({
|
|
192
|
+
messageId,
|
|
193
|
+
body: opts.body,
|
|
194
|
+
react: opts.react,
|
|
195
|
+
json: !!opts.json,
|
|
196
|
+
}));
|
|
197
|
+
});
|
|
175
198
|
message
|
|
176
199
|
.command("list")
|
|
177
200
|
.description("List recent messages on your account. GET /api/v1/messages.")
|
|
@@ -185,6 +208,43 @@ message
|
|
|
185
208
|
since: opts.since,
|
|
186
209
|
json: !!opts.json,
|
|
187
210
|
})));
|
|
211
|
+
const typing = program
|
|
212
|
+
.command("typing")
|
|
213
|
+
.description("Show or clear a typing indicator. iMessage numbers display it; SMS numbers ignore it. POST /api/v1/typing.");
|
|
214
|
+
typing
|
|
215
|
+
.command("start")
|
|
216
|
+
.description("Show a typing indicator to a recipient, as if composing a message from your number.")
|
|
217
|
+
.option("--to-number <e164>", "recipient phone number, E.164 (e.g. +14155551234)")
|
|
218
|
+
.option("--from-number <ref>", "number the indicator appears from: id, owned E.164, or nickname (defaults to onboard's number)")
|
|
219
|
+
.option("--json", "machine-readable output")
|
|
220
|
+
.action(async (opts) => {
|
|
221
|
+
if (!opts.toNumber) {
|
|
222
|
+
console.error("error: --to-number is required. Use `dial typing start --help` for usage.");
|
|
223
|
+
process.exit(2);
|
|
224
|
+
}
|
|
225
|
+
process.exit(await runTypingStart({
|
|
226
|
+
toNumber: opts.toNumber,
|
|
227
|
+
fromNumber: opts.fromNumber,
|
|
228
|
+
json: !!opts.json,
|
|
229
|
+
}));
|
|
230
|
+
});
|
|
231
|
+
typing
|
|
232
|
+
.command("stop")
|
|
233
|
+
.description("Clear a typing indicator previously shown with `typing start`.")
|
|
234
|
+
.option("--to-number <e164>", "recipient phone number, E.164 (e.g. +14155551234)")
|
|
235
|
+
.option("--from-number <ref>", "number the indicator appears from: id, owned E.164, or nickname (defaults to onboard's number)")
|
|
236
|
+
.option("--json", "machine-readable output")
|
|
237
|
+
.action(async (opts) => {
|
|
238
|
+
if (!opts.toNumber) {
|
|
239
|
+
console.error("error: --to-number is required. Use `dial typing stop --help` for usage.");
|
|
240
|
+
process.exit(2);
|
|
241
|
+
}
|
|
242
|
+
process.exit(await runTypingStop({
|
|
243
|
+
toNumber: opts.toNumber,
|
|
244
|
+
fromNumber: opts.fromNumber,
|
|
245
|
+
json: !!opts.json,
|
|
246
|
+
}));
|
|
247
|
+
});
|
|
188
248
|
const call = program
|
|
189
249
|
.command("call")
|
|
190
250
|
.description("Place an outbound voice call. POST /api/v1/calls.")
|
|
@@ -194,7 +254,8 @@ const call = program
|
|
|
194
254
|
.option("--voice-gender <male|female>", "voice gender for the agent (default: female; pass male to override)")
|
|
195
255
|
.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")
|
|
196
256
|
.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")
|
|
197
|
-
.option("--from-number
|
|
257
|
+
.option("--from-number <ref>", "number to call from: id, owned E.164, or nickname (defaults to onboard's number; exclusive with --from-number-id)")
|
|
258
|
+
.option("--from-number-id <id>", "phoneNumberId to call from (defaults to onboard's number; exclusive with --from-number)")
|
|
198
259
|
.option("--max-call-duration <seconds>", "maximum call duration cap (seconds); call is terminated when this limit is reached", (v) => {
|
|
199
260
|
const n = parseInt(v, 10);
|
|
200
261
|
if (!Number.isInteger(n) || n <= 0 || String(n) !== v.trim()) {
|
|
@@ -216,6 +277,7 @@ const call = program
|
|
|
216
277
|
voiceGender: opts.voiceGender,
|
|
217
278
|
transferTo: opts.transferTo,
|
|
218
279
|
idempotencyKey: opts.idempotencyKey,
|
|
280
|
+
fromNumber: opts.fromNumber,
|
|
219
281
|
fromNumberId: opts.fromNumberId,
|
|
220
282
|
maxCallDurationSeconds: opts.maxCallDuration,
|
|
221
283
|
json: !!opts.json,
|
|
@@ -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
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { replyToMessage } from "../../lib/ops/messages.js";
|
|
2
|
+
import { isDialError } from "../../lib/ops/errors.js";
|
|
3
|
+
import { printDialError } from "../../lib/cli-error.js";
|
|
4
|
+
export async function runMessageReply(opts) {
|
|
5
|
+
try {
|
|
6
|
+
const m = await replyToMessage({ messageId: opts.messageId, body: opts.body, reaction: opts.react });
|
|
7
|
+
if (opts.json) {
|
|
8
|
+
console.log(JSON.stringify({ ok: true, message: m }));
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
console.log(`sent.`);
|
|
12
|
+
console.log(` channel: ${m.channel}`);
|
|
13
|
+
console.log(` from: ${m.from}`);
|
|
14
|
+
console.log(` to: ${m.to}`);
|
|
15
|
+
console.log(` body: ${m.body}`);
|
|
16
|
+
if (m.reaction)
|
|
17
|
+
console.log(` reaction: ${m.reaction}`);
|
|
18
|
+
if (m.replyToId)
|
|
19
|
+
console.log(` replyTo: ${m.replyToId}`);
|
|
20
|
+
console.log(` status: ${m.status}`);
|
|
21
|
+
}
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
if (isDialError(e))
|
|
26
|
+
return printDialError(opts.json, e);
|
|
27
|
+
throw e;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -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) {
|
|
@@ -104,3 +105,17 @@ export async function listMessages(opts) {
|
|
|
104
105
|
throw new DialError("list_failed", res.error, res.status);
|
|
105
106
|
return res.data.messages ?? [];
|
|
106
107
|
}
|
|
108
|
+
export async function replyToMessage(opts) {
|
|
109
|
+
const auth = requireAuth();
|
|
110
|
+
// No `to`/`fromNumberId`: the server derives both from the target message —
|
|
111
|
+
// the reply stays in the conversation the target is part of.
|
|
112
|
+
const payload = {};
|
|
113
|
+
if (opts.body !== undefined)
|
|
114
|
+
payload.body = opts.body;
|
|
115
|
+
if (opts.reaction !== undefined)
|
|
116
|
+
payload.reaction = opts.reaction;
|
|
117
|
+
const res = await apiPost(`/api/v1/messages/${encodeURIComponent(opts.messageId)}/reply`, payload, auth.apiKey);
|
|
118
|
+
if (!res.ok)
|
|
119
|
+
throw new DialError("reply_failed", res.error, res.status);
|
|
120
|
+
return res.data.message;
|
|
121
|
+
}
|
|
@@ -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
|
@@ -38,6 +38,8 @@ export const messageSchema = z
|
|
|
38
38
|
direction: z.string().optional(),
|
|
39
39
|
status: statusSchema,
|
|
40
40
|
statusError: z.string().nullish().describe("Failure reason when status is undelivered/failed"),
|
|
41
|
+
replyToId: z.string().nullish().describe("Id of the message this one replies or reacts to; null for ordinary messages"),
|
|
42
|
+
reaction: z.string().nullish().describe("The reaction this message carries (a reaction name or an emoji); null otherwise"),
|
|
41
43
|
createdAt: z.string().optional(),
|
|
42
44
|
})
|
|
43
45
|
.passthrough();
|
package/dist/mcp/tools/index.js
CHANGED
|
@@ -2,6 +2,9 @@ import { listNumbersTool } from "./list-numbers.js";
|
|
|
2
2
|
import { purchaseNumberTool } from "./purchase-number.js";
|
|
3
3
|
import { setNumberPropertiesTool } from "./set-number-properties.js";
|
|
4
4
|
import { sendMessageTool } from "./send-message.js";
|
|
5
|
+
import { replyToMessageTool } from "./reply-to-message.js";
|
|
6
|
+
import { startTypingTool } from "./start-typing.js";
|
|
7
|
+
import { stopTypingTool } from "./stop-typing.js";
|
|
5
8
|
import { listMessagesTool } from "./list-messages.js";
|
|
6
9
|
import { placeCallTool } from "./place-call.js";
|
|
7
10
|
import { listCallsTool } from "./list-calls.js";
|
|
@@ -23,6 +26,9 @@ export const tools = [
|
|
|
23
26
|
purchaseNumberTool,
|
|
24
27
|
setNumberPropertiesTool,
|
|
25
28
|
sendMessageTool,
|
|
29
|
+
replyToMessageTool,
|
|
30
|
+
startTypingTool,
|
|
31
|
+
stopTypingTool,
|
|
26
32
|
listMessagesTool,
|
|
27
33
|
placeCallTool,
|
|
28
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
|
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { jsonResult } from "../result.js";
|
|
3
|
+
import { replyToMessage } from "../../lib/ops/messages.js";
|
|
4
|
+
import { messageSchema } from "../schemas.js";
|
|
5
|
+
const inputSchema = {
|
|
6
|
+
messageId: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe("Id of the message to reply or react to (from list_messages or a message.received event)"),
|
|
9
|
+
body: z.string().optional().describe("Reply text; on an iMessage number it threads under the target message"),
|
|
10
|
+
reaction: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Reaction to send instead of a body: love, like, dislike, laugh, emphasize, question, or a single emoji"),
|
|
14
|
+
};
|
|
15
|
+
export const replyToMessageTool = {
|
|
16
|
+
name: "reply_to_message",
|
|
17
|
+
config: {
|
|
18
|
+
title: "Reply to a message",
|
|
19
|
+
description: "Reply in-thread or react to an existing message. The reply goes out from the Dial number the target message belongs to, to the other party — no from/to needed. Provide exactly one of body or reaction. On iMessage numbers replies thread and reactions are native; recipients that can only receive SMS get an emoji reaction as a regular text, and named reactions are rejected.",
|
|
20
|
+
inputSchema,
|
|
21
|
+
outputSchema: { message: messageSchema },
|
|
22
|
+
annotations: { openWorldHint: true },
|
|
23
|
+
},
|
|
24
|
+
run: async (args) => jsonResult({
|
|
25
|
+
message: await replyToMessage({
|
|
26
|
+
messageId: args.messageId,
|
|
27
|
+
body: args.body,
|
|
28
|
+
reaction: args.reaction,
|
|
29
|
+
}),
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
@@ -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,
|
|
@@ -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
|