mirror of
https://github.com/khodges42/nightShift.git
synced 2026-06-14 10:08:37 +00:00
added hotdog animations
I am a professional software engineer
This commit is contained in:
parent
d08e629bce
commit
93a50ddb42
|
|
@ -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}")
|
||||||
|
|
|
||||||
|
|
@ -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"):
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user