popeye-cli 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/.env.example +24 -1
  2. package/CONTRIBUTING.md +275 -0
  3. package/OPEN_SOURCE_MANIFESTO.md +172 -0
  4. package/README.md +340 -27
  5. package/dist/adapters/claude.d.ts +28 -2
  6. package/dist/adapters/claude.d.ts.map +1 -1
  7. package/dist/adapters/claude.js +273 -20
  8. package/dist/adapters/claude.js.map +1 -1
  9. package/dist/adapters/grok.d.ts +73 -0
  10. package/dist/adapters/grok.d.ts.map +1 -0
  11. package/dist/adapters/grok.js +430 -0
  12. package/dist/adapters/grok.js.map +1 -0
  13. package/dist/adapters/openai.d.ts +1 -1
  14. package/dist/adapters/openai.d.ts.map +1 -1
  15. package/dist/adapters/openai.js +6 -1
  16. package/dist/adapters/openai.js.map +1 -1
  17. package/dist/auth/grok.d.ts +73 -0
  18. package/dist/auth/grok.d.ts.map +1 -0
  19. package/dist/auth/grok.js +211 -0
  20. package/dist/auth/grok.js.map +1 -0
  21. package/dist/auth/index.d.ts +9 -6
  22. package/dist/auth/index.d.ts.map +1 -1
  23. package/dist/auth/index.js +23 -6
  24. package/dist/auth/index.js.map +1 -1
  25. package/dist/cli/commands/auth.d.ts +1 -1
  26. package/dist/cli/commands/auth.d.ts.map +1 -1
  27. package/dist/cli/commands/auth.js +79 -8
  28. package/dist/cli/commands/auth.js.map +1 -1
  29. package/dist/cli/commands/create.d.ts.map +1 -1
  30. package/dist/cli/commands/create.js +15 -4
  31. package/dist/cli/commands/create.js.map +1 -1
  32. package/dist/cli/interactive.d.ts.map +1 -1
  33. package/dist/cli/interactive.js +406 -35
  34. package/dist/cli/interactive.js.map +1 -1
  35. package/dist/config/defaults.d.ts +3 -0
  36. package/dist/config/defaults.d.ts.map +1 -1
  37. package/dist/config/defaults.js +9 -0
  38. package/dist/config/defaults.js.map +1 -1
  39. package/dist/config/index.d.ts +9 -0
  40. package/dist/config/index.d.ts.map +1 -1
  41. package/dist/config/index.js +16 -3
  42. package/dist/config/index.js.map +1 -1
  43. package/dist/config/schema.d.ts +27 -0
  44. package/dist/config/schema.d.ts.map +1 -1
  45. package/dist/config/schema.js +24 -3
  46. package/dist/config/schema.js.map +1 -1
  47. package/dist/generators/fullstack.d.ts +32 -0
  48. package/dist/generators/fullstack.d.ts.map +1 -0
  49. package/dist/generators/fullstack.js +497 -0
  50. package/dist/generators/fullstack.js.map +1 -0
  51. package/dist/generators/index.d.ts +4 -3
  52. package/dist/generators/index.d.ts.map +1 -1
  53. package/dist/generators/index.js +15 -1
  54. package/dist/generators/index.js.map +1 -1
  55. package/dist/generators/python.d.ts +17 -1
  56. package/dist/generators/python.d.ts.map +1 -1
  57. package/dist/generators/python.js +34 -21
  58. package/dist/generators/python.js.map +1 -1
  59. package/dist/generators/templates/fullstack.d.ts +113 -0
  60. package/dist/generators/templates/fullstack.d.ts.map +1 -0
  61. package/dist/generators/templates/fullstack.js +1004 -0
  62. package/dist/generators/templates/fullstack.js.map +1 -0
  63. package/dist/generators/typescript.d.ts +19 -1
  64. package/dist/generators/typescript.d.ts.map +1 -1
  65. package/dist/generators/typescript.js +37 -21
  66. package/dist/generators/typescript.js.map +1 -1
  67. package/dist/types/cli.d.ts +4 -0
  68. package/dist/types/cli.d.ts.map +1 -1
  69. package/dist/types/cli.js.map +1 -1
  70. package/dist/types/consensus.d.ts +119 -2
  71. package/dist/types/consensus.d.ts.map +1 -1
  72. package/dist/types/consensus.js +12 -1
  73. package/dist/types/consensus.js.map +1 -1
  74. package/dist/types/project.d.ts +76 -0
  75. package/dist/types/project.d.ts.map +1 -1
  76. package/dist/types/project.js +1 -1
  77. package/dist/types/project.js.map +1 -1
  78. package/dist/types/workflow.d.ts +170 -16
  79. package/dist/types/workflow.d.ts.map +1 -1
  80. package/dist/types/workflow.js +26 -3
  81. package/dist/types/workflow.js.map +1 -1
  82. package/dist/workflow/consensus.d.ts +29 -3
  83. package/dist/workflow/consensus.d.ts.map +1 -1
  84. package/dist/workflow/consensus.js +334 -27
  85. package/dist/workflow/consensus.js.map +1 -1
  86. package/dist/workflow/execution-mode.d.ts +2 -0
  87. package/dist/workflow/execution-mode.d.ts.map +1 -1
  88. package/dist/workflow/execution-mode.js +20 -0
  89. package/dist/workflow/execution-mode.js.map +1 -1
  90. package/dist/workflow/index.d.ts +2 -0
  91. package/dist/workflow/index.d.ts.map +1 -1
  92. package/dist/workflow/index.js +11 -0
  93. package/dist/workflow/index.js.map +1 -1
  94. package/dist/workflow/milestone-workflow.d.ts +2 -0
  95. package/dist/workflow/milestone-workflow.d.ts.map +1 -1
  96. package/dist/workflow/milestone-workflow.js +19 -2
  97. package/dist/workflow/milestone-workflow.js.map +1 -1
  98. package/dist/workflow/plan-mode.d.ts +66 -2
  99. package/dist/workflow/plan-mode.d.ts.map +1 -1
  100. package/dist/workflow/plan-mode.js +187 -11
  101. package/dist/workflow/plan-mode.js.map +1 -1
  102. package/dist/workflow/plan-storage.d.ts +252 -8
  103. package/dist/workflow/plan-storage.d.ts.map +1 -1
  104. package/dist/workflow/plan-storage.js +580 -33
  105. package/dist/workflow/plan-storage.js.map +1 -1
  106. package/dist/workflow/project-verification.js +1 -1
  107. package/dist/workflow/project-verification.js.map +1 -1
  108. package/dist/workflow/task-workflow.d.ts +2 -0
  109. package/dist/workflow/task-workflow.d.ts.map +1 -1
  110. package/dist/workflow/task-workflow.js +23 -1
  111. package/dist/workflow/task-workflow.js.map +1 -1
  112. package/dist/workflow/test-runner.d.ts +8 -0
  113. package/dist/workflow/test-runner.d.ts.map +1 -1
  114. package/dist/workflow/test-runner.js +92 -0
  115. package/dist/workflow/test-runner.js.map +1 -1
  116. package/dist/workflow/workspace-manager.d.ts +342 -0
  117. package/dist/workflow/workspace-manager.d.ts.map +1 -0
  118. package/dist/workflow/workspace-manager.js +733 -0
  119. package/dist/workflow/workspace-manager.js.map +1 -0
  120. package/package.json +1 -1
  121. package/src/adapters/claude.ts +322 -25
  122. package/src/adapters/grok.ts +492 -0
  123. package/src/adapters/openai.ts +8 -2
  124. package/src/auth/grok.ts +255 -0
  125. package/src/auth/index.ts +27 -9
  126. package/src/cli/commands/auth.ts +89 -10
  127. package/src/cli/commands/create.ts +13 -4
  128. package/src/cli/interactive.ts +453 -34
  129. package/src/config/defaults.ts +9 -0
  130. package/src/config/index.ts +17 -3
  131. package/src/config/schema.ts +25 -3
  132. package/src/generators/fullstack.ts +551 -0
  133. package/src/generators/index.ts +25 -1
  134. package/src/generators/python.ts +65 -21
  135. package/src/generators/templates/fullstack.ts +1047 -0
  136. package/src/generators/typescript.ts +69 -21
  137. package/src/types/cli.ts +4 -0
  138. package/src/types/consensus.ts +135 -3
  139. package/src/types/project.ts +82 -1
  140. package/src/types/workflow.ts +58 -4
  141. package/src/workflow/consensus.ts +461 -31
  142. package/src/workflow/execution-mode.ts +32 -0
  143. package/src/workflow/index.ts +12 -0
  144. package/src/workflow/milestone-workflow.ts +24 -2
  145. package/src/workflow/plan-mode.ts +238 -10
  146. package/src/workflow/plan-storage.ts +835 -35
  147. package/src/workflow/project-verification.ts +1 -1
  148. package/src/workflow/task-workflow.ts +29 -1
  149. package/src/workflow/test-runner.ts +110 -0
  150. package/src/workflow/workspace-manager.ts +912 -0
@@ -5,13 +5,149 @@
5
5
  import * as readline from 'node:readline';
6
6
  import { promises as fs } from 'node:fs';
7
7
  import path from 'node:path';
8
- import { getAuthStatusForDisplay, authenticateClaude, authenticateOpenAI, authenticateGemini, isClaudeCLIInstalled, checkClaudeCLIAuth, checkGeminiAuth, } from '../auth/index.js';
8
+ import { getAuthStatusForDisplay, authenticateClaude, authenticateOpenAI, authenticateGemini, authenticateGrok, isClaudeCLIInstalled, checkClaudeCLIAuth, checkGeminiAuth, checkGrokAuth, } from '../auth/index.js';
9
9
  import { runWorkflow, resumeWorkflow, getWorkflowStatus, getWorkflowSummary, } from '../workflow/index.js';
10
10
  import { analyzeProjectProgress, verifyProjectCompletion, } from '../state/index.js';
11
11
  import { generateProject } from '../generators/index.js';
12
12
  import { discoverProjects, formatProjectForDisplay, } from '../state/registry.js';
13
13
  import { loadConfig, saveConfig } from '../config/index.js';
14
14
  import { printSuccess, printError, printWarning, printInfo, printKeyValue, startSpinner, succeedSpinner, failSpinner, stopSpinner, theme, } from './output.js';
15
+ /**
16
+ * Read popeye.md from project directory
17
+ * Returns null if file doesn't exist
18
+ */
19
+ async function readPopeyeConfig(projectDir) {
20
+ const configPath = path.join(projectDir, 'popeye.md');
21
+ try {
22
+ const content = await fs.readFile(configPath, 'utf-8');
23
+ // Parse YAML frontmatter
24
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
25
+ if (!frontmatterMatch) {
26
+ return null;
27
+ }
28
+ const frontmatter = frontmatterMatch[1];
29
+ const config = {};
30
+ // Parse each line of YAML
31
+ for (const line of frontmatter.split('\n')) {
32
+ const match = line.match(/^(\w+):\s*(.+)$/);
33
+ if (match) {
34
+ const [, key, value] = match;
35
+ const cleanValue = value.trim();
36
+ switch (key) {
37
+ case 'language':
38
+ if (['python', 'typescript', 'fullstack'].includes(cleanValue)) {
39
+ config.language = cleanValue;
40
+ }
41
+ break;
42
+ case 'reviewer':
43
+ if (['openai', 'gemini', 'grok'].includes(cleanValue)) {
44
+ config.reviewer = cleanValue;
45
+ }
46
+ break;
47
+ case 'arbitrator':
48
+ if (['openai', 'gemini', 'grok', 'off'].includes(cleanValue)) {
49
+ if (cleanValue === 'off') {
50
+ config.enableArbitration = false;
51
+ }
52
+ else {
53
+ config.arbitrator = cleanValue;
54
+ config.enableArbitration = true;
55
+ }
56
+ }
57
+ break;
58
+ case 'created':
59
+ config.created = cleanValue;
60
+ break;
61
+ case 'lastRun':
62
+ config.lastRun = cleanValue;
63
+ break;
64
+ case 'projectName':
65
+ config.projectName = cleanValue;
66
+ break;
67
+ }
68
+ }
69
+ }
70
+ // Extract notes section if present
71
+ const notesMatch = content.match(/## Notes\n([\s\S]*?)(?=\n## |$)/);
72
+ if (notesMatch) {
73
+ config.notes = notesMatch[1].trim();
74
+ }
75
+ // Return config only if we have the essential fields
76
+ if (config.language && config.reviewer) {
77
+ return {
78
+ language: config.language,
79
+ reviewer: config.reviewer,
80
+ arbitrator: config.arbitrator || 'gemini',
81
+ enableArbitration: config.enableArbitration ?? true,
82
+ created: config.created || new Date().toISOString(),
83
+ lastRun: config.lastRun || new Date().toISOString(),
84
+ projectName: config.projectName,
85
+ description: config.description,
86
+ notes: config.notes,
87
+ };
88
+ }
89
+ return null;
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ /**
96
+ * Write popeye.md to project directory
97
+ */
98
+ async function writePopeyeConfig(projectDir, config) {
99
+ const configPath = path.join(projectDir, 'popeye.md');
100
+ const content = `---
101
+ # Popeye Project Configuration
102
+ language: ${config.language}
103
+ reviewer: ${config.reviewer}
104
+ arbitrator: ${config.enableArbitration ? config.arbitrator : 'off'}
105
+ created: ${config.created}
106
+ lastRun: ${new Date().toISOString()}
107
+ ${config.projectName ? `projectName: ${config.projectName}` : ''}
108
+ ---
109
+
110
+ # ${config.projectName || 'Popeye Project'}
111
+
112
+ ${config.description ? `## Description\n${config.description}\n` : ''}
113
+ ## Notes
114
+ ${config.notes || 'Add any guidance or notes for Claude here...'}
115
+
116
+ ## Configuration
117
+ - **Language**: ${config.language}
118
+ - **Reviewer**: ${config.reviewer}
119
+ - **Arbitrator**: ${config.enableArbitration ? config.arbitrator : 'disabled'}
120
+
121
+ ## Session History
122
+ - ${config.created.split('T')[0]}: Project created
123
+ - ${new Date().toISOString().split('T')[0]}: Last session
124
+ `;
125
+ await fs.writeFile(configPath, content, 'utf-8');
126
+ }
127
+ /**
128
+ * Update lastRun in popeye.md without changing other content
129
+ */
130
+ async function updatePopeyeLastRun(projectDir) {
131
+ const configPath = path.join(projectDir, 'popeye.md');
132
+ try {
133
+ let content = await fs.readFile(configPath, 'utf-8');
134
+ // Update lastRun in frontmatter
135
+ content = content.replace(/lastRun:\s*.+/, `lastRun: ${new Date().toISOString()}`);
136
+ await fs.writeFile(configPath, content, 'utf-8');
137
+ }
138
+ catch {
139
+ // File doesn't exist, ignore
140
+ }
141
+ }
142
+ /**
143
+ * Apply popeye.md config to session state
144
+ */
145
+ function applyPopeyeConfig(state, config) {
146
+ state.language = config.language;
147
+ state.reviewer = config.reviewer;
148
+ state.arbitrator = config.arbitrator;
149
+ state.enableArbitration = config.enableArbitration;
150
+ }
15
151
  // Note: startSpinner, succeedSpinner, failSpinner, stopSpinner are used in handleIdea
16
152
  /**
17
153
  * Box drawing characters for Claude Code-style UI
@@ -65,17 +201,25 @@ function drawInputBoxTop(state) {
65
201
  const width = Math.min(getTerminalWidth(), 100);
66
202
  // Hints line (above the box)
67
203
  const hints = [
68
- theme.dim('/lang ') + theme.primary('py') + theme.dim('|') + theme.primary('ts'),
204
+ theme.dim('/lang ') + theme.primary('py') + theme.dim('|') + theme.primary('ts') + theme.dim('|') + theme.primary('fs'),
69
205
  theme.dim('/config'),
70
206
  theme.dim('/help'),
71
207
  theme.dim('/exit'),
72
208
  ];
73
209
  console.log(' ' + hints.join(' '));
74
210
  // Status items for the top line
75
- const langStatus = state.language;
76
- const reviewerStatus = state.reviewer === 'openai' ? 'O' : 'G';
77
- const arbitratorStatus = state.enableArbitration ? (state.arbitrator === 'openai' ? 'O' : 'G') : '-';
78
- const allAuth = state.claudeAuth && state.openaiAuth && (state.enableArbitration ? state.geminiAuth : true);
211
+ const langStatus = state.language === 'fullstack' ? 'fs' : state.language;
212
+ const reviewerStatus = state.reviewer === 'openai' ? 'O' : state.reviewer === 'grok' ? 'X' : 'G';
213
+ const arbitratorStatus = state.enableArbitration
214
+ ? (state.arbitrator === 'openai' ? 'O' : state.arbitrator === 'grok' ? 'X' : 'G')
215
+ : '-';
216
+ // Check auth based on which providers are configured
217
+ const reviewerAuthed = state.reviewer === 'openai' ? state.openaiAuth
218
+ : state.reviewer === 'grok' ? state.grokAuth : state.geminiAuth;
219
+ const arbitratorAuthed = !state.enableArbitration ? true
220
+ : state.arbitrator === 'openai' ? state.openaiAuth
221
+ : state.arbitrator === 'grok' ? state.grokAuth : state.geminiAuth;
222
+ const allAuth = state.claudeAuth && reviewerAuthed && arbitratorAuthed;
79
223
  const authIcon = allAuth ? '●' : '○';
80
224
  const authColor = allAuth ? theme.success : theme.warning;
81
225
  // Build status text
@@ -217,6 +361,8 @@ async function ensureAuthentication(state) {
217
361
  state.claudeAuth = status.claude.authenticated;
218
362
  state.openaiAuth = status.openai.authenticated;
219
363
  state.geminiAuth = status.gemini?.authenticated || false;
364
+ const grokStatus = await checkGrokAuth();
365
+ state.grokAuth = grokStatus.authenticated;
220
366
  console.log();
221
367
  printInfo('Checking authentication...');
222
368
  console.log();
@@ -275,16 +421,19 @@ async function ensureAuthentication(state) {
275
421
  state.reviewer = await promptSelection('Who should review Claude\'s plans?', [
276
422
  { label: theme.secondary('OpenAI') + theme.dim(' - GPT-4o reviews plans'), value: 'openai' },
277
423
  { label: theme.secondary('Gemini') + theme.dim(' - Gemini 2.0 reviews plans'), value: 'gemini' },
424
+ { label: theme.secondary('Grok') + theme.dim(' - xAI Grok reviews plans'), value: 'grok' },
278
425
  ], 'openai');
279
426
  // Ask about arbitration
280
427
  console.log();
281
428
  state.enableArbitration = await promptYesNo(theme.primary('Enable arbitration when consensus is stuck?'), true);
282
429
  if (state.enableArbitration) {
283
- // Auto-select the other provider as arbitrator
284
- const defaultArbitrator = state.reviewer === 'openai' ? 'gemini' : 'openai';
430
+ // Auto-select a different provider as arbitrator
431
+ const defaultArbitrator = state.reviewer === 'openai' ? 'gemini'
432
+ : state.reviewer === 'gemini' ? 'openai' : 'gemini';
285
433
  state.arbitrator = await promptSelection('Who should arbitrate when stuck?', [
286
434
  { label: theme.secondary('Gemini') + theme.dim(' - Google Gemini breaks deadlocks'), value: 'gemini' },
287
435
  { label: theme.secondary('OpenAI') + theme.dim(' - OpenAI breaks deadlocks'), value: 'openai' },
436
+ { label: theme.secondary('Grok') + theme.dim(' - xAI Grok breaks deadlocks'), value: 'grok' },
288
437
  ], defaultArbitrator);
289
438
  // Authenticate Gemini if needed for reviewer or arbitrator
290
439
  const needsGemini = state.reviewer === 'gemini' || state.arbitrator === 'gemini';
@@ -308,6 +457,39 @@ async function ensureAuthentication(state) {
308
457
  state.enableArbitration = false;
309
458
  }
310
459
  }
460
+ // Authenticate Grok if needed for reviewer or arbitrator
461
+ const needsGrok = state.reviewer === 'grok' || state.arbitrator === 'grok';
462
+ if (needsGrok && !state.grokAuth) {
463
+ console.log();
464
+ console.log(theme.dim(box.vertical) + ' ' + theme.primary('Grok API') + theme.dim(' - Required for ' + (state.reviewer === 'grok' ? 'review' : 'arbitration')));
465
+ console.log(theme.dim(box.vertical));
466
+ try {
467
+ const success = await authenticateGrok();
468
+ if (success) {
469
+ printSuccess('Grok API ready');
470
+ state.grokAuth = true;
471
+ }
472
+ else {
473
+ printWarning('Grok API not authenticated');
474
+ if (state.reviewer === 'grok') {
475
+ printWarning('Falling back to OpenAI as reviewer');
476
+ state.reviewer = 'openai';
477
+ }
478
+ if (state.arbitrator === 'grok') {
479
+ state.enableArbitration = false;
480
+ }
481
+ }
482
+ }
483
+ catch (err) {
484
+ printError(err instanceof Error ? err.message : 'Authentication failed');
485
+ if (state.reviewer === 'grok') {
486
+ state.reviewer = 'openai';
487
+ }
488
+ if (state.arbitrator === 'grok') {
489
+ state.enableArbitration = false;
490
+ }
491
+ }
492
+ }
311
493
  }
312
494
  // Also check if reviewer is gemini and we need to auth
313
495
  if (state.reviewer === 'gemini' && !state.geminiAuth) {
@@ -330,21 +512,46 @@ async function ensureAuthentication(state) {
330
512
  state.reviewer = 'openai';
331
513
  }
332
514
  }
515
+ // Also check if reviewer is grok and we need to auth
516
+ if (state.reviewer === 'grok' && !state.grokAuth) {
517
+ console.log();
518
+ console.log(theme.dim(box.vertical) + ' ' + theme.primary('Grok API') + theme.dim(' - Required for review'));
519
+ console.log(theme.dim(box.vertical));
520
+ try {
521
+ const success = await authenticateGrok();
522
+ if (success) {
523
+ printSuccess('Grok API ready');
524
+ state.grokAuth = true;
525
+ }
526
+ else {
527
+ printWarning('Grok API not authenticated - falling back to OpenAI');
528
+ state.reviewer = 'openai';
529
+ }
530
+ }
531
+ catch (err) {
532
+ printError(err instanceof Error ? err.message : 'Authentication failed');
533
+ state.reviewer = 'openai';
534
+ }
535
+ }
333
536
  // Save the configuration to persist between sessions
334
537
  await saveConsensusConfig(state);
335
538
  // Show summary
336
539
  console.log();
337
540
  console.log(theme.secondary(' Configuration saved. Use /config to change later.'));
338
- console.log(` ${theme.dim('Reviewer:')} ${theme.primary(state.reviewer === 'openai' ? 'OpenAI (GPT-4o)' : 'Gemini')}`);
339
- console.log(` ${theme.dim('Arbitrator:')} ${state.enableArbitration ? theme.primary(state.arbitrator === 'openai' ? 'OpenAI' : 'Gemini') : theme.dim('Disabled')}`);
541
+ const reviewerName = state.reviewer === 'openai' ? 'OpenAI (GPT-4o)' : state.reviewer === 'grok' ? 'Grok' : 'Gemini';
542
+ const arbitratorName = state.arbitrator === 'openai' ? 'OpenAI' : state.arbitrator === 'grok' ? 'Grok' : 'Gemini';
543
+ console.log(` ${theme.dim('Reviewer:')} ${theme.primary(reviewerName)}`);
544
+ console.log(` ${theme.dim('Arbitrator:')} ${state.enableArbitration ? theme.primary(arbitratorName) : theme.dim('Disabled')}`);
340
545
  console.log();
341
546
  }
342
547
  else if (state.claudeAuth && state.openaiAuth && alreadyConfigured) {
343
548
  // Show loaded configuration
344
549
  console.log();
345
550
  console.log(theme.secondary(' Using saved configuration (use /config to change):'));
346
- console.log(` ${theme.dim('Reviewer:')} ${theme.primary(state.reviewer === 'openai' ? 'OpenAI (GPT-4o)' : 'Gemini')}`);
347
- console.log(` ${theme.dim('Arbitrator:')} ${state.enableArbitration ? theme.primary(state.arbitrator === 'openai' ? 'OpenAI' : 'Gemini') : theme.dim('Disabled')}`);
551
+ const savedReviewerName = state.reviewer === 'openai' ? 'OpenAI (GPT-4o)' : state.reviewer === 'grok' ? 'Grok' : 'Gemini';
552
+ const savedArbitratorName = state.arbitrator === 'openai' ? 'OpenAI' : state.arbitrator === 'grok' ? 'Grok' : 'Gemini';
553
+ console.log(` ${theme.dim('Reviewer:')} ${theme.primary(savedReviewerName)}`);
554
+ console.log(` ${theme.dim('Arbitrator:')} ${state.enableArbitration ? theme.primary(savedArbitratorName) : theme.dim('Disabled')}`);
348
555
  console.log();
349
556
  // Authenticate Gemini if needed based on saved config
350
557
  const needsGemini = state.reviewer === 'gemini' || (state.enableArbitration && state.arbitrator === 'gemini');
@@ -374,6 +581,34 @@ async function ensureAuthentication(state) {
374
581
  }
375
582
  console.log();
376
583
  }
584
+ // Authenticate Grok if needed based on saved config
585
+ const needsGrok = state.reviewer === 'grok' || (state.enableArbitration && state.arbitrator === 'grok');
586
+ if (needsGrok && !state.grokAuth) {
587
+ console.log(theme.dim(box.vertical) + ' ' + theme.primary('Grok API') + theme.dim(' - Required for ' + (state.reviewer === 'grok' ? 'review' : 'arbitration')));
588
+ console.log(theme.dim(box.vertical));
589
+ try {
590
+ const success = await authenticateGrok();
591
+ if (success) {
592
+ printSuccess('Grok API ready');
593
+ state.grokAuth = true;
594
+ }
595
+ else {
596
+ printWarning('Grok API not authenticated');
597
+ if (state.reviewer === 'grok') {
598
+ printWarning('Falling back to OpenAI as reviewer');
599
+ state.reviewer = 'openai';
600
+ }
601
+ if (state.enableArbitration && state.arbitrator === 'grok') {
602
+ printWarning('Disabling arbitration');
603
+ state.enableArbitration = false;
604
+ }
605
+ }
606
+ }
607
+ catch (err) {
608
+ printError(err instanceof Error ? err.message : 'Grok authentication failed');
609
+ }
610
+ console.log();
611
+ }
377
612
  }
378
613
  return state.claudeAuth && state.openaiAuth;
379
614
  }
@@ -390,9 +625,9 @@ function showHelp() {
390
625
  ['/status', 'Show current project status'],
391
626
  ['/auth', 'Re-authenticate services'],
392
627
  ['/config', 'Show/change configuration'],
393
- ['/config reviewer', 'Set reviewer (openai/gemini)'],
394
- ['/config arbitrator', 'Set arbitrator (openai/gemini/off)'],
395
- ['/lang <lang>', 'Set language (python/typescript)'],
628
+ ['/config reviewer', 'Set reviewer (openai/gemini/grok)'],
629
+ ['/config arbitrator', 'Set arbitrator (openai/gemini/grok/off)'],
630
+ ['/lang <lang>', 'Set language (python/typescript/fullstack)'],
396
631
  ['/new <idea>', 'Force start a new project (skips existing check)'],
397
632
  ['/resume', 'Resume interrupted project'],
398
633
  ['/clear', 'Clear screen'],
@@ -446,6 +681,13 @@ async function handleInfo() {
446
681
  console.log(` ${theme.dim('API Key:')} ${theme.dim(geminiStatus.keyLastFour)}`);
447
682
  }
448
683
  console.log();
684
+ console.log(theme.secondary(' Grok:'));
685
+ const grokStatus = await checkGrokAuth();
686
+ console.log(` ${theme.dim('Authenticated:')} ${grokStatus.authenticated ? theme.success('Yes') : theme.dim('No')}`);
687
+ if (grokStatus.authenticated && grokStatus.keyLastFour) {
688
+ console.log(` ${theme.dim('API Key:')} ${theme.dim(grokStatus.keyLastFour)}`);
689
+ }
690
+ console.log();
449
691
  console.log(theme.secondary(' Environment:'));
450
692
  console.log(` ${theme.dim('Node.js:')} ${process.version}`);
451
693
  console.log(` ${theme.dim('Platform:')} ${process.platform}`);
@@ -569,33 +811,41 @@ async function handleConfig(state, args = []) {
569
811
  case 'reviewer':
570
812
  if (args.length > 1) {
571
813
  const newReviewer = args[1].toLowerCase();
572
- if (newReviewer === 'openai' || newReviewer === 'gemini') {
814
+ if (newReviewer === 'openai' || newReviewer === 'gemini' || newReviewer === 'grok') {
573
815
  if (newReviewer === 'gemini' && !state.geminiAuth) {
574
816
  printWarning('Gemini API not authenticated. Run /auth first.');
575
817
  return;
576
818
  }
819
+ if (newReviewer === 'grok' && !state.grokAuth) {
820
+ printWarning('Grok API not authenticated. Run /auth first.');
821
+ return;
822
+ }
577
823
  state.reviewer = newReviewer;
578
824
  // Save to config
579
825
  await saveConsensusConfig(state);
580
826
  printSuccess(`Reviewer set to ${newReviewer}`);
581
827
  }
582
828
  else {
583
- printError('Invalid reviewer. Use: openai or gemini');
829
+ printError('Invalid reviewer. Use: openai, gemini, or grok');
584
830
  }
585
831
  }
586
832
  else {
587
833
  printKeyValue('Reviewer', state.reviewer);
588
- printInfo('Use: /config reviewer <openai|gemini>');
834
+ printInfo('Use: /config reviewer <openai|gemini|grok>');
589
835
  }
590
836
  return;
591
837
  case 'arbitrator':
592
838
  if (args.length > 1) {
593
839
  const newArbitrator = args[1].toLowerCase();
594
- if (newArbitrator === 'openai' || newArbitrator === 'gemini') {
840
+ if (newArbitrator === 'openai' || newArbitrator === 'gemini' || newArbitrator === 'grok') {
595
841
  if (newArbitrator === 'gemini' && !state.geminiAuth) {
596
842
  printWarning('Gemini API not authenticated. Run /auth first.');
597
843
  return;
598
844
  }
845
+ if (newArbitrator === 'grok' && !state.grokAuth) {
846
+ printWarning('Grok API not authenticated. Run /auth first.');
847
+ return;
848
+ }
599
849
  state.arbitrator = newArbitrator;
600
850
  state.enableArbitration = true;
601
851
  // Save to config
@@ -609,24 +859,34 @@ async function handleConfig(state, args = []) {
609
859
  printSuccess('Arbitration disabled');
610
860
  }
611
861
  else {
612
- printError('Invalid arbitrator. Use: openai, gemini, or off');
862
+ printError('Invalid arbitrator. Use: openai, gemini, grok, or off');
613
863
  }
614
864
  }
615
865
  else {
616
866
  printKeyValue('Arbitrator', state.enableArbitration ? state.arbitrator : 'disabled');
617
- printInfo('Use: /config arbitrator <openai|gemini|off>');
867
+ printInfo('Use: /config arbitrator <openai|gemini|grok|off>');
618
868
  }
619
869
  return;
620
870
  case 'language':
621
871
  case 'lang':
622
872
  if (args.length > 1) {
623
- const lang = args[1].toLowerCase();
624
- if (['python', 'typescript'].includes(lang)) {
873
+ // Map shortcuts to full language names
874
+ const langAliases = {
875
+ 'py': 'python',
876
+ 'python': 'python',
877
+ 'ts': 'typescript',
878
+ 'typescript': 'typescript',
879
+ 'fs': 'fullstack',
880
+ 'fullstack': 'fullstack',
881
+ };
882
+ const input = args[1].toLowerCase();
883
+ const lang = langAliases[input];
884
+ if (lang) {
625
885
  state.language = lang;
626
886
  printSuccess(`Language set to ${lang}`);
627
887
  }
628
888
  else {
629
- printError('Invalid language. Use: python or typescript');
889
+ printError('Invalid language. Use: py, ts, fs (or python, typescript, fullstack)');
630
890
  }
631
891
  }
632
892
  else {
@@ -649,19 +909,22 @@ async function handleConfig(state, args = []) {
649
909
  console.log(` ${theme.dim('Claude:')} ${state.claudeAuth ? theme.success('● Ready') : theme.error('○ Not authenticated')}`);
650
910
  console.log(` ${theme.dim('OpenAI:')} ${state.openaiAuth ? theme.success('● Ready') : theme.error('○ Not authenticated')}`);
651
911
  console.log(` ${theme.dim('Gemini:')} ${state.geminiAuth ? theme.success('● Ready') : theme.dim('○ Not configured')}`);
912
+ console.log(` ${theme.dim('Grok:')} ${state.grokAuth ? theme.success('● Ready') : theme.dim('○ Not configured')}`);
652
913
  console.log();
653
914
  console.log(theme.primary.bold(' AI Configuration:'));
654
- console.log(` ${theme.dim('Reviewer:')} ${theme.primary(state.reviewer === 'openai' ? 'OpenAI (GPT-4o)' : 'Gemini')}`);
655
- console.log(` ${theme.dim('Arbitrator:')} ${state.enableArbitration ? theme.primary(state.arbitrator === 'openai' ? 'OpenAI' : 'Gemini') : theme.dim('Disabled')}`);
915
+ const configReviewerName = state.reviewer === 'openai' ? 'OpenAI (GPT-4o)' : state.reviewer === 'grok' ? 'Grok' : 'Gemini';
916
+ const configArbitratorName = state.arbitrator === 'openai' ? 'OpenAI' : state.arbitrator === 'grok' ? 'Grok' : 'Gemini';
917
+ console.log(` ${theme.dim('Reviewer:')} ${theme.primary(configReviewerName)}`);
918
+ console.log(` ${theme.dim('Arbitrator:')} ${state.enableArbitration ? theme.primary(configArbitratorName) : theme.dim('Disabled')}`);
656
919
  console.log();
657
920
  console.log(theme.primary.bold(' Consensus:'));
658
921
  console.log(` ${theme.dim('Threshold:')} ${config.consensus.threshold}%`);
659
922
  console.log(` ${theme.dim('Max Iters:')} ${config.consensus.max_disagreements}`);
660
923
  console.log();
661
924
  console.log(theme.secondary(' Change settings:'));
662
- console.log(theme.dim(' /config reviewer <openai|gemini>'));
663
- console.log(theme.dim(' /config arbitrator <openai|gemini|off>'));
664
- console.log(theme.dim(' /config language <python|typescript>'));
925
+ console.log(theme.dim(' /config reviewer <openai|gemini|grok>'));
926
+ console.log(theme.dim(' /config arbitrator <openai|gemini|grok|off>'));
927
+ console.log(theme.dim(' /config language <python|typescript|fullstack>'));
665
928
  console.log();
666
929
  }
667
930
  /**
@@ -671,12 +934,22 @@ function handleLanguage(args, state) {
671
934
  if (args.length === 0) {
672
935
  console.log();
673
936
  printKeyValue('Current language', state.language);
674
- printInfo('Use /language <python|typescript> to change');
937
+ printInfo('Use /language <py|ts|fs> or <python|typescript|fullstack> to change');
675
938
  return;
676
939
  }
677
- const lang = args[0].toLowerCase();
678
- if (!['python', 'typescript'].includes(lang)) {
679
- printError('Invalid language. Use: python or typescript');
940
+ // Map shortcuts to full language names
941
+ const langAliases = {
942
+ 'py': 'python',
943
+ 'python': 'python',
944
+ 'ts': 'typescript',
945
+ 'typescript': 'typescript',
946
+ 'fs': 'fullstack',
947
+ 'fullstack': 'fullstack',
948
+ };
949
+ const input = args[0].toLowerCase();
950
+ const lang = langAliases[input];
951
+ if (!lang) {
952
+ printError('Invalid language. Use: py, ts, fs (or python, typescript, fullstack)');
680
953
  return;
681
954
  }
682
955
  state.language = lang;
@@ -902,6 +1175,13 @@ async function handleResume(state, args) {
902
1175
  printError('No project directory set');
903
1176
  return;
904
1177
  }
1178
+ // Check for popeye.md and load project-specific configuration
1179
+ const popeyeConfig = await readPopeyeConfig(state.projectDir);
1180
+ if (popeyeConfig) {
1181
+ applyPopeyeConfig(state, popeyeConfig);
1182
+ await updatePopeyeLastRun(state.projectDir);
1183
+ printInfo(`Loaded config from popeye.md (${popeyeConfig.language}, reviewer: ${popeyeConfig.reviewer})`);
1184
+ }
905
1185
  const status = await getWorkflowStatus(state.projectDir);
906
1186
  if (status.exists && status.state) {
907
1187
  // Formal project state exists - analyze actual progress before resuming
@@ -1053,6 +1333,16 @@ async function handleResume(state, args) {
1053
1333
  printSuccess('Workflow completed!');
1054
1334
  console.log(` ${theme.dim('Location:')} ${state.projectDir}`);
1055
1335
  }
1336
+ else if (result.rateLimitPaused) {
1337
+ // Rate limit pause - show friendly message, not an error
1338
+ console.log();
1339
+ console.log(` ${theme.warning('Rate Limit Reached')}`);
1340
+ console.log(` ${theme.dim(result.error || 'API rate limit exceeded')}`);
1341
+ console.log();
1342
+ console.log(` ${theme.info('Your progress has been saved.')}`);
1343
+ console.log(` ${theme.dim('Run')} ${theme.highlight('/resume')} ${theme.dim('after the rate limit resets to continue.')}`);
1344
+ console.log();
1345
+ }
1056
1346
  else {
1057
1347
  printError(result.error || 'Workflow failed');
1058
1348
  printInfo('You can run /resume again with additional guidance');
@@ -1189,7 +1479,38 @@ async function handleResume(state, args) {
1189
1479
  * Extracts key nouns and creates a kebab-case name
1190
1480
  */
1191
1481
  function generateProjectName(idea) {
1192
- // Common words to filter out
1482
+ // 1. First, try to find explicit project name patterns
1483
+ const explicitPatterns = [
1484
+ /(?:called|named|for|planning|project)\s+["']?([A-Z][a-zA-Z0-9]+)["']?/i,
1485
+ /["']([A-Z][a-zA-Z0-9]+)["']/, // Quoted names
1486
+ /\b([A-Z][a-z]+(?:[A-Z][a-z]+)+)\b/, // CamelCase names like "TodoMaster"
1487
+ ];
1488
+ for (const pattern of explicitPatterns) {
1489
+ const match = idea.match(pattern);
1490
+ if (match && match[1] && match[1].length >= 3 && match[1].length <= 30) {
1491
+ // Convert to kebab-case
1492
+ return match[1]
1493
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
1494
+ .toLowerCase()
1495
+ .replace(/[^a-z0-9-]/g, '-')
1496
+ .replace(/-+/g, '-')
1497
+ .replace(/^-|-$/g, '');
1498
+ }
1499
+ }
1500
+ // 2. Look for standalone capitalized words (potential project names)
1501
+ // Exclude common capitalized words at sentence start
1502
+ const capitalizedWords = idea.match(/\b([A-Z][a-z]{2,})\b/g) || [];
1503
+ const excludeCapitalized = new Set([
1504
+ 'Build', 'Create', 'Make', 'Develop', 'Write', 'Implement', 'Design',
1505
+ 'Read', 'Start', 'Help', 'Please', 'Want', 'Need', 'Use', 'Add',
1506
+ 'The', 'This', 'That', 'What', 'When', 'Where', 'How', 'Why',
1507
+ ]);
1508
+ const projectNameCandidates = capitalizedWords.filter(w => !excludeCapitalized.has(w) && w.length >= 3);
1509
+ if (projectNameCandidates.length > 0) {
1510
+ // Use the first non-excluded capitalized word
1511
+ return projectNameCandidates[0].toLowerCase();
1512
+ }
1513
+ // 3. Fall back to extracting meaningful words
1193
1514
  const stopWords = new Set([
1194
1515
  'a', 'an', 'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
1195
1516
  'of', 'with', 'by', 'from', 'as', 'is', 'was', 'are', 'were', 'been',
@@ -1199,6 +1520,9 @@ function generateProjectName(idea) {
1199
1520
  'want', 'like', 'please', 'help', 'me', 'i', 'my', 'we', 'our', 'you',
1200
1521
  'your', 'that', 'which', 'who', 'what', 'where', 'when', 'why', 'how',
1201
1522
  'this', 'these', 'those', 'it', 'its', 'simple', 'basic', 'new',
1523
+ // Action verbs that shouldn't be project names
1524
+ 'read', 'start', 'planning', 'reading', 'starting', 'begin', 'beginning',
1525
+ 'all', 'files', 'file', 'directory', 'folder', 'also',
1202
1526
  ]);
1203
1527
  // Extract meaningful words
1204
1528
  const words = idea
@@ -1348,6 +1672,18 @@ async function handleIdea(idea, state) {
1348
1672
  return;
1349
1673
  }
1350
1674
  succeedSpinner(`Created ${scaffoldResult.filesCreated.length} files`);
1675
+ // Create popeye.md with project configuration
1676
+ await writePopeyeConfig(projectDir, {
1677
+ language: state.language,
1678
+ reviewer: state.reviewer,
1679
+ arbitrator: state.arbitrator,
1680
+ enableArbitration: state.enableArbitration,
1681
+ created: new Date().toISOString(),
1682
+ lastRun: new Date().toISOString(),
1683
+ projectName,
1684
+ description: idea,
1685
+ });
1686
+ printInfo('Created popeye.md with project configuration');
1351
1687
  // Run workflow with reviewer/arbitrator settings
1352
1688
  console.log();
1353
1689
  printInfo('Starting AI workflow...');
@@ -1377,6 +1713,17 @@ async function handleIdea(idea, state) {
1377
1713
  console.log(` ${theme.dim('Location:')} ${projectDir}`);
1378
1714
  state.projectDir = projectDir;
1379
1715
  }
1716
+ else if (workflowResult.rateLimitPaused) {
1717
+ // Rate limit pause - show friendly message, not an error
1718
+ console.log();
1719
+ console.log(` ${theme.warning('Rate Limit Reached')}`);
1720
+ console.log(` ${theme.dim(workflowResult.error || 'API rate limit exceeded')}`);
1721
+ console.log();
1722
+ console.log(` ${theme.info('Your progress has been saved.')}`);
1723
+ console.log(` ${theme.dim('Run')} ${theme.highlight('/resume')} ${theme.dim('after the rate limit resets to continue.')}`);
1724
+ console.log();
1725
+ state.projectDir = projectDir;
1726
+ }
1380
1727
  else {
1381
1728
  printError(workflowResult.error || 'Workflow failed');
1382
1729
  }
@@ -1423,6 +1770,18 @@ async function handleNewProject(idea, state) {
1423
1770
  return;
1424
1771
  }
1425
1772
  succeedSpinner(`Created ${scaffoldResult.filesCreated.length} files`);
1773
+ // Create popeye.md with project configuration
1774
+ await writePopeyeConfig(projectDir, {
1775
+ language: state.language,
1776
+ reviewer: state.reviewer,
1777
+ arbitrator: state.arbitrator,
1778
+ enableArbitration: state.enableArbitration,
1779
+ created: new Date().toISOString(),
1780
+ lastRun: new Date().toISOString(),
1781
+ projectName,
1782
+ description: idea,
1783
+ });
1784
+ printInfo('Created popeye.md with project configuration');
1426
1785
  // Run workflow with reviewer/arbitrator settings
1427
1786
  console.log();
1428
1787
  printInfo('Starting AI workflow...');
@@ -1452,6 +1811,17 @@ async function handleNewProject(idea, state) {
1452
1811
  console.log(` ${theme.dim('Location:')} ${projectDir}`);
1453
1812
  state.projectDir = projectDir;
1454
1813
  }
1814
+ else if (workflowResult.rateLimitPaused) {
1815
+ // Rate limit pause - show friendly message, not an error
1816
+ console.log();
1817
+ console.log(` ${theme.warning('Rate Limit Reached')}`);
1818
+ console.log(` ${theme.dim(workflowResult.error || 'API rate limit exceeded')}`);
1819
+ console.log();
1820
+ console.log(` ${theme.info('Your progress has been saved.')}`);
1821
+ console.log(` ${theme.dim('Run')} ${theme.highlight('/resume')} ${theme.dim('after the rate limit resets to continue.')}`);
1822
+ console.log();
1823
+ state.projectDir = projectDir;
1824
+ }
1455
1825
  else {
1456
1826
  printError(workflowResult.error || 'Workflow failed');
1457
1827
  }
@@ -1471,6 +1841,7 @@ export async function startInteractiveMode() {
1471
1841
  claudeAuth: false,
1472
1842
  openaiAuth: false,
1473
1843
  geminiAuth: false,
1844
+ grokAuth: false,
1474
1845
  // Load saved reviewer/arbitrator settings from config
1475
1846
  reviewer: config.consensus.reviewer,
1476
1847
  arbitrator: config.consensus.arbitrator === 'off' ? 'openai' : config.consensus.arbitrator,
@@ -1485,7 +1856,7 @@ export async function startInteractiveMode() {
1485
1856
  console.log(theme.dim(' ├─ ') + theme.secondary('Reviewer (configurable)') + theme.dim(' - Reviews plans until consensus'));
1486
1857
  console.log(theme.dim(' └─ ') + theme.secondary('Arbitrator (optional)') + theme.dim(' - Breaks deadlocks when stuck'));
1487
1858
  console.log();
1488
- console.log(theme.dim(' You can choose OpenAI or Gemini as reviewer/arbitrator during setup.'));
1859
+ console.log(theme.dim(' You can choose OpenAI, Gemini, or Grok as reviewer/arbitrator during setup.'));
1489
1860
  console.log(theme.dim(' Plans are saved to docs/ folder in markdown format.'));
1490
1861
  console.log();
1491
1862
  // Check and perform authentication