Historical note, August 2026: This article records the 3.2.4 release. CLI commands remain current, but exact terminal output can differ in later builds.
Rebind has always had a serious runtime: Luau on a native core, running your hooks against the live input stream. 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
runtime the app uses. It installs with Rebind and lands on your PATH; there's
nothing separate to download.
Here's the whole surface:
$ rebind
rebind — build, run, and publish Rebind packages
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 Rebind (alias: r)
rebind exec <code> one-off chunk in Rebind (alias: x)
rebind repl interactive session
rebind package [path] build the .rbp archive (alias: p)
rebind publish [path] upload for marketplace review
rebind preview [path] preview the marketplace listing locally
rebind import <file> install a .rbp package (alias: i)
rebind export <path> build a .rbp package (alias: e)
rebind whoami session, device, transport (alias: w)
rebind ui open the control panel as an app window
Single-letter aliases, because you'll be typing these a lot.
Zero to running
$ rebind new hello
created hello/
rebind.toml main.luau README.md .gitignore
next: cd hello && rebind run
The scaffold is deliberately tiny, a manifest and an entry. The manifest also
carries a commented-out [marketplace] block, so the publishing fields are
discoverable without reading the docs:
[package]
name = "hello"
version = "0.0.1"
min_sdk = "3.0.0"
# Uncomment to list on the marketplace (`rebind publish`).
# [marketplace]
# summary = "One line, shown in the grid."
# category = "utility"
# icon = "assets/icon.png"
# # product-page gallery, in order — images and video, up to 8
# media = ["assets/shot-1.png", "assets/demo.mp4"]
# private = false
# drm = false
#
# [marketplace.price]
# one_time = 15.00
# monthly = 5.00
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 runtime, 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 runtime
emits about a script, from its own Log.* output to lifecycle notices, hook
errors, bind errors, and runtime kills, is now published on that script's own
channel. rebind run streams exactly your script's channel: no engine
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 full labeled firehose back when you need everything.)
rebind repl: a live session inside the runtime
rebind repl connects you to a persistent scratch runtime inside the Rebind
engine: the full SDK, real input and output, state that survives between
lines:
$ rebind repl
rebind repl — connected (software) · ⌃D to exit
»
The banner names the transport your session is on, software or hardware.
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. When you want to know what an SDK call actually returns, this is the shortest path to the answer.
rebind exec is the same runtime for one-shots and shell pipelines:
$ rebind exec 'HID.MoveTo(500, 500) print(System.Screen())'
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. If a script declares exec and forgets net, the Net.* use fails at
check time with the exact line, instead of at runtime in front of a user. Two
permissions are enforced today, net and exec. A script that declares no
permission= line holds every permission; declare permission= to narrow it.
rebind lint is new: a Luau linter with a standard library definition for the
entire Rebind SDK, so HID, Log, your hooks, and friends all resolve. It
catches undefined globals, unused variables, and 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 share the same SDK definition, 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) and overrides the entry's modeline where they disagree.
The part 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/center.luau -- rebind run scripts/center.luau
-- scripts/center.luau: the whole file parks the cursor at a known spot
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; protection for paid packages is
handled by the marketplace at download time), and uploads it for review:
$ rebind publish
…
packaged hello.rbp (4 files, 1.4 KB)
overview README.md
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 use your existing Rebind
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, your tier, whether your Rebind Link is physically attached right now, and which transport the session is running on.
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 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 Documentation covers the new surface in full: the CLI reference and the package format 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 package people pay for" needed a bridge. This is the bridge. Show us what you ship.