@blinkdotnew/cli 0.3.0 → 0.3.1

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
@@ -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 <path> [output]").description("Download a file from storage").action(async (storagePath, output) => {
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
- const projectId = requireProjectId();
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 <path>").description("Delete a file from storage").action(async (storagePath) => {
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
- const projectId = requireProjectId();
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 <path>").description("Get public URL for a storage file").action(async (storagePath) => {
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
- const projectId = requireProjectId();
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);
@@ -1438,6 +1466,145 @@ Examples:
1438
1466
  });
1439
1467
  }
1440
1468
 
1469
+ // src/commands/phone.ts
1470
+ function formatPhone(num) {
1471
+ const m = num.match(/^\+1(\d{3})(\d{3})(\d{4})$/);
1472
+ return m ? `+1 (${m[1]}) ${m[2]}-${m[3]}` : num;
1473
+ }
1474
+ function statusDot(status) {
1475
+ return status === "active" ? "\u25CF" : status === "grace" ? "\u26A1" : "\u25CB";
1476
+ }
1477
+ function nextCharge(lastCharged) {
1478
+ if (!lastCharged) return "";
1479
+ const d = new Date(new Date(lastCharged).getTime() + 30 * 864e5);
1480
+ return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
1481
+ }
1482
+ function printRecords(records) {
1483
+ if (!records.length) {
1484
+ console.log("No phone numbers. Run: blink phone buy");
1485
+ return;
1486
+ }
1487
+ records.forEach((r, i) => {
1488
+ const label = r.label ? ` ${r.label}` : "";
1489
+ const primary = i === 0 && r.status === "active" ? " \u2605 primary" : "";
1490
+ const next = nextCharge(r.last_charged_at);
1491
+ const charge = next ? ` Next charge ${next}` : "";
1492
+ console.log(`${statusDot(r.status)} ${formatPhone(r.phone_number)}${label}${primary}`);
1493
+ console.log(` ${r.id} ${r.country}${r.area_code ? ` \xB7 ${r.area_code}` : ""}${charge}`);
1494
+ });
1495
+ }
1496
+ function registerPhoneCommands(program2) {
1497
+ const phone = program2.command("phone").description("Manage workspace phone numbers for AI calling").addHelpText("after", `
1498
+ Commands:
1499
+ list List all workspace phone numbers
1500
+ buy Provision a new phone number
1501
+ label Update a number's label
1502
+ release Release (cancel) a phone number
1503
+
1504
+ Examples:
1505
+ $ blink phone list
1506
+ $ blink phone buy --label "Sales" --country US --area-code 415
1507
+ $ blink phone buy --label "Support"
1508
+ $ blink phone label wpn_abc123 "Support line"
1509
+ $ blink phone release wpn_abc123
1510
+
1511
+ Numbers cost 10 credits/month each. First charge is immediate on buy.
1512
+ Primary number (oldest active) is used by default for \`blink ai call\`.
1513
+ Use \`blink ai call --from +1XXXXXXXXXX\` to specify a different number.
1514
+ `);
1515
+ phone.action(async () => {
1516
+ requireToken();
1517
+ const records = await withSpinner(
1518
+ "Fetching phone numbers...",
1519
+ () => resourcesRequest("/api/v1/phone-numbers")
1520
+ );
1521
+ if (isJsonMode()) return printJson(records);
1522
+ printRecords(records);
1523
+ });
1524
+ phone.command("list").description("List all workspace phone numbers").addHelpText("after", `
1525
+ Examples:
1526
+ $ blink phone list
1527
+ $ blink phone list --json
1528
+ `).action(async () => {
1529
+ requireToken();
1530
+ const records = await withSpinner(
1531
+ "Fetching phone numbers...",
1532
+ () => resourcesRequest("/api/v1/phone-numbers")
1533
+ );
1534
+ if (isJsonMode()) return printJson(records);
1535
+ printRecords(records);
1536
+ });
1537
+ 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", `
1538
+ Examples:
1539
+ $ blink phone buy
1540
+ $ blink phone buy --label "Sales" --area-code 415
1541
+ $ blink phone buy --label "UK Support" --country GB
1542
+ $ blink phone buy --json | jq '.phone_number'
1543
+
1544
+ 10 credits charged immediately, then monthly on anniversary.
1545
+ `).action(async (opts) => {
1546
+ requireToken();
1547
+ const result = await withSpinner(
1548
+ "Provisioning phone number...",
1549
+ () => resourcesRequest("/api/v1/phone-numbers", {
1550
+ body: {
1551
+ label: opts.label || void 0,
1552
+ country: opts.country,
1553
+ area_code: opts.areaCode || void 0
1554
+ }
1555
+ })
1556
+ );
1557
+ if (isJsonMode()) return printJson(result);
1558
+ console.log(`\u2713 Phone number provisioned`);
1559
+ console.log(` Number ${formatPhone(result.phone_number)}`);
1560
+ if (result.label) console.log(` Label ${result.label}`);
1561
+ console.log(` Country ${result.country}${result.area_code ? ` \xB7 Area ${result.area_code}` : ""}`);
1562
+ console.log(` ID ${result.id}`);
1563
+ console.log(` Billing 10 credits/month`);
1564
+ console.log();
1565
+ console.log(`Use it: blink ai call "+1..." "Your task" --from "${result.phone_number}"`);
1566
+ });
1567
+ phone.command("label <id> <label>").description("Set or update the label for a phone number").addHelpText("after", `
1568
+ Examples:
1569
+ $ blink phone label wpn_abc123 "Sales"
1570
+ $ blink phone label wpn_abc123 "" Clear label
1571
+ `).action(async (id, label) => {
1572
+ requireToken();
1573
+ const result = await withSpinner(
1574
+ "Updating label...",
1575
+ () => resourcesRequest(`/api/v1/phone-numbers/${id}`, {
1576
+ method: "PATCH",
1577
+ body: { label }
1578
+ })
1579
+ );
1580
+ if (isJsonMode()) return printJson(result);
1581
+ console.log(`\u2713 Label updated: ${formatPhone(result.phone_number)} \u2192 "${result.label ?? ""}"`);
1582
+ });
1583
+ phone.command("release <id>").description("Release a phone number (permanent)").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", `
1584
+ Examples:
1585
+ $ blink phone release wpn_abc123
1586
+ $ blink phone release wpn_abc123 --yes
1587
+
1588
+ The number is permanently returned to the carrier pool. This action cannot be undone.
1589
+ `).action(async (id, opts) => {
1590
+ requireToken();
1591
+ if (!opts.yes && !isJsonMode()) {
1592
+ const { confirm } = await import("@clack/prompts");
1593
+ const yes = await confirm({ message: `Release ${id}? This cannot be undone.` });
1594
+ if (!yes) {
1595
+ console.log("Cancelled.");
1596
+ return;
1597
+ }
1598
+ }
1599
+ await withSpinner(
1600
+ "Releasing phone number...",
1601
+ () => resourcesRequest(`/api/v1/phone-numbers/${id}`, { method: "DELETE" })
1602
+ );
1603
+ if (isJsonMode()) return printJson({ success: true, id });
1604
+ console.log(`\u2713 Phone number ${id} released`);
1605
+ });
1606
+ }
1607
+
1441
1608
  // src/lib/api-app.ts
1442
1609
  var BASE_URL2 = process.env.BLINK_APP_URL ?? "https://blink.new";
1443
1610
  async function appRequest(path, opts = {}) {
@@ -1981,6 +2148,12 @@ Realtime / RAG / Notify:
1981
2148
  $ blink rag search "how does billing work" --ai
1982
2149
  $ blink notify email user@example.com "Subject" "Body"
1983
2150
 
2151
+ Phone Numbers (10 credits/month per number):
2152
+ $ blink phone list List all workspace phone numbers
2153
+ $ blink phone buy --label Sales Buy a new number (US, UK, CA, AU)
2154
+ $ blink phone label <id> Sales Update label
2155
+ $ blink phone release <id> Release a number
2156
+
1984
2157
  Connectors (38 OAuth providers \u2014 GitHub, Notion, Slack, Stripe, Shopify, Jira, Linear, and more):
1985
2158
  $ blink connector providers List all 38 providers
1986
2159
  $ blink connector status Show all connected accounts
@@ -2042,6 +2215,7 @@ registerRagCommands(program);
2042
2215
  registerNotifyCommands(program);
2043
2216
  registerConnectorCommands(program);
2044
2217
  registerLinkedInCommands(program);
2218
+ registerPhoneCommands(program);
2045
2219
  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
2220
  Examples:
2047
2221
  $ 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.0",
3
+ "version": "0.3.1",
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
@@ -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)')
@@ -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
+ }
@@ -80,11 +80,21 @@ Examples:
80
80
  console.log(table.toString())
81
81
  })
82
82
 
83
- storage.command('download <path> [output]')
84
- .description('Download a file from storage')
85
- .action(async (storagePath: string, output: string | undefined) => {
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
- const projectId = requireProjectId()
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 <path>')
98
- .description('Delete a file from storage')
99
- .action(async (storagePath: string) => {
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
- const projectId = requireProjectId()
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 <path>')
110
- .description('Get public URL for a storage file')
111
- .action(async (storagePath: string) => {
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
- const projectId = requireProjectId()
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)