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

@pd2trader/cli

v0.9.0

Published

Command-line client for the PD2 Trader API: Project Diablo 2 item prices, search, valuation, price checks, market depth, economy index, alerts, live stream, price cards and leaderboards in your terminal.

Readme

@pd2trader/cli

Project Diablo 2 market prices in your terminal, from pd2trader.com. Item prices, price history with sparklines, corruption prices, item search, stash valuation and currency conversion, price checks by sockets/corruption/stats, market depth, the economy index, webhook price alerts, a live event tail, price card images, economy categories, seasons and net-worth leaderboards. All prices are in HR (High Rune units: 1 HR = 1 Lo Rune, a Ber Rune is about 3 HR).

Built on the official @pd2trader/sdk. Needs Node.js 18.3 or newer.

Install

npm i -g @pd2trader/cli
pd2trader price "Ber Rune"

# or run it without installing
npx @pd2trader/cli price "Ber Rune"

Authentication

Anonymous access still works but is deprecated and will be removed soon. Create an API key at https://pd2trader.com/developers/api-keys (sign in with your PD2 account), then save it:

pd2trader auth login     # paste the key; input is hidden
pd2trader auth status    # which key is used and where it comes from
pd2trader auth logout    # remove the saved key

The CLI sends the key as the X-API-Key header. It picks the key in this order:

  1. --api-key <key> flag
  2. PD2TRADER_API_KEY environment variable
  3. the key saved by pd2trader auth login in ~/.config/pd2trader/config.json ($XDG_CONFIG_HOME/pd2trader/config.json if set), written with mode 0600

auth login checks the key against the API before saving it. In CI, pipe it in instead of typing it: echo "$PD2TRADER_API_KEY" | pd2trader auth login, or just set PD2TRADER_API_KEY.

Without a key the CLI prints a one-line hint on stderr. --json or PD2TRADER_NO_HINTS=1 hides it.

PD2 sign-in (for your own stash)

stash export reads your own stash, so it needs a Project Diablo 2 sign-in on top of the API key:

pd2trader login          # opens the PD2 sign-in page; --no-browser only prints the link
pd2trader whoami         # PD2 user, linked game accounts and token expiry
pd2trader logout         # forget the PD2 sign-in (the API key stays)

login saves the PD2 access and refresh tokens plus your linked game accounts in the same config file (mode 0600). The CLI sends the PD2 token as Authorization: Bearer (the API key still goes in X-API-Key) and refreshes it automatically when it is about to expire or the server answers 401, saving the new tokens. If the refresh is rejected, the tokens are removed and you are asked to run pd2trader login again.

Global options

| Option | Description | | --- | --- | | --api-key <KEY> | API key (overrides PD2TRADER_API_KEY and the saved key) | | --hardcore | Hardcore prices (default: softcore) | | --non-ladder | Non-ladder (default: ladder). Item prices are only collected for ladder softcore and hardcore, so price commands find little or no non-ladder data | | --json | Print the raw API response as JSON (no tables, no hints) | | --base-url <URL> | API origin (default https://pd2trader.com, env PD2TRADER_BASE_URL) | | --no-color | Disable colours. NO_COLOR=1 works too; colours are off when output is piped | | -h, --help | Help, also per command: pd2trader <command> --help | | -v, --version | CLI version |

Commands

Example outputs below show the format; the numbers change constantly.

price <item...>

Average, median, 7-day moving average (7d MA) and 7-day change for one or more items. Names are exact and case-insensitive; quote names with spaces. Options: --hours N, --quality Unique|Set.

$ pd2trader price "Ber Rune" "Jah Rune" "Harlequin Crest"
Ladder softcore · prices in HR
Item              Avg  Median  7d MA     7d  Samples
---------------  ----  ------  -----  -----  -------
Ber Rune         1.03    1.00   1.00  +2.1%    3,183
Jah Rune         0.85    0.85   0.86  -1.4%    3,706
Harlequin Crest  0.42    0.40   0.41  +5.0%      214

batch <baseCode...>

Prices for many base codes in one request. Codes come from arguments and/or --file <path> (one per line, commas/spaces also fine, # comments, - = stdin). Codes without data are listed on stderr.

$ pd2trader batch r30 r31 r32 r33
Ladder softcore · prices in HR
Item       Base   Avg  Median  7d MA     7d  Samples
---------  ----  ----  ------  -----  -----  -------
Ber Rune   r30   1.03    1.00   1.00  +2.1%    3,183
Jah Rune   r31   0.85    0.85   0.86  -1.4%    3,706
Cham Rune  r32   0.21    0.20   0.20  +0.5%    3,654
Zod Rune   r33   1.18    1.15   1.15  +0.8%    2,617

history <baseCode>

Daily price history, oldest first (today is not included). --days N (default 30, max 365), --type CODE, and --chart for a sparkline instead of the table.

$ pd2trader history r30 --days 5
r30 (rune) · Ladder softcore · 5 days · prices in HR
Date         Avg  7d MA  Listings
----------  ----  -----  --------
2026-10-11  0.95   0.98       145
2026-10-12  0.99   0.99       137
2026-10-13  1.02   0.99       129
2026-10-14  0.98   1.00       121
2026-10-15  0.97   1.00       158

$ pd2trader history r30 --days 30 --chart
r30 (rune) · Ladder softcore · 30 days · prices in HR

7d MA   ▁▁▁▁▂▂▂▂▃▃▃▄▄▄▄▅▅▅▅▆▆▆▇▇▇▇████
Daily   ▁▃▁▁▃▄▁▂▄▄▂▃▅▄▃▅▆▄▄▇▆▄▆█▆▅▇█▇▆
        2026-09-16          2026-10-15

Latest    1.00 HR (7d MA), 0.97 HR daily
Range     0.88 – 1.00 HR
Change    +13.6%
Listings  4,260

corruptions <item>

Prices per corruption (and socket count) for a unique or set item, highest median first. Option: --hours N (default 168).

$ pd2trader corruptions "Harlequin Crest"
Harlequin Crest  Ladder softcore · prices in HR
Corruption              Avg  Median  Samples
---------------------  ----  ------  -------
Sockets                0.65    0.60       38
  └ 1 socket           0.45    0.45       21
  └ 2 sockets          0.90    0.85       17
to All Skills          0.80    0.75       12
Increase Maximum Life  0.48    0.45        9

check <item>

Price check by properties: an estimated range (25th percentile / median / 75th percentile) from recent listings that match, with confidence, the filters that were and were not applied, and the closest comparable listings. Options: --sockets N, --eth, --corrupted / --not-corrupted, --corruption NAME (implies corrupted), --stat name=value (repeatable; a numeric name is a PD2 stat id), --quality Unique|Set, --hours N (1–720, default 168).

Listings only store sockets, ethereal, corruption, quality and the Rainbow Facet / Hellfire Torch / Spirit Ward variant. Any other stat is listed under "Not applied" and caps confidence at medium. Exits with code 1 when there is no estimate.

$ pd2trader check "Harlequin Crest" --corrupted --corruption item_numsockets --sockets 1
Harlequin Crest harlequin_crest  Ladder softcore · last 168h · prices in HR

Estimate    2.00 HR (range 1.60 – 2.50)
Confidence  medium
Basis       exact · 7 of 64 listings · 1 outliers removed
Matched on  Sockets = 1 (±1 socket counts as a near match); Corrupted only; Corruption includes Sockets

Comparable listings
Price  Match  Listed                Soc  Eth  Corruption
-----  -----  --------------------  ---  ---  ----------
 2.00   100%  2026-10-15 18:42 UTC    1       Sockets
pd2trader check "Rainbow Facet" --stat damageType=cold --stat damagePercent=5 --stat piercePercent=5
pd2trader check "Hellfire Torch" --stat class=Sorceress

appraise <slot|baseCode> --stat key=value... · appraise --file PATH

Rare-item appraisal, the same as the stash view and the desktop overlay: a verdict (keep = probably valuable, check = worth a look, vendor = likely low value), the item's rank among rares of its slot, its key stats (★ ×1.25–2, ★★ ×2–4, ★★★ ×4+ price multiplier, with near misses and corruption-only stats called out) and the usual asking price of similar rares. Exits with code 1 when the item can't be appraised.

  • From stats: a slot (ring, amul, circ, jewl, glov, boot, belt, tors, helm, shield, quiver, weapon) or base code (rin, ci2, …), and --stat key=value per stat. Keys come from pd2trader rare-guide <slot> --options. --corrupted-stat KEY marks a stat that came from corruption (implies --corrupted); --eth, --sockets N.
  • --phase early|late|season appraises the item for that season phase instead of today (stars, rank and price band of that phase).
  • From the game: --file item.json (or --file - for stdin) with the JSON Project Diablo 2 copies on Ctrl+C. This also prints the item's earlier market listings when it has more than three stats.
  • --hardcore uses hardcore listing prices; verdict and stars still come from softcore data.
  • Many items: a --file with several items, one per blank-line-separated block, is appraised as a batch. With --json-input the file is a JSON array of item texts or { "text": … } / { "item": … } objects. --phase applies to every item. The table shows each item's position (#, from 1), verdict, rank, usual price band and top key stat; invalid entries show their error and don't stop the rest. --json prints the raw batch response (index is 0-based there). Exits with code 1 when nothing could be appraised. Every 10 items cost one request of the rate limit.
$ pd2trader appraise ring --stat item_fastercastrate=20 --stat all_resist=11 --corrupted-stat item_fastercastrate
Ring  Ladder softcore · late season · s13-q50-2026-10-17

Verdict         CHECK (high priority)
Rank            top 44% of rings (56th percentile ±18)
Usually listed  0.75–1.00 HR rings at this rank · this season

     Stat                  Why
---  --------------------  -----------------------------------------------
★★★  20% Faster Cast Rate  ×5.7 · only via corruption on rings · corrupted
     All Resistances +11%
$ pd2trader appraise --file stash-rares.txt
Ladder softcore
#  Verdict  Rank             Usually listed  Top key stat
-  -------  ---------------  --------------  -------------------------
1  KEEP     top 4% rings       2.00–4.00 HR  ★★★ +20% Faster Cast Rate
2  NONE                                      not a rare
3  INVALID                                  text must be a non-empty string
1 appraised · 1 not appraised · 1 invalid

rare-guide [slot]

What multiplies a rare's asking price, from a season of market listings. Without a slot: every slot with its strongest key stat. With a slot: key stats with the value they count from, price tiers (? = few listings), whether they're rising or falling, item archetypes and the price ladder (hardcore with --hardcore). --phase early|late|season picks the season phase (default late); --options lists every stat key appraise accepts for the slot.

pd2trader rare-guide ring
pd2trader rare-guide circ --phase early
pd2trader rare-guide weapon --options

rare-history <fingerprint>

Earlier PD2 market listings of one item by its fingerprint (the id from appraise --json): times listed, distinct sellers and each listing's price, marking where the item changed owner. Items with three or fewer stats share fingerprints with unrelated items, so their history is not shown. Exits with code 1 when never listed.

search <query>

Fuzzy item search by base code, name, alias or nickname (typos are fine). The Code column is the base code every other command accepts. Options: --category CATEGORY, --limit N (1–50, default 10).

$ pd2trader search shako
1 hit for 'shako'
#  Item             Code             Category  Match
-  ---------------  ---------------  --------  -----
1  Harlequin Crest  harlequin_crest  uniques   alias

catalog

Every price-tracked item with its base code, type code and quality, sorted by category then name. Option: --category CATEGORY. Categories: runes, currency, ubers, gems, mats, rainbow-facets, maps, charms, uniques, sets, equipment, other.

value <item[:qty]...>

Total HR value of a list of items, with Net Worth's rules: market prices with at least 10 samples are ok, thinner markets are low (shown, but left out of the total), El–Fal runes and missing high runes use fixed values. Items are names, aliases or base codes; :N sets a quantity. Duplicates are merged and unknown items are listed on stderr. Options: --file PATH (- for stdin), --hours N, --rune-pricing dynamic|fixed.

Files are either text (one item[:qty] per line, # comments) or JSON (["ber:2", {"item": "jah", "qty": 1}] or {"items": [...]}). Up to 500 items.

$ pd2trader value ber:3 shako
Ladder softcore · last 168h · dynamic rune prices · prices of 2026-10-16 · values in HR
Item                             Qty  Each  Value  Conf  Samples
-------------------------------  ---  ----  -----  ----  -------
Ber Rune r30                       3  1.02   3.06  ok        412
Harlequin Crest harlequin_crest    1  0.40   0.40  low         7

Total      3.06 HR
Uncertain  0.40 HR (low confidence, not in total)

--rune-pricing (on value, convert and note) sets how runes are valued: dynamic (default) uses the market price for runes with at least 10 listings and the fixed table for El–Fal and thin markets; fixed values every rune at the fixed table (Ist 0.15, Vex 0.5, Lo 1, Jah 2, Ber 3, …). Other items are always valued at market. The caption shows which one was used (dynamic rune prices / fixed rune prices).

convert <amount> <from> <to>

Converts an amount of one item into another from their HR values. Use hr on either side for the HR unit. Options: --hours N, --rune-pricing dynamic|fixed.

$ pd2trader convert 10 ist ber
Ladder softcore · last 168h · dynamic rune prices · prices of 2026-10-16
10 Ist Rune = 1.4706 Ber Rune

Rate        1 Ist Rune = 0.147059 Ber Rune
Inverse     1 Ber Rune = 6.8 Ist Rune
From        0.15 HR each (fixed)
To          1.02 HR each (ok)
Confidence  ok

note <text...>

Reads the asking price from a free-text trade note (1 to 500 characters; several arguments are joined with spaces). The site price parser and a note-price model both read the note, and a second model picks the more likely value. Options: --hours N, --rune-pricing dynamic|fixed. Exits with code 1 when no price is stated.

  • Probability is the calibrated chance that the stated price is the seller's asking price: 80% and above is reliable (80-90% is right about 86% of the time, 90-95% about 91%, above 95% about 97%). 40-80% is marked as a rough read. Below 40%, or when the note states no price, it prints No price stated.
  • Stated uses the fixed rune/WSS table (1 WSS = 0.01 HR). The item table and Market total value the price items at market prices (with --rune-pricing fixed, runes at the fixed table, so the total matches Stated for rune/WSS/HR notes, except Lem, which the note reader counts as 0.02).
  • A current offer (c/o) is not the asking price; a BIN wins (c/o 2 bin 3 reads 3 HR).
$ pd2trader note "wts 2 ist + pul"
Ladder softcore · last 168h · dynamic rune prices · prices of 2026-10-20
Stated       0.33 HR
Probability  95%
Source       parser
Matched      "2 ist + pul"

Item          Qty   Each  Value  Conf   Samples
------------  ---  -----  -----  -----  -------
Ist Rune r24    2   0.15   0.30  fixed        0
Pul Rune r21    1  0.030  0.030  fixed        0

Market  0.33 HR (items at market prices)

$ pd2trader note offer
No price stated

market <item>

Market depth over the last 7 or 30 complete UTC days (--days 7|30): listings observed, listings still active, new listings per day (sparkline) and the ask distribution (lowest, quartiles, highest, IQR spread). There are no bids, and this endpoint does not compute time-to-sell or sell-through.

$ pd2trader market ber
Ber Rune r30  Ladder softcore · 7 days · prices in HR

Window      2026-10-09 – 2026-10-16 (data as of 2026-10-16 04:00 UTC)
Confidence  high (412 listings)
Observed    412 listings updated in the window
Active      137 listings updated in the last 48h
New         198 listings first seen in the window  ▁▁▂▁█▅▃

Asks      Count  Lowest   P25  Median   P75  Highest  Spread (IQR)
--------  -----  ------  ----  ------  ----  -------  ------------
Observed    412    0.82  0.95    1.00  1.10     3.00  0.15 (15.0%)
Active      137    0.85  0.95    0.98  1.05     2.50  0.10 (10.2%)

listings [item name...] · listing <id>

Search live (or, with --archive, sold) PD2 market listings with the overlay's filters. Positional words match the item name. Options: --type CODE, --base CODE, --rarity R[,R] (Normal, Superior, Magic, Rare, Crafted, Set, Unique, Runeword), --price TEXT (price note), --hr-min N, --hr-max N, --hr-max-exclusive (price < --hr-max), --stat KEY[:MIN[:MAX]] (PD2 property key, repeatable; without bounds the item must have the stat; the extended grammar works too, e.g. item_skillongethit{*,44}[0]:15 or firedam|firemindam{4..}[1]:14), --pseudo ID[:MIN[:MAX]] (e.g. pseudo_total_res, repeatable; without bounds value > 0), --weight KEY=W (repeatable; weights a matching stat/pseudo row or adds a row for that stat, which then also requires it, and shows Score/Value columns), --sort bumped|price|score|value or --sort stat:KEY[:asc|desc] (one property key or pseudo id; listings without the stat come last), --asc, --days N (≤365), --limit N (1-100, default 25), --page N (from 1), --facets F[,F] (also count the matches per value of quality, baseCode, typeCode, sockets, ethereal, corrupted, runeword or price; a facet on a dimension you filter on is counted without that filter, so it lists the alternatives). listing <id> shows one listing with its status, modifiers and pseudo stats.

$ pd2trader listings --type circ --rarity rare --pseudo pseudo_total_res:60 --stat item_fastercastrate:20
$ pd2trader listings --type ring --rarity rare --weight pseudo_life=1 --weight pseudo_total_res=2 --sort score
$ pd2trader listings --type circ --sort stat:item_fastercastrate
$ pd2trader listings --type amul --sort stat:pseudo_total_res:asc
$ pd2trader listings --type circ --rarity unique --facets quality,sockets,price
$ pd2trader listings harlequin crest --sort price --asc --page 2
$ pd2trader listing 6712f0c2a1b2c3d4e5f60718

items <category>

Every priced item of an economy category, as shown on pd2trader.com. Options: --quality Unique|Set, --type CODE, --hours N, --low-confidence (include items with fewer than 10 samples). When an item has fewer than 10 samples in the window, the server counts them over the last 30 days, the season or the last 30 days of the previous season instead; the Window column then says which (30d, season, last season). Price stays the 7-day moving average.

Categories: currency, ubers, runes, charms, rainbow-facets, maps-t5, equipment-armor, equipment-belt, equipment-boots, equipment-weapon, equipment-jewelry, equipment-gloves, equipment-helm, equipment-shield, equipment-quivers.

$ pd2trader items equipment-helm --quality Unique
Equipment - Helm · Ladder softcore · last 168h · prices in HR · updated 2026-10-16 04:00 UTC
#  Item               Price     7d  Samples
-  -----------------  -----  -----  -------
1  Griffon's Eye       1.20  +4.3%       57
2  Harlequin Crest     0.41  +5.0%      214
3  Andariel's Visage   0.15  -2.0%       33

runes

Shortcut for items runes, sorted by price, highest first. Option: --low-confidence.

$ pd2trader runes
Runes · Ladder softcore · last 168h · prices in HR · updated 2026-10-16 04:00 UTC
#  Item      Price     7d  Samples
-  --------  -----  -----  -------
1  Zod Rune   1.15  +0.8%    2,617
2  Ber Rune   1.00  +2.1%    3,183
3  Jah Rune   0.86  -1.4%    3,706

index

The economy index: the daily HR cost of a fixed basket of goods, where 100 is its cost at season start (below 100 the goods got cheaper). Shows the latest value with 1-day, 7-day and season change, the last 14 days (or a sparkline of the whole season with --chart) and the components. Options: --basket high-runes|mid-runes|endgame|all (default high-runes), --season ID, --chart. Only ladder is indexed.

$ pd2trader index --chart --season s13
high-runes index · s13 · Ladder softcore · 100 = 2026-04-23

Latest  62.41 on 2026-10-15
1d      -1.2%
7d      -4.7%
Season  -37.6%

Index   ███▇▇▇▅▆▆▄▅▅▃▃▄▂▂▃▁▁
        2026-04-23 2026-10-15
        range 61.00 – 100.00

Component     Weight  Base   Now  Change
------------  ------  ----  ----  ------
Ber Rune r30   12.5%  3.00  1.85  -38.3%
...

card <item>

Downloads the shareable price card (price, 7-day change, trend) of an item. Saves <baseCode>-card.png by default; --out FILE picks the file (- writes the image to stdout) and --url prints the image URL instead, for Discord or Open Graph embeds. Options: --size og|compact (1200×630 or 600×200), --theme dark|light, --format png|svg.

$ pd2trader card ber
Saved Ber Rune card to r30-card.png (48 KB)
$ pd2trader card ber --url --size compact
https://pd2trader.com/api/cards/item/r30.png?size=compact&isLadder=true&isHardcore=false

market and card take names, aliases or base codes and use the top search hit. A name that matches several items equally well (e.g. torch) is an error that lists the candidates.

watch

Live tail of the event stream (/api/stream): prices after every price update, feed for new finds of players with a public profile, badges for newly earned badges, and PD2 market listings (market.listing.created|updated|sold|removed|expired, printed as name/base · price · seller · mode). One line per event with its UTC receive time; reconnects automatically; Ctrl+C exits with code 0. Option: --events prices,feed,badges,market (market = all five listing events; default: prices, feed, badges, as market events are opt-in). With --json every event, including the initial hello, is one JSON line (NDJSON): { "type", "id", "data", "receivedAt" }.

$ pd2trader watch
Connected to https://pd2trader.com/api/stream · prices, feed, badges · Ctrl+C to stop
18:00:41Z  prices  Prices updated · Ladder softcore, Ladder hardcore
18:04:12Z  feed    Gheed (gheed) found Ber Rune ×2 2.00 HR · Ladder softcore
18:04:12Z  badges  Gheed (gheed) earned first-ber · Ladder softcore
$ pd2trader watch --events market
18:05:02Z  market.listing.created  Harlequin Crest/Shako · 6.00 HR · Errol · Ladder softcore

searches list | create | update | delete | alerts

Saved market searches of your PD2 login (pd2trader login first; sent as Authorization: Bearer). A search with --alert is watched: every new or bumped listing that matches becomes an alert, once per listing, kept 30 days. Limits: 25 saved searches, 10 alerting.

  • searches list: your saved searches and the caps.
  • searches create --filters JSON [--name NAME] [--alert]: --filters is the market search filters object.
  • searches update <id> [--name NAME] [--filters JSON] [--alert | --no-alert]
  • searches delete <id>
  • searches alerts [--since ISO] [--limit 1-100]: recent alerts, newest first, as listing lines.

alerts list | create | update | delete | test

Alerts that call a webhook. They belong to your API key, so a key is required. Discord webhook URLs get an embed; any other HTTPS URL gets a JSON body signed with HMAC-SHA256 (X-PD2Trader-Signature: sha256=<hex>, verify it with verifyWebhookSignature from @pd2trader/sdk).

  • Price alerts fire after a price update when <metric> <op> <threshold> holds, at most once per cooldown.

  • Rare item alerts (--rare) fire for every newly listed rare of a slot that has all the required key stats, once per item.

  • alerts list: your alerts with their rule, mode and last delivery.

  • alerts create --item ITEM --metric average|median|change7d --op lt|lte|gt|gte --threshold N --webhook URL [--cooldown H] [--name NAME]: creates a price alert and prints its signing secret once. --hardcore / --non-ladder set its mode. The threshold is in HR for average/median and in percent for change7d; negative values need = (--threshold=-10). --op also accepts <, <=, >, >= (quote them). Cooldown is 1–720 hours (default 12).

  • alerts create --rare --slot SLOT --stat key=min... --webhook URL [--max-hr N] [--min-stars 1-3] [--name NAME]: creates a rare item alert (1–4 --stat requirements, keys from pd2trader rare-guide <slot>). --max-hr (greater than 0) skips listings priced higher (and unpriced ones); --min-stars requires every listed stat to rate at least that many stars.

  • alerts update <id> with any of the create flags, plus --enable / --disable. On a rare alert, one criteria flag (e.g. --max-hr 1.5) keeps the others.

  • alerts delete <id>.

  • alerts test <id>: sends a test delivery now and exits with code 1 if it fails.

$ pd2trader alerts create --item ber --metric median --op lte --threshold 0.9 --webhook https://discord.com/api/webhooks/…
Alert created.

ID          6710a2f4c1e5b8a9d4f3e201
Name        Ber Rune
Item        Ber Rune r30
Rule        median ≤ 0.90 HR
...

Signing secret: whsec_3f9c1a7b…

seasons

Started seasons, newest first; * marks the current one.

$ pd2trader seasons
   ID   Season           Start       End
-  ---  ---------------  ----------  ----------
*  s14  Alliance (S14)   2026-10-23
   s13  Betrayal (S13)   2026-04-23  2026-10-23
   s12  Suffering (S12)  2025-10-25  2026-04-22

leaderboard

Net-worth leaderboard of opted-in players. Options: --board gained|wealth|session (default gained), --timeframe 7d|30d|season (default season), --basis liquid|total (default liquid), --season ID, --limit N (1–100, default 50), --offset N.

The outputs below are illustrative.

$ pd2trader leaderboard --board wealth --limit 3
wealth board · liquid · season · s14 · Ladder softcore · 1,204 players · updated 2026-11-02
Rank    ±  Player   Slug     Value (HR)
----  ---  -------  -------  ----------
   1    –  Deckard  deckard      412.50
   2   ▲1  Akara    akara        398.00
   3  new  Kashya   kashya       377.25

profile <slug>

A player's public profile: net worth, trend sparkline, ranks, best session, badges, recent finds and, if shared, top holdings. Options: --season ID, --basis, --timeframe. Without --hardcore / --non-ladder the player's own mode is used.

$ pd2trader profile deckard
Deckard (deckard)
Season        Season 14 (S14)
Mode          Ladder softcore
Liquid        412.50 HR
Total         530.00 HR
Trend         ▁▁▂▃▄▅▆▇██ liquid, 10 days
Percentile    99 (share of players at or below this net worth)
Rank gained   #4 of 1,204  ▲2  88.00 HR
Rank wealth   #1 of 1,204  –  412.50 HR
Rank session  —
Best session  2h 15m, +12.30 HR (5.47 HR/h) on 2026-10-30
Badges        first-ber, hr-100

Recent finds
When                  Item      Qty  Value (HR)
--------------------  --------  ---  ----------
2026-11-02 18:41 UTC  Ber Rune   +1        1.00

feed

Recent notable finds of opted-in players, newest first. Options: --limit N, --season ID.

$ pd2trader feed --limit 2
Recent finds · Ladder softcore · values in HR
When                  Player   Item      Qty  Value (HR)
--------------------  -------  --------  ---  ----------
2026-11-02 18:41 UTC  Deckard  Ber Rune   +1        1.00
2026-11-02 18:12 UTC  Akara    Zod Rune   +2        2.30

builds usage | characters | character <name>

Ladder characters (level 80+) crawled from PD2's game API. --hardcore selects hardcore (ladder only, so --non-ladder is rejected). Data can lag when PD2's game endpoints are down.

| Option | Description | | --- | --- | | --season <ID> | Season id, e.g. s14 (default: current) | | --class <NAME> | Any of; repeat or comma-separate (--class Sorceress,Paladin) | | --skill <NAME[:N]> | All of; Name:N = at least N hard points (default 1) | | --item <KEY> | All of; catalog keys like unique:griffon_s_eye, rw:infinity | | --merc-type <NAME> | Any of, e.g. "Defensive Auras" | | --merc-item <KEY> | All of (merc gear) | | --min-level <N>, --max-level <N> | 80–99 | | --max-cost <HR> | Maximum gear value; characters without a gear value are left out | | --name <TEXT> | characters: name contains TEXT (case-insensitive) | | --sort, --dir, --page | characters: level (default), life, gearValueHr, lastUpdated; desc (default) or asc; page number | | -n, --limit <N> | usage: rows per table (1–50, default 10); characters: page size (1–50, default 25) |

usage prints class, skill (20+ hard points), item, merc and merc item shares; characters one page of matching characters with level, life, top skills and gear value; character the skills, attributes, gear and merc gear with prices (exit code 1 for an unknown name).

pd2trader builds usage --class Sorceress --skill Nova:20
pd2trader builds characters --item unique:griffon_s_eye --item rw:infinity --sort gearValueHr
pd2trader builds character Butzfire --json | jq '.equipped[] | [.slot, .name, .priceHr]'

sim run | job | cancel | report | reports | delete | import

Horadric Sim, the PD2 damage simulator. With an API key, jobs run as the key owner's PD2 account: its tier limits (free, supporter or beta tester), its concurrent-job limit (shared with its browser session) and its reports. Without a key only Quick Sim is available.

| Subcommand | Description | | --- | --- | | run <spec.json\|profile.txt> | Submit a job: a .json JobSpec, or any other file as profile.txt text (the horadric-sim format). --bundle <ID:HASH> pins a profile without a bundle= line (default: the newest data bundle). A spec the current engine already reported on is served from that report (shown as Reused; nothing is simulated); --force simulates it again. --wait polls (progress on stderr) and prints the report; --timeout <S> gives up after S seconds (default 600) | | job <id> | Status, progress and report link of a job | | cancel <id> | Cancel a queued or running job you submitted | | report <slug\|url> | DPS, kill time and death rate (mean ± 95% CI) per profile and the delta vs the baseline; takes a slug or a …/sim/r/<slug> permalink | | reports [--page N] [--limit N] | Reports of your key's account, newest first (API key required) | | delete <slug\|url> | Delete one of your reports (API key required) | | import <character> | A public PD2 character as a sim build, with skills PD2's API leaves out (e.g. Split Throw) and notes; --json prints the build for a JobSpec |

pd2trader sim run blizzard.profile.txt --wait
pd2trader sim report https://pd2trader.com/sim/r/q8Zr0Lw3VvB2xT9mN4kD1A
pd2trader sim import YozzSorc --json | jq .build > build.json

auth login | status | logout

See Authentication. auth status exits with code 1 when no key is configured.

login · logout · whoami

See PD2 sign-in. whoami exits with code 1 when you are not signed in.

stash export

Export every item in one of your stashes with its HR value. Needs pd2trader login. Nothing is saved on the server.

| Option | Description | | --- | --- | | --account <NAME> | Game account to export. Default: your only linked account, else your PD2 preferred account, else a prompt (interactive) or an error listing the choices | | --format <csv\|json\|table> | Default: from the --out extension (.csv, .json); else a table on a terminal and JSON when piped (--json also picks JSON) | | -o, --out <FILE> | Write to a file instead of stdout. CSV files start with a UTF-8 BOM so spreadsheet apps read them correctly | | --hardcore, --non-ladder | Which stash (default: ladder softcore) | | --no-prices | Skip pricing (faster; value columns are empty) | | --min-value <HR> | Only items worth at least HR; drops unpriced items and recomputes the totals | | --sort <value\|name\|tab> | Sort items. Default: stash order for CSV/JSON, value for the table |

The table shows the totals and the top 20 items (tab, item, quantity, unit and total value, price confidence). For CSV and JSON a one-line summary (items, priced, total HR) goes to stderr. CSV columns: tab,x,y,name,baseName,baseCode,typeCode,quality,quantity,ilvl,ethereal,sockets,socketed,corrupted,corruptions,stats,unitHr,valueHr,confidence (lists joined with |, stats as name: value).

$ pd2trader stash export --out stash.csv
412 items · 380 priced · total 57.31 HR · MainAcc, Ladder softcore → stash.csv

$ pd2trader stash export --account MuleAcc --min-value 1 --sort value --format json | jq '.items[].name'

Scripting

--json prints the API response unchanged (pretty-printed), so it pipes straight into jq. Tables never go to stdout in JSON mode, and hints, warnings and errors always go to stderr.

# Ber price as a bare number
pd2trader price "Ber Rune" --json | jq '.movingAverage7Days // .medianPrice'

# Several items -> TSV (several names print an array)
pd2trader price "Ber Rune" "Jah Rune" --json | jq -r '.[] | [.itemName, .medianPrice] | @tsv'

# Watchlist file -> CSV
pd2trader batch --file watchlist.txt --json | jq -r '.data[] | [.baseCode, .itemName, .medianPrice] | @csv'

# 90 days of Ber history as CSV
pd2trader history r30 --days 90 --json | jq -r '.data[] | [.date[:10], .avgPrice, .movingAverage] | @csv'

# Top 5 runes in hardcore
pd2trader runes --hardcore --json | jq -r '.items[:5][] | "\(.name)\t\(.price)"'

# Alert when Ber moves more than 10% in a week
pct=$(pd2trader price "Ber Rune" --json | jq '.priceChange7Days.changePercent // 0')
awk -v p="$pct" 'BEGIN { exit !(p > 10 || p < -10) }' && echo "Ber moved ${pct}%"

# Stash value as a bare number
pd2trader value --file stash.txt --json | jq .totalHr

# Median ask of active Ber listings
pd2trader market ber --json | jq .activeAsks.median

# Log every price update
pd2trader watch --events prices --json | jq -r 'select(.type == "prices") | .data.completedAt'

Exit codes

| Code | Meaning | | --- | --- | | 0 | Success (also watch stopped with Ctrl+C) | | 1 | API or network error, invalid key (401), rate limit (429), item not found or ambiguous, no data, no estimate (check), failed test delivery (alerts test), alerts without an API key | | 2 | Usage error: unknown command or option, missing or invalid argument |

Errors are short and actionable: a 401 tells you where the key came from and to run pd2trader auth login; a 429 tells you how many seconds to wait (Retry-After).

What's new in 0.9.0

  • sim run --force simulates a spec even when an identical job already has a report (the server reuses it by default; run shows Reused). sim cancel <id> cancels a queued or running job. sim import lists hidden skills and notes.
  • listings --facets F[,F]: facet counts (quality, bases, types, sockets, flags, price histogram) with the results.
  • builds characters --name TEXT: name contains.
  • Requires @pd2trader/sdk 0.9.0 (sim.cancel, SimSubmitOptions.force, BuildCharactersOptions.q).

What's new in 0.8.0

  • sim run <spec.json|profile.txt> [--wait], sim job, sim report, sim reports, sim delete, sim import: Horadric Sim jobs and reports with your API key (the key owner's tier and limits). Requires @pd2trader/sdk 0.8.0 (sim).
  • appraise --phase now also works for a single item (stats or a one-item --file); it no longer forces the batch endpoint.
  • listings: the HR column shows the effective HR ask (price_hr: the seller's HR price, else the HR the price note states), the value --hr-min, --hr-max and --sort price use.
  • alerts --max-hr rejects 0 like the server; builds usage --limit is capped at 50 (rows the server returns).
  • Requires @pd2trader/sdk 0.8.0 (RareAppraiseInput.phase, MarketListing.price_hr).

What's new in 0.7.0

  • builds usage | characters | character <name>: ladder build usage, matching characters and a character's gear with prices. Requires @pd2trader/sdk 0.7.0 (builds).

What's new in 0.6.0

  • --rune-pricing dynamic|fixed on value, convert and note: fixed values every rune at the fixed rune table. The caption shows the rune pricing used. Requires @pd2trader/sdk 0.6.0 (runePricing).

What's new in 0.5.0

  • note <text...>: read the asking price from a trade note (stated HR, calibrated probability, source, matched text and the price items at market). Requires @pd2trader/sdk 0.5.0 (tools.valuateNote).

What's new in 0.4.1

  • Requires @pd2trader/sdk 0.4.1: appraise uses the new /api/rares/appraisal endpoint (0.4.0 got 404s after the server moved rare appraisal).

What's new in 0.4.0

  • appraise (slot/base + --stat, or the game's Ctrl+C JSON with --file), rare-guide, rare-history.
  • appraise --file with several blank-line-separated items (or a --json-input array) appraises them as one batch (SDK rares.appraiseMany): a table numbered from 1, --phase, --json for the raw response (0-based index).

Links