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

@apteva/web-sdk

v0.5.1

Published

Browser SDK for apteva-server. Handles auth (session cookie + API key + ?api_key=), wraps the /api/apps/<name>/* HTTP and MCP proxy, normalizes the MCP JSON-RPC envelope, and surfaces window.__APTEVA_APP__ when the UI is hosted on apteva-server itself. Fr

Readme

@apteva/web-sdk

Browser SDK for calling apteva-server. Framework-agnostic. ~3 KB minified + gzipped.

  • Auth — session cookies, Authorization: Bearer, X-API-Key, ?api_key= query — pick any, the SDK handles them.
  • App dataclient.app("crm").get("/contacts") reverse-proxies through /api/apps/<name>/*.
  • App toolsclient.app("crm").tool("contacts_search", {…}) posts an MCP tools/call, unwraps the JSON-RPC envelope, returns the tool's natural shape.
  • Static-app mode — when the UI is hosted by apteva-server itself (runtime.kind: static), pickBaseURL and pickKioskKey read the <script>window.__APTEVA_APP__</script> block the server injects.

Install

bun add @apteva/web-sdk
# or
npm install @apteva/web-sdk

Quick start

import { AptevaClient } from "@apteva/web-sdk";

const apteva = new AptevaClient({
  baseURL: "https://agents.example.com",
  // Optional. Omit to rely on the session cookie (after auth.login).
  apiKey: process.env.NEXT_PUBLIC_APTEVA_KEY,
});

// Sign in (sets the session cookie on the apteva-server domain).
const me = await apteva.auth.login("[email protected]", "hunter2");

// Read from any installed app's HTTP routes.
const health = await apteva.app("flexylead").get("/healthz");

// Call an MCP tool — the envelope is unwrapped for you.
type Lead = { id: number; name: string; status: string };
const { leads } = await apteva.app("flexylead").tool<{ leads: Lead[] }>(
  "leads_list",
  { limit: 25 },
);

Auth carriers

The SDK sends credentials: "include" on every request, so a session cookie set by auth.login flows automatically — same-origin always, cross-origin when the apteva-server's CORS_ORIGIN allowlists your domain (the default permissive mode echoes any origin).

You can layer on an API key by setting apiKey in the constructor or calling client.setApiKey(...) at runtime. Bearer-wins-over-cookie on the wire.

const apteva = new AptevaClient({
  baseURL: "...",
  apiKey: "sk-...",     // optional
  onUnauthorized: () => router.push("/login"),  // fires once per 401
  timeoutMs: 30_000,    // default, 0 disables
});

App HTTP routes

client.app(name) returns a handle with get / post / put / patch / del that route to /api/apps/<name>/<path>. Same auth, same proxy, same error model.

await apteva.app("storage").post("/upload", { ... });
const file = await apteva.app("storage").get<File>("/files/42");

MCP tools

client.app(name).tool(toolName, args) does the round-trip through /api/apps/<name>/mcp:

const result = await apteva.app("tables").tool<{ rows: Row[]; total: number }>(
  "rows_search",
  { table: "leads", limit: 50 },
);

If the MCP call returns a JSON-RPC error, the SDK throws AptevaError(-1, message, code) — same catch block as HTTP errors.

For lower-level usage, client.callTool(appName, toolName, args) and the standalone unwrapMCP(envelope) are both exported.

Agents

client.agents wraps the core /api/agents/* routes — the running apteva-core child processes.

// Reads
const agents = await apteva.agents.list();              // Agent[]
const agent  = await apteva.agents.get(3);              // Agent
const status = await apteva.agents.status(3);           // AgentStatus — iteration, rate, model, paused, uptime…
const threads  = await apteva.agents.threads(3);        // Thread[]
const channels = await apteva.agents.channels(3);       // ChannelInfo[]
const history  = await apteva.agents.chatHistory(3, 50); // ChatHistoryMessage[]

// Lifecycle
await apteva.agents.start(3);        // spawn the process → updated Agent
await apteva.agents.stop(3);         // terminate          → updated Agent
await apteva.agents.restart(3);      // → { status: "restarted" }
await apteva.agents.togglePause(3);  // → { paused: boolean } — toggle, not a setter

togglePause is a toggle (the server has no separate resume endpoint) — check the returned .paused rather than assuming the new state.

Activity / telemetry

client.telemetry wraps /api/telemetry* — reads plus a live SSE feed.

// Filtered event read
const events = await apteva.telemetry.query({
  agentId: 3, type: "tool.call", limit: 100,
});

// Aggregates
const timeline = await apteva.telemetry.timeline(3, "24h"); // TimelineBucket[]
const stats    = await apteva.telemetry.stats(3, "24h");    // TelemetryStats

// Live feed — returns a handle, call .close() to stop
const sub = apteva.telemetry.stream(3, (event) => {
  console.log(event.type, event.data);
});
// later…
sub.close();

telemetry.stream normalizes a server quirk where event.data occasionally arrives as a JSON-stringified string instead of an object.

For any other SSE endpoint, client.subscribe(path, params, onEvent, opts?) is the generic form telemetry.stream is built on.

Node note: subscribe/stream need a global EventSource (browsers, Deno, Bun, Node 22+). On older Node, pass a polyfill via opts.EventSource.

Chat

client.chat wraps the built-in channel-chat app — one chat is bound to one agent, and the agent's reply streams back token-by-token.

// Pick an agent, get/create its chat, load history
const [agent] = await apteva.agents.list();
const chat = await apteva.chat.create(agent.id);
const history = await apteva.chat.messages(chat.id, { limit: 200 });

// Live feed — the SSE stream interleaves full message rows and token
// deltas; the SDK discriminates them for you.
const sub = apteva.chat.stream(chat.id, {
  since: history.at(-1)?.id ?? 0,
  onMessage: (m) => { /* full ChatMessage row — user | agent | system */ },
  onDelta:   (d) => { /* StreamFrame: append d.text until d.done */ },
});

// Send — posts the user message AND triggers the agent's reply
await apteva.chat.send(chat.id, "what's the status of order 4821?");

sub.close();

chat.send returns the persisted user message, so you can swap an optimistic bubble for the real row.

Rendering note: ChatMessage.components[] carries the agent's respond(components=…) attachments. The SDK hands you the data, but rendering app-provided UI components needs a dynamic-component loader the SDK doesn't include yet — third-party UIs render text + a placeholder for now.

A complete reference chat UI (message list, composer, streaming bubbles, optimistic send) lives in examples/dashboard/src/components/ChatCard.tsx.

Error handling

Every non-2xx response and every MCP error throws an AptevaError:

try {
  await apteva.app("crm").tool("contacts_get", { id: 999 });
} catch (err) {
  if (err instanceof AptevaError) {
    if (err.isUnauthorized()) ...      // 401
    if (err.isNotFound()) ...          // 404
    if (err.status === -1) ...         // MCP-level error
    console.error(err.body);
  }
}

Network failures (no DNS, refused connection) surface as AptevaError(0, "..."). Timeouts surface as AptevaError(0, "request timeout after Xms").

Hosted on apteva-server itself

If your UI is installed as a runtime.kind: static app, apteva-server injects a config block. The SDK picks it up:

import { AptevaClient, pickBaseURL, pickKioskKey } from "@apteva/web-sdk";

const apteva = new AptevaClient({
  baseURL: pickBaseURL(),     // window.__APTEVA_APP__.api_base
  apiKey:  pickKioskKey(),    // ?api_key=... > install config > undefined
});

The same code also works fine when hosted externally (Vercel, etc.) — pickBaseURL returns "" (same-origin) when nothing is injected, and pickKioskKey returns undefined (fall back to cookie auth).

SSE / EventSource

Browsers can't set headers on EventSource, so an API key has to ride as ?api_key=. The SDK has a helper:

const url = apteva.sseURL("/api/events", { project_id: "abc" });
// → https://agents.example.com/api/events?project_id=abc&api_key=sk-...
new EventSource(url);

TypeScript

Ships .d.ts types. client.app<DefaultShape>(name).get<R>(path) lets you type either per-call or per-app.

Example

A complete React 19 + Tailwind v4 dashboard that signs in, reads leads from the Tables app, and browses arbitrary tables lives in examples/dashboard/ in the repo. Run with:

cd examples/dashboard
bun install
API_BASE=http://localhost:5280 bun run build
bun run serve

License

MIT