14 KiB
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 binarygo run ./cmd/sojuboy- Run directly with Godocker-compose up -d --build- Build and run with Docker Composedocker build -t sojuboy .- Build Docker image
Health Checks
./sojuboy --health- Local readiness check (exits 0/1)curl http://localhost:8080/healthz- HTTP health checkcurl http://localhost:8080/ready- HTTP readiness check (requires soju connection)
Testing and Validation
go mod tidy- Clean up module dependenciesgo vet ./...- Run Go static analysisgo 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 loadingsoju/- IRC client for soju bouncer connection with IRCv3 capability negotiationstore/- SQLite storage layer with WAL mode enablednotifier/- Pushover notification system (pluggable interface)summarizer/- OpenAI integration for AI digests and link summarizationscheduler/- Cron-based digest scheduling and retention jobshttpapi/- 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_USERNAMEformat 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%23in 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
--healthflag
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:
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:
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:
// 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:
-- 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
- Entire unused package:
internal/ircclient/base64.go- appears completely unused - Unused imports:
iopackage inmain.goonly for health check discard - Dead helper:
strconvI()insummarizer.go:475- just usestrconv.Itoadirectly - Rarely used export:
config.GetEnvInt()- used once, could be private
🔧 Easy Wins
1. Fix Deprecated API
templates.go:48 - strings.Title() is deprecated:
// 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:
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:
// 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):
- Fix SQLite connection limit
- Extract message processor service
- Remove dead code
- Fix deprecated APIs
Phase 2 (iOS Foundation):
- Multi-target notification system
- Enhanced message broadcasting
- JWT authentication improvements
Phase 3 (Performance):
- OpenAI client pooling
- Database index optimization
- 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 IGNOREwith optionalmsgidfor 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-playbackfor 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
nilerrors 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%23in 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
beforeparameter 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
devicestable - iOS device tokens and user associationsread_receiptstable - per-device read state trackingmessage_queuetable - offline message queueing
Implementation Priority for iOS Support
- APNs integration (native notifications)
- WebSocket + connection management
- JWT authentication system
- Offline sync capabilities
- 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.