@siteboon/claude-code-ui 1.9.0 → 1.10.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 +44 -2
- package/dist/api-docs.html +857 -0
- package/dist/assets/index-BHQThXog.css +32 -0
- package/dist/assets/index-DPk7rbtA.js +902 -0
- package/dist/assets/{vendor-codemirror-D2k1L1JZ.js → vendor-codemirror-B7BYDWj-.js} +17 -17
- package/dist/index.html +3 -3
- package/package.json +8 -4
- package/server/claude-sdk.js +5 -0
- package/server/cursor-cli.js +5 -0
- package/server/database/db.js +180 -3
- package/server/database/init.sql +34 -1
- package/server/index.js +36 -15
- package/server/projects.js +8 -4
- package/server/routes/agent.js +1161 -0
- package/server/routes/git.js +264 -54
- package/server/routes/settings.js +178 -0
- package/dist/assets/index-Bmo7Hu70.css +0 -32
- package/dist/assets/index-D3NZxyU6.js +0 -793
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-
|
|
28
|
+
<script type="module" crossorigin src="/assets/index-DPk7rbtA.js"></script>
|
|
29
29
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-7V_UDHjJ.js">
|
|
30
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-
|
|
30
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-B7BYDWj-.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-BHQThXog.css">
|
|
33
33
|
</head>
|
|
34
34
|
<body>
|
|
35
35
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siteboon/claude-code-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "A web-based UI for Claude Code CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/index.js",
|
|
@@ -46,11 +46,16 @@
|
|
|
46
46
|
"@codemirror/lang-json": "^6.0.1",
|
|
47
47
|
"@codemirror/lang-markdown": "^6.3.3",
|
|
48
48
|
"@codemirror/lang-python": "^6.2.1",
|
|
49
|
+
"@codemirror/merge": "^6.11.1",
|
|
49
50
|
"@codemirror/theme-one-dark": "^6.1.2",
|
|
51
|
+
"@octokit/rest": "^22.0.0",
|
|
52
|
+
"@replit/codemirror-minimap": "^0.5.2",
|
|
50
53
|
"@tailwindcss/typography": "^0.5.16",
|
|
51
54
|
"@uiw/react-codemirror": "^4.23.13",
|
|
52
55
|
"@xterm/addon-clipboard": "^0.1.0",
|
|
56
|
+
"@xterm/addon-fit": "^0.10.0",
|
|
53
57
|
"@xterm/addon-webgl": "^0.18.0",
|
|
58
|
+
"@xterm/xterm": "^5.5.0",
|
|
54
59
|
"bcrypt": "^6.0.0",
|
|
55
60
|
"better-sqlite3": "^12.2.0",
|
|
56
61
|
"chokidar": "^4.0.3",
|
|
@@ -72,12 +77,11 @@
|
|
|
72
77
|
"react-dropzone": "^14.2.3",
|
|
73
78
|
"react-markdown": "^10.1.0",
|
|
74
79
|
"react-router-dom": "^6.8.1",
|
|
80
|
+
"remark-gfm": "^4.0.0",
|
|
75
81
|
"sqlite": "^5.1.1",
|
|
76
82
|
"sqlite3": "^5.1.7",
|
|
77
83
|
"tailwind-merge": "^3.3.1",
|
|
78
|
-
"ws": "^8.14.2"
|
|
79
|
-
"@xterm/xterm": "^5.5.0",
|
|
80
|
-
"@xterm/addon-fit": "^0.10.0"
|
|
84
|
+
"ws": "^8.14.2"
|
|
81
85
|
},
|
|
82
86
|
"devDependencies": {
|
|
83
87
|
"@types/react": "^18.2.43",
|
package/server/claude-sdk.js
CHANGED
|
@@ -378,6 +378,11 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|
|
378
378
|
capturedSessionId = message.session_id;
|
|
379
379
|
addSession(capturedSessionId, queryInstance, tempImagePaths, tempDir);
|
|
380
380
|
|
|
381
|
+
// Set session ID on writer
|
|
382
|
+
if (ws.setSessionId && typeof ws.setSessionId === 'function') {
|
|
383
|
+
ws.setSessionId(capturedSessionId);
|
|
384
|
+
}
|
|
385
|
+
|
|
381
386
|
// Send session-created event only once for new sessions
|
|
382
387
|
if (!sessionId && !sessionCreatedSent) {
|
|
383
388
|
sessionCreatedSent = true;
|
package/server/cursor-cli.js
CHANGED
|
@@ -94,6 +94,11 @@ async function spawnCursor(command, options = {}, ws) {
|
|
|
94
94
|
activeCursorProcesses.set(capturedSessionId, cursorProcess);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// Set session ID on writer (for API endpoint compatibility)
|
|
98
|
+
if (ws.setSessionId && typeof ws.setSessionId === 'function') {
|
|
99
|
+
ws.setSessionId(capturedSessionId);
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
// Send session-created event only once for new sessions
|
|
98
103
|
if (!sessionId && !sessionCreatedSent) {
|
|
99
104
|
sessionCreatedSent = true;
|
package/server/database/db.js
CHANGED
|
@@ -1,18 +1,34 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs';
|
|
4
|
+
import crypto from 'crypto';
|
|
4
5
|
import { fileURLToPath } from 'url';
|
|
5
6
|
import { dirname } from 'path';
|
|
6
7
|
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
9
|
const __dirname = dirname(__filename);
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
// Use DATABASE_PATH environment variable if set, otherwise use default location
|
|
12
|
+
const DB_PATH = process.env.DATABASE_PATH || path.join(__dirname, 'auth.db');
|
|
11
13
|
const INIT_SQL_PATH = path.join(__dirname, 'init.sql');
|
|
12
14
|
|
|
15
|
+
// Ensure database directory exists if custom path is provided
|
|
16
|
+
if (process.env.DATABASE_PATH) {
|
|
17
|
+
const dbDir = path.dirname(DB_PATH);
|
|
18
|
+
try {
|
|
19
|
+
if (!fs.existsSync(dbDir)) {
|
|
20
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
21
|
+
console.log(`Created database directory: ${dbDir}`);
|
|
22
|
+
}
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error(`Failed to create database directory ${dbDir}:`, error.message);
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
13
29
|
// Create database connection
|
|
14
30
|
const db = new Database(DB_PATH);
|
|
15
|
-
console.log(
|
|
31
|
+
console.log(`Connected to SQLite database at: ${DB_PATH}`);
|
|
16
32
|
|
|
17
33
|
// Initialize database with schema
|
|
18
34
|
const initializeDatabase = async () => {
|
|
@@ -79,8 +95,169 @@ const userDb = {
|
|
|
79
95
|
}
|
|
80
96
|
};
|
|
81
97
|
|
|
98
|
+
// API Keys database operations
|
|
99
|
+
const apiKeysDb = {
|
|
100
|
+
// Generate a new API key
|
|
101
|
+
generateApiKey: () => {
|
|
102
|
+
return 'ck_' + crypto.randomBytes(32).toString('hex');
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// Create a new API key
|
|
106
|
+
createApiKey: (userId, keyName) => {
|
|
107
|
+
try {
|
|
108
|
+
const apiKey = apiKeysDb.generateApiKey();
|
|
109
|
+
const stmt = db.prepare('INSERT INTO api_keys (user_id, key_name, api_key) VALUES (?, ?, ?)');
|
|
110
|
+
const result = stmt.run(userId, keyName, apiKey);
|
|
111
|
+
return { id: result.lastInsertRowid, keyName, apiKey };
|
|
112
|
+
} catch (err) {
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// Get all API keys for a user
|
|
118
|
+
getApiKeys: (userId) => {
|
|
119
|
+
try {
|
|
120
|
+
const rows = db.prepare('SELECT id, key_name, api_key, created_at, last_used, is_active FROM api_keys WHERE user_id = ? ORDER BY created_at DESC').all(userId);
|
|
121
|
+
return rows;
|
|
122
|
+
} catch (err) {
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
// Validate API key and get user
|
|
128
|
+
validateApiKey: (apiKey) => {
|
|
129
|
+
try {
|
|
130
|
+
const row = db.prepare(`
|
|
131
|
+
SELECT u.id, u.username, ak.id as api_key_id
|
|
132
|
+
FROM api_keys ak
|
|
133
|
+
JOIN users u ON ak.user_id = u.id
|
|
134
|
+
WHERE ak.api_key = ? AND ak.is_active = 1 AND u.is_active = 1
|
|
135
|
+
`).get(apiKey);
|
|
136
|
+
|
|
137
|
+
if (row) {
|
|
138
|
+
// Update last_used timestamp
|
|
139
|
+
db.prepare('UPDATE api_keys SET last_used = CURRENT_TIMESTAMP WHERE id = ?').run(row.api_key_id);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return row;
|
|
143
|
+
} catch (err) {
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// Delete an API key
|
|
149
|
+
deleteApiKey: (userId, apiKeyId) => {
|
|
150
|
+
try {
|
|
151
|
+
const stmt = db.prepare('DELETE FROM api_keys WHERE id = ? AND user_id = ?');
|
|
152
|
+
const result = stmt.run(apiKeyId, userId);
|
|
153
|
+
return result.changes > 0;
|
|
154
|
+
} catch (err) {
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// Toggle API key active status
|
|
160
|
+
toggleApiKey: (userId, apiKeyId, isActive) => {
|
|
161
|
+
try {
|
|
162
|
+
const stmt = db.prepare('UPDATE api_keys SET is_active = ? WHERE id = ? AND user_id = ?');
|
|
163
|
+
const result = stmt.run(isActive ? 1 : 0, apiKeyId, userId);
|
|
164
|
+
return result.changes > 0;
|
|
165
|
+
} catch (err) {
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// User credentials database operations (for GitHub tokens, GitLab tokens, etc.)
|
|
172
|
+
const credentialsDb = {
|
|
173
|
+
// Create a new credential
|
|
174
|
+
createCredential: (userId, credentialName, credentialType, credentialValue, description = null) => {
|
|
175
|
+
try {
|
|
176
|
+
const stmt = db.prepare('INSERT INTO user_credentials (user_id, credential_name, credential_type, credential_value, description) VALUES (?, ?, ?, ?, ?)');
|
|
177
|
+
const result = stmt.run(userId, credentialName, credentialType, credentialValue, description);
|
|
178
|
+
return { id: result.lastInsertRowid, credentialName, credentialType };
|
|
179
|
+
} catch (err) {
|
|
180
|
+
throw err;
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// Get all credentials for a user, optionally filtered by type
|
|
185
|
+
getCredentials: (userId, credentialType = null) => {
|
|
186
|
+
try {
|
|
187
|
+
let query = 'SELECT id, credential_name, credential_type, description, created_at, is_active FROM user_credentials WHERE user_id = ?';
|
|
188
|
+
const params = [userId];
|
|
189
|
+
|
|
190
|
+
if (credentialType) {
|
|
191
|
+
query += ' AND credential_type = ?';
|
|
192
|
+
params.push(credentialType);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
query += ' ORDER BY created_at DESC';
|
|
196
|
+
|
|
197
|
+
const rows = db.prepare(query).all(...params);
|
|
198
|
+
return rows;
|
|
199
|
+
} catch (err) {
|
|
200
|
+
throw err;
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
// Get active credential value for a user by type (returns most recent active)
|
|
205
|
+
getActiveCredential: (userId, credentialType) => {
|
|
206
|
+
try {
|
|
207
|
+
const row = db.prepare('SELECT credential_value FROM user_credentials WHERE user_id = ? AND credential_type = ? AND is_active = 1 ORDER BY created_at DESC LIMIT 1').get(userId, credentialType);
|
|
208
|
+
return row?.credential_value || null;
|
|
209
|
+
} catch (err) {
|
|
210
|
+
throw err;
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
// Delete a credential
|
|
215
|
+
deleteCredential: (userId, credentialId) => {
|
|
216
|
+
try {
|
|
217
|
+
const stmt = db.prepare('DELETE FROM user_credentials WHERE id = ? AND user_id = ?');
|
|
218
|
+
const result = stmt.run(credentialId, userId);
|
|
219
|
+
return result.changes > 0;
|
|
220
|
+
} catch (err) {
|
|
221
|
+
throw err;
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
// Toggle credential active status
|
|
226
|
+
toggleCredential: (userId, credentialId, isActive) => {
|
|
227
|
+
try {
|
|
228
|
+
const stmt = db.prepare('UPDATE user_credentials SET is_active = ? WHERE id = ? AND user_id = ?');
|
|
229
|
+
const result = stmt.run(isActive ? 1 : 0, credentialId, userId);
|
|
230
|
+
return result.changes > 0;
|
|
231
|
+
} catch (err) {
|
|
232
|
+
throw err;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Backward compatibility - keep old names pointing to new system
|
|
238
|
+
const githubTokensDb = {
|
|
239
|
+
createGithubToken: (userId, tokenName, githubToken, description = null) => {
|
|
240
|
+
return credentialsDb.createCredential(userId, tokenName, 'github_token', githubToken, description);
|
|
241
|
+
},
|
|
242
|
+
getGithubTokens: (userId) => {
|
|
243
|
+
return credentialsDb.getCredentials(userId, 'github_token');
|
|
244
|
+
},
|
|
245
|
+
getActiveGithubToken: (userId) => {
|
|
246
|
+
return credentialsDb.getActiveCredential(userId, 'github_token');
|
|
247
|
+
},
|
|
248
|
+
deleteGithubToken: (userId, tokenId) => {
|
|
249
|
+
return credentialsDb.deleteCredential(userId, tokenId);
|
|
250
|
+
},
|
|
251
|
+
toggleGithubToken: (userId, tokenId, isActive) => {
|
|
252
|
+
return credentialsDb.toggleCredential(userId, tokenId, isActive);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
82
256
|
export {
|
|
83
257
|
db,
|
|
84
258
|
initializeDatabase,
|
|
85
|
-
userDb
|
|
259
|
+
userDb,
|
|
260
|
+
apiKeysDb,
|
|
261
|
+
credentialsDb,
|
|
262
|
+
githubTokensDb // Backward compatibility
|
|
86
263
|
};
|
package/server/database/init.sql
CHANGED
|
@@ -13,4 +13,37 @@ CREATE TABLE IF NOT EXISTS users (
|
|
|
13
13
|
|
|
14
14
|
-- Indexes for performance
|
|
15
15
|
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
|
16
|
-
CREATE INDEX IF NOT EXISTS idx_users_active ON users(is_active);
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_users_active ON users(is_active);
|
|
17
|
+
|
|
18
|
+
-- API Keys table for external API access
|
|
19
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
user_id INTEGER NOT NULL,
|
|
22
|
+
key_name TEXT NOT NULL,
|
|
23
|
+
api_key TEXT UNIQUE NOT NULL,
|
|
24
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
25
|
+
last_used DATETIME,
|
|
26
|
+
is_active BOOLEAN DEFAULT 1,
|
|
27
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_key ON api_keys(api_key);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id);
|
|
32
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(is_active);
|
|
33
|
+
|
|
34
|
+
-- User credentials table for storing various tokens/credentials (GitHub, GitLab, etc.)
|
|
35
|
+
CREATE TABLE IF NOT EXISTS user_credentials (
|
|
36
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
37
|
+
user_id INTEGER NOT NULL,
|
|
38
|
+
credential_name TEXT NOT NULL,
|
|
39
|
+
credential_type TEXT NOT NULL, -- 'github_token', 'gitlab_token', 'bitbucket_token', etc.
|
|
40
|
+
credential_value TEXT NOT NULL,
|
|
41
|
+
description TEXT,
|
|
42
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
43
|
+
is_active BOOLEAN DEFAULT 1,
|
|
44
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_user_credentials_user_id ON user_credentials(user_id);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_user_credentials_type ON user_credentials(credential_type);
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_user_credentials_active ON user_credentials(is_active);
|
package/server/index.js
CHANGED
|
@@ -47,6 +47,8 @@ import cursorRoutes from './routes/cursor.js';
|
|
|
47
47
|
import taskmasterRoutes from './routes/taskmaster.js';
|
|
48
48
|
import mcpUtilsRoutes from './routes/mcp-utils.js';
|
|
49
49
|
import commandsRoutes from './routes/commands.js';
|
|
50
|
+
import settingsRoutes from './routes/settings.js';
|
|
51
|
+
import agentRoutes from './routes/agent.js';
|
|
50
52
|
import { initializeDatabase } from './database/db.js';
|
|
51
53
|
import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
|
|
52
54
|
|
|
@@ -170,7 +172,8 @@ const wss = new WebSocketServer({
|
|
|
170
172
|
app.locals.wss = wss;
|
|
171
173
|
|
|
172
174
|
app.use(cors());
|
|
173
|
-
app.use(express.json());
|
|
175
|
+
app.use(express.json({ limit: '50mb' }));
|
|
176
|
+
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
|
174
177
|
|
|
175
178
|
// Optional API key validation (if configured)
|
|
176
179
|
app.use('/api', validateApiKey);
|
|
@@ -196,6 +199,15 @@ app.use('/api/mcp-utils', authenticateToken, mcpUtilsRoutes);
|
|
|
196
199
|
// Commands API Routes (protected)
|
|
197
200
|
app.use('/api/commands', authenticateToken, commandsRoutes);
|
|
198
201
|
|
|
202
|
+
// Settings API Routes (protected)
|
|
203
|
+
app.use('/api/settings', authenticateToken, settingsRoutes);
|
|
204
|
+
|
|
205
|
+
// Agent API Routes (uses API key authentication)
|
|
206
|
+
app.use('/api/agent', agentRoutes);
|
|
207
|
+
|
|
208
|
+
// Serve public files (like api-docs.html)
|
|
209
|
+
app.use(express.static(path.join(__dirname, '../public')));
|
|
210
|
+
|
|
199
211
|
// Static files served after API routes
|
|
200
212
|
// Add cache control: HTML files should not be cached, but assets can be cached
|
|
201
213
|
app.use(express.static(path.join(__dirname, '../dist'), {
|
|
@@ -397,7 +409,10 @@ app.get('/api/projects/:projectName/file', authenticateToken, async (req, res) =
|
|
|
397
409
|
return res.status(404).json({ error: 'Project not found' });
|
|
398
410
|
}
|
|
399
411
|
|
|
400
|
-
|
|
412
|
+
// Handle both absolute and relative paths
|
|
413
|
+
const resolved = path.isAbsolute(filePath)
|
|
414
|
+
? path.resolve(filePath)
|
|
415
|
+
: path.resolve(projectRoot, filePath);
|
|
401
416
|
const normalizedRoot = path.resolve(projectRoot) + path.sep;
|
|
402
417
|
if (!resolved.startsWith(normalizedRoot)) {
|
|
403
418
|
return res.status(403).json({ error: 'Path must be under project root' });
|
|
@@ -493,21 +508,15 @@ app.put('/api/projects/:projectName/file', authenticateToken, async (req, res) =
|
|
|
493
508
|
return res.status(404).json({ error: 'Project not found' });
|
|
494
509
|
}
|
|
495
510
|
|
|
496
|
-
|
|
511
|
+
// Handle both absolute and relative paths
|
|
512
|
+
const resolved = path.isAbsolute(filePath)
|
|
513
|
+
? path.resolve(filePath)
|
|
514
|
+
: path.resolve(projectRoot, filePath);
|
|
497
515
|
const normalizedRoot = path.resolve(projectRoot) + path.sep;
|
|
498
516
|
if (!resolved.startsWith(normalizedRoot)) {
|
|
499
517
|
return res.status(403).json({ error: 'Path must be under project root' });
|
|
500
518
|
}
|
|
501
519
|
|
|
502
|
-
// Create backup of original file
|
|
503
|
-
try {
|
|
504
|
-
const backupPath = resolved + '.backup.' + Date.now();
|
|
505
|
-
await fsPromises.copyFile(resolved, backupPath);
|
|
506
|
-
console.log('📋 Created backup:', backupPath);
|
|
507
|
-
} catch (backupError) {
|
|
508
|
-
console.warn('Could not create backup:', backupError.message);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
520
|
// Write the new content
|
|
512
521
|
await fsPromises.writeFile(resolved, content, 'utf8');
|
|
513
522
|
|
|
@@ -1234,14 +1243,17 @@ app.get('*', (req, res) => {
|
|
|
1234
1243
|
|
|
1235
1244
|
// Only serve index.html for HTML routes, not for static assets
|
|
1236
1245
|
// Static assets should already be handled by express.static middleware above
|
|
1237
|
-
|
|
1246
|
+
const indexPath = path.join(__dirname, '../dist/index.html');
|
|
1247
|
+
|
|
1248
|
+
// Check if dist/index.html exists (production build available)
|
|
1249
|
+
if (fs.existsSync(indexPath)) {
|
|
1238
1250
|
// Set no-cache headers for HTML to prevent service worker issues
|
|
1239
1251
|
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
1240
1252
|
res.setHeader('Pragma', 'no-cache');
|
|
1241
1253
|
res.setHeader('Expires', '0');
|
|
1242
|
-
res.sendFile(
|
|
1254
|
+
res.sendFile(indexPath);
|
|
1243
1255
|
} else {
|
|
1244
|
-
// In development, redirect to Vite dev server
|
|
1256
|
+
// In development, redirect to Vite dev server only if dist doesn't exist
|
|
1245
1257
|
res.redirect(`http://localhost:${process.env.VITE_PORT || 5173}`);
|
|
1246
1258
|
}
|
|
1247
1259
|
});
|
|
@@ -1336,8 +1348,17 @@ async function startServer() {
|
|
|
1336
1348
|
await initializeDatabase();
|
|
1337
1349
|
console.log('✅ Database initialization skipped (testing)');
|
|
1338
1350
|
|
|
1351
|
+
// Check if running in production mode (dist folder exists)
|
|
1352
|
+
const distIndexPath = path.join(__dirname, '../dist/index.html');
|
|
1353
|
+
const isProduction = fs.existsSync(distIndexPath);
|
|
1354
|
+
|
|
1339
1355
|
// Log Claude implementation mode
|
|
1340
1356
|
console.log('🚀 Using Claude Agents SDK for Claude integration');
|
|
1357
|
+
console.log(`📦 Running in ${isProduction ? 'PRODUCTION' : 'DEVELOPMENT'} mode`);
|
|
1358
|
+
|
|
1359
|
+
if (!isProduction) {
|
|
1360
|
+
console.log(`⚠️ Note: Requests will be proxied to Vite dev server at http://localhost:${process.env.VITE_PORT || 5173}`);
|
|
1361
|
+
}
|
|
1341
1362
|
|
|
1342
1363
|
server.listen(PORT, '0.0.0.0', async () => {
|
|
1343
1364
|
console.log(`Claude Code UI server running on http://0.0.0.0:${PORT}`);
|
package/server/projects.js
CHANGED
|
@@ -523,10 +523,12 @@ async function getProjects() {
|
|
|
523
523
|
|
|
524
524
|
async function getSessions(projectName, limit = 5, offset = 0) {
|
|
525
525
|
const projectDir = path.join(process.env.HOME, '.claude', 'projects', projectName);
|
|
526
|
-
|
|
526
|
+
|
|
527
527
|
try {
|
|
528
528
|
const files = await fs.readdir(projectDir);
|
|
529
|
-
|
|
529
|
+
// agent-*.jsonl files contain session start data at this point. This needs to be revisited
|
|
530
|
+
// periodically to make sure only accurate data is there and no new functionality is added there
|
|
531
|
+
const jsonlFiles = files.filter(file => file.endsWith('.jsonl') && !file.startsWith('agent-'));
|
|
530
532
|
|
|
531
533
|
if (jsonlFiles.length === 0) {
|
|
532
534
|
return { sessions: [], hasMore: false, total: 0 };
|
|
@@ -803,10 +805,12 @@ async function parseJsonlSessions(filePath) {
|
|
|
803
805
|
// Get messages for a specific session with pagination support
|
|
804
806
|
async function getSessionMessages(projectName, sessionId, limit = null, offset = 0) {
|
|
805
807
|
const projectDir = path.join(process.env.HOME, '.claude', 'projects', projectName);
|
|
806
|
-
|
|
808
|
+
|
|
807
809
|
try {
|
|
808
810
|
const files = await fs.readdir(projectDir);
|
|
809
|
-
|
|
811
|
+
// agent-*.jsonl files contain session start data at this point. This needs to be revisited
|
|
812
|
+
// periodically to make sure only accurate data is there and no new functionality is added there
|
|
813
|
+
const jsonlFiles = files.filter(file => file.endsWith('.jsonl') && !file.startsWith('agent-'));
|
|
810
814
|
|
|
811
815
|
if (jsonlFiles.length === 0) {
|
|
812
816
|
return { messages: [], total: 0, hasMore: false };
|