@siteboon/claude-code-ui 1.8.12 → 1.9.1
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 +60 -2
- package/dist/assets/index-Be0ToEQx.js +793 -0
- package/dist/assets/index-Bmo7Hu70.css +32 -0
- package/dist/assets/vendor-codemirror-D2k1L1JZ.js +39 -0
- package/dist/assets/vendor-react-7V_UDHjJ.js +59 -0
- package/dist/assets/vendor-xterm-jI4BCHEb.js +66 -0
- package/dist/clear-cache.html +85 -0
- package/dist/index.html +5 -2
- package/package.json +6 -3
- package/server/claude-sdk.js +513 -0
- package/server/cursor-cli.js +14 -2
- package/server/database/auth.db +0 -0
- package/server/index.js +239 -41
- package/server/projects.js +108 -17
- package/server/routes/commands.js +572 -0
- package/server/utils/commandParser.js +303 -0
- package/dist/assets/index-Cl5xisCA.js +0 -895
- package/dist/assets/index-Co7ALK3i.css +0 -32
- package/server/claude-cli.js +0 -397
package/server/claude-cli.js
DELETED
|
@@ -1,397 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
import crossSpawn from 'cross-spawn';
|
|
3
|
-
import { promises as fs } from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import os from 'os';
|
|
6
|
-
|
|
7
|
-
// Use cross-spawn on Windows for better command execution
|
|
8
|
-
const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn;
|
|
9
|
-
|
|
10
|
-
let activeClaudeProcesses = new Map(); // Track active processes by session ID
|
|
11
|
-
|
|
12
|
-
async function spawnClaude(command, options = {}, ws) {
|
|
13
|
-
return new Promise(async (resolve, reject) => {
|
|
14
|
-
const { sessionId, projectPath, cwd, resume, toolsSettings, permissionMode, images } = options;
|
|
15
|
-
let capturedSessionId = sessionId; // Track session ID throughout the process
|
|
16
|
-
let sessionCreatedSent = false; // Track if we've already sent session-created event
|
|
17
|
-
|
|
18
|
-
// Use tools settings passed from frontend, or defaults
|
|
19
|
-
const settings = toolsSettings || {
|
|
20
|
-
allowedTools: [],
|
|
21
|
-
disallowedTools: [],
|
|
22
|
-
skipPermissions: false
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// Build Claude CLI command - start with print/resume flags first
|
|
26
|
-
const args = [];
|
|
27
|
-
|
|
28
|
-
// Use cwd (actual project directory) instead of projectPath (Claude's metadata directory)
|
|
29
|
-
const workingDir = cwd || process.cwd();
|
|
30
|
-
|
|
31
|
-
// Handle images by saving them to temporary files and passing paths to Claude
|
|
32
|
-
const tempImagePaths = [];
|
|
33
|
-
let tempDir = null;
|
|
34
|
-
if (images && images.length > 0) {
|
|
35
|
-
try {
|
|
36
|
-
// Create temp directory in the project directory so Claude can access it
|
|
37
|
-
tempDir = path.join(workingDir, '.tmp', 'images', Date.now().toString());
|
|
38
|
-
await fs.mkdir(tempDir, { recursive: true });
|
|
39
|
-
|
|
40
|
-
// Save each image to a temp file
|
|
41
|
-
for (const [index, image] of images.entries()) {
|
|
42
|
-
// Extract base64 data and mime type
|
|
43
|
-
const matches = image.data.match(/^data:([^;]+);base64,(.+)$/);
|
|
44
|
-
if (!matches) {
|
|
45
|
-
console.error('Invalid image data format');
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const [, mimeType, base64Data] = matches;
|
|
50
|
-
const extension = mimeType.split('/')[1] || 'png';
|
|
51
|
-
const filename = `image_${index}.${extension}`;
|
|
52
|
-
const filepath = path.join(tempDir, filename);
|
|
53
|
-
|
|
54
|
-
// Write base64 data to file
|
|
55
|
-
await fs.writeFile(filepath, Buffer.from(base64Data, 'base64'));
|
|
56
|
-
tempImagePaths.push(filepath);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Include the full image paths in the prompt for Claude to reference
|
|
60
|
-
// Only modify the command if we actually have images and a command
|
|
61
|
-
if (tempImagePaths.length > 0 && command && command.trim()) {
|
|
62
|
-
const imageNote = `\n\n[Images provided at the following paths:]\n${tempImagePaths.map((p, i) => `${i + 1}. ${p}`).join('\n')}`;
|
|
63
|
-
const modifiedCommand = command + imageNote;
|
|
64
|
-
|
|
65
|
-
// Update the command in args - now that --print and command are separate
|
|
66
|
-
const printIndex = args.indexOf('--print');
|
|
67
|
-
if (printIndex !== -1 && printIndex + 1 < args.length && args[printIndex + 1] === command) {
|
|
68
|
-
args[printIndex + 1] = modifiedCommand;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
} catch (error) {
|
|
74
|
-
console.error('Error processing images for Claude:', error);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Add resume flag if resuming
|
|
79
|
-
if (resume && sessionId) {
|
|
80
|
-
args.push('--resume', sessionId);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Add basic flags
|
|
84
|
-
args.push('--output-format', 'stream-json', '--verbose');
|
|
85
|
-
|
|
86
|
-
// Add MCP config flag only if MCP servers are configured
|
|
87
|
-
try {
|
|
88
|
-
console.log('🔍 Starting MCP config check...');
|
|
89
|
-
// Use already imported modules (fs.promises is imported as fs, path, os)
|
|
90
|
-
const fsSync = await import('fs'); // Import synchronous fs methods
|
|
91
|
-
console.log('✅ Successfully imported fs sync methods');
|
|
92
|
-
|
|
93
|
-
// Check for MCP config in ~/.claude.json
|
|
94
|
-
const claudeConfigPath = path.join(os.homedir(), '.claude.json');
|
|
95
|
-
|
|
96
|
-
console.log(`🔍 Checking for MCP configs in: ${claudeConfigPath}`);
|
|
97
|
-
console.log(` Claude config exists: ${fsSync.existsSync(claudeConfigPath)}`);
|
|
98
|
-
|
|
99
|
-
let hasMcpServers = false;
|
|
100
|
-
|
|
101
|
-
// Check Claude config for MCP servers
|
|
102
|
-
if (fsSync.existsSync(claudeConfigPath)) {
|
|
103
|
-
try {
|
|
104
|
-
const claudeConfig = JSON.parse(fsSync.readFileSync(claudeConfigPath, 'utf8'));
|
|
105
|
-
|
|
106
|
-
// Check global MCP servers
|
|
107
|
-
if (claudeConfig.mcpServers && Object.keys(claudeConfig.mcpServers).length > 0) {
|
|
108
|
-
console.log(`✅ Found ${Object.keys(claudeConfig.mcpServers).length} global MCP servers`);
|
|
109
|
-
hasMcpServers = true;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Check project-specific MCP servers
|
|
113
|
-
if (!hasMcpServers && claudeConfig.claudeProjects) {
|
|
114
|
-
const currentProjectPath = process.cwd();
|
|
115
|
-
const projectConfig = claudeConfig.claudeProjects[currentProjectPath];
|
|
116
|
-
if (projectConfig && projectConfig.mcpServers && Object.keys(projectConfig.mcpServers).length > 0) {
|
|
117
|
-
console.log(`✅ Found ${Object.keys(projectConfig.mcpServers).length} project MCP servers`);
|
|
118
|
-
hasMcpServers = true;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
} catch (e) {
|
|
122
|
-
console.log(`❌ Failed to parse Claude config:`, e.message);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
console.log(`🔍 hasMcpServers result: ${hasMcpServers}`);
|
|
127
|
-
|
|
128
|
-
if (hasMcpServers) {
|
|
129
|
-
// Use Claude config file if it has MCP servers
|
|
130
|
-
let configPath = null;
|
|
131
|
-
|
|
132
|
-
if (fsSync.existsSync(claudeConfigPath)) {
|
|
133
|
-
try {
|
|
134
|
-
const claudeConfig = JSON.parse(fsSync.readFileSync(claudeConfigPath, 'utf8'));
|
|
135
|
-
|
|
136
|
-
// Check if we have any MCP servers (global or project-specific)
|
|
137
|
-
const hasGlobalServers = claudeConfig.mcpServers && Object.keys(claudeConfig.mcpServers).length > 0;
|
|
138
|
-
const currentProjectPath = process.cwd();
|
|
139
|
-
const projectConfig = claudeConfig.claudeProjects && claudeConfig.claudeProjects[currentProjectPath];
|
|
140
|
-
const hasProjectServers = projectConfig && projectConfig.mcpServers && Object.keys(projectConfig.mcpServers).length > 0;
|
|
141
|
-
|
|
142
|
-
if (hasGlobalServers || hasProjectServers) {
|
|
143
|
-
configPath = claudeConfigPath;
|
|
144
|
-
}
|
|
145
|
-
} catch (e) {
|
|
146
|
-
// No valid config found
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (configPath) {
|
|
151
|
-
console.log(`📡 Adding MCP config: ${configPath}`);
|
|
152
|
-
args.push('--mcp-config', configPath);
|
|
153
|
-
} else {
|
|
154
|
-
console.log('⚠️ MCP servers detected but no valid config file found');
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
} catch (error) {
|
|
158
|
-
// If there's any error checking for MCP configs, don't add the flag
|
|
159
|
-
console.log('❌ MCP config check failed:', error.message);
|
|
160
|
-
console.log('📍 Error stack:', error.stack);
|
|
161
|
-
console.log('Note: MCP config check failed, proceeding without MCP support');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Add model for new sessions
|
|
165
|
-
if (!resume) {
|
|
166
|
-
args.push('--model', 'sonnet');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Add permission mode if specified (works for both new and resumed sessions)
|
|
170
|
-
if (permissionMode && permissionMode !== 'default') {
|
|
171
|
-
args.push('--permission-mode', permissionMode);
|
|
172
|
-
console.log('🔒 Using permission mode:', permissionMode);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Add tools settings flags
|
|
176
|
-
// Don't use --dangerously-skip-permissions when in plan mode
|
|
177
|
-
if (settings.skipPermissions && permissionMode !== 'plan') {
|
|
178
|
-
args.push('--dangerously-skip-permissions');
|
|
179
|
-
console.log('⚠️ Using --dangerously-skip-permissions (skipping other tool settings)');
|
|
180
|
-
} else {
|
|
181
|
-
// Only add allowed/disallowed tools if not skipping permissions
|
|
182
|
-
|
|
183
|
-
// Collect all allowed tools, including plan mode defaults
|
|
184
|
-
let allowedTools = [...(settings.allowedTools || [])];
|
|
185
|
-
|
|
186
|
-
// Add plan mode specific tools
|
|
187
|
-
if (permissionMode === 'plan') {
|
|
188
|
-
const planModeTools = ['Read', 'Task', 'exit_plan_mode', 'TodoRead', 'TodoWrite'];
|
|
189
|
-
// Add plan mode tools that aren't already in the allowed list
|
|
190
|
-
for (const tool of planModeTools) {
|
|
191
|
-
if (!allowedTools.includes(tool)) {
|
|
192
|
-
allowedTools.push(tool);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
console.log('📝 Plan mode: Added default allowed tools:', planModeTools);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Add allowed tools
|
|
199
|
-
if (allowedTools.length > 0) {
|
|
200
|
-
for (const tool of allowedTools) {
|
|
201
|
-
args.push('--allowedTools', tool);
|
|
202
|
-
console.log('✅ Allowing tool:', tool);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Add disallowed tools
|
|
207
|
-
if (settings.disallowedTools && settings.disallowedTools.length > 0) {
|
|
208
|
-
for (const tool of settings.disallowedTools) {
|
|
209
|
-
args.push('--disallowedTools', tool);
|
|
210
|
-
console.log('❌ Disallowing tool:', tool);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Log when skip permissions is disabled due to plan mode
|
|
215
|
-
if (settings.skipPermissions && permissionMode === 'plan') {
|
|
216
|
-
console.log('📝 Skip permissions disabled due to plan mode');
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Add print flag with command if we have a command
|
|
221
|
-
if (command && command.trim()) {
|
|
222
|
-
|
|
223
|
-
// Separate arguments for better cross-platform compatibility
|
|
224
|
-
// This prevents issues with spaces and quotes on Windows
|
|
225
|
-
args.push('--print');
|
|
226
|
-
// Use `--` so user input is always treated as text, not options
|
|
227
|
-
args.push('--');
|
|
228
|
-
args.push(command);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
console.log('Spawning Claude CLI:', 'claude', args.map(arg => {
|
|
232
|
-
const cleanArg = arg.replace(/\n/g, '\\n').replace(/\r/g, '\\r');
|
|
233
|
-
return cleanArg.includes(' ') ? `"${cleanArg}"` : cleanArg;
|
|
234
|
-
}).join(' '));
|
|
235
|
-
console.log('Working directory:', workingDir);
|
|
236
|
-
console.log('Session info - Input sessionId:', sessionId, 'Resume:', resume);
|
|
237
|
-
console.log('🔍 Full command args:', JSON.stringify(args, null, 2));
|
|
238
|
-
console.log('🔍 Final Claude command will be: claude ' + args.join(' '));
|
|
239
|
-
|
|
240
|
-
// Use Claude CLI from environment variable or default to 'claude'
|
|
241
|
-
const claudePath = process.env.CLAUDE_CLI_PATH || 'claude';
|
|
242
|
-
console.log('🔍 Using Claude CLI path:', claudePath);
|
|
243
|
-
|
|
244
|
-
const claudeProcess = spawnFunction(claudePath, args, {
|
|
245
|
-
cwd: workingDir,
|
|
246
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
247
|
-
env: { ...process.env } // Inherit all environment variables
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
// Attach temp file info to process for cleanup later
|
|
251
|
-
claudeProcess.tempImagePaths = tempImagePaths;
|
|
252
|
-
claudeProcess.tempDir = tempDir;
|
|
253
|
-
|
|
254
|
-
// Store process reference for potential abort
|
|
255
|
-
const processKey = capturedSessionId || sessionId || Date.now().toString();
|
|
256
|
-
activeClaudeProcesses.set(processKey, claudeProcess);
|
|
257
|
-
|
|
258
|
-
// Handle stdout (streaming JSON responses)
|
|
259
|
-
claudeProcess.stdout.on('data', (data) => {
|
|
260
|
-
const rawOutput = data.toString();
|
|
261
|
-
console.log('📤 Claude CLI stdout:', rawOutput);
|
|
262
|
-
|
|
263
|
-
const lines = rawOutput.split('\n').filter(line => line.trim());
|
|
264
|
-
|
|
265
|
-
for (const line of lines) {
|
|
266
|
-
try {
|
|
267
|
-
const response = JSON.parse(line);
|
|
268
|
-
console.log('📄 Parsed JSON response:', response);
|
|
269
|
-
|
|
270
|
-
// Capture session ID if it's in the response
|
|
271
|
-
if (response.session_id && !capturedSessionId) {
|
|
272
|
-
capturedSessionId = response.session_id;
|
|
273
|
-
console.log('📝 Captured session ID:', capturedSessionId);
|
|
274
|
-
|
|
275
|
-
// Update process key with captured session ID
|
|
276
|
-
if (processKey !== capturedSessionId) {
|
|
277
|
-
activeClaudeProcesses.delete(processKey);
|
|
278
|
-
activeClaudeProcesses.set(capturedSessionId, claudeProcess);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Send session-created event only once for new sessions
|
|
282
|
-
if (!sessionId && !sessionCreatedSent) {
|
|
283
|
-
sessionCreatedSent = true;
|
|
284
|
-
ws.send(JSON.stringify({
|
|
285
|
-
type: 'session-created',
|
|
286
|
-
sessionId: capturedSessionId
|
|
287
|
-
}));
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Send parsed response to WebSocket
|
|
292
|
-
ws.send(JSON.stringify({
|
|
293
|
-
type: 'claude-response',
|
|
294
|
-
data: response
|
|
295
|
-
}));
|
|
296
|
-
} catch (parseError) {
|
|
297
|
-
console.log('📄 Non-JSON response:', line);
|
|
298
|
-
// If not JSON, send as raw text
|
|
299
|
-
ws.send(JSON.stringify({
|
|
300
|
-
type: 'claude-output',
|
|
301
|
-
data: line
|
|
302
|
-
}));
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
// Handle stderr
|
|
308
|
-
claudeProcess.stderr.on('data', (data) => {
|
|
309
|
-
console.error('Claude CLI stderr:', data.toString());
|
|
310
|
-
ws.send(JSON.stringify({
|
|
311
|
-
type: 'claude-error',
|
|
312
|
-
error: data.toString()
|
|
313
|
-
}));
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
// Handle process completion
|
|
317
|
-
claudeProcess.on('close', async (code) => {
|
|
318
|
-
console.log(`Claude CLI process exited with code ${code}`);
|
|
319
|
-
|
|
320
|
-
// Clean up process reference
|
|
321
|
-
const finalSessionId = capturedSessionId || sessionId || processKey;
|
|
322
|
-
activeClaudeProcesses.delete(finalSessionId);
|
|
323
|
-
|
|
324
|
-
ws.send(JSON.stringify({
|
|
325
|
-
type: 'claude-complete',
|
|
326
|
-
exitCode: code,
|
|
327
|
-
isNewSession: !sessionId && !!command // Flag to indicate this was a new session
|
|
328
|
-
}));
|
|
329
|
-
|
|
330
|
-
// Clean up temporary image files if any
|
|
331
|
-
if (claudeProcess.tempImagePaths && claudeProcess.tempImagePaths.length > 0) {
|
|
332
|
-
for (const imagePath of claudeProcess.tempImagePaths) {
|
|
333
|
-
await fs.unlink(imagePath).catch(err =>
|
|
334
|
-
console.error(`Failed to delete temp image ${imagePath}:`, err)
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
if (claudeProcess.tempDir) {
|
|
338
|
-
await fs.rm(claudeProcess.tempDir, { recursive: true, force: true }).catch(err =>
|
|
339
|
-
console.error(`Failed to delete temp directory ${claudeProcess.tempDir}:`, err)
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (code === 0) {
|
|
345
|
-
resolve();
|
|
346
|
-
} else {
|
|
347
|
-
reject(new Error(`Claude CLI exited with code ${code}`));
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
// Handle process errors
|
|
352
|
-
claudeProcess.on('error', (error) => {
|
|
353
|
-
console.error('Claude CLI process error:', error);
|
|
354
|
-
|
|
355
|
-
// Clean up process reference on error
|
|
356
|
-
const finalSessionId = capturedSessionId || sessionId || processKey;
|
|
357
|
-
activeClaudeProcesses.delete(finalSessionId);
|
|
358
|
-
|
|
359
|
-
ws.send(JSON.stringify({
|
|
360
|
-
type: 'claude-error',
|
|
361
|
-
error: error.message
|
|
362
|
-
}));
|
|
363
|
-
|
|
364
|
-
reject(error);
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
// Handle stdin for interactive mode
|
|
368
|
-
if (command) {
|
|
369
|
-
// For --print mode with arguments, we don't need to write to stdin
|
|
370
|
-
claudeProcess.stdin.end();
|
|
371
|
-
} else {
|
|
372
|
-
// For interactive mode, we need to write the command to stdin if provided later
|
|
373
|
-
// Keep stdin open for interactive session
|
|
374
|
-
if (command !== undefined) {
|
|
375
|
-
claudeProcess.stdin.write(command + '\n');
|
|
376
|
-
claudeProcess.stdin.end();
|
|
377
|
-
}
|
|
378
|
-
// If no command provided, stdin stays open for interactive use
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function abortClaudeSession(sessionId) {
|
|
384
|
-
const process = activeClaudeProcesses.get(sessionId);
|
|
385
|
-
if (process) {
|
|
386
|
-
console.log(`🛑 Aborting Claude session: ${sessionId}`);
|
|
387
|
-
process.kill('SIGTERM');
|
|
388
|
-
activeClaudeProcesses.delete(sessionId);
|
|
389
|
-
return true;
|
|
390
|
-
}
|
|
391
|
-
return false;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
export {
|
|
395
|
-
spawnClaude,
|
|
396
|
-
abortClaudeSession
|
|
397
|
-
};
|