@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,406 @@
1
+ import React, { useState } from 'react';
2
+ import { X, Flag, User, ArrowRight, CheckCircle, Circle, AlertCircle, Pause, Edit, Save, Copy, ChevronDown, ChevronRight, Clock } from 'lucide-react';
3
+ import { cn } from '../lib/utils';
4
+ import TaskIndicator from './TaskIndicator';
5
+ import { api } from '../utils/api';
6
+ import { useTaskMaster } from '../contexts/TaskMasterContext';
7
+
8
+ const TaskDetail = ({
9
+ task,
10
+ onClose,
11
+ onEdit,
12
+ onStatusChange,
13
+ onTaskClick,
14
+ isOpen = true,
15
+ className = ''
16
+ }) => {
17
+ const [editMode, setEditMode] = useState(false);
18
+ const [editedTask, setEditedTask] = useState(task || {});
19
+ const [isSaving, setIsSaving] = useState(false);
20
+ const [showDetails, setShowDetails] = useState(false);
21
+ const [showTestStrategy, setShowTestStrategy] = useState(false);
22
+ const { currentProject, refreshTasks } = useTaskMaster();
23
+
24
+ if (!isOpen || !task) return null;
25
+
26
+ const handleSave = async () => {
27
+ if (!currentProject) return;
28
+
29
+ setIsSaving(true);
30
+ try {
31
+ // Only include changed fields
32
+ const updates = {};
33
+ if (editedTask.title !== task.title) updates.title = editedTask.title;
34
+ if (editedTask.description !== task.description) updates.description = editedTask.description;
35
+ if (editedTask.details !== task.details) updates.details = editedTask.details;
36
+
37
+ if (Object.keys(updates).length > 0) {
38
+ const response = await api.taskmaster.updateTask(currentProject.name, task.id, updates);
39
+
40
+ if (response.ok) {
41
+ // Refresh tasks to get updated data
42
+ refreshTasks?.();
43
+ onEdit?.(editedTask);
44
+ setEditMode(false);
45
+ } else {
46
+ const error = await response.json();
47
+ console.error('Failed to update task:', error);
48
+ alert(`Failed to update task: ${error.message}`);
49
+ }
50
+ } else {
51
+ setEditMode(false);
52
+ }
53
+ } catch (error) {
54
+ console.error('Error updating task:', error);
55
+ alert('Error updating task. Please try again.');
56
+ } finally {
57
+ setIsSaving(false);
58
+ }
59
+ };
60
+
61
+ const handleStatusChange = async (newStatus) => {
62
+ if (!currentProject) return;
63
+
64
+ try {
65
+ const response = await api.taskmaster.updateTask(currentProject.name, task.id, { status: newStatus });
66
+
67
+ if (response.ok) {
68
+ refreshTasks?.();
69
+ onStatusChange?.(task.id, newStatus);
70
+ } else {
71
+ const error = await response.json();
72
+ console.error('Failed to update task status:', error);
73
+ alert(`Failed to update task status: ${error.message}`);
74
+ }
75
+ } catch (error) {
76
+ console.error('Error updating task status:', error);
77
+ alert('Error updating task status. Please try again.');
78
+ }
79
+ };
80
+
81
+ const copyTaskId = () => {
82
+ navigator.clipboard.writeText(task.id.toString());
83
+ };
84
+
85
+ const getStatusConfig = (status) => {
86
+ switch (status) {
87
+ case 'done':
88
+ return { icon: CheckCircle, color: 'text-green-600 dark:text-green-400', bg: 'bg-green-50 dark:bg-green-950' };
89
+ case 'in-progress':
90
+ return { icon: Clock, color: 'text-blue-600 dark:text-blue-400', bg: 'bg-blue-50 dark:bg-blue-950' };
91
+ case 'review':
92
+ return { icon: AlertCircle, color: 'text-amber-600 dark:text-amber-400', bg: 'bg-amber-50 dark:bg-amber-950' };
93
+ case 'deferred':
94
+ return { icon: Pause, color: 'text-gray-500 dark:text-gray-400', bg: 'bg-gray-50 dark:bg-gray-800' };
95
+ case 'cancelled':
96
+ return { icon: X, color: 'text-red-600 dark:text-red-400', bg: 'bg-red-50 dark:bg-red-950' };
97
+ default:
98
+ return { icon: Circle, color: 'text-slate-500 dark:text-slate-400', bg: 'bg-slate-50 dark:bg-slate-800' };
99
+ }
100
+ };
101
+
102
+ const statusConfig = getStatusConfig(task.status);
103
+ const StatusIcon = statusConfig.icon;
104
+
105
+
106
+ const getPriorityColor = (priority) => {
107
+ switch (priority) {
108
+ case 'high': return 'text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-950';
109
+ case 'medium': return 'text-yellow-600 dark:text-yellow-400 bg-yellow-50 dark:bg-yellow-950';
110
+ case 'low': return 'text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-950';
111
+ default: return 'text-gray-600 dark:text-gray-400 bg-gray-50 dark:bg-gray-800';
112
+ }
113
+ };
114
+
115
+ const statusOptions = [
116
+ { value: 'pending', label: 'Pending' },
117
+ { value: 'in-progress', label: 'In Progress' },
118
+ { value: 'review', label: 'Review' },
119
+ { value: 'done', label: 'Done' },
120
+ { value: 'deferred', label: 'Deferred' },
121
+ { value: 'cancelled', label: 'Cancelled' }
122
+ ];
123
+
124
+ return (
125
+ <div className="modal-backdrop fixed inset-0 flex items-center justify-center z-[100] md:p-4 bg-black/50">
126
+ <div className={cn(
127
+ 'bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 md:rounded-lg shadow-xl',
128
+ 'w-full md:max-w-4xl h-full md:h-[90vh] flex flex-col',
129
+ className
130
+ )}>
131
+ {/* Header */}
132
+ <div className="flex items-center justify-between p-4 md:p-6 border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
133
+ <div className="flex items-center gap-3 min-w-0 flex-1">
134
+ <StatusIcon className={cn('w-6 h-6', statusConfig.color)} />
135
+ <div className="min-w-0 flex-1">
136
+ <div className="flex items-center gap-2 mb-1">
137
+ <button
138
+ onClick={copyTaskId}
139
+ className="flex items-center gap-1 px-2 py-1 text-xs bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300 rounded hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
140
+ title="Click to copy task ID"
141
+ >
142
+ <span>Task {task.id}</span>
143
+ <Copy className="w-3 h-3" />
144
+ </button>
145
+ {task.parentId && (
146
+ <span className="text-xs text-gray-500 dark:text-gray-400">
147
+ Subtask of Task {task.parentId}
148
+ </span>
149
+ )}
150
+ </div>
151
+ {editMode ? (
152
+ <input
153
+ type="text"
154
+ value={editedTask.title || ''}
155
+ onChange={(e) => setEditedTask({ ...editedTask, title: e.target.value })}
156
+ className="w-full text-lg font-semibold bg-transparent border-b-2 border-blue-500 focus:outline-none text-gray-900 dark:text-white"
157
+ placeholder="Task title"
158
+ />
159
+ ) : (
160
+ <h1 className="text-lg md:text-xl font-semibold text-gray-900 dark:text-white line-clamp-2">
161
+ {task.title}
162
+ </h1>
163
+ )}
164
+ </div>
165
+ </div>
166
+
167
+ <div className="flex items-center gap-2 flex-shrink-0">
168
+ {editMode ? (
169
+ <>
170
+ <button
171
+ onClick={handleSave}
172
+ disabled={isSaving}
173
+ className="p-2 text-green-600 hover:text-green-700 hover:bg-green-50 dark:hover:bg-green-950 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
174
+ title={isSaving ? "Saving..." : "Save changes"}
175
+ >
176
+ <Save className={cn("w-5 h-5", isSaving && "animate-spin")} />
177
+ </button>
178
+ <button
179
+ onClick={() => {
180
+ setEditMode(false);
181
+ setEditedTask(task);
182
+ }}
183
+ disabled={isSaving}
184
+ className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
185
+ title="Cancel editing"
186
+ >
187
+ <X className="w-5 h-5" />
188
+ </button>
189
+ </>
190
+ ) : (
191
+ <button
192
+ onClick={() => setEditMode(true)}
193
+ className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors"
194
+ title="Edit task"
195
+ >
196
+ <Edit className="w-5 h-5" />
197
+ </button>
198
+ )}
199
+
200
+ <button
201
+ onClick={onClose}
202
+ className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors"
203
+ title="Close"
204
+ >
205
+ <X className="w-5 h-5" />
206
+ </button>
207
+ </div>
208
+ </div>
209
+
210
+ {/* Content */}
211
+ <div className="flex-1 overflow-y-auto p-4 md:p-6 space-y-6 min-h-0">
212
+ {/* Status and Metadata Row */}
213
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
214
+ {/* Status */}
215
+ <div className="space-y-2">
216
+ <label className="text-sm font-medium text-gray-700 dark:text-gray-300">Status</label>
217
+ <div className={cn(
218
+ 'w-full px-3 py-2 rounded-md border border-gray-300 dark:border-gray-600',
219
+ statusConfig.bg,
220
+ statusConfig.color
221
+ )}>
222
+ <div className="flex items-center gap-2">
223
+ <StatusIcon className="w-4 h-4" />
224
+ <span className="font-medium capitalize">
225
+ {statusOptions.find(option => option.value === task.status)?.label || task.status}
226
+ </span>
227
+ </div>
228
+ </div>
229
+ </div>
230
+
231
+ {/* Priority */}
232
+ <div className="space-y-2">
233
+ <label className="text-sm font-medium text-gray-700 dark:text-gray-300">Priority</label>
234
+ <div className={cn(
235
+ 'px-3 py-2 rounded-md text-sm font-medium capitalize',
236
+ getPriorityColor(task.priority)
237
+ )}>
238
+ <Flag className="w-4 h-4 inline mr-2" />
239
+ {task.priority || 'Not set'}
240
+ </div>
241
+ </div>
242
+
243
+ {/* Dependencies */}
244
+ <div className="space-y-2">
245
+ <label className="text-sm font-medium text-gray-700 dark:text-gray-300">Dependencies</label>
246
+ {task.dependencies && task.dependencies.length > 0 ? (
247
+ <div className="flex flex-wrap gap-1">
248
+ {task.dependencies.map(depId => (
249
+ <button
250
+ key={depId}
251
+ onClick={() => onTaskClick && onTaskClick({ id: depId })}
252
+ className="px-2 py-1 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded text-sm hover:bg-blue-200 dark:hover:bg-blue-800 transition-colors cursor-pointer disabled:cursor-default disabled:opacity-50"
253
+ disabled={!onTaskClick}
254
+ title={onTaskClick ? `Click to view Task ${depId}` : `Task ${depId}`}
255
+ >
256
+ <ArrowRight className="w-3 h-3 inline mr-1" />
257
+ {depId}
258
+ </button>
259
+ ))}
260
+ </div>
261
+ ) : (
262
+ <span className="text-gray-500 dark:text-gray-400 text-sm">No dependencies</span>
263
+ )}
264
+ </div>
265
+ </div>
266
+
267
+ {/* Description */}
268
+ <div className="space-y-2">
269
+ <label className="text-sm font-medium text-gray-700 dark:text-gray-300">Description</label>
270
+ {editMode ? (
271
+ <textarea
272
+ value={editedTask.description || ''}
273
+ onChange={(e) => setEditedTask({ ...editedTask, description: e.target.value })}
274
+ rows={3}
275
+ className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
276
+ placeholder="Task description"
277
+ />
278
+ ) : (
279
+ <p className="text-gray-700 dark:text-gray-300 whitespace-pre-wrap">
280
+ {task.description || 'No description provided'}
281
+ </p>
282
+ )}
283
+ </div>
284
+
285
+ {/* Implementation Details */}
286
+ {task.details && (
287
+ <div className="border border-gray-200 dark:border-gray-700 rounded-lg">
288
+ <button
289
+ onClick={() => setShowDetails(!showDetails)}
290
+ className="w-full flex items-center justify-between p-4 text-left hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
291
+ >
292
+ <span className="text-sm font-medium text-gray-700 dark:text-gray-300">
293
+ Implementation Details
294
+ </span>
295
+ {showDetails ? (
296
+ <ChevronDown className="w-4 h-4 text-gray-500" />
297
+ ) : (
298
+ <ChevronRight className="w-4 h-4 text-gray-500" />
299
+ )}
300
+ </button>
301
+ {showDetails && (
302
+ <div className="border-t border-gray-200 dark:border-gray-700 p-4">
303
+ {editMode ? (
304
+ <textarea
305
+ value={editedTask.details || ''}
306
+ onChange={(e) => setEditedTask({ ...editedTask, details: e.target.value })}
307
+ rows={4}
308
+ className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
309
+ placeholder="Implementation details"
310
+ />
311
+ ) : (
312
+ <div className="bg-gray-50 dark:bg-gray-800 rounded-md p-4">
313
+ <p className="text-gray-700 dark:text-gray-300 whitespace-pre-wrap">
314
+ {task.details}
315
+ </p>
316
+ </div>
317
+ )}
318
+ </div>
319
+ )}
320
+ </div>
321
+ )}
322
+
323
+ {/* Test Strategy */}
324
+ {task.testStrategy && (
325
+ <div className="border border-gray-200 dark:border-gray-700 rounded-lg">
326
+ <button
327
+ onClick={() => setShowTestStrategy(!showTestStrategy)}
328
+ className="w-full flex items-center justify-between p-4 text-left hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
329
+ >
330
+ <span className="text-sm font-medium text-gray-700 dark:text-gray-300">
331
+ Test Strategy
332
+ </span>
333
+ {showTestStrategy ? (
334
+ <ChevronDown className="w-4 h-4 text-gray-500" />
335
+ ) : (
336
+ <ChevronRight className="w-4 h-4 text-gray-500" />
337
+ )}
338
+ </button>
339
+ {showTestStrategy && (
340
+ <div className="border-t border-gray-200 dark:border-gray-700 p-4">
341
+ <div className="bg-blue-50 dark:bg-blue-950 rounded-md p-4">
342
+ <p className="text-gray-700 dark:text-gray-300 whitespace-pre-wrap">
343
+ {task.testStrategy}
344
+ </p>
345
+ </div>
346
+ </div>
347
+ )}
348
+ </div>
349
+ )}
350
+
351
+ {/* Subtasks */}
352
+ {task.subtasks && task.subtasks.length > 0 && (
353
+ <div className="space-y-3">
354
+ <label className="text-sm font-medium text-gray-700 dark:text-gray-300">
355
+ Subtasks ({task.subtasks.length})
356
+ </label>
357
+ <div className="space-y-2">
358
+ {task.subtasks.map(subtask => {
359
+ const subtaskConfig = getStatusConfig(subtask.status);
360
+ const SubtaskIcon = subtaskConfig.icon;
361
+ return (
362
+ <div key={subtask.id} className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-800 rounded-md">
363
+ <SubtaskIcon className={cn('w-4 h-4', subtaskConfig.color)} />
364
+ <div className="flex-1 min-w-0">
365
+ <h4 className="font-medium text-gray-900 dark:text-white truncate">
366
+ {subtask.title}
367
+ </h4>
368
+ {subtask.description && (
369
+ <p className="text-sm text-gray-600 dark:text-gray-400 truncate">
370
+ {subtask.description}
371
+ </p>
372
+ )}
373
+ </div>
374
+ <span className="text-xs text-gray-500 dark:text-gray-400">
375
+ {subtask.id}
376
+ </span>
377
+ </div>
378
+ );
379
+ })}
380
+ </div>
381
+ </div>
382
+ )}
383
+
384
+ </div>
385
+
386
+ {/* Footer */}
387
+ <div className="flex items-center justify-between p-4 md:p-6 border-t border-gray-200 dark:border-gray-700 flex-shrink-0">
388
+ <div className="text-sm text-gray-500 dark:text-gray-400">
389
+ Task ID: {task.id}
390
+ </div>
391
+
392
+ <div className="flex items-center gap-2">
393
+ <button
394
+ onClick={onClose}
395
+ className="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md transition-colors"
396
+ >
397
+ Close
398
+ </button>
399
+ </div>
400
+ </div>
401
+ </div>
402
+ </div>
403
+ );
404
+ };
405
+
406
+ export default TaskDetail;
@@ -0,0 +1,108 @@
1
+ import React from 'react';
2
+ import { CheckCircle, Settings, X, AlertCircle } from 'lucide-react';
3
+ import { cn } from '../lib/utils';
4
+
5
+ /**
6
+ * TaskIndicator Component
7
+ *
8
+ * Displays TaskMaster status for projects in the sidebar with appropriate
9
+ * icons and colors based on the project's TaskMaster configuration state.
10
+ */
11
+ const TaskIndicator = ({
12
+ status = 'not-configured',
13
+ size = 'sm',
14
+ className = '',
15
+ showLabel = false
16
+ }) => {
17
+ const getIndicatorConfig = () => {
18
+ switch (status) {
19
+ case 'fully-configured':
20
+ return {
21
+ icon: CheckCircle,
22
+ color: 'text-green-500 dark:text-green-400',
23
+ bgColor: 'bg-green-50 dark:bg-green-950',
24
+ label: 'TaskMaster Ready',
25
+ title: 'TaskMaster fully configured with MCP server'
26
+ };
27
+
28
+ case 'taskmaster-only':
29
+ return {
30
+ icon: Settings,
31
+ color: 'text-blue-500 dark:text-blue-400',
32
+ bgColor: 'bg-blue-50 dark:bg-blue-950',
33
+ label: 'TaskMaster Init',
34
+ title: 'TaskMaster initialized, MCP server needs setup'
35
+ };
36
+
37
+ case 'mcp-only':
38
+ return {
39
+ icon: AlertCircle,
40
+ color: 'text-amber-500 dark:text-amber-400',
41
+ bgColor: 'bg-amber-50 dark:bg-amber-950',
42
+ label: 'MCP Ready',
43
+ title: 'MCP server configured, TaskMaster needs initialization'
44
+ };
45
+
46
+ case 'not-configured':
47
+ case 'error':
48
+ default:
49
+ return {
50
+ icon: X,
51
+ color: 'text-gray-400 dark:text-gray-500',
52
+ bgColor: 'bg-gray-50 dark:bg-gray-900',
53
+ label: 'No TaskMaster',
54
+ title: 'TaskMaster not configured'
55
+ };
56
+ }
57
+ };
58
+
59
+ const config = getIndicatorConfig();
60
+ const Icon = config.icon;
61
+
62
+ const sizeClasses = {
63
+ xs: 'w-3 h-3',
64
+ sm: 'w-4 h-4',
65
+ md: 'w-5 h-5',
66
+ lg: 'w-6 h-6'
67
+ };
68
+
69
+ const paddingClasses = {
70
+ xs: 'p-0.5',
71
+ sm: 'p-1',
72
+ md: 'p-1.5',
73
+ lg: 'p-2'
74
+ };
75
+
76
+ if (showLabel) {
77
+ return (
78
+ <div
79
+ className={cn(
80
+ 'inline-flex items-center gap-1.5 text-xs rounded-md px-2 py-1 transition-colors',
81
+ config.bgColor,
82
+ config.color,
83
+ className
84
+ )}
85
+ title={config.title}
86
+ >
87
+ <Icon className={sizeClasses[size]} />
88
+ <span className="font-medium">{config.label}</span>
89
+ </div>
90
+ );
91
+ }
92
+
93
+ return (
94
+ <div
95
+ className={cn(
96
+ 'inline-flex items-center justify-center rounded-full transition-colors',
97
+ config.bgColor,
98
+ paddingClasses[size],
99
+ className
100
+ )}
101
+ title={config.title}
102
+ >
103
+ <Icon className={cn(sizeClasses[size], config.color)} />
104
+ </div>
105
+ );
106
+ };
107
+
108
+ export default TaskIndicator;