groove-dev 0.8.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.
Files changed (84) hide show
  1. package/CLAUDE.md +197 -0
  2. package/LICENSE +40 -0
  3. package/README.md +115 -0
  4. package/docs/GUI_DESIGN_SPEC.md +402 -0
  5. package/favicon.png +0 -0
  6. package/groove-logo-short.png +0 -0
  7. package/groove-logo.png +0 -0
  8. package/package.json +70 -0
  9. package/packages/cli/bin/groove.js +98 -0
  10. package/packages/cli/package.json +15 -0
  11. package/packages/cli/src/client.js +25 -0
  12. package/packages/cli/src/commands/agents.js +38 -0
  13. package/packages/cli/src/commands/approve.js +50 -0
  14. package/packages/cli/src/commands/config.js +35 -0
  15. package/packages/cli/src/commands/kill.js +15 -0
  16. package/packages/cli/src/commands/nuke.js +19 -0
  17. package/packages/cli/src/commands/providers.js +40 -0
  18. package/packages/cli/src/commands/rotate.js +16 -0
  19. package/packages/cli/src/commands/spawn.js +91 -0
  20. package/packages/cli/src/commands/start.js +31 -0
  21. package/packages/cli/src/commands/status.js +38 -0
  22. package/packages/cli/src/commands/stop.js +15 -0
  23. package/packages/cli/src/commands/team.js +77 -0
  24. package/packages/daemon/package.json +18 -0
  25. package/packages/daemon/src/adaptive.js +237 -0
  26. package/packages/daemon/src/api.js +533 -0
  27. package/packages/daemon/src/classifier.js +126 -0
  28. package/packages/daemon/src/credentials.js +121 -0
  29. package/packages/daemon/src/firstrun.js +93 -0
  30. package/packages/daemon/src/index.js +208 -0
  31. package/packages/daemon/src/introducer.js +238 -0
  32. package/packages/daemon/src/journalist.js +600 -0
  33. package/packages/daemon/src/lockmanager.js +58 -0
  34. package/packages/daemon/src/pm.js +108 -0
  35. package/packages/daemon/src/process.js +361 -0
  36. package/packages/daemon/src/providers/aider.js +72 -0
  37. package/packages/daemon/src/providers/base.js +38 -0
  38. package/packages/daemon/src/providers/claude-code.js +167 -0
  39. package/packages/daemon/src/providers/codex.js +68 -0
  40. package/packages/daemon/src/providers/gemini.js +62 -0
  41. package/packages/daemon/src/providers/index.js +38 -0
  42. package/packages/daemon/src/providers/ollama.js +94 -0
  43. package/packages/daemon/src/registry.js +89 -0
  44. package/packages/daemon/src/rotator.js +185 -0
  45. package/packages/daemon/src/router.js +132 -0
  46. package/packages/daemon/src/state.js +34 -0
  47. package/packages/daemon/src/supervisor.js +178 -0
  48. package/packages/daemon/src/teams.js +203 -0
  49. package/packages/daemon/src/terminal/base.js +27 -0
  50. package/packages/daemon/src/terminal/generic.js +27 -0
  51. package/packages/daemon/src/terminal/tmux.js +64 -0
  52. package/packages/daemon/src/tokentracker.js +124 -0
  53. package/packages/daemon/src/validate.js +122 -0
  54. package/packages/daemon/templates/api-builder.json +18 -0
  55. package/packages/daemon/templates/fullstack.json +18 -0
  56. package/packages/daemon/templates/monorepo.json +24 -0
  57. package/packages/gui/dist/assets/index-BO95Rm1F.js +73 -0
  58. package/packages/gui/dist/assets/index-CPzm9ZE9.css +1 -0
  59. package/packages/gui/dist/favicon.png +0 -0
  60. package/packages/gui/dist/groove-logo-short.png +0 -0
  61. package/packages/gui/dist/groove-logo.png +0 -0
  62. package/packages/gui/dist/index.html +13 -0
  63. package/packages/gui/index.html +12 -0
  64. package/packages/gui/package.json +22 -0
  65. package/packages/gui/public/favicon.png +0 -0
  66. package/packages/gui/public/groove-logo-short.png +0 -0
  67. package/packages/gui/public/groove-logo.png +0 -0
  68. package/packages/gui/src/App.jsx +215 -0
  69. package/packages/gui/src/components/AgentActions.jsx +347 -0
  70. package/packages/gui/src/components/AgentChat.jsx +479 -0
  71. package/packages/gui/src/components/AgentNode.jsx +117 -0
  72. package/packages/gui/src/components/AgentPanel.jsx +115 -0
  73. package/packages/gui/src/components/AgentStats.jsx +333 -0
  74. package/packages/gui/src/components/ApprovalQueue.jsx +156 -0
  75. package/packages/gui/src/components/EmptyState.jsx +100 -0
  76. package/packages/gui/src/components/SpawnPanel.jsx +515 -0
  77. package/packages/gui/src/components/TeamSelector.jsx +162 -0
  78. package/packages/gui/src/main.jsx +9 -0
  79. package/packages/gui/src/stores/groove.js +247 -0
  80. package/packages/gui/src/theme.css +67 -0
  81. package/packages/gui/src/views/AgentTree.jsx +148 -0
  82. package/packages/gui/src/views/CommandCenter.jsx +620 -0
  83. package/packages/gui/src/views/JournalistFeed.jsx +149 -0
  84. package/packages/gui/vite.config.js +19 -0
@@ -0,0 +1,50 @@
1
+ // GROOVE CLI — approve/reject commands
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import chalk from 'chalk';
5
+ import { apiCall } from '../client.js';
6
+
7
+ export async function approvals() {
8
+ try {
9
+ const { pending, status } = await apiCall('GET', '/api/approvals');
10
+
11
+ if (pending.length === 0) {
12
+ console.log(chalk.dim(' No pending approvals.'));
13
+ return;
14
+ }
15
+
16
+ console.log(chalk.bold(`\n Pending Approvals (${pending.length})\n`));
17
+ for (const a of pending) {
18
+ console.log(` ${chalk.yellow(a.id)}`);
19
+ console.log(` Agent: ${a.agentName}`);
20
+ console.log(` Action: ${a.action?.description || a.action?.type || 'unknown'}`);
21
+ console.log(` Time: ${new Date(a.requestedAt).toLocaleTimeString()}`);
22
+ console.log('');
23
+ }
24
+ } catch {
25
+ console.error(chalk.red(' Cannot connect to daemon.'));
26
+ process.exit(1);
27
+ }
28
+ }
29
+
30
+ export async function approve(id) {
31
+ try {
32
+ const result = await apiCall('POST', `/api/approvals/${id}/approve`);
33
+ console.log(chalk.green(' Approved:'), result.id);
34
+ } catch (err) {
35
+ console.error(chalk.red(' Failed:'), err.message);
36
+ process.exit(1);
37
+ }
38
+ }
39
+
40
+ export async function reject(id, options) {
41
+ try {
42
+ const result = await apiCall('POST', `/api/approvals/${id}/reject`, {
43
+ reason: options.reason || '',
44
+ });
45
+ console.log(chalk.green(' Rejected:'), result.id);
46
+ } catch (err) {
47
+ console.error(chalk.red(' Failed:'), err.message);
48
+ process.exit(1);
49
+ }
50
+ }
@@ -0,0 +1,35 @@
1
+ // GROOVE CLI — config command
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import chalk from 'chalk';
5
+ import { apiCall } from '../client.js';
6
+
7
+ export async function configShow() {
8
+ try {
9
+ const status = await apiCall('GET', '/api/config');
10
+ console.log(chalk.bold('\n GROOVE Configuration\n'));
11
+ for (const [key, value] of Object.entries(status)) {
12
+ console.log(` ${chalk.dim(key.padEnd(24))} ${chalk.white(JSON.stringify(value))}`);
13
+ }
14
+ console.log('');
15
+ } catch {
16
+ console.error(chalk.red(' Cannot connect to daemon.'));
17
+ process.exit(1);
18
+ }
19
+ }
20
+
21
+ export async function configSet(key, value) {
22
+ try {
23
+ // Auto-parse numbers and booleans
24
+ let parsed = value;
25
+ if (value === 'true') parsed = true;
26
+ else if (value === 'false') parsed = false;
27
+ else if (!isNaN(value) && value !== '') parsed = Number(value);
28
+
29
+ await apiCall('PATCH', '/api/config', { [key]: parsed });
30
+ console.log(chalk.green(` Set ${key} = ${JSON.stringify(parsed)}`));
31
+ } catch (err) {
32
+ console.error(chalk.red(' Failed:'), err.message);
33
+ process.exit(1);
34
+ }
35
+ }
@@ -0,0 +1,15 @@
1
+ // GROOVE CLI — kill command
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import chalk from 'chalk';
5
+ import { apiCall } from '../client.js';
6
+
7
+ export async function kill(id) {
8
+ try {
9
+ await apiCall('DELETE', `/api/agents/${id}`);
10
+ console.log(chalk.green('Killed agent:'), id);
11
+ } catch (err) {
12
+ console.error(chalk.red('Failed to kill:'), err.message);
13
+ process.exit(1);
14
+ }
15
+ }
@@ -0,0 +1,19 @@
1
+ // GROOVE CLI — nuke command
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import chalk from 'chalk';
5
+ import { apiCall } from '../client.js';
6
+
7
+ export async function nuke() {
8
+ try {
9
+ console.log(chalk.yellow('Nuking all agents...'));
10
+ await apiCall('DELETE', '/api/agents');
11
+
12
+ const status = await apiCall('GET', '/api/status');
13
+ process.kill(status.pid, 'SIGTERM');
14
+
15
+ console.log(chalk.green('All agents killed. Daemon stopped.'));
16
+ } catch {
17
+ console.log(chalk.yellow('No running daemon found.'));
18
+ }
19
+ }
@@ -0,0 +1,40 @@
1
+ // GROOVE CLI — providers command
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import chalk from 'chalk';
5
+ import { apiCall } from '../client.js';
6
+
7
+ export async function providers() {
8
+ try {
9
+ const list = await apiCall('GET', '/api/providers');
10
+
11
+ console.log(chalk.bold('\n Available Providers\n'));
12
+
13
+ for (const p of list) {
14
+ const installed = p.installed ? chalk.green('installed') : chalk.red('not installed');
15
+ const authed = p.authType === 'local' || p.authType === 'subscription'
16
+ ? ''
17
+ : p.hasKey ? chalk.green(' (key set)') : chalk.yellow(' (no key)');
18
+
19
+ console.log(` ${chalk.bold(p.name.padEnd(18))} ${installed}${authed}`);
20
+ console.log(` Auth: ${p.authType} Models: ${p.models.map(m => m.name).join(', ')}`);
21
+ if (!p.installed) {
22
+ console.log(` Install: ${chalk.dim(p.installCommand)}`);
23
+ }
24
+ console.log('');
25
+ }
26
+ } catch {
27
+ console.error(chalk.red(' Cannot connect to daemon.'));
28
+ process.exit(1);
29
+ }
30
+ }
31
+
32
+ export async function setKey(provider, key) {
33
+ try {
34
+ const result = await apiCall('POST', `/api/credentials/${provider}`, { key });
35
+ console.log(chalk.green(` Key set for ${provider}:`), result.masked);
36
+ } catch (err) {
37
+ console.error(chalk.red(' Failed:'), err.message);
38
+ process.exit(1);
39
+ }
40
+ }
@@ -0,0 +1,16 @@
1
+ // GROOVE CLI — rotate command
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import chalk from 'chalk';
5
+ import { apiCall } from '../client.js';
6
+
7
+ export async function rotate(id) {
8
+ try {
9
+ console.log(chalk.yellow(` Rotating agent ${id}...`));
10
+ const newAgent = await apiCall('POST', `/api/agents/${id}/rotate`);
11
+ console.log(chalk.green(` Rotated.`), `New session: ${chalk.bold(newAgent.name)} (${newAgent.id})`);
12
+ } catch (err) {
13
+ console.error(chalk.red(' Rotation failed:'), err.message);
14
+ process.exit(1);
15
+ }
16
+ }
@@ -0,0 +1,91 @@
1
+ // GROOVE CLI — spawn command
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import chalk from 'chalk';
5
+ import { createInterface } from 'readline';
6
+ import { apiCall } from '../client.js';
7
+
8
+ const ROLE_PRESETS = {
9
+ backend: { scope: ['src/api/**', 'src/server/**', 'src/lib/**', 'src/db/**'] },
10
+ frontend: { scope: ['src/components/**', 'src/views/**', 'src/pages/**', 'src/styles/**'] },
11
+ devops: { scope: ['Dockerfile*', 'docker-compose*', '.github/**', 'infra/**'] },
12
+ testing: { scope: ['tests/**', 'test/**', '**/*.test.*', '**/*.spec.*'] },
13
+ docs: { scope: ['docs/**', '*.md', 'README*'] },
14
+ fullstack: { scope: [] },
15
+ };
16
+
17
+ export async function spawn(options) {
18
+ // If no role specified, run interactive picker
19
+ if (!options.role) {
20
+ return interactiveSpawn();
21
+ }
22
+
23
+ try {
24
+ const config = {
25
+ role: options.role,
26
+ scope: options.scope || ROLE_PRESETS[options.role]?.scope || [],
27
+ provider: options.provider || 'claude-code',
28
+ model: options.model || null,
29
+ prompt: options.prompt || null,
30
+ };
31
+
32
+ const agent = await apiCall('POST', '/api/agents', config);
33
+
34
+ console.log('');
35
+ console.log(chalk.green(' Agent spawned'));
36
+ console.log(` Name: ${chalk.bold(agent.name)}`);
37
+ console.log(` ID: ${chalk.dim(agent.id)}`);
38
+ console.log(` Role: ${agent.role}`);
39
+ console.log(` Provider: ${agent.provider}`);
40
+ console.log(` Scope: ${agent.scope.length > 0 ? agent.scope.join(', ') : chalk.dim('unrestricted')}`);
41
+ if (agent.prompt) {
42
+ console.log(` Prompt: ${agent.prompt.slice(0, 60)}${agent.prompt.length > 60 ? '...' : ''}`);
43
+ }
44
+ console.log('');
45
+ } catch (err) {
46
+ console.error(chalk.red('Failed to spawn:'), err.message);
47
+ process.exit(1);
48
+ }
49
+ }
50
+
51
+ async function interactiveSpawn() {
52
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
53
+ const ask = (q) => new Promise((res) => rl.question(q, res));
54
+
55
+ console.log('');
56
+ console.log(chalk.bold(' GROOVE — Spawn Agent'));
57
+ console.log('');
58
+ console.log(' Available roles:');
59
+ for (const [role, preset] of Object.entries(ROLE_PRESETS)) {
60
+ const scope = preset.scope.length > 0 ? chalk.dim(preset.scope.join(', ')) : chalk.dim('unrestricted');
61
+ console.log(` ${chalk.bold(role.padEnd(12))} ${scope}`);
62
+ }
63
+ console.log('');
64
+
65
+ const role = await ask(' Role: ');
66
+ if (!role.trim()) {
67
+ console.log(chalk.yellow(' Cancelled.'));
68
+ rl.close();
69
+ return;
70
+ }
71
+
72
+ const prompt = await ask(' Task prompt (optional): ');
73
+ rl.close();
74
+
75
+ const config = {
76
+ role: role.trim(),
77
+ scope: ROLE_PRESETS[role.trim()]?.scope || [],
78
+ provider: 'claude-code',
79
+ prompt: prompt.trim() || null,
80
+ };
81
+
82
+ try {
83
+ const agent = await apiCall('POST', '/api/agents', config);
84
+ console.log('');
85
+ console.log(chalk.green(` Spawned ${chalk.bold(agent.name)}`), chalk.dim(`(${agent.id})`));
86
+ console.log('');
87
+ } catch (err) {
88
+ console.error(chalk.red(' Failed:'), err.message);
89
+ process.exit(1);
90
+ }
91
+ }
@@ -0,0 +1,31 @@
1
+ // GROOVE CLI — start command
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import { Daemon } from '@groove-dev/daemon';
5
+ import chalk from 'chalk';
6
+
7
+ export async function start(options) {
8
+ console.log(chalk.bold('GROOVE') + ' starting daemon...');
9
+
10
+ try {
11
+ const daemon = new Daemon({ port: parseInt(options.port, 10) });
12
+
13
+ const shutdown = async () => {
14
+ console.log('\nShutting down...');
15
+ // Force exit after 3s if stop hangs
16
+ const forceTimer = setTimeout(() => process.exit(1), 3000);
17
+ forceTimer.unref();
18
+ try { await daemon.stop(); } catch { /* ignore */ }
19
+ process.exit(0);
20
+ };
21
+
22
+ process.on('SIGINT', shutdown);
23
+ process.on('SIGTERM', shutdown);
24
+
25
+ await daemon.start();
26
+ console.log(chalk.green('Ready.') + ` Open http://localhost:${options.port} for the GUI.`);
27
+ } catch (err) {
28
+ console.error(chalk.red('Failed to start:'), err.message);
29
+ process.exit(1);
30
+ }
31
+ }
@@ -0,0 +1,38 @@
1
+ // GROOVE CLI — status command
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import chalk from 'chalk';
5
+ import { apiCall } from '../client.js';
6
+
7
+ export async function status() {
8
+ try {
9
+ const s = await apiCall('GET', '/api/status');
10
+ console.log('');
11
+ console.log(chalk.bold(' GROOVE Daemon'));
12
+ console.log('');
13
+ console.log(` Status: ${chalk.green('running')}`);
14
+ console.log(` PID: ${s.pid}`);
15
+ console.log(` Port: ${s.port}`);
16
+ console.log(` Uptime: ${formatUptime(s.uptime)}`);
17
+ console.log(` Agents: ${s.agents} total, ${s.running} running`);
18
+ console.log(` Project: ${s.projectDir}`);
19
+ console.log(` GUI: ${chalk.cyan(`http://localhost:${s.port}`)}`);
20
+ console.log('');
21
+ } catch {
22
+ console.log('');
23
+ console.log(chalk.bold(' GROOVE Daemon'));
24
+ console.log('');
25
+ console.log(` Status: ${chalk.red('not running')}`);
26
+ console.log(` Start: ${chalk.dim('groove start')}`);
27
+ console.log('');
28
+ }
29
+ }
30
+
31
+ function formatUptime(seconds) {
32
+ const h = Math.floor(seconds / 3600);
33
+ const m = Math.floor((seconds % 3600) / 60);
34
+ const s = Math.floor(seconds % 60);
35
+ if (h > 0) return `${h}h ${m}m`;
36
+ if (m > 0) return `${m}m ${s}s`;
37
+ return `${s}s`;
38
+ }
@@ -0,0 +1,15 @@
1
+ // GROOVE CLI — stop command
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import chalk from 'chalk';
5
+ import { apiCall } from '../client.js';
6
+
7
+ export async function stop() {
8
+ try {
9
+ const status = await apiCall('GET', '/api/status');
10
+ process.kill(status.pid, 'SIGTERM');
11
+ console.log(chalk.green('GROOVE daemon stopped.'));
12
+ } catch {
13
+ console.log(chalk.yellow('No running daemon found.'));
14
+ }
15
+ }
@@ -0,0 +1,77 @@
1
+ // GROOVE CLI — team commands
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import chalk from 'chalk';
5
+ import { readFileSync } from 'fs';
6
+ import { apiCall } from '../client.js';
7
+
8
+ export async function teamSave(name) {
9
+ try {
10
+ const team = await apiCall('POST', '/api/teams', { name });
11
+ console.log(chalk.green(` Saved team "${team.name}"`) + ` (${team.agents.length} agents)`);
12
+ } catch (err) {
13
+ console.error(chalk.red(' Failed:'), err.message);
14
+ process.exit(1);
15
+ }
16
+ }
17
+
18
+ export async function teamLoad(name) {
19
+ try {
20
+ console.log(chalk.yellow(` Loading team "${name}"...`));
21
+ const result = await apiCall('POST', `/api/teams/${encodeURIComponent(name)}/load`);
22
+ console.log(chalk.green(` Loaded "${name}"`), `— ${result.agents.length} agents spawned`);
23
+ } catch (err) {
24
+ console.error(chalk.red(' Failed:'), err.message);
25
+ process.exit(1);
26
+ }
27
+ }
28
+
29
+ export async function teamList() {
30
+ try {
31
+ const { teams, activeTeam } = await apiCall('GET', '/api/teams');
32
+ if (teams.length === 0) {
33
+ console.log(chalk.dim(' No saved teams. Use `groove team save "name"` to create one.'));
34
+ return;
35
+ }
36
+ console.log(chalk.bold(`\n Saved Teams (${teams.length})\n`));
37
+ for (const t of teams) {
38
+ const active = t.name === activeTeam ? chalk.green(' (active)') : '';
39
+ console.log(` ${chalk.bold(t.name)}${active} — ${t.agents} agents — saved ${new Date(t.updatedAt).toLocaleDateString()}`);
40
+ }
41
+ console.log('');
42
+ } catch {
43
+ console.error(chalk.red(' Cannot connect to daemon.'));
44
+ process.exit(1);
45
+ }
46
+ }
47
+
48
+ export async function teamDelete(name) {
49
+ try {
50
+ await apiCall('DELETE', `/api/teams/${encodeURIComponent(name)}`);
51
+ console.log(chalk.green(` Deleted team "${name}"`));
52
+ } catch (err) {
53
+ console.error(chalk.red(' Failed:'), err.message);
54
+ process.exit(1);
55
+ }
56
+ }
57
+
58
+ export async function teamExport(name) {
59
+ try {
60
+ const data = await apiCall('GET', `/api/teams/${encodeURIComponent(name)}/export`);
61
+ console.log(JSON.stringify(data, null, 2));
62
+ } catch (err) {
63
+ console.error(chalk.red(' Failed:'), err.message);
64
+ process.exit(1);
65
+ }
66
+ }
67
+
68
+ export async function teamImport(filePath) {
69
+ try {
70
+ const content = readFileSync(filePath, 'utf8');
71
+ const team = await apiCall('POST', '/api/teams/import', JSON.parse(content));
72
+ console.log(chalk.green(` Imported team "${team.name}"`), `(${team.agents.length} agents)`);
73
+ } catch (err) {
74
+ console.error(chalk.red(' Failed:'), err.message);
75
+ process.exit(1);
76
+ }
77
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@groove-dev/daemon",
3
+ "version": "0.8.0",
4
+ "description": "GROOVE daemon — agent orchestration engine",
5
+ "license": "FSL-1.1-Apache-2.0",
6
+ "type": "module",
7
+ "main": "src/index.js",
8
+ "scripts": {
9
+ "dev": "node --watch src/index.js",
10
+ "start": "node src/index.js",
11
+ "test": "node --test test/*.test.js"
12
+ },
13
+ "dependencies": {
14
+ "ws": "^8.17.0",
15
+ "express": "^4.21.0",
16
+ "minimatch": "^10.0.0"
17
+ }
18
+ }