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

@jurojinpoker/pps-handsummaryjs

v1.0.6

Published

Browser-friendly single-hand JSON summary + legacy hands.txt helpers and local CLI

Readme

PPS.HandSummaryJS

Browser-friendly library (TypeScript → ESM in dist/) for working with ProcessedHandsDump JSON — the same hands.txt files produced by PPS.SandBox.Console. Use it from a hand replayer or any bundler (Vite, webpack, etc.): fetch the JSON, then call parseProcessedHandsJson, getHandAtOneBasedIndex, and helpers under src/poker/.

A small CLI (src/cli.tsdist/cli.js) exists only to run local tests against real hands.txt files (filesystem + console.log). It is not required in production web.


Library vs CLI

| Artifact | Purpose | |----------|---------| | dist/index.js + dist/index.d.ts | Public API for the web app and any consumer. No Node-only imports; safe to bundle. | | dist/cli.js | Node-only test harness: read file from disk, print one hand. Run after npm run build. |

Add new poker logic in src/poker/ (new modules under src/poker/, re-export from src/poker/index.ts and src/index.ts as needed).


Web / bundler import

After npm run build, point your app at the package entry (or a relative path to dist):

import {
  parseProcessedHandsJson,
  getHandAtOneBasedIndex,
  type ProcessedHandsDump,
  type HandSummary,
} from 'pps.handsummaryjs';
// or: from '../PPS.HandSummaryJS/dist/index.js'

async function loadDump(url: string): Promise<ProcessedHandsDump> {
  const text = await fetch(url).then((r) => r.text());
  return parseProcessedHandsJson(text, url);
}

The JSON shape matches what the .NET console writes; no adapter layer is required if you use the same files.


Input format: ProcessedHandsDump

See WriteProcessedHandsJson in PPS.SandBox.Console/Program.cs.

| Property | Meaning | |----------|---------| | Run | Run metadata (optional for parsing a single hand). | | Hands | HandSummary array (successful parses). Required. |

Typical path: PPS.SandBox.Console/processedJsons/run-yyyyMMdd-HHmmss/hands.txt

With --parse-only + --save-processed-json, only successful parses appear in Hands.


Public API (summary)

| Export | Role | |--------|------| | Types | ProcessedHandsDump, HandSummary / HandSummaryPartial, ProcessedHandsRunMetadata | | parseProcessedHandsJson | Parse JSON string → validated dump (throws ProcessedHandsParseError) | | assertProcessedHandsDump | Validate an already-JSON.parsed value | | isProcessedHandsDump | Type guard | | getHandAtOneBasedIndex | Select one hand by 1-based index (throws HandIndexError) | | buildHandDisplayLines | Debug text lines (optional for UI) | | countStreetActions | Per-street action counts | | buildHandActionAnalytics | Per-action JSON: pot, potOdds, spr, effectiveStack, mdf, milestones (see below) | | buildHandBreadcrumbFromAnalytics, buildHandBreadcrumbSummary | Breadcrumb model: byStreet, timeline, streetBreadcrumbStrings, playersInBreadcrumb — milestone labels like PlayerActionPlay; folds omitted on early streets; folds on the last street with action (often river) kept (who folded to a bet/raise). Other actions unchanged. buildHandSummaryFromJson also sets breadcrumb. | | PlayerActionMilestoneType, classifyPlayerActionMilestone | Same enum names as Jurojin PlayerActionMilestoneType / PlayerActionPlaysCreator (src/poker/actionMilestone.ts) |

Action analytics (per street / per action)

buildHandActionAnalytics(hand) returns HandActionAnalyticsJson: a flat actions array plus streets grouped the same way. Each row is one PlayerAction in order, with:

| Field | Meaning | |-------|--------| | potBefore / potAfter | Main pot before/after that action (antes, blinds, then Amount added to pot). | | toCall | Chips needed to match the current face bet before this player acts. | | potOdds | If toCall > 0: PotOddsDetaileffectiveCall = min(toCall, acting player’s stack) so short all-ins use lower risk; riskReward = potBefore : effectiveCall (each side ≤ one decimal); percentage / requiredEquity from effectiveCall / (potBefore + effectiveCall). Otherwise null. | | spr | Stack-to-pot ratio: effectiveStack / potBefore when both > 0. | | effectiveStack | With hero identified and still in the hand: min(hero stack, max opponent stack) among players still in (max the hero can lose at this point). Otherwise: minimum remaining stack among all still in. | | mdf | Only when heroIdentified and this row is hero’s action with toCall > 0: MDF = (potBefore − toCall) / potBefore (equivalent to P/(P+B) where potBefore is the pot including the bet to call). Otherwise null. | | milestoneType / milestoneAmount / rawActionType | Semantic play (Limp, OpenRaise, CBet, DonkBet, ThreeBet, …) and amount mirror Jurojin PlayerActionPlay; logic is evaluated per action with street context (not only the last action on the street). UI strings: PLAYER_ACTION_MILESTONE_DESCRIPTIONS. |

Simulation uses Amount / AbsoluteAmount from the PokerStars-style parser (see HandHistoryParser in PPS.Core.PokerStars). Multi-way pots use the same formulas as a simplified heads-up model; treat as approximate for complex spots.


ParsingService on your machine (raw HH → API → HandSummaryJS)

Use this when PPS.ParsingService is running locally (default http://localhost:5111 — see Properties/launchSettings.json).

  1. npm run build
  2. Start the API, e.g. from repo root: dotnet run --project PPS.ParsingService/PPS.ParsingService.csproj
  3. Point the script at a single-hand raw text file (PokerStars-style HH, same content you would paste into HandContent):
npm run consume-local-api-hand -- path/to/one-hand.txt

| Flag | Meaning | |------|--------| | --url <base> | API base URL (default: PARSING_SERVICE_URL env or http://localhost:5111) | | --site <n> | Optional PokerSite enum value (e.g. 1 = PokerStars) | | --out <dir> | Output folder (default: .parsing-api-smoke/, gitignored) |

The script POSTs /api/hh/parse-hand, validates the wire JSON (assertWireParseHandResponse), maps camelCase → PascalCase (wireKeysToHandSummaryPartial), runs buildHandReplaySummary, and writes 01-response.json, 02-wire-validation.txt, 03-hand-replay-summary.json (or skip/error files if the parse failed).

Alias: npm run parsing-api-smoke — same script.

If you see Fetch failed, the script could not reach the API (usually ParsingService not running or wrong --url). Start the service first, then check http://localhost:5111/api/hh/health in a browser. On Windows PowerShell, $ in the file path must be quoted so it is not expanded — use single-quoted path or backtick-escape $ (see 01-fetch-error.txt for the underlying errno after npm run build).


CLI reference (local testing only)

Run from repo root or PPS.HandSummaryJS (run npm run build first).

| Argument | Required | Description | |----------|----------|-------------| | --hand-index <n> | Yes (unless --help) | 1-based index into Hands. | | --file <path> | No | Path to hands.txt. If set, --dir is not used for resolution. | | --dir <path> | No | Base dir with run-* folders. Default: PPS.SandBox.Console/processedJsons. Latest run-* wins. | | --help, -h | No | Usage. | | --json | No | Print buildHandActionAnalytics output as JSON (includes sourceFile, handIndexOneBased, totalHandsInFile). |

Resolving the file when --file is omitted

  1. Resolve --dir against process.cwd() if relative.
  2. List run-* directories, sort descending by name.
  3. Read hands.txt inside the first folder.

Output (CLI)

  • Default: plain text on stdout, ending with an OK line and Source file: path.
  • --json: structured JSON (one hand’s analytics + metadata).
    Errors: stderr, exit code 1.

Project layout

| Path | Role | |------|------| | src/index.ts | Library entry: re-exports types + src/poker/. | | src/types.ts | JSON / HandSummary shapes (extend as the replayer needs). | | src/poker/ | Poker logic: parsing, display helpers; add new files here. | | src/cli.ts | Node-only CLI (imports fs). | | dist/ | Build output (npm run build). Listed in .gitignore; commit after build in CI if needed. |


Build

cd PPS.HandSummaryJS
npm install
npm run build

Publishing to the public npm registry (npmjs.org)

The package name is @jurojinpoker/pps-handsummaryjs. package.json sets publishConfig to registry: https://registry.npmjs.org/ and access: public. The repo PPS.HandSummaryJS/.npmrc pins the @jurojinpoker scope to the same registry.

Installing the package does not require logging in or any npm token; it is a public package on npmjs.

Publishing a new version: you must be a member of the @jurojinpoker org with publish rights. Run npm login for https://registry.npmjs.org/, bump version in package.json, then from PPS.HandSummaryJS:

npm install
npm publish --access public

PowerShell: use npm.cmd if npm hits execution-policy issues.

prepublishOnly runs npm run build so dist/ is fresh.

Consumers

Depend on @jurojinpoker/pps-handsummaryjs with a normal semver range from the public registry. For Next.js apps, list the package under transpilePackages if the app bundles it.

Unit tests (Vitest)

Tests live under test/**/*.test.ts. They exercise parsers, buildHandSummaryFromJson, metrics helpers, and (optionally) the real Aethusa console JSON if the fixture folder exists.

| Command | What it does | |---------|----------------| | npm test | Run all tests once (CI-style). | | npm test -- --reporter=verbose | Same, but prints each test name and timing. | | npm run test:watch | Re-runs tests when files change. |

Snapshots (*.snap in test/__snapshots__/): some tests use expect(...).toMatchSnapshot(). The first time (or after deleting a .snap), Vitest writes that file; later runs compare the new result to the saved JSON. If you meant to change the output (e.g. pot odds formula), update snapshots on purpose:

npx vitest run -u

Then review the diff in Git (and in the .snap file) before committing, so real regressions do not slip through.

Tie a hands.txt hand to unit test output

| What you are looking at | Unit test | How it lines up | |-------------------------|-----------|-----------------| | test/fixtures/minimal-hand.json | test/dumpSummary-and-metrics.test.ts | That file is the ingest; the .snap is the expected summary slice for it. | | test/fixtures/aethusa-console-output/run-*/hands.txt | test/aethusa-console-hands.integration.test.ts | Hands appear in Hands array order: Hands[0] = logical hand #1, same order as the parser wrote them. |

Extract one hand so you can open it next to a test or run buildHandSummaryFromJson on it (from PPS.HandSummaryJS):

# By 1-based index (same as CLI --hand-index)
npm run extract-hand -- ../path/to/hands.txt 17 hand-17.json

# By PokerStars HandId (string after "id:")
npm run extract-hand -- ../path/to/hands.txt id:222296968834 hand-by-id.json

If you omit the output file, JSON prints to stdout. Then in the editor use a split view: the extracted hand-17.json on one side and dumpSummary-and-metrics.test.ts on the other (or a tiny scratch test that calls buildHandSummaryFromJson with that file’s text).

Quick locate in the big dump: search the file for "HandId": "…" or use the extract script with id:.

Comfortable compare (input vs JS output, no scrolling the dump): after npm run build, write two small JSON files next to each other — the raw hand from the dump and the same object run through buildHandSummaryFromJson:

npm run hand-inspect -- path/to/hands.txt 17
# or: npm run hand-inspect -- path/to/hands.txt id:222296968834
# optional third arg: output directory (default: .hand-inspect/)

You get hand-017-<HandId>-input.json and hand-017-<HandId>-summary.json under .hand-inspect/ (gitignored). Open both in a split editor or your diff tool.

Action-only report (file + stderr path): npm run hand-actions-report — same first two arguments as hand-inspect (dump path + index or id:). Writes PPS.HandSummaryJS/.hand-actions-report/hand-<n>-<HandId>-actions.txt (gitignored). The path is printed on stderr. Optional third argument: output directory; add --stdout to also print the columnar timeline (milestone + label, pot %, MDF %, SPR, …) and the grouped-by-player block. Cursor: /hand-actions-report-cmd.

Breadcrumb JSON: npm run hand-breadcrumb — same arguments as hand-inspect / hand-actions-report. Writes PPS.HandSummaryJS/.hand-breadcrumb/hand-<n>-<HandId>-breadcrumb.json. Folds are dropped on every street except the last one that has action (often river); on that street, folds stay so you see folds to bets/raises. Optional --stdout mirrors the JSON. Cursor: /hand-breadcrumb-cmd.

Visual breadcrumb (table): npm run visual-breadcrumb — same arguments and rules; writes PPS.HandSummaryJS/.visual-breadcrumb/hand-<n>-<HandId>-visual-breadcrumb.txt with a four-column text table (Preflop / Flop / Turn / River), each row the next action in order on that street. Optional --stdout. Cursor: /visual-breadcrumb-cmd.

Run CLI examples

node dist/cli.js --dir ../PPS.SandBox.Console/processedJsons --hand-index 1
node dist/cli.js --file ../PPS.SandBox.Console/processedJsons/run-20260327-173017/hands.txt --hand-index 1

Integration test: Aethusa HH → hands.txt → Node

Vitest file: test/aethusa-console-hands.integration.test.ts.

Ingest (raw hand history): PokerStars sample PPS.SandBox.Console/HH/hh.com_Aethusa_289SH_2021.01.06_0.txt (second HH file in that folder; many-player cash hands). The console only accepts a directory of *.txt, so the test workflow uses a folder that contains this file alone.

Generate the processed JSON (from repository root; dotnet writes under PPS.HandSummaryJS/test/fixtures/aethusa-console-output/run-<timestamp>/hands.txt):

mkdir -p .tmp_hh_aethusa_only
cp PPS.SandBox.Console/HH/hh.com_Aethusa_289SH_2021.01.06_0.txt .tmp_hh_aethusa_only/
dotnet run --project PPS.SandBox.Console/PPS.SandBox.Console.csproj -- \
  --parse-only \
  --save-processed-json \
  --processed-json-dir PPS.HandSummaryJS/test/fixtures/aethusa-console-output \
  --hh-path .tmp_hh_aethusa_only

Test flow:

  1. The test resolves the latest test/fixtures/aethusa-console-output/run-*/hands.txt (lexicographic sort on folder name).
  2. If that path is missing (e.g. fresh clone; output is gitignored), the whole describe is skipped — run the command above first.
  3. parseProcessedHandsJson loads the dump (Run + Hands).
  4. One test asserts the expected count of 572 OK hands from that file’s parse-only run.
  5. Another test runs buildHandSummaryFromJson(JSON.stringify(hand)) for every hand (single-hand wire format the web app uses).
  6. A third test walks all replay summaries and checks that every action with toCall > 0 has non-null potOdds (volume check on real parser output).

Note: --parse-only output does not set IsHeroIdentified / hero screen name; MDF hero behavior is covered by the small test/fixtures/minimal-hand.json unit tests.


TypeScript notes

  • module: nodenext; use .js extensions in import paths in source (e.g. ./types.js).
  • src/ library code must not import node:fs / node:path — keep that in cli.ts only.
  • cli.ts uses @types/node via the same tsconfig.json.

Related repo docs

  • .cursor/commands/summary-hand-cmd.md — parser + CLI smoke test.
  • .cursor/commands/test-parser-cmd.md — parse failures / diagnostics.

Future ideas

  • Optional --json on CLI to emit one hand as JSON.
  • Stricter runtime validation (e.g. Zod) if the C# schema grows.
  • Barrel files per feature area under src/poker/ as logic grows.