santoku-cli
View on LuaRocksthe toku command line: build, test, install, release, license notices, and scaffolding for santoku projects
santoku-cli ships toku, the command-line front end of the framework: every santoku rock, and this documentation site itself, is built, tested, and released with it. The commands fall into two groups. The project lifecycle (init, test, install, pack, release, env, clean, plus build, start, and stop for web projects) is driven by santoku-make from a plain-Lua make.lua descriptor: sources are rendered into a build tree, dependencies are installed with luarocks into a private lua_modules, specs run against that tree, and the same tree feeds install and the luarocks release flow. The standalone utilities (template, bundle, lua) expose santoku-template, santoku-bundle, and an instrumented interpreter directly. This tab covers the whole surface, basics to advanced. toku runs processes and touches the real filesystem, so the shell examples are display only; the make.lua descriptor example is plain Lua and runs in the page.
The command surface
Every subcommand, listed here with its description verbatim from the argparse declarations in bin/toku.tk.lua. Every invocation also accepts --verbosity N (default 1): 0 silences the [make] line printed per rebuilt target, 2 and up adds [ok], [src], and [phony] trace lines. build, start, and stop apply to web projects, and a server-only API project is a web project to the harness, so all three apply to it too. install and pack are the mirror image: library projects only.
toku init Initialize a new project
toku license Write LICENSE and per-file SPDX headers from make.lua's license and copyright
toku test Run tests
toku install Install the project
toku pack Build rockspec and tarball without releasing
toku release Release the project
toku env Print shell exports for a project lua tree, for eval
toku clean Clean build artifacts
toku build Build the project
toku start Start the server
toku stop Stop the server
toku template Process templates
toku bundle Create standalone executables
toku lua Run the lua interpreter on a file
toku setup Maintain the toku-managed lua 5.1 toolchain (provisioned by setup-toku.sh from santoku.dev)
toku skills Install the santoku agent skills from santoku.dev into your home directory, never into a project
toku doctor Diagnose the managed toolchain and PATH wiring
toku luarocks Run the managed luarocks
toku luac Run the resolved luac
toku resty Run the managed OpenResty resty script
toku openresty Run the managed OpenResty nginx binary
toku serve Serve a directory over HTTP with the managed OpenResty
toku init: scaffold a library
Extracts a boilerplate, renames every tokuboilerplate occurrence to your name (which must match ^[a-z][a-z0-9-]*$), and runs git init. --here uses the current directory and its name; --dir picks the destination. Either target must be empty or missing, or init refuses before writing anything. The scaffold is a working rock, not a stub: a sqlite-backed library with a migration, a C extension (capi.c), a bin/ entry point, and a passing spec suite. --license ID --copyright HOLDER sets both fields in make.lua and writes LICENSE and SPDX headers. --copyright alone writes a one-line all-rights-reserved LICENSE. With neither flag, as below, the scaffold has no LICENSE and no license field, and toku warns.
$ toku init --name my-lib
Created library project: my-lib
Next steps:
cd my-lib
toku test # Run tests
toku install # Install locally
$ find my-lib -type f -not -path "*/.git/*" | sort
my-lib/.gitignore
my-lib/bin/my-lib.lua
my-lib/lib/my_lib.tk.lua
my-lib/lib/my_lib/capi.c
my-lib/make.lua
my-lib/res/migrations/0.0.1.sql
my-lib/test/spec/my_lib.lua
make.lua: the project descriptor
Everything toku knows about a project comes from make.lua, a plain Lua file returning { env = ... }. This one is santoku-cli's own descriptor: name and version become the rockspec, dependencies are luarocks constraints, env.release is what makes toku release available, and computed fields are plain Lua expressions. The rockspec's source url derives from the release host, so the descriptor never spells it out.
local release = require("santoku.make.release")
local env = {
name = "santoku-cli",
version = "3.0.0-1",
variable_prefix = "TK_CLI",
license = "MIT",
copyright = "Birch Point SWE",
dependencies = {
"lua == 5.1",
"santoku >= 2.0.0, < 3.0.0",
"santoku-fs >= 2.2.0, < 3.0.0",
"santoku-template >= 2.0.0, < 3.0.0",
"santoku-bundle >= 2.0.0, < 3.0.0",
"santoku-system >= 2.2.0, < 3.0.0",
"santoku-test-runner >= 2.0.3, < 3.0.0",
"santoku-make >= 6.0.0, < 7.0.0",
"argparse >= 0.7.1-1, < 1.0.0",
},
}
env.homepage = "https://santoku.dev/#" .. env.name
env.release = {
push = release.git_push(),
host = release.s3_upload({ bucket = "birchpointswe-releases", base_url = "https://releases.santoku.dev" }),
registry = release.luarocks_upload(),
}
return { env = env }
toku license: LICENSE and SPDX headers
Writes LICENSE and a two-line SPDX header (SPDX-License-Identifier, SPDX-FileCopyrightText) into every tracked source file, from make.lua's license and copyright. The header uses the file's own comment syntax and goes after any shebang or tk directive. LICENSE holds the license text, fetched from the SPDX license list, then one section per vendored entry. A stale header is replaced; a file that already carries another notice is left alone and reported. --check writes and fetches nothing, prints each problem and exits nonzero. --license, --copyright and --exclude override make.lua, and --year defaults to the year of the first commit. Name files to limit the run. copyright alone writes a one-line all-rights-reserved LICENSE and no headers; with neither set, nothing is written and toku warns. The make tab covers the vendored and license_exclude keys.
$ toku license --check
toku license: LICENSE doesn't name 2023 Birch Point SWE
toku license: lib/my_lib/capi.c: missing header
$ toku license
lib/my_lib/capi.c: added a header
LICENSE written for 2023 Birch Point SWE
$ head -2 lib/my_lib/capi.c
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2023 Birch Point SWE
Environments and the build tree
Work happens under build/<env>/: --env prod reads make.prod.lua instead of make.lua and builds under build/prod/. Files ending .tk are rendered through santoku-template against the descriptor env with the extension stripped, so lib/my_lib.tk.lua lands as lib/my_lib.lua; a .d sidecar next to each rendered file records what the template read through readfile and declared through depend, so editing either restales the output. The *.flag files persist CLI options like --single and --skip-check so that changing them restales the generated run and check scripts.
$ toku test
$ ls build/default
lua.flag single.flag test
lua_cpath_extra.flag skip_check.flag
lua_path_extra.flag
$ ls build/default/test
Makefile check.sh lua_modules luacheck.lua res test
bin lib lua_modules.ok luarocks.lua run.sh
my-lib-0.0.1-1.rockspec
$ toku test --env prod
$ ls build
default prod
The build tree for web and API projects
A library project builds one tree per environment. A web or API project builds two, main and test, and each holds three subtrees: client, server and dist. Only dist carries a lua_modules, because dist is what OpenResty runs with -p, so the rendered nginx.conf refers to it by relative path. A server-only project still gets a client subtree, holding little more than a lua_modules.ok stamp. When a script needs the project's own modules and its pinned rocks, toku lua --tree test or --tree build points LUA_PATH and LUA_CPATH at the matching dist/lua_modules. dist also holds run.sh, nginx.conf, server.pid and logs.
$ ls build/default/test
client dist server
$ ls build/default/test/dist # public/ too, when the project has a client
lua_modules nginx.conf nginx-fg.conf run.sh
$ ls build/default/test/dist # after a start: run.sh makes these
logs lua_modules nginx.conf nginx-fg.conf run.sh server.pid temp
$ toku lua --tree test scripts/load.lua
toku test: the full pipeline
One command does the whole loop: render sources into build/default/test, luarocks make the test rockspec into a private lua_modules there, run sh run.sh (which invokes the standalone runner as toku test -i "$LUA" --match "^.*%.lua$" test/spec), then sh check.sh (luacheck over lib, bin, and test/spec unless --skip-check). The runner prints one Test: line per spec file and is silent on pass; a failing assertion prints the nested test tag chain, the error, and a traceback. Every spec runs by default, so one command reports every failure; pass -s to stop at the first one.
$ toku test
[make] build/default/test/lib/my_lib.lua
[make] build/default/test/my-lib-0.0.1-1.rockspec
[make] build/default/test/run.sh
Test: test/spec/my_lib.lua
Total: 0 warnings / 0 errors in 3 files
Selecting and instrumenting tests
In project mode --single runs one spec file, --skip-check drops luacheck, --lua swaps the interpreter, -s stops at the first failure instead of running the whole suite, and --trace preloads santoku.trace into the test interpreter for a source trace. Passing file or directory arguments switches to the standalone runner, bypassing the project build entirely: -m filters files by Lua pattern and -i runs each file under an arbitrary interpreter command instead of dofile. -i only applies in that standalone mode; in project mode the interpreter comes from --lua.
$ toku test --single test/spec/my_lib.lua
$ toku test --skip-check
$ toku test --trace
$ toku test --lua luajit
$ toku test -s
$ toku test -m core -i luajit test/spec
Test: test/spec/core.lua
toku test --iterate: the watch loop
Runs test plus check, then blocks on inotifywait watching lib, bin, test, res, and every directory referenced by the .d sidecars, and reruns on any write. It requires inotifywait on PATH (errors with "inotify not found" otherwise), asks for a restart when make.lua itself changes, and bails out after two consecutive failures with no file changes in between. This is the standing dev loop: develop with toku test --iterate --show-logs.
$ toku test --iterate
Test: test/spec/my_lib.lua
[iterate] make.lua changed - please restart iterate
toku test --wasm
Builds a parallel tree under build/default-wasm/ with the C toolchain swapped for emscripten: dependencies compile with emcc, each spec is bundled to a .js file, and run.sh drives them with toku test -i "node --expose-gc" so the same suite runs against the WebAssembly build. --single maps the .lua spec to its .js twin. --wasm-browser uses the same tree but links each spec for the web and runs it in headless Chromium through toku-browser run from santoku-cdp, which must be installed in toku's tree. TOKU_CHROMIUM names the browser command and is required; TOKU_CHROMIUM_FLAGS adds flags, such as --no-sandbox when the run is root inside a container; TOKU_BROWSER_TIMEOUT_MS bounds each spec, 60000 by default. A spec named *.browser.wasm.lua needs a DOM: --wasm-browser runs it and --wasm skips it by name.
$ toku test --wasm
Test: test/spec/my_lib.js
$ toku test --wasm --single test/spec/my_lib.lua
$ TOKU_CHROMIUM=chromium toku test --wasm-browser
Test: test-browser/test/spec/my_lib.js
$ TOKU_CHROMIUM=chromium TOKU_CHROMIUM_FLAGS=--no-sandbox toku test --wasm-browser
toku install
Runs test and check, renders the shippable tree under build/default/build, and luarocks-makes the rockspec there into the active rocks tree. --skip-tests goes straight to the build. --luarocks-config points luarocks at another config, which is how to pull a locally edited santoku rock into a project's build trees: run toku install from the library repo against each luarocks.lua found under the app's build/ directory.
$ toku install
$ toku install --skip-tests
$ find ~/git/myapp/build -name luarocks.lua \
-exec toku install --luarocks-config {} --skip-tests \;
--deps-only: cache dependencies in their own image layer
toku install --deps-only and toku build --deps-only read only the descriptor (make.lua and whatever it runfiles) and install the dependencies it declares into the same trees the full build uses: the rocks tree for a library, and the server and client trees for a web project, plus each local dep's own dependencies. No project source is read or compiled. A later full build finds every dependency installed and builds only the project. In a container build, copy the descriptor files first, run the deps-only step, then copy the rest, so a source change reuses the cached dependency layer. Copy each local dep's make.lua too, plus every file any of these descriptors runfiles, and keep them free of reads from other project files.
COPY make.lua make.common.lua ./
COPY submodules/my-lib/make.lua submodules/my-lib/make.common.lua submodules/my-lib/
RUN toku build --env prod --deps-only
COPY . .
RUN toku build --env prod
toku install --bundled: standalone executables
Instead of installing the rock, bundle every bin/*.lua through santoku-bundle into a native executable and copy it to <prefix>/bin (prefix defaults to $PREFIX when set, then $HOME/.local). Lua modules are embedded and C extensions are linked statically from the .o and vendored archives each rock installs alongside its .so, so the binary keeps working after the build tree is gone. Lua itself links statically, since the managed toolchain ships only liblua.a, so readelf -d lists libc and the system libraries your dependencies link, such as libopenblas; the binary needs no Lua modules on disk and no LUA_PATH. A dependency built before santoku-make 5.0.3 ships no such objects, and the bundler falls back to dynamic linking for it with a warning naming the module. --bundle-mods preloads modules the static require scan cannot see (comma separated), --bundle-ignores skips names (debug is always ignored), --bundle-cc picks the compiler, and --bundle-flags replaces the auto-derived compile flags rather than adding to them, so supply your own -I, -L and -l when you pass it. Any value starting with a dash needs the equals form, since the option parser would otherwise read it as a flag. With --wasm the compiler defaults to emcc and each entry point lands as a .js file plus a node wrapper script. That path needs emcc, emmake and node on PATH, and it rebuilds the whole dependency tree under emcc into build/<env>-wasm, so the first run is slow even for a small project.
$ toku install --bundled --prefix "$HOME/.local"
$ ls ~/.local/bin
my-lib
$ toku install --bundled --bundle-mods my-lib.plugins \
--bundle-ignores ssl --bundle-flags="-O2 -I/usr/include -lm"
toku pack: the release tarball
Builds build/default/build and tars it up as <name>-<version>.tar.gz without touching git or luarocks.org: sources with .tk stripped, the generated Makefile (plus lib/Makefile and bin/Makefile when those trees exist), and LICENSE, all under a <name>-<version>/ prefix. The rockspec sits alongside the tarball, and its source url is env.download: derived from the release host's download(name, version) when make.lua leaves it unset, so a project hosted with s3_upload gets the base_url, name and version joined for it.
$ toku pack
$ ls build/default/build
Makefile bin santoku-cli-3.0.1-1.rockspec santoku-cli-3.0.1-1.tar.gz
$ tar -tzf build/default/build/santoku-cli-3.0.1-1.tar.gz
santoku-cli-3.0.1-1/bin/toku.lua
santoku-cli-3.0.1-1/bin/Makefile
santoku-cli-3.0.1-1/Makefile
santoku-cli-3.0.1-1/LICENSE
toku release: the hooks in env.release
Only exists when make.lua declares env.release; otherwise toku prints the first line below and stops. release runs pack, which runs test and check unless --skip-tests, refuses a tree with uncommitted or untracked files, tags the rock version (an existing tag on HEAD is reused, so a failed release reruns without a bump), then runs the hooks phase by phase: artifacts, push, host, registry. Each hook is a function of ctx, or a table with a run function; ctx carries name, version, tag, commit, tarball, rockspec, build_dir, dir and artifacts. An artifacts hook pushes { path, key, content_type } entries onto ctx.artifacts. From santoku.make.release: git_push pushes HEAD and the tag; s3_upload PUTs ctx.artifacts when the artifacts phase added any, otherwise the tarball and rockspec, with If-None-Match so a key is never overwritten, and an existing key passes only when its bytes match. An entry with mutable = true goes up after the others, at <name>/<key> with no version, as a plain PUT with Cache-Control max-age=300, and s3_upload's latest = true option writes <name>/latest.txt the same way. gh_release creates a GitHub release carrying both files, and points the rockspec's source url at it, taking the repo from the origin remote or opts.repo; luarocks_upload reads LUAROCKS_API_KEY, refuses to run without luasec, and uploads the rockspec alone. It refuses a rockspec whose source url isn't https, since luarocks installs fetch the tarball from that url. A failure names the phases that completed, and the local tag is removed only when no phase ran.
$ toku release
release is not available (requires a non-wasm project with env.release in make.lua)
$ LUAROCKS_API_KEY=... AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... toku release
...
03074fe..5191a0d HEAD -> master
* [new tag] 6.0.1-1 -> 6.0.1-1
uploaded: https://releases.santoku.dev/santoku-make/6.0.1-1/santoku-make-6.0.1-1.tar.gz
uploaded: https://releases.santoku.dev/santoku-make/6.0.1-1/santoku-make-6.0.1-1.rockspec
Done: https://luarocks.org/modules/birchpointswe/santoku-make
-- make.lua: publish extra files beside the tarball
env.release = {
artifacts = function (ctx)
local fs = require("santoku.fs")
local arr = require("santoku.array")
arr.push(ctx.artifacts,
{ path = ctx.tarball, key = fs.basename(ctx.tarball), content_type = "application/gzip" },
{ path = ctx.rockspec, key = fs.basename(ctx.rockspec), content_type = "text/plain; charset=utf-8" },
{ path = "install.sh", key = "install.sh", content_type = "text/x-shellscript" })
end,
push = release.git_push(),
host = release.s3_upload({ bucket = "my-releases", base_url = "https://releases.example.com" }),
registry = release.luarocks_upload(),
}
Releasing a Claude Code plugin by URL
release.claude_plugin_archive({ plugin, base_url }) is an artifacts hook for a repo that holds plugins/<plugin>/ and .claude-plugin/marketplace.json. It checks that plugin.json carries the plugin's name and env.version, zips the plugin directory with its root at the top of the zip, and builds the same bytes on a rerun, so a retried release passes s3_upload's same-bytes check. It then writes a marketplace.json whose entry for the plugin has an archive source: the zip's URL under base_url and its sha256. The zip uploads write-once under the version, and marketplace.json uploads as a mutable key at <name>/marketplace.json, so the marketplace URL stays fixed and moves to each new version within five minutes. Users add that URL with claude plugin marketplace add. The archive source needs Claude Code v2.1.224 or later. It needs zip on the release host.
-- make.lua
local release = require("santoku.make.release")
local env = {
name = "fleet",
version = release.json_field("plugins/fleet/.claude-plugin/plugin.json", "version"),
license = "MIT",
}
env.release = {
artifacts = release.claude_plugin_archive({ plugin = "fleet", base_url = "https://releases.example.com" }),
push = release.git_push(),
host = release.s3_upload({ bucket = "my-releases", base_url = "https://releases.example.com" }),
}
return { env = env }
$ claude plugin marketplace add https://releases.example.com/fleet/marketplace.json
Releasing a Claude Code plugin as pasted text
release.claude_plugin_bundle({ plugin, base_url }) is an artifacts hook for a client that installs from text rather than a URL. It checks plugin.json the same way, then writes <plugin>-<version>.txt: every file under plugins/<plugin>/ in path order, each as a FILE line with its path, byte count and sha256, the exact contents, and an END line. A file must end with a newline and must not contain the marker. It also renders the repo's install.md into paste.md: {{url}} becomes the bundle's URL, {{version}} the version, and {{bundle}} the full bundle text, inserted last so a file holding % or {{ passes through unchanged. Any other placeholder fails the release. Both files upload under the version.
-- install.md
Install Office {{version}}. The files follow; write each one,
and check its sha256 against its FILE line.
{{bundle}}
-- make.lua
env.release = {
artifacts = release.claude_plugin_bundle({ plugin = "office", base_url = "https://releases.example.com" }),
push = release.git_push(),
host = release.s3_upload({ bucket = "my-releases", base_url = "https://releases.example.com" }),
}
toku init --web: scaffold a web app
The web boilerplate is a working todo app: client/ holds the Lua that compiles to WebAssembly (an entry point and a sqlite-backed db module that runs in a worker), server/ the OpenResty side with its nginx.tk.conf and a sync endpoint, and res/ the migrations for both databases. One descriptor, make.lua. It is deliberately not a PWA; see Getting started for adding a service worker and TLS.
$ toku init --web --name my-app
Created web project: my-app
Next steps:
cd my-app
toku build --test # Build for testing
toku start --test # Start development server
$ find my-app -type d -not -path "*/.git*" | sort
my-app
my-app/client
my-app/client/bin
my-app/client/lib
my-app/client/lib/my-app
my-app/client/res
my-app/client/static
my-app/client/test
my-app/client/test/spec
my-app/res
my-app/res/client
my-app/res/client/migrations
my-app/res/server
my-app/res/server/migrations
my-app/server
my-app/server/lib
my-app/server/lib/my-app
my-app/server/lib/my-app/web
my-app/server/test
my-app/server/test/spec
The web loop: build, start, test, stop
build renders the client to WASM and the server tree; --test targets the test environment, without it the main one. start launches OpenResty against the built dist directory via its run.sh (openresty -p <dist> -c nginx.conf), backgrounded by default; --fg execs it in the foreground with the error log on stderr. stop shuts down both environments' servers. Environment variables prefix as usual: a dev server runs as DB_FILE=tmp.db toku start --test. toku test on a web project needs no manual start: when server specs exist it stops any old server, starts a fresh test one, waits for its pid file, and fails fast if the process dies (check the nginx error log). All of this applies to an API project unchanged.
$ toku build --test
$ DB_FILE=tmp.db toku start --test
$ toku test
$ toku stop
$ toku start --fg
Scripting start and stop
start returns once dist/server.pid exists, or fails after nginx.start_timeout seconds (60 by default) with the [emerg] line from the error log. --fg, or TOKU_FG set to anything but 0 or empty, execs openresty instead and never returns, which is what a container needs. One trap in between: the backgrounded server inherits the standard output it was started with, so a piped or non-interactive invocation (ssh host 'toku start --test; curl ...') hangs on the open pipe even though toku itself has already exited. Redirect the server's output, or drive dist/run.sh yourself: it is a plain script, it writes the same pid file, and stop reads that file to send SIGTERM.
$ toku start --test >/dev/null 2>&1 # returns once dist/server.pid exists
$ curl -s http://127.0.0.1:8080/items
$ toku stop
# or run the generated script directly, detached from the calling shell
$ cd build/default/test/dist
$ setsid nohup sh run.sh >/dev/null 2>&1
$ cat server.pid
$ kill -15 "$(cat server.pid)"
Web tests: root, client, server, logs
A web project has three suites: test/spec at the root (shared lib code), client/test/spec (compiled to WASM, run under node), and server/test/spec (run against the live test server). --root, --client, and --server select them; --single infers the suite from the path prefix. --show-logs tails the test server's access.log and error.log alongside the run, and toku stop cleans up the tail process with the server; toku stop --test stops only the test server and leaves a main one running. Server specs run under the same interpreter the server itself uses: OpenResty's LuaJIT when --openresty-dir (or OPENRESTY_DIR) resolves, so a rock that loads in nginx also loads in the specs. Root specs run under the plain interpreter. --lua overrides both.
$ toku test --root
$ toku test --client
$ toku test --server --show-logs
$ toku test --single client/test/spec/my-app.lua
$ toku test --single server/test/spec/my-app.lua
--tree and toku env: run against a project lua tree
toku lua --tree <name> builds a project lua tree if needed, then runs the interpreter with LUA_PATH and LUA_CPATH pointing at it, so ad-hoc scripts see the same rendered sources and pinned rocks the specs do. A library project has one tree, test. A web or API project has test and build, the test and production server trees, run under OpenResty's luajit when it's installed. Paths are absolute and the script runs in your current directory. --env, --dir and --config pick the build environment as they do elsewhere. For anything other than lua, toku env --tree <name> prints export lines to eval into your shell.
$ toku lua --tree test scripts/seed.lua
$ toku lua --env prod --tree build --string 'print(require("my_app.db"))'
$ eval "$(toku env --tree test)"
$ luarocks list
$ sqlite3 tmp.db
toku clean
Default: remove generated files inside build/<env>/ (rendered .lua sources, .d sidecars, stamp files, generated luarocks.lua and luacheck.lua) while keeping the expensive lua_modules trees. --deps removes exactly those trees instead, --all removes the whole build directory, --dry-run prints without removing, and web projects add --wasm, --client, and --server to scope the sweep. Paths outside the project directory are refused outright.
$ toku clean --dry-run
Would remove:
build/default/test/lib/my_lib.lua
build/default/test/bin/my-lib.lua
build/default/test/luarocks.lua
build/default/test/luacheck.lua
$ toku clean --deps
Removed:
build/default/test/lua_modules
build/default/test/lua_modules.ok
$ toku clean --all
Removed:
build
$ toku clean
Removed:
(nothing to clean)
toku serve: a static directory over HTTP
Serves a directory with the managed OpenResty, for a scratch site, a built web project's dist/public-staging, or a page a browser test drives. It writes a temporary nginx prefix (one worker, error log on stderr, no access log, directory listings on, index.html as the index, Cache-Control: no-store), runs openresty in the foreground and prints the URL; Ctrl-C stops it and the prefix is removed. --host defaults to 127.0.0.1 and --port to 8000. It needs the managed OpenResty, which toku setup --resty installs. It refuses a path that is not a directory ("serve: not a directory") and a path holding a space, semicolon or quote ("serve: paths with spaces or quotes are not supported"), since the path goes into nginx.conf unquoted. If openresty exits with an error, such as a port already in use, its own message prints on stderr and toku exits nonzero after removing the prefix.
$ toku serve build/default/test/dist/public-staging --port 8080
serving /home/me/my-app/build/default/test/dist/public-staging at http://127.0.0.1:8080/
toku skills: agent guidance, installed globally
Fetches the santoku agent skills from santoku.dev and installs them into your home directory: one SKILL.md per skill under ~/.claude/skills for Claude Code, and one AGENTS.md under ~/.codex for Codex and the other tools that read that standard. It never writes into a project. A manifest beside the installed skills records what it put there, so a later run removes skills that are no longer published rather than leaving stale guidance behind, and it refuses to overwrite an AGENTS.md it did not write unless you pass --force. Override the target directories with --claude-dir and --codex-dir, and see what would happen with --dry-run.
$ toku skills
[skills] fetching https://santoku.dev/skills.txt
[skills] fetching https://santoku.dev/AGENTS.md
[skills] wrote /home/you/.claude/skills/toku-setup/SKILL.md
...
[skills] wrote /home/you/.codex/AGENTS.md
toku lua: an instrumented interpreter
Runs a string or file under the configured interpreter (--lua overrides). The file comes positionally or with --file. Positionals after the file reach the script as arg[1], arg[2] and so on; with --file every positional is an argument. Put -- before the file when an argument starts with a dash. --string takes no arguments. --tree runs against a project lua tree, covered above. The instruments become -l preloads: --trace loads santoku.trace for line tracing, and --serialize loads santoku.autoserialize, which wraps the global print so tables come out as readable Lua literals instead of table: 0x... addresses.
$ toku lua scripts/load.lua
$ toku lua --string 'print(1 + 2)'
3
$ toku lua --serialize --string 'print({ a = 1, b = { 2, 3 } })'
$ toku lua --trace scripts/step-through.lua
$ toku lua scripts/report.lua input.csv 10
$ toku lua -- scripts/report.lua -v input.csv
$ toku lua --string 'x()' extra
toku lua takes no script arguments with --string: extra
toku template: standalone rendering
The same engine that expands .tk files in the build, exposed directly. -f renders one file (- is stdin or stdout), -d renders a directory tree with -t trimming a prefix from output paths, and -c loads a Lua config whose env table becomes the render environment. -M writes a make-style .d file next to each output: one line naming the source and every file the template read through readfile. toku template injects readfile only; depend exists in project builds.
$ echo '<% return "hello" %>' | toku template -f - -o -
hello
$ toku template -d res/tmpl -t res/tmpl -o build/out -c cfg.lua
$ toku template -f res/index.tk.html -o build/index.html -M
$ cat build/index.html.d
res/index.tk.html: res/header.html
toku bundle: one file in, one executable out
The CLI face of santoku-bundle (see its tab for the library). It scans the entry for string-literal require calls, resolves them against --path and --cpath, merges every Lua module into one file via package.preload, embeds the (optionally luac-stripped) bytecode in a generated C program that also preloads the C extensions' luaopen_ symbols, and compiles it, printing the compile command. Four options are required: --input, --output-directory, --path and --cpath. Unlike toku install --bundled, this command derives no compile flags for you, so --flags is the complete list and must carry the -I that finds lua.h and the -l that supplies liblua, or the compile fails before it starts. Dynamic requires need --mod; names to leave to the runtime take --ignore; --env NAME VALUE bakes in a setenv at startup; --deps writes a make .d file covering every bundled module. Where a C extension ships a .link sidecar it is linked statically, and where it does not the .so is linked directly and a warning names it.
$ toku bundle --input bin/tool.lua --output-directory out \
--path "lua_modules/share/lua/5.1/?.lua" \
--cpath "lua_modules/lib/lua/5.1/?.so" \
--luac-default \
--flags="-O2 -I$(luarocks config variables.LUA_INCDIR) \
-L$(luarocks config variables.LUA_LIBDIR) -llua5.1 -lm" \
--env TOOL_HOME /opt/tool \
--mod tool.plugins --ignore debug
cc out/tool.c -O2 -I... -L... -llua5.1 -lm \
lua_modules/lib/lua/5.1/tool/capi.o -o out/tool
$ ls out
tool tool.c tool.lua tool.luac