Core Lua API
ptool exposes these core runtime helpers directly under ptool and p.
ptool run <lua_file> runs a Lua script and injects the global variable ptool
(or its alias p; for example, p.run is equivalent to ptool.run). For
files ending in .lua, ptool <lua_file> is a CLI shortcut with the same
behavior.
The embedded Lua runtime keeps the base Lua globals and exposes only these standard libraries by default:
tablestringmathutf8
Host-facing built-in modules such as io, os, and package are intentionally
not available. Use ptool APIs such as ptool.fs, ptool.os, ptool.path,
and ptool.run for filesystem, environment, process, network, and other
runtime operations instead.
If you want to pass arguments to a Lua script, you can do it like this:
ptool run script.lua --name alice -v a.txt b.txt
ptool script.lua --name alice -v a.txt b.txt
The arguments can then be parsed with ptool.args.parse(...).
Here is an example script:
ptool.use("v0.1.0")
ptool.run("echo", {"hello", "world"})
Shebang is supported, so you can add this to the top of the file:
#!/usr/bin/env ptool
ptool.use
v0.1.0- Introduced.
ptool.use declares the minimum required ptool version for a script.
ptool.use("v0.1.0")
- The argument is a semantic version string (SemVer) and supports an optional
vprefix, such asv0.1.0or0.1.0. - If the required version is higher than the current
ptoolversion, the script exits immediately with an error saying that the currentptoolversion is too old.
ptool.unindent
v0.1.0- Introduced.
ptool.unindent processes multi-line strings by removing the | prefix after
leading indentation on each line and trimming leading and trailing blank lines.
local str = ptool.unindent([[
| line 1
| line 2
]])
This is equivalent to:
local str = [[line 1
line 2]]
ptool.inspect
v0.1.0- Introduced.
ptool.inspect(value[, options]) renders a Lua value as a readable Lua-style
string. It is primarily intended for debugging and displaying table contents.
value(any, required): The Lua value to inspect.options(table, optional): Rendering options. Supported fields:indent(string, optional): Indentation used for each nesting level. Defaults to two spaces.multiline(boolean, optional): Whether tables are rendered across multiple lines. Defaults totrue.max_depth(integer, optional): Maximum nesting depth to render. Deeper values are replaced with<max-depth>.
- Returns:
string.
Behavior:
- Array-like entries (
1..n) are rendered first. - Remaining table fields are rendered after the array part in stable key order.
- Identifier-like string keys are rendered as
key = value; other keys are rendered as[key] = value. - Recursive table references are rendered as
<cycle>. - Functions, threads, and userdata are rendered as placeholder values such as
<function>and<userdata>.
Example:
local value = {
"hello",
user = { name = "alice", tags = {"dev", "ops"} },
}
value.self = value
print(ptool.inspect(value))
print(ptool.inspect(value, { multiline = false }))
ptool.ask
v0.1.0- Introduced.v0.5.0- Added prompt validation options and prompt subcommands.
ptool.ask provides interactive prompts. You can call it directly for text
input, or use its prompt subcommands for confirmation, selection, multi-select,
and secret input.
Common behavior:
- All
ptool.askprompts require an interactive TTY. Running them in a non-interactive environment raises an error. - If the user cancels a prompt, the script raises an error.
- Unknown option names or invalid option value types raise an error.
ptool.ask
ptool.ask(prompt[, options]) asks the user for a line of text and returns the
answer.
prompt(string, required): The prompt shown to the user.options(table, optional): Prompt options. Supported fields:default(string, optional): Default value used when the user submits an empty answer.help(string, optional): Extra help text shown below the prompt.placeholder(string, optional): Placeholder text shown before the user starts typing.required(boolean, optional): Whether the answer must be non-empty.allow_empty(boolean, optional): Whether to accept an empty answer. Defaults totrue.trim(boolean, optional): Whether to trim leading and trailing whitespace from the answer before returning it.min_length(integer, optional): Minimum accepted character length.max_length(integer, optional): Maximum accepted character length.pattern(string, optional): Regular expression the answer must match.
- Returns:
string.
Example:
local project = ptool.ask("Project name?", {
placeholder = "my-tool",
help = "Lowercase letters, digits, and dashes only",
required = true,
trim = true,
pattern = "^[a-z0-9-]+$",
})
ptool.ask.confirm
v0.5.0- Introduced.
ptool.ask.confirm(prompt[, options]) asks the user for a yes/no answer.
prompt(string, required): The prompt shown to the user.options(table, optional): Prompt options. Supported fields:default(boolean, optional): Default answer used when the user presses Enter without typing.help(string, optional): Extra help text shown below the prompt.
- Returns:
boolean.
Example:
local confirmed = ptool.ask.confirm("Continue?", {
default = true,
})
ptool.ask.select
v0.5.0- Introduced.
ptool.ask.select(prompt, items[, options]) asks the user to choose one item
from a list.
prompt(string, required): The prompt shown to the user.items(table, required): Candidate items. Each entry may be:- A string, which is used as both the display label and the returned value.
- A table like
{ label = "Patch", value = "patch" }.
options(table, optional): Prompt options. Supported fields:help(string, optional): Extra help text shown below the prompt.page_size(integer, optional): Maximum number of rows shown at once.default_index(integer, optional): 1-based index of the initially selected item.
- Returns:
string.
Example:
local bump = ptool.ask.select("Select bump type", {
{ label = "Patch", value = "patch" },
{ label = "Minor", value = "minor" },
{ label = "Major", value = "major" },
}, {
default_index = 2,
})
ptool.ask.multiselect
v0.5.0- Introduced.
ptool.ask.multiselect(prompt, items[, options]) asks the user to choose zero
or more items from a list.
prompt(string, required): The prompt shown to the user.items(table, required): Candidate items. The format is the same asptool.ask.select.options(table, optional): Prompt options. Supported fields:help(string, optional): Extra help text shown below the prompt.page_size(integer, optional): Maximum number of rows shown at once.default_indexes(table, optional): 1-based indexes selected by default.min_selected(integer, optional): Minimum number of items that must be selected.max_selected(integer, optional): Maximum number of items that may be selected.
- Returns:
table.
Example:
local targets = ptool.ask.multiselect("Select targets", {
"linux",
"macos",
"windows",
}, {
default_indexes = { 1, 2 },
min_selected = 1,
})
ptool.ask.secret
v0.5.0- Introduced.
ptool.ask.secret(prompt[, options]) asks the user for secret input such as a
token or password.
prompt(string, required): The prompt shown to the user.options(table, optional): Prompt options. Supported fields:help(string, optional): Extra help text shown below the prompt.required(boolean, optional): Whether the answer must be non-empty.allow_empty(boolean, optional): Whether to accept an empty answer. Defaults tofalse.confirm(boolean, optional): Whether to ask the user to type the secret twice. Defaults tofalse.confirm_prompt(string, optional): Custom prompt for the confirmation step.mismatch_message(string, optional): Custom error message shown when the two answers do not match.display_toggle(boolean, optional): Whether to allow temporarily showing the typed secret.min_length(integer, optional): Minimum accepted character length.max_length(integer, optional): Maximum accepted character length.pattern(string, optional): Regular expression the answer must match.
- Returns:
string.
Example:
local token = ptool.ask.secret("API token?", {
confirm = true,
min_length = 20,
})
ptool.config
v0.1.0- Introduced.
ptool.config sets runtime configuration for the script.
Currently supported fields:
run(table, optional): Default configuration forptool.run. Supported fields:echo(boolean, optional): Default echo switch. Defaults totrue.check(boolean, optional): Whether failures raise an error by default. Defaults tofalse.confirm(boolean, optional): Whether to require confirmation before execution by default. Defaults tofalse.retry(boolean, optional): Whether to ask the user whether to retry after a failed execution whencheck = true. Defaults tofalse.
Example:
ptool.config({
run = {
echo = false,
check = true,
confirm = false,
retry = false,
},
})
ptool.cd
v0.1.0- Introduced.
ptool.cd(path) updates ptool's runtime current directory.
path(string, required): Target directory path, absolute or relative.
Behavior:
- Relative paths are resolved from the current
ptoolruntime directory. - The target must exist and must be a directory.
- This updates
ptoolruntime state and affects APIs that use runtime cwd (such asptool.run,ptool.path.abspath, andptool.path.relpath).
Example:
ptool.cd("foobar")
local res = ptool.run({ cmd = "pwd", stdout = "capture" })
print(res.stdout)
ptool.script_path
v0.4.0- Introduced.
ptool.script_path() returns the absolute path of the current entry script.
- Returns:
string|nil.
Behavior:
- When running through
ptool run <file>, this returns the entry script path as an absolute, normalized path. - The returned path is fixed when the runtime starts and does not change after
ptool.cd(...). - In
ptool repl, this returnsnil.
Example:
local script_path = ptool.script_path()
local script_dir = ptool.path.dirname(script_path)
local project_root = ptool.path.dirname(script_dir)
ptool.try
v0.4.0- Introduced.
ptool.try(fn) runs fn and converts raised errors into return values.
fn(function, required): Callback to execute.- Returns:
ok, value, err.
Return value rules:
- On success,
ok = true,err = nil, andvaluecontains the callback result. - If the callback returns no values,
valueisnil. - If the callback returns one value,
valueis that value. - If the callback returns multiple values,
valueis an array-like table. - On failure,
ok = false,value = nil, anderris a table.
Structured error fields:
kind(string): Stable error category such asio_error,command_failed,invalid_argument,http_error, orlua_error.message(string): Human-readable error message.op(string, optional): API or operation name such asptool.fs.read.detail(string, optional): Extra detail for the failure.path(string, optional): Path involved in a filesystem failure.input(string, optional): Original input that failed to parse or validate.cmd(string, optional): Command name for command failures.status(integer, optional): Exit status or HTTP status when available.stderr(string, optional): Captured stderr for command failures.url(string, optional): URL involved in an HTTP failure.cwd(string, optional): Effective working directory for command failures.target(string, optional): SSH target for SSH-related command failures.retryable(boolean): Whether retrying may make sense. Defaults tofalse.
Behavior:
ptoolAPIs raise structured errors.ptool.tryconverts them into theerrtable above so callers can branch onerr.kindand related fields.- Plain Lua errors are also caught. In that case,
err.kindislua_error, and onlymessageis guaranteed. ptool.tryis the recommended way to handle errors from APIs such asptool.fs.read,ptool.http.request,ptool.run(..., { check = true }), andres:assert_ok().
Example:
local ok, content, err = ptool.try(function()
return ptool.fs.read("missing.txt")
end)
if not ok and err.kind == "io_error" then
print(err.op, err.path)
end
local ok2, _, err2 = ptool.try(function()
local res = ptool.run({
cmd = "sh",
args = {"-c", "echo bad >&2; exit 7"},
stderr = "capture",
})
res:assert_ok()
end)
if not ok2 and err2.kind == "command_failed" then
print(err2.cmd, err2.status, err2.stderr)
end
ptool.run
v0.1.0- Introduced.
ptool.run executes external commands from Rust.
The following call forms are supported:
ptool.run("echo hello world")
ptool.run("echo", "hello world")
ptool.run("echo", {"hello", "world"})
ptool.run("echo hello world", { echo = true })
ptool.run("echo", {"hello", "world"}, { echo = true })
ptool.run({ cmd = "echo", args = {"hello", "world"} })
ptool.run({ cmd = "echo", args = {"hello"}, stdout = "capture" })
Argument rules:
ptool.run(cmdline):cmdlineis split using shell-style (shlex) rules. The first item is treated as the command and the rest as arguments.ptool.run(cmd, argsline):cmdis used directly as the command, andargslineis split into an argument list using shell-style (shlex) rules.ptool.run(cmd, args):cmdis a string andargsis an array of strings.ptool.run(cmdline, options):optionsoverrides settings for this invocation, such asecho.ptool.run(cmd, args, options):argscan be either a string or an array of strings, andoptionsoverrides settings for this invocation, such asecho.ptool.run(options):optionsis a table.- When the second argument is a table: if it is an array (consecutive integer
keys
1..n), it is treated asargs; otherwise it is treated asoptions.
Return value rules:
- A table is always returned with the following fields:
ok(boolean): Whether the exit code is0.code(integer|nil): The process exit code. If the process was terminated by a signal, this isnil.cmd(string): Command name used for the execution.cwd(string): Effective working directory used for the execution.stdout(string, optional): Present whenstdout = "capture".stderr(string, optional): Present whenstderr = "capture".assert_ok(self)(function): Raises a structured error whenok = false. The error kind iscommand_failed, and the error may includecmd,status,stderr, andcwd.
- The default value of
checkcomes fromptool.config({ run = { check = ... } }). If not configured, it defaults tofalse. Whencheck = false, callers can inspectokthemselves or callres:assert_ok(). - When both
check = trueandretry = true,ptool.runasks whether the failed command should be retried before raising the final error. - When
check = true,ptool.runraises the same structuredcommand_failederror thatres:assert_ok()raises. Useptool.try(...)if you want to catch and inspect it from Lua.
Example:
ptool.config({ run = { echo = false } })
ptool.run("echo from ptool")
ptool.run("echo", "from ptool")
ptool.run("echo", {"from", "ptool"})
ptool.run("echo from ptool", { echo = true })
ptool.run("echo", {"from", "ptool"}, { echo = true })
ptool.run("pwd")
local res = ptool.run({
cmd = "sh",
args = {"-c", "echo bad >&2; exit 7"},
stderr = "capture",
})
print(res.ok, res.code)
res:assert_ok()
ptool.run(options) is also supported, where options is a table with the
following fields:
cmd(string, required): The command name or executable path.args(string[], optional): The argument list.cwd(string, optional): The child process working directory.env(table, optional): Additional environment variables, where keys are variable names and values are variable values.echo(boolean, optional): Whether to echo command information for this execution. If omitted, the value fromptool.config({ run = { echo = ... } })is used; if that is also unset, the default istrue.check(boolean, optional): Whether to raise an error immediately when the exit code is not0. If omitted, the value fromptool.config({ run = { check = ... } })is used; if that is also unset, the default isfalse.confirm(boolean, optional): Whether to ask the user for confirmation before execution. If omitted, the value fromptool.config({ run = { confirm = ... } })is used; if that is also unset, the default isfalse.retry(boolean, optional): Whether to ask the user whether to retry after a failed execution whencheck = true. If omitted, the value fromptool.config({ run = { retry = ... } })is used; if that is also unset, the default isfalse.stdout(string, optional): Stdout handling strategy. Supported values:"inherit": Inherit to the current terminal (default)."capture": Capture intores.stdout."null": Discard the output.
stderr(string, optional): Stderr handling strategy. Supported values:"inherit": Inherit to the current terminal (default)."capture": Capture intores.stderr."null": Discard the output.
- When
confirm = true:- If the user refuses the execution, an error is raised immediately.
- If the current environment is not interactive (no TTY), an error is raised immediately.
- When
retry = trueandcheck = true:- If the command fails,
ptool.runasks whether to retry the same command. - If the current environment is not interactive (no TTY), an error is raised immediately instead of prompting for retry.
- If the command fails,
Example:
ptool.run({
cmd = "echo",
args = {"hello"},
env = { FOO = "bar" },
})
local res = ptool.run({
cmd = "sh",
args = {"-c", "printf 'out'; printf 'err' >&2; exit 7"},
stdout = "capture",
stderr = "capture",
check = false,
})
print(res.ok, res.code)
print(res.stdout)
print(res.stderr)
res:assert_ok()
ptool.run_capture
Unreleased- Introduced.
ptool.run_capture executes external commands from Rust with the same call
forms, argument rules, return value rules, and options as ptool.run.
The difference is only the default stream handling:
stdoutdefaults to"capture".stderrdefaults to"capture".
You can still override either field explicitly in options.
Example:
local res = ptool.run_capture("echo hello world")
print(res.stdout)
local res2 = ptool.run_capture({
cmd = "sh",
args = {"-c", "printf 'out'; printf 'err' >&2"},
})
print(res2.stdout)
print(res2.stderr)
local res3 = ptool.run_capture("echo hello", {
stderr = "inherit",
})
print(res3.stdout)