Skill: xujfcn/crazyrouter-ocr (version 1.1.0)

--- name: crazyrouter-ocr description: Image-to-text and OCR via Crazyrouter API using vision models (GPT-4o, Gemini, Claude). Extract text from images, describe images, analyze screenshots. Use when user asks to read text from image, describe what's in an image, or analyze a screenshot. Requires environment variable CRAZYROUTER_API_KEY (get at https://crazyrouter.com). --- # Image Analysis & OCR via Crazyrouter Analyze images, extract text, and describe visual content using vision models through [Crazyrouter](https://crazyrouter.com). ## Script Directory **Agent Execution**: 1. `SKILL_DIR` = this SKILL.md file's directory 2. Script path = `${SKILL_DIR}/scripts/main.mjs` ## Step 0: Check API Key ⛔ BLOCKING ```bash [ -n "${CRAZYROUTER_API_KEY}" ] && echo "key_present" || echo "not_set" ``` | Result | Action | |--------|--------| | `key_present` | Continue | | `not_set` | Ask user to set `CRAZYROUTER_API_KEY`. Get key at https://crazyrouter.com | ## Usage ```bash # Describe image node ${SKILL_DIR}/scripts/main.mjs --image photo.jpg --prompt "Describe this image" # Extract text (OCR) node ${SKILL_DIR}/scripts/main.mjs --image receipt.png --prompt "Extract all text from this image" # Analyze screenshot node ${SKILL_DIR}/scripts/main.mjs --image screenshot.png --prompt "What errors are shown in this screenshot?" # Use specific model node ${SKILL_DIR}/scripts/main.mjs --image chart.png --prompt "Analyze this chart" --model gemini-2.5-flash # Save output node ${SKILL_DIR}/scripts/main.mjs --image document.jpg --prompt "Extract text" --output text.md ``` ### Options | Option | Description | Default | |--------|-------------|---------| | `--image <path>` | Image file (required) | — | | `--prompt <text>` | What to do with the image | `Describe this image in detail` | | `--model <id>` | Vision model | `gpt-4o` | | `--output <file>` | Save to file | stdout |

path: scripts/main.mjs
content: import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import process from "node:process";

const DEFAULT_MODEL = "gpt-4o";
const DEFAULT_PROMPT = "Describe this image in detail";
const API_BASE = process.env.CRAZYROUTER_BASE_URL || "https://crazyrouter.com/v1";

function parseArgs(argv) {
  const args = { image: null, prompt: DEFAULT_PROMPT, model: DEFAULT_MODEL, output: null };
  for (let i = 2; i < argv.length; i++) {
    switch (argv[i]) {
      case "--image": case "-i": args.image = argv[++i]; break;
      case "--prompt": case "-p": args.prompt = argv[++i]; break;
      case "--model": case "-m": args.model = argv[++i]; break;
      case "--output": case "-o": args.output = argv[++i]; break;
      case "--help": case "-h":
        console.log(`Usage: main.mjs --image photo.jpg [--prompt "Describe this"] [--model gpt-4o] [--output text.md]
Models: gpt-4o, gpt-4o-mini, gemini-2.5-flash, gemini-3-pro, claude-sonnet-4
Env: CRAZYROUTER_API_KEY (required)`);
        process.exit(0);
    }
  }
  return args;
}

function getApiKey() {
  const key = process.env.CRAZYROUTER_API_KEY;
  if (!key) { console.error("Error: CRAZYROUTER_API_KEY not set. Get your key at https://crazyrouter.com"); process.exit(1); }
  return key;
}

function getMimeType(filePath) {
  const ext = path.extname(filePath).toLowerCase();
  const types = { ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".gif": "image/gif", ".webp": "image/webp", ".bmp": "image/bmp" };
  return types[ext] || "image/png";
}

async function analyze(args) {
  if (!args.image) { console.error("Error: --image required"); process.exit(1); }
  const [REDACTED_SECRET]);
  const imageBuffer = await readFile(args.image);
  const mimeType = getMimeType(args.image);
  const base64 = imageBuffer.toString("base64");
  console.error(`Image: ${path.basename(args.image)} (${(imageBuffer.length / 1024).toFixed(1)}KB)`);
  console.error(`Model: ${args.model}`);
  const response = await fetch(`${API_BASE}/chat/completions`, {
    method: "POST",
    headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
    body: JSON.stringify({
      model: args.model,
      messages: [{ role: "user", content: [
        { type: "text", text: args.prompt },
        { type: "image_url", image_url: { url: `data:${mimeType};base64,${base64}` } },
      ]}],
      max_tokens: 4096,
    }),
  });
  if (!response.ok) { console.error(`API Error (${response.status}): ${await response.text()}`); process.exit(1); }
  const result = await response.json();
  const text = result.choices?.[0]?.message?.content ?? "";
  if (args.output) {
    await writeFile(args.output, text);
    console.error(`Saved to ${args.output}`);
  } else {
    console.log(text);
  }
  if (result.usage) console.error(`${result.usage.total_tokens} tokens`);
}

const args = parseArgs(process.argv);
analyze(args).catch((err) => { console.error(`Error: ${err.message}`); process.exit(1); });


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