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