@blinkdotnew/cli 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1348,30 +1348,40 @@ async function getPersonId(agentId) {
1348
1348
  return id;
1349
1349
  }
1350
1350
  function registerLinkedInCommands(program2) {
1351
- const li = program2.command("linkedin").description("LinkedIn connector \u2014 publish posts and manage your profile").addHelpText("after", `
1351
+ const li = program2.command("linkedin").description("LinkedIn connector \u2014 publish posts, comment, react, and manage your profile").addHelpText("after", `
1352
1352
  LinkedIn must be linked to your agent via the Integrations tab at blink.new/claw.
1353
1353
  Agent ID defaults to BLINK_AGENT_ID (automatically set on Claw Fly machines).
1354
1354
 
1355
1355
  What works today (w_member_social scope):
1356
1356
  \u2705 blink linkedin me Show your LinkedIn profile
1357
- \u2705 blink linkedin post "Excited to announce..." Publish a text post
1357
+ \u2705 blink linkedin post "text" Publish a text post
1358
1358
  \u2705 blink linkedin delete <postUrn> Delete one of your posts
1359
+ \u2705 blink linkedin like <postUrn> Like a post
1360
+ \u2705 blink linkedin unlike <postUrn> Unlike a post
1361
+ \u2705 blink linkedin comment <postUrn> "text" Add a comment
1359
1362
 
1360
- Not yet available (requires LinkedIn Partner API approval):
1361
- \u2717 Reading posts, comments, likes (needs r_member_social \u2014 restricted scope)
1362
- \u2717 Adding comments (needs Community Management API)
1363
+ For feed reading, search, and profiles \u2014 use scripts/lk.py (requires cookies):
1364
+ python3 scripts/lk.py feed -n 10 Browse your LinkedIn feed
1365
+ python3 scripts/lk.py search "query" Search people
1366
+ python3 scripts/lk.py profile <id> View a profile
1367
+ python3 scripts/lk.py messages Check messages
1368
+ See SKILL.md for cookie setup instructions.
1369
+
1370
+ Post URNs: use the URN returned by "blink linkedin post --json" or extract from
1371
+ a LinkedIn post URL: https://linkedin.com/feed/update/urn:li:activity:123...
1363
1372
 
1364
1373
  Examples:
1365
1374
  $ blink linkedin me
1366
- $ blink linkedin post "Our product just launched!"
1367
- $ blink linkedin post "Team update" --visibility CONNECTIONS
1368
- $ blink linkedin delete "urn:li:ugcPost:1234567890"
1375
+ $ blink linkedin post "Our product just launched! \u{1F680}"
1376
+ $ blink linkedin like "urn:li:share:1234567890"
1377
+ $ blink linkedin comment "urn:li:share:1234567890" "Great post!"
1378
+ $ blink linkedin delete "urn:li:share:1234567890"
1369
1379
  `);
1370
1380
  li.command("me").description("Show your LinkedIn profile (name, ID, email)").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
1371
1381
  Examples:
1372
1382
  $ blink linkedin me
1373
1383
  $ blink linkedin me --json
1374
- $ blink linkedin me --agent clw_xxx
1384
+ $ PERSON_ID=$(blink linkedin me --json | jq -r .sub)
1375
1385
  `).action(async (opts) => {
1376
1386
  requireToken();
1377
1387
  const agentId = requireAgentId(opts.agent);
@@ -1388,10 +1398,12 @@ Examples:
1388
1398
  if (data?.email) console.log(` ${chalk7.dim("Email:")} ${data.email}`);
1389
1399
  });
1390
1400
  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", `
1401
+ Returns the post URN which you can pass to like/comment/delete.
1402
+
1391
1403
  Examples:
1392
1404
  $ blink linkedin post "Excited to share our latest update!"
1393
- $ blink linkedin post "Internal team update" --visibility CONNECTIONS
1394
- $ blink linkedin post "Hello LinkedIn" --json
1405
+ $ blink linkedin post "Internal update" --visibility CONNECTIONS
1406
+ $ POST_URN=$(blink linkedin post "Hello LinkedIn" --json | jq -r .id)
1395
1407
  `).action(async (text, opts) => {
1396
1408
  requireToken();
1397
1409
  const agentId = requireAgentId(opts.agent);
@@ -1423,46 +1435,88 @@ Examples:
1423
1435
  console.log(chalk7.green("\u2713 Post published"));
1424
1436
  if (data?.id) console.log(chalk7.dim(` URN: ${data.id}`));
1425
1437
  });
1426
- 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", `
1427
- Returns an asset URN to use when composing a post with media via blink connector exec.
1428
-
1438
+ li.command("delete <postUrn>").description("Delete one of your LinkedIn posts").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
1429
1439
  Examples:
1430
- $ blink linkedin upload-media https://example.com/photo.jpg
1431
- $ blink linkedin upload-media https://example.com/demo.mp4 --type video
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) => {
1440
+ $ blink linkedin delete "urn:li:share:1234567890"
1441
+ `).action(async (postUrn, opts) => {
1434
1442
  requireToken();
1435
1443
  const agentId = requireAgentId(opts.agent);
1436
- const result = await withSpinner(
1437
- `Uploading ${opts.type}...`,
1438
- () => resourcesRequest("/v1/connectors/linkedin/upload-media", {
1439
- body: { media_url: mediaUrl, media_type: opts.type },
1440
- headers: { "x-blink-agent-id": agentId }
1441
- })
1444
+ const encoded = encodeURIComponent(postUrn);
1445
+ await withSpinner(
1446
+ "Deleting post...",
1447
+ () => liExec(`ugcPosts/${encoded}`, "DELETE", {}, agentId)
1442
1448
  );
1443
- if (isJsonMode()) return printJson(result?.data ?? result);
1444
- const assetUrn = result?.data?.asset_urn;
1445
- if (assetUrn) {
1446
- console.log(chalk7.green("\u2713 Upload complete"));
1447
- console.log(chalk7.dim(` Asset URN: ${assetUrn}`));
1448
- }
1449
+ if (isJsonMode()) return printJson({ deleted: true, urn: postUrn });
1450
+ console.log(chalk7.green("\u2713 Post deleted"));
1449
1451
  });
1450
- li.command("delete <postUrn>").description("Delete one of your LinkedIn posts").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
1451
- <postUrn> is the LinkedIn post URN returned when the post was created.
1452
- e.g. urn:li:ugcPost:1234567890
1452
+ li.command("like <postUrn>").description("Like a LinkedIn post").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
1453
+ <postUrn> accepts urn:li:share:, urn:li:ugcPost:, or urn:li:activity: URNs.
1454
+ LinkedIn feed URLs contain the activity URN: linkedin.com/feed/update/urn:li:activity:123
1453
1455
 
1454
1456
  Examples:
1455
- $ blink linkedin delete "urn:li:ugcPost:1234567890"
1457
+ $ blink linkedin like "urn:li:share:1234567890"
1458
+ $ blink linkedin like "urn:li:activity:1234567890"
1456
1459
  `).action(async (postUrn, opts) => {
1457
1460
  requireToken();
1458
1461
  const agentId = requireAgentId(opts.agent);
1462
+ const personId = await withSpinner(
1463
+ "Resolving LinkedIn identity...",
1464
+ () => getPersonId(agentId)
1465
+ );
1459
1466
  const encoded = encodeURIComponent(postUrn);
1467
+ const data = await withSpinner(
1468
+ "Liking post...",
1469
+ () => liExec(`v2/socialActions/${encoded}/likes`, "POST", {
1470
+ actor: `urn:li:person:${personId}`
1471
+ }, agentId)
1472
+ );
1473
+ if (isJsonMode()) return printJson(data);
1474
+ console.log(chalk7.green("\u2713 Post liked"));
1475
+ if (data?.["$URN"]) console.log(chalk7.dim(` Like URN: ${data["$URN"]}`));
1476
+ });
1477
+ li.command("unlike <postUrn>").description("Unlike a LinkedIn post").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
1478
+ Examples:
1479
+ $ blink linkedin unlike "urn:li:share:1234567890"
1480
+ `).action(async (postUrn, opts) => {
1481
+ requireToken();
1482
+ const agentId = requireAgentId(opts.agent);
1483
+ const personId = await withSpinner(
1484
+ "Resolving LinkedIn identity...",
1485
+ () => getPersonId(agentId)
1486
+ );
1487
+ const encodedPost = encodeURIComponent(postUrn);
1488
+ const encodedPerson = encodeURIComponent(`urn:li:person:${personId}`);
1460
1489
  await withSpinner(
1461
- "Deleting post...",
1462
- () => liExec(`ugcPosts/${encoded}`, "DELETE", {}, agentId)
1490
+ "Unliking post...",
1491
+ () => liExec(`v2/socialActions/${encodedPost}/likes/${encodedPerson}`, "DELETE", {}, agentId)
1463
1492
  );
1464
- if (isJsonMode()) return printJson({ deleted: true, urn: postUrn });
1465
- console.log(chalk7.green("\u2713 Post deleted"));
1493
+ if (isJsonMode()) return printJson({ unliked: true });
1494
+ console.log(chalk7.green("\u2713 Post unliked"));
1495
+ });
1496
+ 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", `
1497
+ <postUrn> accepts urn:li:share:, urn:li:ugcPost:, or urn:li:activity: URNs.
1498
+
1499
+ Examples:
1500
+ $ blink linkedin comment "urn:li:share:1234567890" "Great post!"
1501
+ $ blink linkedin comment "urn:li:activity:1234567890" "Thanks for sharing"
1502
+ `).action(async (postUrn, text, opts) => {
1503
+ requireToken();
1504
+ const agentId = requireAgentId(opts.agent);
1505
+ const personId = await withSpinner(
1506
+ "Resolving LinkedIn identity...",
1507
+ () => getPersonId(agentId)
1508
+ );
1509
+ const encoded = encodeURIComponent(postUrn);
1510
+ const data = await withSpinner(
1511
+ "Adding comment...",
1512
+ () => liExec(`v2/socialActions/${encoded}/comments`, "POST", {
1513
+ actor: `urn:li:person:${personId}`,
1514
+ message: { text }
1515
+ }, agentId)
1516
+ );
1517
+ if (isJsonMode()) return printJson(data);
1518
+ console.log(chalk7.green("\u2713 Comment added"));
1519
+ if (data?.id) console.log(chalk7.dim(` Comment ID: ${data.id}`));
1466
1520
  });
1467
1521
  }
1468
1522
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blinkdotnew/cli",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Blink platform CLI — deploy apps, manage databases, generate AI content",
5
5
  "bin": {
6
6
  "blink": "dist/cli.js"
@@ -30,25 +30,35 @@ async function getPersonId(agentId: string): Promise<string> {
30
30
 
31
31
  export function registerLinkedInCommands(program: Command) {
32
32
  const li = program.command('linkedin')
33
- .description('LinkedIn connector — publish posts and manage your profile')
33
+ .description('LinkedIn connector — publish posts, comment, react, and manage your profile')
34
34
  .addHelpText('after', `
35
35
  LinkedIn must be linked to your agent via the Integrations tab at blink.new/claw.
36
36
  Agent ID defaults to BLINK_AGENT_ID (automatically set on Claw Fly machines).
37
37
 
38
38
  What works today (w_member_social scope):
39
39
  ✅ blink linkedin me Show your LinkedIn profile
40
- ✅ blink linkedin post "Excited to announce..." Publish a text post
40
+ ✅ blink linkedin post "text" Publish a text post
41
41
  ✅ blink linkedin delete <postUrn> Delete one of your posts
42
+ ✅ blink linkedin like <postUrn> Like a post
43
+ ✅ blink linkedin unlike <postUrn> Unlike a post
44
+ ✅ blink linkedin comment <postUrn> "text" Add a comment
42
45
 
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
+ For feed reading, search, and profiles use scripts/lk.py (requires cookies):
47
+ python3 scripts/lk.py feed -n 10 Browse your LinkedIn feed
48
+ python3 scripts/lk.py search "query" Search people
49
+ python3 scripts/lk.py profile <id> View a profile
50
+ python3 scripts/lk.py messages Check messages
51
+ See SKILL.md for cookie setup instructions.
52
+
53
+ Post URNs: use the URN returned by "blink linkedin post --json" or extract from
54
+ a LinkedIn post URL: https://linkedin.com/feed/update/urn:li:activity:123...
46
55
 
47
56
  Examples:
48
57
  $ 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"
58
+ $ blink linkedin post "Our product just launched! 🚀"
59
+ $ blink linkedin like "urn:li:share:1234567890"
60
+ $ blink linkedin comment "urn:li:share:1234567890" "Great post!"
61
+ $ blink linkedin delete "urn:li:share:1234567890"
52
62
  `)
53
63
 
54
64
  // blink linkedin me
@@ -59,7 +69,7 @@ Examples:
59
69
  Examples:
60
70
  $ blink linkedin me
61
71
  $ blink linkedin me --json
62
- $ blink linkedin me --agent clw_xxx
72
+ $ PERSON_ID=$(blink linkedin me --json | jq -r .sub)
63
73
  `)
64
74
  .action(async (opts) => {
65
75
  requireToken()
@@ -82,10 +92,12 @@ Examples:
82
92
  .option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
83
93
  .option('--visibility <vis>', 'PUBLIC | CONNECTIONS (default: PUBLIC)', 'PUBLIC')
84
94
  .addHelpText('after', `
95
+ Returns the post URN which you can pass to like/comment/delete.
96
+
85
97
  Examples:
86
98
  $ blink linkedin post "Excited to share our latest update!"
87
- $ blink linkedin post "Internal team update" --visibility CONNECTIONS
88
- $ blink linkedin post "Hello LinkedIn" --json
99
+ $ blink linkedin post "Internal update" --visibility CONNECTIONS
100
+ $ POST_URN=$(blink linkedin post "Hello LinkedIn" --json | jq -r .id)
89
101
  `)
90
102
  .action(async (text: string, opts) => {
91
103
  requireToken()
@@ -117,55 +129,103 @@ Examples:
117
129
  if (data?.id) console.log(chalk.dim(` URN: ${data.id}`))
118
130
  })
119
131
 
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')
132
+ // blink linkedin delete <postUrn>
133
+ li.command('delete <postUrn>')
134
+ .description('Delete one of your LinkedIn posts')
124
135
  .option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
125
136
  .addHelpText('after', `
126
- Returns an asset URN to use when composing a post with media via blink connector exec.
127
-
128
137
  Examples:
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'])")
138
+ $ blink linkedin delete "urn:li:share:1234567890"
132
139
  `)
133
- .action(async (mediaUrl: string, opts) => {
140
+ .action(async (postUrn: string, opts) => {
134
141
  requireToken()
135
142
  const agentId = requireAgentId(opts.agent)
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
- })
143
+ const encoded = encodeURIComponent(postUrn)
144
+ await withSpinner('Deleting post...', () =>
145
+ liExec(`ugcPosts/${encoded}`, 'DELETE', {}, agentId)
141
146
  )
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}`))
147
- }
147
+ if (isJsonMode()) return printJson({ deleted: true, urn: postUrn })
148
+ console.log(chalk.green('✓ Post deleted'))
148
149
  })
149
150
 
150
- // blink linkedin delete <postUrn>
151
- li.command('delete <postUrn>')
152
- .description('Delete one of your LinkedIn posts')
151
+ // blink linkedin like <postUrn>
152
+ li.command('like <postUrn>')
153
+ .description('Like a LinkedIn post')
153
154
  .option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
154
155
  .addHelpText('after', `
155
- <postUrn> is the LinkedIn post URN returned when the post was created.
156
- e.g. urn:li:ugcPost:1234567890
156
+ <postUrn> accepts urn:li:share:, urn:li:ugcPost:, or urn:li:activity: URNs.
157
+ LinkedIn feed URLs contain the activity URN: linkedin.com/feed/update/urn:li:activity:123
157
158
 
158
159
  Examples:
159
- $ blink linkedin delete "urn:li:ugcPost:1234567890"
160
+ $ blink linkedin like "urn:li:share:1234567890"
161
+ $ blink linkedin like "urn:li:activity:1234567890"
160
162
  `)
161
163
  .action(async (postUrn: string, opts) => {
162
164
  requireToken()
163
165
  const agentId = requireAgentId(opts.agent)
166
+ const personId = await withSpinner('Resolving LinkedIn identity...', () =>
167
+ getPersonId(agentId)
168
+ )
164
169
  const encoded = encodeURIComponent(postUrn)
165
- await withSpinner('Deleting post...', () =>
166
- liExec(`ugcPosts/${encoded}`, 'DELETE', {}, agentId)
170
+ const data = await withSpinner('Liking post...', () =>
171
+ liExec(`v2/socialActions/${encoded}/likes`, 'POST', {
172
+ actor: `urn:li:person:${personId}`,
173
+ }, agentId)
167
174
  )
168
- if (isJsonMode()) return printJson({ deleted: true, urn: postUrn })
169
- console.log(chalk.green('✓ Post deleted'))
175
+ if (isJsonMode()) return printJson(data)
176
+ console.log(chalk.green('✓ Post liked'))
177
+ if (data?.['$URN']) console.log(chalk.dim(` Like URN: ${data['$URN']}`))
178
+ })
179
+
180
+ // blink linkedin unlike <postUrn>
181
+ li.command('unlike <postUrn>')
182
+ .description('Unlike a LinkedIn post')
183
+ .option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
184
+ .addHelpText('after', `
185
+ Examples:
186
+ $ blink linkedin unlike "urn:li:share:1234567890"
187
+ `)
188
+ .action(async (postUrn: string, opts) => {
189
+ requireToken()
190
+ const agentId = requireAgentId(opts.agent)
191
+ const personId = await withSpinner('Resolving LinkedIn identity...', () =>
192
+ getPersonId(agentId)
193
+ )
194
+ const encodedPost = encodeURIComponent(postUrn)
195
+ const encodedPerson = encodeURIComponent(`urn:li:person:${personId}`)
196
+ await withSpinner('Unliking post...', () =>
197
+ liExec(`v2/socialActions/${encodedPost}/likes/${encodedPerson}`, 'DELETE', {}, agentId)
198
+ )
199
+ if (isJsonMode()) return printJson({ unliked: true })
200
+ console.log(chalk.green('✓ Post unliked'))
201
+ })
202
+
203
+ // blink linkedin comment <postUrn> "text"
204
+ li.command('comment <postUrn> <text>')
205
+ .description('Add a comment to a LinkedIn post')
206
+ .option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
207
+ .addHelpText('after', `
208
+ <postUrn> accepts urn:li:share:, urn:li:ugcPost:, or urn:li:activity: URNs.
209
+
210
+ Examples:
211
+ $ blink linkedin comment "urn:li:share:1234567890" "Great post!"
212
+ $ blink linkedin comment "urn:li:activity:1234567890" "Thanks for sharing"
213
+ `)
214
+ .action(async (postUrn: string, text: string, opts) => {
215
+ requireToken()
216
+ const agentId = requireAgentId(opts.agent)
217
+ const personId = await withSpinner('Resolving LinkedIn identity...', () =>
218
+ getPersonId(agentId)
219
+ )
220
+ const encoded = encodeURIComponent(postUrn)
221
+ const data = await withSpinner('Adding comment...', () =>
222
+ liExec(`v2/socialActions/${encoded}/comments`, 'POST', {
223
+ actor: `urn:li:person:${personId}`,
224
+ message: { text },
225
+ }, agentId)
226
+ )
227
+ if (isJsonMode()) return printJson(data)
228
+ console.log(chalk.green('✓ Comment added'))
229
+ if (data?.id) console.log(chalk.dim(` Comment ID: ${data.id}`))
170
230
  })
171
231
  }