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

synrgcoflow

v1.3.0

Published

The CoFlow subscription-runner daemon that executes agent inference jobs through the operator's local Claude CLI subscription

Readme

FlowForge Subscription Runner Daemon

The subscription runner daemon (runner/) is a self-hosted Node process that runs on the operator's own machine. It polls the FlowForge server for inference jobs, executes them through the operator's logged-in claude CLI (using the operator's Anthropic subscription), and posts results back — so the compute cost is subscription-billed rather than API-billed.

Architecture

FlowForge server (Railway)
  └─ agent_runner_jobs table
       ↑ POST /api/agent-runner/jobs/:id/result
       └─ GET /api/agent-runner/jobs  (long-poll, ≤30s)
              ↓
       runner/index.js   ← this daemon
              ↓
       claude CLI  (on the operator's box, logged-in subscription)

Concurrency: one job per daemon process. Run multiple daemon processes to handle parallel jobs (each independently claims via FOR UPDATE SKIP LOCKED on the server).

Tool execution: v1 performs pure inference. The payload.tools field is accepted and passed through but tools are not executed locally in this version.

Requirements

  • Node ≥ 20 (uses global fetch and modern built-ins). The repo pins Node 24 — match that version for consistency.
  • claude CLI installed and authenticated. Install via:
    npm install -g @anthropic-ai/claude-code
    Then authenticate using the interactive login (subscription, NOT an API key):
    claude
    # Follow the /login prompt — use "Claude.ai login" to authenticate with your subscription.
    Verify authentication:
    claude --print "hello" --output-format json
    The daemon explicitly removes ANTHROPIC_API_KEY from the child process environment so the CLI always routes through your subscription even if the host environment has that key set.
  • Zero additional npm dependencies. The daemon uses only Node built-ins and the global fetch API.

Environment Variables

| Variable | Required | Default | Description | |---|---|---|---| | FF_RUNNER_BASE_URL | Yes | — | FlowForge server base URL, e.g. https://app.flowforge.ai | | FF_RUNNER_TOKEN | Yes | — | 64-hex raw Bearer token issued at runner registration (one-time display) | | FF_RUNNER_POLL_IDLE_MS | No | 1000 | Milliseconds to wait after a 204 (idle) before re-polling | | FF_RUNNER_CLAUDE_BIN | No | claude | Path or binary name of the claude CLI | | FF_RUNNER_POLL_ERROR_BACKOFF_MS | No | 1000 | Base backoff (ms) when a poll/claim attempt throws; doubles on each consecutive error | | FF_RUNNER_POLL_ERROR_BACKOFF_MAX_MS | No | 30000 | Maximum backoff (ms) between error retries; prevents thundering-herd during server outages |

Resilience Guarantees

Idempotent result ack: postResult retries on transient network errors and 5xx responses (up to 5 attempts, capped exponential backoff: 1s → 2s → 4s → 8s → 16s). If the server committed the result but the HTTP response was lost, the retry hits the server's idempotent-ack path (200 { ok: true, idempotent: true }) rather than a 403 — so the job is confirmed landed without triggering a second inference run.

Poll error backoff: when claimJob throws (server down, network error), the daemon waits FF_RUNNER_POLL_ERROR_BACKOFF_MS × 2^(n-1) ms before the next attempt (capped at FF_RUNNER_POLL_ERROR_BACKOFF_MAX_MS). The backoff resets to base on the next successful poll (200 job claimed or 204 idle).

End-to-End Live Round-Trip

Step 1 — Register the runner in FlowForge

In the FlowForge app, navigate to Settings → Runners (or use the API):

curl -X POST https://app.flowforge.ai/api/companies/<companyId>/runners \
  -H "Cookie: <session>" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-macbook"}'

The response includes a token field — copy it now, it is shown only once:

{ "id": "...", "name": "my-macbook", "status": "active", "token": "<rawToken>" }

Step 2 — Start the daemon on the runner box

No clone or install step needed — npx fetches the published package on demand.

# Authenticate the claude CLI first (if not already logged in)
claude

# Start the daemon
FF_RUNNER_BASE_URL=https://app.flowforge.ai \
FF_RUNNER_TOKEN=<rawToken> \
npx -y synrgcoflow

You will see structured JSON log lines on stdout:

{"ts":"...","event":"runner.daemon.started","baseUrl":"https://app.flowforge.ai","concurrency":1}
{"ts":"...","event":"runner.poll.idle"}
{"ts":"...","event":"runner.poll.claimed","jobId":"...","agentId":"..."}
{"ts":"...","event":"runner.infer.started","jobId":"...","model":"claude-opus-4-5"}
{"ts":"...","event":"runner.infer.completed","jobId":"...","durationMs":4200,"inputTokens":450,"outputTokens":212,"costCents":0}
{"ts":"...","event":"runner.result.posted","jobId":"...","outcome":"ok"}

Step 3 — Assign an agent to use the subscription runner

In FlowForge, open Agent Detail for any agent and set:

  • Adapter type: subscription_runner
  • Model ID: (optional) e.g. claude-opus-4-5

Step 4 — Trigger a run

Start a chat or trigger a chimera run using that agent. Watch:

SELECT id, status, claimed_at, completed_at
FROM agent_runner_jobs
ORDER BY created_at DESC
LIMIT 5;

The job transitions pending → claimed → completed and the output appears in the FlowForge chat/run view.

Persistent Auto-Start (install / uninstall / status)

By default the daemon runs only while its shell process is alive. To make it auto-start on login AND auto-restart on crash — surviving reboot, logout, and sleep, with no re-run required — install it as an OS-native user service:

FF_RUNNER_BASE_URL=https://app.flowforge.ai \
FF_RUNNER_TOKEN=<rawToken> \
npx synrgcoflow install

If either env var is absent, install prompts for it interactively. Nothing about the token is ever printed back — it is written only into the generated service-unit file, which is chmod'd 0600 (owner read/write only) immediately after creation.

macOS: writes a LaunchAgent (~/Library/LaunchAgents/com.synrgcoflow.runner.plist) with RunAtLoad + KeepAlive.SuccessfulExit=false, then loads it via launchctl bootstrap. Logs go to ~/Library/Logs/synrgcoflow/runner.{out,err}.log.

Linux: writes a systemd --user unit (~/.config/systemd/user/synrgcoflow-runner.service) with Restart=always, enables + starts it via systemctl --user enable --now, and runs loginctl enable-linger <user> so the service keeps running after a full logout (systemd --user instances otherwise stop when the last session ends).

Other platforms: install/uninstall/status print a "not yet supported" message and exit — the daemon still runs fine via the manual npx -y synrgcoflow path documented above.

npx synrgcoflow status      # print the current service state
npx synrgcoflow uninstall   # stop + remove the service unit

The install/uninstall/status subcommands run without requiring FF_RUNNER_BASE_URL/FF_RUNNER_TOKEN to already be set in the environment — only the default (no-argument) daemon path requires them.

Fencing Token (lock_token) Echo

When the server includes a lock_token field alongside a claimed job — on either the WS runner_job push frame or the HTTP GET /api/agent-runner/jobs claim response — the daemon captures it and echoes it back verbatim as lock_token in the POST /api/agent-runner/jobs/:id/result body (both the success and cancelled-result paths). This lets the server's result-write verify the runner still holds the claim lock before committing. If a job carries no lock_token (older server), the field is omitted from the POST entirely — the daemon never invents a token client-side.

Graceful Shutdown

Send SIGINT (Ctrl+C) or SIGTERM to the daemon process. It finishes the in-flight job (if any), then exits 0. In-flight jobs that were interrupted before result posting are reset by the server's 5-minute reaper so they can be retried.

Logs

The daemon writes one JSON line per event to stdout. Pipe to any log aggregator:

FF_RUNNER_BASE_URL=... FF_RUNNER_TOKEN=... npx -y synrgcoflow | tee -a runner.log