groove-dev 0.27.7 → 0.27.11

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 (127) hide show
  1. package/CLAUDE.md +0 -7
  2. package/node_modules/@groove-dev/daemon/src/api.js +496 -44
  3. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +25 -12
  4. package/node_modules/@groove-dev/daemon/src/index.js +7 -0
  5. package/node_modules/@groove-dev/daemon/src/introducer.js +72 -4
  6. package/node_modules/@groove-dev/daemon/src/journalist.js +66 -11
  7. package/node_modules/@groove-dev/daemon/src/process.js +128 -104
  8. package/node_modules/@groove-dev/daemon/src/registry.js +1 -1
  9. package/node_modules/@groove-dev/daemon/src/repo-import.js +541 -0
  10. package/node_modules/@groove-dev/daemon/src/rotator.js +28 -1
  11. package/node_modules/@groove-dev/daemon/src/supervisor.js +2 -1
  12. package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +504 -0
  13. package/node_modules/@groove-dev/daemon/src/validate.js +13 -0
  14. package/node_modules/@groove-dev/daemon/test/journalist.test.js +5 -4
  15. package/node_modules/@groove-dev/daemon/test/rotator.test.js +4 -1
  16. package/node_modules/@groove-dev/gui/dist/assets/index-BE6lYcd7.css +1 -0
  17. package/node_modules/@groove-dev/gui/dist/assets/index-zdzOLAZM.js +677 -0
  18. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  19. package/node_modules/@groove-dev/gui/src/app.css +14 -0
  20. package/node_modules/@groove-dev/gui/src/app.jsx +13 -0
  21. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +130 -1
  22. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +2 -2
  23. package/node_modules/@groove-dev/gui/src/components/agents/agent-mdfiles.jsx +43 -1
  24. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +16 -17
  25. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +141 -1
  26. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +3 -3
  27. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +8 -8
  28. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  29. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  30. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +4 -4
  31. package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +7 -1
  32. package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +26 -8
  33. package/node_modules/@groove-dev/gui/src/components/layout/command-palette.jsx +14 -4
  34. package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +46 -11
  35. package/node_modules/@groove-dev/gui/src/components/marketplace/repo-card.jsx +64 -0
  36. package/node_modules/@groove-dev/gui/src/components/marketplace/repo-import.jsx +363 -0
  37. package/node_modules/@groove-dev/gui/src/components/marketplace/repo-nuke-dialog.jsx +68 -0
  38. package/node_modules/@groove-dev/gui/src/components/pro/pro-gate.jsx +22 -0
  39. package/node_modules/@groove-dev/gui/src/components/pro/upgrade-card.jsx +48 -0
  40. package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +129 -0
  41. package/node_modules/@groove-dev/gui/src/components/settings/remote-server-card.jsx +243 -0
  42. package/node_modules/@groove-dev/gui/src/components/settings/server-dialog.jsx +192 -0
  43. package/node_modules/@groove-dev/gui/src/components/ui/approval-modal.jsx +63 -0
  44. package/node_modules/@groove-dev/gui/src/components/ui/toast.jsx +1 -1
  45. package/node_modules/@groove-dev/gui/src/lib/edition.js +4 -0
  46. package/node_modules/@groove-dev/gui/src/lib/electron.js +17 -0
  47. package/node_modules/@groove-dev/gui/src/lib/status.js +1 -0
  48. package/node_modules/@groove-dev/gui/src/stores/groove.js +150 -6
  49. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +39 -40
  50. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +82 -0
  51. package/node_modules/@groove-dev/gui/src/views/settings.jsx +66 -0
  52. package/node_modules/@groove-dev/gui/vite.config.js +3 -0
  53. package/package.json +7 -2
  54. package/packages/daemon/src/api.js +496 -44
  55. package/packages/daemon/src/gateways/manager.js +25 -12
  56. package/packages/daemon/src/index.js +7 -0
  57. package/packages/daemon/src/introducer.js +72 -4
  58. package/packages/daemon/src/journalist.js +66 -11
  59. package/packages/daemon/src/process.js +128 -104
  60. package/packages/daemon/src/registry.js +1 -1
  61. package/packages/daemon/src/repo-import.js +541 -0
  62. package/packages/daemon/src/rotator.js +28 -1
  63. package/packages/daemon/src/supervisor.js +2 -1
  64. package/packages/daemon/src/tunnel-manager.js +504 -0
  65. package/packages/daemon/src/validate.js +13 -0
  66. package/packages/gui/dist/assets/index-BE6lYcd7.css +1 -0
  67. package/packages/gui/dist/assets/index-zdzOLAZM.js +677 -0
  68. package/packages/gui/dist/index.html +2 -2
  69. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js +3 -3
  70. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js +2 -2
  71. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js +3 -3
  72. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js +5 -5
  73. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-dialog.js +3 -3
  74. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-scroll-area.js +1 -1
  75. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tabs.js +5 -5
  76. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tooltip.js +3 -3
  77. package/packages/gui/node_modules/.vite/deps/_metadata.json +53 -53
  78. package/packages/gui/node_modules/.vite/deps/{chunk-WYSQD5ZG.js → chunk-DH7AESXW.js} +2 -2
  79. package/packages/gui/node_modules/.vite/deps/{chunk-KXLIKZFX.js → chunk-GFE3G4IN.js} +133 -133
  80. package/packages/gui/node_modules/.vite/deps/chunk-GFE3G4IN.js.map +7 -0
  81. package/packages/gui/node_modules/.vite/deps/{chunk-3LBP22MX.js → chunk-LKZVMLRH.js} +6 -6
  82. package/packages/gui/node_modules/.vite/deps/{chunk-J6DMOQWP.js → chunk-MCVDVNE5.js} +2 -2
  83. package/packages/gui/node_modules/.vite/deps/{chunk-3Q7HT7ZF.js → chunk-SPKVQGZX.js} +6 -6
  84. package/packages/gui/src/app.css +14 -0
  85. package/packages/gui/src/app.jsx +13 -0
  86. package/packages/gui/src/components/agents/agent-config.jsx +130 -1
  87. package/packages/gui/src/components/agents/agent-feed.jsx +2 -2
  88. package/packages/gui/src/components/agents/agent-mdfiles.jsx +43 -1
  89. package/packages/gui/src/components/agents/agent-node.jsx +16 -17
  90. package/packages/gui/src/components/agents/spawn-wizard.jsx +141 -1
  91. package/packages/gui/src/components/dashboard/fleet-panel.jsx +3 -3
  92. package/packages/gui/src/components/dashboard/intel-panel.jsx +8 -8
  93. package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
  94. package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  95. package/packages/gui/src/components/layout/activity-bar.jsx +4 -4
  96. package/packages/gui/src/components/layout/app-shell.jsx +7 -1
  97. package/packages/gui/src/components/layout/breadcrumb-bar.jsx +26 -8
  98. package/packages/gui/src/components/layout/command-palette.jsx +14 -4
  99. package/packages/gui/src/components/layout/status-bar.jsx +46 -11
  100. package/packages/gui/src/components/marketplace/repo-card.jsx +64 -0
  101. package/packages/gui/src/components/marketplace/repo-import.jsx +363 -0
  102. package/packages/gui/src/components/marketplace/repo-nuke-dialog.jsx +68 -0
  103. package/packages/gui/src/components/pro/pro-gate.jsx +22 -0
  104. package/packages/gui/src/components/pro/upgrade-card.jsx +48 -0
  105. package/packages/gui/src/components/settings/quick-connect.jsx +129 -0
  106. package/packages/gui/src/components/settings/remote-server-card.jsx +243 -0
  107. package/packages/gui/src/components/settings/server-dialog.jsx +192 -0
  108. package/packages/gui/src/components/ui/approval-modal.jsx +63 -0
  109. package/packages/gui/src/components/ui/toast.jsx +1 -1
  110. package/packages/gui/src/lib/edition.js +4 -0
  111. package/packages/gui/src/lib/electron.js +17 -0
  112. package/packages/gui/src/lib/status.js +1 -0
  113. package/packages/gui/src/stores/groove.js +150 -6
  114. package/packages/gui/src/views/dashboard.jsx +39 -40
  115. package/packages/gui/src/views/marketplace.jsx +82 -0
  116. package/packages/gui/src/views/settings.jsx +66 -0
  117. package/packages/gui/vite.config.js +3 -0
  118. package/node_modules/@groove-dev/gui/dist/assets/index-Bl1_J0sN.js +0 -652
  119. package/node_modules/@groove-dev/gui/dist/assets/index-DjORRpF0.css +0 -1
  120. package/packages/gui/dist/assets/index-Bl1_J0sN.js +0 -652
  121. package/packages/gui/dist/assets/index-DjORRpF0.css +0 -1
  122. package/packages/gui/node_modules/.vite/deps/chunk-KXLIKZFX.js.map +0 -7
  123. package/test-slack.mjs +0 -28
  124. /package/packages/gui/node_modules/.vite/deps/{chunk-WYSQD5ZG.js.map → chunk-DH7AESXW.js.map} +0 -0
  125. /package/packages/gui/node_modules/.vite/deps/{chunk-3LBP22MX.js.map → chunk-LKZVMLRH.js.map} +0 -0
  126. /package/packages/gui/node_modules/.vite/deps/{chunk-J6DMOQWP.js.map → chunk-MCVDVNE5.js.map} +0 -0
  127. /package/packages/gui/node_modules/.vite/deps/{chunk-3Q7HT7ZF.js.map → chunk-SPKVQGZX.js.map} +0 -0
@@ -7,6 +7,7 @@ import { api } from '../lib/api';
7
7
  const WS_URL = `ws://${window.location.hostname}:${window.location.port || 31415}`;
8
8
 
9
9
  let toastCounter = 0;
10
+ let plannerPollInterval = null;
10
11
 
11
12
  function loadJSON(key, fallback = {}) {
12
13
  try { return JSON.parse(localStorage.getItem(key) || JSON.stringify(fallback)); }
@@ -46,6 +47,10 @@ export const useGrooveStore = create((set, get) => ({
46
47
  detailPanel: null, // null | { type: 'agent', agentId } | { type: 'spawn' } | { type: 'journalist' }
47
48
  teamDetailPanels: {}, // { [teamId]: detailPanel } — persists panel state per team
48
49
  commandPaletteOpen: false,
50
+ quickConnectOpen: false,
51
+
52
+ // ── Node expansion (click-to-open persistent panels) ───────
53
+ expandedNodes: loadJSON('groove:expandedNodes'),
49
54
 
50
55
  // ── Layout persistence ────────────────────────────────────
51
56
  detailPanelWidth: Number(localStorage.getItem('groove:detailWidth')) || 480,
@@ -76,6 +81,14 @@ export const useGrooveStore = create((set, get) => ({
76
81
  // ── Toasts ────────────────────────────────────────────────
77
82
  toasts: [],
78
83
 
84
+ // ── Tunnels ────────────────────────────────────────────────
85
+ savedTunnels: [],
86
+ activeTunnelId: null,
87
+
88
+ // ── GitHub Repo Import ────────────────────────────────────
89
+ importedRepos: [],
90
+ importInProgress: false,
91
+
79
92
  // ── Editor state ──────────────────────────────────────────
80
93
  editorFiles: {},
81
94
  editorActiveFile: null,
@@ -129,6 +142,22 @@ export const useGrooveStore = create((set, get) => ({
129
142
  || p.contextUsage !== a.contextUsage || p.name !== a.name || p.model !== a.model;
130
143
  });
131
144
  set({ agents: changed ? msg.data : prev, tokenTimeline: timeline, hydrated: true });
145
+
146
+ // Poll for recommended-team.json while a planner is running
147
+ const hasRunningPlanner = msg.data.some((a) => a.role === 'planner' && a.status === 'running');
148
+ if (hasRunningPlanner && !plannerPollInterval && !get().recommendedTeam) {
149
+ plannerPollInterval = setInterval(() => {
150
+ if (get().recommendedTeam) {
151
+ clearInterval(plannerPollInterval);
152
+ plannerPollInterval = null;
153
+ return;
154
+ }
155
+ get().checkRecommendedTeam();
156
+ }, 3000);
157
+ } else if ((!hasRunningPlanner || get().recommendedTeam) && plannerPollInterval) {
158
+ clearInterval(plannerPollInterval);
159
+ plannerPollInterval = null;
160
+ }
132
161
  break;
133
162
  }
134
163
 
@@ -317,7 +346,6 @@ export const useGrooveStore = create((set, get) => ({
317
346
 
318
347
  case 'approval:request':
319
348
  set((s) => ({ pendingApprovals: [...s.pendingApprovals, msg.data] }));
320
- get().addToast('warning', `Approval needed: ${msg.data?.agentName || 'agent'}`, msg.data?.action?.description);
321
349
  break;
322
350
 
323
351
  case 'approval:resolved': {
@@ -348,10 +376,32 @@ export const useGrooveStore = create((set, get) => ({
348
376
  case 'gateway:status':
349
377
  set({ gateways: msg.data || [] });
350
378
  break;
379
+
380
+ case 'tunnel.connected':
381
+ set({ activeTunnelId: msg.data?.id || null });
382
+ get().fetchTunnels();
383
+ break;
384
+
385
+ case 'tunnel.disconnected':
386
+ set({ activeTunnelId: null });
387
+ get().fetchTunnels();
388
+ break;
389
+
390
+ case 'tunnel.health': {
391
+ const tunnels = get().savedTunnels.map((t) =>
392
+ t.id === msg.data?.id ? { ...t, latencyMs: msg.data.latencyMs, healthy: msg.data.healthy } : t,
393
+ );
394
+ set({ savedTunnels: tunnels });
395
+ break;
396
+ }
351
397
  }
352
398
  };
353
399
 
354
400
  ws.onclose = () => {
401
+ if (plannerPollInterval) {
402
+ clearInterval(plannerPollInterval);
403
+ plannerPollInterval = null;
404
+ }
355
405
  set({ connected: false, hydrated: false, ws: null, daemonHost: null, tunneled: false });
356
406
  setTimeout(() => get().connect(), 2000);
357
407
  };
@@ -459,6 +509,7 @@ export const useGrooveStore = create((set, get) => ({
459
509
  set((s) => ({ detailPanel: null, teamDetailPanels: { ...s.teamDetailPanels, [tid]: null } }));
460
510
  },
461
511
  toggleCommandPalette() { set((s) => ({ commandPaletteOpen: !s.commandPaletteOpen })); },
512
+ toggleQuickConnect() { set((s) => ({ quickConnectOpen: !s.quickConnectOpen })); },
462
513
 
463
514
  setDetailPanelWidth(w) {
464
515
  set({ detailPanelWidth: w });
@@ -474,6 +525,14 @@ export const useGrooveStore = create((set, get) => ({
474
525
  },
475
526
  setTerminalFullHeight(v) { set({ terminalFullHeight: v }); },
476
527
 
528
+ toggleNodeExpanded(id) {
529
+ const expanded = { ...get().expandedNodes };
530
+ expanded[id] = !expanded[id];
531
+ if (!expanded[id]) delete expanded[id];
532
+ set({ expandedNodes: expanded });
533
+ persistJSON('groove:expandedNodes', expanded);
534
+ },
535
+
477
536
  // ── Toasts ────────────────────────────────────────────────
478
537
 
479
538
  addToast(type, message, detail) {
@@ -585,9 +644,7 @@ export const useGrooveStore = create((set, get) => ({
585
644
 
586
645
  // Check if all recommended roles already exist in the planner's team.
587
646
  // If so, auto-delegate instead of showing the "Launch Team" modal.
588
- const planners = get().agents.filter((a) => a.role === 'planner');
589
- const planner = planners.sort((a, b) => (b.lastActivity || '').localeCompare(a.lastActivity || ''))[0];
590
- const teamId = planner?.teamId;
647
+ const teamId = data.teamId || null;
591
648
 
592
649
  if (teamId) {
593
650
  const teamAgents = get().agents.filter((a) => a.teamId === teamId && a.role !== 'planner');
@@ -613,7 +670,7 @@ export const useGrooveStore = create((set, get) => ({
613
670
  }
614
671
 
615
672
  // New agents needed — show the modal for approval
616
- set({ recommendedTeam: data });
673
+ set({ recommendedTeam: { ...data, teamId: data.teamId || null } });
617
674
  } catch {
618
675
  set({ recommendedTeam: null });
619
676
  }
@@ -621,9 +678,10 @@ export const useGrooveStore = create((set, get) => ({
621
678
 
622
679
  async launchRecommendedTeam(modifiedAgents) {
623
680
  try {
681
+ const teamId = get().recommendedTeam?.teamId || null;
624
682
  set({ recommendedTeam: null }); // Dismiss modal immediately
625
683
  get().addToast('info', 'Launching team...');
626
- const body = modifiedAgents ? { agents: modifiedAgents } : undefined;
684
+ const body = { ...(modifiedAgents && { agents: modifiedAgents }), ...(teamId && { teamId }) };
627
685
  const result = await api.post('/recommended-team/launch', body);
628
686
  const sub = [
629
687
  result.phase2Pending ? `${result.phase2Pending} QC queued` : '',
@@ -646,6 +704,92 @@ export const useGrooveStore = create((set, get) => ({
646
704
  }
647
705
  },
648
706
 
707
+ // ── GitHub Repo Import ────────────────────────────────────
708
+
709
+ async fetchImportedRepos() {
710
+ try {
711
+ const repos = await api.get('/repos/imported');
712
+ set({ importedRepos: repos });
713
+ } catch { /* ignore */ }
714
+ },
715
+
716
+ async previewRepo(repoUrl) {
717
+ return api.post('/repos/preview', { repoUrl });
718
+ },
719
+
720
+ async importRepo(repoUrl, targetPath, createTeam, teamName) {
721
+ set({ importInProgress: true });
722
+ try {
723
+ const result = await api.post('/repos/import', { repoUrl, targetPath, createTeam, teamName });
724
+ get().fetchImportedRepos();
725
+ return result;
726
+ } finally {
727
+ set({ importInProgress: false });
728
+ }
729
+ },
730
+
731
+ async softRemoveRepo(importId) {
732
+ await api.delete('/repos/' + importId + '/remove');
733
+ get().fetchImportedRepos();
734
+ },
735
+
736
+ async hardNukeRepo(importId, deleteFiles = true) {
737
+ await api.delete('/repos/' + importId + '/nuke?deleteFiles=' + deleteFiles);
738
+ get().fetchImportedRepos();
739
+ },
740
+
741
+ // ── Tunnels ──────────────────────────────────────────────
742
+
743
+ async fetchTunnels() {
744
+ try {
745
+ const tunnels = await api.get('/tunnels');
746
+ set({ savedTunnels: Array.isArray(tunnels) ? tunnels : [] });
747
+ } catch {}
748
+ },
749
+
750
+ async saveTunnel(config) {
751
+ const result = await api.post('/tunnels', config);
752
+ get().fetchTunnels();
753
+ return result;
754
+ },
755
+
756
+ async updateTunnel(id, config) {
757
+ const result = await api.patch('/tunnels/' + id, config);
758
+ get().fetchTunnels();
759
+ return result;
760
+ },
761
+
762
+ async deleteTunnel(id) {
763
+ await api.delete('/tunnels/' + id);
764
+ get().fetchTunnels();
765
+ },
766
+
767
+ async testTunnel(id) {
768
+ return api.post('/tunnels/' + id + '/test');
769
+ },
770
+
771
+ async connectTunnel(id) {
772
+ const result = await api.post('/tunnels/' + id + '/connect');
773
+ set({ activeTunnelId: id });
774
+ get().fetchTunnels();
775
+ if (result.url) window.open(result.url, '_blank');
776
+ return result;
777
+ },
778
+
779
+ async disconnectTunnel(id) {
780
+ await api.post('/tunnels/' + id + '/disconnect');
781
+ set({ activeTunnelId: null });
782
+ get().fetchTunnels();
783
+ },
784
+
785
+ async installTunnel(id) {
786
+ return api.post('/tunnels/' + id + '/install');
787
+ },
788
+
789
+ async startTunnel(id) {
790
+ return api.post('/tunnels/' + id + '/start');
791
+ },
792
+
649
793
  // ── Journalist ────────────────────────────────────────────
650
794
 
651
795
  async fetchJournalist() {
@@ -19,7 +19,7 @@ function DashboardSkeleton() {
19
19
  return (
20
20
  <div className="flex-1 grid gap-px p-0" style={{
21
21
  gridTemplateRows: 'auto minmax(0, 1fr) minmax(0, 1fr)',
22
- gridTemplateColumns: '3fr 1.5fr 1.5fr',
22
+ gridTemplateColumns: '2fr 2.5fr 1.5fr',
23
23
  background: '#282c34',
24
24
  }}>
25
25
  <div className="col-span-3"><Skeleton className="h-[72px] rounded-none" /></div>
@@ -112,54 +112,53 @@ export default function DashboardView() {
112
112
 
113
113
  <KpiStrip kpis={kpis} />
114
114
 
115
- <div className="flex-1 min-h-0 grid" style={{
116
- gridTemplateRows: 'minmax(0, 1fr) minmax(0, 1fr)',
117
- gridTemplateColumns: '3fr 1.5fr 1.5fr',
118
- background: '#282c34',
119
- gap: '1px',
120
- }}>
121
- <div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 relative">
122
- <TokenChart data={snapshots} />
123
- </div>
115
+ <div className="flex-1 min-h-0 flex flex-col" style={{ background: '#282c34', gap: '1px' }}>
116
+ <div className="min-h-0 flex-1 grid" style={{ gridTemplateColumns: '3fr 1.5fr 1.5fr', gap: '0 1px' }}>
117
+ <div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 relative">
118
+ <TokenChart data={snapshots} />
119
+ </div>
124
120
 
125
- <div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-l border-border">
126
- <div className="px-3 pt-2.5 pb-1">
127
- <span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Cache Performance</span>
121
+ <div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-l border-border">
122
+ <div className="px-3 pt-2.5 pb-1">
123
+ <span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Cache Performance</span>
124
+ </div>
125
+ <CacheRing
126
+ cacheRead={tokens.cacheReadTokens}
127
+ cacheCreation={tokens.cacheCreationTokens}
128
+ totalInput={tokens.totalInputTokens}
129
+ />
128
130
  </div>
129
- <CacheRing
130
- cacheRead={tokens.cacheReadTokens}
131
- cacheCreation={tokens.cacheCreationTokens}
132
- totalInput={tokens.totalInputTokens}
133
- />
134
- </div>
135
131
 
136
- <div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-l border-border">
137
- <div className="px-3 pt-2.5 pb-1">
138
- <span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Model Routing</span>
132
+ <div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-l border-border">
133
+ <div className="px-3 pt-2.5 pb-1">
134
+ <span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Model Routing</span>
135
+ </div>
136
+ <RoutingChart routing={routing} agentBreakdown={agentBreakdown} />
139
137
  </div>
140
- <RoutingChart routing={routing} agentBreakdown={agentBreakdown} />
141
138
  </div>
142
139
 
143
- <div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-t border-border">
144
- <div className="px-3 pt-2.5 pb-1 flex-shrink-0">
145
- <span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Agent Fleet</span>
140
+ <div className="min-h-0 flex-1 grid" style={{ gridTemplateColumns: '2fr 2.5fr 1.5fr', gap: '0 1px' }}>
141
+ <div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-t border-border">
142
+ <div className="px-3 pt-2.5 pb-1 flex-shrink-0">
143
+ <span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Agent Fleet</span>
144
+ </div>
145
+ <FleetPanel agentBreakdown={agentBreakdown} rotating={rotating} teams={teams} />
146
146
  </div>
147
- <FleetPanel agentBreakdown={agentBreakdown} rotating={rotating} teams={teams} />
148
- </div>
149
147
 
150
- <div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-t border-l border-border">
151
- <IntelPanel
152
- tokens={tokens}
153
- rotation={rotation}
154
- adaptive={adaptive}
155
- journalist={journalist}
156
- agentBreakdown={agentBreakdown}
157
- memory={memory}
158
- />
159
- </div>
148
+ <div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-t border-l border-border">
149
+ <IntelPanel
150
+ tokens={tokens}
151
+ rotation={rotation}
152
+ adaptive={adaptive}
153
+ journalist={journalist}
154
+ agentBreakdown={agentBreakdown}
155
+ memory={memory}
156
+ />
157
+ </div>
160
158
 
161
- <div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-t border-l border-border">
162
- <TeamBurnPanel teams={teamBurn} />
159
+ <div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-t border-l border-border">
160
+ <TeamBurnPanel teams={teamBurn} />
161
+ </div>
163
162
  </div>
164
163
  </div>
165
164
 
@@ -18,9 +18,13 @@ import { useToast } from '../lib/hooks/use-toast';
18
18
  import { fmtNum, timeAgo } from '../lib/format';
19
19
  import { useGrooveStore } from '../stores/groove';
20
20
  import { IntegrationWizard, GoogleWorkspaceWizard } from '../components/marketplace/integration-wizard';
21
+ import { RepoImport } from '../components/marketplace/repo-import';
22
+ import { RepoCard } from '../components/marketplace/repo-card';
23
+ import { RepoNukeDialog } from '../components/marketplace/repo-nuke-dialog';
21
24
  import {
22
25
  ChevronLeft, ChevronDown, Sparkles, Plug, LogIn, LogOut,
23
26
  User, Upload, Package, Download, ShoppingBag, RefreshCw, Trash2,
27
+ GitBranch,
24
28
  } from 'lucide-react';
25
29
 
26
30
  // ── Skill Detail ─────────────────────────────────────────
@@ -623,6 +627,82 @@ function AuthArea() {
623
627
  );
624
628
  }
625
629
 
630
+ // ── GitHub Browse ───────────────────────────────────────
631
+ function GitHubBrowse() {
632
+ const importedRepos = useGrooveStore((s) => s.importedRepos);
633
+ const fetchImportedRepos = useGrooveStore((s) => s.fetchImportedRepos);
634
+ const softRemoveRepo = useGrooveStore((s) => s.softRemoveRepo);
635
+ const hardNukeRepo = useGrooveStore((s) => s.hardNukeRepo);
636
+ const toast = useToast();
637
+
638
+ const [nukeTarget, setNukeTarget] = useState(null);
639
+
640
+ useEffect(() => { fetchImportedRepos(); }, []);
641
+
642
+ async function handleRemove(repo) {
643
+ try {
644
+ await softRemoveRepo(repo.id);
645
+ toast.success(`Removed ${repo.repoName || repo.name}`);
646
+ } catch (err) {
647
+ toast.error('Remove failed', err.message);
648
+ }
649
+ }
650
+
651
+ async function handleNukeConfirm(deleteFiles) {
652
+ if (!nukeTarget) return;
653
+ try {
654
+ if (deleteFiles) {
655
+ await hardNukeRepo(nukeTarget.id);
656
+ } else {
657
+ await softRemoveRepo(nukeTarget.id);
658
+ }
659
+ toast.success(`${deleteFiles ? 'Nuked' : 'Removed'} ${nukeTarget.name || nukeTarget.repo}`);
660
+ } catch (err) {
661
+ toast.error('Nuke failed', err.message);
662
+ }
663
+ setNukeTarget(null);
664
+ }
665
+
666
+ return (
667
+ <ScrollArea className="h-full">
668
+ <div className="px-5 py-4 space-y-5">
669
+ <RepoImport />
670
+
671
+ {importedRepos.length > 0 && (
672
+ <div>
673
+ <h3 className="text-xs font-semibold text-text-2 font-sans uppercase tracking-wider mb-3">
674
+ Recently Imported
675
+ </h3>
676
+ <div className="space-y-2">
677
+ {(Array.isArray(importedRepos) ? importedRepos : []).map((repo) => (
678
+ <RepoCard
679
+ key={repo.id}
680
+ repo={repo}
681
+ onRemove={() => handleRemove(repo)}
682
+ onNuke={() => setNukeTarget(repo)}
683
+ />
684
+ ))}
685
+ </div>
686
+ </div>
687
+ )}
688
+
689
+ {importedRepos.length === 0 && (
690
+ <div className="text-center py-16 text-text-4 font-sans text-sm">
691
+ No repos imported yet. Paste a GitHub URL above to get started.
692
+ </div>
693
+ )}
694
+ </div>
695
+
696
+ <RepoNukeDialog
697
+ repo={nukeTarget}
698
+ open={!!nukeTarget}
699
+ onClose={() => setNukeTarget(null)}
700
+ onConfirm={handleNukeConfirm}
701
+ />
702
+ </ScrollArea>
703
+ );
704
+ }
705
+
626
706
  // ── Main ─────────────────────────────────────────────────
627
707
  export default function MarketplaceView() {
628
708
  const [tab, setTab] = useState('skills');
@@ -630,6 +710,7 @@ export default function MarketplaceView() {
630
710
  const tabs = [
631
711
  { id: 'skills', label: 'Skills', icon: Sparkles },
632
712
  { id: 'integrations', label: 'Integrations', icon: Plug },
713
+ { id: 'github', label: 'GitHub', icon: GitBranch },
633
714
  { id: 'library', label: 'My Library', icon: Package },
634
715
  ];
635
716
 
@@ -662,6 +743,7 @@ export default function MarketplaceView() {
662
743
  <div className="flex-1 min-h-0">
663
744
  {tab === 'skills' && <SkillsBrowse />}
664
745
  {tab === 'integrations' && <IntegrationsBrowse />}
746
+ {tab === 'github' && <GitHubBrowse />}
665
747
  {tab === 'library' && <MyLibrary />}
666
748
  </div>
667
749
  </div>
@@ -12,6 +12,9 @@ import { Sheet, SheetContent } from '../components/ui/sheet';
12
12
  import { api } from '../lib/api';
13
13
  import { cn } from '../lib/cn';
14
14
  import { fmtUptime } from '../lib/format';
15
+ import { RemoteServerCard } from '../components/settings/remote-server-card';
16
+ import { ServerDialog } from '../components/settings/server-dialog';
17
+ import { ProGate } from '../components/pro/pro-gate';
15
18
  import {
16
19
  Key, Eye, EyeOff, Check, Cpu, ChevronDown,
17
20
  FolderOpen, FolderSearch, RotateCw, Users, Gauge, Zap,
@@ -974,6 +977,9 @@ export default function SettingsView() {
974
977
  const [gwList, setGwList] = useState([]);
975
978
  const [loading, setLoading] = useState(true);
976
979
  const [folderBrowserOpen, setFolderBrowserOpen] = useState(false);
980
+ const [serverDialogOpen, setServerDialogOpen] = useState(false);
981
+ const [editingServer, setEditingServer] = useState(null);
982
+ const savedTunnels = useGrooveStore((s) => s.savedTunnels);
977
983
  const addToast = useGrooveStore((s) => s.addToast);
978
984
  const marketplaceUser = useGrooveStore((s) => s.marketplaceUser);
979
985
  const marketplaceAuthenticated = useGrooveStore((s) => s.marketplaceAuthenticated);
@@ -992,6 +998,7 @@ export default function SettingsView() {
992
998
  Promise.all([api.get('/providers'), api.get('/config'), api.get('/status'), api.get('/gateways')])
993
999
  .then(([p, c, s, g]) => { setProviders(Array.isArray(p) ? p : []); setConfig(c); setDaemonInfo(s); setGwList(Array.isArray(g) ? g : []); setLoading(false); })
994
1000
  .catch(() => setLoading(false));
1001
+ useGrooveStore.getState().fetchTunnels();
995
1002
  }, []);
996
1003
 
997
1004
  async function addGateway(type) {
@@ -1221,9 +1228,68 @@ export default function SettingsView() {
1221
1228
  </div>
1222
1229
  </div>
1223
1230
  )}
1231
+
1232
+ {/* ═══════ REMOTE SERVERS ═══════ */}
1233
+ <div>
1234
+ <div className="flex items-center gap-2 mb-2.5 px-0.5">
1235
+ <span className="text-2xs font-semibold text-text-3 font-sans uppercase tracking-wider">Remote Servers</span>
1236
+ <div className="flex-1 h-px bg-border-subtle" />
1237
+ </div>
1238
+ <ProGate feature="Remote Access" description="Connect to remote servers via SSH tunnel and manage agents across machines">
1239
+ <div>
1240
+ <div className="flex justify-end mb-2.5">
1241
+ <Button
1242
+ variant="ghost"
1243
+ size="sm"
1244
+ onClick={() => { setEditingServer(null); setServerDialogOpen(true); }}
1245
+ className="h-6 text-2xs gap-1 text-text-3 hover:text-accent"
1246
+ >
1247
+ <Plus size={11} /> Add Server
1248
+ </Button>
1249
+ </div>
1250
+ {savedTunnels.length === 0 ? (
1251
+ <div className="rounded-lg border border-dashed border-border-subtle bg-surface-1/50 px-4 py-6 text-center">
1252
+ <Radio size={20} className="text-text-4 mx-auto mb-2" />
1253
+ <p className="text-xs text-text-3 font-sans">No remote servers configured.</p>
1254
+ <p className="text-2xs text-text-4 font-sans mt-1">Add one to connect to a VPS or remote machine.</p>
1255
+ </div>
1256
+ ) : (
1257
+ <div className="grid grid-cols-2 gap-3">
1258
+ {savedTunnels.map((server) => (
1259
+ <RemoteServerCard
1260
+ key={server.id}
1261
+ server={server}
1262
+ onConnect={() => useGrooveStore.getState().connectTunnel(server.id)}
1263
+ onDisconnect={() => useGrooveStore.getState().disconnectTunnel(server.id)}
1264
+ onTest={() => useGrooveStore.getState().testTunnel(server.id)}
1265
+ onEdit={(s) => { setEditingServer(s); setServerDialogOpen(true); }}
1266
+ onDelete={(id) => useGrooveStore.getState().deleteTunnel(id)}
1267
+ />
1268
+ ))}
1269
+ </div>
1270
+ )}
1271
+ </div>
1272
+ </ProGate>
1273
+ </div>
1274
+
1224
1275
  </div>
1225
1276
  </ScrollArea>
1226
1277
 
1278
+ {/* Server Dialog */}
1279
+ <ServerDialog
1280
+ open={serverDialogOpen}
1281
+ onOpenChange={setServerDialogOpen}
1282
+ server={editingServer}
1283
+ onSave={async (data) => {
1284
+ if (data.id) {
1285
+ await useGrooveStore.getState().updateTunnel(data.id, data);
1286
+ } else {
1287
+ await useGrooveStore.getState().saveTunnel(data);
1288
+ }
1289
+ addToast('success', data.id ? 'Server updated' : 'Server added');
1290
+ }}
1291
+ />
1292
+
1227
1293
  {/* Folder Browser Modal */}
1228
1294
  <FolderBrowser
1229
1295
  open={folderBrowserOpen}
@@ -4,6 +4,9 @@ import tailwindcss from '@tailwindcss/vite';
4
4
 
5
5
  export default defineConfig({
6
6
  plugins: [tailwindcss(), react()],
7
+ define: {
8
+ __GROOVE_EDITION__: JSON.stringify(process.env.GROOVE_EDITION || 'community'),
9
+ },
7
10
  server: {
8
11
  port: 3142,
9
12
  proxy: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.27.7",
3
+ "version": "0.27.11",
4
4
  "description": "Open-source agent orchestration layer — the AI company OS. Local model agent engine (GGUF/Ollama/llama-server), HuggingFace model browser, MCP integrations (Slack, Gmail, Stripe, 15+), agent scheduling (cron), business roles (CMO, CFO, EA). GUI dashboard, multi-agent coordination, zero cold-start, infinite sessions. Works with Claude Code, Codex, Gemini CLI, Ollama, any local model.",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
@@ -43,7 +43,8 @@
43
43
  "workspaces": [
44
44
  "packages/daemon",
45
45
  "packages/cli",
46
- "packages/gui"
46
+ "packages/gui",
47
+ "packages/desktop"
47
48
  ],
48
49
  "engines": {
49
50
  "node": ">=20.0.0"
@@ -52,6 +53,10 @@
52
53
  "dev:daemon": "npm run dev -w packages/daemon",
53
54
  "dev:gui": "npm run dev -w packages/gui",
54
55
  "build": "npm run build -w packages/gui",
56
+ "build:pro": "GROOVE_EDITION=pro npm run build -w packages/gui",
57
+ "start:desktop": "npm run start -w packages/desktop",
58
+ "build:desktop": "npm run build -w packages/desktop",
59
+ "dist:desktop": "npm run build:pro && npm run dist -w packages/desktop",
55
60
  "test": "node --test packages/daemon/test/*.test.js",
56
61
  "prepublishOnly": "npm run build"
57
62
  },