groove-dev 0.27.49 → 0.27.51

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 (28) hide show
  1. package/default/security-review-prompt.md +98 -0
  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 +376 -29
  5. package/node_modules/@groove-dev/daemon/src/firstrun.js +1 -1
  6. package/node_modules/@groove-dev/daemon/src/index.js +7 -0
  7. package/node_modules/@groove-dev/daemon/src/providers/groove-network.js +57 -7
  8. package/node_modules/@groove-dev/gui/dist/assets/{index-B9oPxmNj.js → index-Dd4u8X70.js} +1723 -1723
  9. package/node_modules/@groove-dev/gui/dist/index.html +1 -1
  10. package/node_modules/@groove-dev/gui/package.json +1 -1
  11. package/node_modules/@groove-dev/gui/src/components/network/network-status.jsx +12 -0
  12. package/node_modules/@groove-dev/gui/src/components/network/node-toggle.jsx +18 -15
  13. package/node_modules/@groove-dev/gui/src/stores/groove.js +128 -1
  14. package/node_modules/@groove-dev/gui/src/views/network.jsx +82 -2
  15. package/package.json +1 -1
  16. package/packages/cli/package.json +1 -1
  17. package/packages/daemon/package.json +1 -1
  18. package/packages/daemon/src/api.js +376 -29
  19. package/packages/daemon/src/firstrun.js +1 -1
  20. package/packages/daemon/src/index.js +7 -0
  21. package/packages/daemon/src/providers/groove-network.js +57 -7
  22. package/packages/gui/dist/assets/{index-B9oPxmNj.js → index-Dd4u8X70.js} +1723 -1723
  23. package/packages/gui/dist/index.html +1 -1
  24. package/packages/gui/package.json +1 -1
  25. package/packages/gui/src/components/network/network-status.jsx +12 -0
  26. package/packages/gui/src/components/network/node-toggle.jsx +18 -15
  27. package/packages/gui/src/stores/groove.js +128 -1
  28. package/packages/gui/src/views/network.jsx +82 -2
@@ -6,7 +6,7 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <link rel="icon" type="image/png" href="/favicon.png" />
8
8
  <title>Groove GUI</title>
9
- <script type="module" crossorigin src="/assets/index-B9oPxmNj.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-Dd4u8X70.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/gui",
3
- "version": "0.27.49",
3
+ "version": "0.27.51",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -40,6 +40,7 @@ function KpiTile({ icon: Icon, label, value, sub }) {
40
40
  export function NetworkStatus() {
41
41
  const status = useGrooveStore((s) => s.networkStatus);
42
42
  const ownNodeId = useGrooveStore((s) => s.networkNode.nodeId);
43
+ const signalReachable = useGrooveStore((s) => s.networkStatusReachable);
43
44
  const [nodesOpen, setNodesOpen] = useState(true);
44
45
 
45
46
  const nodes = Array.isArray(status.nodes) ? status.nodes : [];
@@ -51,6 +52,17 @@ export function NetworkStatus() {
51
52
 
52
53
  return (
53
54
  <div className="flex flex-col gap-3">
55
+ {/* Signal connection indicator */}
56
+ <div className="flex items-center gap-2 px-3 py-2 rounded-lg border border-border bg-surface-1">
57
+ <StatusDot status={signalReachable ? 'running' : 'crashed'} size="sm" />
58
+ <span className="text-2xs font-sans text-text-3">Signal:</span>
59
+ <span className="text-2xs font-mono text-text-1">signal.groovedev.ai</span>
60
+ <div className="flex-1" />
61
+ <span className={cn('text-2xs font-sans', signalReachable ? 'text-success' : 'text-danger')}>
62
+ {signalReachable ? 'Connected' : 'Unreachable'}
63
+ </span>
64
+ </div>
65
+
54
66
  {/* KPI strip */}
55
67
  <div className="grid grid-cols-3 gap-2">
56
68
  <KpiTile icon={Globe} label="Nodes" value={fmtNum(nodes.length)} sub={`${nodes.filter((n) => n.status === 'active').length} active`} />
@@ -121,22 +121,25 @@ export function NodeToggle() {
121
121
  </div>
122
122
 
123
123
  {/* Identity */}
124
- <div>
125
- <div className="text-2xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5">Node Identity</div>
126
- <div className="flex items-center gap-2">
127
- <code className="flex-1 h-8 px-2.5 flex items-center bg-surface-0 border border-border-subtle rounded-md text-xs font-mono text-text-1">
128
- {shortAddress(node.nodeId)}
129
- </code>
130
- <Tooltip content={copied ? 'Copied' : 'Copy full address'} side="top">
131
- <button
132
- onClick={handleCopy}
133
- className="h-8 w-8 inline-flex items-center justify-center rounded-md border border-border-subtle bg-surface-0 text-text-3 hover:text-accent hover:border-accent/40 cursor-pointer transition-colors"
134
- >
135
- {copied ? <Check size={12} /> : <Copy size={12} />}
136
- </button>
137
- </Tooltip>
124
+ {node.nodeId && (
125
+ <div className="rounded-md border border-border-subtle bg-surface-0 px-3 py-2.5">
126
+ <div className="text-2xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5">Your Node Identity</div>
127
+ <div className="flex items-center gap-2">
128
+ <code className="flex-1 h-8 px-2.5 flex items-center bg-surface-1 border border-border-subtle rounded-md text-xs font-mono text-text-1">
129
+ {shortAddress(node.nodeId)}
130
+ </code>
131
+ <Tooltip content={copied ? 'Copied' : 'Copy full address'} side="top">
132
+ <button
133
+ onClick={handleCopy}
134
+ className="h-8 w-8 inline-flex items-center justify-center rounded-md border border-border-subtle bg-surface-1 text-text-3 hover:text-accent hover:border-accent/40 cursor-pointer transition-colors"
135
+ >
136
+ {copied ? <Check size={12} /> : <Copy size={12} />}
137
+ </button>
138
+ </Tooltip>
139
+ </div>
140
+ <div className="mt-1.5 text-2xs font-sans text-text-4">This is your network wallet address</div>
138
141
  </div>
139
- </div>
142
+ )}
140
143
 
141
144
  {/* Sessions + memory */}
142
145
  <div className="grid grid-cols-2 gap-2">
@@ -92,7 +92,10 @@ export const useGrooveStore = create((set, get) => ({
92
92
  networkInstallProgress: { installing: false, step: null, message: null, percent: 0, error: null },
93
93
  networkNode: { active: false, status: 'disconnected', nodeId: null, layers: null, model: null, sessions: 0, hardware: null },
94
94
  networkStatus: { nodes: [], coverage: 0, totalLayers: 0, models: [], activeSessions: 0 },
95
+ networkStatusReachable: false,
95
96
  networkEvents: [],
97
+ networkVersion: { installed: null, latest: null, updateAvailable: false },
98
+ networkUpdateProgress: { updating: false, step: null, message: null, percent: 0, error: null },
96
99
 
97
100
  // ── Marketplace Auth ───────────────────────────────────────
98
101
  marketplaceUser: null, // { id, displayName, avatar, ... } or null
@@ -616,6 +619,34 @@ export const useGrooveStore = create((set, get) => ({
616
619
  break;
617
620
  }
618
621
 
622
+ case 'signal_connected': {
623
+ const ev = msg.data || msg || {};
624
+ set((s) => ({
625
+ networkEvents: [...s.networkEvents, {
626
+ level: 'connected',
627
+ msg: ev.msg || ev.message || 'Connected to signal',
628
+ detail: ev.detail || ev.url,
629
+ ...ev,
630
+ timestamp: ev.timestamp || Date.now(),
631
+ }].slice(-100),
632
+ }));
633
+ break;
634
+ }
635
+
636
+ case 'matched': {
637
+ const ev = msg.data || msg || {};
638
+ set((s) => ({
639
+ networkEvents: [...s.networkEvents, {
640
+ level: 'session',
641
+ msg: ev.msg || ev.message || 'Session matched',
642
+ detail: ev.detail || ev.peer || ev.nodeId,
643
+ ...ev,
644
+ timestamp: ev.timestamp || Date.now(),
645
+ }].slice(-100),
646
+ }));
647
+ break;
648
+ }
649
+
619
650
  case 'network:status':
620
651
  set({ networkStatus: { ...get().networkStatus, ...(msg.data || {}) } });
621
652
  break;
@@ -652,6 +683,55 @@ export const useGrooveStore = create((set, get) => ({
652
683
  break;
653
684
  }
654
685
 
686
+ case 'network:update:available': {
687
+ const { installed, latest, updateAvailable } = msg.data || {};
688
+ set({
689
+ networkVersion: {
690
+ installed: installed ?? get().networkVersion.installed,
691
+ latest: latest ?? get().networkVersion.latest,
692
+ updateAvailable: !!updateAvailable,
693
+ },
694
+ });
695
+ break;
696
+ }
697
+
698
+ case 'network:update:progress': {
699
+ const { step, message, percent, version, error } = msg.data || {};
700
+ if (step === 'done') {
701
+ set({
702
+ networkUpdateProgress: { updating: false, step: null, message: null, percent: 0, error: null },
703
+ networkVersion: {
704
+ ...get().networkVersion,
705
+ installed: version || get().networkVersion.latest || get().networkVersion.installed,
706
+ updateAvailable: false,
707
+ },
708
+ });
709
+ get().addToast('success', 'Network package updated');
710
+ } else if (step === 'error') {
711
+ set({
712
+ networkUpdateProgress: {
713
+ updating: false,
714
+ step: 'error',
715
+ message: message || error || 'Update failed',
716
+ percent: 0,
717
+ error: message || error || 'Update failed',
718
+ },
719
+ });
720
+ get().addToast('error', 'Network update failed', message || error);
721
+ } else {
722
+ set({
723
+ networkUpdateProgress: {
724
+ updating: true,
725
+ step: step || 'progress',
726
+ message: message || '',
727
+ percent: Number.isFinite(percent) ? Math.max(0, Math.min(100, percent)) : get().networkUpdateProgress.percent,
728
+ error: null,
729
+ },
730
+ });
731
+ }
732
+ break;
733
+ }
734
+
655
735
  case 'config:updated':
656
736
  get().fetchBetaStatus();
657
737
  get().fetchNetworkInstallStatus();
@@ -1749,11 +1829,58 @@ export const useGrooveStore = create((set, get) => ({
1749
1829
  async fetchNetworkStatus() {
1750
1830
  try {
1751
1831
  const data = await api.get('/network/status');
1752
- set({ networkStatus: { ...get().networkStatus, ...(data || {}) } });
1832
+ set({
1833
+ networkStatus: { ...get().networkStatus, ...(data || {}) },
1834
+ networkStatusReachable: true,
1835
+ });
1836
+ return data;
1837
+ } catch {
1838
+ set({ networkStatusReachable: false });
1839
+ return null;
1840
+ }
1841
+ },
1842
+
1843
+ async checkNetworkUpdate() {
1844
+ try {
1845
+ const data = await api.get('/network/update/check');
1846
+ if (!data) return null;
1847
+ set({
1848
+ networkVersion: {
1849
+ installed: data.installed ?? null,
1850
+ latest: data.latest ?? null,
1851
+ updateAvailable: !!data.updateAvailable,
1852
+ },
1853
+ });
1753
1854
  return data;
1754
1855
  } catch { return null; }
1755
1856
  },
1756
1857
 
1858
+ async updateNetworkPackage() {
1859
+ set({
1860
+ networkUpdateProgress: {
1861
+ updating: true,
1862
+ step: 'starting',
1863
+ message: 'Starting update…',
1864
+ percent: 0,
1865
+ error: null,
1866
+ },
1867
+ });
1868
+ try {
1869
+ await api.post('/network/update');
1870
+ } catch (err) {
1871
+ set({
1872
+ networkUpdateProgress: {
1873
+ updating: false,
1874
+ step: 'error',
1875
+ message: err.message,
1876
+ percent: 0,
1877
+ error: err.message,
1878
+ },
1879
+ });
1880
+ get().addToast('error', 'Update failed', err.message);
1881
+ }
1882
+ },
1883
+
1757
1884
  async startNetworkNode() {
1758
1885
  set({ networkNode: { ...get().networkNode, status: 'connecting' } });
1759
1886
  try {
@@ -9,7 +9,7 @@ import { Dialog, DialogContent, DialogTrigger } from '../components/ui/dialog';
9
9
  import { NodeToggle } from '../components/network/node-toggle';
10
10
  import { NodeDetails } from '../components/network/node-details';
11
11
  import { NetworkStatus } from '../components/network/network-status';
12
- import { Globe, Download, Check, AlertCircle, Loader2, Trash2 } from 'lucide-react';
12
+ import { Globe, Download, Check, AlertCircle, Loader2, Trash2, ArrowUpCircle } from 'lucide-react';
13
13
 
14
14
  const REQUIREMENTS = [
15
15
  'Python 3.10 or higher',
@@ -113,6 +113,79 @@ function InstallGate() {
113
113
  );
114
114
  }
115
115
 
116
+ function UpdateProgress({ progress }) {
117
+ const percent = Math.max(0, Math.min(100, Number.isFinite(progress.percent) ? progress.percent : 0));
118
+ return (
119
+ <div className="flex items-center gap-2 px-3 py-1.5 rounded-md border border-border-subtle bg-surface-0">
120
+ <Loader2 size={11} className="animate-spin text-accent flex-shrink-0" />
121
+ <div className="flex flex-col min-w-0">
122
+ <span className="text-2xs font-sans text-text-1 truncate">{progress.message || 'Updating…'}</span>
123
+ <div className="h-1 w-32 rounded-full bg-surface-3 overflow-hidden mt-0.5">
124
+ <div
125
+ className="h-full rounded-full bg-accent transition-all duration-500 ease-out"
126
+ style={{ width: `${percent}%` }}
127
+ />
128
+ </div>
129
+ </div>
130
+ <span className="text-2xs font-mono text-text-3 tabular-nums">{percent}%</span>
131
+ </div>
132
+ );
133
+ }
134
+
135
+ function UpdateButton() {
136
+ const [open, setOpen] = useState(false);
137
+ const version = useGrooveStore((s) => s.networkVersion);
138
+ const progress = useGrooveStore((s) => s.networkUpdateProgress);
139
+ const updateNetworkPackage = useGrooveStore((s) => s.updateNetworkPackage);
140
+
141
+ if (progress.updating) {
142
+ return <UpdateProgress progress={progress} />;
143
+ }
144
+
145
+ if (!version.updateAvailable) return null;
146
+
147
+ const confirm = async () => {
148
+ try {
149
+ await updateNetworkPackage();
150
+ setOpen(false);
151
+ } catch { /* toast handled */ }
152
+ };
153
+
154
+ return (
155
+ <Dialog open={open} onOpenChange={setOpen}>
156
+ <DialogTrigger asChild>
157
+ <button
158
+ type="button"
159
+ className="inline-flex items-center gap-1"
160
+ title={`Update to ${version.latest}`}
161
+ >
162
+ <Badge variant="warning" className="cursor-pointer">
163
+ <ArrowUpCircle size={10} />
164
+ Update Available
165
+ </Badge>
166
+ </button>
167
+ </DialogTrigger>
168
+ <DialogContent title="Update Network Package" description="Confirm update">
169
+ <div className="px-5 py-4 flex flex-col gap-3">
170
+ <p className="text-sm text-text-1 font-sans leading-relaxed">
171
+ Update to <span className="font-mono text-accent">{version.latest}</span>?
172
+ </p>
173
+ <p className="text-xs text-text-3 font-sans leading-relaxed">
174
+ You are currently running <span className="font-mono">{version.installed || 'unknown'}</span>. Your node will be stopped during the update and restarted after.
175
+ </p>
176
+ </div>
177
+ <div className="flex items-center justify-end gap-2 px-5 py-3 border-t border-border-subtle bg-surface-0">
178
+ <Button variant="ghost" size="sm" onClick={() => setOpen(false)}>Cancel</Button>
179
+ <Button variant="primary" size="sm" onClick={confirm}>
180
+ <ArrowUpCircle size={12} />
181
+ Update
182
+ </Button>
183
+ </div>
184
+ </DialogContent>
185
+ </Dialog>
186
+ );
187
+ }
188
+
116
189
  function UninstallButton() {
117
190
  const [open, setOpen] = useState(false);
118
191
  const uninstallNetworkPackage = useGrooveStore((s) => s.uninstallNetworkPackage);
@@ -162,17 +235,20 @@ function UninstallButton() {
162
235
  export default function NetworkView() {
163
236
  const fetchNetworkNodeStatus = useGrooveStore((s) => s.fetchNetworkNodeStatus);
164
237
  const fetchNetworkStatus = useGrooveStore((s) => s.fetchNetworkStatus);
238
+ const checkNetworkUpdate = useGrooveStore((s) => s.checkNetworkUpdate);
165
239
  const node = useGrooveStore((s) => s.networkNode);
166
240
  const installed = useGrooveStore((s) => s.networkInstalled);
241
+ const version = useGrooveStore((s) => s.networkVersion);
167
242
 
168
243
  useEffect(() => {
169
244
  fetchNetworkNodeStatus();
170
245
  if (installed) {
171
246
  fetchNetworkStatus();
247
+ checkNetworkUpdate();
172
248
  const interval = setInterval(() => { fetchNetworkStatus(); }, 10000);
173
249
  return () => clearInterval(interval);
174
250
  }
175
- }, [fetchNetworkNodeStatus, fetchNetworkStatus, installed]);
251
+ }, [fetchNetworkNodeStatus, fetchNetworkStatus, checkNetworkUpdate, installed]);
176
252
 
177
253
  return (
178
254
  <div className="flex flex-col h-full">
@@ -181,6 +257,10 @@ export default function NetworkView() {
181
257
  <Globe size={14} className="text-accent" />
182
258
  <h2 className="text-sm font-semibold text-text-0 font-sans">Groove Network</h2>
183
259
  <Badge variant="purple">Early Access</Badge>
260
+ {installed && version.installed && (
261
+ <span className="text-2xs font-mono text-text-3 tabular-nums">v{String(version.installed).replace(/^v/, '')}</span>
262
+ )}
263
+ {installed && <UpdateButton />}
184
264
  <div className="flex-1" />
185
265
  {installed && (
186
266
  <>