Rebind has always had a serious runtime — Luau on a Rust core, dispatching your hooks at up to 8,000 Hz. What it didn't have was a serious developer workflow. You wrote scripts in the app's editor, ran them with a click, and read logs in a panel. Fine for a remap; cramped for anything real.
That changes with 3.2.4. The rebind CLI is a full toolchain — scaffold,
validate, lint, run with hot-reload, drop into a live REPL, package, and publish
to the marketplace — all from your terminal, all against the same relay the app
uses. It installs with Rebind and lands on your PATH; there's nothing separate
to download.
Here's the whole surface:
rebind new <name> scaffold a package (alias: n)
rebind check [path] validate a package or script (alias: c)
rebind lint [path] lint the require graph (alias: l)
rebind run [path] run via the local relay (alias: r)
rebind exec <code> one-off chunk in the relay (alias: x)
rebind repl interactive session
rebind package [path] build the .rbp archive (alias: p)
rebind publish [path] upload for marketplace review
rebind whoami session, device, transport (alias: w)
Single-letter aliases, cargo-style, because you'll be typing these a lot.
Zero to running in twenty seconds
$ rebind new hello
created hello/
rebind.toml main.luau .gitignore
next: cd hello && rebind run
The scaffold is deliberately tiny — a manifest and an entry:
[package]
name = "hello"
version = "0.0.1"
min_sdk = "3.0.0"
function OnStart()
Log.Info("hello started")
end
No comment sermons, no boilerplate config. Then:
$ rebind run
checking hello v0.0.1
ok manifest · modeline · require graph (1 module) · compile · permissions
running hello — ⌃C to stop
01:30:03 info Script started
01:30:03 info hello started
The dev loop: hot-reload and logs that are actually yours
rebind run validates first (more on check below), loads your entry into the
live relay, and streams logs. Two things make it a real dev loop:
Hot-reload. Save any source file, manifest, or asset in the package and the running instance is checked, stopped, and relaunched in place:
01:30:08 ⟳ reloaded (main.luau changed)
01:30:08 info Script stopped
01:30:08 info Script started
01:30:08 info hello v2 started
If your edit breaks validation, the previous run stays alive — you never trade a working script for a syntax error.
Scoped logs. This one runs deeper than the CLI. Every log line the relay
emits about a script — its own Log.* output, lifecycle notices, hook errors,
bind errors, runtime kills — is now published on that script's own channel.
rebind run streams exactly your script's channel: no relay diagnostics, no
other scripts' chatter, no hunting through a firehose for your one error. The
same scoping shows up in the app's log view. (--all-logs gives you the
firehose back, labeled, when you're debugging the relay itself.)
rebind repl: a live session inside the runtime
This is the feature we're most excited about. rebind repl connects you to a
persistent scratch runtime inside the relay — the full SDK, real input
output, state that survives between lines:
$ rebind repl
rebind repl — relay connected (software) · ⌃D to exit
» pos = { Input.MousePos() }
» pos
{ 512, 384 }
» HID.Mo<TAB>
HID.Move HID.MoveTo
» HID.MoveTo(500, 500)
TAB completion is queried live from the VM — it completes your own variables and tables, not just a canned list of SDK names. Unfinished chunks continue on the next line. It's the fastest way to answer "what does this SDK call actually return?" that has ever existed for Rebind.
rebind exec is the same runtime for one-shots and shell pipelines:
$ rebind exec 'HID.MoveTo(500, 500) print(Screen.Size())'
2560 1440
$ cat snippet.lua | rebind exec -
State persists across exec calls too — rebind x 'n = (n or 0) + 1' counts.
check and lint: catch it before it runs
rebind check is the contract validator, fully offline: manifest schema and
pricing rules, the whole require graph resolved the same way the runtime
resolves it, Luau compilation of every reachable module, and a permission
audit — undeclared Net.* or System.Exec use fails at check time with the
exact line, instead of at runtime in front of a user.
rebind lint is new: a real Luau linter (built on selene) with a standard
library definition for the entire Rebind SDK, so HID, Log, your hooks, and
friends all resolve. Undefined globals, unused variables, suspicious
comparisons — warnings are advisory, definite bugs block publish.
$ rebind lint
linting hello
warning: dx is assigned a value, but never used
--> main.luau:14
lint: 1 warning, 0 errors
Because check, lint, and the runtime all link the same SDK crate, there is
no drift between "what the validator accepts" and "what actually runs."
Packages grew up
Until now, a script's identity lived in a comment at the top of the file. That was fine for single files and wrong for packages. The contract is now two clean tiers:
- A bare script keeps the classic modeline — it's the only file, it owns its config.
- A package (any directory with
rebind.toml) makes the toml package truth:[package]owns name, version, andmin_sdk; an optional[runtime]section can set every modeline key — tick rate, window matching, permissions, all of it — and overrides the entry's modeline where they disagree.
The part we think you'll actually feel day-to-day: any script under a package
root inherits the package's min_sdk. So a scripts/ folder of one-off
tools needs zero boilerplate per file:
hello/
rebind.toml
main.luau
lib/util.luau -- require("lib/util") from anywhere in the package
scripts/wiggle.luau -- rebind run scripts/wiggle.luau
-- scripts/wiggle.luau — the whole file
function OnStart()
HID.MoveTo(500, 500)
exit()
end
No modeline, no manifest stanza. It inherits compatibility from the package,
keeps its own name and its own (empty) permission set, resolves requires from
the package root, and exit() ends it cleanly when the job is done.
Publishing: @you/package, versioned, from the terminal
rebind publish runs check and lint, builds the .rbp archive (a plain
gzipped tarball of your source and assets — your artifact is never encrypted
locally; DRM for protected packages is applied server-side, per-buyer, at
download time), and uploads it for review:
$ rebind publish
…
packaged hello.rbp (3 files, 1.2 KB)
uploaded @taky/hello v0.0.1 — submitted for review
Marketplace identity is now properly scoped — packages live at
@handle/name, and review happens per-version, so shipping 0.0.2 doesn't
re-litigate 0.0.1. The CLI itself holds no credentials: uploads ride the
relay's authenticated session, same as the app.
And when you can't remember what state you're in:
$ rebind whoami
@taky taky@example.com
tier developer
mcid MCID_E90400001E341DE5DA83E393AA1B4035 (not attached)
transport software
— your account, whether your device is physically attached right now, and whether the session is driving real hardware or software output.
The smaller cuts
3.2.4 accumulates fixes worth naming:
- Script-scoped errors everywhere. Hook errors, bind dispatch failures, timer/coroutine/network errors, runaway-script kills — all now land in the offending script's own log channel in the app, not just the global relay log.
- No more stuck keys on exit. A script that ends (including via
exit()) releases anything it was holding — modifiers included. exit()anddie()are global aliases forScript.Exit().- The file tree shows your whole package —
.tomland.jsonfiles appear alongside.lua/.luau, and.luauis now the default everywhere. - A permission bypass got closed.
System.ExecDetachedis now gated behind theexecpermission likeSystem.Execalways was.
Where to start
The full CLI reference and package format docs are live. If you already have Rebind installed, you already have the CLI — update to 3.2.4, open a terminal, and:
rebind new hello && cd hello && rebind run
We built this because the gap between "wrote a remap in the editor" and "maintains a real package people pay for" needed a bridge. This is the bridge. Show us what you ship.