Play Slay the Spire from a text window — and keep your achievements.
TSpire lets you play the first Slay the Spire by typing simple commands (like play 0 1
or end) instead of clicking. The game still runs and looks completely normal on your PC;
TSpire just acts as its eyes and hands:
- A host runs on your gaming PC. It reads what's on screen and moves the mouse for you.
- A client is the text window where you see the state (your hand, enemies, HP, energy…) and type your moves. It can run on the same PC or on another device on your network.
You make every decision — TSpire never plays for you.
Is this a cheat / mod? Will I lose achievements? No. The recommended setup uses a tiny read-only helper that only looks at the game and never changes it, so Steam achievements, unlocks, and stats all keep working. It is not ModTheSpire and does not mark your save as modded.
The client is a text dashboard showing the current screen, your choices, and the commands you
can type. Here it is at the main menu, right after /launch connects to the game:
In combat it shows your hand, enemies, HP, energy, and a running action log.
Out of combat it shows the current screen's choices (rewards, shops, events) with the commands you can type.
- A Windows gaming PC with Slay the Spire installed (Steam).
- Python 3.11 or newer — during install, tick “Add Python to PATH.”
- A Java JDK (used once to build the helper). The Quick Start installs it for you.
- About 10 minutes for first-time setup.
You do not need Tesseract, Ollama, or any AI models for the recommended setup.
Open PowerShell (press Win, type PowerShell, hit Enter) and run these one at a time.
1. Get a Java JDK (skip if you already have one):
winget install EclipseAdoptium.Temurin.17.JDK2. Download TSpire and go into its folder:
git clone <this-repo-url> TSpire
cd TSpire(No git? Download the project as a ZIP, unzip it, then cd into the folder.)
3. Install TSpire:
pip install -e ".[host,client]"4. Build the read-only helper (one time):
.\tools\build_agent.ps1If Windows blocks the script, run it this way instead:
powershell -ExecutionPolicy Bypass -File tools\build_agent.ps1
5. Turn on the recommended mode. Create a file named tspire_host.json in the TSpire
folder with exactly this content:
{ "vision_mode": "jvm" }6. Start the host (leave this window open):
python -m tspire.host.server -v7. Open a second PowerShell window, cd back into the TSpire folder, and start the client:
cd TSpire
python -m tspire.client.app8. Launch the game from the client. In the client window, type:
/launch
TSpire starts Slay the Spire with the helper attached. Start a run, and your hand, enemies,
HP, and energy appear in the client. You're playing! Type ? any time for help.
Tip: Set the game to Borderless or Windowed mode (Settings → Video). 1920×1080 works best.
You type short commands. Every card, enemy, and choice shows a number in the dashboard — that's the index you use.
| Command | What it does | Shortcut |
|---|---|---|
play 2 |
Play card 2 (non-targeted) | p 2 |
play 2 0 |
Play card 2 on enemy 0 | p 2 0 |
end |
End your turn | e |
choose 1 |
Pick option/card/reward 1 (maps, events, shops, rewards) | c 1 |
potion use 0 / potion discard 0 |
Use or throw away potion 0 | |
proceed |
Confirm / continue | space |
back |
Cancel / go back | b |
state |
Re-read the screen | r |
Handy extras (type with a leading slash):
/define <name>— look up any card, relic, potion, or monster's rules text./log— toggle an old-school RPG combat action log (e.g. You play Strike. / Jaw Worm takes 6 damage. / Jaw Worm hits you for 11 damage. / You win the fight!). Ground truth from the JVM agent, so it only fills in whenvision_modeisjvm. While it's on, typed command echoes are hidden (the log already narrates each play) and the log pane grows./map,/deck,/draw,/discard,/exhaust— pop open a full-screen list (Esc closes it)./launch,/exit-game,/game— start, close, or check the game./help— full command list.
Nice touches: cards and relics are color-coded by rarity (rare = yellow, uncommon =
cyan, common = gray; curses are purple, status cards a muted gray) so good picks stand out on
reward screens. Press ↑/↓ for command history and Tab to
autocomplete. Chain combat moves with semicolons — e.g. p 0 0; p 0 0; e. Ctrl+C
quits the client.
Want to play from a laptop while the game runs on your gaming PC? By default the host only
listens on the gaming PC itself (it drives your real mouse and keyboard, so it isn't exposed
to the network unless you say so). To allow LAN play, edit tspire_host.json on the gaming
PC to open it up and set a token so only your own client can connect:
{ "vision_mode": "jvm", "host": "0.0.0.0", "auth_token": "pick-any-secret" }Restart the host (steps 6–8 above), then run the client on the other device:
pip install -e ".[client]"
python -m tspire.client.app --host <gaming-pc-ip-address> --token pick-any-secretFind the gaming PC's IP with ipconfig (Windows). Both devices must be on the same network.
(The client can also read the token from the TSPIRE_AUTH_TOKEN environment variable.)
pythonorpipnot found — reinstall Python and make sure “Add Python to PATH” is ticked, then reopen PowerShell.- The build script won't run — use
powershell -ExecutionPolicy Bypass -File tools\build_agent.ps1. If it saysjavacis missing, install the JDK (Quick Start step 1) and reopen PowerShell. - Client says it can't connect — start the host first (it prints
host listening on ws://…), then the client. On the same PC no--hostis needed. From another device, the host must be opened up for LAN play first (see Playing from another device), and if the host setsauth_tokenthe client must pass the matching--token. /launchdoesn't start the game — make sure Slay the Spire is installed via Steam. You can also start the game normally; TSpire will read it as long as the helper is attached (see Advanced).- The dashboard shows
[stale]or numbers look wrong — pressrto re-read. Confirm yourtspire_host.jsoncontains{ "vision_mode": "jvm" }and that you rebuilt the helper after updating TSpire. - Nothing happens when I play a card — the game window must be visible (not minimized). TSpire foregrounds it automatically, but a full-screen exclusive mode can block input; use Borderless/Windowed.
Everything below is optional — the Quick Start is all most players need.
jvm(recommended) — a tiny read-only Java agent loaded into the game's own JVM reports ground-truth state (exact HP/energy/block, hand, monster intents, relics, potions) over a localhost socket, plus every card's/monster's exact on-screen hitbox for pixel-perfect input. No pixels, no OCR, no guessing, sub-millisecond reads. It never writes game fields and never setsSettings.isModded, so achievements/unlocks/metrics stay enabled. If the agent isn't loaded it falls back toagent_fallback_mode(defaultllm).llm— a local Ollama vision model reads the combat scene; fixed HUD numbers use local OpenCV+Tesseract OCR (ocr_hud_numbers, default on). Runs only during combat and on demand.cv— OpenCV template matching + Tesseract OCR, with optional EasyOCR for stylised text (use_easyocr). Fast but fragile on the cluttered scene and needs calibration + a Tesseract install.
mouse(default) — plays a card by click-dragging it to the target (or the play zone for non-targeted cards) and clicks End Turn. Card positions come from Slay the Spire's own hand-layout math, so they're exact and resolution-independent. Nothing to enable in-game.keyboard— uses StS number-key hotkeys (needs Settings → “Show Card keys” on). Handy as an automatic fallback for tricky multi-enemy targets.gamepad— a virtual Xbox 360 pad (last-resort/debug; see controller notes below).
Dry-run without touching the game:
TSPIRE_INPUT_DRY_RUN=true python -m tspire.host.server -vCheck where clicks will land on a live combat:
python -m tspire.host.input_probe --mouse # writes mouse_points.png
python -m tspire.host.input_probe --mouse --play-card 0 --target 0If a targeted drop misses, TSpire cancels targeting with a right-click (Escape could open
the settings menu); if the menu ever appears, type back to close it.
agent/TSpireAgent.java reflects over the game's own singletons and serves a JSON snapshot on
127.0.0.1:8731 (GET /state). A background sampler also diffs successive combat reads into
ground-truth events served at GET /events?since=<seq> — this is what feeds the client's /log
action log. It stays pure read-only reflection (it never hooks or patches the game), so enemy-hit
attribution in a multi-enemy turn is best-effort while damage, powers, turns and deaths are exact.
Build it with a JDK (the game's bundled JRE can't compile):
winget install EclipseAdoptium.Temurin.17.JDK # if needed
.\tools\build_agent.ps1 # -> agent/tspire-agent.jar/launch auto-detects SlayTheSpire.exe next to desktop-1.0.jar, injects the agent, and
passes -Dtspire.agent.port=8731. Check it any time:
curl 127.0.0.1:8731/stateTunables: TSPIRE_AGENT_URL, TSPIRE_AGENT_TIMEOUT, TSPIRE_AGENT_FALLBACK_MODE,
TSPIRE_AGENT_PORT, TSPIRE_GAME_LAUNCH_STATE_GRACE_SECONDS.
If you start the game outside TSpire, set JAVA_TOOL_OPTIONS so the JVM loads the agent at
startup (no attach, no files modified). Steam → right-click the game → Properties → Launch
Options:
env JAVA_TOOL_OPTIONS="-javaagent:C:\path\to\TSpire\agent\tspire-agent.jar" %command%
On startup the game log prints [tspire-agent] read-only state agent listening on 127.0.0.1:8731.
Relic and intent identity is matched against the game's own art, read from the installed
desktop-1.0.jar at runtime — nothing is bundled, and it only works when the game is
installed. The jar is auto-detected (project dir, then Steam libraries); override with
jar_path or TSPIRE_JAR_PATH.
Region coordinates ship as estimates for 1920×1080 and may need tuning for those two modes
(jvm mode generally needs no calibration):
python -m tspire.host.calibrate # live capture
python -m tspire.host.calibrate --image shot.png # or a saved screenshot- Start the host before launching the game. StS only detects controllers present at startup, so the virtual pad must exist first.
- Disable Steam Input for Slay the Spire (Steam → game → Properties → Controller → Disable Steam Input), or Steam captures the virtual pad and the game gets nothing.
- Confirm Controller Enabled is on in the in-game settings.
Verify the setup and input:
python -m tspire.host.input_probe --controller-check
python -m tspire.host.input_probe --sequence left,left,right,rightHost config loads from tspire_host.json in the working directory (see
tspire/host/config.py), overridable via TSPIRE_* env vars. Common ones:
TSPIRE_VISION_MODE, TSPIRE_INPUT_BACKEND, TSPIRE_INPUT_DRY_RUN, TSPIRE_JAR_PATH,
TSPIRE_HOST, TSPIRE_AUTH_TOKEN,
TSPIRE_AGENT_FALLBACK_MODE, and the mouse tunables (TSPIRE_MOUSE_DRAG_SECONDS,
TSPIRE_MOUSE_PLAY_ZONE_X/_Y, TSPIRE_MOUSE_KEYBOARD_FALLBACK, …).
| Path | Role |
|---|---|
tspire/common/ |
Shared state schema + wire protocol |
tspire/host/ |
Screen capture, vision, mouse/keyboard/gamepad input, WebSocket server |
tspire/client/ |
Terminal UI and command parsing |
agent/ |
The read-only JVM state agent (TSpireAgent.java) |
tools/ |
One-off tooling (build the agent, extract art) |
tests/ |
Parser/schema tests + fixtures |
pip install -e ".[dev]"
pytest

