run 0.3.1

A lightweight task runner for defining and executing shell commands with a clean, readable syntax.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# run

[![Crates.io](https://img.shields.io/crates/v/run.svg)](https://crates.io/crates/run)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)

**Lightweight task runner that speaks shell, Python, Node, and more, that communicates natively with your AI Agents.**

Define tasks in a `Runfile`, run them from anywhere. Make them available to AI via the in-built MCP server.

```bash
# Runfile

build() cargo build --release

# @os windows
clean() del /Q dist

# @os unix
clean() rm -rf dist

# @shell python
analyze() {
    import sys, json
    with open(sys.argv[1]) as f:
        data = json.load(f)
        print(f"Found {len(data)} records")
}

# @shell node
server() {
    require('http').createServer((req, res) => {
        res.end('Hello from Runfile!');
    }).listen(process.argv[1] || 3000);
}
```

```bash
$ run build
$ run analyze data.json
$ run server 8080
```

Perfect for **project automation**, **CI scripts**, and **personal workflows**.

---

## Table of Contents

- [Why run?]#why-run
- [Installation]#installation
- [Quick Start]#quick-start
- [The Runfile Syntax]#the-runfile-syntax
  - [Basic Functions]#basic-functions
  - [Block Syntax]#block-syntax
  - [Arguments & Defaults]#arguments--defaults
  - [Attributes & Polyglot Scripts]#attributes--polyglot-scripts
  - [Nested Namespaces]#nested-namespaces
  - [Function Composition]#function-composition
  - [AI Agent Integration (MCP)]#ai-agent-integration-mcp
- [Configuration]#configuration
  - [Shell Selection]#shell-selection
  - [Global Runfile]#global-runfile
- [License]#license

---

## Why run?

It hits a common sweet spot — lightweight, readable, and shell-native for quick CLI automation without the overhead of heavier task systems.

- **Zero Config**: Shell, Python, Node, or Ruby: right in your Runfile.
- **Low Overhead**: Instant startup time.
- **Shell Native**: Use the syntax you already know (`$1`, `&&`, pipes).
- **Clean Namespace**: Organise tasks with `group:task` syntax.
- **Global & Local**: Project-specific `./Runfile` or personal `~/.runfile`.

### Comparison

- **vs Make**: `run` is easier for linear scripts and doesn't require learning Makefile quirks (tabs vs spaces, `.PHONY`).
- **vs Just**: `run` is closer to raw shell scripting. It doesn't have a custom language for variables or logic—it just delegates to your shell.
- **vs Both**: `run` is the only task runner with built-in Model Context Protocol support, letting AI agents like Claude discover and execute your tools automatically.

---

## Installation

### Recommended

**macOS/Linux (Homebrew)**
```bash
brew tap nihilok/tap
brew install runfile
```

**Windows (Scoop)**
```powershell
scoop bucket add nihilok https://github.com/nihilok/scoop-bucket
scoop install runfile
```

### Alternative: Cargo

Works on all platforms:

```bash
cargo install run
```

### Tab Completions

Auto-detect your shell and install completions:

```bash
run --install-completion
```

Supports `bash`, `zsh`, `fish`, and `powershell`.

---

## Quick Start

Create a `Runfile` in your project root:

```bash
# Simple one-liner
dev() cargo run

# Multi-step task
deploy() {
    echo "Building..."
    cargo build --release
    echo "Deploying..."
    scp target/release/app server:/bin/
}

# Use Python for complex logic
# @shell python
stats() {
    import sys
    lines = sum(1 for line in open(sys.argv[1]))
    print(f"{sys.argv[1]}: {lines} lines")
}
```

Run your tasks:

```bash
$ run dev
$ run deploy
$ run stats src/main.rs
```

List available tasks:

```bash
$ run --list
```

---

## The Runfile Syntax

`run` parses your `Runfile` to find function definitions. The syntax is designed to be familiar to anyone who has used `bash` or `sh`.

### Basic Functions

For simple, one-line commands, you don't need braces.

```bash
# Usage: run dev
dev() cargo run

# Usage: run fmt
fmt() cargo fmt
```

### Block Syntax

Use `{}` for multi-statement functions. This avoids the need for trailing backslashes.

```bash
ci() {
    echo "Running CI..."
    cargo fmt -- --check
    cargo clippy
    cargo test
    echo "Done!"
}
```

### Arguments & Defaults

Arguments are passed directly to the underlying shell. Access them using standard positional variables: `$1`, `$2`, `$@`.

```bash
# Usage: run commit "Initial commit"
git:commit() {
    git add .
    git commit -m "$1"
}

# Usage: run deploy prod v2
# $1 = prod, $2 = v2 (defaults to 'latest' if missing)
deploy() {
    env=$1
    version=${2:-latest} 
    echo "Deploying $version to $env..."
}

# Pass all arguments through
test() {
    # Usage: run test --release --nocapture
    cargo test $@
}
```

### Attributes & Polyglot Scripts

You can use comment attributes (`# @key value`) or shebang lines to modify function behaviour and select interpreters.

#### Platform Guards (`@os`)

Restrict functions to specific operating systems. This allows you to define platform-specific implementations of the same task.

```bash
# @os windows
clean() del /Q dist

# @os unix
clean() rm -rf dist
```

When you run `run clean`, only the variant matching your current OS will execute.

#### Interpreter Selection

There are two ways to specify a custom interpreter:

**1. Shebang detection** (recommended):

The first line of your function body can be a shebang, just like standalone scripts:

```python
analyze() {
    #!/usr/bin/env python
    import sys, json
    with open(sys.argv[1]) as f:
        data = json.load(f)
        print(f"Found {len(data)} records")
}
```

```javascript
server() {
    #!/usr/bin/env node
    const port = process.argv[1] || 3000;
    require('http').createServer((req, res) => {
        res.end('Hello!');
    }).listen(port);
}
```

**2. Attribute syntax** (`@shell`):

Use comment attributes for explicit control or when you need to override a shebang:

```python
# @shell python3
calc() {
    import sys, math
    radius = float(sys.argv[1])
    print(f"Area: {math.pi * radius**2:.2f}")
}
```

**Precedence**: If both are present, `@shell` takes precedence over the shebang.

**Supported interpreters:** `python`, `python3`, `node`, `ruby`, `pwsh`, `bash`, `sh`

### Nested Namespaces

Organise related commands using colons. `run` parses `name:subname` as a single identifier.

```bash
docker:build() docker build -t app .
docker:up() docker compose up -d
docker:logs() docker compose logs -f
```

Execute them with spaces:

```bash
$ run docker build
$ run docker logs
```

### Function Composition

Functions can call other functions defined in the same Runfile, enabling task composition and code reuse without duplication.

```bash
# Base tasks
build() cargo build --release
test() cargo test
lint() cargo clippy

# Composed task that calls other functions
ci() {
    echo "Running CI pipeline..."
    lint
    test
    build
}

# Deploy depends on successful build
deploy() {
    build || exit 1
    echo "Deploying..."
    scp target/release/app server:/bin/
}
```

When you run `run ci`, all compatible functions are automatically injected into the execution scope, so you can call them directly without spawning new processes.

**Key features:**
- Functions can call sibling functions defined in the same file
- Exit codes are properly propagated (use `|| exit 1` to stop on failure)
- Works across different shells when interpreters are compatible
- Top-level variables are also available to all functions

---

## AI Agent Integration (MCP)

`run` includes built-in support for the **Model Context Protocol (MCP)**, allowing AI agents like Claude to discover and execute your Runfile functions as tools.

### Exposing Functions to AI Agents

Use `@desc` and `@arg` attributes to provide metadata for AI agents:

```bash
# @desc Search the codebase for specific patterns
# @arg 1:pattern string The regex pattern to search for
# @shell python
search() {
    import sys, os, re
    pattern = sys.argv[1]
    for root, dirs, files in os.walk('.'):
        for file in files:
            if file.endswith('.py'):
                path = os.path.join(root, file)
                with open(path) as f:
                    for i, line in enumerate(f, 1):
                        if re.search(pattern, line):
                            print(f"{path}:{i}: {line.strip()}")
}

# @desc Deploy the application to a specific environment
# @arg 1:environment string Target environment (staging|prod)
deploy() {
    ./scripts/deploy.sh $1
}
```

### MCP Server Mode

Start `run` as an MCP server to enable AI agent integration:

```bash
run --serve-mcp
```

Configure in your AI client (e.g., Claude Desktop):

```json
{
  "mcpServers": {
    "my-project": {
      "command": "run",
      "args": ["--serve-mcp", "--runfile", "/path/to/your/project/Runfile"],
      "cwd": "/path/to/your/project"
    }
  }
}
```

**Note**: The `--runfile` argument is required to specify which Runfile the AI agent should use. This allows you to expose specific Runfiles to different AI contexts.

Now AI agents can:
- Discover available tools via `run --inspect`
- Execute functions with typed parameters
- Receive structured outputs

### Inspect Tool Schema

View the generated JSON schema for all MCP-enabled functions:

```bash
run --inspect
```

This outputs the tool definitions that AI agents will see, useful for debugging and validation.

---

## Configuration

### Shell Selection

By default, `run` uses:

- **Windows**: PowerShell (`pwsh` if available, else `powershell`)
- **Unix**: `sh`

You can override this default by setting the `RUN_SHELL` environment variable.

```bash
# Force Zsh for this command
RUN_SHELL=zsh run build

# Make it permanent for your session
export RUN_SHELL=bash
```

**Note**: The commands in your Runfile must be compatible with the configured shell, unless an explicit interpreter (e.g., `# @shell python`) is defined for that function.

### Global Runfile

Create a `~/.runfile` in your home directory to define global commands available anywhere.

```bash
# ~/.runfile

# Usage: run update
update() {
    brew update
    brew upgrade
    rustup update
}

# Usage: run clone <repo>
clone() {
    git clone "https://github.com/$1"
    cd "$(basename "$1" .git)"
}
```

If a local `./Runfile` exists, `run` looks there first. If the command isn't found locally, it falls back to `~/.runfile`.

---

## License

MIT