ring-a-ding-cli 0.1.0 → 0.1.1

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/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # Ring-a-Ding CLI
2
+
3
+ `ring-a-ding-cli` installs the `rad` command used by the public `ring-a-ding`
4
+ OpenClaw skill.
5
+
6
+ ## What You Need
7
+
8
+ Before you can make calls, you need:
9
+
10
+ 1. A Ring-a-Ding API key from [ringading.ai/account](https://ringading.ai/account)
11
+ 2. Your own OpenAI API key from [platform.openai.com/api-keys](https://platform.openai.com/api-keys)
12
+
13
+ Ring-a-Ding is the telephony service. Your OpenAI key powers the voice agent.
14
+
15
+ ## OpenClaw Install
16
+
17
+ 1. Install the CLI:
18
+
19
+ ```bash
20
+ npm install -g ring-a-ding-cli
21
+ ```
22
+
23
+ 2. Install the OpenClaw skill:
24
+
25
+ ```bash
26
+ openclaw skills install ring-a-ding
27
+ ```
28
+
29
+ 3. Check what is still missing:
30
+
31
+ ```bash
32
+ openclaw skills check
33
+ ```
34
+
35
+ 4. Add your keys to `~/.openclaw/openclaw.json`:
36
+
37
+ ```json
38
+ {
39
+ "skills": {
40
+ "entries": {
41
+ "ring-a-ding": {
42
+ "enabled": true,
43
+ "apiKey": "rad_live_...",
44
+ "env": {
45
+ "OPENAI_API_KEY": "sk-..."
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ 5. Start a fresh OpenClaw session.
54
+
55
+ 6. Test with this prompt:
56
+
57
+ ```text
58
+ Use the ring-a-ding skill to call +16143970634 and ask the callee to say ready.
59
+ ```
60
+
61
+ ## Optional CLI Setup
62
+
63
+ If you want to use `rad` directly outside OpenClaw, save your keys locally:
64
+
65
+ ```bash
66
+ rad init
67
+ ```
68
+
69
+ That stores config in `~/.config/ring-a-ding/config.json`.
70
+
71
+ ## Docs
72
+
73
+ - Public setup guide: [ringading.ai/openclaw](https://ringading.ai/openclaw)
74
+ - Account and billing: [ringading.ai/account](https://ringading.ai/account)
package/dist/index.js CHANGED
@@ -14017,7 +14017,7 @@ function loadConfig() {
14017
14017
  if (envApiKey && envOpenaiKey) {
14018
14018
  return {
14019
14019
  apiKey: envApiKey,
14020
- apiUrl: envApiUrl ?? "https://ringading.fly.dev",
14020
+ apiUrl: envApiUrl ?? "https://api.ringading.ai",
14021
14021
  openaiKey: envOpenaiKey
14022
14022
  };
14023
14023
  }
@@ -14029,7 +14029,7 @@ function loadConfig() {
14029
14029
  const parsed = JSON.parse(raw);
14030
14030
  return {
14031
14031
  apiKey: envApiKey ?? parsed.apiKey,
14032
- apiUrl: envApiUrl ?? parsed.apiUrl ?? "https://ringading.fly.dev",
14032
+ apiUrl: envApiUrl ?? parsed.apiUrl ?? "https://api.ringading.ai",
14033
14033
  openaiKey: envOpenaiKey ?? parsed.openaiKey
14034
14034
  };
14035
14035
  } catch {
@@ -14043,7 +14043,7 @@ function requireConfig() {
14043
14043
  `${JSON.stringify({
14044
14044
  error: true,
14045
14045
  errorCode: "NOT_CONFIGURED",
14046
- message: "Ring-a-Ding is not configured. Run: rad init"
14046
+ message: "Ring-a-Ding is not configured. Get your API key at https://ringading.ai/account, then run: rad init"
14047
14047
  })}
14048
14048
  `
14049
14049
  );
@@ -14054,7 +14054,7 @@ function requireConfig() {
14054
14054
  `${JSON.stringify({
14055
14055
  error: true,
14056
14056
  errorCode: "MISSING_API_KEY",
14057
- message: "API key not found. Run: rad init"
14057
+ message: "API key not found. Get your API key at https://ringading.ai/account, then run: rad init"
14058
14058
  })}
14059
14059
  `
14060
14060
  );
@@ -14231,12 +14231,30 @@ function registerEndCommand(program2) {
14231
14231
  }
14232
14232
 
14233
14233
  // src/commands/init.ts
14234
- import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "node:fs";
14234
+ import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "node:fs";
14235
14235
  import { homedir as homedir2 } from "node:os";
14236
- import { dirname, join as join2 } from "node:path";
14236
+ import { dirname as dirname2, join as join3 } from "node:path";
14237
14237
  import { createInterface } from "node:readline/promises";
14238
+
14239
+ // src/skill-file.ts
14240
+ import { existsSync as existsSync2 } from "node:fs";
14241
+ import { dirname, join as join2 } from "node:path";
14238
14242
  import { fileURLToPath } from "node:url";
14239
14243
  var __dirname = dirname(fileURLToPath(import.meta.url));
14244
+ function getBundledSkillPath() {
14245
+ const candidates = [
14246
+ join2(__dirname, "skills", "ring-a-ding", "SKILL.md"),
14247
+ join2(__dirname, "..", "skills", "ring-a-ding", "SKILL.md")
14248
+ ];
14249
+ for (const candidate of candidates) {
14250
+ if (existsSync2(candidate)) {
14251
+ return candidate;
14252
+ }
14253
+ }
14254
+ throw new Error("Skill file not found. Reinstall ring-a-ding-cli.");
14255
+ }
14256
+
14257
+ // src/commands/init.ts
14240
14258
  async function prompt(rl, message) {
14241
14259
  const answer = await rl.question(message);
14242
14260
  return answer.trim();
@@ -14249,11 +14267,24 @@ function getValidationFailureMessage(err) {
14249
14267
  return "FAILED (key may be invalid, but config saved)";
14250
14268
  }
14251
14269
  function registerInitCommand(program2) {
14252
- program2.command("init").description("Configure Ring-a-Ding (API key, OpenAI key)").option("--api-key <key>", "Ring-a-Ding API key").option("--openai-key <key>", "OpenAI API key").option("--api-url <url>", "API URL (default: https://ringading.fly.dev)").action(async (opts) => {
14270
+ program2.command("init").description("Save your Ring-a-Ding API key and OpenAI API key for local use").option("--api-key <key>", "Ring-a-Ding API key").option("--openai-key <key>", "OpenAI API key").option("--api-url <url>", "API URL (default: https://api.ringading.ai)").addHelpText(
14271
+ "after",
14272
+ [
14273
+ "",
14274
+ "You need:",
14275
+ " - A Ring-a-Ding API key from https://ringading.ai/account",
14276
+ " - An OpenAI API key from https://platform.openai.com/api-keys",
14277
+ "",
14278
+ "OpenClaw setup guide: https://ringading.ai/openclaw"
14279
+ ].join("\n")
14280
+ ).action(async (opts) => {
14253
14281
  const rl = createInterface({ input: process.stdin, output: process.stderr });
14282
+ const isInteractive = Boolean(process.stdin.isTTY && process.stderr.isTTY);
14254
14283
  try {
14255
14284
  process.stderr.write("\nRing-a-Ding CLI Setup\n");
14256
14285
  process.stderr.write("=====================\n\n");
14286
+ process.stderr.write("Need a Ring-a-Ding API key? Visit https://ringading.ai/account\n");
14287
+ process.stderr.write("OpenClaw setup guide: https://ringading.ai/openclaw\n\n");
14257
14288
  if (configExists()) {
14258
14289
  const existing = loadConfig();
14259
14290
  if (existing) {
@@ -14286,7 +14317,7 @@ function registerInitCommand(program2) {
14286
14317
  rl.close();
14287
14318
  process.exit(1);
14288
14319
  }
14289
- const apiUrl = opts.apiUrl ?? (await prompt(rl, "API URL [https://ringading.fly.dev]: ") || "https://ringading.fly.dev");
14320
+ const apiUrl = opts.apiUrl ?? (await prompt(rl, "API URL [https://api.ringading.ai]: ") || "https://api.ringading.ai");
14290
14321
  saveConfig({ apiKey, apiUrl, openaiKey });
14291
14322
  process.stderr.write(`
14292
14323
  Config saved to ${getConfigPath()}
@@ -14301,28 +14332,52 @@ Config saved to ${getConfigPath()}
14301
14332
  process.stderr.write(`${getValidationFailureMessage(err)}
14302
14333
  `);
14303
14334
  }
14304
- const openclawDir = join2(homedir2(), ".openclaw");
14305
- if (existsSync2(openclawDir)) {
14306
- process.stderr.write("\nOpenClaw detected!\n");
14307
- const defaultPath = join2(openclawDir, "workspace", "skills", "ring-a-ding", "SKILL.md");
14308
- const skillPath = await prompt(rl, `Copy OpenClaw skill to [${defaultPath}]: `) || defaultPath;
14309
- try {
14310
- const skillSource = join2(__dirname, "..", "skills", "ring-a-ding", "SKILL.md");
14311
- mkdirSync2(dirname(skillPath), { recursive: true });
14312
- copyFileSync(skillSource, skillPath);
14313
- process.stderr.write(`Skill file copied to ${skillPath}
14335
+ const openclawDir = join3(homedir2(), ".openclaw");
14336
+ if (existsSync3(openclawDir)) {
14337
+ process.stderr.write("\nOpenClaw detected.\n");
14338
+ process.stderr.write("Recommended public setup:\n");
14339
+ process.stderr.write(" openclaw skills install ring-a-ding\n");
14340
+ process.stderr.write(" openclaw skills check\n");
14341
+ process.stderr.write(" Start a fresh OpenClaw session\n");
14342
+ if (isInteractive) {
14343
+ const defaultPath = join3(openclawDir, "workspace", "skills", "ring-a-ding", "SKILL.md");
14344
+ const skillPath = await prompt(
14345
+ rl,
14346
+ `
14347
+ Optional local-dev shortcut: copy a local skill file to [${defaultPath}] (press Enter to skip): `
14348
+ );
14349
+ if (skillPath) {
14350
+ try {
14351
+ const skillSource = getBundledSkillPath();
14352
+ mkdirSync2(dirname2(skillPath), { recursive: true });
14353
+ copyFileSync(skillSource, skillPath);
14354
+ process.stderr.write(`Local skill file copied to ${skillPath}
14314
14355
  `);
14315
- } catch (err) {
14316
- process.stderr.write(`Could not copy skill file: ${err.message}
14356
+ } catch (err) {
14357
+ process.stderr.write(`Could not copy skill file: ${err.message}
14317
14358
  `);
14318
- process.stderr.write('Run "rad skill" to print the skill file for manual copy.\n');
14359
+ process.stderr.write('Run "rad skill" to print the skill file for manual copy.\n');
14360
+ }
14361
+ } else {
14362
+ process.stderr.write("Skipped local skill copy.\n");
14363
+ }
14364
+ } else {
14365
+ process.stderr.write("Skipping optional local skill copy in non-interactive mode.\n");
14319
14366
  }
14320
- process.stderr.write('\nAdd "rad" to your OpenClaw shell tool allowlist.\n');
14367
+ process.stderr.write('\nIf OpenClaw uses a shell allowlist, add "rad".\n');
14321
14368
  } else {
14322
- process.stderr.write('\nRun "rad skill" to see the skill file for your AI agent.\n');
14323
- }
14324
- process.stderr.write("\nSetup complete! Ask your AI agent to make a phone call.\n");
14325
- process.stderr.write("\nQuick test:\n");
14369
+ process.stderr.write(
14370
+ "\nIf OpenClaw is on another machine, install the skill there with:\n"
14371
+ );
14372
+ process.stderr.write(" openclaw skills install ring-a-ding\n");
14373
+ }
14374
+ process.stderr.write("\nNext steps for OpenClaw:\n");
14375
+ process.stderr.write(" 1. Install the skill: openclaw skills install ring-a-ding\n");
14376
+ process.stderr.write(" 2. Check readiness: openclaw skills check\n");
14377
+ process.stderr.write(" 3. Start a fresh OpenClaw session\n");
14378
+ process.stderr.write(" 4. Ask OpenClaw to make the call\n");
14379
+ process.stderr.write("Docs: https://ringading.ai/openclaw\n");
14380
+ process.stderr.write("\nOptional CLI-only test:\n");
14326
14381
  process.stderr.write(' rad call "+15551234567" "Test call -- say hello and hang up"\n\n');
14327
14382
  } catch (err) {
14328
14383
  process.stderr.write(`Error: ${err.message}
@@ -14336,13 +14391,10 @@ Config saved to ${getConfigPath()}
14336
14391
 
14337
14392
  // src/commands/skill.ts
14338
14393
  import { readFileSync as readFileSync3 } from "node:fs";
14339
- import { dirname as dirname2, join as join3 } from "node:path";
14340
- import { fileURLToPath as fileURLToPath2 } from "node:url";
14341
- var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
14342
14394
  function registerSkillCommand(program2) {
14343
- program2.command("skill").description("Print the OpenClaw skill file for AI agents").action(() => {
14395
+ program2.command("skill").description("Print the bundled OpenClaw skill file for AI agents").action(() => {
14344
14396
  try {
14345
- const skillPath = join3(__dirname2, "..", "skills", "ring-a-ding", "SKILL.md");
14397
+ const skillPath = getBundledSkillPath();
14346
14398
  const content = readFileSync3(skillPath, "utf-8");
14347
14399
  process.stdout.write(content);
14348
14400
  } catch {
@@ -14398,7 +14450,17 @@ function registerWaitCommand(program2) {
14398
14450
 
14399
14451
  // src/index.ts
14400
14452
  var program = new Command();
14401
- program.name("rad").description("Ring-a-Ding CLI -- give AI agents the ability to make phone calls").version("0.1.0").option("--pretty", "Human-readable output instead of JSON");
14453
+ program.name("rad").description("Ring-a-Ding CLI for outbound AI phone calls in OpenClaw and other agents").version("0.1.1").option("--pretty", "Human-readable output instead of JSON");
14454
+ program.addHelpText(
14455
+ "after",
14456
+ [
14457
+ "",
14458
+ "Setup:",
14459
+ " Get your Ring-a-Ding API key: https://ringading.ai/account",
14460
+ " Get your OpenAI API key: https://platform.openai.com/api-keys",
14461
+ " Public OpenClaw guide: https://ringading.ai/openclaw"
14462
+ ].join("\n")
14463
+ );
14402
14464
  registerCallCommand(program);
14403
14465
  registerStatusCommand(program);
14404
14466
  registerWaitCommand(program);
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ring-a-ding
3
- description: Use the rad CLI to place outbound phone calls with an AI voice agent, then check status, wait for completion, or end calls.
4
- metadata: {"openclaw":{"requires":{"bins":["rad"],"env":["RAD_API_KEY","OPENAI_API_KEY"]},"primaryEnv":"RAD_API_KEY","install":[{"id":"node","kind":"node","package":"ring-a-ding-cli","bins":["rad"],"label":"Install Ring-a-Ding CLI (npm)"}]}}
3
+ description: Use the rad CLI to place outbound AI phone calls. Requires a Ring-a-Ding API key and an OpenAI API key.
4
+ metadata: {"openclaw":{"homepage":"https://ringading.ai/openclaw","requires":{"bins":["rad"],"env":["RAD_API_KEY","OPENAI_API_KEY"]},"primaryEnv":"RAD_API_KEY","install":[{"id":"node","kind":"node","package":"ring-a-ding-cli","bins":["rad"],"label":"Install Ring-a-Ding CLI (npm)"}]}}
5
5
  ---
6
6
 
7
7
  # Ring-a-Ding
@@ -13,6 +13,7 @@ voice conversation, and report back with the outcome.
13
13
 
14
14
  - The shell tool must be allowed to run `rad`.
15
15
  - `RAD_API_KEY` and `OPENAI_API_KEY` must be available for the agent run.
16
+ - Public setup guide: `https://ringading.ai/openclaw`
16
17
 
17
18
  OpenClaw config example:
18
19
 
package/package.json CHANGED
@@ -1,7 +1,17 @@
1
1
  {
2
2
  "name": "ring-a-ding-cli",
3
- "version": "0.1.0",
4
- "description": "CLI for Ring-a-Ding — give AI agents the ability to make phone calls",
3
+ "version": "0.1.1",
4
+ "description": "OpenClaw-ready CLI for Ring-a-Ding AI phone calls",
5
+ "homepage": "https://ringading.ai/openclaw",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/vlbeta/telephone-mcp.git",
9
+ "directory": "packages/cli"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/vlbeta/telephone-mcp/issues"
13
+ },
14
+ "keywords": ["openclaw", "openclaw-skill", "ai-phone-calls", "voice-agent", "openai", "cli"],
5
15
  "type": "module",
6
16
  "main": "dist/index.js",
7
17
  "bin": {
@@ -11,9 +21,13 @@
11
21
  "scripts": {
12
22
  "build": "rm -rf dist && esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js='#!/usr/bin/env node' --external:commander && mkdir -p dist/skills && cp -r skills/. dist/skills",
13
23
  "dev": "tsx src/index.ts",
24
+ "postinstall": "node -e \"if (!process.env.CI && !process.env.npm_config_user_agent?.includes('pnpm')) console.error(['Ring-a-Ding CLI installed.','Get your API key at https://ringading.ai/account','Install the OpenClaw skill with: openclaw skills install ring-a-ding','Docs: https://ringading.ai/openclaw'].join('\\n'))\"",
14
25
  "typecheck": "tsc --noEmit",
15
26
  "test": "vitest run"
16
27
  },
28
+ "engines": {
29
+ "node": ">=20.0.0"
30
+ },
17
31
  "dependencies": {
18
32
  "commander": "^13.0.0"
19
33
  },