@getdial/cli 0.41.0 → 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 +19 -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/lib/ops/calls.js +2 -0
- package/dist/lib/ops/contacts.js +42 -0
- package/dist/lib/ops/messages.js +4 -0
- 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/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";
|
|
@@ -309,15 +310,31 @@ message
|
|
|
309
310
|
.command("list")
|
|
310
311
|
.description("List recent messages on your account. GET /api/v1/messages.")
|
|
311
312
|
.option("--number-id <id>", "filter to a single phone number")
|
|
313
|
+
.option("--contact <e164>", "one contact's conversation, both directions, across every line")
|
|
312
314
|
.option("--group <id>", "filter to one group conversation (see `dial group list`)")
|
|
313
315
|
.option("--direction <dir>", "inbound or outbound")
|
|
314
316
|
.option("--since <iso8601>", "only messages created after this timestamp")
|
|
317
|
+
.option("--search <text>", "match message bodies containing this text (case-insensitive)")
|
|
315
318
|
.option("--json", "machine-readable output")
|
|
316
319
|
.action(async (opts) => process.exit(await runMessageList({
|
|
317
320
|
numberId: opts.numberId,
|
|
321
|
+
contact: opts.contact,
|
|
318
322
|
group: opts.group,
|
|
319
323
|
direction: opts.direction,
|
|
320
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,
|
|
321
338
|
json: !!opts.json,
|
|
322
339
|
})));
|
|
323
340
|
const group = program
|
|
@@ -411,11 +428,13 @@ call
|
|
|
411
428
|
.command("list")
|
|
412
429
|
.description("List recent calls on your account. GET /api/v1/calls.")
|
|
413
430
|
.option("--number-id <id>", "filter to a single phone number")
|
|
431
|
+
.option("--contact <e164>", "one contact's calls, both directions, across every line")
|
|
414
432
|
.option("--direction <dir>", "inbound or outbound")
|
|
415
433
|
.option("--since <iso8601>", "only calls created after this timestamp")
|
|
416
434
|
.option("--json", "machine-readable output")
|
|
417
435
|
.action(async (opts) => process.exit(await runCallList({
|
|
418
436
|
numberId: opts.numberId,
|
|
437
|
+
contact: opts.contact,
|
|
419
438
|
direction: opts.direction,
|
|
420
439
|
since: opts.since,
|
|
421
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 }));
|
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/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
|
};
|
package/package.json
CHANGED
package/skills.tar.gz
CHANGED
|
Binary file
|