Skill: xinhai-ai/exa-search (version 1.0.0)

--- name: exa-search description: Use Exa (exa.ai) Search API to search the web and return structured results (title/url/snippet/text) via a local Node script. Trigger when the user asks to enable Exa search, configure Exa API key, or perform web search using Exa. metadata: {"openclaw":{"emoji":"🔎","requires":{"bins":["node"],"env":["EXA_API_KEY"]},"primaryEnv":"EXA_API_KEY","homepage":"https://exa.ai/docs"}} --- # Exa Search Use Exa’s Search API via the bundled script. ## Requirements - Set `EXA_API_KEY` in the Gateway environment (recommended) or in `~/.openclaw/.env`. ## Commands - Run a search: - `node {baseDir}/scripts/exa_search.mjs "<query>" --count 5` - Include page text in results (costs more): - `node {baseDir}/scripts/exa_search.mjs "<query>" --count 5 --text` - Narrow by time window: - `--start 2025-01-01 --end 2026-02-04` ## Notes - This skill does not modify `web_search`; it provides an Exa-backed alternative you can invoke when you specifically want Exa.

path: scripts/exa_search.mjs
content: #!/usr/bin/env node
/* eslint-disable no-console */

import process from "node:process";

function usage(exitCode = 0) {
  console.error(
    [
      "Usage:",
      '  exa_search.mjs "<query>" [--count N] [--text] [--highlights] [--type auto|neural|keyword] [--start YYYY-MM-DD] [--end YYYY-MM-DD]',
      "",
      "Env:",
      "  EXA_API_KEY   (required)",
    ].join("\n"),
  );
  process.exit(exitCode);
}

function parseArgs(argv) {
  const args = { query: null, count: 5, text: false, highlights: false, type: "auto", start: null, end: null };

  const positionals = [];
  for (let i = 0; i < argv.length; i++) {
    const a = argv[i];
    if (a === "--help" || a === "-h") usage(0);
    if (!a.startsWith("--")) {
      positionals.push(a);
      continue;
    }
    const key = a.slice(2);
    if (key === "text") args.text = true;
    else if (key === "highlights") args.highlights = true;
    else if (key === "count") {
      const v = argv[++i];
      if (!v) usage(2);
      args.count = Number(v);
    } else if (key === "type") {
      const v = argv[++i];
      if (!v) usage(2);
      args.type = v;
    } else if (key === "start") {
      const v = argv[++i];
      if (!v) usage(2);
      args.start = v;
    } else if (key === "end") {
      const v = argv[++i];
      if (!v) usage(2);
      args.end = v;
    } else {
      console.error(`Unknown flag: ${a}`);
      usage(2);
    }
  }

  if (positionals.length === 0) usage(2);
  // Special-case help so we don't require EXA_API_KEY just to print usage.
  if (positionals.length === 1 && (positionals[0] === "--help" || positionals[0] === "-h")) usage(0);
  args.query = positionals.join(" ");
  if (!Number.isFinite(args.count) || args.count < 1 || args.count > 25) {
    console.error("--count must be 1..25");
    process.exit(2);
  }
  return args;
}

function toIsoDateOrNull(dateStr) {
  if (!dateStr) return null;
  // Accept YYYY-MM-DD; interpret as date-only.
  if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
    console.error(`Invalid date: ${dateStr} (expected YYYY-MM-DD)`);
    process.exit(2);
  }
  return `${dateStr}T00:00:00.000Z`;
}

async function main() {
  const args = parseArgs(process.argv.slice(2));

  const [REDACTED_SECRET];
  if (!apiKey) {
    console.error("Missing EXA_API_KEY in environment.");
    usage(2);
  }

  const body = {
    query: args.query,
    type: args.type,
    numResults: args.count,
    contents: {
      text: Boolean(args.text),
      highlights: Boolean(args.highlights),
    },
  };

  const startDate = toIsoDateOrNull(args.start);
  const endDate = toIsoDateOrNull(args.end);
  if (startDate || endDate) {
    body.startPublishedDate = startDate ?? undefined;
    body.endPublishedDate = endDate ?? undefined;
  }

  const res = await fetch("https://api.exa.ai/search", {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-api-key": apiKey,
      accept: "application/json",
    },
    body: JSON.stringify(body),
  });

  const text = await res.text();
  if (!res.ok) {
    console.error(`Exa API error (${res.status}): ${text}`);
    process.exit(1);
  }

  // Return raw JSON so downstream tooling can parse.
  process.stdout.write(text);
}

main().catch((err) => {
  console.error(err?.stack || String(err));
  process.exit(1);
});


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