Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Keleusma as a Scripting and Automation Tool

Navigation: Guide | Documentation Root

This reference consolidates the operator-facing story for using the keleusma command-line tool to write devops and sysadmin automation. The numbered guide chapters teach the language; this document gathers the pieces that turn a script into a deployable command, a streaming filter, or a long-running daemon, and explains how to distribute that artefact as signed and optionally encrypted bytecode.

The material here is drawn together from several chapters and reference documents that each cover one facet. Where a topic has a fuller treatment elsewhere, this document links to it rather than restating it.

Audience

Operators and script authors who want to run Keleusma programs as standalone tools rather than embed the runtime in a Rust host. Readers who want to embed the runtime should start at Chapter 31 instead.

Three ways to run a script

A source script runs in three interchangeable forms. All three accept the same script arguments.

FormCommandPlatform
Explicit subcommandkeleusma run script.kelAll
Extension shorthandkeleusma script.kelAll
Shebang executable./script.kel after chmod +xmacOS and Linux

The shebang form requires the first source line to read #!/usr/bin/env keleusma. The lexer skips that line while preserving source line numbers in diagnostics, so the same file remains compilable on Windows, where it runs through keleusma run. Chapter 2 introduces the shebang at tutorial pace. A compiled bytecode file may also carry a shebang, covered in Chapter 25.

Script arguments

A script reads its own arguments through the shell bundle. shell::arg(0) returns the script path and shell::arg(1) onward the positional arguments the launcher passed after it, mirroring the $0 and $1 convention of POSIX shells. shell::arg_count() reports the number of entries, counting argument zero.

The CLI collects positional arguments for both keleusma run script.kel a b c and the shebang form ./script.kel a b c. A -- terminator marks the end of CLI options, after which every token is treated as a script argument even when it begins with a dash. An unrecognized leading-dash token before -- is rejected as a flag typo rather than passed through.

./report.kel --since 2026-01-01 -- --raw

In that invocation the CLI consumes nothing it does not recognize, and the script receives --since, 2026-01-01, and --raw as positional arguments one through three.

Entry kinds map to deployment shapes

A script declares one of three entry kinds. The CLI inspects the compiled entry block and drives it accordingly. The entry kind is the script author’s primary lever for the deployment shape. Chapter 15, Chapter 16, and Chapter 17 cover the language semantics; the table below maps each to its operational role.

Entry kindDeclarationTerminationOperational shape
Atomicfn main() -> WordRuns to completion in one callOne-shot command. Suited to cron jobs, build steps, and manual invocation.
Stagedyield main(tick: Word) -> WordReturns rather than yieldsCooperative task that pauses and resumes across ticks, then finishes.
Daemonloop main(tick: Word) -> WordOnly on shell::exit or a termination signalLong-running service. Returning is treated as an error.

A few operational consequences follow from how the CLI drives each kind.

  • An atomic fn main has its returned value printed to standard output. The process exit status is not taken from the return value. A script that needs a specific exit code calls shell::exit(code). The repository link-checker uses exactly this pattern.
  • A loop main daemon is rate-limited with --tick-interval, which accepts humanized durations such as 100ms, 1s, 1m, 1h, 1d, and 1w, up to a maximum of four weeks. The runtime is genuinely idle between iterations, so a long-cadence daemon costs page-fault avoidance rather than computation. See METRICS.md for the memory-residency analysis and SECURITY_POLICY.md for the operator guide to daemon cadences.

Delegating work through the shell bundle

Without host-registered natives the language admits only pure total functions and the productive-divergent loop. The CLI registers the shell bundle, which is what makes orchestration possible. A script delegates work to ordinary command-line programs, branches on the Word exit code each returns, accumulates across the run in a mutable private data segment, and sets its own process exit status.

NativePurpose
shell::run(cmd) -> (Word, Text)Run a command through sh -c; returns exit code and stdout. Stderr is discarded.
shell::run_full(cmd) -> (Word, Text, Text)As above, returning exit code, stdout, and stderr.
shell::run_checked(cmd) -> TextRun a command; trap on a non-zero exit.
shell::run_timeout(cmd, ms) -> (Word, Text)Run a command with a wall-clock deadline.
shell::read_file, shell::write_file, shell::append_fileFile input and output.
shell::writeln_err, shell::write_errLog-shaped output to stderr.
shell::arg, shell::arg_countThe script’s own arguments.
shell::exit(code)Terminate the process with an exit status.

The full list, signatures, and per-native contracts are in STANDARD_LIBRARY.md. The bundle’s capability assessment and its standing limitations are in SHELL_AUDIT.md.

The repository’s own Markdown link-checker, scripts/check-md-links.kel, is a complete worked example of the orchestrator pattern and runs in continuous integration. It delegates the partial text-scanning work to POSIX tools through shell::run, drives control flow on the returned Word, accumulates failures in a private data segment described in Chapter 18, and propagates the result through shell::exit. The constructs that make the orchestration total, the partial-operation family, are covered in Chapter 23.

Distributing scripts as signed and encrypted bytecode

A finished script can be compiled to bytecode and delivered as a tamper-evident, optionally confidential artefact. The two policies are independent. Neither, signing only, encryption only, or both may be active.

StepCommand
Generate a signing keypairkeleusma keygen --seed sign.seed --public sign.pub
Generate an encryption keypairkeleusma keygen --kind encryption --seed dest.seed --public dest.pub
Compile, sign, and encryptkeleusma compile script.kel --signing-key sign.seed --encryption-key dest.pub -o script.kel.bin
Run, verifying and decryptingkeleusma run script.kel.bin --verifying-key sign.pub --decryption-key dest.seed

Signing requires the entry function to carry the signed modifier; otherwise the toolchain produces unsigned bytecode and refuses the signing key. Encryption uses X25519 key agreement so the artefact is sealed to a recipient’s public key and opened with the matching private seed. The signing and encryption design is covered in Chapter 26 and the wire format in WIRE_FORMAT.md.

Strict-mode key stores

On a managed host the trust decision is taken out of the operator’s hands. In strict signing mode the CLI loads trusted public keys from a system directory and refuses source files, unsigned bytecode, and bytecode signed by keys outside the store. The --verifying-key argument is rejected so an unprivileged operator cannot relax the policy. Strict encryption mode behaves analogously for the decryption-key store. The directories, environment variables, and threat model are documented in SECURITY_POLICY.md.

A signed and encrypted bytecode artefact that also carries a shebang is directly executable through the operating system shell while remaining tamper-evident and confidential. This is the distributable-runbook delivery shape, described for courier-delivered media in SECURITY_POLICY.md.

Running several scripts under supervision

For workloads that need more than one script, the run-tasks subcommand drives a set of scripts from a TOML manifest through a cooperative scheduler with an event queue, supervised restart, and per-task signing and encryption policy. It lifts the cooperative-RTOS pattern from examples/rtos/ onto the desktop and server.

keleusma run-tasks fleet.toml --quiet

The manifest declares a scheduler tick interval, a [[task]] table for each script with a name, a bytecode path, and a restart policy of never, on_error, or always, and an optional event table. The manifest format, validation rules, and scheduler semantics are documented in RUN_TASKS.md.

Where the static guarantees do not hold

The language otherwise rejects programs whose worst-case execution time or memory cannot be statically bounded. The orchestration natives are an explicit boundary where that guarantee does not extend.

  • A subprocess spawned through shell::run and its siblings has time and memory the verifier cannot model. The static worst-case bounds do not cross these calls. shell::run_timeout caps wall-clock time per call but not memory.
  • shell::read_file and the run natives allocate output buffers on the host heap, outside the script arena’s budget.
  • The orchestration natives read mutable external state, so a script that uses them is not reproducible.
  • shell::exit terminates the host process directly.

These properties belong to the ambient-authority shell bundle, not to the language. A deployment that needs confinement or proven bounds registers a curated subset or its own narrower natives in a custom host rather than shipping the full bundle to untrusted scripts. The complete limitation list is in SHELL_AUDIT.md.