Scriptable automation engine — Lua orchestration over sandboxed polyglot actions.
Kael lets you wrap scripts written in any language (Python, Node.js, Shell) into a composable kit of tools, then run them individually or orchestrate them together with Lua. Each tool runs in Docker for isolation or natively for speed — your choice.
Grab the latest binary for your platform from GitHub Releases. Kael ships as a single binary with no runtime dependencies beyond what your tools need.
# Example: Linux amd64
tar xzf kael_*_linux_amd64.tar.gz
sudo mv kael /usr/local/bin/kael setupThis walks you through initial configuration: where your kit lives, and optionally wiring up an AI tool (Claude Code or OpenCode) to help analyze scripts when you onboard them.
Configuration is written to ~/.kael/config.yaml. You can also set any value via KAEL_ environment variables (e.g. KAEL_KIT=/path/to/kit).
Tools live in namespaces — think of them as folders in a tree. Initialize one:
kael kit init miscThis creates ~/.kael/kit/misc/ with an init.lua and wires it into the root kit. Namespaces can be nested:
kael kit init infra.kubernetesPoint kael at a script (local file or git repo) and tell it which namespace to put it in:
# From a local script
kael kit add ./scripts/download.py misc
# From a git repo
kael kit add git@github.com:user/repo.git misc
# From a monorepo — use // to specify a path within the repo
kael kit add git@github.com:org/scripts.git//tools/check.py miscIf you have an AI tool configured, kael will analyze the script and generate a complete tool definition automatically. Additional flags:
| Flag | Description |
|---|---|
--manual |
Skip AI analysis, generate a skeleton definition |
--force |
Overwrite an existing tool definition |
--prompt "..." |
Additional instructions for the AI analysis |
--executor native |
Override executor (native, docker) |
--entrypoint main.py |
Override entrypoint script filename |
--subdir tools/ |
Override subdirectory within source |
--tag v1.2.0 |
Pin to a git tag, branch, or commit |
--type python |
Override script type (python, shell, node) |
--name my_tool |
Override tool name (Lua filename) |
Override flags are applied after AI analysis, so you can let the AI do most of the work and just correct specific fields.
# Execute a single tool
kael exec misc.download --url https://example.com
# Pipe JSON input
echo '{"url": "https://example.com"}' | kael exec misc.downloadOutput comes back as JSON on stdout, so you can pipe it into jq or other tools.
Under the hood, each tool is a Lua file that calls tools.define_tool(). Here's a complete example:
-- ~/.kael/kit/misc/download.lua
return tools.define_tool({
source = "git@github.com:user/python-downloader.git",
entrypoint = "download.py",
type = "python",
deps = {"requests"},
input_adapter = "json",
output_adapter = "json",
schema = {
input = {
url = "string",
},
},
})| Field | Default | Description |
|---|---|---|
source |
(required) | Local path or git URL to the script directory |
entrypoint |
auto-detected | Script filename to run |
type |
auto-detected | "python", "node", or "shell" |
executor |
"docker" |
"docker" for isolation, "native" for speed |
tag |
— | Git tag, branch, or commit hash |
subdir |
— | Subdirectory within source (for monorepos) |
timeout |
— | Execution timeout in seconds |
deps |
— | Packages to install (pip / npm / apk) |
env |
— | Host environment variables to pass through |
input_adapter |
"args" |
How input reaches the script: "args", "json", or "positional_args" |
args_order |
— | Ordered field names for positional_args adapter |
output_adapter |
"text" |
How output is read: "text", "json", or "lines" |
schema |
— | Input type declarations for validation |
defaults |
— | Default values merged into every call |
Input adapters control how parameters are passed to your script:
args— Converts{name = "world", count = 3}into--name world --count 3. Booleans become standalone flags.json— Sends the full input as JSON on stdin.positional_args— Emits values as bare positional arguments in the order specified byargs_order. Requiresargs_orderto be set. Any remaining keys not inargs_orderare appended as--key valueflags.
-- Script uses $1 for tenant and $2 for region
input_adapter = "positional_args",
args_order = {"tenant_name", "region"},Output adapters control how stdout is interpreted:
text— Returns raw stdout as{output = "..."}.json— Parses stdout as JSON.lines— Splits stdout by newlines into{lines = [...]}.
Declare types for input fields. Fields are required by default; suffix with ? to make optional:
schema = {
input = {
hostnames = "array",
port = "number?",
timeout = "number?",
},
}Supported types: string, number, boolean, array, object.
If several tools share the same source or executor, extract a defaults function:
-- infra/kubernetes/defaults.lua
return function()
return {
source = "/path/to/kubernetes/scripts",
executor = "native",
}
end
-- infra/kubernetes/images_in_use.lua
local base = require("infra.kubernetes.defaults")()
base.entrypoint = "images_in_use.sh"
base.type = "shell"
base.schema = {
input = { namespace = "string?" },
}
return tools.define_tool(base)For more complex workflows, write a Lua script that calls multiple tools:
-- scan.lua
local results = kit.infra.analysis.url_probe({
urls = {"https://example.com", "https://example.org"},
timeout = 10,
})
for _, r in ipairs(results.results) do
print(r.url, r.status_code)
end
local certs = kit.infra.analysis.ssl_check({
hostnames = {"example.com", "example.org"},
})
print(inspect(certs))Run it:
kael run scan.luaThe kit table mirrors your namespace tree, so kit.infra.analysis.ssl_check(...) calls the tool at infra/analysis/ssl_check.lua.
These are available in all Lua scripts and in the REPL:
| Function | Description |
|---|---|
pp(val, depth?) |
Pretty-print a value to stdout (default depth 4) |
inspect(val) |
Return a string representation of any value |
keys(tbl) |
Return a list of all keys in a table |
pluck(list, field) |
Extract one field from each table in a list |
count(tbl) |
Count entries in a table (arrays and maps) |
jq(val, filter) |
Pipe a value through jq and return the result |
readfile(path) |
Read a file, return contents as a string |
writefile(path, str) |
Write a string to a file |
json.encode(val) |
Serialize to JSON string |
json.pretty(val) |
Serialize to indented JSON string |
json.decode(str) |
Parse a JSON string |
Start an interactive session to explore your kit and run tools:
kael replkael> kit.infra.azure.get_tenant_resources({tenant_name = "acme"})
{
name = "my-vm",
type = "Microsoft.Compute/virtualMachines",
location = "eastus"
}
Features:
- Tab completion on
kit.*tool paths - Auto-print — expressions display their result automatically, no
print()needed - Multiline — blocks (
if/for/function) are detected and continued with indentation - History — persisted to
~/.kael/history - Type
helpfor a list of all available helpers
# List all tools in the kit
kael kit list
# Validate kit loads without errors
kael kit validate
# Show full details for a tool
kael kit describe infra.analysis.ssl_check
# Open a tool definition in $EDITOR
kael kit edit infra.analysis.ssl_check
# Remove a tool
kael kit remove misc.downloadGit-sourced tools are cloned once to ~/.kael/cache/ and Docker images are built once. To force a re-fetch and rebuild, use --refresh:
kael run --refresh scan.lua
kael exec --refresh infra.ssl_check --hostname example.com
kael repl --refreshThis runs git fetch + reset on cached repos and forces a fresh docker build.
Kael reads configuration from ~/.kael/config.yaml, environment variables (KAEL_ prefix), and CLI flags — in that order of precedence.
# ~/.kael/config.yaml
kit: /home/user/.kael/kit
ai:
tool: claude
command: "claude -p"
skill_dir: /home/user/.claude/skills| Setting | Flag | Env var | Description |
|---|---|---|---|
kit |
--kit |
KAEL_KIT |
Path to the kit directory |
ai.tool |
— | KAEL_AI_TOOL |
AI tool name (claude or opencode) |
ai.command |
— | KAEL_AI_COMMAND |
Command to invoke the AI tool |
ai.skill_dir |
— | KAEL_AI_SKILL_DIR |
Where AI skills are installed |
Kael itself is a single binary. Depending on how you run your tools, you'll also need:
- Docker or Podman — for containerized execution (default)
- Git — for tools sourced from git repositories
- Python 3 / Node.js / Shell — for native execution of the respective tool types