diff --git a/docs/changelog.d/+usage-pragma-consistency.misc.md b/docs/changelog.d/+usage-pragma-consistency.misc.md new file mode 100644 index 0000000..aeb93ad --- /dev/null +++ b/docs/changelog.d/+usage-pragma-consistency.misc.md @@ -0,0 +1 @@ +Standardized USAGE pragmas and typer CLI parsing across all mise tasks: added missing `#USAGE` directive to `mikado-branch-invariant-check`, converted `pr-comments` and `op-backup` from raw `sys.argv` to typer for consistency with all other uv python scripts. diff --git a/mise-tasks/mikado-branch-invariant-check b/mise-tasks/mikado-branch-invariant-check index ffe3d1a..9060fc8 100755 --- a/mise-tasks/mikado-branch-invariant-check +++ b/mise-tasks/mikado-branch-invariant-check @@ -1,9 +1,10 @@ #!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.12" -# dependencies = ["rich>=13.0.0"] +# dependencies = ["rich>=13.0.0", "typer>=0.15.0"] # /// #MISE description="Validate Mikado Branch Invariant on mikado/* branches" +#USAGE arg "[commit_msg_file]" help="Commit message file (passed by commit-msg hook)" """Validate the Mikado Branch Invariant for C2 change branches. Runs as a commit-msg hook on mikado/* branches. Receives the commit message @@ -24,9 +25,10 @@ Exit code 0 if valid (or not on a mikado/* branch), 1 if violations found. import re import subprocess -import sys from pathlib import Path +from typing import Annotated +import typer from rich.console import Console REPO_DIR = Path(__file__).parent.parent @@ -242,27 +244,36 @@ def check_invariant(commits: list[dict], chain_stem: str) -> list[str]: return errors -def main() -> None: +app = typer.Typer() + + +@app.command() +def main( + commit_msg_file: Annotated[ + str | None, + typer.Argument(help="Commit message file (passed by commit-msg hook)"), + ] = None, +) -> None: console = Console(stderr=True) branch = get_current_branch() if not branch or not branch.startswith("mikado/"): # Not on a mikado branch — nothing to check - sys.exit(0) + raise SystemExit(0) chain_stem = branch.removeprefix("mikado/") commits = get_branch_commits(branch) # If called with a commit message file (commit-msg hook), include the # pending commit in the validation - if len(sys.argv) > 1: - subject = parse_commit_message(sys.argv[1]) + if commit_msg_file is not None: + subject = parse_commit_message(commit_msg_file) if subject: commits.append(make_pending_commit(subject)) if not commits: # No commits on branch yet — valid (length-zero case) - sys.exit(0) + raise SystemExit(0) errors = check_invariant(commits, chain_stem) @@ -286,10 +297,10 @@ def main() -> None: "[dim]See: docs/how-to/agent-change-process.md " "§ The Mikado Branch Invariant[/dim]" ) - sys.exit(1) + raise SystemExit(1) - sys.exit(0) + raise SystemExit(0) if __name__ == "__main__": - main() + app() diff --git a/mise-tasks/op-backup b/mise-tasks/op-backup index 0b486cc..202cb3e 100755 --- a/mise-tasks/op-backup +++ b/mise-tasks/op-backup @@ -1,7 +1,7 @@ #!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.12" -# dependencies = ["rich>=13.0.0"] +# dependencies = ["rich>=13.0.0", "typer>=0.15.0"] # /// #MISE description="Encrypt a 1Password .1pux export and send to indri for borgmatic" #USAGE arg "[export_path]" help="Path to .1pux export file (prompted if omitted)" @@ -29,11 +29,12 @@ DISASTER RECOVERY: import os import shutil import subprocess -import sys import tempfile from datetime import datetime from pathlib import Path +from typing import Annotated +import typer from rich.console import Console REMOTE_DIR = "/Users/erichblume/Documents/1password-backup" @@ -281,26 +282,35 @@ def transfer_to_indri(encrypted_export: Path, encrypted_key: Path, timestamp: st return True -def main() -> int: - if not check_dependencies(): - return 1 +app = typer.Typer() - export_path = get_export_path(sys.argv[1] if len(sys.argv) > 1 else None) + +@app.command() +def main( + export_path_arg: Annotated[ + str | None, + typer.Argument(help="Path to .1pux export file (prompted if omitted)"), + ] = None, +) -> None: + if not check_dependencies(): + raise SystemExit(1) + + export_path = get_export_path(export_path_arg) if not export_path: - return 1 + raise SystemExit(1) file_size = f"{export_path.stat().st_size / 1024 / 1024:.1f} MB" console.print(f"Source: {export_path} ({file_size})") passphrase = fetch_credentials() if not passphrase: - return 1 + raise SystemExit(1) with tempfile.TemporaryDirectory() as tmpdir: result = encrypt(export_path, passphrase, Path(tmpdir)) del passphrase if not result: - return 1 + raise SystemExit(1) encrypted_export, encrypted_key = result @@ -310,7 +320,7 @@ def main() -> int: timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") if not transfer_to_indri(encrypted_export, encrypted_key, timestamp): - return 1 + raise SystemExit(1) console.print() console.print("[bold]DISASTER RECOVERY:[/bold]") @@ -320,8 +330,7 @@ def main() -> int: console.print(" Passphrase: {master_password}:{secret_key}") console.print(f" 4. age -d -i key.txt < ...age > export.1pux") console.print(" 5. Open export.1pux with 1Password or unzip to inspect") - return 0 if __name__ == "__main__": - sys.exit(main()) + app() diff --git a/mise-tasks/pr-comments b/mise-tasks/pr-comments index 9c33f9d..1ec60ef 100755 --- a/mise-tasks/pr-comments +++ b/mise-tasks/pr-comments @@ -1,7 +1,7 @@ #!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.12" -# dependencies = ["httpx>=0.28.0", "rich>=13.0.0"] +# dependencies = ["httpx>=0.28.0", "rich>=13.0.0", "typer>=0.15.0"] # /// #MISE description="List unresolved comments on a PR" #USAGE arg "" help="Pull request number" @@ -14,9 +14,10 @@ if its 'resolver' field is null. Usage: mise run pr-comments """ -import sys +from typing import Annotated import httpx +import typer from rich.console import Console from rich.text import Text @@ -43,20 +44,15 @@ def get_review_comments(client: httpx.Client, pr_number: int, review_id: int) -> return response.json() -def main() -> int: +app = typer.Typer() + + +@app.command() +def main( + pr_number: Annotated[int, typer.Argument(help="Pull request number")], +) -> None: console = Console() - if len(sys.argv) < 2: - console.print("[red]Error:[/red] Please provide a PR number") - console.print("Usage: mise run pr-comments ") - return 1 - - try: - pr_number = int(sys.argv[1]) - except ValueError: - console.print(f"[red]Error:[/red] '{sys.argv[1]}' is not a valid PR number") - return 1 - unresolved_comments: list[tuple[dict, dict]] = [] # (review, comment) pairs with httpx.Client() as client: @@ -68,7 +64,7 @@ def main() -> int: console.print(f"[red]Error:[/red] PR #{pr_number} not found") else: console.print(f"[red]Error:[/red] API request failed: {e}") - return 1 + raise SystemExit(1) # For each review, get comments and filter to unresolved for review in reviews: @@ -83,7 +79,7 @@ def main() -> int: if not unresolved_comments: console.print(f"[green]No unresolved comments on PR #{pr_number}[/green]") - return 0 + raise SystemExit(0) # Display unresolved comments console.print(f"[bold]Unresolved Comments on PR #{pr_number}[/bold] ({len(unresolved_comments)} comments)") @@ -111,8 +107,6 @@ def main() -> int: console.print() - return 0 - if __name__ == "__main__": - sys.exit(main()) + app()