项目事实来源:
docs/project/index.md— spec、issues、多 Agent 交接日志均在该目录。下文为仓库地图与开发命令(L2),新任务请先读 project 文档再动手。
Workspace guide for innate-feeds. This repository contains a full-stack web application for discovering and browsing GitHub trending and starred repositories.
Innate Feeds is a full-stack web app that:
- Displays GitHub trending repositories (daily, weekly, monthly snapshots).
- Displays GitHub starred repositories for the authenticated user.
- Supports filtering by language, topic, search term, snapshot date, stars range, and sorting by stars / updated / created / starred date.
- Syncs data from GitHub via the
ghCLI and/or Firecrawl. - Supports dual deployment modes: API mode (Hono backend + SQLite) and static mode (GitHub Pages with pre-exported JSON data).
innate-feeds/
├── backend/ # Hono API server + CLI + sync logic + data export/import
│ ├── src/
│ │ ├── app/ # Application entry points
│ │ │ ├── server.ts # Hono HTTP server, API routes
│ │ │ └── cli.ts # Command-line interface for sync/list/stats
│ │ ├── collector/ # Data collection layer
│ │ │ ├── github.ts # GitHub API / gh CLI wrappers (uses execFileSync)
│ │ │ ├── firecrawl.ts # Firecrawl-based GitHub Trending scraper
│ │ │ └── sync.ts # Trending and starred sync orchestration
│ │ ├── data/ # Static data export/import utilities
│ │ │ ├── export-incremental.ts # Incremental JSON chunk exporter
│ │ │ ├── export-static.ts # Full static exporter
│ │ │ ├── import-static.ts # Import static JSON into SQLite
│ │ │ ├── manifest-utils.ts # Manifest read/write helpers
│ │ │ └── hidden-store.ts # Hidden ("deleted") items store (~/.innate/hidden.json)
│ │ └── db/ # SQLite database layer
│ │ ├── index.ts # Connection, queries, CRUD helpers
│ │ ├── schema.sql # SQLite schema (trending_repos, starred_repos, topics)
│ │ └── paths.ts # Database path resolution (INNATE_HOME / DB_PATH)
│ ├── package.json
│ ├── tsconfig.json
│ └── feeds.db* # Runtime SQLite database (WAL mode)
├── frontend/ # TanStack Router + Vite + React 19 + Tailwind v4
│ ├── src/
│ │ ├── pages/ # TanStack Router route definitions (page.tsx + route.tsx)
│ │ │ ├── __root/ # Root layout with sidebar, header, category panel
│ │ │ ├── index/ # Redirects to /trending
│ │ │ ├── trending/ # Trending repos page
│ │ │ └── starred/ # Starred repos page
│ │ ├── components/ # AppHeader, AppSidebar, CategoryPanel, FeedCard, FilterBar, StatsCards
│ │ ├── hooks/ # usePersistedFeedFilters
│ │ ├── services/ # API client (feeds.ts) — supports both API and static modes
│ │ ├── types/ # TypeScript domain types (feed.ts)
│ │ ├── lib/ # utils.ts (cn, formatNumber, formatDate), theme.tsx, feed-filters-storage.ts
│ │ ├── themes/ # CSS theme files (linear.css, notion.css)
│ │ ├── main.tsx # React entry point
│ │ ├── router.tsx # Route tree assembly
│ │ └── styles.css # Tailwind CSS v4 theme + dark mode
│ ├── index.html
│ ├── package.json
│ ├── tsconfig.json
│ └── vite.config.ts # Vite config with GitHub Pages plugin + API proxy
├── git-repo-scanner/ # Standalone Go CLI (not part of the web app)
├── docs/ # Documentation
├── tasks/ # Task working directories
├── package.json # Root workspace scripts (uses concurrently)
├── dev.sh # Bash helper to start both dev servers
└── CLAUDE.md
| Layer | Technology |
|---|---|
| Runtime | Bun / Node.js 18+ |
| Frontend framework | React 19 |
| Routing | TanStack Router (manual route registration, not file-based) |
| Build tool | Vite 6 |
| Styling | Tailwind CSS v4 with CSS-based theme configuration |
| UI utilities | lucide-react, clsx, tailwind-merge, sonner (toasts), next-themes |
| Backend framework | Hono 4 |
| HTTP server | @hono/node-server |
| Database | SQLite via better-sqlite3 |
| Data fetching | GitHub CLI (gh) and Firecrawl |
| Validation | Zod (used in API input validation) |
| Type checking | TypeScript 5.7+ |
| Side utility | Go 1.26+ (git-repo-scanner) |
All commands assume you are in the project root unless noted.
bun install
bun run install:all # or: cd backend && bun install && cd ../frontend && bun install# Start backend (http://localhost:4000) and frontend (http://localhost:3000)
bun run dev
# Or use the shell helper
./dev.sh
# Individually
bun run dev:backend # cd backend && bun run dev
bun run dev:frontend # cd frontend && bun run devThe Vite dev server proxies /api requests to http://localhost:4000.
These require the gh CLI to be installed and authenticated (gh auth status).
# From the repo root
cd backend
# Sync trending repos for all periods
bun run sync:trending
# Sync only daily / weekly / monthly
bun run sync:daily
bun run sync:weekly
bun run sync:monthly
# Sync authenticated user's starred repos
bun run sync:starred
# Sync only recently starred (last 24h) — incremental
bun run sync:starred:recent
# Last 90 days of digest issues + current trending + starred window + READMEs
bun run sync:window
# or from repo root: bun run data:sync:window
# CLI equivalents
bunx tsx src/app/cli.ts sync all-trending
bunx tsx src/app/cli.ts sync trending daily
bunx tsx src/app/cli.ts sync starred [username] [--force] [--days N]
bunx tsx src/app/cli.ts sync digest --days 90
bunx tsx src/app/cli.ts sync window [--days 90] [--skip-readme] [--force]cd backend
bun run list # List feed items
bun run list:trending # List trending items
bun run list:starred # List starred items
bun run dates # List available trending snapshot dates
bun run stats # Show database statisticscd backend
# Export incremental JSON chunks (for GitHub Pages static mode)
bun run export:incremental
# Full static export
bun run export:static
# Import static JSON data back into SQLite
bun run import:staticcd frontend
bun run dev # Dev server on port 3000
bun run build # Production build to frontend/dist/
bun run preview # Preview production build# From repo root — sync/export first (see docs/data-update-workflow.md), then:
bun run build:static
# Full 90-day snapshot then static build
bun run data:update:window && bun run build:static
# Or with custom base path for project pages
VITE_BASE_PATH=/your-repo bun run build:pagescd backend && bun run typecheck
cd frontend && bun run typecheckbun run format:ts # Format all TS files with Prettier
bun run format:ts:check # Check formatting without writing┌─────────────────┐ /api/* ┌─────────────────────────────┐
│ Vite dev server│ ─────────────────> │ Hono server (backend/src/ │
│ port 3000 │ (proxied) │ app/server.ts) port 4000 │
└─────────────────┘ └─────────────────────────────┘
│
┌──────────────────────────┼──────────────────────────┐
▼ ▼ ▼
better-sqlite3 sync.ts / cli.ts gh / Firecrawl
(feeds.db) github.ts firecrawl.ts
- API mode (default): Frontend calls
/api/*endpoints, backed by SQLite. README disk cache (./readmes) is returned immediately; the backend refreshes from GitHub in the background. Digest is served from the newest local dump (~/.innate/digestorfrontend/public/data/digest.json), with a live GitHub fallback if the dump is empty. - Static mode (
VITE_STATIC_MODE=true): Frontend fetches pre-exported JSON from/data/(manifest.jsonchunks,digest.json). No backend. The browser still calls GitHub live for digest freshness and READMEs, and falls back to bundled/data/readmes/{owner}/{repo}.mdwhen live fetch fails.
Both modes are meant to be used together: bun run data:update:window writes snapshots into the repo, then bun run build:static ships them. Live fetch keeps Pages from going stale between deploys.
GitHub Trending has no historical API. A 90-day window syncs current daily/weekly/monthly lists, starred repos in that window, digest issues created in that window, and README prefetch for those repos.
| Method | Path | Description |
|---|---|---|
| GET | /api/feeds |
List feed items. Query params: type (trending or starred), language, topics (comma-separated), search, sort (stars| updated| created| starred), order (asc| desc), date, starsMin, starsMax, page, pageSize. |
| GET | /api/feeds/stats |
Aggregate stats: total repos, trending count, starred count, top languages. |
| GET | /api/feeds/languages |
All distinct repository languages. |
| GET | /api/feeds/dates |
Available trending snapshot dates. |
| POST | /api/feeds/sync |
Trigger sync. Body validated with Zod: { type: "trending" | "starred" | "all-trending", period?, username?, force?, days? }. |
| POST | /api/feeds/hide |
Hide an item so it is filtered out of all list/detail responses. Body: { kind: "digest" | "repo", id } (id = digest item id, or repo fullName). Persisted to hidden.json next to the DB; also applied at static-export time. |
| POST | /api/feeds/unhide |
Undo a hide. Same body as /api/feeds/hide. |
- Trending:
sync.tscallsfetchTrendingWithFirecrawl()first. If Firecrawl returns no results, it falls back tofetchTrendingRepos(), which scrapeshttps://github.com/trendingviagh apiand then fetches full repo metadata via the GitHub API. - Starred:
sync.tscallsfetchStarredReposWithDate(), which paginates throughgh api user/starred(orusers/{username}/starred) using theapplication/vnd.github.v3.star+jsonaccept header to obtainstarred_attimestamps. Supports incremental sync viastopAt/daysparameters. - Both pipelines call
upsertTrendingRepo()/upsertStarredRepo(),insertTrendingTopics()/insertStarredTopics()inside a singlebetter-sqlite3transaction. - Window sync (
sync window, default 90 days): current trending + starred since cutoff + digest issues created in-window + README prefetch into./readmesandfrontend/public/data/readmes. - Digest:
issues-digest.tswrites JSON (not SQLite).data:exportcopies the newest dump tofrontend/public/data/digest.json. - README (API):
fetchRepoReadme()is cache-first with a background remote refresh. README (static / browser): live GitHub first, bundled/data/readmesfallback. Batch prefetch skips files newer than 7 days unless--force.
SQLite database file: ~/.innate/feeds.db by default (configurable via DB_PATH or INNATE_HOME env vars). Uses WAL mode with foreign keys enabled.
Schema is defined in backend/src/db/schema.sql:
trending_repos— trending repository snapshots (composite text PKtrending-{date}-{period}-{repoId}, includesperiod,snapshot_date).trending_repo_topics— many-to-many topics for trending repos.starred_repos— starred repository metadata (integer PK = GitHub repo ID, includesstarred_at).starred_repo_topics— many-to-many topics for starred repos.trending_snapshots— historical snapshot analytics table (defined but not yet written to by sync code).
- ES modules everywhere (
"type": "module"in bothpackage.jsonfiles). - Strict TypeScript is enabled (
strict: true). - Path alias:
@/*maps tosrc/*in the frontend. In the backend, local imports use relative paths with explicit.jsextensions (e.g.,import { … } from "./db/index.js"). - No test suite is currently present.
app/server.ts— Hono HTTP server with Zod input validation, global error handler, and try/catch on all routes.app/cli.ts— mirrors the sync/list/stats functionality for command-line use.collector/github.ts— wraps theghCLI withexecFileSync(notexecSync) to prevent shell injection. Validatesusernameagainst a whitelist regex.collector/firecrawl.ts— uses thefirecrawlSDK to scrape GitHub Trending with a JSON extraction schema. Generates deterministic repo IDs from SHA-256 hash offullName.collector/sync.ts— orchestrates trending/starred sync with transactional writes.collector/sync-window.ts— 90-day window: trending + starred + digest + README prefetch for static/API snapshots.data/export-incremental.ts— exports data as incremental JSON chunks with a manifest for static mode.data/import-static.ts— imports static JSON back into SQLite.db/index.ts— all SQL lives here. Uses prepared statements frombetter-sqlite3. Batch-fetches topics to avoid N+1 queries. Exports typed interfaces (FeedItemDTO,FeedStatsDTO,TrendingItemRow,StarredItemRow).db/paths.ts— resolves database path viaDB_PATHenv var orINNATE_HOME(defaults to~/.innate).
router.tsxmanually wires routes (__root,index,trending,starred).indexredirects to/trending. Supports GitHub Pages base path.- Page components (
trending/page.tsx,starred/page.tsx) manage state withuseState/useEffect/useCallbackand callservices/feeds.ts. FilterBarimplements 300ms search debouncing to avoid excessive API calls.usePersistedFeedFiltershook persists filter state to localStorage.- Components use
React.forwardRefand accept aclassNameprop merged via thecn()utility. services/feeds.tssupports both API and static modes. In static mode, fetches JSON chunks viamanifest.json, merges digestdigest.jsonwith live GitHub, and prefers live READMEs with a static-file fallback.services/hidden.tstracks user-hidden items (localStorage +/api/feeds/hide; static mode also merges the exportedhidden.json).feeds.tsfilters them out of every list/detail response.styles.cssdefines a Tailwind v4 theme with CSS custom properties and a.darkvariant. Additional themes available viathemes/linear.cssandthemes/notion.css.
main.gois a single-file CLI.- Recursively walks a folder, detects
.gitdirectories, parses.git/configforremote "origin"URLs. - Supports GitHub and GitLab API enrichment.
| If you are… | Go to… |
|---|---|
| Adding or changing API endpoints | backend/src/app/server.ts |
| Changing how repos are fetched from GitHub | backend/src/collector/github.ts |
| Changing trending/starred/window sync | backend/src/collector/sync.ts, sync-window.ts, firecrawl.ts |
| Changing the 90-day window / README prefetch | backend/src/collector/sync-window.ts |
| Changing digest issues sync | backend/src/collector/issues-digest.ts |
| Changing the database schema or queries | backend/src/db/schema.sql and backend/src/db/index.ts |
| Changing database path resolution | backend/src/db/paths.ts |
| Adding new CLI commands | backend/src/app/cli.ts and backend/package.json scripts |
| Changing GitHub Pages deploy / data cron | .github/workflows/deploy.yml |
| Changing pages / routes | frontend/src/pages/ and frontend/src/router.tsx |
| Changing UI components | frontend/src/components/ |
| Changing API client | frontend/src/services/feeds.ts |
| Changing types shared between frontend and backend concepts | frontend/src/types/feed.ts (backend has its own internal types in db/index.ts) |
| Changing styling / theme | frontend/src/styles.css and frontend/src/themes/ |
| Updating the git scanner | git-repo-scanner/main.go |
- The backend enables CORS for all origins (
app.use("/*", cors())). If deployed publicly, restrict this to known origins. - No authentication or authorization is implemented on API endpoints. The sync endpoints should not be exposed to untrusted users.
github.tsusesexecFileSyncwith argument arrays (not string interpolation) to prevent shell injection. Theusernameparameter is validated against/^[\w.-]{1,39}$/.- POST
/api/feeds/syncvalidates request body with Zod schema before processing. - All GET API routes have try/catch error handling and return structured JSON errors.
- The SQLite database path is controlled by the
DB_PATHenvironment variable or defaults to~/.innate/feeds.db. Ensure the database file is not served or committed. - Environment variables:
PORT(backend port, default 4000),DB_PATH(SQLite path),INNATE_HOME(data directory, default~/.innate),VITE_STATIC_MODE(frontend static mode),VITE_BASE_PATH(base URL for GitHub Pages).
- CI:
.github/workflows/ci.yml(test, typecheck, format, build). Pages:.github/workflows/deploy.yml. - For production API mode:
bun run startbuilds the frontend and serves it from the backend athttp://localhost:4000(same origin as/api). Bind withHOST/PORTas needed. - For GitHub Pages: daily cron runs
sync window(90 days) then deploysfrontend/dist/. Manual Run workflow can choose window / daily / skip. Seedocs/data-update-workflow.md. - Dev still uses Vite on port 3000 with
/apiproxied to the backend.