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.
package mainimport "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))}
One run, one slot. Run it again until the file has none left.
:: 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.
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.
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.
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.
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.
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.