diff options
Diffstat (limited to 'lua.html')
| -rw-r--r-- | lua.html | 56 |
1 files changed, 32 insertions, 24 deletions
@@ -20,44 +20,52 @@ <div class="pd-header__right"><a class="pd-ghlink" href="https://code.tjp.lol/pantograph.git/" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg> Source</a></div> </header> <div class="pd-shell"> -<aside class="pd-sidebar"><div class="pd-sidebar__scroll"><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/></svg>Getting started</p><ul class="pd-navsec__list"><li><a href="index.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="index.html#install" class="pd-navsec__link">Install</a></li><li><a href="index.html#first-run" class="pd-navsec__link">First run</a></li><li><a href="index.html#authenticate" class="pd-navsec__link">Authenticate</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg>Command line</p><ul class="pd-navsec__list"><li><a href="cli.html#starting" class="pd-navsec__link">Starting a session</a></li><li><a href="cli.html#sessions" class="pd-navsec__link"><code>panto sessions</code></a></li><li><a href="cli.html#models" class="pd-navsec__link"><code>panto models</code></a></li><li><a href="cli.html#auth" class="pd-navsec__link"><code>panto auth</code></a></li><li><a href="cli.html#bootstrap" class="pd-navsec__link"><code>panto bootstrap</code></a></li><li><a href="cli.html#lua" class="pd-navsec__link"><code>panto lua</code></a></li><li><a href="cli.html#slash-commands" class="pd-navsec__link">Slash commands</a></li><li><a href="cli.html#environment" class="pd-navsec__link">Environment</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M3 5h11"/><path d="M19 5h2"/><path d="M3 12h6"/><path d="M14 12h7"/><path d="M3 19h10"/><path d="M18 19h3"/><circle cx="16" cy="5" r="2"/><circle cx="11" cy="12" r="2"/><circle cx="15" cy="19" r="2"/></svg>Configuration</p><ul class="pd-navsec__list"><li><a href="configuration.html#files" class="pd-navsec__link">Files & layers</a></li><li><a href="configuration.html#providers" class="pd-navsec__link"><code>[providers]</code></a></li><li><a href="configuration.html#auth" class="pd-navsec__link"><code>[auth]</code></a></li><li><a href="configuration.html#defaults" class="pd-navsec__link"><code>[defaults]</code></a></li><li><a href="configuration.html#policies" class="pd-navsec__link">Tools & extensions</a></li><li><a href="configuration.html#compaction" class="pd-navsec__link"><code>[compaction]</code></a></li><li><a href="configuration.html#models" class="pd-navsec__link"><code>models.toml</code></a></li><li><a href="configuration.html#system-prompt" class="pd-navsec__link">System prompt</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.001"/><path d="M10 8h.001"/><path d="M14 8h.001"/><path d="M18 8h.001"/><path d="M8 12h.001"/><path d="M12 12h.001"/><path d="M16 12h.001"/><path d="M7 16h10"/></svg>Key bindings</p><ul class="pd-navsec__list"><li><a href="keybindings.html#editing" class="pd-navsec__link">Editing & cursor</a></li><li><a href="keybindings.html#submit" class="pd-navsec__link">Submit & newline</a></li><li><a href="keybindings.html#control" class="pd-navsec__link">Selectors & control</a></li><li><a href="keybindings.html#terminal" class="pd-navsec__link">Terminal protocol</a></li></ul></div><div class="pd-navsec pd-navsec--current"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"/><path d="m7 16.5-4.74-2.85"/><path d="m7 16.5 5-3"/><path d="M7 16.5v5.17"/><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"/><path d="m17 16.5-5-3"/><path d="m17 16.5 4.74-2.85"/><path d="M17 16.5v5.17"/><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"/><path d="M12 8 7.26 5.15"/><path d="m12 8 4.74-2.85"/><path d="M12 13.5V8"/></svg>Lua extensions</p><ul class="pd-navsec__list"><li><a href="lua.html#overview" class="pd-navsec__link pd-navsec__link--active">Overview</a></li><li><a href="lua.html#loading" class="pd-navsec__link">Loading extensions</a></li><li><a href="lua.html#tools" class="pd-navsec__link"><code>register_tool</code></a></li><li><a href="lua.html#commands" class="pd-navsec__link"><code>register_command</code></a></li><li><a href="lua.html#events" class="pd-navsec__link">Events: on & emit</a></li><li><a href="lua.html#components" class="pd-navsec__link">Lua components</a></li><li><a href="lua.html#runtime" class="pd-navsec__link">Runtime & luarocks</a></li></ul></div></div></aside> +<aside class="pd-sidebar"><div class="pd-sidebar__scroll"><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/></svg>Getting started</p><ul class="pd-navsec__list"><li><a href="index.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="index.html#install" class="pd-navsec__link">Install</a></li><li><a href="index.html#first-run" class="pd-navsec__link">First run</a></li><li><a href="index.html#authenticate" class="pd-navsec__link">Authenticate</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg>Command line</p><ul class="pd-navsec__list"><li><a href="cli.html#starting" class="pd-navsec__link">Starting a session</a></li><li><a href="cli.html#sessions" class="pd-navsec__link"><code>panto sessions</code></a></li><li><a href="cli.html#models" class="pd-navsec__link"><code>panto models</code></a></li><li><a href="cli.html#auth" class="pd-navsec__link"><code>panto auth</code></a></li><li><a href="cli.html#bootstrap" class="pd-navsec__link"><code>panto bootstrap</code></a></li><li><a href="cli.html#lua" class="pd-navsec__link"><code>panto lua</code></a></li><li><a href="cli.html#slash-commands" class="pd-navsec__link">Slash commands</a></li><li><a href="cli.html#environment" class="pd-navsec__link">Environment</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M3 5h11"/><path d="M19 5h2"/><path d="M3 12h6"/><path d="M14 12h7"/><path d="M3 19h10"/><path d="M18 19h3"/><circle cx="16" cy="5" r="2"/><circle cx="11" cy="12" r="2"/><circle cx="15" cy="19" r="2"/></svg>Configuration</p><ul class="pd-navsec__list"><li><a href="configuration.html#files" class="pd-navsec__link">Files & layers</a></li><li><a href="configuration.html#providers" class="pd-navsec__link"><code>[providers]</code></a></li><li><a href="configuration.html#auth" class="pd-navsec__link"><code>[auth]</code></a></li><li><a href="configuration.html#defaults" class="pd-navsec__link"><code>[defaults]</code></a></li><li><a href="configuration.html#policies" class="pd-navsec__link">Extensions policy</a></li><li><a href="configuration.html#compaction" class="pd-navsec__link"><code>[compaction]</code></a></li><li><a href="configuration.html#models" class="pd-navsec__link"><code>models.toml</code></a></li><li><a href="configuration.html#system-prompt" class="pd-navsec__link">System prompt</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.001"/><path d="M10 8h.001"/><path d="M14 8h.001"/><path d="M18 8h.001"/><path d="M8 12h.001"/><path d="M12 12h.001"/><path d="M16 12h.001"/><path d="M7 16h10"/></svg>Key bindings</p><ul class="pd-navsec__list"><li><a href="keybindings.html#editing" class="pd-navsec__link">Editing & cursor</a></li><li><a href="keybindings.html#submit" class="pd-navsec__link">Submit & newline</a></li><li><a href="keybindings.html#control" class="pd-navsec__link">Selectors & control</a></li><li><a href="keybindings.html#terminal" class="pd-navsec__link">Terminal protocol</a></li></ul></div><div class="pd-navsec pd-navsec--current"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"/><path d="m7 16.5-4.74-2.85"/><path d="m7 16.5 5-3"/><path d="M7 16.5v5.17"/><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"/><path d="m17 16.5-5-3"/><path d="m17 16.5 4.74-2.85"/><path d="M17 16.5v5.17"/><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"/><path d="M12 8 7.26 5.15"/><path d="m12 8 4.74-2.85"/><path d="M12 13.5V8"/></svg>Lua extensions</p><ul class="pd-navsec__list"><li><a href="lua.html#overview" class="pd-navsec__link pd-navsec__link--active">Overview</a></li><li><a href="lua.html#loading" class="pd-navsec__link">Loading extensions</a></li><li><a href="lua.html#tools" class="pd-navsec__link"><code>register_tool</code></a></li><li><a href="lua.html#commands" class="pd-navsec__link"><code>register_command</code></a></li><li><a href="lua.html#events" class="pd-navsec__link">Events: on & emit</a></li><li><a href="lua.html#components" class="pd-navsec__link">Lua components</a></li><li><a href="lua.html#runtime" class="pd-navsec__link">Runtime & luarocks</a></li></ul></div></div></aside> <div class="pd-scrim"></div> -<main class="pd-main"><article class="pd-doc" data-screen-label="Lua extensions"><p class="panto-eyebrow pd-eyebrow">Reference</p><h1 class="pd-doc__h1">Lua extensions</h1><div class="pd-doc__meta"><span class="pg-badge pg-badge--neutral">require("panto")</span></div><p class="pd-lead">Everything beyond the core is a Lua extension — including the standard tools. An extension is a Lua file that calls into the <code>panto.ext</code> table to add tools, slash commands, and UI behaviour. This is how panto stays small without staying useless.</p><h2 id="overview" class="pd-h2">Overview</h2><p class="pd-body">Inside an extension, <code>local panto = require("panto")</code> gives you the API. The <code>panto.ext</code> table has three entry points:</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">register_tool</span><span class="pg-badge pg-badge--neutral">model-facing</span></div><div class="pd-opt__desc">Add a tool the model can call.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">register_command</span><span class="pg-badge pg-badge--neutral">you-facing</span></div><div class="pd-opt__desc">Add a <code>/</code>slash command to the REPL.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">on / emit</span><span class="pg-badge pg-badge--neutral">UI events</span></div><div class="pd-opt__desc">Hook the UI event bus to wrap or replace how things render.</div></div></div><p class="pd-body">All extensions share one long-lived Lua interpreter, so module-global state persists across calls. The <a href="#runtime">runtime</a> includes <code>luv</code> (libuv), so tool handlers can perform async I/O.</p><h2 id="loading" class="pd-h2">Loading extensions</h2><p class="pd-body">Drop a <code>.lua</code> file into an extensions directory and it loads at startup. panto discovers extensions across three layers:</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Layer</th><th>Location</th></tr></thead><tbody><tr><td class="mono">base</td><td class="mono">$PANTO_HOME/agent</td></tr><tr><td class="mono">user</td><td class="mono">$XDG_CONFIG_HOME/panto/extensions/ · else ~/.config/panto/extensions/</td></tr><tr><td class="mono">project</td><td class="mono">./.panto/extensions/</td></tr></tbody></table></div><p class="pd-body">Project shadows user shadows base. Which extensions and tools actually load is gated by the <a href="configuration.html#policies"><code>[extensions]</code> and <code>[tools]</code></a> allow/deny policies.</p><div class="pg-callout pg-callout--warn"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">Name collisions abort startup</p>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 <code>/compact</code>.</div></div><h2 id="tools" class="pd-h2">register_tool</h2><p class="pd-body">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.</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">name</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">The tool name the model invokes.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">description</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">Shown to the model; explain when to use it.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">schema</span><span class="pd-opt__type">table</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">A JSON-Schema table (<code>type = "object"</code>, <code>properties</code>, <code>required</code>) describing the arguments.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">handler</span><span class="pd-opt__type">function(input)</span></div><div class="pd-opt__desc">Receives the decoded arguments table; returns a string result. May yield (async).</div></div></div><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">.panto/extensions/echo.lua</span><span class="pg-code__lang">lua</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>local panto = require("panto") - -panto.ext.register_tool { +<main class="pd-main"><article class="pd-doc" data-screen-label="Lua extensions"><p class="panto-eyebrow pd-eyebrow">Reference</p><h1 class="pd-doc__h1">Lua extensions</h1><div class="pd-doc__meta"><span class="pg-badge pg-badge--neutral">require("panto")</span></div><p class="pd-lead">panto's standard tools are Lua extensions, and your own extensions use the same loader. A source file returns one entry, one tool declaration, or a list of them; activation then registers tools, slash commands, and UI-event handlers into the live session.</p><h2 id="overview" class="pd-h2">Overview</h2><p class="pd-body">Extension source files are evaluated first and activated later. A file must return one of these shapes:</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">{ name, activate }</span></div><div class="pd-opt__desc">A named entry whose <code>activate()</code> function performs registration when the entry survives policy and shadowing.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">{ name, description, schema, handler }</span></div><div class="pd-opt__desc">Sugar form for a single tool entry. Activation registers it as a tool automatically.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">{ ... , ... }</span></div><div class="pd-opt__desc">A list of entries or tool declarations returned from one file.</div></div></div><p class="pd-body">Inside <code>activate()</code>, use <code>local panto = require("panto")</code> and the <code>panto.ext</code> table:</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">register_tool</span></div><div class="pd-opt__desc">Register a model-facing tool.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">register_command</span></div><div class="pd-opt__desc">Register a slash command.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">on / emit</span></div><div class="pd-opt__desc">Subscribe to transcript/UI lifecycle events or fire custom ones.</div></div></div><h2 id="loading" class="pd-h2">Loading extensions</h2><p class="pd-body">panto scans both <code>extensions/</code> and <code>tools/</code> directories at each layer:</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Layer</th><th>Location</th></tr></thead><tbody><tr><td class="mono">base</td><td class="mono">$XDG_DATA_HOME/panto/agent/{extensions,tools}</td></tr><tr><td class="mono">user</td><td class="mono">$XDG_CONFIG_HOME/panto/{extensions,tools} · else ~/.config/panto/{extensions,tools}</td></tr><tr><td class="mono">project</td><td class="mono">./.panto/{extensions,tools}</td></tr></tbody></table></div><p class="pd-body">Additional sources can come from <code>[extensions] paths</code> and <code>[extensions] rocks</code>. Cross-layer precedence is project > user > base. Within one layer, canonical directories beat extra paths, which beat rocks. Identity is the returned entry's <code>name</code>, not the filename.</p><div class="pg-callout pg-callout--warn"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">Eval is side-effect free</p>Every candidate file is evaluated before policy and shadowing are resolved, including entries that will later be denied. Put side effects in <code>activate()</code>, not at top level.</div></div><h2 id="tools" class="pd-h2">register_tool</h2><p class="pd-body">The simplest way to define a tool is to return the sugar form directly:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">.panto/tools/echo.lua</span><span class="pg-code__lang">lua</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>return { name = "echo", description = "Echo back the given message.", schema = { type = "object", properties = { - message = { type = "string", description = "The text to echo back." }, + message = { type = "string" }, }, required = { "message" }, }, handler = function(input) return "echo: " .. input.message end, -}</code></pre></div></div><h2 id="commands" class="pd-h2">register_command</h2><p class="pd-body">A slash command runs locally when you type <code>/name</code> in the REPL. The handler runs synchronously and acts by side effect (typically writing output); its return value is ignored.</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">name</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">Command name, without the leading <code>/</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">description</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">One-line summary (for listings).</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">handler</span><span class="pd-opt__type">function(args)</span></div><div class="pd-opt__desc"><code>args</code> is the trimmed text after the command name — the empty string when none was given.</div></div></div><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">.panto/extensions/greet.lua</span><span class="pg-code__lang">lua</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>local panto = require("panto") - -panto.ext.register_command { +}</code></pre></div></div><p class="pd-body">Use <code>register_tool</code> inside <code>activate()</code> when you want one entry to register multiple tools or commands together.</p><h2 id="commands" class="pd-h2">register_command</h2><p class="pd-body">Slash commands are typically registered from an explicit entry:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">.panto/extensions/greet.lua</span><span class="pg-code__lang">lua</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>return { 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") + activate = function() + local panto = require("panto") + panto.ext.register_command { + name = "greet", + description = "Print a greeting.", + handler = function(args) + local who = args ~= "" and args or "world" + io.write("\n[greet] hello, " .. who .. "!\n") + end, + } end, -}</code></pre></div></div><h2 id="events" class="pd-h2">Events: on & emit</h2><p class="pd-body"><code>panto.ext.on(name, handler)</code> 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.</p><p class="pd-body">The handler receives an event object <code>e</code> that is valid <strong>only</strong> during the call (copy any field you need into a local — don’t read <code>e</code> later at render time):</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">e.name</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">The event name.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">e.<field></span></div><div class="pd-opt__desc">Read-only payload fields (nil when absent, so you can branch on them).</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">e:getComponent()</span></div><div class="pd-opt__desc">The current native component, as an opaque pass-through handle.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">e:setComponent(c)</span></div><div class="pd-opt__desc">Replace the component for this boundary — pass the handle back to wrap it, or a Lua component table to replace it.</div></div></div><p class="pd-body">The events and the payload fields they carry:</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Event</th><th>Payload fields</th></tr></thead><tbody><tr><td class="mono">tool</td><td class="mono">index, tool_name, id, delta, input, output</td></tr><tr><td class="mono">thinking</td><td class="mono">index, delta, text</td></tr><tr><td class="mono">assistant_text</td><td class="mono">index, delta, text</td></tr><tr><td class="mono">user_message</td><td class="mono">text</td></tr><tr><td class="mono">session_start</td><td class="mono">version, cwd, model</td></tr><tr><td class="mono">compaction</td><td class="mono">summary</td></tr><tr><td class="mono">custom</td><td class="mono">— produced by emit</td></tr></tbody></table></div><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">.panto/extensions/skill-badge.lua</span><span class="pg-code__lang">lua</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>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)</code></pre></div></div><p class="pd-body"><code>panto.ext.emit(name, data)</code> fires a custom event on the same bus, running both native and Lua handlers. Today <code>data</code> is surfaced as an opaque <code>custom</code> payload.</p><h2 id="components" class="pd-h2">Lua components</h2><p class="pd-body">A Lua component is a table you hand to <code>e:setComponent</code>. The one required method is <code>render</code>:</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">render</span><span class="pd-opt__type">function(self, width)</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">Return an array of strings — one per line. Runs synchronously (it may <em>not</em> yield), each line is truncated to <code>width</code> columns, and any error becomes a safe fallback line so the frame never crashes. An empty array is valid (zero lines).</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">handleInput</span><span class="pd-opt__type">function(self, data)</span></div><div class="pd-opt__desc">Optional. Handle input routed to the component.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">firstLineChanged / invalidate</span><span class="pd-opt__type">function</span></div><div class="pd-opt__desc">Optional render-cache hooks.</div></div></div><h2 id="runtime" class="pd-h2">Runtime & luarocks</h2><p class="pd-body">panto embeds a pinned Lua and luarocks, and ships exactly one battery: <code>luv</code> (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.</p><p class="pd-body">Anything else is yours to add. Install extra rocks into panto’s tree with the embedded luarocks, via <a href="cli.html#lua"><code>panto lua</code></a>:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>panto lua -e 'arg[0]="luarocks"; require("luarocks.cmd").run_command("install","lpeg")'</code></pre></div></div><div class="pg-callout pg-callout--tip"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">One-time setup</p>On a fresh machine, run <a href="cli.html#bootstrap"><code>panto bootstrap</code></a> once to build the rocks tree before your extensions need it.</div></div><nav class="pd-prevnext"><a class="pd-pn pd-pn--prev" href="keybindings.html"><span class="pd-pn__dir">← Previous</span><span class="pd-pn__title">Key bindings</span></a><span style="flex:1"></span></nav></article></main> +}</code></pre></div></div><h2 id="events" class="pd-h2">Events: on & emit</h2><p class="pd-body"><code>panto.ext.on(name, handler)</code> subscribes to transcript/UI lifecycle events. The handler receives an <code>event</code> userdata with snake-case methods <code>get_component()</code> and <code>set_component()</code>, plus payload fields such as <code>tool_name</code>, <code>delta</code>, <code>text</code>, <code>id</code>, <code>input</code>, <code>output</code>, <code>cwd</code>, and <code>model</code>.</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Event family</th><th>Names</th></tr></thead><tbody><tr><td class="mono">tool</td><td class="mono">tool, tool_details, tool_delta, tool_call_complete, tool_result, tool_collapse</td></tr><tr><td class="mono">thinking</td><td class="mono">thinking, thinking_delta, thinking_complete</td></tr><tr><td class="mono">assistant text</td><td class="mono">assistant_text, assistant_text_delta, assistant_text_complete</td></tr><tr><td class="mono">single-shot</td><td class="mono">user_message, session_start, compaction</td></tr><tr><td class="mono">custom</td><td class="mono">whatever you emit via <code>panto.ext.emit</code></td></tr></tbody></table></div><p class="pd-body">A few payload fields are writable during dispatch: <code>user_message.text</code>, <code>tool_call_complete.input</code> (assign a Lua table; panto serializes it to JSON), and <code>tool_result.output</code>.</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">.panto/extensions/skill-badge.lua</span><span class="pg-code__lang">lua</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>return { + name = "skill-badge", + activate = function() + local panto = require("panto") + panto.ext.on("tool_details", function(e) + if e.tool_name ~= "skill" then return end + local inner = e:get_component() + e:set_component({ + render = function(self, width) + local lines = inner:render(width) + lines[#lines + 1] = "▸ running skill" + return lines + end, + }) + end) + end, +}</code></pre></div></div><h2 id="components" class="pd-h2">Lua components</h2><p class="pd-body">A Lua component is a table passed to <code>set_component()</code>. The required method is <code>render(self, width)</code>, which returns an array of strings. <code>handleInput(self, data)</code> is optional. Differential repaint is cache-derived automatically; you do not need to implement <code>firstLineChanged</code> yourself.</p><h2 id="runtime" class="pd-h2">Runtime & luarocks</h2><p class="pd-body">panto embeds a pinned Lua and luarocks runtime and ships <code>luv</code> as its core async dependency. Extra rocks may be loaded as extension sources via <code>[extensions] rocks</code> and installed with <code>panto update</code>, or added manually with <code>panto lua</code>.</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>panto update +panto lua -e 'arg[0]="luarocks"; require("luarocks.cmd").run_command("install","lpeg")'</code></pre></div></div><nav class="pd-prevnext"><a class="pd-pn pd-pn--prev" href="keybindings.html"><span class="pd-pn__dir">← Previous</span><span class="pd-pn__title">Key bindings</span></a><span style="flex:1"></span></nav></article></main> <aside class="pd-toc"><p class="pd-toc__title">On this page</p><ul class="pd-toc__list"><li><a href="#overview" class="pd-toc__link pd-toc__link--active">Overview</a></li><li><a href="#loading" class="pd-toc__link">Loading extensions</a></li><li><a href="#tools" class="pd-toc__link">register_tool</a></li><li><a href="#commands" class="pd-toc__link">register_command</a></li><li><a href="#events" class="pd-toc__link">Events: on & emit</a></li><li><a href="#components" class="pd-toc__link">Lua components</a></li><li><a href="#runtime" class="pd-toc__link">Runtime & luarocks</a></li></ul></aside> </div> </div> |
