#!/usr/bin/env bash
# Real demos for P006 "AI Agents Will Finally Make Sense After This".
# A tiny Python project (lab/bill-splitter: split a restaurant bill, one failing test) and
# lab/agent, which runs Claude Code headless (claude -p, Sonnet) on it and prints every step of
# its loop: each model request, the tokens it read, each tool it asked for, and each result.
# Nothing else is loaded (no user settings, no auto memory, no MCP servers), and the permission
# mode is dontAsk: read-only commands and edits run, the only other command allowed is
# python3 -m unittest, and anything else is refused.
# The lab is copied to ~/agent-lab (outside this repo, so the agent can't read our project
# files) and removed afterwards. Commands are typed into one real interactive bash; commands and
# output go to demo_output.json. Model answers differ every run: build_script.py reads whatever
# this run produced and asserts the behaviour the script describes.
set -euo pipefail
cd "$(dirname "$0")"
LAB="$HOME/agent-lab"
rm -rf "$LAB"; mkdir -p "$LAB"
cp -R lab/. "$LAB/"
trap 'rm -rf "$LAB"' EXIT

python3 - "$LAB" <<'PY'
import fcntl, json, os, pty, re, select, shutil, struct, sys, termios, time, subprocess
LAB = sys.argv[1]
READY = re.compile(rb"__READY__:(.*?):__ ")
steps = {}

def clean(text):
    text = re.sub(r"\x1b\[[0-9;?]*[A-Za-z]|\x1b[()][A-Z0-9]", "", text)
    lines = [l.rstrip("\r").split("\r")[-1] for l in text.split("\n")]
    return "\n".join(l.rstrip() for l in lines).strip("\n")

class Shell:
    def __init__(self):
        pid, fd = pty.fork()
        if pid == 0:
            fcntl.ioctl(0, termios.TIOCSWINSZ, struct.pack("HHHH", 40, 80, 0, 0))
            os.chdir(LAB)
            os.execvpe("bash", ["bash", "--norc", "--noprofile", "-i"], dict(os.environ, TERM="dumb"))
        self.fd, self.where = fd, ""
        os.write(fd, b"stty -echo; PS1='__REA''DY__:\\W:__ '\n")
        self.read()

    def read(self, timeout=900):
        buf, end = b"", time.time() + timeout
        while time.time() < end:
            if select.select([self.fd], [], [], 0.3)[0]:
                buf += os.read(self.fd, 65536)
                m = None
                for m in READY.finditer(buf):
                    pass
                if m:
                    self.where = m.group(1).decode()
                    return clean(buf[:m.start()].decode("utf-8", "replace"))
        raise SystemExit(f"timed out; last output: {buf[-400:]!r}")

    def run(self, key, cmd):
        shown = f"{self.where}$ {cmd}"
        os.write(self.fd, cmd.encode() + b"\n")
        t = time.time()
        out = self.read()
        steps[key] = {"cmd": shown, "out": out, "seconds": round(time.time() - t, 1)}
        print(f"--- {key} ({time.time()-t:.0f}s)\n{shown}\n{out}\n", flush=True)
        return out

def keep_log(key):
    """The full event stream of the agent run just made (for the script's own checks)."""
    steps[key]["events"] = open(os.path.join(LAB, ".log", "last.jsonl")).read()

s = Shell()
TASK = '"Run the tests (python3 -m unittest), find out why they fail, and fix it."'
ASK = '"What did we fix in this project last time?"'
s.run("ls", "ls")
s.run("cd_in", "cd bill-splitter")
s.run("cat_split", "cat split.py")
s.run("tests_before", "python3 -m unittest")
s.run("cd_out", "cd ..")
s.run("no_tools", f"./agent --no-tools {TASK}"); keep_log("no_tools")
s.run("fix", f"./agent --session fix {TASK}"); keep_log("fix")
s.run("raw1", "./raw 1")
s.run("tests_after", "cd bill-splitter && python3 -m unittest; cd ..")
s.run("grep_fix", "grep tip_percent bill-splitter/split.py")
s.run("mem_none", f"./agent --no-tools {ASK}"); keep_log("mem_none")
s.run("mem_save", './agent --session fix "Add a one-line note to CLAUDE.md saying what you fixed today."'); keep_log("mem_save")
s.run("mem_cat", "cat bill-splitter/CLAUDE.md")
s.run("mem_ask", f"./agent --no-tools {ASK}"); keep_log("mem_ask")
s.run("install", './agent "Install pytest for this project."'); keep_log("install")
# Off-screen: put the original, broken project back for the turn-limit run (the script says so).
shutil.copy("lab/bill-splitter/split.py", os.path.join(LAB, "bill-splitter", "split.py"))
os.remove(os.path.join(LAB, "bill-splitter", "CLAUDE.md"))
s.run("maxturns", f"./agent --max-turns 2 {TASK}"); keep_log("maxturns")
s.run("maxturns_after", "grep 'with_tip =' bill-splitter/split.py")

claude_version = subprocess.run(["claude", "--version"], capture_output=True, text=True).stdout.strip()
steps["_meta"] = {"cmd": "", "out": json.dumps({"claude_code": claude_version, "model": "sonnet",
                   "python": subprocess.run(["python3", "--version"], capture_output=True, text=True).stdout.strip(),
                   "run_at": time.strftime("%Y-%m-%d %H:%M %Z")})}
json.dump(steps, open("demo_output.json", "w"), indent=1)
print("saved demo_output.json")
PY
