agentsclimarketplace

Fx dashboard widgets

Skill nusantara-ventures/exchangerate-skills/skills/fx-dashboard-widgets

Agent Skills for exchangerate.dev — FX rates API. Currency conversion, FX correctness, portfolio translation, multi-currency pricing. Works keyless.

Install
npx -y skills add nusantara-ventures/exchangerate-skills --skill fx-dashboard-widgets

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Build live currency/FX rate displays — a rate ticker, dashboard card, rate table, sparkline/chart, currency converter widget, Slack/CLI rate bot, or watch-a-pair alert. Use whenever the user wants to show exchange rates in a UI, even something as simple as "show the EUR/USD rate on my dashboard" — this skill covers the poller architecture, session-aware refresh, and truthful "as of" labeling so the widget doesn't burn API quota or lie about freshness. Powered by exchangerate.dev (keyless base https://api.exchangerate.dev).

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

7.7 KB, as published. Nobody here has run it

FX dashboard widgets

Rate displays fail in two ways: they hammer the API from every open browser tab, or they show "live" on a Saturday. Both are architecture bugs, not styling bugs. This skill is the pattern for building a currency ticker, card, table, chart, or converter that's fast, quota-safe, and honest about freshness. Examples use exchangerate.dev (keyless, https://api.exchangerate.dev).

1. One poller, many clients — never per-browser API calls

A dashboard with 50 open tabs must not make 50 API calls. Poll once, server-side, cache the result, and serve every client from the cache:

[exchangerate.dev] <--poll-- [your server, 1 poller] --serves--> [N browser clients]

Why this matters beyond politeness: keyless calls are 12 req/min per IP. One poller comfortably fits inside that; N independent browser tabs polling directly will collide on the same IP bucket and start 429ing each other in production.

Live currencies reprice roughly every 60 seconds on trading days (which currencies are live can change — read the per-currency sources map rather than assuming) — polling faster than 60s buys you nothing, you're just re-fetching the same data_updated_at. A sane poll interval is 30-60s during market_session: open, checked against the rate-limit headers on every response:

x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset

If x-ratelimit-remaining hits 0, or you get a 429, back off until x-ratelimit-reset (unix seconds) — don't retry blind, and don't tighten your poll interval to "catch up."

2. Session-aware polling — don't poll a closed market

Every /v1/latest response carries market_session. Use it to drive the poll cadence, not just a fixed timer:

market_sessionMeaningPoll behavior
openInterbank tradingNormal cadence (30-60s)
weekendMarkets closed Sat/SunDrop to hourly, or stop — show "market closed" state
interbank_closedPost-NY-close / pre-Sydney-open gapDrop to hourly, or stop

Rates genuinely do not change outside open — polling every 30s through a weekend is pure quota burn for a number that cannot move. Drive the next poll delay off the session (open → ~45s, anything else → hourly), and resume normal cadence the moment market_session flips back to open.

3. UI truthfulness — say what you know, not what looks good

  • Never show "live" unconditionally. Show data_updated_at — "updated 14:02 UTC" — and let the user judge freshness themselves.
  • A weekend flatline is correct, not a bug. Don't chase it with a bogus "no data" state. Label the last point: "Fri close" or "as of Fri 21:59 UTC", sourced from data_updated_at on the last open-session poll.
  • Mixed tables need per-row provenance. A table with EUR (live) next to a smaller currency (ecb_daily) should not present both the same way. Use the per-currency sources map from /v1/latest to badge each row — "live" vs "daily fix" — rather than trusting the top-level source (which reports the least-fresh tier across the whole response).
  • market_session drives a banner, not just the poller. When session is weekend or interbank_closed, show a small "market closed" indicator next to the rate instead of a pulsing "live" dot.

4. Delta/change display — deltas need a real reference point

"+0.3%" is meaningless unless you say against what. The correct reference is the previous business day's close, not your own last poll:

# today's rate
curl "https://api.exchangerate.dev/v1/latest?base=USD&symbols=EUR"

# yesterday's close for the delta — mind weekends
curl "https://api.exchangerate.dev/v1/2026-07-03?base=USD&symbols=EUR"

Two traps:

  • "Yesterday" on Monday is Friday. Don't naively subtract one calendar day — either compute the prior business day yourself, or just request calendar-yesterday and check is_forward_filled (if true on a Monday request for Sunday, it forward-filled to Friday, which is what you want anyway).
  • Don't compute deltas against your own last poll. That's "change since I last checked," which drifts depending on when your poller happened to run — not a meaningful market statistic. Anchor every delta to a specific business-day close.

5. Sparklines and charts

curl "https://api.exchangerate.dev/v1/range?base=USD&symbols=EUR&start_date=2026-06-01&end_date=2026-07-06"

Row shape (business days only — weekends are absent rows, not nulls):

{"date":"2024-01-02","rates":{"EUR":0.91274},"source":"ecb_daily","is_forward_filled":false,"derived_symbols":[]}
  • Plot on a time axis, not an index axis. If you plot by array index, weekend gaps silently compress out and every week looks the same length. A time (date) x-axis renders the gaps correctly — a 3-day gap over a weekend looks like 3 days, not 1.
  • Paginate beyond 366 rows. One page maxes at 366 rows; check has_more and follow next_cursor for longer ranges — don't assume a year of daily data fits in one call.

6. Wiring it together

The server-side loop is: fetch /v1/latest → if 429 or x-ratelimit-remaining is 0, reschedule for x-ratelimit-reset → else cache rates, sources, market_session, data_updated_at and reschedule off the session (§2: ~45s when open, hourly otherwise). Expose the cache on your own /rates route; the frontend polls that (cheap, no external rate limit) and renders data_updated_at + market_session next to the numbers. Nothing in that loop is exotic — the load-bearing parts are the reset-aware backoff (§1) and the session-driven cadence (§2), already specified above; keep them wired together and the rest is a plain setTimeout + in-memory object.

One-liner CLI ticker, for a terminal dashboard or cron-driven Slack post:

curl -s "https://api.exchangerate.dev/v1/rate/eur-usd" | jq -r '"\(.pair): \(.rate) (\(.market_session), updated \(.data_updated_at))"'

7. Converter widgets

curl "https://api.exchangerate.dev/v1/convert/EUR/USD/100"

Use the server's converted field directly — it's already rounded to the target currency's minor units (0 for JPY, 3 for KWD, 2 for most). Don't multiply rate client-side and re-round; you'll drift from the API's own rounding on edge cases.

Debounce input. A converter bound to an <input> should debounce (250-400ms after the user stops typing) before calling /v1/convert — not fire a request per keystroke. Each call is metered; a fast typist on a 4-digit amount can burn 4+ calls for one intended conversion.

Related skills in this repo

  • exchangerate-dev — the API itself: auth, endpoints, MCP server, error handling.
  • fx-rates-correctness — weekend gaps, forward-fill, precision, triangulation, staleness.
  • multi-currency-pricing — adding multi-currency display prices to an app (different problem: pricing, not live-rate widgets).

For agent-hosted dashboards (a Claude/Cursor session building the widget interactively), the exchangerate.dev MCP server (list_currencies, get_rate, convert, get_range, search_docs) is a viable alternative to hand-rolled REST calls during development — swap to the REST poller above for the shipped, production widget.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.