Standardize USAGE pragmas and typer parsing across mise tasks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erich Blume 2026-03-18 11:42:01 -07:00
commit ef8c2118a1
4 changed files with 54 additions and 39 deletions

View file

@ -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.

View file

@ -1,9 +1,10 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env -S uv run --script
# /// script # /// script
# requires-python = ">=3.12" # 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" #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. """Validate the Mikado Branch Invariant for C2 change branches.
Runs as a commit-msg hook on mikado/* branches. Receives the commit message 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 re
import subprocess import subprocess
import sys
from pathlib import Path from pathlib import Path
from typing import Annotated
import typer
from rich.console import Console from rich.console import Console
REPO_DIR = Path(__file__).parent.parent REPO_DIR = Path(__file__).parent.parent
@ -242,27 +244,36 @@ def check_invariant(commits: list[dict], chain_stem: str) -> list[str]:
return errors 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) console = Console(stderr=True)
branch = get_current_branch() branch = get_current_branch()
if not branch or not branch.startswith("mikado/"): if not branch or not branch.startswith("mikado/"):
# Not on a mikado branch — nothing to check # Not on a mikado branch — nothing to check
sys.exit(0) raise SystemExit(0)
chain_stem = branch.removeprefix("mikado/") chain_stem = branch.removeprefix("mikado/")
commits = get_branch_commits(branch) commits = get_branch_commits(branch)
# If called with a commit message file (commit-msg hook), include the # If called with a commit message file (commit-msg hook), include the
# pending commit in the validation # pending commit in the validation
if len(sys.argv) > 1: if commit_msg_file is not None:
subject = parse_commit_message(sys.argv[1]) subject = parse_commit_message(commit_msg_file)
if subject: if subject:
commits.append(make_pending_commit(subject)) commits.append(make_pending_commit(subject))
if not commits: if not commits:
# No commits on branch yet — valid (length-zero case) # No commits on branch yet — valid (length-zero case)
sys.exit(0) raise SystemExit(0)
errors = check_invariant(commits, chain_stem) errors = check_invariant(commits, chain_stem)
@ -286,10 +297,10 @@ def main() -> None:
"[dim]See: docs/how-to/agent-change-process.md " "[dim]See: docs/how-to/agent-change-process.md "
"§ The Mikado Branch Invariant[/dim]" "§ The Mikado Branch Invariant[/dim]"
) )
sys.exit(1) raise SystemExit(1)
sys.exit(0) raise SystemExit(0)
if __name__ == "__main__": if __name__ == "__main__":
main() app()

View file

@ -1,7 +1,7 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env -S uv run --script
# /// script # /// script
# requires-python = ">=3.12" # 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" #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)" #USAGE arg "[export_path]" help="Path to .1pux export file (prompted if omitted)"
@ -29,11 +29,12 @@ DISASTER RECOVERY:
import os import os
import shutil import shutil
import subprocess import subprocess
import sys
import tempfile import tempfile
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Annotated
import typer
from rich.console import Console from rich.console import Console
REMOTE_DIR = "/Users/erichblume/Documents/1password-backup" 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 return True
def main() -> int: app = typer.Typer()
if not check_dependencies():
return 1
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: if not export_path:
return 1 raise SystemExit(1)
file_size = f"{export_path.stat().st_size / 1024 / 1024:.1f} MB" file_size = f"{export_path.stat().st_size / 1024 / 1024:.1f} MB"
console.print(f"Source: {export_path} ({file_size})") console.print(f"Source: {export_path} ({file_size})")
passphrase = fetch_credentials() passphrase = fetch_credentials()
if not passphrase: if not passphrase:
return 1 raise SystemExit(1)
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
result = encrypt(export_path, passphrase, Path(tmpdir)) result = encrypt(export_path, passphrase, Path(tmpdir))
del passphrase del passphrase
if not result: if not result:
return 1 raise SystemExit(1)
encrypted_export, encrypted_key = result encrypted_export, encrypted_key = result
@ -310,7 +320,7 @@ def main() -> int:
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
if not transfer_to_indri(encrypted_export, encrypted_key, timestamp): if not transfer_to_indri(encrypted_export, encrypted_key, timestamp):
return 1 raise SystemExit(1)
console.print() console.print()
console.print("[bold]DISASTER RECOVERY:[/bold]") console.print("[bold]DISASTER RECOVERY:[/bold]")
@ -320,8 +330,7 @@ def main() -> int:
console.print(" Passphrase: {master_password}:{secret_key}") console.print(" Passphrase: {master_password}:{secret_key}")
console.print(f" 4. age -d -i key.txt < ...age > export.1pux") console.print(f" 4. age -d -i key.txt < ...age > export.1pux")
console.print(" 5. Open export.1pux with 1Password or unzip to inspect") console.print(" 5. Open export.1pux with 1Password or unzip to inspect")
return 0
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) app()

View file

@ -1,7 +1,7 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env -S uv run --script
# /// script # /// script
# requires-python = ">=3.12" # 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" #MISE description="List unresolved comments on a PR"
#USAGE arg "<pr_number>" help="Pull request number" #USAGE arg "<pr_number>" help="Pull request number"
@ -14,9 +14,10 @@ if its 'resolver' field is null.
Usage: mise run pr-comments <pr_number> Usage: mise run pr-comments <pr_number>
""" """
import sys from typing import Annotated
import httpx import httpx
import typer
from rich.console import Console from rich.console import Console
from rich.text import Text 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() 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() console = Console()
if len(sys.argv) < 2:
console.print("[red]Error:[/red] Please provide a PR number")
console.print("Usage: mise run pr-comments <pr_number>")
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 unresolved_comments: list[tuple[dict, dict]] = [] # (review, comment) pairs
with httpx.Client() as client: with httpx.Client() as client:
@ -68,7 +64,7 @@ def main() -> int:
console.print(f"[red]Error:[/red] PR #{pr_number} not found") console.print(f"[red]Error:[/red] PR #{pr_number} not found")
else: else:
console.print(f"[red]Error:[/red] API request failed: {e}") 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 each review, get comments and filter to unresolved
for review in reviews: for review in reviews:
@ -83,7 +79,7 @@ def main() -> int:
if not unresolved_comments: if not unresolved_comments:
console.print(f"[green]No unresolved comments on PR #{pr_number}[/green]") console.print(f"[green]No unresolved comments on PR #{pr_number}[/green]")
return 0 raise SystemExit(0)
# Display unresolved comments # Display unresolved comments
console.print(f"[bold]Unresolved Comments on PR #{pr_number}[/bold] ({len(unresolved_comments)} 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() console.print()
return 0
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) app()