@blinkdotnew/cli 0.3.2 → 0.3.5
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 +121 -19
- package/package.json +1 -1
- package/src/commands/agent.ts +44 -0
- package/src/commands/ai.ts +6 -4
- package/src/commands/connector.ts +47 -1
- package/src/commands/linkedin.ts +14 -2
- package/src/commands/phone.ts +33 -13
package/dist/cli.js
CHANGED
|
@@ -478,12 +478,12 @@ Supported formats: mp3, wav, m4a, ogg
|
|
|
478
478
|
if (isJsonMode()) return printJson(result);
|
|
479
479
|
console.log(result?.text ?? result?.transcript ?? JSON.stringify(result));
|
|
480
480
|
});
|
|
481
|
-
ai.command("call <phone-number> <system-prompt>").description("Make an AI phone call to any number (US/International)").option("--voice <voice>", "Voice: openai:alloy | openai:nova | cartesia:sonic-english", "openai:alloy").option("--max-duration <seconds>", "Max call duration in seconds", "300").option("--no-wait", "Return call_id immediately without waiting for completion").option("--from <number>", "Phone number to call from (E.164 format, e.g. +14155551234). Uses primary number if omitted.").addHelpText("after", `
|
|
481
|
+
ai.command("call <phone-number> <system-prompt>").description("Make an AI phone call to any number (US/International)").option("--voice <voice>", "Voice: openai:alloy | openai:nova | cartesia:sonic-english", "openai:alloy").option("--first-message <text>", 'What the AI says first when call connects (default: "Hello?")').option("--max-duration <seconds>", "Max call duration in seconds", "300").option("--no-wait", "Return call_id immediately without waiting for completion").option("--from <number>", "Phone number to call from (E.164 format, e.g. +14155551234). Uses primary number if omitted.").addHelpText("after", `
|
|
482
482
|
Examples:
|
|
483
|
-
$ blink ai call "+14155551234" "You are collecting a
|
|
484
|
-
$ blink ai call "+14155551234" "Confirm John's appointment
|
|
485
|
-
$ blink ai call "+14155551234" "
|
|
486
|
-
$ blink ai call "+14155551234" "Your task" --json | jq '.call_id'
|
|
483
|
+
$ blink ai call "+14155551234" "You are collecting a $240 payment from John Smith. Be polite." --first-message "Hi John, this is an automated call from Acme."
|
|
484
|
+
$ blink ai call "+14155551234" "Confirm John's appointment at 3pm" --first-message "Hello, calling to confirm your appointment."
|
|
485
|
+
$ blink ai call "+14155551234" "Collect feedback on recent order" --voice openai:nova
|
|
486
|
+
$ blink ai call "+14155551234" "Your task" --no-wait --json | jq '.call_id'
|
|
487
487
|
|
|
488
488
|
Phone number must be in E.164 format: +1XXXXXXXXXX (US), +44XXXXXXXXXX (UK), etc.
|
|
489
489
|
|
|
@@ -507,6 +507,7 @@ Use --from to specify which of your workspace numbers to call from.
|
|
|
507
507
|
system_prompt: systemPrompt,
|
|
508
508
|
voice: opts.voice,
|
|
509
509
|
max_duration_seconds: parseInt(opts.maxDuration),
|
|
510
|
+
...opts.firstMessage ? { first_message: opts.firstMessage } : {},
|
|
510
511
|
...opts.from ? { from_number: opts.from } : {}
|
|
511
512
|
}
|
|
512
513
|
})
|
|
@@ -1127,6 +1128,7 @@ The email is sent from your project's configured sender address (set in blink.ne
|
|
|
1127
1128
|
}
|
|
1128
1129
|
|
|
1129
1130
|
// src/commands/connector.ts
|
|
1131
|
+
init_agent();
|
|
1130
1132
|
import chalk6 from "chalk";
|
|
1131
1133
|
var PROVIDERS = [
|
|
1132
1134
|
// Communication
|
|
@@ -1233,8 +1235,10 @@ ${filtered.length} providers total. Connect at blink.new/settings?tab=connectors
|
|
|
1233
1235
|
if (d.metadata?.email) console.log(chalk6.dim(` Email: ${d.metadata.email}`));
|
|
1234
1236
|
if (d.metadata?.name) console.log(chalk6.dim(` Name: ${d.metadata.name}`));
|
|
1235
1237
|
} else {
|
|
1238
|
+
const agentUrl = process.env.BLINK_WORKSPACE_SLUG && process.env.BLINK_AGENT_ID ? `https://blink.new/${process.env.BLINK_WORKSPACE_SLUG}/claw/${process.env.BLINK_AGENT_ID}` : "https://blink.new/settings?tab=connectors";
|
|
1236
1239
|
console.log(chalk6.red("\u2717 Not connected"));
|
|
1237
|
-
console.log(chalk6.dim(`
|
|
1240
|
+
console.log(chalk6.dim(` Fix: blink connector link ${provider}`));
|
|
1241
|
+
console.log(chalk6.dim(` Or visit: ${agentUrl}`));
|
|
1238
1242
|
}
|
|
1239
1243
|
} else {
|
|
1240
1244
|
const result = await withSpinner(
|
|
@@ -1257,6 +1261,40 @@ ${filtered.length} providers total. Connect at blink.new/settings?tab=connectors
|
|
|
1257
1261
|
${connected.length} provider(s) connected`));
|
|
1258
1262
|
}
|
|
1259
1263
|
});
|
|
1264
|
+
connector.command("link <provider>").description("Auto-link a workspace connector to the current agent").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID)").addHelpText("after", `
|
|
1265
|
+
Links the workspace's connected account for <provider> to this agent automatically.
|
|
1266
|
+
The workspace must already have the account connected at blink.new/settings?tab=connectors.
|
|
1267
|
+
|
|
1268
|
+
This is the self-service fix when an agent gets "not linked" errors \u2014 run this once
|
|
1269
|
+
and the connector will be available for all subsequent commands.
|
|
1270
|
+
|
|
1271
|
+
Examples:
|
|
1272
|
+
$ blink connector link linkedin Link LinkedIn to this agent
|
|
1273
|
+
$ blink connector link notion Link Notion to this agent
|
|
1274
|
+
$ blink connector link slack Link Slack to this agent
|
|
1275
|
+
$ blink connector link linkedin --json Machine-readable output
|
|
1276
|
+
`).action(async (provider, opts) => {
|
|
1277
|
+
requireToken();
|
|
1278
|
+
const agentId = requireAgentId(opts.agent);
|
|
1279
|
+
const result = await withSpinner(
|
|
1280
|
+
`Linking ${provider} to agent ${agentId}...`,
|
|
1281
|
+
() => resourcesRequest(`/v1/connectors/${provider}/link`, {
|
|
1282
|
+
method: "POST",
|
|
1283
|
+
headers: { "x-blink-agent-id": agentId },
|
|
1284
|
+
body: {}
|
|
1285
|
+
})
|
|
1286
|
+
);
|
|
1287
|
+
if (!result?.success) {
|
|
1288
|
+
const agentUrl = process.env.BLINK_WORKSPACE_SLUG && process.env.BLINK_AGENT_ID ? `https://blink.new/${process.env.BLINK_WORKSPACE_SLUG}/claw/${process.env.BLINK_AGENT_ID}` : "https://blink.new/settings?tab=connectors";
|
|
1289
|
+
console.error(chalk6.red(`\u2717 ${result?.error ?? `No ${provider} account in workspace`}`));
|
|
1290
|
+
console.error(chalk6.dim(` Connect one first: ${agentUrl}`));
|
|
1291
|
+
process.exit(1);
|
|
1292
|
+
}
|
|
1293
|
+
if (isJsonMode()) return printJson(result.data);
|
|
1294
|
+
const label = result.data?.account_label ?? provider;
|
|
1295
|
+
console.log(chalk6.green(`\u2713 ${provider} linked`) + chalk6.dim(` (${label})`));
|
|
1296
|
+
console.log(chalk6.dim(` Agent ${agentId} can now use blink connector exec ${provider}`));
|
|
1297
|
+
});
|
|
1260
1298
|
connector.command("exec <provider> <endpoint> [method-or-params] [params]").description("Execute a call on a connected OAuth provider").option("--account <id>", "Specific account ID (if you have multiple accounts)").option("--method <method>", "HTTP method: GET | POST | PUT | PATCH | DELETE (default: POST)", "POST").addHelpText("after", `
|
|
1261
1299
|
<endpoint> is the API path relative to the provider's base URL, OR a GraphQL query string for Linear.
|
|
1262
1300
|
|
|
@@ -1332,13 +1370,24 @@ Provider base URLs used:
|
|
|
1332
1370
|
// src/commands/linkedin.ts
|
|
1333
1371
|
init_agent();
|
|
1334
1372
|
import chalk7 from "chalk";
|
|
1335
|
-
|
|
1373
|
+
function getAgentPageUrl() {
|
|
1374
|
+
const slug = process.env.BLINK_WORKSPACE_SLUG;
|
|
1375
|
+
const agentId = process.env.BLINK_AGENT_ID;
|
|
1376
|
+
if (slug && agentId) return `https://blink.new/${slug}/claw/${agentId}`;
|
|
1377
|
+
return "https://blink.new/claw";
|
|
1378
|
+
}
|
|
1379
|
+
function notLinkedError() {
|
|
1380
|
+
const url = getAgentPageUrl();
|
|
1381
|
+
return `LinkedIn not linked to this agent.
|
|
1382
|
+
Fix: blink connector link linkedin
|
|
1383
|
+
Or connect manually: ${url}`;
|
|
1384
|
+
}
|
|
1336
1385
|
async function liExec(method, httpMethod, params, agentId) {
|
|
1337
1386
|
const result = await resourcesRequest("/v1/connectors/linkedin/execute", {
|
|
1338
1387
|
body: { method, http_method: httpMethod, params },
|
|
1339
1388
|
headers: { "x-blink-agent-id": agentId }
|
|
1340
1389
|
});
|
|
1341
|
-
if (!result?.success) throw new Error(result?.error ??
|
|
1390
|
+
if (!result?.success) throw new Error(result?.error ?? notLinkedError());
|
|
1342
1391
|
return result.data;
|
|
1343
1392
|
}
|
|
1344
1393
|
async function getPersonId(agentId) {
|
|
@@ -1557,7 +1606,7 @@ Commands:
|
|
|
1557
1606
|
|
|
1558
1607
|
Examples:
|
|
1559
1608
|
$ blink phone list
|
|
1560
|
-
$ blink phone buy --label "Sales" --
|
|
1609
|
+
$ blink phone buy --label "Sales" --area-code 415
|
|
1561
1610
|
$ blink phone buy --label "Support"
|
|
1562
1611
|
$ blink phone label wpn_abc123 "Support line"
|
|
1563
1612
|
$ blink phone release wpn_abc123
|
|
@@ -1588,23 +1637,39 @@ Examples:
|
|
|
1588
1637
|
if (isJsonMode()) return printJson(records);
|
|
1589
1638
|
printRecords(records);
|
|
1590
1639
|
});
|
|
1591
|
-
phone.command("buy").description("
|
|
1640
|
+
phone.command("buy").description("Search and buy a phone number (25 credits/month)").option("--label <label>", 'Label for this number, e.g. "Sales" or "Support"').option("--country <code>", "Country code (e.g. US, GB, AU, DE)", "US").option("--area-code <code>", "Preferred area code (e.g. 415, 914, 020)").addHelpText("after", `
|
|
1592
1641
|
Examples:
|
|
1593
|
-
$ blink phone buy
|
|
1594
|
-
$ blink phone buy --label "Sales" --area-code 415
|
|
1642
|
+
$ blink phone buy --label "Sales" --area-code 914
|
|
1595
1643
|
$ blink phone buy --label "UK Support" --country GB
|
|
1644
|
+
$ blink phone buy --label "Germany" --country DE
|
|
1596
1645
|
$ blink phone buy --json | jq '.phone_number'
|
|
1597
1646
|
|
|
1598
|
-
|
|
1647
|
+
Searches real available numbers from Twilio, picks the first result.
|
|
1648
|
+
25 credits charged immediately, then monthly on anniversary.
|
|
1649
|
+
Supported countries: US, CA, GB, AU, NZ, DE, FR, NL, SE, NO, DK, FI, IE, BE, AT, CH, ES, IT, PT, PL, CZ, RO, HU, GR, JP, SG, HK, BR, MX, CL, CO, ZA, IL, PR
|
|
1599
1650
|
`).action(async (opts) => {
|
|
1600
1651
|
requireToken();
|
|
1652
|
+
const searchParams = new URLSearchParams({ country: opts.country ?? "US" });
|
|
1653
|
+
if (opts.areaCode) searchParams.set("area_code", opts.areaCode);
|
|
1654
|
+
const searchResult = await withSpinner(
|
|
1655
|
+
`Searching for ${opts.country ?? "US"} numbers...`,
|
|
1656
|
+
() => resourcesRequest(`/api/v1/phone-numbers/available?${searchParams}`)
|
|
1657
|
+
);
|
|
1658
|
+
if (!searchResult.numbers?.length) {
|
|
1659
|
+
process.stderr.write(`
|
|
1660
|
+
No numbers available${opts.areaCode ? ` in area code ${opts.areaCode}` : ""}. Try a different area code.
|
|
1661
|
+
`);
|
|
1662
|
+
process.exit(1);
|
|
1663
|
+
}
|
|
1664
|
+
const picked = searchResult.numbers[0];
|
|
1601
1665
|
const result = await withSpinner(
|
|
1602
|
-
|
|
1666
|
+
`Provisioning ${picked.phone_number}...`,
|
|
1603
1667
|
() => resourcesRequest("/api/v1/phone-numbers", {
|
|
1604
1668
|
body: {
|
|
1669
|
+
phone_number: picked.phone_number,
|
|
1605
1670
|
label: opts.label || void 0,
|
|
1606
|
-
|
|
1607
|
-
|
|
1671
|
+
locality: picked.locality || void 0,
|
|
1672
|
+
country: picked.country
|
|
1608
1673
|
}
|
|
1609
1674
|
})
|
|
1610
1675
|
);
|
|
@@ -1612,9 +1677,10 @@ Examples:
|
|
|
1612
1677
|
console.log(`\u2713 Phone number provisioned`);
|
|
1613
1678
|
console.log(` Number ${formatPhone(result.phone_number)}`);
|
|
1614
1679
|
if (result.label) console.log(` Label ${result.label}`);
|
|
1615
|
-
|
|
1680
|
+
const loc = result.locality;
|
|
1681
|
+
console.log(` Location ${loc ? `${loc} \xB7 ` : ""}${result.country}`);
|
|
1616
1682
|
console.log(` ID ${result.id}`);
|
|
1617
|
-
console.log(` Billing
|
|
1683
|
+
console.log(` Billing 25 credits/month`);
|
|
1618
1684
|
console.log();
|
|
1619
1685
|
console.log(`Use it: blink ai call "+1..." "Your task" --from "${result.phone_number}"`);
|
|
1620
1686
|
});
|
|
@@ -1642,7 +1708,8 @@ Examples:
|
|
|
1642
1708
|
The number is permanently returned to the carrier pool. This action cannot be undone.
|
|
1643
1709
|
`).action(async (id, opts) => {
|
|
1644
1710
|
requireToken();
|
|
1645
|
-
|
|
1711
|
+
const skipConfirm = process.argv.includes("--yes") || process.argv.includes("-y") || isJsonMode();
|
|
1712
|
+
if (!skipConfirm) {
|
|
1646
1713
|
const { confirm } = await import("@clack/prompts");
|
|
1647
1714
|
const yes = await confirm({ message: `Release ${id}? This cannot be undone.` });
|
|
1648
1715
|
if (!yes) {
|
|
@@ -2050,6 +2117,41 @@ Examples:
|
|
|
2050
2117
|
console.log(chalk11.bold("Machine ") + (a.machine_size ?? "-"));
|
|
2051
2118
|
if (a.fly_app_name) console.log(chalk11.bold("Fly App ") + a.fly_app_name);
|
|
2052
2119
|
});
|
|
2120
|
+
agent.command("url [agent_id]").description("Print the blink.new page URL for this agent (for sharing / setup links)").option("--agent <id>", "Agent ID (defaults to BLINK_AGENT_ID or BLINK_ACTIVE_AGENT)").addHelpText("after", `
|
|
2121
|
+
Returns the direct URL to this agent's page on blink.new.
|
|
2122
|
+
On Claw machines, uses BLINK_WORKSPACE_SLUG + BLINK_AGENT_ID env vars (no API call).
|
|
2123
|
+
Otherwise fetches workspace slug from the API.
|
|
2124
|
+
|
|
2125
|
+
Examples:
|
|
2126
|
+
$ blink agent url
|
|
2127
|
+
https://blink.new/kai/claw/clw_xxx
|
|
2128
|
+
|
|
2129
|
+
$ blink agent url --json
|
|
2130
|
+
{"url":"https://blink.new/kai/claw/clw_xxx","agent_id":"clw_xxx","workspace_slug":"kai"}
|
|
2131
|
+
`).action(async (agentIdArg, opts) => {
|
|
2132
|
+
requireToken();
|
|
2133
|
+
const agentId = requireAgentId(opts.agent ?? agentIdArg);
|
|
2134
|
+
const envSlug = process.env.BLINK_WORKSPACE_SLUG;
|
|
2135
|
+
if (envSlug) {
|
|
2136
|
+
const url2 = `https://blink.new/${envSlug}/claw/${agentId}`;
|
|
2137
|
+
if (isJsonMode()) return printJson({ url: url2, agent_id: agentId, workspace_slug: envSlug });
|
|
2138
|
+
console.log(url2);
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
const result = await withSpinner(
|
|
2142
|
+
"Fetching agent details...",
|
|
2143
|
+
() => appRequest(`/api/claw/agents/${agentId}`)
|
|
2144
|
+
);
|
|
2145
|
+
const a = result?.agent ?? result;
|
|
2146
|
+
const workspaceSlug = a?.workspace_slug ?? a?.workspace_id;
|
|
2147
|
+
if (!workspaceSlug) {
|
|
2148
|
+
console.error(chalk11.red("Could not determine workspace slug"));
|
|
2149
|
+
process.exit(1);
|
|
2150
|
+
}
|
|
2151
|
+
const url = `https://blink.new/${workspaceSlug}/claw/${agentId}`;
|
|
2152
|
+
if (isJsonMode()) return printJson({ url, agent_id: agentId, workspace_slug: workspaceSlug });
|
|
2153
|
+
console.log(url);
|
|
2154
|
+
});
|
|
2053
2155
|
}
|
|
2054
2156
|
|
|
2055
2157
|
// src/commands/secrets.ts
|
package/package.json
CHANGED
package/src/commands/agent.ts
CHANGED
|
@@ -86,4 +86,48 @@ Examples:
|
|
|
86
86
|
console.log(chalk.bold('Machine ') + (a.machine_size ?? '-'))
|
|
87
87
|
if (a.fly_app_name) console.log(chalk.bold('Fly App ') + a.fly_app_name)
|
|
88
88
|
})
|
|
89
|
+
|
|
90
|
+
// blink agent url
|
|
91
|
+
agent.command('url [agent_id]')
|
|
92
|
+
.description('Print the blink.new page URL for this agent (for sharing / setup links)')
|
|
93
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID or BLINK_ACTIVE_AGENT)')
|
|
94
|
+
.addHelpText('after', `
|
|
95
|
+
Returns the direct URL to this agent's page on blink.new.
|
|
96
|
+
On Claw machines, uses BLINK_WORKSPACE_SLUG + BLINK_AGENT_ID env vars (no API call).
|
|
97
|
+
Otherwise fetches workspace slug from the API.
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
$ blink agent url
|
|
101
|
+
https://blink.new/kai/claw/clw_xxx
|
|
102
|
+
|
|
103
|
+
$ blink agent url --json
|
|
104
|
+
{"url":"https://blink.new/kai/claw/clw_xxx","agent_id":"clw_xxx","workspace_slug":"kai"}
|
|
105
|
+
`)
|
|
106
|
+
.action(async (agentIdArg: string | undefined, opts) => {
|
|
107
|
+
requireToken()
|
|
108
|
+
const agentId = requireAgentId(opts.agent ?? agentIdArg)
|
|
109
|
+
|
|
110
|
+
// Fast path: env vars already set on Claw machines (no API call needed)
|
|
111
|
+
const envSlug = process.env.BLINK_WORKSPACE_SLUG
|
|
112
|
+
if (envSlug) {
|
|
113
|
+
const url = `https://blink.new/${envSlug}/claw/${agentId}`
|
|
114
|
+
if (isJsonMode()) return printJson({ url, agent_id: agentId, workspace_slug: envSlug })
|
|
115
|
+
console.log(url)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Fallback: fetch workspace slug from API
|
|
120
|
+
const result = await withSpinner('Fetching agent details...', () =>
|
|
121
|
+
appRequest(`/api/claw/agents/${agentId}`)
|
|
122
|
+
)
|
|
123
|
+
const a = result?.agent ?? result
|
|
124
|
+
const workspaceSlug = a?.workspace_slug ?? a?.workspace_id
|
|
125
|
+
if (!workspaceSlug) {
|
|
126
|
+
console.error(chalk.red('Could not determine workspace slug'))
|
|
127
|
+
process.exit(1)
|
|
128
|
+
}
|
|
129
|
+
const url = `https://blink.new/${workspaceSlug}/claw/${agentId}`
|
|
130
|
+
if (isJsonMode()) return printJson({ url, agent_id: agentId, workspace_slug: workspaceSlug })
|
|
131
|
+
console.log(url)
|
|
132
|
+
})
|
|
89
133
|
}
|
package/src/commands/ai.ts
CHANGED
|
@@ -288,15 +288,16 @@ Supported formats: mp3, wav, m4a, ogg
|
|
|
288
288
|
ai.command('call <phone-number> <system-prompt>')
|
|
289
289
|
.description('Make an AI phone call to any number (US/International)')
|
|
290
290
|
.option('--voice <voice>', 'Voice: openai:alloy | openai:nova | cartesia:sonic-english', 'openai:alloy')
|
|
291
|
+
.option('--first-message <text>', 'What the AI says first when call connects (default: "Hello?")')
|
|
291
292
|
.option('--max-duration <seconds>', 'Max call duration in seconds', '300')
|
|
292
293
|
.option('--no-wait', 'Return call_id immediately without waiting for completion')
|
|
293
294
|
.option('--from <number>', 'Phone number to call from (E.164 format, e.g. +14155551234). Uses primary number if omitted.')
|
|
294
295
|
.addHelpText('after', `
|
|
295
296
|
Examples:
|
|
296
|
-
$ blink ai call "+14155551234" "You are collecting a
|
|
297
|
-
$ blink ai call "+14155551234" "Confirm John's appointment
|
|
298
|
-
$ blink ai call "+14155551234" "
|
|
299
|
-
$ blink ai call "+14155551234" "Your task" --json | jq '.call_id'
|
|
297
|
+
$ blink ai call "+14155551234" "You are collecting a $240 payment from John Smith. Be polite." --first-message "Hi John, this is an automated call from Acme."
|
|
298
|
+
$ blink ai call "+14155551234" "Confirm John's appointment at 3pm" --first-message "Hello, calling to confirm your appointment."
|
|
299
|
+
$ blink ai call "+14155551234" "Collect feedback on recent order" --voice openai:nova
|
|
300
|
+
$ blink ai call "+14155551234" "Your task" --no-wait --json | jq '.call_id'
|
|
300
301
|
|
|
301
302
|
Phone number must be in E.164 format: +1XXXXXXXXXX (US), +44XXXXXXXXXX (UK), etc.
|
|
302
303
|
|
|
@@ -320,6 +321,7 @@ Use --from to specify which of your workspace numbers to call from.
|
|
|
320
321
|
system_prompt: systemPrompt,
|
|
321
322
|
voice: opts.voice,
|
|
322
323
|
max_duration_seconds: parseInt(opts.maxDuration),
|
|
324
|
+
...(opts.firstMessage ? { first_message: opts.firstMessage } : {}),
|
|
323
325
|
...(opts.from ? { from_number: opts.from } : {}),
|
|
324
326
|
},
|
|
325
327
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
import { resourcesRequest } from '../lib/api-resources.js'
|
|
3
3
|
import { requireToken } from '../lib/auth.js'
|
|
4
|
+
import { requireAgentId } from '../lib/agent.js'
|
|
4
5
|
import { printJson, isJsonMode, withSpinner, createTable } from '../lib/output.js'
|
|
5
6
|
import chalk from 'chalk'
|
|
6
7
|
|
|
@@ -124,8 +125,12 @@ Use --account <id> if you have multiple linked accounts for the same provider.
|
|
|
124
125
|
if (d.metadata?.email) console.log(chalk.dim(` Email: ${d.metadata.email}`))
|
|
125
126
|
if (d.metadata?.name) console.log(chalk.dim(` Name: ${d.metadata.name}`))
|
|
126
127
|
} else {
|
|
128
|
+
const agentUrl = process.env.BLINK_WORKSPACE_SLUG && process.env.BLINK_AGENT_ID
|
|
129
|
+
? `https://blink.new/${process.env.BLINK_WORKSPACE_SLUG}/claw/${process.env.BLINK_AGENT_ID}`
|
|
130
|
+
: 'https://blink.new/settings?tab=connectors'
|
|
127
131
|
console.log(chalk.red('✗ Not connected'))
|
|
128
|
-
console.log(chalk.dim(`
|
|
132
|
+
console.log(chalk.dim(` Fix: blink connector link ${provider}`))
|
|
133
|
+
console.log(chalk.dim(` Or visit: ${agentUrl}`))
|
|
129
134
|
}
|
|
130
135
|
} else {
|
|
131
136
|
// List all connected providers
|
|
@@ -149,6 +154,47 @@ Use --account <id> if you have multiple linked accounts for the same provider.
|
|
|
149
154
|
}
|
|
150
155
|
})
|
|
151
156
|
|
|
157
|
+
// blink connector link <provider>
|
|
158
|
+
connector.command('link <provider>')
|
|
159
|
+
.description('Auto-link a workspace connector to the current agent')
|
|
160
|
+
.option('--agent <id>', 'Agent ID (defaults to BLINK_AGENT_ID)')
|
|
161
|
+
.addHelpText('after', `
|
|
162
|
+
Links the workspace's connected account for <provider> to this agent automatically.
|
|
163
|
+
The workspace must already have the account connected at blink.new/settings?tab=connectors.
|
|
164
|
+
|
|
165
|
+
This is the self-service fix when an agent gets "not linked" errors — run this once
|
|
166
|
+
and the connector will be available for all subsequent commands.
|
|
167
|
+
|
|
168
|
+
Examples:
|
|
169
|
+
$ blink connector link linkedin Link LinkedIn to this agent
|
|
170
|
+
$ blink connector link notion Link Notion to this agent
|
|
171
|
+
$ blink connector link slack Link Slack to this agent
|
|
172
|
+
$ blink connector link linkedin --json Machine-readable output
|
|
173
|
+
`)
|
|
174
|
+
.action(async (provider: string, opts) => {
|
|
175
|
+
requireToken()
|
|
176
|
+
const agentId = requireAgentId(opts.agent)
|
|
177
|
+
const result = await withSpinner(`Linking ${provider} to agent ${agentId}...`, () =>
|
|
178
|
+
resourcesRequest(`/v1/connectors/${provider}/link`, {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers: { 'x-blink-agent-id': agentId },
|
|
181
|
+
body: {},
|
|
182
|
+
})
|
|
183
|
+
)
|
|
184
|
+
if (!result?.success) {
|
|
185
|
+
const agentUrl = process.env.BLINK_WORKSPACE_SLUG && process.env.BLINK_AGENT_ID
|
|
186
|
+
? `https://blink.new/${process.env.BLINK_WORKSPACE_SLUG}/claw/${process.env.BLINK_AGENT_ID}`
|
|
187
|
+
: 'https://blink.new/settings?tab=connectors'
|
|
188
|
+
console.error(chalk.red(`✗ ${result?.error ?? `No ${provider} account in workspace`}`))
|
|
189
|
+
console.error(chalk.dim(` Connect one first: ${agentUrl}`))
|
|
190
|
+
process.exit(1)
|
|
191
|
+
}
|
|
192
|
+
if (isJsonMode()) return printJson(result.data)
|
|
193
|
+
const label = result.data?.account_label ?? provider
|
|
194
|
+
console.log(chalk.green(`✓ ${provider} linked`) + chalk.dim(` (${label})`))
|
|
195
|
+
console.log(chalk.dim(` Agent ${agentId} can now use blink connector exec ${provider}`))
|
|
196
|
+
})
|
|
197
|
+
|
|
152
198
|
// blink connector exec <provider> <endpoint> [method-or-params] [params]
|
|
153
199
|
// Supports both patterns:
|
|
154
200
|
// blink connector exec github /user/repos GET
|
package/src/commands/linkedin.ts
CHANGED
|
@@ -5,7 +5,19 @@ import { printJson, isJsonMode, withSpinner } from '../lib/output.js'
|
|
|
5
5
|
import { resourcesRequest } from '../lib/api-resources.js'
|
|
6
6
|
import chalk from 'chalk'
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// Build agent page URL from env vars (set at machine creation via BLINK_WORKSPACE_SLUG).
|
|
9
|
+
// Falls back to generic URL if env vars aren't set (e.g. local dev).
|
|
10
|
+
function getAgentPageUrl(): string {
|
|
11
|
+
const slug = process.env.BLINK_WORKSPACE_SLUG
|
|
12
|
+
const agentId = process.env.BLINK_AGENT_ID
|
|
13
|
+
if (slug && agentId) return `https://blink.new/${slug}/claw/${agentId}`
|
|
14
|
+
return 'https://blink.new/claw'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function notLinkedError(): string {
|
|
18
|
+
const url = getAgentPageUrl()
|
|
19
|
+
return `LinkedIn not linked to this agent.\n Fix: blink connector link linkedin\n Or connect manually: ${url}`
|
|
20
|
+
}
|
|
9
21
|
|
|
10
22
|
async function liExec(
|
|
11
23
|
method: string,
|
|
@@ -17,7 +29,7 @@ async function liExec(
|
|
|
17
29
|
body: { method, http_method: httpMethod, params },
|
|
18
30
|
headers: { 'x-blink-agent-id': agentId },
|
|
19
31
|
})
|
|
20
|
-
if (!result?.success) throw new Error(result?.error ??
|
|
32
|
+
if (!result?.success) throw new Error(result?.error ?? notLinkedError())
|
|
21
33
|
return result.data
|
|
22
34
|
}
|
|
23
35
|
|
package/src/commands/phone.ts
CHANGED
|
@@ -56,7 +56,7 @@ Commands:
|
|
|
56
56
|
|
|
57
57
|
Examples:
|
|
58
58
|
$ blink phone list
|
|
59
|
-
$ blink phone buy --label "Sales" --
|
|
59
|
+
$ blink phone buy --label "Sales" --area-code 415
|
|
60
60
|
$ blink phone buy --label "Support"
|
|
61
61
|
$ blink phone label wpn_abc123 "Support line"
|
|
62
62
|
$ blink phone release wpn_abc123
|
|
@@ -95,27 +95,45 @@ Examples:
|
|
|
95
95
|
|
|
96
96
|
// blink phone buy
|
|
97
97
|
phone.command('buy')
|
|
98
|
-
.description('
|
|
98
|
+
.description('Search and buy a phone number (25 credits/month)')
|
|
99
99
|
.option('--label <label>', 'Label for this number, e.g. "Sales" or "Support"')
|
|
100
|
-
.option('--country <code>', 'Country code
|
|
101
|
-
.option('--area-code <code>', 'Preferred area code (
|
|
100
|
+
.option('--country <code>', 'Country code (e.g. US, GB, AU, DE)', 'US')
|
|
101
|
+
.option('--area-code <code>', 'Preferred area code (e.g. 415, 914, 020)')
|
|
102
102
|
.addHelpText('after', `
|
|
103
103
|
Examples:
|
|
104
|
-
$ blink phone buy
|
|
105
|
-
$ blink phone buy --label "Sales" --area-code 415
|
|
104
|
+
$ blink phone buy --label "Sales" --area-code 914
|
|
106
105
|
$ blink phone buy --label "UK Support" --country GB
|
|
106
|
+
$ blink phone buy --label "Germany" --country DE
|
|
107
107
|
$ blink phone buy --json | jq '.phone_number'
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
Searches real available numbers from Twilio, picks the first result.
|
|
110
|
+
25 credits charged immediately, then monthly on anniversary.
|
|
111
|
+
Supported countries: US, CA, GB, AU, NZ, DE, FR, NL, SE, NO, DK, FI, IE, BE, AT, CH, ES, IT, PT, PL, CZ, RO, HU, GR, JP, SG, HK, BR, MX, CL, CO, ZA, IL, PR
|
|
110
112
|
`)
|
|
111
113
|
.action(async (opts) => {
|
|
112
114
|
requireToken()
|
|
113
|
-
|
|
115
|
+
// Step 1: Search for available numbers
|
|
116
|
+
const searchParams = new URLSearchParams({ country: opts.country ?? 'US' })
|
|
117
|
+
if (opts.areaCode) searchParams.set('area_code', opts.areaCode)
|
|
118
|
+
const searchResult = await withSpinner(`Searching for ${opts.country ?? 'US'} numbers...`, () =>
|
|
119
|
+
resourcesRequest(`/api/v1/phone-numbers/available?${searchParams}`)
|
|
120
|
+
) as { numbers: Array<{ phone_number: string; locality?: string; region?: string; country: string }> }
|
|
121
|
+
|
|
122
|
+
if (!searchResult.numbers?.length) {
|
|
123
|
+
process.stderr.write(`\nNo numbers available${opts.areaCode ? ` in area code ${opts.areaCode}` : ''}. Try a different area code.\n`)
|
|
124
|
+
process.exit(1)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const picked = searchResult.numbers[0]
|
|
128
|
+
|
|
129
|
+
// Step 2: Provision the picked number
|
|
130
|
+
const result = await withSpinner(`Provisioning ${picked.phone_number}...`, () =>
|
|
114
131
|
resourcesRequest('/api/v1/phone-numbers', {
|
|
115
132
|
body: {
|
|
133
|
+
phone_number: picked.phone_number,
|
|
116
134
|
label: opts.label || undefined,
|
|
117
|
-
|
|
118
|
-
|
|
135
|
+
locality: picked.locality || undefined,
|
|
136
|
+
country: picked.country,
|
|
119
137
|
},
|
|
120
138
|
})
|
|
121
139
|
) as PhoneRecord
|
|
@@ -123,9 +141,10 @@ Examples:
|
|
|
123
141
|
console.log(`✓ Phone number provisioned`)
|
|
124
142
|
console.log(` Number ${formatPhone(result.phone_number)}`)
|
|
125
143
|
if (result.label) console.log(` Label ${result.label}`)
|
|
126
|
-
|
|
144
|
+
const loc = (result as any).locality
|
|
145
|
+
console.log(` Location ${loc ? `${loc} · ` : ''}${result.country}`)
|
|
127
146
|
console.log(` ID ${result.id}`)
|
|
128
|
-
console.log(` Billing
|
|
147
|
+
console.log(` Billing 25 credits/month`)
|
|
129
148
|
console.log()
|
|
130
149
|
console.log(`Use it: blink ai call "+1..." "Your task" --from "${result.phone_number}"`)
|
|
131
150
|
})
|
|
@@ -163,7 +182,8 @@ The number is permanently returned to the carrier pool. This action cannot be un
|
|
|
163
182
|
`)
|
|
164
183
|
.action(async (id: string, opts) => {
|
|
165
184
|
requireToken()
|
|
166
|
-
|
|
185
|
+
const skipConfirm = process.argv.includes('--yes') || process.argv.includes('-y') || isJsonMode()
|
|
186
|
+
if (!skipConfirm) {
|
|
167
187
|
const { confirm } = await import('@clack/prompts')
|
|
168
188
|
const yes = await confirm({ message: `Release ${id}? This cannot be undone.` })
|
|
169
189
|
if (!yes) { console.log('Cancelled.'); return }
|