@blinkdotnew/cli 0.2.6 → 0.3.0
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 +71 -164
- package/package.json +1 -1
- package/src/commands/ai.ts +4 -1
- package/src/commands/connector.ts +22 -7
- package/src/commands/linkedin.ts +50 -166
package/dist/cli.js
CHANGED
|
@@ -478,7 +478,7 @@ Supported formats: mp3, wav, m4a, ogg
|
|
|
478
478
|
if (isJsonMode()) return printJson(result);
|
|
479
479
|
console.log(result?.text ?? result?.transcript ?? JSON.stringify(result));
|
|
480
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", `
|
|
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").option("--from <number>", "Phone number to call from (E.164 format, e.g. +14155551234). Uses primary number if omitted.").addHelpText("after", `
|
|
482
482
|
Examples:
|
|
483
483
|
$ blink ai call "+14155551234" "You are collecting a payment of $240 from John Smith. Be polite but firm."
|
|
484
484
|
$ blink ai call "+14155551234" "Confirm John's appointment for tomorrow at 3pm" --voice openai:nova
|
|
@@ -496,6 +496,7 @@ Voices:
|
|
|
496
496
|
cartesia:sonic-english Low-latency, natural
|
|
497
497
|
|
|
498
498
|
Call is charged to your workspace credits after completion (~1 credit/min).
|
|
499
|
+
Use --from to specify which of your workspace numbers to call from.
|
|
499
500
|
`).action(async (phoneNumber, systemPrompt, opts) => {
|
|
500
501
|
requireToken();
|
|
501
502
|
const result = await withSpinner(
|
|
@@ -505,13 +506,14 @@ Call is charged to your workspace credits after completion (~1 credit/min).
|
|
|
505
506
|
phone_number: phoneNumber,
|
|
506
507
|
system_prompt: systemPrompt,
|
|
507
508
|
voice: opts.voice,
|
|
508
|
-
max_duration_seconds: parseInt(opts.maxDuration)
|
|
509
|
+
max_duration_seconds: parseInt(opts.maxDuration),
|
|
510
|
+
...opts.from ? { from_number: opts.from } : {}
|
|
509
511
|
}
|
|
510
512
|
})
|
|
511
513
|
);
|
|
512
514
|
if (isJsonMode()) return printJson(result);
|
|
513
515
|
const callId = result?.call_id;
|
|
514
|
-
if (opts.
|
|
516
|
+
if (!opts.wait) {
|
|
515
517
|
console.log(`Call initiated: ${callId}`);
|
|
516
518
|
console.log(`Poll status: blink ai call-status ${callId}`);
|
|
517
519
|
return;
|
|
@@ -1227,7 +1229,7 @@ ${filtered.length} providers total. Connect at blink.new/settings?tab=connectors
|
|
|
1227
1229
|
${connected.length} provider(s) connected`));
|
|
1228
1230
|
}
|
|
1229
1231
|
});
|
|
1230
|
-
connector.command("exec <provider> <endpoint> [params]").description("Execute a call on a connected OAuth provider").option("--account <id>", "Specific account ID (if you have multiple accounts)").option("--method <method>", "HTTP method: GET | POST | PUT | PATCH | DELETE (default: POST)", "POST").addHelpText("after", `
|
|
1232
|
+
connector.command("exec <provider> <endpoint> [method-or-params] [params]").description("Execute a call on a connected OAuth provider").option("--account <id>", "Specific account ID (if you have multiple accounts)").option("--method <method>", "HTTP method: GET | POST | PUT | PATCH | DELETE (default: POST)", "POST").addHelpText("after", `
|
|
1231
1233
|
<endpoint> is the API path relative to the provider's base URL, OR a GraphQL query string for Linear.
|
|
1232
1234
|
|
|
1233
1235
|
Examples (REST):
|
|
@@ -1260,22 +1262,35 @@ Provider base URLs used:
|
|
|
1260
1262
|
jira https://api.atlassian.com/ex/jira/{cloudId}/rest/api/3/
|
|
1261
1263
|
shopify https://{shop}.myshopify.com/admin/api/2024-10/
|
|
1262
1264
|
... run \`blink connector providers\` for all 38 providers
|
|
1263
|
-
`).action(async (provider, endpoint,
|
|
1265
|
+
`).action(async (provider, endpoint, arg1, arg2, opts) => {
|
|
1264
1266
|
requireToken();
|
|
1267
|
+
const HTTP_METHODS = /^(GET|POST|PUT|PATCH|DELETE|HEAD)$/i;
|
|
1268
|
+
let httpMethod = opts.method;
|
|
1265
1269
|
let params = {};
|
|
1266
|
-
if (
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1270
|
+
if (arg1) {
|
|
1271
|
+
if (HTTP_METHODS.test(arg1)) {
|
|
1272
|
+
httpMethod = arg1.toUpperCase();
|
|
1273
|
+
if (arg2) {
|
|
1274
|
+
try {
|
|
1275
|
+
params = JSON.parse(arg2);
|
|
1276
|
+
} catch {
|
|
1277
|
+
params = {};
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
} else {
|
|
1281
|
+
try {
|
|
1282
|
+
params = JSON.parse(arg1);
|
|
1283
|
+
} catch {
|
|
1284
|
+
params = {};
|
|
1285
|
+
}
|
|
1271
1286
|
}
|
|
1272
1287
|
}
|
|
1273
1288
|
const result = await withSpinner(
|
|
1274
|
-
`${
|
|
1289
|
+
`${httpMethod} ${provider}${endpoint}...`,
|
|
1275
1290
|
() => resourcesRequest(`/v1/connectors/${provider}/execute`, {
|
|
1276
1291
|
body: {
|
|
1277
1292
|
method: endpoint,
|
|
1278
|
-
http_method:
|
|
1293
|
+
http_method: httpMethod,
|
|
1279
1294
|
params,
|
|
1280
1295
|
...opts.account ? { account_id: opts.account } : {}
|
|
1281
1296
|
}
|
|
@@ -1305,20 +1320,26 @@ async function getPersonId(agentId) {
|
|
|
1305
1320
|
return id;
|
|
1306
1321
|
}
|
|
1307
1322
|
function registerLinkedInCommands(program2) {
|
|
1308
|
-
const li = program2.command("linkedin").description("LinkedIn connector \u2014
|
|
1323
|
+
const li = program2.command("linkedin").description("LinkedIn connector \u2014 publish posts and manage your profile").addHelpText("after", `
|
|
1309
1324
|
LinkedIn must be linked to your agent via the Integrations tab at blink.new/claw.
|
|
1310
1325
|
Agent ID defaults to BLINK_AGENT_ID (automatically set on Claw Fly machines).
|
|
1311
1326
|
|
|
1327
|
+
What works today (w_member_social scope):
|
|
1328
|
+
\u2705 blink linkedin me Show your LinkedIn profile
|
|
1329
|
+
\u2705 blink linkedin post "Excited to announce..." Publish a text post
|
|
1330
|
+
\u2705 blink linkedin delete <postUrn> Delete one of your posts
|
|
1331
|
+
|
|
1332
|
+
Not yet available (requires LinkedIn Partner API approval):
|
|
1333
|
+
\u2717 Reading posts, comments, likes (needs r_member_social \u2014 restricted scope)
|
|
1334
|
+
\u2717 Adding comments (needs Community Management API)
|
|
1335
|
+
|
|
1312
1336
|
Examples:
|
|
1313
|
-
$ blink linkedin me
|
|
1314
|
-
$ blink linkedin
|
|
1315
|
-
$ blink linkedin post "
|
|
1316
|
-
$ blink linkedin
|
|
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
|
|
1337
|
+
$ blink linkedin me
|
|
1338
|
+
$ blink linkedin post "Our product just launched!"
|
|
1339
|
+
$ blink linkedin post "Team update" --visibility CONNECTIONS
|
|
1340
|
+
$ blink linkedin delete "urn:li:ugcPost:1234567890"
|
|
1320
1341
|
`);
|
|
1321
|
-
li.command("me").description("Show your LinkedIn profile (name, ID,
|
|
1342
|
+
li.command("me").description("Show your LinkedIn profile (name, ID, email)").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1322
1343
|
Examples:
|
|
1323
1344
|
$ blink linkedin me
|
|
1324
1345
|
$ blink linkedin me --json
|
|
@@ -1338,57 +1359,16 @@ Examples:
|
|
|
1338
1359
|
if (name) console.log(` ${chalk7.dim("Name:")} ${name}`);
|
|
1339
1360
|
if (data?.email) console.log(` ${chalk7.dim("Email:")} ${data.email}`);
|
|
1340
1361
|
});
|
|
1341
|
-
li.command("
|
|
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 authorUrn = encodeURIComponent(`urn:li:person:${personId}`);
|
|
1354
|
-
const data = await withSpinner(
|
|
1355
|
-
"Fetching posts...",
|
|
1356
|
-
() => liExec(
|
|
1357
|
-
`ugcPosts?q=authors&authors=List(${authorUrn})&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 ?? "(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", `
|
|
1362
|
+
li.command("post <text>").description("Publish a text post to LinkedIn").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").option("--visibility <vis>", "PUBLIC | CONNECTIONS (default: PUBLIC)", "PUBLIC").addHelpText("after", `
|
|
1383
1363
|
Examples:
|
|
1384
1364
|
$ blink linkedin post "Excited to share our latest update!"
|
|
1385
|
-
$ blink linkedin post "Internal update" --visibility CONNECTIONS
|
|
1365
|
+
$ blink linkedin post "Internal team update" --visibility CONNECTIONS
|
|
1386
1366
|
$ blink linkedin post "Hello LinkedIn" --json
|
|
1387
1367
|
`).action(async (text, opts) => {
|
|
1388
1368
|
requireToken();
|
|
1389
1369
|
const agentId = requireAgentId(opts.agent);
|
|
1390
1370
|
const personId = await withSpinner(
|
|
1391
|
-
"Resolving
|
|
1371
|
+
"Resolving LinkedIn identity...",
|
|
1392
1372
|
() => getPersonId(agentId)
|
|
1393
1373
|
);
|
|
1394
1374
|
const visibilityMap = {
|
|
@@ -1415,119 +1395,46 @@ Examples:
|
|
|
1415
1395
|
console.log(chalk7.green("\u2713 Post published"));
|
|
1416
1396
|
if (data?.id) console.log(chalk7.dim(` URN: ${data.id}`));
|
|
1417
1397
|
});
|
|
1418
|
-
li.command("
|
|
1419
|
-
|
|
1420
|
-
Use "blink linkedin posts" to find your post URNs.
|
|
1398
|
+
li.command("upload-media <media-url>").description("Upload an image or video to LinkedIn storage, returns asset URN for use in posts").option("--type <type>", "Media type: image | video (default: image)", "image").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1399
|
+
Returns an asset URN to use when composing a post with media via blink connector exec.
|
|
1421
1400
|
|
|
1422
1401
|
Examples:
|
|
1423
|
-
$ blink linkedin
|
|
1424
|
-
$ blink linkedin
|
|
1425
|
-
|
|
1402
|
+
$ blink linkedin upload-media https://example.com/photo.jpg
|
|
1403
|
+
$ blink linkedin upload-media https://example.com/demo.mp4 --type video
|
|
1404
|
+
$ ASSET_URN=$(blink linkedin upload-media https://example.com/photo.jpg --json | python3 -c "import json,sys; print(json.load(sys.stdin)['asset_urn'])")
|
|
1405
|
+
`).action(async (mediaUrl, opts) => {
|
|
1426
1406
|
requireToken();
|
|
1427
1407
|
const agentId = requireAgentId(opts.agent);
|
|
1428
|
-
const
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1408
|
+
const result = await withSpinner(
|
|
1409
|
+
`Uploading ${opts.type}...`,
|
|
1410
|
+
() => resourcesRequest("/v1/connectors/linkedin/upload-media", {
|
|
1411
|
+
body: { media_url: mediaUrl, media_type: opts.type },
|
|
1412
|
+
headers: { "x-blink-agent-id": agentId }
|
|
1413
|
+
})
|
|
1432
1414
|
);
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
if (
|
|
1436
|
-
console.log(chalk7.
|
|
1437
|
-
|
|
1415
|
+
if (isJsonMode()) return printJson(result?.data ?? result);
|
|
1416
|
+
const assetUrn = result?.data?.asset_urn;
|
|
1417
|
+
if (assetUrn) {
|
|
1418
|
+
console.log(chalk7.green("\u2713 Upload complete"));
|
|
1419
|
+
console.log(chalk7.dim(` Asset URN: ${assetUrn}`));
|
|
1438
1420
|
}
|
|
1439
|
-
for (const c of comments) {
|
|
1440
|
-
const author = c.actor ?? "\u2014";
|
|
1441
|
-
const msg = c.message;
|
|
1442
|
-
const text = msg?.text ?? "(no text)";
|
|
1443
|
-
console.log(chalk7.bold(author));
|
|
1444
|
-
console.log(` ${text}`);
|
|
1445
|
-
console.log();
|
|
1446
|
-
}
|
|
1447
|
-
console.log(chalk7.dim(`${comments.length} comment(s)`));
|
|
1448
|
-
});
|
|
1449
|
-
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", `
|
|
1450
|
-
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
1451
|
-
|
|
1452
|
-
Examples:
|
|
1453
|
-
$ blink linkedin comment "urn:li:ugcPost:1234567890" "Great post!"
|
|
1454
|
-
$ blink linkedin comment "urn:li:ugcPost:1234567890" "Thanks for sharing" --json
|
|
1455
|
-
`).action(async (postUrn, text, opts) => {
|
|
1456
|
-
requireToken();
|
|
1457
|
-
const agentId = requireAgentId(opts.agent);
|
|
1458
|
-
const personId = await withSpinner(
|
|
1459
|
-
"Resolving your LinkedIn identity...",
|
|
1460
|
-
() => getPersonId(agentId)
|
|
1461
|
-
);
|
|
1462
|
-
const actor = `urn:li:person:${personId}`;
|
|
1463
|
-
const encoded = encodeURIComponent(postUrn);
|
|
1464
|
-
const data = await withSpinner(
|
|
1465
|
-
"Adding comment...",
|
|
1466
|
-
() => liExec(
|
|
1467
|
-
`rest/socialActions/${encoded}/comments`,
|
|
1468
|
-
"POST",
|
|
1469
|
-
{ actor, object: postUrn, message: { text } },
|
|
1470
|
-
agentId
|
|
1471
|
-
)
|
|
1472
|
-
);
|
|
1473
|
-
if (isJsonMode()) return printJson(data);
|
|
1474
|
-
console.log(chalk7.green("\u2713 Comment added"));
|
|
1475
|
-
if (data?.id) console.log(chalk7.dim(` ID: ${data.id}`));
|
|
1476
1421
|
});
|
|
1477
|
-
li.command("
|
|
1478
|
-
<postUrn> is the LinkedIn post URN
|
|
1422
|
+
li.command("delete <postUrn>").description("Delete one of your LinkedIn posts").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1423
|
+
<postUrn> is the LinkedIn post URN returned when the post was created.
|
|
1424
|
+
e.g. urn:li:ugcPost:1234567890
|
|
1479
1425
|
|
|
1480
1426
|
Examples:
|
|
1481
|
-
$ blink linkedin
|
|
1482
|
-
$ blink linkedin like "urn:li:ugcPost:1234567890" --json
|
|
1427
|
+
$ blink linkedin delete "urn:li:ugcPost:1234567890"
|
|
1483
1428
|
`).action(async (postUrn, opts) => {
|
|
1484
1429
|
requireToken();
|
|
1485
1430
|
const agentId = requireAgentId(opts.agent);
|
|
1486
|
-
const personId = await withSpinner(
|
|
1487
|
-
"Resolving your LinkedIn identity...",
|
|
1488
|
-
() => getPersonId(agentId)
|
|
1489
|
-
);
|
|
1490
|
-
const actor = `urn:li:person:${personId}`;
|
|
1491
1431
|
const encoded = encodeURIComponent(postUrn);
|
|
1492
|
-
|
|
1493
|
-
"
|
|
1494
|
-
() => liExec(
|
|
1495
|
-
`rest/socialActions/${encoded}/likes`,
|
|
1496
|
-
"POST",
|
|
1497
|
-
{ actor, object: postUrn },
|
|
1498
|
-
agentId
|
|
1499
|
-
)
|
|
1500
|
-
);
|
|
1501
|
-
if (isJsonMode()) return printJson(data);
|
|
1502
|
-
console.log(chalk7.green("\u2713 Post liked"));
|
|
1503
|
-
});
|
|
1504
|
-
li.command("unlike <postUrn>").description("Unlike a LinkedIn post").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1505
|
-
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
1506
|
-
|
|
1507
|
-
Examples:
|
|
1508
|
-
$ blink linkedin unlike "urn:li:ugcPost:1234567890"
|
|
1509
|
-
$ blink linkedin unlike "urn:li:ugcPost:1234567890" --json
|
|
1510
|
-
`).action(async (postUrn, opts) => {
|
|
1511
|
-
requireToken();
|
|
1512
|
-
const agentId = requireAgentId(opts.agent);
|
|
1513
|
-
const personId = await withSpinner(
|
|
1514
|
-
"Resolving your LinkedIn identity...",
|
|
1515
|
-
() => getPersonId(agentId)
|
|
1516
|
-
);
|
|
1517
|
-
const actor = `urn:li:person:${personId}`;
|
|
1518
|
-
const encodedPost = encodeURIComponent(postUrn);
|
|
1519
|
-
const encodedActor = encodeURIComponent(actor);
|
|
1520
|
-
const data = await withSpinner(
|
|
1521
|
-
"Unliking post...",
|
|
1522
|
-
() => liExec(
|
|
1523
|
-
`rest/socialActions/${encodedPost}/likes/${encodedActor}?actor=${encodedActor}`,
|
|
1524
|
-
"DELETE",
|
|
1525
|
-
{},
|
|
1526
|
-
agentId
|
|
1527
|
-
)
|
|
1432
|
+
await withSpinner(
|
|
1433
|
+
"Deleting post...",
|
|
1434
|
+
() => liExec(`ugcPosts/${encoded}`, "DELETE", {}, agentId)
|
|
1528
1435
|
);
|
|
1529
|
-
if (isJsonMode()) return printJson(
|
|
1530
|
-
console.log(chalk7.green("\u2713 Post
|
|
1436
|
+
if (isJsonMode()) return printJson({ deleted: true, urn: postUrn });
|
|
1437
|
+
console.log(chalk7.green("\u2713 Post deleted"));
|
|
1531
1438
|
});
|
|
1532
1439
|
}
|
|
1533
1440
|
|
package/package.json
CHANGED
package/src/commands/ai.ts
CHANGED
|
@@ -290,6 +290,7 @@ Supported formats: mp3, wav, m4a, ogg
|
|
|
290
290
|
.option('--voice <voice>', 'Voice: openai:alloy | openai:nova | cartesia:sonic-english', 'openai:alloy')
|
|
291
291
|
.option('--max-duration <seconds>', 'Max call duration in seconds', '300')
|
|
292
292
|
.option('--no-wait', 'Return call_id immediately without waiting for completion')
|
|
293
|
+
.option('--from <number>', 'Phone number to call from (E.164 format, e.g. +14155551234). Uses primary number if omitted.')
|
|
293
294
|
.addHelpText('after', `
|
|
294
295
|
Examples:
|
|
295
296
|
$ blink ai call "+14155551234" "You are collecting a payment of $240 from John Smith. Be polite but firm."
|
|
@@ -308,6 +309,7 @@ Voices:
|
|
|
308
309
|
cartesia:sonic-english Low-latency, natural
|
|
309
310
|
|
|
310
311
|
Call is charged to your workspace credits after completion (~1 credit/min).
|
|
312
|
+
Use --from to specify which of your workspace numbers to call from.
|
|
311
313
|
`)
|
|
312
314
|
.action(async (phoneNumber, systemPrompt, opts) => {
|
|
313
315
|
requireToken()
|
|
@@ -318,6 +320,7 @@ Call is charged to your workspace credits after completion (~1 credit/min).
|
|
|
318
320
|
system_prompt: systemPrompt,
|
|
319
321
|
voice: opts.voice,
|
|
320
322
|
max_duration_seconds: parseInt(opts.maxDuration),
|
|
323
|
+
...(opts.from ? { from_number: opts.from } : {}),
|
|
321
324
|
},
|
|
322
325
|
})
|
|
323
326
|
)
|
|
@@ -325,7 +328,7 @@ Call is charged to your workspace credits after completion (~1 credit/min).
|
|
|
325
328
|
|
|
326
329
|
const callId = result?.call_id as string
|
|
327
330
|
|
|
328
|
-
if (opts.
|
|
331
|
+
if (!opts.wait) { // Commander --no-wait sets opts.wait=false (not opts.noWait)
|
|
329
332
|
console.log(`Call initiated: ${callId}`)
|
|
330
333
|
console.log(`Poll status: blink ai call-status ${callId}`)
|
|
331
334
|
return
|
|
@@ -149,8 +149,12 @@ Use --account <id> if you have multiple linked accounts for the same provider.
|
|
|
149
149
|
}
|
|
150
150
|
})
|
|
151
151
|
|
|
152
|
-
// blink connector exec <provider> <endpoint> [params]
|
|
153
|
-
|
|
152
|
+
// blink connector exec <provider> <endpoint> [method-or-params] [params]
|
|
153
|
+
// Supports both patterns:
|
|
154
|
+
// blink connector exec github /user/repos GET
|
|
155
|
+
// blink connector exec notion /search POST '{"query":"notes"}'
|
|
156
|
+
// blink connector exec notion /search '{"query":"notes"}'
|
|
157
|
+
connector.command('exec <provider> <endpoint> [method-or-params] [params]')
|
|
154
158
|
.description('Execute a call on a connected OAuth provider')
|
|
155
159
|
.option('--account <id>', 'Specific account ID (if you have multiple accounts)')
|
|
156
160
|
.option('--method <method>', 'HTTP method: GET | POST | PUT | PATCH | DELETE (default: POST)', 'POST')
|
|
@@ -188,17 +192,28 @@ Provider base URLs used:
|
|
|
188
192
|
shopify https://{shop}.myshopify.com/admin/api/2024-10/
|
|
189
193
|
... run \`blink connector providers\` for all 38 providers
|
|
190
194
|
`)
|
|
191
|
-
.action(async (provider: string, endpoint: string,
|
|
195
|
+
.action(async (provider: string, endpoint: string, arg1: string | undefined, arg2: string | undefined, opts) => {
|
|
192
196
|
requireToken()
|
|
197
|
+
const HTTP_METHODS = /^(GET|POST|PUT|PATCH|DELETE|HEAD)$/i
|
|
198
|
+
let httpMethod = opts.method
|
|
193
199
|
let params: Record<string, unknown> = {}
|
|
194
|
-
|
|
195
|
-
|
|
200
|
+
|
|
201
|
+
if (arg1) {
|
|
202
|
+
if (HTTP_METHODS.test(arg1)) {
|
|
203
|
+
// Pattern: exec provider endpoint GET ['{"key":"val"}']
|
|
204
|
+
httpMethod = arg1.toUpperCase()
|
|
205
|
+
if (arg2) { try { params = JSON.parse(arg2) } catch { params = {} } }
|
|
206
|
+
} else {
|
|
207
|
+
// Pattern: exec provider endpoint '{"key":"val"}'
|
|
208
|
+
try { params = JSON.parse(arg1) } catch { params = {} }
|
|
209
|
+
}
|
|
196
210
|
}
|
|
197
|
-
|
|
211
|
+
|
|
212
|
+
const result = await withSpinner(`${httpMethod} ${provider}${endpoint}...`, () =>
|
|
198
213
|
resourcesRequest(`/v1/connectors/${provider}/execute`, {
|
|
199
214
|
body: {
|
|
200
215
|
method: endpoint,
|
|
201
|
-
http_method:
|
|
216
|
+
http_method: httpMethod,
|
|
202
217
|
params,
|
|
203
218
|
...(opts.account ? { account_id: opts.account } : {}),
|
|
204
219
|
}
|
package/src/commands/linkedin.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
|
-
import { resourcesRequest } from '../lib/api-resources.js'
|
|
3
2
|
import { requireToken } from '../lib/auth.js'
|
|
4
3
|
import { requireAgentId } from '../lib/agent.js'
|
|
5
4
|
import { printJson, isJsonMode, withSpinner } from '../lib/output.js'
|
|
5
|
+
import { resourcesRequest } from '../lib/api-resources.js'
|
|
6
6
|
import chalk from 'chalk'
|
|
7
7
|
|
|
8
8
|
const NOT_LINKED = 'LinkedIn not linked. Link it in the Agent Integrations tab at blink.new/claw'
|
|
@@ -22,7 +22,6 @@ async function liExec(
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async function getPersonId(agentId: string): Promise<string> {
|
|
25
|
-
// Use OpenID Connect userinfo — more reliable than deprecated /v2/me
|
|
26
25
|
const data = await liExec('v2/userinfo', 'GET', {}, agentId)
|
|
27
26
|
const id = data?.sub ?? data?.id
|
|
28
27
|
if (!id) throw new Error('Could not resolve LinkedIn person ID')
|
|
@@ -31,24 +30,30 @@ async function getPersonId(agentId: string): Promise<string> {
|
|
|
31
30
|
|
|
32
31
|
export function registerLinkedInCommands(program: Command) {
|
|
33
32
|
const li = program.command('linkedin')
|
|
34
|
-
.description('LinkedIn connector —
|
|
33
|
+
.description('LinkedIn connector — publish posts and manage your profile')
|
|
35
34
|
.addHelpText('after', `
|
|
36
35
|
LinkedIn must be linked to your agent via the Integrations tab at blink.new/claw.
|
|
37
36
|
Agent ID defaults to BLINK_AGENT_ID (automatically set on Claw Fly machines).
|
|
38
37
|
|
|
38
|
+
What works today (w_member_social scope):
|
|
39
|
+
✅ blink linkedin me Show your LinkedIn profile
|
|
40
|
+
✅ blink linkedin post "Excited to announce..." Publish a text post
|
|
41
|
+
✅ blink linkedin delete <postUrn> Delete one of your posts
|
|
42
|
+
|
|
43
|
+
Not yet available (requires LinkedIn Partner API approval):
|
|
44
|
+
✗ Reading posts, comments, likes (needs r_member_social — restricted scope)
|
|
45
|
+
✗ Adding comments (needs Community Management API)
|
|
46
|
+
|
|
39
47
|
Examples:
|
|
40
|
-
$ blink linkedin me
|
|
41
|
-
$ blink linkedin
|
|
42
|
-
$ blink linkedin post "
|
|
43
|
-
$ blink linkedin
|
|
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
|
|
48
|
+
$ blink linkedin me
|
|
49
|
+
$ blink linkedin post "Our product just launched!"
|
|
50
|
+
$ blink linkedin post "Team update" --visibility CONNECTIONS
|
|
51
|
+
$ blink linkedin delete "urn:li:ugcPost:1234567890"
|
|
47
52
|
`)
|
|
48
53
|
|
|
49
54
|
// blink linkedin me
|
|
50
55
|
li.command('me')
|
|
51
|
-
.description('Show your LinkedIn profile (name, ID,
|
|
56
|
+
.description('Show your LinkedIn profile (name, ID, email)')
|
|
52
57
|
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
53
58
|
.addHelpText('after', `
|
|
54
59
|
Examples:
|
|
@@ -59,7 +64,6 @@ Examples:
|
|
|
59
64
|
.action(async (opts) => {
|
|
60
65
|
requireToken()
|
|
61
66
|
const agentId = requireAgentId(opts.agent)
|
|
62
|
-
// Use OpenID Connect userinfo endpoint — more reliable than /v2/me
|
|
63
67
|
const data = await withSpinner('Fetching LinkedIn profile...', () =>
|
|
64
68
|
liExec('v2/userinfo', 'GET', {}, agentId)
|
|
65
69
|
)
|
|
@@ -67,69 +71,26 @@ Examples:
|
|
|
67
71
|
const name = data?.name ?? [data?.given_name, data?.family_name].filter(Boolean).join(' ')
|
|
68
72
|
const personId = data?.sub ?? data?.id
|
|
69
73
|
console.log(chalk.bold('LinkedIn Profile'))
|
|
70
|
-
if (personId)
|
|
71
|
-
if (name)
|
|
74
|
+
if (personId) console.log(` ${chalk.dim('ID:')} ${personId}`)
|
|
75
|
+
if (name) console.log(` ${chalk.dim('Name:')} ${name}`)
|
|
72
76
|
if (data?.email) console.log(` ${chalk.dim('Email:')} ${data.email}`)
|
|
73
77
|
})
|
|
74
78
|
|
|
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 authorUrn = encodeURIComponent(`urn:li:person:${personId}`)
|
|
93
|
-
const data = await withSpinner('Fetching posts...', () =>
|
|
94
|
-
liExec(
|
|
95
|
-
`ugcPosts?q=authors&authors=List(${authorUrn})&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 ?? '(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
79
|
// blink linkedin post "text"
|
|
119
80
|
li.command('post <text>')
|
|
120
81
|
.description('Publish a text post to LinkedIn')
|
|
121
82
|
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
122
|
-
.option('--visibility <vis>', '
|
|
83
|
+
.option('--visibility <vis>', 'PUBLIC | CONNECTIONS (default: PUBLIC)', 'PUBLIC')
|
|
123
84
|
.addHelpText('after', `
|
|
124
85
|
Examples:
|
|
125
86
|
$ blink linkedin post "Excited to share our latest update!"
|
|
126
|
-
$ blink linkedin post "Internal update" --visibility CONNECTIONS
|
|
87
|
+
$ blink linkedin post "Internal team update" --visibility CONNECTIONS
|
|
127
88
|
$ blink linkedin post "Hello LinkedIn" --json
|
|
128
89
|
`)
|
|
129
90
|
.action(async (text: string, opts) => {
|
|
130
91
|
requireToken()
|
|
131
92
|
const agentId = requireAgentId(opts.agent)
|
|
132
|
-
const personId = await withSpinner('Resolving
|
|
93
|
+
const personId = await withSpinner('Resolving LinkedIn identity...', () =>
|
|
133
94
|
getPersonId(agentId)
|
|
134
95
|
)
|
|
135
96
|
const visibilityMap: Record<string, string> = {
|
|
@@ -156,132 +117,55 @@ Examples:
|
|
|
156
117
|
if (data?.id) console.log(chalk.dim(` URN: ${data.id}`))
|
|
157
118
|
})
|
|
158
119
|
|
|
159
|
-
// blink linkedin
|
|
160
|
-
li.command('
|
|
161
|
-
.description('
|
|
120
|
+
// blink linkedin upload-media <media-url>
|
|
121
|
+
li.command('upload-media <media-url>')
|
|
122
|
+
.description('Upload an image or video to LinkedIn storage, returns asset URN for use in posts')
|
|
123
|
+
.option('--type <type>', 'Media type: image | video (default: image)', 'image')
|
|
162
124
|
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
163
125
|
.addHelpText('after', `
|
|
164
|
-
|
|
165
|
-
Use "blink linkedin posts" to find your post URNs.
|
|
126
|
+
Returns an asset URN to use when composing a post with media via blink connector exec.
|
|
166
127
|
|
|
167
128
|
Examples:
|
|
168
|
-
$ blink linkedin
|
|
169
|
-
$ blink linkedin
|
|
129
|
+
$ blink linkedin upload-media https://example.com/photo.jpg
|
|
130
|
+
$ blink linkedin upload-media https://example.com/demo.mp4 --type video
|
|
131
|
+
$ ASSET_URN=$(blink linkedin upload-media https://example.com/photo.jpg --json | python3 -c "import json,sys; print(json.load(sys.stdin)['asset_urn'])")
|
|
170
132
|
`)
|
|
171
|
-
.action(async (
|
|
133
|
+
.action(async (mediaUrl: string, opts) => {
|
|
172
134
|
requireToken()
|
|
173
135
|
const agentId = requireAgentId(opts.agent)
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
136
|
+
const result = await withSpinner(`Uploading ${opts.type}...`, () =>
|
|
137
|
+
resourcesRequest('/v1/connectors/linkedin/upload-media', {
|
|
138
|
+
body: { media_url: mediaUrl, media_type: opts.type },
|
|
139
|
+
headers: { 'x-blink-agent-id': agentId },
|
|
140
|
+
})
|
|
177
141
|
)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const msg = c.message as Record<string, unknown> | undefined
|
|
184
|
-
const text = (msg?.text ?? '(no text)') as string
|
|
185
|
-
console.log(chalk.bold(author))
|
|
186
|
-
console.log(` ${text}`)
|
|
187
|
-
console.log()
|
|
142
|
+
if (isJsonMode()) return printJson(result?.data ?? result)
|
|
143
|
+
const assetUrn = result?.data?.asset_urn
|
|
144
|
+
if (assetUrn) {
|
|
145
|
+
console.log(chalk.green('✓ Upload complete'))
|
|
146
|
+
console.log(chalk.dim(` Asset URN: ${assetUrn}`))
|
|
188
147
|
}
|
|
189
|
-
console.log(chalk.dim(`${comments.length} comment(s)`))
|
|
190
148
|
})
|
|
191
149
|
|
|
192
|
-
// blink linkedin
|
|
193
|
-
li.command('
|
|
194
|
-
.description('
|
|
150
|
+
// blink linkedin delete <postUrn>
|
|
151
|
+
li.command('delete <postUrn>')
|
|
152
|
+
.description('Delete one of your LinkedIn posts')
|
|
195
153
|
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
196
154
|
.addHelpText('after', `
|
|
197
|
-
<postUrn> is the LinkedIn post URN
|
|
155
|
+
<postUrn> is the LinkedIn post URN returned when the post was created.
|
|
156
|
+
e.g. urn:li:ugcPost:1234567890
|
|
198
157
|
|
|
199
158
|
Examples:
|
|
200
|
-
$ blink linkedin
|
|
201
|
-
$ blink linkedin comment "urn:li:ugcPost:1234567890" "Thanks for sharing" --json
|
|
202
|
-
`)
|
|
203
|
-
.action(async (postUrn: string, text: string, opts) => {
|
|
204
|
-
requireToken()
|
|
205
|
-
const agentId = requireAgentId(opts.agent)
|
|
206
|
-
const personId = await withSpinner('Resolving your LinkedIn identity...', () =>
|
|
207
|
-
getPersonId(agentId)
|
|
208
|
-
)
|
|
209
|
-
const actor = `urn:li:person:${personId}`
|
|
210
|
-
const encoded = encodeURIComponent(postUrn)
|
|
211
|
-
const data = await withSpinner('Adding comment...', () =>
|
|
212
|
-
liExec(
|
|
213
|
-
`rest/socialActions/${encoded}/comments`,
|
|
214
|
-
'POST',
|
|
215
|
-
{ actor, object: postUrn, message: { text } },
|
|
216
|
-
agentId
|
|
217
|
-
)
|
|
218
|
-
)
|
|
219
|
-
if (isJsonMode()) return printJson(data)
|
|
220
|
-
console.log(chalk.green('✓ Comment added'))
|
|
221
|
-
if (data?.id) console.log(chalk.dim(` ID: ${data.id}`))
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
// blink linkedin like <postUrn>
|
|
225
|
-
li.command('like <postUrn>')
|
|
226
|
-
.description('Like a LinkedIn post')
|
|
227
|
-
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
228
|
-
.addHelpText('after', `
|
|
229
|
-
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
230
|
-
|
|
231
|
-
Examples:
|
|
232
|
-
$ blink linkedin like "urn:li:ugcPost:1234567890"
|
|
233
|
-
$ blink linkedin like "urn:li:ugcPost:1234567890" --json
|
|
159
|
+
$ blink linkedin delete "urn:li:ugcPost:1234567890"
|
|
234
160
|
`)
|
|
235
161
|
.action(async (postUrn: string, opts) => {
|
|
236
162
|
requireToken()
|
|
237
163
|
const agentId = requireAgentId(opts.agent)
|
|
238
|
-
const personId = await withSpinner('Resolving your LinkedIn identity...', () =>
|
|
239
|
-
getPersonId(agentId)
|
|
240
|
-
)
|
|
241
|
-
const actor = `urn:li:person:${personId}`
|
|
242
164
|
const encoded = encodeURIComponent(postUrn)
|
|
243
|
-
|
|
244
|
-
liExec(
|
|
245
|
-
`rest/socialActions/${encoded}/likes`,
|
|
246
|
-
'POST',
|
|
247
|
-
{ actor, object: postUrn },
|
|
248
|
-
agentId
|
|
249
|
-
)
|
|
165
|
+
await withSpinner('Deleting post...', () =>
|
|
166
|
+
liExec(`ugcPosts/${encoded}`, 'DELETE', {}, agentId)
|
|
250
167
|
)
|
|
251
|
-
if (isJsonMode()) return printJson(
|
|
252
|
-
console.log(chalk.green('✓ Post
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
// blink linkedin unlike <postUrn>
|
|
256
|
-
li.command('unlike <postUrn>')
|
|
257
|
-
.description('Unlike a LinkedIn post')
|
|
258
|
-
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
259
|
-
.addHelpText('after', `
|
|
260
|
-
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
261
|
-
|
|
262
|
-
Examples:
|
|
263
|
-
$ blink linkedin unlike "urn:li:ugcPost:1234567890"
|
|
264
|
-
$ blink linkedin unlike "urn:li:ugcPost:1234567890" --json
|
|
265
|
-
`)
|
|
266
|
-
.action(async (postUrn: string, opts) => {
|
|
267
|
-
requireToken()
|
|
268
|
-
const agentId = requireAgentId(opts.agent)
|
|
269
|
-
const personId = await withSpinner('Resolving your LinkedIn identity...', () =>
|
|
270
|
-
getPersonId(agentId)
|
|
271
|
-
)
|
|
272
|
-
const actor = `urn:li:person:${personId}`
|
|
273
|
-
const encodedPost = encodeURIComponent(postUrn)
|
|
274
|
-
const encodedActor = encodeURIComponent(actor)
|
|
275
|
-
// LinkedIn DELETE likes requires: DELETE /rest/socialActions/{postUrn}/likes/{actorUrn}?actor={actorUrn}
|
|
276
|
-
const data = await withSpinner('Unliking post...', () =>
|
|
277
|
-
liExec(
|
|
278
|
-
`rest/socialActions/${encodedPost}/likes/${encodedActor}?actor=${encodedActor}`,
|
|
279
|
-
'DELETE',
|
|
280
|
-
{},
|
|
281
|
-
agentId
|
|
282
|
-
)
|
|
283
|
-
)
|
|
284
|
-
if (isJsonMode()) return printJson(data)
|
|
285
|
-
console.log(chalk.green('✓ Post unliked'))
|
|
168
|
+
if (isJsonMode()) return printJson({ deleted: true, urn: postUrn })
|
|
169
|
+
console.log(chalk.green('✓ Post deleted'))
|
|
286
170
|
})
|
|
287
171
|
}
|