@siteboon/claude-code-ui 1.8.2

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 (106) hide show
  1. package/.env.example +12 -0
  2. package/.nvmrc +1 -0
  3. package/LICENSE +675 -0
  4. package/README.md +275 -0
  5. package/index.html +48 -0
  6. package/package.json +84 -0
  7. package/postcss.config.js +6 -0
  8. package/public/convert-icons.md +53 -0
  9. package/public/favicon.png +0 -0
  10. package/public/favicon.svg +9 -0
  11. package/public/generate-icons.js +49 -0
  12. package/public/icons/claude-ai-icon.svg +1 -0
  13. package/public/icons/cursor.svg +1 -0
  14. package/public/icons/generate-icons.md +19 -0
  15. package/public/icons/icon-128x128.png +0 -0
  16. package/public/icons/icon-128x128.svg +12 -0
  17. package/public/icons/icon-144x144.png +0 -0
  18. package/public/icons/icon-144x144.svg +12 -0
  19. package/public/icons/icon-152x152.png +0 -0
  20. package/public/icons/icon-152x152.svg +12 -0
  21. package/public/icons/icon-192x192.png +0 -0
  22. package/public/icons/icon-192x192.svg +12 -0
  23. package/public/icons/icon-384x384.png +0 -0
  24. package/public/icons/icon-384x384.svg +12 -0
  25. package/public/icons/icon-512x512.png +0 -0
  26. package/public/icons/icon-512x512.svg +12 -0
  27. package/public/icons/icon-72x72.png +0 -0
  28. package/public/icons/icon-72x72.svg +12 -0
  29. package/public/icons/icon-96x96.png +0 -0
  30. package/public/icons/icon-96x96.svg +12 -0
  31. package/public/icons/icon-template.svg +12 -0
  32. package/public/logo.svg +9 -0
  33. package/public/manifest.json +61 -0
  34. package/public/screenshots/cli-selection.png +0 -0
  35. package/public/screenshots/desktop-main.png +0 -0
  36. package/public/screenshots/mobile-chat.png +0 -0
  37. package/public/screenshots/tools-modal.png +0 -0
  38. package/public/sw.js +49 -0
  39. package/server/claude-cli.js +391 -0
  40. package/server/cursor-cli.js +250 -0
  41. package/server/database/db.js +86 -0
  42. package/server/database/init.sql +16 -0
  43. package/server/index.js +1167 -0
  44. package/server/middleware/auth.js +80 -0
  45. package/server/projects.js +1063 -0
  46. package/server/routes/auth.js +135 -0
  47. package/server/routes/cursor.js +794 -0
  48. package/server/routes/git.js +823 -0
  49. package/server/routes/mcp-utils.js +48 -0
  50. package/server/routes/mcp.js +552 -0
  51. package/server/routes/taskmaster.js +1971 -0
  52. package/server/utils/mcp-detector.js +198 -0
  53. package/server/utils/taskmaster-websocket.js +129 -0
  54. package/src/App.jsx +751 -0
  55. package/src/components/ChatInterface.jsx +3485 -0
  56. package/src/components/ClaudeLogo.jsx +11 -0
  57. package/src/components/ClaudeStatus.jsx +107 -0
  58. package/src/components/CodeEditor.jsx +422 -0
  59. package/src/components/CreateTaskModal.jsx +88 -0
  60. package/src/components/CursorLogo.jsx +9 -0
  61. package/src/components/DarkModeToggle.jsx +35 -0
  62. package/src/components/DiffViewer.jsx +41 -0
  63. package/src/components/ErrorBoundary.jsx +73 -0
  64. package/src/components/FileTree.jsx +480 -0
  65. package/src/components/GitPanel.jsx +1283 -0
  66. package/src/components/ImageViewer.jsx +54 -0
  67. package/src/components/LoginForm.jsx +110 -0
  68. package/src/components/MainContent.jsx +577 -0
  69. package/src/components/MicButton.jsx +272 -0
  70. package/src/components/MobileNav.jsx +88 -0
  71. package/src/components/NextTaskBanner.jsx +695 -0
  72. package/src/components/PRDEditor.jsx +871 -0
  73. package/src/components/ProtectedRoute.jsx +44 -0
  74. package/src/components/QuickSettingsPanel.jsx +262 -0
  75. package/src/components/Settings.jsx +2023 -0
  76. package/src/components/SetupForm.jsx +135 -0
  77. package/src/components/Shell.jsx +663 -0
  78. package/src/components/Sidebar.jsx +1665 -0
  79. package/src/components/StandaloneShell.jsx +106 -0
  80. package/src/components/TaskCard.jsx +210 -0
  81. package/src/components/TaskDetail.jsx +406 -0
  82. package/src/components/TaskIndicator.jsx +108 -0
  83. package/src/components/TaskList.jsx +1054 -0
  84. package/src/components/TaskMasterSetupWizard.jsx +603 -0
  85. package/src/components/TaskMasterStatus.jsx +86 -0
  86. package/src/components/TodoList.jsx +91 -0
  87. package/src/components/Tooltip.jsx +91 -0
  88. package/src/components/ui/badge.jsx +31 -0
  89. package/src/components/ui/button.jsx +46 -0
  90. package/src/components/ui/input.jsx +19 -0
  91. package/src/components/ui/scroll-area.jsx +23 -0
  92. package/src/contexts/AuthContext.jsx +158 -0
  93. package/src/contexts/TaskMasterContext.jsx +324 -0
  94. package/src/contexts/TasksSettingsContext.jsx +95 -0
  95. package/src/contexts/ThemeContext.jsx +94 -0
  96. package/src/contexts/WebSocketContext.jsx +29 -0
  97. package/src/hooks/useAudioRecorder.js +109 -0
  98. package/src/hooks/useVersionCheck.js +39 -0
  99. package/src/index.css +822 -0
  100. package/src/lib/utils.js +6 -0
  101. package/src/main.jsx +10 -0
  102. package/src/utils/api.js +141 -0
  103. package/src/utils/websocket.js +109 -0
  104. package/src/utils/whisper.js +37 -0
  105. package/tailwind.config.js +63 -0
  106. package/vite.config.js +29 -0
@@ -0,0 +1,250 @@
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 activeCursorProcesses = new Map(); // Track active processes by session ID
11
+
12
+ async function spawnCursor(command, options = {}, ws) {
13
+ return new Promise(async (resolve, reject) => {
14
+ const { sessionId, projectPath, cwd, resume, toolsSettings, skipPermissions, model, 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
+ let messageBuffer = ''; // Buffer for accumulating assistant messages
18
+
19
+ // Use tools settings passed from frontend, or defaults
20
+ const settings = toolsSettings || {
21
+ allowedShellCommands: [],
22
+ skipPermissions: false
23
+ };
24
+
25
+ // Build Cursor CLI command
26
+ const args = [];
27
+
28
+ // Build flags allowing both resume and prompt together (reply in existing session)
29
+ // Treat presence of sessionId as intention to resume, regardless of resume flag
30
+ if (sessionId) {
31
+ args.push('--resume=' + sessionId);
32
+ }
33
+
34
+ if (command && command.trim()) {
35
+ // Provide a prompt (works for both new and resumed sessions)
36
+ args.push('-p', command);
37
+
38
+ // Add model flag if specified (only meaningful for new sessions; harmless on resume)
39
+ if (!sessionId && model) {
40
+ args.push('--model', model);
41
+ }
42
+
43
+ // Request streaming JSON when we are providing a prompt
44
+ args.push('--output-format', 'stream-json');
45
+ }
46
+
47
+ // Add skip permissions flag if enabled
48
+ if (skipPermissions || settings.skipPermissions) {
49
+ args.push('-f');
50
+ console.log('⚠️ Using -f flag (skip permissions)');
51
+ }
52
+
53
+ // Use cwd (actual project directory) instead of projectPath
54
+ const workingDir = cwd || projectPath || process.cwd();
55
+
56
+ console.log('Spawning Cursor CLI:', 'cursor-agent', args.join(' '));
57
+ console.log('Working directory:', workingDir);
58
+ console.log('Session info - Input sessionId:', sessionId, 'Resume:', resume);
59
+
60
+ const cursorProcess = spawnFunction('cursor-agent', args, {
61
+ cwd: workingDir,
62
+ stdio: ['pipe', 'pipe', 'pipe'],
63
+ env: { ...process.env } // Inherit all environment variables
64
+ });
65
+
66
+ // Store process reference for potential abort
67
+ const processKey = capturedSessionId || Date.now().toString();
68
+ activeCursorProcesses.set(processKey, cursorProcess);
69
+
70
+ // Handle stdout (streaming JSON responses)
71
+ cursorProcess.stdout.on('data', (data) => {
72
+ const rawOutput = data.toString();
73
+ console.log('📤 Cursor CLI stdout:', rawOutput);
74
+
75
+ const lines = rawOutput.split('\n').filter(line => line.trim());
76
+
77
+ for (const line of lines) {
78
+ try {
79
+ const response = JSON.parse(line);
80
+ console.log('📄 Parsed JSON response:', response);
81
+
82
+ // Handle different message types
83
+ switch (response.type) {
84
+ case 'system':
85
+ if (response.subtype === 'init') {
86
+ // Capture session ID
87
+ if (response.session_id && !capturedSessionId) {
88
+ capturedSessionId = response.session_id;
89
+ console.log('📝 Captured session ID:', capturedSessionId);
90
+
91
+ // Update process key with captured session ID
92
+ if (processKey !== capturedSessionId) {
93
+ activeCursorProcesses.delete(processKey);
94
+ activeCursorProcesses.set(capturedSessionId, cursorProcess);
95
+ }
96
+
97
+ // Send session-created event only once for new sessions
98
+ if (!sessionId && !sessionCreatedSent) {
99
+ sessionCreatedSent = true;
100
+ ws.send(JSON.stringify({
101
+ type: 'session-created',
102
+ sessionId: capturedSessionId,
103
+ model: response.model,
104
+ cwd: response.cwd
105
+ }));
106
+ }
107
+ }
108
+
109
+ // Send system info to frontend
110
+ ws.send(JSON.stringify({
111
+ type: 'cursor-system',
112
+ data: response
113
+ }));
114
+ }
115
+ break;
116
+
117
+ case 'user':
118
+ // Forward user message
119
+ ws.send(JSON.stringify({
120
+ type: 'cursor-user',
121
+ data: response
122
+ }));
123
+ break;
124
+
125
+ case 'assistant':
126
+ // Accumulate assistant message chunks
127
+ if (response.message && response.message.content && response.message.content.length > 0) {
128
+ const textContent = response.message.content[0].text;
129
+ messageBuffer += textContent;
130
+
131
+ // Send as Claude-compatible format for frontend
132
+ ws.send(JSON.stringify({
133
+ type: 'claude-response',
134
+ data: {
135
+ type: 'content_block_delta',
136
+ delta: {
137
+ type: 'text_delta',
138
+ text: textContent
139
+ }
140
+ }
141
+ }));
142
+ }
143
+ break;
144
+
145
+ case 'result':
146
+ // Session complete
147
+ console.log('Cursor session result:', response);
148
+
149
+ // Send final message if we have buffered content
150
+ if (messageBuffer) {
151
+ ws.send(JSON.stringify({
152
+ type: 'claude-response',
153
+ data: {
154
+ type: 'content_block_stop'
155
+ }
156
+ }));
157
+ }
158
+
159
+ // Send completion event
160
+ ws.send(JSON.stringify({
161
+ type: 'cursor-result',
162
+ data: response,
163
+ success: response.subtype === 'success'
164
+ }));
165
+ break;
166
+
167
+ default:
168
+ // Forward any other message types
169
+ ws.send(JSON.stringify({
170
+ type: 'cursor-response',
171
+ data: response
172
+ }));
173
+ }
174
+ } catch (parseError) {
175
+ console.log('📄 Non-JSON response:', line);
176
+ // If not JSON, send as raw text
177
+ ws.send(JSON.stringify({
178
+ type: 'cursor-output',
179
+ data: line
180
+ }));
181
+ }
182
+ }
183
+ });
184
+
185
+ // Handle stderr
186
+ cursorProcess.stderr.on('data', (data) => {
187
+ console.error('Cursor CLI stderr:', data.toString());
188
+ ws.send(JSON.stringify({
189
+ type: 'cursor-error',
190
+ error: data.toString()
191
+ }));
192
+ });
193
+
194
+ // Handle process completion
195
+ cursorProcess.on('close', async (code) => {
196
+ console.log(`Cursor CLI process exited with code ${code}`);
197
+
198
+ // Clean up process reference
199
+ const finalSessionId = capturedSessionId || sessionId || processKey;
200
+ activeCursorProcesses.delete(finalSessionId);
201
+
202
+ ws.send(JSON.stringify({
203
+ type: 'claude-complete',
204
+ exitCode: code,
205
+ isNewSession: !sessionId && !!command // Flag to indicate this was a new session
206
+ }));
207
+
208
+ if (code === 0) {
209
+ resolve();
210
+ } else {
211
+ reject(new Error(`Cursor CLI exited with code ${code}`));
212
+ }
213
+ });
214
+
215
+ // Handle process errors
216
+ cursorProcess.on('error', (error) => {
217
+ console.error('Cursor CLI process error:', error);
218
+
219
+ // Clean up process reference on error
220
+ const finalSessionId = capturedSessionId || sessionId || processKey;
221
+ activeCursorProcesses.delete(finalSessionId);
222
+
223
+ ws.send(JSON.stringify({
224
+ type: 'cursor-error',
225
+ error: error.message
226
+ }));
227
+
228
+ reject(error);
229
+ });
230
+
231
+ // Close stdin since Cursor doesn't need interactive input
232
+ cursorProcess.stdin.end();
233
+ });
234
+ }
235
+
236
+ function abortCursorSession(sessionId) {
237
+ const process = activeCursorProcesses.get(sessionId);
238
+ if (process) {
239
+ console.log(`🛑 Aborting Cursor session: ${sessionId}`);
240
+ process.kill('SIGTERM');
241
+ activeCursorProcesses.delete(sessionId);
242
+ return true;
243
+ }
244
+ return false;
245
+ }
246
+
247
+ export {
248
+ spawnCursor,
249
+ abortCursorSession
250
+ };
@@ -0,0 +1,86 @@
1
+ import Database from 'better-sqlite3';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname } from 'path';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ const DB_PATH = path.join(__dirname, 'auth.db');
11
+ const INIT_SQL_PATH = path.join(__dirname, 'init.sql');
12
+
13
+ // Create database connection
14
+ const db = new Database(DB_PATH);
15
+ console.log('Connected to SQLite database');
16
+
17
+ // Initialize database with schema
18
+ const initializeDatabase = async () => {
19
+ try {
20
+ const initSQL = fs.readFileSync(INIT_SQL_PATH, 'utf8');
21
+ db.exec(initSQL);
22
+ console.log('Database initialized successfully');
23
+ } catch (error) {
24
+ console.error('Error initializing database:', error.message);
25
+ throw error;
26
+ }
27
+ };
28
+
29
+ // User database operations
30
+ const userDb = {
31
+ // Check if any users exist
32
+ hasUsers: () => {
33
+ try {
34
+ const row = db.prepare('SELECT COUNT(*) as count FROM users').get();
35
+ return row.count > 0;
36
+ } catch (err) {
37
+ throw err;
38
+ }
39
+ },
40
+
41
+ // Create a new user
42
+ createUser: (username, passwordHash) => {
43
+ try {
44
+ const stmt = db.prepare('INSERT INTO users (username, password_hash) VALUES (?, ?)');
45
+ const result = stmt.run(username, passwordHash);
46
+ return { id: result.lastInsertRowid, username };
47
+ } catch (err) {
48
+ throw err;
49
+ }
50
+ },
51
+
52
+ // Get user by username
53
+ getUserByUsername: (username) => {
54
+ try {
55
+ const row = db.prepare('SELECT * FROM users WHERE username = ? AND is_active = 1').get(username);
56
+ return row;
57
+ } catch (err) {
58
+ throw err;
59
+ }
60
+ },
61
+
62
+ // Update last login time
63
+ updateLastLogin: (userId) => {
64
+ try {
65
+ db.prepare('UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?').run(userId);
66
+ } catch (err) {
67
+ throw err;
68
+ }
69
+ },
70
+
71
+ // Get user by ID
72
+ getUserById: (userId) => {
73
+ try {
74
+ const row = db.prepare('SELECT id, username, created_at, last_login FROM users WHERE id = ? AND is_active = 1').get(userId);
75
+ return row;
76
+ } catch (err) {
77
+ throw err;
78
+ }
79
+ }
80
+ };
81
+
82
+ export {
83
+ db,
84
+ initializeDatabase,
85
+ userDb
86
+ };
@@ -0,0 +1,16 @@
1
+ -- Initialize authentication database
2
+ PRAGMA foreign_keys = ON;
3
+
4
+ -- Users table (single user system)
5
+ CREATE TABLE IF NOT EXISTS users (
6
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
7
+ username TEXT UNIQUE NOT NULL,
8
+ password_hash TEXT NOT NULL,
9
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
10
+ last_login DATETIME,
11
+ is_active BOOLEAN DEFAULT 1
12
+ );
13
+
14
+ -- Indexes for performance
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);