From eed12376c6d6c759ee52018c268847e7d7a77c22 Mon Sep 17 00:00:00 2001 From: pleb Date: Wed, 1 Jul 2026 11:59:01 -0700 Subject: [PATCH] Default interactive CLI to menu --- AGENTS.md | 2 ++ README.md | 5 ++++- src/plugin_helper/cli.py | 9 ++++++++- tests/test_plugin_helper.py | 21 +++++++++++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 66fd2dd..66faca5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,6 +46,8 @@ Guidance for coding agents working in this repo. - Be careful with duplicate instance names across Windows and local roots. Use the menu or pass `--instances-root` explicitly when targeting one install, and keep install/bootstrap state separate per target root. +- After completing repo changes, suggest a concise commit message in the final + response unless the user already asked you to commit. ## Validation diff --git a/README.md b/README.md index aab56ce..8a67657 100644 --- a/README.md +++ b/README.md @@ -66,9 +66,12 @@ backups, and installed file records for different game trees. For normal use, run the Textual menu from the repo root: ```sh -PYTHONPATH=src python -m plugin_helper menu +PYTHONPATH=src python -m plugin_helper ``` +That is equivalent to `PYTHONPATH=src python -m plugin_helper menu` when run +from an interactive terminal. + The menu reads `plugin-helper.local.toml` when present, shows each discovered Beat Saber install with its resolved state directory, and lets you toggle managed plugins with arrow keys and Space. In the plugin table, use `d` to diff --git a/src/plugin_helper/cli.py b/src/plugin_helper/cli.py index fedbfde..3683336 100644 --- a/src/plugin_helper/cli.py +++ b/src/plugin_helper/cli.py @@ -70,7 +70,7 @@ def _add_common(parser: argparse.ArgumentParser, *, suppress_default: bool = Fal def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(prog="plugin-helper") _add_common(parser) - subcommands = parser.add_subparsers(dest="command", required=True) + subcommands = parser.add_subparsers(dest="command") subcommands.add_parser( "instances", @@ -309,6 +309,13 @@ def run(argv: list[str] | None = None) -> int: args = parser.parse_args(argv) try: + if args.command is None: + if sys.stdin.isatty() and sys.stdout.isatty(): + args.command = "menu" + else: + parser.print_help() + return 2 + explicit_instances_root = getattr(args, "instances_root", None) is not None explicit_state_dir = getattr(args, "state_dir", None) is not None runtime = resolve_runtime_config( diff --git a/tests/test_plugin_helper.py b/tests/test_plugin_helper.py index 612f9cb..9b31d5a 100644 --- a/tests/test_plugin_helper.py +++ b/tests/test_plugin_helper.py @@ -4,6 +4,7 @@ import json import tempfile import unittest import os +from io import StringIO from pathlib import Path from unittest.mock import patch from zipfile import ZipFile @@ -180,6 +181,26 @@ state_dir = ".state" self.assertEqual(runtime.state_root, root / "explicit-state") self.assertEqual(runtime.selected_profile.id, "linux") + def test_no_args_prints_help_when_not_interactive(self) -> None: + output = StringIO() + with patch("sys.stdin.isatty", return_value=False), patch("sys.stdout", output): + status = run([]) + + self.assertEqual(status, 2) + self.assertIn("usage: plugin-helper", output.getvalue()) + self.assertIn("menu", output.getvalue()) + + def test_no_command_defaults_to_menu_when_interactive(self) -> None: + with ( + patch("sys.stdin.isatty", return_value=True), + patch("sys.stdout.isatty", return_value=True), + patch("plugin_helper.cli._run_menu", return_value=0) as run_menu, + ): + status = run(["--profile", "linux"]) + + self.assertEqual(status, 0) + run_menu.assert_called_once() + def test_run_ipa_timeout_returns_control(self) -> None: with tempfile.TemporaryDirectory() as tmp: result = _run_ipa(