Skip to main content

timon-scripting

Use when writing timon scripts to keep - named scripts in ~/.config/timon or a project's .timon, shared lib modules, settings in init.lua, structured concurrency with scope, and Fennel. Load the timon skill first for the basics.

Source facts

Repository
joshrotenberg/timon
Last source activity
October 17, 2026 at 03:26
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
timon-scripting
description
Use when writing timon scripts to keep - named scripts in ~/.config/timon or a project's .timon, shared lib modules, settings in init.lua, structured concurrency with scope, and Fennel. Load the timon skill first for the basics.
# Scripting with timon Run `timon` as a command of its own. A host that allows only `timon` commands denies the whole line when it has `;`, `&&`, `|`, `$?`, a redirect or a heredoc around timon. Pass multi-line Lua as one single-quoted argument, which may span lines and cannot contain `'`: use `"` for Lua strings, and put code whose text needs an apostrophe in a file run with `timon eval -f FILE`. Read files with `fs.read` in Lua instead of piping them in, and skip `$?`: a failure prints `timon: kind: message` and exits nonzero. ## Scripts A script is a `.lua` or `.fnl` file. Its arguments are in `arg` (`arg[1]`, ...) and `...`. ```lua fragment #!/usr/bin/env timon -- Print each argument doubled. local out = {} for i, a in ipairs(arg) do out[i] = tonumber(a) * 2 end return out ``` ```sh timon double.lua 1 2 3 # [2,4,6] ``` Flags, typed the way task options are: ```lua local opts, rest = cli.parse({ "--port", "9000", "-v", "notes.txt" }, { port = "integer?", verbose = { type = "boolean", short = "v" }, mode = { type = '"fast" | "full"', default = "fast", doc = "how hard to try" }, }) return opts.port, opts.verbose, opts.mode, rest[1] -- 9000, true, fast, notes.txt ``` In a script, `cli.parse(arg, options)`. An unknown flag or a bad value raises `usage` with did-you-mean, which exits 2; `cli.help(options)` is the text for a `--help`. Names use `_`, and the flag uses `-`: declare `max_depth`, pass `--max-depth` (or `--max_depth`). ## Named scripts and layers A named script is a task: `timon NAME` runs it. The same layout works for you and for a project: ``` ~/.config/timon/ (everywhere) <project>/.timon/ (one project) ├── init.lua settings ├── tasks.lua tasks with dependencies (the timon-tasks skill) ├── scripts/NAME.lua timon NAME [ARGS] ├── lib/NAME.lua require "NAME" ├── mcp/NAME.lua an MCP server: mcp.connect("NAME") └── skills/NAME/ SKILL.md, references/ ``` ```sh timon tasks # what is in reach, with the first line of each script's comment timon triage --all # runs scripts/triage.lua with "--all" in arg timon defs # layers, settings, scripts, lib directories ``` A project's `.timon/` loads only after a person reviews it and runs `timon trust`. Do not trust a project on someone's behalf. ## MCP servers MCP servers are timon's pods: programs whose tools a script calls. ```lua fragment -- .timon/mcp/crates.lua return { description = "crates.io", command = { "cratesio-mcp" } } ``` ```lua fragment local crates = mcp.connect("crates") -- or mcp.connect{ command = { "npx", "-y", "..." } } for _, t in ipairs(crates:tools()) do print(t.name, t.description) end local info = crates:call("get_crate_info", { name = "mlua" }) -- structured content, or text ``` A tool that reports an error raises `mcp_tool_failed`. The server stops when the scope that connected ends. From the shell: `timon mcp servers`, `timon mcp tools crates`, `timon mcp call crates get_crate_info name=mlua`. ## Script runs and resume A script that runs agents can describe itself. It returns `run` and its metadata instead of its result. `budget` applies to `run` like a scope budget: ```lua fragment -- .timon/scripts/review.lua -- Review each file named on the command line, then summarize. return { description = "Review files, then summarize", phases = { "review", "summary" }, budget = { runs = 10, usd = 1 }, run = function(...) phase("review") local reviews = par.map({ ... }, function(file) return agent.run{ prompt = "Review " .. file, label = file, output = "review" } end) phase("summary") return agent.ask("Summarize: " .. json.encode(reviews)) end, } ``` Every `timon NAME` and `timon FILE` is recorded, with its arguments, the sha256 of its source and its totals. Its agent runs point to it with `parent`: ```sh timon review src/a.rs src/b.rs timon runs --scripts # script runs: status, runs, cached, cost timon runs show s_1a2b # one script run: request, result, agent runs timon --resume s_1a2b review src/a.rs src/b.rs ``` `--resume` runs the script again. An agent run with the same request as one that succeeded in the earlier run returns that record with `cached = true`, and spends nothing. Read-only runs are reused. Runs with write access are reused only with `idempotent = true`, or in a worktree nothing has changed since. A cached record's text is the redacted text the record kept. ## Shared code in lib/ ```lua -- ~/.config/timon/lib/gh_util.lua local M = {} function M.ready(prs) return tbl.filter(prs, function(pr) return not pr.draft end) end return M ``` ```lua fragment -- any script local util = require("gh_util") ``` ## Settings `init.lua` returns a table of settings and can do no I/O. Project settings override user settings key by key. ```lua -- ~/.config/timon/init.lua return { defaults = { provider = "claude", model = "opus" }, limits = { concurrency = 4 }, } ``` ## Concurrency `scope(fn)` runs `fn(s)`; tasks spawned on `s` finish before it returns. If any task raises, the others are cancelled and the error is raised. ```lua local a, b = scope(function(s) local ha = s:spawn(function() sleep("20ms") return "slow" end) local hb = s:spawn(function() return "fast" end) return ha:await(), hb:await() end) return a, b -- slow, fast ``` Keep going past failures with `settle`: ```lua local results = par.map({ 1, 2, 3 }, function(n) if n == 2 then error({ kind = "bad_input", message = "two" }) end return n * 10 end, { settle = true }) return tbl.map(results, function(r) return r.ok and r.value or r.error.kind end) -- [10,"bad_input",30] ``` `par.race` returns the first result and cancels the rest: ```lua local value, i = par.race( function() sleep(10) return "slow" end, function() sleep("5ms") return "fast" end ) return value, i -- fast, 2 ``` ## Fennel Every module works from Fennel. Use `.fnl` files, `timon eval --fnl`, or `require` a `.fnl` module. ```fennel (local prs [{:number 12 :draft false} {:number 14 :draft true}]) (-> (tbl.filter prs (fn [pr] (not pr.draft))) (tbl.map (fn [pr] pr.number))) ``` ## Errors in scripts Raise structured errors so callers can branch on them: ```lua local function load_config(file) if not fs.exists(file) then error({ kind = "missing_config", message = file .. " does not exist", hint = "run setup first" }) end return json.decode(fs.read(file)) end local ok, err = try(load_config, "/no/config.json") return err.kind, err.hint -- missing_config, run setup first ```
View on GitHub