@getdial/cli 0.15.2 → 0.16.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
@@ -96,12 +96,14 @@ number
96
96
  })));
97
97
  number
98
98
  .command("set <number>")
99
- .description("Update a number's inbound instruction. PATCH /api/v1/numbers/<id>.")
100
- .requiredOption("--inbound-instruction <text>", "new system prompt for inbound calls to this number")
99
+ .description("Update a number's properties (at least one flag). PATCH /api/v1/numbers/<id>.")
100
+ .option("--inbound-instruction <text>", "new system prompt for inbound calls to this number")
101
+ .option("--nickname <text>", 'human-readable label for the number, e.g. "Support line"; pass "" to clear')
101
102
  .option("--json", "machine-readable output")
102
103
  .action(async (numberArg, opts) => process.exit(await runNumberSet({
103
104
  number: numberArg,
104
105
  inboundInstruction: opts.inboundInstruction,
106
+ nickname: opts.nickname,
105
107
  json: !!opts.json,
106
108
  })));
107
109
  const message = program
@@ -1,8 +1,5 @@
1
1
  import { readAuth } from "../../lib/state.js";
2
- import { installSupervised, supervisorAvailability } from "../../lib/supervisor/index.js";
3
- function resolveDialPath() {
4
- return process.env.DIAL_BIN_OVERRIDE ?? process.argv[1] ?? "dial";
5
- }
2
+ import { installSupervised, resolveListenCommand, supervisorAvailability } from "../../lib/supervisor/index.js";
6
3
  export async function runListenInstall(opts) {
7
4
  const auth = readAuth();
8
5
  if (!auth) {
@@ -21,7 +18,7 @@ export async function runListenInstall(opts) {
21
18
  return 2;
22
19
  }
23
20
  try {
24
- const result = installSupervised(resolveDialPath());
21
+ const result = installSupervised(resolveListenCommand());
25
22
  if (opts.json)
26
23
  console.log(JSON.stringify({ ok: true, changed: result.changed, unit_path: result.unitPath, warnings: result.warnings }));
27
24
  else {
@@ -14,7 +14,8 @@ export async function runNumberList(opts) {
14
14
  }
15
15
  for (const n of numbers) {
16
16
  const tag = n.id === defaultNumberId ? " (default)" : "";
17
- console.log(`${n.number} id=${n.id} ${n.country}${tag}`);
17
+ const nickname = n.nickname ? ` "${n.nickname}"` : "";
18
+ console.log(`${n.number} id=${n.id} ${n.country}${nickname}${tag}`);
18
19
  }
19
20
  return 0;
20
21
  }
@@ -3,7 +3,11 @@ import { isDialError } from "../../lib/ops/errors.js";
3
3
  import { printDialError } from "../../lib/cli-error.js";
4
4
  export async function runNumberSet(opts) {
5
5
  try {
6
- const n = await setNumberProperties({ number: opts.number, inboundInstruction: opts.inboundInstruction });
6
+ const n = await setNumberProperties({
7
+ number: opts.number,
8
+ inboundInstruction: opts.inboundInstruction,
9
+ ...(opts.nickname !== undefined ? { nickname: opts.nickname } : {}),
10
+ });
7
11
  if (opts.json) {
8
12
  console.log(JSON.stringify({ ok: true, number: n }));
9
13
  }
@@ -11,6 +15,7 @@ export async function runNumberSet(opts) {
11
15
  console.log(`updated.`);
12
16
  console.log(` number: ${n.number}`);
13
17
  console.log(` id: ${n.id}`);
18
+ console.log(` nickname: ${n.nickname ?? ""}`);
14
19
  console.log(` inbound instruction: ${n.inboundInstruction ?? ""}`);
15
20
  }
16
21
  return 0;
@@ -6,6 +6,7 @@ const EXIT_1_CODES = new Set([
6
6
  "number_not_found",
7
7
  "not_found",
8
8
  "no_pending_signup",
9
+ "bad_request",
9
10
  ]);
10
11
  /**
11
12
  * Print a DialError the way the generic REST commands always have — `--json` emits
@@ -1,11 +1,8 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { installSupervised, uninstallSupervised, supervisorStatus, supervisorAvailability, lastEventAtFromLog, } from "../supervisor/index.js";
2
+ import { installSupervised, uninstallSupervised, supervisorStatus, supervisorAvailability, lastEventAtFromLog, resolveListenCommand, } from "../supervisor/index.js";
3
3
  import { paths } from "../paths.js";
4
4
  import { requireAuth } from "./auth.js";
5
5
  import { DialError } from "./errors.js";
6
- function resolveDialPath() {
7
- return process.env.DIAL_BIN_OVERRIDE ?? process.argv[1] ?? "dial";
8
- }
9
6
  export function listenInstall() {
10
7
  requireAuth();
11
8
  const supervisor = supervisorAvailability();
@@ -13,7 +10,7 @@ export function listenInstall() {
13
10
  throw new DialError("supervisor_unavailable", supervisor.reason);
14
11
  }
15
12
  try {
16
- return installSupervised(resolveDialPath());
13
+ return installSupervised(resolveListenCommand());
17
14
  }
18
15
  catch (err) {
19
16
  throw new DialError("install_failed", err instanceof Error ? err.message : String(err));
@@ -21,6 +21,14 @@ export async function purchaseNumber(opts) {
21
21
  return res.data.number;
22
22
  }
23
23
  export async function setNumberProperties(opts) {
24
+ const body = {};
25
+ if (opts.inboundInstruction !== undefined)
26
+ body.inboundInstruction = opts.inboundInstruction;
27
+ if (opts.nickname !== undefined)
28
+ body.nickname = opts.nickname;
29
+ if (Object.keys(body).length === 0) {
30
+ throw new DialError("bad_request", "Provide at least one property to update (inboundInstruction or nickname).");
31
+ }
24
32
  const auth = requireAuth();
25
33
  // The REST API keys numbers by id; the CLI/tool takes the E.164 number for ergonomics,
26
34
  // so resolve it to its id first.
@@ -32,7 +40,7 @@ export async function setNumberProperties(opts) {
32
40
  const known = list.data.numbers.map((n) => n.number).join(", ") || "(none)";
33
41
  throw new DialError("number_not_found", `No phone number ${opts.number} on your account. Yours: ${known}.`);
34
42
  }
35
- const res = await apiPatch(`/api/v1/numbers/${match.id}`, { inboundInstruction: opts.inboundInstruction }, auth.apiKey);
43
+ const res = await apiPatch(`/api/v1/numbers/${match.id}`, body, auth.apiKey);
36
44
  if (!res.ok)
37
45
  throw new DialError("update_failed", res.error, res.status);
38
46
  return res.data.number;
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync, statSync } from "node:fs";
2
2
  import { userInfo } from "node:os";
3
+ import { dirname, join } from "node:path";
3
4
  import { paths } from "../paths.js";
4
5
  import { logger } from "../log.js";
5
6
  import { LAUNCHD_LABEL, launchctlBootoutSilent, launchctlLoad, launchctlStatus, launchctlUnload, launchdPlistPath, renderLaunchdPlist, writeLaunchdPlist } from "./launchd.js";
@@ -33,13 +34,45 @@ export function supervisorAvailability() {
33
34
  }
34
35
  return { available: true };
35
36
  }
36
- export function installSupervised(programPath) {
37
+ /**
38
+ * Builds the argv the supervised listen daemon is launched with, baked into
39
+ * the launchd plist / systemd unit. Pure so it can be unit-tested without a
40
+ * real process or filesystem.
41
+ *
42
+ * - `override` (DIAL_BIN_OVERRIDE) always wins, for tests and packaging.
43
+ * - When the current process was launched from npm's ephemeral npx cache
44
+ * (`~/.npm/_npx/<hash>/…`, e.g. `npx @getdial/cli mcp`), baking that script
45
+ * path into a long-lived unit is fragile: the cache can be garbage-collected
46
+ * and the daemon would then point at a path that no longer exists. Re-invoke
47
+ * through `npx @getdial/cli listen` so each launch re-resolves the CLI.
48
+ * - Otherwise launch the running script directly (`<dial> listen`).
49
+ */
50
+ export function buildListenArgs(input) {
51
+ if (input.override)
52
+ return [input.override, "listen"];
53
+ if (/[/\\]_npx[/\\]/.test(input.scriptPath)) {
54
+ const npx = join(input.nodeDir, "npx");
55
+ return [input.npxExists ? npx : "npx", "-y", "@getdial/cli", "listen"];
56
+ }
57
+ return [input.scriptPath || "dial", "listen"];
58
+ }
59
+ /** Resolves {@link buildListenArgs} against the live process/environment. */
60
+ export function resolveListenCommand() {
61
+ const nodeDir = dirname(process.execPath);
62
+ return buildListenArgs({
63
+ override: process.env.DIAL_BIN_OVERRIDE,
64
+ scriptPath: process.argv[1] ?? "",
65
+ nodeDir,
66
+ npxExists: existsSync(join(nodeDir, "npx")),
67
+ });
68
+ }
69
+ export function installSupervised(programArgs) {
37
70
  const platform = currentPlatform();
38
71
  const p = paths();
39
72
  if (platform === "darwin") {
40
73
  const xml = renderLaunchdPlist({
41
74
  label: LAUNCHD_LABEL,
42
- programPath,
75
+ programArgs,
43
76
  stdoutPath: p.listenOutLog,
44
77
  stderrPath: p.listenErrLog,
45
78
  });
@@ -52,7 +85,7 @@ export function installSupervised(programPath) {
52
85
  return { changed, warnings: [], unitPath: path };
53
86
  }
54
87
  else {
55
- const unit = renderSystemdUnit({ programPath });
88
+ const unit = renderSystemdUnit({ programArgs });
56
89
  const { path, changed } = writeSystemdUnit(unit);
57
90
  systemctlEnableAndStart();
58
91
  const warnings = [];
@@ -35,6 +35,7 @@ export function renderLaunchdPlist(params) {
35
35
  // so we must prepend the directory of the currently running node (e.g. nvm's bin dir)
36
36
  // so the shebang can resolve. Falls back to /usr/local/bin which is where Homebrew puts node.
37
37
  const nodeDir = dirname(process.execPath);
38
+ const programArguments = params.programArgs.map((arg) => ` <string>${arg}</string>`).join("\n");
38
39
  return `<?xml version="1.0" encoding="UTF-8"?>
39
40
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
40
41
  <plist version="1.0">
@@ -43,8 +44,7 @@ export function renderLaunchdPlist(params) {
43
44
  <string>${params.label}</string>
44
45
  <key>ProgramArguments</key>
45
46
  <array>
46
- <string>${params.programPath}</string>
47
- <string>listen</string>
47
+ ${programArguments}
48
48
  </array>
49
49
  <key>RunAtLoad</key>
50
50
  <true/>
@@ -27,7 +27,7 @@ Wants=network-online.target
27
27
  [Service]
28
28
  Type=simple
29
29
  Environment="PATH=${nodeDir}:/usr/local/bin:/usr/bin:/bin"
30
- ExecStart=${params.programPath} listen
30
+ ExecStart=${params.programArgs.join(" ")}
31
31
  Restart=on-failure
32
32
  RestartSec=10
33
33
 
@@ -22,6 +22,7 @@ export const phoneNumberSchema = z
22
22
  .object({
23
23
  id: z.string().describe("Phone number id (pn_…)"),
24
24
  number: z.string().describe("E.164 phone number"),
25
+ nickname: z.string().nullable().optional().describe("Human-readable label for the number"),
25
26
  country: z.string().optional(),
26
27
  inboundInstruction: z.string().nullable().optional(),
27
28
  })
@@ -4,13 +4,14 @@ import { setNumberProperties } from "../../lib/ops/numbers.js";
4
4
  import { phoneNumberSchema } from "../schemas.js";
5
5
  const inputSchema = {
6
6
  number: z.string().min(7).describe("The E.164 phone number to update (e.g. +14155550123)"),
7
- inboundInstruction: z.string().min(1).describe("New system prompt for inbound calls to this number"),
7
+ inboundInstruction: z.string().min(1).optional().describe("New system prompt for inbound calls to this number"),
8
+ nickname: z.string().max(100).optional().describe('Human-readable label for the number, e.g. "Support line". Pass an empty string to clear it.'),
8
9
  };
9
10
  export const setNumberPropertiesTool = {
10
11
  name: "set_number_properties",
11
12
  config: {
12
13
  title: "Set Number Properties",
13
- description: "Update a phone number's inbound instruction (the system prompt for inbound calls).",
14
+ description: "Update a phone number's properties: its inbound instruction (the system prompt for inbound calls) and/or its nickname. Provide at least one.",
14
15
  inputSchema,
15
16
  outputSchema: { number: phoneNumberSchema },
16
17
  annotations: { openWorldHint: true },
@@ -19,6 +20,7 @@ export const setNumberPropertiesTool = {
19
20
  number: await setNumberProperties({
20
21
  number: args.number,
21
22
  inboundInstruction: args.inboundInstruction,
23
+ ...(args.nickname !== undefined ? { nickname: args.nickname } : {}),
22
24
  }),
23
25
  }),
24
26
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getdial/cli",
3
- "version": "0.15.2",
3
+ "version": "0.16.0",
4
4
  "description": "Dial CLI — install, sign up, and run the local listen service.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/skills.tar.gz CHANGED
Binary file