Move all three skills into skills/; add root README; fix paths
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
312742cfef
commit
d95e5d452d
84 files changed
+97
-31
No files matched your search
@@ -0,0 +1,64 @@
|
||||
"""Bot wiring for the desktop host: runs the bot and turns its output into page events.
|
||||
|
||||
No Qt here, so it can be tested and dry-run on its own. Fill BOT_CMD, STEP_MARKERS and
|
||||
FRIENDLY_RULES from lines the bot really prints.
|
||||
|
||||
Dry run, no window: python bot_runner.py (prints each event the page would get)
|
||||
"""
|
||||
import json, os, subprocess, sys
|
||||
from pathlib import Path
|
||||
|
||||
BOT_CMD = [sys.executable, "-m", "{{BOT_MODULE}}"] # the bot, started as a child process
|
||||
BOT_CWD = Path(__file__).resolve().parents[1] # folder BOT_CMD runs in (the project root)
|
||||
RAW_LOG = Path(__file__).with_name("bot_raw.log") # every raw bot line, kept for audit
|
||||
# (specific substring, step label from STEPS in dashboard.html, pct). First match wins.
|
||||
# e.g. ("[step 2]", "Login", 40). Never a bare word like "login": it matches unrelated lines.
|
||||
STEP_MARKERS = []
|
||||
# (specific substring, plain-English message, type: info|success|warn|error). First match wins.
|
||||
# e.g. ("[login] done", "Logged in.", "success"). Unmatched lines are hidden, except error lines.
|
||||
FRIENDLY_RULES = []
|
||||
# A line holding any of these is an error line. Unmatched error lines are shown raw, never hidden.
|
||||
ERROR_WORDS = ("error", "exception", "traceback", "fatal", "failed")
|
||||
|
||||
|
||||
def _first(rules, line):
|
||||
low = line.lower()
|
||||
return next((r for r in rules if r[0].lower() in low), None)
|
||||
|
||||
|
||||
def events_for(line):
|
||||
"""The page events, as (event, data) pairs, for one raw bot line."""
|
||||
out = []
|
||||
if marker := _first(STEP_MARKERS, line):
|
||||
out.append(("step", {"label": marker[1], "pct": marker[2]}))
|
||||
if rule := _first(FRIENDLY_RULES, line):
|
||||
out.append(("log", {"msg": rule[1], "type": rule[2]}))
|
||||
elif any(w in line.lower() for w in ERROR_WORDS): # never hide a real failure
|
||||
out.append(("log", {"msg": line, "type": "error"}))
|
||||
return out
|
||||
|
||||
|
||||
def run(send):
|
||||
"""Run the bot in its own process and send(event, data) for each line it prints, as it prints it."""
|
||||
send("status", {"state": "running", "msg": "Running…"})
|
||||
env = {**os.environ, "PYTHONUNBUFFERED": "1", "PYTHONIOENCODING": "utf-8"} # live lines; no cp1252 crash
|
||||
try:
|
||||
raw = open(RAW_LOG, "a", encoding="utf-8")
|
||||
p = subprocess.Popen(BOT_CMD, cwd=BOT_CWD, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
text=True, encoding="utf-8", errors="replace")
|
||||
except OSError as e:
|
||||
send("log", {"msg": f"Could not start the bot: {e}", "type": "error"})
|
||||
send("status", {"state": "error", "msg": "Could not start the bot"})
|
||||
return
|
||||
with p, raw:
|
||||
for line in p.stdout:
|
||||
raw.write(line)
|
||||
for event, data in events_for(line.rstrip()):
|
||||
send(event, data)
|
||||
ok = p.returncode == 0
|
||||
send("status", {"state": "done", "msg": "Completed"} if ok else
|
||||
{"state": "error", "msg": f"Failed (exit code {p.returncode})"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run(lambda event, data: print(json.dumps({"event": event, "data": data}), flush=True))
|
||||
Reference in new issue
Block a user