Skill: solsuk/grago (version 1.0.1)

# Grago **Delegate research and data-fetch tasks to a free local LLM. Save tokens. Use your machine.** Grago bridges the gap between your OpenClaw agent and local LLMs (Ollama, llama.cpp, etc.) that can't use tools natively. It runs shell scripts to fetch live data from the web, APIs, and local files — then pipes the results into your local model with a focused prompt. Your cloud model stays sharp. Your local machine does the grunt work. Your token bill drops. ## ⚠️ Security Model **Grago executes shell commands.** This is intentional — it's the only way to give tool-less local LLMs access to external data. **Safe for:** Trusted, single-user environments (your own Mac Mini, VPS, workstation) **NOT safe for:** Multi-tenant systems, public APIs, untrusted agents If your OpenClaw agent is compromised via prompt injection, Grago can execute arbitrary commands. This is the trade-off for free local compute. Read `SECURITY.md` in the repo for full details. ## When to Use This Skill Use Grago when: - You need live data fetched (web pages, APIs, RSS feeds, logs) - The task is research-heavy and doesn't need your primary model - You want to keep data on your own machine (privacy) - You want to save tokens by offloading analysis to a local LLM ## How It Works 1. **Fetch** — Shell scripts pull live data (curl, jq, grep, etc.) 2. **Analyze** — Results are piped to your local Ollama model with a prompt 3. **Return** — Structured analysis comes back to your OpenClaw agent ## Usage ```bash # Fetch a URL and analyze locally grago fetch "https://example.com" \ --analyze "Summarize the key points" \ --model gemma2 # Multi-source research from a YAML config grago research \ --sources sources.yaml \ --prompt "What are the main themes across these sources?" # Pipe any shell command into your local model grago pipe \ --fetch "curl -s https://api.example.com/data" \ --transform "jq .results" \ --analyze "Identify trends and flag outliers" ``` ## Configuration Config file: `~/.grago/config.yaml` ```yaml default_model: gemma2 # Your preferred Ollama model timeout: 30 # Seconds per fetch max_input_chars: 16000 # Input truncation limit output_format: markdown # markdown | json | text ``` ## Requirements - Ollama installed and running locally (install.sh handles this) - At least one model pulled in Ollama (gemma2, mistral, llama3, etc.) - bash, curl, jq ## Installation ```bash git clone https://github.com/solsuk/grago.git cd grago && ./install.sh ``` ## Notes for the Agent - Prefer `pipe` mode over `fetch --analyze` for reliability (avoids Ollama TTY spinner issues) - Default model is whatever is set in `~/.grago/config.yaml`; override per-call with `--model` - Input is truncated to `max_input_chars` before being sent to the local model - Local model responses can be slow (5–30s depending on hardware and model size) — this is expected - Grago is for **research and fetch delegation** — not for tasks requiring your primary model's reasoning

path: README.md
content: # Grago

**Stop burning tokens. Start using your machine.**

An OpenClaw skill that lets your agent delegate research and data-fetch tasks to a free local LLM running on your own hardware — 24/7, no cloud costs.

---

## ⚠️ Security Notice

**Grago executes shell commands by design.** This is intentional and necessary — local LLMs can't use tools natively, so Grago bridges the gap by running commands on your behalf.

**Risk:** If your OpenClaw agent is compromised or prompt-injected, Grago can execute arbitrary commands on your machine.

**Safe for:**
- ✅ Personal Mac Mini / VPS running your own OpenClaw agent
- ✅ Trusted, single-user environments
- ✅ Developers who understand the security model

**NOT safe for:**
- ❌ Multi-tenant systems
- ❌ Public-facing agent APIs
- ❌ Environments with untrusted agents

Read [SECURITY.md](SECURITY.md) for the full explanation of why "vulnerabilities" are actually features.

If this security model doesn't work for you, stick with paid API services instead.

---

## The Problem

You're running OpenClaw. Your agent is sharp. But every research task, every web fetch, every "go look that up" burns tokens. Cloud models are expensive.

Meanwhile, you've got a Mac Mini (or any capable machine) sitting there doing nothing. It could be running a local LLM for **free**. But there's a catch:

> Small local LLMs can't use tools. They can't browse the web, call APIs, or fetch live data. They're powerful — but blind.

So your expensive cloud model keeps doing all the heavy lifting, and your hardware collects dust.

## The Fix

Grago bridges that gap. It's an OpenClaw skill that lets your agent delegate research and fetch tasks to your local machine — for free.

Grago handles the tool work that local LLMs can't: it fetches URLs, hits APIs, reads files, and transforms data using shell scripts. Then it pipes the results directly into your local model with a focused prompt.

**Your OpenClaw agent stays sharp. Your local machine does the legwork. Your token bill drops.**

---

## Who Is This For?

- You use OpenClaw and your token costs are real
- You have a Mac Mini, M-series Mac, or any machine capable of running Ollama
- You want your agent doing 24/7 research without a cloud bill
- You want your local models earning their keep
- You keep your data on your own hardware

**Requirements:**
- OpenClaw (any plan)
- A machine capable of running Ollama (Mac Mini M2+, MacBook Pro M-series, or equivalent)
- Tested with: Gemma, Mistral, Llama, Qwen, GLM, and more

---

## Installation

```bash
# Clone the repo
git clone https://github.com/solsuk/grago.git
cd grago

# Run the installer (sets up Ollama + a default model if not already installed)
./install.sh
```

The installer will:
1. Check for Ollama — install it if missing
2. Pull a recommended local model (Gemma 2 9B by default)
3. Copy `SKILL.md` to your OpenClaw workspace skills folder
4. Create `~/.grago/config.yaml` with sensible defaults

---

## Usage

### Fetch a URL and analyze with your local model

```bash
grago fetch "https://news.ycombinator.com/" \
  --analyze "Summarize the top 5 stories" \
  --model gemma2
```

### Multi-source research

```bash
grago research \
  --sources sources.yaml \
  --prompt "What are competitors charging for similar tools?"
```

### Chain any shell command into your local model

```bash
grago pipe \
  --fetch "curl -s https://api.example.com/stats" \
  --transform "jq .results" \
  --analyze "Flag anything unusual or worth noting"
```

---

## As an OpenClaw Skill

Once installed, your OpenClaw agent can call Grago directly:

1. Agent receives a research task from you
2. Instead of burning cloud tokens, it calls `grago`
3. Grago fetches the data locally using shell/curl
4. Passes results to your local Ollama model with a focused prompt
5. Returns the analysis back to your OpenClaw agent

Your cloud model handles the thinking. Your local machine handles the fetching and grunt analysis. Best of both worlds.

---

## Configuration

Config lives at `~/.grago/config.yaml`:

```yaml
default_model: gemma2        # Ollama model to use
timeout: 30                  # Seconds per fetch
max_input_chars: 16000       # Truncate input to fit context window
output_format: markdown      # markdown | json | text
```

### sources.yaml Example

```yaml
sources:
  - name: hacker_news
    type: web
    url: "https://news.ycombinator.com/"

  - name: competitor_pricing
    type: web
    url: "https://competitor.com/pricing"

  - name: local_logs
    type: file
    path: "/var/log/myapp/*.log"
    transform: "tail -100"

  - name: live_api
    type: api
    url: "https://api.example.com/v1/stats"
    headers:
      Authorization: "Bearer ${API_KEY}"
```

---

## Commands

| Command | Description |
|---------|-------------|
| `grago fetch <url>` | Fetch a URL, optionally analyze |
| `grago research --sources <yaml>` | Multi-source fetch + analysis |
| `grago pipe --fetch <cmd>` | Chain shell command → local model |
| `grago version` | Show version |
| `grago help` | Show help |

---

## Dependencies

- `bash`, `curl`, `jq` (standard on macOS)
- [Ollama](https://ollama.ai) (installed automatically by `install.sh`)
- Optional: `yq` for YAML sources file parsing, `pup` for HTML parsing

---

## Purchase

Grago is available for **$5 one-time** at [underclassic.com](https://underclassic.com).

No subscription. No cloud fees. Lifetime updates.

---

## License

Single-user license. One purchase, one person. Don't redistribute.

---

*Built for [OpenClaw](https://openclaw.ai) — the personal AI agent platform.*


path: SECURITY.md
content: # Security Notice for Grago

## ⚠️ INTENTIONAL DESIGN: Command Execution is the Feature

Grago uses `eval` to execute shell commands. This is **not a bug** — it's the core functionality.

### Why This Exists

Local LLMs (Ollama, llama.cpp, etc.) **cannot use tools natively**. They can't fetch URLs, call APIs, or run shell commands. Grago bridges that gap by:

1. Accepting shell commands from your OpenClaw agent
2. Executing them in your local environment  
3. Piping results to your local LLM for analysis

**Without command execution, Grago cannot function.**

### The Trade-Off

**Risk:** If your OpenClaw agent is prompt-injected or compromised, Grago can execute arbitrary commands on your machine.

**Mitigation:**
- Grago is designed for **trusted, single-user environments** (your own Mac Mini, VPS, or workstation)
- It's meant for agents YOU control, not public-facing bots
- The threat model assumes your OpenClaw agent is trustworthy

### Not Suitable For

❌ Multi-tenant environments  
❌ Public-facing agent APIs  
❌ Untrusted agents  
❌ Shared systems with sensitive data  

### Suitable For

✅ Your personal Mac Mini running OpenClaw 24/7  
✅ A dedicated research VPS for your agent  
✅ Self-hosted environments where you control the agent  
✅ Developers who understand the security model  

## Comparison to Other Tools

**Grago is no more dangerous than:**
- SSH access to your own machine
- Running shell scripts you wrote
- Using curl/jq/grep manually
- Any OpenClaw skill that uses `exec`

**The difference:** Grago automates what you'd do manually, delegating it to a local LLM.

## ClawHub Scanner Findings

ClawHub's automated scanner correctly identifies:
1. Shell injection via `eval` on user input ✅ **Correct**
2. Arbitrary file read via `sources.yaml` ✅ **Correct**
3. Prompt injection risk ✅ **Correct**

**These are all true.** They're also all intentional.

## If You're Uncomfortable

If this security model doesn't fit your environment:
- Don't install Grago
- Use paid API services instead (OpenAI, Anthropic, etc.)
- Wait for local models to get native tool support

Grago is for power users who want to leverage local compute and accept the trade-offs.

---

**TL;DR:** Grago executes shell commands. That's the point. If you trust your OpenClaw agent and control your environment, it's safe. If not, don't use it.


path: grago.sh
content: #!/usr/bin/env bash
# Grago — Pre-fetch data for tool-less local models
# Usage: grago <command> [options]

set -euo pipefail

VERSION="0.1.0"
CONFIG_DIR="${HOME}/.grago"
CONFIG_FILE="${CONFIG_DIR}/config.yaml"
DEFAULT_MODEL="gemma"
DEFAULT_TIMEOUT=30
DEFAULT_MAX_CHARS=16000
DEFAULT_FORMAT="markdown"

# --- Helpers ---

log() { echo "[grago] $*" >&2; }
err() { echo "[grago] ERROR: $*" >&2; exit 1; }

ensure_deps() {
  for cmd in curl jq; do
    command -v "$cmd" >/dev/null || err "Missing dependency: $cmd"
  done
}

get_config() {
  local key="$1" default="$2"
  if [[ -f "$CONFIG_FILE" ]] && command -v yq >/dev/null 2>&1; then
    local val
    val=$(yq -r ".$key // \"\"" "$CONFIG_FILE" 2>/dev/null)
    [[ -n "$val" && "$val" != "null" ]] && echo "$val" || echo "$default"
  else
    echo "$default"
  fi
}

MODEL=$(get_config "default_model" "$DEFAULT_MODEL")
TIMEOUT=$(get_config "timeout" "$DEFAULT_TIMEOUT")
MAX_CHARS=$(get_config "max_input_chars" "$DEFAULT_MAX_CHARS")
FORMAT=$(get_config "output_format" "$DEFAULT_FORMAT")

truncate_input() {
  head -c "$MAX_CHARS"
}

# --- Core: send text to local model ---

analyze() {
  local prompt="$1"
  local input
  input=$(cat | truncate_input)
  
  local system_msg="You are a research analyst. Respond in ${FORMAT} format. Be concise and focus on actionable insights."
  
  # Try Ollama first
  if command -v ollama >/dev/null 2>&1; then
    echo "${input}" | ollama run "$MODEL" "${prompt}"
    return
  fi
  
  # Fallback: OpenAI-compatible endpoint
  local api_base
  api_base=$(get_config "api_base" "http://localhost:11434/v1")
  
  local payload
  payload=$(jq -n \
    --arg model "$MODEL" \
    --arg system "$system_msg" \
    --arg user "${prompt}\n\n---\nDATA:\n${input}" \
    '{model: $model, messages: [{role: "system", content: $system}, {role: "user", content: $user}], stream: false}')
  
  curl -s "${api_base}/chat/completions" \
    -H "Content-Type: application/json" \
    -d "$payload" | jq -r '.choices[0].message.content // "Error: no response"'
}

# --- Commands ---

cmd_fetch() {
  local url="" prompt="" transform=""
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --analyze) prompt="$2"; shift 2 ;;
      --transform) transform="$2"; shift 2 ;;
      --model) MODEL="$2"; shift 2 ;;
      --timeout) TIMEOUT="$2"; shift 2 ;;
      *) url="$1"; shift ;;
    esac
  done
  
  [[ -z "$url" ]] && err "Usage: grago fetch <url> [--analyze <prompt>] [--transform <cmd>]"
  
  local data
  data=$(curl -sL --max-time "$TIMEOUT" "$url") || err "Failed to fetch: $url"
  
  if [[ -n "$transform" ]]; then
    data=$(echo "$data" | eval "$transform") || err "Transform failed: $transform"
  fi
  
  if [[ -n "$prompt" ]]; then
    echo "$data" | analyze "$prompt"
  else
    echo "$data"
  fi
}

cmd_research() {
  local sources_file="" prompt="" output_file=""
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --sources) sources_file="$2"; shift 2 ;;
      --prompt) prompt="$2"; shift 2 ;;
      --output) output_file="$2"; shift 2 ;;
      --model) MODEL="$2"; shift 2 ;;
      *) shift ;;
    esac
  done
  
  [[ -z "$sources_file" ]] && err "Usage: grago research --sources <file> --prompt <question>"
  [[ ! -f "$sources_file" ]] && err "Sources file not found: $sources_file"
  
  local combined=""
  local count
  count=$(yq '.sources | length' "$sources_file" 2>/dev/null) || err "Failed to parse sources file (need yq)"
  
  for ((i=0; i<count; i++)); do
    local name url transform type
    name=$(yq -r ".sources[$i].name" "$sources_file")
    url=$(yq -r ".sources[$i].url // \"\"" "$sources_file")
    transform=$(yq -r ".sources[$i].transform // \"\"" "$sources_file")
    type=$(yq -r ".sources[$i].type // \"web\"" "$sources_file")
    
    log "Fetching: $name ($type)"
    
    local data=""
    case "$type" in
      web|api)
        data=$(curl -sL --max-time "$TIMEOUT" "$url" 2>/dev/null) || { log "WARN: Failed $name"; continue; }
        ;;
      file)
        local path
        path=$(yq -r ".sources[$i].path" "$sources_file")
        data=$(cat $path 2>/dev/null) || { log "WARN: Failed $name"; continue; }
        ;;
    esac
    
    if [[ -n "$transform" ]]; then
      data=$(echo "$data" | eval "$transform" 2>/dev/null) || data="[transform failed]"
    fi
    
    combined+="
=== SOURCE: ${name} ===
${data}

"
  done
  
  local result
  result=$(echo "$combined" | analyze "${prompt:-Analyze the following data sources and provide key insights.}")
  
  if [[ -n "$output_file" ]]; then
    echo "$result" > "$output_file"
    log "Output saved to: $output_file"
  fi
  
  echo "$result"
}

cmd_pipe() {
  local fetch_cmd="" transform_cmd="" prompt=""
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --fetch) fetch_cmd="$2"; shift 2 ;;
      --transform) transform_cmd="$2"; shift 2 ;;
      --analyze) prompt="$2"; shift 2 ;;
      --model) MODEL="$2"; shift 2 ;;
      *) shift ;;
    esac
  done
  
  [[ -z "$fetch_cmd" ]] && err "Usage: grago pipe --fetch <cmd> [--transform <cmd>] --analyze <prompt>"
  
  local data
  data=$(eval "$fetch_cmd") || err "Fetch command failed"
  
  if [[ -n "$transform_cmd" ]]; then
    data=$(echo "$data" | eval "$transform_cmd") || err "Transform failed"
  fi
  
  echo "$data" | analyze "${prompt:-Summarize this data.}"
}

cmd_version() { echo "grago v${VERSION}"; }

cmd_help() {
  cat <<EOF
Grago v${VERSION} — Pre-fetch data for tool-less local models

Commands:
  fetch <url>                    Fetch URL, optionally analyze
    --analyze <prompt>           Send fetched data to model with prompt
    --transform <cmd>            Transform data before analysis (e.g., "jq .results")
    --model <name>               Override model
  
  research                       Multi-source fetch + analysis
    --sources <yaml>             Sources definition file
    --prompt <question>          Analysis prompt
    --output <file>              Save output to file
  
  pipe                           Chain fetch → transform → analyze
    --fetch <cmd>                Shell command to fetch data
    --transform <cmd>            Transform command
    --analyze <prompt>           Analysis prompt
  
  version                        Show version
  help                           Show this help

Config: ~/.grago/config.yaml
EOF
}

# --- Main ---

ensure_deps

case "${1:-help}" in
  fetch)    shift; cmd_fetch "$@" ;;
  research) shift; cmd_research "$@" ;;
  pipe)     shift; cmd_pipe "$@" ;;
  version)  cmd_version ;;
  help|*)   cmd_help ;;
esac


path: install.sh
content: #!/usr/bin/env bash
# Grago Installer
# Sets up Ollama + a default model, installs grago, and drops the skill into OpenClaw

set -euo pipefail

GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

echo ""
echo "╔══════════════════════════════════╗"
echo "║        GRAGO INSTALLER           ║"
echo "║  Stop burning tokens. Use yours. ║"
echo "╚══════════════════════════════════╝"
echo ""

# 1. Check for Ollama
if ! command -v ollama &>/dev/null; then
  echo -e "${YELLOW}Ollama not found. Installing...${NC}"
  if [[ "$(uname)" == "Darwin" ]]; then
    if command -v brew &>/dev/null; then
      brew install --cask ollama
    else
      echo "Please install Ollama manually from https://ollama.ai and re-run this script."
      exit 1
    fi
  else
    curl -fsSL https://ollama.ai/install.sh | sh
  fi
  echo -e "${GREEN}✓ Ollama installed${NC}"
else
  echo -e "${GREEN}✓ Ollama already installed${NC}"
fi

# 2. Start Ollama if not running
if ! ollama list &>/dev/null 2>&1; then
  echo "Starting Ollama..."
  ollama serve &>/dev/null &
  sleep 3
fi

# 3. Pull a default model if none present
MODEL_COUNT=$(ollama list 2>/dev/null | tail -n +2 | wc -l | tr -d ' ')
if [[ "$MODEL_COUNT" -eq 0 ]]; then
  echo -e "${YELLOW}No local models found. Pulling gemma2:9b (recommended)...${NC}"
  echo "This is a one-time download (~5.4 GB). Go make a coffee. ☕"
  ollama pull gemma2:9b
  DEFAULT_MODEL="gemma2:9b"
  echo -e "${GREEN}✓ gemma2:9b pulled${NC}"
else
  DEFAULT_MODEL=$(ollama list 2>/dev/null | tail -n +2 | head -1 | awk '{print $1}')
  echo -e "${GREEN}✓ Using existing model: ${DEFAULT_MODEL}${NC}"
fi

# 4. Install grago
INSTALL_DIR="/usr/local/bin"
if [[ ! -w "$INSTALL_DIR" ]]; then
  INSTALL_DIR="$HOME/.local/bin"
  mkdir -p "$INSTALL_DIR"
fi

cp grago.sh "$INSTALL_DIR/grago"
chmod +x "$INSTALL_DIR/grago"
echo -e "${GREEN}✓ grago installed to ${INSTALL_DIR}/grago${NC}"

# 5. Write config
CONFIG_DIR="$HOME/.grago"
mkdir -p "$CONFIG_DIR"
if [[ ! -f "$CONFIG_DIR/config.yaml" ]]; then
  cat > "$CONFIG_DIR/config.yaml" << YAML
default_model: ${DEFAULT_MODEL}
timeout: 30
max_input_chars: 16000
output_format: markdown
YAML
  echo -e "${GREEN}✓ Config written to ~/.grago/config.yaml${NC}"
else
  echo -e "${GREEN}✓ Config already exists at ~/.grago/config.yaml${NC}"
fi

# 6. Install OpenClaw skill
SKILL_DIRS=(
  "$HOME/.openclaw/workspace/skills/grago"
  "$HOME/Library/Application Support/openclaw/workspace/skills/grago"
)

SKILL_INSTALLED=false
for DIR in "${SKILL_DIRS[@]}"; do
  if [[ -d "$(dirname "$DIR")" ]]; then
    mkdir -p "$DIR"
    cp SKILL.md "$DIR/"
    echo -e "${GREEN}✓ OpenClaw skill installed to ${DIR}${NC}"
    SKILL_INSTALLED=true
    break
  fi
done

if [[ "$SKILL_INSTALLED" == false ]]; then
  echo -e "${YELLOW}⚠ OpenClaw workspace not found — copy SKILL.md manually to your skills folder${NC}"
fi

# 7. PATH check
if ! echo "$PATH" | grep -q "$INSTALL_DIR"; then
  echo ""
  echo -e "${YELLOW}Note: Add ${INSTALL_DIR} to your PATH if not already:${NC}"
  echo "  echo 'export PATH=\"\$PATH:${INSTALL_DIR}\"' >> ~/.zshrc && source ~/.zshrc"
fi

echo ""
echo -e "${GREEN}═══════════════════════════════════${NC}"
echo -e "${GREEN}  Grago installed! Quick test:${NC}"
echo -e "${GREEN}  grago fetch \"https://example.com\" --analyze \"Summarize this\"${NC}"
echo -e "${GREEN}═══════════════════════════════════${NC}"
echo ""
echo "Full docs: https://github.com/solsuk/grago"
echo "Support:   https://underclassic.com"
echo ""


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)