Skip to content

Quickstart: ship your first Pack

A working Pack with one Event that fires when a viewer redeems channel points. You’ll go from a fresh clone to seeing your Event applied end-to-end in roughly 15 minutes.

  • Rust toolchain (stable) — rustup install stable.
  • A Twitch broadcaster account (for OAuth).
  • mobrule CLI installed locally: from a clone of the platform repo, run cargo install --path cli.
Terminal window
git clone https://example.invalid/mobrule.git
cd mobrule/pack-hello-world

The pack-hello-world/ directory in the platform repo is the reference Pack used by smoke tests; it is the smallest possible working example.

Open pack.toml. You’ll see the required top-level keys, at least one queue, and an [events.*] table:

manifest = 1
id = "org.mobrule.hello-world"
name = "Hello World"
version = "0.1.0"
mpp = "1"
[queues.default]
ready_after = "applied"
[events.play_sound]
summary = "Log a play-sound notification."
queue = "default"

Every Pack must declare at least one [queues.<name>], and every Event names the queue it dispatches through. For every other key, see the full Manifest reference.

From the Pack directory:

Terminal window
mobrule pack validate .

Expected output (last line):

manifest OK: org.mobrule.hello-world (manifest_hash=<sha256>)

A non-zero exit means the TOML failed to parse or one of the JSON Schemas in your Manifest was invalid; the validator prints line/column references.

Terminal window
mobrule-bridge --pack ./pack-hello-world

Expected log lines (abridged):

bridge: loaded Pack org.mobrule.hello-world (hash=…)
bridge: TCP listener on 127.0.0.1:7777
bridge: waiting for Adapter hello

The reference Adapter ships at pack-hello-world/adapter.py. Its core lifecycle is: handshake, grant a dispatch credit per queue with pull, then acknowledge each Invocation and re-pull.

import json, socket
s = socket.create_connection(("127.0.0.1", 7777))
f = s.makefile("rwb", buffering=0)
def send(o): f.write((json.dumps(o) + "\n").encode())
def recv(): return json.loads(f.readline())
send({"mpp": 2, "type": "hello", "pack_id": "org.mobrule.hello-world",
"manifest_hash": "<your-hash>", "bridge_token": "<token>"})
ack = recv()
assert ack["ok"]
send({"mpp": 2, "type": "pull", "queue": "default"})
while True:
msg = recv()
if msg["type"] == "invocation":
inv = msg["id"]
send({"mpp": 2, "type": "applied", "id": inv, "result": None})
send({"mpp": 2, "type": "done", "id": inv})
send({"mpp": 2, "type": "pull", "queue": msg["queue"]})

The pull frame matters: the bridge dispatches at most one Invocation per outstanding pull on a queue, and never dispatches without one. Forget to re-pull and your Adapter goes silent after the first Invocation.

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

Streamer-side Twitch OAuth, channel-point redeem creation, and Cue→Event mapping live in the platform UI. For now, see Operator install. For the Quickstart, we assume the developer is also the broadcaster.