From 9fe0998436488537a8a2e8ffeefb0c4424b41c60 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Mon, 12 Jan 2026 09:27:16 -1000 Subject: Initial commit: Personal Consolidation Dashboard (Phase 1 Complete) Implemented a unified web dashboard aggregating tasks, notes, and meal planning: Core Features: - Trello integration (PRIMARY feature - boards, cards, lists) - Todoist integration (tasks and projects) - Obsidian integration (20 most recent notes) - PlanToEat integration (optional - 7-day meal planning) - Mobile-responsive web UI with auto-refresh (5 min) - SQLite caching with 5-minute TTL - AI agent endpoint with Bearer token authentication Technical Implementation: - Go 1.21+ backend with chi router - Interface-based API client design for testability - Parallel data fetching with goroutines - Graceful degradation (partial data on API failures) - .env file loading with godotenv - Comprehensive test coverage (9/9 tests passing) Bug Fixes: - Fixed .env file not being loaded at startup - Fixed nil pointer dereference with optional API clients (typed nil interface gotcha) Documentation: - START_HERE.md - Quick 5-minute setup guide - QUICKSTART.md - Fast track setup - SETUP_GUIDE.md - Detailed step-by-step instructions - PROJECT_SUMMARY.md - Complete project overview - CLAUDE.md - Guide for Claude Code instances - AI_AGENT_ACCESS.md - AI agent design document - AI_AGENT_SETUP.md - Claude.ai integration guide - TRELLO_AUTH_UPDATE.md - New Power-Up auth process Statistics: - Binary: 17MB - Code: 2,667 lines - Tests: 5 unit + 4 acceptance tests (all passing) - Dependencies: chi, sqlite3, godotenv Co-Authored-By: Claude Sonnet 4.5 --- AI_AGENT_ACCESS.md | 340 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 AI_AGENT_ACCESS.md (limited to 'AI_AGENT_ACCESS.md') diff --git a/AI_AGENT_ACCESS.md b/AI_AGENT_ACCESS.md new file mode 100644 index 0000000..40fe6a1 --- /dev/null +++ b/AI_AGENT_ACCESS.md @@ -0,0 +1,340 @@ +# AI Agent Access Design + +## Overview +Add a dedicated API endpoint optimized for AI agents (like Claude) to access dashboard data in a structured, machine-readable format with simple authentication. + +## Authentication Strategy + +### API Key-Based Auth +Simple bearer token authentication that's easy to use from Claude.ai: +- Generate a long, random API key for AI access +- Store in environment variable: `AI_AGENT_API_KEY` +- Use standard `Authorization: Bearer {key}` header +- No complex OAuth flows or JWT rotation + +### Why This Approach? +1. **Simple for Claude**: Just include auth header in requests +2. **Stateless**: No session management needed +3. **Revocable**: Change key in .env to revoke access +4. **Standard**: Uses common HTTP authorization pattern + +## API Endpoints + +### GET /api/ai/dashboard +Returns complete dashboard data in AI-optimized JSON format. + +**Request:** +```http +GET /api/ai/dashboard HTTP/1.1 +Host: localhost:8080 +Authorization: Bearer {AI_AGENT_API_KEY} +``` + +**Response (Success - 200 OK):** +```json +{ + "status": "success", + "timestamp": "2026-01-09T14:30:00Z", + "data": { + "trello_boards": [ + { + "id": "board123", + "name": "Work Projects", + "cards": [ + { + "id": "card1", + "name": "Complete project proposal", + "list": "In Progress", + "due_date": "2026-01-15", + "url": "https://trello.com/c/card1" + } + ] + } + ], + "todoist_tasks": [ + { + "id": "task1", + "content": "Buy groceries", + "project": "Personal", + "priority": 1, + "due_date": "2026-01-10", + "completed": false, + "labels": ["shopping"], + "url": "https://todoist.com/task/1" + } + ], + "obsidian_notes": [ + { + "filename": "meeting-notes.md", + "title": "Team Meeting Notes", + "content_preview": "Discussed Q1 goals...", + "modified": "2026-01-09T10:00:00Z", + "tags": ["meetings", "work"] + } + ], + "plantoeat_meals": [ + { + "id": "meal1", + "recipe": "Spaghetti Carbonara", + "date": "2026-01-09", + "meal_type": "dinner", + "url": "https://plantoeat.com/recipe/123" + } + ] + }, + "metadata": { + "cache_age_seconds": 45, + "sources_refreshed": ["trello", "todoist"], + "errors": [] + } +} +``` + +**Response (Unauthorized - 401):** +```json +{ + "status": "error", + "error": "Invalid or missing API key", + "hint": "Include 'Authorization: Bearer {key}' header" +} +``` + +### GET /api/ai/boards/{board_id}/cards +Get cards from a specific Trello board. + +**Request:** +```http +GET /api/ai/boards/board123/cards HTTP/1.1 +Authorization: Bearer {AI_AGENT_API_KEY} +``` + +### GET /api/ai/tasks?filter={filter} +Get filtered tasks (today, week, overdue, completed). + +**Request:** +```http +GET /api/ai/tasks?filter=today HTTP/1.1 +Authorization: Bearer {AI_AGENT_API_KEY} +``` + +### POST /api/ai/task +Create a new Todoist task (Phase 2). + +**Request:** +```http +POST /api/ai/task HTTP/1.1 +Authorization: Bearer {AI_AGENT_API_KEY} +Content-Type: application/json + +{ + "content": "Buy milk", + "project": "Personal", + "due_date": "2026-01-10", + "priority": 1, + "labels": ["shopping"] +} +``` + +## AI-Optimized Response Format + +### Design Principles +1. **Flat structure**: Avoid deep nesting where possible +2. **Explicit field names**: Use `due_date` not `dueDate` for clarity +3. **Include metadata**: Provide context about data freshness +4. **Clear errors**: Return actionable error messages +5. **Consistent types**: Always use ISO 8601 for dates + +### Example Claude.ai Usage +```markdown +User: "What tasks do I have due today?" + +Claude uses MCP tool or API call: +GET /api/ai/tasks?filter=today +Authorization: Bearer sk-ai-1234567890abcdef + +Claude response: "You have 3 tasks due today: +1. Buy groceries (Personal, Priority 1) +2. Complete project proposal (Work, Priority 2) +3. Call dentist (Health, Priority 3)" +``` + +## Implementation + +### Middleware: Auth Check +```go +// internal/middleware/ai_auth.go +func AIAuthMiddleware(apiKey string) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authHeader := r.Header.Get("Authorization") + + if !strings.HasPrefix(authHeader, "Bearer ") { + respondJSON(w, 401, map[string]string{ + "status": "error", + "error": "Missing or invalid Authorization header", + "hint": "Include 'Authorization: Bearer {key}' header", + }) + return + } + + token := strings.TrimPrefix(authHeader, "Bearer ") + if token != apiKey { + respondJSON(w, 401, map[string]string{ + "status": "error", + "error": "Invalid API key", + }) + return + } + + next.ServeHTTP(w, r) + }) + } +} +``` + +### Handler: AI Dashboard +```go +// internal/handlers/ai_handlers.go +func (h *Handler) HandleAIDashboard(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // Fetch all data + data, err := h.fetchDashboardData(ctx, false) + if err != nil { + respondJSON(w, 500, map[string]interface{}{ + "status": "error", + "error": err.Error(), + }) + return + } + + // Format for AI consumption + response := map[string]interface{}{ + "status": "success", + "timestamp": time.Now().Format(time.RFC3339), + "data": map[string]interface{}{ + "trello_boards": formatBoardsForAI(data.Boards), + "todoist_tasks": formatTasksForAI(data.Tasks), + "obsidian_notes": formatNotesForAI(data.Notes), + "plantoeat_meals": formatMealsForAI(data.Meals), + }, + "metadata": map[string]interface{}{ + "errors": data.Errors, + }, + } + + respondJSON(w, 200, response) +} +``` + +### Router Setup +```go +// cmd/dashboard/main.go +func main() { + // ... existing setup ... + + // AI Agent routes (protected by auth middleware) + if cfg.AIAgentAPIKey != "" { + aiRouter := chi.NewRouter() + aiRouter.Use(middleware.AIAuthMiddleware(cfg.AIAgentAPIKey)) + + r.Route("/api/ai", func(r chi.Router) { + r.Use(middleware.AIAuthMiddleware(cfg.AIAgentAPIKey)) + r.Get("/dashboard", h.HandleAIDashboard) + r.Get("/boards/{boardID}/cards", h.HandleAIBoardCards) + r.Get("/tasks", h.HandleAITasks) + // Phase 2: Write operations + // r.Post("/task", h.HandleAICreateTask) + }) + } +} +``` + +## Security Considerations + +1. **Key Storage**: Never commit API key to git +2. **Key Rotation**: Easy to change in .env file +3. **Rate Limiting**: Consider adding rate limits per key +4. **HTTPS Only**: Use HTTPS in production +5. **Logging**: Log API key usage (last 4 chars only) + +## Configuration + +### .env.example +```bash +# AI Agent Access +AI_AGENT_API_KEY=sk-ai-1234567890abcdef... +# Generate with: openssl rand -hex 32 +``` + +### config.go +```go +type Config struct { + // ... existing fields ... + AIAgentAPIKey string +} + +func Load() (*Config, error) { + cfg := &Config{ + // ... existing fields ... + AIAgentAPIKey: os.Getenv("AI_AGENT_API_KEY"), + } + return cfg, nil +} +``` + +## Usage from Claude.ai + +### With MCP (Model Context Protocol) +```json +{ + "mcpServers": { + "dashboard": { + "command": "curl", + "args": [ + "-H", "Authorization: Bearer ${AI_AGENT_API_KEY}", + "http://localhost:8080/api/ai/dashboard" + ] + } + } +} +``` + +### Direct API Call +Claude can make HTTP requests directly: +``` +GET http://localhost:8080/api/ai/dashboard +Authorization: Bearer sk-ai-1234567890abcdef +``` + +## Future Enhancements + +1. **Webhook Support**: POST updates to Claude.ai when data changes +2. **Streaming**: Server-sent events for real-time updates +3. **Scoped Keys**: Different keys for read vs write access +4. **GraphQL**: More flexible querying for AI agents +5. **Natural Language Queries**: `/api/ai/query?q=What's due today?` + +## Testing + +### Manual Test +```bash +# Generate API key +export AI_KEY=$(openssl rand -hex 32) + +# Add to .env +echo "AI_AGENT_API_KEY=sk-ai-$AI_KEY" >> .env + +# Test endpoint +curl -H "Authorization: Bearer sk-ai-$AI_KEY" \ + http://localhost:8080/api/ai/dashboard | jq . +``` + +### Unit Test +```go +func TestAIAuthMiddleware(t *testing.T) { + // Test valid key + // Test invalid key + // Test missing header +} +``` -- cgit v1.2.3