groove-dev 0.22.15 → 0.22.16

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.
@@ -3,8 +3,20 @@
3
3
 
4
4
  import { createInterface } from 'readline';
5
5
  import { execSync, execFileSync } from 'child_process';
6
+ import { readFileSync } from 'fs';
7
+ import { resolve, dirname } from 'path';
8
+ import { fileURLToPath } from 'url';
6
9
  import chalk from 'chalk';
7
10
 
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+
13
+ function getVersion() {
14
+ try {
15
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, '../../package.json'), 'utf8'));
16
+ return pkg.version || '0.0.0';
17
+ } catch { return '0.0.0'; }
18
+ }
19
+
8
20
  const rl = () => createInterface({ input: process.stdin, output: process.stdout });
9
21
 
10
22
  function ask(prompt) {
@@ -18,7 +30,6 @@ function askMasked(prompt) {
18
30
  return new Promise((resolve) => {
19
31
  const r = rl();
20
32
  r.question(prompt, (answer) => { r.close(); resolve(answer.trim()); });
21
- // Mask input by overwriting with *
22
33
  r._writeToOutput = function (str) {
23
34
  if (str.includes('\n') || str.includes('\r')) {
24
35
  r.output.write('\n');
@@ -29,6 +40,110 @@ function askMasked(prompt) {
29
40
  });
30
41
  }
31
42
 
43
+ /**
44
+ * Interactive checkbox selector — arrow keys to navigate, space to toggle, enter to confirm.
45
+ */
46
+ function selectMultiple(items, { message = '', startLine = 0 } = {}) {
47
+ return new Promise((resolvePromise) => {
48
+ const selected = new Set();
49
+ let cursor = 0;
50
+
51
+ function render() {
52
+ // Move cursor up to overwrite previous render
53
+ if (startLine > 0) process.stdout.write(`\x1b[${items.length + 2}A`);
54
+
55
+ if (message) console.log(message);
56
+
57
+ for (let i = 0; i < items.length; i++) {
58
+ const item = items[i];
59
+ const isSelected = selected.has(i);
60
+ const isCursor = i === cursor;
61
+
62
+ const checkbox = isSelected ? chalk.cyan('◉') : chalk.dim('○');
63
+ const pointer = isCursor ? chalk.cyan('▸ ') : ' ';
64
+ const label = isCursor ? chalk.bold(item.label) : item.label;
65
+ const hint = item.hint ? chalk.dim(` — ${item.hint}`) : '';
66
+ const tag = item.tag ? ` ${item.tag}` : '';
67
+
68
+ console.log(` ${pointer}${checkbox} ${label}${tag}${hint}`);
69
+ }
70
+ console.log(chalk.dim(' ↑↓ navigate ␣ toggle ⏎ confirm'));
71
+ }
72
+
73
+ // Initial render
74
+ render();
75
+ startLine = 1; // After first render, always overwrite
76
+
77
+ const { stdin } = process;
78
+ stdin.setRawMode(true);
79
+ stdin.resume();
80
+ stdin.setEncoding('utf8');
81
+
82
+ function onKey(key) {
83
+ // Ctrl+C
84
+ if (key === '\x03') {
85
+ stdin.setRawMode(false);
86
+ stdin.removeListener('data', onKey);
87
+ stdin.pause();
88
+ process.exit(0);
89
+ }
90
+
91
+ // Enter — confirm
92
+ if (key === '\r' || key === '\n') {
93
+ stdin.setRawMode(false);
94
+ stdin.removeListener('data', onKey);
95
+ stdin.pause();
96
+ // Clear the hint line
97
+ process.stdout.write(`\x1b[${items.length + 2}A`);
98
+ // Final render with confirmed state
99
+ if (message) console.log(message);
100
+ for (let i = 0; i < items.length; i++) {
101
+ const item = items[i];
102
+ const isSelected = selected.has(i);
103
+ const checkbox = isSelected ? chalk.green('✓') : chalk.dim('·');
104
+ const label = isSelected ? chalk.bold(item.label) : chalk.dim(item.label);
105
+ console.log(` ${checkbox} ${label}`);
106
+ }
107
+ const count = selected.size;
108
+ console.log(count > 0 ? chalk.green(` ${count} selected`) : chalk.dim(' none selected — skipped'));
109
+ resolvePromise([...selected].map((i) => items[i]));
110
+ return;
111
+ }
112
+
113
+ // Space — toggle
114
+ if (key === ' ') {
115
+ if (selected.has(cursor)) selected.delete(cursor);
116
+ else selected.add(cursor);
117
+ render();
118
+ return;
119
+ }
120
+
121
+ // Arrow up
122
+ if (key === '\x1b[A' || key === 'k') {
123
+ cursor = (cursor - 1 + items.length) % items.length;
124
+ render();
125
+ return;
126
+ }
127
+
128
+ // Arrow down
129
+ if (key === '\x1b[B' || key === 'j') {
130
+ cursor = (cursor + 1) % items.length;
131
+ render();
132
+ return;
133
+ }
134
+
135
+ // 'a' — select all
136
+ if (key === 'a') {
137
+ if (selected.size === items.length) selected.clear();
138
+ else items.forEach((_, i) => selected.add(i));
139
+ render();
140
+ }
141
+ }
142
+
143
+ stdin.on('data', onKey);
144
+ });
145
+ }
146
+
32
147
  function cmd(command) {
33
148
  try {
34
149
  return execSync(command, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
@@ -49,7 +164,7 @@ const PROVIDERS = [
49
164
  cli: 'claude',
50
165
  install: 'npm i -g @anthropic-ai/claude-code',
51
166
  auth: 'subscription',
52
- description: 'Anthropic\'s CLI agent. Uses your Claude subscription (no API key needed).',
167
+ description: 'Anthropic\'s CLI agent subscription auth',
53
168
  recommended: true,
54
169
  },
55
170
  {
@@ -59,7 +174,7 @@ const PROVIDERS = [
59
174
  install: 'npm i -g @openai/codex',
60
175
  auth: 'api-key',
61
176
  envKey: 'OPENAI_API_KEY',
62
- description: 'OpenAI\'s coding agent. Requires an OpenAI API key.',
177
+ description: 'OpenAI\'s coding agent API key required',
63
178
  keyHelp: 'Get your key at https://platform.openai.com/api-keys',
64
179
  },
65
180
  {
@@ -69,7 +184,7 @@ const PROVIDERS = [
69
184
  install: 'npm i -g @google/gemini-cli',
70
185
  auth: 'api-key',
71
186
  envKey: 'GEMINI_API_KEY',
72
- description: 'Google\'s coding agent. Requires a Gemini API key.',
187
+ description: 'Google\'s coding agent API key required',
73
188
  keyHelp: 'Get your key at https://aistudio.google.com/apikey',
74
189
  },
75
190
  {
@@ -78,25 +193,29 @@ const PROVIDERS = [
78
193
  cli: 'ollama',
79
194
  install: process.platform === 'darwin' ? 'brew install ollama' : 'See https://ollama.ai/download',
80
195
  auth: 'local',
81
- description: 'Run models locally. No API key, no cloud. Requires 8GB+ RAM.',
196
+ description: 'Run models locally no API key, no cloud',
82
197
  },
83
198
  ];
84
199
 
85
200
  export async function runSetupWizard() {
201
+ const version = getVersion();
202
+
86
203
  console.log('');
87
204
  console.log(chalk.bold(' ┌──────────────────────────────────────────┐'));
88
- console.log(chalk.bold(' │') + ' Welcome to ' + chalk.bold.cyan('GROOVE') + ' ' + chalk.bold('│'));
89
- console.log(chalk.bold(' │') + ' Agent orchestration for AI coding ' + chalk.bold('│'));
205
+ console.log(chalk.bold(' │') + ' ' + chalk.bold('│'));
206
+ console.log(chalk.bold(' │') + ' ' + chalk.bold.cyan('G R O O V E') + ' ' + chalk.bold('│'));
207
+ console.log(chalk.bold(' │') + ' Agent Orchestration Layer ' + chalk.bold('│'));
208
+ console.log(chalk.bold(' │') + ' ' + chalk.bold('│'));
209
+ console.log(chalk.bold(' │') + chalk.dim(` v${version}`) + ' '.repeat(Math.max(0, 37 - version.length)) + chalk.bold('│'));
90
210
  console.log(chalk.bold(' └──────────────────────────────────────────┘'));
91
211
  console.log('');
92
- console.log(chalk.dim(' Let\'s get you set up. This takes about a minute.'));
212
+ console.log(chalk.dim(' First time? Let\'s get you set up in under a minute.'));
93
213
  console.log('');
94
214
 
95
215
  // ── Step 1: System check ────────────────────────────────────
96
- console.log(chalk.bold(' 1. System Check'));
216
+ console.log(chalk.bold.cyan(' ① ') + chalk.bold('System Check'));
97
217
  console.log('');
98
218
 
99
- // Node.js
100
219
  const nodeVersion = process.version;
101
220
  const nodeMajor = parseInt(nodeVersion.slice(1), 10);
102
221
  if (nodeMajor >= 20) {
@@ -107,7 +226,6 @@ export async function runSetupWizard() {
107
226
  process.exit(1);
108
227
  }
109
228
 
110
- // npm
111
229
  const npmVersion = cmd('npm --version');
112
230
  if (npmVersion) {
113
231
  console.log(chalk.green(' ✓') + ` npm ${npmVersion}`);
@@ -116,18 +234,16 @@ export async function runSetupWizard() {
116
234
  process.exit(1);
117
235
  }
118
236
 
119
- // git
120
237
  if (isInstalled('git')) {
121
238
  console.log(chalk.green(' ✓') + ` git ${cmd('git --version')?.replace('git version ', '') || ''}`);
122
239
  } else {
123
240
  console.log(chalk.yellow(' !') + ' git not found — agents may need it for version control');
124
- console.log(chalk.dim(' Install: https://git-scm.com'));
125
241
  }
126
242
 
127
243
  console.log('');
128
244
 
129
245
  // ── Step 2: Provider scan ───────────────────────────────────
130
- console.log(chalk.bold(' 2. AI Providers'));
246
+ console.log(chalk.bold.cyan(' ② ') + chalk.bold('AI Providers'));
131
247
  console.log('');
132
248
 
133
249
  const installed = [];
@@ -136,41 +252,39 @@ export async function runSetupWizard() {
136
252
  for (const p of PROVIDERS) {
137
253
  if (isInstalled(p.cli)) {
138
254
  installed.push(p);
139
- const rec = p.recommended ? chalk.cyan(' (recommended)') : '';
255
+ const rec = p.recommended ? chalk.cyan(' recommended') : '';
140
256
  console.log(chalk.green(' ✓') + ` ${p.name}${rec}`);
141
257
  } else {
142
258
  available.push(p);
143
259
  }
144
260
  }
145
261
 
146
- if (installed.length > 0 && available.length > 0) {
147
- console.log('');
148
- }
262
+ if (installed.length > 0 && available.length > 0) console.log('');
149
263
 
150
264
  // ── Step 3: Install missing providers ───────────────────────
151
265
  if (available.length > 0) {
152
- console.log(chalk.dim(' Available to install:'));
153
- available.forEach((p, i) => {
154
- const rec = p.recommended ? chalk.cyan(' (recommended)') : '';
155
- console.log(` ${chalk.bold(i + 1)}. ${p.name}${rec} — ${p.description}`);
156
- });
157
- console.log(` ${chalk.bold('0')}. Skip — I'll install later`);
158
- console.log('');
266
+ const items = available.map((p) => ({
267
+ label: p.name,
268
+ hint: p.description,
269
+ tag: p.recommended ? chalk.cyan('recommended') : '',
270
+ provider: p,
271
+ }));
159
272
 
160
- const answer = await ask(chalk.bold(' Which providers would you like to install? ') + chalk.dim('(e.g. 1,2 or 0 to skip) '));
161
- const selections = answer.split(/[,\s]+/).map((s) => parseInt(s, 10)).filter((n) => n > 0 && n <= available.length);
273
+ const selected = await selectMultiple(items, {
274
+ message: chalk.dim(' Select providers to install:'),
275
+ });
162
276
 
163
- if (selections.length > 0) {
277
+ if (selected.length > 0) {
164
278
  console.log('');
165
- for (const idx of selections) {
166
- const p = available[idx - 1];
167
- console.log(` Installing ${chalk.bold(p.name)}...`);
279
+ for (const item of selected) {
280
+ const p = item.provider;
281
+ process.stdout.write(` Installing ${chalk.bold(p.name)}...`);
168
282
  try {
169
- execSync(p.install, { stdio: 'inherit' });
170
- console.log(chalk.green(` ✓ ${p.name} installed`));
283
+ execSync(p.install, { stdio: ['pipe', 'pipe', 'pipe'] });
284
+ process.stdout.write(chalk.green(' done\n'));
171
285
  installed.push(p);
172
286
  } catch {
173
- console.log(chalk.red(` ✗ ${p.name} failed to install`));
287
+ process.stdout.write(chalk.red(' failed\n'));
174
288
  console.log(chalk.dim(` Try manually: ${p.install}`));
175
289
  }
176
290
  }
@@ -197,19 +311,19 @@ export async function runSetupWizard() {
197
311
  const keys = {};
198
312
 
199
313
  if (needsKey.length > 0) {
200
- console.log(chalk.bold(' 3. API Keys'));
314
+ console.log(chalk.bold.cyan(' ③ ') + chalk.bold('API Keys'));
201
315
  console.log('');
202
316
 
203
317
  for (const p of needsKey) {
204
318
  console.log(` ${chalk.bold(p.name)} requires an API key.`);
205
319
  if (p.keyHelp) console.log(chalk.dim(` ${p.keyHelp}`));
206
320
 
207
- const key = await askMasked(` Enter ${p.name} API key ${chalk.dim('(or press Enter to skip)')}: `);
321
+ const key = await askMasked(` Enter key ${chalk.dim('(or Enter to skip)')}: `);
208
322
  if (key) {
209
323
  keys[p.id] = key;
210
- console.log(chalk.green(` ✓ ${p.name} key saved`));
324
+ console.log(chalk.green(` ✓ saved`));
211
325
  } else {
212
- console.log(chalk.dim(` Skipped — set it later in Settings or: groove set-key ${p.id} <key>`));
326
+ console.log(chalk.dim(` Skipped — set later: groove set-key ${p.id} <key>`));
213
327
  }
214
328
  console.log('');
215
329
  }
@@ -218,28 +332,31 @@ export async function runSetupWizard() {
218
332
  // ── Step 5: Claude Code auth check ──────────────────────────
219
333
  const hasClaude = installed.some((p) => p.id === 'claude-code');
220
334
  if (hasClaude) {
221
- console.log(chalk.bold(` ${needsKey.length > 0 ? '4' : '3'}. Claude Code Auth`));
335
+ const stepNum = needsKey.length > 0 ? '' : '';
336
+ console.log(chalk.bold.cyan(` ${stepNum} `) + chalk.bold('Claude Code'));
222
337
  console.log('');
223
- console.log(chalk.dim(' Claude Code uses your Anthropic subscription (not API keys).'));
224
- console.log(chalk.dim(' If you haven\'t logged in yet, run `claude` in a terminal to authenticate.'));
338
+ console.log(chalk.dim(' Uses your Anthropic subscription no API key needed.'));
225
339
 
226
- // Quick check if claude is authenticated
227
340
  try {
228
341
  const out = cmd('claude --version');
229
- if (out) {
230
- console.log(chalk.green(' ✓') + ` Claude Code ${out} installed`);
231
- }
342
+ if (out) console.log(chalk.green(' ✓') + ` Claude Code ${out}`);
232
343
  } catch { /* ignore */ }
344
+
345
+ console.log(chalk.dim(' If not logged in yet, run `claude` to authenticate.'));
233
346
  console.log('');
234
347
  }
235
348
 
236
- // ── Done! ──────────────────────────────────────────────────
237
- console.log(chalk.bold(' Setup complete!'));
349
+ // ── Done ───────────────────────────────────────────────────
350
+ console.log(chalk.bold(' ─────────────────────────────────────────'));
351
+ console.log('');
352
+ console.log(chalk.bold.cyan(' Ready to go!'));
238
353
  console.log('');
239
- console.log(` Providers: ${installed.map((p) => p.name).join(', ') || 'none'}`);
354
+ console.log(` Providers ${installed.map((p) => chalk.bold(p.name)).join(chalk.dim(', ')) || chalk.dim('none')}`);
240
355
  if (Object.keys(keys).length > 0) {
241
- console.log(` Keys configured: ${Object.keys(keys).map((k) => PROVIDERS.find((p) => p.id === k)?.name || k).join(', ')}`);
356
+ console.log(` Keys ${Object.keys(keys).map((k) => chalk.bold(PROVIDERS.find((p) => p.id === k)?.name || k)).join(chalk.dim(', '))}`);
242
357
  }
358
+ console.log(` Dashboard ${chalk.cyan('http://localhost:31415')}`);
359
+ console.log(` Docs ${chalk.dim('https://docs.groovedev.ai')}`);
243
360
  console.log('');
244
361
  console.log(chalk.dim(' Starting daemon...'));
245
362
  console.log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.22.15",
3
+ "version": "0.22.16",
4
4
  "description": "Open-source agent orchestration layer — the AI company OS. 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.",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
@@ -3,8 +3,20 @@
3
3
 
4
4
  import { createInterface } from 'readline';
5
5
  import { execSync, execFileSync } from 'child_process';
6
+ import { readFileSync } from 'fs';
7
+ import { resolve, dirname } from 'path';
8
+ import { fileURLToPath } from 'url';
6
9
  import chalk from 'chalk';
7
10
 
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+
13
+ function getVersion() {
14
+ try {
15
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, '../../package.json'), 'utf8'));
16
+ return pkg.version || '0.0.0';
17
+ } catch { return '0.0.0'; }
18
+ }
19
+
8
20
  const rl = () => createInterface({ input: process.stdin, output: process.stdout });
9
21
 
10
22
  function ask(prompt) {
@@ -18,7 +30,6 @@ function askMasked(prompt) {
18
30
  return new Promise((resolve) => {
19
31
  const r = rl();
20
32
  r.question(prompt, (answer) => { r.close(); resolve(answer.trim()); });
21
- // Mask input by overwriting with *
22
33
  r._writeToOutput = function (str) {
23
34
  if (str.includes('\n') || str.includes('\r')) {
24
35
  r.output.write('\n');
@@ -29,6 +40,110 @@ function askMasked(prompt) {
29
40
  });
30
41
  }
31
42
 
43
+ /**
44
+ * Interactive checkbox selector — arrow keys to navigate, space to toggle, enter to confirm.
45
+ */
46
+ function selectMultiple(items, { message = '', startLine = 0 } = {}) {
47
+ return new Promise((resolvePromise) => {
48
+ const selected = new Set();
49
+ let cursor = 0;
50
+
51
+ function render() {
52
+ // Move cursor up to overwrite previous render
53
+ if (startLine > 0) process.stdout.write(`\x1b[${items.length + 2}A`);
54
+
55
+ if (message) console.log(message);
56
+
57
+ for (let i = 0; i < items.length; i++) {
58
+ const item = items[i];
59
+ const isSelected = selected.has(i);
60
+ const isCursor = i === cursor;
61
+
62
+ const checkbox = isSelected ? chalk.cyan('◉') : chalk.dim('○');
63
+ const pointer = isCursor ? chalk.cyan('▸ ') : ' ';
64
+ const label = isCursor ? chalk.bold(item.label) : item.label;
65
+ const hint = item.hint ? chalk.dim(` — ${item.hint}`) : '';
66
+ const tag = item.tag ? ` ${item.tag}` : '';
67
+
68
+ console.log(` ${pointer}${checkbox} ${label}${tag}${hint}`);
69
+ }
70
+ console.log(chalk.dim(' ↑↓ navigate ␣ toggle ⏎ confirm'));
71
+ }
72
+
73
+ // Initial render
74
+ render();
75
+ startLine = 1; // After first render, always overwrite
76
+
77
+ const { stdin } = process;
78
+ stdin.setRawMode(true);
79
+ stdin.resume();
80
+ stdin.setEncoding('utf8');
81
+
82
+ function onKey(key) {
83
+ // Ctrl+C
84
+ if (key === '\x03') {
85
+ stdin.setRawMode(false);
86
+ stdin.removeListener('data', onKey);
87
+ stdin.pause();
88
+ process.exit(0);
89
+ }
90
+
91
+ // Enter — confirm
92
+ if (key === '\r' || key === '\n') {
93
+ stdin.setRawMode(false);
94
+ stdin.removeListener('data', onKey);
95
+ stdin.pause();
96
+ // Clear the hint line
97
+ process.stdout.write(`\x1b[${items.length + 2}A`);
98
+ // Final render with confirmed state
99
+ if (message) console.log(message);
100
+ for (let i = 0; i < items.length; i++) {
101
+ const item = items[i];
102
+ const isSelected = selected.has(i);
103
+ const checkbox = isSelected ? chalk.green('✓') : chalk.dim('·');
104
+ const label = isSelected ? chalk.bold(item.label) : chalk.dim(item.label);
105
+ console.log(` ${checkbox} ${label}`);
106
+ }
107
+ const count = selected.size;
108
+ console.log(count > 0 ? chalk.green(` ${count} selected`) : chalk.dim(' none selected — skipped'));
109
+ resolvePromise([...selected].map((i) => items[i]));
110
+ return;
111
+ }
112
+
113
+ // Space — toggle
114
+ if (key === ' ') {
115
+ if (selected.has(cursor)) selected.delete(cursor);
116
+ else selected.add(cursor);
117
+ render();
118
+ return;
119
+ }
120
+
121
+ // Arrow up
122
+ if (key === '\x1b[A' || key === 'k') {
123
+ cursor = (cursor - 1 + items.length) % items.length;
124
+ render();
125
+ return;
126
+ }
127
+
128
+ // Arrow down
129
+ if (key === '\x1b[B' || key === 'j') {
130
+ cursor = (cursor + 1) % items.length;
131
+ render();
132
+ return;
133
+ }
134
+
135
+ // 'a' — select all
136
+ if (key === 'a') {
137
+ if (selected.size === items.length) selected.clear();
138
+ else items.forEach((_, i) => selected.add(i));
139
+ render();
140
+ }
141
+ }
142
+
143
+ stdin.on('data', onKey);
144
+ });
145
+ }
146
+
32
147
  function cmd(command) {
33
148
  try {
34
149
  return execSync(command, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
@@ -49,7 +164,7 @@ const PROVIDERS = [
49
164
  cli: 'claude',
50
165
  install: 'npm i -g @anthropic-ai/claude-code',
51
166
  auth: 'subscription',
52
- description: 'Anthropic\'s CLI agent. Uses your Claude subscription (no API key needed).',
167
+ description: 'Anthropic\'s CLI agent subscription auth',
53
168
  recommended: true,
54
169
  },
55
170
  {
@@ -59,7 +174,7 @@ const PROVIDERS = [
59
174
  install: 'npm i -g @openai/codex',
60
175
  auth: 'api-key',
61
176
  envKey: 'OPENAI_API_KEY',
62
- description: 'OpenAI\'s coding agent. Requires an OpenAI API key.',
177
+ description: 'OpenAI\'s coding agent API key required',
63
178
  keyHelp: 'Get your key at https://platform.openai.com/api-keys',
64
179
  },
65
180
  {
@@ -69,7 +184,7 @@ const PROVIDERS = [
69
184
  install: 'npm i -g @google/gemini-cli',
70
185
  auth: 'api-key',
71
186
  envKey: 'GEMINI_API_KEY',
72
- description: 'Google\'s coding agent. Requires a Gemini API key.',
187
+ description: 'Google\'s coding agent API key required',
73
188
  keyHelp: 'Get your key at https://aistudio.google.com/apikey',
74
189
  },
75
190
  {
@@ -78,25 +193,29 @@ const PROVIDERS = [
78
193
  cli: 'ollama',
79
194
  install: process.platform === 'darwin' ? 'brew install ollama' : 'See https://ollama.ai/download',
80
195
  auth: 'local',
81
- description: 'Run models locally. No API key, no cloud. Requires 8GB+ RAM.',
196
+ description: 'Run models locally no API key, no cloud',
82
197
  },
83
198
  ];
84
199
 
85
200
  export async function runSetupWizard() {
201
+ const version = getVersion();
202
+
86
203
  console.log('');
87
204
  console.log(chalk.bold(' ┌──────────────────────────────────────────┐'));
88
- console.log(chalk.bold(' │') + ' Welcome to ' + chalk.bold.cyan('GROOVE') + ' ' + chalk.bold('│'));
89
- console.log(chalk.bold(' │') + ' Agent orchestration for AI coding ' + chalk.bold('│'));
205
+ console.log(chalk.bold(' │') + ' ' + chalk.bold('│'));
206
+ console.log(chalk.bold(' │') + ' ' + chalk.bold.cyan('G R O O V E') + ' ' + chalk.bold('│'));
207
+ console.log(chalk.bold(' │') + ' Agent Orchestration Layer ' + chalk.bold('│'));
208
+ console.log(chalk.bold(' │') + ' ' + chalk.bold('│'));
209
+ console.log(chalk.bold(' │') + chalk.dim(` v${version}`) + ' '.repeat(Math.max(0, 37 - version.length)) + chalk.bold('│'));
90
210
  console.log(chalk.bold(' └──────────────────────────────────────────┘'));
91
211
  console.log('');
92
- console.log(chalk.dim(' Let\'s get you set up. This takes about a minute.'));
212
+ console.log(chalk.dim(' First time? Let\'s get you set up in under a minute.'));
93
213
  console.log('');
94
214
 
95
215
  // ── Step 1: System check ────────────────────────────────────
96
- console.log(chalk.bold(' 1. System Check'));
216
+ console.log(chalk.bold.cyan(' ① ') + chalk.bold('System Check'));
97
217
  console.log('');
98
218
 
99
- // Node.js
100
219
  const nodeVersion = process.version;
101
220
  const nodeMajor = parseInt(nodeVersion.slice(1), 10);
102
221
  if (nodeMajor >= 20) {
@@ -107,7 +226,6 @@ export async function runSetupWizard() {
107
226
  process.exit(1);
108
227
  }
109
228
 
110
- // npm
111
229
  const npmVersion = cmd('npm --version');
112
230
  if (npmVersion) {
113
231
  console.log(chalk.green(' ✓') + ` npm ${npmVersion}`);
@@ -116,18 +234,16 @@ export async function runSetupWizard() {
116
234
  process.exit(1);
117
235
  }
118
236
 
119
- // git
120
237
  if (isInstalled('git')) {
121
238
  console.log(chalk.green(' ✓') + ` git ${cmd('git --version')?.replace('git version ', '') || ''}`);
122
239
  } else {
123
240
  console.log(chalk.yellow(' !') + ' git not found — agents may need it for version control');
124
- console.log(chalk.dim(' Install: https://git-scm.com'));
125
241
  }
126
242
 
127
243
  console.log('');
128
244
 
129
245
  // ── Step 2: Provider scan ───────────────────────────────────
130
- console.log(chalk.bold(' 2. AI Providers'));
246
+ console.log(chalk.bold.cyan(' ② ') + chalk.bold('AI Providers'));
131
247
  console.log('');
132
248
 
133
249
  const installed = [];
@@ -136,41 +252,39 @@ export async function runSetupWizard() {
136
252
  for (const p of PROVIDERS) {
137
253
  if (isInstalled(p.cli)) {
138
254
  installed.push(p);
139
- const rec = p.recommended ? chalk.cyan(' (recommended)') : '';
255
+ const rec = p.recommended ? chalk.cyan(' recommended') : '';
140
256
  console.log(chalk.green(' ✓') + ` ${p.name}${rec}`);
141
257
  } else {
142
258
  available.push(p);
143
259
  }
144
260
  }
145
261
 
146
- if (installed.length > 0 && available.length > 0) {
147
- console.log('');
148
- }
262
+ if (installed.length > 0 && available.length > 0) console.log('');
149
263
 
150
264
  // ── Step 3: Install missing providers ───────────────────────
151
265
  if (available.length > 0) {
152
- console.log(chalk.dim(' Available to install:'));
153
- available.forEach((p, i) => {
154
- const rec = p.recommended ? chalk.cyan(' (recommended)') : '';
155
- console.log(` ${chalk.bold(i + 1)}. ${p.name}${rec} — ${p.description}`);
156
- });
157
- console.log(` ${chalk.bold('0')}. Skip — I'll install later`);
158
- console.log('');
266
+ const items = available.map((p) => ({
267
+ label: p.name,
268
+ hint: p.description,
269
+ tag: p.recommended ? chalk.cyan('recommended') : '',
270
+ provider: p,
271
+ }));
159
272
 
160
- const answer = await ask(chalk.bold(' Which providers would you like to install? ') + chalk.dim('(e.g. 1,2 or 0 to skip) '));
161
- const selections = answer.split(/[,\s]+/).map((s) => parseInt(s, 10)).filter((n) => n > 0 && n <= available.length);
273
+ const selected = await selectMultiple(items, {
274
+ message: chalk.dim(' Select providers to install:'),
275
+ });
162
276
 
163
- if (selections.length > 0) {
277
+ if (selected.length > 0) {
164
278
  console.log('');
165
- for (const idx of selections) {
166
- const p = available[idx - 1];
167
- console.log(` Installing ${chalk.bold(p.name)}...`);
279
+ for (const item of selected) {
280
+ const p = item.provider;
281
+ process.stdout.write(` Installing ${chalk.bold(p.name)}...`);
168
282
  try {
169
- execSync(p.install, { stdio: 'inherit' });
170
- console.log(chalk.green(` ✓ ${p.name} installed`));
283
+ execSync(p.install, { stdio: ['pipe', 'pipe', 'pipe'] });
284
+ process.stdout.write(chalk.green(' done\n'));
171
285
  installed.push(p);
172
286
  } catch {
173
- console.log(chalk.red(` ✗ ${p.name} failed to install`));
287
+ process.stdout.write(chalk.red(' failed\n'));
174
288
  console.log(chalk.dim(` Try manually: ${p.install}`));
175
289
  }
176
290
  }
@@ -197,19 +311,19 @@ export async function runSetupWizard() {
197
311
  const keys = {};
198
312
 
199
313
  if (needsKey.length > 0) {
200
- console.log(chalk.bold(' 3. API Keys'));
314
+ console.log(chalk.bold.cyan(' ③ ') + chalk.bold('API Keys'));
201
315
  console.log('');
202
316
 
203
317
  for (const p of needsKey) {
204
318
  console.log(` ${chalk.bold(p.name)} requires an API key.`);
205
319
  if (p.keyHelp) console.log(chalk.dim(` ${p.keyHelp}`));
206
320
 
207
- const key = await askMasked(` Enter ${p.name} API key ${chalk.dim('(or press Enter to skip)')}: `);
321
+ const key = await askMasked(` Enter key ${chalk.dim('(or Enter to skip)')}: `);
208
322
  if (key) {
209
323
  keys[p.id] = key;
210
- console.log(chalk.green(` ✓ ${p.name} key saved`));
324
+ console.log(chalk.green(` ✓ saved`));
211
325
  } else {
212
- console.log(chalk.dim(` Skipped — set it later in Settings or: groove set-key ${p.id} <key>`));
326
+ console.log(chalk.dim(` Skipped — set later: groove set-key ${p.id} <key>`));
213
327
  }
214
328
  console.log('');
215
329
  }
@@ -218,28 +332,31 @@ export async function runSetupWizard() {
218
332
  // ── Step 5: Claude Code auth check ──────────────────────────
219
333
  const hasClaude = installed.some((p) => p.id === 'claude-code');
220
334
  if (hasClaude) {
221
- console.log(chalk.bold(` ${needsKey.length > 0 ? '4' : '3'}. Claude Code Auth`));
335
+ const stepNum = needsKey.length > 0 ? '' : '';
336
+ console.log(chalk.bold.cyan(` ${stepNum} `) + chalk.bold('Claude Code'));
222
337
  console.log('');
223
- console.log(chalk.dim(' Claude Code uses your Anthropic subscription (not API keys).'));
224
- console.log(chalk.dim(' If you haven\'t logged in yet, run `claude` in a terminal to authenticate.'));
338
+ console.log(chalk.dim(' Uses your Anthropic subscription no API key needed.'));
225
339
 
226
- // Quick check if claude is authenticated
227
340
  try {
228
341
  const out = cmd('claude --version');
229
- if (out) {
230
- console.log(chalk.green(' ✓') + ` Claude Code ${out} installed`);
231
- }
342
+ if (out) console.log(chalk.green(' ✓') + ` Claude Code ${out}`);
232
343
  } catch { /* ignore */ }
344
+
345
+ console.log(chalk.dim(' If not logged in yet, run `claude` to authenticate.'));
233
346
  console.log('');
234
347
  }
235
348
 
236
- // ── Done! ──────────────────────────────────────────────────
237
- console.log(chalk.bold(' Setup complete!'));
349
+ // ── Done ───────────────────────────────────────────────────
350
+ console.log(chalk.bold(' ─────────────────────────────────────────'));
351
+ console.log('');
352
+ console.log(chalk.bold.cyan(' Ready to go!'));
238
353
  console.log('');
239
- console.log(` Providers: ${installed.map((p) => p.name).join(', ') || 'none'}`);
354
+ console.log(` Providers ${installed.map((p) => chalk.bold(p.name)).join(chalk.dim(', ')) || chalk.dim('none')}`);
240
355
  if (Object.keys(keys).length > 0) {
241
- console.log(` Keys configured: ${Object.keys(keys).map((k) => PROVIDERS.find((p) => p.id === k)?.name || k).join(', ')}`);
356
+ console.log(` Keys ${Object.keys(keys).map((k) => chalk.bold(PROVIDERS.find((p) => p.id === k)?.name || k)).join(chalk.dim(', '))}`);
242
357
  }
358
+ console.log(` Dashboard ${chalk.cyan('http://localhost:31415')}`);
359
+ console.log(` Docs ${chalk.dim('https://docs.groovedev.ai')}`);
243
360
  console.log('');
244
361
  console.log(chalk.dim(' Starting daemon...'));
245
362
  console.log('');