All posts
·Rebind

Rebind 3.2.4: Introducing the Rebind CLI, a Real Developer Toolchain for Input Scripting

Rebind 3.2.4 ships the rebind CLI — new, run, lint, exec, repl, package, publish. Scaffold a package, hot-reload it against the live relay, poke the SDK from an interactive session, and ship to the marketplace from your terminal.

rebind clirebind scriptluaulua macrorebind packagerebind marketplace

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, and min_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() and die() are global aliases for Script.Exit().
  • The file tree shows your whole package.toml and .json files appear alongside .lua/.luau, and .luau is now the default everywhere.
  • A permission bypass got closed. System.ExecDetached is now gated behind the exec permission like System.Exec always 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.

Back to all posts