Most of Cherry Pi's harness is written in Lua. It manages project layers, jobs, policy checks, receipts and the tools Picache uses. The terminal desktop is written in Go: it renders the pane tree and handles the keyboard and pointer. Both are packed into Cherry Pi's executable. A Lua script can work with that running desktop through picache.
Lua appears at several levels here. It runs the harness itself. It lets you script your own terminal workspace with picache do. Picache can also run a Lua script in code mode with a limited set of tools. The language is shared; what each script may do depends on who started it and where it runs.
From your own pane, a script can open a test pane beside the code, wait for its process to exit and bring the failure into view. It can return a small, machine-readable account of what happened. It works on the live desktop, with the panes and programs already there. The same Lua process can also open a window in Superlogical Rex and read that window's screen back.
The shape of picache do takes a cue from Rex's Lua scripting. Cherry Pi applies its own layer context and caller identity to each workspace call. The Lua runtime that governs those calls is also part of Cherry Pi itself; this is a way to work with the machinery underneath the TUI, not a separate macro recorder. In a picache do script, use picache to open, arrange or read panes in Cherry Pi. Use rex to do the same in a connected Rex session. The two calls can sit side by side in the same Lua script.
A test beside the code
Save the following as tests.lua in a project layer. It opens a terminal pane on the right, runs the project's Python tests and waits for the process to finish:
local P = picache
local tests, err = P.split{
side = "right",
label = "TESTS",
focus = false,
command = "python3 -m unittest -v",
}
if not tests then error(err, 0) end
local done, why = P.wait(
"terminal.child_exited", { pane_id = tests.pane_id }, 600)
if not done then error(why, 0) end
if done.exit_code ~= 0 then
assert(P.zoom(tests.pane_id, true))
end
return { pane_id = tests.pane_id, exit_code = done.exit_code }
Run it from a Cherry Pi terminal with picache do tests.lua. The recording uses --yes in its disposable project so the tape can run unattended; when you run the script yourself, leave that flag out and answer any confirmation it needs.
focus = false leaves the keyboard in the original pane. P.wait watches the new pane's terminal.child_exited event for up to 600 seconds. The event carries the process exit code. On a failure, P.zoom brings the test output forward; the terminal keeps that output after the command exits. The returned table prints as JSON, including the pane ID you can use in a later call.
Run a script, pass it a value
picache do accepts a Lua file, an inline expression or standard input. Arguments after the script arrive in picache.args. A plain key=value is converted to a number or boolean when it reads as one; --args accepts a JSON object when you need nested values or an exact string.
picache do tests.lua
picache do -e 'return picache.session.view()' --json
picache do - < layout.lua
picache do layout.lua name=dev count=2
picache do layout.lua --args '{"name":"true"}'
A returned value prints as JSON. A script that returns nothing prints nothing. When a call fails, it gives Lua nil and a reason, which is why the test script checks err and why before continuing. A Lua error prints the message and ends the script with an error status.
A Rex window from Cherry Pi
picache.* talks to Cherry Pi. The same script also has rex (and picache.rex), which talks to a Superlogical Rex server over its Unix socket: the same calls as rex do. Cherry Pi's layer and caller identity still apply to every picache call. Rex applies its own to rex.call.
Save the following as rex-panes.lua. It creates a Rex session with two terminal blocks, waits for them to draw, then asks Rex for the hello block's screen:
local rex = picache.rex
local shell = "com.superlogical.terminal.shell"
local created, err = rex.call("session.create", {
label = rex.args.label or "from-cherrypi",
initial_windows = {{
window_label = "cherry π",
layout = rex.layout.horizontal(0.5,
rex.layout.block{
flavor = shell,
label = "hello",
options = {
command = { "sh", "-c",
"printf '\\n cherry π opened this Rex window\\n\\n'; exec sleep 25" },
exit = { on_completion = false },
},
},
rex.layout.block{
flavor = shell,
label = "uname",
options = {
command = { "sh", "-c",
"uname -sm; echo; echo this block is a Rex pane; exec sleep 25" },
exit = { on_completion = false },
},
}),
}},
})
if not created then error(err, 0) end
picache.sleep(2)
local hello = created.initial_windows[1].block_ids[1]
local screen = rex.block.call("terminal", "format", { block_id = hello, trim = true })
return {
session_id = created.session_id,
window_id = created.initial_windows[1].window_id,
block_ids = created.initial_windows[1].block_ids,
screen = screen,
}
Run it from a Cherry Pi terminal with picache do rex-panes.lua. You’ll need a Cherry Pi build with the Rex bridge and a running Rex server. The script returns the new window and block IDs along with the text it read from the hello pane.
rex.layout.horizontal and rex.layout.block describe how Rex should arrange the window. rex.block.call("terminal", "format", …) reads a terminal block’s screen. If Rex is unavailable, the call returns nil and a reason.
Cherry Pi inside Rex
Open Cherry Pi in a Rex terminal and you have two pane layouts on screen. Cherry Pi keeps its chat, source and test panes inside that terminal. Rex can split the space around it and run its own programs beside it. A Lua script running in Cherry Pi can work with both layouts.
In the recording, a request in Picache Chat starts a prepared Lua workflow after approval. The workflow opens source and tests in Cherry Pi, then adds repair and review panes in Rex. It reads back the terminal output from Rex so the script can check what actually ran there.
Commands and panes have different jobs
Some commands only need an answer. picache.exec runs a local program without opening a pane. Its argument list goes directly to the program, so a file name with spaces needs no shell quoting. It returns the output and exit status:
local result, err = picache.exec(
{ "git", "status", "--short" }, { timeout = 10 })
if not result then error(err, 0) end
return { code = result.code, files = result.stdout }
Use a pane when the program's screen matters. A test run, server or interactive tool can stay visible after the script ends. The test example uses a pane so you can see the failure in context, keep its output and return to it. The command and the Lua script have separate lifetimes: picache do finishes, while the pane remains in the layer.
React to the workspace
P.wait is for one event. A longer-running script can register picache.on to handle each matching event, or use picache.after and picache.every for timed work. For example, this watch logs terminal exits until the time limit ends:
picache.on("terminal.child_exited", function(ctx, event)
picache.log("pane " .. event.pane_id ..
" exited " .. event.exit_code)
end)
picache do watch.lua --for 30s
The event stream starts with the script, so a split followed immediately by a wait can still catch a fast process exit. Handlers and timers run one at a time in the script. A script can also call picache.session.view() to read the pane tree and live run IDs. The longer TUI article shows what those arrangements look like from the operator's side.
Whose script runs?
picache do runs locally as the caller. In your shell, it has Lua's standard library, including file and process operations. In an agent's confined pane, it stays within that pane's confinement. Calls through picache.* carry the pane's token: the harness checks the caller, the requested effect and the available grants, then leaves a receipt. A plain Lua file operation or shell command runs outside those workspace verbs, with the access the calling process already has.
Cherry Pi also has picache script for Lua that you did not write or fully trust. It runs in a capsule with a limited set of workspace verbs and a call budget. Those two entry points have different authority, so choose the one that matches who wrote the script. The manual has the detailed comparison.