@getdial/cli 0.6.0 → 0.7.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 +2 -32
- package/dist/commands/call.js +10 -10
- package/dist/commands/doctor.js +22 -22
- package/dist/commands/listen/index.js +2 -2
- package/dist/commands/numbers/list.js +5 -6
- package/dist/commands/numbers/provision.js +2 -4
- package/dist/commands/onboard.js +24 -24
- package/dist/commands/send.js +3 -3
- package/dist/commands/signup.js +5 -5
- package/dist/commands/wait-for.js +3 -3
- package/dist/lib/pubnub.js +2 -2
- package/dist/lib/state.js +6 -6
- package/package.json +1 -1
- 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,9 +9,6 @@ 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 { runTest2faStart } from "./commands/test-2fa/start.js";
|
|
13
|
-
import { runTest2faVerify } from "./commands/test-2fa/verify.js";
|
|
14
|
-
import { runTest2faRun } from "./commands/test-2fa/run.js";
|
|
15
12
|
import { runNumbersList } from "./commands/numbers/list.js";
|
|
16
13
|
import { runNumbersProvision } from "./commands/numbers/provision.js";
|
|
17
14
|
import { runSend } from "./commands/send.js";
|
|
@@ -58,31 +55,6 @@ listen
|
|
|
58
55
|
.description("Report listen daemon state and last events.")
|
|
59
56
|
.option("--json", "machine-readable output")
|
|
60
57
|
.action(async (opts) => process.exit(await runListenStatus({ json: !!opts.json })));
|
|
61
|
-
const test2fa = program
|
|
62
|
-
.command("test-2fa")
|
|
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
58
|
const numbers = program
|
|
87
59
|
.command("numbers")
|
|
88
60
|
.description("Manage your Dial phone numbers.");
|
|
@@ -96,12 +68,10 @@ numbers
|
|
|
96
68
|
.description("Provision an additional phone number. POST /api/v1/numbers.")
|
|
97
69
|
.option("--country <iso2>", "ISO-3166-1 alpha-2 country code (defaults to US server-side)")
|
|
98
70
|
.option("--area-code <code>", "preferred area code (US/CA)")
|
|
99
|
-
.option("--agent-id <id>", "attach to an existing agent")
|
|
100
71
|
.option("--json", "machine-readable output")
|
|
101
72
|
.action(async (opts) => process.exit(await runNumbersProvision({
|
|
102
73
|
country: opts.country,
|
|
103
74
|
areaCode: opts.areaCode,
|
|
104
|
-
agentId: opts.agentId,
|
|
105
75
|
json: !!opts.json,
|
|
106
76
|
})));
|
|
107
77
|
program
|
|
@@ -109,7 +79,7 @@ program
|
|
|
109
79
|
.description("Send an SMS. POST /api/v1/messages.")
|
|
110
80
|
.requiredOption("--to <e164>", "destination phone number, E.164 (e.g. +14155551234)")
|
|
111
81
|
.requiredOption("--body <text>", "message body")
|
|
112
|
-
.option("--from-number-id <id>", "
|
|
82
|
+
.option("--from-number-id <id>", "phoneNumberId to send from (defaults to onboard's number)")
|
|
113
83
|
.option("--json", "machine-readable output")
|
|
114
84
|
.action(async (opts) => process.exit(await runSend({
|
|
115
85
|
to: opts.to,
|
|
@@ -123,7 +93,7 @@ program
|
|
|
123
93
|
.requiredOption("--to <e164>", "destination phone number, E.164 (e.g. +14155551234)")
|
|
124
94
|
.requiredOption("--system-prompt <text>", "system prompt for the agent that will speak")
|
|
125
95
|
.option("--language <bcp47>", "BCP-47 language tag for the call", "en-US")
|
|
126
|
-
.option("--from-number-id <id>", "
|
|
96
|
+
.option("--from-number-id <id>", "phoneNumberId to call from (defaults to onboard's number)")
|
|
127
97
|
.option("--json", "machine-readable output")
|
|
128
98
|
.action(async (opts) => process.exit(await runCall({
|
|
129
99
|
to: opts.to,
|
package/dist/commands/call.js
CHANGED
|
@@ -6,17 +6,17 @@ export async function runCall(opts) {
|
|
|
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();
|
|
@@ -6,24 +6,23 @@ export async function runNumbersList(opts) {
|
|
|
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 res = await apiGet("/api/v1/numbers", auth.
|
|
9
|
+
const res = await apiGet("/api/v1/numbers", auth.apiKey);
|
|
10
10
|
if (!res.ok) {
|
|
11
11
|
fail(opts.json, "list_failed", res.error, { status: res.status });
|
|
12
12
|
return 2;
|
|
13
13
|
}
|
|
14
14
|
const numbers = res.data.numbers ?? [];
|
|
15
15
|
if (opts.json) {
|
|
16
|
-
console.log(JSON.stringify({ ok: true, numbers,
|
|
16
|
+
console.log(JSON.stringify({ ok: true, numbers, defaultNumberId: auth.phoneNumberId ?? null }));
|
|
17
17
|
return 0;
|
|
18
18
|
}
|
|
19
19
|
if (numbers.length === 0) {
|
|
20
|
-
console.log("no phone numbers. provision one with `dial
|
|
20
|
+
console.log("no phone numbers. provision one with `dial number purchase`.");
|
|
21
21
|
return 0;
|
|
22
22
|
}
|
|
23
23
|
for (const n of numbers) {
|
|
24
|
-
const tag = n.id === auth.
|
|
25
|
-
|
|
26
|
-
console.log(`${n.number} id=${n.id} ${n.country}${agent}${tag}`);
|
|
24
|
+
const tag = n.id === auth.phoneNumberId ? " (default)" : "";
|
|
25
|
+
console.log(`${n.number} id=${n.id} ${n.country}${tag}`);
|
|
27
26
|
}
|
|
28
27
|
return 0;
|
|
29
28
|
}
|
|
@@ -10,10 +10,8 @@ export async function runNumbersProvision(opts) {
|
|
|
10
10
|
if (opts.country)
|
|
11
11
|
body.country = opts.country;
|
|
12
12
|
if (opts.areaCode)
|
|
13
|
-
body.
|
|
14
|
-
|
|
15
|
-
body.agent_id = opts.agentId;
|
|
16
|
-
const res = await apiPost("/api/v1/numbers", body, auth.api_key);
|
|
13
|
+
body.areaCode = opts.areaCode;
|
|
14
|
+
const res = await apiPost("/api/v1/numbers", body, auth.apiKey);
|
|
17
15
|
if (!res.ok) {
|
|
18
16
|
fail(opts.json, "provision_failed", res.error, { status: res.status });
|
|
19
17
|
return 2;
|
package/dist/commands/onboard.js
CHANGED
|
@@ -22,10 +22,10 @@ export async function runOnboard(opts) {
|
|
|
22
22
|
console.error("No pending signup. Run `dial signup <email>` first, or pass --verification-id.");
|
|
23
23
|
return 1;
|
|
24
24
|
}
|
|
25
|
-
verificationId = pending.
|
|
25
|
+
verificationId = pending.verificationId;
|
|
26
26
|
email = pending.email;
|
|
27
27
|
}
|
|
28
|
-
const res = await apiPost("/api/v1/auth/verify", {
|
|
28
|
+
const res = await apiPost("/api/v1/auth/verify", { verificationId, code: opts.code });
|
|
29
29
|
if (!res.ok) {
|
|
30
30
|
if (opts.json)
|
|
31
31
|
console.log(JSON.stringify({ ok: false, code: "verify_failed", status: res.status, error: res.error }));
|
|
@@ -33,20 +33,20 @@ export async function runOnboard(opts) {
|
|
|
33
33
|
console.error(`onboard failed: ${res.error}`);
|
|
34
34
|
return res.status === 401 ? 1 : 2;
|
|
35
35
|
}
|
|
36
|
-
const apiKey = res.data.
|
|
37
|
-
if (!apiKey || !res.data.
|
|
36
|
+
const apiKey = res.data.apiKey ?? null;
|
|
37
|
+
if (!apiKey || !res.data.accountId) {
|
|
38
38
|
if (opts.json)
|
|
39
|
-
console.log(JSON.stringify({ ok: false, code: "missing_api_key", error: "backend returned no
|
|
39
|
+
console.log(JSON.stringify({ ok: false, code: "missing_api_key", error: "backend returned no apiKey" }));
|
|
40
40
|
else
|
|
41
|
-
console.error("onboard failed: backend returned no
|
|
41
|
+
console.error("onboard failed: backend returned no apiKey");
|
|
42
42
|
return 2;
|
|
43
43
|
}
|
|
44
44
|
writeAuth({
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
apiKey,
|
|
46
|
+
accountId: res.data.accountId,
|
|
47
47
|
email: email ?? "",
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
phoneNumber: res.data.phoneNumber ?? null,
|
|
49
|
+
phoneNumberId: res.data.phoneNumberId ?? null,
|
|
50
50
|
});
|
|
51
51
|
clearPendingSignup();
|
|
52
52
|
const authFile = paths().authFile;
|
|
@@ -54,31 +54,31 @@ export async function runOnboard(opts) {
|
|
|
54
54
|
if (opts.json) {
|
|
55
55
|
console.log(JSON.stringify({
|
|
56
56
|
ok: true,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
listen: { installed: false,
|
|
64
|
-
|
|
57
|
+
apiKeyFingerprint: apiKey.slice(-4),
|
|
58
|
+
apiKeyMasked: masked,
|
|
59
|
+
apiKeyPath: authFile,
|
|
60
|
+
accountId: res.data.accountId,
|
|
61
|
+
phoneNumber: res.data.phoneNumber ?? null,
|
|
62
|
+
phoneNumberId: res.data.phoneNumberId ?? null,
|
|
63
|
+
listen: { installed: false, autoInstalled: false },
|
|
64
|
+
agentHint: {
|
|
65
65
|
action: "ask_user",
|
|
66
66
|
kind: "onboarding_finalization",
|
|
67
67
|
question: LISTEN_QUESTION,
|
|
68
68
|
rationale: LISTEN_RATIONALE,
|
|
69
69
|
presentation: PRESENTATION_HINT,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
finalizationNote: FINALIZATION_HINT,
|
|
71
|
+
useStructuredToolIfAvailable: true,
|
|
72
|
+
onYes: "dial listen install",
|
|
73
|
+
onNo: null,
|
|
74
74
|
},
|
|
75
75
|
}));
|
|
76
76
|
}
|
|
77
77
|
else {
|
|
78
78
|
console.log("onboarded.");
|
|
79
79
|
console.log(` api key: ${masked} (saved to ${authFile})`);
|
|
80
|
-
if (res.data.
|
|
81
|
-
console.log(` phone number: ${res.data.
|
|
80
|
+
if (res.data.phoneNumber)
|
|
81
|
+
console.log(` phone number: ${res.data.phoneNumber}`);
|
|
82
82
|
console.log(``);
|
|
83
83
|
console.log(`Onboarding finalization (required):`);
|
|
84
84
|
console.log(``);
|
package/dist/commands/send.js
CHANGED
|
@@ -6,12 +6,12 @@ export async function runSend(opts) {
|
|
|
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;
|
package/dist/commands/signup.js
CHANGED
|
@@ -4,11 +4,11 @@ const PENDING_FRESH_MS = 10 * 60 * 1000;
|
|
|
4
4
|
export async function runSignup(email, opts) {
|
|
5
5
|
const existing = readPendingSignup();
|
|
6
6
|
if (existing && !opts.force) {
|
|
7
|
-
const age = Date.now() - Date.parse(existing.
|
|
7
|
+
const age = Date.now() - Date.parse(existing.createdAt);
|
|
8
8
|
if (Number.isFinite(age) && age < PENDING_FRESH_MS) {
|
|
9
9
|
const ageS = Math.round(age / 1000);
|
|
10
10
|
if (opts.json) {
|
|
11
|
-
console.log(JSON.stringify({ ok: false, code: "pending_exists",
|
|
11
|
+
console.log(JSON.stringify({ ok: false, code: "pending_exists", verificationId: existing.verificationId, email: existing.email, ageSeconds: ageS }));
|
|
12
12
|
}
|
|
13
13
|
else {
|
|
14
14
|
console.error(`A pending OTP for ${existing.email} is still fresh (${ageS}s old). Use \`dial onboard --code <code>\` or re-run with --force to start a new one.`);
|
|
@@ -24,13 +24,13 @@ export async function runSignup(email, opts) {
|
|
|
24
24
|
console.error(`signup failed: ${res.error}`);
|
|
25
25
|
return 2;
|
|
26
26
|
}
|
|
27
|
-
writePendingSignup({
|
|
27
|
+
writePendingSignup({ verificationId: res.data.verificationId, email, createdAt: new Date().toISOString() });
|
|
28
28
|
if (opts.json) {
|
|
29
|
-
console.log(JSON.stringify({ ok: true,
|
|
29
|
+
console.log(JSON.stringify({ ok: true, verificationId: res.data.verificationId, email }));
|
|
30
30
|
}
|
|
31
31
|
else {
|
|
32
32
|
console.log(`OTP sent to ${email}.`);
|
|
33
|
-
console.log(`Run \`dial onboard --code <code>\` once you have it (
|
|
33
|
+
console.log(`Run \`dial onboard --code <code>\` once you have it (verificationId is stored locally).`);
|
|
34
34
|
}
|
|
35
35
|
return 0;
|
|
36
36
|
}
|
|
@@ -61,11 +61,11 @@ async function waitFromApi(spec, opts) {
|
|
|
61
61
|
const remainingSec = Math.max(1, Math.ceil((deadline - Date.now()) / 1000));
|
|
62
62
|
const timeout = Math.min(PER_POLL_SECONDS, remainingSec);
|
|
63
63
|
const res = await apiPost("/api/v1/events/wait", {
|
|
64
|
-
|
|
64
|
+
eventType: spec.eventType,
|
|
65
65
|
filters: Object.keys(filters).length > 0 ? filters : undefined,
|
|
66
|
-
|
|
66
|
+
regexFilters: Object.keys(regexFilters).length > 0 ? regexFilters : undefined,
|
|
67
67
|
timeout,
|
|
68
|
-
}, auth.
|
|
68
|
+
}, auth.apiKey);
|
|
69
69
|
if (res.ok && res.data?.event) {
|
|
70
70
|
process.stdout.write(JSON.stringify(res.data.event) + "\n");
|
|
71
71
|
return 0;
|
package/dist/lib/pubnub.js
CHANGED
|
@@ -45,7 +45,7 @@ export function startWorker(apiKey, accountId) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
function scheduleRefresh(creds) {
|
|
48
|
-
const ms = Math.max(60, Math.floor(creds.
|
|
48
|
+
const ms = Math.max(60, Math.floor(creds.ttlSeconds / 2)) * 1000;
|
|
49
49
|
refreshTimer = setTimeout(() => refresh(creds), ms);
|
|
50
50
|
}
|
|
51
51
|
async function stop() {
|
|
@@ -81,7 +81,7 @@ export function startWorker(apiKey, accountId) {
|
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
83
|
pn = new PubNub({
|
|
84
|
-
subscribeKey: creds.
|
|
84
|
+
subscribeKey: creds.subscribeKey,
|
|
85
85
|
userId: `dial-cli-${accountId}`,
|
|
86
86
|
ssl: true,
|
|
87
87
|
authKey: creds.token,
|
package/dist/lib/state.js
CHANGED
|
@@ -4,16 +4,16 @@ import { z } from "zod";
|
|
|
4
4
|
import { paths } from "./paths.js";
|
|
5
5
|
import { logger } from "./log.js";
|
|
6
6
|
export const AuthSchema = z.object({
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
apiKey: z.string(),
|
|
8
|
+
accountId: z.string(),
|
|
9
9
|
email: z.string(),
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
phoneNumber: z.string().nullable(),
|
|
11
|
+
phoneNumberId: z.string().nullable(),
|
|
12
12
|
});
|
|
13
13
|
export const PendingSignupSchema = z.object({
|
|
14
|
-
|
|
14
|
+
verificationId: z.string(),
|
|
15
15
|
email: z.string(),
|
|
16
|
-
|
|
16
|
+
createdAt: z.string(),
|
|
17
17
|
});
|
|
18
18
|
const CHMOD_UNSUPPORTED_CODES = new Set(["ENOTSUP", "EOPNOTSUPP", "EPERM"]);
|
|
19
19
|
function ensureDir(path) {
|
package/package.json
CHANGED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { readAuth } from "../../lib/state.js";
|
|
2
|
-
import { apiPost } from "../../lib/api.js";
|
|
3
|
-
import { paths } from "../../lib/paths.js";
|
|
4
|
-
import { supervisorStatus } from "../../lib/supervisor/index.js";
|
|
5
|
-
import { currentSize, tailUntilMatch } from "../../lib/log-tail.js";
|
|
6
|
-
import { parseRegexArg } from "../../lib/event-filter.js";
|
|
7
|
-
export async function runTest2faRun(opts) {
|
|
8
|
-
const auth = readAuth();
|
|
9
|
-
if (!auth) {
|
|
10
|
-
return fail(opts.json, "not_signed_in", "Not signed in. Run `dial signup` and `dial onboard` first.", 1);
|
|
11
|
-
}
|
|
12
|
-
const supervisor = supervisorStatus();
|
|
13
|
-
if (!supervisor.installed || !supervisor.running) {
|
|
14
|
-
return fail(opts.json, "listen_not_running", `Listen daemon is not running (installed=${supervisor.installed}, running=${supervisor.running}). Run \`dial listen install\`.`, 3);
|
|
15
|
-
}
|
|
16
|
-
const numberId = opts.numberId ?? auth.phone_number_id;
|
|
17
|
-
if (!numberId) {
|
|
18
|
-
return fail(opts.json, "no_number", "No default phone_number_id in auth. Pass --number-id <id>.", 1);
|
|
19
|
-
}
|
|
20
|
-
// Start listening BEFORE triggering the send so we don't miss the event.
|
|
21
|
-
const logFile = paths().listenLog;
|
|
22
|
-
const startOffset = currentSize(logFile);
|
|
23
|
-
const startedAt = Date.now();
|
|
24
|
-
const startRes = await apiPost("/api/v1/test/2fa", { phone_number_id: numberId }, auth.api_key);
|
|
25
|
-
if (!startRes.ok) {
|
|
26
|
-
return fail(opts.json, "start_failed", startRes.error, 2, { status: startRes.status });
|
|
27
|
-
}
|
|
28
|
-
// The test backend sends "Your Dial verification code is NNNNNN" — match the body.
|
|
29
|
-
const spec = {
|
|
30
|
-
eventType: "message.received",
|
|
31
|
-
fields: [{ name: "channel", value: "sms" }],
|
|
32
|
-
regexes: [parseRegexArg("body=/Your Dial verification code is \\d{6}/")],
|
|
33
|
-
};
|
|
34
|
-
const hit = await tailUntilMatch(logFile, spec, startOffset, opts.timeoutSeconds * 1000);
|
|
35
|
-
if (!hit) {
|
|
36
|
-
return fail(opts.json, "sms_timeout", `Timed out after ${opts.timeoutSeconds}s waiting for the SMS event in the listen log.`, 2, { session_id: startRes.data.session_id });
|
|
37
|
-
}
|
|
38
|
-
const body = String(hit.obj.body ?? "");
|
|
39
|
-
const codeMatch = /\b\d{6}\b/.exec(body);
|
|
40
|
-
if (!codeMatch) {
|
|
41
|
-
return fail(opts.json, "no_code_in_body", `Could not extract 6-digit code from SMS body: ${body}`, 2, {
|
|
42
|
-
session_id: startRes.data.session_id,
|
|
43
|
-
body,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
const code = codeMatch[0];
|
|
47
|
-
const verifyRes = await apiPost(`/api/v1/test/2fa/${encodeURIComponent(startRes.data.session_id)}/verify`, { code }, auth.api_key);
|
|
48
|
-
if (!verifyRes.ok) {
|
|
49
|
-
return fail(opts.json, "verify_failed", verifyRes.error, 2, {
|
|
50
|
-
status: verifyRes.status,
|
|
51
|
-
session_id: startRes.data.session_id,
|
|
52
|
-
code,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
const totalMs = Date.now() - startedAt;
|
|
56
|
-
if (opts.json) {
|
|
57
|
-
console.log(JSON.stringify({
|
|
58
|
-
ok: true,
|
|
59
|
-
verified: verifyRes.data.verified,
|
|
60
|
-
session_id: startRes.data.session_id,
|
|
61
|
-
code,
|
|
62
|
-
number_id: numberId,
|
|
63
|
-
total_ms: totalMs,
|
|
64
|
-
}));
|
|
65
|
-
}
|
|
66
|
-
else if (verifyRes.data.verified) {
|
|
67
|
-
console.log(`verified.`);
|
|
68
|
-
console.log(` session: ${startRes.data.session_id}`);
|
|
69
|
-
console.log(` code: ${code}`);
|
|
70
|
-
console.log(` total time: ${totalMs}ms`);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
console.error(`Backend rejected the extracted code "${code}" (verified=false).`);
|
|
74
|
-
return 2;
|
|
75
|
-
}
|
|
76
|
-
return 0;
|
|
77
|
-
}
|
|
78
|
-
function fail(json, code, message, exitCode, extra) {
|
|
79
|
-
if (json)
|
|
80
|
-
console.log(JSON.stringify({ ok: false, code, message, ...extra }));
|
|
81
|
-
else
|
|
82
|
-
console.error(message);
|
|
83
|
-
return exitCode;
|
|
84
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { readAuth } from "../../lib/state.js";
|
|
2
|
-
import { apiPost } from "../../lib/api.js";
|
|
3
|
-
export async function runTest2faStart(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 numberId = opts.numberId ?? auth.phone_number_id;
|
|
10
|
-
if (!numberId) {
|
|
11
|
-
fail(opts.json, "no_number", "No default phone_number_id in auth. Pass --number-id <id>.");
|
|
12
|
-
return 1;
|
|
13
|
-
}
|
|
14
|
-
const res = await apiPost("/api/v1/test/2fa", { phone_number_id: numberId }, auth.api_key);
|
|
15
|
-
if (!res.ok) {
|
|
16
|
-
fail(opts.json, "start_failed", res.error, { status: res.status });
|
|
17
|
-
return 2;
|
|
18
|
-
}
|
|
19
|
-
if (opts.json) {
|
|
20
|
-
console.log(JSON.stringify({ ok: true, session_id: res.data.session_id, otp_message: res.data.otp_message, number_id: numberId }));
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
console.log(`test 2fa session started.`);
|
|
24
|
-
console.log(` session: ${res.data.session_id}`);
|
|
25
|
-
console.log(` expected SMS: ${res.data.otp_message}`);
|
|
26
|
-
console.log(` to number: ${numberId}`);
|
|
27
|
-
console.log(``);
|
|
28
|
-
console.log(`Wait for it with: dial wait-for message.received -f channel=sms --json`);
|
|
29
|
-
console.log(`Or run the whole loop: dial test-2fa run`);
|
|
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
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { readAuth } from "../../lib/state.js";
|
|
2
|
-
import { apiPost } from "../../lib/api.js";
|
|
3
|
-
export async function runTest2faVerify(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 apiPost(`/api/v1/test/2fa/${encodeURIComponent(opts.sessionId)}/verify`, { code: opts.code }, auth.api_key);
|
|
10
|
-
if (!res.ok) {
|
|
11
|
-
fail(opts.json, "verify_failed", res.error, { status: res.status });
|
|
12
|
-
return 2;
|
|
13
|
-
}
|
|
14
|
-
if (opts.json) {
|
|
15
|
-
console.log(JSON.stringify({ ok: true, verified: res.data.verified, session_id: opts.sessionId }));
|
|
16
|
-
}
|
|
17
|
-
else if (res.data.verified) {
|
|
18
|
-
console.log("verified.");
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
console.log("not verified.");
|
|
22
|
-
return 2;
|
|
23
|
-
}
|
|
24
|
-
return 0;
|
|
25
|
-
}
|
|
26
|
-
function fail(json, code, message, extra) {
|
|
27
|
-
if (json)
|
|
28
|
-
console.log(JSON.stringify({ ok: false, code, message, ...extra }));
|
|
29
|
-
else
|
|
30
|
-
console.error(message);
|
|
31
|
-
}
|