Skip to content

tot_agent.cli

Click-based command-line interface.

Command tree

graph TD
    root["tot-agent (group)"]
    root --> create
    root --> vote
    root --> simulate
    root --> seed
    root --> goal
    root --> users
    root --> info
    root --> covers

Module reference

tot_agent.cli

cli.py — Full-featured command-line interface for the tot-agent tool.

Entry point: tot-agent (registered via pyproject.toml [project.scripts]).

Global options (available on every sub-command via the top-level group):

--log-level Python logging level: DEBUG, INFO (default), WARNING, ERROR. --log-file Path to write log output in addition to stderr. --model Override the Claude model (e.g. claude-opus-4-5). --max-steps Override the per-run step ceiling. --site-url Override the target application base URL. --headless Run the browser without a visible window (inheritable by sub-commands).

Sub-commands

create Create A/B tests with real book covers. vote Have a single simulated user cast votes. simulate Simulate all configured users voting. seed Full pipeline: create tests, simulate voting, view results. goal Execute a custom natural-language goal. users List configured simulated users. info Show current runtime configuration. covers Preview book covers from a search query (no browser needed).

cli(ctx, log_level, log_file, model, max_steps, site_url)

tot-agent — Autonomous browser agent for scripted GUI testing.

Drive a real Playwright browser with Claude vision + tool-use to execute natural-language test goals against any web application.

Source code in src/tot_agent/cli.py
@click.group()
@click.version_option(__version__, prog_name="tot-agent")
@click.option(
    "--log-level",
    default="INFO",
    show_default=True,
    type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR"], case_sensitive=False),
    help="Python logging level.",
    envvar="TOT_LOG_LEVEL",
)
@click.option(
    "--log-file",
    default=None,
    metavar="PATH",
    help="Write logs to this file in addition to stderr.",
    envvar="TOT_LOG_FILE",
)
@click.option("--model", default=None, metavar="MODEL", help="Override Claude model ID.",
              envvar="AGENT_MODEL")
@click.option("--max-steps", default=None, type=int, metavar="N",
              help="Override per-run step ceiling.", envvar="MAX_AGENT_STEPS")
@click.option("--site-url", default=None, metavar="URL",
              help="Override target application base URL.", envvar="SITE_URL")
@click.pass_context
def cli(
    ctx: click.Context,
    log_level: str,
    log_file: str | None,
    model: str | None,
    max_steps: int | None,
    site_url: str | None,
) -> None:
    """tot-agent — Autonomous browser agent for scripted GUI testing.

    Drive a real Playwright browser with Claude vision + tool-use to execute
    natural-language test goals against any web application.
    """
    _configure_logging(log_level, log_file)
    ctx.ensure_object(dict)
    ctx.obj["model"] = model
    ctx.obj["max_steps"] = max_steps
    ctx.obj["site_url"] = site_url

create(ctx, count, genre, headless)

Create A/B tests with real book covers fetched from Open Library.

Source code in src/tot_agent/cli.py
@cli.command()
@click.option("--count", default=5, show_default=True, help="Number of A/B tests to create.")
@click.option("--genre", default="mixed", show_default=True,
              help="Book genre ('mixed' for random variety).")
@click.option("--headless", is_flag=True, help="Run browser without visible window.")
@click.pass_context
def create(ctx: click.Context, count: int, genre: str, headless: bool) -> None:
    """Create A/B tests with real book covers fetched from Open Library."""
    from tot_agent.agent import CreateTestsGoal
    goal = CreateTestsGoal(count=count, genre=genre).build()
    _run_agent(goal, headless, ctx.obj["model"], ctx.obj["max_steps"], ctx.obj["site_url"])

vote(ctx, user, count, headless)

Have a single simulated user cast votes on existing A/B tests.

Source code in src/tot_agent/cli.py
@cli.command()
@click.option("--user", required=True, help="Username of the simulated voter.")
@click.option("--count", default=3, show_default=True, help="Number of tests to vote on.")
@click.option("--headless", is_flag=True, help="Run browser without visible window.")
@click.pass_context
def vote(ctx: click.Context, user: str, count: int, headless: bool) -> None:
    """Have a single simulated user cast votes on existing A/B tests."""
    from tot_agent.agent import VoteGoal
    from tot_agent.config import get_user

    u = get_user(user)
    if u is None:
        from tot_agent.config import SIM_USERS

        available = [x.username for x in SIM_USERS]
        raise click.BadParameter(
            f"Unknown user {user!r}. Available: {available}", param_hint="--user"
        )
    goal = VoteGoal(u.username, u.password, vote_count=count, bias=u.voting_bias).build()
    _run_agent(goal, headless, ctx.obj["model"], ctx.obj["max_steps"], ctx.obj["site_url"])

simulate(ctx, votes_each, headless)

Simulate all configured users voting on existing A/B tests.

Source code in src/tot_agent/cli.py
@cli.command()
@click.option("--votes-each", default=2, show_default=True,
              help="Number of votes each user casts.")
@click.option("--headless", is_flag=True, help="Run browser without visible window.")
@click.pass_context
def simulate(ctx: click.Context, votes_each: int, headless: bool) -> None:
    """Simulate all configured users voting on existing A/B tests."""
    from tot_agent.agent import SimulateAllUsersGoal
    goal = SimulateAllUsersGoal(vote_count_each=votes_each).build()
    _run_agent(goal, headless, ctx.obj["model"], ctx.obj["max_steps"], ctx.obj["site_url"])

seed(ctx, tests, vote_rounds, headless)

Full pipeline: create tests, simulate all users voting, view results.

Source code in src/tot_agent/cli.py
@cli.command()
@click.option("--tests", default=5, show_default=True, help="Number of A/B tests to seed.")
@click.option("--vote-rounds", default=1, show_default=True,
              help="Voting rounds per user.")
@click.option("--headless", is_flag=True, help="Run browser without visible window.")
@click.pass_context
def seed(ctx: click.Context, tests: int, vote_rounds: int, headless: bool) -> None:
    """Full pipeline: create tests, simulate all users voting, view results."""
    from tot_agent.agent import FullSeedGoal
    goal = FullSeedGoal(test_count=tests, vote_rounds=vote_rounds).build()
    _run_agent(goal, headless, ctx.obj["model"], ctx.obj["max_steps"], ctx.obj["site_url"])

goal(ctx, goal_text, headless)

Execute a custom natural-language GOAL_TEXT against the target site.

Source code in src/tot_agent/cli.py
@cli.command()
@click.argument("goal_text")
@click.option("--headless", is_flag=True, help="Run browser without visible window.")
@click.pass_context
def goal(ctx: click.Context, goal_text: str, headless: bool) -> None:
    """Execute a custom natural-language GOAL_TEXT against the target site."""
    _run_agent(goal_text, headless, ctx.obj["model"], ctx.obj["max_steps"], ctx.obj["site_url"])

users()

List all configured simulated users.

Source code in src/tot_agent/cli.py
@cli.command()
def users() -> None:
    """List all configured simulated users."""
    from tot_agent.config import SIM_USERS
    table = Table(title="Simulated Users", show_header=True, header_style="bold cyan")
    table.add_column("Username", style="cyan")
    table.add_column("Password")
    table.add_column("Display Name")
    table.add_column("Voting Bias")
    for u in SIM_USERS:
        table.add_row(u.username, u.password, u.display_name, u.voting_bias)
    console.print(table)

info(ctx)

Show current runtime configuration (env vars and active overrides).

Source code in src/tot_agent/cli.py
@cli.command()
@click.pass_context
def info(ctx: click.Context) -> None:
    """Show current runtime configuration (env vars and active overrides)."""
    import tot_agent.config as _cfg

    table = Table(title="Runtime Configuration", show_header=True, header_style="bold")
    table.add_column("Setting", style="cyan", no_wrap=True)
    table.add_column("Value")
    rows = [
        ("site_url", ctx.obj.get("site_url") or _cfg.SITE_URL),
        ("model", ctx.obj.get("model") or _cfg.AGENT_MODEL),
        ("max_steps", str(ctx.obj.get("max_steps") or _cfg.MAX_AGENT_STEPS)),
        ("api_key_set", "yes" if _cfg.ANTHROPIC_API_KEY else "[red]NO[/red]"),
        ("screenshot_size", f"{_cfg.SCREENSHOT_WIDTH}x{_cfg.SCREENSHOT_HEIGHT}"),
        ("navigation_timeout_ms", str(_cfg.NAVIGATION_TIMEOUT_MS)),
        ("action_timeout_ms", str(_cfg.ACTION_TIMEOUT_MS)),
        ("wait_for_element_timeout_ms", str(_cfg.WAIT_FOR_ELEMENT_TIMEOUT_MS)),
        ("page_ready_timeout_ms", str(_cfg.PAGE_READY_TIMEOUT_MS)),
        ("sim_users", str(len(_cfg.SIM_USERS))),
    ]
    for name, val in rows:
        table.add_row(name, val)
    console.print(table)
    console.print()
    console.print("[dim]Routes:[/dim]")
    for route, path in _cfg.ROUTES.items():
        console.print(f"  [cyan]{route}[/cyan] -> {path}")

covers(query, count, verify)

Preview book covers from QUERY without launching a browser.

Example:

tot-agent covers "fantasy epic" --count 3 --verify
Source code in src/tot_agent/cli.py
@cli.command()
@click.argument("query")
@click.option("--count", default=4, show_default=True,
              help="Number of covers to fetch.")
@click.option("--verify", is_flag=True,
              help="HEAD-check each URL to confirm it resolves.")
def covers(query: str, count: int, verify: bool) -> None:
    """Preview book covers from QUERY without launching a browser.

    Example:

        tot-agent covers "fantasy epic" --count 3 --verify
    """
    from tot_agent.covers import CoverFetcher, verify_cover_url

    fetcher = CoverFetcher()
    results = fetcher.fetch(query, count=count)
    if not results:
        console.print(f"[red]No covers found for {query!r}[/red]")
        return
    table = Table(title=f"Covers: {query!r}", show_header=True, header_style="bold")
    table.add_column("#", style="dim", width=3)
    table.add_column("Title")
    table.add_column("Author")
    table.add_column("Source")
    table.add_column("URL")
    if verify:
        table.add_column("Live?")
    for i, c in enumerate(results, 1):
        row = [str(i), c.title, c.author, c.source, c.cover_url]
        if verify:
            ok = verify_cover_url(c.cover_url)
            row.append("[green]yes[/green]" if ok else "[red]no[/red]")
        table.add_row(*row)
    console.print(table)