1→#!/usr/bin/env python3 2→""" 3→NEOG OS - Centralized Log Server 4→Collects logs from all NEOG clients across Tailscale network 5→""" 6→ 7→from flask import Flask, request, jsonify, send_from_directory 8→from flask_cors import CORS 9→from flask_socketio import SocketIO, emit 10→import sqlite3 11→import json 12→from datetime import datetime 13→import os 14→import sys 15→import subprocess 16→sys.path.append('/Users/neog') 17→from markdown_converter import generate_html_page 18→ 19→# ========== NOTIFICATION HELPERS ========== 20→ 21→# Webhook configuration (set via API or environment) 22→WEBHOOK_CONFIG = { 23→ 'slack': os.environ.get('CC_SLACK_WEBHOOK', ''), 24→ 'discord': os.environ.get('CC_DISCORD_WEBHOOK', ''), 25→ 'enabled': True 26→} 27→ 28→def send_macos_notification(title, message, subtitle=None, sound='default'): 29→ """Send a macOS notification using osascript""" 30→ try: 31→ script = f'display notification "{message}"' 32→ if subtitle: 33→ script += f' subtitle "{subtitle}"' 34→ script += f' with title "{title}"' 35→ if sound: 36→ script += f' sound name "{sound}"' 37→ subprocess.run(['osascript', '-e', script], check=False, timeout=5) 38→ except Exception as e: 39→ print(f"[NOTIFY] Failed to send notification: {e}") 40→ 41→def send_webhook_notification(title, message, level='info'): 42→ """Send notification to configured webhooks (Slack/Discord)""" 43→ import requests 44→ 45→ if not WEBHOOK_CONFIG['enabled']: 46→ return 47→ 48→ # Color mapping 49→ colors = { 50→ 'info': '#00d4ff', 51→ 'success': '#10b981', 52→ 'warning': '#f59e0b', 53→ 'error': '#ef4444' 54→ } 55→ color = colors.get(level, colors['info']) 56→ 57→ # Slack webhook 58→ if WEBHOOK_CONFIG.get('slack'): 59→ try: 60→ requests.post(WEBHOOK_CONFIG['slack'], json={ 61→ 'attachments': [{ 62→ 'color': color, 63→ 'title': title, 64→ 'text': message, 65→ 'footer': 'CC ROI Dashboard', 66→ 'ts': int(datetime.now().timestamp()) 67→ }] 68→ }, timeout=5) 69→ except Exception as e: 70→ print(f"[WEBHOOK] Slack failed: {e}") 71→ 72→ # Discord webhook 73→ if WEBHOOK_CONFIG.get('discord'): 74→ try: 75→ # Convert hex to int for Discord 76→ color_int = int(color.lstrip('#'), 16) 77→ requests.post(WEBHOOK_CONFIG['discord'], json={ 78→ 'embeds': [{ 79→ 'title': title, 80→ 'description': message, 81→ 'color': color_int, 82→ 'footer': {'text': 'CC ROI Dashboard'} 83→ }] 84→ }, timeout=5) 85→ except Exception as e: 86→ print(f"[WEBHOOK] Discord failed: {e}") 87→ 88→def send_alert(title, message, level='info', subtitle=None): 89→ """Send alert via all configured channels""" 90→ # Always send macOS notification 91→ sound = 'Basso' if level == 'error' else 'default' 92→ send_macos_notification(title, message, subtitle, sound) 93→ 94→ # Send to webhooks if configured 95→ send_webhook_notification(title, message, level) 96→ 97→app = Flask(__name__, static_folder='/Users/neog/static') 98→CORS(app) 99→socketio = SocketIO(app, cors_allowed_origins="*") 100→ 101→DB_PATH = '/Users/neog/logs.db' 102→ 103→def init_db(): 104→ """Initialize SQLite database with logs table and CC tracking tables""" 105→ conn = sqlite3.connect(DB_PATH) 106→ c = conn.cursor() 107→ 108→ # Original logs table 109→ c.execute('''CREATE TABLE IF NOT EXISTS logs 110→ (id INTEGER PRIMARY KEY AUTOINCREMENT, 111→ timestamp TEXT NOT NULL, 112→ level TEXT NOT NULL, 113→ source TEXT NOT NULL, 114→ message TEXT NOT NULL, 115→ data TEXT, 116→ device_ip TEXT, 117→ user_agent TEXT, 118→ url TEXT, 119→ created_at DATETIME DEFAULT CURRENT_TIMESTAMP)''') 120→ 121→ # Create indexes for faster queries 122→ c.execute('CREATE INDEX IF NOT EXISTS idx_timestamp ON logs(timestamp)') 123→ c.execute('CREATE INDEX IF NOT EXISTS idx_level ON logs(level)') 124→ c.execute('CREATE INDEX IF NOT EXISTS idx_source ON logs(source)') 125→ c.execute('CREATE INDEX IF NOT EXISTS idx_device_ip ON logs(device_ip)') 126→ 127→ # ========== CC TRACKING TABLES (Automation ROI Dashboard) ========== 128→ 129→ # cc_runs: Track automation execution sessions 130→ c.execute('''CREATE TABLE IF NOT EXISTS cc_runs ( 131→ run_id TEXT PRIMARY KEY, 132→ session_id TEXT, 133→ prompt TEXT NOT NULL, 134→ source TEXT DEFAULT 'manual', 135→ device_ip TEXT, 136→ status TEXT DEFAULT 'pending', 137→ 138→ input_tokens INTEGER DEFAULT 0, 139→ output_tokens INTEGER DEFAULT 0, 140→ cache_read_tokens INTEGER DEFAULT 0, 141→ model TEXT DEFAULT 'claude-opus-4-5-20251101', 142→ 143→ cost_usd REAL DEFAULT 0, 144→ value_generated_usd REAL DEFAULT 0, 145→ 146→ gate_guardrails_ok BOOLEAN DEFAULT 1, 147→ gate_budget_ok BOOLEAN DEFAULT 1, 148→ gate_heartbeat_ok BOOLEAN DEFAULT 1, 149→ halt_reason TEXT, 150→ 151→ started_at DATETIME DEFAULT CURRENT_TIMESTAMP, 152→ completed_at DATETIME, 153→ duration_ms INTEGER, 154→ 155→ tools_used TEXT, 156→ files_modified TEXT, 157→ error_message TEXT, 158→ tags TEXT 159→ )''') 160→ 161→ # cc_tags: Predefined tags for categorization 162→ c.execute('''CREATE TABLE IF NOT EXISTS cc_tags ( 163→ id INTEGER PRIMARY KEY AUTOINCREMENT, 164→ name TEXT UNIQUE NOT NULL, 165→ color TEXT DEFAULT '#00d4ff', 166→ description TEXT, 167→ created_at DATETIME DEFAULT CURRENT_TIMESTAMP 168→ )''') 169→ 170→ # Seed default tags 171→ default_tags = [ 172→ ('feature', '#10b981', 'New feature development'), 173→ ('bugfix', '#ef4444', 'Bug fixes'), 174→ ('refactor', '#f59e0b', 'Code refactoring'), 175→ ('docs', '#6366f1', 'Documentation'), 176→ ('test', '#8b5cf6', 'Testing'), 177→ ('devops', '#ec4899', 'DevOps and infrastructure'), 178→ ('research', '#14b8a6', 'Research and exploration') 179→ ] 180→ for tag_name, color, desc in default_tags: 181→ c.execute('INSERT OR IGNORE INTO cc_tags (name, color, description) VALUES (?, ?, ?)', 182→ (tag_name, color, desc)) 183→ 184→ # cc_events: Granular events within each run (feedback loop) 185→ c.execute('''CREATE TABLE IF NOT EXISTS cc_events ( 186→ id INTEGER PRIMARY KEY AUTOINCREMENT, 187→ run_id TEXT NOT NULL, 188→ event_type TEXT NOT NULL, 189→ event_subtype TEXT, 190→ 191→ input_tokens INTEGER DEFAULT 0, 192→ output_tokens INTEGER DEFAULT 0, 193→ cost_usd REAL DEFAULT 0, 194→ 195→ input_data TEXT, 196→ output_data TEXT, 197→ duration_ms INTEGER, 198→ success BOOLEAN DEFAULT 1, 199→ 200→ created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 201→ FOREIGN KEY (run_id) REFERENCES cc_runs(run_id) 202→ )''') 203→ 204→ # budget_config: 3-Gate budget limits 205→ c.execute('''CREATE TABLE IF NOT EXISTS budget_config ( 206→ id INTEGER PRIMARY KEY, 207→ name TEXT UNIQUE NOT NULL, 208→ daily_limit_usd REAL DEFAULT 10.00, 209→ monthly_limit_usd REAL DEFAULT 200.00, 210→ max_tokens_per_run INTEGER DEFAULT 100000, 211→ max_cost_per_run_usd REAL DEFAULT 2.00, 212→ alert_threshold_pct INTEGER DEFAULT 80, 213→ is_active BOOLEAN DEFAULT 1, 214→ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP 215→ )''') 216→ 217→ # guardrails: Safety rules 218→ c.execute('''CREATE TABLE IF NOT EXISTS guardrails ( 219→ id INTEGER PRIMARY KEY AUTOINCREMENT, 220→ name TEXT NOT NULL, 221→ rule_type TEXT NOT NULL, 222→ pattern TEXT NOT NULL, 223→ action TEXT DEFAULT 'block', 224→ is_active BOOLEAN DEFAULT 1, 225→ created_at DATETIME DEFAULT CURRENT_TIMESTAMP 226→ )''') 227→ 228→ # CC indexes 229→ c.execute('CREATE INDEX IF NOT EXISTS idx_cc_runs_status ON cc_runs(status)') 230→ c.execute('CREATE INDEX IF NOT EXISTS idx_cc_runs_started ON cc_runs(started_at)') 231→ c.execute('CREATE INDEX IF NOT EXISTS idx_cc_events_run ON cc_events(run_id)') 232→ c.execute('CREATE INDEX IF NOT EXISTS idx_cc_events_type ON cc_events(event_type)') 233→ 234→ # Seed default budget config 235→ c.execute('''INSERT OR IGNORE INTO budget_config (id, name, daily_limit_usd, monthly_limit_usd) 236→ VALUES (1, 'default', 10.00, 200.00)''') 237→ 238→ # Seed default guardrails 239→ guardrails_seed = [ 240→ ('block_rm_rf', 'blocked_command', 'rm -rf /', 'block'), 241→ ('block_rm_rf_home', 'blocked_command', 'rm -rf ~', 'block'), 242→ ('block_sudo', 'blocked_command', 'sudo', 'warn'), 243→ ('block_force_push', 'blocked_command', 'git push --force', 'warn'), 244→ ('block_force_push_main', 'blocked_command', 'git push -f origin main', 'block'), 245→ ('block_secrets', 'blocked_path', '**/*secret*', 'block'), 246→ ('block_env', 'blocked_path', '.env', 'warn'), 247→ ('block_ssh', 'blocked_path', '.ssh', 'block'), 248→ ] 249→ for name, rule_type, pattern, action in guardrails_seed: 250→ c.execute('''INSERT OR IGNORE INTO guardrails (name, rule_type, pattern, action) 251→ VALUES (?, ?, ?, ?)''', (name, rule_type, pattern, action)) 252→ 253→ conn.commit() 254→ conn.close() 255→ print(f"✓ Database initialized at {DB_PATH}") 256→ print(f"✓ CC tracking tables ready (cc_runs, cc_events, budget_config, guardrails)") 257→ 258→@app.route('/') 259→def index(): 260→ """Redirect to live dashboard""" 261→ return ''' 262→ 263→
Server is running on port 9001
267→