     1→#!/usr/bin/env python3
     2→"""
     3→NEOG Gateway - Unified API for NEOG Command macOS App
     4→Centralizes CC ROI Tracking with embedded deployment support.
     5→
     6→Starts automatically with NEOG Command app and provides:
     7→- CC ROI endpoints (runs, budget, gates)
     8→- Gateway status and health
     9→- Real-time WebSocket updates
    10→
    11→Port: 9001 (default, configurable via NEOG_GATEWAY_PORT)
    12→"""
    13→
    14→from flask import Flask, request, jsonify
    15→from flask_cors import CORS
    16→from flask_socketio import SocketIO, emit
    17→import sqlite3
    18→import json
    19→import uuid
    20→import os
    21→import time
    22→import subprocess
    23→from datetime import datetime
    24→from functools import wraps
    25→
    26→# ========== CONFIGURATION ==========
    27→
    28→GATEWAY_VERSION = "1.0.0"
    29→GATEWAY_START_TIME = time.time()
    30→PORT = int(os.environ.get('NEOG_GATEWAY_PORT', 9001))
    31→DB_PATH = os.path.expanduser('~/logs.db')
    32→
    33→# Token cost rates (USD per 1M tokens)
    34→TOKEN_RATES = {
    35→    'claude-opus-4-5-20251101': {'input': 15.00, 'output': 75.00, 'cache_read': 1.50},
    36→    'claude-sonnet-4-5-20250929': {'input': 3.00, 'output': 15.00, 'cache_read': 0.30},
    37→    'default': {'input': 3.00, 'output': 15.00, 'cache_read': 0.30}
    38→}
    39→
    40→# ========== FLASK APP ==========
    41→
    42→app = Flask(__name__)
    43→CORS(app, resources={r"/*": {"origins": "*"}})
    44→socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')
    45→
    46→# ========== DATABASE ==========
    47→
    48→def get_db():
    49→    """Get database connection with row factory"""
    50→    conn = sqlite3.connect(DB_PATH)
    51→    conn.row_factory = sqlite3.Row
    52→    return conn
    53→
    54→def init_db():
    55→    """Initialize database with CC tracking tables"""
    56→    conn = sqlite3.connect(DB_PATH)
    57→    c = conn.cursor()
    58→
    59→    # cc_runs: Track automation execution sessions
    60→    c.execute('''CREATE TABLE IF NOT EXISTS cc_runs (
    61→        run_id TEXT PRIMARY KEY,
    62→        session_id TEXT,
    63→        prompt TEXT NOT NULL,
    64→        source TEXT DEFAULT 'manual',
    65→        device_ip TEXT,
    66→        status TEXT DEFAULT 'pending',
    67→        model TEXT DEFAULT 'claude-opus-4-5-20251101',
    68→
    69→        input_tokens INTEGER DEFAULT 0,
    70→        output_tokens INTEGER DEFAULT 0,
    71→        cache_read_tokens INTEGER DEFAULT 0,
    72→
    73→        cost_usd REAL DEFAULT 0,
    74→        value_generated_usd REAL DEFAULT 0,
    75→        value_is_estimated BOOLEAN DEFAULT 1,
    76→
    77→        gate_guardrails_ok BOOLEAN DEFAULT 1,
    78→        gate_budget_ok BOOLEAN DEFAULT 1,
    79→        gate_heartbeat_ok BOOLEAN DEFAULT 1,
    80→        halt_reason TEXT,
    81→
    82→        started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    83→        completed_at DATETIME,
    84→        duration_ms INTEGER,
    85→
    86→        tools_used TEXT,
    87→        files_modified TEXT,
    88→        error_message TEXT,
    89→        tags TEXT
    90→    )''')
    91→
    92→    # cc_events: Granular events within each run
    93→    c.execute('''CREATE TABLE IF NOT EXISTS cc_events (
    94→        id INTEGER PRIMARY KEY AUTOINCREMENT,
    95→        run_id TEXT NOT NULL,
    96→        event_type TEXT NOT NULL,
    97→        event_subtype TEXT,
    98→        input_tokens INTEGER DEFAULT 0,
    99→        output_tokens INTEGER DEFAULT 0,
   100→        cost_usd REAL DEFAULT 0,
   101→        input_data TEXT,
   102→        output_data TEXT,
   103→        duration_ms INTEGER,
   104→        success BOOLEAN DEFAULT 1,
   105→        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
   106→        FOREIGN KEY (run_id) REFERENCES cc_runs(run_id)
   107→    )''')
   108→
   109→    # budget_config: 3-Gate budget limits
   110→    c.execute('''CREATE TABLE IF NOT EXISTS budget_config (
   111→        id INTEGER PRIMARY KEY,
   112→        name TEXT UNIQUE NOT NULL,
   113→        daily_limit_usd REAL DEFAULT 10.00,
   114→        monthly_limit_usd REAL DEFAULT 200.00,
   115→        max_tokens_per_run INTEGER DEFAULT 100000,
   116→        max_cost_per_run_usd REAL DEFAULT 2.00,
   117→        alert_threshold_pct INTEGER DEFAULT 80,
   118→        is_active BOOLEAN DEFAULT 1,
   119→        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
   120→    )''')
   121→
   122→    # guardrails: Safety rules
   123→    c.execute('''CREATE TABLE IF NOT EXISTS guardrails (
   124→        id INTEGER PRIMARY KEY AUTOINCREMENT,
   125→        name TEXT NOT NULL,
   126→        rule_type TEXT NOT NULL,
   127→        pattern TEXT NOT NULL,
   128→        action TEXT DEFAULT 'block',
   129→        is_active BOOLEAN DEFAULT 1,
   130→        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
   131→    )''')
   132→
   133→    # Indexes
   134→    c.execute('CREATE INDEX IF NOT EXISTS idx_cc_runs_status ON cc_runs(status)')
   135→    c.execute('CREATE INDEX IF NOT EXISTS idx_cc_runs_started ON cc_runs(started_at)')
   136→    c.execute('CREATE INDEX IF NOT EXISTS idx_cc_events_run ON cc_events(run_id)')
   137→
   138→    # Seed default budget config
   139→    c.execute('''INSERT OR IGNORE INTO budget_config (id, name, daily_limit_usd, monthly_limit_usd)
   140→                 VALUES (1, 'default', 10.00, 200.00)''')
   141→
   142→    # Seed default guardrails
   143→    guardrails_seed = [
   144→        ('block_rm_rf', 'blocked_command', 'rm -rf /', 'block'),
   145→        ('block_rm_rf_home', 'blocked_command', 'rm -rf ~', 'block'),
   146→        ('block_sudo', 'blocked_command', 'sudo', 'warn'),
   147→        ('block_force_push', 'blocked_command', 'git push --force', 'warn'),
   148→        ('block_secrets', 'blocked_path', '**/*secret*', 'block'),
   149→        ('block_env', 'blocked_path', '.env', 'warn'),
   150→        ('block_ssh', 'blocked_path', '.ssh', 'block'),
   151→    ]
   152→    for name, rule_type, pattern, action in guardrails_seed:
   153→        c.execute('''INSERT OR IGNORE INTO guardrails (name, rule_type, pattern, action)
   154→                     VALUES (?, ?, ?, ?)''', (name, rule_type, pattern, action))
   155→
   156→    conn.commit()
   157→    conn.close()
   158→    print(f"[GATEWAY] Database initialized at {DB_PATH}")
   159→
   160→# ========== HELPERS ==========
   161→
   162→def calculate_cost(input_tokens, output_tokens, cache_read_tokens=0, model='default'):
   163→    """Calculate cost in USD based on token usage"""
   164→    rates = TOKEN_RATES.get(model, TOKEN_RATES['default'])
   165→    cost = (input_tokens / 1_000_000 * rates['input'] +
   166→            output_tokens / 1_000_000 * rates['output'] +
   167→            cache_read_tokens / 1_000_000 * rates['cache_read'])
   168→    return round(cost, 6)
   169→
   170→def estimate_run_value(prompt, source, cost_usd, tokens):
   171→    """Smart value estimation based on run characteristics"""
   172→    source_multipliers = {
   173→        'ios_app': 3.0,
   174→        'macos_app': 3.0,
   175→        'web': 2.5,
   176→        'dashboard': 2.0,
   177→        'terminal': 1.5,
   178→        'cron': 1.2,
   179→        'manual': 1.0,
   180→        'test': 0.5
   181→    }
   182→    multiplier = source_multipliers.get(source, 1.0)
   183→
   184→    high_value_keywords = ['deploy', 'release', 'fix bug', 'feature', 'refactor', 'optimize', 'security']
   185→    medium_value_keywords = ['review', 'test', 'document', 'update', 'improve']
   186→
   187→    prompt_lower = prompt.lower()
   188→
   189→    if any(kw in prompt_lower for kw in high_value_keywords):
   190→        base_value = 5.0
   191→    elif any(kw in prompt_lower for kw in medium_value_keywords):
   192→        base_value = 3.0
   193→    else:
   194→        base_value = max(1.0, tokens / 2000)
   195→
   196→    estimated_value = base_value * multiplier
   197→    min_value = cost_usd * 2
   198→    return round(max(estimated_value, min_value), 2)
   199→
   200→def send_macos_notification(title, message, subtitle=None):
   201→    """Send a macOS notification"""
   202→    try:
   203→        script = f'display notification "{message}"'
   204→        if subtitle:
   205→            script += f' subtitle "{subtitle}"'
   206→        script += f' with title "{title}"'
   207→        subprocess.run(['osascript', '-e', script], check=False, timeout=5)
   208→    except Exception as e:
   209→        print(f"[NOTIFY] Failed: {e}")
   210→
   211→# ========== GATEWAY ENDPOINTS ==========
   212→
   213→@app.route('/')
   214→def index():
   215→    """Gateway info page"""
   216→    return jsonify({
   217→        'name': 'NEOG Gateway',
   218→        'version': GATEWAY_VERSION,
   219→        'status': 'running',
   220→        'port': PORT,
   221→        'uptime_seconds': int(time.time() - GATEWAY_START_TIME),
   222→        'endpoints': {
   223→            'gateway': ['/health', '/api/gateway/status', '/api/gateway/services'],
   224→            'cc_roi': ['/api/cc/roi/summary', '/api/cc/roi/all-time', '/api/cc/runs', '/api/cc/budget', '/api/cc/health']
   225→        }
   226→    })
   227→
   228→@app.route('/health')
   229→def health():
   230→    """Simple health check"""
   231→    return jsonify({'status': 'ok', 'timestamp': datetime.now().isoformat()})
   232→
   233→@app.route('/api/gateway/status')
   234→def gateway_status():
   235→    """Detailed gateway status"""
   236→    uptime = int(time.time() - GATEWAY_START_TIME)
   237→
   238→    # Check database
   239→    db_ok = False
   240→    db_size = 0
   241→    try:
   242→        if os.path.exists(DB_PATH):
   243→            db_size = os.path.getsize(DB_PATH)
   244→            conn = get_db()
   245→            conn.execute('SELECT 1')
   246→            conn.close()
   247→            db_ok = True
   248→    except:
   249→        pass
   250→
   251→    return jsonify({
   252→        'version': GATEWAY_VERSION,
   253→        'port': PORT,
   254→        'uptime_seconds': uptime,
   255→        'uptime_formatted': f'{uptime // 3600}h {(uptime % 3600) // 60}m {uptime % 60}s',
   256→        'started_at': datetime.fromtimestamp(GATEWAY_START_TIME).isoformat(),
   257→        'database': {
   258→            'path': DB_PATH,
   259→            'ok': db_ok,
   260→            'size_bytes': db_size,
   261→            'size_mb': round(db_size / 1024 / 1024, 2)
   262→        }
   263→    })
   264→
   265→@app.route('/api/gateway/services')
   266→def gateway_services():
   267→    """List available services and their status"""
   268→    services = [
   269→        {'name': 'CC ROI Tracking', 'status': 'active', 'endpoints': 15},
   270→        {'name': 'Budget Management', 'status': 'active', 'endpoints': 3},
   271→        {'name': 'Guardrails', 'status': 'active', 'endpoints': 2},
   272→    ]
   273→    return jsonify({
   274→        'services': services,
   275→        'total_endpoints': sum(s['endpoints'] for s in services)
   276→    })
   277→
   278→# ========== CC RUNS ENDPOINTS ==========
   279→
   280→@app.route('/api/cc/run', methods=['POST'])
   281→def create_run():
   282→    """Create a new automation run"""
   283→    try:
   284→        data = request.json or {}
   285→        run_id = data.get('run_id', str(uuid.uuid4()))
   286→        device_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
   287→
   288→        conn = get_db()
   289→        c = conn.cursor()
   290→        c.execute('''INSERT INTO cc_runs (run_id, session_id, prompt, source, device_ip, status, model)
   291→                     VALUES (?, ?, ?, ?, ?, ?, ?)''',
   292→                  (run_id,
   293→                   data.get('session_id'),
   294→                   data.get('prompt', ''),
   295→                   data.get('source', 'manual'),
   296→                   device_ip,
   297→                   'running',
   298→                   data.get('model', 'claude-opus-4-5-20251101')))
   299→        conn.commit()
   300→        conn.close()

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

#!/usr/bin/env python3
"""
NEOG Gateway - Unified API for NEOG Command macOS App
Centralizes CC ROI Tracking with embedded deployment support.

Starts automatically with NEOG Command app and provides:
- CC ROI endpoints (runs, budget, gates)
- Gateway status and health
- Real-time WebSocket updates

Port: 9001 (default, configurable via NEOG_GATEWAY_PORT)
"""

from flask import Flask, request, jsonify
from flask_cors import CORS
from flask_socketio import SocketIO, emit
import sqlite3
import json
import uuid
import os
import time
import subprocess
from datetime import datetime
from functools import wraps

# ========== CONFIGURATION ==========

GATEWAY_VERSION = "1.0.0"
GATEWAY_START_TIME = time.time()
PORT = int(os.environ.get('NEOG_GATEWAY_PORT', 9001))
DB_PATH = os.path.expanduser('~/logs.db')

# Token cost rates (USD per 1M tokens)
TOKEN_RATES = {
    'claude-opus-4-5-20251101': {'input': 15.00, 'output': 75.00, 'cache_read': 1.50},
    'claude-sonnet-4-5-20250929': {'input': 3.00, 'output': 15.00, 'cache_read': 0.30},
    'default': {'input': 3.00, 'output': 15.00, 'cache_read': 0.30}
}

# ========== FLASK APP ==========

app = Flask(__name__)
CORS(app, resources={r"/*": {"origins": "*"}})
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')

# ========== DATABASE ==========

def get_db():
    """Get database connection with row factory"""
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn

def init_db():
    """Initialize database with CC tracking tables"""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()

    # cc_runs: Track automation execution sessions
    c.execute('''CREATE TABLE IF NOT EXISTS cc_runs (
        run_id TEXT PRIMARY KEY,
        session_id TEXT,
        prompt TEXT NOT NULL,
        source TEXT DEFAULT 'manual',
        device_ip TEXT,
        status TEXT DEFAULT 'pending',
        model TEXT DEFAULT 'claude-opus-4-5-20251101',

        input_tokens INTEGER DEFAULT 0,
        output_tokens INTEGER DEFAULT 0,
        cache_read_tokens INTEGER DEFAULT 0,

        cost_usd REAL DEFAULT 0,
        value_generated_usd REAL DEFAULT 0,
        value_is_estimated BOOLEAN DEFAULT 1,

        gate_guardrails_ok BOOLEAN DEFAULT 1,
        gate_budget_ok BOOLEAN DEFAULT 1,
        gate_heartbeat_ok BOOLEAN DEFAULT 1,
        halt_reason TEXT,

        started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        completed_at DATETIME,
        duration_ms INTEGER,

        tools_used TEXT,
        files_modified TEXT,
        error_message TEXT,
        tags TEXT
    )''')

    # cc_events: Granular events within each run
    c.execute('''CREATE TABLE IF NOT EXISTS cc_events (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        run_id TEXT NOT NULL,
        event_type TEXT NOT NULL,
        event_subtype TEXT,
        input_tokens INTEGER DEFAULT 0,
        output_tokens INTEGER DEFAULT 0,
        cost_usd REAL DEFAULT 0,
        input_data TEXT,
        output_data TEXT,
        duration_ms INTEGER,
        success BOOLEAN DEFAULT 1,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (run_id) REFERENCES cc_runs(run_id)
    )''')

    # budget_config: 3-Gate budget limits
    c.execute('''CREATE TABLE IF NOT EXISTS budget_config (
        id INTEGER PRIMARY KEY,
        name TEXT UNIQUE NOT NULL,
        daily_limit_usd REAL DEFAULT 10.00,
        monthly_limit_usd REAL DEFAULT 200.00,
        max_tokens_per_run INTEGER DEFAULT 100000,
        max_cost_per_run_usd REAL DEFAULT 2.00,
        alert_threshold_pct INTEGER DEFAULT 80,
        is_active BOOLEAN DEFAULT 1,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )''')

    # guardrails: Safety rules
    c.execute('''CREATE TABLE IF NOT EXISTS guardrails (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        rule_type TEXT NOT NULL,
        pattern TEXT NOT NULL,
        action TEXT DEFAULT 'block',
        is_active BOOLEAN DEFAULT 1,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )''')

    # Indexes
    c.execute('CREATE INDEX IF NOT EXISTS idx_cc_runs_status ON cc_runs(status)')
    c.execute('CREATE INDEX IF NOT EXISTS idx_cc_runs_started ON cc_runs(started_at)')
    c.execute('CREATE INDEX IF NOT EXISTS idx_cc_events_run ON cc_events(run_id)')

    # Seed default budget config
    c.execute('''INSERT OR IGNORE INTO budget_config (id, name, daily_limit_usd, monthly_limit_usd)
                 VALUES (1, 'default', 10.00, 200.00)''')

    # Seed default guardrails
    guardrails_seed = [
        ('block_rm_rf', 'blocked_command', 'rm -rf /', 'block'),
        ('block_rm_rf_home', 'blocked_command', 'rm -rf ~', 'block'),
        ('block_sudo', 'blocked_command', 'sudo', 'warn'),
        ('block_force_push', 'blocked_command', 'git push --force', 'warn'),
        ('block_secrets', 'blocked_path', '**/*secret*', 'block'),
        ('block_env', 'blocked_path', '.env', 'warn'),
        ('block_ssh', 'blocked_path', '.ssh', 'block'),
    ]
    for name, rule_type, pattern, action in guardrails_seed:
        c.execute('''INSERT OR IGNORE INTO guardrails (name, rule_type, pattern, action)
                     VALUES (?, ?, ?, ?)''', (name, rule_type, pattern, action))

    conn.commit()
    conn.close()
    print(f"[GATEWAY] Database initialized at {DB_PATH}")

# ========== HELPERS ==========

def calculate_cost(input_tokens, output_tokens, cache_read_tokens=0, model='default'):
    """Calculate cost in USD based on token usage"""
    rates = TOKEN_RATES.get(model, TOKEN_RATES['default'])
    cost = (input_tokens / 1_000_000 * rates['input'] +
            output_tokens / 1_000_000 * rates['output'] +
            cache_read_tokens / 1_000_000 * rates['cache_read'])
    return round(cost, 6)

def estimate_run_value(prompt, source, cost_usd, tokens):
    """Smart value estimation based on run characteristics"""
    source_multipliers = {
        'ios_app': 3.0,
        'macos_app': 3.0,
        'web': 2.5,
        'dashboard': 2.0,
        'terminal': 1.5,
        'cron': 1.2,
        'manual': 1.0,
        'test': 0.5
    }
    multiplier = source_multipliers.get(source, 1.0)

    high_value_keywords = ['deploy', 'release', 'fix bug', 'feature', 'refactor', 'optimize', 'security']
    medium_value_keywords = ['review', 'test', 'document', 'update', 'improve']

    prompt_lower = prompt.lower()

    if any(kw in prompt_lower for kw in high_value_keywords):
        base_value = 5.0
    elif any(kw in prompt_lower for kw in medium_value_keywords):
        base_value = 3.0
    else:
        base_value = max(1.0, tokens / 2000)

    estimated_value = base_value * multiplier
    min_value = cost_usd * 2
    return round(max(estimated_value, min_value), 2)

def send_macos_notification(title, message, subtitle=None):
    """Send a macOS notification"""
    try:
        script = f'display notification "{message}"'
        if subtitle:
            script += f' subtitle "{subtitle}"'
        script += f' with title "{title}"'
        subprocess.run(['osascript', '-e', script], check=False, timeout=5)
    except Exception as e:
        print(f"[NOTIFY] Failed: {e}")

# ========== GATEWAY ENDPOINTS ==========

@app.route('/')
def index():
    """Gateway info page"""
    return jsonify({
        'name': 'NEOG Gateway',
        'version': GATEWAY_VERSION,
        'status': 'running',
        'port': PORT,
        'uptime_seconds': int(time.time() - GATEWAY_START_TIME),
        'endpoints': {
            'gateway': ['/health', '/api/gateway/status', '/api/gateway/services'],
            'cc_roi': ['/api/cc/roi/summary', '/api/cc/roi/all-time', '/api/cc/runs', '/api/cc/budget', '/api/cc/health']
        }
    })

@app.route('/health')
def health():
    """Simple health check"""
    return jsonify({'status': 'ok', 'timestamp': datetime.now().isoformat()})

@app.route('/api/gateway/status')
def gateway_status():
    """Detailed gateway status"""
    uptime = int(time.time() - GATEWAY_START_TIME)

    # Check database
    db_ok = False
    db_size = 0
    try:
        if os.path.exists(DB_PATH):
            db_size = os.path.getsize(DB_PATH)
            conn = get_db()
            conn.execute('SELECT 1')
            conn.close()
            db_ok = True
    except:
        pass

    return jsonify({
        'version': GATEWAY_VERSION,
        'port': PORT,
        'uptime_seconds': uptime,
        'uptime_formatted': f'{uptime // 3600}h {(uptime % 3600) // 60}m {uptime % 60}s',
        'started_at': datetime.fromtimestamp(GATEWAY_START_TIME).isoformat(),
        'database': {
            'path': DB_PATH,
            'ok': db_ok,
            'size_bytes': db_size,
            'size_mb': round(db_size / 1024 / 1024, 2)
        }
    })

@app.route('/api/gateway/services')
def gateway_services():
    """List available services and their status"""
    services = [
        {'name': 'CC ROI Tracking', 'status': 'active', 'endpoints': 15},
        {'name': 'Budget Management', 'status': 'active', 'endpoints': 3},
        {'name': 'Guardrails', 'status': 'active', 'endpoints': 2},
    ]
    return jsonify({
        'services': services,
        'total_endpoints': sum(s['endpoints'] for s in services)
    })

# ========== CC RUNS ENDPOINTS ==========

@app.route('/api/cc/run', methods=['POST'])
def create_run():
    """Create a new automation run"""
    try:
        data = request.json or {}
        run_id = data.get('run_id', str(uuid.uuid4()))
        device_ip = request.headers.get('X-Forwarded-For', request.remote_addr)

        conn = get_db()
        c = conn.cursor()
        c.execute('''INSERT INTO cc_runs (run_id, session_id, prompt, source, device_ip, status, model)
                     VALUES (?, ?, ?, ?, ?, ?, ?)''',
                  (run_id,
                   data.get('session_id'),
                   data.get('prompt', ''),
                   data.get('source', 'manual'),
                   device_ip,
                   'running',
                   data.get('model', 'claude-opus-4-5-20251101')))
        conn.commit()
        conn.close()


Services/servers/claude-cli-server.py:24:def roi_create_run(prompt, session_id, source='cli-server'):
Services/servers/claude-cli-server.py:41:def roi_log_event(run_id, event_type, event_subtype=None, input_tokens=0, output_tokens=0, success=True, data=None):
Services/servers/claude-cli-server.py:58:def roi_complete_run(run_id, status='completed', input_tokens=0, output_tokens=0, error=None):
Services/servers/neog-gateway.py:280:@app.route('/api/cc/run', methods=['POST'])
Services/servers/neog-gateway.py:314:@app.route('/api/cc/run/<run_id>', methods=['GET'])
Services/servers/neog-gateway.py:348:@app.route('/api/cc/run/<run_id>', methods=['PATCH'])
Services/servers/neog-gateway.py:396:@app.route('/api/cc/run/<run_id>/complete', methods=['POST'])
Services/servers/neog-gateway.py:481:@app.route('/api/cc/run/<run_id>/value', methods=['POST'])
Services/servers/neog-gateway.py:511:@app.route('/api/cc/runs', methods=['GET'])
Services/servers/neog-gateway.py:556:@app.route('/api/cc/roi/summary', methods=['GET'])
Services/servers/neog-gateway.py:557:def get_roi_summary():
Services/servers/neog-gateway.py:608:@app.route('/api/cc/roi/all-time', methods=['GET'])
Services/servers/neog-gateway.py:609:def get_roi_all_time():
Services/servers/neog-gateway.py:661:@app.route('/api/cc/roi/daily', methods=['GET'])
Services/servers/neog-gateway.py:662:def get_roi_daily():
Services/servers/neog-gateway.py:688:@app.route('/api/cc/budget', methods=['GET'])
Services/servers/neog-gateway.py:734:@app.route('/api/cc/budget', methods=['POST'])
Services/servers/neog-gateway.py:764:@app.route('/api/cc/health', methods=['GET'])
Services/servers/neog-gateway.py:804:@app.route('/api/cc/guardrails', methods=['GET'])
Services/servers/neog-gateway.py:818:@app.route('/api/cc/gate/check', methods=['POST'])
Services/servers/neog-gateway.py:878:@app.route('/api/cc/event', methods=['POST'])
Services/servers/log-server.py:680:@app.route('/api/cc/run', methods=['POST'])
Services/servers/log-server.py:715:@app.route('/api/cc/run/<run_id>', methods=['PATCH'])
Services/servers/log-server.py:810:@app.route('/api/cc/run/<run_id>/complete', methods=['POST'])
Services/servers/log-server.py:906:@app.route('/api/cc/run/<run_id>/value', methods=['POST'])
Services/servers/log-server.py:949:@app.route('/api/cc/runs/clear-estimates', methods=['POST'])
Services/servers/log-server.py:974:@app.route('/api/cc/runs', methods=['GET'])
Services/servers/log-server.py:1029:@app.route('/api/cc/run/<run_id>', methods=['GET'])
Services/servers/log-server.py:1067:@app.route('/api/cc/roi/real', methods=['GET'])
Services/servers/log-server.py:1068:def get_real_roi():
Services/servers/log-server.py:1128:@app.route('/api/cc/event', methods=['POST'])
Services/servers/log-server.py:1186:@app.route('/api/cc/events', methods=['GET'])
Services/servers/log-server.py:1222:@app.route('/api/cc/roi/summary', methods=['GET'])
Services/servers/log-server.py:1223:def get_roi_summary():
Services/servers/log-server.py:1282:@app.route('/api/cc/roi/all-time', methods=['GET'])
Services/servers/log-server.py:1283:def get_roi_all_time():
Services/servers/log-server.py:1358:@app.route('/api/cc/roi/daily', methods=['GET'])
Services/servers/log-server.py:1359:def get_roi_daily():
Services/servers/log-server.py:1387:@app.route('/api/cc/roi/by-source', methods=['GET'])
Services/servers/log-server.py:1388:def get_roi_by_source():
Services/servers/log-server.py:1413:@app.route('/api/cc/insights', methods=['GET'])
Services/servers/log-server.py:1554:@app.route('/api/cc/budget/auto-scale', methods=['POST'])
Services/servers/log-server.py:1612:@app.route('/api/cc/achievements', methods=['GET'])
Services/servers/log-server.py:1694:@app.route('/api/cc/forecast', methods=['GET'])
Services/servers/log-server.py:1776:@app.route('/api/cc/export', methods=['GET'])
Services/servers/log-server.py:1837:@app.route('/api/cc/export/csv', methods=['GET'])
Services/servers/log-server.py:1869:@app.route('/api/cc/gate/check', methods=['POST'])
Services/servers/log-server.py:1945:@app.route('/api/cc/gate/heartbeat', methods=['POST'])
Services/servers/log-server.py:1975:@app.route('/api/cc/budget', methods=['GET'])
Services/servers/log-server.py:2009:@app.route('/api/cc/budget', methods=['POST'])

[Showing results with pagination = limit: 50, offset: 0]


Let me continue reading the neog-gateway.py to see the complete API structure:


Bash

find /Users/neog -name "*QUICKSTART*" -o -name "*quickstart*" -o -name "*CC-ROI*" 2>/dev/null | head -10

Find quickstart documentation files


   300→        conn.close()
   301→
   302→        socketio.emit('cc_run_created', {
   303→            'run_id': run_id,
   304→            'prompt': data.get('prompt', '')[:100],
   305→            'source': data.get('source', 'manual'),
   306→            'status': 'running'
   307→        })
   308→
   309→        return jsonify({'status': 'ok', 'run_id': run_id})
   310→
   311→    except Exception as e:
   312→        return jsonify({'status': 'error', 'message': str(e)}), 500
   313→
   314→@app.route('/api/cc/run/<run_id>', methods=['GET'])
   315→def get_run(run_id):
   316→    """Get a single run with its events"""
   317→    try:
   318→        conn = get_db()
   319→        c = conn.cursor()
   320→
   321→        c.execute('SELECT * FROM cc_runs WHERE run_id = ?', (run_id,))
   322→        row = c.fetchone()
   323→        if not row:
   324→            conn.close()
   325→            return jsonify({'status': 'error', 'message': 'Run not found'}), 404
   326→
   327→        run = dict(row)
   328→
   329→        # Parse JSON fields
   330→        for field in ['tools_used', 'files_modified', 'tags']:
   331→            if run.get(field):
   332→                try:
   333→                    run[field] = json.loads(run[field])
   334→                except:
   335→                    pass
   336→
   337→        # Get events
   338→        c.execute('SELECT * FROM cc_events WHERE run_id = ? ORDER BY created_at ASC', (run_id,))
   339→        events = [dict(r) for r in c.fetchall()]
   340→        run['events'] = events
   341→
   342→        conn.close()
   343→        return jsonify(run)
   344→
   345→    except Exception as e:
   346→        return jsonify({'status': 'error', 'message': str(e)}), 500
   347→
   348→@app.route('/api/cc/run/<run_id>', methods=['PATCH'])
   349→def update_run(run_id):
   350→    """Update an existing run"""
   351→    try:
   352→        data = request.json or {}
   353→        conn = get_db()
   354→        c = conn.cursor()
   355→
   356→        updates = []
   357→        params = []
   358→        allowed_fields = ['status', 'input_tokens', 'output_tokens', 'cache_read_tokens',
   359→                          'cost_usd', 'value_generated_usd', 'gate_guardrails_ok',
   360→                          'gate_budget_ok', 'gate_heartbeat_ok', 'halt_reason',
   361→                          'tools_used', 'files_modified', 'error_message']
   362→
   363→        for field in allowed_fields:
   364→            if field in data:
   365→                updates.append(f'{field} = ?')
   366→                value = data[field]
   367→                if isinstance(value, (list, dict)):
   368→                    value = json.dumps(value)
   369→                params.append(value)
   370→
   371→        # Auto-calculate cost if tokens provided
   372→        if 'input_tokens' in data or 'output_tokens' in data:
   373→            c.execute('SELECT input_tokens, output_tokens, cache_read_tokens, model FROM cc_runs WHERE run_id = ?', (run_id,))
   374→            row = c.fetchone()
   375→            if row:
   376→                input_t = data.get('input_tokens', row[0])
   377→                output_t = data.get('output_tokens', row[1])
   378→                cache_t = data.get('cache_read_tokens', row[2])
   379→                model = row[3]
   380→                cost = calculate_cost(input_t, output_t, cache_t, model)
   381→                updates.append('cost_usd = ?')
   382→                params.append(cost)
   383→
   384→        if updates:
   385→            params.append(run_id)
   386→            c.execute(f'UPDATE cc_runs SET {", ".join(updates)} WHERE run_id = ?', params)
   387→            conn.commit()
   388→
   389→        conn.close()
   390→        socketio.emit('cc_run_updated', {'run_id': run_id, **data})
   391→        return jsonify({'status': 'ok', 'run_id': run_id})
   392→
   393→    except Exception as e:
   394→        return jsonify({'status': 'error', 'message': str(e)}), 500
   395→
   396→@app.route('/api/cc/run/<run_id>/complete', methods=['POST'])
   397→def complete_run(run_id):
   398→    """Mark a run as completed"""
   399→    try:
   400→        data = request.json or {}
   401→        conn = get_db()
   402→        c = conn.cursor()
   403→
   404→        c.execute('SELECT started_at, model, prompt, source FROM cc_runs WHERE run_id = ?', (run_id,))
   405→        row = c.fetchone()
   406→        if not row:
   407→            conn.close()
   408→            return jsonify({'status': 'error', 'message': 'Run not found'}), 404
   409→
   410→        started_at = datetime.fromisoformat(row[0]) if row[0] else datetime.now()
   411→        model = row[1] or 'default'
   412→        prompt = row[2] or ''
   413→        source = row[3] or 'manual'
   414→        completed_at = datetime.now()
   415→        duration_ms = int((completed_at - started_at).total_seconds() * 1000)
   416→
   417→        input_tokens = data.get('input_tokens', 0)
   418→        output_tokens = data.get('output_tokens', 0)
   419→        total_tokens = input_tokens + output_tokens
   420→        cost_usd = calculate_cost(input_tokens, output_tokens, 0, model)
   421→
   422→        value_generated = data.get('value_generated_usd')
   423→        value_is_estimated = 1
   424→        if value_generated is None or value_generated == 0:
   425→            value_generated = estimate_run_value(prompt, source, cost_usd, total_tokens)
   426→        else:
   427→            value_is_estimated = 0
   428→
   429→        status = data.get('status', 'completed')
   430→        c.execute('''UPDATE cc_runs SET status = ?, completed_at = ?, duration_ms = ?,
   431→                     input_tokens = COALESCE(?, input_tokens),
   432→                     output_tokens = COALESCE(?, output_tokens),
   433→                     cost_usd = ?,
   434→                     value_generated_usd = ?,
   435→                     value_is_estimated = ?,
   436→                     error_message = COALESCE(?, error_message)
   437→                     WHERE run_id = ?''',
   438→                  (status, completed_at.isoformat(), duration_ms,
   439→                   input_tokens, output_tokens, cost_usd,
   440→                   value_generated, value_is_estimated, data.get('error_message'), run_id))
   441→        conn.commit()
   442→
   443→        # Check budget
   444→        today = datetime.now().strftime('%Y-%m-%d')
   445→        c.execute('SELECT COALESCE(SUM(cost_usd), 0) FROM cc_runs WHERE DATE(started_at) = ?', (today,))
   446→        spent_today = c.fetchone()[0]
   447→
   448→        c.execute('SELECT daily_limit_usd, alert_threshold_pct FROM budget_config WHERE name = "default"')
   449→        budget_row = c.fetchone()
   450→        daily_limit = budget_row[0] if budget_row else 10.00
   451→        alert_pct = budget_row[1] if budget_row else 80
   452→        budget_used_pct = (spent_today / daily_limit * 100) if daily_limit > 0 else 0
   453→
   454→        conn.close()
   455→
   456→        # Send budget alerts
   457→        if budget_used_pct >= 100:
   458→            send_macos_notification("CC Budget EXCEEDED", f"${spent_today:.2f}/${daily_limit:.2f}")
   459→        elif budget_used_pct >= alert_pct:
   460→            send_macos_notification("CC Budget Warning", f"{budget_used_pct:.0f}% used")
   461→
   462→        socketio.emit('cc_run_completed', {
   463→            'run_id': run_id,
   464→            'status': status,
   465→            'duration_ms': duration_ms,
   466→            'cost_usd': cost_usd,
   467→            'budget_used_pct': round(budget_used_pct, 1)
   468→        })
   469→
   470→        return jsonify({
   471→            'status': 'ok',
   472→            'run_id': run_id,
   473→            'duration_ms': duration_ms,
   474→            'cost_usd': cost_usd,
   475→            'budget_used_pct': round(budget_used_pct, 1)
   476→        })
   477→
   478→    except Exception as e:
   479→        return jsonify({'status': 'error', 'message': str(e)}), 500
   480→
   481→@app.route('/api/cc/run/<run_id>/value', methods=['POST'])
   482→def update_run_value(run_id):
   483→    """Update the real value of a completed run"""
   484→    try:
   485→        data = request.json or {}
   486→        value_usd = data.get('value_usd', 0)
   487→
   488→        conn = get_db()
   489→        c = conn.cursor()
   490→        c.execute('''UPDATE cc_runs SET value_generated_usd = ?, value_is_estimated = 0
   491→                     WHERE run_id = ?''', (value_usd, run_id))
   492→        conn.commit()
   493→
   494→        c.execute('SELECT cost_usd, value_generated_usd FROM cc_runs WHERE run_id = ?', (run_id,))
   495→        row = c.fetchone()
   496→        conn.close()
   497→
   498→        if row:
   499→            return jsonify({
   500→                'status': 'ok',
   501→                'run_id': run_id,
   502→                'cost_usd': row[0],
   503→                'value_usd': row[1],
   504→                'roi_pct': round((row[1] / row[0] - 1) * 100, 1) if row[0] > 0 else 0
   505→            })
   506→        return jsonify({'status': 'error', 'message': 'Run not found'}), 404
   507→
   508→    except Exception as e:
   509→        return jsonify({'status': 'error', 'message': str(e)}), 500
   510→
   511→@app.route('/api/cc/runs', methods=['GET'])
   512→def get_runs():
   513→    """Get runs with optional filters"""
   514→    try:
   515→        limit = request.args.get('limit', 50, type=int)
   516→        offset = request.args.get('offset', 0, type=int)
   517→        status = request.args.get('status')
   518→        source = request.args.get('source')
   519→
   520→        conn = get_db()
   521→        c = conn.cursor()
   522→
   523→        query = 'SELECT * FROM cc_runs WHERE 1=1'
   524→        params = []
   525→
   526→        if status:
   527→            query += ' AND status = ?'
   528→            params.append(status)
   529→        if source:
   530→            query += ' AND source = ?'
   531→            params.append(source)
   532→
   533→        query += ' ORDER BY started_at DESC LIMIT ? OFFSET ?'
   534→        params.extend([limit, offset])
   535→
   536→        c.execute(query, params)
   537→        runs = [dict(row) for row in c.fetchall()]
   538→
   539→        # Parse JSON fields
   540→        for run in runs:
   541→            for field in ['tools_used', 'files_modified', 'tags']:
   542→                if run.get(field):
   543→                    try:
   544→                        run[field] = json.loads(run[field])
   545→                    except:
   546→                        pass
   547→
   548→        conn.close()
   549→        return jsonify(runs)
   550→
   551→    except Exception as e:
   552→        return jsonify({'status': 'error', 'message': str(e)}), 500
   553→
   554→# ========== ROI ENDPOINTS ==========
   555→
   556→@app.route('/api/cc/roi/summary', methods=['GET'])
   557→def get_roi_summary():
   558→    """Get today's ROI summary"""
   559→    try:
   560→        conn = get_db()
   561→        c = conn.cursor()
   562→        today = datetime.now().strftime('%Y-%m-%d')
   563→
   564→        c.execute('''SELECT
   565→                        COALESCE(SUM(cost_usd), 0) as total_cost,
   566→                        COALESCE(SUM(value_generated_usd), 0) as total_revenue,
   567→                        COALESCE(SUM(input_tokens), 0) as input_tokens,
   568→                        COALESCE(SUM(output_tokens), 0) as output_tokens,
   569→                        COUNT(*) as run_count,
   570→                        COUNT(CASE WHEN status = 'running' THEN 1 END) as active_runs,
   571→                        COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed_runs,
   572→                        COUNT(CASE WHEN status IN ('failed', 'halted') THEN 1 END) as failed_runs
   573→                     FROM cc_runs WHERE DATE(started_at) = ?''', (today,))
   574→
   575→        row = c.fetchone()
   576→        total_cost = row[0]
   577→        total_revenue = row[1]
   578→
   579→        c.execute('SELECT daily_limit_usd FROM budget_config WHERE name = "default"')
   580→        budget_row = c.fetchone()
   581→        daily_limit = budget_row[0] if budget_row else 10.00
   582→        budget_pct = (total_cost / daily_limit * 100) if daily_limit > 0 else 0
   583→
   584→        conn.close()
   585→
   586→        profit = total_revenue - total_cost
   587→        roi_pct = ((total_revenue - total_cost) / total_cost * 100) if total_cost > 0 else 0
   588→
   589→        return jsonify({
   590→            'today': today,
   591→            'cost_usd': round(total_cost, 4),
   592→            'revenue_usd': round(total_revenue, 2),
   593→            'profit_usd': round(profit, 2),
   594→            'roi_pct': round(roi_pct, 1),
   595→            'tokens_input': row[2],
   596→            'tokens_output': row[3],
   597→            'runs_total': row[4],
   598→            'runs_active': row[5],
   599→            'runs_completed': row[6],

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

        conn.close()

        socketio.emit('cc_run_created', {
            'run_id': run_id,
            'prompt': data.get('prompt', '')[:100],
            'source': data.get('source', 'manual'),
            'status': 'running'
        })

        return jsonify({'status': 'ok', 'run_id': run_id})

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/api/cc/run/<run_id>', methods=['GET'])
def get_run(run_id):
    """Get a single run with its events"""
    try:
        conn = get_db()
        c = conn.cursor()

        c.execute('SELECT * FROM cc_runs WHERE run_id = ?', (run_id,))
        row = c.fetchone()
        if not row:
            conn.close()
            return jsonify({'status': 'error', 'message': 'Run not found'}), 404

        run = dict(row)

        # Parse JSON fields
        for field in ['tools_used', 'files_modified', 'tags']:
            if run.get(field):
                try:
                    run[field] = json.loads(run[field])
                except:
                    pass

        # Get events
        c.execute('SELECT * FROM cc_events WHERE run_id = ? ORDER BY created_at ASC', (run_id,))
        events = [dict(r) for r in c.fetchall()]
        run['events'] = events

        conn.close()
        return jsonify(run)

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/api/cc/run/<run_id>', methods=['PATCH'])
def update_run(run_id):
    """Update an existing run"""
    try:
        data = request.json or {}
        conn = get_db()
        c = conn.cursor()

        updates = []
        params = []
        allowed_fields = ['status', 'input_tokens', 'output_tokens', 'cache_read_tokens',
                          'cost_usd', 'value_generated_usd', 'gate_guardrails_ok',
                          'gate_budget_ok', 'gate_heartbeat_ok', 'halt_reason',
                          'tools_used', 'files_modified', 'error_message']

        for field in allowed_fields:
            if field in data:
                updates.append(f'{field} = ?')
                value = data[field]
                if isinstance(value, (list, dict)):
                    value = json.dumps(value)
                params.append(value)

        # Auto-calculate cost if tokens provided
        if 'input_tokens' in data or 'output_tokens' in data:
            c.execute('SELECT input_tokens, output_tokens, cache_read_tokens, model FROM cc_runs WHERE run_id = ?', (run_id,))
            row = c.fetchone()
            if row:
                input_t = data.get('input_tokens', row[0])
                output_t = data.get('output_tokens', row[1])
                cache_t = data.get('cache_read_tokens', row[2])
                model = row[3]
                cost = calculate_cost(input_t, output_t, cache_t, model)
                updates.append('cost_usd = ?')
                params.append(cost)

        if updates:
            params.append(run_id)
            c.execute(f'UPDATE cc_runs SET {", ".join(updates)} WHERE run_id = ?', params)
            conn.commit()

        conn.close()
        socketio.emit('cc_run_updated', {'run_id': run_id, **data})
        return jsonify({'status': 'ok', 'run_id': run_id})

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/api/cc/run/<run_id>/complete', methods=['POST'])
def complete_run(run_id):
    """Mark a run as completed"""
    try:
        data = request.json or {}
        conn = get_db()
        c = conn.cursor()

        c.execute('SELECT started_at, model, prompt, source FROM cc_runs WHERE run_id = ?', (run_id,))
        row = c.fetchone()
        if not row:
            conn.close()
            return jsonify({'status': 'error', 'message': 'Run not found'}), 404

        started_at = datetime.fromisoformat(row[0]) if row[0] else datetime.now()
        model = row[1] or 'default'
        prompt = row[2] or ''
        source = row[3] or 'manual'
        completed_at = datetime.now()
        duration_ms = int((completed_at - started_at).total_seconds() * 1000)

        input_tokens = data.get('input_tokens', 0)
        output_tokens = data.get('output_tokens', 0)
        total_tokens = input_tokens + output_tokens
        cost_usd = calculate_cost(input_tokens, output_tokens, 0, model)

        value_generated = data.get('value_generated_usd')
        value_is_estimated = 1
        if value_generated is None or value_generated == 0:
            value_generated = estimate_run_value(prompt, source, cost_usd, total_tokens)
        else:
            value_is_estimated = 0

        status = data.get('status', 'completed')
        c.execute('''UPDATE cc_runs SET status = ?, completed_at = ?, duration_ms = ?,
                     input_tokens = COALESCE(?, input_tokens),
                     output_tokens = COALESCE(?, output_tokens),
                     cost_usd = ?,
                     value_generated_usd = ?,
                     value_is_estimated = ?,
                     error_message = COALESCE(?, error_message)
                     WHERE run_id = ?''',
                  (status, completed_at.isoformat(), duration_ms,
                   input_tokens, output_tokens, cost_usd,
                   value_generated, value_is_estimated, data.get('error_message'), run_id))
        conn.commit()

        # Check budget
        today = datetime.now().strftime('%Y-%m-%d')
        c.execute('SELECT COALESCE(SUM(cost_usd), 0) FROM cc_runs WHERE DATE(started_at) = ?', (today,))
        spent_today = c.fetchone()[0]

        c.execute('SELECT daily_limit_usd, alert_threshold_pct FROM budget_config WHERE name = "default"')
        budget_row = c.fetchone()
        daily_limit = budget_row[0] if budget_row else 10.00
        alert_pct = budget_row[1] if budget_row else 80
        budget_used_pct = (spent_today / daily_limit * 100) if daily_limit > 0 else 0

        conn.close()

        # Send budget alerts
        if budget_used_pct >= 100:
            send_macos_notification("CC Budget EXCEEDED", f"${spent_today:.2f}/${daily_limit:.2f}")
        elif budget_used_pct >= alert_pct:
            send_macos_notification("CC Budget Warning", f"{budget_used_pct:.0f}% used")

        socketio.emit('cc_run_completed', {
            'run_id': run_id,
            'status': status,
            'duration_ms': duration_ms,
            'cost_usd': cost_usd,
            'budget_used_pct': round(budget_used_pct, 1)
        })

        return jsonify({
            'status': 'ok',
            'run_id': run_id,
            'duration_ms': duration_ms,
            'cost_usd': cost_usd,
            'budget_used_pct': round(budget_used_pct, 1)
        })

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/api/cc/run/<run_id>/value', methods=['POST'])
def update_run_value(run_id):
    """Update the real value of a completed run"""
    try:
        data = request.json or {}
        value_usd = data.get('value_usd', 0)

        conn = get_db()
        c = conn.cursor()
        c.execute('''UPDATE cc_runs SET value_generated_usd = ?, value_is_estimated = 0
                     WHERE run_id = ?''', (value_usd, run_id))
        conn.commit()

        c.execute('SELECT cost_usd, value_generated_usd FROM cc_runs WHERE run_id = ?', (run_id,))
        row = c.fetchone()
        conn.close()

        if row:
            return jsonify({
                'status': 'ok',
                'run_id': run_id,
                'cost_usd': row[0],
                'value_usd': row[1],
                'roi_pct': round((row[1] / row[0] - 1) * 100, 1) if row[0] > 0 else 0
            })
        return jsonify({'status': 'error', 'message': 'Run not found'}), 404

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/api/cc/runs', methods=['GET'])
def get_runs():
    """Get runs with optional filters"""
    try:
        limit = request.args.get('limit', 50, type=int)
        offset = request.args.get('offset', 0, type=int)
        status = request.args.get('status')
        source = request.args.get('source')

        conn = get_db()
        c = conn.cursor()

        query = 'SELECT * FROM cc_runs WHERE 1=1'
        params = []

        if status:
            query += ' AND status = ?'
            params.append(status)
        if source:
            query += ' AND source = ?'
            params.append(source)

        query += ' ORDER BY started_at DESC LIMIT ? OFFSET ?'
        params.extend([limit, offset])

        c.execute(query, params)
        runs = [dict(row) for row in c.fetchall()]

        # Parse JSON fields
        for run in runs:
            for field in ['tools_used', 'files_modified', 'tags']:
                if run.get(field):
                    try:
                        run[field] = json.loads(run[field])
                    except:
                        pass

        conn.close()
        return jsonify(runs)

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

# ========== ROI ENDPOINTS ==========

@app.route('/api/cc/roi/summary', methods=['GET'])
def get_roi_summary():
    """Get today's ROI summary"""
    try:
        conn = get_db()
        c = conn.cursor()
        today = datetime.now().strftime('%Y-%m-%d')

        c.execute('''SELECT
                        COALESCE(SUM(cost_usd), 0) as total_cost,
                        COALESCE(SUM(value_generated_usd), 0) as total_revenue,
                        COALESCE(SUM(input_tokens), 0) as input_tokens,
                        COALESCE(SUM(output_tokens), 0) as output_tokens,
                        COUNT(*) as run_count,
                        COUNT(CASE WHEN status = 'running' THEN 1 END) as active_runs,
                        COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed_runs,
                        COUNT(CASE WHEN status IN ('failed', 'halted') THEN 1 END) as failed_runs
                     FROM cc_runs WHERE DATE(started_at) = ?''', (today,))

        row = c.fetchone()
        total_cost = row[0]
        total_revenue = row[1]

        c.execute('SELECT daily_limit_usd FROM budget_config WHERE name = "default"')
        budget_row = c.fetchone()
        daily_limit = budget_row[0] if budget_row else 10.00
        budget_pct = (total_cost / daily_limit * 100) if daily_limit > 0 else 0

        conn.close()

        profit = total_revenue - total_cost
        roi_pct = ((total_revenue - total_cost) / total_cost * 100) if total_cost > 0 else 0

        return jsonify({
            'today': today,
            'cost_usd': round(total_cost, 4),
            'revenue_usd': round(total_revenue, 2),
            'profit_usd': round(profit, 2),
            'roi_pct': round(roi_pct, 1),
            'tokens_input': row[2],
            'tokens_output': row[3],
            'runs_total': row[4],
            'runs_active': row[5],
            'runs_completed': row[6],


   600→            'runs_failed': row[7],
   601→            'budget_daily_usd': daily_limit,
   602→            'budget_used_pct': round(budget_pct, 1)
   603→        })
   604→
   605→    except Exception as e:
   606→        return jsonify({'status': 'error', 'message': str(e)}), 500
   607→
   608→@app.route('/api/cc/roi/all-time', methods=['GET'])
   609→def get_roi_all_time():
   610→    """Get all-time ROI statistics"""
   611→    try:
   612→        conn = get_db()
   613→        c = conn.cursor()
   614→
   615→        c.execute('''SELECT
   616→                        COALESCE(SUM(cost_usd), 0) as total_cost,
   617→                        COALESCE(SUM(value_generated_usd), 0) as total_revenue,
   618→                        COALESCE(SUM(input_tokens), 0) as input_tokens,
   619→                        COALESCE(SUM(output_tokens), 0) as output_tokens,
   620→                        COUNT(*) as total_runs,
   621→                        COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed_runs,
   622→                        COUNT(CASE WHEN status IN ('failed', 'halted') THEN 1 END) as failed_runs,
   623→                        MIN(DATE(started_at)) as first_run,
   624→                        MAX(DATE(started_at)) as last_run
   625→                     FROM cc_runs''')
   626→
   627→        row = c.fetchone()
   628→        total_cost = row[0]
   629→        total_revenue = row[1]
   630→        total_runs = row[4]
   631→        completed_runs = row[5]
   632→
   633→        c.execute('SELECT COUNT(DISTINCT DATE(started_at)) FROM cc_runs')
   634→        active_days = c.fetchone()[0]
   635→
   636→        conn.close()
   637→
   638→        profit = total_revenue - total_cost
   639→        roi_pct = ((total_revenue - total_cost) / total_cost * 100) if total_cost > 0 else 0
   640→        success_rate = (completed_runs / total_runs * 100) if total_runs > 0 else 0
   641→
   642→        return jsonify({
   643→            'cost_usd': round(total_cost, 4),
   644→            'revenue_usd': round(total_revenue, 2),
   645→            'profit_usd': round(profit, 2),
   646→            'roi_pct': round(roi_pct, 1),
   647→            'tokens_input': row[2],
   648→            'tokens_output': row[3],
   649→            'runs_total': total_runs,
   650→            'runs_completed': completed_runs,
   651→            'runs_failed': row[6],
   652→            'active_days': active_days,
   653→            'first_run_date': row[7],
   654→            'last_run_date': row[8],
   655→            'success_rate_pct': round(success_rate, 1)
   656→        })
   657→
   658→    except Exception as e:
   659→        return jsonify({'status': 'error', 'message': str(e)}), 500
   660→
   661→@app.route('/api/cc/roi/daily', methods=['GET'])
   662→def get_roi_daily():
   663→    """Get daily breakdown"""
   664→    try:
   665→        days = request.args.get('days', 30, type=int)
   666→        conn = get_db()
   667→        c = conn.cursor()
   668→
   669→        c.execute('''SELECT
   670→                        DATE(started_at) as date,
   671→                        COALESCE(SUM(cost_usd), 0) as cost,
   672→                        COALESCE(SUM(value_generated_usd), 0) as revenue,
   673→                        COUNT(*) as runs
   674→                     FROM cc_runs
   675→                     WHERE started_at >= DATE('now', ?)
   676→                     GROUP BY DATE(started_at)
   677→                     ORDER BY date DESC''', (f'-{days} days',))
   678→
   679→        daily = [dict(row) for row in c.fetchall()]
   680→        conn.close()
   681→        return jsonify(daily)
   682→
   683→    except Exception as e:
   684→        return jsonify({'status': 'error', 'message': str(e)}), 500
   685→
   686→# ========== BUDGET ENDPOINTS ==========
   687→
   688→@app.route('/api/cc/budget', methods=['GET'])
   689→def get_budget():
   690→    """Get budget configuration and usage"""
   691→    try:
   692→        conn = get_db()
   693→        c = conn.cursor()
   694→
   695→        # Get budget config
   696→        c.execute('SELECT daily_limit_usd, monthly_limit_usd, max_tokens_per_run, max_cost_per_run_usd, alert_threshold_pct, is_active FROM budget_config WHERE name = "default"')
   697→        row = c.fetchone()
   698→
   699→        if row:
   700→            daily_limit = row[0]
   701→            monthly_limit = row[1]
   702→        else:
   703→            daily_limit = 10.0
   704→            monthly_limit = 200.0
   705→
   706→        # Today's usage
   707→        today = datetime.now().strftime('%Y-%m-%d')
   708→        c.execute('SELECT COALESCE(SUM(cost_usd), 0) FROM cc_runs WHERE DATE(started_at) = ?', (today,))
   709→        spent_today = c.fetchone()[0]
   710→
   711→        # This month's usage
   712→        month_start = datetime.now().strftime('%Y-%m-01')
   713→        c.execute('SELECT COALESCE(SUM(cost_usd), 0) FROM cc_runs WHERE started_at >= ?', (month_start,))
   714→        spent_month = c.fetchone()[0]
   715→
   716→        conn.close()
   717→
   718→        return jsonify({
   719→            'daily_limit_usd': daily_limit,
   720→            'monthly_limit_usd': monthly_limit,
   721→            'max_tokens_per_run': row[2] if row else 100000,
   722→            'max_cost_per_run_usd': row[3] if row else 2.0,
   723→            'alert_threshold_pct': row[4] if row else 80,
   724→            'is_active': row[5] if row else True,
   725→            'spent_today': round(spent_today, 4),
   726→            'spent_month': round(spent_month, 4),
   727→            'daily_remaining': round(daily_limit - spent_today, 4),
   728→            'monthly_remaining': round(monthly_limit - spent_month, 4)
   729→        })
   730→
   731→    except Exception as e:
   732→        return jsonify({'status': 'error', 'message': str(e)}), 500
   733→
   734→@app.route('/api/cc/budget', methods=['POST'])
   735→def update_budget():
   736→    """Update budget configuration"""
   737→    try:
   738→        data = request.json or {}
   739→        conn = get_db()
   740→        c = conn.cursor()
   741→
   742→        updates = []
   743→        params = []
   744→        for field in ['daily_limit_usd', 'monthly_limit_usd', 'max_tokens_per_run', 'max_cost_per_run_usd', 'alert_threshold_pct', 'is_active']:
   745→            if field in data:
   746→                updates.append(f'{field} = ?')
   747→                params.append(data[field])
   748→
   749→        if updates:
   750→            updates.append('updated_at = ?')
   751→            params.append(datetime.now().isoformat())
   752→            params.append('default')
   753→            c.execute(f'UPDATE budget_config SET {", ".join(updates)} WHERE name = ?', params)
   754→            conn.commit()
   755→
   756→        conn.close()
   757→        return jsonify({'status': 'ok'})
   758→
   759→    except Exception as e:
   760→        return jsonify({'status': 'error', 'message': str(e)}), 500
   761→
   762→# ========== HEALTH & GATES ==========
   763→
   764→@app.route('/api/cc/health', methods=['GET'])
   765→def get_gate_health():
   766→    """Get 3-gate health status"""
   767→    try:
   768→        conn = get_db()
   769→        c = conn.cursor()
   770→
   771→        # Guardrails check
   772→        c.execute('SELECT COUNT(*) FROM guardrails WHERE is_active = 1')
   773→        guardrails_count = c.fetchone()[0]
   774→        guardrails_ok = guardrails_count > 0
   775→
   776→        # Budget check
   777→        today = datetime.now().strftime('%Y-%m-%d')
   778→        c.execute('SELECT COALESCE(SUM(cost_usd), 0) FROM cc_runs WHERE DATE(started_at) = ?', (today,))
   779→        spent_today = c.fetchone()[0]
   780→
   781→        c.execute('SELECT daily_limit_usd FROM budget_config WHERE name = "default"')
   782→        row = c.fetchone()
   783→        daily_limit = row[0] if row else 10.0
   784→        budget_ok = spent_today < daily_limit
   785→
   786→        # Heartbeat (gateway is running)
   787→        heartbeat_ok = True
   788→
   789→        conn.close()
   790→
   791→        overall = 'healthy' if all([guardrails_ok, budget_ok, heartbeat_ok]) else 'degraded'
   792→
   793→        return jsonify({
   794→            'overall': overall,
   795→            'guardrails': {'ok': guardrails_ok, 'active_rules': guardrails_count},
   796→            'budget': {'ok': budget_ok, 'spent_today': round(spent_today, 4), 'daily_limit': daily_limit},
   797→            'heartbeat': {'ok': heartbeat_ok, 'uptime': int(time.time() - GATEWAY_START_TIME)},
   798→            'timestamp': datetime.now().isoformat()
   799→        })

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

            'runs_failed': row[7],
            'budget_daily_usd': daily_limit,
            'budget_used_pct': round(budget_pct, 1)
        })

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/api/cc/roi/all-time', methods=['GET'])
def get_roi_all_time():
    """Get all-time ROI statistics"""
    try:
        conn = get_db()
        c = conn.cursor()

        c.execute('''SELECT
                        COALESCE(SUM(cost_usd), 0) as total_cost,
                        COALESCE(SUM(value_generated_usd), 0) as total_revenue,
                        COALESCE(SUM(input_tokens), 0) as input_tokens,
                        COALESCE(SUM(output_tokens), 0) as output_tokens,
                        COUNT(*) as total_runs,
                        COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed_runs,
                        COUNT(CASE WHEN status IN ('failed', 'halted') THEN 1 END) as failed_runs,
                        MIN(DATE(started_at)) as first_run,
                        MAX(DATE(started_at)) as last_run
                     FROM cc_runs''')

        row = c.fetchone()
        total_cost = row[0]
        total_revenue = row[1]
        total_runs = row[4]
        completed_runs = row[5]

        c.execute('SELECT COUNT(DISTINCT DATE(started_at)) FROM cc_runs')
        active_days = c.fetchone()[0]

        conn.close()

        profit = total_revenue - total_cost
        roi_pct = ((total_revenue - total_cost) / total_cost * 100) if total_cost > 0 else 0
        success_rate = (completed_runs / total_runs * 100) if total_runs > 0 else 0

        return jsonify({
            'cost_usd': round(total_cost, 4),
            'revenue_usd': round(total_revenue, 2),
            'profit_usd': round(profit, 2),
            'roi_pct': round(roi_pct, 1),
            'tokens_input': row[2],
            'tokens_output': row[3],
            'runs_total': total_runs,
            'runs_completed': completed_runs,
            'runs_failed': row[6],
            'active_days': active_days,
            'first_run_date': row[7],
            'last_run_date': row[8],
            'success_rate_pct': round(success_rate, 1)
        })

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/api/cc/roi/daily', methods=['GET'])
def get_roi_daily():
    """Get daily breakdown"""
    try:
        days = request.args.get('days', 30, type=int)
        conn = get_db()
        c = conn.cursor()

        c.execute('''SELECT
                        DATE(started_at) as date,
                        COALESCE(SUM(cost_usd), 0) as cost,
                        COALESCE(SUM(value_generated_usd), 0) as revenue,
                        COUNT(*) as runs
                     FROM cc_runs
                     WHERE started_at >= DATE('now', ?)
                     GROUP BY DATE(started_at)
                     ORDER BY date DESC''', (f'-{days} days',))

        daily = [dict(row) for row in c.fetchall()]
        conn.close()
        return jsonify(daily)

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

# ========== BUDGET ENDPOINTS ==========

@app.route('/api/cc/budget', methods=['GET'])
def get_budget():
    """Get budget configuration and usage"""
    try:
        conn = get_db()
        c = conn.cursor()

        # Get budget config
        c.execute('SELECT daily_limit_usd, monthly_limit_usd, max_tokens_per_run, max_cost_per_run_usd, alert_threshold_pct, is_active FROM budget_config WHERE name = "default"')
        row = c.fetchone()

        if row:
            daily_limit = row[0]
            monthly_limit = row[1]
        else:
            daily_limit = 10.0
            monthly_limit = 200.0

        # Today's usage
        today = datetime.now().strftime('%Y-%m-%d')
        c.execute('SELECT COALESCE(SUM(cost_usd), 0) FROM cc_runs WHERE DATE(started_at) = ?', (today,))
        spent_today = c.fetchone()[0]

        # This month's usage
        month_start = datetime.now().strftime('%Y-%m-01')
        c.execute('SELECT COALESCE(SUM(cost_usd), 0) FROM cc_runs WHERE started_at >= ?', (month_start,))
        spent_month = c.fetchone()[0]

        conn.close()

        return jsonify({
            'daily_limit_usd': daily_limit,
            'monthly_limit_usd': monthly_limit,
            'max_tokens_per_run': row[2] if row else 100000,
            'max_cost_per_run_usd': row[3] if row else 2.0,
            'alert_threshold_pct': row[4] if row else 80,
            'is_active': row[5] if row else True,
            'spent_today': round(spent_today, 4),
            'spent_month': round(spent_month, 4),
            'daily_remaining': round(daily_limit - spent_today, 4),
            'monthly_remaining': round(monthly_limit - spent_month, 4)
        })

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/api/cc/budget', methods=['POST'])
def update_budget():
    """Update budget configuration"""
    try:
        data = request.json or {}
        conn = get_db()
        c = conn.cursor()

        updates = []
        params = []
        for field in ['daily_limit_usd', 'monthly_limit_usd', 'max_tokens_per_run', 'max_cost_per_run_usd', 'alert_threshold_pct', 'is_active']:
            if field in data:
                updates.append(f'{field} = ?')
                params.append(data[field])

        if updates:
            updates.append('updated_at = ?')
            params.append(datetime.now().isoformat())
            params.append('default')
            c.execute(f'UPDATE budget_config SET {", ".join(updates)} WHERE name = ?', params)
            conn.commit()

        conn.close()
        return jsonify({'status': 'ok'})

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

# ========== HEALTH & GATES ==========

@app.route('/api/cc/health', methods=['GET'])
def get_gate_health():
    """Get 3-gate health status"""
    try:
        conn = get_db()
        c = conn.cursor()

        # Guardrails check
        c.execute('SELECT COUNT(*) FROM guardrails WHERE is_active = 1')
        guardrails_count = c.fetchone()[0]
        guardrails_ok = guardrails_count > 0

        # Budget check
        today = datetime.now().strftime('%Y-%m-%d')
        c.execute('SELECT COALESCE(SUM(cost_usd), 0) FROM cc_runs WHERE DATE(started_at) = ?', (today,))
        spent_today = c.fetchone()[0]

        c.execute('SELECT daily_limit_usd FROM budget_config WHERE name = "default"')
        row = c.fetchone()
        daily_limit = row[0] if row else 10.0
        budget_ok = spent_today < daily_limit

        # Heartbeat (gateway is running)
        heartbeat_ok = True

        conn.close()

        overall = 'healthy' if all([guardrails_ok, budget_ok, heartbeat_ok]) else 'degraded'

        return jsonify({
            'overall': overall,
            'guardrails': {'ok': guardrails_ok, 'active_rules': guardrails_count},
            'budget': {'ok': budget_ok, 'spent_today': round(spent_today, 4), 'daily_limit': daily_limit},
            'heartbeat': {'ok': heartbeat_ok, 'uptime': int(time.time() - GATEWAY_START_TIME)},
            'timestamp': datetime.now().isoformat()
        })


Let me check for the CC-ROI-QUICKSTART.md mentioned in the CLAUDE.md:


find /Users/neog -name "*CC-ROI*" -type f 2>/dev/null

Find CC-ROI documentation


sqlite3 /Users/neog/logs.db ".tables" 2>/dev/null

Check SQLite database tables


   799→        })
   800→
   801→    except Exception as e:
   802→        return jsonify({'status': 'error', 'message': str(e)}), 500
   803→
   804→@app.route('/api/cc/guardrails', methods=['GET'])
   805→def get_guardrails():
   806→    """Get active guardrails"""
   807→    try:
   808→        conn = get_db()
   809→        c = conn.cursor()
   810→        c.execute('SELECT * FROM guardrails WHERE is_active = 1')
   811→        guardrails = [dict(row) for row in c.fetchall()]
   812→        conn.close()
   813→        return jsonify(guardrails)
   814→
   815→    except Exception as e:
   816→        return jsonify({'status': 'error', 'message': str(e)}), 500
   817→
   818→@app.route('/api/cc/gate/check', methods=['POST'])
   819→def gate_check():
   820→    """Pre-flight safety gate check"""
   821→    try:
   822→        data = request.json or {}
   823→        prompt = data.get('prompt', '')
   824→        estimated_cost = data.get('estimated_cost', 0)
   825→
   826→        conn = get_db()
   827→        c = conn.cursor()
   828→
   829→        # Check guardrails
   830→        c.execute('SELECT pattern, action FROM guardrails WHERE is_active = 1')
   831→        blocked = []
   832→        warned = []
   833→        for pattern, action in c.fetchall():
   834→            if pattern.lower() in prompt.lower():
   835→                if action == 'block':
   836→                    blocked.append(pattern)
   837→                else:
   838→                    warned.append(pattern)
   839→
   840→        # Check budget
   841→        today = datetime.now().strftime('%Y-%m-%d')
   842→        c.execute('SELECT COALESCE(SUM(cost_usd), 0) FROM cc_runs WHERE DATE(started_at) = ?', (today,))
   843→        spent_today = c.fetchone()[0]
   844→
   845→        c.execute('SELECT daily_limit_usd, max_cost_per_run_usd FROM budget_config WHERE name = "default"')
   846→        row = c.fetchone()
   847→        daily_limit = row[0] if row else 10.0
   848→        max_per_run = row[1] if row else 2.0
   849→
   850→        budget_ok = (spent_today + estimated_cost) <= daily_limit
   851→        cost_ok = estimated_cost <= max_per_run
   852→
   853→        conn.close()
   854→
   855→        can_proceed = len(blocked) == 0 and budget_ok and cost_ok
   856→
   857→        return jsonify({
   858→            'can_proceed': can_proceed,
   859→            'guardrails': {
   860→                'blocked': blocked,
   861→                'warned': warned,
   862→                'ok': len(blocked) == 0
   863→            },
   864→            'budget': {
   865→                'ok': budget_ok and cost_ok,
   866→                'spent_today': round(spent_today, 4),
   867→                'daily_limit': daily_limit,
   868→                'remaining': round(daily_limit - spent_today, 4),
   869→                'estimated_cost': estimated_cost
   870→            }
   871→        })
   872→
   873→    except Exception as e:
   874→        return jsonify({'status': 'error', 'message': str(e)}), 500
   875→
   876→# ========== EVENTS ==========
   877→
   878→@app.route('/api/cc/event', methods=['POST'])
   879→def log_event():
   880→    """Log an event for a run"""
   881→    try:
   882→        data = request.json or {}
   883→        run_id = data.get('run_id')
   884→        if not run_id:
   885→            return jsonify({'status': 'error', 'message': 'run_id required'}), 400
   886→
   887→        input_tokens = data.get('input_tokens', 0)
   888→        output_tokens = data.get('output_tokens', 0)
   889→        cost = calculate_cost(input_tokens, output_tokens)
   890→
   891→        conn = get_db()
   892→        c = conn.cursor()
   893→        c.execute('''INSERT INTO cc_events (run_id, event_type, event_subtype,
   894→                     input_tokens, output_tokens, cost_usd, duration_ms, success)
   895→                     VALUES (?, ?, ?, ?, ?, ?, ?, ?)''',
   896→                  (run_id,
   897→                   data.get('event_type', 'unknown'),
   898→                   data.get('event_subtype'),
   899→                   input_tokens, output_tokens, cost,
   900→                   data.get('duration_ms'),
   901→                   data.get('success', True)))
   902→
   903→        event_id = c.lastrowid
   904→
   905→        # Update run totals
   906→        c.execute('''UPDATE cc_runs SET
   907→                     input_tokens = input_tokens + ?,
   908→                     output_tokens = output_tokens + ?,
   909→                     cost_usd = cost_usd + ?
   910→                     WHERE run_id = ?''',
   911→                  (input_tokens, output_tokens, cost, run_id))
   912→
   913→        conn.commit()
   914→        conn.close()
   915→
   916→        socketio.emit('cc_event', {'id': event_id, 'run_id': run_id, 'cost_usd': cost})
   917→        return jsonify({'status': 'ok', 'event_id': event_id, 'cost_usd': cost})
   918→
   919→    except Exception as e:
   920→        return jsonify({'status': 'error', 'message': str(e)}), 500
   921→
   922→# ========== WEBSOCKET ==========
   923→
   924→@socketio.on('connect')
   925→def handle_connect():
   926→    print(f"[WS] Client connected: {request.sid}")
   927→    emit('connected', {'message': 'Connected to NEOG Gateway', 'version': GATEWAY_VERSION})
   928→
   929→@socketio.on('disconnect')
   930→def handle_disconnect():
   931→    print(f"[WS] Client disconnected: {request.sid}")
   932→
   933→# ========== MAIN ==========
   934→
   935→if __name__ == '__main__':
   936→    print(f"""
   937→╔══════════════════════════════════════════════════════════════╗
   938→║                     NEOG Gateway v{GATEWAY_VERSION}                       ║
   939→║              Unified API for NEOG Command                    ║
   940→╠══════════════════════════════════════════════════════════════╣
   941→║  Port: {PORT}                                                   ║
   942→║  Database: {DB_PATH:<47} ║
   943→║  Started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S'):<47} ║
   944→╚══════════════════════════════════════════════════════════════╝
   945→    """)
   946→
   947→    init_db()
   948→    socketio.run(app, host='0.0.0.0', port=PORT, debug=False, allow_unsafe_werkzeug=True)
   949→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

        })

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/api/cc/guardrails', methods=['GET'])
def get_guardrails():
    """Get active guardrails"""
    try:
        conn = get_db()
        c = conn.cursor()
        c.execute('SELECT * FROM guardrails WHERE is_active = 1')
        guardrails = [dict(row) for row in c.fetchall()]
        conn.close()
        return jsonify(guardrails)

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/api/cc/gate/check', methods=['POST'])
def gate_check():
    """Pre-flight safety gate check"""
    try:
        data = request.json or {}
        prompt = data.get('prompt', '')
        estimated_cost = data.get('estimated_cost', 0)

        conn = get_db()
        c = conn.cursor()

        # Check guardrails
        c.execute('SELECT pattern, action FROM guardrails WHERE is_active = 1')
        blocked = []
        warned = []
        for pattern, action in c.fetchall():
            if pattern.lower() in prompt.lower():
                if action == 'block':
                    blocked.append(pattern)
                else:
                    warned.append(pattern)

        # Check budget
        today = datetime.now().strftime('%Y-%m-%d')
        c.execute('SELECT COALESCE(SUM(cost_usd), 0) FROM cc_runs WHERE DATE(started_at) = ?', (today,))
        spent_today = c.fetchone()[0]

        c.execute('SELECT daily_limit_usd, max_cost_per_run_usd FROM budget_config WHERE name = "default"')
        row = c.fetchone()
        daily_limit = row[0] if row else 10.0
        max_per_run = row[1] if row else 2.0

        budget_ok = (spent_today + estimated_cost) <= daily_limit
        cost_ok = estimated_cost <= max_per_run

        conn.close()

        can_proceed = len(blocked) == 0 and budget_ok and cost_ok

        return jsonify({
            'can_proceed': can_proceed,
            'guardrails': {
                'blocked': blocked,
                'warned': warned,
                'ok': len(blocked) == 0
            },
            'budget': {
                'ok': budget_ok and cost_ok,
                'spent_today': round(spent_today, 4),
                'daily_limit': daily_limit,
                'remaining': round(daily_limit - spent_today, 4),
                'estimated_cost': estimated_cost
            }
        })

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

# ========== EVENTS ==========

@app.route('/api/cc/event', methods=['POST'])
def log_event():
    """Log an event for a run"""
    try:
        data = request.json or {}
        run_id = data.get('run_id')
        if not run_id:
            return jsonify({'status': 'error', 'message': 'run_id required'}), 400

        input_tokens = data.get('input_tokens', 0)
        output_tokens = data.get('output_tokens', 0)
        cost = calculate_cost(input_tokens, output_tokens)

        conn = get_db()
        c = conn.cursor()
        c.execute('''INSERT INTO cc_events (run_id, event_type, event_subtype,
                     input_tokens, output_tokens, cost_usd, duration_ms, success)
                     VALUES (?, ?, ?, ?, ?, ?, ?, ?)''',
                  (run_id,
                   data.get('event_type', 'unknown'),
                   data.get('event_subtype'),
                   input_tokens, output_tokens, cost,
                   data.get('duration_ms'),
                   data.get('success', True)))

        event_id = c.lastrowid

        # Update run totals
        c.execute('''UPDATE cc_runs SET
                     input_tokens = input_tokens + ?,
                     output_tokens = output_tokens + ?,
                     cost_usd = cost_usd + ?
                     WHERE run_id = ?''',
                  (input_tokens, output_tokens, cost, run_id))

        conn.commit()
        conn.close()

        socketio.emit('cc_event', {'id': event_id, 'run_id': run_id, 'cost_usd': cost})
        return jsonify({'status': 'ok', 'event_id': event_id, 'cost_usd': cost})

    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

# ========== WEBSOCKET ==========

@socketio.on('connect')
def handle_connect():
    print(f"[WS] Client connected: {request.sid}")
    emit('connected', {'message': 'Connected to NEOG Gateway', 'version': GATEWAY_VERSION})

@socketio.on('disconnect')
def handle_disconnect():
    print(f"[WS] Client disconnected: {request.sid}")

# ========== MAIN ==========

if __name__ == '__main__':
    print(f"""
╔══════════════════════════════════════════════════════════════╗
║                     NEOG Gateway v{GATEWAY_VERSION}                       ║
║              Unified API for NEOG Command                    ║
╠══════════════════════════════════════════════════════════════╣
║  Port: {PORT}                                                   ║
║  Database: {DB_PATH:<47} ║
║  Started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S'):<47} ║
╚══════════════════════════════════════════════════════════════╝
    """)

    init_db()
    socketio.run(app, host='0.0.0.0', port=PORT, debug=False, allow_unsafe_werkzeug=True)


budget_config  cc_runs        guardrails
cc_events      cc_tags        logs


Great! Now let me read the documentation and get more details about the database schema:


sqlite3 /Users/neog/logs.db ".schema cc_runs" 2>/dev/null

Get cc_runs table schema


sqlite3 /Users/neog/logs.db ".schema budget_config" 2>/dev/null

Get budget_config table schema


sqlite3 /Users/neog/logs.db ".schema guardrails" 2>/dev/null

Get guardrails table schema


     1→# Claude Code ROI Dashboard - Quick Start Guide
     2→
     3→Track costs, revenue, and ROI for all your Claude Code automations.
     4→
     5→## Quick Start
     6→
     7→```bash
     8→# 1. Start services
     9→./cc-services.sh start
    10→
    11→# 2. Open dashboard
    12→open http://localhost:9001/roi
    13→
    14→# 3. Check status
    15→./cc-track.sh status
    16→```
    17→
    18→## Tracking Runs
    19→
    20→### Automatic (via CLI Server)
    21→Runs executed through `claude-cli-server.py` (port 9003) are automatically tracked.
    22→
    23→### Manual Tracking
    24→```bash
    25→# Start a run
    26→./cc-track.sh start "Build user dashboard" "ios_app"
    27→# Returns: Run started: abc123...
    28→
    29→# Complete with value generated
    30→./cc-track.sh complete abc123 25.00
    31→```
    32→
    33→### Adding Revenue to Existing Runs
    34→```bash
    35→./cc-track.sh value abc123 50.00
    36→```
    37→
    38→## Safety Gates
    39→
    40→Before running automations, check if they pass safety gates:
    41→
    42→```bash
    43→./cc-track.sh gate "Your automation prompt here"
    44→```
    45→
    46→Gates check:
    47→- **Guardrails**: Blocks dangerous commands (rm -rf, sudo, force push)
    48→- **Budget**: Blocks if daily limit exceeded ($10/day default)
    49→- **Heartbeat**: Monitors active run health
    50→
    51→## Key Commands
    52→
    53→| Command | Description |
    54→|---------|-------------|
    55→| `./cc-services.sh start` | Start all services |
    56→| `./cc-services.sh stop` | Stop all services |
    57→| `./cc-services.sh status` | Check service status |
    58→| `./cc-track.sh status` | Today's stats |
    59→| `./cc-track.sh runs 10` | Recent runs |
    60→| `./cc-track.sh gate "prompt"` | Pre-flight safety check |
    61→| `./cc-daily-report.sh` | Generate daily report |
    62→| `./cc-daily-report.sh --notify` | Report + macOS notification |
    63→
    64→## API Endpoints
    65→
    66→Base URL: `http://localhost:9001/api/cc`
    67→
    68→| Endpoint | Method | Description |
    69→|----------|--------|-------------|
    70→| `/run` | POST | Create new run |
    71→| `/run/<id>/complete` | POST | Complete run |
    72→| `/runs` | GET | List runs |
    73→| `/events` | GET | List events |
    74→| `/roi/summary` | GET | Today's stats |
    75→| `/roi/all-time` | GET | All-time stats |
    76→| `/roi/daily` | GET | Daily breakdown |
    77→| `/gate/check` | POST | Pre-flight gate check |
    78→| `/health` | GET | System health |
    79→| `/budget` | GET/POST | Budget config |
    80→| `/guardrails` | GET/POST | Safety rules |
    81→
    82→## Budget Alerts
    83→
    84→- **80% Warning**: macOS notification when 80% of daily budget used
    85→- **100% Block**: Automations blocked, notification sent
    86→
    87→## Token Costs (per 1M tokens)
    88→
    89→| Model | Input | Output |
    90→|-------|-------|--------|
    91→| Claude Opus 4.5 | $15.00 | $75.00 |
    92→| Claude Sonnet 4.5 | $3.00 | $15.00 |
    93→
    94→## Files
    95→
    96→| File | Purpose |
    97→|------|---------|
    98→| `log-server.py` | Backend API (port 9001) |
    99→| `claude-cli-server.py` | CLI with auto-tracking (port 9003) |
   100→| `automation-roi-dashboard.html` | Web UI |
   101→| `cc-services.sh` | Service manager |
   102→| `cc-track.sh` | CLI tracking tool |
   103→| `cc-daily-report.sh` | Report generator |
   104→
   105→## Dashboard URL
   106→
   107→```
   108→http://localhost:9001/roi
   109→```
   110→
   111→Features:
   112→- Today's cost/revenue/profit
   113→- All-time ROI stats
   114→- 3-Gate status panel
   115→- Cost vs Revenue chart
   116→- Runs and events tables
   117→- Gate configuration
   118→- Real-time WebSocket updates
   119→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

# Claude Code ROI Dashboard - Quick Start Guide

Track costs, revenue, and ROI for all your Claude Code automations.

## Quick Start

```bash
# 1. Start services
./cc-services.sh start

# 2. Open dashboard
open http://localhost:9001/roi

# 3. Check status
./cc-track.sh status
```

## Tracking Runs

### Automatic (via CLI Server)
Runs executed through `claude-cli-server.py` (port 9003) are automatically tracked.

### Manual Tracking
```bash
# Start a run
./cc-track.sh start "Build user dashboard" "ios_app"
# Returns: Run started: abc123...

# Complete with value generated
./cc-track.sh complete abc123 25.00
```

### Adding Revenue to Existing Runs
```bash
./cc-track.sh value abc123 50.00
```

## Safety Gates

Before running automations, check if they pass safety gates:

```bash
./cc-track.sh gate "Your automation prompt here"
```

Gates check:
- **Guardrails**: Blocks dangerous commands (rm -rf, sudo, force push)
- **Budget**: Blocks if daily limit exceeded ($10/day default)
- **Heartbeat**: Monitors active run health

## Key Commands

| Command | Description |
|---------|-------------|
| `./cc-services.sh start` | Start all services |
| `./cc-services.sh stop` | Stop all services |
| `./cc-services.sh status` | Check service status |
| `./cc-track.sh status` | Today's stats |
| `./cc-track.sh runs 10` | Recent runs |
| `./cc-track.sh gate "prompt"` | Pre-flight safety check |
| `./cc-daily-report.sh` | Generate daily report |
| `./cc-daily-report.sh --notify` | Report + macOS notification |

## API Endpoints

Base URL: `http://localhost:9001/api/cc`

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/run` | POST | Create new run |
| `/run/<id>/complete` | POST | Complete run |
| `/runs` | GET | List runs |
| `/events` | GET | List events |
| `/roi/summary` | GET | Today's stats |
| `/roi/all-time` | GET | All-time stats |
| `/roi/daily` | GET | Daily breakdown |
| `/gate/check` | POST | Pre-flight gate check |
| `/health` | GET | System health |
| `/budget` | GET/POST | Budget config |
| `/guardrails` | GET/POST | Safety rules |

## Budget Alerts

- **80% Warning**: macOS notification when 80% of daily budget used
- **100% Block**: Automations blocked, notification sent

## Token Costs (per 1M tokens)

| Model | Input | Output |
|-------|-------|--------|
| Claude Opus 4.5 | $15.00 | $75.00 |
| Claude Sonnet 4.5 | $3.00 | $15.00 |

## Files

| File | Purpose |
|------|---------|
| `log-server.py` | Backend API (port 9001) |
| `claude-cli-server.py` | CLI with auto-tracking (port 9003) |
| `automation-roi-dashboard.html` | Web UI |
| `cc-services.sh` | Service manager |
| `cc-track.sh` | CLI tracking tool |
| `cc-daily-report.sh` | Report generator |

## Dashboard URL

```
http://localhost:9001/roi
```

Features:
- Today's cost/revenue/profit
- All-time ROI stats
- 3-Gate status panel
- Cost vs Revenue chart
- Runs and events tables
- Gate configuration
- Real-time WebSocket updates


CREATE TABLE cc_runs (
        run_id TEXT PRIMARY KEY,
        session_id TEXT,
        prompt TEXT NOT NULL,
        source TEXT DEFAULT 'manual',
        device_ip TEXT,
        status TEXT DEFAULT 'pending',

        input_tokens INTEGER DEFAULT 0,
        output_tokens INTEGER DEFAULT 0,
        cache_read_tokens INTEGER DEFAULT 0,
        model TEXT DEFAULT 'claude-opus-4-5-20251101',

        cost_usd REAL DEFAULT 0,
        value_generated_usd REAL DEFAULT 0,

        gate_guardrails_ok BOOLEAN DEFAULT 1,
        gate_budget_ok BOOLEAN DEFAULT 1,
        gate_heartbeat_ok BOOLEAN DEFAULT 1,
        halt_reason TEXT,

        started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        completed_at DATETIME,
        duration_ms INTEGER,

        tools_used TEXT,
        files_modified TEXT,
        error_message TEXT,
        tags TEXT
    );
CREATE INDEX idx_cc_runs_status ON cc_runs(status);
CREATE INDEX idx_cc_runs_started ON cc_runs(started_at);


CREATE TABLE budget_config (
        id INTEGER PRIMARY KEY,
        name TEXT UNIQUE NOT NULL,
        daily_limit_usd REAL DEFAULT 10.00,
        monthly_limit_usd REAL DEFAULT 200.00,
        max_tokens_per_run INTEGER DEFAULT 100000,
        max_cost_per_run_usd REAL DEFAULT 2.00,
        alert_threshold_pct INTEGER DEFAULT 80,
        is_active BOOLEAN DEFAULT 1,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
    );


CREATE TABLE guardrails (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        rule_type TEXT NOT NULL,
        pattern TEXT NOT NULL,
        action TEXT DEFAULT 'block',
        is_active BOOLEAN DEFAULT 1,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    );


Perfect! Now let me check the cc_events and cc_tags schemas:


sqlite3 /Users/neog/logs.db ".schema cc_events" 2>/dev/null

Get cc_events table schema


sqlite3 /Users/neog/logs.db ".schema cc_tags" 2>/dev/null

Get cc_tags table schema


sqlite3 /Users/neog/logs.db "SELECT COUNT(*) as run_count FROM cc_runs;" 2>/dev/null

Check how many runs are in database


sqlite3 /Users/neog/logs.db "SELECT COALESCE(SUM(cost_usd), 0) as total_cost, COALESCE(SUM(value_generated_usd), 0) as total_revenue FROM cc_runs;" 2>/dev/null

Get total cost and revenue stats


CREATE TABLE cc_events (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        run_id TEXT NOT NULL,
        event_type TEXT NOT NULL,
        event_subtype TEXT,

        input_tokens INTEGER DEFAULT 0,
        output_tokens INTEGER DEFAULT 0,
        cost_usd REAL DEFAULT 0,

        input_data TEXT,
        output_data TEXT,
        duration_ms INTEGER,
        success BOOLEAN DEFAULT 1,

        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (run_id) REFERENCES cc_runs(run_id)
    );
CREATE INDEX idx_cc_events_run ON cc_events(run_id);
CREATE INDEX idx_cc_events_type ON cc_events(event_type);


CREATE TABLE cc_tags (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT UNIQUE NOT NULL,
        color TEXT DEFAULT '#00d4ff',
        description TEXT,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    );


Let me read the other cc shell scripts to complete the picture: