groove-dev 0.12.4 → 0.12.5

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/README.md CHANGED
@@ -12,7 +12,7 @@ npm i -g groove-dev
12
12
  groove start
13
13
  ```
14
14
 
15
- Open `http://localhost:31415` your command center is ready.
15
+ The GUI opens at `http://localhost:31415`. On a VPS? GROOVE detects it and tells you exactly what to do.
16
16
 
17
17
  ---
18
18
 
@@ -61,6 +61,125 @@ GROOVE auto-detects monorepo workspaces (npm, pnpm, lerna) and lets you spawn ea
61
61
  - **Task negotiation** — duplicate roles get assigned non-overlapping work
62
62
  - **Knock protocol** — agents signal before shared/destructive actions
63
63
 
64
+ ## Remote Access
65
+
66
+ Run GROOVE on a VPS and manage your agents from anywhere. No ports exposed to the internet. No tokens. No custom auth code. Zero attack surface.
67
+
68
+ ### How It Works
69
+
70
+ GROOVE never opens ports to the public internet. Instead, it uses battle-tested transport layers — SSH tunnels and WireGuard (Tailscale) — to keep your daemon private.
71
+
72
+ ```
73
+ Your laptop Your VPS
74
+ ┌──────────┐ SSH tunnel ┌──────────────────┐
75
+ │ Browser │ ◄────────────────► │ GROOVE daemon │
76
+ │ localhost│ encrypted │ 127.0.0.1:31415 │
77
+ └──────────┘ └──────────────────┘
78
+ Zero open ports
79
+ ```
80
+
81
+ The daemon always binds to `127.0.0.1`. Nothing reaches it from the public internet. Your SSH keys handle auth. Your browser connects to `localhost` on your machine, and the tunnel forwards traffic securely to the VPS.
82
+
83
+ ### Setup
84
+
85
+ **On your VPS:**
86
+
87
+ ```bash
88
+ npm i -g groove-dev # install
89
+ groove start # start the daemon
90
+ ```
91
+
92
+ **On your laptop:**
93
+
94
+ ```bash
95
+ npm i -g groove-dev # install (one-time)
96
+ groove connect user@your-server-ip # open the GUI
97
+ groove disconnect # close when done
98
+ ```
99
+
100
+ That's it. The GUI opens in your browser automatically.
101
+
102
+ GROOVE auto-detects your environment — VS Code Remote, plain SSH, or local — and tells you exactly what to do. SSH config aliases work too: `groove connect my-vps`.
103
+
104
+ ### Tailscale / LAN Access
105
+
106
+ For multi-device access (phone, tablet, other machines on your network):
107
+
108
+ ```bash
109
+ groove start --host tailscale # auto-detects your Tailscale IP
110
+ groove start --host 192.168.1.5 # explicit IP
111
+ ```
112
+
113
+ Open `http://<ip>:31415` from any device on the same network. Tailscale handles auth via WireGuard.
114
+
115
+ ### What's Blocked
116
+
117
+ GROOVE will reject any attempt to expose the daemon directly to the internet:
118
+
119
+ ```bash
120
+ groove start --host 0.0.0.0 # REJECTED — not allowed
121
+ ```
122
+
123
+ This is by design. Direct exposure requires custom auth, rate limiting, TLS management — attack surface we refuse to create. SSH and WireGuard solve this better than we ever could.
124
+
125
+ ### Federation (Preview)
126
+
127
+ Pair GROOVE daemons across machines with Ed25519 key exchange. The security layer is built — cross-server agent coordination (typed contracts, federated registry) is coming soon.
128
+
129
+ ```bash
130
+ groove federation pair 100.64.1.5 # pair two daemons
131
+ groove federation list # see paired peers
132
+ groove federation status # show keypair + peers
133
+ ```
134
+
135
+ Every cross-server message is signed with Ed25519 keys generated during a pairing ceremony. The receiving daemon verifies the signature before accepting any contract. Replay attacks are rejected (5-minute timestamp window). Tampered payloads are rejected. Unknown senders are rejected.
136
+
137
+ ```
138
+ Server A Server B
139
+ ┌──────────────┐ signed contract ┌──────────────┐
140
+ │ GROOVE daemon│ ◄────────────────►│ GROOVE daemon│
141
+ │ Ed25519 key │ verify + audit │ Ed25519 key │
142
+ └──────────────┘ └──────────────┘
143
+ ```
144
+
145
+ Contracts are typed data (method, path, input/output schema) — not freeform text. No surface for prompt injection. Full audit trail on both sides.
146
+
147
+ ### Audit Log
148
+
149
+ Every state-changing operation is logged to `.groove/audit.log`:
150
+
151
+ ```bash
152
+ groove audit # view recent entries
153
+ groove audit -n 50 # last 50 entries
154
+ ```
155
+
156
+ ```
157
+ 2:14:32 PM agent.spawn id=a1 role=backend provider=claude-code
158
+ 2:14:35 PM agent.spawn id=a2 role=frontend provider=claude-code
159
+ 2:33:12 PM agent.rotate oldId=a1 newId=a3 role=backend
160
+ 2:45:00 PM federation.pair peerId=f63dc52b14b9 peerHost=100.64.1.5
161
+ ```
162
+
163
+ Append-only, `0600` permissions, auto-rotates at 5MB. When team auth is added, every entry will include who performed the action.
164
+
165
+ ### Security Model
166
+
167
+ | Threat | Defense |
168
+ |--------|---------|
169
+ | Remote attackers | Zero open ports. Daemon binds to private interface only. |
170
+ | Network eavesdroppers | SSH (tunnel) or WireGuard (Tailscale) encryption. |
171
+ | Spoofed federation contracts | Ed25519 signature verification on every message. |
172
+ | Replay attacks | 5-minute timestamp window. Reject old/future contracts. |
173
+ | Malformed peer data | Public key validation at pairing time. Peer IDs restricted to hex. |
174
+ | Path traversal | Peer IDs sanitized. No filesystem access across servers. |
175
+ | Privilege escalation | No auth code to exploit. Transport layer handles all access control. |
176
+
177
+ **What we explicitly don't defend against:** Compromised SSH keys, root access to VPS, malicious AI provider responses (out of scope — we're a process manager).
178
+
179
+ **The principle:** "There's nothing to attack" is better than "we have a security system and here's why it's good." GROOVE has zero auth code. The transport layer does all the work.
180
+
181
+ ---
182
+
64
183
  ## Works With Everything
65
184
 
66
185
  | Provider | Auth | Models |
@@ -76,7 +195,7 @@ Works in any terminal, any IDE, any OS. Technical and non-technical users alike.
76
195
 
77
196
  ## The GUI
78
197
 
79
- Open `http://localhost:31415` after starting the daemon:
198
+ Open the dashboard after starting the daemon (local or remote):
80
199
 
81
200
  - **Agent Tree** — visual node graph with Bezier spline connections, role badges, live status
82
201
  - **Chat** — instruct agents, query without disrupting, continue completed agents, streaming text
@@ -99,7 +218,7 @@ GROOVE routes tasks to the cheapest model that can handle them. Planners get Opu
99
218
 
100
219
  ```
101
220
  ┌──────────────────────────────────────────────┐
102
- │ GROOVE DAEMON (:31415)
221
+ │ GROOVE DAEMON (:31415)
103
222
  │ │
104
223
  │ Registry · Introducer · Lock Manager │
105
224
  │ Journalist · Rotator · Adaptive · Indexer │
@@ -22,37 +22,14 @@ export function isFirstRun(grooveDir) {
22
22
  }
23
23
 
24
24
  // Show welcome banner on every startup
25
- export function printWelcome(port, host = '127.0.0.1') {
25
+ export function printWelcome(port, host = '127.0.0.1', firstRun = false) {
26
26
  const providers = listProviders();
27
27
  const installed = providers.filter((p) => p.installed);
28
- const notInstalled = providers.filter((p) => !p.installed);
29
28
 
30
29
  console.log('');
31
- console.log(' ┌─────────────────────────────────────┐');
32
- console.log(' │ Welcome to GROOVE │');
33
- console.log(' │ Agent orchestration for AI coding │');
34
- console.log(' └─────────────────────────────────────┘');
30
+ console.log(' GROOVE');
35
31
  console.log('');
36
32
 
37
- if (installed.length > 0) {
38
- console.log(` Providers (${installed.length} ready):`);
39
- for (const p of installed) {
40
- console.log(` ✓ ${p.name}`);
41
- }
42
- } else {
43
- console.log(' No AI providers detected.');
44
- console.log(' Install at least one: npm i -g @anthropic-ai/claude-code');
45
- }
46
-
47
- if (notInstalled.length > 0) {
48
- console.log('');
49
- console.log(' Available to install:');
50
- for (const p of notInstalled) {
51
- console.log(` · ${p.name.padEnd(18)} ${p.installCommand}`);
52
- }
53
- }
54
-
55
- console.log('');
56
33
  const isRemote = host !== '127.0.0.1';
57
34
 
58
35
  // Detect environment
@@ -63,17 +40,12 @@ export function printWelcome(port, host = '127.0.0.1') {
63
40
  const isServer = !isVSCode && (isSSH || isHeadless);
64
41
 
65
42
  if (isRemote) {
66
- // Bound to network interface (Tailscale/LAN)
67
- console.log(` GUI: http://${host}:${port}`);
68
- console.log(` Host: ${host} (network-accessible)`);
43
+ console.log(` Open: http://${host}:${port}`);
69
44
  } else if (isVSCode) {
70
- // VS Code Remote — port forwarding happens automatically
71
- console.log(` GUI: http://localhost:${port}`);
45
+ console.log(` Open: http://localhost:${port}`);
72
46
  console.log(` VS Code forwards this port automatically.`);
73
47
  } else if (isServer) {
74
- // Plain SSH / headless — need groove connect
75
48
  const sshUser = process.env.SUDO_USER || process.env.USER || 'user';
76
-
77
49
  let serverIp = '';
78
50
  const sshConn = process.env.SSH_CONNECTION || '';
79
51
  if (sshConn) {
@@ -92,20 +64,26 @@ export function printWelcome(port, host = '127.0.0.1') {
92
64
  }
93
65
  }
94
66
  serverIp = serverIp || '<your-server-ip>';
95
-
96
- console.log(` Daemon running on port ${port}`);
97
- console.log('');
98
- console.log(' To open the GUI, open a terminal on your Mac/PC and run:');
99
- console.log('');
67
+ console.log(' Open the GUI from your Mac/PC:');
100
68
  console.log(` npx groove-dev connect ${sshUser}@${serverIp}`);
101
69
  } else {
102
- // Local machine with display
103
- console.log(` GUI: http://localhost:${port}`);
70
+ console.log(` Open: http://localhost:${port}`);
71
+ }
72
+
73
+ console.log(` Stop: groove stop (or Ctrl+C)`);
74
+ console.log(` Docs: https://docs.groovedev.ai`);
75
+
76
+ // Show providers only on first run or if none installed
77
+ if (firstRun || installed.length === 0) {
78
+ console.log('');
79
+ if (installed.length > 0) {
80
+ console.log(` Providers: ${installed.map((p) => p.name).join(', ')}`);
81
+ } else {
82
+ console.log(' No AI providers detected.');
83
+ console.log(' Install one: npm i -g @anthropic-ai/claude-code');
84
+ }
104
85
  }
105
86
 
106
- console.log('');
107
- console.log(' Docs: https://docs.groovedev.ai');
108
- console.log(' GitHub: https://github.com/grooveai-dev/groove');
109
87
  console.log('');
110
88
  }
111
89
 
@@ -89,7 +89,8 @@ export class Daemon {
89
89
  }
90
90
 
91
91
  // First-run detection
92
- if (isFirstRun(this.grooveDir)) {
92
+ this._firstRun = isFirstRun(this.grooveDir);
93
+ if (this._firstRun) {
93
94
  this.config = runFirstTimeSetup(this.grooveDir);
94
95
  } else {
95
96
  this.config = loadConfig(this.grooveDir);
@@ -224,7 +225,7 @@ export class Daemon {
224
225
  writeFileSync(resolve(this.grooveDir, 'daemon.port'), String(this.port));
225
226
  writeFileSync(resolve(this.grooveDir, 'daemon.host'), this.host);
226
227
 
227
- printWelcome(this.port, this.host);
228
+ printWelcome(this.port, this.host, this._firstRun);
228
229
 
229
230
  // Start background services
230
231
  this.journalist.start();
@@ -27,7 +27,6 @@ export class Journalist {
27
27
  // Run first cycle immediately, then on interval
28
28
  this.cycle();
29
29
  this.interval = setInterval(() => this.cycle(), intervalMs);
30
- console.log(` Journalist started (every ${intervalMs / 1000}s)`);
31
30
  }
32
31
 
33
32
  stop() {
@@ -20,7 +20,6 @@ export class Rotator extends EventEmitter {
20
20
  if (this.interval) return;
21
21
  this.enabled = true;
22
22
  this.interval = setInterval(() => this.check(), CHECK_INTERVAL);
23
- console.log(' Rotator started (auto-rotation enabled)');
24
23
  }
25
24
 
26
25
  stop() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.12.4",
3
+ "version": "0.12.5",
4
4
  "description": "Open-source agent orchestration layer for AI coding tools. GUI dashboard, multi-agent coordination, zero cold-start (Journalist), infinite sessions (adaptive context rotation), AI Project Manager, Quick Launch. Works with Claude Code, Codex, Gemini CLI, Ollama.",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
@@ -22,37 +22,14 @@ export function isFirstRun(grooveDir) {
22
22
  }
23
23
 
24
24
  // Show welcome banner on every startup
25
- export function printWelcome(port, host = '127.0.0.1') {
25
+ export function printWelcome(port, host = '127.0.0.1', firstRun = false) {
26
26
  const providers = listProviders();
27
27
  const installed = providers.filter((p) => p.installed);
28
- const notInstalled = providers.filter((p) => !p.installed);
29
28
 
30
29
  console.log('');
31
- console.log(' ┌─────────────────────────────────────┐');
32
- console.log(' │ Welcome to GROOVE │');
33
- console.log(' │ Agent orchestration for AI coding │');
34
- console.log(' └─────────────────────────────────────┘');
30
+ console.log(' GROOVE');
35
31
  console.log('');
36
32
 
37
- if (installed.length > 0) {
38
- console.log(` Providers (${installed.length} ready):`);
39
- for (const p of installed) {
40
- console.log(` ✓ ${p.name}`);
41
- }
42
- } else {
43
- console.log(' No AI providers detected.');
44
- console.log(' Install at least one: npm i -g @anthropic-ai/claude-code');
45
- }
46
-
47
- if (notInstalled.length > 0) {
48
- console.log('');
49
- console.log(' Available to install:');
50
- for (const p of notInstalled) {
51
- console.log(` · ${p.name.padEnd(18)} ${p.installCommand}`);
52
- }
53
- }
54
-
55
- console.log('');
56
33
  const isRemote = host !== '127.0.0.1';
57
34
 
58
35
  // Detect environment
@@ -63,17 +40,12 @@ export function printWelcome(port, host = '127.0.0.1') {
63
40
  const isServer = !isVSCode && (isSSH || isHeadless);
64
41
 
65
42
  if (isRemote) {
66
- // Bound to network interface (Tailscale/LAN)
67
- console.log(` GUI: http://${host}:${port}`);
68
- console.log(` Host: ${host} (network-accessible)`);
43
+ console.log(` Open: http://${host}:${port}`);
69
44
  } else if (isVSCode) {
70
- // VS Code Remote — port forwarding happens automatically
71
- console.log(` GUI: http://localhost:${port}`);
45
+ console.log(` Open: http://localhost:${port}`);
72
46
  console.log(` VS Code forwards this port automatically.`);
73
47
  } else if (isServer) {
74
- // Plain SSH / headless — need groove connect
75
48
  const sshUser = process.env.SUDO_USER || process.env.USER || 'user';
76
-
77
49
  let serverIp = '';
78
50
  const sshConn = process.env.SSH_CONNECTION || '';
79
51
  if (sshConn) {
@@ -92,20 +64,26 @@ export function printWelcome(port, host = '127.0.0.1') {
92
64
  }
93
65
  }
94
66
  serverIp = serverIp || '<your-server-ip>';
95
-
96
- console.log(` Daemon running on port ${port}`);
97
- console.log('');
98
- console.log(' To open the GUI, open a terminal on your Mac/PC and run:');
99
- console.log('');
67
+ console.log(' Open the GUI from your Mac/PC:');
100
68
  console.log(` npx groove-dev connect ${sshUser}@${serverIp}`);
101
69
  } else {
102
- // Local machine with display
103
- console.log(` GUI: http://localhost:${port}`);
70
+ console.log(` Open: http://localhost:${port}`);
71
+ }
72
+
73
+ console.log(` Stop: groove stop (or Ctrl+C)`);
74
+ console.log(` Docs: https://docs.groovedev.ai`);
75
+
76
+ // Show providers only on first run or if none installed
77
+ if (firstRun || installed.length === 0) {
78
+ console.log('');
79
+ if (installed.length > 0) {
80
+ console.log(` Providers: ${installed.map((p) => p.name).join(', ')}`);
81
+ } else {
82
+ console.log(' No AI providers detected.');
83
+ console.log(' Install one: npm i -g @anthropic-ai/claude-code');
84
+ }
104
85
  }
105
86
 
106
- console.log('');
107
- console.log(' Docs: https://docs.groovedev.ai');
108
- console.log(' GitHub: https://github.com/grooveai-dev/groove');
109
87
  console.log('');
110
88
  }
111
89
 
@@ -89,7 +89,8 @@ export class Daemon {
89
89
  }
90
90
 
91
91
  // First-run detection
92
- if (isFirstRun(this.grooveDir)) {
92
+ this._firstRun = isFirstRun(this.grooveDir);
93
+ if (this._firstRun) {
93
94
  this.config = runFirstTimeSetup(this.grooveDir);
94
95
  } else {
95
96
  this.config = loadConfig(this.grooveDir);
@@ -224,7 +225,7 @@ export class Daemon {
224
225
  writeFileSync(resolve(this.grooveDir, 'daemon.port'), String(this.port));
225
226
  writeFileSync(resolve(this.grooveDir, 'daemon.host'), this.host);
226
227
 
227
- printWelcome(this.port, this.host);
228
+ printWelcome(this.port, this.host, this._firstRun);
228
229
 
229
230
  // Start background services
230
231
  this.journalist.start();
@@ -27,7 +27,6 @@ export class Journalist {
27
27
  // Run first cycle immediately, then on interval
28
28
  this.cycle();
29
29
  this.interval = setInterval(() => this.cycle(), intervalMs);
30
- console.log(` Journalist started (every ${intervalMs / 1000}s)`);
31
30
  }
32
31
 
33
32
  stop() {
@@ -20,7 +20,6 @@ export class Rotator extends EventEmitter {
20
20
  if (this.interval) return;
21
21
  this.enabled = true;
22
22
  this.interval = setInterval(() => this.check(), CHECK_INTERVAL);
23
- console.log(' Rotator started (auto-rotation enabled)');
24
23
  }
25
24
 
26
25
  stop() {