Skip to content
BreachPilot

Entrypoints

main.py (Flow A, active) and legacy/cli.py (Flow B, frozen) are the two command-line entries. Root cli.py, agent_loop.py, planner.py, executor.py, observer.py, memory.py, mission.py, and evidence.py are DeprecationWarning shims over legacy.*. The installed breachpilot command maps to main:main.

Overview

EntryFlowHow to run
main.pyFlow A (active engine)python main.py, python main.py --menu, python main.py --target ...
breachpilotFlow A (console script)breachpilot --doctor (same args as main.py)
legacy/cli.pyFlow B (frozen, SQLite)python legacy/cli.py init-mission --config mission.yaml
cli.py (root)Flow B shimpython cli.py status (warns, delegates to legacy.cli)
app.pyFlow A (not a CLI)Imported by main._run_daemon via create_app; never run directly
python main.py --doctor
python main.py --target 10.0.0.50 --mode recon --goal initial_access
breachpilot --daemon --api-port 9000
python legacy/cli.py init-mission --config mission.yaml
python legacy/cli.py status

Flow A — main.py

Argument parser is main.parse_args (main.py:352-645). The --version flag prints BreachPilot <version> and exits.

Flag groups

Group names are the argparse argument groups in parse_args.

GroupFlags
targeting--target, --mode {recon,attack,fast}, --goal, --custom-goal, --config, --model, --model-strategy, --mcp-transport, --http-port, --reports-dir
api keys--setup-api-keys, --api-key-file, --no-api-key-prompt
output--plain, --menu, --json, --quiet, --debug
swarm & reasoning--swarm, --parallel-swarm, --critic, --reflection, --adaptive-exploits, --long-session, --multi-model-consult / --no-multi-model-consult, --observer-mode, --recon-first / --no-recon-first, --ultrathink
operational--doctor, --demo, --resume, --yes, --self-test
eval & regression--eval [TARGET ...], --eval-list, --save-baseline, --check-regression
benchmark suite--benchmark [SUITE ...], --benchmark-list, --scenario, --tag, --trials
ctf autopilot--ctf, --ctf-flag-path, --ctf-root-shell, --ctf-port, --ctf-marker
runtime skills--skills {on,off,hints,lookup}, --skills-list, --skills-include, --skills-exclude, --no-skills-reselect
plugins--list-plugins
webui--demon / --daemon, --web, --api-host, --api-port

Notable defaults from parse_args: --config defaults to config.yaml; --eval and --benchmark use nargs="*" with default None (bare --eval is an empty list, meaning all oracle targets); --ctf-root-shell is store_true with default False (main.py:577-583); --api-host defaults to None (resolved to 127.0.0.1 in _run_daemon); --api-port defaults to None (resolved to 8765).

Dispatch order

Order of the gates in main() (main.py:1307-1528):

StepGateAction
1parse_argsParse argv; set ui.plain from --plain / --quiet / --json
2bootstrap_startup_api_keysLoad keys; interactive prompt only when --menu
3--setup-api-keys aloneSave keys and exit 0 (setup_only)
4ChatGPT runtime_ensure_chatgpt_runtime; skipped for --doctor / --self-test / --eval / --benchmark / --skills-list / --list-plugins
5--daemon / --webConflict check first (exit 2), else _run_daemon
6--doctortools.doctor.run_doctor and exit
7--self-testtools.self_test.run_self_test and exit
8--eval-listPrint eval_targets/*.oracle.json ids and exit 0
9--save-baseline / --check-regression without --eval or --benchmarkError and exit 2
10--benchmark / --benchmark-listtools.benchmark_cli.run_benchmark_cli and exit
11--eval with --targetLegacy single-target tools.eval_harness.run_eval
12--eval without --targetGraded suite run_graded_eval over all (bare) or listed ids; optional save_baseline / check_regression; exit 0, or 1 on regression
13--ctftools.ctf_mode.run_ctf and exit
14--demotools.demo_mode.run_demo and exit
15--skills-listprint_skills_catalog and exit
16--list-pluginslist_discovered_plugins and exit
17--menutools.interactive_menu.run_interactive_menu and exit
18No args and no --targetDefault to WebUI daemon (args.web = True, _run_daemon)
19Otherwiseasync_main(args) (terminal run path)

The graded --eval path (step 12) runs without --target and does not exit 2; exit 2 from tools.eval_harness.run_eval (tools/eval_harness.py:382-387) belongs to the legacy single-target path, which main() only reaches when --target is present.

python main.py --eval                       # graded suite, all oracle targets
python main.py --eval dvwa juice_shop --save-baseline
python main.py --eval --check-regression    # exit 1 on regression
python main.py --eval --target 10.0.0.50    # legacy single-target harness

Exit codes

CodeMeaningSource
0Success; --setup-api-keys only; --eval-list; daemon already running; graded eval passmain.main, main._run_daemon
1Run failure; missing uvicorn; create_app import failure; WebUI build failure; check_regression failure; async_main errorsmain.main, main._run_daemon, main.async_main
2Daemon combined with target/goal/menu/doctor/demo/eval/self-test/skills/plugins/setup flags; non-loopback --api-host; --save-baseline / --check-regression without --eval or --benchmarkmain.main, main._run_daemon
130KeyboardInterruptmain.main, main._run_daemon

_run_daemon (main.py:955-1057) refuses any --api-host outside 127.0.0.1 / localhost / ::1 with exit 2, builds webui/dist/ for --web via _ensure_webui_build, and serves create_app from app.py with uvicorn.

Console script

pyproject.toml:65-66 registers the installed command:

[project.scripts]
breachpilot = "main:main"

breachpilot takes the same flags as python main.py. python main.py with no arguments starts the WebUI daemon (--web); python main.py --menu forces the terminal menu.

Flow B — legacy/cli.py

Parser is legacy/cli.py:build_parser (legacy/cli.py:530-598). Every subcommand except init-mission accepts --mission-id after the subcommand (resume/reattach a named mission instead of the latest active one). Mission loading is _load_mission / _require_mission; the workspace root is _workspace_root (RESEARCH_WORKSPACE, default research_workspace) with the database at research.db.

Mission subcommands

CommandFlags / positionalsFunctionParser lines
init-mission--config <path> (required)cmd_init_missionlegacy/cli.py:547-549
add-scope--allow, --deny, --notescmd_add_scopelegacy/cli.py:551-556
list-scope--mission-idcmd_list_scopelegacy/cli.py:558-560
next-task--mission-idcmd_next_tasklegacy/cli.py:562-564
list-tasks--mission-idcmd_list_taskslegacy/cli.py:566-568
run-task[task_id] (empty = next pending), --mission-idcmd_run_tasklegacy/cli.py:570-573
summarize-target--target, --mission-idcmd_summarize_targetlegacy/cli.py:575-578
list-findings--mission-idcmd_list_findingslegacy/cli.py:580-582
validate-findingfinding_id, --mission-idcmd_validate_findinglegacy/cli.py:584-587
generate-reportfinding_id, --mission-idcmd_generate_reportlegacy/cli.py:589-592
status--mission-idcmd_statuslegacy/cli.py:594-596
python legacy/cli.py init-mission --config mission.yaml
python legacy/cli.py add-scope --allow "*.example.com" --notes "main scope"
python legacy/cli.py next-task --mission-id M-001
python legacy/cli.py run-task T-00001
python legacy/cli.py status

run-task gates through ScopeGate.check_scope, then RiskController, and marks needs_approval without executing when either side requires human approval (legacy/cli.py:298-352). Exit codes: 0 success, 1 error (including scope/risk blocks), 130 on Ctrl-C, and 1 with help text when no subcommand is given (legacy/cli.py:604-622).

Flow B compatibility shims

Root files are one-release proxies that warn and re-export legacy.* (see legacy/README.md). Each follows this shape:

import importlib
import sys
import warnings

warnings.warn("cli is legacy; use legacy.cli", DeprecationWarning, stacklevel=2)
_mod = importlib.import_module("legacy.cli")
sys.modules[__name__] = _mod
Root shimCanonical moduleWarning
cli.pylegacy.clicli is legacy; use legacy.cli
agent_loop.pylegacy.agent_loopagent_loop is legacy; use legacy.agent_loop
planner.pylegacy.plannerplanner is legacy; use legacy.planner
executor.pylegacy.executorexecutor is legacy; use legacy.executor
observer.pylegacy.observerobserver is legacy; use legacy.observer
memory.pylegacy.memorymemory is legacy; use legacy.memory
mission.pylegacy.missionmission is legacy; use legacy.mission
evidence.pylegacy.evidenceevidence is legacy; use legacy.evidence

New code must import from legacy.* (Flow B) or tools.* (Flow A), never from the root shims.

Mission config — mission.yaml vs --config

mission.yaml is the example Flow B mission file consumed by legacy/cli.py init-mission --config mission.yaml. main.py --config is a different flag: it points at config.yaml (provider, model, MCP, and run settings), not at mission.yaml.

program_name: "Example Authorized Program"
objective: "Find valid, in-scope, non-destructive, reproducible vulnerabilities with evidence."
risk_profile: "high_authorized_testing"
allowed_assets:
  - "example.com"
  - "*.example.com"
disallowed_assets:
  - "payments.example.com"
forbidden_actions:
  - "denial_of_service"
rate_limits:
  default_requests_per_second: 2
  max_concurrent_requests: 3
testing_modes:
  - "recon"
  - "analysis"
accounts: []
notes: |
  Operator rules and program context.
mission.yaml keyPurpose
program_nameMission / program label stored on the mission row
objectiveEngagement objective (defaults to DEFAULT_OBJECTIVE in legacy/mission.py)
risk_profileOne of low_noise_non_destructive, standard_authorized, high_authorized_testing; selects testing modes, command/task budgets, and exploit/pivot allowance
allowed_assetsIn-scope domains, IPs, CIDRs, *.wildcards
disallowed_assetsExplicit exclusions, including disallowed_assets that overlap the allow list
forbidden_actionsAugments the profile forbidden_by_default set (union, never replaces)
rate_limitsPer-target rate limits (default_requests_per_second, max_concurrent_requests, search_rate_limit_per_minute)
testing_modesPermitted phases (recon, analysis, test, validate, exploit, report); defaults from the risk profile when empty
accountsOptional test credentials for authenticated testing
notesFree-text program rules and context
FlagDefault / requiredPoints at
main.py --config (targeting group)Defaults to config.yamlFlow A run config (models, MCP, exploit, API); loaded by load_config
legacy/cli.py init-mission --configRequiredFlow B mission YAML (mission.yaml shape); loaded by cmd_init_mission via yaml.safe_load

Implementation note: legacy/mission.py also accepts id, target_assets, and notes keys in _MISSION_KEYS; max_commands_per_session and max_tasks_active are derived from the risk profile, not set directly in mission.yaml.

Source map

  • main.py
  • legacy/cli.py
  • cli.py
  • agent_loop.py
  • planner.py
  • executor.py
  • observer.py
  • memory.py
  • mission.py
  • evidence.py
  • mission.yaml
  • legacy/mission.py
  • legacy/README.md
  • pyproject.toml
  • tools/eval_harness.py
  • docs/cli-reference.md
source: repo docs (build sync)Edit this page on GitHub →