Luafend
Command line tool and Python library for Luafend — source protection for Lua and Luau. One package: protect a script from your terminal, from a build script, from CI, or from your own Python code.
Targets Luau, Lua 5.1 and Lua 5.4, covering Roblox, Garry's Mod, LuaJIT, embedded Lua and standalone hosts.
Install
pip install luafend
Or without installing it into your project:
pipx install luafend
Requires Python 3.9 or newer. Standard library only, no dependencies.
Two ways to run the command line
Type one word — in Command Prompt or PowerShell on Windows, in Terminal on macOS and Linux — and Luafend opens its own shell. Inside it, every command starts with a slash:
luafend
luafend ~ > /login
luafend ~ > /obfuscate main.lua --mode maximum
luafend ~ > /exit
Tab completes commands, files, flags and values. Typing -- suggests the flags that
command accepts. /help lists everything.
Or drop the slash and run the same command straight from your own shell. Nothing opens, the job runs, you get your prompt back — this is the form a build script or CI job uses:
luafend login
luafend obfuscate main.lua --mode maximum --lua luau
Getting started
luafend login # opens your browser, no password typed
luafend obfuscate main.lua # writes main.obf.lua
The first run asks for the mode and the Lua version and remembers both. Every run after that is silent, and prints what it used and where the setting came from.
Use it from Python
The short way: set it on the package, then run.
import luafend
luafend.key = "luf_YOUR_KEY"
luafend.mode = "maximum"
luafend.lua = "luau"
result = luafend.run('print("hello")')
if result.ok:
print(result.output)
else:
print(result.error)
Set luafend.out = "main.obf.lua" and the file is written for you as UTF-8. Set
luafend.source and run() needs no argument at all.
These settings live on the package, so there is one set of them per program. That is right for a script and wrong for concurrent work; for a web server use a client, which carries its own:
client = luafend.Luafend(key="luf_YOUR_KEY")
client.mode = "maximum"
result = client.run(source)
Or describe a single build one line at a time:
import luafend
job = luafend.Job()
job.key = "luf_YOUR_KEY"
job.source = 'print("hello")'
job.mode = "maximum"
job.lua = "luau"
result = job.run()
if result.ok:
print(result.output)
else:
print(result.error)
run() never raises. Every Result also carries the measurements, with no flag to switch
on:
print(result.size, "bytes") # protected script
print(result.source_size, "bytes") # what you sent
print(result.seconds) # how long it took
print(result.timestamp) # when it finished, UTC
print(result.quota_left) # obfuscations left this month
Set job.out = "main.obf.lua" and the file is written for you as UTF-8, line endings
intact.
If you just want the string and are happy for a failure to stop the program, there is a
one line form that raises luafend.LuafendError instead:
print(luafend.obfuscate('print("hello")', key="luf_YOUR_KEY", mode="maximum", lua="luau"))
Leave key out and LUAFEND_TOKEN is used, then the account from luafend login.
mode is lite, balanced or maximum; lua is luau, lua-5.1 or lua-5.4.
Commands
| Command | What it does |
|---|---|
login |
Sign in through your browser |
logout |
Sign out and revoke this machine's token |
whoami |
Name, email, sign-in provider, plan |
billing |
Plan, credits, monthly usage and limits |
obfuscate <file> |
Protect one script |
batch <pattern> |
Protect many files at once |
settings |
Show, reset or write project settings |
cd [folder] |
Show or change the current folder |
update |
Check for a newer version and install it |
docs |
Open the documentation |
version |
Show the CLI version |
Options
| Flag | Meaning |
|---|---|
--mode lite|balanced|maximum |
How much protection. Lite is fastest, Maximum compiles the script into its own virtual machine |
--lua luau|lua-5.1|lua-5.4 |
Which Lua the result has to run on |
-o, --out <path> |
Where to write. A folder for batch, a file for obfuscate |
--pick |
Choose mode and version again instead of using the saved ones |
--force |
Overwrite an existing output file |
--stdout |
Print the result instead of writing a file |
--all |
batch only: rebuild every file, including unchanged ones |
Without --lua, the version is guessed from the file: a .luau extension or Roblox
globals such as game:GetService mean Luau. The guess is always printed.
Protecting many files
luafend batch "src/**/*.lua" -o dist
* and ? never cross a path separator, ** matches any number of directories. A plain
folder means every .lua and .luau inside it. Files that have not changed since their
last build are skipped, so a rebuild does not spend your monthly quota twice. Requests are
paced to your account's rate limit.
Project settings
luafend.json pins how a project builds, so everyone working on it gets the same result:
{
"mode": "maximum",
"lua": "luau",
"out": "dist",
"include": ["src/**/*.lua"]
}
It is searched for upwards from the current folder, so any subfolder of the project
behaves the same. With an include list, plain luafend batch is enough.
luafend settings shows what applies and where each value came from. luafend settings
init writes a starter file next to your token, which then applies everywhere you have no
project file.
Signing in
login never asks for a password. It opens a consent page in the browser where you are
already signed in and waits for approval. The token it receives is stored in your OS
config directory, never in the working directory:
| Windows | %APPDATA%\luafend\config.json |
| macOS | ~/Library/Application Support/luafend/config.json |
| Linux | ~/.config/luafend/config.json |
On macOS and Linux the file is created with mode 0600. logout revokes the token on the
server as well as deleting it here.
The token is only ever sent to the official Luafend API or to a server on your own machine.
In CI
Set LUAFEND_TOKEN instead of signing in. It takes priority over the config file, so no
login step is needed:
- run: pip install luafend
- run: luafend batch "src/**/*.lua" -o dist --mode balanced --lua luau
env:
LUAFEND_TOKEN: ${{ secrets.LUAFEND_TOKEN }}
Without a terminal, nothing is ever asked interactively: a missing setting fails with the flag it needs named, rather than blocking on a prompt nobody can answer.
Exit codes:
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Error |
2 |
Not signed in, or the session expired |
3 |
Out of credits, or the plan does not allow that mode |
Environment
| Variable | Effect |
|---|---|
LUAFEND_TOKEN |
Use this token instead of the stored one |
LUAFEND_API |
Point at a different API host, for local development |
NO_COLOR |
Turn colour off |
LUAFEND_ASCII=1 |
Plain ASCII instead of box drawing characters |
Links
- Website — https://bypass.onl
- Documentation — https://bypass.onl/docs
- The same client for Node.js — https://www.npmjs.com/package/luafend
Release files for luafend 2.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| luafend-2.4.0.tar.gz | 32.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| luafend-2.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 66.1 kB
Release files / luafend-2.4.0.tar.gz
| Download URL | luafend-2.4.0.tar.gz |
|---|---|
| Size | 32.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
522968eb60ebd9f73700e3fcd1eab51f05bdbbac940863547321a312633f4e3b
|
|
BLAKE2b-256 checksum How to use checksums |
5960a8b9155aa6e37d3b69f507a3cc6d943fd5fb69cb26124c27dccb06d72649
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.0
|
Release files / luafend-2.4.0-py3-none-any.whl
| Download URL | luafend-2.4.0-py3-none-any.whl |
|---|---|
| Size | 33.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8ca4fc907d544b404145a1db4948e3265e4ffddc29406847e17b4f97804d542e
|
|
BLAKE2b-256 checksum How to use checksums |
8e0ee6f60dd02282626afa9e748976030395856359531f41bf6c2119c9d864b2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.0
|