summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-07-01 15:36:20 -0600
committert <t@tjp.lol>2026-07-01 15:37:04 -0600
commit800b2c4b6115845f6bf15d90484b3e80e81a606f (patch)
tree914981ad5c49e8941280d015923b7c3143463d64 /examples
parentef7c37c3423ceb896f20676525307f15d6e927b4 (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')
-rw-r--r--examples/extensions/echo.lua8
-rw-r--r--examples/extensions/greet.lua20
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,
}