@akshxy/envgit 0.5.2 → 0.6.1

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
@@ -15,7 +15,7 @@ npm install -g @akshxy/envgit
15
15
  - Secrets are encrypted with **AES-256-GCM** and live in `.envgit/` — safe to commit
16
16
  - The key lives on **your machine only** (`~/.config/envgit/keys/`) — never touches the repo
17
17
  - Onboard a teammate in one command: `envgit share` → send them the link → they run `envgit join`
18
- - `envgit unpack dev` writes a clean, **beautifully formatted `.env`** grouped by service
18
+ - `envgit unpack` writes a clean, **beautifully formatted `.env`** grouped by service
19
19
 
20
20
  ---
21
21
 
@@ -35,8 +35,9 @@ envgit set -f .env.local
35
35
  git add .envgit/
36
36
  git commit -m "chore: add encrypted env"
37
37
 
38
- # 5. Write .env locally whenever you need it
39
- envgit unpack dev
38
+ # 5. Switch env and write .env locally
39
+ envgit use dev
40
+ envgit unpack
40
41
  ```
41
42
 
42
43
  ---
@@ -61,7 +62,7 @@ envgit join abc123... --code Xk9mP2...==
61
62
  # ✓ Key saved to ~/.config/envgit/keys/<project-id>.key
62
63
 
63
64
  envgit verify # confirm the key works
64
- envgit unpack dev # writes .env
65
+ envgit unpack # writes .env (picks up active env)
65
66
  ```
66
67
 
67
68
  The relay is **cryptographically blind** — it stores only AES-256-GCM ciphertext and never sees the passphrase. The link is deleted the moment your teammate uses it.
@@ -131,16 +132,15 @@ Supports 100+ services out of the box: OpenAI, Anthropic, Groq, Stripe, Supabase
131
132
  | Command | Description |
132
133
  |---------|-------------|
133
134
  | `envgit init` | Initialize project, generate key, save to `~/.config/envgit/keys/` |
134
- | `envgit keygen` | Generate a new key for the current project |
135
- | `envgit keygen --show` | Print current key |
136
- | `envgit keygen --set <key>` | Save a key for the current project |
137
135
  | `envgit share` | Upload encrypted key to a one-time relay link |
138
136
  | `envgit join <token> --code <passphrase>` | Download and save a key shared via `envgit share` |
139
- | `envgit rotate-key` | Generate new key and re-encrypt all environments |
137
+ | `envgit rotate-key` | Generate new key and re-encrypt all environments, then run `envgit share` |
140
138
  | `envgit verify` | Confirm all environments decrypt with the current key |
141
139
 
142
140
  ### Variables
143
141
 
142
+ All variable commands are **interactive** — run them without arguments to get a fuzzy search picker.
143
+
144
144
  | Command | Description |
145
145
  |---------|-------------|
146
146
  | `envgit set KEY=VALUE ...` | Set one or more variables |
@@ -149,6 +149,7 @@ Supports 100+ services out of the box: OpenAI, Anthropic, Groq, Stripe, Supabase
149
149
  | `envgit get KEY` | Print a single value |
150
150
  | `envgit delete KEY` | Remove a variable |
151
151
  | `envgit rename OLD NEW` | Rename a variable |
152
+ | `envgit copy KEY --from dev --to prod` | Copy a variable between environments |
152
153
  | `envgit list` | List all keys in the active environment |
153
154
  | `envgit list --show-values` | List keys and their values |
154
155
 
@@ -157,9 +158,9 @@ Supports 100+ services out of the box: OpenAI, Anthropic, Groq, Stripe, Supabase
157
158
  | Command | Description |
158
159
  |---------|-------------|
159
160
  | `envgit envs` | List all environments with variable counts |
161
+ | `envgit use <env>` | Switch active environment (like `git checkout`) |
160
162
  | `envgit add-env <name>` | Create a new environment |
161
- | `envgit unpack <env>` | Decrypt and write a formatted `.env` file |
162
- | `envgit copy KEY --from dev --to prod` | Copy a variable between environments |
163
+ | `envgit unpack [env]` | Decrypt and write a formatted `.env` file |
163
164
  | `envgit diff dev prod` | Show what's different between two environments |
164
165
  | `envgit diff dev prod --show-values` | Include values in the diff |
165
166
 
@@ -176,19 +177,14 @@ Supports 100+ services out of the box: OpenAI, Anthropic, Groq, Stripe, Supabase
176
177
 
177
178
  ### Utilities
178
179
 
179
-
180
180
  | Command | Description |
181
181
  |---------|-------------|
182
+ | `envgit status` | Show project root, active env, key location, `.env` state |
182
183
  | `envgit doctor` | Check everything — key, envs, git safety — in one shot |
183
184
  | `envgit audit` | Show which keys are missing across environments |
185
+ | `envgit scan` | Scan codebase for hardcoded secrets using patterns and entropy analysis |
184
186
  | `envgit template` | Generate a `.env.example` with all keys, no values |
185
- | `envgit template --output .env.example --force` | Overwrite existing file |
186
-
187
- ### Status
188
-
189
- | Command | Description |
190
- |---------|-------------|
191
- | `envgit status` | Show project root, active env, key location, `.env` state |
187
+ | `envgit fix` | Post-upgrade repair: migrate config, fix permissions, update `.gitignore` |
192
188
 
193
189
  ---
194
190
 
@@ -299,12 +295,3 @@ AES-GCM requires a unique IV per (key, message) pair. Reusing an IV with the sam
299
295
  **Why zeroize key bytes after use?**
300
296
  Node.js buffers live in V8's heap. Without explicit zeroization, key bytes can persist in memory until GC runs — and could potentially be read from a core dump or swap file. envgit calls `buffer.fill(0)` immediately after the crypto operation completes.
301
297
 
302
- ---
303
-
304
- ## Commands
305
-
306
- ### Scan
307
-
308
- | Command | Description |
309
- |---------|-------------|
310
- | `envgit scan` | Scan entire codebase for hardcoded secrets using patterns + entropy |
package/bin/envgit.js CHANGED
@@ -1,36 +1,42 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from 'module';
2
3
  import { program } from 'commander';
3
- import { init } from '../src/commands/init.js';
4
- import { set } from '../src/commands/set.js';
5
- import { get } from '../src/commands/get.js';
6
- import { unpack } from '../src/commands/unpack.js';
7
- import { list } from '../src/commands/list.js';
4
+ import { init } from '../src/commands/init.js';
5
+ import { set } from '../src/commands/set.js';
6
+ import { get } from '../src/commands/get.js';
7
+ import { unpack } from '../src/commands/unpack.js';
8
+ import { list } from '../src/commands/list.js';
8
9
  import { importEnv } from '../src/commands/import.js';
9
- import { addEnv } from '../src/commands/add-env.js';
10
- import { status } from '../src/commands/status.js';
11
- import { keygen } from '../src/commands/keygen.js';
10
+ import { addEnv } from '../src/commands/add-env.js';
11
+ import { status } from '../src/commands/status.js';
12
12
  import { deleteKey } from '../src/commands/delete.js';
13
- import { copy } from '../src/commands/copy.js';
13
+ import { copy } from '../src/commands/copy.js';
14
14
  import { renameKey } from '../src/commands/rename-key.js';
15
- import { diff } from '../src/commands/diff.js';
16
- import { run } from '../src/commands/run.js';
17
- import { envs } from '../src/commands/envs.js';
15
+ import { diff } from '../src/commands/diff.js';
16
+ import { run } from '../src/commands/run.js';
17
+ import { envs } from '../src/commands/envs.js';
18
18
  import { exportEnv } from '../src/commands/export.js';
19
- import { verify } from '../src/commands/verify.js';
19
+ import { verify } from '../src/commands/verify.js';
20
20
  import { rotateKey } from '../src/commands/rotate-key.js';
21
- import { share } from '../src/commands/share.js';
22
- import { join } from '../src/commands/join.js';
23
- import { doctor } from '../src/commands/doctor.js';
24
- import { audit } from '../src/commands/audit.js';
25
- import { template } from '../src/commands/template.js';
26
- import { scan } from '../src/commands/scan.js';
21
+ import { share } from '../src/commands/share.js';
22
+ import { join } from '../src/commands/join.js';
23
+ import { doctor } from '../src/commands/doctor.js';
24
+ import { audit } from '../src/commands/audit.js';
25
+ import { template } from '../src/commands/template.js';
26
+ import { scan } from '../src/commands/scan.js';
27
+ import { use } from '../src/commands/use.js';
28
+ import { fix } from '../src/commands/fix.js';
29
+
30
+ const { version } = createRequire(import.meta.url)('../package.json');
27
31
 
28
32
  program
29
33
  .name('envgit')
30
34
  .description('Encrypted per-project environment variable manager')
31
- .version('0.1.0')
35
+ .version(version)
32
36
  .enablePositionalOptions();
33
37
 
38
+ // ── Setup ────────────────────────────────────────────────────────────────────
39
+
34
40
  program
35
41
  .command('init')
36
42
  .description('Initialize envgit in the current project')
@@ -38,33 +44,77 @@ program
38
44
  .action(init);
39
45
 
40
46
  program
41
- .command('status')
42
- .description('Show project root, active env, key source, and .env state')
43
- .action(status);
47
+ .command('fix')
48
+ .description('Fix everything after an upgrade .gitignore, permissions, config migration')
49
+ .action(fix);
50
+
51
+ // ── Environments ──────────────────────────────────────────────────────────────
52
+
53
+ program
54
+ .command('use [env]')
55
+ .description('Switch active environment — omit to pick interactively')
56
+ .action(use);
57
+
58
+ program
59
+ .command('envs')
60
+ .description('List all environments with variable counts')
61
+ .action(envs);
62
+
63
+ program
64
+ .command('add-env <name>')
65
+ .alias('new')
66
+ .description('Create a new environment')
67
+ .action(addEnv);
68
+
69
+ program
70
+ .command('unpack [env]')
71
+ .alias('pull')
72
+ .description('Write .env for the active env — or specify one explicitly')
73
+ .action(unpack);
74
+
75
+ program
76
+ .command('diff [env1] [env2]')
77
+ .description('Show differences between two environments')
78
+ .option('--show-values', 'reveal values in diff output')
79
+ .action(diff);
80
+
81
+ // ── Variables ─────────────────────────────────────────────────────────────────
44
82
 
45
83
  program
46
84
  .command('set [assignments...]')
47
- .description('Set KEY=VALUE pairs, or load from a file with -f')
85
+ .description('Set KEY=VALUE omit args to pick interactively')
48
86
  .option('--env <name>', 'target environment')
49
- .option('-f, --file <path>', 'read variables from a .env file')
87
+ .option('-f, --file <path>', 'import from a .env file')
50
88
  .action(set);
51
89
 
52
90
  program
53
- .command('get <key>')
54
- .description('Print a value by key (defaults to active env)')
91
+ .command('get [key]')
92
+ .description('Print a value omit key to pick interactively')
55
93
  .option('--env <name>', 'target environment')
56
94
  .action(get);
57
95
 
58
96
  program
59
- .command('unpack <env>')
60
- .alias('switch')
61
- .alias('pull')
62
- .description('Decrypt <env> and write a clean .env file, sets it as active')
63
- .action(unpack);
97
+ .command('delete [key]')
98
+ .description('Remove a key — omit to pick interactively')
99
+ .option('--env <name>', 'target environment')
100
+ .action(deleteKey);
101
+
102
+ program
103
+ .command('rename [old-key] [new-key]')
104
+ .description('Rename a key — omit args to pick interactively')
105
+ .option('--env <name>', 'target environment')
106
+ .action(renameKey);
107
+
108
+ program
109
+ .command('copy [key]')
110
+ .description('Copy a key between environments — omit args to pick interactively')
111
+ .option('--from <env>', 'source environment')
112
+ .option('--to <env>', 'destination environment')
113
+ .action(copy);
64
114
 
65
115
  program
66
116
  .command('list')
67
- .description('List keys in an environment (defaults to active env)')
117
+ .description('List all keys in the active environment')
68
118
  .option('--env <name>', 'target environment')
69
119
  .option('--show-values', 'print values alongside keys')
70
120
  .action(list);
@@ -73,80 +123,50 @@ program
73
123
  .command('import')
74
124
  .description('Encrypt an existing .env file into an environment')
75
125
  .option('--env <name>', 'target environment')
76
- .option('--file <path>', 'source file to import', '.env')
126
+ .option('--file <path>', 'source file', '.env')
77
127
  .action(importEnv);
78
128
 
79
- program
80
- .command('add-env <name>')
81
- .description('Add a new environment')
82
- .action(addEnv);
129
+ // ── Key management ───────────────────────────────────────────────────────────
83
130
 
84
131
  program
85
- .command('keygen')
86
- .description('Generate or manage the encryption key')
87
- .option('--show', 'print current key (for sharing with teammates)')
88
- .option('--set <key>', 'save a received key to .envgit.key')
89
- .action(keygen);
132
+ .command('share')
133
+ .description('Upload encrypted key as a one-time link to send to a teammate')
134
+ .action(share);
90
135
 
91
136
  program
92
- .command('delete <key>')
93
- .description('Remove a key from the encrypted env')
94
- .option('--env <name>', 'target environment')
95
- .action(deleteKey);
137
+ .command('join <token>')
138
+ .description('Download and save a key shared via envgit share')
139
+ .requiredOption('--code <passphrase>', 'passphrase from the share output')
140
+ .action(join);
96
141
 
97
142
  program
98
- .command('copy <key>')
99
- .description('Copy a key\'s value between two environments')
100
- .requiredOption('--from <env>', 'source environment')
101
- .requiredOption('--to <env>', 'destination environment')
102
- .action(copy);
143
+ .command('rotate-key')
144
+ .description('Generate a new key and re-encrypt all environments')
145
+ .action(rotateKey);
103
146
 
104
- program
105
- .command('rename <old-key> <new-key>')
106
- .description('Rename a key within an environment')
107
- .option('--env <name>', 'target environment')
108
- .action(renameKey);
147
+ // ── Export & run ─────────────────────────────────────────────────────────────
109
148
 
110
149
  program
111
- .command('diff [env1] [env2]')
112
- .description('Show differences between two environments')
113
- .option('--show-values', 'reveal values in diff output')
114
- .action(diff);
150
+ .command('export')
151
+ .description('Print decrypted vars to stdout')
152
+ .option('--env <name>', 'target environment')
153
+ .option('--format <fmt>', 'dotenv | json | shell', 'dotenv')
154
+ .action(exportEnv);
115
155
 
116
156
  program
117
157
  .command('run [args...]')
118
- .description('Spawn a command with decrypted env vars injected')
158
+ .description('Run a command with decrypted env vars injected — nothing written to disk')
119
159
  .option('--env <name>', 'environment to use')
120
160
  .allowUnknownOption()
121
161
  .passThroughOptions()
122
162
  .action(run);
123
163
 
124
- program
125
- .command('envs')
126
- .description('List all environments with variable counts')
127
- .action(envs);
128
-
129
- program
130
- .command('export')
131
- .description('Print decrypted vars to stdout (dotenv, json, or shell format)')
132
- .option('--env <name>', 'target environment')
133
- .option('--format <fmt>', 'output format: dotenv, json, shell', 'dotenv')
134
- .action(exportEnv);
164
+ // ── Health & safety ──────────────────────────────────────────────────────────
135
165
 
136
166
  program
137
- .command('verify')
138
- .description('Attempt to decrypt every .enc file with the current key')
139
- .action(verify);
140
-
141
- program
142
- .command('rotate-key')
143
- .description('Generate a new key and re-encrypt all environments')
144
- .action(rotateKey);
145
-
146
- program
147
- .command('scan')
148
- .description('Scan codebase for hardcoded secrets using pattern matching and entropy analysis')
149
- .action(scan);
167
+ .command('status')
168
+ .description('Show active environment, key status, and project info')
169
+ .action(status);
150
170
 
151
171
  program
152
172
  .command('doctor')
@@ -159,21 +179,20 @@ program
159
179
  .action(audit);
160
180
 
161
181
  program
162
- .command('template')
163
- .description('Generate a .env.example with all keys but no values')
164
- .option('-o, --output <path>', 'output file path', '.env.example')
165
- .option('-f, --force', 'overwrite if file already exists')
166
- .action(template);
182
+ .command('verify')
183
+ .description('Confirm all environments decrypt correctly with the current key')
184
+ .action(verify);
167
185
 
168
186
  program
169
- .command('share')
170
- .description('Encrypt your key and upload it to a one-time link — send the output to a teammate')
171
- .action(share);
187
+ .command('scan')
188
+ .description('Scan codebase for hardcoded secrets')
189
+ .action(scan);
172
190
 
173
191
  program
174
- .command('join <token>')
175
- .description('Download and save a key from a link generated by envgit share')
176
- .requiredOption('--code <passphrase>', 'passphrase printed by envgit share')
177
- .action(join);
192
+ .command('template')
193
+ .description('Generate a .env.example with all keys, no values')
194
+ .option('-o, --output <path>', 'output path', '.env.example')
195
+ .option('-f, --force', 'overwrite existing file')
196
+ .action(template);
178
197
 
179
198
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akshxy/envgit",
3
- "version": "0.5.2",
3
+ "version": "0.6.1",
4
4
  "description": "Encrypted per-project environment variable manager",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,6 +13,7 @@
13
13
  ],
14
14
  "license": "MIT",
15
15
  "dependencies": {
16
+ "@inquirer/prompts": "^8.3.0",
16
17
  "chalk": "^5.6.2",
17
18
  "commander": "^12.0.0",
18
19
  "js-yaml": "^4.1.0"
@@ -8,6 +8,10 @@ export async function addEnv(name) {
8
8
  const key = loadKey(projectRoot);
9
9
  const config = loadConfig(projectRoot);
10
10
 
11
+ if (!/^[a-z0-9_-]+$/i.test(name)) {
12
+ fatal(`Invalid environment name '${name}' — use only letters, numbers, hyphens, and underscores.`);
13
+ }
14
+
11
15
  if (config.envs.includes(name)) {
12
16
  fatal(`Environment '${name}' already exists.`);
13
17
  }
@@ -10,7 +10,10 @@ export async function audit() {
10
10
  const config = loadConfig(projectRoot);
11
11
 
12
12
  if (config.envs.length < 2) {
13
- console.log(dim('\n Need at least 2 environments to audit.\n'));
13
+ console.log('');
14
+ console.log(dim(' Only one environment — nothing to compare.'));
15
+ console.log(dim(' Add more with: envgit add-env <name>'));
16
+ console.log('');
14
17
  return;
15
18
  }
16
19
 
@@ -1,24 +1,26 @@
1
1
  import { requireProjectRoot, loadKey } from '../keystore.js';
2
+ import { loadConfig } from '../config.js';
2
3
  import { readEncEnv, writeEncEnv } from '../enc.js';
3
- import { ok, fatal, label } from '../ui.js';
4
+ import { ok, fatal, label, envLabel } from '../ui.js';
5
+ import { pickKey, pickEnv } from '../interactive.js';
4
6
 
5
7
  export async function copy(keyName, options) {
6
- if (!options.from || !options.to) {
7
- fatal('Both --from and --to environments are required.');
8
- }
9
-
10
8
  const projectRoot = requireProjectRoot();
11
9
  const key = loadKey(projectRoot);
10
+ const config = loadConfig(projectRoot);
11
+
12
+ const from = options.from ?? await pickEnv(config.envs, 'Copy from environment');
13
+ const to = options.to ?? await pickEnv(config.envs.filter(e => e !== from), 'Copy to environment');
14
+
15
+ const srcVars = readEncEnv(projectRoot, from, key);
12
16
 
13
- const srcVars = readEncEnv(projectRoot, options.from, key);
17
+ const name = keyName ?? await pickKey(srcVars, `Key to copy from [${from}]`);
14
18
 
15
- if (!(keyName in srcVars)) {
16
- fatal(`Key '${keyName}' not found in ${label(options.from)}`);
17
- }
19
+ if (!(name in srcVars)) fatal(`Key '${name}' not found in ${envLabel(from)}`);
18
20
 
19
- const dstVars = readEncEnv(projectRoot, options.to, key);
20
- dstVars[keyName] = srcVars[keyName];
21
- writeEncEnv(projectRoot, options.to, key, dstVars);
21
+ const dstVars = readEncEnv(projectRoot, to, key);
22
+ dstVars[name] = srcVars[name];
23
+ writeEncEnv(projectRoot, to, key, dstVars);
22
24
 
23
- ok(`Copied ${keyName} from ${label(options.from)} → ${label(options.to)}`);
25
+ ok(`Copied ${name} from ${envLabel(from)} → ${envLabel(to)}`);
24
26
  }
@@ -2,7 +2,8 @@ import { requireProjectRoot, loadKey } from '../keystore.js';
2
2
  import { resolveEnv } from '../config.js';
3
3
  import { readEncEnv, writeEncEnv } from '../enc.js';
4
4
  import { getCurrentEnv } from '../state.js';
5
- import { ok, fatal, label } from '../ui.js';
5
+ import { ok, fatal, label, envLabel } from '../ui.js';
6
+ import { pickKey, promptConfirm } from '../interactive.js';
6
7
 
7
8
  export async function deleteKey(keyName, options) {
8
9
  const projectRoot = requireProjectRoot();
@@ -11,11 +12,16 @@ export async function deleteKey(keyName, options) {
11
12
 
12
13
  const vars = readEncEnv(projectRoot, envName, key);
13
14
 
14
- if (!(keyName in vars)) {
15
- fatal(`Key '${keyName}' not found in ${label(envName)}`);
15
+ const name = keyName ?? await pickKey(vars, `Key to delete from [${envName}]`);
16
+
17
+ if (!(name in vars)) fatal(`Key '${name}' not found in ${envLabel(envName)}`);
18
+
19
+ if (!keyName) {
20
+ const confirmed = await promptConfirm(`Delete ${name} from [${envName}]?`);
21
+ if (!confirmed) { process.exit(0); }
16
22
  }
17
23
 
18
- delete vars[keyName];
24
+ delete vars[name];
19
25
  writeEncEnv(projectRoot, envName, key, vars);
20
- ok(`Deleted ${keyName} from ${label(envName)}`);
26
+ ok(`Deleted ${name} from ${envLabel(envName)}`);
21
27
  }
@@ -5,11 +5,8 @@ import chalk from 'chalk';
5
5
  import { findProjectRoot, globalKeyPath } from '../keystore.js';
6
6
  import { loadConfig } from '../config.js';
7
7
  import { readEncEnv } from '../enc.js';
8
- import { bold, dim } from '../ui.js';
8
+ import { ok as pass, fail, warn, bold, dim } from '../ui.js';
9
9
 
10
- function pass(msg) { console.log(chalk.green(` ✓ ${msg}`)); }
11
- function fail(msg) { console.log(chalk.red(` ✗ ${msg}`)); }
12
- function warn(msg) { console.log(chalk.yellow(` ⚠ ${msg}`)); }
13
10
  function section(title) { console.log(`\n${bold(title)}`); }
14
11
 
15
12
  export async function doctor() {
@@ -48,7 +45,7 @@ export async function doctor() {
48
45
  pass(`Key file found ${dim(keyPath)}`);
49
46
  key = readFileSync(keyPath, 'utf8').trim();
50
47
  } else {
51
- fail('Key file missing — run: envgit share / envgit join');
48
+ fail('Key file missing — get it from a teammate: envgit join <token> --code <passphrase>');
52
49
  issues++;
53
50
  }
54
51
  } else {
@@ -57,7 +54,7 @@ export async function doctor() {
57
54
  warn(`Legacy key file at project root ${dim('(consider migrating)')}`);
58
55
  key = readFileSync(legacyPath, 'utf8').trim();
59
56
  } else {
60
- fail('No key found — run: envgit keygen');
57
+ fail('No key found — ask a teammate to run: envgit share');
61
58
  issues++;
62
59
  }
63
60
  }
@@ -1,25 +1,31 @@
1
- import chalk from 'chalk';
2
1
  import { requireProjectRoot, loadKey } from '../keystore.js';
3
2
  import { loadConfig } from '../config.js';
4
3
  import { readEncEnv } from '../enc.js';
5
4
  import { getCurrentEnv } from '../state.js';
6
- import { dim } from '../ui.js';
5
+ import { dim, envLabel } from '../ui.js';
6
+ import chalk from 'chalk';
7
7
 
8
8
  export async function envs() {
9
9
  const projectRoot = requireProjectRoot();
10
- const key = loadKey(projectRoot);
11
- const config = loadConfig(projectRoot);
10
+ const key = loadKey(projectRoot);
11
+ const config = loadConfig(projectRoot);
12
12
  const current = getCurrentEnv(projectRoot);
13
13
 
14
14
  console.log('');
15
15
  for (const envName of config.envs) {
16
16
  const isActive = envName === current;
17
- const bullet = isActive ? chalk.green('●') : ' ';
18
- const vars = readEncEnv(projectRoot, envName, key);
19
- const count = Object.keys(vars).length;
20
- const countStr = dim(`(${count} var${count !== 1 ? 's' : ''})`);
21
- const activeSuffix = isActive ? chalk.cyan(' (active)') : '';
22
- console.log(` ${bullet} ${isActive ? chalk.bold(envName) : envName} ${countStr}${activeSuffix}`);
17
+ const bullet = isActive ? chalk.green('●') : chalk.dim('');
18
+ const vars = readEncEnv(projectRoot, envName, key);
19
+ const count = Object.keys(vars).length;
20
+ const label = isActive ? envLabel(envName) : chalk.dim(`[${envName}]`);
21
+ const countStr = dim(`${count} var${count !== 1 ? 's' : ''}`);
22
+ const hint = isActive ? chalk.dim(' ← active') : '';
23
+ console.log(` ${bullet} ${label} ${countStr}${hint}`);
24
+ }
25
+
26
+ if (!current) {
27
+ console.log('');
28
+ console.log(dim(' No active environment. Run: envgit use <env>'));
23
29
  }
24
30
  console.log('');
25
31
  }