Your first script

Python · Updated 09 November 2026

Set up, write a script class, and run it from the bwu_host GUI or a console.

Python scripts run in bwu_host, the BotWithUs native host. The launcher installs it.

Setup

  • Nothing to install to run scripts. bwu_host brings its own CPython 3.12. Write for Python 3.12.
  • Editor completion. The botwithus package ships type information (py.typed), so an editor that can see the package gives you completion and type checking. If you have the framework source, install it into your editor's environment from its folder with python -m pip install -e .[dev]. python -m pytest there runs the framework's own unit tests on a plain Python 3.11 or newer, with no game client.
  • Scripts only run inside bwu_host. The framework reaches the game through a module that exists only there, so there is no python -m way to run a script.

Where scripts live

Put your scripts in ~/.botwithus/scripts/python (%USERPROFILE%\.botwithus\scripts\python). That is the folder the bwu_host GUI lists.

The shape of a script

A script is one BotScript subclass with a @script_manifest, in a file of its own. Everything you normally need imports from botwithus.api:

from botwithus.api import BotScript, ScriptContext, script_manifest


@script_manifest(name="Hello", version="1.0", author="you")
class Hello(BotScript):
    def on_start(self, ctx: ScriptContext) -> None:
        self.ctx = ctx
        self.loops = 0

    def on_loop(self) -> int:
        me = self.ctx.self_player
        if me.has_tile:
            self.ctx.log.info("standing on %s", me.tile)
        self.loops += 1
        return -1 if self.loops >= 10 else 600


if __name__ == "__main__":
    import time

    from botwithus.core.connection import Connection
    from botwithus.core.native import default_client_pid

    pid = default_client_pid()
    if pid is None:
        raise SystemExit("no game client found")
    conn = Connection(pid)
    conn.start_script(Hello())
    while conn.running_scripts():
        time.sleep(0.5)
    conn.close()

on_loop returns the delay in milliseconds before the next call. A negative value stops the script.

One script per file, and nothing at import time

  • To list a file, the GUI imports it once and looks for exactly one BotScript class. A file with two fails to load.
  • Do no work at import time, such as attaching, walking or long setup. Put it in on_start.
  • The if __name__ == "__main__": block runs only when you start the file from a console (below). The GUI never runs it for a script class.

Running it

In the GUI. Start bwu_host with no arguments. It lists every .py file in ~/.botwithus/scripts/python by its manifest. Pick a script and a client, and start it. Each run gets a worker process of its own.

From a console.

bwu_host --py $HOME\.botwithus\scripts\python\hello.py

This runs the file directly, which is why the example ends with the __main__ block. It attaches to the first game client it finds and runs the script until it stops. With no client running, it exits with no game client found. Extra arguments end up in sys.argv: bwu_host --py hello.py a b gives ['hello.py', 'a', 'b'].

A script's INFO lines go to its run log and to the GUI, not to the bwu_host console, so a console run of Hello prints nothing there. See Logs and crash files for where to find them.

Next: Lifecycle, settings and stopping.