@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 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 { 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
- 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";
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({ verificationId: opts.verificationId, code: opts.code, json: !!opts.json })));
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 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
- const numbers = program
87
- .command("numbers")
72
+ const number = program
73
+ .command("number")
88
74
  .description("Manage your Dial phone numbers.");
89
- numbers
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 runNumbersList({ json: !!opts.json })));
94
- numbers
95
- .command("provision")
96
- .description("Provision an additional phone number. POST /api/v1/numbers.")
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 runNumbersProvision({
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("send")
91
+ const message = program
92
+ .command("message")
109
93
  .description("Send an SMS. POST /api/v1/messages.")
110
- .requiredOption("--to <e164>", "destination phone number, E.164 (e.g. +14155551234)")
111
- .requiredOption("--body <text>", "message body")
112
- .option("--from-number-id <id>", "phone_number_id to send from (defaults to onboard's number)")
113
- .option("--json", "machine-readable output")
114
- .action(async (opts) => process.exit(await runSend({
115
- to: opts.to,
116
- body: opts.body,
117
- fromNumberId: opts.fromNumberId,
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
- .requiredOption("--to <e164>", "destination phone number, E.164 (e.g. +14155551234)")
124
- .requiredOption("--system-prompt <text>", "system prompt for the agent that will speak")
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>", "phone_number_id to call from (defaults to onboard's number)")
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) => process.exit(await runCall({
129
- to: opts.to,
130
- systemPrompt: opts.systemPrompt,
131
- language: opts.language,
132
- fromNumberId: opts.fromNumberId,
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 "../lib/state.js";
2
- import { apiPost } from "../lib/api.js";
3
- export async function runCall(opts) {
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.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();
@@ -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 "../lib/state.js";
2
- import { apiPost } from "../lib/api.js";
3
- export async function runSend(opts) {
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.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;