@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 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 payment of $240 from John Smith. Be polite but firm."
484
- $ blink ai call "+14155551234" "Confirm John's appointment for tomorrow at 3pm" --voice openai:nova
485
- $ blink ai call "+14155551234" "Leave a message about our new product launch" --no-wait
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(` Connect at blink.new/settings?tab=connectors`));
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
- var NOT_LINKED = "LinkedIn not linked. Link it in the Agent Integrations tab at blink.new/claw";
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 ?? NOT_LINKED);
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" --country US --area-code 415
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("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", `
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
- 10 credits charged immediately, then monthly on anniversary.
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
- "Provisioning phone number...",
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
- country: opts.country,
1607
- area_code: opts.areaCode || void 0
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
- console.log(` Country ${result.country}${result.area_code ? ` \xB7 Area ${result.area_code}` : ""}`);
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 10 credits/month`);
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
- if (!opts.yes && !isJsonMode()) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blinkdotnew/cli",
3
- "version": "0.3.2",
3
+ "version": "0.3.5",
4
4
  "description": "Blink platform CLI — deploy apps, manage databases, generate AI content",
5
5
  "bin": {
6
6
  "blink": "dist/cli.js"
@@ -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
  }
@@ -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 payment of $240 from John Smith. Be polite but firm."
297
- $ blink ai call "+14155551234" "Confirm John's appointment for tomorrow at 3pm" --voice openai:nova
298
- $ blink ai call "+14155551234" "Leave a message about our new product launch" --no-wait
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(` Connect at blink.new/settings?tab=connectors`))
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
@@ -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
- const NOT_LINKED = 'LinkedIn not linked. Link it in the Agent Integrations tab at blink.new/claw'
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 ?? NOT_LINKED)
32
+ if (!result?.success) throw new Error(result?.error ?? notLinkedError())
21
33
  return result.data
22
34
  }
23
35
 
@@ -56,7 +56,7 @@ Commands:
56
56
 
57
57
  Examples:
58
58
  $ blink phone list
59
- $ blink phone buy --label "Sales" --country US --area-code 415
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('Provision a new phone number (10 credits/month)')
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: US, GB, CA, AU', 'US')
101
- .option('--area-code <code>', 'Preferred area code (US/CA only, e.g. 415)')
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
- 10 credits charged immediately, then monthly on anniversary.
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
- const result = await withSpinner('Provisioning phone number...', () =>
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
- country: opts.country,
118
- area_code: opts.areaCode || undefined,
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
- console.log(` Country ${result.country}${result.area_code ? ` · Area ${result.area_code}` : ''}`)
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 10 credits/month`)
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
- if (!opts.yes && !isJsonMode()) {
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 }