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.
- package/.env.example +25 -0
- package/.prettierrc +8 -0
- package/README.md +320 -0
- package/dist/adapters/claude.d.ts +82 -0
- package/dist/adapters/claude.d.ts.map +1 -0
- package/dist/adapters/claude.js +230 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/openai.d.ts +48 -0
- package/dist/adapters/openai.d.ts.map +1 -0
- package/dist/adapters/openai.js +257 -0
- package/dist/adapters/openai.js.map +1 -0
- package/dist/auth/claude.d.ts +44 -0
- package/dist/auth/claude.d.ts.map +1 -0
- package/dist/auth/claude.js +139 -0
- package/dist/auth/claude.js.map +1 -0
- package/dist/auth/index.d.ts +61 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +141 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/keychain.d.ts +66 -0
- package/dist/auth/keychain.d.ts.map +1 -0
- package/dist/auth/keychain.js +125 -0
- package/dist/auth/keychain.js.map +1 -0
- package/dist/auth/openai-entry.d.ts +9 -0
- package/dist/auth/openai-entry.d.ts.map +1 -0
- package/dist/auth/openai-entry.js +410 -0
- package/dist/auth/openai-entry.js.map +1 -0
- package/dist/auth/openai.d.ts +71 -0
- package/dist/auth/openai.d.ts.map +1 -0
- package/dist/auth/openai.js +212 -0
- package/dist/auth/openai.js.map +1 -0
- package/dist/auth/server.d.ts +32 -0
- package/dist/auth/server.d.ts.map +1 -0
- package/dist/auth/server.js +213 -0
- package/dist/auth/server.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +10 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +162 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/config.d.ts +10 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +215 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/create.d.ts +10 -0
- package/dist/cli/commands/create.d.ts.map +1 -0
- package/dist/cli/commands/create.js +240 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/index.d.ts +10 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +10 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/resume.d.ts +18 -0
- package/dist/cli/commands/resume.d.ts.map +1 -0
- package/dist/cli/commands/resume.js +241 -0
- package/dist/cli/commands/resume.js.map +1 -0
- package/dist/cli/commands/status.d.ts +18 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +154 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +71 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/interactive.d.ts +9 -0
- package/dist/cli/interactive.d.ts.map +1 -0
- package/dist/cli/interactive.js +330 -0
- package/dist/cli/interactive.js.map +1 -0
- package/dist/cli/output.d.ts +182 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +355 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/config/defaults.d.ts +57 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +103 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +138 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +244 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +220 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +141 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/generators/index.d.ts +101 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +200 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/python.d.ts +48 -0
- package/dist/generators/python.d.ts.map +1 -0
- package/dist/generators/python.js +262 -0
- package/dist/generators/python.js.map +1 -0
- package/dist/generators/templates/index.d.ts +6 -0
- package/dist/generators/templates/index.d.ts.map +1 -0
- package/dist/generators/templates/index.js +6 -0
- package/dist/generators/templates/index.js.map +1 -0
- package/dist/generators/templates/python.d.ts +53 -0
- package/dist/generators/templates/python.d.ts.map +1 -0
- package/dist/generators/templates/python.js +454 -0
- package/dist/generators/templates/python.js.map +1 -0
- package/dist/generators/templates/typescript.d.ts +53 -0
- package/dist/generators/templates/typescript.d.ts.map +1 -0
- package/dist/generators/templates/typescript.js +394 -0
- package/dist/generators/templates/typescript.js.map +1 -0
- package/dist/generators/typescript.d.ts +64 -0
- package/dist/generators/typescript.d.ts.map +1 -0
- package/dist/generators/typescript.js +271 -0
- package/dist/generators/typescript.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/state/index.d.ts +168 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +338 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/persistence.d.ts +91 -0
- package/dist/state/persistence.d.ts.map +1 -0
- package/dist/state/persistence.js +201 -0
- package/dist/state/persistence.js.map +1 -0
- package/dist/types/cli.d.ts +132 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +17 -0
- package/dist/types/cli.js.map +1 -0
- package/dist/types/consensus.d.ts +111 -0
- package/dist/types/consensus.d.ts.map +1 -0
- package/dist/types/consensus.js +29 -0
- package/dist/types/consensus.js.map +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/project.d.ts +73 -0
- package/dist/types/project.d.ts.map +1 -0
- package/dist/types/project.js +55 -0
- package/dist/types/project.js.map +1 -0
- package/dist/types/workflow.d.ts +236 -0
- package/dist/types/workflow.d.ts.map +1 -0
- package/dist/types/workflow.js +74 -0
- package/dist/types/workflow.js.map +1 -0
- package/dist/workflow/consensus.d.ts +89 -0
- package/dist/workflow/consensus.d.ts.map +1 -0
- package/dist/workflow/consensus.js +220 -0
- package/dist/workflow/consensus.js.map +1 -0
- package/dist/workflow/execution-mode.d.ts +82 -0
- package/dist/workflow/execution-mode.d.ts.map +1 -0
- package/dist/workflow/execution-mode.js +346 -0
- package/dist/workflow/execution-mode.js.map +1 -0
- package/dist/workflow/index.d.ts +110 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +283 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts +83 -0
- package/dist/workflow/plan-mode.d.ts.map +1 -0
- package/dist/workflow/plan-mode.js +241 -0
- package/dist/workflow/plan-mode.js.map +1 -0
- package/dist/workflow/test-runner.d.ts +87 -0
- package/dist/workflow/test-runner.d.ts.map +1 -0
- package/dist/workflow/test-runner.js +273 -0
- package/dist/workflow/test-runner.js.map +1 -0
- package/eslint.config.js +25 -0
- package/package.json +66 -0
- package/src/adapters/claude.ts +298 -0
- package/src/adapters/openai.ts +300 -0
- package/src/auth/claude.ts +166 -0
- package/src/auth/index.ts +171 -0
- package/src/auth/keychain.ts +138 -0
- package/src/auth/openai-entry.ts +410 -0
- package/src/auth/openai.ts +260 -0
- package/src/auth/server.ts +252 -0
- package/src/cli/commands/auth.ts +194 -0
- package/src/cli/commands/config.ts +241 -0
- package/src/cli/commands/create.ts +308 -0
- package/src/cli/commands/index.ts +10 -0
- package/src/cli/commands/resume.ts +304 -0
- package/src/cli/commands/status.ts +189 -0
- package/src/cli/index.ts +90 -0
- package/src/cli/interactive.ts +418 -0
- package/src/cli/output.ts +410 -0
- package/src/config/defaults.ts +114 -0
- package/src/config/index.ts +315 -0
- package/src/config/schema.ts +164 -0
- package/src/generators/index.ts +251 -0
- package/src/generators/python.ts +318 -0
- package/src/generators/templates/index.ts +6 -0
- package/src/generators/templates/python.ts +465 -0
- package/src/generators/templates/typescript.ts +417 -0
- package/src/generators/typescript.ts +340 -0
- package/src/index.ts +13 -0
- package/src/state/index.ts +454 -0
- package/src/state/persistence.ts +230 -0
- package/src/types/cli.ts +146 -0
- package/src/types/consensus.ts +116 -0
- package/src/types/index.ts +64 -0
- package/src/types/project.ts +85 -0
- package/src/types/workflow.ts +149 -0
- package/src/workflow/consensus.ts +299 -0
- package/src/workflow/execution-mode.ts +517 -0
- package/src/workflow/index.ts +396 -0
- package/src/workflow/plan-mode.ts +356 -0
- package/src/workflow/test-runner.ts +345 -0
- package/tests/adapters/openai.test.ts +145 -0
- package/tests/config/config.test.ts +208 -0
- package/tests/generators/generators.test.ts +185 -0
- package/tests/types/consensus.test.ts +152 -0
- package/tests/types/project.test.ts +134 -0
- package/tests/workflow/consensus.test.ts +221 -0
- package/tests/workflow/test-runner.test.ts +214 -0
- package/tsconfig.json +25 -0
- 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
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -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
|
+
}
|