Builtin Library
README.md
tools
Public tools.* surface — discovery, authoring, and inspection of
code-mode tools. Installed as the global tools table by the prelude
(via --!global tools).
Tools in this engine are on-disk assets: a <name>.tool/ folder inside a
<box>.toolbox/ parent, callable as tools.<box>.<name>(...). A tool's
metadata comes from four sources in the folder — the --!desc/--!arg/
--!return/--!example docstring above its function in init.luau, the
typed function signature, the sibling .metadata JSON (tags),
and README.md. The engine parses the same four sources to bind each tool
and render its docs page, so the filesystem IS the registry — there's no
runtime registry to keep in sync.
Exports
tools.toolboxes() -> { stdout, value }— the toolbox-first overview. One row per toolbox:{ toolbox, purpose, toolCount },purposesourced from the toolboxREADME.md(first descriptive line) or its.metadatadescription. The "what domains exist" entry point — drill in withtools.search("", { toolbox = "<name>" }).tools.list(tier: (number | string)?, toolbox: string?) -> { stdout, value }— grouped discovery. Tier 1 (default): names only. Tier 2: + one-line descriptions. Tier 3: + the assembled signature / args / returns / examples. A string in either slot is treated as a toolbox name.tools.search(query: string?, opts?) -> { stdout, value, total? }: discovery over the tool family, answered by@builtin::modules.discovery.opts.modepicks how a query ranks:"semantic"by what a tool means,"keyword"by the words it carries, where a hit falls by what the query's words the asset carried weigh (a word few assets carry outweighs several nearly every asset carries), then by the role that matched, then by name. A call naming no mode runsdiscovery.defaultMode(): semantic wherever a world is bound, keyword where none is. The search isdiscovery.searchTools, the questionsearch_tools { query }asks: not in keyword mode on a bound engine, the search core ranks the tools, toolboxes and workflows of the whole published library, then this world's own matches follow (from = "world").opts:toolbox,limit,mode. An empty query lists that toolbox's tools, or the catalogue when none is named. Each row'sidentity(andname) is the<box>.<tool>call path;assetTypesays whether a tool, a toolbox or a workflow matched,registeredwhether the registry holds the function a call would run, andmatchedthe role, file, word counts and weight behind a query hit.total, on a query naming no toolbox, is how many matched in all, of whichvalueholds the bestlimit. A query row also carriesguid,installed(whether this world holds the hit),use(the call that acts on it:tool_exec { script: "return await tools.use('<toolbox>', '<tool>')" }for a tool and for theworkflowtoolbox'sstart, asearch_tools { toolbox }drill-in, or, for a hit this world does not hold,bash { command: "zm ls-tree @<guid>" }, which reads its files),install(thezeromind.installAssetcall, on a hit this world does not hold, which brings it in to run on the job) andpwhen the search core judged it.tools.createToolbox(args) -> { ok, path?, identity?, error? }— scaffold a new.toolbox/. Required:name,description. Optional:sharedCode(override defaultshared.module/init.luau),into(a.packageref to nest the toolbox inside).tools.create(args) -> { ok, path?, identity?, signature?, error? }— author a new tool inside an existing toolbox. Required:name,toolbox,code,description. Optional:args,returns,examples,tags.tools.delete(identity: string) -> { ok, path?, error? }— delete the.tool/folder for"<toolbox>.<name>".tools.get(identity: string) -> ToolMeta?— the tool's assembled metadata ({ name, signature?, description, args, returns?, examples, tags }), ornilif missing.tools.parseCommandLine(identity: string, words: { string }, opts: { cwd: string? }?) -> BindResult— read a command line against the tool's own schema (--!flagand--!restannotations, parameter names, the fields of the trailing options table) and bind it throughtools.bind. Each word is read against the type of the slot it reaches:stringandPathas written,numberandbooleanstrictly, a union of string literals against its members, anything else as JSON,true/false/null, a number, a comma run of numbers (0,5,0), or text; a word there that starts with{or[and does not parse is refusedbad-value, naming the word and the parse error.Pathslots resolve againstopts.cwd(default/zero). A refusal carriesreasonunknown-flag,missing-value,bad-valueorgiven-twice(or one oftools.bind's) and amessagewritten for a shell.tools.callable(toolbox: string, tool: string) -> string?— the identity a call names,"<toolbox>.<tool>", ornilwhen the registry carries no such tool. Reads the bound toolbox proxies, so a.toolfolder whose source never registered a function answersnil. What a surface holding a tool's ASSET uses to say how it is called.tools.render(identity: string, result: any) -> { out: string, err: string?, exit: number? }?— run the tool folder'srender.luau(a module returning(result) -> string | { out, err?, exit? }) over a result. A string return becomes{ out = s };nilwhen the tool holds norender.luau; raises with the render's own message when it raises or returns another type. The module is required at call time, so an edit takes effect on the next call.tools.get(identity).hasRenderstates whether the file exists.
Usage
-- Discovery
print(tools.list().stdout) -- summary: toolbox lines, names only
print(tools.list(2).stdout) -- tier 2: + first-line descriptions
print(tools.list("entityOps").stdout) -- only the `entityOps` toolbox, summary
print(tools.list("entityOps", 3).stdout) -- only `entityOps`, full metadata per tool
-- Authoring (two steps — toolbox first, then tools inside it)
local tb = tools.createToolbox({
name = "mytools",
description = "Demo tooling for greeting agents.",
})
assert(tb.ok, tb.error)
local r = tools.create({
name = "hello",
toolbox = "mytools",
description = "Greet a name.",
code = "return 'hello, ' .. (who or 'world')",
args = {
{ name = "who", type = "string", description = "Name to greet." },
},
returns = { type = "string", description = "The greeting." },
examples = { "mytools.hello('agent')" },
tags = { "demo" },
})
assert(r.ok, r.error)
-- Inspection — assembled metadata for one tool
print(tools.get("mytools.hello").signature)
-- Removal — deletes the .tool/ folder
tools.delete("mytools.hello")
Notes
- Auto-installed as the global
toolsvia--!global tools— user code doesn'trequirethis module directly in normal use. - Per the project principle "tools in Luau, capabilities in Rust", no
FFI calls live here —
asset.*andvfs.*already cover the underlying capabilities, and tools never needed a separate runtime registry once they became proper on-disk assets. tools.createToolboxrefuses to clobber an existing toolbox. To extend one, edit files viavfs.writeand usetools.deleteper tool.- Every toolbox ships a
shared.module/that holds the helpers its tools share. Theinit.luaugenerated bytools.createautomatically declareslocal shared = require(".shared")so thecodeyou pass can reference the module's exports directly. The require is skipped when the toolbox is missingshared.module/, to avoid a guaranteed runtime error. - The asset index can lag disk for a frame or two after
vfs.remove—tools.listsilently skips stale entries to keep discovery clean.