npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

raysurfer

v1.4.4

Published

AI maintained skills for vertical agents

Readme

RaySurfer TypeScript SDK

Website · Docs · Dashboard

AI Maintained Skills for Vertical Agents. Re-use verified code from prior runs rather than serial tool calls or generating code per execution.

Installation

npm install raysurfer

Setup

Set your API key:

export RAYSURFER_API_KEY=your_api_key_here

Get your key from the dashboard.

Low-Level API

For custom integrations, use the RaySurfer client directly with any LLM provider.

Complete Example

import { RaySurfer } from "raysurfer";

const client = new RaySurfer({ apiKey: "your_api_key" });
const task = "Fetch GitHub trending repos";

// 1. Search for cached code matching a task
const result = await client.search({
  task,
  topK: 5,
  minVerdictScore: 0.3,
});

for (const match of result.matches) {
  console.log(`${match.codeBlock.name}: ${match.combinedScore}`);
  console.log(`  Source: ${match.codeBlock.source.slice(0, 80)}...`);
}

// 2. Upload a new code file after execution
await client.upload({
  task,
  fileWritten: {
    path: "fetch_repos.ts",
    content: "function fetch() { ... }",
  },
  succeeded: true,
  executionLogs: "Fetched 10 trending repos successfully",
  dependencies: { "node-fetch": "3.3.0", zod: "3.22.0" },
});

// 2b. Bulk upload prompts/logs/code for sandboxed grading
await client.uploadBulkCodeSnips(
  ["Build a CLI tool", "Add CSV support"],
  [{ path: "cli.ts", content: "function main() { ... }" }],
  [
    {
      path: "logs/run.log",
      content: "Task completed",
      encoding: "utf-8",
    },
  ]
);

// 3. Vote on whether a cached snippet was useful
await client.voteCodeSnip({
  task,
  codeBlockId: result.matches[0].codeBlock.id,
  codeBlockName: result.matches[0].codeBlock.name,
  codeBlockDescription: result.matches[0].codeBlock.description,
  succeeded: true,
});

Client Options

const client = new RaySurfer({
  apiKey: "your_api_key",
  baseUrl: "https://api.raysurfer.com", // optional
  timeout: 30000, // optional, in ms
  organizationId: "org_xxx", // optional, for team namespacing
  workspaceId: "ws_xxx", // optional, for enterprise namespacing
  snipsDesired: "company", // optional, snippet scope
  publicSnips: true, // optional, include community snippets
});

Response Fields

The search() response includes:

| Field | Type | Description | | ---------------- | --------------- | --------------------------------- | | matches | SearchMatch[] | Matching code blocks with scoring | | totalFound | number | Total matches found | | cacheHit | boolean | Whether results were from cache |

Each SearchMatch contains codeBlock (with id, name, source, description, entrypoint, language, dependencies), combinedScore, vectorScore, verdictScore, thumbsUp, thumbsDown, filename, and entrypoint.

Store a Code Block with Full Metadata

const result = await client.storeCodeBlock({
  name: "GitHub User Fetcher",
  source: "function fetchUser(username) { ... }",
  entrypoint: "fetchUser",
  language: "typescript",
  description: "Fetches user data from GitHub API",
  tags: ["github", "api", "user"],
  dependencies: { "node-fetch": "3.3.0" },
});

Retrieve Few-Shot Examples

const examples = await client.getFewShotExamples(
  "Parse CSV files",
  3
);

for (const ex of examples) {
  console.log(`Task: ${ex.task}`);
  console.log(`Code: ${ex.codeSnippet}`);
}

Retrieve Task Patterns

const patterns = await client.getTaskPatterns({
  task: "API integration",
  minThumbsUp: 5,
  topK: 20,
});

for (const p of patterns) {
  console.log(`${p.taskPattern} -> ${p.codeBlockName}`);
}

User-Provided Votes

Instead of relying on AI voting, provide your own votes:

// Single upload with your own vote (AI voting is skipped)
await client.upload({
  task: "Fetch GitHub trending repos",
  fileWritten: file,
  succeeded: true,
  userVote: 1, // 1 = thumbs up, -1 = thumbs down
});

// Bulk upload with per-file votes (AI grading is skipped)
await client.uploadBulkCodeSnips(
  ["Build a CLI tool", "Add CSV support"],
  files,
  logs,
  true, // useRaysurferAiVoting (ignored when userVotes set)
  { "app.ts": 1, "utils.ts": -1 } // userVotes
);

Method Reference

| Method | Description | |--------|-------------| | search({ task, topK?, minVerdictScore?, preferComplete?, inputSchema? }) | Search for cached code snippets | | getCodeSnips({ task, topK?, minVerdictScore? }) | Retrieve cached code snippets by semantic search | | retrieveBest({ task, topK?, minVerdictScore? }) | Retrieve the single best match | | getFewShotExamples(task, k) | Retrieve few-shot examples for code generation prompting | | getTaskPatterns({ task, minThumbsUp?, topK? }) | Retrieve proven task-to-code mappings | | storeCodeBlock({ name, source, entrypoint, language, description, tags?, dependencies?, ... }) | Store a code block with full metadata | | upload({ task, fileWritten, succeeded, useRaysurferAiVoting?, userVote?, executionLogs?, dependencies? }) | Store a single code file with optional dependency versions | | uploadBulkCodeSnips(prompts, filesWritten, logFiles?, useRaysurferAiVoting?, userVotes?) | Bulk upload for grading (AI votes by default, or provide per-file votes) | | delete(snippetId) | Delete a snippet by ID or name | | voteCodeSnip({ task, codeBlockId, codeBlockName, codeBlockDescription, succeeded }) | Vote on snippet usefulness |

Exceptions

Both clients include built-in retry logic with exponential backoff for transient failures (429, 5xx, network errors).

| Exception | Description | | ----------------------- | ---------------------------------------------------- | | RaySurferError | Base exception for all Raysurfer errors | | APIError | API returned an error response (includes statusCode) | | AuthenticationError | API key is invalid or missing | | CacheUnavailableError | Cache backend is unreachable | | RateLimitError | Rate limit exceeded after retries (includes retryAfter) | | ValidationError | Request validation failed (includes field) |

import { RaySurfer, RateLimitError } from "raysurfer";

const client = new RaySurfer({ apiKey: "your_api_key" });

try {
  const result = await client.getCodeSnips({
    task: "Fetch GitHub repos",
  });
} catch (e) {
  if (e instanceof RateLimitError) {
    console.log(`Rate limited after retries: ${e.message}`);
    if (e.retryAfter) {
      console.log(`Try again in ${e.retryAfter}ms`);
    }
  }
}

Claude Agent SDK Drop-in

Swap your import — everything else stays the same:

// Before
import { query } from "@anthropic-ai/claude-agent-sdk";

// After
import { query } from "raysurfer";

for await (const message of query({
  prompt: "Fetch data from GitHub API",
  options: {
    model: "claude-opus-4-6",
    systemPrompt: "You are a helpful assistant.",
  },
})) {
  console.log(message);
}

All Claude SDK types are re-exported from raysurfer, so you don't need a separate import:

import {
  query,
  type Options,
  type SDKMessage,
  type Query,
} from "raysurfer";

Class-based API

import { ClaudeSDKClient } from "raysurfer";

const client = new ClaudeSDKClient({
  model: "claude-opus-4-6",
  systemPrompt: "You are a helpful assistant.",
});

for await (const msg of client.query("Fetch data from GitHub API")) {
  console.log(msg);
}

System Prompt Preset

Use the Claude Code preset system prompt with appended instructions:

for await (const message of query({
  prompt: "Refactor the auth module",
  options: {
    systemPrompt: {
      type: "preset",
      preset: "claude_code",
      append: "Always explain your reasoning.",
    },
  },
})) {
  console.log(message);
}

Query Control Methods

The query() function returns a Query object with full control methods:

const q = query({ prompt: "Build a REST API" });

await q.interrupt();
await q.setPermissionMode("acceptEdits");
await q.setModel("claude-sonnet-4-5-20250929");
await q.setMaxThinkingTokens(4096);
const models = await q.supportedModels();
const info = await q.accountInfo();
q.close();

Without Caching

If RAYSURFER_API_KEY is not set, behaves exactly like the original SDK — no caching, just a pass-through wrapper.

Snippet Retrieval Scope

Control which cached snippets are retrieved:

import { ClaudeSDKClient } from "raysurfer";

// Include company-level snippets (Team/Enterprise)
const client = new ClaudeSDKClient({
  snipsDesired: "company",
});

// Enterprise: client-specific snippets only
const enterpriseClient = new ClaudeSDKClient({
  snipsDesired: "client",
});

| Configuration | Required Tier | |------------------------------|---------------------| | Default (public only) | FREE | | snipsDesired: "company" | TEAM or ENTERPRISE | | snipsDesired: "client" | ENTERPRISE only |

Public Snippets

Include community public snippets (crawled from GitHub) in retrieval results alongside your private snippets:

// High-level
const client = new ClaudeSDKClient({ publicSnips: true });

// Low-level
const rs = new RaySurfer({ apiKey: "...", publicSnips: true });

Agent-Accessible Functions

Mark functions as callable by agents with agentAccessible(). Name and parameters are auto-inferred from the function when not provided:

import { agentAccessible, toAnthropicTool } from "raysurfer";

function fetchWeather(city: string, units: string) {
  return { temp: 72, city, units };
}

// Auto-inferred: name="fetchWeather", parameters={city, units}
const tool = agentAccessible(fetchWeather);

// Or provide explicit metadata
const toolExplicit = agentAccessible(fetchWeather, {
  name: "get_weather",
  description: "Fetch current weather for a city",
  inputSchema: { city: "string", units: "string" },
});

Convert to Anthropic Tool

Use toAnthropicTool() to get an Anthropic-compatible tool definition:

const anthropicTool = toAnthropicTool(toolExplicit);
// { name: "get_weather",
//   description: "Fetch current weather for a city",
//   input_schema: { city: "string", units: "string" } }

Programmatic Tool Calling

Register local tools, then either:

  1. pass in userCode (primary mode), or
  2. generate code inside the sandbox with your own provider key + prompt (optional mode).
import { RaySurfer } from "raysurfer";

const rs = new RaySurfer({ apiKey: "your_api_key" });

rs.tool("add", "Add two numbers together", { a: "integer", b: "integer" },
  async (args) => String(Number(args.a) + Number(args.b))
);

rs.tool("multiply", "Multiply two numbers together", { a: "integer", b: "integer" },
  async (args) => String(Number(args.a) * Number(args.b))
);

const userCode = `
intermediate = add(5, 3)
final = multiply(intermediate, 2)
print(final)
`;
const result = await rs.execute(
  "Add 5 and 3, then multiply the result by 2",
  { userCode }
);
console.log(result.result);     // "16"
console.log(result.toolCalls);  // [{toolName: 'add', ...}, ...]
console.log(result.cacheHit);   // false (reserved field)

How It Works

  1. SDK connects a WebSocket to the server for tool call routing
  2. Your app sends either userCode (primary mode) or codegen inputs (optional mode) to /api/execute/run
  3. Code runs in a sandboxed environment — tool calls are routed back to your local functions via WebSocket
  4. Results are returned with full tool call history

Execute Options

const result = await rs.execute("Your task description", {
  userCode: "print(add(1, 2))", // Primary mode
  timeout: 300000,              // Max time in ms (default 300000)
});

// Optional mode: generate code in sandbox using your own key + prompt
const sandboxCodegenResult = await rs.execute("Your task description", {
  codegen: {
    provider: "anthropic",
    apiKey: "your_anthropic_key",
    model: "claude-opus-4-6",
    prompt: "Write Python code that uses add(a, b) and prints the result for 2 + 3.",
  },
  timeout: 300000,
});

ExecuteResult Fields

| Field | Type | Description | | ------------- | ------------------ | -------------------------------- | | executionId | string | Unique execution identifier | | result | string \| null | Stdout output from the script | | exitCode | number | Process exit code (0 = success) | | durationMs | number | Total execution time | | cacheHit | boolean | Reserved field (always false) | | error | string \| null | Error message if exitCode != 0 | | toolCalls | ToolCallRecord[] | All tool calls made |

License

MIT