@blinkdotnew/cli 0.2.3 → 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 +365 -41
- package/package.json +1 -1
- package/src/cli.ts +12 -0
- package/src/commands/ai.ts +93 -0
- package/src/commands/connector.ts +1 -1
- package/src/commands/linkedin.ts +268 -0
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
|
|
@@ -1067,7 +1155,7 @@ Once linked, use \`blink connector exec\` to call their APIs without managing to
|
|
|
1067
1155
|
|
|
1068
1156
|
Connect accounts at: https://blink.new/settings?tab=connectors
|
|
1069
1157
|
|
|
1070
|
-
Run \`blink connector providers\` to see all
|
|
1158
|
+
Run \`blink connector providers\` to see all 38 supported providers.
|
|
1071
1159
|
|
|
1072
1160
|
Quick examples:
|
|
1073
1161
|
$ blink connector exec github /user/repos GET
|
|
@@ -1198,6 +1286,231 @@ Provider base URLs used:
|
|
|
1198
1286
|
});
|
|
1199
1287
|
}
|
|
1200
1288
|
|
|
1289
|
+
// src/commands/linkedin.ts
|
|
1290
|
+
init_agent();
|
|
1291
|
+
import chalk7 from "chalk";
|
|
1292
|
+
var NOT_LINKED = "LinkedIn not linked. Link it in the Agent Integrations tab at blink.new/claw";
|
|
1293
|
+
async function liExec(method, httpMethod, params, agentId) {
|
|
1294
|
+
const result = await resourcesRequest("/v1/connectors/linkedin/execute", {
|
|
1295
|
+
body: { method, http_method: httpMethod, params },
|
|
1296
|
+
headers: { "x-blink-agent-id": agentId }
|
|
1297
|
+
});
|
|
1298
|
+
if (!result?.success) throw new Error(result?.error ?? NOT_LINKED);
|
|
1299
|
+
return result.data;
|
|
1300
|
+
}
|
|
1301
|
+
async function getPersonId(agentId) {
|
|
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");
|
|
1305
|
+
return id;
|
|
1306
|
+
}
|
|
1307
|
+
function registerLinkedInCommands(program2) {
|
|
1308
|
+
const li = program2.command("linkedin").description("LinkedIn connector \u2014 post content, manage comments, and view your profile").addHelpText("after", `
|
|
1309
|
+
LinkedIn must be linked to your agent via the Integrations tab at blink.new/claw.
|
|
1310
|
+
Agent ID defaults to BLINK_AGENT_ID (automatically set on Claw Fly machines).
|
|
1311
|
+
|
|
1312
|
+
Examples:
|
|
1313
|
+
$ blink linkedin me Show your LinkedIn profile
|
|
1314
|
+
$ blink linkedin posts List your 10 most recent posts
|
|
1315
|
+
$ blink linkedin post "Excited to announce..." Publish a text post
|
|
1316
|
+
$ blink linkedin comments "urn:li:ugcPost:123" Read comments on a post
|
|
1317
|
+
$ blink linkedin comment "urn:li:ugcPost:123" "Great post!" Add a comment
|
|
1318
|
+
$ blink linkedin like "urn:li:ugcPost:123" Like a post
|
|
1319
|
+
$ blink linkedin unlike "urn:li:ugcPost:123" Unlike a post
|
|
1320
|
+
`);
|
|
1321
|
+
li.command("me").description("Show your LinkedIn profile (name, ID, vanity URL)").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1322
|
+
Examples:
|
|
1323
|
+
$ blink linkedin me
|
|
1324
|
+
$ blink linkedin me --json
|
|
1325
|
+
$ blink linkedin me --agent clw_xxx
|
|
1326
|
+
`).action(async (opts) => {
|
|
1327
|
+
requireToken();
|
|
1328
|
+
const agentId = requireAgentId(opts.agent);
|
|
1329
|
+
const data = await withSpinner(
|
|
1330
|
+
"Fetching LinkedIn profile...",
|
|
1331
|
+
() => liExec("v2/userinfo", "GET", {}, agentId)
|
|
1332
|
+
);
|
|
1333
|
+
if (isJsonMode()) return printJson(data);
|
|
1334
|
+
const name = data?.name ?? [data?.given_name, data?.family_name].filter(Boolean).join(" ");
|
|
1335
|
+
const personId = data?.sub ?? data?.id;
|
|
1336
|
+
console.log(chalk7.bold("LinkedIn Profile"));
|
|
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}`);
|
|
1340
|
+
});
|
|
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", `
|
|
1342
|
+
Examples:
|
|
1343
|
+
$ blink linkedin posts
|
|
1344
|
+
$ blink linkedin posts --limit 5
|
|
1345
|
+
$ blink linkedin posts --json | jq '.[].id'
|
|
1346
|
+
`).action(async (opts) => {
|
|
1347
|
+
requireToken();
|
|
1348
|
+
const agentId = requireAgentId(opts.agent);
|
|
1349
|
+
const personId = await withSpinner(
|
|
1350
|
+
"Resolving your LinkedIn identity...",
|
|
1351
|
+
() => getPersonId(agentId)
|
|
1352
|
+
);
|
|
1353
|
+
const urn = encodeURIComponent(`urn:li:person:${personId}`);
|
|
1354
|
+
const data = await withSpinner(
|
|
1355
|
+
"Fetching posts...",
|
|
1356
|
+
() => liExec(
|
|
1357
|
+
`ugcPosts?q=authors&authors=List(${urn})&sortBy=LAST_MODIFIED&count=${opts.limit}`,
|
|
1358
|
+
"GET",
|
|
1359
|
+
{},
|
|
1360
|
+
agentId
|
|
1361
|
+
)
|
|
1362
|
+
);
|
|
1363
|
+
const posts = data?.elements ?? (Array.isArray(data) ? data : []);
|
|
1364
|
+
if (isJsonMode()) return printJson(posts);
|
|
1365
|
+
if (!posts.length) {
|
|
1366
|
+
console.log(chalk7.dim("No posts found."));
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
for (const post of posts) {
|
|
1370
|
+
const id = post.id ?? "\u2014";
|
|
1371
|
+
const content = post.specificContent;
|
|
1372
|
+
const share = content?.["com.linkedin.ugc.ShareContent"];
|
|
1373
|
+
const commentary = share?.shareCommentary;
|
|
1374
|
+
const text = commentary?.text ?? post.text?.text ?? "(no text)";
|
|
1375
|
+
const preview = text.length > 120 ? text.slice(0, 120) + "\u2026" : text;
|
|
1376
|
+
console.log(chalk7.bold(id));
|
|
1377
|
+
console.log(` ${chalk7.dim(preview)}`);
|
|
1378
|
+
console.log();
|
|
1379
|
+
}
|
|
1380
|
+
console.log(chalk7.dim(`${posts.length} post(s)`));
|
|
1381
|
+
});
|
|
1382
|
+
li.command("post <text>").description("Publish a text post to LinkedIn").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").option("--visibility <vis>", "Post visibility: PUBLIC | CONNECTIONS (default: PUBLIC)", "PUBLIC").addHelpText("after", `
|
|
1383
|
+
Examples:
|
|
1384
|
+
$ blink linkedin post "Excited to share our latest update!"
|
|
1385
|
+
$ blink linkedin post "Internal update" --visibility CONNECTIONS
|
|
1386
|
+
$ blink linkedin post "Hello LinkedIn" --json
|
|
1387
|
+
`).action(async (text, opts) => {
|
|
1388
|
+
requireToken();
|
|
1389
|
+
const agentId = requireAgentId(opts.agent);
|
|
1390
|
+
const data = await withSpinner(
|
|
1391
|
+
"Publishing post...",
|
|
1392
|
+
() => liExec("/ugcPosts", "POST", { text, visibility: opts.visibility }, agentId)
|
|
1393
|
+
);
|
|
1394
|
+
if (isJsonMode()) return printJson(data);
|
|
1395
|
+
console.log(chalk7.green("\u2713 Post published"));
|
|
1396
|
+
if (data?.id) console.log(chalk7.dim(` URN: ${data.id}`));
|
|
1397
|
+
});
|
|
1398
|
+
li.command("comments <postUrn>").description("Read comments on a LinkedIn post").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1399
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
1400
|
+
Use "blink linkedin posts" to find your post URNs.
|
|
1401
|
+
|
|
1402
|
+
Examples:
|
|
1403
|
+
$ blink linkedin comments "urn:li:ugcPost:1234567890"
|
|
1404
|
+
$ blink linkedin comments "urn:li:ugcPost:1234567890" --json
|
|
1405
|
+
`).action(async (postUrn, opts) => {
|
|
1406
|
+
requireToken();
|
|
1407
|
+
const agentId = requireAgentId(opts.agent);
|
|
1408
|
+
const encoded = encodeURIComponent(postUrn);
|
|
1409
|
+
const data = await withSpinner(
|
|
1410
|
+
"Fetching comments...",
|
|
1411
|
+
() => liExec(`rest/socialActions/${encoded}/comments`, "GET", {}, agentId)
|
|
1412
|
+
);
|
|
1413
|
+
const comments = data?.elements ?? (Array.isArray(data) ? data : []);
|
|
1414
|
+
if (isJsonMode()) return printJson(comments);
|
|
1415
|
+
if (!comments.length) {
|
|
1416
|
+
console.log(chalk7.dim("No comments."));
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
for (const c of comments) {
|
|
1420
|
+
const author = c.actor ?? "\u2014";
|
|
1421
|
+
const msg = c.message;
|
|
1422
|
+
const text = msg?.text ?? "(no text)";
|
|
1423
|
+
console.log(chalk7.bold(author));
|
|
1424
|
+
console.log(` ${text}`);
|
|
1425
|
+
console.log();
|
|
1426
|
+
}
|
|
1427
|
+
console.log(chalk7.dim(`${comments.length} comment(s)`));
|
|
1428
|
+
});
|
|
1429
|
+
li.command("comment <postUrn> <text>").description("Add a comment to a LinkedIn post").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1430
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
1431
|
+
|
|
1432
|
+
Examples:
|
|
1433
|
+
$ blink linkedin comment "urn:li:ugcPost:1234567890" "Great post!"
|
|
1434
|
+
$ blink linkedin comment "urn:li:ugcPost:1234567890" "Thanks for sharing" --json
|
|
1435
|
+
`).action(async (postUrn, text, opts) => {
|
|
1436
|
+
requireToken();
|
|
1437
|
+
const agentId = requireAgentId(opts.agent);
|
|
1438
|
+
const personId = await withSpinner(
|
|
1439
|
+
"Resolving your LinkedIn identity...",
|
|
1440
|
+
() => getPersonId(agentId)
|
|
1441
|
+
);
|
|
1442
|
+
const actor = `urn:li:person:${personId}`;
|
|
1443
|
+
const encoded = encodeURIComponent(postUrn);
|
|
1444
|
+
const data = await withSpinner(
|
|
1445
|
+
"Adding comment...",
|
|
1446
|
+
() => liExec(
|
|
1447
|
+
`rest/socialActions/${encoded}/comments`,
|
|
1448
|
+
"POST",
|
|
1449
|
+
{ actor, object: postUrn, message: { text } },
|
|
1450
|
+
agentId
|
|
1451
|
+
)
|
|
1452
|
+
);
|
|
1453
|
+
if (isJsonMode()) return printJson(data);
|
|
1454
|
+
console.log(chalk7.green("\u2713 Comment added"));
|
|
1455
|
+
if (data?.id) console.log(chalk7.dim(` ID: ${data.id}`));
|
|
1456
|
+
});
|
|
1457
|
+
li.command("like <postUrn>").description("Like a LinkedIn post").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1458
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
1459
|
+
|
|
1460
|
+
Examples:
|
|
1461
|
+
$ blink linkedin like "urn:li:ugcPost:1234567890"
|
|
1462
|
+
$ blink linkedin like "urn:li:ugcPost:1234567890" --json
|
|
1463
|
+
`).action(async (postUrn, opts) => {
|
|
1464
|
+
requireToken();
|
|
1465
|
+
const agentId = requireAgentId(opts.agent);
|
|
1466
|
+
const personId = await withSpinner(
|
|
1467
|
+
"Resolving your LinkedIn identity...",
|
|
1468
|
+
() => getPersonId(agentId)
|
|
1469
|
+
);
|
|
1470
|
+
const actor = `urn:li:person:${personId}`;
|
|
1471
|
+
const encoded = encodeURIComponent(postUrn);
|
|
1472
|
+
const data = await withSpinner(
|
|
1473
|
+
"Liking post...",
|
|
1474
|
+
() => liExec(
|
|
1475
|
+
`rest/socialActions/${encoded}/likes`,
|
|
1476
|
+
"POST",
|
|
1477
|
+
{ actor, object: postUrn },
|
|
1478
|
+
agentId
|
|
1479
|
+
)
|
|
1480
|
+
);
|
|
1481
|
+
if (isJsonMode()) return printJson(data);
|
|
1482
|
+
console.log(chalk7.green("\u2713 Post liked"));
|
|
1483
|
+
});
|
|
1484
|
+
li.command("unlike <postUrn>").description("Unlike a LinkedIn post").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1485
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
1486
|
+
|
|
1487
|
+
Examples:
|
|
1488
|
+
$ blink linkedin unlike "urn:li:ugcPost:1234567890"
|
|
1489
|
+
$ blink linkedin unlike "urn:li:ugcPost:1234567890" --json
|
|
1490
|
+
`).action(async (postUrn, opts) => {
|
|
1491
|
+
requireToken();
|
|
1492
|
+
const agentId = requireAgentId(opts.agent);
|
|
1493
|
+
const personId = await withSpinner(
|
|
1494
|
+
"Resolving your LinkedIn identity...",
|
|
1495
|
+
() => getPersonId(agentId)
|
|
1496
|
+
);
|
|
1497
|
+
const actor = `urn:li:person:${personId}`;
|
|
1498
|
+
const encodedPost = encodeURIComponent(postUrn);
|
|
1499
|
+
const encodedActor = encodeURIComponent(actor);
|
|
1500
|
+
const data = await withSpinner(
|
|
1501
|
+
"Unliking post...",
|
|
1502
|
+
() => liExec(
|
|
1503
|
+
`rest/socialActions/${encodedPost}/likes/${encodedActor}?actor=${encodedActor}`,
|
|
1504
|
+
"DELETE",
|
|
1505
|
+
{},
|
|
1506
|
+
agentId
|
|
1507
|
+
)
|
|
1508
|
+
);
|
|
1509
|
+
if (isJsonMode()) return printJson(data);
|
|
1510
|
+
console.log(chalk7.green("\u2713 Post unliked"));
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1201
1514
|
// src/lib/api-app.ts
|
|
1202
1515
|
var BASE_URL2 = process.env.BLINK_APP_URL ?? "https://blink.new";
|
|
1203
1516
|
async function appRequest(path, opts = {}) {
|
|
@@ -1236,7 +1549,7 @@ async function appRequest(path, opts = {}) {
|
|
|
1236
1549
|
init_project();
|
|
1237
1550
|
import { readdirSync, readFileSync as readFileSync8 } from "fs";
|
|
1238
1551
|
import { join as join3, relative } from "path";
|
|
1239
|
-
import
|
|
1552
|
+
import chalk8 from "chalk";
|
|
1240
1553
|
function collectFiles(dir) {
|
|
1241
1554
|
const files = [];
|
|
1242
1555
|
function walk(current) {
|
|
@@ -1309,7 +1622,7 @@ Project resolution:
|
|
|
1309
1622
|
const production = opts.prod === true;
|
|
1310
1623
|
if (!isJsonMode()) {
|
|
1311
1624
|
console.log();
|
|
1312
|
-
console.log(
|
|
1625
|
+
console.log(chalk8.bold(" Blink Deploy"));
|
|
1313
1626
|
console.log();
|
|
1314
1627
|
}
|
|
1315
1628
|
const files = await withSpinner(`Packaging ${buildDir}...`, async () => collectFiles(buildDir));
|
|
@@ -1363,7 +1676,7 @@ Project resolution:
|
|
|
1363
1676
|
|
|
1364
1677
|
// src/commands/project.ts
|
|
1365
1678
|
init_project();
|
|
1366
|
-
import
|
|
1679
|
+
import chalk9 from "chalk";
|
|
1367
1680
|
function registerProjectCommands(program2) {
|
|
1368
1681
|
const project = program2.command("project").description("Create, list, and delete Blink projects").addHelpText("after", `
|
|
1369
1682
|
Examples:
|
|
@@ -1392,8 +1705,8 @@ After creating a project, link it to your current directory:
|
|
|
1392
1705
|
);
|
|
1393
1706
|
if (isJsonMode()) return printJson(result);
|
|
1394
1707
|
const proj = result?.project ?? result;
|
|
1395
|
-
console.log(
|
|
1396
|
-
console.log(
|
|
1708
|
+
console.log(chalk9.green("\u2713") + ` Created: ${proj.id}`);
|
|
1709
|
+
console.log(chalk9.dim(" Run `blink link " + proj.id + "` to use it"));
|
|
1397
1710
|
});
|
|
1398
1711
|
project.command("delete <project_id>").description("Delete a project").option("--yes", "Skip confirmation").action(async (projectId, opts) => {
|
|
1399
1712
|
requireToken();
|
|
@@ -1436,7 +1749,7 @@ After linking, most commands work without specifying a project_id:
|
|
|
1436
1749
|
});
|
|
1437
1750
|
}
|
|
1438
1751
|
writeProjectConfig({ projectId: id });
|
|
1439
|
-
console.log(
|
|
1752
|
+
console.log(chalk9.green("\u2713") + " Linked to " + id);
|
|
1440
1753
|
});
|
|
1441
1754
|
program2.command("unlink").description("Remove project link from current directory").action(() => {
|
|
1442
1755
|
clearProjectConfig();
|
|
@@ -1448,29 +1761,29 @@ After linking, most commands work without specifying a project_id:
|
|
|
1448
1761
|
const agentId = resolveAgentId3();
|
|
1449
1762
|
const agentSource = process.env.BLINK_AGENT_ID ? "BLINK_AGENT_ID env" : process.env.BLINK_ACTIVE_AGENT ? "BLINK_ACTIVE_AGENT env" : null;
|
|
1450
1763
|
if (agentId) {
|
|
1451
|
-
console.log(
|
|
1764
|
+
console.log(chalk9.bold("Agent ") + agentId + chalk9.dim(" (" + agentSource + ")"));
|
|
1452
1765
|
} else {
|
|
1453
|
-
console.log(
|
|
1766
|
+
console.log(chalk9.dim("Agent not set (use: eval $(blink agent use clw_xxx --export))"));
|
|
1454
1767
|
}
|
|
1455
1768
|
if (config) {
|
|
1456
1769
|
const projectSource = process.env.BLINK_ACTIVE_PROJECT ? "BLINK_ACTIVE_PROJECT env" : ".blink/project.json";
|
|
1457
|
-
console.log(
|
|
1770
|
+
console.log(chalk9.bold("Project ") + config.projectId + chalk9.dim(" (" + projectSource + ")"));
|
|
1458
1771
|
} else if (process.env.BLINK_ACTIVE_PROJECT) {
|
|
1459
|
-
console.log(
|
|
1772
|
+
console.log(chalk9.bold("Project ") + process.env.BLINK_ACTIVE_PROJECT + chalk9.dim(" (BLINK_ACTIVE_PROJECT env)"));
|
|
1460
1773
|
} else {
|
|
1461
|
-
console.log(
|
|
1774
|
+
console.log(chalk9.dim("Project not linked (use: blink link or eval $(blink use proj_xxx --export))"));
|
|
1462
1775
|
}
|
|
1463
1776
|
const authSource = process.env.BLINK_API_KEY ? "BLINK_API_KEY env" : "~/.config/blink/config.toml";
|
|
1464
1777
|
const hasProjectKey = !!process.env.BLINK_PROJECT_KEY;
|
|
1465
|
-
console.log(
|
|
1778
|
+
console.log(chalk9.bold("Auth ") + authSource);
|
|
1466
1779
|
if (hasProjectKey) {
|
|
1467
|
-
console.log(
|
|
1780
|
+
console.log(chalk9.bold("ProjKey ") + "BLINK_PROJECT_KEY env" + chalk9.dim(" (used for db/storage/rag)"));
|
|
1468
1781
|
}
|
|
1469
1782
|
});
|
|
1470
1783
|
}
|
|
1471
1784
|
|
|
1472
1785
|
// src/commands/auth.ts
|
|
1473
|
-
import
|
|
1786
|
+
import chalk10 from "chalk";
|
|
1474
1787
|
function registerAuthCommands(program2) {
|
|
1475
1788
|
program2.command("login").description("Authenticate with your Blink API key").option("--interactive", "Prompt for API key (for headless/SSH/CI environments)").addHelpText("after", `
|
|
1476
1789
|
Get your API key at: blink.new \u2192 Settings \u2192 API Keys (starts with blnk_ak_)
|
|
@@ -1483,7 +1796,7 @@ In Blink Claw agents: BLINK_API_KEY is already set \u2014 login is not needed.
|
|
|
1483
1796
|
For CI/GitHub Actions: set BLINK_API_KEY as a secret, skip login entirely.
|
|
1484
1797
|
`).action(async (opts) => {
|
|
1485
1798
|
if (process.env.BLINK_API_KEY && !opts.interactive) {
|
|
1486
|
-
console.log(
|
|
1799
|
+
console.log(chalk10.green("\u2713") + " Already authenticated via BLINK_API_KEY env var.");
|
|
1487
1800
|
return;
|
|
1488
1801
|
}
|
|
1489
1802
|
const { password } = await import("@clack/prompts");
|
|
@@ -1493,7 +1806,7 @@ For CI/GitHub Actions: set BLINK_API_KEY as a secret, skip login entirely.
|
|
|
1493
1806
|
process.exit(1);
|
|
1494
1807
|
}
|
|
1495
1808
|
writeConfig({ api_key: apiKey });
|
|
1496
|
-
console.log(
|
|
1809
|
+
console.log(chalk10.green("\u2713") + " Saved to ~/.config/blink/config.toml");
|
|
1497
1810
|
});
|
|
1498
1811
|
program2.command("logout").description("Remove stored credentials").action(() => {
|
|
1499
1812
|
clearConfig();
|
|
@@ -1513,15 +1826,15 @@ For CI/GitHub Actions: set BLINK_API_KEY as a secret, skip login entirely.
|
|
|
1513
1826
|
});
|
|
1514
1827
|
}
|
|
1515
1828
|
const source = process.env.BLINK_API_KEY === token ? "BLINK_API_KEY env" : "~/.config/blink/config.toml";
|
|
1516
|
-
console.log(
|
|
1517
|
-
console.log(
|
|
1518
|
-
console.log(
|
|
1829
|
+
console.log(chalk10.green("\u2713") + " Authenticated");
|
|
1830
|
+
console.log(chalk10.bold("Key ") + token.slice(0, 20) + chalk10.dim("..."));
|
|
1831
|
+
console.log(chalk10.bold("Source ") + chalk10.dim(source));
|
|
1519
1832
|
});
|
|
1520
1833
|
}
|
|
1521
1834
|
|
|
1522
1835
|
// src/commands/agent.ts
|
|
1523
1836
|
init_agent();
|
|
1524
|
-
import
|
|
1837
|
+
import chalk11 from "chalk";
|
|
1525
1838
|
function registerAgentCommands(program2) {
|
|
1526
1839
|
const agent = program2.command("agent").description("Manage Blink Claw agents in your workspace").addHelpText("after", `
|
|
1527
1840
|
Examples:
|
|
@@ -1541,7 +1854,7 @@ Agent ID resolution for all agent/secrets commands (priority: high \u2192 low):
|
|
|
1541
1854
|
if (isJsonMode()) return printJson(result);
|
|
1542
1855
|
const agents = Array.isArray(result) ? result : result?.agents ?? [];
|
|
1543
1856
|
if (!agents.length) {
|
|
1544
|
-
console.log(
|
|
1857
|
+
console.log(chalk11.dim("No agents found. Deploy one at blink.new/claw"));
|
|
1545
1858
|
return;
|
|
1546
1859
|
}
|
|
1547
1860
|
const table = createTable(["ID", "Name", "Status", "Size", "Model"]);
|
|
@@ -1564,9 +1877,9 @@ After setting, secrets commands use this agent automatically:
|
|
|
1564
1877
|
process.stdout.write(`export BLINK_ACTIVE_AGENT=${agentId}
|
|
1565
1878
|
`);
|
|
1566
1879
|
} else {
|
|
1567
|
-
console.log(
|
|
1568
|
-
console.log(
|
|
1569
|
-
console.log(
|
|
1880
|
+
console.log(chalk11.bold("Active agent: ") + agentId);
|
|
1881
|
+
console.log(chalk11.dim(`Run: export BLINK_ACTIVE_AGENT=${agentId}`));
|
|
1882
|
+
console.log(chalk11.dim(`Or: eval $(blink agent use ${agentId} --export)`));
|
|
1570
1883
|
}
|
|
1571
1884
|
});
|
|
1572
1885
|
agent.command("status [agent_id]").description("Show details for an agent").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID or BLINK_ACTIVE_AGENT)").addHelpText("after", `
|
|
@@ -1582,18 +1895,18 @@ Examples:
|
|
|
1582
1895
|
);
|
|
1583
1896
|
if (isJsonMode()) return printJson(result);
|
|
1584
1897
|
const a = result?.agent ?? result;
|
|
1585
|
-
console.log(
|
|
1586
|
-
console.log(
|
|
1587
|
-
console.log(
|
|
1588
|
-
console.log(
|
|
1589
|
-
console.log(
|
|
1590
|
-
if (a.fly_app_name) console.log(
|
|
1898
|
+
console.log(chalk11.bold("ID ") + a.id);
|
|
1899
|
+
console.log(chalk11.bold("Name ") + a.name);
|
|
1900
|
+
console.log(chalk11.bold("Status ") + a.status);
|
|
1901
|
+
console.log(chalk11.bold("Model ") + (a.model ?? "-"));
|
|
1902
|
+
console.log(chalk11.bold("Machine ") + (a.machine_size ?? "-"));
|
|
1903
|
+
if (a.fly_app_name) console.log(chalk11.bold("Fly App ") + a.fly_app_name);
|
|
1591
1904
|
});
|
|
1592
1905
|
}
|
|
1593
1906
|
|
|
1594
1907
|
// src/commands/secrets.ts
|
|
1595
1908
|
init_agent();
|
|
1596
|
-
import
|
|
1909
|
+
import chalk12 from "chalk";
|
|
1597
1910
|
function registerSecretsCommands(program2) {
|
|
1598
1911
|
const secrets = program2.command("secrets").description("Manage encrypted secrets vault for a Claw agent").addHelpText("after", `
|
|
1599
1912
|
Secrets are encrypted key-value pairs stored in the agent's vault.
|
|
@@ -1629,11 +1942,11 @@ Examples:
|
|
|
1629
1942
|
if (isJsonMode()) return printJson(result);
|
|
1630
1943
|
const keys = result?.secrets?.map((s) => s.key) ?? result?.keys ?? [];
|
|
1631
1944
|
if (!keys.length) {
|
|
1632
|
-
console.log(
|
|
1945
|
+
console.log(chalk12.dim("(no secrets set \u2014 use `blink secrets set KEY value`)"));
|
|
1633
1946
|
return;
|
|
1634
1947
|
}
|
|
1635
|
-
for (const k of keys) console.log(
|
|
1636
|
-
console.log(
|
|
1948
|
+
for (const k of keys) console.log(chalk12.bold(k));
|
|
1949
|
+
console.log(chalk12.dim(`
|
|
1637
1950
|
${keys.length} secret${keys.length === 1 ? "" : "s"} (values hidden)`));
|
|
1638
1951
|
});
|
|
1639
1952
|
secrets.command("set <key> <value>").description("Add or update a secret (stored encrypted, value never shown again)").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID or BLINK_ACTIVE_AGENT)").addHelpText("after", `
|
|
@@ -1656,8 +1969,8 @@ After setting, the secret is available as $KEY_NAME in agent shell commands.
|
|
|
1656
1969
|
);
|
|
1657
1970
|
const normalised = key.toUpperCase();
|
|
1658
1971
|
if (!isJsonMode()) {
|
|
1659
|
-
console.log(
|
|
1660
|
-
console.log(
|
|
1972
|
+
console.log(chalk12.green("\u2713") + ` ${normalised} saved`);
|
|
1973
|
+
console.log(chalk12.dim(" Value hidden. Use $" + normalised + " in shell commands."));
|
|
1661
1974
|
} else {
|
|
1662
1975
|
printJson({ status: "ok", key: normalised, agent_id: agentId });
|
|
1663
1976
|
}
|
|
@@ -1753,6 +2066,16 @@ Connectors (38 OAuth providers \u2014 GitHub, Notion, Slack, Stripe, Shopify, Ji
|
|
|
1753
2066
|
$ blink connector exec linear '{ viewer { id name } }' POST GraphQL (Linear)
|
|
1754
2067
|
Connect accounts at: blink.new/settings?tab=connectors
|
|
1755
2068
|
|
|
2069
|
+
LinkedIn (dedicated commands for the LinkedIn connector):
|
|
2070
|
+
$ blink linkedin me Show your LinkedIn profile
|
|
2071
|
+
$ blink linkedin posts List your 10 most recent posts
|
|
2072
|
+
$ blink linkedin post "Hello LinkedIn!" Publish a text post
|
|
2073
|
+
$ blink linkedin comments "urn:li:ugcPost:123" Read comments on a post
|
|
2074
|
+
$ blink linkedin comment "urn:li:ugcPost:123" "Nice!" Add a comment
|
|
2075
|
+
$ blink linkedin like "urn:li:ugcPost:123" Like a post
|
|
2076
|
+
$ blink linkedin unlike "urn:li:ugcPost:123" Unlike a post
|
|
2077
|
+
Link LinkedIn at: blink.new/claw (Agent Integrations tab)
|
|
2078
|
+
|
|
1756
2079
|
Agents (Claw \u2014 zero config on Fly machines, BLINK_AGENT_ID is already set):
|
|
1757
2080
|
$ blink agent list List all agents in workspace
|
|
1758
2081
|
$ blink agent status Show current agent details
|
|
@@ -1791,6 +2114,7 @@ registerRealtimeCommands(program);
|
|
|
1791
2114
|
registerRagCommands(program);
|
|
1792
2115
|
registerNotifyCommands(program);
|
|
1793
2116
|
registerConnectorCommands(program);
|
|
2117
|
+
registerLinkedInCommands(program);
|
|
1794
2118
|
program.command("use <project_id>").description("Set active project for this shell session (alternative to blink link)").option("--export", "Output a shell export statement \u2014 use with eval to actually set it").addHelpText("after", `
|
|
1795
2119
|
Examples:
|
|
1796
2120
|
$ blink use proj_xxx Shows the export command to run
|
|
@@ -1806,10 +2130,10 @@ After setting:
|
|
|
1806
2130
|
process.stdout.write(`export BLINK_ACTIVE_PROJECT=${projectId}
|
|
1807
2131
|
`);
|
|
1808
2132
|
} else {
|
|
1809
|
-
const { default:
|
|
1810
|
-
console.log(
|
|
1811
|
-
console.log(
|
|
1812
|
-
console.log(
|
|
2133
|
+
const { default: chalk13 } = await import("chalk");
|
|
2134
|
+
console.log(chalk13.bold("Active project: ") + projectId);
|
|
2135
|
+
console.log(chalk13.dim(`Run: export BLINK_ACTIVE_PROJECT=${projectId}`));
|
|
2136
|
+
console.log(chalk13.dim(`Or: eval $(blink use ${projectId} --export)`));
|
|
1813
2137
|
}
|
|
1814
2138
|
});
|
|
1815
2139
|
program.action(async () => {
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { registerRealtimeCommands } from './commands/realtime.js'
|
|
|
9
9
|
import { registerRagCommands } from './commands/rag.js'
|
|
10
10
|
import { registerNotifyCommands } from './commands/notify.js'
|
|
11
11
|
import { registerConnectorCommands } from './commands/connector.js'
|
|
12
|
+
import { registerLinkedInCommands } from './commands/linkedin.js'
|
|
12
13
|
import { registerDeployCommands } from './commands/deploy.js'
|
|
13
14
|
import { registerProjectCommands } from './commands/project.js'
|
|
14
15
|
import { registerAuthCommands } from './commands/auth.js'
|
|
@@ -90,6 +91,16 @@ Connectors (38 OAuth providers — GitHub, Notion, Slack, Stripe, Shopify, Jira,
|
|
|
90
91
|
$ blink connector exec linear '{ viewer { id name } }' POST GraphQL (Linear)
|
|
91
92
|
Connect accounts at: blink.new/settings?tab=connectors
|
|
92
93
|
|
|
94
|
+
LinkedIn (dedicated commands for the LinkedIn connector):
|
|
95
|
+
$ blink linkedin me Show your LinkedIn profile
|
|
96
|
+
$ blink linkedin posts List your 10 most recent posts
|
|
97
|
+
$ blink linkedin post "Hello LinkedIn!" Publish a text post
|
|
98
|
+
$ blink linkedin comments "urn:li:ugcPost:123" Read comments on a post
|
|
99
|
+
$ blink linkedin comment "urn:li:ugcPost:123" "Nice!" Add a comment
|
|
100
|
+
$ blink linkedin like "urn:li:ugcPost:123" Like a post
|
|
101
|
+
$ blink linkedin unlike "urn:li:ugcPost:123" Unlike a post
|
|
102
|
+
Link LinkedIn at: blink.new/claw (Agent Integrations tab)
|
|
103
|
+
|
|
93
104
|
Agents (Claw — zero config on Fly machines, BLINK_AGENT_ID is already set):
|
|
94
105
|
$ blink agent list List all agents in workspace
|
|
95
106
|
$ blink agent status Show current agent details
|
|
@@ -130,6 +141,7 @@ registerRealtimeCommands(program)
|
|
|
130
141
|
registerRagCommands(program)
|
|
131
142
|
registerNotifyCommands(program)
|
|
132
143
|
registerConnectorCommands(program)
|
|
144
|
+
registerLinkedInCommands(program)
|
|
133
145
|
|
|
134
146
|
program.command('use <project_id>')
|
|
135
147
|
.description('Set active project for this shell session (alternative to blink link)')
|
package/src/commands/ai.ts
CHANGED
|
@@ -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
|
}
|
|
@@ -65,7 +65,7 @@ Once linked, use \`blink connector exec\` to call their APIs without managing to
|
|
|
65
65
|
|
|
66
66
|
Connect accounts at: https://blink.new/settings?tab=connectors
|
|
67
67
|
|
|
68
|
-
Run \`blink connector providers\` to see all
|
|
68
|
+
Run \`blink connector providers\` to see all 38 supported providers.
|
|
69
69
|
|
|
70
70
|
Quick examples:
|
|
71
71
|
$ blink connector exec github /user/repos GET
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { resourcesRequest } from '../lib/api-resources.js'
|
|
3
|
+
import { requireToken } from '../lib/auth.js'
|
|
4
|
+
import { requireAgentId } from '../lib/agent.js'
|
|
5
|
+
import { printJson, isJsonMode, withSpinner } from '../lib/output.js'
|
|
6
|
+
import chalk from 'chalk'
|
|
7
|
+
|
|
8
|
+
const NOT_LINKED = 'LinkedIn not linked. Link it in the Agent Integrations tab at blink.new/claw'
|
|
9
|
+
|
|
10
|
+
async function liExec(
|
|
11
|
+
method: string,
|
|
12
|
+
httpMethod: string,
|
|
13
|
+
params: Record<string, unknown>,
|
|
14
|
+
agentId: string
|
|
15
|
+
) {
|
|
16
|
+
const result = await resourcesRequest('/v1/connectors/linkedin/execute', {
|
|
17
|
+
body: { method, http_method: httpMethod, params },
|
|
18
|
+
headers: { 'x-blink-agent-id': agentId },
|
|
19
|
+
})
|
|
20
|
+
if (!result?.success) throw new Error(result?.error ?? NOT_LINKED)
|
|
21
|
+
return result.data
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function getPersonId(agentId: string): Promise<string> {
|
|
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')
|
|
29
|
+
return id
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function registerLinkedInCommands(program: Command) {
|
|
33
|
+
const li = program.command('linkedin')
|
|
34
|
+
.description('LinkedIn connector — post content, manage comments, and view your profile')
|
|
35
|
+
.addHelpText('after', `
|
|
36
|
+
LinkedIn must be linked to your agent via the Integrations tab at blink.new/claw.
|
|
37
|
+
Agent ID defaults to BLINK_AGENT_ID (automatically set on Claw Fly machines).
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
$ blink linkedin me Show your LinkedIn profile
|
|
41
|
+
$ blink linkedin posts List your 10 most recent posts
|
|
42
|
+
$ blink linkedin post "Excited to announce..." Publish a text post
|
|
43
|
+
$ blink linkedin comments "urn:li:ugcPost:123" Read comments on a post
|
|
44
|
+
$ blink linkedin comment "urn:li:ugcPost:123" "Great post!" Add a comment
|
|
45
|
+
$ blink linkedin like "urn:li:ugcPost:123" Like a post
|
|
46
|
+
$ blink linkedin unlike "urn:li:ugcPost:123" Unlike a post
|
|
47
|
+
`)
|
|
48
|
+
|
|
49
|
+
// blink linkedin me
|
|
50
|
+
li.command('me')
|
|
51
|
+
.description('Show your LinkedIn profile (name, ID, vanity URL)')
|
|
52
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
53
|
+
.addHelpText('after', `
|
|
54
|
+
Examples:
|
|
55
|
+
$ blink linkedin me
|
|
56
|
+
$ blink linkedin me --json
|
|
57
|
+
$ blink linkedin me --agent clw_xxx
|
|
58
|
+
`)
|
|
59
|
+
.action(async (opts) => {
|
|
60
|
+
requireToken()
|
|
61
|
+
const agentId = requireAgentId(opts.agent)
|
|
62
|
+
// Use OpenID Connect userinfo endpoint — more reliable than /v2/me
|
|
63
|
+
const data = await withSpinner('Fetching LinkedIn profile...', () =>
|
|
64
|
+
liExec('v2/userinfo', 'GET', {}, agentId)
|
|
65
|
+
)
|
|
66
|
+
if (isJsonMode()) return printJson(data)
|
|
67
|
+
const name = data?.name ?? [data?.given_name, data?.family_name].filter(Boolean).join(' ')
|
|
68
|
+
const personId = data?.sub ?? data?.id
|
|
69
|
+
console.log(chalk.bold('LinkedIn Profile'))
|
|
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}`)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// blink linkedin posts
|
|
76
|
+
li.command('posts')
|
|
77
|
+
.description('List your most recent LinkedIn posts (last 10)')
|
|
78
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
79
|
+
.option('--limit <n>', 'Number of posts to fetch (default: 10)', '10')
|
|
80
|
+
.addHelpText('after', `
|
|
81
|
+
Examples:
|
|
82
|
+
$ blink linkedin posts
|
|
83
|
+
$ blink linkedin posts --limit 5
|
|
84
|
+
$ blink linkedin posts --json | jq '.[].id'
|
|
85
|
+
`)
|
|
86
|
+
.action(async (opts) => {
|
|
87
|
+
requireToken()
|
|
88
|
+
const agentId = requireAgentId(opts.agent)
|
|
89
|
+
const personId = await withSpinner('Resolving your LinkedIn identity...', () =>
|
|
90
|
+
getPersonId(agentId)
|
|
91
|
+
)
|
|
92
|
+
const urn = encodeURIComponent(`urn:li:person:${personId}`)
|
|
93
|
+
const data = await withSpinner('Fetching posts...', () =>
|
|
94
|
+
liExec(
|
|
95
|
+
`ugcPosts?q=authors&authors=List(${urn})&sortBy=LAST_MODIFIED&count=${opts.limit}`,
|
|
96
|
+
'GET',
|
|
97
|
+
{},
|
|
98
|
+
agentId
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
const posts: Array<Record<string, unknown>> = data?.elements ?? (Array.isArray(data) ? data : [])
|
|
102
|
+
if (isJsonMode()) return printJson(posts)
|
|
103
|
+
if (!posts.length) { console.log(chalk.dim('No posts found.')); return }
|
|
104
|
+
for (const post of posts) {
|
|
105
|
+
const id = (post.id ?? '—') as string
|
|
106
|
+
const content = post.specificContent as Record<string, unknown> | undefined
|
|
107
|
+
const share = content?.['com.linkedin.ugc.ShareContent'] as Record<string, unknown> | undefined
|
|
108
|
+
const commentary = share?.shareCommentary as Record<string, unknown> | undefined
|
|
109
|
+
const text = (commentary?.text ?? (post.text as Record<string, unknown>)?.text ?? '(no text)') as string
|
|
110
|
+
const preview = text.length > 120 ? text.slice(0, 120) + '…' : text
|
|
111
|
+
console.log(chalk.bold(id))
|
|
112
|
+
console.log(` ${chalk.dim(preview)}`)
|
|
113
|
+
console.log()
|
|
114
|
+
}
|
|
115
|
+
console.log(chalk.dim(`${posts.length} post(s)`))
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// blink linkedin post "text"
|
|
119
|
+
li.command('post <text>')
|
|
120
|
+
.description('Publish a text post to LinkedIn')
|
|
121
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
122
|
+
.option('--visibility <vis>', 'Post visibility: PUBLIC | CONNECTIONS (default: PUBLIC)', 'PUBLIC')
|
|
123
|
+
.addHelpText('after', `
|
|
124
|
+
Examples:
|
|
125
|
+
$ blink linkedin post "Excited to share our latest update!"
|
|
126
|
+
$ blink linkedin post "Internal update" --visibility CONNECTIONS
|
|
127
|
+
$ blink linkedin post "Hello LinkedIn" --json
|
|
128
|
+
`)
|
|
129
|
+
.action(async (text: string, opts) => {
|
|
130
|
+
requireToken()
|
|
131
|
+
const agentId = requireAgentId(opts.agent)
|
|
132
|
+
const data = await withSpinner('Publishing post...', () =>
|
|
133
|
+
liExec('/ugcPosts', 'POST', { text, visibility: opts.visibility }, agentId)
|
|
134
|
+
)
|
|
135
|
+
if (isJsonMode()) return printJson(data)
|
|
136
|
+
console.log(chalk.green('✓ Post published'))
|
|
137
|
+
if (data?.id) console.log(chalk.dim(` URN: ${data.id}`))
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// blink linkedin comments <postUrn>
|
|
141
|
+
li.command('comments <postUrn>')
|
|
142
|
+
.description('Read comments on a LinkedIn post')
|
|
143
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
144
|
+
.addHelpText('after', `
|
|
145
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
146
|
+
Use "blink linkedin posts" to find your post URNs.
|
|
147
|
+
|
|
148
|
+
Examples:
|
|
149
|
+
$ blink linkedin comments "urn:li:ugcPost:1234567890"
|
|
150
|
+
$ blink linkedin comments "urn:li:ugcPost:1234567890" --json
|
|
151
|
+
`)
|
|
152
|
+
.action(async (postUrn: string, opts) => {
|
|
153
|
+
requireToken()
|
|
154
|
+
const agentId = requireAgentId(opts.agent)
|
|
155
|
+
const encoded = encodeURIComponent(postUrn)
|
|
156
|
+
const data = await withSpinner('Fetching comments...', () =>
|
|
157
|
+
liExec(`rest/socialActions/${encoded}/comments`, 'GET', {}, agentId)
|
|
158
|
+
)
|
|
159
|
+
const comments: Array<Record<string, unknown>> = data?.elements ?? (Array.isArray(data) ? data : [])
|
|
160
|
+
if (isJsonMode()) return printJson(comments)
|
|
161
|
+
if (!comments.length) { console.log(chalk.dim('No comments.')); return }
|
|
162
|
+
for (const c of comments) {
|
|
163
|
+
const author = (c.actor ?? '—') as string
|
|
164
|
+
const msg = c.message as Record<string, unknown> | undefined
|
|
165
|
+
const text = (msg?.text ?? '(no text)') as string
|
|
166
|
+
console.log(chalk.bold(author))
|
|
167
|
+
console.log(` ${text}`)
|
|
168
|
+
console.log()
|
|
169
|
+
}
|
|
170
|
+
console.log(chalk.dim(`${comments.length} comment(s)`))
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
// blink linkedin comment <postUrn> "text"
|
|
174
|
+
li.command('comment <postUrn> <text>')
|
|
175
|
+
.description('Add a comment to a LinkedIn post')
|
|
176
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
177
|
+
.addHelpText('after', `
|
|
178
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
179
|
+
|
|
180
|
+
Examples:
|
|
181
|
+
$ blink linkedin comment "urn:li:ugcPost:1234567890" "Great post!"
|
|
182
|
+
$ blink linkedin comment "urn:li:ugcPost:1234567890" "Thanks for sharing" --json
|
|
183
|
+
`)
|
|
184
|
+
.action(async (postUrn: string, text: string, opts) => {
|
|
185
|
+
requireToken()
|
|
186
|
+
const agentId = requireAgentId(opts.agent)
|
|
187
|
+
const personId = await withSpinner('Resolving your LinkedIn identity...', () =>
|
|
188
|
+
getPersonId(agentId)
|
|
189
|
+
)
|
|
190
|
+
const actor = `urn:li:person:${personId}`
|
|
191
|
+
const encoded = encodeURIComponent(postUrn)
|
|
192
|
+
const data = await withSpinner('Adding comment...', () =>
|
|
193
|
+
liExec(
|
|
194
|
+
`rest/socialActions/${encoded}/comments`,
|
|
195
|
+
'POST',
|
|
196
|
+
{ actor, object: postUrn, message: { text } },
|
|
197
|
+
agentId
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
if (isJsonMode()) return printJson(data)
|
|
201
|
+
console.log(chalk.green('✓ Comment added'))
|
|
202
|
+
if (data?.id) console.log(chalk.dim(` ID: ${data.id}`))
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// blink linkedin like <postUrn>
|
|
206
|
+
li.command('like <postUrn>')
|
|
207
|
+
.description('Like a LinkedIn post')
|
|
208
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
209
|
+
.addHelpText('after', `
|
|
210
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
211
|
+
|
|
212
|
+
Examples:
|
|
213
|
+
$ blink linkedin like "urn:li:ugcPost:1234567890"
|
|
214
|
+
$ blink linkedin like "urn:li:ugcPost:1234567890" --json
|
|
215
|
+
`)
|
|
216
|
+
.action(async (postUrn: string, opts) => {
|
|
217
|
+
requireToken()
|
|
218
|
+
const agentId = requireAgentId(opts.agent)
|
|
219
|
+
const personId = await withSpinner('Resolving your LinkedIn identity...', () =>
|
|
220
|
+
getPersonId(agentId)
|
|
221
|
+
)
|
|
222
|
+
const actor = `urn:li:person:${personId}`
|
|
223
|
+
const encoded = encodeURIComponent(postUrn)
|
|
224
|
+
const data = await withSpinner('Liking post...', () =>
|
|
225
|
+
liExec(
|
|
226
|
+
`rest/socialActions/${encoded}/likes`,
|
|
227
|
+
'POST',
|
|
228
|
+
{ actor, object: postUrn },
|
|
229
|
+
agentId
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
if (isJsonMode()) return printJson(data)
|
|
233
|
+
console.log(chalk.green('✓ Post liked'))
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
// blink linkedin unlike <postUrn>
|
|
237
|
+
li.command('unlike <postUrn>')
|
|
238
|
+
.description('Unlike a LinkedIn post')
|
|
239
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
240
|
+
.addHelpText('after', `
|
|
241
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
242
|
+
|
|
243
|
+
Examples:
|
|
244
|
+
$ blink linkedin unlike "urn:li:ugcPost:1234567890"
|
|
245
|
+
$ blink linkedin unlike "urn:li:ugcPost:1234567890" --json
|
|
246
|
+
`)
|
|
247
|
+
.action(async (postUrn: string, opts) => {
|
|
248
|
+
requireToken()
|
|
249
|
+
const agentId = requireAgentId(opts.agent)
|
|
250
|
+
const personId = await withSpinner('Resolving your LinkedIn identity...', () =>
|
|
251
|
+
getPersonId(agentId)
|
|
252
|
+
)
|
|
253
|
+
const actor = `urn:li:person:${personId}`
|
|
254
|
+
const encodedPost = encodeURIComponent(postUrn)
|
|
255
|
+
const encodedActor = encodeURIComponent(actor)
|
|
256
|
+
// LinkedIn DELETE likes requires: DELETE /rest/socialActions/{postUrn}/likes/{actorUrn}?actor={actorUrn}
|
|
257
|
+
const data = await withSpinner('Unliking post...', () =>
|
|
258
|
+
liExec(
|
|
259
|
+
`rest/socialActions/${encodedPost}/likes/${encodedActor}?actor=${encodedActor}`,
|
|
260
|
+
'DELETE',
|
|
261
|
+
{},
|
|
262
|
+
agentId
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
if (isJsonMode()) return printJson(data)
|
|
266
|
+
console.log(chalk.green('✓ Post unliked'))
|
|
267
|
+
})
|
|
268
|
+
}
|