evm-cli 2.4.0__tar.gz → 2.5.0__tar.gz
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.
- {evm_cli-2.4.0/evm_cli.egg-info → evm_cli-2.5.0}/PKG-INFO +127 -5
- {evm_cli-2.4.0 → evm_cli-2.5.0}/README.md +126 -4
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm/__init__.py +1 -1
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm/_completion.py +144 -1
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm/cli.py +206 -3
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm/manager.py +93 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0/evm_cli.egg-info}/PKG-INFO +127 -5
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm_cli.egg-info/SOURCES.txt +2 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/tests/test_coverage_gap.py +2 -1
- evm_cli-2.5.0/tests/test_inject.py +589 -0
- evm_cli-2.5.0/tests/test_shell_integration.py +327 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/tests/test_v230_fixes.py +5 -3
- {evm_cli-2.4.0 → evm_cli-2.5.0}/LICENSE +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm/__main__.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm/_crypto.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm/_groups.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm/_history.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm/_io.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm/_json.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm/_schema.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm/_typing.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm/exceptions.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm/formatters.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm_cli.egg-info/dependency_links.txt +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm_cli.egg-info/entry_points.txt +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm_cli.egg-info/not-zip-safe +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm_cli.egg-info/requires.txt +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/evm_cli.egg-info/top_level.txt +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/pyproject.toml +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/setup.cfg +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/setup.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/tests/test_cli_additional.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/tests/test_cli_boundary.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/tests/test_cli_coverage.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/tests/test_formatters.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/tests/test_io_boundary.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/tests/test_main.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/tests/test_main_entry.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.5.0}/tests/test_main_module.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: evm-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: A command-line tool for managing environment variables
|
|
5
5
|
Author-email: EVM Tool <evm@example.com>
|
|
6
6
|
License: MIT
|
|
@@ -42,7 +42,7 @@ Dynamic: license-file
|
|
|
42
42
|
|
|
43
43
|
A powerful command-line tool for managing environment variables on macOS and Linux systems.
|
|
44
44
|
|
|
45
|
-
**Version**: 2.
|
|
45
|
+
**Version**: 2.5.0
|
|
46
46
|
|
|
47
47
|
## Features
|
|
48
48
|
|
|
@@ -52,6 +52,7 @@ A powerful command-line tool for managing environment variables on macOS and Lin
|
|
|
52
52
|
- ✅ **Backup/Restore**: Timestamps and merge support
|
|
53
53
|
- ✅ **Groups**: Namespace-based organization
|
|
54
54
|
- ✅ **Execute**: Run commands with custom environment
|
|
55
|
+
- ✅ **Inject**: Load variables into the current shell via `eval "$(evm inject)"`
|
|
55
56
|
- ✅ **Load to Memory**: Sync variables to system environment
|
|
56
57
|
- ✅ **Secrets**: HKDF+HMAC-CTR encrypted storage (v3) with auto-migration from v1/v2
|
|
57
58
|
- ✅ **Templates**: `{{VAR}}` reference expansion
|
|
@@ -61,6 +62,7 @@ A powerful command-line tool for managing environment variables on macOS and Lin
|
|
|
61
62
|
- ✅ **Schema**: Define and enforce variable formats and constraints
|
|
62
63
|
- ✅ **History**: Operation audit log with JSONL storage
|
|
63
64
|
- ✅ **Shell Completion**: bash, zsh, fish completion script generation
|
|
65
|
+
- ✅ **Shell Integration**: `evm init` auto-installs `evm-load` + completion into your rc on first use
|
|
64
66
|
- ✅ **Interactive Safety**: Confirmation prompts for destructive operations (`--force` to skip)
|
|
65
67
|
- ✅ **JSON Output**: `--json` flag for structured output (agent-friendly, stdout=data, stderr=errors)
|
|
66
68
|
- ✅ **Quiet Mode**: `--quiet` suppresses all human-readable output
|
|
@@ -98,8 +100,10 @@ evm/
|
|
|
98
100
|
│ ├── formatters.py # Terminal output formatting
|
|
99
101
|
│ └── exceptions.py # Custom exception hierarchy (17 classes)
|
|
100
102
|
├── examples/ # Example scripts
|
|
101
|
-
├── tests/ # Test suite
|
|
103
|
+
├── tests/ # Test suite
|
|
102
104
|
│ ├── test_main.py # Unit + integration tests
|
|
105
|
+
│ ├── test_inject.py # `evm inject` + `evm-load` tests
|
|
106
|
+
│ ├── test_shell_integration.py # `evm init` + auto-install tests
|
|
103
107
|
│ ├── test_io_boundary.py # _io.py boundary tests
|
|
104
108
|
│ ├── test_cli_boundary.py # cli.py boundary tests
|
|
105
109
|
│ ├── test_v230_fixes.py # v2.3.0 code review fix tests
|
|
@@ -320,6 +324,7 @@ evm delete KEY # Delete a variable
|
|
|
320
324
|
evm setg GROUP KEY VALUE # Set in a group
|
|
321
325
|
evm groups # List groups
|
|
322
326
|
evm exec -- COMMAND # Run with env vars
|
|
327
|
+
eval "$(evm inject)" # Load vars into current shell
|
|
323
328
|
evm backup # Create backup
|
|
324
329
|
evm validate # Check schemas
|
|
325
330
|
```
|
|
@@ -403,6 +408,8 @@ evm restore backup.json --merge # Merge with existing
|
|
|
403
408
|
|
|
404
409
|
### Load to System Memory
|
|
405
410
|
|
|
411
|
+
> ⚠️ **Scope note**: `evm loadmemory` sets `os.environ` inside the **evm process itself**. When the CLI command exits, those variables are gone — your interactive shell does **not** see them. Use `evm inject` (below) to get variables into the current shell, or `manager.load_to_memory()` from a Python script to set them in your own process.
|
|
412
|
+
|
|
406
413
|
```bash
|
|
407
414
|
# Load all variables to memory (with EVM: prefix)
|
|
408
415
|
evm loadmemory
|
|
@@ -413,8 +420,72 @@ evm loadmemory --no-prefix
|
|
|
413
420
|
# Load with filter
|
|
414
421
|
evm loadmemory --prefix DEMO_
|
|
415
422
|
|
|
416
|
-
#
|
|
417
|
-
|
|
423
|
+
# Only meaningful when called from a Python script that imports evm:
|
|
424
|
+
# from evm import EnvironmentManager
|
|
425
|
+
# EnvironmentManager().load_to_memory()
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Inject to Current Shell
|
|
429
|
+
|
|
430
|
+
`evm inject` prints shell-sourceable `export` statements to stdout. Wrap it in `eval "$( ... )"` to load variables into your **current** shell session:
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
# Inject all non-grouped variables into the current shell
|
|
434
|
+
eval "$(evm inject)"
|
|
435
|
+
|
|
436
|
+
# Verify they're now in your shell
|
|
437
|
+
echo "$API_KEY"
|
|
438
|
+
|
|
439
|
+
# Target a specific shell (auto-detected from $SHELL by default)
|
|
440
|
+
eval "$(evm inject --shell bash)"
|
|
441
|
+
eval "$(evm inject --shell fish)" # fish uses `set -gx KEY VALUE`
|
|
442
|
+
|
|
443
|
+
# Inject only a group's variables (the `group:` prefix is stripped)
|
|
444
|
+
eval "$(evm inject --group prod)"
|
|
445
|
+
|
|
446
|
+
# Add a prefix to every exported key
|
|
447
|
+
eval "$(evm inject --prefix EVM_)"
|
|
448
|
+
|
|
449
|
+
# Also decrypt and inject secret variables (stored with --secret)
|
|
450
|
+
eval "$(evm inject --include-secrets)"
|
|
451
|
+
|
|
452
|
+
# Preview what would be injected, without eval-ing it
|
|
453
|
+
evm inject --dry-run
|
|
454
|
+
|
|
455
|
+
# Structured output for scripts/agents
|
|
456
|
+
evm inject --json
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
How `inject` decides what to export:
|
|
460
|
+
|
|
461
|
+
| Variable kind | Default | With flag |
|
|
462
|
+
|---|---|---|
|
|
463
|
+
| Plain (e.g. `API_KEY`) | ✅ exported | — |
|
|
464
|
+
| Grouped (e.g. `dev:DB_URL`) | ⏭️ silently skipped (invalid shell identifier) | `--group dev` strips the prefix and exports |
|
|
465
|
+
| Secret (stored with `--secret`) | ⏭️ skipped (would leak ciphertext) | `--include-secrets` decrypts and exports |
|
|
466
|
+
| Invalid shell identifier | ⏭️ skipped, reported in `--json`/`--dry-run` | — |
|
|
467
|
+
|
|
468
|
+
### The `evm-load` shortcut
|
|
469
|
+
|
|
470
|
+
Installing shell completion (see [Shell Completion](#shell-completion)) also defines an `evm-load` shell function that wraps `eval "$(evm inject)"` for you. It correctly handles the `--env-file` global-flag positioning so you don't have to:
|
|
471
|
+
|
|
472
|
+
```bash
|
|
473
|
+
# After sourcing the completion script (bash/zsh/fish), just:
|
|
474
|
+
evm-load # inject from default ~/.evm/env.json
|
|
475
|
+
evm-load --env-file ./project.json # inject from a project-specific file
|
|
476
|
+
evm-load --group prod # inject only the prod group
|
|
477
|
+
evm-load --include-secrets # also decrypt and inject secrets
|
|
478
|
+
evm-load --prefix EVM_ # namespace all keys to avoid collisions
|
|
479
|
+
|
|
480
|
+
# Verify
|
|
481
|
+
echo "$API_KEY"
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Without completion installed, you can still type the `eval` form by hand, or add the alias yourself:
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
# ~/.zshrc or ~/.bashrc
|
|
488
|
+
alias evm-load='eval "$(evm inject)"'
|
|
418
489
|
```
|
|
419
490
|
|
|
420
491
|
### Execute Commands
|
|
@@ -529,6 +600,55 @@ evm history --limit 50
|
|
|
529
600
|
evm history --clear
|
|
530
601
|
```
|
|
531
602
|
|
|
603
|
+
### Shell Integration (`evm init`)
|
|
604
|
+
|
|
605
|
+
EVM can install a shell-integration snippet into your rc file (`~/.zshrc`, `~/.bashrc`, `~/.config/fish/config.fish`). The snippet is **one line** that re-evaluates `evm init` on every shell start — so `evm-load`, tab completion, and any future integration stay in sync with the installed `evm` version automatically (no need to re-install after an upgrade).
|
|
606
|
+
|
|
607
|
+
**Auto-install on first use:** the first time you run any `evm` command, EVM detects your shell from `$SHELL`, appends the integration block to the matching rc file, and prints a notice to stderr. Subsequent commands skip (idempotent). This is the zero-config path — you don't have to do anything.
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
# Just use evm normally — on first run it prints:
|
|
611
|
+
# Installed evm shell integration to ~/.zshrc
|
|
612
|
+
# Restart your shell (or source the rc file) to enable `evm-load` and tab completion.
|
|
613
|
+
# Set EVM_NO_AUTO_INSTALL=1 to skip this.
|
|
614
|
+
|
|
615
|
+
# After restarting the shell, both `evm-load` and tab completion are available.
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
**Manual control** (if you prefer explicit, or the auto-install didn't fit your setup):
|
|
619
|
+
|
|
620
|
+
```bash
|
|
621
|
+
# Explicitly install (same as what auto-install does, but on demand)
|
|
622
|
+
evm init zsh --install # or bash / fish
|
|
623
|
+
|
|
624
|
+
# Check whether it's installed
|
|
625
|
+
evm init zsh --check # exit 0 = installed, 1 = not
|
|
626
|
+
|
|
627
|
+
# Remove the integration block from your rc file
|
|
628
|
+
evm init zsh --uninstall
|
|
629
|
+
|
|
630
|
+
# Force re-add (useful if your rc got out of sync)
|
|
631
|
+
evm init zsh --reinstall
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
**What the rc block looks like** (conda-style markers, easy to grep/remove):
|
|
635
|
+
|
|
636
|
+
```bash
|
|
637
|
+
# >>> evm shell integration >>>
|
|
638
|
+
# Auto-added by evm. Remove with: evm init zsh --uninstall
|
|
639
|
+
eval "$(evm init zsh)"
|
|
640
|
+
# <<< evm shell integration <<<
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
**Opt out of auto-install** — if you don't want EVM touching your rc file automatically:
|
|
644
|
+
|
|
645
|
+
```bash
|
|
646
|
+
export EVM_NO_AUTO_INSTALL=1 # in your rc, or just for one command:
|
|
647
|
+
EVM_NO_AUTO_INSTALL=1 evm get API_KEY
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
> 💡 The integration block installs both **tab completion** and the **`evm-load`** function (a shortcut for `eval "$(evm inject)"` that handles `--env-file` positioning). See [The `evm-load` shortcut](#the-evm-load-shortcut).
|
|
651
|
+
|
|
532
652
|
### Shell Completion
|
|
533
653
|
|
|
534
654
|
```bash
|
|
@@ -544,6 +664,8 @@ echo 'source ~/.evm-completion.zsh' >> ~/.zshrc
|
|
|
544
664
|
evm completion fish > ~/.config/fish/completions/evm.fish
|
|
545
665
|
```
|
|
546
666
|
|
|
667
|
+
> 💡 Each completion script also installs an **`evm-load`** shell function — a shortcut for `eval "$(evm inject)"` that handles `--env-file` flag positioning for you. See [The `evm-load` shortcut](#the-evm-load-shortcut) above.
|
|
668
|
+
|
|
547
669
|
### Interactive Safety
|
|
548
670
|
|
|
549
671
|
```bash
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A powerful command-line tool for managing environment variables on macOS and Linux systems.
|
|
4
4
|
|
|
5
|
-
**Version**: 2.
|
|
5
|
+
**Version**: 2.5.0
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -12,6 +12,7 @@ A powerful command-line tool for managing environment variables on macOS and Lin
|
|
|
12
12
|
- ✅ **Backup/Restore**: Timestamps and merge support
|
|
13
13
|
- ✅ **Groups**: Namespace-based organization
|
|
14
14
|
- ✅ **Execute**: Run commands with custom environment
|
|
15
|
+
- ✅ **Inject**: Load variables into the current shell via `eval "$(evm inject)"`
|
|
15
16
|
- ✅ **Load to Memory**: Sync variables to system environment
|
|
16
17
|
- ✅ **Secrets**: HKDF+HMAC-CTR encrypted storage (v3) with auto-migration from v1/v2
|
|
17
18
|
- ✅ **Templates**: `{{VAR}}` reference expansion
|
|
@@ -21,6 +22,7 @@ A powerful command-line tool for managing environment variables on macOS and Lin
|
|
|
21
22
|
- ✅ **Schema**: Define and enforce variable formats and constraints
|
|
22
23
|
- ✅ **History**: Operation audit log with JSONL storage
|
|
23
24
|
- ✅ **Shell Completion**: bash, zsh, fish completion script generation
|
|
25
|
+
- ✅ **Shell Integration**: `evm init` auto-installs `evm-load` + completion into your rc on first use
|
|
24
26
|
- ✅ **Interactive Safety**: Confirmation prompts for destructive operations (`--force` to skip)
|
|
25
27
|
- ✅ **JSON Output**: `--json` flag for structured output (agent-friendly, stdout=data, stderr=errors)
|
|
26
28
|
- ✅ **Quiet Mode**: `--quiet` suppresses all human-readable output
|
|
@@ -58,8 +60,10 @@ evm/
|
|
|
58
60
|
│ ├── formatters.py # Terminal output formatting
|
|
59
61
|
│ └── exceptions.py # Custom exception hierarchy (17 classes)
|
|
60
62
|
├── examples/ # Example scripts
|
|
61
|
-
├── tests/ # Test suite
|
|
63
|
+
├── tests/ # Test suite
|
|
62
64
|
│ ├── test_main.py # Unit + integration tests
|
|
65
|
+
│ ├── test_inject.py # `evm inject` + `evm-load` tests
|
|
66
|
+
│ ├── test_shell_integration.py # `evm init` + auto-install tests
|
|
63
67
|
│ ├── test_io_boundary.py # _io.py boundary tests
|
|
64
68
|
│ ├── test_cli_boundary.py # cli.py boundary tests
|
|
65
69
|
│ ├── test_v230_fixes.py # v2.3.0 code review fix tests
|
|
@@ -280,6 +284,7 @@ evm delete KEY # Delete a variable
|
|
|
280
284
|
evm setg GROUP KEY VALUE # Set in a group
|
|
281
285
|
evm groups # List groups
|
|
282
286
|
evm exec -- COMMAND # Run with env vars
|
|
287
|
+
eval "$(evm inject)" # Load vars into current shell
|
|
283
288
|
evm backup # Create backup
|
|
284
289
|
evm validate # Check schemas
|
|
285
290
|
```
|
|
@@ -363,6 +368,8 @@ evm restore backup.json --merge # Merge with existing
|
|
|
363
368
|
|
|
364
369
|
### Load to System Memory
|
|
365
370
|
|
|
371
|
+
> ⚠️ **Scope note**: `evm loadmemory` sets `os.environ` inside the **evm process itself**. When the CLI command exits, those variables are gone — your interactive shell does **not** see them. Use `evm inject` (below) to get variables into the current shell, or `manager.load_to_memory()` from a Python script to set them in your own process.
|
|
372
|
+
|
|
366
373
|
```bash
|
|
367
374
|
# Load all variables to memory (with EVM: prefix)
|
|
368
375
|
evm loadmemory
|
|
@@ -373,8 +380,72 @@ evm loadmemory --no-prefix
|
|
|
373
380
|
# Load with filter
|
|
374
381
|
evm loadmemory --prefix DEMO_
|
|
375
382
|
|
|
376
|
-
#
|
|
377
|
-
|
|
383
|
+
# Only meaningful when called from a Python script that imports evm:
|
|
384
|
+
# from evm import EnvironmentManager
|
|
385
|
+
# EnvironmentManager().load_to_memory()
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Inject to Current Shell
|
|
389
|
+
|
|
390
|
+
`evm inject` prints shell-sourceable `export` statements to stdout. Wrap it in `eval "$( ... )"` to load variables into your **current** shell session:
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
# Inject all non-grouped variables into the current shell
|
|
394
|
+
eval "$(evm inject)"
|
|
395
|
+
|
|
396
|
+
# Verify they're now in your shell
|
|
397
|
+
echo "$API_KEY"
|
|
398
|
+
|
|
399
|
+
# Target a specific shell (auto-detected from $SHELL by default)
|
|
400
|
+
eval "$(evm inject --shell bash)"
|
|
401
|
+
eval "$(evm inject --shell fish)" # fish uses `set -gx KEY VALUE`
|
|
402
|
+
|
|
403
|
+
# Inject only a group's variables (the `group:` prefix is stripped)
|
|
404
|
+
eval "$(evm inject --group prod)"
|
|
405
|
+
|
|
406
|
+
# Add a prefix to every exported key
|
|
407
|
+
eval "$(evm inject --prefix EVM_)"
|
|
408
|
+
|
|
409
|
+
# Also decrypt and inject secret variables (stored with --secret)
|
|
410
|
+
eval "$(evm inject --include-secrets)"
|
|
411
|
+
|
|
412
|
+
# Preview what would be injected, without eval-ing it
|
|
413
|
+
evm inject --dry-run
|
|
414
|
+
|
|
415
|
+
# Structured output for scripts/agents
|
|
416
|
+
evm inject --json
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
How `inject` decides what to export:
|
|
420
|
+
|
|
421
|
+
| Variable kind | Default | With flag |
|
|
422
|
+
|---|---|---|
|
|
423
|
+
| Plain (e.g. `API_KEY`) | ✅ exported | — |
|
|
424
|
+
| Grouped (e.g. `dev:DB_URL`) | ⏭️ silently skipped (invalid shell identifier) | `--group dev` strips the prefix and exports |
|
|
425
|
+
| Secret (stored with `--secret`) | ⏭️ skipped (would leak ciphertext) | `--include-secrets` decrypts and exports |
|
|
426
|
+
| Invalid shell identifier | ⏭️ skipped, reported in `--json`/`--dry-run` | — |
|
|
427
|
+
|
|
428
|
+
### The `evm-load` shortcut
|
|
429
|
+
|
|
430
|
+
Installing shell completion (see [Shell Completion](#shell-completion)) also defines an `evm-load` shell function that wraps `eval "$(evm inject)"` for you. It correctly handles the `--env-file` global-flag positioning so you don't have to:
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
# After sourcing the completion script (bash/zsh/fish), just:
|
|
434
|
+
evm-load # inject from default ~/.evm/env.json
|
|
435
|
+
evm-load --env-file ./project.json # inject from a project-specific file
|
|
436
|
+
evm-load --group prod # inject only the prod group
|
|
437
|
+
evm-load --include-secrets # also decrypt and inject secrets
|
|
438
|
+
evm-load --prefix EVM_ # namespace all keys to avoid collisions
|
|
439
|
+
|
|
440
|
+
# Verify
|
|
441
|
+
echo "$API_KEY"
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Without completion installed, you can still type the `eval` form by hand, or add the alias yourself:
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
# ~/.zshrc or ~/.bashrc
|
|
448
|
+
alias evm-load='eval "$(evm inject)"'
|
|
378
449
|
```
|
|
379
450
|
|
|
380
451
|
### Execute Commands
|
|
@@ -489,6 +560,55 @@ evm history --limit 50
|
|
|
489
560
|
evm history --clear
|
|
490
561
|
```
|
|
491
562
|
|
|
563
|
+
### Shell Integration (`evm init`)
|
|
564
|
+
|
|
565
|
+
EVM can install a shell-integration snippet into your rc file (`~/.zshrc`, `~/.bashrc`, `~/.config/fish/config.fish`). The snippet is **one line** that re-evaluates `evm init` on every shell start — so `evm-load`, tab completion, and any future integration stay in sync with the installed `evm` version automatically (no need to re-install after an upgrade).
|
|
566
|
+
|
|
567
|
+
**Auto-install on first use:** the first time you run any `evm` command, EVM detects your shell from `$SHELL`, appends the integration block to the matching rc file, and prints a notice to stderr. Subsequent commands skip (idempotent). This is the zero-config path — you don't have to do anything.
|
|
568
|
+
|
|
569
|
+
```bash
|
|
570
|
+
# Just use evm normally — on first run it prints:
|
|
571
|
+
# Installed evm shell integration to ~/.zshrc
|
|
572
|
+
# Restart your shell (or source the rc file) to enable `evm-load` and tab completion.
|
|
573
|
+
# Set EVM_NO_AUTO_INSTALL=1 to skip this.
|
|
574
|
+
|
|
575
|
+
# After restarting the shell, both `evm-load` and tab completion are available.
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
**Manual control** (if you prefer explicit, or the auto-install didn't fit your setup):
|
|
579
|
+
|
|
580
|
+
```bash
|
|
581
|
+
# Explicitly install (same as what auto-install does, but on demand)
|
|
582
|
+
evm init zsh --install # or bash / fish
|
|
583
|
+
|
|
584
|
+
# Check whether it's installed
|
|
585
|
+
evm init zsh --check # exit 0 = installed, 1 = not
|
|
586
|
+
|
|
587
|
+
# Remove the integration block from your rc file
|
|
588
|
+
evm init zsh --uninstall
|
|
589
|
+
|
|
590
|
+
# Force re-add (useful if your rc got out of sync)
|
|
591
|
+
evm init zsh --reinstall
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
**What the rc block looks like** (conda-style markers, easy to grep/remove):
|
|
595
|
+
|
|
596
|
+
```bash
|
|
597
|
+
# >>> evm shell integration >>>
|
|
598
|
+
# Auto-added by evm. Remove with: evm init zsh --uninstall
|
|
599
|
+
eval "$(evm init zsh)"
|
|
600
|
+
# <<< evm shell integration <<<
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
**Opt out of auto-install** — if you don't want EVM touching your rc file automatically:
|
|
604
|
+
|
|
605
|
+
```bash
|
|
606
|
+
export EVM_NO_AUTO_INSTALL=1 # in your rc, or just for one command:
|
|
607
|
+
EVM_NO_AUTO_INSTALL=1 evm get API_KEY
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
> 💡 The integration block installs both **tab completion** and the **`evm-load`** function (a shortcut for `eval "$(evm inject)"` that handles `--env-file` positioning). See [The `evm-load` shortcut](#the-evm-load-shortcut).
|
|
611
|
+
|
|
492
612
|
### Shell Completion
|
|
493
613
|
|
|
494
614
|
```bash
|
|
@@ -504,6 +624,8 @@ echo 'source ~/.evm-completion.zsh' >> ~/.zshrc
|
|
|
504
624
|
evm completion fish > ~/.config/fish/completions/evm.fish
|
|
505
625
|
```
|
|
506
626
|
|
|
627
|
+
> 💡 Each completion script also installs an **`evm-load`** shell function — a shortcut for `eval "$(evm inject)"` that handles `--env-file` flag positioning for you. See [The `evm-load` shortcut](#the-evm-load-shortcut) above.
|
|
628
|
+
|
|
507
629
|
### Interactive Safety
|
|
508
630
|
|
|
509
631
|
```bash
|
|
@@ -7,6 +7,8 @@ M3: 为 get/delete/edit/expand/validate/rename/copy 等命令
|
|
|
7
7
|
提供动态变量名补全(通过 evm list --json --quiet 获取 key 列表)。
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
def generate_bash_completion(commands: list) -> str:
|
|
12
14
|
"""生成 bash 补全脚本(含动态变量名补全)"""
|
|
@@ -93,9 +95,33 @@ _evm_completions() {{
|
|
|
93
95
|
COMPREPLY=( $(compgen -W "${{commands}} ${{global_opts}}" -- "${{cur}}") )
|
|
94
96
|
}}
|
|
95
97
|
complete -F _evm_completions evm
|
|
98
|
+
''' + _evm_load_posix('bash')
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
_EVM_LOAD_POSIX_TEMPLATE = '''# evm-load: inject EVM variables into the current shell
|
|
102
|
+
# Usage: evm-load [--env-file PATH] [--group NAME] [--include-secrets] [--prefix PREFIX]
|
|
103
|
+
evm-load() {
|
|
104
|
+
local evf=""
|
|
105
|
+
local -a rest=()
|
|
106
|
+
while (($#)); do
|
|
107
|
+
case "$1" in
|
|
108
|
+
--env-file) evf="$2"; shift 2 ;;
|
|
109
|
+
--env-file=*) evf="${1#--env-file=}"; shift ;;
|
|
110
|
+
*) rest+=("$1"); shift ;;
|
|
111
|
+
esac
|
|
112
|
+
done
|
|
113
|
+
local -a pre=()
|
|
114
|
+
[[ -n "$evf" ]] && pre=(--env-file "$evf")
|
|
115
|
+
eval "$(evm "${pre[@]}" inject --shell __SHELL__ "${rest[@]}")"
|
|
116
|
+
}
|
|
96
117
|
'''
|
|
97
118
|
|
|
98
119
|
|
|
120
|
+
def _evm_load_posix(shell: str) -> str:
|
|
121
|
+
"""Return the evm-load shell function for a POSIX shell (bash/zsh)."""
|
|
122
|
+
return _EVM_LOAD_POSIX_TEMPLATE.replace('__SHELL__', shell)
|
|
123
|
+
|
|
124
|
+
|
|
99
125
|
def generate_zsh_completion(commands: list) -> str:
|
|
100
126
|
"""生成 zsh 补全脚本(含动态变量名补全)"""
|
|
101
127
|
return f'''#compdef evm
|
|
@@ -169,7 +195,7 @@ _evm() {{
|
|
|
169
195
|
}}
|
|
170
196
|
|
|
171
197
|
_evm "$@"
|
|
172
|
-
'''
|
|
198
|
+
''' + _evm_load_posix('zsh')
|
|
173
199
|
|
|
174
200
|
|
|
175
201
|
def generate_fish_completion(commands: list) -> str:
|
|
@@ -242,6 +268,20 @@ def generate_fish_completion(commands: list) -> str:
|
|
|
242
268
|
lines.append('# Group name completion')
|
|
243
269
|
for cmd in ['setg', 'getg', 'deleteg', 'listg']:
|
|
244
270
|
lines.append(f"complete -c evm -n '__fish_seen_subcommand_from {cmd}' -a '(__evm_groups)'")
|
|
271
|
+
lines.append('')
|
|
272
|
+
|
|
273
|
+
# evm-load: inject EVM variables into the current shell
|
|
274
|
+
lines.append('# evm-load: inject EVM variables into the current shell')
|
|
275
|
+
lines.append('# Usage: evm-load [--env-file PATH] [--group NAME] [--include-secrets] [--prefix PREFIX]')
|
|
276
|
+
lines.append('function evm-load')
|
|
277
|
+
lines.append(' argparse --ignore-unknown --name=evm-load \'e/env-file=\' -- $argv')
|
|
278
|
+
lines.append(' or return')
|
|
279
|
+
lines.append(' if set -q _flag_env_file')
|
|
280
|
+
lines.append(' evm --env-file $_flag_env_file inject --shell fish $argv | source')
|
|
281
|
+
lines.append(' else')
|
|
282
|
+
lines.append(' evm inject --shell fish $argv | source')
|
|
283
|
+
lines.append(' end')
|
|
284
|
+
lines.append('end')
|
|
245
285
|
|
|
246
286
|
return '\n'.join(lines) + '\n'
|
|
247
287
|
|
|
@@ -251,3 +291,106 @@ SHELL_GENERATORS = {
|
|
|
251
291
|
'zsh': generate_zsh_completion,
|
|
252
292
|
'fish': generate_fish_completion,
|
|
253
293
|
}
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
# ── Shell 集成(rc 文件自动安装/卸载)──────────────────────
|
|
297
|
+
|
|
298
|
+
# 标记块(conda 风格)—— rc 文件中可被 grep / 行级删除
|
|
299
|
+
INTEGRATION_MARKER_START = '# >>> evm shell integration >>>'
|
|
300
|
+
INTEGRATION_MARKER_END = '# <<< evm shell integration <<<'
|
|
301
|
+
|
|
302
|
+
# Shell → rc 文件路径
|
|
303
|
+
SHELL_RC_MAP: dict[str, str] = {
|
|
304
|
+
'bash': '~/.bashrc',
|
|
305
|
+
'zsh': '~/.zshrc',
|
|
306
|
+
'fish': '~/.config/fish/config.fish',
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def get_rc_path(shell: str):
|
|
311
|
+
"""返回 shell 对应 rc 文件路径(Path),未知 shell 返回 None。"""
|
|
312
|
+
rc = SHELL_RC_MAP.get(shell)
|
|
313
|
+
if rc is None:
|
|
314
|
+
return None
|
|
315
|
+
return Path(rc).expanduser()
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def integration_block(shell: str) -> str:
|
|
319
|
+
"""生成要追加到 rc 文件的标记块文本。"""
|
|
320
|
+
return (
|
|
321
|
+
f'\n{INTEGRATION_MARKER_START}\n'
|
|
322
|
+
f'# Auto-added by evm. Remove with: evm init {shell} --uninstall\n'
|
|
323
|
+
f'eval "$(evm init {shell})"\n'
|
|
324
|
+
f'{INTEGRATION_MARKER_END}\n'
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def is_integration_installed(shell: str) -> bool:
|
|
329
|
+
"""检查该 shell 的 rc 文件是否已含 evm 标记块。"""
|
|
330
|
+
rc = get_rc_path(shell)
|
|
331
|
+
if rc is None or not rc.exists():
|
|
332
|
+
return False
|
|
333
|
+
try:
|
|
334
|
+
content = rc.read_text(encoding='utf-8')
|
|
335
|
+
except OSError:
|
|
336
|
+
return False
|
|
337
|
+
return INTEGRATION_MARKER_START in content
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def install_integration(shell: str) -> tuple[bool, str]:
|
|
341
|
+
"""把集成块追加到 shell 的 rc 文件。
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
(success, message)
|
|
345
|
+
"""
|
|
346
|
+
rc = get_rc_path(shell)
|
|
347
|
+
if rc is None:
|
|
348
|
+
return False, f"Unknown shell: {shell}"
|
|
349
|
+
|
|
350
|
+
if is_integration_installed(shell):
|
|
351
|
+
return True, f"Already installed in {rc}"
|
|
352
|
+
|
|
353
|
+
try:
|
|
354
|
+
rc.parent.mkdir(parents=True, exist_ok=True)
|
|
355
|
+
with open(rc, 'a', encoding='utf-8') as f:
|
|
356
|
+
f.write(integration_block(shell))
|
|
357
|
+
return True, f"Installed evm shell integration to {rc}"
|
|
358
|
+
except OSError as e:
|
|
359
|
+
return False, f"Failed to install to {rc}: {e}"
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def uninstall_integration(shell: str) -> tuple[bool, str]:
|
|
363
|
+
"""从 shell 的 rc 文件移除集成块(行级删除)。"""
|
|
364
|
+
rc = get_rc_path(shell)
|
|
365
|
+
if rc is None:
|
|
366
|
+
return False, f"Unknown shell: {shell}"
|
|
367
|
+
if not rc.exists():
|
|
368
|
+
return True, f"Nothing to remove ({rc} does not exist)"
|
|
369
|
+
|
|
370
|
+
try:
|
|
371
|
+
content = rc.read_text(encoding='utf-8')
|
|
372
|
+
if INTEGRATION_MARKER_START not in content:
|
|
373
|
+
return True, f"Nothing to remove (marker not found in {rc})"
|
|
374
|
+
|
|
375
|
+
# 行级删除:从 start 标记行删到 end 标记行(含两端)
|
|
376
|
+
lines = content.split('\n')
|
|
377
|
+
out: list[str] = []
|
|
378
|
+
i = 0
|
|
379
|
+
while i < len(lines):
|
|
380
|
+
if lines[i].strip() == INTEGRATION_MARKER_START:
|
|
381
|
+
# 跳过到 end 标记行(含)
|
|
382
|
+
while i < len(lines) and lines[i].strip() != INTEGRATION_MARKER_END:
|
|
383
|
+
i += 1
|
|
384
|
+
i += 1 # 跳过 end 行
|
|
385
|
+
continue
|
|
386
|
+
out.append(lines[i])
|
|
387
|
+
i += 1
|
|
388
|
+
|
|
389
|
+
new_content = '\n'.join(out)
|
|
390
|
+
# 去除因删除可能留下的尾部空行
|
|
391
|
+
new_content = new_content.rstrip('\n') + '\n' if new_content.strip() else ''
|
|
392
|
+
rc.write_text(new_content, encoding='utf-8')
|
|
393
|
+
return True, f"Removed evm shell integration from {rc}"
|
|
394
|
+
except OSError as e:
|
|
395
|
+
return False, f"Failed to uninstall from {rc}: {e}"
|
|
396
|
+
|