@blinkdotnew/cli 0.3.7 → 0.4.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
@@ -50,7 +50,14 @@ function resolveProjectId(explicitId) {
50
50
  function requireProjectId(explicitId) {
51
51
  const id = resolveProjectId(explicitId);
52
52
  if (!id) {
53
- console.error("Error: No project linked. Run `blink link <project_id>` or use `blink use <project_id>`.");
53
+ process.stderr.write(
54
+ `Error: No project context.
55
+ 1. blink link <project_id> Link to current directory
56
+ 2. export BLINK_ACTIVE_PROJECT=proj_xxx Set env var (CI/agents)
57
+ 3. Pass project_id as argument e.g. blink db query proj_xxx "SELECT 1"
58
+ Don't have a project yet? Run: blink init
59
+ `
60
+ );
54
61
  process.exit(1);
55
62
  }
56
63
  return id;
@@ -136,10 +143,10 @@ function readConfig(profile = "default") {
136
143
  return sections[profile];
137
144
  }
138
145
  function writeConfig(data, profile = "default") {
139
- if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
146
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
140
147
  const existing = existsSync(CONFIG_FILE) ? parseToml(readFileSync(CONFIG_FILE, "utf-8")) : {};
141
148
  existing[profile] = { ...existing[profile], ...data };
142
- writeFileSync(CONFIG_FILE, serializeToml(existing));
149
+ writeFileSync(CONFIG_FILE, serializeToml(existing), { mode: 384 });
143
150
  }
144
151
  function clearConfig(profile = "default") {
145
152
  if (!existsSync(CONFIG_FILE)) return;
@@ -166,7 +173,9 @@ function resolveToken() {
166
173
  function requireToken() {
167
174
  const token = resolveToken();
168
175
  if (!token) {
169
- console.error("Error: Not authenticated. Run `blink login --interactive` or set BLINK_API_KEY env var.");
176
+ process.stderr.write(
177
+ "Error: Not authenticated.\n Get your API key at: blink.new/settings?tab=api-keys (starts with blnk_ak_)\n Then: blink login --interactive Save to ~/.config/blink/config.toml\n or: export BLINK_API_KEY=blnk_ak_... Set env var (CI/agents)\n"
178
+ );
170
179
  process.exit(1);
171
180
  }
172
181
  return token;
@@ -205,6 +214,9 @@ async function resourcesRequest(path, opts = {}) {
205
214
  else if (err) errMsg = JSON.stringify(err);
206
215
  } catch {
207
216
  }
217
+ if (res.status === 401) errMsg += " \u2014 check your API key (blink login --interactive)";
218
+ if (res.status === 403) errMsg += " \u2014 check project permissions or workspace tier";
219
+ if (res.status === 404) errMsg += " \u2014 resource not found (check project ID)";
208
220
  throw new Error(errMsg);
209
221
  }
210
222
  const ct = res.headers.get("content-type") ?? "";
@@ -217,6 +229,13 @@ import chalk from "chalk";
217
229
  import Table from "cli-table3";
218
230
  var isJsonMode = () => process.argv.includes("--json");
219
231
  var isCI = () => !process.stdout.isTTY;
232
+ function printSuccess(msg) {
233
+ if (!isJsonMode()) console.log(chalk.green("\u2713") + " " + msg);
234
+ }
235
+ function printError(msg, hint) {
236
+ console.error(chalk.red("Error:") + " " + msg);
237
+ if (hint) console.error(" " + chalk.dim(hint));
238
+ }
220
239
  function printUrl(label, url) {
221
240
  if (!isJsonMode()) console.log(chalk.bold(label.padEnd(12)) + chalk.cyan(url));
222
241
  }
@@ -1802,9 +1821,13 @@ async function appRequest(path, opts = {}) {
1802
1821
  const errText = await res.text();
1803
1822
  let errMsg = `HTTP ${res.status}`;
1804
1823
  try {
1805
- errMsg = JSON.parse(errText).error ?? errMsg;
1824
+ const parsed = JSON.parse(errText);
1825
+ errMsg = parsed.error ?? parsed.message ?? errMsg;
1806
1826
  } catch {
1807
1827
  }
1828
+ if (res.status === 401) errMsg += " \u2014 check your API key (blink login --interactive)";
1829
+ if (res.status === 403) errMsg += " \u2014 check project permissions or workspace tier";
1830
+ if (res.status === 404) errMsg += " \u2014 resource not found (check project ID)";
1808
1831
  throw new Error(errMsg);
1809
1832
  }
1810
1833
  const ct = res.headers.get("content-type") ?? "";
@@ -1957,7 +1980,7 @@ After creating a project, link it to your current directory:
1957
1980
  `);
1958
1981
  project.command("list").description("List all projects").action(async () => {
1959
1982
  requireToken();
1960
- const result = await withSpinner("Loading projects...", () => appRequest("/api/projects"));
1983
+ const result = await withSpinner("Loading projects...", () => appRequest("/api/v1/projects"));
1961
1984
  if (isJsonMode()) return printJson(result);
1962
1985
  const projects = result?.projects ?? result ?? [];
1963
1986
  const table = createTable(["ID", "Name", "URL"]);
@@ -2003,7 +2026,7 @@ After linking, most commands work without specifying a project_id:
2003
2026
  requireToken();
2004
2027
  let id = projectId;
2005
2028
  if (!id) {
2006
- const result = await withSpinner("Loading projects...", () => appRequest("/api/projects"));
2029
+ const result = await withSpinner("Loading projects...", () => appRequest("/api/v1/projects"));
2007
2030
  const projects = result?.projects ?? result ?? [];
2008
2031
  if (!projects.length) {
2009
2032
  console.log("No projects found. Create one with `blink project create <name>`.");
@@ -2303,87 +2326,1511 @@ Examples:
2303
2326
  });
2304
2327
  }
2305
2328
 
2306
- // src/cli.ts
2307
- var require2 = createRequire(import.meta.url);
2308
- var pkg = require2("../package.json");
2309
- var program = new Command();
2310
- program.name("blink").description("Blink platform CLI \u2014 build, deploy, and manage AI-powered apps").version(pkg.version).option("--token <key>", "Override API token for this command").option("--json", "Machine-readable JSON output (no colors, great for scripting)").option("-y, --yes", "Skip confirmation prompts").option("--debug", "Verbose request logging").option("--profile <name>", "Use named config profile from ~/.config/blink/config.toml").addHelpText("after", `
2311
- Auth:
2312
- API key lives at blink.new \u2192 Settings \u2192 API Keys (starts with blnk_ak_)
2313
- $ blink login --interactive Save your API key to ~/.config/blink/config.toml
2314
- $ export BLINK_API_KEY=blnk_ak_... Or set env var directly (used in CI/agents)
2315
-
2316
- Quick Start:
2317
- $ blink login --interactive Authenticate
2318
- $ blink link Link current dir to a project (interactive picker)
2319
- $ npm run build && blink deploy ./dist --prod Build then deploy to production
2329
+ // src/commands/auth-config.ts
2330
+ init_project();
2331
+ import chalk13 from "chalk";
2332
+ function printAuthConfig(auth) {
2333
+ printKv("Mode", auth.mode ?? "redirect");
2334
+ console.log();
2335
+ console.log(chalk13.bold("Providers:"));
2336
+ for (const [name, cfg] of Object.entries(auth.providers ?? {})) {
2337
+ const status = cfg.enabled ? chalk13.green("enabled") : chalk13.dim("disabled");
2338
+ console.log(` ${name.padEnd(12)} ${status}`);
2339
+ }
2340
+ }
2341
+ function registerAuthConfigCommands(program2) {
2342
+ const authConfig = program2.command("auth-config").description("Configure Blink Auth \u2014 providers, mode, and BYOC credentials").addHelpText("after", `
2343
+ Manage authentication settings for your Blink project.
2344
+ Configure OAuth providers (Google, GitHub, Microsoft, Apple, Email)
2345
+ and set managed/headless mode.
2320
2346
 
2321
- Deploy:
2322
- $ blink deploy ./dist --prod Deploy build output to production
2323
- $ blink deploy ./dist Preview deployment (safe, non-destructive)
2324
- $ blink deployments List past deployments
2325
- $ blink rollback Rollback production to previous version
2347
+ Examples:
2348
+ $ blink auth-config get Show current auth config
2349
+ $ blink auth-config set --provider google --enabled true
2350
+ $ blink auth-config set --mode managed
2351
+ $ blink auth-config byoc-set --provider google --client-id xxx --client-secret yyy
2352
+ $ blink auth-config byoc-remove --provider google
2353
+ `);
2354
+ authConfig.command("get [project_id]").description("Show current auth configuration").addHelpText("after", `
2355
+ Examples:
2356
+ $ blink auth-config get Show auth config for linked project
2357
+ $ blink auth-config get proj_xxx Show auth config for specific project
2358
+ $ blink auth-config get --json Machine-readable output
2359
+ `).action(async (projectArg) => {
2360
+ requireToken();
2361
+ const projectId = requireProjectId(projectArg);
2362
+ const result = await withSpinner(
2363
+ "Loading auth config...",
2364
+ () => appRequest(`/api/projects/${projectId}/auth`)
2365
+ );
2366
+ if (isJsonMode()) return printJson(result);
2367
+ const auth = result?.auth ?? result;
2368
+ printAuthConfig(auth);
2369
+ });
2370
+ authConfig.command("set [project_id]").description("Update auth provider or mode").option("--provider <name>", "Provider to update (google/github/microsoft/apple/email)").option("--enabled <bool>", "Enable or disable provider (true/false)").option("--mode <mode>", "Auth mode (managed/headless)").addHelpText("after", `
2371
+ Examples:
2372
+ $ blink auth-config set --provider google --enabled true
2373
+ $ blink auth-config set --provider email --enabled false
2374
+ $ blink auth-config set --mode managed
2375
+ $ blink auth-config set proj_xxx --provider github --enabled true
2376
+ `).action(async (projectArg, opts) => {
2377
+ requireToken();
2378
+ const projectId = requireProjectId(projectArg);
2379
+ const current = await appRequest(`/api/projects/${projectId}/auth`);
2380
+ const auth = current?.auth ?? current ?? {};
2381
+ if (opts.mode) auth.mode = opts.mode;
2382
+ if (opts.provider && opts.enabled !== void 0) {
2383
+ if (!auth.providers) auth.providers = {};
2384
+ const existing = auth.providers[opts.provider];
2385
+ const providerConfig = typeof existing === "object" && existing ? existing : {};
2386
+ auth.providers[opts.provider] = { ...providerConfig, enabled: opts.enabled === "true" };
2387
+ }
2388
+ const result = await withSpinner(
2389
+ "Updating auth config...",
2390
+ () => appRequest(`/api/projects/${projectId}/auth`, { method: "PUT", body: auth })
2391
+ );
2392
+ if (isJsonMode()) return printJson(result);
2393
+ printSuccess("Auth config updated");
2394
+ });
2395
+ registerByocCommands(authConfig);
2396
+ }
2397
+ function registerByocCommands(authConfig) {
2398
+ authConfig.command("byoc-set [project_id]").description("Set BYOC (Bring Your Own Credentials) for an OAuth provider").requiredOption("--provider <name>", "Provider (google/github/microsoft/apple)").requiredOption("--client-id <id>", "OAuth client ID").requiredOption("--client-secret <secret>", "OAuth client secret").addHelpText("after", `
2399
+ Examples:
2400
+ $ blink auth-config byoc-set --provider google --client-id xxx --client-secret yyy
2401
+ $ blink auth-config byoc-set proj_xxx --provider github --client-id xxx --client-secret yyy
2402
+ `).action(async (projectArg, opts) => {
2403
+ requireToken();
2404
+ const projectId = requireProjectId(projectArg);
2405
+ const body = { provider: opts.provider, client_id: opts.clientId, client_secret: opts.clientSecret };
2406
+ await withSpinner(
2407
+ "Setting BYOC credentials...",
2408
+ () => appRequest(`/api/projects/${projectId}/auth/byoc`, { body })
2409
+ );
2410
+ if (isJsonMode()) return printJson({ status: "ok", provider: opts.provider });
2411
+ printSuccess(`BYOC credentials set for ${opts.provider}`);
2412
+ });
2413
+ authConfig.command("byoc-remove [project_id]").description("Remove BYOC credentials for an OAuth provider").requiredOption("--provider <name>", "Provider (google/github/microsoft/apple)").addHelpText("after", `
2414
+ Examples:
2415
+ $ blink auth-config byoc-remove --provider google
2416
+ $ blink auth-config byoc-remove proj_xxx --provider github
2417
+ `).action(async (projectArg, opts) => {
2418
+ requireToken();
2419
+ const projectId = requireProjectId(projectArg);
2420
+ await withSpinner(
2421
+ "Removing BYOC credentials...",
2422
+ () => appRequest(`/api/projects/${projectId}/auth/byoc`, {
2423
+ method: "DELETE",
2424
+ body: { provider: opts.provider }
2425
+ })
2426
+ );
2427
+ if (isJsonMode()) return printJson({ status: "ok", provider: opts.provider });
2428
+ printSuccess(`BYOC credentials removed for ${opts.provider}`);
2429
+ });
2430
+ }
2326
2431
 
2327
- Database (requires linked project or project_id):
2328
- $ blink db query "SELECT * FROM users LIMIT 10"
2329
- $ blink db query proj_xxx "SELECT count(*) FROM orders"
2330
- $ blink db exec schema.sql Run a SQL file
2331
- $ blink db list Show all tables
2332
- $ blink db list users Show rows in the users table
2432
+ // src/commands/domains.ts
2433
+ init_project();
2434
+ import chalk14 from "chalk";
2435
+ function printDomainRow(d) {
2436
+ const status = d.verified ? chalk14.green("verified") : chalk14.yellow("pending");
2437
+ return [d.id ?? "-", d.domain, status, d.dns_type ?? "-"];
2438
+ }
2439
+ function registerDomainsCommands(program2) {
2440
+ const domains = program2.command("domains").description("Custom domain management \u2014 add, verify, remove, purchase, and connect").addHelpText("after", `
2441
+ Manage custom domains for your Blink projects.
2442
+ Search and purchase domains directly, or connect existing ones.
2333
2443
 
2334
- Storage:
2335
- $ blink storage upload ./photo.jpg
2336
- $ blink storage list
2337
- $ blink storage url images/photo.jpg
2338
- $ blink storage download images/photo.jpg ./local.jpg
2444
+ Examples:
2445
+ $ blink domains list List domains on linked project
2446
+ $ blink domains add example.com Add a custom domain
2447
+ $ blink domains verify dom_xxx Trigger DNS verification
2448
+ $ blink domains remove dom_xxx --yes Remove a domain
2449
+ $ blink domains search cool-app Search available domains
2450
+ $ blink domains purchase cool-app.com Purchase a domain
2451
+ $ blink domains my List your purchased domains
2452
+ $ blink domains connect example.com Connect a purchased domain to project
2453
+ `);
2454
+ domains.command("list [project_id]").description("List all domains on a project").addHelpText("after", `
2455
+ Examples:
2456
+ $ blink domains list List domains for linked project
2457
+ $ blink domains list proj_xxx List domains for specific project
2458
+ $ blink domains list --json Machine-readable output
2459
+ `).action(async (projectArg) => {
2460
+ requireToken();
2461
+ const projectId = requireProjectId(projectArg);
2462
+ const result = await withSpinner(
2463
+ "Loading domains...",
2464
+ () => appRequest(`/api/project/${projectId}/domains`)
2465
+ );
2466
+ if (isJsonMode()) return printJson(result);
2467
+ const items = result?.domains ?? result ?? [];
2468
+ if (!items.length) {
2469
+ console.log(chalk14.dim("(no domains \u2014 use `blink domains add <domain>`)"));
2470
+ return;
2471
+ }
2472
+ const table = createTable(["ID", "Domain", "Status", "DNS"]);
2473
+ for (const d of items) table.push(printDomainRow(d));
2474
+ console.log(table.toString());
2475
+ });
2476
+ domains.command("add <domain> [project_id]").description("Add a custom domain to a project").addHelpText("after", `
2477
+ Examples:
2478
+ $ blink domains add example.com Add to linked project
2479
+ $ blink domains add example.com proj_xxx Add to specific project
2480
+ `).action(async (domain, projectArg) => {
2481
+ requireToken();
2482
+ const projectId = requireProjectId(projectArg);
2483
+ const result = await withSpinner(
2484
+ `Adding ${domain}...`,
2485
+ () => appRequest(`/api/project/${projectId}/domains`, { body: { domain } })
2486
+ );
2487
+ if (isJsonMode()) return printJson(result);
2488
+ printSuccess(`Domain ${domain} added`);
2489
+ const domainId = result?.domain?.id ?? result?.id;
2490
+ if (domainId) printKv("Domain ID", domainId);
2491
+ console.log(chalk14.dim("Run `blink domains verify " + (domainId ?? "<domain_id>") + "` after configuring DNS"));
2492
+ });
2493
+ registerDomainActions(domains);
2494
+ registerGlobalDomainCommands(domains);
2495
+ }
2496
+ function registerDomainActions(domains) {
2497
+ domains.command("verify <domain_id> [project_id]").description("Trigger DNS verification for a domain").addHelpText("after", `
2498
+ Examples:
2499
+ $ blink domains verify dom_xxx
2500
+ $ blink domains verify dom_xxx proj_xxx
2501
+ `).action(async (domainId, projectArg) => {
2502
+ requireToken();
2503
+ const projectId = requireProjectId(projectArg);
2504
+ const result = await withSpinner(
2505
+ "Verifying DNS...",
2506
+ () => appRequest(`/api/project/${projectId}/domains/${domainId}`, { method: "POST" })
2507
+ );
2508
+ if (isJsonMode()) return printJson(result);
2509
+ const verified = result?.hostname_status === "active";
2510
+ if (verified) printSuccess("Domain verified");
2511
+ else console.log(chalk14.yellow("!") + " DNS not yet propagated \u2014 try again in a few minutes");
2512
+ });
2513
+ domains.command("remove <domain_id> [project_id]").description("Remove a domain from a project").option("--yes", "Skip confirmation").addHelpText("after", `
2514
+ Examples:
2515
+ $ blink domains remove dom_xxx --yes
2516
+ $ blink domains remove dom_xxx proj_xxx
2517
+ `).action(async (domainId, projectArg, opts) => {
2518
+ requireToken();
2519
+ const projectId = requireProjectId(projectArg);
2520
+ if (!shouldSkipConfirm(opts)) {
2521
+ const { confirm } = await import("@clack/prompts");
2522
+ const ok = await confirm({ message: `Remove domain ${domainId}?` });
2523
+ if (!ok) {
2524
+ console.log("Cancelled.");
2525
+ return;
2526
+ }
2527
+ }
2528
+ await withSpinner(
2529
+ "Removing domain...",
2530
+ () => appRequest(`/api/project/${projectId}/domains/${domainId}`, { method: "DELETE" })
2531
+ );
2532
+ if (isJsonMode()) return printJson({ status: "ok", domain_id: domainId });
2533
+ printSuccess("Domain removed");
2534
+ });
2535
+ }
2536
+ function registerGlobalDomainCommands(domains) {
2537
+ domains.command("search <query>").description("Search available domains for purchase (no auth needed)").addHelpText("after", `
2538
+ Examples:
2539
+ $ blink domains search cool-app
2540
+ $ blink domains search my-startup --json
2541
+ `).action(async (query) => {
2542
+ const result = await withSpinner(
2543
+ "Searching domains...",
2544
+ () => appRequest(`/api/domains/search?q=${encodeURIComponent(query)}`)
2545
+ );
2546
+ if (isJsonMode()) return printJson(result);
2547
+ const items = result?.results ?? result ?? [];
2548
+ if (!items.length) {
2549
+ console.log(chalk14.dim("No results"));
2550
+ return;
2551
+ }
2552
+ const table = createTable(["Domain", "Available", "Price"]);
2553
+ for (const d of items) {
2554
+ const avail = d.available ? chalk14.green("yes") : chalk14.red("no");
2555
+ table.push([d.domain, avail, d.price ?? "-"]);
2556
+ }
2557
+ console.log(table.toString());
2558
+ });
2559
+ domains.command("purchase <domain>").description("Purchase a domain").option("--period <years>", "Registration period in years", "1").addHelpText("after", `
2560
+ Examples:
2561
+ $ blink domains purchase cool-app.com
2562
+ $ blink domains purchase cool-app.com --period 2
2563
+ $ blink domains purchase cool-app.com --json
2564
+ `).option("--yes", "Skip confirmation").action(async (domain, opts) => {
2565
+ requireToken();
2566
+ if (!shouldSkipConfirm(opts)) {
2567
+ const { confirm } = await import("@clack/prompts");
2568
+ const ok = await confirm({ message: `Purchase ${domain} for ${opts.period} year(s)? This will charge your account.` });
2569
+ if (!ok) {
2570
+ console.log("Cancelled.");
2571
+ return;
2572
+ }
2573
+ }
2574
+ const result = await withSpinner(
2575
+ `Purchasing ${domain}...`,
2576
+ () => appRequest("/api/domains/purchase", { body: { domain, period: Number(opts.period) } })
2577
+ );
2578
+ if (isJsonMode()) return printJson(result);
2579
+ printSuccess(`Domain ${domain} purchased`);
2580
+ if (result?.domain_id) printKv("Domain ID", result.domain_id);
2581
+ });
2582
+ domains.command("connect <domain> [project_id]").description("Connect a purchased domain to a project").addHelpText("after", `
2583
+ Examples:
2584
+ $ blink domains connect example.com Connect to linked project
2585
+ $ blink domains connect example.com proj_xxx Connect to specific project
2586
+ `).action(async (domain, projectArg) => {
2587
+ requireToken();
2588
+ const projectId = requireProjectId(projectArg);
2589
+ const result = await withSpinner(
2590
+ `Connecting ${domain}...`,
2591
+ () => appRequest("/api/domains/connect", { body: { domainId: domain, projectId } })
2592
+ );
2593
+ if (isJsonMode()) return printJson(result);
2594
+ printSuccess(`${domain} connected to ${projectId}`);
2595
+ });
2596
+ domains.command("my").description("List your purchased domains").addHelpText("after", `
2597
+ Examples:
2598
+ $ blink domains my
2599
+ $ blink domains my --json
2600
+ `).action(async () => {
2601
+ requireToken();
2602
+ const result = await withSpinner(
2603
+ "Loading your domains...",
2604
+ () => appRequest("/api/domains/my")
2605
+ );
2606
+ if (isJsonMode()) return printJson(result);
2607
+ const items = result?.domains ?? result ?? [];
2608
+ if (!items.length) {
2609
+ console.log(chalk14.dim("(no purchased domains)"));
2610
+ return;
2611
+ }
2612
+ const table = createTable(["Domain", "Status", "Expires"]);
2613
+ for (const d of items) table.push([d.domain, d.status ?? "-", d.expiration_date ?? "-"]);
2614
+ console.log(table.toString());
2615
+ });
2616
+ }
2617
+ function shouldSkipConfirm(opts) {
2618
+ return opts.yes || process.argv.includes("--yes") || process.argv.includes("-y") || isJsonMode() || !process.stdout.isTTY;
2619
+ }
2339
2620
 
2340
- AI (no project needed \u2014 workspace-scoped):
2341
- $ blink ai image "a futuristic city at sunset"
2342
- $ blink ai text "Summarize this: ..." --model anthropic/claude-sonnet-4.5
2343
- $ blink ai video "ocean waves at sunset" --duration 10s
2344
- $ blink ai speech "Hello world" --voice nova --output hello.mp3
2345
- $ blink ai transcribe ./meeting.mp3
2621
+ // src/commands/hosting.ts
2622
+ init_project();
2623
+ import chalk15 from "chalk";
2624
+ var STATE_COLORS = {
2625
+ active: chalk15.green,
2626
+ inactive: chalk15.dim,
2627
+ grace: chalk15.yellow,
2628
+ offline: chalk15.red
2629
+ };
2630
+ function printHostingStatus(data) {
2631
+ const state = String(data.status ?? data.state ?? "unknown");
2632
+ const colorFn = STATE_COLORS[state] ?? chalk15.white;
2633
+ printKv("State", colorFn(state));
2634
+ if (data.hosting_tier) printKv("Tier", String(data.hosting_tier));
2635
+ if (data.hosting_prod_url) printKv("URL", String(data.hosting_prod_url));
2636
+ }
2637
+ function registerHostingCommands(program2) {
2638
+ const hosting = program2.command("hosting").description("Hosting management \u2014 activate, deactivate, and check status").addHelpText("after", `
2639
+ Manage hosting for your Blink project.
2640
+ Hosting gives your project a live URL on blinkpowered.com or a custom domain.
2346
2641
 
2347
- Web & Data:
2348
- $ blink search "latest AI news" --count 10
2349
- $ blink fetch https://api.github.com/users/octocat
2350
- $ blink fetch https://api.example.com --method POST --body '{"key":"val"}'
2351
- $ blink scrape https://example.com --text Clean text (no HTML)
2352
- $ blink scrape https://example.com --extract "prices" AI-extract specific data
2642
+ Examples:
2643
+ $ blink hosting status Check hosting state and URLs
2644
+ $ blink hosting activate Activate hosting
2645
+ $ blink hosting deactivate --yes Deactivate hosting
2646
+ $ blink hosting reactivate Reactivate after deactivation
2647
+ `);
2648
+ hosting.command("status [project_id]").description("Show hosting state, tier, and URLs").addHelpText("after", `
2649
+ Examples:
2650
+ $ blink hosting status Status for linked project
2651
+ $ blink hosting status proj_xxx Status for specific project
2652
+ $ blink hosting status --json Machine-readable output
2653
+ `).action(async (projectArg) => {
2654
+ requireToken();
2655
+ const projectId = requireProjectId(projectArg);
2656
+ const result = await withSpinner(
2657
+ "Loading hosting status...",
2658
+ () => appRequest(`/api/project/${projectId}/hosting/status`)
2659
+ );
2660
+ if (isJsonMode()) return printJson(result);
2661
+ printHostingStatus(result);
2662
+ });
2663
+ hosting.command("activate [project_id]").description("Activate hosting for a project").addHelpText("after", `
2664
+ Examples:
2665
+ $ blink hosting activate
2666
+ $ blink hosting activate proj_xxx
2667
+ `).action(async (projectArg) => {
2668
+ requireToken();
2669
+ const projectId = requireProjectId(projectArg);
2670
+ const result = await withSpinner(
2671
+ "Activating hosting...",
2672
+ () => appRequest(`/api/project/${projectId}/hosting/activate`, { method: "POST" })
2673
+ );
2674
+ if (isJsonMode()) return printJson(result);
2675
+ printSuccess("Hosting activated");
2676
+ if (result?.hosting_prod_url) printKv("URL", result.hosting_prod_url);
2677
+ });
2678
+ hosting.command("deactivate [project_id]").description("Deactivate hosting for a project").option("--yes", "Skip confirmation").addHelpText("after", `
2679
+ Examples:
2680
+ $ blink hosting deactivate --yes
2681
+ $ blink hosting deactivate proj_xxx
2682
+ `).action(async (projectArg, opts) => {
2683
+ requireToken();
2684
+ const projectId = requireProjectId(projectArg);
2685
+ if (!shouldSkipConfirm2(opts)) {
2686
+ const { confirm } = await import("@clack/prompts");
2687
+ const ok = await confirm({ message: "Deactivate hosting? Your site will go offline." });
2688
+ if (!ok) {
2689
+ console.log("Cancelled.");
2690
+ return;
2691
+ }
2692
+ }
2693
+ await withSpinner(
2694
+ "Deactivating hosting...",
2695
+ () => appRequest(`/api/project/${projectId}/hosting/deactivate`, { method: "POST" })
2696
+ );
2697
+ if (isJsonMode()) return printJson({ status: "ok", project_id: projectId });
2698
+ printSuccess("Hosting deactivated");
2699
+ });
2700
+ hosting.command("reactivate [project_id]").description("Reactivate hosting after deactivation").addHelpText("after", `
2701
+ Examples:
2702
+ $ blink hosting reactivate
2703
+ $ blink hosting reactivate proj_xxx
2704
+ `).action(async (projectArg) => {
2705
+ requireToken();
2706
+ const projectId = requireProjectId(projectArg);
2707
+ const result = await withSpinner(
2708
+ "Reactivating hosting...",
2709
+ () => appRequest(`/api/project/${projectId}/hosting/reactivate`, { method: "POST" })
2710
+ );
2711
+ if (isJsonMode()) return printJson(result);
2712
+ printSuccess("Hosting reactivated");
2713
+ if (result?.hosting_prod_url) printKv("URL", result.hosting_prod_url);
2714
+ });
2715
+ }
2716
+ function shouldSkipConfirm2(opts) {
2717
+ return opts.yes || process.argv.includes("--yes") || process.argv.includes("-y") || isJsonMode() || !process.stdout.isTTY;
2718
+ }
2353
2719
 
2354
- Realtime / RAG / Notify:
2355
- $ blink realtime publish updates '{"type":"refresh"}'
2356
- $ blink rag search "how does billing work" --ai
2357
- $ blink notify email user@example.com "Subject" "Body"
2720
+ // src/commands/security.ts
2721
+ init_project();
2722
+ import chalk16 from "chalk";
2723
+ function printSecurityPolicy(data) {
2724
+ const modules = data.modules ?? data;
2725
+ const table = createTable(["Module", "Require Auth"]);
2726
+ for (const [name, cfg] of Object.entries(modules)) {
2727
+ const status = cfg.require_auth ? chalk16.green("yes") : chalk16.dim("no");
2728
+ table.push([name, status]);
2729
+ }
2730
+ console.log(table.toString());
2731
+ }
2732
+ function printCorsOrigins(origins) {
2733
+ if (!origins.length) {
2734
+ console.log(chalk16.dim("(no CORS origins configured \u2014 all origins allowed)"));
2735
+ return;
2736
+ }
2737
+ for (const o of origins) console.log(` ${o}`);
2738
+ console.log(chalk16.dim(`
2739
+ ${origins.length} origin${origins.length === 1 ? "" : "s"}`));
2740
+ }
2741
+ function registerSecurityCommands(program2) {
2742
+ registerSecurityGroup(program2);
2743
+ registerCorsGroup(program2);
2744
+ }
2745
+ function registerSecurityGroup(program2) {
2746
+ const security = program2.command("security").description("Security policy \u2014 require auth per module (db/ai/storage/realtime/rag)").addHelpText("after", `
2747
+ Control which backend modules require user authentication.
2748
+ When require_auth is true, unauthenticated requests are rejected.
2358
2749
 
2359
- Phone Numbers (10 credits/month per number):
2360
- $ blink phone list List all workspace phone numbers
2361
- $ blink phone buy --label Sales Buy a new number (US, UK, CA, AU)
2362
- $ blink phone label <id> Sales Update label
2363
- $ blink phone release <id> Release a number
2750
+ Examples:
2751
+ $ blink security get Show security policy
2752
+ $ blink security set --module db --require-auth true
2753
+ $ blink security set --module ai --require-auth false
2754
+ `);
2755
+ security.command("get [project_id]").description("Show current security policy").addHelpText("after", `
2756
+ Examples:
2757
+ $ blink security get Show policy for linked project
2758
+ $ blink security get proj_xxx Show policy for specific project
2759
+ $ blink security get --json Machine-readable output
2760
+ `).action(async (projectArg) => {
2761
+ requireToken();
2762
+ const projectId = requireProjectId(projectArg);
2763
+ const result = await withSpinner(
2764
+ "Loading security policy...",
2765
+ () => appRequest(`/api/project/${projectId}/security`)
2766
+ );
2767
+ if (isJsonMode()) return printJson(result);
2768
+ printSecurityPolicy(result?.policy ?? result);
2769
+ });
2770
+ security.command("set [project_id]").description("Update security policy for a module").requiredOption("--module <name>", "Module (db/ai/storage/realtime/rag)").requiredOption("--require-auth <bool>", "Require auth (true/false)").addHelpText("after", `
2771
+ Examples:
2772
+ $ blink security set --module db --require-auth true
2773
+ $ blink security set --module ai --require-auth false
2774
+ $ blink security set proj_xxx --module storage --require-auth true
2775
+ `).action(async (projectArg, opts) => {
2776
+ requireToken();
2777
+ const projectId = requireProjectId(projectArg);
2778
+ const requireAuth = opts.requireAuth === "true";
2779
+ const body = { policy: { modules: { [opts.module]: { require_auth: requireAuth } } } };
2780
+ await withSpinner(
2781
+ "Updating security policy...",
2782
+ () => appRequest(`/api/project/${projectId}/security`, { method: "PUT", body })
2783
+ );
2784
+ if (isJsonMode()) return printJson({ status: "ok", module: opts.module, require_auth: requireAuth });
2785
+ printSuccess(`${opts.module}: require_auth = ${requireAuth}`);
2786
+ });
2787
+ }
2788
+ function registerCorsGroup(program2) {
2789
+ const cors = program2.command("cors").description("CORS origin management \u2014 control which origins can access your project APIs").addHelpText("after", `
2790
+ Manage allowed CORS origins for your project.
2791
+ When no origins are configured, all origins are allowed.
2364
2792
 
2365
- Connectors (38 OAuth providers \u2014 GitHub, Notion, Slack, Stripe, Shopify, Jira, Linear, and more):
2366
- $ blink connector providers List all 38 providers
2367
- $ blink connector status Show all connected accounts
2368
- $ blink connector status github Check a specific provider
2369
- $ blink connector exec github /user/repos GET Call any REST endpoint
2370
- $ blink connector exec notion /search POST '{"query":"notes"}' Notion search
2371
- $ blink connector exec slack /chat.postMessage POST '{"channel":"C123","text":"hi"}'
2372
- $ blink connector exec stripe /customers GET '{"limit":5}'
2373
- $ blink connector exec jira /search GET '{"jql":"assignee=currentUser()"}'
2374
- $ blink connector exec linear '{ viewer { id name } }' POST GraphQL (Linear)
2375
- Connect accounts at: blink.new/settings?tab=connectors
2793
+ Examples:
2794
+ $ blink cors get Show allowed origins
2795
+ $ blink cors set --origins https://example.com https://app.example.com
2796
+ `);
2797
+ cors.command("get [project_id]").description("Show allowed CORS origins").addHelpText("after", `
2798
+ Examples:
2799
+ $ blink cors get Show origins for linked project
2800
+ $ blink cors get proj_xxx Show origins for specific project
2801
+ $ blink cors get --json Machine-readable output
2802
+ `).action(async (projectArg) => {
2803
+ requireToken();
2804
+ const projectId = requireProjectId(projectArg);
2805
+ const result = await withSpinner(
2806
+ "Loading CORS config...",
2807
+ () => appRequest(`/api/project/${projectId}/cors`)
2808
+ );
2809
+ if (isJsonMode()) return printJson(result);
2810
+ const origins = result?.custom_origins ?? result?.origins ?? [];
2811
+ printCorsOrigins(origins);
2812
+ });
2813
+ cors.command("set [project_id]").description("Set allowed CORS origins").requiredOption("--origins <urls...>", "Allowed origins (space-separated)").addHelpText("after", `
2814
+ Examples:
2815
+ $ blink cors set --origins https://example.com
2816
+ $ blink cors set --origins https://example.com https://app.example.com
2817
+ $ blink cors set proj_xxx --origins https://example.com
2818
+ `).action(async (projectArg, opts) => {
2819
+ requireToken();
2820
+ const projectId = requireProjectId(projectArg);
2821
+ const origins = opts.origins;
2822
+ await withSpinner(
2823
+ "Updating CORS origins...",
2824
+ () => appRequest(`/api/project/${projectId}/cors`, { method: "PUT", body: { custom_origins: origins } })
2825
+ );
2826
+ if (isJsonMode()) return printJson({ status: "ok", origins });
2827
+ printSuccess(`CORS origins set: ${origins.join(", ")}`);
2828
+ });
2829
+ }
2376
2830
 
2377
- LinkedIn (dedicated commands for the LinkedIn connector):
2378
- $ blink linkedin me Show your LinkedIn profile
2379
- $ blink linkedin posts List your 10 most recent posts
2380
- $ blink linkedin post "Hello LinkedIn!" Publish a text post
2381
- $ blink linkedin comments "urn:li:ugcPost:123" Read comments on a post
2382
- $ blink linkedin comment "urn:li:ugcPost:123" "Nice!" Add a comment
2831
+ // src/commands/env.ts
2832
+ init_project();
2833
+ import { readFileSync as readFileSync9, existsSync as existsSync4 } from "fs";
2834
+ import chalk17 from "chalk";
2835
+ async function resolveSecretId(projectId, keyName) {
2836
+ const result = await appRequest(`/api/projects/${projectId}/secrets`);
2837
+ const secrets = result?.secrets ?? result ?? [];
2838
+ const match = secrets.find((s) => s.key === keyName);
2839
+ if (!match) {
2840
+ printError(`Env var "${keyName}" not found in project ${projectId}`);
2841
+ process.exit(1);
2842
+ }
2843
+ return match.id;
2844
+ }
2845
+ function parseEnvFile(filePath) {
2846
+ if (!existsSync4(filePath)) {
2847
+ printError(`File not found: ${filePath}`);
2848
+ process.exit(1);
2849
+ }
2850
+ const content = readFileSync9(filePath, "utf-8");
2851
+ const entries = [];
2852
+ for (const line of content.split("\n")) {
2853
+ const trimmed = line.trim();
2854
+ if (!trimmed || trimmed.startsWith("#")) continue;
2855
+ const eqIdx = trimmed.indexOf("=");
2856
+ if (eqIdx === -1) continue;
2857
+ const key = trimmed.slice(0, eqIdx).trim();
2858
+ let value = trimmed.slice(eqIdx + 1).trim();
2859
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
2860
+ value = value.slice(1, -1);
2861
+ }
2862
+ if (key) entries.push({ key, value });
2863
+ }
2864
+ return entries;
2865
+ }
2866
+ function registerEnvCommands(program2) {
2867
+ const env = program2.command("env").description("Manage project environment variables (secrets)").addHelpText("after", `
2868
+ Environment variables are encrypted key-value pairs for your project.
2869
+ They are injected at runtime in backend functions and edge workers.
2870
+ Use "env pull > .env.local" to export as a file.
2871
+
2872
+ Note: For Claw agent secrets, use "blink secrets" instead.
2873
+
2874
+ Examples:
2875
+ $ blink env list List env vars
2876
+ $ blink env set DATABASE_URL postgres://...
2877
+ $ blink env delete OLD_KEY
2878
+ $ blink env push .env Bulk import from .env file
2879
+ $ blink env pull > .env.local Export all vars to stdout
2880
+ `);
2881
+ registerEnvList(env);
2882
+ registerEnvSet(env);
2883
+ registerEnvDelete(env);
2884
+ registerEnvPush(env);
2885
+ registerEnvPull(env);
2886
+ }
2887
+ function registerEnvList(env) {
2888
+ env.command("list [project_id]").description("List environment variables").addHelpText("after", `
2889
+ Examples:
2890
+ $ blink env list List for linked project
2891
+ $ blink env list proj_xxx List for specific project
2892
+ $ blink env list --json Machine-readable output
2893
+ `).action(async (projectArg) => {
2894
+ requireToken();
2895
+ const projectId = requireProjectId(projectArg);
2896
+ const result = await withSpinner(
2897
+ "Loading env vars...",
2898
+ () => appRequest(`/api/projects/${projectId}/secrets`)
2899
+ );
2900
+ if (isJsonMode()) return printJson(result);
2901
+ const secrets = result?.secrets ?? result ?? [];
2902
+ if (!secrets.length) {
2903
+ console.log(chalk17.dim("(no env vars \u2014 use `blink env set KEY value`)"));
2904
+ return;
2905
+ }
2906
+ const table = createTable(["Key", "Value"]);
2907
+ for (const s of secrets) table.push([chalk17.bold(s.key), s.value ?? ""]);
2908
+ console.log(table.toString());
2909
+ console.log(chalk17.dim(`
2910
+ ${secrets.length} variable${secrets.length === 1 ? "" : "s"}`));
2911
+ });
2912
+ }
2913
+ function registerEnvSet(env) {
2914
+ env.command("set <key> <value>").description("Create or update an environment variable").option("--project <id>", "Project ID (defaults to linked project)").addHelpText("after", `
2915
+ Examples:
2916
+ $ blink env set DATABASE_URL postgres://user:pass@host/db
2917
+ $ blink env set STRIPE_KEY sk_live_xxx
2918
+ $ blink env set API_SECRET mysecret --project proj_xxx
2919
+ `).action(async (key, value, opts) => {
2920
+ requireToken();
2921
+ const projectId = requireProjectId(opts.project);
2922
+ await withSpinner(
2923
+ `Setting ${key}...`,
2924
+ () => appRequest(`/api/projects/${projectId}/secrets`, {
2925
+ method: "POST",
2926
+ body: { key, value }
2927
+ })
2928
+ );
2929
+ if (isJsonMode()) return printJson({ status: "ok", key, project_id: projectId });
2930
+ printSuccess(`${key} saved to project ${projectId}`);
2931
+ });
2932
+ }
2933
+ function registerEnvDelete(env) {
2934
+ env.command("delete <key>").description("Delete an environment variable").option("--project <id>", "Project ID (defaults to linked project)").option("--yes", "Skip confirmation").addHelpText("after", `
2935
+ Examples:
2936
+ $ blink env delete OLD_KEY
2937
+ $ blink env delete OLD_KEY --yes
2938
+ $ blink env delete OLD_KEY --project proj_xxx
2939
+ `).action(async (key, opts) => {
2940
+ requireToken();
2941
+ const projectId = requireProjectId(opts.project);
2942
+ const skipConfirm = opts.yes || process.argv.includes("--yes") || process.argv.includes("-y");
2943
+ if (!skipConfirm && !isJsonMode() && process.stdout.isTTY) {
2944
+ const { confirm } = await import("@clack/prompts");
2945
+ const ok = await confirm({ message: `Delete env var "${key}" from project ${projectId}?` });
2946
+ if (!ok) {
2947
+ console.log("Cancelled.");
2948
+ return;
2949
+ }
2950
+ }
2951
+ const secretId = await resolveSecretId(projectId, key);
2952
+ await withSpinner(
2953
+ `Deleting ${key}...`,
2954
+ () => appRequest(`/api/projects/${projectId}/secrets/${secretId}`, { method: "DELETE" })
2955
+ );
2956
+ if (isJsonMode()) return printJson({ status: "ok", key, project_id: projectId });
2957
+ printSuccess(`Deleted ${key}`);
2958
+ });
2959
+ }
2960
+ function registerEnvPush(env) {
2961
+ env.command("push <file>").description("Bulk import environment variables from a .env file").option("--project <id>", "Project ID (defaults to linked project)").option("--yes", "Skip confirmation").addHelpText("after", `
2962
+ Reads KEY=VALUE pairs from the file and upserts each one.
2963
+ Lines starting with # and blank lines are skipped.
2964
+
2965
+ Examples:
2966
+ $ blink env push .env
2967
+ $ blink env push .env.production --project proj_xxx
2968
+ $ blink env push .env --yes
2969
+ `).action(async (file, opts) => {
2970
+ requireToken();
2971
+ const projectId = requireProjectId(opts.project);
2972
+ const entries = parseEnvFile(file);
2973
+ if (!entries.length) {
2974
+ printError(`No KEY=VALUE pairs found in ${file}`);
2975
+ process.exit(1);
2976
+ }
2977
+ const skipConfirm = opts.yes || process.argv.includes("--yes") || process.argv.includes("-y") || isJsonMode() || !process.stdout.isTTY;
2978
+ if (!skipConfirm) {
2979
+ console.log(chalk17.dim(` Will upsert ${entries.length} vars: ${entries.map((e) => e.key).join(", ")}`));
2980
+ const { confirm } = await import("@clack/prompts");
2981
+ const ok = await confirm({ message: `Push ${entries.length} env vars to project ${projectId}?` });
2982
+ if (!ok) {
2983
+ console.log("Cancelled.");
2984
+ return;
2985
+ }
2986
+ }
2987
+ await withSpinner(`Pushing ${entries.length} vars from ${file}...`, async () => {
2988
+ for (const { key, value } of entries) {
2989
+ await appRequest(`/api/projects/${projectId}/secrets`, {
2990
+ method: "POST",
2991
+ body: { key, value }
2992
+ });
2993
+ }
2994
+ });
2995
+ if (isJsonMode()) return printJson({ status: "ok", count: entries.length, project_id: projectId });
2996
+ printSuccess(`Pushed ${entries.length} env var${entries.length === 1 ? "" : "s"} to ${projectId}`);
2997
+ });
2998
+ }
2999
+ function registerEnvPull(env) {
3000
+ env.command("pull [project_id]").description("Export all environment variables as KEY=VALUE to stdout").addHelpText("after", `
3001
+ Prints all env vars as KEY=VALUE lines to stdout.
3002
+ Pipe to a file to create a local .env:
3003
+
3004
+ Examples:
3005
+ $ blink env pull > .env.local
3006
+ $ blink env pull proj_xxx > .env.production
3007
+ $ blink env pull --json
3008
+ `).action(async (projectArg) => {
3009
+ requireToken();
3010
+ const projectId = requireProjectId(projectArg);
3011
+ const result = await appRequest(`/api/projects/${projectId}/secrets`);
3012
+ const secrets = result?.secrets ?? result ?? [];
3013
+ if (isJsonMode()) return printJson(secrets);
3014
+ if (process.stdout.isTTY) {
3015
+ process.stderr.write(chalk17.yellow("\u26A0 Printing secret values to terminal. Pipe to file: blink env pull > .env.local\n"));
3016
+ }
3017
+ for (const s of secrets) {
3018
+ process.stdout.write(`${s.key}=${s.value ?? ""}
3019
+ `);
3020
+ }
3021
+ });
3022
+ }
3023
+
3024
+ // src/commands/backend.ts
3025
+ init_project();
3026
+ import { readdirSync as readdirSync2, readFileSync as readFileSync10, statSync } from "fs";
3027
+ import { join as join4, relative as relative2 } from "path";
3028
+ import chalk18 from "chalk";
3029
+ var EXCLUDED_FILES = /* @__PURE__ */ new Set(["package-lock.json", "bun.lockb", "yarn.lock", "pnpm-lock.yaml"]);
3030
+ function collectBackendFiles(dir) {
3031
+ const files = [];
3032
+ function walk(current) {
3033
+ for (const entry of readdirSync2(current, { withFileTypes: true })) {
3034
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
3035
+ const fullPath = join4(current, entry.name);
3036
+ if (entry.isDirectory()) {
3037
+ walk(fullPath);
3038
+ continue;
3039
+ }
3040
+ if (EXCLUDED_FILES.has(entry.name)) continue;
3041
+ const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
3042
+ if (!["ts", "js", "tsx", "jsx", "json"].includes(ext)) continue;
3043
+ files.push({
3044
+ path: join4("backend", relative2(dir, fullPath)),
3045
+ source_code: readFileSync10(fullPath, "utf-8")
3046
+ });
3047
+ }
3048
+ }
3049
+ walk(dir);
3050
+ return files;
3051
+ }
3052
+ function resolveProjectAndDir(arg1, arg2) {
3053
+ if (arg2) return { projectId: requireProjectId(arg1), dir: arg2 };
3054
+ if (arg1?.startsWith("proj_")) return { projectId: requireProjectId(arg1), dir: "backend" };
3055
+ return { projectId: requireProjectId(), dir: arg1 ?? "backend" };
3056
+ }
3057
+ function registerBackendCommands(program2) {
3058
+ const backend = program2.command("backend").description("Manage backend (CF Workers for Platforms) for your project").addHelpText("after", `
3059
+ Deploy, monitor, and manage your project's backend worker.
3060
+ The backend is a Hono server running on Cloudflare Workers for Platforms.
3061
+
3062
+ Examples:
3063
+ $ blink backend deploy Deploy backend/ folder
3064
+ $ blink backend deploy ./server Deploy from custom dir
3065
+ $ blink backend status Check deployment status
3066
+ $ blink backend logs View recent logs
3067
+ $ blink backend delete --yes Remove backend worker
3068
+ `);
3069
+ registerBackendDeploy(backend);
3070
+ registerBackendStatus(backend);
3071
+ registerBackendLogs(backend);
3072
+ registerBackendDelete(backend);
3073
+ }
3074
+ function registerBackendDeploy(backend) {
3075
+ backend.command("deploy [project_id] [dir]").description("Deploy backend files to CF Workers for Platforms").addHelpText("after", `
3076
+ Reads all .ts/.js/.json files from the backend/ directory (or specified dir),
3077
+ bundles and deploys them as a Cloudflare Worker.
3078
+
3079
+ Examples:
3080
+ $ blink backend deploy Deploy backend/ for linked project
3081
+ $ blink backend deploy ./server Deploy from custom directory
3082
+ $ blink backend deploy proj_xxx Deploy backend/ for specific project
3083
+ $ blink backend deploy proj_xxx ./api Deploy ./api for specific project
3084
+ `).action(async (arg1, arg2) => {
3085
+ requireToken();
3086
+ const { projectId, dir } = resolveProjectAndDir(arg1, arg2);
3087
+ if (!statSync(dir, { throwIfNoEntry: false })?.isDirectory()) {
3088
+ printError(`Directory not found: ${dir}`, `Create a backend/ folder with index.ts`);
3089
+ process.exit(1);
3090
+ }
3091
+ const files = collectBackendFiles(dir);
3092
+ if (!files.length) {
3093
+ printError(`No .ts/.js files found in ${dir}`);
3094
+ process.exit(1);
3095
+ }
3096
+ if (!isJsonMode()) console.log(chalk18.dim(` ${files.length} file${files.length === 1 ? "" : "s"} in ${dir}`));
3097
+ const result = await withSpinner(
3098
+ "Deploying backend...",
3099
+ () => appRequest(`/api/v1/projects/${projectId}/backend/deploy`, { body: { files } })
3100
+ );
3101
+ if (isJsonMode()) return printJson(result);
3102
+ printSuccess("Backend deployed");
3103
+ if (result?.url) printUrl("URL", result.url);
3104
+ printKv("Project", projectId);
3105
+ });
3106
+ }
3107
+ function registerBackendStatus(backend) {
3108
+ backend.command("status [project_id]").description("Check backend deployment status").addHelpText("after", `
3109
+ Examples:
3110
+ $ blink backend status
3111
+ $ blink backend status proj_xxx
3112
+ $ blink backend status --json
3113
+ `).action(async (projectArg) => {
3114
+ requireToken();
3115
+ const projectId = requireProjectId(projectArg);
3116
+ const result = await withSpinner(
3117
+ "Checking status...",
3118
+ () => appRequest(`/api/v1/projects/${projectId}/backend/status`)
3119
+ );
3120
+ if (isJsonMode()) return printJson(result);
3121
+ printKv("Project", projectId);
3122
+ printKv("Enabled", result?.enabled ? "yes" : "no");
3123
+ printKv("Tier", result?.tier ?? "-");
3124
+ printKv("Requests", String(result?.requests_this_month ?? 0));
3125
+ if (result?.enabled) printUrl("URL", `https://${projectId.slice(-8)}.backend.blink.new`);
3126
+ });
3127
+ }
3128
+ function registerBackendLogs(backend) {
3129
+ backend.command("logs [project_id]").description("View recent backend logs").option("--minutes <n>", "Minutes of logs to fetch", "30").option("--slug <name>", "Function slug", "index").addHelpText("after", `
3130
+ Examples:
3131
+ $ blink backend logs Last 30 min of logs
3132
+ $ blink backend logs --minutes 60 Last hour
3133
+ $ blink backend logs proj_xxx --minutes 5
3134
+ $ blink backend logs --json
3135
+ `).action(async (projectArg, opts) => {
3136
+ requireToken();
3137
+ const projectId = requireProjectId(projectArg);
3138
+ const slug = opts.slug ?? "index";
3139
+ const since = new Date(Date.now() - parseInt(opts.minutes ?? "30") * 60 * 1e3).toISOString();
3140
+ const result = await withSpinner(
3141
+ "Fetching logs...",
3142
+ () => appRequest(`/api/v1/projects/${projectId}/functions/${slug}/logs?since=${since}`)
3143
+ );
3144
+ if (isJsonMode()) return printJson(result);
3145
+ const logs = result?.logs ?? result ?? [];
3146
+ if (!logs.length) {
3147
+ console.log(chalk18.dim("(no logs)"));
3148
+ return;
3149
+ }
3150
+ for (const log of logs) {
3151
+ const ts = log.timestamp ? chalk18.dim(new Date(log.timestamp).toLocaleTimeString()) + " " : "";
3152
+ const err = log.error ? chalk18.red(` ${log.error}`) : "";
3153
+ console.log(`${ts}${log.method ?? ""} ${log.path ?? ""} ${log.status_code ?? ""} ${log.latency_ms ?? ""}ms${err}`);
3154
+ }
3155
+ });
3156
+ }
3157
+ function registerBackendDelete(backend) {
3158
+ backend.command("delete [project_id]").description("Delete the backend worker").option("--yes", "Skip confirmation").addHelpText("after", `
3159
+ Examples:
3160
+ $ blink backend delete --yes
3161
+ $ blink backend delete proj_xxx --yes
3162
+ `).action(async (projectArg, opts) => {
3163
+ requireToken();
3164
+ const projectId = requireProjectId(projectArg);
3165
+ const skipConfirm = opts.yes || process.argv.includes("--yes") || process.argv.includes("-y");
3166
+ if (!skipConfirm && !isJsonMode() && process.stdout.isTTY) {
3167
+ const { confirm } = await import("@clack/prompts");
3168
+ const ok = await confirm({ message: `Delete backend for project ${projectId}? This cannot be undone.` });
3169
+ if (!ok) {
3170
+ console.log("Cancelled.");
3171
+ return;
3172
+ }
3173
+ }
3174
+ await withSpinner(
3175
+ "Deleting backend...",
3176
+ () => appRequest(`/api/project/${projectId}/backend`, { method: "DELETE" })
3177
+ );
3178
+ if (isJsonMode()) return printJson({ status: "ok", project_id: projectId });
3179
+ printSuccess(`Backend deleted for ${projectId}`);
3180
+ });
3181
+ }
3182
+
3183
+ // src/commands/functions.ts
3184
+ init_project();
3185
+ import chalk19 from "chalk";
3186
+ var toDate = (ts) => new Date(typeof ts === "number" && ts < 1e11 ? ts * 1e3 : ts);
3187
+ function registerFunctionsCommands(program2) {
3188
+ const fns = program2.command("functions").description("Manage edge functions (legacy) for your project").addHelpText("after", `
3189
+ List, inspect, and manage edge functions deployed to your project.
3190
+ For the newer backend worker (CF Workers for Platforms), use "blink backend".
3191
+
3192
+ Examples:
3193
+ $ blink functions list List all functions
3194
+ $ blink functions get index Get function details
3195
+ $ blink functions logs index View function logs
3196
+ $ blink functions delete old-fn --yes Delete a function
3197
+ `);
3198
+ registerFnList(fns);
3199
+ registerFnGet(fns);
3200
+ registerFnDelete(fns);
3201
+ registerFnLogs(fns);
3202
+ }
3203
+ function registerFnList(fns) {
3204
+ fns.command("list [project_id]").description("List all edge functions").addHelpText("after", `
3205
+ Examples:
3206
+ $ blink functions list
3207
+ $ blink functions list proj_xxx
3208
+ $ blink functions list --json
3209
+ `).action(async (projectArg) => {
3210
+ requireToken();
3211
+ const projectId = requireProjectId(projectArg);
3212
+ const result = await withSpinner(
3213
+ "Loading functions...",
3214
+ () => appRequest(`/api/v1/projects/${projectId}/functions`)
3215
+ );
3216
+ const functions = result?.functions ?? result ?? [];
3217
+ if (isJsonMode()) return printJson(functions);
3218
+ if (!functions.length) {
3219
+ console.log(chalk19.dim("(no functions)"));
3220
+ return;
3221
+ }
3222
+ const table = createTable(["Slug", "Status", "Created"]);
3223
+ for (const fn of functions) {
3224
+ table.push([fn.slug, fn.status ?? "-", fn.created_at ? toDate(fn.created_at).toLocaleDateString() : "-"]);
3225
+ }
3226
+ console.log(table.toString());
3227
+ });
3228
+ }
3229
+ function registerFnGet(fns) {
3230
+ fns.command("get <slug> [project_id]").description("Get details of a specific function").addHelpText("after", `
3231
+ Examples:
3232
+ $ blink functions get index
3233
+ $ blink functions get my-api proj_xxx
3234
+ $ blink functions get index --json
3235
+ `).action(async (slug, projectArg) => {
3236
+ requireToken();
3237
+ const projectId = requireProjectId(projectArg);
3238
+ const result = await withSpinner(
3239
+ `Loading ${slug}...`,
3240
+ () => appRequest(`/api/v1/projects/${projectId}/functions/${slug}`)
3241
+ );
3242
+ if (isJsonMode()) return printJson(result);
3243
+ const fn = result?.function ?? result;
3244
+ console.log(chalk19.bold("Slug: ") + (fn?.slug ?? slug));
3245
+ if (fn?.status) console.log(chalk19.bold("Status: ") + fn.status);
3246
+ if (fn?.url) console.log(chalk19.bold("URL: ") + chalk19.cyan(fn.url));
3247
+ if (fn?.created_at) console.log(chalk19.bold("Created: ") + toDate(fn.created_at).toLocaleString());
3248
+ });
3249
+ }
3250
+ function registerFnDelete(fns) {
3251
+ fns.command("delete <slug> [project_id]").description("Delete an edge function").option("--yes", "Skip confirmation").addHelpText("after", `
3252
+ Examples:
3253
+ $ blink functions delete old-fn --yes
3254
+ $ blink functions delete my-api proj_xxx --yes
3255
+ `).action(async (slug, projectArg, opts) => {
3256
+ requireToken();
3257
+ const projectId = requireProjectId(projectArg);
3258
+ const skipConfirm = opts.yes || process.argv.includes("--yes") || process.argv.includes("-y");
3259
+ if (!skipConfirm && !isJsonMode() && process.stdout.isTTY) {
3260
+ const { confirm } = await import("@clack/prompts");
3261
+ const ok = await confirm({ message: `Delete function "${slug}" from project ${projectId}?` });
3262
+ if (!ok) {
3263
+ console.log("Cancelled.");
3264
+ return;
3265
+ }
3266
+ }
3267
+ await withSpinner(
3268
+ `Deleting ${slug}...`,
3269
+ () => appRequest(`/api/v1/projects/${projectId}/functions/${slug}`, { method: "DELETE" })
3270
+ );
3271
+ if (isJsonMode()) return printJson({ status: "ok", slug, project_id: projectId });
3272
+ printSuccess(`Deleted function ${slug}`);
3273
+ });
3274
+ }
3275
+ function registerFnLogs(fns) {
3276
+ fns.command("logs <slug> [project_id]").description("View logs for a specific function").option("--minutes <n>", "Minutes of logs to fetch", "30").option("--limit <n>", "Max number of log entries").addHelpText("after", `
3277
+ Examples:
3278
+ $ blink functions logs index Last 30 min
3279
+ $ blink functions logs my-api --minutes 60 Last hour
3280
+ $ blink functions logs index proj_xxx --limit 100
3281
+ $ blink functions logs index --json
3282
+ `).action(async (slug, projectArg, opts) => {
3283
+ requireToken();
3284
+ const projectId = requireProjectId(projectArg);
3285
+ const since = new Date(Date.now() - parseInt(opts.minutes ?? "30") * 60 * 1e3).toISOString();
3286
+ const params = new URLSearchParams({ since });
3287
+ if (opts.limit) params.set("limit", opts.limit);
3288
+ const result = await withSpinner(
3289
+ "Fetching logs...",
3290
+ () => appRequest(`/api/v1/projects/${projectId}/functions/${slug}/logs?${params}`)
3291
+ );
3292
+ if (isJsonMode()) return printJson(result);
3293
+ const logs = result?.logs ?? result ?? [];
3294
+ if (!logs.length) {
3295
+ console.log(chalk19.dim("(no logs)"));
3296
+ return;
3297
+ }
3298
+ for (const log of logs) {
3299
+ const ts = log.timestamp ? chalk19.dim(new Date(log.timestamp).toLocaleTimeString()) + " " : "";
3300
+ const level = log.level ? chalk19.yellow(`[${log.level}] `) : "";
3301
+ console.log(`${ts}${level}${log.message ?? JSON.stringify(log)}`);
3302
+ }
3303
+ });
3304
+ }
3305
+
3306
+ // src/commands/init.ts
3307
+ init_project();
3308
+ import { basename as basename3 } from "path";
3309
+ import chalk20 from "chalk";
3310
+ function registerInitCommands(program2) {
3311
+ program2.command("init").description("Initialize a new Blink project and link it to the current directory").option("--name <name>", "Project name (defaults to current directory name)").option("--from <project_id>", "Create a new project named after an existing one").addHelpText("after", `
3312
+ Creates a new Blink project and writes .blink/project.json in the current directory.
3313
+ After init, all commands work without specifying a project_id.
3314
+
3315
+ Examples:
3316
+ $ blink init Create project named after current dir
3317
+ $ blink init --name "My SaaS App" Create with custom name
3318
+ $ blink init --from proj_xxx Clone from existing project
3319
+ $ blink init --json Machine-readable output
3320
+
3321
+ After init:
3322
+ $ blink deploy ./dist --prod
3323
+ $ blink db query "SELECT * FROM users"
3324
+ $ blink env set DATABASE_URL postgres://...
3325
+ `).action(async (opts) => {
3326
+ requireToken();
3327
+ if (opts.from) {
3328
+ await initFromExisting(opts.from);
3329
+ } else {
3330
+ await initNew(opts.name);
3331
+ }
3332
+ });
3333
+ }
3334
+ async function initNew(nameOpt) {
3335
+ const name = nameOpt ?? basename3(process.cwd());
3336
+ const result = await withSpinner(
3337
+ `Creating project "${name}"...`,
3338
+ () => appRequest("/api/projects/create", { method: "POST", body: { prompt: name } })
3339
+ );
3340
+ const proj = result?.project ?? result;
3341
+ if (!proj?.id) {
3342
+ printError("Failed to create project \u2014 no ID returned");
3343
+ process.exit(1);
3344
+ }
3345
+ writeProjectConfig({ projectId: proj.id });
3346
+ if (isJsonMode()) return printJson({ project_id: proj.id, name: proj.name ?? name });
3347
+ printSuccess(`Project created and linked`);
3348
+ printKv("ID", proj.id);
3349
+ printKv("Name", proj.name ?? name);
3350
+ console.log(chalk20.dim("\n Run `blink deploy ./dist --prod` to deploy"));
3351
+ }
3352
+ async function initFromExisting(sourceId) {
3353
+ const source = await withSpinner(
3354
+ "Loading source project...",
3355
+ () => appRequest(`/api/projects/${sourceId}`)
3356
+ );
3357
+ const sourceName = source?.project?.name ?? source?.name ?? sourceId;
3358
+ const name = `${sourceName} (copy)`;
3359
+ const result = await withSpinner(
3360
+ `Creating project "${name}"...`,
3361
+ () => appRequest("/api/projects/create", { method: "POST", body: { prompt: name } })
3362
+ );
3363
+ const proj = result?.project ?? result;
3364
+ if (!proj?.id) {
3365
+ printError("Failed to create project \u2014 no ID returned");
3366
+ process.exit(1);
3367
+ }
3368
+ writeProjectConfig({ projectId: proj.id });
3369
+ if (isJsonMode()) return printJson({ project_id: proj.id, name: proj.name ?? name, from: sourceId });
3370
+ printSuccess(`Project created from ${sourceId} and linked`);
3371
+ printKv("ID", proj.id);
3372
+ printKv("Name", proj.name ?? name);
3373
+ printKv("From", sourceId);
3374
+ }
3375
+
3376
+ // src/commands/workspace.ts
3377
+ import chalk21 from "chalk";
3378
+ function printWorkspaceTable(workspaces) {
3379
+ const table = createTable(["ID", "Name", "Slug", "Tier", "Role"]);
3380
+ for (const w of workspaces) table.push([w.id, w.name, w.slug ?? "-", w.tier ?? "-", w.role ?? "-"]);
3381
+ console.log(table.toString());
3382
+ }
3383
+ function printMemberTable(members) {
3384
+ const table = createTable(["Name", "Email", "Role"]);
3385
+ for (const m of members) table.push([m.name ?? "-", m.email, m.role ?? "-"]);
3386
+ console.log(table.toString());
3387
+ }
3388
+ function registerWorkspaceCommands(program2) {
3389
+ const workspace = program2.command("workspace").description("Manage workspaces, members, and invitations").addHelpText("after", `
3390
+ Examples:
3391
+ $ blink workspace list
3392
+ $ blink workspace create "My Team"
3393
+ $ blink workspace switch wsp_xxx
3394
+ $ blink workspace members wsp_xxx
3395
+ $ blink workspace invite user@example.com wsp_xxx --role admin
3396
+ `);
3397
+ workspace.command("list").description("List all workspaces you belong to").addHelpText("after", `
3398
+ Examples:
3399
+ $ blink workspace list
3400
+ $ blink workspace list --json
3401
+ `).action(async () => {
3402
+ requireToken();
3403
+ const result = await withSpinner("Loading workspaces...", () => appRequest("/api/workspaces"));
3404
+ const workspaces = result?.workspaces ?? result ?? [];
3405
+ if (isJsonMode()) return printJson(workspaces);
3406
+ if (!workspaces.length) return console.log(chalk21.dim("No workspaces found."));
3407
+ printWorkspaceTable(workspaces);
3408
+ });
3409
+ workspace.command("create <name>").description("Create a new workspace").addHelpText("after", `
3410
+ Examples:
3411
+ $ blink workspace create "My Team"
3412
+ $ blink workspace create "Acme Corp" --json
3413
+ `).action(async (name) => {
3414
+ requireToken();
3415
+ const result = await withSpinner(
3416
+ `Creating "${name}"...`,
3417
+ () => appRequest("/api/workspaces", { method: "POST", body: { name } })
3418
+ );
3419
+ if (isJsonMode()) return printJson(result);
3420
+ const ws = result?.workspace ?? result;
3421
+ printSuccess(`Created workspace: ${ws.name} (${ws.id})`);
3422
+ });
3423
+ workspace.command("switch <workspace_id>").description("Switch your active workspace").addHelpText("after", `
3424
+ Examples:
3425
+ $ blink workspace switch wsp_xxx
3426
+ `).action(async (workspaceId) => {
3427
+ requireToken();
3428
+ await withSpinner(
3429
+ "Switching workspace...",
3430
+ () => appRequest("/api/workspaces/switch", { method: "POST", body: { workspace_id: workspaceId } })
3431
+ );
3432
+ if (isJsonMode()) return printJson({ status: "ok", workspace_id: workspaceId });
3433
+ printSuccess(`Switched to workspace ${workspaceId}`);
3434
+ });
3435
+ workspace.command("members [workspace_id]").description("List members of a workspace").option("--workspace <id>", "Workspace ID").addHelpText("after", `
3436
+ Examples:
3437
+ $ blink workspace members wsp_xxx
3438
+ $ blink workspace members --workspace wsp_xxx
3439
+ $ blink workspace members wsp_xxx --json
3440
+ `).action(async (workspaceIdArg, opts) => {
3441
+ requireToken();
3442
+ const workspaceId = workspaceIdArg ?? opts.workspace;
3443
+ if (!workspaceId) {
3444
+ printError("Workspace ID required", "blink workspace members wsp_xxx");
3445
+ process.exit(1);
3446
+ }
3447
+ const result = await withSpinner(
3448
+ "Loading members...",
3449
+ () => appRequest(`/api/workspaces/${workspaceId}/members`)
3450
+ );
3451
+ const members = result?.members ?? result ?? [];
3452
+ if (isJsonMode()) return printJson(members);
3453
+ if (!members.length) return console.log(chalk21.dim("No members found."));
3454
+ printMemberTable(members);
3455
+ });
3456
+ workspace.command("invite <email> [workspace_id]").description("Invite a member to a workspace").option("--workspace <id>", "Workspace ID").option("--role <role>", "Role: admin, member, viewer", "member").addHelpText("after", `
3457
+ Examples:
3458
+ $ blink workspace invite user@example.com wsp_xxx
3459
+ $ blink workspace invite user@example.com --workspace wsp_xxx --role admin
3460
+ $ blink workspace invite user@example.com wsp_xxx --role viewer
3461
+ `).action(async (email, workspaceIdArg, opts) => {
3462
+ requireToken();
3463
+ const workspaceId = workspaceIdArg ?? opts.workspace;
3464
+ if (!workspaceId) {
3465
+ printError("Workspace ID required", "blink workspace invite user@example.com wsp_xxx");
3466
+ process.exit(1);
3467
+ }
3468
+ const result = await withSpinner(
3469
+ `Inviting ${email}...`,
3470
+ () => appRequest(`/api/workspaces/${workspaceId}/invites`, {
3471
+ method: "POST",
3472
+ body: { emails: [email], role: opts.role }
3473
+ })
3474
+ );
3475
+ if (isJsonMode()) return printJson(result);
3476
+ printSuccess(`Invited ${email} as ${opts.role} to ${workspaceId}`);
3477
+ });
3478
+ }
3479
+
3480
+ // src/commands/versions.ts
3481
+ init_project();
3482
+ import chalk22 from "chalk";
3483
+ function printVersionTable(versions) {
3484
+ const table = createTable(["ID", "Description", "Created"]);
3485
+ for (const v of versions) {
3486
+ table.push([v.id, v.description ?? v.message ?? "-", new Date(v.created_at).toLocaleString()]);
3487
+ }
3488
+ console.log(table.toString());
3489
+ }
3490
+ function registerVersionCommands(program2) {
3491
+ const versions = program2.command("versions").description("Manage project version snapshots").addHelpText("after", `
3492
+ Examples:
3493
+ $ blink versions list
3494
+ $ blink versions save --message "before refactor"
3495
+ $ blink versions restore ver_xxx
3496
+ `);
3497
+ versions.command("list [project_id]").description("List saved versions for a project").addHelpText("after", `
3498
+ Examples:
3499
+ $ blink versions list
3500
+ $ blink versions list proj_xxx
3501
+ $ blink versions list --json
3502
+ `).action(async (projectIdArg) => {
3503
+ requireToken();
3504
+ const projectId = requireProjectId(projectIdArg);
3505
+ const result = await withSpinner(
3506
+ "Loading versions...",
3507
+ () => appRequest(`/api/versions?projectId=${projectId}`)
3508
+ );
3509
+ const versionsList = result?.versions ?? result ?? [];
3510
+ if (isJsonMode()) return printJson(versionsList);
3511
+ if (!versionsList.length) return console.log(chalk22.dim("No versions saved yet."));
3512
+ printVersionTable(versionsList);
3513
+ });
3514
+ versions.command("save [project_id]").description("Save a version snapshot of the current project state").requiredOption("--message <msg>", "Version description (required)").addHelpText("after", `
3515
+ Examples:
3516
+ $ blink versions save --message "stable v1"
3517
+ $ blink versions save proj_xxx --message "pre-deploy"
3518
+ $ blink versions save --json
3519
+ `).action(async (projectIdArg, opts) => {
3520
+ requireToken();
3521
+ const projectId = requireProjectId(projectIdArg);
3522
+ const result = await withSpinner(
3523
+ "Saving version...",
3524
+ () => appRequest("/api/save-version", {
3525
+ method: "POST",
3526
+ body: { projectId, description: opts.message }
3527
+ })
3528
+ );
3529
+ if (isJsonMode()) return printJson(result);
3530
+ const ver = result?.version ?? result;
3531
+ printSuccess(`Version saved: ${ver?.id ?? "ok"}`);
3532
+ });
3533
+ versions.command("restore <version_id> [project_id]").description("Restore a project to a saved version").option("--yes", "Skip confirmation").addHelpText("after", `
3534
+ Examples:
3535
+ $ blink versions restore ver_xxx
3536
+ $ blink versions restore ver_xxx proj_xxx --yes
3537
+ `).action(async (versionId, projectIdArg, opts) => {
3538
+ requireToken();
3539
+ const projectId = requireProjectId(projectIdArg);
3540
+ const skipConfirm = opts.yes || process.argv.includes("--yes") || process.argv.includes("-y");
3541
+ if (!skipConfirm && !isJsonMode() && process.stdout.isTTY) {
3542
+ const { confirm } = await import("@clack/prompts");
3543
+ const ok = await confirm({ message: `Restore project ${projectId} to version ${versionId}?` });
3544
+ if (!ok) {
3545
+ console.log("Cancelled.");
3546
+ return;
3547
+ }
3548
+ }
3549
+ const result = await withSpinner(
3550
+ "Restoring version...",
3551
+ () => appRequest("/api/versions/restore", {
3552
+ method: "POST",
3553
+ body: { projectId, identifier: versionId }
3554
+ })
3555
+ );
3556
+ if (isJsonMode()) return printJson(result);
3557
+ printSuccess(`Restored to version ${versionId}`);
3558
+ });
3559
+ }
3560
+
3561
+ // src/commands/billing.ts
3562
+ import chalk23 from "chalk";
3563
+ function printUsage(data) {
3564
+ console.log();
3565
+ printKv("Total", `${data.total ?? 0} credits used`);
3566
+ const breakdown = data.breakdown ?? [];
3567
+ if (breakdown.length) {
3568
+ console.log();
3569
+ console.log(chalk23.bold("Breakdown:"));
3570
+ for (const item of breakdown) printKv(` ${item.category}`, `${item.amount}`);
3571
+ }
3572
+ console.log();
3573
+ }
3574
+ function registerBillingCommands(program2) {
3575
+ program2.command("credits").description("Check your credit balance and tier").addHelpText("after", `
3576
+ Examples:
3577
+ $ blink credits
3578
+ $ blink credits --json
3579
+ `).action(async () => {
3580
+ requireToken();
3581
+ const result = await withSpinner(
3582
+ "Loading usage...",
3583
+ () => appRequest("/api/usage/summary?period=month")
3584
+ );
3585
+ if (isJsonMode()) return printJson(result);
3586
+ const data = result ?? {};
3587
+ console.log();
3588
+ printKv("Used", `${data.total ?? 0} credits this month`);
3589
+ console.log();
3590
+ });
3591
+ program2.command("usage").description("Show usage summary for the current billing period").option("--period <granularity>", "Period granularity: hour, day, week, month", "day").addHelpText("after", `
3592
+ Examples:
3593
+ $ blink usage
3594
+ $ blink usage --period month
3595
+ $ blink usage --json
3596
+ `).action(async (opts) => {
3597
+ requireToken();
3598
+ const result = await withSpinner(
3599
+ "Loading usage...",
3600
+ () => appRequest(`/api/usage/summary?period=${opts.period}`)
3601
+ );
3602
+ if (isJsonMode()) return printJson(result);
3603
+ printUsage(result);
3604
+ });
3605
+ }
3606
+
3607
+ // src/commands/tokens.ts
3608
+ import chalk24 from "chalk";
3609
+ function printTokenTable(tokens) {
3610
+ const table = createTable(["ID", "Name", "Prefix", "Created", "Last Used"]);
3611
+ for (const t of tokens) {
3612
+ table.push([
3613
+ t.id,
3614
+ t.name ?? "-",
3615
+ t.key_prefix ?? "-",
3616
+ t.created_at ? new Date(t.created_at).toLocaleDateString() : "-",
3617
+ t.last_used_at ? new Date(t.last_used_at).toLocaleDateString() : "never"
3618
+ ]);
3619
+ }
3620
+ console.log(table.toString());
3621
+ }
3622
+ function printNewToken(token) {
3623
+ console.log();
3624
+ printSuccess(`Token created: ${token.name}`);
3625
+ console.log();
3626
+ console.log(chalk24.bold(" Token: ") + chalk24.green(token.key));
3627
+ console.log();
3628
+ console.log(chalk24.yellow(" \u26A0 Save this token now \u2014 it will not be shown again!"));
3629
+ console.log(chalk24.dim(" Use as: export BLINK_API_KEY=" + token.key));
3630
+ console.log();
3631
+ }
3632
+ function registerTokenCommands(program2) {
3633
+ const tokens = program2.command("tokens").description("Manage personal access tokens (API keys)").addHelpText("after", `
3634
+ Examples:
3635
+ $ blink tokens list
3636
+ $ blink tokens create --name "CI deploy key"
3637
+ $ blink tokens revoke tok_xxx --yes
3638
+ `);
3639
+ tokens.command("list").description("List all personal access tokens").addHelpText("after", `
3640
+ Examples:
3641
+ $ blink tokens list
3642
+ $ blink tokens list --json
3643
+ `).action(async () => {
3644
+ requireToken();
3645
+ const result = await withSpinner("Loading tokens...", () => appRequest("/api/tokens"));
3646
+ const tokensList = result?.tokens ?? result ?? [];
3647
+ if (isJsonMode()) return printJson(tokensList);
3648
+ if (!tokensList.length) return console.log(chalk24.dim('No tokens found. Create one with `blink tokens create --name "my key"`.'));
3649
+ printTokenTable(tokensList);
3650
+ });
3651
+ tokens.command("create").description("Create a new personal access token").requiredOption("--name <name>", 'Name for the token (e.g. "CI deploy key")').addHelpText("after", `
3652
+ Examples:
3653
+ $ blink tokens create --name "CI deploy key"
3654
+ $ blink tokens create --name "staging" --json
3655
+ `).action(async (opts) => {
3656
+ requireToken();
3657
+ const result = await withSpinner(
3658
+ "Creating token...",
3659
+ () => appRequest("/api/tokens", { method: "POST", body: { name: opts.name } })
3660
+ );
3661
+ if (isJsonMode()) return printJson(result);
3662
+ const token = result?.token ?? result;
3663
+ printNewToken(token);
3664
+ });
3665
+ tokens.command("revoke <token_id>").description("Revoke (delete) a personal access token").option("--yes", "Skip confirmation").addHelpText("after", `
3666
+ Examples:
3667
+ $ blink tokens revoke tok_xxx
3668
+ $ blink tokens revoke tok_xxx --yes
3669
+ `).action(async (tokenId, opts) => {
3670
+ requireToken();
3671
+ const skipConfirm = opts.yes || process.argv.includes("--yes") || process.argv.includes("-y");
3672
+ if (!skipConfirm && !isJsonMode() && process.stdout.isTTY) {
3673
+ const { confirm } = await import("@clack/prompts");
3674
+ const ok = await confirm({ message: `Revoke token ${tokenId}? This cannot be undone.` });
3675
+ if (!ok) {
3676
+ console.log("Cancelled.");
3677
+ return;
3678
+ }
3679
+ }
3680
+ await withSpinner(
3681
+ "Revoking token...",
3682
+ () => appRequest(`/api/tokens/${tokenId}`, { method: "DELETE" })
3683
+ );
3684
+ if (isJsonMode()) return printJson({ status: "ok", token_id: tokenId });
3685
+ printSuccess(`Revoked token ${tokenId}`);
3686
+ });
3687
+ }
3688
+
3689
+ // src/cli.ts
3690
+ var require2 = createRequire(import.meta.url);
3691
+ var pkg = require2("../package.json");
3692
+ var program = new Command();
3693
+ program.name("blink").description("Blink platform CLI \u2014 build, deploy, and manage AI-powered apps").version(pkg.version).option("--token <key>", "Override API token for this command").option("--json", "Machine-readable JSON output (no colors, great for scripting)").option("-y, --yes", "Skip confirmation prompts").option("--debug", "Verbose request logging").option("--profile <name>", "Use named config profile from ~/.config/blink/config.toml").addHelpText("after", `
3694
+ Auth:
3695
+ API key lives at blink.new \u2192 Settings \u2192 API Keys (starts with blnk_ak_)
3696
+ $ blink login --interactive Save your API key to ~/.config/blink/config.toml
3697
+ $ export BLINK_API_KEY=blnk_ak_... Or set env var directly (used in CI/agents)
3698
+
3699
+ Quick Start:
3700
+ $ blink login --interactive Authenticate
3701
+ $ blink link Link current dir to a project (interactive picker)
3702
+ $ npm run build && blink deploy ./dist --prod Build then deploy to production
3703
+
3704
+ Deploy:
3705
+ $ blink deploy ./dist --prod Deploy build output to production
3706
+ $ blink deploy ./dist Preview deployment (safe, non-destructive)
3707
+ $ blink deployments List past deployments
3708
+ $ blink rollback Rollback production to previous version
3709
+
3710
+ Database (requires linked project or project_id):
3711
+ $ blink db query "SELECT * FROM users LIMIT 10"
3712
+ $ blink db query proj_xxx "SELECT count(*) FROM orders"
3713
+ $ blink db exec schema.sql Run a SQL file
3714
+ $ blink db list Show all tables
3715
+ $ blink db list users Show rows in the users table
3716
+
3717
+ Storage:
3718
+ $ blink storage upload ./photo.jpg
3719
+ $ blink storage list
3720
+ $ blink storage url images/photo.jpg
3721
+ $ blink storage download images/photo.jpg ./local.jpg
3722
+
3723
+ AI (no project needed \u2014 workspace-scoped):
3724
+ $ blink ai image "a futuristic city at sunset"
3725
+ $ blink ai text "Summarize this: ..." --model anthropic/claude-sonnet-4.5
3726
+ $ blink ai video "ocean waves at sunset" --duration 10s
3727
+ $ blink ai speech "Hello world" --voice nova --output hello.mp3
3728
+ $ blink ai transcribe ./meeting.mp3
3729
+
3730
+ Web & Data:
3731
+ $ blink search "latest AI news" --count 10
3732
+ $ blink fetch https://api.github.com/users/octocat
3733
+ $ blink fetch https://api.example.com --method POST --body '{"key":"val"}'
3734
+ $ blink scrape https://example.com --text Clean text (no HTML)
3735
+ $ blink scrape https://example.com --extract "prices" AI-extract specific data
3736
+
3737
+ Realtime / RAG / Notify:
3738
+ $ blink realtime publish updates '{"type":"refresh"}'
3739
+ $ blink rag search "how does billing work" --ai
3740
+ $ blink notify email user@example.com "Subject" "Body"
3741
+
3742
+ Phone Numbers (10 credits/month per number):
3743
+ $ blink phone list List all workspace phone numbers
3744
+ $ blink phone buy --label Sales Buy a new number (US, UK, CA, AU)
3745
+ $ blink phone label <id> Sales Update label
3746
+ $ blink phone release <id> Release a number
3747
+
3748
+ Connectors (38 OAuth providers \u2014 GitHub, Notion, Slack, Stripe, Shopify, Jira, Linear, and more):
3749
+ $ blink connector providers List all 38 providers
3750
+ $ blink connector status Show all connected accounts
3751
+ $ blink connector status github Check a specific provider
3752
+ $ blink connector exec github /user/repos GET Call any REST endpoint
3753
+ $ blink connector exec notion /search POST '{"query":"notes"}' Notion search
3754
+ $ blink connector exec slack /chat.postMessage POST '{"channel":"C123","text":"hi"}'
3755
+ $ blink connector exec stripe /customers GET '{"limit":5}'
3756
+ $ blink connector exec jira /search GET '{"jql":"assignee=currentUser()"}'
3757
+ $ blink connector exec linear '{ viewer { id name } }' POST GraphQL (Linear)
3758
+ Connect accounts at: blink.new/settings?tab=connectors
3759
+
3760
+ LinkedIn (dedicated commands for the LinkedIn connector):
3761
+ $ blink linkedin me Show your LinkedIn profile
3762
+ $ blink linkedin posts List your 10 most recent posts
3763
+ $ blink linkedin post "Hello LinkedIn!" Publish a text post
3764
+ $ blink linkedin comments "urn:li:ugcPost:123" Read comments on a post
3765
+ $ blink linkedin comment "urn:li:ugcPost:123" "Nice!" Add a comment
2383
3766
  $ blink linkedin like "urn:li:ugcPost:123" Like a post
2384
3767
  $ blink linkedin unlike "urn:li:ugcPost:123" Unlike a post
2385
3768
  Link LinkedIn at: blink.new/claw (Agent Integrations tab)
2386
3769
 
3770
+ Environment Variables (project secrets):
3771
+ $ blink env list List env var names
3772
+ $ blink env set STRIPE_KEY sk_live_xxx Set an env var
3773
+ $ blink env delete OLD_KEY Remove an env var
3774
+ $ blink env push .env Bulk import from .env file
3775
+ $ blink env pull > .env.local Export as KEY=VALUE
3776
+
3777
+ Backend (Hono server on CF Workers):
3778
+ $ blink backend deploy ./backend Deploy backend/ to Cloudflare Workers
3779
+ $ blink backend status Check backend status + URL
3780
+ $ blink backend logs Tail backend request logs
3781
+ $ blink backend delete --yes Remove backend
3782
+
3783
+ Auth Config (Blink Auth providers):
3784
+ $ blink auth-config get Show auth configuration
3785
+ $ blink auth-config set --provider google --enabled true
3786
+ $ blink auth-config byoc-set --provider google --client-id X --client-secret Y
3787
+
3788
+ Domains:
3789
+ $ blink domains list List custom domains
3790
+ $ blink domains add myapp.com Add a custom domain
3791
+ $ blink domains verify <domain_id> Verify DNS
3792
+ $ blink domains search "myapp" Search available domains
3793
+ $ blink domains purchase myapp.com Buy a domain
3794
+
3795
+ Hosting:
3796
+ $ blink hosting status Check hosting state + URLs
3797
+ $ blink hosting activate Activate production hosting
3798
+ $ blink hosting deactivate --yes Deactivate hosting
3799
+
3800
+ Security & CORS:
3801
+ $ blink security get Show per-module auth policy
3802
+ $ blink security set --module db --require-auth true
3803
+ $ blink cors get Show allowed CORS origins
3804
+ $ blink cors set --origins https://example.com
3805
+
3806
+ Functions (legacy edge functions):
3807
+ $ blink functions list List edge functions
3808
+ $ blink functions logs index View function logs
3809
+ $ blink functions delete old-fn --yes Delete a function
3810
+
3811
+ Versions:
3812
+ $ blink versions list List saved versions
3813
+ $ blink versions save --message "v1.0" Save a snapshot
3814
+ $ blink versions restore ver_xxx --yes Restore to a version
3815
+
3816
+ Workspace:
3817
+ $ blink workspace list List workspaces
3818
+ $ blink workspace create "My Team" Create workspace
3819
+ $ blink workspace members List members + roles
3820
+
3821
+ Billing:
3822
+ $ blink credits Check credit balance
3823
+ $ blink usage Usage breakdown
3824
+
3825
+ Tokens (Personal Access Tokens):
3826
+ $ blink tokens list List PATs
3827
+ $ blink tokens create --name "CI" Create PAT (shown once!)
3828
+ $ blink tokens revoke <id> --yes Revoke a PAT
3829
+
3830
+ Init:
3831
+ $ blink init Create project + link to current dir
3832
+ $ blink init --name "My App" Create with custom name
3833
+
2387
3834
  Agents (Claw \u2014 zero config on Fly machines, BLINK_AGENT_ID is already set):
2388
3835
  $ blink agent list List all agents in workspace
2389
3836
  $ blink agent status Show current agent details
@@ -2393,7 +3840,6 @@ Secrets (encrypted agent vault \u2014 values never shown):
2393
3840
  $ blink secrets list List secret key names
2394
3841
  $ blink secrets set GITHUB_TOKEN ghp_xxx Add/update a secret
2395
3842
  $ blink secrets delete OLD_KEY Remove a secret
2396
- $ blink secrets set --agent clw_other KEY v Set secret on another agent (agent manager pattern)
2397
3843
 
2398
3844
  Tip \u2014 check full context (agent + project + auth):
2399
3845
  $ blink status
@@ -2425,6 +3871,18 @@ registerConnectorCommands(program);
2425
3871
  registerLinkedInCommands(program);
2426
3872
  registerPhoneCommands(program);
2427
3873
  registerSmsCommands(program);
3874
+ registerAuthConfigCommands(program);
3875
+ registerDomainsCommands(program);
3876
+ registerHostingCommands(program);
3877
+ registerSecurityCommands(program);
3878
+ registerEnvCommands(program);
3879
+ registerBackendCommands(program);
3880
+ registerFunctionsCommands(program);
3881
+ registerInitCommands(program);
3882
+ registerWorkspaceCommands(program);
3883
+ registerVersionCommands(program);
3884
+ registerBillingCommands(program);
3885
+ registerTokenCommands(program);
2428
3886
  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", `
2429
3887
  Examples:
2430
3888
  $ blink use proj_xxx Shows the export command to run
@@ -2440,10 +3898,10 @@ After setting:
2440
3898
  process.stdout.write(`export BLINK_ACTIVE_PROJECT=${projectId}
2441
3899
  `);
2442
3900
  } else {
2443
- const { default: chalk13 } = await import("chalk");
2444
- console.log(chalk13.bold("Active project: ") + projectId);
2445
- console.log(chalk13.dim(`Run: export BLINK_ACTIVE_PROJECT=${projectId}`));
2446
- console.log(chalk13.dim(`Or: eval $(blink use ${projectId} --export)`));
3901
+ const { default: chalk25 } = await import("chalk");
3902
+ console.log(chalk25.bold("Active project: ") + projectId);
3903
+ console.log(chalk25.dim(`Run: export BLINK_ACTIVE_PROJECT=${projectId}`));
3904
+ console.log(chalk25.dim(`Or: eval $(blink use ${projectId} --export)`));
2447
3905
  }
2448
3906
  });
2449
3907
  program.action(async () => {