@getdial/cli 0.40.1 → 0.42.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 +23 -0
- package/dist/commands/call/list.js +1 -0
- package/dist/commands/contacts/list.js +59 -0
- package/dist/commands/message/list.js +2 -0
- package/dist/commands/number/set.js +2 -0
- package/dist/lib/ops/calls.js +2 -0
- package/dist/lib/ops/contacts.js +42 -0
- package/dist/lib/ops/messages.js +4 -0
- package/dist/lib/ops/numbers.js +5 -1
- package/dist/mcp/schemas.js +34 -0
- package/dist/mcp/tools/index.js +2 -0
- package/dist/mcp/tools/list-calls.js +6 -1
- package/dist/mcp/tools/list-contacts.js +35 -0
- package/dist/mcp/tools/list-messages.js +16 -3
- package/dist/mcp/tools/set-number-properties.js +12 -1
- package/dist/mcp/tools/tool-names.js +1 -0
- package/package.json +1 -1
- package/skills.tar.gz +0 -0
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ import { runNumberPurchase } from "./commands/number/purchase.js";
|
|
|
18
18
|
import { runNumberSet } from "./commands/number/set.js";
|
|
19
19
|
import { runNumberWhatsapp } from "./commands/number/whatsapp.js";
|
|
20
20
|
import { runGroupList } from "./commands/group/list.js";
|
|
21
|
+
import { runContactsList } from "./commands/contacts/list.js";
|
|
21
22
|
import { runMessageSend } from "./commands/message/send.js";
|
|
22
23
|
import { runMessageReply } from "./commands/message/reply.js";
|
|
23
24
|
import { runMessageList } from "./commands/message/list.js";
|
|
@@ -216,6 +217,8 @@ number
|
|
|
216
217
|
return n;
|
|
217
218
|
})
|
|
218
219
|
.option("--clear-max-call-duration", "remove the per-number call duration cap")
|
|
220
|
+
.option("--channel <imessage|whatsapp|both>", 'which display profile(s) --name and --avatar are written to; "both" sets one identity on every channel the number has, in a single call. Cannot be combined with --first-name/--last-name/--whatsapp-name/--whatsapp-avatar')
|
|
221
|
+
.option("--name <text>", 'display name for --channel (required alongside it); WhatsApp stores it verbatim (1-25 chars), iMessage splits it on the first space ("Maya Chen" -> Maya / Chen)')
|
|
219
222
|
.option("--first-name <text>", 'iMessage display first name shown beside this number\'s messages (iMessage numbers only); pass "" to clear')
|
|
220
223
|
.option("--last-name <text>", 'iMessage display last name (iMessage numbers only); pass "" to clear')
|
|
221
224
|
.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")
|
|
@@ -238,6 +241,8 @@ number
|
|
|
238
241
|
inboundLanguage: opts.inboundLanguage,
|
|
239
242
|
nickname: opts.nickname,
|
|
240
243
|
maxCallDurationSeconds,
|
|
244
|
+
channel: opts.channel,
|
|
245
|
+
name: opts.name,
|
|
241
246
|
firstName: opts.firstName,
|
|
242
247
|
lastName: opts.lastName,
|
|
243
248
|
avatar: opts.avatar,
|
|
@@ -305,15 +310,31 @@ message
|
|
|
305
310
|
.command("list")
|
|
306
311
|
.description("List recent messages on your account. GET /api/v1/messages.")
|
|
307
312
|
.option("--number-id <id>", "filter to a single phone number")
|
|
313
|
+
.option("--contact <e164>", "one contact's conversation, both directions, across every line")
|
|
308
314
|
.option("--group <id>", "filter to one group conversation (see `dial group list`)")
|
|
309
315
|
.option("--direction <dir>", "inbound or outbound")
|
|
310
316
|
.option("--since <iso8601>", "only messages created after this timestamp")
|
|
317
|
+
.option("--search <text>", "match message bodies containing this text (case-insensitive)")
|
|
311
318
|
.option("--json", "machine-readable output")
|
|
312
319
|
.action(async (opts) => process.exit(await runMessageList({
|
|
313
320
|
numberId: opts.numberId,
|
|
321
|
+
contact: opts.contact,
|
|
314
322
|
group: opts.group,
|
|
315
323
|
direction: opts.direction,
|
|
316
324
|
since: opts.since,
|
|
325
|
+
search: opts.search,
|
|
326
|
+
json: !!opts.json,
|
|
327
|
+
})));
|
|
328
|
+
program
|
|
329
|
+
.command("contacts")
|
|
330
|
+
.description("Every number your lines have texted or called, newest activity first. Walks every page " +
|
|
331
|
+
"unless you pass --limit. GET /api/v1/contacts.")
|
|
332
|
+
.option("--limit <n>", "return one page of at most n contacts (1-1000) instead of all", (v) => Number.parseInt(v, 10))
|
|
333
|
+
.option("--starting-after <iso8601>", "page cursor: the lastAt of the last contact you received")
|
|
334
|
+
.option("--json", "machine-readable output")
|
|
335
|
+
.action(async (opts) => process.exit(await runContactsList({
|
|
336
|
+
limit: opts.limit,
|
|
337
|
+
startingAfter: opts.startingAfter,
|
|
317
338
|
json: !!opts.json,
|
|
318
339
|
})));
|
|
319
340
|
const group = program
|
|
@@ -407,11 +428,13 @@ call
|
|
|
407
428
|
.command("list")
|
|
408
429
|
.description("List recent calls on your account. GET /api/v1/calls.")
|
|
409
430
|
.option("--number-id <id>", "filter to a single phone number")
|
|
431
|
+
.option("--contact <e164>", "one contact's calls, both directions, across every line")
|
|
410
432
|
.option("--direction <dir>", "inbound or outbound")
|
|
411
433
|
.option("--since <iso8601>", "only calls created after this timestamp")
|
|
412
434
|
.option("--json", "machine-readable output")
|
|
413
435
|
.action(async (opts) => process.exit(await runCallList({
|
|
414
436
|
numberId: opts.numberId,
|
|
437
|
+
contact: opts.contact,
|
|
415
438
|
direction: opts.direction,
|
|
416
439
|
since: opts.since,
|
|
417
440
|
json: !!opts.json,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { listContacts, listAllContacts } from "../../lib/ops/contacts.js";
|
|
2
|
+
import { isDialError } from "../../lib/ops/errors.js";
|
|
3
|
+
import { printDialError } from "../../lib/cli-error.js";
|
|
4
|
+
/**
|
|
5
|
+
* The preview line for one contact.
|
|
6
|
+
*
|
|
7
|
+
* The API reports facts, not a sentence, so the wording is chosen here — which also means the
|
|
8
|
+
* terminal and the dashboard can each say it their own way without the server picking for them.
|
|
9
|
+
*/
|
|
10
|
+
function preview(c) {
|
|
11
|
+
if (c.lastKind === "call") {
|
|
12
|
+
const direction = c.lastDirection === "inbound" ? "incoming" : "outgoing";
|
|
13
|
+
// 0 seconds means it never connected, so a duration is only worth printing above it.
|
|
14
|
+
return c.lastCallDuration ? `${direction} call, ${c.lastCallDuration}s` : `${direction} call`;
|
|
15
|
+
}
|
|
16
|
+
if (c.lastRedacted)
|
|
17
|
+
return "(deleted by retention)";
|
|
18
|
+
if (!c.lastBody)
|
|
19
|
+
return c.lastMediaCount > 0 ? `(${c.lastMediaCount} media)` : "";
|
|
20
|
+
const prefix = c.lastDirection === "inbound" ? "" : "you: ";
|
|
21
|
+
return `${prefix}${c.lastBody}`;
|
|
22
|
+
}
|
|
23
|
+
export async function runContactsList(opts) {
|
|
24
|
+
try {
|
|
25
|
+
// `--limit` asks for one page and gets exactly that, cursor included. Without it the command
|
|
26
|
+
// answers the question it is named for — who have I talked to — by walking every page.
|
|
27
|
+
const paged = opts.limit !== undefined || opts.startingAfter !== undefined;
|
|
28
|
+
const page = paged
|
|
29
|
+
? await listContacts({ limit: opts.limit, startingAfter: opts.startingAfter })
|
|
30
|
+
: { contacts: await listAllContacts(), hasMore: false };
|
|
31
|
+
if (opts.json) {
|
|
32
|
+
console.log(JSON.stringify({ ok: true, contacts: page.contacts, hasMore: page.hasMore }));
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
if (page.contacts.length === 0) {
|
|
36
|
+
console.log("no contacts. a number appears here once one of your lines texts or calls it.");
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
for (const c of page.contacts) {
|
|
40
|
+
const counts = [
|
|
41
|
+
c.messageCount > 0 ? `${c.messageCount} msg` : null,
|
|
42
|
+
c.callCount > 0 ? `${c.callCount} call` : null,
|
|
43
|
+
]
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.join(", ");
|
|
46
|
+
console.log(`${c.number.padEnd(16)} ${c.lastAt} ${counts.padEnd(18)} ${preview(c)}`);
|
|
47
|
+
}
|
|
48
|
+
if (page.hasMore) {
|
|
49
|
+
const cursor = page.contacts[page.contacts.length - 1].lastAt;
|
|
50
|
+
console.log(`\nmore contacts. next page: dial contacts --starting-after ${cursor}`);
|
|
51
|
+
}
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
if (isDialError(e))
|
|
56
|
+
return printDialError(opts.json, e);
|
|
57
|
+
throw e;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -6,8 +6,10 @@ export async function runMessageList(opts) {
|
|
|
6
6
|
const messages = await listMessages({
|
|
7
7
|
numberId: opts.numberId,
|
|
8
8
|
groupId: opts.group,
|
|
9
|
+
contact: opts.contact,
|
|
9
10
|
direction: opts.direction,
|
|
10
11
|
since: opts.since,
|
|
12
|
+
search: opts.search,
|
|
11
13
|
});
|
|
12
14
|
if (opts.json) {
|
|
13
15
|
console.log(JSON.stringify({ ok: true, messages }));
|
|
@@ -14,6 +14,8 @@ export async function runNumberSet(opts) {
|
|
|
14
14
|
...(opts.maxCallDurationSeconds !== undefined
|
|
15
15
|
? { maxCallDurationSeconds: opts.maxCallDurationSeconds }
|
|
16
16
|
: {}),
|
|
17
|
+
...(opts.channel !== undefined ? { channel: opts.channel } : {}),
|
|
18
|
+
...(opts.name !== undefined ? { name: opts.name } : {}),
|
|
17
19
|
...(opts.firstName !== undefined ? { firstName: opts.firstName } : {}),
|
|
18
20
|
...(opts.lastName !== undefined ? { lastName: opts.lastName } : {}),
|
|
19
21
|
...(opts.avatar !== undefined ? { avatar: opts.avatar } : {}),
|
package/dist/lib/ops/calls.js
CHANGED
|
@@ -29,6 +29,8 @@ export async function listCalls(opts) {
|
|
|
29
29
|
params.set("direction", opts.direction);
|
|
30
30
|
if (opts.since)
|
|
31
31
|
params.set("since", opts.since);
|
|
32
|
+
if (opts.contact)
|
|
33
|
+
params.set("contact", opts.contact);
|
|
32
34
|
const qs = params.toString();
|
|
33
35
|
const res = await apiGet(qs ? `/api/v1/calls?${qs}` : "/api/v1/calls", auth?.apiKey);
|
|
34
36
|
if (!res.ok)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { apiGet } from "../api.js";
|
|
2
|
+
import { maybeAuth } from "./auth.js";
|
|
3
|
+
import { DialError } from "./errors.js";
|
|
4
|
+
/**
|
|
5
|
+
* One page of contacts — every number the account's lines have messaged or called.
|
|
6
|
+
*
|
|
7
|
+
* Shared by `dial contacts` and the local MCP `list_contacts` tool, so both speak to the API
|
|
8
|
+
* through one place and inherit the saved key the same way.
|
|
9
|
+
*/
|
|
10
|
+
export async function listContacts(opts = {}) {
|
|
11
|
+
const auth = maybeAuth();
|
|
12
|
+
const params = new URLSearchParams();
|
|
13
|
+
if (opts.limit !== undefined)
|
|
14
|
+
params.set("limit", String(opts.limit));
|
|
15
|
+
if (opts.startingAfter)
|
|
16
|
+
params.set("starting_after", opts.startingAfter);
|
|
17
|
+
const qs = params.toString();
|
|
18
|
+
const res = await apiGet(qs ? `/api/v1/contacts?${qs}` : "/api/v1/contacts", auth?.apiKey);
|
|
19
|
+
if (!res.ok)
|
|
20
|
+
throw new DialError("list_failed", res.error, res.status);
|
|
21
|
+
return { contacts: res.data.contacts ?? [], hasMore: res.data.hasMore ?? false };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Every contact, following the cursor until the API says there are no more.
|
|
25
|
+
*
|
|
26
|
+
* `dial contacts` answers "who have I talked to", and a first page is not an answer to that — so
|
|
27
|
+
* the default walks the pages. `--limit` opts back into a single page for a caller that wants one.
|
|
28
|
+
*/
|
|
29
|
+
export async function listAllContacts(pageSize = 1000) {
|
|
30
|
+
const all = [];
|
|
31
|
+
let startingAfter;
|
|
32
|
+
// Bounded rather than `while (hasMore)`: a bug at either end that always answered `hasMore:
|
|
33
|
+
// true` would otherwise loop forever against the network. 25 pages is 25,000 contacts.
|
|
34
|
+
for (let page = 0; page < 25; page++) {
|
|
35
|
+
const { contacts, hasMore } = await listContacts({ limit: pageSize, startingAfter });
|
|
36
|
+
all.push(...contacts);
|
|
37
|
+
if (!hasMore || contacts.length === 0)
|
|
38
|
+
break;
|
|
39
|
+
startingAfter = contacts[contacts.length - 1].lastAt;
|
|
40
|
+
}
|
|
41
|
+
return all;
|
|
42
|
+
}
|
package/dist/lib/ops/messages.js
CHANGED
|
@@ -117,6 +117,10 @@ export async function listMessages(opts) {
|
|
|
117
117
|
params.set("direction", opts.direction);
|
|
118
118
|
if (opts.since)
|
|
119
119
|
params.set("since", opts.since);
|
|
120
|
+
if (opts.search)
|
|
121
|
+
params.set("search", opts.search);
|
|
122
|
+
if (opts.contact)
|
|
123
|
+
params.set("contact", opts.contact);
|
|
120
124
|
const qs = params.toString();
|
|
121
125
|
const res = await apiGet(qs ? `/api/v1/messages?${qs}` : "/api/v1/messages", auth?.apiKey);
|
|
122
126
|
if (!res.ok)
|
package/dist/lib/ops/numbers.js
CHANGED
|
@@ -117,6 +117,10 @@ export async function setNumberProperties(opts) {
|
|
|
117
117
|
body.nickname = opts.nickname;
|
|
118
118
|
if (opts.maxCallDurationSeconds !== undefined)
|
|
119
119
|
body.maxCallDurationSeconds = opts.maxCallDurationSeconds;
|
|
120
|
+
if (opts.channel !== undefined)
|
|
121
|
+
body.channel = opts.channel;
|
|
122
|
+
if (opts.name !== undefined)
|
|
123
|
+
body.name = opts.name;
|
|
120
124
|
if (opts.firstName !== undefined)
|
|
121
125
|
body.firstName = opts.firstName;
|
|
122
126
|
if (opts.lastName !== undefined)
|
|
@@ -137,7 +141,7 @@ export async function setNumberProperties(opts) {
|
|
|
137
141
|
if (opts.whatsappAvatar !== undefined && !whatsappAvatarFile)
|
|
138
142
|
body.whatsappAvatarUrl = opts.whatsappAvatar;
|
|
139
143
|
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).");
|
|
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).");
|
|
141
145
|
}
|
|
142
146
|
const auth = maybeAuth();
|
|
143
147
|
// The REST API keys numbers by id; the CLI/tool takes the E.164 number for ergonomics,
|
package/dist/mcp/schemas.js
CHANGED
|
@@ -85,6 +85,40 @@ export const groupSchema = z.object({
|
|
|
85
85
|
.optional()
|
|
86
86
|
.describe("ISO-8601: when Dial first learned of this group (a join, or its first message)"),
|
|
87
87
|
});
|
|
88
|
+
/**
|
|
89
|
+
* One derived contact. Mirrors the hosted server's `contactSchema`.
|
|
90
|
+
*
|
|
91
|
+
* The preview fields report facts rather than a sentence — `lastKind` selects whether `lastBody`
|
|
92
|
+
* or `lastCallDuration` means anything, and `lastRedacted` / `lastMediaCount` explain an empty
|
|
93
|
+
* `lastBody` — so an agent can word the summary itself.
|
|
94
|
+
*/
|
|
95
|
+
export const contactSchema = z.object({
|
|
96
|
+
number: z.string().describe("The contact's number, E.164 — pass as `contact` to list_messages"),
|
|
97
|
+
messageCount: z
|
|
98
|
+
.number()
|
|
99
|
+
.describe("One-to-one messages with this contact across every line on the account, both directions"),
|
|
100
|
+
callCount: z
|
|
101
|
+
.number()
|
|
102
|
+
.describe("Calls with this contact across every line on the account, both directions"),
|
|
103
|
+
lastAt: z.string().describe("ISO-8601 timestamp of the most recent message or call"),
|
|
104
|
+
lastDirection: z
|
|
105
|
+
.enum(["inbound", "outbound"])
|
|
106
|
+
.describe("Whether the most recent interaction came from them or you"),
|
|
107
|
+
lastKind: z.enum(["message", "call"]).describe("What the most recent interaction was"),
|
|
108
|
+
lastBody: z
|
|
109
|
+
.string()
|
|
110
|
+
.describe("The most recent message's text. Empty for a call, a media-only message, or a redacted one"),
|
|
111
|
+
lastMediaCount: z
|
|
112
|
+
.number()
|
|
113
|
+
.describe("Attachments on the most recent message; 0 for a call or a text-only message"),
|
|
114
|
+
lastRedacted: z
|
|
115
|
+
.boolean()
|
|
116
|
+
.describe("True when data retention cleared the most recent message's content"),
|
|
117
|
+
lastCallDuration: z
|
|
118
|
+
.number()
|
|
119
|
+
.nullable()
|
|
120
|
+
.describe("The most recent call's duration in seconds, or null when the most recent interaction was a message"),
|
|
121
|
+
});
|
|
88
122
|
export const messageSchema = z
|
|
89
123
|
.object({
|
|
90
124
|
id: z.string(),
|
package/dist/mcp/tools/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { startTypingTool } from "./start-typing.js";
|
|
|
7
7
|
import { stopTypingTool } from "./stop-typing.js";
|
|
8
8
|
import { listMessagesTool } from "./list-messages.js";
|
|
9
9
|
import { listGroupsTool } from "./list-groups.js";
|
|
10
|
+
import { listContactsTool } from "./list-contacts.js";
|
|
10
11
|
import { placeCallTool } from "./place-call.js";
|
|
11
12
|
import { listCallsTool } from "./list-calls.js";
|
|
12
13
|
import { getCallTool } from "./get-call.js";
|
|
@@ -33,6 +34,7 @@ export const tools = [
|
|
|
33
34
|
stopTypingTool,
|
|
34
35
|
listMessagesTool,
|
|
35
36
|
listGroupsTool,
|
|
37
|
+
listContactsTool,
|
|
36
38
|
placeCallTool,
|
|
37
39
|
listCallsTool,
|
|
38
40
|
getCallTool,
|
|
@@ -3,6 +3,10 @@ import { jsonResult } from "../result.js";
|
|
|
3
3
|
import { listCalls } from "../../lib/ops/calls.js";
|
|
4
4
|
import { callSchema } from "../schemas.js";
|
|
5
5
|
const inputSchema = {
|
|
6
|
+
contact: z
|
|
7
|
+
.string()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("One conversation: calls exchanged with this number (E.164), both directions, across every line"),
|
|
6
10
|
numberId: z.string().optional().describe("Filter to a single phone number id"),
|
|
7
11
|
direction: z.enum(["inbound", "outbound"]).optional().describe("Filter by direction"),
|
|
8
12
|
since: z.string().optional().describe("Only calls created after this ISO-8601 timestamp"),
|
|
@@ -11,7 +15,7 @@ export const listCallsTool = {
|
|
|
11
15
|
name: "list_calls",
|
|
12
16
|
config: {
|
|
13
17
|
title: "List Calls",
|
|
14
|
-
description: "List recent calls on your account, newest first.",
|
|
18
|
+
description: "List recent calls on your account, newest first. Pass `contact` to read one person's call history.",
|
|
15
19
|
inputSchema,
|
|
16
20
|
outputSchema: { calls: z.array(callSchema) },
|
|
17
21
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
@@ -21,6 +25,7 @@ export const listCallsTool = {
|
|
|
21
25
|
numberId: args.numberId,
|
|
22
26
|
direction: args.direction,
|
|
23
27
|
since: args.since,
|
|
28
|
+
contact: args.contact,
|
|
24
29
|
}),
|
|
25
30
|
}),
|
|
26
31
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { jsonResult } from "../result.js";
|
|
3
|
+
import { listContacts } from "../../lib/ops/contacts.js";
|
|
4
|
+
import { contactSchema } from "../schemas.js";
|
|
5
|
+
const inputSchema = {
|
|
6
|
+
limit: z
|
|
7
|
+
.number()
|
|
8
|
+
.int()
|
|
9
|
+
.min(1)
|
|
10
|
+
.max(1000)
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Max contacts to return (1-1000; default 100)"),
|
|
13
|
+
startingAfter: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("ISO-8601 cursor — the lastAt of the last contact you received; returns only older ones"),
|
|
17
|
+
};
|
|
18
|
+
export const listContactsTool = {
|
|
19
|
+
name: "list_contacts",
|
|
20
|
+
config: {
|
|
21
|
+
title: "List Contacts",
|
|
22
|
+
description: "List every number your lines have exchanged a message or a call with, newest activity first, with " +
|
|
23
|
+
"per-contact counts and a summary of the most recent interaction. Derived from history — there is no " +
|
|
24
|
+
"address book to add anyone to. Counts span every line on the account, so one person reached on two of " +
|
|
25
|
+
"your numbers is one contact. Group conversations are not contacts; use list_groups for those. Pass a " +
|
|
26
|
+
"contact's `number` to list_messages as `contact` to read that one conversation.",
|
|
27
|
+
inputSchema,
|
|
28
|
+
outputSchema: { contacts: z.array(contactSchema), hasMore: z.boolean() },
|
|
29
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
30
|
+
},
|
|
31
|
+
run: async (args) => jsonResult(await listContacts({
|
|
32
|
+
limit: args.limit,
|
|
33
|
+
startingAfter: args.startingAfter,
|
|
34
|
+
})),
|
|
35
|
+
};
|
|
@@ -3,6 +3,15 @@ import { jsonResult } from "../result.js";
|
|
|
3
3
|
import { listMessages } from "../../lib/ops/messages.js";
|
|
4
4
|
import { messageSchema } from "../schemas.js";
|
|
5
5
|
const inputSchema = {
|
|
6
|
+
search: z
|
|
7
|
+
.string()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("Case-insensitive substring match on the message body; searches your whole history"),
|
|
10
|
+
contact: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("One conversation: messages exchanged with this number (E.164), both directions, across " +
|
|
14
|
+
"every line. Group messages are never included — use groupId for those"),
|
|
6
15
|
numberId: z.string().optional().describe("Filter to a single phone number id"),
|
|
7
16
|
groupId: z
|
|
8
17
|
.string()
|
|
@@ -15,9 +24,11 @@ export const listMessagesTool = {
|
|
|
15
24
|
name: "list_messages",
|
|
16
25
|
config: {
|
|
17
26
|
title: "List Messages",
|
|
18
|
-
description: "List recent messages on your account, newest first. Pass
|
|
19
|
-
"conversation
|
|
20
|
-
"
|
|
27
|
+
description: "List recent messages on your account, newest first. Pass `contact` to read one person's " +
|
|
28
|
+
"whole conversation, or `groupId` to read one group's. Pass `search` to find messages by " +
|
|
29
|
+
"their text — that searches all of your history, then returns the 100 most recent matches. " +
|
|
30
|
+
"On a group message `to` is null and the destination is groupId; which of your numbers the " +
|
|
31
|
+
"conversation is on is phoneNumberId.",
|
|
21
32
|
inputSchema,
|
|
22
33
|
outputSchema: { messages: z.array(messageSchema) },
|
|
23
34
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
@@ -28,6 +39,8 @@ export const listMessagesTool = {
|
|
|
28
39
|
groupId: args.groupId,
|
|
29
40
|
direction: args.direction,
|
|
30
41
|
since: args.since,
|
|
42
|
+
search: args.search,
|
|
43
|
+
contact: args.contact,
|
|
31
44
|
}),
|
|
32
45
|
}),
|
|
33
46
|
};
|
|
@@ -29,6 +29,15 @@ const inputSchema = {
|
|
|
29
29
|
.nullable()
|
|
30
30
|
.optional()
|
|
31
31
|
.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). On a free account (no top-up or subscription yet) a value above 300 is rejected with 400. Pass null to clear the cap; omit to leave it unchanged."),
|
|
32
|
+
channel: z
|
|
33
|
+
.enum(["imessage", "whatsapp", "both"])
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('Which display profile(s) `name` and `avatarUrl` are written to: "imessage", "whatsapp", or "both". Use this when the number should present the same identity everywhere — one call instead of two, validated against every targeted channel before any of them is written, so the profiles cannot drift apart. Cannot be combined with firstName/lastName/whatsappName/whatsappAvatarUrl.'),
|
|
36
|
+
name: z
|
|
37
|
+
.string()
|
|
38
|
+
.min(1)
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('The display name to set on the channel(s) `channel` names; requires `channel`. WhatsApp stores it verbatim (1-25 chars); iMessage has no single-name field, so it is split on the first space — "Ana Lee" becomes firstName "Ana", lastName "Lee".'),
|
|
32
41
|
firstName: z
|
|
33
42
|
.string()
|
|
34
43
|
.max(30)
|
|
@@ -62,7 +71,7 @@ export const setNumberPropertiesTool = {
|
|
|
62
71
|
name: "set_number_properties",
|
|
63
72
|
config: {
|
|
64
73
|
title: "Set Number Properties",
|
|
65
|
-
description:
|
|
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.',
|
|
66
75
|
inputSchema,
|
|
67
76
|
outputSchema: { number: phoneNumberSchema },
|
|
68
77
|
annotations: { openWorldHint: true },
|
|
@@ -81,6 +90,8 @@ export const setNumberPropertiesTool = {
|
|
|
81
90
|
...(args.maxCallDurationSeconds !== undefined
|
|
82
91
|
? { maxCallDurationSeconds: args.maxCallDurationSeconds }
|
|
83
92
|
: {}),
|
|
93
|
+
...(args.channel !== undefined ? { channel: args.channel } : {}),
|
|
94
|
+
...(args.name !== undefined ? { name: args.name } : {}),
|
|
84
95
|
...(args.firstName !== undefined ? { firstName: args.firstName } : {}),
|
|
85
96
|
...(args.whatsappName !== undefined ? { whatsappName: args.whatsappName } : {}),
|
|
86
97
|
...(args.callingEnabled !== undefined
|
package/package.json
CHANGED
package/skills.tar.gz
CHANGED
|
Binary file
|