popeye-cli 1.1.0 → 1.2.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/.env.example +24 -1
- package/CONTRIBUTING.md +275 -0
- package/OPEN_SOURCE_MANIFESTO.md +172 -0
- package/README.md +340 -27
- package/dist/adapters/claude.d.ts +5 -2
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +239 -19
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/grok.d.ts +73 -0
- package/dist/adapters/grok.d.ts.map +1 -0
- package/dist/adapters/grok.js +430 -0
- package/dist/adapters/grok.js.map +1 -0
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +6 -1
- package/dist/adapters/openai.js.map +1 -1
- package/dist/auth/grok.d.ts +73 -0
- package/dist/auth/grok.d.ts.map +1 -0
- package/dist/auth/grok.js +211 -0
- package/dist/auth/grok.js.map +1 -0
- package/dist/auth/index.d.ts +9 -6
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +23 -6
- package/dist/auth/index.js.map +1 -1
- package/dist/cli/commands/auth.d.ts +1 -1
- package/dist/cli/commands/auth.d.ts.map +1 -1
- package/dist/cli/commands/auth.js +79 -8
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +15 -4
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +374 -35
- package/dist/cli/interactive.js.map +1 -1
- package/dist/config/defaults.d.ts +3 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +9 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +16 -3
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +27 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +24 -3
- package/dist/config/schema.js.map +1 -1
- package/dist/generators/fullstack.d.ts +32 -0
- package/dist/generators/fullstack.d.ts.map +1 -0
- package/dist/generators/fullstack.js +497 -0
- package/dist/generators/fullstack.js.map +1 -0
- package/dist/generators/index.d.ts +4 -3
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +15 -1
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/python.d.ts +17 -1
- package/dist/generators/python.d.ts.map +1 -1
- package/dist/generators/python.js +34 -21
- package/dist/generators/python.js.map +1 -1
- package/dist/generators/templates/fullstack.d.ts +113 -0
- package/dist/generators/templates/fullstack.d.ts.map +1 -0
- package/dist/generators/templates/fullstack.js +1004 -0
- package/dist/generators/templates/fullstack.js.map +1 -0
- package/dist/generators/typescript.d.ts +19 -1
- package/dist/generators/typescript.d.ts.map +1 -1
- package/dist/generators/typescript.js +37 -21
- package/dist/generators/typescript.js.map +1 -1
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/cli.js.map +1 -1
- package/dist/types/consensus.d.ts +119 -2
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +12 -1
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/project.d.ts +76 -0
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +1 -1
- package/dist/types/project.js.map +1 -1
- package/dist/types/workflow.d.ts +162 -16
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +24 -1
- package/dist/types/workflow.js.map +1 -1
- package/dist/workflow/consensus.d.ts +29 -3
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js +334 -27
- package/dist/workflow/consensus.js.map +1 -1
- package/dist/workflow/milestone-workflow.js +2 -2
- package/dist/workflow/milestone-workflow.js.map +1 -1
- package/dist/workflow/plan-mode.d.ts +66 -2
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +187 -11
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/plan-storage.d.ts +252 -8
- package/dist/workflow/plan-storage.d.ts.map +1 -1
- package/dist/workflow/plan-storage.js +580 -33
- package/dist/workflow/plan-storage.js.map +1 -1
- package/dist/workflow/project-verification.js +1 -1
- package/dist/workflow/project-verification.js.map +1 -1
- package/dist/workflow/task-workflow.d.ts.map +1 -1
- package/dist/workflow/task-workflow.js +4 -1
- package/dist/workflow/task-workflow.js.map +1 -1
- package/dist/workflow/test-runner.d.ts +8 -0
- package/dist/workflow/test-runner.d.ts.map +1 -1
- package/dist/workflow/test-runner.js +92 -0
- package/dist/workflow/test-runner.js.map +1 -1
- package/dist/workflow/workspace-manager.d.ts +342 -0
- package/dist/workflow/workspace-manager.d.ts.map +1 -0
- package/dist/workflow/workspace-manager.js +733 -0
- package/dist/workflow/workspace-manager.js.map +1 -0
- package/package.json +1 -1
- package/src/adapters/claude.ts +263 -24
- package/src/adapters/grok.ts +492 -0
- package/src/adapters/openai.ts +8 -2
- package/src/auth/grok.ts +255 -0
- package/src/auth/index.ts +27 -9
- package/src/cli/commands/auth.ts +89 -10
- package/src/cli/commands/create.ts +13 -4
- package/src/cli/interactive.ts +424 -34
- package/src/config/defaults.ts +9 -0
- package/src/config/index.ts +17 -3
- package/src/config/schema.ts +25 -3
- package/src/generators/fullstack.ts +551 -0
- package/src/generators/index.ts +25 -1
- package/src/generators/python.ts +65 -21
- package/src/generators/templates/fullstack.ts +1047 -0
- package/src/generators/typescript.ts +69 -21
- package/src/types/cli.ts +4 -0
- package/src/types/consensus.ts +135 -3
- package/src/types/project.ts +82 -1
- package/src/types/workflow.ts +56 -2
- package/src/workflow/consensus.ts +461 -31
- package/src/workflow/milestone-workflow.ts +2 -2
- package/src/workflow/plan-mode.ts +238 -10
- package/src/workflow/plan-storage.ts +835 -35
- package/src/workflow/project-verification.ts +1 -1
- package/src/workflow/task-workflow.ts +4 -1
- package/src/workflow/test-runner.ts +110 -0
- package/src/workflow/workspace-manager.ts +912 -0
package/src/cli/interactive.ts
CHANGED
|
@@ -11,9 +11,11 @@ import {
|
|
|
11
11
|
authenticateClaude,
|
|
12
12
|
authenticateOpenAI,
|
|
13
13
|
authenticateGemini,
|
|
14
|
+
authenticateGrok,
|
|
14
15
|
isClaudeCLIInstalled,
|
|
15
16
|
checkClaudeCLIAuth,
|
|
16
17
|
checkGeminiAuth,
|
|
18
|
+
checkGrokAuth,
|
|
17
19
|
} from '../auth/index.js';
|
|
18
20
|
import {
|
|
19
21
|
runWorkflow,
|
|
@@ -46,6 +48,177 @@ import {
|
|
|
46
48
|
theme,
|
|
47
49
|
} from './output.js';
|
|
48
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Project-local configuration stored in popeye.md
|
|
53
|
+
*/
|
|
54
|
+
interface PopeyeProjectConfig {
|
|
55
|
+
language: OutputLanguage;
|
|
56
|
+
reviewer: AIProvider;
|
|
57
|
+
arbitrator: AIProvider;
|
|
58
|
+
enableArbitration: boolean;
|
|
59
|
+
created: string;
|
|
60
|
+
lastRun: string;
|
|
61
|
+
projectName?: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
notes?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Read popeye.md from project directory
|
|
68
|
+
* Returns null if file doesn't exist
|
|
69
|
+
*/
|
|
70
|
+
async function readPopeyeConfig(projectDir: string): Promise<PopeyeProjectConfig | null> {
|
|
71
|
+
const configPath = path.join(projectDir, 'popeye.md');
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
75
|
+
|
|
76
|
+
// Parse YAML frontmatter
|
|
77
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
78
|
+
if (!frontmatterMatch) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const frontmatter = frontmatterMatch[1];
|
|
83
|
+
const config: Partial<PopeyeProjectConfig> = {};
|
|
84
|
+
|
|
85
|
+
// Parse each line of YAML
|
|
86
|
+
for (const line of frontmatter.split('\n')) {
|
|
87
|
+
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
88
|
+
if (match) {
|
|
89
|
+
const [, key, value] = match;
|
|
90
|
+
const cleanValue = value.trim();
|
|
91
|
+
|
|
92
|
+
switch (key) {
|
|
93
|
+
case 'language':
|
|
94
|
+
if (['python', 'typescript', 'fullstack'].includes(cleanValue)) {
|
|
95
|
+
config.language = cleanValue as OutputLanguage;
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
case 'reviewer':
|
|
99
|
+
if (['openai', 'gemini', 'grok'].includes(cleanValue)) {
|
|
100
|
+
config.reviewer = cleanValue as AIProvider;
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
case 'arbitrator':
|
|
104
|
+
if (['openai', 'gemini', 'grok', 'off'].includes(cleanValue)) {
|
|
105
|
+
if (cleanValue === 'off') {
|
|
106
|
+
config.enableArbitration = false;
|
|
107
|
+
} else {
|
|
108
|
+
config.arbitrator = cleanValue as AIProvider;
|
|
109
|
+
config.enableArbitration = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
case 'created':
|
|
114
|
+
config.created = cleanValue;
|
|
115
|
+
break;
|
|
116
|
+
case 'lastRun':
|
|
117
|
+
config.lastRun = cleanValue;
|
|
118
|
+
break;
|
|
119
|
+
case 'projectName':
|
|
120
|
+
config.projectName = cleanValue;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Extract notes section if present
|
|
127
|
+
const notesMatch = content.match(/## Notes\n([\s\S]*?)(?=\n## |$)/);
|
|
128
|
+
if (notesMatch) {
|
|
129
|
+
config.notes = notesMatch[1].trim();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Return config only if we have the essential fields
|
|
133
|
+
if (config.language && config.reviewer) {
|
|
134
|
+
return {
|
|
135
|
+
language: config.language,
|
|
136
|
+
reviewer: config.reviewer,
|
|
137
|
+
arbitrator: config.arbitrator || 'gemini',
|
|
138
|
+
enableArbitration: config.enableArbitration ?? true,
|
|
139
|
+
created: config.created || new Date().toISOString(),
|
|
140
|
+
lastRun: config.lastRun || new Date().toISOString(),
|
|
141
|
+
projectName: config.projectName,
|
|
142
|
+
description: config.description,
|
|
143
|
+
notes: config.notes,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return null;
|
|
148
|
+
} catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Write popeye.md to project directory
|
|
155
|
+
*/
|
|
156
|
+
async function writePopeyeConfig(
|
|
157
|
+
projectDir: string,
|
|
158
|
+
config: PopeyeProjectConfig
|
|
159
|
+
): Promise<void> {
|
|
160
|
+
const configPath = path.join(projectDir, 'popeye.md');
|
|
161
|
+
|
|
162
|
+
const content = `---
|
|
163
|
+
# Popeye Project Configuration
|
|
164
|
+
language: ${config.language}
|
|
165
|
+
reviewer: ${config.reviewer}
|
|
166
|
+
arbitrator: ${config.enableArbitration ? config.arbitrator : 'off'}
|
|
167
|
+
created: ${config.created}
|
|
168
|
+
lastRun: ${new Date().toISOString()}
|
|
169
|
+
${config.projectName ? `projectName: ${config.projectName}` : ''}
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
# ${config.projectName || 'Popeye Project'}
|
|
173
|
+
|
|
174
|
+
${config.description ? `## Description\n${config.description}\n` : ''}
|
|
175
|
+
## Notes
|
|
176
|
+
${config.notes || 'Add any guidance or notes for Claude here...'}
|
|
177
|
+
|
|
178
|
+
## Configuration
|
|
179
|
+
- **Language**: ${config.language}
|
|
180
|
+
- **Reviewer**: ${config.reviewer}
|
|
181
|
+
- **Arbitrator**: ${config.enableArbitration ? config.arbitrator : 'disabled'}
|
|
182
|
+
|
|
183
|
+
## Session History
|
|
184
|
+
- ${config.created.split('T')[0]}: Project created
|
|
185
|
+
- ${new Date().toISOString().split('T')[0]}: Last session
|
|
186
|
+
`;
|
|
187
|
+
|
|
188
|
+
await fs.writeFile(configPath, content, 'utf-8');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Update lastRun in popeye.md without changing other content
|
|
193
|
+
*/
|
|
194
|
+
async function updatePopeyeLastRun(projectDir: string): Promise<void> {
|
|
195
|
+
const configPath = path.join(projectDir, 'popeye.md');
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
let content = await fs.readFile(configPath, 'utf-8');
|
|
199
|
+
|
|
200
|
+
// Update lastRun in frontmatter
|
|
201
|
+
content = content.replace(
|
|
202
|
+
/lastRun:\s*.+/,
|
|
203
|
+
`lastRun: ${new Date().toISOString()}`
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
await fs.writeFile(configPath, content, 'utf-8');
|
|
207
|
+
} catch {
|
|
208
|
+
// File doesn't exist, ignore
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Apply popeye.md config to session state
|
|
214
|
+
*/
|
|
215
|
+
function applyPopeyeConfig(state: SessionState, config: PopeyeProjectConfig): void {
|
|
216
|
+
state.language = config.language;
|
|
217
|
+
state.reviewer = config.reviewer;
|
|
218
|
+
state.arbitrator = config.arbitrator;
|
|
219
|
+
state.enableArbitration = config.enableArbitration;
|
|
220
|
+
}
|
|
221
|
+
|
|
49
222
|
// Note: startSpinner, succeedSpinner, failSpinner, stopSpinner are used in handleIdea
|
|
50
223
|
|
|
51
224
|
/**
|
|
@@ -73,6 +246,7 @@ interface SessionState {
|
|
|
73
246
|
claudeAuth: boolean;
|
|
74
247
|
openaiAuth: boolean;
|
|
75
248
|
geminiAuth: boolean;
|
|
249
|
+
grokAuth: boolean;
|
|
76
250
|
reviewer: AIProvider;
|
|
77
251
|
arbitrator: AIProvider;
|
|
78
252
|
enableArbitration: boolean;
|
|
@@ -128,7 +302,7 @@ function drawInputBoxTop(state: SessionState): void {
|
|
|
128
302
|
|
|
129
303
|
// Hints line (above the box)
|
|
130
304
|
const hints = [
|
|
131
|
-
theme.dim('/lang ') + theme.primary('py') + theme.dim('|') + theme.primary('ts'),
|
|
305
|
+
theme.dim('/lang ') + theme.primary('py') + theme.dim('|') + theme.primary('ts') + theme.dim('|') + theme.primary('fs'),
|
|
132
306
|
theme.dim('/config'),
|
|
133
307
|
theme.dim('/help'),
|
|
134
308
|
theme.dim('/exit'),
|
|
@@ -136,10 +310,18 @@ function drawInputBoxTop(state: SessionState): void {
|
|
|
136
310
|
console.log(' ' + hints.join(' '));
|
|
137
311
|
|
|
138
312
|
// Status items for the top line
|
|
139
|
-
const langStatus = state.language;
|
|
140
|
-
const reviewerStatus = state.reviewer === 'openai' ? 'O' : 'G';
|
|
141
|
-
const arbitratorStatus = state.enableArbitration
|
|
142
|
-
|
|
313
|
+
const langStatus = state.language === 'fullstack' ? 'fs' : state.language;
|
|
314
|
+
const reviewerStatus = state.reviewer === 'openai' ? 'O' : state.reviewer === 'grok' ? 'X' : 'G';
|
|
315
|
+
const arbitratorStatus = state.enableArbitration
|
|
316
|
+
? (state.arbitrator === 'openai' ? 'O' : state.arbitrator === 'grok' ? 'X' : 'G')
|
|
317
|
+
: '-';
|
|
318
|
+
// Check auth based on which providers are configured
|
|
319
|
+
const reviewerAuthed = state.reviewer === 'openai' ? state.openaiAuth
|
|
320
|
+
: state.reviewer === 'grok' ? state.grokAuth : state.geminiAuth;
|
|
321
|
+
const arbitratorAuthed = !state.enableArbitration ? true
|
|
322
|
+
: state.arbitrator === 'openai' ? state.openaiAuth
|
|
323
|
+
: state.arbitrator === 'grok' ? state.grokAuth : state.geminiAuth;
|
|
324
|
+
const allAuth = state.claudeAuth && reviewerAuthed && arbitratorAuthed;
|
|
143
325
|
const authIcon = allAuth ? '●' : '○';
|
|
144
326
|
const authColor = allAuth ? theme.success : theme.warning;
|
|
145
327
|
|
|
@@ -300,6 +482,8 @@ async function ensureAuthentication(state: SessionState): Promise<boolean> {
|
|
|
300
482
|
state.claudeAuth = status.claude.authenticated;
|
|
301
483
|
state.openaiAuth = status.openai.authenticated;
|
|
302
484
|
state.geminiAuth = status.gemini?.authenticated || false;
|
|
485
|
+
const grokStatus = await checkGrokAuth();
|
|
486
|
+
state.grokAuth = grokStatus.authenticated;
|
|
303
487
|
|
|
304
488
|
console.log();
|
|
305
489
|
printInfo('Checking authentication...');
|
|
@@ -362,6 +546,7 @@ async function ensureAuthentication(state: SessionState): Promise<boolean> {
|
|
|
362
546
|
[
|
|
363
547
|
{ label: theme.secondary('OpenAI') + theme.dim(' - GPT-4o reviews plans'), value: 'openai' },
|
|
364
548
|
{ label: theme.secondary('Gemini') + theme.dim(' - Gemini 2.0 reviews plans'), value: 'gemini' },
|
|
549
|
+
{ label: theme.secondary('Grok') + theme.dim(' - xAI Grok reviews plans'), value: 'grok' },
|
|
365
550
|
],
|
|
366
551
|
'openai'
|
|
367
552
|
) as AIProvider;
|
|
@@ -374,14 +559,16 @@ async function ensureAuthentication(state: SessionState): Promise<boolean> {
|
|
|
374
559
|
);
|
|
375
560
|
|
|
376
561
|
if (state.enableArbitration) {
|
|
377
|
-
// Auto-select
|
|
378
|
-
const defaultArbitrator = state.reviewer === 'openai' ? 'gemini'
|
|
562
|
+
// Auto-select a different provider as arbitrator
|
|
563
|
+
const defaultArbitrator = state.reviewer === 'openai' ? 'gemini'
|
|
564
|
+
: state.reviewer === 'gemini' ? 'openai' : 'gemini';
|
|
379
565
|
|
|
380
566
|
state.arbitrator = await promptSelection(
|
|
381
567
|
'Who should arbitrate when stuck?',
|
|
382
568
|
[
|
|
383
569
|
{ label: theme.secondary('Gemini') + theme.dim(' - Google Gemini breaks deadlocks'), value: 'gemini' },
|
|
384
570
|
{ label: theme.secondary('OpenAI') + theme.dim(' - OpenAI breaks deadlocks'), value: 'openai' },
|
|
571
|
+
{ label: theme.secondary('Grok') + theme.dim(' - xAI Grok breaks deadlocks'), value: 'grok' },
|
|
385
572
|
],
|
|
386
573
|
defaultArbitrator
|
|
387
574
|
) as AIProvider;
|
|
@@ -407,6 +594,39 @@ async function ensureAuthentication(state: SessionState): Promise<boolean> {
|
|
|
407
594
|
state.enableArbitration = false;
|
|
408
595
|
}
|
|
409
596
|
}
|
|
597
|
+
|
|
598
|
+
// Authenticate Grok if needed for reviewer or arbitrator
|
|
599
|
+
const needsGrok = state.reviewer === 'grok' || state.arbitrator === 'grok';
|
|
600
|
+
if (needsGrok && !state.grokAuth) {
|
|
601
|
+
console.log();
|
|
602
|
+
console.log(theme.dim(box.vertical) + ' ' + theme.primary('Grok API') + theme.dim(' - Required for ' + (state.reviewer === 'grok' ? 'review' : 'arbitration')));
|
|
603
|
+
console.log(theme.dim(box.vertical));
|
|
604
|
+
|
|
605
|
+
try {
|
|
606
|
+
const success = await authenticateGrok();
|
|
607
|
+
if (success) {
|
|
608
|
+
printSuccess('Grok API ready');
|
|
609
|
+
state.grokAuth = true;
|
|
610
|
+
} else {
|
|
611
|
+
printWarning('Grok API not authenticated');
|
|
612
|
+
if (state.reviewer === 'grok') {
|
|
613
|
+
printWarning('Falling back to OpenAI as reviewer');
|
|
614
|
+
state.reviewer = 'openai';
|
|
615
|
+
}
|
|
616
|
+
if (state.arbitrator === 'grok') {
|
|
617
|
+
state.enableArbitration = false;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
} catch (err) {
|
|
621
|
+
printError(err instanceof Error ? err.message : 'Authentication failed');
|
|
622
|
+
if (state.reviewer === 'grok') {
|
|
623
|
+
state.reviewer = 'openai';
|
|
624
|
+
}
|
|
625
|
+
if (state.arbitrator === 'grok') {
|
|
626
|
+
state.enableArbitration = false;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
410
630
|
}
|
|
411
631
|
|
|
412
632
|
// Also check if reviewer is gemini and we need to auth
|
|
@@ -430,21 +650,46 @@ async function ensureAuthentication(state: SessionState): Promise<boolean> {
|
|
|
430
650
|
}
|
|
431
651
|
}
|
|
432
652
|
|
|
653
|
+
// Also check if reviewer is grok and we need to auth
|
|
654
|
+
if (state.reviewer === 'grok' && !state.grokAuth) {
|
|
655
|
+
console.log();
|
|
656
|
+
console.log(theme.dim(box.vertical) + ' ' + theme.primary('Grok API') + theme.dim(' - Required for review'));
|
|
657
|
+
console.log(theme.dim(box.vertical));
|
|
658
|
+
|
|
659
|
+
try {
|
|
660
|
+
const success = await authenticateGrok();
|
|
661
|
+
if (success) {
|
|
662
|
+
printSuccess('Grok API ready');
|
|
663
|
+
state.grokAuth = true;
|
|
664
|
+
} else {
|
|
665
|
+
printWarning('Grok API not authenticated - falling back to OpenAI');
|
|
666
|
+
state.reviewer = 'openai';
|
|
667
|
+
}
|
|
668
|
+
} catch (err) {
|
|
669
|
+
printError(err instanceof Error ? err.message : 'Authentication failed');
|
|
670
|
+
state.reviewer = 'openai';
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
433
674
|
// Save the configuration to persist between sessions
|
|
434
675
|
await saveConsensusConfig(state);
|
|
435
676
|
|
|
436
677
|
// Show summary
|
|
437
678
|
console.log();
|
|
438
679
|
console.log(theme.secondary(' Configuration saved. Use /config to change later.'));
|
|
439
|
-
|
|
440
|
-
|
|
680
|
+
const reviewerName = state.reviewer === 'openai' ? 'OpenAI (GPT-4o)' : state.reviewer === 'grok' ? 'Grok' : 'Gemini';
|
|
681
|
+
const arbitratorName = state.arbitrator === 'openai' ? 'OpenAI' : state.arbitrator === 'grok' ? 'Grok' : 'Gemini';
|
|
682
|
+
console.log(` ${theme.dim('Reviewer:')} ${theme.primary(reviewerName)}`);
|
|
683
|
+
console.log(` ${theme.dim('Arbitrator:')} ${state.enableArbitration ? theme.primary(arbitratorName) : theme.dim('Disabled')}`);
|
|
441
684
|
console.log();
|
|
442
685
|
} else if (state.claudeAuth && state.openaiAuth && alreadyConfigured) {
|
|
443
686
|
// Show loaded configuration
|
|
444
687
|
console.log();
|
|
445
688
|
console.log(theme.secondary(' Using saved configuration (use /config to change):'));
|
|
446
|
-
|
|
447
|
-
|
|
689
|
+
const savedReviewerName = state.reviewer === 'openai' ? 'OpenAI (GPT-4o)' : state.reviewer === 'grok' ? 'Grok' : 'Gemini';
|
|
690
|
+
const savedArbitratorName = state.arbitrator === 'openai' ? 'OpenAI' : state.arbitrator === 'grok' ? 'Grok' : 'Gemini';
|
|
691
|
+
console.log(` ${theme.dim('Reviewer:')} ${theme.primary(savedReviewerName)}`);
|
|
692
|
+
console.log(` ${theme.dim('Arbitrator:')} ${state.enableArbitration ? theme.primary(savedArbitratorName) : theme.dim('Disabled')}`);
|
|
448
693
|
console.log();
|
|
449
694
|
|
|
450
695
|
// Authenticate Gemini if needed based on saved config
|
|
@@ -474,6 +719,34 @@ async function ensureAuthentication(state: SessionState): Promise<boolean> {
|
|
|
474
719
|
}
|
|
475
720
|
console.log();
|
|
476
721
|
}
|
|
722
|
+
|
|
723
|
+
// Authenticate Grok if needed based on saved config
|
|
724
|
+
const needsGrok = state.reviewer === 'grok' || (state.enableArbitration && state.arbitrator === 'grok');
|
|
725
|
+
if (needsGrok && !state.grokAuth) {
|
|
726
|
+
console.log(theme.dim(box.vertical) + ' ' + theme.primary('Grok API') + theme.dim(' - Required for ' + (state.reviewer === 'grok' ? 'review' : 'arbitration')));
|
|
727
|
+
console.log(theme.dim(box.vertical));
|
|
728
|
+
|
|
729
|
+
try {
|
|
730
|
+
const success = await authenticateGrok();
|
|
731
|
+
if (success) {
|
|
732
|
+
printSuccess('Grok API ready');
|
|
733
|
+
state.grokAuth = true;
|
|
734
|
+
} else {
|
|
735
|
+
printWarning('Grok API not authenticated');
|
|
736
|
+
if (state.reviewer === 'grok') {
|
|
737
|
+
printWarning('Falling back to OpenAI as reviewer');
|
|
738
|
+
state.reviewer = 'openai';
|
|
739
|
+
}
|
|
740
|
+
if (state.enableArbitration && state.arbitrator === 'grok') {
|
|
741
|
+
printWarning('Disabling arbitration');
|
|
742
|
+
state.enableArbitration = false;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
} catch (err) {
|
|
746
|
+
printError(err instanceof Error ? err.message : 'Grok authentication failed');
|
|
747
|
+
}
|
|
748
|
+
console.log();
|
|
749
|
+
}
|
|
477
750
|
}
|
|
478
751
|
|
|
479
752
|
return state.claudeAuth && state.openaiAuth;
|
|
@@ -493,9 +766,9 @@ function showHelp(): void {
|
|
|
493
766
|
['/status', 'Show current project status'],
|
|
494
767
|
['/auth', 'Re-authenticate services'],
|
|
495
768
|
['/config', 'Show/change configuration'],
|
|
496
|
-
['/config reviewer', 'Set reviewer (openai/gemini)'],
|
|
497
|
-
['/config arbitrator', 'Set arbitrator (openai/gemini/off)'],
|
|
498
|
-
['/lang <lang>', 'Set language (python/typescript)'],
|
|
769
|
+
['/config reviewer', 'Set reviewer (openai/gemini/grok)'],
|
|
770
|
+
['/config arbitrator', 'Set arbitrator (openai/gemini/grok/off)'],
|
|
771
|
+
['/lang <lang>', 'Set language (python/typescript/fullstack)'],
|
|
499
772
|
['/new <idea>', 'Force start a new project (skips existing check)'],
|
|
500
773
|
['/resume', 'Resume interrupted project'],
|
|
501
774
|
['/clear', 'Clear screen'],
|
|
@@ -557,6 +830,14 @@ async function handleInfo(): Promise<void> {
|
|
|
557
830
|
console.log(` ${theme.dim('API Key:')} ${theme.dim(geminiStatus.keyLastFour)}`);
|
|
558
831
|
}
|
|
559
832
|
|
|
833
|
+
console.log();
|
|
834
|
+
console.log(theme.secondary(' Grok:'));
|
|
835
|
+
const grokStatus = await checkGrokAuth();
|
|
836
|
+
console.log(` ${theme.dim('Authenticated:')} ${grokStatus.authenticated ? theme.success('Yes') : theme.dim('No')}`);
|
|
837
|
+
if (grokStatus.authenticated && grokStatus.keyLastFour) {
|
|
838
|
+
console.log(` ${theme.dim('API Key:')} ${theme.dim(grokStatus.keyLastFour)}`);
|
|
839
|
+
}
|
|
840
|
+
|
|
560
841
|
console.log();
|
|
561
842
|
console.log(theme.secondary(' Environment:'));
|
|
562
843
|
console.log(` ${theme.dim('Node.js:')} ${process.version}`);
|
|
@@ -707,32 +988,40 @@ async function handleConfig(state: SessionState, args: string[] = []): Promise<v
|
|
|
707
988
|
case 'reviewer':
|
|
708
989
|
if (args.length > 1) {
|
|
709
990
|
const newReviewer = args[1].toLowerCase();
|
|
710
|
-
if (newReviewer === 'openai' || newReviewer === 'gemini') {
|
|
991
|
+
if (newReviewer === 'openai' || newReviewer === 'gemini' || newReviewer === 'grok') {
|
|
711
992
|
if (newReviewer === 'gemini' && !state.geminiAuth) {
|
|
712
993
|
printWarning('Gemini API not authenticated. Run /auth first.');
|
|
713
994
|
return;
|
|
714
995
|
}
|
|
996
|
+
if (newReviewer === 'grok' && !state.grokAuth) {
|
|
997
|
+
printWarning('Grok API not authenticated. Run /auth first.');
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
715
1000
|
state.reviewer = newReviewer as AIProvider;
|
|
716
1001
|
// Save to config
|
|
717
1002
|
await saveConsensusConfig(state);
|
|
718
1003
|
printSuccess(`Reviewer set to ${newReviewer}`);
|
|
719
1004
|
} else {
|
|
720
|
-
printError('Invalid reviewer. Use: openai or
|
|
1005
|
+
printError('Invalid reviewer. Use: openai, gemini, or grok');
|
|
721
1006
|
}
|
|
722
1007
|
} else {
|
|
723
1008
|
printKeyValue('Reviewer', state.reviewer);
|
|
724
|
-
printInfo('Use: /config reviewer <openai|gemini>');
|
|
1009
|
+
printInfo('Use: /config reviewer <openai|gemini|grok>');
|
|
725
1010
|
}
|
|
726
1011
|
return;
|
|
727
1012
|
|
|
728
1013
|
case 'arbitrator':
|
|
729
1014
|
if (args.length > 1) {
|
|
730
1015
|
const newArbitrator = args[1].toLowerCase();
|
|
731
|
-
if (newArbitrator === 'openai' || newArbitrator === 'gemini') {
|
|
1016
|
+
if (newArbitrator === 'openai' || newArbitrator === 'gemini' || newArbitrator === 'grok') {
|
|
732
1017
|
if (newArbitrator === 'gemini' && !state.geminiAuth) {
|
|
733
1018
|
printWarning('Gemini API not authenticated. Run /auth first.');
|
|
734
1019
|
return;
|
|
735
1020
|
}
|
|
1021
|
+
if (newArbitrator === 'grok' && !state.grokAuth) {
|
|
1022
|
+
printWarning('Grok API not authenticated. Run /auth first.');
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
736
1025
|
state.arbitrator = newArbitrator as AIProvider;
|
|
737
1026
|
state.enableArbitration = true;
|
|
738
1027
|
// Save to config
|
|
@@ -744,23 +1033,33 @@ async function handleConfig(state: SessionState, args: string[] = []): Promise<v
|
|
|
744
1033
|
await saveConsensusConfig(state);
|
|
745
1034
|
printSuccess('Arbitration disabled');
|
|
746
1035
|
} else {
|
|
747
|
-
printError('Invalid arbitrator. Use: openai, gemini, or off');
|
|
1036
|
+
printError('Invalid arbitrator. Use: openai, gemini, grok, or off');
|
|
748
1037
|
}
|
|
749
1038
|
} else {
|
|
750
1039
|
printKeyValue('Arbitrator', state.enableArbitration ? state.arbitrator : 'disabled');
|
|
751
|
-
printInfo('Use: /config arbitrator <openai|gemini|off>');
|
|
1040
|
+
printInfo('Use: /config arbitrator <openai|gemini|grok|off>');
|
|
752
1041
|
}
|
|
753
1042
|
return;
|
|
754
1043
|
|
|
755
1044
|
case 'language':
|
|
756
1045
|
case 'lang':
|
|
757
1046
|
if (args.length > 1) {
|
|
758
|
-
|
|
759
|
-
|
|
1047
|
+
// Map shortcuts to full language names
|
|
1048
|
+
const langAliases: Record<string, OutputLanguage> = {
|
|
1049
|
+
'py': 'python',
|
|
1050
|
+
'python': 'python',
|
|
1051
|
+
'ts': 'typescript',
|
|
1052
|
+
'typescript': 'typescript',
|
|
1053
|
+
'fs': 'fullstack',
|
|
1054
|
+
'fullstack': 'fullstack',
|
|
1055
|
+
};
|
|
1056
|
+
const input = args[1].toLowerCase();
|
|
1057
|
+
const lang = langAliases[input];
|
|
1058
|
+
if (lang) {
|
|
760
1059
|
state.language = lang;
|
|
761
1060
|
printSuccess(`Language set to ${lang}`);
|
|
762
1061
|
} else {
|
|
763
|
-
printError('Invalid language. Use:
|
|
1062
|
+
printError('Invalid language. Use: py, ts, fs (or python, typescript, fullstack)');
|
|
764
1063
|
}
|
|
765
1064
|
} else {
|
|
766
1065
|
printKeyValue('Language', state.language);
|
|
@@ -784,19 +1083,22 @@ async function handleConfig(state: SessionState, args: string[] = []): Promise<v
|
|
|
784
1083
|
console.log(` ${theme.dim('Claude:')} ${state.claudeAuth ? theme.success('● Ready') : theme.error('○ Not authenticated')}`);
|
|
785
1084
|
console.log(` ${theme.dim('OpenAI:')} ${state.openaiAuth ? theme.success('● Ready') : theme.error('○ Not authenticated')}`);
|
|
786
1085
|
console.log(` ${theme.dim('Gemini:')} ${state.geminiAuth ? theme.success('● Ready') : theme.dim('○ Not configured')}`);
|
|
1086
|
+
console.log(` ${theme.dim('Grok:')} ${state.grokAuth ? theme.success('● Ready') : theme.dim('○ Not configured')}`);
|
|
787
1087
|
console.log();
|
|
788
1088
|
console.log(theme.primary.bold(' AI Configuration:'));
|
|
789
|
-
|
|
790
|
-
|
|
1089
|
+
const configReviewerName = state.reviewer === 'openai' ? 'OpenAI (GPT-4o)' : state.reviewer === 'grok' ? 'Grok' : 'Gemini';
|
|
1090
|
+
const configArbitratorName = state.arbitrator === 'openai' ? 'OpenAI' : state.arbitrator === 'grok' ? 'Grok' : 'Gemini';
|
|
1091
|
+
console.log(` ${theme.dim('Reviewer:')} ${theme.primary(configReviewerName)}`);
|
|
1092
|
+
console.log(` ${theme.dim('Arbitrator:')} ${state.enableArbitration ? theme.primary(configArbitratorName) : theme.dim('Disabled')}`);
|
|
791
1093
|
console.log();
|
|
792
1094
|
console.log(theme.primary.bold(' Consensus:'));
|
|
793
1095
|
console.log(` ${theme.dim('Threshold:')} ${config.consensus.threshold}%`);
|
|
794
1096
|
console.log(` ${theme.dim('Max Iters:')} ${config.consensus.max_disagreements}`);
|
|
795
1097
|
console.log();
|
|
796
1098
|
console.log(theme.secondary(' Change settings:'));
|
|
797
|
-
console.log(theme.dim(' /config reviewer <openai|gemini>'));
|
|
798
|
-
console.log(theme.dim(' /config arbitrator <openai|gemini|off>'));
|
|
799
|
-
console.log(theme.dim(' /config language <python|typescript>'));
|
|
1099
|
+
console.log(theme.dim(' /config reviewer <openai|gemini|grok>'));
|
|
1100
|
+
console.log(theme.dim(' /config arbitrator <openai|gemini|grok|off>'));
|
|
1101
|
+
console.log(theme.dim(' /config language <python|typescript|fullstack>'));
|
|
800
1102
|
console.log();
|
|
801
1103
|
}
|
|
802
1104
|
|
|
@@ -807,13 +1109,25 @@ function handleLanguage(args: string[], state: SessionState): void {
|
|
|
807
1109
|
if (args.length === 0) {
|
|
808
1110
|
console.log();
|
|
809
1111
|
printKeyValue('Current language', state.language);
|
|
810
|
-
printInfo('Use /language <python|typescript> to change');
|
|
1112
|
+
printInfo('Use /language <py|ts|fs> or <python|typescript|fullstack> to change');
|
|
811
1113
|
return;
|
|
812
1114
|
}
|
|
813
1115
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1116
|
+
// Map shortcuts to full language names
|
|
1117
|
+
const langAliases: Record<string, OutputLanguage> = {
|
|
1118
|
+
'py': 'python',
|
|
1119
|
+
'python': 'python',
|
|
1120
|
+
'ts': 'typescript',
|
|
1121
|
+
'typescript': 'typescript',
|
|
1122
|
+
'fs': 'fullstack',
|
|
1123
|
+
'fullstack': 'fullstack',
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
const input = args[0].toLowerCase();
|
|
1127
|
+
const lang = langAliases[input];
|
|
1128
|
+
|
|
1129
|
+
if (!lang) {
|
|
1130
|
+
printError('Invalid language. Use: py, ts, fs (or python, typescript, fullstack)');
|
|
817
1131
|
return;
|
|
818
1132
|
}
|
|
819
1133
|
|
|
@@ -1092,6 +1406,14 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
1092
1406
|
return;
|
|
1093
1407
|
}
|
|
1094
1408
|
|
|
1409
|
+
// Check for popeye.md and load project-specific configuration
|
|
1410
|
+
const popeyeConfig = await readPopeyeConfig(state.projectDir);
|
|
1411
|
+
if (popeyeConfig) {
|
|
1412
|
+
applyPopeyeConfig(state, popeyeConfig);
|
|
1413
|
+
await updatePopeyeLastRun(state.projectDir);
|
|
1414
|
+
printInfo(`Loaded config from popeye.md (${popeyeConfig.language}, reviewer: ${popeyeConfig.reviewer})`);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1095
1417
|
const status = await getWorkflowStatus(state.projectDir);
|
|
1096
1418
|
|
|
1097
1419
|
if (status.exists && status.state) {
|
|
@@ -1448,7 +1770,45 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
1448
1770
|
* Extracts key nouns and creates a kebab-case name
|
|
1449
1771
|
*/
|
|
1450
1772
|
function generateProjectName(idea: string): string {
|
|
1451
|
-
//
|
|
1773
|
+
// 1. First, try to find explicit project name patterns
|
|
1774
|
+
const explicitPatterns = [
|
|
1775
|
+
/(?:called|named|for|planning|project)\s+["']?([A-Z][a-zA-Z0-9]+)["']?/i,
|
|
1776
|
+
/["']([A-Z][a-zA-Z0-9]+)["']/, // Quoted names
|
|
1777
|
+
/\b([A-Z][a-z]+(?:[A-Z][a-z]+)+)\b/, // CamelCase names like "TodoMaster"
|
|
1778
|
+
];
|
|
1779
|
+
|
|
1780
|
+
for (const pattern of explicitPatterns) {
|
|
1781
|
+
const match = idea.match(pattern);
|
|
1782
|
+
if (match && match[1] && match[1].length >= 3 && match[1].length <= 30) {
|
|
1783
|
+
// Convert to kebab-case
|
|
1784
|
+
return match[1]
|
|
1785
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
1786
|
+
.toLowerCase()
|
|
1787
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
1788
|
+
.replace(/-+/g, '-')
|
|
1789
|
+
.replace(/^-|-$/g, '');
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
// 2. Look for standalone capitalized words (potential project names)
|
|
1794
|
+
// Exclude common capitalized words at sentence start
|
|
1795
|
+
const capitalizedWords = idea.match(/\b([A-Z][a-z]{2,})\b/g) || [];
|
|
1796
|
+
const excludeCapitalized = new Set([
|
|
1797
|
+
'Build', 'Create', 'Make', 'Develop', 'Write', 'Implement', 'Design',
|
|
1798
|
+
'Read', 'Start', 'Help', 'Please', 'Want', 'Need', 'Use', 'Add',
|
|
1799
|
+
'The', 'This', 'That', 'What', 'When', 'Where', 'How', 'Why',
|
|
1800
|
+
]);
|
|
1801
|
+
|
|
1802
|
+
const projectNameCandidates = capitalizedWords.filter(
|
|
1803
|
+
w => !excludeCapitalized.has(w) && w.length >= 3
|
|
1804
|
+
);
|
|
1805
|
+
|
|
1806
|
+
if (projectNameCandidates.length > 0) {
|
|
1807
|
+
// Use the first non-excluded capitalized word
|
|
1808
|
+
return projectNameCandidates[0].toLowerCase();
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// 3. Fall back to extracting meaningful words
|
|
1452
1812
|
const stopWords = new Set([
|
|
1453
1813
|
'a', 'an', 'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
|
|
1454
1814
|
'of', 'with', 'by', 'from', 'as', 'is', 'was', 'are', 'were', 'been',
|
|
@@ -1458,6 +1818,9 @@ function generateProjectName(idea: string): string {
|
|
|
1458
1818
|
'want', 'like', 'please', 'help', 'me', 'i', 'my', 'we', 'our', 'you',
|
|
1459
1819
|
'your', 'that', 'which', 'who', 'what', 'where', 'when', 'why', 'how',
|
|
1460
1820
|
'this', 'these', 'those', 'it', 'its', 'simple', 'basic', 'new',
|
|
1821
|
+
// Action verbs that shouldn't be project names
|
|
1822
|
+
'read', 'start', 'planning', 'reading', 'starting', 'begin', 'beginning',
|
|
1823
|
+
'all', 'files', 'file', 'directory', 'folder', 'also',
|
|
1461
1824
|
]);
|
|
1462
1825
|
|
|
1463
1826
|
// Extract meaningful words
|
|
@@ -1638,6 +2001,19 @@ async function handleIdea(idea: string, state: SessionState): Promise<void> {
|
|
|
1638
2001
|
|
|
1639
2002
|
succeedSpinner(`Created ${scaffoldResult.filesCreated.length} files`);
|
|
1640
2003
|
|
|
2004
|
+
// Create popeye.md with project configuration
|
|
2005
|
+
await writePopeyeConfig(projectDir, {
|
|
2006
|
+
language: state.language,
|
|
2007
|
+
reviewer: state.reviewer,
|
|
2008
|
+
arbitrator: state.arbitrator,
|
|
2009
|
+
enableArbitration: state.enableArbitration,
|
|
2010
|
+
created: new Date().toISOString(),
|
|
2011
|
+
lastRun: new Date().toISOString(),
|
|
2012
|
+
projectName,
|
|
2013
|
+
description: idea,
|
|
2014
|
+
});
|
|
2015
|
+
printInfo('Created popeye.md with project configuration');
|
|
2016
|
+
|
|
1641
2017
|
// Run workflow with reviewer/arbitrator settings
|
|
1642
2018
|
console.log();
|
|
1643
2019
|
printInfo('Starting AI workflow...');
|
|
@@ -1726,6 +2102,19 @@ async function handleNewProject(idea: string, state: SessionState): Promise<void
|
|
|
1726
2102
|
|
|
1727
2103
|
succeedSpinner(`Created ${scaffoldResult.filesCreated.length} files`);
|
|
1728
2104
|
|
|
2105
|
+
// Create popeye.md with project configuration
|
|
2106
|
+
await writePopeyeConfig(projectDir, {
|
|
2107
|
+
language: state.language,
|
|
2108
|
+
reviewer: state.reviewer,
|
|
2109
|
+
arbitrator: state.arbitrator,
|
|
2110
|
+
enableArbitration: state.enableArbitration,
|
|
2111
|
+
created: new Date().toISOString(),
|
|
2112
|
+
lastRun: new Date().toISOString(),
|
|
2113
|
+
projectName,
|
|
2114
|
+
description: idea,
|
|
2115
|
+
});
|
|
2116
|
+
printInfo('Created popeye.md with project configuration');
|
|
2117
|
+
|
|
1729
2118
|
// Run workflow with reviewer/arbitrator settings
|
|
1730
2119
|
console.log();
|
|
1731
2120
|
printInfo('Starting AI workflow...');
|
|
@@ -1779,6 +2168,7 @@ export async function startInteractiveMode(): Promise<void> {
|
|
|
1779
2168
|
claudeAuth: false,
|
|
1780
2169
|
openaiAuth: false,
|
|
1781
2170
|
geminiAuth: false,
|
|
2171
|
+
grokAuth: false,
|
|
1782
2172
|
// Load saved reviewer/arbitrator settings from config
|
|
1783
2173
|
reviewer: config.consensus.reviewer,
|
|
1784
2174
|
arbitrator: config.consensus.arbitrator === 'off' ? 'openai' : config.consensus.arbitrator,
|
|
@@ -1795,7 +2185,7 @@ export async function startInteractiveMode(): Promise<void> {
|
|
|
1795
2185
|
console.log(theme.dim(' ├─ ') + theme.secondary('Reviewer (configurable)') + theme.dim(' - Reviews plans until consensus'));
|
|
1796
2186
|
console.log(theme.dim(' └─ ') + theme.secondary('Arbitrator (optional)') + theme.dim(' - Breaks deadlocks when stuck'));
|
|
1797
2187
|
console.log();
|
|
1798
|
-
console.log(theme.dim(' You can choose OpenAI or
|
|
2188
|
+
console.log(theme.dim(' You can choose OpenAI, Gemini, or Grok as reviewer/arbitrator during setup.'));
|
|
1799
2189
|
console.log(theme.dim(' Plans are saved to docs/ folder in markdown format.'));
|
|
1800
2190
|
console.log();
|
|
1801
2191
|
|