Scripting overview
Scripts are small programs that live inside the launcher. They can fetch things from the web, print lines into the log, post notifications, pin a status line in the header, or become new terminal commands. Write them in Lua 5.4 or JavaScript (QuickJS). Both languages get the same API.
Your first script
Section titled “Your first script”script new hn # hn.lua from an RSS template, opens the editorscript new feed.js # JavaScript instead (or: script new feed js)script new weather --cmd # a command template that takes argumentsOr tap New script in the script manager (scripts). Then:
script run hn # run it nowscript every hn 30m # and every 30 minutesThe fastest start is the script library: script lib lists 15 ready-made
scripts and script install weather installs one with sensible timers.
Lua or JavaScript?
Section titled “Lua or JavaScript?”| Lua 5.4 | JavaScript (QuickJS) | |
|---|---|---|
| Network calls | block (fine, scripts never run on the UI thread) | async: await http.get(…); Promise.all works |
| JSON | json.encode / json.decode |
built-in JSON |
log(…) or print(…) |
log(…), console.log, console.error |
|
| Arguments | args (table), argline (string) |
args (array), argline (string) |
| Top level | return value prints it |
return value prints it; top-level await works |
Pick whichever you like; the bundled library has both.
Running scripts
Section titled “Running scripts”A script runs:
- on demand:
script run <name>, the Run button, orCtrl+Rin the editor, - on an interval:
script every <name> 30m(15m,2h,1d, a number of minutes, oroff), - at daily times:
script at <name> 08:00,18:30(oroff), - as a command, if it is marked
@command, - on an event new, if it declares
@on.
Intervals have Android’s 15-minute minimum. An interval and daily times can be combined. Setting a
timer turns a disabled script back on; script off <name> disables it without losing the timers.
script ls shows every script with its timers, whether it is a command, notification state and the
last run’s result. The script manager shows the same: new one line per script
(how it runs and when it last ran, red if that failed) with its on/off switch; tap a script for
Run, Edit, Export, notifications, timers and Delete.
The script’s return value is printed into the log (tables and objects as JSON). Each run gets a
── name · 22:40 ── header and the script’s own colour. When a script fails, the error line is
tappable and opens the editor at the failing line.
One-liners
Section titled “One-liners”lua return 6 * 7js [1, 2, 3].map(x => x * 2)= 2^10lua and js evaluate a snippet and print its value. The Lua REPL tries return <code> first, so
expressions work without return.
Notifications
Section titled “Notifications”notify(title, body, ctx) only posts when notifications are on for that script:
script notify hn onWith notifications off, notify does nothing visible, but the script’s log lines still show the
content. If Android blocks Kolahal’s notifications, a “tap to allow” line appears (once a day). See
Notifications.
Status lines
Section titled “Status lines”status(text) pins one line into the header for this script, like weather ▸ Pune: ☀️ +27°C.
status(nil) (or null in JS) clears it. Only the first line is used, up to 80 characters.
status # list status linesstatus clear weather # remove one (or: status clear all)Tapping a status line runs its script again. header.status off hides them all.
Command scripts
Section titled “Command scripts”Add @command in a comment and the script becomes a terminal command named after the script.
@desc gives it a one-line description in help and in suggestions.
new @usage lists its arguments, written like the usage lines in help: [a|b] is a
choice, <city> a value you type. The words in it are suggested as you type (xkcd offers
xkcd random), and help shows the line.
-- weather.lua-- @command-- @desc current weather: weather [city]-- @usage [<city>]local city = argline ~= "" and argline or "London"return (http.get("https://wttr.in/" .. city .. "?format=3").body:gsub("%s+$", ""))weather parisIn JavaScript use // @command and // @desc …. Arguments arrive in args and argline. A
command script run from the prompt may also call launcher.run. When
the same script runs on a timer, it gets no arguments, which is how most library scripts tell the
two cases apart.
A command that takes no arguments runs as soon as you have typed enough of its name, like the built-in
ones, when launch.instant is all. That’s a script with an empty @usage line, or one that never
reads args or argline. A script that reads them but has no @usage line always waits for Enter.
Event triggers
Section titled “Event triggers”new A script can also run when something happens on the device, by declaring one or more @on
directives:
-- @on unlock-- @on chargerThe supported events are unlock, charger, unplug, wifi, offline, notification,
boot and headset. During an event run, the script gets an event object describing what
happened (at least its type). Check the in-editor docs for the exact fields of each event.
-- @on charger-- @on unpluglocal b = device.battery()status((b.charging and "charging " or "on battery ") .. tostring(b.level) .. "%")Sandbox and limits
Section titled “Sandbox and limits”Scripts run in a separate :script process, so a crashing engine can’t take down the home screen.
- 60 seconds per run, and a 64 MB memory cap. new In Lua,
string.reprefuses results over 16 MB, and a run whose memory grows past 256 MB between checks is stopped (the script process is restarted). - At most 500 log lines per run; past that one line says the rest weren’t logged.
- Lua: a fresh state per run.
java,io,package,require,debug,dofile,loadfileand process-levelosfunctions are removed (os.time,os.date,os.clock,os.difftimeremain), and only text chunks can be loaded. The timeout and memory cap are enforced by an instruction-count hook that also covers coroutines, andpcall,xpcallandcoroutine.resumecan’t swallow them. A watchdog kills the script process if a C function outlives the deadline. - JavaScript: QuickJS with only the bindings in the API, a 64 MB memory limit and an evaluation timeout. There is no file, process or network access besides the API.
sleep(ms)is capped at 5000 ms per call.restart scriptsstops the script process if something is stuck. Scheduled scripts start it again.
What a script may use
Section titled “What a script may use”new Your notifications, the clipboard, the list of your apps and your local network are only
for scripts that ask, with a @uses line at the top:
-- @uses notifications, clipboard@uses |
allows |
|---|---|
notifications |
notifications.recent and the @on notification trigger |
clipboard |
clipboard.get and clipboard.set |
apps |
apps.list |
lan |
http requests to this phone or your local network (192.168.…, router pages, a home server) |
Without it, those calls stop the script with an error saying what to add.
new On Android 17 and newer, reaching your local network also needs Android’s
local network permission: run local-network once, or tap the line a blocked request leaves in the
log. Code you type at the prompt (lua, js, =) may use everything. Scripts that come from outside (a file, a backup)
arrive switched off, and script on <name> says what the script uses as you turn it on.
Links a script puts on a line (url, image, or a notification’s link) must be web links:
anything else (tel:, upi:, an app’s own link) is dropped. A line’s copy must be what the line
shows, part of it, or its link.
Moving scripts
Section titled “Moving scripts”Scripts are part of the backup file, and can be imported and exported as
single .lua / .js files or synced with a folder (script dir, script export, script import).