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

@pipedai/worker

v1.3.16

Published

PipedAI worker — registers with pipedai-api, polls for due runs, and invokes Claude with the trigger's MCP.

Readme

pipedai-worker

PipedAI worker — Node CLI that registers with pipedai-api, polls for runs assigned to this machine, and invokes claude -p with the trigger's MCP config. Distributed via npm as @pipedai/worker. Runs on Marolence-owned and BYOM (Bring-Your-Own-Machine) client hosts.

See ARCHITECTURE.md for the runtime model and CLAUDE.md for working guidelines.

Quick install (one-liner)

Once you have a wrk_ registration token from the dashboard's Settings → API Keys → Worker Registration tab:

npm i -g @pipedai/worker \
  && pipedai-worker register --token=wrk_… --api-url=https://api-beta.pipedai.app \
  && pipedai-worker run

For Claude Max mode, run claude login interactively first. For api-key mode, export ANTHROPIC_API_KEY instead and pass AUTH_MODE=api-key to register.

pipedai-worker doctor runs a diagnostic checklist (claude install, auth, network, file permissions) and prints actionable hints for any check that fails — start there if registration or run-loop misbehaves.

Windows (PowerShell / cmd)

Same npm i -g @pipedai/worker command — npm installs a pipedai-worker.cmd shim on %PATH%. Run from an elevated PowerShell or cmd window. Requires worker 1.3.6+ (older builds had two Windows-specific bugs: mkdir '' during register, and spawn claude ENOENT during run). The worker config lands under %USERPROFILE%\.pipedai\worker.json. claude login works the same way as on macOS / Linux.

Stack

  • Node 22 + TypeScript 5.9
  • commander for arg parsing
  • tsup bundles to a single executable dist/cli.js
  • zod for input validation
  • Vitest, ESLint, Prettier
  • mise pins Node 22

Local Setup

  1. Clone all three pipedai repos as siblings (pipedai-api, pipedai-website, pipedai-worker) under one parent directory.

  2. Install mise (or install Node 22 directly):

    brew install mise
    mise install        # picks up mise.toml -> node@22
  3. Install dependencies and build:

    npm install
    npm run build       # emits dist/cli.js with #!/usr/bin/env node shebang
  4. (One-time) install the binary globally so pipedai-worker is on $PATH:

    npm install -g .

    v2 will replace this with npm install -g @pipedai/worker from the public registry.

Subcommands

pipedai-worker register --token <wrk_…> --api-url <https://…> [--hostname <host>]
pipedai-worker run [--config <path>]
pipedai-worker doctor [--api-url <url>]
pipedai-worker version

register

Trades a wrk_ workspace registration token for a persistent wkt_ worker token, and writes the credentials to ~/.pipedai/worker.json (chmod 600).

AUTH_MODE=claude-max pipedai-worker register \
  --token wrk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
  --api-url https://pipedai-api-production.up.railway.app

AUTH_MODE accepts claude-max (default) or api-key. Use api-key on machines where Claude Code is configured with ANTHROPIC_API_KEY instead of a Max subscription.

Re-running register is idempotent — it preserves the same workerId and rotates the wkt_ token (useful if the local worker.json was lost or compromised).

Exit codes: 0 success, 1 API rejected (401/404/4xx), 2 local error (network unreachable or invalid AUTH_MODE).

Multiple environments on one host

To register the same machine against more than one environment (e.g. beta and production at once), pass --config <path> so each registration writes to its own file instead of overwriting the default:

pipedai-worker register \
  --token wrk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
  --api-url https://api-beta.pipedai.app \
  --config ~/.pipedai/worker-beta.json

pipedai-worker register \
  --token wrk_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy \
  --api-url https://api.pipedai.app \
  --config ~/.pipedai/worker-prod.json

Then start one run process per config (each its own poll loop, own CONCURRENCY budget, own service file). See "Running as a Service" below for the service-file pattern — copy the launchd / systemd template once per environment, change the Label / Description, and append --config ~/.pipedai/worker-<env>.json to the run command.

Pool worker registration (--pool)

Marolence-managed pool workers serve any workspace's usePool=true triggers via a shared global queue. Registration takes a pool_* token (admin-minted via POST /api/v1/admin/pool-workers/api-keys, platform-admin-only — wrk_* tokens are rejected by the pool endpoint, and vice versa).

pipedai-worker register \
  --pool \
  --token pool_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
  --api-url https://api.pipedai.app \
  --hostname studio-pool-1 \
  --config ~/.pipedai/worker.pool-1.json

PoolWorker.hostname is @unique on its own (no workspace disambiguation — pool workers are scopeless), so to register multiple pool workers on the same host you MUST pass distinct --hostname values (e.g. studio-pool-1, studio-pool-2, studio-pool-3). Each one needs its own --config path + its own LaunchAgent / systemd unit.

The post-register success message shows scope: pool (Marolence-managed) instead of workspace: … so it's obvious at a glance which kind of registration landed. Pool workers serve every workspace, not just one.

run

Reads ~/.pipedai/worker.json and enters the poll loop. Press Ctrl-C once to stop after the current run finishes; press it again to force-exit.

POLL_INTERVAL_MS=60000 pipedai-worker run

| Env | Default | Notes | |---|---|---| | POLL_INTERVAL_MS | 60000 | Floor 1000. Smaller values risk hot-spinning the API. |

Pre-flight check

Optional per-trigger HTTP gate the worker consults before invoking claude -p. The customer endpoint returns {run: false} to skip the Claude invocation entirely — useful for triggers whose work is event-conditional ("only run if there are new emails to process"). Saves tokens on cron triggers that fire on a fixed schedule but sometimes have nothing to do.

Configured per-trigger in the dashboard (Pre-flight URL + optional headers + optional HMAC signing secret + on-demand toggle). The worker reads the four fields from /poll alongside the existing MCP config.

Wire shape (worker → your endpoint):

POST {preflightUrl}
  X-Pipedai-Trigger-Id: trg_abc
  X-Pipedai-Run-Id:     run_xyz
  X-Pipedai-Signature:  sha256=<hex(hmac-sha256(rawBody, preflightSecret))>
  Content-Type:         application/json
  (+ any headers from preflightHeaders)

  {
    "triggerId":     "trg_abc",
    "runId":         "run_xyz",
    "scheduledAt":   "2026-05-27T15:00:00.000Z",
    "lastRunAt":     "2026-05-27T14:30:00.000Z",
    "lastRunStatus": "succeeded",
    "attempt":       1,
    "source":        "cron"
  }

Expected response:

  • 200 {"run": true} → worker proceeds to claude -p as normal.
  • 200 {"run": false, "reason": "no new emails"} → run lands as status='skipped' with the reason string surfaced in the dashboard's run-history detail. The worker NEVER spawns Claude.
  • Anything else (non-2xx, 30s timeout, unreachable, malformed JSON body, missing run field) → run lands as status='failed' with faultClass='gate_check_failed'. The api retries within the trigger's maxRetries budget the same way an infra fault would.

Tiny example (Node http — a real customer endpoint would probably check a database):

import http from 'http';
http.createServer((req, res) => {
  let body = '';
  req.on('data', (c) => (body += c));
  req.on('end', async () => {
    const ctx = JSON.parse(body);
    const hasWork = await checkDatabaseForPendingItems(ctx.triggerId);
    res.writeHead(200, { 'content-type': 'application/json' });
    res.end(JSON.stringify(
      hasWork ? { run: true } : { run: false, reason: 'nothing pending' }
    ));
  });
}).listen(3000);

Verifying the signature:

import { createHmac } from 'crypto';
function verify(rawBody, signatureHeader, secret) {
  const expected = `sha256=${createHmac('sha256', secret)
    .update(rawBody).digest('hex')}`;
  return expected === signatureHeader;
}

The HMAC is computed over the raw body bytes — verify BEFORE parsing JSON, since JSON.parse(rawBody) then JSON.stringify(...) can reorder keys and mismatch.

On-demand bypass: when an operator fires the trigger via the dashboard's "Run now" button (or POST /triggers/:id/run via API key), the worker skips the pre-flight check by default — on-demand means "I want this NOW". Customers who want gating on every run, including on-demand, flip the trigger's "Also gate on-demand runs" toggle.

30s timeout: hardcoded. If your gate logic can't decide in 30s, the answer should be pre-computed somewhere cheaper (e.g. a flag that a separate job sets when new work arrives).

Running as a Service

The worker is a long-running process — keep it alive with the host's service manager. Sample config files for the four common surfaces ship in templates/; copy and edit per host.

systemd (Linux)

sudo cp templates/systemd-pipedai-worker.service /etc/systemd/system/pipedai-worker.service
sudo $EDITOR /etc/systemd/system/pipedai-worker.service   # set User= and AUTH_MODE / ANTHROPIC_API_KEY
sudo systemctl daemon-reload
sudo systemctl enable --now pipedai-worker
journalctl -u pipedai-worker -f

The template uses KillSignal=SIGINT and TimeoutStopSec=120 so the worker has time to finish its in-flight run before systemd escalates.

launchd (macOS)

cp templates/launchd-com.marolence.pipedai-worker.plist \
   ~/Library/LaunchAgents/com.marolence.pipedai-worker.plist
$EDITOR ~/Library/LaunchAgents/com.marolence.pipedai-worker.plist  # set AUTH_MODE / ANTHROPIC_API_KEY
launchctl load -w ~/Library/LaunchAgents/com.marolence.pipedai-worker.plist
launchctl list | grep pipedai-worker
tail -f /tmp/pipedai-worker.out.log

nssm (Windows)

choco install nssm
copy templates\windows-nssm-install.bat .
notepad windows-nssm-install.bat   :: edit AUTH_MODE / paths
:: Open an Administrator command prompt and run:
windows-nssm-install.bat
nssm start PipedAIWorker

Docker / docker-compose

cp templates/Dockerfile templates/docker-compose.yml ./
ANTHROPIC_API_KEY=sk-... docker compose up -d
docker compose logs -f pipedai-worker

The compose file bind-mounts ~/.pipedai so the container reads the host's worker.json — register on the host first with pipedai-worker register --token=… --api-url=… before bringing the container up.

npm Scripts

| Script | Purpose | |---|---| | npm run dev | Run the CLI directly via tsx (no build). Useful for npx tsx src/cli.ts <subcommand>. | | npm run build | tsup bundle to dist/cli.js. | | npm start | node dist/cli.js. Equivalent to running the bin. | | npm run type-check | tsc --noEmit. | | npm run lint | ESLint over src/. | | npm run format | Prettier write-in-place. | | npm test | Run the Vitest suite once. | | npm run test:watch | Vitest watch mode. |

Multi-Repo Workflow

Phases, tasks, and tickets live in the Marolence Developer Portal MCP, not in this repo. There is no PLAN.md. See CLAUDE.md (managed centrally — don't hand-edit) for how to find your work, propose scope changes, and follow the commit conventions that drive task attribution and billing.

When a task spans repos, navigate to ../pipedai-api or ../pipedai-website and make changes there directly — they're sibling clones in the same parent directory.