groove-dev 0.11.0 → 0.12.0
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/node_modules/@groove-dev/cli/bin/groove.js +32 -0
- package/node_modules/@groove-dev/cli/src/commands/audit.js +60 -0
- package/node_modules/@groove-dev/cli/src/commands/connect.js +279 -0
- package/node_modules/@groove-dev/cli/src/commands/disconnect.js +91 -0
- package/node_modules/@groove-dev/cli/src/commands/federation.js +84 -0
- package/node_modules/@groove-dev/cli/src/commands/start.js +7 -2
- package/node_modules/@groove-dev/cli/src/commands/status.js +4 -1
- package/node_modules/@groove-dev/daemon/src/api.js +106 -2
- package/node_modules/@groove-dev/daemon/src/audit.js +65 -0
- package/node_modules/@groove-dev/daemon/src/federation.js +352 -0
- package/node_modules/@groove-dev/daemon/src/firstrun.js +27 -2
- package/node_modules/@groove-dev/daemon/src/index.js +59 -6
- package/node_modules/@groove-dev/gui/dist/assets/{index-BqZnnVJF.js → index-B49YqEXS.js} +17 -17
- package/node_modules/@groove-dev/gui/dist/assets/{index-CPzm9ZE9.css → index-Gfb8Zxy9.css} +1 -1
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/src/App.jsx +24 -1
- package/node_modules/@groove-dev/gui/src/components/SpawnPanel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/stores/groove.js +19 -2
- package/node_modules/@groove-dev/gui/src/theme.css +2 -2
- package/package.json +1 -1
- package/packages/cli/bin/groove.js +32 -0
- package/packages/cli/src/commands/audit.js +60 -0
- package/packages/cli/src/commands/connect.js +279 -0
- package/packages/cli/src/commands/disconnect.js +91 -0
- package/packages/cli/src/commands/federation.js +84 -0
- package/packages/cli/src/commands/start.js +7 -2
- package/packages/cli/src/commands/status.js +4 -1
- package/packages/daemon/src/api.js +106 -2
- package/packages/daemon/src/audit.js +65 -0
- package/packages/daemon/src/federation.js +352 -0
- package/packages/daemon/src/firstrun.js +27 -2
- package/packages/daemon/src/index.js +59 -6
- package/packages/gui/dist/assets/{index-BqZnnVJF.js → index-B49YqEXS.js} +17 -17
- package/packages/gui/dist/assets/{index-CPzm9ZE9.css → index-Gfb8Zxy9.css} +1 -1
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/src/App.jsx +24 -1
- package/packages/gui/src/components/SpawnPanel.jsx +1 -1
- package/packages/gui/src/stores/groove.js +19 -2
- package/packages/gui/src/theme.css +2 -2
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { createServer as createHttpServer } from 'http';
|
|
5
5
|
import { createServer as createNetServer } from 'net';
|
|
6
|
+
import { execFileSync } from 'child_process';
|
|
6
7
|
import { resolve } from 'path';
|
|
7
8
|
import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync } from 'fs';
|
|
8
9
|
import express from 'express';
|
|
@@ -24,15 +25,55 @@ import { TaskClassifier } from './classifier.js';
|
|
|
24
25
|
import { ModelRouter } from './router.js';
|
|
25
26
|
import { ProjectManager } from './pm.js';
|
|
26
27
|
import { CodebaseIndexer } from './indexer.js';
|
|
28
|
+
import { AuditLogger } from './audit.js';
|
|
29
|
+
import { Federation } from './federation.js';
|
|
27
30
|
import { isFirstRun, runFirstTimeSetup, loadConfig, saveConfig, printWelcome } from './firstrun.js';
|
|
28
31
|
|
|
29
32
|
const DEFAULT_PORT = 31415;
|
|
33
|
+
const DEFAULT_HOST = '127.0.0.1';
|
|
30
34
|
|
|
31
35
|
export { loadConfig, saveConfig } from './firstrun.js';
|
|
32
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the --host value to a bind address.
|
|
39
|
+
* - 'tailscale' → auto-detect via `tailscale ip -4`
|
|
40
|
+
* - '0.0.0.0' / '::' → rejected (security policy)
|
|
41
|
+
* - anything else → used as-is
|
|
42
|
+
*/
|
|
43
|
+
function resolveHost(host) {
|
|
44
|
+
if (!host || host === 'localhost') return DEFAULT_HOST;
|
|
45
|
+
|
|
46
|
+
// Block direct internet exposure
|
|
47
|
+
if (host === '0.0.0.0' || host === '::') {
|
|
48
|
+
console.error('\n Direct internet exposure not supported.');
|
|
49
|
+
console.error(' Use `groove connect` (SSH tunnel) or `--host tailscale` instead.\n');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Auto-detect Tailscale IP
|
|
54
|
+
if (host === 'tailscale') {
|
|
55
|
+
try {
|
|
56
|
+
const ip = execFileSync('tailscale', ['ip', '-4'], {
|
|
57
|
+
encoding: 'utf8',
|
|
58
|
+
timeout: 5000,
|
|
59
|
+
}).trim().split('\n')[0];
|
|
60
|
+
if (!ip) throw new Error('empty response');
|
|
61
|
+
return ip;
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error('\n Could not detect Tailscale IP.');
|
|
64
|
+
console.error(' Make sure Tailscale is installed and running: `tailscale status`');
|
|
65
|
+
console.error(` Error: ${err.message}\n`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return host;
|
|
71
|
+
}
|
|
72
|
+
|
|
33
73
|
export class Daemon {
|
|
34
74
|
constructor(options = {}) {
|
|
35
75
|
this.port = options.port !== undefined ? options.port : (parseInt(process.env.GROOVE_PORT, 10) || DEFAULT_PORT);
|
|
76
|
+
this.host = resolveHost(options.host);
|
|
36
77
|
this.projectDir = options.projectDir || process.cwd();
|
|
37
78
|
this.grooveDir = options.grooveDir || resolve(this.projectDir, '.groove');
|
|
38
79
|
this.pidFile = resolve(this.grooveDir, 'daemon.pid');
|
|
@@ -71,6 +112,8 @@ export class Daemon {
|
|
|
71
112
|
this.router = new ModelRouter(this);
|
|
72
113
|
this.pm = new ProjectManager(this);
|
|
73
114
|
this.indexer = new CodebaseIndexer(this);
|
|
115
|
+
this.audit = new AuditLogger(this.grooveDir);
|
|
116
|
+
this.federation = new Federation(this);
|
|
74
117
|
|
|
75
118
|
// HTTP + WebSocket server
|
|
76
119
|
this.app = express();
|
|
@@ -80,13 +123,17 @@ export class Daemon {
|
|
|
80
123
|
maxPayload: 1024 * 1024, // 1MB max message
|
|
81
124
|
verifyClient: ({ req }) => {
|
|
82
125
|
const origin = req.headers.origin;
|
|
83
|
-
// Allow: no origin (CLI/native clients), localhost origins
|
|
126
|
+
// Allow: no origin (CLI/native clients), localhost origins, bound host
|
|
84
127
|
if (!origin) return true;
|
|
85
128
|
const allowed = [
|
|
86
129
|
`http://localhost:${this.port}`,
|
|
87
130
|
`http://127.0.0.1:${this.port}`,
|
|
88
131
|
'http://localhost:3142',
|
|
89
132
|
];
|
|
133
|
+
// Allow the bound interface (for Tailscale/LAN access)
|
|
134
|
+
if (this.host !== DEFAULT_HOST) {
|
|
135
|
+
allowed.push(`http://${this.host}:${this.port}`);
|
|
136
|
+
}
|
|
90
137
|
return allowed.includes(origin);
|
|
91
138
|
},
|
|
92
139
|
});
|
|
@@ -143,11 +190,12 @@ export class Daemon {
|
|
|
143
190
|
}
|
|
144
191
|
|
|
145
192
|
// Auto-find an open port if the default is taken
|
|
193
|
+
const bindHost = this.host;
|
|
146
194
|
const checkPort = (port) => new Promise((res) => {
|
|
147
195
|
const tester = createNetServer();
|
|
148
196
|
tester.once('error', () => res(false));
|
|
149
197
|
tester.once('listening', () => { tester.close(); res(true); });
|
|
150
|
-
tester.listen(port,
|
|
198
|
+
tester.listen(port, bindHost);
|
|
151
199
|
}).catch(() => false);
|
|
152
200
|
|
|
153
201
|
if (!(await checkPort(this.port))) {
|
|
@@ -172,12 +220,13 @@ export class Daemon {
|
|
|
172
220
|
this.registry.restore(this.state.get('agents') || []);
|
|
173
221
|
|
|
174
222
|
return new Promise((resolvePromise) => {
|
|
175
|
-
this.server.listen(this.port,
|
|
223
|
+
this.server.listen(this.port, this.host, () => {
|
|
176
224
|
writeFileSync(this.pidFile, String(process.pid));
|
|
177
|
-
// Write actual port so CLI can find us
|
|
225
|
+
// Write actual port and host so CLI can find us
|
|
178
226
|
writeFileSync(resolve(this.grooveDir, 'daemon.port'), String(this.port));
|
|
227
|
+
writeFileSync(resolve(this.grooveDir, 'daemon.host'), this.host);
|
|
179
228
|
|
|
180
|
-
printWelcome(this.port);
|
|
229
|
+
printWelcome(this.port, this.host);
|
|
181
230
|
|
|
182
231
|
// Start background services
|
|
183
232
|
this.journalist.start();
|
|
@@ -203,10 +252,14 @@ export class Daemon {
|
|
|
203
252
|
// Kill all agent processes
|
|
204
253
|
await this.processes.killAll();
|
|
205
254
|
|
|
206
|
-
// Clean up PID
|
|
255
|
+
// Clean up PID and host files
|
|
207
256
|
if (existsSync(this.pidFile)) {
|
|
208
257
|
unlinkSync(this.pidFile);
|
|
209
258
|
}
|
|
259
|
+
const hostFile = resolve(this.grooveDir, 'daemon.host');
|
|
260
|
+
if (existsSync(hostFile)) {
|
|
261
|
+
unlinkSync(hostFile);
|
|
262
|
+
}
|
|
210
263
|
|
|
211
264
|
// Clean up generated files
|
|
212
265
|
const registryPath = resolve(this.projectDir, 'AGENTS_REGISTRY.md');
|