Log inGet started

Toolbox (asset type)

A toolbox is a namespace folder that groups related .tool/ children plus shared helpers. Toolboxes are the addressing layer for tools — every tool's identity is <toolbox>.<name>, so the toolbox name…

When to use one

  • You have two or more related tools that belong together (an anim toolbox with play / stop / list; an assets toolbox with compose / validate).
  • You need a shared helper module exclusively used by a tool set.
  • You want a clean dot-namespaced identity for your tools (my_tools.foo, my_tools.bar).

If you only have one tool and no shared helpers, you still need a toolbox — it's the required parent.

Where it lives

  • Source: /zero/source/.../<Name>.toolbox/
  • Identity: <Name> (the .toolbox suffix strips).
  • Folder shape (container-style — open child set):
    • README.md — instance documentation. Required.
    • shared.module/ — optional shared helper module (init.luau + README.md).
    • <x>.tool/ — any number of .tool/ children.

The validator permits unlisted children (allow_unlisted: true) so tool subfolders don't need to be declared per-name.

How to create one

asset.create("toolbox", "<Name>")
-- Creates: /zero/source/tools/<Name>.toolbox/
--   README.md            (instance README template)
--   shared.module/       (ok / fail result-envelope constructors)

Then add tools under it:

local box = asset.resolve("<Name>.toolbox")
asset.create("tool", "<toolName>", { into = box })

How it operates

  1. Registration. Writing into a .toolbox/ folder indexes the toolbox. It becomes the parent namespace for any .tool/ children.
  2. Tool identity composition. Each child .tool/'s identity is <toolbox>.<tool>. Listing the toolbox's tools enumerates the children.
  3. Shared helpers. Tools inside the toolbox pull shared.module/ in via require(".shared") (relative require from the tool's init.luau).
  4. Hot reload. Editing shared.module/init.luau reloads every tool in the toolbox (they all require it). Editing the toolbox folder structure (adding / removing tools) reflects in tools.list immediately.

Discovery

  • asset.list("toolbox") — every registered toolbox.
  • asset.inspect("<name>") — child tools, source, this type README.
  • cat /zero/source/<Name>.toolbox — same summary.
  • tools.list() — every tool across every toolbox.

Authoring conventions

  • Per-tool implementation lives in each <x>.tool/init.luau. The tool's full function body — argument validation, the call into engine APIs, the return value — goes in init.luau with --!desc / --!arg / --!return / --!example annotations above the function. Use typed function syntax when the signature has clear types. Do NOT make init.luau a one-line forwarder onto a function defined in shared.module/.
  • Keep shared.module/ thin: result envelope constructors (shared.ok(value, formatted), shared.fail(message, exitCode)), argument validators, common formatters — anything genuinely used by more than one tool. Per-tool logic does NOT belong here.
  • Group by domain, not by surface — assets toolbox holds asset operations, entity toolbox holds entity operations. Tools that feel unrelated probably want different toolboxes.
  • Document the toolbox's surface in README.md — what problem space it covers, who calls into it, any shared invariants.

Common pitfalls

  • Missing parent toolbox. Tools cannot exist outside a toolbox. Always scaffold the toolbox first.
  • Cross-toolbox shared. Each toolbox has its own shared.module/. Don't try to share helpers across toolboxes; if you need cross-toolbox helpers, factor them into a standalone .module.
  • Toolbox identity. Strip the .toolbox suffix in identity references (my_tools, not my_tools.toolbox).
  • .tool — the required child(ren) of a toolbox.
  • .module — for cross-toolbox utilities.
  • asset-type
  • reference