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.
- package/node_modules/@groove-dev/cli/src/setup.js +166 -49
- package/package.json +1 -1
- package/packages/cli/src/setup.js +166 -49
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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(' │') + '
|
|
89
|
-
console.log(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
|
|
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('
|
|
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('
|
|
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('
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
161
|
-
|
|
273
|
+
const selected = await selectMultiple(items, {
|
|
274
|
+
message: chalk.dim(' Select providers to install:'),
|
|
275
|
+
});
|
|
162
276
|
|
|
163
|
-
if (
|
|
277
|
+
if (selected.length > 0) {
|
|
164
278
|
console.log('');
|
|
165
|
-
for (const
|
|
166
|
-
const p =
|
|
167
|
-
|
|
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: '
|
|
170
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
|
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(` ✓
|
|
324
|
+
console.log(chalk.green(` ✓ saved`));
|
|
211
325
|
} else {
|
|
212
|
-
console.log(chalk.dim(` Skipped — set
|
|
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
|
-
|
|
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('
|
|
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('
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(' │') + '
|
|
89
|
-
console.log(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
|
|
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('
|
|
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('
|
|
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('
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
161
|
-
|
|
273
|
+
const selected = await selectMultiple(items, {
|
|
274
|
+
message: chalk.dim(' Select providers to install:'),
|
|
275
|
+
});
|
|
162
276
|
|
|
163
|
-
if (
|
|
277
|
+
if (selected.length > 0) {
|
|
164
278
|
console.log('');
|
|
165
|
-
for (const
|
|
166
|
-
const p =
|
|
167
|
-
|
|
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: '
|
|
170
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
|
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(` ✓
|
|
324
|
+
console.log(chalk.green(` ✓ saved`));
|
|
211
325
|
} else {
|
|
212
|
-
console.log(chalk.dim(` Skipped — set
|
|
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
|
-
|
|
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('
|
|
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('
|
|
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
|
|
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
|
|
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('');
|