Get started locally
# one time git clone https://github.com/Johnnyohay/anu.git && cd anu npm ci # refresh the data (all free, no API keys) npm run collect # defense news + today's brief npm run housing # Zillow prices, rents, forecast, inventory; Freddie Mac rates; housing news npm run videos # new videos and Shorts from config/videos.yaml # apply config/*.yaml and rebuild the JSON the pages read npm run build:api # look at it npx serve public # open http://localhost:3000 (Docs tab: /docs.html) npm run app # Ask ANU with a free local model (see README)
Push to main and the GitHub Action does the same every morning at 10:00 UTC and publishes the site. A push that only changes config/ or public/ redeploys within a few minutes.
Architecture
Collectors pull free public sources into data/ (committed, so history builds up). build-api checks your YAML, adds your custom attributes, and writes static JSON to public/api/v1/. Every page and Ask ANU read only that JSON. There's no server or database, and nothing costs money.
Orange-edged boxes are the parts you customize without code.
What you can change
| File | What it controls | Apply with |
|---|---|---|
config/anu.yaml | Tabs and their order, feature flags, the housing map's starting view, budget choices, rental-math defaults, ANU score weights, custom attributes | npm run build:api (score weights and rental defaults: npm run housing first) |
config/videos.yaml | The YouTube channels on the Watch tab, their topics, how long videos stay, keywords for mixed channels | npm run videos, then build:api |
config/requests.yaml | Your feature wishlist, shown below with status | npm run build:api |
data/sectors.json, companies.json, ideas.json, research.json, catalysts.json | The defense desk's hand-written knowledge: sector briefs, company directory, build ideas, reading list, upcoming events | npm run build:api |
src/collect.ts (FEEDS, SECTOR_RULES) | Defense news sources and how stories are sorted into sectors | npm run collect |
src/housing.ts (NEWS_FEEDS, TOPIC_RULES) | Housing news sources and topic tags | npm run housing |
src/prompts.ts | How Ask ANU talks, and its modes (Teach me, Housing: where to buy, Something to watch…) | restart npm run app |
public/desk.css | Colors, fonts and spacing for the Housing, Watch and Docs tabs (light and dark) | reload the page |
Mistakes in a YAML file stop build:api with the file, the setting and what's wrong (for example unknown field "prise"), so a typo never reaches the live site.
Tabs
desks: in config/anu.yaml sets the tab switcher at the top of every page. Reorder the list, rename a label, or set enabled: false to hide one. Current tabs:
| id | Label | Page | Shown |
|---|
Feature flags
Each flag switches one feature on or off. A flag that isn't listed counts as on. To add a flag for a new feature, add a line under flags: and read it in the page with ANU.flag(CFG, "your.flag") (see Adding features in code). Current values:
| Flag | State | What it does |
|---|
Custom attributes
A custom attribute is a new metric you define in YAML as a formula over the numbers ANU already has for every metro. You don't write any page code. Each one automatically:
- appears in the housing map's Color by menu, with its own legend;
- is aggregated to states (median, mean, min, max, sum or count of the state's metros) to shade the map behind the dots;
- becomes a sortable column in the rankings if
column: true; - shows in each metro's panel, and reaches Ask ANU (in
get_metroandrank_metrosresults ascustom).
# config/anu.yaml → housing.attributes - id: rentBurden # letters and digits; used in URLs and the API label: Rent as % of a $90k income formula: rent * 12 / 90000 * 100 # + - * / ( ), numbers, min() max() abs() round() format: percent # money kmoney signedMoney percent percent2 signedPercent number color: seq # seq = one hue low→high · div = two hues around zero aggregate: median # how metros roll up to a state column: true # add to the rankings table higherIsBetter: false # default sort direction
Fields a formula can use: . A metro missing any field in the formula gets no value and shows gray. Formulas are read by ANU's own small calculator (src/config.ts), never run as code.
| id | Label | Formula | Format · color · states | Column |
|---|
Housing defaults
Video channels
The Watch tab and the housing Shorts strip come from config/videos.yaml. To add a channel, open it on YouTube, view the page source, search for externalId (starts with UC) and add a line. Any new topic name becomes a filter on the Watch tab.
| Channel | Topic | Channel id |
|---|
ANU embeds and links videos and never downloads them. TikTok and Instagram have no free public feeds and don't allow scraping, so they aren't collected.
Feature requests
Write what you want next in config/requests.yaml (title, area, status, notes and, if it should be switchable, the flag it will use). Then ask Claude Code in the repo: “Build the planned requests in config/requests.yaml.” Mark them done when they ship.
| Request | Area | Status | Notes |
|---|
Adding features in code
- A switchable page feature: add
my.feature: trueunderflags:, then in the page:if (ANU.flag(CFG, "my.feature")) renderMyFeature();. Add a row toFLAG_INFOindocs.htmlso it's described here. - A new setting: add it to the schema in
src/config.ts(it's validated and published inconfig.jsonautomatically), then read it fromCFGin the page. - A new built-in metro metric (from a new data source): compute it in
collectMarket()insrc/housing.ts, add it to theMetrotype andSORTSinsrc/housing-kb.ts, toFORMULA_FIELDSinsrc/config.ts, and toMETRICSinhousing.html. - A new tab: copy
videos.htmlas a starting point (it loadsdesk.cssanddesk.js), add it underdesks:, and publish its data fromsrc/build-api.ts. - A new Ask ANU lookup: add a query in
src/housing-kb.tsorsrc/kb.tsand a tool insrc/tools.ts; the local app, the CLI (src/kb-cli.ts) and the Claude Code agent (.claude/agents/anu.md) can all use it.
File map
config/ | YAML settings: anu.yaml (site), videos.yaml (channels), requests.yaml (wishlist) |
src/config.ts | Loads and validates the YAML; the formula calculator and state roll-ups for custom attributes |
src/collect.ts | Daily defense news collector and free morning brief |
src/housing.ts | Housing collector: Zillow, Freddie Mac, housing news; rental math and ANU score |
src/housing-coords.ts | One-time: places metros on the map from the Census gazetteer (npm run housing:coords) |
src/housing-kb.ts | Housing data types and read-only queries (used by Ask ANU and the CLI) |
src/videos.ts | Video collector from YouTube's public channel feeds |
src/rss.ts | Shared RSS/Atom reader |
src/build-api.ts | Writes public/api/v1/, including config.json |
src/kb.ts, tools.ts, prompts.ts, anu.ts, server.ts | Ask ANU: queries, tools, instructions, the agent loop and the local app server |
public/ | The site: index.html, housing.html, videos.html, docs.html, shared desk.css and desk.js |
.github/workflows/daily.yml | Runs every collector at 10:00 UTC, commits data/, deploys GitHub Pages |