evm-cli 2.4.0__tar.gz → 2.6.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-2.6.0}/PKG-INFO +166 -5
- {evm_cli-2.4.0 → evm_cli-2.6.0}/README.md +165 -4
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/__init__.py +1 -1
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_completion.py +144 -1
- evm_cli-2.6.0/evm/_upgrade.py +164 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/cli.py +289 -4
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/manager.py +96 -1
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm_cli.egg-info/PKG-INFO +166 -5
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm_cli.egg-info/SOURCES.txt +4 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_coverage_gap.py +2 -1
- evm_cli-2.6.0/tests/test_inject.py +589 -0
- evm_cli-2.6.0/tests/test_shell_integration.py +327 -0
- evm_cli-2.6.0/tests/test_upgrade.py +488 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_v230_fixes.py +5 -3
- {evm_cli-2.4.0 → evm_cli-2.6.0}/LICENSE +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/__main__.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_crypto.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_groups.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_history.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_io.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_json.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_schema.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_typing.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/exceptions.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/formatters.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm_cli.egg-info/dependency_links.txt +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm_cli.egg-info/entry_points.txt +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm_cli.egg-info/not-zip-safe +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm_cli.egg-info/requires.txt +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/evm_cli.egg-info/top_level.txt +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/pyproject.toml +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/setup.cfg +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/setup.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_cli_additional.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_cli_boundary.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_cli_coverage.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_formatters.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_io_boundary.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_main.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_main_entry.py +0 -0
- {evm_cli-2.4.0 → evm_cli-2.6.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.6.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.6.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,8 @@ 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
|
|
66
|
+
- ✅ **Self-Upgrade**: `evm upgrade` checks PyPI and pip-installs the latest version
|
|
64
67
|
- ✅ **Interactive Safety**: Confirmation prompts for destructive operations (`--force` to skip)
|
|
65
68
|
- ✅ **JSON Output**: `--json` flag for structured output (agent-friendly, stdout=data, stderr=errors)
|
|
66
69
|
- ✅ **Quiet Mode**: `--quiet` suppresses all human-readable output
|
|
@@ -95,13 +98,22 @@ evm/
|
|
|
95
98
|
│ ├── _completion.py # Shell completion generators (bash/zsh/fish)
|
|
96
99
|
│ ├── _json.py # JSON output helpers (agent-friendly)
|
|
97
100
|
│ ├── _crypto.py # HKDF + HMAC-CTR encryption module
|
|
101
|
+
│ ├── _upgrade.py # Self-upgrade: PyPI version check + pip install
|
|
102
|
+
│ ├── _typing.py # Shared typing helpers (Protocol mixins)
|
|
98
103
|
│ ├── formatters.py # Terminal output formatting
|
|
99
104
|
│ └── exceptions.py # Custom exception hierarchy (17 classes)
|
|
100
105
|
├── examples/ # Example scripts
|
|
101
|
-
├── tests/ # Test suite
|
|
106
|
+
├── tests/ # Test suite
|
|
102
107
|
│ ├── test_main.py # Unit + integration tests
|
|
108
|
+
│ ├── test_inject.py # `evm inject` + `evm-load` tests
|
|
109
|
+
│ ├── test_shell_integration.py # `evm init` + auto-install tests
|
|
110
|
+
│ ├── test_upgrade.py # `evm upgrade` tests
|
|
103
111
|
│ ├── test_io_boundary.py # _io.py boundary tests
|
|
104
112
|
│ ├── test_cli_boundary.py # cli.py boundary tests
|
|
113
|
+
│ ├── test_cli_additional.py # cli.py additional coverage
|
|
114
|
+
│ ├── test_cli_coverage.py # cli.py coverage gap tests
|
|
115
|
+
│ ├── test_main_entry.py # `evm` entry-point tests
|
|
116
|
+
│ ├── test_main_module.py # `python -m evm` entry tests
|
|
105
117
|
│ ├── test_v230_fixes.py # v2.3.0 code review fix tests
|
|
106
118
|
│ ├── test_coverage_gap.py # Coverage gap tests (98% target)
|
|
107
119
|
│ ├── test_formatters.py # Formatter output tests
|
|
@@ -320,8 +332,11 @@ evm delete KEY # Delete a variable
|
|
|
320
332
|
evm setg GROUP KEY VALUE # Set in a group
|
|
321
333
|
evm groups # List groups
|
|
322
334
|
evm exec -- COMMAND # Run with env vars
|
|
335
|
+
eval "$(evm inject)" # Load vars into current shell
|
|
323
336
|
evm backup # Create backup
|
|
324
337
|
evm validate # Check schemas
|
|
338
|
+
evm upgrade # Upgrade to latest PyPI release
|
|
339
|
+
evm upgrade --check # Check for updates only
|
|
325
340
|
```
|
|
326
341
|
|
|
327
342
|
## Usage
|
|
@@ -403,6 +418,8 @@ evm restore backup.json --merge # Merge with existing
|
|
|
403
418
|
|
|
404
419
|
### Load to System Memory
|
|
405
420
|
|
|
421
|
+
> ⚠️ **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.
|
|
422
|
+
|
|
406
423
|
```bash
|
|
407
424
|
# Load all variables to memory (with EVM: prefix)
|
|
408
425
|
evm loadmemory
|
|
@@ -413,8 +430,72 @@ evm loadmemory --no-prefix
|
|
|
413
430
|
# Load with filter
|
|
414
431
|
evm loadmemory --prefix DEMO_
|
|
415
432
|
|
|
416
|
-
#
|
|
417
|
-
|
|
433
|
+
# Only meaningful when called from a Python script that imports evm:
|
|
434
|
+
# from evm import EnvironmentManager
|
|
435
|
+
# EnvironmentManager().load_to_memory()
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Inject to Current Shell
|
|
439
|
+
|
|
440
|
+
`evm inject` prints shell-sourceable `export` statements to stdout. Wrap it in `eval "$( ... )"` to load variables into your **current** shell session:
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
# Inject all non-grouped variables into the current shell
|
|
444
|
+
eval "$(evm inject)"
|
|
445
|
+
|
|
446
|
+
# Verify they're now in your shell
|
|
447
|
+
echo "$API_KEY"
|
|
448
|
+
|
|
449
|
+
# Target a specific shell (auto-detected from $SHELL by default)
|
|
450
|
+
eval "$(evm inject --shell bash)"
|
|
451
|
+
eval "$(evm inject --shell fish)" # fish uses `set -gx KEY VALUE`
|
|
452
|
+
|
|
453
|
+
# Inject only a group's variables (the `group:` prefix is stripped)
|
|
454
|
+
eval "$(evm inject --group prod)"
|
|
455
|
+
|
|
456
|
+
# Add a prefix to every exported key
|
|
457
|
+
eval "$(evm inject --prefix EVM_)"
|
|
458
|
+
|
|
459
|
+
# Also decrypt and inject secret variables (stored with --secret)
|
|
460
|
+
eval "$(evm inject --include-secrets)"
|
|
461
|
+
|
|
462
|
+
# Preview what would be injected, without eval-ing it
|
|
463
|
+
evm inject --dry-run
|
|
464
|
+
|
|
465
|
+
# Structured output for scripts/agents
|
|
466
|
+
evm inject --json
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
How `inject` decides what to export:
|
|
470
|
+
|
|
471
|
+
| Variable kind | Default | With flag |
|
|
472
|
+
|---|---|---|
|
|
473
|
+
| Plain (e.g. `API_KEY`) | ✅ exported | — |
|
|
474
|
+
| Grouped (e.g. `dev:DB_URL`) | ⏭️ silently skipped (invalid shell identifier) | `--group dev` strips the prefix and exports |
|
|
475
|
+
| Secret (stored with `--secret`) | ⏭️ skipped (would leak ciphertext) | `--include-secrets` decrypts and exports |
|
|
476
|
+
| Invalid shell identifier | ⏭️ skipped, reported in `--json`/`--dry-run` | — |
|
|
477
|
+
|
|
478
|
+
### The `evm-load` shortcut
|
|
479
|
+
|
|
480
|
+
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:
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
# After sourcing the completion script (bash/zsh/fish), just:
|
|
484
|
+
evm-load # inject from default ~/.evm/env.json
|
|
485
|
+
evm-load --env-file ./project.json # inject from a project-specific file
|
|
486
|
+
evm-load --group prod # inject only the prod group
|
|
487
|
+
evm-load --include-secrets # also decrypt and inject secrets
|
|
488
|
+
evm-load --prefix EVM_ # namespace all keys to avoid collisions
|
|
489
|
+
|
|
490
|
+
# Verify
|
|
491
|
+
echo "$API_KEY"
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Without completion installed, you can still type the `eval` form by hand, or add the alias yourself:
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
# ~/.zshrc or ~/.bashrc
|
|
498
|
+
alias evm-load='eval "$(evm inject)"'
|
|
418
499
|
```
|
|
419
500
|
|
|
420
501
|
### Execute Commands
|
|
@@ -529,6 +610,84 @@ evm history --limit 50
|
|
|
529
610
|
evm history --clear
|
|
530
611
|
```
|
|
531
612
|
|
|
613
|
+
### Self-Upgrade (`evm upgrade`)
|
|
614
|
+
|
|
615
|
+
Check PyPI for a newer `evm` release and pip-install it in one step. Uses only the standard library — no extra dependencies.
|
|
616
|
+
|
|
617
|
+
```bash
|
|
618
|
+
# Check whether a newer version is available (no changes made)
|
|
619
|
+
evm upgrade --check
|
|
620
|
+
# exit 0 = up to date, exit 1 = update available (or network error)
|
|
621
|
+
|
|
622
|
+
# Upgrade to the latest release
|
|
623
|
+
evm upgrade
|
|
624
|
+
# → Upgraded from 2.5.0 to 2.6.0.
|
|
625
|
+
|
|
626
|
+
# Preview the pip command without running it
|
|
627
|
+
evm upgrade --dry-run
|
|
628
|
+
|
|
629
|
+
# Skip the pre-check and run pip directly
|
|
630
|
+
evm upgrade --force
|
|
631
|
+
|
|
632
|
+
# Structured JSON output (stdout = data, stderr = errors)
|
|
633
|
+
evm upgrade --check --json
|
|
634
|
+
# stdout: {"status": "ok", "data": {"current": "2.5.0", "latest": "2.6.0", "update_available": true}}
|
|
635
|
+
|
|
636
|
+
evm upgrade --json
|
|
637
|
+
# stdout: {"status": "ok", "data": {"current": "2.5.0", "new_version": "2.6.0", "action": "upgraded", "upgraded": true, "message": "Upgraded from 2.5.0 to 2.6.0."}}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
`evm upgrade` calls `pip install --upgrade evm-cli` using the **same Python interpreter** that runs `evm`, so it upgrades the correct installation. If the network is unreachable, `--check` reports `unknown` and exits 1; a plain `evm upgrade` aborts before touching pip.
|
|
641
|
+
|
|
642
|
+
### Shell Integration (`evm init`)
|
|
643
|
+
|
|
644
|
+
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).
|
|
645
|
+
|
|
646
|
+
**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.
|
|
647
|
+
|
|
648
|
+
```bash
|
|
649
|
+
# Just use evm normally — on first run it prints:
|
|
650
|
+
# Installed evm shell integration to ~/.zshrc
|
|
651
|
+
# Restart your shell (or source the rc file) to enable `evm-load` and tab completion.
|
|
652
|
+
# Set EVM_NO_AUTO_INSTALL=1 to skip this.
|
|
653
|
+
|
|
654
|
+
# After restarting the shell, both `evm-load` and tab completion are available.
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
**Manual control** (if you prefer explicit, or the auto-install didn't fit your setup):
|
|
658
|
+
|
|
659
|
+
```bash
|
|
660
|
+
# Explicitly install (same as what auto-install does, but on demand)
|
|
661
|
+
evm init zsh --install # or bash / fish
|
|
662
|
+
|
|
663
|
+
# Check whether it's installed
|
|
664
|
+
evm init zsh --check # exit 0 = installed, 1 = not
|
|
665
|
+
|
|
666
|
+
# Remove the integration block from your rc file
|
|
667
|
+
evm init zsh --uninstall
|
|
668
|
+
|
|
669
|
+
# Force re-add (useful if your rc got out of sync)
|
|
670
|
+
evm init zsh --reinstall
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
**What the rc block looks like** (conda-style markers, easy to grep/remove):
|
|
674
|
+
|
|
675
|
+
```bash
|
|
676
|
+
# >>> evm shell integration >>>
|
|
677
|
+
# Auto-added by evm. Remove with: evm init zsh --uninstall
|
|
678
|
+
eval "$(evm init zsh)"
|
|
679
|
+
# <<< evm shell integration <<<
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
**Opt out of auto-install** — if you don't want EVM touching your rc file automatically:
|
|
683
|
+
|
|
684
|
+
```bash
|
|
685
|
+
export EVM_NO_AUTO_INSTALL=1 # in your rc, or just for one command:
|
|
686
|
+
EVM_NO_AUTO_INSTALL=1 evm get API_KEY
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
> 💡 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).
|
|
690
|
+
|
|
532
691
|
### Shell Completion
|
|
533
692
|
|
|
534
693
|
```bash
|
|
@@ -544,6 +703,8 @@ echo 'source ~/.evm-completion.zsh' >> ~/.zshrc
|
|
|
544
703
|
evm completion fish > ~/.config/fish/completions/evm.fish
|
|
545
704
|
```
|
|
546
705
|
|
|
706
|
+
> 💡 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.
|
|
707
|
+
|
|
547
708
|
### Interactive Safety
|
|
548
709
|
|
|
549
710
|
```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.6.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,8 @@ 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
|
|
26
|
+
- ✅ **Self-Upgrade**: `evm upgrade` checks PyPI and pip-installs the latest version
|
|
24
27
|
- ✅ **Interactive Safety**: Confirmation prompts for destructive operations (`--force` to skip)
|
|
25
28
|
- ✅ **JSON Output**: `--json` flag for structured output (agent-friendly, stdout=data, stderr=errors)
|
|
26
29
|
- ✅ **Quiet Mode**: `--quiet` suppresses all human-readable output
|
|
@@ -55,13 +58,22 @@ evm/
|
|
|
55
58
|
│ ├── _completion.py # Shell completion generators (bash/zsh/fish)
|
|
56
59
|
│ ├── _json.py # JSON output helpers (agent-friendly)
|
|
57
60
|
│ ├── _crypto.py # HKDF + HMAC-CTR encryption module
|
|
61
|
+
│ ├── _upgrade.py # Self-upgrade: PyPI version check + pip install
|
|
62
|
+
│ ├── _typing.py # Shared typing helpers (Protocol mixins)
|
|
58
63
|
│ ├── formatters.py # Terminal output formatting
|
|
59
64
|
│ └── exceptions.py # Custom exception hierarchy (17 classes)
|
|
60
65
|
├── examples/ # Example scripts
|
|
61
|
-
├── tests/ # Test suite
|
|
66
|
+
├── tests/ # Test suite
|
|
62
67
|
│ ├── test_main.py # Unit + integration tests
|
|
68
|
+
│ ├── test_inject.py # `evm inject` + `evm-load` tests
|
|
69
|
+
│ ├── test_shell_integration.py # `evm init` + auto-install tests
|
|
70
|
+
│ ├── test_upgrade.py # `evm upgrade` tests
|
|
63
71
|
│ ├── test_io_boundary.py # _io.py boundary tests
|
|
64
72
|
│ ├── test_cli_boundary.py # cli.py boundary tests
|
|
73
|
+
│ ├── test_cli_additional.py # cli.py additional coverage
|
|
74
|
+
│ ├── test_cli_coverage.py # cli.py coverage gap tests
|
|
75
|
+
│ ├── test_main_entry.py # `evm` entry-point tests
|
|
76
|
+
│ ├── test_main_module.py # `python -m evm` entry tests
|
|
65
77
|
│ ├── test_v230_fixes.py # v2.3.0 code review fix tests
|
|
66
78
|
│ ├── test_coverage_gap.py # Coverage gap tests (98% target)
|
|
67
79
|
│ ├── test_formatters.py # Formatter output tests
|
|
@@ -280,8 +292,11 @@ evm delete KEY # Delete a variable
|
|
|
280
292
|
evm setg GROUP KEY VALUE # Set in a group
|
|
281
293
|
evm groups # List groups
|
|
282
294
|
evm exec -- COMMAND # Run with env vars
|
|
295
|
+
eval "$(evm inject)" # Load vars into current shell
|
|
283
296
|
evm backup # Create backup
|
|
284
297
|
evm validate # Check schemas
|
|
298
|
+
evm upgrade # Upgrade to latest PyPI release
|
|
299
|
+
evm upgrade --check # Check for updates only
|
|
285
300
|
```
|
|
286
301
|
|
|
287
302
|
## Usage
|
|
@@ -363,6 +378,8 @@ evm restore backup.json --merge # Merge with existing
|
|
|
363
378
|
|
|
364
379
|
### Load to System Memory
|
|
365
380
|
|
|
381
|
+
> ⚠️ **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.
|
|
382
|
+
|
|
366
383
|
```bash
|
|
367
384
|
# Load all variables to memory (with EVM: prefix)
|
|
368
385
|
evm loadmemory
|
|
@@ -373,8 +390,72 @@ evm loadmemory --no-prefix
|
|
|
373
390
|
# Load with filter
|
|
374
391
|
evm loadmemory --prefix DEMO_
|
|
375
392
|
|
|
376
|
-
#
|
|
377
|
-
|
|
393
|
+
# Only meaningful when called from a Python script that imports evm:
|
|
394
|
+
# from evm import EnvironmentManager
|
|
395
|
+
# EnvironmentManager().load_to_memory()
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Inject to Current Shell
|
|
399
|
+
|
|
400
|
+
`evm inject` prints shell-sourceable `export` statements to stdout. Wrap it in `eval "$( ... )"` to load variables into your **current** shell session:
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
# Inject all non-grouped variables into the current shell
|
|
404
|
+
eval "$(evm inject)"
|
|
405
|
+
|
|
406
|
+
# Verify they're now in your shell
|
|
407
|
+
echo "$API_KEY"
|
|
408
|
+
|
|
409
|
+
# Target a specific shell (auto-detected from $SHELL by default)
|
|
410
|
+
eval "$(evm inject --shell bash)"
|
|
411
|
+
eval "$(evm inject --shell fish)" # fish uses `set -gx KEY VALUE`
|
|
412
|
+
|
|
413
|
+
# Inject only a group's variables (the `group:` prefix is stripped)
|
|
414
|
+
eval "$(evm inject --group prod)"
|
|
415
|
+
|
|
416
|
+
# Add a prefix to every exported key
|
|
417
|
+
eval "$(evm inject --prefix EVM_)"
|
|
418
|
+
|
|
419
|
+
# Also decrypt and inject secret variables (stored with --secret)
|
|
420
|
+
eval "$(evm inject --include-secrets)"
|
|
421
|
+
|
|
422
|
+
# Preview what would be injected, without eval-ing it
|
|
423
|
+
evm inject --dry-run
|
|
424
|
+
|
|
425
|
+
# Structured output for scripts/agents
|
|
426
|
+
evm inject --json
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
How `inject` decides what to export:
|
|
430
|
+
|
|
431
|
+
| Variable kind | Default | With flag |
|
|
432
|
+
|---|---|---|
|
|
433
|
+
| Plain (e.g. `API_KEY`) | ✅ exported | — |
|
|
434
|
+
| Grouped (e.g. `dev:DB_URL`) | ⏭️ silently skipped (invalid shell identifier) | `--group dev` strips the prefix and exports |
|
|
435
|
+
| Secret (stored with `--secret`) | ⏭️ skipped (would leak ciphertext) | `--include-secrets` decrypts and exports |
|
|
436
|
+
| Invalid shell identifier | ⏭️ skipped, reported in `--json`/`--dry-run` | — |
|
|
437
|
+
|
|
438
|
+
### The `evm-load` shortcut
|
|
439
|
+
|
|
440
|
+
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:
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
# After sourcing the completion script (bash/zsh/fish), just:
|
|
444
|
+
evm-load # inject from default ~/.evm/env.json
|
|
445
|
+
evm-load --env-file ./project.json # inject from a project-specific file
|
|
446
|
+
evm-load --group prod # inject only the prod group
|
|
447
|
+
evm-load --include-secrets # also decrypt and inject secrets
|
|
448
|
+
evm-load --prefix EVM_ # namespace all keys to avoid collisions
|
|
449
|
+
|
|
450
|
+
# Verify
|
|
451
|
+
echo "$API_KEY"
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Without completion installed, you can still type the `eval` form by hand, or add the alias yourself:
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
# ~/.zshrc or ~/.bashrc
|
|
458
|
+
alias evm-load='eval "$(evm inject)"'
|
|
378
459
|
```
|
|
379
460
|
|
|
380
461
|
### Execute Commands
|
|
@@ -489,6 +570,84 @@ evm history --limit 50
|
|
|
489
570
|
evm history --clear
|
|
490
571
|
```
|
|
491
572
|
|
|
573
|
+
### Self-Upgrade (`evm upgrade`)
|
|
574
|
+
|
|
575
|
+
Check PyPI for a newer `evm` release and pip-install it in one step. Uses only the standard library — no extra dependencies.
|
|
576
|
+
|
|
577
|
+
```bash
|
|
578
|
+
# Check whether a newer version is available (no changes made)
|
|
579
|
+
evm upgrade --check
|
|
580
|
+
# exit 0 = up to date, exit 1 = update available (or network error)
|
|
581
|
+
|
|
582
|
+
# Upgrade to the latest release
|
|
583
|
+
evm upgrade
|
|
584
|
+
# → Upgraded from 2.5.0 to 2.6.0.
|
|
585
|
+
|
|
586
|
+
# Preview the pip command without running it
|
|
587
|
+
evm upgrade --dry-run
|
|
588
|
+
|
|
589
|
+
# Skip the pre-check and run pip directly
|
|
590
|
+
evm upgrade --force
|
|
591
|
+
|
|
592
|
+
# Structured JSON output (stdout = data, stderr = errors)
|
|
593
|
+
evm upgrade --check --json
|
|
594
|
+
# stdout: {"status": "ok", "data": {"current": "2.5.0", "latest": "2.6.0", "update_available": true}}
|
|
595
|
+
|
|
596
|
+
evm upgrade --json
|
|
597
|
+
# stdout: {"status": "ok", "data": {"current": "2.5.0", "new_version": "2.6.0", "action": "upgraded", "upgraded": true, "message": "Upgraded from 2.5.0 to 2.6.0."}}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
`evm upgrade` calls `pip install --upgrade evm-cli` using the **same Python interpreter** that runs `evm`, so it upgrades the correct installation. If the network is unreachable, `--check` reports `unknown` and exits 1; a plain `evm upgrade` aborts before touching pip.
|
|
601
|
+
|
|
602
|
+
### Shell Integration (`evm init`)
|
|
603
|
+
|
|
604
|
+
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).
|
|
605
|
+
|
|
606
|
+
**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.
|
|
607
|
+
|
|
608
|
+
```bash
|
|
609
|
+
# Just use evm normally — on first run it prints:
|
|
610
|
+
# Installed evm shell integration to ~/.zshrc
|
|
611
|
+
# Restart your shell (or source the rc file) to enable `evm-load` and tab completion.
|
|
612
|
+
# Set EVM_NO_AUTO_INSTALL=1 to skip this.
|
|
613
|
+
|
|
614
|
+
# After restarting the shell, both `evm-load` and tab completion are available.
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
**Manual control** (if you prefer explicit, or the auto-install didn't fit your setup):
|
|
618
|
+
|
|
619
|
+
```bash
|
|
620
|
+
# Explicitly install (same as what auto-install does, but on demand)
|
|
621
|
+
evm init zsh --install # or bash / fish
|
|
622
|
+
|
|
623
|
+
# Check whether it's installed
|
|
624
|
+
evm init zsh --check # exit 0 = installed, 1 = not
|
|
625
|
+
|
|
626
|
+
# Remove the integration block from your rc file
|
|
627
|
+
evm init zsh --uninstall
|
|
628
|
+
|
|
629
|
+
# Force re-add (useful if your rc got out of sync)
|
|
630
|
+
evm init zsh --reinstall
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
**What the rc block looks like** (conda-style markers, easy to grep/remove):
|
|
634
|
+
|
|
635
|
+
```bash
|
|
636
|
+
# >>> evm shell integration >>>
|
|
637
|
+
# Auto-added by evm. Remove with: evm init zsh --uninstall
|
|
638
|
+
eval "$(evm init zsh)"
|
|
639
|
+
# <<< evm shell integration <<<
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
**Opt out of auto-install** — if you don't want EVM touching your rc file automatically:
|
|
643
|
+
|
|
644
|
+
```bash
|
|
645
|
+
export EVM_NO_AUTO_INSTALL=1 # in your rc, or just for one command:
|
|
646
|
+
EVM_NO_AUTO_INSTALL=1 evm get API_KEY
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
> 💡 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).
|
|
650
|
+
|
|
492
651
|
### Shell Completion
|
|
493
652
|
|
|
494
653
|
```bash
|
|
@@ -504,6 +663,8 @@ echo 'source ~/.evm-completion.zsh' >> ~/.zshrc
|
|
|
504
663
|
evm completion fish > ~/.config/fish/completions/evm.fish
|
|
505
664
|
```
|
|
506
665
|
|
|
666
|
+
> 💡 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.
|
|
667
|
+
|
|
507
668
|
### Interactive Safety
|
|
508
669
|
|
|
509
670
|
```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
|
+
|