@mrpatronz/nexusflow 0.2.13 → 0.2.18

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 (228) hide show
  1. package/.agents/skills/ccc/SKILL.md +71 -0
  2. package/.agents/skills/ccc/references/management.md +110 -0
  3. package/.agents/skills/ccc/references/settings.md +126 -0
  4. package/.github/workflows/ci.yml +44 -0
  5. package/.github/workflows/dependabot-merge.yml +53 -0
  6. package/.github/workflows/release-desktop.yml +67 -0
  7. package/.github/workflows/release-npm.yml +65 -0
  8. package/.github/workflows/release-vscode.yml +61 -0
  9. package/GETTING_STARTED.md +5 -0
  10. package/README.md +53 -0
  11. package/desktop/build-installer.js +228 -0
  12. package/desktop/build.js +118 -0
  13. package/desktop/installer/nexusflow.iss +35 -0
  14. package/desktop/neutralino.config.json +39 -0
  15. package/desktop/package-lock.json +1060 -0
  16. package/desktop/package.json +14 -0
  17. package/dist/commands/adapter.d.ts +27 -0
  18. package/dist/commands/adapter.d.ts.map +1 -0
  19. package/dist/commands/adapter.js +318 -0
  20. package/dist/commands/adapter.js.map +1 -0
  21. package/dist/commands/add-repo.d.ts.map +1 -1
  22. package/dist/commands/add-repo.js +0 -1
  23. package/dist/commands/add-repo.js.map +1 -1
  24. package/dist/commands/commands.test.js +2 -2
  25. package/dist/commands/commands.test.js.map +1 -1
  26. package/dist/commands/config.d.ts +22 -0
  27. package/dist/commands/config.d.ts.map +1 -0
  28. package/dist/commands/config.js +50 -0
  29. package/dist/commands/config.js.map +1 -0
  30. package/dist/commands/create.d.ts.map +1 -1
  31. package/dist/commands/create.js +14 -17
  32. package/dist/commands/create.js.map +1 -1
  33. package/dist/commands/desktop.d.ts +12 -0
  34. package/dist/commands/desktop.d.ts.map +1 -0
  35. package/dist/commands/desktop.js +119 -0
  36. package/dist/commands/desktop.js.map +1 -0
  37. package/dist/commands/doctor.d.ts.map +1 -1
  38. package/dist/commands/doctor.js +11 -0
  39. package/dist/commands/doctor.js.map +1 -1
  40. package/dist/commands/refresh.d.ts +1 -0
  41. package/dist/commands/refresh.d.ts.map +1 -1
  42. package/dist/commands/refresh.js +2 -14
  43. package/dist/commands/refresh.js.map +1 -1
  44. package/dist/commands/sync.d.ts.map +1 -1
  45. package/dist/commands/sync.js +1 -8
  46. package/dist/commands/sync.js.map +1 -1
  47. package/dist/commands/tui.d.ts +8 -0
  48. package/dist/commands/tui.d.ts.map +1 -0
  49. package/dist/commands/tui.js +439 -0
  50. package/dist/commands/tui.js.map +1 -0
  51. package/dist/commands/ui.d.ts +3 -1
  52. package/dist/commands/ui.d.ts.map +1 -1
  53. package/dist/commands/ui.js +76 -19
  54. package/dist/commands/ui.js.map +1 -1
  55. package/dist/core/adapters/local-storage.d.ts +14 -0
  56. package/dist/core/adapters/local-storage.d.ts.map +1 -0
  57. package/dist/core/adapters/local-storage.js +58 -0
  58. package/dist/core/adapters/local-storage.js.map +1 -0
  59. package/dist/core/adapters/obsidian-storage.d.ts +28 -0
  60. package/dist/core/adapters/obsidian-storage.d.ts.map +1 -0
  61. package/dist/core/adapters/obsidian-storage.js +135 -0
  62. package/dist/core/adapters/obsidian-storage.js.map +1 -0
  63. package/dist/core/adapters/registry.d.ts +8 -0
  64. package/dist/core/adapters/registry.d.ts.map +1 -0
  65. package/dist/core/adapters/registry.js +37 -0
  66. package/dist/core/adapters/registry.js.map +1 -0
  67. package/dist/core/adapters/storage.test.d.ts +2 -0
  68. package/dist/core/adapters/storage.test.d.ts.map +1 -0
  69. package/dist/core/adapters/storage.test.js +106 -0
  70. package/dist/core/adapters/storage.test.js.map +1 -0
  71. package/dist/core/adapters/vault-storage.d.ts +16 -0
  72. package/dist/core/adapters/vault-storage.d.ts.map +1 -0
  73. package/dist/core/adapters/vault-storage.js +77 -0
  74. package/dist/core/adapters/vault-storage.js.map +1 -0
  75. package/dist/core/config.d.ts.map +1 -1
  76. package/dist/core/config.js +28 -4
  77. package/dist/core/config.js.map +1 -1
  78. package/dist/core/plugins/index.d.ts +14 -0
  79. package/dist/core/plugins/index.d.ts.map +1 -0
  80. package/dist/core/plugins/index.js +2 -0
  81. package/dist/core/plugins/index.js.map +1 -0
  82. package/dist/core/plugins/loader.d.ts +2 -0
  83. package/dist/core/plugins/loader.d.ts.map +1 -0
  84. package/dist/core/plugins/loader.js +34 -0
  85. package/dist/core/plugins/loader.js.map +1 -0
  86. package/dist/core/ports/storage.d.ts +59 -0
  87. package/dist/core/ports/storage.d.ts.map +1 -0
  88. package/dist/core/ports/storage.js +10 -0
  89. package/dist/core/ports/storage.js.map +1 -0
  90. package/dist/core/storage.d.ts +10 -0
  91. package/dist/core/storage.d.ts.map +1 -0
  92. package/dist/core/storage.js +29 -0
  93. package/dist/core/storage.js.map +1 -0
  94. package/dist/core/workspace.d.ts.map +1 -1
  95. package/dist/core/workspace.js +110 -16
  96. package/dist/core/workspace.js.map +1 -1
  97. package/dist/generators/base.d.ts.map +1 -1
  98. package/dist/generators/base.js +19 -6
  99. package/dist/generators/base.js.map +1 -1
  100. package/dist/generators/diff-context.d.ts +6 -0
  101. package/dist/generators/diff-context.d.ts.map +1 -0
  102. package/dist/generators/diff-context.js +68 -0
  103. package/dist/generators/diff-context.js.map +1 -0
  104. package/dist/generators/index.d.ts +1 -1
  105. package/dist/generators/index.d.ts.map +1 -1
  106. package/dist/generators/index.js +75 -18
  107. package/dist/generators/index.js.map +1 -1
  108. package/dist/generators/map-generator.d.ts.map +1 -1
  109. package/dist/generators/map-generator.js +7 -11
  110. package/dist/generators/map-generator.js.map +1 -1
  111. package/dist/generators/plan-generator.d.ts.map +1 -1
  112. package/dist/generators/plan-generator.js +3 -5
  113. package/dist/generators/plan-generator.js.map +1 -1
  114. package/dist/gui/app_logo.png +0 -0
  115. package/dist/gui/assets/index-CB-jWded.css +1 -0
  116. package/dist/gui/assets/index-DDXpZZu3.js +25 -0
  117. package/dist/gui/index.html +2 -2
  118. package/dist/index.js +174 -20
  119. package/dist/index.js.map +1 -1
  120. package/dist/server.d.ts.map +1 -1
  121. package/dist/server.js +212 -28
  122. package/dist/server.js.map +1 -1
  123. package/dist/server.test.js +173 -38
  124. package/dist/server.test.js.map +1 -1
  125. package/dist/types.d.ts +11 -2
  126. package/dist/types.d.ts.map +1 -1
  127. package/dist/utils/git.d.ts +16 -0
  128. package/dist/utils/git.d.ts.map +1 -1
  129. package/dist/utils/git.js +52 -0
  130. package/dist/utils/git.js.map +1 -1
  131. package/dist/utils/local-ai.d.ts.map +1 -1
  132. package/dist/utils/local-ai.js +7 -3
  133. package/dist/utils/local-ai.js.map +1 -1
  134. package/dist/utils/multi-git.d.ts +2 -0
  135. package/dist/utils/multi-git.d.ts.map +1 -1
  136. package/dist/utils/multi-git.js +5 -2
  137. package/dist/utils/multi-git.js.map +1 -1
  138. package/dist/utils/update-check.d.ts +3 -1
  139. package/dist/utils/update-check.d.ts.map +1 -1
  140. package/dist/utils/update-check.js +23 -34
  141. package/dist/utils/update-check.js.map +1 -1
  142. package/dist/utils/workflow-advisor.d.ts +16 -0
  143. package/dist/utils/workflow-advisor.d.ts.map +1 -0
  144. package/dist/utils/workflow-advisor.js +100 -0
  145. package/dist/utils/workflow-advisor.js.map +1 -0
  146. package/dist/utils/workflows.d.ts +11 -0
  147. package/dist/utils/workflows.d.ts.map +1 -0
  148. package/dist/utils/workflows.js +156 -0
  149. package/dist/utils/workflows.js.map +1 -0
  150. package/dist/utils/workflows.test.d.ts +2 -0
  151. package/dist/utils/workflows.test.d.ts.map +1 -0
  152. package/dist/utils/workflows.test.js +86 -0
  153. package/dist/utils/workflows.test.js.map +1 -0
  154. package/extension/media/icon.svg +1 -0
  155. package/extension/package-lock.json +4893 -1660
  156. package/extension/package.json +58 -11
  157. package/extension/src/extension.ts +476 -27
  158. package/extension/tsconfig.json +4 -2
  159. package/gui/e2e/wizard.spec.ts +19 -0
  160. package/gui/package-lock.json +342 -381
  161. package/gui/package.json +12 -12
  162. package/gui/public/app_logo.png +0 -0
  163. package/gui/src/App.tsx +991 -81
  164. package/gui/src/features/workspace/WorkspaceList.tsx +30 -30
  165. package/gui/src/index.css +64 -22
  166. package/gui/src/main.tsx +89 -5
  167. package/package.json +11 -12
  168. package/resources/workflows/plan-implement-review.md +8 -0
  169. package/resources/workflows/research-verify.md +6 -0
  170. package/resources/workflows/solo-developer.md +3 -0
  171. package/skills-lock.json +11 -0
  172. package/src/commands/adapter.ts +348 -0
  173. package/src/commands/add-repo.ts +0 -1
  174. package/src/commands/commands.test.ts +3 -2
  175. package/src/commands/config.ts +52 -0
  176. package/src/commands/create.ts +14 -17
  177. package/src/commands/desktop.ts +128 -0
  178. package/src/commands/doctor.ts +13 -0
  179. package/src/commands/mcp.ts +2 -0
  180. package/src/commands/refresh.ts +4 -14
  181. package/src/commands/sync.ts +2 -8
  182. package/src/commands/tui.ts +484 -0
  183. package/src/commands/ui.ts +83 -19
  184. package/src/core/adapters/local-storage.ts +66 -0
  185. package/src/core/adapters/obsidian-storage.ts +151 -0
  186. package/src/core/adapters/registry.ts +44 -0
  187. package/src/core/adapters/storage.test.ts +174 -0
  188. package/src/core/adapters/vault-storage.ts +86 -0
  189. package/src/core/config.ts +29 -4
  190. package/src/core/plugins/index.ts +17 -0
  191. package/src/core/plugins/loader.ts +36 -0
  192. package/src/core/ports/storage.ts +71 -0
  193. package/src/core/storage.ts +37 -0
  194. package/src/core/workspace.ts +122 -15
  195. package/src/generators/base.ts +21 -5
  196. package/src/generators/diff-context.ts +75 -0
  197. package/src/generators/index.ts +93 -21
  198. package/src/generators/map-generator.ts +7 -10
  199. package/src/generators/plan-generator.ts +6 -4
  200. package/src/index.ts +178 -21
  201. package/src/server.test.ts +193 -39
  202. package/src/server.ts +238 -28
  203. package/src/types.ts +14 -2
  204. package/src/utils/git.ts +56 -0
  205. package/src/utils/local-ai.ts +8 -3
  206. package/src/utils/multi-git.ts +16 -9
  207. package/src/utils/update-check.ts +42 -31
  208. package/src/utils/workflow-advisor.ts +118 -0
  209. package/src/utils/workflows.test.ts +114 -0
  210. package/src/utils/workflows.ts +178 -0
  211. package/.github/workflows/release.yml +0 -100
  212. package/dist/commands/pack.d.ts +0 -7
  213. package/dist/commands/pack.d.ts.map +0 -1
  214. package/dist/commands/pack.js +0 -64
  215. package/dist/commands/pack.js.map +0 -1
  216. package/dist/core/packer.d.ts +0 -14
  217. package/dist/core/packer.d.ts.map +0 -1
  218. package/dist/core/packer.js +0 -87
  219. package/dist/core/packer.js.map +0 -1
  220. package/dist/core/packer.test.d.ts +0 -2
  221. package/dist/core/packer.test.d.ts.map +0 -1
  222. package/dist/core/packer.test.js +0 -82
  223. package/dist/core/packer.test.js.map +0 -1
  224. package/dist/gui/assets/index-CFWwqFux.js +0 -23
  225. package/dist/gui/assets/index-Dvc9QMvl.css +0 -2
  226. package/src/commands/pack.ts +0 -73
  227. package/src/core/packer.test.ts +0 -99
  228. package/src/core/packer.ts +0 -107
package/gui/src/App.tsx CHANGED
@@ -17,6 +17,9 @@ import {
17
17
  Copy,
18
18
  X,
19
19
  Cpu,
20
+ Workflow,
21
+ Trash2,
22
+ CheckCircle,
20
23
  } from 'lucide-react';
21
24
  import './App.css';
22
25
  import { WorkspaceList } from './features/workspace/WorkspaceList.js';
@@ -28,6 +31,7 @@ interface LocalLlmConfig {
28
31
  provider: 'ollama' | 'openai-compatible';
29
32
  endpoint: string;
30
33
  model: string;
34
+ apiKey?: string;
31
35
  }
32
36
 
33
37
  interface NexusFlowConfig {
@@ -38,7 +42,25 @@ interface NexusFlowConfig {
38
42
  defaultEditor?: string | null;
39
43
  scanDepth: number;
40
44
  localLlm?: LocalLlmConfig;
41
- packContextXml?: boolean;
45
+ storageProvider?: string;
46
+ adapterConfig?: Record<string, Record<string, any>>;
47
+ plugins?: string[];
48
+ }
49
+
50
+ interface AdapterConfigField {
51
+ key: string;
52
+ label: string;
53
+ type: 'string' | 'boolean' | 'number' | 'path';
54
+ required?: boolean;
55
+ default?: any;
56
+ description?: string;
57
+ }
58
+
59
+ interface StorageAdapterMeta {
60
+ name: string;
61
+ displayName: string;
62
+ description: string;
63
+ configFields: AdapterConfigField[];
42
64
  }
43
65
 
44
66
  interface DetectedAI {
@@ -93,12 +115,12 @@ interface RunningService {
93
115
  startedAt: string;
94
116
  }
95
117
 
96
- const API_BASE = import.meta.env.DEV ? 'http://localhost:3000' : '';
118
+ const API_BASE = (import.meta.env.DEV || (typeof window !== 'undefined' && (window as any).Neutralino)) ? 'http://localhost:3000' : '';
97
119
  const isVsCode = new URLSearchParams(window.location.search).get('env') === 'vscode';
98
120
  let toastIdCounter = 0;
99
121
 
100
122
  export default function App() {
101
- const [view, setView] = useState<'guide' | 'create' | 'workspaces' | 'settings'>('guide');
123
+ const [view, setView] = useState<'guide' | 'create' | 'workspaces' | 'settings' | 'workflows'>('guide');
102
124
 
103
125
  // Toast State
104
126
  interface Toast {
@@ -121,12 +143,15 @@ export default function App() {
121
143
  const [configLoading, setConfigLoading] = useState(true);
122
144
  const [configExists, setConfigExists] = useState<boolean>(true);
123
145
  const [saveStatus, setSaveStatus] = useState<'success' | 'error' | null>(null);
146
+ const [adapters, setAdapters] = useState<StorageAdapterMeta[]>([]);
124
147
 
125
148
  // Update Check State
126
149
  const [updateStatus, setUpdateStatus] = useState<{
127
150
  currentVersion: string;
128
151
  latestVersion: string;
129
152
  updateAvailable: boolean;
153
+ downloadUrl?: string | null;
154
+ releaseNotes?: string;
130
155
  } | null>(null);
131
156
  const [appVersion, setAppVersion] = useState('0.1.5');
132
157
  const [defaultPaths, setDefaultPaths] = useState<{ devDir: string; workspacesDir: string } | null>(null);
@@ -153,13 +178,37 @@ export default function App() {
153
178
  { id: 'worktrees', name: 'Create Git Worktrees', status: 'pending', message: 'Waiting...' },
154
179
  { id: 'analysis', name: 'Analyze Repositories', status: 'pending', message: 'Waiting...' },
155
180
  { id: 'context', name: 'Generate AI Context Files', status: 'pending', message: 'Waiting...' },
156
- { id: 'pack', name: 'Pack Codebase Context', status: 'pending', message: 'Waiting...' },
157
181
  ]);
158
182
 
159
183
  const [toolsStatus, setToolsStatus] = useState<any[]>([]);
160
184
  const [toolsLoading, setToolsLoading] = useState(false);
161
185
  const [updatingToolId, setUpdatingToolId] = useState<string | null>(null);
162
186
 
187
+ // App Autoupdate State
188
+ const [updatingApp, setUpdatingApp] = useState(false);
189
+ const [updateStep, setUpdateStep] = useState<'idle' | 'downloading' | 'applying' | 'error'>('idle');
190
+
191
+ // Workflow Strategy State
192
+ const [workflowTemplates, setWorkflowTemplates] = useState<any[]>([]);
193
+ const [selectedWorkflowId, setSelectedWorkflowId] = useState<string>('plan-implement-review');
194
+ const [customTeamworkInstructions, setCustomTeamworkInstructions] = useState<string>('');
195
+ const [suggestingWorkflow, setSuggestingWorkflow] = useState(false);
196
+ const [suggestedDifficulty, setSuggestedDifficulty] = useState<'simple' | 'moderate' | 'complex' | null>(null);
197
+ const [suggestedRationale, setSuggestedRationale] = useState('');
198
+
199
+ // Workflow Strategy Management State
200
+ const [selectedMgtTemplateId, setSelectedMgtTemplateId] = useState<string | null>(null);
201
+ const [isEditingTemplate, setIsEditingTemplate] = useState(false);
202
+ const [mgtTemplateName, setMgtTemplateName] = useState('');
203
+ const [mgtTemplateContent, setMgtTemplateContent] = useState('');
204
+ const [analysisResult, setAnalysisResult] = useState<string | null>(null);
205
+ const [analyzingTemplate, setAnalyzingTemplate] = useState(false);
206
+ const [savingTemplate, setSavingTemplate] = useState(false);
207
+ const [deletingTemplate, setDeletingTemplate] = useState(false);
208
+ const [selectedInspectAssistant, setSelectedInspectAssistant] = useState<string>('antigravity');
209
+ const [suggestedImprovement, setSuggestedImprovement] = useState<string | null>(null);
210
+ const [mgtAnalysisComment, setMgtAnalysisComment] = useState('');
211
+
163
212
  // Resumption Commands State
164
213
  const [testCommand, setTestCommand] = useState('npm run test');
165
214
  const [mockCommand, setMockCommand] = useState('');
@@ -296,6 +345,55 @@ export default function App() {
296
345
  }
297
346
  };
298
347
 
348
+ const handleAutoUpdate = async () => {
349
+ if (!updateStatus || !updateStatus.downloadUrl) {
350
+ showToast('No update download URL found.', 'error');
351
+ return;
352
+ }
353
+
354
+ setUpdatingApp(true);
355
+ setUpdateStep('downloading');
356
+
357
+ try {
358
+ // 1. Download the installer
359
+ const downloadRes = await fetch(`${API_BASE}/api/updates/download`, {
360
+ method: 'POST',
361
+ headers: { 'Content-Type': 'application/json' },
362
+ body: JSON.stringify({ downloadUrl: updateStatus.downloadUrl }),
363
+ });
364
+ const downloadData = await downloadRes.json();
365
+
366
+ if (!downloadRes.ok || !downloadData.success) {
367
+ throw new Error(downloadData.error || 'Failed to download installer binary');
368
+ }
369
+
370
+ // 2. Apply the installer
371
+ setUpdateStep('applying');
372
+ const applyRes = await fetch(`${API_BASE}/api/updates/apply`, {
373
+ method: 'POST',
374
+ });
375
+ const applyData = await applyRes.json();
376
+
377
+ if (!applyRes.ok || !applyData.success) {
378
+ throw new Error(applyData.error || 'Failed to trigger application update');
379
+ }
380
+
381
+ showToast('Update downloaded! App is restarting...', 'success');
382
+
383
+ // 3. Exit Neutralino client window to unlock files on disk
384
+ if (typeof window !== 'undefined' && (window as any).Neutralino) {
385
+ setTimeout(() => {
386
+ (window as any).Neutralino.app.exit();
387
+ }, 800);
388
+ }
389
+ } catch (err: any) {
390
+ console.error('Update failed:', err);
391
+ setUpdateStep('error');
392
+ showToast(`Update Failed: ${err.message || 'Unknown error'}`, 'error');
393
+ setUpdatingApp(false);
394
+ }
395
+ };
396
+
299
397
  const fetchLlmRecommendation = async () => {
300
398
  try {
301
399
  const res = await fetch(`${API_BASE}/api/local-llm/recommend`);
@@ -331,7 +429,20 @@ export default function App() {
331
429
  }
332
430
  };
333
431
 
432
+ const fetchAdapters = async () => {
433
+ try {
434
+ const res = await fetch(`${API_BASE}/api/adapters`);
435
+ const data = await res.json();
436
+ if (data.adapters) {
437
+ setAdapters(data.adapters);
438
+ }
439
+ } catch (e) {
440
+ console.error('Failed to fetch adapters:', e);
441
+ }
442
+ };
443
+
334
444
  const fetchConfig = async () => {
445
+ fetchAdapters();
335
446
  try {
336
447
  const res = await fetch(`${API_BASE}/api/config`);
337
448
  const data = await res.json();
@@ -404,6 +515,10 @@ export default function App() {
404
515
  const res = await fetch(`${API_BASE}/api/ai-detect`);
405
516
  const data = await res.json();
406
517
  setAiAssistants(data);
518
+ const firstHarness = data.find((ai: any) => ai.detected && ai.command);
519
+ if (firstHarness) {
520
+ setSelectedInspectAssistant(firstHarness.name);
521
+ }
407
522
  setSelectedAI(data.filter((ai: DetectedAI) => ai.detected).map((ai: DetectedAI) => ai.name));
408
523
  } catch (e) {
409
524
  console.error(e);
@@ -442,6 +557,109 @@ export default function App() {
442
557
  }
443
558
  };
444
559
 
560
+ const fetchWorkflowTemplates = async () => {
561
+ try {
562
+ const res = await fetch(`${API_BASE}/api/workflows/templates`);
563
+ if (res.ok) {
564
+ const data = await res.json();
565
+ setWorkflowTemplates(data.templates || []);
566
+
567
+ // Default to plan-implement-review template
568
+ const pir = data.templates?.find((t: any) => t.id === 'plan-implement-review');
569
+ if (pir) {
570
+ setCustomTeamworkInstructions(pir.content);
571
+ }
572
+ }
573
+ } catch (e) {
574
+ console.error('Failed to fetch workflow templates:', e);
575
+ }
576
+ };
577
+
578
+ const handleSaveTemplate = async () => {
579
+ if (!mgtTemplateName.trim() || !mgtTemplateContent.trim()) {
580
+ showToast('Name and content are required.', 'error');
581
+ return;
582
+ }
583
+ setSavingTemplate(true);
584
+ try {
585
+ const res = await fetch(`${API_BASE}/api/workflows/templates`, {
586
+ method: 'POST',
587
+ headers: { 'Content-Type': 'application/json' },
588
+ body: JSON.stringify({
589
+ id: selectedMgtTemplateId,
590
+ name: mgtTemplateName,
591
+ content: mgtTemplateContent,
592
+ }),
593
+ });
594
+ if (res.ok) {
595
+ const data = await res.json();
596
+ showToast('Strategy template saved successfully!', 'success');
597
+ await fetchWorkflowTemplates();
598
+ setSelectedMgtTemplateId(data.template.id);
599
+ setIsEditingTemplate(false);
600
+ setAnalysisResult(null);
601
+ } else {
602
+ const err = await res.json();
603
+ showToast(err.error || 'Failed to save template', 'error');
604
+ }
605
+ } catch (e: any) {
606
+ showToast(e.message || 'Error saving template', 'error');
607
+ } finally {
608
+ setSavingTemplate(false);
609
+ }
610
+ };
611
+
612
+ const handleDeleteTemplate = async (id: string) => {
613
+ if (!window.confirm('Are you sure you want to delete this custom strategy template?')) {
614
+ return;
615
+ }
616
+ setDeletingTemplate(true);
617
+ try {
618
+ const res = await fetch(`${API_BASE}/api/workflows/templates/${encodeURIComponent(id)}`, {
619
+ method: 'DELETE',
620
+ });
621
+ if (res.ok) {
622
+ showToast('Strategy template deleted successfully!', 'success');
623
+ await fetchWorkflowTemplates();
624
+ setSelectedMgtTemplateId(null);
625
+ setAnalysisResult(null);
626
+ } else {
627
+ const err = await res.json();
628
+ showToast(err.error || 'Failed to delete template', 'error');
629
+ }
630
+ } catch (e: any) {
631
+ showToast(e.message || 'Error deleting template', 'error');
632
+ } finally {
633
+ setDeletingTemplate(false);
634
+ }
635
+ };
636
+
637
+ const handleAnalyzeTemplate = async (id: string, content: string, assistant: string) => {
638
+ setAnalyzingTemplate(true);
639
+ setAnalysisResult(null);
640
+ setSuggestedImprovement(null);
641
+ try {
642
+ const res = await fetch(`${API_BASE}/api/workflows/templates/${encodeURIComponent(id)}/analyze`, {
643
+ method: 'POST',
644
+ headers: { 'Content-Type': 'application/json' },
645
+ body: JSON.stringify({ content, assistant, comment: mgtAnalysisComment }),
646
+ });
647
+ if (res.ok) {
648
+ const data = await res.json();
649
+ setAnalysisResult(data.analysis);
650
+ setSuggestedImprovement(data.suggestedImprovement || null);
651
+ showToast('Strategy analysis completed successfully!', 'success');
652
+ } else {
653
+ const err = await res.json();
654
+ showToast(err.error || 'Failed to analyze template using the selected assistant harness.', 'error');
655
+ }
656
+ } catch (e: any) {
657
+ showToast(e.message || 'Error analyzing template', 'error');
658
+ } finally {
659
+ setAnalyzingTemplate(false);
660
+ }
661
+ };
662
+
445
663
  const fetchWorkspaceServices = async (wsId: string, silent = false) => {
446
664
  if (!silent) setServicesLoading(true);
447
665
  try {
@@ -623,6 +841,41 @@ export default function App() {
623
841
  }
624
842
  };
625
843
 
844
+ const handleSuggestWorkflow = async () => {
845
+ if (!description) {
846
+ showToast('Please enter a description for the workspace details first.', 'info');
847
+ return;
848
+ }
849
+ setSuggestingWorkflow(true);
850
+ setSuggestedDifficulty(null);
851
+ setSuggestedRationale('');
852
+ try {
853
+ const res = await fetch(`${API_BASE}/api/workspace/suggest-workflow`, {
854
+ method: 'POST',
855
+ headers: { 'Content-Type': 'application/json' },
856
+ body: JSON.stringify({
857
+ description,
858
+ repos: selectedRepos,
859
+ }),
860
+ });
861
+ const data = await res.json();
862
+ if (res.ok && data.success) {
863
+ setSuggestedDifficulty(data.difficulty);
864
+ setSuggestedRationale(data.rationale);
865
+ setSelectedWorkflowId(data.suggestedWorkflowId);
866
+ setCustomTeamworkInstructions(data.customInstructions);
867
+ showToast(`Suggested strategy populated successfully: ${data.difficulty.toUpperCase()} difficulty.`, 'success');
868
+ } else {
869
+ showToast(data.error || 'Failed to auto-suggest strategy.', 'error');
870
+ }
871
+ } catch (e) {
872
+ console.error(e);
873
+ showToast('Network error while requesting strategy suggestion.', 'error');
874
+ } finally {
875
+ setSuggestingWorkflow(false);
876
+ }
877
+ };
878
+
626
879
  const handleCreateWorkspace = async () => {
627
880
  if (!branchName || selectedRepos.length === 0) return;
628
881
  setCreating(true);
@@ -633,7 +886,6 @@ export default function App() {
633
886
  { id: 'worktrees', name: 'Create Git Worktrees', status: 'pending', message: 'Waiting...' },
634
887
  { id: 'analysis', name: 'Analyze Repositories', status: 'pending', message: 'Waiting...' },
635
888
  { id: 'context', name: 'Generate AI Context Files', status: 'pending', message: 'Waiting...' },
636
- { id: 'pack', name: 'Pack Codebase Context', status: 'pending', message: 'Waiting...' },
637
889
  ]);
638
890
 
639
891
  try {
@@ -646,6 +898,7 @@ export default function App() {
646
898
  repos: selectedRepos,
647
899
  assistants: selectedAI,
648
900
  localLlmEnabled,
901
+ teamworkInstructions: customTeamworkInstructions || undefined,
649
902
  resumption: {
650
903
  testCommand,
651
904
  mockCommand: mockCommand || undefined,
@@ -669,7 +922,7 @@ export default function App() {
669
922
  if (job.status === 'completed') {
670
923
  eventSource.close();
671
924
  setCreatedWorkspace({ path: job.workspacePath });
672
- setActiveStep(3);
925
+ setActiveStep(4);
673
926
  setCreating(false);
674
927
  fetchWorkspaces();
675
928
 
@@ -834,6 +1087,7 @@ export default function App() {
834
1087
  fetchAIAssistants();
835
1088
  fetchWorkspaces();
836
1089
  fetchUpdateStatus();
1090
+ fetchWorkflowTemplates();
837
1091
 
838
1092
  // Support auto-loading workspace from VS Code or URL params
839
1093
  const queryParams = new URLSearchParams(window.location.search);
@@ -979,7 +1233,7 @@ Core Instructions:
979
1233
  fetchGitChanges(wsName);
980
1234
  fetchWorkspaceSessions(wsName);
981
1235
  }
982
- showToast('Repository successfully added. Configurations and Repomix packing updated.', 'success');
1236
+ showToast('Repository successfully added and configurations updated.', 'success');
983
1237
  } else {
984
1238
  showToast(`Failed to add repository: ${data.error || 'Unknown error'}`, 'error');
985
1239
  }
@@ -1157,20 +1411,45 @@ Core Instructions:
1157
1411
  </div>
1158
1412
 
1159
1413
  <div className="pt-2">
1160
- <div className="flex items-center gap-3">
1161
- <input
1162
- type="checkbox"
1163
- id="onboardingPackContextXml"
1164
- className="w-4 h-4 rounded border-slate-800 bg-slate-950/60 text-indigo-650 focus:ring-indigo-500/50 cursor-pointer"
1165
- checked={config.packContextXml || false}
1166
- onChange={(e) => setConfig({ ...config, packContextXml: e.target.checked })}
1167
- />
1168
- <label htmlFor="onboardingPackContextXml" className="text-xs font-semibold text-slate-200 cursor-pointer select-none">
1169
- Pack Codebase Context with Repomix (XML)
1170
- </label>
1171
- </div>
1172
- <span className="text-[10px] text-slate-500 mt-1.5 block leading-normal">
1173
- Aggregates all repository files into a single token-efficient XML file (<code>nexusflow-context.xml</code>) at the workspace root, giving AI assistants immediate access to the full codebase state.
1414
+ <label htmlFor="onboardingStorageProvider" className="text-xs font-semibold text-slate-200 block mb-1">
1415
+ Storage Provider
1416
+ </label>
1417
+ <select
1418
+ id="onboardingStorageProvider"
1419
+ className="w-full text-xs bg-slate-950/60 border border-slate-800 rounded-lg p-2 text-slate-200 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500/50"
1420
+ value={config.storageProvider || 'local'}
1421
+ onChange={(e) => {
1422
+ const val = e.target.value;
1423
+ const newConf = { ...config, storageProvider: val };
1424
+ const selectedAdapter = adapters.find(a => a.name === val);
1425
+ if (selectedAdapter?.configFields?.length) {
1426
+ if (!newConf.adapterConfig) newConf.adapterConfig = {};
1427
+ if (!newConf.adapterConfig[val]) newConf.adapterConfig[val] = {};
1428
+ selectedAdapter.configFields.forEach(f => {
1429
+ if (newConf.adapterConfig![val][f.key] === undefined && f.default !== undefined) {
1430
+ newConf.adapterConfig![val][f.key] = f.default;
1431
+ }
1432
+ });
1433
+ }
1434
+ setConfig(newConf);
1435
+ }}
1436
+ >
1437
+ {adapters.length > 0 ? (
1438
+ adapters.map((a) => (
1439
+ <option key={a.name} value={a.name}>
1440
+ {a.displayName}
1441
+ </option>
1442
+ ))
1443
+ ) : (
1444
+ <>
1445
+ <option value="local">Local Workspace (Folders)</option>
1446
+ <option value="central-vault">Obsidian Central Vault</option>
1447
+ </>
1448
+ )}
1449
+ </select>
1450
+ <span className="text-[10px] text-slate-500 mt-1 block leading-normal">
1451
+ {adapters.find(a => a.name === (config.storageProvider || 'local'))?.description ||
1452
+ 'Choose where to store maps, plans, and knowledge files. Centralized vault keeps repositories 100% clean and allows Obsidian integration.'}
1174
1453
  </span>
1175
1454
  </div>
1176
1455
 
@@ -1428,40 +1707,43 @@ Core Instructions:
1428
1707
  }
1429
1708
 
1430
1709
  return (
1431
- <div className="flex min-h-screen bg-[#060813] bg-gradient-to-br from-[#0c0f24] via-[#060813] to-[#04050a] text-gray-100 font-sans">
1710
+ <div className="flex min-h-screen bg-[#05070c] text-slate-300 font-mono crt-screen">
1432
1711
  {/* Sidebar Navigation */}
1433
- <aside className="w-64 bg-slate-950/40 border-r border-slate-900 flex flex-col p-6 shrink-0 shadow-[0_0_50px_rgba(0,0,0,0.8)] backdrop-blur-xl relative z-10">
1712
+ <aside className="w-64 bg-[#05070c] border-r border-slate-900 flex flex-col p-6 shrink-0 relative z-10 neon-border">
1434
1713
  <div className="flex items-center gap-3 mb-10 px-2">
1435
- <div className="w-9 h-9 bg-gradient-to-tr from-cyan-400 via-indigo-500 to-purple-650 rounded-lg flex items-center justify-center font-extrabold text-white shadow-lg shadow-indigo-500/25 text-lg select-none">
1436
- N
1714
+ <div className="w-9 h-9 bg-gradient-to-tr from-cyan-500 to-violet-600 rounded-lg flex items-center justify-center font-bold text-white shadow-lg shadow-cyan-500/10 text-lg select-none">
1715
+ NF
1716
+ </div>
1717
+ <div>
1718
+ <span className="text-sm font-bold tracking-wider text-white">
1719
+ NexusFlow
1720
+ </span>
1721
+ <span className="block text-[8px] text-cyan-400 font-semibold tracking-widest uppercase mt-0.5">COMMAND_CENTER</span>
1437
1722
  </div>
1438
- <span className="text-xl font-bold tracking-tight bg-gradient-to-r from-white via-slate-200 to-slate-400 bg-clip-text text-transparent">
1439
- NexusFlow
1440
- </span>
1441
1723
  </div>
1442
1724
  <nav className="flex-1">
1443
- <ul className="flex flex-col gap-2.5">
1725
+ <ul className="flex flex-col gap-2">
1444
1726
  <li>
1445
1727
  <button
1446
- className={`w-full flex items-center gap-3 p-3 rounded-xl text-sm font-semibold transition-all cursor-pointer border relative overflow-hidden group ${
1728
+ className={`w-full flex items-center gap-3 p-3 rounded-lg text-xs font-bold transition-all cursor-pointer border relative overflow-hidden group ${
1447
1729
  view === 'guide'
1448
- ? 'text-white bg-indigo-500/10 border-indigo-500/25 shadow-[0_0_20px_rgba(99,102,241,0.08)] before:absolute before:left-0 before:top-2 before:bottom-2 before:w-[3.5px] before:bg-indigo-500 before:rounded-r'
1449
- : 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40 hover:-translate-x-0.5'
1730
+ ? 'text-cyan-400 bg-cyan-950/30 border-cyan-900/60 shadow-[0_0_15px_rgba(6,182,212,0.15)]'
1731
+ : 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40'
1450
1732
  }`}
1451
1733
  onClick={() => {
1452
1734
  setView('guide');
1453
1735
  }}
1454
1736
  >
1455
- <Sparkles size={18} className="text-indigo-400 group-hover:scale-110 transition-transform" />
1737
+ <Sparkles size={16} className="text-cyan-400 group-hover:scale-110 transition-transform" />
1456
1738
  Getting Started
1457
1739
  </button>
1458
1740
  </li>
1459
1741
  <li>
1460
1742
  <button
1461
- className={`w-full flex items-center gap-3 p-3 rounded-xl text-sm font-semibold transition-all cursor-pointer border relative overflow-hidden group ${
1743
+ className={`w-full flex items-center gap-3 p-3 rounded-lg text-xs font-bold transition-all cursor-pointer border relative overflow-hidden group ${
1462
1744
  view === 'create'
1463
- ? 'text-white bg-indigo-500/10 border-indigo-500/25 shadow-[0_0_20px_rgba(99,102,241,0.08)] before:absolute before:left-0 before:top-2 before:bottom-2 before:w-[3.5px] before:bg-indigo-500 before:rounded-r'
1464
- : 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40 hover:-translate-x-0.5'
1745
+ ? 'text-cyan-400 bg-cyan-950/30 border-cyan-900/60 shadow-[0_0_15px_rgba(6,182,212,0.15)]'
1746
+ : 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40'
1465
1747
  }`}
1466
1748
  onClick={() => {
1467
1749
  setView('create');
@@ -1473,51 +1755,74 @@ Core Instructions:
1473
1755
  setLocalLlmEnabled(config?.localLlm?.enabled || false);
1474
1756
  }}
1475
1757
  >
1476
- <PlusCircle size={18} className="text-indigo-400 group-hover:scale-110 transition-transform" />
1758
+ <PlusCircle size={16} className="text-cyan-400 group-hover:scale-110 transition-transform" />
1477
1759
  New Workspace
1478
1760
  </button>
1479
1761
  </li>
1480
1762
  <li>
1481
1763
  <button
1482
- className={`w-full flex items-center gap-3 p-3 rounded-xl text-sm font-semibold transition-all cursor-pointer border relative overflow-hidden group ${
1764
+ className={`w-full flex items-center gap-3 p-3 rounded-lg text-xs font-bold transition-all cursor-pointer border relative overflow-hidden group ${
1483
1765
  view === 'workspaces'
1484
- ? 'text-white bg-indigo-500/10 border-indigo-500/25 shadow-[0_0_20px_rgba(99,102,241,0.08)] before:absolute before:left-0 before:top-2 before:bottom-2 before:w-[3.5px] before:bg-indigo-500 before:rounded-r'
1485
- : 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40 hover:-translate-x-0.5'
1766
+ ? 'text-cyan-400 bg-cyan-950/30 border-cyan-900/60 shadow-[0_0_15px_rgba(6,182,212,0.15)]'
1767
+ : 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40'
1486
1768
  }`}
1487
1769
  onClick={() => {
1488
1770
  setView('workspaces');
1489
1771
  fetchWorkspaces();
1490
1772
  }}
1491
1773
  >
1492
- <FolderGit2 size={18} className="text-indigo-400 group-hover:scale-110 transition-transform" />
1774
+ <FolderGit2 size={16} className="text-cyan-400 group-hover:scale-110 transition-transform" />
1493
1775
  Active Workspaces
1494
1776
  </button>
1495
1777
  </li>
1496
1778
  <li>
1497
1779
  <button
1498
- className={`w-full flex items-center gap-3 p-3 rounded-xl text-sm font-semibold transition-all cursor-pointer border relative overflow-hidden group ${
1780
+ className={`w-full flex items-center gap-3 p-3 rounded-lg text-xs font-bold transition-all cursor-pointer border relative overflow-hidden group ${
1781
+ view === 'workflows'
1782
+ ? 'text-cyan-400 bg-cyan-950/30 border-cyan-900/60 shadow-[0_0_15px_rgba(6,182,212,0.15)]'
1783
+ : 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40'
1784
+ }`}
1785
+ onClick={() => {
1786
+ setView('workflows');
1787
+ fetchWorkflowTemplates();
1788
+ if (workflowTemplates.length > 0) {
1789
+ const first = workflowTemplates[0];
1790
+ setSelectedMgtTemplateId(first.id);
1791
+ setMgtTemplateName(first.name);
1792
+ setMgtTemplateContent(first.content);
1793
+ }
1794
+ setAnalysisResult(null);
1795
+ }}
1796
+ >
1797
+ <Workflow size={16} className="text-cyan-400 group-hover:scale-110 transition-transform" />
1798
+ Team Strategies
1799
+ </button>
1800
+ </li>
1801
+ <li>
1802
+ <button
1803
+ className={`w-full flex items-center gap-3 p-3 rounded-lg text-xs font-bold transition-all cursor-pointer border relative overflow-hidden group ${
1499
1804
  view === 'settings'
1500
- ? 'text-white bg-indigo-500/10 border-indigo-500/25 shadow-[0_0_20px_rgba(99,102,241,0.08)] before:absolute before:left-0 before:top-2 before:bottom-2 before:w-[3.5px] before:bg-indigo-500 before:rounded-r'
1501
- : 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40 hover:-translate-x-0.5'
1805
+ ? 'text-cyan-400 bg-cyan-950/30 border-cyan-900/60 shadow-[0_0_15px_rgba(6,182,212,0.15)]'
1806
+ : 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40'
1502
1807
  }`}
1503
1808
  onClick={() => {
1504
1809
  setView('settings');
1505
1810
  fetchConfig();
1506
1811
  }}
1507
1812
  >
1508
- <SettingsIcon size={18} className="text-indigo-400 group-hover:scale-110 transition-transform" />
1813
+ <SettingsIcon size={16} className="text-cyan-400 group-hover:scale-110 transition-transform" />
1509
1814
  Settings
1510
1815
  </button>
1511
1816
  </li>
1512
1817
  </ul>
1513
1818
  </nav>
1514
1819
  <div className="pt-6 border-t border-slate-900 text-[10px] text-slate-500 text-center tracking-wider font-semibold uppercase">
1515
- NexusFlow Engine v{appVersion}
1820
+ SYSTEM_UI: ONLINE v{appVersion}
1516
1821
  </div>
1517
1822
  </aside>
1518
1823
 
1519
1824
  {/* Main Content Area */}
1520
- <main className="flex-1 p-10 overflow-y-auto max-w-7xl w-full mx-auto">
1825
+ <main className="flex-1 p-8 overflow-y-auto max-w-7xl w-full mx-auto">
1521
1826
  {configLoading ? (
1522
1827
  <div className="flex flex-col items-center justify-center py-40 gap-4 text-gray-400">
1523
1828
  <RefreshCw className="animate-spin text-indigo-400" size={32} />
@@ -1530,24 +1835,46 @@ Core Instructions:
1530
1835
  <div className="mb-6 p-4 bg-gradient-to-r from-amber-500/10 to-orange-600/10 border border-amber-500/30 rounded-xl shadow-lg flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 backdrop-blur-sm">
1531
1836
  <div className="flex items-center gap-3">
1532
1837
  <div className="w-10 h-10 rounded-lg bg-amber-500/10 border border-amber-500/20 flex items-center justify-center text-amber-400 shrink-0">
1533
- <Sparkles size={20} />
1838
+ {updatingApp ? (
1839
+ <RefreshCw size={20} className="animate-spin text-amber-400" />
1840
+ ) : (
1841
+ <Sparkles size={20} />
1842
+ )}
1534
1843
  </div>
1535
1844
  <div>
1536
1845
  <h4 className="text-sm font-bold text-amber-300">
1537
- A new version of NexusFlow is available!
1846
+ {updatingApp ? (
1847
+ updateStep === 'downloading' ? 'Downloading Update...' : 'Applying Update...'
1848
+ ) : (
1849
+ 'A new version of NexusFlow is available!'
1850
+ )}
1538
1851
  </h4>
1539
1852
  <p className="text-xs text-gray-400 mt-0.5">
1540
- Upgrade from v{updateStatus.currentVersion} to v{updateStatus.latestVersion} to get the latest features and bug fixes.
1853
+ {updatingApp ? (
1854
+ updateStep === 'downloading' ? 'Fetching installer from GitHub Releases...' : 'Closing app and launching silent installer setup...'
1855
+ ) : (
1856
+ `Upgrade from v${updateStatus.currentVersion} to v${updateStatus.latestVersion} to get the latest features and bug fixes.`
1857
+ )}
1541
1858
  </p>
1542
1859
  </div>
1543
1860
  </div>
1544
1861
  <div className="flex items-center gap-3 shrink-0">
1862
+ {typeof window !== 'undefined' && (window as any).Neutralino && updateStatus.downloadUrl && (
1863
+ <button
1864
+ onClick={handleAutoUpdate}
1865
+ disabled={updatingApp}
1866
+ className="px-4 py-2 bg-gradient-to-r from-emerald-500 to-teal-600 hover:from-emerald-600 hover:to-teal-700 text-white font-bold text-xs rounded-lg transition-all shadow-md disabled:opacity-50 cursor-pointer flex items-center gap-1.5"
1867
+ >
1868
+ {updatingApp ? 'Installing...' : 'Install Automatically'}
1869
+ </button>
1870
+ )}
1545
1871
  <button
1546
1872
  onClick={() => {
1547
1873
  navigator.clipboard.writeText('npm install -g @mrpatronz/nexusflow');
1548
1874
  showToast('Update command copied to clipboard!', 'success');
1549
1875
  }}
1550
- className="px-4 py-2 bg-amber-500 hover:bg-amber-600 text-[#060813] font-bold text-xs rounded-lg transition-all shadow-md shadow-amber-500/10 cursor-pointer flex items-center gap-1.5"
1876
+ disabled={updatingApp}
1877
+ className="px-4 py-2 bg-amber-500 hover:bg-amber-600 text-[#060813] font-bold text-xs rounded-lg transition-all shadow-md shadow-amber-500/10 disabled:opacity-50 cursor-pointer flex items-center gap-1.5"
1551
1878
  >
1552
1879
  Copy Update Command
1553
1880
  </button>
@@ -1612,17 +1939,7 @@ Core Instructions:
1612
1939
  </div>
1613
1940
  </div>
1614
1941
 
1615
- <div className="flex gap-4">
1616
- <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">
1617
- 4
1618
- </div>
1619
- <div>
1620
- <h3 className="text-sm font-semibold text-white">Repomix Packing (Optional)</h3>
1621
- <p className="text-xs text-gray-400 mt-1">
1622
- If enabled, NexusFlow aggregates the entire multi-repo codebase using <code>Repomix</code> into a token-efficient XML file <code>nexusflow-context.xml</code>, giving AI immediate access to the full codebase state. You can toggle this setting in Settings.
1623
- </p>
1624
- </div>
1625
- </div>
1942
+
1626
1943
  </div>
1627
1944
 
1628
1945
  {/* Right: Quick actions and Telemetry info */}
@@ -1722,11 +2039,11 @@ Core Instructions:
1722
2039
  </header>
1723
2040
 
1724
2041
  {/* Progress Circle bar */}
1725
- <div className="flex justify-between items-center max-w-xl mx-auto mb-14 relative px-4">
2042
+ <div className="flex justify-between items-center max-w-2xl mx-auto mb-14 relative px-4">
1726
2043
  <div className="absolute top-4 left-6 right-6 h-[2px] bg-gray-800 -z-10"></div>
1727
2044
  <div
1728
2045
  className="absolute top-4 left-6 h-[2px] bg-gradient-to-r from-cyan-400 to-indigo-500 -z-10 transition-all duration-300"
1729
- style={{ width: `${(activeStep / 3) * 92}%` }}
2046
+ style={{ width: `${(activeStep / 4) * 95}%` }}
1730
2047
  ></div>
1731
2048
  <div className="flex flex-col items-center gap-2">
1732
2049
  <div className={`w-8 h-8 rounded-full border-2 flex items-center justify-center font-bold text-xs transition-all ${
@@ -1754,11 +2071,19 @@ Core Instructions:
1754
2071
  </div>
1755
2072
  <div className="flex flex-col items-center gap-2">
1756
2073
  <div className={`w-8 h-8 rounded-full border-2 flex items-center justify-center font-bold text-xs transition-all ${
1757
- 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'
2074
+ activeStep > 3 ? 'bg-emerald-500 border-emerald-500 text-white' : activeStep === 3 ? 'border-indigo-500 bg-[#0b0f19] text-white shadow-lg shadow-indigo-500/20' : 'border-gray-800 bg-gray-900 text-gray-500'
2075
+ }`}>
2076
+ {activeStep > 3 ? <Check size={14} /> : '4'}
2077
+ </div>
2078
+ <span className={`text-[11px] font-semibold tracking-wide uppercase ${activeStep === 3 ? 'text-white' : 'text-gray-500'}`}>Strategy</span>
2079
+ </div>
2080
+ <div className="flex flex-col items-center gap-2">
2081
+ <div className={`w-8 h-8 rounded-full border-2 flex items-center justify-center font-bold text-xs transition-all ${
2082
+ activeStep === 4 ? 'border-emerald-500 bg-[#0b0f19] text-emerald-400 shadow-lg shadow-emerald-500/20' : 'border-gray-800 bg-gray-900 text-gray-500'
1758
2083
  }`}>
1759
- 4
2084
+ 5
1760
2085
  </div>
1761
- <span className={`text-[11px] font-semibold tracking-wide uppercase ${activeStep === 3 ? 'text-emerald-400' : 'text-gray-500'}`}>Complete</span>
2086
+ <span className={`text-[11px] font-semibold tracking-wide uppercase ${activeStep === 4 ? 'text-emerald-400' : 'text-gray-500'}`}>Complete</span>
1762
2087
  </div>
1763
2088
  </div>
1764
2089
 
@@ -2120,6 +2445,189 @@ Core Instructions:
2120
2445
  >
2121
2446
  <ArrowLeft size={16} /> Back
2122
2447
  </button>
2448
+ <button
2449
+ 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"
2450
+ onClick={() => setActiveStep(3)}
2451
+ >
2452
+ Next Step <ArrowRight size={16} />
2453
+ </button>
2454
+ </div>
2455
+ </>
2456
+ )}
2457
+ </div>
2458
+ )}
2459
+
2460
+ {/* Step 3: Team Strategy */}
2461
+ {activeStep === 3 && (
2462
+ <div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-8 shadow-xl backdrop-blur-sm">
2463
+ {creating ? (
2464
+ <div className="flex flex-col items-center py-6">
2465
+ <h3 className="text-lg font-bold text-white mb-2 flex items-center gap-2.5">
2466
+ <RefreshCw className="animate-spin text-indigo-400" size={20} />
2467
+ Building Workspace...
2468
+ </h3>
2469
+ <p className="text-xs text-gray-400 mb-8">
2470
+ Setting up your multi-repo workspace. This will take a moment.
2471
+ </p>
2472
+
2473
+ <div className="w-full max-w-md space-y-4">
2474
+ {creationSteps.map((step) => {
2475
+ const isPending = step.status === 'pending';
2476
+ const isRunning = step.status === 'running';
2477
+ const isCompleted = step.status === 'completed';
2478
+ const isFailed = step.status === 'failed';
2479
+
2480
+ return (
2481
+ <div
2482
+ key={step.id}
2483
+ className={`flex items-start gap-4 p-4 rounded-xl border transition-all duration-300 ${
2484
+ isRunning
2485
+ ? 'bg-indigo-500/10 border-indigo-500/50 shadow-md shadow-indigo-500/5'
2486
+ : isCompleted
2487
+ ? 'bg-emerald-500/5 border-emerald-500/20 opacity-80'
2488
+ : isFailed
2489
+ ? 'bg-rose-500/5 border-rose-500/30'
2490
+ : 'bg-gray-900/20 border-gray-800/40 opacity-40'
2491
+ }`}
2492
+ >
2493
+ <div className="mt-0.5">
2494
+ {isRunning && (
2495
+ <div className="relative flex h-5 w-5 items-center justify-center">
2496
+ <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-indigo-400 opacity-75"></span>
2497
+ <RefreshCw className="animate-spin text-indigo-400 relative" size={16} />
2498
+ </div>
2499
+ )}
2500
+ {isCompleted && (
2501
+ <div className="h-5 w-5 rounded-full bg-emerald-500/20 border border-emerald-500/40 flex items-center justify-center text-emerald-400">
2502
+ <Check size={12} />
2503
+ </div>
2504
+ )}
2505
+ {isFailed && (
2506
+ <div className="h-5 w-5 rounded-full bg-rose-500/20 border border-rose-500/40 flex items-center justify-center text-rose-400">
2507
+ <AlertTriangle size={12} />
2508
+ </div>
2509
+ )}
2510
+ {isPending && (
2511
+ <div className="h-5 w-5 rounded-full border-2 border-gray-800 flex items-center justify-center text-gray-600">
2512
+ <div className="w-1.5 h-1.5 rounded-full bg-gray-800"></div>
2513
+ </div>
2514
+ )}
2515
+ </div>
2516
+ <div className="flex-1 min-w-0">
2517
+ <h4 className={`text-sm font-bold truncate ${
2518
+ isRunning ? 'text-indigo-400' : isCompleted ? 'text-emerald-400' : isFailed ? 'text-rose-400' : 'text-gray-500'
2519
+ }`}>
2520
+ {step.name}
2521
+ </h4>
2522
+ <p className="text-xs text-gray-400 mt-1 font-mono break-words leading-relaxed">
2523
+ {step.message}
2524
+ </p>
2525
+ </div>
2526
+ </div>
2527
+ );
2528
+ })}
2529
+ </div>
2530
+
2531
+ {creationError && (
2532
+ <div className="mt-6 p-4 bg-rose-500/10 border border-rose-500/20 text-rose-450 rounded-xl text-xs w-full max-w-md flex flex-col gap-3">
2533
+ <span className="font-bold flex items-center gap-1.5">
2534
+ <AlertCircle size={14} className="text-rose-450" /> Build Failed
2535
+ </span>
2536
+ <span className="font-mono">{creationError}</span>
2537
+ <button
2538
+ className="w-full mt-2 py-2 px-4 rounded-lg bg-rose-600 hover:bg-rose-700 text-white font-bold text-[11px] transition-colors cursor-pointer"
2539
+ onClick={() => {
2540
+ setCreating(false);
2541
+ setCreationError(null);
2542
+ }}
2543
+ >
2544
+ Dismiss
2545
+ </button>
2546
+ </div>
2547
+ )}
2548
+ </div>
2549
+ ) : (
2550
+ <>
2551
+ <div className="mb-8">
2552
+ <div className="flex justify-between items-center mb-2">
2553
+ <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider">Team Collaboration Strategy</label>
2554
+ <button
2555
+ type="button"
2556
+ className="px-3 py-1.5 rounded-lg bg-indigo-650 hover:bg-indigo-700 text-white font-bold text-[11px] transition-all cursor-pointer flex items-center gap-1.5 disabled:opacity-50"
2557
+ onClick={handleSuggestWorkflow}
2558
+ disabled={suggestingWorkflow}
2559
+ >
2560
+ <Sparkles size={12} className={suggestingWorkflow ? "animate-spin" : ""} />
2561
+ {suggestingWorkflow ? "Analyzing..." : "Auto-Suggest Strategy"}
2562
+ </button>
2563
+ </div>
2564
+ <p className="text-xs text-gray-500 mb-4">
2565
+ Select an agent cooperation pattern. This writes instructions to <code>AGENTS.md</code> directing how the team coordinates.
2566
+ </p>
2567
+
2568
+ {suggestedDifficulty && (
2569
+ <div className="mb-6 p-4 bg-indigo-950/20 border border-indigo-500/30 rounded-xl text-xs flex flex-col gap-2 shadow-inner">
2570
+ <div className="flex items-center gap-2">
2571
+ <span className="font-bold text-gray-300">Suggested Task Difficulty:</span>
2572
+ <span className={`px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wide ${
2573
+ suggestedDifficulty === 'simple'
2574
+ ? 'bg-emerald-500/20 border border-emerald-500/30 text-emerald-400'
2575
+ : suggestedDifficulty === 'moderate'
2576
+ ? 'bg-amber-500/20 border border-amber-500/30 text-amber-400'
2577
+ : 'bg-rose-500/20 border border-rose-500/30 text-rose-450'
2578
+ }`}>
2579
+ {suggestedDifficulty}
2580
+ </span>
2581
+ </div>
2582
+ <p className="text-gray-400 leading-relaxed font-sans">
2583
+ {suggestedRationale}
2584
+ </p>
2585
+ </div>
2586
+ )}
2587
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
2588
+ {workflowTemplates.map((template) => {
2589
+ const isSelected = selectedWorkflowId === template.id;
2590
+ return (
2591
+ <div
2592
+ key={template.id}
2593
+ className={`bg-[#111827]/60 border rounded-xl p-4 flex flex-col gap-2 cursor-pointer hover:border-gray-700 transition-all ${
2594
+ isSelected ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
2595
+ }`}
2596
+ onClick={() => {
2597
+ setSelectedWorkflowId(template.id);
2598
+ setCustomTeamworkInstructions(template.content);
2599
+ }}
2600
+ >
2601
+ <span className="text-sm font-bold text-white">{template.name}</span>
2602
+ <p className="text-[11px] text-gray-500 leading-relaxed font-sans">
2603
+ {template.description}
2604
+ </p>
2605
+ </div>
2606
+ );
2607
+ })}
2608
+ </div>
2609
+ </div>
2610
+
2611
+ <div className="mb-8">
2612
+ <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Cooperation Instructions (AGENTS.md)</label>
2613
+ <p className="text-xs text-gray-500 mb-3 font-sans">
2614
+ You can customize these instructions directly. They will be saved in the workspace context.
2615
+ </p>
2616
+ <textarea
2617
+ className="w-full bg-[#111827] border border-gray-850 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-3 text-white placeholder-gray-605 transition-all outline-none text-xs font-mono min-h-[220px] resize-y shadow-inner leading-relaxed"
2618
+ value={customTeamworkInstructions}
2619
+ onChange={(e) => setCustomTeamworkInstructions(e.target.value)}
2620
+ placeholder="Enter custom instructions for how the AI agents should coordinate..."
2621
+ />
2622
+ </div>
2623
+
2624
+ <div className="flex justify-between">
2625
+ <button
2626
+ 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"
2627
+ onClick={() => setActiveStep(2)}
2628
+ >
2629
+ <ArrowLeft size={16} /> Back
2630
+ </button>
2123
2631
  <button
2124
2632
  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"
2125
2633
  disabled={creating}
@@ -2141,8 +2649,8 @@ Core Instructions:
2141
2649
  </div>
2142
2650
  )}
2143
2651
 
2144
- {/* Step 3: Success Screen */}
2145
- {activeStep === 3 && createdWorkspace && (
2652
+ {/* Step 4: Success Screen */}
2653
+ {activeStep === 4 && createdWorkspace && (
2146
2654
  <div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-10 text-center shadow-xl backdrop-blur-sm">
2147
2655
  <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">
2148
2656
  <Check size={36} />
@@ -2252,6 +2760,298 @@ Core Instructions:
2252
2760
  />
2253
2761
  )}
2254
2762
 
2763
+ {/* View: Workflows View */}
2764
+ {view === 'workflows' && (
2765
+ <div className="max-w-6xl mx-auto">
2766
+ <header className="mb-8">
2767
+ <h1 className="text-3xl font-extrabold tracking-tight text-white mb-2">Team Collaboration Strategies</h1>
2768
+ <p className="text-sm text-gray-400 font-sans">Predefine and review custom teamwork guidelines injected into workspaces' AGENTS.md files.</p>
2769
+ </header>
2770
+
2771
+ <div className="grid grid-cols-1 lg:grid-cols-12 gap-8 text-left">
2772
+ {/* Left sidebar: Strategy List */}
2773
+ <div className="lg:col-span-4 flex flex-col gap-4">
2774
+ <div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-5 shadow-md flex flex-col gap-4">
2775
+ <div className="flex justify-between items-center">
2776
+ <h3 className="text-xs font-semibold uppercase tracking-wider text-gray-500">Strategies</h3>
2777
+ <button
2778
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-bold bg-indigo-500 hover:bg-indigo-600 text-white transition-all cursor-pointer shadow-md shadow-indigo-500/10"
2779
+ onClick={() => {
2780
+ setIsEditingTemplate(true);
2781
+ setMgtTemplateName('New Strategy');
2782
+ setMgtTemplateContent('# New Strategy\n\nWrite custom teamwork rules here for subagent orchestration...');
2783
+ setSelectedMgtTemplateId(null);
2784
+ setAnalysisResult(null);
2785
+ }}
2786
+ >
2787
+ <PlusCircle size={12} /> Add New
2788
+ </button>
2789
+ </div>
2790
+
2791
+ <div className="flex flex-col gap-2 max-h-[480px] overflow-y-auto pr-1">
2792
+ {workflowTemplates.map((template) => {
2793
+ const isSelected = selectedMgtTemplateId === template.id;
2794
+ return (
2795
+ <div
2796
+ key={template.id}
2797
+ className={`p-3 rounded-lg border text-left cursor-pointer transition-all flex flex-col gap-1.5 ${
2798
+ isSelected
2799
+ ? 'border-indigo-500 bg-indigo-500/5'
2800
+ : 'border-gray-800/80 bg-gray-900/10 hover:border-gray-700/60 hover:bg-gray-800/10'
2801
+ }`}
2802
+ onClick={() => {
2803
+ setSelectedMgtTemplateId(template.id);
2804
+ setIsEditingTemplate(false);
2805
+ setMgtTemplateName(template.name);
2806
+ setMgtTemplateContent(template.content);
2807
+ setAnalysisResult(null);
2808
+ }}
2809
+ >
2810
+ <div className="flex items-center justify-between gap-2">
2811
+ <span className={`text-xs font-bold ${isSelected ? 'text-indigo-400' : 'text-white'}`}>
2812
+ {template.name}
2813
+ </span>
2814
+ <span
2815
+ className={`text-[9px] px-1.5 py-0.5 rounded font-mono uppercase tracking-wider font-semibold border ${
2816
+ template.custom
2817
+ ? 'text-cyan-400 border-cyan-500/20 bg-cyan-500/5'
2818
+ : 'text-gray-400 border-gray-800 bg-gray-800/20'
2819
+ }`}
2820
+ >
2821
+ {template.custom ? 'Custom' : 'Built-in'}
2822
+ </span>
2823
+ </div>
2824
+ <p className="text-[10px] text-gray-500 line-clamp-2 leading-relaxed font-sans">
2825
+ {template.description}
2826
+ </p>
2827
+ </div>
2828
+ );
2829
+ })}
2830
+ </div>
2831
+ </div>
2832
+ </div>
2833
+
2834
+ {/* Right side: Strategy Detail / Edit Panel */}
2835
+ <div className="lg:col-span-8 flex flex-col gap-6">
2836
+ {(selectedMgtTemplateId || isEditingTemplate) ? (
2837
+ <div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-6 shadow-md flex flex-col gap-5">
2838
+ <div className="flex justify-between items-start gap-4">
2839
+ <div className="flex-1">
2840
+ {isEditingTemplate ? (
2841
+ <div className="flex flex-col gap-2">
2842
+ <label className="text-xs font-semibold text-gray-400 uppercase tracking-wider">Strategy Name</label>
2843
+ <input
2844
+ type="text"
2845
+ className="w-full bg-[#111827] border border-gray-850 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-2 text-white text-sm outline-none transition-all"
2846
+ value={mgtTemplateName}
2847
+ onChange={(e) => setMgtTemplateName(e.target.value)}
2848
+ placeholder="e.g. Test-Driven Development"
2849
+ />
2850
+ </div>
2851
+ ) : (
2852
+ <div>
2853
+ <h2 className="text-xl font-bold text-white flex items-center gap-2">
2854
+ {mgtTemplateName}
2855
+ {workflowTemplates.find(t => t.id === selectedMgtTemplateId)?.custom && (
2856
+ <span className="text-[10px] px-2 py-0.5 rounded font-mono bg-cyan-500/10 border border-cyan-500/20 text-cyan-400 uppercase">
2857
+ Custom Template
2858
+ </span>
2859
+ )}
2860
+ </h2>
2861
+ <p className="text-xs text-gray-500 mt-1 font-sans">
2862
+ {workflowTemplates.find(t => t.id === selectedMgtTemplateId)?.description || 'Custom strategy template.'}
2863
+ </p>
2864
+ </div>
2865
+ )}
2866
+ </div>
2867
+
2868
+ <div className="flex gap-2">
2869
+ {isEditingTemplate ? (
2870
+ <>
2871
+ <button
2872
+ className="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-bold bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white transition-all cursor-pointer"
2873
+ onClick={() => {
2874
+ if (selectedMgtTemplateId) {
2875
+ const original = workflowTemplates.find(t => t.id === selectedMgtTemplateId);
2876
+ if (original) {
2877
+ setMgtTemplateName(original.name);
2878
+ setMgtTemplateContent(original.content);
2879
+ }
2880
+ setIsEditingTemplate(false);
2881
+ } else {
2882
+ setIsEditingTemplate(false);
2883
+ if (workflowTemplates.length > 0) {
2884
+ const first = workflowTemplates[0];
2885
+ setSelectedMgtTemplateId(first.id);
2886
+ setMgtTemplateName(first.name);
2887
+ setMgtTemplateContent(first.content);
2888
+ }
2889
+ }
2890
+ }}
2891
+ >
2892
+ Cancel
2893
+ </button>
2894
+ <button
2895
+ className="inline-flex items-center gap-1.5 px-4 py-2 rounded-lg text-xs font-bold bg-indigo-500 hover:bg-indigo-600 text-white transition-all cursor-pointer shadow-md shadow-indigo-500/10"
2896
+ onClick={handleSaveTemplate}
2897
+ disabled={savingTemplate}
2898
+ >
2899
+ {savingTemplate ? 'Saving...' : 'Save Strategy'}
2900
+ </button>
2901
+ </>
2902
+ ) : (
2903
+ <>
2904
+ {workflowTemplates.find(t => t.id === selectedMgtTemplateId)?.custom && (
2905
+ <>
2906
+ <button
2907
+ className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-xs font-bold bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white transition-all cursor-pointer"
2908
+ onClick={() => setIsEditingTemplate(true)}
2909
+ >
2910
+ Edit
2911
+ </button>
2912
+ <button
2913
+ className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-xs font-bold bg-rose-500/10 border border-rose-500/20 hover:bg-rose-500/20 text-rose-400 transition-all cursor-pointer"
2914
+ onClick={() => handleDeleteTemplate(selectedMgtTemplateId!)}
2915
+ disabled={deletingTemplate}
2916
+ >
2917
+ <Trash2 size={12} /> Delete
2918
+ </button>
2919
+ </>
2920
+ )}
2921
+ {!workflowTemplates.find(t => t.id === selectedMgtTemplateId)?.custom && (
2922
+ <button
2923
+ className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-xs font-bold bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white transition-all cursor-pointer"
2924
+ onClick={() => {
2925
+ setIsEditingTemplate(true);
2926
+ setMgtTemplateName(`${mgtTemplateName} Copy`);
2927
+ setMgtTemplateContent(mgtTemplateContent);
2928
+ setSelectedMgtTemplateId(null);
2929
+ setAnalysisResult(null);
2930
+ }}
2931
+ >
2932
+ Duplicate & Customize
2933
+ </button>
2934
+ )}
2935
+ </>
2936
+ )}
2937
+ </div>
2938
+ </div>
2939
+
2940
+ <div className="flex flex-col gap-2">
2941
+ <label className="text-xs font-semibold text-gray-400 uppercase tracking-wider">Guidelines Markdown</label>
2942
+ <textarea
2943
+ className="w-full bg-[#111827] border border-gray-850 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 font-mono min-h-[250px] resize-y shadow-inner leading-relaxed"
2944
+ value={mgtTemplateContent}
2945
+ onChange={(e) => setMgtTemplateContent(e.target.value)}
2946
+ disabled={!isEditingTemplate}
2947
+ placeholder="Write cooperation guidelines in Markdown..."
2948
+ />
2949
+ </div>
2950
+
2951
+ {!isEditingTemplate && selectedMgtTemplateId && (
2952
+ <div className="border-t border-gray-800/60 pt-5 flex flex-col gap-4">
2953
+ <div className="flex flex-col gap-4">
2954
+ <div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
2955
+ <div className="flex flex-col">
2956
+ <span className="text-xs font-bold text-white flex items-center gap-1.5">
2957
+ <Sparkles size={14} className="text-indigo-400" /> AI Strategy Analysis
2958
+ </span>
2959
+ <span className="text-[10px] text-gray-500 mt-0.5 font-sans">Select an AI assistant harness installed on your system to inspect these guidelines.</span>
2960
+ </div>
2961
+
2962
+ <div className="flex items-center gap-3 w-full sm:w-auto justify-end">
2963
+ <div className="flex items-center gap-2">
2964
+ <span className="text-[10px] text-gray-400 font-semibold uppercase tracking-wider font-sans">Harness:</span>
2965
+ <select
2966
+ className="bg-gray-900 border border-gray-800 text-xs text-white rounded-lg px-2.5 py-1.5 focus:border-indigo-500 transition-all outline-none disabled:opacity-40"
2967
+ value={selectedInspectAssistant}
2968
+ onChange={(e) => setSelectedInspectAssistant(e.target.value)}
2969
+ disabled={aiAssistants.filter(ai => ai.detected && ai.command).length === 0}
2970
+ >
2971
+ {aiAssistants.filter(ai => ai.detected && ai.command).length > 0 ? (
2972
+ aiAssistants
2973
+ .filter(ai => ai.detected && ai.command)
2974
+ .map(ai => (
2975
+ <option key={ai.name} value={ai.name}>
2976
+ {ai.displayName}
2977
+ </option>
2978
+ ))
2979
+ ) : (
2980
+ <option value="">No Harness Found</option>
2981
+ )}
2982
+ </select>
2983
+ </div>
2984
+
2985
+ <button
2986
+ className="inline-flex items-center gap-1.5 px-4 py-2 rounded-lg text-xs font-bold bg-indigo-500 hover:bg-indigo-600 text-white transition-all cursor-pointer shadow-md shadow-indigo-500/10 disabled:opacity-40"
2987
+ onClick={() => handleAnalyzeTemplate(selectedMgtTemplateId, mgtTemplateContent, selectedInspectAssistant)}
2988
+ disabled={analyzingTemplate || aiAssistants.filter(ai => ai.detected && ai.command).length === 0}
2989
+ >
2990
+ {analyzingTemplate ? (
2991
+ <>
2992
+ <RefreshCw className="animate-spin" size={12} /> Inspecting...
2993
+ </>
2994
+ ) : (
2995
+ <>
2996
+ <Cpu size={12} /> Inspect Strategy
2997
+ </>
2998
+ )}
2999
+ </button>
3000
+ </div>
3001
+ </div>
3002
+
3003
+ <div className="flex flex-col gap-2">
3004
+ <label className="text-[10px] font-semibold text-gray-400 uppercase tracking-wider text-left">Evaluation Focus / Instructions (Optional)</label>
3005
+ <textarea
3006
+ className="w-full bg-[#111827]/40 border border-gray-850 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-2 text-white placeholder-gray-600 transition-all outline-none text-xs min-h-[60px] resize-y leading-relaxed font-sans"
3007
+ value={mgtAnalysisComment}
3008
+ onChange={(e) => setMgtAnalysisComment(e.target.value)}
3009
+ placeholder="e.g. Focus on checking if timeouts are handled well, check subagent roles coordination..."
3010
+ disabled={analyzingTemplate}
3011
+ />
3012
+ </div>
3013
+ </div>
3014
+
3015
+ {analysisResult && (
3016
+ <div className="flex flex-col gap-4">
3017
+ <div className="bg-[#1e1e38]/20 border border-indigo-500/10 rounded-xl p-5 text-xs text-gray-300 leading-relaxed font-sans max-h-[300px] overflow-y-auto text-left whitespace-pre-wrap select-text">
3018
+ {analysisResult}
3019
+ </div>
3020
+ {suggestedImprovement && (
3021
+ <div className="flex justify-end">
3022
+ <button
3023
+ className="inline-flex items-center gap-1.5 px-4 py-2.5 rounded-lg text-xs font-bold bg-green-600 hover:bg-green-700 text-white transition-all cursor-pointer shadow-md shadow-green-600/10"
3024
+ onClick={() => {
3025
+ setMgtTemplateContent(suggestedImprovement);
3026
+ setIsEditingTemplate(true);
3027
+ setSuggestedImprovement(null);
3028
+ setAnalysisResult(null);
3029
+ showToast('Suggested improvements applied! Click Save to persist changes.', 'success');
3030
+ }}
3031
+ >
3032
+ <CheckCircle size={14} /> Apply Suggested Improvements
3033
+ </button>
3034
+ </div>
3035
+ )}
3036
+ </div>
3037
+ )}
3038
+ </div>
3039
+ )}
3040
+ </div>
3041
+ ) : (
3042
+ <div className="h-[400px] bg-[#111827]/10 border border-gray-800/60 border-dashed rounded-xl flex flex-col items-center justify-center text-center p-6">
3043
+ <FolderOpen size={36} className="text-gray-600 mb-3" />
3044
+ <span className="text-sm font-semibold text-gray-400">No strategy template selected</span>
3045
+ <p className="text-xs text-gray-500 mt-1 max-w-sm font-sans">
3046
+ Select a template from the list to view, edit, or analyze it, or add a new custom teamwork workflow template.
3047
+ </p>
3048
+ </div>
3049
+ )}
3050
+ </div>
3051
+ </div>
3052
+ </div>
3053
+ )}
3054
+
2255
3055
  {/* View 3: Settings View */}
2256
3056
  {view === 'settings' && config && (
2257
3057
  <div className="max-w-4xl mx-auto">
@@ -2342,22 +3142,115 @@ Core Instructions:
2342
3142
  </div>
2343
3143
 
2344
3144
  <div className="flex flex-col md:col-span-2 border-t border-gray-800/60 pt-6 mt-2">
2345
- <h3 className="text-sm font-bold text-white mb-2">Codebase Context Settings</h3>
2346
- <div className="flex items-center gap-3">
2347
- <input
2348
- type="checkbox"
2349
- id="packContextXml"
2350
- className="w-4 h-4 rounded border-gray-800 bg-[#111827] text-indigo-600 focus:ring-indigo-500 cursor-pointer"
2351
- checked={config.packContextXml || false}
2352
- onChange={(e) => setConfig({ ...config, packContextXml: e.target.checked })}
2353
- />
2354
- <label htmlFor="packContextXml" className="text-xs font-semibold text-white cursor-pointer select-none">
2355
- Pack Codebase Context with Repomix (XML)
3145
+ <h3 className="text-sm font-bold text-white mb-2">Workspace Storage Settings</h3>
3146
+ <div className="flex flex-col gap-1.5">
3147
+ <label htmlFor="storageProvider" className="text-xs font-semibold text-white select-none">
3148
+ Storage Provider
2356
3149
  </label>
3150
+ <select
3151
+ id="storageProvider"
3152
+ className="w-full max-w-md text-xs bg-[#111827] border border-gray-800 rounded-lg p-2 text-white focus:outline-none focus:border-indigo-500"
3153
+ value={config.storageProvider || 'local'}
3154
+ onChange={(e) => {
3155
+ const val = e.target.value;
3156
+ const newConf = { ...config, storageProvider: val };
3157
+ const selectedAdapter = adapters.find(a => a.name === val);
3158
+ if (selectedAdapter?.configFields?.length) {
3159
+ if (!newConf.adapterConfig) newConf.adapterConfig = {};
3160
+ if (!newConf.adapterConfig[val]) newConf.adapterConfig[val] = {};
3161
+ selectedAdapter.configFields.forEach(f => {
3162
+ if (newConf.adapterConfig![val][f.key] === undefined && f.default !== undefined) {
3163
+ newConf.adapterConfig![val][f.key] = f.default;
3164
+ }
3165
+ });
3166
+ }
3167
+ setConfig(newConf);
3168
+ }}
3169
+ >
3170
+ {adapters.length > 0 ? (
3171
+ adapters.map((a) => (
3172
+ <option key={a.name} value={a.name}>
3173
+ {a.displayName}
3174
+ </option>
3175
+ ))
3176
+ ) : (
3177
+ <>
3178
+ <option value="local">Local Workspace (Folders)</option>
3179
+ <option value="central-vault">Obsidian Central Vault</option>
3180
+ </>
3181
+ )}
3182
+ </select>
2357
3183
  </div>
2358
3184
  <span className="text-[10px] text-gray-500 mt-1">
2359
- Aggregates files of all repositories in a workspace into a single token-efficient XML file (<code>nexusflow-context.xml</code>) at the workspace root, giving AI assistants immediate access to the full codebase state.
3185
+ {adapters.find(a => a.name === (config.storageProvider || 'local'))?.description ||
3186
+ 'Choose where to store maps, plans, and knowledge files. Centralized vault keeps repositories 100% clean and allows Obsidian integration.'}
2360
3187
  </span>
3188
+
3189
+ {/* Dynamic config fields */}
3190
+ {(() => {
3191
+ const activeProv = config.storageProvider || 'local';
3192
+ const selectedAdapter = adapters.find((a) => a.name === activeProv);
3193
+ if (!selectedAdapter || !selectedAdapter.configFields || selectedAdapter.configFields.length === 0) {
3194
+ return null;
3195
+ }
3196
+ return (
3197
+ <div className="mt-4 p-4 rounded-lg bg-gray-950/40 border border-gray-800/80 max-w-md flex flex-col gap-4">
3198
+ <h4 className="text-xs font-semibold text-gray-300 uppercase tracking-wider">
3199
+ {selectedAdapter.displayName} Settings
3200
+ </h4>
3201
+ {selectedAdapter.configFields.map((field) => {
3202
+ const value = config.adapterConfig?.[activeProv]?.[field.key] ?? field.default ?? '';
3203
+ const updateField = (val: any) => {
3204
+ const newAdapterConfig = {
3205
+ ...(config.adapterConfig || {}),
3206
+ [activeProv]: {
3207
+ ...(config.adapterConfig?.[activeProv] || {}),
3208
+ [field.key]: val,
3209
+ },
3210
+ };
3211
+ setConfig({ ...config, adapterConfig: newAdapterConfig });
3212
+ };
3213
+
3214
+ if (field.type === 'boolean') {
3215
+ return (
3216
+ <label key={field.key} className="flex items-start gap-3 cursor-pointer select-none">
3217
+ <input
3218
+ type="checkbox"
3219
+ className="mt-0.5 rounded border-gray-800 bg-gray-950 text-indigo-500 focus:ring-0 focus:ring-offset-0"
3220
+ checked={!!value}
3221
+ onChange={(e) => updateField(e.target.checked)}
3222
+ />
3223
+ <div className="flex flex-col">
3224
+ <span className="text-xs font-medium text-slate-200">{field.label}</span>
3225
+ {field.description && (
3226
+ <span className="text-[10px] text-gray-500 leading-normal mt-0.5">{field.description}</span>
3227
+ )}
3228
+ </div>
3229
+ </label>
3230
+ );
3231
+ }
3232
+
3233
+ return (
3234
+ <div key={field.key} className="flex flex-col gap-1">
3235
+ <label className="text-[11px] font-semibold text-slate-300">{field.label}</label>
3236
+ <input
3237
+ type={field.type === 'number' ? 'number' : 'text'}
3238
+ className="w-full text-xs bg-slate-950 border border-gray-800 rounded p-2 text-white focus:outline-none focus:border-indigo-500"
3239
+ value={value}
3240
+ onChange={(e) => {
3241
+ const val = field.type === 'number' ? Number(e.target.value) : e.target.value;
3242
+ updateField(val);
3243
+ }}
3244
+ />
3245
+ {field.description && (
3246
+ <span className="text-[10px] text-gray-500 leading-normal mt-0.5">{field.description}</span>
3247
+ )}
3248
+ </div>
3249
+ );
3250
+ })}
3251
+ </div>
3252
+ );
3253
+ })()}
2361
3254
  </div>
2362
3255
  </div>
2363
3256
 
@@ -2449,6 +3342,23 @@ Core Instructions:
2449
3342
  />
2450
3343
  <span className="text-[10px] text-gray-500 mt-1">Exact name of the model registered on the server (e.g., <code>qwen2.5-coder:1.5b</code>).</span>
2451
3344
  </div>
3345
+
3346
+ {config.localLlm.provider === 'openai-compatible' && (
3347
+ <div className="flex flex-col md:col-span-2">
3348
+ <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">API Key (Optional)</label>
3349
+ <input
3350
+ type="password"
3351
+ 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-605 transition-all outline-none text-sm shadow-inner"
3352
+ value={config.localLlm.apiKey || ''}
3353
+ placeholder="sk-..."
3354
+ onChange={(e) => setConfig({
3355
+ ...config,
3356
+ localLlm: { ...config.localLlm!, apiKey: e.target.value }
3357
+ })}
3358
+ />
3359
+ <span className="text-[10px] text-gray-500 mt-1">Bearer token for authenticating with cloud LLM providers (e.g. OpenAI, DeepSeek, OpenRouter).</span>
3360
+ </div>
3361
+ )}
2452
3362
  </div>
2453
3363
 
2454
3364
  <div className="flex items-center justify-between pt-4 border-t border-gray-800/40">