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

@actantdb/box

v0.0.12

Published

Local-first ActantDB Box — Upstash-Box-shaped SDK for sandboxed agent workspaces (exec, files, git, schedules, snapshots, agent) backed entirely by ActantDB primitives.

Downloads

245

Readme

@actantdb/box

Local-first ActantDB Box. A sandboxed agent workspace with file, exec, git, schedule, and snapshot primitives — every action captured in a hash-chained ActantDB ledger.

The SDK surface is a 1:1 mirror of Upstash Box so porting is a one-line import change. The cloud control plane lands in a future release (see docs/CLOUD_ROADMAP.md); for now, mode: "cloud" resolves the contract but throws on every operation.

Install

npm install @actantdb/box

No Rust toolchain, no Docker, no exposed ports.

Quickstart

import { Box } from "@actantdb/box";

const box = await Box.create({ name: "my-workspace" });

// Files
await box.files.write({ path: "hello.txt", content: "world" });
const back = await box.files.read("hello.txt"); // → "world"

// Exec
const run = await box.exec.command("ls -la");
console.log(run.result); // { exit: 0, output: "...", stderr: "" }

// Stream exec
for await (const chunk of box.exec.stream("npm test")) {
  if (chunk.type === "stdout") process.stdout.write(chunk.line + "\n");
}

// Git
await box.git.clone({ repo: "https://github.com/you/repo.git" });
await box.git.commit({ message: "feat: new thing", authorEmail: "[email protected]" });

// Schedules (no extra deps — internal setInterval scheduler)
await box.schedule.exec({ everyMs: 60_000, command: "git pull" });

// Snapshot the workspace
const snap = await box.snapshot({ name: "before-experiment" });

// ... later, restore into a brand-new box:
const replica = await Box.fromSnapshot(snap.id);

await box.delete();

Migrating from @upstash/box

- import { Box } from "@upstash/box";
+ import { Box } from "@actantdb/box";

  const box = await Box.create({
    name: "demo",
-   apiKey: process.env.UPSTASH_BOX_KEY,   // ignored in local mode
  });

That's it. Every Upstash-Box method has the same shape in this package. The difference is what it costs and where it runs:

| Concern | @upstash/box | @actantdb/box (local) | | --------------------- | ------------------------ | ------------------------- | | Pricing | $/CPU-hr | free | | Network required | yes | no | | Audit log | server-side trace | hash-chained local ledger | | Replay with overrides | n/a | @actantdb/replay | | Policy engine | n/a | @actantdb/policy |

When mode: "cloud" lands, the contract is already in place — the call site above doesn't change.

API reference

Box.create(config?) / Box.get(id) / Box.getByName(name) / Box.list()

const box = await Box.create({
  name: "demo",
  mode: "local",        // default. "cloud" throws on every method.
  agent: myAgent,       // optional — needed for box.agent.run.
  storeRoot: "/tmp/x",  // override ~/.actantdb/boxes.
  cwd: "src",           // initial workspace-relative cwd.
  model: "claude-opus", // display-only.
  initCommand: "git clone ...", // optional one-shot at create.
  keepAlive: true,
});

Box.list() walks ~/.actantdb/boxes/*/box.json and returns BoxData[]. Box.fromSnapshot(snapshotId, config) creates a fresh Box whose workspace is hydrated from a saved snapshot.

box.agent.run({ prompt, responseSchema?, timeout?, policy?, autoApprove? })

Wraps the user-supplied agent with @actantdb/mastra::withActant. Records the full timeline (agent_run_started, user_message_received, model_call, tool_call_*, guard_verdict, approval_*, agent_run_finished) into the box's ledger. Returns a Run.

box.agent.stream(...) yields AgentChunk values:

type AgentChunk =
  | { type: "text-delta"; text: string }
  | { type: "tool-call"; toolName: string; input: unknown }
  | { type: "tool-result"; toolName: string; result: unknown }
  | { type: "finish"; result: unknown };

If your agent exposes a stream() function, we pass through; otherwise we synthesize a single finish chunk from generate().

box.exec.command(cmd, opts?) / box.exec.stream(cmd, opts?)

Spawns a subprocess inside the workspace via node:child_process. Buffers output, persists tool_call_completed (with exit code + stdout + stderr) and an effect_observed{ kind: "exec_completed" } event.

const run = await box.exec.command("npm run build", { timeoutMs: 60_000 });
if (run.status !== "ok") console.error(run.result);

Streaming yields line-buffered ExecChunk values:

for await (const c of box.exec.stream("yarn install")) {
  if (c.type === "stdout") process.stdout.write(c.line + "\n");
  if (c.type === "stderr") process.stderr.write(c.line + "\n");
  if (c.type === "exit") console.log("done", c.code);
}

box.files.write / read / list / upload / download

await box.files.write({ path: "src/index.ts", content: "export {};" });
const text = await box.files.read("src/index.ts");
const entries = await box.files.list("src");
await box.files.upload([{ path: "/host/secret.env", destination: ".env" }]);
await box.files.download({ folder: "/tmp/exported-box" });

Every path is resolved relative to box.cwd and refused if it escapes the workspace.

box.git.*

await box.git.clone({ repo: "...", branch: "main" });
const diff = await box.git.diff();
const status = await box.git.status();          // { branch, ahead, behind, files, clean }
await box.git.updateConfig({ userName: "Alice", userEmail: "a@x" });
await box.git.commit({ message: "wip" });
await box.git.push({ branch: "main" });
const pr = await box.git.createPR({ title: "Wire up X", body: "..." });
if (!pr.submitted) console.log("gh missing, run by hand:", pr.command);
await box.git.checkout({ branch: "feat/y", create: true });
await box.git.exec({ args: ["log", "--oneline", "-n", "5"] });

box.schedule.*

Zero-dep scheduler. Persists to <workspace>/.actantdb/schedules.json so Box.get(...) resurrects timers on process restart.

const s = await box.schedule.exec({ everyMs: 30_000, command: "git pull" });
// or:
await box.schedule.agent({ cron: "*/5 * * * *", prompt: "review the queue" });

await box.schedule.pause(s.id);
await box.schedule.resume(s.id);
await box.schedule.delete(s.id);

Cron strings parse the common forms (*/N * * * *, 0 */N * * *, 0 0 */N * *); anything else falls back to 60s. Prefer everyMs for precision.

box.snapshot({ name? }) / box.listSnapshots() / box.deleteSnapshot(id) / Box.fromSnapshot(id, config?)

Snapshots are a deep copy of the workspace dir (the per-box ledger is recreated on restore, intentionally). Local snapshots live under <storeRoot>/.snapshots/<id>/.

const snap = await box.snapshot({ name: "before-experiment" });
const replica = await Box.fromSnapshot(snap.id, { name: "replica" });

Lifecycle

await box.pause();   // stops schedules, marks status=paused.
await box.resume();
await box.delete();  // closes ledger, removes the box dir.
await box.cd("src"); // workspace-relative.
box.cwd;             // current workspace-relative path.
box.modelConfig;     // { harness: "local", model }.
await box.configureModel("claude-opus");
box.keepAlive = false;

Run

Returned by exec / agent methods.

run.id;            // ledger run id
run.status;        // "pending" | "running" | "ok" | "error" | "cancelled"
run.result;        // tool output (exec) or model output (agent)
run.cost;          // { inputTokens: 0, outputTokens: 0, computeMs, totalUsd: 0 }
await run.cancel();
run.logs();        // ActantEvent[] for this run

Local mode never infers token counts. inputTokens/outputTokens are always 0; computeMs is measured. The cloud surface will populate the rest.

Errors

Every error thrown from the public API is a BoxError:

import { BoxError } from "@actantdb/box";

try {
  await box.files.read("missing");
} catch (err) {
  if (err instanceof BoxError && err.code === "not_found") { /* ... */ }
}

Codes: not_found, already_exists, io_error, exec_failed, git_failed, schedule_not_found, snapshot_not_found, invalid_argument, cloud_unsupported, deleted.

Cloud mode (Phase 2)

Box.create({ mode: "cloud" }) resolves to a Box whose every operation throws cloud_unsupported. The contract is here so consumer code is portable the day the control plane lands. See docs/CLOUD_ROADMAP.md.

License

Apache-2.0.