@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,313 @@
1
+ import { Schema } from "effect";
2
+ export const DeclaredTypeSchema = Schema.Literal("string", "boolean", "int64", "float", "date", "json");
3
+ export const VisibilitySchema = Schema.Literal("private", "public");
4
+ export const RolloutFunctionSchema = Schema.Literal("linear", "step", "ease_in_out");
5
+ export const RuntimeModeSchema = Schema.Literal("centralized", "standalone");
6
+ export const TypegenModeSchema = Schema.Literal("semantic", "minimal");
7
+ const TrimmedStringSchema = Schema.String;
8
+ const NonEmptyStringSchema = Schema.String.pipe(Schema.minLength(1));
9
+ export const RuntimeConfigSchema = Schema.Struct({
10
+ organization: Schema.optional(Schema.NullOr(TrimmedStringSchema)),
11
+ org: Schema.optional(Schema.NullOr(TrimmedStringSchema)),
12
+ project: Schema.optional(Schema.NullOr(TrimmedStringSchema)),
13
+ environment: Schema.optional(Schema.NullOr(TrimmedStringSchema)),
14
+ stage: Schema.optional(Schema.NullOr(TrimmedStringSchema)),
15
+ typegen: Schema.optional(Schema.NullOr(TypegenModeSchema)),
16
+ config: Schema.optional(Schema.Struct({
17
+ typegen: Schema.optional(TypegenModeSchema),
18
+ mode: Schema.optional(RuntimeModeSchema),
19
+ })),
20
+ });
21
+ export const CliConfigSchema = Schema.Union(Schema.Struct({
22
+ baseUrl: NonEmptyStringSchema,
23
+ activeSessionId: NonEmptyStringSchema,
24
+ }), Schema.Struct({
25
+ baseUrl: NonEmptyStringSchema,
26
+ activeAccountId: NonEmptyStringSchema,
27
+ }));
28
+ export const CliSessionSchema = Schema.Union(Schema.Struct({
29
+ accessToken: NonEmptyStringSchema,
30
+ refreshToken: NonEmptyStringSchema,
31
+ accessTokenExpiresAtMs: Schema.Number.pipe(Schema.finite()),
32
+ refreshTokenExpiresAtMs: Schema.Number.pipe(Schema.finite()),
33
+ clerkUserId: NonEmptyStringSchema,
34
+ orgId: Schema.NullOr(NonEmptyStringSchema),
35
+ orgSlug: Schema.NullOr(NonEmptyStringSchema),
36
+ lastOrgId: Schema.optional(Schema.NullOr(NonEmptyStringSchema)),
37
+ lastOrgSlug: Schema.optional(Schema.NullOr(NonEmptyStringSchema)),
38
+ }), Schema.Struct({
39
+ accessToken: NonEmptyStringSchema,
40
+ refreshToken: NonEmptyStringSchema,
41
+ accessTokenExpiresAtMs: Schema.Number.pipe(Schema.finite()),
42
+ refreshTokenExpiresAtMs: Schema.Number.pipe(Schema.finite()),
43
+ clerkUserId: NonEmptyStringSchema,
44
+ orgId: NonEmptyStringSchema,
45
+ orgSlug: NonEmptyStringSchema,
46
+ }));
47
+ export const ErrorEnvelopeSchema = Schema.Struct({
48
+ error: Schema.Struct({
49
+ code: Schema.optional(Schema.String),
50
+ message: Schema.optional(Schema.String),
51
+ requestId: Schema.optional(Schema.NullOr(Schema.String)),
52
+ }),
53
+ });
54
+ export const CliSessionResponseSchema = Schema.Struct({
55
+ clerkUserId: Schema.String,
56
+ displayName: Schema.NullOr(Schema.String),
57
+ email: Schema.NullOr(Schema.String),
58
+ orgId: Schema.String,
59
+ orgSlug: Schema.String,
60
+ source: Schema.Literal("clerk", "cli"),
61
+ requestId: Schema.optional(Schema.String),
62
+ });
63
+ export const DeviceStartResponseSchema = Schema.Struct({
64
+ deviceCode: Schema.String,
65
+ userCode: Schema.String,
66
+ verificationUri: Schema.String,
67
+ intervalSec: Schema.Number.pipe(Schema.finite()),
68
+ expiresInSec: Schema.Number.pipe(Schema.finite()),
69
+ requestId: Schema.optional(Schema.String),
70
+ });
71
+ export const DevicePollPendingSchema = Schema.Struct({
72
+ status: Schema.Literal("pending"),
73
+ intervalSec: Schema.Number.pipe(Schema.finite()),
74
+ requestId: Schema.optional(Schema.String),
75
+ });
76
+ export const DevicePollApprovedSchema = Schema.Struct({
77
+ status: Schema.Literal("approved"),
78
+ intervalSec: Schema.Number.pipe(Schema.finite()),
79
+ accessToken: Schema.String,
80
+ refreshToken: Schema.String,
81
+ accessTokenExpiresAtMs: Schema.Number.pipe(Schema.finite()),
82
+ refreshTokenExpiresAtMs: Schema.Number.pipe(Schema.finite()),
83
+ orgId: Schema.String,
84
+ orgSlug: Schema.String,
85
+ clerkUserId: Schema.String,
86
+ displayName: Schema.NullOr(Schema.String),
87
+ email: Schema.NullOr(Schema.String),
88
+ requestId: Schema.optional(Schema.String),
89
+ });
90
+ export const DevicePollResponseSchema = Schema.Union(DevicePollPendingSchema, DevicePollApprovedSchema);
91
+ export const RefreshResponseSchema = Schema.Struct({
92
+ accessToken: Schema.String,
93
+ refreshToken: Schema.String,
94
+ accessTokenExpiresAtMs: Schema.Number.pipe(Schema.finite()),
95
+ refreshTokenExpiresAtMs: Schema.Number.pipe(Schema.finite()),
96
+ orgId: Schema.String,
97
+ orgSlug: Schema.String,
98
+ clerkUserId: Schema.String,
99
+ displayName: Schema.NullOr(Schema.String),
100
+ email: Schema.NullOr(Schema.String),
101
+ requestId: Schema.optional(Schema.String),
102
+ });
103
+ export const EnvDecisionSchema = Schema.Struct({
104
+ bucket: Schema.Number.pipe(Schema.finite()),
105
+ chance: Schema.Number.pipe(Schema.finite()),
106
+ seed: Schema.optional(Schema.String),
107
+ key: Schema.optional(Schema.String),
108
+ matchedRule: Schema.String,
109
+ });
110
+ export const EnvResolvedValueSchema = Schema.Struct({
111
+ name: Schema.String,
112
+ kind: Schema.Literal("secret", "ab_roll", "rollout"),
113
+ declaredType: DeclaredTypeSchema,
114
+ visibility: VisibilitySchema,
115
+ value: Schema.Unknown,
116
+ decision: Schema.optional(EnvDecisionSchema),
117
+ });
118
+ export const EnvEvaluateResponseSchema = Schema.Struct({
119
+ name: Schema.String,
120
+ kind: Schema.Literal("secret", "ab_roll", "rollout"),
121
+ declaredType: DeclaredTypeSchema,
122
+ visibility: VisibilitySchema,
123
+ value: Schema.Unknown,
124
+ decision: Schema.optional(EnvDecisionSchema),
125
+ });
126
+ export const EnvEvaluateBatchResponseSchema = Schema.Struct({
127
+ values: Schema.Array(EnvResolvedValueSchema),
128
+ });
129
+ export const RolloutMilestoneSchema = Schema.Struct({
130
+ at: Schema.String,
131
+ percentage: Schema.Number.pipe(Schema.finite()),
132
+ });
133
+ export const EnvVariableListItemSchema = Schema.Struct({
134
+ name: Schema.String,
135
+ visibility: VisibilitySchema,
136
+ kind: Schema.Literal("secret", "ab_roll", "rollout"),
137
+ declaredType: DeclaredTypeSchema,
138
+ createdAtMs: Schema.Number.pipe(Schema.finite()),
139
+ updatedAtMs: Schema.Number.pipe(Schema.finite()),
140
+ chance: Schema.NullOr(Schema.Number.pipe(Schema.finite())),
141
+ rolloutFunction: Schema.NullOr(RolloutFunctionSchema),
142
+ rolloutMilestones: Schema.NullOr(Schema.Array(RolloutMilestoneSchema)),
143
+ });
144
+ export const EnvListResponseSchema = Schema.Struct({
145
+ variables: Schema.Array(EnvVariableListItemSchema),
146
+ });
147
+ export const EnvWriteResponseSchema = Schema.Struct({
148
+ createdCount: Schema.Number.pipe(Schema.finite()),
149
+ updatedCount: Schema.Number.pipe(Schema.finite()),
150
+ deletedCount: Schema.Number.pipe(Schema.finite()),
151
+ });
152
+ export const EnvPullValueSchema = Schema.Struct({
153
+ name: Schema.String,
154
+ kind: Schema.Literal("secret", "ab_roll", "rollout"),
155
+ declaredType: DeclaredTypeSchema,
156
+ value: Schema.String,
157
+ });
158
+ export const EnvPullResponseSchema = Schema.Struct({
159
+ values: Schema.Array(EnvPullValueSchema),
160
+ byName: Schema.Record({ key: Schema.String, value: Schema.String }),
161
+ });
162
+ export const TypegenVariableSchema = Schema.Struct({
163
+ name: Schema.String,
164
+ visibility: VisibilitySchema,
165
+ kind: Schema.Literal("secret", "ab_roll", "rollout"),
166
+ declaredType: DeclaredTypeSchema,
167
+ required: Schema.Boolean,
168
+ updatedAtMs: Schema.Number.pipe(Schema.finite()),
169
+ typeScriptType: Schema.String,
170
+ valueATypeScriptType: Schema.NullOr(Schema.String),
171
+ valueBTypeScriptType: Schema.NullOr(Schema.String),
172
+ rolloutFunction: Schema.NullOr(RolloutFunctionSchema),
173
+ rolloutMilestones: Schema.NullOr(Schema.Array(RolloutMilestoneSchema)),
174
+ });
175
+ export const TypegenManifestSchema = Schema.Struct({
176
+ orgId: Schema.String,
177
+ orgSlug: Schema.String,
178
+ projectSlug: Schema.String,
179
+ stageSlug: Schema.String,
180
+ generatedAtMs: Schema.Number.pipe(Schema.finite()),
181
+ manifestVersion: Schema.String,
182
+ variables: Schema.Array(TypegenVariableSchema),
183
+ });
184
+ export const OrganizationSummarySchema = Schema.Struct({
185
+ id: Schema.String,
186
+ slug: Schema.String,
187
+ name: Schema.String,
188
+ role: Schema.String,
189
+ imageUrl: Schema.String,
190
+ });
191
+ export const OrganizationsResponseSchema = Schema.Struct({
192
+ organizations: Schema.Array(OrganizationSummarySchema),
193
+ });
194
+ export const ProjectSummarySchema = Schema.Struct({
195
+ id: Schema.String,
196
+ orgId: Schema.String,
197
+ orgSlug: Schema.String,
198
+ name: Schema.String,
199
+ slug: Schema.String,
200
+ slugBase: Schema.String,
201
+ createdByClerkUserId: Schema.String,
202
+ createdAtMs: Schema.Number.pipe(Schema.finite()),
203
+ updatedAtMs: Schema.Number.pipe(Schema.finite()),
204
+ });
205
+ export const ProjectListItemSchema = Schema.Struct({
206
+ id: Schema.String,
207
+ orgId: Schema.String,
208
+ orgSlug: Schema.String,
209
+ name: Schema.String,
210
+ slug: Schema.String,
211
+ slugBase: Schema.String,
212
+ createdByClerkUserId: Schema.String,
213
+ createdAtMs: Schema.Number.pipe(Schema.finite()),
214
+ updatedAtMs: Schema.Number.pipe(Schema.finite()),
215
+ secretCount: Schema.Number.pipe(Schema.finite()),
216
+ });
217
+ export const ProjectsListResponseSchema = Schema.Struct({
218
+ projects: Schema.Array(ProjectListItemSchema),
219
+ });
220
+ export const ProjectCreateResponseSchema = Schema.Struct({
221
+ project: ProjectSummarySchema,
222
+ });
223
+ export const ProjectDeleteResponseSchema = Schema.Struct({
224
+ deletedProjectId: Schema.String,
225
+ deletedProjectSlug: Schema.String,
226
+ });
227
+ export const StageSummarySchema = Schema.Struct({
228
+ id: Schema.String,
229
+ projectId: Schema.String,
230
+ orgId: Schema.String,
231
+ slug: Schema.String,
232
+ name: Schema.String,
233
+ isDefault: Schema.Boolean,
234
+ variableCount: Schema.Number.pipe(Schema.finite()),
235
+ createdAtMs: Schema.Number.pipe(Schema.finite()),
236
+ updatedAtMs: Schema.Number.pipe(Schema.finite()),
237
+ });
238
+ export const StagesListResponseSchema = Schema.Struct({
239
+ stages: Schema.Array(StageSummarySchema),
240
+ });
241
+ export const StageCreateResponseSchema = Schema.Struct({
242
+ stage: StageSummarySchema,
243
+ });
244
+ export const StageRenameResponseSchema = Schema.Struct({
245
+ stage: StageSummarySchema,
246
+ });
247
+ export const StageDeleteResponseSchema = Schema.Struct({
248
+ deletedStageSlug: Schema.String,
249
+ });
250
+ export const FeatureUsageSchema = Schema.Struct({
251
+ featureId: Schema.String,
252
+ allowed: Schema.Boolean,
253
+ usage: Schema.NullOr(Schema.Number.pipe(Schema.finite())),
254
+ includedUsage: Schema.NullOr(Schema.Number.pipe(Schema.finite())),
255
+ usageLimit: Schema.NullOr(Schema.Number.pipe(Schema.finite())),
256
+ overageAllowed: Schema.NullOr(Schema.Boolean),
257
+ nextResetAtMs: Schema.NullOr(Schema.Number.pipe(Schema.finite())),
258
+ });
259
+ export const BillingCatalogResponseSchema = Schema.Struct({
260
+ variants: Schema.Array(Schema.Unknown),
261
+ featureIds: Schema.Struct({
262
+ staticRequests: Schema.String,
263
+ dynamicRequests: Schema.String,
264
+ storageBytes: Schema.String,
265
+ }),
266
+ });
267
+ export const BillingStatusResponseSchema = Schema.Struct({
268
+ orgId: Schema.String,
269
+ orgRole: Schema.NullOr(Schema.String),
270
+ canManageBilling: Schema.Boolean,
271
+ currentProductId: Schema.NullOr(Schema.String),
272
+ currentTier: Schema.NullOr(Schema.String),
273
+ currentInterval: Schema.NullOr(Schema.String),
274
+ currentOverageMode: Schema.NullOr(Schema.String),
275
+ hasScheduledPlanChange: Schema.Boolean,
276
+ scheduledPlanChange: Schema.NullOr(Schema.Unknown),
277
+ usage: Schema.Struct({
278
+ staticRequests: FeatureUsageSchema,
279
+ dynamicRequests: FeatureUsageSchema,
280
+ storageBytes: FeatureUsageSchema,
281
+ }),
282
+ storageMirrorBytes: Schema.Number.pipe(Schema.finite()),
283
+ variants: Schema.Array(Schema.Unknown),
284
+ });
285
+ export const AuditEventSchema = Schema.Struct({
286
+ id: Schema.String,
287
+ orgId: Schema.String,
288
+ orgSlug: Schema.String,
289
+ projectId: Schema.NullOr(Schema.String),
290
+ projectSlug: Schema.NullOr(Schema.String),
291
+ stageSlug: Schema.NullOr(Schema.String),
292
+ eventType: Schema.String,
293
+ category: Schema.String,
294
+ occurredAtMs: Schema.Number.pipe(Schema.finite()),
295
+ actorSource: Schema.String,
296
+ actorClerkUserId: Schema.NullOr(Schema.String),
297
+ actorDisplayName: Schema.NullOr(Schema.String),
298
+ actorEmail: Schema.NullOr(Schema.String),
299
+ subjectType: Schema.String,
300
+ subjectId: Schema.NullOr(Schema.String),
301
+ subjectName: Schema.NullOr(Schema.String),
302
+ title: Schema.String,
303
+ description: Schema.String,
304
+ severity: Schema.String,
305
+ payloadJson: Schema.String,
306
+ retentionTier: Schema.String,
307
+ expiresAtMs: Schema.NullOr(Schema.Number.pipe(Schema.finite())),
308
+ });
309
+ export const AuditListResponseSchema = Schema.Struct({
310
+ items: Schema.Array(AuditEventSchema),
311
+ nextBeforeOccurredAtMs: Schema.NullOr(Schema.Number.pipe(Schema.finite())),
312
+ hasMore: Schema.Boolean,
313
+ });
@@ -2,10 +2,22 @@ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
4
  import { spawn } from "node:child_process";
5
+ import { Either, Schema } from "effect";
6
+ import { CliConfigSchema, CliSessionSchema } from "./contracts/index.js";
5
7
  const SERVICE_NAME = "barekey-cli";
6
- const CONFIG_DIR = path.join(homedir(), ".config", "barekey");
7
- const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
8
- const CREDENTIALS_DIR = path.join(CONFIG_DIR, "credentials");
8
+ function resolveHomeDir() {
9
+ const configuredHome = process.env.HOME?.trim();
10
+ return configuredHome && configuredHome.length > 0 ? configuredHome : homedir();
11
+ }
12
+ function getConfigDirPath() {
13
+ return path.join(resolveHomeDir(), ".config", "barekey");
14
+ }
15
+ function getConfigPath() {
16
+ return path.join(getConfigDirPath(), "config.json");
17
+ }
18
+ function getCredentialsDirPath() {
19
+ return path.join(getConfigDirPath(), "credentials");
20
+ }
9
21
  function runCommand(command, args, input) {
10
22
  return new Promise((resolve) => {
11
23
  const child = spawn(command, args, {
@@ -32,14 +44,14 @@ function runCommand(command, args, input) {
32
44
  });
33
45
  }
34
46
  async function ensureConfigDir() {
35
- await mkdir(CONFIG_DIR, {
47
+ await mkdir(getConfigDirPath(), {
36
48
  recursive: true,
37
49
  mode: 0o700,
38
50
  });
39
51
  }
40
52
  async function ensureCredentialsDir() {
41
53
  await ensureConfigDir();
42
- await mkdir(CREDENTIALS_DIR, {
54
+ await mkdir(getCredentialsDirPath(), {
43
55
  recursive: true,
44
56
  mode: 0o700,
45
57
  });
@@ -61,7 +73,7 @@ async function writeJsonFile(filePath, value) {
61
73
  });
62
74
  }
63
75
  function credentialsPathForAccount(accountId) {
64
- return path.join(CREDENTIALS_DIR, `${encodeURIComponent(accountId)}.json`);
76
+ return path.join(getCredentialsDirPath(), `${encodeURIComponent(accountId)}.json`);
65
77
  }
66
78
  async function canUseLinuxSecretTool() {
67
79
  if (process.platform !== "linux") {
@@ -153,13 +165,30 @@ async function deleteFromKeychain(account) {
153
165
  return false;
154
166
  }
155
167
  export async function saveConfig(config) {
156
- await writeJsonFile(CONFIG_PATH, config);
168
+ await writeJsonFile(getConfigPath(), config);
157
169
  }
158
170
  export async function loadConfig() {
159
- return readJsonFile(CONFIG_PATH);
171
+ const decoded = await readJsonFile(getConfigPath());
172
+ if (decoded === null) {
173
+ return null;
174
+ }
175
+ const result = Schema.decodeUnknownEither(CliConfigSchema)(decoded);
176
+ if (Either.isLeft(result)) {
177
+ return null;
178
+ }
179
+ if ("activeSessionId" in result.right) {
180
+ return {
181
+ baseUrl: result.right.baseUrl,
182
+ activeSessionId: result.right.activeSessionId,
183
+ };
184
+ }
185
+ return {
186
+ baseUrl: result.right.baseUrl,
187
+ activeSessionId: result.right.activeAccountId,
188
+ };
160
189
  }
161
190
  export async function clearConfig() {
162
- await rm(CONFIG_PATH, {
191
+ await rm(getConfigPath(), {
163
192
  force: true,
164
193
  });
165
194
  }
@@ -182,14 +211,44 @@ export async function loadCredentials(accountId) {
182
211
  const fromKeychain = await getFromKeychain(accountId);
183
212
  if (fromKeychain !== null) {
184
213
  try {
185
- return JSON.parse(fromKeychain);
214
+ const decoded = Schema.decodeUnknownEither(CliSessionSchema)(JSON.parse(fromKeychain));
215
+ if (Either.isRight(decoded)) {
216
+ return {
217
+ accessToken: decoded.right.accessToken,
218
+ refreshToken: decoded.right.refreshToken,
219
+ accessTokenExpiresAtMs: decoded.right.accessTokenExpiresAtMs,
220
+ refreshTokenExpiresAtMs: decoded.right.refreshTokenExpiresAtMs,
221
+ clerkUserId: decoded.right.clerkUserId,
222
+ orgId: decoded.right.orgId ?? null,
223
+ orgSlug: decoded.right.orgSlug ?? null,
224
+ lastOrgId: ("lastOrgId" in decoded.right ? decoded.right.lastOrgId : undefined) ?? decoded.right.orgId ?? null,
225
+ lastOrgSlug: ("lastOrgSlug" in decoded.right ? decoded.right.lastOrgSlug : undefined) ?? decoded.right.orgSlug ?? null,
226
+ };
227
+ }
186
228
  }
187
229
  catch {
188
230
  // Fall back to file credentials when keychain data is malformed.
189
231
  }
190
232
  }
191
233
  const fromFile = await readJsonFile(credentialsPathForAccount(accountId));
192
- return fromFile;
234
+ if (fromFile === null) {
235
+ return null;
236
+ }
237
+ const decoded = Schema.decodeUnknownEither(CliSessionSchema)(fromFile);
238
+ if (Either.isLeft(decoded)) {
239
+ return null;
240
+ }
241
+ return {
242
+ accessToken: decoded.right.accessToken,
243
+ refreshToken: decoded.right.refreshToken,
244
+ accessTokenExpiresAtMs: decoded.right.accessTokenExpiresAtMs,
245
+ refreshTokenExpiresAtMs: decoded.right.refreshTokenExpiresAtMs,
246
+ clerkUserId: decoded.right.clerkUserId,
247
+ orgId: decoded.right.orgId ?? null,
248
+ orgSlug: decoded.right.orgSlug ?? null,
249
+ lastOrgId: ("lastOrgId" in decoded.right ? decoded.right.lastOrgId : undefined) ?? decoded.right.orgId ?? null,
250
+ lastOrgSlug: ("lastOrgSlug" in decoded.right ? decoded.right.lastOrgSlug : undefined) ?? decoded.right.orgSlug ?? null,
251
+ };
193
252
  }
194
253
  export async function deleteCredentials(accountId) {
195
254
  await deleteFromKeychain(accountId);
package/dist/http.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Effect, Schema } from "effect";
1
2
  export declare class CliHttpError extends Error {
2
3
  readonly code: string;
3
4
  readonly status: number;
@@ -14,9 +15,42 @@ export declare function postJson<TResponse>(input: {
14
15
  path: string;
15
16
  payload: unknown;
16
17
  accessToken?: string | null;
18
+ schema?: Schema.Schema<TResponse>;
17
19
  }): Promise<TResponse>;
18
20
  export declare function getJson<TResponse>(input: {
19
21
  baseUrl: string;
20
22
  path: string;
21
23
  accessToken?: string | null;
24
+ schema?: Schema.Schema<TResponse>;
22
25
  }): Promise<TResponse>;
26
+ /**
27
+ * Executes one typed HTTP POST request as an Effect program.
28
+ *
29
+ * @param input The request configuration and response schema.
30
+ * @returns An Effect that succeeds with the decoded JSON response.
31
+ * @remarks CLI command programs use this so schema decode failures and transport failures share one typed path.
32
+ * @lastModified 2026-03-19
33
+ * @author GPT-5.4
34
+ */
35
+ export declare function postJsonEffect<TResponse>(input: {
36
+ baseUrl: string;
37
+ path: string;
38
+ payload: unknown;
39
+ accessToken?: string | null;
40
+ schema: Schema.Schema<TResponse>;
41
+ }): Effect.Effect<TResponse, CliHttpError, never>;
42
+ /**
43
+ * Executes one typed HTTP GET request as an Effect program.
44
+ *
45
+ * @param input The request configuration and response schema.
46
+ * @returns An Effect that succeeds with the decoded JSON response.
47
+ * @remarks CLI command programs use this so schema decode failures and transport failures share one typed path.
48
+ * @lastModified 2026-03-19
49
+ * @author GPT-5.4
50
+ */
51
+ export declare function getJsonEffect<TResponse>(input: {
52
+ baseUrl: string;
53
+ path: string;
54
+ accessToken?: string | null;
55
+ schema: Schema.Schema<TResponse>;
56
+ }): Effect.Effect<TResponse, CliHttpError, never>;
package/dist/http.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { Effect, Either, Schema } from "effect";
1
2
  export class CliHttpError extends Error {
2
3
  code;
3
4
  status;
@@ -18,6 +19,17 @@ async function parseJson(response) {
18
19
  return null;
19
20
  }
20
21
  }
22
+ function decodeResponse(schema, payload) {
23
+ const decoded = Schema.decodeUnknownEither(schema)(payload);
24
+ if (Either.isLeft(decoded)) {
25
+ throw new CliHttpError({
26
+ code: "INVALID_RESPONSE",
27
+ status: 500,
28
+ message: "Barekey returned an unexpected response payload.",
29
+ });
30
+ }
31
+ return decoded.right;
32
+ }
21
33
  export async function postJson(input) {
22
34
  const response = await fetch(`${input.baseUrl.replace(/\/$/, "")}${input.path}`, {
23
35
  method: "POST",
@@ -41,7 +53,7 @@ export async function postJson(input) {
41
53
  requestId: payload?.error?.requestId ?? null,
42
54
  });
43
55
  }
44
- return parsed;
56
+ return input.schema ? decodeResponse(input.schema, parsed) : parsed;
45
57
  }
46
58
  export async function getJson(input) {
47
59
  const response = await fetch(`${input.baseUrl.replace(/\/$/, "")}${input.path}`, {
@@ -62,5 +74,47 @@ export async function getJson(input) {
62
74
  requestId: payload?.error?.requestId ?? null,
63
75
  });
64
76
  }
65
- return parsed;
77
+ return input.schema ? decodeResponse(input.schema, parsed) : parsed;
78
+ }
79
+ /**
80
+ * Executes one typed HTTP POST request as an Effect program.
81
+ *
82
+ * @param input The request configuration and response schema.
83
+ * @returns An Effect that succeeds with the decoded JSON response.
84
+ * @remarks CLI command programs use this so schema decode failures and transport failures share one typed path.
85
+ * @lastModified 2026-03-19
86
+ * @author GPT-5.4
87
+ */
88
+ export function postJsonEffect(input) {
89
+ return Effect.tryPromise({
90
+ try: () => postJson(input),
91
+ catch: (error) => error instanceof CliHttpError
92
+ ? error
93
+ : new CliHttpError({
94
+ code: "HTTP_ERROR",
95
+ status: 500,
96
+ message: error instanceof Error ? error.message : "HTTP request failed.",
97
+ }),
98
+ });
99
+ }
100
+ /**
101
+ * Executes one typed HTTP GET request as an Effect program.
102
+ *
103
+ * @param input The request configuration and response schema.
104
+ * @returns An Effect that succeeds with the decoded JSON response.
105
+ * @remarks CLI command programs use this so schema decode failures and transport failures share one typed path.
106
+ * @lastModified 2026-03-19
107
+ * @author GPT-5.4
108
+ */
109
+ export function getJsonEffect(input) {
110
+ return Effect.tryPromise({
111
+ try: () => getJson(input),
112
+ catch: (error) => error instanceof CliHttpError
113
+ ? error
114
+ : new CliHttpError({
115
+ code: "HTTP_ERROR",
116
+ status: 500,
117
+ message: error instanceof Error ? error.message : "HTTP request failed.",
118
+ }),
119
+ });
66
120
  }
package/dist/index.js CHANGED
@@ -2,12 +2,24 @@
2
2
  import { Command } from "commander";
3
3
  import pc from "picocolors";
4
4
  import { registerAuthCommands } from "./commands/auth.js";
5
+ import { registerAuditCommands } from "./commands/audit.js";
6
+ import { registerBillingCommands } from "./commands/billing.js";
5
7
  import { registerEnvCommands } from "./commands/env.js";
8
+ import { registerInitCommand } from "./commands/init.js";
9
+ import { registerOrgCommands } from "./commands/org.js";
10
+ import { registerProjectCommands } from "./commands/project.js";
11
+ import { registerStageCommands } from "./commands/stage.js";
6
12
  import { registerTypegenCommand } from "./commands/typegen.js";
7
13
  import { CLI_DESCRIPTION, CLI_NAME, CLI_VERSION } from "./constants.js";
8
14
  const program = new Command();
9
15
  program.name(CLI_NAME).description(CLI_DESCRIPTION).version(CLI_VERSION);
10
16
  registerAuthCommands(program);
17
+ registerAuditCommands(program);
18
+ registerBillingCommands(program);
19
+ registerInitCommand(program);
20
+ registerOrgCommands(program);
21
+ registerProjectCommands(program);
22
+ registerStageCommands(program);
11
23
  registerEnvCommands(program);
12
24
  registerTypegenCommand(program);
13
25
  program.parseAsync(process.argv).catch((error) => {
@@ -1,5 +1,7 @@
1
1
  import { access, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { Either, Schema } from "effect";
4
+ import { RuntimeConfigSchema } from "./contracts/index.js";
3
5
  async function fileExists(filePath) {
4
6
  try {
5
7
  await access(filePath);
@@ -30,36 +32,22 @@ export async function loadRuntimeConfig() {
30
32
  }
31
33
  const raw = await readFile(configPath, "utf8");
32
34
  const parsed = JSON.parse(raw);
33
- const config = typeof parsed.config === "object" && parsed.config !== null
34
- ? parsed.config
35
- : undefined;
35
+ const decoded = Schema.decodeUnknownEither(RuntimeConfigSchema)(parsed);
36
+ if (Either.isLeft(decoded)) {
37
+ throw new Error(`Invalid barekey.json at ${configPath}.`);
38
+ }
39
+ const config = decoded.right.config;
40
+ const mode = config?.mode ?? "centralized";
41
+ const typegen = mode === "standalone" ? "minimal" : (config?.typegen ?? decoded.right.typegen ?? "semantic");
36
42
  return {
37
43
  path: configPath,
38
44
  config: {
39
- org: typeof parsed.organization === "string"
40
- ? parsed.organization.trim()
41
- : typeof parsed.org === "string"
42
- ? parsed.org.trim()
43
- : undefined,
44
- project: typeof parsed.project === "string" ? parsed.project.trim() : undefined,
45
- environment: typeof parsed.environment === "string"
46
- ? parsed.environment.trim()
47
- : typeof parsed.stage === "string"
48
- ? parsed.stage.trim()
49
- : undefined,
45
+ org: (decoded.right.organization ?? decoded.right.org ?? undefined)?.trim() || undefined,
46
+ project: decoded.right.project?.trim() || undefined,
47
+ environment: (decoded.right.environment ?? decoded.right.stage ?? undefined)?.trim() || undefined,
50
48
  config: {
51
- mode: config?.mode === "centralized" || config?.mode === "standalone"
52
- ? config.mode
53
- : "centralized",
54
- typegen: (config?.mode === "centralized" || config?.mode === "standalone"
55
- ? config.mode
56
- : "centralized") === "standalone"
57
- ? "minimal"
58
- : config?.typegen === "semantic" || config?.typegen === "minimal"
59
- ? config.typegen
60
- : parsed.typegen === "semantic" || parsed.typegen === "minimal"
61
- ? parsed.typegen
62
- : "semantic",
49
+ mode,
50
+ typegen,
63
51
  },
64
52
  },
65
53
  };