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

@deus-hq/sdk

v0.10.2

Published

Deus SDK — run AI coding agents in isolated sandboxes

Readme

@deus-hq/sdk

TypeScript SDK for running AI coding agents (Claude) inside isolated cloud sandboxes.

Install

npm install @deus-hq/sdk

Environment variables

| Variable | Required | Description | |----------|----------|-------------| | DEUS_API_KEY | Yes | Your Deus API key (starts with deus_sk_) | | ANTHROPIC_API_KEY | Yes* | Anthropic API key for the agent (*unless org-level key configured) | | DEUS_BASE_URL | No | API base URL (defaults to https://api.deusmachine.ai) |

All can also be passed directly to configure() or as options on individual function calls.

Quick start

Level 1: run() — fire-and-forget

One async call. Provisions a sandbox, clones a repo, runs the agent, returns the result.

import { configure, Environment, run } from "@deus-hq/sdk";

configure({
  apiKey: process.env.DEUS_API_KEY,
  anthropicApiKey: process.env.ANTHROPIC_API_KEY,
});

const env = Environment.from("agnt-base")
  .repo("acme/backend");  // Also accepts github.com/acme/backend, full URLs, or SSH

const result = await run({
  task: "Add a health-check endpoint at GET /healthz",
  environment: env,
});

console.log(result.output);       // Agent's final response
console.log(`$${result.cost.usd}`); // Cost in USD
console.log(result.filesChanged);  // Files modified

RunResult

interface RunResult {
  id: string;             // Session ID
  workspaceId: string;    // Workspace ID
  output: string;         // Agent's final text output
  cost: Cost;             // { inputTokens, outputTokens, usd }
  turns: number;          // Number of agent turns
  duration: number;       // Wall-clock time in milliseconds
  filesChanged: string[]; // Files the agent modified
}

Level 2: stream() — runtime events

Pull-based async iteration over canonical runtime events. Supports multi-turn conversations.

import { configure, Environment, createWorkspace, createSession, stream } from "@deus-hq/sdk";

configure({
  apiKey: process.env.DEUS_API_KEY,
  anthropicApiKey: process.env.ANTHROPIC_API_KEY,
});

const env = Environment.from("agnt-base").repo("acme/backend");
const handle = await createWorkspace({ environment: env });
const session = await createSession({ workspaceId: handle.id });

for await (const event of stream(session, "Fix the failing tests")) {
  switch (event.type) {
    case "message.text.delta":
      process.stdout.write(event.delta);
      break;
    case "message.ended":
      console.log();
      break;
    case "message.part.updated":
      if (event.part.type === "TOOL") {
        console.log(`tool ${event.part.toolName}: ${event.part.state.status}`);
      }
      break;
    case "turn.started":
      console.log(`\n--- turn ${event.turnId} ---`);
      break;
    case "turn.ended":
      console.log(`Done — $${event.cost ?? 0}`);
      break;
    case "session.error":
      console.error(event.error.message);
      break;
  }
}

// Send a follow-up message on the same session
for await (const event of stream(session, "Now add integration tests")) {
  // ...
}

RuntimeEventStream helpers

stream() returns a RuntimeEventStream with additional methods:

const events = stream(session, "Add tests");

// Convert to Web ReadableStream (for SSE endpoints)
const readable = events.toReadableStream();

// Cancel the agent
events.abort();

Level 3: createSessionClient() — reactive UI

Push-based WebSocket client for browser frontends. Maintains full session state with subscribe/notify.

import { createSessionClient, createSessionToken } from "@deus-hq/sdk";

// Server-side: create a short-lived token (don't expose your API key to browsers)
const { token } = await createSessionToken(sessionId);

// Client-side: connect via WebSocket
const client = createSessionClient({
  sessionId,
  token,
  url: "https://api.deus.co",
});

client.subscribe((state) => {
  console.log(`Status: ${state.status}`);
  console.log(`Text: ${state.turn?.text}`);

  if (state.pendingQuestion) {
    client.answerQuestion(
      state.pendingQuestion.questionId,
      state.pendingQuestion.sessionId,
      ["yes"],
    );
  }

  if (state.pendingPermission) {
    client.respondToPermission(
      state.pendingPermission.requestId,
      state.pendingPermission.sessionId,
      { behavior: "allow" },
    );
  }
});

client.connect();
client.send("Fix the bug");

SessionState

interface SessionState {
  connected: boolean;
  status: "connecting" | "provisioning" | "ready" | "running" | "error";
  history: Message[];
  completedTurns: TurnState[];
  turn: TurnState | null;
  pendingQuestion: PendingQuestion | null;
  pendingPermission: PendingPermission | null;
  pendingHook: PendingHook | null;
  error: string | null;
}

Environments

Environments are persistent provisioning recipes. Define once — template, repo, packages, setup steps, secrets — and create many workspaces from it by ID reference. This eliminates repetitive configuration and enables a natural two-phase workflow:

  1. Define the environment (once, or when the build steps change)
  2. Create workspaces from it (repeatedly, with just an ID)

Why environments?

Without environments, every workspace creation requires the full config:

// Without environments: repeat the full config every time
const result1 = await run({
  task: "Fix the auth bug",
  environment: Environment.from("agnt-base")
    .repo("https://github.com/acme/backend")
    .packages(["postgresql-client"])
    .setup(["npm install", "npm run db:migrate"]),
});

const result2 = await run({
  task: "Add rate limiting",
  environment: Environment.from("agnt-base")
    .repo("https://github.com/acme/backend")
    .packages(["postgresql-client"])
    .setup(["npm install", "npm run db:migrate"]),  // duplicated!
});

With environments, define once and reference by ID:

// With environments: define once, reuse everywhere
const env = await createEnvironment({
  name: "backend-dev",
  environment: Environment.from("agnt-base")
    .repo("https://github.com/acme/backend")
    .packages(["postgresql-client"])
    .setup(["npm install", "npm run db:migrate"]),
});

const result1 = await run({ task: "Fix the auth bug", environmentId: env.id, user: "alice" });
const result2 = await run({ task: "Add rate limiting", environmentId: env.id, user: "alice" });

Creating an environment

Use the Environment.from() builder to define the provisioning recipe, then persist it with createEnvironment():

import { Environment, createEnvironment } from "@deus-hq/sdk";

const env = await createEnvironment({
  name: "backend-dev",
  environment: Environment.from("agnt-base")
    .repo("https://github.com/acme/backend", "main")
    .packages(["postgresql-client", "redis-tools"])
    .setup(["npm install"])
    .setup(["npm run db:migrate"])
    .env({ NODE_ENV: "development" })
    .browser(),
});

console.log(env.id);   // "019abc..."
console.log(env.name); // "backend-dev"

With auto-stored secrets

If you have the secret values at environment creation time, pass them via secrets. The backend stores them encrypted and scopes them to the new environment:

const env = await createEnvironment({
  name: "backend-dev",
  environment: Environment.from("agnt-base")
    .repo("https://github.com/acme/backend")
    .setup(["npm install"]),
  secrets: {
    GITHUB_TOKEN: "ghp_abc123...",
    NPM_TOKEN: "npm_xyz...",
  },
});
// Equivalent to creating the environment, then calling createSecret()
// for each key with { environmentIds: [env.id] }.

User-scoped environments

Environments can be scoped to a specific user. This is useful when end-users of your platform each have their own repos or configurations:

// Org-level template (no userId — created by the integrator)
const orgTemplate = await createEnvironment({
  name: "backend-dev",
  environment: Environment.from("agnt-base")
    .repo("https://github.com/acme/backend")
    .setup(["npm install"]),
});

await createSecret("ANTHROPIC_API_KEY", "sk-ant-...");

// User-scoped environment (scoped to alice)
const aliceEnv = await createEnvironment({
  name: "my-project",
  userId: "[email protected]",
  environment: Environment.from("agnt-base")
    .repo("https://github.com/alice/my-project")
    .setup(["pip install -r requirements.txt"]),
  secrets: {
    GITHUB_TOKEN: "ghp_abc123...",
  },
});

Using environments with run() and createWorkspace()

Once an environment exists, reference it by ID:

// One-shot run
const result = await run({
  task: "Fix the flaky test in auth.test.ts",
  environmentId: env.id,
  user: "[email protected]",
  anthropicApiKey: process.env.ANTHROPIC_API_KEY,
});

// Or with manual workspace lifecycle
const ws = await createWorkspace({
  environmentId: env.id,
  userId: "[email protected]",
});
const session = await createSession({ workspaceId: ws.id, user: "[email protected]" });
for await (const event of stream(session, "Refactor the auth module")) {
  // ...
}

environmentId and inline config (environment) are mutually exclusive — you must use one or the other, not both.

Per-workspace checkout

The environment owns the repo and its default branch. Each workspace can optionally override only the checkout target:

// Use the environment's configured branch, or the repo default branch
await createWorkspace({ environmentId: env.id });

// Checkout an existing branch or ref; provisioning fails if it cannot be resolved
await createWorkspace({
  environmentId: env.id,
  checkout: "feature/existing-branch",
});

// Create or reuse a task branch from a base branch or commit SHA
await createWorkspace({
  environmentId: env.id,
  checkout: {
    branch: "deus/fix-login",
    from: "main",
  },
});

The same checkout option is available on run() when it auto-creates a workspace:

await run({
  task: "Fix the login redirect",
  environmentId: env.id,
  checkout: { branch: "deus/fix-login", from: "main" },
});

Updating an environment

Name is immutable. Config can be updated:

import { updateEnvironment } from "@deus-hq/sdk";

await updateEnvironment(env.id, {
  config: {
    packages: ["postgresql-client", "redis-tools", "curl"],  // added curl
  },
});

Updates affect future workspaces only — existing running workspaces keep the config they were provisioned with (config is snapshotted at workspace creation time).

Listing environments

import { listEnvironments } from "@deus-hq/sdk";

// List org-level templates only
for await (const env of listEnvironments()) {
  console.log(`${env.name} (${env.id})`);
}

// List org templates + a specific user's environments
for await (const env of listEnvironments({ userId: "[email protected]" })) {
  console.log(`${env.name} — ${env.userId ?? "org-level"}`);
}

Environment builder reference

| Method | Description | |--------|-------------| | Environment.from(template) | Create from a template (e.g. "agnt-base") | | .repo(url, branch?) | Repository to clone, with optional branch | | .packages(list) | System packages to install via apt-get | | .setup(commands) | Sequential setup commands (post-clone) | | .setup(phase, commands) | Sequential setup commands in a specific phase | | .setupParallel(...branches) | Parallel branches (post-clone) | | .setupParallel(phase, ...branches) | Parallel branches in a specific phase | | .env(vars) | Environment variables for setup + agent | | .secrets(secrets) | Secret values to store encrypted and scope to the created environment | | .timeout(ms) | Sandbox timeout (30s-24h, default 1h) | | .metadata(data) | Arbitrary key-value metadata | | .lifecycle({ onTimeout }) | Sandbox lifecycle ("pause" or "kill") | | .browser(config?) | Enable agent-browser (headless Chrome) | | .user(userId) | Scope to a specific user |


Secrets

Secrets store sensitive values (API tokens, credentials) encrypted at rest. Secret names are the environment variable names injected into setup commands and the agent process. A secret can apply to every environment or only to specific environment IDs, so rotating a token takes effect on the next workspace without updating the environment.

Two scopes

  • Org-level secrets: Shared across all users in the organization. Typically platform keys like ANTHROPIC_API_KEY.
  • User-scoped secrets: Personal to a specific user. Typically personal access tokens like GITHUB_TOKEN.

When resolving secrets at workspace creation, environment-scoped secrets take priority over global secrets, and user secrets take priority over org secrets of the same name.

Storing secrets

import { createSecret } from "@deus-hq/sdk";

// Org-level secret (no userId)
await createSecret("ANTHROPIC_API_KEY", "sk-ant-...");

// User-scoped secret
await createSecret("GITHUB_TOKEN", "ghp_abc123...", {
  userId: "[email protected]",
});

// Environment-scoped secret
await createSecret("DATABASE_URL", "postgres://...", {
  environmentIds: [env.id],
});

createSecret is an upsert for the same key name, owner, and scope. This makes token rotation straightforward:

// Rotate a token — next workspace creation picks it up automatically
await createSecret("GITHUB_TOKEN", "ghp_NEW_TOKEN...", {
  userId: "[email protected]",
});

Listing secrets

Returns metadata only — secret values are never returned by the API.

import { listSecrets } from "@deus-hq/sdk";

// Org-level secrets
for await (const secret of listSecrets()) {
  console.log(secret.keyName, secret.updatedAt);
}

// User-scoped secrets
for await (const secret of listSecrets({ userId: "[email protected]" })) {
  console.log(secret.keyName, secret.updatedAt);
}

Deleting secrets

Delete by the stable id returned from listSecrets().

import { deleteSecret } from "@deus-hq/sdk";

await deleteSecret("sec_123");

Secret resolution at workspace creation

When a workspace is created from an environment, the backend resolves every secret that applies to that environment:

  1. User-owned secret scoped to the environment
  2. Org-owned secret scoped to the environment
  3. User-owned global secret
  4. Org-owned global secret
Environment: env_123, user: "[email protected]"
  |
  +-- "GITHUB_TOKEN" -> user secret scoped to env_123
  +-- "ANTHROPIC_API_KEY" -> org global secret
  |
  Result: { GITHUB_TOKEN: "ghp_...", ANTHROPIC_API_KEY: "sk-ant-..." }

Inline Anthropic API key

As an alternative to storing the Anthropic key as a secret, you can pass it inline on every run() or createWorkspace() call:

const result = await run({
  task: "Fix tests",
  environmentId: env.id,
  user: "[email protected]",
  anthropicApiKey: "sk-ant-...",  // bypasses secrets store
});

Typical integration pattern

A complete integration typically looks like this:

import {
  configure,
  createEnvironment,
  createSecret,
  Environment,
  run,
} from "@deus-hq/sdk";

// 1. Configure the SDK (once, at app startup)
configure({ apiKey: process.env.DEUS_API_KEY });

// 2. Store platform secrets (once, or via admin dashboard)
await createSecret("ANTHROPIC_API_KEY", process.env.ANTHROPIC_API_KEY!);

// 3. Define environments (once per project, or when build steps change)
const env = await createEnvironment({
  name: "acme-backend",
  environment: Environment.from("agnt-base")
    .repo("https://github.com/acme/backend")
    .packages(["postgresql-client"])
    .setup(["npm install", "npm run db:migrate"])
    .browser(),
});

// 4. When a user connects: store their personal token
async function onUserConnect(userId: string, githubToken: string) {
  await createSecret("GITHUB_TOKEN", githubToken, { userId });
}

// 5. When a user submits a task: run the agent
async function onTaskSubmit(userId: string, task: string) {
  const result = await run({
    task,
    environmentId: env.id,
    user: userId,
    onText: (delta) => sendToClient(userId, delta),
    onEvent: (event) => {
      if (event.type === "workspace.provisioning") sendStatus(userId, "Setting up...");
      if (event.type === "workspace.ready") sendStatus(userId, "Running agent...");
    },
  });
  return result;
}

Environment configuration (inline)

The Environment.from() builder can also be used inline with createWorkspace() or run() without persisting an environment:

const env = Environment.from("agnt-base")
  .repo("github.com/acme/backend", "main")
  .packages(["postgresql-client"])
  .setup(["npm install", "npm run db:migrate"])
  .env({ DATABASE_URL: "postgres://localhost:5432/test" })
  .timeout(600_000);

const result = await run({ task: "Run the test suite and fix failures", environment: env });

Setup phases

Setup commands run in two phases: pre-clone (before the repo is cloned) and post-clone (after — the default). Multiple .setup() calls accumulate in order.

const env = Environment.from("agnt-base")
  .repo("acme/backend")
  .setup("pre-clone", ["setup-credentials.sh"])   // runs before git clone
  .setup(["npm install"])                          // post-clone (default)
  .setup(["npm run db:migrate"]);                  // runs after npm install

Parallel setup

Use .setupParallel() to run independent branches concurrently. Each branch runs its commands sequentially; branches execute in parallel. The next call waits for all branches to finish.

const env = Environment.from("agnt-base")
  .repo("acme/backend")
  .setupParallel(
    ["npm install"],                    // branch 1
    ["pip install -r requirements.txt"], // branch 2 (runs concurrently)
  )
  .setup(["npm run db:migrate"]);       // waits for both branches

// With a phase
Environment.from("agnt-base")
  .setupParallel("pre-clone",
    ["setup-aws.sh"],
    ["setup-gcp.sh"],
  );

Private repositories

For private repos, store a GitHub token as a secret. The provisioning pipeline uses resolved GITHUB_TOKEN / GH_TOKEN values to configure git credentials before cloning:

// Store the user's GitHub PAT
await createSecret("GITHUB_TOKEN", "ghp_abc123...", { userId: "[email protected]" });

// Create the environment
const env = await createEnvironment({
  name: "private-backend",
  environment: Environment.from("agnt-base")
    .repo("https://github.com/acme/private-repo"),
});

// Workspace creation resolves the token and clones the private repo
const result = await run({
  task: "Fix the auth bug",
  environmentId: env.id,
  user: "[email protected]",
});

The repo URL is always clean (https://github.com/...) — tokens are never embedded in the URL.


MCP servers

Attach MCP tool servers to the agent:

const env = Environment.from("agnt-base").repo("github.com/acme/backend");

const result = await run({
  task: "Look up the latest issues and fix the top one",
  environment: env,
  mcpServers: {
    github: {
      command: "npx",
      args: ["-y", "@modelcontextprotocol/server-github"],
      env: { GITHUB_TOKEN: "ghp_..." },
    },
  },
});

Hooks

Intercept agent lifecycle events. Decision hooks (PreToolUse, Stop) block the agent until you respond:

const env = Environment.from("agnt-base").repo("github.com/acme/backend");

const result = await run({
  task: "Refactor the auth module",
  environment: env,
  hooks: {
    PreToolUse: (input) => {
      if (input.tool_name === "Bash" && input.tool_input?.command?.includes("rm")) {
        return { allow: false, reason: "Destructive commands are not allowed" };
      }
      return { allow: true };
    },
    Stop: (input) => {
      return { allow: true }; // Let the agent stop
    },
  },
});

Error handling

import { run, isDeusError, Environment } from "@deus-hq/sdk";

try {
  const env = Environment.from("agnt-base").repo("github.com/acme/backend");
  await run({ task: "Fix tests", environment: env });
} catch (err) {
  if (isDeusError(err)) {
    console.error(`[${err.code}] ${err.message}`);
    // err.code: "MISSING_API_KEY" | "API_ERROR" | "TIMEOUT" | "ABORTED"
    // err.statusCode: HTTP status (if from API)
  }
}

During streaming, errors arrive as events:

for await (const event of stream(session, "Fix tests")) {
  if (event.type === "session.error") {
    console.error(event.error.message);
    if (!event.recoverable) break;
  }
}

Workspace management

import {
  Environment,
  createWorkspace,
  listWorkspaces,
  stopWorkspace,
  pauseWorkspace,
  resumeWorkspace,
  deleteWorkspace,
} from "@deus-hq/sdk";

// Create from inline config
const env = Environment.from("agnt-base").repo("github.com/acme/backend");
const handle = await createWorkspace({ environment: env });

// Or from a persistent environment
const handle2 = await createWorkspace({
  environmentId: "019abc...",
  userId: "[email protected]",
});

// List, stop, pause, resume, delete
for await (const ws of listWorkspaces({ status: "RUNNING" })) {
  console.log(ws.id, ws.status);
}

await pauseWorkspace(handle.id);   // Suspend the VM (preserves state)
await resumeWorkspace(handle.id);  // Wake it back up
await stopWorkspace(handle.id);    // Shut down the VM
await deleteWorkspace(handle.id);  // Permanently remove

API reference

Configuration

| Function | Description | |----------|-------------| | configure(options) | Set global defaults (apiKey, baseUrl, anthropicApiKey, logLevel) |

Environments

| Function | Description | |----------|-------------| | createEnvironment(options) | Persist a provisioning recipe. Returns EnvironmentHandle | | updateEnvironment(id, options) | Update config (name is immutable) | | listEnvironments(options?) | Auto-paginating async generator of EnvironmentSummary |

Secrets

| Function | Description | |----------|-------------| | createSecret(keyName, value, options?) | Create or update a secret. Pass userId for user-scoped and environmentIds for environment-scoped | | updateSecret(keyName, value, options?) | Alias for createSecret | | listSecrets(options?) | Auto-paginating async generator of SecretMetadata (no values) | | deleteSecret(id, options?) | Delete a secret by ID |

Workspaces

| Function | Description | |----------|-------------| | createWorkspace(options) | Create workspace from environment or environmentId, with optional checkout. Returns WorkspaceHandle | | listWorkspaces(options?) | Auto-paginating async generator of WorkspaceSummary | | stopWorkspace(id, options?) | Shut down the sandbox VM | | pauseWorkspace(id, options?) | Suspend the sandbox VM (preserves state) | | resumeWorkspace(id, options?) | Resume a paused sandbox | | deleteWorkspace(id, options?) | Permanently remove workspace and snapshots |

Sessions

| Function | Description | |----------|-------------| | createSession(options) | Create a session in a workspace. Returns Session | | getSession(id, options?) | Get session detail with messages | | listSessions(options?) | Auto-paginating async generator of SessionSummary | | createSessionToken(id, options?) | Create a short-lived JWT for browser WebSocket connections |

Execution

| Function | Description | |----------|-------------| | run(options) | Fire-and-forget: auto-provisions, runs agent, returns RunResult | | stream(session, message, options?) | Returns RuntimeEventStream (async iterable of SessionRuntimeEvent) | | consume(eventStream, sessionId, workspaceId, options?) | Iterate with callbacks and hooks | | createSessionClient(options) | Push-based WebSocket client for browser UIs |

License

MIT