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

sootsim

v0.1.311

Published

sootsim CLI + vite/metro plugins + skills registry. bridge client for driving the proprietary sootsim-engine over WebSocket.

Readme

sootsim

The public, publishable face of SootSim: a CLI, two pairs of bundler plugins, a Detox/Maestro test surface, and a skills registry for driving a browser-native React Native simulator.

Overview

sootsim is everything you install to talk to a SootSim instance — it contains no rendering code. The canvas engine (CanvasKit renderer, Yoga layout, iOS/Android shell chrome, Electron shell) lives in the private sootsim-engine workspace package and is shipped at runtime as a versioned tarball, fetched from the contrast.dev CDN and unpacked into ~/.sootsim.

What this package gives you:

  • the sootsim CLI — the primary debugging and automation surface (inspect the UI tree, tap elements, capture flows, screenshot, record, run agents)
  • sootsim/vite and sootsim/metro — bundler plugins that serve the installed engine runtime from your own dev server
  • sootsim/jump-to-source-babel — an optional Babel plugin that annotates JSX with source locations so inspect mode can open the selected element in your editor
  • sootsim/detox — a drop-in Detox driver + jest preset
  • sootsim/sdk — the inspect/interact verbs as a programmatic API
  • sootsim/skills — a registry of Contrast-flavored automation skills

Platform status: iOS is the parity baseline. Android is bootstrapped and usable for focused conformance slices (bundle/runtime identity, device profiles, shell chrome, gesture + three-button nav, system UI/window metrics with cutout-safe insets) but is not full parity until the Android launch gates in the Contrast repo plan pass.

What's in this package (and what's not)

| in sootsim | in sootsim-engine (private, not published) | | --- | --- | | CLI, bridge client, drivers | CanvasKit renderer, Yoga layout | | bridge daemon host (SootSimBridgeHost) | iOS/Android shell chrome, home grid, app switcher | | vite/metro plugins, RN resolver, native stubs | Electron shell, wasm | | Detox/Maestro runners, screenshots, skills | the actual pixels |

The engine is delivered at runtime, never bundled into this package. A committed-but-unbuilt engine fix has no effect on a running sim until the runtime tarball is rebuilt and re-fetched.

Architecture

The architecture is a three-tier relay: a short-lived CLI process, a single persistent bridge daemon, and the sim itself — a browser/Electron page that internally splits into shell, compositor, and tenant workers.

graph TD
  subgraph cli["sootsim CLI process (short-lived, cli/)"]
    BIN["bin.ts (dispatcher + telemetry)"]
    SETUP["commands/setup.ts (guided daemon setup)"]
    RUNTIMECMD["commands/runtime.ts (runtime install/use)"]
    INSPECT["commands/inspect/* (describe/find/do/get/wait)"]
    FLOW["bridge-flow-runner.ts (Maestro YAML)"]
    DETOXCMD["commands/detox.ts + detox/ driver"]
    SHOTS["commands/screenshot + record"]
    AGENTCMD["commands/agent.ts"]
    WSB["ws-bridge.ts (WsBridge client)"]
    DRIVERS["drivers/* (chromium/electron/playwright/system)"]
  end
  subgraph daemon["bridge daemon process (persistent :7668)"]
    HOST["SootSimBridgeHost (host/bridge-host.ts, HTTP+WS)"]
    AGENTHOST["AgentHost (host/agent-host.ts, FIFO fan-out)"]
    AGENTSESS["agent-sessions.ts + attached-projects.ts"]
    SCAN["/__server-scan (dev-server-scanner.ts)"]
    PROXY["fetch-proxy-handler + websocket-proxy"]
    RTHTTP["runtime HTTP + self-update (runtime-delivery.ts)"]
  end
  subgraph sim["sim page (browser / Electron, launched by a driver)"]
    SHELLW["shell worker (iOS chrome, home grid, app switcher)"]
    COMPOSITORW["compositor worker (app canvases + independent rAF)"]
    TENANTW["tenant worker (guest RN app tree)"]
  end
  subgraph build["build-time plugins (src/)"]
    VITEONE["sootsim/vite = vite-plugin-one.ts (serve runtime at /__soot/)"]
    VITERN["sootsim() = vite-plugin.ts (RN resolver + native stubs)"]
    METRO["sootsim/metro = metro-plugin.ts"]
    COMPAT["@sootsim/compat stub-manifest (native seams)"]
    ENGINESHIM["sootsim-engine react-native shim"]
  end
  SKILLS["sootsim/skills registry.ts (builtin skills)"]
  subgraph fs["~/.sootsim (home-paths.ts)"]
    RTDIR["runtimes/<version>/ (engine assets)"]
    LOCK["daemon.json lockfile"]
    CFG["config.json"]
  end
  CDN["contrast.dev CDN (runtimes/manifest.json + tarballs)"]
  BIN --> SETUP
  BIN --> RUNTIMECMD
  BIN --> INSPECT
  BIN --> FLOW
  BIN --> DETOXCMD
  BIN --> SHOTS
  BIN --> AGENTCMD
  SETUP --> LOCK
  SETUP -->|"register launchd/systemd"| HOST
  RUNTIMECMD --> RTDIR
  RUNTIMECMD -->|"fetch runtime"| CDN
  INSPECT --> WSB
  FLOW --> WSB
  SHOTS --> WSB
  AGENTCMD --> WSB
  DETOXCMD --> DRIVERS
  DETOXCMD --> WSB
  WSB -->|"JSON WS cmds"| HOST
  HOST -->|"forward to registered sim"| SHELLW
  SHELLW -->|"replies + pushes"| HOST
  HOST -->|"relayed results"| WSB
  HOST --> AGENTHOST
  AGENTHOST --> AGENTSESS
  AGENTCMD --> AGENTHOST
  HOST --> SCAN
  HOST --> PROXY
  HOST --> RTHTTP
  RTHTTP --> RTDIR
  RTHTTP -->|"manifest + tarball"| CDN
  DRIVERS -->|"open sim page URL"| SHELLW
  SHELLW -->|"NativeUIRequest"| TENANTW
  VITEONE -->|"serve /__soot/ from"| RTDIR
  METRO -->|"serve /__soot/ from"| RTDIR
  VITEONE -.->|"launch electron app"| SHELLW
  VITERN -->|"alias react-native"| ENGINESHIM
  VITERN -->|"alias native pkgs"| COMPAT
  VITERN -->|"dev server hosts"| HOST
  SKILLS --> INSPECT

The CLI process (cli/bin.ts) parses argv, emits invocation telemetry, and lazy-imports exactly one command module per run. Bare sootsim and sootsim setup own guided one-time machine work: ask which package manager you use for app commands, explain repo-pinned Node versions, recommend a global sootsim install, and install the faster background daemon when it is missing. sootsim open ensures the engine runtime exists and starts a local foreground bridge when no daemon is reachable, then opens a WsBridge (cli/ws-bridge.ts) on port 7668 and sends JSON commands. Inspection/interaction verbs live in cli/commands/inspect/* and are re-exported as the programmatic sootsim/sdk; the Maestro runner is cli/bridge-flow-runner.ts; the Detox compat driver is in detox/. Test and screenshot paths additionally use cli/drivers/*. Browser sims launch in an isolated Playwright Chrome for Testing profile; Electron is the native desktop surface.

The daemon is one processSootSimBridgeHost (src/host/bridge-host.ts). sootsim serve runs it in the foreground; the background daemon is the same process wrapped with autostart and the ~/.sootsim/daemon.json lockfile. It is an HTTP+WS hub: sims register over WS, CLI clients connect to drive them, and the host forwards each command to the targeted sim and relays the reply back. It also serves the engine runtime over HTTP (with a self-update route), exposes /__server-scan to discover local metro/expo/vxrn/one dev servers, and proxies guest-app fetch/WebSocket traffic so the cross-origin tenant worker can reach localhost. The AgentHost extension owns one FIFO reader per agent session and fans agent events out to every subscriber.

The sim is a browser/Electron page launched by a CLI driver. Inside that page run three workers: the shell worker owns device and native state, the compositor worker paints every visible CanvasKit surface, and the tenant worker runs the guest RN tree and sends native-UI requests to the shell. The bridge always addresses the shell worker, never an undifferentiated sim.

Two distinct plugin families round it out, and they must not be conflated:

  • The published sootsim/vite (src/vite-plugin-one.ts, sootsimPlugin) and sootsim/metro plugins do one thing: serve the installed runtime at /__soot/ so an app's own dev server can host the sim shell (the vite plugin can also launch the Electron app).
  • The internal sootsim() plugin (src/vite-plugin.ts) is a separate, much larger RN-resolution / native-stub plugin: it aliases react-native to the engine shim and native packages to @sootsim/compat stubs, applies worklets transforms, and is the one that instantiates SootSimBridgeHost inside its own dev server. It is used to build the engine/shell and to load external RN apps — it is not the published sootsim/vite export.

Key components

| component | role | key files | | --- | --- | --- | | sootsim CLI (bin + dispatcher) | Short-lived terminal entry point. Parses argv, emits telemetry, lazy-imports one command per run, and routes guided setup/open/runtime/inspection flows without a hidden bootstrap path. | cli/bin.ts, cli/parse-args.ts, cli/help.ts, cli/commands/setup.ts, cli/commands/control.ts | | WsBridge client | Client side of the CLI→daemon link. Resolves the daemon port from the lockfile, opens a WS to :7668, sends JSON commands and awaits {id,result}/{id,error}. Used by every interactive command. | cli/ws-bridge.ts, src/bridge-constants.ts, cli/current-sim.ts | | inspect / SDK command surface | Runtime inspection + interaction verbs (describe, find, do tap, get errors, wait, layout, settle). Re-exported as sootsim/sdk so programmatic callers drive a sim the same way the CLI does. | cli/commands/inspect/core.ts, cli/commands/inspect/actions.ts, cli/commands/inspect.ts, src/sdk.ts | | Maestro / Detox runners | Drop-in test compatibility. bridge-flow-runner.ts runs Maestro YAML and recorded flows over the bridge; detox/ provides a by/element/expect/device driver + jest preset so existing Detox suites run unchanged. | cli/bridge-flow-runner.ts, cli/commands/maestro.ts, cli/commands/detox.ts, detox/index.ts, detox/jest-preset.cjs | | sim drivers | Launch or attach the actual sim page. Playwright owns isolated browser profiles; Electron owns the native desktop surface. The current sim is reused unless --new, --profile, or --ephemeral requests a separate one. | cli/drivers/index.ts, cli/drivers/registry.ts, cli/drivers/electron.ts, cli/drivers/playwright.ts | | SootSimBridgeHost (serve/daemon) | The bridge process (sootsim serve, or autostarted as the daemon via launchd/systemd). HTTP+WS hub on :7668: relays commands between CLI clients and registered sims; also serves runtime HTTP, /__server-scan, and fetch/WS proxies. | src/host/bridge-host.ts, cli/commands/serve.ts, cli/commands/daemon.ts, src/home-paths.ts | | AgentHost + sessions | Agent-routing extension of the host: owns the single FIFO reader per agent session and fans agent:event/session-status pushes to every WS subscriber. Backed by agent-sessions + attached-projects stores. | src/host/agent-host.ts, src/agent-sessions.ts, src/attached-projects.ts, src/agent-daemon-client.ts | | host proxies + dev-server scan | Host-side helpers so a cross-origin tenant worker can reach local dev servers and same-origin APIs: /__server-scan discovers running metro/expo/vxrn/one servers; fetch + websocket proxies relay guest-app network through the daemon. | src/host/fetch-proxy-handler.ts, src/host/websocket-proxy.ts, scripts/dev-server-scanner.ts, src/dev-bundle-resolution.ts | | runtime delivery + sootsim home | Versioned engine runtime management. The shared machinery owns manifest fetch, sha256 verification, channels, and auto-update. SootSim binds it to https://contrast.dev and ~/.sootsim/runtimes/<version>. A repo can select runtimeVersion without changing the machine default. | src/runtime-delivery.ts, packages/contrast-runtime-delivery/, src/home-paths.ts, src/runtime-assets.ts, scripts/postinstall.cjs | | sootsim/vite + sootsim/metro (runtime serving) | Published bundler plugins. Both serve the installed engine runtime at /__soot/ from ~/.sootsim/runtimes/<version> so an app's own dev server can host the sim shell; the vite plugin can also launch the Electron app. | src/vite-plugin-one.ts, src/metro-plugin.ts, src/runtime-assets.ts | | sootsim() vite resolver plugin (internal) | The large RN-resolution / native-stub vite plugin: aliases react-native to the engine shim, native packages to @sootsim/compat stubs, applies worklets/babel transforms, and instantiates SootSimBridgeHost in its dev server. Distinct from the published sootsim/vite export. | src/vite-plugin.ts, src/worklets-babel.ts, packages/compat/src/stub-manifest.ts | | skills registry | SootSim agent skills (sootsim-setup, sootsim-debug, sootsim-test, sootsim-visual, and the generated contrast index) installable with sootsim skill. | skills/*.md, src/skills/registry.ts, src/skills/types.ts |

Getting started

curl -fsSL https://sootsim.com/install.sh | sh
sootsim setup           # guided one-time machine setup
sootsim open 8081       # load a running metro/expo dev server
sootsim open /settings  # dispatch a React Native deep link into that app

The first sootsim open launches an isolated Chrome for Testing sim. Later opens reuse that sim and reload it at the requested target. Use sootsim do reload when the target has not changed. Reserve --new for a genuinely concurrent sim; repeated new browser trees consume memory and CPU on shared machines.

The public shell script is the recommended, inspectable bootstrap path; sootsim is the normal path afterward. Manual installs can use npm install --global sootsim, bun add --global sootsim, or pnpm add --global sootsim, followed by sootsim setup. There is no per-app install: start your Metro, Expo, or One dev server the way you already do, then open it from the CLI. There is no iOS native build in the inner loop; Metro alone is enough. sootsim open starts a local bridge when no daemon is reachable; the guided setup installs the faster background daemon so later CLI commands reuse one server. A desktop GUI (Electron) is optional on top, via sootsim desktop install. Native Electron windows created by sootsim open belong to that CLI session and close when the session exits; sootsim desktop keeps the persistent desktop-app lifecycle. In Electron, File > New Window duplicates the focused simulator, while File > New Simulator > opens a new window for a selected device (same device list as the Window > menu).

To uninstall the machine daemon and cached runtimes, run sootsim daemon uninstall. It stops current and legacy launchd / systemd services, removes daemon logs, and removes ~/.sootsim/ (runtimes, profiles, recordings, and lockfiles). It does not remove the optional /Applications/sootsim.app desktop bundle or macOS preference plist. To inspect existing disk usage before uninstalling, run sootsim cleanup; add --apply only after reviewing the preview.

Jump to source in inspect mode

Inspect mode can open a selected React Native element in your editor when the bundle includes source metadata. Add the optional Babel plugin to your app's Metro/Babel config:

plugins: [
  [
    'sootsim/jump-to-source-babel',
    {
      include: [__dirname],
    },
  ],
  'react-native-worklets/plugin',
]

The plugin adds an srcloc="/absolute/path/App.tsx:12:3" prop to JSX elements outside node_modules, dist, and build. Keep it before react-native-worklets/plugin in a custom Babel stack that lists both plugins. Apps using babel-preset-expo omit the explicit Worklets plugin because the preset configures it automatically.

CLI

The CLI is the primary debugging surface for SootSim — use it first for runtime inspection, interaction, animation debugging, shell tracing, screenshots, and flow capture, not just as a test runner.

sootsim list              # connected tabs
sootsim open 8081         # load a running metro/expo dev server
sootsim open /settings    # route via React Native Linking
sootsim describe          # dump UI tree
sootsim get errors 5      # recent runtime failures
sootsim compat            # scan native package compatibility
sootsim report-issue "…"  # preview an opt-in compatibility report
sootsim find --testid cta # inspect one node deeply
sootsim debug enable animated,layout
sootsim debug trace shell on 240
sootsim do tap-text "..." # interact
sootsim debug snapshot before
sootsim debug snapshot after
sootsim debug diff before after
sootsim maestro start     # begin a Maestro draft
sootsim maestro keep      # persist the last action
sootsim maestro end --output .maestro/login.yaml --validate
sootsim detox             # run Detox/Jest suites against sootsim

See the full command reference via sootsim --help or at src/features/site/docs/sootsim/cli/ in the Contrast repo.

Sim-scoped commands use the primary fallback only when one driveable sim is connected. With multiple driveable sims, the bridge lists their ids and refuses to choose. Run sootsim use <id> to pin later commands, or pass --sim <id> to target one command.

Bundler plugins (sootsim/vite and sootsim/metro)

These serve the installed engine runtime from your own dev server. They do not resolve React Native or stub native modules — that is the internal sootsim() plugin's job (see Architecture).

// vite.config.ts
import { sootsimPlugin } from 'sootsim/vite'

export default {
  plugins: [sootsimPlugin()],
}
// metro.config.js
const { withSootsim } = require('sootsim/metro')

module.exports = withSootsim({ /* your metro config */ })

Coming from Detox

SootSim ships a drop-in Detox driver. Existing Detox test files (import { by, element, expect, device } from 'detox') run against SootSim with no code changes — just add one line to your jest config:

// jest config
{ preset: 'sootsim/detox/jest-preset' }

Or use the CLI directly:

sootsim detox            # auto-discovers e2e/ tests and launches a shell
sootsim detox init       # scaffold a config + sample test

See ../../docs/migrating-from-detox.md for details.

Coming from Maestro

Maestro is SootSim's single YAML test surface. Point it at your existing .maestro/ directory, generate a test, or author one from live actions:

sootsim maestro                      # discover .maestro/ and run all flows
sootsim maestro test .maestro/       # explicit
sootsim maestro generate "verify login"
sootsim maestro --list-compat        # see the verb support matrix

Most Maestro verbs work out of the box (tapOn, assertVisible, inputText, scrollUntilVisible, launchApp, when:, repeat, runFlow, …). Verbs that need real device hardware (GPS, radio, photo library) throw a clear error. See ../../docs/migrating-from-maestro.md for the full compat matrix.

Screenshots and recordings

For animation-heavy debugging, the useful path is usually:

sootsim debug enable animated
sootsim debug trace shell on 240
sootsim debug snapshot before
# reproduce the transition
sootsim debug snapshot after
sootsim debug diff before after
sootsim screenshot --with-frame --output /tmp/sootsim-framed.png
sootsim record --duration 5 --output /tmp/sootsim-anim.mp4

sootsim screenshot --with-frame composes the real SootSim shell device chrome around the raw screen bitmap (reusing the shell bezel/button geometry, excluding the Electron top bar, rail gutter, and other window chrome). Flows can request the framed export inline without changing Maestro syntax:

sootsim wait ready and the screenshot readiness guard accept inspectable native content, a populated node tree, or a live-frame channel whose publish count advances across probes. Canvas-only GL, WebGPU, and video surfaces can therefore prove that they are painting without weakening the boot-card guard.

- takeScreenshot: hero
- takeScreenshot:
    path: marketing/hero
    withFrame: true

For plan-driven app-store exports, use sootsim screenshot appstore:

app: 8081
device: iphone-14

capture:
  flow: .maestro/capture-app-store-screenshots.yaml
  mode: raw+framed

compose:
  canvases: [iphone-6-9, iphone-6-1]
  background: cyan
  text:
    preset: bold-top
  slides:
    - id: splash-hero
      screenshot: apple/iphone/en/01-splash.png
      headline: First punch.
      subheadline: Every pick in one place.
sootsim screenshot appstore --plan .sootsim/app-store.yaml

The plan runner can reuse a visible sim for capture (--sim a9), stop after raw/framed intermediates (--capture-only), or rerender final marketing canvases from an existing raw directory (--compose-only). If your flow already writes screenshots to explicit project paths, set from: + pathMode: flow so sootsim screenshot appstore respects the flow's own takeScreenshot paths instead of prepending --screenshots <rawDir>:

capture:
  flow: .maestro/capture-app-store-screenshots.yaml
  from: ./apps/app-store-screenshots/public/screenshots/apple/iphone/en
  pathMode: flow

In the browser shell, Screenshot Mode turns the live shell into a simple DOM composition surface: the rail + mac menu bar disappear, the device shifts down with a short transition, and editable title/subtitle fields appear above the frame for quick art-direction passes.

Skills registry

sootsim skill install installs SootSim agent skills in the standard <skill-name>/SKILL.md layout used by Codex and Claude Code. The bundled skills cover setup, debugging/perf/accessibility, testing, visual review, and the generated contrast command index. The published CLI registry and generated website docs both come from packages/sootsim-skills/:

  • packages/sootsim/skills/contrast/SKILL.md is generated from that registry
  • src/features/site/docs/sootsim/cli/* is generated output, not hand-edited

When command docs or examples drift, update packages/sootsim-skills/ and run:

bun run generate:sootsim-docs

Runtime delivery (the sootsim home and CDN)

The engine never ships inside this package. runtime-delivery.ts names sootsim's CDN origin (default https://contrast.dev, overridable in config.json), its SOOTSIM_* env overrides, and its hosted paths, then hands them to the shared delivery machinery in @contrast/runtime-delivery — an internal workspace package the CLI bundles, so a published install has it inlined. That machinery fetches runtimes/manifest.json + the runtime tarball, verifies its sha256, and unpacks it under ~/.sootsim:

~/.sootsim/
├── runtimes/
│   ├── <active>/         unpacked engine assets (served at /__soot/)
│   └── <rollback>/       previous version for rollback
├── cache/                legacy downloads; empty after cleanup
├── profiles/             persistent named app storage, without browser caches
├── electron/userData/    desktop app storage, with bounded browser caches
├── daemon.json           lockfile: pid, ports, active runtime, heartbeat
├── automatic-cleanup-v1.json  completed legacy cleanup generation
└── config.json           user prefs: update channel, cdn origin override

The bridge daemon serves these assets over HTTP and exposes a self-update route; sootsim upgrade / sootsim runtime drive explicit version changes from the CLI side. Installation retains the active runtime plus one rollback and removes the downloaded archive after extraction. Disposable Playwright caches live in a bounded per-session temporary directory; Electron's persistent HTTP cache is disabled for simulator profiles and globally capped for app chrome. Cookies, localStorage, IndexedDB, service workers, recordings, and captured frames are user-owned data and are not removed by the default cleanup command.

Per-repo configuration is optional. When sootsim.config.ts sets runtimeVersion, sootsim open installs that version without changing the machine default and serves it from a version-specific localhost origin. Running from a monorepo root resolves the only React Native app from package workspaces, then shares that app root across config, font, and splash discovery. The fallback scan is bounded and prunes dependency, build, native, fixture, and example trees.

The first interactive CLI command after this cleanup generation finishes its requested work, then launches the same safe default plan in a detached maintenance process. The app and terminal command are usable before the legacy filesystem scan begins. The worker prints the reclaimed size to stderr, records completion, and does not run the scan again. Open browser profiles and a runtime served by a live daemon or development bridge are left untouched. The automatic pass retries on a later command after they close. The explicit sootsim cleanup --aggressive command remains the only path that removes the rollback runtime or user-created recordings.

Development and building

This package is part of the Contrast monorepo. To build the published CLI bundle:

bun run build:cli

The output is dist-cli/bin.js — a single esbuild output published as the sootsim npm bin. Library exports build to dist-lib/ (consumed via the exports map: ., ./vite, ./metro, ./sdk, ./skills, ./detox, and the host/agent helpers). bun run pack:smoke validates the publishable tarball.