@mrpatronz/nexusflow 0.2.17 → 0.2.19

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 (320) 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/dependabot.yml +52 -52
  5. package/.github/workflows/ci.yml +44 -0
  6. package/.github/workflows/dependabot-merge.yml +53 -53
  7. package/.github/workflows/release-desktop.yml +67 -0
  8. package/.github/workflows/release-npm.yml +65 -0
  9. package/.github/workflows/release-vscode.yml +61 -0
  10. package/.vscode/launch.json +17 -17
  11. package/.vscode/tasks.json +17 -17
  12. package/GETTING_STARTED.md +114 -114
  13. package/README.md +364 -336
  14. package/desktop/build-installer.js +228 -228
  15. package/desktop/build.js +118 -118
  16. package/desktop/installer/nexusflow.iss +35 -35
  17. package/desktop/neutralino.config.json +38 -38
  18. package/desktop/package-lock.json +1060 -1060
  19. package/desktop/package.json +14 -14
  20. package/dist/commands/adapter.d.ts +27 -0
  21. package/dist/commands/adapter.d.ts.map +1 -0
  22. package/dist/commands/adapter.js +318 -0
  23. package/dist/commands/adapter.js.map +1 -0
  24. package/dist/commands/add-repo.d.ts.map +1 -1
  25. package/dist/commands/add-repo.js +0 -1
  26. package/dist/commands/add-repo.js.map +1 -1
  27. package/dist/commands/commands.test.js +2 -2
  28. package/dist/commands/commands.test.js.map +1 -1
  29. package/dist/commands/config.d.ts +22 -0
  30. package/dist/commands/config.d.ts.map +1 -0
  31. package/dist/commands/config.js +50 -0
  32. package/dist/commands/config.js.map +1 -0
  33. package/dist/commands/create.d.ts.map +1 -1
  34. package/dist/commands/create.js +14 -17
  35. package/dist/commands/create.js.map +1 -1
  36. package/dist/commands/doctor.d.ts.map +1 -1
  37. package/dist/commands/doctor.js +11 -0
  38. package/dist/commands/doctor.js.map +1 -1
  39. package/dist/commands/refresh.d.ts +1 -0
  40. package/dist/commands/refresh.d.ts.map +1 -1
  41. package/dist/commands/refresh.js +2 -14
  42. package/dist/commands/refresh.js.map +1 -1
  43. package/dist/commands/sync.d.ts.map +1 -1
  44. package/dist/commands/sync.js +36 -54
  45. package/dist/commands/sync.js.map +1 -1
  46. package/dist/commands/tui.d.ts.map +1 -1
  47. package/dist/commands/tui.js +63 -10
  48. package/dist/commands/tui.js.map +1 -1
  49. package/dist/core/adapters/local-storage.d.ts +14 -0
  50. package/dist/core/adapters/local-storage.d.ts.map +1 -0
  51. package/dist/core/adapters/local-storage.js +58 -0
  52. package/dist/core/adapters/local-storage.js.map +1 -0
  53. package/dist/core/adapters/obsidian-storage.d.ts +28 -0
  54. package/dist/core/adapters/obsidian-storage.d.ts.map +1 -0
  55. package/dist/core/adapters/obsidian-storage.js +135 -0
  56. package/dist/core/adapters/obsidian-storage.js.map +1 -0
  57. package/dist/core/adapters/registry.d.ts +8 -0
  58. package/dist/core/adapters/registry.d.ts.map +1 -0
  59. package/dist/core/adapters/registry.js +37 -0
  60. package/dist/core/adapters/registry.js.map +1 -0
  61. package/dist/core/adapters/storage.test.d.ts +2 -0
  62. package/dist/core/adapters/storage.test.d.ts.map +1 -0
  63. package/dist/core/adapters/storage.test.js +106 -0
  64. package/dist/core/adapters/storage.test.js.map +1 -0
  65. package/dist/core/adapters/vault-storage.d.ts +16 -0
  66. package/dist/core/adapters/vault-storage.d.ts.map +1 -0
  67. package/dist/core/adapters/vault-storage.js +77 -0
  68. package/dist/core/adapters/vault-storage.js.map +1 -0
  69. package/dist/core/config.d.ts.map +1 -1
  70. package/dist/core/config.js +28 -4
  71. package/dist/core/config.js.map +1 -1
  72. package/dist/core/plugins/index.d.ts +14 -0
  73. package/dist/core/plugins/index.d.ts.map +1 -0
  74. package/dist/core/plugins/index.js +2 -0
  75. package/dist/core/plugins/index.js.map +1 -0
  76. package/dist/core/plugins/loader.d.ts +2 -0
  77. package/dist/core/plugins/loader.d.ts.map +1 -0
  78. package/dist/core/plugins/loader.js +34 -0
  79. package/dist/core/plugins/loader.js.map +1 -0
  80. package/dist/core/ports/storage.d.ts +59 -0
  81. package/dist/core/ports/storage.d.ts.map +1 -0
  82. package/dist/core/ports/storage.js +10 -0
  83. package/dist/core/ports/storage.js.map +1 -0
  84. package/dist/core/storage.d.ts +10 -0
  85. package/dist/core/storage.d.ts.map +1 -0
  86. package/dist/core/storage.js +29 -0
  87. package/dist/core/storage.js.map +1 -0
  88. package/dist/core/sync.d.ts +44 -0
  89. package/dist/core/sync.d.ts.map +1 -0
  90. package/dist/core/sync.js +82 -0
  91. package/dist/core/sync.js.map +1 -0
  92. package/dist/core/workspace-state.d.ts +51 -0
  93. package/dist/core/workspace-state.d.ts.map +1 -0
  94. package/dist/core/workspace-state.js +108 -0
  95. package/dist/core/workspace-state.js.map +1 -0
  96. package/dist/core/workspace-state.test.d.ts +2 -0
  97. package/dist/core/workspace-state.test.d.ts.map +1 -0
  98. package/dist/core/workspace-state.test.js +84 -0
  99. package/dist/core/workspace-state.test.js.map +1 -0
  100. package/dist/core/workspace.d.ts.map +1 -1
  101. package/dist/core/workspace.js +110 -16
  102. package/dist/core/workspace.js.map +1 -1
  103. package/dist/generators/base.d.ts.map +1 -1
  104. package/dist/generators/base.js +80 -79
  105. package/dist/generators/base.js.map +1 -1
  106. package/dist/generators/codex.js +11 -11
  107. package/dist/generators/copilot.js +17 -17
  108. package/dist/generators/cursor.js +5 -5
  109. package/dist/generators/diff-context.d.ts +6 -0
  110. package/dist/generators/diff-context.d.ts.map +1 -0
  111. package/dist/generators/diff-context.js +68 -0
  112. package/dist/generators/diff-context.js.map +1 -0
  113. package/dist/generators/index.d.ts +1 -1
  114. package/dist/generators/index.d.ts.map +1 -1
  115. package/dist/generators/index.js +125 -68
  116. package/dist/generators/index.js.map +1 -1
  117. package/dist/generators/map-generator.d.ts.map +1 -1
  118. package/dist/generators/map-generator.js +21 -25
  119. package/dist/generators/map-generator.js.map +1 -1
  120. package/dist/generators/plan-generator.d.ts.map +1 -1
  121. package/dist/generators/plan-generator.js +3 -5
  122. package/dist/generators/plan-generator.js.map +1 -1
  123. package/dist/generators/skills-generator.js +28 -28
  124. package/dist/gui/assets/index-Bdbld6yY.css +1 -0
  125. package/dist/gui/assets/index-D8iJPvnk.js +26 -0
  126. package/dist/gui/icons.svg +24 -24
  127. package/dist/gui/index.html +18 -18
  128. package/dist/index.js +131 -20
  129. package/dist/index.js.map +1 -1
  130. package/dist/mcp/server.d.ts.map +1 -1
  131. package/dist/mcp/server.js +42 -0
  132. package/dist/mcp/server.js.map +1 -1
  133. package/dist/server.d.ts.map +1 -1
  134. package/dist/server.js +140 -56
  135. package/dist/server.js.map +1 -1
  136. package/dist/server.test.js +105 -48
  137. package/dist/server.test.js.map +1 -1
  138. package/dist/types.d.ts +38 -2
  139. package/dist/types.d.ts.map +1 -1
  140. package/dist/utils/local-ai.d.ts.map +1 -1
  141. package/dist/utils/local-ai.js +7 -3
  142. package/dist/utils/local-ai.js.map +1 -1
  143. package/dist/utils/multi-git.d.ts +33 -8
  144. package/dist/utils/multi-git.d.ts.map +1 -1
  145. package/dist/utils/multi-git.js +91 -19
  146. package/dist/utils/multi-git.js.map +1 -1
  147. package/dist/utils/multi-git.test.d.ts +2 -0
  148. package/dist/utils/multi-git.test.d.ts.map +1 -0
  149. package/dist/utils/multi-git.test.js +125 -0
  150. package/dist/utils/multi-git.test.js.map +1 -0
  151. package/dist/utils/update-check.d.ts.map +1 -1
  152. package/dist/utils/update-check.js +0 -28
  153. package/dist/utils/update-check.js.map +1 -1
  154. package/dist/utils/workflow-advisor.d.ts +16 -0
  155. package/dist/utils/workflow-advisor.d.ts.map +1 -0
  156. package/dist/utils/workflow-advisor.js +100 -0
  157. package/dist/utils/workflow-advisor.js.map +1 -0
  158. package/extension/package-lock.json +6044 -2673
  159. package/extension/package.json +117 -108
  160. package/extension/src/extension.ts +653 -631
  161. package/extension/tsconfig.json +21 -21
  162. package/gui/README.md +73 -73
  163. package/gui/e2e/dashboard.spec.ts +61 -0
  164. package/gui/e2e/wizard.spec.ts +346 -346
  165. package/gui/eslint.config.js +26 -26
  166. package/gui/index.html +17 -17
  167. package/gui/package-lock.json +3137 -3079
  168. package/gui/package.json +37 -36
  169. package/gui/playwright.config.ts +41 -41
  170. package/gui/public/icons.svg +24 -24
  171. package/gui/src/App.css +1 -1
  172. package/gui/src/App.tsx +3573 -3407
  173. package/gui/src/app/AppSidebar.tsx +69 -0
  174. package/gui/src/assets/vite.svg +1 -1
  175. package/gui/src/components/AddRepoPicker.tsx +81 -0
  176. package/gui/src/components/ui/Button.tsx +40 -0
  177. package/gui/src/components/ui/Card.tsx +6 -0
  178. package/gui/src/components/ui/EmptyState.tsx +22 -0
  179. package/gui/src/components/ui/Input.tsx +21 -0
  180. package/gui/src/components/ui/Menu.tsx +71 -0
  181. package/gui/src/components/ui/Modal.tsx +39 -0
  182. package/gui/src/components/ui/PageHeader.tsx +21 -0
  183. package/gui/src/components/ui/RepoStatusStrip.tsx +33 -0
  184. package/gui/src/components/ui/Skeleton.tsx +5 -0
  185. package/gui/src/components/ui/StatusPill.tsx +36 -0
  186. package/gui/src/components/ui/Tabs.tsx +40 -0
  187. package/gui/src/components/ui/cn.ts +4 -0
  188. package/gui/src/components/ui/index.ts +16 -0
  189. package/gui/src/features/changes/ChangesViewer.tsx +388 -388
  190. package/gui/src/features/knowledge/KnowledgeBase.tsx +84 -84
  191. package/gui/src/features/onboarding/OnboardingWizard.tsx +230 -230
  192. package/gui/src/features/plan/ImplementationPlan.tsx +32 -32
  193. package/gui/src/features/services/ServiceConsole.tsx +215 -250
  194. package/gui/src/features/sessions/SessionHistory.tsx +195 -195
  195. package/gui/src/index.css +167 -152
  196. package/gui/src/lib/status.ts +24 -0
  197. package/gui/src/main.tsx +94 -94
  198. package/gui/src/pages/DashboardPage.tsx +129 -0
  199. package/gui/src/pages/WorkspacesPage.tsx +352 -0
  200. package/gui/src/types.ts +81 -62
  201. package/gui/tsconfig.app.json +25 -25
  202. package/gui/tsconfig.json +7 -7
  203. package/gui/tsconfig.node.json +24 -24
  204. package/gui/vite.config.ts +12 -12
  205. package/package.json +55 -56
  206. package/resources/workflows/plan-implement-review.md +8 -8
  207. package/resources/workflows/research-verify.md +6 -6
  208. package/resources/workflows/solo-developer.md +3 -3
  209. package/scripts/simulate-workspaces.ts +55 -55
  210. package/skills-lock.json +11 -0
  211. package/src/analyzers/detect-apis.ts +290 -290
  212. package/src/analyzers/detect-deps.ts +315 -315
  213. package/src/analyzers/detect-existing.ts +74 -74
  214. package/src/analyzers/detect-ports.ts +110 -110
  215. package/src/analyzers/index.ts +97 -97
  216. package/src/analyzers/messaging-analyzer.ts +254 -254
  217. package/src/analyzers/readme-summarizer.ts +102 -102
  218. package/src/analyzers/run-analyzer.ts +269 -269
  219. package/src/analyzers/tech-stack.ts +283 -283
  220. package/src/commands/adapter.ts +348 -0
  221. package/src/commands/add-repo.ts +156 -157
  222. package/src/commands/commands.test.ts +156 -155
  223. package/src/commands/commit.ts +131 -131
  224. package/src/commands/config.ts +52 -0
  225. package/src/commands/create.ts +204 -207
  226. package/src/commands/desktop.ts +128 -128
  227. package/src/commands/diff.ts +125 -125
  228. package/src/commands/doctor.ts +370 -357
  229. package/src/commands/handoff.ts +266 -266
  230. package/src/commands/init.ts +134 -134
  231. package/src/commands/list.ts +46 -46
  232. package/src/commands/logs.ts +63 -63
  233. package/src/commands/mcp.ts +94 -92
  234. package/src/commands/open.ts +129 -129
  235. package/src/commands/refresh.ts +137 -147
  236. package/src/commands/remove.ts +98 -98
  237. package/src/commands/start.ts +117 -117
  238. package/src/commands/status.ts +54 -54
  239. package/src/commands/stop.ts +55 -55
  240. package/src/commands/sync.ts +125 -151
  241. package/src/commands/tui.ts +484 -424
  242. package/src/commands/ui.ts +111 -111
  243. package/src/core/adapters/local-storage.ts +66 -0
  244. package/src/core/adapters/obsidian-storage.ts +151 -0
  245. package/src/core/adapters/registry.ts +44 -0
  246. package/src/core/adapters/storage.test.ts +174 -0
  247. package/src/core/adapters/vault-storage.ts +86 -0
  248. package/src/core/config.test.ts +96 -96
  249. package/src/core/config.ts +140 -115
  250. package/src/core/graph.ts +344 -344
  251. package/src/core/plugins/index.ts +17 -0
  252. package/src/core/plugins/loader.ts +36 -0
  253. package/src/core/ports/storage.ts +71 -0
  254. package/src/core/scanner.test.ts +61 -61
  255. package/src/core/scanner.ts +91 -91
  256. package/src/core/storage.ts +37 -0
  257. package/src/core/sync.ts +121 -0
  258. package/src/core/workspace-state.test.ts +108 -0
  259. package/src/core/workspace-state.ts +130 -0
  260. package/src/core/workspace.ts +487 -380
  261. package/src/core/worktree.ts +120 -120
  262. package/src/generators/antigravity.ts +32 -32
  263. package/src/generators/base.ts +243 -240
  264. package/src/generators/claude.ts +34 -34
  265. package/src/generators/codex.ts +48 -48
  266. package/src/generators/copilot.ts +56 -56
  267. package/src/generators/cursor.ts +44 -44
  268. package/src/generators/diff-context.ts +75 -0
  269. package/src/generators/index.ts +322 -250
  270. package/src/generators/map-generator.test.ts +159 -159
  271. package/src/generators/map-generator.ts +539 -542
  272. package/src/generators/plan-generator.ts +483 -481
  273. package/src/generators/skills-generator.ts +259 -259
  274. package/src/index.ts +539 -426
  275. package/src/mcp/server.ts +338 -294
  276. package/src/orchestration/detect.ts +313 -313
  277. package/src/orchestration/index.ts +7 -7
  278. package/src/orchestration/runner.ts +283 -283
  279. package/src/server.test.ts +618 -555
  280. package/src/server.ts +1402 -1308
  281. package/src/types.ts +451 -408
  282. package/src/utils/detect-ai.test.ts +44 -44
  283. package/src/utils/detect-ai.ts +84 -84
  284. package/src/utils/detect-editors.test.ts +48 -48
  285. package/src/utils/detect-editors.ts +57 -57
  286. package/src/utils/git.test.ts +74 -74
  287. package/src/utils/git.ts +117 -117
  288. package/src/utils/local-ai.test.ts +130 -130
  289. package/src/utils/local-ai.ts +116 -111
  290. package/src/utils/multi-git.test.ts +158 -0
  291. package/src/utils/multi-git.ts +405 -313
  292. package/src/utils/prompts.ts +209 -209
  293. package/src/utils/session-finder.ts +483 -483
  294. package/src/utils/system-scanner.test.ts +89 -89
  295. package/src/utils/system-scanner.ts +96 -96
  296. package/src/utils/update-check.ts +286 -309
  297. package/src/utils/workflow-advisor.ts +118 -0
  298. package/src/utils/workflows.test.ts +114 -114
  299. package/src/utils/workflows.ts +178 -178
  300. package/tsconfig.json +19 -19
  301. package/vitest.config.ts +14 -14
  302. package/.github/workflows/release.yml +0 -112
  303. package/dist/commands/pack.d.ts +0 -7
  304. package/dist/commands/pack.d.ts.map +0 -1
  305. package/dist/commands/pack.js +0 -64
  306. package/dist/commands/pack.js.map +0 -1
  307. package/dist/core/packer.d.ts +0 -14
  308. package/dist/core/packer.d.ts.map +0 -1
  309. package/dist/core/packer.js +0 -87
  310. package/dist/core/packer.js.map +0 -1
  311. package/dist/core/packer.test.d.ts +0 -2
  312. package/dist/core/packer.test.d.ts.map +0 -1
  313. package/dist/core/packer.test.js +0 -82
  314. package/dist/core/packer.test.js.map +0 -1
  315. package/dist/gui/assets/index-Ct3qq-X4.js +0 -25
  316. package/dist/gui/assets/index-P_ZrgHXg.css +0 -1
  317. package/gui/src/features/workspace/WorkspaceList.tsx +0 -666
  318. package/src/commands/pack.ts +0 -73
  319. package/src/core/packer.test.ts +0 -99
  320. package/src/core/packer.ts +0 -107
@@ -0,0 +1,352 @@
1
+ import { useMemo, useState, type ComponentProps } from 'react';
2
+ import { RefreshCw, Play, MoreVertical, ExternalLink, Sparkles, Trash2, Search, FolderGit2 } from 'lucide-react';
3
+ import type { Feature, WorkspaceStatus, RepoInfo } from '../types.js';
4
+ import { Button, Card, EmptyState, Menu, PageHeader, StatusPill, Tabs, Input, Skeleton, cn } from '../components/ui/index.js';
5
+ import type { TabItem } from '../components/ui/index.js';
6
+ import { AddRepoPicker } from '../components/AddRepoPicker.js';
7
+ import { syncMeta, repoName } from '../lib/status.js';
8
+ import { SessionHistory } from '../features/sessions/SessionHistory.js';
9
+ import { ServiceConsole } from '../features/services/ServiceConsole.js';
10
+ import { ChangesViewer } from '../features/changes/ChangesViewer.js';
11
+ import { KnowledgeBase } from '../features/knowledge/KnowledgeBase.js';
12
+ import { ImplementationPlan } from '../features/plan/ImplementationPlan.js';
13
+
14
+ type SubTab = 'overview' | 'sessions' | 'services' | 'changes' | 'knowledge' | 'plan';
15
+
16
+ const TABS: TabItem[] = [
17
+ { value: 'overview', label: 'Overview' },
18
+ { value: 'changes', label: 'Changes' },
19
+ { value: 'services', label: 'Services' },
20
+ { value: 'sessions', label: 'Sessions' },
21
+ { value: 'knowledge', label: 'Knowledge' },
22
+ { value: 'plan', label: 'Plan' },
23
+ ];
24
+
25
+ const FILTERS = ['all', 'changes', 'running'] as const;
26
+ type Filter = (typeof FILTERS)[number];
27
+
28
+ interface WorkspacesPageProps {
29
+ workspaces: Feature[];
30
+ workspaceStatuses: Record<string, WorkspaceStatus>;
31
+ statusesLoading: boolean;
32
+ workspacesLoading: boolean;
33
+ fetchWorkspaces: () => Promise<void>;
34
+ selectedId: string | null;
35
+ subTab: SubTab;
36
+ onSelect: (id: string) => void;
37
+ onSelectTab: (id: string, tab: SubTab) => void;
38
+ resumingWs: string | null;
39
+ handleResumeSession: (ws: Feature, sessionId?: string, assistant?: string) => Promise<void>;
40
+ handleCopyPrompt: (ws: Feature) => void;
41
+ handleOpenInEditor: (workspacePath: string) => Promise<void>;
42
+ handleDeleteWorkspace: (wsName: string) => Promise<void>;
43
+ deleteWsLoading: string | null;
44
+ repos: RepoInfo[];
45
+ addRepoLoading: boolean;
46
+ handleAddRepo: (wsName: string, repoPath: string) => Promise<void>;
47
+ sessionProps: Omit<ComponentProps<typeof SessionHistory>, 'ws'>;
48
+ serviceProps: Omit<ComponentProps<typeof ServiceConsole>, 'ws'>;
49
+ changesProps: Omit<ComponentProps<typeof ChangesViewer>, 'ws'>;
50
+ knowledgeProps: Omit<ComponentProps<typeof KnowledgeBase>, 'ws'>;
51
+ planProps: ComponentProps<typeof ImplementationPlan>;
52
+ }
53
+
54
+ export function WorkspacesPage(props: WorkspacesPageProps) {
55
+ const {
56
+ workspaces,
57
+ workspaceStatuses,
58
+ workspacesLoading,
59
+ fetchWorkspaces,
60
+ selectedId,
61
+ subTab,
62
+ onSelect,
63
+ onSelectTab,
64
+ resumingWs,
65
+ handleResumeSession,
66
+ handleCopyPrompt,
67
+ handleOpenInEditor,
68
+ handleDeleteWorkspace,
69
+ deleteWsLoading,
70
+ repos,
71
+ addRepoLoading,
72
+ handleAddRepo,
73
+ sessionProps,
74
+ serviceProps,
75
+ changesProps,
76
+ knowledgeProps,
77
+ planProps,
78
+ } = props;
79
+
80
+ const [query, setQuery] = useState('');
81
+ const [filter, setFilter] = useState<Filter>('all');
82
+
83
+ const selected = workspaces.find((w) => w.branchName === selectedId) ?? null;
84
+
85
+ const filtered = useMemo(() => {
86
+ const q = query.trim().toLowerCase();
87
+ return workspaces.filter((w) => {
88
+ const st = workspaceStatuses[w.branchName];
89
+ if (q && !`${w.branchName} ${w.description}`.toLowerCase().includes(q)) return false;
90
+ if (filter === 'changes' && !(st && st.changedFiles > 0)) return false;
91
+ if (filter === 'running' && !(st && st.runningServices > 0)) return false;
92
+ return true;
93
+ });
94
+ }, [workspaces, workspaceStatuses, query, filter]);
95
+
96
+ const repoRows = selected
97
+ ? selected.repos.map((rp) => {
98
+ const name = repoName(rp);
99
+ const change = changesProps.gitChanges?.find((c: { repoName: string; files?: unknown[] }) => c.repoName === name);
100
+ const changedCount: number | null = change ? change.files?.length ?? 0 : null;
101
+ const svcs = (serviceProps.services ?? []).filter((s) => (s.cwd ?? '').split(/[\\/]/).includes(name));
102
+ const ports = Array.from(new Set(svcs.map((s) => s.port).filter((p): p is number => typeof p === 'number')));
103
+ return { name, changedCount, ports, serviceCount: svcs.length };
104
+ })
105
+ : [];
106
+
107
+ const availableRepos = selected ? repos.filter((r) => !selected.repos.includes(r.path)) : [];
108
+
109
+ return (
110
+ <div className="mx-auto max-w-7xl animate-fade-in">
111
+ <PageHeader
112
+ title="Workspaces"
113
+ subtitle="Select a workspace to inspect its changes, services, sessions and context."
114
+ actions={
115
+ <Button
116
+ variant="secondary"
117
+ icon={<RefreshCw size={14} className={workspacesLoading ? 'animate-spin text-accent' : ''} />}
118
+ onClick={fetchWorkspaces}
119
+ disabled={workspacesLoading}
120
+ >
121
+ Refresh
122
+ </Button>
123
+ }
124
+ />
125
+
126
+ <div className="flex flex-col gap-5 lg:flex-row">
127
+ {/* ── List pane ─────────────────────────────────────────── */}
128
+ <div className="lg:w-80 lg:shrink-0">
129
+ <div className="relative mb-3">
130
+ <Search size={14} className="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-content-faint" />
131
+ <Input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search workspaces" className="pl-8" />
132
+ </div>
133
+ <div className="mb-3 flex items-center gap-1">
134
+ {FILTERS.map((f) => (
135
+ <button
136
+ key={f}
137
+ onClick={() => setFilter(f)}
138
+ className={cn(
139
+ 'rounded-md px-2.5 py-1 text-xs font-medium capitalize transition-colors cursor-pointer',
140
+ filter === f ? 'bg-accent-soft text-accent' : 'text-content-faint hover:text-content',
141
+ )}
142
+ >
143
+ {f}
144
+ </button>
145
+ ))}
146
+ </div>
147
+
148
+ {workspacesLoading ? (
149
+ <div className="flex flex-col gap-1.5">
150
+ {[0, 1, 2, 3].map((i) => (
151
+ <Skeleton key={i} className="h-14" />
152
+ ))}
153
+ </div>
154
+ ) : filtered.length === 0 ? (
155
+ <Card className="p-6 text-center text-sm text-content-muted">No workspaces match.</Card>
156
+ ) : (
157
+ <div className="flex flex-col gap-1.5">
158
+ {filtered.map((w) => {
159
+ const st = workspaceStatuses[w.branchName];
160
+ const active = w.branchName === selectedId;
161
+ const sync = st ? syncMeta(st.syncStatus) : null;
162
+ return (
163
+ <button
164
+ key={w.id}
165
+ onClick={() => onSelect(w.branchName)}
166
+ className={cn(
167
+ 'rounded-lg border p-3 text-left transition-colors cursor-pointer',
168
+ active ? 'border-accent/50 bg-accent-soft' : 'border-hairline bg-surface hover:border-hairline-strong hover:bg-raised',
169
+ )}
170
+ >
171
+ <div className="flex items-center justify-between gap-2">
172
+ <span className="truncate font-mono text-sm font-semibold text-content">{w.branchName}</span>
173
+ <span className="shrink-0 text-[11px] text-content-faint">{w.repos.length} repos</span>
174
+ </div>
175
+ <div className="mt-1.5 flex items-center gap-2">
176
+ <span
177
+ className={cn('h-1.5 w-1.5 rounded-full', st?.changedFiles ? 'bg-warning' : 'bg-success')}
178
+ title={st?.changedFiles ? `${st.changedFiles} uncommitted` : 'Clean'}
179
+ />
180
+ {st?.runningServices ? <span className="h-1.5 w-1.5 rounded-full bg-running" title="Running services" /> : null}
181
+ {sync && <span className="text-[11px] text-content-faint">{sync.label}</span>}
182
+ </div>
183
+ </button>
184
+ );
185
+ })}
186
+ </div>
187
+ )}
188
+ </div>
189
+
190
+ {/* ── Detail pane ───────────────────────────────────────── */}
191
+ <div className="min-w-0 flex-1">
192
+ {!selected ? (
193
+ <Card>
194
+ <EmptyState
195
+ icon={<FolderGit2 size={40} />}
196
+ title="No workspace selected"
197
+ description="Pick a workspace from the list to see its status, repositories, changes, services and sessions."
198
+ />
199
+ </Card>
200
+ ) : (
201
+ <div>
202
+ <Card className="mb-4 p-5">
203
+ <div className="flex items-start justify-between gap-4">
204
+ <div className="min-w-0">
205
+ <h2 className="font-display text-xl font-bold text-content">{selected.branchName}</h2>
206
+ <div className="mt-1 flex items-center gap-2 text-xs text-content-faint">
207
+ <span>Created {new Date(selected.createdAt).toLocaleDateString()}</span>
208
+ <span>·</span>
209
+ <span>{selected.repos.length} repos</span>
210
+ </div>
211
+ </div>
212
+ <div className="flex shrink-0 items-center gap-2">
213
+ <Button
214
+ variant="primary"
215
+ icon={<Play size={13} className={resumingWs === selected.branchName ? 'animate-spin' : ''} />}
216
+ disabled={resumingWs === selected.branchName}
217
+ onClick={() => handleResumeSession(selected)}
218
+ >
219
+ {resumingWs === selected.branchName ? 'Resuming…' : 'Resume in Editor'}
220
+ </Button>
221
+ <Menu
222
+ label="More actions"
223
+ trigger={
224
+ <span className="grid h-9 w-9 place-items-center rounded-md border border-hairline bg-surface text-content-muted hover:bg-raised hover:text-content">
225
+ <MoreVertical size={16} />
226
+ </span>
227
+ }
228
+ items={[
229
+ { label: 'Open Folder', icon: <ExternalLink size={14} />, onClick: () => void handleOpenInEditor(selected.workspacePath) },
230
+ { label: 'Copy AI Context', icon: <Sparkles size={14} className="text-accent" />, onClick: () => handleCopyPrompt(selected) },
231
+ {
232
+ label: deleteWsLoading === selected.branchName ? 'Deleting…' : 'Delete Workspace',
233
+ icon: <Trash2 size={14} />,
234
+ danger: true,
235
+ disabled: deleteWsLoading === selected.branchName,
236
+ onClick: () => void handleDeleteWorkspace(selected.branchName),
237
+ },
238
+ ]}
239
+ />
240
+ </div>
241
+ </div>
242
+
243
+ <div className="mt-3 flex flex-wrap items-center gap-2">
244
+ {(() => {
245
+ const st = workspaceStatuses[selected.branchName];
246
+ if (!st) return null;
247
+ const sync = syncMeta(st.syncStatus);
248
+ return (
249
+ <>
250
+ {st.changedFiles > 0 ? (
251
+ <StatusPill tone="warning" dot>
252
+ {st.changedFiles} uncommitted
253
+ </StatusPill>
254
+ ) : (
255
+ <StatusPill tone="idle" dot>
256
+ Clean
257
+ </StatusPill>
258
+ )}
259
+ {st.runningServices > 0 ? (
260
+ <StatusPill tone="running" dot>
261
+ {st.runningServices} running
262
+ </StatusPill>
263
+ ) : (
264
+ <StatusPill tone="idle" dot>
265
+ No services
266
+ </StatusPill>
267
+ )}
268
+ <StatusPill tone={sync.tone}>
269
+ <RefreshCw size={11} /> {sync.label}
270
+ </StatusPill>
271
+ {st.pendingValidation && <StatusPill tone="warning">Needs validation</StatusPill>}
272
+ </>
273
+ );
274
+ })()}
275
+ </div>
276
+ </Card>
277
+
278
+ <Tabs items={TABS} value={subTab} onChange={(v) => onSelectTab(selected.branchName, v as SubTab)} className="mb-4" />
279
+
280
+ <div className="animate-fade-in">
281
+ {subTab === 'overview' && (
282
+ <div className="flex flex-col gap-4">
283
+ <Card className="p-5">
284
+ <h3 className="mb-2 font-display text-sm font-semibold text-content">Description</h3>
285
+ {selected.description ? (
286
+ <p className="whitespace-pre-line text-sm leading-relaxed text-content-muted">{selected.description}</p>
287
+ ) : (
288
+ <p className="text-sm text-content-faint">No description provided.</p>
289
+ )}
290
+ </Card>
291
+ <Card className="p-5">
292
+ <div className="mb-4 flex items-center justify-between gap-3">
293
+ <h3 className="font-display text-sm font-semibold text-content">
294
+ Repositories <span className="text-content-faint">({selected.repos.length})</span>
295
+ </h3>
296
+ {availableRepos.length > 0 && (
297
+ <AddRepoPicker
298
+ repos={availableRepos}
299
+ disabled={addRepoLoading}
300
+ onAdd={(path) => {
301
+ if (
302
+ window.confirm(
303
+ `Add repository "${repoName(path)}" to this workspace?\nThis creates a new git worktree and re-runs analysis.`,
304
+ )
305
+ ) {
306
+ void handleAddRepo(selected.branchName, path);
307
+ }
308
+ }}
309
+ />
310
+ )}
311
+ </div>
312
+ <div className="divide-y divide-hairline">
313
+ {repoRows.map((r) => (
314
+ <div key={r.name} className="flex items-center gap-3 py-2.5 first:pt-0 last:pb-0">
315
+ <span
316
+ className={cn(
317
+ 'h-2 w-2 shrink-0 rounded-full',
318
+ r.changedCount === null ? 'bg-idle' : r.changedCount > 0 ? 'bg-warning' : 'bg-success',
319
+ )}
320
+ title={r.changedCount === null ? 'Status unknown' : r.changedCount > 0 ? 'Uncommitted changes' : 'Clean'}
321
+ />
322
+ <span className="min-w-0 flex-1 truncate font-mono text-sm text-content">{r.name}</span>
323
+ <div className="flex shrink-0 items-center gap-2">
324
+ {r.ports.map((p) => (
325
+ <span key={p} className="rounded border border-hairline bg-base px-1.5 py-0.5 font-mono text-[11px] text-running">
326
+ :{p}
327
+ </span>
328
+ ))}
329
+ <span className="w-24 text-right text-xs text-content-faint">
330
+ {r.changedCount === null ? '—' : r.changedCount > 0 ? `${r.changedCount} changed` : 'clean'}
331
+ </span>
332
+ </div>
333
+ </div>
334
+ ))}
335
+ </div>
336
+ <p className="mt-3 text-[11px] text-content-faint">Per-repo git state and detected service ports.</p>
337
+ </Card>
338
+ </div>
339
+ )}
340
+ {subTab === 'sessions' && <SessionHistory ws={selected} {...sessionProps} />}
341
+ {subTab === 'services' && <ServiceConsole ws={selected} {...serviceProps} />}
342
+ {subTab === 'changes' && <ChangesViewer ws={selected} {...changesProps} />}
343
+ {subTab === 'knowledge' && <KnowledgeBase ws={selected} {...knowledgeProps} />}
344
+ {subTab === 'plan' && <ImplementationPlan {...planProps} />}
345
+ </div>
346
+ </div>
347
+ )}
348
+ </div>
349
+ </div>
350
+ </div>
351
+ );
352
+ }
package/gui/src/types.ts CHANGED
@@ -1,62 +1,81 @@
1
- // Types matched with src/types.ts
2
-
3
- export interface NexusFlowConfig {
4
- version: string;
5
- devDir: string;
6
- workspacesDir: string;
7
- defaultAssistant: string | null;
8
- defaultEditor?: string | null;
9
- scanDepth: number;
10
- }
11
-
12
- export interface DetectedAI {
13
- name: string;
14
- displayName: string;
15
- detected: boolean;
16
- command?: string;
17
- }
18
-
19
- export interface DetectedEditor {
20
- name: string;
21
- command: string;
22
- detected: boolean;
23
- }
24
-
25
- export interface RepoInfo {
26
- name: string;
27
- path: string;
28
- defaultBranch: string;
29
- }
30
-
31
- export interface Feature {
32
- id: string;
33
- branchName: string;
34
- description: string;
35
- repos: string[];
36
- assistants: string[];
37
- workspacePath: string;
38
- createdAt: string;
39
- }
40
-
41
- export interface ServiceConfig {
42
- name: string;
43
- cwd: string;
44
- command: string;
45
- args: string[];
46
- port?: number;
47
- source: string;
48
- }
49
-
50
- export interface OrchestrationDetection {
51
- tool: string;
52
- configPath: string;
53
- startCommand: string;
54
- stopCommand: string;
55
- }
56
-
57
- export interface RunningService {
58
- name: string;
59
- pid: number;
60
- config: ServiceConfig;
61
- startedAt: string;
62
- }
1
+ // Types matched with src/types.ts
2
+
3
+ export interface NexusFlowConfig {
4
+ version: string;
5
+ devDir: string;
6
+ workspacesDir: string;
7
+ defaultAssistant: string | null;
8
+ defaultEditor?: string | null;
9
+ scanDepth: number;
10
+ }
11
+
12
+ export interface DetectedAI {
13
+ name: string;
14
+ displayName: string;
15
+ detected: boolean;
16
+ command?: string;
17
+ }
18
+
19
+ export interface DetectedEditor {
20
+ name: string;
21
+ command: string;
22
+ detected: boolean;
23
+ }
24
+
25
+ export interface RepoInfo {
26
+ name: string;
27
+ path: string;
28
+ defaultBranch: string;
29
+ }
30
+
31
+ export interface Feature {
32
+ id: string;
33
+ branchName: string;
34
+ description: string;
35
+ repos: string[];
36
+ assistants: string[];
37
+ workspacePath: string;
38
+ createdAt: string;
39
+ }
40
+
41
+ /** Classified outcome of a sync/rebase attempt for a repo (mirrors src/types.ts). */
42
+ export type SyncStatus = 'up-to-date' | 'rebased' | 'conflict' | 'stash-conflict' | 'error';
43
+
44
+ /** At-a-glance status for one workspace, from GET /api/workspaces/status. */
45
+ export interface WorkspaceStatus {
46
+ id: string;
47
+ branchName: string;
48
+ /** Total uncommitted files across all repo worktrees. */
49
+ changedFiles: number;
50
+ /** Number of repos with uncommitted changes. */
51
+ dirtyRepos: number;
52
+ /** Number of currently running orchestrated services. */
53
+ runningServices: number;
54
+ /** Worst-case sync classification across repos, or 'unknown' if never synced. */
55
+ syncStatus: SyncStatus | 'unknown';
56
+ /** True when any repo pulled in new commits and awaits re-validation. */
57
+ pendingValidation: boolean;
58
+ }
59
+
60
+ export interface ServiceConfig {
61
+ name: string;
62
+ cwd: string;
63
+ command: string;
64
+ args: string[];
65
+ port?: number;
66
+ source: string;
67
+ }
68
+
69
+ export interface OrchestrationDetection {
70
+ tool: string;
71
+ configPath: string;
72
+ startCommand: string;
73
+ stopCommand: string;
74
+ }
75
+
76
+ export interface RunningService {
77
+ name: string;
78
+ pid: number;
79
+ config: ServiceConfig;
80
+ startedAt: string;
81
+ }
@@ -1,25 +1,25 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
- "target": "es2023",
5
- "lib": ["ES2023", "DOM"],
6
- "module": "esnext",
7
- "types": ["vite/client"],
8
- "skipLibCheck": true,
9
-
10
- /* Bundler mode */
11
- "moduleResolution": "bundler",
12
- "allowImportingTsExtensions": true,
13
- "verbatimModuleSyntax": true,
14
- "moduleDetection": "force",
15
- "noEmit": true,
16
- "jsx": "react-jsx",
17
-
18
- /* Linting */
19
- "noUnusedLocals": true,
20
- "noUnusedParameters": true,
21
- "erasableSyntaxOnly": true,
22
- "noFallthroughCasesInSwitch": true
23
- },
24
- "include": ["src"]
25
- }
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "es2023",
5
+ "lib": ["ES2023", "DOM"],
6
+ "module": "esnext",
7
+ "types": ["vite/client"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+ "jsx": "react-jsx",
17
+
18
+ /* Linting */
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "erasableSyntaxOnly": true,
22
+ "noFallthroughCasesInSwitch": true
23
+ },
24
+ "include": ["src"]
25
+ }
package/gui/tsconfig.json CHANGED
@@ -1,7 +1,7 @@
1
- {
2
- "files": [],
3
- "references": [
4
- { "path": "./tsconfig.app.json" },
5
- { "path": "./tsconfig.node.json" }
6
- ]
7
- }
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -1,24 +1,24 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
- "target": "es2023",
5
- "lib": ["ES2023"],
6
- "module": "esnext",
7
- "types": ["node"],
8
- "skipLibCheck": true,
9
-
10
- /* Bundler mode */
11
- "moduleResolution": "bundler",
12
- "allowImportingTsExtensions": true,
13
- "verbatimModuleSyntax": true,
14
- "moduleDetection": "force",
15
- "noEmit": true,
16
-
17
- /* Linting */
18
- "noUnusedLocals": true,
19
- "noUnusedParameters": true,
20
- "erasableSyntaxOnly": true,
21
- "noFallthroughCasesInSwitch": true
22
- },
23
- "include": ["vite.config.ts"]
24
- }
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "es2023",
5
+ "lib": ["ES2023"],
6
+ "module": "esnext",
7
+ "types": ["node"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+
17
+ /* Linting */
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "erasableSyntaxOnly": true,
21
+ "noFallthroughCasesInSwitch": true
22
+ },
23
+ "include": ["vite.config.ts"]
24
+ }
@@ -1,12 +1,12 @@
1
- import { defineConfig } from 'vite'
2
- import react from '@vitejs/plugin-react'
3
- import tailwindcss from '@tailwindcss/vite'
4
-
5
- // https://vite.dev/config/
6
- export default defineConfig({
7
- plugins: [react(), tailwindcss()],
8
- build: {
9
- outDir: '../dist/gui',
10
- emptyOutDir: true,
11
- },
12
- })
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import tailwindcss from '@tailwindcss/vite'
4
+
5
+ // https://vite.dev/config/
6
+ export default defineConfig({
7
+ plugins: [react(), tailwindcss()],
8
+ build: {
9
+ outDir: '../dist/gui',
10
+ emptyOutDir: true,
11
+ },
12
+ })