sojuboy/CLAUDE.md

340 lines
14 KiB
Markdown
Raw Permalink Normal View History

# 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=<timestamp>`
**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=<id>&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.