@blinkdotnew/cli 0.3.5 → 0.3.7

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/AGENTS.md CHANGED
@@ -38,6 +38,7 @@ packages/cli/
38
38
  │ │ ├── db.ts # blink db query/exec/list → core.blink.new
39
39
  │ │ ├── deploy.ts # blink deploy / deployments / rollback → blink.new
40
40
  │ │ ├── notify.ts # blink notify email → core.blink.new
41
+ │ │ ├── sms.ts # blink sms send → core.blink.new
41
42
  │ │ ├── project.ts # blink project / blink link / blink status → blink.new
42
43
  │ │ ├── rag.ts # blink rag search/upload/collections → core.blink.new
43
44
  │ │ ├── realtime.ts # blink realtime publish → core.blink.new
@@ -144,6 +145,14 @@ blink notify email <project_id> <to> <subject> [body]
144
145
  blink notify email <to> <subject> --file ./body.html
145
146
  ```
146
147
 
148
+ ### SMS (→ core.blink.new /api/v1/sms/send)
149
+ ```bash
150
+ blink sms send <to> <message> # Send SMS from workspace primary number
151
+ blink sms send <to> <message> --from <number> # Send from specific workspace number
152
+ blink sms send <to> <message> --json # Machine-readable output
153
+ ```
154
+ Requires a provisioned workspace phone number (`blink phone buy`). 0.1 credits/message.
155
+
147
156
  ### Connectors (→ core.blink.new /api/v1/connectors/[provider]/execute)
148
157
  ```bash
149
158
  blink connector exec <provider> <action> [params-json] [--account <id>] [--method POST]
package/dist/cli.js CHANGED
@@ -197,7 +197,12 @@ async function resourcesRequest(path, opts = {}) {
197
197
  const errText = await res.text();
198
198
  let errMsg = `HTTP ${res.status}`;
199
199
  try {
200
- errMsg = JSON.parse(errText).error ?? errMsg;
200
+ const parsed = JSON.parse(errText);
201
+ const err = parsed.error;
202
+ if (typeof err === "string") errMsg = err;
203
+ else if (err?.message) errMsg = err.message;
204
+ else if (parsed.message) errMsg = parsed.message;
205
+ else if (err) errMsg = JSON.stringify(err);
201
206
  } catch {
202
207
  }
203
208
  throw new Error(errMsg);
@@ -1391,7 +1396,7 @@ async function liExec(method, httpMethod, params, agentId) {
1391
1396
  return result.data;
1392
1397
  }
1393
1398
  async function getPersonId(agentId) {
1394
- const data = await liExec("v2/userinfo", "GET", {}, agentId);
1399
+ const data = await liExec("userinfo", "GET", {}, agentId);
1395
1400
  const id = data?.sub ?? data?.id;
1396
1401
  if (!id) throw new Error("Could not resolve LinkedIn person ID");
1397
1402
  return id;
@@ -1436,7 +1441,7 @@ Examples:
1436
1441
  const agentId = requireAgentId(opts.agent);
1437
1442
  const data = await withSpinner(
1438
1443
  "Fetching LinkedIn profile...",
1439
- () => liExec("v2/userinfo", "GET", {}, agentId)
1444
+ () => liExec("userinfo", "GET", {}, agentId)
1440
1445
  );
1441
1446
  if (isJsonMode()) return printJson(data);
1442
1447
  const name = data?.name ?? [data?.given_name, data?.family_name].filter(Boolean).join(" ");
@@ -1515,7 +1520,7 @@ Examples:
1515
1520
  const encoded = encodeURIComponent(postUrn);
1516
1521
  const data = await withSpinner(
1517
1522
  "Liking post...",
1518
- () => liExec(`v2/socialActions/${encoded}/likes`, "POST", {
1523
+ () => liExec(`socialActions/${encoded}/likes`, "POST", {
1519
1524
  actor: `urn:li:person:${personId}`
1520
1525
  }, agentId)
1521
1526
  );
@@ -1537,7 +1542,7 @@ Examples:
1537
1542
  const encodedPerson = encodeURIComponent(`urn:li:person:${personId}`);
1538
1543
  await withSpinner(
1539
1544
  "Unliking post...",
1540
- () => liExec(`v2/socialActions/${encodedPost}/likes/${encodedPerson}`, "DELETE", {}, agentId)
1545
+ () => liExec(`socialActions/${encodedPost}/likes/${encodedPerson}`, "DELETE", {}, agentId)
1541
1546
  );
1542
1547
  if (isJsonMode()) return printJson({ unliked: true });
1543
1548
  console.log(chalk7.green("\u2713 Post unliked"));
@@ -1558,7 +1563,7 @@ Examples:
1558
1563
  const encoded = encodeURIComponent(postUrn);
1559
1564
  const data = await withSpinner(
1560
1565
  "Adding comment...",
1561
- () => liExec(`v2/socialActions/${encoded}/comments`, "POST", {
1566
+ () => liExec(`socialActions/${encoded}/comments`, "POST", {
1562
1567
  actor: `urn:li:person:${personId}`,
1563
1568
  message: { text }
1564
1569
  }, agentId)
@@ -1726,6 +1731,53 @@ The number is permanently returned to the carrier pool. This action cannot be un
1726
1731
  });
1727
1732
  }
1728
1733
 
1734
+ // src/commands/sms.ts
1735
+ function formatPhone2(num) {
1736
+ const m = num.match(/^\+1(\d{3})(\d{3})(\d{4})$/);
1737
+ return m ? `+1 (${m[1]}) ${m[2]}-${m[3]}` : num;
1738
+ }
1739
+ function registerSmsCommands(program2) {
1740
+ const sms = program2.command("sms").description("Send SMS messages from your workspace phone number").addHelpText("after", `
1741
+ Commands:
1742
+ send Send an SMS text message to any phone number
1743
+
1744
+ Examples:
1745
+ $ blink sms send "+14155551234" "Your order is ready!"
1746
+ $ blink sms send "+14155551234" "Code: 492817" --from "+19143720262"
1747
+ $ blink sms send "+447911123456" "Your appointment is confirmed."
1748
+ $ blink sms send "+14155551234" "Hello" --json
1749
+
1750
+ Requires a provisioned workspace phone number (blink phone list / blink phone buy).
1751
+ 0.1 credits per message. Credits charged immediately on send.
1752
+ `).action(() => sms.help());
1753
+ sms.command("send <to> <message>").description("Send an SMS message (0.1 credits per message)").option("--from <number>", "Specific sender number (default: workspace primary)").addHelpText("after", `
1754
+ Examples:
1755
+ $ blink sms send "+14155551234" "Your appointment is confirmed for tomorrow at 2pm."
1756
+ $ blink sms send "+447911123456" "Your order #1042 has shipped!"
1757
+ $ blink sms send "+14155551234" "Code: 492817" --from "+19143720262"
1758
+ $ blink sms send "+14155551234" "Hello" --json
1759
+
1760
+ Phone numbers must be in E.164 format (+14155551234).
1761
+ Messages over 160 characters count as multiple segments (still 0.1 credits).
1762
+ Requires a provisioned workspace phone number: blink phone buy
1763
+ `).action(async (to, message, opts) => {
1764
+ requireToken();
1765
+ const body = { to, message };
1766
+ if (opts.from?.trim()) body.from = opts.from.trim();
1767
+ const result = await withSpinner(
1768
+ `Sending SMS to ${to}...`,
1769
+ () => resourcesRequest("/api/v1/sms/send", { body })
1770
+ );
1771
+ if (isJsonMode()) return printJson(result);
1772
+ console.log(`\u2713 SMS sent`);
1773
+ console.log(` To ${formatPhone2(result.to)}`);
1774
+ console.log(` From ${formatPhone2(result.from)}`);
1775
+ if (result.segment_count > 1) console.log(` Segments ${result.segment_count}`);
1776
+ console.log(` ID ${result.message_id}`);
1777
+ console.log(` Credits ${result.credits_charged}`);
1778
+ });
1779
+ }
1780
+
1729
1781
  // src/lib/api-app.ts
1730
1782
  var BASE_URL2 = process.env.BLINK_APP_URL ?? "https://blink.new";
1731
1783
  async function appRequest(path, opts = {}) {
@@ -2372,6 +2424,7 @@ registerNotifyCommands(program);
2372
2424
  registerConnectorCommands(program);
2373
2425
  registerLinkedInCommands(program);
2374
2426
  registerPhoneCommands(program);
2427
+ registerSmsCommands(program);
2375
2428
  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", `
2376
2429
  Examples:
2377
2430
  $ blink use proj_xxx Shows the export command to run
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blinkdotnew/cli",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Blink platform CLI — deploy apps, manage databases, generate AI content",
5
5
  "bin": {
6
6
  "blink": "dist/cli.js"
package/src/cli.ts CHANGED
@@ -11,6 +11,7 @@ import { registerNotifyCommands } from './commands/notify.js'
11
11
  import { registerConnectorCommands } from './commands/connector.js'
12
12
  import { registerLinkedInCommands } from './commands/linkedin.js'
13
13
  import { registerPhoneCommands } from './commands/phone.js'
14
+ import { registerSmsCommands } from './commands/sms.js'
14
15
  import { registerDeployCommands } from './commands/deploy.js'
15
16
  import { registerProjectCommands } from './commands/project.js'
16
17
  import { registerAuthCommands } from './commands/auth.js'
@@ -150,6 +151,7 @@ registerNotifyCommands(program)
150
151
  registerConnectorCommands(program)
151
152
  registerLinkedInCommands(program)
152
153
  registerPhoneCommands(program)
154
+ registerSmsCommands(program)
153
155
 
154
156
  program.command('use <project_id>')
155
157
  .description('Set active project for this shell session (alternative to blink link)')
@@ -34,7 +34,7 @@ async function liExec(
34
34
  }
35
35
 
36
36
  async function getPersonId(agentId: string): Promise<string> {
37
- const data = await liExec('v2/userinfo', 'GET', {}, agentId)
37
+ const data = await liExec('userinfo', 'GET', {}, agentId)
38
38
  const id = data?.sub ?? data?.id
39
39
  if (!id) throw new Error('Could not resolve LinkedIn person ID')
40
40
  return id
@@ -87,7 +87,7 @@ Examples:
87
87
  requireToken()
88
88
  const agentId = requireAgentId(opts.agent)
89
89
  const data = await withSpinner('Fetching LinkedIn profile...', () =>
90
- liExec('v2/userinfo', 'GET', {}, agentId)
90
+ liExec('userinfo', 'GET', {}, agentId)
91
91
  )
92
92
  if (isJsonMode()) return printJson(data)
93
93
  const name = data?.name ?? [data?.given_name, data?.family_name].filter(Boolean).join(' ')
@@ -180,7 +180,7 @@ Examples:
180
180
  )
181
181
  const encoded = encodeURIComponent(postUrn)
182
182
  const data = await withSpinner('Liking post...', () =>
183
- liExec(`v2/socialActions/${encoded}/likes`, 'POST', {
183
+ liExec(`socialActions/${encoded}/likes`, 'POST', {
184
184
  actor: `urn:li:person:${personId}`,
185
185
  }, agentId)
186
186
  )
@@ -206,7 +206,7 @@ Examples:
206
206
  const encodedPost = encodeURIComponent(postUrn)
207
207
  const encodedPerson = encodeURIComponent(`urn:li:person:${personId}`)
208
208
  await withSpinner('Unliking post...', () =>
209
- liExec(`v2/socialActions/${encodedPost}/likes/${encodedPerson}`, 'DELETE', {}, agentId)
209
+ liExec(`socialActions/${encodedPost}/likes/${encodedPerson}`, 'DELETE', {}, agentId)
210
210
  )
211
211
  if (isJsonMode()) return printJson({ unliked: true })
212
212
  console.log(chalk.green('✓ Post unliked'))
@@ -231,7 +231,7 @@ Examples:
231
231
  )
232
232
  const encoded = encodeURIComponent(postUrn)
233
233
  const data = await withSpinner('Adding comment...', () =>
234
- liExec(`v2/socialActions/${encoded}/comments`, 'POST', {
234
+ liExec(`socialActions/${encoded}/comments`, 'POST', {
235
235
  actor: `urn:li:person:${personId}`,
236
236
  message: { text },
237
237
  }, agentId)
@@ -0,0 +1,71 @@
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 SmsResult {
7
+ message_id: string
8
+ twilio_sid: string
9
+ status: string
10
+ to: string
11
+ from: string
12
+ segment_count: number
13
+ credits_charged: number
14
+ }
15
+
16
+ function formatPhone(num: string): string {
17
+ const m = num.match(/^\+1(\d{3})(\d{3})(\d{4})$/)
18
+ return m ? `+1 (${m[1]}) ${m[2]}-${m[3]}` : num
19
+ }
20
+
21
+ export function registerSmsCommands(program: Command) {
22
+ const sms = program.command('sms')
23
+ .description('Send SMS messages from your workspace phone number')
24
+ .addHelpText('after', `
25
+ Commands:
26
+ send Send an SMS text message to any phone number
27
+
28
+ Examples:
29
+ $ blink sms send "+14155551234" "Your order is ready!"
30
+ $ blink sms send "+14155551234" "Code: 492817" --from "+19143720262"
31
+ $ blink sms send "+447911123456" "Your appointment is confirmed."
32
+ $ blink sms send "+14155551234" "Hello" --json
33
+
34
+ Requires a provisioned workspace phone number (blink phone list / blink phone buy).
35
+ 0.1 credits per message. Credits charged immediately on send.
36
+ `)
37
+ .action(() => sms.help())
38
+
39
+ sms.command('send <to> <message>')
40
+ .description('Send an SMS message (0.1 credits per message)')
41
+ .option('--from <number>', 'Specific sender number (default: workspace primary)')
42
+ .addHelpText('after', `
43
+ Examples:
44
+ $ blink sms send "+14155551234" "Your appointment is confirmed for tomorrow at 2pm."
45
+ $ blink sms send "+447911123456" "Your order #1042 has shipped!"
46
+ $ blink sms send "+14155551234" "Code: 492817" --from "+19143720262"
47
+ $ blink sms send "+14155551234" "Hello" --json
48
+
49
+ Phone numbers must be in E.164 format (+14155551234).
50
+ Messages over 160 characters count as multiple segments (still 0.1 credits).
51
+ Requires a provisioned workspace phone number: blink phone buy
52
+ `)
53
+ .action(async (to: string, message: string, opts) => {
54
+ requireToken()
55
+ const body: Record<string, string> = { to, message }
56
+ if (opts.from?.trim()) body.from = opts.from.trim()
57
+
58
+ const result = await withSpinner(`Sending SMS to ${to}...`, () =>
59
+ resourcesRequest('/api/v1/sms/send', { body })
60
+ ) as SmsResult
61
+
62
+ if (isJsonMode()) return printJson(result)
63
+
64
+ console.log(`✓ SMS sent`)
65
+ console.log(` To ${formatPhone(result.to)}`)
66
+ console.log(` From ${formatPhone(result.from)}`)
67
+ if (result.segment_count > 1) console.log(` Segments ${result.segment_count}`)
68
+ console.log(` ID ${result.message_id}`)
69
+ console.log(` Credits ${result.credits_charged}`)
70
+ })
71
+ }
@@ -35,7 +35,14 @@ export async function resourcesRequest(path: string, opts: RequestOptions = {})
35
35
  if (!res.ok) {
36
36
  const errText = await res.text()
37
37
  let errMsg = `HTTP ${res.status}`
38
- try { errMsg = JSON.parse(errText).error ?? errMsg } catch { /* use default */ }
38
+ try {
39
+ const parsed = JSON.parse(errText)
40
+ const err = parsed.error
41
+ if (typeof err === 'string') errMsg = err
42
+ else if (err?.message) errMsg = err.message
43
+ else if (parsed.message) errMsg = parsed.message
44
+ else if (err) errMsg = JSON.stringify(err)
45
+ } catch { /* use default */ }
39
46
  throw new Error(errMsg)
40
47
  }
41
48
  const ct = res.headers.get('content-type') ?? ''