popeye-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/.env.example +25 -0
  2. package/.prettierrc +8 -0
  3. package/README.md +320 -0
  4. package/dist/adapters/claude.d.ts +82 -0
  5. package/dist/adapters/claude.d.ts.map +1 -0
  6. package/dist/adapters/claude.js +230 -0
  7. package/dist/adapters/claude.js.map +1 -0
  8. package/dist/adapters/openai.d.ts +48 -0
  9. package/dist/adapters/openai.d.ts.map +1 -0
  10. package/dist/adapters/openai.js +257 -0
  11. package/dist/adapters/openai.js.map +1 -0
  12. package/dist/auth/claude.d.ts +44 -0
  13. package/dist/auth/claude.d.ts.map +1 -0
  14. package/dist/auth/claude.js +139 -0
  15. package/dist/auth/claude.js.map +1 -0
  16. package/dist/auth/index.d.ts +61 -0
  17. package/dist/auth/index.d.ts.map +1 -0
  18. package/dist/auth/index.js +141 -0
  19. package/dist/auth/index.js.map +1 -0
  20. package/dist/auth/keychain.d.ts +66 -0
  21. package/dist/auth/keychain.d.ts.map +1 -0
  22. package/dist/auth/keychain.js +125 -0
  23. package/dist/auth/keychain.js.map +1 -0
  24. package/dist/auth/openai-entry.d.ts +9 -0
  25. package/dist/auth/openai-entry.d.ts.map +1 -0
  26. package/dist/auth/openai-entry.js +410 -0
  27. package/dist/auth/openai-entry.js.map +1 -0
  28. package/dist/auth/openai.d.ts +71 -0
  29. package/dist/auth/openai.d.ts.map +1 -0
  30. package/dist/auth/openai.js +212 -0
  31. package/dist/auth/openai.js.map +1 -0
  32. package/dist/auth/server.d.ts +32 -0
  33. package/dist/auth/server.d.ts.map +1 -0
  34. package/dist/auth/server.js +213 -0
  35. package/dist/auth/server.js.map +1 -0
  36. package/dist/cli/commands/auth.d.ts +10 -0
  37. package/dist/cli/commands/auth.d.ts.map +1 -0
  38. package/dist/cli/commands/auth.js +162 -0
  39. package/dist/cli/commands/auth.js.map +1 -0
  40. package/dist/cli/commands/config.d.ts +10 -0
  41. package/dist/cli/commands/config.d.ts.map +1 -0
  42. package/dist/cli/commands/config.js +215 -0
  43. package/dist/cli/commands/config.js.map +1 -0
  44. package/dist/cli/commands/create.d.ts +10 -0
  45. package/dist/cli/commands/create.d.ts.map +1 -0
  46. package/dist/cli/commands/create.js +240 -0
  47. package/dist/cli/commands/create.js.map +1 -0
  48. package/dist/cli/commands/index.d.ts +10 -0
  49. package/dist/cli/commands/index.d.ts.map +1 -0
  50. package/dist/cli/commands/index.js +10 -0
  51. package/dist/cli/commands/index.js.map +1 -0
  52. package/dist/cli/commands/resume.d.ts +18 -0
  53. package/dist/cli/commands/resume.d.ts.map +1 -0
  54. package/dist/cli/commands/resume.js +241 -0
  55. package/dist/cli/commands/resume.js.map +1 -0
  56. package/dist/cli/commands/status.d.ts +18 -0
  57. package/dist/cli/commands/status.d.ts.map +1 -0
  58. package/dist/cli/commands/status.js +154 -0
  59. package/dist/cli/commands/status.js.map +1 -0
  60. package/dist/cli/index.d.ts +17 -0
  61. package/dist/cli/index.d.ts.map +1 -0
  62. package/dist/cli/index.js +71 -0
  63. package/dist/cli/index.js.map +1 -0
  64. package/dist/cli/interactive.d.ts +9 -0
  65. package/dist/cli/interactive.d.ts.map +1 -0
  66. package/dist/cli/interactive.js +330 -0
  67. package/dist/cli/interactive.js.map +1 -0
  68. package/dist/cli/output.d.ts +182 -0
  69. package/dist/cli/output.d.ts.map +1 -0
  70. package/dist/cli/output.js +355 -0
  71. package/dist/cli/output.js.map +1 -0
  72. package/dist/config/defaults.d.ts +57 -0
  73. package/dist/config/defaults.d.ts.map +1 -0
  74. package/dist/config/defaults.js +103 -0
  75. package/dist/config/defaults.js.map +1 -0
  76. package/dist/config/index.d.ts +138 -0
  77. package/dist/config/index.d.ts.map +1 -0
  78. package/dist/config/index.js +244 -0
  79. package/dist/config/index.js.map +1 -0
  80. package/dist/config/schema.d.ts +220 -0
  81. package/dist/config/schema.d.ts.map +1 -0
  82. package/dist/config/schema.js +141 -0
  83. package/dist/config/schema.js.map +1 -0
  84. package/dist/generators/index.d.ts +101 -0
  85. package/dist/generators/index.d.ts.map +1 -0
  86. package/dist/generators/index.js +200 -0
  87. package/dist/generators/index.js.map +1 -0
  88. package/dist/generators/python.d.ts +48 -0
  89. package/dist/generators/python.d.ts.map +1 -0
  90. package/dist/generators/python.js +262 -0
  91. package/dist/generators/python.js.map +1 -0
  92. package/dist/generators/templates/index.d.ts +6 -0
  93. package/dist/generators/templates/index.d.ts.map +1 -0
  94. package/dist/generators/templates/index.js +6 -0
  95. package/dist/generators/templates/index.js.map +1 -0
  96. package/dist/generators/templates/python.d.ts +53 -0
  97. package/dist/generators/templates/python.d.ts.map +1 -0
  98. package/dist/generators/templates/python.js +454 -0
  99. package/dist/generators/templates/python.js.map +1 -0
  100. package/dist/generators/templates/typescript.d.ts +53 -0
  101. package/dist/generators/templates/typescript.d.ts.map +1 -0
  102. package/dist/generators/templates/typescript.js +394 -0
  103. package/dist/generators/templates/typescript.js.map +1 -0
  104. package/dist/generators/typescript.d.ts +64 -0
  105. package/dist/generators/typescript.d.ts.map +1 -0
  106. package/dist/generators/typescript.js +271 -0
  107. package/dist/generators/typescript.js.map +1 -0
  108. package/dist/index.d.ts +7 -0
  109. package/dist/index.d.ts.map +1 -0
  110. package/dist/index.js +12 -0
  111. package/dist/index.js.map +1 -0
  112. package/dist/state/index.d.ts +168 -0
  113. package/dist/state/index.d.ts.map +1 -0
  114. package/dist/state/index.js +338 -0
  115. package/dist/state/index.js.map +1 -0
  116. package/dist/state/persistence.d.ts +91 -0
  117. package/dist/state/persistence.d.ts.map +1 -0
  118. package/dist/state/persistence.js +201 -0
  119. package/dist/state/persistence.js.map +1 -0
  120. package/dist/types/cli.d.ts +132 -0
  121. package/dist/types/cli.d.ts.map +1 -0
  122. package/dist/types/cli.js +17 -0
  123. package/dist/types/cli.js.map +1 -0
  124. package/dist/types/consensus.d.ts +111 -0
  125. package/dist/types/consensus.d.ts.map +1 -0
  126. package/dist/types/consensus.js +29 -0
  127. package/dist/types/consensus.js.map +1 -0
  128. package/dist/types/index.d.ts +9 -0
  129. package/dist/types/index.d.ts.map +1 -0
  130. package/dist/types/index.js +13 -0
  131. package/dist/types/index.js.map +1 -0
  132. package/dist/types/project.d.ts +73 -0
  133. package/dist/types/project.d.ts.map +1 -0
  134. package/dist/types/project.js +55 -0
  135. package/dist/types/project.js.map +1 -0
  136. package/dist/types/workflow.d.ts +236 -0
  137. package/dist/types/workflow.d.ts.map +1 -0
  138. package/dist/types/workflow.js +74 -0
  139. package/dist/types/workflow.js.map +1 -0
  140. package/dist/workflow/consensus.d.ts +89 -0
  141. package/dist/workflow/consensus.d.ts.map +1 -0
  142. package/dist/workflow/consensus.js +220 -0
  143. package/dist/workflow/consensus.js.map +1 -0
  144. package/dist/workflow/execution-mode.d.ts +82 -0
  145. package/dist/workflow/execution-mode.d.ts.map +1 -0
  146. package/dist/workflow/execution-mode.js +346 -0
  147. package/dist/workflow/execution-mode.js.map +1 -0
  148. package/dist/workflow/index.d.ts +110 -0
  149. package/dist/workflow/index.d.ts.map +1 -0
  150. package/dist/workflow/index.js +283 -0
  151. package/dist/workflow/index.js.map +1 -0
  152. package/dist/workflow/plan-mode.d.ts +83 -0
  153. package/dist/workflow/plan-mode.d.ts.map +1 -0
  154. package/dist/workflow/plan-mode.js +241 -0
  155. package/dist/workflow/plan-mode.js.map +1 -0
  156. package/dist/workflow/test-runner.d.ts +87 -0
  157. package/dist/workflow/test-runner.d.ts.map +1 -0
  158. package/dist/workflow/test-runner.js +273 -0
  159. package/dist/workflow/test-runner.js.map +1 -0
  160. package/eslint.config.js +25 -0
  161. package/package.json +66 -0
  162. package/src/adapters/claude.ts +298 -0
  163. package/src/adapters/openai.ts +300 -0
  164. package/src/auth/claude.ts +166 -0
  165. package/src/auth/index.ts +171 -0
  166. package/src/auth/keychain.ts +138 -0
  167. package/src/auth/openai-entry.ts +410 -0
  168. package/src/auth/openai.ts +260 -0
  169. package/src/auth/server.ts +252 -0
  170. package/src/cli/commands/auth.ts +194 -0
  171. package/src/cli/commands/config.ts +241 -0
  172. package/src/cli/commands/create.ts +308 -0
  173. package/src/cli/commands/index.ts +10 -0
  174. package/src/cli/commands/resume.ts +304 -0
  175. package/src/cli/commands/status.ts +189 -0
  176. package/src/cli/index.ts +90 -0
  177. package/src/cli/interactive.ts +418 -0
  178. package/src/cli/output.ts +410 -0
  179. package/src/config/defaults.ts +114 -0
  180. package/src/config/index.ts +315 -0
  181. package/src/config/schema.ts +164 -0
  182. package/src/generators/index.ts +251 -0
  183. package/src/generators/python.ts +318 -0
  184. package/src/generators/templates/index.ts +6 -0
  185. package/src/generators/templates/python.ts +465 -0
  186. package/src/generators/templates/typescript.ts +417 -0
  187. package/src/generators/typescript.ts +340 -0
  188. package/src/index.ts +13 -0
  189. package/src/state/index.ts +454 -0
  190. package/src/state/persistence.ts +230 -0
  191. package/src/types/cli.ts +146 -0
  192. package/src/types/consensus.ts +116 -0
  193. package/src/types/index.ts +64 -0
  194. package/src/types/project.ts +85 -0
  195. package/src/types/workflow.ts +149 -0
  196. package/src/workflow/consensus.ts +299 -0
  197. package/src/workflow/execution-mode.ts +517 -0
  198. package/src/workflow/index.ts +396 -0
  199. package/src/workflow/plan-mode.ts +356 -0
  200. package/src/workflow/test-runner.ts +345 -0
  201. package/tests/adapters/openai.test.ts +145 -0
  202. package/tests/config/config.test.ts +208 -0
  203. package/tests/generators/generators.test.ts +185 -0
  204. package/tests/types/consensus.test.ts +152 -0
  205. package/tests/types/project.test.ts +134 -0
  206. package/tests/workflow/consensus.test.ts +221 -0
  207. package/tests/workflow/test-runner.test.ts +214 -0
  208. package/tsconfig.json +25 -0
  209. package/vitest.config.ts +22 -0
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Status command
3
+ * Shows project status and progress
4
+ */
5
+
6
+ import { Command } from 'commander';
7
+ import path from 'node:path';
8
+ import {
9
+ getWorkflowStatus,
10
+ getWorkflowSummary,
11
+ validateReadyForExecution,
12
+ } from '../../workflow/index.js';
13
+ import {
14
+ printHeader,
15
+ printSection,
16
+ printSuccess,
17
+ printError,
18
+ printWarning,
19
+ printInfo,
20
+ printKeyValue,
21
+ printProgress,
22
+ printProjectState,
23
+ startSpinner,
24
+ succeedSpinner,
25
+ failSpinner,
26
+ } from '../output.js';
27
+
28
+ /**
29
+ * Create the status command
30
+ */
31
+ export function createStatusCommand(): Command {
32
+ const status = new Command('status')
33
+ .description('Show project status')
34
+ .argument('[directory]', 'Project directory', '.')
35
+ .option('-v, --verbose', 'Show detailed status')
36
+ .option('--json', 'Output as JSON')
37
+ .action(async (directory: string, options) => {
38
+ const projectDir = path.resolve(directory);
39
+
40
+ startSpinner('Loading project status...');
41
+
42
+ try {
43
+ const status = await getWorkflowStatus(projectDir);
44
+
45
+ if (!status.exists) {
46
+ failSpinner('No project found');
47
+ printInfo(`No Popeye project found at: ${projectDir}`);
48
+ printInfo('Run "popeye-cli create <idea>" to create a new project');
49
+ process.exit(1);
50
+ }
51
+
52
+ succeedSpinner('Status loaded');
53
+
54
+ // JSON output
55
+ if (options.json) {
56
+ console.log(JSON.stringify({ status, progress: status.progress }, null, 2));
57
+ return;
58
+ }
59
+
60
+ // Verbose output
61
+ if (options.verbose && status.state) {
62
+ printProjectState(status.state);
63
+ return;
64
+ }
65
+
66
+ // Standard output
67
+ const state = status.state!;
68
+ const progress = status.progress!;
69
+
70
+ printHeader(`Project: ${state.name}`);
71
+
72
+ printSection('Status');
73
+ printKeyValue('Phase', state.phase);
74
+ printKeyValue('Status', state.status);
75
+ printKeyValue('Language', state.language);
76
+
77
+ printSection('Progress');
78
+ printKeyValue('Milestones', `${progress.completedMilestones}/${progress.totalMilestones}`);
79
+ printKeyValue('Tasks', `${progress.completedTasks}/${progress.totalTasks}`);
80
+ printProgress(progress.completedTasks, progress.totalTasks);
81
+
82
+ // Show current work
83
+ if (state.currentMilestone) {
84
+ const milestone = state.milestones.find((m) => m.id === state.currentMilestone);
85
+ if (milestone) {
86
+ printSection('Current Work');
87
+ printKeyValue('Milestone', milestone.name);
88
+
89
+ if (state.currentTask) {
90
+ const task = milestone.tasks.find((t) => t.id === state.currentTask);
91
+ if (task) {
92
+ printKeyValue('Task', task.name);
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ // Show consensus info
99
+ if (state.consensusHistory && state.consensusHistory.length > 0) {
100
+ const lastConsensus = state.consensusHistory[state.consensusHistory.length - 1];
101
+ printSection('Consensus');
102
+ printKeyValue('Last Score', `${lastConsensus.result.score}%`);
103
+ printKeyValue('Iterations', state.consensusHistory.length.toString());
104
+ }
105
+
106
+ // Show errors
107
+ if (state.error) {
108
+ printSection('Error');
109
+ printError(state.error);
110
+ }
111
+
112
+ // Show next steps
113
+ console.log();
114
+ if (state.status === 'complete') {
115
+ printSuccess('Project is complete!');
116
+ } else if (state.status === 'failed') {
117
+ printWarning('Project failed. Run "popeye-cli resume" to retry.');
118
+ } else if (state.phase === 'plan') {
119
+ printInfo('Run "popeye-cli resume" to continue planning.');
120
+ } else {
121
+ printInfo('Run "popeye-cli resume" to continue execution.');
122
+ }
123
+ } catch (error) {
124
+ failSpinner('Failed to load status');
125
+ printError(error instanceof Error ? error.message : 'Unknown error');
126
+ process.exit(1);
127
+ }
128
+ });
129
+
130
+ return status;
131
+ }
132
+
133
+ /**
134
+ * Create the validate command
135
+ */
136
+ export function createValidateCommand(): Command {
137
+ const validate = new Command('validate')
138
+ .description('Validate project is ready for execution')
139
+ .argument('[directory]', 'Project directory', '.')
140
+ .action(async (directory: string) => {
141
+ const projectDir = path.resolve(directory);
142
+
143
+ startSpinner('Validating project...');
144
+
145
+ try {
146
+ const result = await validateReadyForExecution(projectDir);
147
+
148
+ if (result.ready) {
149
+ succeedSpinner('Project is valid and ready for execution');
150
+ printSuccess('All validation checks passed');
151
+ } else {
152
+ failSpinner('Project validation failed');
153
+ printSection('Issues Found');
154
+ for (const issue of result.issues) {
155
+ printWarning(issue);
156
+ }
157
+ process.exit(1);
158
+ }
159
+ } catch (error) {
160
+ failSpinner('Validation failed');
161
+ printError(error instanceof Error ? error.message : 'Unknown error');
162
+ process.exit(1);
163
+ }
164
+ });
165
+
166
+ return validate;
167
+ }
168
+
169
+ /**
170
+ * Create the summary command
171
+ */
172
+ export function createSummaryCommand(): Command {
173
+ const summary = new Command('summary')
174
+ .description('Show detailed project summary')
175
+ .argument('[directory]', 'Project directory', '.')
176
+ .action(async (directory: string) => {
177
+ const projectDir = path.resolve(directory);
178
+
179
+ try {
180
+ const summaryText = await getWorkflowSummary(projectDir);
181
+ console.log(summaryText);
182
+ } catch (error) {
183
+ printError(error instanceof Error ? error.message : 'Unknown error');
184
+ process.exit(1);
185
+ }
186
+ });
187
+
188
+ return summary;
189
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * CLI module index
3
+ * Main entry point for the CLI interface
4
+ */
5
+
6
+ import { Command } from 'commander';
7
+ import {
8
+ createAuthCommand,
9
+ createCreateCommand,
10
+ createStatusCommand,
11
+ createValidateCommand,
12
+ createSummaryCommand,
13
+ createResumeCommand,
14
+ createResetCommand,
15
+ createCancelCommand,
16
+ createConfigCommand,
17
+ } from './commands/index.js';
18
+ import { startInteractiveMode } from './interactive.js';
19
+ import { printHeader, printError, theme } from './output.js';
20
+
21
+ // Re-export
22
+ export * from './output.js';
23
+ export * from './interactive.js';
24
+ export * from './commands/index.js';
25
+
26
+ /**
27
+ * Package version (will be replaced during build)
28
+ */
29
+ const VERSION = '1.0.0';
30
+
31
+ /**
32
+ * Create the main CLI program
33
+ */
34
+ export function createProgram(): Command {
35
+ const program = new Command();
36
+
37
+ program
38
+ .name('popeye-cli')
39
+ .description('Fully autonomous code generation powered by Claude CLI and OpenAI consensus')
40
+ .version(VERSION)
41
+ .option('-v, --verbose', 'Enable verbose output')
42
+ .option('--no-color', 'Disable colored output');
43
+
44
+ // Add commands
45
+ program.addCommand(createAuthCommand());
46
+ program.addCommand(createCreateCommand());
47
+ program.addCommand(createStatusCommand());
48
+ program.addCommand(createValidateCommand());
49
+ program.addCommand(createSummaryCommand());
50
+ program.addCommand(createResumeCommand());
51
+ program.addCommand(createResetCommand());
52
+ program.addCommand(createCancelCommand());
53
+ program.addCommand(createConfigCommand());
54
+
55
+ // Interactive mode command
56
+ program
57
+ .command('interactive')
58
+ .alias('i')
59
+ .description('Start interactive mode')
60
+ .action(async () => {
61
+ await startInteractiveMode();
62
+ });
63
+
64
+ // Default action (no command specified)
65
+ program.action(async (_options, command) => {
66
+ // If no command specified and no arguments, show help
67
+ if (command.args.length === 0) {
68
+ printHeader('Popeye CLI');
69
+ console.log(theme.secondary('Autonomous code generation powered by AI consensus'));
70
+ console.log();
71
+ program.outputHelp();
72
+ }
73
+ });
74
+
75
+ return program;
76
+ }
77
+
78
+ /**
79
+ * Run the CLI
80
+ */
81
+ export async function runCLI(args: string[] = process.argv): Promise<void> {
82
+ const program = createProgram();
83
+
84
+ try {
85
+ await program.parseAsync(args);
86
+ } catch (error) {
87
+ printError(error instanceof Error ? error.message : 'Unknown error');
88
+ process.exit(1);
89
+ }
90
+ }
@@ -0,0 +1,418 @@
1
+ /**
2
+ * Interactive mode
3
+ * REPL-style interface for Popeye CLI
4
+ */
5
+
6
+ import * as readline from 'node:readline';
7
+ import { getAuthStatusForDisplay } from '../auth/index.js';
8
+ import {
9
+ runWorkflow,
10
+ resumeWorkflow,
11
+ getWorkflowStatus,
12
+ getWorkflowSummary,
13
+ } from '../workflow/index.js';
14
+ import { generateProject } from '../generators/index.js';
15
+ import { loadConfig } from '../config/index.js';
16
+ import type { ProjectSpec, OutputLanguage, OpenAIModel } from '../types/project.js';
17
+ import {
18
+ printHeader,
19
+ printSection,
20
+ printSuccess,
21
+ printError,
22
+ printWarning,
23
+ printInfo,
24
+ printKeyValue,
25
+ printAuthStatus,
26
+ startSpinner,
27
+ succeedSpinner,
28
+ failSpinner,
29
+ stopSpinner,
30
+ clearConsole,
31
+ theme,
32
+ } from './output.js';
33
+
34
+ /**
35
+ * Interactive session state
36
+ */
37
+ interface SessionState {
38
+ projectDir: string | null;
39
+ language: OutputLanguage;
40
+ model: OpenAIModel;
41
+ authenticated: boolean;
42
+ }
43
+
44
+ /**
45
+ * Command handler type
46
+ */
47
+ type CommandHandler = (args: string[], state: SessionState) => Promise<void>;
48
+
49
+ /**
50
+ * Available slash commands
51
+ */
52
+ const commands: Record<string, { description: string; handler: CommandHandler }> = {
53
+ '/help': {
54
+ description: 'Show available commands',
55
+ handler: handleHelp,
56
+ },
57
+ '/status': {
58
+ description: 'Show current project status',
59
+ handler: handleStatus,
60
+ },
61
+ '/auth': {
62
+ description: 'Check authentication status',
63
+ handler: handleAuth,
64
+ },
65
+ '/config': {
66
+ description: 'Show current configuration',
67
+ handler: handleConfig,
68
+ },
69
+ '/language': {
70
+ description: 'Set output language (python/typescript)',
71
+ handler: handleLanguage,
72
+ },
73
+ '/model': {
74
+ description: 'Set OpenAI model',
75
+ handler: handleModel,
76
+ },
77
+ '/project': {
78
+ description: 'Set project directory',
79
+ handler: handleProject,
80
+ },
81
+ '/resume': {
82
+ description: 'Resume current project workflow',
83
+ handler: handleResume,
84
+ },
85
+ '/clear': {
86
+ description: 'Clear the screen',
87
+ handler: handleClear,
88
+ },
89
+ '/exit': {
90
+ description: 'Exit interactive mode',
91
+ handler: handleExit,
92
+ },
93
+ '/quit': {
94
+ description: 'Exit interactive mode',
95
+ handler: handleExit,
96
+ },
97
+ };
98
+
99
+ /**
100
+ * Start interactive mode
101
+ */
102
+ export async function startInteractiveMode(): Promise<void> {
103
+ clearConsole();
104
+ printHeader('Popeye CLI - Interactive Mode');
105
+
106
+ printInfo('Type your project idea or use /help for commands');
107
+ console.log();
108
+
109
+ // Initialize state
110
+ const config = await loadConfig();
111
+ const state: SessionState = {
112
+ projectDir: process.cwd(),
113
+ language: config.project.default_language,
114
+ model: config.apis.openai.model,
115
+ authenticated: false,
116
+ };
117
+
118
+ // Check authentication
119
+ const authStatus = await getAuthStatusForDisplay();
120
+ state.authenticated = authStatus.claude.authenticated && authStatus.openai.authenticated;
121
+
122
+ if (!state.authenticated) {
123
+ printWarning('Not fully authenticated. Run /auth to check status.');
124
+ console.log();
125
+ }
126
+
127
+ // Create readline interface
128
+ const rl = readline.createInterface({
129
+ input: process.stdin,
130
+ output: process.stdout,
131
+ prompt: theme.primary('popeye> '),
132
+ });
133
+
134
+ rl.prompt();
135
+
136
+ rl.on('line', async (line) => {
137
+ const input = line.trim();
138
+
139
+ if (!input) {
140
+ rl.prompt();
141
+ return;
142
+ }
143
+
144
+ try {
145
+ if (input.startsWith('/')) {
146
+ // Handle command
147
+ const [command, ...args] = input.split(/\s+/);
148
+ const cmd = commands[command.toLowerCase()];
149
+
150
+ if (cmd) {
151
+ await cmd.handler(args, state);
152
+ } else {
153
+ printError(`Unknown command: ${command}. Use /help for available commands.`);
154
+ }
155
+ } else {
156
+ // Treat as project idea
157
+ await handleIdea(input, state);
158
+ }
159
+ } catch (error) {
160
+ printError(error instanceof Error ? error.message : 'Unknown error');
161
+ }
162
+
163
+ console.log();
164
+ rl.prompt();
165
+ });
166
+
167
+ rl.on('close', () => {
168
+ console.log();
169
+ printInfo('Goodbye!');
170
+ process.exit(0);
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Handle /help command
176
+ */
177
+ async function handleHelp(_args: string[], _state: SessionState): Promise<void> {
178
+ printSection('Available Commands');
179
+
180
+ for (const [cmd, info] of Object.entries(commands)) {
181
+ console.log(` ${theme.primary(cmd.padEnd(15))} ${info.description}`);
182
+ }
183
+
184
+ console.log();
185
+ printInfo('Or type a project idea to start creating');
186
+ }
187
+
188
+ /**
189
+ * Handle /status command
190
+ */
191
+ async function handleStatus(_args: string[], state: SessionState): Promise<void> {
192
+ if (!state.projectDir) {
193
+ printInfo('No project directory set. Use /project <dir> to set one.');
194
+ return;
195
+ }
196
+
197
+ const status = await getWorkflowStatus(state.projectDir);
198
+
199
+ if (!status.exists) {
200
+ printInfo('No project found in current directory');
201
+ printKeyValue('Directory', state.projectDir);
202
+ printKeyValue('Language', state.language);
203
+ printKeyValue('Model', state.model);
204
+ return;
205
+ }
206
+
207
+ const summary = await getWorkflowSummary(state.projectDir);
208
+ console.log(summary);
209
+ }
210
+
211
+ /**
212
+ * Handle /auth command
213
+ */
214
+ async function handleAuth(_args: string[], state: SessionState): Promise<void> {
215
+ const status = await getAuthStatusForDisplay();
216
+ printAuthStatus(status);
217
+
218
+ state.authenticated = status.claude.authenticated && status.openai.authenticated;
219
+
220
+ if (!state.authenticated) {
221
+ console.log();
222
+ printInfo('Run "popeye-cli auth login" to authenticate');
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Handle /config command
228
+ */
229
+ async function handleConfig(_args: string[], state: SessionState): Promise<void> {
230
+ const config = await loadConfig();
231
+
232
+ printSection('Current Session');
233
+ printKeyValue('Project Dir', state.projectDir || 'Not set');
234
+ printKeyValue('Language', state.language);
235
+ printKeyValue('Model', state.model);
236
+ printKeyValue('Authenticated', state.authenticated ? 'Yes' : 'No');
237
+
238
+ printSection('Configuration');
239
+ printKeyValue('Threshold', `${config.consensus.threshold}%`);
240
+ printKeyValue('Max Iterations', config.consensus.max_disagreements.toString());
241
+ }
242
+
243
+ /**
244
+ * Handle /language command
245
+ */
246
+ async function handleLanguage(args: string[], state: SessionState): Promise<void> {
247
+ if (args.length === 0) {
248
+ printKeyValue('Current language', state.language);
249
+ printInfo('Use /language <python|typescript> to change');
250
+ return;
251
+ }
252
+
253
+ const lang = args[0].toLowerCase() as OutputLanguage;
254
+ if (!['python', 'typescript'].includes(lang)) {
255
+ printError('Invalid language. Use: python or typescript');
256
+ return;
257
+ }
258
+
259
+ state.language = lang;
260
+ printSuccess(`Language set to: ${lang}`);
261
+ }
262
+
263
+ /**
264
+ * Handle /model command
265
+ */
266
+ async function handleModel(args: string[], state: SessionState): Promise<void> {
267
+ const validModels = ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview', 'o1-mini'];
268
+
269
+ if (args.length === 0) {
270
+ printKeyValue('Current model', state.model);
271
+ printInfo(`Use /model <${validModels.join('|')}> to change`);
272
+ return;
273
+ }
274
+
275
+ const model = args[0] as OpenAIModel;
276
+ if (!validModels.includes(model)) {
277
+ printError(`Invalid model. Use one of: ${validModels.join(', ')}`);
278
+ return;
279
+ }
280
+
281
+ state.model = model;
282
+ printSuccess(`Model set to: ${model}`);
283
+ }
284
+
285
+ /**
286
+ * Handle /project command
287
+ */
288
+ async function handleProject(args: string[], state: SessionState): Promise<void> {
289
+ const path = await import('node:path');
290
+
291
+ if (args.length === 0) {
292
+ printKeyValue('Current directory', state.projectDir || 'Not set');
293
+ printInfo('Use /project <directory> to change');
294
+ return;
295
+ }
296
+
297
+ const dir = path.resolve(args[0]);
298
+ state.projectDir = dir;
299
+ printSuccess(`Project directory set to: ${dir}`);
300
+ }
301
+
302
+ /**
303
+ * Handle /resume command
304
+ */
305
+ async function handleResume(_args: string[], state: SessionState): Promise<void> {
306
+ if (!state.projectDir) {
307
+ printError('No project directory set. Use /project <dir> first.');
308
+ return;
309
+ }
310
+
311
+ if (!state.authenticated) {
312
+ printError('Not authenticated. Run "popeye-cli auth login" first.');
313
+ return;
314
+ }
315
+
316
+ const status = await getWorkflowStatus(state.projectDir);
317
+
318
+ if (!status.exists) {
319
+ printError('No project found to resume');
320
+ return;
321
+ }
322
+
323
+ printInfo('Resuming workflow...');
324
+ console.log();
325
+
326
+ const result = await resumeWorkflow(state.projectDir, {
327
+ onProgress: (phase, message) => {
328
+ console.log(` [${phase}] ${message}`);
329
+ },
330
+ });
331
+
332
+ if (result.success) {
333
+ printSuccess('Workflow completed!');
334
+ } else {
335
+ printError(result.error || 'Workflow failed');
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Handle /clear command
341
+ */
342
+ async function handleClear(_args: string[], _state: SessionState): Promise<void> {
343
+ clearConsole();
344
+ }
345
+
346
+ /**
347
+ * Handle /exit command
348
+ */
349
+ async function handleExit(_args: string[], _state: SessionState): Promise<void> {
350
+ printInfo('Goodbye!');
351
+ process.exit(0);
352
+ }
353
+
354
+ /**
355
+ * Handle project idea input
356
+ */
357
+ async function handleIdea(idea: string, state: SessionState): Promise<void> {
358
+ if (!state.authenticated) {
359
+ printError('Not authenticated. Run /auth to check status.');
360
+ return;
361
+ }
362
+
363
+ printSection('Creating Project');
364
+ printKeyValue('Idea', idea);
365
+ printKeyValue('Language', state.language);
366
+ printKeyValue('Model', state.model);
367
+ console.log();
368
+
369
+ // Generate project name
370
+ const projectName = idea
371
+ .toLowerCase()
372
+ .replace(/[^a-z0-9\s]/g, '')
373
+ .split(/\s+/)
374
+ .slice(0, 3)
375
+ .join('-')
376
+ .substring(0, 30) || 'my-project';
377
+
378
+ const path = await import('node:path');
379
+ const projectDir = path.join(state.projectDir || process.cwd(), projectName);
380
+
381
+ const spec: ProjectSpec = {
382
+ idea,
383
+ name: projectName,
384
+ language: state.language,
385
+ openaiModel: state.model,
386
+ outputDir: state.projectDir || process.cwd(),
387
+ };
388
+
389
+ // Generate scaffold
390
+ startSpinner('Creating project structure...');
391
+ const scaffoldResult = await generateProject(spec, state.projectDir || process.cwd());
392
+
393
+ if (!scaffoldResult.success) {
394
+ failSpinner('Scaffolding failed');
395
+ printError(scaffoldResult.error || 'Failed to create project');
396
+ return;
397
+ }
398
+
399
+ succeedSpinner(`Created ${scaffoldResult.filesCreated.length} files`);
400
+
401
+ // Run workflow
402
+ const workflowResult = await runWorkflow(spec, {
403
+ projectDir,
404
+ onProgress: (phase, message) => {
405
+ console.log(` [${phase}] ${message}`);
406
+ },
407
+ });
408
+
409
+ stopSpinner();
410
+
411
+ if (workflowResult.success) {
412
+ printSuccess('Project created successfully!');
413
+ printKeyValue('Location', projectDir);
414
+ state.projectDir = projectDir;
415
+ } else {
416
+ printError(workflowResult.error || 'Workflow failed');
417
+ }
418
+ }