All posts
·Rebind

Rebind 3.2.4: Introducing the Rebind CLI

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

rebind clirebind scriptluaumacrosrebind packagerebind marketplace

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, and min_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() 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 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.

All posts