A statically validated DSL and CLI for multiple git projects orchestration.
You switch machines. You have fifteen repos. You have a different setup script per project, a makefile that forgets cd between lines, env vars leaking everywhere, and a shell script graveyard nobody trusts.
kiru fixes this with a single config file, a small DSL, and zero shell archaeology.
sanctuary — your master workspace directory. all repos live relative to it.
pr — a project declaration. url, local dir, sync behavior, optional per-project config.
fn — a named execution block scoped to a project directory. primitives never leak between blocks.
run — orchestration block. concurrent chains of sequential function calls. each chain runs in order; chains run concurrently.
TUI — interactive terminal UI during run. shows chain headers, live spinner per task, and status colors. press q or Ctrl+C to abort. after completion, a colored ANSI dump shows full output per task.
kiru sync clone all declared repos into sanctuary
kiru run <name> <project> run a run block (with TUI)
kiru fn <name> <project> run a function directly (plain output)
kiru validate validate the configuration file
kiru version print version
kiru -c <path> <command> use a custom config file
kiru --config <path> <command> same as -c
Config is discovered at ~/.config/kiru/main.kiru by default.
When SANCTUARY=0 and ./.kiru/main.kiru exists, that local file is preferred.
Override either behavior with -c.
Everything lives in one .kiru file — project declarations, execution logic, variables. No separate config and script files.
shell = `bash`;
var shell workdir = `echo $HOME/dev`;
var string app = `todo`;
sanctuary = $workdir;
pr todo {
url = `git@github.com:yourname/todo.git`;
dir = `todo`;
sync = `clone`;
branch = `main`;
include = `.kiru/main.kiru`;
var shell version = `git describe --tags --always --dirty 2>/dev/null || echo dev`;
fn build {
log `Building ${app} at ${version}`;
var shell os = `uname -s`;
case $os {
`Linux` {
cd `cmd`;
exec `go build -ldflags='${version}' -o bin/${app} .`;
};
`Darwin` {
cd `cmd`;
exec `go build -ldflags='${version}' -o bin/${app} .`;
};
_ { log `unsupported OS: ${os}`; };
};
}
fn test {
env [
CGO_ENABLED = `0`,
GOPATH = `$HOME/go`
] {
exec `go test -race ./...`;
exec `go vet ./...`;
};
}
run ci {
test => build;
}
}
| declaration | description |
|---|---|
shell = \...`;` |
required, must be first. declares the shell for exec and var shell |
sanctuary = \...` | $var;` |
required. absolute path to workspace root |
import `./path`; |
import other .kiru files, relative paths only |
var string name = \...` | $var;` |
string variable (global or project-scoped) |
var shell name = \...`;` |
runs content via declared shell, stores stdout |
pr name { ... } |
project declaration |
fn name { ... } |
execution block |
run name { ... } |
orchestration block with chain syntax |
- Global vars (declared at the top level) — accessible everywhere: global scope, project fields, project vars, function bodies.
- Project vars (declared inside
pr { }) — accessible only within that project's function bodies. - Fn-local vars (declared inside
fn { }orenv { }) — scoped to that block, shadow any outer var with the same name.
| field | required | description |
|---|---|---|
url |
yes | git clone url |
dir |
yes | directory name relative to sanctuary, must be unique |
sync |
no | clone (default) — skip if exists. ignore — skip entirely |
include |
no | path to a .kiru file inside the project, relative to project dir |
branch |
no | branch to clone. defaults to repo default branch |
| primitive | description |
|---|---|
exec \...`;` |
run command via declared shell. non-zero exit fails the block |
cd \...`;` |
change cwd relative to project dir. cannot escape project dir |
log \...`;` |
print to TUI output. never fails |
env [...] { }; |
scoped env vars. inner env overrides outer. no leakage |
var <type> name = ...; |
fn-local variable. shadows outer var with same name |
case <expr> { ... }; |
conditional branching. first-matching arm wins |
case <expr> {
`literal` { ... };
$var_ref { ... };
_ { ... }; # default / catch-all
};
- Condition is an expression (backtick or
$varreference) - Patterns support
${interpolation}inside backticks - First matching arm wins; execution continues after the
caseblock - Each arm body ends with
;; the entirecaseblock ends with;
| syntax | description |
|---|---|
fn_name; |
single function call as a concurrent chain |
fn_a => fn_b => fn_c; |
sequential chain: fn_a → fn_b → fn_c in order |
fn_a; fn_b => fn_c; |
two concurrent chains (first: fn_a alone, second: fn_b → fn_c) |
Chains are separated by ;. Each chain runs sequentially; chains run concurrently. If a function in a chain fails, the rest of that chain is skipped but other chains continue.
| syntax | type | notes |
|---|---|---|
`...` |
string | use ${name} to interpolate variables |
$name |
var ref | standalone reference, outside backticks |
| delimiter | job |
|---|---|
| no parens | primitives are bare keywords — exec, cd, log |
[] |
typed list — env[] |
{} |
statement block |
; |
statement terminator inside {} and run chain separator |
, |
item separator inside [] |
=> |
sequential chain separator inside run blocks |
During kiru run, an interactive TUI shows live progress:
- Spinner animates on running tasks
- Status colors: green (ok), red (failed), yellow (running), gray (pending/skipped)
- Press
qorCtrl+Cto abort - After completion, a colored ANSI summary dump is printed
shellmust be declared before anyexec,var shell, orfn.sanctuarymust be declared before anypr.- Variables must be declared before they are referenced.
cdcannot escape the project directory. hard fail at runtime.- Circular imports fail at parse time.
- Two projects cannot share the same
dir. parse error. envblocks save and restore variable scope — declarations inside are local.
If a project declares include, that file is parsed after kiru sync clones the repo. It can define fns scoped to that project. It cannot declare sanctuary, pr, or shell.
# calendar/.kiru/main.kiru
fn build {
exec `pnpm build`;
}
fn dev {
exec `pnpm dev`;
}
Contributions are welcome! Feel free to open issues or submit pull requests.
