FEAT Animated ASCII banner with raccoon mascot for PyRIT CLI#1417
FEAT Animated ASCII banner with raccoon mascot for PyRIT CLI#1417romanlutz merged 31 commits intoAzure:mainfrom
Conversation
- Create pyrit/cli/banner.py with frame-based animation engine - Raccoon mascot walks in from right, PYRIT text reveals left-to-right - Semantic color roles with light/dark terminal theme support - Graceful degradation: static banner when not a TTY, NO_COLOR, CI, or --no-animation - Ctrl+C during animation skips to static banner - Add --no-animation flag to pyrit_shell CLI - 24 unit tests covering color roles, themes, animation capability detection, frames, and fallback - Update existing shell tests for new banner integration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move cursor up (frame_height - 1) lines instead of frame_height, since the rendered frame has (N-1) newlines for N lines, leaving the cursor on the last line rather than below it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Redesign raccoon with bandit mask (=o.o=), w nose, wider head (/\___/\), and striped tail (~~) to look like a raccoon not a cat - Reserve vertical space before animation to prevent scroll drift when cursor is near bottom of terminal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Single raccoon with clear features: pointed ears, bandit mask (=o o=), w nose, bushy striped tail (~~~~~) - Remove duplicate right-side raccoon from commands section - Fix animation drift: wait for background init to complete before playing animation so log messages don't corrupt cursor positioning - Build banner programmatically to guarantee correct line widths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The raccoon is now rendered using Unicode braille characters with inverted dots — the raccoon face is drawn as positive space on an empty background, blending naturally with the banner's box-drawing and block-letter aesthetic. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
User-edited raccoon trimmed to 12 lines. Cleared bottom-row braille dots (bits 6-7) from chin characters to avoid visual artifacts below the chin line. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use RACCOON_BODY color for all header lines during animation Phase 2 instead of PYRIT_TEXT for lines with block letters. This ensures the entire raccoon lights up uniformly. Also fix PYRIT_START_ROW (2) so both subtitles fit within HEADER_ROWS (12), add lowercase y to block letters, and remove stray braille dots from chin line. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Normalize all braille raccoon lines to exactly 30 chars (replace mixed regular spaces with braille empty U+2800) to fix alignment - Use RACCOON_BODY color for all header rows including subtitle rows so the entire raccoon lights up consistently - Add striped raccoon tail hanging from divider into commands section with alternating thick/thin stripes and bushy tip Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove SUBTITLE color overrides in Phase 2 and Phase 3 animation so all raccoon lines use consistent RACCOON_BODY color throughout - Shift chin motif left by 2 positions to center under face - Redesign tail as 6-char wide braille art with alternating dense/ sparse stripes tapering from wide to narrow tip Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tail now starts at w=5, bulges to w=6, then tapers through 5,4,3,2,1. Dark stripes use full braille fill, light stripes use thin edge delimiters only (top dots at boundaries). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tail is now 9 chars wide (50% wider), curls rightward with increasing offset per line, and uses vertical braille delimiters (left=dots 1,2,3 right=dots 4,5,6) on light stripes instead of horizontal ones. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Lines with tail content now always use COMMANDS color instead of inheriting BORDER color from empty separator lines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tail offsets now follow 0,0,1,2,3,3,3,2,1,0 creating a proper curl that curves right then sweeps back left at the tip. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Major animation upgrade inspired by GitHub Copilot CLI approach: - Per-segment coloring: AnimationFrame now supports segment_colors for different colors within the same line (raccoon=magenta, PYRIT text=cyan, subtitles=white, tail=magenta, stars=yellow) - Vibrant ANSI color theme: bright_cyan for PYRIT text, bright_magenta for raccoon/tail, bright_yellow for sparkles - Sparkle stars (✦ ✧ · *) appear during raccoon entry and celebration phases at randomized positions - 3-frame sparkle celebration instead of 2 - Phase 4 preserves segment colors from static banner Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sparkle segments in Phase 3 overlapped with PYRIT_TEXT and SUBTITLE segments, causing _render_line_with_segments to output characters twice (e.g. 106 visible chars instead of 96). Rewrote the function to use a per-character color map where later segments override earlier ones, then group consecutive same-role characters for rendering. This guarantees visible output width matches input. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add per-segment coloring to quick start lines and non-tail command lines so the border characters (║) use BORDER color (cyan) while the text content uses COMMANDS color (white). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds an animated (with static fallback) startup banner to pyrit_shell, including a new banner module and accompanying unit tests, plus a --no-animation flag to disable animation.
Changes:
- Introduces
pyrit/cli/banner.pyimplementing frame-based terminal animation with theme-aware ANSI coloring and static fallback. - Updates
pyrit_shellto play the banner before starting the REPL and adds--no-animationCLI flag. - Adds/updates unit tests for the banner behavior and adjusts shell tests for the new intro behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
pyrit/cli/banner.py |
New banner implementation (static + animation rendering, theme detection, animation gating). |
pyrit/cli/pyrit_shell.py |
Integrates banner via cmdloop() override; adds --no-animation flag wiring. |
tests/unit/cli/test_banner.py |
New unit tests for banner colors/themes/frames/static/animation gating. |
tests/unit/cli/test_pyrit_shell.py |
Updates intro expectation now that intro is set via cmdloop()/banner. |
- Add Returns sections to all function docstrings (DOC201) - Simplify can_animate() return logic (SIM103) - Remove unused loop variable sparkle_idx (B007) - Fix segs variable redefinition in _build_animation_frames (mypy) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Apply ruff formatting fixes and auto-fixable lint issues. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
spencrr
left a comment
There was a problem hiding this comment.
LGTM! 🦝
Nits from my copilot review session:
Nit: No test coverage for Ctrl+C → static banner fallback
play_animation() has a KeyboardInterrupt handler (line ~700) that clears the screen, writes the static banner, and restores the cursor. This path isn't tested — a regression here could leave the terminal cursor hidden.
Suggested fix prompt:
Add a test to
TestPlayAnimationintests/unit/cli/test_banner.pythat mockscan_animateto returnTrue, mocks_detect_themeto returnDARK_THEME, mockssys.stdoutas a TTY, and mockstime.sleepwith a side_effect that counts calls and raisesKeyboardInterruptafter 3 invocations (to let a few frames render before the interrupt). Assert that the cursor is restored (\033[?25hin the written output) andplay_animationreturns"". Follow the existing test patterns in the class.
Nit: No test verifying animation actually writes frames to stdout
TestPlayAnimation only tests the non-animated paths (no_animation=True and can_animate()=False). The happy path — where animation runs and writes colored frames to stdout — has no test coverage.
Suggested fix prompt:
Add a test to
TestPlayAnimationintests/unit/cli/test_banner.pythat mockscan_animateto returnTrue, mocks_detect_themeto returnDARK_THEME, mockstime.sleepas a no-op, and mockssys.stdoutas a TTY. Assert thatplay_animation()returns"", thatstdout.writewas called, and that the written output contains ANSI escape codes (\033[), box-drawing characters (╔), and cursor hide/restore sequences (\033[?25land\033[?25h). Follow the existing test patterns in the class.
- Extract ASCII art assets (raccoon, PYRIT letters, tail) to banner_assets.py - Remove unused RACCOON_MASK and RACCOON_EYES ColorRole members and theme entries - Add StaticBannerData dataclass to replace opaque tuple return type - Add descriptive comments for layout constants (BOX_W, RACCOON_COL, etc.) - Fix PyRITShell.__init__ signature: add -> None and keyword-only args - Guard sparkle color segments on actual character insertion - Fix Ctrl+C handler to reposition cursor before clearing screen - Strengthen test_all_frames_have_consistent_width: check end chars + width - Add tests for --no-animation CLI flag in TestMain - Add tests for cmdloop/play_animation integration wiring Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Test that animation writes ANSI frames with cursor hide/restore to stdout - Test that Ctrl+C during animation restores cursor and shows static banner Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Added tests. thanks for the suggestion! |
…er' into romanlutz/animated-ascii-banner
…rrupt Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Adds an animated startup banner to pyrit_shell featuring a braille-art raccoon mascot, the PyRIT block-letter logo, and a striped curling tail —
inspired by the GitHub Copilot CLI's animated banner approach.
What it looks like
The banner plays a ~2.5 second animation on shell startup:
Architecture
- Per-segment coloring: Different ANSI colors within the same line (raccoon=magenta, PyRIT=cyan, tail=magenta, stars=yellow, border=cyan)
- Semantic color roles mapped to 4-bit ANSI palette with dark/light theme support
- Braille raccoon art — high-detail inverted Unicode braille face with bandit mask
- Curling striped tail — 9-char wide braille tail with S-curve shape and alternating dense/sparse stripes
- Graceful degradation: falls back to static banner for non-TTY, NO_COLOR, PYRIT_NO_ANIMATION, CI environments
- Ctrl+C skips animation to static banner immediately
corrupting cursor positioning. Adds --no-animation CLI flag.
How to test
Animated banner
python -m pyrit.cli.pyrit_shell
Static banner (skip animation)
python -m pyrit.cli.pyrit_shell --no-animation
Environment variable to disable
PYRIT_NO_ANIMATION=1 python -m pyrit.cli.pyrit_shell
Scope
roakey.banner.mp4