dev-vault 0.1.2__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 (35) hide show
  1. dev_vault-0.1.2/LICENSE +21 -0
  2. dev_vault-0.1.2/PKG-INFO +170 -0
  3. dev_vault-0.1.2/README.md +138 -0
  4. dev_vault-0.1.2/dev_vault/__init__.py +3 -0
  5. dev_vault-0.1.2/dev_vault/cli.py +177 -0
  6. dev_vault-0.1.2/dev_vault/commands/__init__.py +0 -0
  7. dev_vault-0.1.2/dev_vault/commands/config_cmd.py +42 -0
  8. dev_vault-0.1.2/dev_vault/commands/get.py +134 -0
  9. dev_vault-0.1.2/dev_vault/commands/inject.py +91 -0
  10. dev_vault-0.1.2/dev_vault/commands/item.py +122 -0
  11. dev_vault-0.1.2/dev_vault/commands/migrate.py +108 -0
  12. dev_vault-0.1.2/dev_vault/commands/run.py +117 -0
  13. dev_vault-0.1.2/dev_vault/commands/set_cmd.py +83 -0
  14. dev_vault-0.1.2/dev_vault/commands/setup.py +232 -0
  15. dev_vault-0.1.2/dev_vault/commands/vault.py +90 -0
  16. dev_vault-0.1.2/dev_vault/core/__init__.py +0 -0
  17. dev_vault-0.1.2/dev_vault/core/config.py +165 -0
  18. dev_vault-0.1.2/dev_vault/core/keyring_store.py +68 -0
  19. dev_vault-0.1.2/dev_vault/core/manifest.py +56 -0
  20. dev_vault-0.1.2/dev_vault/core/resolver.py +65 -0
  21. dev_vault-0.1.2/dev_vault/providers/__init__.py +7 -0
  22. dev_vault-0.1.2/dev_vault/providers/base.py +21 -0
  23. dev_vault-0.1.2/dev_vault/providers/keycloak.py +135 -0
  24. dev_vault-0.1.2/dev_vault/setup_path.py +93 -0
  25. dev_vault-0.1.2/dev_vault/ui/__init__.py +0 -0
  26. dev_vault-0.1.2/dev_vault/ui/console.py +6 -0
  27. dev_vault-0.1.2/dev_vault/utils.py +11 -0
  28. dev_vault-0.1.2/dev_vault.egg-info/PKG-INFO +170 -0
  29. dev_vault-0.1.2/dev_vault.egg-info/SOURCES.txt +33 -0
  30. dev_vault-0.1.2/dev_vault.egg-info/dependency_links.txt +1 -0
  31. dev_vault-0.1.2/dev_vault.egg-info/entry_points.txt +4 -0
  32. dev_vault-0.1.2/dev_vault.egg-info/requires.txt +6 -0
  33. dev_vault-0.1.2/dev_vault.egg-info/top_level.txt +1 -0
  34. dev_vault-0.1.2/pyproject.toml +52 -0
  35. dev_vault-0.1.2/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 caetanominuzzo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: dev-vault
3
+ Version: 0.1.2
4
+ Summary: Developer secret vault + OIDC token provider — for developers, scripts, and AI agents
5
+ Author: Caetano Minuzzo
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/caetanominuzzo/dev-vault
8
+ Project-URL: Repository, https://github.com/caetanominuzzo/dev-vault
9
+ Project-URL: Issues, https://github.com/caetanominuzzo/dev-vault/issues
10
+ Project-URL: Changelog, https://github.com/caetanominuzzo/dev-vault/releases
11
+ Keywords: vault,secrets,keyring,cli,oidc,keycloak,token,oauth,ai-agent
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Security
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.7
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Requires-Python: >=3.7
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: httpx>=0.24.0
26
+ Requires-Dist: pyyaml>=6.0.0
27
+ Requires-Dist: pyperclip>=1.8.2
28
+ Requires-Dist: rich>=13.0.0
29
+ Requires-Dist: inquirer>=3.0.0
30
+ Requires-Dist: keyring>=24.0.0
31
+ Dynamic: license-file
32
+
33
+ # dev-vault
34
+
35
+ **All your secrets in one command.** Developer secret vault + OIDC token provider for developers, scripts, and AI agents.
36
+
37
+ ## Why dev-vault?
38
+
39
+ AI agents need secrets (API keys, bearer tokens) but can't safely read `.env` files, and hardcoding secrets in prompts is a security risk. dev-vault stores secrets in your OS keyring and exposes them via a simple CLI -- the secret never appears in conversation context, only where it's needed.
40
+
41
+ ## Quick Start
42
+
43
+ ```bash
44
+ pip install dev-vault
45
+ ```
46
+
47
+ ### Store a secret
48
+
49
+ ```bash
50
+ dv set datadog api_key # prompts for value (masked)
51
+ dv set datadog api_key "abc123" # inline (for scripts)
52
+ dv set datadog app_key "def456" # multiple fields per item
53
+ ```
54
+
55
+ ### Retrieve a secret
56
+
57
+ ```bash
58
+ dv get datadog # primary field -> stdout
59
+ dv get datadog api_key # specific field (planned: prefix matching)
60
+ dv get default datadog api_key # explicit vault
61
+ ```
62
+
63
+ ### Use with AI agents
64
+
65
+ ```bash
66
+ # Agent prompt: "use dv to get the bearer for the endpoint /api/motorcycles"
67
+ curl -H "Authorization: Bearer $(dv get prod caetano)" https://api.mottu.com/api/motorcycles
68
+
69
+ # Agent prompt: "query datadog for error rates"
70
+ DD_API_KEY=$(dv get datadog) python check_errors.py
71
+
72
+ # Or with dv run -- agent just says "run the script"
73
+ dv run -- python check_errors.py # secrets injected from .dv.yaml
74
+ ```
75
+
76
+ ### Run commands with secrets injected
77
+
78
+ ```bash
79
+ # Explicit mapping
80
+ dv run -s DD_API_KEY=datadog/api_key -s DD_APP_KEY=datadog/app_key -- python app.py
81
+
82
+ # Using .dv.yaml manifest (checked into git, no secrets)
83
+ dv run -- python app.py
84
+ ```
85
+
86
+ ### Project manifest (`.dv.yaml`)
87
+
88
+ Place in your project root. Maps environment variables to secret references:
89
+
90
+ ```yaml
91
+ secrets:
92
+ DD_API_KEY: datadog/api_key
93
+ DD_APP_KEY: datadog/app_key
94
+ BEARER_TOKEN: prod/admin@example.com # OIDC -> fresh token
95
+ ```
96
+
97
+ ### Template injection
98
+
99
+ ```bash
100
+ echo 'KEY={{dv://default/datadog/api_key}}' | dv inject
101
+ # Output: KEY=abc123
102
+ ```
103
+
104
+ ## OIDC Token Provider
105
+
106
+ dev-vault can fetch fresh OIDC tokens from Keycloak (with more providers planned):
107
+
108
+ ```bash
109
+ dv setup # interactive wizard
110
+ dv get prod admin@example.com # returns a fresh access_token
111
+ dv get prod api-client # client_credentials flow
112
+ ```
113
+
114
+ ### Migrating from sso-cli
115
+
116
+ ```bash
117
+ pip install dev-vault
118
+ dv migrate sso-cli # imports config + keyring secrets
119
+ dv get prod admin@example.com # same token, new tool
120
+ ```
121
+
122
+ ## Commands
123
+
124
+ | Command | Description |
125
+ |---------|-------------|
126
+ | `dv get [vault] <item> [field]` | Retrieve secret or OIDC token |
127
+ | `dv set [vault] <item> <field> [value]` | Store a secret |
128
+ | `dv run [-s KEY=ref] -- <cmd>` | Run command with secrets as env vars |
129
+ | `dv inject` | Replace `{{dv://...}}` refs in stdin |
130
+ | `dv item list\|create\|show\|delete` | Manage items |
131
+ | `dv vault list\|create\|delete` | Manage vaults |
132
+ | `dv setup [--reset]` | Interactive setup wizard |
133
+ | `dv migrate sso-cli` | Import from sso-cli |
134
+ | `dv config show` | Display current config |
135
+
136
+ All commands support `--json` for programmatic output and `-v` for debug logging.
137
+
138
+ ## Security
139
+
140
+ dev-vault is built with a strict security-first approach:
141
+
142
+ - **Secrets never touch disk.** All secret values are stored exclusively in the OS keyring (macOS Keychain, Linux Secret Service, Windows Credential Manager). The config YAML only contains metadata (vault names, item names, field names, OIDC provider URLs).
143
+ - **No secrets in logs or output.** Debug/verbose mode (`-v`) never logs secret values. Human-friendly output goes to stderr; only raw secret values go to stdout (for `$(dv get ...)` substitution).
144
+ - **Masked input.** Interactive secret entry uses `getpass` (no terminal echo).
145
+ - **Subprocess isolation.** `dv run` injects secrets as environment variables only into the child process -- they don't leak into the parent shell or shell history.
146
+ - **No network calls for static secrets.** Only OIDC items make network requests, and only to the configured SSO endpoint.
147
+ - **Config file permissions.** The config directory (`~/.config/dev-vault/`) inherits your user's default umask. No world-readable files.
148
+ - **No telemetry.** dev-vault makes zero calls home. No analytics, no crash reporting.
149
+
150
+ ### Supply chain
151
+
152
+ - Minimal dependencies: `httpx`, `pyyaml`, `keyring`, `rich`, `inquirer`, `pyperclip` -- all well-established, actively maintained packages.
153
+ - Published to PyPI with standard setuptools build.
154
+ - Source available on GitHub for audit.
155
+
156
+ ## How It Works
157
+
158
+ - **Config** location: `~/.config/dev-vault/config.yaml` (XDG-compliant), fallback `~/.dv.yaml`. Override with `DV_CONFIG` env var.
159
+ - **OIDC items** fetch fresh tokens on every call (no caching, no stale tokens). Static items return stored keyring values.
160
+ - **Secret references** use `dv://vault/item/field` URIs or shorthand (`item/field`, `vault/item`).
161
+
162
+ ---
163
+
164
+ PyPI package: https://pypi.org/project/dev-vault/
165
+
166
+ ## See Also
167
+
168
+ - [Agent State](https://agentstate.tech/) -- Persistent memory and tools for AI agents
169
+ - [sso-cli](https://pypi.org/project/sso-cli/) -- Single Sign-On token CLI (the ancestor of dev-vault)
170
+ - [terminal-to-here](https://github.com/caetanominuzzo/terminal-to-here) -- VS Code extension to open terminal at any folder
@@ -0,0 +1,138 @@
1
+ # dev-vault
2
+
3
+ **All your secrets in one command.** Developer secret vault + OIDC token provider for developers, scripts, and AI agents.
4
+
5
+ ## Why dev-vault?
6
+
7
+ AI agents need secrets (API keys, bearer tokens) but can't safely read `.env` files, and hardcoding secrets in prompts is a security risk. dev-vault stores secrets in your OS keyring and exposes them via a simple CLI -- the secret never appears in conversation context, only where it's needed.
8
+
9
+ ## Quick Start
10
+
11
+ ```bash
12
+ pip install dev-vault
13
+ ```
14
+
15
+ ### Store a secret
16
+
17
+ ```bash
18
+ dv set datadog api_key # prompts for value (masked)
19
+ dv set datadog api_key "abc123" # inline (for scripts)
20
+ dv set datadog app_key "def456" # multiple fields per item
21
+ ```
22
+
23
+ ### Retrieve a secret
24
+
25
+ ```bash
26
+ dv get datadog # primary field -> stdout
27
+ dv get datadog api_key # specific field (planned: prefix matching)
28
+ dv get default datadog api_key # explicit vault
29
+ ```
30
+
31
+ ### Use with AI agents
32
+
33
+ ```bash
34
+ # Agent prompt: "use dv to get the bearer for the endpoint /api/motorcycles"
35
+ curl -H "Authorization: Bearer $(dv get prod caetano)" https://api.mottu.com/api/motorcycles
36
+
37
+ # Agent prompt: "query datadog for error rates"
38
+ DD_API_KEY=$(dv get datadog) python check_errors.py
39
+
40
+ # Or with dv run -- agent just says "run the script"
41
+ dv run -- python check_errors.py # secrets injected from .dv.yaml
42
+ ```
43
+
44
+ ### Run commands with secrets injected
45
+
46
+ ```bash
47
+ # Explicit mapping
48
+ dv run -s DD_API_KEY=datadog/api_key -s DD_APP_KEY=datadog/app_key -- python app.py
49
+
50
+ # Using .dv.yaml manifest (checked into git, no secrets)
51
+ dv run -- python app.py
52
+ ```
53
+
54
+ ### Project manifest (`.dv.yaml`)
55
+
56
+ Place in your project root. Maps environment variables to secret references:
57
+
58
+ ```yaml
59
+ secrets:
60
+ DD_API_KEY: datadog/api_key
61
+ DD_APP_KEY: datadog/app_key
62
+ BEARER_TOKEN: prod/admin@example.com # OIDC -> fresh token
63
+ ```
64
+
65
+ ### Template injection
66
+
67
+ ```bash
68
+ echo 'KEY={{dv://default/datadog/api_key}}' | dv inject
69
+ # Output: KEY=abc123
70
+ ```
71
+
72
+ ## OIDC Token Provider
73
+
74
+ dev-vault can fetch fresh OIDC tokens from Keycloak (with more providers planned):
75
+
76
+ ```bash
77
+ dv setup # interactive wizard
78
+ dv get prod admin@example.com # returns a fresh access_token
79
+ dv get prod api-client # client_credentials flow
80
+ ```
81
+
82
+ ### Migrating from sso-cli
83
+
84
+ ```bash
85
+ pip install dev-vault
86
+ dv migrate sso-cli # imports config + keyring secrets
87
+ dv get prod admin@example.com # same token, new tool
88
+ ```
89
+
90
+ ## Commands
91
+
92
+ | Command | Description |
93
+ |---------|-------------|
94
+ | `dv get [vault] <item> [field]` | Retrieve secret or OIDC token |
95
+ | `dv set [vault] <item> <field> [value]` | Store a secret |
96
+ | `dv run [-s KEY=ref] -- <cmd>` | Run command with secrets as env vars |
97
+ | `dv inject` | Replace `{{dv://...}}` refs in stdin |
98
+ | `dv item list\|create\|show\|delete` | Manage items |
99
+ | `dv vault list\|create\|delete` | Manage vaults |
100
+ | `dv setup [--reset]` | Interactive setup wizard |
101
+ | `dv migrate sso-cli` | Import from sso-cli |
102
+ | `dv config show` | Display current config |
103
+
104
+ All commands support `--json` for programmatic output and `-v` for debug logging.
105
+
106
+ ## Security
107
+
108
+ dev-vault is built with a strict security-first approach:
109
+
110
+ - **Secrets never touch disk.** All secret values are stored exclusively in the OS keyring (macOS Keychain, Linux Secret Service, Windows Credential Manager). The config YAML only contains metadata (vault names, item names, field names, OIDC provider URLs).
111
+ - **No secrets in logs or output.** Debug/verbose mode (`-v`) never logs secret values. Human-friendly output goes to stderr; only raw secret values go to stdout (for `$(dv get ...)` substitution).
112
+ - **Masked input.** Interactive secret entry uses `getpass` (no terminal echo).
113
+ - **Subprocess isolation.** `dv run` injects secrets as environment variables only into the child process -- they don't leak into the parent shell or shell history.
114
+ - **No network calls for static secrets.** Only OIDC items make network requests, and only to the configured SSO endpoint.
115
+ - **Config file permissions.** The config directory (`~/.config/dev-vault/`) inherits your user's default umask. No world-readable files.
116
+ - **No telemetry.** dev-vault makes zero calls home. No analytics, no crash reporting.
117
+
118
+ ### Supply chain
119
+
120
+ - Minimal dependencies: `httpx`, `pyyaml`, `keyring`, `rich`, `inquirer`, `pyperclip` -- all well-established, actively maintained packages.
121
+ - Published to PyPI with standard setuptools build.
122
+ - Source available on GitHub for audit.
123
+
124
+ ## How It Works
125
+
126
+ - **Config** location: `~/.config/dev-vault/config.yaml` (XDG-compliant), fallback `~/.dv.yaml`. Override with `DV_CONFIG` env var.
127
+ - **OIDC items** fetch fresh tokens on every call (no caching, no stale tokens). Static items return stored keyring values.
128
+ - **Secret references** use `dv://vault/item/field` URIs or shorthand (`item/field`, `vault/item`).
129
+
130
+ ---
131
+
132
+ PyPI package: https://pypi.org/project/dev-vault/
133
+
134
+ ## See Also
135
+
136
+ - [Agent State](https://agentstate.tech/) -- Persistent memory and tools for AI agents
137
+ - [sso-cli](https://pypi.org/project/sso-cli/) -- Single Sign-On token CLI (the ancestor of dev-vault)
138
+ - [terminal-to-here](https://github.com/caetanominuzzo/terminal-to-here) -- VS Code extension to open terminal at any folder
@@ -0,0 +1,3 @@
1
+ """dev-vault -- Developer secret vault + OIDC token provider."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,177 @@
1
+ """
2
+ dev-vault CLI entry point.
3
+
4
+ Routes subcommands to their respective handlers.
5
+ """
6
+
7
+ import argparse
8
+ import logging
9
+ import os
10
+ import sys
11
+
12
+ from rich.console import Console
13
+ from rich.logging import RichHandler
14
+
15
+ from . import __version__
16
+
17
+ console = Console()
18
+ logger = logging.getLogger("dev_vault")
19
+
20
+
21
+ def _setup_logging(verbose: bool) -> None:
22
+ enabled = verbose or os.environ.get("DV_DEBUG", "").strip() in ("1", "true", "yes")
23
+ if not enabled:
24
+ return
25
+ logging.basicConfig(
26
+ level=logging.DEBUG,
27
+ format="%(message)s",
28
+ datefmt="[%X]",
29
+ handlers=[RichHandler(
30
+ console=Console(stderr=True),
31
+ rich_tracebacks=True,
32
+ show_path=False,
33
+ )],
34
+ )
35
+ logger.debug("Verbose mode enabled")
36
+
37
+
38
+ def _build_parser() -> argparse.ArgumentParser:
39
+ parser = argparse.ArgumentParser(
40
+ prog="dv",
41
+ description="dev-vault -- Developer secret vault + OIDC token provider",
42
+ )
43
+ parser.add_argument("--version", action="version", version=f"dev-vault {__version__}")
44
+ parser.add_argument("-v", "--verbose", action="store_true", help="enable debug output")
45
+ parser.add_argument("--json", action="store_true", help="JSON output")
46
+
47
+ sub = parser.add_subparsers(dest="command")
48
+
49
+ # dv get -- supports both:
50
+ # dv get prod caetano (positional: vault item)
51
+ # dv get prod caetano password (positional: vault item field)
52
+ # dv get caetano (just item, default vault)
53
+ # dv get prod/caetano (slash shorthand still works)
54
+ p_get = sub.add_parser("get", help="retrieve a secret or OIDC token")
55
+ p_get.add_argument("args", nargs="+", help="[vault] item [field] or vault/item[/field]")
56
+
57
+ # dv set -- supports:
58
+ # dv set item field [value]
59
+ # dv set vault item field [value]
60
+ p_set = sub.add_parser("set", help="store a secret")
61
+ p_set.add_argument("args", nargs="+", help="[vault] item field [value]")
62
+
63
+ # dv run
64
+ p_run = sub.add_parser("run", help="run command with secrets injected as env vars")
65
+ p_run.add_argument("-s", "--secret", action="append", help="KEY=ref mapping")
66
+ p_run.add_argument("run_command", nargs=argparse.REMAINDER, help="command to run (after --)")
67
+
68
+ # dv inject
69
+ sub.add_parser("inject", help="replace {{dv://...}} refs in stdin")
70
+
71
+ # dv item
72
+ p_item = sub.add_parser("item", help="manage items")
73
+ item_sub = p_item.add_subparsers(dest="item_action")
74
+
75
+ item_sub.add_parser("list", help="list items")
76
+ p_ic = item_sub.add_parser("create", help="create item")
77
+ p_ic.add_argument("name", help="item name")
78
+ p_ic.add_argument("--kind", choices=["static", "oidc"], default="static")
79
+ p_ic.add_argument("--vault", help="override vault")
80
+
81
+ p_is = item_sub.add_parser("show", help="show item details")
82
+ p_is.add_argument("name", help="item name")
83
+ p_is.add_argument("--vault", help="override vault")
84
+
85
+ p_id = item_sub.add_parser("delete", help="delete item")
86
+ p_id.add_argument("name", help="item name")
87
+ p_id.add_argument("--vault", help="override vault")
88
+
89
+ # dv vault
90
+ p_vault = sub.add_parser("vault", help="manage vaults")
91
+ vault_sub = p_vault.add_subparsers(dest="vault_action")
92
+ vault_sub.add_parser("list", help="list vaults")
93
+ vc = vault_sub.add_parser("create", help="create vault")
94
+ vc.add_argument("name", help="vault name")
95
+ vd = vault_sub.add_parser("delete", help="delete vault")
96
+ vd.add_argument("name", help="vault name")
97
+
98
+ # dv setup
99
+ p_setup = sub.add_parser("setup", help="interactive setup wizard")
100
+ p_setup.add_argument("--reset", action="store_true", help="backup config and start fresh")
101
+
102
+ # dv migrate
103
+ p_migrate = sub.add_parser("migrate", help="import from another tool")
104
+ p_migrate.add_argument("source", nargs="?", default="sso-cli", help="source tool (default: sso-cli)")
105
+
106
+ # dv config
107
+ p_config = sub.add_parser("config", help="show configuration")
108
+ config_sub = p_config.add_subparsers(dest="config_action")
109
+ config_sub.add_parser("show", help="display current config")
110
+
111
+ return parser
112
+
113
+
114
+ def cli() -> None:
115
+ parser = _build_parser()
116
+ args = parser.parse_args()
117
+
118
+ _setup_logging(args.verbose)
119
+
120
+ # Strip leading '--' from run command
121
+ if args.command == "run" and hasattr(args, "run_command"):
122
+ cmd = getattr(args, "run_command", [])
123
+ if cmd and cmd[0] == "--":
124
+ args.run_command = cmd[1:]
125
+
126
+ if not args.command:
127
+ parser.print_help()
128
+ return
129
+
130
+ # Propagate --json to args
131
+ try:
132
+ _dispatch(args)
133
+ except KeyboardInterrupt:
134
+ print("\nInterrupted.", file=sys.stderr)
135
+ sys.exit(130)
136
+ except Exception as e:
137
+ logger.debug("Error: %s", e, exc_info=True)
138
+ print(f"Error: {e}", file=sys.stderr)
139
+ sys.exit(1)
140
+
141
+
142
+ def _dispatch(args) -> None:
143
+ cmd = args.command
144
+ if cmd == "get":
145
+ from .commands.get import run
146
+ run(args)
147
+ elif cmd == "set":
148
+ from .commands.set_cmd import run
149
+ run(args)
150
+ elif cmd == "run":
151
+ from .commands.run import run as run_cmd
152
+ run_cmd(args)
153
+ elif cmd == "inject":
154
+ from .commands.inject import run
155
+ run(args)
156
+ elif cmd == "item":
157
+ from .commands.item import run
158
+ run(args)
159
+ elif cmd == "vault":
160
+ from .commands.vault import run
161
+ run(args)
162
+ elif cmd == "setup":
163
+ from .commands.setup import run
164
+ run(args)
165
+ elif cmd == "migrate":
166
+ from .commands.migrate import run
167
+ run(args)
168
+ elif cmd == "config":
169
+ from .commands.config_cmd import run
170
+ run(args)
171
+ else:
172
+ print(f"Unknown command: {cmd}", file=sys.stderr)
173
+ sys.exit(1)
174
+
175
+
176
+ if __name__ == "__main__":
177
+ cli()
File without changes
@@ -0,0 +1,42 @@
1
+ """
2
+ dv config show -- display current configuration.
3
+
4
+ Usage:
5
+ dv config show
6
+ """
7
+
8
+ import json as json_mod
9
+ import logging
10
+ import sys
11
+
12
+ import yaml
13
+
14
+ from ..core.config import find_config_path, load_config
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def run(args) -> None:
20
+ """Execute the config command."""
21
+ action = getattr(args, "config_action", "show")
22
+ if action == "show":
23
+ _show(args)
24
+ else:
25
+ print(f"Unknown config action: {action}", file=sys.stderr)
26
+ sys.exit(1)
27
+
28
+
29
+ def _show(args) -> None:
30
+ config_path = find_config_path()
31
+ try:
32
+ config = load_config()
33
+ except FileNotFoundError:
34
+ print(f"No config found at: {config_path}", file=sys.stderr)
35
+ sys.exit(1)
36
+
37
+ if getattr(args, "json", False):
38
+ print(json_mod.dumps({"path": config_path, "config": config}, indent=2))
39
+ return
40
+
41
+ print(f"Config: {config_path}\n")
42
+ print(yaml.dump(config, default_flow_style=False, sort_keys=False), end="")
@@ -0,0 +1,134 @@
1
+ """
2
+ dv get -- retrieve a secret or OIDC token.
3
+
4
+ Usage:
5
+ dv get <item> # default vault, primary field
6
+ dv get <vault> <item> # specific vault
7
+ dv get <vault> <item> <field> # specific vault + field
8
+ dv get vault/item/field # slash shorthand
9
+ """
10
+
11
+ import asyncio
12
+ import json as json_mod
13
+ import logging
14
+ import sys
15
+
16
+ from ..core.config import ensure_config, get_item, get_default_vault, list_all_items
17
+ from ..core.keyring_store import get_secret
18
+ from ..core.resolver import resolve, resolve_prefix
19
+ from ..providers import PROVIDERS
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ def run(args) -> None:
25
+ asyncio.run(_get(args))
26
+
27
+
28
+ def _parse_args(args) -> tuple:
29
+ """Parse positional args into (vault, item, field).
30
+
31
+ Supports:
32
+ ["item"] -> (default, item, None)
33
+ ["vault", "item"] -> (vault, item, None)
34
+ ["vault", "item", "f"] -> (vault, item, f)
35
+ ["vault/item"] -> resolve via slash
36
+ ["vault/item/field"] -> resolve via slash
37
+ """
38
+ parts = args.args
39
+ config = ensure_config()
40
+ default_vault = get_default_vault(config)
41
+
42
+ # If single arg with slashes, use resolver
43
+ if len(parts) == 1 and "/" in parts[0]:
44
+ return resolve(parts[0], default_vault)
45
+
46
+ if len(parts) == 1:
47
+ # Could be just an item name (default vault)
48
+ # Or could be a vault name if it matches a vault
49
+ vaults = list(config.get("vaults", {}).keys())
50
+ if parts[0] in vaults:
51
+ # Ambiguous: is it a vault or an item in default vault?
52
+ # Check if it's also an item in default vault
53
+ default_items = config.get("vaults", {}).get(default_vault, {}).get("items", {})
54
+ if parts[0] in default_items:
55
+ return default_vault, parts[0], None
56
+ # It's a vault name but no item specified
57
+ print(f"Error: vault '{parts[0]}' specified but no item name.", file=sys.stderr)
58
+ print(f"Usage: dv get {parts[0]} <item>", file=sys.stderr)
59
+ sys.exit(1)
60
+ return default_vault, parts[0], None
61
+ elif len(parts) == 2:
62
+ return parts[0], parts[1], None
63
+ elif len(parts) == 3:
64
+ return parts[0], parts[1], parts[2]
65
+ else:
66
+ print("Error: too many arguments. Usage: dv get [vault] <item> [field]", file=sys.stderr)
67
+ sys.exit(1)
68
+
69
+
70
+ async def _get(args) -> None:
71
+ config = ensure_config()
72
+ vault, item_name, field = _parse_args(args)
73
+ logger.debug("Resolved: vault=%s item=%s field=%s", vault, item_name, field)
74
+
75
+ item = get_item(config, vault, item_name)
76
+ if not item:
77
+ print(f"Error: item '{item_name}' not found in vault '{vault}'.", file=sys.stderr)
78
+ sys.exit(1)
79
+
80
+ kind = item.get("kind", "static")
81
+
82
+ if kind == "oidc" and field is None:
83
+ value = await _get_oidc_token(item, vault, item_name)
84
+ elif kind == "oidc" and field == "access_token":
85
+ value = await _get_oidc_token(item, vault, item_name)
86
+ else:
87
+ resolved_field = field or _primary_field(item)
88
+ if not resolved_field:
89
+ print(f"Error: item '{item_name}' has no fields.", file=sys.stderr)
90
+ sys.exit(1)
91
+ value = get_secret(vault, item_name, resolved_field)
92
+ if value is None:
93
+ print(
94
+ f"Error: no secret for {vault}/{item_name}/{resolved_field}. "
95
+ "Run 'dv set' to store it.",
96
+ file=sys.stderr,
97
+ )
98
+ sys.exit(1)
99
+
100
+ if getattr(args, "json", False):
101
+ print(json_mod.dumps({"vault": vault, "item": item_name, "field": field, "value": value}))
102
+ else:
103
+ print(value)
104
+
105
+
106
+ async def _get_oidc_token(item: dict, vault: str, item_name: str) -> str:
107
+ provider_name = item.get("provider", "keycloak")
108
+ provider = PROVIDERS.get(provider_name)
109
+ if not provider:
110
+ print(f"Error: unknown OIDC provider '{provider_name}'.", file=sys.stderr)
111
+ sys.exit(1)
112
+
113
+ item_config = item.get("config", {})
114
+ auth_type = item_config.get("auth_type", "user")
115
+ secret_field = "client_secret" if auth_type == "client" else "password"
116
+ secret = get_secret(vault, item_name, secret_field)
117
+ if secret is None:
118
+ print(
119
+ f"Error: no {secret_field} in keyring for {vault}/{item_name}. "
120
+ "Run 'dv setup' to configure.",
121
+ file=sys.stderr,
122
+ )
123
+ sys.exit(1)
124
+
125
+ return await provider.get_token(item_config, secret)
126
+
127
+
128
+ def _primary_field(item: dict) -> str:
129
+ fields = item.get("fields", [])
130
+ if isinstance(fields, list) and fields:
131
+ return fields[0]
132
+ if isinstance(fields, dict) and fields:
133
+ return next(iter(fields))
134
+ return "value"