Skip to content

victorljii/sts2-cli

Repository files navigation

sts2-cli

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.

Architecture

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).

Requirements

  • Slay the Spire 2 on Steam (the engine DLLs are copied from your install)
  • .NET SDK (dotnet on PATH; .NET 10 supported)
  • Go 1.22+
  • Python 3.9+ (only for the act.py CLI; uses the standard library, no extra deps)

Setup

git clone <this-repo>
cd sts2-cli
./setup.sh      # copies DLLs from Steam → IL patches → builds the C# engine

If you change the C# engine code, rebuild it:

~/.dotnet-arm64/dotnet build src/Sts2Headless/Sts2Headless.csproj

Quick Start

1. Start the HTTP bridge

The 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

Environment variables

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)

2. Start playing (recommended)

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 step

Characters: 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"}'

3. Drive / test the bridge with agent/act.py

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...].

Session lifecycle

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 first

Only one run needs to be active at a time; act.py always targets the running session reported by /health.

Two ways to drive a run

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 turn

Interactive REPL (recommended for manual testing):

python3 agent/act.py repl
STS2 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.

act.py command reference

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)

del ends the session on the bridge. In the REPL, q/quit/exit only leaves the REPL — the session keeps running until you del it (or stop the bridge).

Letting an AI agent play (Cursor skill)

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:

  1. Start the bridge on port 9090 (see Quick Start). The skill assumes http://localhost:9090.
  2. 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 with play / end / node / pick / opt / ...,
    • follows the bundled strategy (combo math, map routing, boss tactics).

What the skill contains:

  • the act.py command 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.)

Logs & replay

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"

Replaying a run in the terminal

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.jsonl

Navigation 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

HTTP API

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)

Supported characters

Character Status
Ironclad Fully playable
Silent Fully playable
Defect Fully playable
Regent Fully playable
Necrobinder Fully playable

Project layout

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

License

See LICENSE.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors