raffi 0.25.0

A simple fuzzel launcher based on YAML file
Documentation
---
title: Scripts
description: Execute inline scripts with bash, python, or custom interpreters
---

import { Aside } from '@astrojs/starlight/components';
import { Image } from 'astro:assets';
import launcherScriptScreenshot from '../../../assets/screenshots/launcher-script-preview.png';

## Overview

Raffi entries can execute inline scripts instead of launching binaries directly. Scripts are useful for:

- Running shell commands
- Executing Python/Ruby/Perl code
- Chaining multiple commands
- Setting up environments before launching applications

<Image src={launcherScriptScreenshot} alt="Launcher script source and interpreter shown in the preview pane" width={800} />

## Script Configuration

**`script`** `string` (required)

Inline script content. Can be a single line or multi-line string.

  The script is executed using the interpreter specified in `binary` (or the default script shell).


**`binary`** `string`

Script interpreter. Defaults to `bash` (or the value set via `--default-script-shell` or `general.default_script_shell`).

  Common values:

- `bash` (default)
- `python3`
- `ruby`
- `perl`
- `/bin/zsh`


**`args`** `array`

Arguments passed to the script (not the interpreter).

  These are available as `$1`, `$2`, etc. in shell scripts or `sys.argv` in Python.


## Bash Scripts

### Single-Line Script

```yaml title="launchers.yaml"
hello_script:
  script: "echo 'Hello, World!'"
  description: "Hello Script"
  icon: "script"
```

### Multi-Line Script

```yaml title="launchers.yaml"
hello_script:
  script: |
    echo "hello world and show me your env"
    env
  description: "Hello Script"
  icon: "script"
```

### Script with Arguments

```yaml title="launchers.yaml"
backup_script:
  script: |
    SOURCE=$1
    DEST=$2
    rsync -av "$SOURCE" "$DEST"
    notify-send "Backup complete" "$SOURCE → $DEST"
  args: ["~/Documents", "~/Backups"]
  description: "Backup Documents"
  icon: "backup"
```

## Python Scripts

### Basic Python Script

```yaml title="launchers.yaml"
hello_python:
  binary: python3
  script: |
    import os
    print("hello world and show me your env")
    print(os.environ)
  description: "Hello Python script"
  icon: "python"
```

### Python Script with Arguments

```yaml title="launchers.yaml"
process_data:
  binary: python3
  script: |
    import sys
    import json

    input_file = sys.argv[1]
    with open(input_file) as f:
        data = json.load(f)

    print(f"Processed {len(data)} records")
  args: ["~/data/input.json"]
  description: "Process Data"
  icon: "python"
```

## Custom Interpreters

### Ruby Script

```yaml title="launchers.yaml"
ruby_script:
  binary: ruby
  script: |
    puts "Hello from Ruby!"
    puts "Ruby version: #{RUBY_VERSION}"
  description: "Ruby Hello"
  icon: "ruby"
```

### Perl Script

```yaml title="launchers.yaml"
perl_script:
  binary: perl
  script: |
    print "Hello from Perl!\n";
    print "Perl version: $^V\n";
  description: "Perl Hello"
  icon: "perl"
```

### Zsh Script

```yaml title="launchers.yaml"
zsh_script:
  binary: /bin/zsh
  script: |
    echo "Hello from Zsh!"
    echo "Zsh version: $ZSH_VERSION"
  description: "Zsh Hello"
  icon: "terminal"
```

## Default Script Shell

Set a default interpreter for all scripts:

### Via Configuration

```yaml title="raffi.yaml"
version: 1

general:
  default_script_shell: /bin/zsh
```

```yaml title="launchers.yaml"
my_script:
  script: "echo 'This runs with zsh'"
  description: "My Script"
```

### Via Command Line

```bash
raffi --default-script-shell /bin/zsh
```

<Aside type="note">
The `binary` field in individual entries overrides the default script shell.
</Aside>

## Script Execution

When an entry with a `script` field is selected:

1. Raffi checks if the interpreter exists in PATH
2. The script is executed as: `<interpreter> -c "<script>"`
3. If `args` are present, they're passed to the script (not the interpreter)

### Print-Only Mode

To see the exact command without executing:

```bash
raffi --print-only
```

For a script entry, this outputs a shell-ready command line:

```bash
env -- bash -c 'echo "hello world and show me your env"
env'
```

Quoting is preserved, so the output can be evaluated directly with `sh -c "$cmd"` or handed to a
compositor as a single argument. A multi-line script stays multi-line inside the quotes; do not pipe
it through `xargs`, which strips the quotes.

## Real-World Examples

### Password Generator

```yaml title="launchers.yaml"
passgen:
  binary: kitty-ctrl
  args: ["jump", "-t", "GeneratePassword", "genpasswd"]
  description: "Password Generator"
  icon: password
```

### System Monitoring

```yaml title="launchers.yaml"
bpytop:
  binary: kitty-ctrl
  args: ["jump", "bpytop"]
  description: "BpyTOP"
  icon: bashtop
  ifexist: bpytop
```

### Custom Window Management

```yaml title="launchers.yaml"
center-window:
  binary: wmctrl-resize-and-center
  description: "Center window"
  icon: wmsnap

snap-window-around:
  binary: wmctrl-resize-and-center
  args: [rotate]
  description: "Snap window around"
  icon: wmaround
  ifenveq: [XDG_SESSION_TYPE, x11]
```

## Path Expansion in Scripts

<Aside type="caution">
Path expansion (`~/` and `${VAR}`) is **not** applied to script content.
</Aside>

If you need path expansion in scripts, do it explicitly:

```yaml title="launchers.yaml"
backup:
  script: |
    HOME=$HOME
    SOURCE="$HOME/Documents"
    DEST="$HOME/Backups"
    rsync -av "$SOURCE" "$DEST"
  description: "Backup"
```

Or use arguments with expansion:

```yaml title="launchers.yaml"
backup:
  script: |
    rsync -av "$1" "$2"
  args: ["~/Documents", "~/Backups"]
  description: "Backup"
```

## Validation

Script entries are validated on startup:

1. **Interpreter check**: The `binary` (or default shell) must exist in PATH
2. **Conditional checks**: If `ifexist`, `ifenvset`, or `ifenveq` are present, they must pass
3. **Disabled check**: If `disabled: true`, the entry is hidden

If validation fails, the entry is automatically hidden from the launcher.