Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What is ValueCell

ValueCell is a community-driven, multi-agent platform for financial applications. It combines a Python FastAPI backend with a React/Tauri frontend to deliver AI-powered stock research, trading strategy automation, and portfolio management via a multi-agent orchestration system.

## Development Commands

### Backend (Python)

```bash
# Install dependencies
uv sync

# Run backend in dev mode
uv run python -m valuecell.server.main

# Tests
uv run pytest ./python # All tests
uv run pytest ./python/tests/test_foo.py # Single file
uv run pytest -k "test_name" # Single test by name

# Lint & format
make lint # ruff check
make format # ruff format + isort
```

### Frontend

```bash
cd frontend
bun install
bun run dev # Dev server (port 1420)
bun run build # Production build
bun run typecheck # Type check (react-router typegen + tsc)
bun run lint # Biome lint
bun run lint:fix # Biome auto-fix
bun run format # Biome format
```

### Docker (full stack)

```bash
docker compose up -d --build # Start everything
docker compose logs -f # Follow logs
```

### Quick start (full dev environment)

```bash
bash start.sh # Linux/macOS — installs tools, syncs deps, starts both servers
```

## Architecture Overview

### Backend Layers

```
FastAPI (server/)
└── Orchestrator (core/coordinate/orchestrator.py)
├── Super Agent (core/super_agent/) — triage: answer OR hand off to Planner
├── Planner (core/plan/planner.py) — converts intent → ExecutionPlan; triggers HITL
├── Task Executor (core/task/executor.py) — runs plan tasks via A2A protocol
└── Event Router (core/event/) — maps A2A events → typed responses → UI stream
└── Conversation Store (core/conversation/) — SQLite persistence
Agents (agents/)
├── research_agent/ — SEC EDGAR-based company analysis
├── prompt_strategy_agent/ — LLM-driven trading strategies
├── grid_agent/ — grid trading automation
└── news_agent/ — news retrieval & scheduled delivery
Adapters (adapters/)
├── Yahoo Finance, AKShare, BaoStock — market data
├── CCXT — 40+ exchange integrations
└── EDGAR — SEC filing retrieval
Storage
├── SQLite (aiosqlite/SQLAlchemy async) — conversations, tasks, watchlists
└── LanceDB — vector embeddings
```

### Orchestration Flow

1. **Super Agent** — fast triage; either answers directly or enriches the query and hands off to Planner
2. **Planner** — produces a typed `ExecutionPlan`; detects missing params; blocks for Human-in-the-Loop (HITL) approval/clarification when needed
3. **Task Executor** — executes plan tasks asynchronously via Agent2Agent (A2A) protocol
4. **Event Router** — translates `TaskStatusUpdateEvent` → `BaseResponse` subtypes, annotates with stable `item_id`, streams to UI and persists

### Frontend Architecture

- **Framework**: React 19 + React Router 7 + Vite (rolldown)
- **Desktop**: Tauri 2 (cross-platform app wrapper)
- **State**: Zustand stores; TanStack React Query for server sync
- **Forms**: TanStack React Form + Zod
- **UI**: Radix UI headless components + Tailwind CSS 4 + shadcn
- **Charts**: ECharts + TradingView integration
- **i18n**: i18next (en, zh_CN, zh_TW, ja)
- Key entry points: `frontend/src/root.tsx` (routing), `frontend/src/app/agent/chat.tsx` (main chat UI)

### Configuration System (3-tier priority)

1. Environment variables (highest)
2. `.env` file
3. `python/configs/*.yaml` files (defaults: `config.yaml`, `providers/`, `agents/`, `agent_cards/`)

Copy `.env.example` to `.env` and set at least one LLM provider key (e.g. `OPENROUTER_API_KEY`).

## Code Conventions (from AGENTS.md)

- **Async-first**: all I/O must be async — use `httpx`, SQLAlchemy async, `anyio`
- **Type hints**: required on all public and internal APIs; prefer Pydantic models over `dict`
- **Imports**: avoid inline imports; use qualified imports for 3+ names from one module
- **Logging**: `loguru` with `{}` placeholders — `logger.info` for key events, `logger.warning` for recoverable errors, `logger.exception` only for unexpected errors
- **Error handling**: catch specific exceptions; max 2 nesting levels
- **Function size**: keep under 200 lines, max 10 parameters (prefer structs)
- **Runtime checks**: prefer Pydantic validation over `getattr`/`hasattr`
- **Python version**: 3.12+; package manager: `uv`; virtual env at `./python/.venv`
62 changes: 62 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# =============================================================================
# Valuecell — Docker Compose
#
# Services:
# backend - Python FastAPI (uvicorn), port 8000
# frontend - React/Vite (nginx), port 3200
#
# Usage:
# docker compose up -d --build
# docker compose logs -f
# =============================================================================

services:

# ---------------------------------------------------------------------------
# Backend — Python FastAPI
# ---------------------------------------------------------------------------
backend:
build:
context: ./python
dockerfile: ../docker/DockerFile
image: valuecell-backend:latest
container_name: valuecell_backend
restart: unless-stopped
env_file:
- ./python/.env
environment:
# Skip stdin control thread — stdin is closed in Docker non-interactive mode
# which would cause immediate shutdown via control_loop EOF handler
ENV: local_dev
volumes:
- ./python/configs:/app/configs
- valuecell_userdata:/root/.config/valuecell
ports:
- "8000:8000"
healthcheck:
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:8000/api/v1/system/health')\" || exit 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s

# ---------------------------------------------------------------------------
# Frontend — React/Vite (served via Nginx)
# ---------------------------------------------------------------------------
frontend:
build:
context: .
dockerfile: ./docker/Dockerfile.frontend
args:
VITE_API_BASE_URL: "http://10.11.2.150:8000/api/v1"
image: valuecell-frontend:latest
container_name: valuecell_frontend
restart: unless-stopped
ports:
- "3200:80"
depends_on:
backend:
condition: service_healthy

volumes:
valuecell_userdata:
2 changes: 1 addition & 1 deletion docker/DockerFile
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ RUN --mount=type=cache,target=/root/.cache/uv \
EXPOSE 8000

# Run the application.
CMD ["uv", "run", "python", "main.py", "--host", "0.0.0.0", "--port", "8000"]
CMD ["uv", "run", "python", "-m", "valuecell.server.main"]
23 changes: 23 additions & 0 deletions docker/Dockerfile.frontend
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# =============================================================================
# Valuecell Frontend — Pre-built Static Serve
#
# Frontend is pre-built on host (bun run build) due to CPU AVX requirement.
# This Dockerfile copies the build output and serves it with Nginx.
#
# To rebuild frontend:
# cd /root/.openclaw/apps/valuecell/frontend
# VITE_API_BASE_URL=http://10.11.2.150:8000/api/v1 bun run build
# docker compose build frontend
# =============================================================================

FROM nginx:alpine

# Copy pre-built frontend assets
COPY frontend/build/client /usr/share/nginx/html

# Copy nginx config
COPY docker/nginx.frontend.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
22 changes: 22 additions & 0 deletions docker/nginx.frontend.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
server {
listen 80;
server_name _;

root /usr/share/nginx/html;
index index.html;

# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

# SPA routing — fallback to index.html
location / {
try_files $uri $uri/ /index.html;
}

# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
28 changes: 28 additions & 0 deletions ecosystem.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module.exports = {
apps: [
{
name: 'valuecell-backend',
script: 'uv',
args: 'run python -m valuecell.server.main',
cwd: '/root/.openclaw/apps/valuecell/python',
env: {
NODE_ENV: 'production',
HOST: '0.0.0.0',
PORT: '8000',
},
interpreter: 'none',
},
{
name: 'valuecell-frontend',
script: 'bun',
args: 'run start',
cwd: '/root/.openclaw/apps/valuecell/frontend',
env: {
NODE_ENV: 'production',
PORT: '3200',
HOST: '0.0.0.0',
},
interpreter: 'none',
}
]
};
2 changes: 2 additions & 0 deletions frontend/src/api/setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,14 @@ export const useUpdateProviderConfig = () => {
provider: string;
api_key?: string;
base_url?: string;
auth_token?: string;
}) =>
apiClient.put<ApiResponse<null>>(
`/models/providers/${params.provider}/config`,
{
api_key: params.api_key,
base_url: params.base_url,
auth_token: params.auth_token,
},
),
onSuccess: (_data, variables) => {
Expand Down
Loading