psl

Prompt Script Language — an AI native language that lets you embed AI instructions in files written in other languages.

Wrap an instruction in :: delimiters and it becomes a slot. The psl compiler fills the slot with code the model writes, in place, in the file you already have.

Try a run

fib.go.psl
package main
import "fmt"
// :: one-line doc comment for Fib, Go style ::
func Fib(n int) int {
if n < 2 {
return n
}
a, b := 0, 1
:: fill in the iterative loop and return, using a and b ::
}
func main() {
fmt.Println(Fib(10))
}
$psl fib.go.psl

One run, one slot. Run it again until the file has none left.

The whole syntax

:: xxx ::

That is all of it. No keywords, no imports, no config in the file. Whitespace inside the delimiters is optional, and a slot may span several lines.

It writes in your language

The model is shown the whole file with the slot marked, so what comes back is written in the surrounding language and reuses the names already defined above it. A slot alone on its line is indented to that line's column.

It stays out of real code

C++'s std::cout, Rust's Foo::Bar, PHP's self::method — scope resolution always glues :: to an identifier, and a glued :: is never a delimiter. Existing code in the file keeps working the way it reads.

One slot, one run

Each run resolves the first remaining slot and writes it back into the file. The file is always a real file, never a half-generated draft, and you can stop and read it after any run.

Pick a model per slot

A bare :: xxx :: uses your default model. Name one in front of the instruction — :: gpt-5.6> xxx :: — and that slot alone goes to it.

Giving a slot more to go on

--image
psl ui.html.psl --image design.png

Hand the slot something to look at — a screenshot, a mockup, a photo. Takes a file path, a data: URL or base64. PNG, JPEG, GIF and WebP.

--prompt
psl bot.py.psl --prompt "move(x, y) takes absolute screen pixels, origin top-left"

Tell it what the file cannot: the API being called, what each parameter means, in what units. Pass a path instead of text and psl reads the file. It is context, never the instruction.

Getting it

One line, and nothing to install alongside it. The installer takes the build for your platform from the latest release, checks it against the release's checksums — one it cannot verify is never installed — and puts psl on your PATH. No Go toolchain involved.

curl -fsSL https://raw.githubusercontent.com/lhypds/psl/main/get.sh | sh

Goes to /usr/local/bin when you can write there, and ~/.local/bin otherwise, so no root is involved either way. Pass --prefix through the pipe to put it somewhere else.

psl update

Keeps itself current from there. Every download is checked against the release's checksums — one it cannot verify is never installed, and a failed update leaves the working psl exactly where it was.

Where it is used

Pob →Desktop automation, driven by Macro PSL. The macros that operate an application are written with slots, so a step can say what to do instead of naming a coordinate.