Content
# garrascript
**Prompts as scripts.** A garrascript file (`grsc` for short) is a
[docopt](http://docopt.org/) interface and an LLM prompt in the same file:
docopt parses the command line, the parsed arguments are interpolated into the
prompt, and the result is sent to a model on [OpenRouter](https://openrouter.ai)
— with MCP tools attached if a `.mcp.json` is present.
```
#!/usr/bin/env grsc
"""
poem: writes short poems on demand.
Usage:
poem [--tone=<tone>] <topic>...
poem --version
poem (-h | --help)
Options:
--tone=<tone> Mood of the poem [default: playful].
"""
---
You are a poet. Write in a {{tone}} tone. Keep it under 8 lines.
---
Write a short poem about {{topic}}.
```
```console
$ chmod +x poem.grsc
$ ./poem.grsc --tone=solemn the sea at night
...
$ ./poem.grsc --help # docopt handles this for free
```
## Install
```console
$ go install github.com/aldrinleal/garrascript/cmd/grsc@latest
```
## Configuration
| Variable | Purpose |
|-----------------------|--------------------------------------------------|
| `OPENROUTER_API_KEY` | Required. Your OpenRouter API key. |
| `OPENROUTER_MODEL` | Model to use. Defaults to `z-ai/glm-5.2`. |
| `OPENROUTER_BASE_URL` | Alternate OpenAI-compatible endpoint (optional). |
Model precedence: the script's own `metadata.garrascript.model` (skill format)
wins, then `OPENROUTER_MODEL`, then the default.
### Dotenv files
`--env-file=<path>` loads `KEY=VALUE` pairs before the script runs, so they are
visible to `{{env.VAR}}`, to `${VAR}` in `.mcp.json`, and to the `OPENROUTER_*`
settings above. The flag is repeatable and must precede `<script>`; variables
already present in the environment are never overwritten, and among files the
first one to define a key wins.
A script can carry its own environment in the shebang:
```
#!/usr/bin/env -S grsc --env-file .env
```
Paths resolve against the working directory, not the script's directory. Note
that Linux truncates shebang lines at 255 characters, so keep them short.
## File formats
`grsc` accepts two layouts, detected from the file content (the
`#!/usr/bin/env grsc` shebang is optional in both).
### Classic (`.grsc`)
1. A `"""triple-quoted"""` docopt docstring — this is the CLI of your script.
2. A line containing only `---`, then the **system message**.
3. Another `---` line, then the **user message** (runs to end of file; later
`---` lines are kept verbatim).
With a single `---` separator there is no system message, only a user message.
### Skill format (`SKILL.md`)
Files in the [openclaw/clawhub skill format](https://docs.openclaw.ai/clawhub/skill-format)
also run: YAML frontmatter followed by a markdown body.
```markdown
---
name: haiku
description: Writes a single haiku about a topic.
version: 1.0.0
usage: |
Usage:
haiku <topic>...
metadata:
openclaw:
requires:
env: [OPENROUTER_API_KEY]
bins: []
garrascript:
model: z-ai/glm-5.2 # optional per-script model override
---
The markdown body is the system message.
---
Everything after a standalone `---` in the body is the user message.
```
Mapping rules:
- `description` + `usage` (top-level or `metadata.garrascript.usage`) become
the docopt document. Without a `usage`, it defaults to
`Usage: <name> [<input>...]`.
- The body is the system message; an optional standalone `---` line splits
off the user message. Alternatively, set the user message template via
`metadata.garrascript.user`.
- `metadata.openclaw.requires.env` / `requires.bins` (and the `clawdbot` /
`clawdis` aliases) are checked before running; missing requirements abort.
- `version` feeds docopt's `--version`.
## Interpolation
`{{placeholder}}` tokens in the system and user messages are replaced with
docopt results:
- `{{topic}}`, `{{tone}}` — cleaned names for `<topic>` / `--tone`
- `{{<topic>}}`, `{{--tone}}` — raw docopt keys also work
- Repeated arguments (`<topic>...`) join with spaces
- Booleans render as `true` / `false`, unset options as empty strings
- `{{env.HOME}}` — environment variables
- Unknown placeholders are left untouched
## Built-in tools
Every script runs with two built-in tools the model can call:
- **`exit`** — ends the run immediately; the `grsc` process exits with the
given status `code` (default 0). This lets scripts act as shell predicates:
```
Reply with nothing. Call exit with code 0 if the input is valid JSON,
code 1 otherwise: {{input}}
```
```console
$ ./is-json.grsc '{"a":1}' && echo valid
```
- **`write`** — writes `text` verbatim to `stdout` or `stderr` (`stream`,
default `stdout`). Useful for emitting exact machine-readable output apart
from the model's conversational answer, or diagnostics to stderr.
Built-in names are plain (`exit`, `write`); MCP tools are always namespaced
(`server__tool`), so they never collide.
## MCP tools
If the working directory has a `.mcp.json` (Claude Code style), `grsc`
connects to every server, lists its tools, and exposes them to the model as
`<server>__<tool>`. Tool calls are resolved transparently in a loop until the
model produces its final answer (tool activity is logged to stderr).
```json
{
"mcpServers": {
"fs": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
},
"remote": {
"type": "http",
"url": "https://example.com/mcp",
"headers": { "Authorization": "Bearer ${MY_TOKEN}" }
}
}
}
```
`stdio` (default), `http` (streamable HTTP), and `sse` servers are supported;
`${VAR}` in commands, args, env, URLs, and headers expands from the
environment.
## Examples
- [`examples/demo.grsc`](examples/demo.grsc) — minimal greeter
- [`examples/poem.grsc`](examples/poem.grsc) — options + positional args
- [`examples/skills/haiku/SKILL.md`](examples/skills/haiku/SKILL.md) — skill format
- [`examples/tools/`](examples/tools/) — MCP filesystem tools
## Development
```console
$ go build ./...
$ go test ./...
```
## License
[MIT](LICENSE)