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

@lionrapid/cli

v0.5.1

Published

LionRapid CLI — sync translation files (iOS, Android, Flutter, JSON, YAML, PO) with the LionRapid backend

Readme

@lionrapid/cli

The LionRapid command-line tool. Syncs translation files in many formats (iOS .strings/.xcstrings, Android strings.xml, Flutter .arb, generic JSON, YAML, PO) with the LionRapid backend, and generates type-safe TypeScript keys from your translations.

This is the non-web integration wedge: it reaches platforms the web surfaces (WordPress, edge proxy, JS snippet, React SDK) can't — mobile apps, backend services, static-site generators, and CI/CD — by speaking the existing /api/integrations/* REST surface instead of the DOM.

New here? Start with the Getting Started guide (install → init → push → translate → pull), then use this README as the reference.

Status

Phase 1 (file-sync dev-loop) is implemented. Command surface:

| Command | Status | What it does | | ----------------- | ------ | ------------------------------------------------------------------- | | init | ✅ | Scaffold a lionrapid.yml (detects Flutter/iOS/Android/Rails/JS) | | push | ✅ | Upload source strings to the backend (server handles MT) | | status [job-id] | ✅ | What the backend actually did with what push queued | | pull [--types] | ✅ | Download translations into target files; optionally regen types | | validate [--ci] | ✅ | Check local files vs the backend for missing/orphan/empty keys | | types generate | ✅ | Generate a typed .d.ts from your source files (offline) | | types check | ✅ | Report ICU issues in your source translations | | scan [--json] | ✅ | Find user-facing strings in source → inventory keyed by auto_key | | scan --push | ✅ | Seed the safe wrap candidates to the backend under their auto_key | | snapshot | ✅ | Write offline-resilience bundles (raw ContentUnit) per locale/ns | | doctor | ✅ | Preflight: config, credentials, server connectivity, source files |

Formats: JSON, YAML, PO (incl. plurals + msgctxt), Flutter .arb, iOS .strings + .xcstrings, Android strings.xml, XLIFF (1.2/2.0, CAT-tool interchange).

Multiple namespaces: give any files entry its own namespace to map different file trees to different backend namespaces (defaults to the top-level namespace). Each is pushed/pulled/validated independently, so the same key name in two namespaces never collides:

namespace: app
files:
  - { format: json, source: common/en.json, target: common/{locale}.json, namespace: common }
  - { format: json, source: app/en.json,    target: app/{locale}.json } # → namespace: app

Glob entries: source may be a glob (*/?); target and namespace must then carry a {basename} token, replaced per matched file with its extension-less name. One stanza covers a whole directory of per-page (or per-feature) catalogs, each in its own namespace:

files:
  - format: json
    source: locales/en/*.json # locales/en/home.json, pricing.json, …
    target: locales/{locale}/{basename}.json
    namespace: '{basename}' # → namespaces home, pricing, …

Globs expand once at config load (sorted, so behavior is deterministic) — push/pull/validate see one concrete entry per matched file, and doctor reports the expanded count. A glob that matches nothing prints a warning instead of silently doing nothing; a glob entry without {basename} in both target and namespace is rejected as ambiguous.

Flat output: JSON/YAML targets are written nested by default (a pushed hero.title comes back as {"hero": {"title": …}}). Set flat: true on a files entry to keep dotted keys literal on pull, so a flat-keyed file round-trips flat for flat-keyed consumers (CMS imports, webhook payloads):

files:
  - { format: json, source: locales/en.json, target: locales/{locale}.json, flat: true }

Reliability & automation

Built for CI and AI agents — every networked command is resilient and scriptable.

  • --json (all commands): emit one result envelope { ok, command, … } on stdout; human prose goes to stderr. On failure, a structured error envelope { ok: false, error: { type, message, status? }, exitCode } is written to stdout. Parse stdout, read stderr.
  • --dry-run (push, pull): report exactly what would happen — counts, keys, target files — without any network mutation (push) or file write (pull). pull --dry-run still does the read-only fetch so the counts are real.
  • Exit codes (stable, gate CI on them): 0 ok · 1 unexpected · 2 validation/config · 3 auth · 4 network/5xx · 5 partial.
  • Automatic retries: transient failures (network error, 5xx, 429) are retried with exponential backoff + jitter. A 429 honors Retry-After when the server sends it; when it doesn't, the CLI waits a rate-limit-window 30 s instead of jittering, so a many-namespace push rides out a per-minute limit. Tune with LIONRAPID_RETRIES (default 3; 0 disables). Writes are safe to retry — the integration endpoints upsert by composite key.
  • Partial progress (push, pull): one failing namespace or file doesn't zero the run — the rest complete, failures are listed per file (and as error fields under --json), and the exit code is 5 (partial). Re-running retries just the failures; pushes upsert, so that's always safe.
  • Pushed ≠ stored (pushstatus): sync/batch answers 202 Accepted, so push's counts are what the CLI sent; the backend stores them in a background job that can still fail or dead-letter. push therefore prints the job ids it queued and saves them to ~/.lionrapid/jobs.json, and lionrapid status <job-id> reports the server's verdict — stored, still queued, retrying, or dead-lettered (with the reason and every failed attempt). Bare lionrapid status lists every job this machine pushed, newest first, re-checking only the unresolved ones; with nothing recorded it falls back to the server's queue health + dead-letter queue. Offline, it still prints the local record, labelled (last known). History is capped at 500 entries / 30 days and never shares a file with your credentials.
  • Concurrent pull: namespace fetches fan out with bounded concurrency (LIONRAPID_CONCURRENCY, default 6) and show interactive progress on a TTY. Output stays deterministic — only the network fan-out is parallelized.
  • doctor: one command to verify config validity, credential resolution, backend reachability/auth, and that declared source files exist — each checked independently so you get an actionable list, not a stack trace.

Offline resilience (snapshot)

lionrapid snapshot writes the raw ContentUnit bundle ({ meta, translations }) per locale/namespace, byte-shaped like the API response — bypassing the format adapters (which flatten to display strings and drop the styles map). Bundle these with your app and load them via the runtime so translations keep working with the server down:

import bundle from './snapshots/es.app.json' assert { type: 'json' };
import {
  LionRapidBuilder,
  MemoryRepository,
  StaticRepository,
} from '@lionrapid/core';

const core = await LionRapidBuilder.init({
  defaultLocale: 'es',
  namespace: 'app',
})
  .use(new MemoryRepository({ enabled: true }))
  .useStaticBundle({ es: { app: bundle.translations } }) // offline layer (memory → static → network)
  .build();
core.t('welcome'); // works offline, styles intact

Node apps can read a snapshot directory at runtime via new FileRepository('./snapshots') from @lionrapid/storage/node.

No-library localization (scan)

For a codebase with no i18n library (hardcoded strings, no t()), scan finds user-facing strings via the TypeScript AST over a finite allowlist of UI sinks (JSX text + placeholder/alt/aria-label/…), classifies them, and keys each by content-hash auto_key — the same key the web edge proxy / DOM auto-detect produces, so a string already translated on your website is reused in your app.

  • action: wrap — static text/attributes (deterministic, safe).
  • action: review — interpolation / inline markup / dynamic values (left for a human or AI agent to author correctly).

scan --json emits a machine-readable inventory on stdout (human prose on stderr) so an AI coding agent can drive the judgment-heavy wrapping while the CLI owns the correctness-critical parts (the auto_key hash, file writes, sync). scan --push seeds the safe candidates into the translation memory.

Wrapping (done by you / your agent)

The CLI intentionally does not rewrite your source. It hands you the inventory — each candidate carries its autoKey, and the result carries the namespace — and you (or your AI coding agent, e.g. Claude Code / Cursor) wrap using the SDK, which also lets the agent handle the review cases (interpolation, plurals) a deterministic codemod can't:

// inventory: { namespace: "app", autoKey: "auto_2d0f6b8300be", text: "Add to cart", sink: "jsx-text" }
<Text>Add to cart</Text>
//   →
<Text><Trans namespace="app" i18nKey="auto_2d0f6b8300be">Add to cart</Trans></Text>

// inventory: { autoKey: "auto_13348442cc6a", text: "Search", sink: "jsx-attr:placeholder" }
placeholder="Search"  →  placeholder={t('app:auto_13348442cc6a', 'Search')}

Because the key is the same content-hash the web produces, any of these strings already translated on your website are translated in the app for free.

Usage

lionrapid init --project <id> --targets es,fr
lionrapid push                 # prints the job ids it queued
lionrapid status               # …and what the backend did with them
lionrapid status <job-id>      # the full story on one job
lionrapid pull --types
lionrapid validate --ci

# no-library localization
lionrapid scan --json          # inventory of discovered strings (auto_key-keyed)
lionrapid scan --push          # seed the safe candidates into the TM

The API key is read from LIONRAPID_API_KEY (or ~/.lionrapid/credentials), never from the committed lionrapid.yml.

Development

npm run build -w packages/cli
npm test -w packages/cli