# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview SeaFare Montana is a maritime logistics AI agent platform. A single-page web app with a Flask API backend, Claude API agent with tool-calling, and PostgreSQL database (SQLite in dev). Deployed on Render.com free tier (Python 3.11) with auto-deploy from `master`. ## Commands ```bash # Run locally python seafare_api.py # Starts Flask on port 5050 # Production (Render) gunicorn seafare_api:app --bind 0.0.0.0:$PORT --workers 1 --threads 2 --timeout 120 # Check Python syntax before committing (all core files) python -m py_compile seafare_api.py && python -m py_compile seafare_agent.py && python -m py_compile maritime_db.py && python -m py_compile moltbook_bot.py && python -m py_compile telegram_bot.py && python -m py_compile ais_provider.py && python -m py_compile equasis_parser.py && python -m py_compile marinetraffic_parser.py && python -m py_compile maritime_compliance.py # Run tests python -m pytest tests/ -v --tb=short # Deploy: push to master triggers Render auto-deploy git add && git commit -m "message" && git push ``` Pre-commit hook runs automatically: syntax check → pytest → .env secret guard. ## Architecture ``` index.html ─── Single-page frontend (vanilla JS, Leaflet map, no framework) ↓ fetch /api/v1/* seafare_api.py ─── Flask REST API (auth, wallet, chat, admin endpoints) ↓ generate_response() seafare_agent.py ─── Claude API agent with 26 tools (tool-calling loop, max 7 iterations) ↓ _TOOL_DISPATCH dict → handler functions maritime_db.py ─── Database layer (19 tables, PostgreSQL/SQLite dual-mode) ↑ external data config.py ─── Centralized constants (version, rate limits, cache TTLs, fees, env var names) marinetraffic_parser.py ─── Ports DB (16,553 ports) + routing engine + cargo classification + premium tool helpers ais_provider.py ─── Unified AIS: AISStream WebSocket + AISHub REST + Digitraffic REST + MT fallback equasis_parser.py ─── IMO vessel registry (authenticated web scraping) maritime_compliance.py ─── Sanctions screening + dark fleet detection (standalone, no external deps) moltbook_bot.py ─── Promotional bot for Moltbook platform (background daemon) telegram_bot.py ─── Telegram bot: standalone service, group chats, vessel watch alerts ``` ### AI Agent (`seafare_agent.py`) Uses Claude `claude-sonnet-4-5-20250929` with tool-calling. The agent loop sends user message → Claude responds with `tool_use` blocks → `_execute_tool_inner()` runs them via `_TOOL_DISPATCH` dict → results sent back → repeats until `stop_reason != "tool_use"` (max 7 iterations via `AGENT_MAX_TOOL_ITERATIONS`). System prompt is action-oriented: agent detects request type and uses tools immediately, no filler text. **Tools (26):** `search_vessel`, `get_vessel_details`, `get_position`, `search_vessels_near_port`, `calculate_route`, `find_vessels_for_cargo`, `calculate_demurrage`, `search_contacts`, `unlock_contacts`, `get_freight_rate`, `screen_sanctions`, `check_port_congestion`, `get_bunker_prices`, `optimize_bunker`, `generate_charter_party`, `vessel_performance`, `generate_bill_of_lading`, `optimize_crew_change`, `calculate_insurance`, `estimate_port_costs`, `weather_routing`, `generate_fixture_recap`, `detect_ais_anomaly`, `detect_dark_fleet`, `search_web`, `get_revenue` `user_context` (profile summary + balance) is injected into the system prompt so the agent knows who it's talking to. ### AIS Data Priority Chain (`ais_provider.py`) For single vessel position (`get_vessel_position`): 1. **DB cache** — fresh position (< 5 min) 2. **AISStream** — persistent background thread OR sync fallback (8s WebSocket query in request thread) 3. **AISHub REST** — on-demand query (1 min rate limit) 4. **Digitraffic** — Finnish/Baltic waters (free, no key) 5. **MarineTraffic** — scraping fallback 6. **Stale DB** — old cache as last resort **Important**: Daemon threads die on Render's gunicorn gthread workers. Sync fallback (`query_position_sync`, `query_area_sync`) runs inside Flask request threads and works reliably. ### Vessel Search Features Both `search_vessels_near_port` and `find_vessels_for_cargo` use **expanding radius** search: - Radius steps: 50 → 200 → 500 NM. First step uses full AIS provider (slow, ~6-8s). Wider steps use DB-only queries (instant). - Always returns at least 3 vessels (MIN_VESSELS). Each vessel has `distance_nm` and `map_link`. - `map_link` format: `{{SHOWMAP~lat~lon~zoom~name}}` — rendered as clickable buttons in chat by `formatResponse()` step 7 in `index.html`. Uses `~` delimiter (not `|`) to avoid markdown table conflicts. ### Auth System (`seafare_api.py`) Hybrid stateless + stateful: primary auth uses `itsdangerous` signed tokens (30-day TTL, survives Render DB wipes). Fallback to legacy UUID DB sessions. `auto_promote_admin()` checks `ADMIN_EMAILS` env var on every auth. ### Payment Flow `search_contacts` → free masked preview → user confirms → `unlock_contacts` → `charge_user()` (atomic SQL `WHERE balance >= ?`) → `add_service_charge()` → `save_purchased_contacts()`. Previously purchased contacts are returned free via `has_purchased_contact()`. ### i18n (Dual) - **Server-side**: `TEXTS` dict in `seafare_api.py` + `L(lang, key)` helper — for API error messages - **Client-side**: `i18n` JS object in `index.html` + `t(key)` helper — for all UI labels. Three languages: EN, RU, ES. Always add keys to all three. ### Background Threads (`seafare_api.py`) Two daemon threads auto-start at module level: 1. **Keep-alive** (`_start_keep_alive`): pings `/health` every 14 min to prevent sleep 2. **Moltbook bot** (`_start_moltbook_bot`): searches maritime posts on Moltbook every 30 min, generates expert comments via Claude Haiku ### Telegram Bot (`telegram_bot.py` — separate systemd service) Runs as standalone process (`seafare-telegram.service`), NOT inside gunicorn (daemon threads die on fork with `--preload`). **Features:** Full AI agent (26 tools), group chat support (@mention/reply/commands), vessel watch alerts (/watch, /unwatch, /watches), auto language detection, per-user rate limiting in groups. **Vessel Watch:** Background checker thread polls DB positions every 10 min. Arrival watches (one-shot) fire when vessel enters port radius. Status watches (continuous) fire on nav status changes. Max 5 watches per chat, auto-expire 7 days. DB table: `vessel_watches`. ### Adding New Tools 1. Add helper function in `marinetraffic_parser.py` (or `maritime_compliance.py` for compliance tools) 2. Add tool definition to `TOOLS` list in `seafare_agent.py` (with `input_schema`) 3. Add handler function `_tool_()` in `seafare_agent.py` 4. Add entry to `_TOOL_DISPATCH` dict in `seafare_agent.py` 5. Add trigger rule in `SYSTEM_PROMPT` BEHAVIOR section 6. Add UI service card in `index.html` + i18n keys in all 3 languages (EN/RU/ES) ## Key Conventions - **Versioning**: `APP_VERSION` in `config.py` (semver). Bump with every commit: PATCH for fixes, MINOR for features, MAJOR for breaking changes. Displays in `/health` endpoint and sidebar footer. - **Config centralization**: All constants live in `config.py` (rate limits, cache TTLs, fees, env var names). Import from there, don't hardcode. - **Data sources are secret**: System prompt instructs agent to never reveal Equasis/MarineTraffic. Present data as "our maritime intelligence network." - **Admin**: `is_admin` users get free access to all paid features. Check via `is_admin` flag on user dict. - **Atomic balance operations**: Always use `charge_user()` (returns bool) — never manually subtract balance. - **DB migrations**: Use `CREATE TABLE IF NOT EXISTS` and `CREATE INDEX IF NOT EXISTS` in `init_db()`. No migration framework — backward-compatible additions only. - **.env contains secrets** — never commit. Credentials are set via Render dashboard env vars in production. - **Single HTML file**: All CSS, HTML, and JS live in `index.html`. No build step, no bundler. - **Port database**: `world_ports.json` — 16,553 ports from UN/LOCODE. Regenerate with `python merge_unlocode.py`. - **Cargo classification**: `CARGO_TO_VESSEL` dict in `marinetraffic_parser.py` maps cargo types → vessel categories (bulk, tanker, container, roro, general). ## Database (PostgreSQL + SQLite) **Dual-mode**: `maritime_db.py` auto-detects `DATABASE_URL` env var → PostgreSQL in production, SQLite locally. Wrapper classes (`_PgCursorWrapper`, `_PgConnectionWrapper`) translate SQLite-style `?` placeholders to `%s` and auto-add `RETURNING id` to INSERTs. **Always write SQL with `?` placeholders** — the wrapper handles conversion. **Tables (22):** `vessels`, `positions`, `contacts`, `port_calls`, `demurrage`, `users`, `sessions`, `chat_history`, `user_profiles`, `wallets`, `deposits`, `withdrawals`, `service_charges`, `purchased_contacts`, `port_vessel_cache`, `equasis_cache`, `equasis_daily_counter`, `query_log`, `moltbook_comments`, `user_memory`, `conversation_summaries`, `vessel_watches` All user-related tables FK to `users(id)`. Vessel-related tables use `mmsi` as key. ## Environment Variables | Variable | Purpose | |----------|---------| | `ANTHROPIC_API_KEY` | Claude API (required for AI agent + Moltbook bot) | | `EQUASIS_USER` / `EQUASIS_PASS` | Equasis.org login | | `GOOGLE_CLIENT_ID` | Google OAuth | | `ADMIN_EMAILS` | Comma-separated admin emails (auto-promoted on login) | | `SECRET_KEY` | Token signing (falls back to ANTHROPIC_API_KEY) | | `MOLTBOOK_API_KEY` | Moltbook platform API key (bot disabled if not set) | | `AISSTREAM_API_KEY` | AISStream.io WebSocket API key (real-time AIS) | | `AISHUB_USERNAME` | AISHub.net contributor username (REST AIS queries) | | `DIGITRAFFIC_ENABLED` | '1' (default) to enable Finnish Digitraffic AIS, '0' to disable | | `TELEGRAM_BOT_TOKEN` | Telegram Bot API token from @BotFather (bot disabled if not set) | | `DATABASE_URL` | PostgreSQL connection string (auto-set by Render; if absent → SQLite) |