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

@actionbox/sdk

v0.2.1

Published

TypeScript client for Actionbox durable human decisions

Readme

Actionbox Node/TypeScript SDK

Actionbox gives backend services a durable, server-authoritative way to ask a human for a decision and continue when that decision is available. This package is the typed Node.js/TypeScript client for creating, resolving, and waiting on Actions, plus managing source-scoped heartbeat Watches.

Documentation

Requirements

  • Node.js 18 or newer
  • An Actionbox Source API key, supplied through ACTIONBOX_API_KEY

Keep API keys and Watch capability URLs on trusted servers, workers, or CI jobs. Do not bundle this SDK or its credentials into a browser application.

Install

npm install @actionbox/sdk
import { ActionboxClient } from "@actionbox/sdk";

const client = new ActionboxClient({ apiKey: process.env.ACTIONBOX_API_KEY! });
const decision = await client.ask({
  title: "Deploy to production?",
  options: ["Approve", "Reject"],
  assignee_email: "[email protected]",
});
console.log(decision);

Use assignee_email for human-managed configuration. Actionbox resolves it only among active reviewers in the Source workspace. Stable integrations may use assignee_user_id instead; the ID is copyable from the Team settings page. Do not send both fields.

Team integrations can restrict discovery to several reviewers:

await client.create({
  title: "Approve emergency access",
  visibility: "restricted",
  reviewer_emails: ["[email protected]", "[email protected]"],
});

Use either reviewer_emails or reviewer_user_ids, with at most 15 members.

The SDK uses the hosted production API at https://api.actionbox.cloud by default. Customer integrations should use that default; the optional baseUrl override is reserved for maintainer-controlled test environments.

The SDK uses the same REST contract as the CLI and Python SDK. It does not own state; the API remains authoritative. The concise single-choice API returns the selected option ID as a string.

Typed interactions and responses

The SDK exports typed interaction and response unions matching the REST API. Use interaction with create or ask for boolean, text, numeric, multi-choice, or form responses:

import {
  ActionboxClient,
  BooleanInteraction,
  TypedResponse,
} from "@actionbox/sdk";

const client = new ActionboxClient({ apiKey: process.env.ACTIONBOX_API_KEY! });
const interaction: BooleanInteraction = {
  type: "boolean",
  label: "Deploy now?",
  true_label: "Deploy",
  false_label: "Hold",
};

const action = await client.create({
  title: "Deploy configuration",
  interaction,
});

const resolved = await client.resolve(action.id, {
  response: { type: "boolean", value: true },
  reason: "Approved by release manager",
});
const response: TypedResponse | null = resolved.response;
console.log(response); // { type: "boolean", value: true }

Supported interaction types are boolean, single_choice, multi_choice, text, integer, number, rating, and form. Form responses use { type: "form", values: { ... } }. Create inputs also accept bounded developer context blocks and an explicit typed on_expire fallback; omitting it returns expired without inventing a response.

resolve supports the typed request shape and a concise positional form for a single choice:

await client.resolve(action.id, { response: { type: "text", value: "ship" } });
await client.resolve(action.id, "approve", "Approved"); // single-choice shorthand

Action.interaction and Action.response expose the canonical typed wire values. options, option_id, and ask({ options: [...] }) are first-class single-choice conveniences.

Optional decision context

Keep simple Actions unchanged. For higher-impact reviews, add the generic structured context with a semantic helper:

import { deploymentDecisionContext } from "@actionbox/sdk";

const action = await client.create({
  title: "Deploy 2.18.0?",
  decision_class: "production_deployment",
  decision_context: deploymentDecisionContext({
    reason: "Release passed staging.",
    proposed_change: "Deploy 2.18.0 to production.",
    risk_level: "high",
    reversibility: "reversible",
    rollback_plan: "Restore the previous image.",
  }),
});

The generic, refund, database-change, and access-request helpers emit the same wire shape; they do not create server-side template types.

If action.context_request is present, a reviewer has asked for more detail. Update the same Action so the pending request clears and the reviewer sees the latest, version-bound context:

await client.update(action.id, {
  decision_context: deploymentDecisionContext({
    reason: "The reviewer requested the operational risk.",
    proposed_change: "Deploy 2.18.0 to production.",
    risk_level: "high",
    reversibility: "reversible",
    rollback_plan: "Restore the previous image.",
  }),
});

If the Source cannot truthfully supply it, close the request explicitly:

await client.markContextUnavailable(
  action.id,
  "Production customer data is not accessible to this worker.",
  "cannot_access",
);

Execution outcomes

After carrying out an approved operation, report its real result with the resolved Action's exact binding:

const outcome = await client.reportOutcome(resolved.id, {
  status: "success",
  duration_ms: 48_312,
  rollback: false,
  action_version: resolved.action_version!,
  fingerprint: resolved.fingerprint!,
});

Exact retries are safe; Actionbox rejects a conflicting second outcome.

Action Controls

Paid plans can attach a few secondary operations to an Action. A control does not answer or close the Action. It asks the Source to do something, such as retrying a job, while the reviewer keeps the original decision open.

import { ActionboxClient, control, link } from "@actionbox/sdk";

const action = await client.create({
  title: "Deployment failed",
  callback_url: "https://ci.example.com/actionbox",
  controls: [
    control("retry", "Retry deployment"),
    control("rollback", "Roll back", { destructive: true }),
    link("logs", "Open logs", "https://ci.example.com/runs/4821"),
  ],
});

The signed action.control_requested webhook includes a control_request_id. Report the operation state with the Source client:

await client.reportControlResult(controlRequestId, {
  status: "running",
  message: "Retry started",
});
await client.reportControlResult(controlRequestId, {
  status: "succeeded",
  message: "Deployment recovered",
});

Use failed when the operation does not complete. ActionBox records the result without resolving the parent Action. During staged rollout, the API may return CONTROLS_DISABLED until the feature is enabled for the paid workspace. ActionBox never runs an infrastructure command itself. Your callback handler maps each control key to an operation and reports the result. A later Watch heartbeat or meaningful Agent Run update can add independent recovery evidence. That evidence does not replace the Source's reported result.

Agent Runs

Group one agent task and its Actions with three small calls:

let run = await client.startRun({
  external_id: "checkout-fix-42",
  agent_name: "codex",
  title: "Fix checkout deadlock",
  stall_after_seconds: 900,
});
run = await client.progressRun(run, { stage: "tests", checkpoint: "test-184" });
const action = await client.create({
  title: "Approve staging migration",
  run_id: run.id,
  callback_url: "https://agent.example.com/actionbox",
  controls: [control("resume", "Resume run")],
});
run = await client.completeRun(run);

The SDK handles progress sequence numbers. Runs are optional; standalone Actions continue to work exactly as before. When stall_after_seconds is set, unchanged status/stage/checkpoint updates do not reset the timer. Actionbox creates one ordinary Action if progress stalls and resolves it when progress changes or the Run completes; waiting pauses the timer. Meaningful progress or completion also verifies the latest delivered control on an Action linked to that Run.

Heartbeat Watches

Source credentials can create and list Watches scoped to that Source. The raw heartbeat URL is returned only by creation:

import { sendHeartbeat } from "@actionbox/sdk";

const watch = await client.createWatch({
  name: "Nightly backup",
  schedule_type: "interval",
  interval_seconds: 3600,
  grace_seconds: 60,
  signal_method: "post",
});
await sendHeartbeat(watch.heartbeat_url, "start");

Watch creation automatically uses a secure idempotency key. If your application retries the whole call, pass the same key as the second argument:

await client.createWatch(input, { idempotencyKey: "watch-nightly-backup-v1" });

Use sendHeartbeat for ping, start, success, or fail; it always sends POST. signal_method: "post" prevents link previewers and security scanners from accidentally recording a heartbeat with GET. Source-scoped management helpers are available as pauseWatch, resumeWatch, rotateWatchToken, and archiveWatch; only create/rotate return a raw URL. Store capability URLs in a secret manager; Watch details and exports never return them. When Action Controls are enabled for a paid workspace, a Watch incident also offers Skip this occurrence and Pause monitoring. Skipping closes only the current incident and advances the Watch schedule. A later healthy signal can independently verify a delivered control request.

Verify decision receipts

Resolved Actions include an Ed25519-signed receipt. Fetch ActionBox's public key set and bind verification to the Action you expected:

const keys = await fetch(
  "https://api.actionbox.cloud/.well-known/actionbox-receipt-keys.json",
).then((response) => response.json());

const claims = await client.verifyReceipt(action.receipt, keys, {
  expectedActionId: action.id,
  expectedEnvironment: "live",
  expectedFingerprint: action.fingerprint,
  maxAgeSeconds: 300,
});

Cache the public key set according to its response headers and refresh it when verification encounters a new key ID. Receipt verification expects the live environment by default. Pass expectedEnvironment: null only when a tool deliberately accepts receipts from more than one environment. The verifier always validates the timezone-aware resolved_at timestamp and rejects receipts beyond the future-clock-skew tolerance. maxAgeSeconds is optional and adds an upper age limit when your workflow needs one.

License

MIT. See LICENSE.