@blinkdotnew/cli 0.2.2 → 0.2.4
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 +451 -73
- package/package.json +1 -1
- package/src/cli.ts +23 -3
- package/src/commands/connector.ts +183 -34
- package/src/commands/linkedin.ts +265 -0
package/dist/cli.js
CHANGED
|
@@ -1009,35 +1009,170 @@ The email is sent from your project's configured sender address (set in blink.ne
|
|
|
1009
1009
|
}
|
|
1010
1010
|
|
|
1011
1011
|
// src/commands/connector.ts
|
|
1012
|
+
import chalk6 from "chalk";
|
|
1013
|
+
var PROVIDERS = [
|
|
1014
|
+
// Communication
|
|
1015
|
+
{ key: "slack", label: "Slack", category: "Communication" },
|
|
1016
|
+
{ key: "discord", label: "Discord", category: "Communication" },
|
|
1017
|
+
// Email & Calendar
|
|
1018
|
+
{ key: "google_gmail", label: "Gmail", category: "Email & Calendar" },
|
|
1019
|
+
{ key: "google_calendar", label: "Google Calendar", category: "Email & Calendar" },
|
|
1020
|
+
{ key: "microsoft_outlook", label: "Outlook", category: "Email & Calendar" },
|
|
1021
|
+
{ key: "microsoft_calendar", label: "Microsoft Calendar", category: "Email & Calendar" },
|
|
1022
|
+
// Files & Docs
|
|
1023
|
+
{ key: "google_drive", label: "Google Drive", category: "Files & Docs" },
|
|
1024
|
+
{ key: "google_docs", label: "Google Docs", category: "Files & Docs" },
|
|
1025
|
+
{ key: "google_sheets", label: "Google Sheets", category: "Files & Docs" },
|
|
1026
|
+
{ key: "google_slides", label: "Google Slides", category: "Files & Docs" },
|
|
1027
|
+
{ key: "microsoft_onedrive", label: "OneDrive", category: "Files & Docs" },
|
|
1028
|
+
{ key: "notion", label: "Notion", category: "Files & Docs" },
|
|
1029
|
+
// Dev Tools
|
|
1030
|
+
{ key: "github", label: "GitHub", category: "Dev Tools" },
|
|
1031
|
+
{ key: "linear", label: "Linear", category: "Dev Tools" },
|
|
1032
|
+
{ key: "jira", label: "Jira", category: "Dev Tools" },
|
|
1033
|
+
{ key: "vercel", label: "Vercel", category: "Dev Tools" },
|
|
1034
|
+
{ key: "figma", label: "Figma", category: "Dev Tools" },
|
|
1035
|
+
// CRM & Sales
|
|
1036
|
+
{ key: "hubspot", label: "HubSpot", category: "CRM & Sales" },
|
|
1037
|
+
{ key: "salesforce", label: "Salesforce", category: "CRM & Sales" },
|
|
1038
|
+
{ key: "pipedrive", label: "Pipedrive", category: "CRM & Sales" },
|
|
1039
|
+
{ key: "attio", label: "Attio", category: "CRM & Sales" },
|
|
1040
|
+
// Marketing
|
|
1041
|
+
{ key: "mailchimp", label: "Mailchimp", category: "Marketing" },
|
|
1042
|
+
{ key: "typeform", label: "Typeform", category: "Marketing" },
|
|
1043
|
+
// Data & Productivity
|
|
1044
|
+
{ key: "airtable", label: "Airtable", category: "Data & Productivity" },
|
|
1045
|
+
{ key: "asana", label: "Asana", category: "Data & Productivity" },
|
|
1046
|
+
{ key: "calendly", label: "Calendly", category: "Data & Productivity" },
|
|
1047
|
+
// Social & Creator
|
|
1048
|
+
{ key: "twitter", label: "X (Twitter)", category: "Social & Creator" },
|
|
1049
|
+
{ key: "instagram", label: "Instagram", category: "Social & Creator" },
|
|
1050
|
+
{ key: "tiktok", label: "TikTok", category: "Social & Creator" },
|
|
1051
|
+
{ key: "youtube", label: "YouTube", category: "Social & Creator" },
|
|
1052
|
+
{ key: "linkedin", label: "LinkedIn", category: "Social & Creator" },
|
|
1053
|
+
{ key: "reddit", label: "Reddit", category: "Social & Creator" },
|
|
1054
|
+
{ key: "loom", label: "Loom", category: "Social & Creator" },
|
|
1055
|
+
// Meetings
|
|
1056
|
+
{ key: "zoom", label: "Zoom", category: "Meetings" },
|
|
1057
|
+
{ key: "microsoft_teams", label: "Microsoft Teams", category: "Meetings" },
|
|
1058
|
+
// Commerce
|
|
1059
|
+
{ key: "stripe", label: "Stripe", category: "Commerce" },
|
|
1060
|
+
{ key: "shopify", label: "Shopify", category: "Commerce" },
|
|
1061
|
+
{ key: "etsy", label: "Etsy", category: "Commerce" }
|
|
1062
|
+
];
|
|
1012
1063
|
function registerConnectorCommands(program2) {
|
|
1013
|
-
const connector = program2.command("connector").description("Execute actions on connected OAuth apps (Notion, Slack,
|
|
1014
|
-
Connectors are OAuth accounts linked to your workspace
|
|
1015
|
-
Once linked, use blink connector exec to call their APIs without managing tokens yourself.
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
$ blink connector exec
|
|
1023
|
-
$ blink connector exec
|
|
1024
|
-
$ blink connector exec
|
|
1025
|
-
$ blink connector exec
|
|
1026
|
-
$ blink connector exec
|
|
1027
|
-
$ blink connector exec
|
|
1028
|
-
|
|
1029
|
-
|
|
1064
|
+
const connector = program2.command("connector").description("Execute actions on connected OAuth apps (GitHub, Notion, Slack, Reddit, Google, and 30+ more)").addHelpText("after", `
|
|
1065
|
+
Connectors are OAuth accounts linked to your workspace at blink.new \u2192 Settings \u2192 Integrations.
|
|
1066
|
+
Once linked, use \`blink connector exec\` to call their APIs without managing tokens yourself.
|
|
1067
|
+
|
|
1068
|
+
Connect accounts at: https://blink.new/settings?tab=connectors
|
|
1069
|
+
|
|
1070
|
+
Run \`blink connector providers\` to see all 38 supported providers.
|
|
1071
|
+
|
|
1072
|
+
Quick examples:
|
|
1073
|
+
$ blink connector exec github /user/repos GET
|
|
1074
|
+
$ blink connector exec notion /search POST '{"query":"meeting notes"}'
|
|
1075
|
+
$ blink connector exec slack /chat.postMessage POST '{"channel":"C123","text":"Hello"}'
|
|
1076
|
+
$ blink connector exec google_calendar /calendars/primary/events GET
|
|
1077
|
+
$ blink connector exec jira /issue POST '{"fields":{"project":{"key":"PROJ"},"summary":"Bug"}}'
|
|
1078
|
+
$ blink connector exec stripe /customers GET '{"limit":10}'
|
|
1079
|
+
$ blink connector exec shopify /orders.json GET
|
|
1080
|
+
$ blink connector exec linear '{ viewer { id name } }' POST # GraphQL
|
|
1081
|
+
|
|
1082
|
+
Use --method GET for reads, POST for writes (default: POST).
|
|
1083
|
+
Use --account <id> if you have multiple linked accounts for the same provider.
|
|
1030
1084
|
`);
|
|
1031
|
-
connector.command("
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
`
|
|
1085
|
+
connector.command("providers").description("List all supported OAuth connector providers").option("--category <cat>", "Filter by category").action((opts) => {
|
|
1086
|
+
const filtered = opts.category ? PROVIDERS.filter((p) => p.category.toLowerCase().includes(opts.category.toLowerCase())) : PROVIDERS;
|
|
1087
|
+
if (isJsonMode()) return printJson(filtered);
|
|
1088
|
+
const categories = [...new Set(filtered.map((p) => p.category))];
|
|
1089
|
+
for (const cat of categories) {
|
|
1090
|
+
console.log(chalk6.bold.cyan(`
|
|
1091
|
+
${cat}`));
|
|
1092
|
+
const catProviders = filtered.filter((p) => p.category === cat);
|
|
1093
|
+
for (const p of catProviders) {
|
|
1094
|
+
console.log(` ${chalk6.green(p.key.padEnd(22))} ${chalk6.dim(p.label)}`);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
console.log(chalk6.dim(`
|
|
1098
|
+
${filtered.length} providers total. Connect at blink.new/settings?tab=connectors`));
|
|
1099
|
+
});
|
|
1100
|
+
connector.command("status [provider]").description("Check connection status for a provider (or all providers)").option("--account <id>", "Check a specific account ID").action(async (provider, opts) => {
|
|
1101
|
+
requireToken();
|
|
1102
|
+
if (provider) {
|
|
1103
|
+
const result = await withSpinner(
|
|
1104
|
+
`Checking ${provider} status...`,
|
|
1105
|
+
() => resourcesRequest(`/v1/connectors/${provider}/status`, {
|
|
1106
|
+
method: "GET",
|
|
1107
|
+
...opts.account ? { query: { account_id: opts.account } } : {}
|
|
1108
|
+
})
|
|
1109
|
+
);
|
|
1110
|
+
if (isJsonMode()) return printJson(result);
|
|
1111
|
+
const d = result?.data ?? result;
|
|
1112
|
+
if (d?.connected) {
|
|
1113
|
+
console.log(chalk6.green("\u2713 Connected"));
|
|
1114
|
+
if (d.account_id) console.log(chalk6.dim(` Account: ${d.account_id}`));
|
|
1115
|
+
if (d.metadata?.email) console.log(chalk6.dim(` Email: ${d.metadata.email}`));
|
|
1116
|
+
if (d.metadata?.name) console.log(chalk6.dim(` Name: ${d.metadata.name}`));
|
|
1117
|
+
} else {
|
|
1118
|
+
console.log(chalk6.red("\u2717 Not connected"));
|
|
1119
|
+
console.log(chalk6.dim(` Connect at blink.new/settings?tab=connectors`));
|
|
1120
|
+
}
|
|
1121
|
+
} else {
|
|
1122
|
+
const result = await withSpinner(
|
|
1123
|
+
"Checking all connectors...",
|
|
1124
|
+
() => resourcesRequest("/v1/connectors/linked", { method: "GET" })
|
|
1125
|
+
);
|
|
1126
|
+
if (isJsonMode()) return printJson(result);
|
|
1127
|
+
const connected = result?.data ?? [];
|
|
1128
|
+
if (!connected.length) {
|
|
1129
|
+
console.log(chalk6.dim("No connectors linked. Connect at blink.new/settings?tab=connectors"));
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
const table = createTable(["Provider", "Accounts"]);
|
|
1133
|
+
for (const c of connected) {
|
|
1134
|
+
const accounts = c.accounts.map((a) => a.label || a.account_id).join(", ");
|
|
1135
|
+
table.push([chalk6.green(c.provider), accounts]);
|
|
1136
|
+
}
|
|
1137
|
+
console.log(table.toString());
|
|
1138
|
+
console.log(chalk6.dim(`
|
|
1139
|
+
${connected.length} provider(s) connected`));
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
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", `
|
|
1143
|
+
<endpoint> is the API path relative to the provider's base URL, OR a GraphQL query string for Linear.
|
|
1144
|
+
|
|
1145
|
+
Examples (REST):
|
|
1146
|
+
$ blink connector exec github /user/repos GET
|
|
1147
|
+
$ blink connector exec github /repos/owner/repo/issues POST '{"title":"Bug","body":"..."}'
|
|
1148
|
+
$ blink connector exec notion /search POST '{"query":"meeting notes"}'
|
|
1149
|
+
$ blink connector exec slack /chat.postMessage POST '{"channel":"C123","text":"Hello"}'
|
|
1150
|
+
$ blink connector exec google_drive /files GET '{"q":"mimeType=\\'application/pdf\\'"}'
|
|
1151
|
+
$ blink connector exec jira /search GET '{"jql":"assignee=currentUser()","maxResults":20}'
|
|
1152
|
+
$ blink connector exec stripe /customers GET '{"limit":5}'
|
|
1153
|
+
$ blink connector exec shopify /orders.json GET
|
|
1154
|
+
$ blink connector exec zoom /users/me/meetings GET
|
|
1155
|
+
$ blink connector exec github /user/repos GET --json | jq '.[].full_name'
|
|
1156
|
+
|
|
1157
|
+
Examples (GraphQL \u2014 Linear only):
|
|
1158
|
+
$ blink connector exec linear '{ viewer { id name email } }' POST
|
|
1159
|
+
$ blink connector exec linear 'mutation { issueCreate(input: { title: "Bug", teamId: "xxx" }) { issue { id } } }' POST
|
|
1160
|
+
|
|
1161
|
+
Multiple accounts:
|
|
1162
|
+
$ blink connector exec github /user/repos GET --account acct_xxx
|
|
1163
|
+
|
|
1164
|
+
Provider base URLs used:
|
|
1165
|
+
github https://api.github.com/
|
|
1166
|
+
stripe https://api.stripe.com/v1/
|
|
1167
|
+
notion https://api.notion.com/v1/
|
|
1168
|
+
slack https://slack.com/api/
|
|
1169
|
+
google_gmail https://gmail.googleapis.com/gmail/v1/
|
|
1170
|
+
google_drive https://www.googleapis.com/drive/v3/
|
|
1171
|
+
google_calendar https://www.googleapis.com/calendar/v3/
|
|
1172
|
+
jira https://api.atlassian.com/ex/jira/{cloudId}/rest/api/3/
|
|
1173
|
+
shopify https://{shop}.myshopify.com/admin/api/2024-10/
|
|
1174
|
+
... run \`blink connector providers\` for all 38 providers
|
|
1175
|
+
`).action(async (provider, endpoint, paramsArg, opts) => {
|
|
1041
1176
|
requireToken();
|
|
1042
1177
|
let params = {};
|
|
1043
1178
|
if (paramsArg) {
|
|
@@ -1048,10 +1183,10 @@ Examples:
|
|
|
1048
1183
|
}
|
|
1049
1184
|
}
|
|
1050
1185
|
const result = await withSpinner(
|
|
1051
|
-
|
|
1186
|
+
`${opts.method} ${provider}${endpoint}...`,
|
|
1052
1187
|
() => resourcesRequest(`/v1/connectors/${provider}/execute`, {
|
|
1053
1188
|
body: {
|
|
1054
|
-
method:
|
|
1189
|
+
method: endpoint,
|
|
1055
1190
|
http_method: opts.method,
|
|
1056
1191
|
params,
|
|
1057
1192
|
...opts.account ? { account_id: opts.account } : {}
|
|
@@ -1059,7 +1194,231 @@ Examples:
|
|
|
1059
1194
|
})
|
|
1060
1195
|
);
|
|
1061
1196
|
if (isJsonMode()) return printJson(result);
|
|
1062
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1197
|
+
console.log(JSON.stringify(result?.data ?? result, null, 2));
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// src/commands/linkedin.ts
|
|
1202
|
+
init_agent();
|
|
1203
|
+
import chalk7 from "chalk";
|
|
1204
|
+
var NOT_LINKED = "LinkedIn not linked. Link it in the Agent Integrations tab at blink.new/claw";
|
|
1205
|
+
async function liExec(method, httpMethod, params, agentId) {
|
|
1206
|
+
const result = await resourcesRequest("/v1/connectors/linkedin/execute", {
|
|
1207
|
+
body: { method, http_method: httpMethod, params },
|
|
1208
|
+
headers: { "x-blink-agent-id": agentId }
|
|
1209
|
+
});
|
|
1210
|
+
if (!result?.success) throw new Error(result?.error ?? NOT_LINKED);
|
|
1211
|
+
return result.data;
|
|
1212
|
+
}
|
|
1213
|
+
async function getPersonId(agentId) {
|
|
1214
|
+
const data = await liExec("/me", "GET", {}, agentId);
|
|
1215
|
+
const id = data?.id;
|
|
1216
|
+
if (!id) throw new Error("Could not resolve LinkedIn person ID from /me response");
|
|
1217
|
+
return id;
|
|
1218
|
+
}
|
|
1219
|
+
function registerLinkedInCommands(program2) {
|
|
1220
|
+
const li = program2.command("linkedin").description("LinkedIn connector \u2014 post content, manage comments, and view your profile").addHelpText("after", `
|
|
1221
|
+
LinkedIn must be linked to your agent via the Integrations tab at blink.new/claw.
|
|
1222
|
+
Agent ID defaults to BLINK_AGENT_ID (automatically set on Claw Fly machines).
|
|
1223
|
+
|
|
1224
|
+
Examples:
|
|
1225
|
+
$ blink linkedin me Show your LinkedIn profile
|
|
1226
|
+
$ blink linkedin posts List your 10 most recent posts
|
|
1227
|
+
$ blink linkedin post "Excited to announce..." Publish a text post
|
|
1228
|
+
$ blink linkedin comments "urn:li:ugcPost:123" Read comments on a post
|
|
1229
|
+
$ blink linkedin comment "urn:li:ugcPost:123" "Great post!" Add a comment
|
|
1230
|
+
$ blink linkedin like "urn:li:ugcPost:123" Like a post
|
|
1231
|
+
$ blink linkedin unlike "urn:li:ugcPost:123" Unlike a post
|
|
1232
|
+
`);
|
|
1233
|
+
li.command("me").description("Show your LinkedIn profile (name, ID, vanity URL)").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1234
|
+
Examples:
|
|
1235
|
+
$ blink linkedin me
|
|
1236
|
+
$ blink linkedin me --json
|
|
1237
|
+
$ blink linkedin me --agent clw_xxx
|
|
1238
|
+
`).action(async (opts) => {
|
|
1239
|
+
requireToken();
|
|
1240
|
+
const agentId = requireAgentId(opts.agent);
|
|
1241
|
+
const data = await withSpinner(
|
|
1242
|
+
"Fetching LinkedIn profile...",
|
|
1243
|
+
() => liExec("/me", "GET", {}, agentId)
|
|
1244
|
+
);
|
|
1245
|
+
if (isJsonMode()) return printJson(data);
|
|
1246
|
+
const name = [data?.localizedFirstName, data?.localizedLastName].filter(Boolean).join(" ");
|
|
1247
|
+
console.log(chalk7.bold("LinkedIn Profile"));
|
|
1248
|
+
console.log(` ${chalk7.dim("ID:")} ${data?.id ?? "\u2014"}`);
|
|
1249
|
+
if (name) console.log(` ${chalk7.dim("Name:")} ${name}`);
|
|
1250
|
+
if (data?.vanityName) console.log(` ${chalk7.dim("URL:")} https://linkedin.com/in/${data.vanityName}`);
|
|
1251
|
+
});
|
|
1252
|
+
li.command("posts").description("List your most recent LinkedIn posts (last 10)").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").option("--limit <n>", "Number of posts to fetch (default: 10)", "10").addHelpText("after", `
|
|
1253
|
+
Examples:
|
|
1254
|
+
$ blink linkedin posts
|
|
1255
|
+
$ blink linkedin posts --limit 5
|
|
1256
|
+
$ blink linkedin posts --json | jq '.[].id'
|
|
1257
|
+
`).action(async (opts) => {
|
|
1258
|
+
requireToken();
|
|
1259
|
+
const agentId = requireAgentId(opts.agent);
|
|
1260
|
+
const personId = await withSpinner(
|
|
1261
|
+
"Resolving your LinkedIn identity...",
|
|
1262
|
+
() => getPersonId(agentId)
|
|
1263
|
+
);
|
|
1264
|
+
const urn = encodeURIComponent(`urn:li:person:${personId}`);
|
|
1265
|
+
const data = await withSpinner(
|
|
1266
|
+
"Fetching posts...",
|
|
1267
|
+
() => liExec(
|
|
1268
|
+
`/ugcPosts?q=authors&authors=List(${urn})&sortBy=LAST_MODIFIED&count=${opts.limit}`,
|
|
1269
|
+
"GET",
|
|
1270
|
+
{},
|
|
1271
|
+
agentId
|
|
1272
|
+
)
|
|
1273
|
+
);
|
|
1274
|
+
const posts = data?.elements ?? (Array.isArray(data) ? data : []);
|
|
1275
|
+
if (isJsonMode()) return printJson(posts);
|
|
1276
|
+
if (!posts.length) {
|
|
1277
|
+
console.log(chalk7.dim("No posts found."));
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
for (const post of posts) {
|
|
1281
|
+
const id = post.id ?? "\u2014";
|
|
1282
|
+
const content = post.specificContent;
|
|
1283
|
+
const share = content?.["com.linkedin.ugc.ShareContent"];
|
|
1284
|
+
const commentary = share?.shareCommentary;
|
|
1285
|
+
const text = commentary?.text ?? post.text?.text ?? "(no text)";
|
|
1286
|
+
const preview = text.length > 120 ? text.slice(0, 120) + "\u2026" : text;
|
|
1287
|
+
console.log(chalk7.bold(id));
|
|
1288
|
+
console.log(` ${chalk7.dim(preview)}`);
|
|
1289
|
+
console.log();
|
|
1290
|
+
}
|
|
1291
|
+
console.log(chalk7.dim(`${posts.length} post(s)`));
|
|
1292
|
+
});
|
|
1293
|
+
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", `
|
|
1294
|
+
Examples:
|
|
1295
|
+
$ blink linkedin post "Excited to share our latest update!"
|
|
1296
|
+
$ blink linkedin post "Internal update" --visibility CONNECTIONS
|
|
1297
|
+
$ blink linkedin post "Hello LinkedIn" --json
|
|
1298
|
+
`).action(async (text, opts) => {
|
|
1299
|
+
requireToken();
|
|
1300
|
+
const agentId = requireAgentId(opts.agent);
|
|
1301
|
+
const data = await withSpinner(
|
|
1302
|
+
"Publishing post...",
|
|
1303
|
+
() => liExec("/ugcPosts", "POST", { text, visibility: opts.visibility }, agentId)
|
|
1304
|
+
);
|
|
1305
|
+
if (isJsonMode()) return printJson(data);
|
|
1306
|
+
console.log(chalk7.green("\u2713 Post published"));
|
|
1307
|
+
if (data?.id) console.log(chalk7.dim(` URN: ${data.id}`));
|
|
1308
|
+
});
|
|
1309
|
+
li.command("comments <postUrn>").description("Read comments on a LinkedIn post").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1310
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
1311
|
+
Use "blink linkedin posts" to find your post URNs.
|
|
1312
|
+
|
|
1313
|
+
Examples:
|
|
1314
|
+
$ blink linkedin comments "urn:li:ugcPost:1234567890"
|
|
1315
|
+
$ blink linkedin comments "urn:li:ugcPost:1234567890" --json
|
|
1316
|
+
`).action(async (postUrn, opts) => {
|
|
1317
|
+
requireToken();
|
|
1318
|
+
const agentId = requireAgentId(opts.agent);
|
|
1319
|
+
const encoded = encodeURIComponent(postUrn);
|
|
1320
|
+
const data = await withSpinner(
|
|
1321
|
+
"Fetching comments...",
|
|
1322
|
+
() => liExec(`rest/socialActions/${encoded}/comments`, "GET", {}, agentId)
|
|
1323
|
+
);
|
|
1324
|
+
const comments = data?.elements ?? (Array.isArray(data) ? data : []);
|
|
1325
|
+
if (isJsonMode()) return printJson(comments);
|
|
1326
|
+
if (!comments.length) {
|
|
1327
|
+
console.log(chalk7.dim("No comments."));
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
for (const c of comments) {
|
|
1331
|
+
const author = c.actor ?? "\u2014";
|
|
1332
|
+
const msg = c.message;
|
|
1333
|
+
const text = msg?.text ?? "(no text)";
|
|
1334
|
+
console.log(chalk7.bold(author));
|
|
1335
|
+
console.log(` ${text}`);
|
|
1336
|
+
console.log();
|
|
1337
|
+
}
|
|
1338
|
+
console.log(chalk7.dim(`${comments.length} comment(s)`));
|
|
1339
|
+
});
|
|
1340
|
+
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", `
|
|
1341
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
1342
|
+
|
|
1343
|
+
Examples:
|
|
1344
|
+
$ blink linkedin comment "urn:li:ugcPost:1234567890" "Great post!"
|
|
1345
|
+
$ blink linkedin comment "urn:li:ugcPost:1234567890" "Thanks for sharing" --json
|
|
1346
|
+
`).action(async (postUrn, text, 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 actor = `urn:li:person:${personId}`;
|
|
1354
|
+
const encoded = encodeURIComponent(postUrn);
|
|
1355
|
+
const data = await withSpinner(
|
|
1356
|
+
"Adding comment...",
|
|
1357
|
+
() => liExec(
|
|
1358
|
+
`rest/socialActions/${encoded}/comments`,
|
|
1359
|
+
"POST",
|
|
1360
|
+
{ actor, object: postUrn, message: { text } },
|
|
1361
|
+
agentId
|
|
1362
|
+
)
|
|
1363
|
+
);
|
|
1364
|
+
if (isJsonMode()) return printJson(data);
|
|
1365
|
+
console.log(chalk7.green("\u2713 Comment added"));
|
|
1366
|
+
if (data?.id) console.log(chalk7.dim(` ID: ${data.id}`));
|
|
1367
|
+
});
|
|
1368
|
+
li.command("like <postUrn>").description("Like a LinkedIn post").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1369
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
1370
|
+
|
|
1371
|
+
Examples:
|
|
1372
|
+
$ blink linkedin like "urn:li:ugcPost:1234567890"
|
|
1373
|
+
$ blink linkedin like "urn:li:ugcPost:1234567890" --json
|
|
1374
|
+
`).action(async (postUrn, opts) => {
|
|
1375
|
+
requireToken();
|
|
1376
|
+
const agentId = requireAgentId(opts.agent);
|
|
1377
|
+
const personId = await withSpinner(
|
|
1378
|
+
"Resolving your LinkedIn identity...",
|
|
1379
|
+
() => getPersonId(agentId)
|
|
1380
|
+
);
|
|
1381
|
+
const actor = `urn:li:person:${personId}`;
|
|
1382
|
+
const encoded = encodeURIComponent(postUrn);
|
|
1383
|
+
const data = await withSpinner(
|
|
1384
|
+
"Liking post...",
|
|
1385
|
+
() => liExec(
|
|
1386
|
+
`rest/socialActions/${encoded}/likes`,
|
|
1387
|
+
"POST",
|
|
1388
|
+
{ actor, object: postUrn },
|
|
1389
|
+
agentId
|
|
1390
|
+
)
|
|
1391
|
+
);
|
|
1392
|
+
if (isJsonMode()) return printJson(data);
|
|
1393
|
+
console.log(chalk7.green("\u2713 Post liked"));
|
|
1394
|
+
});
|
|
1395
|
+
li.command("unlike <postUrn>").description("Unlike a LinkedIn post").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1396
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
1397
|
+
|
|
1398
|
+
Examples:
|
|
1399
|
+
$ blink linkedin unlike "urn:li:ugcPost:1234567890"
|
|
1400
|
+
$ blink linkedin unlike "urn:li:ugcPost:1234567890" --json
|
|
1401
|
+
`).action(async (postUrn, opts) => {
|
|
1402
|
+
requireToken();
|
|
1403
|
+
const agentId = requireAgentId(opts.agent);
|
|
1404
|
+
const personId = await withSpinner(
|
|
1405
|
+
"Resolving your LinkedIn identity...",
|
|
1406
|
+
() => getPersonId(agentId)
|
|
1407
|
+
);
|
|
1408
|
+
const actor = `urn:li:person:${personId}`;
|
|
1409
|
+
const encodedPost = encodeURIComponent(postUrn);
|
|
1410
|
+
const encodedActor = encodeURIComponent(actor);
|
|
1411
|
+
const data = await withSpinner(
|
|
1412
|
+
"Unliking post...",
|
|
1413
|
+
() => liExec(
|
|
1414
|
+
`rest/socialActions/${encodedPost}/likes/${encodedActor}?actor=${encodedActor}`,
|
|
1415
|
+
"DELETE",
|
|
1416
|
+
{},
|
|
1417
|
+
agentId
|
|
1418
|
+
)
|
|
1419
|
+
);
|
|
1420
|
+
if (isJsonMode()) return printJson(data);
|
|
1421
|
+
console.log(chalk7.green("\u2713 Post unliked"));
|
|
1063
1422
|
});
|
|
1064
1423
|
}
|
|
1065
1424
|
|
|
@@ -1101,7 +1460,7 @@ async function appRequest(path, opts = {}) {
|
|
|
1101
1460
|
init_project();
|
|
1102
1461
|
import { readdirSync, readFileSync as readFileSync8 } from "fs";
|
|
1103
1462
|
import { join as join3, relative } from "path";
|
|
1104
|
-
import
|
|
1463
|
+
import chalk8 from "chalk";
|
|
1105
1464
|
function collectFiles(dir) {
|
|
1106
1465
|
const files = [];
|
|
1107
1466
|
function walk(current) {
|
|
@@ -1174,7 +1533,7 @@ Project resolution:
|
|
|
1174
1533
|
const production = opts.prod === true;
|
|
1175
1534
|
if (!isJsonMode()) {
|
|
1176
1535
|
console.log();
|
|
1177
|
-
console.log(
|
|
1536
|
+
console.log(chalk8.bold(" Blink Deploy"));
|
|
1178
1537
|
console.log();
|
|
1179
1538
|
}
|
|
1180
1539
|
const files = await withSpinner(`Packaging ${buildDir}...`, async () => collectFiles(buildDir));
|
|
@@ -1228,7 +1587,7 @@ Project resolution:
|
|
|
1228
1587
|
|
|
1229
1588
|
// src/commands/project.ts
|
|
1230
1589
|
init_project();
|
|
1231
|
-
import
|
|
1590
|
+
import chalk9 from "chalk";
|
|
1232
1591
|
function registerProjectCommands(program2) {
|
|
1233
1592
|
const project = program2.command("project").description("Create, list, and delete Blink projects").addHelpText("after", `
|
|
1234
1593
|
Examples:
|
|
@@ -1257,8 +1616,8 @@ After creating a project, link it to your current directory:
|
|
|
1257
1616
|
);
|
|
1258
1617
|
if (isJsonMode()) return printJson(result);
|
|
1259
1618
|
const proj = result?.project ?? result;
|
|
1260
|
-
console.log(
|
|
1261
|
-
console.log(
|
|
1619
|
+
console.log(chalk9.green("\u2713") + ` Created: ${proj.id}`);
|
|
1620
|
+
console.log(chalk9.dim(" Run `blink link " + proj.id + "` to use it"));
|
|
1262
1621
|
});
|
|
1263
1622
|
project.command("delete <project_id>").description("Delete a project").option("--yes", "Skip confirmation").action(async (projectId, opts) => {
|
|
1264
1623
|
requireToken();
|
|
@@ -1301,7 +1660,7 @@ After linking, most commands work without specifying a project_id:
|
|
|
1301
1660
|
});
|
|
1302
1661
|
}
|
|
1303
1662
|
writeProjectConfig({ projectId: id });
|
|
1304
|
-
console.log(
|
|
1663
|
+
console.log(chalk9.green("\u2713") + " Linked to " + id);
|
|
1305
1664
|
});
|
|
1306
1665
|
program2.command("unlink").description("Remove project link from current directory").action(() => {
|
|
1307
1666
|
clearProjectConfig();
|
|
@@ -1313,29 +1672,29 @@ After linking, most commands work without specifying a project_id:
|
|
|
1313
1672
|
const agentId = resolveAgentId3();
|
|
1314
1673
|
const agentSource = process.env.BLINK_AGENT_ID ? "BLINK_AGENT_ID env" : process.env.BLINK_ACTIVE_AGENT ? "BLINK_ACTIVE_AGENT env" : null;
|
|
1315
1674
|
if (agentId) {
|
|
1316
|
-
console.log(
|
|
1675
|
+
console.log(chalk9.bold("Agent ") + agentId + chalk9.dim(" (" + agentSource + ")"));
|
|
1317
1676
|
} else {
|
|
1318
|
-
console.log(
|
|
1677
|
+
console.log(chalk9.dim("Agent not set (use: eval $(blink agent use clw_xxx --export))"));
|
|
1319
1678
|
}
|
|
1320
1679
|
if (config) {
|
|
1321
1680
|
const projectSource = process.env.BLINK_ACTIVE_PROJECT ? "BLINK_ACTIVE_PROJECT env" : ".blink/project.json";
|
|
1322
|
-
console.log(
|
|
1681
|
+
console.log(chalk9.bold("Project ") + config.projectId + chalk9.dim(" (" + projectSource + ")"));
|
|
1323
1682
|
} else if (process.env.BLINK_ACTIVE_PROJECT) {
|
|
1324
|
-
console.log(
|
|
1683
|
+
console.log(chalk9.bold("Project ") + process.env.BLINK_ACTIVE_PROJECT + chalk9.dim(" (BLINK_ACTIVE_PROJECT env)"));
|
|
1325
1684
|
} else {
|
|
1326
|
-
console.log(
|
|
1685
|
+
console.log(chalk9.dim("Project not linked (use: blink link or eval $(blink use proj_xxx --export))"));
|
|
1327
1686
|
}
|
|
1328
1687
|
const authSource = process.env.BLINK_API_KEY ? "BLINK_API_KEY env" : "~/.config/blink/config.toml";
|
|
1329
1688
|
const hasProjectKey = !!process.env.BLINK_PROJECT_KEY;
|
|
1330
|
-
console.log(
|
|
1689
|
+
console.log(chalk9.bold("Auth ") + authSource);
|
|
1331
1690
|
if (hasProjectKey) {
|
|
1332
|
-
console.log(
|
|
1691
|
+
console.log(chalk9.bold("ProjKey ") + "BLINK_PROJECT_KEY env" + chalk9.dim(" (used for db/storage/rag)"));
|
|
1333
1692
|
}
|
|
1334
1693
|
});
|
|
1335
1694
|
}
|
|
1336
1695
|
|
|
1337
1696
|
// src/commands/auth.ts
|
|
1338
|
-
import
|
|
1697
|
+
import chalk10 from "chalk";
|
|
1339
1698
|
function registerAuthCommands(program2) {
|
|
1340
1699
|
program2.command("login").description("Authenticate with your Blink API key").option("--interactive", "Prompt for API key (for headless/SSH/CI environments)").addHelpText("after", `
|
|
1341
1700
|
Get your API key at: blink.new \u2192 Settings \u2192 API Keys (starts with blnk_ak_)
|
|
@@ -1348,7 +1707,7 @@ In Blink Claw agents: BLINK_API_KEY is already set \u2014 login is not needed.
|
|
|
1348
1707
|
For CI/GitHub Actions: set BLINK_API_KEY as a secret, skip login entirely.
|
|
1349
1708
|
`).action(async (opts) => {
|
|
1350
1709
|
if (process.env.BLINK_API_KEY && !opts.interactive) {
|
|
1351
|
-
console.log(
|
|
1710
|
+
console.log(chalk10.green("\u2713") + " Already authenticated via BLINK_API_KEY env var.");
|
|
1352
1711
|
return;
|
|
1353
1712
|
}
|
|
1354
1713
|
const { password } = await import("@clack/prompts");
|
|
@@ -1358,7 +1717,7 @@ For CI/GitHub Actions: set BLINK_API_KEY as a secret, skip login entirely.
|
|
|
1358
1717
|
process.exit(1);
|
|
1359
1718
|
}
|
|
1360
1719
|
writeConfig({ api_key: apiKey });
|
|
1361
|
-
console.log(
|
|
1720
|
+
console.log(chalk10.green("\u2713") + " Saved to ~/.config/blink/config.toml");
|
|
1362
1721
|
});
|
|
1363
1722
|
program2.command("logout").description("Remove stored credentials").action(() => {
|
|
1364
1723
|
clearConfig();
|
|
@@ -1378,15 +1737,15 @@ For CI/GitHub Actions: set BLINK_API_KEY as a secret, skip login entirely.
|
|
|
1378
1737
|
});
|
|
1379
1738
|
}
|
|
1380
1739
|
const source = process.env.BLINK_API_KEY === token ? "BLINK_API_KEY env" : "~/.config/blink/config.toml";
|
|
1381
|
-
console.log(
|
|
1382
|
-
console.log(
|
|
1383
|
-
console.log(
|
|
1740
|
+
console.log(chalk10.green("\u2713") + " Authenticated");
|
|
1741
|
+
console.log(chalk10.bold("Key ") + token.slice(0, 20) + chalk10.dim("..."));
|
|
1742
|
+
console.log(chalk10.bold("Source ") + chalk10.dim(source));
|
|
1384
1743
|
});
|
|
1385
1744
|
}
|
|
1386
1745
|
|
|
1387
1746
|
// src/commands/agent.ts
|
|
1388
1747
|
init_agent();
|
|
1389
|
-
import
|
|
1748
|
+
import chalk11 from "chalk";
|
|
1390
1749
|
function registerAgentCommands(program2) {
|
|
1391
1750
|
const agent = program2.command("agent").description("Manage Blink Claw agents in your workspace").addHelpText("after", `
|
|
1392
1751
|
Examples:
|
|
@@ -1406,7 +1765,7 @@ Agent ID resolution for all agent/secrets commands (priority: high \u2192 low):
|
|
|
1406
1765
|
if (isJsonMode()) return printJson(result);
|
|
1407
1766
|
const agents = Array.isArray(result) ? result : result?.agents ?? [];
|
|
1408
1767
|
if (!agents.length) {
|
|
1409
|
-
console.log(
|
|
1768
|
+
console.log(chalk11.dim("No agents found. Deploy one at blink.new/claw"));
|
|
1410
1769
|
return;
|
|
1411
1770
|
}
|
|
1412
1771
|
const table = createTable(["ID", "Name", "Status", "Size", "Model"]);
|
|
@@ -1429,9 +1788,9 @@ After setting, secrets commands use this agent automatically:
|
|
|
1429
1788
|
process.stdout.write(`export BLINK_ACTIVE_AGENT=${agentId}
|
|
1430
1789
|
`);
|
|
1431
1790
|
} else {
|
|
1432
|
-
console.log(
|
|
1433
|
-
console.log(
|
|
1434
|
-
console.log(
|
|
1791
|
+
console.log(chalk11.bold("Active agent: ") + agentId);
|
|
1792
|
+
console.log(chalk11.dim(`Run: export BLINK_ACTIVE_AGENT=${agentId}`));
|
|
1793
|
+
console.log(chalk11.dim(`Or: eval $(blink agent use ${agentId} --export)`));
|
|
1435
1794
|
}
|
|
1436
1795
|
});
|
|
1437
1796
|
agent.command("status [agent_id]").description("Show details for an agent").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID or BLINK_ACTIVE_AGENT)").addHelpText("after", `
|
|
@@ -1447,18 +1806,18 @@ Examples:
|
|
|
1447
1806
|
);
|
|
1448
1807
|
if (isJsonMode()) return printJson(result);
|
|
1449
1808
|
const a = result?.agent ?? result;
|
|
1450
|
-
console.log(
|
|
1451
|
-
console.log(
|
|
1452
|
-
console.log(
|
|
1453
|
-
console.log(
|
|
1454
|
-
console.log(
|
|
1455
|
-
if (a.fly_app_name) console.log(
|
|
1809
|
+
console.log(chalk11.bold("ID ") + a.id);
|
|
1810
|
+
console.log(chalk11.bold("Name ") + a.name);
|
|
1811
|
+
console.log(chalk11.bold("Status ") + a.status);
|
|
1812
|
+
console.log(chalk11.bold("Model ") + (a.model ?? "-"));
|
|
1813
|
+
console.log(chalk11.bold("Machine ") + (a.machine_size ?? "-"));
|
|
1814
|
+
if (a.fly_app_name) console.log(chalk11.bold("Fly App ") + a.fly_app_name);
|
|
1456
1815
|
});
|
|
1457
1816
|
}
|
|
1458
1817
|
|
|
1459
1818
|
// src/commands/secrets.ts
|
|
1460
1819
|
init_agent();
|
|
1461
|
-
import
|
|
1820
|
+
import chalk12 from "chalk";
|
|
1462
1821
|
function registerSecretsCommands(program2) {
|
|
1463
1822
|
const secrets = program2.command("secrets").description("Manage encrypted secrets vault for a Claw agent").addHelpText("after", `
|
|
1464
1823
|
Secrets are encrypted key-value pairs stored in the agent's vault.
|
|
@@ -1494,11 +1853,11 @@ Examples:
|
|
|
1494
1853
|
if (isJsonMode()) return printJson(result);
|
|
1495
1854
|
const keys = result?.secrets?.map((s) => s.key) ?? result?.keys ?? [];
|
|
1496
1855
|
if (!keys.length) {
|
|
1497
|
-
console.log(
|
|
1856
|
+
console.log(chalk12.dim("(no secrets set \u2014 use `blink secrets set KEY value`)"));
|
|
1498
1857
|
return;
|
|
1499
1858
|
}
|
|
1500
|
-
for (const k of keys) console.log(
|
|
1501
|
-
console.log(
|
|
1859
|
+
for (const k of keys) console.log(chalk12.bold(k));
|
|
1860
|
+
console.log(chalk12.dim(`
|
|
1502
1861
|
${keys.length} secret${keys.length === 1 ? "" : "s"} (values hidden)`));
|
|
1503
1862
|
});
|
|
1504
1863
|
secrets.command("set <key> <value>").description("Add or update a secret (stored encrypted, value never shown again)").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID or BLINK_ACTIVE_AGENT)").addHelpText("after", `
|
|
@@ -1521,8 +1880,8 @@ After setting, the secret is available as $KEY_NAME in agent shell commands.
|
|
|
1521
1880
|
);
|
|
1522
1881
|
const normalised = key.toUpperCase();
|
|
1523
1882
|
if (!isJsonMode()) {
|
|
1524
|
-
console.log(
|
|
1525
|
-
console.log(
|
|
1883
|
+
console.log(chalk12.green("\u2713") + ` ${normalised} saved`);
|
|
1884
|
+
console.log(chalk12.dim(" Value hidden. Use $" + normalised + " in shell commands."));
|
|
1526
1885
|
} else {
|
|
1527
1886
|
printJson({ status: "ok", key: normalised, agent_id: agentId });
|
|
1528
1887
|
}
|
|
@@ -1606,9 +1965,27 @@ Realtime / RAG / Notify:
|
|
|
1606
1965
|
$ blink rag search "how does billing work" --ai
|
|
1607
1966
|
$ blink notify email user@example.com "Subject" "Body"
|
|
1608
1967
|
|
|
1609
|
-
Connectors (Notion, Slack,
|
|
1610
|
-
$ blink connector
|
|
1611
|
-
$ blink connector
|
|
1968
|
+
Connectors (38 OAuth providers \u2014 GitHub, Notion, Slack, Stripe, Shopify, Jira, Linear, and more):
|
|
1969
|
+
$ blink connector providers List all 38 providers
|
|
1970
|
+
$ blink connector status Show all connected accounts
|
|
1971
|
+
$ blink connector status github Check a specific provider
|
|
1972
|
+
$ blink connector exec github /user/repos GET Call any REST endpoint
|
|
1973
|
+
$ blink connector exec notion /search POST '{"query":"notes"}' Notion search
|
|
1974
|
+
$ blink connector exec slack /chat.postMessage POST '{"channel":"C123","text":"hi"}'
|
|
1975
|
+
$ blink connector exec stripe /customers GET '{"limit":5}'
|
|
1976
|
+
$ blink connector exec jira /search GET '{"jql":"assignee=currentUser()"}'
|
|
1977
|
+
$ blink connector exec linear '{ viewer { id name } }' POST GraphQL (Linear)
|
|
1978
|
+
Connect accounts at: blink.new/settings?tab=connectors
|
|
1979
|
+
|
|
1980
|
+
LinkedIn (dedicated commands for the LinkedIn connector):
|
|
1981
|
+
$ blink linkedin me Show your LinkedIn profile
|
|
1982
|
+
$ blink linkedin posts List your 10 most recent posts
|
|
1983
|
+
$ blink linkedin post "Hello LinkedIn!" Publish a text post
|
|
1984
|
+
$ blink linkedin comments "urn:li:ugcPost:123" Read comments on a post
|
|
1985
|
+
$ blink linkedin comment "urn:li:ugcPost:123" "Nice!" Add a comment
|
|
1986
|
+
$ blink linkedin like "urn:li:ugcPost:123" Like a post
|
|
1987
|
+
$ blink linkedin unlike "urn:li:ugcPost:123" Unlike a post
|
|
1988
|
+
Link LinkedIn at: blink.new/claw (Agent Integrations tab)
|
|
1612
1989
|
|
|
1613
1990
|
Agents (Claw \u2014 zero config on Fly machines, BLINK_AGENT_ID is already set):
|
|
1614
1991
|
$ blink agent list List all agents in workspace
|
|
@@ -1648,6 +2025,7 @@ registerRealtimeCommands(program);
|
|
|
1648
2025
|
registerRagCommands(program);
|
|
1649
2026
|
registerNotifyCommands(program);
|
|
1650
2027
|
registerConnectorCommands(program);
|
|
2028
|
+
registerLinkedInCommands(program);
|
|
1651
2029
|
program.command("use <project_id>").description("Set active project for this shell session (alternative to blink link)").option("--export", "Output a shell export statement \u2014 use with eval to actually set it").addHelpText("after", `
|
|
1652
2030
|
Examples:
|
|
1653
2031
|
$ blink use proj_xxx Shows the export command to run
|
|
@@ -1663,10 +2041,10 @@ After setting:
|
|
|
1663
2041
|
process.stdout.write(`export BLINK_ACTIVE_PROJECT=${projectId}
|
|
1664
2042
|
`);
|
|
1665
2043
|
} else {
|
|
1666
|
-
const { default:
|
|
1667
|
-
console.log(
|
|
1668
|
-
console.log(
|
|
1669
|
-
console.log(
|
|
2044
|
+
const { default: chalk13 } = await import("chalk");
|
|
2045
|
+
console.log(chalk13.bold("Active project: ") + projectId);
|
|
2046
|
+
console.log(chalk13.dim(`Run: export BLINK_ACTIVE_PROJECT=${projectId}`));
|
|
2047
|
+
console.log(chalk13.dim(`Or: eval $(blink use ${projectId} --export)`));
|
|
1670
2048
|
}
|
|
1671
2049
|
});
|
|
1672
2050
|
program.action(async () => {
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { registerRealtimeCommands } from './commands/realtime.js'
|
|
|
9
9
|
import { registerRagCommands } from './commands/rag.js'
|
|
10
10
|
import { registerNotifyCommands } from './commands/notify.js'
|
|
11
11
|
import { registerConnectorCommands } from './commands/connector.js'
|
|
12
|
+
import { registerLinkedInCommands } from './commands/linkedin.js'
|
|
12
13
|
import { registerDeployCommands } from './commands/deploy.js'
|
|
13
14
|
import { registerProjectCommands } from './commands/project.js'
|
|
14
15
|
import { registerAuthCommands } from './commands/auth.js'
|
|
@@ -78,9 +79,27 @@ Realtime / RAG / Notify:
|
|
|
78
79
|
$ blink rag search "how does billing work" --ai
|
|
79
80
|
$ blink notify email user@example.com "Subject" "Body"
|
|
80
81
|
|
|
81
|
-
Connectors (Notion, Slack,
|
|
82
|
-
$ blink connector
|
|
83
|
-
$ blink connector
|
|
82
|
+
Connectors (38 OAuth providers — GitHub, Notion, Slack, Stripe, Shopify, Jira, Linear, and more):
|
|
83
|
+
$ blink connector providers List all 38 providers
|
|
84
|
+
$ blink connector status Show all connected accounts
|
|
85
|
+
$ blink connector status github Check a specific provider
|
|
86
|
+
$ blink connector exec github /user/repos GET Call any REST endpoint
|
|
87
|
+
$ blink connector exec notion /search POST '{"query":"notes"}' Notion search
|
|
88
|
+
$ blink connector exec slack /chat.postMessage POST '{"channel":"C123","text":"hi"}'
|
|
89
|
+
$ blink connector exec stripe /customers GET '{"limit":5}'
|
|
90
|
+
$ blink connector exec jira /search GET '{"jql":"assignee=currentUser()"}'
|
|
91
|
+
$ blink connector exec linear '{ viewer { id name } }' POST GraphQL (Linear)
|
|
92
|
+
Connect accounts at: blink.new/settings?tab=connectors
|
|
93
|
+
|
|
94
|
+
LinkedIn (dedicated commands for the LinkedIn connector):
|
|
95
|
+
$ blink linkedin me Show your LinkedIn profile
|
|
96
|
+
$ blink linkedin posts List your 10 most recent posts
|
|
97
|
+
$ blink linkedin post "Hello LinkedIn!" Publish a text post
|
|
98
|
+
$ blink linkedin comments "urn:li:ugcPost:123" Read comments on a post
|
|
99
|
+
$ blink linkedin comment "urn:li:ugcPost:123" "Nice!" Add a comment
|
|
100
|
+
$ blink linkedin like "urn:li:ugcPost:123" Like a post
|
|
101
|
+
$ blink linkedin unlike "urn:li:ugcPost:123" Unlike a post
|
|
102
|
+
Link LinkedIn at: blink.new/claw (Agent Integrations tab)
|
|
84
103
|
|
|
85
104
|
Agents (Claw — zero config on Fly machines, BLINK_AGENT_ID is already set):
|
|
86
105
|
$ blink agent list List all agents in workspace
|
|
@@ -122,6 +141,7 @@ registerRealtimeCommands(program)
|
|
|
122
141
|
registerRagCommands(program)
|
|
123
142
|
registerNotifyCommands(program)
|
|
124
143
|
registerConnectorCommands(program)
|
|
144
|
+
registerLinkedInCommands(program)
|
|
125
145
|
|
|
126
146
|
program.command('use <project_id>')
|
|
127
147
|
.description('Set active project for this shell session (alternative to blink link)')
|
|
@@ -1,54 +1,203 @@
|
|
|
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 { printJson, isJsonMode, withSpinner } from '../lib/output.js'
|
|
4
|
+
import { printJson, isJsonMode, withSpinner, createTable } from '../lib/output.js'
|
|
5
|
+
import chalk from 'chalk'
|
|
6
|
+
|
|
7
|
+
// All supported OAuth connector providers
|
|
8
|
+
const PROVIDERS = [
|
|
9
|
+
// Communication
|
|
10
|
+
{ key: 'slack', label: 'Slack', category: 'Communication' },
|
|
11
|
+
{ key: 'discord', label: 'Discord', category: 'Communication' },
|
|
12
|
+
// Email & Calendar
|
|
13
|
+
{ key: 'google_gmail', label: 'Gmail', category: 'Email & Calendar' },
|
|
14
|
+
{ key: 'google_calendar', label: 'Google Calendar', category: 'Email & Calendar' },
|
|
15
|
+
{ key: 'microsoft_outlook', label: 'Outlook', category: 'Email & Calendar' },
|
|
16
|
+
{ key: 'microsoft_calendar', label: 'Microsoft Calendar', category: 'Email & Calendar' },
|
|
17
|
+
// Files & Docs
|
|
18
|
+
{ key: 'google_drive', label: 'Google Drive', category: 'Files & Docs' },
|
|
19
|
+
{ key: 'google_docs', label: 'Google Docs', category: 'Files & Docs' },
|
|
20
|
+
{ key: 'google_sheets', label: 'Google Sheets', category: 'Files & Docs' },
|
|
21
|
+
{ key: 'google_slides', label: 'Google Slides', category: 'Files & Docs' },
|
|
22
|
+
{ key: 'microsoft_onedrive', label: 'OneDrive', category: 'Files & Docs' },
|
|
23
|
+
{ key: 'notion', label: 'Notion', category: 'Files & Docs' },
|
|
24
|
+
// Dev Tools
|
|
25
|
+
{ key: 'github', label: 'GitHub', category: 'Dev Tools' },
|
|
26
|
+
{ key: 'linear', label: 'Linear', category: 'Dev Tools' },
|
|
27
|
+
{ key: 'jira', label: 'Jira', category: 'Dev Tools' },
|
|
28
|
+
{ key: 'vercel', label: 'Vercel', category: 'Dev Tools' },
|
|
29
|
+
{ key: 'figma', label: 'Figma', category: 'Dev Tools' },
|
|
30
|
+
// CRM & Sales
|
|
31
|
+
{ key: 'hubspot', label: 'HubSpot', category: 'CRM & Sales' },
|
|
32
|
+
{ key: 'salesforce', label: 'Salesforce', category: 'CRM & Sales' },
|
|
33
|
+
{ key: 'pipedrive', label: 'Pipedrive', category: 'CRM & Sales' },
|
|
34
|
+
{ key: 'attio', label: 'Attio', category: 'CRM & Sales' },
|
|
35
|
+
// Marketing
|
|
36
|
+
{ key: 'mailchimp', label: 'Mailchimp', category: 'Marketing' },
|
|
37
|
+
{ key: 'typeform', label: 'Typeform', category: 'Marketing' },
|
|
38
|
+
// Data & Productivity
|
|
39
|
+
{ key: 'airtable', label: 'Airtable', category: 'Data & Productivity' },
|
|
40
|
+
{ key: 'asana', label: 'Asana', category: 'Data & Productivity' },
|
|
41
|
+
{ key: 'calendly', label: 'Calendly', category: 'Data & Productivity' },
|
|
42
|
+
// Social & Creator
|
|
43
|
+
{ key: 'twitter', label: 'X (Twitter)', category: 'Social & Creator' },
|
|
44
|
+
{ key: 'instagram', label: 'Instagram', category: 'Social & Creator' },
|
|
45
|
+
{ key: 'tiktok', label: 'TikTok', category: 'Social & Creator' },
|
|
46
|
+
{ key: 'youtube', label: 'YouTube', category: 'Social & Creator' },
|
|
47
|
+
{ key: 'linkedin', label: 'LinkedIn', category: 'Social & Creator' },
|
|
48
|
+
{ key: 'reddit', label: 'Reddit', category: 'Social & Creator' },
|
|
49
|
+
{ key: 'loom', label: 'Loom', category: 'Social & Creator' },
|
|
50
|
+
// Meetings
|
|
51
|
+
{ key: 'zoom', label: 'Zoom', category: 'Meetings' },
|
|
52
|
+
{ key: 'microsoft_teams', label: 'Microsoft Teams', category: 'Meetings' },
|
|
53
|
+
// Commerce
|
|
54
|
+
{ key: 'stripe', label: 'Stripe', category: 'Commerce' },
|
|
55
|
+
{ key: 'shopify', label: 'Shopify', category: 'Commerce' },
|
|
56
|
+
{ key: 'etsy', label: 'Etsy', category: 'Commerce' },
|
|
57
|
+
] as const
|
|
5
58
|
|
|
6
59
|
export function registerConnectorCommands(program: Command) {
|
|
7
60
|
const connector = program.command('connector')
|
|
8
|
-
.description('Execute actions on connected OAuth apps (Notion, Slack,
|
|
61
|
+
.description('Execute actions on connected OAuth apps (GitHub, Notion, Slack, Reddit, Google, and 30+ more)')
|
|
9
62
|
.addHelpText('after', `
|
|
10
|
-
Connectors are OAuth accounts linked to your workspace
|
|
11
|
-
Once linked, use blink connector exec to call their APIs without managing tokens yourself.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
$ blink connector exec
|
|
19
|
-
$ blink connector exec
|
|
20
|
-
$ blink connector exec
|
|
21
|
-
$ blink connector exec
|
|
22
|
-
$ blink connector exec
|
|
23
|
-
$ blink connector exec
|
|
24
|
-
|
|
25
|
-
|
|
63
|
+
Connectors are OAuth accounts linked to your workspace at blink.new → Settings → Integrations.
|
|
64
|
+
Once linked, use \`blink connector exec\` to call their APIs without managing tokens yourself.
|
|
65
|
+
|
|
66
|
+
Connect accounts at: https://blink.new/settings?tab=connectors
|
|
67
|
+
|
|
68
|
+
Run \`blink connector providers\` to see all 38 supported providers.
|
|
69
|
+
|
|
70
|
+
Quick examples:
|
|
71
|
+
$ blink connector exec github /user/repos GET
|
|
72
|
+
$ blink connector exec notion /search POST '{"query":"meeting notes"}'
|
|
73
|
+
$ blink connector exec slack /chat.postMessage POST '{"channel":"C123","text":"Hello"}'
|
|
74
|
+
$ blink connector exec google_calendar /calendars/primary/events GET
|
|
75
|
+
$ blink connector exec jira /issue POST '{"fields":{"project":{"key":"PROJ"},"summary":"Bug"}}'
|
|
76
|
+
$ blink connector exec stripe /customers GET '{"limit":10}'
|
|
77
|
+
$ blink connector exec shopify /orders.json GET
|
|
78
|
+
$ blink connector exec linear '{ viewer { id name } }' POST # GraphQL
|
|
79
|
+
|
|
80
|
+
Use --method GET for reads, POST for writes (default: POST).
|
|
81
|
+
Use --account <id> if you have multiple linked accounts for the same provider.
|
|
26
82
|
`)
|
|
27
83
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.
|
|
31
|
-
.option('--
|
|
84
|
+
// blink connector providers
|
|
85
|
+
connector.command('providers')
|
|
86
|
+
.description('List all supported OAuth connector providers')
|
|
87
|
+
.option('--category <cat>', 'Filter by category')
|
|
88
|
+
.action((opts) => {
|
|
89
|
+
const filtered = opts.category
|
|
90
|
+
? PROVIDERS.filter(p => p.category.toLowerCase().includes(opts.category.toLowerCase()))
|
|
91
|
+
: PROVIDERS
|
|
92
|
+
|
|
93
|
+
if (isJsonMode()) return printJson(filtered)
|
|
94
|
+
|
|
95
|
+
const categories = [...new Set(filtered.map(p => p.category))]
|
|
96
|
+
for (const cat of categories) {
|
|
97
|
+
console.log(chalk.bold.cyan(`\n${cat}`))
|
|
98
|
+
const catProviders = filtered.filter(p => p.category === cat)
|
|
99
|
+
for (const p of catProviders) {
|
|
100
|
+
console.log(` ${chalk.green(p.key.padEnd(22))} ${chalk.dim(p.label)}`)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
console.log(chalk.dim(`\n${filtered.length} providers total. Connect at blink.new/settings?tab=connectors`))
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// blink connector status [provider]
|
|
107
|
+
connector.command('status [provider]')
|
|
108
|
+
.description('Check connection status for a provider (or all providers)')
|
|
109
|
+
.option('--account <id>', 'Check a specific account ID')
|
|
110
|
+
.action(async (provider: string | undefined, opts) => {
|
|
111
|
+
requireToken()
|
|
112
|
+
if (provider) {
|
|
113
|
+
const result = await withSpinner(`Checking ${provider} status...`, () =>
|
|
114
|
+
resourcesRequest(`/v1/connectors/${provider}/status`, {
|
|
115
|
+
method: 'GET',
|
|
116
|
+
...(opts.account ? { query: { account_id: opts.account } } : {})
|
|
117
|
+
})
|
|
118
|
+
)
|
|
119
|
+
if (isJsonMode()) return printJson(result)
|
|
120
|
+
const d = result?.data ?? result
|
|
121
|
+
if (d?.connected) {
|
|
122
|
+
console.log(chalk.green('✓ Connected'))
|
|
123
|
+
if (d.account_id) console.log(chalk.dim(` Account: ${d.account_id}`))
|
|
124
|
+
if (d.metadata?.email) console.log(chalk.dim(` Email: ${d.metadata.email}`))
|
|
125
|
+
if (d.metadata?.name) console.log(chalk.dim(` Name: ${d.metadata.name}`))
|
|
126
|
+
} else {
|
|
127
|
+
console.log(chalk.red('✗ Not connected'))
|
|
128
|
+
console.log(chalk.dim(` Connect at blink.new/settings?tab=connectors`))
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
// List all connected providers
|
|
132
|
+
const result = await withSpinner('Checking all connectors...', () =>
|
|
133
|
+
resourcesRequest('/v1/connectors/linked', { method: 'GET' })
|
|
134
|
+
)
|
|
135
|
+
if (isJsonMode()) return printJson(result)
|
|
136
|
+
const connected: Array<{ provider: string; accounts: Array<{ account_id: string; label: string }> }> =
|
|
137
|
+
result?.data ?? []
|
|
138
|
+
if (!connected.length) {
|
|
139
|
+
console.log(chalk.dim('No connectors linked. Connect at blink.new/settings?tab=connectors'))
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
const table = createTable(['Provider', 'Accounts'])
|
|
143
|
+
for (const c of connected) {
|
|
144
|
+
const accounts = c.accounts.map(a => a.label || a.account_id).join(', ')
|
|
145
|
+
table.push([chalk.green(c.provider), accounts])
|
|
146
|
+
}
|
|
147
|
+
console.log(table.toString())
|
|
148
|
+
console.log(chalk.dim(`\n${connected.length} provider(s) connected`))
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// blink connector exec <provider> <endpoint> [params]
|
|
153
|
+
connector.command('exec <provider> <endpoint> [params]')
|
|
154
|
+
.description('Execute a call on a connected OAuth provider')
|
|
155
|
+
.option('--account <id>', 'Specific account ID (if you have multiple accounts)')
|
|
156
|
+
.option('--method <method>', 'HTTP method: GET | POST | PUT | PATCH | DELETE (default: POST)', 'POST')
|
|
32
157
|
.addHelpText('after', `
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
$ blink connector exec
|
|
37
|
-
$ blink connector exec
|
|
38
|
-
$ blink connector exec
|
|
39
|
-
$ blink connector exec
|
|
40
|
-
$ blink connector exec
|
|
158
|
+
<endpoint> is the API path relative to the provider's base URL, OR a GraphQL query string for Linear.
|
|
159
|
+
|
|
160
|
+
Examples (REST):
|
|
161
|
+
$ blink connector exec github /user/repos GET
|
|
162
|
+
$ blink connector exec github /repos/owner/repo/issues POST '{"title":"Bug","body":"..."}'
|
|
163
|
+
$ blink connector exec notion /search POST '{"query":"meeting notes"}'
|
|
164
|
+
$ blink connector exec slack /chat.postMessage POST '{"channel":"C123","text":"Hello"}'
|
|
165
|
+
$ blink connector exec google_drive /files GET '{"q":"mimeType=\\'application/pdf\\'"}'
|
|
166
|
+
$ blink connector exec jira /search GET '{"jql":"assignee=currentUser()","maxResults":20}'
|
|
167
|
+
$ blink connector exec stripe /customers GET '{"limit":5}'
|
|
168
|
+
$ blink connector exec shopify /orders.json GET
|
|
169
|
+
$ blink connector exec zoom /users/me/meetings GET
|
|
170
|
+
$ blink connector exec github /user/repos GET --json | jq '.[].full_name'
|
|
171
|
+
|
|
172
|
+
Examples (GraphQL — Linear only):
|
|
173
|
+
$ blink connector exec linear '{ viewer { id name email } }' POST
|
|
174
|
+
$ blink connector exec linear 'mutation { issueCreate(input: { title: "Bug", teamId: "xxx" }) { issue { id } } }' POST
|
|
175
|
+
|
|
176
|
+
Multiple accounts:
|
|
177
|
+
$ blink connector exec github /user/repos GET --account acct_xxx
|
|
178
|
+
|
|
179
|
+
Provider base URLs used:
|
|
180
|
+
github https://api.github.com/
|
|
181
|
+
stripe https://api.stripe.com/v1/
|
|
182
|
+
notion https://api.notion.com/v1/
|
|
183
|
+
slack https://slack.com/api/
|
|
184
|
+
google_gmail https://gmail.googleapis.com/gmail/v1/
|
|
185
|
+
google_drive https://www.googleapis.com/drive/v3/
|
|
186
|
+
google_calendar https://www.googleapis.com/calendar/v3/
|
|
187
|
+
jira https://api.atlassian.com/ex/jira/{cloudId}/rest/api/3/
|
|
188
|
+
shopify https://{shop}.myshopify.com/admin/api/2024-10/
|
|
189
|
+
... run \`blink connector providers\` for all 38 providers
|
|
41
190
|
`)
|
|
42
|
-
.action(async (provider: string,
|
|
191
|
+
.action(async (provider: string, endpoint: string, paramsArg: string | undefined, opts) => {
|
|
43
192
|
requireToken()
|
|
44
193
|
let params: Record<string, unknown> = {}
|
|
45
194
|
if (paramsArg) {
|
|
46
195
|
try { params = JSON.parse(paramsArg) } catch { params = {} }
|
|
47
196
|
}
|
|
48
|
-
const result = await withSpinner(
|
|
197
|
+
const result = await withSpinner(`${opts.method} ${provider}${endpoint}...`, () =>
|
|
49
198
|
resourcesRequest(`/v1/connectors/${provider}/execute`, {
|
|
50
199
|
body: {
|
|
51
|
-
method:
|
|
200
|
+
method: endpoint,
|
|
52
201
|
http_method: opts.method,
|
|
53
202
|
params,
|
|
54
203
|
...(opts.account ? { account_id: opts.account } : {}),
|
|
@@ -56,6 +205,6 @@ Examples:
|
|
|
56
205
|
})
|
|
57
206
|
)
|
|
58
207
|
if (isJsonMode()) return printJson(result)
|
|
59
|
-
console.log(JSON.stringify(result, null, 2))
|
|
208
|
+
console.log(JSON.stringify(result?.data ?? result, null, 2))
|
|
60
209
|
})
|
|
61
210
|
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { resourcesRequest } from '../lib/api-resources.js'
|
|
3
|
+
import { requireToken } from '../lib/auth.js'
|
|
4
|
+
import { requireAgentId } from '../lib/agent.js'
|
|
5
|
+
import { printJson, isJsonMode, withSpinner } from '../lib/output.js'
|
|
6
|
+
import chalk from 'chalk'
|
|
7
|
+
|
|
8
|
+
const NOT_LINKED = 'LinkedIn not linked. Link it in the Agent Integrations tab at blink.new/claw'
|
|
9
|
+
|
|
10
|
+
async function liExec(
|
|
11
|
+
method: string,
|
|
12
|
+
httpMethod: string,
|
|
13
|
+
params: Record<string, unknown>,
|
|
14
|
+
agentId: string
|
|
15
|
+
) {
|
|
16
|
+
const result = await resourcesRequest('/v1/connectors/linkedin/execute', {
|
|
17
|
+
body: { method, http_method: httpMethod, params },
|
|
18
|
+
headers: { 'x-blink-agent-id': agentId },
|
|
19
|
+
})
|
|
20
|
+
if (!result?.success) throw new Error(result?.error ?? NOT_LINKED)
|
|
21
|
+
return result.data
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function getPersonId(agentId: string): Promise<string> {
|
|
25
|
+
const data = await liExec('/me', 'GET', {}, agentId)
|
|
26
|
+
const id = data?.id
|
|
27
|
+
if (!id) throw new Error('Could not resolve LinkedIn person ID from /me response')
|
|
28
|
+
return id
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function registerLinkedInCommands(program: Command) {
|
|
32
|
+
const li = program.command('linkedin')
|
|
33
|
+
.description('LinkedIn connector — post content, manage comments, and view your profile')
|
|
34
|
+
.addHelpText('after', `
|
|
35
|
+
LinkedIn must be linked to your agent via the Integrations tab at blink.new/claw.
|
|
36
|
+
Agent ID defaults to BLINK_AGENT_ID (automatically set on Claw Fly machines).
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
$ blink linkedin me Show your LinkedIn profile
|
|
40
|
+
$ blink linkedin posts List your 10 most recent posts
|
|
41
|
+
$ blink linkedin post "Excited to announce..." Publish a text post
|
|
42
|
+
$ blink linkedin comments "urn:li:ugcPost:123" Read comments on a post
|
|
43
|
+
$ blink linkedin comment "urn:li:ugcPost:123" "Great post!" Add a comment
|
|
44
|
+
$ blink linkedin like "urn:li:ugcPost:123" Like a post
|
|
45
|
+
$ blink linkedin unlike "urn:li:ugcPost:123" Unlike a post
|
|
46
|
+
`)
|
|
47
|
+
|
|
48
|
+
// blink linkedin me
|
|
49
|
+
li.command('me')
|
|
50
|
+
.description('Show your LinkedIn profile (name, ID, vanity URL)')
|
|
51
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
52
|
+
.addHelpText('after', `
|
|
53
|
+
Examples:
|
|
54
|
+
$ blink linkedin me
|
|
55
|
+
$ blink linkedin me --json
|
|
56
|
+
$ blink linkedin me --agent clw_xxx
|
|
57
|
+
`)
|
|
58
|
+
.action(async (opts) => {
|
|
59
|
+
requireToken()
|
|
60
|
+
const agentId = requireAgentId(opts.agent)
|
|
61
|
+
const data = await withSpinner('Fetching LinkedIn profile...', () =>
|
|
62
|
+
liExec('/me', 'GET', {}, agentId)
|
|
63
|
+
)
|
|
64
|
+
if (isJsonMode()) return printJson(data)
|
|
65
|
+
const name = [data?.localizedFirstName, data?.localizedLastName].filter(Boolean).join(' ')
|
|
66
|
+
console.log(chalk.bold('LinkedIn Profile'))
|
|
67
|
+
console.log(` ${chalk.dim('ID:')} ${data?.id ?? '—'}`)
|
|
68
|
+
if (name) console.log(` ${chalk.dim('Name:')} ${name}`)
|
|
69
|
+
if (data?.vanityName) console.log(` ${chalk.dim('URL:')} https://linkedin.com/in/${data.vanityName}`)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// blink linkedin posts
|
|
73
|
+
li.command('posts')
|
|
74
|
+
.description('List your most recent LinkedIn posts (last 10)')
|
|
75
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
76
|
+
.option('--limit <n>', 'Number of posts to fetch (default: 10)', '10')
|
|
77
|
+
.addHelpText('after', `
|
|
78
|
+
Examples:
|
|
79
|
+
$ blink linkedin posts
|
|
80
|
+
$ blink linkedin posts --limit 5
|
|
81
|
+
$ blink linkedin posts --json | jq '.[].id'
|
|
82
|
+
`)
|
|
83
|
+
.action(async (opts) => {
|
|
84
|
+
requireToken()
|
|
85
|
+
const agentId = requireAgentId(opts.agent)
|
|
86
|
+
const personId = await withSpinner('Resolving your LinkedIn identity...', () =>
|
|
87
|
+
getPersonId(agentId)
|
|
88
|
+
)
|
|
89
|
+
const urn = encodeURIComponent(`urn:li:person:${personId}`)
|
|
90
|
+
const data = await withSpinner('Fetching posts...', () =>
|
|
91
|
+
liExec(
|
|
92
|
+
`/ugcPosts?q=authors&authors=List(${urn})&sortBy=LAST_MODIFIED&count=${opts.limit}`,
|
|
93
|
+
'GET',
|
|
94
|
+
{},
|
|
95
|
+
agentId
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
const posts: Array<Record<string, unknown>> = data?.elements ?? (Array.isArray(data) ? data : [])
|
|
99
|
+
if (isJsonMode()) return printJson(posts)
|
|
100
|
+
if (!posts.length) { console.log(chalk.dim('No posts found.')); return }
|
|
101
|
+
for (const post of posts) {
|
|
102
|
+
const id = (post.id ?? '—') as string
|
|
103
|
+
const content = post.specificContent as Record<string, unknown> | undefined
|
|
104
|
+
const share = content?.['com.linkedin.ugc.ShareContent'] as Record<string, unknown> | undefined
|
|
105
|
+
const commentary = share?.shareCommentary as Record<string, unknown> | undefined
|
|
106
|
+
const text = (commentary?.text ?? (post.text as Record<string, unknown>)?.text ?? '(no text)') as string
|
|
107
|
+
const preview = text.length > 120 ? text.slice(0, 120) + '…' : text
|
|
108
|
+
console.log(chalk.bold(id))
|
|
109
|
+
console.log(` ${chalk.dim(preview)}`)
|
|
110
|
+
console.log()
|
|
111
|
+
}
|
|
112
|
+
console.log(chalk.dim(`${posts.length} post(s)`))
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// blink linkedin post "text"
|
|
116
|
+
li.command('post <text>')
|
|
117
|
+
.description('Publish a text post to LinkedIn')
|
|
118
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
119
|
+
.option('--visibility <vis>', 'Post visibility: PUBLIC | CONNECTIONS (default: PUBLIC)', 'PUBLIC')
|
|
120
|
+
.addHelpText('after', `
|
|
121
|
+
Examples:
|
|
122
|
+
$ blink linkedin post "Excited to share our latest update!"
|
|
123
|
+
$ blink linkedin post "Internal update" --visibility CONNECTIONS
|
|
124
|
+
$ blink linkedin post "Hello LinkedIn" --json
|
|
125
|
+
`)
|
|
126
|
+
.action(async (text: string, opts) => {
|
|
127
|
+
requireToken()
|
|
128
|
+
const agentId = requireAgentId(opts.agent)
|
|
129
|
+
const data = await withSpinner('Publishing post...', () =>
|
|
130
|
+
liExec('/ugcPosts', 'POST', { text, visibility: opts.visibility }, agentId)
|
|
131
|
+
)
|
|
132
|
+
if (isJsonMode()) return printJson(data)
|
|
133
|
+
console.log(chalk.green('✓ Post published'))
|
|
134
|
+
if (data?.id) console.log(chalk.dim(` URN: ${data.id}`))
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// blink linkedin comments <postUrn>
|
|
138
|
+
li.command('comments <postUrn>')
|
|
139
|
+
.description('Read comments on a LinkedIn post')
|
|
140
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
141
|
+
.addHelpText('after', `
|
|
142
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
143
|
+
Use "blink linkedin posts" to find your post URNs.
|
|
144
|
+
|
|
145
|
+
Examples:
|
|
146
|
+
$ blink linkedin comments "urn:li:ugcPost:1234567890"
|
|
147
|
+
$ blink linkedin comments "urn:li:ugcPost:1234567890" --json
|
|
148
|
+
`)
|
|
149
|
+
.action(async (postUrn: string, opts) => {
|
|
150
|
+
requireToken()
|
|
151
|
+
const agentId = requireAgentId(opts.agent)
|
|
152
|
+
const encoded = encodeURIComponent(postUrn)
|
|
153
|
+
const data = await withSpinner('Fetching comments...', () =>
|
|
154
|
+
liExec(`rest/socialActions/${encoded}/comments`, 'GET', {}, agentId)
|
|
155
|
+
)
|
|
156
|
+
const comments: Array<Record<string, unknown>> = data?.elements ?? (Array.isArray(data) ? data : [])
|
|
157
|
+
if (isJsonMode()) return printJson(comments)
|
|
158
|
+
if (!comments.length) { console.log(chalk.dim('No comments.')); return }
|
|
159
|
+
for (const c of comments) {
|
|
160
|
+
const author = (c.actor ?? '—') as string
|
|
161
|
+
const msg = c.message as Record<string, unknown> | undefined
|
|
162
|
+
const text = (msg?.text ?? '(no text)') as string
|
|
163
|
+
console.log(chalk.bold(author))
|
|
164
|
+
console.log(` ${text}`)
|
|
165
|
+
console.log()
|
|
166
|
+
}
|
|
167
|
+
console.log(chalk.dim(`${comments.length} comment(s)`))
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// blink linkedin comment <postUrn> "text"
|
|
171
|
+
li.command('comment <postUrn> <text>')
|
|
172
|
+
.description('Add a comment to a LinkedIn post')
|
|
173
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
174
|
+
.addHelpText('after', `
|
|
175
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
176
|
+
|
|
177
|
+
Examples:
|
|
178
|
+
$ blink linkedin comment "urn:li:ugcPost:1234567890" "Great post!"
|
|
179
|
+
$ blink linkedin comment "urn:li:ugcPost:1234567890" "Thanks for sharing" --json
|
|
180
|
+
`)
|
|
181
|
+
.action(async (postUrn: string, text: string, opts) => {
|
|
182
|
+
requireToken()
|
|
183
|
+
const agentId = requireAgentId(opts.agent)
|
|
184
|
+
const personId = await withSpinner('Resolving your LinkedIn identity...', () =>
|
|
185
|
+
getPersonId(agentId)
|
|
186
|
+
)
|
|
187
|
+
const actor = `urn:li:person:${personId}`
|
|
188
|
+
const encoded = encodeURIComponent(postUrn)
|
|
189
|
+
const data = await withSpinner('Adding comment...', () =>
|
|
190
|
+
liExec(
|
|
191
|
+
`rest/socialActions/${encoded}/comments`,
|
|
192
|
+
'POST',
|
|
193
|
+
{ actor, object: postUrn, message: { text } },
|
|
194
|
+
agentId
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
if (isJsonMode()) return printJson(data)
|
|
198
|
+
console.log(chalk.green('✓ Comment added'))
|
|
199
|
+
if (data?.id) console.log(chalk.dim(` ID: ${data.id}`))
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
// blink linkedin like <postUrn>
|
|
203
|
+
li.command('like <postUrn>')
|
|
204
|
+
.description('Like a LinkedIn post')
|
|
205
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
206
|
+
.addHelpText('after', `
|
|
207
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
208
|
+
|
|
209
|
+
Examples:
|
|
210
|
+
$ blink linkedin like "urn:li:ugcPost:1234567890"
|
|
211
|
+
$ blink linkedin like "urn:li:ugcPost:1234567890" --json
|
|
212
|
+
`)
|
|
213
|
+
.action(async (postUrn: string, opts) => {
|
|
214
|
+
requireToken()
|
|
215
|
+
const agentId = requireAgentId(opts.agent)
|
|
216
|
+
const personId = await withSpinner('Resolving your LinkedIn identity...', () =>
|
|
217
|
+
getPersonId(agentId)
|
|
218
|
+
)
|
|
219
|
+
const actor = `urn:li:person:${personId}`
|
|
220
|
+
const encoded = encodeURIComponent(postUrn)
|
|
221
|
+
const data = await withSpinner('Liking post...', () =>
|
|
222
|
+
liExec(
|
|
223
|
+
`rest/socialActions/${encoded}/likes`,
|
|
224
|
+
'POST',
|
|
225
|
+
{ actor, object: postUrn },
|
|
226
|
+
agentId
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
if (isJsonMode()) return printJson(data)
|
|
230
|
+
console.log(chalk.green('✓ Post liked'))
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
// blink linkedin unlike <postUrn>
|
|
234
|
+
li.command('unlike <postUrn>')
|
|
235
|
+
.description('Unlike a LinkedIn post')
|
|
236
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
237
|
+
.addHelpText('after', `
|
|
238
|
+
<postUrn> is the LinkedIn post URN, e.g. urn:li:ugcPost:1234567890
|
|
239
|
+
|
|
240
|
+
Examples:
|
|
241
|
+
$ blink linkedin unlike "urn:li:ugcPost:1234567890"
|
|
242
|
+
$ blink linkedin unlike "urn:li:ugcPost:1234567890" --json
|
|
243
|
+
`)
|
|
244
|
+
.action(async (postUrn: string, opts) => {
|
|
245
|
+
requireToken()
|
|
246
|
+
const agentId = requireAgentId(opts.agent)
|
|
247
|
+
const personId = await withSpinner('Resolving your LinkedIn identity...', () =>
|
|
248
|
+
getPersonId(agentId)
|
|
249
|
+
)
|
|
250
|
+
const actor = `urn:li:person:${personId}`
|
|
251
|
+
const encodedPost = encodeURIComponent(postUrn)
|
|
252
|
+
const encodedActor = encodeURIComponent(actor)
|
|
253
|
+
// LinkedIn DELETE likes requires: DELETE /rest/socialActions/{postUrn}/likes/{actorUrn}?actor={actorUrn}
|
|
254
|
+
const data = await withSpinner('Unliking post...', () =>
|
|
255
|
+
liExec(
|
|
256
|
+
`rest/socialActions/${encodedPost}/likes/${encodedActor}?actor=${encodedActor}`,
|
|
257
|
+
'DELETE',
|
|
258
|
+
{},
|
|
259
|
+
agentId
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
if (isJsonMode()) return printJson(data)
|
|
263
|
+
console.log(chalk.green('✓ Post unliked'))
|
|
264
|
+
})
|
|
265
|
+
}
|