@mrpatronz/nexusflow 0.2.12 → 0.2.17

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 (233) hide show
  1. package/.github/dependabot.yml +52 -52
  2. package/.github/workflows/dependabot-merge.yml +53 -0
  3. package/.github/workflows/release.yml +112 -100
  4. package/.vscode/launch.json +17 -17
  5. package/.vscode/tasks.json +17 -17
  6. package/GETTING_STARTED.md +114 -109
  7. package/README.md +336 -311
  8. package/desktop/build-installer.js +228 -0
  9. package/desktop/build.js +118 -0
  10. package/desktop/installer/nexusflow.iss +35 -0
  11. package/desktop/neutralino.config.json +39 -0
  12. package/desktop/package-lock.json +1060 -0
  13. package/desktop/package.json +14 -0
  14. package/dist/commands/add-repo.d.ts.map +1 -1
  15. package/dist/commands/add-repo.js +24 -14
  16. package/dist/commands/add-repo.js.map +1 -1
  17. package/dist/commands/commit.js +1 -1
  18. package/dist/commands/commit.js.map +1 -1
  19. package/dist/commands/desktop.d.ts +12 -0
  20. package/dist/commands/desktop.d.ts.map +1 -0
  21. package/dist/commands/desktop.js +119 -0
  22. package/dist/commands/desktop.js.map +1 -0
  23. package/dist/commands/diff.js +1 -1
  24. package/dist/commands/diff.js.map +1 -1
  25. package/dist/commands/doctor.js +1 -1
  26. package/dist/commands/doctor.js.map +1 -1
  27. package/dist/commands/handoff.js +1 -1
  28. package/dist/commands/handoff.js.map +1 -1
  29. package/dist/commands/logs.js +1 -1
  30. package/dist/commands/logs.js.map +1 -1
  31. package/dist/commands/open.d.ts.map +1 -1
  32. package/dist/commands/open.js +12 -7
  33. package/dist/commands/open.js.map +1 -1
  34. package/dist/commands/pack.js +1 -1
  35. package/dist/commands/pack.js.map +1 -1
  36. package/dist/commands/refresh.js +13 -8
  37. package/dist/commands/refresh.js.map +1 -1
  38. package/dist/commands/remove.d.ts.map +1 -1
  39. package/dist/commands/remove.js +12 -7
  40. package/dist/commands/remove.js.map +1 -1
  41. package/dist/commands/start.js +1 -1
  42. package/dist/commands/start.js.map +1 -1
  43. package/dist/commands/status.js +1 -1
  44. package/dist/commands/status.js.map +1 -1
  45. package/dist/commands/stop.js +1 -1
  46. package/dist/commands/stop.js.map +1 -1
  47. package/dist/commands/sync.d.ts.map +1 -1
  48. package/dist/commands/sync.js +14 -11
  49. package/dist/commands/sync.js.map +1 -1
  50. package/dist/commands/tui.d.ts +8 -0
  51. package/dist/commands/tui.d.ts.map +1 -0
  52. package/dist/commands/tui.js +386 -0
  53. package/dist/commands/tui.js.map +1 -0
  54. package/dist/commands/ui.d.ts +3 -1
  55. package/dist/commands/ui.d.ts.map +1 -1
  56. package/dist/commands/ui.js +76 -19
  57. package/dist/commands/ui.js.map +1 -1
  58. package/dist/core/workspace.d.ts +7 -0
  59. package/dist/core/workspace.d.ts.map +1 -1
  60. package/dist/core/workspace.js +45 -1
  61. package/dist/core/workspace.js.map +1 -1
  62. package/dist/generators/base.d.ts.map +1 -1
  63. package/dist/generators/base.js +78 -66
  64. package/dist/generators/base.js.map +1 -1
  65. package/dist/generators/codex.js +11 -11
  66. package/dist/generators/copilot.js +17 -17
  67. package/dist/generators/cursor.js +5 -5
  68. package/dist/generators/index.d.ts.map +1 -1
  69. package/dist/generators/index.js +64 -50
  70. package/dist/generators/index.js.map +1 -1
  71. package/dist/generators/map-generator.js +14 -14
  72. package/dist/generators/skills-generator.js +28 -28
  73. package/dist/gui/app_logo.png +0 -0
  74. package/dist/gui/assets/index-Ct3qq-X4.js +25 -0
  75. package/dist/gui/assets/index-P_ZrgHXg.css +1 -0
  76. package/dist/gui/icons.svg +24 -24
  77. package/dist/gui/index.html +18 -18
  78. package/dist/index.js +43 -0
  79. package/dist/index.js.map +1 -1
  80. package/dist/orchestration/runner.d.ts +5 -6
  81. package/dist/orchestration/runner.d.ts.map +1 -1
  82. package/dist/orchestration/runner.js +105 -102
  83. package/dist/orchestration/runner.js.map +1 -1
  84. package/dist/server.d.ts.map +1 -1
  85. package/dist/server.js +235 -2
  86. package/dist/server.js.map +1 -1
  87. package/dist/server.test.js +78 -0
  88. package/dist/server.test.js.map +1 -1
  89. package/dist/types.d.ts +4 -0
  90. package/dist/types.d.ts.map +1 -1
  91. package/dist/utils/detect-editors.js +1 -1
  92. package/dist/utils/detect-editors.js.map +1 -1
  93. package/dist/utils/detect-editors.test.js +3 -3
  94. package/dist/utils/detect-editors.test.js.map +1 -1
  95. package/dist/utils/git.d.ts +16 -0
  96. package/dist/utils/git.d.ts.map +1 -1
  97. package/dist/utils/git.js +52 -0
  98. package/dist/utils/git.js.map +1 -1
  99. package/dist/utils/multi-git.d.ts +2 -0
  100. package/dist/utils/multi-git.d.ts.map +1 -1
  101. package/dist/utils/multi-git.js +5 -2
  102. package/dist/utils/multi-git.js.map +1 -1
  103. package/dist/utils/prompts.d.ts +1 -0
  104. package/dist/utils/prompts.d.ts.map +1 -1
  105. package/dist/utils/prompts.js +77 -15
  106. package/dist/utils/prompts.js.map +1 -1
  107. package/dist/utils/update-check.d.ts +3 -1
  108. package/dist/utils/update-check.d.ts.map +1 -1
  109. package/dist/utils/update-check.js +23 -6
  110. package/dist/utils/update-check.js.map +1 -1
  111. package/dist/utils/workflows.d.ts +11 -0
  112. package/dist/utils/workflows.d.ts.map +1 -0
  113. package/dist/utils/workflows.js +156 -0
  114. package/dist/utils/workflows.js.map +1 -0
  115. package/dist/utils/workflows.test.d.ts +2 -0
  116. package/dist/utils/workflows.test.d.ts.map +1 -0
  117. package/dist/utils/workflows.test.js +86 -0
  118. package/dist/utils/workflows.test.js.map +1 -0
  119. package/extension/media/icon.svg +1 -0
  120. package/extension/package-lock.json +2673 -2811
  121. package/extension/package.json +108 -61
  122. package/extension/src/extension.ts +631 -168
  123. package/extension/tsconfig.json +21 -19
  124. package/gui/README.md +73 -73
  125. package/gui/e2e/wizard.spec.ts +346 -327
  126. package/gui/eslint.config.js +26 -22
  127. package/gui/index.html +17 -17
  128. package/gui/package-lock.json +3079 -3118
  129. package/gui/package.json +36 -36
  130. package/gui/playwright.config.ts +41 -41
  131. package/gui/public/app_logo.png +0 -0
  132. package/gui/public/icons.svg +24 -24
  133. package/gui/src/App.css +1 -1
  134. package/gui/src/App.tsx +3407 -2498
  135. package/gui/src/assets/vite.svg +1 -1
  136. package/gui/src/features/changes/ChangesViewer.tsx +388 -212
  137. package/gui/src/features/knowledge/KnowledgeBase.tsx +84 -84
  138. package/gui/src/features/onboarding/OnboardingWizard.tsx +230 -230
  139. package/gui/src/features/plan/ImplementationPlan.tsx +32 -32
  140. package/gui/src/features/services/ServiceConsole.tsx +250 -159
  141. package/gui/src/features/sessions/SessionHistory.tsx +195 -195
  142. package/gui/src/features/workspace/WorkspaceList.tsx +666 -514
  143. package/gui/src/index.css +152 -17
  144. package/gui/src/main.tsx +94 -10
  145. package/gui/src/types.ts +62 -62
  146. package/gui/tsconfig.app.json +25 -25
  147. package/gui/tsconfig.json +7 -7
  148. package/gui/tsconfig.node.json +24 -24
  149. package/gui/vite.config.ts +12 -12
  150. package/package.json +56 -55
  151. package/resources/workflows/plan-implement-review.md +8 -0
  152. package/resources/workflows/research-verify.md +6 -0
  153. package/resources/workflows/solo-developer.md +3 -0
  154. package/scripts/simulate-workspaces.ts +55 -55
  155. package/src/analyzers/detect-apis.ts +290 -290
  156. package/src/analyzers/detect-deps.ts +315 -315
  157. package/src/analyzers/detect-existing.ts +74 -74
  158. package/src/analyzers/detect-ports.ts +110 -110
  159. package/src/analyzers/index.ts +97 -97
  160. package/src/analyzers/messaging-analyzer.ts +254 -254
  161. package/src/analyzers/readme-summarizer.ts +102 -102
  162. package/src/analyzers/run-analyzer.ts +269 -269
  163. package/src/analyzers/tech-stack.ts +283 -283
  164. package/src/commands/add-repo.ts +157 -141
  165. package/src/commands/commands.test.ts +155 -155
  166. package/src/commands/commit.ts +131 -131
  167. package/src/commands/create.ts +207 -207
  168. package/src/commands/desktop.ts +128 -0
  169. package/src/commands/diff.ts +125 -125
  170. package/src/commands/doctor.ts +357 -357
  171. package/src/commands/handoff.ts +266 -266
  172. package/src/commands/init.ts +134 -134
  173. package/src/commands/list.ts +46 -46
  174. package/src/commands/logs.ts +63 -63
  175. package/src/commands/mcp.ts +92 -92
  176. package/src/commands/open.ts +129 -121
  177. package/src/commands/pack.ts +73 -73
  178. package/src/commands/refresh.ts +147 -139
  179. package/src/commands/remove.ts +98 -90
  180. package/src/commands/start.ts +117 -117
  181. package/src/commands/status.ts +54 -54
  182. package/src/commands/stop.ts +55 -55
  183. package/src/commands/sync.ts +151 -145
  184. package/src/commands/tui.ts +424 -0
  185. package/src/commands/ui.ts +111 -47
  186. package/src/core/config.test.ts +96 -96
  187. package/src/core/config.ts +115 -115
  188. package/src/core/graph.ts +344 -344
  189. package/src/core/packer.test.ts +99 -99
  190. package/src/core/packer.ts +107 -107
  191. package/src/core/scanner.test.ts +61 -61
  192. package/src/core/scanner.ts +91 -91
  193. package/src/core/workspace.ts +380 -333
  194. package/src/core/worktree.ts +120 -120
  195. package/src/generators/antigravity.ts +32 -32
  196. package/src/generators/base.ts +240 -227
  197. package/src/generators/claude.ts +34 -34
  198. package/src/generators/codex.ts +48 -48
  199. package/src/generators/copilot.ts +56 -56
  200. package/src/generators/cursor.ts +44 -44
  201. package/src/generators/index.ts +250 -230
  202. package/src/generators/map-generator.test.ts +159 -159
  203. package/src/generators/map-generator.ts +542 -542
  204. package/src/generators/plan-generator.ts +481 -481
  205. package/src/generators/skills-generator.ts +259 -259
  206. package/src/index.ts +426 -382
  207. package/src/mcp/server.ts +294 -294
  208. package/src/orchestration/detect.ts +313 -313
  209. package/src/orchestration/index.ts +7 -7
  210. package/src/orchestration/runner.ts +283 -279
  211. package/src/server.test.ts +555 -464
  212. package/src/server.ts +1308 -1047
  213. package/src/types.ts +408 -403
  214. package/src/utils/detect-ai.test.ts +44 -44
  215. package/src/utils/detect-ai.ts +84 -84
  216. package/src/utils/detect-editors.test.ts +48 -48
  217. package/src/utils/detect-editors.ts +57 -57
  218. package/src/utils/git.test.ts +74 -74
  219. package/src/utils/git.ts +117 -61
  220. package/src/utils/local-ai.test.ts +130 -130
  221. package/src/utils/local-ai.ts +111 -111
  222. package/src/utils/multi-git.ts +313 -306
  223. package/src/utils/prompts.ts +209 -135
  224. package/src/utils/session-finder.ts +483 -483
  225. package/src/utils/system-scanner.test.ts +89 -89
  226. package/src/utils/system-scanner.ts +96 -96
  227. package/src/utils/update-check.ts +309 -275
  228. package/src/utils/workflows.test.ts +114 -0
  229. package/src/utils/workflows.ts +178 -0
  230. package/tsconfig.json +19 -19
  231. package/vitest.config.ts +14 -14
  232. package/dist/gui/assets/index-Ca2VRMrQ.js +0 -22
  233. package/dist/gui/assets/index-fj0RJiOb.css +0 -2
@@ -1,514 +1,666 @@
1
- import React from 'react';
2
- import { Play, RefreshCw, FolderGit2, Sparkles, ExternalLink, Trash2 } from 'lucide-react';
3
- import type { Feature, RunningService, ServiceConfig, OrchestrationDetection, RepoInfo } from '../../types.js';
4
-
5
- // Feature subcomponents
6
- import { SessionHistory } from '../sessions/SessionHistory.js';
7
- import { ServiceConsole } from '../services/ServiceConsole.js';
8
- import { ChangesViewer } from '../changes/ChangesViewer.js';
9
- import { KnowledgeBase } from '../knowledge/KnowledgeBase.js';
10
- import { ImplementationPlan } from '../plan/ImplementationPlan.js';
11
-
12
- interface WorkspaceListProps {
13
- workspacesLoading: boolean;
14
- workspaces: Feature[];
15
- activeWsId: string | null;
16
- setActiveWsId: (id: string | null) => void;
17
- resumingWs: string | null;
18
- handleResumeSession: (ws: Feature, sessionId?: string, assistant?: string) => Promise<void>;
19
- handleCopyPrompt: (ws: Feature) => void;
20
- handleOpenInEditor: (workspacePath: string) => Promise<void>;
21
- fetchWorkspaces: () => Promise<void>;
22
-
23
- // Deletion and repo addition props
24
- repos: RepoInfo[];
25
- deleteWsLoading: string | null;
26
- addRepoLoading: boolean;
27
- handleDeleteWorkspace: (wsName: string) => Promise<void>;
28
- handleAddRepo: (wsName: string, repoPath: string) => Promise<void>;
29
-
30
- // ServiceConsole props
31
- services: ServiceConfig[];
32
- runningServices: RunningService[];
33
- selectedLogService: string | null;
34
- serviceLogs: string;
35
- logsEndRef: React.RefObject<HTMLDivElement | null>;
36
- setSelectedLogService: (val: string | null) => void;
37
- handleStartServices: (wsId: string) => Promise<void>;
38
- handleStopServices: (wsId: string) => Promise<void>;
39
- orchTools: OrchestrationDetection[];
40
- servicesLoading: boolean;
41
-
42
-
43
- // ChangesViewer props
44
- gitChanges: any[];
45
- gitChangesLoading: boolean;
46
- syncLoading: boolean;
47
- syncResults: any[] | null;
48
- commitMessage: string;
49
- showCommitModal: boolean;
50
- commitLoading: boolean;
51
- commitResults: any[] | null;
52
- setSyncResults: (val: any[] | null) => void;
53
- setCommitResults: (val: any[] | null) => void;
54
- setCommitMessage: (val: string) => void;
55
- setShowCommitModal: (val: boolean) => void;
56
- fetchGitChanges: (wsId: string) => Promise<void>;
57
- handleSyncAll: (wsId: string) => Promise<void>;
58
- handleCommitAll: (wsId: string) => Promise<void>;
59
-
60
- // KnowledgeBase props
61
- knowledgeContent: string;
62
- knowledgeLoading: boolean;
63
- isEditingKnowledge: boolean;
64
- editedKnowledge: string;
65
- saveKnowledgeLoading: boolean;
66
- setEditedKnowledge: (val: string) => void;
67
- setIsEditingKnowledge: (val: boolean) => void;
68
- handleSaveKnowledge: (wsId: string) => Promise<void>;
69
-
70
- // ImplementationPlan props
71
- planContent: string;
72
- planLoading: boolean;
73
-
74
- // SessionHistory props
75
- sessions: any[];
76
- sessionsLoading: boolean;
77
- activeSession: any | null;
78
- transcript: any[];
79
- transcriptLoading: boolean;
80
- setActiveSession: (val: any | null) => void;
81
- setTranscript: (val: any[]) => void;
82
- fetchSessionTranscript: (assistant: string, sessionId: string) => Promise<void>;
83
-
84
- // Navigation tab states
85
- subTab: any;
86
- setSubTab: (tab: any) => void;
87
- }
88
-
89
- const parseInlineStyles = (text: string): React.ReactNode => {
90
- const tokenRegex = /(\*\*.*?\*\*|`.*?`|\*.*?\*)/g;
91
- const parts = text.split(tokenRegex);
92
-
93
- return parts.map((part, index) => {
94
- if (part.startsWith('**') && part.endsWith('**')) {
95
- return <strong key={index} className="font-bold text-gray-200">{part.slice(2, -2)}</strong>;
96
- }
97
- if (part.startsWith('`') && part.endsWith('`')) {
98
- return <code key={index} className="px-1.5 py-0.5 bg-gray-900 border border-gray-800 text-indigo-300 rounded font-mono text-[10px]">{part.slice(1, -1)}</code>;
99
- }
100
- if (part.startsWith('*') && part.endsWith('*')) {
101
- return <em key={index} className="italic text-gray-300">{part.slice(1, -1)}</em>;
102
- }
103
- return part;
104
- });
105
- };
106
-
107
- const renderFormattedDescription = (text: string) => {
108
- if (!text) return null;
109
-
110
- const lines = text.split('\n');
111
- const elements: React.ReactNode[] = [];
112
- let currentList: { type: 'ul' | 'ol'; items: string[] } | null = null;
113
-
114
- const flushList = (key: number) => {
115
- if (currentList) {
116
- const ListTag = currentList.type;
117
- elements.push(
118
- <ListTag key={`list-${key}`} className={currentList.type === 'ul' ? 'list-disc pl-5 my-1.5 space-y-1' : 'list-decimal pl-5 my-1.5 space-y-1'}>
119
- {currentList.items.map((item, idx) => (
120
- <li key={`item-${idx}`} className="leading-relaxed">{parseInlineStyles(item)}</li>
121
- ))}
122
- </ListTag>
123
- );
124
- currentList = null;
125
- }
126
- };
127
-
128
- lines.forEach((line, idx) => {
129
- const trimmed = line.trim();
130
-
131
- // Check for bullet list item
132
- const bulletMatch = line.match(/^(\s*)[-*+]\s+(.*)/);
133
- if (bulletMatch) {
134
- if (!currentList || currentList.type !== 'ul') {
135
- flushList(idx);
136
- currentList = { type: 'ul', items: [] };
137
- }
138
- currentList.items.push(bulletMatch[2]);
139
- return;
140
- }
141
-
142
- // Check for numbered list item
143
- const numberMatch = line.match(/^(\s*)\d+\.\s+(.*)/);
144
- if (numberMatch) {
145
- if (!currentList || currentList.type !== 'ol') {
146
- flushList(idx);
147
- currentList = { type: 'ol', items: [] };
148
- }
149
- currentList.items.push(numberMatch[2]);
150
- return;
151
- }
152
-
153
- // If not a list item, flush any active list first
154
- flushList(idx);
155
-
156
- if (trimmed === '') {
157
- elements.push(<div key={`empty-${idx}`} className="h-2" />);
158
- } else {
159
- elements.push(
160
- <div key={`line-${idx}`} className="leading-relaxed">
161
- {parseInlineStyles(line)}
162
- </div>
163
- );
164
- }
165
- });
166
-
167
- flushList(lines.length);
168
- return <div className="space-y-1">{elements}</div>;
169
- };
170
-
171
- export const WorkspaceList: React.FC<WorkspaceListProps> = ({
172
- workspacesLoading,
173
- workspaces,
174
- activeWsId,
175
- setActiveWsId,
176
- resumingWs,
177
- handleResumeSession,
178
- handleCopyPrompt,
179
- handleOpenInEditor,
180
- fetchWorkspaces,
181
- repos,
182
- deleteWsLoading,
183
- addRepoLoading,
184
- handleDeleteWorkspace,
185
- handleAddRepo,
186
-
187
- // ServiceConsole
188
- services,
189
- runningServices,
190
- selectedLogService,
191
- serviceLogs,
192
- logsEndRef,
193
- setSelectedLogService,
194
- handleStartServices,
195
- handleStopServices,
196
- orchTools,
197
- servicesLoading,
198
-
199
-
200
- // ChangesViewer
201
- gitChanges,
202
- gitChangesLoading,
203
- syncLoading,
204
- syncResults,
205
- commitMessage,
206
- showCommitModal,
207
- commitLoading,
208
- commitResults,
209
- setSyncResults,
210
- setCommitResults,
211
- setCommitMessage,
212
- setShowCommitModal,
213
- fetchGitChanges,
214
- handleSyncAll,
215
- handleCommitAll,
216
-
217
- // KnowledgeBase
218
- knowledgeContent,
219
- knowledgeLoading,
220
- isEditingKnowledge,
221
- editedKnowledge,
222
- saveKnowledgeLoading,
223
- setEditedKnowledge,
224
- setIsEditingKnowledge,
225
- handleSaveKnowledge,
226
-
227
- // ImplementationPlan
228
- planContent,
229
- planLoading,
230
-
231
- // SessionHistory
232
- sessions,
233
- sessionsLoading,
234
- activeSession,
235
- transcript,
236
- transcriptLoading,
237
- setActiveSession,
238
- setTranscript,
239
- fetchSessionTranscript,
240
-
241
- // Tabs
242
- subTab,
243
- setSubTab,
244
- }) => {
245
- return (
246
- <div className="max-w-5xl mx-auto">
247
- <header className="flex justify-between items-center mb-8">
248
- <div>
249
- <h1 className="text-3xl font-extrabold tracking-tight text-white mb-2">Active Workspaces</h1>
250
- <p className="text-sm text-gray-400">Monitor running servers, libraries, and processes on your development branches.</p>
251
- </div>
252
- <button
253
- className="inline-flex items-center justify-center gap-2 px-4 py-2 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 rounded-lg text-xs font-semibold transition-all cursor-pointer"
254
- onClick={fetchWorkspaces}
255
- disabled={workspacesLoading}
256
- >
257
- <RefreshCw size={14} className={workspacesLoading ? 'animate-spin text-indigo-400' : ''} /> Refresh
258
- </button>
259
- </header>
260
-
261
- {workspacesLoading ? (
262
- <div className="flex flex-col items-center py-40 gap-4 text-gray-400">
263
- <RefreshCw className="animate-spin text-indigo-400" size={32} />
264
- <span className="text-sm">Fetching workspace details...</span>
265
- </div>
266
- ) : workspaces.length === 0 ? (
267
- <div className="flex flex-col items-center justify-center py-24 bg-[#111827]/20 border border-gray-800/80 rounded-xl text-center">
268
- <FolderGit2 size={44} className="text-gray-600 mb-4" />
269
- <h3 className="text-lg font-bold text-white">No Active Workspaces</h3>
270
- <p className="text-xs text-gray-505 max-w-sm mt-1">
271
- No feature workspaces were detected in your development folder. Create a workspace to start.
272
- </p>
273
- </div>
274
- ) : (
275
- <div className="flex flex-col gap-6">
276
- {workspaces.map((ws) => {
277
- const isExpanded = activeWsId === ws.branchName;
278
- return (
279
- <div key={ws.id} className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-6 shadow-md">
280
- <div className="flex justify-between items-start gap-4 mb-4">
281
- <div>
282
- <h3 className="text-lg font-bold text-white hover:text-indigo-400 transition-colors">{ws.branchName}</h3>
283
- <div className="flex items-center gap-3 text-xs text-gray-500 mt-1">
284
- <span>Created: {new Date(ws.createdAt).toLocaleDateString()}</span>
285
- <span className="h-1 w-1 rounded-full bg-gray-700"></span>
286
- <span>{ws.repos.length} repos</span>
287
- </div>
288
- </div>
289
- <div className="flex gap-2">
290
- <button
291
- className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 bg-emerald-650 hover:bg-emerald-600 text-white rounded-lg text-xs font-semibold transition-all cursor-pointer shadow-md shadow-emerald-600/10 disabled:opacity-40 disabled:cursor-not-allowed"
292
- onClick={() => handleResumeSession(ws)}
293
- disabled={resumingWs === ws.branchName}
294
- >
295
- <Play size={12} className={resumingWs === ws.branchName ? 'animate-spin' : ''} />
296
- {resumingWs === ws.branchName ? 'Resuming...' : 'Resume Session'}
297
- </button>
298
- <button
299
- className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 rounded-lg text-xs font-semibold transition-all cursor-pointer"
300
- onClick={() => handleCopyPrompt(ws)}
301
- >
302
- <Sparkles size={12} className="text-cyan-400" /> Copy Prompt
303
- </button>
304
- <button
305
- className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 rounded-lg text-xs font-semibold transition-all cursor-pointer"
306
- onClick={() => handleOpenInEditor(ws.workspacePath)}
307
- >
308
- <ExternalLink size={12} /> Open
309
- </button>
310
- <button
311
- className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 bg-indigo-500 hover:bg-indigo-600 text-white rounded-lg text-xs font-semibold transition-all cursor-pointer shadow-md shadow-indigo-500/10"
312
- onClick={() => {
313
- if (isExpanded) {
314
- setActiveWsId(null);
315
- } else {
316
- setActiveWsId(ws.branchName);
317
- }
318
- }}
319
- >
320
- {isExpanded ? 'Hide Runner' : 'Orchestrate'}
321
- </button>
322
- <button
323
- className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 bg-red-950/40 border border-red-900/60 hover:bg-red-900/60 hover:border-red-800 rounded-lg text-xs font-semibold text-red-200 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed animate-fade-in"
324
- onClick={() => handleDeleteWorkspace(ws.branchName)}
325
- disabled={deleteWsLoading === ws.branchName}
326
- >
327
- <Trash2 size={12} className={deleteWsLoading === ws.branchName ? 'animate-spin' : ''} />
328
- {deleteWsLoading === ws.branchName ? 'Deleting...' : 'Delete'}
329
- </button>
330
- </div>
331
- </div>
332
-
333
- <div className="text-xs text-gray-400 bg-gray-950/40 border-l-2 border-indigo-500 rounded-r-lg px-4 py-3 mb-4 font-medium italic">
334
- {renderFormattedDescription(ws.description)}
335
- </div>
336
-
337
- <div className="flex items-center gap-3 mb-4">
338
- <div className="flex flex-wrap gap-2">
339
- {ws.repos.map((repoPath) => (
340
- <span key={repoPath} className="text-[10px] px-2.5 py-1 bg-gray-900 border border-gray-800 text-gray-300 rounded-md font-semibold">
341
- {repoPath.split(/[\\/]/).pop()}
342
- </span>
343
- ))}
344
- </div>
345
-
346
- {repos.filter((r) => !ws.repos.includes(r.path)).length > 0 && (
347
- <div className="flex items-center gap-1.5 ml-auto">
348
- <select
349
- id={`add-repo-select-feature-${ws.branchName}`}
350
- className="bg-gray-900 border border-gray-800 text-gray-300 rounded-md text-[10px] px-2 py-1 font-semibold outline-none focus:border-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"
351
- defaultValue=""
352
- onChange={async (e) => {
353
- const val = e.target.value;
354
- if (val) {
355
- if (window.confirm(`Are you sure you want to add repository "${val.split(/[\\/]/).pop()}" to this workspace?\nThis will create a new git worktree and re-run analysis.`)) {
356
- await handleAddRepo(ws.branchName, val);
357
- }
358
- e.target.value = "";
359
- }
360
- }}
361
- disabled={addRepoLoading}
362
- >
363
- <option value="" disabled>+ Add Repository</option>
364
- {repos
365
- .filter((r) => !ws.repos.includes(r.path))
366
- .map((r) => (
367
- <option key={r.path} value={r.path}>{r.name}</option>
368
- ))
369
- }
370
- </select>
371
- </div>
372
- )}
373
- </div>
374
-
375
- {/* Expansion: Process management console */}
376
- {isExpanded && (
377
- <div className="mt-6 pt-6 border-t border-gray-800/80">
378
- {/* Sub-tab Navigation */}
379
- <div className="flex border-b border-gray-800/80 mb-6 gap-2">
380
- <button
381
- className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
382
- subTab === 'sessions'
383
- ? 'border-indigo-500 text-white'
384
- : 'border-transparent text-gray-500 hover:text-gray-300'
385
- }`}
386
- onClick={() => setSubTab('sessions')}
387
- >
388
- AI Session History
389
- </button>
390
- <button
391
- className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
392
- subTab === 'services'
393
- ? 'border-indigo-500 text-white'
394
- : 'border-transparent text-gray-500 hover:text-gray-300'
395
- }`}
396
- onClick={() => setSubTab('services')}
397
- >
398
- Orchestrated Services
399
- </button>
400
- <button
401
- className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
402
- subTab === 'changes'
403
- ? 'border-indigo-500 text-white'
404
- : 'border-transparent text-gray-500 hover:text-gray-300'
405
- }`}
406
- onClick={() => setSubTab('changes')}
407
- >
408
- Active Changes (AI Diffs)
409
- </button>
410
- <button
411
- className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
412
- subTab === 'knowledge'
413
- ? 'border-indigo-500 text-white'
414
- : 'border-transparent text-gray-500 hover:text-gray-300'
415
- }`}
416
- onClick={() => setSubTab('knowledge')}
417
- >
418
- Knowledge Base
419
- </button>
420
- <button
421
- className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
422
- subTab === 'plan'
423
- ? 'border-indigo-500 text-white'
424
- : 'border-transparent text-gray-500 hover:text-gray-300'
425
- }`}
426
- onClick={() => setSubTab('plan')}
427
- >
428
- Implementation Plan
429
- </button>
430
- </div>
431
-
432
- {subTab === 'sessions' && (
433
- <SessionHistory
434
- ws={ws}
435
- sessions={sessions}
436
- sessionsLoading={sessionsLoading}
437
- activeSession={activeSession}
438
- transcript={transcript}
439
- transcriptLoading={transcriptLoading}
440
- workspaces={workspaces}
441
- setActiveSession={setActiveSession}
442
- setTranscript={setTranscript}
443
- fetchSessionTranscript={fetchSessionTranscript}
444
- handleResumeSession={handleResumeSession}
445
- />
446
- )}
447
-
448
- {subTab === 'services' && (
449
- <ServiceConsole
450
- ws={ws}
451
- services={services}
452
- runningServices={runningServices}
453
- selectedLogService={selectedLogService}
454
- serviceLogs={serviceLogs}
455
- logsEndRef={logsEndRef}
456
- setSelectedLogService={setSelectedLogService}
457
- handleStartServices={handleStartServices}
458
- handleStopServices={handleStopServices}
459
- orchTools={orchTools}
460
- servicesLoading={servicesLoading}
461
- />
462
- )}
463
-
464
- {subTab === 'changes' && (
465
- <ChangesViewer
466
- ws={ws}
467
- gitChanges={gitChanges}
468
- gitChangesLoading={gitChangesLoading}
469
- syncLoading={syncLoading}
470
- syncResults={syncResults}
471
- commitMessage={commitMessage}
472
- showCommitModal={showCommitModal}
473
- commitLoading={commitLoading}
474
- commitResults={commitResults}
475
- setSyncResults={setSyncResults}
476
- setCommitResults={setCommitResults}
477
- setCommitMessage={setCommitMessage}
478
- setShowCommitModal={setShowCommitModal}
479
- fetchGitChanges={fetchGitChanges}
480
- handleSyncAll={handleSyncAll}
481
- handleCommitAll={handleCommitAll}
482
- />
483
- )}
484
-
485
- {subTab === 'knowledge' && (
486
- <KnowledgeBase
487
- ws={ws}
488
- knowledgeContent={knowledgeContent}
489
- knowledgeLoading={knowledgeLoading}
490
- isEditingKnowledge={isEditingKnowledge}
491
- editedKnowledge={editedKnowledge}
492
- saveKnowledgeLoading={saveKnowledgeLoading}
493
- setEditedKnowledge={setEditedKnowledge}
494
- setIsEditingKnowledge={setIsEditingKnowledge}
495
- handleSaveKnowledge={handleSaveKnowledge}
496
- />
497
- )}
498
-
499
- {subTab === 'plan' && (
500
- <ImplementationPlan
501
- planContent={planContent}
502
- planLoading={planLoading}
503
- />
504
- )}
505
- </div>
506
- )}
507
- </div>
508
- );
509
- })}
510
- </div>
511
- )}
512
- </div>
513
- );
514
- };
1
+ import React from 'react';
2
+ import { Play, RefreshCw, FolderGit2, Sparkles, ExternalLink, Trash2, Terminal, Cpu, Layers, Copy, Check } from 'lucide-react';
3
+ import type { Feature, RunningService, ServiceConfig, OrchestrationDetection, RepoInfo, DetectedAI } from '../../types.js';
4
+
5
+ // Feature subcomponents
6
+ import { SessionHistory } from '../sessions/SessionHistory.js';
7
+ import { ServiceConsole } from '../services/ServiceConsole.js';
8
+ import { ChangesViewer } from '../changes/ChangesViewer.js';
9
+ import { KnowledgeBase } from '../knowledge/KnowledgeBase.js';
10
+ import { ImplementationPlan } from '../plan/ImplementationPlan.js';
11
+
12
+ interface WorkspaceListProps {
13
+ workspacesLoading: boolean;
14
+ workspaces: Feature[];
15
+ activeWsId: string | null;
16
+ setActiveWsId: (id: string | null) => void;
17
+ resumingWs: string | null;
18
+ handleResumeSession: (ws: Feature, sessionId?: string, assistant?: string) => Promise<void>;
19
+ handleCopyPrompt: (ws: Feature) => void;
20
+ handleOpenInEditor: (workspacePath: string) => Promise<void>;
21
+ fetchWorkspaces: () => Promise<void>;
22
+
23
+ // Deletion and repo addition props
24
+ repos: RepoInfo[];
25
+ deleteWsLoading: string | null;
26
+ addRepoLoading: boolean;
27
+ handleDeleteWorkspace: (wsName: string) => Promise<void>;
28
+ handleAddRepo: (wsName: string, repoPath: string) => Promise<void>;
29
+
30
+ // ServiceConsole props
31
+ services: ServiceConfig[];
32
+ runningServices: RunningService[];
33
+ selectedLogService: string | null;
34
+ serviceLogs: string;
35
+ logsEndRef: React.RefObject<HTMLDivElement | null>;
36
+ setSelectedLogService: (val: string | null) => void;
37
+ handleStartServices: (wsId: string) => Promise<void>;
38
+ handleStopServices: (wsId: string) => Promise<void>;
39
+ orchTools: OrchestrationDetection[];
40
+ servicesLoading: boolean;
41
+
42
+
43
+ // ChangesViewer props
44
+ gitChanges: any[];
45
+ gitChangesLoading: boolean;
46
+ syncLoading: boolean;
47
+ syncResults: any[] | null;
48
+ commitMessage: string;
49
+ showCommitModal: boolean;
50
+ commitLoading: boolean;
51
+ commitResults: any[] | null;
52
+ setSyncResults: (val: any[] | null) => void;
53
+ setCommitResults: (val: any[] | null) => void;
54
+ setCommitMessage: (val: string) => void;
55
+ setShowCommitModal: (val: boolean) => void;
56
+ fetchGitChanges: (wsId: string) => Promise<void>;
57
+ handleSyncAll: (wsId: string) => Promise<void>;
58
+ handleCommitAll: (wsId: string) => Promise<void>;
59
+
60
+ // KnowledgeBase props
61
+ knowledgeContent: string;
62
+ knowledgeLoading: boolean;
63
+ isEditingKnowledge: boolean;
64
+ editedKnowledge: string;
65
+ saveKnowledgeLoading: boolean;
66
+ setEditedKnowledge: (val: string) => void;
67
+ setIsEditingKnowledge: (val: boolean) => void;
68
+ handleSaveKnowledge: (wsId: string) => Promise<void>;
69
+
70
+ // ImplementationPlan props
71
+ planContent: string;
72
+ planLoading: boolean;
73
+
74
+ // SessionHistory props
75
+ sessions: any[];
76
+ sessionsLoading: boolean;
77
+ activeSession: any | null;
78
+ transcript: any[];
79
+ transcriptLoading: boolean;
80
+ setActiveSession: (val: any | null) => void;
81
+ setTranscript: (val: any[]) => void;
82
+ fetchSessionTranscript: (assistant: string, sessionId: string) => Promise<void>;
83
+
84
+ // Navigation tab states
85
+ subTab: 'overview' | 'sessions' | 'services' | 'changes' | 'knowledge' | 'plan';
86
+ setSubTab: (tab: 'overview' | 'sessions' | 'services' | 'changes' | 'knowledge' | 'plan') => void;
87
+ aiAssistants: DetectedAI[];
88
+ }
89
+
90
+ const parseInlineStyles = (text: string): React.ReactNode => {
91
+ const tokenRegex = /(\*\*.*?\*\*|`.*?`|\*.*?\*)/g;
92
+ const parts = text.split(tokenRegex);
93
+
94
+ return parts.map((part, index) => {
95
+ if (part.startsWith('**') && part.endsWith('**')) {
96
+ return <strong key={index} className="font-bold text-gray-200">{part.slice(2, -2)}</strong>;
97
+ }
98
+ if (part.startsWith('`') && part.endsWith('`')) {
99
+ return <code key={index} className="px-1.5 py-0.5 bg-gray-900 border border-gray-800 text-cyan-400 rounded font-mono text-[10px]">{part.slice(1, -1)}</code>;
100
+ }
101
+ if (part.startsWith('*') && part.endsWith('*')) {
102
+ return <em key={index} className="italic text-gray-300">{part.slice(1, -1)}</em>;
103
+ }
104
+ return part;
105
+ });
106
+ };
107
+
108
+ const renderFormattedDescription = (text: string) => {
109
+ if (!text) return null;
110
+
111
+ const lines = text.split('\n');
112
+ const elements: React.ReactNode[] = [];
113
+ let currentList: { type: 'ul' | 'ol'; items: string[] } | null = null;
114
+
115
+ const flushList = (key: number) => {
116
+ if (currentList) {
117
+ const ListTag = currentList.type;
118
+ elements.push(
119
+ <ListTag key={`list-${key}`} className={currentList.type === 'ul' ? 'list-disc pl-5 my-1.5 space-y-1' : 'list-decimal pl-5 my-1.5 space-y-1'}>
120
+ {currentList.items.map((item, idx) => (
121
+ <li key={`item-${idx}`} className="leading-relaxed">{parseInlineStyles(item)}</li>
122
+ ))}
123
+ </ListTag>
124
+ );
125
+ currentList = null;
126
+ }
127
+ };
128
+
129
+ lines.forEach((line, idx) => {
130
+ const trimmed = line.trim();
131
+
132
+ // Check for bullet list item
133
+ const bulletMatch = line.match(/^(\s*)[-*+]\s+(.*)/);
134
+ if (bulletMatch) {
135
+ if (!currentList || currentList.type !== 'ul') {
136
+ flushList(idx);
137
+ currentList = { type: 'ul', items: [] };
138
+ }
139
+ currentList.items.push(bulletMatch[2]);
140
+ return;
141
+ }
142
+
143
+ // Check for numbered list item
144
+ const numberMatch = line.match(/^(\s*)\d+\.\s+(.*)/);
145
+ if (numberMatch) {
146
+ if (!currentList || currentList.type !== 'ol') {
147
+ flushList(idx);
148
+ currentList = { type: 'ol', items: [] };
149
+ }
150
+ currentList.items.push(numberMatch[2]);
151
+ return;
152
+ }
153
+
154
+ // If not a list item, flush any active list first
155
+ flushList(idx);
156
+
157
+ if (trimmed === '') {
158
+ elements.push(<div key={`empty-${idx}`} className="h-2" />);
159
+ } else {
160
+ elements.push(
161
+ <div key={`line-${idx}`} className="leading-relaxed">
162
+ {parseInlineStyles(line)}
163
+ </div>
164
+ );
165
+ }
166
+ });
167
+
168
+ flushList(lines.length);
169
+ return <div className="space-y-1">{elements}</div>;
170
+ };
171
+
172
+ export const WorkspaceList: React.FC<WorkspaceListProps> = ({
173
+ workspacesLoading,
174
+ workspaces,
175
+ activeWsId,
176
+ setActiveWsId,
177
+ resumingWs,
178
+ handleResumeSession,
179
+ handleCopyPrompt,
180
+ handleOpenInEditor,
181
+ fetchWorkspaces,
182
+ repos,
183
+ deleteWsLoading,
184
+ addRepoLoading,
185
+ handleDeleteWorkspace,
186
+ handleAddRepo,
187
+
188
+ // ServiceConsole
189
+ services,
190
+ runningServices,
191
+ selectedLogService,
192
+ serviceLogs,
193
+ logsEndRef,
194
+ setSelectedLogService,
195
+ handleStartServices,
196
+ handleStopServices,
197
+ orchTools,
198
+ servicesLoading,
199
+
200
+
201
+ // ChangesViewer
202
+ gitChanges,
203
+ gitChangesLoading,
204
+ syncLoading,
205
+ syncResults,
206
+ commitMessage,
207
+ showCommitModal,
208
+ commitLoading,
209
+ commitResults,
210
+ setSyncResults,
211
+ setCommitResults,
212
+ setCommitMessage,
213
+ setShowCommitModal,
214
+ fetchGitChanges,
215
+ handleSyncAll,
216
+ handleCommitAll,
217
+
218
+ // KnowledgeBase
219
+ knowledgeContent,
220
+ knowledgeLoading,
221
+ isEditingKnowledge,
222
+ editedKnowledge,
223
+ saveKnowledgeLoading,
224
+ setEditedKnowledge,
225
+ setIsEditingKnowledge,
226
+ handleSaveKnowledge,
227
+
228
+ // ImplementationPlan
229
+ planContent,
230
+ planLoading,
231
+
232
+ // SessionHistory
233
+ sessions,
234
+ sessionsLoading,
235
+ activeSession,
236
+ transcript,
237
+ transcriptLoading,
238
+ setActiveSession,
239
+ setTranscript,
240
+ fetchSessionTranscript,
241
+
242
+ // Tabs
243
+ subTab,
244
+ setSubTab,
245
+ aiAssistants,
246
+ }) => {
247
+ const [copiedText, setCopiedText] = React.useState<string | null>(null);
248
+ const handleCopy = (text: string) => {
249
+ navigator.clipboard.writeText(text);
250
+ setCopiedText(text);
251
+ setTimeout(() => setCopiedText(null), 2000);
252
+ };
253
+
254
+ return (
255
+ <div className="max-w-5xl mx-auto">
256
+ <header className="flex justify-between items-center mb-8">
257
+ <div>
258
+ <h1 className="text-3xl font-extrabold tracking-tight text-white mb-2">Active Workspaces</h1>
259
+ <p className="text-sm text-gray-400">Monitor running servers, libraries, and processes on your development branches.</p>
260
+ </div>
261
+ <button
262
+ className="inline-flex items-center justify-center gap-2 px-4 py-2 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 rounded-lg text-xs font-semibold transition-all cursor-pointer"
263
+ onClick={fetchWorkspaces}
264
+ disabled={workspacesLoading}
265
+ >
266
+ <RefreshCw size={14} className={workspacesLoading ? 'animate-spin text-cyan-400' : ''} /> Refresh
267
+ </button>
268
+ </header>
269
+
270
+ {workspacesLoading ? (
271
+ <div className="flex flex-col items-center py-40 gap-4 text-gray-400">
272
+ <RefreshCw className="animate-spin text-cyan-400" size={32} />
273
+ <span className="text-sm">Fetching workspace details...</span>
274
+ </div>
275
+ ) : workspaces.length === 0 ? (
276
+ <div className="flex flex-col items-center justify-center py-24 bg-[#111827]/20 border border-gray-800/80 rounded-xl text-center">
277
+ <FolderGit2 size={44} className="text-gray-600 mb-4" />
278
+ <h3 className="text-lg font-bold text-white">No Active Workspaces</h3>
279
+ <p className="text-xs text-gray-505 max-w-sm mt-1">
280
+ No feature workspaces were detected in your development folder. Create a workspace to start.
281
+ </p>
282
+ </div>
283
+ ) : (
284
+ <div className="flex flex-col gap-6">
285
+ {workspaces.map((ws) => {
286
+ const isExpanded = activeWsId === ws.branchName;
287
+ return (
288
+ <div key={ws.id} className="glass-card p-8 rounded-2xl shadow-xl hover:shadow-2xl border border-slate-800/40 relative overflow-hidden transition-all duration-300 before:absolute before:inset-x-0 before:top-0 before:h-[1px] before:bg-gradient-to-r before:from-cyan-500/10 via-violet-500/10 to-transparent">
289
+ <div className="flex justify-between items-start gap-4 mb-4">
290
+ <div>
291
+ <h3 className="text-lg font-bold text-white hover:text-cyan-400 transition-colors">{ws.branchName}</h3>
292
+ <div className="flex items-center gap-3 text-xs text-slate-500 mt-1">
293
+ <span>Created: {new Date(ws.createdAt).toLocaleDateString()}</span>
294
+ <span className="h-1 w-1 rounded-full bg-slate-850"></span>
295
+ <span>{ws.repos.length} repos</span>
296
+ </div>
297
+ </div>
298
+ <div className="flex gap-2">
299
+ <button
300
+ className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2.5 bg-emerald-650 hover:bg-emerald-600 text-white rounded-xl text-xs font-semibold transition-all cursor-pointer shadow-md shadow-emerald-600/10 disabled:opacity-40 disabled:cursor-not-allowed"
301
+ onClick={() => handleResumeSession(ws)}
302
+ disabled={resumingWs === ws.branchName}
303
+ >
304
+ <Play size={12} className={resumingWs === ws.branchName ? 'animate-spin' : ''} />
305
+ {resumingWs === ws.branchName ? 'Resuming...' : 'Resume Session'}
306
+ </button>
307
+ <button
308
+ className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2.5 bg-slate-900 border border-slate-800/80 hover:bg-slate-800 hover:border-slate-700 rounded-xl text-xs font-semibold transition-all cursor-pointer text-slate-350 hover:text-white"
309
+ onClick={() => handleCopyPrompt(ws)}
310
+ >
311
+ <Sparkles size={12} className="text-cyan-400" /> Copy Prompt
312
+ </button>
313
+ <button
314
+ className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2.5 bg-slate-900 border border-slate-800/80 hover:bg-slate-800 hover:border-slate-700 rounded-xl text-xs font-semibold transition-all cursor-pointer text-slate-350 hover:text-white"
315
+ onClick={() => handleOpenInEditor(ws.workspacePath)}
316
+ >
317
+ <ExternalLink size={12} /> Open
318
+ </button>
319
+ <button
320
+ className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2.5 bg-cyan-600 hover:bg-cyan-500 text-white rounded-xl text-xs font-semibold transition-all cursor-pointer shadow-md shadow-cyan-650/10"
321
+ onClick={() => {
322
+ if (isExpanded) {
323
+ setActiveWsId(null);
324
+ } else {
325
+ setActiveWsId(ws.branchName);
326
+ }
327
+ }}
328
+ >
329
+ {isExpanded ? 'Hide Runner' : 'Orchestrate'}
330
+ </button>
331
+ <button
332
+ className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2.5 bg-red-950/40 border border-red-900/60 hover:bg-red-900/60 hover:border-red-800 rounded-xl text-xs font-semibold text-red-200 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed animate-fade-in"
333
+ onClick={() => handleDeleteWorkspace(ws.branchName)}
334
+ disabled={deleteWsLoading === ws.branchName}
335
+ >
336
+ <Trash2 size={12} className={deleteWsLoading === ws.branchName ? 'animate-spin' : ''} />
337
+ {deleteWsLoading === ws.branchName ? 'Deleting...' : 'Delete'}
338
+ </button>
339
+ </div>
340
+ </div>
341
+
342
+ <div className="text-xs text-slate-350 bg-slate-950/60 border-l-2 border-cyan-500/80 rounded-r-xl px-5 py-3.5 mb-5 font-normal leading-relaxed relative overflow-hidden before:absolute before:inset-y-0 before:right-0 before:w-12 before:bg-gradient-to-r before:from-transparent before:to-cyan-500/2">
343
+ {renderFormattedDescription(ws.description)}
344
+ </div>
345
+
346
+ <div className="flex items-center gap-3 mb-4">
347
+ <div className="flex flex-wrap gap-2">
348
+ {ws.repos.map((repoPath) => (
349
+ <span key={repoPath} className="text-[10px] px-2.5 py-1.5 bg-slate-950/60 border border-slate-850 text-slate-300 rounded-lg font-semibold select-all">
350
+ {repoPath.split(/[\\/]/).pop()}
351
+ </span>
352
+ ))}
353
+ </div>
354
+
355
+ {repos.filter((r) => !ws.repos.includes(r.path)).length > 0 && (
356
+ <div className="flex items-center gap-1.5 ml-auto">
357
+ <select
358
+ id={`add-repo-select-feature-${ws.branchName}`}
359
+ className="bg-slate-905 border border-slate-800 text-slate-300 rounded-lg text-[10px] px-3 py-1.5 font-semibold outline-none focus:border-cyan-500 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition-all hover:border-slate-700"
360
+ defaultValue=""
361
+ onChange={async (e) => {
362
+ const val = e.target.value;
363
+ if (val) {
364
+ if (window.confirm(`Are you sure you want to add repository "${val.split(/[\\/]/).pop()}" to this workspace?\nThis will create a new git worktree and re-run analysis.`)) {
365
+ await handleAddRepo(ws.branchName, val);
366
+ }
367
+ e.target.value = "";
368
+ }
369
+ }}
370
+ disabled={addRepoLoading}
371
+ >
372
+ <option value="" disabled>+ Add Repository</option>
373
+ {repos
374
+ .filter((r) => !ws.repos.includes(r.path))
375
+ .map((r) => (
376
+ <option key={r.path} value={r.path}>{r.name}</option>
377
+ ))
378
+ }
379
+ </select>
380
+ </div>
381
+ )}
382
+ </div>
383
+
384
+ {/* Expansion: Process management console */}
385
+ {isExpanded && (
386
+ <div className="mt-6 pt-6 border-t border-slate-850/80">
387
+ {/* Sub-tab Navigation */}
388
+ <div className="flex flex-wrap items-center gap-1.5 p-1 bg-slate-950/60 rounded-xl border border-slate-900/60 mb-6 w-max max-w-full overflow-x-auto">
389
+ <button
390
+ className={`px-4 py-2 rounded-lg text-xs font-bold transition-all cursor-pointer select-none ${
391
+ subTab === 'overview'
392
+ ? 'bg-cyan-950/30 border border-cyan-900/60 text-cyan-400 shadow-[0_0_15px_rgba(6,182,212,0.1)]'
393
+ : 'text-slate-500 hover:text-slate-300 hover:bg-slate-900/40 border border-transparent'
394
+ }`}
395
+ onClick={() => setSubTab('overview')}
396
+ >
397
+ Harness & Capabilities
398
+ </button>
399
+ <button
400
+ className={`px-4 py-2 rounded-lg text-xs font-bold transition-all cursor-pointer select-none ${
401
+ subTab === 'sessions'
402
+ ? 'bg-cyan-950/30 border border-cyan-900/60 text-cyan-400 shadow-[0_0_15px_rgba(6,182,212,0.1)]'
403
+ : 'text-slate-500 hover:text-slate-300 hover:bg-slate-900/40 border border-transparent'
404
+ }`}
405
+ onClick={() => setSubTab('sessions')}
406
+ >
407
+ AI Session History
408
+ </button>
409
+ <button
410
+ className={`px-4 py-2 rounded-lg text-xs font-bold transition-all cursor-pointer select-none ${
411
+ subTab === 'services'
412
+ ? 'bg-cyan-950/30 border border-cyan-900/60 text-cyan-400 shadow-[0_0_15px_rgba(6,182,212,0.1)]'
413
+ : 'text-slate-500 hover:text-slate-300 hover:bg-slate-900/40 border border-transparent'
414
+ }`}
415
+ onClick={() => setSubTab('services')}
416
+ >
417
+ Orchestrated Services
418
+ </button>
419
+ <button
420
+ className={`px-4 py-2 rounded-lg text-xs font-bold transition-all cursor-pointer select-none ${
421
+ subTab === 'changes'
422
+ ? 'bg-cyan-950/30 border border-cyan-900/60 text-cyan-400 shadow-[0_0_15px_rgba(6,182,212,0.1)]'
423
+ : 'text-slate-500 hover:text-slate-300 hover:bg-slate-900/40 border border-transparent'
424
+ }`}
425
+ onClick={() => setSubTab('changes')}
426
+ >
427
+ Active Changes (AI Diffs)
428
+ </button>
429
+ <button
430
+ className={`px-4 py-2 rounded-lg text-xs font-bold transition-all cursor-pointer select-none ${
431
+ subTab === 'knowledge'
432
+ ? 'bg-cyan-950/30 border border-cyan-900/60 text-cyan-400 shadow-[0_0_15px_rgba(6,182,212,0.1)]'
433
+ : 'text-slate-500 hover:text-slate-300 hover:bg-slate-900/40 border border-transparent'
434
+ }`}
435
+ onClick={() => setSubTab('knowledge')}
436
+ >
437
+ Knowledge Base
438
+ </button>
439
+ <button
440
+ className={`px-4 py-2 rounded-lg text-xs font-bold transition-all cursor-pointer select-none ${
441
+ subTab === 'plan'
442
+ ? 'bg-cyan-950/30 border border-cyan-900/60 text-cyan-400 shadow-[0_0_15px_rgba(6,182,212,0.1)]'
443
+ : 'text-slate-500 hover:text-slate-300 hover:bg-slate-900/40 border border-transparent'
444
+ }`}
445
+ onClick={() => setSubTab('plan')}
446
+ >
447
+ Implementation Plan
448
+ </button>
449
+ </div>
450
+
451
+ {subTab === 'overview' && (
452
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6 text-sm text-gray-300 animate-fade-in">
453
+ {/* Section 1: AI Assistant Harnesses */}
454
+ <div className="bg-gray-950/20 border border-gray-800/80 rounded-xl p-5">
455
+ <div className="flex items-center gap-2 mb-4 border-b border-gray-800/80 pb-3">
456
+ <Cpu size={18} className="text-cyan-400" />
457
+ <h4 className="font-bold text-white">AI Assistant Harnesses</h4>
458
+ </div>
459
+ <p className="text-xs text-gray-400 mb-4">
460
+ These coding assistants are configured to run contextually inside this workspace. Select an assistant to see its setup.
461
+ </p>
462
+ <div className="flex flex-col gap-3">
463
+ {['antigravity', 'claude', 'copilot', 'cursor', 'codex'].map((name) => {
464
+ const isConfigured = ws.assistants.includes(name as any);
465
+ const isDetected = aiAssistants.find((a) => a.name === name)?.detected;
466
+ let displayName = name.charAt(0).toUpperCase() + name.slice(1);
467
+ if (name === 'antigravity') displayName = 'Antigravity (Ollama/Gemini)';
468
+ else if (name === 'codex') displayName = 'OpenAI Codex';
469
+ else if (name === 'copilot') displayName = 'GitHub Copilot';
470
+ else if (name === 'claude') displayName = 'Claude Code';
471
+
472
+ let resumeCmd = '';
473
+ if (name === 'antigravity') resumeCmd = 'agy --continue';
474
+ else if (name === 'claude') resumeCmd = 'claude --resume';
475
+ else if (name === 'codex') resumeCmd = 'codex resume';
476
+ else if (name === 'copilot') resumeCmd = 'copilot --resume';
477
+
478
+ return (
479
+ <div key={name} className="flex flex-col p-3 bg-gray-900/40 border border-gray-800/60 rounded-lg">
480
+ <div className="flex items-center justify-between gap-2">
481
+ <div className="flex items-center gap-2">
482
+ <span className={`h-2 w-2 rounded-full ${isConfigured ? 'bg-cyan-500 shadow-sm' : 'bg-gray-700'}`}></span>
483
+ <span className="font-semibold text-xs text-gray-200">{displayName}</span>
484
+ </div>
485
+ <div className="flex items-center gap-1.5 text-[10px] font-medium">
486
+ {isConfigured && <span className="text-cyan-400 bg-cyan-950/20 px-2 py-0.5 rounded border border-cyan-900/40 text-[9px] font-bold uppercase tracking-wider">Active</span>}
487
+ {isDetected ? (
488
+ <span className="text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded border border-emerald-500/20 text-[9px] font-bold uppercase tracking-wider">Installed</span>
489
+ ) : (
490
+ <span className="text-gray-500 bg-gray-950 px-2 py-0.5 rounded border border-gray-800/60 text-[9px] font-bold uppercase tracking-wider">Not Probed</span>
491
+ )}
492
+ </div>
493
+ </div>
494
+ {isConfigured && resumeCmd && (
495
+ <div className="mt-2.5 flex items-center justify-between gap-2 bg-gray-950/80 px-2.5 py-1.5 rounded border border-gray-800/60 font-mono text-[10px]">
496
+ <span className="text-cyan-400 truncate">{resumeCmd}</span>
497
+ <button
498
+ onClick={() => handleCopy(resumeCmd)}
499
+ className="text-gray-400 hover:text-white transition-colors cursor-pointer"
500
+ title="Copy Resume Command"
501
+ >
502
+ {copiedText === resumeCmd ? <Check size={11} className="text-emerald-400" /> : <Copy size={11} />}
503
+ </button>
504
+ </div>
505
+ )}
506
+ </div>
507
+ );
508
+ })}
509
+ </div>
510
+ </div>
511
+
512
+ {/* Section 2: Model Context Protocol (MCP) */}
513
+ <div className="flex flex-col gap-6">
514
+ <div className="bg-gray-950/20 border border-gray-800/80 rounded-xl p-5">
515
+ <div className="flex items-center gap-2 mb-4 border-b border-gray-800/80 pb-3">
516
+ <Layers size={18} className="text-cyan-400" />
517
+ <h4 className="font-bold text-white">Local MCP Server (Tools)</h4>
518
+ </div>
519
+ <p className="text-xs text-gray-400 mb-4">
520
+ NexusFlow runs a Model Context Protocol (MCP) server that exposes local workspace-scoped tools to your AI agents.
521
+ </p>
522
+ <div className="flex flex-col gap-3.5">
523
+ <div className="flex flex-col gap-1">
524
+ <div className="flex items-center gap-2 text-xs font-semibold text-cyan-455">
525
+ <code>search_workspace</code>
526
+ </div>
527
+ <span className="text-[11px] text-gray-400">
528
+ Extremely fast query/regex search across all microservices and repos in the workspace.
529
+ </span>
530
+ </div>
531
+ <div className="flex flex-col gap-1 border-t border-gray-800/40 pt-2.5">
532
+ <div className="flex items-center gap-2 text-xs font-semibold text-cyan-455">
533
+ <code>get_service_logs</code>
534
+ </div>
535
+ <span className="text-[11px] text-gray-400">
536
+ Fetch and parse recent stdout/stderr output from any background service running in the workspace.
537
+ </span>
538
+ </div>
539
+ <div className="flex flex-col gap-1 border-t border-gray-800/40 pt-2.5">
540
+ <div className="flex items-center gap-2 text-xs font-semibold text-cyan-455">
541
+ <code>delegate_to_local_agent</code>
542
+ </div>
543
+ <span className="text-[11px] text-gray-400">
544
+ Delegate sub-tasks or log analysis to a lightweight LLM running locally to optimize remote token usage.
545
+ </span>
546
+ </div>
547
+ </div>
548
+ </div>
549
+
550
+ {/* CLI shortcuts */}
551
+ <div className="bg-gray-950/20 border border-gray-800/80 rounded-xl p-5">
552
+ <div className="flex items-center gap-2 mb-4 border-b border-gray-800/80 pb-3">
553
+ <Terminal size={18} className="text-cyan-400" />
554
+ <h4 className="font-bold text-white">Workspace CLI Shortcuts</h4>
555
+ </div>
556
+ <div className="flex flex-col gap-2.5">
557
+ {[
558
+ { cmd: 'nexusflow start', desc: 'Spin up detected backend/frontend services' },
559
+ { cmd: 'nexusflow sync', desc: 'Rebase worktrees with upstream defaults' },
560
+ { cmd: 'nexusflow commit -m "update"', desc: 'Stage and commit changes across repos' },
561
+ { cmd: 'nexusflow diff', desc: 'Unified codebase diff across repos' },
562
+ { cmd: 'nexusflow handoff', desc: 'Generate resume context file' }
563
+ ].map(({ cmd, desc }) => (
564
+ <div key={cmd} className="flex flex-col gap-1">
565
+ <div className="flex items-center justify-between gap-2">
566
+ <code className="text-xs text-cyan-400">{cmd}</code>
567
+ <button
568
+ onClick={() => handleCopy(cmd)}
569
+ className="text-gray-400 hover:text-white transition-colors cursor-pointer"
570
+ title="Copy Command"
571
+ >
572
+ {copiedText === cmd ? <Check size={11} className="text-emerald-400" /> : <Copy size={11} />}
573
+ </button>
574
+ </div>
575
+ <span className="text-[11px] text-gray-550">{desc}</span>
576
+ </div>
577
+ ))}
578
+ </div>
579
+ </div>
580
+ </div>
581
+ </div>
582
+ )}
583
+
584
+ {subTab === 'sessions' && (
585
+ <SessionHistory
586
+ ws={ws}
587
+ sessions={sessions}
588
+ sessionsLoading={sessionsLoading}
589
+ activeSession={activeSession}
590
+ transcript={transcript}
591
+ transcriptLoading={transcriptLoading}
592
+ workspaces={workspaces}
593
+ setActiveSession={setActiveSession}
594
+ setTranscript={setTranscript}
595
+ fetchSessionTranscript={fetchSessionTranscript}
596
+ handleResumeSession={handleResumeSession}
597
+ />
598
+ )}
599
+
600
+ {subTab === 'services' && (
601
+ <ServiceConsole
602
+ ws={ws}
603
+ services={services}
604
+ runningServices={runningServices}
605
+ selectedLogService={selectedLogService}
606
+ serviceLogs={serviceLogs}
607
+ logsEndRef={logsEndRef}
608
+ setSelectedLogService={setSelectedLogService}
609
+ handleStartServices={handleStartServices}
610
+ handleStopServices={handleStopServices}
611
+ orchTools={orchTools}
612
+ servicesLoading={servicesLoading}
613
+ />
614
+ )}
615
+
616
+ {subTab === 'changes' && (
617
+ <ChangesViewer
618
+ ws={ws}
619
+ gitChanges={gitChanges}
620
+ gitChangesLoading={gitChangesLoading}
621
+ syncLoading={syncLoading}
622
+ syncResults={syncResults}
623
+ commitMessage={commitMessage}
624
+ showCommitModal={showCommitModal}
625
+ commitLoading={commitLoading}
626
+ commitResults={commitResults}
627
+ setSyncResults={setSyncResults}
628
+ setCommitResults={setCommitResults}
629
+ setCommitMessage={setCommitMessage}
630
+ setShowCommitModal={setShowCommitModal}
631
+ fetchGitChanges={fetchGitChanges}
632
+ handleSyncAll={handleSyncAll}
633
+ handleCommitAll={handleCommitAll}
634
+ />
635
+ )}
636
+
637
+ {subTab === 'knowledge' && (
638
+ <KnowledgeBase
639
+ ws={ws}
640
+ knowledgeContent={knowledgeContent}
641
+ knowledgeLoading={knowledgeLoading}
642
+ isEditingKnowledge={isEditingKnowledge}
643
+ editedKnowledge={editedKnowledge}
644
+ saveKnowledgeLoading={saveKnowledgeLoading}
645
+ setEditedKnowledge={setEditedKnowledge}
646
+ setIsEditingKnowledge={setIsEditingKnowledge}
647
+ handleSaveKnowledge={handleSaveKnowledge}
648
+ />
649
+ )}
650
+
651
+ {subTab === 'plan' && (
652
+ <ImplementationPlan
653
+ planContent={planContent}
654
+ planLoading={planLoading}
655
+ />
656
+ )}
657
+ </div>
658
+ )}
659
+ </div>
660
+ );
661
+ })}
662
+ </div>
663
+ )}
664
+ </div>
665
+ );
666
+ };