groove-dev 0.27.143 → 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 (186) hide show
  1. package/CLAUDE.md +0 -7
  2. package/node_modules/@groove-dev/cli/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/package.json +1 -1
  4. package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
  5. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
  6. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  7. package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
  8. package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
  9. package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
  10. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  11. package/node_modules/@groove-dev/daemon/src/process.js +2 -2
  12. package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
  13. package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
  14. package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
  15. package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
  16. package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
  17. package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
  18. package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
  19. package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
  21. package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
  22. package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
  23. package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
  24. package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
  25. package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
  26. package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
  27. package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
  28. package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
  29. package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
  30. package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
  31. package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  32. package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  33. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  34. package/node_modules/@groove-dev/gui/package.json +1 -1
  35. package/node_modules/@groove-dev/gui/src/{app.jsx → App.jsx} +0 -2
  36. package/node_modules/@groove-dev/gui/src/app.css +35 -0
  37. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
  38. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
  39. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
  40. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
  41. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
  42. package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
  43. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
  44. package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
  45. package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
  46. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
  47. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
  48. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
  49. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  50. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
  51. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
  52. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  53. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  54. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
  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 +409 -507
  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/packages/gui/src/{app.jsx → 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/lab/lab-assistant.jsx +316 -82
  141. package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
  142. package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
  143. package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
  144. package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
  145. package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
  146. package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
  147. package/packages/gui/src/components/network/network-health.jsx +2 -2
  148. package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
  149. package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
  150. package/packages/gui/src/components/ui/sheet.jsx +5 -2
  151. package/packages/gui/src/lib/cron.js +64 -0
  152. package/packages/gui/src/lib/status.js +24 -24
  153. package/packages/gui/src/lib/theme-hex.js +1 -0
  154. package/packages/gui/src/stores/groove.js +34 -3144
  155. package/packages/gui/src/stores/helpers.js +10 -0
  156. package/packages/gui/src/stores/slices/agents-slice.js +452 -0
  157. package/packages/gui/src/stores/slices/automations-slice.js +96 -0
  158. package/packages/gui/src/stores/slices/chat-slice.js +227 -0
  159. package/packages/gui/src/stores/slices/editor-slice.js +285 -0
  160. package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
  161. package/packages/gui/src/stores/slices/network-slice.js +361 -0
  162. package/packages/gui/src/stores/slices/preview-slice.js +109 -0
  163. package/packages/gui/src/stores/slices/providers-slice.js +897 -0
  164. package/packages/gui/src/stores/slices/teams-slice.js +413 -0
  165. package/packages/gui/src/stores/slices/ui-slice.js +98 -0
  166. package/packages/gui/src/views/agents.jsx +5 -5
  167. package/packages/gui/src/views/dashboard.jsx +12 -13
  168. package/packages/gui/src/views/marketplace.jsx +191 -3
  169. package/packages/gui/src/views/model-lab.jsx +17 -6
  170. package/packages/gui/src/views/models.jsx +409 -507
  171. package/packages/gui/src/views/network.jsx +3 -3
  172. package/packages/gui/src/views/settings.jsx +81 -94
  173. package/packages/gui/src/views/teams.jsx +40 -483
  174. package/SECURITY_SWEEP.md +0 -228
  175. package/TRAINING_DATA_v4.md +0 -6
  176. package/node_modules/@groove-dev/gui/dist/assets/index-CCVvAoQn.css +0 -1
  177. package/node_modules/@groove-dev/gui/dist/assets/index-DGIv_TRm.js +0 -984
  178. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -379
  179. package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
  180. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
  181. package/packages/gui/dist/assets/index-CCVvAoQn.css +0 -1
  182. package/packages/gui/dist/assets/index-DGIv_TRm.js +0 -984
  183. package/packages/gui/src/components/agents/agent-chat.jsx +0 -379
  184. package/packages/gui/src/views/preview.jsx +0 -6
  185. package/packages/gui/src/views/subscription-panel.jsx +0 -327
  186. 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
+ }