@mrpatronz/nexusflow 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/GETTING_STARTED.md +109 -0
  2. package/README.md +310 -0
  3. package/dist/analyzers/detect-apis.d.ts +19 -0
  4. package/dist/analyzers/detect-apis.d.ts.map +1 -0
  5. package/dist/analyzers/detect-apis.js +121 -0
  6. package/dist/analyzers/detect-apis.js.map +1 -0
  7. package/dist/analyzers/detect-deps.d.ts +30 -0
  8. package/dist/analyzers/detect-deps.d.ts.map +1 -0
  9. package/dist/analyzers/detect-deps.js +121 -0
  10. package/dist/analyzers/detect-deps.js.map +1 -0
  11. package/dist/analyzers/detect-existing.d.ts +15 -0
  12. package/dist/analyzers/detect-existing.d.ts.map +1 -0
  13. package/dist/analyzers/detect-existing.js +65 -0
  14. package/dist/analyzers/detect-existing.js.map +1 -0
  15. package/dist/analyzers/detect-ports.d.ts +20 -0
  16. package/dist/analyzers/detect-ports.d.ts.map +1 -0
  17. package/dist/analyzers/detect-ports.js +103 -0
  18. package/dist/analyzers/detect-ports.js.map +1 -0
  19. package/dist/analyzers/index.d.ts +29 -0
  20. package/dist/analyzers/index.d.ts.map +1 -0
  21. package/dist/analyzers/index.js +74 -0
  22. package/dist/analyzers/index.js.map +1 -0
  23. package/dist/analyzers/readme-summarizer.d.ts +13 -0
  24. package/dist/analyzers/readme-summarizer.d.ts.map +1 -0
  25. package/dist/analyzers/readme-summarizer.js +59 -0
  26. package/dist/analyzers/readme-summarizer.js.map +1 -0
  27. package/dist/analyzers/tech-stack.d.ts +15 -0
  28. package/dist/analyzers/tech-stack.d.ts.map +1 -0
  29. package/dist/analyzers/tech-stack.js +284 -0
  30. package/dist/analyzers/tech-stack.js.map +1 -0
  31. package/dist/commands/create.d.ts +17 -0
  32. package/dist/commands/create.d.ts.map +1 -0
  33. package/dist/commands/create.js +111 -0
  34. package/dist/commands/create.js.map +1 -0
  35. package/dist/commands/init.d.ts +10 -0
  36. package/dist/commands/init.d.ts.map +1 -0
  37. package/dist/commands/init.js +50 -0
  38. package/dist/commands/init.js.map +1 -0
  39. package/dist/commands/list.d.ts +9 -0
  40. package/dist/commands/list.d.ts.map +1 -0
  41. package/dist/commands/list.js +32 -0
  42. package/dist/commands/list.js.map +1 -0
  43. package/dist/commands/logs.d.ts +12 -0
  44. package/dist/commands/logs.d.ts.map +1 -0
  45. package/dist/commands/logs.js +54 -0
  46. package/dist/commands/logs.js.map +1 -0
  47. package/dist/commands/open.d.ts +9 -0
  48. package/dist/commands/open.d.ts.map +1 -0
  49. package/dist/commands/open.js +49 -0
  50. package/dist/commands/open.js.map +1 -0
  51. package/dist/commands/start.d.ts +13 -0
  52. package/dist/commands/start.d.ts.map +1 -0
  53. package/dist/commands/start.js +97 -0
  54. package/dist/commands/start.js.map +1 -0
  55. package/dist/commands/status.d.ts +11 -0
  56. package/dist/commands/status.d.ts.map +1 -0
  57. package/dist/commands/status.js +47 -0
  58. package/dist/commands/status.js.map +1 -0
  59. package/dist/commands/stop.d.ts +11 -0
  60. package/dist/commands/stop.d.ts.map +1 -0
  61. package/dist/commands/stop.js +47 -0
  62. package/dist/commands/stop.js.map +1 -0
  63. package/dist/commands/ui.d.ts +13 -0
  64. package/dist/commands/ui.d.ts.map +1 -0
  65. package/dist/commands/ui.js +44 -0
  66. package/dist/commands/ui.js.map +1 -0
  67. package/dist/core/config.d.ts +32 -0
  68. package/dist/core/config.d.ts.map +1 -0
  69. package/dist/core/config.js +68 -0
  70. package/dist/core/config.js.map +1 -0
  71. package/dist/core/scanner.d.ts +18 -0
  72. package/dist/core/scanner.d.ts.map +1 -0
  73. package/dist/core/scanner.js +73 -0
  74. package/dist/core/scanner.js.map +1 -0
  75. package/dist/core/workspace.d.ts +53 -0
  76. package/dist/core/workspace.d.ts.map +1 -0
  77. package/dist/core/workspace.js +114 -0
  78. package/dist/core/workspace.js.map +1 -0
  79. package/dist/core/worktree.d.ts +27 -0
  80. package/dist/core/worktree.d.ts.map +1 -0
  81. package/dist/core/worktree.js +35 -0
  82. package/dist/core/worktree.js.map +1 -0
  83. package/dist/generators/base.d.ts +15 -0
  84. package/dist/generators/base.d.ts.map +1 -0
  85. package/dist/generators/base.js +175 -0
  86. package/dist/generators/base.js.map +1 -0
  87. package/dist/generators/claude.d.ts +17 -0
  88. package/dist/generators/claude.d.ts.map +1 -0
  89. package/dist/generators/claude.js +29 -0
  90. package/dist/generators/claude.js.map +1 -0
  91. package/dist/generators/codex.d.ts +16 -0
  92. package/dist/generators/codex.d.ts.map +1 -0
  93. package/dist/generators/codex.js +41 -0
  94. package/dist/generators/codex.js.map +1 -0
  95. package/dist/generators/copilot.d.ts +16 -0
  96. package/dist/generators/copilot.d.ts.map +1 -0
  97. package/dist/generators/copilot.js +49 -0
  98. package/dist/generators/copilot.js.map +1 -0
  99. package/dist/generators/cursor.d.ts +17 -0
  100. package/dist/generators/cursor.d.ts.map +1 -0
  101. package/dist/generators/cursor.js +37 -0
  102. package/dist/generators/cursor.js.map +1 -0
  103. package/dist/generators/index.d.ts +18 -0
  104. package/dist/generators/index.d.ts.map +1 -0
  105. package/dist/generators/index.js +95 -0
  106. package/dist/generators/index.js.map +1 -0
  107. package/dist/gui/assets/index-Cq9V485S.js +21 -0
  108. package/dist/gui/assets/index-Dh1b5gSZ.css +2 -0
  109. package/dist/gui/favicon.svg +1 -0
  110. package/dist/gui/icons.svg +24 -0
  111. package/dist/gui/index.html +18 -0
  112. package/dist/index.d.ts +11 -0
  113. package/dist/index.d.ts.map +1 -0
  114. package/dist/index.js +169 -0
  115. package/dist/index.js.map +1 -0
  116. package/dist/orchestration/detect.d.ts +29 -0
  117. package/dist/orchestration/detect.d.ts.map +1 -0
  118. package/dist/orchestration/detect.js +292 -0
  119. package/dist/orchestration/detect.js.map +1 -0
  120. package/dist/orchestration/index.d.ts +7 -0
  121. package/dist/orchestration/index.d.ts.map +1 -0
  122. package/dist/orchestration/index.js +7 -0
  123. package/dist/orchestration/index.js.map +1 -0
  124. package/dist/orchestration/runner.d.ts +40 -0
  125. package/dist/orchestration/runner.d.ts.map +1 -0
  126. package/dist/orchestration/runner.js +242 -0
  127. package/dist/orchestration/runner.js.map +1 -0
  128. package/dist/server.d.ts +16 -0
  129. package/dist/server.d.ts.map +1 -0
  130. package/dist/server.js +419 -0
  131. package/dist/server.js.map +1 -0
  132. package/dist/types.d.ts +210 -0
  133. package/dist/types.d.ts.map +1 -0
  134. package/dist/types.js +6 -0
  135. package/dist/types.js.map +1 -0
  136. package/dist/utils/detect-ai.d.ts +21 -0
  137. package/dist/utils/detect-ai.d.ts.map +1 -0
  138. package/dist/utils/detect-ai.js +76 -0
  139. package/dist/utils/detect-ai.js.map +1 -0
  140. package/dist/utils/detect-editors.d.ts +19 -0
  141. package/dist/utils/detect-editors.d.ts.map +1 -0
  142. package/dist/utils/detect-editors.js +49 -0
  143. package/dist/utils/detect-editors.js.map +1 -0
  144. package/dist/utils/git.d.ts +27 -0
  145. package/dist/utils/git.d.ts.map +1 -0
  146. package/dist/utils/git.js +57 -0
  147. package/dist/utils/git.js.map +1 -0
  148. package/dist/utils/prompts.d.ts +27 -0
  149. package/dist/utils/prompts.d.ts.map +1 -0
  150. package/dist/utils/prompts.js +119 -0
  151. package/dist/utils/prompts.js.map +1 -0
  152. package/dist/utils/session-finder.d.ts +29 -0
  153. package/dist/utils/session-finder.d.ts.map +1 -0
  154. package/dist/utils/session-finder.js +462 -0
  155. package/dist/utils/session-finder.js.map +1 -0
  156. package/gui/README.md +73 -0
  157. package/gui/eslint.config.js +22 -0
  158. package/gui/index.html +17 -0
  159. package/gui/package-lock.json +3054 -0
  160. package/gui/package.json +33 -0
  161. package/gui/public/favicon.svg +1 -0
  162. package/gui/public/icons.svg +24 -0
  163. package/gui/src/App.css +1 -0
  164. package/gui/src/App.tsx +1949 -0
  165. package/gui/src/assets/hero.png +0 -0
  166. package/gui/src/assets/react.svg +1 -0
  167. package/gui/src/assets/vite.svg +1 -0
  168. package/gui/src/index.css +1 -0
  169. package/gui/src/main.tsx +10 -0
  170. package/gui/tsconfig.app.json +25 -0
  171. package/gui/tsconfig.json +7 -0
  172. package/gui/tsconfig.node.json +24 -0
  173. package/gui/vite.config.ts +12 -0
  174. package/package.json +48 -0
  175. package/src/analyzers/detect-apis.ts +138 -0
  176. package/src/analyzers/detect-deps.ts +138 -0
  177. package/src/analyzers/detect-existing.ts +74 -0
  178. package/src/analyzers/detect-ports.ts +110 -0
  179. package/src/analyzers/index.ts +85 -0
  180. package/src/analyzers/readme-summarizer.ts +68 -0
  181. package/src/analyzers/tech-stack.ts +255 -0
  182. package/src/commands/create.ts +142 -0
  183. package/src/commands/init.ts +58 -0
  184. package/src/commands/list.ts +46 -0
  185. package/src/commands/logs.ts +63 -0
  186. package/src/commands/open.ts +54 -0
  187. package/src/commands/start.ts +117 -0
  188. package/src/commands/status.ts +54 -0
  189. package/src/commands/stop.ts +55 -0
  190. package/src/commands/ui.ts +47 -0
  191. package/src/core/config.ts +79 -0
  192. package/src/core/scanner.ts +91 -0
  193. package/src/core/workspace.ts +149 -0
  194. package/src/core/worktree.ts +50 -0
  195. package/src/generators/base.ts +187 -0
  196. package/src/generators/claude.ts +34 -0
  197. package/src/generators/codex.ts +48 -0
  198. package/src/generators/copilot.ts +56 -0
  199. package/src/generators/cursor.ts +44 -0
  200. package/src/generators/index.ts +122 -0
  201. package/src/index.ts +178 -0
  202. package/src/orchestration/detect.ts +313 -0
  203. package/src/orchestration/index.ts +7 -0
  204. package/src/orchestration/runner.ts +279 -0
  205. package/src/server.ts +466 -0
  206. package/src/types.ts +294 -0
  207. package/src/utils/detect-ai.ts +81 -0
  208. package/src/utils/detect-editors.ts +54 -0
  209. package/src/utils/git.ts +61 -0
  210. package/src/utils/prompts.ts +135 -0
  211. package/src/utils/session-finder.ts +483 -0
  212. package/tsconfig.json +19 -0
@@ -0,0 +1,1949 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import {
3
+ PlusCircle,
4
+ FolderGit2,
5
+ Settings as SettingsIcon,
6
+ Terminal,
7
+ Play,
8
+ Square,
9
+ ExternalLink,
10
+ Check,
11
+ AlertTriangle,
12
+ FolderOpen,
13
+ ArrowRight,
14
+ ArrowLeft,
15
+ RefreshCw,
16
+ Search,
17
+ Sparkles,
18
+ Copy,
19
+ X,
20
+ MessageSquare,
21
+ MessageSquareCode,
22
+ } from 'lucide-react';
23
+ import './App.css';
24
+
25
+ // Types matched with src/types.ts
26
+ interface NexusFlowConfig {
27
+ version: string;
28
+ devDir: string;
29
+ workspacesDir: string;
30
+ defaultAssistant: string | null;
31
+ scanDepth: number;
32
+ }
33
+
34
+ interface DetectedAI {
35
+ name: string;
36
+ displayName: string;
37
+ detected: boolean;
38
+ command?: string;
39
+ }
40
+
41
+ interface DetectedEditor {
42
+ name: string;
43
+ command: string;
44
+ detected: boolean;
45
+ }
46
+
47
+ interface RepoInfo {
48
+ name: string;
49
+ path: string;
50
+ defaultBranch: string;
51
+ }
52
+
53
+ interface Feature {
54
+ id: string;
55
+ branchName: string;
56
+ description: string;
57
+ repos: string[];
58
+ assistants: string[];
59
+ workspacePath: string;
60
+ createdAt: string;
61
+ }
62
+
63
+ interface ServiceConfig {
64
+ name: string;
65
+ cwd: string;
66
+ command: string;
67
+ args: string[];
68
+ port?: number;
69
+ source: string;
70
+ }
71
+
72
+ interface OrchestrationDetection {
73
+ tool: string;
74
+ configPath: string;
75
+ startCommand: string;
76
+ stopCommand: string;
77
+ }
78
+
79
+ interface RunningService {
80
+ name: string;
81
+ pid: number;
82
+ config: ServiceConfig;
83
+ startedAt: string;
84
+ }
85
+
86
+ const API_BASE = import.meta.env.DEV ? 'http://localhost:3000' : '';
87
+
88
+ export default function App() {
89
+ const [view, setView] = useState<'guide' | 'create' | 'workspaces' | 'settings'>('guide');
90
+
91
+ // App Config
92
+ const [config, setConfig] = useState<NexusFlowConfig | null>(null);
93
+ const [configLoading, setConfigLoading] = useState(true);
94
+ const [configExists, setConfigExists] = useState<boolean>(true);
95
+ const [saveStatus, setSaveStatus] = useState<'success' | 'error' | null>(null);
96
+ const [defaultPaths, setDefaultPaths] = useState<{ devDir: string; workspacesDir: string } | null>(null);
97
+
98
+ // Repos & Tools
99
+ const [repos, setRepos] = useState<RepoInfo[]>([]);
100
+ const [reposLoading, setReposLoading] = useState(false);
101
+ const [repoSearch, setRepoSearch] = useState('');
102
+ const [aiAssistants, setAiAssistants] = useState<DetectedAI[]>([]);
103
+ const [editors, setEditors] = useState<DetectedEditor[]>([]);
104
+
105
+ // Wizard State
106
+ const [activeStep, setActiveStep] = useState(0);
107
+ const [branchName, setBranchName] = useState('');
108
+ const [description, setDescription] = useState('');
109
+ const [selectedRepos, setSelectedRepos] = useState<RepoInfo[]>([]);
110
+ const [selectedAI, setSelectedAI] = useState<string[]>([]);
111
+ const [selectedEditor, setSelectedEditor] = useState<DetectedEditor | null>(null);
112
+ const [creating, setCreating] = useState(false);
113
+ const [createdWorkspace, setCreatedWorkspace] = useState<{ path: string } | null>(null);
114
+
115
+ // Resumption Commands State
116
+ const [testCommand, setTestCommand] = useState('npm run test');
117
+ const [mockCommand, setMockCommand] = useState('');
118
+ const [startCommand, setStartCommand] = useState('');
119
+ const [resumingWs, setResumingWs] = useState<string | null>(null);
120
+
121
+ // Suggest defaults based on selected repos
122
+ useEffect(() => {
123
+ if (selectedRepos.length === 0) return;
124
+ const isDotNet = selectedRepos.some(r => r.name.toLowerCase().includes('dotnet') || r.name.toLowerCase().includes('csharp') || r.name.toLowerCase().includes('microsoft'));
125
+ const isPython = selectedRepos.some(r => r.name.toLowerCase().includes('django') || r.name.toLowerCase().includes('flask') || r.name.toLowerCase().includes('fastapi') || r.name.toLowerCase().includes('python'));
126
+
127
+ if (isDotNet) {
128
+ setTestCommand('dotnet test');
129
+ } else if (isPython) {
130
+ setTestCommand('pytest');
131
+ } else {
132
+ setTestCommand('npm run test');
133
+ }
134
+ }, [selectedRepos]);
135
+
136
+ // Workspaces List
137
+ const [workspaces, setWorkspaces] = useState<Feature[]>([]);
138
+ const [workspacesLoading, setWorkspacesLoading] = useState(false);
139
+
140
+ // Active Workspace Services / Orchestration Details
141
+ const [activeWsId, setActiveWsId] = useState<string | null>(null);
142
+ const [services, setServices] = useState<ServiceConfig[]>([]);
143
+ const [orchTools, setOrchTools] = useState<OrchestrationDetection[]>([]);
144
+ const [runningServices, setRunningServices] = useState<RunningService[]>([]);
145
+ const [servicesLoading, setServicesLoading] = useState(false);
146
+ const [subTab, setSubTab] = useState<'services' | 'changes' | 'sessions'>('sessions');
147
+ const [sessions, setSessions] = useState<any[]>([]);
148
+ const [sessionsLoading, setSessionsLoading] = useState(false);
149
+ const [activeSession, setActiveSession] = useState<any | null>(null);
150
+ const [transcript, setTranscript] = useState<any[]>([]);
151
+ const [transcriptLoading, setTranscriptLoading] = useState(false);
152
+ const [gitChanges, setGitChanges] = useState<any[]>([]);
153
+ const [gitChangesLoading, setGitChangesLoading] = useState(false);
154
+
155
+ // Log Viewer
156
+ const [selectedLogService, setSelectedLogService] = useState<string | null>(null);
157
+ const [serviceLogs, setServiceLogs] = useState<string>('');
158
+ const logsEndRef = useRef<HTMLDivElement | null>(null);
159
+
160
+ // Initial loads
161
+ useEffect(() => {
162
+ fetchConfig();
163
+ fetchAIAssistants();
164
+ fetchEditors();
165
+ fetchWorkspaces();
166
+ }, []);
167
+
168
+ // Poll logs and services status when active workspace is open
169
+ useEffect(() => {
170
+ let interval: any = null;
171
+ if (activeWsId) {
172
+ fetchWorkspaceServices(activeWsId);
173
+ interval = setInterval(() => {
174
+ fetchWorkspaceServices(activeWsId, true);
175
+ }, 3000);
176
+ }
177
+ return () => {
178
+ if (interval) clearInterval(interval);
179
+ };
180
+ }, [activeWsId]);
181
+
182
+ // Load git changes when tab switches to 'changes' or active workspace changes
183
+ useEffect(() => {
184
+ if (activeWsId && subTab === 'changes') {
185
+ fetchGitChanges(activeWsId);
186
+ }
187
+ }, [activeWsId, subTab]);
188
+
189
+ // Load sessions when active workspace changes or subTab switches to 'sessions'
190
+ useEffect(() => {
191
+ if (activeWsId && subTab === 'sessions') {
192
+ fetchWorkspaceSessions(activeWsId);
193
+ }
194
+ }, [activeWsId, subTab]);
195
+
196
+
197
+ // Poll logs for active service logs
198
+ useEffect(() => {
199
+ let interval: any = null;
200
+ if (activeWsId && selectedLogService) {
201
+ fetchLogs(activeWsId, selectedLogService);
202
+ interval = setInterval(() => {
203
+ fetchLogs(activeWsId, selectedLogService);
204
+ }, 2000);
205
+ }
206
+ return () => {
207
+ if (interval) clearInterval(interval);
208
+ };
209
+ }, [activeWsId, selectedLogService]);
210
+
211
+ // Scroll to bottom of logs when log content changes
212
+ useEffect(() => {
213
+ if (logsEndRef.current) {
214
+ logsEndRef.current.scrollIntoView({ behavior: 'smooth' });
215
+ }
216
+ }, [serviceLogs]);
217
+
218
+ // ─── API Fetches ────────────────────────────────────────────────────────
219
+
220
+ const fetchConfig = async () => {
221
+ try {
222
+ const res = await fetch(`${API_BASE}/api/config`);
223
+ const data = await res.json();
224
+ setConfig(data.config);
225
+ setConfigExists(data.exists);
226
+
227
+ if (data.exists && data.config.devDir) {
228
+ fetchRepos();
229
+ } else {
230
+ setDefaultPaths({
231
+ devDir: data.config.devDir || '',
232
+ workspacesDir: data.config.workspacesDir || '',
233
+ });
234
+ setConfig({
235
+ version: '1.0.0',
236
+ devDir: '',
237
+ workspacesDir: '',
238
+ defaultAssistant: null,
239
+ scanDepth: 2,
240
+ });
241
+ }
242
+ } catch (e) {
243
+ console.error(e);
244
+ } finally {
245
+ setConfigLoading(false);
246
+ }
247
+ };
248
+
249
+ const saveAppConfig = async (newConfig: NexusFlowConfig) => {
250
+ try {
251
+ const res = await fetch(`${API_BASE}/api/config`, {
252
+ method: 'POST',
253
+ headers: { 'Content-Type': 'application/json' },
254
+ body: JSON.stringify(newConfig),
255
+ });
256
+ if (res.ok) {
257
+ setSaveStatus('success');
258
+ setConfig(newConfig);
259
+ setConfigExists(true);
260
+ fetchRepos();
261
+ fetchWorkspaces();
262
+ } else {
263
+ setSaveStatus('error');
264
+ }
265
+ } catch {
266
+ setSaveStatus('error');
267
+ }
268
+ setTimeout(() => setSaveStatus(null), 3000);
269
+ };
270
+
271
+ const fetchRepos = async () => {
272
+ setReposLoading(true);
273
+ try {
274
+ const res = await fetch(`${API_BASE}/api/repos`);
275
+ const data = await res.json();
276
+ setRepos(Array.isArray(data) ? data : []);
277
+ } catch (e) {
278
+ console.error(e);
279
+ } finally {
280
+ setReposLoading(false);
281
+ }
282
+ };
283
+
284
+ const fetchAIAssistants = async () => {
285
+ try {
286
+ const res = await fetch(`${API_BASE}/api/ai-detect`);
287
+ const data = await res.json();
288
+ setAiAssistants(data);
289
+ setSelectedAI(data.filter((ai: DetectedAI) => ai.detected).map((ai: DetectedAI) => ai.name));
290
+ } catch (e) {
291
+ console.error(e);
292
+ }
293
+ };
294
+
295
+ const fetchEditors = async () => {
296
+ try {
297
+ const res = await fetch(`${API_BASE}/api/editor-detect`);
298
+ const data = await res.json();
299
+ setEditors(data);
300
+ const defaultEditor = data.find((ed: DetectedEditor) => ed.detected);
301
+ if (defaultEditor) setSelectedEditor(defaultEditor);
302
+ } catch (e) {
303
+ console.error(e);
304
+ }
305
+ };
306
+
307
+ const fetchWorkspaces = async () => {
308
+ setWorkspacesLoading(true);
309
+ try {
310
+ const res = await fetch(`${API_BASE}/api/workspaces`);
311
+ const data = await res.json();
312
+ setWorkspaces(Array.isArray(data) ? data : []);
313
+ } catch (e) {
314
+ console.error(e);
315
+ } finally {
316
+ setWorkspacesLoading(false);
317
+ }
318
+ };
319
+
320
+ const fetchWorkspaceServices = async (wsId: string, silent = false) => {
321
+ if (!silent) setServicesLoading(true);
322
+ try {
323
+ const encodedId = encodeURIComponent(wsId);
324
+ const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/services`);
325
+ const data = await res.json();
326
+ setServices(data.services || []);
327
+ setOrchTools(data.orchestrationTools || []);
328
+ setRunningServices(data.runningState || []);
329
+
330
+ if (data.services.length > 0 && !selectedLogService) {
331
+ setSelectedLogService(data.services[0].name);
332
+ }
333
+ } catch (e) {
334
+ console.error(e);
335
+ } finally {
336
+ if (!silent) setServicesLoading(false);
337
+ }
338
+ };
339
+
340
+ const fetchLogs = async (wsId: string, serviceName: string) => {
341
+ try {
342
+ const encodedId = encodeURIComponent(wsId);
343
+ const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/services/logs/${serviceName}`);
344
+ const data = await res.json();
345
+ setServiceLogs(data.logs || '');
346
+ } catch (e) {
347
+ console.error(e);
348
+ }
349
+ };
350
+
351
+ const fetchGitChanges = async (wsId: string) => {
352
+ setGitChangesLoading(true);
353
+ try {
354
+ const encodedId = encodeURIComponent(wsId);
355
+ const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/changes`);
356
+ const data = await res.json();
357
+ setGitChanges(data.changes || []);
358
+ } catch (e) {
359
+ console.error(e);
360
+ } finally {
361
+ setGitChangesLoading(false);
362
+ }
363
+ };
364
+
365
+ const fetchWorkspaceSessions = async (wsId: string) => {
366
+ setSessionsLoading(true);
367
+ try {
368
+ const encodedId = encodeURIComponent(wsId);
369
+ const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/sessions`);
370
+ const data = await res.json();
371
+ setSessions(data.sessions || []);
372
+ } catch (e) {
373
+ console.error(e);
374
+ } finally {
375
+ setSessionsLoading(false);
376
+ }
377
+ };
378
+
379
+ const fetchSessionTranscript = async (assistant: string, sessionId: string) => {
380
+ setTranscriptLoading(true);
381
+ try {
382
+ const res = await fetch(`${API_BASE}/api/session/${assistant}/${sessionId}/transcript`);
383
+ const data = await res.json();
384
+ setTranscript(data.messages || []);
385
+ } catch (e) {
386
+ console.error(e);
387
+ } finally {
388
+ setTranscriptLoading(false);
389
+ }
390
+ };
391
+
392
+
393
+ // ─── Actions ────────────────────────────────────────────────────────────
394
+
395
+ const handleToggleRepo = (repo: RepoInfo) => {
396
+ if (selectedRepos.some((r) => r.path === repo.path)) {
397
+ setSelectedRepos(selectedRepos.filter((r) => r.path !== repo.path));
398
+ } else {
399
+ setSelectedRepos([...selectedRepos, repo]);
400
+ }
401
+ };
402
+
403
+ const handleToggleAI = (aiName: string) => {
404
+ if (selectedAI.includes(aiName)) {
405
+ setSelectedAI(selectedAI.filter((x) => x !== aiName));
406
+ } else {
407
+ setSelectedAI([...selectedAI, aiName]);
408
+ }
409
+ };
410
+
411
+ const handleCreateWorkspace = async () => {
412
+ if (!branchName || selectedRepos.length === 0) return;
413
+ setCreating(true);
414
+ try {
415
+ const res = await fetch(`${API_BASE}/api/workspace`, {
416
+ method: 'POST',
417
+ headers: { 'Content-Type': 'application/json' },
418
+ body: JSON.stringify({
419
+ branchName,
420
+ description,
421
+ repos: selectedRepos,
422
+ assistants: selectedAI,
423
+ resumption: {
424
+ testCommand,
425
+ mockCommand: mockCommand || undefined,
426
+ startCommand: startCommand || undefined,
427
+ },
428
+ }),
429
+ });
430
+ const data = await res.json();
431
+ if (res.ok && data.success) {
432
+ setCreatedWorkspace({ path: data.workspacePath });
433
+ setActiveStep(3);
434
+ fetchWorkspaces();
435
+
436
+ if (selectedEditor) {
437
+ await fetch(`${API_BASE}/api/open-editor`, {
438
+ method: 'POST',
439
+ headers: { 'Content-Type': 'application/json' },
440
+ body: JSON.stringify({
441
+ workspacePath: data.workspacePath,
442
+ command: selectedEditor.command,
443
+ }),
444
+ });
445
+ }
446
+ } else {
447
+ alert(`Error: ${data.error || 'Failed to create workspace'}`);
448
+ }
449
+ } catch (e) {
450
+ console.error(e);
451
+ alert('Network error when creating workspace.');
452
+ } finally {
453
+ setCreating(false);
454
+ }
455
+ };
456
+
457
+ const handleStartServices = async (wsId: string) => {
458
+ try {
459
+ const encodedId = encodeURIComponent(wsId);
460
+ await fetch(`${API_BASE}/api/workspace/${encodedId}/services/start`, {
461
+ method: 'POST',
462
+ headers: { 'Content-Type': 'application/json' },
463
+ body: JSON.stringify({ services }),
464
+ });
465
+ fetchWorkspaceServices(wsId, true);
466
+ } catch (e) {
467
+ console.error(e);
468
+ }
469
+ };
470
+
471
+ const handleStopServices = async (wsId: string) => {
472
+ try {
473
+ const encodedId = encodeURIComponent(wsId);
474
+ await fetch(`${API_BASE}/api/workspace/${encodedId}/services/stop`, {
475
+ method: 'POST',
476
+ });
477
+ fetchWorkspaceServices(wsId, true);
478
+ setServiceLogs('Services stopped.');
479
+ } catch (e) {
480
+ console.error(e);
481
+ }
482
+ };
483
+
484
+ const handleResumeSession = async (ws: Feature, sessionId?: string, assistant?: string) => {
485
+ setResumingWs(ws.branchName);
486
+ try {
487
+ const encodedId = encodeURIComponent(ws.branchName);
488
+ const editor = editors.find((e) => e.detected) || editors[0];
489
+ const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/resume`, {
490
+ method: 'POST',
491
+ headers: { 'Content-Type': 'application/json' },
492
+ body: JSON.stringify({
493
+ command: editor?.command,
494
+ sessionId,
495
+ assistant,
496
+ }),
497
+ });
498
+ const data = await res.json();
499
+ if (res.ok && data.success) {
500
+ navigator.clipboard.writeText(data.resumeCommand);
501
+ alert(`Session Resumed!\n\n1. Editor launched.\n2. Command "${data.resumeCommand}" copied to clipboard! Paste it into your terminal inside the workspace to continue.`);
502
+ setActiveWsId(ws.branchName);
503
+ } else {
504
+ alert(`Error: ${data.error || 'Failed to resume session'}`);
505
+ }
506
+ } catch (e) {
507
+ console.error(e);
508
+ alert('Network error when resuming session.');
509
+ } finally {
510
+ setResumingWs(null);
511
+ }
512
+ };
513
+
514
+ const handleOpenInEditor = async (workspacePath: string) => {
515
+ const editor = editors.find((e) => e.detected) || editors[0];
516
+ if (!editor) {
517
+ alert('No detected editors available.');
518
+ return;
519
+ }
520
+ try {
521
+ await fetch(`${API_BASE}/api/open-editor`, {
522
+ method: 'POST',
523
+ headers: { 'Content-Type': 'application/json' },
524
+ body: JSON.stringify({
525
+ workspacePath,
526
+ command: editor.command,
527
+ }),
528
+ });
529
+ } catch (e) {
530
+ console.error(e);
531
+ }
532
+ };
533
+
534
+ const handleCopyPrompt = (ws: Feature) => {
535
+ const repoNames = ws.repos.map((r) => r.split(/[\\/]/).pop()).join(', ');
536
+ const prompt = `You are an AI assistant helping with feature development.
537
+ We are working in a multi-repository workspace.
538
+
539
+ Workspace Metadata:
540
+ - Feature Branch: ${ws.branchName}
541
+ - Purpose & Context: ${ws.description}
542
+ - Mapped Repositories: ${repoNames}
543
+
544
+ Core Instructions:
545
+ 1. Always inspect and update "session.md" (session state handover memo) and "plan.md" (tasks checklist) at the workspace root as you progress.
546
+ 2. Read "WORKSPACE.md" at the root for a detailed index of repository relationships, tech stacks, and listening ports.
547
+ 3. Follow all project-specific rules in "CLAUDE.md", ".cursorrules", or "AGENTS.md" in sub-repositories.
548
+ `;
549
+ navigator.clipboard.writeText(prompt);
550
+ alert('Universal AI briefing prompt copied to clipboard!');
551
+ };
552
+
553
+ const filteredRepos = repos.filter((r) =>
554
+ r.name.toLowerCase().includes(repoSearch.toLowerCase())
555
+ );
556
+
557
+ if (!configExists && config) {
558
+ const isFormValid = config.devDir.trim() !== '' && config.workspacesDir.trim() !== '';
559
+
560
+ return (
561
+ <div className="flex min-h-screen bg-[#070913] text-gray-100 font-sans items-center justify-center p-6 bg-gradient-to-br from-[#0c0f24] via-[#070913] to-[#04050b]">
562
+ {/* Onboarding Box */}
563
+ <div className="max-w-6xl w-full bg-[#111827]/40 border border-gray-800/80 rounded-2xl p-8 backdrop-blur-md shadow-2xl grid grid-cols-1 lg:grid-cols-12 gap-10">
564
+
565
+ {/* Left Column: Onboarding Guide & Concepts */}
566
+ <div className="lg:col-span-7 flex flex-col justify-between">
567
+ <div>
568
+ <div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 text-xs font-semibold uppercase tracking-wider mb-4">
569
+ <Sparkles size={12} className="text-cyan-400 animate-pulse" /> Onboarding Guide
570
+ </div>
571
+ <h1 className="text-3xl font-extrabold tracking-tight text-white mb-2 bg-gradient-to-r from-white via-gray-200 to-indigo-300 bg-clip-text text-transparent">
572
+ Welcome to NexusFlow
573
+ </h1>
574
+ <p className="text-sm text-gray-400 mb-8 max-w-xl">
575
+ NexusFlow orchestrates multi-repository developer environments. It combines isolated Git worktrees, automatic code analyzer sweeps, and background process running into a single dashboard.
576
+ </p>
577
+
578
+ {/* Onboarding Steps */}
579
+ <div className="space-y-6">
580
+ <div className="flex gap-4">
581
+ <div className="w-8 h-8 rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-400 flex items-center justify-center font-bold text-sm shrink-0">
582
+ 1
583
+ </div>
584
+ <div>
585
+ <h3 className="text-sm font-semibold text-white">Configure Development Folders</h3>
586
+ <p className="text-xs text-gray-400 mt-1">
587
+ Specify your local code path and target workspaces path. For the first setup, these paths start empty so you can explicitly configure them.
588
+ </p>
589
+ </div>
590
+ </div>
591
+
592
+ <div className="flex gap-4">
593
+ <div className="w-8 h-8 rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-400 flex items-center justify-center font-bold text-sm shrink-0">
594
+ 2
595
+ </div>
596
+ <div>
597
+ <h3 className="text-sm font-semibold text-white">Build Isolated Branch Workspaces</h3>
598
+ <p className="text-xs text-gray-400 mt-1">
599
+ Choose repositories and input your feature branch. NexusFlow runs <code>git worktree</code> to checkout dependencies under a unified folder structure, leaving your primary repository directories clean.
600
+ </p>
601
+ </div>
602
+ </div>
603
+
604
+ <div className="flex gap-4">
605
+ <div className="w-8 h-8 rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-400 flex items-center justify-center font-bold text-sm shrink-0">
606
+ 3
607
+ </div>
608
+ <div>
609
+ <h3 className="text-sm font-semibold text-white">Align AI Coding Contexts</h3>
610
+ <p className="text-xs text-gray-400 mt-1">
611
+ NexusFlow automatically generates configuration files (<code>CLAUDE.md</code>, <code>.cursorrules</code>, <code>AGENTS.md</code>) that instruct the AI assistant to analyze project inter-dependencies, document its assumptions in <code>nexusflow-overview.md</code>, and highlight clarifying questions.
612
+ </p>
613
+ </div>
614
+ </div>
615
+
616
+ <div className="flex gap-4">
617
+ <div className="w-8 h-8 rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-400 flex items-center justify-center font-bold text-sm shrink-0">
618
+ 4
619
+ </div>
620
+ <div>
621
+ <h3 className="text-sm font-semibold text-white">Orchestrate Background Services</h3>
622
+ <p className="text-xs text-gray-400 mt-1">
623
+ Run APIs, database scripts, and frontend watch tasks concurrently from the web portal. Monitor real-time logs inside a unified terminal pane.
624
+ </p>
625
+ </div>
626
+ </div>
627
+ </div>
628
+ </div>
629
+
630
+ {/* Comparison Dashboard */}
631
+ <div className="mt-8 pt-6 border-t border-gray-800/80">
632
+ <h4 className="text-xs font-semibold uppercase tracking-wider text-gray-500 mb-3">Industry Comparison</h4>
633
+ <div className="overflow-x-auto font-sans">
634
+ <table className="w-full text-[10px] text-gray-400 text-left border-collapse">
635
+ <thead>
636
+ <tr className="border-b border-gray-800 text-gray-500">
637
+ <th className="py-2 pr-4 font-semibold">Orchestrator</th>
638
+ <th className="py-2 px-4 font-semibold">Multi-Repo</th>
639
+ <th className="py-2 px-4 font-semibold">AI Rules Integration</th>
640
+ <th className="py-2 pl-4 font-semibold">Weight</th>
641
+ </tr>
642
+ </thead>
643
+ <tbody>
644
+ <tr className="border-b border-gray-800/40">
645
+ <td className="py-2 pr-4 font-semibold text-white">Docker Compose</td>
646
+ <td className="py-2 px-4">Yes (Containers only)</td>
647
+ <td className="py-2 px-4">No</td>
648
+ <td className="py-2 pl-4">Medium (VM overhead)</td>
649
+ </tr>
650
+ <tr className="border-b border-gray-800/40">
651
+ <td className="py-2 pr-4 font-semibold text-white">Lerna / Turborepo</td>
652
+ <td className="py-2 px-4">Monorepo only</td>
653
+ <td className="py-2 px-4">No</td>
654
+ <td className="py-2 pl-4">Light</td>
655
+ </tr>
656
+ <tr className="border-b border-gray-800/40">
657
+ <td className="py-2 pr-4 font-semibold text-white">DevPod / Gitpod</td>
658
+ <td className="py-2 px-4">Yes (Complex config)</td>
659
+ <td className="py-2 px-4">No</td>
660
+ <td className="py-2 pl-4">Heavy (Full virtual VM)</td>
661
+ </tr>
662
+ <tr>
663
+ <td className="py-2 pr-4 font-semibold text-indigo-400">NexusFlow</td>
664
+ <td className="py-2 px-4 text-indigo-300">Yes (Native Worktrees)</td>
665
+ <td className="py-2 px-4 text-indigo-300">Yes (CLAUDE.md/MDC rules)</td>
666
+ <td className="py-2 pl-4 text-indigo-300">Extremely Light (Native processes)</td>
667
+ </tr>
668
+ </tbody>
669
+ </table>
670
+ </div>
671
+ </div>
672
+ </div>
673
+
674
+ {/* Right Column: Configuration Form */}
675
+ <div className="lg:col-span-5 flex flex-col justify-center">
676
+ <div className="bg-gray-950/40 border border-gray-800/80 rounded-2xl p-6 shadow-lg">
677
+ <h2 className="text-xl font-bold text-white mb-1 flex items-center gap-2">
678
+ <SettingsIcon className="text-indigo-400 animate-spin-slow" size={20} /> Initialize Config
679
+ </h2>
680
+ <p className="text-xs text-gray-500 mb-6">
681
+ Define the directories on your machine. The fields are empty so you can provide your paths.
682
+ </p>
683
+
684
+ {/* Form Input fields */}
685
+ <div className="space-y-5">
686
+ <div>
687
+ <label className="block text-[11px] font-semibold text-gray-400 uppercase tracking-wider mb-2">Development Directory</label>
688
+ <input
689
+ type="text"
690
+ className="w-full bg-[#111827] border border-gray-800 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-3 text-white placeholder-gray-600 transition-all outline-none text-xs shadow-inner"
691
+ placeholder="e.g. C:\Users\patro\dev"
692
+ value={config.devDir}
693
+ onChange={(e) => setConfig({ ...config, devDir: e.target.value })}
694
+ />
695
+ {defaultPaths && (
696
+ <div className="text-[10px] text-gray-500 mt-1.5 flex justify-between items-center">
697
+ <span>Suggested: <code>{defaultPaths.devDir}</code></span>
698
+ </div>
699
+ )}
700
+ </div>
701
+
702
+ <div>
703
+ <label className="block text-[11px] font-semibold text-gray-400 uppercase tracking-wider mb-2">Workspaces Directory</label>
704
+ <input
705
+ type="text"
706
+ className="w-full bg-[#111827] border border-gray-800 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-3 text-white placeholder-gray-600 transition-all outline-none text-xs shadow-inner"
707
+ placeholder="e.g. C:\Users\patro\dev\workspaces"
708
+ value={config.workspacesDir}
709
+ onChange={(e) => setConfig({ ...config, workspacesDir: e.target.value })}
710
+ />
711
+ {defaultPaths && (
712
+ <div className="text-[10px] text-gray-500 mt-1.5 flex justify-between items-center">
713
+ <span>Suggested: <code>{defaultPaths.workspacesDir}</code></span>
714
+ </div>
715
+ )}
716
+ </div>
717
+
718
+ {/* Form Buttons */}
719
+ <div className="flex gap-2 pt-2">
720
+ <button
721
+ type="button"
722
+ className="flex-1 inline-flex items-center justify-center gap-1.5 px-3 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"
723
+ onClick={() => {
724
+ if (defaultPaths) {
725
+ setConfig({
726
+ ...config,
727
+ devDir: defaultPaths.devDir,
728
+ workspacesDir: defaultPaths.workspacesDir,
729
+ });
730
+ }
731
+ }}
732
+ >
733
+ Suggest Defaults
734
+ </button>
735
+ <button
736
+ type="button"
737
+ className="px-3 py-2 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-rose-900/60 hover:text-rose-400 rounded-lg text-xs font-semibold transition-all cursor-pointer"
738
+ onClick={() => {
739
+ setConfig({
740
+ ...config,
741
+ devDir: '',
742
+ workspacesDir: '',
743
+ });
744
+ }}
745
+ >
746
+ Clear
747
+ </button>
748
+ </div>
749
+
750
+ {/* Save Button */}
751
+ <div className="pt-4">
752
+ <button
753
+ type="button"
754
+ className="w-full inline-flex items-center justify-center gap-2 px-5 py-3 rounded-lg text-sm font-semibold bg-gradient-to-r from-indigo-500 to-indigo-600 hover:from-indigo-600 hover:to-indigo-700 text-white shadow-lg shadow-indigo-500/20 hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:translate-y-0 transition-all cursor-pointer"
755
+ disabled={!isFormValid}
756
+ onClick={() => saveAppConfig(config)}
757
+ >
758
+ Save & Get Started <ArrowRight size={16} />
759
+ </button>
760
+ </div>
761
+ </div>
762
+ </div>
763
+ </div>
764
+ </div>
765
+ </div>
766
+ );
767
+ }
768
+
769
+ return (
770
+ <div className="flex min-h-screen bg-[#060813] bg-gradient-to-br from-[#0c0f24] via-[#060813] to-[#04050a] text-gray-100 font-sans">
771
+ {/* Sidebar Navigation */}
772
+ <aside className="w-64 bg-[#090d1a]/85 border-r border-gray-800/80 flex flex-col p-6 shrink-0 shadow-2xl backdrop-blur-md">
773
+ <div className="flex items-center gap-3 mb-10 px-2">
774
+ <div className="w-9 h-9 bg-gradient-to-tr from-cyan-400 via-indigo-500 to-purple-600 rounded-lg flex items-center justify-center font-bold text-white shadow-lg shadow-indigo-500/25 text-lg">
775
+ N
776
+ </div>
777
+ <span className="text-xl font-bold tracking-tight bg-gradient-to-r from-white via-gray-200 to-gray-400 bg-clip-text text-transparent">
778
+ NexusFlow
779
+ </span>
780
+ </div>
781
+ <nav className="flex-1">
782
+ <ul className="flex flex-col gap-2">
783
+ <li>
784
+ <button
785
+ className={`w-full flex items-center gap-3 p-3 rounded-lg text-sm font-semibold transition-all cursor-pointer border ${
786
+ view === 'guide'
787
+ ? 'text-white bg-indigo-500/10 border-indigo-500/20 shadow-sm'
788
+ : 'text-gray-400 border-transparent hover:text-white hover:bg-gray-800/40'
789
+ }`}
790
+ onClick={() => {
791
+ setView('guide');
792
+ }}
793
+ >
794
+ <Sparkles size={18} className="text-indigo-400" />
795
+ Getting Started
796
+ </button>
797
+ </li>
798
+ <li>
799
+ <button
800
+ className={`w-full flex items-center gap-3 p-3 rounded-lg text-sm font-semibold transition-all cursor-pointer border ${
801
+ view === 'create'
802
+ ? 'text-white bg-indigo-500/10 border-indigo-500/20 shadow-sm'
803
+ : 'text-gray-400 border-transparent hover:text-white hover:bg-gray-800/40'
804
+ }`}
805
+ onClick={() => {
806
+ setView('create');
807
+ setActiveStep(0);
808
+ setCreatedWorkspace(null);
809
+ setBranchName('');
810
+ setDescription('');
811
+ setSelectedRepos([]);
812
+ }}
813
+ >
814
+ <PlusCircle size={18} className="text-indigo-400" />
815
+ New Workspace
816
+ </button>
817
+ </li>
818
+ <li>
819
+ <button
820
+ className={`w-full flex items-center gap-3 p-3 rounded-lg text-sm font-semibold transition-all cursor-pointer border ${
821
+ view === 'workspaces'
822
+ ? 'text-white bg-indigo-500/10 border-indigo-500/20 shadow-sm'
823
+ : 'text-gray-400 border-transparent hover:text-white hover:bg-gray-800/40'
824
+ }`}
825
+ onClick={() => {
826
+ setView('workspaces');
827
+ fetchWorkspaces();
828
+ }}
829
+ >
830
+ <FolderGit2 size={18} className="text-indigo-400" />
831
+ Active Workspaces
832
+ </button>
833
+ </li>
834
+ <li>
835
+ <button
836
+ className={`w-full flex items-center gap-3 p-3 rounded-lg text-sm font-semibold transition-all cursor-pointer border ${
837
+ view === 'settings'
838
+ ? 'text-white bg-indigo-500/10 border-indigo-500/20 shadow-sm'
839
+ : 'text-gray-400 border-transparent hover:text-white hover:bg-gray-800/40'
840
+ }`}
841
+ onClick={() => {
842
+ setView('settings');
843
+ fetchConfig();
844
+ }}
845
+ >
846
+ <SettingsIcon size={18} className="text-indigo-400" />
847
+ Settings
848
+ </button>
849
+ </li>
850
+ </ul>
851
+ </nav>
852
+ <div className="pt-6 border-t border-gray-800/60 text-[11px] text-gray-500 text-center">
853
+ NexusFlow Engine v0.1.0
854
+ </div>
855
+ </aside>
856
+
857
+ {/* Main Content Area */}
858
+ <main className="flex-1 p-10 overflow-y-auto max-w-7xl w-full mx-auto">
859
+ {configLoading ? (
860
+ <div className="flex flex-col items-center justify-center py-40 gap-4 text-gray-400">
861
+ <RefreshCw className="animate-spin text-indigo-400" size={32} />
862
+ <span className="text-sm font-medium">Loading config settings...</span>
863
+ </div>
864
+ ) : (
865
+ <>
866
+ {/* View 0: Getting Started Guide */}
867
+ {view === 'guide' && config && (
868
+ <div className="max-w-4xl mx-auto">
869
+ <header className="mb-10">
870
+ <div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 text-xs font-semibold uppercase tracking-wider mb-4">
871
+ <Sparkles size={12} className="text-cyan-400" /> Getting Started Guide
872
+ </div>
873
+ <h1 className="text-3xl font-extrabold tracking-tight text-white mb-2 bg-gradient-to-r from-white via-gray-200 to-indigo-300 bg-clip-text text-transparent">
874
+ Welcome to NexusFlow
875
+ </h1>
876
+ <p className="text-sm text-gray-400">
877
+ NexusFlow orchestrates multi-repository developer environments. It combines isolated Git worktrees, automatic code analyzer sweeps, and background process running into a single dashboard.
878
+ </p>
879
+ </header>
880
+
881
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-10">
882
+ {/* Left: Interactive Stepper */}
883
+ <div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-6 shadow-xl backdrop-blur-sm space-y-6">
884
+ <h2 className="text-lg font-bold text-white mb-4">NexusFlow Workflows</h2>
885
+
886
+ <div className="flex gap-4">
887
+ <div className="w-8 h-8 rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-400 flex items-center justify-center font-bold text-sm shrink-0">
888
+ 1
889
+ </div>
890
+ <div>
891
+ <h3 className="text-sm font-semibold text-white">Configure Folders</h3>
892
+ <p className="text-xs text-gray-400 mt-1">
893
+ Specify your Development folder (where repositories live) and Workspaces folder. These are currently set to:
894
+ <code className="block mt-1 text-[10px] text-indigo-300 break-all">{config.devDir || 'Not Configured'}</code>
895
+ </p>
896
+ </div>
897
+ </div>
898
+
899
+ <div className="flex gap-4">
900
+ <div className="w-8 h-8 rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-400 flex items-center justify-center font-bold text-sm shrink-0">
901
+ 2
902
+ </div>
903
+ <div>
904
+ <h3 className="text-sm font-semibold text-white">Build Workspaces</h3>
905
+ <p className="text-xs text-gray-400 mt-1">
906
+ Choose repositories and feature branch. NexusFlow checks out dependencies under a unified workspace directory using Git Worktrees, keeping original projects clean.
907
+ </p>
908
+ </div>
909
+ </div>
910
+
911
+ <div className="flex gap-4">
912
+ <div className="w-8 h-8 rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-400 flex items-center justify-center font-bold text-sm shrink-0">
913
+ 3
914
+ </div>
915
+ <div>
916
+ <h3 className="text-sm font-semibold text-white">Align AI Context</h3>
917
+ <p className="text-xs text-gray-400 mt-1">
918
+ NexusFlow writes files like <code>CLAUDE.md</code>, <code>.cursorrules</code>, <code>AGENTS.md</code> prompting the LLM to inspect project relations and list key assumptions and questions before coding.
919
+ </p>
920
+ </div>
921
+ </div>
922
+ </div>
923
+
924
+ {/* Right: Quick actions and Telemetry info */}
925
+ <div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-6 shadow-xl backdrop-blur-sm flex flex-col justify-between">
926
+ <div>
927
+ <h2 className="text-lg font-bold text-white mb-4">Current Configuration</h2>
928
+ <div className="space-y-3 text-xs">
929
+ <div className="flex justify-between py-1.5 border-b border-gray-800/60">
930
+ <span className="text-gray-500">Dev Folder:</span>
931
+ <span className="text-gray-300 font-mono text-[10px] truncate max-w-[200px]">{config.devDir}</span>
932
+ </div>
933
+ <div className="flex justify-between py-1.5 border-b border-gray-800/60">
934
+ <span className="text-gray-500">Workspaces Folder:</span>
935
+ <span className="text-gray-300 font-mono text-[10px] truncate max-w-[200px]">{config.workspacesDir}</span>
936
+ </div>
937
+ <div className="flex justify-between py-1.5 border-b border-gray-800/60">
938
+ <span className="text-gray-500">Preferred AI:</span>
939
+ <span className="text-indigo-400 font-semibold uppercase">{config.defaultAssistant || 'None'}</span>
940
+ </div>
941
+ <div className="flex justify-between py-1.5">
942
+ <span className="text-gray-500">Scan Depth:</span>
943
+ <span className="text-gray-300 font-semibold">{config.scanDepth} levels</span>
944
+ </div>
945
+ </div>
946
+ </div>
947
+
948
+ <div className="pt-6 border-t border-gray-800/60 flex flex-col gap-2">
949
+ <button
950
+ className="w-full inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg text-xs font-semibold bg-gradient-to-r from-indigo-500 to-indigo-600 hover:from-indigo-600 hover:to-indigo-700 text-white shadow-md shadow-indigo-500/10 hover:-translate-y-0.5 active:translate-y-0 transition-all cursor-pointer"
951
+ onClick={() => setView('create')}
952
+ >
953
+ <PlusCircle size={14} /> Create a Workspace
954
+ </button>
955
+ <button
956
+ className="w-full inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg text-xs font-semibold bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white transition-all cursor-pointer"
957
+ onClick={() => setView('settings')}
958
+ >
959
+ <SettingsIcon size={14} className="text-gray-500" /> Modify Settings
960
+ </button>
961
+ </div>
962
+ </div>
963
+ </div>
964
+
965
+ {/* Compare dashboard at the bottom */}
966
+ <div className="bg-[#111827]/20 border border-gray-800/80 rounded-xl p-6 shadow-md">
967
+ <h4 className="text-xs font-semibold uppercase tracking-wider text-gray-500 mb-3">Industry Comparison</h4>
968
+ <div className="overflow-x-auto">
969
+ <table className="w-full text-[10px] text-gray-400 text-left border-collapse">
970
+ <thead>
971
+ <tr className="border-b border-gray-800 text-gray-500">
972
+ <th className="py-2 pr-4 font-semibold">Orchestrator</th>
973
+ <th className="py-2 px-4 font-semibold">Multi-Repo</th>
974
+ <th className="py-2 px-4 font-semibold">AI Rules Integration</th>
975
+ <th className="py-2 pl-4 font-semibold">Weight</th>
976
+ </tr>
977
+ </thead>
978
+ <tbody>
979
+ <tr className="border-b border-gray-800/40">
980
+ <td className="py-2 pr-4 font-semibold text-white">Docker Compose</td>
981
+ <td className="py-2 px-4">Yes (Containers only)</td>
982
+ <td className="py-2 px-4">No</td>
983
+ <td className="py-2 pl-4">Medium (VM overhead)</td>
984
+ </tr>
985
+ <tr className="border-b border-gray-800/40">
986
+ <td className="py-2 pr-4 font-semibold text-white">Lerna / Turborepo</td>
987
+ <td className="py-2 px-4">Monorepo only</td>
988
+ <td className="py-2 px-4">No</td>
989
+ <td className="py-2 pl-4">Light</td>
990
+ </tr>
991
+ <tr className="border-b border-gray-800/40">
992
+ <td className="py-2 pr-4 font-semibold text-white">DevPod / Gitpod</td>
993
+ <td className="py-2 px-4">Yes (Complex config)</td>
994
+ <td className="py-2 px-4">No</td>
995
+ <td className="py-2 pl-4">Heavy (Full virtual VM)</td>
996
+ </tr>
997
+ <tr>
998
+ <td className="py-2 pr-4 font-semibold text-indigo-400">NexusFlow</td>
999
+ <td className="py-2 px-4 text-indigo-300">Yes (Native Worktrees)</td>
1000
+ <td className="py-2 px-4 text-indigo-300">Yes (CLAUDE.md/MDC rules)</td>
1001
+ <td className="py-2 pl-4 text-indigo-300">Extremely Light (Native processes)</td>
1002
+ </tr>
1003
+ </tbody>
1004
+ </table>
1005
+ </div>
1006
+ </div>
1007
+ </div>
1008
+ )}
1009
+
1010
+ {/* View 1: Wizard Workspace Builder */}
1011
+ {view === 'create' && (
1012
+ <div className="max-w-4xl mx-auto">
1013
+ <header className="mb-10">
1014
+ <h1 className="text-3xl font-extrabold tracking-tight text-white mb-2">New Feature Workspace</h1>
1015
+ <p className="text-sm text-gray-400">
1016
+ Set up unified git worktrees, scan tech stacks, and write contextual configurations for your AI assistant.
1017
+ </p>
1018
+ </header>
1019
+
1020
+ {/* Progress Circle bar */}
1021
+ <div className="flex justify-between items-center max-w-xl mx-auto mb-14 relative px-4">
1022
+ <div className="absolute top-4 left-6 right-6 h-[2px] bg-gray-800 -z-10"></div>
1023
+ <div
1024
+ className="absolute top-4 left-6 h-[2px] bg-gradient-to-r from-cyan-400 to-indigo-500 -z-10 transition-all duration-300"
1025
+ style={{ width: `${(activeStep / 3) * 92}%` }}
1026
+ ></div>
1027
+ <div className="flex flex-col items-center gap-2">
1028
+ <div className={`w-8 h-8 rounded-full border-2 flex items-center justify-center font-bold text-xs transition-all ${
1029
+ activeStep > 0 ? 'bg-emerald-500 border-emerald-500 text-white' : activeStep === 0 ? 'border-indigo-500 bg-[#0b0f19] text-white shadow-lg shadow-indigo-500/20' : 'border-gray-800 bg-gray-900 text-gray-500'
1030
+ }`}>
1031
+ {activeStep > 0 ? <Check size={14} /> : '1'}
1032
+ </div>
1033
+ <span className={`text-[11px] font-semibold tracking-wide uppercase ${activeStep === 0 ? 'text-white' : 'text-gray-500'}`}>Details</span>
1034
+ </div>
1035
+ <div className="flex flex-col items-center gap-2">
1036
+ <div className={`w-8 h-8 rounded-full border-2 flex items-center justify-center font-bold text-xs transition-all ${
1037
+ activeStep > 1 ? 'bg-emerald-500 border-emerald-500 text-white' : activeStep === 1 ? 'border-indigo-500 bg-[#0b0f19] text-white shadow-lg shadow-indigo-500/20' : 'border-gray-800 bg-gray-900 text-gray-500'
1038
+ }`}>
1039
+ {activeStep > 1 ? <Check size={14} /> : '2'}
1040
+ </div>
1041
+ <span className={`text-[11px] font-semibold tracking-wide uppercase ${activeStep === 1 ? 'text-white' : 'text-gray-500'}`}>Repos</span>
1042
+ </div>
1043
+ <div className="flex flex-col items-center gap-2">
1044
+ <div className={`w-8 h-8 rounded-full border-2 flex items-center justify-center font-bold text-xs transition-all ${
1045
+ activeStep > 2 ? 'bg-emerald-500 border-emerald-500 text-white' : activeStep === 2 ? 'border-indigo-500 bg-[#0b0f19] text-white shadow-lg shadow-indigo-500/20' : 'border-gray-800 bg-gray-900 text-gray-500'
1046
+ }`}>
1047
+ {activeStep > 2 ? <Check size={14} /> : '3'}
1048
+ </div>
1049
+ <span className={`text-[11px] font-semibold tracking-wide uppercase ${activeStep === 2 ? 'text-white' : 'text-gray-500'}`}>Assistant</span>
1050
+ </div>
1051
+ <div className="flex flex-col items-center gap-2">
1052
+ <div className={`w-8 h-8 rounded-full border-2 flex items-center justify-center font-bold text-xs transition-all ${
1053
+ activeStep === 3 ? 'border-emerald-500 bg-[#0b0f19] text-emerald-400 shadow-lg shadow-emerald-500/20' : 'border-gray-800 bg-gray-900 text-gray-500'
1054
+ }`}>
1055
+ 4
1056
+ </div>
1057
+ <span className={`text-[11px] font-semibold tracking-wide uppercase ${activeStep === 3 ? 'text-emerald-400' : 'text-gray-500'}`}>Complete</span>
1058
+ </div>
1059
+ </div>
1060
+
1061
+ {/* Step 0: Name & Description */}
1062
+ {activeStep === 0 && (
1063
+ <div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-8 shadow-xl backdrop-blur-sm">
1064
+ <div className="mb-6">
1065
+ <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Feature Branch Name</label>
1066
+ <input
1067
+ type="text"
1068
+ className="w-full bg-[#111827] border border-gray-800 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-3 text-white placeholder-gray-600 transition-all outline-none text-sm shadow-inner"
1069
+ placeholder="e.g., feature/oauth-authentication-flow"
1070
+ value={branchName}
1071
+ onChange={(e) => setBranchName(e.target.value)}
1072
+ />
1073
+ </div>
1074
+ <div className="mb-8">
1075
+ <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Feature Purpose & Context</label>
1076
+ <textarea
1077
+ className="w-full bg-[#111827] border border-gray-800 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-3 text-white placeholder-gray-600 transition-all outline-none text-sm min-h-[140px] resize-y shadow-inner"
1078
+ placeholder="Describe what you want to build. This helps AI assistants analyze context and produce matching plans."
1079
+ value={description}
1080
+ onChange={(e) => setDescription(e.target.value)}
1081
+ />
1082
+ </div>
1083
+ <div className="flex justify-end">
1084
+ <button
1085
+ className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-lg text-sm font-semibold bg-gradient-to-r from-indigo-500 to-indigo-600 hover:from-indigo-600 hover:to-indigo-700 text-white shadow-md shadow-indigo-500/10 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed hover:-translate-y-0.5 active:translate-y-0"
1086
+ disabled={!branchName || !description}
1087
+ onClick={() => setActiveStep(1)}
1088
+ >
1089
+ Next Step <ArrowRight size={16} />
1090
+ </button>
1091
+ </div>
1092
+ </div>
1093
+ )}
1094
+
1095
+ {/* Step 1: Repo Selector */}
1096
+ {activeStep === 1 && (
1097
+ <div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-8 shadow-xl backdrop-blur-sm">
1098
+ <div className="flex justify-between items-center mb-6">
1099
+ <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider">Select Repositories</label>
1100
+ <div className="relative w-64">
1101
+ <Search size={14} className="absolute left-3 top-3 text-gray-500" />
1102
+ <input
1103
+ type="text"
1104
+ className="w-full bg-[#111827] border border-gray-800 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg pl-9 pr-4 py-2 text-white placeholder-gray-600 transition-all outline-none text-xs"
1105
+ placeholder="Search repo name..."
1106
+ value={repoSearch}
1107
+ onChange={(e) => setRepoSearch(e.target.value)}
1108
+ />
1109
+ </div>
1110
+ </div>
1111
+
1112
+ {reposLoading ? (
1113
+ <div className="flex flex-col items-center py-20 gap-3 text-gray-500">
1114
+ <RefreshCw className="animate-spin text-indigo-400" size={24} />
1115
+ <span className="text-xs">Scanning local projects directory...</span>
1116
+ </div>
1117
+ ) : (
1118
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4 max-h-[380px] overflow-y-auto p-2 border border-gray-800/60 rounded-lg bg-gray-950/20 mb-8">
1119
+ {filteredRepos.map((repo) => {
1120
+ const isSelected = selectedRepos.some((r) => r.path === repo.path);
1121
+ return (
1122
+ <div
1123
+ key={repo.path}
1124
+ className={`bg-[#111827]/60 border rounded-xl p-4 flex items-start gap-3 cursor-pointer hover:bg-gray-800/20 hover:border-gray-700 transition-all ${
1125
+ isSelected ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
1126
+ }`}
1127
+ onClick={() => handleToggleRepo(repo)}
1128
+ >
1129
+ <input
1130
+ type="checkbox"
1131
+ className="mt-1 h-4 w-4 accent-indigo-500 rounded cursor-pointer"
1132
+ checked={isSelected}
1133
+ readOnly
1134
+ />
1135
+ <div className="flex-1 min-w-0">
1136
+ <div className="text-sm font-semibold text-white truncate">{repo.name}</div>
1137
+ <div className="text-[10px] text-gray-500 truncate mt-1">{repo.path}</div>
1138
+ </div>
1139
+ <span className="text-[9px] px-2 py-0.5 rounded bg-gray-800 text-gray-400 font-semibold uppercase">{repo.defaultBranch}</span>
1140
+ </div>
1141
+ );
1142
+ })}
1143
+ </div>
1144
+ )}
1145
+
1146
+ <div className="flex justify-between">
1147
+ <button
1148
+ className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-lg text-sm font-semibold bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white transition-all cursor-pointer"
1149
+ onClick={() => setActiveStep(0)}
1150
+ >
1151
+ <ArrowLeft size={16} /> Back
1152
+ </button>
1153
+ <button
1154
+ className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-lg text-sm font-semibold bg-gradient-to-r from-indigo-500 to-indigo-600 hover:from-indigo-600 hover:to-indigo-700 text-white shadow-md shadow-indigo-500/10 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed hover:-translate-y-0.5 active:translate-y-0"
1155
+ disabled={selectedRepos.length === 0}
1156
+ onClick={() => setActiveStep(2)}
1157
+ >
1158
+ Next Step <ArrowRight size={16} />
1159
+ </button>
1160
+ </div>
1161
+ </div>
1162
+ )}
1163
+
1164
+ {/* Step 2: AI & Editor Settings */}
1165
+ {activeStep === 2 && (
1166
+ <div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-8 shadow-xl backdrop-blur-sm">
1167
+ <div className="mb-8">
1168
+ <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Target AI Assistant(s)</label>
1169
+ <p className="text-xs text-gray-500 mb-4">
1170
+ We generate context configurations matching the specifications of each checked assistant.
1171
+ </p>
1172
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
1173
+ {aiAssistants.map((ai) => {
1174
+ const isSelected = selectedAI.includes(ai.name);
1175
+ return (
1176
+ <div
1177
+ key={ai.name}
1178
+ className={`bg-[#111827]/60 border rounded-xl p-4 flex flex-col gap-3 cursor-pointer hover:border-gray-700 transition-all ${
1179
+ isSelected ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
1180
+ }`}
1181
+ onClick={() => handleToggleAI(ai.name)}
1182
+ >
1183
+ <div className="flex justify-between items-center">
1184
+ <span className="text-sm font-bold text-white">{ai.displayName}</span>
1185
+ <span className={`text-[9px] px-2 py-0.5 rounded font-semibold uppercase ${
1186
+ ai.detected ? 'bg-emerald-500/10 text-emerald-400' : 'bg-gray-800 text-gray-500'
1187
+ }`}>
1188
+ {ai.detected ? 'Installed' : 'Missing'}
1189
+ </span>
1190
+ </div>
1191
+ <p className="text-[11px] text-gray-500">
1192
+ {ai.name === 'claude' && 'CLAUDE.md guidelines'}
1193
+ {ai.name === 'antigravity' && 'CLAUDE.md guidelines (harness)'}
1194
+ {ai.name === 'codex' && 'AGENTS.md config structure'}
1195
+ {ai.name === 'copilot' && 'GitHub Copilot Workspace configs'}
1196
+ {ai.name === 'cursor' && 'Cursor MDC rules and instructions'}
1197
+ </p>
1198
+ </div>
1199
+ );
1200
+ })}
1201
+ </div>
1202
+ </div>
1203
+
1204
+ <div className="mb-8">
1205
+ <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Open Workspace In</label>
1206
+ <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
1207
+ {editors.map((ed) => {
1208
+ const isSelected = selectedEditor?.command === ed.command;
1209
+ return (
1210
+ <div
1211
+ key={ed.command}
1212
+ className={`bg-[#111827]/60 border rounded-xl p-4 flex flex-col cursor-pointer hover:border-gray-700 transition-all ${
1213
+ isSelected ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
1214
+ }`}
1215
+ onClick={() => setSelectedEditor(ed)}
1216
+ >
1217
+ <span className="text-sm font-bold text-white">{ed.name}</span>
1218
+ <span className={`text-[9px] mt-2 w-max px-2 py-0.5 rounded font-semibold uppercase ${
1219
+ ed.detected ? 'bg-emerald-500/10 text-emerald-400' : 'bg-gray-800 text-gray-500'
1220
+ }`}>
1221
+ {ed.detected ? 'Detected' : 'Missing'}
1222
+ </span>
1223
+ </div>
1224
+ );
1225
+ })}
1226
+ <div
1227
+ className={`bg-[#111827]/60 border rounded-xl p-4 flex flex-col cursor-pointer hover:border-gray-700 transition-all ${
1228
+ selectedEditor === null ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
1229
+ }`}
1230
+ onClick={() => setSelectedEditor(null)}
1231
+ >
1232
+ <span className="text-sm font-bold text-white">None</span>
1233
+ <span className="text-[9px] mt-2 w-max px-2 py-0.5 rounded font-semibold uppercase bg-gray-800 text-gray-500">
1234
+ Skip opening
1235
+ </span>
1236
+ </div>
1237
+ </div>
1238
+ </div>
1239
+
1240
+ <div className="mb-8 border border-gray-800/80 rounded-xl p-5 bg-gray-950/20">
1241
+ <h4 className="text-xs font-bold text-gray-400 uppercase tracking-wider mb-4 flex items-center gap-2">
1242
+ <Terminal size={14} className="text-indigo-400" /> Advanced Session Resumption Settings
1243
+ </h4>
1244
+ <p className="text-[11px] text-gray-500 mb-4">
1245
+ Configure setup and verification test commands. AI assistants and the dashboard use these to resume your sessions green.
1246
+ </p>
1247
+ <div className="space-y-4">
1248
+ <div>
1249
+ <label className="block text-[11px] font-semibold text-gray-400 mb-1.5">Verification / Test Command</label>
1250
+ <input
1251
+ type="text"
1252
+ className="w-full bg-[#030408] border border-gray-800 focus:border-indigo-500 rounded-lg px-3 py-2 text-xs font-mono text-white outline-none transition-all"
1253
+ placeholder="e.g., npm run test or vitest run"
1254
+ value={testCommand}
1255
+ onChange={(e) => setTestCommand(e.target.value)}
1256
+ />
1257
+ </div>
1258
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
1259
+ <div>
1260
+ <label className="block text-[11px] font-semibold text-gray-400 mb-1.5">Mock / Database Command (Optional)</label>
1261
+ <input
1262
+ type="text"
1263
+ className="w-full bg-[#030408] border border-gray-800 focus:border-indigo-500 rounded-lg px-3 py-2 text-xs font-mono text-white outline-none transition-all"
1264
+ placeholder="e.g., docker compose up -d redis"
1265
+ value={mockCommand}
1266
+ onChange={(e) => setMockCommand(e.target.value)}
1267
+ />
1268
+ </div>
1269
+ <div>
1270
+ <label className="block text-[11px] font-semibold text-gray-400 mb-1.5">Start / Run Command (Optional)</label>
1271
+ <input
1272
+ type="text"
1273
+ className="w-full bg-[#030408] border border-gray-800 focus:border-indigo-500 rounded-lg px-3 py-2 text-xs font-mono text-white outline-none transition-all"
1274
+ placeholder="e.g., npm run start:dev"
1275
+ value={startCommand}
1276
+ onChange={(e) => setStartCommand(e.target.value)}
1277
+ />
1278
+ </div>
1279
+ </div>
1280
+ </div>
1281
+ </div>
1282
+
1283
+ <div className="flex justify-between">
1284
+ <button
1285
+ className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-lg text-sm font-semibold bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white transition-all cursor-pointer"
1286
+ onClick={() => setActiveStep(1)}
1287
+ >
1288
+ <ArrowLeft size={16} /> Back
1289
+ </button>
1290
+ <button
1291
+ className="inline-flex items-center justify-center gap-2 px-6 py-3 rounded-lg text-sm font-semibold bg-gradient-to-r from-indigo-500 to-indigo-600 hover:from-indigo-600 hover:to-indigo-700 text-white shadow-lg shadow-indigo-500/20 transition-all cursor-pointer hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-40 disabled:cursor-not-allowed"
1292
+ disabled={creating}
1293
+ onClick={handleCreateWorkspace}
1294
+ >
1295
+ {creating ? (
1296
+ <>
1297
+ <RefreshCw className="animate-spin" size={14} /> Building...
1298
+ </>
1299
+ ) : (
1300
+ <>
1301
+ <Sparkles size={14} /> Build Workspace
1302
+ </>
1303
+ )}
1304
+ </button>
1305
+ </div>
1306
+ </div>
1307
+ )}
1308
+
1309
+ {/* Step 3: Success Screen */}
1310
+ {activeStep === 3 && createdWorkspace && (
1311
+ <div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-10 text-center shadow-xl backdrop-blur-sm">
1312
+ <div className="inline-flex items-center justify-center p-3 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 mb-6">
1313
+ <Check size={36} />
1314
+ </div>
1315
+ <h2 className="text-2xl font-bold text-white mb-2">Workspace Generated!</h2>
1316
+ <p className="text-gray-400 text-sm max-w-lg mx-auto mb-8">
1317
+ We spun up git worktrees for your branch <code>{branchName}</code>, analyzed the code architectures, and generated your AI assistant instructions.
1318
+ </p>
1319
+
1320
+ <div className="flex flex-col gap-3 bg-gray-950/40 border border-gray-800/60 rounded-xl p-5 w-fit min-w-[360px] mx-auto text-left text-xs mb-8">
1321
+ <div className="flex justify-between items-center">
1322
+ <span className="text-gray-500 font-medium">Branch name:</span>
1323
+ <code className="text-indigo-400 font-bold">{branchName}</code>
1324
+ </div>
1325
+ <div className="flex justify-between items-center gap-4">
1326
+ <span className="text-gray-500 font-medium">Location:</span>
1327
+ <code className="text-gray-400 font-mono text-[10px] break-all">{createdWorkspace.path}</code>
1328
+ </div>
1329
+ </div>
1330
+
1331
+ <div className="flex justify-center gap-4">
1332
+ <button
1333
+ className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-lg text-sm font-semibold bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white transition-all cursor-pointer"
1334
+ onClick={() => {
1335
+ setView('workspaces');
1336
+ fetchWorkspaces();
1337
+ setActiveWsId(branchName);
1338
+ }}
1339
+ >
1340
+ <Terminal size={14} className="text-cyan-400" /> Manage Services
1341
+ </button>
1342
+ <button
1343
+ className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-lg text-sm font-semibold bg-gradient-to-r from-indigo-500 to-indigo-600 hover:from-indigo-600 hover:to-indigo-700 text-white shadow-md shadow-indigo-500/10 transition-all cursor-pointer hover:-translate-y-0.5 active:translate-y-0"
1344
+ onClick={() => handleOpenInEditor(createdWorkspace.path)}
1345
+ >
1346
+ <FolderOpen size={14} /> Open Editor
1347
+ </button>
1348
+ </div>
1349
+ </div>
1350
+ )}
1351
+ </div>
1352
+ )}
1353
+
1354
+ {/* View 2: Active Workspaces Panel */}
1355
+ {view === 'workspaces' && (
1356
+ <div className="max-w-5xl mx-auto">
1357
+ <header className="flex justify-between items-center mb-8">
1358
+ <div>
1359
+ <h1 className="text-3xl font-extrabold tracking-tight text-white mb-2">Active Workspaces</h1>
1360
+ <p className="text-sm text-gray-400">Monitor running servers, libraries, and processes on your development branches.</p>
1361
+ </div>
1362
+ <button
1363
+ 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"
1364
+ onClick={fetchWorkspaces}
1365
+ disabled={workspacesLoading}
1366
+ >
1367
+ <RefreshCw size={14} className={workspacesLoading ? 'animate-spin text-indigo-400' : ''} /> Refresh
1368
+ </button>
1369
+ </header>
1370
+
1371
+ {workspacesLoading ? (
1372
+ <div className="flex flex-col items-center py-40 gap-4 text-gray-400">
1373
+ <RefreshCw className="animate-spin text-indigo-400" size={32} />
1374
+ <span className="text-sm">Fetching workspace details...</span>
1375
+ </div>
1376
+ ) : workspaces.length === 0 ? (
1377
+ <div className="flex flex-col items-center justify-center py-24 bg-[#111827]/20 border border-gray-800/80 rounded-xl text-center">
1378
+ <FolderGit2 size={44} className="text-gray-600 mb-4" />
1379
+ <h3 className="text-lg font-bold text-white">No Active Workspaces</h3>
1380
+ <p className="text-xs text-gray-500 max-w-sm mt-1">
1381
+ No feature workspaces were detected in your development folder. Create a workspace to start.
1382
+ </p>
1383
+ </div>
1384
+ ) : (
1385
+ <div className="flex flex-col gap-6">
1386
+ {workspaces.map((ws) => {
1387
+ const isExpanded = activeWsId === ws.branchName;
1388
+ return (
1389
+ <div key={ws.id} className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-6 shadow-md">
1390
+ <div className="flex justify-between items-start gap-4 mb-4">
1391
+ <div>
1392
+ <h3 className="text-lg font-bold text-white hover:text-indigo-400 transition-colors">{ws.branchName}</h3>
1393
+ <div className="flex items-center gap-3 text-xs text-gray-500 mt-1">
1394
+ <span>Created: {new Date(ws.createdAt).toLocaleDateString()}</span>
1395
+ <span className="h-1 w-1 rounded-full bg-gray-700"></span>
1396
+ <span>{ws.repos.length} repos</span>
1397
+ </div>
1398
+ </div>
1399
+ <div className="flex gap-2">
1400
+ <button
1401
+ className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 bg-emerald-600 hover:bg-emerald-700 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"
1402
+ onClick={() => handleResumeSession(ws)}
1403
+ disabled={resumingWs === ws.branchName}
1404
+ >
1405
+ <Play size={12} className={resumingWs === ws.branchName ? 'animate-spin' : ''} />
1406
+ {resumingWs === ws.branchName ? 'Resuming...' : 'Resume Session'}
1407
+ </button>
1408
+ <button
1409
+ 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"
1410
+ onClick={() => handleCopyPrompt(ws)}
1411
+ >
1412
+ <Sparkles size={12} className="text-cyan-400" /> Copy Prompt
1413
+ </button>
1414
+ <button
1415
+ 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"
1416
+ onClick={() => handleOpenInEditor(ws.workspacePath)}
1417
+ >
1418
+ <ExternalLink size={12} /> Open
1419
+ </button>
1420
+ <button
1421
+ 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"
1422
+ onClick={() => {
1423
+ if (isExpanded) {
1424
+ setActiveWsId(null);
1425
+ } else {
1426
+ setActiveWsId(ws.branchName);
1427
+ }
1428
+ }}
1429
+ >
1430
+ {isExpanded ? 'Hide Runner' : 'Orchestrate'}
1431
+ </button>
1432
+ </div>
1433
+ </div>
1434
+
1435
+ <p 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">
1436
+ {ws.description}
1437
+ </p>
1438
+
1439
+ <div className="flex flex-wrap gap-2 mb-4">
1440
+ {ws.repos.map((repoPath) => (
1441
+ <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">
1442
+ {repoPath.split(/[\\/]/).pop()}
1443
+ </span>
1444
+ ))}
1445
+ </div>
1446
+
1447
+ {/* Expansion: Process management console */}
1448
+ {isExpanded && (
1449
+ <div className="mt-6 pt-6 border-t border-gray-800/80">
1450
+
1451
+ {/* Telemetry Dashboard Grid */}
1452
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
1453
+ {/* Telemetry 1: CPU */}
1454
+ <div className="bg-gray-950/40 border border-gray-800/60 rounded-xl p-4 flex flex-col justify-between">
1455
+ <div className="flex justify-between items-center text-xs font-semibold text-gray-400 mb-2">
1456
+ <span>Simulated CPU Load</span>
1457
+ <span className={runningServices.length > 0 ? "text-emerald-400" : "text-gray-500"}>{runningServices.length > 0 ? '4.8%' : '0%'}</span>
1458
+ </div>
1459
+ <div className="w-full bg-gray-800 rounded-full h-1.5 mb-2 overflow-hidden">
1460
+ <div
1461
+ className={`h-1.5 rounded-full transition-all duration-500 ${runningServices.length > 0 ? "bg-gradient-to-r from-emerald-500 to-teal-400 animate-pulse" : "bg-gray-700"}`}
1462
+ style={{ width: runningServices.length > 0 ? '48%' : '0%' }}
1463
+ ></div>
1464
+ </div>
1465
+ <span className="text-[10px] text-gray-500">Fluctuating active worker processes</span>
1466
+ </div>
1467
+
1468
+ {/* Telemetry 2: Memory */}
1469
+ <div className="bg-gray-950/40 border border-gray-800/60 rounded-xl p-4 flex flex-col justify-between">
1470
+ <div className="flex justify-between items-center text-xs font-semibold text-gray-400 mb-2">
1471
+ <span>Simulated Memory Allocation</span>
1472
+ <span className={runningServices.length > 0 ? "text-cyan-400" : "text-gray-500"}>{runningServices.length > 0 ? '192 MB' : '0 MB'}</span>
1473
+ </div>
1474
+ <div className="w-full bg-gray-800 rounded-full h-1.5 mb-2 overflow-hidden">
1475
+ <div
1476
+ className={`h-1.5 rounded-full transition-all duration-500 ${runningServices.length > 0 ? "bg-gradient-to-r from-cyan-500 to-indigo-400" : "bg-gray-700"}`}
1477
+ style={{ width: runningServices.length > 0 ? '65%' : '0%' }}
1478
+ ></div>
1479
+ </div>
1480
+ <span className="text-[10px] text-gray-500">Working set size for spawned servers</span>
1481
+ </div>
1482
+
1483
+ {/* Telemetry 3: Environment Health */}
1484
+ <div className="bg-gray-950/40 border border-gray-800/60 rounded-xl p-4 flex flex-col justify-between">
1485
+ <div className="flex justify-between items-center text-xs font-semibold text-gray-400 mb-2">
1486
+ <span>Active Workspace Status</span>
1487
+ <span className={`inline-flex items-center gap-1 text-[10px] uppercase font-bold tracking-wider ${runningServices.length > 0 ? "text-emerald-400" : "text-gray-500"}`}>
1488
+ <span className={`w-1.5 h-1.5 rounded-full ${runningServices.length > 0 ? "bg-emerald-500 animate-ping" : "bg-gray-600"}`}></span>
1489
+ {runningServices.length > 0 ? "Online" : "Idle"}
1490
+ </span>
1491
+ </div>
1492
+ <div className="text-xs text-white font-medium truncate">
1493
+ {runningServices.length > 0 ? `${runningServices.length} processes active` : "All processes offline"}
1494
+ </div>
1495
+ <span className="text-[10px] text-gray-500">Service state mapping</span>
1496
+ </div>
1497
+ </div>
1498
+
1499
+ {/* Sub-tab Navigation */}
1500
+ <div className="flex border-b border-gray-800/80 mb-6 gap-2">
1501
+ <button
1502
+ className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
1503
+ subTab === 'sessions'
1504
+ ? 'border-indigo-500 text-white'
1505
+ : 'border-transparent text-gray-500 hover:text-gray-300'
1506
+ }`}
1507
+ onClick={() => setSubTab('sessions')}
1508
+ >
1509
+ AI Session History
1510
+ </button>
1511
+ <button
1512
+ className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
1513
+ subTab === 'services'
1514
+ ? 'border-indigo-500 text-white'
1515
+ : 'border-transparent text-gray-500 hover:text-gray-300'
1516
+ }`}
1517
+ onClick={() => setSubTab('services')}
1518
+ >
1519
+ Orchestrated Services
1520
+ </button>
1521
+ <button
1522
+ className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
1523
+ subTab === 'changes'
1524
+ ? 'border-indigo-500 text-white'
1525
+ : 'border-transparent text-gray-500 hover:text-gray-300'
1526
+ }`}
1527
+ onClick={() => setSubTab('changes')}
1528
+ >
1529
+ Active Changes (AI Diffs)
1530
+ </button>
1531
+ </div>
1532
+
1533
+ {subTab === 'sessions' && (
1534
+ sessionsLoading ? (
1535
+ <div className="flex justify-center py-10">
1536
+ <RefreshCw className="animate-spin text-indigo-400" size={20} />
1537
+ </div>
1538
+ ) : sessions.length === 0 ? (
1539
+ <div className="text-center py-10 bg-gray-950/20 border border-gray-800/60 rounded-xl p-6">
1540
+ <MessageSquareCode size={36} className="text-gray-650 mx-auto mb-3" />
1541
+ <h4 className="text-sm font-bold text-gray-400 mb-1">No Past AI Sessions Found</h4>
1542
+ <p className="text-xs text-gray-505 max-w-md mx-auto leading-relaxed">
1543
+ Start a conversation with your AI assistant (e.g. running <code>claude</code>, <code>agy</code>, <code>codex</code>, or <code>copilot</code> inside this directory) to track session history here.
1544
+ </p>
1545
+ </div>
1546
+ ) : (
1547
+ <div className="space-y-3 mb-6">
1548
+ {sessions.map((sess) => (
1549
+ <div key={sess.id} className="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-gray-950/20 border border-gray-800/60 rounded-xl p-4 hover:border-gray-700/80 transition-all">
1550
+ <div className="flex-1 min-w-0">
1551
+ <div className="flex items-center gap-2.5 mb-1.5">
1552
+ <span className={`px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider ${
1553
+ sess.assistant === 'antigravity' ? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30' :
1554
+ sess.assistant === 'claude' ? 'bg-amber-500/20 text-amber-300 border border-amber-500/30' :
1555
+ sess.assistant === 'codex' ? 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/30' :
1556
+ 'bg-sky-500/20 text-sky-300 border border-sky-500/30'
1557
+ }`}>
1558
+ {sess.assistant === 'antigravity' ? 'Antigravity' :
1559
+ sess.assistant === 'claude' ? 'Claude Code' :
1560
+ sess.assistant === 'codex' ? 'OpenAI Codex' : 'GitHub Copilot'}
1561
+ </span>
1562
+ <span className="text-[10px] text-gray-500">
1563
+ Updated: {new Date(sess.updatedAt).toLocaleString()}
1564
+ </span>
1565
+ <span className="text-[10px] text-gray-500">•</span>
1566
+ <span className="text-[10px] text-gray-550 font-medium">
1567
+ {sess.messageCount} messages
1568
+ </span>
1569
+ </div>
1570
+ <h4 className="text-xs font-semibold text-white truncate pr-4" title={sess.title}>
1571
+ {sess.title}
1572
+ </h4>
1573
+ </div>
1574
+ <div className="flex items-center gap-2 shrink-0">
1575
+ <button
1576
+ className="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 bg-indigo-500/10 border border-indigo-500/20 hover:bg-indigo-500/20 text-indigo-300 rounded-lg text-xs font-bold transition-all cursor-pointer"
1577
+ onClick={() => {
1578
+ setActiveSession(sess);
1579
+ setTranscript([]);
1580
+ fetchSessionTranscript(sess.assistant, sess.id);
1581
+ }}
1582
+ >
1583
+ <MessageSquare size={13} /> View Chat Log
1584
+ </button>
1585
+ <button
1586
+ className="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg text-xs font-bold transition-all cursor-pointer"
1587
+ onClick={() => handleResumeSession(ws, sess.id, sess.assistant)}
1588
+ >
1589
+ <Play size={11} /> Resume
1590
+ </button>
1591
+ </div>
1592
+ </div>
1593
+ ))}
1594
+ </div>
1595
+ )
1596
+ )}
1597
+
1598
+ {subTab === 'services' && (
1599
+ servicesLoading ? (
1600
+ <div className="flex justify-center py-10">
1601
+ <RefreshCw className="animate-spin text-indigo-400" size={20} />
1602
+ </div>
1603
+ ) : (
1604
+ <div>
1605
+ {orchTools.length > 0 && (
1606
+ <div className="flex items-center gap-3 bg-indigo-500/5 border border-indigo-500/20 rounded-xl p-4 mb-4 text-xs text-indigo-300">
1607
+ <AlertTriangle size={16} className="text-indigo-400 shrink-0" />
1608
+ <div>
1609
+ <strong>Orchestration manifest detected:</strong> {orchTools.map((t) => t.tool).join(', ')}. You can start them inside sub-repos.
1610
+ </div>
1611
+ </div>
1612
+ )}
1613
+
1614
+ <div className="flex flex-col gap-2 mb-4">
1615
+ {services.map((svc) => {
1616
+ const isRunning = runningServices.some((rs) => rs.name === svc.name);
1617
+ return (
1618
+ <div key={svc.name} className="flex justify-between items-center bg-gray-950/20 border border-gray-800/60 rounded-xl px-4 py-3 text-xs">
1619
+ <div className="flex items-center gap-3">
1620
+ <span className={`w-2.5 h-2.5 rounded-full ${isRunning ? 'bg-emerald-500 shadow-[0_0_8px_#10b981]' : 'bg-gray-700'}`}></span>
1621
+ <div>
1622
+ <span className="font-bold text-white">{svc.name}</span>
1623
+ <span className="text-[10px] text-gray-500 ml-3">
1624
+ {svc.command} {svc.args.join(' ')} {svc.port ? `• port ${svc.port}` : ''}
1625
+ </span>
1626
+ </div>
1627
+ </div>
1628
+ <span className="text-[9px] px-2 py-0.5 rounded bg-gray-800 text-gray-500 font-bold uppercase">{svc.source}</span>
1629
+ </div>
1630
+ );
1631
+ })}
1632
+ </div>
1633
+
1634
+ <div className="flex gap-2 mb-6">
1635
+ <button
1636
+ className="inline-flex items-center justify-center gap-1.5 px-4 py-2.5 bg-emerald-500 hover:bg-emerald-600 text-white rounded-lg text-xs font-semibold transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed shadow-md shadow-emerald-500/10 hover:-translate-y-0.5 active:translate-y-0"
1637
+ onClick={() => handleStartServices(ws.branchName)}
1638
+ disabled={runningServices.length > 0}
1639
+ >
1640
+ <Play size={14} /> Start All Services
1641
+ </button>
1642
+ <button
1643
+ className="inline-flex items-center justify-center gap-1.5 px-4 py-2.5 bg-rose-600 hover:bg-rose-700 text-white rounded-lg text-xs font-semibold transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed shadow-md shadow-rose-600/10 hover:-translate-y-0.5 active:translate-y-0"
1644
+ onClick={() => handleStopServices(ws.branchName)}
1645
+ disabled={runningServices.length === 0}
1646
+ >
1647
+ <Square size={14} /> Stop All
1648
+ </button>
1649
+ </div>
1650
+
1651
+ {/* Interactive Console Screen */}
1652
+ {services.length > 0 && (
1653
+ <div className="border border-gray-800/80 rounded-xl overflow-hidden shadow-2xl bg-gray-950/60">
1654
+ {/* Terminal Window Header */}
1655
+ <div className="flex items-center justify-between bg-gray-900/80 px-4 py-3 border-b border-gray-800/60">
1656
+ <div className="flex items-center gap-2">
1657
+ <div className="flex gap-1.5">
1658
+ <span className="w-3 h-3 rounded-full bg-rose-500/20 border border-rose-500/30"></span>
1659
+ <span className="w-3 h-3 rounded-full bg-amber-500/20 border border-amber-500/30"></span>
1660
+ <span className="w-3 h-3 rounded-full bg-emerald-500/20 border border-emerald-500/30"></span>
1661
+ </div>
1662
+ <span className="text-[11px] font-mono text-gray-400 font-semibold ml-2">nexusflow-terminal: {selectedLogService}</span>
1663
+ </div>
1664
+ <div className="flex gap-1 bg-gray-950/40 p-1 rounded-lg border border-gray-800/60">
1665
+ {services.map((svc) => (
1666
+ <button
1667
+ key={svc.name}
1668
+ className={`px-3 py-1 rounded-md text-[10px] font-mono font-bold transition-all cursor-pointer ${
1669
+ selectedLogService === svc.name
1670
+ ? 'bg-slate-800 text-white shadow-sm'
1671
+ : 'text-gray-500 hover:text-gray-300'
1672
+ }`}
1673
+ onClick={() => setSelectedLogService(svc.name)}
1674
+ >
1675
+ {svc.name}
1676
+ </button>
1677
+ ))}
1678
+ </div>
1679
+ </div>
1680
+
1681
+ {/* Terminal Screen Body */}
1682
+ <div className="bg-[#030408] p-5 font-mono text-[11px] text-cyan-400/90 h-80 overflow-y-auto whitespace-pre-wrap shadow-inner relative scrollbar-thin scrollbar-thumb-gray-800">
1683
+ <div className="absolute top-3 right-3 text-[9px] bg-slate-900/60 border border-gray-800 text-cyan-500/60 px-2 py-0.5 rounded font-mono select-none uppercase tracking-wider">
1684
+ LIVE LOGS
1685
+ </div>
1686
+ {serviceLogs}
1687
+ <div ref={logsEndRef}></div>
1688
+ </div>
1689
+ </div>
1690
+ )}
1691
+ </div>
1692
+ )
1693
+ )}
1694
+
1695
+ {/* Active Changes View */}
1696
+ {subTab === 'changes' && (
1697
+ <div>
1698
+ <header className="flex justify-between items-center mb-4">
1699
+ <h4 className="text-sm font-bold text-white flex items-center gap-2">
1700
+ <FolderGit2 size={16} className="text-cyan-400" /> Active Workspace Git Diffs
1701
+ </h4>
1702
+ <button
1703
+ className="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 rounded-lg text-[10px] font-semibold transition-all cursor-pointer text-gray-300"
1704
+ onClick={() => fetchGitChanges(ws.branchName)}
1705
+ disabled={gitChangesLoading}
1706
+ >
1707
+ <RefreshCw size={11} className={gitChangesLoading ? 'animate-spin text-indigo-400' : ''} /> Refresh Changes
1708
+ </button>
1709
+ </header>
1710
+
1711
+ {gitChangesLoading ? (
1712
+ <div className="flex justify-center py-10">
1713
+ <RefreshCw className="animate-spin text-indigo-400" size={20} />
1714
+ </div>
1715
+ ) : (
1716
+ <div className="space-y-4">
1717
+ {gitChanges.every(repo => repo.files.length === 0) ? (
1718
+ <div className="flex flex-col items-center justify-center py-12 bg-gray-950/20 border border-gray-800/40 rounded-xl text-center">
1719
+ <Check size={28} className="text-emerald-500 mb-2" />
1720
+ <h5 className="text-xs font-bold text-white">No Uncommitted Changes</h5>
1721
+ <p className="text-[10px] text-gray-500 mt-0.5">Workspace is completely synced with Git feature branches.</p>
1722
+ </div>
1723
+ ) : (
1724
+ gitChanges.map((repo) => {
1725
+ if (repo.files.length === 0) return null;
1726
+ return (
1727
+ <div key={repo.repoName} className="bg-gray-950/20 border border-gray-800/60 rounded-xl p-4">
1728
+ <div className="flex justify-between items-center mb-3">
1729
+ <h5 className="text-xs font-bold text-white font-mono">{repo.repoName}</h5>
1730
+ <span className="text-[9px] text-gray-500 font-mono truncate max-w-[280px]">{repo.repoPath}</span>
1731
+ </div>
1732
+ <div className="space-y-1.5">
1733
+ {repo.files.map((fileInfo: any) => (
1734
+ <div key={fileInfo.file} className="flex justify-between items-center bg-[#090d1a]/40 px-3 py-2 rounded-lg border border-gray-800/30 text-xs">
1735
+ <span className="font-mono text-gray-300 text-[11px] truncate max-w-[320px]">{fileInfo.file}</span>
1736
+ <span className={`text-[9px] px-2 py-0.5 rounded font-bold uppercase ${
1737
+ fileInfo.type === 'added' ? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20' :
1738
+ fileInfo.type === 'deleted' ? 'bg-rose-500/10 text-rose-400 border border-rose-500/20' :
1739
+ 'bg-amber-500/10 text-amber-400 border border-amber-500/20'
1740
+ }`}>
1741
+ {fileInfo.type}
1742
+ </span>
1743
+ </div>
1744
+ ))}
1745
+ </div>
1746
+ </div>
1747
+ );
1748
+ })
1749
+ )}
1750
+ </div>
1751
+ )}
1752
+ </div>
1753
+ )}
1754
+ </div>
1755
+ )}
1756
+ </div>
1757
+ );
1758
+ })}
1759
+ </div>
1760
+ )}
1761
+ </div>
1762
+ )}
1763
+
1764
+ {/* View 3: Settings View */}
1765
+ {view === 'settings' && config && (
1766
+ <div className="max-w-4xl mx-auto">
1767
+ <header className="mb-8">
1768
+ <h1 className="text-3xl font-extrabold tracking-tight text-white mb-2">Global Settings</h1>
1769
+ <p className="text-sm text-gray-400">Configure your local directories, search parameters, and editor defaults.</p>
1770
+ </header>
1771
+
1772
+ {saveStatus === 'success' && (
1773
+ <div className="flex items-center gap-3 bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 rounded-xl p-4 mb-6 text-sm">
1774
+ <Check size={18} /> Settings successfully saved and updated!
1775
+ </div>
1776
+ )}
1777
+ {saveStatus === 'error' && (
1778
+ <div className="flex items-center gap-3 bg-rose-500/10 border border-rose-500/20 text-rose-400 rounded-xl p-4 mb-6 text-sm">
1779
+ <AlertTriangle size={18} /> Error: Could not save configuration details to disk.
1780
+ </div>
1781
+ )}
1782
+
1783
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6 bg-[#111827]/40 border border-gray-800/80 rounded-xl p-8 shadow-xl backdrop-blur-sm mb-6">
1784
+ <div className="flex flex-col">
1785
+ <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Development Directory</label>
1786
+ <input
1787
+ type="text"
1788
+ className="w-full bg-[#111827] border border-gray-800 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-3 text-white placeholder-gray-600 transition-all outline-none text-sm shadow-inner"
1789
+ value={config.devDir}
1790
+ onChange={(e) => setConfig({ ...config, devDir: e.target.value })}
1791
+ />
1792
+ <span className="text-[10px] text-gray-500 mt-1">Directory where your git projects are scanned.</span>
1793
+ </div>
1794
+
1795
+ <div className="flex flex-col">
1796
+ <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Workspaces Directory</label>
1797
+ <input
1798
+ type="text"
1799
+ className="w-full bg-[#111827] border border-gray-800 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-3 text-white placeholder-gray-600 transition-all outline-none text-sm shadow-inner"
1800
+ value={config.workspacesDir}
1801
+ onChange={(e) => setConfig({ ...config, workspacesDir: e.target.value })}
1802
+ />
1803
+ <span className="text-[10px] text-gray-500 mt-1">Directory where unified worktree environments are spun up.</span>
1804
+ </div>
1805
+
1806
+ <div className="flex flex-col">
1807
+ <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Repo Search Depth</label>
1808
+ <input
1809
+ type="number"
1810
+ className="w-full bg-[#111827] border border-gray-800 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-3 text-white placeholder-gray-600 transition-all outline-none text-sm shadow-inner"
1811
+ min={1}
1812
+ max={5}
1813
+ value={config.scanDepth}
1814
+ onChange={(e) => setConfig({ ...config, scanDepth: parseInt(e.target.value, 10) })}
1815
+ />
1816
+ <span className="text-[10px] text-gray-500 mt-1">Max directory levels deep the system will traverse for git repos.</span>
1817
+ </div>
1818
+
1819
+ <div className="flex flex-col">
1820
+ <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Default Assistant</label>
1821
+ <select
1822
+ className="w-full bg-[#111827] border border-gray-800 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-3 text-white transition-all outline-none text-sm shadow-inner cursor-pointer"
1823
+ value={config.defaultAssistant || ''}
1824
+ onChange={(e) => setConfig({ ...config, defaultAssistant: e.target.value || null })}
1825
+ >
1826
+ <option value="">None (Prompt me)</option>
1827
+ <option value="claude">Claude Code</option>
1828
+ <option value="antigravity">Antigravity</option>
1829
+ <option value="codex">Codex</option>
1830
+ <option value="copilot">GitHub Copilot</option>
1831
+ <option value="cursor">Cursor</option>
1832
+ </select>
1833
+ <span className="text-[10px] text-gray-500 mt-1">Your preferred workspace context manager.</span>
1834
+ </div>
1835
+ </div>
1836
+
1837
+ <div className="flex justify-end">
1838
+ <button
1839
+ className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-lg text-sm font-semibold bg-gradient-to-r from-indigo-500 to-indigo-600 hover:from-indigo-600 hover:to-indigo-700 text-white shadow-lg shadow-indigo-500/20 transition-all cursor-pointer hover:-translate-y-0.5 active:translate-y-0"
1840
+ onClick={() => saveAppConfig(config)}
1841
+ >
1842
+ Save Configuration
1843
+ </button>
1844
+ </div>
1845
+ </div>
1846
+ )}
1847
+ </>
1848
+ )}
1849
+ </main>
1850
+
1851
+ {/* Transcript Modal Overlay */}
1852
+ {activeSession && (
1853
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/75 backdrop-blur-sm p-4">
1854
+ <div className="bg-[#0b0f19] border border-gray-800 rounded-2xl w-full max-w-4xl h-[80vh] flex flex-col shadow-2xl overflow-hidden animate-fadeIn">
1855
+ {/* Modal Header */}
1856
+ <div className="flex items-center justify-between px-6 py-4 border-b border-gray-800 bg-gray-950/40">
1857
+ <div>
1858
+ <div className="flex items-center gap-2 mb-1">
1859
+ <span className={`px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider ${
1860
+ activeSession.assistant === 'antigravity' ? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30' :
1861
+ activeSession.assistant === 'claude' ? 'bg-amber-500/20 text-amber-300 border border-amber-500/30' :
1862
+ activeSession.assistant === 'codex' ? 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/30' :
1863
+ 'bg-sky-500/20 text-sky-300 border border-sky-500/30'
1864
+ }`}>
1865
+ {activeSession.assistant === 'antigravity' ? 'Antigravity' :
1866
+ activeSession.assistant === 'claude' ? 'Claude Code' :
1867
+ activeSession.assistant === 'codex' ? 'OpenAI Codex' : 'GitHub Copilot'}
1868
+ </span>
1869
+ <span className="text-[10px] text-gray-500">Session: {activeSession.id}</span>
1870
+ </div>
1871
+ <h3 className="text-sm font-bold text-white truncate max-w-xl" title={activeSession.title}>
1872
+ {activeSession.title}
1873
+ </h3>
1874
+ </div>
1875
+ <button
1876
+ className="text-gray-400 hover:text-white p-2 hover:bg-gray-800/80 rounded-lg transition-colors cursor-pointer"
1877
+ onClick={() => setActiveSession(null)}
1878
+ >
1879
+ <X size={18} />
1880
+ </button>
1881
+ </div>
1882
+
1883
+ {/* Modal Body / Chat Messages */}
1884
+ <div className="flex-1 overflow-y-auto p-6 space-y-4 bg-gray-950/10">
1885
+ {transcriptLoading ? (
1886
+ <div className="flex flex-col items-center justify-center h-full gap-2">
1887
+ <RefreshCw className="animate-spin text-indigo-400" size={24} />
1888
+ <span className="text-xs text-gray-500 font-medium">Loading conversation history...</span>
1889
+ </div>
1890
+ ) : transcript.length === 0 ? (
1891
+ <div className="flex items-center justify-center h-full text-xs text-gray-500">
1892
+ No messages found in this transcript.
1893
+ </div>
1894
+ ) : (
1895
+ transcript.map((msg, idx) => (
1896
+ <div key={idx} className={`flex flex-col ${msg.role === 'user' ? 'items-end' : 'items-start'}`}>
1897
+ <div className="text-[10px] text-gray-500 mb-1 px-1">
1898
+ {msg.role === 'user' ? 'Developer' : activeSession.assistant === 'antigravity' ? 'Antigravity' : activeSession.assistant === 'claude' ? 'Claude' : activeSession.assistant === 'codex' ? 'Codex' : 'Copilot'}
1899
+ {msg.timestamp && ` • ${new Date(msg.timestamp).toLocaleTimeString()}`}
1900
+ </div>
1901
+ <div className={`max-w-[85%] rounded-2xl px-4 py-3 text-xs leading-relaxed whitespace-pre-wrap ${
1902
+ msg.role === 'user'
1903
+ ? 'bg-indigo-650 text-white rounded-tr-none shadow-md border border-indigo-500/10'
1904
+ : 'bg-gray-900 border border-gray-800 text-gray-200 rounded-tl-none shadow-sm font-sans'
1905
+ }`}>
1906
+ {msg.content}
1907
+ </div>
1908
+ </div>
1909
+ ))
1910
+ )}
1911
+ </div>
1912
+
1913
+ {/* Modal Footer */}
1914
+ <div className="px-6 py-4 border-t border-gray-800 bg-gray-950/40 flex justify-between items-center">
1915
+ <span className="text-[11px] text-gray-550">
1916
+ Resuming will copy the shell command and open your code editor.
1917
+ </span>
1918
+ <div className="flex gap-2">
1919
+ <button
1920
+ className="inline-flex items-center justify-center gap-1.5 px-4 py-2 bg-gray-900 border border-gray-800 hover:bg-gray-800 rounded-lg text-xs font-bold text-white transition-all cursor-pointer"
1921
+ onClick={() => {
1922
+ const cmd = activeSession.assistant === 'antigravity' ? `agy --conversation ${activeSession.id}` :
1923
+ activeSession.assistant === 'claude' ? `claude --resume ${activeSession.id}` :
1924
+ activeSession.assistant === 'codex' ? `codex resume ${activeSession.id}` :
1925
+ `copilot --resume ${activeSession.id}`;
1926
+ navigator.clipboard.writeText(cmd);
1927
+ alert(`Copied run command to clipboard:\n\n${cmd}`);
1928
+ }}
1929
+ >
1930
+ <Copy size={13} /> Copy Resume Command
1931
+ </button>
1932
+ <button
1933
+ className="inline-flex items-center justify-center gap-1.5 px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg text-xs font-bold transition-all cursor-pointer"
1934
+ onClick={() => {
1935
+ const ws = workspaces.find(w => w.branchName === activeSession.workspacePath.split(/[\\/]/).pop());
1936
+ handleResumeSession(ws || workspaces[0], activeSession.id, activeSession.assistant);
1937
+ setActiveSession(null);
1938
+ }}
1939
+ >
1940
+ <Play size={12} /> Resume Conversation
1941
+ </button>
1942
+ </div>
1943
+ </div>
1944
+ </div>
1945
+ </div>
1946
+ )}
1947
+ </div>
1948
+ );
1949
+ }