# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Development Commands ### Building and Running - `go build -o sojuboy ./cmd/sojuboy` - Build the binary - `go run ./cmd/sojuboy` - Run directly with Go - `docker-compose up -d --build` - Build and run with Docker Compose - `docker build -t sojuboy .` - Build Docker image ### Health Checks - `./sojuboy --health` - Local readiness check (exits 0/1) - `curl http://localhost:8080/healthz` - HTTP health check - `curl http://localhost:8080/ready` - HTTP readiness check (requires soju connection) ### Testing and Validation - `go mod tidy` - Clean up module dependencies - `go vet ./...` - Run Go static analysis - `go fmt ./...` - Format Go code ## Architecture Overview This is a Go service that acts as an IRC bouncer companion for soju, providing notifications, message storage, and AI summaries. ### Core Components **Entry Point**: `cmd/sojuboy/main.go` - Main application entry point with service wiring and configuration **Core Modules** (all in `internal/`): - `config/` - Environment-based configuration loading - `soju/` - IRC client for soju bouncer connection with IRCv3 capability negotiation - `store/` - SQLite storage layer with WAL mode enabled - `notifier/` - Pushover notification system (pluggable interface) - `summarizer/` - OpenAI integration for AI digests and link summarization - `scheduler/` - Cron-based digest scheduling and retention jobs - `httpapi/` - HTTP API server with Web UI (templates + static assets) - `logging/` - Structured logging setup ### Key Architectural Patterns **IRC Integration**: Uses raw IRC protocol with sorcix/irc parser, implements irssi-style authentication for soju multi-client support (`username/network@client` format) **Message Flow**: IRC messages → storage → mention detection → notifications + AI processing **Web Architecture**: Go stdlib `net/http` with `html/template`, embedded static assets, SSE for real-time updates **AI Integration**: OpenAI client with GPT-5 default (fallback to GPT-4o-mini), separate prompts for conversation digests vs link summarization ### Configuration All configuration via environment variables (see README.md for complete reference). Key patterns: - Soju connection: `SOJU_HOST`, `SOJU_PORT`, `SOJU_TLS`, `IRC_USERNAME` format critical for per-client history - Required for functionality: `OPENAI_API_KEY`, `PUSHOVER_USER_KEY`, `PUSHOVER_API_TOKEN` - Default data path: `/data/app.db` (SQLite with WAL mode) ### HTTP API Structure - Authentication via `HTTP_TOKEN` (cookie or Bearer/query param) - JSON APIs: `/tail`, `/history`, `/trigger`, `/linkcard`, `/linksummary`, `/metrics` - Web UI with SSE tail, infinite scroll, link cards, on-demand summarization - Channel-based operations (URL encode `#` as `%23` in requests) ### Storage Schema SQLite with modernc.org/sqlite driver: - Messages table with server-time timestamps - Channel-based organization - Retention management (daily cleanup at 03:00) - WAL mode for concurrent access ### Development Notes - Go 1.23+ required (see go.mod) - Static binary compilation for distroless Docker image - No external build tools (Make, npm, etc.) - pure Go toolchain - Configuration via .env file for local development - Docker health checks via `--health` flag ## Code Review Findings ### 🚀 Major Refactors for iOS Features #### 1. Extract Message Processing Service The `alert` function in `main.go:143-212` is doing too much: - Message storage - Notification logic - Rate limiting - DM vs channel handling **Recommendation:** Create `internal/messaging/processor.go` with separate concerns: ```go type MessageProcessor struct { store Store notifiers []Notifier // Multi-target for iOS + Pushover rateLimiter RateLimiter broadcaster Broadcaster } ``` #### 2. Multi-Target Notification System Current system hardcoded to single Pushover notifier. For iOS: ```go type NotificationDispatcher struct { targets []Notifier // Pushover, APNs, etc. } ``` #### 3. Extract Rate Limiter Move `rateLimiter` struct to `internal/ratelimit/` package for reusability and testing. ### ⚡ Critical Performance Issues #### 1. SQLite Connection Bottleneck `store.go:30` - `SetMaxOpenConns(1)` is extremely restrictive: ```go // Current: Major bottleneck db.SetMaxOpenConns(1) // Better: Allow concurrent reads db.SetMaxOpenConns(5) db.SetMaxIdleConns(2) ``` #### 2. Inefficient Array Reversal `store.go:147-150` - Manual reversal is inefficient: ```sql -- Better: Use SQL ORDER BY instead SELECT ... ORDER BY at ASC LIMIT ? ``` #### 3. OpenAI Client Recreation `openai.go:60` - Client created per request. Should use singleton pattern with connection pooling. ### 🗑️ Dead Code Cleanup 1. **Entire unused package:** `internal/ircclient/base64.go` - appears completely unused 2. **Unused imports:** `io` package in `main.go` only for health check discard 3. **Dead helper:** `strconvI()` in `summarizer.go:475` - just use `strconv.Itoa` directly 4. **Rarely used export:** `config.GetEnvInt()` - used once, could be private ### 🔧 Easy Wins #### 1. Fix Deprecated API `templates.go:48` - `strings.Title()` is deprecated: ```go // Replace with data["Title"] = cases.Title(language.English).String(base) ``` #### 2. Hard-coded Values - Health check port hardcoded to `8080` - use configured listen address - Text limits hardcoded in summarizer - make configurable - Temperature values hardcoded - make configurable #### 3. Database Schema Missing indexes for performance: ```sql CREATE INDEX IF NOT EXISTS idx_messages_author ON messages(author); CREATE INDEX IF NOT EXISTS idx_messages_time ON messages(at); ``` ### 🛡️ Error Handling Improvements #### 1. Silent Failures `pushover.go:24` - Returns `nil` on error conditions without logging: ```go // Better error handling if p == nil || p.app == nil || p.userKey == "" { return errors.New("pushover not configured") } ``` #### 2. Message Truncation No user feedback when messages truncated to 1024 chars. ### 📱 iOS-Specific Optimizations #### 1. Message Broadcaster Enhancement Current SSE broadcasting is basic - needs: - Connection state management - Message queuing for disconnected clients - WebSocket support alongside SSE #### 2. Auth Token Management Simple token auth needs enhancement for mobile: - JWT tokens with refresh capability - Device-specific sessions - Proper expiration handling ### 🎯 Implementation Priority **Phase 1 (High Impact):** 1. Fix SQLite connection limit 2. Extract message processor service 3. Remove dead code 4. Fix deprecated APIs **Phase 2 (iOS Foundation):** 1. Multi-target notification system 2. Enhanced message broadcasting 3. JWT authentication improvements **Phase 3 (Performance):** 1. OpenAI client pooling 2. Database index optimization 3. Rate limiter improvements ## Application Behavior & Quirks ### Message Storage Logic - **DM Handling**: Direct messages TO the bot are stored under the sender's nick, not the bot's nick (`main.go:146-151`) - **Message Deduplication**: Uses `INSERT OR IGNORE` with optional `msgid` for soju message deduplication - **Channel Normalization**: All channel lookups are case-insensitive via `lower()` SQL function ### Notification Logic - **Rate Limiting**: Per-channel + per-keyword combinations, not global (`main.go:199`) - **Quiet Hours**: Local timezone-based, supports midnight wrap-around (`config.go:267-271`) - **Urgent Keywords**: Bypass quiet hours but still respect rate limits - **Backfill Suppression**: Messages older than 5 minutes are not notified unless `NOTIFY_BACKFILL=true` ### IRC Protocol Specifics - **Authentication**: Uses raw PASS/USER, not SASL (despite config hint) - **Capability Negotiation**: Requests IRCv3 `draft/event-playback` for automatic message replay - **Fallback History**: CHATHISTORY LATEST only used when event-playback unavailable - **Connection Management**: Exponential backoff (1s → 30s max) with automatic reconnection ### AI Summarization Behavior - **Message Grouping**: Same author within `SUMM_GROUP_WINDOW` (default 120s) are concatenated - **Link Processing**: Extracts URLs via regex, fetches with readability for clean text - **Image Handling**: Direct image URLs sent as vision inputs to GPT-5 - **Fallback Logic**: Local text-based summary if OpenAI fails - **Model Selection**: Auto-detects reasoning models (gpt-5, o1) to skip temperature setting ### Web UI Implementation - **SSE Streaming**: Real-time message delivery with 15s heartbeat - **Link Cards**: Cached 24h, special handling for X/Twitter oEmbed and YouTube - **Authentication**: Cookie-based with 7-day expiration, secure flag auto-detection - **Channel Selector**: Populated from DB + config fallback for empty databases ### Storage Patterns - **WAL Mode**: Enabled for concurrent read access despite single connection limit - **Retention**: Daily cleanup at 03:00 local time, configurable days - **Timestamps**: Always stored as UTC, server-time tags preferred over local time - **Migration**: Best-effort schema updates with ignored errors for missing columns ### Error Handling Patterns - **Silent Failures**: Many subsystems return `nil` errors to prevent cascading failures - **Graceful Degradation**: Missing summarizer/notifier doesn't break core IRC functionality - **Connection Resilience**: IRC client handles ERROR messages and reconnects automatically ### Security Considerations - **Token Storage**: HTTP_TOKEN stored in plain text, suitable for internal deployments - **CORS**: No CORS headers - designed for same-origin web UI access - **TLS**: Configurable per connection, defaults to enabled - **Input Sanitization**: Minimal - relies on template escaping and database parameterization ### Performance Characteristics - **Memory Usage**: Link/summary caches unbounded - potential memory leak over time - **Concurrent Requests**: Severely limited by single DB connection - **OpenAI Rate Limits**: No built-in rate limiting or retry logic - **SSE Scalability**: In-memory subscriber map, no persistence across restarts ### Development Workflow - **Hot Reload**: Not supported - requires full restart for config/code changes - **Logging**: Structured JSON to stdout, debug mode shows IRC protocol messages - **Health Monitoring**: Separate metrics endpoint for Prometheus integration - **Docker**: Multi-stage build with distroless final image for security ### Common Gotchas - **Channel Names**: Must URL-encode `#` as `%23` in HTTP requests - **Time Formats**: Mix of RFC3339 and RFC3339Nano depending on context - **Case Sensitivity**: IRC nicks/channels case-insensitive, but stored as-received - **CHATHISTORY**: Requires soju-specific timestamp format, may not work with other bouncers - **Health Check**: Hard-coded to port 8080 regardless of configured listen address ## Planned iOS App Development ### Context User wants to build a native iOS Swift app that consumes this Go server's HTTP APIs. The server should remain in Go (excellent fit for backend services), while the iOS app provides a native mobile experience. ### Required Server Enhancements for iOS **1. iOS Push Notifications (Priority 1)** - Add Apple Push Notification service (APNs) integration alongside Pushover - New endpoints: `POST /api/devices` (device token registration) - Enhanced notification system supporting multiple targets - Environment variables: `APNS_KEY_ID`, `APNS_TEAM_ID`, `APNS_BUNDLE_ID`, `APNS_KEY_PATH`, `APNS_SANDBOX` **2. Enhanced Real-time Communication** - Add WebSocket support alongside existing SSE (better for iOS background handling) - Message queuing for disconnected clients - New endpoints: `GET /api/ws?channel=#chan`, `GET /api/missed?since=` **3. Mobile-Optimized Authentication** - JWT tokens with refresh capability (current simple token is too basic) - Device-specific authentication for multiple iOS devices - New endpoints: `POST /api/auth/login`, `POST /api/auth/refresh`, `POST /api/auth/logout` **4. Offline Support & Sync** - Cursor-based pagination (improve current `before` parameter approach) - Message read/unread state tracking per device - Enhanced endpoints: `GET /api/sync?cursor=&limit=100`, `POST /api/read-receipts`, `GET /api/unread-count` **5. iOS-Specific Optimizations** - Batch operations: `POST /api/linkcards/batch` - Image size variants: `GET /api/linkcard?url=...&size=thumb` - Performance optimizations for mobile bandwidth ### iOS App Feature Parity Goals The native app should match current web UI functionality: - Live chat view with real-time updates - Link cards and on-demand link summarization - Native push notifications (replacing Pushover for mobile users) - On-demand AI summarization - Channel switching and history browsing - Infinite scroll for chat history ### Database Schema Extensions Needed - `devices` table - iOS device tokens and user associations - `read_receipts` table - per-device read state tracking - `message_queue` table - offline message queueing ### Implementation Priority for iOS Support 1. APNs integration (native notifications) 2. WebSocket + connection management 3. JWT authentication system 4. Offline sync capabilities 5. Mobile-specific API optimizations ### Architecture Decision: Keep Go Backend **Question Asked:** Could this project be rewritten in Swift? **Answer:** Technically feasible but not recommended. **Pros of Swift rewrite:** Strong type safety, modern concurrency (async/await), cross-platform potential **Cons:** Limited ecosystem for IRC libraries, SQLite libraries, cron scheduling, HTTP templating; larger Docker images; significant development effort to recreate working functionality **Recommended Approach:** Keep Go backend (excellent fit for network services, mature ecosystem, single static binary deployment) + build native Swift iOS frontend. This leverages both platforms' strengths effectively.