@getdial/cli 0.6.0 → 0.8.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 +136 -63
- package/dist/commands/call/get.js +37 -0
- package/dist/commands/call/list.js +42 -0
- package/dist/commands/{call.js → call/send.js} +13 -13
- package/dist/commands/doctor.js +22 -22
- package/dist/commands/listen/index.js +2 -2
- package/dist/commands/local-target/add-cmd.js +27 -0
- package/dist/commands/local-target/add-url.js +29 -0
- package/dist/commands/local-target/list.js +16 -0
- package/dist/commands/local-target/remove.js +14 -0
- package/dist/commands/message/list.js +42 -0
- package/dist/commands/{send.js → message/send.js} +6 -6
- package/dist/commands/{numbers → number}/list.js +6 -7
- package/dist/commands/{numbers/provision.js → number/purchase.js} +5 -7
- package/dist/commands/onboard.js +53 -24
- package/dist/commands/signup.js +5 -5
- package/dist/commands/wait-for.js +3 -3
- package/dist/lib/fanout.js +131 -0
- package/dist/lib/local-targets.js +122 -0
- package/dist/lib/paths.js +1 -0
- package/dist/lib/pubnub.js +6 -2
- package/dist/lib/skill-install.js +100 -0
- package/dist/lib/state.js +6 -6
- package/package.json +5 -2
- package/skill.tar.gz +0 -0
- package/dist/commands/test-2fa/run.js +0 -84
- package/dist/commands/test-2fa/start.js +0 -38
- package/dist/commands/test-2fa/verify.js +0 -31
package/dist/cli.js
CHANGED
|
@@ -9,18 +9,23 @@ import { runListenInstall } from "./commands/listen/install.js";
|
|
|
9
9
|
import { runListenUninstall } from "./commands/listen/uninstall.js";
|
|
10
10
|
import { runListenStatus } from "./commands/listen/status.js";
|
|
11
11
|
import { runWaitFor } from "./commands/wait-for.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
12
|
+
import { runNumberList } from "./commands/number/list.js";
|
|
13
|
+
import { runNumberPurchase } from "./commands/number/purchase.js";
|
|
14
|
+
import { runMessageSend } from "./commands/message/send.js";
|
|
15
|
+
import { runMessageList } from "./commands/message/list.js";
|
|
16
|
+
import { runCallSend } from "./commands/call/send.js";
|
|
17
|
+
import { runCallList } from "./commands/call/list.js";
|
|
18
|
+
import { runCallGet } from "./commands/call/get.js";
|
|
19
|
+
import { runLocalTargetAddUrl } from "./commands/local-target/add-url.js";
|
|
20
|
+
import { runLocalTargetAddCmd } from "./commands/local-target/add-cmd.js";
|
|
21
|
+
import { runLocalTargetRemove } from "./commands/local-target/remove.js";
|
|
22
|
+
import { runLocalTargetList } from "./commands/local-target/list.js";
|
|
19
23
|
const program = new Command();
|
|
20
24
|
program
|
|
21
25
|
.name("dial")
|
|
22
26
|
.description("Dial CLI — set up your account and run the listen service.")
|
|
23
|
-
.version(VERSION)
|
|
27
|
+
.version(VERSION)
|
|
28
|
+
.enablePositionalOptions();
|
|
24
29
|
program
|
|
25
30
|
.command("doctor")
|
|
26
31
|
.description("Report state and what to do next.")
|
|
@@ -37,8 +42,14 @@ program
|
|
|
37
42
|
.description("Verify the OTP and finish onboarding.")
|
|
38
43
|
.option("--verification-id <id>", "explicit verification id (falls back to local pending signup)")
|
|
39
44
|
.requiredOption("--code <code>", "6-digit OTP from your email")
|
|
45
|
+
.option("--agent <name>", "install the Dial skill into the named agent's config dir. One of: claude-code, cursor, codex, opencode, pi, openclaw, nanoclaw, hermes. Repeatable.", (v, prev = []) => [...prev, v], [])
|
|
40
46
|
.option("--json", "machine-readable output")
|
|
41
|
-
.action(async (opts) => process.exit(await runOnboard({
|
|
47
|
+
.action(async (opts) => process.exit(await runOnboard({
|
|
48
|
+
verificationId: opts.verificationId,
|
|
49
|
+
code: opts.code,
|
|
50
|
+
agents: opts.agent,
|
|
51
|
+
json: !!opts.json,
|
|
52
|
+
})));
|
|
42
53
|
const listen = program
|
|
43
54
|
.command("listen")
|
|
44
55
|
.description("Run the listen worker (used by launchd/systemd).")
|
|
@@ -58,80 +69,142 @@ listen
|
|
|
58
69
|
.description("Report listen daemon state and last events.")
|
|
59
70
|
.option("--json", "machine-readable output")
|
|
60
71
|
.action(async (opts) => process.exit(await runListenStatus({ json: !!opts.json })));
|
|
61
|
-
const
|
|
62
|
-
.command("
|
|
63
|
-
.description("Drive the dashboard test-2fa loop (sends a fake SMS to your Dial number and verifies it).");
|
|
64
|
-
test2fa
|
|
65
|
-
.command("start")
|
|
66
|
-
.description("Trigger /api/v1/test/2fa for your default number. Use --number-id to override.")
|
|
67
|
-
.option("--number-id <id>", "phone_number_id to send the test SMS to (defaults to onboard's number)")
|
|
68
|
-
.option("--json", "machine-readable output")
|
|
69
|
-
.action(async (opts) => process.exit(await runTest2faStart({ numberId: opts.numberId, json: !!opts.json })));
|
|
70
|
-
test2fa
|
|
71
|
-
.command("verify <session-id> <code>")
|
|
72
|
-
.description("Submit the 6-digit code to /api/v1/test/2fa/<session>/verify.")
|
|
73
|
-
.option("--json", "machine-readable output")
|
|
74
|
-
.action(async (sessionId, code, opts) => process.exit(await runTest2faVerify({ sessionId, code, json: !!opts.json })));
|
|
75
|
-
test2fa
|
|
76
|
-
.command("run")
|
|
77
|
-
.description("Run the full loop: start, wait for the SMS in listen.log, parse code, verify.")
|
|
78
|
-
.option("--number-id <id>", "phone_number_id to send the test SMS to (defaults to onboard's number)")
|
|
79
|
-
.option("-t, --timeout <seconds>", "max seconds to wait for the SMS event (default 60)", (v) => parseInt(v, 10), 60)
|
|
80
|
-
.option("--json", "machine-readable output")
|
|
81
|
-
.action(async (opts) => process.exit(await runTest2faRun({
|
|
82
|
-
numberId: opts.numberId,
|
|
83
|
-
timeoutSeconds: opts.timeout,
|
|
84
|
-
json: !!opts.json,
|
|
85
|
-
})));
|
|
86
|
-
const numbers = program
|
|
87
|
-
.command("numbers")
|
|
72
|
+
const number = program
|
|
73
|
+
.command("number")
|
|
88
74
|
.description("Manage your Dial phone numbers.");
|
|
89
|
-
|
|
75
|
+
number
|
|
90
76
|
.command("list")
|
|
91
77
|
.description("List the numbers on your account. GET /api/v1/numbers.")
|
|
92
78
|
.option("--json", "machine-readable output")
|
|
93
|
-
.action(async (opts) => process.exit(await
|
|
94
|
-
|
|
95
|
-
.command("
|
|
96
|
-
.description("
|
|
79
|
+
.action(async (opts) => process.exit(await runNumberList({ json: !!opts.json })));
|
|
80
|
+
number
|
|
81
|
+
.command("purchase")
|
|
82
|
+
.description("Purchase an additional phone number. POST /api/v1/numbers.")
|
|
97
83
|
.option("--country <iso2>", "ISO-3166-1 alpha-2 country code (defaults to US server-side)")
|
|
98
84
|
.option("--area-code <code>", "preferred area code (US/CA)")
|
|
99
|
-
.option("--agent-id <id>", "attach to an existing agent")
|
|
100
85
|
.option("--json", "machine-readable output")
|
|
101
|
-
.action(async (opts) => process.exit(await
|
|
86
|
+
.action(async (opts) => process.exit(await runNumberPurchase({
|
|
102
87
|
country: opts.country,
|
|
103
88
|
areaCode: opts.areaCode,
|
|
104
|
-
agentId: opts.agentId,
|
|
105
89
|
json: !!opts.json,
|
|
106
90
|
})));
|
|
107
|
-
program
|
|
108
|
-
.command("
|
|
91
|
+
const message = program
|
|
92
|
+
.command("message")
|
|
109
93
|
.description("Send an SMS. POST /api/v1/messages.")
|
|
110
|
-
.
|
|
111
|
-
.
|
|
112
|
-
.option("--from-number-id <id>", "
|
|
113
|
-
.option("--json", "machine-readable output")
|
|
114
|
-
.action(async (opts) =>
|
|
115
|
-
to
|
|
116
|
-
|
|
117
|
-
|
|
94
|
+
.option("--to <e164>", "destination phone number, E.164 (e.g. +14155551234)")
|
|
95
|
+
.option("--body <text>", "message body")
|
|
96
|
+
.option("--from-number-id <id>", "phoneNumberId to send from (defaults to onboard's number)")
|
|
97
|
+
.option("--json", "machine-readable output")
|
|
98
|
+
.action(async (opts) => {
|
|
99
|
+
if (!opts.to || !opts.body) {
|
|
100
|
+
console.error("error: --to and --body are required to send a message. Use `dial message list` to list, or `dial message --help` for usage.");
|
|
101
|
+
process.exit(2);
|
|
102
|
+
}
|
|
103
|
+
process.exit(await runMessageSend({
|
|
104
|
+
to: opts.to,
|
|
105
|
+
body: opts.body,
|
|
106
|
+
fromNumberId: opts.fromNumberId,
|
|
107
|
+
json: !!opts.json,
|
|
108
|
+
}));
|
|
109
|
+
});
|
|
110
|
+
message
|
|
111
|
+
.command("list")
|
|
112
|
+
.description("List recent messages on your account. GET /api/v1/messages.")
|
|
113
|
+
.option("--number-id <id>", "filter to a single phone number")
|
|
114
|
+
.option("--direction <dir>", "inbound or outbound")
|
|
115
|
+
.option("--since <iso8601>", "only messages created after this timestamp")
|
|
116
|
+
.option("--json", "machine-readable output")
|
|
117
|
+
.action(async (opts) => process.exit(await runMessageList({
|
|
118
|
+
numberId: opts.numberId,
|
|
119
|
+
direction: opts.direction,
|
|
120
|
+
since: opts.since,
|
|
118
121
|
json: !!opts.json,
|
|
119
122
|
})));
|
|
120
|
-
program
|
|
123
|
+
const call = program
|
|
121
124
|
.command("call")
|
|
122
125
|
.description("Place an outbound voice call. POST /api/v1/calls.")
|
|
123
|
-
.
|
|
124
|
-
.
|
|
126
|
+
.option("--to <e164>", "destination phone number, E.164 (e.g. +14155551234)")
|
|
127
|
+
.option("--system-prompt <text>", "system prompt for the agent that will speak")
|
|
125
128
|
.option("--language <bcp47>", "BCP-47 language tag for the call", "en-US")
|
|
126
|
-
.option("--from-number-id <id>", "
|
|
129
|
+
.option("--from-number-id <id>", "phoneNumberId to call from (defaults to onboard's number)")
|
|
127
130
|
.option("--json", "machine-readable output")
|
|
128
|
-
.action(async (opts) =>
|
|
129
|
-
to
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
.action(async (opts) => {
|
|
132
|
+
if (!opts.to || !opts.systemPrompt) {
|
|
133
|
+
console.error("error: --to and --system-prompt are required to place a call. Use `dial call list` to list, `dial call get <id>` to fetch one, or `dial call --help` for usage.");
|
|
134
|
+
process.exit(2);
|
|
135
|
+
}
|
|
136
|
+
process.exit(await runCallSend({
|
|
137
|
+
to: opts.to,
|
|
138
|
+
systemPrompt: opts.systemPrompt,
|
|
139
|
+
language: opts.language,
|
|
140
|
+
fromNumberId: opts.fromNumberId,
|
|
141
|
+
json: !!opts.json,
|
|
142
|
+
}));
|
|
143
|
+
});
|
|
144
|
+
call
|
|
145
|
+
.command("list")
|
|
146
|
+
.description("List recent calls on your account. GET /api/v1/calls.")
|
|
147
|
+
.option("--number-id <id>", "filter to a single phone number")
|
|
148
|
+
.option("--direction <dir>", "inbound or outbound")
|
|
149
|
+
.option("--since <iso8601>", "only calls created after this timestamp")
|
|
150
|
+
.option("--json", "machine-readable output")
|
|
151
|
+
.action(async (opts) => process.exit(await runCallList({
|
|
152
|
+
numberId: opts.numberId,
|
|
153
|
+
direction: opts.direction,
|
|
154
|
+
since: opts.since,
|
|
155
|
+
json: !!opts.json,
|
|
156
|
+
})));
|
|
157
|
+
call
|
|
158
|
+
.command("get <call-id>")
|
|
159
|
+
.description("Fetch a single call by id. GET /api/v1/calls/<id>.")
|
|
160
|
+
.option("--json", "machine-readable output")
|
|
161
|
+
.action(async (callId, opts) => process.exit(await runCallGet({ callId, json: !!opts.json })));
|
|
162
|
+
const localTarget = program
|
|
163
|
+
.command("local-target")
|
|
164
|
+
.description("Register local fan-out targets the listen daemon delivers events to.")
|
|
165
|
+
.enablePositionalOptions();
|
|
166
|
+
const localTargetAdd = localTarget
|
|
167
|
+
.command("add")
|
|
168
|
+
.description("Register a new local fan-out target (url or cmd).")
|
|
169
|
+
.enablePositionalOptions();
|
|
170
|
+
localTargetAdd
|
|
171
|
+
.command("url <url>")
|
|
172
|
+
.description("Register a loopback HTTP endpoint. The daemon POSTs each event JSON to <url>.")
|
|
173
|
+
.option("--secret <value>", "HMAC-SHA256 key. The daemon signs each request body and sends the hex digest.")
|
|
174
|
+
.option("--signature-header <name>", "HTTP header for the HMAC signature (defaults to X-Dial-Signature; only used with --secret)")
|
|
175
|
+
.option("--bearer <token>", "static bearer token, sent as `Authorization: Bearer <token>`")
|
|
176
|
+
.option("--timeout <seconds>", "per-attempt timeout (default 5)", (v) => parseInt(v, 10))
|
|
177
|
+
.option("--json", "machine-readable output")
|
|
178
|
+
.action(async (url, opts) => process.exit(await runLocalTargetAddUrl({
|
|
179
|
+
url,
|
|
180
|
+
secret: opts.secret,
|
|
181
|
+
signatureHeader: opts.signatureHeader,
|
|
182
|
+
bearer: opts.bearer,
|
|
183
|
+
timeoutSeconds: opts.timeout,
|
|
133
184
|
json: !!opts.json,
|
|
134
185
|
})));
|
|
186
|
+
localTargetAdd
|
|
187
|
+
.command("cmd <path> [args...]")
|
|
188
|
+
.description("Register an executable. The daemon spawns it per event with the event JSON as the final positional argument.")
|
|
189
|
+
.option("--timeout <seconds>", "per-attempt timeout (default 5)", (v) => parseInt(v, 10))
|
|
190
|
+
.option("--json", "machine-readable output")
|
|
191
|
+
.passThroughOptions(true)
|
|
192
|
+
.action(async (path, args, opts) => process.exit(await runLocalTargetAddCmd({
|
|
193
|
+
path,
|
|
194
|
+
args: args ?? [],
|
|
195
|
+
timeoutSeconds: opts.timeout,
|
|
196
|
+
json: !!opts.json,
|
|
197
|
+
})));
|
|
198
|
+
localTarget
|
|
199
|
+
.command("remove <id>")
|
|
200
|
+
.description("Unregister a target by id (URL for url targets, path for cmd targets).")
|
|
201
|
+
.option("--json", "machine-readable output")
|
|
202
|
+
.action(async (id, opts) => process.exit(await runLocalTargetRemove({ id, json: !!opts.json })));
|
|
203
|
+
localTarget
|
|
204
|
+
.command("list")
|
|
205
|
+
.description("List the local targets currently registered for fan-out.")
|
|
206
|
+
.option("--json", "machine-readable output")
|
|
207
|
+
.action(async (opts) => process.exit(await runLocalTargetList({ json: !!opts.json })));
|
|
135
208
|
program
|
|
136
209
|
.command("wait-for <event-type>")
|
|
137
210
|
.description("Wait for the next matching event in the listen log (e.g. call.ended, message.received).")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readAuth } from "../../lib/state.js";
|
|
2
|
+
import { apiGet } from "../../lib/api.js";
|
|
3
|
+
export async function runCallGet(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/calls/${encodeURIComponent(opts.callId)}`, auth.apiKey);
|
|
10
|
+
if (!res.ok) {
|
|
11
|
+
fail(opts.json, res.status === 404 ? "not_found" : "get_failed", res.error, { status: res.status });
|
|
12
|
+
return res.status === 404 ? 1 : 2;
|
|
13
|
+
}
|
|
14
|
+
const c = res.data.call;
|
|
15
|
+
if (opts.json) {
|
|
16
|
+
console.log(JSON.stringify({ ok: true, call: c }));
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
console.log(`id: ${c.id}`);
|
|
20
|
+
console.log(`direction: ${c.direction}`);
|
|
21
|
+
console.log(`from: ${c.from}`);
|
|
22
|
+
console.log(`to: ${c.to}`);
|
|
23
|
+
console.log(`status: ${c.status}`);
|
|
24
|
+
console.log(`duration: ${c.duration}s`);
|
|
25
|
+
console.log(`created: ${c.createdAt}`);
|
|
26
|
+
if (c.transcript) {
|
|
27
|
+
console.log(`transcript:`);
|
|
28
|
+
console.log(c.transcript);
|
|
29
|
+
}
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
function fail(json, code, message, extra) {
|
|
33
|
+
if (json)
|
|
34
|
+
console.log(JSON.stringify({ ok: false, code, message, ...extra }));
|
|
35
|
+
else
|
|
36
|
+
console.error(message);
|
|
37
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readAuth } from "../../lib/state.js";
|
|
2
|
+
import { apiGet } from "../../lib/api.js";
|
|
3
|
+
export async function runCallList(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 params = new URLSearchParams();
|
|
10
|
+
if (opts.numberId)
|
|
11
|
+
params.set("numberId", opts.numberId);
|
|
12
|
+
if (opts.direction)
|
|
13
|
+
params.set("direction", opts.direction);
|
|
14
|
+
if (opts.since)
|
|
15
|
+
params.set("since", opts.since);
|
|
16
|
+
const qs = params.toString();
|
|
17
|
+
const path = qs ? `/api/v1/calls?${qs}` : "/api/v1/calls";
|
|
18
|
+
const res = await apiGet(path, auth.apiKey);
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
fail(opts.json, "list_failed", res.error, { status: res.status });
|
|
21
|
+
return 2;
|
|
22
|
+
}
|
|
23
|
+
const calls = res.data.calls ?? [];
|
|
24
|
+
if (opts.json) {
|
|
25
|
+
console.log(JSON.stringify({ ok: true, calls }));
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
if (calls.length === 0) {
|
|
29
|
+
console.log("no calls.");
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
for (const c of calls) {
|
|
33
|
+
console.log(`${c.createdAt} ${c.direction.padEnd(8)} ${c.from} -> ${c.to} ${c.status} ${c.duration}s id=${c.id}`);
|
|
34
|
+
}
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
function fail(json, code, message, extra) {
|
|
38
|
+
if (json)
|
|
39
|
+
console.log(JSON.stringify({ ok: false, code, message, ...extra }));
|
|
40
|
+
else
|
|
41
|
+
console.error(message);
|
|
42
|
+
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { readAuth } from "
|
|
2
|
-
import { apiPost } from "
|
|
3
|
-
export async function
|
|
1
|
+
import { readAuth } from "../../lib/state.js";
|
|
2
|
+
import { apiPost } from "../../lib/api.js";
|
|
3
|
+
export async function runCallSend(opts) {
|
|
4
4
|
const auth = readAuth();
|
|
5
5
|
if (!auth) {
|
|
6
6
|
fail(opts.json, "not_signed_in", "Not signed in. Run `dial signup` and `dial onboard` first.");
|
|
7
7
|
return 1;
|
|
8
8
|
}
|
|
9
|
-
const fromNumberId = opts.fromNumberId ?? auth.
|
|
9
|
+
const fromNumberId = opts.fromNumberId ?? auth.phoneNumberId;
|
|
10
10
|
if (!fromNumberId) {
|
|
11
|
-
fail(opts.json, "no_from_number", "No default
|
|
11
|
+
fail(opts.json, "no_from_number", "No default phoneNumberId in auth. Pass --from-number-id <id>.");
|
|
12
12
|
return 1;
|
|
13
13
|
}
|
|
14
14
|
const res = await apiPost("/api/v1/calls", {
|
|
15
15
|
to: opts.to,
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
fromNumberId,
|
|
17
|
+
systemPrompt: opts.systemPrompt,
|
|
18
18
|
language: opts.language,
|
|
19
|
-
}, auth.
|
|
19
|
+
}, auth.apiKey);
|
|
20
20
|
if (!res.ok) {
|
|
21
21
|
fail(opts.json, "call_failed", res.error, { status: res.status });
|
|
22
22
|
return 2;
|
|
@@ -28,14 +28,14 @@ export async function runCall(opts) {
|
|
|
28
28
|
console.log(JSON.stringify({
|
|
29
29
|
ok: true,
|
|
30
30
|
call: c,
|
|
31
|
-
|
|
31
|
+
agentHint: {
|
|
32
32
|
action: "ask_user_about_waiting",
|
|
33
33
|
kind: "post_call_followup",
|
|
34
34
|
question: followUpQuestion,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
waitCommand: waitCmd,
|
|
36
|
+
onYes: waitCmd,
|
|
37
|
+
onNo: null,
|
|
38
|
+
useStructuredToolIfAvailable: true,
|
|
39
39
|
},
|
|
40
40
|
}));
|
|
41
41
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -9,19 +9,19 @@ async function buildReport() {
|
|
|
9
9
|
const auth = readAuth();
|
|
10
10
|
const pending = readPendingSignup();
|
|
11
11
|
let keyValid = null;
|
|
12
|
-
if (auth?.
|
|
13
|
-
const res = await apiGet("/api/v1/account", auth.
|
|
12
|
+
if (auth?.apiKey) {
|
|
13
|
+
const res = await apiGet("/api/v1/account", auth.apiKey);
|
|
14
14
|
keyValid = res.ok;
|
|
15
15
|
}
|
|
16
|
-
const pendingAgeMs = pending ? Date.now() - Date.parse(pending.
|
|
16
|
+
const pendingAgeMs = pending ? Date.now() - Date.parse(pending.createdAt) : null;
|
|
17
17
|
const pendingExpired = pendingAgeMs == null ? null : pendingAgeMs > OTP_EXPIRY_MS;
|
|
18
|
-
let listenState = { installed: false, running: false,
|
|
18
|
+
let listenState = { installed: false, running: false, lastEventAt: null };
|
|
19
19
|
try {
|
|
20
20
|
const s = supervisorStatus();
|
|
21
21
|
listenState = {
|
|
22
22
|
installed: s.installed,
|
|
23
23
|
running: s.running,
|
|
24
|
-
|
|
24
|
+
lastEventAt: lastEventAtFromLog(paths().listenLog),
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
catch {
|
|
@@ -47,43 +47,43 @@ async function buildReport() {
|
|
|
47
47
|
}
|
|
48
48
|
return {
|
|
49
49
|
cli: { version: VERSION, node: process.versions.node },
|
|
50
|
-
backend: { url: baseUrl(), reachable: ping.reachable,
|
|
50
|
+
backend: { url: baseUrl(), reachable: ping.reachable, latencyMs: ping.latencyMs },
|
|
51
51
|
auth: {
|
|
52
|
-
|
|
52
|
+
signedIn: Boolean(auth),
|
|
53
53
|
email: auth?.email ?? null,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
accountId: auth?.accountId ?? null,
|
|
55
|
+
apiKeyPresent: Boolean(auth?.apiKey),
|
|
56
|
+
apiKeyFingerprint: auth?.apiKey ? auth.apiKey.slice(-4) : null,
|
|
57
|
+
keyValid,
|
|
58
58
|
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
pendingOtp: {
|
|
60
|
+
verificationId: pending?.verificationId ?? null,
|
|
61
|
+
ageSeconds: pendingAgeMs == null ? null : Math.round(pendingAgeMs / 1000),
|
|
62
62
|
expired: pendingExpired,
|
|
63
63
|
},
|
|
64
64
|
listen: listenState,
|
|
65
|
-
|
|
65
|
+
nextStep,
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
function humanRender(r) {
|
|
69
69
|
const lines = [];
|
|
70
70
|
lines.push(`dial ${r.cli.version} (node ${r.cli.node})`);
|
|
71
|
-
lines.push(`backend: ${r.backend.url} ${r.backend.reachable ? `reachable${r.backend.
|
|
72
|
-
if (r.auth.
|
|
73
|
-
lines.push(`auth: signed in as ${r.auth.email} (account ${r.auth.
|
|
71
|
+
lines.push(`backend: ${r.backend.url} ${r.backend.reachable ? `reachable${r.backend.latencyMs != null ? ` (${r.backend.latencyMs}ms)` : ""}` : "UNREACHABLE"}`);
|
|
72
|
+
if (r.auth.signedIn) {
|
|
73
|
+
lines.push(`auth: signed in as ${r.auth.email} (account ${r.auth.accountId}, key sk_live_***${r.auth.apiKeyFingerprint})${r.auth.keyValid === false ? " [key rejected by backend]" : ""}`);
|
|
74
74
|
}
|
|
75
75
|
else {
|
|
76
76
|
lines.push(`auth: not signed in`);
|
|
77
77
|
}
|
|
78
|
-
if (r.
|
|
79
|
-
lines.push(`pending otp: ${r.
|
|
78
|
+
if (r.pendingOtp.verificationId) {
|
|
79
|
+
lines.push(`pending otp: ${r.pendingOtp.ageSeconds}s old${r.pendingOtp.expired ? " (EXPIRED)" : ""}`);
|
|
80
80
|
}
|
|
81
81
|
else {
|
|
82
82
|
lines.push(`pending otp: none`);
|
|
83
83
|
}
|
|
84
|
-
lines.push(`listen: ${r.listen.installed ? (r.listen.running ? "running" : "installed (stopped)") : "not installed"}${r.listen.
|
|
84
|
+
lines.push(`listen: ${r.listen.installed ? (r.listen.running ? "running" : "installed (stopped)") : "not installed"}${r.listen.lastEventAt ? `, last event ${r.listen.lastEventAt}` : ""}`);
|
|
85
85
|
lines.push("");
|
|
86
|
-
lines.push(`next: ${r.
|
|
86
|
+
lines.push(`next: ${r.nextStep}`);
|
|
87
87
|
return lines.join("\n");
|
|
88
88
|
}
|
|
89
89
|
export async function runDoctor(opts) {
|
|
@@ -15,8 +15,8 @@ export async function runListen() {
|
|
|
15
15
|
}
|
|
16
16
|
return 1;
|
|
17
17
|
}
|
|
18
|
-
appendJsonl(paths().listenLog, { ts: new Date().toISOString(), lifecycle: "startup", ok: true,
|
|
19
|
-
const ctrl = startWorker(auth.
|
|
18
|
+
appendJsonl(paths().listenLog, { ts: new Date().toISOString(), lifecycle: "startup", ok: true, accountId: auth.accountId });
|
|
19
|
+
const ctrl = startWorker(auth.apiKey, auth.accountId);
|
|
20
20
|
const onSignal = async (sig) => {
|
|
21
21
|
appendJsonl(paths().listenLog, { ts: new Date().toISOString(), lifecycle: "shutdown", signal: sig });
|
|
22
22
|
await ctrl.stop();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { addTarget, LocalTargetError } from "../../lib/local-targets.js";
|
|
2
|
+
export async function runLocalTargetAddCmd(opts) {
|
|
3
|
+
try {
|
|
4
|
+
const { added } = addTarget({
|
|
5
|
+
kind: "cmd",
|
|
6
|
+
path: opts.path,
|
|
7
|
+
args: opts.args,
|
|
8
|
+
timeoutSeconds: opts.timeoutSeconds,
|
|
9
|
+
});
|
|
10
|
+
if (opts.json) {
|
|
11
|
+
console.log(JSON.stringify({ ok: true, added, path: opts.path, args: opts.args }));
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
console.log(added ? `added cmd target: ${opts.path}` : `cmd target already registered: ${opts.path}`);
|
|
15
|
+
}
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
const code = err instanceof LocalTargetError ? err.code : "add_failed";
|
|
20
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
21
|
+
if (opts.json)
|
|
22
|
+
console.log(JSON.stringify({ ok: false, code, message }));
|
|
23
|
+
else
|
|
24
|
+
console.error(`add cmd failed: ${message}`);
|
|
25
|
+
return 2;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { addTarget, LocalTargetError, DEFAULT_SIGNATURE_HEADER } from "../../lib/local-targets.js";
|
|
2
|
+
export async function runLocalTargetAddUrl(opts) {
|
|
3
|
+
try {
|
|
4
|
+
const { added } = addTarget({
|
|
5
|
+
kind: "url",
|
|
6
|
+
url: opts.url,
|
|
7
|
+
secret: opts.secret,
|
|
8
|
+
signatureHeader: opts.signatureHeader ?? (opts.secret ? DEFAULT_SIGNATURE_HEADER : undefined),
|
|
9
|
+
bearer: opts.bearer,
|
|
10
|
+
timeoutSeconds: opts.timeoutSeconds,
|
|
11
|
+
});
|
|
12
|
+
if (opts.json) {
|
|
13
|
+
console.log(JSON.stringify({ ok: true, added, url: opts.url }));
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
console.log(added ? `added url target: ${opts.url}` : `url target already registered: ${opts.url}`);
|
|
17
|
+
}
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
const code = err instanceof LocalTargetError ? err.code : "add_failed";
|
|
22
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23
|
+
if (opts.json)
|
|
24
|
+
console.log(JSON.stringify({ ok: false, code, message }));
|
|
25
|
+
else
|
|
26
|
+
console.error(`add url failed: ${message}`);
|
|
27
|
+
return 2;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { listTargets, targetId } from "../../lib/local-targets.js";
|
|
2
|
+
export async function runLocalTargetList(opts) {
|
|
3
|
+
const targets = listTargets();
|
|
4
|
+
if (opts.json) {
|
|
5
|
+
console.log(JSON.stringify({ ok: true, targets }));
|
|
6
|
+
return 0;
|
|
7
|
+
}
|
|
8
|
+
if (targets.length === 0) {
|
|
9
|
+
console.log("no local targets registered. add one with `dial local-target add url <url>` or `dial local-target add cmd <path>`.");
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
12
|
+
for (const t of targets) {
|
|
13
|
+
console.log(`${t.kind.padEnd(4)} ${targetId(t)}`);
|
|
14
|
+
}
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { removeTarget } from "../../lib/local-targets.js";
|
|
2
|
+
export async function runLocalTargetRemove(opts) {
|
|
3
|
+
const { removed } = removeTarget(opts.id);
|
|
4
|
+
if (opts.json) {
|
|
5
|
+
console.log(JSON.stringify({ ok: removed, removed, id: opts.id }));
|
|
6
|
+
}
|
|
7
|
+
else if (removed) {
|
|
8
|
+
console.log(`removed: ${opts.id}`);
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
console.error(`not found: ${opts.id}`);
|
|
12
|
+
}
|
|
13
|
+
return removed ? 0 : 1;
|
|
14
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readAuth } from "../../lib/state.js";
|
|
2
|
+
import { apiGet } from "../../lib/api.js";
|
|
3
|
+
export async function runMessageList(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 params = new URLSearchParams();
|
|
10
|
+
if (opts.numberId)
|
|
11
|
+
params.set("numberId", opts.numberId);
|
|
12
|
+
if (opts.direction)
|
|
13
|
+
params.set("direction", opts.direction);
|
|
14
|
+
if (opts.since)
|
|
15
|
+
params.set("since", opts.since);
|
|
16
|
+
const qs = params.toString();
|
|
17
|
+
const path = qs ? `/api/v1/messages?${qs}` : "/api/v1/messages";
|
|
18
|
+
const res = await apiGet(path, auth.apiKey);
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
fail(opts.json, "list_failed", res.error, { status: res.status });
|
|
21
|
+
return 2;
|
|
22
|
+
}
|
|
23
|
+
const messages = res.data.messages ?? [];
|
|
24
|
+
if (opts.json) {
|
|
25
|
+
console.log(JSON.stringify({ ok: true, messages }));
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
if (messages.length === 0) {
|
|
29
|
+
console.log("no messages.");
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
for (const m of messages) {
|
|
33
|
+
console.log(`${m.createdAt} ${m.direction.padEnd(8)} ${m.from} -> ${m.to} ${m.body}`);
|
|
34
|
+
}
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
function fail(json, code, message, extra) {
|
|
38
|
+
if (json)
|
|
39
|
+
console.log(JSON.stringify({ ok: false, code, message, ...extra }));
|
|
40
|
+
else
|
|
41
|
+
console.error(message);
|
|
42
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { readAuth } from "
|
|
2
|
-
import { apiPost } from "
|
|
3
|
-
export async function
|
|
1
|
+
import { readAuth } from "../../lib/state.js";
|
|
2
|
+
import { apiPost } from "../../lib/api.js";
|
|
3
|
+
export async function runMessageSend(opts) {
|
|
4
4
|
const auth = readAuth();
|
|
5
5
|
if (!auth) {
|
|
6
6
|
fail(opts.json, "not_signed_in", "Not signed in. Run `dial signup` and `dial onboard` first.");
|
|
7
7
|
return 1;
|
|
8
8
|
}
|
|
9
|
-
const fromNumberId = opts.fromNumberId ?? auth.
|
|
9
|
+
const fromNumberId = opts.fromNumberId ?? auth.phoneNumberId;
|
|
10
10
|
if (!fromNumberId) {
|
|
11
|
-
fail(opts.json, "no_from_number", "No default
|
|
11
|
+
fail(opts.json, "no_from_number", "No default phoneNumberId in auth. Pass --from-number-id <id>.");
|
|
12
12
|
return 1;
|
|
13
13
|
}
|
|
14
|
-
const res = await apiPost("/api/v1/messages", { to: opts.to, body: opts.body, channel: "sms",
|
|
14
|
+
const res = await apiPost("/api/v1/messages", { to: opts.to, body: opts.body, channel: "sms", fromNumberId }, auth.apiKey);
|
|
15
15
|
if (!res.ok) {
|
|
16
16
|
fail(opts.json, "send_failed", res.error, { status: res.status });
|
|
17
17
|
return 2;
|