Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,8 @@ Install the plugin from the Claude Code marketplace.

### Step 3: Configure API Key

```bash
./scripts/setup.sh
```
Run `/grok-swarm:setup` inside Claude Code — an OAuth browser flow will
authorize your OpenRouter account without exposing your API key in-context.

### Usage in Claude Code

Expand Down Expand Up @@ -147,9 +146,9 @@ cp -r src/plugin ~/.openclaw/extensions/grok-swarm/

### Configure API Key

```bash
./scripts/setup.sh
```
**For Claude Code:** Run `/grok-swarm:setup` — OAuth browser flow, no key in-context.

**For OpenClaw/CLI:** Run `./scripts/setup.sh` or set `OPENROUTER_API_KEY` manually.

---

Expand All @@ -175,9 +174,9 @@ grok-swarm --help

### For Claude Code

```bash
# Set up API key
./scripts/setup.sh
```
# Authorize via OAuth (no API key in-context)
/grok-swarm:setup

# Try it out
/grok-swarm:analyze Review my auth module for security issues
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ npm install -g @openclaw/grok-swarm

# Install the plugin
/plugin install grok-swarm@khaentertainment

# Set up API key
./scripts/setup.sh
```

Then run `/grok-swarm:setup` inside Claude Code — an OAuth browser flow will
authorize your OpenRouter account without exposing your API key in-context.

### Option 3: ClawHub (OpenClaw)

```bash
Expand Down
98 changes: 78 additions & 20 deletions platforms/claude/commands/setup.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,100 @@
---
name: setup
description: Set up your OpenRouter API key for Grok Swarm. Run this before first use if prompted.
description: Authorize Grok Swarm with your OpenRouter account via OAuth. No API key handling in-context.
argument-hint: None
allowed-tools:
- Bash
---

# Setup Grok Swarm

This command guides you through configuring your OpenRouter API key for Grok Swarm.
Follow these steps exactly. Do not ask the user for their API key — the OAuth
flow ensures the key never passes through this conversation.

## Usage
## Step 1 — Check for existing key

Run:
```bash
OAUTH_PATH="$(find ~/.claude/plugins -name 'oauth_setup.py' 2>/dev/null | head -1)"
if [ -n "$OAUTH_PATH" ]; then
python3 "$(dirname "$OAUTH_PATH")/oauth_setup.py" --check 2>/dev/null
else
OAUTH_PATH="$(find /usr /usr/local ~/.local -name 'oauth_setup.py' 2>/dev/null | head -1)"
[ -n "$OAUTH_PATH" ] && python3 "$OAUTH_PATH" --check 2>/dev/null
fi
```
/grok-swarm:setup

Alternative (locate bridge relative to this command file):
```bash
OAUTH_PATH="$(find ~/.claude/plugins -name 'oauth_setup.py' 2>/dev/null | head -1)"
if [ -n "$OAUTH_PATH" ]; then
BRIDGE_DIR="$(dirname "$OAUTH_PATH")"
else
OAUTH_PATH="$(find /usr /usr/local ~/.local -name 'oauth_setup.py' 2>/dev/null | head -1)"
[ -n "$OAUTH_PATH" ] && BRIDGE_DIR="$(dirname "$OAUTH_PATH")"
fi
if [ -f "$BRIDGE_DIR/oauth_setup.py" ]; then
python3 "$BRIDGE_DIR/oauth_setup.py" --check
fi
```

## What it does
If the check exits 0, tell the user their key is already configured and offer
to run a test query. **Stop here.**

## Step 2 — Run the OAuth flow

1. Checks for existing API key in:
- `~/.claude/grok-swarm.local.md` (plugin settings)
- `~/.config/grok-swarm/config.json` (CLI config)
- `$OPENROUTER_API_KEY` environment variable
Locate `oauth_setup.py` in the plugin's `src/bridge/` directory and run it
with a 240 seconds timeout. The timeout is enforced by the Bash tool invocation
(via the `timeout` parameter in the tool call or CI runner timeout settings):

```bash
OAUTH_PATH="$(find ~/.claude/plugins -name 'oauth_setup.py' 2>/dev/null | head -1)"
if [ -n "$OAUTH_PATH" ]; then
PLUGIN_ROOT="$(cd "$(dirname "$OAUTH_PATH")/../.." 2>/dev/null && pwd)"
else
OAUTH_PATH="$(find /usr /usr/local ~/.local -name 'oauth_setup.py' 2>/dev/null | head -1)"
[ -n "$OAUTH_PATH" ] && PLUGIN_ROOT="$(cd "$(dirname "$OAUTH_PATH")/../.." 2>/dev/null && pwd)"
fi
timeout 240s python3 "$PLUGIN_ROOT/src/bridge/oauth_setup.py"
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

2. If no key found, prompts you to enter one
**Note**: The `timeout 240s` wrapper ensures the command terminates if the OAuth
flow exceeds 240 seconds. The script itself has an internal OAUTH_TIMEOUT_SECS
(180s) for the callback phase plus roughly 30s for token exchange, so the 240s
outer limit provides a safe margin.

3. Saves configuration to plugin settings file
The script will:
1. Print an authorization URL
2. Start a local server on `localhost:3000`
3. Wait up to 180 seconds for the browser callback
4. Exchange the auth code for an API key
5. Save the key to `~/.config/grok-swarm/config.json` (chmod 600)

## First-time setup
## Step 3 — Present the auth URL

If you're seeing this for the first time:
Show the user the URL printed by the script and tell them:

1. Get an OpenRouter API key at https://openrouter.ai/keys
2. Run `/grok-swarm:setup`
3. Paste your API key when prompted
> **Click this link to authorize Grok Swarm with your OpenRouter account.**
> After approving in your browser, return here — setup completes automatically.

## Troubleshooting
## Step 4 — Report result

- **Invalid key error**: Make sure you copied the key correctly from OpenRouter
- **Key starts with `sk-`**: That's correct! OpenRouter keys begin with `sk-or-v1-`
- **Permission denied**: The setup script needs to write to `~/.claude/` and `~/.config/`
- If the script exits 0: confirm success and suggest running `/grok-swarm:analyze Hello world`
- If it exits 1 (timeout or port conflict): show the error message from the script
and suggest the manual fallback:
Direct them to https://openrouter.ai/keys for a key.

## xAI Direct Users

If the user says they want to use xAI directly (not OpenRouter), run:
```bash
OAUTH_PATH="$(find ~/.claude/plugins -name 'oauth_setup.py' 2>/dev/null | head -1)"
if [ -n "$OAUTH_PATH" ]; then
PLUGIN_ROOT="$(cd "$(dirname "$OAUTH_PATH")/../.." 2>/dev/null && pwd)"
else
OAUTH_PATH="$(find /usr /usr/local ~/.local -name 'oauth_setup.py' 2>/dev/null | head -1)"
[ -n "$OAUTH_PATH" ] && PLUGIN_ROOT="$(cd "$(dirname "$OAUTH_PATH")/../.." 2>/dev/null && pwd)"
fi
python3 "$PLUGIN_ROOT/src/bridge/oauth_setup.py" --provider xai
```
This prints manual credential instructions without attempting OAuth.
16 changes: 13 additions & 3 deletions platforms/claude/commands/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ if api_key=$(get_api_key) && [ -n "$api_key" ]; then
exit 0
fi

# No API key - prompt for setup
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
exec "$SCRIPT_DIR/setup.sh"
# No API key found — guide user to the OAuth-based setup command
echo ""
echo "No API key configured."
echo ""
echo "Run '/grok-swarm:setup' inside Claude Code to authorize via OpenRouter's"
echo "OAuth flow. Your key will never pass through Claude's context window."
echo ""
echo "Or set manually:"
echo " export OPENROUTER_API_KEY=sk-or-v1-..."
echo " # or: mkdir -p ~/.config/grok-swarm && echo '{\"api_key\": \"sk-or-v1-...\"}' > ~/.config/grok-swarm/config.json"
echo ""
echo "Get a key at: https://openrouter.ai/keys"
exit 1
12 changes: 12 additions & 0 deletions skills/grok-refactor/bridge/grok_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,18 @@ def get_api_key():
except (json.JSONDecodeError, KeyError):
pass

# 2b. Claude Code plugin settings file (legacy: written by setup.sh)
claude_settings = Path.home() / ".claude" / "grok-swarm.local.md"
if claude_settings.exists():
try:
for line in claude_settings.read_text(encoding="utf-8").splitlines():
if line.startswith("api_key:"):
key = line.split(":", 1)[1].strip()
if key:
return key
except OSError:
pass

# 3. OpenClaw auth profiles (for OpenClaw integration)
auth_paths = [
Path.home() / ".openclaw" / "agents" / "coder" / "agent" / "auth-profiles.json",
Expand Down
4 changes: 2 additions & 2 deletions src/agent/grok_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,8 @@ def parse_code_blocks(response_text: str) -> list[dict]:
"""
blocks = []

# Pattern 1: lang:/path/to/file (language tag contains path)
lang_path_pattern = re.compile(r'^(\w+):(/[^/\s\n]+(?:/[^/\s\n]+)*)\n', re.MULTILINE)
# Pattern 1: lang:path/to/file (language tag contains path; relative OR absolute)
lang_path_pattern = re.compile(r'^(\w+):([^\s\n]+)\n', re.MULTILINE)

# Pattern 2: // FILE: /path or # FILE: /path
file_marker_pattern = re.compile(
Expand Down
20 changes: 17 additions & 3 deletions src/bridge/grok_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@ def get_api_key():
except (json.JSONDecodeError, KeyError):
pass

# 2b. Claude Code plugin settings file (legacy: written by setup.sh)
claude_settings = Path.home() / ".claude" / "grok-swarm.local.md"
if claude_settings.exists():
try:
for line in claude_settings.read_text(encoding="utf-8").splitlines():
if line.startswith("api_key:"):
key = line.split(":", 1)[1].strip()
if key:
return key
except OSError:
pass

# 3. OpenClaw auth profiles (for OpenClaw integration)
auth_paths = [
Path.home() / ".openclaw" / "agents" / "coder" / "agent" / "auth-profiles.json",
Expand Down Expand Up @@ -175,7 +187,9 @@ def _safe_dest(output_path, file_path):
"""
raw = Path(file_path)
if raw.is_absolute():
raise ValueError(f"Absolute paths are not allowed: {file_path!r}")
rebased = Path(raw.name)
print(f"WARNING: Absolute path rebased to output dir: {file_path!r} → {rebased!r}", file=sys.stderr)
raw = rebased
if ".." in raw.parts:
raise ValueError(f"Path traversal not allowed: {file_path!r}")
dest = (output_path / raw).resolve()
Expand Down Expand Up @@ -212,8 +226,8 @@ def parse_and_write_files(response_text, output_dir):
written = []
output_path = Path(output_dir)

# Pattern 1: lang:/path/to/file (language tag contains path)
lang_path_pattern = re.compile(r'^(\w+):(/[^\s\n]+(?:/[^\s\n]+)*)\n', re.MULTILINE)
# Pattern 1: lang:path/to/file (language tag contains path; relative OR absolute — broad match)
lang_path_pattern = re.compile(r'^(\w+):([^\s\n]+)\n', re.MULTILINE)

# Pattern 2: // FILE: /path or # FILE: /path
file_marker_pattern = re.compile(
Expand Down
Loading