groove-dev 0.26.13 → 0.26.15
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.
- package/GROOVE_AI_PITCH_DECK_OUTLINE.docx +0 -0
- package/node_modules/@groove-dev/daemon/src/api.js +9 -0
- package/node_modules/@groove-dev/daemon/src/process.js +3 -1
- package/node_modules/@groove-dev/daemon/src/skills.js +46 -1
- package/node_modules/@groove-dev/daemon/src/terminal-pty.js +2 -2
- package/node_modules/@groove-dev/gui/dist/assets/index-CBlL5EFb.js +638 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-C4cVCdfw.css → index-E07lphaH.css} +1 -1
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/src/views/editor.jsx +58 -2
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +92 -7
- package/package.json +1 -1
- package/packages/daemon/src/api.js +9 -0
- package/packages/daemon/src/process.js +3 -1
- package/packages/daemon/src/skills.js +46 -1
- package/packages/daemon/src/terminal-pty.js +2 -2
- package/packages/gui/dist/assets/index-CBlL5EFb.js +638 -0
- package/packages/gui/dist/assets/{index-C4cVCdfw.css → index-E07lphaH.css} +1 -1
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/src/views/editor.jsx +58 -2
- package/packages/gui/src/views/marketplace.jsx +92 -7
- package/pitch_deck/index.html +718 -0
- package/pitch_deck/slides.json +197 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BluxaN32.js +0 -633
- package/packages/gui/dist/assets/index-BluxaN32.js +0 -633
|
@@ -19,7 +19,7 @@ import { fmtNum, timeAgo } from '../lib/format';
|
|
|
19
19
|
import { useGrooveStore } from '../stores/groove';
|
|
20
20
|
import {
|
|
21
21
|
ChevronLeft, ChevronDown, Sparkles, Plug, LogIn, LogOut,
|
|
22
|
-
User, Upload, Package, Download, ShoppingBag,
|
|
22
|
+
User, Upload, Package, Download, ShoppingBag, RefreshCw, Trash2,
|
|
23
23
|
} from 'lucide-react';
|
|
24
24
|
|
|
25
25
|
// ── Skill Detail ─────────────────────────────────────────
|
|
@@ -28,6 +28,8 @@ function SkillDetail({ skill, onBack }) {
|
|
|
28
28
|
const [content, setContent] = useState('');
|
|
29
29
|
const [requiresPurchase, setRequiresPurchase] = useState(false);
|
|
30
30
|
const [installing, setInstalling] = useState(false);
|
|
31
|
+
const [updating, setUpdating] = useState(false);
|
|
32
|
+
const [uninstalling, setUninstalling] = useState(false);
|
|
31
33
|
const [installed, setInstalled] = useState(skill.installed);
|
|
32
34
|
const [loadingContent, setLoadingContent] = useState(true);
|
|
33
35
|
|
|
@@ -52,6 +54,28 @@ function SkillDetail({ skill, onBack }) {
|
|
|
52
54
|
setInstalling(false);
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
async function handleUpdate() {
|
|
58
|
+
setUpdating(true);
|
|
59
|
+
try {
|
|
60
|
+
await api.post(`/skills/${skill.id}/update`);
|
|
61
|
+
toast.success(`${skill.name} updated to latest`);
|
|
62
|
+
// Refresh content preview
|
|
63
|
+
const d = await api.get(`/skills/${skill.id}/content`);
|
|
64
|
+
if (d.content) setContent(d.content);
|
|
65
|
+
} catch (err) { toast.error('Update failed', err.message); }
|
|
66
|
+
setUpdating(false);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function handleUninstall() {
|
|
70
|
+
setUninstalling(true);
|
|
71
|
+
try {
|
|
72
|
+
await api.delete(`/skills/${skill.id}`);
|
|
73
|
+
setInstalled(false);
|
|
74
|
+
toast.success(`${skill.name} uninstalled`);
|
|
75
|
+
} catch (err) { toast.error('Uninstall failed', err.message); }
|
|
76
|
+
setUninstalling(false);
|
|
77
|
+
}
|
|
78
|
+
|
|
55
79
|
async function handleBuy() {
|
|
56
80
|
const { marketplaceAuthenticated, marketplaceLogin, marketplaceCheckout } = useGrooveStore.getState();
|
|
57
81
|
if (!marketplaceAuthenticated) {
|
|
@@ -133,13 +157,32 @@ function SkillDetail({ skill, onBack }) {
|
|
|
133
157
|
>
|
|
134
158
|
Buy ${(skill.price || 0).toFixed(2)}
|
|
135
159
|
</button>
|
|
160
|
+
) : installed ? (
|
|
161
|
+
<div className="mt-3 flex flex-col gap-1.5">
|
|
162
|
+
<button
|
|
163
|
+
onClick={handleUpdate}
|
|
164
|
+
disabled={updating}
|
|
165
|
+
className="w-full py-2 px-3 text-xs font-sans font-semibold rounded cursor-pointer transition-all hover:opacity-85 disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-center gap-2 border bg-accent/15 text-accent border-accent/20 hover:bg-accent/25"
|
|
166
|
+
>
|
|
167
|
+
<RefreshCw size={12} className={updating ? 'animate-spin' : ''} />
|
|
168
|
+
{updating ? 'Updating...' : 'Pull Latest'}
|
|
169
|
+
</button>
|
|
170
|
+
<button
|
|
171
|
+
onClick={handleUninstall}
|
|
172
|
+
disabled={uninstalling}
|
|
173
|
+
className="w-full py-2 px-3 text-xs font-sans font-semibold rounded cursor-pointer transition-all hover:opacity-85 disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-center gap-2 border bg-error/10 text-error border-error/20 hover:bg-error/15"
|
|
174
|
+
>
|
|
175
|
+
<Trash2 size={12} />
|
|
176
|
+
{uninstalling ? 'Removing...' : 'Uninstall'}
|
|
177
|
+
</button>
|
|
178
|
+
</div>
|
|
136
179
|
) : (
|
|
137
180
|
<button
|
|
138
181
|
onClick={handleInstall}
|
|
139
|
-
disabled={installing
|
|
140
|
-
className=
|
|
182
|
+
disabled={installing}
|
|
183
|
+
className="w-full mt-3 py-2 px-3 text-xs font-sans font-semibold rounded cursor-pointer transition-all hover:opacity-85 disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-center gap-2 border bg-accent/15 text-accent border-accent/20 hover:bg-accent/25"
|
|
141
184
|
>
|
|
142
|
-
{installing ? 'Installing...' :
|
|
185
|
+
{installing ? 'Installing...' : 'Install'}
|
|
143
186
|
</button>
|
|
144
187
|
)}
|
|
145
188
|
|
|
@@ -278,9 +321,15 @@ function MyLibrary() {
|
|
|
278
321
|
const [purchases, setPurchases] = useState([]);
|
|
279
322
|
const [installed, setInstalled] = useState([]);
|
|
280
323
|
const [loading, setLoading] = useState(true);
|
|
324
|
+
const [busyId, setBusyId] = useState(null);
|
|
281
325
|
const toast = useToast();
|
|
282
326
|
const fileRef = useRef(null);
|
|
283
327
|
|
|
328
|
+
const refreshInstalled = async () => {
|
|
329
|
+
const data = await api.get('/skills/installed');
|
|
330
|
+
setInstalled(Array.isArray(data) ? data : data.skills || []);
|
|
331
|
+
};
|
|
332
|
+
|
|
284
333
|
useEffect(() => {
|
|
285
334
|
setLoading(true);
|
|
286
335
|
Promise.all([
|
|
@@ -292,6 +341,26 @@ function MyLibrary() {
|
|
|
292
341
|
}).finally(() => setLoading(false));
|
|
293
342
|
}, [authenticated]);
|
|
294
343
|
|
|
344
|
+
async function handleUpdate(s) {
|
|
345
|
+
setBusyId(s.id);
|
|
346
|
+
try {
|
|
347
|
+
await api.post(`/skills/${s.id}/update`);
|
|
348
|
+
toast.success(`${s.name || s.id} updated`);
|
|
349
|
+
await refreshInstalled();
|
|
350
|
+
} catch (err) { toast.error('Update failed', err.message); }
|
|
351
|
+
setBusyId(null);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function handleUninstall(s) {
|
|
355
|
+
setBusyId(s.id);
|
|
356
|
+
try {
|
|
357
|
+
await api.delete(`/skills/${s.id}`);
|
|
358
|
+
toast.success(`${s.name || s.id} uninstalled`);
|
|
359
|
+
await refreshInstalled();
|
|
360
|
+
} catch (err) { toast.error('Uninstall failed', err.message); }
|
|
361
|
+
setBusyId(null);
|
|
362
|
+
}
|
|
363
|
+
|
|
295
364
|
async function handleImport(e) {
|
|
296
365
|
const file = e.target.files?.[0];
|
|
297
366
|
if (!file) return;
|
|
@@ -300,9 +369,7 @@ function MyLibrary() {
|
|
|
300
369
|
const name = file.name.replace(/\.md$/i, '');
|
|
301
370
|
await api.post('/skills/import', { name, content });
|
|
302
371
|
toast.success(`Imported "${name}"`);
|
|
303
|
-
|
|
304
|
-
const data = await api.get('/skills/installed');
|
|
305
|
-
setInstalled(Array.isArray(data) ? data : data.skills || []);
|
|
372
|
+
await refreshInstalled();
|
|
306
373
|
} catch (err) {
|
|
307
374
|
toast.error('Import failed', err.message);
|
|
308
375
|
}
|
|
@@ -398,6 +465,24 @@ function MyLibrary() {
|
|
|
398
465
|
<div className="text-xs font-semibold text-text-0 font-sans truncate">{s.name || s.id}</div>
|
|
399
466
|
<div className="text-2xs text-text-3 font-sans truncate">{s.description || s.category || 'local skill'}</div>
|
|
400
467
|
</div>
|
|
468
|
+
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
469
|
+
<button
|
|
470
|
+
onClick={() => handleUpdate(s)}
|
|
471
|
+
disabled={busyId === s.id}
|
|
472
|
+
title="Pull latest version"
|
|
473
|
+
className="p-1.5 rounded text-text-3 hover:text-accent hover:bg-accent/10 cursor-pointer transition-colors disabled:opacity-50"
|
|
474
|
+
>
|
|
475
|
+
<RefreshCw size={12} className={busyId === s.id ? 'animate-spin' : ''} />
|
|
476
|
+
</button>
|
|
477
|
+
<button
|
|
478
|
+
onClick={() => handleUninstall(s)}
|
|
479
|
+
disabled={busyId === s.id}
|
|
480
|
+
title="Uninstall"
|
|
481
|
+
className="p-1.5 rounded text-text-3 hover:text-error hover:bg-error/10 cursor-pointer transition-colors disabled:opacity-50"
|
|
482
|
+
>
|
|
483
|
+
<Trash2 size={12} />
|
|
484
|
+
</button>
|
|
485
|
+
</div>
|
|
401
486
|
<Badge variant="accent" className="text-2xs flex-shrink-0">Installed</Badge>
|
|
402
487
|
</div>
|
|
403
488
|
))}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.15",
|
|
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)",
|
|
@@ -891,6 +891,15 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
891
891
|
}
|
|
892
892
|
});
|
|
893
893
|
|
|
894
|
+
app.post('/api/skills/:id/update', async (req, res) => {
|
|
895
|
+
try {
|
|
896
|
+
const result = await daemon.skills.update(req.params.id);
|
|
897
|
+
res.json(result);
|
|
898
|
+
} catch (err) {
|
|
899
|
+
res.status(400).json({ error: err.message });
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
|
|
894
903
|
app.post('/api/skills/:id/rate', async (req, res) => {
|
|
895
904
|
try {
|
|
896
905
|
const rating = parseInt(req.body?.rating, 10);
|
|
@@ -296,8 +296,10 @@ IMPORTANT: No task has been assigned yet. You MUST wait for the user to tell you
|
|
|
296
296
|
|
|
297
297
|
// Apply PM review instructions for Auto permission mode
|
|
298
298
|
// Agents call the PM endpoint before risky operations for AI review
|
|
299
|
+
// Skip for sandboxed providers (Codex) — localhost is unreachable from their sandbox
|
|
299
300
|
const permission = config.permission || 'full';
|
|
300
|
-
|
|
301
|
+
const sandboxedProviders = ['codex'];
|
|
302
|
+
if ((permission === 'auto' || permission === 'supervised') && !sandboxedProviders.includes(providerName)) {
|
|
301
303
|
const port = this.daemon.port || 31415;
|
|
302
304
|
const pmPrompt = `## PM Review (Auto Mode)
|
|
303
305
|
|
|
@@ -64,7 +64,7 @@ export class SkillStore {
|
|
|
64
64
|
});
|
|
65
65
|
if (res.ok) {
|
|
66
66
|
user = await res.json();
|
|
67
|
-
console.log(
|
|
67
|
+
console.log(`[Groove:Auth] Authenticated: ${user.username || user.id}`);
|
|
68
68
|
} else {
|
|
69
69
|
return null; // Invalid token
|
|
70
70
|
}
|
|
@@ -329,6 +329,51 @@ export class SkillStore {
|
|
|
329
329
|
return { id: skillId, installed: false };
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
/**
|
|
333
|
+
* Update an installed skill — re-downloads latest content from the API.
|
|
334
|
+
*/
|
|
335
|
+
async update(skillId) {
|
|
336
|
+
if (!this._isInstalled(skillId)) throw new Error(`Skill not installed: ${skillId}`);
|
|
337
|
+
|
|
338
|
+
let entry = this.registry.find((s) => s.id === skillId);
|
|
339
|
+
if (!entry) {
|
|
340
|
+
await this._refreshRegistry();
|
|
341
|
+
entry = this.registry.find((s) => s.id === skillId);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
let content = null;
|
|
345
|
+
|
|
346
|
+
// Try live API content endpoint
|
|
347
|
+
try {
|
|
348
|
+
const res = await this._fetch(`${SKILLS_API}/skills/${skillId}/content`, { timeout: 10000 });
|
|
349
|
+
if (res.ok) {
|
|
350
|
+
const data = await res.json();
|
|
351
|
+
content = data.content;
|
|
352
|
+
}
|
|
353
|
+
} catch { /* fall through */ }
|
|
354
|
+
|
|
355
|
+
// Fall back to contentUrl
|
|
356
|
+
if (!content && entry?.contentUrl) {
|
|
357
|
+
try {
|
|
358
|
+
const res = await this._fetch(entry.contentUrl, { timeout: 10000 });
|
|
359
|
+
if (res.ok) content = await res.text();
|
|
360
|
+
} catch { /* fall through */ }
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!content) {
|
|
364
|
+
throw new Error('Could not download latest version. Check your internet connection.');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Overwrite local content
|
|
368
|
+
const skillDir = resolve(this.skillsDir, skillId);
|
|
369
|
+
mkdirSync(skillDir, { recursive: true });
|
|
370
|
+
writeFileSync(resolve(skillDir, 'SKILL.md'), content);
|
|
371
|
+
|
|
372
|
+
this.daemon.audit.log('skill.update', { id: skillId, name: entry?.name || skillId });
|
|
373
|
+
|
|
374
|
+
return { id: skillId, name: entry?.name || skillId, installed: true, updated: true };
|
|
375
|
+
}
|
|
376
|
+
|
|
332
377
|
/**
|
|
333
378
|
* Rate a skill. Proxies to the skills server.
|
|
334
379
|
*/
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
3
|
|
|
4
4
|
import { spawn, execFileSync } from 'child_process';
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
5
6
|
import { existsSync } from 'fs';
|
|
6
7
|
|
|
7
8
|
// Python helper that creates a real PTY and relays I/O through stdin/stdout pipes.
|
|
@@ -81,12 +82,11 @@ export class TerminalManager {
|
|
|
81
82
|
constructor(daemon) {
|
|
82
83
|
this.daemon = daemon;
|
|
83
84
|
this.sessions = new Map();
|
|
84
|
-
this.counter = 0;
|
|
85
85
|
this._python = this._findPython();
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
spawn(ws, options = {}) {
|
|
89
|
-
const id = `term-${
|
|
89
|
+
const id = `term-${randomUUID()}`;
|
|
90
90
|
const shell = this._detectShell();
|
|
91
91
|
const cwd = options.cwd || this.daemon.projectDir;
|
|
92
92
|
const cols = options.cols || 120;
|