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

puras

v0.4.0

Published

Puras JavaScript/TypeScript SDK — call your deployed Puras skills from Node, React, and React Native / Expo.

Readme

puras

JavaScript/TypeScript SDK for Puras — call your deployed Puras skills from Node, React, and React Native / Expo. Zero dependencies, works everywhere fetch does.

The Python counterpart (pip install puras) additionally ships the in-skill runtime and the deploy CLI; this package is the client half only: submit jobs, wait for them, read results.

npm install puras

Quickstart (Node / backend)

import { Puras } from "puras";

const puras = new Puras(); // reads PURAS_API_KEY from env

// Fully-qualified skill path, copyable from the skill's page:
const ad = await puras.run("acme/ugc-ads/ugc-ad", {
  product: "https://example.com/product",
  duration: 15,
});
console.log(ad.video); // signed media URL

For your own skills, set a default skillpack once and call skills by name:

const puras = new Puras({ apiKey: process.env.PURAS_API_KEY, skillpack: "ugc-ads" });
const ad = await puras.run("ugc-ad", { product: url });

React / Expo (client apps)

Use a publishable key (puras_pub_…), minted in the dashboard under API keys → Publishable. Publishable keys are designed to ship inside an app bundle:

  • they can only submit jobs and read the jobs they submitted — no drive, no skillpack management, no job-history enumeration;
  • you can restrict them to a skill allowlist at creation;
  • you can set a daily spend cap so an extracted key can't drain your balance.

Never embed a secret key (puras_live_…) in an app.

import { Puras } from "puras";

const puras = new Puras({ apiKey: "puras_pub_XXXXXXXX.YYYY..." });

async function generateVideo(photoUrl: string) {
  const job = await puras.submit("acme/ugc-ads/ugc-ad", { product: photoUrl });
  // Poll — media skills take minutes. Update your UI as you go:
  const done = await puras.wait(job.id, { pollIntervalMs: 3000, timeoutMs: 15 * 60_000 });
  if (done.status !== "succeeded") throw new Error(done.error ?? done.status);
  return done.result;
}

Three ways to consume a job

  • One awaitawait puras.run(skill, inputs) submits and polls to completion, returning the skill's result. (For short jobs, submit(…, { wait: true }) holds the HTTP request open server-side instead.)
  • Asyncawait puras.submit(skill, inputs) returns the job id immediately; check back later with get() / wait().
  • Live events (SSE)puras.events(jobId) is an async iterator over the job's event stream (step, tool_call, log, …), for progress UIs:
const job = await puras.submit("acme/ugc-ads/ugc-ad", { product: url });
for await (const ev of puras.events(job.id)) {
  console.log(ev.type, ev.payload);
}
const done = await puras.get(job.id); // terminal by now

Transport is picked automatically: true SSE where fetch can stream (Node, browsers), polling fallback where it can't (React Native / Expo). Force one with { transport: "sse" | "poll" }.

API

new Puras(options?)

| option | default | | | --- | --- | --- | | apiKey | PURAS_API_KEY env (Node) | puras_live_… or puras_pub_… | | skillpack | — | default skillpack (slug or UUID) for bare skill names | | apiBase | https://api.puras.co | | | timeoutMs | 120000 | per-request timeout | | fetch | global fetch | custom implementation (tests, polyfills) |

puras.run(skill, inputs?, options?) → Promise<result>

Submit + wait, returning the skill's result. Throws JobError (carrying the full job) if the job failed, was cancelled, or is still running when timeoutMs (default 10 min) elapses. Waiting is client-side polling (pollIntervalMs, default 2 s) so it works on React Native and survives multi-minute media jobs.

puras.submit(skill, inputs?, options?) → Promise<Job>

Fire-and-return — gives you the job {id, status, …} immediately. Options: skillpack, version (pin a deployment), wait + timeoutSeconds (server-side wait, for short jobs).

puras.get(jobId) → Promise<Job> / puras.wait(jobId, options?) → Promise<Job>

Fetch a job once, or poll it to a terminal status.

puras.feedback(jobId, options?) → Promise<JobFeedback>

Record end-user feedback on a job's result — a thumb and/or a comment — so you can later see which skills run well and which don't. Options: rating (1 good, -1 bad, 0 comment-only), comment (free-form text), endUserId (opaque id of the end user; re-posting with the same (job, endUserId) overwrites their rating/comment). At least one of a non-zero rating or a comment is required.

const result = await puras.run("acme/ugc-ads/ugc-ad", { product: url });
// …user reacts in your UI…
await puras.feedback(job.id, { rating: 1, endUserId: user.id });

puras.events(jobId, options?) → AsyncGenerator<JobEvent>

Stream the job's events live (SSE with automatic polling fallback); the iterator ends when the job reaches a terminal status. Options: transport ("auto" | "sse" | "poll"), afterId (resume), pollIntervalMs, signal.

puras.chat(skill, options?) → Promise<Conversation>

Open a multi-turn chat against a mode: chat skill. The skill's SKILL.md is the system prompt and its tools are the agent's tools; each message runs one turn and resolves with the model's reply, with the thread remembered server-side.

const chat = await puras.chat("acme/support-desk/refund-concierge");
console.log(await chat.send("Hi, my order AMZ-1001 arrived defective"));
console.log(await chat.send("It's the earbuds, EARBUDS-X2")); // continues the thread

options: skillpack, version (pin a deployment), model (pin a text-model slug for every turn), title.

A Conversation has:

  • send(message, options?) → Promise<string> — run a turn, resolve with the reply. options is the same wait/poll budget as wait(). Throws JobError on a failed/cancelled turn.
  • sendJob(message) → Promise<Job> — run a turn but return the job immediately (stream it with puras.events(job.id); read the reply from puras.get(job.id).result.reply).
  • stream(message, options?) → AsyncGenerator<JobEvent> — send + stream the turn's events live; read the reply after with history().
  • history() → Promise<{role, text}[]> — the rendered transcript so far.
  • delete() → Promise<void>.
  • id / skill — resume later with new Conversation(...) semantics via the id.

Lower-level: createConversation, getConversation, listConversations, sendMessage, deleteConversation.

Works with either key kind: a publishable key (puras_pub_…) can open conversations and send turns directly from a web/mobile app — scoped, like jobs, to the conversations it created and bounded by its allowed-skills fence + daily spend cap. A secret key (puras_live_…) is unrestricted in a backend. (listConversations is the one exception — it stays secret/JWT-only so an app key can't enumerate the workspace's chats.)

Errors

  • PurasAPIError — non-2xx API response (.status, .detail).
  • JobError — terminal-but-not-succeeded job (.job).

Skill addressing

skill accepts the same forms as the Python SDK and the dashboard:

| form | example | resolves against | | --- | --- | --- | | workspace/skillpack/skill | acme/ugc-ads/ugc-ad | any public skillpack (or your own) | | skillpack/skill | ugc-ads/ugc-ad | your workspace | | skill | ugc-ad | the default skillpack you configured |

License

MIT