Credential broker via keyring (zero plaintext on disk), CLI secrets subcommand, 29 new tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
91 lines
2.7 KiB
Python
Executable File
91 lines
2.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Echo Core CLI tool."""
|
|
|
|
import argparse
|
|
import getpass
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Add project root to path
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
|
|
from src.secrets import set_secret, get_secret, list_secrets, delete_secret, check_secrets
|
|
|
|
|
|
def cmd_secrets(args):
|
|
"""Handle secrets subcommand."""
|
|
if args.secrets_action == "set":
|
|
if args.file:
|
|
path = Path(args.file)
|
|
if not path.exists():
|
|
print(f"Error: file {args.file} not found")
|
|
sys.exit(1)
|
|
value = path.read_text().strip()
|
|
set_secret(args.name, value)
|
|
path.unlink() # Delete source file after storing
|
|
print(f"Secret '{args.name}' set from file (file deleted)")
|
|
else:
|
|
value = getpass.getpass(f"Enter value for '{args.name}': ")
|
|
set_secret(args.name, value)
|
|
print(f"Secret '{args.name}' set")
|
|
|
|
elif args.secrets_action == "list":
|
|
names = list_secrets()
|
|
if not names:
|
|
print("No secrets stored")
|
|
else:
|
|
for name in names:
|
|
print(f" - {name}")
|
|
|
|
elif args.secrets_action == "delete":
|
|
if delete_secret(args.name):
|
|
print(f"Secret '{args.name}' deleted")
|
|
else:
|
|
print(f"Secret '{args.name}' not found")
|
|
|
|
elif args.secrets_action == "test":
|
|
results = check_secrets()
|
|
for name, exists in results.items():
|
|
print(f" {name}: {'OK' if exists else 'MISSING'}")
|
|
if all(results.values()):
|
|
print("\nAll required secrets present.")
|
|
else:
|
|
print("\nWARNING: Some required secrets are missing!")
|
|
sys.exit(1)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(prog="echo", description="Echo Core CLI")
|
|
sub = parser.add_subparsers(dest="command")
|
|
|
|
# secrets
|
|
secrets_parser = sub.add_parser("secrets", help="Manage secrets")
|
|
secrets_sub = secrets_parser.add_subparsers(dest="secrets_action")
|
|
|
|
set_p = secrets_sub.add_parser("set", help="Set a secret")
|
|
set_p.add_argument("name", help="Secret name")
|
|
set_p.add_argument("--file", help="Read value from file (file deleted after)")
|
|
|
|
secrets_sub.add_parser("list", help="List secret names")
|
|
|
|
del_p = secrets_sub.add_parser("delete", help="Delete a secret")
|
|
del_p.add_argument("name", help="Secret name")
|
|
|
|
secrets_sub.add_parser("test", help="Check required secrets")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.command is None:
|
|
parser.print_help()
|
|
sys.exit(0)
|
|
|
|
if args.command == "secrets":
|
|
if args.secrets_action is None:
|
|
secrets_parser.print_help()
|
|
sys.exit(0)
|
|
cmd_secrets(args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|