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

@oneshot-agent/sdk

v0.32.1

Published

Autonomous Agent SDK for executing real-world commercial transactions with automatic x402 payments

Readme

@oneshot-agent/sdk

Autonomous Agent SDK for executing real-world commercial transactions with automatic x402 payments.

Installation

npm install @oneshot-agent/sdk

Using with Claude Desktop, Cursor, or Claude Code?

Use the MCP server instead for zero-code integration:

npx -y @oneshot-agent/mcp-server

See the MCP Server documentation for setup instructions.

Teach your coding agent to use OneShot

Install the OneShot Agent Skills into Claude Code, Cursor, Codex, and 70+ other agents so they know how to call the SDK and MCP tools:

# All OneShot skills
npx skills add oneshot-agent/agent-skills

# Or just what you need (e.g. setup + email)
npx skills add oneshot-agent/agent-skills --skill oneshot --skill oneshot-email

Skills available: oneshot (setup/auth), oneshot-email, oneshot-messaging, oneshot-research, oneshot-enrichment, oneshot-commerce, oneshot-browser, oneshot-build, oneshot-compute, soul-markets. Source: oneshot-agent/agent-skills.

Quick Start

import { OneShot } from '@oneshot-agent/sdk';

const agent = new OneShot({
  privateKey: process.env.AGENT_PRIVATE_KEY!
});

// Send email
await agent.email({
  to: '[email protected]',
  subject: 'Hello',
  body: 'Hello World!'
});

// Make a voice call
const call = await agent.voice({
  objective: 'Call the restaurant to make a reservation for 2 at 7pm',
  target_number: '+14155551234'
});
console.log(call.transcript);

// Send SMS
await agent.sms({
  message: 'Your order has shipped!',
  to_number: '+14155551234'
});

// Build a website
const site = await agent.build({
  type: 'saas',
  product: {
    name: 'TaskFlow',
    description: 'AI-powered task management for remote teams'
  }
});
console.log('Live at:', site.production_url);

// Research
const report = await agent.research({ topic: 'AI agents', depth: 'deep' });

// Check balance
const balance = await agent.getBalance(agent.usdcAddress);

Network

The SDK operates on Base Mainnet with real USDC. Fund your agent wallet with USDC on Base before making paid tool calls.

agent.usdcAddress; // 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
agent.chainId;     // 8453

Paying with ETH

Set currency: 'ETH' and the SDK keeps the wallet funded in USDC for you:

  • Before each payment it reads the wallet's USDC and subtracts payments it has signed in the last ~90s that may not have settled yet. If that covers the charge, no swap happens — the call costs one eth_call.
  • Otherwise it swaps ETH→USDC on Uniswap V3 (Base mainnet) for swapBufferMultiplier × the charge (default 10, range 1–1000), so one swap covers many calls. If your provider exposes getBalance and the wallet's ETH cannot cover the buffer, it swaps just the shortfall (a send-only provider always gets the buffered amount). Set swapBufferMultiplier: 1 for exact-shortfall swaps.
  • Concurrent calls from one instance are serialized around the swap, so a burst never double-swaps or races the wallet nonce.

Run one process per wallet in ETH mode: each process keeps its own in-flight ledger. ETH mode requires a wallet provider with sendTransaction and is Base mainnet only.

Available Methods

| Method | Description | |--------|-------------| | Communication | | | email() | Send emails with attachments | | voice() | Make phone calls | | sms() | Send SMS messages | | inboxList() | List inbound emails | | inboxGet() | Get email by ID | | smsInboxList() | List inbound SMS | | smsInboxGet() | Get SMS by ID | | notifications() | List agent notifications | | markNotificationRead() | Mark notification as read | | Domain Pool | | | listDomains() | List your sending domains with warmup/rotation state | | pauseDomain() | Take a domain out of rotation | | resumeDomain() | Put a paused domain back into rotation | | Research & People | | | research() | Deep web research | | peopleSearch() | Search people by criteria | | enrichProfile() | Enrich from LinkedIn/email | | findEmail() | Find email for a person | | verifyEmail() | Verify email deliverability | | companySearch() | Search companies by name, domain, industry, location, size, funding | | enrichCompany() | Enrich a company from domain, name, LinkedIn URL, or ticker | | localSearch() | Discover local businesses (restaurants, contractors, practices) by category × location | | localResolve() | Business name + address → website domain, phone, status, confidence | | govSolicitations() | Federal Sources Sought / Presolicitation notices by NAICS with the contracting officer's contact | | deepResearchPerson() | Full dossier on a person | | socialProfiles() | Find all social accounts | | articleSearch() | Find articles about a person | | personNewsfeed() | Recent social posts with engagement | | personInterests() | Analyze interests across categories | | personInteractions() | Map followers, following, replies | | Web & Commerce | | | webSearch() | Search the web | | webRead() | Read any URL as markdown + screenshot | | commerceBuy() | Purchase products | | commerceSearch() | Search products | | Browser | | | browser() | Autonomous browser — navigate, click, extract | | createBrowserProfile() | Create a persistent browser profile | | listBrowserProfiles() | List saved browser profiles | | deleteBrowserProfile() | Delete a browser profile | | Build | | | build() | Build and deploy production websites | | updateBuild() | Update an existing website | | Compute (Agentic Goals) | | | compute() | Launch autonomous multi-step goal | | getComputeGoal() | Get goal status and progress | | getComputeTasks() | List tasks for a goal | | getComputeBudget() | Check remaining budget for a goal | | respondToComputeTask() | Provide input to a pending task | | pauseComputeGoal() | Pause a running goal | | resumeComputeGoal() | Resume a paused goal | | cancelComputeGoal() | Cancel a goal | | fundComputeGoal() | Add funds to a goal's budget | | Analytics & Balance | | | getBalance() | Check token balance | | getUnifiedBalance() | Get balance across all chains | | spendBreakdown() | Spending breakdown by tool/period | | rocs() | Return on compute stats | | receiptsList() | List payment receipts (limit/offset or since/until window) | | tagReceiptValue() | Tag a receipt with business value (by receipt_id or request_id) |

Configuration

interface OneShotConfig {
  privateKey: string;    // Required
  baseUrl?: string;      // Override API URL
  rpcUrl?: string;       // Override RPC URL
  debug?: boolean;       // Enable logging
  logger?: (msg: string) => void;
  budgets?: {            // Spend budget (see below)
    daily?: number;
    perTransaction?: number;
    alertAt?: number;
    pauseAt?: number;
  };
  alerts?: { email?: string };
}

Spend Budgets

Cap what the agent can spend, so a runaway loop can't drain the wallet overnight.

const agent = await OneShot.create({
  cdp: true,
  budgets: {
    daily: 50,           // max USDC per UTC day
    perTransaction: 5,   // max USDC for any single call
    alertAt: 0.8,        // warn at 80% of daily
    pauseAt: 1.0,        // stop paying at 100%
  },
  alerts: { email: '[email protected]' },
});

Unlike maxCost (a per-call client-side check), budgets are enforced server-side against your receipt ledger. The config is synced once before your first paid call, so the daily figure is a true per-agent total — shared across every process, restart, and serverless invocation using the same wallet, not a per-instance counter.

At alertAt you get a notification (in-app via agent.notifications(), plus email if you set one). At pauseAt paid calls are rejected with a BudgetExceededError until the window resets at the next UTC midnight:

try {
  await agent.research({ topic: '...' });
} catch (err) {
  if (err instanceof BudgetExceededError) {
    console.log(`${err.reason} budget hit — $${err.spent} spent, resumes ${err.resetsAt}`);
  }
}

Check utilization any time:

const b = await agent.budgets();
// { daily_usdc: 50, spent_today_usdc: '41.20', pct_used: 0.824, resets_at: '...' }

Omit budgets entirely to leave whatever is stored server-side untouched — agents without a budget are unlimited, which is the default.

The sync fails closed: if the budget can't be confirmed with the server (network error, 5xx, rate limit, rejected config) the paid call throws BudgetSyncError and is not made — proceeding would silently drop the guardrail you asked for. It retries on the next paid call. An invalid config (daily: -1, alertAt: 2) throws ValidationError at construction.

Authentication

Every request identifies you by your wallet address (X-Agent-ID). Paid tools additionally settle a signed x402 USDC payment.

Read endpointsinboxList / inboxGet, smsInboxList / smsInboxGet, notifications / markNotificationRead, getUnifiedBalance, and createBrowserProfile / listBrowserProfiles / deleteBrowserProfile — return private, per-agent data. Since a wallet address is public, the SDK proves you actually control it: on each read it signs a short-lived EIP-712 read proof and sends it as the x-agent-proof header. The API verifies the signature locally and rejects any request whose proof doesn't match the X-Agent-ID wallet, so no one can read your inbox/balance by supplying your address.

This is fully automatic — you don't do anything beyond providing your privateKey. Requires SDK ≥ 0.25.0. Older SDKs (or raw HTTP callers) will keep working until the API enables enforcement, after which they must send a valid x-agent-proof; upgrade to stay ahead of it.

Tool Options

All methods accept these common options:

interface ToolOptions {
  maxCost?: number;      // Max USDC willing to pay (see supported tools below)
  timeout?: number;      // Timeout in seconds
  signal?: AbortSignal;  // Cancel before payment (see Cancellation section)
  wait?: boolean;        // Wait for async jobs (default: true)
  onStatusUpdate?: (status: string, requestId: string) => void;
}

maxCost Support

The maxCost option is a client-side guard — the SDK compares the quoted price against your limit and throws before signing any payment if it exceeds it. This is supported on tools with variable pricing:

| Method | maxCost | |--------|:---------:| | commerceBuy() | Supported | | voice() | Supported | | sms() | Supported | | build() / updateBuild() | Supported | | browser() | Supported | | All other tools | Not applicable (fixed low-cost pricing) |

Error Handling

import {
  ValidationError,
  ToolError,
  JobError,
  JobTimeoutError,
  ContentBlockedError,
  EmergencyNumberError
} from '@oneshot-agent/sdk';

try {
  await agent.voice({
    objective: 'Make a call',
    target_number: '+14155551234'
  });
} catch (error) {
  if (error instanceof ContentBlockedError) {
    // Content blocked by safety filters
    console.log(`Blocked: ${error.message}`);
    console.log(`Categories: ${error.categories.join(', ')}`);
  } else if (error instanceof EmergencyNumberError) {
    // Attempted to call/SMS emergency number
    console.log(`Emergency number blocked: ${error.blockedNumber}`);
  } else if (error instanceof ValidationError) {
    console.log(`Invalid: ${error.field}`);
  } else if (error instanceof ToolError) {
    console.log(`API error: ${error.statusCode}`);
  } else if (error instanceof JobTimeoutError) {
    console.log(`Timeout: ${error.jobId}`);
  } else if (error instanceof JobError) {
    // An async job failed. `error.code` is a STABLE, machine-readable code —
    // branch on it rather than parsing `error.message`.
    switch (error.code) {
      case 'insufficient_funds':
        console.log('Fund your wallet — do not blind-retry');
        break;
      case 'invalid_input':
        console.log('Fix the request — retrying as-is will not help');
        break;
      case 'rate_limited':
      case 'provider_unavailable':
      case 'internal_error':
        console.log('Transient — safe to retry');
        break;
      default:
        console.log(`Job failed (${error.code}): ${error.message}`);
    }
  }
}

Job error codes

When a JobError is thrown, error.code is one of: insufficient_funds, payment_failed, invalid_input, content_blocked, rate_limited, provider_unavailable, provider_auth, enrichment_exhausted, checkout_failed, internal_error. See the Check Job Status reference for the full meaning and recommended reaction for each.

Examples

Build Websites

// Build a SaaS landing page
const site = await agent.build({
  type: 'saas',
  product: {
    name: 'TaskFlow',
    description: 'AI-powered task management for remote teams. Automate workflows, track progress, and collaborate seamlessly.',
    industry: 'Productivity',
    pricing: 'Free tier, Pro $12/mo, Team $29/mo'
  },
  lead_capture: { enabled: true },
  brand: {
    primary_color: '#4F46E5',
    tone: 'professional'
  }
});

console.log('Website URL:', site.production_url);
console.log('Preview:', site.preview_url);

// Update existing website
const updated = await agent.updateBuild({
  build_id: site.request_id,
  product: {
    name: 'TaskFlow 2.0',
    description: 'Now with AI automation! Task management reimagined.',
    pricing: 'Free tier, Pro $15/mo, Team $35/mo'
  }
});

Build types: saas, portfolio, agency, personal, product, funnel, restaurant, event

Voice Calls

// Simple call
const call = await agent.voice({
  objective: 'Call to schedule a dentist appointment for next Tuesday',
  target_number: '+14155551234',
  caller_persona: 'A polite assistant scheduling an appointment',
  context: 'Patient prefers morning appointments',
  maxCost: 5
});

console.log('Transcript:', call.transcript);
console.log('Summary:', call.summary);
console.log('Success:', call.success_evaluation);

// Conference call (multiple numbers)
const conference = await agent.voice({
  objective: 'Connect the buyer and seller to negotiate the final price',
  target_number: ['+14155551234', '+14155555678'],
  caller_persona: 'A professional meeting facilitator'
});

SMS

// Single recipient
await agent.sms({
  message: 'Your appointment is confirmed for tomorrow at 10am',
  to_number: '+14155551234'
});

// Multiple recipients (up to 10)
await agent.sms({
  message: 'Team meeting moved to 3pm',
  to_number: ['+14155551234', '+14155555678', '+14155559012']
});

// Check SMS inbox
const inbox = await agent.smsInboxList({ limit: 10 });
for (const msg of inbox.messages) {
  console.log(`From ${msg.from}: ${msg.body}`);
}

// Get specific message
const msg = await agent.smsInboxGet('msg_abc123');

Email with Attachments

await agent.email({
  to: ['[email protected]', '[email protected]'],
  subject: 'Report',
  body: 'See attached.',
  attachments: [{
    filename: 'report.pdf',
    content: base64Content,
    content_type: 'application/pdf'
  }]
});

Custom Sender (name, mailbox, domain)

// Renders as: From: Jane Doe <[email protected]>
await agent.email({
  to: '[email protected]',
  subject: 'Hello',
  body: 'Sent from a custom sender.',
  from_name: 'Jane Doe',    // display name (optional)
  from_mailbox: 'jane',     // local-part, defaults to "agent"
  from_domain: 'acme.com'   // defaults to "oneshotagent.com"
});

from_domain must be a domain you've provisioned through OneShot. Defaults produce [email protected].

Dedicated mailboxes: pass mailbox_mode: 'mailbox' when first provisioning a new from_domain to give each address a real dedicated mailbox (better deliverability + per-address warmup) instead of the default relay (header-only) send; it adds a one-time mailbox_provisioning_fee to the quote. It only applies at first setup — an already-provisioned domain keeps the mode it was created with, and requesting mailbox on an existing relay domain returns 400 mailbox_mode_conflict.

Domain Pool & Warmup

OneShot runs a per-agent domain pool with server-side warmup so your cold email lands in the inbox. Understanding pin-vs-rotate is the key concept:

  • Rotate (recommended for cold outreach): omit both from_domain and from_mailbox. The server picks a warmed, under-cap domain from your own pool, applies warmup-score and daily-limit gates, and returns the chosen address on the quote (quote.from_address). This is the only mode that gets warmup protection. If you own no eligible domain, the quote returns 400 no_sending_domain — there is no shared fallback sender; you must provision/own a domain to send.
  • Pin: set from_domain (and/or from_mailbox) to force an exact sender. Rotation is bypassed — and so are the warmup-score and daily-limit gates. Pinning a still-warming or over-cap domain will hurt deliverability; the send still goes out, but the response carries a non-blocking warning (see below).

Reputation is per-domain, shared by every mailbox on it. Running multiple mailboxes on one domain (jane@, sales@, …) does not improve deliverability or raise capacity — they share the same warmup score and the same daily_send_limit.

Provisioning: a domain enters your pool the first time you reference it in a send/quote (auto-provisioned, then provisioning → verified → warming → active once its warmup score crosses the activation threshold). There is no separate provisioning call today. New domains start in warming and aren't rotation- eligible until they graduate.

The warning field (non-blocking — the send still happens; read it to defer):

| warning | Meaning | |-----------|---------| | pinned_domain_warming | Your pinned domain is still warming (low warmup score) — poor deliverability likely. | | pinned_over_limit | Your pinned domain is over its daily_send_limit for today. |

Pinned sends are never blocked by warmup/limit — branch on warning and defer. (The one hard error is 400 no_sending_domain on an un-pinned send when you own no eligible domain — provision or pin one.)

// Inspect and manage the pool
const { domains } = await agent.listDomains();
// each: { domain, pool_status, warmup_score, daily_send_limit, daily_sent_count, ... }

await agent.pauseDomain('acme.com');   // take out of rotation
await agent.resumeDomain('acme.com');  // put back (only from 'paused')

const res = await agent.email({ to: '[email protected]', subject: 'Hi', body: '…' });
if (res.warning) {
  // e.g. 'pinned_domain_warming' — back off and let warmup finish
}

People Search & Enrichment

const results = await agent.peopleSearch({
  job_titles: ['CEO', 'CTO'],
  companies: ['Stripe'],
  limit: 10
});

const profile = await agent.enrichProfile({
  linkedin_url: results.results[0].linkedin_url
});

Commerce

const order = await agent.commerceBuy({
  product_url: 'https://amazon.com/dp/B07ZPC9QD4',
  shipping_address: {
    first_name: 'John',
    last_name: 'Doe',
    street: '123 Main St',
    city: 'San Francisco',
    state: 'CA',
    zip_code: '94102',
    country: 'US',
    phone: '4155550100'
  },
  maxCost: 100
});

Notifications

// List notifications
const notifications = await agent.notifications({ unread: true, limit: 20 });
for (const n of notifications.notifications) {
  console.log(`[${n.type}] ${n.title}: ${n.message}`);
}

// Mark as read
await agent.markNotificationRead('notification-uuid');

Cancellation

Use AbortSignal to cancel operations before payment is made. Once payment is signed, the operation will execute regardless of cancellation.

const controller = new AbortController();

// Cancel after 5 seconds if still in quote phase
setTimeout(() => controller.abort(), 5000);

try {
  await agent.voice({
    objective: 'Make a reservation',
    target_number: '+14155551234',
    signal: controller.signal
  });
} catch (error) {
  if (error.message === 'Operation cancelled before payment') {
    console.log('Cancelled before paying - no charge');
  }
}

Important: The signal can cancel:

  • Quote requests (before receiving price)
  • The decision phase (after quote, before payment)

The signal cannot cancel:

  • Operations after payment is signed (call/SMS will still execute)
  • Server-side job execution

Links

License

MIT

Physical mail

agent.physicalMail exposes uploadArtwork, validateAddress, preview, getQuote, approve, send, getOrder, recover, and cancel for U.S. letters and 4×6 postcards. Inspect the proof and price before explicitly approving each quote. Persist idempotencyKey before send; use recover(key) after uncertain responses. Approval is never inferred by automatic payment retries. Payment receipts and postal events are separate; delivery does not prove readership. See docs/physical-mail.md in the OneShot repository for configuration and examples.