Last active
April 4, 2025 00:05
-
-
Save watson0x90/cf3122778cb3414c6f648d7691e95e9f to your computer and use it in GitHub Desktop.
This script is designed to create a hacker-themed data transformation visualization using the rich library.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Script Name: DataTransformationLoader.py | |
# Author: Ryan Watson | |
# Gist Github: https://gist.github.com/Watson0x90 | |
# Created on: 2025-04-03 | |
# Last Modified: 2025-04-03 | |
# Description: Data transformation loader for hacker-themed visualization | |
# Purpose: This script is designed to create a hacker-themed data transformation visualization using the rich library. | |
# Version: 1.0.0 | |
# License: MIT License | |
# Dependencies: rich | |
# Usage: python DataTransformationLoader.py | |
import time | |
import random | |
import string | |
try: | |
from rich import print | |
except ImportError: | |
print("Rich library is not installed. Please install it using 'pip install rich'.") | |
exit(1) | |
from rich.console import Console | |
from rich.live import Live | |
from rich.panel import Panel | |
from rich.columns import Columns | |
from rich.text import Text | |
from rich.progress import Progress, BarColumn, TextColumn | |
from rich import box | |
from rich.layout import Layout | |
from rich.align import Align | |
console = Console() | |
# Character sets for transformations | |
BINARY_CHARS = "01" | |
HEX_CHARS = "0123456789ABCDEF" | |
ASCII_CHARS = string.ascii_letters + string.digits + string.punctuation | |
SPECIAL_CHARS = "!@#$%^&*()_+-=[]{}|;:,.<>?/~`" | |
def generate_random_string(length, char_set): | |
"""Generate a random string using the provided character set.""" | |
return ''.join(random.choice(char_set) for _ in range(length)) | |
def create_transition_frames(start_text, end_text, steps=10): | |
"""Create transition frames between two texts.""" | |
frames = [] | |
# Ensure both strings are the same length for transition | |
max_len = max(len(start_text), len(end_text)) | |
start_text = start_text.ljust(max_len) | |
end_text = end_text.ljust(max_len) | |
for step in range(steps + 1): | |
transition_text = "" | |
for i in range(max_len): | |
if step == steps: | |
# Final step shows the end text | |
char = end_text[i] if i < len(end_text) else " " | |
elif i < len(start_text) and i < len(end_text): | |
# Determine if this character should change in this step | |
if random.random() < step/steps: | |
char = end_text[i] | |
else: | |
char = start_text[i] | |
else: | |
char = " " | |
transition_text += char | |
frames.append(transition_text) | |
return frames | |
class DataBlock: | |
"""A data block with multiple representations and animated transformations.""" | |
def __init__(self, id, initial_data=None, length=8): | |
self.id = id | |
self.length = length if initial_data is None else len(initial_data) | |
self.ascii_data = initial_data if initial_data else generate_random_string(length, ASCII_CHARS) | |
self.binary_data = self._to_binary(self.ascii_data) | |
self.hex_data = self._to_hex(self.ascii_data) | |
self.encrypted_data = self._encrypt(self.ascii_data) | |
self.current_display = "ascii" # start with ASCII display | |
self.transition_frames = [] | |
self.current_frame = 0 | |
self.transitioning = False | |
self.transition_target = None | |
# Styling | |
self.styles = { | |
"ascii": "cyan", | |
"binary": "green", | |
"hex": "yellow", | |
"encrypted": "red" | |
} | |
def _to_binary(self, text): | |
"""Convert text to binary representation.""" | |
return ' '.join(format(ord(c), '08b') for c in text) | |
def _to_hex(self, text): | |
"""Convert text to hexadecimal representation.""" | |
return ' '.join(format(ord(c), '02x') for c in text) | |
def _encrypt(self, text): | |
"""Simple 'encryption' for visual effect.""" | |
return ''.join(chr((ord(c) + 7) % 128) for c in text) | |
def start_transition(self, target): | |
"""Start a transition to a new data representation.""" | |
if not self.transitioning and self.current_display != target: | |
start_data = getattr(self, f"{self.current_display}_data") | |
end_data = getattr(self, f"{target}_data") | |
self.transition_frames = create_transition_frames(start_data, end_data, steps=12) | |
self.current_frame = 0 | |
self.transitioning = True | |
self.transition_target = target | |
def update(self): | |
"""Update the animation state of the data block.""" | |
if self.transitioning: | |
if self.current_frame < len(self.transition_frames) - 1: | |
self.current_frame += 1 | |
else: | |
self.transitioning = False | |
self.current_display = self.transition_target | |
def render(self): | |
"""Render the current state of the data block.""" | |
if self.transitioning: | |
current_text = self.transition_frames[self.current_frame] | |
# Mix styles during transition for visual effect | |
start_style = self.styles[self.current_display] | |
end_style = self.styles[self.transition_target] | |
# Create glitch effect during transition | |
styled_text = Text() | |
for i, char in enumerate(current_text): | |
if random.random() < 0.2: # Occasional glitch | |
style = random.choice([start_style, end_style, "bold white"]) | |
else: | |
progress = self.current_frame / (len(self.transition_frames) - 1) | |
style = end_style if random.random() < progress else start_style | |
styled_text.append(char, style=style) | |
label = f"[{self.current_display} → {self.transition_target}]" | |
else: | |
current_data = getattr(self, f"{self.current_display}_data") | |
styled_text = Text(current_data, style=self.styles[self.current_display]) | |
label = f"[{self.current_display.upper()}]" | |
return Panel( | |
styled_text, | |
title=f"Data Block #{self.id} {label}", | |
border_style="bright_black", | |
box=box.ROUNDED | |
) | |
class HackerDataVisualizer: | |
"""Main class for the hacker-themed data visualization.""" | |
def __init__(self, num_blocks=5): | |
self.blocks = [DataBlock(i+1, length=random.randint(8, 16)) for i in range(num_blocks)] | |
self.progress = Progress( | |
TextColumn("[bold green]{task.description}"), | |
BarColumn(complete_style="green", finished_style="red"), | |
TextColumn("[bold]{task.percentage:>3.0f}%"), | |
expand=True | |
) | |
self.process_task = self.progress.add_task("[bold]Processing data...", total=100) | |
# Create layout | |
self.layout = Layout() | |
self.layout.split_column( | |
Layout(name="header", size=3), | |
Layout(name="main", ratio=1) | |
) | |
self.layout["main"].split_row( | |
Layout(name="blocks", ratio=3), | |
Layout(name="sidebar", ratio=1) | |
) | |
self.layout["sidebar"].split_column( | |
Layout(name="controls", ratio=1), | |
Layout(name="progress", ratio=1) | |
) | |
# Initialize header | |
self.update_header() | |
# Operation counters for display | |
self.operations = { | |
"encryptions": 0, | |
"decryptions": 0, | |
"transforms": 0, | |
"data_blocks": len(self.blocks) | |
} | |
def update_header(self): | |
"""Update the header with hacker-style information.""" | |
header_text = Text() | |
header_text.append("[ ", style="bold white") | |
header_text.append("CYBERSEC", style="bold red") | |
header_text.append(" :: ", style="bold white") | |
header_text.append("DATA TRANSFORMER", style="bold green") | |
header_text.append(" :: ", style="bold white") | |
header_text.append(f"SESSION:{random.randint(1000, 9999)}", style="bold yellow") | |
header_text.append(" ]", style="bold white") | |
self.layout["header"].update(Panel(header_text, box=box.ROUNDED)) | |
def update_controls(self): | |
"""Update the control panel with operation statistics.""" | |
stats = Text() | |
stats.append("OPERATIONS\n\n", style="bold white") | |
stats.append(f"Encryptions: ", style="bright_white") | |
stats.append(f"{self.operations['encryptions']}\n", style="red") | |
stats.append(f"Decryptions: ", style="bright_white") | |
stats.append(f"{self.operations['decryptions']}\n", style="green") | |
stats.append(f"Transforms: ", style="bright_white") | |
stats.append(f"{self.operations['transforms']}\n", style="yellow") | |
stats.append(f"Data Blocks: ", style="bright_white") | |
stats.append(f"{self.operations['data_blocks']}\n", style="cyan") | |
# Add some hackerish details | |
stats.append("\nSTATUS: ", style="bright_white") | |
stats.append("ACTIVE\n", style="green bold") | |
# Add a fake IP and port | |
ip = f"{random.randint(1, 255)}.{random.randint(1, 255)}.{random.randint(1, 255)}.{random.randint(1, 255)}" | |
port = random.randint(1000, 9999) | |
stats.append(f"TARGET: ", style="bright_white") | |
stats.append(f"{ip}:{port}\n", style="yellow") | |
self.layout["controls"].update(Panel(stats, title="System Monitor", border_style="bright_black")) | |
def update_progress(self, value): | |
"""Update the progress bar.""" | |
self.progress.update(self.process_task, completed=value) | |
self.layout["progress"].update(Panel(self.progress, title="Data Processing", border_style="bright_black")) | |
def update(self): | |
"""Update all components of the visualization.""" | |
# Update all data blocks | |
for block in self.blocks: | |
block.update() | |
# Randomly start transitions for some blocks | |
for block in self.blocks: | |
if not block.transitioning and random.random() < 0.05: | |
# Choose a new format to transition to | |
current = block.current_display | |
options = [k for k in block.styles.keys() if k != current] | |
target = random.choice(options) | |
block.start_transition(target) | |
# Update operation counters | |
if target == "encrypted": | |
self.operations["encryptions"] += 1 | |
elif current == "encrypted": | |
self.operations["decryptions"] += 1 | |
self.operations["transforms"] += 1 | |
# Render all blocks | |
block_panels = [block.render() for block in self.blocks] | |
self.layout["blocks"].update(Columns(block_panels, equal=True)) | |
# Update other components | |
self.update_controls() | |
def run(self, duration=60): | |
"""Run the visualization for the specified duration.""" | |
start_time = time.time() | |
with Live(self.layout, refresh_per_second=20, screen=True) as live: | |
while time.time() - start_time < duration: | |
progress = min(100, ((time.time() - start_time) / duration) * 100) | |
self.update_progress(progress) | |
self.update() | |
time.sleep(0.05) # Smoother animation with faster refresh | |
def main(): | |
console.print("[bold green]Initializing Data Transformation System...[/bold green]") | |
time.sleep(1) | |
console.print("[bold yellow]Starting visualization engine...[/bold yellow]") | |
time.sleep(0.5) | |
visualizer = HackerDataVisualizer(num_blocks=5) | |
try: | |
visualizer.run(duration=60) # Run for 60 seconds | |
console.print("[bold green]Data processing complete.[/bold green]") | |
except KeyboardInterrupt: | |
console.print("[bold red]Operation terminated by user.[/bold red]") | |
if __name__ == "__main__": | |
main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Script Name: ghost_army_loader.py | |
# Author: Ryan Watson | |
# Gist Github: https://gist.github.com/Watson0x90 | |
# Created on: 2025-04-03 | |
# Last Modified: 2025-04-03 | |
# Description: Data transformation loader for hacker-themed visualization | |
# Purpose: This script is designed to create a visualization using the rich library. | |
# Version: 1.0.0 | |
# License: MIT License | |
# Dependencies: rich | |
# Usage: python ghost_army_loader.py | |
try: | |
from rich import print | |
except ImportError: | |
print("Rich library is not installed. Please install it using 'pip install rich'.") | |
exit(1) | |
import time | |
import random | |
import string | |
import itertools | |
import math | |
from datetime import datetime | |
from rich.console import Console | |
from rich.live import Live | |
from rich.panel import Panel | |
from rich.columns import Columns | |
from rich.text import Text | |
from rich.progress import Progress, BarColumn, TextColumn, SpinnerColumn | |
from rich import box | |
from rich.layout import Layout | |
from rich.align import Align | |
from rich.table import Table | |
# Initialize console | |
console = Console() | |
# Ghost Army operational code names and tools | |
# Based on historical deception tactics and modern cyber operations | |
GHOST_CODENAMES = [ | |
"PHANTOM", "SPECTER", "MIRAGE", "DECOY", "DOPPELGANGER", | |
"MASQUERADE", "CHAMELEON", "ECHO", "FACADE", "HOLOGRAM", | |
"SHADOWCAST", "APPARITION", "POLTERGEIST", "SILHOUETTE", "WRAITH", | |
"INFLATABLE", "SONIC", "RUBBER", "SPOOF", "DECEPTION", | |
"BLUFF", "RUSE", "ARTIFICE", "SIMULATION", "PROJECTION" | |
] | |
DECEPTION_TYPES = [ | |
"VISUAL", "ACOUSTIC", "RADIO", "NETWORK", "INFRASTRUCTURE", | |
"IDENTITY", "SYSTEM", "BEHAVIOR", "TRAFFIC", "SUPPLY CHAIN", | |
"CREDENTIAL", "PROTOCOL", "APPEARANCE", "PRESENCE", "MOVEMENT" | |
] | |
TARGET_PLATFORMS = [ | |
"CORPORATE NETWORKS", "INDUSTRIAL SYSTEMS", "CLOUD INFRASTRUCTURE", "PUBLIC NETWORKS", | |
"TELECOM", "MOBILE DEVICES", "WEB APPLICATIONS", "EMBEDDED SYSTEMS", "IOT DEVICES", | |
"SOCIAL MEDIA", "EMAIL SYSTEMS", "AUTHENTICATION SERVICES", "VIRTUAL MACHINES" | |
] | |
OPERATIONS = [ | |
"RUBBER RIVER", "SONIC FOREST", "GHOST DIVISION", "DECOY OUTPOST", | |
"PHANTOM FLEET", "SPOOKY HIGHWAY", "SHADOW BATTALION", "INFLATABLE ARMY", | |
"ECHO PATROL", "DISAPPEARING ACT", "ARTFUL RUSE", "MIRAGE SQUADRON" | |
] | |
ENCRYPTION_ALGORITHMS = [ | |
"AES-256-GCM", "ECC P-384", "ECDH", "ChaCha20-Poly1305", | |
"RSA-4096", "SHA-512", "HMAC-SHA-256", "XTS-AES" | |
] | |
C2_PROTOCOLS = [ | |
"HTTPS-DYNAMIC", "DNS-TUNNEL", "TOR-BRIDGE", "ICMP-COVERT", | |
"TCP-MASKED", "UDP-FRAGMENTED", "CELLULAR-SMS", "SATELLITE-BURST" | |
] | |
DECEPTION_STAGES = [ | |
"RECONNAISSANCE", "MISATTRIBUTION", "PHANTOM PRESENCE", | |
"HONEYPOT DEPLOYMENT", "FALSE FLAG", "DECOY PLACEMENT", | |
"TRAFFIC MANIPULATION", "ILLUSION PROJECTION", "COVER TRACKS" | |
] | |
class HexDisplay: | |
"""Simulates a hex dump display with animated changes.""" | |
def __init__(self, width=16, rows=6): | |
self.width = width | |
self.rows = rows | |
self.offset = 0 | |
self.data = self._generate_random_data(rows * width) | |
self.highlights = [] | |
self.last_update = time.time() | |
self.update_interval = 0.2 | |
def _generate_random_data(self, size): | |
return [random.randint(0, 255) for _ in range(size)] | |
def update(self): | |
"""Update the hex display with new data and highlights.""" | |
if time.time() - self.last_update > self.update_interval: | |
# Add a new row of data and remove the first one | |
new_row = self._generate_random_data(self.width) | |
self.data = self.data[self.width:] + new_row | |
# Update offset | |
self.offset += self.width | |
# Randomly highlight bytes | |
self.highlights = [] | |
if random.random() < 0.7: # 70% chance of highlighting something | |
highlight_count = random.randint(1, 4) | |
for _ in range(highlight_count): | |
pos = random.randint(0, len(self.data) - 1) | |
length = random.randint(1, min(8, len(self.data) - pos)) | |
self.highlights.append((pos, length, random.choice(["red", "green", "yellow", "blue"]))) | |
self.last_update = time.time() | |
def render(self): | |
"""Render the hex display as a rich Text object.""" | |
result = Text() | |
# Add header | |
result.append("OFFSET | ", style="bright_white") | |
for i in range(self.width): | |
result.append(f"{i:02X} ", style="bright_white") | |
result.append("| ASCII\n", style="bright_white") | |
result.append("─" * 62 + "\n", style="bright_black") | |
# Add data rows | |
for row in range(self.rows): | |
row_offset = self.offset + (row * self.width) | |
result.append(f"{row_offset:08X} | ", style="bright_blue") | |
# Add hex values | |
ascii_repr = "" | |
for col in range(self.width): | |
pos = row * self.width + col | |
if pos < len(self.data): | |
byte = self.data[pos] | |
# Check if this byte is highlighted | |
styled = False | |
for h_pos, h_len, h_style in self.highlights: | |
if h_pos <= pos < h_pos + h_len: | |
result.append(f"{byte:02X} ", style=h_style) | |
styled = True | |
break | |
if not styled: | |
result.append(f"{byte:02X} ", style="green") | |
# Build ASCII representation | |
if 32 <= byte <= 126: # Printable ASCII | |
ascii_repr += chr(byte) | |
else: | |
ascii_repr += "." | |
# Add ASCII representation | |
result.append("| ", style="bright_white") | |
result.append(ascii_repr, style="cyan") | |
result.append("\n") | |
return result | |
class GhostTerminal: | |
"""Simulates a terminal with Ghost Army deception operations.""" | |
def __init__(self): | |
self.lines = [] | |
self.max_lines = 10 | |
self.add_line("[bold green]23rd HEADQUARTERS SPECIAL TROOPS - CYBER DIVISION[/bold green]") | |
self.add_line("[yellow]CLASSIFIED // PHANTOM CLEARANCE REQUIRED[/yellow]") | |
self.add_line("Initializing deception environment...") | |
self.last_update = time.time() | |
self.update_interval = random.uniform(0.5, 2.0) | |
# Current "operation" being shown | |
self.current_operation = None | |
self.operation_stage = 0 | |
self.operation_started = False | |
def add_line(self, text): | |
"""Add a line to the terminal output.""" | |
timestamp = time.strftime("[%H:%M:%S]", time.localtime()) | |
self.lines.append(f"{timestamp} {text}") | |
if len(self.lines) > self.max_lines: | |
self.lines = self.lines[-self.max_lines:] | |
def start_new_operation(self): | |
"""Start a new Ghost Army operation sequence.""" | |
self.current_operation = random.choice(OPERATIONS) | |
self.operation_stage = 0 | |
self.operation_started = True | |
self.add_line(f"[bold green]INITIATING OPERATION: {self.current_operation}[/bold green]") | |
self.add_line(f"[cyan]Deploying deception assets...[/cyan]") | |
def update(self): | |
"""Update the terminal with new output.""" | |
if time.time() - self.last_update > self.update_interval: | |
if not self.operation_started or self.operation_stage >= len(DECEPTION_STAGES): | |
self.start_new_operation() | |
else: | |
stage = DECEPTION_STAGES[self.operation_stage] | |
tool = random.choice(GHOST_CODENAMES) | |
if random.random() < 0.7: # 70% chance of stage advancement | |
self.add_line(f"[bold yellow]STAGE {self.operation_stage + 1}: {stage}[/bold yellow]") | |
self.add_line(f"Deploying {tool} deception module...") | |
self.operation_stage += 1 | |
else: | |
# Show more detailed operations based on WWII Ghost Army tactics but in cyber context | |
actions = [ | |
f"Generating false network traffic patterns...", | |
f"Deploying decoy service on port {random.randint(1, 65535)}", | |
f"Implementing {tool} deception framework", | |
f"Creating phantom {random.choice(C2_PROTOCOLS)} communications", | |
f"Projecting false {random.choice(['firewall', 'IDS', 'EDR', 'HIPS'])} alerts", | |
f"Simulating activity in memory region 0x{random.randint(0x10000000, 0xFFFFFFFF):X}", | |
f"Deploying false credential repositories", | |
f"Establishing phantom presence on {random.choice(TARGET_PLATFORMS)}", | |
f"Creating {random.choice(DECEPTION_TYPES)} deception layer", | |
f"Implementing {random.choice(ENCRYPTION_ALGORITHMS)} for covert channel", | |
f"Projecting false data exfiltration: {random.randint(1, 1000)}MB", | |
f"Establishing false attribution trails" | |
] | |
self.add_line(f"[cyan]{random.choice(actions)}[/cyan]") | |
# Occasionally show "success" messages | |
if random.random() < 0.3: | |
success_messages = [ | |
f"[bold green]SUCCESS: {tool} deception successfully deployed[/bold green]", | |
f"[bold green]CONFIRMED: {random.choice(DECEPTION_TYPES)} illusion operational[/bold green]", | |
f"[bold green]MISDIRECTION: Adversary engaging with {random.choice(GHOST_CODENAMES)} decoys[/bold green]" | |
] | |
self.add_line(random.choice(success_messages)) | |
self.last_update = time.time() | |
self.update_interval = random.uniform(0.5, 2.0) | |
def render(self): | |
"""Render the terminal output.""" | |
text = Text("\n".join(self.lines)) | |
return text | |
class DeceptionSelector: | |
"""Shows a selection of available Ghost Army deception tactics.""" | |
def __init__(self): | |
self.selected_index = random.randint(0, len(GHOST_CODENAMES) - 1) | |
self.last_update = time.time() | |
self.update_interval = random.uniform(1.0, 3.0) | |
def update(self): | |
"""Update the deception selector.""" | |
if time.time() - self.last_update > self.update_interval: | |
self.selected_index = random.randint(0, len(GHOST_CODENAMES) - 1) | |
self.last_update = time.time() | |
self.update_interval = random.uniform(1.0, 3.0) | |
def render(self): | |
"""Render the deception selector as a Table.""" | |
table = Table(title="AVAILABLE DECEPTION TACTICS", box=box.SIMPLE) | |
table.add_column("CODENAME", style="yellow") | |
table.add_column("TARGET", style="cyan") | |
table.add_column("TYPE", style="green") | |
table.add_column("STATUS", style="bright_white") | |
# Add a selection of deception tools - inspired by original Ghost Army tactics | |
displayed_count = min(8, len(GHOST_CODENAMES)) | |
displayed_indices = sorted(random.sample(range(len(GHOST_CODENAMES)), displayed_count)) | |
for i, idx in enumerate(displayed_indices): | |
codename = GHOST_CODENAMES[idx] | |
target = random.choice(TARGET_PLATFORMS) | |
deception_type = random.choice(DECEPTION_TYPES) | |
if idx == self.selected_index: | |
status = "[bold green]DEPLOYED[/bold green]" | |
style = "on dark_green" | |
else: | |
status = "READY" if random.random() < 0.8 else "[red]PREPARING[/red]" | |
style = "" | |
table.add_row(codename, target, deception_type, status, style=style) | |
return table | |
class DeceptionCanvas: | |
"""Shows information about the current deception operation.""" | |
def __init__(self): | |
self.current_operation = self._generate_random_operation() | |
self.last_update = time.time() | |
self.update_interval = random.uniform(5.0, 10.0) | |
def _generate_random_operation(self): | |
"""Generate a random deception operation profile.""" | |
adversaries = ["APT GROUPS", "NATION-STATE ACTORS", "CRIMINAL SYNDICATES", "HACKTIVIST COLLECTIVES", "UNKNOWN THREAT ACTORS"] | |
objectives = ["MISDIRECTION", "FALSE INTELLIGENCE", "RESOURCE EXHAUSTION", "ATTRIBUTION CONFUSION", "HONEYPOT DEPLOYMENT"] | |
# Ghost Army historically used various deception elements | |
return { | |
"operation_name": f"OPERATION {random.choice(['PHANTOM', 'MIRAGE', 'DECOY', 'INFLATABLE', 'RUBBER', 'SONIC', 'GHOST'])}-{random.randint(100, 999)}", | |
"network": f"{random.randint(10, 192)}.{random.randint(0, 255)}.{random.randint(0, 255)}.0/24", | |
"adversary_target": random.choice(adversaries), | |
"objective": random.choice(objectives), | |
"confidence": random.randint(60, 99), | |
"deception_elements": [random.choice(DECEPTION_TYPES) for _ in range(random.randint(1, 3))], | |
"assets_deployed": sorted(random.sample(range(5, 50), random.randint(3, 6))), | |
"deployment_date": f"{random.randint(1, 28):02d}/{random.randint(1, 12):02d}/{random.randint(2020, 2022)}", | |
"believability": random.choice(["BASIC", "CONVINCING", "ELABORATE", "INDISTINGUISHABLE"]) | |
} | |
def update(self): | |
"""Update the deception operation information.""" | |
if time.time() - self.last_update > self.update_interval: | |
self.current_operation = self._generate_random_operation() | |
self.last_update = time.time() | |
self.update_interval = random.uniform(5.0, 10.0) | |
def render(self): | |
"""Render the deception operation information.""" | |
operation = self.current_operation | |
believability_style = { | |
"BASIC": "green", | |
"CONVINCING": "yellow", | |
"ELABORATE": "cyan", | |
"INDISTINGUISHABLE": "bold green on black" | |
} | |
text = Text() | |
text.append(f"OPERATION: ", style="bright_white") | |
text.append(f"{operation['operation_name']}\n", style="yellow") | |
text.append(f"DECEPTION ZONE: ", style="bright_white") | |
text.append(f"{operation['network']}\n", style="cyan") | |
text.append(f"TARGET AUDIENCE: ", style="bright_white") | |
text.append(f"{operation['adversary_target']}\n", style="cyan") | |
text.append(f"PRIMARY OBJECTIVE: ", style="bright_white") | |
text.append(f"{operation['objective']}\n", style="green") | |
text.append(f"BELIEVABILITY: ", style="bright_white") | |
text.append(f"{operation['believability']}\n", style=believability_style[operation['believability']]) | |
text.append(f"ASSETS DEPLOYED: ", style="bright_white") | |
text.append(f"{', '.join(str(p) for p in operation['assets_deployed'])}\n", style="yellow") | |
text.append(f"DECEPTION TYPES: ", style="bright_white") | |
text.append(f"{', '.join(operation['deception_elements'])}\n", style="yellow") | |
text.append(f"DEPLOYMENT DATE: ", style="bright_white") | |
text.append(f"{operation['deployment_date']}", style="cyan") | |
return Panel(text, title="DECEPTION CANVAS", border_style="green") | |
class ProgressMonitor: | |
"""Shows progress bars for various deception operations.""" | |
def __init__(self): | |
self.progress = Progress( | |
SpinnerColumn(), | |
TextColumn("[bold green]{task.description}"), | |
BarColumn(complete_style="green"), | |
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), | |
expand=True | |
) | |
# Add tasks with Ghost Army themed names | |
self.tasks = { | |
"main": self.progress.add_task("[red]DECEPTION OPERATION", total=100), | |
"decoy": self.progress.add_task("[yellow]DECOY DEPLOYMENT", total=100), | |
"projection": self.progress.add_task("[blue]MIRAGE PROJECTION", total=100), | |
"sonic": self.progress.add_task("[magenta]SONIC DECEPTION", total=100) | |
} | |
self.last_update = time.time() | |
def update(self): | |
"""Update progress bars.""" | |
if time.time() - self.last_update > 0.1: | |
for task_id in self.tasks: | |
current = self.progress.tasks[self.tasks[task_id]].completed | |
# Determine new progress | |
if current >= 100: | |
# Reset task | |
self.progress.update(self.tasks[task_id], completed=0) | |
else: | |
# Increment by a random amount | |
increment = random.uniform(0.1, 1.0) | |
new_value = min(100, current + increment) | |
self.progress.update(self.tasks[task_id], completed=new_value) | |
self.last_update = time.time() | |
def render(self): | |
"""Render the progress monitor.""" | |
return self.progress | |
class StatusDisplay: | |
"""Shows status of various deception systems and operations.""" | |
def __init__(self): | |
self.statuses = { | |
"PHANTOM PRESENCE": "ACTIVE", | |
"SONIC DECEPTION": "ONLINE", | |
"RUBBER TANKS": "DEPLOYED", | |
"SHADOW NETWORK": "STANDBY", | |
"GHOST DIVISION": "ACTIVE", | |
"DECEPTION DATABASE": "CONNECTED", | |
"MIRAGE PROJECTION": "ACTIVE", | |
} | |
self.last_update = time.time() | |
self.update_interval = random.uniform(1.0, 3.0) | |
def update(self): | |
"""Update system statuses.""" | |
if time.time() - self.last_update > self.update_interval: | |
# Randomly change some statuses | |
keys = list(self.statuses.keys()) | |
for _ in range(random.randint(1, 3)): | |
key = random.choice(keys) | |
self.statuses[key] = random.choice(["ACTIVE", "ONLINE", "DEPLOYED", "STANDBY", "OFFLINE", "UPDATING"]) | |
self.last_update = time.time() | |
self.update_interval = random.uniform(1.0, 3.0) | |
def render(self): | |
"""Render the status display.""" | |
table = Table(title="DECEPTION STATUS", box=box.SIMPLE, expand=True) | |
table.add_column("SYSTEM", style="cyan") | |
table.add_column("STATUS", justify="right") | |
for system, status in self.statuses.items(): | |
if status in ["ACTIVE", "ONLINE", "DEPLOYED"]: | |
status_style = "green" | |
elif status == "STANDBY": | |
status_style = "yellow" | |
else: | |
status_style = "red" | |
table.add_row(system, f"[{status_style}]{status}[/{status_style}]") | |
return table | |
class WatermarkText: | |
"""Creates a watermark text effect for background visuals.""" | |
def __init__(self): | |
self.texts = [ | |
"TOP SECRET", "GHOST ARMY", "DECEPTION", "CLASSIFIED", "23RD HQ", "1944-2025", | |
"PHANTOM", "DECOY", "INFLATABLE", "RUBBER", "SONIC", "MIRAGE", | |
"CHAMELEON", "HOLOGRAM", "SPECTER", "SHADOW", "DOPPELGANGER", "PROJECTION" | |
] | |
self.positions = [] | |
self._initialize_positions() | |
self.last_update = time.time() | |
self.update_interval = 1.0 | |
def _initialize_positions(self): | |
"""Initialize random positions for watermark texts.""" | |
console_width = console.width if hasattr(console, 'width') else 80 | |
console_height = console.height if hasattr(console, 'height') else 24 | |
for _ in range(10): # Create 10 watermark texts | |
self.positions.append({ | |
"text": random.choice(self.texts), | |
"x": random.randint(0, console_width - 20), | |
"y": random.randint(0, console_height - 5), | |
"style": random.choice(["dim red", "dim green", "dim white"]) | |
}) | |
def update(self): | |
"""Update watermark text positions.""" | |
if time.time() - self.last_update > self.update_interval: | |
# Move some of the texts | |
for pos in self.positions: | |
if random.random() < 0.3: | |
# Move in a random direction | |
pos["x"] += random.randint(-3, 3) | |
pos["y"] += random.randint(-2, 2) | |
# Keep within reasonable bounds | |
pos["x"] = max(0, min(pos["x"], console.width - 20)) | |
pos["y"] = max(0, min(pos["y"], console.height - 5)) | |
# Occasionally change the text | |
if random.random() < 0.2: | |
pos["text"] = random.choice(self.texts) | |
self.last_update = time.time() | |
def render(self): | |
"""Creates a representation of the watermark texts. | |
Note: Cannot be directly rendered in Rich - used for reference.""" | |
return self.positions | |
class GhostArmyUI: | |
"""Main UI class for the Ghost Army visualization.""" | |
def __init__(self): | |
# Create components | |
self.hex_display = HexDisplay() | |
self.terminal = GhostTerminal() | |
self.deception_selector = DeceptionSelector() | |
self.deception_canvas = DeceptionCanvas() | |
self.progress_monitor = ProgressMonitor() | |
self.status_display = StatusDisplay() | |
self.watermark = WatermarkText() | |
# Create layout | |
self.layout = Layout() | |
self.layout.split_column( | |
Layout(name="header", size=55), # Increased size for the custom Ghost Army logo | |
Layout(name="main", ratio=1), | |
Layout(name="footer", size=3) | |
) | |
# Split the main area | |
self.layout["main"].split_row( | |
Layout(name="left_panel", ratio=1), | |
Layout(name="right_panel", ratio=1) | |
) | |
# Split the left panel | |
self.layout["left_panel"].split_column( | |
Layout(name="hex_dump", ratio=3), | |
Layout(name="terminal", ratio=2) | |
) | |
# Split the right panel | |
self.layout["right_panel"].split_column( | |
Layout(name="deception_canvas", ratio=1), | |
Layout(name="deception_selector", ratio=1), | |
Layout(name="status_display", ratio=1) | |
) | |
# Set up the footer with progress monitor | |
self.layout["footer"].update(self.progress_monitor.render()) | |
# Set up header | |
self.update_header() | |
# Operation timestamps for authenticity | |
self.start_time = time.time() | |
self.last_heartbeat = self.start_time | |
# Visual elements | |
self.blink_state = False | |
self.blink_time = time.time() | |
self.frame_count = 0 | |
def update_header(self): | |
"""Update the header display with Ghost Army logo and branding.""" | |
header_text = Text() | |
# Ghost Army Logo (custom ASCII art) | |
ghost_logo = [ | |
" ", | |
" ", | |
" ", | |
" ", | |
" ░░░░░ ░░░░░ ", | |
" ░░░ ░░░░░░ ░░░░░░ ░░░ ", | |
" ░░ ░▓▒░ ░░░░░░░░░░░░░░░░░░░░░░░ ░░▒▒ ░░ ", | |
" ░░ ░████████▓▒░ ░▒▓███████▓░ ░░ ", | |
" ░░ ▒███████████████████▓▒▒▒▒▒▒▒▒▓███████████████████▒ ░░ ", | |
" ░░ ▒██████████████████████████████████████████████████▒ ░░░ ", | |
" ░░░ ▓████████████████████████████████████████████████████▒ ░░ ", | |
" ░░░ ░▓███████████████████████████████████▒░ ░▒████████████▓ ░░░ ", | |
" ░░░ ░▓█████████████████████████████████▓ ▓███████████▓ ░░ ", | |
" ░░ ░▓███████████████████████████████▓░ ░░░ ░████████████▓ ░░ ", | |
" ░░ ▒████████████████████████████▓▒░ ▒██▒░ ░░ ▒█████████████▒ ░░ ", | |
" ░░ ▒█████████████████▓▓▓▓▒▒░░ ░▒▒░ ░██▓░▒███████████████▒ ░░ ", | |
" ░░ ░▓███████████▓▒░ ░▒▒░▓████████████████▓░ ░░ ", | |
" ░░ ▒█████████▓░ ░░▒▒░ ░▓█████████▓▒▒▓██████▒ ░ ", | |
" ░ ░█████████░ ░▒█████▒ ░▒██████▓▒░ ██████░ ░░ ", | |
" ░░ ▒████████░ ░▓█████▓░ ▒█████▒▒░ ▓█████▒ ░░ ", | |
" ░░ ████████▓ ▒██████▒ ░░▒░ ▒▓█▒ ██████▓ ░░ ", | |
" ░ ░████████▒ ▓█████▓░ ░▒██▓░ ░███████░ ░░ ", | |
" ░░ ▒████████░ ░██████▓░ ░▓██▒░▒▓▒ ▒███████░ ░░ ", | |
" ░░ ▒████████ ░▓██████▒ ▓███████████░ ████████▒ ░ ", | |
" ░░ ▒████████ ▓██████▓ ░▓███████▓▓███▓█▓ ░████████▒ ░ ", | |
" ░░ ▒████████ ░▓██████▒ ░█████████▓▒█▓██▒█▓░░░▒███████░ ░ ", | |
" ░░ ░████████ ░▓█████▓░ ▒█████████░▒██▒██░█▓███████████░ ░░ ", | |
" ░░ ████████ ▓█████▓ ▒█████████ ▒▓█▓▒██░ ▒███████████░ ░░ ", | |
" ░░ ▓███████ ░█████▓ ████████▒ ▒█▒▒████░▓█████████▒ ░ ", | |
" ░ ▒███████░ ▒████▓░ ▓█████████▒░▒██▒▓████░░▓████████░ ░ ", | |
" ░░ ░▓██████▓ ░▓██████▒ ▓████████░ ▓██▓░ ███▒░░███████▒░ ░░ ", | |
" ░░ ▒███████░░▓██████▓░ ▓███████░ ▒▓█▓▓▒ █████▒▓██████░ ░ ", | |
" ░░ ░▒██████▓░▒███████▒ ▓████████░░█████▒░████████████▒ ░░ ", | |
" ░░ ░▓██████▓▒▓███████▒ ░███████▒███████▒░ ██████████▓░ ░░ ", | |
" ░ ░▓████████████████▒ ██████▓█████████▒▒█████████▓░ ░░ ", | |
" ░░ ░█████████████████▓ ░███████████████▒█████████▓░ ░░░ ", | |
" ░░ ░▓█████████████████░ ████████████████████████▓ ░░ ", | |
" ░░░ ▒█████████████████▒░ ▒██████████████████████▒ ░░ ", | |
" ░░░ ░█████████████████▓░ ████████████████████▓░ ░░ ", | |
" ░░ ░▒█████████████████▒ ▒▒ ███████████████████▒░ ░░ ", | |
" ░░░ ░▓████████████████▓ ░▒▒▒ █▒████████████████▒░ ░░ ", | |
" ░░░ ░▓████████████████░▒▒▒░▒████████████████▒░ ░░░ ", | |
" ░░░ ░▒███████████████▓██▒███████████████▒░ ░░░ ", | |
" ░░░ ░▒██████████████████████████████▒░ ░░░ ", | |
" ░░░ ░▓████████████████████████▓░ ░░░ ", | |
" ░░░ ▒███████████████████▓░ ░░░ ", | |
" ░░░ ▒██████████████▒ ░░ ", | |
" ░░░ ░███████▓░ ░░░ ", | |
" ░░░ ░▒▒░ ░░░ ", | |
" ░░░░ ░░░░ ", | |
" ░░░ ", | |
" ", | |
" ", | |
" ", | |
" " | |
] | |
# Add the Ghost Army logo with ghostly color scheme | |
for line in ghost_logo: | |
# Create a pulsing/fading effect by alternating between colors based on frame count | |
frame_mod = getattr(self, 'frame_count', 0) % 60 | |
if frame_mod < 20: | |
header_text.append(f"{line}\n", style="bright_white") | |
elif frame_mod < 40: | |
header_text.append(f"{line}\n", style="white") | |
else: | |
header_text.append(f"{line}\n", style="dim white") | |
# Add GHOST ARMY banner | |
header_text.append(" ██████╗ ██╗ ██╗ ██████╗ ███████╗████████╗ █████╗ ██████╗ ███╗ ███╗██╗ ██╗\n", style="bold red") | |
header_text.append(" ██╔════╝ ██║ ██║██╔═══██╗██╔════╝╚══██╔══╝ ██╔══██╗██╔══██╗████╗ ████║╚██╗ ██╔╝\n", style="bold red") | |
header_text.append(" ██║ ███╗███████║██║ ██║███████╗ ██║ ███████║██████╔╝██╔████╔██║ ╚████╔╝ \n", style="bold red") | |
header_text.append(" ██║ ██║██╔══██║██║ ██║╚════██║ ██║ ██╔══██║██╔══██╗██║╚██╔╝██║ ╚██╔╝ \n", style="bold red") | |
header_text.append(" ╚██████╔╝██║ ██║╚██████╔╝███████║ ██║ ██║ ██║██║ ██║██║ ╚═╝ ██║ ██║ \n", style="bold red") | |
header_text.append(" ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ \n", style="bold red") | |
# Add Ghost Army signature line with historical reference | |
header_text.append("23rd HEADQUARTERS SPECIAL TROOPS // ", style="bright_white") | |
header_text.append("CYBER DIVISION", style="bold green") | |
header_text.append(" // ", style="bright_white") | |
header_text.append("EST. 1944", style="bold yellow") | |
header_text.append(" // ", style="bright_white") | |
header_text.append("TOP SECRET", style="bold yellow") | |
self.layout["header"].update(Panel(header_text, box=box.HEAVY, border_style="green")) | |
def update(self): | |
"""Update all UI components.""" | |
# Update components | |
self.hex_display.update() | |
self.terminal.update() | |
self.deception_selector.update() | |
self.deception_canvas.update() | |
self.progress_monitor.update() | |
self.status_display.update() | |
self.watermark.update() | |
# Blinking effect for certain elements | |
current_time = time.time() | |
if current_time - self.blink_time > 0.5: | |
self.blink_state = not self.blink_state | |
self.blink_time = current_time | |
# Update panel titles and borders based on state | |
border_style_hex = "green" if self.blink_state else "bright_green" | |
border_style_term = "yellow" if self.blink_state else "bright_yellow" | |
# Create warning text for panels | |
memory_title = Text() | |
memory_title.append("DECEPTION MONITOR", style="bold") | |
if self.frame_count % 40 < 20: # Alternate warning display | |
memory_title.append(" [!]", style="bold red") | |
terminal_title = Text() | |
terminal_title.append("GHOST ARMY TERMINAL", style="bold") | |
if random.random() < 0.2: # Occasional secure indicator | |
terminal_title.append(" [SECURE]", style="bold green") | |
# Generate a random access ID that changes periodically - with historical reference | |
# The real Ghost Army had numerical designations for their deception units | |
unit_id = f"23-{random.randint(100, 999)}-{chr(random.randint(65, 90))}" | |
# Update panels with enhanced styling | |
self.layout["hex_dump"].update(Panel( | |
self.hex_display.render(), | |
title=memory_title, | |
border_style=border_style_hex, | |
subtitle=f"UNIT: {unit_id}" if self.blink_state else None | |
)) | |
self.layout["terminal"].update(Panel( | |
self.terminal.render(), | |
title=terminal_title, | |
border_style=border_style_term, | |
subtitle=datetime.now().strftime("%H:%M:%S") | |
)) | |
self.layout["deception_canvas"].update(self.deception_canvas.render()) | |
self.layout["deception_selector"].update(Panel( | |
self.deception_selector.render(), | |
border_style="green", | |
subtitle=f"CLEARANCE: PHANTOM LEVEL" if self.frame_count % 30 < 15 else None | |
)) | |
self.layout["status_display"].update(Panel( | |
self.status_display.render(), | |
border_style="magenta" if self.blink_state else "bright_magenta", | |
subtitle="EST. 1944 - PRESENT DAY" | |
)) | |
# Increment frame counter | |
self.frame_count += 1 | |
# Heartbeat logging (adds authenticity) | |
if current_time - self.last_heartbeat > 5.0: | |
self.last_heartbeat = current_time | |
def render(self): | |
"""Render the complete UI.""" | |
return self.layout | |
def biometric_scan_simulation(): | |
"""Simulate a biometric authentication process with Ghost Army theme.""" | |
console.print("[bold]BIOMETRIC AUTHENTICATION REQUIRED[/bold]") | |
console.print("Initializing retinal scanner for Ghost Army access...", style="bright_green") | |
# Progress bar for scan | |
with Progress( | |
SpinnerColumn(), | |
TextColumn("[bold green]Scanning..."), | |
BarColumn(complete_style="green"), | |
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), | |
console=console | |
) as progress: | |
scan_task = progress.add_task("Scanning", total=100) | |
for i in range(101): | |
progress.update(scan_task, completed=i) | |
time.sleep(0.03) | |
console.print("Retinal pattern recognized.", style="green") | |
console.print("Welcome, [bold]DECEPTION SPECIALIST[/bold]") | |
time.sleep(1) | |
def run_animation(duration=60): | |
"""Run the Ghost Army visualization for the specified duration.""" | |
# Clear screen and show initialization sequence | |
console.clear() | |
# Show elaborate boot sequence with Ghost Army theme | |
console.print("[bold red]■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■[/bold red]") | |
console.print("[bold green] 23rd HEADQUARTERS SPECIAL TROOPS - CYBER DIVISION [/bold green]") | |
console.print("[bold red]■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■[/bold red]") | |
time.sleep(1) | |
# Show historical callout to original WWII unit | |
console.print("\n[bold yellow]HISTORICAL REFERENCE:[/bold yellow]") | |
console.print("The original Ghost Army (1944-1945) used inflatable tanks,") | |
console.print("sound effects, radio deception, and impersonation to fool") | |
console.print("the enemy about the location and size of Allied forces.") | |
console.print("[italic]Today, we continue this legacy in the cyber domain.[/italic]") | |
time.sleep(3) | |
# Simulate biometric authentication | |
biometric_scan_simulation() | |
# Show encryption animation with Ghost Army theme | |
console.print("[bold green]ESTABLISHING DECEPTIVE ENVIRONMENT[/bold green]") | |
encrypted = "9a3f7c2e8b1d0e5f4c6a2d9e7b3f1c8a4d0e6b2f9c7a3d8e0b5f2c1a7e9d4b6c2f8a0e3d7c5b1f9a" | |
decrypted = "INITIALIZING 23rd HEADQUARTERS CYBER DECEPTION FRAMEWORK - PHANTOM ENVIRONMENT" | |
# Display the decryption process character by character | |
for i in range(len(decrypted) + 1): | |
# Show partially decrypted text | |
partial = decrypted[:i] + encrypted[i:len(decrypted)] | |
console.print(f"\r{partial}", end="") | |
time.sleep(0.05) | |
console.print("\n[bold green]DECEPTION ENVIRONMENT ESTABLISHED[/bold green]") | |
time.sleep(0.5) | |
# Display security notice with Ghost Army theme | |
console.print("\n[bold red on white]SECURITY NOTICE[/bold red on white]") | |
console.print("This system employs advanced psychological operations and deception tactics.") | |
console.print("All activities are logged and regularly audited for operational security.") | |
console.print("\n[bold red]CLASSIFIED MATERIAL[/bold red]") | |
console.print("Handling of information requires PHANTOM CLEARANCE level access") | |
console.print("\"The greater the deception, the more believable it must appear\" - Ghost Army Manual, 1944") | |
time.sleep(2) | |
# Initialize the UI with the Ghost Army UI | |
ui = GhostArmyUI() | |
# Show module loading with Ghost Army codenames | |
modules = [ | |
"PHANTOM_PRESENCE", "SONIC_DECEPTION", "RADIO_SPOOF", "DECOY_DEPLOYMENT", | |
"NETWORK_MIRAGE", "TRAFFIC_ILLUSION", "DOPPELGANGER", "CHAMELEON", | |
"RUBBER_INFRA", "GHOST_DIVISION", "SHADOW_PROJECTION", "PROJECTION_23" | |
] | |
console.print("[bold green]Loading Ghost Army tactical modules...[/bold green]") | |
with Progress( | |
SpinnerColumn(), | |
TextColumn("[bright_green]{task.description}"), | |
BarColumn(complete_style="green"), | |
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), | |
console=console | |
) as progress: | |
load_task = progress.add_task("Loading", total=len(modules)) | |
for i, module in enumerate(modules): | |
progress.update(load_task, advance=1, description=f"Loading {module}") | |
time.sleep(0.2) | |
console.print("[bold green]All deception modules loaded successfully[/bold green]") | |
time.sleep(0.5) | |
# Start the live display with enhanced visual effects | |
start_time = time.time() | |
console.print("[bold yellow]Initiating Ghost Army operational interface...[/bold yellow]") | |
time.sleep(0.5) | |
with Live(ui.render(), refresh_per_second=20, screen=True) as live: | |
try: | |
while time.time() - start_time < duration: | |
ui.update() | |
time.sleep(0.05) # 20fps for smooth animation | |
except KeyboardInterrupt: | |
console.print("[bold red]Emergency shutdown initiated. Collapsing deceptive environment...[/bold red]") | |
# Cleanup messages with Ghost Army themed shutdown sequence | |
console.print("[bold red]TACTICAL WITHDRAWAL SEQUENCE INITIATED[/bold red]") | |
shutdown_steps = [ | |
"Recalling phantom assets...", | |
"Disabling sonic deception modules...", | |
"Dissolving network mirages...", | |
"Deflating decoy infrastructure...", | |
"Withdrawing false identities...", | |
"Erasing deception footprints...", | |
"Sanitizing operational environment...", | |
"Dispersing Ghost Division resources..." | |
] | |
with Progress( | |
SpinnerColumn(), | |
TextColumn("[bright_red]{task.description}"), | |
BarColumn(complete_style="red"), | |
console=console | |
) as progress: | |
shutdown_task = progress.add_task("Withdrawing", total=len(shutdown_steps)) | |
for step in shutdown_steps: | |
progress.update(shutdown_task, advance=1, description=step) | |
time.sleep(0.3) | |
console.print("[bold green]Tactical withdrawal complete.[/bold green]") | |
console.print("[yellow]Remember: The best deceptions are the ones never discovered.[/yellow]") | |
if __name__ == "__main__": | |
try: | |
run_animation() | |
except KeyboardInterrupt: | |
console.print("[bold red]Process terminated by user.[/bold red]") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment