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

@pstdio/pocketcoder-sdk

v0.4.0

Published

Runtime-validated TypeScript client for the PocketCoder control plane.

Readme

@pstdio/pocketcoder-sdk

The Node ESM SDK for the PocketCoder control plane. It ships built JavaScript and declarations, so consumers do not need Bun, a TypeScript loader, or access to PocketCoder's private workspace packages.

Requires Node 22.19 or newer. Install it with Bun:

bun add @pstdio/pocketcoder-sdk

Use the SDK from a trusted backend. Do not put a PocketCoder API key in browser code or inside a workspace. The SDK uses platform fetch, combines its timeout with caller cancellation, validates successful and error responses, and provides typed pagination. Retryable transport failures and 408/425/429/5xx responses are retried only for reads or mutations carrying an Idempotency-Key. Set the bounded retry count with maxRetries (default 2).

import { PocketCoderClient } from "@pstdio/pocketcoder-sdk";

const client = new PocketCoderClient({
  baseUrl: "https://pocketcoder.example.com",
  apiKey: process.env.POCKETCODER_KEY!,
});

for await (const workspace of client.workspaces.all({ state: "ready" })) {
  console.log(workspace.id, workspace.agent_state);
}

const terminal = client.terminals.connect("workspace-id");
terminal.onMessage((message) => {
  if (message.type === "output") process.stdout.write(Buffer.from(message.data_b64, "base64"));
});

client.terminals.connect(id, { sessionId }) opens or reattaches an authenticated terminal WebSocket. client.terminals.list(id) reads the metadata-only terminal audit history.

Use client.raw(path, init) for endpoints not yet represented by a typed resource client. API failures throw PocketCoderError; expired or deleted conversation history throws the more specific ConversationGoneError.

Resolve one user turn after preservation

Create one long-lived WorkspaceTurnResolver in a trusted backend. Call it only when handling a user turn. A ready workspace is returned unchanged. A preserved workspace is resumed through your callback, checked against the source checkpoint, and returned only after it becomes ready.

import {
  PocketCoderClient,
  WorkspaceTurnResolver,
} from "@pstdio/pocketcoder-sdk";

const client = new PocketCoderClient({
  baseUrl: process.env.POCKETCODER_URL!,
  apiKey: process.env.POCKETCODER_KEY!,
});

const resolver = new WorkspaceTurnResolver({
  client,
  resumeWorkspace: async ({ source, attemptId, signal }) => {
    // Mint fresh workspace-scoped input. It must expire with the new workspace.
    const launchInput = await issueWorkspaceBootstrap({ signal });
    const result = await client.workspaces.resume(
      source.id,
      {
        external_id: `turn-${attemptId}`,
        launch_input: launchInput,
      },
      attemptId,
      { signal },
    );
    return result.workspace;
  },
});

export async function handleTurn(sourceWorkspaceId: string, prompt: string) {
  const { workspace } = await resolver.resolve(sourceWorkspaceId);
  await client.agent.sendMessage(workspace.id, { content: prompt });
  return { workspace_id: workspace.id };
}

The key needs workspaces:read; a callback that calls resume() also needs workspaces:restore. Prompt relay and attachments need services:relay and attachments:write respectively.

The resolver stores no A-to-B mapping. Return the resolved id through your normal request or session state if a later request needs it. Canceling one caller stops that caller's wait. It does not roll back a resume request already accepted by the server, and another caller may join the same in-memory attempt. Lifecycle failures use fixed messages and never expose callback errors or launch input.