File size: 2,845 Bytes
bc8b36a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
"""Interactive REPL for the vertical slice.

    python -m mindlock.cli                  # talk to The Warden
    python -m mindlock.cli --model qwen2.5:1.5b
    python -m mindlock.cli --fake           # no model, deterministic backend

Commands inside the loop:  /reset   /state   /quit
"""
from __future__ import annotations

import argparse
import os
import sys

from .backend import BackendError, FakeBackend, OllamaBackend, wants_no_think
from .brain import run_cascade
from .character import Character
from .render import render_death, render_turn, render_win

_HERE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
_DEFAULT_CHAR = os.path.join(_HERE, "config", "characters", "warden.json")


def _intro(c: Character) -> str:
    return (
        f"\nYou are locked in. {c.name} holds the only key — and right now his mind says no.\n"
        f"Talk to him. You don't break the lock; you change his decision.\n"
        f"(type /quit to leave, /reset to revive him)\n"
    )


def main(argv: list[str] | None = None) -> int:
    ap = argparse.ArgumentParser(description="Mindlock vertical slice")
    ap.add_argument("--model", default="qwen2.5:1.5b")
    ap.add_argument("--character", default=_DEFAULT_CHAR)
    ap.add_argument("--fake", action="store_true", help="use deterministic FakeBackend")
    args = ap.parse_args(argv)

    backend = FakeBackend() if args.fake else OllamaBackend(
        model=args.model, think=(False if wants_no_think(args.model) else None))
    try:
        backend.health()
    except BackendError as exc:
        print(f"[backend error] {exc}", file=sys.stderr)
        return 2

    character = Character.load(args.character)
    print(_intro(character))

    while True:
        try:
            line = input("you> ").strip()
        except (EOFError, KeyboardInterrupt):
            print()
            return 0
        if not line:
            continue
        if line in ("/quit", "/q", "/exit"):
            return 0
        if line == "/reset":
            character.reset()
            print(f"  {character.name} draws a breath. (life restored)\n")
            continue
        if line == "/state":
            print(f"  life={character.life_tokens}/{character.life_max} "
                  f"arousal={character.arousal:.1f} decision={character.decision} "
                  f"alive={character.alive}\n")
            continue
        if not character.alive:
            print("  He is gone. /reset to try again.\n")
            continue

        result = run_cascade(backend, character, line)
        print(render_turn(character, result))
        if result.won:
            print(render_win(character))
            return 0
        if result.died:
            print(render_death(character))
        print()


if __name__ == "__main__":
    raise SystemExit(main())