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

@projectpac/flow-router

v0.4.1

Published

Part of PAC: @projectpac/flow-router.

Readme

@projectpac/flow-router

A principal types one thing, and something has to decide which flow that is.

The only flow here that talks to no peer. It reads what the network is offering -- the skills board, the query board, and what this principal holds -- asks its own agent which of those the text is asking for, and writes the answer down.

It decides, and then starts what it decided. It still does not reach into another flow: a flow that did would be starting work with itself as the caller, and none of the caller-derived accounting PAC is built on would mean anything. What it does instead is call the route the chosen intent declared, through the control api -- which runs as the target flow's own handler, so the work is that flow's, exactly as if the principal had called it. Before this, a decision sat unstarted unless an interface had been written knowing the target by name, and the two nothing had been written for were decided about and dropped.

What it can route to is not a list in this file, and neither is how to start it. This is a flow service claiming ctx.router, and the choices are the intents the flows on this node declare into it. A routable flow declares from an optional child scope -- ctx.inject(["router"], (scoped) => scoped.router.intent({...})) -- so a node with no router still runs the flow, and one that gains a router later hears from it the moment the service comes up. Each intent also declares its own entry -- which of that flow's routes starts it, whether it needs a peer or an offer first, and what that flow's own body calls the pieces. So a flow somebody adds afterwards is routable and startable the moment it is installed, and one that is disabled stops being offered -- the registration is an effect of the flow's own scope -- without this flow or any interface knowing anything about either. The declaring plugin is derived, never named, so nothing can declare for another flow; a name is one flow's, and a node gets one fallback, with a second of either refused where it is declared.

An intent that needs a peer starts one per peer that speaks the flow. Every node that runs a flow advertises it under one kind, so who speaks it is one narrow question of discovery, and an empty answer is an answer: starting with whoever this node happens to know would put the guess back, a message nobody can answer. The decision named a kind of work, not a counterparty. That is right for work that is an offer anyone may take up; a flow where starting stakes something the principal owns wants its own guard against agreeing twice. GET /available is the same question asked for an interface -- what the peers say they speak, and whether this node has it -- which is what a principal wants to know before installing something.

The board is the part worth being careful about. Its rows come from a directory: not this node, not a peer this node chose, so a name or a description is text of unknown origin. It travels as the run's context, which the loop delimits and labels as data, and anything in it that could close a delimiter is neutralized first -- otherwise a hostile row would not so much inject a prompt as break every routing run this node ever does. The model's answer is then re-validated against the live board: a skill that is not on it cannot be picked.

| | | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------- | | service | ctx.router: intent(declaration), what a routable flow declares into | | routes | POST /prompts, GET /prompts, GET /board, GET /available, POST /prompts/:prompt/dispatched, POST /prompts/:prompt/failed | | record | refs: what a decision started, several when it fanned out over peers; kept through ctx.records | | payloads | none -- nothing crosses the wire | | background | the sweep, every 60s on an interval of its own, for a decision nothing ever dispatched | | needs | any source, to say what this principal holds; no operations, no tools | | intents | none: it is the thing that routes, not a thing routed to |

Running it

pnpm check       # includes tests/router.spec.ts: decisions over a live board
just demo        # headless: two nodes, everything routable installed, one scripted decision dispatched
just dev         # the lived-in network: panes, pages, the box -- then say one thing:
just prompt "find the spotify artists we both listen to"
just prompt "plan me a weekend in Lisbon"
just prompt "What does my Spotify music say about my taste? Be specific."
just a2-prompt "Name a playlist for late night driving, mostly synths"
just prompt "I want to trade my 1 log for 1 copper"

The router routes to what the node speaks, so its lane installs everything routable, after the record store every flow here keeps its records through: the skills flow with the sample library, the template's echo, joint search once the box pane answers, and the objects flow once the DON pane's object daemons do -- the one intent here that needs a peer rather than an offer, which is what makes the fan-out visible. With no MODEL the scripted executor always picks the fallback echo; real routing wants MODEL=pi MODEL_ARG="-p --model ..." just dev. devtools/README.md is the manual.

The sibling layout

Cross-repo dependencies are link: paths into checkouts beside this one -- nothing of PAC's is fetched from a registry. Clone the repos this one links (the link: values in the package manifests) into the same parent directory, run pnpm install, and everything resolves from source. The flow's whole-node suite lives in tests/, standing up real nodes from the sibling pac-node. The design repo (pac) is the front door: what PAC is, and how the repos fit together.