groove-dev 0.27.64 → 0.27.66

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 (40) hide show
  1. package/README.md +1 -1
  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 +103 -31
  5. package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +0 -1
  6. package/node_modules/@groove-dev/gui/dist/assets/{index-DiiEKVEo.js → index-BvvSZvQz.js} +1735 -1735
  7. package/node_modules/@groove-dev/gui/dist/assets/index-DFp5IOnd.css +1 -0
  8. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  9. package/node_modules/@groove-dev/gui/package.json +1 -1
  10. package/node_modules/@groove-dev/gui/src/components/network/activity-chart.jsx +10 -14
  11. package/node_modules/@groove-dev/gui/src/components/network/compute-header.jsx +67 -200
  12. package/node_modules/@groove-dev/gui/src/components/network/earnings-card.jsx +30 -0
  13. package/node_modules/@groove-dev/gui/src/components/network/fleet-table.jsx +114 -72
  14. package/node_modules/@groove-dev/gui/src/components/network/identity-bar.jsx +94 -0
  15. package/node_modules/@groove-dev/gui/src/components/network/node-card.jsx +88 -0
  16. package/node_modules/@groove-dev/gui/src/components/network/wallet-view.jsx +77 -0
  17. package/node_modules/@groove-dev/gui/src/components/onboarding/setup-wizard.jsx +1 -1
  18. package/node_modules/@groove-dev/gui/src/stores/groove.js +13 -0
  19. package/node_modules/@groove-dev/gui/src/views/network.jsx +59 -18
  20. package/package.json +1 -1
  21. package/packages/cli/package.json +1 -1
  22. package/packages/daemon/package.json +1 -1
  23. package/packages/daemon/src/api.js +103 -31
  24. package/packages/daemon/src/providers/claude-code.js +0 -1
  25. package/packages/gui/dist/assets/{index-DiiEKVEo.js → index-BvvSZvQz.js} +1735 -1735
  26. package/packages/gui/dist/assets/index-DFp5IOnd.css +1 -0
  27. package/packages/gui/dist/index.html +2 -2
  28. package/packages/gui/package.json +1 -1
  29. package/packages/gui/src/components/network/activity-chart.jsx +10 -14
  30. package/packages/gui/src/components/network/compute-header.jsx +67 -200
  31. package/packages/gui/src/components/network/earnings-card.jsx +30 -0
  32. package/packages/gui/src/components/network/fleet-table.jsx +114 -72
  33. package/packages/gui/src/components/network/identity-bar.jsx +94 -0
  34. package/packages/gui/src/components/network/node-card.jsx +88 -0
  35. package/packages/gui/src/components/network/wallet-view.jsx +77 -0
  36. package/packages/gui/src/components/onboarding/setup-wizard.jsx +1 -1
  37. package/packages/gui/src/stores/groove.js +13 -0
  38. package/packages/gui/src/views/network.jsx +59 -18
  39. package/node_modules/@groove-dev/gui/dist/assets/index-B3AqeyS4.css +0 -1
  40. package/packages/gui/dist/assets/index-B3AqeyS4.css +0 -1
@@ -0,0 +1,94 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { memo, useState, useEffect, useCallback } from 'react';
3
+ import { useGrooveStore } from '../../stores/groove';
4
+ import { cn } from '../../lib/cn';
5
+ import { StatusDot } from '../ui/status-dot';
6
+ import { Button } from '../ui/button';
7
+ import { Tooltip } from '../ui/tooltip';
8
+ import { fmtUptime } from '../../lib/format';
9
+ import { Copy, Check, Wallet } from 'lucide-react';
10
+
11
+ function shortAddr(addr) {
12
+ if (!addr || typeof addr !== 'string') return '—';
13
+ if (addr.length < 14) return addr;
14
+ return `${addr.slice(0, 6)}…${addr.slice(-4)}`;
15
+ }
16
+
17
+ function nodeStatusMap(node) {
18
+ if (node.active && node.status === 'connected') return 'running';
19
+ if (node.status === 'connecting') return 'starting';
20
+ return 'stopped';
21
+ }
22
+
23
+ function statusColor(status) {
24
+ if (status === 'connected') return 'text-success';
25
+ if (status === 'connecting') return 'text-warning';
26
+ return 'text-danger';
27
+ }
28
+
29
+ export const IdentityBar = memo(function IdentityBar() {
30
+ const node = useGrooveStore((s) => s.networkNode);
31
+ const wallet = useGrooveStore((s) => s.networkWallet);
32
+ const [copied, setCopied] = useState(false);
33
+ const [uptime, setUptime] = useState(0);
34
+
35
+ useEffect(() => {
36
+ if (!node.startedAt) return;
37
+ const tick = () => setUptime(Math.floor((Date.now() - node.startedAt) / 1000));
38
+ tick();
39
+ const id = setInterval(tick, 1000);
40
+ return () => clearInterval(id);
41
+ }, [node.startedAt]);
42
+
43
+ const handleCopy = useCallback(async () => {
44
+ if (!node.nodeId) return;
45
+ try {
46
+ await navigator.clipboard.writeText(node.nodeId);
47
+ setCopied(true);
48
+ setTimeout(() => setCopied(false), 1500);
49
+ } catch { /* clipboard blocked */ }
50
+ }, [node.nodeId]);
51
+
52
+ const connStatus = node.status || 'disconnected';
53
+
54
+ return (
55
+ <div className="flex items-center h-11 px-4 bg-surface-0 border-b border-border-subtle gap-3">
56
+ {/* Left: node identity */}
57
+ <div className="flex items-center gap-2">
58
+ <StatusDot status={nodeStatusMap(node)} size="sm" />
59
+ <code className="text-xs font-mono text-text-0">{shortAddr(node.nodeId)}</code>
60
+ <Tooltip content={copied ? 'Copied' : 'Copy address'} side="bottom">
61
+ <button
62
+ onClick={handleCopy}
63
+ className="h-6 w-6 inline-flex items-center justify-center rounded border border-border-subtle text-text-3 hover:text-accent hover:border-accent/40 cursor-pointer transition-colors"
64
+ >
65
+ {copied ? <Check size={10} /> : <Copy size={10} />}
66
+ </button>
67
+ </Tooltip>
68
+ </div>
69
+
70
+ {/* Center: status + uptime */}
71
+ <div className="flex-1 flex items-center justify-center gap-3">
72
+ <span className={cn('text-2xs uppercase tracking-widest font-sans', statusColor(connStatus))}>
73
+ {connStatus === 'connected' ? 'Connected' : connStatus === 'connecting' ? 'Connecting' : 'Disconnected'}
74
+ </span>
75
+ <span className="w-1 h-1 rounded-full bg-text-4" />
76
+ <span className="text-2xs font-mono text-text-3 tabular-nums">
77
+ {fmtUptime(uptime)}
78
+ </span>
79
+ </div>
80
+
81
+ {/* Right: token balance + wallet */}
82
+ <div className="flex items-center gap-3">
83
+ <span className="text-xs font-mono text-text-2">
84
+ <span className="text-text-0">{wallet.balance}</span>
85
+ <span className="text-text-3"> GROOVE</span>
86
+ </span>
87
+ <Button variant="outline" size="sm" className="text-2xs gap-1.5" disabled>
88
+ <Wallet size={12} />
89
+ Connect Wallet
90
+ </Button>
91
+ </div>
92
+ </div>
93
+ );
94
+ });
@@ -0,0 +1,88 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { memo } from 'react';
3
+ import { useGrooveStore } from '../../stores/groove';
4
+ import { cn } from '../../lib/cn';
5
+ import { StatusDot } from '../ui/status-dot';
6
+
7
+ function shortAddr(addr) {
8
+ if (!addr || typeof addr !== 'string') return '—';
9
+ if (addr.length < 14) return addr;
10
+ return `${addr.slice(0, 6)}…${addr.slice(-4)}`;
11
+ }
12
+
13
+ function nodeStatusMap(node) {
14
+ if (node.active && node.status === 'connected') return 'running';
15
+ if (node.status === 'connecting') return 'starting';
16
+ return 'stopped';
17
+ }
18
+
19
+ function modelShort(model) {
20
+ if (!model) return '—';
21
+ const parts = model.split('/');
22
+ return parts[parts.length - 1];
23
+ }
24
+
25
+ export const NodeCard = memo(function NodeCard() {
26
+ const node = useGrooveStore((s) => s.networkNode);
27
+ const hardware = node.hardware || {};
28
+ const memPct = Number.isFinite(node.memoryPct) ? node.memoryPct : null;
29
+ const layersLabel = Array.isArray(node.layers) ? `${node.layers[0]}-${node.layers[1]}` : '—';
30
+
31
+ const metrics = [
32
+ { label: 'DEVICE', value: hardware.device || 'auto' },
33
+ { label: 'GPU', value: hardware.gpu || 'None' },
34
+ { label: 'LAYERS', value: layersLabel },
35
+ { label: 'MODEL', value: modelShort(node.model) },
36
+ { label: 'SESSIONS', value: node.sessions ?? 0 },
37
+ { label: 'VRAM', value: hardware.memory || '—' },
38
+ ];
39
+
40
+ return (
41
+ <div className="flex flex-col h-full">
42
+ <div className="px-3 pt-2.5 pb-1">
43
+ <span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Your Node</span>
44
+ </div>
45
+
46
+ <div className="px-3 py-2 space-y-2">
47
+ <div>
48
+ <div className="text-2xs text-text-4 uppercase tracking-wider">ADDRESS</div>
49
+ <div className="text-xs font-mono text-accent truncate">{shortAddr(node.nodeId)}</div>
50
+ </div>
51
+
52
+ <div className="flex items-center gap-1.5">
53
+ <StatusDot status={nodeStatusMap(node)} size="sm" />
54
+ <span className="text-2xs capitalize text-text-2">{node.status || 'disconnected'}</span>
55
+ </div>
56
+
57
+ <div className="border-t border-border-subtle my-1" />
58
+
59
+ <div className="grid grid-cols-2 gap-x-3 gap-y-1.5">
60
+ {metrics.map((m) => (
61
+ <div key={m.label}>
62
+ <div className="text-2xs text-text-4 uppercase tracking-wider">{m.label}</div>
63
+ <div className="text-xs font-mono text-text-1 tabular-nums truncate">{m.value}</div>
64
+ </div>
65
+ ))}
66
+ </div>
67
+
68
+ {memPct != null && (
69
+ <div className="mt-1">
70
+ <div className="flex items-center justify-between mb-0.5">
71
+ <span className="text-2xs text-text-4 uppercase tracking-wider">MEM</span>
72
+ <span className="text-2xs font-mono text-text-3 tabular-nums">{Math.round(memPct)}%</span>
73
+ </div>
74
+ <div className="h-1 w-full rounded-full bg-surface-3 overflow-hidden">
75
+ <div
76
+ className={cn(
77
+ 'h-full rounded-full transition-all',
78
+ memPct > 90 ? 'bg-danger' : memPct > 70 ? 'bg-warning' : 'bg-accent',
79
+ )}
80
+ style={{ width: `${Math.min(100, Math.max(0, memPct))}%` }}
81
+ />
82
+ </div>
83
+ </div>
84
+ )}
85
+ </div>
86
+ </div>
87
+ );
88
+ });
@@ -0,0 +1,77 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { memo } from 'react';
3
+ import { Badge } from '../ui/badge';
4
+ import { Button } from '../ui/button';
5
+ import { Wallet, Cpu, Activity, Clock, Zap } from 'lucide-react';
6
+
7
+ const REWARD_CARDS = [
8
+ { icon: Cpu, label: 'Compute Hours', value: '—' },
9
+ { icon: Activity, label: 'Sessions Served', value: '—' },
10
+ { icon: Clock, label: 'Uptime Score', value: '—' },
11
+ { icon: Zap, label: 'Network Score', value: '—' },
12
+ ];
13
+
14
+ const PAYOUT_COLS = ['Date', 'Amount', 'Status', 'TX'];
15
+
16
+ export const WalletView = memo(function WalletView() {
17
+ return (
18
+ <div className="flex flex-col h-full overflow-auto">
19
+ {/* Hero */}
20
+ <div className="px-6 py-8 text-center border-b border-border-subtle bg-surface-0">
21
+ <div className="text-3xl font-mono font-semibold text-text-0 tabular-nums">0.00</div>
22
+ <div className="text-sm font-mono text-text-3 mt-1">GROOVE</div>
23
+ <Badge variant="purple" className="mt-3">Base L2</Badge>
24
+ <div className="mt-4">
25
+ <Button variant="primary" size="md" className="gap-2" disabled>
26
+ <Wallet size={14} />
27
+ Connect Wallet
28
+ </Button>
29
+ </div>
30
+ <div className="text-2xs text-text-4 mt-2">Connect your Base wallet to claim rewards</div>
31
+ </div>
32
+
33
+ {/* Reward Metrics */}
34
+ <div className="px-4 py-4">
35
+ <div className="text-2xs font-mono text-text-3 uppercase tracking-widest mb-3">EARNING POTENTIAL</div>
36
+ <div className="grid grid-cols-4 gap-2">
37
+ {REWARD_CARDS.map((card) => (
38
+ <div key={card.label} className="rounded-md border border-border-subtle bg-surface-1 px-3 py-3 text-center">
39
+ <card.icon size={16} className="text-text-3 mx-auto mb-1.5" />
40
+ <div className="text-lg font-mono text-text-0 tabular-nums">{card.value}</div>
41
+ <div className="text-2xs text-text-4 mt-0.5">{card.label}</div>
42
+ </div>
43
+ ))}
44
+ </div>
45
+ </div>
46
+
47
+ {/* Earnings Timeline */}
48
+ <div className="px-4 py-4 border-t border-border-subtle">
49
+ <div className="text-2xs font-mono text-text-3 uppercase tracking-widest mb-3">EARNINGS HISTORY</div>
50
+ <div className="h-40 rounded-md border border-border-subtle bg-surface-0 flex items-center justify-center">
51
+ <span className="text-xs font-mono text-text-4">Earnings data will appear here</span>
52
+ </div>
53
+ </div>
54
+
55
+ {/* Payouts Table */}
56
+ <div className="px-4 py-4 border-t border-border-subtle">
57
+ <div className="text-2xs font-mono text-text-3 uppercase tracking-widest mb-3">PAYOUT HISTORY</div>
58
+ <div className="grid grid-cols-4 px-3 py-1.5 text-2xs font-mono text-text-4 uppercase tracking-wider">
59
+ {PAYOUT_COLS.map((col) => (
60
+ <span key={col}>{col}</span>
61
+ ))}
62
+ </div>
63
+ <div className="px-3 py-6 text-center text-xs font-mono text-text-4">No payouts yet</div>
64
+ </div>
65
+
66
+ {/* Banner */}
67
+ <div className="px-4 pb-6">
68
+ <div className="rounded-md border border-purple/20 bg-purple/5 px-4 py-3 flex items-center gap-3">
69
+ <Zap size={16} className="text-purple flex-shrink-0" />
70
+ <span className="text-xs font-sans text-text-2">
71
+ Rewards go live when the GROOVE token launches on Base. Keep your node running to accumulate compute credits.
72
+ </span>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ );
77
+ });
@@ -19,7 +19,7 @@ const PROVIDERS = [
19
19
  id: 'claude-code',
20
20
  name: 'Claude Code',
21
21
  subtitle: 'by Anthropic',
22
- models: ['Opus 4.7', 'Opus 4.6', 'Sonnet 4.6', 'Haiku 4.5'],
22
+ models: ['Opus 4.6', 'Sonnet 4.6', 'Haiku 4.5'],
23
23
  authType: 'Subscription or API key',
24
24
  authModes: ['subscription', 'apikey'],
25
25
  recommended: true,
@@ -105,6 +105,8 @@ export const useGrooveStore = create((set, get) => ({
105
105
  networkUpdateProgress: { updating: false, step: null, message: null, percent: 0, error: null },
106
106
  networkCompute: { totalRamMb: 0, totalVramMb: 0, totalCpuCores: 0, totalBandwidthMbps: 0, activeNodes: 0, totalNodes: 0, avgLoad: 0 },
107
107
  networkSnapshots: [],
108
+ networkWallet: { connected: false, address: null, balance: '0.00', token: 'GROOVE', chain: 'base-l2' },
109
+ networkEarnings: { today: 0, thisWeek: 0, allTime: 0, history: [] },
108
110
 
109
111
  // ── Marketplace Auth ───────────────────────────────────────
110
112
  marketplaceUser: null, // { id, displayName, avatar, ... } or null
@@ -732,6 +734,8 @@ export const useGrooveStore = create((set, get) => ({
732
734
  nodeCount: wsActive.length,
733
735
  avgLoad: wsActive.length > 0 ? wsActive.reduce((s, n) => s + (n.load || 0), 0) / wsActive.length : 0,
734
736
  myLoad: wsOwn?.load ?? 0,
737
+ totalVramMb: wsNodes.reduce((s, n) => s + (n.vram_mb || 0), 0),
738
+ totalRamMb: wsNodes.reduce((s, n) => s + (n.ram_mb || 0), 0),
735
739
  };
736
740
  let wsSnapshots = [...get().networkSnapshots, wsSnap];
737
741
  if (wsSnapshots.length > 100) wsSnapshots = wsSnapshots.slice(-100);
@@ -2179,6 +2183,8 @@ export const useGrooveStore = create((set, get) => ({
2179
2183
  nodeCount: activeNodes.length,
2180
2184
  avgLoad: activeNodes.length > 0 ? activeNodes.reduce((s, n) => s + (n.load || 0), 0) / activeNodes.length : 0,
2181
2185
  myLoad: ownNode?.load ?? 0,
2186
+ totalVramMb: nodes.reduce((s, n) => s + (n.vram_mb || 0), 0),
2187
+ totalRamMb: nodes.reduce((s, n) => s + (n.ram_mb || 0), 0),
2182
2188
  };
2183
2189
  let snapshots = [...get().networkSnapshots, snap];
2184
2190
  if (snapshots.length > 100) snapshots = snapshots.slice(-100);
@@ -2258,6 +2264,13 @@ export const useGrooveStore = create((set, get) => ({
2258
2264
  }
2259
2265
  },
2260
2266
 
2267
+ async fetchNetworkWallet() {
2268
+ return get().networkWallet;
2269
+ },
2270
+ async fetchNetworkEarnings() {
2271
+ return get().networkEarnings;
2272
+ },
2273
+
2261
2274
  async renameFile(oldPath, newPath) {
2262
2275
  try {
2263
2276
  await api.post('/files/rename', { oldPath, newPath });
@@ -6,12 +6,17 @@ import { Badge } from '../components/ui/badge';
6
6
  import { Button } from '../components/ui/button';
7
7
  import { Dialog, DialogContent, DialogTrigger } from '../components/ui/dialog';
8
8
  import { ScrollArea } from '../components/ui/scroll-area';
9
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '../components/ui/tabs';
9
10
  import { cn } from '../lib/cn';
10
- import { NodeToggle } from '../components/network/node-toggle';
11
+ import { IdentityBar } from '../components/network/identity-bar';
11
12
  import { ComputeHeader } from '../components/network/compute-header';
12
13
  import { ActivityChart } from '../components/network/activity-chart';
13
14
  import { ActivityStream } from '../components/network/activity-stream';
14
15
  import { NetworkHealth } from '../components/network/network-health';
16
+ import { NodeCard } from '../components/network/node-card';
17
+ import { EarningsCard } from '../components/network/earnings-card';
18
+ import { FleetTable } from '../components/network/fleet-table';
19
+ import { WalletView } from '../components/network/wallet-view';
15
20
  import { HEX, hexAlpha } from '../lib/theme-hex';
16
21
  import { Globe, Download, Check, AlertCircle, Loader2, Trash2, ArrowUpCircle, Zap } from 'lucide-react';
17
22
 
@@ -405,12 +410,44 @@ function IdleHero() {
405
410
  );
406
411
  }
407
412
 
413
+ function NetworkOverview() {
414
+ return (
415
+ <div className="flex flex-col h-full">
416
+ <ComputeHeader />
417
+ <div className="flex-1 min-h-0 grid" style={{ gridTemplateColumns: '2.5fr 1fr', gap: '1px', background: HEX.surface3 }}>
418
+ {/* Left column */}
419
+ <div className="flex flex-col min-w-0 min-h-0" style={{ gap: '1px' }}>
420
+ <div className="flex-1 min-h-0 overflow-hidden bg-surface-1">
421
+ <ActivityChart />
422
+ </div>
423
+ <div className="flex-[0.5] min-h-0 overflow-hidden bg-surface-1">
424
+ <ActivityStream />
425
+ </div>
426
+ </div>
427
+ {/* Right column */}
428
+ <div className="flex flex-col min-w-0 min-h-0" style={{ gap: '1px' }}>
429
+ <div className="min-h-0 overflow-hidden bg-surface-1">
430
+ <NodeCard />
431
+ </div>
432
+ <div className="flex-1 min-h-0 overflow-hidden bg-surface-1">
433
+ <NetworkHealth />
434
+ </div>
435
+ <div className="min-h-0 overflow-hidden bg-surface-1">
436
+ <EarningsCard />
437
+ </div>
438
+ </div>
439
+ </div>
440
+ </div>
441
+ );
442
+ }
443
+
408
444
  export default function NetworkView() {
409
445
  const fetchNetworkNodeStatus = useGrooveStore((s) => s.fetchNetworkNodeStatus);
410
446
  const fetchNetworkStatus = useGrooveStore((s) => s.fetchNetworkStatus);
411
447
  const checkNetworkUpdate = useGrooveStore((s) => s.checkNetworkUpdate);
412
448
  const installed = useGrooveStore((s) => s.networkInstalled);
413
449
  const nodeActive = useGrooveStore((s) => s.networkNode.active);
450
+ const [activeTab, setActiveTab] = useState('overview');
414
451
 
415
452
  useEffect(() => {
416
453
  fetchNetworkNodeStatus();
@@ -440,23 +477,27 @@ export default function NetworkView() {
440
477
  return (
441
478
  <div className="flex flex-col h-full">
442
479
  <NetworkHeader />
443
-
444
- <ComputeHeader />
445
-
446
- <div className="flex-1 min-h-0 flex flex-col" style={{ background: HEX.surface3, gap: '1px' }}>
447
- <div className="min-h-0 flex-1 grid" style={{ gridTemplateColumns: '3fr 1.5fr', gap: '0 1px' }}>
448
- <div className="min-w-0 min-h-0 overflow-hidden bg-surface-1">
449
- <ActivityChart />
450
- </div>
451
- <div className="min-w-0 min-h-0 overflow-hidden bg-surface-1">
452
- <NetworkHealth />
453
- </div>
454
- </div>
455
-
456
- <div className="min-h-0 flex-[0.6] bg-surface-1">
457
- <ActivityStream />
458
- </div>
459
- </div>
480
+ <IdentityBar />
481
+
482
+ <Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 min-h-0 flex flex-col">
483
+ <TabsList className="bg-surface-0 border-b border-border-subtle px-4 flex-shrink-0">
484
+ <TabsTrigger value="overview" className="text-xs">Overview</TabsTrigger>
485
+ <TabsTrigger value="fleet" className="text-xs">Fleet</TabsTrigger>
486
+ <TabsTrigger value="wallet" className="text-xs">Wallet</TabsTrigger>
487
+ </TabsList>
488
+
489
+ <TabsContent value="overview" className="flex-1 min-h-0 overflow-hidden">
490
+ <NetworkOverview />
491
+ </TabsContent>
492
+
493
+ <TabsContent value="fleet" className="flex-1 min-h-0 overflow-hidden bg-surface-1">
494
+ <FleetTable />
495
+ </TabsContent>
496
+
497
+ <TabsContent value="wallet" className="flex-1 min-h-0 overflow-hidden">
498
+ <WalletView />
499
+ </TabsContent>
500
+ </Tabs>
460
501
  </div>
461
502
  );
462
503
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.27.64",
3
+ "version": "0.27.66",
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)",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.64",
3
+ "version": "0.27.66",
4
4
  "description": "GROOVE CLI — manage AI coding agents from your terminal",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/daemon",
3
- "version": "0.27.64",
3
+ "version": "0.27.66",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -4145,9 +4145,36 @@ Keep responses concise. Help them think, don't lecture them about the system the
4145
4145
 
4146
4146
  // Network node lifecycle (gated)
4147
4147
 
4148
+ let _localHwCache = null;
4149
+ function getLocalHardware() {
4150
+ if (!_localHwCache) {
4151
+ const sys = OllamaProvider.getSystemHardware();
4152
+ const vramGb = sys.gpu?.vram || 0;
4153
+ const ramGb = sys.totalRamGb || 0;
4154
+ const vramMb = vramGb * 1024;
4155
+ const ramMb = ramGb * 1024;
4156
+ const fmtGb = (gb) => gb > 0 ? `${gb} GB` : null;
4157
+ _localHwCache = {
4158
+ device: sys.gpu?.type === 'nvidia' ? 'cuda' : sys.gpu?.type === 'apple-silicon' ? 'metal' : 'cpu',
4159
+ gpu: sys.gpu?.name || null,
4160
+ memory: fmtGb(vramGb) || fmtGb(ramGb),
4161
+ vram: fmtGb(vramGb),
4162
+ ram: fmtGb(ramGb),
4163
+ cpuCores: sys.cores || null,
4164
+ ram_mb: ramMb,
4165
+ vram_mb: vramMb,
4166
+ gpu_model: sys.gpu?.name || null,
4167
+ cpu_cores: sys.cores || 0,
4168
+ bandwidth_mbps: 0,
4169
+ max_context_length: 0,
4170
+ };
4171
+ }
4172
+ return _localHwCache;
4173
+ }
4174
+
4148
4175
  function snapshotNode() {
4149
4176
  const n = daemon.networkNode || {};
4150
- const hw = n.hardware || {};
4177
+ const hw = n.hardware || getLocalHardware();
4151
4178
  return {
4152
4179
  active: !!n.active,
4153
4180
  status: n.status || 'stopped',
@@ -4155,7 +4182,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
4155
4182
  layers: n.layers || null,
4156
4183
  model: n.model || null,
4157
4184
  sessions: n.sessions || 0,
4158
- hardware: n.hardware || null,
4185
+ hardware: hw,
4159
4186
  installed: !!(daemon.config?.networkBeta?.installed),
4160
4187
  ram_mb: Number(hw.ram_mb) || 0,
4161
4188
  vram_mb: Number(hw.vram_mb) || 0,
@@ -4212,6 +4239,12 @@ Keep responses concise. Help them think, don't lecture them about the system the
4212
4239
  cpuCores: caps.cpu_cores || null,
4213
4240
  bandwidthMbps: caps.bandwidth_mbps || null,
4214
4241
  maxContext: caps.max_context_length || null,
4242
+ ram_mb: Number(caps.ram_mb) || 0,
4243
+ vram_mb: Number(caps.vram_mb) || 0,
4244
+ gpu_model: caps.gpu_model || null,
4245
+ cpu_cores: Number(caps.cpu_cores) || 0,
4246
+ bandwidth_mbps: Number(caps.bandwidth_mbps) || 0,
4247
+ max_context_length: Number(caps.max_context_length) || 0,
4215
4248
  };
4216
4249
  }
4217
4250
 
@@ -4282,7 +4315,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
4282
4315
  layers: null,
4283
4316
  model: null,
4284
4317
  sessions: 0,
4285
- hardware: null,
4318
+ hardware: getLocalHardware(),
4286
4319
  startedAt: Date.now(),
4287
4320
  events: [],
4288
4321
  };
@@ -4454,14 +4487,15 @@ Keep responses concise. Help them think, don't lecture them about the system the
4454
4487
  daemon.networkNode.layers = self.layers;
4455
4488
  changed = true;
4456
4489
  }
4457
- if (self.device) {
4458
- daemon.networkNode.hardware = {
4459
- device: self.device,
4460
- memory: daemon.networkNode.hardware?.memory || null,
4461
- gpu: daemon.networkNode.hardware?.gpu || null,
4462
- };
4463
- changed = true;
4464
- }
4490
+ const prev = daemon.networkNode.hardware || getLocalHardware();
4491
+ const enriched = { ...prev };
4492
+ if (self.device) enriched.device = self.device;
4493
+ if (self.gpu_model) { enriched.gpu = self.gpu_model; enriched.gpu_model = self.gpu_model; }
4494
+ if (Number(self.ram_mb) > 0) { enriched.ram_mb = Number(self.ram_mb); }
4495
+ if (Number(self.vram_mb) > 0) { enriched.vram_mb = Number(self.vram_mb); enriched.memory = enriched.vram_mb >= 1024 ? `${(enriched.vram_mb / 1024).toFixed(1)} GB` : `${enriched.vram_mb} MB`; }
4496
+ if (Number(self.cpu_cores) > 0) { enriched.cpu_cores = Number(self.cpu_cores); enriched.cpuCores = Number(self.cpu_cores); }
4497
+ daemon.networkNode.hardware = enriched;
4498
+ changed = true;
4465
4499
  }
4466
4500
  const availModel = Array.isArray(data.models)
4467
4501
  ? data.models.find((m) => m && m.available !== false)
@@ -4474,25 +4508,39 @@ Keep responses concise. Help them think, don't lecture them about the system the
4474
4508
  }
4475
4509
 
4476
4510
  const capStr = (s, max = 200) => (typeof s === 'string' ? s.slice(0, max) : s);
4477
- const safeNodes = (Array.isArray(data.nodes) ? data.nodes : []).map((n) => ({
4478
- node_id: capStr(n.node_id || n.nodeId),
4479
- device: capStr(n.device),
4480
- layers: Array.isArray(n.layers) ? n.layers.slice(0, 2) : n.layers,
4481
- status: capStr(n.status, 50),
4482
- active_sessions: n.active_sessions ?? 0,
4483
- ram_mb: Number(n.ram_mb) || 0,
4484
- vram_mb: Number(n.vram_mb) || 0,
4485
- gpu_model: capStr(n.gpu_model || '', 200),
4486
- cpu_cores: Number(n.cpu_cores) || 0,
4487
- bandwidth_mbps: Number(n.bandwidth_mbps) || 0.0,
4488
- max_context_length: Number(n.max_context_length) || 0,
4489
- load: Number(n.load) || 0.0,
4490
- gpu_utilization_pct: Number(n.gpu_utilization_pct) || 0,
4491
- vram_used_mb: Number(n.vram_used_mb) || 0,
4492
- ram_used_mb: Number(n.ram_used_mb) || 0,
4493
- ram_pct: Number(n.ram_pct) || 0,
4494
- uptime_seconds: Number(n.uptime_seconds) || 0,
4495
- }));
4511
+ const selfId = daemon.networkNode?.nodeId;
4512
+ const localHw = getLocalHardware();
4513
+ const safeNodes = (Array.isArray(data.nodes) ? data.nodes : []).map((n) => {
4514
+ const nid = n.node_id || n.nodeId || '';
4515
+ const isSelf = selfId && nid && (nid === selfId || (nid.length >= 6 && selfId.startsWith(nid.replace(/\.{2,}$/, ''))));
4516
+ const base = {
4517
+ node_id: capStr(nid),
4518
+ device: capStr(n.device),
4519
+ layers: Array.isArray(n.layers) ? n.layers.slice(0, 2) : n.layers,
4520
+ status: capStr(n.status, 50),
4521
+ active_sessions: n.active_sessions ?? 0,
4522
+ ram_mb: Number(n.ram_mb) || 0,
4523
+ vram_mb: Number(n.vram_mb) || 0,
4524
+ gpu_model: capStr(n.gpu_model || '', 200),
4525
+ cpu_cores: Number(n.cpu_cores) || 0,
4526
+ bandwidth_mbps: Number(n.bandwidth_mbps) || 0.0,
4527
+ max_context_length: Number(n.max_context_length) || 0,
4528
+ load: Number(n.load) || 0.0,
4529
+ gpu_utilization_pct: Number(n.gpu_utilization_pct) || 0,
4530
+ vram_used_mb: Number(n.vram_used_mb) || 0,
4531
+ ram_used_mb: Number(n.ram_used_mb) || 0,
4532
+ ram_pct: Number(n.ram_pct) || 0,
4533
+ uptime_seconds: Number(n.uptime_seconds) || 0,
4534
+ };
4535
+ if (isSelf) {
4536
+ if (!base.device) base.device = localHw.device;
4537
+ if (!base.gpu_model) base.gpu_model = localHw.gpu_model || '';
4538
+ if (!base.ram_mb) base.ram_mb = localHw.ram_mb;
4539
+ if (!base.vram_mb) base.vram_mb = localHw.vram_mb;
4540
+ if (!base.cpu_cores) base.cpu_cores = localHw.cpu_cores;
4541
+ }
4542
+ return base;
4543
+ });
4496
4544
 
4497
4545
  return res.json({
4498
4546
  nodes: safeNodes,
@@ -4515,7 +4563,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
4515
4563
  const localCpuCores = sysHw.cores || 0;
4516
4564
  const selfNode = node.active && node.nodeId ? [{
4517
4565
  node_id: node.nodeId,
4518
- device: hw.device || 'auto',
4566
+ device: hw.device || (sysHw.gpu?.type === 'nvidia' ? 'cuda' : sysHw.gpu?.type === 'apple-silicon' ? 'metal' : 'cpu'),
4519
4567
  layers: node.layers || [0, 0],
4520
4568
  status: node.status === 'connected' ? 'active' : node.status,
4521
4569
  active_sessions: node.sessions || 0,
@@ -5178,6 +5226,30 @@ Keep responses concise. Help them think, don't lecture them about the system the
5178
5226
  })();
5179
5227
  });
5180
5228
 
5229
+ // --- Wallet & earnings stubs (Base L2 — wired to real data post-mainnet) ---
5230
+
5231
+ app.get('/api/network/wallet', networkGate, (req, res) => {
5232
+ res.json({ connected: false, address: null, balance: '0.00', token: 'GROOVE', chain: 'base-l2' });
5233
+ });
5234
+
5235
+ app.get('/api/network/earnings', networkGate, (req, res) => {
5236
+ res.json({ today: 0, thisWeek: 0, allTime: 0, history: [], currency: 'GROOVE' });
5237
+ });
5238
+
5239
+ app.post('/api/network/wallet/connect', networkGate, (req, res) => {
5240
+ res.status(501).json({ error: 'Wallet connection not yet available. Coming with mainnet launch.' });
5241
+ });
5242
+
5243
+ app.get('/api/network/node/identity', networkGate, (req, res) => {
5244
+ const node = daemon.networkNode;
5245
+ res.json({
5246
+ nodeId: node?.nodeId || null,
5247
+ address: node?.nodeId || null,
5248
+ startedAt: node?.startedAt || null,
5249
+ uptime: node?.startedAt ? Math.floor((Date.now() - node.startedAt) / 1000) : 0,
5250
+ });
5251
+ });
5252
+
5181
5253
  // Startup hook — called from index.js once the server is up. Non-blocking;
5182
5254
  // updates daemon.networkUpdateAvailable and broadcasts so the GUI can badge.
5183
5255
  daemon.checkNetworkUpdate = async function checkNetworkUpdate() {
@@ -40,7 +40,6 @@ export class ClaudeCodeProvider extends Provider {
40
40
  static managesOwnContext = true; // Claude Code compacts context internally (~25-37% → 2-8%)
41
41
  static models = [
42
42
  { id: 'claude-opus-4-6', name: 'Claude Opus 4.6', tier: 'heavy', contextWindow: 1_000_000 },
43
- { id: 'claude-opus-4-7', name: 'Claude Opus 4.7', tier: 'heavy', contextWindow: 1_000_000 },
44
43
  { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6', tier: 'medium', contextWindow: 200_000 },
45
44
  { id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5', tier: 'light', contextWindow: 200_000 },
46
45
  ];