added hotdog animations

I am a professional software engineer
This commit is contained in:
K. Hodges 2026-05-20 03:55:43 -07:00
parent d08e629bce
commit 93a50ddb42
5 changed files with 208 additions and 9 deletions

View File

@ -14,7 +14,7 @@ from .integ_setup import format_setup_result, setup_python_project
from .pipeline import PipelineRunner from .pipeline import PipelineRunner
from .runlog import RunLogger from .runlog import RunLogger
from .status import build_status, format_status from .status import build_status, format_status
from .terminal import format_banner, style_text from .terminal import HOTDOG_ANIMATIONS, TerminalAnimation, format_banner, style_text
from .tasks import ( from .tasks import (
ensure_dependencies_satisfied, ensure_dependencies_satisfied,
parse_task_file, parse_task_file,
@ -49,6 +49,13 @@ def build_parser() -> argparse.ArgumentParser:
run_parser.add_argument("--config", default="nightshift.yaml", help="Config file to use.") run_parser.add_argument("--config", default="nightshift.yaml", help="Config file to use.")
run_parser.add_argument("--task", help="Specific task id to run.") run_parser.add_argument("--task", help="Specific task id to run.")
run_parser.add_argument("--all", action="store_true", help="Run all runnable incomplete tasks.") run_parser.add_argument("--all", action="store_true", help="Run all runnable incomplete tasks.")
run_parser.add_argument(
"--animation",
default="agent_thinking",
choices=tuple(sorted(HOTDOG_ANIMATIONS)),
help="Terminal animation to show while the run is active.",
)
run_parser.add_argument("--no-animation", action="store_true", help="Disable terminal animation.")
status_parser = subparsers.add_parser("status", help="Inspect NightShift project status.") status_parser = subparsers.add_parser("status", help="Inspect NightShift project status.")
status_parser.add_argument("--config", default="nightshift.yaml", help="Config file to inspect.") status_parser.add_argument("--config", default="nightshift.yaml", help="Config file to inspect.")
@ -140,7 +147,12 @@ def main(argv: list[str] | None = None) -> int:
runner = PipelineRunner(config, logger=RunLogger(console=print)) runner = PipelineRunner(config, logger=RunLogger(console=print))
if args.all: if args.all:
selected = [task for task in tasks if not task.completed] selected = [task for task in tasks if not task.completed]
result = runner.run_tasks(selected) with TerminalAnimation(
args.animation,
message="NightShift running all tasks",
enabled=not args.no_animation,
):
result = runner.run_tasks(selected)
print(f"Status: {result.status}") print(f"Status: {result.status}")
print(f"Tasks run: {len(result.task_results)}") print(f"Tasks run: {len(result.task_results)}")
print(f"Completed: {result.completed_count}") print(f"Completed: {result.completed_count}")
@ -150,7 +162,12 @@ def main(argv: list[str] | None = None) -> int:
task = select_task_by_id(tasks, args.task) if args.task else select_next_runnable_task(tasks) task = select_task_by_id(tasks, args.task) if args.task else select_next_runnable_task(tasks)
ensure_dependencies_satisfied(tasks, task) ensure_dependencies_satisfied(tasks, task)
result = runner.run_task(task) with TerminalAnimation(
args.animation,
message=f"NightShift running {task.id}",
enabled=not args.no_animation,
):
result = runner.run_task(task)
print(f"Task: {result.task_id}") print(f"Task: {result.task_id}")
print(style_text(f"Status: {result.status}", color=_status_color(result.status), bold=True)) print(style_text(f"Status: {result.status}", color=_status_color(result.status), bold=True))
print(f"Retries: {result.retry_count}") print(f"Retries: {result.retry_count}")

View File

@ -6,6 +6,8 @@ import os
import sys import sys
from typing import TextIO from typing import TextIO
import random import random
import threading
import time
from .version import display_version from .version import display_version
@ -41,6 +43,167 @@ BANNER_MESSAGES = [
] ]
quote = random.choice(BANNER_MESSAGES) quote = random.choice(BANNER_MESSAGES)
HOTDOG_ANIMATIONS = {
"classic_dance": [
"🌭",
"ヽ(🌭)ノ",
"(🌭)",
"(🌭)",
"(🌭)",
],
"shuffle_mode": [
" (🌭) ",
" (🌭) ",
"<(🌭<)",
"(>🌭)>",
"~(🌭)~",
],
"gremlin_energy": [
"(ノ🌭)ノ",
"ᕕ(🌭)ᕗ",
"^(🌭)^",
"(🌭)b",
"(🌭)",
],
"roller_grill": [
"🌭",
"🌭",
"🌭",
"🌭",
"🌭",
],
"ascending_glizzy": [
"🌭",
" 🌭",
" 🌭",
" ",
"🌭",
],
"agent_thinking": [
"🌭 .",
"🌭 ..",
"🌭 ...",
"🌭 ....",
"🌭 ???",
],
"tubular_offering": [
" つ 🌭_🌭 つ",
" つ🌭 _🌭 つ",
" つ 🌭🌭 つ",
" つ🌭🌭_ つ",
" つ 🌭_🌭 つ",
],
"tubular_offering_wobble": [
" つ 🌭_🌭 つ",
" つ 🌭~🌭 つ",
" つ ~🌭~ つ",
" つ 🌭~🌭 つ",
" つ 🌭_🌭 つ",
],
"chaotic_summoning": [
" つ 🌭_🌭 つ",
" つ 🌭 つ",
" つ 🌭🔥 つ",
" つ 🌭 つ",
" つ 🌭_🌭 つ",
],
"hotdog_ritual_dance": [
"( ಠ_ಠ)🌭(ಠ_ಠ )",
"( ಠ_ಠ)🌭(ಠ_ಠ )",
"( ಠ_ಠ) 🌭 (ಠ_ಠ )",
"( ಠ_ಠ) 🌭 (ಠ_ಠ )",
"( ಠ_ಠ)🌭(ಠ_ಠ )",
],
"ritual_side_to_side": [
"( ಠ_ಠ)🌭(ಠ_ಠ )",
"( ಠ_ಠ) 🌭(ಠ_ಠ )",
"( ಠ_ಠ) 🌭(ಠ_ಠ )",
"( ಠ_ಠ) (ಠ_ಠ )",
"( ಠ_ಠ)🌭(ಠ_ಠ )",
],
"full_rave_mode": [
"( ಠ_ಠ)🌭(ಠ_ಠ )",
"(ಠ_ಠ )🌭( ಠ_ಠ)",
"( ಠ_ಠ)🌭(ಠ_ಠ )",
"(ಠ_ಠ )🔥🌭🔥( ಠ_ಠ)",
"( ಠ_ಠ)🌭(ಠ_ಠ )",
],
"terminal_cult_initiation": [
"( ಠ_ಠ) (ಠ_ಠ )",
"( ಠ_ಠ)🌭 (ಠ_ಠ )",
"( ಠ_ಠ) 🌭 (ಠ_ಠ )",
"( ಠ_ಠ) 🌭 (ಠ_ಠ )",
"( ಠ_ಠ) 🌭 (ಠ_ಠ )",
],
}
class TerminalAnimation:
"""Transient terminal status animation."""
def __init__(
self,
name: str = "agent_thinking",
*,
message: str = "NightShift running",
stream: TextIO | None = None,
interval_seconds: float = 0.18,
enabled: bool = True,
) -> None:
self.frames = animation_frames(name)
self.message = message
self.stream = stream or sys.stderr
self.interval_seconds = interval_seconds
self.enabled = enabled and should_style(self.stream)
self._stop = threading.Event()
self._thread: threading.Thread | None = None
self._width = 0
def __enter__(self) -> "TerminalAnimation":
self.start()
return self
def __exit__(self, *_exc: object) -> None:
self.stop()
def start(self) -> None:
if not self.enabled or self._thread is not None:
return
self._thread = threading.Thread(target=self._run, daemon=True)
self._thread.start()
def stop(self) -> None:
if self._thread is None:
return
self._stop.set()
self._thread.join(timeout=1)
self._clear()
self._thread = None
def _run(self) -> None:
index = 0
while not self._stop.is_set():
frame = self.frames[index % len(self.frames)]
text = f"{frame} {self.message}"
self._width = max(self._width, len(text))
self.stream.write("\r" + text.ljust(self._width))
self.stream.flush()
index += 1
self._stop.wait(self.interval_seconds)
def _clear(self) -> None:
if not self.enabled:
return
self.stream.write("\r" + (" " * self._width) + "\r")
self.stream.flush()
def animation_frames(name: str) -> tuple[str, ...]:
frames = HOTDOG_ANIMATIONS.get(name)
if not frames:
frames = HOTDOG_ANIMATIONS["agent_thinking"]
return tuple(frames)
def should_style(stream: TextIO | None = None) -> bool: def should_style(stream: TextIO | None = None) -> bool:
stream = stream or sys.stdout stream = stream or sys.stdout
if os.environ.get("NO_COLOR"): if os.environ.get("NO_COLOR"):

View File

@ -3,10 +3,10 @@
from __future__ import annotations from __future__ import annotations
PACKAGE_VERSION = "0.2.2" PACKAGE_VERSION = "0.2.3"
RELEASE_CHANNEL = "alpha" RELEASE_CHANNEL = "alpha"
hotdog_version = "footlong" hotdog_version = "new-york"
topping_version = "mustard" topping_version = "sport-peppers"
HOTDOG_VERSIONS = ( HOTDOG_VERSIONS = (
"bratwurst", "bratwurst",

View File

@ -6,7 +6,13 @@ from unittest.mock import patch
from nightshift.artifacts import ArtifactStore from nightshift.artifacts import ArtifactStore
from nightshift.runlog import RunLogger from nightshift.runlog import RunLogger
from nightshift.terminal import format_banner, format_console_event_line from nightshift.terminal import (
HOTDOG_ANIMATIONS,
TerminalAnimation,
animation_frames,
format_banner,
format_console_event_line,
)
class FakeTTY(StringIO): class FakeTTY(StringIO):
@ -25,6 +31,19 @@ class TerminalStylingTests(unittest.TestCase):
self.assertIn("NightShift", banner) self.assertIn("NightShift", banner)
self.assertIn("\x1b[", banner) self.assertIn("\x1b[", banner)
def test_animation_frames_fall_back_to_agent_thinking(self) -> None:
self.assertEqual(animation_frames("missing"), tuple(HOTDOG_ANIMATIONS["agent_thinking"]))
self.assertEqual(animation_frames("classic_dance"), tuple(HOTDOG_ANIMATIONS["classic_dance"]))
def test_terminal_animation_is_disabled_for_non_tty(self) -> None:
stream = StringIO()
animation = TerminalAnimation(stream=stream)
with animation:
pass
self.assertEqual(stream.getvalue(), "")
def test_console_event_line_colors_success_and_failure(self) -> None: def test_console_event_line_colors_success_and_failure(self) -> None:
success = format_console_event_line( success = format_console_event_line(
"2026-05-17T00:00:00Z", "2026-05-17T00:00:00Z",

View File

@ -15,8 +15,8 @@ from nightshift.version import (
class VersionTests(unittest.TestCase): class VersionTests(unittest.TestCase):
def test_display_version_includes_channel_hotdog_and_topping(self) -> None: def test_display_version_includes_channel_hotdog_and_topping(self) -> None:
self.assertEqual(display_version(), "0.2.2-alpha-footlong-mustard") self.assertEqual(display_version(), "0.2.3-alpha-new-york-sport-peppers")
self.assertEqual(PACKAGE_VERSION, "0.2.2") self.assertEqual(PACKAGE_VERSION, "0.2.3")
self.assertIn(hotdog_version, HOTDOG_VERSIONS) self.assertIn(hotdog_version, HOTDOG_VERSIONS)
self.assertIn(topping_version, TOPPING_VERSIONS) self.assertIn(topping_version, TOPPING_VERSIONS)