groove-dev 0.27.142 → 0.27.144

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 (187) hide show
  1. package/node_modules/@groove-dev/cli/package.json +1 -1
  2. package/node_modules/@groove-dev/daemon/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
  4. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
  5. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  6. package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
  7. package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
  8. package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
  9. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  10. package/node_modules/@groove-dev/daemon/src/process.js +2 -2
  11. package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
  12. package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
  13. package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
  14. package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
  15. package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
  16. package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
  17. package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
  18. package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
  19. package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
  21. package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
  22. package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
  23. package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
  24. package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
  25. package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
  26. package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
  27. package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
  28. package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
  29. package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
  30. package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  31. package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  32. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  33. package/node_modules/@groove-dev/gui/package.json +1 -1
  34. package/{packages/gui/src/app.jsx → node_modules/@groove-dev/gui/src/App.jsx} +0 -2
  35. package/node_modules/@groove-dev/gui/src/app.css +35 -0
  36. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
  37. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
  38. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
  39. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
  40. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
  41. package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
  42. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
  43. package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
  44. package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
  45. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
  46. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
  47. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
  48. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  49. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
  50. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
  51. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  52. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  53. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
  54. package/node_modules/@groove-dev/gui/src/components/editor/selection-menu.jsx +2 -0
  55. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
  56. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
  57. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +195 -14
  58. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +286 -102
  59. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
  60. package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
  61. package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
  62. package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
  63. package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
  64. package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
  65. package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
  66. package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
  67. package/node_modules/@groove-dev/gui/src/lib/status.js +24 -24
  68. package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
  69. package/node_modules/@groove-dev/gui/src/stores/groove.js +34 -3144
  70. package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
  71. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +452 -0
  72. package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
  73. package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +227 -0
  74. package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
  75. package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
  76. package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
  77. package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
  78. package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
  79. package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
  80. package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
  81. package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
  82. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
  83. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
  84. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +17 -6
  85. package/node_modules/@groove-dev/gui/src/views/models.jsx +410 -509
  86. package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
  87. package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
  88. package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
  89. package/package.json +1 -1
  90. package/packages/cli/package.json +1 -1
  91. package/packages/daemon/package.json +1 -1
  92. package/packages/daemon/src/api.js +1086 -6532
  93. package/packages/daemon/src/gateways/manager.js +35 -1
  94. package/packages/daemon/src/index.js +3 -0
  95. package/packages/daemon/src/journalist.js +23 -13
  96. package/packages/daemon/src/mlx-server.js +365 -0
  97. package/packages/daemon/src/model-lab.js +308 -12
  98. package/packages/daemon/src/pm.js +1 -1
  99. package/packages/daemon/src/process.js +2 -2
  100. package/packages/daemon/src/providers/local.js +36 -8
  101. package/packages/daemon/src/registry.js +21 -5
  102. package/packages/daemon/src/routes/agents.js +889 -0
  103. package/packages/daemon/src/routes/coordination.js +318 -0
  104. package/packages/daemon/src/routes/files.js +751 -0
  105. package/packages/daemon/src/routes/integrations.js +485 -0
  106. package/packages/daemon/src/routes/network.js +1784 -0
  107. package/packages/daemon/src/routes/providers.js +755 -0
  108. package/packages/daemon/src/routes/schedules.js +110 -0
  109. package/packages/daemon/src/routes/teams.js +650 -0
  110. package/packages/daemon/src/scheduler.js +456 -24
  111. package/packages/daemon/src/teams.js +1 -1
  112. package/packages/daemon/src/validate.js +38 -1
  113. package/packages/daemon/templates/mlx-setup.json +12 -0
  114. package/packages/daemon/templates/tgi-setup.json +1 -1
  115. package/packages/daemon/templates/vllm-setup.json +1 -1
  116. package/packages/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  117. package/packages/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  118. package/packages/gui/dist/index.html +2 -2
  119. package/packages/gui/package.json +1 -1
  120. package/{node_modules/@groove-dev/gui/src/app.jsx → packages/gui/src/App.jsx} +0 -2
  121. package/packages/gui/src/app.css +35 -0
  122. package/packages/gui/src/components/agents/agent-config.jsx +1 -128
  123. package/packages/gui/src/components/agents/agent-feed.jsx +144 -31
  124. package/packages/gui/src/components/agents/agent-node.jsx +8 -13
  125. package/packages/gui/src/components/agents/code-review.jsx +159 -122
  126. package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
  127. package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
  128. package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
  129. package/packages/gui/src/components/automations/automation-card.jsx +274 -0
  130. package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
  131. package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
  132. package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
  133. package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
  134. package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  135. package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
  136. package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
  137. package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
  138. package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  139. package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
  140. package/packages/gui/src/components/editor/selection-menu.jsx +2 -0
  141. package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
  142. package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
  143. package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
  144. package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
  145. package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
  146. package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
  147. package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
  148. package/packages/gui/src/components/network/network-health.jsx +2 -2
  149. package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
  150. package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
  151. package/packages/gui/src/components/ui/sheet.jsx +5 -2
  152. package/packages/gui/src/lib/cron.js +64 -0
  153. package/packages/gui/src/lib/status.js +24 -24
  154. package/packages/gui/src/lib/theme-hex.js +1 -0
  155. package/packages/gui/src/stores/groove.js +34 -3144
  156. package/packages/gui/src/stores/helpers.js +10 -0
  157. package/packages/gui/src/stores/slices/agents-slice.js +452 -0
  158. package/packages/gui/src/stores/slices/automations-slice.js +96 -0
  159. package/packages/gui/src/stores/slices/chat-slice.js +227 -0
  160. package/packages/gui/src/stores/slices/editor-slice.js +285 -0
  161. package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
  162. package/packages/gui/src/stores/slices/network-slice.js +361 -0
  163. package/packages/gui/src/stores/slices/preview-slice.js +109 -0
  164. package/packages/gui/src/stores/slices/providers-slice.js +897 -0
  165. package/packages/gui/src/stores/slices/teams-slice.js +413 -0
  166. package/packages/gui/src/stores/slices/ui-slice.js +98 -0
  167. package/packages/gui/src/views/agents.jsx +5 -5
  168. package/packages/gui/src/views/dashboard.jsx +12 -13
  169. package/packages/gui/src/views/marketplace.jsx +191 -3
  170. package/packages/gui/src/views/model-lab.jsx +17 -6
  171. package/packages/gui/src/views/models.jsx +410 -509
  172. package/packages/gui/src/views/network.jsx +3 -3
  173. package/packages/gui/src/views/settings.jsx +81 -94
  174. package/packages/gui/src/views/teams.jsx +40 -483
  175. package/SECURITY_SWEEP.md +0 -228
  176. package/TRAINING_DATA_v4.md +0 -6
  177. package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +0 -984
  178. package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +0 -1
  179. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -322
  180. package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
  181. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
  182. package/packages/gui/dist/assets/index-Bjd91ufV.js +0 -984
  183. package/packages/gui/dist/assets/index-BqdwIFn4.css +0 -1
  184. package/packages/gui/src/components/agents/agent-chat.jsx +0 -322
  185. package/packages/gui/src/views/preview.jsx +0 -6
  186. package/packages/gui/src/views/subscription-panel.jsx +0 -327
  187. package/test.py +0 -571
@@ -0,0 +1,461 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+
3
+ import { api } from '../../lib/api';
4
+
5
+ export const createMarketplaceSlice = (set, get) => ({
6
+ // ── Marketplace Auth ───────────────────────────────────────
7
+ marketplaceUser: null,
8
+ marketplaceAuthenticated: false,
9
+ edition: 'community',
10
+ subscription: {
11
+ plan: 'community',
12
+ status: 'none',
13
+ active: false,
14
+ features: [],
15
+ seats: 1,
16
+ periodEnd: null,
17
+ cancelAtPeriodEnd: false,
18
+ },
19
+
20
+ // ── Training Data ──────────────────────────────────────────
21
+ trainingOptIn: false,
22
+ trainingStats: null,
23
+ dataSharingDismissed: false,
24
+ dataSharingModalOpen: false,
25
+
26
+ // ── Project Directory ───────────────────────────────────────
27
+ projectDir: null,
28
+ recentProjects: [],
29
+ showProjectPicker: false,
30
+
31
+ // ── Tunnels ────────────────────────────────────────────────
32
+ savedTunnels: [],
33
+ tunnelConnectStep: null,
34
+
35
+ // ── GitHub Repo Import ────────────────────────────────────
36
+ importedRepos: [],
37
+ importInProgress: false,
38
+
39
+ // ── Gateways ──────────────────────────────────────────────
40
+ gateways: [],
41
+
42
+ // ── Onboarding ────────────────────────────────────────────
43
+ onboardingComplete: localStorage.getItem('groove:onboardingComplete') === 'true',
44
+
45
+ // ── Journalist ────────────────────────────────────────────
46
+ journalistStatus: null,
47
+
48
+ // ── Marketplace Auth Actions ──────────────────────────────
49
+
50
+ async checkMarketplaceAuth() {
51
+ try {
52
+ const data = await api.get('/auth/status');
53
+ set({
54
+ marketplaceAuthenticated: data.authenticated || false,
55
+ marketplaceUser: data.user || null,
56
+ });
57
+ try {
58
+ const edition = await api.get('/edition');
59
+ set({
60
+ edition: edition.edition || 'community',
61
+ subscription: {
62
+ plan: edition.plan || 'community',
63
+ status: edition.status || (edition.subscriptionActive ? 'active' : 'none'),
64
+ active: edition.subscriptionActive === true,
65
+ features: edition.features || [],
66
+ seats: edition.seats || 1,
67
+ periodEnd: edition.periodEnd || null,
68
+ cancelAtPeriodEnd: edition.cancelAtPeriodEnd || false,
69
+ },
70
+ });
71
+ } catch { /* edition endpoint may not exist */ }
72
+ } catch {
73
+ set({ marketplaceAuthenticated: false, marketplaceUser: null });
74
+ }
75
+ },
76
+
77
+ async marketplaceLogin() {
78
+ try {
79
+ const data = await api.get('/auth/login-url');
80
+ if (data.url) window.open(data.url, '_blank');
81
+ const poll = setInterval(async () => {
82
+ try {
83
+ const status = await api.get('/auth/status');
84
+ if (status.authenticated) {
85
+ clearInterval(poll);
86
+ set({ marketplaceAuthenticated: true, marketplaceUser: status.user });
87
+ get().addToast('success', `Signed in as ${status.user?.displayName || status.user?.id || 'user'}`);
88
+ try {
89
+ const edition = await api.get('/edition');
90
+ set({
91
+ edition: edition.edition || 'community',
92
+ subscription: {
93
+ plan: edition.plan || 'community',
94
+ status: edition.status || (edition.subscriptionActive ? 'active' : 'none'),
95
+ active: edition.subscriptionActive === true,
96
+ features: edition.features || [],
97
+ seats: edition.seats || 1,
98
+ periodEnd: edition.periodEnd || null,
99
+ cancelAtPeriodEnd: edition.cancelAtPeriodEnd || false,
100
+ },
101
+ });
102
+ } catch { /* edition endpoint may not exist */ }
103
+ setTimeout(async () => {
104
+ try {
105
+ const e = await api.get('/edition');
106
+ set({
107
+ edition: e.edition || 'community',
108
+ subscription: {
109
+ plan: e.plan || 'community',
110
+ status: e.status || (e.subscriptionActive ? 'active' : 'none'),
111
+ active: e.subscriptionActive === true,
112
+ features: e.features || [],
113
+ seats: e.seats || 1,
114
+ periodEnd: e.periodEnd || null,
115
+ cancelAtPeriodEnd: e.cancelAtPeriodEnd || false,
116
+ },
117
+ });
118
+ } catch { /* delayed re-fetch may fail */ }
119
+ }, 2000);
120
+ }
121
+ } catch { /* keep polling */ }
122
+ }, 2000);
123
+ setTimeout(() => clearInterval(poll), 300000);
124
+ } catch (err) {
125
+ get().addToast('error', 'Login failed', err.message);
126
+ }
127
+ },
128
+
129
+ async marketplaceLogout() {
130
+ try {
131
+ await api.post('/auth/logout');
132
+ set({ marketplaceAuthenticated: false, marketplaceUser: null });
133
+ get().addToast('info', 'Signed out of marketplace');
134
+ } catch (err) {
135
+ get().addToast('error', 'Logout failed', err.message);
136
+ }
137
+ },
138
+
139
+ async marketplaceCheckout(skillId) {
140
+ try {
141
+ const data = await api.post('/auth/checkout', { skillId });
142
+ if (data.url) window.open(data.url, '_blank');
143
+ return data;
144
+ } catch (err) {
145
+ get().addToast('error', 'Checkout failed', err.message);
146
+ throw err;
147
+ }
148
+ },
149
+
150
+ // ── Subscription ────────────────────────────────────────────
151
+
152
+ async fetchSubscriptionPlans() {
153
+ return api.get('/subscription/plans');
154
+ },
155
+
156
+ async startCheckout(priceId) {
157
+ try {
158
+ const data = await api.post('/subscription/checkout', { priceId });
159
+ if (data.url) {
160
+ if (window.groove?.openExternal) {
161
+ window.groove.openExternal(data.url);
162
+ } else {
163
+ window.open(data.url, '_blank');
164
+ }
165
+ }
166
+ return data;
167
+ } catch (err) {
168
+ if (err.status === 401 || err.message?.includes('Not authenticated')) {
169
+ get().addToast('info', 'Please sign in to subscribe');
170
+ get().marketplaceLogin();
171
+ } else if (err.status === 409) {
172
+ get().addToast('info', 'Already subscribed', 'Use Manage Subscription to switch plans');
173
+ } else {
174
+ get().addToast('error', 'Checkout failed', err.message);
175
+ }
176
+ throw err;
177
+ }
178
+ },
179
+
180
+ async openPortal() {
181
+ try {
182
+ const data = await api.post('/subscription/portal');
183
+ if (data.url) {
184
+ if (window.groove?.openExternal) {
185
+ window.groove.openExternal(data.url);
186
+ } else {
187
+ window.open(data.url, '_blank');
188
+ }
189
+ }
190
+ return data;
191
+ } catch (err) {
192
+ get().addToast('error', 'Portal failed', err.message);
193
+ throw err;
194
+ }
195
+ },
196
+
197
+ async updateSeats(seats) {
198
+ try {
199
+ const data = await api.patch('/subscription', { seats });
200
+ set({ subscription: { ...get().subscription, ...data } });
201
+ get().addToast('success', `Updated to ${seats} seat${seats !== 1 ? 's' : ''}`);
202
+ return data;
203
+ } catch (err) {
204
+ get().addToast('error', 'Seat update failed', err.message);
205
+ throw err;
206
+ }
207
+ },
208
+
209
+ // ── Training Data ─────────────────────────────────────────
210
+
211
+ async setTrainingOptIn(enabled) {
212
+ try {
213
+ await api.post('/training/opt-in', { enabled });
214
+ set({ trainingOptIn: enabled, dataSharingModalOpen: false });
215
+ if (!enabled) set({ trainingStats: null });
216
+ } catch (e) {
217
+ get().addToast('error', 'Failed to update training preference', e.body?.detail || e.message);
218
+ }
219
+ },
220
+
221
+ async fetchTrainingStatus() {
222
+ try {
223
+ const data = await api.get('/training/status');
224
+ set({ trainingOptIn: data.optedIn, trainingStats: data });
225
+ } catch { /* endpoint may not exist on older daemons */ }
226
+ },
227
+
228
+ async dismissDataSharingModal(permanent) {
229
+ if (permanent) {
230
+ try { await api.patch('/config', { dataSharingDismissed: true }); } catch {}
231
+ set({ dataSharingDismissed: true, dataSharingModalOpen: false });
232
+ } else {
233
+ set({ dataSharingModalOpen: false });
234
+ }
235
+ },
236
+
237
+ // ── Project Directory ────────────────────────────────────
238
+
239
+ async fetchProjectDir() {
240
+ try {
241
+ const data = await api.get('/project-dir');
242
+ const isHome = /^\/home\/[^/]+$/.test(data.projectDir) || data.projectDir === '/root';
243
+ set({
244
+ projectDir: data.projectDir,
245
+ recentProjects: data.recentProjects || [],
246
+ showProjectPicker: isHome || (data.recentProjects || []).length === 0,
247
+ editorTreeCache: {},
248
+ });
249
+ } catch {}
250
+ },
251
+
252
+ async setProjectDir(path) {
253
+ const data = await api.post('/project-dir', { path });
254
+ try { await api.post('/files/root', { root: data.projectDir }); } catch {}
255
+ set({
256
+ projectDir: data.projectDir,
257
+ recentProjects: data.recentProjects || [],
258
+ showProjectPicker: false,
259
+ editorTreeCache: {},
260
+ });
261
+ get().fetchTreeDir('');
262
+ },
263
+
264
+ async removeRecentProject(path) {
265
+ try {
266
+ await api.delete('/projects/recent', { path });
267
+ } catch {}
268
+ get().fetchProjectDir();
269
+ },
270
+
271
+ toggleProjectPicker() {
272
+ set((s) => ({ showProjectPicker: !s.showProjectPicker }));
273
+ },
274
+
275
+ // ── Tunnels ──────────────────────────────────────────────
276
+
277
+ async fetchTunnels() {
278
+ try {
279
+ const tunnels = await api.get('/tunnels');
280
+ set({ savedTunnels: Array.isArray(tunnels) ? tunnels : [] });
281
+ } catch {}
282
+ },
283
+
284
+ async saveTunnel(config) {
285
+ const result = await api.post('/tunnels', config);
286
+ get().fetchTunnels();
287
+ return result;
288
+ },
289
+
290
+ async updateTunnel(id, config) {
291
+ const result = await api.patch(`/tunnels/${encodeURIComponent(id)}`, config);
292
+ get().fetchTunnels();
293
+ return result;
294
+ },
295
+
296
+ async deleteTunnel(id) {
297
+ await api.delete(`/tunnels/${encodeURIComponent(id)}`);
298
+ get().fetchTunnels();
299
+ },
300
+
301
+ async testTunnel(id) {
302
+ return api.post(`/tunnels/${encodeURIComponent(id)}/test`);
303
+ },
304
+
305
+ async connectTunnel(id) {
306
+ try {
307
+ const result = await api.post(`/tunnels/${encodeURIComponent(id)}/connect`);
308
+ get().fetchTunnels();
309
+ if (result.localPort && result.name) {
310
+ if (window.groove?.remote?.openWindow) {
311
+ window.groove.remote.openWindow(result.localPort, result.name);
312
+ } else {
313
+ window.open(`http://localhost:${result.localPort}?instance=${encodeURIComponent(result.name)}`, '_blank');
314
+ }
315
+ }
316
+ return result;
317
+ } finally {
318
+ set({ tunnelConnectStep: null });
319
+ }
320
+ },
321
+
322
+ async upgradeTunnel(id) {
323
+ return api.post(`/tunnels/${encodeURIComponent(id)}/upgrade`);
324
+ },
325
+
326
+ async disconnectTunnel(id) {
327
+ const tunnel = get().savedTunnels.find(t => t.id === id);
328
+ await api.post(`/tunnels/${encodeURIComponent(id)}/disconnect`);
329
+ get().fetchTunnels();
330
+ if (tunnel?.localPort && window.groove?.remote?.closeByPort) {
331
+ window.groove.remote.closeByPort(tunnel.localPort);
332
+ }
333
+ },
334
+
335
+ async installTunnel(id) {
336
+ return api.post(`/tunnels/${encodeURIComponent(id)}/install`);
337
+ },
338
+
339
+ async startTunnel(id) {
340
+ return api.post(`/tunnels/${encodeURIComponent(id)}/start`);
341
+ },
342
+
343
+ // ── GitHub Repo Import ────────────────────────────────────
344
+
345
+ async fetchImportedRepos() {
346
+ try {
347
+ const repos = await api.get('/repos/imported');
348
+ set({ importedRepos: repos });
349
+ } catch { /* ignore */ }
350
+ },
351
+
352
+ async previewRepo(repoUrl) {
353
+ return api.post('/repos/preview', { repoUrl });
354
+ },
355
+
356
+ async importRepo(repoUrl, targetPath, createTeam, teamName) {
357
+ set({ importInProgress: true });
358
+ try {
359
+ const result = await api.post('/repos/import', { repoUrl, targetPath, createTeam, teamName });
360
+ get().fetchImportedRepos();
361
+ return result;
362
+ } finally {
363
+ set({ importInProgress: false });
364
+ }
365
+ },
366
+
367
+ async softRemoveRepo(importId) {
368
+ await api.delete(`/repos/${encodeURIComponent(importId)}/remove`);
369
+ get().fetchImportedRepos();
370
+ },
371
+
372
+ async hardNukeRepo(importId, deleteFiles = true) {
373
+ await api.delete(`/repos/${encodeURIComponent(importId)}/nuke?deleteFiles=${deleteFiles}`);
374
+ get().fetchImportedRepos();
375
+ },
376
+
377
+ // ── Journalist ────────────────────────────────────────────
378
+
379
+ async fetchJournalist() {
380
+ try {
381
+ const data = await api.get('/journalist');
382
+ set({ journalistStatus: data });
383
+ return data;
384
+ } catch { return null; }
385
+ },
386
+
387
+ async triggerJournalistCycle() {
388
+ try {
389
+ const data = await api.post('/journalist/cycle');
390
+ get().addToast('success', 'Synthesis cycle triggered');
391
+ set({ journalistStatus: data });
392
+ return data;
393
+ } catch (err) {
394
+ get().addToast('error', 'Synthesis failed', err.message);
395
+ throw err;
396
+ }
397
+ },
398
+
399
+ // ── Onboarding ────────────────────────────────────────────
400
+
401
+ async fetchOnboardingStatus() {
402
+ try {
403
+ const data = await api.get('/onboarding/status');
404
+ if (data?.complete) {
405
+ set({ onboardingComplete: true });
406
+ localStorage.setItem('groove:onboardingComplete', 'true');
407
+ }
408
+ return data;
409
+ } catch {
410
+ return null;
411
+ }
412
+ },
413
+
414
+ dismissOnboarding() {
415
+ set({ onboardingComplete: true });
416
+ localStorage.setItem('groove:onboardingComplete', 'true');
417
+ api.post('/onboarding/dismiss').catch(() => {});
418
+ },
419
+
420
+ // ── Integration Agent Install ────────────────────────────
421
+
422
+ async installViaExistingAgent(integration, agentId) {
423
+ const message = buildIntegrationPrompt(integration);
424
+ await get().instructAgent(agentId, message);
425
+ get().setActiveView('agents');
426
+ get().selectAgent(agentId);
427
+ },
428
+
429
+ async spawnIntegrationTeam(integration) {
430
+ const team = await get().createTeam(integration.name);
431
+ const prompt = buildIntegrationPrompt(integration);
432
+ const agent = await get().spawnAgent({ role: 'planner', prompt, teamId: team.id });
433
+ get().setActiveView('agents');
434
+ get().selectAgent(agent.id);
435
+ return agent;
436
+ },
437
+ });
438
+
439
+ function buildIntegrationPrompt(integration) {
440
+ const lines = [
441
+ `Set up the "${integration.name}" integration for this project.`,
442
+ '',
443
+ ];
444
+ if (integration.description) lines.push(`**Description:** ${integration.description}`);
445
+ if (integration.npmPackage) lines.push(`**npm package:** ${integration.npmPackage}`);
446
+ if (integration.authType) lines.push(`**Auth type:** ${integration.authType}`);
447
+ if (integration.envKeys?.length) {
448
+ lines.push('', '**Environment keys required:**');
449
+ for (const k of integration.envKeys) {
450
+ lines.push(`- \`${k.key}\` — ${k.label}${k.required ? ' (required)' : ''}`);
451
+ }
452
+ }
453
+ if (integration.setupSteps?.length) {
454
+ lines.push('', '**Setup steps:**');
455
+ integration.setupSteps.forEach((step, i) => lines.push(`${i + 1}. ${step}`));
456
+ }
457
+ if (integration.setupUrl) lines.push(``, `**Setup URL:** ${integration.setupUrl}`);
458
+ if (integration.agentInstructions) lines.push('', `**Agent instructions:** ${integration.agentInstructions}`);
459
+ lines.push('', 'Follow the setup steps, configure environment keys, and verify the integration works.');
460
+ return lines.join('\n');
461
+ }