Windows-compat hardening across every branch to reach 100% on the seedgo standards audit: - UTF-8 stdout/stderr reconfigure guards (getattr form) on Rich/CLI entry points - platform-branched POSIX-only subprocess kwargs (start_new_session -> CREATE_NEW_PROCESS_GROUP on win32) - seedgo windows_compat checker: credit the getattr reconfigure form + locking test - commons: shared-init test-suite speedup - spawn: aipass_framework template — strip pytest.ini inline comments + add scaffold smoke test - includes in-progress cross-OS testing work (doctor/init_flow/cross_os handlers + tests) Verified: full audit 17/17 at 100%, pyright clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013uzDhtcZ6wT1T9e2AHPQig
163 lines
4.7 KiB
Python
163 lines
4.7 KiB
Python
# =================== AIPass ====================
|
|
# Name: secrets.py
|
|
# Description: Secrets Module — cross-branch in-process door
|
|
# Version: 1.0.0
|
|
# Created: 2026-06-15
|
|
# Modified: 2026-06-15
|
|
# =============================================
|
|
|
|
"""
|
|
Secrets Module
|
|
|
|
Cross-branch in-process API for the secrets provider store.
|
|
Consumers import directly instead of shelling out to the CLI.
|
|
|
|
Functions:
|
|
get_secret() - Read a secret by provider/slug
|
|
set_secret() - Write a secret to the provider store
|
|
list_secrets() - List available slugs for a provider
|
|
handle_command() - Route CLI commands (seedgo module discovery)
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
if sys.platform == "win32":
|
|
os.environ.setdefault("PYTHONUTF8", "1")
|
|
for _stream in (sys.stdout, sys.stderr):
|
|
_reconfigure = getattr(_stream, "reconfigure", None)
|
|
if _reconfigure is not None:
|
|
_reconfigure(encoding="utf-8", errors="replace")
|
|
from typing import Any, List, Optional, Union
|
|
|
|
from aipass.prax import logger # noqa: F401 — seedgo imports standard
|
|
from aipass.cli.apps.modules import console, header
|
|
from aipass.api.apps.handlers.json import json_handler
|
|
from aipass.api.apps.handlers.auth import secrets as _handler
|
|
|
|
|
|
def print_introspection():
|
|
"""Show module introspection - connected handlers and capabilities"""
|
|
console.print()
|
|
header("Secrets Module Introspection")
|
|
console.print()
|
|
|
|
console.print("[cyan]Purpose:[/cyan] Cross-branch secrets access (in-process)")
|
|
console.print()
|
|
|
|
console.print("[cyan]Connected Handlers:[/cyan]")
|
|
console.print(" • api.apps.handlers.auth.secrets")
|
|
console.print()
|
|
|
|
console.print("[cyan]Available Workflows:[/cyan]")
|
|
console.print(" • get_secret() - Read secret by provider/slug")
|
|
console.print(" • set_secret() - Write secret to provider store")
|
|
console.print(" • list_secrets() - List slugs for a provider")
|
|
console.print()
|
|
|
|
|
|
def print_help():
|
|
"""Print help output for secrets module"""
|
|
print_introspection()
|
|
|
|
|
|
def handle_command(command: str, args: List[str]) -> bool:
|
|
"""
|
|
Handle secrets commands (module discovery hook).
|
|
|
|
This module does not own any CLI commands — get-secret is routed
|
|
through api_key.py. This exists for seedgo module discovery only.
|
|
|
|
Args:
|
|
command: Command name
|
|
args: Command arguments
|
|
|
|
Returns:
|
|
False — no commands handled here
|
|
"""
|
|
if not args:
|
|
print_introspection()
|
|
return True
|
|
|
|
if args[0] in ("--help", "-h", "help"):
|
|
print_help()
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def get_secret(provider: str, slug: str, as_json: bool = False) -> Optional[Any]:
|
|
"""
|
|
Read a secret from the provider store.
|
|
|
|
This is the sanctioned cross-branch import path. Consumers call this
|
|
instead of shelling out to 'drone @api get-secret'.
|
|
|
|
Args:
|
|
provider: Provider directory name (e.g., 'telegram', 'openrouter')
|
|
slug: Secret identifier (without .json extension)
|
|
as_json: If True, return full parsed dict; otherwise extract primary token
|
|
|
|
Returns:
|
|
Secret value (str or dict) or None if not found
|
|
"""
|
|
result = _handler.get_secret(provider, slug, as_json=as_json)
|
|
json_handler.log_operation("secrets_get", {"provider": provider, "slug": slug, "found": result is not None})
|
|
return result
|
|
|
|
|
|
def set_secret(provider: str, slug: str, value: Union[str, dict], *, as_json: bool = False) -> Path:
|
|
"""
|
|
Write a secret to the provider store.
|
|
|
|
This is the sanctioned cross-branch write path. Consumers call this
|
|
instead of shelling out to the CLI.
|
|
|
|
Args:
|
|
provider: Provider directory name (e.g., 'telegram')
|
|
slug: Secret identifier (without .json extension)
|
|
value: Secret value — string or dict
|
|
as_json: If True, json.dump the value; else write as plain string
|
|
|
|
Returns:
|
|
Path to the written file
|
|
|
|
Raises:
|
|
OSError: If directory creation or file write fails
|
|
"""
|
|
result = _handler.set_secret(provider, slug, value, as_json=as_json)
|
|
json_handler.log_operation("secrets_set", {"provider": provider, "slug": slug, "wrote": str(result)})
|
|
return result
|
|
|
|
|
|
def list_secrets(provider: str) -> List[str]:
|
|
"""
|
|
List available secret slugs for a provider.
|
|
|
|
Args:
|
|
provider: Provider directory name
|
|
|
|
Returns:
|
|
Sorted list of slug names
|
|
"""
|
|
return _handler.list_secrets(provider)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
"""Standalone execution mode"""
|
|
args = sys.argv[1:]
|
|
|
|
if len(args) == 0:
|
|
print_introspection()
|
|
sys.exit(0)
|
|
|
|
if args[0] in ["--help", "-h", "help"]:
|
|
print_help()
|
|
sys.exit(0)
|
|
|
|
console.print()
|
|
console.print(f"[red]Unknown command: {args[0]}[/red]")
|
|
console.print()
|
|
sys.exit(1)
|