@getdial/cli 0.43.0 → 0.44.1

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 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 { runLookup } from "./commands/lookup/lookup.js";
21
22
  import { runContactsList } from "./commands/contacts/list.js";
22
23
  import { runMessageSend } from "./commands/message/send.js";
23
24
  import { runMessageReply } from "./commands/message/reply.js";
@@ -337,6 +338,13 @@ program
337
338
  startingAfter: opts.startingAfter,
338
339
  json: !!opts.json,
339
340
  })));
341
+ program
342
+ .command("lookup")
343
+ .argument("<number>", "the phone number to look up, in E.164 (e.g. +14155550123)")
344
+ .description("What channels a phone number can receive on, so you can pick one before sending. Works on " +
345
+ "any number, not just yours. GET /api/v1/lookup.")
346
+ .option("--json", "machine-readable output")
347
+ .action(async (number, opts) => process.exit(await runLookup(number, { json: !!opts.json })));
340
348
  const group = program
341
349
  .command("group")
342
350
  .description("Group conversations your lines are in (WhatsApp).");
@@ -0,0 +1,31 @@
1
+ import { lookupNumber } from "../../lib/ops/lookup.js";
2
+ import { isDialError } from "../../lib/ops/errors.js";
3
+ import { printDialError } from "../../lib/cli-error.js";
4
+ /** Channels in the order they are printed, with the label each one shows as. */
5
+ const CHANNEL_LABELS = {
6
+ imessage: "iMessage",
7
+ };
8
+ export async function runLookup(number, opts) {
9
+ try {
10
+ const result = await lookupNumber(number);
11
+ if (opts.json) {
12
+ console.log(JSON.stringify({ ok: true, ...result }));
13
+ return 0;
14
+ }
15
+ console.log(result.number);
16
+ for (const [channel, supported] of Object.entries(result.supports)) {
17
+ // A channel added to the API after this CLI shipped still prints, under its
18
+ // raw key — reporting an unknown channel is better than hiding it.
19
+ const label = CHANNEL_LABELS[channel] ?? channel;
20
+ console.log(` ${label.padEnd(9)} ${supported ? "yes" : "no"}`);
21
+ }
22
+ return 0;
23
+ }
24
+ catch (e) {
25
+ // A lookup that could not be completed exits non-zero rather than printing
26
+ // "no": the two mean different things, and only one is worth retrying.
27
+ if (isDialError(e))
28
+ return printDialError(opts.json, e);
29
+ throw e;
30
+ }
31
+ }
@@ -0,0 +1,20 @@
1
+ import { apiGet } from "../api.js";
2
+ import { maybeAuth } from "./auth.js";
3
+ import { DialError } from "./errors.js";
4
+ /**
5
+ * Look a number up.
6
+ *
7
+ * Shared by `dial lookup` and the local MCP `lookup_number` tool, so both speak to
8
+ * the API through one place and inherit the saved key the same way.
9
+ *
10
+ * A lookup Dial could not complete comes back as an error (502), never as a
11
+ * negative verdict — so a `false` here always means the number genuinely is not
12
+ * reachable on that channel, and callers can treat the two differently.
13
+ */
14
+ export async function lookupNumber(number) {
15
+ const auth = maybeAuth();
16
+ const res = await apiGet(`/api/v1/lookup?number=${encodeURIComponent(number)}`, auth?.apiKey);
17
+ if (!res.ok)
18
+ throw new DialError("lookup_failed", res.error, res.status);
19
+ return res.data;
20
+ }
@@ -8,6 +8,7 @@ import { stopTypingTool } from "./stop-typing.js";
8
8
  import { listMessagesTool } from "./list-messages.js";
9
9
  import { listGroupsTool } from "./list-groups.js";
10
10
  import { listContactsTool } from "./list-contacts.js";
11
+ import { lookupNumberTool } from "./lookup-number.js";
11
12
  import { placeCallTool } from "./place-call.js";
12
13
  import { listCallsTool } from "./list-calls.js";
13
14
  import { getCallTool } from "./get-call.js";
@@ -35,6 +36,7 @@ export const tools = [
35
36
  listMessagesTool,
36
37
  listGroupsTool,
37
38
  listContactsTool,
39
+ lookupNumberTool,
38
40
  placeCallTool,
39
41
  listCallsTool,
40
42
  getCallTool,
@@ -8,7 +8,7 @@ export const listGroupsTool = {
8
8
  title: "List Groups",
9
9
  description: "List the group conversations your lines are in. A group is a conversation that isn't a phone " +
10
10
  "number, so it has an id of its own: pass it as groupId to send_message, or to list_messages to " +
11
- "read that conversation. Groups exist on WhatsApp lines. " +
11
+ "read that conversation. Groups exist on WhatsApp lines and iMessage numbers. " +
12
12
  "A group's name can be null — it is read live from the line holding the conversation, so a line " +
13
13
  "that can't answer in time yields a null name rather than hiding the group. " +
14
14
  "There is no join event: list again to see a group your line was just added to.",
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { lookupNumber } from "../../lib/ops/lookup.js";
4
+ const inputSchema = {
5
+ number: z
6
+ .string()
7
+ .describe("The phone number to look up, in E.164 — e.g. +14155550123. Any number, not just your own."),
8
+ };
9
+ export const lookupNumberTool = {
10
+ name: "lookup_number",
11
+ config: {
12
+ title: "Look Up A Number",
13
+ description: "Check what channels a phone number can receive on, before sending to it. Works on ANY number in the " +
14
+ "world — it doesn't have to be one of your numbers, and you don't have to have messaged it before. " +
15
+ "`supports` describes the number you asked about, not your own line: each key is a channel and the " +
16
+ "boolean says whether that number can be reached there. Read the channels you need by name, since more " +
17
+ "are added over time. The answer is point-in-time, not a property of the number — someone who changes " +
18
+ "device or turns the service off stops being reachable — so treat `true` as a strong signal for picking " +
19
+ "a channel rather than a promise that the send will land. A lookup that fails is an error, never a " +
20
+ "`false`, so a `false` always means the number genuinely isn't reachable there.",
21
+ inputSchema,
22
+ outputSchema: {
23
+ number: z.string().describe("The number you asked about, normalized to E.164"),
24
+ supports: z
25
+ .object({
26
+ imessage: z.boolean().describe("Whether the number can currently receive iMessage"),
27
+ })
28
+ .describe("One key per channel, true when the number can be reached there"),
29
+ },
30
+ annotations: { readOnlyHint: true, openWorldHint: true },
31
+ },
32
+ run: async (args) => jsonResult(await lookupNumber(args.number)),
33
+ };
@@ -12,7 +12,7 @@ const inputSchema = {
12
12
  .string()
13
13
  .min(1)
14
14
  .optional()
15
- .describe("A group conversation to send into (see list_groups), instead of a to number. The sending line comes from the group, so no from-number is needed. Provide exactly one of to or groupId"),
15
+ .describe("A group conversation to send into (see list_groups), instead of a to number. The sending line comes from the group, so no from-number is needed, and neither is channel — the group already knows which one it is on, and naming a different one is refused. Provide exactly one of to or groupId"),
16
16
  channel: z
17
17
  .enum(["imessage", "whatsapp"])
18
18
  .optional()
@@ -48,7 +48,8 @@ export const sendMessageTool = {
48
48
  "optionally with media attachments (MMS). On an iMessage number, a single audio attachment is " +
49
49
  "delivered as a voice message unless forceAudioFile is true. " +
50
50
  "Address it with exactly one of to or groupId. A WhatsApp number sends text and a single " +
51
- "attachment (a caption is allowed only with an image or video).",
51
+ "attachment (a caption is allowed only with an image or video); a WhatsApp group is text-only, " +
52
+ "while an iMessage group takes media like any other iMessage conversation.",
52
53
  inputSchema,
53
54
  outputSchema: { message: messageSchema },
54
55
  annotations: { openWorldHint: true },
@@ -31,6 +31,7 @@ export const OPERATIONAL_TOOL_NAMES = [
31
31
  "list_messages",
32
32
  "list_groups",
33
33
  "list_contacts",
34
+ "lookup_number",
34
35
  "place_call",
35
36
  "list_calls",
36
37
  "get_call",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getdial/cli",
3
- "version": "0.43.0",
3
+ "version": "0.44.1",
4
4
  "description": "Dial CLI — install, sign up, and run the local listen service.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/skills.tar.gz CHANGED
Binary file