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

pi-for-each

v0.1.2

Published

pi extension: a $each@ prompt-loop editor feature with variable insertion over directory children or file lines.

Readme

pi-for-each

A pi extension that adds a /for-$each prompt loop – and hides it from your LLM! Supports children-in-directory and line-in-files iteration.

Why

Like subagents but much simpler, sequential and with more control for the user. Instead of describing the loop to the agent, just make a loop. No need to tell the agent about your control structure if you already know the control structure. Each iteration the LLM only sees the necessary context and the iteration prompt, no other iterations. This prevents bias drift or context rot compared to a loop that repeats commands and execution within the same context.

Demo

What it does

Compose a message that contains a $each@<path> token (dollar + each + at-sign + a file or directory path), then invoke it as the /for command:

/for Please reword the skill in $each@./skills/ and make it more polite

While composing the message, typing $each@ opens pi's fuzzy file/directory search (the same one @ after a space opens) so you can pick a path. The token becomes $each@<file-or-dir>.

When the /for command runs, it executes a prompt loop:

  • Directory — if the path points to a directory, the loop iterates over all of its child elements (files and subdirectories, excluding dotfiles). Each iteration replaces the full $each@<dir> token with <dir>/<child> (directories keep a trailing slash), e.g. $each@./skills/./skills/karate/.
  • File (lines) — if the path points to a file, the loop iterates over every line of the file. Each iteration replaces the full $each@<file> token with the corresponding line.

Iterations run strictly sequentially (no parallelism). The first iteration is sent as a normal message into the current (origin) session. Every following iteration forks the session into a new session file so the previous iteration's message is replaced while the earlier conversation context is preserved, then sends the next replacement.

While the loop runs, a hint is shown in the same region the UI normally uses for queued messages, e.g.:

for-loop · 2/5 · directory
↳ ./skills/baking

Example

Given two subdirectories karate and baking inside ./skills/:

User:  Have a look at the readme
Pi:    (acknowledges)
User:  /for Please reword the skill in $each@./skills/ and make it more polite

This expands to:

User:  Please reword the skill in ./skills/karate/ and make it more polite
       ... (wait for answer) ...
       fork → replace last message (new session file)
User:  Please reword the skill in ./skills/baking/ and make it more polite

The origin session keeps the first iteration; each subsequent iteration lives in its own forked session file, so the session list shows one session per iteration.

Design notes

  • This is driven by a real registered command, /for. The fork semantic requires ctx.fork(), which is only available on ExtensionCommandContext (the context passed to command handlers) — ExtensionContext (used by the input/session_start handlers) does not expose it. So the loop runs inside the /for command handler, where cmdCtx.fork() is available.
  • The submitted message must start with /for so that pi's command pipeline routes it to the handler. A bare $each@<path> message submitted as a normal message is not a command and is intercepted with a hint to use /for.
  • The $each@ fuzzy search reuses pi's built-in fuzzy file/directory provider (CombinedAutocompleteProvider.getFuzzyFileSuggestions) via a wrapping AutocompleteProvider, with a readdir fallback. The editor is extended so that typing $each@ opens that search exactly as @ after a space does.
  • The full token — starting at the dollar sign and including each, @ and the path, up to (but not including) the first following whitespace — is replaced by the iteration value. Not just the path.

Install

pi install npm:pi-for-each

To try it without installing (from a clone):

pi -e ./index.ts

License

MIT