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

sprintforge

v0.1.0

Published

SprintForge command line interface — sprint planning and Sprint Accelerator from the terminal.

Readme

sprintforge

The SprintForge command line interface — sprint planning, the Sprint Accelerator and agentic repository setup from the terminal.

npm install -g sprintforge
sprintforge --help

Command surface

The grammar is noun → verb, like gh. sprintforge --help lists every command of the shipped surface; the ones whose slice has not landed yet say so and point at the issue that adds them.

| Area | Commands | |---|---| | Identity | login, logout, whoami | | Agentic repo setup | init, sync, doctor [--check] | | Sprint Accelerator | queue add\|rm\|ls, watch add\|rm\|ls, night-shift run\|trigger\|schedule\|setup\|seat, trigger now, runs ls\|view\|follow | | Telemetry | telemetry (reserved; usage telemetry is coming) |

--json

Every command takes --json and then prints exactly one JSON document on stdout — no progress lines, no prompts:

{ "schemaVersion": 1, "ok": true, "command": "telemetry", "data": { "status": "coming" } }
{ "schemaVersion": 1, "ok": false, "command": "nope",
  "error": { "code": "unknown_command", "message": "Unknown command 'nope'.", "hint": "Run 'sprintforge --help'." } }

schemaVersion only changes when the envelope changes shape. error.code is stable; error.message is for humans.

Global flags: --json, --help/-h, --version/-v, and --team <name> (see Team resolution).

| Exit code | Meaning | |---|---| | 0 | success | | 1 | the command ran and failed | | 2 | usage — unknown command, missing or unknown subcommand, unknown flag | | 3 | a known command whose slice has not landed yet | | 4 | this CLI is older than the minimum version SprintForge supports |

init — prepare a repository for agentic work

sprintforge init                      # this repository, the Standard preset, Claude Code
sprintforge init --preset=minimal     # minimal | standard | full
sprintforge init --tool-folders=keep  # answer the migrate/keep question up front
sprintforge init --root=../app        # another checkout
sprintforge init --non-interactive    # no prompts: previous answers, else defaults

init is a conversation, so it needs a terminal: with --json alone it refuses instead of hanging on a prompt, and points at --non-interactive. What happens, in order:

  1. Preflight. You must be signed in. init shows the current branch and the state of the working tree and asks you to create a clean branch yourself — it never creates or switches a branch, and it stops right there if you answer no. It then checks that your agent CLI and gh are available and scans the repository: stacks, deployable units, an existing rulebook, persona and skill folders, docs, templates, workflows and a previous decision record.
  2. Selections. The preset — Minimal (rulebook, Definition of Done, templates, docs layout, decision record), Standard (the default: Minimal plus feature-designer, one developer persona per detected stack, reviewer, security-reviewer and the process skills) or Full (Standard plus the design skills, tdd-engineer, handoff and parallel dispatching) — then a fine-tune list where every catalogue item can be added with +<item> or a line number and dropped with -<item>. The business and marketing personas are never in a preset; the fine-tune list is the only way to add one. If the repository keeps persona or skill content in a tool folder (.claude/agents/…), you choose migrate (move it to .github/agents/ and .agents/skills/, wrappers generated from there) or keep (edited in place, recorded in .agents/kept-agent-files.json and from then on invisible to sync and to doctor --check). Finally, each stack-specific skill the scan found a reason for is offered on its own line with that reason — "react is a dependency in web/package.json" — and is only written if you say yes. Two more multi-selects follow: the Definition of Done — the items a change is measured by, each then set to always or when applicable with its condition (docs=when: the change alters behaviour, security-gate=always, separated by ;) — and the standard slices every feature breakdown gets. Both are written once into AGENTS.md; the pull request template's checklist is generated from the Definition of Done, word for word, and the generated PRD-to-issues skill reads the slices.
  3. The sprintforge skill. Before the session starts, init installs its own skill at .agents/skills/sprintforge/SKILL.md and generates its wrapper for your agent tool (.claude/skills/sprintforge/SKILL.md for Claude Code). It is copied, never tailored: it lists every command, when to use each, that every call takes --json, and the one hard rule — never run a spending command without the user's explicit yes in the conversation. Afterwards, "put epic 95 on watch" in your agent runs sprintforge watch add 95 --json, and "start a run" asks you first. A file you chose to keep at that path is left alone.
  4. The agent session. Claude Code starts interactively with the init prompt, the unpacked pack and your selections. It asks the docs-layout question — global, per service, co-located design notes, or keep what exists — and writes AGENTS.md, a thin CLAUDE.md and GEMINI.md, the docs folders from the fixed set, the templates, and the decision record.
  5. Validation. The CLI checks the tree, not the agent's word: AGENTS.md exists, the tool files point at it and stayed thin, the decision record is status: complete and carries every answer plus the pack and CLI version, and no documentation folder outside features/, decisions/ and developers/ appeared. Every selected persona must name one of the build or test commands the CLI read out of the repository's own manifests and point at the docs tree — a persona that does not is a template, and the run fails. AGENTS.md must hold exactly the Definition of Done items that were selected, with their marks, and no item that was dropped; the issue forms for feature, bug and finding must exist with a required acceptance criteria field on the feature form; and the pull request template must carry the generated checklist and a verification matrix. sync and the doctor agent-setup checks run on the result, with the kept files excluded.
  6. Finish. A diffstat, then your choice: commit and open a draft pull request that links the decision record, commit only, or leave everything uncommitted. Nothing is pushed without that explicit yes.

The pack is downloaded into a temporary directory and removed when the run ends — after success, after a failure and after Ctrl+C.

Existing files are improved, never replaced: the agent edits the rulebook, persona, skill or template that is already there, so the change is an edit in the branch diff and the team's own wording survives.

Running it again

The decision record's frontmatter is the state — there is no lock file — and all three of these read it:

  • A re-run pre-fills every question with the previous answer, so pressing Enter through the dialogue reproduces the previous setup. Afterwards it prints only the answers that moved (preset: minimal → standard), or says that nothing changed. A typed --preset= wins over the record; the record wins over the built-in default.

  • An interrupted run leaves status: in progress. The next run stops on one question before anything is downloaded: resume (the default — the agent continues that record, keeping its number) or start over (every question asked again, the record rewritten).

  • --non-interactive answers every question the way a re-run would and never prompts: previous answer where the record has one, default where it does not, and the agent runs headless. It combines with --json. Two flags steer it:

    sprintforge init --non-interactive --finish=commit   # pr | commit | leave (default: leave)
    sprintforge init --non-interactive --resume=start-over

    A scripted run cannot confirm a branch, so the branch question is answered from the state itself: a dirty tree or a protected branch (main, master, develop, trunk) exits non-zero with the same message as a no, before the pack is downloaded. Nothing is pushed unless --finish=pr was typed, and any failed check still exits non-zero.

Agent setup: sync and doctor

Your personas live in .github/agents/<role>.agent.md and your skills in .agents/skills/<name>/SKILL.md. sync generates the wrappers each agent tool expects from them, so you edit one file and every tool follows:

sprintforge sync                  # write the wrappers for every supported tool
sprintforge sync --check          # report drift, write nothing, exit non-zero if any
sprintforge sync --tool=claude    # one tool only
sprintforge doctor --check        # the CI gate: rulebook, frontmatter and drift

Three things make this safe to re-run in a repository that also has hand-written agents and skills:

  • every generated file carries a GENERATED by … marker, and only marked files are ever deleted;
  • a skill whose name collides with a built-in command of the tool is renamed rather than shadowing it — a code-review skill is published to Claude Code as sprintforge-code-review;
  • a source with unreadable frontmatter fails the run before anything is written.

doctor reports on the agent setup: the rulebook (.agents/AGENTS.md), the frontmatter of every persona and skill, and wrapper drift. --check is the CI mode — local checks only, no login and no network, no secret value ever read, and a non-zero exit on any failure. Without --check it also names the groups it could not check without a login. When the repository has .github/workflows/sprintforge-night-shift.yml, both modes also check that it grants id-token: write: the runner signs in to SprintForge with a GitHub Actions OIDC token, and without that permission the runner cannot authenticate to SprintForge.

Both take --root=<dir> (or a bare directory argument) to work on another tree, and --tool=<id> to narrow to one agent tool. Value flags are attached with =, because the router reads a detached value as a subcommand.

Team resolution

Most commands act for a team. The CLI never asks you to name one twice:

  1. --team <name> wins whenever it is given, and is just this once — it is not remembered.
  2. Otherwise the CLI reads the git remote of the current directory (git remote get-url, falling back to gh repo view) and asks GET /api/cli/teams/resolve?repository=owner/repo. The answer only ever contains teams you are a member of.
  3. One match is used without saying anything.
  4. Several matches are asked once, and the answer is remembered in your own CLI config — keyed by repository, never written into the repository, so it is your choice and not your colleagues'.
  5. No match is explained rather than guessed: the repository is not linked to any of your teams.

sprintforge whoami shows what this resolves to for the current directory. With --json, nothing is ever prompted: a repository matching two of your teams comes back as team_ambiguous and you pass --team.

| error.code | Meaning | |---|---| | not_a_repository | this directory is not a GitHub repository checkout | | team_not_linked | the repository is not linked to any team you are a member of | | team_ambiguous | several of your teams work here and nothing could be asked (--json) | | unknown_team | --team named a team you are not a member of in this repository |

The night-shift queue

sprintforge queue ls                     # what is queued for the resolved team
sprintforge queue add 123                # add the Claude:queue label through the API
sprintforge queue rm 123                 # take it off again
sprintforge queue add 123 --repo octo/app   # when two of the team's repositories have a #123
sprintforge queue ls --json              # { "data": { "team": …, "issues": [ … ] } }

The issue is addressed the way you think of it — by number. The CLI resolves the team first (see Team resolution), asks GET /api/cli/issues/lookup which of that team's issues carry the number, and then calls the same queue endpoints the web app uses. Nothing is guessed: a number that is in two of the team's repositories is refused with the list, and you pick with --repo.

queue ls --json returns issues as { id, number, title, url, status, repository, assignees, queueOrder }, in queue order; queue add and queue rm return the issue they acted on plus the same list as it is afterwards.

| error.code | Meaning | |---|---| | issue_not_found | no repository the team tracks has that issue number | | issue_ambiguous | several of the team's repositories do — pass --repo <owner/repo> | | not_entitled | your SprintForge plan does not include this; the server's sentence is printed | | forbidden | the server refused — for example you are not a member of that team | | token_invalid | the CLI token expired or was revoked; run sprintforge login |

Entitlement is decided by the API and never by the CLI: a refusal is rendered as the one sentence the server wrote, never as a stack trace.

Watching an epic

sprintforge watch ls                     # what the resolved team is watching
sprintforge watch add 95                 # put Claude:watch on the epic through the API
sprintforge watch rm 95                  # take it off again
sprintforge watch add 95 --repo octo/app # when two of the team's repositories have a #95

Claude:watch on a parent issue means "implement its sub-issues without me queueing them one at a time". The issue is looked up by number exactly as queue does, and the label is written by the GitHub App after the API has checked the issue is in one of the team's repositories.

Two answers come back as warnings, printed as ! … lines and carried in data.warnings under --json: Watch is switched off for the team, and the epic has no open sub-issues. Neither fails the command — the label is on, it simply has nothing to do yet. watch ls --json returns issues as { number, title, url, repository, pendingChildren, eligiblePayers } plus watchEnabled, and watch add|rm return the issue they acted on with watched, subIssueCount and warnings.

The error.code values are the ones in the table above, plus watch_add_failed / watch_remove_failed when GitHub itself refused the label.

Starting a run

sprintforge night-shift run                 # confirm, then run whatever is queued
sprintforge night-shift trigger now         # trigger a run immediately
sprintforge trigger now                     # shorthand: trigger immediately
sprintforge night-shift run --issues 12,13  # only these issues
sprintforge night-shift run --model opus    # override the model for this run
sprintforge night-shift run --yes           # skip the question

This one spends money, so it asks. Before anything is triggered the CLI reads the Sprint Accelerator status for the resolved team and shows who pays, what runs and what is left:

This starts a night-shift run and spends budget.
  Payer   octo-dev
  Team    Alpha
  Model   opus (override)
  Budget  12.5% of the weekly quota (~1,250,000 tokens) left — Smart mode
  Issues  #12, #13
Start the run? [y/N]:

Anything but y/yes triggers nothing and exits non-zero. The budget figures are the server's own, so the number you confirm is the number the run is dispatched with.

--json without --yes is refused (confirmation_required, exit 2) and reads nothing: one JSON document on stdout leaves no room for a prompt, and a prompt nobody answers must never be taken as a yes. Scripts and agents pass --yes deliberately, after showing the spend to the person paying.

--model takes a label from the team's model table — an unknown one is refused with the list. run --json --yes returns { triggered, payer, model, budget, issues, status, runUrl, triggeredAt }.

| error.code | Meaning | |---|---| | confirmation_required | --json without --yes; nothing was read and nothing was triggered | | run_declined | the question was answered no; nothing was triggered | | unknown_model | --model named a model the team's model table does not have | | status_unavailable | the spend could not be read, so nothing was asked and nothing triggered | | trigger_failed | the API refused the run; the server's sentence is printed |

Setting up the night shift

sprintforge night-shift setup                           # configure workflow and permissions
sprintforge night-shift setup --yes                     # non-interactive: accept prompts automatically
sprintforge night-shift setup --repo octo/app           # target a specific repository
sprintforge night-shift setup --force                   # overwrite existing workflow if present
sprintforge night-shift setup --json                    # { "data": { "workflow": …, "secrets": …, "permissions": … } }

night-shift setup configures a repository for autonomous night-shift runs:

  1. Workflow permissions. Checks gh api repos/{owner}/{repo}/actions/permissions/workflow for can_approve_pull_request_reviews and default_workflow_permissions: write. Sets them if needed (or prints the required admin action if lacking permissions).
  2. No runner secret. The runner authenticates every SprintForge call with a GitHub Actions OIDC token, so setup stores no SprintForge secret in the repository ("secrets": {} under --json). SPRINTFORGE_API_URL is optional and only needed for a SprintForge other than the default.
  3. Workflow file. Generates .github/workflows/sprintforge-night-shift.yml with id-token: write, contents: write, pull-requests: write, issues: write, scheduled cron, and repository branch-locking concurrency.

| error.code | Meaning | |---|---| | workflow_exists | .github/workflows/sprintforge-night-shift.yml already exists (use --force to overwrite) | | permissions_declined | actions permissions update declined | | permissions_failed | actions permissions could not be updated (adminAction required) |

Connecting a Claude seat

sprintforge night-shift seat connect               # prompts for repository or organization secret
sprintforge night-shift seat connect --repo-secret # store as repository secret in current repository
sprintforge night-shift seat connect --org-secret  # store as organization secret in repo's organization
sprintforge night-shift seat connect --repo octo/app --repo-secret
sprintforge night-shift seat connect --org octo --org-secret

A night-shift run or Claude Review spends an individual developer's subscription seat, billed to that developer's secret. night-shift seat connect connects a personal subscription token to GitHub under the exact secret name the workflow expects (CLAUDE_CODE_OAUTH_TOKEN_<GITHUB_LOGIN_UPPERCASE>):

  1. Interactive scope selection: Prompts every time whether to store the token as a repository or organization secret. Non-interactive or script use passes --repo-secret or --org-secret.
  2. Permission check before minting: Verifies GitHub permissions before running claude setup-token:
    • For organization secrets, checks that your account has the admin role in the organization. Without org admin rights, it exits non-zero, prints the exact secret name and the gh secret set command for an administrator to run, and does not create a repository secret (no silent fallback).
    • For repository secrets, checks that your account has admin rights on the repository.
  3. Zero credential leakage: Captures the token from claude setup-token and pipes it via stdin into gh secret set. The token is never printed, logged, written to disk, put in argv, or sent to SprintForge.
  4. --json shape: Under --json, outputs a structured result with secretName, scope, target, and created. On permission refusal, created is false and includes adminAction with the recommended command.

| error.code | Meaning | |---|---| | admin_required | organization secrets require org admin rights; admin command is printed | | permission_denied | repository secrets require admin permissions on the repository | | token_capture_failed | claude setup-token failed or token could not be parsed | | not_signed_in | gh is not authenticated; run gh auth login | | not_a_repository | cannot determine target repository/organization outside a repo |

Your night-shift schedule

A schedule is per developer per team — it names whose Claude seat gets spent — so these two commands only ever read and write your own, and there is no flag that points them at somebody else.

sprintforge night-shift schedule                                    # same as 'show'
sprintforge night-shift schedule show
sprintforge night-shift schedule set --every day --at 20:00
sprintforge night-shift schedule set --every '2 weeks' --on mon,thu --at 21:00
sprintforge night-shift schedule set --timezone America/New_York
sprintforge night-shift schedule set --disable

show prints the recurrence, the time, the zone and the next run rendered in that zone — which is the point of showing it at all, because it is how you confirm the zone is the one you meant:

Night shift for Alpha: every 2 weeks on Mon, Thu at 21:00
  Time      21:00 Europe/Brussels
  Next run  Thu 8 Oct 2026, 21:00 Europe/Brussels
  Last run  Mon 5 Oct 2026, 21:00 Europe/Brussels — Succeeded

| Flag | Takes | |---|---| | --every | day, daily, 2d, '2 days', week, 3w, '3 weeks' — --every 2 days unquoted works too | | --on | weekdays of a weekly recurrence: mon,wed,fri, or the full names | | --at | 20:00, 9:05 or 0900 | | --timezone, --tz | an IANA zone, e.g. Europe/Brussels | | --enable, --disable | switch the schedule on or off without touching the recurrence |

set is a read-modify-write: flags you leave out keep what is stored, so set --at 21:00 moves the time and leaves the recurrence alone. Giving any of the flags above switches the schedule on unless you also pass --disable.

The time zone. --timezone always wins. Otherwise a schedule that is already running keeps the zone it was set with, and only a schedule that is not running yet picks up this machine's zone (TZ, else the host setting) — so setting a schedule from a laptop abroad cannot quietly move a standing 20:00 run into another zone.

Validity is the server's to judge: an unknown zone, a weekly recurrence with no weekday or an interval past the ceiling are refused by the API and its sentence is printed as written. The CLI keeps no second opinion, which is also why what you set here is exactly what the Sprint Accelerator settings screen shows.

schedule show|set --json returns { team, repository, schedule }, where schedule is { enabled, unit, interval, timeOfDay, weekdays, timeZoneId, description, nextRunAtUtc, nextRunLocal, lastFiredAtUtc, lastStatus }; set adds updated: true.

| error.code | Meaning | |---|---| | usage | a flag was mistyped, a value is not a recurrence/weekday/time, or set was given nothing to set | | schedule_unavailable | the current schedule could not be read; nothing was written | | schedule_rejected | the API refused the schedule; its validation message is printed |

Following runs

sprintforge runs ls                       # the team's runs of the last 14 days
sprintforge runs ls --limit 10
sprintforge runs view <run-id>            # status, phase, issue, pull request
sprintforge runs follow <run-id>          # live output until the run ends
sprintforge runs follow <run-id> --since 412

ls is one line per run — short id, status, phase, developer, issue — running ones first, then the most recently dispatched; it reads the same endpoint the Runs tab reads, so the terminal and the web app cannot disagree about what ran. view takes the full run id printed by runs ls --json.

Night-shift runs of Alpha, last 14 days (2):
aaaaaaaa  Running    Implementing octocat        CapturePlatform/app#148 runs ls | view | follow
bbbbbbbb  Succeeded  Done         hubot          CapturePlatform/app#147 night-shift schedule

follow is the terminal equivalent of leaving the Runs tab's Output panel open. It opens the live-output stream (Server-Sent Events) with your CLI token as Authorization: Bearer — never in the query string — prints each line as it arrives, and exits 0 when the run ends.

A dropped connection is expected, not an error. The server ends a stream at its lifetime cap, a laptop lid closes, a train enters a tunnel. follow remembers the sequence number of the last batch it printed, reconnects with it as Last-Event-ID (and as afterSeq, for a proxy that strips the header), and drops any batch at or below it — so a resume never shows a gap and never prints a line twice. Reconnections are announced on stderr, so sprintforge runs follow <id> > run.log keeps the log clean. After ten consecutive failed attempts it gives up with the exact command to pick up where it stopped (--since <seq>).

runs ls|view --json returns { team, windowDays, runs } / { team, run }, where a run is { id, kind, status, phase, ended, developer, repository, issue, issues, runUrl, pullRequest, loopId, dispatchedAt, startedAt, endedAt }. follow --json prints nothing while it waits and one envelope when the run ends — { team, run, lines, batches, reconnects, lastSeq, ended } — so a script can block on a run and then read how it went.

| error.code | Meaning | |---|---| | usage | view/follow was given something that is not a run id | | runs_unavailable | the list could not be read | | run_not_found | no such run in this team — another team's run reads the same way | | run_stream_unavailable | the stream was refused (not a member, too many open streams); the server's sentence is printed | | stream_lost | the connection could not be re-established; the hint resumes it with --since |

Version gate

Before the first command that talks to SprintForge, the CLI reads the anonymous GET /api/cli/metadata and compares its own version with minimumVersion. An outdated CLI prints an upgrade message and exits 4 without calling any other endpoint. Metadata that cannot be reached never blocks a command — a network problem is not the same as being out of date.

Configuration

| Variable | Default | Purpose | |---|---|---| | SPRINTFORGE_API_URL | https://sprintforge.apps.demo.captureplatform.com | the SprintForge API to talk to | | SPRINTFORGE_CONFIG_DIR | $XDG_CONFIG_HOME/sprintforge | where credentials.json and config.json live |

config.json holds preferences only — today, the team remembered per repository under teams. The token is never in it.

Development

node bin/sprintforge.mjs --help
node --test test/*.test.mjs

The CLI has no runtime dependencies and uses only Node built-ins (Node >= 20.11).