@blinkdotnew/cli 0.3.0 → 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 +272 -44
- package/package.json +1 -1
- package/src/cli.ts +8 -0
- package/src/commands/linkedin.ts +102 -42
- package/src/commands/phone.ts +177 -0
- package/src/commands/storage.ts +28 -12
package/dist/cli.js
CHANGED
|
@@ -886,9 +886,21 @@ Examples:
|
|
|
886
886
|
for (const f of files) table.push([f.name, f.size ? `${(f.size / 1024).toFixed(1)} KB` : "-"]);
|
|
887
887
|
console.log(table.toString());
|
|
888
888
|
});
|
|
889
|
-
storage.command("download <
|
|
889
|
+
storage.command("download <arg1> [arg2] [output]").description("Download a file from storage \u2014 blink storage download <path> [output] OR blink storage download <proj> <path> [output]").action(async (arg1, arg2, output) => {
|
|
890
890
|
requireToken();
|
|
891
|
-
|
|
891
|
+
let projectId;
|
|
892
|
+
let storagePath;
|
|
893
|
+
if (arg2 !== void 0 && !arg1.startsWith("proj_")) {
|
|
894
|
+
projectId = requireProjectId();
|
|
895
|
+
storagePath = arg1;
|
|
896
|
+
output = arg2;
|
|
897
|
+
} else if (arg2 !== void 0) {
|
|
898
|
+
projectId = requireProjectId(arg1);
|
|
899
|
+
storagePath = arg2;
|
|
900
|
+
} else {
|
|
901
|
+
projectId = requireProjectId();
|
|
902
|
+
storagePath = arg1;
|
|
903
|
+
}
|
|
892
904
|
const result = await withSpinner(
|
|
893
905
|
"Downloading...",
|
|
894
906
|
() => resourcesRequest(`/api/storage/${projectId}/download`, { body: { path: storagePath } })
|
|
@@ -898,9 +910,17 @@ Examples:
|
|
|
898
910
|
else writeFileSync4(outFile, Buffer.from(result?.data ?? "", "base64"));
|
|
899
911
|
if (!isJsonMode()) console.log("Saved to " + outFile);
|
|
900
912
|
});
|
|
901
|
-
storage.command("delete <
|
|
913
|
+
storage.command("delete <arg1> [arg2]").description("Delete a file from storage \u2014 blink storage delete <path> OR blink storage delete <proj> <path>").action(async (arg1, arg2) => {
|
|
902
914
|
requireToken();
|
|
903
|
-
|
|
915
|
+
let projectId;
|
|
916
|
+
let storagePath;
|
|
917
|
+
if (arg2 !== void 0) {
|
|
918
|
+
projectId = requireProjectId(arg1);
|
|
919
|
+
storagePath = arg2;
|
|
920
|
+
} else {
|
|
921
|
+
projectId = requireProjectId();
|
|
922
|
+
storagePath = arg1;
|
|
923
|
+
}
|
|
904
924
|
await withSpinner(
|
|
905
925
|
"Deleting...",
|
|
906
926
|
() => resourcesRequest(`/api/storage/${projectId}/remove`, { method: "DELETE", body: { path: storagePath } })
|
|
@@ -908,9 +928,17 @@ Examples:
|
|
|
908
928
|
if (!isJsonMode()) console.log("Deleted: " + storagePath);
|
|
909
929
|
else printJson({ status: "ok", path: storagePath });
|
|
910
930
|
});
|
|
911
|
-
storage.command("url <
|
|
931
|
+
storage.command("url <arg1> [arg2]").description("Get public URL for a storage file \u2014 blink storage url <path> OR blink storage url <proj> <path>").action(async (arg1, arg2) => {
|
|
912
932
|
requireToken();
|
|
913
|
-
|
|
933
|
+
let projectId;
|
|
934
|
+
let storagePath;
|
|
935
|
+
if (arg2 !== void 0) {
|
|
936
|
+
projectId = requireProjectId(arg1);
|
|
937
|
+
storagePath = arg2;
|
|
938
|
+
} else {
|
|
939
|
+
projectId = requireProjectId();
|
|
940
|
+
storagePath = arg1;
|
|
941
|
+
}
|
|
914
942
|
const result = await resourcesRequest(`/api/storage/${projectId}/public-url`, { body: { path: storagePath } });
|
|
915
943
|
if (isJsonMode()) return printJson(result);
|
|
916
944
|
console.log(result?.url ?? result);
|
|
@@ -1320,30 +1348,40 @@ async function getPersonId(agentId) {
|
|
|
1320
1348
|
return id;
|
|
1321
1349
|
}
|
|
1322
1350
|
function registerLinkedInCommands(program2) {
|
|
1323
|
-
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", `
|
|
1324
1352
|
LinkedIn must be linked to your agent via the Integrations tab at blink.new/claw.
|
|
1325
1353
|
Agent ID defaults to BLINK_AGENT_ID (automatically set on Claw Fly machines).
|
|
1326
1354
|
|
|
1327
1355
|
What works today (w_member_social scope):
|
|
1328
1356
|
\u2705 blink linkedin me Show your LinkedIn profile
|
|
1329
|
-
\u2705 blink linkedin post "
|
|
1357
|
+
\u2705 blink linkedin post "text" Publish a text post
|
|
1330
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
|
|
1362
|
+
|
|
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.
|
|
1331
1369
|
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
\u2717 Adding comments (needs Community Management API)
|
|
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...
|
|
1335
1372
|
|
|
1336
1373
|
Examples:
|
|
1337
1374
|
$ blink linkedin me
|
|
1338
|
-
$ blink linkedin post "Our product just launched!"
|
|
1339
|
-
$ blink linkedin
|
|
1340
|
-
$ blink linkedin
|
|
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"
|
|
1341
1379
|
`);
|
|
1342
1380
|
li.command("me").description("Show your LinkedIn profile (name, ID, email)").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1343
1381
|
Examples:
|
|
1344
1382
|
$ blink linkedin me
|
|
1345
1383
|
$ blink linkedin me --json
|
|
1346
|
-
$ blink linkedin me --
|
|
1384
|
+
$ PERSON_ID=$(blink linkedin me --json | jq -r .sub)
|
|
1347
1385
|
`).action(async (opts) => {
|
|
1348
1386
|
requireToken();
|
|
1349
1387
|
const agentId = requireAgentId(opts.agent);
|
|
@@ -1360,10 +1398,12 @@ Examples:
|
|
|
1360
1398
|
if (data?.email) console.log(` ${chalk7.dim("Email:")} ${data.email}`);
|
|
1361
1399
|
});
|
|
1362
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
|
+
|
|
1363
1403
|
Examples:
|
|
1364
1404
|
$ blink linkedin post "Excited to share our latest update!"
|
|
1365
|
-
$ blink linkedin post "Internal
|
|
1366
|
-
$ 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)
|
|
1367
1407
|
`).action(async (text, opts) => {
|
|
1368
1408
|
requireToken();
|
|
1369
1409
|
const agentId = requireAgentId(opts.agent);
|
|
@@ -1395,46 +1435,227 @@ Examples:
|
|
|
1395
1435
|
console.log(chalk7.green("\u2713 Post published"));
|
|
1396
1436
|
if (data?.id) console.log(chalk7.dim(` URN: ${data.id}`));
|
|
1397
1437
|
});
|
|
1398
|
-
li.command("
|
|
1399
|
-
Returns an asset URN to use when composing a post with media via blink connector exec.
|
|
1400
|
-
|
|
1438
|
+
li.command("delete <postUrn>").description("Delete one of your LinkedIn posts").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1401
1439
|
Examples:
|
|
1402
|
-
$ blink linkedin
|
|
1403
|
-
|
|
1404
|
-
$ ASSET_URN=$(blink linkedin upload-media https://example.com/photo.jpg --json | python3 -c "import json,sys; print(json.load(sys.stdin)['asset_urn'])")
|
|
1405
|
-
`).action(async (mediaUrl, opts) => {
|
|
1440
|
+
$ blink linkedin delete "urn:li:share:1234567890"
|
|
1441
|
+
`).action(async (postUrn, opts) => {
|
|
1406
1442
|
requireToken();
|
|
1407
1443
|
const agentId = requireAgentId(opts.agent);
|
|
1408
|
-
const
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
headers: { "x-blink-agent-id": agentId }
|
|
1413
|
-
})
|
|
1444
|
+
const encoded = encodeURIComponent(postUrn);
|
|
1445
|
+
await withSpinner(
|
|
1446
|
+
"Deleting post...",
|
|
1447
|
+
() => liExec(`ugcPosts/${encoded}`, "DELETE", {}, agentId)
|
|
1414
1448
|
);
|
|
1415
|
-
if (isJsonMode()) return printJson(
|
|
1416
|
-
|
|
1417
|
-
if (assetUrn) {
|
|
1418
|
-
console.log(chalk7.green("\u2713 Upload complete"));
|
|
1419
|
-
console.log(chalk7.dim(` Asset URN: ${assetUrn}`));
|
|
1420
|
-
}
|
|
1449
|
+
if (isJsonMode()) return printJson({ deleted: true, urn: postUrn });
|
|
1450
|
+
console.log(chalk7.green("\u2713 Post deleted"));
|
|
1421
1451
|
});
|
|
1422
|
-
li.command("
|
|
1423
|
-
<postUrn>
|
|
1424
|
-
|
|
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
|
|
1425
1455
|
|
|
1426
1456
|
Examples:
|
|
1427
|
-
$ blink linkedin
|
|
1457
|
+
$ blink linkedin like "urn:li:share:1234567890"
|
|
1458
|
+
$ blink linkedin like "urn:li:activity:1234567890"
|
|
1428
1459
|
`).action(async (postUrn, opts) => {
|
|
1429
1460
|
requireToken();
|
|
1430
1461
|
const agentId = requireAgentId(opts.agent);
|
|
1462
|
+
const personId = await withSpinner(
|
|
1463
|
+
"Resolving LinkedIn identity...",
|
|
1464
|
+
() => getPersonId(agentId)
|
|
1465
|
+
);
|
|
1431
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}`);
|
|
1432
1489
|
await withSpinner(
|
|
1433
|
-
"
|
|
1434
|
-
() => liExec(`
|
|
1490
|
+
"Unliking post...",
|
|
1491
|
+
() => liExec(`v2/socialActions/${encodedPost}/likes/${encodedPerson}`, "DELETE", {}, agentId)
|
|
1435
1492
|
);
|
|
1436
|
-
if (isJsonMode()) return printJson({
|
|
1437
|
-
console.log(chalk7.green("\u2713 Post
|
|
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}`));
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// src/commands/phone.ts
|
|
1524
|
+
function formatPhone(num) {
|
|
1525
|
+
const m = num.match(/^\+1(\d{3})(\d{3})(\d{4})$/);
|
|
1526
|
+
return m ? `+1 (${m[1]}) ${m[2]}-${m[3]}` : num;
|
|
1527
|
+
}
|
|
1528
|
+
function statusDot(status) {
|
|
1529
|
+
return status === "active" ? "\u25CF" : status === "grace" ? "\u26A1" : "\u25CB";
|
|
1530
|
+
}
|
|
1531
|
+
function nextCharge(lastCharged) {
|
|
1532
|
+
if (!lastCharged) return "";
|
|
1533
|
+
const d = new Date(new Date(lastCharged).getTime() + 30 * 864e5);
|
|
1534
|
+
return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
|
|
1535
|
+
}
|
|
1536
|
+
function printRecords(records) {
|
|
1537
|
+
if (!records.length) {
|
|
1538
|
+
console.log("No phone numbers. Run: blink phone buy");
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
records.forEach((r, i) => {
|
|
1542
|
+
const label = r.label ? ` ${r.label}` : "";
|
|
1543
|
+
const primary = i === 0 && r.status === "active" ? " \u2605 primary" : "";
|
|
1544
|
+
const next = nextCharge(r.last_charged_at);
|
|
1545
|
+
const charge = next ? ` Next charge ${next}` : "";
|
|
1546
|
+
console.log(`${statusDot(r.status)} ${formatPhone(r.phone_number)}${label}${primary}`);
|
|
1547
|
+
console.log(` ${r.id} ${r.country}${r.area_code ? ` \xB7 ${r.area_code}` : ""}${charge}`);
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
function registerPhoneCommands(program2) {
|
|
1551
|
+
const phone = program2.command("phone").description("Manage workspace phone numbers for AI calling").addHelpText("after", `
|
|
1552
|
+
Commands:
|
|
1553
|
+
list List all workspace phone numbers
|
|
1554
|
+
buy Provision a new phone number
|
|
1555
|
+
label Update a number's label
|
|
1556
|
+
release Release (cancel) a phone number
|
|
1557
|
+
|
|
1558
|
+
Examples:
|
|
1559
|
+
$ blink phone list
|
|
1560
|
+
$ blink phone buy --label "Sales" --country US --area-code 415
|
|
1561
|
+
$ blink phone buy --label "Support"
|
|
1562
|
+
$ blink phone label wpn_abc123 "Support line"
|
|
1563
|
+
$ blink phone release wpn_abc123
|
|
1564
|
+
|
|
1565
|
+
Numbers cost 10 credits/month each. First charge is immediate on buy.
|
|
1566
|
+
Primary number (oldest active) is used by default for \`blink ai call\`.
|
|
1567
|
+
Use \`blink ai call --from +1XXXXXXXXXX\` to specify a different number.
|
|
1568
|
+
`);
|
|
1569
|
+
phone.action(async () => {
|
|
1570
|
+
requireToken();
|
|
1571
|
+
const records = await withSpinner(
|
|
1572
|
+
"Fetching phone numbers...",
|
|
1573
|
+
() => resourcesRequest("/api/v1/phone-numbers")
|
|
1574
|
+
);
|
|
1575
|
+
if (isJsonMode()) return printJson(records);
|
|
1576
|
+
printRecords(records);
|
|
1577
|
+
});
|
|
1578
|
+
phone.command("list").description("List all workspace phone numbers").addHelpText("after", `
|
|
1579
|
+
Examples:
|
|
1580
|
+
$ blink phone list
|
|
1581
|
+
$ blink phone list --json
|
|
1582
|
+
`).action(async () => {
|
|
1583
|
+
requireToken();
|
|
1584
|
+
const records = await withSpinner(
|
|
1585
|
+
"Fetching phone numbers...",
|
|
1586
|
+
() => resourcesRequest("/api/v1/phone-numbers")
|
|
1587
|
+
);
|
|
1588
|
+
if (isJsonMode()) return printJson(records);
|
|
1589
|
+
printRecords(records);
|
|
1590
|
+
});
|
|
1591
|
+
phone.command("buy").description("Provision a new phone number (10 credits/month)").option("--label <label>", 'Label for this number, e.g. "Sales" or "Support"').option("--country <code>", "Country code: US, GB, CA, AU", "US").option("--area-code <code>", "Preferred area code (US/CA only, e.g. 415)").addHelpText("after", `
|
|
1592
|
+
Examples:
|
|
1593
|
+
$ blink phone buy
|
|
1594
|
+
$ blink phone buy --label "Sales" --area-code 415
|
|
1595
|
+
$ blink phone buy --label "UK Support" --country GB
|
|
1596
|
+
$ blink phone buy --json | jq '.phone_number'
|
|
1597
|
+
|
|
1598
|
+
10 credits charged immediately, then monthly on anniversary.
|
|
1599
|
+
`).action(async (opts) => {
|
|
1600
|
+
requireToken();
|
|
1601
|
+
const result = await withSpinner(
|
|
1602
|
+
"Provisioning phone number...",
|
|
1603
|
+
() => resourcesRequest("/api/v1/phone-numbers", {
|
|
1604
|
+
body: {
|
|
1605
|
+
label: opts.label || void 0,
|
|
1606
|
+
country: opts.country,
|
|
1607
|
+
area_code: opts.areaCode || void 0
|
|
1608
|
+
}
|
|
1609
|
+
})
|
|
1610
|
+
);
|
|
1611
|
+
if (isJsonMode()) return printJson(result);
|
|
1612
|
+
console.log(`\u2713 Phone number provisioned`);
|
|
1613
|
+
console.log(` Number ${formatPhone(result.phone_number)}`);
|
|
1614
|
+
if (result.label) console.log(` Label ${result.label}`);
|
|
1615
|
+
console.log(` Country ${result.country}${result.area_code ? ` \xB7 Area ${result.area_code}` : ""}`);
|
|
1616
|
+
console.log(` ID ${result.id}`);
|
|
1617
|
+
console.log(` Billing 10 credits/month`);
|
|
1618
|
+
console.log();
|
|
1619
|
+
console.log(`Use it: blink ai call "+1..." "Your task" --from "${result.phone_number}"`);
|
|
1620
|
+
});
|
|
1621
|
+
phone.command("label <id> <label>").description("Set or update the label for a phone number").addHelpText("after", `
|
|
1622
|
+
Examples:
|
|
1623
|
+
$ blink phone label wpn_abc123 "Sales"
|
|
1624
|
+
$ blink phone label wpn_abc123 "" Clear label
|
|
1625
|
+
`).action(async (id, label) => {
|
|
1626
|
+
requireToken();
|
|
1627
|
+
const result = await withSpinner(
|
|
1628
|
+
"Updating label...",
|
|
1629
|
+
() => resourcesRequest(`/api/v1/phone-numbers/${id}`, {
|
|
1630
|
+
method: "PATCH",
|
|
1631
|
+
body: { label }
|
|
1632
|
+
})
|
|
1633
|
+
);
|
|
1634
|
+
if (isJsonMode()) return printJson(result);
|
|
1635
|
+
console.log(`\u2713 Label updated: ${formatPhone(result.phone_number)} \u2192 "${result.label ?? ""}"`);
|
|
1636
|
+
});
|
|
1637
|
+
phone.command("release <id>").description("Release a phone number (permanent)").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", `
|
|
1638
|
+
Examples:
|
|
1639
|
+
$ blink phone release wpn_abc123
|
|
1640
|
+
$ blink phone release wpn_abc123 --yes
|
|
1641
|
+
|
|
1642
|
+
The number is permanently returned to the carrier pool. This action cannot be undone.
|
|
1643
|
+
`).action(async (id, opts) => {
|
|
1644
|
+
requireToken();
|
|
1645
|
+
if (!opts.yes && !isJsonMode()) {
|
|
1646
|
+
const { confirm } = await import("@clack/prompts");
|
|
1647
|
+
const yes = await confirm({ message: `Release ${id}? This cannot be undone.` });
|
|
1648
|
+
if (!yes) {
|
|
1649
|
+
console.log("Cancelled.");
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
await withSpinner(
|
|
1654
|
+
"Releasing phone number...",
|
|
1655
|
+
() => resourcesRequest(`/api/v1/phone-numbers/${id}`, { method: "DELETE" })
|
|
1656
|
+
);
|
|
1657
|
+
if (isJsonMode()) return printJson({ success: true, id });
|
|
1658
|
+
console.log(`\u2713 Phone number ${id} released`);
|
|
1438
1659
|
});
|
|
1439
1660
|
}
|
|
1440
1661
|
|
|
@@ -1981,6 +2202,12 @@ Realtime / RAG / Notify:
|
|
|
1981
2202
|
$ blink rag search "how does billing work" --ai
|
|
1982
2203
|
$ blink notify email user@example.com "Subject" "Body"
|
|
1983
2204
|
|
|
2205
|
+
Phone Numbers (10 credits/month per number):
|
|
2206
|
+
$ blink phone list List all workspace phone numbers
|
|
2207
|
+
$ blink phone buy --label Sales Buy a new number (US, UK, CA, AU)
|
|
2208
|
+
$ blink phone label <id> Sales Update label
|
|
2209
|
+
$ blink phone release <id> Release a number
|
|
2210
|
+
|
|
1984
2211
|
Connectors (38 OAuth providers \u2014 GitHub, Notion, Slack, Stripe, Shopify, Jira, Linear, and more):
|
|
1985
2212
|
$ blink connector providers List all 38 providers
|
|
1986
2213
|
$ blink connector status Show all connected accounts
|
|
@@ -2042,6 +2269,7 @@ registerRagCommands(program);
|
|
|
2042
2269
|
registerNotifyCommands(program);
|
|
2043
2270
|
registerConnectorCommands(program);
|
|
2044
2271
|
registerLinkedInCommands(program);
|
|
2272
|
+
registerPhoneCommands(program);
|
|
2045
2273
|
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", `
|
|
2046
2274
|
Examples:
|
|
2047
2275
|
$ blink use proj_xxx Shows the export command to run
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { registerRagCommands } from './commands/rag.js'
|
|
|
10
10
|
import { registerNotifyCommands } from './commands/notify.js'
|
|
11
11
|
import { registerConnectorCommands } from './commands/connector.js'
|
|
12
12
|
import { registerLinkedInCommands } from './commands/linkedin.js'
|
|
13
|
+
import { registerPhoneCommands } from './commands/phone.js'
|
|
13
14
|
import { registerDeployCommands } from './commands/deploy.js'
|
|
14
15
|
import { registerProjectCommands } from './commands/project.js'
|
|
15
16
|
import { registerAuthCommands } from './commands/auth.js'
|
|
@@ -79,6 +80,12 @@ Realtime / RAG / Notify:
|
|
|
79
80
|
$ blink rag search "how does billing work" --ai
|
|
80
81
|
$ blink notify email user@example.com "Subject" "Body"
|
|
81
82
|
|
|
83
|
+
Phone Numbers (10 credits/month per number):
|
|
84
|
+
$ blink phone list List all workspace phone numbers
|
|
85
|
+
$ blink phone buy --label Sales Buy a new number (US, UK, CA, AU)
|
|
86
|
+
$ blink phone label <id> Sales Update label
|
|
87
|
+
$ blink phone release <id> Release a number
|
|
88
|
+
|
|
82
89
|
Connectors (38 OAuth providers — GitHub, Notion, Slack, Stripe, Shopify, Jira, Linear, and more):
|
|
83
90
|
$ blink connector providers List all 38 providers
|
|
84
91
|
$ blink connector status Show all connected accounts
|
|
@@ -142,6 +149,7 @@ registerRagCommands(program)
|
|
|
142
149
|
registerNotifyCommands(program)
|
|
143
150
|
registerConnectorCommands(program)
|
|
144
151
|
registerLinkedInCommands(program)
|
|
152
|
+
registerPhoneCommands(program)
|
|
145
153
|
|
|
146
154
|
program.command('use <project_id>')
|
|
147
155
|
.description('Set active project for this shell session (alternative to blink link)')
|
package/src/commands/linkedin.ts
CHANGED
|
@@ -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 "
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
51
|
-
$ blink linkedin
|
|
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 --
|
|
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
|
|
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
|
|
121
|
-
li.command('
|
|
122
|
-
.description('
|
|
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
|
|
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 (
|
|
140
|
+
.action(async (postUrn: string, opts) => {
|
|
134
141
|
requireToken()
|
|
135
142
|
const agentId = requireAgentId(opts.agent)
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
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(
|
|
143
|
-
|
|
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
|
|
151
|
-
li.command('
|
|
152
|
-
.description('
|
|
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>
|
|
156
|
-
|
|
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
|
|
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('
|
|
166
|
-
liExec(`
|
|
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(
|
|
169
|
-
console.log(chalk.green('✓ Post
|
|
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
|
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { resourcesRequest } from '../lib/api-resources.js'
|
|
3
|
+
import { requireToken } from '../lib/auth.js'
|
|
4
|
+
import { printJson, isJsonMode, withSpinner } from '../lib/output.js'
|
|
5
|
+
|
|
6
|
+
interface PhoneRecord {
|
|
7
|
+
id: string
|
|
8
|
+
phone_number: string
|
|
9
|
+
label?: string | null
|
|
10
|
+
area_code?: string | null
|
|
11
|
+
country: string
|
|
12
|
+
status: string
|
|
13
|
+
last_charged_at?: string | null
|
|
14
|
+
created_at: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function formatPhone(num: string): string {
|
|
18
|
+
const m = num.match(/^\+1(\d{3})(\d{3})(\d{4})$/)
|
|
19
|
+
return m ? `+1 (${m[1]}) ${m[2]}-${m[3]}` : num
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function statusDot(status: string): string {
|
|
23
|
+
return status === 'active' ? '●' : status === 'grace' ? '⚡' : '○'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function nextCharge(lastCharged?: string | null): string {
|
|
27
|
+
if (!lastCharged) return ''
|
|
28
|
+
const d = new Date(new Date(lastCharged).getTime() + 30 * 86_400_000)
|
|
29
|
+
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function printRecords(records: PhoneRecord[]) {
|
|
33
|
+
if (!records.length) {
|
|
34
|
+
console.log('No phone numbers. Run: blink phone buy')
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
records.forEach((r, i) => {
|
|
38
|
+
const label = r.label ? ` ${r.label}` : ''
|
|
39
|
+
const primary = i === 0 && r.status === 'active' ? ' ★ primary' : ''
|
|
40
|
+
const next = nextCharge(r.last_charged_at)
|
|
41
|
+
const charge = next ? ` Next charge ${next}` : ''
|
|
42
|
+
console.log(`${statusDot(r.status)} ${formatPhone(r.phone_number)}${label}${primary}`)
|
|
43
|
+
console.log(` ${r.id} ${r.country}${r.area_code ? ` · ${r.area_code}` : ''}${charge}`)
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function registerPhoneCommands(program: Command) {
|
|
48
|
+
const phone = program.command('phone')
|
|
49
|
+
.description('Manage workspace phone numbers for AI calling')
|
|
50
|
+
.addHelpText('after', `
|
|
51
|
+
Commands:
|
|
52
|
+
list List all workspace phone numbers
|
|
53
|
+
buy Provision a new phone number
|
|
54
|
+
label Update a number's label
|
|
55
|
+
release Release (cancel) a phone number
|
|
56
|
+
|
|
57
|
+
Examples:
|
|
58
|
+
$ blink phone list
|
|
59
|
+
$ blink phone buy --label "Sales" --country US --area-code 415
|
|
60
|
+
$ blink phone buy --label "Support"
|
|
61
|
+
$ blink phone label wpn_abc123 "Support line"
|
|
62
|
+
$ blink phone release wpn_abc123
|
|
63
|
+
|
|
64
|
+
Numbers cost 10 credits/month each. First charge is immediate on buy.
|
|
65
|
+
Primary number (oldest active) is used by default for \`blink ai call\`.
|
|
66
|
+
Use \`blink ai call --from +1XXXXXXXXXX\` to specify a different number.
|
|
67
|
+
`)
|
|
68
|
+
|
|
69
|
+
// Default: list
|
|
70
|
+
phone.action(async () => {
|
|
71
|
+
requireToken()
|
|
72
|
+
const records = await withSpinner('Fetching phone numbers...', () =>
|
|
73
|
+
resourcesRequest('/api/v1/phone-numbers')
|
|
74
|
+
) as PhoneRecord[]
|
|
75
|
+
if (isJsonMode()) return printJson(records)
|
|
76
|
+
printRecords(records)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// blink phone list
|
|
80
|
+
phone.command('list')
|
|
81
|
+
.description('List all workspace phone numbers')
|
|
82
|
+
.addHelpText('after', `
|
|
83
|
+
Examples:
|
|
84
|
+
$ blink phone list
|
|
85
|
+
$ blink phone list --json
|
|
86
|
+
`)
|
|
87
|
+
.action(async () => {
|
|
88
|
+
requireToken()
|
|
89
|
+
const records = await withSpinner('Fetching phone numbers...', () =>
|
|
90
|
+
resourcesRequest('/api/v1/phone-numbers')
|
|
91
|
+
) as PhoneRecord[]
|
|
92
|
+
if (isJsonMode()) return printJson(records)
|
|
93
|
+
printRecords(records)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// blink phone buy
|
|
97
|
+
phone.command('buy')
|
|
98
|
+
.description('Provision a new phone number (10 credits/month)')
|
|
99
|
+
.option('--label <label>', 'Label for this number, e.g. "Sales" or "Support"')
|
|
100
|
+
.option('--country <code>', 'Country code: US, GB, CA, AU', 'US')
|
|
101
|
+
.option('--area-code <code>', 'Preferred area code (US/CA only, e.g. 415)')
|
|
102
|
+
.addHelpText('after', `
|
|
103
|
+
Examples:
|
|
104
|
+
$ blink phone buy
|
|
105
|
+
$ blink phone buy --label "Sales" --area-code 415
|
|
106
|
+
$ blink phone buy --label "UK Support" --country GB
|
|
107
|
+
$ blink phone buy --json | jq '.phone_number'
|
|
108
|
+
|
|
109
|
+
10 credits charged immediately, then monthly on anniversary.
|
|
110
|
+
`)
|
|
111
|
+
.action(async (opts) => {
|
|
112
|
+
requireToken()
|
|
113
|
+
const result = await withSpinner('Provisioning phone number...', () =>
|
|
114
|
+
resourcesRequest('/api/v1/phone-numbers', {
|
|
115
|
+
body: {
|
|
116
|
+
label: opts.label || undefined,
|
|
117
|
+
country: opts.country,
|
|
118
|
+
area_code: opts.areaCode || undefined,
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
) as PhoneRecord
|
|
122
|
+
if (isJsonMode()) return printJson(result)
|
|
123
|
+
console.log(`✓ Phone number provisioned`)
|
|
124
|
+
console.log(` Number ${formatPhone(result.phone_number)}`)
|
|
125
|
+
if (result.label) console.log(` Label ${result.label}`)
|
|
126
|
+
console.log(` Country ${result.country}${result.area_code ? ` · Area ${result.area_code}` : ''}`)
|
|
127
|
+
console.log(` ID ${result.id}`)
|
|
128
|
+
console.log(` Billing 10 credits/month`)
|
|
129
|
+
console.log()
|
|
130
|
+
console.log(`Use it: blink ai call "+1..." "Your task" --from "${result.phone_number}"`)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// blink phone label <id> <label>
|
|
134
|
+
phone.command('label <id> <label>')
|
|
135
|
+
.description('Set or update the label for a phone number')
|
|
136
|
+
.addHelpText('after', `
|
|
137
|
+
Examples:
|
|
138
|
+
$ blink phone label wpn_abc123 "Sales"
|
|
139
|
+
$ blink phone label wpn_abc123 "" Clear label
|
|
140
|
+
`)
|
|
141
|
+
.action(async (id: string, label: string) => {
|
|
142
|
+
requireToken()
|
|
143
|
+
const result = await withSpinner('Updating label...', () =>
|
|
144
|
+
resourcesRequest(`/api/v1/phone-numbers/${id}`, {
|
|
145
|
+
method: 'PATCH',
|
|
146
|
+
body: { label },
|
|
147
|
+
})
|
|
148
|
+
) as PhoneRecord
|
|
149
|
+
if (isJsonMode()) return printJson(result)
|
|
150
|
+
console.log(`✓ Label updated: ${formatPhone(result.phone_number)} → "${result.label ?? ''}"`)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// blink phone release <id>
|
|
154
|
+
phone.command('release <id>')
|
|
155
|
+
.description('Release a phone number (permanent)')
|
|
156
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
157
|
+
.addHelpText('after', `
|
|
158
|
+
Examples:
|
|
159
|
+
$ blink phone release wpn_abc123
|
|
160
|
+
$ blink phone release wpn_abc123 --yes
|
|
161
|
+
|
|
162
|
+
The number is permanently returned to the carrier pool. This action cannot be undone.
|
|
163
|
+
`)
|
|
164
|
+
.action(async (id: string, opts) => {
|
|
165
|
+
requireToken()
|
|
166
|
+
if (!opts.yes && !isJsonMode()) {
|
|
167
|
+
const { confirm } = await import('@clack/prompts')
|
|
168
|
+
const yes = await confirm({ message: `Release ${id}? This cannot be undone.` })
|
|
169
|
+
if (!yes) { console.log('Cancelled.'); return }
|
|
170
|
+
}
|
|
171
|
+
await withSpinner('Releasing phone number...', () =>
|
|
172
|
+
resourcesRequest(`/api/v1/phone-numbers/${id}`, { method: 'DELETE' })
|
|
173
|
+
)
|
|
174
|
+
if (isJsonMode()) return printJson({ success: true, id })
|
|
175
|
+
console.log(`✓ Phone number ${id} released`)
|
|
176
|
+
})
|
|
177
|
+
}
|
package/src/commands/storage.ts
CHANGED
|
@@ -80,11 +80,21 @@ Examples:
|
|
|
80
80
|
console.log(table.toString())
|
|
81
81
|
})
|
|
82
82
|
|
|
83
|
-
storage.command('download <
|
|
84
|
-
.description('Download a file from storage')
|
|
85
|
-
.action(async (
|
|
83
|
+
storage.command('download <arg1> [arg2] [output]')
|
|
84
|
+
.description('Download a file from storage — blink storage download <path> [output] OR blink storage download <proj> <path> [output]')
|
|
85
|
+
.action(async (arg1: string, arg2: string | undefined, output: string | undefined) => {
|
|
86
86
|
requireToken()
|
|
87
|
-
|
|
87
|
+
let projectId: string
|
|
88
|
+
let storagePath: string
|
|
89
|
+
if (arg2 !== undefined && !arg1.startsWith('proj_')) {
|
|
90
|
+
// blink storage download <path> <output> (no project_id)
|
|
91
|
+
projectId = requireProjectId(); storagePath = arg1; output = arg2
|
|
92
|
+
} else if (arg2 !== undefined) {
|
|
93
|
+
// blink storage download proj_xxx <path> [output]
|
|
94
|
+
projectId = requireProjectId(arg1); storagePath = arg2
|
|
95
|
+
} else {
|
|
96
|
+
projectId = requireProjectId(); storagePath = arg1
|
|
97
|
+
}
|
|
88
98
|
const result = await withSpinner('Downloading...', () =>
|
|
89
99
|
resourcesRequest(`/api/storage/${projectId}/download`, { body: { path: storagePath } })
|
|
90
100
|
)
|
|
@@ -94,11 +104,14 @@ Examples:
|
|
|
94
104
|
if (!isJsonMode()) console.log('Saved to ' + outFile)
|
|
95
105
|
})
|
|
96
106
|
|
|
97
|
-
storage.command('delete <
|
|
98
|
-
.description('Delete a file from storage')
|
|
99
|
-
.action(async (
|
|
107
|
+
storage.command('delete <arg1> [arg2]')
|
|
108
|
+
.description('Delete a file from storage — blink storage delete <path> OR blink storage delete <proj> <path>')
|
|
109
|
+
.action(async (arg1: string, arg2: string | undefined) => {
|
|
100
110
|
requireToken()
|
|
101
|
-
|
|
111
|
+
let projectId: string
|
|
112
|
+
let storagePath: string
|
|
113
|
+
if (arg2 !== undefined) { projectId = requireProjectId(arg1); storagePath = arg2 }
|
|
114
|
+
else { projectId = requireProjectId(); storagePath = arg1 }
|
|
102
115
|
await withSpinner('Deleting...', () =>
|
|
103
116
|
resourcesRequest(`/api/storage/${projectId}/remove`, { method: 'DELETE', body: { path: storagePath } })
|
|
104
117
|
)
|
|
@@ -106,11 +119,14 @@ Examples:
|
|
|
106
119
|
else printJson({ status: 'ok', path: storagePath })
|
|
107
120
|
})
|
|
108
121
|
|
|
109
|
-
storage.command('url <
|
|
110
|
-
.description('Get public URL for a storage file')
|
|
111
|
-
.action(async (
|
|
122
|
+
storage.command('url <arg1> [arg2]')
|
|
123
|
+
.description('Get public URL for a storage file — blink storage url <path> OR blink storage url <proj> <path>')
|
|
124
|
+
.action(async (arg1: string, arg2: string | undefined) => {
|
|
112
125
|
requireToken()
|
|
113
|
-
|
|
126
|
+
let projectId: string
|
|
127
|
+
let storagePath: string
|
|
128
|
+
if (arg2 !== undefined) { projectId = requireProjectId(arg1); storagePath = arg2 }
|
|
129
|
+
else { projectId = requireProjectId(); storagePath = arg1 }
|
|
114
130
|
const result = await resourcesRequest(`/api/storage/${projectId}/public-url`, { body: { path: storagePath } })
|
|
115
131
|
if (isJsonMode()) return printJson(result)
|
|
116
132
|
console.log(result?.url ?? result)
|