{"id":51132674,"url":"https://github.com/igorjs/advisor","last_synced_at":"2026-06-25T14:30:31.326Z","repository":{"id":357236297,"uuid":"1235396029","full_name":"igorjs/advisor","owner":"igorjs","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-11T22:37:21.000Z","size":271,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-12T00:25:31.830Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/igorjs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-11T09:32:05.000Z","updated_at":"2026-05-11T22:37:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/igorjs/advisor","commit_stats":null,"previous_names":["igorjs/advisor"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/igorjs/advisor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorjs%2Fadvisor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorjs%2Fadvisor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorjs%2Fadvisor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorjs%2Fadvisor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/igorjs","download_url":"https://codeload.github.com/igorjs/advisor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorjs%2Fadvisor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34780124,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-25T14:30:29.969Z","updated_at":"2026-06-25T14:30:31.312Z","avatar_url":"https://github.com/igorjs.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Advisor\n\n\u003e **WARNING: THIS IS A TEST/DEMONSTRATION PROJECT.**\n\u003e\n\u003e This application is built as an experiment and **MUST NOT** be used as\n\u003e a source of real financial advice. The strategies, recommendations, and records\n\u003e generated by this software are produced by a large language model (LLM) and are\n\u003e for **demonstration purposes only**.\n\u003e\n\u003e **Use at your own risk.**\n\n## Exclusion of Liability\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED. THE AUTHORS AND COPYRIGHT HOLDERS SHALL NOT BE LIABLE FOR ANY CLAIM,\nDAMAGES, OR OTHER LIABILITY ARISING FROM THE USE OF THIS SOFTWARE.\n\nIN PARTICULAR:\n\n- **No financial advice.** Nothing produced by this application constitutes\n  financial, investment, legal, tax, or other professional advice.\n- **No fiduciary relationship.** Use of this software does not create any\n  advisory, fiduciary, or professional-services relationship.\n- **No accuracy guarantee.** LLM-generated content may be inaccurate,\n  incomplete, outdated, or entirely fabricated. Do not rely on it for any\n  real-world decision.\n- **No liability for losses.** The authors accept no responsibility for any\n  financial loss, damage, or other harm resulting from the use of, or reliance\n  on, this software or its outputs.\n\nBy using this software you acknowledge and accept these terms. If you do not\nagree, do not use this software.\n\nFor the full license terms, see [LICENSE](./LICENSE).\n\n---\n\nFull-stack agentic LLM advisor. Start a conversation, the AI agent interviews\nyou about your client's situation, researches the web, and produces structured\nadvisory records that you can review, edit, and manage.\n\n## Prerequisites\n\n- [Node.js](https://nodejs.org/) \u003e= 26 (LTS)\n- [pnpm](https://pnpm.io/) \u003e= 11\n\n## Quick Start\n\n```bash\n# Install dependencies\npnpm install\n\n# Set up environment\ncp .env.example .env\n# Edit .env with your API keys (see Environment below)\n\n# Run database migrations\npnpm db:migrate\n\n# Start development servers (client + server)\npnpm dev\n```\n\nThe client runs at [http://localhost:5173](http://localhost:5173) and proxies\nAPI requests to the server on port 3001.\n\n## Environment\n\n| Variable             | Required | Description                                     |\n| -------------------- | -------- | ----------------------------------------------- |\n| `LLM_API_KEY`        | Yes      | API key for the LLM provider                    |\n| `LLM_BASE_URL`       | No       | OpenAI-compatible base URL (defaults to OpenAI) |\n| `LLM_MODEL`          | No       | Model identifier (defaults to `gpt-4o-mini`)    |\n| `JINA_API_KEY`       | Yes      | Jina Search API key for web research            |\n| `TURSO_DATABASE_URL` | No       | Turso remote database URL                       |\n| `TURSO_AUTH_TOKEN`   | No       | Turso authentication token                      |\n| `RATE_LIMIT_MAX`     | No       | Requests per minute per IP (defaults to 100)    |\n\n### API Keys Setup\n\nThe app requires two API keys: one for the LLM and one for web search.\n\n**1. LLM provider (OpenRouter recommended)**\n\nOpenRouter gives access to multiple models (GPT-4o, Claude, Llama, etc.)\nthrough a single API key with pay-as-you-go pricing.\n\n1. Create an account at [openrouter.ai](https://openrouter.ai/)\n2. Go to [Keys](https://openrouter.ai/keys) and click \"Create Key\"\n3. Copy the key and add to your `.env`:\n\n```env\nLLM_API_KEY=sk-or-v1-your-key-here\nLLM_BASE_URL=https://openrouter.ai/api/v1\nLLM_MODEL=openai/gpt-4o-mini\n```\n\nAlternatively, use OpenAI directly:\n\n1. Create an account at [platform.openai.com](https://platform.openai.com/)\n2. Go to [API Keys](https://platform.openai.com/api-keys) and create a key\n3. Add to `.env` (no `LLM_BASE_URL` needed, defaults to OpenAI):\n\n```env\nLLM_API_KEY=sk-your-key-here\nLLM_MODEL=gpt-4o-mini\n```\n\n**2. Jina Search API (web research)**\n\nThe AI agent uses Jina to search the web for current tax rates and legislation.\n\n1. Create an account at [jina.ai](https://jina.ai/)\n2. Go to [API Keys](https://jina.ai/api-key) and generate a key\n3. Add to `.env`:\n\n```env\nJINA_API_KEY=jina_your-key-here\n```\n\n**3. Turso database (optional)**\n\nThe app uses a local SQLite file by default, no setup needed. For\nproduction/edge deployment, Turso provides a hosted SQLite-compatible\ndatabase with automatic replication.\n\n1. Create an account at [turso.tech](https://turso.tech/)\n2. Install the CLI: `brew install tursodatabase/tap/turso` (or see [docs](https://docs.turso.tech/cli/installation))\n3. Authenticate: `turso auth login`\n4. Create a database: `turso db create advisor`\n5. Get the connection URL: `turso db show advisor --url`\n6. Create an auth token: `turso db tokens create advisor`\n7. Add to `.env`:\n\n```env\nTURSO_DATABASE_URL=libsql://advisor-your-username.turso.io\nTURSO_AUTH_TOKEN=your-auth-token\n```\n\nWhen both are set, the app runs as an embedded replica with automatic sync.\nWhen absent, it falls back to `file:data/advisor.db` (zero config).\n\n## Architecture\n\n```\nadvisor/\n├── client/              # React 19 SPA (Vite + Tailwind CSS 4)\n│   └── src/\n│       ├── api/         # API client functions\n│       ├── components/  # Chat, records, forms, kbd hints, theme toggle\n│       ├── hooks/       # useChatStream, useConversationId, useHotkey\n│       ├── lib/         # Error toast helpers\n│       ├── locales/     # i18n translation files\n│       └── types/       # Shared type definitions\n├── server/              # Hono API server\n│   └── src/\n│       ├── config/      # LLM, rate-limit, search, system prompt\n│       ├── db/          # Drizzle ORM schema and migrations\n│       ├── dto/         # Data transfer objects (conversation, record, message)\n│       ├── lib/         # Result/Option types, HTTP helpers, extractRecords\n│       ├── middleware/   # CORS, security, rate-limiter, idempotency, logging\n│       ├── routes/      # HTTP route handlers (conversations, records, chat)\n│       └── services/    # Agent loop, conversation, record, LLM, search\n├── e2e/                 # Playwright end-to-end tests\n├── docs/dev/            # Design decisions and implementation plan\n└── package.json         # Root workspace configuration\n```\n\n### Key Design Decisions\n\n- **Agentic interview:** The AI asks one clarifying question at a time before\n  producing strategies. This creates a natural conversation flow rather than\n  dumping questions on the user.\n- **Two-phase records extraction:** If the LLM returns prose instead of JSON,\n  a focused second call with `response_format: json_object` converts it to\n  structured records. Handles model non-compliance gracefully.\n- **SSE streaming:** Agent events (searching, responding, records) stream to\n  the client in real-time via Server-Sent Events.\n- **Errors as values:** Services return `Result\u003cT, DomainError\u003e` instead of\n  throwing. Routes use a `matchResult()` helper.\n- **Message persistence:** Chat messages are stored in the DB and hydrated on\n  page refresh via the conversation API. A deterministic sentinel (`[records:N]`)\n  is stored for records-producing messages and rendered as localised text by\n  the client.\n- **Dark mode:** System preference detection on load, session-only toggle.\n  Class-based via `@variant dark` in Tailwind CSS 4 with no flash of wrong\n  theme (inline script in `\u003chead\u003e`).\n- **Zero type assertions:** No `as` casts, no `!` non-null assertions. Type\n  guards and null checks throughout.\n- **Functional core:** Result/Option types inspired by pure-fx, strict\n  TypeScript 6, `null` over `undefined`.\n\n## Tech Stack\n\n- **Frontend:** React 19, @tanstack/react-query, Tailwind CSS 4, react-i18next, sonner\n- **Backend:** Hono, Drizzle ORM, Turso/libSQL, OpenAI SDK, Pino, zod 4\n- **Testing:** Vitest (88 unit/integration tests), Playwright (30 e2e tests), axe-core accessibility\n- **Language:** TypeScript 6 (strict mode, no `any`, no `as` assertions)\n\n## API Endpoints\n\n```\nGET    /api/health                                             Health check\n\nPOST   /api/v1/conversations                                   Create a conversation\nGET    /api/v1/conversations/:id                               Get conversation with records + messages\nPATCH  /api/v1/conversations/:id                               Re-query with updated text\n\nPOST   /api/v1/conversations/:id/chat                          Send message (SSE stream)\nPOST   /api/v1/conversations/:id/edit/:messageId               Edit message and re-run (SSE stream)\n\nPATCH  /api/v1/conversations/:id/records/:recordId             Update a record\nDELETE /api/v1/conversations/:id/records/:recordId             Delete a record\n```\n\n## Scripts\n\n| Command            | Description                                      |\n| ------------------ | ------------------------------------------------ |\n| `pnpm dev`         | Start both client and server in development mode |\n| `pnpm test`        | Run all unit and integration tests (Vitest)      |\n| `pnpm test:e2e`    | Run e2e tests headed (Playwright, needs server)  |\n| `pnpm test:e2e:ci` | Run e2e tests headless (for CI pipelines)        |\n| `pnpm test:watch`  | Run server tests in watch mode                   |\n| `pnpm build`       | Build both client and server                     |\n| `pnpm db:generate` | Generate a new database migration                |\n| `pnpm db:migrate`  | Apply pending migrations                         |\n\n## Testing\n\n```bash\n# Unit + integration tests (fast, no server needed)\npnpm test\n\n# End-to-end tests headed (starts dev servers, real LLM calls)\npnpm test:e2e\n\n# End-to-end tests headless (CI)\npnpm test:e2e:ci\n\n# Watch mode for TDD\npnpm test:watch\n```\n\n### Unit and Integration Tests (88 tests)\n\n- **Unit:** Result/Option library, extractRecords JSON parsing, conversation DTO message filtering\n- **Middleware:** Error handler status mapping, rate limiter behaviour\n- **Service:** Conversation CRUD, record CRUD, re-query atomicity, LLM failure propagation\n- **API Integration:** Full HTTP request lifecycle via Hono `app.request()`, including records PATCH/DELETE routes\n\n### End-to-End Tests (30 tests, Playwright + axe-core)\n\n- **Landing page:** Title, subtitle, form validation, submit button states\n- **Conversation flow:** Prompt submission, URL routing, AI responses, follow-up messages\n- **Records panel:** Multi-turn interview to records, edit/delete buttons\n- **Persistence:** Message hydration on page refresh, sentinel rendering\n- **Navigation:** New chat, URL routing, error states\n- **Input UX:** Enter for newlines, Cmd+Enter to submit, focus management\n- **Dark mode:** System preference detection, toggle, visibility on both views\n- **Accessibility:** axe-core audit (critical + serious) on landing, chat, and dark mode\n- **Keyboard navigation:** `/` focus, Escape cancel, Cmd+Enter submit, Tab traversal\n\n## Design Docs\n\n- [Requirements](docs/dev/requirements.md) -- Initial project requirements\n- [Design Decisions](docs/dev/design.md) -- Architecture, API design, error handling philosophy\n- [Implementation Plan](docs/dev/implementation.md) -- File structure, build order, testing approach\n\n## License\n\nThis project is licensed under the\n[GNU Affero General Public License v3.0](./LICENSE)\n(SPDX: `AGPL-3.0-only`).\n\nSee the [LICENSE](./LICENSE) file for the full license text.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figorjs%2Fadvisor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Figorjs%2Fadvisor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figorjs%2Fadvisor/lists"}