From c21a4ebd001ca305d862b5390f090f2ef14163cf Mon Sep 17 00:00:00 2001 From: t Date: Tue, 16 Jun 2026 19:28:54 -0600 Subject: base site: claude design plus some tweaks --- lua.html | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 lua.html (limited to 'lua.html') diff --git a/lua.html b/lua.html new file mode 100644 index 0000000..7baeba0 --- /dev/null +++ b/lua.html @@ -0,0 +1,66 @@ + + + + + +panto — Lua extensions + + + + + +
+
+
+ +panto/docs +CLI +
+ + +
+
+ +
+

Reference

Lua extensions

require("panto")

Everything beyond the core is a Lua extension — including the standard tools. An extension is a Lua file that calls into the panto.ext table to add tools, slash commands, and UI behaviour. This is how panto stays small without staying useless.

Overview

Inside an extension, local panto = require("panto") gives you the API. The panto.ext table has three entry points:

register_toolmodel-facing
Add a tool the model can call.
register_commandyou-facing
Add a /slash command to the REPL.
on / emitUI events
Hook the UI event bus to wrap or replace how things render.

All extensions share one long-lived Lua interpreter, so module-global state persists across calls. The runtime includes luv (libuv), so tool handlers can perform async I/O.

Loading extensions

Drop a .lua file into an extensions directory and it loads at startup. panto discovers extensions across three layers:

LayerLocation
base$PANTO_HOME/agent
user$XDG_CONFIG_HOME/panto/extensions/ · else ~/.config/panto/extensions/
project./.panto/extensions/

Project shadows user shadows base. Which extensions and tools actually load is gated by the [extensions] and [tools] allow/deny policies.

Name collisions abort startup

If two surviving extensions register a tool (or command) with the same name, panto refuses to start rather than pick one silently. A Lua command may not shadow a builtin like /compact.

register_tool

A tool is something the model can call. Describe it with a JSON-Schema for its arguments and a handler that returns a string result.

namestringrequired
The tool name the model invokes.
descriptionstringrequired
Shown to the model; explain when to use it.
schematablerequired
A JSON-Schema table (type = "object", properties, required) describing the arguments.
handlerfunction(input)
Receives the decoded arguments table; returns a string result. May yield (async).
.panto/extensions/echo.lualua
local panto = require("panto")
+
+panto.ext.register_tool {
+  name = "echo",
+  description = "Echo back the given message.",
+  schema = {
+    type = "object",
+    properties = {
+      message = { type = "string", description = "The text to echo back." },
+    },
+    required = { "message" },
+  },
+  handler = function(input)
+    return "echo: " .. input.message
+  end,
+}

register_command

A slash command runs locally when you type /name in the REPL. The handler runs synchronously and acts by side effect (typically writing output); its return value is ignored.

namestringrequired
Command name, without the leading /.
descriptionstringrequired
One-line summary (for listings).
handlerfunction(args)
args is the trimmed text after the command name — the empty string when none was given.
.panto/extensions/greet.lualua
local panto = require("panto")
+
+panto.ext.register_command {
+  name = "greet",
+  description = "Print a greeting. Optional args name who to greet.",
+  handler = function(args)
+    local who = args ~= "" and args or "world"
+    io.write("\n[greet] hello, " .. who .. "!\n")
+  end,
+}

Events: on & emit

panto.ext.on(name, handler) subscribes a handler to the UI event bus — the same bus the built-in TUI uses. Handlers fire in registration order, and can wrap or replace the component that renders a given moment of the transcript.

The handler receives an event object e that is valid only during the call (copy any field you need into a local — don’t read e later at render time):

e.namestring
The event name.
e.<field>
Read-only payload fields (nil when absent, so you can branch on them).
e:getComponent()
The current native component, as an opaque pass-through handle.
e:setComponent(c)
Replace the component for this boundary — pass the handle back to wrap it, or a Lua component table to replace it.

The events and the payload fields they carry:

EventPayload fields
toolindex, tool_name, id, delta, input, output
thinkingindex, delta, text
assistant_textindex, delta, text
user_messagetext
session_startversion, cwd, model
compactionsummary
custom— produced by emit
.panto/extensions/skill-badge.lualua
local panto = require("panto")
+
+-- Change how the "skill" tool renders in the transcript.
+panto.ext.on("tool", function(e)
+  if e.tool_name ~= "skill" then return end   -- ignore every other tool
+  local name = e.tool_name                     -- snapshot now: e is short-lived
+  e:setComponent({
+    render = function(self, width)
+      return { "▸ running skill: " .. name }
+    end,
+  })
+end)

panto.ext.emit(name, data) fires a custom event on the same bus, running both native and Lua handlers. Today data is surfaced as an opaque custom payload.

Lua components

A Lua component is a table you hand to e:setComponent. The one required method is render:

renderfunction(self, width)required
Return an array of strings — one per line. Runs synchronously (it may not yield), each line is truncated to width columns, and any error becomes a safe fallback line so the frame never crashes. An empty array is valid (zero lines).
handleInputfunction(self, data)
Optional. Handle input routed to the component.
firstLineChanged / invalidatefunction
Optional render-cache hooks.

Runtime & luarocks

panto embeds a pinned Lua and luarocks, and ships exactly one battery: luv (libuv’s event loop), the single runtime dependency. It drives panto’s coroutine scheduler and gives extensions one rich async-I/O surface. The bootstrap reconciles the installed rocks tree against panto’s manifest on every startup — installing what’s missing, removing what’s stale.

Anything else is yours to add. Install extra rocks into panto’s tree with the embedded luarocks, via panto lua:

shellbash
panto lua -e 'arg[0]="luarocks"; require("luarocks.cmd").run_command("install","lpeg")'

One-time setup

On a fresh machine, run panto bootstrap once to build the rocks tree before your extensions need it.
+ +
+
+ + + \ No newline at end of file -- cgit v1.3