@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.
- package/README.md +55 -17
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/index-DXtzL-q9.css +32 -0
- package/dist/assets/index-Do2w3FiK.js +1189 -0
- package/dist/assets/{vendor-codemirror-B7BYDWj-.js → vendor-codemirror-CnTQH7Pk.js} +1 -1
- package/dist/assets/{vendor-react-7V_UDHjJ.js → vendor-react-DVSKlM5e.js} +9 -9
- package/dist/index.html +4 -4
- package/package.json +6 -2
- package/server/cli.js +225 -0
- package/server/database/auth.db +0 -0
- package/server/database/db.js +35 -1
- package/server/index.js +86 -41
- package/server/middleware/auth.js +34 -3
- package/server/routes/projects.js +378 -0
- package/dist/assets/index-B4_v-YUz.css +0 -32
- 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-
|
|
29
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-react-
|
|
30
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-
|
|
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-
|
|
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.
|
|
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/
|
|
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
|
+
});
|
package/server/database/auth.db
CHANGED
|
Binary file
|
package/server/database/db.js
CHANGED
|
@@ -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
|
-
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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
|
-
|
|
229
|
-
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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],
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
1422
|
-
console.log(
|
|
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(
|
|
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
|
-
|
|
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('
|
|
1480
|
+
console.error('[ERROR] Failed to start server:', error);
|
|
1436
1481
|
process.exit(1);
|
|
1437
1482
|
}
|
|
1438
1483
|
}
|