Skill: blueberrywoodsym/x-ai (version 1.0.2)

--- name: xai description: Chat with Grok models via xAI API. Supports Grok-3, Grok-3-mini, vision, and more. homepage: https://docs.x.ai user-invocable: true disable-model-invocation: true triggers: - grok - xai - ask grok metadata: clawdbot: emoji: "🤖" primaryEnv: XAI_API_KEY requires: bins: [node] env: [XAI_API_KEY] --- # xAI / Grok Chat with xAI's Grok models. Supports text and vision. ## Setup [INSTALLATION AND USE INSTRUCTIONS](https://claude.ai/public/artifacts/5bb1d4ca-68ef-4d74-900c-d14fefa5c094) ```bash export XAI_API_KEY="xai-YOUR-KEY" ``` Get your API key at: https://console.x.ai ## Commands ### Chat with Grok ```bash node {baseDir}/scripts/chat.js "What is the meaning of life?" ``` ### Use a specific model ```bash node {baseDir}/scripts/chat.js --model grok-3-mini "Quick question: 2+2?" ``` ### Vision (analyze images) ```bash node {baseDir}/scripts/chat.js --image /path/to/image.jpg "What's in this image?" ``` ### 🔍 Search X/Twitter (Real-time!) ```bash node {baseDir}/scripts/search-x.js "Remotion video framework" node {baseDir}/scripts/search-x.js --days 7 "Claude AI tips" node {baseDir}/scripts/search-x.js --handles @remotion_dev "updates" ``` Uses xAI Responses API with x_search tool for real X posts with citations. ### List available models ```bash node {baseDir}/scripts/models.js ``` ## Available Models - `grok-3` - Most capable, best for complex tasks - `grok-3-mini` - Fast and efficient - `grok-3-fast` - Optimized for speed - `grok-2-vision-1212` - Vision model for image understanding ## Example Usage **User:** "Ask Grok what it thinks about AI safety" **Action:** Run chat.js with the prompt **User:** "Use Grok to analyze this image" (with attached image) **Action:** Run chat.js with --image flag **User:** "What Grok models are available?" **Action:** Run models.js ## API Reference xAI API Docs: https://docs.x.ai/api ## Environment Variables - `XAI_API_KEY` - Your xAI API key (required) - `XAI_MODEL` - Default model (optional, defaults to grok-3) ## Security & Permissions **What this skill does:** - Sends chat prompts to xAI's API at `api.x.ai` - Vision mode sends images to xAI for analysis - `scripts/models.js` lists available models (read-only) **What this skill does NOT do:** - Does not read arbitrary local files — `--image` only accepts files with image extensions (`.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`) - Does not read config files or access the filesystem beyond the specified image path - Does not store conversation history or logs - Does not send credentials to any endpoint other than `api.x.ai` - Cannot be invoked autonomously by the agent (`disable-model-invocation: true`) **Bundled scripts:** `scripts/chat.js` (chat), `scripts/models.js` (list models), `scripts/search-x.js` (X search) Review scripts before first use to verify behavior.

path: .gitignore
content: node_modules/
.env
*.log


path: package.json
content: {
  "name": "clawdbot-skill-xai",
  "version": "1.0.0",
  "description": "xAI/Grok integration for Clawdbot",
  "keywords": ["clawdbot-skill", "xai", "grok", "ai", "chat"],
  "author": "Lobster",
  "license": "MIT",
  "scripts": {
    "chat": "node scripts/chat.js",
    "models": "node scripts/models.js"
  }
}


path: README.md
content: # xAI / Grok Skill for Clawdbot

Chat with xAI's Grok models from Clawdbot. Supports text chat, vision, and all Grok models including Grok-4.

## Installation

```bash
clawdhub install xai
# or
cd ~/clawd/skills && git clone https://github.com/mvanhorn/clawdbot-skill-xai xai
```

## Setup

Get your API key from [console.x.ai](https://console.x.ai), then:

```bash
clawdbot config set skills.entries.xai.apiKey "xai-YOUR-KEY"
```

Or set environment variable:
```bash
export XAI_API_KEY="xai-YOUR-KEY"
```

## Usage

### Chat with Grok
```bash
node scripts/chat.js "What is the meaning of life?"
```

### Use specific model
```bash
node scripts/chat.js --model grok-4-0709 "Complex question here"
node scripts/chat.js --model grok-3-mini "Quick question"
```

### Vision (analyze images)
```bash
node scripts/chat.js --image photo.jpg "What's in this image?"
```

### System prompts
```bash
node scripts/chat.js --system "You are a pirate" "Tell me about ships"
```

### List available models
```bash
node scripts/models.js
```

## Available Models

- `grok-3` - Capable general model
- `grok-3-mini` - Fast and efficient
- `grok-4-0709` - Latest Grok 4
- `grok-4-fast-reasoning` - Fast with reasoning
- `grok-2-vision-1212` - Image understanding
- `grok-2-image-1212` - Image generation

## Clawdbot Integration

Once installed, you can just say:
- "Ask Grok about [topic]"
- "Use Grok to analyze this image"
- "Have Grok 4 explain [concept]"

## License

MIT


path: scripts/chat.js
content: #!/usr/bin/env node
/**
 * xAI/Grok Chat Script
 * 
 * Usage:
 *   node chat.js "Your prompt here"
 *   node chat.js --model grok-3-mini "Quick question"
 *   node chat.js --image /path/to/image.jpg "Describe this"
 *   node chat.js --system "You are a pirate" "Tell me about ships"
 */

const https = require('https');
const fs = require('fs');
const path = require('path');

const API_BASE = 'api.x.ai';
const DEFAULT_MODEL = 'grok-3';

function getApiKey() {
  return process.env.XAI_API_KEY || null;
}

function parseArgs(args) {
  const result = {
    model: process.env.XAI_MODEL || DEFAULT_MODEL,
    system: null,
    image: null,
    prompt: '',
    stream: false,
    json: false,
  };
  
  let i = 0;
  while (i < args.length) {
    const arg = args[i];
    
    if (arg === '--model' || arg === '-m') {
      result.model = args[++i];
    } else if (arg === '--system' || arg === '-s') {
      result.system = args[++i];
    } else if (arg === '--image' || arg === '-i') {
      result.image = args[++i];
    } else if (arg === '--stream') {
      result.stream = true;
    } else if (arg === '--json' || arg === '-j') {
      result.json = true;
    } else if (!arg.startsWith('-')) {
      result.prompt = args.slice(i).join(' ');
      break;
    }
    i++;
  }
  
  return result;
}

function imageToBase64(imagePath) {
  const absolutePath = path.resolve(imagePath);

  // Validate file extension is an allowed image type
  const ext = path.extname(absolutePath).toLowerCase();
  const mimeTypes = {
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.png': 'image/png',
    '.gif': 'image/gif',
    '.webp': 'image/webp',
  };

  if (!mimeTypes[ext]) {
    throw new Error(`Unsupported image format: ${ext}. Allowed: ${Object.keys(mimeTypes).join(', ')}`);
  }

  if (!fs.existsSync(absolutePath)) {
    throw new Error(`Image not found: ${absolutePath}`);
  }

  const imageData = fs.readFileSync(absolutePath);
  const base64 = imageData.toString('base64');
  const mimeType = mimeTypes[ext];

  return `data:${mimeType};base64,${base64}`;
}

async function chat(options) {
  const [REDACTED_SECRET]);
  if (!apiKey) {
    console.error('❌ No API key found. Set XAI_API_KEY or configure in clawdbot.');
    console.error('   Get your key at: https://console.x.ai');
    process.exit(1);
  }
  
  // Build messages
  const messages = [];
  
  if (options.system) {
    messages.push({ role: 'system', content: options.system });
  }
  
  // Build user message content
  let userContent;
  if (options.image) {
    // Vision request
    const imageUrl = imageToBase64(options.image);
    userContent = [
      { type: 'image_url', image_url: { url: imageUrl } },
      { type: 'text', text: options.prompt || 'Describe this image.' },
    ];
    // Use vision model if not specified
    if (options.model === DEFAULT_MODEL) {
      options.model = 'grok-2-vision-1212';
    }
  } else {
    userContent = options.prompt;
  }
  
  messages.push({ role: 'user', content: userContent });
  
  const payload = {
    model: options.model,
    messages: messages,
    stream: options.stream,
  };
  
  return new Promise((resolve, reject) => {
    const req = https.request({
      hostname: API_BASE,
      path: '/v1/chat/completions',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`,
      },
    }, (res) => {
      let data = '';
      
      res.on('data', chunk => {
        data += chunk;
        
        if (options.stream) {
          // Handle streaming responses
          const lines = data.split('\n');
          data = lines.pop(); // Keep incomplete line
          
          for (const line of lines) {
            if (line.startsWith('data: ') && line !== 'data: [DONE]') {
              try {
                const json = JSON.parse(line.slice(6));
                const content = json.choices?.[0]?.delta?.content;
                if (content) {
                  process.stdout.write(content);
                }
              } catch (e) {
                // Ignore parse errors in stream
              }
            }
          }
        }
      });
      
      res.on('end', () => {
        if (res.statusCode !== 200) {
          console.error(`❌ API Error (${res.statusCode}):`, data);
          process.exit(1);
        }
        
        if (!options.stream) {
          try {
            const response = JSON.parse(data);
            const content = response.choices?.[0]?.message?.content;
            
            if (options.json) {
              console.log(JSON.stringify(response, null, 2));
            } else {
              console.log(content || '(no response)');
            }
            
            resolve(response);
          } catch (e) {
            console.error('❌ Failed to parse response:', e.message);
            process.exit(1);
          }
        } else {
          console.log(); // Newline after streaming
          resolve();
        }
      });
    });
    
    req.on('error', (e) => {
      console.error('❌ Request failed:', e.message);
      process.exit(1);
    });
    
    req.write(JSON.stringify(payload));
    req.end();
  });
}

// Main
const args = process.argv.slice(2);

if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
  console.log(`
🤖 xAI/Grok Chat

Usage:
  node chat.js [options] "Your prompt"

Options:
  --model, -m <model>   Model to use (default: grok-3)
  --system, -s <text>   System prompt
  --image, -i <path>    Image for vision analysis
  --stream              Stream the response
  --json, -j            Output full JSON response
  --help, -h            Show this help

Models:
  grok-3               Most capable
  grok-3-mini          Fast and efficient
  grok-3-fast          Speed optimized
  grok-2-vision-1212   Vision/image analysis

Examples:
  node chat.js "What is quantum computing?"
  node chat.js -m grok-3-mini "Quick: capital of France?"
  node chat.js -i photo.jpg "What's in this image?"
  node chat.js -s "You are a poet" "Write about the moon"
`);
  process.exit(0);
}

const options = parseArgs(args);

if (!options.prompt && !options.image) {
  console.error('❌ Please provide a prompt');
  process.exit(1);
}

chat(options);


path: scripts/models.js
content: #!/usr/bin/env node
/**
 * List available xAI models
 */

const https = require('https');

const API_BASE = 'api.x.ai';

function getApiKey() {
  return process.env.XAI_API_KEY || null;
}

async function listModels() {
  const [REDACTED_SECRET]);
  if (!apiKey) {
    console.error('❌ No API key found. Set XAI_API_KEY or configure in clawdbot.');
    process.exit(1);
  }
  
  return new Promise((resolve, reject) => {
    const req = https.request({
      hostname: API_BASE,
      path: '/v1/models',
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
      },
    }, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => {
        if (res.statusCode !== 200) {
          console.error(`❌ API Error (${res.statusCode}):`, data);
          process.exit(1);
        }
        
        try {
          const response = JSON.parse(data);
          const models = response.data || [];
          
          console.log('🤖 Available xAI Models:\n');
          
          for (const model of models) {
            console.log(`  • ${model.id}`);
            if (model.description) {
              console.log(`    ${model.description}`);
            }
          }
          
          if (models.length === 0) {
            console.log('  (no models available)');
          }
          
          console.log('\nUsage: node chat.js --model <model-id> "Your prompt"');
          
          resolve(models);
        } catch (e) {
          console.error('❌ Failed to parse response:', e.message);
          process.exit(1);
        }
      });
    });
    
    req.on('error', (e) => {
      console.error('❌ Request failed:', e.message);
      process.exit(1);
    });
    
    req.end();
  });
}

listModels();


path: scripts/search-x.js
content: #!/usr/bin/env node
/**
 * xAI X Search Script - Uses Responses API with x_search tool
 * 
 * Usage:
 *   node search-x.js "Remotion best practices"
 *   node search-x.js --days 30 "AI video creation"
 *   node search-x.js --handles @remotion_dev,@jonnyburger "updates"
 */

const https = require('https');

const API_BASE = 'api.x.ai';
const DEFAULT_MODEL = 'grok-4-1-fast'; // Optimized for agentic search

function getApiKey() {
  return process.env.XAI_API_KEY || null;
}

function parseArgs(args) {
  const result = {
    model: DEFAULT_MODEL,
    query: '',
    days: 30,
    handles: [],
    excludeHandles: [],
    json: false,
    requireCitations: true,
  };
  
  let i = 0;
  while (i < args.length) {
    const arg = args[i];
    
    if (arg === '--model' || arg === '-m') {
      result.model = args[++i];
    } else if (arg === '--days' || arg === '-d') {
      result.days = parseInt(args[++i], 10);
    } else if (arg === '--handles' || arg === '-h') {
      result.handles = args[++i].split(',').map(h => h.trim());
    } else if (arg === '--exclude') {
      result.excludeHandles = args[++i].split(',').map(h => h.trim());
    } else if (arg === '--json' || arg === '-j') {
      result.json = true;
    } else if (arg === '--no-require-citations') {
      result.requireCitations = false;
    } else if (!arg.startsWith('-')) {
      result.query = args.slice(i).join(' ');
      break;
    }
    i++;
  }
  
  return result;
}

function getDateRange(days) {
  const to = new Date();
  const from = new Date();
  from.setDate(from.getDate() - days);
  
  return {
    from_date: from.toISOString().split('T')[0],
    to_date: to.toISOString().split('T')[0],
  };
}

async function searchX(options) {
  const [REDACTED_SECRET]);
  if (!apiKey) {
    console.error('❌ No API key found. Set XAI_API_KEY or configure in clawdbot.');
    process.exit(1);
  }
  
  const dateRange = getDateRange(options.days);
  
  // Build x_search tool config
  const xSearchTool = {
    type: 'x_search',
    x_search: {
      from_date: dateRange.from_date,
      to_date: dateRange.to_date,
    }
  };
  
  if (options.handles.length > 0) {
    xSearchTool.x_search.allowed_x_handles = options.handles;
  }
  if (options.excludeHandles.length > 0) {
    xSearchTool.x_search.excluded_x_handles = options.excludeHandles;
  }
  
  const payload = {
    model: options.model,
    input: `Search X/Twitter and find real posts about: ${options.query}

Give me actual tweets with:
- Username/handle
- The actual tweet content
- Date if available
- Link to the tweet

Only include REAL posts you find. If you can't find any, say so.`,
    tools: [xSearchTool],
  };
  
  return new Promise((resolve, reject) => {
    const req = https.request({
      hostname: API_BASE,
      path: '/v1/responses',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`,
      },
    }, (res) => {
      let data = '';
      
      res.on('data', chunk => data += chunk);
      
      res.on('end', () => {
        if (res.statusCode !== 200) {
          console.error(`❌ API Error (${res.statusCode}):`, data);
          process.exit(1);
        }
        
        try {
          const response = JSON.parse(data);
          
          if (options.json) {
            console.log(JSON.stringify(response, null, 2));
            resolve(response);
            return;
          }
          
          // Extract content from Responses API format
          let content = '(no response)';
          if (response.output) {
            for (const item of response.output) {
              if (item.type === 'message' && item.content) {
                for (const c of item.content) {
                  if (c.type === 'output_text' && c.text) {
                    content = c.text;
                  }
                }
              }
            }
          }
          
          // Extract citations from annotations
          let xCitations = [];
          if (response.output) {
            for (const item of response.output) {
              if (item.content) {
                for (const c of item.content) {
                  if (c.annotations) {
                    for (const ann of c.annotations) {
                      if (ann.type === 'url_citation' && ann.url) {
                        if (ann.url.includes('x.com') || ann.url.includes('twitter.com')) {
                          xCitations.push(ann);
                        }
                      }
                    }
                  }
                }
              }
            }
          }
          // Dedupe by URL
          xCitations = [...new Map(xCitations.map(c => [c.url, c])).values()];
          
          if (options.requireCitations && xCitations.length === 0) {
            console.error('⚠️  No X/Twitter citations found in response.');
            console.error('   Grok may not have found relevant posts, or X search may not be enabled.');
            console.error('\nResponse anyway:\n');
          }
          
          console.log(content);
          
          if (xCitations.length > 0) {
            console.log('\n📎 Citations:');
            for (const cite of xCitations) {
              console.log(`   ${cite.url}`);
            }
          }
          
          resolve(response);
        } catch (e) {
          console.error('❌ Failed to parse response:', e.message);
          console.error('Raw:', data.slice(0, 500));
          process.exit(1);
        }
      });
    });
    
    req.on('error', (e) => {
      console.error('❌ Request failed:', e.message);
      process.exit(1);
    });
    
    req.write(JSON.stringify(payload));
    req.end();
  });
}

// Main
const args = process.argv.slice(2);

if (args.length === 0 || args.includes('--help')) {
  console.log(`
🔍 xAI X Search (Responses API)

Usage:
  node search-x.js [options] "Your search query"

Options:
  --model, -m <model>     Model (default: grok-4-1-fast)
  --days, -d <n>          Search last N days (default: 30)
  --handles <list>        Only search these handles (comma-separated)
  --exclude <list>        Exclude these handles (comma-separated)
  --json, -j              Output full JSON response
  --no-require-citations  Don't warn if no X citations found
  --help                  Show this help

Examples:
  node search-x.js "Remotion video framework tips"
  node search-x.js --days 7 "Claude AI"
  node search-x.js --handles @remotion_dev "new features"
  node search-x.js --days 30 "AI video creation best practices"
`);
  process.exit(0);
}

const options = parseArgs(args);

if (!options.query) {
  console.error('❌ Please provide a search query');
  process.exit(1);
}

console.error(`🔍 Searching X for: "${options.query}" (last ${options.days} days)...\n`);
searchX(options);


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