"""1Password CLI integration for knoe secret management. All secrets are stored in the 'knoey' vault so they are isolated from the user's personal 1Password vaults. The 'administrator' item holds the database master password. """ from __future__ import annotations import json import secrets import shutil import string import subprocess _VAULT = "knoey" _ADMIN_ITEM = "administrator" def op_available() -> bool: return shutil.which("op") is not None def _op(*args: str, check: bool = True) -> subprocess.CompletedProcess: if not op_available(): raise RuntimeError( "1Password CLI (op) not found. Install: brew install 1password-cli" ) return subprocess.run( ["op", *args], capture_output=True, text=True, check=check, ) def ensure_op_signed_in() -> None: """Ensure the op CLI has an active session; trigger sign-in if not.""" if not op_available(): raise RuntimeError( "1Password CLI (op) not found. Install: brew install 1password-cli" ) result = subprocess.run( ["op", "whoami"], capture_output=True, text=True, ) if result.returncode != 0: subprocess.run(["op", "signin"], check=True) def ensure_knoey_vault() -> None: """Create the 'knoey' vault if it does not already exist.""" result = _op("vault", "list", "--format", "json", check=True) try: vaults = json.loads(result.stdout or "[]") except json.JSONDecodeError: vaults = [] names = [v.get("name", "") for v in vaults] if _VAULT not in names: _op("vault", "create", _VAULT) print(f"[INFO] Created 1Password vault '{_VAULT}'") else: print(f"[INFO] 1Password vault '{_VAULT}' already exists") def get_secret(item: str, field: str = "password") -> str: """Retrieve a field value from an item in the knoey vault.""" result = _op( "item", "get", item, "--vault", _VAULT, "--fields", field, "--reveal", check=False, ) if result.returncode != 0: return "" return result.stdout.strip() def set_secret(item: str, field: str, value: str) -> None: """Set a field on an existing item, or create the item if absent.""" check_result = _op("item", "get", item, "--vault", _VAULT, check=False) if check_result.returncode == 0: _op( "item", "edit", item, "--vault", _VAULT, f"{field}={value}", ) else: _op( "item", "create", "--category", "login", "--title", item, "--vault", _VAULT, f"{field}={value}", ) def get_administrator_password() -> str: """Return the administrator password from the knoey vault.""" return get_secret(_ADMIN_ITEM, "password") def ensure_administrator_secret() -> str: """Return the administrator password, creating the item if absent.""" pw = get_secret(_ADMIN_ITEM, "password") if pw: return pw pw = _generate_password() _op( "item", "create", "--category", "login", "--title", _ADMIN_ITEM, "--vault", _VAULT, f"password={pw}", ) print(f"[INFO] Created 1Password item '{_ADMIN_ITEM}' in vault '{_VAULT}'") return pw def _generate_password(length: int = 32) -> str: alphabet = string.ascii_letters + string.digits return "".join(secrets.choice(alphabet) for _ in range(length))