groove-dev 0.27.75 → 0.27.77
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/CLAUDE.md +7 -0
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/preview.js +14 -0
- package/node_modules/@groove-dev/daemon/src/process.js +4 -1
- package/node_modules/@groove-dev/gui/dist/assets/{index-CAT9SCJi.js → index-BbmPDhuW.js} +319 -323
- package/node_modules/@groove-dev/gui/dist/assets/index-kbR5tOHu.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/app.css +12 -0
- package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +32 -27
- package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +26 -24
- package/node_modules/@groove-dev/gui/src/components/preview/preview-toolbar.jsx +34 -6
- package/node_modules/@groove-dev/gui/src/components/preview/preview-workspace.jsx +19 -4
- package/node_modules/@groove-dev/gui/src/components/preview/screenshot-overlay.jsx +91 -57
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/preview.js +14 -0
- package/packages/daemon/src/process.js +4 -1
- package/packages/gui/dist/assets/{index-CAT9SCJi.js → index-BbmPDhuW.js} +319 -323
- package/packages/gui/dist/assets/index-kbR5tOHu.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/app.css +12 -0
- package/packages/gui/src/components/chat/chat-input.jsx +32 -27
- package/packages/gui/src/components/chat/chat-messages.jsx +26 -24
- package/packages/gui/src/components/preview/preview-toolbar.jsx +34 -6
- package/packages/gui/src/components/preview/preview-workspace.jsx +19 -4
- package/packages/gui/src/components/preview/screenshot-overlay.jsx +91 -57
- package/welcome.png +0 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CVzz6zyb.css +0 -1
- package/packages/gui/dist/assets/index-CVzz6zyb.css +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
3
|
-
import { Send, Paperclip, MonitorX, Loader2 } from 'lucide-react';
|
|
3
|
+
import { Send, Paperclip, MonitorX, Loader2, MessageCircle, Camera, RefreshCw } from 'lucide-react';
|
|
4
4
|
import { useGrooveStore } from '../../stores/groove';
|
|
5
5
|
import { cn } from '../../lib/cn';
|
|
6
6
|
import { timeAgo } from '../../lib/format';
|
|
@@ -137,9 +137,24 @@ function PreviewChat() {
|
|
|
137
137
|
<div ref={scrollRef} className="flex-1 overflow-y-auto px-4 py-4 space-y-4">
|
|
138
138
|
{previewChat.length === 0 && (
|
|
139
139
|
<div className="flex items-center justify-center h-full">
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
<div className="max-w-xs w-full px-5 py-5 bg-surface-1 border border-border-subtle rounded-xl text-center">
|
|
141
|
+
<MessageCircle size={24} className="mx-auto text-accent mb-3" />
|
|
142
|
+
<h3 className="text-sm font-semibold text-text-0 font-sans mb-3">Preview is live!</h3>
|
|
143
|
+
<ul className="text-left space-y-2 text-2xs text-text-2 font-sans">
|
|
144
|
+
<li className="flex gap-2">
|
|
145
|
+
<Send size={11} className="text-text-3 mt-0.5 flex-shrink-0" />
|
|
146
|
+
<span>Type a message to request changes — your feedback goes to the team planner who routes it to the right agent</span>
|
|
147
|
+
</li>
|
|
148
|
+
<li className="flex gap-2">
|
|
149
|
+
<Camera size={11} className="text-text-3 mt-0.5 flex-shrink-0" />
|
|
150
|
+
<span>Use the camera icon to screenshot a specific area and annotate it</span>
|
|
151
|
+
</li>
|
|
152
|
+
<li className="flex gap-2">
|
|
153
|
+
<RefreshCw size={11} className="text-text-3 mt-0.5 flex-shrink-0" />
|
|
154
|
+
<span>Changes auto-refresh via hot module reload</span>
|
|
155
|
+
</li>
|
|
156
|
+
</ul>
|
|
157
|
+
</div>
|
|
143
158
|
</div>
|
|
144
159
|
)}
|
|
145
160
|
{previewChat.map((msg, i) => (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
3
|
-
import { Send, X } from 'lucide-react';
|
|
3
|
+
import { Send, X, Loader2 } from 'lucide-react';
|
|
4
4
|
import { useGrooveStore } from '../../stores/groove';
|
|
5
5
|
|
|
6
6
|
export function ScreenshotOverlay({ iframeRef }) {
|
|
@@ -11,8 +11,9 @@ export function ScreenshotOverlay({ iframeRef }) {
|
|
|
11
11
|
const [dragging, setDragging] = useState(false);
|
|
12
12
|
const [start, setStart] = useState(null);
|
|
13
13
|
const [end, setEnd] = useState(null);
|
|
14
|
-
const [captured, setCaptured] = useState(null); // { base64, rect }
|
|
14
|
+
const [captured, setCaptured] = useState(null); // { base64, rect, loading }
|
|
15
15
|
const [comment, setComment] = useState('');
|
|
16
|
+
const [flashRect, setFlashRect] = useState(null);
|
|
16
17
|
|
|
17
18
|
const handleMouseDown = useCallback((e) => {
|
|
18
19
|
if (captured) return;
|
|
@@ -48,9 +49,37 @@ export function ScreenshotOverlay({ iframeRef }) {
|
|
|
48
49
|
return;
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
setCaptured({ base64: null, rect: selRect, loading: true });
|
|
53
|
+
|
|
54
|
+
function finishCapture(base64) {
|
|
55
|
+
setCaptured({ base64, rect: selRect, loading: false });
|
|
56
|
+
setFlashRect(selRect);
|
|
57
|
+
setTimeout(() => setFlashRect(null), 600);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function drawPlaceholder() {
|
|
61
|
+
const canvas = document.createElement('canvas');
|
|
62
|
+
const dpr = window.devicePixelRatio || 1;
|
|
63
|
+
canvas.width = selRect.w * dpr;
|
|
64
|
+
canvas.height = selRect.h * dpr;
|
|
65
|
+
const ctx = canvas.getContext('2d');
|
|
66
|
+
ctx.scale(dpr, dpr);
|
|
67
|
+
ctx.fillStyle = '#1e2127';
|
|
68
|
+
ctx.fillRect(0, 0, selRect.w, selRect.h);
|
|
69
|
+
ctx.strokeStyle = '#3e4451';
|
|
70
|
+
ctx.lineWidth = 1;
|
|
71
|
+
ctx.strokeRect(4, 4, selRect.w - 8, selRect.h - 8);
|
|
72
|
+
ctx.fillStyle = '#6e7681';
|
|
73
|
+
ctx.font = '12px Inter, sans-serif';
|
|
74
|
+
ctx.textAlign = 'center';
|
|
75
|
+
ctx.textBaseline = 'middle';
|
|
76
|
+
ctx.fillText(`${Math.round(selRect.w)} × ${Math.round(selRect.h)}`, selRect.w / 2, selRect.h / 2);
|
|
77
|
+
return canvas.toDataURL('image/png');
|
|
78
|
+
}
|
|
79
|
+
|
|
51
80
|
try {
|
|
52
81
|
const iframe = iframeRef.current;
|
|
53
|
-
if (!iframe) {
|
|
82
|
+
if (!iframe) { finishCapture(drawPlaceholder()); return; }
|
|
54
83
|
|
|
55
84
|
const canvas = document.createElement('canvas');
|
|
56
85
|
const dpr = window.devicePixelRatio || 1;
|
|
@@ -59,53 +88,25 @@ export function ScreenshotOverlay({ iframeRef }) {
|
|
|
59
88
|
const ctx = canvas.getContext('2d');
|
|
60
89
|
ctx.scale(dpr, dpr);
|
|
61
90
|
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
</svg>`;
|
|
77
|
-
const blob = new Blob([svgStr], { type: 'image/svg+xml;charset=utf-8' });
|
|
78
|
-
const url = URL.createObjectURL(blob);
|
|
79
|
-
const img = new Image();
|
|
80
|
-
img.onload = () => {
|
|
81
|
-
ctx.drawImage(img, 0, 0, selRect.w, selRect.h);
|
|
82
|
-
URL.revokeObjectURL(url);
|
|
83
|
-
setCaptured({ base64: canvas.toDataURL('image/png'), rect: selRect });
|
|
84
|
-
};
|
|
85
|
-
img.onerror = () => {
|
|
86
|
-
URL.revokeObjectURL(url);
|
|
87
|
-
// Fallback: region placeholder
|
|
88
|
-
ctx.fillStyle = '#1e2127';
|
|
89
|
-
ctx.fillRect(0, 0, selRect.w, selRect.h);
|
|
90
|
-
ctx.fillStyle = '#6e7681';
|
|
91
|
-
ctx.font = '12px Inter, sans-serif';
|
|
92
|
-
ctx.textAlign = 'center';
|
|
93
|
-
ctx.fillText(`Region ${Math.round(selRect.w)}×${Math.round(selRect.h)}`, selRect.w / 2, selRect.h / 2);
|
|
94
|
-
setCaptured({ base64: canvas.toDataURL('image/png'), rect: selRect });
|
|
95
|
-
};
|
|
96
|
-
img.src = url;
|
|
97
|
-
} else {
|
|
98
|
-
ctx.fillStyle = '#1e2127';
|
|
99
|
-
ctx.fillRect(0, 0, selRect.w, selRect.h);
|
|
100
|
-
ctx.fillStyle = '#6e7681';
|
|
101
|
-
ctx.font = '12px Inter, sans-serif';
|
|
102
|
-
ctx.textAlign = 'center';
|
|
103
|
-
ctx.fillText(`Region ${Math.round(selRect.w)}×${Math.round(selRect.h)}`, selRect.w / 2, selRect.h / 2);
|
|
104
|
-
setCaptured({ base64: canvas.toDataURL('image/png'), rect: selRect });
|
|
91
|
+
const iframeRect = iframe.getBoundingClientRect();
|
|
92
|
+
const overlayRect = overlayRef.current.getBoundingClientRect();
|
|
93
|
+
const offsetX = selRect.x - (iframeRect.left - overlayRect.left);
|
|
94
|
+
const offsetY = selRect.y - (iframeRect.top - overlayRect.top);
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
ctx.drawImage(iframe, -offsetX * dpr, -offsetY * dpr, iframeRect.width * dpr, iframeRect.height * dpr, 0, 0, selRect.w, selRect.h);
|
|
98
|
+
const testPixel = ctx.getImageData(0, 0, 1, 1).data;
|
|
99
|
+
if (testPixel[0] === 0 && testPixel[1] === 0 && testPixel[2] === 0 && testPixel[3] === 0) {
|
|
100
|
+
throw new Error('blank');
|
|
101
|
+
}
|
|
102
|
+
finishCapture(canvas.toDataURL('image/png'));
|
|
103
|
+
} catch {
|
|
104
|
+
finishCapture(drawPlaceholder());
|
|
105
105
|
}
|
|
106
106
|
} catch {
|
|
107
107
|
setStart(null);
|
|
108
108
|
setEnd(null);
|
|
109
|
+
setCaptured(null);
|
|
109
110
|
}
|
|
110
111
|
}, [dragging, start, end, iframeRef]);
|
|
111
112
|
|
|
@@ -118,7 +119,7 @@ export function ScreenshotOverlay({ iframeRef }) {
|
|
|
118
119
|
}, [toggleScreenshotMode]);
|
|
119
120
|
|
|
120
121
|
function handleSubmit() {
|
|
121
|
-
if (!captured) return;
|
|
122
|
+
if (!captured || captured.loading) return;
|
|
122
123
|
iteratePreview(comment || 'See screenshot', captured.base64);
|
|
123
124
|
toggleScreenshotMode();
|
|
124
125
|
}
|
|
@@ -130,6 +131,19 @@ export function ScreenshotOverlay({ iframeRef }) {
|
|
|
130
131
|
height: Math.abs(end.y - start.y),
|
|
131
132
|
} : null;
|
|
132
133
|
|
|
134
|
+
const popoverPosition = useCallback((rect) => {
|
|
135
|
+
if (!rect || !overlayRef.current) return {};
|
|
136
|
+
const overlayH = overlayRef.current.clientHeight;
|
|
137
|
+
const overlayW = overlayRef.current.clientWidth;
|
|
138
|
+
const spaceBelow = overlayH - (rect.y + rect.h + 8);
|
|
139
|
+
const popoverH = 200;
|
|
140
|
+
const placeAbove = spaceBelow < popoverH && rect.y > popoverH;
|
|
141
|
+
return {
|
|
142
|
+
left: Math.max(8, Math.min(rect.x, overlayW - 300)),
|
|
143
|
+
top: placeAbove ? rect.y - popoverH - 8 : rect.y + rect.h + 8,
|
|
144
|
+
};
|
|
145
|
+
}, []);
|
|
146
|
+
|
|
133
147
|
return (
|
|
134
148
|
<div
|
|
135
149
|
ref={overlayRef}
|
|
@@ -150,21 +164,40 @@ export function ScreenshotOverlay({ iframeRef }) {
|
|
|
150
164
|
/>
|
|
151
165
|
)}
|
|
152
166
|
|
|
167
|
+
{/* Capture flash */}
|
|
168
|
+
{flashRect && (
|
|
169
|
+
<div
|
|
170
|
+
className="absolute pointer-events-none animate-capture-flash rounded"
|
|
171
|
+
style={{ left: flashRect.x, top: flashRect.y, width: flashRect.w, height: flashRect.h }}
|
|
172
|
+
/>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
{/* Captured selection outline */}
|
|
176
|
+
{captured && (
|
|
177
|
+
<div
|
|
178
|
+
className="absolute border-2 border-accent rounded pointer-events-none"
|
|
179
|
+
style={{ left: captured.rect.x, top: captured.rect.y, width: captured.rect.w, height: captured.rect.h }}
|
|
180
|
+
/>
|
|
181
|
+
)}
|
|
182
|
+
|
|
153
183
|
{/* Capture popover */}
|
|
154
184
|
{captured && (
|
|
155
185
|
<div
|
|
156
186
|
className="absolute z-40 w-72 bg-surface-2 border border-border rounded-lg shadow-2xl animate-chat-fade-in"
|
|
157
|
-
style={
|
|
158
|
-
left: Math.min(captured.rect.x, overlayRef.current?.clientWidth - 300 || 0),
|
|
159
|
-
top: captured.rect.y + captured.rect.h + 8,
|
|
160
|
-
}}
|
|
187
|
+
style={popoverPosition(captured.rect)}
|
|
161
188
|
>
|
|
162
189
|
<div className="p-3 border-b border-border-subtle">
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
190
|
+
{captured.loading ? (
|
|
191
|
+
<div className="w-full h-24 rounded border border-border-subtle bg-surface-0 flex items-center justify-center">
|
|
192
|
+
<Loader2 size={20} className="text-accent animate-spin" />
|
|
193
|
+
</div>
|
|
194
|
+
) : (
|
|
195
|
+
<img
|
|
196
|
+
src={captured.base64}
|
|
197
|
+
alt="Screenshot"
|
|
198
|
+
className="w-full h-auto rounded border border-border-subtle max-h-32 object-contain bg-surface-0"
|
|
199
|
+
/>
|
|
200
|
+
)}
|
|
168
201
|
</div>
|
|
169
202
|
<div className="p-3 flex items-center gap-2">
|
|
170
203
|
<input
|
|
@@ -178,7 +211,8 @@ export function ScreenshotOverlay({ iframeRef }) {
|
|
|
178
211
|
/>
|
|
179
212
|
<button
|
|
180
213
|
onClick={handleSubmit}
|
|
181
|
-
|
|
214
|
+
disabled={captured.loading}
|
|
215
|
+
className="w-8 h-8 flex items-center justify-center rounded-md bg-accent/15 text-accent hover:bg-accent/25 transition-colors cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed"
|
|
182
216
|
>
|
|
183
217
|
<Send size={14} />
|
|
184
218
|
</button>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.77",
|
|
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)",
|
|
@@ -168,6 +168,20 @@ export class PreviewService {
|
|
|
168
168
|
if (!command) {
|
|
169
169
|
return Promise.resolve({ launched: false, reason: 'no_command' });
|
|
170
170
|
}
|
|
171
|
+
// If command references an npm script, verify it exists in package.json
|
|
172
|
+
const npmRunMatch = command.match(/npm\s+run\s+(\S+)/);
|
|
173
|
+
if (npmRunMatch) {
|
|
174
|
+
const scriptName = npmRunMatch[1];
|
|
175
|
+
const pkgPath = resolve(baseDir, 'package.json');
|
|
176
|
+
try {
|
|
177
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
178
|
+
if (!pkg.scripts || !pkg.scripts[scriptName]) {
|
|
179
|
+
return Promise.resolve({ launched: false, reason: 'no_dev_script' });
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
return Promise.resolve({ launched: false, reason: 'no_dev_script' });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
171
185
|
const urlPattern = preview.urlPattern
|
|
172
186
|
? new RegExp(preview.urlPattern)
|
|
173
187
|
: /https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0):\d+/;
|
|
@@ -114,6 +114,9 @@ CRITICAL — NEVER DO THESE:
|
|
|
114
114
|
- NEVER kill the daemon process. No "kill <pid>", "pkill groove", "killall node", etc.
|
|
115
115
|
- NEVER run "./promote.sh", "./promote-local.sh", or any publish/deploy script.
|
|
116
116
|
- NEVER start long-running dev servers (vite dev, npm start, next dev, etc.).
|
|
117
|
+
- NEVER use 'git add -f' or 'git add --force' to bypass .gitignore. If a file is gitignored, it should stay gitignored. Only stage files that git tracks normally. If .gitignore prevents staging, report it in your output — do NOT force-add.
|
|
118
|
+
- NEVER use 'git push --force' or 'git push -f'. Force-pushing can destroy shared history.
|
|
119
|
+
- NEVER modify .gitignore to include files that were previously excluded.
|
|
117
120
|
|
|
118
121
|
Restarting the daemon destroys ALL other agents currently running in other teams. Verification is done via "npm run build" and "npm test", which exit cleanly. If code changes require a daemon restart to take effect, report that in your output so the user can restart manually — do NOT do it yourself.
|
|
119
122
|
|
|
@@ -1161,7 +1164,7 @@ For normal file edits within your scope, proceed without review.
|
|
|
1161
1164
|
const workingDir = plan.workingDir;
|
|
1162
1165
|
preview.launch(teamId, workingDir, plan.preview).then((result) => {
|
|
1163
1166
|
if (!result.launched) {
|
|
1164
|
-
const intentionalSkips = new Set(['no_preview', 'cli', 'none']);
|
|
1167
|
+
const intentionalSkips = new Set(['no_preview', 'cli', 'none', 'no_command', 'no_dev_script']);
|
|
1165
1168
|
if (intentionalSkips.has(result.reason)) {
|
|
1166
1169
|
console.log(`[Groove] Preview for team ${teamId} intentionally skipped: ${result.reason}`);
|
|
1167
1170
|
return;
|