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

@pinkynrg/crew

v1.0.18

Published

Fan a named task across a group of local projects — run in parallel, open as one VSCode workspace, or hand the set to Claude Code. One persistent config, run via npx.

Readme

crew

Fan a named task out across a group of local projects — run it in parallel, open the group as one VSCode workspace, or hand the whole set to Claude Code. Driven by one persistent config. Runs via npx with nothing to install by hand.

crew is thin on purpose. It does not know what a task means — install, build, start are just strings forwarded to each project's own runner (make, npm, a shell script). crew owns the fan-out (parallelism, labelled output, exit-code aggregation, lifecycle); the project owns task semantics.

Install

Run with no install (npx needs the full scoped package name):

npx @pinkynrg/crew list

Or install globally — the command is crew once installed:

npm i -g @pinkynrg/crew
crew list

Requires Node >= 18 on a POSIX system (macOS or Linux), with code (VSCode CLI) and claude on your PATH. Zero runtime dependencies — crew is Node built-ins only, including its own parallel process runner.

Update

npm has no "upgrade one global" command — reinstall at @latest:

npm i -g @pinkynrg/crew@latest
crew --version                    # confirm

npx @pinkynrg/crew@latest … always fetches the newest without installing. Your ~/.config/crew/config.json is untouched by upgrades (and self-heals any dropped fields on first load of a newer version).

The three-tab workflow

The three surfaces are separate commands on purpose — each wants its own terminal / lifecycle. crew never spawns terminals; arrange the tabs (or aliases / npm scripts) yourself:

| Tab | Command | Owns | | --- | --- | --- | | 1 | crew start | the dev servers (streams until Ctrl-C) | | 2 | crew workspace | one multi-root VSCode window | | 3 | crew claude | an interactive Claude Code session |

Each opens a multiselect picker (preselected with your last pick) and remembers the selection, so tabs 2 and 3 open the same set you started.

Quick start

crew add          # wizard: create a project (run once per project)
crew install      # pick projects, install them (waits, reports pass/fail)
crew start        # pick projects to run locally (remembers your pick)
crew start env=qa # same, passing a placeholder value to the start task
crew workspace    # open the remembered set as one VSCode window
crew claude       # launch Claude Code over the remembered set
crew edit         # wizard: change a project later

Concepts

  • Projects are the only building block — there are no named groups. You choose a set of projects per run from an interactive multiselect (preselected with your last pick); projects are never named on the CLI.
  • The chosen set is remembered globally (machine-local local.json) and reused across start/install/workspace/claude — so crew workspace right after crew start opens the same set. crew list shows the current remembered selection.
  • Paths are ~-expanded and resolved relative to the current directory. Before any command acts, crew verifies each selected project's path exists and fails naming the offender.
  • Folder lists (workspace folders, claude --add-dir) are deduped by resolved absolute path, so a project selected twice is never listed twice.

The runner / tasks model

A task name becomes a command per project, with no duplication:

  1. project.tasks[<task>] if present — an explicit override.
  2. else project.runner with {task} substituted (e.g. make {task}make build).
  3. else the project is run-less for that task and is skipped (with a one-line note). Run-less projects still appear in workspace and claude.

If, after resolution, no project in the target can run the task, crew errors and runs nothing.

Example config

~/.config/crew/config.json:

{
  "version": 2,
  "workspaceName": "crew",
  "longRunning": ["start", "dev", "watch"],
  "projects": {
    "api": {
      "path": "~/code/api",
      "type": "backend",
      "runner": "make {task}"
    },
    "web": {
      "path": "~/code/web",
      "type": "frontend",
      "runner": "npm run {task}"
    },
    "worker": {
      "path": "~/code/worker",
      "type": "backend",
      "tasks": {
        "start": "AWS_PROFILE=pre_bee ./scripts/run.sh {env}"
      }
    },
    "docs": {
      "path": "~/code/docs",
      "type": "other"
    }
  }
}

Here api runs any task through make {task}, web through npm run {task}, worker has an explicit tasks.start override with an {env} placeholder, and docs is run-less (skipped for that task, kept for workspace/claude).

Placeholders & args (strict)

Resolved commands may contain {name} placeholders. {task} is filled automatically from the task name; {envfile} is filled by crew (see wiring below); everything else comes from your key=value args (key=value fills {key} by name). Projects are chosen in the picker — any bare command-line token is ignored (with a yellow warning).

Resolution rules:

  • every placeholder must be satisfied, else a red error lists the unresolved ones and nothing runs;
  • a key=value that matches no placeholder is skipped with a yellow warning (so crew start env=local still runs when nothing has an {env});
  • substituted values are shell-quoted, so spaces and metacharacters are safe.
crew start env=qa   # opens the picker, then fills {env} in each project's start

crew hardcodes no task names or values beyond the longRunning list — no baked-in local/pre/qa/pro vocabulary.

Dependency graph

crew graph derives a read-only dependency graph from each project's .envs/* files — who calls whom — with no manual edge list. It powers the connectivity check crew start does on a co-running set.

Give each project a match: the complete hostname(s) it's served under — exact strings, listing every env variant. An edge P → T is drawn when a URL in P's env files has a host equal to one of T's match strings.

"projects": {
  "api": {
    "path": "api", "runner": "make {task}",
    "match": ["api.example.com", "qa-api.example.com", "pre-api.example.com"]
  }
}
  • Exact match, so api.example.com matches only that host — never rge-api.example.com or vpc-…-api-….amazonaws.com. No globs, no collisions.
  • List each env variant (qa-, pre-, …); add new ones when envs appear.
  • A project with no match has no id, so nothing can point at it — crew graph flags it. crew derives nothing from folder/file/env names; the exact hosts are the whole rule.

When you crew start a set, crew warns if the selection isn't connected in this graph (crew graph restricted to the chosen projects) — i.e. you're running projects that won't actually talk to each other locally. It's a warning, not a block.

Two execution modes

The mode is decided by whether the task is in config.longRunning:

  • Long-running (start, dev, watch, …): parallel and streamed with labelled, per-project-colored output. Ctrl-C — or any one process exiting — tears the whole group down. crew owns the terminal and exits with an aggregate code.
  • Run-to-completion (install, build, test, …): parallel, but crew waits for all to finish (it does not kill the others when one finishes), then prints a per-project pass/fail summary and exits non-zero if any project failed.

How teardown works (and why it's reliable)

crew runs each command via /bin/sh -c in its own process group (spawn detached). On teardown it signals the whole group by pgid (kill(-pgid)) — SIGTERM, then SIGKILL after a grace period (CREW_KILL_GRACE_MS, default 5000ms). A second Ctrl-C force-kills immediately.

This is the key reason crew rolls its own runner instead of a ppid-walking tree-kill: reparented grandchildren — a dev server's autoreload child, a supervisord, anything that daemonizes — get orphaned to init and escape a ppid walk, leaving a port bound. A process-group signal reaches them regardless of reparenting. POSIX only (macOS + Linux).

Commands

Actions:

crew help                       usage (also: no args, -h, --help)
crew list                       list projects                      (alias: ls)
crew install                    pick projects, run their install task
crew start [args]               pick projects, run their start task (local wiring)
crew workspace [--fileless]     pick projects, open one VSCode window (alias: code)
crew claude                     pick projects, launch Claude Code once (--add-dir)
crew graph [project...]         dependency graph derived from .envs files

start/install/workspace/claude always open the interactive multiselect (preselected with your last pick); the selection is remembered globally.

Config:

crew add                               wizard: create a new project
crew edit [name]                       wizard: modify an existing project
crew remove <name>                     delete a project (confirm; -y) (alias: rm)
crew guards [project]                  list/manage guards (add/remove/link/unlink)
crew dir [path]                        show/set the projects dir (relative paths resolve here)
crew config [path|edit]                print merged config / its path / open in $EDITOR
crew pull <url>                        fetch config.json from a URL, install it (backs up current)

Global flags: --dry-run, --skip-guards, --config <path>, -y/--yes, -h/--help, -v/--version. Every acting command supports --dry-run to print what it would do without running.

Guards

A project can require named guards — preconditions verified before crew start/install does anything. crew stays agnostic: a guard is just a shell command, and it passes iff it exits 0. Guards live in a top-level registry and attach to projects many-to-many:

{
  "guards": {
    "aws": {
      "command": "aws sts get-caller-identity --profile pre_bee >/dev/null 2>&1",
      "message": "AWS SSO expired — run: aws sso login --profile pre_bee"
    },
    "vpn": {
      "command": "ifconfig | grep -qE 'inet (10\\.11\\.12\\.|172\\.27\\.)'",
      "message": "VPN not connected."
    }
  },
  "projects": {
    "backend":   { "path": "~/code/backend",   "type": "backend", "guards": ["aws"] },
    "orchestra": { "path": "~/code/orchestra",  "type": "backend", "guards": ["aws", "vpn"] }
  }
}

Before a run, crew collects the union of the target's guards, deduped by name — a guard shared by several projects runs once, not per project. All run in parallel; if any fails, crew prints each failure's message in red and aborts before anything starts:

guards: aws, vpn
  ✓ aws
  ✗ vpn: VPN not connected.
crew: guard failed — nothing started.

Bypass with --skip-guards. Guards only gate start/installworkspace and claude don't run them.

Managing guards

All wizard/select-driven — no hand-editing:

crew guards [project]    list guards (all, or just a project's), with which projects use each
crew guards add          wizard: name + command + failure message, then attach to projects
crew guards remove       pick a guard to delete (also detaches it from every project)
crew guards link         pick a guard, then toggle which projects use it (multi-select)
crew guards unlink       pick a guard, then pick linked projects to detach

You can also attach guards from the project side in crew edit <project> (the guards multi-select). Both sides write the same project.guards list.

The hidden workspace file

crew workspace generates the multi-root .code-workspace file inside crew's own config dir — not your project — at:

~/.config/crew/workspaces/<selection>.code-workspace

<selection> is the sorted member names joined — the same set produces the same file regardless of pick order. crew opens it with code <that file>. This keeps the workspace file invisible in your project explorer and out of git, while staying deterministic and reopenable: the file is regenerated on every invocation to reflect the current config, and code <file> focuses an existing window for that workspace instead of duplicating it.

--fileless is an alternative: it opens a new window on the first folder, then code --adds the rest as an in-memory Untitled Workspace (attaches to the last active instance; less deterministic).

Claude sessions (stable history)

Claude Code stores its per-directory history under ~/.claude/projects/<cwd-slug>/, keyed by the directory it's launched in. So crew claude launches Claude with a stable, crew-managed working directory per selection:

~/.config/crew/sessions/<selection>/

Every project is still passed via --add-dir, so the whole set is fully accessible. Because the cwd is fixed to the sorted set of names — not the first member — your Claude history for a given set is stable: picking the same projects in any order reuses the same history, and it never lives inside one project's folder.

Name the chat history with an optional session name: crew claude billing-work keeps history in ~/.config/crew/sessions/billing-work/. Omit it to get a name auto-derived from the selected projects. (It's a name, not a path — always kept under crew's sessions dir.)

Note: the working dir is a crew-owned folder, not a project checkout, so there's no cwd CLAUDE.md/git at the root — each project brings its own via --add-dir. Switching to this scheme starts history under the new stable path; any prior history under a project's slug isn't deleted, just no longer auto-loaded.

Config

  • User-level: ~/.config/crew/config.json (created on first write).
  • Project-local: a ./.crew.json in the current directory merges on top of the user config (its projects/guards override by name).
  • --config <path> points at a specific config file instead.

On load, a config with a missing or < 2 version is migrated to v2 in memory and written back. A v1 project's single start block becomes tasks.start.

Projects directory (shareable config)

A project's path can be relative — it resolves against a machine-local projects directory. Set it once per machine:

crew dir ~/Projects     # set it
crew dir                # show it

The projects dir is stored in local.json (next to the config — ~/.config/crew/local.json), never in config.json. That keeps config.json fully shareable. Project paths are then short and portable:

{
  "projects": {
    "backend":  { "path": "bee-beepro-backend", "type": "backend", "runner": "make {task}" },
    "frontend": { "path": "bee-beepro-frontend", "type": "frontend", "runner": "npm run {task}" }
  }
}

~…/absolute paths are still honoured as-is (escape hatch for a repo living outside the projects dir). A relative path with no projects dir set is a clear error pointing you at crew dir.

Sharing a config with your team

Because config.json never contains machine-specific data, it's directly committable:

  1. Keep projects/guards on relative paths (crew dir + crew add/edit do this).
  2. Commit config.json (in a repo, or git init inside ~/.config/crew). Gitignore local.json (and workspaces/, sessions/, tmp/) — those are machine-local/generated.
  3. A teammate installs it — clone/symlink to ~/.config/crew/config.json, or fetch it straight from the repo with crew pull <raw-url> (backs up any current config; a private repo needs a token/PAT in the URL). Then crew dir <their-path> once and everything resolves; no absolute paths are ever shared.

--config <path> works too: local.json is always read from beside the config file, so an isolated config keeps its own machine settings.

Known limitations (by design)

  • No task dependency graph and no ordering. crew fans out one task at a time. Need "install before start"? That's two commands typed in sequence. No caching, no build-system behavior — that's make / turbo / nx territory.
  • No startup ordering within a run. All projects start simultaneously; long-running services must tolerate their dependencies coming up in any order (retry / reconnect).
  • No up/bundler command, no terminal or pane spawning, no tmux, no self-built process manager, no health-check / wait-for-ready, no port-conflict detection, no plugin system, telemetry, or auto-update.

License

MIT