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

lorcana-deck

v1.0.3

Published

Terminal deck builder for Disney Lorcana with ASCII card art

Readme

lorcana-deck-builder

A terminal deck builder for Disney Lorcana, with ASCII card art, live search and deck-legality checks. No dependencies, just Node 18+.

 LORCANA deck builder                        deck aggro-ruby · 60/60 legal
 / ink:ruby cost<=3  │  amb ame eme RUB sap ste  │  Core  │  84 matches
 BROWSE 84   tab → deck              │╭──────────────────────────────────────╮
▸4x  ⬡ 2  ◆  Beast - Aggressi…  Ruby ││  ⬡ 2  ◆   Beast                      │
 4x  ⬡ 1  ◆  Dangerous Plan     Ruby ││           Aggressive Lord            │
  ·  ⬡ 3  ◆  Fidget - Ratigan…  Ruby │├──────────────────────────────────────┤
                                     ││  Character · Storyborn · Hero ·      │
                                     ││  Prince · Whisper                    │
                                     │├──────────────────────────────────────┤
                                     ││  Boost 2 ⬡ (Once during your turn,   │
                                     ││  you may pay 2 ⬡ to put the top      │
                                     ││  card of your deck facedown under    │
                                     ││  this character.)                    │
                                     │├──────────────────────────────────────┤
                                     ││  ¤ 3   ♥ 2   ◊ 1                     │
                                     ││  Ruby · Uncommon · set 10            │
                                     │╰──────────────────────────────────────╯

Running it

npx lorcana-deck             # no install, just run it

Or install it for good:

npm install -g lorcana-deck  # gives you `lorcana-deck` and `lorcana`

From a checkout:

node bin/lorcana.js          # open the builder
npm link                     # installs `lorcana` globally from this working copy

Card data

Card data comes from LorcanaJSON (community-maintained, Ravensburger card images and text). It downloads once to ~/.lorcana-deck/cards.json on first run (about 9 MB, ~2 500 unique cards), and everything after that works offline.

lorcana update    # re-download when a new set drops

Keys

| key | action | | --- | --- | | ↑ ↓ / j k | move the cursor | | PgUp PgDn | jump a page | | / space / + | add a copy to the deck | | / backspace | remove a copy | | D | remove all copies | | tab | switch between the browse list and the deck list | | / | search, filters as you type | | 16 | toggle an ink filter (Amber, Amethyst, Emerald, Ruby, Sapphire, Steel) | | t | cycle the card type filter | | i | show inkable cards only | | f | cycle format: Core / Infinity / any | | r | cycle sort: cost / name / lore / power | | p | show the selected card full size | | n s o | new deck / save / open | | e | export the deck as a text list | | c | clear the deck | | ? | help | | q | quit |

Search

The search box takes plain words plus filters, combined with spaces. Prefix any term with - to exclude it.

ink:ruby cost<=3 kw:evasive        cheap evasive Ruby cards
type:character sub:princess        princesses
lore>=3 -ink:amber                 big questers outside Amber
text:"draw a card" cost<=2         cheap draw

| filter | matches | | --- | --- | | ink: i: | ink colour (dual-ink cards match either half) | | type: t: | Character, Action, Item, Location | | sub: | classifications: Hero, Villain, Princess, Floodborn… | | kw: k: | keywords: Evasive, Rush, Shift, Bodyguard… | | cost: lore: str: will: | numbers, with < <= > >= = | | rarity: set: story: | printing details | | text: x: | rules text | | inkwell:yes / inkwell:no | whether the card can be inked |

Deck rules it checks

  • 60 cards minimum
  • at most 4 copies of a card (cards with their own limit, like The Glass Slipper at 2 and Dalmatian Puppy - Tail Wagger at 99, use theirs)
  • at most 2 inks, counting both halves of a dual-ink card
  • every card legal in the selected format

It also nags about soft stuff: under 45% inkable, or an average cost above 4.

Importing decks

Paste a list from Dreamborn, inkdecks or anywhere else. Either give it a file or pipe it in:

lorcana import mydeck.txt                 # deck is named after the file
pbpaste | lorcana import -                # straight from the clipboard
pbpaste | lorcana import - --name azurite # ... with a name you pick

Name matching ignores case, accents, apostrophes and punctuation, and the - between a character and their version is optional, so all of these find the same card:

4 Elsa - Spirit of Winter
4 elsa spirit of winter
4x ELSA - SPIRIT OF WINTER

The parser also copes with the decoration real exports carry: 4x or x4 quantities, - and * bullets, section headers like Characters (18), tabs, Windows line endings, # and // comments, and set annotations such as (TFC) 1, (1/204) or a leading [TFC-1]. Duplicate lines for one card are added together and capped at that card's play limit.

Anything it cannot match is reported with the closest names it knows, so a typo takes one look to fix:

skipped "Ursula Deciever"   did you mean: Ursula - Deceiver | Ursula - Vanessa | Ursula - Sea Witch

After importing it prints the same legality check the builder uses, which is usually how you find out a list was for a different format:

Imported 60 cards → ~/.lorcana-deck/decks/azurite.json
 ! Te Kā - The Burning One is not Core-legal

Simulating games

lorcana sim plays your deck out against other decks and reports how it did.

lorcana sim queenbehavior                          # mirror match, 500 games
lorcana sim queenbehavior --vs sparring-ruby-steel-aggro
lorcana sim queenbehavior --vs all --games 2000    # every other saved deck
lorcana sim queenbehavior --vs aggro --trace       # replay one game, action by action
 queenbehavior   400 games per matchup   fidelity 100.0%

 sparring-ruby-steel-aggro  █████░░░░░░░░░░ 26.5% +/-4.3  fidelity 100.0%
   106W 294L   avg turns 8.6   ◊ 12.6 vs 17.0   first-player edge 57.0%

--games defaults to 500 and runs at roughly 3 500 games a second, so a big gauntlet costs a second or two. --seed makes a run reproducible. Who goes first alternates every game, and first-player edge reports how much that mattered: a number near 50% means the matchup is interactive, while 75%+ means both decks are mostly ignoring each other and racing.

Opponent decks are just saved decks, so paste a list from Dreamborn or inkdecks into a file and lorcana import it. With no --vs it plays the mirror, which needs nothing extra. The repository also carries two synthetic sparring decks in decks/ - generated filler rather than real meta lists, so replace them with the real thing when you care about the answer.

Fidelity, and why it is printed everywhere

Card data from LorcanaJSON gives an ability's type but its effect as prose, so the engine cannot read what a card does. Abilities are hand-written scripts in src/sim/scripts.js, keyed by card name:

// When you play this character, you may play a character with cost 1 for free.
register('Pocahontas - Guiding the Tribe', {
  onPlay: ctx => ctx.playFreeFromHand(card => isCharacter(card) && card.cost === 1),
})

Every script sits under the printed text it implements, so a misread card is easy to spot. A card with no script still plays as a body with its keywords, which means results stay runnable but stop being true. fidelity is the share of the deck the engine can play properly, and any deck below 90% says so on every line. The report also lists exactly which cards it is faking, so a suspicious number is traceable to the cards that caused it.

The 22 keywords split the same way: Evasive, Rush, Bodyguard, Support, Ward, Resist, Challenger, Singer, Sing Together, Reckless, Alert, Vanish and Shift are enforced by the engine; Boost and the one-off Shift variants are not, and count against fidelity.

What the bot is and is not

The bot inks on a curve, plays the most it can afford, quests with everything that gains lore, and challenges only when the lore maths favours it - denying a quester is weighed against the quest it skips and the body it might lose, and it trades more readily when it is behind. That is about beginner-to-decent play. It does not bluff, hold blockers, plan two turns out, or know what your deck is trying to do.

So a 35% or a 65% is telling you something structural. A 51% is telling you nothing, which is why the +/- margin is printed next to it.

Other commands

lorcana search "ink:sapphire kw:ward"   # print matching cards
lorcana show "Elsa - Spirit of Winter"  # print one card
lorcana decks                           # list saved decks
lorcana open aggro-ruby                 # open the builder on a saved deck
lorcana stats aggro-ruby                # curve, inks, warnings
lorcana export aggro-ruby               # text list, one "4 Card Name" per line
lorcana import list.txt                 # build a deck from that format
lorcana sim aggro-ruby --vs all          # simulate against every saved deck

Decks are plain JSON in ~/.lorcana-deck/decks/ (override with LORCANA_HOME). The text format is the usual 4 Card Name list that Dreamborn and friends use, so decks move in and out easily. See Importing decks for everything the parser tolerates.

Flags

--ascii swaps the unicode glyphs and box drawing for plain ASCII. --no-color (or NO_COLOR=1) turns off colour.