lazy-vault 1.0.0 → 2.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/README.md CHANGED
@@ -1,47 +1,52 @@
1
- # Lazy Vault
1
+ # lazy-vault
2
2
 
3
3
  ![npm version](https://img.shields.io/npm/v/lazy-vault?color=blue)
4
4
  ![license](https://img.shields.io/badge/license-MIT-green)
5
5
  ![status](https://img.shields.io/badge/status-stable-brightgreen)
6
6
 
7
- **Stop leaking secrets & Start committing safely.**
7
+ **Security for the lazy developer.**
8
+ Stop worrying about sharing `.env` files. `lazy-vault` encrypts your secrets so you can safely commit them to Git.
8
9
 
9
- `lazy-vault` is a simple CLI tool that lets you **encrypt `.env` files**, commit them to Git safely, and **sync secrets across machines** using a password you control.
10
-
11
- No cloud. No accounts. No lock-in.
10
+ Now with **Smart Profiles** and **Project Configuration**.
12
11
 
13
12
  ---
14
13
 
15
- ## Why Lazy Vault?
16
-
17
- Environment variables are:
14
+ ## What is lazy-vault?
18
15
 
19
- - Critical
20
- - Sensitive
21
- - Painful to share across machines and teams
16
+ `lazy-vault` is a CLI tool for **secure environment variable management**:
22
17
 
23
- `.env` files don’t belong in Git — but **encrypted `.env` files do**.
18
+ - Encrypt `.env` files
19
+ - Commit encrypted secrets to Git
20
+ - Sync secrets across machines safely
21
+ - Manage multiple environments (dev, prod, staging)
22
+ - Use strong cryptography without complexity
24
23
 
25
- `lazy-vault` solves this by giving you a **password-based, zero-trust workflow**.
24
+ No cloud.
25
+ No accounts.
26
+ No vendor lock-in.
27
+ Your password never leaves your machine.
26
28
 
27
29
  ---
28
30
 
29
31
  ## Core Features
30
32
 
31
33
  - **Strong Encryption**
32
- Uses modern, authenticated encryption with a password-derived key.
34
+ AES-256-GCM + Argon2id (memory-hard key derivation)
35
+
36
+ - **Git-Safe Workflow**
37
+ Commit `.env.enc`, never `.env`
33
38
 
34
- - **Git-Friendly**
35
- Encrypt once commit `.env.enc` → safely sync anywhere.
39
+ - **Smart Profiles (v2)**
40
+ Security modes for speed vs paranoia
36
41
 
37
- - **Simple CLI Workflow**
38
- Two commands. No config files. No magic.
42
+ - **Project Configuration (v2)**
43
+ Multi-environment support via config file
39
44
 
40
- - **Merge-Aware Syncing**
41
- Remote secrets override conflicts, local-only keys are preserved.
45
+ - **Merge-Safe Syncing**
46
+ Remote secrets override conflicts, local-only keys are preserved
42
47
 
43
- - **Safe by Default**
44
- Automatically adds `.env` to `.gitignore`.
48
+ - **Automation Ready**
49
+ Headless mode for CI/CD and deployments
45
50
 
46
51
  ---
47
52
 
@@ -51,7 +56,7 @@ Environment variables are:
51
56
  npm install -g lazy-vault
52
57
  ```
53
58
 
54
- Or use without installing:
59
+ Or without installing:
55
60
 
56
61
  ```bash
57
62
  npx lazy-vault
@@ -59,121 +64,180 @@ npx lazy-vault
59
64
 
60
65
  ---
61
66
 
62
- ## Quick Start
67
+ # Quick Start
63
68
 
64
- ### Encrypt Your `.env`
69
+ ## Initialize (Optional)
70
+
71
+ Create a project config for multi-environment setups:
65
72
 
66
73
  ```bash
67
- lazy-vault lock
74
+ lazy-vault init
68
75
  ```
69
76
 
70
- You will be prompted for a password.
77
+ Creates:
71
78
 
72
- This will:
79
+ ```json
80
+ lazy.config.json
81
+ ```
82
+
83
+ ---
73
84
 
74
- - Encrypt `.env` → `.env.enc`
75
- - Add `.env` to `.gitignore`
76
- - Leave `.env.enc` safe to commit
85
+ ## Lock (Encrypt)
86
+
87
+ When you add new secrets:
77
88
 
78
89
  ```bash
79
- git add .env.enc
80
- git commit -m "Add encrypted env"
90
+ lazy-vault lock
81
91
  ```
82
92
 
93
+ ### What it does:
94
+
95
+ - Encrypts `.env` → `.env.enc`
96
+ - Uses **AES-256-GCM + Argon2id**
97
+ - Adds `.env` to `.gitignore`
98
+ - Safe to commit `.env.enc`
99
+
83
100
  ---
84
101
 
85
- ### Sync on Another Machine
102
+ ## Sync (Decrypt & Merge)
103
+
104
+ When pulling code or deploying:
86
105
 
87
106
  ```bash
88
107
  lazy-vault sync
89
108
  ```
90
109
 
91
- - Enter the same password
92
- - `.env` will be created or merged automatically
110
+ ### What it does:
93
111
 
94
- ---
112
+ - Decrypts `.env.enc`
113
+ - Merges into `.env`
114
+
115
+ **Smart Merge Logic:**
95
116
 
96
- ## How It Works (High Level)
117
+ - Remote keys overwrite local conflicts
118
+ - Local-only keys are preserved
97
119
 
98
- 1. Your password is converted into a cryptographic key using a memory-hard algorithm
99
- 2. `.env` is encrypted using authenticated encryption
100
- 3. The encrypted file (`.env.enc`) contains:
101
- - A random salt
102
- - A random IV
103
- - An authentication tag
104
- - The encrypted payload
120
+ ---
121
+
122
+ # Configuration & Profiles (v2)
123
+
124
+ ## Project Configuration
125
+
126
+ `lazy.config.json`
127
+
128
+ ```json
129
+ {
130
+ "default": {
131
+ "source": ".env",
132
+ "output": ".env.enc",
133
+ "security": "light"
134
+ },
135
+ "production": {
136
+ "source": ".env.prod",
137
+ "output": ".env.prod.enc",
138
+ "security": "heavy"
139
+ }
140
+ }
141
+ ```
105
142
 
106
- 4. On sync, the file is decrypted and merged safely
143
+ Now you can run:
107
144
 
108
- **Your password is never stored. Ever.**
145
+ ```bash
146
+ lazy-vault lock production
147
+ lazy-vault sync production
148
+ ```
109
149
 
110
150
  ---
111
151
 
112
- ## Security Model
152
+ ## Security Profiles
113
153
 
114
- - **Zero-Knowledge**:
115
- `lazy-vault` cannot recover your password.
154
+ Trade speed for paranoia.
116
155
 
117
- - **Authenticated Encryption**:
118
- Tampered or corrupted files will fail to decrypt.
156
+ ### Light (default)
119
157
 
120
- - **Local-Only Secrets**:
121
- All encryption happens on your machine.
158
+ - Fast (~0.5s)
159
+ - Optimized for frequent dev usage
122
160
 
123
- **Important**
124
- If you lose your password, your secrets cannot be recovered.
161
+ ### Heavy
162
+
163
+ - Slow (~1s+)
164
+ - Uses ~256MB RAM
165
+ - GPU-resistant
166
+ - Designed for production secrets
167
+
168
+ ```bash
169
+ lazy-vault lock --profile heavy
170
+ ```
125
171
 
126
172
  ---
127
173
 
128
- ## Supported `.env` Format
174
+ # Automation & CI (Headless Mode)
175
+
176
+ For scripts, pipelines, and deployments:
129
177
 
130
- - Simple `KEY=value` pairs
131
- - Comments (`#`) are ignored
132
- - Quotes are supported
178
+ ```bash
179
+ export LAZY_VAULT_PASSWORD="your-secure-password"
180
+ lazy-vault sync
181
+ ```
133
182
 
134
- ```env
135
- DATABASE_URL="postgres://localhost/db"
136
- API_KEY=super-secret
183
+ PowerShell:
184
+
185
+ ```powershell
186
+ $env:LAZY_VAULT_PASSWORD="your-secure-password"
187
+ lazy-vault sync
137
188
  ```
138
189
 
139
- Advanced `.env` features (multiline values, shell expansion) are intentionally not supported.
190
+ No interactive prompts.
191
+ Safe for CI/CD.
140
192
 
141
193
  ---
142
194
 
143
- ## Commands
195
+ # 🛠 CLI Reference
144
196
 
145
- ### `lazy-vault lock`
197
+ | Command | Description |
198
+ | ------------ | --------------------------- |
199
+ | `init` | Create `lazy.config.json` |
200
+ | `lock [env]` | Encrypt environment |
201
+ | `sync [env]` | Decrypt & merge environment |
146
202
 
147
- Encrypts `.env` into `.env.enc`.
203
+ ### Flags
148
204
 
149
- ```bash
150
- lazy-vault lock
151
- ```
205
+ | Flag | Description |
206
+ | ---------------------- | ------------------------------------ |
207
+ | `-p, --profile <mode>` | Security profile (`light` / `heavy`) |
208
+ | `-i, --input <path>` | Input file override |
209
+ | `-o, --output <path>` | Output file override |
152
210
 
153
211
  ---
154
212
 
155
- ### `lazy-vault sync`
213
+ # Security Model
156
214
 
157
- Decrypts `.env.enc` and merges it into `.env`.
215
+ - Zero-knowledge encryption
216
+ - Local-only cryptography
217
+ - Authenticated encryption (tamper detection)
218
+ - No password storage
219
+ - No recovery backdoors
158
220
 
159
- ```bash
160
- lazy-vault sync
161
- ```
221
+ > If you lose your password, your secrets **cannot be recovered**.
222
+
223
+ This is by design.
162
224
 
163
225
  ---
164
226
 
165
- ## Contributing
227
+ # 🤝 Contributing
166
228
 
167
229
  Contributions are welcome.
168
230
 
169
231
  1. Fork the repo
170
232
  2. Create a feature branch
171
- 3. Open a pull request
233
+ 3. Open a PR
172
234
 
173
- Security-related issues should be reported responsibly.
235
+ Security issues should be reported responsibly.
174
236
 
175
237
  ---
176
238
 
177
- ## 📄 License
239
+ # 📄 License
178
240
 
179
241
  MIT License © ghost
242
+
243
+ ---
package/dist/cli/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/bin.ts
4
- import { Command as Command3 } from "commander";
4
+ import { Command as Command4 } from "commander";
5
5
 
6
6
  // src/cli/commands/sync.ts
7
7
  import { Command } from "commander";
@@ -10,43 +10,32 @@ import inquirer from "inquirer";
10
10
 
11
11
  // src/lib/storage.ts
12
12
  import fs from "fs-extra";
13
-
14
- // src/constants/index.ts
15
- var ENC_FILE = ".env.enc";
16
- var RAW_FILE = ".env";
17
-
18
- // src/lib/storage.ts
19
- async function hasRawEnv() {
20
- return fs.pathExists(RAW_FILE);
21
- }
22
- async function hasEncEnv() {
23
- return fs.pathExists(ENC_FILE);
13
+ import path from "path";
14
+ async function exists(filePath) {
15
+ return fs.pathExists(filePath);
24
16
  }
25
- async function readRawEnv() {
26
- if (!await hasRawEnv()) return "";
27
- return fs.readFile(RAW_FILE, "utf-8");
28
- }
29
- async function readEncEnv() {
30
- if (!await hasEncEnv()) {
31
- throw new Error("No encrypted .env.enc file found.");
17
+ async function readFile(filePath) {
18
+ if (!await exists(filePath)) {
19
+ throw new Error(`File not found: ${filePath}`);
32
20
  }
33
- return fs.readFile(ENC_FILE, "utf-8");
34
- }
35
- async function writeRawEnv(content) {
36
- await fs.writeFile(RAW_FILE, content, "utf-8");
21
+ return fs.readFile(filePath, "utf-8");
37
22
  }
38
- async function writeEncEnv(content) {
39
- await fs.writeFile(ENC_FILE, content, "utf-8");
23
+ async function writeFile(filePath, content) {
24
+ await fs.writeFile(filePath, content, "utf-8");
40
25
  }
41
- async function ensureGitIgnore() {
26
+ async function addToGitIgnore(filePath) {
42
27
  const gitignorePath = ".gitignore";
43
- const ignoreRule = "\n# Added by lazy-vault\n.env\n";
28
+ const filename = path.basename(filePath);
29
+ const ignoreRule = `
30
+ ${filename}`;
31
+ if (filename.endsWith(".enc")) return;
44
32
  if (!await fs.pathExists(gitignorePath)) {
45
- await fs.writeFile(gitignorePath, ignoreRule);
33
+ await fs.writeFile(gitignorePath, `# Added by lazy-vault${ignoreRule}
34
+ `);
46
35
  return;
47
36
  }
48
37
  const content = await fs.readFile(gitignorePath, "utf-8");
49
- if (!content.includes(".env")) {
38
+ if (!content.includes(filename)) {
50
39
  await fs.appendFile(gitignorePath, ignoreRule);
51
40
  }
52
41
  }
@@ -54,9 +43,20 @@ async function ensureGitIgnore() {
54
43
  // src/lib/crypto.ts
55
44
  import crypto from "crypto";
56
45
  import argon2 from "argon2";
46
+
47
+ // src/constants/index.ts
57
48
  var ALGORITHM = "aes-256-gcm";
58
- var VERSION = "v1";
59
- async function encrypt(text, password) {
49
+ var CONFIG_FILE = "lazy.config.json";
50
+ var PROFILES = {
51
+ // Good for CI/CD and frequent locking
52
+ light: { timeCost: 3, memoryCost: 64 * 1024, parallelism: 1 },
53
+ // Good for long-term storage or high-value keys
54
+ heavy: { timeCost: 10, memoryCost: 256 * 1024, parallelism: 4 }
55
+ };
56
+
57
+ // src/lib/crypto.ts
58
+ async function encrypt(text, password, profileName = "light") {
59
+ const settings = PROFILES[profileName];
60
60
  const salt = crypto.randomBytes(16);
61
61
  const iv = crypto.randomBytes(16);
62
62
  const key = await argon2.hash(password, {
@@ -64,52 +64,60 @@ async function encrypt(text, password) {
64
64
  salt,
65
65
  raw: true,
66
66
  hashLength: 32,
67
- timeCost: 3,
68
- memoryCost: 65536,
69
- parallelism: 1
67
+ timeCost: settings.timeCost,
68
+ memoryCost: settings.memoryCost,
69
+ parallelism: settings.parallelism
70
70
  });
71
- if (ALGORITHM !== "aes-256-gcm") {
72
- throw new Error("Unsupported encryption algorithm");
73
- }
74
71
  const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
75
72
  const encrypted = Buffer.concat([
76
73
  cipher.update(text, "utf8"),
77
74
  cipher.final()
78
75
  ]);
79
76
  const authTag = cipher.getAuthTag();
77
+ const envelope = {
78
+ v: 2,
79
+ mode: "password",
80
+ profile: profileName,
81
+ ops: {
82
+ mem: settings.memoryCost,
83
+ time: settings.timeCost,
84
+ parallel: settings.parallelism
85
+ },
86
+ salt: salt.toString("hex"),
87
+ iv: iv.toString("hex"),
88
+ tag: authTag.toString("hex"),
89
+ data: encrypted.toString("hex")
90
+ };
80
91
  key.fill(0);
81
- return `${VERSION}:${salt.toString("hex")}:${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted.toString("hex")}`;
92
+ return JSON.stringify(envelope, null, 2);
82
93
  }
83
- async function decrypt(encryptedText, password) {
84
- const parts = encryptedText.split(":");
85
- if (parts.length !== 5) {
86
- throw new Error("Invalid encrypted file format");
87
- }
88
- const [version, saltHex, ivHex, authTagHex, cipherTextHex] = parts;
89
- if (version !== "v1") {
90
- throw new Error(`Unsupported encrypted format version: ${version}`);
94
+ async function decrypt(content, password) {
95
+ try {
96
+ const envelope = JSON.parse(content);
97
+ const key = await argon2.hash(password, {
98
+ type: argon2.argon2id,
99
+ salt: Buffer.from(envelope.salt, "hex"),
100
+ raw: true,
101
+ hashLength: 32,
102
+ timeCost: envelope.ops.time,
103
+ memoryCost: envelope.ops.mem,
104
+ parallelism: envelope.ops.parallel
105
+ });
106
+ const decipher = crypto.createDecipheriv(
107
+ ALGORITHM,
108
+ key,
109
+ Buffer.from(envelope.iv, "hex")
110
+ );
111
+ decipher.setAuthTag(Buffer.from(envelope.tag, "hex"));
112
+ const decrypted = Buffer.concat([
113
+ decipher.update(Buffer.from(envelope.data, "hex")),
114
+ decipher.final()
115
+ ]);
116
+ key.fill(0);
117
+ return decrypted.toString("utf8");
118
+ } catch (error) {
119
+ throw new Error(`Decryption failed: ${error.message}`);
91
120
  }
92
- const salt = Buffer.from(saltHex, "hex");
93
- const iv = Buffer.from(ivHex, "hex");
94
- const authTag = Buffer.from(authTagHex, "hex");
95
- const cipherText = Buffer.from(cipherTextHex, "hex");
96
- const key = await argon2.hash(password, {
97
- type: argon2.argon2id,
98
- salt,
99
- raw: true,
100
- hashLength: 32,
101
- timeCost: 3,
102
- memoryCost: 65536,
103
- parallelism: 1
104
- });
105
- const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
106
- decipher.setAuthTag(authTag);
107
- const decrypted = Buffer.concat([
108
- decipher.update(cipherText),
109
- decipher.final()
110
- ]);
111
- key.fill(0);
112
- return decrypted.toString("utf8");
113
121
  }
114
122
 
115
123
  // src/lib/parser.ts
@@ -147,42 +155,92 @@ function merge(local, remote) {
147
155
  return { ...local, ...remote };
148
156
  }
149
157
 
158
+ // src/lib/config.ts
159
+ import fs2 from "fs-extra";
160
+ import "path";
161
+ var DEFAULTS = {
162
+ source: ".env",
163
+ output: ".env.env",
164
+ security: "light"
165
+ };
166
+ async function loadConfig(envName = "default") {
167
+ if (!await fs2.pathExists(CONFIG_FILE)) {
168
+ return DEFAULTS;
169
+ }
170
+ try {
171
+ const raw = await fs2.readFile(CONFIG_FILE, "utf-8");
172
+ const config = JSON.parse(raw);
173
+ if (!config[envName]) {
174
+ if (envName === "default") return DEFAULTS;
175
+ throw new Error(`Environment "${envName}" not found in ${CONFIG_FILE}`);
176
+ }
177
+ return { ...DEFAULTS, ...config[envName] };
178
+ } catch (error) {
179
+ throw new Error(`Failed to parse ${CONFIG_FILE}: ${error.message}`);
180
+ }
181
+ }
182
+ async function createTemplateConfig() {
183
+ const template = {
184
+ default: { source: ".env", output: ".env.enc", security: "light" },
185
+ production: {
186
+ source: ".env.prod",
187
+ output: ".env.prod.enc",
188
+ security: "heavy"
189
+ }
190
+ };
191
+ await fs2.writeJson(CONFIG_FILE, template, { spaces: 2 });
192
+ }
193
+
150
194
  // src/cli/commands/sync.ts
151
- var syncCommand = new Command("sync").description("Decrypts .env.enc and merges it with local .env").action(async () => {
195
+ var syncCommand = new Command("sync").description("Decrypts .env.enc and merges it with local .env").argument("[env]", "Environment name", "default").option("-i, --input <path>", "Override encrypted file path").option("-o, --output <path>", "Override target .env path").action(async (envName, options) => {
152
196
  try {
153
- console.log(chalk.blue("Preparing to Sync (Decrypt)..."));
154
- if (!await hasEncEnv()) {
155
- console.error(chalk.red("Error: No .env.enc file found."));
197
+ console.log(chalk.blue("Preparing to Sync..."));
198
+ const fileConfig = await loadConfig(envName);
199
+ const encryptedPath = options.input || fileConfig.output;
200
+ const rawPath = options.output || fileConfig.source;
201
+ console.log(chalk.blue(` Syncing [${envName}]...`));
202
+ console.log(chalk.gray(` Source (Encrypted): ${encryptedPath}`));
203
+ console.log(chalk.gray(` Target (Raw): ${rawPath}`));
204
+ if (!await exists(encryptedPath)) {
205
+ console.error(chalk.red(`Error: File ${encryptedPath} not found.`));
156
206
  process.exit(1);
157
207
  }
158
- const answers = await inquirer.prompt([
159
- {
160
- type: "password",
161
- name: "password",
162
- message: "Enter password to decrypt:",
163
- mask: "*"
164
- }
165
- ]);
166
- const password = answers.password;
167
- const encryptedContent = await readEncEnv();
208
+ let password = process.env.LAZY_ENV_PASSWORD;
209
+ if (!password) {
210
+ const answers = await inquirer.prompt([
211
+ {
212
+ type: "password",
213
+ name: "password",
214
+ message: "Enter password to decrypt:",
215
+ mask: "*"
216
+ }
217
+ ]);
218
+ password = answers.password;
219
+ } else {
220
+ console.log(chalk.yellow(" Using password from LAZY_ENV_PASSWORD"));
221
+ }
222
+ const encryptedContent = await readFile(encryptedPath);
168
223
  let decryptedRaw = "";
169
224
  try {
170
- decryptedRaw = await decrypt(encryptedContent, password);
225
+ decryptedRaw = await decrypt(
226
+ encryptedContent,
227
+ password
228
+ );
171
229
  } catch (e) {
172
230
  throw new Error("Invalid password or corrupted file.");
173
231
  }
174
232
  const remoteObj = parse(decryptedRaw);
175
233
  let finalObj = remoteObj;
176
- let actionMsg = "Created new .env";
177
- if (await hasRawEnv()) {
178
- console.log(chalk.gray("Local .env found. Merging..."));
179
- const localRaw = await readRawEnv();
234
+ let actionMsg = `Created new ${rawPath}`;
235
+ if (await exists(rawPath)) {
236
+ console.log(chalk.gray(` Local ${rawPath} found. Merging...`));
237
+ const localRaw = await readFile(rawPath);
180
238
  const localObj = parse(localRaw);
181
239
  finalObj = merge(localObj, remoteObj);
182
240
  actionMsg = "Merged with local .env";
183
241
  }
184
242
  const finalString = stringify(finalObj);
185
- await writeRawEnv(finalString);
243
+ await writeFile(rawPath, finalString);
186
244
  console.log(chalk.green(`
187
245
  Success! ${actionMsg}`));
188
246
  console.log(
@@ -195,43 +253,62 @@ Success! ${actionMsg}`));
195
253
  });
196
254
 
197
255
  // src/cli/commands/lock.ts
198
- import { Command as Command2 } from "commander";
256
+ import { Command as Command2, Option } from "commander";
199
257
  import chalk2 from "chalk";
200
258
  import inquirer2 from "inquirer";
201
- var lockCommand = new Command2("lock").description("Encrypts local .env file and saves it to env.enc").action(async () => {
259
+ var lockCommand = new Command2("lock").description("Encrypts local .env file and saves it to env.enc").argument("[env]", "Environment name (default, production, etc)", "default").option("-i, --input <path>", "Override input path").option("-o, --output <path>", "Override output path").addOption(
260
+ new Option("-p, --profile <mode>", "Override security profile").choices([
261
+ "light",
262
+ "heavy"
263
+ ])
264
+ ).action(async (envName, options) => {
202
265
  try {
203
- console.log(chalk2.blue("Preparing to lock (Encrypt)..."));
204
- if (!await hasRawEnv()) {
205
- console.error(chalk2.red("Error: No .env file found to encrypt."));
266
+ console.log(chalk2.blue("Preparing to lock ..."));
267
+ const fileConfig = await loadConfig(envName);
268
+ const sourcePath = options.input || fileConfig.source;
269
+ const outputPath = options.output || fileConfig.output;
270
+ const securityProfile = options.profile || fileConfig.security;
271
+ console.log(chalk2.blue(` Locking [${envName}]...`));
272
+ console.log(chalk2.gray(` Source: ${sourcePath}`));
273
+ console.log(chalk2.gray(` Target: ${outputPath}`));
274
+ console.log(chalk2.gray(` Profile: ${securityProfile}`));
275
+ if (!await exists(sourcePath)) {
276
+ console.error(chalk2.red(`Error: File ${sourcePath} not found.`));
206
277
  process.exit(1);
207
278
  }
208
- const answers = await inquirer2.prompt([
209
- {
210
- type: "password",
211
- name: "password",
212
- message: "Enter Password to encrypt: ",
213
- mask: "*",
214
- validate: (input) => input.length > 0 ? true : "Password cannot be empty."
215
- },
216
- {
217
- type: "password",
218
- name: "passwordConfirm",
219
- message: "Confirm password:",
220
- mask: "*",
221
- validate: (input, answers2) => input === answers2.password || "Passwords do not match."
279
+ let password = process.env.LAZY_ENV_PASSWORD;
280
+ if (!password) {
281
+ const answers = await inquirer2.prompt([
282
+ {
283
+ type: "password",
284
+ name: "password",
285
+ message: "Enter Password to encrypt: ",
286
+ mask: "*",
287
+ validate: (input) => input.length > 0 ? true : "Password cannot be empty."
288
+ },
289
+ {
290
+ type: "password",
291
+ name: "passwordConfirm",
292
+ message: "Confirm password:",
293
+ mask: "*"
294
+ }
295
+ ]);
296
+ if (answers.password !== answers.passwordConfirm) {
297
+ console.error(chalk2.red("\n Error: Passwords do not match."));
298
+ process.exit(1);
222
299
  }
223
- ]);
224
- const password = answers.password;
225
- const rawContent = await readRawEnv();
226
- const parsed = parse(rawContent);
227
- const keysCount = Object.keys(parsed).length;
228
- const encryptedData = await encrypt(rawContent, password);
229
- await writeEncEnv(encryptedData);
230
- await ensureGitIgnore();
231
- console.log(
232
- chalk2.green(`
233
- Success! Encrypted ${keysCount} secrets to .env.enc`)
300
+ password = answers.password;
301
+ }
302
+ const rawContent = await readFile(sourcePath);
303
+ const encryptedData = await encrypt(
304
+ rawContent,
305
+ password,
306
+ securityProfile
234
307
  );
308
+ await writeFile(outputPath, encryptedData);
309
+ await addToGitIgnore(sourcePath);
310
+ console.log(chalk2.green(`
311
+ Success! Encrypted secrets to ${outputPath}`));
235
312
  console.log(chalk2.gray("You can now commit .env.enc to Git."));
236
313
  } catch (error) {
237
314
  console.error(chalk2.red("\n Failed:"), error.message);
@@ -240,14 +317,32 @@ Success! Encrypted ${keysCount} secrets to .env.enc`)
240
317
  });
241
318
 
242
319
  // src/cli/bin.ts
320
+ import chalk4 from "chalk";
321
+
322
+ // src/cli/commands/init.ts
323
+ import { Command as Command3 } from "commander";
243
324
  import chalk3 from "chalk";
244
- var program = new Command3();
325
+ import fs3 from "fs-extra";
326
+ var initCommand = new Command3("init").description("Creates a lazy.config.json file").action(async () => {
327
+ if (await fs3.pathExists(CONFIG_FILE)) {
328
+ console.log(chalk3.yellow("lazy.config.json already exists."));
329
+ return;
330
+ }
331
+ await createTemplateConfig();
332
+ console.log(chalk3.green(`
333
+ Created ${CONFIG_FILE}`));
334
+ console.log(chalk3.gray('You can now run "lazy-env lock production"'));
335
+ });
336
+
337
+ // src/cli/bin.ts
338
+ var program = new Command4();
245
339
  program.name("lazy-vault").description("A secure, simple way to manage encrypted .env files in Git").version("1.0.0");
246
340
  program.addCommand(syncCommand);
247
341
  program.addCommand(lockCommand);
342
+ program.addCommand(initCommand);
248
343
  program.on("command:*", () => {
249
344
  console.error(
250
- chalk3.red(
345
+ chalk4.red(
251
346
  "Invalid command: %s\nSee --help for a list of available commands."
252
347
  ),
253
348
  program.args.join(" ")
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/bin.ts","../../src/cli/commands/sync.ts","../../src/lib/storage.ts","../../src/constants/index.ts","../../src/lib/crypto.ts","../../src/lib/parser.ts","../../src/cli/commands/lock.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport { Command } from \"commander\";\r\nimport { syncCommand } from \"./commands/sync\";\r\nimport { lockCommand } from \"./commands/lock\";\r\nimport chalk from \"chalk\";\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name(\"lazy-vault\")\r\n .description(\"A secure, simple way to manage encrypted .env files in Git\")\r\n .version(\"1.0.0\");\r\n\r\nprogram.addCommand(syncCommand);\r\nprogram.addCommand(lockCommand);\r\n\r\nprogram.on(\"command:*\", () => {\r\n console.error(\r\n chalk.red(\r\n \"Invalid command: %s\\nSee --help for a list of available commands.\",\r\n ),\r\n program.args.join(\" \"),\r\n );\r\n process.exit(1);\r\n});\r\n\r\nprogram.parse(process.argv);\r\n\r\nif (!process.argv.slice(2).length) {\r\n program.outputHelp();\r\n}\r\n","import { Command } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport inquirer from \"inquirer\";\r\n\r\nimport * as storage from \"../../lib/storage\";\r\nimport * as crypto from \"../../lib/crypto\";\r\nimport * as parser from \"../../lib/parser\";\r\n\r\nexport const syncCommand = new Command(\"sync\")\r\n .description(\"Decrypts .env.enc and merges it with local .env\")\r\n .action(async () => {\r\n try {\r\n console.log(chalk.blue(\"Preparing to Sync (Decrypt)...\"));\r\n\r\n if (!(await storage.hasEncEnv())) {\r\n console.error(chalk.red(\"Error: No .env.enc file found.\"));\r\n process.exit(1);\r\n }\r\n\r\n const answers = await inquirer.prompt([\r\n {\r\n type: \"password\",\r\n name: \"password\",\r\n message: \"Enter password to decrypt:\",\r\n mask: \"*\",\r\n },\r\n ]);\r\n\r\n const password = answers.password;\r\n\r\n const encryptedContent = await storage.readEncEnv();\r\n let decryptedRaw = \"\";\r\n\r\n try {\r\n decryptedRaw = await crypto.decrypt(encryptedContent, password);\r\n } catch (e) {\r\n throw new Error(\"Invalid password or corrupted file.\");\r\n }\r\n\r\n const remoteObj = parser.parse(decryptedRaw);\r\n\r\n let finalObj = remoteObj;\r\n let actionMsg = \"Created new .env\";\r\n\r\n if (await storage.hasRawEnv()) {\r\n console.log(chalk.gray(\"Local .env found. Merging...\"));\r\n const localRaw = await storage.readRawEnv();\r\n const localObj = parser.parse(localRaw);\r\n\r\n // MERGE: Remote wins conflicts, Local keeps unique keys\r\n finalObj = parser.merge(localObj, remoteObj);\r\n actionMsg = \"Merged with local .env\";\r\n }\r\n\r\n const finalString = parser.stringify(finalObj);\r\n await storage.writeRawEnv(finalString);\r\n\r\n console.log(chalk.green(`\\nSuccess! ${actionMsg}`));\r\n console.log(\r\n chalk.white(\"Keys in final .env: \") +\r\n chalk.yellow(Object.keys(finalObj).length),\r\n );\r\n } catch (error: any) {\r\n console.error(chalk.red(\"\\nFailed:\"), error.message);\r\n process.exit(1);\r\n }\r\n });\r\n","import fs from \"fs-extra\";\r\nimport { ENC_FILE, RAW_FILE } from \"../constants\";\r\n\r\n/**\r\n * Checks if the raw .env file exists.\r\n * @return True if the file exists, false otherwise\r\n */\r\nexport async function hasRawEnv(): Promise<boolean> {\r\n return fs.pathExists(RAW_FILE);\r\n}\r\n\r\n/**\r\n * Checks if the encrypted .env file exists.\r\n * @return True if the file exists, false otherwise\r\n */\r\nexport async function hasEncEnv(): Promise<boolean> {\r\n return fs.pathExists(ENC_FILE);\r\n}\r\n\r\n/**\r\n * Reads the RAW .env file.\r\n * @return The content of the raw .env file\r\n */\r\nexport async function readRawEnv(): Promise<string> {\r\n if (!(await hasRawEnv())) return \"\";\r\n return fs.readFile(RAW_FILE, \"utf-8\");\r\n}\r\n\r\n/**\r\n * Reads the ENCRYPTED .env.enc file.\r\n * @return The content of the encrypted .env.enc file\r\n */\r\nexport async function readEncEnv(): Promise<string> {\r\n if (!(await hasEncEnv())) {\r\n throw new Error(\"No encrypted .env.enc file found.\");\r\n }\r\n return fs.readFile(ENC_FILE, \"utf-8\");\r\n}\r\n\r\n/**\r\n * Writes data to the RAW .env file.\r\n * @param content The content to write to the raw .env file\r\n * @return A promise that resolves when the write is complete\r\n */\r\nexport async function writeRawEnv(content: string): Promise<void> {\r\n await fs.writeFile(RAW_FILE, content, \"utf-8\");\r\n}\r\n\r\n/**\r\n * Writes data to the ENCRYPTED .env.enc file.\r\n * @param content The content to write to the encrypted .env.enc file\r\n * @return A promise that resolves when the write is complete\r\n */\r\nexport async function writeEncEnv(content: string): Promise<void> {\r\n await fs.writeFile(ENC_FILE, content, \"utf-8\");\r\n}\r\n\r\n/**\r\n * Adds .env to .gitignore if it's missing.\r\n * This is a safety feature to prevent accidental commits.\r\n */\r\nexport async function ensureGitIgnore(): Promise<void> {\r\n const gitignorePath = \".gitignore\";\r\n const ignoreRule = \"\\n# Added by lazy-vault\\n.env\\n\";\r\n\r\n if (!(await fs.pathExists(gitignorePath))) {\r\n await fs.writeFile(gitignorePath, ignoreRule);\r\n return;\r\n }\r\n\r\n const content = await fs.readFile(gitignorePath, \"utf-8\");\r\n if (!content.includes(\".env\")) {\r\n await fs.appendFile(gitignorePath, ignoreRule);\r\n }\r\n}\r\n","export const ALGORITHM = \"aes-256-gcm\";\r\nexport const ENC_FILE = \".env.enc\";\r\nexport const RAW_FILE = \".env\";\r\n","import crypto from \"node:crypto\";\r\nimport argon2 from \"argon2\";\r\n\r\nconst ALGORITHM = \"aes-256-gcm\" as const;\r\nconst VERSION = \"v1\";\r\n\r\n/**\r\n * Encrypts a raw string using a password.\r\n * Format: salt:iv:authTag:cipherText\r\n * @param text The string to encrypt\r\n * @param password The password to use for encryption\r\n */\r\n\r\nexport async function encrypt(text: string, password: string): Promise<string> {\r\n const salt = crypto.randomBytes(16);\r\n const iv = crypto.randomBytes(16);\r\n\r\n const key = await argon2.hash(password, {\r\n type: argon2.argon2id,\r\n salt: salt,\r\n raw: true,\r\n hashLength: 32,\r\n timeCost: 3,\r\n memoryCost: 65536,\r\n parallelism: 1,\r\n });\r\n\r\n if (ALGORITHM !== \"aes-256-gcm\") {\r\n throw new Error(\"Unsupported encryption algorithm\");\r\n }\r\n\r\n const cipher = crypto.createCipheriv(ALGORITHM, key, iv);\r\n\r\n const encrypted = Buffer.concat([\r\n cipher.update(text, \"utf8\"),\r\n cipher.final(),\r\n ]);\r\n const authTag = cipher.getAuthTag();\r\n\r\n key.fill(0);\r\n\r\n return `${VERSION}:${salt.toString(\"hex\")}:${iv.toString(\"hex\")}:${authTag.toString(\"hex\")}:${encrypted.toString(\"hex\")}`;\r\n}\r\n\r\n/** * Decrypts an encrypted string using a password.\r\n * @param encryptedText The encrypted string to decrypt\r\n * @param password The password to use for decryption\r\n * @return The decrypted string\r\n */\r\nexport async function decrypt(\r\n encryptedText: string,\r\n password: string,\r\n): Promise<string> {\r\n const parts = encryptedText.split(\":\");\r\n\r\n if (parts.length !== 5) {\r\n throw new Error(\"Invalid encrypted file format\");\r\n }\r\n const [version, saltHex, ivHex, authTagHex, cipherTextHex] = parts as [\r\n string,\r\n string,\r\n string,\r\n string,\r\n string,\r\n ];\r\n\r\n if (version !== \"v1\") {\r\n throw new Error(`Unsupported encrypted format version: ${version}`);\r\n }\r\n const salt = Buffer.from(saltHex, \"hex\");\r\n const iv = Buffer.from(ivHex, \"hex\");\r\n const authTag = Buffer.from(authTagHex, \"hex\");\r\n const cipherText = Buffer.from(cipherTextHex, \"hex\");\r\n\r\n const key = await argon2.hash(password, {\r\n type: argon2.argon2id,\r\n salt: salt,\r\n raw: true,\r\n hashLength: 32,\r\n timeCost: 3,\r\n memoryCost: 65536,\r\n parallelism: 1,\r\n });\r\n\r\n const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);\r\n decipher.setAuthTag(authTag);\r\n\r\n const decrypted = Buffer.concat([\r\n decipher.update(cipherText),\r\n decipher.final(),\r\n ]);\r\n\r\n key.fill(0);\r\n\r\n return decrypted.toString(\"utf8\");\r\n}\r\n","import type { EnvObject } from \"..\";\r\n\r\n/**\r\n * Parses a raw .env string into a Key-Value object.\r\n * Ignores comments and empty lines.\r\n * @param content The raw .env string\r\n * @return An object representing the parsed .env variables\r\n */\r\n\r\nexport function parse(content: string): EnvObject {\r\n const result: EnvObject = {};\r\n const lines = content.split(/\\r?\\n/);\r\n\r\n for (const line of lines) {\r\n const trimmedLine = line.trim();\r\n if (trimmedLine === \"\" || trimmedLine.startsWith(\"#\")) {\r\n continue;\r\n }\r\n\r\n if (!trimmedLine.includes(\"=\")) {\r\n throw new Error(`Invalid env line (missing '='): ${line}`);\r\n }\r\n\r\n const [rawKey, ...valueParts] = trimmedLine.split(\"=\") as [string, any];\r\n\r\n const key = rawKey.trim();\r\n if (!key) {\r\n throw new Error(`Invalid env line (empty key): ${line}`);\r\n }\r\n\r\n const value = valueParts.join(\"=\");\r\n const cleanValue = value.replace(/^['\"](.*)['\"]$/, \"$1\").trim();\r\n\r\n result[key] = cleanValue;\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * Stringifies a Key-Value object into a raw .env string.\r\n * @param envObject The object representing .env variables\r\n * @return A raw .env string\r\n */\r\nexport function stringify(envObject: EnvObject): string {\r\n let result = \"\";\r\n for (const [key, value] of Object.entries(envObject)) {\r\n result += `${key}=${value}\\n`;\r\n }\r\n\r\n return result.trimEnd();\r\n}\r\n\r\n/**\r\n * Merge logic\r\n * Remote keys override local keys if they exist\r\n * Unique keys from both are preserved\r\n * @param local The local .env object\r\n * @param remote The remote .env object\r\n */\r\n\r\nexport function merge(local: EnvObject, remote: EnvObject): EnvObject {\r\n return { ...local, ...remote };\r\n}\r\n","import { Command } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport inquirer, { type Answers } from \"inquirer\";\r\n\r\nimport * as storage from \"../../lib/storage\";\r\nimport * as crypto from \"../../lib/crypto\";\r\nimport { parse } from \"../../lib/parser\";\r\n\r\nexport const lockCommand = new Command(\"lock\")\r\n .description(\"Encrypts local .env file and saves it to env.enc\")\r\n .action(async () => {\r\n try {\r\n console.log(chalk.blue(\"Preparing to lock (Encrypt)...\"));\r\n\r\n if (!(await storage.hasRawEnv())) {\r\n console.error(chalk.red(\"Error: No .env file found to encrypt.\"));\r\n process.exit(1);\r\n }\r\n\r\n const answers = await inquirer.prompt([\r\n {\r\n type: \"password\",\r\n name: \"password\",\r\n message: \"Enter Password to encrypt: \",\r\n mask: \"*\",\r\n validate: (input) =>\r\n input.length > 0 ? true : \"Password cannot be empty.\",\r\n },\r\n {\r\n type: \"password\",\r\n name: \"passwordConfirm\",\r\n message: \"Confirm password:\",\r\n mask: \"*\",\r\n validate: (input: string, answers: Answers) =>\r\n input === answers.password || \"Passwords do not match.\",\r\n },\r\n ]);\r\n\r\n const password = answers.password;\r\n\r\n const rawContent = await storage.readRawEnv();\r\n const parsed = parse(rawContent);\r\n const keysCount = Object.keys(parsed).length;\r\n\r\n const encryptedData = await crypto.encrypt(rawContent, password);\r\n await storage.writeEncEnv(encryptedData);\r\n await storage.ensureGitIgnore();\r\n\r\n console.log(\r\n chalk.green(`\\nSuccess! Encrypted ${keysCount} secrets to .env.enc`),\r\n );\r\n console.log(chalk.gray(\"You can now commit .env.enc to Git.\"));\r\n } catch (error: any) {\r\n console.error(chalk.red(\"\\n Failed:\"), error.message);\r\n process.exit(1);\r\n }\r\n });\r\n"],"mappings":";;;AACA,SAAS,WAAAA,gBAAe;;;ACDxB,SAAS,eAAe;AACxB,OAAO,WAAW;AAClB,OAAO,cAAc;;;ACFrB,OAAO,QAAQ;;;ACCR,IAAM,WAAW;AACjB,IAAM,WAAW;;;ADKxB,eAAsB,YAA8B;AAClD,SAAO,GAAG,WAAW,QAAQ;AAC/B;AAMA,eAAsB,YAA8B;AAClD,SAAO,GAAG,WAAW,QAAQ;AAC/B;AAMA,eAAsB,aAA8B;AAClD,MAAI,CAAE,MAAM,UAAU,EAAI,QAAO;AACjC,SAAO,GAAG,SAAS,UAAU,OAAO;AACtC;AAMA,eAAsB,aAA8B;AAClD,MAAI,CAAE,MAAM,UAAU,GAAI;AACxB,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AACA,SAAO,GAAG,SAAS,UAAU,OAAO;AACtC;AAOA,eAAsB,YAAY,SAAgC;AAChE,QAAM,GAAG,UAAU,UAAU,SAAS,OAAO;AAC/C;AAOA,eAAsB,YAAY,SAAgC;AAChE,QAAM,GAAG,UAAU,UAAU,SAAS,OAAO;AAC/C;AAMA,eAAsB,kBAAiC;AACrD,QAAM,gBAAgB;AACtB,QAAM,aAAa;AAEnB,MAAI,CAAE,MAAM,GAAG,WAAW,aAAa,GAAI;AACzC,UAAM,GAAG,UAAU,eAAe,UAAU;AAC5C;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,GAAG,SAAS,eAAe,OAAO;AACxD,MAAI,CAAC,QAAQ,SAAS,MAAM,GAAG;AAC7B,UAAM,GAAG,WAAW,eAAe,UAAU;AAAA,EAC/C;AACF;;;AE1EA,OAAO,YAAY;AACnB,OAAO,YAAY;AAEnB,IAAM,YAAY;AAClB,IAAM,UAAU;AAShB,eAAsB,QAAQ,MAAc,UAAmC;AAC7E,QAAM,OAAO,OAAO,YAAY,EAAE;AAClC,QAAM,KAAK,OAAO,YAAY,EAAE;AAEhC,QAAM,MAAM,MAAM,OAAO,KAAK,UAAU;AAAA,IACtC,MAAM,OAAO;AAAA,IACb;AAAA,IACA,KAAK;AAAA,IACL,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,aAAa;AAAA,EACf,CAAC;AAED,MAAI,cAAc,eAAe;AAC/B,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AAEA,QAAM,SAAS,OAAO,eAAe,WAAW,KAAK,EAAE;AAEvD,QAAM,YAAY,OAAO,OAAO;AAAA,IAC9B,OAAO,OAAO,MAAM,MAAM;AAAA,IAC1B,OAAO,MAAM;AAAA,EACf,CAAC;AACD,QAAM,UAAU,OAAO,WAAW;AAElC,MAAI,KAAK,CAAC;AAEV,SAAO,GAAG,OAAO,IAAI,KAAK,SAAS,KAAK,CAAC,IAAI,GAAG,SAAS,KAAK,CAAC,IAAI,QAAQ,SAAS,KAAK,CAAC,IAAI,UAAU,SAAS,KAAK,CAAC;AACzH;AAOA,eAAsB,QACpB,eACA,UACiB;AACjB,QAAM,QAAQ,cAAc,MAAM,GAAG;AAErC,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AACA,QAAM,CAAC,SAAS,SAAS,OAAO,YAAY,aAAa,IAAI;AAQ7D,MAAI,YAAY,MAAM;AACpB,UAAM,IAAI,MAAM,yCAAyC,OAAO,EAAE;AAAA,EACpE;AACA,QAAM,OAAO,OAAO,KAAK,SAAS,KAAK;AACvC,QAAM,KAAK,OAAO,KAAK,OAAO,KAAK;AACnC,QAAM,UAAU,OAAO,KAAK,YAAY,KAAK;AAC7C,QAAM,aAAa,OAAO,KAAK,eAAe,KAAK;AAEnD,QAAM,MAAM,MAAM,OAAO,KAAK,UAAU;AAAA,IACtC,MAAM,OAAO;AAAA,IACb;AAAA,IACA,KAAK;AAAA,IACL,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,aAAa;AAAA,EACf,CAAC;AAED,QAAM,WAAW,OAAO,iBAAiB,WAAW,KAAK,EAAE;AAC3D,WAAS,WAAW,OAAO;AAE3B,QAAM,YAAY,OAAO,OAAO;AAAA,IAC9B,SAAS,OAAO,UAAU;AAAA,IAC1B,SAAS,MAAM;AAAA,EACjB,CAAC;AAED,MAAI,KAAK,CAAC;AAEV,SAAO,UAAU,SAAS,MAAM;AAClC;;;ACtFO,SAAS,MAAM,SAA4B;AAChD,QAAM,SAAoB,CAAC;AAC3B,QAAM,QAAQ,QAAQ,MAAM,OAAO;AAEnC,aAAW,QAAQ,OAAO;AACxB,UAAM,cAAc,KAAK,KAAK;AAC9B,QAAI,gBAAgB,MAAM,YAAY,WAAW,GAAG,GAAG;AACrD;AAAA,IACF;AAEA,QAAI,CAAC,YAAY,SAAS,GAAG,GAAG;AAC9B,YAAM,IAAI,MAAM,mCAAmC,IAAI,EAAE;AAAA,IAC3D;AAEA,UAAM,CAAC,QAAQ,GAAG,UAAU,IAAI,YAAY,MAAM,GAAG;AAErD,UAAM,MAAM,OAAO,KAAK;AACxB,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,iCAAiC,IAAI,EAAE;AAAA,IACzD;AAEA,UAAM,QAAQ,WAAW,KAAK,GAAG;AACjC,UAAM,aAAa,MAAM,QAAQ,kBAAkB,IAAI,EAAE,KAAK;AAE9D,WAAO,GAAG,IAAI;AAAA,EAChB;AAEA,SAAO;AACT;AAOO,SAAS,UAAU,WAA8B;AACtD,MAAI,SAAS;AACb,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,cAAU,GAAG,GAAG,IAAI,KAAK;AAAA;AAAA,EAC3B;AAEA,SAAO,OAAO,QAAQ;AACxB;AAUO,SAAS,MAAM,OAAkB,QAA8B;AACpE,SAAO,EAAE,GAAG,OAAO,GAAG,OAAO;AAC/B;;;AJvDO,IAAM,cAAc,IAAI,QAAQ,MAAM,EAC1C,YAAY,iDAAiD,EAC7D,OAAO,YAAY;AAClB,MAAI;AACF,YAAQ,IAAI,MAAM,KAAK,gCAAgC,CAAC;AAExD,QAAI,CAAE,MAAc,UAAU,GAAI;AAChC,cAAQ,MAAM,MAAM,IAAI,gCAAgC,CAAC;AACzD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,UAAU,MAAM,SAAS,OAAO;AAAA,MACpC;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,MAAM;AAAA,MACR;AAAA,IACF,CAAC;AAED,UAAM,WAAW,QAAQ;AAEzB,UAAM,mBAAmB,MAAc,WAAW;AAClD,QAAI,eAAe;AAEnB,QAAI;AACF,qBAAe,MAAa,QAAQ,kBAAkB,QAAQ;AAAA,IAChE,SAAS,GAAG;AACV,YAAM,IAAI,MAAM,qCAAqC;AAAA,IACvD;AAEA,UAAM,YAAmB,MAAM,YAAY;AAE3C,QAAI,WAAW;AACf,QAAI,YAAY;AAEhB,QAAI,MAAc,UAAU,GAAG;AAC7B,cAAQ,IAAI,MAAM,KAAK,8BAA8B,CAAC;AACtD,YAAM,WAAW,MAAc,WAAW;AAC1C,YAAM,WAAkB,MAAM,QAAQ;AAGtC,iBAAkB,MAAM,UAAU,SAAS;AAC3C,kBAAY;AAAA,IACd;AAEA,UAAM,cAAqB,UAAU,QAAQ;AAC7C,UAAc,YAAY,WAAW;AAErC,YAAQ,IAAI,MAAM,MAAM;AAAA,WAAc,SAAS,EAAE,CAAC;AAClD,YAAQ;AAAA,MACN,MAAM,MAAM,sBAAsB,IAChC,MAAM,OAAO,OAAO,KAAK,QAAQ,EAAE,MAAM;AAAA,IAC7C;AAAA,EACF,SAAS,OAAY;AACnB,YAAQ,MAAM,MAAM,IAAI,WAAW,GAAG,MAAM,OAAO;AACnD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;;;AKlEH,SAAS,WAAAC,gBAAe;AACxB,OAAOC,YAAW;AAClB,OAAOC,eAAgC;AAMhC,IAAM,cAAc,IAAIC,SAAQ,MAAM,EAC1C,YAAY,kDAAkD,EAC9D,OAAO,YAAY;AAClB,MAAI;AACF,YAAQ,IAAIC,OAAM,KAAK,gCAAgC,CAAC;AAExD,QAAI,CAAE,MAAc,UAAU,GAAI;AAChC,cAAQ,MAAMA,OAAM,IAAI,uCAAuC,CAAC;AAChE,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,UAAU,MAAMC,UAAS,OAAO;AAAA,MACpC;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,MAAM;AAAA,QACN,UAAU,CAAC,UACT,MAAM,SAAS,IAAI,OAAO;AAAA,MAC9B;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,MAAM;AAAA,QACN,UAAU,CAAC,OAAeC,aACxB,UAAUA,SAAQ,YAAY;AAAA,MAClC;AAAA,IACF,CAAC;AAED,UAAM,WAAW,QAAQ;AAEzB,UAAM,aAAa,MAAc,WAAW;AAC5C,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,YAAY,OAAO,KAAK,MAAM,EAAE;AAEtC,UAAM,gBAAgB,MAAa,QAAQ,YAAY,QAAQ;AAC/D,UAAc,YAAY,aAAa;AACvC,UAAc,gBAAgB;AAE9B,YAAQ;AAAA,MACNF,OAAM,MAAM;AAAA,qBAAwB,SAAS,sBAAsB;AAAA,IACrE;AACA,YAAQ,IAAIA,OAAM,KAAK,qCAAqC,CAAC;AAAA,EAC/D,SAAS,OAAY;AACnB,YAAQ,MAAMA,OAAM,IAAI,YAAY,GAAG,MAAM,OAAO;AACpD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;;;ANpDH,OAAOG,YAAW;AAElB,IAAM,UAAU,IAAIC,SAAQ;AAE5B,QACG,KAAK,YAAY,EACjB,YAAY,4DAA4D,EACxE,QAAQ,OAAO;AAElB,QAAQ,WAAW,WAAW;AAC9B,QAAQ,WAAW,WAAW;AAE9B,QAAQ,GAAG,aAAa,MAAM;AAC5B,UAAQ;AAAA,IACND,OAAM;AAAA,MACJ;AAAA,IACF;AAAA,IACA,QAAQ,KAAK,KAAK,GAAG;AAAA,EACvB;AACA,UAAQ,KAAK,CAAC;AAChB,CAAC;AAED,QAAQ,MAAM,QAAQ,IAAI;AAE1B,IAAI,CAAC,QAAQ,KAAK,MAAM,CAAC,EAAE,QAAQ;AACjC,UAAQ,WAAW;AACrB;","names":["Command","Command","chalk","inquirer","Command","chalk","inquirer","answers","chalk","Command"]}
1
+ {"version":3,"sources":["../../src/cli/bin.ts","../../src/cli/commands/sync.ts","../../src/lib/storage.ts","../../src/lib/crypto.ts","../../src/constants/index.ts","../../src/lib/parser.ts","../../src/lib/config.ts","../../src/cli/commands/lock.ts","../../src/cli/commands/init.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport { Command } from \"commander\";\r\nimport { syncCommand } from \"./commands/sync\";\r\nimport { lockCommand } from \"./commands/lock\";\r\nimport chalk from \"chalk\";\r\nimport { initCommand } from \"./commands/init\";\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name(\"lazy-vault\")\r\n .description(\"A secure, simple way to manage encrypted .env files in Git\")\r\n .version(\"1.0.0\");\r\n\r\nprogram.addCommand(syncCommand);\r\nprogram.addCommand(lockCommand);\r\nprogram.addCommand(initCommand);\r\n\r\nprogram.on(\"command:*\", () => {\r\n console.error(\r\n chalk.red(\r\n \"Invalid command: %s\\nSee --help for a list of available commands.\",\r\n ),\r\n program.args.join(\" \"),\r\n );\r\n process.exit(1);\r\n});\r\n\r\nprogram.parse(process.argv);\r\n\r\nif (!process.argv.slice(2).length) {\r\n program.outputHelp();\r\n}\r\n","import { Command } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport inquirer from \"inquirer\";\r\n\r\nimport * as storage from \"../../lib/storage\";\r\nimport * as crypto from \"../../lib/crypto\";\r\nimport * as parser from \"../../lib/parser\";\r\nimport * as config from \"../../lib/config\";\r\n\r\nexport const syncCommand = new Command(\"sync\")\r\n .description(\"Decrypts .env.enc and merges it with local .env\")\r\n .argument(\"[env]\", \"Environment name\", \"default\") // Support for 'lazy-guard sync production'\r\n .option(\"-i, --input <path>\", \"Override encrypted file path\")\r\n .option(\"-o, --output <path>\", \"Override target .env path\")\r\n .action(async (envName, options) => {\r\n try {\r\n console.log(chalk.blue(\"Preparing to Sync...\"));\r\n\r\n const fileConfig = await config.loadConfig(envName);\r\n\r\n const encryptedPath = options.input || fileConfig.output;\r\n const rawPath = options.output || fileConfig.source;\r\n\r\n console.log(chalk.blue(` Syncing [${envName}]...`));\r\n console.log(chalk.gray(` Source (Encrypted): ${encryptedPath}`));\r\n console.log(chalk.gray(` Target (Raw): ${rawPath}`));\r\n\r\n if (!(await storage.exists(encryptedPath))) {\r\n console.error(chalk.red(`Error: File ${encryptedPath} not found.`));\r\n process.exit(1);\r\n }\r\n\r\n let password = process.env.LAZY_ENV_PASSWORD; // FOR CI/CD support.\r\n if (!password) {\r\n const answers = await inquirer.prompt([\r\n {\r\n type: \"password\",\r\n name: \"password\",\r\n message: \"Enter password to decrypt:\",\r\n mask: \"*\",\r\n },\r\n ]);\r\n\r\n password = answers.password;\r\n } else {\r\n console.log(chalk.yellow(\" Using password from LAZY_ENV_PASSWORD\"));\r\n }\r\n\r\n const encryptedContent = await storage.readFile(encryptedPath);\r\n let decryptedRaw = \"\";\r\n\r\n try {\r\n decryptedRaw = await crypto.decrypt(\r\n encryptedContent,\r\n password as string,\r\n );\r\n } catch (e) {\r\n throw new Error(\"Invalid password or corrupted file.\");\r\n }\r\n\r\n const remoteObj = parser.parse(decryptedRaw);\r\n\r\n let finalObj = remoteObj;\r\n let actionMsg = `Created new ${rawPath}`;\r\n\r\n if (await storage.exists(rawPath)) {\r\n console.log(chalk.gray(` Local ${rawPath} found. Merging...`));\r\n const localRaw = await storage.readFile(rawPath);\r\n const localObj = parser.parse(localRaw);\r\n\r\n // MERGE: Remote wins conflicts, Local keeps unique keys\r\n finalObj = parser.merge(localObj, remoteObj);\r\n actionMsg = \"Merged with local .env\";\r\n }\r\n\r\n const finalString = parser.stringify(finalObj);\r\n await storage.writeFile(rawPath, finalString);\r\n\r\n console.log(chalk.green(`\\nSuccess! ${actionMsg}`));\r\n console.log(\r\n chalk.white(\"Keys in final .env: \") +\r\n chalk.yellow(Object.keys(finalObj).length),\r\n );\r\n } catch (error: any) {\r\n console.error(chalk.red(\"\\nFailed:\"), error.message);\r\n process.exit(1);\r\n }\r\n });\r\n","import fs from \"fs-extra\";\r\nimport path from \"path\";\r\n\r\n/**\r\n * Checks if the .env files exists.\r\n * @return True if the file exists, false otherwise\r\n */\r\nexport async function exists(filePath: string): Promise<boolean> {\r\n return fs.pathExists(filePath);\r\n}\r\n\r\n/**\r\n * Reads the .env files.\r\n * @return The content of the raw .env file\r\n */\r\nexport async function readFile(filePath: string): Promise<string> {\r\n if (!(await exists(filePath))) {\r\n throw new Error(`File not found: ${filePath}`);\r\n }\r\n return fs.readFile(filePath, \"utf-8\");\r\n}\r\n\r\n/**\r\n * Writes data to the .env files.\r\n * @param content The content to write to the raw .env file\r\n * @return A promise that resolves when the write is complete\r\n */\r\nexport async function writeFile(\r\n filePath: string,\r\n content: string,\r\n): Promise<void> {\r\n await fs.writeFile(filePath, content, \"utf-8\");\r\n}\r\n\r\n/**\r\n * Adds .env to .gitignore if it's missing.\r\n * This is a safety feature to prevent accidental commits.\r\n */\r\nexport async function addToGitIgnore(filePath: string): Promise<void> {\r\n const gitignorePath = \".gitignore\";\r\n const filename = path.basename(filePath);\r\n const ignoreRule = `\\n${filename}`;\r\n\r\n // Don't ignore encrypted files!\r\n if (filename.endsWith(\".enc\")) return;\r\n\r\n if (!(await fs.pathExists(gitignorePath))) {\r\n await fs.writeFile(gitignorePath, `# Added by lazy-vault${ignoreRule}\\n`);\r\n return;\r\n }\r\n\r\n const content = await fs.readFile(gitignorePath, \"utf-8\");\r\n if (!content.includes(filename)) {\r\n await fs.appendFile(gitignorePath, ignoreRule);\r\n }\r\n}\r\n","import crypto from \"node:crypto\";\r\nimport argon2 from \"argon2\";\r\nimport { ALGORITHM, PROFILES, VERSION } from \"../constants\";\r\nimport type { EncryptedEnvelope, ProfileName } from \"../types\";\r\n\r\n/**\r\n * Encrypts a raw string using a password.\r\n * Format: salt:iv:authTag:cipherText\r\n * @param text The string to encrypt\r\n * @param password The password to use for encryption\r\n */\r\n\r\nexport async function encrypt(\r\n text: string,\r\n password: string,\r\n profileName: ProfileName = \"light\",\r\n): Promise<string> {\r\n const settings = PROFILES[profileName];\r\n\r\n const salt = crypto.randomBytes(16);\r\n const iv = crypto.randomBytes(16);\r\n\r\n const key = await argon2.hash(password, {\r\n type: argon2.argon2id,\r\n salt: salt,\r\n raw: true,\r\n hashLength: 32,\r\n timeCost: settings.timeCost,\r\n memoryCost: settings.memoryCost,\r\n parallelism: settings.parallelism,\r\n });\r\n\r\n const cipher = crypto.createCipheriv(ALGORITHM, key, iv);\r\n\r\n const encrypted = Buffer.concat([\r\n cipher.update(text, \"utf8\"),\r\n cipher.final(),\r\n ]);\r\n const authTag = cipher.getAuthTag();\r\n\r\n const envelope: EncryptedEnvelope = {\r\n v: 2,\r\n mode: \"password\",\r\n profile: profileName,\r\n ops: {\r\n mem: settings.memoryCost,\r\n time: settings.timeCost,\r\n parallel: settings.parallelism,\r\n },\r\n salt: salt.toString(\"hex\"),\r\n iv: iv.toString(\"hex\"),\r\n tag: authTag.toString(\"hex\"),\r\n data: encrypted.toString(\"hex\"),\r\n };\r\n\r\n key.fill(0);\r\n\r\n return JSON.stringify(envelope, null, 2);\r\n}\r\n\r\n/** * Decrypts an encrypted string using a password.\r\n * @param encryptedText The encrypted string to decrypt\r\n * @param password The password to use for decryption\r\n * @return The decrypted string\r\n */\r\nexport async function decrypt(\r\n content: string,\r\n password: string,\r\n): Promise<string> {\r\n try {\r\n const envelope = JSON.parse(content) as EncryptedEnvelope;\r\n const key = await argon2.hash(password, {\r\n type: argon2.argon2id,\r\n salt: Buffer.from(envelope.salt, \"hex\"),\r\n raw: true,\r\n hashLength: 32,\r\n timeCost: envelope.ops.time,\r\n memoryCost: envelope.ops.mem,\r\n parallelism: envelope.ops.parallel,\r\n });\r\n\r\n const decipher = crypto.createDecipheriv(\r\n ALGORITHM,\r\n key,\r\n Buffer.from(envelope.iv, \"hex\"),\r\n );\r\n decipher.setAuthTag(Buffer.from(envelope.tag, \"hex\"));\r\n\r\n const decrypted = Buffer.concat([\r\n decipher.update(Buffer.from(envelope.data, \"hex\")),\r\n decipher.final(),\r\n ]);\r\n\r\n key.fill(0);\r\n\r\n return decrypted.toString(\"utf8\");\r\n } catch (error: any) {\r\n throw new Error(`Decryption failed: ${error.message}`);\r\n }\r\n}\r\n","export const ALGORITHM = \"aes-256-gcm\";\r\nexport const ENC_FILE = \".env.enc\";\r\nexport const RAW_FILE = \".env\";\r\nexport const VERSION = \"v1\";\r\nexport const CONFIG_FILE = \"lazy.config.json\";\r\n\r\nexport const PROFILES = {\r\n // Good for CI/CD and frequent locking\r\n light: { timeCost: 3, memoryCost: 64 * 1024, parallelism: 1 },\r\n // Good for long-term storage or high-value keys\r\n heavy: { timeCost: 10, memoryCost: 256 * 1024, parallelism: 4 },\r\n};\r\n","import type { EnvObject } from \"..\";\r\n\r\n/**\r\n * Parses a raw .env string into a Key-Value object.\r\n * Ignores comments and empty lines.\r\n * @param content The raw .env string\r\n * @return An object representing the parsed .env variables\r\n */\r\n\r\nexport function parse(content: string): EnvObject {\r\n const result: EnvObject = {};\r\n const lines = content.split(/\\r?\\n/);\r\n\r\n for (const line of lines) {\r\n const trimmedLine = line.trim();\r\n if (trimmedLine === \"\" || trimmedLine.startsWith(\"#\")) {\r\n continue;\r\n }\r\n\r\n if (!trimmedLine.includes(\"=\")) {\r\n throw new Error(`Invalid env line (missing '='): ${line}`);\r\n }\r\n\r\n const [rawKey, ...valueParts] = trimmedLine.split(\"=\") as [string, any];\r\n\r\n const key = rawKey.trim();\r\n if (!key) {\r\n throw new Error(`Invalid env line (empty key): ${line}`);\r\n }\r\n\r\n const value = valueParts.join(\"=\");\r\n const cleanValue = value.replace(/^['\"](.*)['\"]$/, \"$1\").trim();\r\n\r\n result[key] = cleanValue;\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * Stringifies a Key-Value object into a raw .env string.\r\n * @param envObject The object representing .env variables\r\n * @return A raw .env string\r\n */\r\nexport function stringify(envObject: EnvObject): string {\r\n let result = \"\";\r\n for (const [key, value] of Object.entries(envObject)) {\r\n result += `${key}=${value}\\n`;\r\n }\r\n\r\n return result.trimEnd();\r\n}\r\n\r\n/**\r\n * Merge logic\r\n * Remote keys override local keys if they exist\r\n * Unique keys from both are preserved\r\n * @param local The local .env object\r\n * @param remote The remote .env object\r\n */\r\n\r\nexport function merge(local: EnvObject, remote: EnvObject): EnvObject {\r\n return { ...local, ...remote };\r\n}\r\n","import fs from \"fs-extra\";\r\nimport path from \"path\";\r\nimport type { ConfigMap, ENVConfig } from \"../types\";\r\nimport type { EnvObject } from \"..\";\r\nimport { CONFIG_FILE } from \"../constants\";\r\n\r\nconst DEFAULTS: ENVConfig = {\r\n source: \".env\",\r\n output: \".env.env\",\r\n security: \"light\",\r\n};\r\n\r\nexport async function loadConfig(\r\n envName: string = \"default\",\r\n): Promise<ENVConfig> {\r\n if (!(await fs.pathExists(CONFIG_FILE))) {\r\n return DEFAULTS;\r\n }\r\n\r\n try {\r\n const raw = await fs.readFile(CONFIG_FILE, \"utf-8\");\r\n const config = JSON.parse(raw) as ConfigMap;\r\n\r\n if (!config[envName]) {\r\n if (envName === \"default\") return DEFAULTS;\r\n throw new Error(`Environment \"${envName}\" not found in ${CONFIG_FILE}`);\r\n }\r\n\r\n return { ...DEFAULTS, ...config[envName] };\r\n } catch (error: any) {\r\n throw new Error(`Failed to parse ${CONFIG_FILE}: ${error.message}`);\r\n }\r\n}\r\n\r\nexport async function createTemplateConfig(): Promise<void> {\r\n const template: ConfigMap = {\r\n default: { source: \".env\", output: \".env.enc\", security: \"light\" },\r\n production: {\r\n source: \".env.prod\",\r\n output: \".env.prod.enc\",\r\n security: \"heavy\",\r\n },\r\n };\r\n await fs.writeJson(CONFIG_FILE, template, { spaces: 2 });\r\n}\r\n","import { Command, Option } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport inquirer, { type Answers } from \"inquirer\";\r\n\r\nimport * as storage from \"../../lib/storage\";\r\nimport * as crypto from \"../../lib/crypto\";\r\nimport * as config from \"../../lib/config\";\r\n\r\nimport { parse } from \"../../lib/parser\";\r\nimport type { ProfileName } from \"../../types\";\r\n\r\nexport const lockCommand = new Command(\"lock\")\r\n .description(\"Encrypts local .env file and saves it to env.enc\")\r\n .argument(\"[env]\", \"Environment name (default, production, etc)\", \"default\")\r\n .option(\"-i, --input <path>\", \"Override input path\")\r\n .option(\"-o, --output <path>\", \"Override output path\")\r\n .addOption(\r\n new Option(\"-p, --profile <mode>\", \"Override security profile\").choices([\r\n \"light\",\r\n \"heavy\",\r\n ]),\r\n )\r\n .action(async (envName, options) => {\r\n try {\r\n console.log(chalk.blue(\"Preparing to lock ...\"));\r\n\r\n const fileConfig = await config.loadConfig(envName);\r\n const sourcePath = options.input || fileConfig.source;\r\n const outputPath = options.output || fileConfig.output;\r\n const securityProfile = options.profile || fileConfig.security;\r\n\r\n console.log(chalk.blue(` Locking [${envName}]...`));\r\n console.log(chalk.gray(` Source: ${sourcePath}`));\r\n console.log(chalk.gray(` Target: ${outputPath}`));\r\n console.log(chalk.gray(` Profile: ${securityProfile}`));\r\n\r\n if (!(await storage.exists(sourcePath))) {\r\n console.error(chalk.red(`Error: File ${sourcePath} not found.`));\r\n process.exit(1);\r\n }\r\n\r\n let password = process.env.LAZY_ENV_PASSWORD;\r\n if (!password) {\r\n const answers = await inquirer.prompt([\r\n {\r\n type: \"password\",\r\n name: \"password\",\r\n message: \"Enter Password to encrypt: \",\r\n mask: \"*\",\r\n validate: (input) =>\r\n input.length > 0 ? true : \"Password cannot be empty.\",\r\n },\r\n {\r\n type: \"password\",\r\n name: \"passwordConfirm\",\r\n message: \"Confirm password:\",\r\n mask: \"*\",\r\n },\r\n ]);\r\n\r\n if (answers.password !== answers.passwordConfirm) {\r\n console.error(chalk.red(\"\\n Error: Passwords do not match.\"));\r\n process.exit(1);\r\n }\r\n\r\n password = answers.password;\r\n }\r\n\r\n const rawContent = await storage.readFile(sourcePath);\r\n\r\n const encryptedData = await crypto.encrypt(\r\n rawContent,\r\n password as string,\r\n securityProfile as ProfileName,\r\n );\r\n await storage.writeFile(outputPath, encryptedData);\r\n await storage.addToGitIgnore(sourcePath);\r\n\r\n console.log(chalk.green(`\\nSuccess! Encrypted secrets to ${outputPath}`));\r\n console.log(chalk.gray(\"You can now commit .env.enc to Git.\"));\r\n } catch (error: any) {\r\n console.error(chalk.red(\"\\n Failed:\"), error.message);\r\n process.exit(1);\r\n }\r\n });\r\n","import { Command } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport * as config from \"../../lib/config\";\r\nimport fs from \"fs-extra\";\r\nimport { CONFIG_FILE } from \"../../constants\";\r\n\r\nexport const initCommand = new Command(\"init\")\r\n .description(\"Creates a lazy.config.json file\")\r\n .action(async () => {\r\n if (await fs.pathExists(CONFIG_FILE)) {\r\n console.log(chalk.yellow(\"lazy.config.json already exists.\"));\r\n return;\r\n }\r\n\r\n await config.createTemplateConfig();\r\n console.log(chalk.green(`\\nCreated ${CONFIG_FILE}`));\r\n console.log(chalk.gray('You can now run \"lazy-env lock production\"'));\r\n });\r\n"],"mappings":"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","names":["Command","fs","fs","Command","chalk","inquirer","Command","chalk","inquirer","chalk","Command","chalk","fs","Command","fs","chalk","Command","chalk"]}
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "lazy-vault",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "A simple CLI for encrypting and syncing .env files safely in Git",
5
5
  "license": "MIT",
6
6
  "author": "ghost",
7
7
  "type": "module",
8
8
  "bin": {
9
- "lazy-env": "./dist/cli/bin.js"
9
+ "lazy-vault": "./dist/cli/bin.js"
10
10
  },
11
11
  "engines": {
12
12
  "node": ">=18"
@@ -24,12 +24,12 @@
24
24
  ],
25
25
  "repository": {
26
26
  "type": "git",
27
- "url": "https://github.com/Ghunter254/lazy-env"
27
+ "url": "https://github.com/Ghunter254/lazy-vault"
28
28
  },
29
29
  "bugs": {
30
- "url": "https://github.com/Ghunter254/lazy-env/issues"
30
+ "url": "https://github.com/Ghunter254/lazy-vault/issues"
31
31
  },
32
- "homepage": "https://github.com/Ghunter254/lazy-env#readme",
32
+ "homepage": "https://github.com/Ghunter254/lazy-vault#readme",
33
33
  "keywords": [
34
34
  "env",
35
35
  "dotenv",