@mirage-cli/call-cli 0.1.8

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/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/bin.js ADDED
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env node
2
+ // src/cli.ts
3
+ import { Command as Command6 } from "commander";
4
+
5
+ // src/commands/call.ts
6
+ import { Command } from "commander";
7
+ import { existsSync } from "fs";
8
+
9
+ // src/shared/config.ts
10
+ import { homedir } from "os";
11
+ import { join } from "path";
12
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
13
+ var CONFIG_DIR = join(homedir(), ".config", "call");
14
+ var CONFIG_PATH = join(CONFIG_DIR, "config.json");
15
+ function loadFileConfig() {
16
+ try {
17
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
18
+ } catch {
19
+ return {};
20
+ }
21
+ }
22
+ function saveFileConfig(config) {
23
+ mkdirSync(CONFIG_DIR, { recursive: true });
24
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + `
25
+ `);
26
+ }
27
+ function getCliConfig() {
28
+ const file = loadFileConfig();
29
+ return {
30
+ serverUrl: process.env.CALL_SERVER_URL || file.server_url || "http://127.0.0.1:5556",
31
+ apiKey: process.env.CALL_API_KEY || file.api_key || undefined
32
+ };
33
+ }
34
+
35
+ // src/client.ts
36
+ class CallClient {
37
+ baseUrl;
38
+ authHeaders;
39
+ constructor(serverUrl) {
40
+ const config = getCliConfig();
41
+ this.baseUrl = (serverUrl ?? config.serverUrl).replace(/\/$/, "");
42
+ this.authHeaders = {};
43
+ if (config.apiKey) {
44
+ this.authHeaders["X-API-Key"] = config.apiKey;
45
+ }
46
+ }
47
+ async request(path, options) {
48
+ const url = `${this.baseUrl}${path}`;
49
+ const resp = await fetch(url, {
50
+ ...options,
51
+ headers: {
52
+ "Content-Type": "application/json",
53
+ ...this.authHeaders,
54
+ ...options?.headers
55
+ }
56
+ });
57
+ if (!resp.ok) {
58
+ const body = await resp.text();
59
+ let msg;
60
+ try {
61
+ msg = JSON.parse(body).error ?? body;
62
+ } catch {
63
+ msg = body;
64
+ }
65
+ throw new Error(`${resp.status}: ${msg}`);
66
+ }
67
+ return resp.json();
68
+ }
69
+ async health() {
70
+ return this.request("/health");
71
+ }
72
+ async initiateCall(phone, audioId, silencePrefixSecs) {
73
+ return this.request("/calls", {
74
+ method: "POST",
75
+ body: JSON.stringify({
76
+ phone,
77
+ audio_id: audioId,
78
+ silence_prefix_secs: silencePrefixSecs
79
+ })
80
+ });
81
+ }
82
+ async getCallStatus(id) {
83
+ return this.request(`/calls/${id}`);
84
+ }
85
+ async hangup(id) {
86
+ return this.request(`/calls/${id}`, { method: "DELETE" });
87
+ }
88
+ async tts(text, voiceId, modelId) {
89
+ return this.request("/audio/tts", {
90
+ method: "POST",
91
+ body: JSON.stringify({
92
+ text,
93
+ voice_id: voiceId,
94
+ model_id: modelId
95
+ })
96
+ });
97
+ }
98
+ async uploadAudio(filePath) {
99
+ const file = Bun.file(filePath);
100
+ const formData = new FormData;
101
+ formData.append("file", file);
102
+ const url = `${this.baseUrl}/audio/upload`;
103
+ const resp = await fetch(url, {
104
+ method: "POST",
105
+ body: formData,
106
+ headers: { ...this.authHeaders }
107
+ });
108
+ if (!resp.ok) {
109
+ const body = await resp.text();
110
+ throw new Error(`Upload failed (${resp.status}): ${body}`);
111
+ }
112
+ return resp.json();
113
+ }
114
+ }
115
+
116
+ // src/format.ts
117
+ import chalk from "chalk";
118
+ function error(msg) {
119
+ console.error(chalk.red(`Error: ${msg}`));
120
+ }
121
+ function success(msg) {
122
+ console.log(chalk.green(msg));
123
+ }
124
+ function info(msg) {
125
+ console.log(chalk.cyan(msg));
126
+ }
127
+ var statusColors = {
128
+ initiating: chalk.yellow,
129
+ ringing: chalk.yellow,
130
+ playing: chalk.blue,
131
+ completed: chalk.green,
132
+ failed: chalk.red
133
+ };
134
+ function formatCallState(call) {
135
+ const colorFn = statusColors[call.status] ?? chalk.white;
136
+ const lines = [
137
+ `${chalk.bold("Call")} ${chalk.dim(call.id)}`,
138
+ ` Phone: ${call.phone}`,
139
+ ` Status: ${colorFn(call.status)}`,
140
+ ` Audio: ${call.audio_id}`,
141
+ ` Started: ${call.created_at}`
142
+ ];
143
+ if (call.completed_at) {
144
+ lines.push(` Ended: ${call.completed_at}`);
145
+ }
146
+ if (call.error) {
147
+ lines.push(` Error: ${chalk.red(call.error)}`);
148
+ }
149
+ return lines.join(`
150
+ `);
151
+ }
152
+
153
+ // src/commands/call.ts
154
+ var callCommand = new Command("call").description("Initiate a phone call and play audio").argument("<phone>", "Phone number to call (e.g. +15551234567)").argument("[audio]", "Audio file path or audio ID (aud_xxx)").option("-t, --text <text>", "Generate audio from text via ElevenLabs TTS").option("--voice <id>", "ElevenLabs voice ID").option("--model <id>", "ElevenLabs model ID").option("-s, --silence <secs>", "Silence prefix seconds", "12").option("-w, --wait", "Wait for call to complete").action(async (phone, audio, opts) => {
155
+ try {
156
+ const client = new CallClient;
157
+ let audioId;
158
+ if (opts.text) {
159
+ info(`Generating speech: "${opts.text.slice(0, 60)}${opts.text.length > 60 ? "..." : ""}"`);
160
+ const meta = await client.tts(opts.text, opts.voice, opts.model);
161
+ audioId = meta.id;
162
+ success(`TTS: ${meta.id} (${meta.duration_secs}s)`);
163
+ } else if (!audio) {
164
+ error("Provide an audio file/ID or use --text for TTS");
165
+ process.exit(1);
166
+ } else if (existsSync(audio)) {
167
+ info(`Uploading ${audio}...`);
168
+ const meta = await client.uploadAudio(audio);
169
+ audioId = meta.id;
170
+ success(`Uploaded: ${meta.id} (${meta.duration_secs}s)`);
171
+ } else if (audio.startsWith("aud_")) {
172
+ audioId = audio;
173
+ } else {
174
+ error(`File not found and not an audio ID: ${audio}`);
175
+ process.exit(1);
176
+ }
177
+ info(`Calling ${phone}...`);
178
+ const call = await client.initiateCall(phone, audioId, parseInt(opts.silence, 10));
179
+ console.log(formatCallState(call));
180
+ if (opts.wait) {
181
+ info("Waiting for call to complete...");
182
+ let status = call.status;
183
+ while (status === "initiating" || status === "ringing" || status === "playing") {
184
+ await Bun.sleep(2000);
185
+ const updated = await client.getCallStatus(call.id);
186
+ if (updated.status !== status) {
187
+ status = updated.status;
188
+ console.log(formatCallState(updated));
189
+ }
190
+ }
191
+ }
192
+ } catch (err) {
193
+ error(String(err));
194
+ process.exit(1);
195
+ }
196
+ });
197
+
198
+ // src/commands/status.ts
199
+ import { Command as Command2 } from "commander";
200
+ var statusCommand = new Command2("status").description("Check call status").argument("[id]", "Call ID (default: latest)").action(async (id) => {
201
+ try {
202
+ const client = new CallClient;
203
+ const call = await client.getCallStatus(id ?? "latest");
204
+ console.log(formatCallState(call));
205
+ } catch (err) {
206
+ error(String(err));
207
+ process.exit(1);
208
+ }
209
+ });
210
+
211
+ // src/commands/hangup.ts
212
+ import { Command as Command3 } from "commander";
213
+ var hangupCommand = new Command3("hangup").description("Hang up an active call").argument("<id>", "Call ID").action(async (id) => {
214
+ try {
215
+ const client = new CallClient;
216
+ const call = await client.hangup(id);
217
+ success("Call ended.");
218
+ console.log(formatCallState(call));
219
+ } catch (err) {
220
+ error(String(err));
221
+ process.exit(1);
222
+ }
223
+ });
224
+
225
+ // src/commands/upload.ts
226
+ import { Command as Command4 } from "commander";
227
+ var uploadCommand = new Command4("upload").description("Upload and convert an audio file").argument("<file>", "Audio file to upload").action(async (file) => {
228
+ try {
229
+ const client = new CallClient;
230
+ const meta = await client.uploadAudio(file);
231
+ success(`Uploaded: ${meta.id} (${meta.duration_secs}s)`);
232
+ } catch (err) {
233
+ error(String(err));
234
+ process.exit(1);
235
+ }
236
+ });
237
+
238
+ // src/commands/config.ts
239
+ import { Command as Command5 } from "commander";
240
+ var VALID_KEYS = ["server_url", "elevenlabs_api_key", "api_key"];
241
+ var configCommand = new Command5("config").description("Get or set CLI configuration").argument("[key]", "Config key (server_url, elevenlabs_api_key)").argument("[value]", "Value to set").action((key, value) => {
242
+ const config = loadFileConfig();
243
+ if (!key) {
244
+ info("Config (~/.config/call/config.json):");
245
+ for (const k of VALID_KEYS) {
246
+ const v = config[k];
247
+ if (v) {
248
+ const display = k.includes("key") ? v.slice(0, 8) + "..." : v;
249
+ console.log(` ${k}: ${display}`);
250
+ }
251
+ }
252
+ if (!VALID_KEYS.some((k) => config[k])) {
253
+ console.log(" (empty)");
254
+ }
255
+ return;
256
+ }
257
+ if (!VALID_KEYS.includes(key)) {
258
+ error(`Unknown key: ${key}. Valid keys: ${VALID_KEYS.join(", ")}`);
259
+ process.exit(1);
260
+ }
261
+ if (value === undefined) {
262
+ const v = config[key];
263
+ if (v) {
264
+ console.log(v);
265
+ } else {
266
+ info(`${key} is not set`);
267
+ }
268
+ } else {
269
+ config[key] = value;
270
+ saveFileConfig(config);
271
+ const display = key.includes("key") ? value.slice(0, 8) + "..." : value;
272
+ success(`Set ${key} = ${display}`);
273
+ }
274
+ });
275
+
276
+ // src/cli.ts
277
+ var cached = null;
278
+ function buildProgram() {
279
+ if (cached !== null)
280
+ return cached;
281
+ const program = new Command6().name("call").description("Phone call + audio injection CLI").version("0.1.6");
282
+ program.addCommand(callCommand);
283
+ program.addCommand(statusCommand);
284
+ program.addCommand(hangupCommand);
285
+ program.addCommand(uploadCommand);
286
+ program.addCommand(configCommand);
287
+ program.action(async () => {
288
+ try {
289
+ const client = new CallClient;
290
+ const health = await client.health();
291
+ const btStatus = health.bluetooth_connected ? "connected" : "disconnected";
292
+ process.stdout.write(`call-server: ${health.status} | BT: ${btStatus} | Active calls: ${health.active_calls}
293
+ `);
294
+ } catch (err) {
295
+ process.stderr.write(`Cannot reach call-server: ${err}
296
+ `);
297
+ process.exit(1);
298
+ }
299
+ });
300
+ cached = program;
301
+ return program;
302
+ }
303
+
304
+ // src/bin.ts
305
+ buildProgram().parseAsync(process.argv).catch((err) => {
306
+ const message = err instanceof Error ? err.message : String(err);
307
+ process.stderr.write(JSON.stringify({ error: message }) + `
308
+ `);
309
+ process.exit(1);
310
+ });
package/dist/cli.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * call CLI as a commander.js program. Vendored from call-cli/src/cli (the
3
+ * server half of the upstream repo is not shipped). Auth + config via
4
+ * `~/.config/call/config.json` or the `CALL_SERVER_URL` / `CALL_API_KEY`
5
+ * env vars.
6
+ *
7
+ * Read-only subcommands: `status`, `config show`.
8
+ * Mutating: `call` (place an outbound call with TTS or pre-uploaded audio),
9
+ * `hangup`, `upload`, `config set`. These reach into the Cloudflare Worker
10
+ * backend which relays to an Android phone over Bluetooth/ADB.
11
+ */
12
+ import { Command } from "commander";
13
+ export declare function buildProgram(): Command;
14
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,wBAAgB,YAAY,IAAI,OAAO,CA6BtC"}
package/dist/cli.js ADDED
@@ -0,0 +1,304 @@
1
+ // src/cli.ts
2
+ import { Command as Command6 } from "commander";
3
+
4
+ // src/commands/call.ts
5
+ import { Command } from "commander";
6
+ import { existsSync } from "fs";
7
+
8
+ // src/shared/config.ts
9
+ import { homedir } from "os";
10
+ import { join } from "path";
11
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
12
+ var CONFIG_DIR = join(homedir(), ".config", "call");
13
+ var CONFIG_PATH = join(CONFIG_DIR, "config.json");
14
+ function loadFileConfig() {
15
+ try {
16
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
17
+ } catch {
18
+ return {};
19
+ }
20
+ }
21
+ function saveFileConfig(config) {
22
+ mkdirSync(CONFIG_DIR, { recursive: true });
23
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + `
24
+ `);
25
+ }
26
+ function getCliConfig() {
27
+ const file = loadFileConfig();
28
+ return {
29
+ serverUrl: process.env.CALL_SERVER_URL || file.server_url || "http://127.0.0.1:5556",
30
+ apiKey: process.env.CALL_API_KEY || file.api_key || undefined
31
+ };
32
+ }
33
+
34
+ // src/client.ts
35
+ class CallClient {
36
+ baseUrl;
37
+ authHeaders;
38
+ constructor(serverUrl) {
39
+ const config = getCliConfig();
40
+ this.baseUrl = (serverUrl ?? config.serverUrl).replace(/\/$/, "");
41
+ this.authHeaders = {};
42
+ if (config.apiKey) {
43
+ this.authHeaders["X-API-Key"] = config.apiKey;
44
+ }
45
+ }
46
+ async request(path, options) {
47
+ const url = `${this.baseUrl}${path}`;
48
+ const resp = await fetch(url, {
49
+ ...options,
50
+ headers: {
51
+ "Content-Type": "application/json",
52
+ ...this.authHeaders,
53
+ ...options?.headers
54
+ }
55
+ });
56
+ if (!resp.ok) {
57
+ const body = await resp.text();
58
+ let msg;
59
+ try {
60
+ msg = JSON.parse(body).error ?? body;
61
+ } catch {
62
+ msg = body;
63
+ }
64
+ throw new Error(`${resp.status}: ${msg}`);
65
+ }
66
+ return resp.json();
67
+ }
68
+ async health() {
69
+ return this.request("/health");
70
+ }
71
+ async initiateCall(phone, audioId, silencePrefixSecs) {
72
+ return this.request("/calls", {
73
+ method: "POST",
74
+ body: JSON.stringify({
75
+ phone,
76
+ audio_id: audioId,
77
+ silence_prefix_secs: silencePrefixSecs
78
+ })
79
+ });
80
+ }
81
+ async getCallStatus(id) {
82
+ return this.request(`/calls/${id}`);
83
+ }
84
+ async hangup(id) {
85
+ return this.request(`/calls/${id}`, { method: "DELETE" });
86
+ }
87
+ async tts(text, voiceId, modelId) {
88
+ return this.request("/audio/tts", {
89
+ method: "POST",
90
+ body: JSON.stringify({
91
+ text,
92
+ voice_id: voiceId,
93
+ model_id: modelId
94
+ })
95
+ });
96
+ }
97
+ async uploadAudio(filePath) {
98
+ const file = Bun.file(filePath);
99
+ const formData = new FormData;
100
+ formData.append("file", file);
101
+ const url = `${this.baseUrl}/audio/upload`;
102
+ const resp = await fetch(url, {
103
+ method: "POST",
104
+ body: formData,
105
+ headers: { ...this.authHeaders }
106
+ });
107
+ if (!resp.ok) {
108
+ const body = await resp.text();
109
+ throw new Error(`Upload failed (${resp.status}): ${body}`);
110
+ }
111
+ return resp.json();
112
+ }
113
+ }
114
+
115
+ // src/format.ts
116
+ import chalk from "chalk";
117
+ function error(msg) {
118
+ console.error(chalk.red(`Error: ${msg}`));
119
+ }
120
+ function success(msg) {
121
+ console.log(chalk.green(msg));
122
+ }
123
+ function info(msg) {
124
+ console.log(chalk.cyan(msg));
125
+ }
126
+ var statusColors = {
127
+ initiating: chalk.yellow,
128
+ ringing: chalk.yellow,
129
+ playing: chalk.blue,
130
+ completed: chalk.green,
131
+ failed: chalk.red
132
+ };
133
+ function formatCallState(call) {
134
+ const colorFn = statusColors[call.status] ?? chalk.white;
135
+ const lines = [
136
+ `${chalk.bold("Call")} ${chalk.dim(call.id)}`,
137
+ ` Phone: ${call.phone}`,
138
+ ` Status: ${colorFn(call.status)}`,
139
+ ` Audio: ${call.audio_id}`,
140
+ ` Started: ${call.created_at}`
141
+ ];
142
+ if (call.completed_at) {
143
+ lines.push(` Ended: ${call.completed_at}`);
144
+ }
145
+ if (call.error) {
146
+ lines.push(` Error: ${chalk.red(call.error)}`);
147
+ }
148
+ return lines.join(`
149
+ `);
150
+ }
151
+
152
+ // src/commands/call.ts
153
+ var callCommand = new Command("call").description("Initiate a phone call and play audio").argument("<phone>", "Phone number to call (e.g. +15551234567)").argument("[audio]", "Audio file path or audio ID (aud_xxx)").option("-t, --text <text>", "Generate audio from text via ElevenLabs TTS").option("--voice <id>", "ElevenLabs voice ID").option("--model <id>", "ElevenLabs model ID").option("-s, --silence <secs>", "Silence prefix seconds", "12").option("-w, --wait", "Wait for call to complete").action(async (phone, audio, opts) => {
154
+ try {
155
+ const client = new CallClient;
156
+ let audioId;
157
+ if (opts.text) {
158
+ info(`Generating speech: "${opts.text.slice(0, 60)}${opts.text.length > 60 ? "..." : ""}"`);
159
+ const meta = await client.tts(opts.text, opts.voice, opts.model);
160
+ audioId = meta.id;
161
+ success(`TTS: ${meta.id} (${meta.duration_secs}s)`);
162
+ } else if (!audio) {
163
+ error("Provide an audio file/ID or use --text for TTS");
164
+ process.exit(1);
165
+ } else if (existsSync(audio)) {
166
+ info(`Uploading ${audio}...`);
167
+ const meta = await client.uploadAudio(audio);
168
+ audioId = meta.id;
169
+ success(`Uploaded: ${meta.id} (${meta.duration_secs}s)`);
170
+ } else if (audio.startsWith("aud_")) {
171
+ audioId = audio;
172
+ } else {
173
+ error(`File not found and not an audio ID: ${audio}`);
174
+ process.exit(1);
175
+ }
176
+ info(`Calling ${phone}...`);
177
+ const call = await client.initiateCall(phone, audioId, parseInt(opts.silence, 10));
178
+ console.log(formatCallState(call));
179
+ if (opts.wait) {
180
+ info("Waiting for call to complete...");
181
+ let status = call.status;
182
+ while (status === "initiating" || status === "ringing" || status === "playing") {
183
+ await Bun.sleep(2000);
184
+ const updated = await client.getCallStatus(call.id);
185
+ if (updated.status !== status) {
186
+ status = updated.status;
187
+ console.log(formatCallState(updated));
188
+ }
189
+ }
190
+ }
191
+ } catch (err) {
192
+ error(String(err));
193
+ process.exit(1);
194
+ }
195
+ });
196
+
197
+ // src/commands/status.ts
198
+ import { Command as Command2 } from "commander";
199
+ var statusCommand = new Command2("status").description("Check call status").argument("[id]", "Call ID (default: latest)").action(async (id) => {
200
+ try {
201
+ const client = new CallClient;
202
+ const call = await client.getCallStatus(id ?? "latest");
203
+ console.log(formatCallState(call));
204
+ } catch (err) {
205
+ error(String(err));
206
+ process.exit(1);
207
+ }
208
+ });
209
+
210
+ // src/commands/hangup.ts
211
+ import { Command as Command3 } from "commander";
212
+ var hangupCommand = new Command3("hangup").description("Hang up an active call").argument("<id>", "Call ID").action(async (id) => {
213
+ try {
214
+ const client = new CallClient;
215
+ const call = await client.hangup(id);
216
+ success("Call ended.");
217
+ console.log(formatCallState(call));
218
+ } catch (err) {
219
+ error(String(err));
220
+ process.exit(1);
221
+ }
222
+ });
223
+
224
+ // src/commands/upload.ts
225
+ import { Command as Command4 } from "commander";
226
+ var uploadCommand = new Command4("upload").description("Upload and convert an audio file").argument("<file>", "Audio file to upload").action(async (file) => {
227
+ try {
228
+ const client = new CallClient;
229
+ const meta = await client.uploadAudio(file);
230
+ success(`Uploaded: ${meta.id} (${meta.duration_secs}s)`);
231
+ } catch (err) {
232
+ error(String(err));
233
+ process.exit(1);
234
+ }
235
+ });
236
+
237
+ // src/commands/config.ts
238
+ import { Command as Command5 } from "commander";
239
+ var VALID_KEYS = ["server_url", "elevenlabs_api_key", "api_key"];
240
+ var configCommand = new Command5("config").description("Get or set CLI configuration").argument("[key]", "Config key (server_url, elevenlabs_api_key)").argument("[value]", "Value to set").action((key, value) => {
241
+ const config = loadFileConfig();
242
+ if (!key) {
243
+ info("Config (~/.config/call/config.json):");
244
+ for (const k of VALID_KEYS) {
245
+ const v = config[k];
246
+ if (v) {
247
+ const display = k.includes("key") ? v.slice(0, 8) + "..." : v;
248
+ console.log(` ${k}: ${display}`);
249
+ }
250
+ }
251
+ if (!VALID_KEYS.some((k) => config[k])) {
252
+ console.log(" (empty)");
253
+ }
254
+ return;
255
+ }
256
+ if (!VALID_KEYS.includes(key)) {
257
+ error(`Unknown key: ${key}. Valid keys: ${VALID_KEYS.join(", ")}`);
258
+ process.exit(1);
259
+ }
260
+ if (value === undefined) {
261
+ const v = config[key];
262
+ if (v) {
263
+ console.log(v);
264
+ } else {
265
+ info(`${key} is not set`);
266
+ }
267
+ } else {
268
+ config[key] = value;
269
+ saveFileConfig(config);
270
+ const display = key.includes("key") ? value.slice(0, 8) + "..." : value;
271
+ success(`Set ${key} = ${display}`);
272
+ }
273
+ });
274
+
275
+ // src/cli.ts
276
+ var cached = null;
277
+ function buildProgram() {
278
+ if (cached !== null)
279
+ return cached;
280
+ const program = new Command6().name("call").description("Phone call + audio injection CLI").version("0.1.6");
281
+ program.addCommand(callCommand);
282
+ program.addCommand(statusCommand);
283
+ program.addCommand(hangupCommand);
284
+ program.addCommand(uploadCommand);
285
+ program.addCommand(configCommand);
286
+ program.action(async () => {
287
+ try {
288
+ const client = new CallClient;
289
+ const health = await client.health();
290
+ const btStatus = health.bluetooth_connected ? "connected" : "disconnected";
291
+ process.stdout.write(`call-server: ${health.status} | BT: ${btStatus} | Active calls: ${health.active_calls}
292
+ `);
293
+ } catch (err) {
294
+ process.stderr.write(`Cannot reach call-server: ${err}
295
+ `);
296
+ process.exit(1);
297
+ }
298
+ });
299
+ cached = program;
300
+ return program;
301
+ }
302
+ export {
303
+ buildProgram
304
+ };
@@ -0,0 +1,14 @@
1
+ import type { CallState, AudioMeta, HealthResponse } from "./shared/types.ts";
2
+ export declare class CallClient {
3
+ private baseUrl;
4
+ private authHeaders;
5
+ constructor(serverUrl?: string);
6
+ private request;
7
+ health(): Promise<HealthResponse>;
8
+ initiateCall(phone: string, audioId: string, silencePrefixSecs?: number): Promise<CallState>;
9
+ getCallStatus(id: string): Promise<CallState>;
10
+ hangup(id: string): Promise<CallState>;
11
+ tts(text: string, voiceId?: string, modelId?: string): Promise<AudioMeta>;
12
+ uploadAudio(filePath: string): Promise<AudioMeta>;
13
+ }
14
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,SAAS,EACT,cAAc,EACf,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAyB;gBAEhC,SAAS,CAAC,EAAE,MAAM;YAShB,OAAO;IA4Bf,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC;IAIjC,YAAY,CAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,SAAS,CAAC;IAWf,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAI7C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAItC,GAAG,CACP,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,SAAS,CAAC;IAWf,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;CAmBxD"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const callCommand: Command;
3
+ //# sourceMappingURL=call.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"call.d.ts","sourceRoot":"","sources":["../../src/commands/call.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,eAAO,MAAM,WAAW,SAgEpB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const configCommand: Command;
3
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,eAAO,MAAM,aAAa,SA2CtB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const hangupCommand: Command;
3
+ //# sourceMappingURL=hangup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hangup.d.ts","sourceRoot":"","sources":["../../src/commands/hangup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,eAAO,MAAM,aAAa,SAatB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const statusCommand: Command;
3
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,eAAO,MAAM,aAAa,SAYtB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const uploadCommand: Command;
3
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/commands/upload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,eAAO,MAAM,aAAa,SAYtB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { CallState } from "./shared/types.ts";
2
+ export declare function error(msg: string): void;
3
+ export declare function success(msg: string): void;
4
+ export declare function info(msg: string): void;
5
+ export declare function formatCallState(call: CallState): string;
6
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEvC;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEzC;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEtC;AAUD,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAgBvD"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Library entrypoint. Exposes `buildProgram()` for in-process wrappers like
3
+ * `@mirage-cli/call`, plus the client and shared types.
4
+ */
5
+ export { buildProgram } from "./cli.ts";
6
+ export { CallClient } from "./client.ts";
7
+ export { getCliConfig, loadFileConfig, saveFileConfig } from "./shared/config.ts";
8
+ export type { CallState, AudioMeta, HealthResponse } from "./shared/types.ts";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAClF,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,308 @@
1
+ // src/cli.ts
2
+ import { Command as Command6 } from "commander";
3
+
4
+ // src/commands/call.ts
5
+ import { Command } from "commander";
6
+ import { existsSync } from "fs";
7
+
8
+ // src/shared/config.ts
9
+ import { homedir } from "os";
10
+ import { join } from "path";
11
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
12
+ var CONFIG_DIR = join(homedir(), ".config", "call");
13
+ var CONFIG_PATH = join(CONFIG_DIR, "config.json");
14
+ function loadFileConfig() {
15
+ try {
16
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
17
+ } catch {
18
+ return {};
19
+ }
20
+ }
21
+ function saveFileConfig(config) {
22
+ mkdirSync(CONFIG_DIR, { recursive: true });
23
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + `
24
+ `);
25
+ }
26
+ function getCliConfig() {
27
+ const file = loadFileConfig();
28
+ return {
29
+ serverUrl: process.env.CALL_SERVER_URL || file.server_url || "http://127.0.0.1:5556",
30
+ apiKey: process.env.CALL_API_KEY || file.api_key || undefined
31
+ };
32
+ }
33
+
34
+ // src/client.ts
35
+ class CallClient {
36
+ baseUrl;
37
+ authHeaders;
38
+ constructor(serverUrl) {
39
+ const config = getCliConfig();
40
+ this.baseUrl = (serverUrl ?? config.serverUrl).replace(/\/$/, "");
41
+ this.authHeaders = {};
42
+ if (config.apiKey) {
43
+ this.authHeaders["X-API-Key"] = config.apiKey;
44
+ }
45
+ }
46
+ async request(path, options) {
47
+ const url = `${this.baseUrl}${path}`;
48
+ const resp = await fetch(url, {
49
+ ...options,
50
+ headers: {
51
+ "Content-Type": "application/json",
52
+ ...this.authHeaders,
53
+ ...options?.headers
54
+ }
55
+ });
56
+ if (!resp.ok) {
57
+ const body = await resp.text();
58
+ let msg;
59
+ try {
60
+ msg = JSON.parse(body).error ?? body;
61
+ } catch {
62
+ msg = body;
63
+ }
64
+ throw new Error(`${resp.status}: ${msg}`);
65
+ }
66
+ return resp.json();
67
+ }
68
+ async health() {
69
+ return this.request("/health");
70
+ }
71
+ async initiateCall(phone, audioId, silencePrefixSecs) {
72
+ return this.request("/calls", {
73
+ method: "POST",
74
+ body: JSON.stringify({
75
+ phone,
76
+ audio_id: audioId,
77
+ silence_prefix_secs: silencePrefixSecs
78
+ })
79
+ });
80
+ }
81
+ async getCallStatus(id) {
82
+ return this.request(`/calls/${id}`);
83
+ }
84
+ async hangup(id) {
85
+ return this.request(`/calls/${id}`, { method: "DELETE" });
86
+ }
87
+ async tts(text, voiceId, modelId) {
88
+ return this.request("/audio/tts", {
89
+ method: "POST",
90
+ body: JSON.stringify({
91
+ text,
92
+ voice_id: voiceId,
93
+ model_id: modelId
94
+ })
95
+ });
96
+ }
97
+ async uploadAudio(filePath) {
98
+ const file = Bun.file(filePath);
99
+ const formData = new FormData;
100
+ formData.append("file", file);
101
+ const url = `${this.baseUrl}/audio/upload`;
102
+ const resp = await fetch(url, {
103
+ method: "POST",
104
+ body: formData,
105
+ headers: { ...this.authHeaders }
106
+ });
107
+ if (!resp.ok) {
108
+ const body = await resp.text();
109
+ throw new Error(`Upload failed (${resp.status}): ${body}`);
110
+ }
111
+ return resp.json();
112
+ }
113
+ }
114
+
115
+ // src/format.ts
116
+ import chalk from "chalk";
117
+ function error(msg) {
118
+ console.error(chalk.red(`Error: ${msg}`));
119
+ }
120
+ function success(msg) {
121
+ console.log(chalk.green(msg));
122
+ }
123
+ function info(msg) {
124
+ console.log(chalk.cyan(msg));
125
+ }
126
+ var statusColors = {
127
+ initiating: chalk.yellow,
128
+ ringing: chalk.yellow,
129
+ playing: chalk.blue,
130
+ completed: chalk.green,
131
+ failed: chalk.red
132
+ };
133
+ function formatCallState(call) {
134
+ const colorFn = statusColors[call.status] ?? chalk.white;
135
+ const lines = [
136
+ `${chalk.bold("Call")} ${chalk.dim(call.id)}`,
137
+ ` Phone: ${call.phone}`,
138
+ ` Status: ${colorFn(call.status)}`,
139
+ ` Audio: ${call.audio_id}`,
140
+ ` Started: ${call.created_at}`
141
+ ];
142
+ if (call.completed_at) {
143
+ lines.push(` Ended: ${call.completed_at}`);
144
+ }
145
+ if (call.error) {
146
+ lines.push(` Error: ${chalk.red(call.error)}`);
147
+ }
148
+ return lines.join(`
149
+ `);
150
+ }
151
+
152
+ // src/commands/call.ts
153
+ var callCommand = new Command("call").description("Initiate a phone call and play audio").argument("<phone>", "Phone number to call (e.g. +15551234567)").argument("[audio]", "Audio file path or audio ID (aud_xxx)").option("-t, --text <text>", "Generate audio from text via ElevenLabs TTS").option("--voice <id>", "ElevenLabs voice ID").option("--model <id>", "ElevenLabs model ID").option("-s, --silence <secs>", "Silence prefix seconds", "12").option("-w, --wait", "Wait for call to complete").action(async (phone, audio, opts) => {
154
+ try {
155
+ const client = new CallClient;
156
+ let audioId;
157
+ if (opts.text) {
158
+ info(`Generating speech: "${opts.text.slice(0, 60)}${opts.text.length > 60 ? "..." : ""}"`);
159
+ const meta = await client.tts(opts.text, opts.voice, opts.model);
160
+ audioId = meta.id;
161
+ success(`TTS: ${meta.id} (${meta.duration_secs}s)`);
162
+ } else if (!audio) {
163
+ error("Provide an audio file/ID or use --text for TTS");
164
+ process.exit(1);
165
+ } else if (existsSync(audio)) {
166
+ info(`Uploading ${audio}...`);
167
+ const meta = await client.uploadAudio(audio);
168
+ audioId = meta.id;
169
+ success(`Uploaded: ${meta.id} (${meta.duration_secs}s)`);
170
+ } else if (audio.startsWith("aud_")) {
171
+ audioId = audio;
172
+ } else {
173
+ error(`File not found and not an audio ID: ${audio}`);
174
+ process.exit(1);
175
+ }
176
+ info(`Calling ${phone}...`);
177
+ const call = await client.initiateCall(phone, audioId, parseInt(opts.silence, 10));
178
+ console.log(formatCallState(call));
179
+ if (opts.wait) {
180
+ info("Waiting for call to complete...");
181
+ let status = call.status;
182
+ while (status === "initiating" || status === "ringing" || status === "playing") {
183
+ await Bun.sleep(2000);
184
+ const updated = await client.getCallStatus(call.id);
185
+ if (updated.status !== status) {
186
+ status = updated.status;
187
+ console.log(formatCallState(updated));
188
+ }
189
+ }
190
+ }
191
+ } catch (err) {
192
+ error(String(err));
193
+ process.exit(1);
194
+ }
195
+ });
196
+
197
+ // src/commands/status.ts
198
+ import { Command as Command2 } from "commander";
199
+ var statusCommand = new Command2("status").description("Check call status").argument("[id]", "Call ID (default: latest)").action(async (id) => {
200
+ try {
201
+ const client = new CallClient;
202
+ const call = await client.getCallStatus(id ?? "latest");
203
+ console.log(formatCallState(call));
204
+ } catch (err) {
205
+ error(String(err));
206
+ process.exit(1);
207
+ }
208
+ });
209
+
210
+ // src/commands/hangup.ts
211
+ import { Command as Command3 } from "commander";
212
+ var hangupCommand = new Command3("hangup").description("Hang up an active call").argument("<id>", "Call ID").action(async (id) => {
213
+ try {
214
+ const client = new CallClient;
215
+ const call = await client.hangup(id);
216
+ success("Call ended.");
217
+ console.log(formatCallState(call));
218
+ } catch (err) {
219
+ error(String(err));
220
+ process.exit(1);
221
+ }
222
+ });
223
+
224
+ // src/commands/upload.ts
225
+ import { Command as Command4 } from "commander";
226
+ var uploadCommand = new Command4("upload").description("Upload and convert an audio file").argument("<file>", "Audio file to upload").action(async (file) => {
227
+ try {
228
+ const client = new CallClient;
229
+ const meta = await client.uploadAudio(file);
230
+ success(`Uploaded: ${meta.id} (${meta.duration_secs}s)`);
231
+ } catch (err) {
232
+ error(String(err));
233
+ process.exit(1);
234
+ }
235
+ });
236
+
237
+ // src/commands/config.ts
238
+ import { Command as Command5 } from "commander";
239
+ var VALID_KEYS = ["server_url", "elevenlabs_api_key", "api_key"];
240
+ var configCommand = new Command5("config").description("Get or set CLI configuration").argument("[key]", "Config key (server_url, elevenlabs_api_key)").argument("[value]", "Value to set").action((key, value) => {
241
+ const config = loadFileConfig();
242
+ if (!key) {
243
+ info("Config (~/.config/call/config.json):");
244
+ for (const k of VALID_KEYS) {
245
+ const v = config[k];
246
+ if (v) {
247
+ const display = k.includes("key") ? v.slice(0, 8) + "..." : v;
248
+ console.log(` ${k}: ${display}`);
249
+ }
250
+ }
251
+ if (!VALID_KEYS.some((k) => config[k])) {
252
+ console.log(" (empty)");
253
+ }
254
+ return;
255
+ }
256
+ if (!VALID_KEYS.includes(key)) {
257
+ error(`Unknown key: ${key}. Valid keys: ${VALID_KEYS.join(", ")}`);
258
+ process.exit(1);
259
+ }
260
+ if (value === undefined) {
261
+ const v = config[key];
262
+ if (v) {
263
+ console.log(v);
264
+ } else {
265
+ info(`${key} is not set`);
266
+ }
267
+ } else {
268
+ config[key] = value;
269
+ saveFileConfig(config);
270
+ const display = key.includes("key") ? value.slice(0, 8) + "..." : value;
271
+ success(`Set ${key} = ${display}`);
272
+ }
273
+ });
274
+
275
+ // src/cli.ts
276
+ var cached = null;
277
+ function buildProgram() {
278
+ if (cached !== null)
279
+ return cached;
280
+ const program = new Command6().name("call").description("Phone call + audio injection CLI").version("0.1.6");
281
+ program.addCommand(callCommand);
282
+ program.addCommand(statusCommand);
283
+ program.addCommand(hangupCommand);
284
+ program.addCommand(uploadCommand);
285
+ program.addCommand(configCommand);
286
+ program.action(async () => {
287
+ try {
288
+ const client = new CallClient;
289
+ const health = await client.health();
290
+ const btStatus = health.bluetooth_connected ? "connected" : "disconnected";
291
+ process.stdout.write(`call-server: ${health.status} | BT: ${btStatus} | Active calls: ${health.active_calls}
292
+ `);
293
+ } catch (err) {
294
+ process.stderr.write(`Cannot reach call-server: ${err}
295
+ `);
296
+ process.exit(1);
297
+ }
298
+ });
299
+ cached = program;
300
+ return program;
301
+ }
302
+ export {
303
+ saveFileConfig,
304
+ loadFileConfig,
305
+ getCliConfig,
306
+ buildProgram,
307
+ CallClient
308
+ };
@@ -0,0 +1,13 @@
1
+ export interface CliConfig {
2
+ serverUrl: string;
3
+ apiKey?: string;
4
+ }
5
+ export interface FileConfig {
6
+ server_url?: string;
7
+ elevenlabs_api_key?: string;
8
+ api_key?: string;
9
+ }
10
+ export declare function loadFileConfig(): FileConfig;
11
+ export declare function saveFileConfig(config: FileConfig): void;
12
+ export declare function getCliConfig(): CliConfig;
13
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/shared/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAKD,wBAAgB,cAAc,IAAI,UAAU,CAM3C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAGvD;AAED,wBAAgB,YAAY,IAAI,SAAS,CASxC"}
@@ -0,0 +1,22 @@
1
+ export type CallStatus = "initiating" | "ringing" | "playing" | "completed" | "failed";
2
+ export interface CallState {
3
+ id: string;
4
+ phone: string;
5
+ status: CallStatus;
6
+ audio_id: string;
7
+ created_at: string;
8
+ completed_at: string | null;
9
+ error: string | null;
10
+ }
11
+ export interface AudioMeta {
12
+ id: string;
13
+ original_name: string;
14
+ duration_secs: number;
15
+ path: string;
16
+ }
17
+ export interface HealthResponse {
18
+ status: "ok" | "error";
19
+ active_calls: number;
20
+ bluetooth_connected: boolean;
21
+ }
22
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAClB,YAAY,GACZ,SAAS,GACT,SAAS,GACT,WAAW,GACX,QAAQ,CAAC;AAEb,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,OAAO,CAAC;CAC9B"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@mirage-cli/call-cli",
3
+ "version": "0.1.8",
4
+ "description": "Phone call + audio injection CLI — make calls with TTS or pre-uploaded audio via an Android-bridge Cloudflare Worker. Mirage / Cloudflare-Worker compatible.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/alexbruf/mirage-cli.git",
9
+ "directory": "packages/call-cli"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/alexbruf/mirage-cli/issues"
13
+ },
14
+ "homepage": "https://github.com/alexbruf/mirage-cli/tree/main/packages/call-cli#readme",
15
+ "keywords": [
16
+ "call",
17
+ "telephony",
18
+ "tts",
19
+ "elevenlabs",
20
+ "cli",
21
+ "mirage"
22
+ ],
23
+ "type": "module",
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js"
30
+ },
31
+ "./cli": {
32
+ "types": "./dist/cli.d.ts",
33
+ "import": "./dist/cli.js"
34
+ }
35
+ },
36
+ "bin": {
37
+ "call": "./dist/bin.js"
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "README.md",
42
+ "LICENSE"
43
+ ],
44
+ "scripts": {
45
+ "dev": "bun run src/cli.ts",
46
+ "build": "bun build src/bin.ts src/cli.ts src/index.ts --target=node --outdir=dist --external commander --external chalk && bun run scripts/postbuild.ts && bun run build:types",
47
+ "build:types": "tsc -p tsconfig.build.json",
48
+ "test": "bun test --reporter=dots",
49
+ "typecheck": "tsc --noEmit"
50
+ },
51
+ "dependencies": {
52
+ "chalk": "^5.6.2",
53
+ "commander": "^14.0.3"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ },
58
+ "engines": {
59
+ "bun": ">=1.1.0",
60
+ "node": ">=18.0.0"
61
+ }
62
+ }