# Examples and scripts

The MIT-licensed scripts each simulator ships — check.sh, the Kalshi bots, the GitHub review bots and four example stacks — with a run command for each.

[Rystic-Inc/rystic-scripts](https://github.com/Rystic-Inc/rystic-scripts) holds the scripts that ship in the simulators' release archives, one folder per simulator, plus four example stacks. The scripts are MIT-licensed: copy, change and share them. The simulators stay under each archive's `LICENSE.txt`.

```bash
git clone https://github.com/Rystic-Inc/rystic-scripts
```

| folder | what's in it |
|---|---|
| `<simulator>/check.sh` | a smoke check, one per simulator |
| `kalshi/` | `trade-check.sh`, `api-check.py`, `bots/`, `recordings/capture-setup.sh` |
| `github-pulls/bots/` | `bot.py`, `test_bot.py` and five broken `variants/` |
| `environments/` | four example stacks |

## What you need

1. `bash` and `curl`. The shell scripts run on the `bash` 3.2 that macOS ships. None uses `jq`.
2. `curl` 7.75 or later for `s3/check.sh`, which signs its requests with `--aws-sigv4`.
3. Python 3.9 or later. The Kalshi scripts use only the standard library; the GitHub bots need `requests` (`pip install requests`).
4. `openssl` for `api-check.py`. Kalshi's and Clerk's `check.sh` use it when it's there.
5. An installed simulator, and the API key or license `rystic login` stored. A script that boots a simulator finds them where the CLI does.

## check.sh

One per simulator. It boots its own simulator from the binary in its folder, runs a short round trip on that simulator's API ([table below](#what-each-one-checks)), then resets. The simulator stays up so you can point a client at it.

### Run the shipped copy

An installed simulator lives in `~/.rystic/twins/<product>/<version>/` (under `$RYSTIC_HOME` if you set it): the binary, `check.sh`, `HOWTO.md`, `LICENSE.txt` and any extra scripts. `rystic check` prints the path:

```bash
rystic check stripe-twin
# -> ✓ stripe-twin 0.0.11 serves and resets cleanly (model …)
# ->   deeper product checks: bash /Users/you/.rystic/twins/stripe-twin/0.0.11/check.sh

bash ~/.rystic/twins/stripe-twin/0.0.11/check.sh localhost:18080
# -> ✓ stripe-twin is good to go on localhost:18080 (kill 4242 to stop; its log is …)
```

1. The argument is the address to serve on. Without one it takes port 8080; Clerk, Dropbox, Google Drive, Notion and S3 take a free port and print it.
2. `RYSTIC_CHECK_STOP=1` stops the simulator once the checks pass, for CI:

```bash
RYSTIC_CHECK_STOP=1 bash ~/.rystic/twins/stripe-twin/0.0.11/check.sh localhost:18081
# -> ✓ stripe-twin passed its checks on localhost:18081 (stopped)
```

[`rystic check`](https://www.rystic.ai/docs/cli-reference.md#check) is the same idea for any simulator: it serves, reports a model hash and resets.

### Run the repo copy

`check.sh` runs the binary next to it. Put the two in a folder of your own, not in `~/.rystic`:

```bash
mkdir check
cp rystic-scripts/stripe/check.sh check/
ln -s ~/.rystic/twins/stripe-twin/0.0.11/stripe-twin check/
RYSTIC_CHECK_STOP=1 bash check/check.sh localhost:18081
# -> ✓ stripe-twin passed its checks on localhost:18081 (stopped)
```

### What each one checks

| simulator | `check.sh` |
|---|---|
| [Kalshi](https://www.rystic.ai/docs/simulators/kalshi.md) | seeds a market and reads it back; then run `trade-check.sh` |
| [GitHub](https://www.rystic.ai/docs/simulators/github-pulls.md) | lists the open-queue world's pull requests, reads one, refuses an unknown one |
| [Stripe](https://www.rystic.ai/docs/simulators/stripe.md) | creates a customer, reads it back, 404 on an unknown id |
| [Slack](https://www.rystic.ai/docs/simulators/slack.md) | creates a channel, posts a message, reads both back, refuses an unknown channel |
| [Linear](https://www.rystic.ai/docs/simulators/linear.md) | files an issue in the sprint-board world, reads it back, refuses an unknown issue |
| [Lob](https://www.rystic.ai/docs/simulators/lob.md) | saves an address, sends a postcard to it, reads both back, refuses an unknown postcard |
| [Notion](https://www.rystic.ai/docs/simulators/notion.md) | adds a page and a paragraph to the team wiki, reads them back, trashes the page; refuses a missing page and a request without `Notion-Version` |
| [Okta](https://www.rystic.ai/docs/simulators/okta.md) | creates a staged user and a group, adds the user, reads both back, 404 on an unknown user |
| [PayPal](https://www.rystic.ai/docs/simulators/paypal.md) | creates an order, reads it, patches it, 404 on an unknown capture |
| [Resend](https://www.rystic.ai/docs/simulators/resend.md) | creates, reads and lists an email; creates, reads and deletes a webhook; 404 on an unknown email |
| [S3](https://www.rystic.ai/docs/simulators/s3.md) | lists the static-site world's buckets, puts and reads an object under a key with a slash, deletes it, refuses a missing key |
| [Clerk](https://www.rystic.ai/docs/simulators/clerk.md) | creates a user and an organization, merges metadata, refuses a missing user and a request with no secret key; with `openssl`, signs in through the browser companion |
| Dropbox | creates a folder, uploads and downloads a file, refuses a duplicate and a missing path with 409, deletes |
| Google Drive | creates a folder, uploads a file into it, downloads it, 404 on a missing file, deletes |

## Kalshi

Start a simulator with a market in it:

```bash
rystic run kalshi-twin -d --scenario tight_book
# -> kalshi-twin 0.0.40 (model …) → http://localhost:8080  [native default]
```

In the archive these scripts sit next to the binary; in the repo they're under `kalshi/`.

### Bots

```bash
B=rystic-scripts/kalshi/bots
python3 $B/book-reader.py http://localhost:8080
# -> bid 0.3900 x 25  |  ask 0.4000 x 25  |  mid 0.3950
python3 $B/balance-watcher.py http://localhost:8080
# -> balance 10000.0000  |  positions flat  |  0 resting order(s)  |  0 fill(s)
python3 $B/simple-bot.py http://localhost:8080
# -> buy 1 @ 0.4000: filled 1.00 — balance 9999.6000
python3 $B/random-walker.py http://localhost:8080
# -> the twin coped: every order answered, account and book still read
```

1. `book-reader.py` places nothing. It polls one book twice a second and warns if the book crosses or a price leaves (0, 1).
2. `balance-watcher.py` places nothing. Every two seconds it prints balance, positions, resting orders and fills, so you can watch them move beside your own bot.
3. `simple-bot.py` buys one YES contract at the best ask every second, immediate-or-cancel.
4. `random-walker.py` sends 40 random orders from a fixed seed and passes if every answer is a 2xx or a documented 4xx. Arguments after the URL: ticker, order count, seed.
5. Each bot takes the URL first, else `$RYSTIC_ADDR`, else `http://localhost:8080`. `book-reader`, `simple-bot` and `random-walker` use the first active market unless you pass a ticker second.
6. The first three run until Ctrl-C. With `RYSTIC_TICKS=5` they stop after five ticks and print a verdict. That is how [`rystic grid kalshi-twin`](https://www.rystic.ai/docs/cli-reference.md#grid) runs all four against six worlds.

### Checks

```bash
bash rystic-scripts/kalshi/trade-check.sh localhost:8080
# -> ✓ trade check passed: FOK bid 10 @ 0.40 crossed the book — fill, position, book depletion, and WS egress all correct

python3 rystic-scripts/kalshi/api-check.py http://localhost:8080
# -> ✓ account.limits: GET account/limits -> 200 (want 200)
# -> …one line per check, then the total
```

1. `trade-check.sh` resets the simulator, seeds a book, crosses it with a fill-or-kill order, and checks the fill, the position, the depleted book and the WebSocket fill in egress.
2. `api-check.py` resets the simulator, seeds its own world, and walks 93 checks across the Trade API, one `✓` or `✗` line each. It exits 1 if any check fails.

### Sort your captures into tapes

`capture-setup.sh` doesn't record anything. It sorts Kalshi capture files you already have into the folder layout replay and [backtesting](https://www.rystic.ai/docs/kalshi-backtesting.md) read:

```bash
bash rystic-scripts/kalshi/recordings/capture-setup.sh ~/Downloads/captures
# -> ✓ 2 placed, 0 already in place, 0 skipped — 2 markets / 2 files under /Users/you/data/kalshi-orderbook/events
```

1. Every `.ndjson.gz` under the paths you pass is copied to `events/<market_ticker>/`. The ticker comes from the file's first record, so file names don't matter.
2. The destination is `$KALSHI_CAPTURE_DIR`, default `~/data/kalshi-orderbook`. That folder is what `-tapes` takes.
3. Files already in place are counted, not copied again. A file that isn't gzip is skipped and named.

## GitHub review bots

`github-pulls/bots/` holds a Kalshi market maker, not a GitHub client. It is the code under review in the [GitHub simulator](https://www.rystic.ai/docs/simulators/github-pulls.md)'s `github-pulls_bot_pr_queue` world.

1. `bot.py` keeps a bid and an ask resting behind the touch on one market, never crosses, and cancels everything at `SESSION_SECONDS` (default 30) or on SIGTERM. It is configured through the environment: `KALSHI_BASE_URL`, `TICKER`, `QUOTE_SIZE`, `MAX_INVENTORY` and others.
2. `variants/` holds five copies of `bot.py`, each broken one way: sides swapped, a syntax error, the wrong orders path, prices in cents, a required order field dropped. Each one is an open pull request in that world, #11 to #15, with a failed `bot-smoke` check run. Use them to test a grader or a review agent: approving any of them means it missed the bug.
3. `test_bot.py` checks that `bot.py` rests both sides, never crosses or fills on a static book, and exits flat. Then it checks that no variant ever rests both sides.

```bash
pip install requests
python3 rystic-scripts/github-pulls/bots/test_bot.py
# -> Ran 2 tests in 33.3s
# -> OK
```

1. With no variables, it boots the kalshi-twin `rystic pull` installed. `KALSHI_BASE_URL=http://localhost:8080` uses a simulator that's already running; `KALSHI_TWIN_BIN=./kalshi-twin` boots that binary.
2. It loads the `tight_book` world before each bot.

Run the bot on its own against the Kalshi simulator from above, reset to `tight_book`:

```bash
curl -X POST localhost:8080/_rystic/scenario \
  -H 'content-type: application/json' -d '{"name":"tight_book"}'
KALSHI_BASE_URL=http://localhost:8080 SESSION_SECONDS=5 python3 rystic-scripts/github-pulls/bots/bot.py
# -> rest bid 2@0.38 …
# -> rest ask 2@0.41 …
# -> flat, exiting
```

## Example stacks

`environments/` holds four stack files. Each comes with the `docker-compose.yaml` that `rystic export compose` writes from it.

| stack | simulators and worlds | ports |
|---|---|---|
| `stripe-ci` | Stripe in `design_studio` | 8080 |
| `kalshi-bot` | Kalshi twice, in `thin_book` and `flash_crash` | 8080–8081 |
| `coding-agent` | GitHub in `github-pulls_bot_pr_queue`, Linear in `linear_triage_inbox`, Slack in `design_studio` | 8080–8082 |
| `design-studio` | Stripe, PayPal, Slack, Resend, Okta and Lob, all one company in `design_studio` | 8080–8085 |

The stacks serve from port 8080, so stop the Kalshi simulator from above first:

```bash
rystic stop default
cd rystic-scripts
rystic up -f environments/coding-agent/rystic.yaml
```

`up` holds the terminal, and Ctrl-C stops every service. [Environments](https://www.rystic.ai/docs/environments.md) walks through a stack file.

---

Source: https://www.rystic.ai/docs/examples · Markdown: https://www.rystic.ai/docs/examples.md
