@getdial/cli 0.3.0 → 0.4.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 CHANGED
@@ -12,6 +12,10 @@ import { runWaitFor } from "./commands/wait-for.js";
12
12
  import { runTest2faStart } from "./commands/test-2fa/start.js";
13
13
  import { runTest2faVerify } from "./commands/test-2fa/verify.js";
14
14
  import { runTest2faRun } from "./commands/test-2fa/run.js";
15
+ import { runNumbersList } from "./commands/numbers/list.js";
16
+ import { runNumbersProvision } from "./commands/numbers/provision.js";
17
+ import { runSend } from "./commands/send.js";
18
+ import { runCall } from "./commands/call.js";
15
19
  const program = new Command();
16
20
  program
17
21
  .name("dial")
@@ -79,6 +83,57 @@ test2fa
79
83
  timeoutSeconds: opts.timeout,
80
84
  json: !!opts.json,
81
85
  })));
86
+ const numbers = program
87
+ .command("numbers")
88
+ .description("Manage your Dial phone numbers.");
89
+ numbers
90
+ .command("list")
91
+ .description("List the numbers on your account. GET /api/v1/numbers.")
92
+ .option("--json", "machine-readable output")
93
+ .action(async (opts) => process.exit(await runNumbersList({ json: !!opts.json })));
94
+ numbers
95
+ .command("provision")
96
+ .description("Provision an additional phone number. POST /api/v1/numbers.")
97
+ .option("--country <iso2>", "ISO-3166-1 alpha-2 country code (defaults to US server-side)")
98
+ .option("--area-code <code>", "preferred area code (US/CA)")
99
+ .option("--agent-id <id>", "attach to an existing agent")
100
+ .option("--json", "machine-readable output")
101
+ .action(async (opts) => process.exit(await runNumbersProvision({
102
+ country: opts.country,
103
+ areaCode: opts.areaCode,
104
+ agentId: opts.agentId,
105
+ json: !!opts.json,
106
+ })));
107
+ program
108
+ .command("send")
109
+ .description("Send an SMS or WhatsApp message. POST /api/v1/messages.")
110
+ .requiredOption("--to <e164>", "destination phone number, E.164 (e.g. +14155551234)")
111
+ .requiredOption("--body <text>", "message body")
112
+ .option("--channel <sms|whatsapp>", "channel to send on", "sms")
113
+ .option("--from-number-id <id>", "phone_number_id to send from (defaults to onboard's number)")
114
+ .option("--json", "machine-readable output")
115
+ .action(async (opts) => process.exit(await runSend({
116
+ to: opts.to,
117
+ body: opts.body,
118
+ channel: opts.channel,
119
+ fromNumberId: opts.fromNumberId,
120
+ json: !!opts.json,
121
+ })));
122
+ program
123
+ .command("call")
124
+ .description("Place an outbound voice call. POST /api/v1/calls.")
125
+ .requiredOption("--to <e164>", "destination phone number, E.164 (e.g. +14155551234)")
126
+ .requiredOption("--system-prompt <text>", "system prompt for the agent that will speak")
127
+ .option("--language <bcp47>", "BCP-47 language tag for the call", "en-US")
128
+ .option("--from-number-id <id>", "phone_number_id to call from (defaults to onboard's number)")
129
+ .option("--json", "machine-readable output")
130
+ .action(async (opts) => process.exit(await runCall({
131
+ to: opts.to,
132
+ systemPrompt: opts.systemPrompt,
133
+ language: opts.language,
134
+ fromNumberId: opts.fromNumberId,
135
+ json: !!opts.json,
136
+ })));
82
137
  program
83
138
  .command("wait-for <event-type>")
84
139
  .description("Wait for the next matching event in the listen log (e.g. call.ended, message.received).")
@@ -0,0 +1,72 @@
1
+ import { readAuth } from "../lib/state.js";
2
+ import { apiPost } from "../lib/api.js";
3
+ export async function runCall(opts) {
4
+ const auth = readAuth();
5
+ if (!auth) {
6
+ fail(opts.json, "not_signed_in", "Not signed in. Run `dial signup` and `dial onboard` first.");
7
+ return 1;
8
+ }
9
+ const fromNumberId = opts.fromNumberId ?? auth.phone_number_id;
10
+ if (!fromNumberId) {
11
+ fail(opts.json, "no_from_number", "No default phone_number_id in auth. Pass --from-number-id <id>.");
12
+ return 1;
13
+ }
14
+ const res = await apiPost("/api/v1/calls", {
15
+ to: opts.to,
16
+ from_number_id: fromNumberId,
17
+ system_prompt: opts.systemPrompt,
18
+ language: opts.language,
19
+ }, auth.api_key);
20
+ if (!res.ok) {
21
+ fail(opts.json, "call_failed", res.error, { status: res.status });
22
+ return 2;
23
+ }
24
+ const c = res.data.call;
25
+ const waitCmd = `dial wait-for call.ended -f call_id=${c.id} --json`;
26
+ const followUpQuestion = "Want me to wait for the call to finish and report back (duration, final status, transcript)?";
27
+ if (opts.json) {
28
+ console.log(JSON.stringify({
29
+ ok: true,
30
+ call: c,
31
+ agent_hint: {
32
+ action: "ask_user_about_waiting",
33
+ kind: "post_call_followup",
34
+ question: followUpQuestion,
35
+ wait_command: waitCmd,
36
+ on_yes: waitCmd,
37
+ on_no: null,
38
+ use_structured_tool_if_available: true,
39
+ },
40
+ }));
41
+ }
42
+ else {
43
+ console.log(`call initiated.`);
44
+ console.log(` from: ${c.from}`);
45
+ console.log(` to: ${c.to}`);
46
+ console.log(` status: ${c.status}`);
47
+ console.log(` call id: ${c.id}`);
48
+ console.log(` twilio: ${c.sid}`);
49
+ if (c.retellCallId)
50
+ console.log(` retell: ${c.retellCallId}`);
51
+ console.log(``);
52
+ console.log(`The call has been dispatched. To learn when it ends (duration, final`);
53
+ console.log(`status, transcript availability), wait for the call.ended event:`);
54
+ console.log(``);
55
+ console.log(` ${waitCmd}`);
56
+ console.log(``);
57
+ console.log(`Suggestion: ask the user whether they'd like you to wait for the call`);
58
+ console.log(`to finish and report back, e.g.`);
59
+ console.log(``);
60
+ console.log(` "${followUpQuestion}"`);
61
+ console.log(``);
62
+ console.log(`If yes → run the wait-for above and report when it returns.`);
63
+ console.log(`If no → you're done; they can run the wait-for themselves later.`);
64
+ }
65
+ return 0;
66
+ }
67
+ function fail(json, code, message, extra) {
68
+ if (json)
69
+ console.log(JSON.stringify({ ok: false, code, message, ...extra }));
70
+ else
71
+ console.error(message);
72
+ }
@@ -0,0 +1,35 @@
1
+ import { readAuth } from "../../lib/state.js";
2
+ import { apiGet } from "../../lib/api.js";
3
+ export async function runNumbersList(opts) {
4
+ const auth = readAuth();
5
+ if (!auth) {
6
+ fail(opts.json, "not_signed_in", "Not signed in. Run `dial signup` and `dial onboard` first.");
7
+ return 1;
8
+ }
9
+ const res = await apiGet("/api/v1/numbers", auth.api_key);
10
+ if (!res.ok) {
11
+ fail(opts.json, "list_failed", res.error, { status: res.status });
12
+ return 2;
13
+ }
14
+ const numbers = res.data.numbers ?? [];
15
+ if (opts.json) {
16
+ console.log(JSON.stringify({ ok: true, numbers, default_number_id: auth.phone_number_id ?? null }));
17
+ return 0;
18
+ }
19
+ if (numbers.length === 0) {
20
+ console.log("no phone numbers. provision one with `dial numbers provision`.");
21
+ return 0;
22
+ }
23
+ for (const n of numbers) {
24
+ const tag = n.id === auth.phone_number_id ? " (default)" : "";
25
+ const agent = n.agent ? ` agent=${n.agent.name}` : "";
26
+ console.log(`${n.number} id=${n.id} ${n.country}${agent}${tag}`);
27
+ }
28
+ return 0;
29
+ }
30
+ function fail(json, code, message, extra) {
31
+ if (json)
32
+ console.log(JSON.stringify({ ok: false, code, message, ...extra }));
33
+ else
34
+ console.error(message);
35
+ }
@@ -0,0 +1,38 @@
1
+ import { readAuth } from "../../lib/state.js";
2
+ import { apiPost } from "../../lib/api.js";
3
+ export async function runNumbersProvision(opts) {
4
+ const auth = readAuth();
5
+ if (!auth) {
6
+ fail(opts.json, "not_signed_in", "Not signed in. Run `dial signup` and `dial onboard` first.");
7
+ return 1;
8
+ }
9
+ const body = {};
10
+ if (opts.country)
11
+ body.country = opts.country;
12
+ if (opts.areaCode)
13
+ body.area_code = opts.areaCode;
14
+ if (opts.agentId)
15
+ body.agent_id = opts.agentId;
16
+ const res = await apiPost("/api/v1/numbers", body, auth.api_key);
17
+ if (!res.ok) {
18
+ fail(opts.json, "provision_failed", res.error, { status: res.status });
19
+ return 2;
20
+ }
21
+ const n = res.data.number;
22
+ if (opts.json) {
23
+ console.log(JSON.stringify({ ok: true, number: n }));
24
+ }
25
+ else {
26
+ console.log(`provisioned.`);
27
+ console.log(` number: ${n.number}`);
28
+ console.log(` id: ${n.id}`);
29
+ console.log(` country: ${n.country}`);
30
+ }
31
+ return 0;
32
+ }
33
+ function fail(json, code, message, extra) {
34
+ if (json)
35
+ console.log(JSON.stringify({ ok: false, code, message, ...extra }));
36
+ else
37
+ console.error(message);
38
+ }
@@ -9,6 +9,7 @@ const LISTEN_RATIONALE = "The background notification listening service runs on
9
9
  "Without it the agent is send-only: it can place outbound calls and send messages, but it has no way to know when something arrives at your Dial number. " +
10
10
  "It can always be installed later with `dial listen install`.";
11
11
  const PRESENTATION_HINT = "When you ask the user, describe it as \"the background notification listening service.\" Don't expose technical details (launchd, plists, daemons, systemd units) — that's not how to talk about it to a non-technical user.";
12
+ const FINALIZATION_HINT = "To finalize onboarding, ask the user this question before continuing with any other task. If the user's original request was something else (e.g. \"call X\", \"send Y\"), briefly pause that task, ask the question, get an answer, then resume.";
12
13
  export async function runOnboard(opts) {
13
14
  let verificationId = opts.verificationId;
14
15
  let email = null;
@@ -62,9 +63,11 @@ export async function runOnboard(opts) {
62
63
  listen: { installed: false, auto_installed: false },
63
64
  agent_hint: {
64
65
  action: "ask_user",
66
+ kind: "onboarding_finalization",
65
67
  question: LISTEN_QUESTION,
66
68
  rationale: LISTEN_RATIONALE,
67
69
  presentation: PRESENTATION_HINT,
70
+ finalization_note: FINALIZATION_HINT,
68
71
  use_structured_tool_if_available: true,
69
72
  on_yes: "dial listen install",
70
73
  on_no: null,
@@ -77,19 +80,24 @@ export async function runOnboard(opts) {
77
80
  if (res.data.phone_number)
78
81
  console.log(` phone number: ${res.data.phone_number}`);
79
82
  console.log(``);
80
- console.log(`The background notification listening service is not running yet.`);
81
- console.log(`${LISTEN_RATIONALE}`);
83
+ console.log(`Onboarding finalization (required):`);
82
84
  console.log(``);
83
- console.log(`If you are an agent, ask the user now:`);
85
+ console.log(FINALIZATION_HINT);
86
+ console.log(``);
87
+ console.log(`Ask the user:`);
84
88
  console.log(``);
85
89
  console.log(` "${LISTEN_QUESTION}"`);
86
90
  console.log(``);
87
- console.log(PRESENTATION_HINT);
91
+ console.log(`Background: ${LISTEN_RATIONALE}`);
92
+ console.log(``);
93
+ console.log(`Phrasing rules: ${PRESENTATION_HINT}`);
88
94
  console.log(``);
89
- console.log(`Use your ask-user / clarification tool if you have one; otherwise just ask in your regular reply and wait for the answer.`);
95
+ console.log(`Use your structured ask-user / clarification tool if you have one;`);
96
+ console.log(`otherwise ask in your regular reply and wait for the answer.`);
90
97
  console.log(``);
91
- console.log(`If yes: run \`dial listen install\``);
92
- console.log(`If no: leave it off install later with \`dial listen install\``);
98
+ console.log(`After the user answers:`);
99
+ console.log(` yes run \`dial listen install\`, then resume the original task.`);
100
+ console.log(` no → resume the original task. They can install later with \`dial listen install\`.`);
93
101
  }
94
102
  return 0;
95
103
  }
@@ -0,0 +1,43 @@
1
+ import { readAuth } from "../lib/state.js";
2
+ import { apiPost } from "../lib/api.js";
3
+ export async function runSend(opts) {
4
+ const auth = readAuth();
5
+ if (!auth) {
6
+ fail(opts.json, "not_signed_in", "Not signed in. Run `dial signup` and `dial onboard` first.");
7
+ return 1;
8
+ }
9
+ const fromNumberId = opts.fromNumberId ?? auth.phone_number_id;
10
+ if (!fromNumberId) {
11
+ fail(opts.json, "no_from_number", "No default phone_number_id in auth. Pass --from-number-id <id>.");
12
+ return 1;
13
+ }
14
+ if (opts.channel !== "sms" && opts.channel !== "whatsapp") {
15
+ fail(opts.json, "bad_channel", `--channel must be sms or whatsapp, got "${opts.channel}".`);
16
+ return 1;
17
+ }
18
+ const res = await apiPost("/api/v1/messages", { to: opts.to, body: opts.body, channel: opts.channel, from_number_id: fromNumberId }, auth.api_key);
19
+ if (!res.ok) {
20
+ fail(opts.json, "send_failed", res.error, { status: res.status });
21
+ return 2;
22
+ }
23
+ const m = res.data.message;
24
+ if (opts.json) {
25
+ console.log(JSON.stringify({ ok: true, message: m }));
26
+ }
27
+ else {
28
+ console.log(`sent.`);
29
+ console.log(` channel: ${m.channel}`);
30
+ console.log(` from: ${m.from}`);
31
+ console.log(` to: ${m.to}`);
32
+ console.log(` body: ${m.body}`);
33
+ console.log(` status: ${m.status}`);
34
+ console.log(` sid: ${m.sid}`);
35
+ }
36
+ return 0;
37
+ }
38
+ function fail(json, code, message, extra) {
39
+ if (json)
40
+ console.log(JSON.stringify({ ok: false, code, message, ...extra }));
41
+ else
42
+ console.error(message);
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getdial/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Dial CLI — install, sign up, and run the local listen service.",
5
5
  "license": "MIT",
6
6
  "bin": {