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

dbnav

v0.1.0

Published

Unofficial Deutsche Bahn journey search CLI and TUI built on db-vendo-client. Deterministic JSON/NDJSON output for AI agents, rich local filters and sorting for humans.

Downloads

27

Readme

dbnav

Unofficial Deutsche Bahn journey search as a CLI and terminal UI, built on db-vendo-client (dbnav profile). It exposes more search, filter, and sort control than DB Navigator and is designed to be reliable for AI agents: deterministic, versioned JSON/NDJSON output, strict schemas, documented exit codes, and zero prompts in non-interactive use.

⚠️ Unofficial project — not affiliated with Deutsche Bahn

This is an independent, community-built tool. It is not endorsed by, supported by, or affiliated with Deutsche Bahn AG in any way, and it uses an undocumented API that can change or break at any time without notice.

  • Prices shown with a BahnCard are local estimates, never binding quotes — always verify on bahn.de before booking.
  • Use it responsibly: keep search windows small, leave the default retry/backoff and caching behavior on, and set a contact user agent (DB_VENDO_USER_AGENT) so operators can reach you.
  • "DB", "Deutsche Bahn", and "DB Navigator" are trademarks of Deutsche Bahn AG and are used here only to describe compatibility.

Install & build

Requires Node.js 22+.

npx dbnav --help     # run without installing (once published to npm)
npm install -g dbnav # or install globally

From a checkout:

npm install
npm run build        # compiles to dist/, bin/dbnav.js is the executable
npm link             # optional: makes `dbnav` available on PATH

Development: npm run dev -- journeys --help (tsx, no build needed).

Commands

dbnav                     open the TUI (only on an interactive terminal)
dbnav tui                 open the TUI explicitly
dbnav locations <query>   resolve station names to stable IDs
dbnav journeys [options]  search journeys in a datetime window
dbnav journey <token>     refresh one journey by refresh token
dbnav schema [part]       print JSON Schemas (request | response | stream)
dbnav config show|path    inspect the config file
dbnav completion <shell>  bash | zsh | fish | powershell completion script

Global flags: --json (single JSON document; default when stdout is not a TTY), --stream (NDJSON), --pretty (non-streaming JSON only), --table (force human table), --debug (diagnostics on stderr), --no-config (ignore the user config file).

Quick start

# 1. Find station IDs (stable across searches):
dbnav locations "Frankfurt Hbf" --json

# 2. Search a window, sorted by effective price then duration:
dbnav journeys --json \
  --from 8000105 --to 8000191 \
  --start 2026-08-01T08:00 --end 2026-08-01T18:00 \
  --bahncard bahncard50 --max-price 60 \
  --sort price:asc,duration:asc,transfers:asc --limit 10

# 3. Stream results page-by-page as NDJSON:
dbnav journeys --stream --from 8000105 --to 8000191 --start 2026-08-01T08:00

# 4. Inspect what would be sent without contacting DB:
dbnav journeys --explain --from 8000105 --to 8000191 --start 2026-08-01T08:00

Humans on a TTY get a table by default and may use relative datetimes (--start "tomorrow 08:00"). Machine mode (--json/--stream/no TTY) accepts ISO 8601 only and never prompts.

Journey options (selection)

Every TUI setting has a CLI flag. See dbnav journeys --help for the full list.

  • Route & window: --from/--to/--via (IDs or unambiguous names; --pick-first resolves ambiguity deterministically), --start/--end (window may cross midnight or span up to 7 days; --allow-long-window overrides with a rate-limit warning), --time-mode departure|arrival, --timezone (default Europe/Berlin).
  • DB request: --products ice,ic,re,rb,sbahn,ubahn,tram,bus,ferry or presets (all, rail, long-distance, regional, local), --max-transfers, --min-transfer-minutes, --first-class, --passengers, --age-group (baby 0-5, child 6-14, young 15-26, adult 27-64, senior 65+), --bahncard none|bahncard25|bahncard50 + --bahncard-class 1|2, --bike, --accessibility none|partial|complete, --walking-speed, --no-start-with-walking, --include-slower, --best-price, --d-ticket-only, --d-ticket-discount, --stopovers, --no-remarks, --results-per-page.
  • Budget & transport: --timeout <ms>, --max-pages <n>, --retries <n> (exponential backoff with jitter), --cache default|bypass|refresh (2-minute TTL).
  • Local filters (applied after fetching, never sent upstream): --max-price, --min-price, --price-basis effective|base, --require-price, --max-duration 2h/--min-duration 45m, --filter-max-transfers, --direct, --depart-after/--depart-before/--arrive-after/--arrive-before (ISO sub-windows), --include-product/--exclude-product, --include-category/--exclude-category, --include-line/--exclude-line, --include-operator/--exclude-operator, --no-cancelled, --max-departure-delay, --max-arrival-delay, --min-transfer-buffer (+ --reject-risky-transfers for unknown buffers), --require-walking/--avoid-walking, --max-walking 15m, --require-bike-info, --require-d-ticket, --via-preferred, --via-avoided, --no-dedupe.
  • Sorting & selection: --sort price:asc,duration:asc,transfers:asc (stable multi-key; keys: departure, arrival, price, duration, transfers, delay, walking, reliability, score; missing values always sort last), --score-weights price=1,duration=1,transfers=0.5,delay=0.5,walking=0.25 (weighted min-max score, lower is better, missing values count as worst case), --pareto (drop journeys strictly dominated on price+duration+transfers; journeys with missing values are never removed), --limit.

Configuration & precedence

Config file (strict JSON, unknown keys rejected):

  • Windows: %APPDATA%\dbnav\config.json
  • macOS: ~/Library/Application Support/dbnav/config.json
  • Linux: $XDG_CONFIG_HOME/dbnav/config.json
  • Override the path with DBNAV_CONFIG.
{
  "defaults": { "bahncard": "bahncard25", "filters": { "requirePrice": true } },
  "presets": {
    "commute": { "from": "8000105", "to": "8000244", "products": { "nationalExpress": false } }
  }
}

Use presets/saved searches with dbnav journeys --preset commute …. Load a full request with --input request.json or --input - (stdin JSON).

Precedence (highest wins): CLI flags → --input JSON → environment → preset → config defaults → built-in defaults. Nested filters/products/scoreWeights merge per key. --explain shows the resolved request and the source of every value.

Environment variables

DB_VENDO_USER_AGENT       honest contact-style UA passed to db-vendo-client
DB_NAVIGATOR_USER_AGENT   transport-workaround UA (default DBNavigator/Android/25.18.2)
DB_NAVIGATOR_APP_VERSION  transport-workaround app version (default 25.18.2)
DBNAV_TIMEZONE            default timezone (default Europe/Berlin)
DBNAV_CONFIG              explicit config file path
DBNAV_LIVE_TESTS=1        enable opt-in live smoke tests

See .env.example. No personal data is committed or emitted in output.

Output contract

schemaVersion: "1.0" is versioned independently of the package. Print the machine schemas with dbnav schema request|response|stream.

  • Datetimes are ISO 8601 with offsets; money is numeric major units + ISO currency code; JSON field names never vary with locale; missing, null, 0, and false are distinct; results embed the effective request and app version.
  • Success envelope: { schemaVersion, ok: true, command, appVersion, query, range, journeys, stats, warnings } where stats = { pages, fetched, deduplicated, inRange, returned, filtered: {reason: count}, cacheHit, elapsedMs, complete, truncatedReason }.
  • Each journey: stable fingerprint id, refreshToken, planned/realtime departure/arrival, delays, durationMinutes, transfers, minTransferBufferMinutes, walking info, cancellation flags, loadFactor, dTicketSuitable, fare (see below), legs (with IDs, times, line/product/operator, walking, cancellation, delay, stopovers on request), remarks, officialDbSearchUrl, dataWarnings.
  • NDJSON (--stream): one compact object per line, each with schemaVersion, type, monotone sequence. Event types: meta, journey (default framing), page (--stream-events page), progress, done (exactly once on success), error (only after a stream already began; before that, errors are a plain JSON error envelope on stdout). Diagnostics always go to stderr; stdout stays pure.

Fares and BahnCard estimates

DB's API often returns prices without the requested BahnCard reduction. dbnav keeps the upstream amount as fare.basePrice and computes a local fare.effectivePrice: BahnCard 25 → 25% off; BahnCard 50 → 50% off Flexpreis/flexible fares, 25% off saver fares; rounded to 2 decimals. Estimates carry discountSource: "bahncard-local-estimate", discountRate, priceConfidence: "estimated", and an explanation. No price is ever invented when upstream returns none. Price filters and sorting use the effective price by default (--price-basis base to switch); max-price bounds are widened internally so journeys qualifying only after the local discount are not lost.

Exit codes

0  success                      4  upstream/network failure (after retries)
1  internal error               5  no matches
2  validation/usage error       6  partial/truncated success (see stats.truncatedReason)
3  ambiguous/unknown location 130  cancelled (Ctrl+C)

Ambiguous locations (exit 3) return error.details.candidates so agents can pick an ID and retry. Broken pipes (| head) end the stream gracefully.

Time-window pagination

db-vendo-client returns pages, not ranges. dbnav pages until the requested boundary is crossed (based on normalized timestamps), the cursor is exhausted, or a budget (--max-pages, --timeout, Ctrl+C) stops it — in which case stats.complete: false and stats.truncatedReason say why. Departure mode pages forward via laterThan; arrival mode pages backward via earlierThan; the first request carries exactly one of departure/arrival and cursor requests never carry a datetime (vendo enforces this). Pages are deduplicated by refresh token with a deterministic fingerprint fallback.

DB transport workaround

The DB backend sometimes rejects generic clients (HTTP 403) or negotiates HTTP/2 in a way that breaks Node's fetch stack. src/db/dbnav-profile.ts wraps the dbnav profile: HTTP/1.1 pinned via ALPN with keep-alive, DB Navigator-like Android headers with a per-request Instana UUID, randomized user agents disabled, and the base profile's transformReq preserved. App version/device values are configurable via environment variables; the honest project user agent is passed separately to createClient.

TUI

dbnav tui — fully keyboard-driven (press ? for help):

  • From/To/Via with debounced live suggestions (offline fallback) and explicit ID-visible selection; start/end editors supporting cross-day windows; every DB option and the common local filters in organized sections.
  • Results stream in page by page with progress, hidden-by-filter count, and truncation state. Sorting (s/S), Pareto (p), and filter toggles re-rank instantly without refetching. Enter opens a detail view (legs, delays, remarks, fare explanation, refresh token, bahn.de handoff URL).
  • Clear loading/empty/error/cancelled/partial states, responsive narrow-terminal layout, no mouse required; the terminal is restored on exit, error, and Ctrl+C.

Official DB handoff

Each journey includes officialDbSearchUrl, a search handoff to int.bahn.de pre-filled with origin, destination, time, class, and products. It is not a booking deep link and may not show the exact itinerary first. refreshToken works with dbnav journey <token> for realtime refresh via db-vendo-client; it is not a public DB Navigator share URL.

AI agents

A ready-to-use agent skill lives at skills/dbnav-cli/SKILL.md (non-interactive workflow, schemas, exit-code handling, streaming, caveats). Its accuracy is enforced by tests/skill.test.ts, and it ships inside the npm package.

Install the skill (Claude Code / compatible agents)

# per project:
mkdir -p .claude/skills/dbnav-cli
curl -fsSL https://raw.githubusercontent.com/TheReal-Flo/dbnav/main/skills/dbnav-cli/SKILL.md \
  -o .claude/skills/dbnav-cli/SKILL.md

# or globally for your user:
mkdir -p ~/.claude/skills/dbnav-cli
curl -fsSL https://raw.githubusercontent.com/TheReal-Flo/dbnav/main/skills/dbnav-cli/SKILL.md \
  -o ~/.claude/skills/dbnav-cli/SKILL.md

If you installed the npm package, the skill is also on disk at node_modules/dbnav/skills/dbnav-cli/SKILL.md — copy it from there instead of curl:

mkdir -p .claude/skills/dbnav-cli
cp node_modules/dbnav/skills/dbnav-cli/SKILL.md .claude/skills/dbnav-cli/

The skill teaches an agent to resolve station IDs first, run non-interactive JSON/NDJSON searches, interpret exit codes and partial results, and never treat estimated BahnCard prices as quotes. The dbnav binary itself is invoked via npx dbnav — no global install required.

Development

npm run typecheck   # strict TS
npm run lint        # eslint
npm test            # vitest unit + mocked integration tests
npm run build       # tsc -> dist/
npm run test:live   # opt-in live smoke tests (set DBNAV_LIVE_TESTS=1); never in CI

The domain/ and search/ layers are UI-free (no stdout writes, no process.exit) and dependency-injected (client, clock, sleep, RNG) for deterministic tests.

Limitations

  • BahnCard-reduced prices are estimates, not quotes; verify at bahn.de before booking.
  • Load factor, bike info, D-Ticket suitability, and delay data appear only when upstream exposes them; filters that depend on them are conservative by default.
  • Prices for multi-passenger groups depend on upstream behavior; passenger count is forwarded but per-person pricing is not recalculated locally.
  • The vendo API is unofficial and may change without notice.