@siteboon/claude-code-ui 1.10.5 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +55 -17
  2. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  3. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  4. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  5. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  6. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  7. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  8. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  9. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  10. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  11. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  12. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  13. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  14. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  15. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  16. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  17. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  18. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  19. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  20. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  21. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  22. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  23. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  24. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  25. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  26. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  27. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  28. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  29. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  30. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  31. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  32. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  33. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  34. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  35. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  36. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  37. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  38. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  39. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  40. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  41. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  42. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  44. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  45. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  46. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  47. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  48. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  49. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  50. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  51. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  52. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  53. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  54. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  55. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  56. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  57. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  58. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  59. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  60. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  61. package/dist/assets/index-DXtzL-q9.css +32 -0
  62. package/dist/assets/index-Do2w3FiK.js +1189 -0
  63. package/dist/assets/{vendor-codemirror-B7BYDWj-.js → vendor-codemirror-CnTQH7Pk.js} +1 -1
  64. package/dist/assets/{vendor-react-7V_UDHjJ.js → vendor-react-DVSKlM5e.js} +9 -9
  65. package/dist/index.html +4 -4
  66. package/package.json +6 -2
  67. package/server/cli.js +225 -0
  68. package/server/database/auth.db +0 -0
  69. package/server/database/db.js +35 -1
  70. package/server/index.js +86 -41
  71. package/server/middleware/auth.js +34 -3
  72. package/server/routes/projects.js +378 -0
  73. package/dist/assets/index-B4_v-YUz.css +0 -32
  74. package/dist/assets/index-C4hbM6ph.js +0 -932
package/dist/index.html CHANGED
@@ -25,11 +25,11 @@
25
25
 
26
26
  <!-- Prevent zoom on iOS -->
27
27
  <meta name="format-detection" content="telephone=no" />
28
- <script type="module" crossorigin src="/assets/index-C4hbM6ph.js"></script>
29
- <link rel="modulepreload" crossorigin href="/assets/vendor-react-7V_UDHjJ.js">
30
- <link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-B7BYDWj-.js">
28
+ <script type="module" crossorigin src="/assets/index-Do2w3FiK.js"></script>
29
+ <link rel="modulepreload" crossorigin href="/assets/vendor-react-DVSKlM5e.js">
30
+ <link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-CnTQH7Pk.js">
31
31
  <link rel="modulepreload" crossorigin href="/assets/vendor-xterm-jI4BCHEb.js">
32
- <link rel="stylesheet" crossorigin href="/assets/index-B4_v-YUz.css">
32
+ <link rel="stylesheet" crossorigin href="/assets/index-DXtzL-q9.css">
33
33
  </head>
34
34
  <body>
35
35
  <div id="root"></div>
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@siteboon/claude-code-ui",
3
- "version": "1.10.5",
3
+ "version": "1.12.0",
4
4
  "description": "A web-based UI for Claude Code CLI",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
7
7
  "bin": {
8
- "claude-code-ui": "server/index.js"
8
+ "claude-code-ui": "server/cli.js",
9
+ "cloudcli": "server/cli.js"
9
10
  },
10
11
  "files": [
11
12
  "server/",
@@ -67,6 +68,7 @@
67
68
  "fuse.js": "^7.0.0",
68
69
  "gray-matter": "^4.0.3",
69
70
  "jsonwebtoken": "^9.0.2",
71
+ "katex": "^0.16.25",
70
72
  "lucide-react": "^0.515.0",
71
73
  "mime-types": "^3.0.1",
72
74
  "multer": "^2.0.1",
@@ -77,7 +79,9 @@
77
79
  "react-dropzone": "^14.2.3",
78
80
  "react-markdown": "^10.1.0",
79
81
  "react-router-dom": "^6.8.1",
82
+ "rehype-katex": "^7.0.1",
80
83
  "remark-gfm": "^4.0.0",
84
+ "remark-math": "^6.0.0",
81
85
  "sqlite": "^5.1.1",
82
86
  "sqlite3": "^5.1.7",
83
87
  "tailwind-merge": "^3.3.1",
package/server/cli.js ADDED
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code UI CLI
4
+ *
5
+ * Provides command-line utilities for managing Claude Code UI
6
+ *
7
+ * Commands:
8
+ * (no args) - Start the server (default)
9
+ * start - Start the server
10
+ * status - Show configuration and data locations
11
+ * help - Show help information
12
+ * version - Show version information
13
+ */
14
+
15
+ import fs from 'fs';
16
+ import path from 'path';
17
+ import { fileURLToPath } from 'url';
18
+ import { dirname } from 'path';
19
+
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = dirname(__filename);
22
+
23
+ // ANSI color codes for terminal output
24
+ const colors = {
25
+ reset: '\x1b[0m',
26
+ bright: '\x1b[1m',
27
+ dim: '\x1b[2m',
28
+
29
+ // Foreground colors
30
+ cyan: '\x1b[36m',
31
+ green: '\x1b[32m',
32
+ yellow: '\x1b[33m',
33
+ blue: '\x1b[34m',
34
+ magenta: '\x1b[35m',
35
+ white: '\x1b[37m',
36
+ gray: '\x1b[90m',
37
+ };
38
+
39
+ // Helper to colorize text
40
+ const c = {
41
+ info: (text) => `${colors.cyan}${text}${colors.reset}`,
42
+ ok: (text) => `${colors.green}${text}${colors.reset}`,
43
+ warn: (text) => `${colors.yellow}${text}${colors.reset}`,
44
+ error: (text) => `${colors.yellow}${text}${colors.reset}`,
45
+ tip: (text) => `${colors.blue}${text}${colors.reset}`,
46
+ bright: (text) => `${colors.bright}${text}${colors.reset}`,
47
+ dim: (text) => `${colors.dim}${text}${colors.reset}`,
48
+ };
49
+
50
+ // Load package.json for version info
51
+ const packageJsonPath = path.join(__dirname, '../package.json');
52
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
53
+
54
+ // Load environment variables from .env file if it exists
55
+ function loadEnvFile() {
56
+ try {
57
+ const envPath = path.join(__dirname, '../.env');
58
+ const envFile = fs.readFileSync(envPath, 'utf8');
59
+ envFile.split('\n').forEach(line => {
60
+ const trimmedLine = line.trim();
61
+ if (trimmedLine && !trimmedLine.startsWith('#')) {
62
+ const [key, ...valueParts] = trimmedLine.split('=');
63
+ if (key && valueParts.length > 0 && !process.env[key]) {
64
+ process.env[key] = valueParts.join('=').trim();
65
+ }
66
+ }
67
+ });
68
+ } catch (e) {
69
+ // .env file is optional
70
+ }
71
+ }
72
+
73
+ // Get the database path (same logic as db.js)
74
+ function getDatabasePath() {
75
+ loadEnvFile();
76
+ return process.env.DATABASE_PATH || path.join(__dirname, 'database', 'auth.db');
77
+ }
78
+
79
+ // Get the installation directory
80
+ function getInstallDir() {
81
+ return path.join(__dirname, '..');
82
+ }
83
+
84
+ // Show status command
85
+ function showStatus() {
86
+ console.log(`\n${c.bright('Claude Code UI - Status')}\n`);
87
+ console.log(c.dim('═'.repeat(60)));
88
+
89
+ // Version info
90
+ console.log(`\n${c.info('[INFO]')} Version: ${c.bright(packageJson.version)}`);
91
+
92
+ // Installation location
93
+ const installDir = getInstallDir();
94
+ console.log(`\n${c.info('[INFO]')} Installation Directory:`);
95
+ console.log(` ${c.dim(installDir)}`);
96
+
97
+ // Database location
98
+ const dbPath = getDatabasePath();
99
+ const dbExists = fs.existsSync(dbPath);
100
+ console.log(`\n${c.info('[INFO]')} Database Location:`);
101
+ console.log(` ${c.dim(dbPath)}`);
102
+ console.log(` Status: ${dbExists ? c.ok('[OK] Exists') : c.warn('[WARN] Not created yet (will be created on first run)')}`);
103
+
104
+ if (dbExists) {
105
+ const stats = fs.statSync(dbPath);
106
+ console.log(` Size: ${c.dim((stats.size / 1024).toFixed(2) + ' KB')}`);
107
+ console.log(` Modified: ${c.dim(stats.mtime.toLocaleString())}`);
108
+ }
109
+
110
+ // Environment variables
111
+ console.log(`\n${c.info('[INFO]')} Configuration:`);
112
+ console.log(` PORT: ${c.bright(process.env.PORT || '3001')} ${c.dim(process.env.PORT ? '' : '(default)')}`);
113
+ console.log(` DATABASE_PATH: ${c.dim(process.env.DATABASE_PATH || '(using default location)')}`);
114
+ console.log(` CLAUDE_CLI_PATH: ${c.dim(process.env.CLAUDE_CLI_PATH || 'claude (default)')}`);
115
+ console.log(` CONTEXT_WINDOW: ${c.dim(process.env.CONTEXT_WINDOW || '160000 (default)')}`);
116
+
117
+ // Claude projects folder
118
+ const claudeProjectsPath = path.join(process.env.HOME, '.claude', 'projects');
119
+ const projectsExists = fs.existsSync(claudeProjectsPath);
120
+ console.log(`\n${c.info('[INFO]')} Claude Projects Folder:`);
121
+ console.log(` ${c.dim(claudeProjectsPath)}`);
122
+ console.log(` Status: ${projectsExists ? c.ok('[OK] Exists') : c.warn('[WARN] Not found')}`);
123
+
124
+ // Config file location
125
+ const envFilePath = path.join(__dirname, '../.env');
126
+ const envExists = fs.existsSync(envFilePath);
127
+ console.log(`\n${c.info('[INFO]')} Configuration File:`);
128
+ console.log(` ${c.dim(envFilePath)}`);
129
+ console.log(` Status: ${envExists ? c.ok('[OK] Exists') : c.warn('[WARN] Not found (using defaults)')}`);
130
+
131
+ console.log('\n' + c.dim('═'.repeat(60)));
132
+ console.log(`\n${c.tip('[TIP]')} Hints:`);
133
+ console.log(` ${c.dim('>')} Set DATABASE_PATH env variable to use a custom database location`);
134
+ console.log(` ${c.dim('>')} Create .env file in installation directory for persistent config`);
135
+ console.log(` ${c.dim('>')} Run "claude-code-ui" or "cloudcli start" to start the server`);
136
+ console.log(` ${c.dim('>')} Access the UI at http://localhost:3001 (or custom PORT)\n`);
137
+ }
138
+
139
+ // Show help
140
+ function showHelp() {
141
+ console.log(`
142
+ ╔═══════════════════════════════════════════════════════════════╗
143
+ ║ Claude Code UI - Command Line Tool ║
144
+ ╚═══════════════════════════════════════════════════════════════╝
145
+
146
+ Usage:
147
+ claude-code-ui [command]
148
+ cloudcli [command]
149
+
150
+ Commands:
151
+ start Start the Claude Code UI server (default)
152
+ status Show configuration and data locations
153
+ help Show this help information
154
+ version Show version information
155
+
156
+ Examples:
157
+ $ claude-code-ui # Start the server
158
+ $ cloudcli status # Show configuration
159
+ $ cloudcli help # Show help
160
+
161
+ Environment Variables:
162
+ PORT Set server port (default: 3001)
163
+ DATABASE_PATH Set custom database location
164
+ CLAUDE_CLI_PATH Set custom Claude CLI path
165
+ CONTEXT_WINDOW Set context window size (default: 160000)
166
+
167
+ Configuration:
168
+ Create a .env file in the installation directory to set
169
+ persistent environment variables. Use 'cloudcli status' to
170
+ see the installation directory path.
171
+
172
+ Documentation:
173
+ ${packageJson.homepage || 'https://github.com/siteboon/claudecodeui'}
174
+
175
+ Report Issues:
176
+ ${packageJson.bugs?.url || 'https://github.com/siteboon/claudecodeui/issues'}
177
+ `);
178
+ }
179
+
180
+ // Show version
181
+ function showVersion() {
182
+ console.log(`${packageJson.version}`);
183
+ }
184
+
185
+ // Start the server
186
+ async function startServer() {
187
+ // Import and run the server
188
+ await import('./index.js');
189
+ }
190
+
191
+ // Main CLI handler
192
+ async function main() {
193
+ const args = process.argv.slice(2);
194
+ const command = args[0] || 'start';
195
+
196
+ switch (command) {
197
+ case 'start':
198
+ await startServer();
199
+ break;
200
+ case 'status':
201
+ case 'info':
202
+ showStatus();
203
+ break;
204
+ case 'help':
205
+ case '-h':
206
+ case '--help':
207
+ showHelp();
208
+ break;
209
+ case 'version':
210
+ case '-v':
211
+ case '--version':
212
+ showVersion();
213
+ break;
214
+ default:
215
+ console.error(`\n❌ Unknown command: ${command}`);
216
+ console.log(' Run "cloudcli help" for usage information.\n');
217
+ process.exit(1);
218
+ }
219
+ }
220
+
221
+ // Run the CLI
222
+ main().catch(error => {
223
+ console.error('\n❌ Error:', error.message);
224
+ process.exit(1);
225
+ });
Binary file
@@ -8,6 +8,20 @@ import { dirname } from 'path';
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = dirname(__filename);
10
10
 
11
+ // ANSI color codes for terminal output
12
+ const colors = {
13
+ reset: '\x1b[0m',
14
+ bright: '\x1b[1m',
15
+ cyan: '\x1b[36m',
16
+ dim: '\x1b[2m',
17
+ };
18
+
19
+ const c = {
20
+ info: (text) => `${colors.cyan}${text}${colors.reset}`,
21
+ bright: (text) => `${colors.bright}${text}${colors.reset}`,
22
+ dim: (text) => `${colors.dim}${text}${colors.reset}`,
23
+ };
24
+
11
25
  // Use DATABASE_PATH environment variable if set, otherwise use default location
12
26
  const DB_PATH = process.env.DATABASE_PATH || path.join(__dirname, 'auth.db');
13
27
  const INIT_SQL_PATH = path.join(__dirname, 'init.sql');
@@ -28,7 +42,18 @@ if (process.env.DATABASE_PATH) {
28
42
 
29
43
  // Create database connection
30
44
  const db = new Database(DB_PATH);
31
- console.log(`Connected to SQLite database at: ${DB_PATH}`);
45
+
46
+ // Show app installation path prominently
47
+ const appInstallPath = path.join(__dirname, '../..');
48
+ console.log('');
49
+ console.log(c.dim('═'.repeat(60)));
50
+ console.log(`${c.info('[INFO]')} App Installation: ${c.bright(appInstallPath)}`);
51
+ console.log(`${c.info('[INFO]')} Database: ${c.dim(path.relative(appInstallPath, DB_PATH))}`);
52
+ if (process.env.DATABASE_PATH) {
53
+ console.log(` ${c.dim('(Using custom DATABASE_PATH from environment)')}`);
54
+ }
55
+ console.log(c.dim('═'.repeat(60)));
56
+ console.log('');
32
57
 
33
58
  // Initialize database with schema
34
59
  const initializeDatabase = async () => {
@@ -92,6 +117,15 @@ const userDb = {
92
117
  } catch (err) {
93
118
  throw err;
94
119
  }
120
+ },
121
+
122
+ getFirstUser: () => {
123
+ try {
124
+ const row = db.prepare('SELECT id, username, created_at, last_login FROM users WHERE is_active = 1 LIMIT 1').get();
125
+ return row;
126
+ } catch (err) {
127
+ throw err;
128
+ }
95
129
  }
96
130
  };
97
131
 
package/server/index.js CHANGED
@@ -8,6 +8,26 @@ import { dirname } from 'path';
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = dirname(__filename);
10
10
 
11
+ // ANSI color codes for terminal output
12
+ const colors = {
13
+ reset: '\x1b[0m',
14
+ bright: '\x1b[1m',
15
+ cyan: '\x1b[36m',
16
+ green: '\x1b[32m',
17
+ yellow: '\x1b[33m',
18
+ blue: '\x1b[34m',
19
+ dim: '\x1b[2m',
20
+ };
21
+
22
+ const c = {
23
+ info: (text) => `${colors.cyan}${text}${colors.reset}`,
24
+ ok: (text) => `${colors.green}${text}${colors.reset}`,
25
+ warn: (text) => `${colors.yellow}${text}${colors.reset}`,
26
+ tip: (text) => `${colors.blue}${text}${colors.reset}`,
27
+ bright: (text) => `${colors.bright}${text}${colors.reset}`,
28
+ dim: (text) => `${colors.dim}${text}${colors.reset}`,
29
+ };
30
+
11
31
  try {
12
32
  const envPath = path.join(__dirname, '../.env');
13
33
  const envFile = fs.readFileSync(envPath, 'utf8');
@@ -49,6 +69,7 @@ import mcpUtilsRoutes from './routes/mcp-utils.js';
49
69
  import commandsRoutes from './routes/commands.js';
50
70
  import settingsRoutes from './routes/settings.js';
51
71
  import agentRoutes from './routes/agent.js';
72
+ import projectsRoutes from './routes/projects.js';
52
73
  import { initializeDatabase } from './database/db.js';
53
74
  import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
54
75
 
@@ -116,7 +137,7 @@ async function setupProjectsWatcher() {
116
137
  });
117
138
 
118
139
  } catch (error) {
119
- console.error(' Error handling project changes:', error);
140
+ console.error('[ERROR] Error handling project changes:', error);
120
141
  }
121
142
  }, 300); // 300ms debounce (slightly faster than before)
122
143
  };
@@ -129,13 +150,13 @@ async function setupProjectsWatcher() {
129
150
  .on('addDir', (dirPath) => debouncedUpdate('addDir', dirPath))
130
151
  .on('unlinkDir', (dirPath) => debouncedUpdate('unlinkDir', dirPath))
131
152
  .on('error', (error) => {
132
- console.error(' Chokidar watcher error:', error);
153
+ console.error('[ERROR] Chokidar watcher error:', error);
133
154
  })
134
155
  .on('ready', () => {
135
156
  });
136
157
 
137
158
  } catch (error) {
138
- console.error(' Failed to setup projects watcher:', error);
159
+ console.error('[ERROR] Failed to setup projects watcher:', error);
139
160
  }
140
161
  }
141
162
 
@@ -149,6 +170,19 @@ const wss = new WebSocketServer({
149
170
  verifyClient: (info) => {
150
171
  console.log('WebSocket connection attempt to:', info.req.url);
151
172
 
173
+ // Platform mode: always allow connection
174
+ if (process.env.VITE_IS_PLATFORM === 'true') {
175
+ const user = authenticateWebSocket(null); // Will return first user
176
+ if (!user) {
177
+ console.log('[WARN] Platform mode: No user found in database');
178
+ return false;
179
+ }
180
+ info.req.user = user;
181
+ console.log('[OK] Platform mode WebSocket authenticated for user:', user.username);
182
+ return true;
183
+ }
184
+
185
+ // Normal mode: verify token
152
186
  // Extract token from query parameters or headers
153
187
  const url = new URL(info.req.url, 'http://localhost');
154
188
  const token = url.searchParams.get('token') ||
@@ -157,13 +191,13 @@ const wss = new WebSocketServer({
157
191
  // Verify token
158
192
  const user = authenticateWebSocket(token);
159
193
  if (!user) {
160
- console.log(' WebSocket authentication failed');
194
+ console.log('[WARN] WebSocket authentication failed');
161
195
  return false;
162
196
  }
163
197
 
164
198
  // Store user info in the request for later use
165
199
  info.req.user = user;
166
- console.log(' WebSocket authenticated for user:', user.username);
200
+ console.log('[OK] WebSocket authenticated for user:', user.username);
167
201
  return true;
168
202
  }
169
203
  });
@@ -175,12 +209,23 @@ app.use(cors());
175
209
  app.use(express.json({ limit: '50mb' }));
176
210
  app.use(express.urlencoded({ limit: '50mb', extended: true }));
177
211
 
212
+ // Public health check endpoint (no authentication required)
213
+ app.get('/health', (req, res) => {
214
+ res.json({
215
+ status: 'ok',
216
+ timestamp: new Date().toISOString()
217
+ });
218
+ });
219
+
178
220
  // Optional API key validation (if configured)
179
221
  app.use('/api', validateApiKey);
180
222
 
181
223
  // Authentication routes (public)
182
224
  app.use('/api/auth', authRoutes);
183
225
 
226
+ // Projects API Routes (protected)
227
+ app.use('/api/projects', authenticateToken, projectsRoutes);
228
+
184
229
  // Git API Routes (protected)
185
230
  app.use('/api/git', authenticateToken, gitRoutes);
186
231
 
@@ -225,17 +270,8 @@ app.use(express.static(path.join(__dirname, '../dist'), {
225
270
  }));
226
271
 
227
272
  // API Routes (protected)
228
- app.get('/api/config', authenticateToken, (req, res) => {
229
- const host = req.headers.host || `${req.hostname}:${PORT}`;
230
- const protocol = req.protocol === 'https' || req.get('x-forwarded-proto') === 'https' ? 'wss' : 'ws';
231
-
232
- console.log('Config API called - Returning host:', host, 'Protocol:', protocol);
233
-
234
- res.json({
235
- serverPort: PORT,
236
- wsUrl: `${protocol}://${host}`
237
- });
238
- });
273
+ // /api/config endpoint removed - no longer needed
274
+ // Frontend now uses window.location for WebSocket URLs
239
275
 
240
276
  // System update endpoint
241
277
  app.post('/api/system/update', authenticateToken, async (req, res) => {
@@ -462,7 +498,7 @@ app.get('/api/projects/:projectName/file', authenticateToken, async (req, res) =
462
498
  const { projectName } = req.params;
463
499
  const { filePath } = req.query;
464
500
 
465
- console.log('📄 File read request:', projectName, filePath);
501
+ console.log('[DEBUG] File read request:', projectName, filePath);
466
502
 
467
503
  // Security: ensure the requested path is inside the project root
468
504
  if (!filePath) {
@@ -503,7 +539,7 @@ app.get('/api/projects/:projectName/files/content', authenticateToken, async (re
503
539
  const { projectName } = req.params;
504
540
  const { path: filePath } = req.query;
505
541
 
506
- console.log('🖼️ Binary file serve request:', projectName, filePath);
542
+ console.log('[DEBUG] Binary file serve request:', projectName, filePath);
507
543
 
508
544
  // Security: ensure the requested path is inside the project root
509
545
  if (!filePath) {
@@ -557,7 +593,7 @@ app.put('/api/projects/:projectName/file', authenticateToken, async (req, res) =
557
593
  const { projectName } = req.params;
558
594
  const { filePath, content } = req.body;
559
595
 
560
- console.log('💾 File save request:', projectName, filePath);
596
+ console.log('[DEBUG] File save request:', projectName, filePath);
561
597
 
562
598
  // Security: ensure the requested path is inside the project root
563
599
  if (!filePath) {
@@ -628,7 +664,7 @@ app.get('/api/projects/:projectName/files', authenticateToken, async (req, res)
628
664
  const hiddenFiles = files.filter(f => f.name.startsWith('.'));
629
665
  res.json(files);
630
666
  } catch (error) {
631
- console.error(' File tree error:', error.message);
667
+ console.error('[ERROR] File tree error:', error.message);
632
668
  res.status(500).json({ error: error.message });
633
669
  }
634
670
  });
@@ -636,7 +672,7 @@ app.get('/api/projects/:projectName/files', authenticateToken, async (req, res)
636
672
  // WebSocket connection handler that routes based on URL path
637
673
  wss.on('connection', (ws, request) => {
638
674
  const url = request.url;
639
- console.log('🔗 Client connected to:', url);
675
+ console.log('[INFO] Client connected to:', url);
640
676
 
641
677
  // Parse URL to get pathname without query parameters
642
678
  const urlObj = new URL(url, 'http://localhost');
@@ -647,14 +683,14 @@ wss.on('connection', (ws, request) => {
647
683
  } else if (pathname === '/ws') {
648
684
  handleChatConnection(ws);
649
685
  } else {
650
- console.log(' Unknown WebSocket path:', pathname);
686
+ console.log('[WARN] Unknown WebSocket path:', pathname);
651
687
  ws.close();
652
688
  }
653
689
  });
654
690
 
655
691
  // Handle chat WebSocket connections
656
692
  function handleChatConnection(ws) {
657
- console.log('💬 Chat WebSocket connected');
693
+ console.log('[INFO] Chat WebSocket connected');
658
694
 
659
695
  // Add to connected clients for project updates
660
696
  connectedClients.add(ws);
@@ -664,28 +700,28 @@ function handleChatConnection(ws) {
664
700
  const data = JSON.parse(message);
665
701
 
666
702
  if (data.type === 'claude-command') {
667
- console.log('💬 User message:', data.command || '[Continue/Resume]');
703
+ console.log('[DEBUG] User message:', data.command || '[Continue/Resume]');
668
704
  console.log('📁 Project:', data.options?.projectPath || 'Unknown');
669
705
  console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New');
670
706
 
671
707
  // Use Claude Agents SDK
672
708
  await queryClaudeSDK(data.command, data.options, ws);
673
709
  } else if (data.type === 'cursor-command') {
674
- console.log('🖱️ Cursor message:', data.command || '[Continue/Resume]');
710
+ console.log('[DEBUG] Cursor message:', data.command || '[Continue/Resume]');
675
711
  console.log('📁 Project:', data.options?.cwd || 'Unknown');
676
712
  console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New');
677
713
  console.log('🤖 Model:', data.options?.model || 'default');
678
714
  await spawnCursor(data.command, data.options, ws);
679
715
  } else if (data.type === 'cursor-resume') {
680
716
  // Backward compatibility: treat as cursor-command with resume and no prompt
681
- console.log('🖱️ Cursor resume session (compat):', data.sessionId);
717
+ console.log('[DEBUG] Cursor resume session (compat):', data.sessionId);
682
718
  await spawnCursor('', {
683
719
  sessionId: data.sessionId,
684
720
  resume: true,
685
721
  cwd: data.options?.cwd
686
722
  }, ws);
687
723
  } else if (data.type === 'abort-session') {
688
- console.log('🛑 Abort session request:', data.sessionId);
724
+ console.log('[DEBUG] Abort session request:', data.sessionId);
689
725
  const provider = data.provider || 'claude';
690
726
  let success;
691
727
 
@@ -703,7 +739,7 @@ function handleChatConnection(ws) {
703
739
  success
704
740
  }));
705
741
  } else if (data.type === 'cursor-abort') {
706
- console.log('🛑 Abort Cursor session:', data.sessionId);
742
+ console.log('[DEBUG] Abort Cursor session:', data.sessionId);
707
743
  const success = abortCursorSession(data.sessionId);
708
744
  ws.send(JSON.stringify({
709
745
  type: 'session-aborted',
@@ -742,7 +778,7 @@ function handleChatConnection(ws) {
742
778
  }));
743
779
  }
744
780
  } catch (error) {
745
- console.error(' Chat WebSocket error:', error.message);
781
+ console.error('[ERROR] Chat WebSocket error:', error.message);
746
782
  ws.send(JSON.stringify({
747
783
  type: 'error',
748
784
  error: error.message
@@ -776,7 +812,7 @@ function handleShellConnection(ws) {
776
812
  const initialCommand = data.initialCommand;
777
813
  const isPlainShell = data.isPlainShell || (!!initialCommand && !hasSession) || provider === 'plain-shell';
778
814
 
779
- console.log('🚀 Starting shell in:', projectPath);
815
+ console.log('[INFO] Starting shell in:', projectPath);
780
816
  console.log('📋 Session info:', hasSession ? `Resume session ${sessionId}` : (isPlainShell ? 'Plain shell mode' : 'New session'));
781
817
  console.log('🤖 Provider:', isPlainShell ? 'plain-shell' : provider);
782
818
  if (initialCommand) {
@@ -889,7 +925,7 @@ function handleShellConnection(ws) {
889
925
  let match;
890
926
  while ((match = pattern.exec(data)) !== null) {
891
927
  const url = match[1];
892
- console.log('🔗 Detected URL for opening:', url);
928
+ console.log('[DEBUG] Detected URL for opening:', url);
893
929
 
894
930
  // Send URL opening message to client
895
931
  ws.send(JSON.stringify({
@@ -899,7 +935,7 @@ function handleShellConnection(ws) {
899
935
 
900
936
  // Replace the OPEN_URL pattern with a user-friendly message
901
937
  if (pattern.source.includes('OPEN_URL')) {
902
- outputData = outputData.replace(match[0], `🌐 Opening in browser: ${url}`);
938
+ outputData = outputData.replace(match[0], `[INFO] Opening in browser: ${url}`);
903
939
  }
904
940
  }
905
941
  });
@@ -925,7 +961,7 @@ function handleShellConnection(ws) {
925
961
  });
926
962
 
927
963
  } catch (spawnError) {
928
- console.error(' Error spawning process:', spawnError);
964
+ console.error('[ERROR] Error spawning process:', spawnError);
929
965
  ws.send(JSON.stringify({
930
966
  type: 'output',
931
967
  data: `\r\n\x1b[31mError: ${spawnError.message}\x1b[0m\r\n`
@@ -951,7 +987,7 @@ function handleShellConnection(ws) {
951
987
  }
952
988
  }
953
989
  } catch (error) {
954
- console.error(' Shell WebSocket error:', error.message);
990
+ console.error('[ERROR] Shell WebSocket error:', error.message);
955
991
  if (ws.readyState === WebSocket.OPEN) {
956
992
  ws.send(JSON.stringify({
957
993
  type: 'output',
@@ -970,7 +1006,7 @@ function handleShellConnection(ws) {
970
1006
  });
971
1007
 
972
1008
  ws.on('error', (error) => {
973
- console.error(' Shell WebSocket error:', error);
1009
+ console.error('[ERROR] Shell WebSocket error:', error);
974
1010
  });
975
1011
  }
976
1012
  // Audio transcription endpoint
@@ -1411,28 +1447,37 @@ async function startServer() {
1411
1447
  try {
1412
1448
  // Initialize authentication database
1413
1449
  await initializeDatabase();
1414
- console.log('✅ Database initialization skipped (testing)');
1415
1450
 
1416
1451
  // Check if running in production mode (dist folder exists)
1417
1452
  const distIndexPath = path.join(__dirname, '../dist/index.html');
1418
1453
  const isProduction = fs.existsSync(distIndexPath);
1419
1454
 
1420
1455
  // Log Claude implementation mode
1421
- console.log('🚀 Using Claude Agents SDK for Claude integration');
1422
- console.log(`📦 Running in ${isProduction ? 'PRODUCTION' : 'DEVELOPMENT'} mode`);
1456
+ console.log(`${c.info('[INFO]')} Using Claude Agents SDK for Claude integration`);
1457
+ console.log(`${c.info('[INFO]')} Running in ${c.bright(isProduction ? 'PRODUCTION' : 'DEVELOPMENT')} mode`);
1423
1458
 
1424
1459
  if (!isProduction) {
1425
- console.log(`⚠️ Note: Requests will be proxied to Vite dev server at http://localhost:${process.env.VITE_PORT || 5173}`);
1460
+ console.log(`${c.warn('[WARN]')} Note: Requests will be proxied to Vite dev server at ${c.dim('http://localhost:' + (process.env.VITE_PORT || 5173))}`);
1426
1461
  }
1427
1462
 
1428
1463
  server.listen(PORT, '0.0.0.0', async () => {
1429
- console.log(`Claude Code UI server running on http://0.0.0.0:${PORT}`);
1464
+ const appInstallPath = path.join(__dirname, '..');
1465
+
1466
+ console.log('');
1467
+ console.log(c.dim('═'.repeat(63)));
1468
+ console.log(` ${c.bright('Claude Code UI Server - Ready')}`);
1469
+ console.log(c.dim('═'.repeat(63)));
1470
+ console.log('');
1471
+ console.log(`${c.info('[INFO]')} Server URL: ${c.bright('http://0.0.0.0:' + PORT)}`);
1472
+ console.log(`${c.info('[INFO]')} Installed at: ${c.dim(appInstallPath)}`);
1473
+ console.log(`${c.tip('[TIP]')} Run "cloudcli status" for full configuration details`);
1474
+ console.log('');
1430
1475
 
1431
1476
  // Start watching the projects folder for changes
1432
1477
  await setupProjectsWatcher();
1433
1478
  });
1434
1479
  } catch (error) {
1435
- console.error(' Failed to start server:', error);
1480
+ console.error('[ERROR] Failed to start server:', error);
1436
1481
  process.exit(1);
1437
1482
  }
1438
1483
  }