Skip to main content
Glama

reflex

Screen-reading macros and bots for Windows, written in YAML and driven by people or by AI agents.

reflex watches a window, finds images, colours and text in it, and sends keys, clicks and gamepad input back. It can do that while the window is behind other windows and without touching your mouse, so you can keep using the PC. Scripts run step by step ("wait for the Play button, click it, attack until Victory shows"). Watchers react at frame rate ("whenever the heal icon lights up, press 4"). Both work together.

It also runs as an MCP server, so an AI agent can look at a window, act on it, and hand the fast work to a script it writes. An agent takes seconds to think. A boss telegraph lasts 300 ms. reflex is the part that doesn't think.

reflex doctor
PASS  window capture (works while covered)  384x261, 76.0 fps, p95 18.5 ms
PASS  background keys (PostMessage)         key down/up delivered without focus
PASS  background clicks (PostMessage)       click routed to the child window under the point
PASS  screen capture                        available
PASS  text recognition (OCR)                Windows OCR (en-GB)

Install

Python 3.10+ on Windows 10 or 11.

pip install -e ".[all]"     # OCR (the engine built into Windows) and the MCP server
reflex doctor               # proves capture and input work on your machine

pip install -e . gives you the engine without OCR or MCP. The engine itself is plain Python and runs anywhere. Capture and input are Windows-only.

Related MCP server: OpenSky

Five minutes

Find your window, grab a frame of it, and cut a template out of it:

reflex windows --title "My Game"
reflex capture --title "My Game" -o shot.png
reflex capture --title "My Game" -o img/play.png --region 600,580,80,40

Write a script:

name: daily claim
target:
  window: {title: "My Game"}
  input: postmessage            # background input; use sendinput if the game ignores it
steps:
  - wait_for: {image: img/play.png}
    timeout_s: 20
    as: play
  - click: $play
  - wait_for: {text: {contains: "Reward"}}
  - click: {text: {contains: "Claim"}}
  - log: "claimed"

Check it, try it without sending input, then run it:

reflex validate daily.yaml
reflex run daily.yaml --dry-run
reflex run daily.yaml
reflex run daily.yaml --at 09:00 --at 21:00      # or --every 30m

ctrl+alt+end stops any run. ctrl+alt+home pauses it. Both work from any window, full-screen games included, and stopping releases every key and button reflex was holding.

Scripts

A script is a list of steps. reflex schema prints all 29 with their fields. The ones you'll use most:

step

what it does

press: space / press: [ctrl, s]

keys in order (hold_ms, times)

chord: [ctrl, shift, esc]

keys together

type: "gg ${name}\n"

text, any characters

click: [x, y] / click: $found / click: {image: x.png}

click a point, a stored match, or something it finds now

drag, scroll, move, right_click, double_click

the rest of the mouse

wait: 500

pause (watchers keep running)

wait_for: <condition> / wait_gone: <condition>

with timeout_s, as, on_timeout: fail | continue | [steps]

find, read_text

look once and store the result in a variable

if / repeat

repeat: {times: N}, {while: c}, {until: c}, {forever: true}; break, continue

set, call, log, stop, fail, screenshot

variables, functions, output

stick: left

gamepad stick (see below)

Conditions are screen checks, expressions, or both:

{image: img/boss.png}                              # template match (multi-scale, transparent PNGs)
{image: {template: img/coin.png, count_min: 3}}    # at least three on screen
{text: {region: [20, 20, 160, 30], number_below: 300}}   # "HP 240/1200" -> 240 < 300
{color_search: {rgb: [255, 40, 40], region: [0, 0, 1280, 80]}}
{stable: {for_ms: 800}}                            # the screen stopped moving
"${battles < 20 and gold > 500}"
{any_of: [...]}  {all_of: [...]}  {not: ...}

Expressions use ${...}: arithmetic, comparisons, and/or/not, fields of stored matches (${loot.x + 10}) and a few functions (min max abs round int float str len). They're evaluated against a whitelist, so a macro file can't run code.

Mistakes are caught before the script runs, with their location:

FAIL farm.yaml
     farm.yaml: steps[3]<start battle>.clik: a step needs exactly one action, found ['clik'] (did you mean 'click'?)

The validator catches unknown key names (in steps and watcher actions), missing template files, malformed expressions and calls to undefined functions. Values built from ${...} variables can only be checked when they run. Runs are bounded too: a script stops at timeout_s (default one hour), and every wait_for has its own timeout.

humanize: {jitter_ms: 40, click_jitter_px: 2} adds small random delays and click offsets. examples/scripts/auto_battle.yaml uses most of the above.

Watchers

Watchers are the reactive half: a condition, the input to send, and rules for how often. They run on every frame a script grabs, so a script can be waiting for a loading screen while a watcher closes popups.

watchers:
  - id: close_popup
    kind: on_match
    cooldown_s: 1
    params:
      when: {predicate: text, region: [300, 200, 400, 60], contains: "Daily bonus"}
      do: [{click: [680, 215]}]
    expect: {kind: gone, within_ms: 800}     # check the click worked
    trip_after_unverified: 3                 # three misses in a row: stop pressing

After each fire, expect checks that the screen changed the way it should. If the input keeps doing nothing (wrong window, game lagging), the watcher trips instead of spamming. A watcher file on its own runs with reflex run watchers.yaml --title "My Game" --duration 30m.

Several windows at once

reflex run farm.yaml --title "Client" --all-windows

One copy per matching window, in parallel. Background capture and input are what make this work: no instance needs focus.

Recording

reflex record --title "My Game" -o farm.yaml --images

Play normally, then press ctrl+alt+end. You get an ordinary script you can edit. Typing becomes one type step using your real keyboard layout, held keys keep their hold time, press-move-release becomes a drag, and pauses become wait steps.

With --images, each click also saves a 48 px crop of what was under the pointer and clicks that instead of a fixed pixel, so the script survives the window moving. Input reflex sends itself is never recorded.

For AI agents (MCP)

claude mcp add reflex -- reflex mcp --base-dir C:/path/to/macros

19 tools. screenshot returns the window as an image (covered windows too) with the scale to map coordinates back. find_image, find_text, read_text and find_color look things up. click, press, type_text, drag and scroll act, in the background by default. save_template turns a region the agent can see into an image condition.

Then the part that matters. schema, validate_script and run_script (or watch for watchers) let the agent write a script and run it in the background; the call returns a run id at once. The agent polls run_status for the log and result and calls stop_run when it's done. dry_run reports the input a script would have sent.

The tools are plain methods on reflex.agent.ReflexTools, so other agent frameworks can call them without MCP.

Python

from reflex.backends.windows import WindowSource, PostMessageSink, find_window
from reflex.vision import find_image
from reflex.ocr import read_text

win = find_window(title="My Game")
frame = WindowSource(win).grab()              # BGR numpy array of the client area
hits = find_image(frame, "img/coin.png", max_results=10)
hp = read_text(frame, (20, 20, 160, 30)).text
PostMessageSink(win).click(*hits[0].center)

Scripts: reflex.script.load_script() and run_script(). Watchers: reflex.runtime.ReflexRuntime. Predicates, recipes and expectations are plug-ins (reflex list), and other packages can register their own through the reflex.plugins entry point.

Input: which one to use

postmessage (default)

sendinput

needs the window focused

no

yes (it waits for focus rather than typing elsewhere)

moves your real cursor

no

yes

works while you use the PC

yes

no

works with

most apps, launchers, emulators, many games

games that read raw input or DirectInput

If clicks or keys do nothing with postmessage, switch to sendinput.

Gamepad: target: {gamepad: true} adds a virtual Xbox 360 controller. pad_a, pad_rt, pad_up and the rest work anywhere a key name does, and stick: left with x/y moves a stick. It needs vgamepad and the ViGEmBus driver, which you install yourself.

Limits, and what reflex won't do

  • Minimized windows can't be captured. There's nothing to render. Covered is fine. reflex raises an error rather than handing your conditions black frames.

  • A locked PC refuses screen copies and real input. Window capture and background input keep working.

  • A few games render black to window capture. reflex notices and falls back to copying the screen area, which needs the window visible.

  • reflex sends ordinary Windows input and makes no attempt to hide from anti-cheat. No kernel drivers, no detection evasion, and that won't change. Many online games ban automation in their terms; check before running a bot on an account you care about.

Development

pip install -e ".[all,test]"
pytest                       # the Windows tests drive real windows the suite creates
REFLEX_LIVE_INPUT=1 pytest   # also tests SendInput, which moves your cursor

The tests never touch your own windows. reflex.backends.windows.testwindow makes a real Win32 window that paints known colours and records every message it gets, so capture and input are tested end to end. The replay fixtures are drawn by a script, not captured from a game.

MIT licence.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to autonomously control Windows 11 and 10 desktops via sub-10ms screen capture, native UI Automation element inspection, and zero-lag keyboard and mouse input. Combines a visual plane with a semantic plane and stall detection so agents can operate real applications reliably without vision-only guessing.
    348 npm
    141 PyPI
    5
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to inspect and drive native Windows desktop applications (WinForms, WPF, WinUI 3, and WebView2 hybrids) through Microsoft UI Automation by capturing interactive control snapshots, then clicking, filling, and asserting on elements via ephemeral refs or semantic screen-map targets. Mutating actions are dry-run by default and gated behind explicit arming, allow-lists, and a kill switch for lawful, assisted QA testing.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to drive Windows desktop automation — opening and interacting with applications, native UI controls, keyboard/mouse input, files, processes, clipboard, and Chrome/Edge browsing via UI Automation and CDP. All actions pass through a safety policy with human approval, emergency stop, and deterministic verification.
    MIT