@blinkdotnew/cli 0.5.1 → 0.5.3

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
@@ -2019,6 +2019,31 @@ After creating a project, link it to your current directory:
2019
2019
  if (!isJsonMode()) console.log("Deleted.");
2020
2020
  else printJson({ status: "ok" });
2021
2021
  });
2022
+ project.command("update [project_id]").description("Update project settings").option("--name <name>", "Rename the project").option("--visibility <vis>", "Set visibility: public or private").addHelpText("after", `
2023
+ Examples:
2024
+ $ blink project update --visibility private
2025
+ $ blink project update --name "New Name"
2026
+ $ blink project update proj_xxx --visibility public
2027
+ $ blink project update --visibility private --name "Secret Project"
2028
+ `).action(async (projectArg, opts) => {
2029
+ requireToken();
2030
+ const projectId = requireProjectId(projectArg);
2031
+ const body = {};
2032
+ if (opts.name) body.name = opts.name;
2033
+ if (opts.visibility) body.visibility = opts.visibility;
2034
+ if (!Object.keys(body).length) {
2035
+ console.error("Error: Provide at least one option (--name, --visibility)");
2036
+ process.exit(1);
2037
+ }
2038
+ const result = await withSpinner(
2039
+ "Updating project...",
2040
+ () => appRequest(`/api/projects/${projectId}`, { method: "PATCH", body })
2041
+ );
2042
+ if (isJsonMode()) return printJson(result);
2043
+ printSuccess("Project updated");
2044
+ if (opts.name) printKv("Name", opts.name);
2045
+ if (opts.visibility) printKv("Visibility", opts.visibility);
2046
+ });
2022
2047
  program2.command("link [project_id]").description("Link current directory to a project \u2014 saves project_id to .blink/project.json").addHelpText("after", `
2023
2048
  Examples:
2024
2049
  $ blink link Interactive picker \u2014 choose from your projects
@@ -3411,13 +3436,14 @@ init_project();
3411
3436
  import { basename as basename3 } from "path";
3412
3437
  import chalk20 from "chalk";
3413
3438
  function registerInitCommands(program2) {
3414
- 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", `
3439
+ 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("--visibility <vis>", "Project visibility: public or private", "public").option("--from <project_id>", "Create a new project named after an existing one").addHelpText("after", `
3415
3440
  Creates a new Blink project and writes .blink/project.json in the current directory.
3416
3441
  After init, all commands work without specifying a project_id.
3417
3442
 
3418
3443
  Examples:
3419
3444
  $ blink init Create project named after current dir
3420
3445
  $ blink init --name "My SaaS App" Create with custom name
3446
+ $ blink init --visibility private Create a private project
3421
3447
  $ blink init --from proj_xxx Clone from existing project
3422
3448
  $ blink init --json Machine-readable output
3423
3449
 
@@ -3428,13 +3454,13 @@ After init:
3428
3454
  `).action(async (opts) => {
3429
3455
  requireToken();
3430
3456
  if (opts.from) {
3431
- await initFromExisting(opts.from);
3457
+ await initFromExisting(opts.from, opts.visibility);
3432
3458
  } else {
3433
- await initNew(opts.name);
3459
+ await initNew(opts.name, opts.visibility);
3434
3460
  }
3435
3461
  });
3436
3462
  }
3437
- async function initNew(nameOpt) {
3463
+ async function initNew(nameOpt, visibility) {
3438
3464
  const name = nameOpt ?? basename3(process.cwd());
3439
3465
  const result = await withSpinner(
3440
3466
  `Creating project "${name}"...`,
@@ -3445,14 +3471,18 @@ async function initNew(nameOpt) {
3445
3471
  printError("Failed to create project \u2014 no ID returned");
3446
3472
  process.exit(1);
3447
3473
  }
3474
+ if (visibility === "private") {
3475
+ await appRequest(`/api/projects/${proj.id}`, { method: "PATCH", body: { visibility: "private" } });
3476
+ }
3448
3477
  writeProjectConfig({ projectId: proj.id });
3449
- if (isJsonMode()) return printJson({ project_id: proj.id, name: proj.name ?? name });
3478
+ if (isJsonMode()) return printJson({ project_id: proj.id, name: proj.name ?? name, visibility: visibility ?? "public" });
3450
3479
  printSuccess(`Project created and linked`);
3451
3480
  printKv("ID", proj.id);
3452
3481
  printKv("Name", proj.name ?? name);
3482
+ if (visibility === "private") printKv("Visibility", "private");
3453
3483
  console.log(chalk20.dim("\n Run `blink deploy ./dist --prod` to deploy"));
3454
3484
  }
3455
- async function initFromExisting(sourceId) {
3485
+ async function initFromExisting(sourceId, visibility) {
3456
3486
  const source = await withSpinner(
3457
3487
  "Loading source project...",
3458
3488
  () => appRequest(`/api/projects/${sourceId}`)
@@ -3663,19 +3693,14 @@ Examples:
3663
3693
 
3664
3694
  // src/commands/billing.ts
3665
3695
  import chalk23 from "chalk";
3666
- function printUsage(data) {
3667
- console.log();
3668
- printKv("Total", `${data.total ?? 0} credits used`);
3669
- const breakdown = data.breakdown ?? [];
3670
- if (breakdown.length) {
3671
- console.log();
3672
- console.log(chalk23.bold("Breakdown:"));
3673
- for (const item of breakdown) printKv(` ${item.category}`, `${item.amount}`);
3674
- }
3675
- console.log();
3696
+ function formatCredits(n) {
3697
+ return n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : n.toFixed(1);
3698
+ }
3699
+ function formatUsd(n) {
3700
+ return `$${n.toFixed(2)}`;
3676
3701
  }
3677
3702
  function registerBillingCommands(program2) {
3678
- program2.command("credits").description("Check your credit balance and tier").addHelpText("after", `
3703
+ program2.command("credits").description("Check your credit usage this month").addHelpText("after", `
3679
3704
  Examples:
3680
3705
  $ blink credits
3681
3706
  $ blink credits --json
@@ -3686,15 +3711,23 @@ Examples:
3686
3711
  () => appRequest("/api/usage/summary?period=month")
3687
3712
  );
3688
3713
  if (isJsonMode()) return printJson(result);
3689
- const data = result ?? {};
3714
+ const buckets = result?.data ?? [];
3715
+ const current = buckets[0];
3690
3716
  console.log();
3691
- printKv("Used", `${data.total ?? 0} credits this month`);
3717
+ if (current) {
3718
+ printKv("Period", current.time_bucket.slice(0, 7));
3719
+ printKv("Credits", formatCredits(current.total_credits));
3720
+ printKv("Requests", String(current.total_requests));
3721
+ printKv("Cost", formatUsd(current.total_cost_usd));
3722
+ } else {
3723
+ console.log(chalk23.dim(" No usage data for this month."));
3724
+ }
3692
3725
  console.log();
3693
3726
  });
3694
- program2.command("usage").description("Show usage summary for the current billing period").option("--period <granularity>", "Period granularity: hour, day, week, month", "day").addHelpText("after", `
3727
+ program2.command("usage").description("Show usage summary for the current billing period").option("--period <granularity>", "Period granularity: hour, day, week, month", "month").addHelpText("after", `
3695
3728
  Examples:
3696
3729
  $ blink usage
3697
- $ blink usage --period month
3730
+ $ blink usage --period day
3698
3731
  $ blink usage --json
3699
3732
  `).action(async (opts) => {
3700
3733
  requireToken();
@@ -3703,7 +3736,16 @@ Examples:
3703
3736
  () => appRequest(`/api/usage/summary?period=${opts.period}`)
3704
3737
  );
3705
3738
  if (isJsonMode()) return printJson(result);
3706
- printUsage(result);
3739
+ const buckets = result?.data ?? [];
3740
+ if (!buckets.length) {
3741
+ console.log(chalk23.dim("\n No usage data.\n"));
3742
+ return;
3743
+ }
3744
+ const table = createTable(["Period", "Credits", "Requests", "Cost"]);
3745
+ for (const b of buckets) {
3746
+ table.push([b.time_bucket.slice(0, 10), formatCredits(b.total_credits), String(b.total_requests), formatUsd(b.total_cost_usd)]);
3747
+ }
3748
+ console.log(table.toString());
3707
3749
  });
3708
3750
  }
3709
3751
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blinkdotnew/cli",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Blink CLI — full-stack cloud infrastructure from your terminal. Deploy, database, auth, storage, backend, domains, and more.",
5
5
  "bin": {
6
6
  "blink": "dist/cli.js"
@@ -1,24 +1,28 @@
1
1
  import { Command } from 'commander'
2
2
  import { appRequest } from '../lib/api-app.js'
3
3
  import { requireToken } from '../lib/auth.js'
4
- import { printJson, printKv, isJsonMode, withSpinner } from '../lib/output.js'
4
+ import { printJson, printKv, isJsonMode, withSpinner, createTable } from '../lib/output.js'
5
5
  import chalk from 'chalk'
6
6
 
7
- function printUsage(data: { total?: number; breakdown?: Array<{ category: string; amount: number }> }) {
8
- console.log()
9
- printKv('Total', `${data.total ?? 0} credits used`)
10
- const breakdown = data.breakdown ?? []
11
- if (breakdown.length) {
12
- console.log()
13
- console.log(chalk.bold('Breakdown:'))
14
- for (const item of breakdown) printKv(` ${item.category}`, `${item.amount}`)
15
- }
16
- console.log()
7
+ interface UsageBucket {
8
+ period: string
9
+ time_bucket: string
10
+ total_credits: number
11
+ total_requests: number
12
+ total_cost_usd: number
13
+ }
14
+
15
+ function formatCredits(n: number): string {
16
+ return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : n.toFixed(1)
17
+ }
18
+
19
+ function formatUsd(n: number): string {
20
+ return `$${n.toFixed(2)}`
17
21
  }
18
22
 
19
23
  export function registerBillingCommands(program: Command) {
20
24
  program.command('credits')
21
- .description('Check your credit balance and tier')
25
+ .description('Check your credit usage this month')
22
26
  .addHelpText('after', `
23
27
  Examples:
24
28
  $ blink credits
@@ -30,19 +34,27 @@ Examples:
30
34
  appRequest('/api/usage/summary?period=month')
31
35
  )
32
36
  if (isJsonMode()) return printJson(result)
33
- const data = result ?? {}
37
+ const buckets: UsageBucket[] = result?.data ?? []
38
+ const current = buckets[0]
34
39
  console.log()
35
- printKv('Used', `${data.total ?? 0} credits this month`)
40
+ if (current) {
41
+ printKv('Period', current.time_bucket.slice(0, 7))
42
+ printKv('Credits', formatCredits(current.total_credits))
43
+ printKv('Requests', String(current.total_requests))
44
+ printKv('Cost', formatUsd(current.total_cost_usd))
45
+ } else {
46
+ console.log(chalk.dim(' No usage data for this month.'))
47
+ }
36
48
  console.log()
37
49
  })
38
50
 
39
51
  program.command('usage')
40
52
  .description('Show usage summary for the current billing period')
41
- .option('--period <granularity>', 'Period granularity: hour, day, week, month', 'day')
53
+ .option('--period <granularity>', 'Period granularity: hour, day, week, month', 'month')
42
54
  .addHelpText('after', `
43
55
  Examples:
44
56
  $ blink usage
45
- $ blink usage --period month
57
+ $ blink usage --period day
46
58
  $ blink usage --json
47
59
  `)
48
60
  .action(async (opts) => {
@@ -51,6 +63,15 @@ Examples:
51
63
  appRequest(`/api/usage/summary?period=${opts.period}`)
52
64
  )
53
65
  if (isJsonMode()) return printJson(result)
54
- printUsage(result)
66
+ const buckets: UsageBucket[] = result?.data ?? []
67
+ if (!buckets.length) {
68
+ console.log(chalk.dim('\n No usage data.\n'))
69
+ return
70
+ }
71
+ const table = createTable(['Period', 'Credits', 'Requests', 'Cost'])
72
+ for (const b of buckets) {
73
+ table.push([b.time_bucket.slice(0, 10), formatCredits(b.total_credits), String(b.total_requests), formatUsd(b.total_cost_usd)])
74
+ }
75
+ console.log(table.toString())
55
76
  })
56
77
  }
@@ -10,6 +10,7 @@ export function registerInitCommands(program: Command) {
10
10
  program.command('init')
11
11
  .description('Initialize a new Blink project and link it to the current directory')
12
12
  .option('--name <name>', 'Project name (defaults to current directory name)')
13
+ .option('--visibility <vis>', 'Project visibility: public or private', 'public')
13
14
  .option('--from <project_id>', 'Create a new project named after an existing one')
14
15
  .addHelpText('after', `
15
16
  Creates a new Blink project and writes .blink/project.json in the current directory.
@@ -18,6 +19,7 @@ After init, all commands work without specifying a project_id.
18
19
  Examples:
19
20
  $ blink init Create project named after current dir
20
21
  $ blink init --name "My SaaS App" Create with custom name
22
+ $ blink init --visibility private Create a private project
21
23
  $ blink init --from proj_xxx Clone from existing project
22
24
  $ blink init --json Machine-readable output
23
25
 
@@ -29,14 +31,14 @@ After init:
29
31
  .action(async (opts) => {
30
32
  requireToken()
31
33
  if (opts.from) {
32
- await initFromExisting(opts.from)
34
+ await initFromExisting(opts.from, opts.visibility)
33
35
  } else {
34
- await initNew(opts.name)
36
+ await initNew(opts.name, opts.visibility)
35
37
  }
36
38
  })
37
39
  }
38
40
 
39
- async function initNew(nameOpt?: string) {
41
+ async function initNew(nameOpt?: string, visibility?: string) {
40
42
  const name = nameOpt ?? basename(process.cwd())
41
43
  const result = await withSpinner(`Creating project "${name}"...`, () =>
42
44
  appRequest('/api/projects/create', { method: 'POST', body: { prompt: name } })
@@ -46,15 +48,19 @@ async function initNew(nameOpt?: string) {
46
48
  printError('Failed to create project — no ID returned')
47
49
  process.exit(1)
48
50
  }
51
+ if (visibility === 'private') {
52
+ await appRequest(`/api/projects/${proj.id}`, { method: 'PATCH', body: { visibility: 'private' } })
53
+ }
49
54
  writeProjectConfig({ projectId: proj.id })
50
- if (isJsonMode()) return printJson({ project_id: proj.id, name: proj.name ?? name })
55
+ if (isJsonMode()) return printJson({ project_id: proj.id, name: proj.name ?? name, visibility: visibility ?? 'public' })
51
56
  printSuccess(`Project created and linked`)
52
57
  printKv('ID', proj.id)
53
58
  printKv('Name', proj.name ?? name)
59
+ if (visibility === 'private') printKv('Visibility', 'private')
54
60
  console.log(chalk.dim('\n Run `blink deploy ./dist --prod` to deploy'))
55
61
  }
56
62
 
57
- async function initFromExisting(sourceId: string) {
63
+ async function initFromExisting(sourceId: string, visibility?: string) {
58
64
  const source = await withSpinner('Loading source project...', () =>
59
65
  appRequest(`/api/projects/${sourceId}`)
60
66
  )
@@ -1,8 +1,8 @@
1
1
  import { Command } from 'commander'
2
2
  import { appRequest } from '../lib/api-app.js'
3
3
  import { requireToken } from '../lib/auth.js'
4
- import { writeProjectConfig, clearProjectConfig, readProjectConfig } from '../lib/project.js'
5
- import { printJson, isJsonMode, withSpinner, createTable } from '../lib/output.js'
4
+ import { writeProjectConfig, clearProjectConfig, readProjectConfig, requireProjectId } from '../lib/project.js'
5
+ import { printJson, printSuccess, isJsonMode, withSpinner, createTable, printKv } from '../lib/output.js'
6
6
  import chalk from 'chalk'
7
7
 
8
8
  export function registerProjectCommands(program: Command) {
@@ -60,6 +60,36 @@ After creating a project, link it to your current directory:
60
60
  else printJson({ status: 'ok' })
61
61
  })
62
62
 
63
+ project.command('update [project_id]')
64
+ .description('Update project settings')
65
+ .option('--name <name>', 'Rename the project')
66
+ .option('--visibility <vis>', 'Set visibility: public or private')
67
+ .addHelpText('after', `
68
+ Examples:
69
+ $ blink project update --visibility private
70
+ $ blink project update --name "New Name"
71
+ $ blink project update proj_xxx --visibility public
72
+ $ blink project update --visibility private --name "Secret Project"
73
+ `)
74
+ .action(async (projectArg: string | undefined, opts) => {
75
+ requireToken()
76
+ const projectId = requireProjectId(projectArg)
77
+ const body: Record<string, string> = {}
78
+ if (opts.name) body.name = opts.name
79
+ if (opts.visibility) body.visibility = opts.visibility
80
+ if (!Object.keys(body).length) {
81
+ console.error('Error: Provide at least one option (--name, --visibility)')
82
+ process.exit(1)
83
+ }
84
+ const result = await withSpinner('Updating project...', () =>
85
+ appRequest(`/api/projects/${projectId}`, { method: 'PATCH', body })
86
+ )
87
+ if (isJsonMode()) return printJson(result)
88
+ printSuccess('Project updated')
89
+ if (opts.name) printKv('Name', opts.name)
90
+ if (opts.visibility) printKv('Visibility', opts.visibility)
91
+ })
92
+
63
93
  program.command('link [project_id]')
64
94
  .description('Link current directory to a project — saves project_id to .blink/project.json')
65
95
  .addHelpText('after', `