diff options
| author | t <t@tjp.lol> | 2026-07-01 15:36:20 -0600 |
|---|---|---|
| committer | t <t@tjp.lol> | 2026-07-01 15:37:04 -0600 |
| commit | 800b2c4b6115845f6bf15d90484b3e80e81a606f (patch) | |
| tree | 914981ad5c49e8941280d015923b7c3143463d64 /examples/extensions | |
| parent | ef7c37c3423ceb896f20676525307f15d6e927b4 (diff) | |
Load extensions via unified policy, entry/activate, rocks and paths
Replace the split [tools]/[extensions] config sections and the two-stage
load gate with a single model:
- [extensions] is now one policy resolved across all layers. Rules from
every layer are kept (not clobbered) and resolved by last-match-wins
after sorting by (layer, glob specificity, deny-last), so a base layer
can carve one name out of an otherwise-denied group and a higher layer's
broad rule still wins. Default is allow; whitelist via deny=["**"].
- Registration is deferred: a source returns an entry {name, activate}
(or a list, or the sugar tool table), is always eval'd side-effect-free,
and only permitted names get activate()d. Identity is the declared name,
not the filename. Collapses the old pre/post-load two-stage gate.
- The loader runs two passes (eval -> shadow -> filter -> activate) with
precedence project>user>base and, within a layer, rocks<paths<dir.
- extensions.paths adds extra scan dirs; extensions.rocks loads luarocks
packages as extension sources (require-as-entries). Startup installs
only missing rocks (no per-launch network hit); panto update force-
(re)installs every configured rock.
deny is a feature toggle, not a security boundary: rocks is where registry
code enters, so the pin list is the trust boundary. Rock integrity
(first-party GPG signing + --verify) is designed but not yet wired; until
then rocks pins which version, not which bytes.
Breaking: [tools] is removed; extensions must return entries. Built-in
tools and the wc example keep working via the sugar tool form.
Diffstat (limited to 'examples/extensions')
| -rw-r--r-- | examples/extensions/echo.lua | 8 | ||||
| -rw-r--r-- | examples/extensions/greet.lua | 20 |
2 files changed, 20 insertions, 8 deletions
diff --git a/examples/extensions/echo.lua b/examples/extensions/echo.lua index 2f2d4de..112ee25 100644 --- a/examples/extensions/echo.lua +++ b/examples/extensions/echo.lua @@ -1,9 +1,11 @@ -- A trivial Lua tool that echoes back whatever the LLM asks it to. -- Useful for exercising the panto CLI's Lua extension path end-to-end. +-- +-- Single-tool sugar form: return a table with a `handler` and panto +-- registers it as a tool. Its `name` is the identity gated by the +-- `[extensions]` allow/deny policy. -local panto = require("panto") - -panto.ext.register_tool { +return { name = "echo", description = "Echo back the given message. Useful for testing whether tools work.", schema = { diff --git a/examples/extensions/greet.lua b/examples/extensions/greet.lua index d74095b..38d8ca0 100644 --- a/examples/extensions/greet.lua +++ b/examples/extensions/greet.lua @@ -2,17 +2,27 @@ -- Drop this under .panto/extensions/ (project) or your user config's -- extensions/ dir, then type `/greet` or `/greet <name>` in the REPL. -- +-- Extensions return an *entry* `{ name, activate }`. The file is always +-- eval'd, but `activate()` runs only if `name` is permitted by the +-- `[extensions]` policy — so registration is deferred into activate(). +-- One entry may register any number of tools/commands. +-- -- Slash-command handlers run synchronously and act by side effect (here, -- writing to stdout). `args` is the trimmed text after the command name; -- it is an empty string when none was given. The return value is ignored. local panto = require("panto") -panto.ext.register_command { +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() + 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, + } end, } |
