# TTS Workshop Search the Tabletop Simulator Steam Workshop, fetch full TTS save files, and analyze their contents. A lightweight, client-only pnpm monorepo. ## Packages | Package | Role | Runtime | | ------------------- | ------------------------------------------------------ | ---------- | | `apps/web` | React frontend: search + mod pages | Browser | | `apps/proxy` | Hono HTTP server: Workshop search + save fetch | Node | | `packages/tts` | Fetch save from Steam, BSON-parse to `TTSMod` | Node | | `packages/extract` | Analyze a `TTSMod`: objects, asset refs, downloads | Isomorphic | | `packages/shared` | Shared types + zod schemas | Isomorphic | See [`docs/architecture.md`](docs/architecture.md) for the architecture and [`docs/implementation-plan.md`](docs/implementation-plan.md) for the plan. ## Setup ```sh pnpm install cp .env.example .env # then set STEAM_API_KEY pnpm dev # runs the proxy at http://localhost:3000 ``` Get a Steam Web API key at https://steamcommunity.com/dev/apikey (free). To run the frontend alongside the proxy, open a second terminal and run `pnpm --filter @tts/web dev` (serves at http://localhost:5173 and proxies `/search`, `/items`, and `/health` to the backend). ## API | Method | Path | Description | | ------ | ----------------- | -------------------------------------------- | | GET | `/health` | Liveness | | GET | `/search?q=&page=`| Search the Workshop (scrapes browse page) | | GET | `/items/:id` | Full parsed `TTSMod` (BSON save) | | GET | `/items/:id/file` | Raw save bytes, filename from header | ## Commands ```sh pnpm dev # run the proxy (tsx watch) pnpm dev:web # run the frontend (vite) pnpm build # compile all packages pnpm typecheck # typecheck all packages pnpm test # run the unit tests (vitest) pnpm lint # lint all packages ``` ## How search works Steam has no official search API. The proxy fetches the Workshop browse page (`steamcommunity.com/workshop/browse/?appid=286160`) and parses the embedded `window.SSR.renderContext` JSON (a React Query cache containing a `workshop_browse` entry with the results). This is more robust than scraping the DOM, but Steam can still change the page structure — if search breaks, that parser is the first place to look. ## Notes - `STEAM_API_KEY` is optional. Search works without it. The mod page works without it too when you arrive from a search result: the frontend passes the item's `file_url` (already present in search results) to `/items/:id` via a `fileUrl` query param, so the proxy downloads the save directly instead of calling the Steam API. Without a key *and* without a `fileUrl` (e.g. a deep-linked mod page), `/items/*` returns 500. - Each request fetches fresh; there is no caching by design (client-only tool). - `packages/extract` has zero runtime dependencies and runs in browser or Node, ready for a future frontend.