#devops #scripting-language #scripting-automation #cli #automation

app latch-lang

Latch — a minimal scripting language for local automation and tool orchestration

4 releases

new 0.2.2 Feb 19, 2026
0.2.1 Feb 19, 2026
0.2.0 Feb 19, 2026
0.1.0 Feb 19, 2026

#1883 in Command line utilities

MIT license

625KB
3.5K SLoC

Latch Logo

Latch

Minimal scripting language for local automation and tool orchestration.

crates.io License: MIT


Write .lt scripts to automate file operations, run shell commands, make HTTP calls, and orchestrate parallel tasks — all in a clean, readable syntax with zero boilerplate.

# deploy.lt
config := json.parse(fs.read("deploy.json")) or {}
target := config?.target ?? "staging"

branch := proc.exec("git branch --show-current").stdout |> trim()
print("Deploying ${branch} to ${target}")

files := fs.glob("dist/**/*.js")
parallel f in files workers=4 {
    proc.exec("aws s3 cp ${f} s3://my-bucket/${f}")
}

print("Deployed ${len(files)} files!")
stop 0

Install

cargo install latch-lang

From source

git clone https://github.com/kaelvalen/latch-lang.git
cd latch-lang
cargo install --path .

Pre-built binaries

Download from Releases and add to your PATH.

After install, verify:

latch version
# → latch v0.2.2

Quick Start

Create hello.lt:

name := "World"
print("Hello, ${name}!")

items := ["files", "processes", "http", "parallel"]
for item in items {
    print("  ✓ ${item}")
}

Run it:

latch run hello.lt

Features

Feature Example
Variables name := "latch"
Type annotations port: int := 8080
String interpolation "Hello ${name}!"
Lists & Dicts [1, 2, 3], {"key": "val"}
Functions fn greet(name) { return "hi ${name}" }
Anonymous functions fn(x) { return x * 2 }
If / Else if x > 0 { ... } else { ... }
For loops for item in list { ... }
Range loops for i in 0..10 { ... }
Parallel parallel f in files workers=4 { ... }
Error handling try { ... } catch e { ... }
Fallback values data := fs.read("x") or "default"
Null coalesce name := config?.name ?? "anonymous"
Safe access resp?.headers, val?.field
Pipe operator list |> sort() |> filter(fn(x) { return x > 2 })
Membership test "x" in list, "key" in dict
Range literal 1..10[1, 2, ..., 9]
Compound assign count += 1, total *= 2
Modulo 10 % 31
Exit codes stop 0 / stop 1
Null literal x := null, x == null
File I/O fs.read, fs.write, fs.append, fs.readlines, fs.exists, fs.glob, fs.mkdir, fs.remove, fs.stat
Shell commands proc.exec("cmd"), proc.exec(["git", "status"]), proc.pipe([...])
HTTP http.get(url), http.post(url, body) → HttpResponse
JSON json.parse(str), json.stringify(value)
Env vars env.get(key), env.set(k, v), env.list()
Path utils path.join, path.basename, path.dirname, path.ext, path.abs
Time time.now(), time.sleep(ms)
AI ai.ask(prompt), ai.summarize(text)
Index mutation list[0] = 5, dict["key"] = val
Higher-order sort(list), filter(list, fn), map(list, fn), each(list, fn)
String utils lower, upper, starts_with, ends_with, trim, split, replace
Comments # hash and // line comments
REPL latch repl

CLI

latch run <file.lt>      # Run a script
latch check <file.lt>    # Static analysis (no execution)
latch repl               # Interactive REPL
latch version            # Print version

Operators

Operator Description Precedence
|> Pipe (inject as first arg) 1 (lowest)
or Error fallback 2
?? Null coalesce 3
|| Logical OR 4
&& Logical AND 5
== != Equality 6
< > <= >= in Comparison / membership 7
.. Range 8
+ - Add / subtract / concat 9
* / % Multiply / divide / modulo 10
! - Unary not / negate 11
. ?. [] () Access / safe access / index / call 12 (highest)

Compound: += -= *= /= %=

Standard Library

Built-in Functions

print("hello")              # Print to stdout
len([1, 2, 3])              # → 3
str(42)                     # → "42"
int("7")                    # → 7
float("3.14")               # → 3.14
typeof(x)                   # → "string"
push([1, 2], 3)             # → [1, 2, 3]
keys({"a": 1})              # → ["a"]
values({"a": 1})            # → [1]
range(0, 5)                 # → [0, 1, 2, 3, 4]
split("a,b,c", ",")         # → ["a", "b", "c"]
trim("  hi  ")              # → "hi"
lower("HELLO")              # → "hello"
upper("hello")              # → "HELLO"
starts_with("hello", "he")  # → true
ends_with("hello", "lo")    # → true
contains("hello", "ell")    # → true
replace("foo", "o", "0")    # → "f00"
sort([3, 1, 2])             # → [1, 2, 3]
filter(list, fn(x) { return x > 0 })
map(list, fn(x) { return x * 2 })
each(list, fn(x) { print(x) })

Modules

# fs — File System
content := fs.read("file.txt")
fs.write("out.txt", content)
fs.append("log.txt", "new entry\n")
lines := fs.readlines("data.csv")
fs.exists("path")
files := fs.glob("**/*.lt")
fs.mkdir("build/output")
fs.remove("tmp/cache")
info := fs.stat("file.txt")     # → {size, is_file, is_dir, readonly}

# proc — Processes
result := proc.exec("ls -la")
result := proc.exec(["git", "status"])   # array form (no shell)
piped := proc.pipe(["cat log.txt", "grep ERROR", "wc -l"])

# http — HTTP Client (returns HttpResponse)
resp := http.get("https://api.example.com/data")
print(resp.status)     # 200
print(resp.body)       # response body
print(resp.headers)    # headers dict

resp := http.post("https://api.example.com", "{\"key\": \"value\"}")

# json — JSON
data := json.parse("{\"name\": \"latch\"}")
back := json.stringify(data)

# env — Environment Variables
home := env.get("HOME") or "/tmp"
env.set("MODE", "production")   # current process only
all := env.list()

# path — Path Utilities
full := path.join("/home", "user/file.txt")
print(path.basename("/a/b/c.txt"))   # → c.txt
print(path.dirname("/a/b/c.txt"))    # → /a/b
print(path.ext("file.tar.gz"))       # → gz

# time — Time
now := time.now()           # RFC 3339 timestamp
time.sleep(500)             # Sleep 500ms

# ai — AI (requires LATCH_AI_KEY env var)
answer := ai.ask("Explain Rust in one sentence")
summary := ai.summarize(fs.read("article.txt"))

Error Messages

Latch produces structured, actionable errors:

[latch] Semantic Error
  file: deploy.lt
  line: 12  col: 5
  → result := undeclared_var + 1
  reason: Undefined variable 'undeclared_var'
  hint: Declare the variable first with ':='

Parallel Execution

Parallel blocks run all workers to completion. If any worker fails, the first error is returned after every worker has finished — no silent partial failures.

servers := ["web-1", "web-2", "web-3", "web-4"]
parallel s in servers workers=4 {
    proc.exec("ssh ${s} 'systemctl restart app'")
}

Use as CI Exit Code

result := proc.exec("cargo test")
if result.code != 0 {
    print("Tests failed!")
    stop 1
}
stop 0

Examples

See the examples/ directory:

Full Reference

See docs/stdlib.md for the complete standard library reference.

License

MIT — see LICENSE

Dependencies

~7–24MB
~284K SLoC