@blinkdotnew/cli 0.3.1 → 0.3.3
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 +180 -42
- package/package.json +1 -1
- package/src/commands/agent.ts +44 -0
- package/src/commands/connector.ts +47 -1
- package/src/commands/linkedin.ts +116 -44
- package/src/commands/phone.ts +2 -1
package/dist/cli.js
CHANGED
|
@@ -1127,6 +1127,7 @@ The email is sent from your project's configured sender address (set in blink.ne
|
|
|
1127
1127
|
}
|
|
1128
1128
|
|
|
1129
1129
|
// src/commands/connector.ts
|
|
1130
|
+
init_agent();
|
|
1130
1131
|
import chalk6 from "chalk";
|
|
1131
1132
|
var PROVIDERS = [
|
|
1132
1133
|
// Communication
|
|
@@ -1233,8 +1234,10 @@ ${filtered.length} providers total. Connect at blink.new/settings?tab=connectors
|
|
|
1233
1234
|
if (d.metadata?.email) console.log(chalk6.dim(` Email: ${d.metadata.email}`));
|
|
1234
1235
|
if (d.metadata?.name) console.log(chalk6.dim(` Name: ${d.metadata.name}`));
|
|
1235
1236
|
} else {
|
|
1237
|
+
const agentUrl = process.env.BLINK_WORKSPACE_SLUG && process.env.BLINK_AGENT_ID ? `https://blink.new/${process.env.BLINK_WORKSPACE_SLUG}/claw/${process.env.BLINK_AGENT_ID}` : "https://blink.new/settings?tab=connectors";
|
|
1236
1238
|
console.log(chalk6.red("\u2717 Not connected"));
|
|
1237
|
-
console.log(chalk6.dim(`
|
|
1239
|
+
console.log(chalk6.dim(` Fix: blink connector link ${provider}`));
|
|
1240
|
+
console.log(chalk6.dim(` Or visit: ${agentUrl}`));
|
|
1238
1241
|
}
|
|
1239
1242
|
} else {
|
|
1240
1243
|
const result = await withSpinner(
|
|
@@ -1257,6 +1260,40 @@ ${filtered.length} providers total. Connect at blink.new/settings?tab=connectors
|
|
|
1257
1260
|
${connected.length} provider(s) connected`));
|
|
1258
1261
|
}
|
|
1259
1262
|
});
|
|
1263
|
+
connector.command("link <provider>").description("Auto-link a workspace connector to the current agent").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1264
|
+
Links the workspace's connected account for <provider> to this agent automatically.
|
|
1265
|
+
The workspace must already have the account connected at blink.new/settings?tab=connectors.
|
|
1266
|
+
|
|
1267
|
+
This is the self-service fix when an agent gets "not linked" errors \u2014 run this once
|
|
1268
|
+
and the connector will be available for all subsequent commands.
|
|
1269
|
+
|
|
1270
|
+
Examples:
|
|
1271
|
+
$ blink connector link linkedin Link LinkedIn to this agent
|
|
1272
|
+
$ blink connector link notion Link Notion to this agent
|
|
1273
|
+
$ blink connector link slack Link Slack to this agent
|
|
1274
|
+
$ blink connector link linkedin --json Machine-readable output
|
|
1275
|
+
`).action(async (provider, opts) => {
|
|
1276
|
+
requireToken();
|
|
1277
|
+
const agentId = requireAgentId(opts.agent);
|
|
1278
|
+
const result = await withSpinner(
|
|
1279
|
+
`Linking ${provider} to agent ${agentId}...`,
|
|
1280
|
+
() => resourcesRequest(`/v1/connectors/${provider}/link`, {
|
|
1281
|
+
method: "POST",
|
|
1282
|
+
headers: { "x-blink-agent-id": agentId },
|
|
1283
|
+
body: {}
|
|
1284
|
+
})
|
|
1285
|
+
);
|
|
1286
|
+
if (!result?.success) {
|
|
1287
|
+
const agentUrl = process.env.BLINK_WORKSPACE_SLUG && process.env.BLINK_AGENT_ID ? `https://blink.new/${process.env.BLINK_WORKSPACE_SLUG}/claw/${process.env.BLINK_AGENT_ID}` : "https://blink.new/settings?tab=connectors";
|
|
1288
|
+
console.error(chalk6.red(`\u2717 ${result?.error ?? `No ${provider} account in workspace`}`));
|
|
1289
|
+
console.error(chalk6.dim(` Connect one first: ${agentUrl}`));
|
|
1290
|
+
process.exit(1);
|
|
1291
|
+
}
|
|
1292
|
+
if (isJsonMode()) return printJson(result.data);
|
|
1293
|
+
const label = result.data?.account_label ?? provider;
|
|
1294
|
+
console.log(chalk6.green(`\u2713 ${provider} linked`) + chalk6.dim(` (${label})`));
|
|
1295
|
+
console.log(chalk6.dim(` Agent ${agentId} can now use blink connector exec ${provider}`));
|
|
1296
|
+
});
|
|
1260
1297
|
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", `
|
|
1261
1298
|
<endpoint> is the API path relative to the provider's base URL, OR a GraphQL query string for Linear.
|
|
1262
1299
|
|
|
@@ -1332,13 +1369,24 @@ Provider base URLs used:
|
|
|
1332
1369
|
// src/commands/linkedin.ts
|
|
1333
1370
|
init_agent();
|
|
1334
1371
|
import chalk7 from "chalk";
|
|
1335
|
-
|
|
1372
|
+
function getAgentPageUrl() {
|
|
1373
|
+
const slug = process.env.BLINK_WORKSPACE_SLUG;
|
|
1374
|
+
const agentId = process.env.BLINK_AGENT_ID;
|
|
1375
|
+
if (slug && agentId) return `https://blink.new/${slug}/claw/${agentId}`;
|
|
1376
|
+
return "https://blink.new/claw";
|
|
1377
|
+
}
|
|
1378
|
+
function notLinkedError() {
|
|
1379
|
+
const url = getAgentPageUrl();
|
|
1380
|
+
return `LinkedIn not linked to this agent.
|
|
1381
|
+
Fix: blink connector link linkedin
|
|
1382
|
+
Or connect manually: ${url}`;
|
|
1383
|
+
}
|
|
1336
1384
|
async function liExec(method, httpMethod, params, agentId) {
|
|
1337
1385
|
const result = await resourcesRequest("/v1/connectors/linkedin/execute", {
|
|
1338
1386
|
body: { method, http_method: httpMethod, params },
|
|
1339
1387
|
headers: { "x-blink-agent-id": agentId }
|
|
1340
1388
|
});
|
|
1341
|
-
if (!result?.success) throw new Error(result?.error ??
|
|
1389
|
+
if (!result?.success) throw new Error(result?.error ?? notLinkedError());
|
|
1342
1390
|
return result.data;
|
|
1343
1391
|
}
|
|
1344
1392
|
async function getPersonId(agentId) {
|
|
@@ -1348,30 +1396,40 @@ async function getPersonId(agentId) {
|
|
|
1348
1396
|
return id;
|
|
1349
1397
|
}
|
|
1350
1398
|
function registerLinkedInCommands(program2) {
|
|
1351
|
-
const li = program2.command("linkedin").description("LinkedIn connector \u2014 publish posts and manage your profile").addHelpText("after", `
|
|
1399
|
+
const li = program2.command("linkedin").description("LinkedIn connector \u2014 publish posts, comment, react, and manage your profile").addHelpText("after", `
|
|
1352
1400
|
LinkedIn must be linked to your agent via the Integrations tab at blink.new/claw.
|
|
1353
1401
|
Agent ID defaults to BLINK_AGENT_ID (automatically set on Claw Fly machines).
|
|
1354
1402
|
|
|
1355
1403
|
What works today (w_member_social scope):
|
|
1356
1404
|
\u2705 blink linkedin me Show your LinkedIn profile
|
|
1357
|
-
\u2705 blink linkedin post "
|
|
1405
|
+
\u2705 blink linkedin post "text" Publish a text post
|
|
1358
1406
|
\u2705 blink linkedin delete <postUrn> Delete one of your posts
|
|
1407
|
+
\u2705 blink linkedin like <postUrn> Like a post
|
|
1408
|
+
\u2705 blink linkedin unlike <postUrn> Unlike a post
|
|
1409
|
+
\u2705 blink linkedin comment <postUrn> "text" Add a comment
|
|
1359
1410
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1411
|
+
For feed reading, search, and profiles \u2014 use scripts/lk.py (requires cookies):
|
|
1412
|
+
python3 scripts/lk.py feed -n 10 Browse your LinkedIn feed
|
|
1413
|
+
python3 scripts/lk.py search "query" Search people
|
|
1414
|
+
python3 scripts/lk.py profile <id> View a profile
|
|
1415
|
+
python3 scripts/lk.py messages Check messages
|
|
1416
|
+
See SKILL.md for cookie setup instructions.
|
|
1417
|
+
|
|
1418
|
+
Post URNs: use the URN returned by "blink linkedin post --json" or extract from
|
|
1419
|
+
a LinkedIn post URL: https://linkedin.com/feed/update/urn:li:activity:123...
|
|
1363
1420
|
|
|
1364
1421
|
Examples:
|
|
1365
1422
|
$ blink linkedin me
|
|
1366
|
-
$ blink linkedin post "Our product just launched!"
|
|
1367
|
-
$ blink linkedin
|
|
1368
|
-
$ blink linkedin
|
|
1423
|
+
$ blink linkedin post "Our product just launched! \u{1F680}"
|
|
1424
|
+
$ blink linkedin like "urn:li:share:1234567890"
|
|
1425
|
+
$ blink linkedin comment "urn:li:share:1234567890" "Great post!"
|
|
1426
|
+
$ blink linkedin delete "urn:li:share:1234567890"
|
|
1369
1427
|
`);
|
|
1370
1428
|
li.command("me").description("Show your LinkedIn profile (name, ID, email)").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1371
1429
|
Examples:
|
|
1372
1430
|
$ blink linkedin me
|
|
1373
1431
|
$ blink linkedin me --json
|
|
1374
|
-
$ blink linkedin me --
|
|
1432
|
+
$ PERSON_ID=$(blink linkedin me --json | jq -r .sub)
|
|
1375
1433
|
`).action(async (opts) => {
|
|
1376
1434
|
requireToken();
|
|
1377
1435
|
const agentId = requireAgentId(opts.agent);
|
|
@@ -1388,10 +1446,12 @@ Examples:
|
|
|
1388
1446
|
if (data?.email) console.log(` ${chalk7.dim("Email:")} ${data.email}`);
|
|
1389
1447
|
});
|
|
1390
1448
|
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", `
|
|
1449
|
+
Returns the post URN which you can pass to like/comment/delete.
|
|
1450
|
+
|
|
1391
1451
|
Examples:
|
|
1392
1452
|
$ blink linkedin post "Excited to share our latest update!"
|
|
1393
|
-
$ blink linkedin post "Internal
|
|
1394
|
-
$ blink linkedin post "Hello LinkedIn" --json
|
|
1453
|
+
$ blink linkedin post "Internal update" --visibility CONNECTIONS
|
|
1454
|
+
$ POST_URN=$(blink linkedin post "Hello LinkedIn" --json | jq -r .id)
|
|
1395
1455
|
`).action(async (text, opts) => {
|
|
1396
1456
|
requireToken();
|
|
1397
1457
|
const agentId = requireAgentId(opts.agent);
|
|
@@ -1423,46 +1483,88 @@ Examples:
|
|
|
1423
1483
|
console.log(chalk7.green("\u2713 Post published"));
|
|
1424
1484
|
if (data?.id) console.log(chalk7.dim(` URN: ${data.id}`));
|
|
1425
1485
|
});
|
|
1426
|
-
li.command("
|
|
1427
|
-
Returns an asset URN to use when composing a post with media via blink connector exec.
|
|
1428
|
-
|
|
1486
|
+
li.command("delete <postUrn>").description("Delete one of your LinkedIn posts").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1429
1487
|
Examples:
|
|
1430
|
-
$ blink linkedin
|
|
1431
|
-
|
|
1432
|
-
$ ASSET_URN=$(blink linkedin upload-media https://example.com/photo.jpg --json | python3 -c "import json,sys; print(json.load(sys.stdin)['asset_urn'])")
|
|
1433
|
-
`).action(async (mediaUrl, opts) => {
|
|
1488
|
+
$ blink linkedin delete "urn:li:share:1234567890"
|
|
1489
|
+
`).action(async (postUrn, opts) => {
|
|
1434
1490
|
requireToken();
|
|
1435
1491
|
const agentId = requireAgentId(opts.agent);
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
headers: { "x-blink-agent-id": agentId }
|
|
1441
|
-
})
|
|
1492
|
+
const encoded = encodeURIComponent(postUrn);
|
|
1493
|
+
await withSpinner(
|
|
1494
|
+
"Deleting post...",
|
|
1495
|
+
() => liExec(`ugcPosts/${encoded}`, "DELETE", {}, agentId)
|
|
1442
1496
|
);
|
|
1443
|
-
if (isJsonMode()) return printJson(
|
|
1444
|
-
|
|
1445
|
-
if (assetUrn) {
|
|
1446
|
-
console.log(chalk7.green("\u2713 Upload complete"));
|
|
1447
|
-
console.log(chalk7.dim(` Asset URN: ${assetUrn}`));
|
|
1448
|
-
}
|
|
1497
|
+
if (isJsonMode()) return printJson({ deleted: true, urn: postUrn });
|
|
1498
|
+
console.log(chalk7.green("\u2713 Post deleted"));
|
|
1449
1499
|
});
|
|
1450
|
-
li.command("
|
|
1451
|
-
<postUrn>
|
|
1452
|
-
|
|
1500
|
+
li.command("like <postUrn>").description("Like a LinkedIn post").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1501
|
+
<postUrn> accepts urn:li:share:, urn:li:ugcPost:, or urn:li:activity: URNs.
|
|
1502
|
+
LinkedIn feed URLs contain the activity URN: linkedin.com/feed/update/urn:li:activity:123
|
|
1453
1503
|
|
|
1454
1504
|
Examples:
|
|
1455
|
-
$ blink linkedin
|
|
1505
|
+
$ blink linkedin like "urn:li:share:1234567890"
|
|
1506
|
+
$ blink linkedin like "urn:li:activity:1234567890"
|
|
1456
1507
|
`).action(async (postUrn, opts) => {
|
|
1457
1508
|
requireToken();
|
|
1458
1509
|
const agentId = requireAgentId(opts.agent);
|
|
1510
|
+
const personId = await withSpinner(
|
|
1511
|
+
"Resolving LinkedIn identity...",
|
|
1512
|
+
() => getPersonId(agentId)
|
|
1513
|
+
);
|
|
1459
1514
|
const encoded = encodeURIComponent(postUrn);
|
|
1515
|
+
const data = await withSpinner(
|
|
1516
|
+
"Liking post...",
|
|
1517
|
+
() => liExec(`v2/socialActions/${encoded}/likes`, "POST", {
|
|
1518
|
+
actor: `urn:li:person:${personId}`
|
|
1519
|
+
}, agentId)
|
|
1520
|
+
);
|
|
1521
|
+
if (isJsonMode()) return printJson(data);
|
|
1522
|
+
console.log(chalk7.green("\u2713 Post liked"));
|
|
1523
|
+
if (data?.["$URN"]) console.log(chalk7.dim(` Like URN: ${data["$URN"]}`));
|
|
1524
|
+
});
|
|
1525
|
+
li.command("unlike <postUrn>").description("Unlike a LinkedIn post").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1526
|
+
Examples:
|
|
1527
|
+
$ blink linkedin unlike "urn:li:share:1234567890"
|
|
1528
|
+
`).action(async (postUrn, opts) => {
|
|
1529
|
+
requireToken();
|
|
1530
|
+
const agentId = requireAgentId(opts.agent);
|
|
1531
|
+
const personId = await withSpinner(
|
|
1532
|
+
"Resolving LinkedIn identity...",
|
|
1533
|
+
() => getPersonId(agentId)
|
|
1534
|
+
);
|
|
1535
|
+
const encodedPost = encodeURIComponent(postUrn);
|
|
1536
|
+
const encodedPerson = encodeURIComponent(`urn:li:person:${personId}`);
|
|
1460
1537
|
await withSpinner(
|
|
1461
|
-
"
|
|
1462
|
-
() => liExec(`
|
|
1538
|
+
"Unliking post...",
|
|
1539
|
+
() => liExec(`v2/socialActions/${encodedPost}/likes/${encodedPerson}`, "DELETE", {}, agentId)
|
|
1463
1540
|
);
|
|
1464
|
-
if (isJsonMode()) return printJson({
|
|
1465
|
-
console.log(chalk7.green("\u2713 Post
|
|
1541
|
+
if (isJsonMode()) return printJson({ unliked: true });
|
|
1542
|
+
console.log(chalk7.green("\u2713 Post unliked"));
|
|
1543
|
+
});
|
|
1544
|
+
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", `
|
|
1545
|
+
<postUrn> accepts urn:li:share:, urn:li:ugcPost:, or urn:li:activity: URNs.
|
|
1546
|
+
|
|
1547
|
+
Examples:
|
|
1548
|
+
$ blink linkedin comment "urn:li:share:1234567890" "Great post!"
|
|
1549
|
+
$ blink linkedin comment "urn:li:activity:1234567890" "Thanks for sharing"
|
|
1550
|
+
`).action(async (postUrn, text, opts) => {
|
|
1551
|
+
requireToken();
|
|
1552
|
+
const agentId = requireAgentId(opts.agent);
|
|
1553
|
+
const personId = await withSpinner(
|
|
1554
|
+
"Resolving LinkedIn identity...",
|
|
1555
|
+
() => getPersonId(agentId)
|
|
1556
|
+
);
|
|
1557
|
+
const encoded = encodeURIComponent(postUrn);
|
|
1558
|
+
const data = await withSpinner(
|
|
1559
|
+
"Adding comment...",
|
|
1560
|
+
() => liExec(`v2/socialActions/${encoded}/comments`, "POST", {
|
|
1561
|
+
actor: `urn:li:person:${personId}`,
|
|
1562
|
+
message: { text }
|
|
1563
|
+
}, agentId)
|
|
1564
|
+
);
|
|
1565
|
+
if (isJsonMode()) return printJson(data);
|
|
1566
|
+
console.log(chalk7.green("\u2713 Comment added"));
|
|
1567
|
+
if (data?.id) console.log(chalk7.dim(` Comment ID: ${data.id}`));
|
|
1466
1568
|
});
|
|
1467
1569
|
}
|
|
1468
1570
|
|
|
@@ -1588,7 +1690,8 @@ Examples:
|
|
|
1588
1690
|
The number is permanently returned to the carrier pool. This action cannot be undone.
|
|
1589
1691
|
`).action(async (id, opts) => {
|
|
1590
1692
|
requireToken();
|
|
1591
|
-
|
|
1693
|
+
const skipConfirm = process.argv.includes("--yes") || process.argv.includes("-y") || isJsonMode();
|
|
1694
|
+
if (!skipConfirm) {
|
|
1592
1695
|
const { confirm } = await import("@clack/prompts");
|
|
1593
1696
|
const yes = await confirm({ message: `Release ${id}? This cannot be undone.` });
|
|
1594
1697
|
if (!yes) {
|
|
@@ -1996,6 +2099,41 @@ Examples:
|
|
|
1996
2099
|
console.log(chalk11.bold("Machine ") + (a.machine_size ?? "-"));
|
|
1997
2100
|
if (a.fly_app_name) console.log(chalk11.bold("Fly App ") + a.fly_app_name);
|
|
1998
2101
|
});
|
|
2102
|
+
agent.command("url [agent_id]").description("Print the blink.new page URL for this agent (for sharing / setup links)").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID or BLINK_ACTIVE_AGENT)").addHelpText("after", `
|
|
2103
|
+
Returns the direct URL to this agent's page on blink.new.
|
|
2104
|
+
On Claw machines, uses BLINK_WORKSPACE_SLUG + BLINK_AGENT_ID env vars (no API call).
|
|
2105
|
+
Otherwise fetches workspace slug from the API.
|
|
2106
|
+
|
|
2107
|
+
Examples:
|
|
2108
|
+
$ blink agent url
|
|
2109
|
+
https://blink.new/kai/claw/clw_xxx
|
|
2110
|
+
|
|
2111
|
+
$ blink agent url --json
|
|
2112
|
+
{"url":"https://blink.new/kai/claw/clw_xxx","agent_id":"clw_xxx","workspace_slug":"kai"}
|
|
2113
|
+
`).action(async (agentIdArg, opts) => {
|
|
2114
|
+
requireToken();
|
|
2115
|
+
const agentId = requireAgentId(opts.agent ?? agentIdArg);
|
|
2116
|
+
const envSlug = process.env.BLINK_WORKSPACE_SLUG;
|
|
2117
|
+
if (envSlug) {
|
|
2118
|
+
const url2 = `https://blink.new/${envSlug}/claw/${agentId}`;
|
|
2119
|
+
if (isJsonMode()) return printJson({ url: url2, agent_id: agentId, workspace_slug: envSlug });
|
|
2120
|
+
console.log(url2);
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
const result = await withSpinner(
|
|
2124
|
+
"Fetching agent details...",
|
|
2125
|
+
() => appRequest(`/api/claw/agents/${agentId}`)
|
|
2126
|
+
);
|
|
2127
|
+
const a = result?.agent ?? result;
|
|
2128
|
+
const workspaceSlug = a?.workspace_slug ?? a?.workspace_id;
|
|
2129
|
+
if (!workspaceSlug) {
|
|
2130
|
+
console.error(chalk11.red("Could not determine workspace slug"));
|
|
2131
|
+
process.exit(1);
|
|
2132
|
+
}
|
|
2133
|
+
const url = `https://blink.new/${workspaceSlug}/claw/${agentId}`;
|
|
2134
|
+
if (isJsonMode()) return printJson({ url, agent_id: agentId, workspace_slug: workspaceSlug });
|
|
2135
|
+
console.log(url);
|
|
2136
|
+
});
|
|
1999
2137
|
}
|
|
2000
2138
|
|
|
2001
2139
|
// src/commands/secrets.ts
|
package/package.json
CHANGED
package/src/commands/agent.ts
CHANGED
|
@@ -86,4 +86,48 @@ Examples:
|
|
|
86
86
|
console.log(chalk.bold('Machine ') + (a.machine_size ?? '-'))
|
|
87
87
|
if (a.fly_app_name) console.log(chalk.bold('Fly App ') + a.fly_app_name)
|
|
88
88
|
})
|
|
89
|
+
|
|
90
|
+
// blink agent url
|
|
91
|
+
agent.command('url [agent_id]')
|
|
92
|
+
.description('Print the blink.new page URL for this agent (for sharing / setup links)')
|
|
93
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID or BLINK_ACTIVE_AGENT)')
|
|
94
|
+
.addHelpText('after', `
|
|
95
|
+
Returns the direct URL to this agent's page on blink.new.
|
|
96
|
+
On Claw machines, uses BLINK_WORKSPACE_SLUG + BLINK_AGENT_ID env vars (no API call).
|
|
97
|
+
Otherwise fetches workspace slug from the API.
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
$ blink agent url
|
|
101
|
+
https://blink.new/kai/claw/clw_xxx
|
|
102
|
+
|
|
103
|
+
$ blink agent url --json
|
|
104
|
+
{"url":"https://blink.new/kai/claw/clw_xxx","agent_id":"clw_xxx","workspace_slug":"kai"}
|
|
105
|
+
`)
|
|
106
|
+
.action(async (agentIdArg: string | undefined, opts) => {
|
|
107
|
+
requireToken()
|
|
108
|
+
const agentId = requireAgentId(opts.agent ?? agentIdArg)
|
|
109
|
+
|
|
110
|
+
// Fast path: env vars already set on Claw machines (no API call needed)
|
|
111
|
+
const envSlug = process.env.BLINK_WORKSPACE_SLUG
|
|
112
|
+
if (envSlug) {
|
|
113
|
+
const url = `https://blink.new/${envSlug}/claw/${agentId}`
|
|
114
|
+
if (isJsonMode()) return printJson({ url, agent_id: agentId, workspace_slug: envSlug })
|
|
115
|
+
console.log(url)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Fallback: fetch workspace slug from API
|
|
120
|
+
const result = await withSpinner('Fetching agent details...', () =>
|
|
121
|
+
appRequest(`/api/claw/agents/${agentId}`)
|
|
122
|
+
)
|
|
123
|
+
const a = result?.agent ?? result
|
|
124
|
+
const workspaceSlug = a?.workspace_slug ?? a?.workspace_id
|
|
125
|
+
if (!workspaceSlug) {
|
|
126
|
+
console.error(chalk.red('Could not determine workspace slug'))
|
|
127
|
+
process.exit(1)
|
|
128
|
+
}
|
|
129
|
+
const url = `https://blink.new/${workspaceSlug}/claw/${agentId}`
|
|
130
|
+
if (isJsonMode()) return printJson({ url, agent_id: agentId, workspace_slug: workspaceSlug })
|
|
131
|
+
console.log(url)
|
|
132
|
+
})
|
|
89
133
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
import { resourcesRequest } from '../lib/api-resources.js'
|
|
3
3
|
import { requireToken } from '../lib/auth.js'
|
|
4
|
+
import { requireAgentId } from '../lib/agent.js'
|
|
4
5
|
import { printJson, isJsonMode, withSpinner, createTable } from '../lib/output.js'
|
|
5
6
|
import chalk from 'chalk'
|
|
6
7
|
|
|
@@ -124,8 +125,12 @@ Use --account <id> if you have multiple linked accounts for the same provider.
|
|
|
124
125
|
if (d.metadata?.email) console.log(chalk.dim(` Email: ${d.metadata.email}`))
|
|
125
126
|
if (d.metadata?.name) console.log(chalk.dim(` Name: ${d.metadata.name}`))
|
|
126
127
|
} else {
|
|
128
|
+
const agentUrl = process.env.BLINK_WORKSPACE_SLUG && process.env.BLINK_AGENT_ID
|
|
129
|
+
? `https://blink.new/${process.env.BLINK_WORKSPACE_SLUG}/claw/${process.env.BLINK_AGENT_ID}`
|
|
130
|
+
: 'https://blink.new/settings?tab=connectors'
|
|
127
131
|
console.log(chalk.red('✗ Not connected'))
|
|
128
|
-
console.log(chalk.dim(`
|
|
132
|
+
console.log(chalk.dim(` Fix: blink connector link ${provider}`))
|
|
133
|
+
console.log(chalk.dim(` Or visit: ${agentUrl}`))
|
|
129
134
|
}
|
|
130
135
|
} else {
|
|
131
136
|
// List all connected providers
|
|
@@ -149,6 +154,47 @@ Use --account <id> if you have multiple linked accounts for the same provider.
|
|
|
149
154
|
}
|
|
150
155
|
})
|
|
151
156
|
|
|
157
|
+
// blink connector link <provider>
|
|
158
|
+
connector.command('link <provider>')
|
|
159
|
+
.description('Auto-link a workspace connector to the current agent')
|
|
160
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
161
|
+
.addHelpText('after', `
|
|
162
|
+
Links the workspace's connected account for <provider> to this agent automatically.
|
|
163
|
+
The workspace must already have the account connected at blink.new/settings?tab=connectors.
|
|
164
|
+
|
|
165
|
+
This is the self-service fix when an agent gets "not linked" errors — run this once
|
|
166
|
+
and the connector will be available for all subsequent commands.
|
|
167
|
+
|
|
168
|
+
Examples:
|
|
169
|
+
$ blink connector link linkedin Link LinkedIn to this agent
|
|
170
|
+
$ blink connector link notion Link Notion to this agent
|
|
171
|
+
$ blink connector link slack Link Slack to this agent
|
|
172
|
+
$ blink connector link linkedin --json Machine-readable output
|
|
173
|
+
`)
|
|
174
|
+
.action(async (provider: string, opts) => {
|
|
175
|
+
requireToken()
|
|
176
|
+
const agentId = requireAgentId(opts.agent)
|
|
177
|
+
const result = await withSpinner(`Linking ${provider} to agent ${agentId}...`, () =>
|
|
178
|
+
resourcesRequest(`/v1/connectors/${provider}/link`, {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers: { 'x-blink-agent-id': agentId },
|
|
181
|
+
body: {},
|
|
182
|
+
})
|
|
183
|
+
)
|
|
184
|
+
if (!result?.success) {
|
|
185
|
+
const agentUrl = process.env.BLINK_WORKSPACE_SLUG && process.env.BLINK_AGENT_ID
|
|
186
|
+
? `https://blink.new/${process.env.BLINK_WORKSPACE_SLUG}/claw/${process.env.BLINK_AGENT_ID}`
|
|
187
|
+
: 'https://blink.new/settings?tab=connectors'
|
|
188
|
+
console.error(chalk.red(`✗ ${result?.error ?? `No ${provider} account in workspace`}`))
|
|
189
|
+
console.error(chalk.dim(` Connect one first: ${agentUrl}`))
|
|
190
|
+
process.exit(1)
|
|
191
|
+
}
|
|
192
|
+
if (isJsonMode()) return printJson(result.data)
|
|
193
|
+
const label = result.data?.account_label ?? provider
|
|
194
|
+
console.log(chalk.green(`✓ ${provider} linked`) + chalk.dim(` (${label})`))
|
|
195
|
+
console.log(chalk.dim(` Agent ${agentId} can now use blink connector exec ${provider}`))
|
|
196
|
+
})
|
|
197
|
+
|
|
152
198
|
// blink connector exec <provider> <endpoint> [method-or-params] [params]
|
|
153
199
|
// Supports both patterns:
|
|
154
200
|
// blink connector exec github /user/repos GET
|
package/src/commands/linkedin.ts
CHANGED
|
@@ -5,7 +5,19 @@ import { printJson, isJsonMode, withSpinner } from '../lib/output.js'
|
|
|
5
5
|
import { resourcesRequest } from '../lib/api-resources.js'
|
|
6
6
|
import chalk from 'chalk'
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// Build agent page URL from env vars (set at machine creation via BLINK_WORKSPACE_SLUG).
|
|
9
|
+
// Falls back to generic URL if env vars aren't set (e.g. local dev).
|
|
10
|
+
function getAgentPageUrl(): string {
|
|
11
|
+
const slug = process.env.BLINK_WORKSPACE_SLUG
|
|
12
|
+
const agentId = process.env.BLINK_AGENT_ID
|
|
13
|
+
if (slug && agentId) return `https://blink.new/${slug}/claw/${agentId}`
|
|
14
|
+
return 'https://blink.new/claw'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function notLinkedError(): string {
|
|
18
|
+
const url = getAgentPageUrl()
|
|
19
|
+
return `LinkedIn not linked to this agent.\n Fix: blink connector link linkedin\n Or connect manually: ${url}`
|
|
20
|
+
}
|
|
9
21
|
|
|
10
22
|
async function liExec(
|
|
11
23
|
method: string,
|
|
@@ -17,7 +29,7 @@ async function liExec(
|
|
|
17
29
|
body: { method, http_method: httpMethod, params },
|
|
18
30
|
headers: { 'x-blink-agent-id': agentId },
|
|
19
31
|
})
|
|
20
|
-
if (!result?.success) throw new Error(result?.error ??
|
|
32
|
+
if (!result?.success) throw new Error(result?.error ?? notLinkedError())
|
|
21
33
|
return result.data
|
|
22
34
|
}
|
|
23
35
|
|
|
@@ -30,25 +42,35 @@ async function getPersonId(agentId: string): Promise<string> {
|
|
|
30
42
|
|
|
31
43
|
export function registerLinkedInCommands(program: Command) {
|
|
32
44
|
const li = program.command('linkedin')
|
|
33
|
-
.description('LinkedIn connector — publish posts and manage your profile')
|
|
45
|
+
.description('LinkedIn connector — publish posts, comment, react, and manage your profile')
|
|
34
46
|
.addHelpText('after', `
|
|
35
47
|
LinkedIn must be linked to your agent via the Integrations tab at blink.new/claw.
|
|
36
48
|
Agent ID defaults to BLINK_AGENT_ID (automatically set on Claw Fly machines).
|
|
37
49
|
|
|
38
50
|
What works today (w_member_social scope):
|
|
39
51
|
✅ blink linkedin me Show your LinkedIn profile
|
|
40
|
-
✅ blink linkedin post "
|
|
52
|
+
✅ blink linkedin post "text" Publish a text post
|
|
41
53
|
✅ blink linkedin delete <postUrn> Delete one of your posts
|
|
54
|
+
✅ blink linkedin like <postUrn> Like a post
|
|
55
|
+
✅ blink linkedin unlike <postUrn> Unlike a post
|
|
56
|
+
✅ blink linkedin comment <postUrn> "text" Add a comment
|
|
57
|
+
|
|
58
|
+
For feed reading, search, and profiles — use scripts/lk.py (requires cookies):
|
|
59
|
+
python3 scripts/lk.py feed -n 10 Browse your LinkedIn feed
|
|
60
|
+
python3 scripts/lk.py search "query" Search people
|
|
61
|
+
python3 scripts/lk.py profile <id> View a profile
|
|
62
|
+
python3 scripts/lk.py messages Check messages
|
|
63
|
+
See SKILL.md for cookie setup instructions.
|
|
42
64
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
✗ Adding comments (needs Community Management API)
|
|
65
|
+
Post URNs: use the URN returned by "blink linkedin post --json" or extract from
|
|
66
|
+
a LinkedIn post URL: https://linkedin.com/feed/update/urn:li:activity:123...
|
|
46
67
|
|
|
47
68
|
Examples:
|
|
48
69
|
$ blink linkedin me
|
|
49
|
-
$ blink linkedin post "Our product just launched!"
|
|
50
|
-
$ blink linkedin
|
|
51
|
-
$ blink linkedin
|
|
70
|
+
$ blink linkedin post "Our product just launched! 🚀"
|
|
71
|
+
$ blink linkedin like "urn:li:share:1234567890"
|
|
72
|
+
$ blink linkedin comment "urn:li:share:1234567890" "Great post!"
|
|
73
|
+
$ blink linkedin delete "urn:li:share:1234567890"
|
|
52
74
|
`)
|
|
53
75
|
|
|
54
76
|
// blink linkedin me
|
|
@@ -59,7 +81,7 @@ Examples:
|
|
|
59
81
|
Examples:
|
|
60
82
|
$ blink linkedin me
|
|
61
83
|
$ blink linkedin me --json
|
|
62
|
-
$ blink linkedin me --
|
|
84
|
+
$ PERSON_ID=$(blink linkedin me --json | jq -r .sub)
|
|
63
85
|
`)
|
|
64
86
|
.action(async (opts) => {
|
|
65
87
|
requireToken()
|
|
@@ -82,10 +104,12 @@ Examples:
|
|
|
82
104
|
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
83
105
|
.option('--visibility <vis>', 'PUBLIC | CONNECTIONS (default: PUBLIC)', 'PUBLIC')
|
|
84
106
|
.addHelpText('after', `
|
|
107
|
+
Returns the post URN which you can pass to like/comment/delete.
|
|
108
|
+
|
|
85
109
|
Examples:
|
|
86
110
|
$ blink linkedin post "Excited to share our latest update!"
|
|
87
|
-
$ blink linkedin post "Internal
|
|
88
|
-
$ blink linkedin post "Hello LinkedIn" --json
|
|
111
|
+
$ blink linkedin post "Internal update" --visibility CONNECTIONS
|
|
112
|
+
$ POST_URN=$(blink linkedin post "Hello LinkedIn" --json | jq -r .id)
|
|
89
113
|
`)
|
|
90
114
|
.action(async (text: string, opts) => {
|
|
91
115
|
requireToken()
|
|
@@ -117,55 +141,103 @@ Examples:
|
|
|
117
141
|
if (data?.id) console.log(chalk.dim(` URN: ${data.id}`))
|
|
118
142
|
})
|
|
119
143
|
|
|
120
|
-
// blink linkedin
|
|
121
|
-
li.command('
|
|
122
|
-
.description('
|
|
123
|
-
.option('--type <type>', 'Media type: image | video (default: image)', 'image')
|
|
144
|
+
// blink linkedin delete <postUrn>
|
|
145
|
+
li.command('delete <postUrn>')
|
|
146
|
+
.description('Delete one of your LinkedIn posts')
|
|
124
147
|
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
125
148
|
.addHelpText('after', `
|
|
126
|
-
Returns an asset URN to use when composing a post with media via blink connector exec.
|
|
127
|
-
|
|
128
149
|
Examples:
|
|
129
|
-
$ blink linkedin
|
|
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'])")
|
|
150
|
+
$ blink linkedin delete "urn:li:share:1234567890"
|
|
132
151
|
`)
|
|
133
|
-
.action(async (
|
|
152
|
+
.action(async (postUrn: string, opts) => {
|
|
134
153
|
requireToken()
|
|
135
154
|
const agentId = requireAgentId(opts.agent)
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
headers: { 'x-blink-agent-id': agentId },
|
|
140
|
-
})
|
|
155
|
+
const encoded = encodeURIComponent(postUrn)
|
|
156
|
+
await withSpinner('Deleting post...', () =>
|
|
157
|
+
liExec(`ugcPosts/${encoded}`, 'DELETE', {}, agentId)
|
|
141
158
|
)
|
|
142
|
-
if (isJsonMode()) return printJson(
|
|
143
|
-
|
|
144
|
-
if (assetUrn) {
|
|
145
|
-
console.log(chalk.green('✓ Upload complete'))
|
|
146
|
-
console.log(chalk.dim(` Asset URN: ${assetUrn}`))
|
|
147
|
-
}
|
|
159
|
+
if (isJsonMode()) return printJson({ deleted: true, urn: postUrn })
|
|
160
|
+
console.log(chalk.green('✓ Post deleted'))
|
|
148
161
|
})
|
|
149
162
|
|
|
150
|
-
// blink linkedin
|
|
151
|
-
li.command('
|
|
152
|
-
.description('
|
|
163
|
+
// blink linkedin like <postUrn>
|
|
164
|
+
li.command('like <postUrn>')
|
|
165
|
+
.description('Like a LinkedIn post')
|
|
153
166
|
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
154
167
|
.addHelpText('after', `
|
|
155
|
-
<postUrn>
|
|
156
|
-
|
|
168
|
+
<postUrn> accepts urn:li:share:, urn:li:ugcPost:, or urn:li:activity: URNs.
|
|
169
|
+
LinkedIn feed URLs contain the activity URN: linkedin.com/feed/update/urn:li:activity:123
|
|
157
170
|
|
|
158
171
|
Examples:
|
|
159
|
-
$ blink linkedin
|
|
172
|
+
$ blink linkedin like "urn:li:share:1234567890"
|
|
173
|
+
$ blink linkedin like "urn:li:activity:1234567890"
|
|
160
174
|
`)
|
|
161
175
|
.action(async (postUrn: string, opts) => {
|
|
162
176
|
requireToken()
|
|
163
177
|
const agentId = requireAgentId(opts.agent)
|
|
178
|
+
const personId = await withSpinner('Resolving LinkedIn identity...', () =>
|
|
179
|
+
getPersonId(agentId)
|
|
180
|
+
)
|
|
164
181
|
const encoded = encodeURIComponent(postUrn)
|
|
165
|
-
await withSpinner('
|
|
166
|
-
liExec(`
|
|
182
|
+
const data = await withSpinner('Liking post...', () =>
|
|
183
|
+
liExec(`v2/socialActions/${encoded}/likes`, 'POST', {
|
|
184
|
+
actor: `urn:li:person:${personId}`,
|
|
185
|
+
}, agentId)
|
|
167
186
|
)
|
|
168
|
-
if (isJsonMode()) return printJson(
|
|
169
|
-
console.log(chalk.green('✓ Post
|
|
187
|
+
if (isJsonMode()) return printJson(data)
|
|
188
|
+
console.log(chalk.green('✓ Post liked'))
|
|
189
|
+
if (data?.['$URN']) console.log(chalk.dim(` Like URN: ${data['$URN']}`))
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
// blink linkedin unlike <postUrn>
|
|
193
|
+
li.command('unlike <postUrn>')
|
|
194
|
+
.description('Unlike a LinkedIn post')
|
|
195
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
196
|
+
.addHelpText('after', `
|
|
197
|
+
Examples:
|
|
198
|
+
$ blink linkedin unlike "urn:li:share:1234567890"
|
|
199
|
+
`)
|
|
200
|
+
.action(async (postUrn: string, opts) => {
|
|
201
|
+
requireToken()
|
|
202
|
+
const agentId = requireAgentId(opts.agent)
|
|
203
|
+
const personId = await withSpinner('Resolving LinkedIn identity...', () =>
|
|
204
|
+
getPersonId(agentId)
|
|
205
|
+
)
|
|
206
|
+
const encodedPost = encodeURIComponent(postUrn)
|
|
207
|
+
const encodedPerson = encodeURIComponent(`urn:li:person:${personId}`)
|
|
208
|
+
await withSpinner('Unliking post...', () =>
|
|
209
|
+
liExec(`v2/socialActions/${encodedPost}/likes/${encodedPerson}`, 'DELETE', {}, agentId)
|
|
210
|
+
)
|
|
211
|
+
if (isJsonMode()) return printJson({ unliked: true })
|
|
212
|
+
console.log(chalk.green('✓ Post unliked'))
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
// blink linkedin comment <postUrn> "text"
|
|
216
|
+
li.command('comment <postUrn> <text>')
|
|
217
|
+
.description('Add a comment to a LinkedIn post')
|
|
218
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
219
|
+
.addHelpText('after', `
|
|
220
|
+
<postUrn> accepts urn:li:share:, urn:li:ugcPost:, or urn:li:activity: URNs.
|
|
221
|
+
|
|
222
|
+
Examples:
|
|
223
|
+
$ blink linkedin comment "urn:li:share:1234567890" "Great post!"
|
|
224
|
+
$ blink linkedin comment "urn:li:activity:1234567890" "Thanks for sharing"
|
|
225
|
+
`)
|
|
226
|
+
.action(async (postUrn: string, text: string, opts) => {
|
|
227
|
+
requireToken()
|
|
228
|
+
const agentId = requireAgentId(opts.agent)
|
|
229
|
+
const personId = await withSpinner('Resolving LinkedIn identity...', () =>
|
|
230
|
+
getPersonId(agentId)
|
|
231
|
+
)
|
|
232
|
+
const encoded = encodeURIComponent(postUrn)
|
|
233
|
+
const data = await withSpinner('Adding comment...', () =>
|
|
234
|
+
liExec(`v2/socialActions/${encoded}/comments`, 'POST', {
|
|
235
|
+
actor: `urn:li:person:${personId}`,
|
|
236
|
+
message: { text },
|
|
237
|
+
}, agentId)
|
|
238
|
+
)
|
|
239
|
+
if (isJsonMode()) return printJson(data)
|
|
240
|
+
console.log(chalk.green('✓ Comment added'))
|
|
241
|
+
if (data?.id) console.log(chalk.dim(` Comment ID: ${data.id}`))
|
|
170
242
|
})
|
|
171
243
|
}
|
package/src/commands/phone.ts
CHANGED
|
@@ -163,7 +163,8 @@ The number is permanently returned to the carrier pool. This action cannot be un
|
|
|
163
163
|
`)
|
|
164
164
|
.action(async (id: string, opts) => {
|
|
165
165
|
requireToken()
|
|
166
|
-
|
|
166
|
+
const skipConfirm = process.argv.includes('--yes') || process.argv.includes('-y') || isJsonMode()
|
|
167
|
+
if (!skipConfirm) {
|
|
167
168
|
const { confirm } = await import('@clack/prompts')
|
|
168
169
|
const yes = await confirm({ message: `Release ${id}? This cannot be undone.` })
|
|
169
170
|
if (!yes) { console.log('Cancelled.'); return }
|