Everything core from cicero-pas's .task-manager, with instance data reduced to skeleton: empty stage directories, the task template, empty local/ scaffolding, and a README covering install (clone into .task-manager/, vendored on purpose) and update (update.sh replaces core wholesale; local/ and tasks/ survive). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
78 lines
2.6 KiB
Python
78 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Live kanban board for ../tasks/ — board, session timeline, heads-up display.
|
|
|
|
python3 .task-manager/manager/board.py # serve on :26071, open a browser
|
|
python3 .task-manager/manager/board.py --port 9000 --no-open
|
|
|
|
The manager sits cleanly on top of the tasks/ directory: it reads and moves
|
|
task files, but the tasks work as a plain folder kanban without it. See
|
|
../CLAUDE.md for the workflow and the module map:
|
|
|
|
config.py paths, stages, launch configuration
|
|
state.py shared registries, event persistence, SSE fan-out
|
|
taskfiles.py reading/moving task files (the only code touching tasks/)
|
|
events.py hook payloads → displayable events, session registry
|
|
agents.py headless work/review agents: launch, reap, stop, diff
|
|
watch.py 2s disk poller narrating moves made outside the API
|
|
httpd.py HTTP routes, SSE stream, the page itself
|
|
.prompts/ agent prompt templates (read fresh on every launch)
|
|
|
|
Stdlib only, no install.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import errno
|
|
import threading
|
|
import webbrowser
|
|
from http.server import ThreadingHTTPServer
|
|
|
|
import config
|
|
import drive
|
|
import events
|
|
import github
|
|
import httpd
|
|
import state
|
|
import watch
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--port", type=int, default=config.PORT)
|
|
parser.add_argument("--no-open", action="store_true", help="don't open a browser")
|
|
args = parser.parse_args()
|
|
state.serve_port = args.port
|
|
|
|
url = f"http://127.0.0.1:{args.port}/"
|
|
try:
|
|
server = ThreadingHTTPServer(("127.0.0.1", args.port), httpd.Handler)
|
|
except OSError as exc:
|
|
if exc.errno != errno.EADDRINUSE:
|
|
raise
|
|
# The port is pinned, so this is nearly always the board already running.
|
|
print(f"Port {args.port} is already in use — assuming the board is up at {url}")
|
|
if not args.no_open:
|
|
webbrowser.open(url)
|
|
return
|
|
|
|
config.SESSIONS_DIR.mkdir(exist_ok=True)
|
|
config.AGENT_DIR.mkdir(exist_ok=True)
|
|
events.load_disk_sessions()
|
|
threading.Thread(target=watch.watcher, daemon=True).start()
|
|
threading.Thread(target=github.poller, daemon=True).start()
|
|
threading.Thread(target=github.reconcile, daemon=True).start()
|
|
drive.adopt()
|
|
|
|
print(f"Task board for {config.TASKS}\n {url}\n Ctrl-C to stop")
|
|
if not args.no_open:
|
|
webbrowser.open(url)
|
|
try:
|
|
server.serve_forever()
|
|
except KeyboardInterrupt:
|
|
print("\nstopped")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|