@envsave/cli 1.0.43 → 1.0.45

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.
Files changed (3) hide show
  1. package/README.md +79 -17
  2. package/dist/cli.js +47 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -26,11 +26,11 @@ import { get } from "@envsave/cli";
26
26
  const openai = new OpenAI({ apiKey: get("es_7a3f2b1e9c8d4f6b2a1c3d5f") });
27
27
  ```
28
28
 
29
- This is the single problem this project is built around. Everything else — the CLI, the license server, the Python bridge, the cloud backup — exists to make opaque-key-based secrets practical to use day to day.
29
+ This is the single problem this project is built around. Everything else — the CLI, the license server, the Python bridge, the Vercel push, the cloud backup — exists to make opaque-key-based secrets practical to use day to day.
30
30
 
31
31
  ## Pricing
32
32
 
33
- Local personal use is **free and unlimited** — store as many secrets as you want with no license. A license is only required to sync your vault to the cloud; activate one with `envsave activate <token>` — get yours at https://envsave.com.
33
+ Local personal use is **free and unlimited** — store as many secrets as you want, on as many machines as you want, with no license required. A license is only needed to sync your vault to the EnvSave cloud (backup / restore / sync across machines). Activate one with `envsave activate <token>` — get yours at https://envsave.com.
34
34
 
35
35
  ## Installation
36
36
 
@@ -48,8 +48,16 @@ Python users:
48
48
  pip install envsave
49
49
  ```
50
50
 
51
+ Check the installed version any time:
52
+
53
+ ```bash
54
+ envsave --version
55
+ ```
56
+
51
57
  ## Activation
52
58
 
59
+ Activation is only required for cloud sync. Local use works without it.
60
+
53
61
  ### Online (production)
54
62
 
55
63
  Requires the license server running at `https://api.envsave.com`.
@@ -92,12 +100,12 @@ You'll be prompted to set and confirm a master password.
92
100
  ```bash
93
101
  envsave set OPENAI_API_KEY sk-abc123
94
102
  # Output:
95
- # OPENAI_API_KEY → es_7a3f2b1e9c8d4f6b
103
+ # Added: OPENAI_API_KEY → es_7a3f2b1e9c8d4f6b
96
104
  # Use this in your code to access OPENAI_API_KEY:
97
105
  # envsave.get("es_7a3f2b1e9c8d4f6b")
98
106
  ```
99
107
 
100
- The opaque key (`es_...`) is an HMAC-SHA256 hash — no trace of the original key name.
108
+ 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.
101
109
 
102
110
  ## Retrieving Secrets
103
111
 
@@ -106,6 +114,9 @@ The opaque key (`es_...`) is an HMAC-SHA256 hash — no trace of the original ke
106
114
  ```bash
107
115
  # By opaque key (tries session cache first, then prompts for password)
108
116
  envsave get es_7a3f2b1e9c8d4f6b
117
+
118
+ # By clean name (password required — returns the plaintext value)
119
+ envsave show OPENAI_API_KEY
109
120
  ```
110
121
 
111
122
  ### Node.js / Bun
@@ -131,28 +142,78 @@ api_key = get("es_7a3f2b1e9c8d4f6b")
131
142
  ## Other Commands
132
143
 
133
144
  ```bash
134
- # Show opaque key for a named secret (requires password)
135
- envsave reveal OPENAI_API_KEY
145
+ # Show opaque key(s) for any name matching a keyword (password required)
146
+ envsave reveal OPENAI # matches OPENAI_API_KEY, OPENAI_ORG, …
147
+ envsave reveal OPENAI_API_KEY DATABASE_URL # multiple keywords at once
136
148
 
137
- # Reveal multiple keys at once
138
- envsave reveal OPENAI_API_KEY DATABASE_URL STRIPE_KEY
149
+ # Show the real value by clean name (password required)
150
+ envsave show OPENAI_API_KEY
139
151
 
140
- # List all stored key names (requires password)
152
+ # Search clean names without revealing opaque keys (no password if session is warm)
153
+ envsave search openai
154
+
155
+ # Scan the current project for hardcoded env references and opaque keys
156
+ envsave scan [dir]
157
+
158
+ # List all stored key names (password required)
141
159
  envsave list
142
160
 
143
- # Delete a secret (requires password)
161
+ # Delete a secret (password required)
144
162
  envsave delete OPENAI_API_KEY
145
163
 
146
- # Bulk import from .env file (requires password)
164
+ # Rename a secret (password required)
165
+ # Creates a new opaque key derived from the new name, copies the value over,
166
+ # and deletes the old opaque key. The output prints OLD → NEW for easy
167
+ # search-and-replace in your code.
168
+ envsave rename CLERK_SECRET_KEY_THEHOTELAGENTS CLERK_SECRET_KEY_DIRECTBOOKAI
169
+ # Output:
170
+ # Renamed: CLERK_SECRET_KEY_THEHOTELAGENTS → CLERK_SECRET_KEY_DIRECTBOOKAI
171
+ # Replace in your code:
172
+ # OLD (deleted): es_7cXxcbsVaJQv...
173
+ # NEW (created): es_enLaxEjzTO5r...
174
+
175
+ # Bulk import from .env file (password required)
147
176
  envsave import .env
148
177
 
149
- # Export all secrets as .env format (requires password)
178
+ # Export all secrets as .env format (password required)
150
179
  envsave export
180
+
181
+ # Change master password
182
+ envsave passwd
183
+
184
+ # Rate limit config (password required to change)
185
+ envsave config # show current
186
+ envsave config --max-gets 120 # set max gets/minute
187
+
188
+ # Show version
189
+ envsave --version
190
+ ```
191
+
192
+ ## Deploying to Vercel
193
+
194
+ `envsave vercel` pushes selected secrets to a Vercel project so serverless functions and build-time code both work without any `.env` on disk.
195
+
196
+ ```bash
197
+ # In a directory already linked with `vercel link`
198
+ envsave vercel OPENAI_API_KEY DATABASE_URL
199
+
200
+ # Push by opaque id instead of clean name
201
+ envsave vercel es_7a3f2b1e9c8d4f6b
202
+
203
+ # Restrict to specific environments (default is production, preview, development)
204
+ envsave vercel OPENAI_API_KEY --env production,preview
151
205
  ```
152
206
 
207
+ Each secret is uploaded under **both** the opaque id (`es_...`) and the clean name (`OPENAI_API_KEY`). That means:
208
+
209
+ - **Build-time / server code** can keep calling `envsave.get("es_7a3f...")` — the opaque id is present as an env var, so no password or vault is needed in the deployed environment.
210
+ - **Runtime serverless functions** that expect `process.env.OPENAI_API_KEY` keep working unchanged.
211
+
212
+ Only the secrets you name on the command line are pushed — the rest of your vault stays local.
213
+
153
214
  ## Cloud Backup & Sync
154
215
 
155
- The encrypted vault can be backed up to the EnvSave cloud server for disaster recovery or syncing across machines. The server only stores the encrypted bytes — it never sees plaintext secrets.
216
+ Requires an active license. The encrypted vault can be backed up to the EnvSave cloud server for disaster recovery or syncing across machines. The server only stores the encrypted bytes — it never sees plaintext secrets.
156
217
 
157
218
  ```bash
158
219
  # Upload encrypted vault to cloud
@@ -179,7 +240,7 @@ envsave sync
179
240
 
180
241
  ```bash
181
242
  # 1. Install envsave
182
- bun install @envsave/cli
243
+ npm install -g @envsave/cli
183
244
 
184
245
  # 2. Activate with same license token
185
246
  envsave activate <token>
@@ -198,8 +259,9 @@ envsave list
198
259
  2. Stored at `~/.envsave/vault.enc` (binary: salt + iv + encrypted data + auth tag)
199
260
  3. Key names are HMAC-SHA256 hashes — fully opaque, no trace of original name
200
261
  4. Session cache stores decrypted secrets in memory — no password needed for repeated reads
201
- 5. Optional SSH key adds a "something you have" hardware factor
202
- 6. LLM agents only see opaque keys — they can read values but can't discover or enumerate secrets
262
+ 5. `envsave get` is rate-limited (configurable via `envsave config --max-gets`) to blunt automated scraping from a compromised session
263
+ 6. Optional SSH key adds a "something you have" hardware factor
264
+ 7. LLM agents only see opaque keys — they can read values but can't discover or enumerate secrets
203
265
 
204
266
  ## License Tokens
205
267
 
@@ -219,5 +281,5 @@ When a license token expires, the CLI automatically contacts the server to renew
219
281
  | File | Purpose |
220
282
  |------|---------|
221
283
  | `~/.envsave/vault.enc` | Encrypted vault |
222
- | `~/.envsave/license.json` | Activated license |
284
+ | `~/.envsave/license.json` | Activated license (cloud sync only) |
223
285
  | `~/.envsave/session.json` | Session cache (decrypted secrets) |
package/dist/cli.js CHANGED
@@ -100,6 +100,7 @@ __export(exports_vault, {
100
100
  searchKeys: () => searchKeys,
101
101
  revealKeysByPattern: () => revealKeysByPattern,
102
102
  revealKey: () => revealKey,
103
+ renameSecret: () => renameSecret,
103
104
  listKeys: () => listKeys,
104
105
  initVault: () => initVault,
105
106
  importEnv: () => importEnv,
@@ -446,6 +447,28 @@ function deleteSecret(key, password) {
446
447
  writeSessionCache(vault.secrets, vault.registry);
447
448
  return true;
448
449
  }
450
+ function renameSecret(oldName, newName, password) {
451
+ if (oldName === newName) {
452
+ throw new Error("Old and new names are the same.");
453
+ }
454
+ const vault = readMainVault(password);
455
+ const sshKeyContent = loadSshKey(vault);
456
+ const oldOpaque = computeOpaqueName(vault.hmacSeed, oldName, sshKeyContent);
457
+ if (!vault.registry.includes(oldName) || !(oldOpaque in vault.secrets)) {
458
+ throw new Error(`Secret '${oldName}' not found.`);
459
+ }
460
+ if (vault.registry.includes(newName)) {
461
+ throw new Error(`Secret '${newName}' already exists.`);
462
+ }
463
+ const newOpaque = computeOpaqueName(vault.hmacSeed, newName, sshKeyContent);
464
+ const value = vault.secrets[oldOpaque];
465
+ vault.secrets[newOpaque] = value;
466
+ delete vault.secrets[oldOpaque];
467
+ vault.registry = vault.registry.map((k) => k === oldName ? newName : k);
468
+ writeMainVault(vault, password);
469
+ writeSessionCache(vault.secrets, vault.registry);
470
+ return { oldOpaque, newOpaque };
471
+ }
449
472
  function listKeys(password) {
450
473
  const vault = readMainVault(password);
451
474
  writeSessionCache(vault.secrets, vault.registry);
@@ -836,7 +859,7 @@ async function checkLicense() {
836
859
  // package.json
837
860
  var package_default = {
838
861
  name: "@envsave/cli",
839
- version: "1.0.43",
862
+ version: "1.0.45",
840
863
  description: "Local secret vault for LLM-safe environments — encrypted secrets that agents can't bulk-discover",
841
864
  type: "module",
842
865
  main: "dist/index.js",
@@ -922,6 +945,7 @@ Usage:
922
945
  envsave show <key> Show real value by key name (password required)
923
946
  envsave reveal <keyword> [keyword2]... Show opaque key(s) for any name matching keyword (password required)
924
947
  envsave delete <key> Remove a secret (password required)
948
+ envsave rename <old> <new> Rename a secret (creates new opaque key, deletes old) (password required)
925
949
  envsave search <keyword> Search keys matching keyword (no password)
926
950
  envsave scan [dir] Scan project for env variable usage
927
951
  envsave passwd Change master password
@@ -1168,6 +1192,28 @@ Use 'envsave reveal <KEY>' to get the opaque key for each secret.`);
1168
1192
  }
1169
1193
  break;
1170
1194
  }
1195
+ case "rename": {
1196
+ const oldName = args[1];
1197
+ const newName = args[2];
1198
+ if (!oldName || !newName) {
1199
+ console.error("Usage: envsave rename <old> <new>");
1200
+ process.exit(1);
1201
+ }
1202
+ const password = await requirePassword();
1203
+ const { oldOpaque, newOpaque } = renameSecret(oldName, newName, password);
1204
+ console.log(`
1205
+ Renamed: ${oldName} → ${newName}`);
1206
+ console.log(`
1207
+ Replace in your code:`);
1208
+ console.log(` OLD (deleted): ${oldOpaque}`);
1209
+ console.log(` NEW (created): ${newOpaque}`);
1210
+ console.log(`
1211
+ Or replace the full call:`);
1212
+ console.log(` envsave.get("${oldOpaque}")`);
1213
+ console.log(` envsave.get("${newOpaque}")
1214
+ `);
1215
+ break;
1216
+ }
1171
1217
  case "config": {
1172
1218
  const maxGets = getFlag("--max-gets");
1173
1219
  if (!maxGets) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envsave/cli",
3
- "version": "1.0.43",
3
+ "version": "1.0.45",
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",