Skip to content

Instantly share code, notes, and snippets.

@aliou
Created May 11, 2026 19:35
Show Gist options
  • Select an option

  • Save aliou/17322b3fa9e816e2231fd4be05005398 to your computer and use it in GitHub Desktop.

Select an option

Save aliou/17322b3fa9e816e2231fd4be05005398 to your computer and use it in GitHub Desktop.
`pi -e https://gist.github.com/aliou/17322b3fa9e816e2231fd4be05005398` — Pi extension for LLM-style shebang prompt scripts

pi shebang script

Gist: https://gist.github.com/aliou/17322b3fa9e816e2231fd4be05005398

A small Pi extension that adds --shebang, inspired by Simon Willison's TIL post Using LLM in the shebang line of a script.

When enabled, Pi treats the first input as a script path, reads that file, strips the shebang line, and sends the remaining body as the prompt.

Install

pi install https://gist.github.com/aliou/17322b3fa9e816e2231fd4be05005398

Or test directly:

pi --no-extensions -e https://gist.github.com/aliou/17322b3fa9e816e2231fd4be05005398 -p --shebang=true ./pelican.sh

Pelican example

Create pelican.sh:

#!/usr/bin/env -S pi --no-extensions -e https://gist.github.com/aliou/17322b3fa9e816e2231fd4be05005398 -p --shebang=true
Generate an SVG of a pelican riding a bicycle

Then run:

chmod +x pelican.sh
./pelican.sh

Without -p, Pi opens interactive mode, runs the prompt, and keeps the session open.

Caveats

Use --shebang=true, not just --shebang. Pi parses unknown flags before extension flags are registered, so a bare --shebang can consume the script path as its value.

Extra script arguments are ignored by this first version. The extension uses only the first input line as the script path.

import { readFile } from "node:fs/promises";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
const FLAG_NAME = "shebang";
function stripShebang(source: string): string {
const lines = source.split(/\r?\n/);
if (lines[0]?.startsWith("#!")) {
lines.shift();
}
return lines.join("\n").trimStart();
}
function getFirstLine(text: string): string {
return text.split(/\r?\n/, 1)[0]?.trim() ?? "";
}
export default function (pi: ExtensionAPI) {
pi.registerFlag(FLAG_NAME, {
description: "Treat the first prompt as a script file and use its body as the prompt",
type: "boolean",
default: false,
});
pi.on("input", async (event) => {
if (pi.getFlag(FLAG_NAME) !== true) return;
const scriptPath = getFirstLine(event.text);
if (!scriptPath) {
return {
action: "handled",
};
}
const source = await readFile(scriptPath, "utf8");
const prompt = stripShebang(source);
return {
action: "transform",
text: prompt,
};
});
}
{
"name": "pi-shebang-script",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"clean": "echo 'nothing to clean'",
"build": "echo 'nothing to build'",
"check": "tsc --noEmit"
},
"pi": {
"extensions": [
"./index.ts"
]
},
"devDependencies": {
"@earendil-works/pi-coding-agent": "latest",
"typescript": "latest"
}
}
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noEmit": true,
"skipLibCheck": true
},
"include": ["index.ts"]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment