dotcloak 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +170 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +618 -0
- package/dist/cli/index.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 R.M
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/logo.png" alt="dotcloak" width="400" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# dotcloak
|
|
6
|
+
|
|
7
|
+
> Encrypt your `.env` so AI coding tools can only read ciphertext.
|
|
8
|
+
|
|
9
|
+
dotcloak is a Node.js CLI that encrypts `.env` files with [age](https://age-encryption.org/) and only injects plaintext secrets into the child process you launch with `dotcloak run`.
|
|
10
|
+
|
|
11
|
+
- Repository: <https://github.com/3062-in-zamud/dotcloak>
|
|
12
|
+
- Node.js: `>=20`
|
|
13
|
+
- License: MIT
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g dotcloak
|
|
21
|
+
|
|
22
|
+
cat > .env <<'EOF'
|
|
23
|
+
API_KEY=super-secret
|
|
24
|
+
DATABASE_URL=postgres://localhost/app
|
|
25
|
+
EOF
|
|
26
|
+
|
|
27
|
+
dotcloak init
|
|
28
|
+
dotcloak status
|
|
29
|
+
dotcloak run -- node -e "console.log(process.env.API_KEY)"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
What happens:
|
|
33
|
+
|
|
34
|
+
1. `dotcloak init` creates `.env.cloak`, `.dotcloak/key.age`, and `.dotcloak/config.toml`.
|
|
35
|
+
2. dotcloak appends `.dotcloak/key.age` to `.gitignore`, `.claudeignore`, and `.cursorignore`.
|
|
36
|
+
3. The original `.env` is deleted unless you pass `--keep`.
|
|
37
|
+
4. `dotcloak run` decrypts secrets in memory and passes them to the command you run.
|
|
38
|
+
|
|
39
|
+
For one-off usage without global install:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx dotcloak init
|
|
43
|
+
npx dotcloak run -- npm start
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Static CLI Flow
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
.env
|
|
50
|
+
-> dotcloak init
|
|
51
|
+
-> .env.cloak + .dotcloak/key.age
|
|
52
|
+
-> dotcloak run -- <command>
|
|
53
|
+
-> child process receives process.env
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Command Reference
|
|
57
|
+
|
|
58
|
+
### `dotcloak init`
|
|
59
|
+
|
|
60
|
+
Encrypt a plaintext `.env` file and initialize dotcloak in the current project.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
dotcloak init
|
|
64
|
+
dotcloak init --keep
|
|
65
|
+
dotcloak init --file .env.local
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `dotcloak run -- <command>`
|
|
69
|
+
|
|
70
|
+
Run a command with decrypted secrets injected into the child process environment.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
dotcloak run -- npm start
|
|
74
|
+
dotcloak run -- node -e "console.log(process.env.API_KEY)"
|
|
75
|
+
dotcloak run --file .env.production.cloak -- npm run worker
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `dotcloak set`
|
|
79
|
+
|
|
80
|
+
Add or update a secret in the encrypted store.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
dotcloak set API_KEY=rotated-secret
|
|
84
|
+
dotcloak set DATABASE_URL
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The second form prompts for a hidden value.
|
|
88
|
+
|
|
89
|
+
### `dotcloak unset`
|
|
90
|
+
|
|
91
|
+
Remove a secret from the encrypted store.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
dotcloak unset API_KEY
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `dotcloak list`
|
|
98
|
+
|
|
99
|
+
List secrets from the encrypted store.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
dotcloak list
|
|
103
|
+
dotcloak list --show
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Values are masked by default. `--show` prints plaintext values and should only be used in a trusted terminal.
|
|
107
|
+
|
|
108
|
+
### `dotcloak edit`
|
|
109
|
+
|
|
110
|
+
Edit decrypted secrets in your `$EDITOR` or `$VISUAL`, then re-encrypt on save.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
EDITOR=nvim dotcloak edit
|
|
114
|
+
VISUAL="code --wait" dotcloak edit
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### `dotcloak status`
|
|
118
|
+
|
|
119
|
+
Show whether dotcloak is initialized and whether plaintext `.env` still exists.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
dotcloak status
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `dotcloak key export`
|
|
126
|
+
|
|
127
|
+
Print the current age secret key so you can back it up or transfer it securely.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
dotcloak key export > dotcloak-backup.age
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### `dotcloak key import`
|
|
134
|
+
|
|
135
|
+
Import an exported age secret key into the current project.
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
dotcloak key import ./dotcloak-backup.age
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Security Model
|
|
142
|
+
|
|
143
|
+
### What dotcloak protects
|
|
144
|
+
|
|
145
|
+
- Plaintext `.env` does not need to stay on disk after `dotcloak init`.
|
|
146
|
+
- AI coding tools scanning the filesystem only see encrypted `.env.cloak`.
|
|
147
|
+
- Your app keeps using `process.env` with no application code changes.
|
|
148
|
+
|
|
149
|
+
### What dotcloak does not protect
|
|
150
|
+
|
|
151
|
+
- It does not protect secrets from a process you launch yourself with `dotcloak run`.
|
|
152
|
+
- It does not replace OS isolation, secret rotation, or host hardening.
|
|
153
|
+
- It does not protect against anyone who can already inspect your process memory or environment.
|
|
154
|
+
|
|
155
|
+
### Linux note
|
|
156
|
+
|
|
157
|
+
`dotcloak run` injects secrets into the child process environment. That protects `.env` from filesystem-based AI scans, but it does **not** harden Linux against same-user inspection of `/proc/<pid>/environ` or other OS-level process introspection. Treat dotcloak as filesystem protection, not a sandbox boundary.
|
|
158
|
+
|
|
159
|
+
## Why use this for AI tools?
|
|
160
|
+
|
|
161
|
+
Ignore files are advisory. dotcloak changes the artifact on disk instead: the file an AI tool can read is ciphertext, not plaintext. That is the narrow problem this tool is designed to solve.
|
|
162
|
+
|
|
163
|
+
## Development
|
|
164
|
+
|
|
165
|
+
- Contribution guide: [CONTRIBUTING.md](./CONTRIBUTING.md)
|
|
166
|
+
- Release checklist: [RELEASING.md](./RELEASING.md)
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/cli/banner.ts
|
|
7
|
+
function printBanner() {
|
|
8
|
+
console.log(`
|
|
9
|
+
_ _ _ _
|
|
10
|
+
__| | ___ | |_ ___| | ___ __ _| | __
|
|
11
|
+
/ _' |/ _ \\| __/ __| |/ _ \\ / _' | |/ /
|
|
12
|
+
| (_| | (_) | || (__| | (_) | (_| | <
|
|
13
|
+
\\__,_|\\___/ \\__\\___|_|\\___/ \\__,_|_|\\_\\
|
|
14
|
+
|
|
15
|
+
keep your .env invisible
|
|
16
|
+
`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/cli/commands/edit.ts
|
|
20
|
+
import { execSync } from "child_process";
|
|
21
|
+
import * as fs3 from "fs";
|
|
22
|
+
import * as os from "os";
|
|
23
|
+
import * as path2 from "path";
|
|
24
|
+
import * as age4 from "age-encryption";
|
|
25
|
+
|
|
26
|
+
// src/core/cloak-file.ts
|
|
27
|
+
import * as fs from "fs";
|
|
28
|
+
function readCloakFile(filePath) {
|
|
29
|
+
if (!fs.existsSync(filePath)) {
|
|
30
|
+
throw new Error(`Cloak file not found: ${filePath}`);
|
|
31
|
+
}
|
|
32
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
33
|
+
}
|
|
34
|
+
function writeCloakFile(filePath, content) {
|
|
35
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
36
|
+
}
|
|
37
|
+
function cloakFileExists(filePath) {
|
|
38
|
+
return fs.existsSync(filePath);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/core/env-parser.ts
|
|
42
|
+
function parse(content) {
|
|
43
|
+
const env = /* @__PURE__ */ new Map();
|
|
44
|
+
const lines = content.split("\n");
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
const trimmed = line.trim();
|
|
47
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
48
|
+
const withoutExport = trimmed.startsWith("export ") ? trimmed.slice(7) : trimmed;
|
|
49
|
+
const eqIndex = withoutExport.indexOf("=");
|
|
50
|
+
if (eqIndex === -1) continue;
|
|
51
|
+
const key = withoutExport.slice(0, eqIndex).trim();
|
|
52
|
+
let value = withoutExport.slice(eqIndex + 1).trim();
|
|
53
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
54
|
+
value = value.slice(1, -1);
|
|
55
|
+
}
|
|
56
|
+
env.set(key, value);
|
|
57
|
+
}
|
|
58
|
+
return env;
|
|
59
|
+
}
|
|
60
|
+
function stringify(env) {
|
|
61
|
+
const lines = [];
|
|
62
|
+
for (const [key, value] of env) {
|
|
63
|
+
if (value.includes("\n") || value.includes(" ") || value.includes('"')) {
|
|
64
|
+
lines.push(`${key}="${value.replace(/"/g, '\\"')}"`);
|
|
65
|
+
} else {
|
|
66
|
+
lines.push(`${key}=${value}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return `${lines.join("\n")}
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/core/secret-manager.ts
|
|
74
|
+
import * as age3 from "age-encryption";
|
|
75
|
+
|
|
76
|
+
// src/crypto/age-engine.ts
|
|
77
|
+
import * as age from "age-encryption";
|
|
78
|
+
async function encrypt(plaintext, recipient) {
|
|
79
|
+
const encrypter = new age.Encrypter();
|
|
80
|
+
encrypter.addRecipient(recipient);
|
|
81
|
+
const encrypted = await encrypter.encrypt(plaintext);
|
|
82
|
+
return age.armor.encode(encrypted);
|
|
83
|
+
}
|
|
84
|
+
async function decrypt(armored, identity) {
|
|
85
|
+
const encrypted = age.armor.decode(armored);
|
|
86
|
+
const decrypter = new age.Decrypter();
|
|
87
|
+
decrypter.addIdentity(identity);
|
|
88
|
+
const decrypted = await decrypter.decrypt(encrypted, "text");
|
|
89
|
+
return decrypted;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/crypto/key-manager.ts
|
|
93
|
+
import * as fs2 from "fs";
|
|
94
|
+
import * as path from "path";
|
|
95
|
+
import * as age2 from "age-encryption";
|
|
96
|
+
var DOTCLOAK_DIR = ".dotcloak";
|
|
97
|
+
var KEY_FILE = "key.age";
|
|
98
|
+
async function generateKeyPair() {
|
|
99
|
+
const identity = await age2.generateIdentity();
|
|
100
|
+
const recipient = await age2.identityToRecipient(identity);
|
|
101
|
+
return { identity, recipient };
|
|
102
|
+
}
|
|
103
|
+
function saveIdentity(projectDir, identity) {
|
|
104
|
+
const dir = path.join(projectDir, DOTCLOAK_DIR);
|
|
105
|
+
if (!fs2.existsSync(dir)) {
|
|
106
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
107
|
+
}
|
|
108
|
+
const keyPath = path.join(dir, KEY_FILE);
|
|
109
|
+
fs2.writeFileSync(keyPath, identity, { mode: 384 });
|
|
110
|
+
}
|
|
111
|
+
function loadIdentity(projectDir) {
|
|
112
|
+
const keyPath = path.join(projectDir, DOTCLOAK_DIR, KEY_FILE);
|
|
113
|
+
if (!fs2.existsSync(keyPath)) {
|
|
114
|
+
throw new Error(`Key file not found: ${keyPath}
|
|
115
|
+
Run 'dotcloak init' first.`);
|
|
116
|
+
}
|
|
117
|
+
return fs2.readFileSync(keyPath, "utf-8").trim();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/core/secret-manager.ts
|
|
121
|
+
async function getSecrets(projectDir, cloakPath) {
|
|
122
|
+
const identity = loadIdentity(projectDir);
|
|
123
|
+
const armored = readCloakFile(cloakPath);
|
|
124
|
+
const plaintext = await decrypt(armored, identity);
|
|
125
|
+
return parse(plaintext);
|
|
126
|
+
}
|
|
127
|
+
async function setSecret(projectDir, cloakPath, key, value) {
|
|
128
|
+
const identity = loadIdentity(projectDir);
|
|
129
|
+
const recipient = await age3.identityToRecipient(identity);
|
|
130
|
+
let secrets;
|
|
131
|
+
try {
|
|
132
|
+
const armored2 = readCloakFile(cloakPath);
|
|
133
|
+
const plaintext2 = await decrypt(armored2, identity);
|
|
134
|
+
secrets = parse(plaintext2);
|
|
135
|
+
} catch {
|
|
136
|
+
secrets = /* @__PURE__ */ new Map();
|
|
137
|
+
}
|
|
138
|
+
secrets.set(key, value);
|
|
139
|
+
const plaintext = stringify(secrets);
|
|
140
|
+
const armored = await encrypt(plaintext, recipient);
|
|
141
|
+
writeCloakFile(cloakPath, armored);
|
|
142
|
+
}
|
|
143
|
+
async function unsetSecret(projectDir, cloakPath, key) {
|
|
144
|
+
const identity = loadIdentity(projectDir);
|
|
145
|
+
const recipient = await age3.identityToRecipient(identity);
|
|
146
|
+
const armored = readCloakFile(cloakPath);
|
|
147
|
+
const plaintext = await decrypt(armored, identity);
|
|
148
|
+
const secrets = parse(plaintext);
|
|
149
|
+
if (!secrets.has(key)) return false;
|
|
150
|
+
secrets.delete(key);
|
|
151
|
+
const newPlaintext = stringify(secrets);
|
|
152
|
+
const newArmored = await encrypt(newPlaintext, recipient);
|
|
153
|
+
writeCloakFile(cloakPath, newArmored);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
function maskValue(value) {
|
|
157
|
+
if (value.length <= 4) return "****";
|
|
158
|
+
return value.slice(0, 2) + "*".repeat(value.length - 4) + value.slice(-2);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/cli/errors.ts
|
|
162
|
+
var CliError = class extends Error {
|
|
163
|
+
constructor(whatHappened, howToFix, options) {
|
|
164
|
+
super(whatHappened);
|
|
165
|
+
this.whatHappened = whatHappened;
|
|
166
|
+
this.howToFix = howToFix;
|
|
167
|
+
this.name = "CliError";
|
|
168
|
+
if (options?.cause !== void 0) {
|
|
169
|
+
;
|
|
170
|
+
this.cause = options.cause;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
function fail(whatHappened, howToFix) {
|
|
175
|
+
throw new CliError(whatHappened, howToFix);
|
|
176
|
+
}
|
|
177
|
+
function normalizeCliError(error, fallbackFix) {
|
|
178
|
+
if (error instanceof CliError) {
|
|
179
|
+
return error;
|
|
180
|
+
}
|
|
181
|
+
if (error instanceof Error) {
|
|
182
|
+
if (error.message.startsWith("Key file not found:")) {
|
|
183
|
+
return new CliError(
|
|
184
|
+
"Encryption key was not found.",
|
|
185
|
+
"Run 'dotcloak init' or 'dotcloak key import <file>' first.",
|
|
186
|
+
{ cause: error }
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
if (error.message.startsWith("Cloak file not found:")) {
|
|
190
|
+
return new CliError(
|
|
191
|
+
"Encrypted env file was not found.",
|
|
192
|
+
"Run 'dotcloak init' or pass '--file <path>' to an existing .env.cloak file.",
|
|
193
|
+
{ cause: error }
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
if (error.message.startsWith("Failed to start command")) {
|
|
197
|
+
return new CliError(
|
|
198
|
+
error.message,
|
|
199
|
+
"Verify the command exists and is available on your PATH, then try again.",
|
|
200
|
+
{ cause: error }
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return new CliError("Unexpected error occurred.", fallbackFix, { cause: error });
|
|
205
|
+
}
|
|
206
|
+
function renderCliError(error, fallbackFix) {
|
|
207
|
+
const cliError = normalizeCliError(error, fallbackFix);
|
|
208
|
+
console.error(`What happened: ${cliError.whatHappened}`);
|
|
209
|
+
console.error(`How to fix: ${cliError.howToFix}`);
|
|
210
|
+
}
|
|
211
|
+
function withCliErrorHandling(action, fallbackFix) {
|
|
212
|
+
return async (...args) => {
|
|
213
|
+
try {
|
|
214
|
+
await action(...args);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
renderCliError(error, fallbackFix);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/cli/commands/edit.ts
|
|
223
|
+
function registerEditCommand(program2) {
|
|
224
|
+
program2.command("edit").description("Edit secrets in your $EDITOR").option("-f, --file <path>", "Path to .env.cloak file", ".env.cloak").action(
|
|
225
|
+
withCliErrorHandling(async (options) => {
|
|
226
|
+
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
227
|
+
const projectDir = process.cwd();
|
|
228
|
+
const cloakPath = path2.resolve(projectDir, options.file);
|
|
229
|
+
const secrets = await getSecrets(projectDir, cloakPath);
|
|
230
|
+
const plaintext = stringify(secrets);
|
|
231
|
+
const tmpFile = path2.join(os.tmpdir(), `dotcloak-edit-${Date.now()}.env`);
|
|
232
|
+
fs3.writeFileSync(tmpFile, plaintext, { mode: 384 });
|
|
233
|
+
try {
|
|
234
|
+
try {
|
|
235
|
+
execSync(`${editor} "${tmpFile}"`, { stdio: "inherit" });
|
|
236
|
+
} catch (error) {
|
|
237
|
+
throw new CliError(
|
|
238
|
+
`Editor '${editor}' failed while editing secrets.`,
|
|
239
|
+
"Set $EDITOR to a working editor and rerun the command.",
|
|
240
|
+
{ cause: error }
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
const edited = fs3.readFileSync(tmpFile, "utf-8");
|
|
244
|
+
const newSecrets = parse(edited);
|
|
245
|
+
const identity = loadIdentity(projectDir);
|
|
246
|
+
const recipient = await age4.identityToRecipient(identity);
|
|
247
|
+
const newPlaintext = stringify(newSecrets);
|
|
248
|
+
const armored = await encrypt(newPlaintext, recipient);
|
|
249
|
+
writeCloakFile(cloakPath, armored);
|
|
250
|
+
console.log("Secrets updated.");
|
|
251
|
+
} finally {
|
|
252
|
+
if (fs3.existsSync(tmpFile)) {
|
|
253
|
+
fs3.unlinkSync(tmpFile);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}, "Verify your editor configuration and rerun the edit command.")
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/cli/commands/init.ts
|
|
261
|
+
import * as fs6 from "fs";
|
|
262
|
+
import * as path5 from "path";
|
|
263
|
+
|
|
264
|
+
// src/config/defaults.ts
|
|
265
|
+
function createDefaultConfig(recipient) {
|
|
266
|
+
return {
|
|
267
|
+
dotcloak: { version: "1" },
|
|
268
|
+
encryption: { recipient },
|
|
269
|
+
files: { sources: [".env"] },
|
|
270
|
+
options: { delete_original: true, backup: true }
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/config/loader.ts
|
|
275
|
+
import * as fs4 from "fs";
|
|
276
|
+
import * as path3 from "path";
|
|
277
|
+
import * as TOML from "smol-toml";
|
|
278
|
+
|
|
279
|
+
// src/config/types.ts
|
|
280
|
+
import { z } from "zod";
|
|
281
|
+
var DotcloakConfigSchema = z.object({
|
|
282
|
+
dotcloak: z.object({
|
|
283
|
+
version: z.string().default("1")
|
|
284
|
+
}),
|
|
285
|
+
encryption: z.object({
|
|
286
|
+
recipient: z.string()
|
|
287
|
+
}),
|
|
288
|
+
files: z.object({
|
|
289
|
+
sources: z.array(z.string()).default([".env"])
|
|
290
|
+
}),
|
|
291
|
+
options: z.object({
|
|
292
|
+
delete_original: z.boolean().default(true),
|
|
293
|
+
backup: z.boolean().default(true)
|
|
294
|
+
})
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// src/config/loader.ts
|
|
298
|
+
var CONFIG_FILE = "config.toml";
|
|
299
|
+
var DOTCLOAK_DIR2 = ".dotcloak";
|
|
300
|
+
function saveConfig(projectDir, config) {
|
|
301
|
+
const dir = path3.join(projectDir, DOTCLOAK_DIR2);
|
|
302
|
+
if (!fs4.existsSync(dir)) {
|
|
303
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
304
|
+
}
|
|
305
|
+
const configPath = path3.join(dir, CONFIG_FILE);
|
|
306
|
+
const content = TOML.stringify(config);
|
|
307
|
+
fs4.writeFileSync(configPath, content, "utf-8");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/cli/ignore-files.ts
|
|
311
|
+
import * as fs5 from "fs";
|
|
312
|
+
import * as path4 from "path";
|
|
313
|
+
var IGNORE_SPECS = [
|
|
314
|
+
{
|
|
315
|
+
fileName: ".gitignore",
|
|
316
|
+
entries: [".dotcloak/key.age", ".env", ".env.*", "!.env.cloak"]
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
fileName: ".claudeignore",
|
|
320
|
+
entries: [".dotcloak/key.age"]
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
fileName: ".cursorignore",
|
|
324
|
+
entries: [".dotcloak/key.age"]
|
|
325
|
+
}
|
|
326
|
+
];
|
|
327
|
+
function updateIgnoreFiles(projectDir) {
|
|
328
|
+
const updatedFiles = [];
|
|
329
|
+
for (const spec of IGNORE_SPECS) {
|
|
330
|
+
const filePath = path4.join(projectDir, spec.fileName);
|
|
331
|
+
const existing = fs5.existsSync(filePath) ? fs5.readFileSync(filePath, "utf-8") : "";
|
|
332
|
+
const existingLines = new Set(existing.split(/\r?\n/).filter((line) => line.length > 0));
|
|
333
|
+
const missingEntries = spec.entries.filter((entry) => !existingLines.has(entry));
|
|
334
|
+
if (missingEntries.length === 0) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
338
|
+
const header = existingLines.has("# dotcloak") ? "" : "# dotcloak\n";
|
|
339
|
+
const addition = `${prefix}${header}${missingEntries.join("\n")}
|
|
340
|
+
`;
|
|
341
|
+
fs5.appendFileSync(filePath, addition, "utf-8");
|
|
342
|
+
updatedFiles.push(spec.fileName);
|
|
343
|
+
}
|
|
344
|
+
return updatedFiles;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/cli/commands/init.ts
|
|
348
|
+
function registerInitCommand(program2) {
|
|
349
|
+
program2.command("init").description("Initialize dotcloak: generate keys and encrypt .env").option("-f, --file <path>", "Path to .env file", ".env").option("--keep", "Keep the original .env file").action(
|
|
350
|
+
withCliErrorHandling(async (options) => {
|
|
351
|
+
const projectDir = process.cwd();
|
|
352
|
+
const envPath = path5.resolve(projectDir, options.file);
|
|
353
|
+
const cloakPath = `${envPath}.cloak`;
|
|
354
|
+
if (!fs6.existsSync(envPath)) {
|
|
355
|
+
fail(
|
|
356
|
+
`${options.file} was not found.`,
|
|
357
|
+
`Create ${options.file} or pass '--file <path>' to the correct env file.`
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
console.log("Generating encryption keys...");
|
|
361
|
+
const keyPair = await generateKeyPair();
|
|
362
|
+
saveIdentity(projectDir, keyPair.identity);
|
|
363
|
+
console.log("Encrypting .env...");
|
|
364
|
+
const plaintext = fs6.readFileSync(envPath, "utf-8");
|
|
365
|
+
const armored = await encrypt(plaintext, keyPair.recipient);
|
|
366
|
+
writeCloakFile(cloakPath, armored);
|
|
367
|
+
const config = createDefaultConfig(keyPair.recipient);
|
|
368
|
+
saveConfig(projectDir, config);
|
|
369
|
+
const updatedIgnoreFiles = updateIgnoreFiles(projectDir);
|
|
370
|
+
for (const fileName of updatedIgnoreFiles) {
|
|
371
|
+
console.log(`Updated ${fileName}`);
|
|
372
|
+
}
|
|
373
|
+
if (!options.keep) {
|
|
374
|
+
fs6.unlinkSync(envPath);
|
|
375
|
+
console.log(`Deleted ${options.file}`);
|
|
376
|
+
}
|
|
377
|
+
console.log("Done! Your secrets are now encrypted.");
|
|
378
|
+
console.log(` Encrypted file: ${path5.relative(projectDir, cloakPath)}`);
|
|
379
|
+
console.log(" Key file: .dotcloak/key.age");
|
|
380
|
+
console.log("");
|
|
381
|
+
console.log("Run your app with:");
|
|
382
|
+
console.log(" dotcloak run <command>");
|
|
383
|
+
}, "Check the env file path and try 'dotcloak init' again.")
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/cli/commands/key.ts
|
|
388
|
+
import * as fs7 from "fs";
|
|
389
|
+
import * as path6 from "path";
|
|
390
|
+
function registerKeyCommand(program2) {
|
|
391
|
+
const keyCmd = program2.command("key").description("Manage encryption keys");
|
|
392
|
+
keyCmd.command("export").description("Export the secret key").action(
|
|
393
|
+
withCliErrorHandling(() => {
|
|
394
|
+
const projectDir = process.cwd();
|
|
395
|
+
const identity = loadIdentity(projectDir);
|
|
396
|
+
console.log(identity);
|
|
397
|
+
}, "Run 'dotcloak init' or 'dotcloak key import <file>' before exporting a key.")
|
|
398
|
+
);
|
|
399
|
+
keyCmd.command("import").description("Import a secret key").argument("<keyfile>", "Path to key file").action(
|
|
400
|
+
withCliErrorHandling((keyfile) => {
|
|
401
|
+
const projectDir = process.cwd();
|
|
402
|
+
const keyPath = path6.resolve(keyfile);
|
|
403
|
+
if (!fs7.existsSync(keyPath)) {
|
|
404
|
+
fail(
|
|
405
|
+
`Key file was not found: ${keyfile}`,
|
|
406
|
+
"Pass a valid path to an exported age secret key file."
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
const identity = fs7.readFileSync(keyPath, "utf-8").trim();
|
|
410
|
+
if (identity.length === 0) {
|
|
411
|
+
fail("Key file is empty.", "Export the key again and retry the import.");
|
|
412
|
+
}
|
|
413
|
+
saveIdentity(projectDir, identity);
|
|
414
|
+
console.log("Key imported successfully.");
|
|
415
|
+
}, "Verify the key file path and content, then retry the import.")
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/cli/commands/list.ts
|
|
420
|
+
import * as path7 from "path";
|
|
421
|
+
function registerListCommand(program2) {
|
|
422
|
+
program2.command("list").description("List all secrets (masked values)").option("-f, --file <path>", "Path to .env.cloak file", ".env.cloak").option("--show", "Show actual values (use with caution)").action(
|
|
423
|
+
withCliErrorHandling(async (options) => {
|
|
424
|
+
const projectDir = process.cwd();
|
|
425
|
+
const cloakPath = path7.resolve(projectDir, options.file);
|
|
426
|
+
const secrets = await getSecrets(projectDir, cloakPath);
|
|
427
|
+
if (secrets.size === 0) {
|
|
428
|
+
console.log("No secrets found.");
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
for (const [key, value] of secrets) {
|
|
432
|
+
const displayValue = options.show ? value : maskValue(value);
|
|
433
|
+
console.log(`${key}=${displayValue}`);
|
|
434
|
+
}
|
|
435
|
+
}, "Run 'dotcloak init' first or pass '--file <path>' to an existing .env.cloak file.")
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/cli/commands/run.ts
|
|
440
|
+
import * as path8 from "path";
|
|
441
|
+
|
|
442
|
+
// src/runner/child-process.ts
|
|
443
|
+
import { spawn } from "child_process";
|
|
444
|
+
function runWithSecrets(command, args, secrets) {
|
|
445
|
+
const envVars = {};
|
|
446
|
+
for (const [key, value] of secrets) {
|
|
447
|
+
envVars[key] = value;
|
|
448
|
+
}
|
|
449
|
+
return new Promise((resolve8, reject) => {
|
|
450
|
+
const child = spawn(command, args, {
|
|
451
|
+
env: { ...process.env, ...envVars },
|
|
452
|
+
stdio: "inherit"
|
|
453
|
+
});
|
|
454
|
+
child.on("error", (err) => {
|
|
455
|
+
reject(new Error(`Failed to start command "${command}": ${err.message}`));
|
|
456
|
+
});
|
|
457
|
+
child.on("exit", (code) => {
|
|
458
|
+
resolve8(code ?? 1);
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/cli/commands/run.ts
|
|
464
|
+
function registerRunCommand(program2) {
|
|
465
|
+
program2.command("run").description("Run a command with decrypted secrets injected as environment variables").option("-f, --file <path>", "Path to .env.cloak file", ".env.cloak").argument("<command...>", "Command to run").action(
|
|
466
|
+
withCliErrorHandling(async (commandArgs, options) => {
|
|
467
|
+
const projectDir = process.cwd();
|
|
468
|
+
const cloakPath = path8.resolve(projectDir, options.file);
|
|
469
|
+
const secrets = await getSecrets(projectDir, cloakPath);
|
|
470
|
+
const [cmd, ...args] = commandArgs;
|
|
471
|
+
const exitCode = await runWithSecrets(cmd, args, secrets);
|
|
472
|
+
process.exit(exitCode);
|
|
473
|
+
}, "Run 'dotcloak init' first and verify the target command exists.")
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/cli/commands/set.ts
|
|
478
|
+
import * as path9 from "path";
|
|
479
|
+
|
|
480
|
+
// src/cli/prompt-secret.ts
|
|
481
|
+
async function promptHiddenValue(prompt, io = { input: process.stdin, output: process.stdout }) {
|
|
482
|
+
const { input, output } = io;
|
|
483
|
+
const setRawMode = input.setRawMode;
|
|
484
|
+
if (!input.isTTY || !output.isTTY || !setRawMode) {
|
|
485
|
+
throw new CliError(
|
|
486
|
+
"Cannot prompt for a secret in a non-interactive terminal.",
|
|
487
|
+
"Use 'dotcloak set KEY=VALUE' or rerun the command in a TTY."
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
return await new Promise((resolve8, reject) => {
|
|
491
|
+
let value = "";
|
|
492
|
+
const previousRawMode = input.isRaw ?? false;
|
|
493
|
+
const cleanup = () => {
|
|
494
|
+
input.off("data", onData);
|
|
495
|
+
setRawMode(previousRawMode);
|
|
496
|
+
input.pause();
|
|
497
|
+
output.write("\n");
|
|
498
|
+
};
|
|
499
|
+
const appendChunk = (chunk) => {
|
|
500
|
+
for (const character of chunk) {
|
|
501
|
+
if (character === "\r" || character === "\n") {
|
|
502
|
+
cleanup();
|
|
503
|
+
resolve8(value);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (character === "" || character === "") {
|
|
507
|
+
cleanup();
|
|
508
|
+
reject(
|
|
509
|
+
new CliError(
|
|
510
|
+
"Secret entry was cancelled.",
|
|
511
|
+
"Run 'dotcloak set KEY' again to retry the hidden prompt."
|
|
512
|
+
)
|
|
513
|
+
);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (character === "\b" || character === "\x7F") {
|
|
517
|
+
value = value.slice(0, -1);
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
if (character < " " && character !== " ") {
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
value += character;
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
const onData = (chunk) => {
|
|
527
|
+
appendChunk(typeof chunk === "string" ? chunk : chunk.toString("utf-8"));
|
|
528
|
+
};
|
|
529
|
+
output.write(prompt);
|
|
530
|
+
input.setEncoding?.("utf-8");
|
|
531
|
+
input.resume();
|
|
532
|
+
setRawMode(true);
|
|
533
|
+
input.on("data", onData);
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/cli/commands/set.ts
|
|
538
|
+
function registerSetCommand(program2) {
|
|
539
|
+
program2.command("set").description("Set a secret (KEY=VALUE or hidden prompt)").argument("<key-or-key=value>", "Key name or Key=Value pair").option("-f, --file <path>", "Path to .env.cloak file", ".env.cloak").action(
|
|
540
|
+
withCliErrorHandling(async (keyOrKeyValue, options) => {
|
|
541
|
+
const projectDir = process.cwd();
|
|
542
|
+
const cloakPath = path9.resolve(projectDir, options.file);
|
|
543
|
+
const eqIndex = keyOrKeyValue.indexOf("=");
|
|
544
|
+
const hasInlineValue = eqIndex !== -1;
|
|
545
|
+
const key = hasInlineValue ? keyOrKeyValue.slice(0, eqIndex) : keyOrKeyValue;
|
|
546
|
+
if (key.trim().length === 0) {
|
|
547
|
+
fail(
|
|
548
|
+
"Secret key is empty.",
|
|
549
|
+
"Use 'dotcloak set KEY=VALUE' or 'dotcloak set KEY' with a non-empty key."
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
const value = hasInlineValue ? keyOrKeyValue.slice(eqIndex + 1) : await promptHiddenValue(`Enter value for ${key}: `);
|
|
553
|
+
await setSecret(projectDir, cloakPath, key, value);
|
|
554
|
+
console.log(`Set ${key}`);
|
|
555
|
+
}, "Use 'dotcloak set KEY=VALUE' or 'dotcloak set KEY' and try again.")
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/cli/commands/status.ts
|
|
560
|
+
import * as fs8 from "fs";
|
|
561
|
+
import * as path10 from "path";
|
|
562
|
+
function registerStatusCommand(program2) {
|
|
563
|
+
program2.command("status").description("Show dotcloak status").action(
|
|
564
|
+
withCliErrorHandling(() => {
|
|
565
|
+
const projectDir = process.cwd();
|
|
566
|
+
const keyExists = fs8.existsSync(path10.join(projectDir, ".dotcloak", "key.age"));
|
|
567
|
+
const configExists = fs8.existsSync(path10.join(projectDir, ".dotcloak", "config.toml"));
|
|
568
|
+
const cloakExists = cloakFileExists(path10.join(projectDir, ".env.cloak"));
|
|
569
|
+
const envExists = fs8.existsSync(path10.join(projectDir, ".env"));
|
|
570
|
+
console.log("dotcloak status:");
|
|
571
|
+
console.log(` Initialized: ${keyExists && configExists ? "Yes" : "No"}`);
|
|
572
|
+
console.log(` Key file: ${keyExists ? "Found" : "Missing"}`);
|
|
573
|
+
console.log(` Config: ${configExists ? "Found" : "Missing"}`);
|
|
574
|
+
console.log(` Encrypted .env: ${cloakExists ? "Found" : "Missing"}`);
|
|
575
|
+
if (envExists) {
|
|
576
|
+
console.log("");
|
|
577
|
+
console.log(" WARNING: Unencrypted .env file detected!");
|
|
578
|
+
console.log(' Run "dotcloak init" to encrypt it.');
|
|
579
|
+
}
|
|
580
|
+
}, "Check filesystem permissions and rerun the status command.")
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// src/cli/commands/unset.ts
|
|
585
|
+
import * as path11 from "path";
|
|
586
|
+
function registerUnsetCommand(program2) {
|
|
587
|
+
program2.command("unset").description("Remove a secret").argument("<key>", "Key to remove").option("-f, --file <path>", "Path to .env.cloak file", ".env.cloak").action(
|
|
588
|
+
withCliErrorHandling(async (key, options) => {
|
|
589
|
+
const projectDir = process.cwd();
|
|
590
|
+
const cloakPath = path11.resolve(projectDir, options.file);
|
|
591
|
+
const removed = await unsetSecret(projectDir, cloakPath, key);
|
|
592
|
+
if (!removed) {
|
|
593
|
+
fail(
|
|
594
|
+
`Secret '${key}' was not found.`,
|
|
595
|
+
"Use 'dotcloak list' to confirm the key name before removing it."
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
console.log(`Removed ${key}`);
|
|
599
|
+
}, "Run 'dotcloak init' first or confirm the key exists before unsetting it.")
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// src/cli/index.ts
|
|
604
|
+
if (process.argv.length <= 2) {
|
|
605
|
+
printBanner();
|
|
606
|
+
}
|
|
607
|
+
var program = new Command();
|
|
608
|
+
program.name("dotcloak").description("Encrypt your .env so AI coding tools can't read it").version("0.1.0");
|
|
609
|
+
registerInitCommand(program);
|
|
610
|
+
registerRunCommand(program);
|
|
611
|
+
registerListCommand(program);
|
|
612
|
+
registerSetCommand(program);
|
|
613
|
+
registerUnsetCommand(program);
|
|
614
|
+
registerStatusCommand(program);
|
|
615
|
+
registerEditCommand(program);
|
|
616
|
+
registerKeyCommand(program);
|
|
617
|
+
program.parse();
|
|
618
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts","../../src/cli/banner.ts","../../src/cli/commands/edit.ts","../../src/core/cloak-file.ts","../../src/core/env-parser.ts","../../src/core/secret-manager.ts","../../src/crypto/age-engine.ts","../../src/crypto/key-manager.ts","../../src/cli/errors.ts","../../src/cli/commands/init.ts","../../src/config/defaults.ts","../../src/config/loader.ts","../../src/config/types.ts","../../src/cli/ignore-files.ts","../../src/cli/commands/key.ts","../../src/cli/commands/list.ts","../../src/cli/commands/run.ts","../../src/runner/child-process.ts","../../src/cli/commands/set.ts","../../src/cli/prompt-secret.ts","../../src/cli/commands/status.ts","../../src/cli/commands/unset.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { printBanner } from './banner.js'\nimport { registerEditCommand } from './commands/edit.js'\nimport { registerInitCommand } from './commands/init.js'\nimport { registerKeyCommand } from './commands/key.js'\nimport { registerListCommand } from './commands/list.js'\nimport { registerRunCommand } from './commands/run.js'\nimport { registerSetCommand } from './commands/set.js'\nimport { registerStatusCommand } from './commands/status.js'\nimport { registerUnsetCommand } from './commands/unset.js'\n\n// サブコマンドなしで起動した場合(--help 表示時)のみバナーを表示\nif (process.argv.length <= 2) {\n printBanner()\n}\n\nconst program = new Command()\n\nprogram\n .name('dotcloak')\n .description(\"Encrypt your .env so AI coding tools can't read it\")\n .version('0.1.0')\n\nregisterInitCommand(program)\nregisterRunCommand(program)\nregisterListCommand(program)\nregisterSetCommand(program)\nregisterUnsetCommand(program)\nregisterStatusCommand(program)\nregisterEditCommand(program)\nregisterKeyCommand(program)\n\nprogram.parse()\n","export function printBanner(): void {\n console.log(`\n _ _ _ _\n __| | ___ | |_ ___| | ___ __ _| | __\n / _' |/ _ \\\\| __/ __| |/ _ \\\\ / _' | |/ /\n | (_| | (_) | || (__| | (_) | (_| | <\n \\\\__,_|\\\\___/ \\\\__\\\\___|_|\\\\___/ \\\\__,_|_|\\\\_\\\\\n\n keep your .env invisible\n`)\n}\n","import { execSync } from 'node:child_process'\nimport * as fs from 'node:fs'\nimport * as os from 'node:os'\nimport * as path from 'node:path'\nimport * as age from 'age-encryption'\nimport type { Command } from 'commander'\nimport { writeCloakFile } from '../../core/cloak-file.js'\nimport { parse, stringify } from '../../core/env-parser.js'\nimport { getSecrets } from '../../core/secret-manager.js'\nimport { encrypt } from '../../crypto/age-engine.js'\nimport { loadIdentity } from '../../crypto/key-manager.js'\nimport { CliError, withCliErrorHandling } from '../errors.js'\n\nexport function registerEditCommand(program: Command): void {\n program\n .command('edit')\n .description('Edit secrets in your $EDITOR')\n .option('-f, --file <path>', 'Path to .env.cloak file', '.env.cloak')\n .action(\n withCliErrorHandling(async (options: { file: string }) => {\n const editor = process.env.EDITOR || process.env.VISUAL || 'vi'\n const projectDir = process.cwd()\n const cloakPath = path.resolve(projectDir, options.file)\n\n const secrets = await getSecrets(projectDir, cloakPath)\n const plaintext = stringify(secrets)\n\n const tmpFile = path.join(os.tmpdir(), `dotcloak-edit-${Date.now()}.env`)\n fs.writeFileSync(tmpFile, plaintext, { mode: 0o600 })\n\n try {\n try {\n execSync(`${editor} \"${tmpFile}\"`, { stdio: 'inherit' })\n } catch (error) {\n throw new CliError(\n `Editor '${editor}' failed while editing secrets.`,\n 'Set $EDITOR to a working editor and rerun the command.',\n { cause: error },\n )\n }\n\n const edited = fs.readFileSync(tmpFile, 'utf-8')\n const newSecrets = parse(edited)\n\n const identity = loadIdentity(projectDir)\n const recipient = await age.identityToRecipient(identity)\n const newPlaintext = stringify(newSecrets)\n const armored = await encrypt(newPlaintext, recipient)\n writeCloakFile(cloakPath, armored)\n\n console.log('Secrets updated.')\n } finally {\n if (fs.existsSync(tmpFile)) {\n fs.unlinkSync(tmpFile)\n }\n }\n }, 'Verify your editor configuration and rerun the edit command.'),\n )\n}\n","import * as fs from 'node:fs'\n\nexport function readCloakFile(filePath: string): string {\n if (!fs.existsSync(filePath)) {\n throw new Error(`Cloak file not found: ${filePath}`)\n }\n return fs.readFileSync(filePath, 'utf-8')\n}\n\nexport function writeCloakFile(filePath: string, content: string): void {\n fs.writeFileSync(filePath, content, 'utf-8')\n}\n\nexport function cloakFileExists(filePath: string): boolean {\n return fs.existsSync(filePath)\n}\n","import type { EnvMap } from './types.js'\n\nexport function parse(content: string): EnvMap {\n const env: EnvMap = new Map()\n const lines = content.split('\\n')\n\n for (const line of lines) {\n const trimmed = line.trim()\n if (trimmed === '' || trimmed.startsWith('#')) continue\n\n const withoutExport = trimmed.startsWith('export ') ? trimmed.slice(7) : trimmed\n\n const eqIndex = withoutExport.indexOf('=')\n if (eqIndex === -1) continue\n\n const key = withoutExport.slice(0, eqIndex).trim()\n let value = withoutExport.slice(eqIndex + 1).trim()\n\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1)\n }\n\n env.set(key, value)\n }\n\n return env\n}\n\nexport function stringify(env: EnvMap): string {\n const lines: string[] = []\n for (const [key, value] of env) {\n if (value.includes('\\n') || value.includes(' ') || value.includes('\"')) {\n lines.push(`${key}=\"${value.replace(/\"/g, '\\\\\"')}\"`)\n } else {\n lines.push(`${key}=${value}`)\n }\n }\n return `${lines.join('\\n')}\\n`\n}\n","import * as age from 'age-encryption'\nimport { decrypt, encrypt } from '../crypto/age-engine.js'\nimport { loadIdentity } from '../crypto/key-manager.js'\nimport { readCloakFile, writeCloakFile } from './cloak-file.js'\nimport { parse, stringify } from './env-parser.js'\nimport type { EnvMap } from './types.js'\n\nexport async function getSecrets(projectDir: string, cloakPath: string): Promise<EnvMap> {\n const identity = loadIdentity(projectDir)\n const armored = readCloakFile(cloakPath)\n const plaintext = await decrypt(armored, identity)\n return parse(plaintext)\n}\n\nexport async function setSecret(\n projectDir: string,\n cloakPath: string,\n key: string,\n value: string,\n): Promise<void> {\n const identity = loadIdentity(projectDir)\n const recipient = await age.identityToRecipient(identity)\n\n let secrets: EnvMap\n try {\n const armored = readCloakFile(cloakPath)\n const plaintext = await decrypt(armored, identity)\n secrets = parse(plaintext)\n } catch {\n secrets = new Map()\n }\n\n secrets.set(key, value)\n const plaintext = stringify(secrets)\n const armored = await encrypt(plaintext, recipient)\n writeCloakFile(cloakPath, armored)\n}\n\nexport async function unsetSecret(\n projectDir: string,\n cloakPath: string,\n key: string,\n): Promise<boolean> {\n const identity = loadIdentity(projectDir)\n const recipient = await age.identityToRecipient(identity)\n const armored = readCloakFile(cloakPath)\n const plaintext = await decrypt(armored, identity)\n const secrets = parse(plaintext)\n\n if (!secrets.has(key)) return false\n\n secrets.delete(key)\n const newPlaintext = stringify(secrets)\n const newArmored = await encrypt(newPlaintext, recipient)\n writeCloakFile(cloakPath, newArmored)\n return true\n}\n\nexport function maskValue(value: string): string {\n if (value.length <= 4) return '****'\n return value.slice(0, 2) + '*'.repeat(value.length - 4) + value.slice(-2)\n}\n","import * as age from 'age-encryption'\n\nexport async function encrypt(plaintext: string, recipient: string): Promise<string> {\n const encrypter = new age.Encrypter()\n encrypter.addRecipient(recipient)\n const encrypted = await encrypter.encrypt(plaintext)\n return age.armor.encode(encrypted)\n}\n\nexport async function decrypt(armored: string, identity: string): Promise<string> {\n const encrypted = age.armor.decode(armored)\n const decrypter = new age.Decrypter()\n decrypter.addIdentity(identity)\n const decrypted = await decrypter.decrypt(encrypted, 'text')\n return decrypted\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport * as age from 'age-encryption'\nimport type { KeyPair } from '../core/types.js'\n\nconst DOTCLOAK_DIR = '.dotcloak'\nconst KEY_FILE = 'key.age'\n\nexport async function generateKeyPair(): Promise<KeyPair> {\n const identity = await age.generateIdentity()\n const recipient = await age.identityToRecipient(identity)\n return { identity, recipient }\n}\n\nexport function saveIdentity(projectDir: string, identity: string): void {\n const dir = path.join(projectDir, DOTCLOAK_DIR)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n const keyPath = path.join(dir, KEY_FILE)\n fs.writeFileSync(keyPath, identity, { mode: 0o600 })\n}\n\nexport function loadIdentity(projectDir: string): string {\n const keyPath = path.join(projectDir, DOTCLOAK_DIR, KEY_FILE)\n if (!fs.existsSync(keyPath)) {\n throw new Error(`Key file not found: ${keyPath}\\nRun 'dotcloak init' first.`)\n }\n return fs.readFileSync(keyPath, 'utf-8').trim()\n}\n","export class CliError extends Error {\n constructor(\n public readonly whatHappened: string,\n public readonly howToFix: string,\n options?: { cause?: unknown },\n ) {\n super(whatHappened)\n this.name = 'CliError'\n\n if (options?.cause !== undefined) {\n ;(this as Error & { cause?: unknown }).cause = options.cause\n }\n }\n}\n\nexport function fail(whatHappened: string, howToFix: string): never {\n throw new CliError(whatHappened, howToFix)\n}\n\nexport function normalizeCliError(error: unknown, fallbackFix: string): CliError {\n if (error instanceof CliError) {\n return error\n }\n\n if (error instanceof Error) {\n if (error.message.startsWith('Key file not found:')) {\n return new CliError(\n 'Encryption key was not found.',\n \"Run 'dotcloak init' or 'dotcloak key import <file>' first.\",\n { cause: error },\n )\n }\n\n if (error.message.startsWith('Cloak file not found:')) {\n return new CliError(\n 'Encrypted env file was not found.',\n \"Run 'dotcloak init' or pass '--file <path>' to an existing .env.cloak file.\",\n { cause: error },\n )\n }\n\n if (error.message.startsWith('Failed to start command')) {\n return new CliError(\n error.message,\n 'Verify the command exists and is available on your PATH, then try again.',\n { cause: error },\n )\n }\n }\n\n return new CliError('Unexpected error occurred.', fallbackFix, { cause: error })\n}\n\nexport function renderCliError(error: unknown, fallbackFix: string): void {\n const cliError = normalizeCliError(error, fallbackFix)\n console.error(`What happened: ${cliError.whatHappened}`)\n console.error(`How to fix: ${cliError.howToFix}`)\n}\n\nexport function withCliErrorHandling<T extends unknown[]>(\n action: (...args: T) => Promise<void> | void,\n fallbackFix: string,\n): (...args: T) => Promise<void> {\n return async (...args: T): Promise<void> => {\n try {\n await action(...args)\n } catch (error) {\n renderCliError(error, fallbackFix)\n process.exit(1)\n }\n }\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type { Command } from 'commander'\nimport { createDefaultConfig } from '../../config/defaults.js'\nimport { saveConfig } from '../../config/loader.js'\nimport { writeCloakFile } from '../../core/cloak-file.js'\nimport { encrypt } from '../../crypto/age-engine.js'\nimport { generateKeyPair, saveIdentity } from '../../crypto/key-manager.js'\nimport { fail, withCliErrorHandling } from '../errors.js'\nimport { updateIgnoreFiles } from '../ignore-files.js'\n\nexport function registerInitCommand(program: Command): void {\n program\n .command('init')\n .description('Initialize dotcloak: generate keys and encrypt .env')\n .option('-f, --file <path>', 'Path to .env file', '.env')\n .option('--keep', 'Keep the original .env file')\n .action(\n withCliErrorHandling(async (options: { file: string; keep?: boolean }) => {\n const projectDir = process.cwd()\n const envPath = path.resolve(projectDir, options.file)\n const cloakPath = `${envPath}.cloak`\n\n if (!fs.existsSync(envPath)) {\n fail(\n `${options.file} was not found.`,\n `Create ${options.file} or pass '--file <path>' to the correct env file.`,\n )\n }\n\n console.log('Generating encryption keys...')\n const keyPair = await generateKeyPair()\n saveIdentity(projectDir, keyPair.identity)\n\n console.log('Encrypting .env...')\n const plaintext = fs.readFileSync(envPath, 'utf-8')\n const armored = await encrypt(plaintext, keyPair.recipient)\n writeCloakFile(cloakPath, armored)\n\n const config = createDefaultConfig(keyPair.recipient)\n saveConfig(projectDir, config)\n\n const updatedIgnoreFiles = updateIgnoreFiles(projectDir)\n for (const fileName of updatedIgnoreFiles) {\n console.log(`Updated ${fileName}`)\n }\n\n if (!options.keep) {\n fs.unlinkSync(envPath)\n console.log(`Deleted ${options.file}`)\n }\n\n console.log('Done! Your secrets are now encrypted.')\n console.log(` Encrypted file: ${path.relative(projectDir, cloakPath)}`)\n console.log(' Key file: .dotcloak/key.age')\n console.log('')\n console.log('Run your app with:')\n console.log(' dotcloak run <command>')\n }, \"Check the env file path and try 'dotcloak init' again.\"),\n )\n}\n","import type { DotcloakConfig } from './types.js'\n\nexport function createDefaultConfig(recipient: string): DotcloakConfig {\n return {\n dotcloak: { version: '1' },\n encryption: { recipient },\n files: { sources: ['.env'] },\n options: { delete_original: true, backup: true },\n }\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport * as TOML from 'smol-toml'\nimport { DotcloakConfigSchema } from './types.js'\nimport type { DotcloakConfig } from './types.js'\n\nconst CONFIG_FILE = 'config.toml'\nconst DOTCLOAK_DIR = '.dotcloak'\n\nexport function loadConfig(projectDir: string): DotcloakConfig {\n const configPath = path.join(projectDir, DOTCLOAK_DIR, CONFIG_FILE)\n if (!fs.existsSync(configPath)) {\n throw new Error(`Config not found: ${configPath}\\nRun 'dotcloak init' first.`)\n }\n const content = fs.readFileSync(configPath, 'utf-8')\n const parsed = TOML.parse(content)\n return DotcloakConfigSchema.parse(parsed)\n}\n\nexport function saveConfig(projectDir: string, config: DotcloakConfig): void {\n const dir = path.join(projectDir, DOTCLOAK_DIR)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n const configPath = path.join(dir, CONFIG_FILE)\n const content = TOML.stringify(config as Record<string, unknown>)\n fs.writeFileSync(configPath, content, 'utf-8')\n}\n","import { z } from 'zod'\n\nexport const DotcloakConfigSchema = z.object({\n dotcloak: z.object({\n version: z.string().default('1'),\n }),\n encryption: z.object({\n recipient: z.string(),\n }),\n files: z.object({\n sources: z.array(z.string()).default(['.env']),\n }),\n options: z.object({\n delete_original: z.boolean().default(true),\n backup: z.boolean().default(true),\n }),\n})\n\nexport type DotcloakConfig = z.infer<typeof DotcloakConfigSchema>\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\n\ntype IgnoreSpec = {\n fileName: string\n entries: string[]\n}\n\nconst IGNORE_SPECS: IgnoreSpec[] = [\n {\n fileName: '.gitignore',\n entries: ['.dotcloak/key.age', '.env', '.env.*', '!.env.cloak'],\n },\n {\n fileName: '.claudeignore',\n entries: ['.dotcloak/key.age'],\n },\n {\n fileName: '.cursorignore',\n entries: ['.dotcloak/key.age'],\n },\n]\n\nexport function updateIgnoreFiles(projectDir: string): string[] {\n const updatedFiles: string[] = []\n\n for (const spec of IGNORE_SPECS) {\n const filePath = path.join(projectDir, spec.fileName)\n const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''\n const existingLines = new Set(existing.split(/\\r?\\n/).filter((line) => line.length > 0))\n const missingEntries = spec.entries.filter((entry) => !existingLines.has(entry))\n\n if (missingEntries.length === 0) {\n continue\n }\n\n const prefix = existing.length > 0 && !existing.endsWith('\\n') ? '\\n' : ''\n const header = existingLines.has('# dotcloak') ? '' : '# dotcloak\\n'\n const addition = `${prefix}${header}${missingEntries.join('\\n')}\\n`\n\n fs.appendFileSync(filePath, addition, 'utf-8')\n updatedFiles.push(spec.fileName)\n }\n\n return updatedFiles\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type { Command } from 'commander'\nimport { loadIdentity, saveIdentity } from '../../crypto/key-manager.js'\nimport { fail, withCliErrorHandling } from '../errors.js'\n\nexport function registerKeyCommand(program: Command): void {\n const keyCmd = program.command('key').description('Manage encryption keys')\n\n keyCmd\n .command('export')\n .description('Export the secret key')\n .action(\n withCliErrorHandling(() => {\n const projectDir = process.cwd()\n const identity = loadIdentity(projectDir)\n console.log(identity)\n }, \"Run 'dotcloak init' or 'dotcloak key import <file>' before exporting a key.\"),\n )\n\n keyCmd\n .command('import')\n .description('Import a secret key')\n .argument('<keyfile>', 'Path to key file')\n .action(\n withCliErrorHandling((keyfile: string) => {\n const projectDir = process.cwd()\n const keyPath = path.resolve(keyfile)\n\n if (!fs.existsSync(keyPath)) {\n fail(\n `Key file was not found: ${keyfile}`,\n 'Pass a valid path to an exported age secret key file.',\n )\n }\n\n const identity = fs.readFileSync(keyPath, 'utf-8').trim()\n if (identity.length === 0) {\n fail('Key file is empty.', 'Export the key again and retry the import.')\n }\n\n saveIdentity(projectDir, identity)\n console.log('Key imported successfully.')\n }, 'Verify the key file path and content, then retry the import.'),\n )\n}\n","import * as path from 'node:path'\nimport type { Command } from 'commander'\nimport { getSecrets, maskValue } from '../../core/secret-manager.js'\nimport { withCliErrorHandling } from '../errors.js'\n\nexport function registerListCommand(program: Command): void {\n program\n .command('list')\n .description('List all secrets (masked values)')\n .option('-f, --file <path>', 'Path to .env.cloak file', '.env.cloak')\n .option('--show', 'Show actual values (use with caution)')\n .action(\n withCliErrorHandling(async (options: { file: string; show?: boolean }) => {\n const projectDir = process.cwd()\n const cloakPath = path.resolve(projectDir, options.file)\n\n const secrets = await getSecrets(projectDir, cloakPath)\n\n if (secrets.size === 0) {\n console.log('No secrets found.')\n return\n }\n\n for (const [key, value] of secrets) {\n const displayValue = options.show ? value : maskValue(value)\n console.log(`${key}=${displayValue}`)\n }\n }, \"Run 'dotcloak init' first or pass '--file <path>' to an existing .env.cloak file.\"),\n )\n}\n","import * as path from 'node:path'\nimport type { Command } from 'commander'\nimport { getSecrets } from '../../core/secret-manager.js'\nimport { runWithSecrets } from '../../runner/child-process.js'\nimport { withCliErrorHandling } from '../errors.js'\n\nexport function registerRunCommand(program: Command): void {\n program\n .command('run')\n .description('Run a command with decrypted secrets injected as environment variables')\n .option('-f, --file <path>', 'Path to .env.cloak file', '.env.cloak')\n .argument('<command...>', 'Command to run')\n .action(\n withCliErrorHandling(async (commandArgs: string[], options: { file: string }) => {\n const projectDir = process.cwd()\n const cloakPath = path.resolve(projectDir, options.file)\n\n const secrets = await getSecrets(projectDir, cloakPath)\n const [cmd, ...args] = commandArgs\n const exitCode = await runWithSecrets(cmd, args, secrets)\n process.exit(exitCode)\n }, \"Run 'dotcloak init' first and verify the target command exists.\"),\n )\n}\n","import { spawn } from 'node:child_process'\nimport type { EnvMap } from '../core/types.js'\n\nexport function runWithSecrets(command: string, args: string[], secrets: EnvMap): Promise<number> {\n const envVars: Record<string, string> = {}\n for (const [key, value] of secrets) {\n envVars[key] = value\n }\n\n return new Promise((resolve, reject) => {\n const child = spawn(command, args, {\n env: { ...process.env, ...envVars },\n stdio: 'inherit',\n })\n\n child.on('error', (err) => {\n reject(new Error(`Failed to start command \"${command}\": ${err.message}`))\n })\n\n child.on('exit', (code) => {\n resolve(code ?? 1)\n })\n })\n}\n","import * as path from 'node:path'\nimport type { Command } from 'commander'\nimport { setSecret } from '../../core/secret-manager.js'\nimport { fail, withCliErrorHandling } from '../errors.js'\nimport { promptHiddenValue } from '../prompt-secret.js'\n\nexport function registerSetCommand(program: Command): void {\n program\n .command('set')\n .description('Set a secret (KEY=VALUE or hidden prompt)')\n .argument('<key-or-key=value>', 'Key name or Key=Value pair')\n .option('-f, --file <path>', 'Path to .env.cloak file', '.env.cloak')\n .action(\n withCliErrorHandling(async (keyOrKeyValue: string, options: { file: string }) => {\n const projectDir = process.cwd()\n const cloakPath = path.resolve(projectDir, options.file)\n\n const eqIndex = keyOrKeyValue.indexOf('=')\n const hasInlineValue = eqIndex !== -1\n const key = hasInlineValue ? keyOrKeyValue.slice(0, eqIndex) : keyOrKeyValue\n\n if (key.trim().length === 0) {\n fail(\n 'Secret key is empty.',\n \"Use 'dotcloak set KEY=VALUE' or 'dotcloak set KEY' with a non-empty key.\",\n )\n }\n\n const value = hasInlineValue\n ? keyOrKeyValue.slice(eqIndex + 1)\n : await promptHiddenValue(`Enter value for ${key}: `)\n\n await setSecret(projectDir, cloakPath, key, value)\n console.log(`Set ${key}`)\n }, \"Use 'dotcloak set KEY=VALUE' or 'dotcloak set KEY' and try again.\"),\n )\n}\n","import { CliError } from './errors.js'\n\nexport type HiddenPromptInput = {\n isRaw?: boolean\n isTTY?: boolean\n off(event: 'data', listener: (chunk: string | Buffer) => void): unknown\n on(event: 'data', listener: (chunk: string | Buffer) => void): unknown\n pause(): void\n resume(): void\n setEncoding?(encoding: BufferEncoding): void\n setRawMode?(mode: boolean): void\n}\n\nexport type HiddenPromptOutput = {\n isTTY?: boolean\n write(chunk: string): unknown\n}\n\nexport type HiddenPromptIO = {\n input: HiddenPromptInput\n output: HiddenPromptOutput\n}\n\nexport async function promptHiddenValue(\n prompt: string,\n io: HiddenPromptIO = { input: process.stdin, output: process.stdout },\n): Promise<string> {\n const { input, output } = io\n const setRawMode = input.setRawMode\n\n if (!input.isTTY || !output.isTTY || !setRawMode) {\n throw new CliError(\n 'Cannot prompt for a secret in a non-interactive terminal.',\n \"Use 'dotcloak set KEY=VALUE' or rerun the command in a TTY.\",\n )\n }\n\n return await new Promise((resolve, reject) => {\n let value = ''\n const previousRawMode = input.isRaw ?? false\n\n const cleanup = (): void => {\n input.off('data', onData)\n setRawMode(previousRawMode)\n input.pause()\n output.write('\\n')\n }\n\n const appendChunk = (chunk: string): void => {\n for (const character of chunk) {\n if (character === '\\r' || character === '\\n') {\n cleanup()\n resolve(value)\n return\n }\n\n if (character === '\\u0003' || character === '\\u0004') {\n cleanup()\n reject(\n new CliError(\n 'Secret entry was cancelled.',\n \"Run 'dotcloak set KEY' again to retry the hidden prompt.\",\n ),\n )\n return\n }\n\n if (character === '\\u0008' || character === '\\u007f') {\n value = value.slice(0, -1)\n continue\n }\n\n if (character < ' ' && character !== '\\t') {\n continue\n }\n\n value += character\n }\n }\n\n const onData = (chunk: string | Buffer): void => {\n appendChunk(typeof chunk === 'string' ? chunk : chunk.toString('utf-8'))\n }\n\n output.write(prompt)\n input.setEncoding?.('utf-8')\n input.resume()\n setRawMode(true)\n input.on('data', onData)\n })\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type { Command } from 'commander'\nimport { cloakFileExists } from '../../core/cloak-file.js'\nimport { withCliErrorHandling } from '../errors.js'\n\nexport function registerStatusCommand(program: Command): void {\n program\n .command('status')\n .description('Show dotcloak status')\n .action(\n withCliErrorHandling(() => {\n const projectDir = process.cwd()\n const keyExists = fs.existsSync(path.join(projectDir, '.dotcloak', 'key.age'))\n const configExists = fs.existsSync(path.join(projectDir, '.dotcloak', 'config.toml'))\n const cloakExists = cloakFileExists(path.join(projectDir, '.env.cloak'))\n const envExists = fs.existsSync(path.join(projectDir, '.env'))\n\n console.log('dotcloak status:')\n console.log(` Initialized: ${keyExists && configExists ? 'Yes' : 'No'}`)\n console.log(` Key file: ${keyExists ? 'Found' : 'Missing'}`)\n console.log(` Config: ${configExists ? 'Found' : 'Missing'}`)\n console.log(` Encrypted .env: ${cloakExists ? 'Found' : 'Missing'}`)\n\n if (envExists) {\n console.log('')\n console.log(' WARNING: Unencrypted .env file detected!')\n console.log(' Run \"dotcloak init\" to encrypt it.')\n }\n }, 'Check filesystem permissions and rerun the status command.'),\n )\n}\n","import * as path from 'node:path'\nimport type { Command } from 'commander'\nimport { unsetSecret } from '../../core/secret-manager.js'\nimport { fail, withCliErrorHandling } from '../errors.js'\n\nexport function registerUnsetCommand(program: Command): void {\n program\n .command('unset')\n .description('Remove a secret')\n .argument('<key>', 'Key to remove')\n .option('-f, --file <path>', 'Path to .env.cloak file', '.env.cloak')\n .action(\n withCliErrorHandling(async (key: string, options: { file: string }) => {\n const projectDir = process.cwd()\n const cloakPath = path.resolve(projectDir, options.file)\n\n const removed = await unsetSecret(projectDir, cloakPath, key)\n if (!removed) {\n fail(\n `Secret '${key}' was not found.`,\n \"Use 'dotcloak list' to confirm the key name before removing it.\",\n )\n }\n\n console.log(`Removed ${key}`)\n }, \"Run 'dotcloak init' first or confirm the key exists before unsetting it.\"),\n )\n}\n"],"mappings":"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","names":["fs","path","age","age","fs","age","armored","plaintext","program","fs","path","fs","path","DOTCLOAK_DIR","DOTCLOAK_DIR","fs","path","program","fs","path","program","path","program","path","resolve","program","path","resolve","program","fs","path","program","path","program"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dotcloak",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Encrypt your .env so AI coding tools can't read it",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dotcloak": "./dist/cli/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup",
|
|
11
|
+
"dev": "tsx src/cli/index.ts",
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"test:coverage": "vitest run --coverage",
|
|
16
|
+
"lint": "biome check .",
|
|
17
|
+
"lint:fix": "biome check --write .",
|
|
18
|
+
"format": "biome format --write .",
|
|
19
|
+
"verify": "npm run lint && npm run typecheck && npm run test && npm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"dotenv",
|
|
23
|
+
"encryption",
|
|
24
|
+
"secrets",
|
|
25
|
+
"ai",
|
|
26
|
+
"security",
|
|
27
|
+
"age",
|
|
28
|
+
"env",
|
|
29
|
+
"environment-variables"
|
|
30
|
+
],
|
|
31
|
+
"author": "3062-in-zamud",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/3062-in-zamud/dotcloak.git"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/3062-in-zamud/dotcloak",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/3062-in-zamud/dotcloak/issues"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20"
|
|
43
|
+
},
|
|
44
|
+
"files": ["dist/cli"],
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"age-encryption": "^0.3.0",
|
|
47
|
+
"commander": "^14.0.0",
|
|
48
|
+
"smol-toml": "^1.3.0",
|
|
49
|
+
"zod": "^3.24.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@biomejs/biome": "^1.9.0",
|
|
53
|
+
"@types/node": "^22.0.0",
|
|
54
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
55
|
+
"tsup": "^8.4.0",
|
|
56
|
+
"tsx": "^4.21.0",
|
|
57
|
+
"typescript": "^5.7.0",
|
|
58
|
+
"vitest": "^3.0.0"
|
|
59
|
+
}
|
|
60
|
+
}
|