@envsave/cli 1.0.52 → 1.0.54
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 +14 -0
- package/dist/SKILL.md +1 -1
- package/dist/cli.js +47 -82
- package/dist/index.js +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://envsave.com/logo.png">
|
|
4
|
+
<img src="https://envsave.com/logo-light.png" alt="EnvSave" width="320">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
1
8
|
# EnvSave
|
|
2
9
|
|
|
3
10
|
A local encrypted vault for secrets (API keys, tokens, passwords) designed to be safe for use with LLM agents. Secrets are accessed via opaque keys (`es_...`) — agents can read values but can't discover or enumerate other secrets.
|
|
@@ -117,6 +124,13 @@ envsave set OPENAI_API_KEY sk-abc123
|
|
|
117
124
|
# envsave.get("es_7a3f2b1e9c8d4f6b")
|
|
118
125
|
```
|
|
119
126
|
|
|
127
|
+
If the value contains characters your shell interprets — `!` (history expansion in zsh/bash), `$`, backticks, spaces — either single-quote it (`envsave set DB_PASS 'p@ss!word'`) or omit it and you'll be prompted with hidden input:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
envsave set DB_PASS
|
|
131
|
+
# Value for DB_PASS: ********
|
|
132
|
+
```
|
|
133
|
+
|
|
120
134
|
The opaque key (`es_...`) is an HMAC-SHA256 hash — no trace of the original key name. Re-running `envsave set` on an existing key updates the value in place; the opaque key stays the same, so you don't need to touch any code.
|
|
121
135
|
|
|
122
136
|
## Retrieving Secrets
|
package/dist/SKILL.md
CHANGED
|
@@ -167,7 +167,7 @@ The command automatically:
|
|
|
167
167
|
|
|
168
168
|
| Task | User Command |
|
|
169
169
|
|------|-------------|
|
|
170
|
-
| Add a secret | `envsave set <NAME> <VALUE>` |
|
|
170
|
+
| Add a secret | `envsave set <NAME> <VALUE>` (quote values with `!`/`$`; omit VALUE to be prompted) |
|
|
171
171
|
| Get opaque key | `envsave reveal <NAME>` |
|
|
172
172
|
| Rename a secret | `envsave rename <OLD_NAME> <NEW_NAME>` (creates new opaque key, deletes old — output prints OLD → NEW for code search-and-replace) |
|
|
173
173
|
| Unlock vault | `envsave list` |
|
package/dist/cli.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
2
|
+
var __esm = (fn, res, err) => () => {
|
|
3
|
+
if (fn)
|
|
4
|
+
try {
|
|
5
|
+
res = fn(fn = 0);
|
|
6
|
+
} catch (e) {
|
|
7
|
+
err = [e];
|
|
8
|
+
}
|
|
9
|
+
if (err)
|
|
10
|
+
throw err[0];
|
|
11
|
+
return res;
|
|
15
12
|
};
|
|
16
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
13
|
|
|
18
14
|
// src/crypto.ts
|
|
19
15
|
import { createCipheriv, createDecipheriv, createHmac, randomBytes, pbkdf2Sync } from "node:crypto";
|
|
@@ -92,29 +88,6 @@ function parseEnvFile(filePath) {
|
|
|
92
88
|
var init_env_parser = () => {};
|
|
93
89
|
|
|
94
90
|
// src/vault.ts
|
|
95
|
-
var exports_vault = {};
|
|
96
|
-
__export(exports_vault, {
|
|
97
|
-
vaultExists: () => vaultExists,
|
|
98
|
-
showSecret: () => showSecret,
|
|
99
|
-
setSecret: () => setSecret,
|
|
100
|
-
searchKeys: () => searchKeys,
|
|
101
|
-
revealKeysByPattern: () => revealKeysByPattern,
|
|
102
|
-
revealKey: () => revealKey,
|
|
103
|
-
renameSecret: () => renameSecret,
|
|
104
|
-
listKeys: () => listKeys,
|
|
105
|
-
initVault: () => initVault,
|
|
106
|
-
importEnv: () => importEnv,
|
|
107
|
-
hasSecretWithSession: () => hasSecretWithSession,
|
|
108
|
-
getSecretWithSession: () => getSecretWithSession,
|
|
109
|
-
getSecretFromCache: () => getSecretFromCache,
|
|
110
|
-
getSecret: () => getSecret,
|
|
111
|
-
getRateLimitConfig: () => getRateLimitConfig,
|
|
112
|
-
getDeploySecrets: () => getDeploySecrets,
|
|
113
|
-
exportEnv: () => exportEnv,
|
|
114
|
-
deleteSecret: () => deleteSecret,
|
|
115
|
-
configureRateLimit: () => configureRateLimit,
|
|
116
|
-
changePassword: () => changePassword
|
|
117
|
-
});
|
|
118
91
|
import { readFileSync as readFileSync2, writeFileSync, existsSync, mkdirSync, chmodSync, appendFileSync } from "node:fs";
|
|
119
92
|
import { join } from "node:path";
|
|
120
93
|
import { homedir } from "node:os";
|
|
@@ -527,19 +500,6 @@ function getDeploySecrets(keys, password) {
|
|
|
527
500
|
}
|
|
528
501
|
return results;
|
|
529
502
|
}
|
|
530
|
-
function getSecretWithSession(opaqueKey) {
|
|
531
|
-
return getSecretFromCache(opaqueKey);
|
|
532
|
-
}
|
|
533
|
-
function hasSecretWithSession(opaqueKey) {
|
|
534
|
-
try {
|
|
535
|
-
const cache = readSessionCache();
|
|
536
|
-
if (!cache)
|
|
537
|
-
return false;
|
|
538
|
-
return opaqueKey in cache.secrets;
|
|
539
|
-
} catch {
|
|
540
|
-
return false;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
503
|
var DEFAULT_RATE_LIMIT_WINDOW_MS, DEFAULT_RATE_LIMIT_MAX = 30, BASE_CHARS_V3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@$&*=-!", BASE_CHARS_V4 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", BASE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
|
|
544
504
|
var init_vault = __esm(() => {
|
|
545
505
|
init_crypto();
|
|
@@ -548,10 +508,6 @@ var init_vault = __esm(() => {
|
|
|
548
508
|
});
|
|
549
509
|
|
|
550
510
|
// src/scanner.ts
|
|
551
|
-
var exports_scanner = {};
|
|
552
|
-
__export(exports_scanner, {
|
|
553
|
-
scanProject: () => scanProject
|
|
554
|
-
});
|
|
555
511
|
import { readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
556
512
|
import { join as join3, relative } from "node:path";
|
|
557
513
|
function walkDir(dir, base, results) {
|
|
@@ -860,7 +816,7 @@ async function checkLicense() {
|
|
|
860
816
|
// package.json
|
|
861
817
|
var package_default = {
|
|
862
818
|
name: "@envsave/cli",
|
|
863
|
-
version: "1.0.
|
|
819
|
+
version: "1.0.54",
|
|
864
820
|
description: "Local secret vault for LLM-safe environments — encrypted secrets that agents can't bulk-discover",
|
|
865
821
|
type: "module",
|
|
866
822
|
main: "dist/index.js",
|
|
@@ -873,8 +829,8 @@ var package_default = {
|
|
|
873
829
|
"python"
|
|
874
830
|
],
|
|
875
831
|
scripts: {
|
|
876
|
-
prebuild: `bun -e "const p=require('./package.json');const v=p.version.split('.');v[2]=+v[2]+1;p.version=v.join('.');require('fs').writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')"`,
|
|
877
832
|
build: "bun build src/cli.ts --outdir dist --target node --format esm && bun build src/index.ts --outdir dist --target node --format esm --outfile dist/index.js && cp .claude/skills/envsave/SKILL.md dist/SKILL.md",
|
|
833
|
+
release: "bun run scripts/release.ts",
|
|
878
834
|
dev: "bun run src/cli.ts",
|
|
879
835
|
webapp: "bun run webapp/server.ts",
|
|
880
836
|
"lint:claude": "claude -p 'you are a linter. please look at the changes vs. main and report any issues related to typos. report the filename and line number on one line, and a description of the issue on the second line. do not return any other text.'"
|
|
@@ -944,7 +900,7 @@ EnvSave — Local Secret Vault for LLM-Safe Environments
|
|
|
944
900
|
Usage:
|
|
945
901
|
envsave activate <token> [--offline] Activate license (--offline skips server)
|
|
946
902
|
envsave init [--env <f>] [--ssh-key <f>] Initialize vault
|
|
947
|
-
envsave set <key>
|
|
903
|
+
envsave set <key> [value] Add/update a secret (omit value to be prompted — safe for !, $, spaces)
|
|
948
904
|
envsave get <opaque_key> Retrieve a value (no password needed)
|
|
949
905
|
envsave show <key> Show real value by key name (password required)
|
|
950
906
|
envsave reveal <keyword> [keyword2]... Show opaque key(s) for any name matching keyword (password required)
|
|
@@ -991,24 +947,26 @@ async function promptPassword(prompt = "Master password: ") {
|
|
|
991
947
|
stdin.setRawMode(true);
|
|
992
948
|
}
|
|
993
949
|
let password = "";
|
|
994
|
-
const onData = (
|
|
995
|
-
const c
|
|
996
|
-
|
|
950
|
+
const onData = (chunk) => {
|
|
951
|
+
for (const c of chunk.toString()) {
|
|
952
|
+
if (c === `
|
|
997
953
|
` || c === "\r" || c === "\x04") {
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
954
|
+
if (stdin.isTTY && stdin.setRawMode) {
|
|
955
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
956
|
+
}
|
|
957
|
+
stdin.removeListener("data", onData);
|
|
958
|
+
process.stderr.write(`
|
|
1003
959
|
`);
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
960
|
+
rl.close();
|
|
961
|
+
resolve(password);
|
|
962
|
+
return;
|
|
963
|
+
} else if (c === "\x03") {
|
|
964
|
+
process.exit(1);
|
|
965
|
+
} else if (c === "" || c === "\b") {
|
|
966
|
+
password = password.slice(0, -1);
|
|
967
|
+
} else {
|
|
968
|
+
password += c;
|
|
969
|
+
}
|
|
1012
970
|
}
|
|
1013
971
|
};
|
|
1014
972
|
stdin.on("data", onData);
|
|
@@ -1098,11 +1056,18 @@ Use 'envsave reveal <KEY>' to get the opaque key for each secret.`);
|
|
|
1098
1056
|
}
|
|
1099
1057
|
case "set": {
|
|
1100
1058
|
const key = args[1];
|
|
1101
|
-
|
|
1102
|
-
if (!key
|
|
1103
|
-
console.error("Usage: envsave set <key>
|
|
1059
|
+
let value = args[2];
|
|
1060
|
+
if (!key) {
|
|
1061
|
+
console.error("Usage: envsave set <key> [value]");
|
|
1104
1062
|
process.exit(1);
|
|
1105
1063
|
}
|
|
1064
|
+
if (value === undefined) {
|
|
1065
|
+
value = await promptPassword(`Value for ${key}: `);
|
|
1066
|
+
if (!value) {
|
|
1067
|
+
console.error("Error: value cannot be empty.");
|
|
1068
|
+
process.exit(1);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1106
1071
|
const password = await requirePassword();
|
|
1107
1072
|
const { opaque, updated } = setSecret(key, value, password);
|
|
1108
1073
|
console.log(`
|
|
@@ -1224,9 +1189,9 @@ Use 'envsave reveal <KEY>' to get the opaque key for each secret.`);
|
|
|
1224
1189
|
case "config": {
|
|
1225
1190
|
const maxGets = getFlag("--max-gets");
|
|
1226
1191
|
if (!maxGets) {
|
|
1227
|
-
const
|
|
1192
|
+
const config = getRateLimitConfig();
|
|
1228
1193
|
console.log(`Rate limit config:`);
|
|
1229
|
-
console.log(` Max gets per minute: ${
|
|
1194
|
+
console.log(` Max gets per minute: ${config.maxGetsPerMinute}`);
|
|
1230
1195
|
break;
|
|
1231
1196
|
}
|
|
1232
1197
|
const password = await requirePassword();
|
|
@@ -1264,8 +1229,8 @@ Use 'envsave reveal <KEY>' to get the opaque key for each secret.`);
|
|
|
1264
1229
|
console.error("Passwords do not match.");
|
|
1265
1230
|
process.exit(1);
|
|
1266
1231
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1232
|
+
await Promise.resolve().then(() => init_vault());
|
|
1233
|
+
changePassword(oldPassword, newPassword);
|
|
1269
1234
|
console.log("Password changed successfully!");
|
|
1270
1235
|
break;
|
|
1271
1236
|
}
|
|
@@ -1296,8 +1261,8 @@ ${matches.length} key(s) matching "${keyword}".`);
|
|
|
1296
1261
|
}
|
|
1297
1262
|
case "scan": {
|
|
1298
1263
|
const scanDir = args.slice(1).find((a) => !a.startsWith("--")) || process.cwd();
|
|
1299
|
-
|
|
1300
|
-
const results =
|
|
1264
|
+
await Promise.resolve().then(() => init_scanner());
|
|
1265
|
+
const results = scanProject(scanDir);
|
|
1301
1266
|
if (results.length === 0) {
|
|
1302
1267
|
console.log("No env variable usage found.");
|
|
1303
1268
|
} else {
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@envsave/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.54",
|
|
4
4
|
"description": "Local secret vault for LLM-safe environments — encrypted secrets that agents can't bulk-discover",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"python"
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
|
-
"prebuild": "bun -e \"const p=require('./package.json');const v=p.version.split('.');v[2]=+v[2]+1;p.version=v.join('.');require('fs').writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')\"",
|
|
17
16
|
"build": "bun build src/cli.ts --outdir dist --target node --format esm && bun build src/index.ts --outdir dist --target node --format esm --outfile dist/index.js && cp .claude/skills/envsave/SKILL.md dist/SKILL.md",
|
|
17
|
+
"release": "bun run scripts/release.ts",
|
|
18
18
|
"dev": "bun run src/cli.ts",
|
|
19
19
|
"webapp": "bun run webapp/server.ts",
|
|
20
20
|
"lint:claude": "claude -p 'you are a linter. please look at the changes vs. main and report any issues related to typos. report the filename and line number on one line, and a description of the issue on the second line. do not return any other text.'"
|