Skip to content

[WIP] Split oversized TUI views and CLI command files - #525

Merged
Nicola Franco (franconicola) merged 10 commits into
mainfrom
claude/split-oversized-tui-views
Jul 26, 2026
Merged

[WIP] Split oversized TUI views and CLI command files#525
Nicola Franco (franconicola) merged 10 commits into
mainfrom
claude/split-oversized-tui-views

Conversation

@Claude

@Claude Claude AI commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Thanks for asking me to work on this. I will get started on it and keep this PR's description up to date as I form a plan and make progress.


This section details on the original issue you should resolve

<issue_title>Split oversized TUI views and CLI command files</issue_title>
<issue_description>Problem.
Several TUI / CLI files exceed 1 kLOC, mixing rendering, event handling, and business logic.

Targets.

Actions.

  • For each TUI file: extract widgets to siblings (screens/, widgets/), keep top-level files as routers.
  • For cli/commands/attack.py: split into parse_config() (pure, testable) and run_attack() (uses HackAgent). Both reusable from the TUI.
  • Add textual snapshot tests for any newly extracted screens.

Acceptance: no file in cli/ exceeds 600 lines; parse_config is unit-tested; TUI snapshot tests added.</issue_description>

Comments on the Issue (you are @claude[agent] in this section)

Comment thread hackagent/cli/tui/views/results/tab.py Fixed
bias_gap_str = (
f" Bias gap: [bold]{float(bias_gap):.3f}[/bold]"
)
except (TypeError, ValueError):
_backend = self.create_backend()
_res_page = _backend.list_results(run_id=_rid, page=1, page_size=500)
run_results = list(_res_page.items)
except Exception:
if not attack_type_display:
attack_type_display = getattr(_att, "type", "") or ""
break
except Exception:
else run.timestamp.replace(tzinfo=tz.UTC)
)
run_age = (now - run_timestamp).total_seconds() / 60
except Exception:
):
delta = run.completed_at - run.started_at
duration = f"{delta.total_seconds():.1f}s"
except Exception:
Comment thread hackagent/cli/tui/views/results/formatters/summaries.py Fixed
Comment thread hackagent/cli/tui/views/results/tab.py Fixed
Comment thread hackagent/cli/tui/views/results/tab.py Fixed
Comment thread hackagent/cli/tui/views/results/tab.py Fixed
@franconicola

Copy link
Copy Markdown
Member

@claude[agent] fix the comments from co-pilot

from textual.drivers.linux_driver import LinuxDriver

LinuxDriver._query_in_band_window_resize = lambda self: None
except Exception:
from textual.drivers.linux_inline_driver import LinuxInlineDriver

LinuxInlineDriver._query_in_band_window_resize = lambda self: None
except Exception:
try:
if tui_log_handler in hackagent_logger.handlers:
hackagent_logger.removeHandler(tui_log_handler)
except Exception:
try:
self.query_one("#escalate-only-mitigated", Checkbox).display = is_chain
self.query_one("#escalate-only-mitigated-help", Static).display = is_chain
except Exception:
@franconicola
Nicola Franco (franconicola) marked this pull request as ready for review July 26, 2026 16:05
Copilot AI review requested due to automatic review settings July 26, 2026 16:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors oversized CLI/TUI modules into smaller packages, extracting reusable helpers (notably for hackagent eval config parsing and TUI views) and adding snapshot-based UI regression tests to guard layout changes.

Changes:

  • Split hackagent cli tui views into packages with router-style __init__.py re-exports, and extracted results/attacks tab logic into mixins/modules.
  • Refactored hackagent scan into a package (helpers.py, command.py, quick.py) and adjusted unit tests to patch the new import paths.
  • Added pytest-textual-snapshot and introduced snapshot tests + standalone Textual “app” harnesses for AttacksTab and ResultsTab.

Reviewed changes

Copilot reviewed 64 out of 69 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/cli/tui/test_view_snapshots.py Adds snapshot tests for extracted TUI tabs.
tests/unit/cli/tui/snapshot_apps/init.py Declares snapshot-app package.
tests/unit/cli/tui/snapshot_apps/attacks_tab_app.py Standalone Textual app for AttacksTab snapshotting.
tests/unit/cli/tui/snapshot_apps/results_tab_app.py Standalone Textual app for ResultsTab snapshotting.
tests/unit/cli/test_scan_command.py Updates patch paths to new scan package structure.
tests/unit/cli/test_attack_config.py Adds unit tests for parse_config / guardrail config helpers.
pyproject.toml Adds pytest-textual-snapshot dev dependency.
hackagent/cli/main.py Moves help/TUI bootstrap helpers into dedicated modules and adjusts imports/registration.
hackagent/cli/help_page.py Extracts rich --help rendering into its own module.
hackagent/cli/bootstrap.py Extracts default TUI launch + terminal patch + welcome screen.
hackagent/cli/commands/scan/init.py Creates router module re-exporting scan API (compat surface).
hackagent/cli/commands/scan/command.py Holds the scan click command (logic moved out of former monolith).
hackagent/cli/commands/scan/helpers.py Extracts scan preset defaults + pure helpers (provider endpoint, ASR parsing).
hackagent/cli/commands/scan/quick.py Extracts reusable run_quick_scan implementation behind hackagent eval.
hackagent/cli/commands/attack/init.py Creates router module re-exporting eval surface + helpers.
hackagent/cli/commands/attack/catalog.py Centralizes strategy metadata used by eval list/info.
hackagent/cli/commands/attack/options.py Centralizes shared click options for strategy commands.
hackagent/cli/commands/attack/config.py Implements parse_config/goal parsing/guardrail-config helpers.
hackagent/cli/commands/attack/runner.py Shared execution path for all hackagent eval <strategy> commands.
hackagent/cli/commands/attack/display.py Rich rendering helpers for eval command output.
hackagent/cli/commands/attack/group.py Defines hackagent eval group and campaign entrypoint behavior.
hackagent/cli/commands/attack/info.py Implements hackagent eval list/info.
hackagent/cli/commands/attack/strategies.py Generates per-strategy hackagent eval <strategy> subcommands.
hackagent/cli/commands/attack/chain.py Adds/maintains hackagent eval chain command.
hackagent/cli/tui/views/attacks/init.py Router/re-export module preserving historical import surface.
hackagent/cli/tui/views/attacks/tab.py New AttacksTab router composing extracted mixins.
hackagent/cli/tui/views/attacks/layout.py Extracted widget-tree composition for AttacksTab.
hackagent/cli/tui/views/attacks/helpers.py Extracted module-level constants/utilities for AttacksTab.
hackagent/cli/tui/views/attacks/runner.py Extracted validation + worker-launch logic for AttacksTab.
hackagent/cli/tui/views/results/init.py Router/re-export module preserving historical import surface.
hackagent/cli/tui/views/results/tab.py New ResultsTab router composing extracted mixins.
hackagent/cli/tui/views/results/table.py Extracted run-list table rendering for results view.
hackagent/cli/tui/views/results/details.py Extracted right-panel run/result detail rendering.
hackagent/cli/tui/views/results/export.py Extracted CSV/JSON export actions for results view.
hackagent/cli/tui/views/results/formatters/init.py Aggregates pure formatting helpers for results view.
hackagent/cli/tui/views/results/formatters/datetimes.py Extracted datetime coercion + local formatting.
hackagent/cli/tui/views/results/formatters/text.py Extracted Rich escaping + message formatting utilities.
hackagent/cli/tui/views/results/formatters/summaries.py Extracted summary/detail string builders for results/traces/config.
hackagent/cli/tui/views/results/formatters/run_report.py Extracted run-level report header builder.
hackagent/cli/tui/attack_specs/init.py Introduces TUI-local spec registry as a decoupled package.
hackagent/cli/tui/attack_specs/registry.py Registry for ordered technique_key -> spec mapping.
hackagent/cli/tui/attack_specs/types.py Dataclasses/enums for declarative form specs + validation.
hackagent/cli/tui/attack_specs/specs/init.py Imports/registers all per-technique specs.
hackagent/cli/tui/attack_specs/specs/advprefix.py Adds AdvPrefix spec.
hackagent/cli/tui/attack_specs/specs/autodan_turbo.py Adds AutoDAN-Turbo spec.
hackagent/cli/tui/attack_specs/specs/baseline.py Adds Baseline spec.
hackagent/cli/tui/attack_specs/specs/bon.py Adds BoN spec.
hackagent/cli/tui/attack_specs/specs/cipherchat.py Adds CipherChat spec.
hackagent/cli/tui/attack_specs/specs/fc.py Adds FC-Attack spec.
hackagent/cli/tui/attack_specs/specs/flipattack.py Adds FlipAttack spec.
hackagent/cli/tui/attack_specs/specs/h4rm3l.py Adds h4rm3l spec.
hackagent/cli/tui/attack_specs/specs/mml.py Adds MML spec.
hackagent/cli/tui/attack_specs/specs/pair.py Adds PAIR spec.
hackagent/cli/tui/attack_specs/specs/pap.py Adds PAP spec.
hackagent/cli/tui/attack_specs/specs/static_template.py Adds Static Template spec.
hackagent/cli/tui/attack_specs/specs/tap.py Adds TAP spec.
hackagent/cli/tui/attack_specs/specs/tfc.py Adds tFC-Attack spec.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread hackagent/cli/main.py
Comment on lines 418 to +424
if __name__ == "__main__":
cli()


# Add command groups
cli.add_command(config.config)
cli.add_command(agent.agent)
Comment on lines +226 to +234
def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handle button press events."""
if event.button.id == "refresh-results":
self.refresh_data()
elif event.button.id == "export-csv":
self._export_results_csv()
elif event.button.id == "export-json":
self._export_results_json()

Comment on lines +41 to +74
for result in self.results_data:
# Get status
status = "Unknown"
if hasattr(result, "evaluation_status"):
status_val = result.evaluation_status
status = (
status_val.value
if hasattr(status_val, "value")
else str(status_val)
)

# Get created date
created = "Unknown"
if hasattr(result, "created_at") and result.created_at:
created = str(result.created_at)

# Calculate duration
duration = "N/A"
if hasattr(result, "run") and result.run:
run = result.run
if (
hasattr(run, "started_at")
and run.started_at
and hasattr(run, "completed_at")
and run.completed_at
):
try:
if isinstance(run.started_at, datetime) and isinstance(
run.completed_at, datetime
):
delta = run.completed_at - run.started_at
duration = f"{delta.total_seconds():.1f}s"
except Exception:
pass
Comment on lines +253 to +257
def action_show_summary(self) -> None:
"""Show a quick summary for the selected run."""
if self.selected_result:
self._show_result_summary(self.selected_result)

self._show_advanced = (
pinned or self._advanced_hover_preview or self._advanced_focus_preview
)
except Exception:
@franconicola
Nicola Franco (franconicola) force-pushed the claude/split-oversized-tui-views branch from d677b28 to a46ea5d Compare July 26, 2026 16:23
@franconicola
Nicola Franco (franconicola) merged commit bcfc18c into main Jul 26, 2026
24 of 25 checks passed
@franconicola
Nicola Franco (franconicola) deleted the claude/split-oversized-tui-views branch July 26, 2026 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Split oversized TUI views and CLI command files

3 participants