Add CLI subcommands and doctor diagnostics

This commit is contained in:
Thales Maciel 2026-02-26 17:38:06 -03:00
parent 9c7d7b35b1
commit ad1af63fac
7 changed files with 385 additions and 13 deletions

View file

@ -15,9 +15,10 @@ from pathlib import Path
from typing import Any
from aiprocess import LlamaProcessor
from config import Config, load, redacted_dict
from constants import MODEL_PATH, RECORD_TIMEOUT_SEC, STT_LANGUAGE
from config import Config, load, redacted_dict, validate
from constants import DEFAULT_CONFIG_PATH, MODEL_PATH, RECORD_TIMEOUT_SEC, STT_LANGUAGE
from desktop import get_desktop_adapter
from diagnostics import run_diagnostics
from recorder import start_recording as start_audio_recording
from recorder import stop_recording as stop_audio_recording
from vocabulary import VocabularyEngine
@ -377,20 +378,80 @@ def _lock_single_instance():
return lock_file
def main():
global _LOCK_HANDLE
def _build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument("--config", default="", help="path to config.json")
parser.add_argument("--dry-run", action="store_true", help="log hotkey only")
parser.add_argument("-v", "--verbose", action="store_true", help="enable verbose logs")
args = parser.parse_args()
subparsers = parser.add_subparsers(dest="command")
run_parser = subparsers.add_parser("run", help="run the aman daemon")
run_parser.add_argument("--config", default="", help="path to config.json")
run_parser.add_argument("--dry-run", action="store_true", help="log hotkey only")
run_parser.add_argument("-v", "--verbose", action="store_true", help="enable verbose logs")
doctor_parser = subparsers.add_parser("doctor", help="run startup diagnostics")
doctor_parser.add_argument("--config", default="", help="path to config.json")
doctor_parser.add_argument("--json", action="store_true", help="print JSON output")
doctor_parser.add_argument("-v", "--verbose", action="store_true", help="enable verbose logs")
init_parser = subparsers.add_parser("init", help="write a default config")
init_parser.add_argument("--config", default="", help="path to config.json")
init_parser.add_argument("--force", action="store_true", help="overwrite existing config")
return parser
def _parse_cli_args(argv: list[str]) -> argparse.Namespace:
parser = _build_parser()
normalized_argv = list(argv)
known_commands = {"run", "doctor", "init"}
if not normalized_argv or normalized_argv[0] not in known_commands:
normalized_argv = ["run", *normalized_argv]
return parser.parse_args(normalized_argv)
def _configure_logging(verbose: bool) -> None:
logging.basicConfig(
stream=sys.stderr,
level=logging.DEBUG if args.verbose else logging.INFO,
level=logging.DEBUG if verbose else logging.INFO,
format="aman: %(asctime)s %(levelname)s %(message)s",
)
cfg = load(args.config)
def _doctor_command(args: argparse.Namespace) -> int:
report = run_diagnostics(args.config)
if args.json:
print(report.to_json())
else:
for check in report.checks:
status = "OK" if check.ok else "FAIL"
line = f"[{status}] {check.id}: {check.message}"
if check.hint:
line = f"{line} | hint: {check.hint}"
print(line)
print(f"overall: {'ok' if report.ok else 'failed'}")
return 0 if report.ok else 2
def _init_command(args: argparse.Namespace) -> int:
config_path = Path(args.config) if args.config else DEFAULT_CONFIG_PATH
if config_path.exists() and not args.force:
logging.error("init failed: config already exists at %s (use --force to overwrite)", config_path)
return 1
cfg = Config()
validate(cfg)
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text(f"{json.dumps(redacted_dict(cfg), indent=2)}\n", encoding="utf-8")
logging.info("wrote default config to %s", config_path)
return 0
def _run_command(args: argparse.Namespace) -> int:
global _LOCK_HANDLE
try:
cfg = load(args.config)
except Exception as exc:
logging.error("startup failed: %s", exc)
return 1
_LOCK_HANDLE = _lock_single_instance()
logging.info("hotkey: %s", cfg.daemon.hotkey)
@ -415,7 +476,7 @@ def main():
daemon = Daemon(cfg, desktop, verbose=args.verbose)
except Exception as exc:
logging.error("startup failed: %s", exc)
raise SystemExit(1)
return 1
shutdown_once = threading.Event()
@ -441,13 +502,28 @@ def main():
)
except Exception as exc:
logging.error("hotkey setup failed: %s", exc)
raise SystemExit(1)
return 1
logging.info("ready")
try:
desktop.run_tray(daemon.get_state, lambda: shutdown("quit requested"))
finally:
daemon.shutdown(timeout=1.0)
return 0
def main(argv: list[str] | None = None) -> int:
args = _parse_cli_args(list(argv) if argv is not None else sys.argv[1:])
if args.command == "run":
_configure_logging(args.verbose)
return _run_command(args)
if args.command == "doctor":
_configure_logging(args.verbose)
return _doctor_command(args)
if args.command == "init":
_configure_logging(False)
return _init_command(args)
raise RuntimeError(f"unsupported command: {args.command}")
if __name__ == "__main__":
main()
raise SystemExit(main())