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,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create command
|
|
3
|
+
* One-shot project creation from an idea
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { ProjectSpecSchema, type OutputLanguage, type OpenAIModel } from '../../types/project.js';
|
|
9
|
+
import { requireAuth } from '../../auth/index.js';
|
|
10
|
+
import { runWorkflow } from '../../workflow/index.js';
|
|
11
|
+
import { generateProject, projectDirExists, cleanupProject } from '../../generators/index.js';
|
|
12
|
+
import {
|
|
13
|
+
printHeader,
|
|
14
|
+
printSection,
|
|
15
|
+
printSuccess,
|
|
16
|
+
printError,
|
|
17
|
+
printWarning,
|
|
18
|
+
printInfo,
|
|
19
|
+
printKeyValue,
|
|
20
|
+
printConsensusResult,
|
|
21
|
+
startSpinner,
|
|
22
|
+
updateSpinner,
|
|
23
|
+
succeedSpinner,
|
|
24
|
+
failSpinner,
|
|
25
|
+
stopSpinner,
|
|
26
|
+
} from '../output.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create the create command
|
|
30
|
+
*/
|
|
31
|
+
export function createCreateCommand(): Command {
|
|
32
|
+
const create = new Command('create')
|
|
33
|
+
.description('Create a new project from an idea')
|
|
34
|
+
.argument('<idea>', 'Project idea or description')
|
|
35
|
+
.option('-n, --name <name>', 'Project name')
|
|
36
|
+
.option(
|
|
37
|
+
'-l, --language <lang>',
|
|
38
|
+
'Output language (python, typescript)',
|
|
39
|
+
'python'
|
|
40
|
+
)
|
|
41
|
+
.option(
|
|
42
|
+
'-m, --model <model>',
|
|
43
|
+
'OpenAI model for consensus (gpt-4o, gpt-4o-mini, o1-preview)',
|
|
44
|
+
'gpt-4o'
|
|
45
|
+
)
|
|
46
|
+
.option(
|
|
47
|
+
'-o, --output <dir>',
|
|
48
|
+
'Output directory',
|
|
49
|
+
process.cwd()
|
|
50
|
+
)
|
|
51
|
+
.option(
|
|
52
|
+
'--threshold <percent>',
|
|
53
|
+
'Consensus threshold percentage',
|
|
54
|
+
'95'
|
|
55
|
+
)
|
|
56
|
+
.option(
|
|
57
|
+
'--max-iterations <count>',
|
|
58
|
+
'Maximum consensus iterations',
|
|
59
|
+
'5'
|
|
60
|
+
)
|
|
61
|
+
.option(
|
|
62
|
+
'--skip-scaffold',
|
|
63
|
+
'Skip initial project scaffolding'
|
|
64
|
+
)
|
|
65
|
+
.action(async (idea: string, options) => {
|
|
66
|
+
try {
|
|
67
|
+
// Validate inputs
|
|
68
|
+
const language = options.language as OutputLanguage;
|
|
69
|
+
if (!['python', 'typescript'].includes(language)) {
|
|
70
|
+
printError(`Invalid language: ${language}. Use 'python' or 'typescript'.`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const model = options.model as OpenAIModel;
|
|
75
|
+
const validModels = ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview', 'o1-mini'];
|
|
76
|
+
if (!validModels.includes(model)) {
|
|
77
|
+
printError(`Invalid model: ${model}. Use one of: ${validModels.join(', ')}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Generate project name from idea if not provided
|
|
82
|
+
const projectName = options.name || generateProjectName(idea);
|
|
83
|
+
const outputDir = path.resolve(options.output);
|
|
84
|
+
const projectDir = path.join(outputDir, projectName);
|
|
85
|
+
const threshold = parseInt(options.threshold, 10);
|
|
86
|
+
const maxIterations = parseInt(options.maxIterations, 10);
|
|
87
|
+
|
|
88
|
+
// Validate project spec
|
|
89
|
+
const specResult = ProjectSpecSchema.safeParse({
|
|
90
|
+
idea,
|
|
91
|
+
name: projectName,
|
|
92
|
+
language,
|
|
93
|
+
openaiModel: model,
|
|
94
|
+
outputDir,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (!specResult.success) {
|
|
98
|
+
printError(`Invalid project specification: ${specResult.error.message}`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const spec = specResult.data;
|
|
103
|
+
|
|
104
|
+
// Print header
|
|
105
|
+
printHeader('Popeye CLI - Project Creation');
|
|
106
|
+
|
|
107
|
+
printSection('Project Configuration');
|
|
108
|
+
printKeyValue('Name', projectName);
|
|
109
|
+
printKeyValue('Language', language);
|
|
110
|
+
printKeyValue('Model', model);
|
|
111
|
+
printKeyValue('Output', projectDir);
|
|
112
|
+
printKeyValue('Threshold', `${threshold}%`);
|
|
113
|
+
console.log();
|
|
114
|
+
|
|
115
|
+
printSection('Idea');
|
|
116
|
+
console.log(` ${idea}`);
|
|
117
|
+
console.log();
|
|
118
|
+
|
|
119
|
+
// Check if directory exists
|
|
120
|
+
if (await projectDirExists(projectDir)) {
|
|
121
|
+
printWarning(`Directory already exists: ${projectDir}`);
|
|
122
|
+
printInfo('Use a different name or output directory');
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Require authentication
|
|
127
|
+
printSection('Authentication');
|
|
128
|
+
startSpinner('Checking authentication...');
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await requireAuth();
|
|
132
|
+
succeedSpinner('Authentication verified');
|
|
133
|
+
} catch (error) {
|
|
134
|
+
failSpinner('Authentication required');
|
|
135
|
+
printError(error instanceof Error ? error.message : 'Authentication failed');
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Generate project scaffold
|
|
140
|
+
if (!options.skipScaffold) {
|
|
141
|
+
printSection('Project Scaffolding');
|
|
142
|
+
startSpinner('Creating project structure...');
|
|
143
|
+
|
|
144
|
+
const scaffoldResult = await generateProject(spec, outputDir);
|
|
145
|
+
|
|
146
|
+
if (!scaffoldResult.success) {
|
|
147
|
+
failSpinner('Scaffolding failed');
|
|
148
|
+
printError(scaffoldResult.error || 'Failed to create project structure');
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
succeedSpinner(`Created ${scaffoldResult.filesCreated.length} files`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Run the workflow
|
|
156
|
+
printSection('Workflow Execution');
|
|
157
|
+
|
|
158
|
+
const workflowResult = await runWorkflow(spec, {
|
|
159
|
+
projectDir,
|
|
160
|
+
consensusConfig: {
|
|
161
|
+
threshold,
|
|
162
|
+
maxIterations,
|
|
163
|
+
openaiModel: model,
|
|
164
|
+
},
|
|
165
|
+
onProgress: (phase, message) => {
|
|
166
|
+
handleProgressUpdate(phase, message);
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Stop any running spinner
|
|
171
|
+
stopSpinner();
|
|
172
|
+
|
|
173
|
+
// Print results
|
|
174
|
+
console.log();
|
|
175
|
+
if (workflowResult.success) {
|
|
176
|
+
printHeader('Project Created Successfully!');
|
|
177
|
+
|
|
178
|
+
printSection('Summary');
|
|
179
|
+
printKeyValue('Location', projectDir);
|
|
180
|
+
printKeyValue('Language', language);
|
|
181
|
+
|
|
182
|
+
if (workflowResult.planResult?.consensusResult) {
|
|
183
|
+
printKeyValue('Final Consensus', `${workflowResult.planResult.consensusResult.finalScore}%`);
|
|
184
|
+
printKeyValue('Iterations', workflowResult.planResult.consensusResult.totalIterations.toString());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (workflowResult.executionResult) {
|
|
188
|
+
printKeyValue('Tasks Completed', workflowResult.executionResult.completedTasks.toString());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log();
|
|
192
|
+
printSuccess('Your project is ready!');
|
|
193
|
+
console.log();
|
|
194
|
+
printInfo(`cd ${projectDir}`);
|
|
195
|
+
printInfo(language === 'python' ? 'python -m pytest tests/' : 'npm test');
|
|
196
|
+
} else {
|
|
197
|
+
printHeader('Project Creation Failed');
|
|
198
|
+
|
|
199
|
+
if (workflowResult.error) {
|
|
200
|
+
printError(workflowResult.error);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (workflowResult.planResult?.consensusResult) {
|
|
204
|
+
printSection('Consensus Status');
|
|
205
|
+
printConsensusResult(
|
|
206
|
+
workflowResult.planResult.consensusResult.iterations[
|
|
207
|
+
workflowResult.planResult.consensusResult.iterations.length - 1
|
|
208
|
+
]?.result || { score: 0, analysis: '', approved: false, rawResponse: '' }
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Cleanup on failure
|
|
213
|
+
printWarning('Cleaning up failed project...');
|
|
214
|
+
await cleanupProject(projectDir);
|
|
215
|
+
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
stopSpinner();
|
|
220
|
+
printError(error instanceof Error ? error.message : 'Unknown error');
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
return create;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Generate a project name from an idea
|
|
230
|
+
*/
|
|
231
|
+
function generateProjectName(idea: string): string {
|
|
232
|
+
// Take first few words, lowercase, replace spaces with hyphens
|
|
233
|
+
return idea
|
|
234
|
+
.toLowerCase()
|
|
235
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
236
|
+
.split(/\s+/)
|
|
237
|
+
.slice(0, 3)
|
|
238
|
+
.join('-')
|
|
239
|
+
.substring(0, 30) || 'my-project';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Handle progress updates from the workflow
|
|
244
|
+
*/
|
|
245
|
+
let currentSpinner: ReturnType<typeof startSpinner> | null = null;
|
|
246
|
+
let lastPhase = '';
|
|
247
|
+
|
|
248
|
+
function handleProgressUpdate(phase: string, message: string): void {
|
|
249
|
+
// Map phases to user-friendly names
|
|
250
|
+
const phaseNames: Record<string, string> = {
|
|
251
|
+
'plan-init': 'Initializing',
|
|
252
|
+
'expand-idea': 'Expanding Idea',
|
|
253
|
+
'get-context': 'Analyzing Context',
|
|
254
|
+
'create-plan': 'Creating Plan',
|
|
255
|
+
'consensus': 'Consensus Review',
|
|
256
|
+
'execution-start': 'Starting Execution',
|
|
257
|
+
'task-start': 'Executing Task',
|
|
258
|
+
'task-complete': 'Task Complete',
|
|
259
|
+
'task-failed': 'Task Failed',
|
|
260
|
+
'execution-complete': 'Execution Complete',
|
|
261
|
+
'complete': 'Complete',
|
|
262
|
+
'failed': 'Failed',
|
|
263
|
+
'error': 'Error',
|
|
264
|
+
'workflow': 'Workflow',
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const phaseName = phaseNames[phase] || phase;
|
|
268
|
+
|
|
269
|
+
// Handle phase transitions
|
|
270
|
+
if (phase !== lastPhase) {
|
|
271
|
+
if (currentSpinner) {
|
|
272
|
+
if (phase === 'error' || phase === 'failed') {
|
|
273
|
+
failSpinner();
|
|
274
|
+
} else {
|
|
275
|
+
succeedSpinner();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!['complete', 'failed', 'error', 'task-complete', 'task-failed'].includes(phase)) {
|
|
280
|
+
currentSpinner = startSpinner(`${phaseName}: ${message}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
lastPhase = phase;
|
|
284
|
+
} else if (currentSpinner) {
|
|
285
|
+
updateSpinner(`${phaseName}: ${message}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Handle terminal phases
|
|
289
|
+
if (phase === 'complete') {
|
|
290
|
+
if (currentSpinner) {
|
|
291
|
+
succeedSpinner(message);
|
|
292
|
+
currentSpinner = null;
|
|
293
|
+
} else {
|
|
294
|
+
printSuccess(message);
|
|
295
|
+
}
|
|
296
|
+
} else if (phase === 'failed' || phase === 'error') {
|
|
297
|
+
if (currentSpinner) {
|
|
298
|
+
failSpinner(message);
|
|
299
|
+
currentSpinner = null;
|
|
300
|
+
} else {
|
|
301
|
+
printError(message);
|
|
302
|
+
}
|
|
303
|
+
} else if (phase === 'task-complete') {
|
|
304
|
+
printSuccess(message);
|
|
305
|
+
} else if (phase === 'task-failed') {
|
|
306
|
+
printError(message);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI commands index
|
|
3
|
+
* Exports all command creators
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { createAuthCommand } from './auth.js';
|
|
7
|
+
export { createCreateCommand } from './create.js';
|
|
8
|
+
export { createStatusCommand, createValidateCommand, createSummaryCommand } from './status.js';
|
|
9
|
+
export { createResumeCommand, createResetCommand, createCancelCommand } from './resume.js';
|
|
10
|
+
export { createConfigCommand } from './config.js';
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resume command
|
|
3
|
+
* Resume an interrupted workflow
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { requireAuth } from '../../auth/index.js';
|
|
9
|
+
import {
|
|
10
|
+
resumeWorkflow,
|
|
11
|
+
getWorkflowStatus,
|
|
12
|
+
resetWorkflow,
|
|
13
|
+
cancelWorkflow,
|
|
14
|
+
} from '../../workflow/index.js';
|
|
15
|
+
import type { WorkflowPhase } from '../../types/workflow.js';
|
|
16
|
+
import {
|
|
17
|
+
printHeader,
|
|
18
|
+
printSection,
|
|
19
|
+
printSuccess,
|
|
20
|
+
printError,
|
|
21
|
+
printWarning,
|
|
22
|
+
printInfo,
|
|
23
|
+
printKeyValue,
|
|
24
|
+
startSpinner,
|
|
25
|
+
updateSpinner,
|
|
26
|
+
succeedSpinner,
|
|
27
|
+
failSpinner,
|
|
28
|
+
stopSpinner,
|
|
29
|
+
} from '../output.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create the resume command
|
|
33
|
+
*/
|
|
34
|
+
export function createResumeCommand(): Command {
|
|
35
|
+
const resume = new Command('resume')
|
|
36
|
+
.description('Resume an interrupted project workflow')
|
|
37
|
+
.argument('[directory]', 'Project directory', '.')
|
|
38
|
+
.option(
|
|
39
|
+
'--threshold <percent>',
|
|
40
|
+
'Consensus threshold percentage',
|
|
41
|
+
'95'
|
|
42
|
+
)
|
|
43
|
+
.option(
|
|
44
|
+
'--max-iterations <count>',
|
|
45
|
+
'Maximum consensus iterations',
|
|
46
|
+
'5'
|
|
47
|
+
)
|
|
48
|
+
.option(
|
|
49
|
+
'--max-retries <count>',
|
|
50
|
+
'Maximum task retries',
|
|
51
|
+
'3'
|
|
52
|
+
)
|
|
53
|
+
.action(async (directory: string, options) => {
|
|
54
|
+
const projectDir = path.resolve(directory);
|
|
55
|
+
const threshold = parseInt(options.threshold, 10);
|
|
56
|
+
const maxIterations = parseInt(options.maxIterations, 10);
|
|
57
|
+
const maxRetries = parseInt(options.maxRetries, 10);
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Check project exists
|
|
61
|
+
const status = await getWorkflowStatus(projectDir);
|
|
62
|
+
|
|
63
|
+
if (!status.exists || !status.state) {
|
|
64
|
+
printError('No project found at this location');
|
|
65
|
+
printInfo('Run "popeye-cli create <idea>" to create a new project');
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
printHeader(`Resuming: ${status.state.name}`);
|
|
70
|
+
|
|
71
|
+
printSection('Current State');
|
|
72
|
+
printKeyValue('Phase', status.state.phase);
|
|
73
|
+
printKeyValue('Status', status.state.status);
|
|
74
|
+
|
|
75
|
+
if (status.progress) {
|
|
76
|
+
printKeyValue('Progress', `${status.progress.percentComplete}%`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (status.state.status === 'complete') {
|
|
80
|
+
printSuccess('Project is already complete!');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Require authentication
|
|
85
|
+
printSection('Authentication');
|
|
86
|
+
startSpinner('Checking authentication...');
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await requireAuth();
|
|
90
|
+
succeedSpinner('Authentication verified');
|
|
91
|
+
} catch (error) {
|
|
92
|
+
failSpinner('Authentication required');
|
|
93
|
+
printError(error instanceof Error ? error.message : 'Authentication failed');
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Resume workflow
|
|
98
|
+
printSection('Resuming Workflow');
|
|
99
|
+
|
|
100
|
+
const result = await resumeWorkflow(projectDir, {
|
|
101
|
+
consensusConfig: {
|
|
102
|
+
threshold,
|
|
103
|
+
maxIterations,
|
|
104
|
+
},
|
|
105
|
+
maxRetries,
|
|
106
|
+
onProgress: (phase, message) => {
|
|
107
|
+
handleProgressUpdate(phase, message);
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Stop any running spinner
|
|
112
|
+
stopSpinner();
|
|
113
|
+
|
|
114
|
+
// Print results
|
|
115
|
+
console.log();
|
|
116
|
+
if (result.success) {
|
|
117
|
+
printHeader('Workflow Complete!');
|
|
118
|
+
|
|
119
|
+
printSection('Summary');
|
|
120
|
+
printKeyValue('Phase', result.state.phase);
|
|
121
|
+
printKeyValue('Status', result.state.status);
|
|
122
|
+
|
|
123
|
+
if (result.executionResult) {
|
|
124
|
+
printKeyValue('Tasks Completed', result.executionResult.completedTasks.toString());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
printSuccess('Project workflow completed successfully!');
|
|
128
|
+
} else {
|
|
129
|
+
printHeader('Workflow Failed');
|
|
130
|
+
|
|
131
|
+
if (result.error) {
|
|
132
|
+
printError(result.error);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
printWarning('Run "popeye-cli resume" to try again');
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
stopSpinner();
|
|
140
|
+
printError(error instanceof Error ? error.message : 'Unknown error');
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return resume;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Create the reset command
|
|
150
|
+
*/
|
|
151
|
+
export function createResetCommand(): Command {
|
|
152
|
+
const reset = new Command('reset')
|
|
153
|
+
.description('Reset project to a specific phase')
|
|
154
|
+
.argument('[directory]', 'Project directory', '.')
|
|
155
|
+
.option('-p, --phase <phase>', 'Phase to reset to (plan, execution)', 'plan')
|
|
156
|
+
.option('-f, --force', 'Force reset without confirmation')
|
|
157
|
+
.action(async (directory: string, options) => {
|
|
158
|
+
const projectDir = path.resolve(directory);
|
|
159
|
+
const phase = options.phase as WorkflowPhase;
|
|
160
|
+
|
|
161
|
+
if (!['plan', 'execution'].includes(phase)) {
|
|
162
|
+
printError(`Invalid phase: ${phase}. Use 'plan' or 'execution'.`);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// Check project exists
|
|
168
|
+
const status = await getWorkflowStatus(projectDir);
|
|
169
|
+
|
|
170
|
+
if (!status.exists || !status.state) {
|
|
171
|
+
printError('No project found at this location');
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!options.force) {
|
|
176
|
+
printWarning(`This will reset the project to the '${phase}' phase.`);
|
|
177
|
+
printWarning('A backup will be created before reset.');
|
|
178
|
+
printInfo('Use --force to skip this warning.');
|
|
179
|
+
// In a real implementation, we'd prompt for confirmation here
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
startSpinner(`Resetting to ${phase} phase...`);
|
|
183
|
+
|
|
184
|
+
await resetWorkflow(projectDir, phase);
|
|
185
|
+
|
|
186
|
+
succeedSpinner(`Reset to ${phase} phase`);
|
|
187
|
+
printSuccess('Project has been reset');
|
|
188
|
+
printInfo('Run "popeye-cli resume" to continue');
|
|
189
|
+
} catch (error) {
|
|
190
|
+
failSpinner('Reset failed');
|
|
191
|
+
printError(error instanceof Error ? error.message : 'Unknown error');
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return reset;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Create the cancel command
|
|
201
|
+
*/
|
|
202
|
+
export function createCancelCommand(): Command {
|
|
203
|
+
const cancel = new Command('cancel')
|
|
204
|
+
.description('Cancel and delete a project')
|
|
205
|
+
.argument('[directory]', 'Project directory', '.')
|
|
206
|
+
.option('-f, --force', 'Force cancel without confirmation')
|
|
207
|
+
.action(async (directory: string, options) => {
|
|
208
|
+
const projectDir = path.resolve(directory);
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
// Check project exists
|
|
212
|
+
const status = await getWorkflowStatus(projectDir);
|
|
213
|
+
|
|
214
|
+
if (!status.exists) {
|
|
215
|
+
printError('No project found at this location');
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!options.force) {
|
|
220
|
+
printWarning('This will delete all project state and cannot be undone.');
|
|
221
|
+
printInfo('Use --force to skip this warning.');
|
|
222
|
+
// In a real implementation, we'd prompt for confirmation here
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
startSpinner('Cancelling project...');
|
|
226
|
+
|
|
227
|
+
const deleted = await cancelWorkflow(projectDir);
|
|
228
|
+
|
|
229
|
+
if (deleted) {
|
|
230
|
+
succeedSpinner('Project cancelled');
|
|
231
|
+
printSuccess('Project state has been deleted');
|
|
232
|
+
} else {
|
|
233
|
+
failSpinner('No state to delete');
|
|
234
|
+
}
|
|
235
|
+
} catch (error) {
|
|
236
|
+
failSpinner('Cancel failed');
|
|
237
|
+
printError(error instanceof Error ? error.message : 'Unknown error');
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return cancel;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Handle progress updates from the workflow
|
|
247
|
+
*/
|
|
248
|
+
let currentSpinner: ReturnType<typeof startSpinner> | null = null;
|
|
249
|
+
let lastPhase = '';
|
|
250
|
+
|
|
251
|
+
function handleProgressUpdate(phase: string, message: string): void {
|
|
252
|
+
const phaseNames: Record<string, string> = {
|
|
253
|
+
'plan-init': 'Initializing',
|
|
254
|
+
'expand-idea': 'Expanding Idea',
|
|
255
|
+
'get-context': 'Analyzing Context',
|
|
256
|
+
'create-plan': 'Creating Plan',
|
|
257
|
+
'consensus': 'Consensus Review',
|
|
258
|
+
'execution-start': 'Starting Execution',
|
|
259
|
+
'task-start': 'Executing Task',
|
|
260
|
+
'task-complete': 'Task Complete',
|
|
261
|
+
'task-failed': 'Task Failed',
|
|
262
|
+
'execution-complete': 'Execution Complete',
|
|
263
|
+
'complete': 'Complete',
|
|
264
|
+
'failed': 'Failed',
|
|
265
|
+
'error': 'Error',
|
|
266
|
+
'workflow': 'Workflow',
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const phaseName = phaseNames[phase] || phase;
|
|
270
|
+
|
|
271
|
+
if (phase !== lastPhase) {
|
|
272
|
+
if (currentSpinner) {
|
|
273
|
+
if (phase === 'error' || phase === 'failed') {
|
|
274
|
+
failSpinner();
|
|
275
|
+
} else {
|
|
276
|
+
succeedSpinner();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!['complete', 'failed', 'error', 'task-complete', 'task-failed'].includes(phase)) {
|
|
281
|
+
currentSpinner = startSpinner(`${phaseName}: ${message}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
lastPhase = phase;
|
|
285
|
+
} else if (currentSpinner) {
|
|
286
|
+
updateSpinner(`${phaseName}: ${message}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (phase === 'complete') {
|
|
290
|
+
if (currentSpinner) {
|
|
291
|
+
succeedSpinner(message);
|
|
292
|
+
currentSpinner = null;
|
|
293
|
+
} else {
|
|
294
|
+
printSuccess(message);
|
|
295
|
+
}
|
|
296
|
+
} else if (phase === 'failed' || phase === 'error') {
|
|
297
|
+
if (currentSpinner) {
|
|
298
|
+
failSpinner(message);
|
|
299
|
+
currentSpinner = null;
|
|
300
|
+
} else {
|
|
301
|
+
printError(message);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|