@getdial/cli 0.13.2 → 0.14.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 (51) hide show
  1. package/README.md +13 -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 +6 -0
  28. package/dist/mcp/server.js +20 -0
  29. package/dist/mcp/tool.js +1 -0
  30. package/dist/mcp/tools/add-command-target.js +22 -0
  31. package/dist/mcp/tools/add-url-target.js +26 -0
  32. package/dist/mcp/tools/get-account-status.js +13 -0
  33. package/dist/mcp/tools/get-call.js +16 -0
  34. package/dist/mcp/tools/index.js +41 -0
  35. package/dist/mcp/tools/list-calls.js +22 -0
  36. package/dist/mcp/tools/list-local-targets.js +12 -0
  37. package/dist/mcp/tools/list-messages.js +22 -0
  38. package/dist/mcp/tools/list-numbers.js +12 -0
  39. package/dist/mcp/tools/listen-install.js +13 -0
  40. package/dist/mcp/tools/listen-status.js +12 -0
  41. package/dist/mcp/tools/listen-uninstall.js +12 -0
  42. package/dist/mcp/tools/onboard.js +34 -0
  43. package/dist/mcp/tools/place-call.js +33 -0
  44. package/dist/mcp/tools/purchase-number.js +22 -0
  45. package/dist/mcp/tools/remove-local-target.js +16 -0
  46. package/dist/mcp/tools/send-message.js +22 -0
  47. package/dist/mcp/tools/set-number-properties.js +20 -0
  48. package/dist/mcp/tools/sign-up.js +18 -0
  49. package/dist/mcp/tools/wait-for-event.js +25 -0
  50. package/package.json +2 -1
  51. 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,6 @@
1
+ export function jsonResult(data) {
2
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
3
+ }
4
+ export function errorResult(message) {
5
+ return { content: [{ type: "text", text: message }], isError: true };
6
+ }
@@ -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,22 @@
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
+ annotations: {},
16
+ },
17
+ run: async (args) => jsonResult(addCommandTarget({
18
+ path: args.path,
19
+ args: args.args,
20
+ timeoutSeconds: args.timeoutSeconds,
21
+ })),
22
+ };
@@ -0,0 +1,26 @@
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
+ annotations: {},
18
+ },
19
+ run: async (args) => jsonResult(addUrlTarget({
20
+ url: args.url,
21
+ secret: args.secret,
22
+ signatureHeader: args.signatureHeader,
23
+ bearer: args.bearer,
24
+ timeoutSeconds: args.timeoutSeconds,
25
+ })),
26
+ };
@@ -0,0 +1,13 @@
1
+ import { jsonResult } from "../result.js";
2
+ import { accountStatus } from "../../lib/ops/account.js";
3
+ export const getAccountStatusTool = {
4
+ name: "get_account_status",
5
+ config: {
6
+ title: "Get Account Status",
7
+ description: "Report local Dial setup state: CLI version, backend reachability, sign-in/key validity, " +
8
+ "any pending OTP, the listen daemon state, and the recommended next step.",
9
+ inputSchema: {},
10
+ annotations: { readOnlyHint: true, openWorldHint: true },
11
+ },
12
+ run: async () => jsonResult(await accountStatus()),
13
+ };
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { getCall } from "../../lib/ops/calls.js";
4
+ const inputSchema = {
5
+ callId: z.string().min(1).describe("The call id to fetch"),
6
+ };
7
+ export const getCallTool = {
8
+ name: "get_call",
9
+ config: {
10
+ title: "Get Call",
11
+ description: "Fetch a single call by id — status, duration, and transcript when available.",
12
+ inputSchema,
13
+ annotations: { readOnlyHint: true, openWorldHint: true },
14
+ },
15
+ run: async (args) => jsonResult(await getCall(args.callId)),
16
+ };
@@ -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,22 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { listCalls } from "../../lib/ops/calls.js";
4
+ const inputSchema = {
5
+ numberId: z.string().optional().describe("Filter to a single phone number id"),
6
+ direction: z.enum(["inbound", "outbound"]).optional().describe("Filter by direction"),
7
+ since: z.string().optional().describe("Only calls created after this ISO-8601 timestamp"),
8
+ };
9
+ export const listCallsTool = {
10
+ name: "list_calls",
11
+ config: {
12
+ title: "List Calls",
13
+ description: "List recent calls on your account, newest first.",
14
+ inputSchema,
15
+ annotations: { readOnlyHint: true, openWorldHint: true },
16
+ },
17
+ run: async (args) => jsonResult(await listCalls({
18
+ numberId: args.numberId,
19
+ direction: args.direction,
20
+ since: args.since,
21
+ })),
22
+ };
@@ -0,0 +1,12 @@
1
+ import { jsonResult } from "../result.js";
2
+ import { listLocalTargets } from "../../lib/ops/local-targets.js";
3
+ export const listLocalTargetsTool = {
4
+ name: "list_local_targets",
5
+ config: {
6
+ title: "List Fan-out Targets",
7
+ description: "List the local fan-out targets the listen daemon currently delivers events to.",
8
+ inputSchema: {},
9
+ annotations: { readOnlyHint: true },
10
+ },
11
+ run: async () => jsonResult(listLocalTargets()),
12
+ };
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { listMessages } from "../../lib/ops/messages.js";
4
+ const inputSchema = {
5
+ numberId: z.string().optional().describe("Filter to a single phone number id"),
6
+ direction: z.enum(["inbound", "outbound"]).optional().describe("Filter by direction"),
7
+ since: z.string().optional().describe("Only messages created after this ISO-8601 timestamp"),
8
+ };
9
+ export const listMessagesTool = {
10
+ name: "list_messages",
11
+ config: {
12
+ title: "List Messages",
13
+ description: "List recent messages on your account, newest first.",
14
+ inputSchema,
15
+ annotations: { readOnlyHint: true, openWorldHint: true },
16
+ },
17
+ run: async (args) => jsonResult(await listMessages({
18
+ numberId: args.numberId,
19
+ direction: args.direction,
20
+ since: args.since,
21
+ })),
22
+ };
@@ -0,0 +1,12 @@
1
+ import { jsonResult } from "../result.js";
2
+ import { listNumbers } from "../../lib/ops/numbers.js";
3
+ export const listNumbersTool = {
4
+ name: "list_numbers",
5
+ config: {
6
+ title: "List Phone Numbers",
7
+ description: "List the phone numbers on your Dial account, with the default number id.",
8
+ inputSchema: {},
9
+ annotations: { readOnlyHint: true, openWorldHint: true },
10
+ },
11
+ run: async () => jsonResult(await listNumbers()),
12
+ };
@@ -0,0 +1,13 @@
1
+ import { jsonResult } from "../result.js";
2
+ import { listenInstall } from "../../lib/ops/listen.js";
3
+ export const listenInstallTool = {
4
+ name: "listen_install",
5
+ config: {
6
+ title: "Install Listen Daemon",
7
+ description: "Install the background event daemon (launchd on macOS, systemd --user on Linux) so inbound " +
8
+ "SMS and call-ended events are delivered to this machine in real time.",
9
+ inputSchema: {},
10
+ annotations: {},
11
+ },
12
+ run: async () => jsonResult(listenInstall()),
13
+ };
@@ -0,0 +1,12 @@
1
+ import { jsonResult } from "../result.js";
2
+ import { listenStatus } from "../../lib/ops/listen.js";
3
+ export const listenStatusTool = {
4
+ name: "listen_status",
5
+ config: {
6
+ title: "Listen Daemon Status",
7
+ description: "Report the background event daemon's state (installed/running/pid) and the last few events.",
8
+ inputSchema: {},
9
+ annotations: { readOnlyHint: true },
10
+ },
11
+ run: async () => jsonResult(listenStatus()),
12
+ };
@@ -0,0 +1,12 @@
1
+ import { jsonResult } from "../result.js";
2
+ import { listenUninstall } from "../../lib/ops/listen.js";
3
+ export const listenUninstallTool = {
4
+ name: "listen_uninstall",
5
+ config: {
6
+ title: "Uninstall Listen Daemon",
7
+ description: "Stop and remove the background event daemon from this machine.",
8
+ inputSchema: {},
9
+ annotations: { destructiveHint: true },
10
+ },
11
+ run: async () => jsonResult(listenUninstall()),
12
+ };
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { onboard } from "../../lib/ops/account.js";
4
+ const inputSchema = {
5
+ code: z.string().min(1).describe("6-digit OTP from the sign-up email"),
6
+ verificationId: z.string().optional().describe("Explicit verification id (defaults to the local pending signup)"),
7
+ inboundInstruction: z.string().optional().describe("System prompt for inbound calls to a newly provisioned number (new accounts)"),
8
+ agents: z.array(z.string()).optional().describe("Agent names to install the Dial skill into (e.g. claude-code, cursor)"),
9
+ };
10
+ export const onboardTool = {
11
+ name: "onboard",
12
+ config: {
13
+ title: "Onboard",
14
+ description: "Verify the sign-up OTP and finish onboarding: saves the API key locally and optionally installs " +
15
+ "the Dial skill into named agents. Returns the account summary (the raw API key is never returned).",
16
+ inputSchema,
17
+ annotations: { openWorldHint: true },
18
+ },
19
+ run: async (args) => {
20
+ const r = await onboard({
21
+ code: args.code,
22
+ verificationId: args.verificationId,
23
+ inboundInstruction: args.inboundInstruction,
24
+ agents: args.agents,
25
+ });
26
+ // Never surface the raw API key to the model; it's saved to disk for the CLI to read.
27
+ const { apiKey: _omit, ...safe } = r;
28
+ void _omit;
29
+ return jsonResult({
30
+ ...safe,
31
+ listenAvailable: r.supervisor.available,
32
+ });
33
+ },
34
+ };
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { placeCall } from "../../lib/ops/calls.js";
4
+ const inputSchema = {
5
+ to: z.string().min(7).describe("Destination phone number, E.164 (e.g. +14155550123)"),
6
+ outboundInstruction: z.string().min(1).describe("System prompt for the AI voice agent on this call"),
7
+ language: z.string().default("en-US").describe("BCP-47 language tag for the call"),
8
+ fromNumberId: z.string().optional().describe("Number id to call from; defaults to your primary number"),
9
+ };
10
+ export const placeCallTool = {
11
+ name: "place_call",
12
+ config: {
13
+ title: "Place AI Voice Call",
14
+ description: "Place an outbound voice call handled by an AI agent. The call runs asynchronously — " +
15
+ "use wait_for_event to block until it ends, then get_call for the transcript.",
16
+ inputSchema,
17
+ annotations: { openWorldHint: true },
18
+ },
19
+ run: async (args) => {
20
+ const call = await placeCall({
21
+ to: args.to,
22
+ outboundInstruction: args.outboundInstruction,
23
+ language: args.language ?? "en-US",
24
+ fromNumberId: args.fromNumberId,
25
+ });
26
+ return jsonResult({
27
+ call,
28
+ hint: `The call is now in progress. To be notified when it ends, call wait_for_event with ` +
29
+ `eventType "call.ended" and field "callId=${call.id}". Then call get_call with callId ` +
30
+ `"${call.id}" for the final status, duration, and transcript.`,
31
+ });
32
+ },
33
+ };
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { purchaseNumber } from "../../lib/ops/numbers.js";
4
+ const inputSchema = {
5
+ inboundInstruction: z.string().min(1).describe("System prompt for inbound calls to this number"),
6
+ country: z.string().optional().describe("ISO-3166-1 alpha-2 country code (defaults to US server-side)"),
7
+ areaCode: z.string().optional().describe("Preferred area code (US/CA)"),
8
+ };
9
+ export const purchaseNumberTool = {
10
+ name: "purchase_number",
11
+ config: {
12
+ title: "Purchase Phone Number",
13
+ description: "Purchase an additional phone number. This spends money on the account.",
14
+ inputSchema,
15
+ annotations: { destructiveHint: true, openWorldHint: true },
16
+ },
17
+ run: async (args) => jsonResult(await purchaseNumber({
18
+ inboundInstruction: args.inboundInstruction,
19
+ country: args.country,
20
+ areaCode: args.areaCode,
21
+ })),
22
+ };
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { removeLocalTarget } from "../../lib/ops/local-targets.js";
4
+ const inputSchema = {
5
+ id: z.string().min(1).describe("Target id: the URL for url targets, the path for cmd targets"),
6
+ };
7
+ export const removeLocalTargetTool = {
8
+ name: "remove_local_target",
9
+ config: {
10
+ title: "Remove Fan-out Target",
11
+ description: "Unregister a local fan-out target by id. Returns removed:false if no such target.",
12
+ inputSchema,
13
+ annotations: { destructiveHint: true },
14
+ },
15
+ run: async (args) => jsonResult(removeLocalTarget(args.id)),
16
+ };
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "../result.js";
3
+ import { sendMessage } from "../../lib/ops/messages.js";
4
+ const inputSchema = {
5
+ to: z.string().min(7).describe("Destination phone number, E.164 (e.g. +14155550123)"),
6
+ body: z.string().min(1).describe("Message body"),
7
+ fromNumberId: z.string().optional().describe("Number id to send from; defaults to your primary number"),
8
+ };
9
+ export const sendMessageTool = {
10
+ name: "send_message",
11
+ config: {
12
+ title: "Send SMS",
13
+ description: "Send an SMS from one of your Dial numbers.",
14
+ inputSchema,
15
+ annotations: { openWorldHint: true },
16
+ },
17
+ run: async (args) => jsonResult(await sendMessage({
18
+ to: args.to,
19
+ body: args.body,
20
+ fromNumberId: args.fromNumberId,
21
+ })),
22
+ };