@barekey/cli 0.4.0 → 0.5.0

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.
Files changed (60) hide show
  1. package/README.md +53 -12
  2. package/bun.lock +9 -3
  3. package/dist/auth-provider.js +7 -4
  4. package/dist/command-utils.js +6 -6
  5. package/dist/commands/audit.d.ts +2 -0
  6. package/dist/commands/audit.js +47 -0
  7. package/dist/commands/auth.js +22 -7
  8. package/dist/commands/billing.d.ts +2 -0
  9. package/dist/commands/billing.js +62 -0
  10. package/dist/commands/env.js +157 -125
  11. package/dist/commands/init.d.ts +2 -0
  12. package/dist/commands/init.js +32 -0
  13. package/dist/commands/org.d.ts +2 -0
  14. package/dist/commands/org.js +85 -0
  15. package/dist/commands/project.d.ts +2 -0
  16. package/dist/commands/project.js +99 -0
  17. package/dist/commands/stage.d.ts +2 -0
  18. package/dist/commands/stage.js +125 -0
  19. package/dist/commands/target-prompts.d.ts +184 -0
  20. package/dist/commands/target-prompts.js +312 -0
  21. package/dist/commands/typegen.d.ts +2 -2
  22. package/dist/commands/typegen.js +57 -32
  23. package/dist/constants.d.ts +1 -1
  24. package/dist/constants.js +1 -1
  25. package/dist/context/session-id.d.ts +11 -0
  26. package/dist/context/session-id.js +14 -0
  27. package/dist/contracts/index.d.ts +491 -0
  28. package/dist/contracts/index.js +307 -0
  29. package/dist/credentials-store.js +70 -11
  30. package/dist/http.d.ts +34 -0
  31. package/dist/http.js +56 -2
  32. package/dist/index.js +12 -0
  33. package/dist/runtime-config.js +14 -26
  34. package/dist/typegen/core.d.ts +45 -0
  35. package/dist/typegen/core.js +219 -0
  36. package/dist/types.d.ts +5 -3
  37. package/package.json +2 -2
  38. package/src/auth-provider.ts +8 -5
  39. package/src/command-utils.ts +6 -6
  40. package/src/commands/audit.ts +63 -0
  41. package/src/commands/auth.ts +32 -37
  42. package/src/commands/billing.ts +73 -0
  43. package/src/commands/env.ts +211 -218
  44. package/src/commands/init.ts +47 -0
  45. package/src/commands/org.ts +104 -0
  46. package/src/commands/project.ts +130 -0
  47. package/src/commands/stage.ts +167 -0
  48. package/src/commands/target-prompts.ts +357 -0
  49. package/src/commands/typegen.ts +71 -45
  50. package/src/constants.ts +1 -1
  51. package/src/context/session-id.ts +14 -0
  52. package/src/contracts/index.ts +370 -0
  53. package/src/credentials-store.ts +86 -12
  54. package/src/http.ts +78 -2
  55. package/src/index.ts +12 -0
  56. package/src/runtime-config.ts +19 -32
  57. package/src/typegen/core.ts +311 -0
  58. package/src/types.ts +5 -3
  59. package/test/command-utils.test.ts +47 -0
  60. package/test/credentials-store.test.ts +40 -0
package/README.md CHANGED
@@ -1,34 +1,75 @@
1
1
  # @barekey/cli
2
2
 
3
- CLI for logging into Barekey, managing environment variables, and pulling resolved values into local workflows.
3
+ CLI for Barekey login, variable management, pull workflows, and SDK type generation.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- bun add -g @barekey/cli
8
+ npm install -g @barekey/cli
9
9
  ```
10
10
 
11
11
  ## Quickstart
12
12
 
13
13
  ```bash
14
14
  barekey auth login
15
- barekey env list --org acme --project api --stage development
16
- barekey env get DATABASE_URL --org acme --project api --stage development
17
- barekey env pull --org acme --project api --stage development --out .env
15
+ barekey auth whoami
16
+ ```
17
+
18
+ Create `barekey.json`:
19
+
20
+ ```json
21
+ {
22
+ "organization": "acme",
23
+ "project": "web",
24
+ "environment": "development"
25
+ }
26
+ ```
27
+
28
+ Create and read a variable:
29
+
30
+ ```bash
31
+ barekey env new DATABASE_URL "postgres://localhost:5432/app" --type string
32
+ barekey env get DATABASE_URL
33
+ ```
34
+
35
+ Update it:
36
+
37
+ ```bash
38
+ barekey env set DATABASE_URL "postgres://localhost:5432/app_v2"
39
+ ```
40
+
41
+ Pull local files:
42
+
43
+ ```bash
44
+ barekey env pull --out .env.local
45
+ barekey env pull --format json --out barekey.local.json
46
+ ```
47
+
48
+ Generate SDK types:
49
+
50
+ ```bash
51
+ barekey typegen
52
+ barekey typegen --watch
18
53
  ```
19
54
 
20
55
  ## Common commands
21
56
 
22
57
  ```bash
23
- barekey auth whoami
24
- barekey env new FEATURE_FLAG true --type boolean --org acme --project api --stage development
25
- barekey env set CHECKOUT_COPY original --ab redesign --chance 0.25 --org acme --project api --stage development
26
- barekey env delete FEATURE_FLAG --yes --org acme --project api --stage development
27
- barekey env get-many --names DATABASE_URL,FEATURE_FLAG --org acme --project api --stage development
28
- barekey typegen --org acme --project api --stage development
29
- barekey typegen --watch --org acme --project api --stage development
58
+ barekey env list
59
+ barekey env get-many --names DATABASE_URL,REDIS_URL
60
+ barekey env new FEATURE_ENABLED true --type boolean
61
+ barekey env set PUBLIC_TITLE "Barekey Docs" --visibility public
62
+ barekey env set CHECKOUT_FLOW control --ab treatment --chance 0.5
63
+ barekey env delete FEATURE_ENABLED --yes
30
64
  ```
31
65
 
66
+ ## Notes
67
+
68
+ - `env new` creates with an initial value.
69
+ - `env set` is the upsert command.
70
+ - `get-many` uses a comma-separated `--names` value.
71
+ - Target resolution comes from flags, `barekey.json`, and the stored login org.
72
+
32
73
  ## Development
33
74
 
34
75
  ```bash
package/bun.lock CHANGED
@@ -5,9 +5,9 @@
5
5
  "": {
6
6
  "name": "@barekey/cli",
7
7
  "dependencies": {
8
- "@barekey/sdk": "^0.5.0",
9
8
  "@clack/prompts": "^0.11.0",
10
9
  "commander": "^14.0.1",
10
+ "effect": "3.19.19",
11
11
  "open": "^10.2.0",
12
12
  "picocolors": "^1.1.1",
13
13
  },
@@ -18,12 +18,12 @@
18
18
  },
19
19
  },
20
20
  "packages": {
21
- "@barekey/sdk": ["@barekey/sdk@0.5.0", "", {}, "sha512-+8RoKix9DZee3ZheGP5wJLnet6XaEGHTfcJOuvtJLmfQKRBllUy+wevsvELi1wOjw42pWaG3DYyb68XHznZRQw=="],
22
-
23
21
  "@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="],
24
22
 
25
23
  "@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="],
26
24
 
25
+ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
26
+
27
27
  "@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
28
28
 
29
29
  "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
@@ -36,6 +36,10 @@
36
36
 
37
37
  "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
38
38
 
39
+ "effect": ["effect@3.19.19", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-Yc8U/SVXo2dHnaP7zNBlAo83h/nzSJpi7vph6Hzyl4ulgMBIgPmz3UzOjb9sBgpFE00gC0iETR244sfXDNLHRg=="],
40
+
41
+ "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="],
42
+
39
43
  "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
40
44
 
41
45
  "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
@@ -46,6 +50,8 @@
46
50
 
47
51
  "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
48
52
 
53
+ "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
54
+
49
55
  "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
50
56
 
51
57
  "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
@@ -1,5 +1,6 @@
1
1
  import { postJson } from "./http.js";
2
2
  import { loadConfig, loadCredentials, saveCredentials } from "./credentials-store.js";
3
+ import { buildSessionId } from "./context/session-id.js";
3
4
  export function createCliAuthProvider() {
4
5
  let cachedCredentials = null;
5
6
  let forceRefresh = false;
@@ -8,19 +9,19 @@ export function createCliAuthProvider() {
8
9
  if (config === null) {
9
10
  throw new Error("Not logged in. Run barekey login first.");
10
11
  }
11
- const credentials = await loadCredentials(config.activeAccountId);
12
+ const credentials = await loadCredentials(config.activeSessionId);
12
13
  if (credentials === null) {
13
14
  throw new Error("CLI credentials are missing. Run barekey login again.");
14
15
  }
15
16
  cachedCredentials = credentials;
16
17
  return {
17
18
  baseUrl: config.baseUrl,
18
- accountId: config.activeAccountId,
19
+ sessionId: config.activeSessionId,
19
20
  credentials,
20
21
  };
21
22
  }
22
23
  async function refreshIfNeeded() {
23
- const { baseUrl, accountId, credentials } = await readCurrentCredentials();
24
+ const { baseUrl, credentials } = await readCurrentCredentials();
24
25
  const now = Date.now();
25
26
  if (!forceRefresh && credentials.accessTokenExpiresAtMs > now + 10_000) {
26
27
  return credentials;
@@ -40,8 +41,10 @@ export function createCliAuthProvider() {
40
41
  clerkUserId: refreshed.clerkUserId,
41
42
  orgId: refreshed.orgId,
42
43
  orgSlug: refreshed.orgSlug,
44
+ lastOrgId: refreshed.orgId,
45
+ lastOrgSlug: refreshed.orgSlug,
43
46
  };
44
- await saveCredentials(accountId, nextCredentials);
47
+ await saveCredentials(buildSessionId(baseUrl, refreshed.clerkUserId), nextCredentials);
45
48
  cachedCredentials = nextCredentials;
46
49
  forceRefresh = false;
47
50
  return nextCredentials;
@@ -27,13 +27,13 @@ export async function requireLocalSession() {
27
27
  if (config === null) {
28
28
  throw new Error("Not logged in. Run barekey auth login first.");
29
29
  }
30
- const credentials = await loadCredentials(config.activeAccountId);
30
+ const credentials = await loadCredentials(config.activeSessionId);
31
31
  if (credentials === null) {
32
32
  throw new Error("Saved credentials not found. Run barekey auth login again.");
33
33
  }
34
34
  return {
35
35
  baseUrl: config.baseUrl,
36
- accountId: config.activeAccountId,
36
+ accountId: config.activeSessionId,
37
37
  credentials,
38
38
  };
39
39
  }
@@ -42,12 +42,12 @@ export async function resolveTarget(options, local) {
42
42
  const isStandalone = runtime?.config.config?.mode === "standalone";
43
43
  const projectSlug = options.project?.trim() || runtime?.config.project || "";
44
44
  const stageSlug = options.stage?.trim() || runtime?.config.environment || "";
45
- const orgSlug = options.org?.trim() || runtime?.config.org || local?.credentials.orgSlug || "";
46
- if (!isStandalone && (projectSlug.length === 0 || stageSlug.length === 0)) {
45
+ const orgSlug = options.org?.trim() || runtime?.config.org || "";
46
+ if (!isStandalone && (orgSlug.length === 0 || projectSlug.length === 0 || stageSlug.length === 0)) {
47
47
  const hint = runtime
48
- ? `Found ${runtime.path} but project/environment is incomplete.`
48
+ ? `Found ${runtime.path} but organization/project/environment is incomplete.`
49
49
  : "No barekey.json found in current directory tree.";
50
- throw new Error(`${hint} Pass --project/--stage, or create barekey.json with {"organization":"...","project":"...","environment":"..."}.`);
50
+ throw new Error(`${hint} Run barekey init or pass --org/--project/--stage.`);
51
51
  }
52
52
  return {
53
53
  projectSlug,
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerAuditCommands(program: Command): void;
@@ -0,0 +1,47 @@
1
+ import { createCliAuthProvider } from "../auth-provider.js";
2
+ import { requireLocalSession, toJsonOutput } from "../command-utils.js";
3
+ import { AuditListResponseSchema } from "../contracts/index.js";
4
+ import { postJson } from "../http.js";
5
+ import { promptForOrganizationSlug } from "./target-prompts.js";
6
+ async function runAuditList(options) {
7
+ const local = await requireLocalSession();
8
+ const authProvider = createCliAuthProvider();
9
+ const accessToken = await authProvider.getAccessToken();
10
+ const orgSlug = await promptForOrganizationSlug(options.org);
11
+ const limit = options.limit ? Number.parseInt(options.limit, 10) : 25;
12
+ const response = await postJson({
13
+ baseUrl: local.baseUrl,
14
+ path: "/v1/cli/audit/list",
15
+ accessToken,
16
+ payload: {
17
+ orgSlug,
18
+ projectSlug: options.project?.trim() || null,
19
+ limit: Number.isFinite(limit) ? limit : 25,
20
+ },
21
+ schema: AuditListResponseSchema,
22
+ });
23
+ if (options.json) {
24
+ toJsonOutput(true, response);
25
+ return;
26
+ }
27
+ if (response.items.length === 0) {
28
+ console.log("No audit events found.");
29
+ return;
30
+ }
31
+ for (const item of response.items) {
32
+ console.log(`${new Date(item.occurredAtMs).toISOString()} ${item.category} ${item.title}`);
33
+ }
34
+ }
35
+ export function registerAuditCommands(program) {
36
+ const audit = program.command("audit").description("Audit history");
37
+ audit
38
+ .command("list")
39
+ .description("List recent audit events")
40
+ .option("--org <slug>", "Organization slug")
41
+ .option("--project <slug>", "Project slug")
42
+ .option("--limit <count>", "Maximum number of events", "25")
43
+ .option("--json", "Machine-readable output", false)
44
+ .action(async (options) => {
45
+ await runAuditList(options);
46
+ });
47
+ }
@@ -4,7 +4,9 @@ import { intro, outro, spinner } from "@clack/prompts";
4
4
  import pc from "picocolors";
5
5
  import open from "open";
6
6
  import { createCliAuthProvider } from "../auth-provider.js";
7
+ import { buildSessionId } from "../context/session-id.js";
7
8
  import { clearConfig, deleteCredentials, saveConfig, saveCredentials, } from "../credentials-store.js";
9
+ import { CliSessionResponseSchema, DevicePollResponseSchema, DeviceStartResponseSchema, RefreshResponseSchema, } from "../contracts/index.js";
8
10
  import { getJson, postJson } from "../http.js";
9
11
  import { requireLocalSession, resolveBaseUrl, toJsonOutput } from "../command-utils.js";
10
12
  function resolveClientName() {
@@ -48,6 +50,7 @@ async function runLogin(options) {
48
50
  payload: {
49
51
  clientName: resolveClientName(),
50
52
  },
53
+ schema: DeviceStartResponseSchema,
51
54
  });
52
55
  const verificationUri = resolveVerificationUri(baseUrl, started.verificationUri);
53
56
  loading.stop("Authorization initialized");
@@ -72,28 +75,40 @@ async function runLogin(options) {
72
75
  payload: {
73
76
  deviceCode: started.deviceCode,
74
77
  },
78
+ schema: DevicePollResponseSchema,
75
79
  });
76
80
  if (poll.status === "pending") {
77
81
  await wait(Math.max(1, poll.intervalSec) * 1000);
78
82
  continue;
79
83
  }
80
- const accountId = `${poll.orgSlug}:${poll.clerkUserId}`;
81
- await saveCredentials(accountId, {
84
+ const refreshed = RefreshResponseSchema.make({
82
85
  accessToken: poll.accessToken,
83
86
  refreshToken: poll.refreshToken,
84
87
  accessTokenExpiresAtMs: poll.accessTokenExpiresAtMs,
85
88
  refreshTokenExpiresAtMs: poll.refreshTokenExpiresAtMs,
86
- clerkUserId: poll.clerkUserId,
87
89
  orgId: poll.orgId,
88
90
  orgSlug: poll.orgSlug,
91
+ clerkUserId: poll.clerkUserId,
92
+ });
93
+ const sessionId = buildSessionId(baseUrl, refreshed.clerkUserId);
94
+ await saveCredentials(sessionId, {
95
+ accessToken: refreshed.accessToken,
96
+ refreshToken: refreshed.refreshToken,
97
+ accessTokenExpiresAtMs: refreshed.accessTokenExpiresAtMs,
98
+ refreshTokenExpiresAtMs: refreshed.refreshTokenExpiresAtMs,
99
+ clerkUserId: refreshed.clerkUserId,
100
+ orgId: refreshed.orgId,
101
+ orgSlug: refreshed.orgSlug,
102
+ lastOrgId: refreshed.orgId,
103
+ lastOrgSlug: refreshed.orgSlug,
89
104
  });
90
105
  await saveConfig({
91
106
  baseUrl,
92
- activeAccountId: accountId,
107
+ activeSessionId: sessionId,
93
108
  });
94
109
  pollSpinner.stop("Login approved");
95
110
  pollActive = false;
96
- outro(`Logged in as ${pc.bold(poll.clerkUserId)} in ${pc.bold(poll.orgSlug)}.`);
111
+ outro(`Logged in as ${pc.bold(refreshed.clerkUserId)}.`);
97
112
  return;
98
113
  }
99
114
  pollSpinner.stop("Timed out");
@@ -131,13 +146,14 @@ async function runWhoami(options) {
131
146
  baseUrl: local.baseUrl,
132
147
  path: "/v1/cli/session",
133
148
  accessToken,
149
+ schema: CliSessionResponseSchema,
134
150
  });
135
151
  if (options.json) {
136
152
  toJsonOutput(true, session);
137
153
  return;
138
154
  }
139
155
  console.log(`${pc.bold("User")}: ${session.clerkUserId}`);
140
- console.log(`${pc.bold("Org")}: ${session.orgSlug}`);
156
+ console.log(`${pc.bold("Current org")}: ${session.orgSlug}`);
141
157
  console.log(`${pc.bold("Source")}: ${session.source}`);
142
158
  }
143
159
  export function registerAuthCommands(program) {
@@ -162,7 +178,6 @@ export function registerAuthCommands(program) {
162
178
  .action(async (options) => {
163
179
  await runWhoami(options);
164
180
  });
165
- // Backward-compatible top-level auth aliases.
166
181
  program
167
182
  .command("login")
168
183
  .description("Alias for barekey auth login")
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerBillingCommands(program: Command): void;
@@ -0,0 +1,62 @@
1
+ import { createCliAuthProvider } from "../auth-provider.js";
2
+ import { requireLocalSession, toJsonOutput } from "../command-utils.js";
3
+ import { BillingCatalogResponseSchema, BillingStatusResponseSchema } from "../contracts/index.js";
4
+ import { getJson, postJson } from "../http.js";
5
+ import { promptForOrganizationSlug } from "./target-prompts.js";
6
+ async function runBillingCatalog(options) {
7
+ const local = await requireLocalSession();
8
+ const authProvider = createCliAuthProvider();
9
+ const accessToken = await authProvider.getAccessToken();
10
+ const response = await getJson({
11
+ baseUrl: local.baseUrl,
12
+ path: "/v1/cli/billing/catalog",
13
+ accessToken,
14
+ schema: BillingCatalogResponseSchema,
15
+ });
16
+ if (options.json) {
17
+ toJsonOutput(true, response);
18
+ return;
19
+ }
20
+ console.log(`Plans: ${response.variants.length}`);
21
+ console.log(`Metered features: ${Object.values(response.featureIds).join(", ")}`);
22
+ }
23
+ async function runBillingStatus(options) {
24
+ const local = await requireLocalSession();
25
+ const authProvider = createCliAuthProvider();
26
+ const accessToken = await authProvider.getAccessToken();
27
+ const orgSlug = await promptForOrganizationSlug(options.org);
28
+ const response = await postJson({
29
+ baseUrl: local.baseUrl,
30
+ path: "/v1/cli/billing/status",
31
+ accessToken,
32
+ payload: {
33
+ orgSlug,
34
+ },
35
+ schema: BillingStatusResponseSchema,
36
+ });
37
+ if (options.json) {
38
+ toJsonOutput(true, response);
39
+ return;
40
+ }
41
+ console.log(`Tier: ${response.currentTier ?? "none"}`);
42
+ console.log(`Product: ${response.currentProductId ?? "none"}`);
43
+ console.log(`Can manage billing: ${response.canManageBilling ? "yes" : "no"}`);
44
+ }
45
+ export function registerBillingCommands(program) {
46
+ const billing = program.command("billing").description("Billing information");
47
+ billing
48
+ .command("catalog")
49
+ .description("Show the public billing catalog")
50
+ .option("--json", "Machine-readable output", false)
51
+ .action(async (options) => {
52
+ await runBillingCatalog(options);
53
+ });
54
+ billing
55
+ .command("status")
56
+ .description("Show billing status for an organization")
57
+ .option("--org <slug>", "Organization slug")
58
+ .option("--json", "Machine-readable output", false)
59
+ .action(async (options) => {
60
+ await runBillingStatus(options);
61
+ });
62
+ }