@getdial/cli 0.5.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 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>", "phone_number_id to send from (defaults to onboard's number)")
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>", "phone_number_id to call from (defaults to onboard's number)")
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,
@@ -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.phone_number_id;
9
+ const fromNumberId = opts.fromNumberId ?? auth.phoneNumberId;
10
10
  if (!fromNumberId) {
11
- fail(opts.json, "no_from_number", "No default phone_number_id in auth. Pass --from-number-id <id>.");
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
- from_number_id: fromNumberId,
17
- system_prompt: opts.systemPrompt,
16
+ fromNumberId,
17
+ systemPrompt: opts.systemPrompt,
18
18
  language: opts.language,
19
- }, auth.api_key);
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
- agent_hint: {
31
+ agentHint: {
32
32
  action: "ask_user_about_waiting",
33
33
  kind: "post_call_followup",
34
34
  question: followUpQuestion,
35
- wait_command: waitCmd,
36
- on_yes: waitCmd,
37
- on_no: null,
38
- use_structured_tool_if_available: true,
35
+ waitCommand: waitCmd,
36
+ onYes: waitCmd,
37
+ onNo: null,
38
+ useStructuredToolIfAvailable: true,
39
39
  },
40
40
  }));
41
41
  }
@@ -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?.api_key) {
13
- const res = await apiGet("/api/v1/account", auth.api_key);
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.created_at) : null;
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, last_event_at: null };
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
- last_event_at: lastEventAtFromLog(paths().listenLog),
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, latency_ms: ping.latencyMs },
50
+ backend: { url: baseUrl(), reachable: ping.reachable, latencyMs: ping.latencyMs },
51
51
  auth: {
52
- signed_in: Boolean(auth),
52
+ signedIn: Boolean(auth),
53
53
  email: auth?.email ?? null,
54
- account_id: auth?.account_id ?? null,
55
- api_key_present: Boolean(auth?.api_key),
56
- api_key_fingerprint: auth?.api_key ? auth.api_key.slice(-4) : null,
57
- key_valid: keyValid,
54
+ accountId: auth?.accountId ?? null,
55
+ apiKeyPresent: Boolean(auth?.apiKey),
56
+ apiKeyFingerprint: auth?.apiKey ? auth.apiKey.slice(-4) : null,
57
+ keyValid,
58
58
  },
59
- pending_otp: {
60
- verification_id: pending?.verification_id ?? null,
61
- age_seconds: pendingAgeMs == null ? null : Math.round(pendingAgeMs / 1000),
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
- next_step: nextStep,
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.latency_ms != null ? ` (${r.backend.latency_ms}ms)` : ""}` : "UNREACHABLE"}`);
72
- if (r.auth.signed_in) {
73
- lines.push(`auth: signed in as ${r.auth.email} (account ${r.auth.account_id}, key sk_live_***${r.auth.api_key_fingerprint})${r.auth.key_valid === false ? " [key rejected by backend]" : ""}`);
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.pending_otp.verification_id) {
79
- lines.push(`pending otp: ${r.pending_otp.age_seconds}s old${r.pending_otp.expired ? " (EXPIRED)" : ""}`);
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.last_event_at ? `, last event ${r.listen.last_event_at}` : ""}`);
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.next_step}`);
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, account_id: auth.account_id });
19
- const ctrl = startWorker(auth.api_key, auth.account_id);
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.api_key);
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, default_number_id: auth.phone_number_id ?? null }));
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 numbers provision`.");
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.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}`);
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.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);
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;
@@ -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.verification_id;
25
+ verificationId = pending.verificationId;
26
26
  email = pending.email;
27
27
  }
28
- const res = await apiPost("/api/v1/auth/verify", { verification_id: verificationId, code: opts.code });
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.api_key ?? null;
37
- if (!apiKey || !res.data.account_id) {
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 api_key" }));
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 api_key");
41
+ console.error("onboard failed: backend returned no apiKey");
42
42
  return 2;
43
43
  }
44
44
  writeAuth({
45
- api_key: apiKey,
46
- account_id: res.data.account_id,
45
+ apiKey,
46
+ accountId: res.data.accountId,
47
47
  email: email ?? "",
48
- phone_number: res.data.phone_number ?? null,
49
- phone_number_id: res.data.phone_number_id ?? null,
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
- api_key_fingerprint: apiKey.slice(-4),
58
- api_key_masked: masked,
59
- api_key_path: authFile,
60
- account_id: res.data.account_id,
61
- phone_number: res.data.phone_number ?? null,
62
- phone_number_id: res.data.phone_number_id ?? null,
63
- listen: { installed: false, auto_installed: false },
64
- agent_hint: {
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
- finalization_note: FINALIZATION_HINT,
71
- use_structured_tool_if_available: true,
72
- on_yes: "dial listen install",
73
- on_no: null,
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.phone_number)
81
- console.log(` phone number: ${res.data.phone_number}`);
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(``);
@@ -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.phone_number_id;
9
+ const fromNumberId = opts.fromNumberId ?? auth.phoneNumberId;
10
10
  if (!fromNumberId) {
11
- fail(opts.json, "no_from_number", "No default phone_number_id in auth. Pass --from-number-id <id>.");
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", from_number_id: fromNumberId }, auth.api_key);
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;
@@ -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.created_at);
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", verification_id: existing.verification_id, email: existing.email, age_seconds: ageS }));
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({ verification_id: res.data.verification_id, email, created_at: new Date().toISOString() });
27
+ writePendingSignup({ verificationId: res.data.verificationId, email, createdAt: new Date().toISOString() });
28
28
  if (opts.json) {
29
- console.log(JSON.stringify({ ok: true, verification_id: res.data.verification_id, email }));
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 (verification_id is stored locally).`);
33
+ console.log(`Run \`dial onboard --code <code>\` once you have it (verificationId is stored locally).`);
34
34
  }
35
35
  return 0;
36
36
  }
@@ -1,33 +1,23 @@
1
+ import { readAuth } from "../lib/state.js";
1
2
  import { paths } from "../lib/paths.js";
2
3
  import { supervisorStatus } from "../lib/supervisor/index.js";
3
4
  import { parseFieldArg, parseRegexArg } from "../lib/event-filter.js";
4
5
  import { currentSize, findLatestMatch, tailUntilMatch } from "../lib/log-tail.js";
6
+ import { apiPost } from "../lib/api.js";
7
+ const PER_POLL_SECONDS = 30;
5
8
  export async function runWaitFor(opts) {
6
- const status = supervisorStatus();
7
- if (!status.installed || !status.running) {
8
- const hint = !status.installed
9
- ? "Run `dial listen install` (or rerun `dial onboard`)."
10
- : "It's installed but not running. Reinstall with `dial listen install` to restart it.";
11
- if (opts.json) {
12
- console.log(JSON.stringify({
13
- ok: false,
14
- reason: "listen_not_running",
15
- installed: status.installed,
16
- running: status.running,
17
- pid: status.pid,
18
- unit_path: status.unitPath,
19
- }));
20
- }
21
- else {
22
- console.error(`listen daemon is not running (installed=${status.installed}, running=${status.running}). ${hint}`);
23
- }
24
- return 3;
25
- }
26
9
  const spec = {
27
10
  eventType: opts.eventType,
28
11
  fields: opts.fields.map(parseFieldArg),
29
12
  regexes: opts.regexes.map(parseRegexArg),
30
13
  };
14
+ const status = supervisorStatus();
15
+ if (status.installed && status.running) {
16
+ return waitFromLog(spec, opts);
17
+ }
18
+ return waitFromApi(spec, opts);
19
+ }
20
+ async function waitFromLog(spec, opts) {
31
21
  const file = paths().listenLog;
32
22
  const startOffset = currentSize(file);
33
23
  const hit = await tailUntilMatch(file, spec, startOffset, opts.timeoutSeconds * 1000);
@@ -54,3 +44,51 @@ export async function runWaitFor(opts) {
54
44
  }
55
45
  return 2;
56
46
  }
47
+ async function waitFromApi(spec, opts) {
48
+ const auth = readAuth();
49
+ if (!auth) {
50
+ fail(opts.json, "not_signed_in", "Not signed in. Run `dial signup` and `dial onboard` first.");
51
+ return 1;
52
+ }
53
+ const filters = {};
54
+ for (const f of spec.fields)
55
+ filters[f.name] = f.value;
56
+ const regexFilters = {};
57
+ for (const r of spec.regexes)
58
+ regexFilters[r.name] = { pattern: r.regex.source, flags: r.regex.flags };
59
+ const deadline = Date.now() + opts.timeoutSeconds * 1000;
60
+ while (Date.now() < deadline) {
61
+ const remainingSec = Math.max(1, Math.ceil((deadline - Date.now()) / 1000));
62
+ const timeout = Math.min(PER_POLL_SECONDS, remainingSec);
63
+ const res = await apiPost("/api/v1/events/wait", {
64
+ eventType: spec.eventType,
65
+ filters: Object.keys(filters).length > 0 ? filters : undefined,
66
+ regexFilters: Object.keys(regexFilters).length > 0 ? regexFilters : undefined,
67
+ timeout,
68
+ }, auth.apiKey);
69
+ if (res.ok && res.data?.event) {
70
+ process.stdout.write(JSON.stringify(res.data.event) + "\n");
71
+ return 0;
72
+ }
73
+ if (res.ok === false && res.status === 408) {
74
+ continue;
75
+ }
76
+ if (res.ok === false) {
77
+ fail(opts.json, "api_fallback_failed", res.error, { status: res.status });
78
+ return 4;
79
+ }
80
+ }
81
+ if (opts.json) {
82
+ console.log(JSON.stringify({ ok: false, timeout: true, source: "api", event: null }));
83
+ }
84
+ else {
85
+ console.error(`timed out after ${opts.timeoutSeconds}s; no matching ${opts.eventType} via API fallback.`);
86
+ }
87
+ return 2;
88
+ }
89
+ function fail(json, code, message, extra) {
90
+ if (json)
91
+ console.log(JSON.stringify({ ok: false, code, message, ...extra }));
92
+ else
93
+ console.error(message);
94
+ }
@@ -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.ttl_seconds / 2)) * 1000;
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.subscribe_key,
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
- api_key: z.string(),
8
- account_id: z.string(),
7
+ apiKey: z.string(),
8
+ accountId: z.string(),
9
9
  email: z.string(),
10
- phone_number: z.string().nullable(),
11
- phone_number_id: z.string().nullable(),
10
+ phoneNumber: z.string().nullable(),
11
+ phoneNumberId: z.string().nullable(),
12
12
  });
13
13
  export const PendingSignupSchema = z.object({
14
- verification_id: z.string(),
14
+ verificationId: z.string(),
15
15
  email: z.string(),
16
- created_at: z.string(),
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@getdial/cli",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Dial CLI — install, sign up, and run the local listen service.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -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
- }