From d1733f34e0da88e9f62128646bd4e5773b93981a Mon Sep 17 00:00:00 2001 From: simos Date: Sat, 1 Nov 2025 05:55:11 +0000 Subject: [PATCH 1/7] feat(chat): add CLAUDE.md support and fix scroll behavior Add system prompt configuration to enable CLAUDE.md loading from project, user config, and local directories. This allows Claude Code to use custom instructions defined in CLAUDE.md files. Fix scroll position management during message streaming to prevent conflicting with user's manual scroll actions. Remove automatic scroll state reset in scrollToBottom function and let scroll event handler manage the state naturally. Also remove debug logging for session ID capture. --- server/claude-sdk.js | 12 +++++++++++- src/components/ChatInterface.jsx | 12 ++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/server/claude-sdk.js b/server/claude-sdk.js index ca8c0da..9fea127 100644 --- a/server/claude-sdk.js +++ b/server/claude-sdk.js @@ -79,6 +79,16 @@ function mapCliOptionsToSDK(options = {}) { // Map model (default to sonnet) sdkOptions.model = options.model || 'sonnet'; + // Map system prompt configuration + sdkOptions.systemPrompt = { + type: 'preset', + preset: 'claude_code' // Required to use CLAUDE.md + }; + + // Map setting sources for CLAUDE.md loading + // This loads CLAUDE.md from project, user (~/.config/claude/CLAUDE.md), and local directories + sdkOptions.settingSources = ['project', 'user', 'local']; + // Map resume session if (sessionId) { sdkOptions.resume = sessionId; @@ -374,7 +384,7 @@ async function queryClaudeSDK(command, options = {}, ws) { for await (const message of queryInstance) { // Capture session ID from first message if (message.session_id && !capturedSessionId) { - console.log('📝 Captured session ID:', message.session_id); + capturedSessionId = message.session_id; addSession(capturedSessionId, queryInstance, tempImagePaths, tempDir); diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx index f9c5e96..19d8eeb 100644 --- a/src/components/ChatInterface.jsx +++ b/src/components/ChatInterface.jsx @@ -2603,7 +2603,8 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess const scrollToBottom = useCallback(() => { if (scrollContainerRef.current) { scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight; - setIsUserScrolledUp(false); + // Don't reset isUserScrolledUp here - let the scroll handler manage it + // This prevents fighting with user's scroll position during streaming } }, []); @@ -3522,7 +3523,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess const prevTop = scrollPositionRef.current.top; const newHeight = container.scrollHeight; const heightDiff = newHeight - prevHeight; - + // If content was added above the current view, adjust scroll position if (heightDiff > 0 && prevTop > 0) { container.scrollTop = prevTop + heightDiff; @@ -3536,9 +3537,12 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess if (scrollContainerRef.current && chatMessages.length > 0 && !isLoadingSessionRef.current) { // Only scroll if we're not in the middle of loading a session // This prevents the "double scroll" effect during session switching - // Also reset scroll state + // Reset scroll state when switching sessions setIsUserScrolledUp(false); - setTimeout(() => scrollToBottom(), 200); // Delay to ensure full rendering + setTimeout(() => { + scrollToBottom(); + // After scrolling, the scroll event handler will naturally set isUserScrolledUp based on position + }, 200); // Delay to ensure full rendering } }, [selectedSession?.id, selectedProject?.name]); // Only trigger when session/project changes From 1c95c598eb3ec811a2058b3529f1e127264a6dd8 Mon Sep 17 00:00:00 2001 From: simos Date: Sun, 2 Nov 2025 07:53:22 +0000 Subject: [PATCH 2/7] docs: update installation and CLI documentation Update .env.example with comprehensive CLI command documentation and clearer DATABASE_PATH configuration comments. Enhance README.md with restructured installation guide featuring new cloudcli commands, detailed PM2 background service setup instructions, and improved organization of global installation benefits and restart procedures. Add CLI command reference showing cloudcli start, status, help, and version commands. Expand PM2 section with separate subsections for installation, service startup, and auto-start configuration. --- .env.example | 19 +++- README.md | 74 ++++++++++---- package.json | 3 +- server/cli.js | 225 ++++++++++++++++++++++++++++++++++++++++++ server/database/db.js | 27 ++++- server/index.js | 89 +++++++++++------ 6 files changed, 383 insertions(+), 54 deletions(-) create mode 100755 server/cli.js diff --git a/.env.example b/.env.example index a7e9528..d4b83f1 100755 --- a/.env.example +++ b/.env.example @@ -1,5 +1,15 @@ # Claude Code UI Environment Configuration # Only includes variables that are actually used in the code +# +# TIP: Run 'cloudcli status' to see where this file should be located +# and to view your current configuration. +# +# Available CLI commands: +# claude-code-ui - Start the server (default) +# cloudcli start - Start the server +# cloudcli status - Show configuration and data locations +# cloudcli help - Show help information +# cloudcli version - Show version information # ============================================================================= # SERVER CONFIGURATION @@ -19,10 +29,11 @@ VITE_PORT=5173 # ============================================================================= # Path to the authentication database file -# This should be set to a persistent volume path when running in containers -# Default: server/database/auth.db (relative to project root) -# Example for Docker: /data/auth.db -# DATABASE_PATH=/data/auth.db +# This is where user credentials, API keys, and tokens are stored. +# +# To use a custom location: +# DATABASE_PATH=/path/to/your/custom/auth.db +# # Claude Code context window size (maximum tokens per session) # Note: VITE_ prefix makes it available to frontend VITE_CONTEXT_WINDOW=160000 diff --git a/README.md b/README.md index c94709d..1f81fd0 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,7 @@ npx @siteboon/claude-code-ui The server will start and be accessible at `http://localhost:3001` (or your configured PORT). -**To restart**: Simply run the same `npx` command again after stopping the server (Ctrl+C or Cmd+C). - +**To restart**: Simply run the same `npx` command again after stopping the server ### Global Installation (For Regular Use) For frequent use, install globally once: @@ -85,32 +84,71 @@ Then start with a simple command: claude-code-ui ``` -**Benefits**: -- Faster startup (no download/cache check) -- Simple command to remember -- Same experience every time **To restart**: Stop with Ctrl+C and run `claude-code-ui` again. -### Run as Background Service (Optional) +### CLI Commands -To keep the server running in the background, use PM2: +After global installation, you have access to both `claude-code-ui` and `cloudcli` commands: ```bash -# Install PM2 globally (one-time) -npm install -g pm2 +# Start the server (default command) +claude-code-ui +cloudcli start -# Start the server -pm2 start claude-code-ui --name "claude-ui" +# Show configuration and data locations +cloudcli status -# Manage the service -pm2 list # View status -pm2 restart claude-ui # Restart -pm2 stop claude-ui # Stop -pm2 logs claude-ui # View logs -pm2 startup # Auto-start on system boot +# Show help information +cloudcli help + +# Show version +cloudcli version ``` +**The `cloudcli status` command shows you:** +- Installation directory location +- Database location (where credentials are stored) +- Current configuration (PORT, DATABASE_PATH, etc.) +- Claude projects folder location +- Configuration file location + +``` + +### Run as Background Service (Recommended for Production) + +For production use, run Claude Code UI as a background service using PM2 (Process Manager 2): + +#### Install PM2 + +```bash +npm install -g pm2 +``` + +#### Start as Background Service + +```bash +# Start the server in background +pm2 start claude-code-ui --name "claude-code-ui" + +# Or using the shorter alias +pm2 start cloudcli --name "claude-code-ui" +``` + + +#### Auto-Start on System Boot + +To make Claude Code UI start automatically when your system boots: + +```bash +# Generate startup script for your platform +pm2 startup + +# Save current process list +pm2 save +``` + + ### Local Development Installation 1. **Clone the repository:** diff --git a/package.json b/package.json index 7299490..e778951 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "type": "module", "main": "server/index.js", "bin": { - "claude-code-ui": "server/index.js" + "claude-code-ui": "server/cli.js", + "cloudcli": "server/cli.js" }, "files": [ "server/", diff --git a/server/cli.js b/server/cli.js new file mode 100755 index 0000000..7401a9e --- /dev/null +++ b/server/cli.js @@ -0,0 +1,225 @@ +#!/usr/bin/env node +/** + * Claude Code UI CLI + * + * Provides command-line utilities for managing Claude Code UI + * + * Commands: + * (no args) - Start the server (default) + * start - Start the server + * status - Show configuration and data locations + * help - Show help information + * version - Show version information + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// ANSI color codes for terminal output +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + dim: '\x1b[2m', + + // Foreground colors + cyan: '\x1b[36m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + white: '\x1b[37m', + gray: '\x1b[90m', +}; + +// Helper to colorize text +const c = { + info: (text) => `${colors.cyan}${text}${colors.reset}`, + ok: (text) => `${colors.green}${text}${colors.reset}`, + warn: (text) => `${colors.yellow}${text}${colors.reset}`, + error: (text) => `${colors.yellow}${text}${colors.reset}`, + tip: (text) => `${colors.blue}${text}${colors.reset}`, + bright: (text) => `${colors.bright}${text}${colors.reset}`, + dim: (text) => `${colors.dim}${text}${colors.reset}`, +}; + +// Load package.json for version info +const packageJsonPath = path.join(__dirname, '../package.json'); +const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + +// Load environment variables from .env file if it exists +function loadEnvFile() { + try { + const envPath = path.join(__dirname, '../.env'); + const envFile = fs.readFileSync(envPath, 'utf8'); + envFile.split('\n').forEach(line => { + const trimmedLine = line.trim(); + if (trimmedLine && !trimmedLine.startsWith('#')) { + const [key, ...valueParts] = trimmedLine.split('='); + if (key && valueParts.length > 0 && !process.env[key]) { + process.env[key] = valueParts.join('=').trim(); + } + } + }); + } catch (e) { + // .env file is optional + } +} + +// Get the database path (same logic as db.js) +function getDatabasePath() { + loadEnvFile(); + return process.env.DATABASE_PATH || path.join(__dirname, 'database', 'auth.db'); +} + +// Get the installation directory +function getInstallDir() { + return path.join(__dirname, '..'); +} + +// Show status command +function showStatus() { + console.log(`\n${c.bright('Claude Code UI - Status')}\n`); + console.log(c.dim('═'.repeat(60))); + + // Version info + console.log(`\n${c.info('[INFO]')} Version: ${c.bright(packageJson.version)}`); + + // Installation location + const installDir = getInstallDir(); + console.log(`\n${c.info('[INFO]')} Installation Directory:`); + console.log(` ${c.dim(installDir)}`); + + // Database location + const dbPath = getDatabasePath(); + const dbExists = fs.existsSync(dbPath); + console.log(`\n${c.info('[INFO]')} Database Location:`); + console.log(` ${c.dim(dbPath)}`); + console.log(` Status: ${dbExists ? c.ok('[OK] Exists') : c.warn('[WARN] Not created yet (will be created on first run)')}`); + + if (dbExists) { + const stats = fs.statSync(dbPath); + console.log(` Size: ${c.dim((stats.size / 1024).toFixed(2) + ' KB')}`); + console.log(` Modified: ${c.dim(stats.mtime.toLocaleString())}`); + } + + // Environment variables + console.log(`\n${c.info('[INFO]')} Configuration:`); + console.log(` PORT: ${c.bright(process.env.PORT || '3001')} ${c.dim(process.env.PORT ? '' : '(default)')}`); + console.log(` DATABASE_PATH: ${c.dim(process.env.DATABASE_PATH || '(using default location)')}`); + console.log(` CLAUDE_CLI_PATH: ${c.dim(process.env.CLAUDE_CLI_PATH || 'claude (default)')}`); + console.log(` CONTEXT_WINDOW: ${c.dim(process.env.CONTEXT_WINDOW || '160000 (default)')}`); + + // Claude projects folder + const claudeProjectsPath = path.join(process.env.HOME, '.claude', 'projects'); + const projectsExists = fs.existsSync(claudeProjectsPath); + console.log(`\n${c.info('[INFO]')} Claude Projects Folder:`); + console.log(` ${c.dim(claudeProjectsPath)}`); + console.log(` Status: ${projectsExists ? c.ok('[OK] Exists') : c.warn('[WARN] Not found')}`); + + // Config file location + const envFilePath = path.join(__dirname, '../.env'); + const envExists = fs.existsSync(envFilePath); + console.log(`\n${c.info('[INFO]')} Configuration File:`); + console.log(` ${c.dim(envFilePath)}`); + console.log(` Status: ${envExists ? c.ok('[OK] Exists') : c.warn('[WARN] Not found (using defaults)')}`); + + console.log('\n' + c.dim('═'.repeat(60))); + console.log(`\n${c.tip('[TIP]')} Hints:`); + console.log(` ${c.dim('>')} Set DATABASE_PATH env variable to use a custom database location`); + console.log(` ${c.dim('>')} Create .env file in installation directory for persistent config`); + console.log(` ${c.dim('>')} Run "claude-code-ui" or "cloudcli start" to start the server`); + console.log(` ${c.dim('>')} Access the UI at http://localhost:3001 (or custom PORT)\n`); +} + +// Show help +function showHelp() { + console.log(` +╔═══════════════════════════════════════════════════════════════╗ +║ Claude Code UI - Command Line Tool ║ +╚═══════════════════════════════════════════════════════════════╝ + +Usage: + claude-code-ui [command] + cloudcli [command] + +Commands: + start Start the Claude Code UI server (default) + status Show configuration and data locations + help Show this help information + version Show version information + +Examples: + $ claude-code-ui # Start the server + $ cloudcli status # Show configuration + $ cloudcli help # Show help + +Environment Variables: + PORT Set server port (default: 3001) + DATABASE_PATH Set custom database location + CLAUDE_CLI_PATH Set custom Claude CLI path + CONTEXT_WINDOW Set context window size (default: 160000) + +Configuration: + Create a .env file in the installation directory to set + persistent environment variables. Use 'cloudcli status' to + see the installation directory path. + +Documentation: + ${packageJson.homepage || 'https://github.com/siteboon/claudecodeui'} + +Report Issues: + ${packageJson.bugs?.url || 'https://github.com/siteboon/claudecodeui/issues'} +`); +} + +// Show version +function showVersion() { + console.log(`${packageJson.version}`); +} + +// Start the server +async function startServer() { + // Import and run the server + await import('./index.js'); +} + +// Main CLI handler +async function main() { + const args = process.argv.slice(2); + const command = args[0] || 'start'; + + switch (command) { + case 'start': + await startServer(); + break; + case 'status': + case 'info': + showStatus(); + break; + case 'help': + case '-h': + case '--help': + showHelp(); + break; + case 'version': + case '-v': + case '--version': + showVersion(); + break; + default: + console.error(`\n❌ Unknown command: ${command}`); + console.log(' Run "cloudcli help" for usage information.\n'); + process.exit(1); + } +} + +// Run the CLI +main().catch(error => { + console.error('\n❌ Error:', error.message); + process.exit(1); +}); diff --git a/server/database/db.js b/server/database/db.js index b6959e5..b8c56a2 100644 --- a/server/database/db.js +++ b/server/database/db.js @@ -8,6 +8,20 @@ import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); +// ANSI color codes for terminal output +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + cyan: '\x1b[36m', + dim: '\x1b[2m', +}; + +const c = { + info: (text) => `${colors.cyan}${text}${colors.reset}`, + bright: (text) => `${colors.bright}${text}${colors.reset}`, + dim: (text) => `${colors.dim}${text}${colors.reset}`, +}; + // Use DATABASE_PATH environment variable if set, otherwise use default location const DB_PATH = process.env.DATABASE_PATH || path.join(__dirname, 'auth.db'); const INIT_SQL_PATH = path.join(__dirname, 'init.sql'); @@ -28,7 +42,18 @@ if (process.env.DATABASE_PATH) { // Create database connection const db = new Database(DB_PATH); -console.log(`Connected to SQLite database at: ${DB_PATH}`); + +// Show app installation path prominently +const appInstallPath = path.join(__dirname, '../..'); +console.log(''); +console.log(c.dim('═'.repeat(60))); +console.log(`${c.info('[INFO]')} App Installation: ${c.bright(appInstallPath)}`); +console.log(`${c.info('[INFO]')} Database: ${c.dim(path.relative(appInstallPath, DB_PATH))}`); +if (process.env.DATABASE_PATH) { + console.log(` ${c.dim('(Using custom DATABASE_PATH from environment)')}`); +} +console.log(c.dim('═'.repeat(60))); +console.log(''); // Initialize database with schema const initializeDatabase = async () => { diff --git a/server/index.js b/server/index.js index 9a39a5d..4562f80 100755 --- a/server/index.js +++ b/server/index.js @@ -8,6 +8,26 @@ import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); +// ANSI color codes for terminal output +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + cyan: '\x1b[36m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + dim: '\x1b[2m', +}; + +const c = { + info: (text) => `${colors.cyan}${text}${colors.reset}`, + ok: (text) => `${colors.green}${text}${colors.reset}`, + warn: (text) => `${colors.yellow}${text}${colors.reset}`, + tip: (text) => `${colors.blue}${text}${colors.reset}`, + bright: (text) => `${colors.bright}${text}${colors.reset}`, + dim: (text) => `${colors.dim}${text}${colors.reset}`, +}; + try { const envPath = path.join(__dirname, '../.env'); const envFile = fs.readFileSync(envPath, 'utf8'); @@ -116,7 +136,7 @@ async function setupProjectsWatcher() { }); } catch (error) { - console.error('❌ Error handling project changes:', error); + console.error('[ERROR] Error handling project changes:', error); } }, 300); // 300ms debounce (slightly faster than before) }; @@ -129,13 +149,13 @@ async function setupProjectsWatcher() { .on('addDir', (dirPath) => debouncedUpdate('addDir', dirPath)) .on('unlinkDir', (dirPath) => debouncedUpdate('unlinkDir', dirPath)) .on('error', (error) => { - console.error('❌ Chokidar watcher error:', error); + console.error('[ERROR] Chokidar watcher error:', error); }) .on('ready', () => { }); } catch (error) { - console.error('❌ Failed to setup projects watcher:', error); + console.error('[ERROR] Failed to setup projects watcher:', error); } } @@ -157,13 +177,13 @@ const wss = new WebSocketServer({ // Verify token const user = authenticateWebSocket(token); if (!user) { - console.log('❌ WebSocket authentication failed'); + console.log('[WARN] WebSocket authentication failed'); return false; } // Store user info in the request for later use info.req.user = user; - console.log('✅ WebSocket authenticated for user:', user.username); + console.log('[OK] WebSocket authenticated for user:', user.username); return true; } }); @@ -462,7 +482,7 @@ app.get('/api/projects/:projectName/file', authenticateToken, async (req, res) = const { projectName } = req.params; const { filePath } = req.query; - console.log('📄 File read request:', projectName, filePath); + console.log('[DEBUG] File read request:', projectName, filePath); // Security: ensure the requested path is inside the project root if (!filePath) { @@ -503,7 +523,7 @@ app.get('/api/projects/:projectName/files/content', authenticateToken, async (re const { projectName } = req.params; const { path: filePath } = req.query; - console.log('🖼️ Binary file serve request:', projectName, filePath); + console.log('[DEBUG] Binary file serve request:', projectName, filePath); // Security: ensure the requested path is inside the project root if (!filePath) { @@ -557,7 +577,7 @@ app.put('/api/projects/:projectName/file', authenticateToken, async (req, res) = const { projectName } = req.params; const { filePath, content } = req.body; - console.log('💾 File save request:', projectName, filePath); + console.log('[DEBUG] File save request:', projectName, filePath); // Security: ensure the requested path is inside the project root if (!filePath) { @@ -628,7 +648,7 @@ app.get('/api/projects/:projectName/files', authenticateToken, async (req, res) const hiddenFiles = files.filter(f => f.name.startsWith('.')); res.json(files); } catch (error) { - console.error('❌ File tree error:', error.message); + console.error('[ERROR] File tree error:', error.message); res.status(500).json({ error: error.message }); } }); @@ -636,7 +656,7 @@ app.get('/api/projects/:projectName/files', authenticateToken, async (req, res) // WebSocket connection handler that routes based on URL path wss.on('connection', (ws, request) => { const url = request.url; - console.log('🔗 Client connected to:', url); + console.log('[INFO] Client connected to:', url); // Parse URL to get pathname without query parameters const urlObj = new URL(url, 'http://localhost'); @@ -647,14 +667,14 @@ wss.on('connection', (ws, request) => { } else if (pathname === '/ws') { handleChatConnection(ws); } else { - console.log('❌ Unknown WebSocket path:', pathname); + console.log('[WARN] Unknown WebSocket path:', pathname); ws.close(); } }); // Handle chat WebSocket connections function handleChatConnection(ws) { - console.log('💬 Chat WebSocket connected'); + console.log('[INFO] Chat WebSocket connected'); // Add to connected clients for project updates connectedClients.add(ws); @@ -664,28 +684,28 @@ function handleChatConnection(ws) { const data = JSON.parse(message); if (data.type === 'claude-command') { - console.log('💬 User message:', data.command || '[Continue/Resume]'); + console.log('[DEBUG] User message:', data.command || '[Continue/Resume]'); console.log('📁 Project:', data.options?.projectPath || 'Unknown'); console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New'); // Use Claude Agents SDK await queryClaudeSDK(data.command, data.options, ws); } else if (data.type === 'cursor-command') { - console.log('🖱️ Cursor message:', data.command || '[Continue/Resume]'); + console.log('[DEBUG] Cursor message:', data.command || '[Continue/Resume]'); console.log('📁 Project:', data.options?.cwd || 'Unknown'); console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New'); console.log('🤖 Model:', data.options?.model || 'default'); await spawnCursor(data.command, data.options, ws); } else if (data.type === 'cursor-resume') { // Backward compatibility: treat as cursor-command with resume and no prompt - console.log('🖱️ Cursor resume session (compat):', data.sessionId); + console.log('[DEBUG] Cursor resume session (compat):', data.sessionId); await spawnCursor('', { sessionId: data.sessionId, resume: true, cwd: data.options?.cwd }, ws); } else if (data.type === 'abort-session') { - console.log('🛑 Abort session request:', data.sessionId); + console.log('[DEBUG] Abort session request:', data.sessionId); const provider = data.provider || 'claude'; let success; @@ -703,7 +723,7 @@ function handleChatConnection(ws) { success })); } else if (data.type === 'cursor-abort') { - console.log('🛑 Abort Cursor session:', data.sessionId); + console.log('[DEBUG] Abort Cursor session:', data.sessionId); const success = abortCursorSession(data.sessionId); ws.send(JSON.stringify({ type: 'session-aborted', @@ -742,7 +762,7 @@ function handleChatConnection(ws) { })); } } catch (error) { - console.error('❌ Chat WebSocket error:', error.message); + console.error('[ERROR] Chat WebSocket error:', error.message); ws.send(JSON.stringify({ type: 'error', error: error.message @@ -776,7 +796,7 @@ function handleShellConnection(ws) { const initialCommand = data.initialCommand; const isPlainShell = data.isPlainShell || (!!initialCommand && !hasSession) || provider === 'plain-shell'; - console.log('🚀 Starting shell in:', projectPath); + console.log('[INFO] Starting shell in:', projectPath); console.log('📋 Session info:', hasSession ? `Resume session ${sessionId}` : (isPlainShell ? 'Plain shell mode' : 'New session')); console.log('🤖 Provider:', isPlainShell ? 'plain-shell' : provider); if (initialCommand) { @@ -889,7 +909,7 @@ function handleShellConnection(ws) { let match; while ((match = pattern.exec(data)) !== null) { const url = match[1]; - console.log('🔗 Detected URL for opening:', url); + console.log('[DEBUG] Detected URL for opening:', url); // Send URL opening message to client ws.send(JSON.stringify({ @@ -899,7 +919,7 @@ function handleShellConnection(ws) { // Replace the OPEN_URL pattern with a user-friendly message if (pattern.source.includes('OPEN_URL')) { - outputData = outputData.replace(match[0], `🌐 Opening in browser: ${url}`); + outputData = outputData.replace(match[0], `[INFO] Opening in browser: ${url}`); } } }); @@ -925,7 +945,7 @@ function handleShellConnection(ws) { }); } catch (spawnError) { - console.error('❌ Error spawning process:', spawnError); + console.error('[ERROR] Error spawning process:', spawnError); ws.send(JSON.stringify({ type: 'output', data: `\r\n\x1b[31mError: ${spawnError.message}\x1b[0m\r\n` @@ -951,7 +971,7 @@ function handleShellConnection(ws) { } } } catch (error) { - console.error('❌ Shell WebSocket error:', error.message); + console.error('[ERROR] Shell WebSocket error:', error.message); if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'output', @@ -970,7 +990,7 @@ function handleShellConnection(ws) { }); ws.on('error', (error) => { - console.error('❌ Shell WebSocket error:', error); + console.error('[ERROR] Shell WebSocket error:', error); }); } // Audio transcription endpoint @@ -1411,28 +1431,37 @@ async function startServer() { try { // Initialize authentication database await initializeDatabase(); - console.log('✅ Database initialization skipped (testing)'); // Check if running in production mode (dist folder exists) const distIndexPath = path.join(__dirname, '../dist/index.html'); const isProduction = fs.existsSync(distIndexPath); // Log Claude implementation mode - console.log('🚀 Using Claude Agents SDK for Claude integration'); - console.log(`📦 Running in ${isProduction ? 'PRODUCTION' : 'DEVELOPMENT'} mode`); + console.log(`${c.info('[INFO]')} Using Claude Agents SDK for Claude integration`); + console.log(`${c.info('[INFO]')} Running in ${c.bright(isProduction ? 'PRODUCTION' : 'DEVELOPMENT')} mode`); if (!isProduction) { - console.log(`⚠️ Note: Requests will be proxied to Vite dev server at http://localhost:${process.env.VITE_PORT || 5173}`); + console.log(`${c.warn('[WARN]')} Note: Requests will be proxied to Vite dev server at ${c.dim('http://localhost:' + (process.env.VITE_PORT || 5173))}`); } server.listen(PORT, '0.0.0.0', async () => { - console.log(`Claude Code UI server running on http://0.0.0.0:${PORT}`); + const appInstallPath = path.join(__dirname, '..'); + + console.log(''); + console.log(c.dim('═'.repeat(63))); + console.log(` ${c.bright('Claude Code UI Server - Ready')}`); + console.log(c.dim('═'.repeat(63))); + console.log(''); + console.log(`${c.info('[INFO]')} Server URL: ${c.bright('http://0.0.0.0:' + PORT)}`); + console.log(`${c.info('[INFO]')} Installed at: ${c.dim(appInstallPath)}`); + console.log(`${c.tip('[TIP]')} Run "cloudcli status" for full configuration details`); + console.log(''); // Start watching the projects folder for changes await setupProjectsWatcher(); }); } catch (error) { - console.error('❌ Failed to start server:', error); + console.error('[ERROR] Failed to start server:', error); process.exit(1); } } From 57739a659f0fbfb72e13a8d6af7d403cc458e26c Mon Sep 17 00:00:00 2001 From: simos Date: Sun, 2 Nov 2025 08:01:11 +0000 Subject: [PATCH 3/7] package-lock.json --- package-lock.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index a44bf48..4ca2e5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,8 @@ "ws": "^8.14.2" }, "bin": { - "claude-code-ui": "server/index.js" + "claude-code-ui": "server/cli.js", + "cloudcli": "server/cli.js" }, "devDependencies": { "@types/react": "^18.2.43", From b31f7afdf5acfaa1245b6c8ec0525091df2b5b8e Mon Sep 17 00:00:00 2001 From: simos Date: Sun, 2 Nov 2025 10:27:25 +0100 Subject: [PATCH 4/7] Release 1.11.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4ca2e5d..3c1ecde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@siteboon/claude-code-ui", - "version": "1.10.5", + "version": "1.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@siteboon/claude-code-ui", - "version": "1.10.5", + "version": "1.11.0", "license": "MIT", "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.1.29", diff --git a/package.json b/package.json index 911188b..e065c22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@siteboon/claude-code-ui", - "version": "1.10.5", + "version": "1.11.0", "description": "A web-based UI for Claude Code CLI", "type": "module", "main": "server/index.js", From c7dbab086bcf15964b1cc0e9b4be9a438adb8ded Mon Sep 17 00:00:00 2001 From: simos Date: Sun, 2 Nov 2025 09:36:31 +0000 Subject: [PATCH 5/7] fixing slash commands button --- src/components/ChatInterface.jsx | 92 ++++++++++++++++---------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx index 19d8eeb..e5afb0b 100644 --- a/src/components/ChatInterface.jsx +++ b/src/components/ChatInterface.jsx @@ -4451,6 +4451,51 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess total={tokenBudget?.total || parseInt(import.meta.env.VITE_CONTEXT_WINDOW) || 160000} /> + {/* Slash commands button */} + + {/* Clear input button - positioned to the right of token pie, only shows when there's input */} {input.trim() && ( - {/* Send button */}