agent-core-credentials 0.8.2__py3-none-any.whl

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.
@@ -0,0 +1,67 @@
1
+ """agent_core_credentials — KeePass-backed credential vault.
2
+
3
+ Public API for credential operations. Used by the CLI and available
4
+ for direct import elsewhere.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from pathlib import Path
11
+
12
+ from agent_core_credentials.master_password import get_master_password, set_master_password
13
+ from agent_core_credentials.models import Credential, CredentialSummary
14
+ from agent_core_credentials.secrets import SecretNotFoundError, get
15
+ from agent_core_credentials.store import CredentialStore
16
+
17
+ __all__ = [
18
+ "Credential",
19
+ "CredentialSummary",
20
+ "SecretNotFoundError",
21
+ "default_vault_path",
22
+ "delete_credential",
23
+ "get",
24
+ "get_credential",
25
+ "get_master_password",
26
+ "list_credentials",
27
+ "set_credential",
28
+ "set_master_password",
29
+ ]
30
+
31
+
32
+ def default_vault_path() -> Path:
33
+ """Resolve the default vault path.
34
+
35
+ Honours the AGENT_CORE_VAULT_PATH env var if set; otherwise falls
36
+ back to ~/.agent-core/credentials.kdbx.
37
+ """
38
+ override = os.environ.get("AGENT_CORE_VAULT_PATH")
39
+ if override:
40
+ return Path(override)
41
+ return Path.home() / ".agent-core" / "credentials.kdbx"
42
+
43
+
44
+ def get_credential(service: str) -> Credential | None:
45
+ """Retrieve a credential by service name."""
46
+ return CredentialStore(default_vault_path()).get(service)
47
+
48
+
49
+ def set_credential(
50
+ service: str,
51
+ username: str,
52
+ password: str,
53
+ url: str = "",
54
+ notes: str = "",
55
+ ) -> None:
56
+ """Store or overwrite a credential."""
57
+ CredentialStore(default_vault_path()).set(service, username, password, url, notes)
58
+
59
+
60
+ def list_credentials() -> list[CredentialSummary]:
61
+ """List all stored credentials without passwords."""
62
+ return CredentialStore(default_vault_path()).list()
63
+
64
+
65
+ def delete_credential(service: str) -> bool:
66
+ """Delete a credential. Returns True if found and deleted."""
67
+ return CredentialStore(default_vault_path()).delete(service)
@@ -0,0 +1,156 @@
1
+ """agent-core creds CLI — manage stored credentials."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+
9
+ import typer
10
+ from pykeepass import create_database # type: ignore[import-untyped]
11
+ from rich import print as rprint
12
+
13
+ from agent_core_credentials import default_vault_path
14
+ from agent_core_credentials.master_password import set_master_password
15
+ from agent_core_credentials.store import CredentialStore
16
+
17
+ creds_app = typer.Typer(
18
+ name="creds",
19
+ help="Manage stored credentials.",
20
+ no_args_is_help=True,
21
+ )
22
+
23
+ # Default paths — overridden in tests via monkeypatch
24
+ _vault_path = default_vault_path()
25
+ _env_path = Path.home() / ".agent-core" / ".env"
26
+
27
+
28
+ def _load_env() -> None:
29
+ """Load .env into the process environment if not already set."""
30
+ if os.environ.get("AGENT_CORE_VAULT_PASSWORD"):
31
+ return
32
+ if _env_path.exists():
33
+ for line in _env_path.read_text().splitlines():
34
+ if "=" in line and not line.startswith("#"):
35
+ key, _, value = line.partition("=")
36
+ os.environ.setdefault(key.strip(), value.strip())
37
+
38
+
39
+ def _store() -> CredentialStore:
40
+ """Create a CredentialStore for the current vault path."""
41
+ _load_env()
42
+ return CredentialStore(_vault_path)
43
+
44
+
45
+ @creds_app.command("init")
46
+ def init_vault() -> None:
47
+ """Initialize the credential vault with a master password."""
48
+ if _vault_path.exists():
49
+ rprint(f"[red]Credential vault already exists at {_vault_path}[/red]")
50
+ raise typer.Exit(1)
51
+
52
+ password = typer.prompt("Master password", hide_input=True)
53
+ confirm = typer.prompt("Confirm password", hide_input=True)
54
+
55
+ if password != confirm:
56
+ rprint("[red]Passwords do not match.[/red]")
57
+ raise typer.Exit(1)
58
+
59
+ _vault_path.parent.mkdir(parents=True, exist_ok=True)
60
+ create_database(str(_vault_path), password=password)
61
+
62
+ set_master_password(_vault_path, password)
63
+
64
+ rprint(f"[green]Created credential vault at {_vault_path}[/green]")
65
+
66
+
67
+ @creds_app.command("set")
68
+ def set_credential(
69
+ service: str = typer.Argument(help="Service name (e.g. apex, etsy)"),
70
+ ) -> None:
71
+ """Store a credential with secure password input."""
72
+ username = typer.prompt("Username")
73
+ password = typer.prompt("Password", hide_input=True)
74
+ url = typer.prompt("URL (optional)", default="")
75
+ notes = typer.prompt("Notes (optional)", default="")
76
+
77
+ try:
78
+ _store().set(service, username, password, url, notes)
79
+ except ValueError as e:
80
+ rprint(f"[red]{e}[/red]")
81
+ raise typer.Exit(1) from None
82
+
83
+ rprint(f"[green]Saved {service}[/green]")
84
+
85
+
86
+ @creds_app.command("get")
87
+ def get_credential(
88
+ service: str = typer.Argument(help="Service name to retrieve"),
89
+ as_json: bool = typer.Option(False, "--json", help="Output as JSON (metadata only, no secret)"),
90
+ ) -> None:
91
+ """Retrieve a stored credential."""
92
+ try:
93
+ cred = _store().get(service)
94
+ except ValueError as e:
95
+ rprint(f"[red]{e}[/red]")
96
+ raise typer.Exit(1) from None
97
+
98
+ if cred is None:
99
+ rprint(f"[red]No credential found for {service}[/red]")
100
+ raise typer.Exit(1)
101
+
102
+ if as_json:
103
+ print(json.dumps({"name": cred.service, "exists": True, "length": len(cred.password)}))
104
+ else:
105
+ rprint(f"[bold]Service:[/bold] {cred.service}")
106
+ rprint(f"[bold]Length:[/bold] {len(cred.password)} chars")
107
+ if cred.mtime is not None:
108
+ rprint(f"[bold]Modified:[/bold] {cred.mtime.strftime('%Y-%m-%d %H:%M UTC')}")
109
+ else:
110
+ rprint("[bold]Modified:[/bold] (unknown)")
111
+
112
+
113
+ @creds_app.command("list")
114
+ def list_credentials(
115
+ as_json: bool = typer.Option(False, "--json", help="Output as JSON"),
116
+ ) -> None:
117
+ """List all stored credentials."""
118
+ try:
119
+ summaries = _store().list()
120
+ except ValueError as e:
121
+ rprint(f"[red]{e}[/red]")
122
+ raise typer.Exit(1) from None
123
+
124
+ if as_json:
125
+ print(
126
+ json.dumps(
127
+ [
128
+ {"service": s.service, "username": s.username, "url": s.url}
129
+ for s in summaries
130
+ ]
131
+ )
132
+ )
133
+ else:
134
+ if not summaries:
135
+ rprint("[yellow]No credentials stored.[/yellow]")
136
+ return
137
+ for s in summaries:
138
+ rprint(s.service)
139
+
140
+
141
+ @creds_app.command("delete")
142
+ def delete_credential(
143
+ service: str = typer.Argument(help="Service name to delete"),
144
+ ) -> None:
145
+ """Delete a stored credential."""
146
+ try:
147
+ deleted = _store().delete(service)
148
+ except ValueError as e:
149
+ rprint(f"[red]{e}[/red]")
150
+ raise typer.Exit(1) from None
151
+
152
+ if not deleted:
153
+ rprint(f"[red]No credential found for {service}[/red]")
154
+ raise typer.Exit(1)
155
+
156
+ rprint(f"[green]Deleted {service}[/green]")
@@ -0,0 +1,217 @@
1
+ """Master password store — OS keyring (primary) + owner-only encrypted file (fallback).
2
+
3
+ Strategy pattern: ``_KeyringStore`` and ``EncryptedFileStore`` each implement
4
+ ``get() -> str | None`` and ``set(password: str) -> None``. ``_get_backend()``
5
+ selects the strategy at runtime by probing the keyring. ``get_master_password``
6
+ and ``set_master_password`` are the public entry-points.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import logging
13
+ import os
14
+ from pathlib import Path
15
+
16
+ import keyring
17
+ import keyring.errors
18
+ from cryptography.fernet import Fernet, InvalidToken
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # Constants
24
+ # ---------------------------------------------------------------------------
25
+
26
+ KEYRING_SERVICE: str = "agent-core"
27
+ _ENV_PATH: Path = Path.home() / ".agent-core" / ".env"
28
+
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Helpers
32
+ # ---------------------------------------------------------------------------
33
+
34
+
35
+ def _username_for(vault_path: Path) -> str:
36
+ """Return the per-vault keyring username (absolute path string)."""
37
+ return str(vault_path.resolve())
38
+
39
+
40
+ def _hash_for(vault_path: Path) -> str:
41
+ """Return first 12 hex chars of sha256(resolved vault path) for filename suffixes."""
42
+ digest = hashlib.sha256(str(vault_path.resolve()).encode()).hexdigest()
43
+ return digest[:12]
44
+
45
+
46
+ # ---------------------------------------------------------------------------
47
+ # EncryptedFileStore — owner-only Fernet-encrypted file fallback
48
+ # ---------------------------------------------------------------------------
49
+
50
+
51
+ class EncryptedFileStore:
52
+ """Owner-only encrypted file fallback for headless Linux/CI environments.
53
+
54
+ Two files are written next to the vault:
55
+ - ``_key_path``: 32-byte Fernet key (generated once, chmod 0o600).
56
+ - ``_data_path``: Fernet-encrypted master password (chmod 0o600).
57
+
58
+ Both files use a hash suffix so multiple vaults in the same directory
59
+ don't collide.
60
+ """
61
+
62
+ def __init__(self, vault_path: Path) -> None:
63
+ h = _hash_for(vault_path)
64
+ self._key_path: Path = vault_path.parent / f".vault-key-{h}"
65
+ self._data_path: Path = vault_path.parent / f".vault-pass-{h}"
66
+
67
+ def _load_key(self) -> bytes:
68
+ """Load or generate the Fernet key. Writes 0o600 on creation."""
69
+ if self._key_path.exists():
70
+ return self._key_path.read_bytes()
71
+ key = Fernet.generate_key()
72
+ self._key_path.write_bytes(key)
73
+ os.chmod(self._key_path, 0o600)
74
+ return key
75
+
76
+ def get(self) -> str | None:
77
+ """Return the decrypted password, or None if absent or unreadable."""
78
+ if not self._data_path.exists():
79
+ return None
80
+ key = self._load_key()
81
+ f = Fernet(key)
82
+ try:
83
+ ciphertext = self._data_path.read_bytes()
84
+ return f.decrypt(ciphertext).decode("utf-8")
85
+ except InvalidToken:
86
+ # Key was regenerated (old key deleted); old ciphertext is unreadable.
87
+ logger.debug(
88
+ "EncryptedFileStore: InvalidToken — key/data file mismatch; returning None",
89
+ )
90
+ return None
91
+
92
+ def set(self, password: str) -> None:
93
+ """Encrypt and persist the password. Both files are chmod 0o600."""
94
+ key = self._load_key()
95
+ f = Fernet(key)
96
+ ciphertext = f.encrypt(password.encode("utf-8"))
97
+ self._data_path.write_bytes(ciphertext)
98
+ os.chmod(self._data_path, 0o600)
99
+
100
+
101
+ # ---------------------------------------------------------------------------
102
+ # _KeyringStore — OS keyring (primary) backend
103
+ # ---------------------------------------------------------------------------
104
+
105
+
106
+ class _KeyringStore:
107
+ """OS keyring backend (macOS Keychain, Windows Credential Manager, Secret Service)."""
108
+
109
+ def __init__(self, vault_path: Path) -> None:
110
+ self._username: str = _username_for(vault_path)
111
+
112
+ def get(self) -> str | None:
113
+ """Return the password from the keyring, or None if not stored."""
114
+ return keyring.get_password(KEYRING_SERVICE, self._username)
115
+
116
+ def set(self, password: str) -> None:
117
+ """Persist the password in the keyring."""
118
+ keyring.set_password(KEYRING_SERVICE, self._username, password)
119
+
120
+
121
+ # ---------------------------------------------------------------------------
122
+ # Backend factory
123
+ # ---------------------------------------------------------------------------
124
+
125
+
126
+ def _get_backend(vault_path: Path) -> _KeyringStore | EncryptedFileStore:
127
+ """Return the appropriate backend by probing the OS keyring.
128
+
129
+ A harmless read is performed on a non-secret probe key. If ``get_password``
130
+ returns ``None`` (no entry exists — the normal case) the keyring is
131
+ functional; ``_KeyringStore`` is returned. If it raises any exception
132
+ (``NoKeyringError`` on headless Linux/CI, or any other error), fall back
133
+ to ``EncryptedFileStore``.
134
+ """
135
+ try:
136
+ keyring.get_password("__agent-core-probe__", "__probe__")
137
+ logger.debug("_get_backend: OS keyring available; using _KeyringStore")
138
+ return _KeyringStore(vault_path)
139
+ except Exception as exc: # noqa: BLE001
140
+ logger.debug("_get_backend: keyring unavailable (%s); using EncryptedFileStore", exc, exc_info=True)
141
+ return EncryptedFileStore(vault_path)
142
+
143
+
144
+ # ---------------------------------------------------------------------------
145
+ # .env migration helpers
146
+ # ---------------------------------------------------------------------------
147
+
148
+
149
+ def _read_env_file_password(env_path: Path) -> str | None:
150
+ """Parse env_path line by line; return AGENT_CORE_VAULT_PASSWORD value or None."""
151
+ if not env_path.exists():
152
+ return None
153
+ for line in env_path.read_text().splitlines():
154
+ stripped = line.strip()
155
+ if stripped.startswith("#"):
156
+ continue
157
+ if stripped.startswith("AGENT_CORE_VAULT_PASSWORD="):
158
+ value = stripped[len("AGENT_CORE_VAULT_PASSWORD="):]
159
+ return value if value else None
160
+ return None
161
+
162
+
163
+ def _scrub_env_file_password(env_path: Path) -> None:
164
+ """Remove all non-comment AGENT_CORE_VAULT_PASSWORD= lines from env_path.
165
+
166
+ No-op if the file does not exist. Idempotent.
167
+ """
168
+ if not env_path.exists():
169
+ return
170
+ original = env_path.read_text()
171
+ lines = original.splitlines(keepends=True)
172
+ kept = [
173
+ line
174
+ for line in lines
175
+ if not (
176
+ not line.strip().startswith("#")
177
+ and "AGENT_CORE_VAULT_PASSWORD=" in line
178
+ )
179
+ ]
180
+ env_path.write_text("".join(kept))
181
+
182
+
183
+ # ---------------------------------------------------------------------------
184
+ # Public API
185
+ # ---------------------------------------------------------------------------
186
+
187
+
188
+ def get_master_password(
189
+ vault_path: Path,
190
+ env_path: Path = _ENV_PATH,
191
+ ) -> str | None:
192
+ """Return the master password for vault_path, migrating from .env if needed.
193
+
194
+ Resolution order:
195
+ 1. Backend (keyring or encrypted file).
196
+ 2. Plaintext ``AGENT_CORE_VAULT_PASSWORD`` in ``env_path`` — triggers a
197
+ one-time migration: write to backend, scrub from ``.env``.
198
+ 3. Return ``None`` if neither source has a password.
199
+ """
200
+ backend = _get_backend(vault_path)
201
+ stored = backend.get()
202
+ if stored:
203
+ return stored
204
+
205
+ env_password = _read_env_file_password(env_path)
206
+ if env_password:
207
+ logger.debug("get_master_password: migrating password from .env to backend")
208
+ backend.set(env_password)
209
+ _scrub_env_file_password(env_path)
210
+ return env_password
211
+
212
+ return None
213
+
214
+
215
+ def set_master_password(vault_path: Path, password: str) -> None:
216
+ """Persist the master password for vault_path in the active backend."""
217
+ _get_backend(vault_path).set(password)
@@ -0,0 +1,27 @@
1
+ """Credential data models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from datetime import datetime
7
+
8
+
9
+ @dataclass
10
+ class Credential:
11
+ """A stored credential with all fields."""
12
+
13
+ service: str
14
+ username: str
15
+ password: str
16
+ url: str = ""
17
+ notes: str = ""
18
+ mtime: datetime | None = None
19
+
20
+
21
+ @dataclass
22
+ class CredentialSummary:
23
+ """A credential summary without the password."""
24
+
25
+ service: str
26
+ username: str
27
+ url: str = ""
File without changes
@@ -0,0 +1,66 @@
1
+ """secrets.get() — vault-first, env-fallback secret accessor.
2
+
3
+ Resolution order:
4
+ 1. Vault entry with ``service == name`` — returns ``cred.password``.
5
+ 2. ``os.environ.get(name)`` — env fallback for migration safety.
6
+ 3. Raises ``SecretNotFoundError`` if neither source has the secret.
7
+
8
+ Vault failures (no vault file, bad password, any pykeepass error) are caught,
9
+ logged at DEBUG, and fall through silently to the env fallback.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import os
16
+
17
+ log = logging.getLogger(__name__)
18
+
19
+
20
+ class SecretNotFoundError(Exception):
21
+ """Raised when neither the vault nor os.environ contains the named secret."""
22
+
23
+
24
+ def _open_store():
25
+ """Open the credential store against the default vault path.
26
+
27
+ Isolated into its own function so tests can monkeypatch it without
28
+ touching the real vault.
29
+ """
30
+ from agent_core_credentials import default_vault_path
31
+ from agent_core_credentials.store import CredentialStore
32
+
33
+ return CredentialStore(default_vault_path())
34
+
35
+
36
+ def get(name: str) -> str:
37
+ """Return the secret named *name*.
38
+
39
+ Resolution order:
40
+ 1. Vault entry whose ``service`` field equals *name*.
41
+ 2. ``os.environ[name]`` (migration fallback — works while vault is
42
+ being populated).
43
+ 3. Raises :exc:`SecretNotFoundError`.
44
+
45
+ Any exception thrown while opening or querying the vault is caught
46
+ and logged at DEBUG so that a missing / locked vault never hard-fails
47
+ a call that could succeed from the environment.
48
+ """
49
+ # --- vault path ---
50
+ try:
51
+ store = _open_store()
52
+ cred = store.get(name)
53
+ if cred is not None and cred.password:
54
+ return cred.password
55
+ except Exception:
56
+ log.debug("vault lookup failed for %r — falling through to env", name, exc_info=True)
57
+
58
+ # --- env fallback ---
59
+ value = os.environ.get(name)
60
+ if value:
61
+ return value
62
+
63
+ raise SecretNotFoundError(
64
+ f"Secret {name!r} not found in vault or environment. "
65
+ "Populate the vault entry or set the env var before starting."
66
+ )
@@ -0,0 +1,134 @@
1
+ """KeePass credential store — pykeepass wrapper."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from pykeepass import PyKeePass, create_database # type: ignore[import-untyped]
8
+
9
+ from agent_core_credentials.master_password import get_master_password
10
+ from agent_core_credentials.models import Credential, CredentialSummary
11
+
12
+
13
+ class CredentialStore:
14
+ """CRUD operations on an encrypted KeePass vault.
15
+
16
+ Each operation opens the vault, performs the action, and saves/closes.
17
+ The master password is fetched from the OS keyring or owner-only encrypted
18
+ file fallback via ``get_master_password()``.
19
+
20
+ Args:
21
+ vault_path: Path to the .kdbx file. Created on first write.
22
+ _master_password: Optional DI override for the master password (tests).
23
+ """
24
+
25
+ def __init__(self, vault_path: Path, *, _master_password: str | None = None) -> None:
26
+ """Initialize the store with a path to the vault file."""
27
+ self.vault_path = vault_path
28
+ self._password_override = _master_password
29
+
30
+ def _get_password(self) -> str:
31
+ """Return the master password from override DI or the active backend."""
32
+ if self._password_override is not None:
33
+ return self._password_override
34
+ result = get_master_password(self.vault_path)
35
+ if not result:
36
+ msg = (
37
+ "No master password found. Run 'agent-core-creds init' to initialize "
38
+ "the vault, or ensure the OS keyring is accessible."
39
+ )
40
+ raise ValueError(msg)
41
+ return result
42
+
43
+ def _open(self) -> PyKeePass:
44
+ """Open the existing vault; raise ``FileNotFoundError`` if it's absent.
45
+
46
+ Reads and deletes must NOT create a vault — silently creating an empty
47
+ ``.kdbx`` on a read masks a missing or misconfigured vault (and can
48
+ shadow the real one). Only the write path may create; see
49
+ :meth:`_open_or_create`.
50
+ """
51
+ # Validate the master-password config first (a config error is more
52
+ # fundamental), then require the vault to already exist.
53
+ password = self._get_password()
54
+ if not self.vault_path.exists():
55
+ raise FileNotFoundError(
56
+ f"credentials vault not found at {self.vault_path}; "
57
+ "set a credential (or run the init command) to create it"
58
+ )
59
+ return PyKeePass(str(self.vault_path), password=password)
60
+
61
+ def _open_or_create(self) -> PyKeePass:
62
+ """Open the vault, creating an empty one if it's absent.
63
+
64
+ Only the explicit write path (:meth:`set`) may call this — a missing
65
+ vault on first write is expected.
66
+ """
67
+ password = self._get_password()
68
+ if not self.vault_path.exists():
69
+ return create_database(str(self.vault_path), password=password)
70
+ return PyKeePass(str(self.vault_path), password=password)
71
+
72
+ def get(self, service: str) -> Credential | None:
73
+ """Retrieve a credential by service name."""
74
+ kp = self._open()
75
+ entry = kp.find_entries(title=service, first=True)
76
+ if entry is None:
77
+ return None
78
+ return Credential(
79
+ service=entry.title,
80
+ username=entry.username or "",
81
+ password=entry.password or "",
82
+ url=entry.url or "",
83
+ notes=entry.notes or "",
84
+ mtime=entry.mtime,
85
+ )
86
+
87
+ def set(
88
+ self,
89
+ service: str,
90
+ username: str,
91
+ password: str,
92
+ url: str = "",
93
+ notes: str = "",
94
+ ) -> None:
95
+ """Store or overwrite a credential (creates the vault on first write)."""
96
+ kp = self._open_or_create()
97
+ existing = kp.find_entries(title=service, first=True)
98
+ if existing:
99
+ existing.username = username
100
+ existing.password = password
101
+ existing.url = url
102
+ existing.notes = notes
103
+ else:
104
+ kp.add_entry(
105
+ kp.root_group,
106
+ title=service,
107
+ username=username,
108
+ password=password,
109
+ url=url,
110
+ notes=notes,
111
+ )
112
+ kp.save()
113
+
114
+ def list(self) -> list[CredentialSummary]:
115
+ """List all stored credentials without passwords."""
116
+ kp = self._open()
117
+ return [
118
+ CredentialSummary(
119
+ service=entry.title,
120
+ username=entry.username or "",
121
+ url=entry.url or "",
122
+ )
123
+ for entry in kp.entries
124
+ ]
125
+
126
+ def delete(self, service: str) -> bool:
127
+ """Delete a credential by service name."""
128
+ kp = self._open()
129
+ entry = kp.find_entries(title=service, first=True)
130
+ if entry is None:
131
+ return False
132
+ kp.delete_entry(entry)
133
+ kp.save()
134
+ return True
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-core-credentials
3
+ Version: 0.8.2
4
+ Summary: KeePass-backed credential vault for agent-core agents
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.12
7
+ Requires-Dist: cryptography>=41.0
8
+ Requires-Dist: keyring>=24.0
9
+ Requires-Dist: pykeepass>=4.1.1.post1
10
+ Requires-Dist: rich>=13.0
11
+ Requires-Dist: typer>=0.12
@@ -0,0 +1,11 @@
1
+ agent_core_credentials/__init__.py,sha256=rBEA_pPjRPkLBfbCWzgQIW3-_brN7Uj2QxyfLwW-MJc,1927
2
+ agent_core_credentials/cli.py,sha256=wEryTVD0R5o14vHgH31ROsN2MPm5gziC_3PQB2ArKyw,4807
3
+ agent_core_credentials/master_password.py,sha256=DbzRFE_XouYUhmvibLckbwOhVIULNIGaawFst0kwr6I,7844
4
+ agent_core_credentials/models.py,sha256=qk3SrE0-zdxIAZKS0ggylLn5k99V75imXn_6_D1Jaig,480
5
+ agent_core_credentials/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ agent_core_credentials/secrets.py,sha256=GZJm56k9BcR2ZtPlQr_5_st52b7YzjjBzo9lCn4xME8,2081
7
+ agent_core_credentials/store.py,sha256=XgwiPlyr53F3PzUiSWTpoltc5p4T7cnIccMDyg9-xwI,4834
8
+ agent_core_credentials-0.8.2.dist-info/METADATA,sha256=3CyNeGgTtQFlPgY-aVg_lDyynWlouNfXwbaYAWBg7Xc,331
9
+ agent_core_credentials-0.8.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
10
+ agent_core_credentials-0.8.2.dist-info/entry_points.txt,sha256=eaSA66yx6RVt8WTmN4OZS3KYCAGuuioUMJlH08a7xpQ,74
11
+ agent_core_credentials-0.8.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ agent-core-creds = agent_core_credentials.cli:creds_app