@getdial/cli 0.27.0 → 0.29.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 +27 -2
- package/dist/commands/message/reply.js +29 -0
- package/dist/commands/message/send.js +7 -1
- package/dist/lib/ops/messages.js +25 -2
- package/dist/mcp/schemas.js +2 -0
- package/dist/mcp/tools/index.js +2 -0
- package/dist/mcp/tools/reply-to-message.js +31 -0
- package/dist/mcp/tools/send-message.js +15 -4
- 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,6 +14,7 @@ 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";
|
|
18
19
|
import { runCallSend } from "./commands/call/send.js";
|
|
19
20
|
import { runCallList } from "./commands/call/list.js";
|
|
@@ -152,10 +153,15 @@ const message = program
|
|
|
152
153
|
.option("--body <text>", "message body")
|
|
153
154
|
.option("--from-number-id <id>", "phoneNumberId to send from (defaults to onboard's number)")
|
|
154
155
|
.option("--media <path-or-url>", "media attachment: local file path (uploaded) or public http(s) URL (repeatable, max 10)", (v, prev = []) => [...prev, v], [])
|
|
156
|
+
.option("--force-audio-file", "send an audio attachment as a regular file attachment instead of an iMessage voice message")
|
|
155
157
|
.option("--json", "machine-readable output")
|
|
156
158
|
.action(async (opts) => {
|
|
157
|
-
if (!opts.to
|
|
158
|
-
console.error("error: --to
|
|
159
|
+
if (!opts.to) {
|
|
160
|
+
console.error("error: --to is required to send a message. Use `dial message list` to list, or `dial message --help` for usage.");
|
|
161
|
+
process.exit(2);
|
|
162
|
+
}
|
|
163
|
+
if (!opts.body && (opts.media ?? []).length === 0) {
|
|
164
|
+
console.error("error: provide --body, --media, or both — a message needs text or an attachment.");
|
|
159
165
|
process.exit(2);
|
|
160
166
|
}
|
|
161
167
|
process.exit(await runMessageSend({
|
|
@@ -163,6 +169,25 @@ const message = program
|
|
|
163
169
|
body: opts.body,
|
|
164
170
|
fromNumberId: opts.fromNumberId,
|
|
165
171
|
media: opts.media,
|
|
172
|
+
forceAudioFile: !!opts.forceAudioFile,
|
|
173
|
+
json: !!opts.json,
|
|
174
|
+
}));
|
|
175
|
+
});
|
|
176
|
+
message
|
|
177
|
+
.command("reply <messageId>")
|
|
178
|
+
.description("Reply or react to a message. POST /api/v1/messages/:id/reply.")
|
|
179
|
+
.option("--body <text>", "reply text (threads under the target on iMessage numbers)")
|
|
180
|
+
.option("--react <reaction>", "reaction: love|like|dislike|laugh|emphasize|question, or a single emoji")
|
|
181
|
+
.option("--json", "machine-readable output")
|
|
182
|
+
.action(async (messageId, opts) => {
|
|
183
|
+
if ((opts.body === undefined) === (opts.react === undefined)) {
|
|
184
|
+
console.error("error: provide exactly one of --body or --react. Use `dial message reply --help` for usage.");
|
|
185
|
+
process.exit(2);
|
|
186
|
+
}
|
|
187
|
+
process.exit(await runMessageReply({
|
|
188
|
+
messageId,
|
|
189
|
+
body: opts.body,
|
|
190
|
+
react: opts.react,
|
|
166
191
|
json: !!opts.json,
|
|
167
192
|
}));
|
|
168
193
|
});
|
|
@@ -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
|
+
}
|
|
@@ -3,7 +3,13 @@ import { isDialError } from "../../lib/ops/errors.js";
|
|
|
3
3
|
import { printDialError } from "../../lib/cli-error.js";
|
|
4
4
|
export async function runMessageSend(opts) {
|
|
5
5
|
try {
|
|
6
|
-
const m = await sendMessage({
|
|
6
|
+
const m = await sendMessage({
|
|
7
|
+
to: opts.to,
|
|
8
|
+
body: opts.body,
|
|
9
|
+
fromNumberId: opts.fromNumberId,
|
|
10
|
+
media: opts.media,
|
|
11
|
+
forceAudioFile: opts.forceAudioFile,
|
|
12
|
+
});
|
|
7
13
|
if (opts.json) {
|
|
8
14
|
console.log(JSON.stringify({ ok: true, message: m }));
|
|
9
15
|
}
|
package/dist/lib/ops/messages.js
CHANGED
|
@@ -58,13 +58,22 @@ export async function sendMessage(opts) {
|
|
|
58
58
|
const hasFiles = media.some((m) => !isHttpUrl(m));
|
|
59
59
|
let res;
|
|
60
60
|
if (!hasFiles) {
|
|
61
|
-
res = await apiPost("/api/v1/messages", {
|
|
61
|
+
res = await apiPost("/api/v1/messages", {
|
|
62
|
+
to: opts.to,
|
|
63
|
+
...(opts.body ? { body: opts.body } : {}),
|
|
64
|
+
fromNumberId,
|
|
65
|
+
...(media.length ? { mediaUrls: media } : {}),
|
|
66
|
+
...(opts.forceAudioFile ? { forceAudioFile: true } : {}),
|
|
67
|
+
}, auth.apiKey);
|
|
62
68
|
}
|
|
63
69
|
else {
|
|
64
70
|
const form = new ApiFormData();
|
|
65
71
|
form.set("to", opts.to);
|
|
66
|
-
|
|
72
|
+
if (opts.body)
|
|
73
|
+
form.set("body", opts.body);
|
|
67
74
|
form.set("fromNumberId", fromNumberId);
|
|
75
|
+
if (opts.forceAudioFile)
|
|
76
|
+
form.set("forceAudioFile", "true");
|
|
68
77
|
for (const item of media) {
|
|
69
78
|
if (isHttpUrl(item)) {
|
|
70
79
|
form.append("mediaUrls", item);
|
|
@@ -95,3 +104,17 @@ export async function listMessages(opts) {
|
|
|
95
104
|
throw new DialError("list_failed", res.error, res.status);
|
|
96
105
|
return res.data.messages ?? [];
|
|
97
106
|
}
|
|
107
|
+
export async function replyToMessage(opts) {
|
|
108
|
+
const auth = requireAuth();
|
|
109
|
+
// No `to`/`fromNumberId`: the server derives both from the target message —
|
|
110
|
+
// the reply stays in the conversation the target is part of.
|
|
111
|
+
const payload = {};
|
|
112
|
+
if (opts.body !== undefined)
|
|
113
|
+
payload.body = opts.body;
|
|
114
|
+
if (opts.reaction !== undefined)
|
|
115
|
+
payload.reaction = opts.reaction;
|
|
116
|
+
const res = await apiPost(`/api/v1/messages/${encodeURIComponent(opts.messageId)}/reply`, payload, auth.apiKey);
|
|
117
|
+
if (!res.ok)
|
|
118
|
+
throw new DialError("reply_failed", res.error, res.status);
|
|
119
|
+
return res.data.message;
|
|
120
|
+
}
|
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,7 @@ 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";
|
|
5
6
|
import { listMessagesTool } from "./list-messages.js";
|
|
6
7
|
import { placeCallTool } from "./place-call.js";
|
|
7
8
|
import { listCallsTool } from "./list-calls.js";
|
|
@@ -23,6 +24,7 @@ export const tools = [
|
|
|
23
24
|
purchaseNumberTool,
|
|
24
25
|
setNumberPropertiesTool,
|
|
25
26
|
sendMessageTool,
|
|
27
|
+
replyToMessageTool,
|
|
26
28
|
listMessagesTool,
|
|
27
29
|
placeCallTool,
|
|
28
30
|
listCallsTool,
|
|
@@ -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
|
+
};
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { jsonResult } from "../result.js";
|
|
3
|
-
import { sendMessage } from "../../lib/ops/messages.js";
|
|
3
|
+
import { sendMessage, MAX_MEDIA_ITEMS } from "../../lib/ops/messages.js";
|
|
4
4
|
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
|
-
body: z.string().
|
|
7
|
+
body: z.string().optional().describe("Message body; optional when mediaUrls is given (media-only send)"),
|
|
8
8
|
fromNumberId: z.string().optional().describe("Number id to send from; defaults to your primary number"),
|
|
9
|
+
mediaUrls: z
|
|
10
|
+
.array(z.string().url())
|
|
11
|
+
.max(MAX_MEDIA_ITEMS)
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Publicly reachable http(s) URLs of media to attach (MMS); Dial mirrors and re-hosts them"),
|
|
14
|
+
forceAudioFile: z
|
|
15
|
+
.boolean()
|
|
16
|
+
.optional()
|
|
17
|
+
.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."),
|
|
9
18
|
};
|
|
10
19
|
export const sendMessageTool = {
|
|
11
20
|
name: "send_message",
|
|
12
21
|
config: {
|
|
13
|
-
title: "Send
|
|
14
|
-
description: "Send
|
|
22
|
+
title: "Send message",
|
|
23
|
+
description: "Send a message from one of your Dial numbers, optionally with media attachments (MMS). On an iMessage number, a single audio attachment is delivered as a voice message unless forceAudioFile is true.",
|
|
15
24
|
inputSchema,
|
|
16
25
|
outputSchema: { message: messageSchema },
|
|
17
26
|
annotations: { openWorldHint: true },
|
|
@@ -21,6 +30,8 @@ export const sendMessageTool = {
|
|
|
21
30
|
to: args.to,
|
|
22
31
|
body: args.body,
|
|
23
32
|
fromNumberId: args.fromNumberId,
|
|
33
|
+
media: args.mediaUrls,
|
|
34
|
+
forceAudioFile: args.forceAudioFile,
|
|
24
35
|
}),
|
|
25
36
|
}),
|
|
26
37
|
};
|
package/package.json
CHANGED
package/skills.tar.gz
CHANGED
|
Binary file
|