Skip to content

Quickstart: ship your first Pack

A working Pack with one Event, fired through the bridge so you can watch it apply end-to-end. You’ll go from a fresh clone to a paired bridge running the reference Adapter, then fire the Event from the CLI and read back its result.

  • Rust toolchain (stable): rustup install stable.
  • Python 3 on your PATH (the reference Adapter is a stdlib-only Python script).
  • A Twitch broadcaster account. Pairing the bridge connects it to your channel.
  • The mobrule app installed and able to run. It bundles the Dashboard and the bridge daemon. See Install mobrule.

The pack-hello-world/ directory in the platform repo is the reference Pack used by smoke tests, the smallest working example. Clone the platform repo and change into it.

Terminal window
git clone <REPO_URL> mobrule
cd mobrule/packs/pack-hello-world

Replace <REPO_URL> with the platform repository URL from your mobrule distribution.

The CLI validates Packs and fires test Events. Install it from the clone.

Terminal window
cargo install --path ../../cli

mobrule --version should now print a version. The reference Adapter shells out to mobrule pack print-hash, so the CLI must stay on your PATH.

Open pack.toml. It declares the Pack identity, an [adapter] block the bridge uses to launch the Adapter, one queue, and one Event:

manifest = 1
id = "tv.mobrule.hello-world"
name = "Hello World"
version = "0.1.0"
mpp = "2"
authors = ["mobrule"]
[adapter]
command = "python3"
args = ["-u", "adapter.py"]
restart = "on_crash"
[queues.default]
ready_after = "applied"
[events.play_sound]
summary = "Log a play-sound notification (hermetic; no audio output)."
queue = "default"
[events.play_sound.params_schema]
type = "object"
additionalProperties = false
required = ["sound_id"]
[events.play_sound.params_schema.properties.sound_id]
type = "string"
maxLength = 64
"x-mobrule-viewer-override" = true

Every Pack declares at least one [queues.<name>], and every Event names the queue it dispatches through. The [adapter] block tells the bridge what process to spawn; the bridge runs it and injects the Adapter’s auth token. For every other key, see the Manifest reference.

From the Pack directory:

Terminal window
mobrule pack validate .

On success the CLI prints the Pack id with its Event count, then the canonical Manifest hash:

ok tv.mobrule.hello-world (1 events)
manifest_hash = sha256:<64-hex>

A non-zero exit means the TOML failed to parse or a JSON Schema in your Manifest was invalid; the validator points at the offending line and column. Exit code 2 means it parsed but emitted warnings (for example, an unbounded string with no maxLength).

The bridge boots unpaired. While unpaired it refuses every Adapter connection, so pairing comes first.

  1. Launch the mobrule app. The Dashboard opens and starts the bundled bridge daemon.
  2. On the landing screen, click Connect channel and complete the Twitch flow. This pairs the bridge to your broadcaster account.
  3. Select the hello-world Pack as the active Pack.

With a paired bridge and an active Pack, the bridge launches the Pack’s Adapter itself using the [adapter] block from pack.toml. It injects the Adapter’s auth token through the MOBRULE_ADAPTER_TOKEN environment variable, so the Adapter never needs a token handed to it by hand. The reference Adapter (pack-hello-world/adapter.py) reads that variable, connects to the bridge on 127.0.0.1:7777, and completes the handshake.

You do not have to write an Adapter to finish this Quickstart; the reference Adapter is what the bridge just launched. Knowing its loop explains what you’ll see when you fire an Event.

After the hello handshake succeeds, the Adapter sends one pull per declared queue to grant the bridge a dispatch credit. For each Invocation it acknowledges receipt, applies the effect, marks it done, then re-pulls to grant the next credit:

# Handshake: hello -> hello_ack
send({"mpp": 2, "type": "hello", "pack_id": PACK_ID,
"manifest_hash": MANIFEST_HASH, "bridge_token": TOKEN})
ack = recv()
assert ack["ok"]
# Grant one dispatch credit on the queue.
send({"mpp": 2, "type": "pull", "queue": "default"})
while True:
msg = recv()
if msg["type"] == "invocation":
inv = msg["id"]
send({"mpp": 2, "type": "ack", "id": inv})
send({"mpp": 2, "type": "applied", "id": inv, "result": None})
send({"mpp": 2, "type": "done", "id": inv})
send({"mpp": 2, "type": "pull", "queue": msg["queue"]})

TOKEN comes from MOBRULE_ADAPTER_TOKEN, MANIFEST_HASH from mobrule pack print-hash. The pull frame is load-bearing: the bridge dispatches at most one Invocation per outstanding pull on a queue, and never dispatches without one. Skip the re-pull and the Adapter goes silent after the first Invocation.

For the full walkthrough, including failed, log, and heartbeat frames, see the Adapter tutorial: Python. The wire spec lives at MPP protocol.

With the bridge paired and the Adapter running, fire the play_sound Event through the bridge from the CLI:

Terminal window
mobrule dev invoke play_sound --params '{"sound_id":"coin"}'
invocation 1 queued on `default`

The bridge dispatches the Invocation to the Adapter, which logs PLAY: coin to its stderr and walks the ack/applied/done lifecycle. Read the result back:

Terminal window
mobrule dev invocations
#1 play_sound [done] queue=default

A [done] state is your first Event applied end-to-end: CLI to bridge to Adapter and back.

Channel-point redeem creation and Cue to Event mapping live in the Dashboard. To let viewers trigger this Event with channel points, create a reward and map its Cue to play_sound. See Operator install and the UI overview.