If you're new to Luau, check out seal's Luau Book and Luau's official documentation.
Although seal provides some builtin globals (such as p, pp, channel (in a child thread), and script), most features are in the standard library. You can import standard libraries like so:
local fs = require("@std/fs")
local colors = require("@std/colors") -- (the most important one tbh)
-- some libs are nested:
local http = require("@std/net/http")
local prompt = require("@std/io/prompt")With Luau Language Server, you should be able to see documentation, usage examples, and type definitions for each library when you hover over them in your editor.
For convenience, all of seal's type definitions and documentation are located in your project's ./.seal/typedefs directory, and for script codebases, in your home directory's ~/.seal/typedefs folder.
If you want to see documentation on the web, check out the references folder in this repository.
You can use seal to write programs that interact with your filesystem, get and send things on the internet, connect other programs together (glue scripts), do multiple things at the same time, and more. In the future, you'll be able to write cross-platform GUI apps directly in seal!
Some fun things you could write with seal
- Automatically launch a program when you modify anything in a specific folder.
- Automatically back up everything in a folder every few hours to your server.
- GUI automation with programs like
kdotoolor AutoHotKey. Why write unreadable AHK when you can write Luau to write and run the AHK for you? - Custom touchpad gestures on Linux (.. I've done this before; need to port it to seal)
- A background script that reminds you to drink water every hour.
local fs = require("@std/fs")Examples
local content = fs.readfile("./myfile.txt")
print(content)Without erroring if the file doesn't exist or isn't found:
local content, result = fs.file.try_read("./myfile.txt")
if content then
print(content)
elseif result == "NotFound" then
print("file not found")
elseif result == "PermissionDenied" then
print("permission denied")
endWrite files to a path:
local seally_path = fs.path.join(script:parent(), "seally.txt")
fs.writefile(seally_path, "did you know seals can bark?")Write a new directory, creating any directories needed, without erroring if one doesn't exist:
fs.makedir("./src/elements", {
create_missing = true,
error_if_exists = false,
})Write a new directory tree, with content:
fs.writetree("./tests", fs.tree()
:with_file("run_tests.luau", run_tests_src)
:with_tree("cases", fs.tree()
:with_file("case1", cases[1])
)
)List all files in a directory, recursively:
local files = fs.listdir("./src", true)Loop over paths in a directory (not recursively):
for _, path in fs.listdir("./src") do
if fs.is(path) == "File" then
print(fs.readfile(path))
end
endFiles, erroring if the file doesn't exist:
fs.removefile(fs.path.join(script:parent(), "seally.txt"))Without erroring if the file doesn't exist/permission denied:
local removed = fs.file.try_remove("./idk/some_file.txt")Directory trees (empty or not):
fs.removetree("./src/old")Without erroring:
local removed, result, partial = fs.dir.try_remove("./src/old")
if result == "NotFound" or result == "PermissionDenied" then
error(print(`can't remove dir cause {result}`))
end
if partial then
error(`uh oh, directory partially removed? {partial}`)
endContains useful functions for getting the user's home directory, cwd, project directories, normalizing and canonicalizing paths (handling Windows edge cases for you!), and of course, joining paths together in a cross-platform compatible way.
Removing files older than n weeks (fs and datetime):
Uploading a file whenever it gets added to a folder (file watching):
local http = require("@std/net/http")Examples
local response = http.get {
url = "https://jsonplaceholder.typicode.com/posts",
params = {
userId = "1",
}
}:unwrap_json()Tables passed to http.post automatically get treated as JSON!
local post_response = http.post {
url = "https://mycatlist.me/api/add_cat/post",
headers = {
Authorization = `Bearer {TOKEN}`,
},
body = {
name = "Taz",
age = 12,
}, -- pass a table? seal serializes it for you (and sets Content-Type: application/json)!
}
if not post_response.ok then
print(`uh oh, got: {post_response.status_code}`)
endLibrary for running other programs as child processes and exiting the current program (process.exit).
local process = require("@std/process")Examples
Run a quick shell command with your default shell:
local output = process.shell("seal ./cats.luau"):unwrap()
local files = process.shell("ls -l"):unwrap()Run a program directly as a child process (waits 'til it completes)
local result = process.run {
program = "seal",
args = { "./cats.luau" },
}:unwrap()Spawn a long-running child process and see what it's outputting in realtime:
-- listen to all user input on linux
local child = process.spawn {
program = "libinput debug-events --show-keycodes",
shell = "sh",
}
-- ensure seal's running in sudo (administrator mode) so we can see keycodes
local err = child.stderr:read(128, 0.25)
if err and err:match("Permission denied") then
error("must run as sudo")
end
for line in child.stdout:lines() do
print(line)
endSpawn a child process in parallel, allowing you to run multiple programs at the same time:
for _, path in fs.listdir("./input") do
process.spawn {
program = "fixer",
args = { path },
}
endHigher-level terminal prompt and validation functions.
Lower-level access to the terminal, including setting rawmode and listening for terminal events.
local prompt = require("@std/io/prompt")
local input = require("@std/io/input")Examples
local prompt = require("@std/io/prompt")
if prompt.confirm("are roses red") then
print("violets are blue")
endlocal prompt = require("@std/io/prompt")
local response = prompt.text("What's your name?")
print(`Hello {response}!`)seal is not an async runtime, and doesn't bind to tokio or similar for performance and simplicity, but it still supports concurrency and parallelism!
Although seal doesn't have a task library to make coroutine scheduling more ergonomic, you can absolutely use Luau's coroutine library for concurrency. Just keep in mind that standard library functions, including fs.readfile, time.wait, and http.get can completely block the VM. This means you can use coroutines to interleave operations, but you can't use them as an alternative to thread.spawn in terms of making an inherently blocking operation non-blocking.
local thread = require("@std/thread")seal provides access to Real Rust Threads with a relatively simple API. Each Rust thread spawns its own Luau VM, which allows you to execute Luau code in parallel.
To send messages between threads, use the :send and :read methods located on ThreadHandle (for parent threads) and channel (child threads) respectively. The regular :send/:read methods seamlessly serialize and transmit data tables for you between VMs!
For better performance, use the bytes APIs to exchange buffers without serialization overhead.
Example
-- parent.luau
local thread = require("@std/thread")
local handle = thread.spawn {
path = "./child.luau",
data = {
url = "https://example.net",
}
}
-- do something else
local res = handle:read_await()
handle:join() -- don't forget to join your handles!Child threads have a global channel exposed, which you can use to send data to the main thread:
-- child.luau
local http = require("@std/net/http")
if channel then
local data = channel.data :: { url: string }
local response = http.get(data.url):unwrap_json()
channel:send(response)
end