English | 简体中文
Run Slay the Spire 2 headless and drive it programmatically — all damage, card effects, enemy AI, relics and RNG come from the real game engine, identical to the retail game. Everything is unlocked: all characters, cards, relics, potions and ascension levels.
This repo started as a fork of an upstream CLI project but has since diverged
substantially. It is now centered on a Go HTTP bridge that wraps the C# headless
engine as a RESTful API, plus a small Python CLI (agent/act.py) for driving and
testing the bridge by hand or from an agent/LLM.
The legacy interactive player has been removed. The only Python kept is the bridge CLI (
agent/act.py) and the replay viewer (python/replay.py, see Logs & replay), which reads the bridge's own logs.
agent/act.py (CLI / REPL) or any HTTP client
│ HTTP (REST, port 9090 by convention)
▼
http/ ── Go HTTP bridge ──────────────┐
• session management │ spawns & manages
• auto-save / crash recovery │ stdin/stdout JSON
• command logging (JSONL) ▼
src/Sts2Headless (C#)
│ RunSimulator.cs
▼
sts2.dll (game engine, IL patched)
+ src/GodotStubs (replaces GodotSharp.dll)
+ Harmony patches (localization)
The bridge is a pure HTTP server. To interact with it from a terminal, use the
companion CLI agent/act.py (one-shot commands or an interactive REPL).
- Slay the Spire 2 on Steam (the engine DLLs are copied from your install)
- .NET SDK (
dotnetonPATH; .NET 10 supported) - Go 1.22+
- Python 3.9+ (only for the
act.pyCLI; uses the standard library, no extra deps)
git clone <this-repo>
cd sts2-cli
./setup.sh # copies DLLs from Steam → IL patches → builds the C# engineIf you change the C# engine code, rebuild it:
~/.dotnet-arm64/dotnet build src/Sts2Headless/Sts2Headless.csprojThe bridge needs STS2_GAME_DIR (the game's data directory). The default port is
10086, but the CLI (act.py) talks to 9090, so start it on 9090:
cd http
STS2_BRIDGE_PORT=9090 \
STS2_GAME_DIR="$HOME/Library/Application Support/Steam/steamapps/common/Slay the Spire 2/SlayTheSpire2.app/Contents/Resources/data_sts2_macos_arm64" \
go run .Check it is up:
curl -s http://localhost:9090/api/v1/health| Variable | Default | Description |
|---|---|---|
STS2_GAME_DIR |
(required) | Game data directory (DLLs + content) |
STS2_BRIDGE_PORT |
10086 |
HTTP port (use 9090 for the act.py CLI) |
STS2_COMMAND_TIMEOUT |
30s |
Per-command timeout (Go duration format) |
Once the bridge is up, the easiest way is to use the skill.
The strategy it uses lives in the agent/ directory (currently Necrobinder only, reaching
up to the act 1 boss).
To drive it yourself, start a run with one CLI command (no manual session creation):
python3 agent/act.py new Necrobinder # create a session + start a run in one stepCharacters: Ironclad, Silent, Defect, Regent, Necrobinder. Full command reference below.
Calling the HTTP API by hand (for debugging): you don't need this to play — it's mainly for debugging or integration. Under the hood a run is two steps: create a session to get a
session_id, then start the run with a character.SID=$(curl -s -X POST http://localhost:9090/api/v1/sessions \ -H 'Content-Type: application/json' -d '{}' \ | python3 -c "import sys,json; print(json.load(sys.stdin)['session_id'])") curl -s -X POST "http://localhost:9090/api/v1/sessions/$SID/run" \ -H 'Content-Type: application/json' \ -d '{"character":"Necrobinder","ascension":0,"lang":"zh"}'
agent/act.py is the companion CLI for the bridge. It auto-discovers the active
session via /health, so you never pass a session id; new creates the session and
starts the run in one step, with no manual curl.
Invocation: python3 agent/act.py <command> [args...].
python3 agent/act.py new Necrobinder # 1. create session + start run
python3 agent/act.py new Silent 10 en # optional: character ascension lang
python3 agent/act.py s # 2. play — see command table below
# ...
python3 agent/act.py del # 3. stop the current session
python3 agent/act.py del saves/final.json # ...or stop and save a checkpoint firstOnly one run needs to be active at a time; act.py always targets the running session
reported by /health.
One-shot commands (one HTTP call per invocation):
python3 agent/act.py s # show current state
python3 agent/act.py map # show the map
python3 agent/act.py node 3 0 # pick map node (col, row)
python3 agent/act.py play 0 1 # play hand card 0 at enemy 1
python3 agent/act.py end # end turnInteractive REPL (recommended for manual testing):
python3 agent/act.py replSTS2 HTTP 交互模式 — 输入 help 查看命令, 回车查看状态, q 退出
sts2> new Necrobinder # start a run (if none active)
sts2> s # view state
sts2> play 0 1 # play card 0 at enemy 1
sts2> end # end turn
sts2> help # command cheatsheet
sts2> del # stop the session
sts2> q # leave the REPL (session keeps running unless you del it)
After every card you play, hand indices are renumbered — always look at the latest output before sending the next command.
| Command | Usage | Purpose |
|---|---|---|
new |
new <character> [ascension] [lang] |
Create a session and start a run |
s |
s |
Show current state |
map |
map |
Show full map |
node |
node <col> <row> |
Select a map node |
play |
play <card_idx> [target_idx] |
Play a hand card (attacks need a target) |
end |
end |
End turn |
opt |
opt <idx> |
Event / rest-site option |
pick |
pick <idx> |
Pick a card reward |
skip |
skip |
Skip the card reward |
sel |
sel <idx> |
Select cards (e.g. Seance) |
skipsel |
skipsel |
Skip a selection |
bundle |
bundle <idx> |
Select a card bundle |
potion |
potion <idx> [target] |
Use a potion |
proceed |
proceed |
Continue / confirm |
leave |
leave |
Leave the room |
remove |
remove |
Remove a card (shop) |
a |
a '{"action":"...","args":{...}}' |
Send a raw action JSON |
del |
del [save_path] |
Stop the current session (optionally save first) |
delends the session on the bridge. In the REPL,q/quit/exitonly leaves the REPL — the session keeps running until youdelit (or stop the bridge).
This repo ships a Cursor Agent Skill at
skills/play-sts2/ that teaches the agent to
play through the HTTP bridge using agent/act.py. With it, you can just tell Cursor
"play Slay the Spire 2" and it will drive the run for you.
How to use it:
- Start the bridge on port 9090 (see Quick Start). The
skill assumes
http://localhost:9090. - Ask Cursor to play, e.g. "start a new STS2 run as Necrobinder and play it". The
skill activates automatically (it triggers on STS2 gameplay requests) and the agent:
- creates a run with
python3 agent/act.py new <character>, - reads state with
act.py s/map, makes decisions, and acts withplay/end/node/pick/opt/ ..., - follows the bundled strategy (combo math, map routing, boss tactics).
- creates a run with
What the skill contains:
- the
act.pycommand cheatsheet and decision-point reference, - a per-turn checklist and boss quick-reference (Necrobinder-focused),
- pointers to the strategy/card notes under
agent/(STRATEGY.md,CARDS.md,HTTP_API_NOTES.md,GAME_LOG.md).
You can edit SKILL.md to change strategy or add characters; Cursor picks up the changes
on the next run. (This is a Cursor-specific feature — without Cursor you can still drive
the bridge manually with act.py.)
Every session is logged automatically. The bridge writes one JSONL file per session
under logs/:
logs/<session_id>/<session_id>_<timestamp>.jsonl
Each line is one step — the command that was sent, the full response/game state, the decision type and how long it took:
{"step":1,"timestamp":"2026-06-11T17:26:23Z","command":{"cmd":"start_run","character":"Necrobinder"},"response":{...},"response_type":"decision","decision":"event_choice","elapsed_ms":2800}You can also query recent steps from a running session via the API:
curl -s "http://localhost:9090/api/v1/sessions/$SID/history?limit=10"python/replay.py is a TUI that steps through a session log frame by frame — showing
HP / block / energy deltas, enemies, map, rewards and more.
pip install -r requirements.txt # textual + rich (one-time)
# Pick a log file from logs/<session_id>/ and open it
python3 python/replay.py logs/d6974a9f510e9e27/d6974a9f510e9e27_20260611_172623.jsonlNavigation keys:
| Key | Action | Key | Action |
|---|---|---|---|
→ / l |
next step | ← / h |
previous step |
↓ / j |
next floor | ↑ / k |
previous floor |
] |
next round | [ |
previous round |
g |
go to step | Home / End |
first / last step |
q |
quit |
The full endpoint list, action catalog, response shapes and error codes live in
http/README.md. Endpoints (all under /api/v1):
| Method | Path | Description |
|---|---|---|
| GET | /health |
Health check + active sessions |
| POST | /sessions |
Create a session |
| DELETE | /sessions/{id} |
Destroy a session (?save_path= to save first) |
| POST | /sessions/{id}/run |
Start a run |
| GET | /sessions/{id}/state |
Current decision point / game state |
| POST | /sessions/{id}/action |
Execute an action |
| GET | /sessions/{id}/map |
Map graph |
| POST | /sessions/{id}/save |
Save a checkpoint |
| POST | /sessions/{id}/load |
Load a checkpoint |
| GET | /sessions/{id}/history |
Command history (JSONL) |
| Character | Status |
|---|---|
| Ironclad | Fully playable |
| Silent | Fully playable |
| Defect | Fully playable |
| Regent | Fully playable |
| Necrobinder | Fully playable |
http/ Go HTTP bridge (see http/README.md)
├── main.go entry point
├── config/ configuration + env vars
├── engine/ C# subprocess management
├── session/ session lifecycle + auto-save
├── handler/ HTTP routes + endpoints
├── model/ request/response/game data types
├── history/ command log (JSONL)
└── middleware/ logging + panic recovery
agent/ CLI + agent assets
├── act.py HTTP CLI (one-shot + REPL)
├── HTTP_API_NOTES.md interaction gotchas
└── *.md strategy / card notes
python/replay.py TUI to replay a session log (logs/<id>/*.jsonl)
src/Sts2Headless/ C# headless engine (JSON over stdin/stdout)
src/GodotStubs/ no-op replacement for GodotSharp.dll
localization_eng/ English game data
localization_zhs/ Chinese game data
See LICENSE.