@blinkdotnew/cli 0.2.4 → 0.2.5

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
@@ -268,6 +268,7 @@ Commands:
268
268
  animate Image-to-video animation (URL or local file)
269
269
  speech Text-to-speech (saves .mp3)
270
270
  transcribe Audio-to-text (file or URL)
271
+ call Make an AI phone call to any US/International number
271
272
 
272
273
  Examples:
273
274
  $ blink ai text "Write a product description for a CLI tool"
@@ -281,6 +282,7 @@ Examples:
281
282
  $ blink ai speech "Hello, welcome to Blink." --voice nova --output hello.mp3
282
283
  $ blink ai transcribe ./meeting.mp3 --language en
283
284
  $ blink ai transcribe https://example.com/audio.mp3
285
+ $ blink ai call "+14155551234" "Collect payment from John Smith"
284
286
 
285
287
  No project needed \u2014 AI commands are workspace-scoped.
286
288
  Add --json to any command for machine-readable output.
@@ -476,6 +478,92 @@ Supported formats: mp3, wav, m4a, ogg
476
478
  if (isJsonMode()) return printJson(result);
477
479
  console.log(result?.text ?? result?.transcript ?? JSON.stringify(result));
478
480
  });
481
+ ai.command("call <phone-number> <system-prompt>").description("Make an AI phone call to any number (US/International)").option("--voice <voice>", "Voice: openai:alloy | openai:nova | cartesia:sonic-english", "openai:alloy").option("--max-duration <seconds>", "Max call duration in seconds", "300").option("--no-wait", "Return call_id immediately without waiting for completion").addHelpText("after", `
482
+ Examples:
483
+ $ blink ai call "+14155551234" "You are collecting a payment of $240 from John Smith. Be polite but firm."
484
+ $ blink ai call "+14155551234" "Confirm John's appointment for tomorrow at 3pm" --voice openai:nova
485
+ $ blink ai call "+14155551234" "Leave a message about our new product launch" --no-wait
486
+ $ blink ai call "+14155551234" "Your task" --json | jq '.call_id'
487
+
488
+ Phone number must be in E.164 format: +1XXXXXXXXXX (US), +44XXXXXXXXXX (UK), etc.
489
+
490
+ Voices:
491
+ openai:alloy Balanced, neutral (default)
492
+ openai:nova Friendly, warm
493
+ openai:echo Clear, conversational
494
+ openai:onyx Deep, authoritative
495
+ openai:shimmer Soft, gentle
496
+ cartesia:sonic-english Low-latency, natural
497
+
498
+ Call is charged to your workspace credits after completion (~1 credit/min).
499
+ `).action(async (phoneNumber, systemPrompt, opts) => {
500
+ requireToken();
501
+ const result = await withSpinner(
502
+ "Initiating AI call...",
503
+ () => resourcesRequest("/api/v1/ai/call", {
504
+ body: {
505
+ phone_number: phoneNumber,
506
+ system_prompt: systemPrompt,
507
+ voice: opts.voice,
508
+ max_duration_seconds: parseInt(opts.maxDuration)
509
+ }
510
+ })
511
+ );
512
+ if (isJsonMode()) return printJson(result);
513
+ const callId = result?.call_id;
514
+ if (opts.noWait) {
515
+ console.log(`Call initiated: ${callId}`);
516
+ console.log(`Poll status: blink ai call-status ${callId}`);
517
+ return;
518
+ }
519
+ console.log(`Call started \u2192 ${callId}`);
520
+ console.log(`Calling ${phoneNumber}...`);
521
+ let status = "queued";
522
+ let attempts = 0;
523
+ while (!["completed", "failed", "no-answer", "busy"].includes(status) && attempts < 120) {
524
+ await new Promise((r) => setTimeout(r, 5e3));
525
+ attempts++;
526
+ const poll = await resourcesRequest(`/api/v1/ai/call/${callId}`, { method: "GET" }).catch(() => null);
527
+ if (poll) {
528
+ status = poll.status;
529
+ if (["completed", "failed", "no-answer", "busy"].includes(status)) {
530
+ if (status === "completed") {
531
+ console.log(`
532
+ Call completed (${poll.duration_seconds}s)`);
533
+ if (poll.transcript) console.log(`
534
+ Transcript:
535
+ ${poll.transcript}`);
536
+ if (poll.credits_charged) console.log(`
537
+ Credits charged: ${poll.credits_charged}`);
538
+ } else {
539
+ console.log(`
540
+ Call ended: ${status}`);
541
+ }
542
+ return;
543
+ }
544
+ }
545
+ }
546
+ console.log(`
547
+ Call in progress. Check status: blink ai call-status ${callId}`);
548
+ });
549
+ ai.command("call-status <call-id>").description("Get status of an AI phone call").addHelpText("after", `
550
+ Examples:
551
+ $ blink ai call-status vc_a1b2c3d4
552
+ $ blink ai call-status vc_a1b2c3d4 --json
553
+ `).action(async (callId, _opts) => {
554
+ requireToken();
555
+ const result = await withSpinner(
556
+ "Fetching call status...",
557
+ () => resourcesRequest(`/api/v1/ai/call/${callId}`, { method: "GET" })
558
+ );
559
+ if (isJsonMode()) return printJson(result);
560
+ console.log(`Status: ${result?.status}`);
561
+ if (result?.duration_seconds) console.log(`Duration: ${result.duration_seconds}s`);
562
+ if (result?.transcript) console.log(`
563
+ Transcript:
564
+ ${result.transcript}`);
565
+ if (result?.credits_charged) console.log(`Credits: ${result.credits_charged}`);
566
+ });
479
567
  }
480
568
 
481
569
  // src/commands/web.ts
@@ -1211,9 +1299,9 @@ async function liExec(method, httpMethod, params, agentId) {
1211
1299
  return result.data;
1212
1300
  }
1213
1301
  async function getPersonId(agentId) {
1214
- const data = await liExec("/me", "GET", {}, agentId);
1215
- const id = data?.id;
1216
- if (!id) throw new Error("Could not resolve LinkedIn person ID from /me response");
1302
+ const data = await liExec("v2/userinfo", "GET", {}, agentId);
1303
+ const id = data?.sub ?? data?.id;
1304
+ if (!id) throw new Error("Could not resolve LinkedIn person ID");
1217
1305
  return id;
1218
1306
  }
1219
1307
  function registerLinkedInCommands(program2) {
@@ -1240,14 +1328,15 @@ Examples:
1240
1328
  const agentId = requireAgentId(opts.agent);
1241
1329
  const data = await withSpinner(
1242
1330
  "Fetching LinkedIn profile...",
1243
- () => liExec("/me", "GET", {}, agentId)
1331
+ () => liExec("v2/userinfo", "GET", {}, agentId)
1244
1332
  );
1245
1333
  if (isJsonMode()) return printJson(data);
1246
- const name = [data?.localizedFirstName, data?.localizedLastName].filter(Boolean).join(" ");
1334
+ const name = data?.name ?? [data?.given_name, data?.family_name].filter(Boolean).join(" ");
1335
+ const personId = data?.sub ?? data?.id;
1247
1336
  console.log(chalk7.bold("LinkedIn Profile"));
1248
- console.log(` ${chalk7.dim("ID:")} ${data?.id ?? "\u2014"}`);
1249
- if (name) console.log(` ${chalk7.dim("Name:")} ${name}`);
1250
- if (data?.vanityName) console.log(` ${chalk7.dim("URL:")} https://linkedin.com/in/${data.vanityName}`);
1337
+ if (personId) console.log(` ${chalk7.dim("ID:")} ${personId}`);
1338
+ if (name) console.log(` ${chalk7.dim("Name:")} ${name}`);
1339
+ if (data?.email) console.log(` ${chalk7.dim("Email:")} ${data.email}`);
1251
1340
  });
1252
1341
  li.command("posts").description("List your most recent LinkedIn posts (last 10)").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").option("--limit <n>", "Number of posts to fetch (default: 10)", "10").addHelpText("after", `
1253
1342
  Examples:
@@ -1265,7 +1354,7 @@ Examples:
1265
1354
  const data = await withSpinner(
1266
1355
  "Fetching posts...",
1267
1356
  () => liExec(
1268
- `/ugcPosts?q=authors&authors=List(${urn})&sortBy=LAST_MODIFIED&count=${opts.limit}`,
1357
+ `ugcPosts?q=authors&authors=List(${urn})&sortBy=LAST_MODIFIED&count=${opts.limit}`,
1269
1358
  "GET",
1270
1359
  {},
1271
1360
  agentId
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blinkdotnew/cli",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Blink platform CLI — deploy apps, manage databases, generate AI content",
5
5
  "bin": {
6
6
  "blink": "dist/cli.js"
@@ -42,6 +42,7 @@ Commands:
42
42
  animate Image-to-video animation (URL or local file)
43
43
  speech Text-to-speech (saves .mp3)
44
44
  transcribe Audio-to-text (file or URL)
45
+ call Make an AI phone call to any US/International number
45
46
 
46
47
  Examples:
47
48
  $ blink ai text "Write a product description for a CLI tool"
@@ -55,6 +56,7 @@ Examples:
55
56
  $ blink ai speech "Hello, welcome to Blink." --voice nova --output hello.mp3
56
57
  $ blink ai transcribe ./meeting.mp3 --language en
57
58
  $ blink ai transcribe https://example.com/audio.mp3
59
+ $ blink ai call "+14155551234" "Collect payment from John Smith"
58
60
 
59
61
  No project needed — AI commands are workspace-scoped.
60
62
  Add --json to any command for machine-readable output.
@@ -282,4 +284,95 @@ Supported formats: mp3, wav, m4a, ogg
282
284
  if (isJsonMode()) return printJson(result)
283
285
  console.log(result?.text ?? result?.transcript ?? JSON.stringify(result))
284
286
  })
287
+
288
+ ai.command('call <phone-number> <system-prompt>')
289
+ .description('Make an AI phone call to any number (US/International)')
290
+ .option('--voice <voice>', 'Voice: openai:alloy | openai:nova | cartesia:sonic-english', 'openai:alloy')
291
+ .option('--max-duration <seconds>', 'Max call duration in seconds', '300')
292
+ .option('--no-wait', 'Return call_id immediately without waiting for completion')
293
+ .addHelpText('after', `
294
+ Examples:
295
+ $ blink ai call "+14155551234" "You are collecting a payment of $240 from John Smith. Be polite but firm."
296
+ $ blink ai call "+14155551234" "Confirm John's appointment for tomorrow at 3pm" --voice openai:nova
297
+ $ blink ai call "+14155551234" "Leave a message about our new product launch" --no-wait
298
+ $ blink ai call "+14155551234" "Your task" --json | jq '.call_id'
299
+
300
+ Phone number must be in E.164 format: +1XXXXXXXXXX (US), +44XXXXXXXXXX (UK), etc.
301
+
302
+ Voices:
303
+ openai:alloy Balanced, neutral (default)
304
+ openai:nova Friendly, warm
305
+ openai:echo Clear, conversational
306
+ openai:onyx Deep, authoritative
307
+ openai:shimmer Soft, gentle
308
+ cartesia:sonic-english Low-latency, natural
309
+
310
+ Call is charged to your workspace credits after completion (~1 credit/min).
311
+ `)
312
+ .action(async (phoneNumber, systemPrompt, opts) => {
313
+ requireToken()
314
+ const result = await withSpinner('Initiating AI call...', () =>
315
+ resourcesRequest('/api/v1/ai/call', {
316
+ body: {
317
+ phone_number: phoneNumber,
318
+ system_prompt: systemPrompt,
319
+ voice: opts.voice,
320
+ max_duration_seconds: parseInt(opts.maxDuration),
321
+ },
322
+ })
323
+ )
324
+ if (isJsonMode()) return printJson(result)
325
+
326
+ const callId = result?.call_id as string
327
+
328
+ if (opts.noWait) {
329
+ console.log(`Call initiated: ${callId}`)
330
+ console.log(`Poll status: blink ai call-status ${callId}`)
331
+ return
332
+ }
333
+
334
+ // Poll until completed
335
+ console.log(`Call started → ${callId}`)
336
+ console.log(`Calling ${phoneNumber}...`)
337
+ let status = 'queued'
338
+ let attempts = 0
339
+ while (!['completed', 'failed', 'no-answer', 'busy'].includes(status) && attempts < 120) {
340
+ await new Promise(r => setTimeout(r, 5000))
341
+ attempts++
342
+ const poll = await resourcesRequest(`/api/v1/ai/call/${callId}`, { method: 'GET' }).catch(() => null)
343
+ if (poll) {
344
+ status = poll.status as string
345
+ if (['completed', 'failed', 'no-answer', 'busy'].includes(status)) {
346
+ if (status === 'completed') {
347
+ console.log(`\nCall completed (${poll.duration_seconds}s)`)
348
+ if (poll.transcript) console.log(`\nTranscript:\n${poll.transcript}`)
349
+ if (poll.credits_charged) console.log(`\nCredits charged: ${poll.credits_charged}`)
350
+ } else {
351
+ console.log(`\nCall ended: ${status}`)
352
+ }
353
+ return
354
+ }
355
+ }
356
+ }
357
+ console.log(`\nCall in progress. Check status: blink ai call-status ${callId}`)
358
+ })
359
+
360
+ ai.command('call-status <call-id>')
361
+ .description('Get status of an AI phone call')
362
+ .addHelpText('after', `
363
+ Examples:
364
+ $ blink ai call-status vc_a1b2c3d4
365
+ $ blink ai call-status vc_a1b2c3d4 --json
366
+ `)
367
+ .action(async (callId, _opts) => {
368
+ requireToken()
369
+ const result = await withSpinner('Fetching call status...', () =>
370
+ resourcesRequest(`/api/v1/ai/call/${callId}`, { method: 'GET' })
371
+ )
372
+ if (isJsonMode()) return printJson(result)
373
+ console.log(`Status: ${result?.status}`)
374
+ if (result?.duration_seconds) console.log(`Duration: ${result.duration_seconds}s`)
375
+ if (result?.transcript) console.log(`\nTranscript:\n${result.transcript}`)
376
+ if (result?.credits_charged) console.log(`Credits: ${result.credits_charged}`)
377
+ })
285
378
  }
@@ -22,9 +22,10 @@ async function liExec(
22
22
  }
23
23
 
24
24
  async function getPersonId(agentId: string): Promise<string> {
25
- const data = await liExec('/me', 'GET', {}, agentId)
26
- const id = data?.id
27
- if (!id) throw new Error('Could not resolve LinkedIn person ID from /me response')
25
+ // Use OpenID Connect userinfo more reliable than deprecated /v2/me
26
+ const data = await liExec('v2/userinfo', 'GET', {}, agentId)
27
+ const id = data?.sub ?? data?.id
28
+ if (!id) throw new Error('Could not resolve LinkedIn person ID')
28
29
  return id
29
30
  }
30
31
 
@@ -58,15 +59,17 @@ Examples:
58
59
  .action(async (opts) => {
59
60
  requireToken()
60
61
  const agentId = requireAgentId(opts.agent)
62
+ // Use OpenID Connect userinfo endpoint — more reliable than /v2/me
61
63
  const data = await withSpinner('Fetching LinkedIn profile...', () =>
62
- liExec('/me', 'GET', {}, agentId)
64
+ liExec('v2/userinfo', 'GET', {}, agentId)
63
65
  )
64
66
  if (isJsonMode()) return printJson(data)
65
- const name = [data?.localizedFirstName, data?.localizedLastName].filter(Boolean).join(' ')
67
+ const name = data?.name ?? [data?.given_name, data?.family_name].filter(Boolean).join(' ')
68
+ const personId = data?.sub ?? data?.id
66
69
  console.log(chalk.bold('LinkedIn Profile'))
67
- console.log(` ${chalk.dim('ID:')} ${data?.id ?? '—'}`)
68
- if (name) console.log(` ${chalk.dim('Name:')} ${name}`)
69
- if (data?.vanityName) console.log(` ${chalk.dim('URL:')} https://linkedin.com/in/${data.vanityName}`)
70
+ if (personId) console.log(` ${chalk.dim('ID:')} ${personId}`)
71
+ if (name) console.log(` ${chalk.dim('Name:')} ${name}`)
72
+ if (data?.email) console.log(` ${chalk.dim('Email:')} ${data.email}`)
70
73
  })
71
74
 
72
75
  // blink linkedin posts
@@ -89,7 +92,7 @@ Examples:
89
92
  const urn = encodeURIComponent(`urn:li:person:${personId}`)
90
93
  const data = await withSpinner('Fetching posts...', () =>
91
94
  liExec(
92
- `/ugcPosts?q=authors&authors=List(${urn})&sortBy=LAST_MODIFIED&count=${opts.limit}`,
95
+ `ugcPosts?q=authors&authors=List(${urn})&sortBy=LAST_MODIFIED&count=${opts.limit}`,
93
96
  'GET',
94
97
  {},
95
98
  agentId