Script Plugins - ArunPrakashG/native-launcher GitHub Wiki

Script Plugins

Script plugins allow you to extend Native Launcher functionality using any programming language without compiling Rust code. Create plugins with simple shell scripts, Python, Node.js, Ruby, or any executable.

Quick Start

1. Create Your First Plugin

# Create plugin directory
mkdir -p ~/.config/native-launcher/plugins/hello-world
cd ~/.config/native-launcher/plugins/hello-world

2. Write Manifest (plugin.toml)

[metadata]
name = "Hello World"
description = "My first plugin"
author = "Your Name"
version = "1.0.0"
priority = 600

triggers = ["hello ", "hi "]

[execution]
script = "hello.sh"
interpreter = "bash"
output_format = "json"
timeout_ms = 1000

3. Write Script (hello.sh)

#!/usr/bin/env bash

cat <<EOF
{
  "results": [
    {
      "title": "Hello, $1!",
      "subtitle": "Press Enter to copy greeting",
      "command": "echo 'Hello, $1!' | wl-copy && notify-send 'Copied' 'Greeting copied'"
    }
  ]
}
EOF

4. Make Executable and Test

chmod +x hello.sh
./hello.sh "World"  # Test directly

5. Use in Launcher

Restart Native Launcher and type: hello World


How Script Plugins Work

Architecture

User types: "weather Tokyo"
         ↓
Launcher checks triggers → Finds "weather " plugin
         ↓
Execute: bash weather.sh "Tokyo"
         ↓
Parse JSON/text output
         ↓
Display results in launcher
         ↓
User selects → Execute result command

Plugin Discovery

Native Launcher scans these directories on startup:

  1. ~/.config/native-launcher/plugins/ - Your plugins
  2. /usr/share/native-launcher/plugins/ - System plugins
  3. ./plugins/ - Development plugins (current directory)

Each subdirectory with a plugin.toml is loaded as a plugin.


Manifest Format

Complete Example

[metadata]
name = "Plugin Name"           # Display name
description = "What it does"   # Short description
author = "Your Name"           # Author
version = "1.0.0"              # Semantic version
priority = 600                 # 0-1000 (higher = searched first)
icon = "icon-name"             # Optional icon

# Command triggers (include trailing space for word-based)
triggers = ["command ", "cmd ", "c "]

[execution]
script = "main.sh"             # Script path (relative to plugin dir)
interpreter = "bash"           # Optional: bash, python3, node, ruby
output_format = "json"         # "json" or "text"
timeout_ms = 3000             # Max execution time (milliseconds)
show_on_empty = false         # Show results when query is empty

# Optional environment variables
[environment]
API_KEY = "your-key"
DEBUG = "true"

Priority System

Determines search order (higher priority = searched first):

Priority Usage
900-1000 Critical system plugins
850 Advanced Calculator
750 SSH Plugin
700 File Browser
600-699 User plugins (high) ← Your plugins
500-599 User plugins (normal) ← Your plugins
400-499 User plugins (low)
0-399 Experimental

Recommendation: Use 600 for important plugins, 500 for normal plugins.

Triggers

Word-based triggers (require trailing space):

triggers = ["weather ", "w "]
# Matches: "weather Tokyo", "w London"
# Doesn't match: "weatherman" (no space)

Symbol-based triggers (no space needed):

triggers = ["#", "@color"]
# Matches: "#FF5733", "@color red"

Multiple triggers:

triggers = ["calc ", "calculate ", "="]
# Gives users flexibility

Output Formats

JSON Format (Recommended)

Advantages: Structured, supports all fields, easier to parse

Structure:

{
  "results": [
    {
      "title": "Result Title",
      "subtitle": "Optional description",
      "command": "shell command to run",
      "icon": "optional-icon-name"
    }
  ]
}

Python Example:

#!/usr/bin/env python3
import json
import sys

query = sys.argv[1] if len(sys.argv) > 1 else ""

results = {
    "results": [
        {
            "title": f"You searched for: {query}",
            "subtitle": "Click to copy",
            "command": f"echo '{query}' | wl-copy"
        }
    ]
}

print(json.dumps(results))

Bash Example:

#!/usr/bin/env bash
QUERY="$1"

cat <<EOF
{
  "results": [
    {
      "title": "Result: $QUERY",
      "subtitle": "Press Enter",
      "command": "notify-send 'You selected' '$QUERY'"
    }
  ]
}
EOF

Text Format (Simple)

Advantages: Simple, quick for basic plugins

Format: title|subtitle|command (pipe-separated)

Example:

#!/usr/bin/env bash
echo "First Option|Does something cool|echo 'option1'"
echo "Second Option|Does something else|echo 'option2'"
echo "Simple Result"  # Just title (command = title)

Manifest:

[execution]
output_format = "text"

Built-in Example Plugins

🌤️ Weather

Get weather forecasts for any location.

Install:

cp -r /path/to/examples/plugins/weather ~/.config/native-launcher/plugins/

Usage:

  • weather Tokyo - Current weather in Tokyo
  • w London - Quick weather lookup

Features:

  • Current conditions (temp, humidity, wind)
  • Copy to clipboard
  • Open forecast in browser
  • ASCII art display

😀 Emoji Search

Search 200+ emojis by keyword.

Install:

cp -r /path/to/examples/plugins/emoji ~/.config/native-launcher/plugins/

Usage:

  • emoji smile - Find smiling emojis
  • em heart - Find heart emojis
  • :fire - Quick emoji search

Features:

  • Keyword-based search
  • Instant clipboard copy
  • Desktop notification

🎨 Color Picker

Convert colors between formats.

Install:

cp -r /path/to/examples/plugins/color ~/.config/native-launcher/plugins/

Usage:

  • color #FF5733 - Convert hex
  • col rgb(255,87,51) - Convert RGB
  • #FF5733 - Quick color lookup

Features:

  • Hex ↔ RGB ↔ HSL conversion
  • CSS variable format
  • Tailwind CSS hints

Common Patterns

Clipboard Integration

Always use wl-copy for Wayland clipboard:

# Copy text
echo "Hello" | wl-copy

# Copy with notification
echo "Result" | wl-copy && notify-send 'Copied' 'Result'

# Copy without newline
echo -n "No newline" | wl-copy

In results:

{
  "command": "echo -n 'Copy me' | wl-copy && notify-send 'Copied' 'Success'"
}

API Calls

Bash with curl:

#!/usr/bin/env bash
QUERY="$1"
DATA=$(curl -s "https://api.example.com/search?q=$QUERY")

# Parse with jq
TITLE=$(echo "$DATA" | jq -r '.title')

cat <<EOF
{
  "results": [{"title": "$TITLE", "command": "echo 'Done'"}]
}
EOF

Python with requests:

#!/usr/bin/env python3
import requests
import json
import sys

query = sys.argv[1]
response = requests.get(f"https://api.example.com/search?q={query}")
data = response.json()

results = {
    "results": [
        {"title": data['title'], "command": "echo 'Done'"}
    ]
}

print(json.dumps(results))

File Operations

Open file/URL:

{
  "command": "xdg-open '/path/to/file.pdf'"
}
{
  "command": "xdg-open 'https://example.com'"
}

Terminal commands:

{
  "command": "alacritty -e bash -c 'command; read -p \"Press Enter\"'"
}

Error Handling

Show errors as results:

#!/usr/bin/env bash

if [ -z "$1" ]; then
    cat <<EOF
{
  "results": [
    {
      "title": "Enter a query",
      "subtitle": "Example: mycommand something",
      "command": "echo 'No query'"
    }
  ]
}
EOF
    exit 0
fi

DATA=$(curl -s "https://api.example.com/$1" 2>/dev/null)

if [ $? -ne 0 ]; then
    cat <<EOF
{
  "results": [
    {
      "title": "API Error",
      "subtitle": "Could not fetch data",
      "command": "echo 'Error'"
    }
  ]
}
EOF
    exit 0
fi

# Process data...

Best Practices

Performance

Do:

  • Keep scripts fast (<100ms target)
  • Cache API responses
  • Limit results to 10-20
  • Set reasonable timeouts

Don't:

  • Make slow API calls on every keystroke
  • Return hundreds of results
  • Use blocking operations

User Experience

Do:

  • Write clear, descriptive titles
  • Explain what happens in subtitle
  • Provide helpful empty-query hints
  • Use desktop notifications for feedback

Don't:

  • Use cryptic abbreviations
  • Return empty results silently
  • Assume users know what will happen

Security

Do:

  • Quote all variables in shell commands
  • Validate input before processing
  • Check file paths exist
  • Use proper escaping

Don't:

  • Use eval on user input
  • Execute unvalidated commands
  • Trust external data blindly

Bad (Command Injection):

command="$1"  # User types: "; rm -rf /"
eval "$command"  # ⚠️ DANGEROUS!

Good (Safe):

QUERY="$1"
echo "Safe: $QUERY" | wl-copy

Debugging & Testing

Test Plugin Directly

cd ~/.config/native-launcher/plugins/my-plugin
./script.sh "test query"

Validate JSON Output

./script.sh "query" | jq
# If jq fails, you have invalid JSON

Enable Debug Logs

RUST_LOG=debug native-launcher 2>&1 | grep -i plugin

Common Issues

Plugin not loading:

# Check TOML syntax
cat plugin.toml

# Check script exists
ls -lh script.sh

# Check executable permission
chmod +x script.sh

# Check logs
RUST_LOG=debug native-launcher

Script not executing:

# Test directly
./script.sh "test"

# Check shebang
head -1 script.sh  # Should be #!/usr/bin/env bash

# Check interpreter
which python3
which bash

No results showing:

# Validate JSON
./script.sh "test" | jq

# Check trigger matches
# Query must start with trigger

# Verify priority
# Higher priority = searched first

Advanced Topics

Persistent State

Store plugin state in cache directory:

#!/usr/bin/env bash
CACHE_DIR="$HOME/.cache/native-launcher/my-plugin"
STATE_FILE="$CACHE_DIR/state.json"

mkdir -p "$CACHE_DIR"

# Read state
if [ -f "$STATE_FILE" ]; then
    LAST_QUERY=$(jq -r '.last_query' "$STATE_FILE")
fi

# Save state
echo "{\"last_query\": \"$1\"}" > "$STATE_FILE"

Background Updates

Use systemd user timer for periodic tasks:

# ~/.config/systemd/user/my-plugin-update.timer
[Unit]
Description=My Plugin Update

[Timer]
OnBootSec=5min
OnUnitActiveSec=1h

[Install]
WantedBy=timers.target
# ~/.config/systemd/user/my-plugin-update.service
[Unit]
Description=Update My Plugin Cache

[Service]
Type=oneshot
ExecStart=/home/user/.config/native-launcher/plugins/my-plugin/update.sh

Enable:

systemctl --user enable --now my-plugin-update.timer

Multi-Language Scripts

Python with virtual environment:

[execution]
script = "venv/bin/python"
interpreter = ""  # Script handles everything

[environment]
PYTHONPATH = "/path/to/venv/lib/python3.x/site-packages"

Node.js:

[execution]
script = "index.js"
interpreter = "node"

Ruby:

[execution]
script = "main.rb"
interpreter = "ruby"

Plugin Ideas

Suggested Plugins to Build

Search & Information:

  • 📖 Dictionary - Word definitions (dict.org)
  • 🌐 Translation - Translate text
  • 📰 News - Headlines from API
  • 🔍 Wikipedia - Quick lookups
  • 🎬 IMDb Search - Movie info

Productivity:

  • 📋 Clipboard History - Browse clipboard
  • 📝 Snippet Manager - Code snippets
  • 🔖 Bookmark Manager - Browser bookmarks
  • 📅 Calendar - Quick event creation
  • ⏰ Timer - Countdown timers

Development:

  • 🐙 GitHub - Search repos/issues
  • 📦 NPM Search - Package lookup
  • 🦀 Crates.io - Rust crates
  • 💻 StackOverflow - Question search
  • 🐳 Docker - Container management

System:

  • 💻 System Info - CPU/RAM/disk
  • 🔧 Process Killer - Find and kill processes
  • 📁 Recent Files - File history
  • 🔌 Network Info - IP, speed test
  • 🔋 Battery Status - Battery info

Utilities:

  • 🔐 Password Generator - Secure passwords
  • 📊 QR Code - Generate QR codes
  • 🔢 Base64 - Encode/decode
  • 🔑 Hash Calculator - MD5/SHA256
  • 📏 Unit Converter - Advanced conversions
  • 💱 Cryptocurrency - Live prices
  • 📈 Stock Ticker - Stock quotes

Dependencies

Required Packages

For launcher:

  • wl-clipboard - Clipboard support
  • libnotify - Desktop notifications

For Python plugins:

  • python3 - Python interpreter
  • python3-pip - Package manager (for dependencies)

For API-based plugins:

  • curl or wget - HTTP requests
  • jq - JSON parsing (bash scripts)

Installation

Arch Linux:

sudo pacman -S wl-clipboard libnotify python python-pip curl jq

Ubuntu/Debian:

sudo apt install wl-clipboard libnotify-bin python3 python3-pip curl jq

Fedora:

sudo dnf install wl-clipboard libnotify python3 python3-pip curl jq

Distribution

Sharing Your Plugin

1. Create repository:

my-awesome-plugin/
├── README.md
├── plugin.toml
├── script.sh
├── LICENSE
└── examples/
    └── usage.md

2. Installation instructions:

## Installation

```bash
git clone https://github.com/user/my-plugin \
  ~/.config/native-launcher/plugins/my-plugin
```

Or download:

wget https://github.com/user/my-plugin/releases/latest/download/plugin.tar.gz
tar -xzf plugin.tar.gz -C ~/.config/native-launcher/plugins/

**3. Document dependencies**:
```markdown
## Dependencies

- `curl` - For API requests
- `jq` - For JSON parsing
- `python3-requests` - For Python HTTP

Resources

Documentation

Community

  • GitHub Discussions - Share your plugins!
  • Issue Tracker - Report bugs
  • Wiki - This page and more

Tools

  • jq - JSON processor for testing
  • shellcheck - Bash script linter
  • python -m json.tool - Validate JSON

FAQ

Q: Can I use any programming language?
A: Yes! Any language that can output text (JSON/plain) works. Bash, Python, Ruby, Node.js, Go, Rust executables, etc.

Q: How do I handle API keys securely?
A: Use environment variables in [environment] section, or read from ~/.config/my-plugin/config.

Q: Can plugins interact with each other?
A: Not currently. Each plugin runs independently.

Q: What if my script is slow?
A: Set a higher timeout_ms and cache results. Consider background updates.

Q: Can I use external dependencies?
A: Yes! Document them in your README. Python virtual envs and npm packages work fine.

Q: How do I update a plugin?
A: Replace files and restart launcher. Hot reload not yet supported.

Q: Is there a plugin marketplace?
A: Not yet, but planned for future releases!

Q: Can I make GUI plugins?
A: Not currently. Plugins return text results only. Custom UI planned for future.


License

Script plugins are independent projects. Your plugins can use any license.

Native Launcher itself is MIT licensed.


Next: Plugin System Overview | Development Guide