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

probe-kit

v0.1.2

Published

Business-agnostic Playwright long-session probe toolkit. AI writes real step code; Probe Kit executes, keeps session, collects evidence, archives runs, writes bug docs.

Readme

Probe Kit

Business-agnostic probing library. Playwright is one built-in subject kind (the browser handle); Probe Kit is not limited to the browser.

AI writes real TypeScript step code; Probe Kit executes it, keeps a long-running session alive across steps, collects evidence, and archives the full probing process. The object being probed is called the subject and is exposed to every step as ctx.subject (a Handle).

Probe Kit does not model business state, project topology, worktrees, containers, branches, deployment environments, or concurrency policy. Those are external environment concerns expressed through project configuration, ports, env vars, data dirs, and filesystem paths.

The authoritative specification is doc/PROBE_KIT_SPEC.md. This README is a usage guide; when they disagree, the SPEC wins.


Core Concepts

| Concept | What it is | |---|---| | Probe | A probing-purpose container. Answers: "What are we investigating/validating?" Lives at .probe/<title>/. | | Session | One controller-backed long-running session under a Probe. Answers: "How do I reach the running subject?" Each probe start creates a new sessionId. A Probe may have many sessions over time. | | Step | One execution of AI-written TypeScript code against a Handle. Source lives in steps/; evidence lives in step-runs/<slug>/. | | Handle | The runtime handle to the probed subject (ctx.subject): browser / electron / process / none. Browser-only APIs (page, screenshot, ...) exist only on browser handles. | | Probe Kit | Stateless executor. It never knows about current, runId, worktrees, or business entities. |


Installation

pnpm add -D @probe/kit @playwright/test
# one-time browser install
pnpm exec playwright install chromium

Add a probe.config.ts at your project root (see below).


Quick Start

# 1. Create a Probe container (purpose template generated)
probe create --title workflow-trigger

# 2. Start one long session under it
probe start --probe workflow-trigger
# -> sessionId / sessionFile printed

# 3. Run steps against that session (AI writes steps/*.step.ts)
probe step --session-file .probe/workflow-trigger/sessions/<sessionId>.json \
  .probe/workflow-trigger/steps/001-smoke.step.ts

# 4. Inspect, then stop (session file is retained as history)
probe status --session-file .probe/workflow-trigger/sessions/<sessionId>.json
probe stop  --session-file .probe/workflow-trigger/sessions/<sessionId>.json

The normal probing loop is:

create probe -> start session -> run steps -> stop
            -> change code -> start NEW session -> run more steps

Directory Structure

.probe/<title>/
  probe.json                 probe metadata (title, project, status)
  purpose.md                 what we're investigating (template)
  sessions/
    <sessionId>.json         one session's runtime record (port/pid/baseURL)
  logs/
    <sessionId>/
      backend.log            backend process stdout/stderr
      frontend.log           frontend process stdout/stderr
      dev.log                electron dev server log
  browser/
    <sessionId>/
      storage-state.json     (optional) captured browser state
  steps/                     authored step SOURCE code
    001-smoke.step.ts
    002-open-dialog.step.ts
  step-runs/                 step EXECUTION evidence (probe-level, accumulates across sessions)
    001-smoke/
      step.ts                source snapshot
      result.json            pass/fail/duration/error
      screenshot.png         on success
      failure.png            on failure
      console.json
      network.json
      layout.json / aria.txt (when step calls captureLayout/captureAria)
  bugs/
    BUG-001.md               standard bug report
  summary.md                 probe-level aggregated report

CLI Reference

All commands take explicit arguments. There is no current probe.

| Command | Required args | Notes | |---|---|---| | probe create | --title <title> | Creates container + templates. Does not start anything. | | probe start | --probe <title> | New sessionId each run. --session-id/--port optional. | | probe step | --session-file <file> <stepFile> | --probe is not accepted (a probe may have many sessions). --slug optional. | | probe status | --session-file <file> | Controller status + Probe step-runs overview. | | probe stop | --session-file <file> | Stops controller; keeps session file (sets status: stopped). | | probe list | — | Lists probes under probesDir. |

Env: PROBE_ROOT (project root, default cwd).


Step Contract

Steps are real TypeScript, not a DSL.

import { expect } from "@playwright/test"
import type { ProbeStep } from "@probe/kit"

const step: ProbeStep = {
  id: "workflow/open-trigger-dialog",

  async run(ctx) {
    const { page } = ctx

    await page.goto(ctx.baseURL)
    await expect(page.getByRole("heading", { name: "devloop" })).toBeVisible()

    await page.getByRole("button", { name: "触发执行" }).first().click()
    await expect(page.getByRole("dialog")).toBeVisible()

    await ctx.screenshot("dialog.png")
    await ctx.captureLayout(["[role=dialog]", "input", "button"], "layout.json")
  },
}

export default step

StepContext provides: page / context / browser? / electronApp?, baseURL, probeDir, stepRunDir, and the evidence toolbox — screenshot / captureLayout / captureAria / writeJson / writeText / api / markBug.

step.id is a human-readable label and is decoupled from the evidence slug (derived from the filename, or overridden with --slug).


Project Configuration (probe.config.ts)

External projects express isolation entirely through config. Probe Kit never infers the isolation strategy.

Web project

import { defineProbeConfig, webApp } from "@probe/kit"

export default defineProbeConfig({
  name: "devloop",

  ports: {
    WEB_PORT: "auto",     // dynamic allocation
    SERVER_PORT: "auto",
  },

  vars: {
    DATA_DIR: "<probeDir>/data/<sessionId>",
  },

  target: webApp({
    baseURL: "http://127.0.0.1:<WEB_PORT>",
    processes: [
      {
        name: "backend",
        command: "pnpm --filter @devloop/server dev --port <SERVER_PORT>",
        readyUrl: "http://127.0.0.1:<SERVER_PORT>/api/ops/workflows",
        env: { DEVLOOP_DATA_DIR: "<DATA_DIR>" },
      },
      {
        name: "frontend",
        command: "pnpm --filter @devloop/web dev --port <WEB_PORT>",
        readyUrl: "http://127.0.0.1:<WEB_PORT>",
      },
    ],
  }),
})

Electron project

import { defineProbeConfig, electronApp } from "@probe/kit"

export default defineProbeConfig({
  name: "flashbox",

  ports: {
    RENDERER_PORT: "auto",
  },

  target: electronApp({
    appDir: "apps/host",
    devCommand: "pnpm --filter flashbox-host dev --port <RENDERER_PORT>",
    rendererReadyUrl: "http://127.0.0.1:<RENDERER_PORT>",
    userDataDir: "<probeDir>/userData/<sessionId>",
    env: {
      FLASHBOX_PROFILE: "development",
      FLASHBOX_SKIP_ONBOARDING: "1",
      FLASHBOX_USERDATA: "<probeDir>/userData/<sessionId>",
      APP_INSTANCE: "probe-<probeTitle>-<sessionId>",
    },
  }),
})

Template variables

Every config string supports <NAME> interpolation:

<probeTitle>   <probeDir>   <sessionId>   <rootDir>
<any port name, e.g. WEB_PORT>
<any var name, e.g. DATA_DIR>

Ports

ports: {
  WEB_PORT: "auto",      // dynamically allocated, written to session file
  SERVER_PORT: "3583",   // fixed (for reproducibility)
}

How isolation is built (external concerns)

Probe Kit only consumes rootDir + config + probeTitle + sessionId. Everything else is the external project's job:

worktree isolation      -> each worktree runs its own probe create/start/step/stop
container isolation     -> each container has its own PROBE_ROOT + probesDir
data isolation          -> DATA_DIR / userDataDir via <probeDir>/.../<sessionId>
port isolation          -> ports: { X: "auto" }
config isolation        -> different probe.config.ts per environment

Evidence Rules

Each step execution writes to step-runs/<slug>/. Required files:

step.ts        source snapshot of the executed step
result.json    { stepId, status, durationMs, error?, timestamp }
console.json   page console messages during this step
network.json   network requests during this step

On success: screenshot.png (recommended). On failure: failure.png + result.json.

Layout bugs must call captureLayout() — screenshot-only layout reports are insufficient. captureLayout records boundingBox + full computed style (display/visibility/position/zIndex/width/height/padding/margin/gap/flex/grid/overflow/fontSize/color/backgroundColor/border/...).


Bug Reports

ctx.markBug({ id, area, severity, reproduction, expected, actual, evidence? }) writes bugs/<id>.md with the standard template (Reproduction / Expected / Actual / Evidence / Root Cause / Fix / Regression Result). After fixing, rerun the same probe and update the bug's regressionResult to verified.


Invariants (non-negotiable)

  • No current probe. All commands take explicit args.
  • No runId. Containers are Probes (.probe/<title>/).
  • probe step requires --session-file (not --probe).
  • probe stop keeps the session file as history.
  • All Probe state lives inside .probe/<title>/.
  • External projects express isolation through config, never through Probe Kit domain logic.
  • No business names (flashbox, devloop, workflow, miniapp, ...) inside the kit.

Architecture

cli.ts                  command routing + process management (no Playwright objects)
controller.ts           the ONLY process holding Playwright objects; HTTP RPC (status/step/stop)
controller-client.ts    CLI-side RPC to the controller (reads explicit sessionFile)
config.ts               load probe.config.ts; port allocation; <NAME> template interpolation
artifact.ts             .probe/<title>/ structure; purpose template; summary
session-store.ts        read/write an explicit session file (no .probe/, no current)
step-runner.ts          jiti-import .step.ts; inject StepContext; archive source; collect evidence
evidence.ts             console/network/layout/aria collectors
bug-report.ts           standard bug markdown
launchers/web-app.ts    spawn backend/frontend; wait readyUrl; launch Chromium
launchers/electron-app.ts  spawn devCommand; wait rendererReadyUrl; launch Electron

The controller is a detached long-running process; the CLI just routes commands to it over local HTTP RPC. Step execution reuses the same browser page across steps within a session.