@evan-moon/firma
Advanced tools
+1
-1
| { | ||
| "name": "@evan-moon/firma", | ||
| "version": "0.20.0", | ||
| "version": "0.21.0", | ||
| "mcpName": "io.github.evan-moon/firma", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+48
-236
@@ -12,4 +12,4 @@ <p align="center"> | ||
| <p align="center"> | ||
| Drop your trade history into Claude.<br/> | ||
| Get back a full financial picture — portfolio, net worth, cash flow — stored in a local SQLite file. | ||
| Drop your trade history into Claude. Get back the full picture —<br/> | ||
| portfolio, net worth, cash flow — stored in one local SQLite file. No account. No cloud. No catch. | ||
| </p> | ||
@@ -22,3 +22,2 @@ | ||
| <a href="https://nodejs.org/"><img src="https://img.shields.io/badge/node-%3E%3D22-339933?style=flat&logo=node.js&logoColor=white" alt="Node >= 22"></a> | ||
| <a href="https://glama.ai/mcp/servers/evan-moon/firma"><img src="https://glama.ai/mcp/servers/evan-moon/firma/badges/score.svg" alt="firma MCP server"></a> | ||
| <a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/MCP-ready-7c3aed?style=flat" alt="MCP-ready"></a> | ||
@@ -28,6 +27,2 @@ </p> | ||
| <p align="center"> | ||
| <code>npm install -g @evan-moon/firma</code> | ||
| </p> | ||
| <p align="center"> | ||
| <video src="https://github.com/user-attachments/assets/dbdaf14c-fc95-4c66-9d8e-8f674fb8aa7b" controls width="700"></video> | ||
@@ -58,14 +53,12 @@ </p> | ||
| ## Why firma | ||
| ## Your brokerage app shows you a number. firma shows you the truth. | ||
| - **Talks to Claude natively.** `firma mcp install` wires firma into Claude Desktop. Every command you can run in the terminal, Claude can call through MCP — log trades, sync prices, render charts, reason about your data. | ||
| - **Local-first, no exceptions.** Everything lives in `~/.firma/firma.db`. No server, no sync, no account. Your financial data never leaves your machine. | ||
| - **Multi-market, one stored currency.** Track holdings across 9 markets (US, KRX, KOSDAQ, JP, HK, LSE, XETRA, NSE, TW) — plus non-stock assets like crypto, commodities, and real estate. Every value is stored in USD and displayed in the currency you pick, with historical FX rates from FRED — so a 2018 balance shows the 2018 exchange rate, not today's. | ||
| - **Transactions as source of truth.** No holdings table to drift out of sync. Portfolio, cost basis, and P&L are always derived live from your trade log. | ||
| - **A daily brief that actually advises.** `get_market_brief` doesn't just dump data — it returns a weekly *stance* (lean aggressive or defensive), a ranked *recommendation* per holding (BUY / ADD / HOLD / TRIM / SELL / AVOID with conviction, time horizon, English thesis, and a `show_valuation` drill-down), and a *watchlist* of new-ticker candidates, all computed deterministically from your holdings, cash flow, and macro signals. Each holding is auto-classified into one of five categories (growth, quality, hedge, income, speculative) and run through a category-specific playbook that enforces position-size ceilings. A cross-cutting correlation guard then downgrades any BUY/ADD that would lift the portfolio's weighted-average 90-day pairwise correlation by more than 0.10 *and* push it above 0.70, so "more of what you already own" gets caught before it lands. The same call bundles macro signals, commodity prices, the dividend calendar, global macro (World Bank), and active disaster alerts (GDACS) — no extra API keys needed for the global data. | ||
| - **A simulator nobody else ships.** `project_net_worth` runs a multi-scenario Monte Carlo (1000-iteration GBM, shared seeded shocks across scenarios) over *your* portfolio at *any* horizon — next month for short-term outlook or 30 years for FIRE planning, one tool. You supply the (return, vol) assumptions for each scenario and Claude discloses them verbatim; firma never bakes in defaults. Returns P10 / P25 / P50 / P75 / P90 paths, probability of reaching an optional target, and median months to reach. Diagnostic and thought-experiment tools stay strictly separated — the brief never invents future numbers, projections always show their assumptions. (See [Forward projection](#forward-projection) below.) | ||
| - **Time-series features work on day one.** The first `firma sync` after install backfills 90 days of daily-close history from Yahoo's keyless chart endpoint — full `shares × close` snapshot rows for everything you hold and price-only rows for watchlist peers. Correlation, `effective_n`, Sharpe, max drawdown, annualized volatility, and CAGR are populated immediately, instead of waiting 30+ days for snapshots to accrue. Subsequent syncs skip tickers that already have coverage, and a single ticker failing on Yahoo never poisons the rest of the run. | ||
| - **Fund-manager voice, not data dump.** Claude talks back like a private fund manager — decisive ("MSFT ADD high-conviction, not before Thu PCE"), evidence-based (cites the firma fields behind every call), micro-macro bridged (every macro statement ends with a dollar number on your book, not "could pressure tech"). The brief's recommendations layer + a Voice-and-judgment instruction layer keeps the answer at the level a paid advisor would give — and refuses to fabricate when data is missing. | ||
| - **Tracks your decisions, not just your positions.** `show_thesis_track` joins your recorded `reason` per trade to current outcomes (validated / underwater / cut_loss). `show_anti_portfolio` follows the post-exit prices of positions you fully sold (missed_rebound / good_call) — the data brokerages deliberately don't show, because it highlights bad sells. Six months from now, Claude reads your trade reasons back to you with proof. | ||
| - **Two clean tool families.** Diagnostic (`get_market_brief`, every `show_*`, `report`) returns *currently true* facts — no future-looking probabilities. Thought-experiment (`project_net_worth`, `simulate_scenario`) returns conditional outcomes only when you supply assumptions, and Claude discloses them verbatim. The brief never invents future numbers; projections always show their math. | ||
| - **It talks to Claude. Natively.** Every command lives behind MCP — log trades, sync prices, render charts, reason about your money. You type plain English; Claude does the rest. | ||
| - **Local-first, no exceptions.** Everything sits in `~/.firma/firma.db`. No server, no sync, no account. Your financial data never leaves your machine. Ever. | ||
| - **Nine markets, one source of truth.** US, KRX, KOSDAQ, JP, HK, LSE, XETRA, NSE, TW — plus crypto, commodities, and real estate. Stored in USD, shown in any currency, with *historical* FX from FRED: a 2018 balance gets the 2018 rate, not today's. | ||
| - **No holdings table to lie to you.** Portfolio, cost basis, P&L — all derived live from your trade log. Nothing drifts out of sync, because there's nothing to drift. | ||
| - **A brief that actually has an opinion.** `get_market_brief` doesn't dump data — it takes a stance. A weekly lean (aggressive or defensive), a ranked call per holding (BUY / ADD / HOLD / TRIM / SELL / AVOID with conviction and an English thesis), and a watchlist of what to look at next. Every holding is classified, run through a category playbook, and checked by a correlation guard that catches "more of what you already own" before it lands. | ||
| - **A simulator nobody else ships.** `project_net_worth` runs multi-scenario Monte Carlo over *your* portfolio at *any* horizon — next month or 30 years, one tool. `simulate_scenario` does the deterministic day-to-week version ("PCE comes in hot Thursday — how much does that cost me?"). You supply the assumptions, Claude discloses them verbatim, firma does the math. No hidden "advisor view." | ||
| - **Time-series features from day one.** The first `firma sync` backfills 90 days of daily history. Correlation, Sharpe, max drawdown, volatility, CAGR — populated immediately, not 30 days from now. | ||
| - **It tracks your decisions, not just your positions.** `show_thesis_track` reads your own trade reasons back to you against the outcome. `show_anti_portfolio` follows what you sold — the data brokerages hide because it exposes bad exits. | ||
@@ -76,18 +69,2 @@ --- | ||
| Three ways to install — all work the same way once set up. | ||
| ### macOS Installer | ||
| Download and double-click. Node.js is bundled — nothing to install first. Claude Desktop and Claude Code are connected automatically; a setup dialog then shows the two `firma config set` commands for your free API keys. | ||
| **[↓ Download firma-mac-arm64.pkg](https://github.com/evan-moon/firma/releases/latest/download/firma-mac-arm64.pkg)** | ||
| ### Windows Installer | ||
| Download and run. Node.js is bundled and `firma` is added to your `PATH` — nothing to install first. Claude Desktop and Claude Code are connected automatically; a setup dialog then shows the two `firma config set` commands for your free API keys. The installer is unsigned — if SmartScreen warns, choose **More info → Run anyway**. | ||
| **[↓ Download firma-win-x64.exe](https://github.com/evan-moon/firma/releases/latest/download/firma-win-x64.exe)** | ||
| ### npm | ||
| ```bash | ||
@@ -101,214 +78,64 @@ # 1. Install | ||
| # 3. Connect Claude Desktop | ||
| # 3. Connect Claude Desktop, then restart it | ||
| firma mcp install | ||
| # Restart Claude Desktop — firma tools will appear in the toolbar. | ||
| ``` | ||
| **Drop your data into Claude.** Paste a CSV, brokerage export, or plain text — Claude logs everything and syncs prices automatically. | ||
| **Rather not touch a terminal?** Grab the [macOS](https://github.com/evan-moon/firma/releases/latest/download/firma-mac-arm64.pkg) or [Windows](https://github.com/evan-moon/firma/releases/latest/download/firma-win-x64.exe) installer — Node.js is bundled, Claude is wired up for you. | ||
| > Prefer the terminal? The CLI is the safety net — setup, manual entry, repair, and a few `show` commands for quick inspection. Analysis, briefs, and projections go through Claude. See [CLI reference](#cli-reference). | ||
| Then **drop your data into Claude** — a CSV, a brokerage export, plain text. It logs everything and syncs prices automatically. | ||
| > If firma helps you track your money, please [⭐ star the repo](https://github.com/evan-moon/firma/stargazers) — it's the cheapest way to help others find it. | ||
| > If firma helps you see where your money went, [⭐ star the repo](https://github.com/evan-moon/firma/stargazers) — it's the cheapest way to help others find it. | ||
| --- | ||
| ## CLI reference | ||
| ## How it's split: MCP thinks, the CLI keeps you safe | ||
| The CLI is the safety net — three things only: **setup, manual data entry, and repair**. Anything analytical (brief, recommendations, projections, news, valuation, macro) goes through Claude. | ||
| firma is **MCP-first**. Analysis, briefs, recommendations, news, valuation, projections — all of it happens in conversation with Claude. The CLI exists for the three things a chat can't safely own: **setup, manual data entry, and repair.** | ||
| ### Setup | ||
| ```bash | ||
| # Setup | ||
| firma config set <key> # finnhub-key / fred-key / currency | ||
| firma mcp install # register with Claude Desktop | ||
| firma doctor # check keys, data state, FX cache, coverage | ||
| | Command | What it does | | ||
| |---|---| | ||
| | `firma config set finnhub-key KEY` | Set Finnhub API key (prices, news, earnings) | | ||
| | `firma config set fred-key KEY` | Set FRED API key (macro & FX history) | | ||
| | `firma config set currency CODE` | Set display currency (USD, KRW, JPY, …) | | ||
| | `firma config get [key]` | Show config values | | ||
| | `firma mcp install` | Register MCP server in Claude Desktop | | ||
| | `firma doctor` | Check API keys, data state, FX cache, time-series coverage | | ||
| | `firma profile` | Set up your goals (birth year, retirement target, target net worth, risk tolerance, notes) — every field optional | | ||
| # Manual entry | ||
| firma add txn # buy / sell / deposit / dividend / tax — any of 9 markets | ||
| firma add monthly # month-end balance + cash flow in one flow | ||
| firma add snapshot # sync prices, snapshot today's portfolio | ||
| ### Manual data entry | ||
| # Repair (when Claude or you got it wrong) | ||
| firma edit txn / balance / flow / snapshot | ||
| firma delete txn / ... # alias: firma rm | ||
| | Command | What it does | | ||
| |---|---| | ||
| | `firma add txn` | Record a transaction — a stock in any of 9 markets, or a non-stock asset (crypto / commodity / real estate) — type buy / sell / deposit / dividend / tax | | ||
| | `firma add balance [-p YYYY-MM]` | Monthly asset & liability snapshot | | ||
| | `firma add flow [-p YYYY-MM]` | Monthly income & expense entry | | ||
| | `firma add monthly [-p YYYY-MM]` | Balance + flow in one call (month-end) | | ||
| | `firma add snapshot` | Sync prices and record today's portfolio snapshot | | ||
| | `firma add price [name] [value]` | Set the current value of non-stock holdings (they have no price feed) | | ||
| # Verify (read-only — just confirm what landed in the DB) | ||
| firma show portfolio / txns / balance / flow / snapshot / profile / fx | ||
| ### Repair (when Claude or you got something wrong) | ||
| # Sync | ||
| firma sync # prices (Finnhub) + FX history (FRED) + correlation cache | ||
| firma sync fx # FX history only | ||
| ``` | ||
| | Command | What it does | | ||
| |---|---| | ||
| | `firma edit txn / balance / flow / snapshot` | Edit an existing entry (interactive picker) | | ||
| | `firma delete txn / balance / flow / snapshot` | Delete an entry. Alias: `firma rm` | | ||
| Everything analytical — `get_market_brief`, every `show_*`, `report`, `project_net_worth`, `simulate_scenario` — lives in Claude. Two tool families that never mix: **diagnostic** (what's true right now, no fortune-telling) and **thought experiment** (what-ifs, assumptions always disclosed). | ||
| ### Verify (quick inspection) | ||
| | Command | What it does | | ||
| |---|---| | ||
| | `firma show portfolio` | Holdings table with P&L, avg cost, market value, sector/country allocation, FX return decomposition (for non-USD display currency) | | ||
| | `firma show txns [ticker]` | Transaction history | | ||
| | `firma show balance [period]` | Stored balance entries | | ||
| | `firma show flow [period]` | Stored cash flow entries | | ||
| | `firma show snapshot [ticker]` | Portfolio value history; `--from`/`--to` for date range | | ||
| | `firma show profile` | Show the stored profile | | ||
| | `firma show fx [currency]` | Inspect cached FX history with `--from`/`--to`/`--limit` | | ||
| ### Sync | ||
| | Command | What it does | | ||
| |---|---| | ||
| | `firma sync` | Fetch latest prices (Finnhub) + FX rate history (FRED). On the first run, also pulls 90 days of Yahoo daily-close history for held stocks and watchlist peers so correlation / risk metrics work immediately. Idempotent — later runs skip tickers already covered | | ||
| | `firma sync fx` | FX history only — incremental backfill from your earliest entry date | | ||
| --- | ||
| ## Claude integration (MCP) | ||
| After `firma mcp install`, Claude has full read/write access to your data through conversation. Tools split into **two epistemic families** — Claude is told never to mix them: | ||
| - **Diagnostic** — `get_market_brief`, every `show_*`, `report`. Returns *currently true* facts: deterministic, rule-derived, no forward-looking probability distributions. The brief never invents future numbers. | ||
| - **Thought experiment** — `project_net_worth` (stochastic Monte Carlo distribution over months/years) and `simulate_scenario` (deterministic conditional point-estimate for shocks within days/weeks). Caller must supply assumptions (return + vol, or shock magnitudes), and Claude discloses them verbatim. Firma never bakes in defaults. | ||
| **Voice and judgment.** On top of the data and tools, firma ships a fund-manager voice layer in its server instructions: 4 explicit principles that govern *how* Claude answers — decisiveness over neutrality, every macro statement must end with a dollar number on your book, behavioral anchors (`show_thesis_track` on buys, `show_anti_portfolio` on sells), and every BUY/HOLD/SELL stance must cite the firma field that justifies it. Vague hedge phrases like "could pressure tech" or "조건부로 보라" are explicit violations. This is why firma's answers read like a paid advisor's, not a chatbot's data dump. | ||
| `get_market_brief` is the primary diagnostic entry point. In a single call it returns: | ||
| - **Stance** — a deterministic weekly verdict (aggressive / mixed / defensive / contrarian-aggressive) at the intersection of market signal and your personal capacity (runway, goal pace, idle cash) | ||
| - **Recommendations** — a per-ticker `recommendations[]` array, one entry per holding, with action (BUY / ADD / HOLD / TRIM / SELL / AVOID), conviction (high / medium / low), time horizon (tactical / strategic / long), an English thesis bullet list, earnings catalysts, the playbook inputs used to reach the call, any conflicts raised by the cross-cutting guards, and a `drill_down` pointer to the next tool. Each holding is classified into growth / quality / hedge / income / speculative and rated by the matching playbook; results are ranked by severity × conviction so TRIM/SELL/AVOID surface first. A correlation guard runs across every BUY/ADD candidate: if a starter position would lift the portfolio's weighted-average 90d pairwise correlation by more than 0.10 *and* push it above 0.70, the conviction is downgraded one tier and a `correlation_guard` conflict is appended. Claude reads the array top-down and narrates the verdict; it doesn't reorder or re-derive the calls. | ||
| - **Watchlist** — new-ticker candidates surfaced from peers of what you already hold, reordered within each relation so the lowest-correlation candidates come first | ||
| - **Freshness** — last-synced dates for prices and FX, so Claude knows when to `sync` first | ||
| - **Portfolio** — holdings with weights, daily P&L, total cost vs market value | ||
| - **Concentration** — HHI by ticker, sector, currency, country, plus `effective_n` and the 90d pairwise correlation matrix (`firma sync` rebuilds the cache from snapshot history) | ||
| - **Movers** — top winners and losers for the day | ||
| - **News** — recent headlines per holding (with rule-derived sentiment + mechanism attribution) | ||
| - **Earnings** — upcoming earnings dates with EPS estimates | ||
| - **Economic calendar** — high/medium-impact events for the week | ||
| - **Macro** — FRED snapshot (VIX, 10Y yield, credit spread, fed funds, FX impact in home currency) | ||
| - **Stress & Regime** — Economic Stress Index + Risk-on / Mixed / Risk-off bias | ||
| - **Commodities** — WTI oil, gold, copper (via FRED — no extra key) | ||
| - **Dividend calendar** — upcoming ex-dates and estimated income for held positions | ||
| - **Goal tracking** — measured CAGR vs needed CAGR (no projected net worth — forward-looking probabilities route to `project_net_worth`) | ||
| If today's snapshot is missing, `get_market_brief` records one automatically before assembling the brief, so daily history accrues even when you forget to run `firma add snapshot`. | ||
| Diagnostic drill-down tools: `show_portfolio`, `show_txns`, `show_balance`, `show_flow`, `show_snapshot`, `show_concentration`, `show_dividend`, `show_news`, `show_financials`, `show_valuation`, `show_earnings`, `show_macro`, `show_fx`, `show_risk`, `show_benchmark`, `show_profile`, `show_thesis_track` (past trades with recorded `reason` ↔ outcome — for thesis review and behavioral anchoring), `show_anti_portfolio` (post-exit performance of fully-sold positions — surfaces missed_rebound vs good_call labels with summary counts). | ||
| **MCP Resources** (attach as context with `@firma:…`): `firma://portfolio` (current holdings table), `firma://thesis-log` (recent trades with reasons), `firma://profile` (your goals). Use these in Claude Code when you want the model to reason about your portfolio without burning a tool call. | ||
| ### Forward projection | ||
| `project_net_worth` is the only thought-experiment tool today. Unlike a generic Monte Carlo calculator, it operates on *your actual portfolio* and treats assumption disclosure as a hard contract. | ||
| - **Multi-scenario in one call.** Pass up to 8 scenarios (e.g. bear / base / bull, or "historical vs stressed"). Each scenario has its own label + `expected_annual_return` + `annual_volatility`. All scenarios share the same seeded shock sequence so cross-scenario differences reflect (return, vol) — not random noise. Apples-to-apples. | ||
| - **Any horizon, one tool.** `horizon_months` accepts 1 (next-month outlook) through 720 (60-year FIRE planning). Same GBM math, same fan-chart output. No artificial short/long split. | ||
| - **Optional target.** Supply `target_value` for `probability_of_reaching_target` + `median_months_to_target`. Omit it for the pure distribution at the horizon. | ||
| - **Caller-supplied assumptions, always disclosed.** Claude is required to surface the (return, vol) it used for each scenario verbatim — so the user sees what's modeled, not a hidden "advisor view." A natural anchor scenario is the user's *measured* `risk_summary.annualized_return_pct` and `annualized_vol_pct` from the brief — Claude reads those and uses them as one scenario among others. | ||
| - **Output.** Per scenario: P10 / P25 / P50 / P75 / P90 final values, percentile trajectories (yearly resolution by default; monthly available), probability of reaching target, median months to reach, plus the (return, vol) echoed back. 1000 iterations per scenario by default (P-band ±1.5%); tunable up to 10000 for smoother fan charts. | ||
| > "Will I retire on time?" — Claude pulls your `target_net_worth` and `retirement_target_year` from your profile, your `current_net_worth` and 90d realized vol from the brief, runs `project_net_worth` with three scenarios (historical-stats / bear / bull), shows the fan and the probability per scenario, and discloses every assumption. The brief never claims a future number. | ||
| ### Conditional what-if (`simulate_scenario`) | ||
| For the *day-to-week* horizon — "what if PCE comes in hot Thursday", "TSLA earnings miss by 10%", "S&P −2% tomorrow" — `simulate_scenario` is the deterministic sibling of `project_net_worth`. You supply the shocks (the assumption), it walks every holding via per-ticker beta (pulled from `holdings[].fundamentals.beta_5y`), and returns point-estimate P&L impact per holding and portfolio total. Optionally pass `daily_vol_pct` from the brief and each scenario also returns a `one_sigma_range_usd`, so a hypothetical move can be sized against your portfolio's natural daily volatility ("a 1.3σ event, not a tail"). High-impact macro events in the brief (FOMC, CPI, PCE, NFP within 7 days) auto-trigger this chain — Claude doesn't wait for you to ask. | ||
| **Prompts** — type `/` in Claude Desktop to access guided workflows. Firma ships five MCP prompts (no need to memorize — `/` opens autocomplete): | ||
| | Prompt | What it does | | ||
| |---|---| | ||
| | `import-trades` | Confirms column mapping then inserts every row in a single `add_txn` call — handles 100s of rows in one shot | | ||
| | `import-balance` | Same flow for a net-worth spreadsheet (rows = months, columns = balance categories) → one `add_balance` call | | ||
| | `import-flow` | Same flow for an income/expense spreadsheet → one `add_flow` call | | ||
| | `month-end` | Walks through balance + cash flow entry for the period, submits in one batch | | ||
| | `setup-profile` | Captures your goals (birth year, retirement target, target net worth, risk tolerance, notes) so all future analysis is anchored to them — every field optional | | ||
| **Reasoning frameworks** (no slash command — Claude applies them automatically based on how you phrase the question): | ||
| | Framework | Triggered when… | | ||
| |---|---| | ||
| | `pre-mortem` | You're considering a buy or seem anchored to a single thesis — Claude generates five plausible failure modes tied to actual data, with portfolio impact in dollars | | ||
| | `rebalance` | Concentration drift or "how's my balance" questions — current vs target, specific trades, wash-sale checks | | ||
| | `tax-harvest` | Year-end or tax-related questions — loss scan, wash-sale rule, replacements, savings by bracket | | ||
| | `scenario` | "What if X happens" questions — routed to `simulate_scenario` with explicit beta assumptions Claude discloses | | ||
| `setup_status` (called by Claude at session start) returns the `analyst_context` block — the fund-manager persona principles + data-gating rules + the four frameworks above. This is loaded on every conversation, so the right framework fires without you typing anything. | ||
| Historical FX note: `firma sync fx` backfills daily FX rates (KRW, JPY, EUR, CNY, GBP, HKD, INR, TWD) from FRED starting at your earliest transaction date. Subsequent runs are increment-only. This means a 2018 balance in KRW uses the 2018 rate — not today's. | ||
| --- | ||
| ## Architecture | ||
| Firma is a Yarn Berry monorepo with a layered, port-and-adapter design. The application layer never imports an external-API package directly — it talks to domain interfaces, and adapters implement those interfaces against specific services. | ||
| A Yarn Berry monorepo with a strict port-and-adapter layout. The application layer never imports an external API directly — it talks to domain interfaces, and adapters implement them. | ||
| Workspace layout reflects the layers: | ||
| ``` | ||
| packages/ | ||
| domain/ ports + types + routers, no external-API dep | ||
| domain/ market/ (Quote, StockSnapshot, Candle, news, …), | ||
| macro/ (series, country indicators, live FX, stress, | ||
| regime), event/ (world disaster events) | ||
| external-api/ raw API clients, no firma-domain dep | ||
| finnhub/ fred/ worldbank/ gdacs/ open-er-api/ yahoo-finance/ | ||
| adapter/ the only layer that imports both sides | ||
| adapters/ provider registry — createMarketProviders, … | ||
| use-case/ business logic | ||
| portfolio/ brief/ advisor/ | ||
| shared/ cross-cutting | ||
| db/ utils/ | ||
| domain/ ports + types, zero external-API knowledge | ||
| external-api/ raw clients (finnhub, fred, worldbank, gdacs, yahoo, …), zero domain knowledge | ||
| adapter/ the only layer that imports both sides | ||
| use-case/ business logic (portfolio, brief, advisor, projection, scenario) | ||
| shared/ db + utils | ||
| apps/ | ||
| cli/ mcp/ docs/ | ||
| cli/ mcp/ docs/ | ||
| ``` | ||
| Import dependencies (`A → B` means "A imports B"): | ||
| **The rule:** external-API packages know nothing about firma's domain; domain packages know nothing about any external API. `@firma/adapters` is the single bridge. Adding a data source touches no command and no business logic — a new raw client, one adapter, one registration line. | ||
| ``` | ||
| apps/cli, apps/mcp | ||
| → @firma/adapters data-clients.ts builds providers via the registry | ||
| → @firma/domain commands/tools call domain clients directly | ||
| for simple reads (e.g. `show news`) | ||
| → use-case packages richer flows (portfolio sync, brief) | ||
| See [CONTRIBUTING.md](CONTRIBUTING.md) for the full walkthrough and the recipe for adding a provider. | ||
| use-case (portfolio, brief, advisor) | ||
| → @firma/domain brief also composes @firma/advisor | ||
| @firma/adapters | ||
| → @firma/domain implements MarketDataProvider / MacroDataProvider / | ||
| EventDataProvider | ||
| → external-api packages calls the raw HTTP/RSS clients | ||
| external-api (finnhub, fred, worldbank, gdacs, open-er-api, yahoo-finance) | ||
| → shared only pure API clients; no domain/adapter knowledge | ||
| @firma/domain | ||
| → shared only the stable core; no external-api / adapter / | ||
| use-case / app dependency | ||
| shared (db, utils) | ||
| → nothing cross-cutting leaf; imported by every layer above | ||
| ``` | ||
| At runtime, data flows the opposite way: | ||
| ``` | ||
| External API → @firma/adapters → domain client → use-case → cli / mcp | ||
| ``` | ||
| **Dependency rule:** external-API packages have zero firma-domain knowledge; domain packages have zero external-API knowledge. `@firma/adapters` is the only package that imports both sides. Application code (`apps/cli`, `apps/mcp`) talks to domain clients; the one place it touches `@firma/adapters` is `data-clients.ts`, which constructs the concrete providers. | ||
| **Capabilities** — each domain interface declares a capability set; the router picks a provider per capability: | ||
| | Sub-domain | Capabilities | | ||
| |---|---| | ||
| | `market` | quote, snapshot, candles, news, insider, financials, earnings, dividends, peers, economic-calendar | | ||
| | `macro` | series, metadata, search, country-indicator, live-fx | | ||
| | `event` | disasters | | ||
| **Why this matters:** adding a data source touches no command or business-logic code. Adding KRX coverage, for example, means a new `packages/external-api/naver/` raw client, a `createNaverProvider(): MarketDataProvider` in `packages/adapter/adapters/src/`, and one registration line in `packages/adapter/adapters/src/registry.ts`. See [CONTRIBUTING.md](CONTRIBUTING.md) for the full recipe. | ||
| ## Development | ||
@@ -321,10 +148,7 @@ | ||
| yarn install | ||
| yarn dev:cli show portfolio # CLI dev mode | ||
| yarn typecheck # Full type check | ||
| yarn test # Run unit tests | ||
| yarn typecheck | ||
| yarn test | ||
| ``` | ||
| See [CONTRIBUTING.md](CONTRIBUTING.md) for the full architecture walkthrough and the recipe for adding a new external data provider. | ||
| --- | ||
@@ -334,20 +158,8 @@ | ||
| Firma is the financial intelligence layer of the [Herald](https://ai-herald.vercel.app) ambient voice assistant stack. When connected, Herald can: | ||
| firma is the financial-intelligence layer of the [Herald](https://ai-herald.vercel.app) ambient voice stack. Connected, Herald reports your net worth by voice, answers "how much did I spend on fees this quarter?" without a spreadsheet, and pulls market context mid-conversation. Herald + firma + [Memex](https://github.com/evan-moon/memex): ambient voice, financial intelligence, persistent memory — one personal AI stack. | ||
| - report your portfolio performance and net worth by voice | ||
| - answer "how much did I spend on fees this quarter?" without opening a spreadsheet | ||
| - surface market context mid-conversation alongside your own position data | ||
| Herald + Firma + [Memex](https://github.com/evan-moon/memex) — ambient voice, financial intelligence, and persistent memory in one personal AI stack. | ||
| --- | ||
| ## llms.txt | ||
| [`llms.txt`](llms.txt) is a machine-readable summary of this project for LLM agents — concise description with documentation links, following the [llms.txt standard](https://llmstxt.org/). | ||
| --- | ||
| ## License | ||
| MIT © [Evan Moon](https://github.com/evan-moon) |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
734510
1.05%3048
3.64%158
-54.34%15
7.14%