popeye-cli 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/README.md +115 -17
- package/dist/adapters/gemini.d.ts +2 -2
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +5 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +307 -22
- package/dist/cli/interactive.js.map +1 -1
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/schema.d.ts +4 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +2 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/generators/all.d.ts +30 -0
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +5 -5
- package/dist/generators/all.js.map +1 -1
- package/dist/types/consensus.d.ts +10 -20
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +8 -3
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -2
- package/dist/types/index.js.map +1 -1
- package/dist/types/project.d.ts +15 -16
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +15 -8
- package/dist/types/project.js.map +1 -1
- package/dist/types/workflow.d.ts +2 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +2 -1
- package/dist/types/workflow.js.map +1 -1
- package/dist/upgrade/context.d.ts +37 -0
- package/dist/upgrade/context.d.ts.map +1 -0
- package/dist/upgrade/context.js +284 -0
- package/dist/upgrade/context.js.map +1 -0
- package/dist/upgrade/handlers.d.ts +103 -0
- package/dist/upgrade/handlers.d.ts.map +1 -0
- package/dist/upgrade/handlers.js +384 -0
- package/dist/upgrade/handlers.js.map +1 -0
- package/dist/upgrade/index.d.ts +26 -0
- package/dist/upgrade/index.d.ts.map +1 -0
- package/dist/upgrade/index.js +194 -0
- package/dist/upgrade/index.js.map +1 -0
- package/dist/upgrade/transitions.d.ts +34 -0
- package/dist/upgrade/transitions.d.ts.map +1 -0
- package/dist/upgrade/transitions.js +56 -0
- package/dist/upgrade/transitions.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +41 -5
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/task-workflow.d.ts.map +1 -1
- package/dist/workflow/task-workflow.js +3 -2
- package/dist/workflow/task-workflow.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/gemini.ts +2 -2
- package/src/cli/index.ts +5 -2
- package/src/cli/interactive.ts +353 -23
- package/src/config/schema.ts +2 -1
- package/src/generators/all.ts +5 -5
- package/src/types/consensus.ts +11 -5
- package/src/types/index.ts +2 -0
- package/src/types/project.ts +18 -9
- package/src/types/workflow.ts +2 -1
- package/src/upgrade/context.ts +332 -0
- package/src/upgrade/handlers.ts +477 -0
- package/src/upgrade/index.ts +244 -0
- package/src/upgrade/transitions.ts +80 -0
- package/src/workflow/plan-mode.ts +41 -7
- package/src/workflow/task-workflow.ts +3 -2
- package/tests/cli/model-command.test.ts +93 -0
- package/tests/types/project.test.ts +90 -15
- package/tests/types/workflow-schema.test.ts +59 -0
- package/tests/upgrade/context.test.ts +211 -0
- package/tests/upgrade/transitions.test.ts +85 -0
package/src/cli/interactive.ts
CHANGED
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
import * as readline from 'node:readline';
|
|
7
7
|
import { promises as fs } from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
|
|
11
|
+
// Get package version
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const packageJson = require('../../package.json');
|
|
14
|
+
const VERSION: string = packageJson.version;
|
|
9
15
|
import {
|
|
10
16
|
getAuthStatusForDisplay,
|
|
11
17
|
authenticateClaude,
|
|
@@ -22,10 +28,12 @@ import {
|
|
|
22
28
|
resumeWorkflow,
|
|
23
29
|
getWorkflowStatus,
|
|
24
30
|
getWorkflowSummary,
|
|
31
|
+
resetWorkflow,
|
|
25
32
|
} from '../workflow/index.js';
|
|
26
33
|
import {
|
|
27
34
|
analyzeProjectProgress,
|
|
28
35
|
verifyProjectCompletion,
|
|
36
|
+
storeSpecification,
|
|
29
37
|
} from '../state/index.js';
|
|
30
38
|
import { generateProject } from '../generators/index.js';
|
|
31
39
|
import {
|
|
@@ -33,8 +41,14 @@ import {
|
|
|
33
41
|
formatProjectForDisplay,
|
|
34
42
|
} from '../state/registry.js';
|
|
35
43
|
import { loadConfig, saveConfig } from '../config/index.js';
|
|
44
|
+
import { getValidUpgradeTargets, getTransitionDetails } from '../upgrade/transitions.js';
|
|
45
|
+
import { upgradeProject } from '../upgrade/index.js';
|
|
46
|
+
import { buildUpgradeContext } from '../upgrade/context.js';
|
|
47
|
+
import { OutputLanguageSchema, KNOWN_OPENAI_MODELS } from '../types/project.js';
|
|
36
48
|
import type { ProjectSpec, OutputLanguage, OpenAIModel } from '../types/project.js';
|
|
37
|
-
import
|
|
49
|
+
import { GeminiModelSchema, KNOWN_GEMINI_MODELS } from '../types/consensus.js';
|
|
50
|
+
import { OpenAIModelSchema } from '../types/project.js';
|
|
51
|
+
import type { AIProvider, GeminiModel, GrokModel } from '../types/consensus.js';
|
|
38
52
|
import {
|
|
39
53
|
printSuccess,
|
|
40
54
|
printError,
|
|
@@ -61,6 +75,9 @@ interface PopeyeProjectConfig {
|
|
|
61
75
|
projectName?: string;
|
|
62
76
|
description?: string;
|
|
63
77
|
notes?: string;
|
|
78
|
+
openaiModel?: OpenAIModel;
|
|
79
|
+
geminiModel?: GeminiModel;
|
|
80
|
+
grokModel?: GrokModel;
|
|
64
81
|
}
|
|
65
82
|
|
|
66
83
|
/**
|
|
@@ -91,7 +108,7 @@ async function readPopeyeConfig(projectDir: string): Promise<PopeyeProjectConfig
|
|
|
91
108
|
|
|
92
109
|
switch (key) {
|
|
93
110
|
case 'language':
|
|
94
|
-
if (
|
|
111
|
+
if (OutputLanguageSchema.safeParse(cleanValue).success) {
|
|
95
112
|
config.language = cleanValue as OutputLanguage;
|
|
96
113
|
}
|
|
97
114
|
break;
|
|
@@ -119,6 +136,21 @@ async function readPopeyeConfig(projectDir: string): Promise<PopeyeProjectConfig
|
|
|
119
136
|
case 'projectName':
|
|
120
137
|
config.projectName = cleanValue;
|
|
121
138
|
break;
|
|
139
|
+
case 'openaiModel':
|
|
140
|
+
if (OpenAIModelSchema.safeParse(cleanValue).success) {
|
|
141
|
+
config.openaiModel = cleanValue as OpenAIModel;
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
case 'geminiModel':
|
|
145
|
+
if (GeminiModelSchema.safeParse(cleanValue).success) {
|
|
146
|
+
config.geminiModel = cleanValue as GeminiModel;
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
case 'grokModel':
|
|
150
|
+
if (cleanValue.length > 0) {
|
|
151
|
+
config.grokModel = cleanValue;
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
122
154
|
}
|
|
123
155
|
}
|
|
124
156
|
}
|
|
@@ -159,6 +191,12 @@ async function writePopeyeConfig(
|
|
|
159
191
|
): Promise<void> {
|
|
160
192
|
const configPath = path.join(projectDir, 'popeye.md');
|
|
161
193
|
|
|
194
|
+
const modelLines = [
|
|
195
|
+
config.openaiModel ? `openaiModel: ${config.openaiModel}` : '',
|
|
196
|
+
config.geminiModel ? `geminiModel: ${config.geminiModel}` : '',
|
|
197
|
+
config.grokModel ? `grokModel: ${config.grokModel}` : '',
|
|
198
|
+
].filter(Boolean).join('\n');
|
|
199
|
+
|
|
162
200
|
const content = `---
|
|
163
201
|
# Popeye Project Configuration
|
|
164
202
|
language: ${config.language}
|
|
@@ -167,6 +205,7 @@ arbitrator: ${config.enableArbitration ? config.arbitrator : 'off'}
|
|
|
167
205
|
created: ${config.created}
|
|
168
206
|
lastRun: ${new Date().toISOString()}
|
|
169
207
|
${config.projectName ? `projectName: ${config.projectName}` : ''}
|
|
208
|
+
${modelLines}
|
|
170
209
|
---
|
|
171
210
|
|
|
172
211
|
# ${config.projectName || 'Popeye Project'}
|
|
@@ -217,6 +256,9 @@ function applyPopeyeConfig(state: SessionState, config: PopeyeProjectConfig): vo
|
|
|
217
256
|
state.reviewer = config.reviewer;
|
|
218
257
|
state.arbitrator = config.arbitrator;
|
|
219
258
|
state.enableArbitration = config.enableArbitration;
|
|
259
|
+
if (config.openaiModel) state.openaiModel = config.openaiModel;
|
|
260
|
+
if (config.geminiModel) state.geminiModel = config.geminiModel;
|
|
261
|
+
if (config.grokModel) state.grokModel = config.grokModel;
|
|
220
262
|
}
|
|
221
263
|
|
|
222
264
|
// Note: startSpinner, succeedSpinner, failSpinner, stopSpinner are used in handleIdea
|
|
@@ -241,8 +283,9 @@ const box = {
|
|
|
241
283
|
interface SessionState {
|
|
242
284
|
projectDir: string | null;
|
|
243
285
|
language: OutputLanguage;
|
|
244
|
-
|
|
286
|
+
openaiModel: OpenAIModel;
|
|
245
287
|
geminiModel: GeminiModel;
|
|
288
|
+
grokModel: GrokModel;
|
|
246
289
|
claudeAuth: boolean;
|
|
247
290
|
openaiAuth: boolean;
|
|
248
291
|
geminiAuth: boolean;
|
|
@@ -264,7 +307,7 @@ function getTerminalWidth(): number {
|
|
|
264
307
|
*/
|
|
265
308
|
function drawHeader(): void {
|
|
266
309
|
const width = getTerminalWidth();
|
|
267
|
-
const title =
|
|
310
|
+
const title = ` Popeye CLI v${VERSION} `;
|
|
268
311
|
const subtitle = ' Autonomous Code Generation with AI Consensus ';
|
|
269
312
|
|
|
270
313
|
// Top border
|
|
@@ -780,7 +823,9 @@ function showHelp(): void {
|
|
|
780
823
|
['/config', 'Show/change configuration'],
|
|
781
824
|
['/config reviewer', 'Set reviewer (openai/gemini/grok)'],
|
|
782
825
|
['/config arbitrator', 'Set arbitrator (openai/gemini/grok/off)'],
|
|
783
|
-
['/lang <lang>', 'Set language (
|
|
826
|
+
['/lang <lang>', 'Set language (be/fe/fs/web/all)'],
|
|
827
|
+
['/model [provider] [model]', 'Show/set AI model (openai/gemini/grok)'],
|
|
828
|
+
['/upgrade [target]', 'Upgrade project type (e.g., fullstack -> all)'],
|
|
784
829
|
['/new <idea>', 'Force start a new project (skips existing check)'],
|
|
785
830
|
['/resume', 'Resume interrupted project'],
|
|
786
831
|
['/clear', 'Clear screen'],
|
|
@@ -933,6 +978,10 @@ async function handleInput(input: string, state: SessionState): Promise<boolean>
|
|
|
933
978
|
await handleResume(state, args);
|
|
934
979
|
break;
|
|
935
980
|
|
|
981
|
+
case '/upgrade':
|
|
982
|
+
await handleUpgrade(state, args);
|
|
983
|
+
break;
|
|
984
|
+
|
|
936
985
|
case '/new':
|
|
937
986
|
// Force start a new project even if existing projects found
|
|
938
987
|
if (args.length === 0) {
|
|
@@ -1090,9 +1139,13 @@ async function handleConfig(state: SessionState, args: string[] = []): Promise<v
|
|
|
1090
1139
|
}
|
|
1091
1140
|
return;
|
|
1092
1141
|
|
|
1142
|
+
case 'model':
|
|
1143
|
+
handleModel(args.slice(1), state);
|
|
1144
|
+
return;
|
|
1145
|
+
|
|
1093
1146
|
default:
|
|
1094
1147
|
printError(`Unknown config option: ${subcommand}`);
|
|
1095
|
-
printInfo('Options: reviewer, arbitrator, language');
|
|
1148
|
+
printInfo('Options: reviewer, arbitrator, language, model');
|
|
1096
1149
|
return;
|
|
1097
1150
|
}
|
|
1098
1151
|
}
|
|
@@ -1110,11 +1163,16 @@ async function handleConfig(state: SessionState, args: string[] = []): Promise<v
|
|
|
1110
1163
|
console.log(` ${theme.dim('Grok:')} ${state.grokAuth ? theme.success('● Ready') : theme.dim('○ Not configured')}`);
|
|
1111
1164
|
console.log();
|
|
1112
1165
|
console.log(theme.primary.bold(' AI Configuration:'));
|
|
1113
|
-
const configReviewerName = state.reviewer === 'openai' ?
|
|
1166
|
+
const configReviewerName = state.reviewer === 'openai' ? `OpenAI (${state.openaiModel})` : state.reviewer === 'grok' ? `Grok (${state.grokModel})` : `Gemini (${state.geminiModel})`;
|
|
1114
1167
|
const configArbitratorName = state.arbitrator === 'openai' ? 'OpenAI' : state.arbitrator === 'grok' ? 'Grok' : 'Gemini';
|
|
1115
1168
|
console.log(` ${theme.dim('Reviewer:')} ${theme.primary(configReviewerName)}`);
|
|
1116
1169
|
console.log(` ${theme.dim('Arbitrator:')} ${state.enableArbitration ? theme.primary(configArbitratorName) : theme.dim('Disabled')}`);
|
|
1117
1170
|
console.log();
|
|
1171
|
+
console.log(theme.primary.bold(' Models:'));
|
|
1172
|
+
console.log(` ${theme.dim('OpenAI:')} ${theme.primary(state.openaiModel)}`);
|
|
1173
|
+
console.log(` ${theme.dim('Gemini:')} ${theme.primary(state.geminiModel)}`);
|
|
1174
|
+
console.log(` ${theme.dim('Grok:')} ${theme.primary(state.grokModel)}`);
|
|
1175
|
+
console.log();
|
|
1118
1176
|
console.log(theme.primary.bold(' Consensus:'));
|
|
1119
1177
|
console.log(` ${theme.dim('Threshold:')} ${config.consensus.threshold}%`);
|
|
1120
1178
|
console.log(` ${theme.dim('Max Iters:')} ${config.consensus.max_disagreements}`);
|
|
@@ -1123,6 +1181,7 @@ async function handleConfig(state: SessionState, args: string[] = []): Promise<v
|
|
|
1123
1181
|
console.log(theme.dim(' /config reviewer <openai|gemini|grok>'));
|
|
1124
1182
|
console.log(theme.dim(' /config arbitrator <openai|gemini|grok|off>'));
|
|
1125
1183
|
console.log(theme.dim(' /config language <be|fe|fs|web|all>'));
|
|
1184
|
+
console.log(theme.dim(' /config model <provider> <model>'));
|
|
1126
1185
|
console.log();
|
|
1127
1186
|
}
|
|
1128
1187
|
|
|
@@ -1178,27 +1237,281 @@ function handleLanguage(args: string[], state: SessionState): void {
|
|
|
1178
1237
|
}
|
|
1179
1238
|
|
|
1180
1239
|
/**
|
|
1181
|
-
*
|
|
1240
|
+
* Available models per provider for display
|
|
1182
1241
|
*/
|
|
1183
|
-
|
|
1184
|
-
|
|
1242
|
+
const KNOWN_MODELS: Record<string, readonly string[]> = {
|
|
1243
|
+
openai: KNOWN_OPENAI_MODELS,
|
|
1244
|
+
gemini: KNOWN_GEMINI_MODELS,
|
|
1245
|
+
grok: ['grok-3', 'grok-3-mini', 'grok-2'],
|
|
1246
|
+
};
|
|
1185
1247
|
|
|
1248
|
+
/**
|
|
1249
|
+
* Handle /model command - multi-provider model switching
|
|
1250
|
+
*/
|
|
1251
|
+
function handleModel(args: string[], state: SessionState): void {
|
|
1252
|
+
// /model (no args) -> show all provider models
|
|
1186
1253
|
if (args.length === 0) {
|
|
1187
1254
|
console.log();
|
|
1188
|
-
|
|
1189
|
-
|
|
1255
|
+
console.log(theme.primary.bold(' Models:'));
|
|
1256
|
+
console.log(` ${theme.dim('OpenAI:')} ${theme.primary(state.openaiModel)}`);
|
|
1257
|
+
console.log(` ${theme.dim('Gemini:')} ${theme.primary(state.geminiModel)}`);
|
|
1258
|
+
console.log(` ${theme.dim('Grok:')} ${theme.primary(state.grokModel)}`);
|
|
1259
|
+
console.log();
|
|
1260
|
+
console.log(theme.secondary(' Usage:'));
|
|
1261
|
+
console.log(theme.dim(' /model <provider> <model> Set model for provider'));
|
|
1262
|
+
console.log(theme.dim(' /model <provider> list Show available models'));
|
|
1263
|
+
console.log(theme.dim(' /model <openai-model> Set OpenAI model (shortcut)'));
|
|
1190
1264
|
return;
|
|
1191
1265
|
}
|
|
1192
1266
|
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1267
|
+
const firstArg = args[0].toLowerCase();
|
|
1268
|
+
|
|
1269
|
+
// Check if first arg is a provider
|
|
1270
|
+
if (firstArg === 'openai' || firstArg === 'gemini' || firstArg === 'grok') {
|
|
1271
|
+
const provider = firstArg;
|
|
1272
|
+
|
|
1273
|
+
// /model <provider> or /model <provider> list -> show known models
|
|
1274
|
+
if (args.length === 1 || args[1]?.toLowerCase() === 'list') {
|
|
1275
|
+
console.log();
|
|
1276
|
+
const currentModel = provider === 'openai' ? state.openaiModel
|
|
1277
|
+
: provider === 'gemini' ? state.geminiModel : state.grokModel;
|
|
1278
|
+
console.log(theme.primary.bold(` ${provider} models:`));
|
|
1279
|
+
console.log(` ${theme.dim('Current:')} ${theme.primary(currentModel)}`);
|
|
1280
|
+
console.log(` ${theme.dim('Known models:')}`);
|
|
1281
|
+
for (const m of KNOWN_MODELS[provider]) {
|
|
1282
|
+
const marker = m === currentModel ? theme.success(' (active)') : '';
|
|
1283
|
+
console.log(` ${theme.secondary(m)}${marker}`);
|
|
1284
|
+
}
|
|
1285
|
+
console.log();
|
|
1286
|
+
console.log(theme.dim(' Custom models are also accepted (e.g., gpt-5, gemini-2.5-pro)'));
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// /model <provider> <model> -> set model (warn if unknown but accept)
|
|
1291
|
+
const newModel = args[1];
|
|
1292
|
+
|
|
1293
|
+
if (!newModel || newModel.length === 0) {
|
|
1294
|
+
printError('Model name must not be empty.');
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
const isKnown = KNOWN_MODELS[provider].includes(newModel);
|
|
1299
|
+
|
|
1300
|
+
if (provider === 'openai') {
|
|
1301
|
+
state.openaiModel = newModel;
|
|
1302
|
+
} else if (provider === 'gemini') {
|
|
1303
|
+
state.geminiModel = newModel;
|
|
1304
|
+
} else if (provider === 'grok') {
|
|
1305
|
+
state.grokModel = newModel;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
console.log();
|
|
1309
|
+
if (isKnown) {
|
|
1310
|
+
printSuccess(`${provider} model set to ${newModel}`);
|
|
1311
|
+
} else {
|
|
1312
|
+
printSuccess(`${provider} model set to ${newModel}`);
|
|
1313
|
+
printInfo(`Note: '${newModel}' is not in the known models list. Make sure it's a valid ${provider} model.`);
|
|
1314
|
+
}
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// Backward compat: /model <known-openai-model> (auto-detect known OpenAI model name)
|
|
1319
|
+
if ((KNOWN_OPENAI_MODELS as readonly string[]).includes(firstArg)) {
|
|
1320
|
+
state.openaiModel = firstArg;
|
|
1321
|
+
console.log();
|
|
1322
|
+
printSuccess(`OpenAI model set to ${firstArg}`);
|
|
1196
1323
|
return;
|
|
1197
1324
|
}
|
|
1198
1325
|
|
|
1199
|
-
|
|
1326
|
+
printError(`Unknown provider: ${firstArg}`);
|
|
1327
|
+
printInfo('Use: /model <openai|gemini|grok> <model>');
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/**
|
|
1331
|
+
* Handle /upgrade command - upgrade project type
|
|
1332
|
+
*/
|
|
1333
|
+
async function handleUpgrade(state: SessionState, args: string[]): Promise<void> {
|
|
1334
|
+
if (!state.projectDir) {
|
|
1335
|
+
printError('No active project. Start or resume a project first.');
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// Load current state to get language
|
|
1340
|
+
const status = await getWorkflowStatus(state.projectDir);
|
|
1341
|
+
if (!status.exists || !status.state) {
|
|
1342
|
+
printError('No project state found in current directory.');
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
const currentLanguage = status.state.language;
|
|
1347
|
+
const validTargets = getValidUpgradeTargets(currentLanguage);
|
|
1348
|
+
|
|
1349
|
+
if (validTargets.length === 0) {
|
|
1350
|
+
printInfo(`Project type '${currentLanguage}' is already at maximum scope. No upgrades available.`);
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// Determine target
|
|
1355
|
+
let targetLanguage: OutputLanguage | null = null;
|
|
1356
|
+
|
|
1357
|
+
if (args.length > 0) {
|
|
1358
|
+
const langAliases: Record<string, OutputLanguage> = {
|
|
1359
|
+
'py': 'python', 'python': 'python', 'be': 'python', 'backend': 'python',
|
|
1360
|
+
'ts': 'typescript', 'typescript': 'typescript', 'fe': 'typescript', 'frontend': 'typescript',
|
|
1361
|
+
'fs': 'fullstack', 'fullstack': 'fullstack',
|
|
1362
|
+
'web': 'website', 'website': 'website',
|
|
1363
|
+
'all': 'all',
|
|
1364
|
+
};
|
|
1365
|
+
const resolved = langAliases[args[0].toLowerCase()];
|
|
1366
|
+
if (resolved && validTargets.includes(resolved)) {
|
|
1367
|
+
targetLanguage = resolved;
|
|
1368
|
+
} else if (resolved) {
|
|
1369
|
+
printError(`Cannot upgrade from '${currentLanguage}' to '${resolved}'.`);
|
|
1370
|
+
printInfo(`Valid targets: ${validTargets.join(', ')}`);
|
|
1371
|
+
return;
|
|
1372
|
+
} else {
|
|
1373
|
+
printError(`Unknown target: ${args[0]}`);
|
|
1374
|
+
printInfo(`Valid targets: ${validTargets.join(', ')}`);
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
} else {
|
|
1378
|
+
// Prompt selection
|
|
1379
|
+
const target = await promptSelection(
|
|
1380
|
+
`Upgrade '${currentLanguage}' project to:`,
|
|
1381
|
+
validTargets.map((t) => {
|
|
1382
|
+
const details = getTransitionDetails(currentLanguage, t);
|
|
1383
|
+
return {
|
|
1384
|
+
value: t,
|
|
1385
|
+
label: `${t} - ${details?.description || ''}`,
|
|
1386
|
+
};
|
|
1387
|
+
}),
|
|
1388
|
+
validTargets[0],
|
|
1389
|
+
);
|
|
1390
|
+
targetLanguage = target as OutputLanguage;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
if (!targetLanguage) return;
|
|
1394
|
+
|
|
1395
|
+
const transition = getTransitionDetails(currentLanguage, targetLanguage);
|
|
1396
|
+
if (!transition) return;
|
|
1397
|
+
|
|
1398
|
+
// Show dry-run summary
|
|
1399
|
+
console.log();
|
|
1400
|
+
console.log(theme.primary.bold(' Upgrade Summary:'));
|
|
1401
|
+
console.log(` ${theme.dim('From:')} ${theme.primary(currentLanguage)}`);
|
|
1402
|
+
console.log(` ${theme.dim('To:')} ${theme.primary(targetLanguage)}`);
|
|
1403
|
+
console.log(` ${theme.dim('New apps:')} ${transition.newApps.join(', ') || 'none'}`);
|
|
1404
|
+
console.log(` ${theme.dim('Restructure:')} ${transition.requiresRestructure ? 'Yes - code will be moved to apps/' : 'No'}`);
|
|
1405
|
+
console.log(` ${theme.dim('Description:')} ${transition.description}`);
|
|
1200
1406
|
console.log();
|
|
1201
|
-
|
|
1407
|
+
|
|
1408
|
+
// Confirm
|
|
1409
|
+
const confirmed = await promptYesNo(
|
|
1410
|
+
theme.primary('Proceed with upgrade?'),
|
|
1411
|
+
true,
|
|
1412
|
+
);
|
|
1413
|
+
|
|
1414
|
+
if (!confirmed) {
|
|
1415
|
+
printInfo('Upgrade cancelled.');
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// Execute upgrade
|
|
1420
|
+
console.log();
|
|
1421
|
+
startSpinner(`Upgrading ${currentLanguage} -> ${targetLanguage}...`);
|
|
1422
|
+
|
|
1423
|
+
const result = await upgradeProject(state.projectDir, targetLanguage);
|
|
1424
|
+
|
|
1425
|
+
if (result.success) {
|
|
1426
|
+
succeedSpinner(`Upgraded to ${targetLanguage}`);
|
|
1427
|
+
state.language = targetLanguage;
|
|
1428
|
+
|
|
1429
|
+
console.log();
|
|
1430
|
+
if (result.filesCreated.length > 0) {
|
|
1431
|
+
printInfo(`Created ${result.filesCreated.length} new files`);
|
|
1432
|
+
}
|
|
1433
|
+
if (result.filesMoved.length > 0) {
|
|
1434
|
+
printInfo(`Moved ${result.filesMoved.length} items`);
|
|
1435
|
+
}
|
|
1436
|
+
printSuccess(`Project upgraded from '${currentLanguage}' to '${targetLanguage}'`);
|
|
1437
|
+
|
|
1438
|
+
// Build upgrade context for planning
|
|
1439
|
+
console.log();
|
|
1440
|
+
startSpinner('Building expansion context...');
|
|
1441
|
+
|
|
1442
|
+
const upgradeContext = await buildUpgradeContext(
|
|
1443
|
+
state.projectDir,
|
|
1444
|
+
transition,
|
|
1445
|
+
status.state!.idea || 'Project expansion',
|
|
1446
|
+
currentLanguage,
|
|
1447
|
+
);
|
|
1448
|
+
|
|
1449
|
+
succeedSpinner('Expansion context ready');
|
|
1450
|
+
|
|
1451
|
+
// Show what will be planned
|
|
1452
|
+
console.log();
|
|
1453
|
+
console.log(theme.primary.bold(' Expansion Planning:'));
|
|
1454
|
+
console.log(` ${theme.dim('Existing apps:')} ${upgradeContext.existingApps.join(', ')} (already built)`);
|
|
1455
|
+
console.log(` ${theme.dim('New apps:')} ${upgradeContext.newApps.join(', ')} (will be planned)`);
|
|
1456
|
+
console.log();
|
|
1457
|
+
|
|
1458
|
+
// Ask user if they want to start planning now
|
|
1459
|
+
const startPlanning = await promptYesNo(
|
|
1460
|
+
theme.primary('Start planning the new apps now?'),
|
|
1461
|
+
true,
|
|
1462
|
+
);
|
|
1463
|
+
|
|
1464
|
+
if (!startPlanning) {
|
|
1465
|
+
printInfo('You can start planning later with /resume');
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// Reset state to plan phase so workflow re-plans for the expanded project
|
|
1470
|
+
// This clears old plan/milestones but keeps the idea and project metadata
|
|
1471
|
+
await resetWorkflow(state.projectDir, 'plan');
|
|
1472
|
+
|
|
1473
|
+
// Clear old specification so the idea gets re-expanded for the new project scope
|
|
1474
|
+
// The upgrade context will guide the planner to focus on new apps
|
|
1475
|
+
await storeSpecification(state.projectDir, '');
|
|
1476
|
+
|
|
1477
|
+
console.log();
|
|
1478
|
+
printInfo('Starting expansion planning...');
|
|
1479
|
+
console.log();
|
|
1480
|
+
|
|
1481
|
+
const workflowResult = await resumeWorkflow(state.projectDir, {
|
|
1482
|
+
consensusConfig: {
|
|
1483
|
+
reviewer: state.reviewer,
|
|
1484
|
+
arbitrator: state.arbitrator,
|
|
1485
|
+
enableArbitration: state.enableArbitration,
|
|
1486
|
+
openaiModel: state.openaiModel,
|
|
1487
|
+
geminiModel: state.geminiModel,
|
|
1488
|
+
grokModel: state.grokModel,
|
|
1489
|
+
},
|
|
1490
|
+
additionalContext: upgradeContext.summary,
|
|
1491
|
+
onProgress: (phase, message) => {
|
|
1492
|
+
console.log(` ${theme.dim(`[${phase}]`)} ${message}`);
|
|
1493
|
+
},
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
console.log();
|
|
1497
|
+
if (workflowResult.success) {
|
|
1498
|
+
printSuccess('Expansion planning and implementation complete!');
|
|
1499
|
+
console.log(` ${theme.dim('Location:')} ${state.projectDir}`);
|
|
1500
|
+
} else if (workflowResult.rateLimitPaused) {
|
|
1501
|
+
console.log(` ${theme.warning('Rate Limit Reached')}`);
|
|
1502
|
+
console.log(` ${theme.dim(workflowResult.error || 'API rate limit exceeded')}`);
|
|
1503
|
+
console.log();
|
|
1504
|
+
console.log(` ${theme.info('Your progress has been saved.')}`);
|
|
1505
|
+
console.log(` ${theme.dim('Run')} ${theme.highlight('/resume')} ${theme.dim('after the rate limit resets to continue.')}`);
|
|
1506
|
+
} else {
|
|
1507
|
+
printError(workflowResult.error || 'Expansion workflow failed');
|
|
1508
|
+
printInfo('Use /resume to retry.');
|
|
1509
|
+
}
|
|
1510
|
+
} else {
|
|
1511
|
+
failSpinner('Upgrade failed');
|
|
1512
|
+
printError(result.error || 'Unknown error during upgrade');
|
|
1513
|
+
printInfo('All changes have been rolled back.');
|
|
1514
|
+
}
|
|
1202
1515
|
}
|
|
1203
1516
|
|
|
1204
1517
|
/**
|
|
@@ -1618,6 +1931,9 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
1618
1931
|
reviewer: state.reviewer,
|
|
1619
1932
|
arbitrator: state.arbitrator,
|
|
1620
1933
|
enableArbitration: state.enableArbitration,
|
|
1934
|
+
openaiModel: state.openaiModel,
|
|
1935
|
+
geminiModel: state.geminiModel,
|
|
1936
|
+
grokModel: state.grokModel,
|
|
1621
1937
|
},
|
|
1622
1938
|
additionalContext,
|
|
1623
1939
|
onProgress: (phase, message) => {
|
|
@@ -1771,7 +2087,7 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
1771
2087
|
idea: discovered.idea || discovered.plan?.slice(0, 500) || `Continue developing ${projectName}`,
|
|
1772
2088
|
name: projectName,
|
|
1773
2089
|
language: discovered.language || state.language,
|
|
1774
|
-
openaiModel: state.
|
|
2090
|
+
openaiModel: state.openaiModel,
|
|
1775
2091
|
outputDir: state.projectDir,
|
|
1776
2092
|
};
|
|
1777
2093
|
|
|
@@ -1789,6 +2105,9 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
1789
2105
|
reviewer: state.reviewer,
|
|
1790
2106
|
arbitrator: state.arbitrator,
|
|
1791
2107
|
enableArbitration: state.enableArbitration,
|
|
2108
|
+
openaiModel: state.openaiModel,
|
|
2109
|
+
geminiModel: state.geminiModel,
|
|
2110
|
+
grokModel: state.grokModel,
|
|
1792
2111
|
},
|
|
1793
2112
|
onProgress: (phase, message) => {
|
|
1794
2113
|
console.log(` ${theme.dim(`[${phase}]`)} ${message}`);
|
|
@@ -2028,14 +2347,14 @@ async function handleIdea(idea: string, state: SessionState): Promise<void> {
|
|
|
2028
2347
|
console.log(` ${theme.dim('Idea:')} ${idea}`);
|
|
2029
2348
|
console.log(` ${theme.dim('Name:')} ${theme.primary(projectName)}`);
|
|
2030
2349
|
console.log(` ${theme.dim('Language:')} ${theme.primary(state.language)}`);
|
|
2031
|
-
console.log(` ${theme.dim('Model:')} ${theme.secondary(state.
|
|
2350
|
+
console.log(` ${theme.dim('Model:')} ${theme.secondary(state.openaiModel)}`);
|
|
2032
2351
|
console.log();
|
|
2033
2352
|
|
|
2034
2353
|
const spec: ProjectSpec = {
|
|
2035
2354
|
idea,
|
|
2036
2355
|
name: projectName,
|
|
2037
2356
|
language: state.language,
|
|
2038
|
-
openaiModel: state.
|
|
2357
|
+
openaiModel: state.openaiModel,
|
|
2039
2358
|
outputDir: cwd,
|
|
2040
2359
|
};
|
|
2041
2360
|
|
|
@@ -2061,6 +2380,9 @@ async function handleIdea(idea: string, state: SessionState): Promise<void> {
|
|
|
2061
2380
|
lastRun: new Date().toISOString(),
|
|
2062
2381
|
projectName,
|
|
2063
2382
|
description: idea,
|
|
2383
|
+
openaiModel: state.openaiModel,
|
|
2384
|
+
geminiModel: state.geminiModel,
|
|
2385
|
+
grokModel: state.grokModel,
|
|
2064
2386
|
});
|
|
2065
2387
|
printInfo('Created popeye.md with project configuration');
|
|
2066
2388
|
|
|
@@ -2079,7 +2401,9 @@ async function handleIdea(idea: string, state: SessionState): Promise<void> {
|
|
|
2079
2401
|
reviewer: state.reviewer,
|
|
2080
2402
|
arbitrator: state.arbitrator,
|
|
2081
2403
|
enableArbitration: state.enableArbitration,
|
|
2404
|
+
openaiModel: state.openaiModel,
|
|
2082
2405
|
geminiModel: state.geminiModel,
|
|
2406
|
+
grokModel: state.grokModel,
|
|
2083
2407
|
},
|
|
2084
2408
|
onProgress: (phase, message) => {
|
|
2085
2409
|
console.log(` ${theme.dim(`[${phase}]`)} ${message}`);
|
|
@@ -2139,14 +2463,14 @@ async function handleNewProject(idea: string, state: SessionState): Promise<void
|
|
|
2139
2463
|
console.log(` ${theme.dim('Idea:')} ${idea}`);
|
|
2140
2464
|
console.log(` ${theme.dim('Name:')} ${theme.primary(projectName)}`);
|
|
2141
2465
|
console.log(` ${theme.dim('Language:')} ${theme.primary(state.language)}`);
|
|
2142
|
-
console.log(` ${theme.dim('Model:')} ${theme.secondary(state.
|
|
2466
|
+
console.log(` ${theme.dim('Model:')} ${theme.secondary(state.openaiModel)}`);
|
|
2143
2467
|
console.log();
|
|
2144
2468
|
|
|
2145
2469
|
const spec: ProjectSpec = {
|
|
2146
2470
|
idea,
|
|
2147
2471
|
name: projectName,
|
|
2148
2472
|
language: state.language,
|
|
2149
|
-
openaiModel: state.
|
|
2473
|
+
openaiModel: state.openaiModel,
|
|
2150
2474
|
outputDir: cwd,
|
|
2151
2475
|
};
|
|
2152
2476
|
|
|
@@ -2172,6 +2496,9 @@ async function handleNewProject(idea: string, state: SessionState): Promise<void
|
|
|
2172
2496
|
lastRun: new Date().toISOString(),
|
|
2173
2497
|
projectName,
|
|
2174
2498
|
description: idea,
|
|
2499
|
+
openaiModel: state.openaiModel,
|
|
2500
|
+
geminiModel: state.geminiModel,
|
|
2501
|
+
grokModel: state.grokModel,
|
|
2175
2502
|
});
|
|
2176
2503
|
printInfo('Created popeye.md with project configuration');
|
|
2177
2504
|
|
|
@@ -2190,7 +2517,9 @@ async function handleNewProject(idea: string, state: SessionState): Promise<void
|
|
|
2190
2517
|
reviewer: state.reviewer,
|
|
2191
2518
|
arbitrator: state.arbitrator,
|
|
2192
2519
|
enableArbitration: state.enableArbitration,
|
|
2520
|
+
openaiModel: state.openaiModel,
|
|
2193
2521
|
geminiModel: state.geminiModel,
|
|
2522
|
+
grokModel: state.grokModel,
|
|
2194
2523
|
},
|
|
2195
2524
|
onProgress: (phase, message) => {
|
|
2196
2525
|
console.log(` ${theme.dim(`[${phase}]`)} ${message}`);
|
|
@@ -2233,8 +2562,9 @@ export async function startInteractiveMode(): Promise<void> {
|
|
|
2233
2562
|
const state: SessionState = {
|
|
2234
2563
|
projectDir: process.cwd(),
|
|
2235
2564
|
language: config.project.default_language,
|
|
2236
|
-
|
|
2565
|
+
openaiModel: config.apis.openai.model,
|
|
2237
2566
|
geminiModel: 'gemini-2.0-flash',
|
|
2567
|
+
grokModel: config.apis.grok.model,
|
|
2238
2568
|
claudeAuth: false,
|
|
2239
2569
|
openaiAuth: false,
|
|
2240
2570
|
geminiAuth: false,
|
package/src/config/schema.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
+
import { OutputLanguageSchema } from '../types/project.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Consensus settings schema
|
|
@@ -92,7 +93,7 @@ export const TypeScriptSettingsSchema = z.object({
|
|
|
92
93
|
* Project defaults schema
|
|
93
94
|
*/
|
|
94
95
|
export const ProjectSettingsSchema = z.object({
|
|
95
|
-
default_language:
|
|
96
|
+
default_language: OutputLanguageSchema.default('python'),
|
|
96
97
|
python: PythonSettingsSchema.default({
|
|
97
98
|
package_manager: 'pip',
|
|
98
99
|
test_framework: 'pytest',
|
package/src/generators/all.ts
CHANGED
|
@@ -43,7 +43,7 @@ function toPythonPackageName(name: string): string {
|
|
|
43
43
|
/**
|
|
44
44
|
* Generate workspace.json for "all" projects
|
|
45
45
|
*/
|
|
46
|
-
function generateAllWorkspaceJson(projectName: string): string {
|
|
46
|
+
export function generateAllWorkspaceJson(projectName: string): string {
|
|
47
47
|
const packageName = toPythonPackageName(projectName);
|
|
48
48
|
|
|
49
49
|
const config: WorkspaceConfig = {
|
|
@@ -131,7 +131,7 @@ function generateAllWorkspaceJson(projectName: string): string {
|
|
|
131
131
|
/**
|
|
132
132
|
* Generate root package.json for npm workspaces
|
|
133
133
|
*/
|
|
134
|
-
function generateRootPackageJson(projectName: string): string {
|
|
134
|
+
export function generateRootPackageJson(projectName: string): string {
|
|
135
135
|
return JSON.stringify(
|
|
136
136
|
{
|
|
137
137
|
name: `@${projectName}/root`,
|
|
@@ -164,7 +164,7 @@ function generateRootPackageJson(projectName: string): string {
|
|
|
164
164
|
/**
|
|
165
165
|
* Generate docker-compose.yml for "all" projects
|
|
166
166
|
*/
|
|
167
|
-
function generateAllDockerCompose(projectName: string): string {
|
|
167
|
+
export function generateAllDockerCompose(projectName: string): string {
|
|
168
168
|
return `version: '3.8'
|
|
169
169
|
|
|
170
170
|
services:
|
|
@@ -342,7 +342,7 @@ Generated by [Popeye CLI](https://github.com/popeye-cli/popeye)
|
|
|
342
342
|
/**
|
|
343
343
|
* Generate design tokens package
|
|
344
344
|
*/
|
|
345
|
-
function generateDesignTokensPackage(projectName: string): {
|
|
345
|
+
export function generateDesignTokensPackage(projectName: string): {
|
|
346
346
|
files: Array<{ path: string; content: string }>;
|
|
347
347
|
} {
|
|
348
348
|
return {
|
|
@@ -496,7 +496,7 @@ export default preset;
|
|
|
496
496
|
/**
|
|
497
497
|
* Generate UI components package
|
|
498
498
|
*/
|
|
499
|
-
function generateUiPackage(projectName: string): {
|
|
499
|
+
export function generateUiPackage(projectName: string): {
|
|
500
500
|
files: Array<{ path: string; content: string }>;
|
|
501
501
|
} {
|
|
502
502
|
return {
|