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 +74 -0
- package/dist/index.js +94 -32
- package/dist/skills/ring-a-ding/SKILL.md +3 -2
- package/package.json +16 -2
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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("
|
|
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.
|
|
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 =
|
|
14305
|
-
if (
|
|
14306
|
-
process.stderr.write("\nOpenClaw detected
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
|
|
14313
|
-
|
|
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
|
-
|
|
14316
|
-
|
|
14356
|
+
} catch (err) {
|
|
14357
|
+
process.stderr.write(`Could not copy skill file: ${err.message}
|
|
14317
14358
|
`);
|
|
14318
|
-
|
|
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('\
|
|
14367
|
+
process.stderr.write('\nIf OpenClaw uses a shell allowlist, add "rad".\n');
|
|
14321
14368
|
} else {
|
|
14322
|
-
process.stderr.write(
|
|
14323
|
-
|
|
14324
|
-
|
|
14325
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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.
|
|
4
|
-
"description": "CLI for Ring-a-Ding
|
|
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
|
},
|