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

@thezzisu/droidnode

v0.150.1

Published

Run Factory's droid CLI on plain Node.js. Sidesteps the Bun 1.3.x standalone-init NULL-allocator race that crashes `droid --resume`, and Bun's long-session memory leaks.

Downloads

1,144

Readme

@thezzisu/droidnode

A Node.js-side launcher for Factory's droid CLI. It extracts the JS bundle from the official droid binary and runs it under plain Node — not Bun — to sidestep two long-standing Bun 1.3.x issues that hurt droid users on long sessions:

  1. NULL-allocator race in standalone init. droid --resume <id> of a large mission session crashes with Segmentation fault at address 0x0 — Bun's standalone fork/fanout pressure leaves bun.default_allocator with a NULL vtable, and the first hash.update(<long string>) call segfaults inside Allocator.rawAlloc. Backtrace decoded from bun.report:

    Allocator.zig:129   mem.Allocator.rawAlloc            ← NULL vtable.alloc
    array_list.zig:57   AlignedManaged.initCapacity
    unicode.zig:315     toUTF8AllocWithType
    encoding.zig:483    bun.js.webcore.encoding.constructFromU16
    encoding.zig:63     Bun__encoding__constructFromUTF16
    JSBuffer.cpp:551    WebCore::constructFromEncoding
    JSHash.cpp:182      Bun::jsHashProtoFuncUpdate        ← hash.update(<string>)
    <JS>                droid session-resume / digest pipeline

    Related upstream reports: oven-sh/bun#25798, oven-sh/bun#14254, anthropics/claude-code#17546. The race only fires inside Bun's standalone-executable init under spawn pressure; running the same JS bundle through a fresh Bun (or Node) process never enters that path.

  2. Bun memory growth on long-lived sessions. Mission-mode droid sessions running for hours accumulate RSS in Bun beyond what the bundle's logical heap explains. Node + V8 doesn't exhibit the same drift.

droid is © Factory AI. This repository ships zero proprietary code — no JS bundle, no native binaries, no assets. We depend on the official droid npm package as the sole source of truth and apply a small set of Node-compat patches at extraction time. All trademarks and copyrights belong to their respective owners.

Install

npm install -g @thezzisu/droidnode

Or run ad-hoc (downloads droid + @factory/cli- + koffi the first time):

npx -y @thezzisu/droidnode --resume <session-id>

Both forms transparently install the droid npm package as a dependency, so a separate npm install -g droid is not required.

Use

droidnode --version           # tracks the underlying droid version
droidnode --help              # full droid help (passthrough)
droidnode --resume <id>       # the case this exists for
droidnode --fork <id>         # forks
droidnode exec "do thing"     # non-interactive

Every droid flag/subcommand is passed through verbatim.

Introspection

droidnode --print-paths       # JSON: resolved droid binary, Node, shim dir, cache dir
droidnode --reextract         # wipe the cache for the current droid binary and re-extract

Environment

| Variable | Purpose | |---|---| | DROID_BIN | Override droid binary location | | DROIDNODE_VERBOSE | Print extraction progress on first run | | XDG_CACHE_HOME | Cache base (defaults to ~/.cache) |

Cache lives at $XDG_CACHE_HOME/droidnode/<binary-fingerprint>/. The key includes the droid binary's size + head/tail hash + the shim directory path, so multiple droid versions and multiple droidnode installs coexist without collisions.

How it works

  1. Resolve droid via require('droid/platform.js').getBinaryPathWithInfo() — the same logic Factory's own shim uses, picking @factory/cli-<platform>{,-baseline} based on the host CPU's AVX2 support.
  2. Locate the .bun ELF section by scanning for the /$bunfs/root/droid\0// @bun\n opener. Files inside are packed sequentially as <path>\0<content>; we walk the list by looking for \0/$bunfs/root/ boundaries.
  3. Extract every non-droid entry to <cache>/embedded/<basename> and chmod +x the natives (rg, librust_pty-*.so, agent-browser, install shell scripts).
  4. Apply the following patches to the JS bundle and write it as <cache>/droid.node.mjs:
    • Truncate at the last //# debugId=<hex>\n line. Bun appends raw Zstd sourcemap blobs past it that fail Node's JS parser on the embedded NULL bytes.
    • Replace every /$bunfs/root/ literal with the absolute path to <cache>/embedded/.
    • import.meta.requireglobalThis.__bunRequire (installed by bun-shim.mjs; Node has no import.meta.require).
    • "bun:ffi" → absolute path to src/shims/bun-ffi.mjs (koffi-backed dlopen for the PTY .so).
    • "bun:jsc" → absolute path to src/shims/bun-jsc.mjs (empty heapStats stub; droid only calls it inside try/catch).
    • from"ws" → absolute path to the ws package's entry (so the bundle's import X from "ws" resolves outside any node_modules tree).
    • this.server=Bun.serve(this.server=await Bun.serve( ×2. Our Bun.serve polyfill is async; Bun's original is sync.
  5. Spawn node --import bun-shim.mjs --enable-source-maps <cache>/droid.node.mjs <argv>. The shim preload:
    • Overrides process.execPath / argv0 / argv[0] so droid's self-spawn (subagent fanout, restart-after-update) re-enters us via basename(execPath).includes("droid").
    • Installs globalThis.Bun with subset of spawn/spawnSync/file/which/gc/connect/serve/fileURLToPath/version.
    • Implements Bun.serve over node:http + ws (the only thing droid daemon needs that Bun has and Node doesn't).
    • Routes import.meta.require through createRequire(import.meta.url), intercepting node-fetch and abort-controller to the Node 18+ / 15+ globals so neither package needs to be installed.
    • Aliases Buffer.SlowBuffer = Buffer (Node 22+ removed SlowBuffer; the bundled buffer-equal-constant-time still references it).

Subsequent invocations hit the cache and skip extraction.

Cross-version compatibility

The patch set is stable across the droid versions we've tested (0.135.1 through 0.140.0 — every published version we could install at the time). The CI smoke job runs extraction + --version on every push; the auto-track workflow re-runs it daily against the latest droid release before bumping our pin.

Despite the patches being stable, the dependencies.droid range is pinned to ^<latest-tested> so a stale install never silently runs against an untested droid major. The bot opens a new tag/release whenever upstream droid moves.

Automation

  • .github/workflows/ci.yml — runs scripts/smoke.js (extract + --version) on Node 24 and 26, on every push and PR.
  • .github/workflows/auto-track.yml — runs daily; if npm view droid version exceeds our package.json version, runs the smoke against the new droid, bumps both version and dependencies.droid, commits to main, tags v<version>, and creates a GitHub release.
  • .github/workflows/publish.yml — triggered on v*.*.* tag push (or manual dispatch). Publishes to npm via Trusted Publisher (OIDC) — no long-lived NPM_TOKEN, no static credentials. The workflow exchanges GitHub Actions' built-in OIDC token for an npm publish token at request time, and emits a provenance attestation so users can verify the published tarball was built from this exact commit.

One-time Trusted Publisher setup

  1. Sign in to npmjs.com as the publisher account (must have publish rights on @thezzisu).
  2. Go to Settings → Trusted Publishers, click Add Trusted Publisher.
  3. Fill in:
    • Publisher: GitHub Actions
    • Organization or username: thezzisu
    • Repository: droidnode
    • Workflow filename: publish.yml
    • Environment: leave blank (or pin to release if you create that environment for required reviewers)
  4. Save. From the next tag push, the publish workflow authenticates via OIDC — no secrets in the repo, no token rotation, and the published version page on npmjs.com shows a "Built and signed on GitHub Actions" badge linking back to the workflow run.

The very first publish of a new package name can be done either by manually dispatching publish.yml once the trusted publisher is configured (npm now supports OIDC-based namespace claims), or by a single manual npm publish from a maintainer's machine to register the name, after which OIDC takes over.

Platform support

Verified: Linux x64 (the configuration that hits the bug hardest, especially under WSL2). The extractor and shims are platform-agnostic; darwin-arm64 and darwin-x64 are wired in package.json — community testing welcome.

Windows is not supported in this release. droid's standalone uses a different process model on win32 and we haven't reproduced or fixed the same crash there.

Limitations

  • Subagent fanout in long missions hasn't been stress-tested end-to-end. Session restore + UI/Plan reload + cwd restoration are confirmed working.
  • droid update (the in-place updater) tries to overwrite its own binary. Through this wrapper it would target the resolved @factory/cli-*/bin/droid inside our node_modules; for npm-managed installs npm update -g @thezzisu/droidnode is the supported upgrade path.
  • keytar-backed credential storage uses droid's own Linux fallback when keytar can't load (the bundle wraps it in try/catch). The wrapper does not provide it separately.

License

Wrapper code (bin/, lib/, src/, scripts/, this README) is MIT — see LICENSE.

The droid CLI, its JS bundle, the @factory/cli-* binaries, and all related Factory AI intellectual property are not covered by this license and remain subject to Factory's own terms. This project does not redistribute any of it.

Acknowledgements

  • Factory AI — the actual CLI we're patching around.
  • The oven-sh/bun team — fixing this upstream eventually will retire this shim.