@envpilot/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2042 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command as Command9 } from "commander";
5
+
6
+ // src/commands/login.ts
7
+ import { Command } from "commander";
8
+ import chalk2 from "chalk";
9
+ import open from "open";
10
+
11
+ // src/lib/ui.ts
12
+ import chalk from "chalk";
13
+ import ora from "ora";
14
+ function createSpinner(text) {
15
+ return ora({
16
+ text,
17
+ color: "cyan"
18
+ });
19
+ }
20
+ async function withSpinner(text, operation, options) {
21
+ const spinner = createSpinner(text);
22
+ spinner.start();
23
+ try {
24
+ const result = await operation();
25
+ spinner.succeed(options?.successText ?? text);
26
+ return result;
27
+ } catch (error2) {
28
+ spinner.fail(options?.failText ?? text);
29
+ throw error2;
30
+ }
31
+ }
32
+ function success(message) {
33
+ console.log(chalk.green("\u2713"), message);
34
+ }
35
+ function info(message) {
36
+ console.log(chalk.blue("\u2139"), message);
37
+ }
38
+ function warning(message) {
39
+ console.log(chalk.yellow("\u26A0"), message);
40
+ }
41
+ function error(message) {
42
+ console.log(chalk.red("\u2717"), message);
43
+ }
44
+ function header(text) {
45
+ console.log();
46
+ console.log(chalk.bold(text));
47
+ console.log(chalk.dim("\u2500".repeat(text.length)));
48
+ }
49
+ function table(data, columns) {
50
+ if (data.length === 0) {
51
+ console.log(chalk.dim("No data to display"));
52
+ return;
53
+ }
54
+ const widths = columns.map((col) => {
55
+ const headerWidth = col.header.length;
56
+ const maxDataWidth = Math.max(
57
+ ...data.map((row) => String(row[col.key] ?? "").length)
58
+ );
59
+ return col.width ?? Math.max(headerWidth, maxDataWidth);
60
+ });
61
+ const headerLine = columns.map((col, i) => col.header.padEnd(widths[i])).join(" ");
62
+ console.log(chalk.bold(headerLine));
63
+ console.log(chalk.dim("\u2500".repeat(headerLine.length)));
64
+ for (const row of data) {
65
+ const line = columns.map((col, i) => String(row[col.key] ?? "").padEnd(widths[i])).join(" ");
66
+ console.log(line);
67
+ }
68
+ }
69
+ function keyValue(pairs) {
70
+ const maxKeyLength = Math.max(...pairs.map(([key]) => key.length));
71
+ for (const [key, value] of pairs) {
72
+ const paddedKey = key.padEnd(maxKeyLength);
73
+ console.log(`${chalk.dim(paddedKey)} ${value ?? chalk.dim("(not set)")}`);
74
+ }
75
+ }
76
+ function diff(added, removed, changed) {
77
+ if (Object.keys(added).length === 0 && Object.keys(removed).length === 0 && Object.keys(changed).length === 0) {
78
+ console.log(chalk.dim("No changes"));
79
+ return;
80
+ }
81
+ for (const [key, value] of Object.entries(added)) {
82
+ console.log(chalk.green(`+ ${key}=${maskValue(value)}`));
83
+ }
84
+ for (const [key, value] of Object.entries(removed)) {
85
+ console.log(chalk.red(`- ${key}=${maskValue(value)}`));
86
+ }
87
+ for (const [key, { local, remote }] of Object.entries(changed)) {
88
+ console.log(chalk.red(`- ${key}=${maskValue(remote)}`));
89
+ console.log(chalk.green(`+ ${key}=${maskValue(local)}`));
90
+ }
91
+ }
92
+ function maskValue(value, showChars = 4) {
93
+ if (value.length <= showChars * 2) {
94
+ return "*".repeat(value.length);
95
+ }
96
+ return value.slice(0, showChars) + "****" + value.slice(-showChars);
97
+ }
98
+ function formatRole(role) {
99
+ switch (role) {
100
+ case "admin":
101
+ return chalk.green("Admin");
102
+ case "team_lead":
103
+ return chalk.blue("Team Lead");
104
+ case "member":
105
+ return chalk.yellow("Member");
106
+ default:
107
+ return chalk.dim("Unknown");
108
+ }
109
+ }
110
+ function roleNotice(role) {
111
+ if (role === "member") {
112
+ console.log(
113
+ chalk.yellow(
114
+ " You have Member access. Write operations will create pending requests for approval."
115
+ )
116
+ );
117
+ }
118
+ }
119
+ function formatProjectRole(role) {
120
+ switch (role) {
121
+ case "manager":
122
+ return chalk.green("Manager");
123
+ case "developer":
124
+ return chalk.blue("Developer");
125
+ case "viewer":
126
+ return chalk.yellow("Viewer");
127
+ default:
128
+ return chalk.dim("-");
129
+ }
130
+ }
131
+ function projectRoleNotice(projectRole) {
132
+ if (projectRole === "viewer") {
133
+ console.log(
134
+ chalk.yellow(
135
+ " You have Viewer access to this project. You can only view variables you have been explicitly granted access to."
136
+ )
137
+ );
138
+ }
139
+ }
140
+
141
+ // src/lib/config.ts
142
+ import Conf from "conf";
143
+ var DEFAULT_API_URL = "https://www.envpilot.dev";
144
+ var config = new Conf({
145
+ projectName: "envpilot",
146
+ defaults: {
147
+ apiUrl: DEFAULT_API_URL
148
+ }
149
+ });
150
+ function getConfig() {
151
+ return {
152
+ apiUrl: config.get("apiUrl") ?? DEFAULT_API_URL,
153
+ accessToken: config.get("accessToken"),
154
+ refreshToken: config.get("refreshToken"),
155
+ activeProjectId: config.get("activeProjectId"),
156
+ activeOrganizationId: config.get("activeOrganizationId"),
157
+ user: config.get("user"),
158
+ role: config.get("role")
159
+ };
160
+ }
161
+ function getApiUrl() {
162
+ return config.get("apiUrl") ?? DEFAULT_API_URL;
163
+ }
164
+ function setApiUrl(url) {
165
+ config.set("apiUrl", url);
166
+ }
167
+ function getAccessToken() {
168
+ return config.get("accessToken");
169
+ }
170
+ function setAccessToken(token) {
171
+ config.set("accessToken", token);
172
+ }
173
+ function setRefreshToken(token) {
174
+ config.set("refreshToken", token);
175
+ }
176
+ function setActiveProjectId(projectId) {
177
+ config.set("activeProjectId", projectId);
178
+ }
179
+ function setActiveOrganizationId(organizationId) {
180
+ config.set("activeOrganizationId", organizationId);
181
+ }
182
+ function getUser() {
183
+ return config.get("user");
184
+ }
185
+ function setUser(user) {
186
+ config.set("user", user);
187
+ }
188
+ function getRole() {
189
+ return config.get("role");
190
+ }
191
+ function setRole(role) {
192
+ config.set("role", role);
193
+ }
194
+ function isAuthenticated() {
195
+ return !!config.get("accessToken");
196
+ }
197
+ function clearAuth() {
198
+ config.delete("accessToken");
199
+ config.delete("refreshToken");
200
+ config.delete("user");
201
+ config.delete("role");
202
+ }
203
+ function clearConfig() {
204
+ config.clear();
205
+ }
206
+ function getConfigPath() {
207
+ return config.path;
208
+ }
209
+
210
+ // src/lib/api.ts
211
+ var APIError = class extends Error {
212
+ constructor(message, statusCode, code) {
213
+ super(message);
214
+ this.statusCode = statusCode;
215
+ this.code = code;
216
+ this.name = "APIError";
217
+ }
218
+ };
219
+ var APIClient = class {
220
+ baseUrl;
221
+ accessToken;
222
+ constructor(options) {
223
+ this.baseUrl = options?.baseUrl ?? getApiUrl();
224
+ this.accessToken = options?.accessToken ?? getAccessToken();
225
+ }
226
+ /**
227
+ * Get headers for API requests
228
+ */
229
+ getHeaders() {
230
+ const headers = {
231
+ "Content-Type": "application/json"
232
+ };
233
+ if (this.accessToken) {
234
+ headers["Authorization"] = `Bearer ${this.accessToken}`;
235
+ }
236
+ return headers;
237
+ }
238
+ /**
239
+ * Make a GET request
240
+ */
241
+ async get(path, params) {
242
+ const url = new URL(path, this.baseUrl);
243
+ if (params) {
244
+ for (const [key, value] of Object.entries(params)) {
245
+ url.searchParams.set(key, value);
246
+ }
247
+ }
248
+ const response = await fetch(url.toString(), {
249
+ method: "GET",
250
+ headers: this.getHeaders()
251
+ });
252
+ return this.handleResponse(response);
253
+ }
254
+ /**
255
+ * Make a POST request
256
+ */
257
+ async post(path, body) {
258
+ const url = new URL(path, this.baseUrl);
259
+ const response = await fetch(url.toString(), {
260
+ method: "POST",
261
+ headers: this.getHeaders(),
262
+ body: body ? JSON.stringify(body) : void 0
263
+ });
264
+ return this.handleResponse(response);
265
+ }
266
+ /**
267
+ * Make a PUT request
268
+ */
269
+ async put(path, body) {
270
+ const url = new URL(path, this.baseUrl);
271
+ const response = await fetch(url.toString(), {
272
+ method: "PUT",
273
+ headers: this.getHeaders(),
274
+ body: body ? JSON.stringify(body) : void 0
275
+ });
276
+ return this.handleResponse(response);
277
+ }
278
+ /**
279
+ * Make a PATCH request
280
+ */
281
+ async patch(path, body) {
282
+ const url = new URL(path, this.baseUrl);
283
+ const response = await fetch(url.toString(), {
284
+ method: "PATCH",
285
+ headers: this.getHeaders(),
286
+ body: body ? JSON.stringify(body) : void 0
287
+ });
288
+ return this.handleResponse(response);
289
+ }
290
+ /**
291
+ * Make a DELETE request
292
+ */
293
+ async delete(path) {
294
+ const url = new URL(path, this.baseUrl);
295
+ const response = await fetch(url.toString(), {
296
+ method: "DELETE",
297
+ headers: this.getHeaders()
298
+ });
299
+ if (!response.ok) {
300
+ await this.handleError(response);
301
+ }
302
+ }
303
+ /**
304
+ * Handle API response
305
+ */
306
+ async handleResponse(response) {
307
+ if (!response.ok) {
308
+ await this.handleError(response);
309
+ }
310
+ const contentType = response.headers.get("content-type") || "";
311
+ if (!contentType.includes("application/json")) {
312
+ const body2 = await response.text();
313
+ const preview = body2.replace(/\s+/g, " ").slice(0, 160);
314
+ throw new APIError(
315
+ `Expected JSON but got ${contentType || "unknown content type"} from ${response.url}. Response starts with: ${preview}`,
316
+ response.status || 500
317
+ );
318
+ }
319
+ const body = await response.text();
320
+ try {
321
+ const data = JSON.parse(body);
322
+ return data;
323
+ } catch {
324
+ const preview = body.replace(/\s+/g, " ").slice(0, 160);
325
+ throw new APIError(
326
+ `Failed to parse JSON response from ${response.url}. Response starts with: ${preview}`,
327
+ response.status || 500
328
+ );
329
+ }
330
+ }
331
+ /**
332
+ * Handle API errors
333
+ */
334
+ async handleError(response) {
335
+ let message = `Request failed with status ${response.status}`;
336
+ let code;
337
+ try {
338
+ const data = await response.json();
339
+ message = data.error || data.message || message;
340
+ code = data.code;
341
+ } catch {
342
+ }
343
+ if (response.status === 401) {
344
+ clearAuth();
345
+ throw new APIError(
346
+ "Authentication required. Please run `envpilot login`.",
347
+ 401,
348
+ "UNAUTHORIZED"
349
+ );
350
+ }
351
+ if (response.status === 403) {
352
+ throw new APIError(message || "Access denied.", 403, code || "FORBIDDEN");
353
+ }
354
+ if (response.status === 402) {
355
+ throw new APIError(
356
+ message || "Payment is currently disabled for this pre-alpha build.",
357
+ 402,
358
+ "PAYMENT_REQUIRED"
359
+ );
360
+ }
361
+ throw new APIError(message, response.status, code);
362
+ }
363
+ // ============================================
364
+ // High-level API methods
365
+ // ============================================
366
+ /**
367
+ * Get current user info
368
+ */
369
+ async getCurrentUser() {
370
+ return this.get("/api/cli/auth/me");
371
+ }
372
+ /**
373
+ * Get tier info for the active organization
374
+ */
375
+ async getTierInfo(organizationId) {
376
+ return this.get("/api/cli/tier", { organizationId });
377
+ }
378
+ /**
379
+ * List organizations the user has access to
380
+ */
381
+ async listOrganizations() {
382
+ const response = await this.get(
383
+ "/api/cli/organizations"
384
+ );
385
+ return response.data || [];
386
+ }
387
+ /**
388
+ * List projects in an organization
389
+ */
390
+ async listProjects(organizationId) {
391
+ const response = await this.get(
392
+ "/api/cli/projects",
393
+ { organizationId }
394
+ );
395
+ return response.data || [];
396
+ }
397
+ /**
398
+ * Get a project by ID
399
+ */
400
+ async getProject(projectId) {
401
+ return this.get(`/api/cli/projects/${projectId}`);
402
+ }
403
+ /**
404
+ * List variables in a project
405
+ */
406
+ async listVariables(projectId, environment) {
407
+ const params = { projectId };
408
+ if (environment) {
409
+ params.environment = environment;
410
+ }
411
+ const response = await this.get(
412
+ "/api/cli/variables",
413
+ params
414
+ );
415
+ return response.data || [];
416
+ }
417
+ /**
418
+ * Get a variable by ID (with decrypted value)
419
+ */
420
+ async getVariable(variableId) {
421
+ return this.get(`/api/cli/variables/${variableId}`);
422
+ }
423
+ /**
424
+ * Create a new variable
425
+ */
426
+ async createVariable(data) {
427
+ return this.post("/api/cli/variables", data);
428
+ }
429
+ /**
430
+ * Update a variable
431
+ */
432
+ async updateVariable(variableId, data) {
433
+ return this.patch(`/api/cli/variables/${variableId}`, data);
434
+ }
435
+ /**
436
+ * Delete a variable
437
+ */
438
+ async deleteVariable(variableId) {
439
+ return this.delete(`/api/cli/variables/${variableId}`);
440
+ }
441
+ /**
442
+ * Bulk create/update variables
443
+ */
444
+ async bulkUpsertVariables(data) {
445
+ return this.post("/api/cli/variables/bulk", data);
446
+ }
447
+ // ============================================
448
+ // Authentication methods
449
+ // ============================================
450
+ /**
451
+ * Initiate CLI authentication flow
452
+ */
453
+ async initiateAuth(deviceName) {
454
+ return this.post("/api/cli/auth/initiate", { deviceName });
455
+ }
456
+ /**
457
+ * Poll for authentication status
458
+ */
459
+ async pollAuth(code) {
460
+ return this.get("/api/cli/auth/poll", { code });
461
+ }
462
+ /**
463
+ * Refresh access token
464
+ */
465
+ async refreshToken(refreshToken) {
466
+ return this.post("/api/cli/auth/refresh", { refreshToken });
467
+ }
468
+ /**
469
+ * Revoke access token (logout)
470
+ */
471
+ async revokeToken() {
472
+ return this.post("/api/cli/auth/revoke", {});
473
+ }
474
+ };
475
+ function createAPIClient() {
476
+ return new APIClient();
477
+ }
478
+
479
+ // src/commands/login.ts
480
+ import { hostname } from "os";
481
+ var POLL_INTERVAL_MS = 2e3;
482
+ var MAX_POLL_ATTEMPTS = 150;
483
+ var loginCommand = new Command("login").description("Authenticate with Envpilot").option("--api-url <url>", "API URL (default: http://localhost:3000)").option("--no-browser", "Do not automatically open the browser").action(async (options) => {
484
+ try {
485
+ if (options.apiUrl) {
486
+ setApiUrl(options.apiUrl);
487
+ }
488
+ const api = createAPIClient();
489
+ const deviceName = `CLI - ${hostname()}`;
490
+ info("Starting authentication flow...");
491
+ const spinner = createSpinner("Generating authentication code...");
492
+ spinner.start();
493
+ const initResponse = await api.post("/api/cli/auth?action=initiate", { deviceName });
494
+ spinner.stop();
495
+ console.log();
496
+ console.log(chalk2.bold("Your authentication code:"));
497
+ console.log();
498
+ console.log(chalk2.cyan.bold(` ${initResponse.code}`));
499
+ console.log();
500
+ console.log(`Open this URL to authenticate:`);
501
+ console.log(chalk2.dim(initResponse.url));
502
+ console.log();
503
+ if (options.browser !== false) {
504
+ info("Opening browser...");
505
+ await open(initResponse.url);
506
+ }
507
+ const pollSpinner = createSpinner("Waiting for authentication...");
508
+ pollSpinner.start();
509
+ let authenticated = false;
510
+ let attempts = 0;
511
+ while (!authenticated && attempts < MAX_POLL_ATTEMPTS) {
512
+ await sleep(POLL_INTERVAL_MS);
513
+ const pollResponse = await api.get("/api/cli/auth", { action: "poll", code: initResponse.code });
514
+ if (pollResponse.status === "authenticated") {
515
+ pollSpinner.stop();
516
+ if (pollResponse.accessToken) {
517
+ setAccessToken(pollResponse.accessToken);
518
+ }
519
+ if (pollResponse.refreshToken) {
520
+ setRefreshToken(pollResponse.refreshToken);
521
+ }
522
+ if (pollResponse.user) {
523
+ setUser({
524
+ id: pollResponse.user.id,
525
+ email: pollResponse.user.email,
526
+ name: pollResponse.user.name
527
+ });
528
+ }
529
+ authenticated = true;
530
+ console.log();
531
+ success(`Logged in as ${chalk2.bold(pollResponse.user?.email)}`);
532
+ console.log();
533
+ console.log("Next steps:");
534
+ console.log(
535
+ ` ${chalk2.cyan("envpilot init")} Initialize a project in the current directory`
536
+ );
537
+ console.log(
538
+ ` ${chalk2.cyan("envpilot list")} List your projects and organizations`
539
+ );
540
+ console.log();
541
+ break;
542
+ }
543
+ if (pollResponse.status === "expired" || pollResponse.status === "not_found") {
544
+ pollSpinner.stop();
545
+ error("Authentication code expired. Please try again.");
546
+ process.exit(1);
547
+ }
548
+ attempts++;
549
+ }
550
+ if (!authenticated) {
551
+ pollSpinner.stop();
552
+ error("Authentication timed out. Please try again.");
553
+ process.exit(1);
554
+ }
555
+ } catch (err) {
556
+ error(err instanceof Error ? err.message : "Authentication failed");
557
+ process.exit(1);
558
+ }
559
+ });
560
+ function sleep(ms) {
561
+ return new Promise((resolve) => setTimeout(resolve, ms));
562
+ }
563
+
564
+ // src/commands/init.ts
565
+ import { Command as Command2 } from "commander";
566
+ import chalk4 from "chalk";
567
+ import inquirer from "inquirer";
568
+
569
+ // src/lib/project-config.ts
570
+ import { readFileSync, writeFileSync, existsSync, unlinkSync } from "fs";
571
+ import { execSync } from "child_process";
572
+ import { join } from "path";
573
+
574
+ // src/types/index.ts
575
+ import { z } from "zod";
576
+ var userSchema = z.object({
577
+ id: z.string(),
578
+ email: z.string().email(),
579
+ name: z.string().optional()
580
+ });
581
+ var organizationSchema = z.object({
582
+ _id: z.string(),
583
+ name: z.string(),
584
+ slug: z.string(),
585
+ tier: z.enum(["free", "pro"]),
586
+ role: z.string().optional()
587
+ });
588
+ var projectSchema = z.object({
589
+ _id: z.string(),
590
+ name: z.string(),
591
+ slug: z.string(),
592
+ organizationId: z.string(),
593
+ description: z.string().optional(),
594
+ icon: z.string().optional(),
595
+ color: z.string().optional(),
596
+ userRole: z.string().nullable().optional(),
597
+ projectRole: z.string().nullable().optional()
598
+ });
599
+ var variableSchema = z.object({
600
+ _id: z.string(),
601
+ key: z.string(),
602
+ value: z.string(),
603
+ environment: z.enum(["development", "staging", "production"]),
604
+ projectId: z.string(),
605
+ description: z.string().optional(),
606
+ isSensitive: z.boolean().optional(),
607
+ version: z.number().optional()
608
+ });
609
+ var environmentSchema = z.enum([
610
+ "development",
611
+ "staging",
612
+ "production"
613
+ ]);
614
+ var cliConfigSchema = z.object({
615
+ apiUrl: z.string().url(),
616
+ accessToken: z.string().optional(),
617
+ refreshToken: z.string().optional(),
618
+ activeProjectId: z.string().optional(),
619
+ activeOrganizationId: z.string().optional(),
620
+ user: userSchema.optional(),
621
+ role: z.enum(["admin", "team_lead", "member"]).optional()
622
+ });
623
+ var projectConfigSchema = z.object({
624
+ projectId: z.string(),
625
+ organizationId: z.string(),
626
+ environment: environmentSchema.default("development")
627
+ });
628
+
629
+ // src/lib/project-config.ts
630
+ var CONFIG_FILE_NAME = ".envpilot";
631
+ function getProjectConfigPath(directory = process.cwd()) {
632
+ return join(directory, CONFIG_FILE_NAME);
633
+ }
634
+ function hasProjectConfig(directory = process.cwd()) {
635
+ return existsSync(getProjectConfigPath(directory));
636
+ }
637
+ function readProjectConfig(directory = process.cwd()) {
638
+ const configPath = getProjectConfigPath(directory);
639
+ if (!existsSync(configPath)) {
640
+ return null;
641
+ }
642
+ try {
643
+ const content = readFileSync(configPath, "utf-8");
644
+ const parsed = JSON.parse(content);
645
+ return projectConfigSchema.parse(parsed);
646
+ } catch {
647
+ return null;
648
+ }
649
+ }
650
+ function writeProjectConfig(config2, directory = process.cwd()) {
651
+ const configPath = getProjectConfigPath(directory);
652
+ const content = JSON.stringify(config2, null, 2) + "\n";
653
+ writeFileSync(configPath, content, "utf-8");
654
+ }
655
+ function updateProjectConfig(updates, directory = process.cwd()) {
656
+ const existing = readProjectConfig(directory);
657
+ if (!existing) {
658
+ throw new Error("No project config found. Run `envpilot init` first.");
659
+ }
660
+ const updated = { ...existing, ...updates };
661
+ writeProjectConfig(updated, directory);
662
+ }
663
+ function ensureEnvInGitignore(directory = process.cwd()) {
664
+ const gitignorePath = join(directory, ".gitignore");
665
+ if (!existsSync(gitignorePath)) {
666
+ writeFileSync(gitignorePath, ".env\n.env.local\n", "utf-8");
667
+ return;
668
+ }
669
+ const content = readFileSync(gitignorePath, "utf-8");
670
+ const lines = content.split("\n");
671
+ if (lines.some((line) => line.trim() === ".env")) {
672
+ return;
673
+ }
674
+ const newContent = content.endsWith("\n") ? content + ".env\n" : content + "\n.env\n";
675
+ writeFileSync(gitignorePath, newContent, "utf-8");
676
+ }
677
+ function getTrackedEnvFiles(directory = process.cwd()) {
678
+ try {
679
+ const result = execSync("git ls-files --cached .env .env.* .env.local", {
680
+ cwd: directory,
681
+ encoding: "utf-8",
682
+ stdio: ["pipe", "pipe", "pipe"]
683
+ });
684
+ return result.trim().split("\n").filter((f) => f.length > 0);
685
+ } catch {
686
+ return [];
687
+ }
688
+ }
689
+
690
+ // src/lib/errors.ts
691
+ import chalk3 from "chalk";
692
+ var CLIError = class extends Error {
693
+ constructor(message, code, suggestion) {
694
+ super(message);
695
+ this.code = code;
696
+ this.suggestion = suggestion;
697
+ this.name = "CLIError";
698
+ }
699
+ };
700
+ var ErrorCodes = {
701
+ NOT_AUTHENTICATED: "NOT_AUTHENTICATED",
702
+ NOT_INITIALIZED: "NOT_INITIALIZED",
703
+ PROJECT_NOT_FOUND: "PROJECT_NOT_FOUND",
704
+ ORGANIZATION_NOT_FOUND: "ORGANIZATION_NOT_FOUND",
705
+ VARIABLE_NOT_FOUND: "VARIABLE_NOT_FOUND",
706
+ INVALID_CONFIG: "INVALID_CONFIG",
707
+ NETWORK_ERROR: "NETWORK_ERROR",
708
+ PERMISSION_DENIED: "PERMISSION_DENIED",
709
+ TIER_LIMIT_EXCEEDED: "TIER_LIMIT_EXCEEDED",
710
+ FILE_NOT_FOUND: "FILE_NOT_FOUND",
711
+ INVALID_INPUT: "INVALID_INPUT",
712
+ UNKNOWN_ERROR: "UNKNOWN_ERROR"
713
+ };
714
+ function notAuthenticated() {
715
+ return new CLIError(
716
+ "You are not authenticated.",
717
+ ErrorCodes.NOT_AUTHENTICATED,
718
+ "Run `envpilot login` to authenticate."
719
+ );
720
+ }
721
+ function notInitialized() {
722
+ return new CLIError(
723
+ "This directory is not initialized with Envpilot.",
724
+ ErrorCodes.NOT_INITIALIZED,
725
+ "Run `envpilot init` to initialize."
726
+ );
727
+ }
728
+ function fileNotFound(path) {
729
+ return new CLIError(`File not found: ${path}`, ErrorCodes.FILE_NOT_FOUND);
730
+ }
731
+
732
+ // src/commands/init.ts
733
+ var initCommand = new Command2("init").description("Initialize Envpilot in the current directory").option("-o, --organization <id>", "Organization ID").option("-p, --project <id>", "Project ID").option(
734
+ "-e, --environment <env>",
735
+ "Default environment (development, staging, production)"
736
+ ).option("-f, --force", "Overwrite existing configuration").action(async (options) => {
737
+ try {
738
+ if (!isAuthenticated()) {
739
+ throw notAuthenticated();
740
+ }
741
+ if (hasProjectConfig() && !options.force) {
742
+ warning("This directory is already initialized with Envpilot.");
743
+ const { proceed } = await inquirer.prompt([
744
+ {
745
+ type: "confirm",
746
+ name: "proceed",
747
+ message: "Do you want to reinitialize?",
748
+ default: false
749
+ }
750
+ ]);
751
+ if (!proceed) {
752
+ info("Initialization cancelled.");
753
+ return;
754
+ }
755
+ }
756
+ const api = createAPIClient();
757
+ const organizations = await withSpinner(
758
+ "Fetching organizations...",
759
+ async () => {
760
+ const response = await api.get("/api/cli/organizations");
761
+ return response.data || [];
762
+ }
763
+ );
764
+ if (organizations.length === 0) {
765
+ error("No organizations found. Please create an organization first.");
766
+ process.exit(1);
767
+ }
768
+ let selectedOrg;
769
+ if (options.organization) {
770
+ const org = organizations.find(
771
+ (o) => o._id === options.organization || o.slug === options.organization
772
+ );
773
+ if (!org) {
774
+ error(`Organization not found: ${options.organization}`);
775
+ process.exit(1);
776
+ }
777
+ selectedOrg = org;
778
+ } else if (organizations.length === 1) {
779
+ selectedOrg = organizations[0];
780
+ info(`Using organization: ${chalk4.bold(selectedOrg.name)}`);
781
+ } else {
782
+ const { orgId } = await inquirer.prompt([
783
+ {
784
+ type: "list",
785
+ name: "orgId",
786
+ message: "Select an organization:",
787
+ choices: organizations.map((org) => ({
788
+ name: `${org.name} ${org.tier === "pro" ? chalk4.green("(Pro)") : chalk4.dim("(Free)")}`,
789
+ value: org._id
790
+ }))
791
+ }
792
+ ]);
793
+ selectedOrg = organizations.find((o) => o._id === orgId);
794
+ }
795
+ const projects = await withSpinner("Fetching projects...", async () => {
796
+ const response = await api.get(
797
+ "/api/cli/projects",
798
+ { organizationId: selectedOrg._id }
799
+ );
800
+ return response.data || [];
801
+ });
802
+ if (projects.length === 0) {
803
+ error("No projects found. Please create a project first.");
804
+ process.exit(1);
805
+ }
806
+ let selectedProject;
807
+ if (options.project) {
808
+ const project = projects.find(
809
+ (p) => p._id === options.project || p.slug === options.project
810
+ );
811
+ if (!project) {
812
+ error(`Project not found: ${options.project}`);
813
+ process.exit(1);
814
+ }
815
+ selectedProject = project;
816
+ } else if (projects.length === 1) {
817
+ selectedProject = projects[0];
818
+ info(`Using project: ${chalk4.bold(selectedProject.name)}`);
819
+ } else {
820
+ const { projectId } = await inquirer.prompt([
821
+ {
822
+ type: "list",
823
+ name: "projectId",
824
+ message: "Select a project:",
825
+ choices: projects.map((project) => ({
826
+ name: `${project.icon || "\u{1F4E6}"} ${project.name}`,
827
+ value: project._id
828
+ }))
829
+ }
830
+ ]);
831
+ selectedProject = projects.find((p) => p._id === projectId);
832
+ }
833
+ let selectedEnvironment = "development";
834
+ if (options.environment) {
835
+ if (!["development", "staging", "production"].includes(
836
+ options.environment
837
+ )) {
838
+ error(
839
+ "Invalid environment. Must be: development, staging, or production"
840
+ );
841
+ process.exit(1);
842
+ }
843
+ selectedEnvironment = options.environment;
844
+ } else {
845
+ const { environment } = await inquirer.prompt([
846
+ {
847
+ type: "list",
848
+ name: "environment",
849
+ message: "Select default environment:",
850
+ choices: [
851
+ { name: "Development", value: "development" },
852
+ { name: "Staging", value: "staging" },
853
+ { name: "Production", value: "production" }
854
+ ],
855
+ default: "development"
856
+ }
857
+ ]);
858
+ selectedEnvironment = environment;
859
+ }
860
+ writeProjectConfig({
861
+ projectId: selectedProject._id,
862
+ organizationId: selectedOrg._id,
863
+ environment: selectedEnvironment
864
+ });
865
+ setActiveOrganizationId(selectedOrg._id);
866
+ setActiveProjectId(selectedProject._id);
867
+ if (selectedOrg.role) {
868
+ setRole(selectedOrg.role);
869
+ }
870
+ ensureEnvInGitignore();
871
+ const trackedFiles = getTrackedEnvFiles();
872
+ if (trackedFiles.length > 0) {
873
+ console.log();
874
+ warning("Security risk: .env files are tracked by git!");
875
+ for (const file of trackedFiles) {
876
+ console.log(chalk4.red(` tracked: ${file}`));
877
+ }
878
+ console.log();
879
+ console.log(
880
+ chalk4.yellow(
881
+ " Run the following to untrack them (without deleting the files):"
882
+ )
883
+ );
884
+ for (const file of trackedFiles) {
885
+ console.log(chalk4.cyan(` git rm --cached ${file}`));
886
+ }
887
+ }
888
+ console.log();
889
+ success("Project initialized!");
890
+ console.log();
891
+ console.log(chalk4.dim("Configuration saved to .envpilot"));
892
+ if (selectedOrg.role) {
893
+ console.log(chalk4.dim(` Org role: ${formatRole(selectedOrg.role)}`));
894
+ roleNotice(selectedOrg.role);
895
+ }
896
+ if (selectedProject.projectRole) {
897
+ console.log(
898
+ chalk4.dim(
899
+ ` Project role: ${formatProjectRole(selectedProject.projectRole)}`
900
+ )
901
+ );
902
+ projectRoleNotice(selectedProject.projectRole);
903
+ }
904
+ console.log();
905
+ console.log("Next steps:");
906
+ console.log(
907
+ ` ${chalk4.cyan("envpilot pull")} Download environment variables`
908
+ );
909
+ console.log(
910
+ ` ${chalk4.cyan("envpilot push")} Upload local .env to cloud`
911
+ );
912
+ console.log();
913
+ } catch (err) {
914
+ error(err instanceof Error ? err.message : "Initialization failed");
915
+ process.exit(1);
916
+ }
917
+ });
918
+
919
+ // src/commands/pull.ts
920
+ import { Command as Command3 } from "commander";
921
+ import chalk5 from "chalk";
922
+ import inquirer2 from "inquirer";
923
+
924
+ // src/lib/env-file.ts
925
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
926
+ import { join as join2 } from "path";
927
+ function parseEnvFile(content) {
928
+ const result = {};
929
+ const lines = content.split("\n");
930
+ for (const line of lines) {
931
+ const trimmed = line.trim();
932
+ if (!trimmed || trimmed.startsWith("#")) {
933
+ continue;
934
+ }
935
+ const equalsIndex = line.indexOf("=");
936
+ if (equalsIndex === -1) {
937
+ continue;
938
+ }
939
+ const key = line.substring(0, equalsIndex).trim();
940
+ let value = line.substring(equalsIndex + 1);
941
+ value = parseValue(value);
942
+ if (isValidEnvKey(key)) {
943
+ result[key] = value;
944
+ }
945
+ }
946
+ return result;
947
+ }
948
+ function parseValue(value) {
949
+ value = value.trim();
950
+ if (value.startsWith('"') && value.endsWith('"')) {
951
+ value = value.slice(1, -1);
952
+ value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
953
+ } else if (value.startsWith("'") && value.endsWith("'")) {
954
+ value = value.slice(1, -1);
955
+ } else {
956
+ const commentIndex = value.indexOf(" #");
957
+ if (commentIndex !== -1) {
958
+ value = value.substring(0, commentIndex).trim();
959
+ }
960
+ }
961
+ return value;
962
+ }
963
+ function isValidEnvKey(key) {
964
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
965
+ }
966
+ function stringifyEnv(vars, options) {
967
+ let keys = Object.keys(vars);
968
+ if (options?.sort) {
969
+ keys = keys.sort();
970
+ }
971
+ const lines = [];
972
+ for (const key of keys) {
973
+ const value = vars[key];
974
+ if (options?.comments?.[key]) {
975
+ lines.push(`# ${options.comments[key]}`);
976
+ }
977
+ const formattedValue = formatValue(value);
978
+ lines.push(`${key}=${formattedValue}`);
979
+ }
980
+ return lines.join("\n") + "\n";
981
+ }
982
+ function formatValue(value) {
983
+ const needsQuotes = value.includes("\n") || value.includes("\r") || value.includes('"') || value.includes("'") || value.includes(" ") || value.includes("#") || value.startsWith(" ") || value.endsWith(" ");
984
+ if (!needsQuotes) {
985
+ return value;
986
+ }
987
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
988
+ return `"${escaped}"`;
989
+ }
990
+ function diffEnvVars(local, remote) {
991
+ const added = {};
992
+ const removed = {};
993
+ const changed = {};
994
+ const unchanged = [];
995
+ for (const [key, value] of Object.entries(local)) {
996
+ if (!(key in remote)) {
997
+ added[key] = value;
998
+ } else if (remote[key] !== value) {
999
+ changed[key] = { local: value, remote: remote[key] };
1000
+ } else {
1001
+ unchanged.push(key);
1002
+ }
1003
+ }
1004
+ for (const [key, value] of Object.entries(remote)) {
1005
+ if (!(key in local)) {
1006
+ removed[key] = value;
1007
+ }
1008
+ }
1009
+ return { added, removed, changed, unchanged };
1010
+ }
1011
+ function readEnvFile(filePath) {
1012
+ if (!existsSync2(filePath)) {
1013
+ return null;
1014
+ }
1015
+ const content = readFileSync2(filePath, "utf-8");
1016
+ return parseEnvFile(content);
1017
+ }
1018
+ function writeEnvFile(filePath, vars, options) {
1019
+ const content = stringifyEnv(vars, options);
1020
+ writeFileSync2(filePath, content, "utf-8");
1021
+ }
1022
+ function getEnvPathForEnvironment(environment, directory = process.cwd()) {
1023
+ if (environment === "development") {
1024
+ return join2(directory, ".env");
1025
+ }
1026
+ return join2(directory, `.env.${environment}`);
1027
+ }
1028
+
1029
+ // src/commands/pull.ts
1030
+ var pullCommand = new Command3("pull").description("Download environment variables to local .env file").option(
1031
+ "-e, --env <environment>",
1032
+ "Environment (development, staging, production)"
1033
+ ).option("-f, --file <path>", "Output file path (default: .env)").option("--force", "Overwrite without confirmation").option("--format <format>", "Output format: env, json", "env").option("--dry-run", "Show what would be downloaded without writing").action(async (options) => {
1034
+ try {
1035
+ if (!isAuthenticated()) {
1036
+ throw notAuthenticated();
1037
+ }
1038
+ const projectConfig = readProjectConfig();
1039
+ if (!projectConfig) {
1040
+ throw notInitialized();
1041
+ }
1042
+ const trackedFiles = getTrackedEnvFiles();
1043
+ if (trackedFiles.length > 0) {
1044
+ error("Security risk: .env files are tracked by git!");
1045
+ console.log();
1046
+ for (const file of trackedFiles) {
1047
+ console.log(chalk5.red(` tracked: ${file}`));
1048
+ }
1049
+ console.log();
1050
+ console.log(
1051
+ chalk5.yellow(
1052
+ " Run the following to untrack them (without deleting the files):"
1053
+ )
1054
+ );
1055
+ for (const file of trackedFiles) {
1056
+ console.log(chalk5.cyan(` git rm --cached ${file}`));
1057
+ }
1058
+ console.log();
1059
+ process.exit(1);
1060
+ }
1061
+ const environment = options.env || projectConfig.environment || "development";
1062
+ const outputPath = options.file || getEnvPathForEnvironment(environment);
1063
+ const api = createAPIClient();
1064
+ let metaProjectRole;
1065
+ const variables = await withSpinner(
1066
+ `Fetching ${chalk5.bold(environment)} variables...`,
1067
+ async () => {
1068
+ const response = await api.get("/api/cli/variables", {
1069
+ projectId: projectConfig.projectId,
1070
+ environment
1071
+ });
1072
+ metaProjectRole = response.meta?.projectRole;
1073
+ return response.data || [];
1074
+ }
1075
+ );
1076
+ if (variables.length === 0) {
1077
+ warning(`No variables found for ${environment} environment.`);
1078
+ return;
1079
+ }
1080
+ const remoteVars = {};
1081
+ for (const variable of variables) {
1082
+ remoteVars[variable.key] = variable.value;
1083
+ }
1084
+ const localVars = readEnvFile(outputPath) || {};
1085
+ const diffResult = diffEnvVars(remoteVars, localVars);
1086
+ const hasChanges = Object.keys(diffResult.added).length > 0 || Object.keys(diffResult.removed).length > 0 || Object.keys(diffResult.changed).length > 0;
1087
+ if (!hasChanges) {
1088
+ success("Local file is up to date.");
1089
+ return;
1090
+ }
1091
+ console.log();
1092
+ console.log(chalk5.bold("Changes:"));
1093
+ console.log();
1094
+ diff(diffResult.added, diffResult.removed, diffResult.changed);
1095
+ console.log();
1096
+ if (options.dryRun) {
1097
+ info("Dry run - no changes written.");
1098
+ return;
1099
+ }
1100
+ if (!options.force && Object.keys(localVars).length > 0) {
1101
+ const { proceed } = await inquirer2.prompt([
1102
+ {
1103
+ type: "confirm",
1104
+ name: "proceed",
1105
+ message: `Overwrite ${outputPath}?`,
1106
+ default: true
1107
+ }
1108
+ ]);
1109
+ if (!proceed) {
1110
+ info("Pull cancelled.");
1111
+ return;
1112
+ }
1113
+ }
1114
+ if (options.format === "json") {
1115
+ const fs = await import("fs");
1116
+ fs.writeFileSync(
1117
+ outputPath,
1118
+ JSON.stringify(remoteVars, null, 2) + "\n"
1119
+ );
1120
+ } else {
1121
+ const comments = {};
1122
+ for (const variable of variables) {
1123
+ if (variable.description) {
1124
+ comments[variable.key] = variable.description;
1125
+ }
1126
+ }
1127
+ writeEnvFile(outputPath, remoteVars, { sort: true, comments });
1128
+ }
1129
+ success(
1130
+ `Downloaded ${variables.length} variables to ${chalk5.bold(outputPath)}`
1131
+ );
1132
+ if (metaProjectRole === "viewer") {
1133
+ info(
1134
+ "You have Viewer access to this project. You may only see variables you have been explicitly granted access to."
1135
+ );
1136
+ }
1137
+ console.log();
1138
+ console.log(
1139
+ chalk5.dim(` Added: ${Object.keys(diffResult.added).length}`)
1140
+ );
1141
+ console.log(
1142
+ chalk5.dim(` Changed: ${Object.keys(diffResult.changed).length}`)
1143
+ );
1144
+ console.log(
1145
+ chalk5.dim(` Removed: ${Object.keys(diffResult.removed).length}`)
1146
+ );
1147
+ } catch (err) {
1148
+ error(err instanceof Error ? err.message : "Pull failed");
1149
+ process.exit(1);
1150
+ }
1151
+ });
1152
+
1153
+ // src/commands/push.ts
1154
+ import { Command as Command4 } from "commander";
1155
+ import chalk6 from "chalk";
1156
+ import inquirer3 from "inquirer";
1157
+
1158
+ // src/lib/validators.ts
1159
+ import { z as z2 } from "zod";
1160
+ var envKeySchema = z2.string().min(1, "Key cannot be empty").max(256, "Key cannot exceed 256 characters").regex(
1161
+ /^[A-Za-z_][A-Za-z0-9_]*$/,
1162
+ "Key must start with a letter or underscore, followed by letters, numbers, or underscores"
1163
+ );
1164
+ var envValueSchema = z2.string().max(65536, "Value cannot exceed 64KB");
1165
+ var environmentSchema2 = z2.enum([
1166
+ "development",
1167
+ "staging",
1168
+ "production"
1169
+ ]);
1170
+ var projectSlugSchema = z2.string().min(1, "Slug cannot be empty").max(128, "Slug cannot exceed 128 characters").regex(
1171
+ /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/,
1172
+ "Slug must be lowercase alphanumeric with hyphens, cannot start or end with hyphen"
1173
+ );
1174
+ var urlSchema = z2.string().url("Must be a valid URL");
1175
+ var tokenSchema = z2.string().min(1, "Token cannot be empty").regex(/^env_[A-Za-z0-9]{48}$/, "Invalid token format");
1176
+ var filePathSchema = z2.string().min(1, "File path cannot be empty");
1177
+ function validateEnvVars(vars) {
1178
+ const valid = {};
1179
+ const invalid = [];
1180
+ for (const [key, value] of Object.entries(vars)) {
1181
+ const keyResult = envKeySchema.safeParse(key);
1182
+ const valueResult = envValueSchema.safeParse(value);
1183
+ if (!keyResult.success) {
1184
+ invalid.push({ key, error: keyResult.error.issues[0].message });
1185
+ continue;
1186
+ }
1187
+ if (!valueResult.success) {
1188
+ invalid.push({ key, error: valueResult.error.issues[0].message });
1189
+ continue;
1190
+ }
1191
+ valid[key] = value;
1192
+ }
1193
+ return { valid, invalid };
1194
+ }
1195
+
1196
+ // src/commands/push.ts
1197
+ var pushCommand = new Command4("push").description("Upload local .env file to cloud").option(
1198
+ "-e, --env <environment>",
1199
+ "Target environment (development, staging, production)"
1200
+ ).option("-f, --file <path>", "Input file path (default: .env)").option("--merge", "Merge with existing variables (default)").option("--replace", "Replace all existing variables").option("--dry-run", "Show what would be uploaded without making changes").option("--force", "Skip confirmation").action(async (options) => {
1201
+ try {
1202
+ if (!isAuthenticated()) {
1203
+ throw notAuthenticated();
1204
+ }
1205
+ const projectConfig = readProjectConfig();
1206
+ if (!projectConfig) {
1207
+ throw notInitialized();
1208
+ }
1209
+ const trackedFiles = getTrackedEnvFiles();
1210
+ if (trackedFiles.length > 0) {
1211
+ error("Security risk: .env files are tracked by git!");
1212
+ console.log();
1213
+ for (const file of trackedFiles) {
1214
+ console.log(chalk6.red(` tracked: ${file}`));
1215
+ }
1216
+ console.log();
1217
+ console.log(
1218
+ chalk6.yellow(
1219
+ " Run the following to untrack them (without deleting the files):"
1220
+ )
1221
+ );
1222
+ for (const file of trackedFiles) {
1223
+ console.log(chalk6.cyan(` git rm --cached ${file}`));
1224
+ }
1225
+ console.log();
1226
+ process.exit(1);
1227
+ }
1228
+ const api = createAPIClient();
1229
+ const projects = await api.get("/api/cli/projects", { organizationId: projectConfig.organizationId });
1230
+ const currentProject = projects.data?.find(
1231
+ (p) => p._id === projectConfig.projectId
1232
+ );
1233
+ const projectRole = currentProject?.projectRole;
1234
+ if (projectRole === "viewer") {
1235
+ error(
1236
+ "Unfortunately, as a member, you cannot push in any environment. Please access or request access to your team lead."
1237
+ );
1238
+ process.exit(1);
1239
+ }
1240
+ const role = getRole();
1241
+ if (role === "member") {
1242
+ warning(
1243
+ "You have Member access. Push will create pending requests that require Admin or Team Lead approval."
1244
+ );
1245
+ console.log();
1246
+ if (!options.force) {
1247
+ const { proceed } = await inquirer3.prompt([
1248
+ {
1249
+ type: "confirm",
1250
+ name: "proceed",
1251
+ message: "Continue with creating approval requests?",
1252
+ default: true
1253
+ }
1254
+ ]);
1255
+ if (!proceed) {
1256
+ info("Push cancelled.");
1257
+ return;
1258
+ }
1259
+ }
1260
+ }
1261
+ const environment = options.env || projectConfig.environment || "development";
1262
+ const inputPath = options.file || getEnvPathForEnvironment(environment);
1263
+ const mode = options.replace ? "replace" : "merge";
1264
+ const localVars = readEnvFile(inputPath);
1265
+ if (!localVars) {
1266
+ throw fileNotFound(inputPath);
1267
+ }
1268
+ if (Object.keys(localVars).length === 0) {
1269
+ warning(`No variables found in ${inputPath}`);
1270
+ return;
1271
+ }
1272
+ const { valid, invalid } = validateEnvVars(localVars);
1273
+ if (invalid.length > 0) {
1274
+ warning("Some variables have invalid keys and will be skipped:");
1275
+ for (const { key, error: err } of invalid) {
1276
+ console.log(chalk6.red(` ${key}: ${err}`));
1277
+ }
1278
+ console.log();
1279
+ }
1280
+ if (Object.keys(valid).length === 0) {
1281
+ error("No valid variables to push.");
1282
+ return;
1283
+ }
1284
+ const remoteVariables = await withSpinner(
1285
+ "Fetching current variables...",
1286
+ async () => {
1287
+ const response = await api.get("/api/cli/variables", {
1288
+ projectId: projectConfig.projectId,
1289
+ environment
1290
+ });
1291
+ return response.data || [];
1292
+ }
1293
+ );
1294
+ const remoteVars = {};
1295
+ for (const variable of remoteVariables) {
1296
+ remoteVars[variable.key] = variable.value;
1297
+ }
1298
+ const diffResult = diffEnvVars(valid, remoteVars);
1299
+ const hasChanges = Object.keys(diffResult.added).length > 0 || Object.keys(diffResult.changed).length > 0 || mode === "replace" && Object.keys(diffResult.removed).length > 0;
1300
+ if (!hasChanges) {
1301
+ success("Remote is up to date.");
1302
+ return;
1303
+ }
1304
+ console.log();
1305
+ console.log(chalk6.bold("Changes to push:"));
1306
+ console.log();
1307
+ const removedToShow = mode === "replace" ? diffResult.removed : {};
1308
+ diff(diffResult.added, removedToShow, diffResult.changed);
1309
+ if (mode === "merge" && Object.keys(diffResult.removed).length > 0) {
1310
+ console.log();
1311
+ console.log(
1312
+ chalk6.dim(
1313
+ `Note: ${Object.keys(diffResult.removed).length} remote variables not in local file will be preserved (use --replace to remove them)`
1314
+ )
1315
+ );
1316
+ }
1317
+ console.log();
1318
+ if (options.dryRun) {
1319
+ info("Dry run - no changes made.");
1320
+ console.log();
1321
+ console.log("Summary:");
1322
+ console.log(` Would add: ${Object.keys(diffResult.added).length}`);
1323
+ console.log(
1324
+ ` Would update: ${Object.keys(diffResult.changed).length}`
1325
+ );
1326
+ if (mode === "replace") {
1327
+ console.log(
1328
+ ` Would delete: ${Object.keys(diffResult.removed).length}`
1329
+ );
1330
+ }
1331
+ return;
1332
+ }
1333
+ if (!options.force) {
1334
+ const confirmMessage = mode === "replace" ? `Push ${Object.keys(valid).length} variables and delete ${Object.keys(diffResult.removed).length} remote-only variables?` : `Push ${Object.keys(valid).length} variables to ${environment}?`;
1335
+ const { proceed } = await inquirer3.prompt([
1336
+ {
1337
+ type: "confirm",
1338
+ name: "proceed",
1339
+ message: confirmMessage,
1340
+ default: true
1341
+ }
1342
+ ]);
1343
+ if (!proceed) {
1344
+ info("Push cancelled.");
1345
+ return;
1346
+ }
1347
+ }
1348
+ const result = await withSpinner(
1349
+ `Pushing variables to ${chalk6.bold(environment)}...`,
1350
+ async () => {
1351
+ const response = await api.post("/api/cli/variables/bulk", {
1352
+ projectId: projectConfig.projectId,
1353
+ environment,
1354
+ variables: Object.entries(valid).map(([key, value]) => ({
1355
+ key,
1356
+ value
1357
+ })),
1358
+ mode
1359
+ });
1360
+ return response.data;
1361
+ }
1362
+ );
1363
+ if (result?.requested && result.requested > 0) {
1364
+ success(
1365
+ `Created ${result.requested} pending request(s) for ${chalk6.bold(environment)}`
1366
+ );
1367
+ console.log();
1368
+ console.log(
1369
+ chalk6.yellow(
1370
+ " These changes require approval from an Admin or Team Lead."
1371
+ )
1372
+ );
1373
+ console.log();
1374
+ console.log(chalk6.dim(` Requested: ${result.requested}`));
1375
+ if (result?.skipped && result.skipped > 0) {
1376
+ console.log(chalk6.dim(` Skipped: ${result.skipped}`));
1377
+ }
1378
+ } else {
1379
+ success(
1380
+ `Pushed ${result?.total || Object.keys(valid).length} variables to ${chalk6.bold(environment)}`
1381
+ );
1382
+ console.log();
1383
+ console.log(chalk6.dim(` Created: ${result?.created || 0}`));
1384
+ console.log(chalk6.dim(` Updated: ${result?.updated || 0}`));
1385
+ if (mode === "replace") {
1386
+ console.log(chalk6.dim(` Deleted: ${result?.deleted || 0}`));
1387
+ }
1388
+ }
1389
+ } catch (err) {
1390
+ error(err instanceof Error ? err.message : "Push failed");
1391
+ process.exit(1);
1392
+ }
1393
+ });
1394
+
1395
+ // src/commands/switch.ts
1396
+ import { Command as Command5 } from "commander";
1397
+ import chalk7 from "chalk";
1398
+ import inquirer4 from "inquirer";
1399
+ var switchCommand = new Command5("switch").description("Switch project or environment").argument("[target]", "project slug or environment name").option("-o, --organization <id>", "Switch organization").option("-p, --project <id>", "Switch project").option(
1400
+ "-e, --env <environment>",
1401
+ "Switch environment (development, staging, production)"
1402
+ ).action(async (target, options) => {
1403
+ try {
1404
+ if (!isAuthenticated()) {
1405
+ throw notAuthenticated();
1406
+ }
1407
+ const api = createAPIClient();
1408
+ const projectConfig = readProjectConfig();
1409
+ if (options.env || target && ["development", "staging", "production"].includes(target)) {
1410
+ const environment = options.env || target;
1411
+ if (!projectConfig) {
1412
+ error("No project initialized. Run `envpilot init` first.");
1413
+ process.exit(1);
1414
+ }
1415
+ updateProjectConfig({ environment });
1416
+ success(`Switched to ${chalk7.bold(environment)} environment`);
1417
+ return;
1418
+ }
1419
+ if (options.organization) {
1420
+ const organizations = await withSpinner(
1421
+ "Fetching organizations...",
1422
+ async () => {
1423
+ const response = await api.get("/api/cli/organizations");
1424
+ return response.data || [];
1425
+ }
1426
+ );
1427
+ const org = organizations.find(
1428
+ (o) => o._id === options.organization || o.slug === options.organization
1429
+ );
1430
+ if (!org) {
1431
+ error(`Organization not found: ${options.organization}`);
1432
+ process.exit(1);
1433
+ }
1434
+ setActiveOrganizationId(org._id);
1435
+ if (org.role) {
1436
+ setRole(org.role);
1437
+ }
1438
+ if (projectConfig) {
1439
+ updateProjectConfig({ organizationId: org._id });
1440
+ }
1441
+ success(`Switched to organization: ${chalk7.bold(org.name)}`);
1442
+ if (org.role) {
1443
+ console.log(chalk7.dim(` Role: ${formatRole(org.role)}`));
1444
+ roleNotice(org.role);
1445
+ }
1446
+ return;
1447
+ }
1448
+ if (options.project || target) {
1449
+ const projectIdentifier = options.project || target;
1450
+ let organizationId = projectConfig?.organizationId;
1451
+ if (!organizationId) {
1452
+ const organizations = await withSpinner(
1453
+ "Fetching organizations...",
1454
+ async () => {
1455
+ const response = await api.get("/api/cli/organizations");
1456
+ return response.data || [];
1457
+ }
1458
+ );
1459
+ if (organizations.length === 0) {
1460
+ error("No organizations found.");
1461
+ process.exit(1);
1462
+ }
1463
+ if (organizations.length === 1) {
1464
+ organizationId = organizations[0]._id;
1465
+ if (organizations[0].role) {
1466
+ setRole(organizations[0].role);
1467
+ }
1468
+ } else {
1469
+ const { orgId } = await inquirer4.prompt([
1470
+ {
1471
+ type: "list",
1472
+ name: "orgId",
1473
+ message: "Select an organization:",
1474
+ choices: organizations.map((org) => ({
1475
+ name: `${org.name} ${org.tier === "pro" ? chalk7.green("(Pro)") : chalk7.dim("(Free)")}`,
1476
+ value: org._id
1477
+ }))
1478
+ }
1479
+ ]);
1480
+ organizationId = orgId;
1481
+ const selectedOrg = organizations.find((o) => o._id === orgId);
1482
+ if (selectedOrg?.role) {
1483
+ setRole(selectedOrg.role);
1484
+ }
1485
+ }
1486
+ }
1487
+ const projects = await withSpinner("Fetching projects...", async () => {
1488
+ const response = await api.get(
1489
+ "/api/cli/projects",
1490
+ { organizationId }
1491
+ );
1492
+ return response.data || [];
1493
+ });
1494
+ const project = projects.find(
1495
+ (p) => p._id === projectIdentifier || p.slug === projectIdentifier
1496
+ );
1497
+ if (!project) {
1498
+ error(`Project not found: ${projectIdentifier}`);
1499
+ console.log();
1500
+ console.log("Available projects:");
1501
+ for (const p of projects) {
1502
+ console.log(` ${p.icon || "\u{1F4E6}"} ${p.name} (${p.slug})`);
1503
+ }
1504
+ process.exit(1);
1505
+ }
1506
+ setActiveProjectId(project._id);
1507
+ setActiveOrganizationId(organizationId);
1508
+ const environment = projectConfig?.environment || "development";
1509
+ writeProjectConfig({
1510
+ projectId: project._id,
1511
+ organizationId,
1512
+ environment
1513
+ });
1514
+ success(`Switched to project: ${chalk7.bold(project.name)}`);
1515
+ if (project.projectRole) {
1516
+ console.log(
1517
+ chalk7.dim(
1518
+ ` Project role: ${formatProjectRole(project.projectRole)}`
1519
+ )
1520
+ );
1521
+ projectRoleNotice(project.projectRole);
1522
+ }
1523
+ return;
1524
+ }
1525
+ if (!target && !options.project && !options.organization && !options.env) {
1526
+ const { switchType } = await inquirer4.prompt([
1527
+ {
1528
+ type: "list",
1529
+ name: "switchType",
1530
+ message: "What would you like to switch?",
1531
+ choices: [
1532
+ { name: "Environment", value: "environment" },
1533
+ { name: "Project", value: "project" },
1534
+ { name: "Organization", value: "organization" }
1535
+ ]
1536
+ }
1537
+ ]);
1538
+ if (switchType === "environment") {
1539
+ if (!projectConfig) {
1540
+ error("No project initialized. Run `envpilot init` first.");
1541
+ process.exit(1);
1542
+ }
1543
+ const { environment } = await inquirer4.prompt([
1544
+ {
1545
+ type: "list",
1546
+ name: "environment",
1547
+ message: "Select environment:",
1548
+ choices: [
1549
+ { name: "Development", value: "development" },
1550
+ { name: "Staging", value: "staging" },
1551
+ { name: "Production", value: "production" }
1552
+ ],
1553
+ default: projectConfig.environment
1554
+ }
1555
+ ]);
1556
+ updateProjectConfig({ environment });
1557
+ success(`Switched to ${chalk7.bold(environment)} environment`);
1558
+ return;
1559
+ }
1560
+ if (switchType === "organization" || switchType === "project") {
1561
+ const organizations = await withSpinner(
1562
+ "Fetching organizations...",
1563
+ async () => {
1564
+ const response = await api.get("/api/cli/organizations");
1565
+ return response.data || [];
1566
+ }
1567
+ );
1568
+ if (organizations.length === 0) {
1569
+ error("No organizations found.");
1570
+ process.exit(1);
1571
+ }
1572
+ const { orgId } = await inquirer4.prompt([
1573
+ {
1574
+ type: "list",
1575
+ name: "orgId",
1576
+ message: "Select an organization:",
1577
+ choices: organizations.map((org) => ({
1578
+ name: `${org.name} ${org.tier === "pro" ? chalk7.green("(Pro)") : chalk7.dim("(Free)")}`,
1579
+ value: org._id
1580
+ })),
1581
+ default: projectConfig?.organizationId
1582
+ }
1583
+ ]);
1584
+ if (switchType === "organization") {
1585
+ setActiveOrganizationId(orgId);
1586
+ const org = organizations.find((o) => o._id === orgId);
1587
+ if (org.role) {
1588
+ setRole(org.role);
1589
+ }
1590
+ success(`Switched to organization: ${chalk7.bold(org.name)}`);
1591
+ if (org.role) {
1592
+ console.log(chalk7.dim(` Role: ${formatRole(org.role)}`));
1593
+ roleNotice(org.role);
1594
+ }
1595
+ return;
1596
+ }
1597
+ const selectedOrg = organizations.find((o) => o._id === orgId);
1598
+ if (selectedOrg?.role) {
1599
+ setRole(selectedOrg.role);
1600
+ }
1601
+ const projects = await withSpinner(
1602
+ "Fetching projects...",
1603
+ async () => {
1604
+ const response = await api.get("/api/cli/projects", { organizationId: orgId });
1605
+ return response.data || [];
1606
+ }
1607
+ );
1608
+ if (projects.length === 0) {
1609
+ error("No projects found in this organization.");
1610
+ process.exit(1);
1611
+ }
1612
+ const { projectId } = await inquirer4.prompt([
1613
+ {
1614
+ type: "list",
1615
+ name: "projectId",
1616
+ message: "Select a project:",
1617
+ choices: projects.map((project2) => ({
1618
+ name: `${project2.icon || "\u{1F4E6}"} ${project2.name}`,
1619
+ value: project2._id
1620
+ })),
1621
+ default: projectConfig?.projectId
1622
+ }
1623
+ ]);
1624
+ const project = projects.find((p) => p._id === projectId);
1625
+ const environment = projectConfig?.environment || "development";
1626
+ setActiveProjectId(projectId);
1627
+ setActiveOrganizationId(orgId);
1628
+ writeProjectConfig({
1629
+ projectId,
1630
+ organizationId: orgId,
1631
+ environment
1632
+ });
1633
+ success(`Switched to project: ${chalk7.bold(project.name)}`);
1634
+ if (project.projectRole) {
1635
+ console.log(
1636
+ chalk7.dim(
1637
+ ` Project role: ${formatProjectRole(project.projectRole)}`
1638
+ )
1639
+ );
1640
+ projectRoleNotice(project.projectRole);
1641
+ }
1642
+ }
1643
+ }
1644
+ } catch (err) {
1645
+ error(err instanceof Error ? err.message : "Switch failed");
1646
+ process.exit(1);
1647
+ }
1648
+ });
1649
+
1650
+ // src/commands/list.ts
1651
+ import { Command as Command6 } from "commander";
1652
+ import chalk8 from "chalk";
1653
+ var listCommand = new Command6("list").description("List resources").argument(
1654
+ "[resource]",
1655
+ "Resource type: projects, organizations, variables",
1656
+ "projects"
1657
+ ).option("-o, --organization <id>", "Organization ID (for projects/variables)").option("-p, --project <id>", "Project ID (for variables)").option("-e, --env <environment>", "Environment filter (for variables)").option("--show-values", "Show actual variable values (masked by default)").option("--json", "Output as JSON").action(async (resource, options) => {
1658
+ try {
1659
+ if (!isAuthenticated()) {
1660
+ throw notAuthenticated();
1661
+ }
1662
+ const api = createAPIClient();
1663
+ const projectConfig = readProjectConfig();
1664
+ switch (resource) {
1665
+ case "orgs":
1666
+ case "organizations":
1667
+ await listOrganizations(api, options);
1668
+ break;
1669
+ case "projects":
1670
+ await listProjects(api, projectConfig, options);
1671
+ break;
1672
+ case "vars":
1673
+ case "variables":
1674
+ await listVariables(api, projectConfig, options);
1675
+ break;
1676
+ default:
1677
+ error(`Unknown resource: ${resource}`);
1678
+ console.log();
1679
+ console.log("Available resources:");
1680
+ console.log(" organizations (orgs) List your organizations");
1681
+ console.log(
1682
+ " projects List projects in an organization"
1683
+ );
1684
+ console.log(" variables (vars) List variables in a project");
1685
+ process.exit(1);
1686
+ }
1687
+ } catch (err) {
1688
+ error(err instanceof Error ? err.message : "List failed");
1689
+ process.exit(1);
1690
+ }
1691
+ });
1692
+ async function listOrganizations(api, options) {
1693
+ const organizations = await withSpinner(
1694
+ "Fetching organizations...",
1695
+ async () => {
1696
+ const response = await api.get("/api/cli/organizations");
1697
+ return response.data || [];
1698
+ }
1699
+ );
1700
+ if (organizations.length === 0) {
1701
+ info("No organizations found.");
1702
+ return;
1703
+ }
1704
+ if (options.json) {
1705
+ console.log(JSON.stringify(organizations, null, 2));
1706
+ return;
1707
+ }
1708
+ header("Organizations");
1709
+ console.log();
1710
+ table(
1711
+ organizations.map((org) => ({
1712
+ name: org.name,
1713
+ slug: org.slug,
1714
+ tier: org.tier === "pro" ? chalk8.green("Pro") : chalk8.dim("Free"),
1715
+ role: org.role
1716
+ })),
1717
+ [
1718
+ { key: "name", header: "Name" },
1719
+ { key: "slug", header: "Slug" },
1720
+ { key: "tier", header: "Tier" },
1721
+ { key: "role", header: "Role" }
1722
+ ]
1723
+ );
1724
+ }
1725
+ async function listProjects(api, projectConfig, options) {
1726
+ let organizationId = options.organization || projectConfig?.organizationId;
1727
+ if (!organizationId) {
1728
+ const organizations = await withSpinner(
1729
+ "Fetching organizations...",
1730
+ async () => {
1731
+ const response = await api.get("/api/cli/organizations");
1732
+ return response.data || [];
1733
+ }
1734
+ );
1735
+ if (organizations.length === 0) {
1736
+ info("No organizations found.");
1737
+ return;
1738
+ }
1739
+ if (organizations.length === 1) {
1740
+ organizationId = organizations[0]._id;
1741
+ } else {
1742
+ info("Multiple organizations found. Use --organization to specify one.");
1743
+ console.log();
1744
+ for (const org of organizations) {
1745
+ console.log(` ${org.name} (${org.slug}): --organization ${org._id}`);
1746
+ }
1747
+ return;
1748
+ }
1749
+ }
1750
+ const projects = await withSpinner("Fetching projects...", async () => {
1751
+ const response = await api.get(
1752
+ "/api/cli/projects",
1753
+ { organizationId }
1754
+ );
1755
+ return response.data || [];
1756
+ });
1757
+ if (projects.length === 0) {
1758
+ info("No projects found.");
1759
+ return;
1760
+ }
1761
+ if (options.json) {
1762
+ console.log(JSON.stringify(projects, null, 2));
1763
+ return;
1764
+ }
1765
+ header("Projects");
1766
+ console.log();
1767
+ table(
1768
+ projects.map((project) => ({
1769
+ icon: project.icon || "\u{1F4E6}",
1770
+ name: project.name,
1771
+ slug: project.slug,
1772
+ description: project.description || chalk8.dim("-"),
1773
+ role: project.userRole === "admin" ? formatRole("admin") : formatProjectRole(project.projectRole),
1774
+ active: projectConfig?.projectId === project._id ? chalk8.green("\u2713") : ""
1775
+ })),
1776
+ [
1777
+ { key: "icon", header: "" },
1778
+ { key: "name", header: "Name" },
1779
+ { key: "slug", header: "Slug" },
1780
+ { key: "description", header: "Description", width: 30 },
1781
+ { key: "role", header: "Role" },
1782
+ { key: "active", header: "" }
1783
+ ]
1784
+ );
1785
+ const role = getRole();
1786
+ if (role) {
1787
+ console.log();
1788
+ console.log(chalk8.dim(`Your org role: ${formatRole(role)}`));
1789
+ }
1790
+ }
1791
+ async function listVariables(api, projectConfig, options) {
1792
+ const projectId = options.project || projectConfig?.projectId;
1793
+ const environment = options.env || projectConfig?.environment;
1794
+ if (!projectId) {
1795
+ error("No project specified. Use --project or run `envpilot init` first.");
1796
+ process.exit(1);
1797
+ }
1798
+ let metaProjectRole;
1799
+ const variables = await withSpinner("Fetching variables...", async () => {
1800
+ const params = { projectId };
1801
+ if (environment) {
1802
+ params.environment = environment;
1803
+ }
1804
+ const response = await api.get("/api/cli/variables", params);
1805
+ metaProjectRole = response.meta?.projectRole;
1806
+ return response.data || [];
1807
+ });
1808
+ if (variables.length === 0) {
1809
+ info(`No variables found${environment ? ` for ${environment}` : ""}.`);
1810
+ return;
1811
+ }
1812
+ if (options.json) {
1813
+ const output = variables.map((v) => ({
1814
+ ...v,
1815
+ value: options.showValues ? v.value : maskValue(v.value)
1816
+ }));
1817
+ console.log(JSON.stringify(output, null, 2));
1818
+ return;
1819
+ }
1820
+ header(`Variables${environment ? ` (${environment})` : ""}`);
1821
+ console.log();
1822
+ table(
1823
+ variables.map((variable) => ({
1824
+ key: variable.key,
1825
+ value: options.showValues ? variable.value : maskValue(variable.value),
1826
+ sensitive: variable.isSensitive ? chalk8.yellow("\u25CF") : "",
1827
+ version: `v${variable.version}`
1828
+ })),
1829
+ [
1830
+ { key: "key", header: "Key" },
1831
+ { key: "value", header: "Value", width: 40 },
1832
+ { key: "sensitive", header: "" },
1833
+ { key: "version", header: "Ver" }
1834
+ ]
1835
+ );
1836
+ console.log();
1837
+ console.log(chalk8.dim(`Total: ${variables.length} variables`));
1838
+ const role = getRole();
1839
+ if (role) {
1840
+ console.log(chalk8.dim(`Your org role: ${formatRole(role)}`));
1841
+ }
1842
+ if (metaProjectRole) {
1843
+ console.log(
1844
+ chalk8.dim(`Your project role: ${formatProjectRole(metaProjectRole)}`)
1845
+ );
1846
+ }
1847
+ if (role === "member" || metaProjectRole === "viewer") {
1848
+ console.log(
1849
+ chalk8.dim("You may only see variables you have been granted access to.")
1850
+ );
1851
+ }
1852
+ if (!options.showValues) {
1853
+ console.log(chalk8.dim("Use --show-values to see actual values"));
1854
+ }
1855
+ }
1856
+
1857
+ // src/commands/config.ts
1858
+ import { Command as Command7 } from "commander";
1859
+ import chalk9 from "chalk";
1860
+ var configCommand = new Command7("config").description("Manage CLI configuration").argument("[action]", "Action: get, set, list, path, reset").argument("[key]", "Config key (for get/set)").argument("[value]", "Config value (for set)").action(async (action, key, value) => {
1861
+ try {
1862
+ switch (action) {
1863
+ case "get":
1864
+ await handleGet(key);
1865
+ break;
1866
+ case "set":
1867
+ await handleSet(key, value);
1868
+ break;
1869
+ case "list":
1870
+ case void 0:
1871
+ await handleList();
1872
+ break;
1873
+ case "path":
1874
+ await handlePath();
1875
+ break;
1876
+ case "reset":
1877
+ await handleReset();
1878
+ break;
1879
+ default:
1880
+ error(`Unknown action: ${action}`);
1881
+ console.log();
1882
+ console.log("Available actions:");
1883
+ console.log(" list Show all configuration");
1884
+ console.log(" get <key> Get a specific config value");
1885
+ console.log(" set <key> <value> Set a config value");
1886
+ console.log(" path Show config file locations");
1887
+ console.log(" reset Reset all configuration");
1888
+ process.exit(1);
1889
+ }
1890
+ } catch (err) {
1891
+ error(err instanceof Error ? err.message : "Config operation failed");
1892
+ process.exit(1);
1893
+ }
1894
+ });
1895
+ async function handleGet(key) {
1896
+ if (!key) {
1897
+ error("Missing key. Usage: envpilot config get <key>");
1898
+ console.log();
1899
+ console.log("Available keys:");
1900
+ console.log(" apiUrl API endpoint URL");
1901
+ console.log(" user Current authenticated user");
1902
+ console.log(" activeProjectId Currently active project");
1903
+ console.log(" activeOrganizationId Currently active organization");
1904
+ process.exit(1);
1905
+ }
1906
+ const config2 = getConfig();
1907
+ switch (key) {
1908
+ case "apiUrl":
1909
+ console.log(config2.apiUrl);
1910
+ break;
1911
+ case "user":
1912
+ if (config2.user) {
1913
+ console.log(JSON.stringify(config2.user, null, 2));
1914
+ } else {
1915
+ console.log(chalk9.dim("(not set)"));
1916
+ }
1917
+ break;
1918
+ case "activeProjectId":
1919
+ console.log(config2.activeProjectId || chalk9.dim("(not set)"));
1920
+ break;
1921
+ case "activeOrganizationId":
1922
+ console.log(config2.activeOrganizationId || chalk9.dim("(not set)"));
1923
+ break;
1924
+ default:
1925
+ error(`Unknown key: ${key}`);
1926
+ process.exit(1);
1927
+ }
1928
+ }
1929
+ async function handleSet(key, value) {
1930
+ if (!key || value === void 0) {
1931
+ error("Missing key or value. Usage: envpilot config set <key> <value>");
1932
+ console.log();
1933
+ console.log("Settable keys:");
1934
+ console.log(" apiUrl API endpoint URL");
1935
+ process.exit(1);
1936
+ }
1937
+ switch (key) {
1938
+ case "apiUrl":
1939
+ try {
1940
+ new URL(value);
1941
+ } catch {
1942
+ error("Invalid URL format");
1943
+ process.exit(1);
1944
+ }
1945
+ setApiUrl(value);
1946
+ success(`Set apiUrl to ${value}`);
1947
+ break;
1948
+ default:
1949
+ error(`Cannot set key: ${key}`);
1950
+ console.log();
1951
+ console.log("Settable keys:");
1952
+ console.log(" apiUrl API endpoint URL");
1953
+ process.exit(1);
1954
+ }
1955
+ }
1956
+ async function handleList() {
1957
+ const config2 = getConfig();
1958
+ const projectConfig = readProjectConfig();
1959
+ header("Global Configuration");
1960
+ console.log();
1961
+ keyValue([
1962
+ ["API URL", config2.apiUrl],
1963
+ ["Authenticated", isAuthenticated() ? chalk9.green("Yes") : chalk9.red("No")],
1964
+ ["User", config2.user?.email],
1965
+ ["Active Organization", config2.activeOrganizationId],
1966
+ ["Active Project", config2.activeProjectId]
1967
+ ]);
1968
+ console.log();
1969
+ if (projectConfig) {
1970
+ header("Project Configuration (.envpilot)");
1971
+ console.log();
1972
+ keyValue([
1973
+ ["Project ID", projectConfig.projectId],
1974
+ ["Organization ID", projectConfig.organizationId],
1975
+ ["Environment", projectConfig.environment]
1976
+ ]);
1977
+ console.log();
1978
+ } else {
1979
+ info("No project configuration found in current directory.");
1980
+ console.log();
1981
+ }
1982
+ }
1983
+ async function handlePath() {
1984
+ header("Configuration Paths");
1985
+ console.log();
1986
+ keyValue([
1987
+ ["Global config", getConfigPath()],
1988
+ ["Project config", getProjectConfigPath()]
1989
+ ]);
1990
+ }
1991
+ async function handleReset() {
1992
+ const inquirer5 = await import("inquirer");
1993
+ const { confirm } = await inquirer5.default.prompt([
1994
+ {
1995
+ type: "confirm",
1996
+ name: "confirm",
1997
+ message: "Are you sure you want to reset all configuration? This will log you out.",
1998
+ default: false
1999
+ }
2000
+ ]);
2001
+ if (!confirm) {
2002
+ info("Reset cancelled.");
2003
+ return;
2004
+ }
2005
+ clearConfig();
2006
+ success("Configuration reset.");
2007
+ }
2008
+
2009
+ // src/commands/logout.ts
2010
+ import { Command as Command8 } from "commander";
2011
+ var logoutCommand = new Command8("logout").description("Log out from Envpilot").action(async () => {
2012
+ try {
2013
+ if (!isAuthenticated()) {
2014
+ info("You are not logged in.");
2015
+ return;
2016
+ }
2017
+ const user = getUser();
2018
+ const api = createAPIClient();
2019
+ try {
2020
+ await api.post("/api/cli/auth?action=revoke", {});
2021
+ } catch {
2022
+ }
2023
+ clearAuth();
2024
+ success(`Logged out${user?.email ? ` from ${user.email}` : ""}`);
2025
+ } catch (err) {
2026
+ error(err instanceof Error ? err.message : "Logout failed");
2027
+ process.exit(1);
2028
+ }
2029
+ });
2030
+
2031
+ // src/index.ts
2032
+ var program = new Command9();
2033
+ program.name("envpilot").description("Envpilot CLI - Sync, secure, and share environment variables").version("0.1.0");
2034
+ program.addCommand(loginCommand);
2035
+ program.addCommand(logoutCommand);
2036
+ program.addCommand(initCommand);
2037
+ program.addCommand(pullCommand);
2038
+ program.addCommand(pushCommand);
2039
+ program.addCommand(switchCommand);
2040
+ program.addCommand(listCommand);
2041
+ program.addCommand(configCommand);
2042
+ program.parse();