@getdial/cli 0.13.2 → 0.15.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.
Files changed (52) hide show
  1. package/README.md +15 -1
  2. package/dist/cli.js +5 -0
  3. package/dist/commands/call/get.js +28 -36
  4. package/dist/commands/call/list.js +20 -37
  5. package/dist/commands/call/send.js +52 -65
  6. package/dist/commands/doctor.js +2 -68
  7. package/dist/commands/mcp.js +17 -0
  8. package/dist/commands/message/list.js +20 -37
  9. package/dist/commands/message/send.js +22 -35
  10. package/dist/commands/number/list.js +21 -29
  11. package/dist/commands/number/purchase.js +23 -32
  12. package/dist/commands/number/set.js +19 -40
  13. package/dist/commands/onboard.js +36 -67
  14. package/dist/commands/signup.js +23 -24
  15. package/dist/commands/wait-for.js +33 -67
  16. package/dist/lib/cli-error.js +24 -0
  17. package/dist/lib/ops/account.js +136 -0
  18. package/dist/lib/ops/auth.js +18 -0
  19. package/dist/lib/ops/calls.js +33 -0
  20. package/dist/lib/ops/errors.js +23 -0
  21. package/dist/lib/ops/events.js +63 -0
  22. package/dist/lib/ops/listen.js +51 -0
  23. package/dist/lib/ops/local-targets.js +35 -0
  24. package/dist/lib/ops/messages.js +26 -0
  25. package/dist/lib/ops/numbers.js +39 -0
  26. package/dist/mcp/register.js +27 -0
  27. package/dist/mcp/result.js +16 -0
  28. package/dist/mcp/schemas.js +50 -0
  29. package/dist/mcp/server.js +20 -0
  30. package/dist/mcp/tool.js +1 -0
  31. package/dist/mcp/tools/add-command-target.js +27 -0
  32. package/dist/mcp/tools/add-url-target.js +30 -0
  33. package/dist/mcp/tools/get-account-status.js +22 -0
  34. package/dist/mcp/tools/get-call.js +18 -0
  35. package/dist/mcp/tools/index.js +41 -0
  36. package/dist/mcp/tools/list-calls.js +26 -0
  37. package/dist/mcp/tools/list-local-targets.js +15 -0
  38. package/dist/mcp/tools/list-messages.js +26 -0
  39. package/dist/mcp/tools/list-numbers.js +18 -0
  40. package/dist/mcp/tools/listen-install.js +19 -0
  41. package/dist/mcp/tools/listen-status.js +21 -0
  42. package/dist/mcp/tools/listen-uninstall.js +14 -0
  43. package/dist/mcp/tools/onboard.js +44 -0
  44. package/dist/mcp/tools/place-call.js +35 -0
  45. package/dist/mcp/tools/purchase-number.js +26 -0
  46. package/dist/mcp/tools/remove-local-target.js +20 -0
  47. package/dist/mcp/tools/send-message.js +26 -0
  48. package/dist/mcp/tools/set-number-properties.js +24 -0
  49. package/dist/mcp/tools/sign-up.js +22 -0
  50. package/dist/mcp/tools/wait-for-event.js +32 -0
  51. package/package.json +2 -1
  52. package/skills.tar.gz +0 -0
@@ -0,0 +1,63 @@
1
+ import { paths } from "../paths.js";
2
+ import { supervisorStatus } from "../supervisor/index.js";
3
+ import { parseFieldArg, parseRegexArg } from "../event-filter.js";
4
+ import { currentSize, findLatestMatch, tailUntilMatch } from "../log-tail.js";
5
+ import { apiPost } from "../api.js";
6
+ import { requireAuth } from "./auth.js";
7
+ import { DialError } from "./errors.js";
8
+ const PER_POLL_SECONDS = 30;
9
+ /**
10
+ * Wait for a matching account event. Tails the local listen log when the daemon is
11
+ * running, otherwise long-polls the REST API. Console-free: returns what to print and
12
+ * whether it timed out; throws DialError for auth/API failures.
13
+ */
14
+ export async function waitForEvent(opts) {
15
+ const spec = {
16
+ eventType: opts.eventType,
17
+ fields: opts.fields.map(parseFieldArg),
18
+ regexes: opts.regexes.map(parseRegexArg),
19
+ };
20
+ const status = supervisorStatus();
21
+ if (status.installed && status.running)
22
+ return waitFromLog(spec, opts);
23
+ return waitFromApi(spec, opts);
24
+ }
25
+ async function waitFromLog(spec, opts) {
26
+ const file = paths().listenLog;
27
+ const startOffset = currentSize(file);
28
+ const hit = await tailUntilMatch(file, spec, startOffset, opts.timeoutSeconds * 1000);
29
+ if (hit)
30
+ return { source: "log", timedOut: false, event: hit.obj, line: hit.line };
31
+ const fallback = findLatestMatch(file, spec);
32
+ if (fallback)
33
+ return { source: "log", timedOut: true, event: fallback.obj, line: fallback.line };
34
+ return { source: null, timedOut: true, event: null, line: null };
35
+ }
36
+ async function waitFromApi(spec, opts) {
37
+ const auth = requireAuth();
38
+ const filters = {};
39
+ for (const f of spec.fields)
40
+ filters[f.name] = f.value;
41
+ const regexFilters = {};
42
+ for (const r of spec.regexes)
43
+ regexFilters[r.name] = { pattern: r.regex.source, flags: r.regex.flags };
44
+ const deadline = Date.now() + opts.timeoutSeconds * 1000;
45
+ while (Date.now() < deadline) {
46
+ const remainingSec = Math.max(1, Math.ceil((deadline - Date.now()) / 1000));
47
+ const timeout = Math.min(PER_POLL_SECONDS, remainingSec);
48
+ const res = await apiPost("/api/v1/events/wait", {
49
+ eventType: spec.eventType,
50
+ filters: Object.keys(filters).length > 0 ? filters : undefined,
51
+ regexFilters: Object.keys(regexFilters).length > 0 ? regexFilters : undefined,
52
+ timeout,
53
+ }, auth.apiKey);
54
+ if (res.ok && res.data?.event) {
55
+ return { source: "api", timedOut: false, event: res.data.event, line: JSON.stringify(res.data.event) };
56
+ }
57
+ if (res.ok === false && res.status === 408)
58
+ continue;
59
+ if (res.ok === false)
60
+ throw new DialError("api_fallback_failed", res.error, res.status);
61
+ }
62
+ return { source: "api", timedOut: true, event: null, line: null };
63
+ }
@@ -0,0 +1,51 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { installSupervised, uninstallSupervised, supervisorStatus, supervisorAvailability, lastEventAtFromLog, } from "../supervisor/index.js";
3
+ import { paths } from "../paths.js";
4
+ import { requireAuth } from "./auth.js";
5
+ import { DialError } from "./errors.js";
6
+ function resolveDialPath() {
7
+ return process.env.DIAL_BIN_OVERRIDE ?? process.argv[1] ?? "dial";
8
+ }
9
+ export function listenInstall() {
10
+ requireAuth();
11
+ const supervisor = supervisorAvailability();
12
+ if (!supervisor.available) {
13
+ throw new DialError("supervisor_unavailable", supervisor.reason);
14
+ }
15
+ try {
16
+ return installSupervised(resolveDialPath());
17
+ }
18
+ catch (err) {
19
+ throw new DialError("install_failed", err instanceof Error ? err.message : String(err));
20
+ }
21
+ }
22
+ export function listenUninstall() {
23
+ try {
24
+ uninstallSupervised();
25
+ return { ok: true };
26
+ }
27
+ catch (err) {
28
+ throw new DialError("uninstall_failed", err instanceof Error ? err.message : String(err));
29
+ }
30
+ }
31
+ export function listenStatus() {
32
+ const s = supervisorStatus();
33
+ const lastEventAt = lastEventAtFromLog(paths().listenLog);
34
+ let lastEvents = [];
35
+ try {
36
+ const raw = readFileSync(paths().listenLog, "utf8");
37
+ const lines = raw.trim().split("\n").filter(Boolean);
38
+ lastEvents = lines.slice(-5).map((l) => {
39
+ try {
40
+ return JSON.parse(l);
41
+ }
42
+ catch {
43
+ return l;
44
+ }
45
+ });
46
+ }
47
+ catch {
48
+ // no log yet — leave empty
49
+ }
50
+ return { installed: s.installed, running: s.running, pid: s.pid, unitPath: s.unitPath, lastEventAt, lastEvents };
51
+ }
@@ -0,0 +1,35 @@
1
+ import { addTarget, removeTarget, listTargets, targetId, LocalTargetError, DEFAULT_SIGNATURE_HEADER, } from "../local-targets.js";
2
+ import { DialError } from "./errors.js";
3
+ function wrap(fn) {
4
+ try {
5
+ return fn();
6
+ }
7
+ catch (err) {
8
+ if (err instanceof LocalTargetError)
9
+ throw new DialError(err.code, err.message);
10
+ throw err;
11
+ }
12
+ }
13
+ export function addUrlTarget(opts) {
14
+ const { added } = wrap(() => addTarget({
15
+ kind: "url",
16
+ url: opts.url,
17
+ secret: opts.secret,
18
+ signatureHeader: opts.signatureHeader ?? (opts.secret ? DEFAULT_SIGNATURE_HEADER : undefined),
19
+ bearer: opts.bearer,
20
+ timeoutSeconds: opts.timeoutSeconds,
21
+ }));
22
+ return { added, url: opts.url };
23
+ }
24
+ export function addCommandTarget(opts) {
25
+ const args = opts.args ?? [];
26
+ const { added } = wrap(() => addTarget({ kind: "cmd", path: opts.path, args, timeoutSeconds: opts.timeoutSeconds }));
27
+ return { added, path: opts.path, args };
28
+ }
29
+ export function removeLocalTarget(id) {
30
+ const { removed } = removeTarget(id);
31
+ return { removed, id };
32
+ }
33
+ export function listLocalTargets() {
34
+ return listTargets().map((t) => ({ kind: t.kind, id: targetId(t), target: t }));
35
+ }
@@ -0,0 +1,26 @@
1
+ import { apiGet, apiPost } from "../api.js";
2
+ import { requireAuth, requireFromNumberId } from "./auth.js";
3
+ import { DialError } from "./errors.js";
4
+ export async function sendMessage(opts) {
5
+ const auth = requireAuth();
6
+ const fromNumberId = requireFromNumberId(auth, opts.fromNumberId);
7
+ const res = await apiPost("/api/v1/messages", { to: opts.to, body: opts.body, channel: "sms", fromNumberId }, auth.apiKey);
8
+ if (!res.ok)
9
+ throw new DialError("send_failed", res.error, res.status);
10
+ return res.data.message;
11
+ }
12
+ export async function listMessages(opts) {
13
+ const auth = requireAuth();
14
+ const params = new URLSearchParams();
15
+ if (opts.numberId)
16
+ params.set("numberId", opts.numberId);
17
+ if (opts.direction)
18
+ params.set("direction", opts.direction);
19
+ if (opts.since)
20
+ params.set("since", opts.since);
21
+ const qs = params.toString();
22
+ const res = await apiGet(qs ? `/api/v1/messages?${qs}` : "/api/v1/messages", auth.apiKey);
23
+ if (!res.ok)
24
+ throw new DialError("list_failed", res.error, res.status);
25
+ return res.data.messages ?? [];
26
+ }
@@ -0,0 +1,39 @@
1
+ import { apiGet, apiPost, apiPatch } from "../api.js";
2
+ import { requireAuth } from "./auth.js";
3
+ import { DialError } from "./errors.js";
4
+ export async function listNumbers() {
5
+ const auth = requireAuth();
6
+ const res = await apiGet("/api/v1/numbers", auth.apiKey);
7
+ if (!res.ok)
8
+ throw new DialError("list_failed", res.error, res.status);
9
+ return { numbers: res.data.numbers ?? [], defaultNumberId: auth.phoneNumberId ?? null };
10
+ }
11
+ export async function purchaseNumber(opts) {
12
+ const auth = requireAuth();
13
+ const body = { inboundInstruction: opts.inboundInstruction };
14
+ if (opts.country)
15
+ body.country = opts.country;
16
+ if (opts.areaCode)
17
+ body.areaCode = opts.areaCode;
18
+ const res = await apiPost("/api/v1/numbers", body, auth.apiKey);
19
+ if (!res.ok)
20
+ throw new DialError("purchase_failed", res.error, res.status);
21
+ return res.data.number;
22
+ }
23
+ export async function setNumberProperties(opts) {
24
+ const auth = requireAuth();
25
+ // The REST API keys numbers by id; the CLI/tool takes the E.164 number for ergonomics,
26
+ // so resolve it to its id first.
27
+ const list = await apiGet("/api/v1/numbers", auth.apiKey);
28
+ if (!list.ok)
29
+ throw new DialError("list_failed", list.error, list.status);
30
+ const match = list.data.numbers.find((n) => n.number === opts.number);
31
+ if (!match) {
32
+ const known = list.data.numbers.map((n) => n.number).join(", ") || "(none)";
33
+ throw new DialError("number_not_found", `No phone number ${opts.number} on your account. Yours: ${known}.`);
34
+ }
35
+ const res = await apiPatch(`/api/v1/numbers/${match.id}`, { inboundInstruction: opts.inboundInstruction }, auth.apiKey);
36
+ if (!res.ok)
37
+ throw new DialError("update_failed", res.error, res.status);
38
+ return res.data.number;
39
+ }
@@ -0,0 +1,27 @@
1
+ import { ZodError } from "zod";
2
+ import { tools } from "./tools/index.js";
3
+ import { errorResult } from "./result.js";
4
+ import { isDialError } from "../lib/ops/errors.js";
5
+ import { logger } from "../lib/log.js";
6
+ /**
7
+ * Register every tool onto the MCP server. DialError/ZodError surface their message to
8
+ * the model as a tool error; unexpected errors are logged (to stderr — safe for stdio)
9
+ * and surfaced generically.
10
+ */
11
+ export function registerTools(server) {
12
+ for (const tool of tools) {
13
+ server.registerTool(tool.name, tool.config, async (args) => {
14
+ try {
15
+ return await tool.run(args ?? {});
16
+ }
17
+ catch (err) {
18
+ if (isDialError(err))
19
+ return errorResult(err.message);
20
+ if (err instanceof ZodError)
21
+ return errorResult(`Invalid input: ${err.message}`);
22
+ logger.error({ err, tool: tool.name }, "mcp tool error");
23
+ return errorResult("Internal error running tool.");
24
+ }
25
+ });
26
+ }
27
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * JSON tool response. Rendered as text, and mirrored into `structuredContent` (the
3
+ * JSON-coerced form) for clients that validate against the tool's `outputSchema`.
4
+ */
5
+ export function jsonResult(data) {
6
+ const text = JSON.stringify(data, null, 2);
7
+ const structured = JSON.parse(text);
8
+ const result = { content: [{ type: "text", text }] };
9
+ if (structured !== null && typeof structured === "object" && !Array.isArray(structured)) {
10
+ result.structuredContent = structured;
11
+ }
12
+ return result;
13
+ }
14
+ export function errorResult(message) {
15
+ return { content: [{ type: "text", text: message }], isError: true };
16
+ }
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Reusable Zod output schemas for tool results. Inner entity objects are
4
+ * `.passthrough()` so extra fields aren't stripped from `structuredContent`.
5
+ * Mirrors the remote server's schemas so the two surfaces stay aligned.
6
+ */
7
+ export const phoneNumberSchema = z
8
+ .object({
9
+ id: z.string().describe("Phone number id (pn_…)"),
10
+ number: z.string().describe("E.164 phone number"),
11
+ country: z.string().optional(),
12
+ inboundInstruction: z.string().nullable().optional(),
13
+ })
14
+ .passthrough();
15
+ export const messageSchema = z
16
+ .object({
17
+ id: z.string(),
18
+ from: z.string(),
19
+ to: z.string(),
20
+ body: z.string(),
21
+ channel: z.string().optional(),
22
+ direction: z.string().optional(),
23
+ status: z.string(),
24
+ createdAt: z.string().optional(),
25
+ })
26
+ .passthrough();
27
+ export const callSchema = z
28
+ .object({
29
+ id: z.string(),
30
+ from: z.string(),
31
+ to: z.string(),
32
+ direction: z.string().optional(),
33
+ status: z.string(),
34
+ duration: z.number().optional(),
35
+ transcript: z.string().nullable().optional(),
36
+ instruction: z.string().nullable().optional(),
37
+ createdAt: z.string().optional(),
38
+ })
39
+ .passthrough();
40
+ export const eventSchema = z
41
+ .object({})
42
+ .passthrough()
43
+ .describe("The matched account event; shape varies by event type");
44
+ export const localTargetSchema = z
45
+ .object({
46
+ kind: z.string().describe('"url" or "cmd"'),
47
+ id: z.string().describe("Target id (URL for url targets, path for cmd targets)"),
48
+ target: z.object({}).passthrough(),
49
+ })
50
+ .passthrough();
@@ -0,0 +1,20 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { VERSION } from "../lib/version.js";
3
+ import { registerTools } from "./register.js";
4
+ // Server identity shown by MCP clients. Mirrors the remote Dial MCP server: name/title +
5
+ // the scalable Dial favicon (served same-origin, https). SVG with sizes:["any"] covers
6
+ // every render size.
7
+ const SERVER_INFO = {
8
+ name: "dial",
9
+ title: "Dial",
10
+ version: VERSION,
11
+ websiteUrl: "https://getdial.ai",
12
+ icons: [
13
+ { src: "https://getdial.ai/favicon.svg", mimeType: "image/svg+xml", sizes: ["any"] },
14
+ ],
15
+ };
16
+ export function buildServer() {
17
+ const server = new McpServer(SERVER_INFO);
18
+ registerTools(server);
19
+ return server;
20
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { addCommandTarget } from "../../lib/ops/local-targets.js";
4
+ const inputSchema = {
5
+ path: z.string().min(1).describe("Absolute path to an executable the daemon spawns once per event"),
6
+ args: z.array(z.string()).optional().describe("Extra args; the event JSON is appended as the final positional arg"),
7
+ timeoutSeconds: z.number().int().positive().optional().describe("Per-attempt timeout (default 5)"),
8
+ };
9
+ export const addCommandTargetTool = {
10
+ name: "add_command_target",
11
+ config: {
12
+ title: "Add Command Fan-out Target",
13
+ description: "Register an executable the local listen daemon runs once per event (event JSON as the final arg).",
14
+ inputSchema,
15
+ outputSchema: {
16
+ added: z.boolean().describe("False if the target was already registered"),
17
+ path: z.string(),
18
+ args: z.array(z.string()),
19
+ },
20
+ annotations: { openWorldHint: false },
21
+ },
22
+ run: async (args) => jsonResult(addCommandTarget({
23
+ path: args.path,
24
+ args: args.args,
25
+ timeoutSeconds: args.timeoutSeconds,
26
+ })),
27
+ };
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { addUrlTarget } from "../../lib/ops/local-targets.js";
4
+ const inputSchema = {
5
+ url: z.string().min(1).describe("Loopback HTTP endpoint the listen daemon POSTs each event JSON to"),
6
+ secret: z.string().optional().describe("HMAC-SHA256 key; the daemon signs each request body and sends the hex digest"),
7
+ signatureHeader: z.string().optional().describe("Header for the HMAC signature (default X-Dial-Signature; only with secret)"),
8
+ bearer: z.string().optional().describe("Static bearer token, sent as Authorization: Bearer <token>"),
9
+ timeoutSeconds: z.number().int().positive().optional().describe("Per-attempt timeout (default 5)"),
10
+ };
11
+ export const addUrlTargetTool = {
12
+ name: "add_url_target",
13
+ config: {
14
+ title: "Add URL Fan-out Target",
15
+ description: "Register a loopback HTTP endpoint the local listen daemon delivers each event to.",
16
+ inputSchema,
17
+ outputSchema: {
18
+ added: z.boolean().describe("False if the target was already registered"),
19
+ url: z.string(),
20
+ },
21
+ annotations: { openWorldHint: false },
22
+ },
23
+ run: async (args) => jsonResult(addUrlTarget({
24
+ url: args.url,
25
+ secret: args.secret,
26
+ signatureHeader: args.signatureHeader,
27
+ bearer: args.bearer,
28
+ timeoutSeconds: args.timeoutSeconds,
29
+ })),
30
+ };
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { accountStatus } from "../../lib/ops/account.js";
4
+ export const getAccountStatusTool = {
5
+ name: "get_account_status",
6
+ config: {
7
+ title: "Get Account Status",
8
+ description: "Report local Dial setup state: CLI version, backend reachability, sign-in/key validity, " +
9
+ "any pending OTP, the listen daemon state, and the recommended next step.",
10
+ inputSchema: {},
11
+ outputSchema: {
12
+ cli: z.object({}).passthrough().describe("CLI and node versions"),
13
+ backend: z.object({}).passthrough().describe("Backend URL and reachability"),
14
+ auth: z.object({}).passthrough().describe("Sign-in and API-key state"),
15
+ pendingOtp: z.object({}).passthrough().describe("Any pending sign-up OTP"),
16
+ listen: z.object({}).passthrough().describe("Listen daemon state"),
17
+ nextStep: z.string().describe("Recommended next step (signup, onboard, install_listen, ready, …)"),
18
+ },
19
+ annotations: { readOnlyHint: true, openWorldHint: true },
20
+ },
21
+ run: async () => jsonResult(await accountStatus()),
22
+ };
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { getCall } from "../../lib/ops/calls.js";
4
+ import { callSchema } from "../schemas.js";
5
+ const inputSchema = {
6
+ callId: z.string().min(1).describe("The call id to fetch"),
7
+ };
8
+ export const getCallTool = {
9
+ name: "get_call",
10
+ config: {
11
+ title: "Get Call",
12
+ description: "Fetch a single call by id — status, duration, and transcript when available.",
13
+ inputSchema,
14
+ outputSchema: { call: callSchema },
15
+ annotations: { readOnlyHint: true, openWorldHint: true },
16
+ },
17
+ run: async (args) => jsonResult({ call: await getCall(args.callId) }),
18
+ };
@@ -0,0 +1,41 @@
1
+ import { listNumbersTool } from "./list-numbers.js";
2
+ import { purchaseNumberTool } from "./purchase-number.js";
3
+ import { setNumberPropertiesTool } from "./set-number-properties.js";
4
+ import { sendMessageTool } from "./send-message.js";
5
+ import { listMessagesTool } from "./list-messages.js";
6
+ import { placeCallTool } from "./place-call.js";
7
+ import { listCallsTool } from "./list-calls.js";
8
+ import { getCallTool } from "./get-call.js";
9
+ import { getAccountStatusTool } from "./get-account-status.js";
10
+ import { signUpTool } from "./sign-up.js";
11
+ import { onboardTool } from "./onboard.js";
12
+ import { waitForEventTool } from "./wait-for-event.js";
13
+ import { addUrlTargetTool } from "./add-url-target.js";
14
+ import { addCommandTargetTool } from "./add-command-target.js";
15
+ import { removeLocalTargetTool } from "./remove-local-target.js";
16
+ import { listLocalTargetsTool } from "./list-local-targets.js";
17
+ import { listenInstallTool } from "./listen-install.js";
18
+ import { listenUninstallTool } from "./listen-uninstall.js";
19
+ import { listenStatusTool } from "./listen-status.js";
20
+ /** Every tool registered on the local stdio MCP server. */
21
+ export const tools = [
22
+ listNumbersTool,
23
+ purchaseNumberTool,
24
+ setNumberPropertiesTool,
25
+ sendMessageTool,
26
+ listMessagesTool,
27
+ placeCallTool,
28
+ listCallsTool,
29
+ getCallTool,
30
+ getAccountStatusTool,
31
+ signUpTool,
32
+ onboardTool,
33
+ waitForEventTool,
34
+ addUrlTargetTool,
35
+ addCommandTargetTool,
36
+ removeLocalTargetTool,
37
+ listLocalTargetsTool,
38
+ listenInstallTool,
39
+ listenUninstallTool,
40
+ listenStatusTool,
41
+ ];
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { listCalls } from "../../lib/ops/calls.js";
4
+ import { callSchema } from "../schemas.js";
5
+ const inputSchema = {
6
+ numberId: z.string().optional().describe("Filter to a single phone number id"),
7
+ direction: z.enum(["inbound", "outbound"]).optional().describe("Filter by direction"),
8
+ since: z.string().optional().describe("Only calls created after this ISO-8601 timestamp"),
9
+ };
10
+ export const listCallsTool = {
11
+ name: "list_calls",
12
+ config: {
13
+ title: "List Calls",
14
+ description: "List recent calls on your account, newest first.",
15
+ inputSchema,
16
+ outputSchema: { calls: z.array(callSchema) },
17
+ annotations: { readOnlyHint: true, openWorldHint: true },
18
+ },
19
+ run: async (args) => jsonResult({
20
+ calls: await listCalls({
21
+ numberId: args.numberId,
22
+ direction: args.direction,
23
+ since: args.since,
24
+ }),
25
+ }),
26
+ };
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { listLocalTargets } from "../../lib/ops/local-targets.js";
4
+ import { localTargetSchema } from "../schemas.js";
5
+ export const listLocalTargetsTool = {
6
+ name: "list_local_targets",
7
+ config: {
8
+ title: "List Fan-out Targets",
9
+ description: "List the local fan-out targets the listen daemon currently delivers events to.",
10
+ inputSchema: {},
11
+ outputSchema: { targets: z.array(localTargetSchema) },
12
+ annotations: { readOnlyHint: true },
13
+ },
14
+ run: async () => jsonResult({ targets: listLocalTargets() }),
15
+ };
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { listMessages } from "../../lib/ops/messages.js";
4
+ import { messageSchema } from "../schemas.js";
5
+ const inputSchema = {
6
+ numberId: z.string().optional().describe("Filter to a single phone number id"),
7
+ direction: z.enum(["inbound", "outbound"]).optional().describe("Filter by direction"),
8
+ since: z.string().optional().describe("Only messages created after this ISO-8601 timestamp"),
9
+ };
10
+ export const listMessagesTool = {
11
+ name: "list_messages",
12
+ config: {
13
+ title: "List Messages",
14
+ description: "List recent messages on your account, newest first.",
15
+ inputSchema,
16
+ outputSchema: { messages: z.array(messageSchema) },
17
+ annotations: { readOnlyHint: true, openWorldHint: true },
18
+ },
19
+ run: async (args) => jsonResult({
20
+ messages: await listMessages({
21
+ numberId: args.numberId,
22
+ direction: args.direction,
23
+ since: args.since,
24
+ }),
25
+ }),
26
+ };
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { listNumbers } from "../../lib/ops/numbers.js";
4
+ import { phoneNumberSchema } from "../schemas.js";
5
+ export const listNumbersTool = {
6
+ name: "list_numbers",
7
+ config: {
8
+ title: "List Phone Numbers",
9
+ description: "List the phone numbers on your Dial account, with the default number id.",
10
+ inputSchema: {},
11
+ outputSchema: {
12
+ numbers: z.array(phoneNumberSchema),
13
+ defaultNumberId: z.string().nullable().describe("Your primary number id, or null"),
14
+ },
15
+ annotations: { readOnlyHint: true, openWorldHint: true },
16
+ },
17
+ run: async () => jsonResult(await listNumbers()),
18
+ };
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { listenInstall } from "../../lib/ops/listen.js";
4
+ export const listenInstallTool = {
5
+ name: "listen_install",
6
+ config: {
7
+ title: "Install Listen Daemon",
8
+ description: "Install the background event daemon (launchd on macOS, systemd --user on Linux) so inbound " +
9
+ "SMS and call-ended events are delivered to this machine in real time.",
10
+ inputSchema: {},
11
+ outputSchema: {
12
+ changed: z.boolean().describe("False if the daemon was already installed"),
13
+ warnings: z.array(z.string()),
14
+ unitPath: z.string().describe("Path to the installed launchd/systemd unit"),
15
+ },
16
+ annotations: {},
17
+ },
18
+ run: async () => jsonResult(listenInstall()),
19
+ };
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { listenStatus } from "../../lib/ops/listen.js";
4
+ export const listenStatusTool = {
5
+ name: "listen_status",
6
+ config: {
7
+ title: "Listen Daemon Status",
8
+ description: "Report the background event daemon's state (installed/running/pid) and the last few events.",
9
+ inputSchema: {},
10
+ outputSchema: {
11
+ installed: z.boolean(),
12
+ running: z.boolean(),
13
+ pid: z.number().nullable(),
14
+ unitPath: z.string(),
15
+ lastEventAt: z.string().nullable(),
16
+ lastEvents: z.array(z.unknown()).describe("Up to the last 5 events from the local log"),
17
+ },
18
+ annotations: { readOnlyHint: true },
19
+ },
20
+ run: async () => jsonResult(listenStatus()),
21
+ };
@@ -0,0 +1,14 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { listenUninstall } from "../../lib/ops/listen.js";
4
+ export const listenUninstallTool = {
5
+ name: "listen_uninstall",
6
+ config: {
7
+ title: "Uninstall Listen Daemon",
8
+ description: "Stop and remove the background event daemon from this machine.",
9
+ inputSchema: {},
10
+ outputSchema: { ok: z.boolean() },
11
+ annotations: { destructiveHint: true },
12
+ },
13
+ run: async () => jsonResult(listenUninstall()),
14
+ };