beakcrypt 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1370 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/lib/errors.ts
7
+ import pc from "picocolors";
8
+ var CliError = class extends Error {
9
+ constructor(message, hint) {
10
+ super(message);
11
+ this.hint = hint;
12
+ this.name = "CliError";
13
+ }
14
+ };
15
+ function handleResultError(result) {
16
+ throw new CliError(result.error, `Error code: ${result.code}`);
17
+ }
18
+ function unwrapResult(result) {
19
+ if (!result.ok) {
20
+ handleResultError(result);
21
+ }
22
+ return result.data;
23
+ }
24
+ function handleError(error) {
25
+ if (error instanceof CliError) {
26
+ console.error(`
27
+ ${pc.red("Error:")} ${error.message}`);
28
+ if (error.hint) {
29
+ console.error(`${pc.dim(error.hint)}`);
30
+ }
31
+ } else if (error instanceof Error) {
32
+ console.error(`
33
+ ${pc.red("Error:")} ${error.message}`);
34
+ } else {
35
+ console.error(`
36
+ ${pc.red("Error:")} An unexpected error occurred.`);
37
+ }
38
+ process.exit(1);
39
+ }
40
+
41
+ // src/commands/login.ts
42
+ import { api } from "@beakcrypt/convex";
43
+
44
+ // src/lib/global-config.ts
45
+ import { mkdir, readFile, writeFile, rm } from "fs/promises";
46
+ import { existsSync } from "fs";
47
+ import { homedir } from "os";
48
+ import { join } from "path";
49
+ var CONFIG_DIR = join(homedir(), ".beakcrypt");
50
+ var AUTH_FILE = join(CONFIG_DIR, "auth.json");
51
+ function getConfigDir() {
52
+ return CONFIG_DIR;
53
+ }
54
+ async function ensureConfigDir() {
55
+ await mkdir(CONFIG_DIR, { recursive: true });
56
+ }
57
+ async function getAuthConfig() {
58
+ if (!existsSync(AUTH_FILE)) return null;
59
+ try {
60
+ const data = await readFile(AUTH_FILE, "utf-8");
61
+ return JSON.parse(data);
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+ async function saveAuthConfig(config) {
67
+ await ensureConfigDir();
68
+ await writeFile(AUTH_FILE, JSON.stringify(config, null, 2), { mode: 384 });
69
+ }
70
+ async function clearAllConfig() {
71
+ if (existsSync(CONFIG_DIR)) {
72
+ await rm(CONFIG_DIR, { recursive: true });
73
+ }
74
+ }
75
+ function isLoggedIn() {
76
+ return existsSync(AUTH_FILE);
77
+ }
78
+
79
+ // src/lib/auth.ts
80
+ import {
81
+ createServer
82
+ } from "http";
83
+ import { URL } from "url";
84
+ import open from "open";
85
+ import ora from "ora";
86
+ import pc3 from "picocolors";
87
+
88
+ // src/lib/output.ts
89
+ import pc2 from "picocolors";
90
+ function success(message) {
91
+ console.log(`${pc2.green("\u2713")} ${message}`);
92
+ }
93
+ function info(message) {
94
+ console.log(`${pc2.blue("\u2139")} ${message}`);
95
+ }
96
+ function warn(message) {
97
+ console.log(`${pc2.yellow("\u26A0")} ${message}`);
98
+ }
99
+ function dim(message) {
100
+ return pc2.dim(message);
101
+ }
102
+ function bold(message) {
103
+ return pc2.bold(message);
104
+ }
105
+ function link(url) {
106
+ return pc2.cyan(pc2.underline(url));
107
+ }
108
+ function table(rows, headers) {
109
+ const allRows = headers ? [headers, ...rows] : rows;
110
+ const colWidths = [];
111
+ for (const row of allRows) {
112
+ for (let i = 0; i < row.length; i++) {
113
+ colWidths[i] = Math.max(colWidths[i] ?? 0, (row[i] ?? "").length);
114
+ }
115
+ }
116
+ for (let i = 0; i < allRows.length; i++) {
117
+ const row = allRows[i];
118
+ const line = row.map((cell, j) => (cell ?? "").padEnd(colWidths[j] ?? 0)).join(" ");
119
+ if (i === 0 && headers) {
120
+ console.log(pc2.bold(line));
121
+ console.log(colWidths.map((w) => "\u2500".repeat(w)).join("\u2500\u2500"));
122
+ } else {
123
+ console.log(line);
124
+ }
125
+ }
126
+ }
127
+
128
+ // src/lib/auth.ts
129
+ function startCallbackServer() {
130
+ return new Promise((resolve3, reject) => {
131
+ let resolveToken;
132
+ const tokenPromise = new Promise((res) => {
133
+ resolveToken = res;
134
+ });
135
+ const server = createServer((req, res) => {
136
+ const url = new URL(req.url ?? "/", `http://localhost`);
137
+ if (url.pathname === "/callback") {
138
+ const sessionToken = url.searchParams.get("session_token");
139
+ if (sessionToken) {
140
+ res.writeHead(200, { "Content-Type": "text/html" });
141
+ res.end(`
142
+ <html>
143
+ <body style="display:flex;justify-content:center;align-items:center;height:100vh;font-family:system-ui;background:#0a0a0a;color:#fafafa">
144
+ <div style="text-align:center">
145
+ <h1>Authenticated!</h1>
146
+ <p>You can close this tab and return to the terminal.</p>
147
+ </div>
148
+ </body>
149
+ </html>
150
+ `);
151
+ resolveToken({ sessionToken });
152
+ setTimeout(() => server.close(), 500);
153
+ } else {
154
+ res.writeHead(400, { "Content-Type": "text/plain" });
155
+ res.end("Missing session_token parameter");
156
+ }
157
+ } else {
158
+ res.writeHead(404, { "Content-Type": "text/plain" });
159
+ res.end("Not found");
160
+ }
161
+ });
162
+ server.listen(0, "127.0.0.1", () => {
163
+ const addr = server.address();
164
+ if (!addr || typeof addr === "string") {
165
+ reject(new Error("Failed to start callback server"));
166
+ return;
167
+ }
168
+ resolve3({
169
+ port: addr.port,
170
+ waitForToken: () => tokenPromise
171
+ });
172
+ });
173
+ server.on("error", reject);
174
+ });
175
+ }
176
+ async function loginFlow(siteUrl, convexUrl, convexSiteUrl) {
177
+ const { port, waitForToken } = await startCallbackServer();
178
+ const callbackUrl = `http://localhost:${port}/callback`;
179
+ const loginUrl = `${siteUrl}/auth/cli?callback=${encodeURIComponent(callbackUrl)}`;
180
+ const isTTY = process.stdin.isTTY;
181
+ if (isTTY) {
182
+ info(`Opening browser to log in...`);
183
+ console.log(`${pc3.dim("If the browser doesn't open, visit:")}`);
184
+ console.log(`${link(loginUrl)}
185
+ `);
186
+ try {
187
+ await open(loginUrl);
188
+ } catch {
189
+ }
190
+ } else {
191
+ console.log(`Visit this URL to log in:
192
+ ${link(loginUrl)}
193
+ `);
194
+ }
195
+ const spinner = ora("Waiting for authentication...").start();
196
+ const result = await waitForToken();
197
+ spinner.stop();
198
+ const config = {
199
+ sessionToken: result.sessionToken,
200
+ convexUrl,
201
+ convexSiteUrl,
202
+ siteUrl
203
+ };
204
+ await saveAuthConfig(config);
205
+ return config;
206
+ }
207
+ function getDefaultUrls() {
208
+ return {
209
+ siteUrl: process.env.BEAKCRYPT_SITE_URL ?? "https://app.beakcrypt.com",
210
+ convexUrl: process.env.BEAKCRYPT_CONVEX_URL ?? "",
211
+ convexSiteUrl: process.env.BEAKCRYPT_CONVEX_SITE_URL ?? ""
212
+ };
213
+ }
214
+ function validateUrls(urls) {
215
+ if (!urls.convexUrl) {
216
+ throw new CliError(
217
+ "BEAKCRYPT_CONVEX_URL is not configured.",
218
+ "Set the BEAKCRYPT_CONVEX_URL environment variable or configure it in ~/.beakcrypt/auth.json"
219
+ );
220
+ }
221
+ if (!urls.convexSiteUrl) {
222
+ throw new CliError(
223
+ "BEAKCRYPT_CONVEX_SITE_URL is not configured.",
224
+ "Set the BEAKCRYPT_CONVEX_SITE_URL environment variable or configure it in ~/.beakcrypt/auth.json"
225
+ );
226
+ }
227
+ }
228
+
229
+ // src/lib/convex-client.ts
230
+ import { ConvexHttpClient } from "convex/browser";
231
+ var clientInstance = null;
232
+ var cachedAuth = null;
233
+ async function getConvexToken(auth) {
234
+ const url = `${auth.convexSiteUrl}/api/auth/convex/token`;
235
+ const res = await fetch(url, {
236
+ method: "GET",
237
+ headers: {
238
+ Authorization: `Bearer ${auth.sessionToken}`
239
+ }
240
+ });
241
+ if (!res.ok) {
242
+ const body = await res.text().catch(() => "");
243
+ console.error(`Token exchange failed: ${res.status} ${res.statusText}`);
244
+ console.error(`URL: ${url}`);
245
+ if (body) console.error(`Body: ${body}`);
246
+ throw new CliError("Session expired. Please run `beakcrypt login` again.");
247
+ }
248
+ const data = await res.json();
249
+ return data.token;
250
+ }
251
+ async function getClient() {
252
+ const auth = await getAuthConfig();
253
+ if (!auth) {
254
+ throw new CliError("Not logged in. Run `beakcrypt login` first.");
255
+ }
256
+ cachedAuth = auth;
257
+ if (!clientInstance) {
258
+ clientInstance = new ConvexHttpClient(auth.convexUrl);
259
+ }
260
+ const token = await getConvexToken(auth);
261
+ clientInstance.setAuth(token);
262
+ return clientInstance;
263
+ }
264
+ async function query(fn, args) {
265
+ const client = await getClient();
266
+ return client.query(fn, args);
267
+ }
268
+ async function mutation(fn, args) {
269
+ const client = await getClient();
270
+ return client.mutation(fn, args);
271
+ }
272
+ function getSessionToken() {
273
+ return cachedAuth?.sessionToken ?? null;
274
+ }
275
+ function getSiteUrl() {
276
+ if (!cachedAuth) throw new CliError("Not logged in.");
277
+ return cachedAuth.siteUrl;
278
+ }
279
+
280
+ // src/commands/login.ts
281
+ async function loginCommand() {
282
+ const existing = await getAuthConfig();
283
+ if (existing) {
284
+ info("Already logged in. Refreshing session...");
285
+ }
286
+ const urls = existing ? {
287
+ siteUrl: existing.siteUrl,
288
+ convexUrl: existing.convexUrl,
289
+ convexSiteUrl: existing.convexSiteUrl
290
+ } : getDefaultUrls();
291
+ validateUrls(urls);
292
+ await loginFlow(urls.siteUrl, urls.convexUrl, urls.convexSiteUrl);
293
+ try {
294
+ const user = await query(api.auth.getCurrentUser, {});
295
+ if (user) {
296
+ success(`Logged in as ${bold(user.email)}`);
297
+ } else {
298
+ success("Logged in successfully.");
299
+ }
300
+ } catch {
301
+ success("Logged in successfully.");
302
+ }
303
+ }
304
+
305
+ // src/lib/interactive.ts
306
+ import prompts from "prompts";
307
+ function isInteractive() {
308
+ return Boolean(process.stdin.isTTY);
309
+ }
310
+ function requireInteractive(command) {
311
+ if (!isInteractive()) {
312
+ throw new CliError(
313
+ `Cannot run interactive prompts in non-interactive mode.`,
314
+ `Run \`beakcrypt ${command}\` in an interactive terminal, or provide required flags.`
315
+ );
316
+ }
317
+ }
318
+ async function select(opts) {
319
+ const { value } = await prompts(
320
+ {
321
+ type: "select",
322
+ name: "value",
323
+ message: opts.message,
324
+ choices: opts.choices
325
+ },
326
+ { onCancel: () => process.exit(0) }
327
+ );
328
+ return value;
329
+ }
330
+ async function confirm(message, initial = false) {
331
+ const { value } = await prompts(
332
+ {
333
+ type: "confirm",
334
+ name: "value",
335
+ message,
336
+ initial
337
+ },
338
+ { onCancel: () => process.exit(0) }
339
+ );
340
+ return value;
341
+ }
342
+ async function text(opts) {
343
+ const { value } = await prompts(
344
+ {
345
+ type: "text",
346
+ name: "value",
347
+ message: opts.message,
348
+ ...opts.placeholder ? { initial: opts.placeholder } : {},
349
+ validate: opts.validate ? (v) => {
350
+ const result = opts.validate(v);
351
+ return result === true ? true : result;
352
+ } : void 0
353
+ },
354
+ { onCancel: () => process.exit(0) }
355
+ );
356
+ return value;
357
+ }
358
+
359
+ // src/commands/logout.ts
360
+ async function logoutCommand(opts) {
361
+ if (!isLoggedIn()) {
362
+ info("Not currently logged in.");
363
+ return;
364
+ }
365
+ if (!opts.yes) {
366
+ const confirmed = await confirm(
367
+ "This will remove all credentials and device keys. Continue?"
368
+ );
369
+ if (!confirmed) return;
370
+ }
371
+ await clearAllConfig();
372
+ success("Logged out. All credentials and keys removed.");
373
+ }
374
+
375
+ // src/commands/whoami.ts
376
+ import { api as api3 } from "@beakcrypt/convex";
377
+
378
+ // src/lib/context.ts
379
+ import { api as api2 } from "@beakcrypt/convex";
380
+
381
+ // src/lib/project-config.ts
382
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, appendFile } from "fs/promises";
383
+ import { existsSync as existsSync2 } from "fs";
384
+ import { join as join2, dirname } from "path";
385
+ var CONFIG_DIR_NAME = ".beakcrypt";
386
+ var CONFIG_FILE_NAME = "project.json";
387
+ function findProjectConfigDir(startDir) {
388
+ let dir = startDir ?? process.cwd();
389
+ const root = dirname(dir) === dir ? dir : "/";
390
+ while (true) {
391
+ const configPath = join2(dir, CONFIG_DIR_NAME, CONFIG_FILE_NAME);
392
+ if (existsSync2(configPath)) {
393
+ return join2(dir, CONFIG_DIR_NAME);
394
+ }
395
+ const parent = dirname(dir);
396
+ if (parent === dir || dir === root) break;
397
+ dir = parent;
398
+ }
399
+ return null;
400
+ }
401
+ async function getProjectConfig(startDir) {
402
+ const configDir = findProjectConfigDir(startDir);
403
+ if (!configDir) return null;
404
+ try {
405
+ const data = await readFile2(join2(configDir, CONFIG_FILE_NAME), "utf-8");
406
+ return JSON.parse(data);
407
+ } catch {
408
+ return null;
409
+ }
410
+ }
411
+ async function saveProjectConfig(config, dir) {
412
+ const baseDir = dir ?? process.cwd();
413
+ const configDir = join2(baseDir, CONFIG_DIR_NAME);
414
+ await mkdir2(configDir, { recursive: true });
415
+ await writeFile2(
416
+ join2(configDir, CONFIG_FILE_NAME),
417
+ JSON.stringify(config, null, 2)
418
+ );
419
+ await ensureGitignore(baseDir);
420
+ return configDir;
421
+ }
422
+ async function ensureGitignore(baseDir) {
423
+ const gitignorePath = join2(baseDir, ".gitignore");
424
+ if (existsSync2(gitignorePath)) {
425
+ const content = await readFile2(gitignorePath, "utf-8");
426
+ if (content.includes(CONFIG_DIR_NAME)) return;
427
+ const suffix = content.endsWith("\n") ? "" : "\n";
428
+ await appendFile(
429
+ gitignorePath,
430
+ `${suffix}
431
+ # Beakcrypt CLI
432
+ ${CONFIG_DIR_NAME}
433
+ `
434
+ );
435
+ } else {
436
+ await writeFile2(gitignorePath, `# Beakcrypt CLI
437
+ ${CONFIG_DIR_NAME}
438
+ `);
439
+ }
440
+ }
441
+
442
+ // src/lib/context.ts
443
+ async function ensureAuth() {
444
+ const auth = await getAuthConfig();
445
+ if (auth) return;
446
+ if (!isInteractive()) {
447
+ throw new CliError("Not logged in. Run `beakcrypt login` first.");
448
+ }
449
+ const shouldLogin = await confirm(
450
+ "You're not logged in. Log in now?",
451
+ true
452
+ );
453
+ if (!shouldLogin) process.exit(0);
454
+ await loginCommand();
455
+ }
456
+ async function resolveContext(flags) {
457
+ await ensureAuth();
458
+ const projectConfig = await getProjectConfig();
459
+ const orgId = flags.org ? await resolveOrgBySlug(flags.org) : projectConfig?.orgId;
460
+ const orgSlug = flags.org ?? projectConfig?.orgSlug;
461
+ const projectId = flags.project ? await resolveProjectByName(orgId, flags.project) : projectConfig?.projectId;
462
+ const projectName = flags.project ?? projectConfig?.projectName;
463
+ const envName = flags.env ?? projectConfig?.defaultEnv;
464
+ if (!orgId || !projectId || !envName || !orgSlug || !projectName) {
465
+ if (!isInteractive()) {
466
+ throw new CliError(
467
+ "No project linked in this directory.",
468
+ "Run `beakcrypt link` to connect this directory to a project."
469
+ );
470
+ }
471
+ const config = await interactiveLink();
472
+ const envId2 = await resolveEnvId(
473
+ config.projectId,
474
+ flags.env ?? config.defaultEnv
475
+ );
476
+ return {
477
+ orgId: config.orgId,
478
+ orgSlug: config.orgSlug,
479
+ projectId: config.projectId,
480
+ projectName: config.projectName,
481
+ envName: flags.env ?? config.defaultEnv,
482
+ environmentId: envId2
483
+ };
484
+ }
485
+ const envId = await resolveEnvId(projectId, envName);
486
+ return {
487
+ orgId,
488
+ orgSlug,
489
+ projectId,
490
+ projectName,
491
+ envName,
492
+ environmentId: envId
493
+ };
494
+ }
495
+ async function resolveOrgBySlug(slug) {
496
+ const result = await query(api2.organizations.getBySlug, { slug });
497
+ const org2 = unwrapResult(result);
498
+ return org2._id;
499
+ }
500
+ async function resolveProjectByName(orgId, name) {
501
+ const result = await query(api2.projects.getByName, {
502
+ orgId,
503
+ name
504
+ });
505
+ const project = unwrapResult(result);
506
+ return project._id;
507
+ }
508
+ async function resolveEnvId(projectId, envName) {
509
+ if (envName === "local") {
510
+ await mutation(api2.environments.ensurePersonalLocal, {
511
+ projectId,
512
+ syncFromDev: false
513
+ });
514
+ }
515
+ const result = await query(api2.environments.list, {
516
+ projectId
517
+ });
518
+ const envs = unwrapResult(result);
519
+ const env2 = envs.find((e) => e.name === envName);
520
+ if (!env2) {
521
+ throw new CliError(`Environment "${envName}" not found.`);
522
+ }
523
+ return env2._id;
524
+ }
525
+ async function interactiveLink() {
526
+ requireInteractive("link");
527
+ const orgsResult = await query(api2.organizations.list, {});
528
+ const orgs = unwrapResult(orgsResult);
529
+ if (orgs.length === 0) {
530
+ throw new CliError("You don't belong to any organizations.");
531
+ }
532
+ const orgId = await select({
533
+ message: "Select an organization:",
534
+ choices: orgs.map((o) => ({
535
+ title: `${o.name} (${o.slug})`,
536
+ value: o._id
537
+ }))
538
+ });
539
+ const selectedOrg = orgs.find((o) => o._id === orgId);
540
+ const action = await select({
541
+ message: "What would you like to do?",
542
+ choices: [
543
+ { title: "Link to an existing project", value: "link" },
544
+ { title: "Create a new project", value: "create" }
545
+ ]
546
+ });
547
+ let projectId;
548
+ let projectName;
549
+ if (action === "create") {
550
+ const name = await text({
551
+ message: "Enter project name:",
552
+ validate: (v) => v.trim().length > 0 ? true : "Project name is required"
553
+ });
554
+ const createResult = await mutation(api2.projects.create, {
555
+ name: name.trim(),
556
+ orgId
557
+ });
558
+ const project = unwrapResult(createResult);
559
+ projectId = project._id;
560
+ projectName = project.name;
561
+ success(`Created project "${projectName}" in ${selectedOrg.name}`);
562
+ } else {
563
+ const projectsResult = await query(api2.projects.list, {
564
+ orgId
565
+ });
566
+ const projects = unwrapResult(projectsResult);
567
+ if (projects.length === 0) {
568
+ throw new CliError("No projects in this organization. Create one first.");
569
+ }
570
+ projectId = await select({
571
+ message: "Select a project:",
572
+ choices: projects.map((p) => ({
573
+ title: p.name,
574
+ value: p._id
575
+ }))
576
+ });
577
+ const selectedProject = projects.find(
578
+ (p) => p._id === projectId
579
+ );
580
+ projectName = selectedProject.name;
581
+ }
582
+ await mutation(api2.environments.ensurePersonalLocal, {
583
+ projectId,
584
+ syncFromDev: false
585
+ });
586
+ const envsResult = await query(api2.environments.list, {
587
+ projectId
588
+ });
589
+ const envs = unwrapResult(envsResult);
590
+ const defaultEnv = await select({
591
+ message: "Select a default environment:",
592
+ choices: envs.map(
593
+ (e) => ({
594
+ title: e.isPersonal ? `${e.name} (personal)` : e.name,
595
+ value: e.name
596
+ })
597
+ )
598
+ });
599
+ const config = {
600
+ orgId,
601
+ orgSlug: selectedOrg.slug,
602
+ projectId,
603
+ projectName,
604
+ defaultEnv
605
+ };
606
+ const configDir = await saveProjectConfig(config);
607
+ success(
608
+ `Linked to ${selectedOrg.slug}/${projectName} (${defaultEnv})`
609
+ );
610
+ info(`Created ${configDir}/project.json`);
611
+ info(`Added .beakcrypt to .gitignore`);
612
+ return config;
613
+ }
614
+
615
+ // src/commands/whoami.ts
616
+ async function whoamiCommand() {
617
+ await ensureAuth();
618
+ const user = await query(api3.auth.getCurrentUser, {});
619
+ if (!user) {
620
+ throw new CliError(
621
+ "Could not retrieve user info. Try `beakcrypt login` again."
622
+ );
623
+ }
624
+ console.log();
625
+ console.log(` ${bold("Email:")} ${user.email}`);
626
+ console.log(` ${bold("Name:")} ${user.name}`);
627
+ console.log();
628
+ }
629
+
630
+ // src/lib/key-manager.ts
631
+ import ora2 from "ora";
632
+ import open2 from "open";
633
+
634
+ // ../crypto/src/core.ts
635
+ var RSA_ALGORITHM = {
636
+ name: "RSA-OAEP",
637
+ modulusLength: 4096,
638
+ publicExponent: new Uint8Array([1, 0, 1]),
639
+ hash: "SHA-256"
640
+ };
641
+ var AES_ALGORITHM = "AES-GCM";
642
+ var AES_KEY_LENGTH = 256;
643
+ var IV_BYTE_LENGTH = 12;
644
+ async function generateKeyPair() {
645
+ const keyPair = await crypto.subtle.generateKey(RSA_ALGORITHM, true, [
646
+ "wrapKey",
647
+ "unwrapKey"
648
+ ]);
649
+ const [publicKey, privateKey] = await Promise.all([
650
+ crypto.subtle.exportKey("jwk", keyPair.publicKey),
651
+ crypto.subtle.exportKey("jwk", keyPair.privateKey)
652
+ ]);
653
+ return { publicKey, privateKey };
654
+ }
655
+ async function unwrapOrgKey(wrappedKeyBase64, privateKeyJwk) {
656
+ const privateKey = await crypto.subtle.importKey(
657
+ "jwk",
658
+ privateKeyJwk,
659
+ RSA_ALGORITHM,
660
+ false,
661
+ ["unwrapKey"]
662
+ );
663
+ const orgKey = await crypto.subtle.unwrapKey(
664
+ "raw",
665
+ base64ToBuffer(wrappedKeyBase64),
666
+ privateKey,
667
+ { name: "RSA-OAEP" },
668
+ { name: AES_ALGORITHM, length: AES_KEY_LENGTH },
669
+ true,
670
+ ["encrypt", "decrypt"]
671
+ );
672
+ const raw = await crypto.subtle.exportKey("raw", orgKey);
673
+ return bufferToBase64(raw);
674
+ }
675
+ async function encryptSecret(plaintext, orgKeyBase64) {
676
+ const key = await crypto.subtle.importKey(
677
+ "raw",
678
+ base64ToBuffer(orgKeyBase64),
679
+ { name: AES_ALGORITHM },
680
+ false,
681
+ ["encrypt"]
682
+ );
683
+ const iv = crypto.getRandomValues(new Uint8Array(IV_BYTE_LENGTH));
684
+ const encoded = new TextEncoder().encode(plaintext);
685
+ const ciphertext = await crypto.subtle.encrypt(
686
+ { name: AES_ALGORITHM, iv },
687
+ key,
688
+ encoded
689
+ );
690
+ return `${bufferToBase64(iv.buffer)}:${bufferToBase64(ciphertext)}`;
691
+ }
692
+ async function decryptSecret(encryptedValue, orgKeyBase64) {
693
+ const [ivBase64, ciphertextBase64] = encryptedValue.split(":");
694
+ if (!ivBase64 || !ciphertextBase64) {
695
+ throw new Error(
696
+ "Invalid encrypted value format \u2014 expected 'iv:ciphertext'"
697
+ );
698
+ }
699
+ const key = await crypto.subtle.importKey(
700
+ "raw",
701
+ base64ToBuffer(orgKeyBase64),
702
+ { name: AES_ALGORITHM },
703
+ false,
704
+ ["decrypt"]
705
+ );
706
+ const decrypted = await crypto.subtle.decrypt(
707
+ { name: AES_ALGORITHM, iv: base64ToBuffer(ivBase64) },
708
+ key,
709
+ base64ToBuffer(ciphertextBase64)
710
+ );
711
+ return new TextDecoder().decode(decrypted);
712
+ }
713
+ function bufferToBase64(buffer) {
714
+ const bytes = new Uint8Array(buffer);
715
+ let binary = "";
716
+ for (let i = 0; i < bytes.byteLength; i++) {
717
+ binary += String.fromCharCode(bytes[i]);
718
+ }
719
+ return btoa(binary);
720
+ }
721
+ function base64ToBuffer(base64) {
722
+ const binary = atob(base64);
723
+ const bytes = new Uint8Array(binary.length);
724
+ for (let i = 0; i < binary.length; i++) {
725
+ bytes[i] = binary.charCodeAt(i);
726
+ }
727
+ return bytes.buffer;
728
+ }
729
+
730
+ // src/lib/key-manager.ts
731
+ import { api as api4 } from "@beakcrypt/convex";
732
+
733
+ // src/lib/key-store.ts
734
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3, rm as rm2 } from "fs/promises";
735
+ import { existsSync as existsSync3 } from "fs";
736
+ import { join as join3 } from "path";
737
+ function getKeysDir() {
738
+ return join3(getConfigDir(), "keys");
739
+ }
740
+ function getKeyFile(orgId) {
741
+ return join3(getKeysDir(), `${orgId}.json`);
742
+ }
743
+ async function getStoredKey(orgId) {
744
+ const file = getKeyFile(orgId);
745
+ if (!existsSync3(file)) return null;
746
+ try {
747
+ const data = await readFile3(file, "utf-8");
748
+ return JSON.parse(data);
749
+ } catch {
750
+ return null;
751
+ }
752
+ }
753
+ async function saveKey(orgId, data) {
754
+ const dir = getKeysDir();
755
+ await mkdir3(dir, { recursive: true });
756
+ await writeFile3(getKeyFile(orgId), JSON.stringify(data, null, 2), {
757
+ mode: 384
758
+ });
759
+ }
760
+ async function updateWrappedOrgKey(orgId, wrappedOrgKey) {
761
+ const existing = await getStoredKey(orgId);
762
+ if (!existing) throw new Error(`No key stored for org ${orgId}`);
763
+ existing.wrappedOrgKey = wrappedOrgKey;
764
+ await saveKey(orgId, existing);
765
+ }
766
+
767
+ // src/lib/key-manager.ts
768
+ async function ensureOrgKey(orgId) {
769
+ const stored = await getStoredKey(orgId);
770
+ if (stored?.wrappedOrgKey) {
771
+ return await unwrapOrgKey(stored.wrappedOrgKey, stored.privateKey);
772
+ }
773
+ if (stored?.keyId) {
774
+ const keyResult = await query(api4.keys.getMyKey, {
775
+ orgId,
776
+ publicKey: JSON.stringify(stored.publicKey)
777
+ });
778
+ const keyRecord = unwrapResult(keyResult);
779
+ if (keyRecord?.status === "active" && keyRecord.wrappedOrgKey) {
780
+ await updateWrappedOrgKey(orgId, keyRecord.wrappedOrgKey);
781
+ return await unwrapOrgKey(
782
+ keyRecord.wrappedOrgKey,
783
+ stored.privateKey
784
+ );
785
+ }
786
+ if (keyRecord?.status === "pending") {
787
+ return await waitForApproval(orgId, stored);
788
+ }
789
+ throw new Error(
790
+ "Device key is not active. An admin may need to approve this device."
791
+ );
792
+ }
793
+ return await registerNewDevice(orgId);
794
+ }
795
+ async function registerNewDevice(orgId) {
796
+ const sessionToken = getSessionToken();
797
+ if (!sessionToken) throw new Error("Not logged in");
798
+ const spinner = ora2("Generating device keys...").start();
799
+ let keyPair;
800
+ let keyRecord;
801
+ try {
802
+ keyPair = await generateKeyPair();
803
+ spinner.text = "Registering device with organization...";
804
+ const result = await mutation(api4.keys.registerKey, {
805
+ orgId,
806
+ publicKey: JSON.stringify(keyPair.publicKey),
807
+ sessionToken
808
+ });
809
+ keyRecord = unwrapResult(result);
810
+ spinner.stop();
811
+ } catch (err) {
812
+ spinner.fail("Device registration failed.");
813
+ throw err;
814
+ }
815
+ const stored = {
816
+ publicKey: keyPair.publicKey,
817
+ privateKey: keyPair.privateKey,
818
+ keyId: keyRecord._id,
819
+ wrappedOrgKey: keyRecord.wrappedOrgKey
820
+ };
821
+ await saveKey(orgId, stored);
822
+ if (keyRecord.status === "active" && keyRecord.wrappedOrgKey) {
823
+ success("Device registered and activated.");
824
+ return await unwrapOrgKey(
825
+ keyRecord.wrappedOrgKey,
826
+ keyPair.privateKey
827
+ );
828
+ }
829
+ return await waitForApproval(orgId, stored);
830
+ }
831
+ async function waitForApproval(orgId, stored) {
832
+ try {
833
+ const projectConfig = await getProjectConfig();
834
+ if (projectConfig?.orgSlug && stored.keyId) {
835
+ const siteUrl = getSiteUrl();
836
+ const approveUrl = `${siteUrl}/${projectConfig.orgSlug}/sessions?approveSession=${stored.keyId}`;
837
+ info("Opening browser to approve this device...");
838
+ console.log(`${dim("If the browser doesn't open, visit:")}`);
839
+ console.log(`${link(approveUrl)}
840
+ `);
841
+ try {
842
+ await open2(approveUrl);
843
+ } catch {
844
+ }
845
+ }
846
+ } catch {
847
+ }
848
+ const spinner = ora2("Waiting for device to be approved...").start();
849
+ spinner.indent = 2;
850
+ while (true) {
851
+ await new Promise((r) => setTimeout(r, 5e3));
852
+ const result = await query(api4.keys.getMyKey, {
853
+ orgId,
854
+ publicKey: JSON.stringify(stored.publicKey)
855
+ });
856
+ const keyRecord = unwrapResult(result);
857
+ if (keyRecord?.status === "active" && keyRecord.wrappedOrgKey) {
858
+ spinner.stop();
859
+ await updateWrappedOrgKey(orgId, keyRecord.wrappedOrgKey);
860
+ success("Device approved!");
861
+ return await unwrapOrgKey(
862
+ keyRecord.wrappedOrgKey,
863
+ stored.privateKey
864
+ );
865
+ }
866
+ if (keyRecord?.status === "revoked") {
867
+ spinner.stop();
868
+ throw new Error("Device key was revoked. Re-run `beakcrypt login`.");
869
+ }
870
+ }
871
+ }
872
+
873
+ // src/commands/link.ts
874
+ async function linkCommand() {
875
+ await ensureAuth();
876
+ const config = await interactiveLink();
877
+ await ensureOrgKey(config.orgId);
878
+ }
879
+
880
+ // src/commands/pull.ts
881
+ import { resolve } from "path";
882
+ import ora3 from "ora";
883
+ import { api as api5 } from "@beakcrypt/convex";
884
+
885
+ // src/lib/env-file.ts
886
+ import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
887
+ import { existsSync as existsSync4 } from "fs";
888
+ function parseEnvFile(content) {
889
+ const env2 = {};
890
+ for (const line of content.split("\n")) {
891
+ const trimmed = line.trim();
892
+ if (!trimmed || trimmed.startsWith("#")) continue;
893
+ const eqIndex = trimmed.indexOf("=");
894
+ if (eqIndex === -1) continue;
895
+ const key = trimmed.slice(0, eqIndex).trim();
896
+ let value = trimmed.slice(eqIndex + 1).trim();
897
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
898
+ value = value.slice(1, -1);
899
+ }
900
+ if (key) {
901
+ env2[key] = value;
902
+ }
903
+ }
904
+ return env2;
905
+ }
906
+ function serializeEnvFile(env2) {
907
+ const lines = [
908
+ "# Generated by Beakcrypt CLI",
909
+ `# ${(/* @__PURE__ */ new Date()).toISOString()}`,
910
+ ""
911
+ ];
912
+ const sorted = Object.entries(env2).sort(([a], [b]) => a.localeCompare(b));
913
+ for (const [key, value] of sorted) {
914
+ if (/[\s#]/.test(value)) {
915
+ lines.push(`${key}="${value}"`);
916
+ } else {
917
+ lines.push(`${key}=${value}`);
918
+ }
919
+ }
920
+ lines.push("");
921
+ return lines.join("\n");
922
+ }
923
+ async function readEnvFile(filePath) {
924
+ if (!existsSync4(filePath)) {
925
+ return {};
926
+ }
927
+ const content = await readFile4(filePath, "utf-8");
928
+ return parseEnvFile(content);
929
+ }
930
+ async function writeEnvFile(filePath, env2) {
931
+ await writeFile4(filePath, serializeEnvFile(env2));
932
+ }
933
+
934
+ // src/commands/pull.ts
935
+ async function pullCommand(file, opts) {
936
+ const filePath = resolve(file ?? ".env.local");
937
+ const ctx = await resolveContext(opts);
938
+ const spinner = ora3("Pulling secrets...").start();
939
+ const orgKey = await ensureOrgKey(ctx.orgId);
940
+ const secretsResult = await query(api5.secrets.list, {
941
+ environmentId: ctx.environmentId
942
+ });
943
+ const secrets2 = unwrapResult(secretsResult);
944
+ const decrypted = {};
945
+ for (const secret of secrets2) {
946
+ decrypted[secret.key] = await decryptSecret(secret.encryptedValue, orgKey);
947
+ }
948
+ await writeEnvFile(filePath, decrypted);
949
+ spinner.stop();
950
+ success(
951
+ `Pulled ${secrets2.length} secret${secrets2.length === 1 ? "" : "s"} to ${file ?? ".env.local"}`
952
+ );
953
+ }
954
+
955
+ // src/commands/push.ts
956
+ import { resolve as resolve2 } from "path";
957
+ import { existsSync as existsSync5 } from "fs";
958
+ import ora4 from "ora";
959
+ import { api as api6 } from "@beakcrypt/convex";
960
+ async function pushCommand(file, opts) {
961
+ const filePath = resolve2(file ?? ".env.local");
962
+ if (!existsSync5(filePath)) {
963
+ throw new CliError(`File not found: ${filePath}`);
964
+ }
965
+ const ctx = await resolveContext(opts);
966
+ const env2 = await readEnvFile(filePath);
967
+ const keys = Object.keys(env2);
968
+ if (keys.length === 0) {
969
+ info("No secrets found in file.");
970
+ return;
971
+ }
972
+ if (!opts.yes) {
973
+ info(
974
+ `Pushing ${keys.length} secret${keys.length === 1 ? "" : "s"} to ${ctx.orgSlug}/${ctx.projectName} (${ctx.envName})`
975
+ );
976
+ const confirmed = await confirm("Continue?", true);
977
+ if (!confirmed) return;
978
+ }
979
+ const spinner = ora4("Encrypting and pushing secrets...").start();
980
+ const orgKey = await ensureOrgKey(ctx.orgId);
981
+ const encryptedSecrets = [];
982
+ for (const [key, value] of Object.entries(env2)) {
983
+ const encrypted = await encryptSecret(value, orgKey);
984
+ encryptedSecrets.push({ key, encryptedValue: encrypted });
985
+ }
986
+ const result = await mutation(api6.secrets.bulkCreate, {
987
+ environmentId: ctx.environmentId,
988
+ secrets: encryptedSecrets,
989
+ overwrite: true
990
+ });
991
+ const summary = unwrapResult(result);
992
+ spinner.stop();
993
+ success(
994
+ `Pushed ${summary.created} created, ${summary.updated} updated, ${summary.skipped} skipped`
995
+ );
996
+ }
997
+
998
+ // src/commands/run.ts
999
+ import { spawn } from "child_process";
1000
+ import ora5 from "ora";
1001
+ import { api as api7 } from "@beakcrypt/convex";
1002
+ async function runCommand(args, opts) {
1003
+ if (args.length === 0) {
1004
+ throw new CliError(
1005
+ "No command provided. Usage: beakcrypt run -- <command>"
1006
+ );
1007
+ }
1008
+ const ctx = await resolveContext(opts);
1009
+ const spinner = ora5("Loading secrets...").start();
1010
+ const orgKey = await ensureOrgKey(ctx.orgId);
1011
+ const secretsResult = await query(api7.secrets.list, {
1012
+ environmentId: ctx.environmentId
1013
+ });
1014
+ const secrets2 = unwrapResult(secretsResult);
1015
+ const decrypted = {};
1016
+ for (const secret of secrets2) {
1017
+ decrypted[secret.key] = await decryptSecret(secret.encryptedValue, orgKey);
1018
+ }
1019
+ spinner.stop();
1020
+ const [command, ...commandArgs] = args;
1021
+ const child = spawn(command, commandArgs, {
1022
+ stdio: "inherit",
1023
+ env: { ...process.env, ...decrypted }
1024
+ });
1025
+ child.on("close", (code) => {
1026
+ process.exit(code ?? 0);
1027
+ });
1028
+ child.on("error", (err) => {
1029
+ throw new CliError(`Failed to run command: ${err.message}`);
1030
+ });
1031
+ }
1032
+
1033
+ // src/commands/secrets/list.ts
1034
+ import { api as api8 } from "@beakcrypt/convex";
1035
+ async function secretsListCommand(opts) {
1036
+ const ctx = await resolveContext(opts);
1037
+ const result = await query(api8.secrets.list, {
1038
+ environmentId: ctx.environmentId
1039
+ });
1040
+ const secrets2 = unwrapResult(result);
1041
+ if (secrets2.length === 0) {
1042
+ info(
1043
+ `No secrets in ${ctx.orgSlug}/${ctx.projectName} (${ctx.envName})`
1044
+ );
1045
+ return;
1046
+ }
1047
+ console.log(
1048
+ `
1049
+ ${bold(`${ctx.orgSlug}/${ctx.projectName}`)} ${dim(`(${ctx.envName})`)}
1050
+ `
1051
+ );
1052
+ table(
1053
+ secrets2.map((s) => [s.key, "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"]),
1054
+ ["Key", "Value"]
1055
+ );
1056
+ console.log(
1057
+ `
1058
+ ${dim(`${secrets2.length} secret${secrets2.length === 1 ? "" : "s"}`)}
1059
+ `
1060
+ );
1061
+ }
1062
+
1063
+ // src/commands/secrets/set.ts
1064
+ import ora6 from "ora";
1065
+ import { api as api9 } from "@beakcrypt/convex";
1066
+ async function secretsSetCommand(pairs, opts) {
1067
+ if (pairs.length === 0) {
1068
+ throw new CliError(
1069
+ "No key=value pairs provided. Usage: beakcrypt secrets set KEY=VALUE"
1070
+ );
1071
+ }
1072
+ const parsed = [];
1073
+ for (const pair of pairs) {
1074
+ const eqIndex = pair.indexOf("=");
1075
+ if (eqIndex === -1) {
1076
+ throw new CliError(`Invalid format: "${pair}". Use KEY=VALUE.`);
1077
+ }
1078
+ parsed.push({
1079
+ key: pair.slice(0, eqIndex),
1080
+ value: pair.slice(eqIndex + 1)
1081
+ });
1082
+ }
1083
+ const ctx = await resolveContext(opts);
1084
+ const spinner = ora6("Setting secrets...").start();
1085
+ const orgKey = await ensureOrgKey(ctx.orgId);
1086
+ const encryptedSecrets = [];
1087
+ for (const { key, value } of parsed) {
1088
+ const encrypted = await encryptSecret(value, orgKey);
1089
+ encryptedSecrets.push({ key, encryptedValue: encrypted });
1090
+ }
1091
+ const result = await mutation(api9.secrets.bulkCreate, {
1092
+ environmentId: ctx.environmentId,
1093
+ secrets: encryptedSecrets,
1094
+ overwrite: true
1095
+ });
1096
+ unwrapResult(result);
1097
+ spinner.stop();
1098
+ success(
1099
+ `Set ${parsed.length} secret${parsed.length === 1 ? "" : "s"} in ${ctx.envName}`
1100
+ );
1101
+ }
1102
+
1103
+ // src/commands/secrets/remove.ts
1104
+ import ora7 from "ora";
1105
+ import { api as api10 } from "@beakcrypt/convex";
1106
+ async function secretsRemoveCommand(keys, opts) {
1107
+ if (keys.length === 0) {
1108
+ throw new CliError(
1109
+ "No keys provided. Usage: beakcrypt secrets remove KEY [KEY2...]"
1110
+ );
1111
+ }
1112
+ const ctx = await resolveContext(opts);
1113
+ const spinner = ora7("Removing secrets...").start();
1114
+ const secretsResult = await query(api10.secrets.list, {
1115
+ environmentId: ctx.environmentId
1116
+ });
1117
+ const secrets2 = unwrapResult(secretsResult);
1118
+ let removed = 0;
1119
+ const notFound = [];
1120
+ for (const key of keys) {
1121
+ const secret = secrets2.find((s) => s.key === key);
1122
+ if (secret) {
1123
+ const result = await mutation(api10.secrets.remove, {
1124
+ id: secret._id
1125
+ });
1126
+ unwrapResult(result);
1127
+ removed++;
1128
+ } else {
1129
+ notFound.push(key);
1130
+ }
1131
+ }
1132
+ spinner.stop();
1133
+ if (removed > 0) {
1134
+ success(
1135
+ `Removed ${removed} secret${removed === 1 ? "" : "s"} from ${ctx.envName}`
1136
+ );
1137
+ }
1138
+ if (notFound.length > 0) {
1139
+ warn(`Not found: ${notFound.join(", ")}`);
1140
+ }
1141
+ }
1142
+
1143
+ // src/commands/secrets/clear.ts
1144
+ import ora8 from "ora";
1145
+ import { api as api11 } from "@beakcrypt/convex";
1146
+ async function secretsClearCommand(opts) {
1147
+ const ctx = await resolveContext(opts);
1148
+ if (!opts.yes) {
1149
+ const confirmed = await confirm(
1150
+ `Delete ALL secrets in ${ctx.orgSlug}/${ctx.projectName} (${ctx.envName})?`
1151
+ );
1152
+ if (!confirmed) return;
1153
+ }
1154
+ const spinner = ora8("Clearing secrets...").start();
1155
+ const result = await mutation(api11.secrets.removeAll, {
1156
+ environmentId: ctx.environmentId
1157
+ });
1158
+ const summary = unwrapResult(result);
1159
+ spinner.stop();
1160
+ success(
1161
+ `Deleted ${summary.deleted} secret${summary.deleted === 1 ? "" : "s"}`
1162
+ );
1163
+ }
1164
+
1165
+ // src/commands/org/list.ts
1166
+ import { api as api12 } from "@beakcrypt/convex";
1167
+ async function orgListCommand() {
1168
+ await ensureAuth();
1169
+ const result = await query(api12.organizations.list, {});
1170
+ const orgs = unwrapResult(result);
1171
+ if (orgs.length === 0) {
1172
+ info("You don't belong to any organizations.");
1173
+ return;
1174
+ }
1175
+ console.log();
1176
+ table(
1177
+ orgs.map((o) => [o.name, o.slug]),
1178
+ ["Name", "Slug"]
1179
+ );
1180
+ console.log();
1181
+ }
1182
+
1183
+ // src/commands/env/list.ts
1184
+ import { api as api13 } from "@beakcrypt/convex";
1185
+ async function envListCommand(opts) {
1186
+ const ctx = await resolveContext({ ...opts, env: "development" });
1187
+ const result = await query(api13.environments.list, {
1188
+ projectId: ctx.projectId
1189
+ });
1190
+ const envs = unwrapResult(result);
1191
+ if (envs.length === 0) {
1192
+ info("No environments found.");
1193
+ return;
1194
+ }
1195
+ console.log(
1196
+ `
1197
+ ${bold(`${ctx.orgSlug}/${ctx.projectName}`)} environments:
1198
+ `
1199
+ );
1200
+ table(
1201
+ envs.map((e) => [
1202
+ e.name,
1203
+ e.isPersonal ? "personal" : "shared"
1204
+ ]),
1205
+ ["Name", "Type"]
1206
+ );
1207
+ console.log();
1208
+ }
1209
+
1210
+ // src/interactive-mode.ts
1211
+ import pc4 from "picocolors";
1212
+ async function interactiveMode() {
1213
+ requireInteractive("(no args)");
1214
+ console.log(`
1215
+ ${pc4.bold("Beakcrypt")} ${pc4.dim("v0.1.0")}
1216
+ `);
1217
+ const auth = await getAuthConfig();
1218
+ if (!auth) {
1219
+ const shouldLogin = await confirm(
1220
+ "You're not logged in. Log in now?",
1221
+ true
1222
+ );
1223
+ if (!shouldLogin) process.exit(0);
1224
+ await loginCommand();
1225
+ }
1226
+ const config = await getProjectConfig();
1227
+ if (!config) {
1228
+ info("No project linked in this directory.");
1229
+ const shouldLink = await confirm(
1230
+ "Link to a project now?",
1231
+ true
1232
+ );
1233
+ if (!shouldLink) process.exit(0);
1234
+ await linkCommand();
1235
+ return;
1236
+ }
1237
+ console.log(
1238
+ `Linked to ${bold(`${config.orgSlug}/${config.projectName}`)} ${dim(`(${config.defaultEnv}`)})`
1239
+ );
1240
+ console.log();
1241
+ const action = await select({
1242
+ message: "What would you like to do?",
1243
+ choices: [
1244
+ { title: "Pull secrets (.env.local)", value: "pull" },
1245
+ { title: "Push secrets (.env.local)", value: "push" },
1246
+ { title: "List secrets", value: "list" },
1247
+ { title: "Change project link", value: "link" }
1248
+ ]
1249
+ });
1250
+ switch (action) {
1251
+ case "pull":
1252
+ await pullCommand(void 0, {});
1253
+ break;
1254
+ case "push":
1255
+ await pushCommand(void 0, {});
1256
+ break;
1257
+ case "list":
1258
+ await secretsListCommand({});
1259
+ break;
1260
+ case "link":
1261
+ await linkCommand();
1262
+ break;
1263
+ }
1264
+ }
1265
+
1266
+ // src/index.ts
1267
+ var program = new Command();
1268
+ program.name("beakcrypt").description("Secure environment variable management with E2E encryption").version("0.1.0").action(async () => {
1269
+ try {
1270
+ await interactiveMode();
1271
+ } catch (err) {
1272
+ handleError(err);
1273
+ }
1274
+ });
1275
+ program.command("login").description("Log in to Beakcrypt via GitHub OAuth").action(async () => {
1276
+ try {
1277
+ await loginCommand();
1278
+ } catch (err) {
1279
+ handleError(err);
1280
+ }
1281
+ });
1282
+ program.command("logout").description("Log out and remove all credentials").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
1283
+ try {
1284
+ await logoutCommand(opts);
1285
+ } catch (err) {
1286
+ handleError(err);
1287
+ }
1288
+ });
1289
+ program.command("whoami").description("Show current user info").action(async () => {
1290
+ try {
1291
+ await whoamiCommand();
1292
+ } catch (err) {
1293
+ handleError(err);
1294
+ }
1295
+ });
1296
+ program.command("link").description("Link this directory to a Beakcrypt project").action(async () => {
1297
+ try {
1298
+ await linkCommand();
1299
+ } catch (err) {
1300
+ handleError(err);
1301
+ }
1302
+ });
1303
+ program.command("pull [file]").description("Pull secrets to a local .env file").option("-o, --org <slug>", "Organization slug").option("-p, --project <name>", "Project name").option("-e, --env <name>", "Environment name").action(async (file, opts) => {
1304
+ try {
1305
+ await pullCommand(file, opts);
1306
+ } catch (err) {
1307
+ handleError(err);
1308
+ }
1309
+ });
1310
+ program.command("push [file]").description("Push secrets from a local .env file").option("-o, --org <slug>", "Organization slug").option("-p, --project <name>", "Project name").option("-e, --env <name>", "Environment name").option("-y, --yes", "Skip confirmation prompt").action(async (file, opts) => {
1311
+ try {
1312
+ await pushCommand(file, opts);
1313
+ } catch (err) {
1314
+ handleError(err);
1315
+ }
1316
+ });
1317
+ program.command("run").description("Run a command with injected secrets").option("-o, --org <slug>", "Organization slug").option("-p, --project <name>", "Project name").option("-e, --env <name>", "Environment name").allowUnknownOption().action(async (opts, cmd) => {
1318
+ try {
1319
+ await runCommand(cmd.args, opts);
1320
+ } catch (err) {
1321
+ handleError(err);
1322
+ }
1323
+ });
1324
+ var secrets = program.command("secrets").description("Manage secrets");
1325
+ secrets.command("list").description("List secrets (values masked)").option("-o, --org <slug>", "Organization slug").option("-p, --project <name>", "Project name").option("-e, --env <name>", "Environment name").action(async (opts) => {
1326
+ try {
1327
+ await secretsListCommand(opts);
1328
+ } catch (err) {
1329
+ handleError(err);
1330
+ }
1331
+ });
1332
+ secrets.command("set <pairs...>").description("Set secrets (KEY=VALUE)").option("-o, --org <slug>", "Organization slug").option("-p, --project <name>", "Project name").option("-e, --env <name>", "Environment name").action(async (pairs, opts) => {
1333
+ try {
1334
+ await secretsSetCommand(pairs, opts);
1335
+ } catch (err) {
1336
+ handleError(err);
1337
+ }
1338
+ });
1339
+ secrets.command("remove <keys...>").description("Remove secrets by key").option("-o, --org <slug>", "Organization slug").option("-p, --project <name>", "Project name").option("-e, --env <name>", "Environment name").action(async (keys, opts) => {
1340
+ try {
1341
+ await secretsRemoveCommand(keys, opts);
1342
+ } catch (err) {
1343
+ handleError(err);
1344
+ }
1345
+ });
1346
+ secrets.command("clear").description("Delete all secrets in an environment").option("-o, --org <slug>", "Organization slug").option("-p, --project <name>", "Project name").option("-e, --env <name>", "Environment name").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
1347
+ try {
1348
+ await secretsClearCommand(opts);
1349
+ } catch (err) {
1350
+ handleError(err);
1351
+ }
1352
+ });
1353
+ var org = program.command("org").description("Manage organizations");
1354
+ org.command("list").description("List your organizations").action(async () => {
1355
+ try {
1356
+ await orgListCommand();
1357
+ } catch (err) {
1358
+ handleError(err);
1359
+ }
1360
+ });
1361
+ var env = program.command("env").description("Manage environments");
1362
+ env.command("list").description("List environments for the linked project").option("-o, --org <slug>", "Organization slug").option("-p, --project <name>", "Project name").action(async (opts) => {
1363
+ try {
1364
+ await envListCommand(opts);
1365
+ } catch (err) {
1366
+ handleError(err);
1367
+ }
1368
+ });
1369
+ program.parse();
1370
+ //# sourceMappingURL=index.js.map