Single source of truth for AI coding assistants (Cursor, Claude Code, Codex, and similar).
For Cursor Cloud, use the same guidance as the rest of this file: What this project is, Commands, Architecture, Conventions, and Notes for agents. No separate Cursor-only setup, env files, or backend is required.
- Node.js via nvm: The update script installs Node.js 20 via nvm. Source nvm before running npm commands:
export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh". - Dev server:
npm run devstarts Vite on port 5173 with--host(accessible on all interfaces). No external services are needed. - Testing without API keys: Sending requests without provider API keys configured produces a 401 error — this is expected behavior and confirms the request pipeline is working.
Roshi is a client-only React + TypeScript + Vite SPA — a Postman-like UI for calling LLM provider APIs. There is no backend service, no server database, and no Docker. Data lives in the browser (IndexedDB via Dexie.js for providers/history/settings; localStorage for theme preference).
| Action | Command |
|---|---|
| Install dependencies | npm install |
| Dev server | npm run dev — Vite on port 5173 (--host enabled) |
| Lint | npm run lint |
| Typecheck only | npm run typecheck — tsc -b |
| Format (write) | npm run format — Prettier |
| Format (check) | npm run format:check |
| Production build | npm run build — tsc -b && vite build |
| Preview production build | npm run preview |
| Tests (single run) | npm run test — Vitest + jsdom |
| Tests (watch) | npm run test:watch |
| Test coverage | npm run test:coverage — V8 provider + thresholds |
| Tauri (desktop) dev | npm run tauri:dev |
| Tauri (desktop) build | npm run tauri:build — platform packages (e.g. macOS) |
Pre-commit: Lefthook runs Prettier, ESLint, and typecheck sequentially (lefthook.yml). Prettier and ESLint auto-fix staged files and restage fixes. Run npx lefthook install after clone if hooks are not active.
Validate non-trivial changes with npm run test, npm run build, and npm run lint.
The app sends HTTP requests with fetch, streams SSE with eventsource-parser, and persists history and settings to IndexedDB.
- User edits the composer (
composer-store: messages, parameters, custom headers). useSendRequestvalidates input and callsllm-client.sendRequest().- The adapter (
src/adapters/) mapsNormalizedRequestto the provider’s wire format, runsfetch, and parses the response intoNormalizedResponse/ stream chunks. - response-store holds loading, streaming text, errors, and raw request/response payloads.
- A history entry is written to IndexedDB; UI updates via Zustand selectors.
request-store.ts is a thin re-export of composer-store and response-store for backward compatibility; prefer importing those stores directly.
ProviderAdapter is defined in src/adapters/types.ts. getAdapter() in src/adapters/index.ts picks the implementation by ProviderConfig.type:
openai-compatibleandcustom→openaiAdapter(Chat Completions–style JSON).anthropic→anthropicAdapter(Anthropic Messages API).google-gemini→ currently the same as OpenAI-compatible (fallback until a dedicated adapter exists).
- provider-store — providers, API keys, models, selection (providers in IndexedDB; selection in IndexedDB
settings). - composer-store — composer payload (messages, temperature, tokens, headers, etc.).
- response-store — last run: streaming buffer, parsed response, errors, raw JSON.
- history-store — history list from IndexedDB.
- theme-store — light/dark; preference persisted in localStorage (
llm-tester-theme).
src/dev/dev-proxy-plugin.ts exposes /api/proxy?url=... so the Vite dev server can forward requests and ease CORS during development. The packaged Tauri app calls provider URLs directly. Treat /api/proxy as optional dev tooling, not a required part of v1 behavior.
Built-in provider models are loaded from the external models.dev API, filtered by provider name.
Seeded from src/providers/builtins.ts: OpenAI, Anthropic, OpenRouter (see that file for base URLs and auth). Users can add more providers in the UI.
- Imports: path alias
@/*→src/*(seevite.config.ts,tsconfig.app.json). Prefer@/…over deep relative paths. - shadcn/ui v4: built on
@base-ui/react, not Radix. There is noasChild— use therenderprop where needed. - Tailwind v4: theme and plugins live in
src/index.css(@plugin,@theme). Colors useoklch(). - TypeScript:
erasableSyntaxOnlyis on — no parameter properties in constructors. - react-resizable-panels v4: use
orientation, notdirection. - Files: kebab-case filenames; PascalCase components/types;
UPPER_SNAKE_CASEconstants. - Dark mode:
darkclass on<html>, driven bytheme-store. - Exports: named exports only (no default exports) unless an ecosystem tool requires otherwise.
- Vitest tests live next to source (
src/**/*.test.tspervitest.config.ts). Coverage is intentionally scoped to non-visual logic invitest.config.ts; thresholds are enforced when runningtest:coverage(95% lines/functions/statements/branches). - ESLint may report pre-existing issues (e.g. unused variables, conditional hooks in
CodeView.tsx, react-refresh noise in generated UI). Do not assume new edits caused all warnings. - Users enter API keys in the UI; secrets are stored locally in IndexedDB. No
.envor server-side secrets are required for the app to run. - Git workflow: after finishing implementation work that changed files, always commit and push:
- Run validation (
npm run typecheck,npm run test,npm run lint) for non-trivial changes. - Review with
git statusandgit diff; stage relevant files (never secrets like.env). - Commit with a clear, conventional message focused on why, not just what.
- Push to the current branch (
git pushorgit push -u origin HEADif needed). Skip commit/push for read-only tasks, when there are no file changes, or when the user explicitly says not to.
- Run validation (
- Git tags: always use non-interactive mode:
git tag -m "vX.Y.Z" vX.Y.Z. Do not use annotated tags without-mas they open an interactive editor. - After non-trivial changes, always run all three validation commands before considering the task complete:
npm run typecheck npm run test npm run lint