Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
# coin-ids
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Owns the curated mapping from uppercase ticker symbol (`BTC`, `ETH`, …) to
the opaque CoinGecko coin id (`bitcoin`, `ethereum`, …) that the
`/simple/price` endpoint requires. Without this translation step the
provider would have to either fall back to CoinGecko's heavyweight
`/coins/list` discovery endpoint (~10k entries, defeats tree-shaking) or
ask every caller to learn CoinGecko's id convention — neither is
acceptable for a slim sibling.
## Features
- Frozen `COIN_IDS` map covering the top 16 cryptos by market cap at
landing (BTC, ETH, SOL, USDT, USDC, BNB, XRP, ADA, DOGE, DOT, AVAX,
MATIC, LINK, UNI, ATOM, LTC).
- Case-insensitive `getCoinGeckoId(symbol)` lookup returning `string | undefined`.
- Zero runtime dependencies; pure data + a one-line lookup function.
## Usage: Basic
```ts
import { getCoinGeckoId } from "@suaveplan/currency-provider-coingecko/coin-ids/coin-ids";
getCoinGeckoId("BTC"); // "bitcoin"
getCoinGeckoId("eth"); // "ethereum" — case-insensitive
getCoinGeckoId("ZZZ"); // undefined — unknown symbol
```
## Usage: Advanced
The provider uses `getCoinGeckoId` as the proactive `UNKNOWN_CURRENCY`
gate. Consumers can mirror the same pattern when composing their own
filters before hitting the registry:
```ts
import {
COIN_IDS,
getCoinGeckoId,
} from "@suaveplan/currency-provider-coingecko/coin-ids/coin-ids";
function partitionSupported(symbols: readonly string[]): {
supported: string[];
unknown: string[];
} {
const supported: string[] = [];
const unknown: string[] = [];
for (const symbol of symbols) {
if (getCoinGeckoId(symbol) !== undefined) {
supported.push(symbol);
} else {
unknown.push(symbol);
}
}
return { supported, unknown };
}
// Iterate the full map when generating a UI dropdown:
const everyKnown = Object.keys(COIN_IDS);
```
## API Reference
### `COIN_IDS: Readonly<Record<string, string>>`
Frozen mapping from uppercase ticker (key) to CoinGecko coin id (value).
Keys are always uppercase; mutation throws because the object is frozen.
### `getCoinGeckoId(symbol: string): string | undefined`
Returns the CoinGecko coin id for the given symbol. The input is
upper-cased before the lookup, so `"btc"` and `"BTC"` both resolve to
`"bitcoin"`. Returns `undefined` for any symbol not present in
`COIN_IDS`. Does not throw.
## Implementation Notes
The map is intentionally hand-curated rather than fetched at runtime: a
sibling that issues a discovery call before every batch would (a) add a
network round trip every consumer pays for once, (b) bloat the
in-memory cache, and (c) couple this package's reliability to a second
upstream endpoint whose response shape can drift. Adding a new symbol is
a one-line PR; the trade-off is heavily skewed toward "type it in".

View File

@@ -0,0 +1,114 @@
# CoinGeckoProvider
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
CoinGecko sibling for `@suaveplan/currency-providers`. Implements the
`CurrencyProvider` contract from `@suaveplan/currency` against the
public `/simple/price` endpoint, with optional Pro-tier `apiKey`
support that attaches the `x-cg-pro-api-key` header to every outbound
request. The free public tier (no key, 10–30 calls/min) and the Pro
tier share the same URL shape, so upgrading from free to Pro is a
single-field config change with no other wiring impact.
## Features
- `CoinGeckoProvider implements CurrencyProvider` — `name = "coingecko"`,
`supportsCrypto = true`, `supportsFiat = true`.
- Optional `apiKey` config (`x-cg-pro-api-key` header attachment).
- Optional `baseUrl` and `fetch` config (default
`https://api.coingecko.com/api/v3` and `globalThis.fetch`).
- Caller `AbortSignal` propagation via the shared `httpRequest()` helper
(composes with timeouts from the helper).
- Proactive `UNKNOWN_CURRENCY` gate — unknown `options.targets` reject
before the HTTP call is issued.
- Typed `CurrencyProviderError` on every failure path: `HTTP_ERROR`,
`PARSE_ERROR`, `UPSTREAM_ERROR`, `UNKNOWN_CURRENCY`.
## Usage: Basic
```ts
import { CoinGeckoProvider } from "@suaveplan/currency-provider-coingecko/coingecko/coingecko";
const provider = new CoinGeckoProvider();
const result = await provider.fetchRates({ base: "USD" });
// result.rates.BTC = 0.0000149… (1 USD ≈ 0.0000149 BTC at ~$67k/BTC)
```
## Usage: Advanced
```ts
import { CurrencyConverter } from "@suaveplan/currency";
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
import { CoinGeckoProvider } from "@suaveplan/currency-provider-coingecko/coingecko/coingecko";
const registry = new CurrencyProviderRegistry();
registry.register(
new CoinGeckoProvider({
apiKey: process.env.COINGECKO_PRO_KEY,
baseUrl: "https://pro-api.coingecko.com/api/v3",
}),
);
const converter = new CurrencyConverter({
provider: registry.get("coingecko"),
staleDurationMs: 60_000, // 1 minute — crypto rates move fast
});
// Targets are validated against the curated COIN_IDS map BEFORE the
// HTTP call — passing an unknown target raises UNKNOWN_CURRENCY
// without making a network round trip.
const controller = new AbortController();
const rates = await registry.get("coingecko").fetchRates({
base: "EUR",
targets: ["BTC", "ETH"],
signal: controller.signal,
});
```
## API Reference
### `class CoinGeckoProvider implements CurrencyProvider`
- `constructor(config?: CoinGeckoConfig)` — config is fully optional.
- `readonly name: "coingecko"`
- `readonly supportsCrypto: true`
- `readonly supportsFiat: true`
- `fetchRates(options?: RateFetchOptions): Promise<RateFetchResult>`
- Issues `GET ${baseUrl}/simple/price?ids=<curated-ids>&vs_currencies=<base.toLowerCase()>`.
- Forwards `options.signal` to the shared `httpRequest()` helper.
- Pre-validates `options.targets` against `COIN_IDS`; an unknown entry
raises `CurrencyProviderError(UNKNOWN_CURRENCY)` before any network
activity.
- Inverts upstream "1 crypto = X fiat" prices into the converter's
"1 base = X crypto" convention.
- Throws `CurrencyProviderError` with `code` ∈ {`HTTP_ERROR`,
`PARSE_ERROR`, `UPSTREAM_ERROR`, `UNKNOWN_CURRENCY`} carrying
`providerName === "coingecko"`.
### `interface CoinGeckoConfig extends NoKeyConfig`
- `readonly baseUrl?: string` — overrides `https://api.coingecko.com/api/v3`.
- `readonly fetch?: FetchLike` — overrides `globalThis.fetch`.
- `readonly apiKey?: string` — Pro-tier key, attached as
`x-cg-pro-api-key` header when present.
## Implementation Notes
- Pre-validating `options.targets` against `COIN_IDS` (not against the
upstream response) means the rejection message names the bad symbol
rather than swallowing it with a missing-key in the result map. This
is the difference between "your `ZZZ` symbol is not in our curated
set" (actionable) and "your rate result has no `ZZZ` field" (silent
defect).
- The shape guard (`isResponseShape`) accepts any value matching
`Record<string, Record<string, number>>` rather than asserting the
exact `bitcoin`/`ethereum` keys CoinGecko currently returns. The
upstream may add new coins or rename old ones, and a strict assertion
would treat that as a wire-format break instead of a benign superset.
- Headers are constructed once at instantiation time and frozen so the
per-request `init` spread is the only mutable copy — defends against
accidental key leakage into a downstream debugger.