@barekey/cli 0.4.0 → 0.5.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.
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 +31 -9
  8. package/dist/commands/billing.d.ts +2 -0
  9. package/dist/commands/billing.js +59 -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 +499 -0
  28. package/dist/contracts/index.js +313 -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 +45 -39
  42. package/src/commands/billing.ts +70 -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 +376 -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
@@ -0,0 +1,45 @@
1
+ import { type TypegenManifest } from "../contracts/index.js";
2
+ export type CliTypegenMode = "semantic" | "minimal";
3
+ export type CliRuntimeMode = "centralized" | "standalone";
4
+ export type CliTypegenResult = {
5
+ written: boolean;
6
+ path: string;
7
+ serverPath: string;
8
+ publicPath: string;
9
+ manifestVersion: string;
10
+ };
11
+ type TypegenIdentity = {
12
+ baseUrl: string | null;
13
+ orgSlug: string;
14
+ projectSlug: string;
15
+ stageSlug: string;
16
+ typegenMode: CliTypegenMode;
17
+ runtimeMode: CliRuntimeMode;
18
+ localEnvRoot: string | null;
19
+ };
20
+ /**
21
+ * Renders the generated SDK declaration files for one manifest.
22
+ *
23
+ * @param manifest The decoded typegen manifest.
24
+ * @param mode The requested typegen mode.
25
+ * @returns The rendered server and public declaration contents.
26
+ * @remarks The CLI owns this rendering so it no longer depends on SDK runtime helpers.
27
+ * @lastModified 2026-03-19
28
+ * @author GPT-5.4
29
+ */
30
+ export declare function renderGeneratedTypesForManifest(manifest: TypegenManifest, mode: CliTypegenMode): {
31
+ serverContents: string;
32
+ publicContents: string;
33
+ };
34
+ /**
35
+ * Writes generated SDK declaration files into the installed `@barekey/sdk` package.
36
+ *
37
+ * @param manifest The decoded typegen manifest.
38
+ * @param identity The manifest identity used for metadata freshness.
39
+ * @returns The typegen write result.
40
+ * @remarks This preserves the existing generated file locations without importing the SDK package at runtime.
41
+ * @lastModified 2026-03-19
42
+ * @author GPT-5.4
43
+ */
44
+ export declare function writeInstalledSdkGeneratedTypes(manifest: TypegenManifest, identity: TypegenIdentity): Promise<CliTypegenResult>;
45
+ export {};
@@ -0,0 +1,219 @@
1
+ import { createRequire } from "node:module";
2
+ import { access, readFile, rename, unlink, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { Schema } from "effect";
5
+ import { TypegenManifestSchema } from "../contracts/index.js";
6
+ const MANIFEST_VERSION_PATTERN = /\/\* barekey-manifest-version: ([^\n]+) \*\//;
7
+ const TypegenMetadataSchema = Schema.Struct({
8
+ version: Schema.Number,
9
+ last: Schema.String,
10
+ baseUrl: Schema.NullOr(Schema.String),
11
+ orgSlug: Schema.NullOr(Schema.String),
12
+ projectSlug: Schema.NullOr(Schema.String),
13
+ stageSlug: Schema.NullOr(Schema.String),
14
+ typegenMode: Schema.NullOr(Schema.Literal("semantic", "minimal")),
15
+ runtimeMode: Schema.NullOr(Schema.Literal("centralized", "standalone")),
16
+ localEnvRoot: Schema.NullOr(Schema.String),
17
+ });
18
+ function renderLinearMilestones(milestones) {
19
+ if (milestones.length === 0) {
20
+ return "readonly []";
21
+ }
22
+ return `readonly [${milestones
23
+ .map((milestone) => `readonly [${JSON.stringify(milestone.at)}, ${String(milestone.percentage)}]`)
24
+ .join(", ")}]`;
25
+ }
26
+ function renderRolloutMetadataType(row) {
27
+ const renderedMilestones = renderLinearMilestones(row.rolloutMilestones ?? []);
28
+ if (row.rolloutFunction === "step") {
29
+ return `Step<${renderedMilestones}>`;
30
+ }
31
+ if (row.rolloutFunction === "ease_in_out") {
32
+ return `EaseInOut<${renderedMilestones}>`;
33
+ }
34
+ return `Linear<${renderedMilestones}>`;
35
+ }
36
+ function renderVariableType(row, mode) {
37
+ if (mode === "minimal") {
38
+ return row.typeScriptType;
39
+ }
40
+ const descriptor = `{ Type: ${row.typeScriptType}; Kind: ${JSON.stringify(row.kind)}; Visibility: ${JSON.stringify(row.visibility)}; Rollout: ${row.kind === "rollout" ? renderRolloutMetadataType(row) : "never"} }`;
41
+ return `Env<${descriptor}>`;
42
+ }
43
+ function buildGeneratedTypesContents(manifest, input) {
44
+ const mapLines = manifest.variables
45
+ .slice()
46
+ .filter(input.include)
47
+ .sort((left, right) => left.name.localeCompare(right.name))
48
+ .map((row) => ` ${JSON.stringify(row.name)}: ${renderVariableType(row, input.mode)};`)
49
+ .join("\n");
50
+ const importLine = input.mode === "semantic"
51
+ ? `import type { EaseInOut, Env, Linear, Step } from "${input.typeModulePath}";\n\n`
52
+ : "";
53
+ return `/* eslint-disable */\n/* This file is generated by barekey typegen. */\n/* barekey-manifest-version: ${manifest.manifestVersion} */\n\n${importLine}declare module "${input.declaredModulePath}" {\n interface ${input.interfaceName} {\n${mapLines.length > 0 ? mapLines : ""}\n }\n}\n\nexport {};\n`;
54
+ }
55
+ function readManifestVersion(contents) {
56
+ return contents?.match(MANIFEST_VERSION_PATTERN)?.[1]?.trim() ?? null;
57
+ }
58
+ async function readTextFile(filePath) {
59
+ try {
60
+ return await readFile(filePath, "utf8");
61
+ }
62
+ catch (error) {
63
+ const nodeError = error;
64
+ if (nodeError?.code === "ENOENT") {
65
+ return null;
66
+ }
67
+ throw error;
68
+ }
69
+ }
70
+ async function writeTextFileAtomic(filePath, value) {
71
+ const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
72
+ try {
73
+ await writeFile(temporaryPath, value, "utf8");
74
+ await rename(temporaryPath, filePath);
75
+ }
76
+ catch (error) {
77
+ try {
78
+ await unlink(temporaryPath);
79
+ }
80
+ catch {
81
+ // Best-effort cleanup only.
82
+ }
83
+ throw error;
84
+ }
85
+ }
86
+ async function resolveInstalledSdkTypegenTarget() {
87
+ const require = createRequire(path.join(process.cwd(), "__barekey_typegen__.cjs"));
88
+ let resolvedEntrypoint;
89
+ try {
90
+ resolvedEntrypoint = require.resolve("@barekey/sdk/package.json");
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ let current = path.dirname(resolvedEntrypoint);
96
+ while (true) {
97
+ const candidatePackageJson = path.join(current, "package.json");
98
+ const rawPackageJson = await readTextFile(candidatePackageJson);
99
+ if (rawPackageJson !== null) {
100
+ try {
101
+ const parsed = JSON.parse(rawPackageJson);
102
+ if (parsed.name === "@barekey/sdk") {
103
+ const serverGeneratedTypesPath = path.join(current, "generated.server.d.ts");
104
+ const publicGeneratedTypesPath = path.join(current, "generated.public.d.ts");
105
+ await access(serverGeneratedTypesPath);
106
+ await access(publicGeneratedTypesPath);
107
+ return {
108
+ packageRoot: current,
109
+ serverGeneratedTypesPath,
110
+ publicGeneratedTypesPath,
111
+ typegenMetadataPath: path.join(current, "typegen.json"),
112
+ };
113
+ }
114
+ }
115
+ catch {
116
+ // Keep walking upward.
117
+ }
118
+ }
119
+ const parent = path.dirname(current);
120
+ if (parent === current) {
121
+ return null;
122
+ }
123
+ current = parent;
124
+ }
125
+ }
126
+ function buildTypegenMetadataContents(lastGeneratedAt, identity) {
127
+ return `${JSON.stringify({
128
+ version: 1,
129
+ last: lastGeneratedAt.toISOString(),
130
+ baseUrl: identity.baseUrl,
131
+ orgSlug: identity.orgSlug,
132
+ projectSlug: identity.projectSlug,
133
+ stageSlug: identity.stageSlug,
134
+ typegenMode: identity.typegenMode,
135
+ runtimeMode: identity.runtimeMode,
136
+ localEnvRoot: identity.localEnvRoot,
137
+ }, null, 2)}\n`;
138
+ }
139
+ /**
140
+ * Renders the generated SDK declaration files for one manifest.
141
+ *
142
+ * @param manifest The decoded typegen manifest.
143
+ * @param mode The requested typegen mode.
144
+ * @returns The rendered server and public declaration contents.
145
+ * @remarks The CLI owns this rendering so it no longer depends on SDK runtime helpers.
146
+ * @lastModified 2026-03-19
147
+ * @author GPT-5.4
148
+ */
149
+ export function renderGeneratedTypesForManifest(manifest, mode) {
150
+ const decodedManifest = Schema.decodeUnknownSync(TypegenManifestSchema)(manifest);
151
+ return {
152
+ serverContents: buildGeneratedTypesContents(decodedManifest, {
153
+ mode,
154
+ typeModulePath: "./dist/types.js",
155
+ declaredModulePath: "./dist/types.js",
156
+ interfaceName: "BarekeyGeneratedTypeMap",
157
+ include: () => true,
158
+ }),
159
+ publicContents: buildGeneratedTypesContents(decodedManifest, {
160
+ mode,
161
+ typeModulePath: "./dist/public-types.js",
162
+ declaredModulePath: "./dist/public-types.js",
163
+ interfaceName: "BarekeyPublicGeneratedTypeMap",
164
+ include: (row) => row.visibility === "public",
165
+ }),
166
+ };
167
+ }
168
+ /**
169
+ * Writes generated SDK declaration files into the installed `@barekey/sdk` package.
170
+ *
171
+ * @param manifest The decoded typegen manifest.
172
+ * @param identity The manifest identity used for metadata freshness.
173
+ * @returns The typegen write result.
174
+ * @remarks This preserves the existing generated file locations without importing the SDK package at runtime.
175
+ * @lastModified 2026-03-19
176
+ * @author GPT-5.4
177
+ */
178
+ export async function writeInstalledSdkGeneratedTypes(manifest, identity) {
179
+ const target = await resolveInstalledSdkTypegenTarget();
180
+ if (target === null) {
181
+ return {
182
+ written: false,
183
+ path: "",
184
+ serverPath: "",
185
+ publicPath: "",
186
+ manifestVersion: manifest.manifestVersion,
187
+ };
188
+ }
189
+ const [existingServerContents, existingPublicContents] = await Promise.all([
190
+ readTextFile(target.serverGeneratedTypesPath),
191
+ readTextFile(target.publicGeneratedTypesPath),
192
+ ]);
193
+ const rendered = renderGeneratedTypesForManifest(manifest, identity.typegenMode);
194
+ if (existingServerContents === rendered.serverContents &&
195
+ existingPublicContents === rendered.publicContents &&
196
+ readManifestVersion(existingServerContents) === manifest.manifestVersion &&
197
+ readManifestVersion(existingPublicContents) === manifest.manifestVersion) {
198
+ await writeTextFileAtomic(target.typegenMetadataPath, buildTypegenMetadataContents(new Date(), identity));
199
+ return {
200
+ written: false,
201
+ path: target.serverGeneratedTypesPath,
202
+ serverPath: target.serverGeneratedTypesPath,
203
+ publicPath: target.publicGeneratedTypesPath,
204
+ manifestVersion: manifest.manifestVersion,
205
+ };
206
+ }
207
+ await Promise.all([
208
+ writeTextFileAtomic(target.serverGeneratedTypesPath, rendered.serverContents),
209
+ writeTextFileAtomic(target.publicGeneratedTypesPath, rendered.publicContents),
210
+ ]);
211
+ await writeTextFileAtomic(target.typegenMetadataPath, buildTypegenMetadataContents(new Date(), identity));
212
+ return {
213
+ written: true,
214
+ path: target.serverGeneratedTypesPath,
215
+ serverPath: target.serverGeneratedTypesPath,
216
+ publicPath: target.publicGeneratedTypesPath,
217
+ manifestVersion: manifest.manifestVersion,
218
+ };
219
+ }
package/dist/types.d.ts CHANGED
@@ -4,10 +4,12 @@ export type CliCredentials = {
4
4
  accessTokenExpiresAtMs: number;
5
5
  refreshTokenExpiresAtMs: number;
6
6
  clerkUserId: string;
7
- orgId: string;
8
- orgSlug: string;
7
+ orgId: string | null;
8
+ orgSlug: string | null;
9
+ lastOrgId?: string | null;
10
+ lastOrgSlug?: string | null;
9
11
  };
10
12
  export type CliConfig = {
11
13
  baseUrl: string;
12
- activeAccountId: string;
14
+ activeSessionId: string;
13
15
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barekey/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Barekey command line interface",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,8 +15,8 @@
15
15
  "packageManager": "bun@1.2.22",
16
16
  "dependencies": {
17
17
  "@clack/prompts": "^0.11.0",
18
- "@barekey/sdk": "^0.5.0",
19
18
  "commander": "^14.0.1",
19
+ "effect": "3.19.19",
20
20
  "open": "^10.2.0",
21
21
  "picocolors": "^1.1.1"
22
22
  },
@@ -1,6 +1,7 @@
1
1
  import { postJson } from "./http.js";
2
2
  import type { CliCredentials } from "./types.js";
3
3
  import { loadConfig, loadCredentials, saveCredentials } from "./credentials-store.js";
4
+ import { buildSessionId } from "./context/session-id.js";
4
5
 
5
6
  type CliAuthProvider = {
6
7
  getAccessToken(): Promise<string>;
@@ -13,7 +14,7 @@ export function createCliAuthProvider(): CliAuthProvider {
13
14
 
14
15
  async function readCurrentCredentials(): Promise<{
15
16
  baseUrl: string;
16
- accountId: string;
17
+ sessionId: string;
17
18
  credentials: CliCredentials;
18
19
  }> {
19
20
  const config = await loadConfig();
@@ -21,7 +22,7 @@ export function createCliAuthProvider(): CliAuthProvider {
21
22
  throw new Error("Not logged in. Run barekey login first.");
22
23
  }
23
24
 
24
- const credentials = await loadCredentials(config.activeAccountId);
25
+ const credentials = await loadCredentials(config.activeSessionId);
25
26
  if (credentials === null) {
26
27
  throw new Error("CLI credentials are missing. Run barekey login again.");
27
28
  }
@@ -30,13 +31,13 @@ export function createCliAuthProvider(): CliAuthProvider {
30
31
 
31
32
  return {
32
33
  baseUrl: config.baseUrl,
33
- accountId: config.activeAccountId,
34
+ sessionId: config.activeSessionId,
34
35
  credentials,
35
36
  };
36
37
  }
37
38
 
38
39
  async function refreshIfNeeded(): Promise<CliCredentials> {
39
- const { baseUrl, accountId, credentials } = await readCurrentCredentials();
40
+ const { baseUrl, credentials } = await readCurrentCredentials();
40
41
  const now = Date.now();
41
42
  if (!forceRefresh && credentials.accessTokenExpiresAtMs > now + 10_000) {
42
43
  return credentials;
@@ -66,9 +67,11 @@ export function createCliAuthProvider(): CliAuthProvider {
66
67
  clerkUserId: refreshed.clerkUserId,
67
68
  orgId: refreshed.orgId,
68
69
  orgSlug: refreshed.orgSlug,
70
+ lastOrgId: refreshed.orgId,
71
+ lastOrgSlug: refreshed.orgSlug,
69
72
  };
70
73
 
71
- await saveCredentials(accountId, nextCredentials);
74
+ await saveCredentials(buildSessionId(baseUrl, refreshed.clerkUserId), nextCredentials);
72
75
  cachedCredentials = nextCredentials;
73
76
  forceRefresh = false;
74
77
  return nextCredentials;
@@ -49,14 +49,14 @@ export async function requireLocalSession(): Promise<LocalSession> {
49
49
  throw new Error("Not logged in. Run barekey auth login first.");
50
50
  }
51
51
 
52
- const credentials = await loadCredentials(config.activeAccountId);
52
+ const credentials = await loadCredentials(config.activeSessionId);
53
53
  if (credentials === null) {
54
54
  throw new Error("Saved credentials not found. Run barekey auth login again.");
55
55
  }
56
56
 
57
57
  return {
58
58
  baseUrl: config.baseUrl,
59
- accountId: config.activeAccountId,
59
+ accountId: config.activeSessionId,
60
60
  credentials,
61
61
  };
62
62
  }
@@ -74,14 +74,14 @@ export async function resolveTarget(
74
74
 
75
75
  const projectSlug = options.project?.trim() || runtime?.config.project || "";
76
76
  const stageSlug = options.stage?.trim() || runtime?.config.environment || "";
77
- const orgSlug = options.org?.trim() || runtime?.config.org || local?.credentials.orgSlug || "";
77
+ const orgSlug = options.org?.trim() || runtime?.config.org || "";
78
78
 
79
- if (!isStandalone && (projectSlug.length === 0 || stageSlug.length === 0)) {
79
+ if (!isStandalone && (orgSlug.length === 0 || projectSlug.length === 0 || stageSlug.length === 0)) {
80
80
  const hint = runtime
81
- ? `Found ${runtime.path} but project/environment is incomplete.`
81
+ ? `Found ${runtime.path} but organization/project/environment is incomplete.`
82
82
  : "No barekey.json found in current directory tree.";
83
83
  throw new Error(
84
- `${hint} Pass --project/--stage, or create barekey.json with {"organization":"...","project":"...","environment":"..."}.`,
84
+ `${hint} Run barekey init or pass --org/--project/--stage.`,
85
85
  );
86
86
  }
87
87
 
@@ -0,0 +1,63 @@
1
+ import { Command } from "commander";
2
+
3
+ import { createCliAuthProvider } from "../auth-provider.js";
4
+ import { requireLocalSession, toJsonOutput, type EnvTargetOptions } from "../command-utils.js";
5
+ import { AuditListResponseSchema } from "../contracts/index.js";
6
+ import { postJson } from "../http.js";
7
+ import { promptForOrganizationSlug } from "./target-prompts.js";
8
+
9
+ async function runAuditList(
10
+ options: EnvTargetOptions & {
11
+ limit?: string;
12
+ json?: boolean;
13
+ },
14
+ ) {
15
+ const local = await requireLocalSession();
16
+ const authProvider = createCliAuthProvider();
17
+ const accessToken = await authProvider.getAccessToken();
18
+ const orgSlug = await promptForOrganizationSlug(options.org);
19
+ const limit = options.limit ? Number.parseInt(options.limit, 10) : 25;
20
+
21
+ const response = await postJson({
22
+ baseUrl: local.baseUrl,
23
+ path: "/v1/cli/audit/list",
24
+ accessToken,
25
+ payload: {
26
+ orgSlug,
27
+ projectSlug: options.project?.trim() || null,
28
+ limit: Number.isFinite(limit) ? limit : 25,
29
+ },
30
+ schema: AuditListResponseSchema,
31
+ });
32
+
33
+ if (options.json) {
34
+ toJsonOutput(true, response);
35
+ return;
36
+ }
37
+
38
+ if (response.items.length === 0) {
39
+ console.log("No audit events found.");
40
+ return;
41
+ }
42
+
43
+ for (const item of response.items) {
44
+ console.log(`${new Date(item.occurredAtMs).toISOString()} ${item.category} ${item.title}`);
45
+ }
46
+ }
47
+
48
+ export function registerAuditCommands(program: Command): void {
49
+ const audit = program.command("audit").description("Audit history");
50
+
51
+ audit
52
+ .command("list")
53
+ .description("List recent audit events")
54
+ .option("--org <slug>", "Organization slug")
55
+ .option("--project <slug>", "Project slug")
56
+ .option("--limit <count>", "Maximum number of events", "25")
57
+ .option("--json", "Machine-readable output", false)
58
+ .action(
59
+ async (options: EnvTargetOptions & { limit?: string; json?: boolean }) => {
60
+ await runAuditList(options);
61
+ },
62
+ );
63
+ }
@@ -7,12 +7,19 @@ import pc from "picocolors";
7
7
  import open from "open";
8
8
 
9
9
  import { createCliAuthProvider } from "../auth-provider.js";
10
+ import { buildSessionId } from "../context/session-id.js";
10
11
  import {
11
12
  clearConfig,
12
13
  deleteCredentials,
13
14
  saveConfig,
14
15
  saveCredentials,
15
16
  } from "../credentials-store.js";
17
+ import {
18
+ CliSessionResponseSchema,
19
+ DevicePollResponseSchema,
20
+ DeviceStartResponseSchema,
21
+ RefreshResponseSchema,
22
+ } from "../contracts/index.js";
16
23
  import { getJson, postJson } from "../http.js";
17
24
  import { requireLocalSession, resolveBaseUrl, toJsonOutput } from "../command-utils.js";
18
25
 
@@ -50,6 +57,14 @@ function resolveVerificationUri(baseUrl: string, verificationUri: string): strin
50
57
  return verificationUri;
51
58
  }
52
59
 
60
+ function resolveDisplayName(input: {
61
+ displayName: string | null;
62
+ clerkUserId: string;
63
+ }): string {
64
+ const displayName = input.displayName?.trim();
65
+ return displayName && displayName.length > 0 ? displayName : input.clerkUserId;
66
+ }
67
+
53
68
  async function runLogin(options: { baseUrl?: string }): Promise<void> {
54
69
  const baseUrl = await resolveBaseUrl(options.baseUrl);
55
70
  intro("Barekey CLI login");
@@ -60,18 +75,13 @@ async function runLogin(options: { baseUrl?: string }): Promise<void> {
60
75
  let pollActive = false;
61
76
 
62
77
  try {
63
- const started = await postJson<{
64
- deviceCode: string;
65
- userCode: string;
66
- verificationUri: string;
67
- intervalSec: number;
68
- expiresInSec: number;
69
- }>({
78
+ const started = await postJson({
70
79
  baseUrl,
71
80
  path: "/v1/cli/device/start",
72
81
  payload: {
73
82
  clientName: resolveClientName(),
74
83
  },
84
+ schema: DeviceStartResponseSchema,
75
85
  });
76
86
  const verificationUri = resolveVerificationUri(baseUrl, started.verificationUri);
77
87
 
@@ -93,27 +103,13 @@ async function runLogin(options: { baseUrl?: string }): Promise<void> {
93
103
  pollActive = true;
94
104
 
95
105
  while (Date.now() < expiresAtMs) {
96
- const poll = await postJson<
97
- | {
98
- status: "pending";
99
- intervalSec: number;
100
- }
101
- | {
102
- status: "approved";
103
- accessToken: string;
104
- refreshToken: string;
105
- accessTokenExpiresAtMs: number;
106
- refreshTokenExpiresAtMs: number;
107
- orgId: string;
108
- orgSlug: string;
109
- clerkUserId: string;
110
- }
111
- >({
106
+ const poll = await postJson({
112
107
  baseUrl,
113
108
  path: "/v1/cli/device/poll",
114
109
  payload: {
115
110
  deviceCode: started.deviceCode,
116
111
  },
112
+ schema: DevicePollResponseSchema,
117
113
  });
118
114
 
119
115
  if (poll.status === "pending") {
@@ -121,24 +117,38 @@ async function runLogin(options: { baseUrl?: string }): Promise<void> {
121
117
  continue;
122
118
  }
123
119
 
124
- const accountId = `${poll.orgSlug}:${poll.clerkUserId}`;
125
- await saveCredentials(accountId, {
120
+ const refreshed = RefreshResponseSchema.make({
126
121
  accessToken: poll.accessToken,
127
122
  refreshToken: poll.refreshToken,
128
123
  accessTokenExpiresAtMs: poll.accessTokenExpiresAtMs,
129
124
  refreshTokenExpiresAtMs: poll.refreshTokenExpiresAtMs,
130
- clerkUserId: poll.clerkUserId,
131
125
  orgId: poll.orgId,
132
126
  orgSlug: poll.orgSlug,
127
+ clerkUserId: poll.clerkUserId,
128
+ displayName: poll.displayName,
129
+ email: poll.email,
130
+ });
131
+ const sessionId = buildSessionId(baseUrl, refreshed.clerkUserId);
132
+
133
+ await saveCredentials(sessionId, {
134
+ accessToken: refreshed.accessToken,
135
+ refreshToken: refreshed.refreshToken,
136
+ accessTokenExpiresAtMs: refreshed.accessTokenExpiresAtMs,
137
+ refreshTokenExpiresAtMs: refreshed.refreshTokenExpiresAtMs,
138
+ clerkUserId: refreshed.clerkUserId,
139
+ orgId: refreshed.orgId,
140
+ orgSlug: refreshed.orgSlug,
141
+ lastOrgId: refreshed.orgId,
142
+ lastOrgSlug: refreshed.orgSlug,
133
143
  });
134
144
  await saveConfig({
135
145
  baseUrl,
136
- activeAccountId: accountId,
146
+ activeSessionId: sessionId,
137
147
  });
138
148
 
139
149
  pollSpinner.stop("Login approved");
140
150
  pollActive = false;
141
- outro(`Logged in as ${pc.bold(poll.clerkUserId)} in ${pc.bold(poll.orgSlug)}.`);
151
+ outro(`Signed in as ${pc.bold(resolveDisplayName(refreshed))}.`);
142
152
  return;
143
153
  }
144
154
 
@@ -158,7 +168,7 @@ async function runLogin(options: { baseUrl?: string }): Promise<void> {
158
168
 
159
169
  async function runLogout(): Promise<void> {
160
170
  const local = await requireLocalSession();
161
- await postJson<{ revoked: boolean }>({
171
+ await postJson({
162
172
  baseUrl: local.baseUrl,
163
173
  path: "/v1/cli/logout",
164
174
  payload: {
@@ -174,15 +184,11 @@ async function runWhoami(options: { json?: boolean }): Promise<void> {
174
184
  const local = await requireLocalSession();
175
185
  const authProvider = createCliAuthProvider();
176
186
  const accessToken = await authProvider.getAccessToken();
177
- const session = await getJson<{
178
- clerkUserId: string;
179
- orgId: string;
180
- orgSlug: string;
181
- source: "clerk" | "cli";
182
- }>({
187
+ const session = await getJson({
183
188
  baseUrl: local.baseUrl,
184
189
  path: "/v1/cli/session",
185
190
  accessToken,
191
+ schema: CliSessionResponseSchema,
186
192
  });
187
193
 
188
194
  if (options.json) {
@@ -190,9 +196,10 @@ async function runWhoami(options: { json?: boolean }): Promise<void> {
190
196
  return;
191
197
  }
192
198
 
193
- console.log(`${pc.bold("User")}: ${session.clerkUserId}`);
194
- console.log(`${pc.bold("Org")}: ${session.orgSlug}`);
195
- console.log(`${pc.bold("Source")}: ${session.source}`);
199
+ console.log(resolveDisplayName(session));
200
+ if (session.email !== null) {
201
+ console.log(session.email);
202
+ }
196
203
  }
197
204
 
198
205
  export function registerAuthCommands(program: Command): void {
@@ -221,7 +228,6 @@ export function registerAuthCommands(program: Command): void {
221
228
  await runWhoami(options);
222
229
  });
223
230
 
224
- // Backward-compatible top-level auth aliases.
225
231
  program
226
232
  .command("login")
227
233
  .description("Alias for barekey auth login")