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

@rayrun/sdk

v0.13.1

Published

Type-safe client for the Rayrun public API

Readme

@rayrun/sdk

npm install @rayrun/sdk
import { Rayrun } from '@rayrun/sdk';

const rayrun = new Rayrun({ apiKey: process.env.RAYRUN_API_KEY });
const identity = await rayrun.identity.get();
const { items: connections } = await rayrun.connections.list();
const { items: matchingTools } = await rayrun.tools.list({ query: 'create issue' });

Create keys in Dashboard → Settings → API keys. The plaintext is shown once.

identity.get() returns the active credential name and UID, authentication method, user, workspace, and granted scopes. It requires authentication but no resource-specific read scope.

The client covers catalog search, connection and credential setup, source deployments, OAuth links, indexing, tool and client policies, access profiles, Workspace Skills, Activity, review queues, and webhooks. Safe reads retry transient failures; writes do not retry automatically.

Deploy source projects

Use the CLI to read a directory safely, or send the same base64 file representation through the SDK. The example reads a reviewed CI artifact containing the complete project as an array of { path, contentBase64 } objects. Validation is read-only; update stores an encrypted immutable revision and queues an isolated build. Commit one stable projectId UUID in rayrun.json so every checkout and retry converges on the same deployment.

import { readFile } from 'node:fs/promises';
import { setTimeout as delay } from 'node:timers/promises';
import { Rayrun } from '@rayrun/sdk';

const files = JSON.parse(await readFile('./source-files.json', 'utf8'));
const apiKey = process.env.RAYRUN_API_KEY;
const manifest = JSON.parse(await readFile('./rayrun.json', 'utf8'));
const sourceProjectId = manifest.projectId;
const workspaceSecretUid = process.env.RAYRUN_GITHUB_SECRET_UID;
if (!apiKey || !sourceProjectId || !workspaceSecretUid) {
  throw new Error('Deployment credentials and source project ID are required.');
}
if (!/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/iu.test(sourceProjectId)) {
  throw new Error('rayrun.json projectId must be a UUID.');
}
const rayrun = new Rayrun({ apiKey });

const validation = await rayrun.deployments.validate(files);
const queued = await rayrun.deployments.deploySourceProject(sourceProjectId, {
  displayName: validation.name,
  files,
  secretBindings: { GITHUB_TOKEN: workspaceSecretUid },
});

let deployment = await rayrun.deployments.get(queued.buildUid);
const deploymentTimeoutMs = Number(process.env.RAYRUN_DEPLOY_TIMEOUT_MS ?? 60 * 60_000);
if (!Number.isSafeInteger(deploymentTimeoutMs) || deploymentTimeoutMs <= 0) {
  throw new Error('Invalid deployment timeout.');
}
const deploymentDeadline = Date.now() + deploymentTimeoutMs;
while (
  ['queued', 'running'].includes(deployment.build.status) ||
  (deployment.build.status === 'succeeded' &&
    (!deployment.release || ['pending', 'verifying'].includes(deployment.release.status)))
) {
  if (Date.now() >= deploymentDeadline) throw new Error('Deployment timed out.');
  await delay(2_000);
  deployment = await rayrun.deployments.get(queued.buildUid);
}

if (deployment.build.status !== 'succeeded' || deployment.release?.status !== 'active') {
  throw new Error(
    deployment.release?.errorMessage ?? deployment.build.errorMessage ?? 'Deployment failed.',
  );
}

const log = await rayrun.deployments.getLog(queued.buildUid);
const releases = await rayrun.deployments.listReleases(queued.connectionId);
const rollbackTarget = releases.items.find(({ status }) => status === 'superseded');
if (!rollbackTarget) throw new Error('No inactive release is available to roll back.');
const rollback = await rayrun.deployments.rollback(queued.connectionId, rollbackTarget.uid);

let rollbackRelease = await rayrun.deployments.getRelease(queued.connectionId, rollback.releaseUid);
const rollbackDeadline = Date.now() + deploymentTimeoutMs;
while (['pending', 'verifying'].includes(rollbackRelease.status)) {
  if (Date.now() >= rollbackDeadline) throw new Error('Rollback timed out.');
  await delay(2_000);
  rollbackRelease = await rayrun.deployments.getRelease(queued.connectionId, rollback.releaseUid);
}
if (rollbackRelease.status !== 'active') {
  throw new Error(rollbackRelease.errorMessage ?? `Rollback ${rollbackRelease.status}.`);
}

Recover a clean checkout without deploying by resolving the committed project ID. Adopt only an existing legacy upload that has no project identity; project and connection associations cannot be reassigned.

const project = await rayrun.deployments.getSourceProject(sourceProjectId);
console.log(project.connection.uid, project.source.revision, project.activeBuild?.uid);

await rayrun.deployments.adoptSourceProject(sourceProjectId, legacyConnectionId);

Use the Dashboard's Deploy services API-key preset. It grants deployments:read for status and release history, deployments:logs:read for encrypted build logs, and deployments:write for validation, upload, and rollback. Secret bindings contain workspace-secret UIDs, never plaintext values.

Manage workspace-secret values without returning them from the API:

const created = await rayrun.secrets.create({
  name: 'GITHUB_TOKEN',
  value: process.env.GITHUB_TOKEN,
});
const { items: secrets } = await rayrun.secrets.list();
await rayrun.secrets.rotate(created.secret.uid, {
  redeploy: true,
  value: process.env.NEW_GITHUB_TOKEN,
});
await rayrun.secrets.archive(created.secret.uid);

Use secrets:read to list safe metadata and secrets:write to create, rotate, or archive secrets. Rotation with redeploy: true also requires deployments:write because it restarts bound services.

Connect a GitHub repository once to deploy the selected branch now and on every future push:

const setup = await rayrun.githubRepositories.create({
  owner: 'rayrun',
  repository: 'hacker-news-mcp',
  previewsEnabled: true,
});

console.log(setup.installationUrl);
const repository = await rayrun.githubRepositories.get(setup.uid);

await rayrun.githubRepositories.disconnect(setup.uid);

The installation URL grants the Rayrun GitHub App access to the repository. Same-repository pull requests get isolated preview services; fork pull requests are ignored because previews may receive workspace secrets.

Embed Rayrun in an agent product

Map one application user to an external principal, then mint a short-lived, row-scoped MCP session:

const { uid } = await rayrun.externalPrincipals.upsert({
  externalId: 'customer_123',
  displayName: 'Ada',
  metadata: { plan: 'pro' },
});
const session = await rayrun.externalPrincipals.createSession(uid, {
  runtimeContext: { accountId: 'customer_123', plan: 'pro' },
});

console.log(session.endpoint, session.sessionToken);

Sessions without toolAccessProfileUid are read-only. Pass a profile to define the explicit session ceiling when the embedded user needs approved mutating tools.

Use createConnectLink when that user must authorize an upstream OAuth account. Correlate its returned uid with connectLinkUid in the signed external.authorization.completed or external.authorization.failed webhook. Failure codes are provider_denied, incomplete_response, authorization_failed, and expired. List and revoke sessions explicitly, and delete the principal when the application user is removed. Use an API key with principals:read, principals:write, sessions:read, and sessions:write. The Python rayrun-sdk distribution exposes the same lifecycle through Rayrun and AsyncRayrun.

Inspect the effective policy applied to one connected client without reproducing policy logic in your application:

const { items: effectivePolicy } = await rayrun.clients.listTools(clientUid, {
  query: 'create issue',
});

Each result includes the effective decision and the workspace, access-profile, or client layer that restricted it.

Reuse one access ceiling across clients

const { profile } = await rayrun.accessProfiles.create({
  name: 'Support agents',
  description: 'Read by default; sensitive tools stay blocked.',
});

const policy = await rayrun.accessProfiles.setPolicy(
  profile.uid,
  'read-only',
  profile.toolPolicyVersion,
);
const details = await rayrun.accessProfiles.update(profile.uid, {
  description: 'Read-only support access; sensitive tools stay blocked.',
  expectedVersion: policy.toolPolicyVersion,
  name: 'Support agents',
});
await rayrun.clients.setAccessProfile(clientUid, profile.uid, clientToolPolicyVersion);

const profileHistory = await rayrun.accessProfiles.listConfigurationVersions(profile.uid);
const original = await rayrun.accessProfiles.getConfigurationVersion(
  profile.uid,
  profileHistory.items.at(-1).uid,
);
await rayrun.accessProfiles.restoreConfigurationVersion(profile.uid, original.version.uid, {
  expectedVersion: details.toolPolicyVersion,
  reason: 'Restore the reviewed access ceiling',
  riskConfirmed: true,
});

The effective policy is always the intersection of the workspace, profile, and client rules. A profile can narrow access but cannot grant something blocked by the workspace or client. History versions profile details and rules together; assignments and archival stay outside the snapshot. Restore refuses missing tools and requires riskConfirmed when it would allow a Destructive or Unknown tool. Profile history is limited to 64 MiB per workspace.

Manage connection configuration history

Connection names, slugs, descriptions, timeouts, enablement, and payload-capture preferences are saved as one immutable configuration snapshot. Credentials, headers, OAuth state, identity and tool policy, health, and index state are deliberately excluded.

const { items: connections } = await rayrun.connections.list();
const connection = connections[0];

const updated = await rayrun.connections.setEnabled(connection.uid, {
  enabled: false,
  expectedVersion: connection.configurationVersion,
});

const history = await rayrun.connections.listConfigurationVersions(connection.uid, { limit: 25 });
const selected = await rayrun.connections.getConfigurationVersion(
  connection.uid,
  history.items.at(-1).uid,
);

await rayrun.connections.restoreConfigurationVersion(connection.uid, selected.version.uid, {
  expectedVersion: updated.connection.configurationVersion,
  reason: 'Restore the reviewed service configuration',
});

Every material update records the user or API key, channel, time, and optional reason. Identical saves are no-ops. Restore creates a new attributed version and fails with version_conflict if the connection changed after it was read.

Manage hosted tool hooks

Hooks are versioned TypeScript adapters hosted and sandboxed by Rayrun. Pull the generated types and draft, test it without a live upstream call, then create an immutable shadow or active revision.

const { hook } = await rayrun.hooks.get(connectionId, toolUid);

const tested = await rayrun.hooks.test(connectionId, toolUid, {
  source: hook.draftSource,
  config: hook.draftConfig,
  arguments: { query: 'release' },
  mockResult: { items: [] },
});

const saved = await rayrun.hooks.saveDraft(connectionId, toolUid, {
  source: hook.draftSource,
  config: hook.draftConfig,
  expectedVersion: hook.version,
  reason: 'Normalize upstream release results',
});

const deployed = await rayrun.hooks.deploy(connectionId, toolUid, {
  expectedVersion: saved.hook.version,
  mode: 'shadow',
});

const runs = await rayrun.hooks.listRuns(connectionId, toolUid, { limit: 25 });

const history = await rayrun.hooks.listDraftVersions(connectionId, toolUid, { limit: 25 });
const prior = await rayrun.hooks.getDraftVersion(connectionId, toolUid, history.items.at(-1).uid);
await rayrun.hooks.restoreDraft(connectionId, toolUid, prior.version.uid, {
  expectedVersion: deployed.hook.version,
  reason: 'Restore the previous argument mapping',
});

setDeployment moves an active or shadow pointer to an existing compatible revision, or deactivates it with revisionUid: null. Every mutation uses expectedVersion; fetch and reconcile instead of blindly retrying a version_conflict. Use a full-control API key for hook writes. Read-only keys can inspect source, generated declarations, revision history, and deployed-run metadata; captured logs and errors require hooks:write. RayrunApiError.diagnostics carries compiler line and column details for editor and CI output.

Draft history is immutable and separate from deployment revisions: every material save records the API key or user, optional change note, source, and non-secret config. Re-saving identical content is a no-op. Restore always creates a new attributed draft head; it never rewrites the selected version. Source plus config history is limited to 64 MiB per workspace. Export required versions, then call hooks.reset with the current hook UID and version to permanently remove that hook's draft history and deployment revisions. Rayrun blocks connection deletion while hook history remains.

Publish Workspace Skills

Encode each file as a package-relative path and base64 content. Validate before creating when CI needs the parsed name, frontmatter, digests, and warnings before it mutates workspace state.

const files = [
  {
    path: 'SKILL.md',
    contentBase64: Buffer.from(
      `---
name: incident-response
description: Investigate and communicate production incidents.
---

# Incident response
`,
    ).toString('base64'),
  },
];

const validation = await rayrun.skills.validate(files);
const created = await rayrun.skills.create({ files, source: 'api' });

await rayrun.skills.publish(created.skillUid, {
  deliveryMode: 'both',
  expectedVersion: created.configurationVersion,
  profileUids: [],
  riskConfirmed: validation.riskFlags.length > 0,
});

saveDraft creates an immutable revision without changing the published pointer. updateAudience changes Code/Direct delivery and access-profile targeting without republishing. setEnabled, archive, export, exportVersion, and restoreVersion cover the reversible lifecycle; delete permanently removes retained revisions and delivery summaries after name and version confirmation. The name remains reserved.

Use skills:read for inspection and export and add skills:write deliberately for publication automation. The ordinary workspace-management key preset excludes skills:write. Rayrun stores Skill bytes encrypted under the workspace key and serves them only after reauthorizing the MCP client on each read. allowed-tools is guidance, not access policy.

Publish hosted services to your customers

A publication offers your hosted services to your own customers. They connect their AI clients to the publication's address and sign in with your OpenID Connect provider, without joining your workspace.

const { publication } = await rayrun.publications.create({
  connectionUids: [connectionId],
  displayName: 'Acme issues',
  slug: 'acme-issues',
});

await rayrun.publications.setSignInProvider(publication.uid, {
  clientId: 'rayrun-acme-issues',
  clientSecret: process.env.ACME_OIDC_CLIENT_SECRET,
  expectedVersion: publication.configurationVersion,
  issuer: 'https://login.example.com',
  scopes: ['openid', 'email', 'profile'],
  tokenEndpointAuthMethod: 'client_secret_basic',
});

await rayrun.publications.publish(publication.uid, { reason: 'Launch' });

Your customers add publication.mcpUrl to their AI clients. Allow publication.callbackUrl as a redirect URI at your provider before anybody signs in, and a custom domain's callbackUrl as well once you add one.

update, setSignInProvider, removeSignInProvider, and restoreConfigurationVersion save a new configuration version and take the expectedVersion they were read at. The client secret is write-only: omit it to keep the stored one, which works only while the issuer and client ID stay the same. listEndUsers and removeEndUser manage the people who have signed in, listApprovals shows the calls they are being asked to approve, and setDomain, checkDomain, and removeDomain serve the publication at a hostname of your own. A domain's blockedBy names what makes every request through it answer 404 whatever its status: an unpublished publication, a missing sign-in provider, or a suspended workspace.

Use publications:read to inspect publications and add publications:write to change them; the Manage publications key preset holds exactly those two. Subscribe a webhook to publication.changed to follow every change, including a custom domain that starts or stops serving, and to publication.approval.requested to show an end user the approval link their call is waiting on.

Speculative programmatic tool calling (streamed run-ahead)

Speculative programmatic tool calling (sPTC) lets Rayrun start eligible read calls while the model is still writing execute_code. If the finished program makes the same call, its result may already be waiting. Alex Zhang introduced sPTC; Rayrun adapts the design from the MIT-licensed spec-ptc project for an MCP gateway. Rayrun Chat uses it automatically. Actual latency savings depend on how long model generation and eligible reads overlap.

The helper below receives three values from your application: the MCP OAuth access token, your MCP client, and an async iterable containing only execute_code argument deltas. The OpenAI Responses API emits response.function_call_arguments.delta events; append event.delta. The Anthropic Messages API emits content_block_delta events whose event.delta.type is input_json_delta; append event.delta.partial_json.

import { RayrunGateway } from '@rayrun/sdk';

const callExecuteCodeWithRunAhead = async ({ mcp, mcpAccessToken, streamedArgumentDeltas }) => {
  const gateway = new RayrunGateway({ accessToken: mcpAccessToken });
  const runAhead = await gateway.codeRunAhead.open();
  let finalArguments = '';

  try {
    for await (const delta of streamedArgumentDeltas) {
      finalArguments += delta;
      void runAhead?.feedArguments(finalArguments);
    }

    await runAhead?.flush();

    const result = await mcp.callTool({
      name: 'execute_code',
      arguments: JSON.parse(finalArguments),
      ...(runAhead ? { _meta: runAhead.meta } : {}),
    });

    return result;
  } finally {
    await runAhead?.close();
  }
};

open() returns null when this optional optimization is rate-limited or temporarily unavailable. feedArguments() coalesces snapshots while one request is in flight; call flush() once before the final MCP request. Pass cumulative argument snapshots to feedArguments(), not individual provider deltas. It throws synchronously when a snapshot is not a string, so TypeScript users get the mistake at the call site. If a snapshot is neither identical to nor an append-only extension of the previous snapshot, the SDK immediately removes the session metadata and sends close, even while an earlier feed is in flight. Calling close() after abandoning a model branch also starts cancellation immediately. If a session becomes unavailable, send the final MCP call normally. Rayrun runs ahead only allowed tools approved as Read and advertised as read-only and idempotent. The final call rechecks identity, policy, definition, arguments, and program before using an early result. If the final code or dry_run differs, Rayrun closes the session and signals cancellation before ordinary execution. Cancellation is cooperative, but Rayrun never gives a stale speculative result to the final call.

Manage webhook configuration history

Webhook destination, description, event selection, delivery mode, batch size, enablement, and payload forwarding are saved together as immutable configuration versions. Signing secrets, delivery attempts, retry state, and health stay outside the snapshot.

const createdWebhook = await rayrun.webhooks.create({
  eventTypes: ['connection.indexed'],
  url: process.env.RAYRUN_WEBHOOK_URL,
});
// Store createdWebhook.secret now; Rayrun returns it only once.

const { items: webhooks } = await rayrun.webhooks.list();
const webhook = webhooks[0];

const updated = await rayrun.webhooks.update(webhook.uid, {
  enabled: false,
  expectedVersion: webhook.configurationVersion,
  reason: 'Pause deliveries during maintenance',
});

const history = await rayrun.webhooks.listConfigurationVersions(webhook.uid, { limit: 25 });
const selected = await rayrun.webhooks.getConfigurationVersion(
  webhook.uid,
  history.items.at(-1).uid,
);

await rayrun.webhooks.restoreConfigurationVersion(webhook.uid, selected.version.uid, {
  expectedVersion: updated.configurationVersion,
  reason: 'Restore the reviewed destination',
});

await rayrun.webhooks.replay(webhook.uid, failedDeliveryUid);
await rayrun.webhooks.delete(createdWebhook.uid);

Every material update records the user or API key, channel, time, and optional reason. Restore creates a new version, revalidates the historical destination URL, and preserves the endpoint’s current signing secret and delivery state. Identical saves are no-ops, stale expected versions fail, and immutable webhook configuration history is limited to 64 MiB per workspace. Destination paths and query strings can contain provider tokens, so read-only keys receive null URLs; keys with webhooks:write receive the full current and historical destination.

Verify webhook signatures

Pass the exact request body and the Rayrun-Signature header before parsing the event. Verification also rejects timestamps outside a five-minute replay window by default.

import { verifyWebhookSignature } from '@rayrun/sdk';

const valid = verifyWebhookSignature({
  body: rawBody,
  secret: process.env.RAYRUN_WEBHOOK_SECRET,
  signature: request.headers['rayrun-signature'],
});

After verification, parse the body as WebhookPayload. Every event has a stable id, type, and occurredAt; its data shape is narrowed by type. Delivery is at least once, so make processing idempotent and deduplicate on id. For activity.call, arguments and result contain the captured tool payloads, or null when capture was disabled for that connection, the webhook did not opt in with forwardToolPayloads: true, or no result existed. payloadCaptured distinguishes a metadata-only webhook from a call where capture was disabled. Rayrun observes MCP tool arguments and results; it does not receive the host's full Claude or GPT prompt, assistant response, or surrounding chat.

import type { WebhookPayload } from '@rayrun/sdk';

const event: WebhookPayload = JSON.parse(rawBody);
if (event.type === 'review.changed') {
  console.log(event.data.reviewUid, event.data.status);
}

Verify who is calling a hosted server

When a publication's end user calls a tool, Rayrun signs who they are and sends it to the hosted server as the Rayrun-Identity header and as _meta["io.rayrun/identity"]. A call from your own workspace carries neither. Every hosted server is given RAYRUN_CONNECTION_UID and RAYRUN_IDENTITY_JWKS_URL, so passing process.env is all the configuration verification needs.

import { verifyIdentityAssertion } from '@rayrun/sdk';

const identity = await verifyIdentityAssertion({
  assertion: request.headers.get('rayrun-identity'),
  environment: process.env,
});

if (identity !== null) {
  console.log(identity.endUserUid, identity.externalId, identity.publicationUid);
}

It resolves null for a call without an assertion, and for one that fails any check: the signature, the type, the audience (this server's connection), or the one-minute lifetime. It throws when an assertion arrives but there is no audience or key set URL to check it with, or when the key set cannot be fetched, so a misconfigured server fails loudly instead of serving everybody as nobody. externalId is the subject the publication's sign-in provider asserted, which is what your own user records already know them by; email is present only when that provider said it was verified.

Version-controlled access policies

Server-side drafts

const document = await rayrun.accessProfiles.exportConfiguration('profile123');
document.configuration.toolAccessMode = 'read-only';
const draft = await rayrun.accessProfiles.saveDraft(document, { expectedDraftRevision: 0 });
const review = await rayrun.accessProfiles.reviewDraft(document.profileUid, draft.draftUid);
await rayrun.accessProfiles.activateDraft(document.profileUid, draft.draftUid, {
  reason: 'Reviewed support rollout',
});

Draft saving requires policies:write and leaves live access unchanged. listDrafts(profileUid, { beforeRevision }) returns up to 100 immutable revisions with attribution; the first save expects revision zero, and later saves require the latest draft revision. Review is a read-only validated diff, not an approval record. Activation applies the exact saved document and rejects a stale live version.

clone(profileUid, { expectedVersion, name }) atomically creates an unassigned block-all profile with the source configuration in its first draft. archive(profileUid, expectedVersion) retires an unused profile; history and drafts remain readable. Deactivation uses applyConfiguration with block-all and an empty toolRules list, retaining assignments and recording a live revision.

Direct configuration apply

const document = await rayrun.accessProfiles.exportConfiguration('profile123');
document.configuration.toolAccessMode = 'read-only';
const plan = await rayrun.accessProfiles.planConfiguration(document);
// Review plan.before, plan.after, and plan.requiresRiskConfirmation.
await rayrun.accessProfiles.applyConfiguration(document, { reason: 'Reviewed in PR 42' });

Persist the exported JSON in version control for review. The complete configuration is replaced in one transaction with the exported expectedVersion and workspace as guards. Omitted tool rules revert to the profile default. A stale document fails with 409 version_conflict; export again and review before retrying. High-risk grants require riskConfirmed: true. Export and plan require policies:read, and apply requires policies:write. Client assignments, connections, credentials, and content scanners are outside this document. Each changed apply records one immutable access-profile revision.