@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 +55 -0
- package/dist/commands/call.js +72 -0
- package/dist/commands/numbers/list.js +35 -0
- package/dist/commands/numbers/provision.js +38 -0
- package/dist/commands/onboard.js +15 -7
- package/dist/commands/send.js +43 -0
- package/package.json +1 -1
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
|
+
}
|
package/dist/commands/onboard.js
CHANGED
|
@@ -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(`
|
|
81
|
-
console.log(`${LISTEN_RATIONALE}`);
|
|
83
|
+
console.log(`Onboarding finalization (required):`);
|
|
82
84
|
console.log(``);
|
|
83
|
-
console.log(
|
|
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(
|
|
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
|
|
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(`
|
|
92
|
-
console.log(`
|
|
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
|
+
}
|