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.
Files changed (41) hide show
  1. {evm_cli-2.4.0 → evm_cli-2.6.0}/PKG-INFO +166 -5
  2. {evm_cli-2.4.0 → evm_cli-2.6.0}/README.md +165 -4
  3. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/__init__.py +1 -1
  4. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_completion.py +144 -1
  5. evm_cli-2.6.0/evm/_upgrade.py +164 -0
  6. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/cli.py +289 -4
  7. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/manager.py +96 -1
  8. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm_cli.egg-info/PKG-INFO +166 -5
  9. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm_cli.egg-info/SOURCES.txt +4 -0
  10. {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_coverage_gap.py +2 -1
  11. evm_cli-2.6.0/tests/test_inject.py +589 -0
  12. evm_cli-2.6.0/tests/test_shell_integration.py +327 -0
  13. evm_cli-2.6.0/tests/test_upgrade.py +488 -0
  14. {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_v230_fixes.py +5 -3
  15. {evm_cli-2.4.0 → evm_cli-2.6.0}/LICENSE +0 -0
  16. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/__main__.py +0 -0
  17. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_crypto.py +0 -0
  18. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_groups.py +0 -0
  19. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_history.py +0 -0
  20. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_io.py +0 -0
  21. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_json.py +0 -0
  22. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_schema.py +0 -0
  23. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/_typing.py +0 -0
  24. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/exceptions.py +0 -0
  25. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm/formatters.py +0 -0
  26. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm_cli.egg-info/dependency_links.txt +0 -0
  27. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm_cli.egg-info/entry_points.txt +0 -0
  28. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm_cli.egg-info/not-zip-safe +0 -0
  29. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm_cli.egg-info/requires.txt +0 -0
  30. {evm_cli-2.4.0 → evm_cli-2.6.0}/evm_cli.egg-info/top_level.txt +0 -0
  31. {evm_cli-2.4.0 → evm_cli-2.6.0}/pyproject.toml +0 -0
  32. {evm_cli-2.4.0 → evm_cli-2.6.0}/setup.cfg +0 -0
  33. {evm_cli-2.4.0 → evm_cli-2.6.0}/setup.py +0 -0
  34. {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_cli_additional.py +0 -0
  35. {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_cli_boundary.py +0 -0
  36. {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_cli_coverage.py +0 -0
  37. {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_formatters.py +0 -0
  38. {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_io_boundary.py +0 -0
  39. {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_main.py +0 -0
  40. {evm_cli-2.4.0 → evm_cli-2.6.0}/tests/test_main_entry.py +0 -0
  41. {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.4.0
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.4.0
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 (575 tests)
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
- # Check in Python
417
- python -c "import os; print(os.environ.get('EVM:API_KEY'))"
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.4.0
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 (575 tests)
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
- # Check in Python
377
- python -c "import os; print(os.environ.get('EVM:API_KEY'))"
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
@@ -3,7 +3,7 @@ EVM - Environment Variable Manager
3
3
  A command-line tool for managing environment variables.
4
4
  """
5
5
 
6
- __version__ = "2.4.0"
6
+ __version__ = "2.6.0"
7
7
  __author__ = "EVM Tool"
8
8
 
9
9
  from .exceptions import (
@@ -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
+