@zqw-cli/qenv 1.0.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,1487 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/index.ts
27
+ var import_commander = require("commander");
28
+ var import_chalk5 = __toESM(require("chalk"));
29
+
30
+ // src/utils/platform.ts
31
+ var import_node_child_process = require("child_process");
32
+ function getPlatform() {
33
+ switch (process.platform) {
34
+ case "win32":
35
+ return "windows";
36
+ case "darwin":
37
+ return "macos";
38
+ default:
39
+ return "linux";
40
+ }
41
+ }
42
+ function isElevated() {
43
+ var _a;
44
+ const platform = getPlatform();
45
+ if (platform === "windows") {
46
+ try {
47
+ (0, import_node_child_process.execSync)("net session", { stdio: "ignore" });
48
+ return true;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+ return ((_a = process.getuid) == null ? void 0 : _a.call(process)) === 0;
54
+ }
55
+ function getHomeDir() {
56
+ return process.env.HOME || process.env.USERPROFILE || "";
57
+ }
58
+
59
+ // src/drivers/windows.ts
60
+ var import_node_child_process2 = require("child_process");
61
+
62
+ // src/utils/logger.ts
63
+ var import_chalk = __toESM(require("chalk"));
64
+ var prefix = import_chalk.default.blue("qenv");
65
+ var logger = {
66
+ success(msg) {
67
+ console.log(`${prefix} ${import_chalk.default.green("\u2714")} ${msg}`);
68
+ },
69
+ warn(msg) {
70
+ console.log(`${prefix} ${import_chalk.default.yellow("\u26A0")} ${msg}`);
71
+ },
72
+ error(msg) {
73
+ console.error(`${prefix} ${import_chalk.default.red("\u2716")} ${msg}`);
74
+ },
75
+ info(msg) {
76
+ console.log(`${prefix} ${import_chalk.default.cyan("\u2139")} ${msg}`);
77
+ },
78
+ plain(msg) {
79
+ console.log(msg);
80
+ },
81
+ debug(msg) {
82
+ if (process.env.QENV_VERBOSE === "1") {
83
+ console.log(`${prefix} ${import_chalk.default.gray("[debug]")} ${msg}`);
84
+ }
85
+ },
86
+ table(headers, rows) {
87
+ const colWidths = headers.map(
88
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] || "").length))
89
+ );
90
+ const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(" ");
91
+ console.log(` ${import_chalk.default.bold(headerLine)}`);
92
+ const sep = colWidths.map((w) => "\u2500".repeat(w)).join("\u2500\u2500");
93
+ console.log(` ${sep}`);
94
+ for (const row of rows) {
95
+ const line = row.map((cell, i) => cell.padEnd(colWidths[i])).join(" ");
96
+ console.log(` ${line}`);
97
+ }
98
+ }
99
+ };
100
+
101
+ // src/utils/validator.ts
102
+ var VAR_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
103
+ var WINDOWS_SETX_MAX_LENGTH = 1024;
104
+ function validateName(name) {
105
+ if (!name || name.trim().length === 0) {
106
+ return { valid: false, message: "Variable name cannot be empty" };
107
+ }
108
+ if (!VAR_NAME_PATTERN.test(name)) {
109
+ return {
110
+ valid: false,
111
+ message: `Invalid variable name "${name}". Must match /^[A-Za-z_][A-Za-z0-9_]*$/`
112
+ };
113
+ }
114
+ return { valid: true };
115
+ }
116
+ function validateValueForWindows(value) {
117
+ if (value.length > WINDOWS_SETX_MAX_LENGTH) {
118
+ return {
119
+ valid: false,
120
+ message: `Value exceeds Windows setx limit of ${WINDOWS_SETX_MAX_LENGTH} characters (got ${value.length})`
121
+ };
122
+ }
123
+ return { valid: true };
124
+ }
125
+ function parseDotEnvLine(line) {
126
+ const trimmed = line.trim();
127
+ if (!trimmed || trimmed.startsWith("#")) {
128
+ return null;
129
+ }
130
+ const eqIndex = trimmed.indexOf("=");
131
+ if (eqIndex === -1) {
132
+ return null;
133
+ }
134
+ const key = trimmed.slice(0, eqIndex).trim();
135
+ let value = trimmed.slice(eqIndex + 1).trim();
136
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
137
+ value = value.slice(1, -1);
138
+ }
139
+ return { key, value };
140
+ }
141
+ function serializeDotEnv(vars) {
142
+ return Object.entries(vars).map(([key, value]) => {
143
+ const needsQuotes = /[\s#"'=]/.test(value);
144
+ const quoted = needsQuotes ? `"${value.replace(/"/g, '\\"')}"` : value;
145
+ return `${key}=${quoted}`;
146
+ }).join("\n");
147
+ }
148
+
149
+ // src/drivers/windows.ts
150
+ var WindowsDriver = class {
151
+ constructor(options = {}) {
152
+ this.systemWide = options.systemWide || false;
153
+ }
154
+ async get(name) {
155
+ try {
156
+ const regPath = this.systemWide ? "HKLM\\System\\CurrentControlSet\\Control\\Session Manager\\Environment" : "HKCU\\Environment";
157
+ const output = (0, import_node_child_process2.execSync)(`reg query "${regPath}" /v ${name}`, {
158
+ encoding: "utf-8",
159
+ stdio: ["pipe", "pipe", "pipe"]
160
+ });
161
+ const match = output.match(/\s+\S+\s+REG_(?:SZ|EXPAND_SZ)\s+(.*)/);
162
+ if (match) {
163
+ return match[1].trim();
164
+ }
165
+ return void 0;
166
+ } catch {
167
+ return void 0;
168
+ }
169
+ }
170
+ async set(name, value) {
171
+ if (this.systemWide) {
172
+ if (!isElevated()) {
173
+ throw new Error("System-wide operations require administrator privileges. Run as Administrator.");
174
+ }
175
+ (0, import_node_child_process2.execSync)(
176
+ `reg add "HKLM\\System\\CurrentControlSet\\Control\\Session Manager\\Environment" /v ${name} /t REG_EXPAND_SZ /d "${value}" /f`,
177
+ { stdio: "pipe" }
178
+ );
179
+ } else {
180
+ const validation = validateValueForWindows(value);
181
+ if (!validation.valid) {
182
+ throw new Error(validation.message);
183
+ }
184
+ (0, import_node_child_process2.execSync)(`setx ${name} "${value}"`, { stdio: "pipe" });
185
+ }
186
+ this.broadcastSettingChange();
187
+ }
188
+ async remove(name) {
189
+ const regPath = this.systemWide ? "HKLM\\System\\CurrentControlSet\\Control\\Session Manager\\Environment" : "HKCU\\Environment";
190
+ if (this.systemWide && !isElevated()) {
191
+ throw new Error("System-wide operations require administrator privileges. Run as Administrator.");
192
+ }
193
+ try {
194
+ (0, import_node_child_process2.execSync)(`reg delete "${regPath}" /v ${name} /f`, { stdio: "pipe" });
195
+ this.broadcastSettingChange();
196
+ } catch {
197
+ throw new Error(`Failed to remove variable "${name}". It may not exist.`);
198
+ }
199
+ }
200
+ async list() {
201
+ const result = {};
202
+ try {
203
+ const regPath = this.systemWide ? "HKLM\\System\\CurrentControlSet\\Control\\Session Manager\\Environment" : "HKCU\\Environment";
204
+ const output = (0, import_node_child_process2.execSync)(`reg query "${regPath}"`, {
205
+ encoding: "utf-8",
206
+ stdio: ["pipe", "pipe", "pipe"]
207
+ });
208
+ const lines = output.split("\n");
209
+ for (const line of lines) {
210
+ const match = line.match(/^\s+(\S+)\s+REG_(?:SZ|EXPAND_SZ)\s+(.*)/);
211
+ if (match) {
212
+ result[match[1].trim()] = match[2].trim();
213
+ }
214
+ }
215
+ } catch {
216
+ logger.debug("Failed to query Windows registry");
217
+ }
218
+ return result;
219
+ }
220
+ /**
221
+ * Broadcast WM_SETTINGCHANGE to notify other processes of env change.
222
+ */
223
+ broadcastSettingChange() {
224
+ try {
225
+ (0, import_node_child_process2.execSync)(
226
+ `powershell -Command "[System.Environment]::SetEnvironmentVariable('__qenv_broadcast__', $null, 'User')"`,
227
+ { stdio: "pipe" }
228
+ );
229
+ } catch {
230
+ logger.debug("Failed to broadcast WM_SETTINGCHANGE");
231
+ }
232
+ }
233
+ };
234
+
235
+ // src/drivers/unix.ts
236
+ var import_node_fs = require("fs");
237
+ var import_node_child_process3 = require("child_process");
238
+
239
+ // src/shell/detect.ts
240
+ function detectShell() {
241
+ const override = process.env.QENV_SHELL;
242
+ if (override && isValidShell(override)) {
243
+ return override;
244
+ }
245
+ if (process.platform === "win32") {
246
+ if (process.env.PSModulePath) {
247
+ return "pwsh";
248
+ }
249
+ return "cmd";
250
+ }
251
+ const shell = process.env.SHELL || "";
252
+ if (shell.includes("zsh")) return "zsh";
253
+ if (shell.includes("fish")) return "fish";
254
+ if (shell.includes("bash")) return "bash";
255
+ const parentPid = process.ppid;
256
+ if (parentPid) {
257
+ try {
258
+ const { execSync: execSync4 } = require("child_process");
259
+ const parentName = execSync4(`ps -p ${parentPid} -o comm=`, {
260
+ encoding: "utf-8",
261
+ stdio: ["pipe", "pipe", "pipe"]
262
+ }).trim();
263
+ if (parentName.includes("zsh")) return "zsh";
264
+ if (parentName.includes("fish")) return "fish";
265
+ if (parentName.includes("bash")) return "bash";
266
+ if (parentName.includes("pwsh") || parentName.includes("powershell")) return "pwsh";
267
+ } catch {
268
+ }
269
+ }
270
+ return "bash";
271
+ }
272
+ function isValidShell(shell) {
273
+ return ["bash", "zsh", "fish", "pwsh", "cmd"].includes(shell);
274
+ }
275
+ function getShellName(shell) {
276
+ const names = {
277
+ bash: "Bash",
278
+ zsh: "Zsh",
279
+ fish: "Fish",
280
+ pwsh: "PowerShell",
281
+ cmd: "Command Prompt"
282
+ };
283
+ return names[shell] || shell;
284
+ }
285
+
286
+ // src/drivers/unix.ts
287
+ var SHELL_FILES = {
288
+ zsh: ["~/.zshrc", "~/.zprofile"],
289
+ bash: ["~/.bashrc", "~/.bash_profile", "~/.profile"],
290
+ fish: ["~/.config/fish/config.fish"]
291
+ };
292
+ var QENV_MARKER = "# qenv:managed";
293
+ var UnixDriver = class {
294
+ constructor(options = {}) {
295
+ this.systemWide = options.systemWide || false;
296
+ }
297
+ async get(name) {
298
+ if (process.env[name] !== void 0) {
299
+ return process.env[name];
300
+ }
301
+ const configPath = this.getConfigFilePath();
302
+ if (configPath && (0, import_node_fs.existsSync)(configPath)) {
303
+ const content = (0, import_node_fs.readFileSync)(configPath, "utf-8");
304
+ const regex = new RegExp(`^export ${name}="(.+)"\\s*${QENV_MARKER.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m");
305
+ const match = content.match(regex);
306
+ if (match) {
307
+ return match[1];
308
+ }
309
+ }
310
+ return void 0;
311
+ }
312
+ async set(name, value) {
313
+ if (this.systemWide) {
314
+ const line = `${name}="${value}"`;
315
+ try {
316
+ (0, import_node_child_process3.execSync)(`sudo sh -c 'echo "${line} ${QENV_MARKER}" >> /etc/environment'`, { stdio: "pipe" });
317
+ } catch {
318
+ throw new Error("System-wide operations require sudo privileges.");
319
+ }
320
+ return;
321
+ }
322
+ const configPath = this.getConfigFilePath();
323
+ if (!configPath) {
324
+ throw new Error("Could not determine shell configuration file.");
325
+ }
326
+ const exportLine = `export ${name}="${value}" ${QENV_MARKER}`;
327
+ let content = "";
328
+ if ((0, import_node_fs.existsSync)(configPath)) {
329
+ content = (0, import_node_fs.readFileSync)(configPath, "utf-8");
330
+ }
331
+ const regex = new RegExp(`^export ${name}=.*${QENV_MARKER.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m");
332
+ if (regex.test(content)) {
333
+ content = content.replace(regex, exportLine);
334
+ } else {
335
+ content = content.trimEnd() + "\n" + exportLine + "\n";
336
+ }
337
+ (0, import_node_fs.writeFileSync)(configPath, content, "utf-8");
338
+ logger.debug(`Wrote to ${configPath}`);
339
+ }
340
+ async remove(name) {
341
+ if (this.systemWide) {
342
+ try {
343
+ (0, import_node_child_process3.execSync)(
344
+ `sudo sed -i '/^${name}=.*${QENV_MARKER.replace(/\//g, "\\/")}/d' /etc/environment`,
345
+ { stdio: "pipe" }
346
+ );
347
+ } catch {
348
+ throw new Error("System-wide operations require sudo privileges.");
349
+ }
350
+ return;
351
+ }
352
+ const configPath = this.getConfigFilePath();
353
+ if (!configPath || !(0, import_node_fs.existsSync)(configPath)) {
354
+ throw new Error(`Variable "${name}" not found in shell configuration.`);
355
+ }
356
+ let content = (0, import_node_fs.readFileSync)(configPath, "utf-8");
357
+ const regex = new RegExp(`^export ${name}=.*${QENV_MARKER.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "m");
358
+ if (!regex.test(content)) {
359
+ throw new Error(`Variable "${name}" is not managed by qenv.`);
360
+ }
361
+ content = content.replace(regex, "");
362
+ (0, import_node_fs.writeFileSync)(configPath, content, "utf-8");
363
+ }
364
+ async list() {
365
+ const result = {};
366
+ for (const [key, value] of Object.entries(process.env)) {
367
+ if (value !== void 0) {
368
+ result[key] = value;
369
+ }
370
+ }
371
+ return result;
372
+ }
373
+ /**
374
+ * Determine which shell config file to use.
375
+ */
376
+ getConfigFilePath() {
377
+ const home = getHomeDir();
378
+ if (!home) return null;
379
+ const shell = detectShell();
380
+ const files = SHELL_FILES[shell] || SHELL_FILES["bash"];
381
+ for (const file of files) {
382
+ const resolved = file.replace("~", home);
383
+ if ((0, import_node_fs.existsSync)(resolved)) {
384
+ return resolved;
385
+ }
386
+ }
387
+ if (files.length > 0) {
388
+ return files[0].replace("~", home);
389
+ }
390
+ return null;
391
+ }
392
+ };
393
+
394
+ // src/drivers/dotenv.ts
395
+ var import_node_fs2 = require("fs");
396
+ var import_node_path = require("path");
397
+ var DotenvDriver = class {
398
+ constructor(filePath = ".env") {
399
+ this.filePath = (0, import_node_path.resolve)(process.cwd(), filePath);
400
+ }
401
+ async get(name) {
402
+ const vars = await this.readFile();
403
+ return vars[name];
404
+ }
405
+ async set(name, value) {
406
+ const vars = await this.readFile();
407
+ vars[name] = value;
408
+ await this.writeFile(vars);
409
+ }
410
+ async remove(name) {
411
+ const vars = await this.readFile();
412
+ if (!(name in vars)) {
413
+ throw new Error(`Variable "${name}" not found in ${this.filePath}`);
414
+ }
415
+ delete vars[name];
416
+ await this.writeFile(vars);
417
+ }
418
+ async list() {
419
+ return this.readFile();
420
+ }
421
+ /**
422
+ * Remove all variables from the .env file.
423
+ */
424
+ async clear() {
425
+ await this.writeFile({});
426
+ }
427
+ /**
428
+ * Read and parse the .env file.
429
+ */
430
+ async readFile() {
431
+ const result = {};
432
+ if (!(0, import_node_fs2.existsSync)(this.filePath)) {
433
+ return result;
434
+ }
435
+ const content = (0, import_node_fs2.readFileSync)(this.filePath, "utf-8");
436
+ const lines = content.split("\n");
437
+ for (const line of lines) {
438
+ const parsed = parseDotEnvLine(line);
439
+ if (parsed) {
440
+ result[parsed.key] = parsed.value;
441
+ }
442
+ }
443
+ return result;
444
+ }
445
+ /**
446
+ * Write variables to the .env file, preserving comments.
447
+ */
448
+ async writeFile(vars) {
449
+ const lines = [];
450
+ if ((0, import_node_fs2.existsSync)(this.filePath)) {
451
+ const content = (0, import_node_fs2.readFileSync)(this.filePath, "utf-8");
452
+ const existingLines = content.split("\n");
453
+ const writtenKeys = /* @__PURE__ */ new Set();
454
+ for (const line of existingLines) {
455
+ const parsed = parseDotEnvLine(line);
456
+ if (parsed) {
457
+ if (parsed.key in vars) {
458
+ const value = vars[parsed.key];
459
+ const needsQuotes = /[\s#"'=]/.test(value);
460
+ const quoted = needsQuotes ? `"${value.replace(/"/g, '\\"')}"` : value;
461
+ lines.push(`${parsed.key}=${quoted}`);
462
+ writtenKeys.add(parsed.key);
463
+ }
464
+ } else {
465
+ lines.push(line);
466
+ }
467
+ }
468
+ for (const [key, value] of Object.entries(vars)) {
469
+ if (!writtenKeys.has(key)) {
470
+ const needsQuotes = /[\s#"'=]/.test(value);
471
+ const quoted = needsQuotes ? `"${value.replace(/"/g, '\\"')}"` : value;
472
+ lines.push(`${key}=${quoted}`);
473
+ }
474
+ }
475
+ } else {
476
+ for (const [key, value] of Object.entries(vars)) {
477
+ const needsQuotes = /[\s#"'=]/.test(value);
478
+ const quoted = needsQuotes ? `"${value.replace(/"/g, '\\"')}"` : value;
479
+ lines.push(`${key}=${quoted}`);
480
+ }
481
+ }
482
+ (0, import_node_fs2.writeFileSync)(this.filePath, lines.join("\n"), "utf-8");
483
+ }
484
+ /** Get the file path of this driver */
485
+ getFilePath() {
486
+ return this.filePath;
487
+ }
488
+ };
489
+
490
+ // src/drivers/index.ts
491
+ function getSystemDriver(options = {}) {
492
+ const platform = getPlatform();
493
+ if (platform === "windows") {
494
+ return new WindowsDriver(options);
495
+ }
496
+ return new UnixDriver(options);
497
+ }
498
+ function getDotenvDriver(filePath) {
499
+ return new DotenvDriver(filePath);
500
+ }
501
+
502
+ // src/manifest/index.ts
503
+ var import_conf = __toESM(require("conf"));
504
+ var SCHEMA = {
505
+ version: {
506
+ type: "number",
507
+ default: 1
508
+ },
509
+ vars: {
510
+ type: "object",
511
+ default: {}
512
+ }
513
+ };
514
+ function getVersion() {
515
+ try {
516
+ return `qenv@1.0.0`;
517
+ } catch {
518
+ return "qenv@unknown";
519
+ }
520
+ }
521
+ var ManifestManager = class {
522
+ constructor() {
523
+ this.store = new import_conf.default({
524
+ projectName: "qenv",
525
+ schema: SCHEMA,
526
+ defaults: {
527
+ version: 1,
528
+ vars: {}
529
+ }
530
+ });
531
+ }
532
+ /**
533
+ * Get all tracked variables.
534
+ */
535
+ getAll() {
536
+ return this.store.get("vars") || {};
537
+ }
538
+ /**
539
+ * Get a single variable entry.
540
+ */
541
+ get(name) {
542
+ const vars = this.getAll();
543
+ return vars[name];
544
+ }
545
+ /**
546
+ * Track a variable in the manifest.
547
+ */
548
+ set(name, scope, filePath = null) {
549
+ const vars = this.getAll();
550
+ const now = (/* @__PURE__ */ new Date()).toISOString();
551
+ const existing = vars[name];
552
+ vars[name] = {
553
+ scope,
554
+ filePath,
555
+ setAt: (existing == null ? void 0 : existing.setAt) || now,
556
+ updatedAt: now,
557
+ setBy: getVersion()
558
+ };
559
+ this.store.set("vars", vars);
560
+ }
561
+ /**
562
+ * Remove a variable from the manifest.
563
+ */
564
+ remove(name) {
565
+ const vars = this.getAll();
566
+ if (!(name in vars)) {
567
+ return false;
568
+ }
569
+ delete vars[name];
570
+ this.store.set("vars", vars);
571
+ return true;
572
+ }
573
+ /**
574
+ * Check if a variable is tracked.
575
+ */
576
+ has(name) {
577
+ const vars = this.getAll();
578
+ return name in vars;
579
+ }
580
+ /**
581
+ * Clear all manifest entries.
582
+ */
583
+ clear() {
584
+ this.store.set("vars", {});
585
+ }
586
+ /**
587
+ * Remove specific entries by name.
588
+ */
589
+ removeMany(names) {
590
+ const vars = this.getAll();
591
+ let removed = 0;
592
+ for (const name of names) {
593
+ if (name in vars) {
594
+ delete vars[name];
595
+ removed++;
596
+ }
597
+ }
598
+ this.store.set("vars", vars);
599
+ return removed;
600
+ }
601
+ /**
602
+ * Get the config file path (useful for debugging).
603
+ */
604
+ get path() {
605
+ return this.store.path;
606
+ }
607
+ };
608
+ var manifest = new ManifestManager();
609
+
610
+ // src/shell/eval.ts
611
+ function outputShellEval(key, value, action, shell) {
612
+ const expressions = {
613
+ bash: {
614
+ set: `export ${key}="${escapeValue(value || "")}"`,
615
+ unset: `unset ${key}`
616
+ },
617
+ zsh: {
618
+ set: `export ${key}="${escapeValue(value || "")}"`,
619
+ unset: `unset ${key}`
620
+ },
621
+ fish: {
622
+ set: `set -gx ${key} "${escapeValue(value || "")}"`,
623
+ unset: `set -e ${key}`
624
+ },
625
+ pwsh: {
626
+ set: `$env:${key} = "${escapeValue(value || "")}"`,
627
+ unset: `Remove-Item Env:${key} -ErrorAction SilentlyContinue`
628
+ },
629
+ cmd: {
630
+ set: `set "${key}=${value || ""}"`,
631
+ unset: `set "${key}="`
632
+ }
633
+ };
634
+ const shellExprs = expressions[shell] || expressions["bash"];
635
+ const expr = shellExprs[action];
636
+ if (expr) {
637
+ process.stdout.write(expr);
638
+ }
639
+ }
640
+ function escapeValue(value) {
641
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "\\$").replace(/`/g, "\\`");
642
+ }
643
+ function outputNoOp() {
644
+ process.stdout.write(":");
645
+ }
646
+
647
+ // src/commands/set.ts
648
+ function registerSetCommand(program, isShellEval) {
649
+ program.command("set <name> <value>").description("Set an environment variable").option("--local", "Set for current shell session only (requires qenv init)").option("--system", "Set system-wide (requires admin/sudo)").option("--file [path]", "Write to .env file (default: ./.env)").option("--json", "Parse value as JSON").option("--overwrite", "Overwrite if already exists").addHelpText("after", `
650
+ Examples:
651
+ $ qenv set NODE_ENV production
652
+ $ qenv set API_KEY sk-xxx --local
653
+ $ qenv set PORT 3000 --file .env.local
654
+ `).action(async (name, value, options) => {
655
+ const validation = validateName(name);
656
+ if (!validation.valid) {
657
+ logger.error(validation.message);
658
+ process.exitCode = 1;
659
+ return;
660
+ }
661
+ let finalValue = value;
662
+ if (options.json) {
663
+ try {
664
+ JSON.parse(value);
665
+ finalValue = value;
666
+ } catch {
667
+ logger.error(`Invalid JSON value: ${value}`);
668
+ process.exitCode = 1;
669
+ return;
670
+ }
671
+ }
672
+ try {
673
+ if (options.local) {
674
+ if (isShellEval) {
675
+ const shell = detectShell();
676
+ outputShellEval(name, finalValue, "set", shell);
677
+ } else {
678
+ logger.warn("--local requires qenv init. Run `qenv init` first.");
679
+ logger.info('Hint: use `eval "$(qenv --shell-eval set ' + name + " " + value + ' --local)"` as a workaround.');
680
+ }
681
+ return;
682
+ }
683
+ if (options.file !== void 0) {
684
+ const filePath = typeof options.file === "string" ? options.file : ".env";
685
+ const driver2 = getDotenvDriver(filePath);
686
+ if (!options.overwrite) {
687
+ const existing = await driver2.get(name);
688
+ if (existing !== void 0) {
689
+ logger.warn(`Variable "${name}" already exists in ${filePath}. Use --overwrite to replace.`);
690
+ process.exitCode = 1;
691
+ return;
692
+ }
693
+ }
694
+ await driver2.set(name, finalValue);
695
+ manifest.set(name, "file", driver2.getFilePath());
696
+ if (isShellEval) {
697
+ outputNoOp();
698
+ } else {
699
+ logger.success(`Set ${name} in ${filePath}`);
700
+ }
701
+ return;
702
+ }
703
+ const driver = getSystemDriver({ systemWide: options.system });
704
+ if (!options.overwrite) {
705
+ const existing = await driver.get(name);
706
+ if (existing !== void 0) {
707
+ logger.warn(`Variable "${name}" already exists. Use --overwrite to replace.`);
708
+ process.exitCode = 1;
709
+ return;
710
+ }
711
+ }
712
+ await driver.set(name, finalValue);
713
+ manifest.set(name, options.system ? "system-wide" : "system");
714
+ if (isShellEval) {
715
+ outputNoOp();
716
+ } else {
717
+ const scope = options.system ? "system-wide" : "user";
718
+ logger.success(`Set ${name}=${finalValue} (${scope})`);
719
+ }
720
+ } catch (err) {
721
+ logger.error(err.message || String(err));
722
+ process.exitCode = 1;
723
+ }
724
+ });
725
+ }
726
+
727
+ // src/commands/get.ts
728
+ function registerGetCommand(program) {
729
+ program.command("get <name>").description("Get an environment variable value").option("--source", "Show the source of the variable (qenv/system/file)").option("--file [path]", "Read from .env file (default: ./.env)").option("--json", "Output as JSON").option("--raw", "Output raw value only (for scripting)").addHelpText("after", `
730
+ Examples:
731
+ $ qenv get NODE_ENV
732
+ $ qenv get API_KEY --source
733
+ $ qenv get DATABASE_URL --file .env.prod
734
+ $ qenv get PORT --raw
735
+ `).action(async (name, options) => {
736
+ const validation = validateName(name);
737
+ if (!validation.valid) {
738
+ logger.error(validation.message);
739
+ process.exitCode = 1;
740
+ return;
741
+ }
742
+ try {
743
+ let value;
744
+ let source = "system";
745
+ if (options.file !== void 0) {
746
+ const filePath = typeof options.file === "string" ? options.file : ".env";
747
+ const driver = getDotenvDriver(filePath);
748
+ value = await driver.get(name);
749
+ source = `file (${filePath})`;
750
+ } else {
751
+ const driver = getSystemDriver();
752
+ value = await driver.get(name);
753
+ const entry = manifest.get(name);
754
+ if (entry) {
755
+ source = `qenv (${entry.scope})`;
756
+ }
757
+ }
758
+ if (value === void 0) {
759
+ if (options.raw) {
760
+ process.exitCode = 1;
761
+ return;
762
+ }
763
+ logger.warn(`Variable "${name}" not found`);
764
+ process.exitCode = 1;
765
+ return;
766
+ }
767
+ if (options.raw) {
768
+ process.stdout.write(value);
769
+ return;
770
+ }
771
+ if (options.json) {
772
+ logger.plain(JSON.stringify({ name, value, source }));
773
+ return;
774
+ }
775
+ if (options.source) {
776
+ logger.plain(`${name}=${value}`);
777
+ logger.info(`Source: ${source}`);
778
+ } else {
779
+ logger.plain(`${name}=${value}`);
780
+ }
781
+ } catch (err) {
782
+ logger.error(err.message || String(err));
783
+ process.exitCode = 1;
784
+ }
785
+ });
786
+ }
787
+
788
+ // src/commands/remove.ts
789
+ var import_node_readline = require("readline");
790
+ function confirm(message) {
791
+ const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
792
+ return new Promise((resolve4) => {
793
+ rl.question(`${message} (y/N) `, (answer) => {
794
+ rl.close();
795
+ resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
796
+ });
797
+ });
798
+ }
799
+ function registerRemoveCommand(program, isShellEval) {
800
+ program.command("remove <name>").alias("rm").description("Remove an environment variable").option("--system", "Remove system-wide variable (requires admin/sudo)").option("--local", "Remove from current shell session").option("--file [path]", "Remove from .env file (default: ./.env)").option("--force", "Skip confirmation prompt").option("--all", "Remove all variables (only with --file)").addHelpText("after", `
801
+ Examples:
802
+ $ qenv remove NODE_ENV
803
+ $ qenv remove API_KEY --force
804
+ $ qenv remove DATABASE_URL --file .env.prod
805
+ $ qenv remove --all --file
806
+ `).action(async (name, options) => {
807
+ try {
808
+ if (options.local) {
809
+ if (isShellEval) {
810
+ const shell = detectShell();
811
+ outputShellEval(name, null, "unset", shell);
812
+ } else {
813
+ logger.warn("--local requires qenv init. Run `qenv init` first.");
814
+ }
815
+ return;
816
+ }
817
+ if (options.file !== void 0) {
818
+ const filePath = typeof options.file === "string" ? options.file : ".env";
819
+ const driver2 = getDotenvDriver(filePath);
820
+ if (options.all) {
821
+ if (!options.force) {
822
+ const confirmed = await confirm(`Remove all variables from ${filePath}?`);
823
+ if (!confirmed) {
824
+ logger.info("Cancelled.");
825
+ return;
826
+ }
827
+ }
828
+ await driver2.clear();
829
+ logger.success(`Cleared all variables from ${filePath}`);
830
+ if (isShellEval) outputNoOp();
831
+ return;
832
+ }
833
+ const validation2 = validateName(name);
834
+ if (!validation2.valid) {
835
+ logger.error(validation2.message);
836
+ process.exitCode = 1;
837
+ return;
838
+ }
839
+ await driver2.remove(name);
840
+ manifest.remove(name);
841
+ if (isShellEval) {
842
+ outputNoOp();
843
+ } else {
844
+ logger.success(`Removed ${name} from ${filePath}`);
845
+ }
846
+ return;
847
+ }
848
+ const validation = validateName(name);
849
+ if (!validation.valid) {
850
+ logger.error(validation.message);
851
+ process.exitCode = 1;
852
+ return;
853
+ }
854
+ if (!options.force) {
855
+ const confirmed = await confirm(`Remove variable "${name}"?`);
856
+ if (!confirmed) {
857
+ logger.info("Cancelled.");
858
+ return;
859
+ }
860
+ }
861
+ const driver = getSystemDriver({ systemWide: options.system });
862
+ await driver.remove(name);
863
+ manifest.remove(name);
864
+ if (isShellEval) {
865
+ outputNoOp();
866
+ } else {
867
+ logger.success(`Removed ${name}`);
868
+ }
869
+ } catch (err) {
870
+ logger.error(err.message || String(err));
871
+ process.exitCode = 1;
872
+ }
873
+ });
874
+ }
875
+
876
+ // src/commands/list.ts
877
+ var import_chalk2 = __toESM(require("chalk"));
878
+ function timeAgo(dateStr) {
879
+ const date = new Date(dateStr);
880
+ const now = /* @__PURE__ */ new Date();
881
+ const diffMs = now.getTime() - date.getTime();
882
+ const diffSec = Math.floor(diffMs / 1e3);
883
+ const diffMin = Math.floor(diffSec / 60);
884
+ const diffHour = Math.floor(diffMin / 60);
885
+ const diffDay = Math.floor(diffHour / 24);
886
+ if (diffDay > 0) return `${diffDay} day${diffDay > 1 ? "s" : ""} ago`;
887
+ if (diffHour > 0) return `${diffHour} hour${diffHour > 1 ? "s" : ""} ago`;
888
+ if (diffMin > 0) return `${diffMin} min${diffMin > 1 ? "s" : ""} ago`;
889
+ return "just now";
890
+ }
891
+ function maskValue(value) {
892
+ if (value.length <= 6) return value;
893
+ const visible = value.slice(0, 3);
894
+ const tail = value.slice(-3);
895
+ return `${visible}***${tail}`;
896
+ }
897
+ function registerListCommand(program) {
898
+ program.command("list").alias("ls").description("List environment variables").option("--all", "List all system environment variables").option("--file [path]", "List variables from .env file (default: ./.env)").option("--json", "Output as JSON").option("--verify", "Verify manifest entries against actual system values").option("--stale", "Show only stale (not found) manifest entries").addHelpText("after", `
899
+ Examples:
900
+ $ qenv list
901
+ $ qenv list --all
902
+ $ qenv list --file .env.prod
903
+ $ qenv list --json
904
+ $ qenv list --verify
905
+ $ qenv list --stale
906
+ `).action(async (options) => {
907
+ try {
908
+ if (options.file !== void 0) {
909
+ const filePath = typeof options.file === "string" ? options.file : ".env";
910
+ const driver2 = getDotenvDriver(filePath);
911
+ const vars2 = await driver2.list();
912
+ if (options.json) {
913
+ logger.plain(JSON.stringify(vars2, null, 2));
914
+ return;
915
+ }
916
+ const entries2 = Object.entries(vars2);
917
+ if (entries2.length === 0) {
918
+ logger.info(`No variables found in ${filePath}`);
919
+ return;
920
+ }
921
+ logger.plain("");
922
+ logger.table(
923
+ ["NAME", "VALUE"],
924
+ entries2.map(([key, value]) => [key, value])
925
+ );
926
+ logger.plain("");
927
+ return;
928
+ }
929
+ if (options.all) {
930
+ const driver2 = getSystemDriver();
931
+ const vars2 = await driver2.list();
932
+ if (options.json) {
933
+ logger.plain(JSON.stringify(vars2, null, 2));
934
+ return;
935
+ }
936
+ const entries2 = Object.entries(vars2).sort(([a], [b]) => a.localeCompare(b));
937
+ if (entries2.length === 0) {
938
+ logger.info("No environment variables found");
939
+ return;
940
+ }
941
+ logger.plain("");
942
+ for (const [key, value] of entries2) {
943
+ logger.plain(` ${import_chalk2.default.bold(key)}=${value}`);
944
+ }
945
+ logger.plain(`
946
+ Total: ${entries2.length} variables
947
+ `);
948
+ return;
949
+ }
950
+ const vars = manifest.getAll();
951
+ const entries = Object.entries(vars);
952
+ if (entries.length === 0) {
953
+ logger.info("No variables managed by qenv. Use `qenv set` to add variables.");
954
+ return;
955
+ }
956
+ const driver = getSystemDriver();
957
+ const rows = [];
958
+ let staleCount = 0;
959
+ for (const [name, entry] of entries) {
960
+ let currentValue;
961
+ if (entry.scope === "file" && entry.filePath) {
962
+ const fileDriver = getDotenvDriver(entry.filePath);
963
+ currentValue = await fileDriver.get(name);
964
+ } else {
965
+ currentValue = await driver.get(name);
966
+ }
967
+ const isStale = currentValue === void 0;
968
+ if (isStale) staleCount++;
969
+ if (options.stale && !isStale) continue;
970
+ const valueDisplay = isStale ? import_chalk2.default.yellow("(not found) \u26A0") : maskValue(currentValue);
971
+ const updated = timeAgo(entry.updatedAt);
972
+ rows.push([name, valueDisplay, entry.scope, updated]);
973
+ }
974
+ if (options.json) {
975
+ const jsonData = {};
976
+ for (const [name, entry] of entries) {
977
+ let currentValue;
978
+ if (entry.scope === "file" && entry.filePath) {
979
+ const fileDriver = getDotenvDriver(entry.filePath);
980
+ currentValue = await fileDriver.get(name);
981
+ } else {
982
+ currentValue = await driver.get(name);
983
+ }
984
+ jsonData[name] = {
985
+ ...entry,
986
+ currentValue: currentValue || null,
987
+ stale: currentValue === void 0
988
+ };
989
+ }
990
+ logger.plain(JSON.stringify(jsonData, null, 2));
991
+ return;
992
+ }
993
+ if (rows.length === 0) {
994
+ logger.info("No stale entries found.");
995
+ return;
996
+ }
997
+ logger.plain("");
998
+ logger.table(["NAME", "VALUE", "SCOPE", "UPDATED"], rows);
999
+ logger.plain("");
1000
+ if (staleCount > 0) {
1001
+ logger.warn(
1002
+ `${staleCount} stale ${staleCount === 1 ? "entry" : "entries"} found. Run \`qenv clean\` to remove.`
1003
+ );
1004
+ }
1005
+ } catch (err) {
1006
+ logger.error(err.message || String(err));
1007
+ process.exitCode = 1;
1008
+ }
1009
+ });
1010
+ }
1011
+
1012
+ // src/commands/diff.ts
1013
+ var import_chalk3 = __toESM(require("chalk"));
1014
+ var import_node_readline2 = require("readline");
1015
+ function confirm2(message) {
1016
+ const rl = (0, import_node_readline2.createInterface)({ input: process.stdin, output: process.stdout });
1017
+ return new Promise((resolve4) => {
1018
+ rl.question(`${message} (y/N) `, (answer) => {
1019
+ rl.close();
1020
+ resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
1021
+ });
1022
+ });
1023
+ }
1024
+ function registerDiffCommand(program) {
1025
+ program.command("diff").description("Compare manifest records with actual system values").option("--json", "Output as JSON").addHelpText("after", `
1026
+ Examples:
1027
+ $ qenv diff
1028
+ $ qenv diff --json
1029
+ `).action(async (options) => {
1030
+ try {
1031
+ const vars = manifest.getAll();
1032
+ const entries = Object.entries(vars);
1033
+ if (entries.length === 0) {
1034
+ logger.info("No variables managed by qenv.");
1035
+ return;
1036
+ }
1037
+ const driver = getSystemDriver();
1038
+ const diffs = [];
1039
+ for (const [name, entry] of entries) {
1040
+ let currentValue;
1041
+ if (entry.scope === "file" && entry.filePath) {
1042
+ const fileDriver = getDotenvDriver(entry.filePath);
1043
+ currentValue = await fileDriver.get(name);
1044
+ } else {
1045
+ currentValue = await driver.get(name);
1046
+ }
1047
+ if (currentValue === void 0) {
1048
+ diffs.push({
1049
+ name,
1050
+ scope: entry.scope,
1051
+ manifestStatus: "tracked",
1052
+ currentValue: null,
1053
+ status: "missing"
1054
+ });
1055
+ } else {
1056
+ diffs.push({
1057
+ name,
1058
+ scope: entry.scope,
1059
+ manifestStatus: "tracked",
1060
+ currentValue,
1061
+ status: "match"
1062
+ });
1063
+ }
1064
+ }
1065
+ if (options.json) {
1066
+ logger.plain(JSON.stringify(diffs, null, 2));
1067
+ return;
1068
+ }
1069
+ logger.plain("");
1070
+ const rows = [];
1071
+ for (const diff of diffs) {
1072
+ let statusIcon;
1073
+ switch (diff.status) {
1074
+ case "match":
1075
+ statusIcon = import_chalk3.default.green("\u2714 in sync");
1076
+ break;
1077
+ case "changed":
1078
+ statusIcon = import_chalk3.default.yellow("~ changed");
1079
+ break;
1080
+ case "missing":
1081
+ statusIcon = import_chalk3.default.red("\u2716 missing");
1082
+ break;
1083
+ }
1084
+ rows.push([diff.name, diff.scope, statusIcon]);
1085
+ }
1086
+ logger.table(["NAME", "SCOPE", "STATUS"], rows);
1087
+ logger.plain("");
1088
+ const missingCount = diffs.filter((d) => d.status === "missing").length;
1089
+ const changedCount = diffs.filter((d) => d.status === "changed").length;
1090
+ if (missingCount > 0) {
1091
+ logger.warn(`${missingCount} variable${missingCount > 1 ? "s" : ""} missing from system. Run \`qenv clean\` to remove stale entries.`);
1092
+ }
1093
+ if (changedCount > 0) {
1094
+ logger.info(`${changedCount} variable${changedCount > 1 ? "s" : ""} changed since last set.`);
1095
+ }
1096
+ if (missingCount === 0 && changedCount === 0) {
1097
+ logger.success("All variables are in sync.");
1098
+ }
1099
+ } catch (err) {
1100
+ logger.error(err.message || String(err));
1101
+ process.exitCode = 1;
1102
+ }
1103
+ });
1104
+ }
1105
+ function registerCleanCommand(program) {
1106
+ program.command("clean").description("Remove stale entries from manifest").option("--force", "Skip confirmation prompt").addHelpText("after", `
1107
+ Examples:
1108
+ $ qenv clean
1109
+ $ qenv clean --force
1110
+ `).action(async (options) => {
1111
+ try {
1112
+ const vars = manifest.getAll();
1113
+ const entries = Object.entries(vars);
1114
+ if (entries.length === 0) {
1115
+ logger.info("No variables managed by qenv.");
1116
+ return;
1117
+ }
1118
+ const driver = getSystemDriver();
1119
+ const staleNames = [];
1120
+ for (const [name, entry] of entries) {
1121
+ let currentValue;
1122
+ if (entry.scope === "file" && entry.filePath) {
1123
+ const fileDriver = getDotenvDriver(entry.filePath);
1124
+ currentValue = await fileDriver.get(name);
1125
+ } else {
1126
+ currentValue = await driver.get(name);
1127
+ }
1128
+ if (currentValue === void 0) {
1129
+ staleNames.push(name);
1130
+ }
1131
+ }
1132
+ if (staleNames.length === 0) {
1133
+ logger.success("No stale entries found. Everything is clean!");
1134
+ return;
1135
+ }
1136
+ logger.plain("");
1137
+ logger.info(`Found ${staleNames.length} stale ${staleNames.length === 1 ? "entry" : "entries"}:`);
1138
+ for (const name of staleNames) {
1139
+ logger.plain(` ${import_chalk3.default.yellow("\u26A0")} ${name}`);
1140
+ }
1141
+ logger.plain("");
1142
+ if (!options.force) {
1143
+ const confirmed = await confirm2("Remove these stale entries from manifest?");
1144
+ if (!confirmed) {
1145
+ logger.info("Cancelled.");
1146
+ return;
1147
+ }
1148
+ }
1149
+ const removed = manifest.removeMany(staleNames);
1150
+ logger.success(`Removed ${removed} stale ${removed === 1 ? "entry" : "entries"} from manifest.`);
1151
+ } catch (err) {
1152
+ logger.error(err.message || String(err));
1153
+ process.exitCode = 1;
1154
+ }
1155
+ });
1156
+ }
1157
+
1158
+ // src/commands/init.ts
1159
+ var import_node_fs3 = require("fs");
1160
+ var import_node_path2 = require("path");
1161
+ var SHELL_CONFIGS = {
1162
+ bash: "~/.bashrc",
1163
+ zsh: "~/.zshrc",
1164
+ fish: "~/.config/fish/config.fish",
1165
+ pwsh: "",
1166
+ // Determined dynamically via $PROFILE
1167
+ cmd: ""
1168
+ // Not applicable
1169
+ };
1170
+ var WRAPPER_CODE = {
1171
+ bash: `
1172
+ # qenv shell integration
1173
+ qenv() { eval "$(command qenv --shell-eval "$@")"; }
1174
+ # end qenv`,
1175
+ zsh: `
1176
+ # qenv shell integration
1177
+ qenv() { eval "$(command qenv --shell-eval "$@")"; }
1178
+ # end qenv`,
1179
+ fish: `
1180
+ # qenv shell integration
1181
+ function qenv
1182
+ eval (command qenv --shell-eval $argv)
1183
+ end
1184
+ # end qenv`,
1185
+ pwsh: `
1186
+ # qenv shell integration
1187
+ function qenv { $result = & qenv.cmd --shell-eval @args; if ($result) { Invoke-Expression $result } }
1188
+ # end qenv`,
1189
+ cmd: ""
1190
+ };
1191
+ var WRAPPER_START = "# qenv shell integration";
1192
+ var WRAPPER_END = "# end qenv";
1193
+ function getConfigPath(shell) {
1194
+ const home = getHomeDir();
1195
+ if (!home) return null;
1196
+ if (shell === "pwsh") {
1197
+ const psProfile = process.env.PROFILE;
1198
+ if (psProfile) return psProfile;
1199
+ if (process.platform === "win32") {
1200
+ return (0, import_node_path2.resolve)(process.env.USERPROFILE || home, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1");
1201
+ }
1202
+ return (0, import_node_path2.resolve)(home, ".config", "powershell", "Microsoft.PowerShell_profile.ps1");
1203
+ }
1204
+ if (shell === "cmd") {
1205
+ return null;
1206
+ }
1207
+ const configPath = SHELL_CONFIGS[shell];
1208
+ return configPath ? configPath.replace("~", home) : null;
1209
+ }
1210
+ function registerInitCommand(program) {
1211
+ program.command("init").description("Install shell integration for --local mode support").option("--shell <type>", "Specify shell type (bash/zsh/fish/pwsh)").option("--print", "Print wrapper code without installing").option("--uninstall", "Remove shell integration").addHelpText("after", `
1212
+ Examples:
1213
+ $ qenv init
1214
+ $ qenv init --shell zsh
1215
+ $ qenv init --print
1216
+ $ qenv init --uninstall
1217
+ `).action(async (options) => {
1218
+ try {
1219
+ const shell = options.shell || detectShell();
1220
+ if (shell === "cmd") {
1221
+ logger.error("Command Prompt (cmd) does not support shell integration.");
1222
+ logger.info("Please use PowerShell instead.");
1223
+ process.exitCode = 1;
1224
+ return;
1225
+ }
1226
+ const wrapperCode = WRAPPER_CODE[shell];
1227
+ if (!wrapperCode) {
1228
+ logger.error(`Shell "${shell}" is not supported.`);
1229
+ process.exitCode = 1;
1230
+ return;
1231
+ }
1232
+ if (options.print) {
1233
+ logger.info(`Add the following to your ${getShellName(shell)} config:
1234
+ `);
1235
+ logger.plain(wrapperCode);
1236
+ return;
1237
+ }
1238
+ const configPath = getConfigPath(shell);
1239
+ if (!configPath) {
1240
+ logger.error("Could not determine shell config file path.");
1241
+ logger.info("Use --print to see the wrapper code and add it manually.");
1242
+ process.exitCode = 1;
1243
+ return;
1244
+ }
1245
+ if (options.uninstall) {
1246
+ if (!(0, import_node_fs3.existsSync)(configPath)) {
1247
+ logger.info("Shell config file not found. Nothing to uninstall.");
1248
+ return;
1249
+ }
1250
+ let content2 = (0, import_node_fs3.readFileSync)(configPath, "utf-8");
1251
+ const startIdx = content2.indexOf(WRAPPER_START);
1252
+ const endIdx = content2.indexOf(WRAPPER_END);
1253
+ if (startIdx === -1) {
1254
+ logger.info("qenv shell integration not found. Nothing to uninstall.");
1255
+ return;
1256
+ }
1257
+ const before = content2.slice(0, startIdx).trimEnd();
1258
+ const after = content2.slice(endIdx + WRAPPER_END.length).trimStart();
1259
+ content2 = before + (after ? "\n" + after : "") + "\n";
1260
+ (0, import_node_fs3.writeFileSync)(configPath, content2, "utf-8");
1261
+ logger.success(`Removed qenv integration from ${configPath}`);
1262
+ logger.info("Restart your shell to apply changes.");
1263
+ return;
1264
+ }
1265
+ let content = "";
1266
+ if ((0, import_node_fs3.existsSync)(configPath)) {
1267
+ content = (0, import_node_fs3.readFileSync)(configPath, "utf-8");
1268
+ if (content.includes(WRAPPER_START)) {
1269
+ logger.info("qenv shell integration is already installed.");
1270
+ logger.info(`Config file: ${configPath}`);
1271
+ return;
1272
+ }
1273
+ }
1274
+ (0, import_node_fs3.appendFileSync)(configPath, "\n" + wrapperCode.trim() + "\n", "utf-8");
1275
+ logger.success(`Installed qenv integration in ${configPath}`);
1276
+ logger.info(`Shell: ${getShellName(shell)}`);
1277
+ logger.info("Restart your shell or run:");
1278
+ if (shell === "fish") {
1279
+ logger.plain(` source ${configPath}`);
1280
+ } else if (shell === "pwsh") {
1281
+ logger.plain(` . $PROFILE`);
1282
+ } else {
1283
+ logger.plain(` source ${configPath}`);
1284
+ }
1285
+ } catch (err) {
1286
+ logger.error(err.message || String(err));
1287
+ process.exitCode = 1;
1288
+ }
1289
+ });
1290
+ }
1291
+
1292
+ // src/commands/import.ts
1293
+ var import_node_fs4 = require("fs");
1294
+ var import_node_path3 = require("path");
1295
+ var import_chalk4 = __toESM(require("chalk"));
1296
+ function registerImportCommand(program, isShellEval) {
1297
+ program.command("import <file>").description("Import variables from a .env file").option("--local", "Import to current shell session (requires qenv init)").option("--dry-run", "Preview changes without applying").option("--overwrite", "Overwrite existing variables").addHelpText("after", `
1298
+ Examples:
1299
+ $ qenv import .env
1300
+ $ qenv import .env.prod --dry-run
1301
+ $ qenv import .env --local
1302
+ $ qenv import .env --overwrite
1303
+ `).action(async (file, options) => {
1304
+ try {
1305
+ const filePath = (0, import_node_path3.resolve)(process.cwd(), file);
1306
+ if (!(0, import_node_fs4.existsSync)(filePath)) {
1307
+ logger.error(`File not found: ${filePath}`);
1308
+ process.exitCode = 1;
1309
+ return;
1310
+ }
1311
+ const content = (0, import_node_fs4.readFileSync)(filePath, "utf-8");
1312
+ const lines = content.split("\n");
1313
+ const vars = {};
1314
+ for (const line of lines) {
1315
+ const parsed = parseDotEnvLine(line);
1316
+ if (parsed) {
1317
+ vars[parsed.key] = parsed.value;
1318
+ }
1319
+ }
1320
+ const entries = Object.entries(vars);
1321
+ if (entries.length === 0) {
1322
+ logger.info(`No variables found in ${file}`);
1323
+ return;
1324
+ }
1325
+ if (options.dryRun) {
1326
+ logger.info(`Preview of import from ${file}:
1327
+ `);
1328
+ for (const [key, value] of entries) {
1329
+ logger.plain(` ${import_chalk4.default.green("+")} ${key}=${value}`);
1330
+ }
1331
+ logger.plain(`
1332
+ Total: ${entries.length} variable${entries.length > 1 ? "s" : ""}`);
1333
+ return;
1334
+ }
1335
+ if (options.local) {
1336
+ if (isShellEval) {
1337
+ const shell = detectShell();
1338
+ const evalParts = [];
1339
+ for (const [key, value] of entries) {
1340
+ const expressions = {
1341
+ bash: `export ${key}="${value}"`,
1342
+ zsh: `export ${key}="${value}"`,
1343
+ fish: `set -gx ${key} "${value}"`,
1344
+ pwsh: `$env:${key} = "${value}"`,
1345
+ cmd: `set "${key}=${value}"`
1346
+ };
1347
+ evalParts.push(expressions[shell] || expressions["bash"]);
1348
+ }
1349
+ const separator = shell === "fish" ? "; " : "; ";
1350
+ process.stdout.write(evalParts.join(separator));
1351
+ } else {
1352
+ logger.warn("--local requires qenv init. Run `qenv init` first.");
1353
+ }
1354
+ return;
1355
+ }
1356
+ const driver = getSystemDriver();
1357
+ let imported = 0;
1358
+ let skipped = 0;
1359
+ for (const [key, value] of entries) {
1360
+ if (!options.overwrite) {
1361
+ const existing = await driver.get(key);
1362
+ if (existing !== void 0) {
1363
+ logger.warn(`Skipped ${key} (already exists). Use --overwrite to replace.`);
1364
+ skipped++;
1365
+ continue;
1366
+ }
1367
+ }
1368
+ await driver.set(key, value);
1369
+ manifest.set(key, "system");
1370
+ imported++;
1371
+ }
1372
+ if (isShellEval) {
1373
+ outputNoOp();
1374
+ } else {
1375
+ logger.success(`Imported ${imported} variable${imported > 1 ? "s" : ""} from ${file}`);
1376
+ if (skipped > 0) {
1377
+ logger.info(`Skipped ${skipped} existing variable${skipped > 1 ? "s" : ""}.`);
1378
+ }
1379
+ }
1380
+ } catch (err) {
1381
+ logger.error(err.message || String(err));
1382
+ process.exitCode = 1;
1383
+ }
1384
+ });
1385
+ }
1386
+ function registerExportCommand(program) {
1387
+ program.command("export [file]").description("Export variables to .env format").option("--all", "Export all system environment variables").option("--filter <prefix>", "Only export variables matching prefix").addHelpText("after", `
1388
+ Examples:
1389
+ $ qenv export
1390
+ $ qenv export .env.backup
1391
+ $ qenv export --all
1392
+ $ qenv export --filter REACT_APP_
1393
+ `).action(async (file, options) => {
1394
+ try {
1395
+ let vars = {};
1396
+ if (options.all) {
1397
+ const driver = getSystemDriver();
1398
+ vars = await driver.list();
1399
+ } else {
1400
+ const manifestVars = manifest.getAll();
1401
+ const driver = getSystemDriver();
1402
+ for (const [name, entry] of Object.entries(manifestVars)) {
1403
+ let value;
1404
+ if (entry.scope === "file" && entry.filePath) {
1405
+ const fileDriver = getDotenvDriver(entry.filePath);
1406
+ value = await fileDriver.get(name);
1407
+ } else {
1408
+ value = await driver.get(name);
1409
+ }
1410
+ if (value !== void 0) {
1411
+ vars[name] = value;
1412
+ }
1413
+ }
1414
+ }
1415
+ if (options.filter) {
1416
+ const prefix2 = options.filter;
1417
+ const filtered = {};
1418
+ for (const [key, value] of Object.entries(vars)) {
1419
+ if (key.startsWith(prefix2)) {
1420
+ filtered[key] = value;
1421
+ }
1422
+ }
1423
+ vars = filtered;
1424
+ }
1425
+ const entries = Object.entries(vars);
1426
+ if (entries.length === 0) {
1427
+ logger.info("No variables to export.");
1428
+ return;
1429
+ }
1430
+ const output = serializeDotEnv(vars);
1431
+ if (file) {
1432
+ const outPath = (0, import_node_path3.resolve)(process.cwd(), file);
1433
+ (0, import_node_fs4.writeFileSync)(outPath, output + "\n", "utf-8");
1434
+ logger.success(`Exported ${entries.length} variable${entries.length > 1 ? "s" : ""} to ${file}`);
1435
+ } else {
1436
+ logger.plain(output);
1437
+ }
1438
+ } catch (err) {
1439
+ logger.error(err.message || String(err));
1440
+ process.exitCode = 1;
1441
+ }
1442
+ });
1443
+ }
1444
+
1445
+ // src/index.ts
1446
+ var VERSION = "1.0.0";
1447
+ function isShellEvalMode() {
1448
+ return process.argv.includes("--shell-eval");
1449
+ }
1450
+ function setupGlobals() {
1451
+ if (process.argv.includes("--verbose")) {
1452
+ process.env.QENV_VERBOSE = "1";
1453
+ }
1454
+ if (process.argv.includes("--no-color")) {
1455
+ process.env.NO_COLOR = "1";
1456
+ process.env.FORCE_COLOR = "0";
1457
+ }
1458
+ if (process.argv.includes("--silent")) {
1459
+ const originalLog = console.log;
1460
+ const originalInfo = console.info;
1461
+ console.log = () => {
1462
+ };
1463
+ console.info = () => {
1464
+ };
1465
+ }
1466
+ }
1467
+ function main() {
1468
+ setupGlobals();
1469
+ const shellEval = isShellEvalMode();
1470
+ const argv = process.argv.filter((arg) => arg !== "--shell-eval");
1471
+ const program = new import_commander.Command();
1472
+ program.name("qenv").version(VERSION, "-V, --version", "Output the version number").description(`${import_chalk5.default.bold(`qenv v${VERSION}`)} \u2014 Cross-platform env var manager`).option("--no-color", "Disable colored output").option("--silent", "Suppress all stdout output (errors only)").option("--verbose", "Output debug information").addHelpText("after", `
1473
+ Run ${import_chalk5.default.cyan("qenv <command> --help")} for detailed usage of each command.
1474
+ `);
1475
+ registerSetCommand(program, shellEval);
1476
+ registerGetCommand(program);
1477
+ registerRemoveCommand(program, shellEval);
1478
+ registerListCommand(program);
1479
+ registerDiffCommand(program);
1480
+ registerCleanCommand(program);
1481
+ registerInitCommand(program);
1482
+ registerImportCommand(program, shellEval);
1483
+ registerExportCommand(program);
1484
+ program.parse(argv);
1485
+ }
1486
+ main();
1487
+ //# sourceMappingURL=index.js.map