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

@oreoasis/sdk

v0.1.0

Published

Oreoasis SDK — sign an AI agent's action locally and get a signed, publicly-verifiable receipt.

Downloads

84

Readme

@oreoasis/sdk

Your agent did the work. Prove it. Sign an AI agent's action locally and get a signed, publicly-verifiable receipt — a link anyone can open and check without trusting your backend (or ours).

  • 🔏 Local signing — the receipt is signed in your process with your agent's Ed25519 key (via @kashscript/attest). Inputs/outputs are hashed client-side; raw content never leaves your machine.
  • 🧩 One lineclient.wrap(type, fn) records a receipt around any function.
  • 🎯 Typed errors — every failure is a specific class (OreoasisRateLimitError, OreoasisPlanLimitError, …) carrying the server code + traceId.
  • 🔁 Idempotent + resilient — transient network failures resend the identical signed envelope; the server dedups a retry that already landed.

Verification is free forever. Python SDK is a post-launch fast-follow.

Install

bun add @oreoasis/sdk        # or: npm i @oreoasis/sdk

Quickstart (under 5 minutes)

import { OreoasisClient, generateAgentKey } from "@oreoasis/sdk";

// 1. An agent signing identity. PERSIST IT — see "Your agent's key" below.
//    A key minted fresh on every boot is a NEW agent every restart.
const agent = await loadOrCreateAgentKey("my-bot");

// 2. A client (get your API key at oreoasis.com).
const client = new OreoasisClient({ apiKey: process.env.OREOASIS_API_KEY!, agent });

// 3. Record an action → get a public verify URL.
const receipt = await client.record({
  action: { type: "tool.call", tool: "search_flights", summary: "Booked SEA→LHR" },
  inputs: [{ name: "query", content: "SEA to LHR, 2 pax" }],   // hashed locally
  outputs: [{ name: "result", content: "PNR ABC123" }],
  status: "completed",
});

console.log(receipt.verifyUrl); // → https://oreoasis.com/verify/receipt/rcpt_…  (open it!)

Your agent's key — persist it

generateAgentKey() mints a new identity. Call it on every boot and every restart is a different agent: your receipts stay verifiable (the public key travels inside the envelope, so nothing you already issued is affected) but the continuity is gone — nobody can say "these 40,000 receipts are all from the same agent."

Persist the key. Ephemeral is the exception, for a one-off script or a test.

import { readFile, writeFile } from "node:fs/promises";
import { generateAgentKey, agentKeyFromSecret, exportAgentSecret } from "@oreoasis/sdk";

async function loadOrCreateAgentKey(name: string) {
  const path = process.env.AGENT_KEY_PATH ?? "./.agent-key";
  try {
    return agentKeyFromSecret(name, (await readFile(path, "utf8")).trim());
  } catch {
    const agent = await generateAgentKey(name);
    // Treat this like any other secret: 0600, outside the repo, in your secret
    // store in production. Anyone holding it can sign receipts as your agent.
    await writeFile(path, exportAgentSecret(agent), { mode: 0o600 });
    return agent;
  }
}

Losing the key is survivable — mint a new one and carry on; old receipts keep verifying under the old public key forever. What you cannot get back is the claim that both sets came from the same agent.

One-line middleware

Wrap any async function — every call emits a receipt (completed or failed), and the wrapped result/exception passes straight through:

const search = client.wrap("tool.call", searchFlights, {
  summary: (q) => `search: ${q}`,
  inputs: (q) => [{ name: "query", content: q }],
  outputs: (r) => [{ name: "result", content: JSON.stringify(r) }],
});

await search("SEA→LHR"); // runs searchFlights AND records a signed receipt

What wrap records, precisely — worth reading once, because the answers are deliberate:

  • One receipt per ATTEMPT, not per logical operation. If your caller retries a failed call three times you get three receipts: two failed, one completed. That is correct — each attempt is an action that happened, and a record that hid the failures would be a worse record. If you want one receipt for the whole operation, wrap the retry loop rather than the function inside it.
  • A throw records status: "failed" and re-throws. Your control flow is unchanged; the receipt is a side effect that never swallows an error.
  • Non-Error throws (a string, an object, undefined — legal in JS) are coerced with String(err) into meta.error. You get "undefined" rather than a crash inside the receipt path.
  • Receipt failures never break the wrapped call. If recording fails (network, rate limit, plan cap), the wrapped function's result still returns; route the error via opts.onError if you want to know.
  • The return value is hashed only if you ask. outputs(result) is your function — whatever it returns is what gets hashed. If your function returns a stream or an AsyncIterator, do not pass it to outputs: hashing it would consume it, and JSON.stringify of a stream is {}, which hashes to a meaningless constant. Either buffer it first and hash the buffer, or record the receipt after the stream completes with a hash of what you actually sent.

Typed errors

import {
  OreoasisRateLimitError,
  OreoasisPlanLimitError,
  OreoasisClockSkewError,
} from "@oreoasis/sdk";

try {
  await client.record({ action: { type: "x", summary: "y" } });
} catch (err) {
  if (err instanceof OreoasisRateLimitError) await sleep(err.retryAfterSec * 1000);
  else if (err instanceof OreoasisPlanLimitError) console.warn("upgrade your plan");
  else if (err instanceof OreoasisClockSkewError) console.error("this machine's clock is wrong");
  else throw err; // err.code + err.traceId are always available
}

Time: what a receipt proves, and what it doesn't

A receipt carries three times, and they are not equally trustworthy:

| Time | Set by | Means | |---|---|---| | timestamp in the claim — "claimed by agent" | you (the SDK defaults it to Date.now()) | what your agent said the time was. It is inside the signed payload, so it proves what was signed — never when it happened. | | "received by Oreoasis" | the server | when we received and accepted the receipt. Ours to attest. | | "anchored" | the server | when the chain tip carrying it was published to a public transparency log. |

The verify page shows all three, labelled, and says which two Oreoasis vouches for. Design your evidence around received and anchored; treat the claimed time as a helpful annotation.

Past timestamps are accepted — backfilling a day of history is legitimate and supported. A timestamp more than 24 hours in the future of server time is refused with 422 CLAIM_TIMESTAMP_IN_FUTURE (OreoasisClockSkewError), because that is a broken clock or an attempt to pre-date evidence. Operators can widen or narrow the window with OREOASIS_CLAIM_FUTURE_SKEW_MS.

prev is your assertion of a predecessor receipt. Nothing validates it — it may point at a receipt that doesn't exist — so the verify page labels it "agent-claimed predecessor" and never links it. Oreoasis's own chain link (chain.prevHash) is separate, server-built and org-signed.

Anchoring: what to expect

Free-tier receipts anchor in batches, typically within a few hours — until then the verify page honestly says "anchoring pending". Paid plans anchor each receipt immediately. A receipt is signed and chained the moment it is accepted; anchoring adds the public existence-proof on top, and its absence never means the receipt is invalid.

⚠️ Never put personal data in meta

Read this once, properly — it is the only field where a mistake is permanent.

Everything you pass as inputs / outputs is hashed locally: the raw content never leaves your process. meta is the exception. It is free-form, and it ships verbatim into:

  1. the signed payload — so it is inside the thing you are asking people to trust, and changing it later invalidates the signature;
  2. the public verify page — anyone with the link reads it;
  3. the hash chain, whose tip is published to a public transparency log.

Transparency logs cannot be erased. Not by you, not by us, not by a court order to us — that permanence is the entire point of anchoring, and it applies to your mistakes as faithfully as to your evidence. We can withdraw a payload from display on request (see the takedown policy), but the hash and the log entry stay, forever.

So: no names, no emails, no card or account numbers, no free-text a customer wrote, nothing you would not print on a postcard. Use meta for the boring annotations it is for — a workflow id, an attempt number, an internal reference:

meta: { workflow: "refunds", attempt: 2, region: "eu-west" }   // fine
meta: { customerEmail: "…", note: userInput }                  // NEVER

Size limit: meta is capped at 8 KiB of JSON. Over that the ingest refuses the receipt with 422 META_TOO_LARGE — a deliberately loud failure, because the alternative is discovering it after it is anchored. Large content belongs in inputs/outputs as a hash.

Hashing notes

  • Empty content hashes fine, and means nothing. sha256("") is a perfectly valid digest (e3b0c442…) and it is the same digest for every empty input, so it proves only "there was a field here". If content is genuinely absent, omit the ref rather than passing "". If it is merely large or already hashed, pass sha256 directly instead of content.
  • Large content (>10 MB): pass a precomputed sha256. The SDK hashes with WebCrypto over an in-memory buffer; on an edge runtime or a small container, hashing a very large blob is the thing that will fall over, and it is work you have usually already done upstream.
  • Uint8Array and string both work — strings are hashed as UTF-8. The two are not interchangeable: "41" and Uint8Array([0x41]) are different bytes and hash differently. Pick one representation per field and stay with it.
  • Duplicate names in inputs are allowed and preserved in order. Nothing de-duplicates them; a reader sees exactly what you sent.

Idempotency

Pass an idempotencyKey for at-least-once safety — a retry that already landed replays the original receipt instead of creating a duplicate:

await client.record(action, { idempotencyKey: order.id });

Conformance

Receipts are DSSE envelopes with payloadType application/vnd.oreoasis.agent-receipt+json (registered in the KashScript DST registry) over a JCS-canonical claim. Published test vectors live in vectors/agent-receipt-v1.json; any conformant signer reproduces them byte-for-byte.