From 5d6141b139e41e090229877f7863cca2963457c5 Mon Sep 17 00:00:00 2001 From: Erik M Jacobs Date: Tue, 20 Jan 2026 17:14:22 -0500 Subject: [PATCH] feat: Add --no-usage flag to disable usage tracking Adds --no-usage CLI flag to disable usage tracking, which significantly reduces memory usage by avoiding parsing of large JSONL conversation logs. Changes: - Add --no-usage and --cwd CLI options to bin/cc-web.js - Conditionally create usageReader/usageAnalytics in server.js - Pass usageTracking flag to client on WebSocket connect - Update client to skip usage requests when disabled - Hide 'Show Token Stats' setting when usage tracking disabled Co-Authored-By: Claude Opus 4.5 --- bin/cc-web.js | 22 ++++++++++++++- src/public/app.js | 68 ++++++++++++++++++++++++++++++++++++++++------- src/server.js | 25 ++++++++++------- 3 files changed, 96 insertions(+), 19 deletions(-) diff --git a/bin/cc-web.js b/bin/cc-web.js index 803ea4a..44197fb 100755 --- a/bin/cc-web.js +++ b/bin/cc-web.js @@ -20,6 +20,8 @@ program .option('--cert ', 'path to SSL certificate file') .option('--key ', 'path to SSL private key file') .option('--dev', 'development mode with additional logging') + .option('--cwd ', 'working directory for Claude sessions (default: current directory)') + .option('--no-usage', 'disable usage tracking (reduces memory usage)') .option('--plan ', 'subscription plan (pro, max5, max20)', 'max20') .option('--claude-alias ', 'display alias for Claude (default: env CLAUDE_ALIAS or "Claude")') .option('--codex-alias ', 'display alias for Codex (default: env CODEX_ALIAS or "Codex")') @@ -62,6 +64,22 @@ async function main() { } } + // Resolve and validate working directory if provided + let cwd = process.cwd(); + if (options.cwd) { + const resolvedCwd = path.resolve(options.cwd); + const fs = require('fs'); + if (!fs.existsSync(resolvedCwd)) { + console.error(`Error: Working directory does not exist: ${resolvedCwd}`); + process.exit(1); + } + if (!fs.statSync(resolvedCwd).isDirectory()) { + console.error(`Error: Path is not a directory: ${resolvedCwd}`); + process.exit(1); + } + cwd = resolvedCwd; + } + const serverOptions = { port, auth: authToken, @@ -70,6 +88,8 @@ async function main() { cert: options.cert, key: options.key, dev: options.dev, + cwd: cwd, + usageTracking: options.usage !== false, // --no-usage sets this to false plan: options.plan, // UI aliases for assistants claudeAlias: options.claudeAlias || process.env.CLAUDE_ALIAS || 'Claude', @@ -80,7 +100,7 @@ async function main() { console.log('Starting Claude Code Web Interface...'); console.log(`Port: ${port}`); - console.log('Mode: Folder selection mode'); + console.log(`Working directory: ${cwd}`); console.log(`Plan: ${options.plan}`); console.log(`Aliases: Claude → "${serverOptions.claudeAlias}", Codex → "${serverOptions.codexAlias}", Agent → "${serverOptions.agentAlias}"`); diff --git a/src/public/app.js b/src/public/app.js index b35e564..76e5ae9 100644 --- a/src/public/app.js +++ b/src/public/app.js @@ -31,7 +31,8 @@ class ClaudeCodeWebInterface { this.sessionStats = null; this.sessionTimer = null; this.sessionTimerInterval = null; - + this.usageTrackingEnabled = true; // Server-side usage tracking state + this.splitContainer = null; this.init(); } @@ -614,8 +615,11 @@ class ClaudeCodeWebInterface { switch (message.type) { case 'connected': this.connectionId = message.connectionId; + // Store server-side usage tracking state + this.usageTrackingEnabled = message.usageTracking !== false; + this.updateUsageTrackingUI(); break; - + case 'session_created': this.currentClaudeSessionId = message.sessionId; this.currentClaudeSessionName = message.sessionName; @@ -816,8 +820,8 @@ class ClaudeCodeWebInterface { case 'usage_update': this.updateUsageDisplay( - message.sessionStats, - message.dailyStats, + message.sessionStats, + message.dailyStats, message.sessionTimer, message.analytics, message.burnRate, @@ -825,7 +829,20 @@ class ClaudeCodeWebInterface { message.limits ); break; - + + case 'usage_stats': + // Handle usage_stats response (sent when tracking is disabled) + if (message.disabled) { + this.usageTrackingEnabled = false; + this.updateUsageTrackingUI(); + // Stop polling since it's disabled + if (this.usageUpdateTimer) { + clearInterval(this.usageUpdateTimer); + this.usageUpdateTimer = null; + } + } + break; + default: console.log('Unknown message type:', message.type); } @@ -985,18 +1002,21 @@ class ClaudeCodeWebInterface { showSettings() { const modal = document.getElementById('settingsModal'); modal.classList.add('active'); - + // Prevent body scroll on mobile when modal is open if (this.isMobile) { document.body.style.overflow = 'hidden'; } - + const settings = this.loadSettings(); document.getElementById('fontSize').value = settings.fontSize; document.getElementById('fontSizeValue').textContent = settings.fontSize + 'px'; const themeSelect = document.getElementById('themeSelect'); if (themeSelect) themeSelect.value = settings.theme === 'light' ? 'light' : 'dark'; document.getElementById('showTokenStats').checked = settings.showTokenStats; + + // Update usage tracking UI state (hide checkbox if disabled server-side) + this.updateUsageTrackingUI(); } hideSettings() { @@ -1828,15 +1848,45 @@ class ClaudeCodeWebInterface { } requestUsageStats() { + // Skip if usage tracking is disabled server-side + if (!this.usageTrackingEnabled) { + return; + } + if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.send(JSON.stringify({ type: 'get_usage' })); } - + // Start periodic updates if not already running if (!this.usageUpdateTimer) { this.usageUpdateTimer = setInterval(() => { this.requestUsageStats(); - }, 10000); // Update every 10 seconds for more real-time stats + }, 60000); // Update every 60 seconds + } + } + + updateUsageTrackingUI() { + // Update the "Show Token Stats" checkbox in settings based on server-side usage tracking state + const checkbox = document.getElementById('showTokenStats'); + const settingGroup = checkbox?.closest('.setting-group'); + + if (!this.usageTrackingEnabled) { + // Disable and hide the setting when usage tracking is disabled server-side + if (checkbox) { + checkbox.disabled = true; + checkbox.checked = false; + } + if (settingGroup) { + settingGroup.style.display = 'none'; + } + } else { + // Re-enable if usage tracking becomes available + if (checkbox) { + checkbox.disabled = false; + } + if (settingGroup) { + settingGroup.style.display = ''; + } } } diff --git a/src/server.js b/src/server.js index de88bf1..902d08f 100644 --- a/src/server.js +++ b/src/server.js @@ -24,10 +24,11 @@ class ClaudeCodeWebServer { this.keyFile = options.key; this.folderMode = options.folderMode !== false; // Default to true this.selectedWorkingDir = null; - this.baseFolder = process.cwd(); // The folder where the app runs from + this.baseFolder = options.cwd || process.cwd(); // The folder where the app runs from (or specified via --cwd) + this.usageTracking = options.usageTracking !== false; // Default to true, --no-usage disables // Session duration in hours (default to 5 hours from first message) this.sessionDurationHours = parseFloat(process.env.CLAUDE_SESSION_HOURS || options.sessionHours || 5); - + this.app = express(); this.claudeSessions = new Map(); // Persistent sessions (claude, codex, or agent) this.webSocketConnections = new Map(); // Maps WebSocket connection ID to session info @@ -35,12 +36,13 @@ class ClaudeCodeWebServer { this.codexBridge = new CodexBridge(); this.agentBridge = new AgentBridge(); this.sessionStore = new SessionStore(); - this.usageReader = new UsageReader(this.sessionDurationHours); - this.usageAnalytics = new UsageAnalytics({ + // Only create usage tracking objects if enabled (they parse large JSONL files) + this.usageReader = this.usageTracking ? new UsageReader(this.sessionDurationHours) : null; + this.usageAnalytics = this.usageTracking ? new UsageAnalytics({ sessionDurationHours: this.sessionDurationHours, plan: options.plan || process.env.CLAUDE_PLAN || 'max20', customCostLimit: parseFloat(process.env.CLAUDE_COST_LIMIT || options.customCostLimit || 50.00) - }); + }) : null; this.autoSaveInterval = null; this.startTime = Date.now(); // Track server start time this.isShuttingDown = false; // Flag to prevent duplicate shutdown @@ -51,7 +53,7 @@ class ClaudeCodeWebServer { codex: options.codexAlias || process.env.CODEX_ALIAS || 'Codex', agent: options.agentAlias || process.env.AGENT_ALIAS || 'Cursor' }; - + this.setupExpress(); this.loadPersistedSessions(); this.setupAutoSave(); @@ -629,10 +631,11 @@ class ClaudeCodeWebServer { this.cleanupWebSocketConnection(wsId); }); - // Send initial connection message + // Send initial connection message with server configuration this.sendToWebSocket(ws, { type: 'connected', - connectionId: wsId + connectionId: wsId, + usageTracking: this.usageTracking }); // If sessionId provided, auto-join that session @@ -745,7 +748,11 @@ class ClaudeCodeWebServer { break; case 'get_usage': - this.handleGetUsage(wsInfo); + if (this.usageTracking) { + this.handleGetUsage(wsInfo); + } else { + this.sendToWebSocket(wsInfo.ws, { type: 'usage_stats', disabled: true }); + } break; default: