multi-agents-cli 1.1.64 → 1.1.66
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/core/workflow/complete.js +22 -7
- package/init.js +1 -1
- package/lib/questions-flow.js +9 -9
- package/lib/ui.js +57 -7
- package/lib/validate.js +73 -0
- package/package.json +1 -1
- /package/lib/{questions.js → data-config.js} +0 -0
|
@@ -557,19 +557,34 @@ const main = async () => {
|
|
|
557
557
|
}
|
|
558
558
|
|
|
559
559
|
separator();
|
|
560
|
-
|
|
560
|
+
const boxWidth = 54;
|
|
561
|
+
const rev = (s) => `\x1b[7m${s}\x1b[27m`;
|
|
562
|
+
const pad = (s, w) => s + ' '.repeat(Math.max(0, w - s.length));
|
|
563
|
+
|
|
564
|
+
console.log('');
|
|
565
|
+
console.log(' ' + rev(' ' + pad(' What happens next', boxWidth) + ' '));
|
|
566
|
+
console.log(' ' + rev(' ' + pad('', boxWidth) + ' '));
|
|
561
567
|
|
|
562
568
|
if (nextAgent) {
|
|
563
|
-
console.log(
|
|
564
|
-
console.log(
|
|
565
|
-
console.log(` ${dim('Select: ' + nextScope + ' → ' + nextAgent)}`);
|
|
569
|
+
console.log(' ' + rev(' ' + pad(` -> Run: npm run agent`, boxWidth) + ' '));
|
|
570
|
+
console.log(' ' + rev(' ' + pad(` -> Select: ${nextScope} -> ${nextAgent}`, boxWidth) + ' '));
|
|
566
571
|
} else {
|
|
567
|
-
console.log(
|
|
572
|
+
console.log(' ' + rev(' ' + pad(` -> All agents complete - ready to deploy`, boxWidth) + ' '));
|
|
568
573
|
}
|
|
574
|
+
|
|
575
|
+
console.log(' ' + rev(' ' + pad('', boxWidth) + ' '));
|
|
576
|
+
console.log('');
|
|
569
577
|
} catch {
|
|
570
578
|
separator();
|
|
571
|
-
|
|
572
|
-
|
|
579
|
+
const boxWidth2 = 54;
|
|
580
|
+
const rev2 = (s) => `\x1b[7m${s}\x1b[27m`;
|
|
581
|
+
const pad2 = (s, w) => s + ' '.repeat(Math.max(0, w - s.length));
|
|
582
|
+
console.log('');
|
|
583
|
+
console.log(' ' + rev2(' ' + pad2(' What happens next', boxWidth2) + ' '));
|
|
584
|
+
console.log(' ' + rev2(' ' + pad2('', boxWidth2) + ' '));
|
|
585
|
+
console.log(' ' + rev2(' ' + pad2(` -> Run: npm run agent`, boxWidth2) + ' '));
|
|
586
|
+
console.log(' ' + rev2(' ' + pad2('', boxWidth2) + ' '));
|
|
587
|
+
console.log('');
|
|
573
588
|
}
|
|
574
589
|
|
|
575
590
|
console.log('\n');
|
package/init.js
CHANGED
package/lib/questions-flow.js
CHANGED
|
@@ -8,7 +8,7 @@ const {
|
|
|
8
8
|
CLIENT_FRAMEWORKS, BACKEND_FRAMEWORKS, FRAMEWORK_VERSION_FALLBACK,
|
|
9
9
|
fetchLatestVersions, STATE_OPTIONS, UI_OPTIONS, STYLING_OPTIONS,
|
|
10
10
|
DB_OPTIONS, ORM_OPTIONS_BY_DB, ORM_OPTIONS, AUTH_OPTIONS,
|
|
11
|
-
} = require('./
|
|
11
|
+
} = require('./data-config');
|
|
12
12
|
const { buildIDEOptions, verifyIDE, detectTerminal } = require('./detect');
|
|
13
13
|
|
|
14
14
|
// ── Step definitions ──────────────────────────────────────────────────────────
|
|
@@ -23,7 +23,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
23
23
|
run: async (machine, answers) => {
|
|
24
24
|
stepHeader(1, 12);
|
|
25
25
|
console.log(`\n${bold(blue('Client configuration'))}`);
|
|
26
|
-
return await selectRequired('* Client framework (required):', CLIENT_FRAMEWORKS, machine, 0);
|
|
26
|
+
return await selectRequired('* Client framework (required):', CLIENT_FRAMEWORKS, machine, 0, answers);
|
|
27
27
|
},
|
|
28
28
|
},
|
|
29
29
|
|
|
@@ -60,7 +60,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
60
60
|
run: async (machine, answers) => {
|
|
61
61
|
stepHeader(3, 12);
|
|
62
62
|
const opts = STATE_OPTIONS[answers.clientFw.value] || [];
|
|
63
|
-
return await selectOptional('State management:', opts, machine, 2);
|
|
63
|
+
return await selectOptional('State management:', opts, machine, 2, answers);
|
|
64
64
|
},
|
|
65
65
|
},
|
|
66
66
|
|
|
@@ -70,7 +70,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
70
70
|
run: async (machine, answers) => {
|
|
71
71
|
stepHeader(4, 12);
|
|
72
72
|
const opts = UI_OPTIONS[answers.clientFw.value] || [];
|
|
73
|
-
return await selectOptional('UI library:', opts, machine, 3);
|
|
73
|
+
return await selectOptional('UI library:', opts, machine, 3, answers);
|
|
74
74
|
},
|
|
75
75
|
},
|
|
76
76
|
|
|
@@ -79,7 +79,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
79
79
|
name: 'clientStyle',
|
|
80
80
|
run: async (machine, answers) => {
|
|
81
81
|
stepHeader(5, 12);
|
|
82
|
-
return await selectOptional('Styling:', STYLING_OPTIONS, machine, 4);
|
|
82
|
+
return await selectOptional('Styling:', STYLING_OPTIONS, machine, 4, answers);
|
|
83
83
|
},
|
|
84
84
|
},
|
|
85
85
|
|
|
@@ -157,7 +157,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
157
157
|
run: async (machine, answers) => {
|
|
158
158
|
stepHeader(9, 12);
|
|
159
159
|
if (!answers.backendFwObj) return null;
|
|
160
|
-
return await selectOptional('Database type:', DB_OPTIONS, machine, 8);
|
|
160
|
+
return await selectOptional('Database type:', DB_OPTIONS, machine, 8, answers);
|
|
161
161
|
},
|
|
162
162
|
},
|
|
163
163
|
|
|
@@ -169,7 +169,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
169
169
|
const { backendFwObj, backendDb } = answers;
|
|
170
170
|
if (!backendFwObj || !backendDb) return null;
|
|
171
171
|
const ormChoices = ORM_OPTIONS_BY_DB[backendDb] || ORM_OPTIONS[backendFwObj.value] || [];
|
|
172
|
-
return await selectOptional('ORM / query layer:', ormChoices, machine, 9);
|
|
172
|
+
return await selectOptional('ORM / query layer:', ormChoices, machine, 9, answers);
|
|
173
173
|
},
|
|
174
174
|
},
|
|
175
175
|
|
|
@@ -180,7 +180,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
180
180
|
stepHeader(11, 12);
|
|
181
181
|
const { backendFwObj } = answers;
|
|
182
182
|
if (!backendFwObj) return null;
|
|
183
|
-
return await selectOptional('Auth strategy:', AUTH_OPTIONS[backendFwObj.value] || [], machine, 10);
|
|
183
|
+
return await selectOptional('Auth strategy:', AUTH_OPTIONS[backendFwObj.value] || [], machine, 10, answers);
|
|
184
184
|
},
|
|
185
185
|
},
|
|
186
186
|
|
|
@@ -206,7 +206,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
206
206
|
else console.log(`\n ${'\x1b[33m'}No IDEs detected.${'\x1b[0m'}\n`);
|
|
207
207
|
|
|
208
208
|
while (true) {
|
|
209
|
-
const ideChoice = await selectRequired('* IDE / editor (required):', sorted, machine, 11);
|
|
209
|
+
const ideChoice = await selectRequired('* IDE / editor (required):', sorted, machine, 11, answers);
|
|
210
210
|
if (ideChoice === RESTART || ideChoice === BACK) return ideChoice;
|
|
211
211
|
|
|
212
212
|
if (ideChoice.cmd && !ideChoice.detected) {
|
package/lib/ui.js
CHANGED
|
@@ -121,36 +121,86 @@ const stepHeader = (stepIndex, totalSteps) => {
|
|
|
121
121
|
// ── Step selection helpers ────────────────────────────────────────────────────
|
|
122
122
|
|
|
123
123
|
const { BACK, RESTART } = require('./steps');
|
|
124
|
+
const { validateCombination } = require('./validate');
|
|
124
125
|
|
|
125
|
-
const
|
|
126
|
+
const NONE_LABEL = '→ None of these - I\'ll specify my own';
|
|
127
|
+
|
|
128
|
+
const selectRequired = async (prompt, items, stepMachine, stepIndex, context = {}) => {
|
|
126
129
|
const isFirstStep = stepIndex <= 1;
|
|
127
130
|
const navOpts = stepMachine ? stepMachine.navOptions(stepIndex, isFirstStep) : [];
|
|
128
|
-
const
|
|
131
|
+
const noneOpt = { label: NONE_LABEL };
|
|
132
|
+
const allItems = [...items, noneOpt, ...navOpts];
|
|
129
133
|
|
|
130
134
|
const idx = await arrowSelect(prompt, allItems.map(i => ({ label: typeof i === 'string' ? i : i.label })));
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
// None of these - free text input
|
|
137
|
+
if (idx === items.length) {
|
|
138
|
+
const typed = await ask(` Type your own value: `);
|
|
139
|
+
const check = validateCombination(typed, context);
|
|
140
|
+
if (!check.valid) {
|
|
141
|
+
console.log(`
|
|
142
|
+
${yellow('⚠')} ${check.reason}
|
|
143
|
+
`);
|
|
144
|
+
const opts = [
|
|
145
|
+
'→ Continue - I understand the tradeoff',
|
|
146
|
+
...check.alternatives.map(a => `→ ${a}`),
|
|
147
|
+
'→ Type a different value',
|
|
148
|
+
];
|
|
149
|
+
const choice = await arrowSelect('How would you like to proceed?', opts.map(o => ({ label: o })));
|
|
150
|
+
if (choice === 0) return typed;
|
|
151
|
+
if (choice === opts.length - 1) return selectRequired(prompt, items, stepMachine, stepIndex, context);
|
|
152
|
+
return check.alternatives[choice - 1];
|
|
153
|
+
}
|
|
154
|
+
console.log(dim(' Custom value - the agent will handle compatibility at runtime.'));
|
|
155
|
+
return typed;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (idx > items.length) {
|
|
159
|
+
const nav = navOpts[idx - items.length - 1];
|
|
134
160
|
return nav ? nav.value : RESTART;
|
|
135
161
|
}
|
|
136
162
|
return items[idx];
|
|
137
163
|
};
|
|
138
164
|
|
|
139
|
-
const selectOptional = async (prompt, items, stepMachine, stepIndex) => {
|
|
165
|
+
const selectOptional = async (prompt, items, stepMachine, stepIndex, context = {}) => {
|
|
140
166
|
if (!items || items.length === 0) return null;
|
|
141
167
|
const isFirstStep = stepIndex <= 1;
|
|
142
168
|
const navOpts = stepMachine ? stepMachine.navOptions(stepIndex, isFirstStep) : [];
|
|
143
169
|
const choices = [
|
|
144
170
|
...items.map(i => ({ label: typeof i === 'string' ? i : i.label })),
|
|
145
171
|
{ label: dim('Skip (agent will propose when needed)') },
|
|
172
|
+
{ label: NONE_LABEL },
|
|
146
173
|
...navOpts.map(n => ({ label: n.label })),
|
|
147
174
|
];
|
|
148
175
|
|
|
149
176
|
const idx = await arrowSelect(prompt, choices);
|
|
150
177
|
|
|
151
178
|
if (idx === items.length) return null; // skip
|
|
152
|
-
|
|
153
|
-
|
|
179
|
+
|
|
180
|
+
// None of these - free text input
|
|
181
|
+
if (idx === items.length + 1) {
|
|
182
|
+
const typed = await ask(` Type your own value: `);
|
|
183
|
+
const check = validateCombination(typed, context);
|
|
184
|
+
if (!check.valid) {
|
|
185
|
+
console.log(`
|
|
186
|
+
${yellow('⚠')} ${check.reason}
|
|
187
|
+
`);
|
|
188
|
+
const opts = [
|
|
189
|
+
'→ Continue - I understand the tradeoff',
|
|
190
|
+
...check.alternatives.map(a => `→ ${a}`),
|
|
191
|
+
'→ Type a different value',
|
|
192
|
+
];
|
|
193
|
+
const choice = await arrowSelect('How would you like to proceed?', opts.map(o => ({ label: o })));
|
|
194
|
+
if (choice === 0) return typed;
|
|
195
|
+
if (choice === opts.length - 1) return selectOptional(prompt, items, stepMachine, stepIndex, context);
|
|
196
|
+
return check.alternatives[choice - 1];
|
|
197
|
+
}
|
|
198
|
+
console.log(dim(' Custom value - the agent will handle compatibility at runtime.'));
|
|
199
|
+
return typed;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (idx > items.length + 1) {
|
|
203
|
+
const nav = navOpts[idx - items.length - 2];
|
|
154
204
|
return nav ? nav.value : RESTART;
|
|
155
205
|
}
|
|
156
206
|
return typeof items[idx] === 'string' ? items[idx] : items[idx].value;
|
package/lib/validate.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ── Compatibility matrix ──────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
const PYTHON_BACKENDS = ['FastAPI', 'Django'];
|
|
6
|
+
const NODE_BACKENDS = ['Express', 'NestJS', 'Fastify'];
|
|
7
|
+
const NODE_ONLY_ORMS = ['Prisma', 'TypeORM', 'Drizzle', 'Sequelize', 'MikroORM', 'raw pg driver', 'raw mysql2 driver', 'better-sqlite3'];
|
|
8
|
+
const PYTHON_ONLY_ORMS = ['SQLAlchemy', 'Tortoise ORM', 'Django ORM (built-in)'];
|
|
9
|
+
const MONGO_ONLY_ORMS = ['Beanie (MongoDB)'];
|
|
10
|
+
const NEXTJS_ONLY_LIBS = ['shadcn/ui', 'Radix UI'];
|
|
11
|
+
const MONGO_DBS = ['MongoDB'];
|
|
12
|
+
|
|
13
|
+
// ── validateCombination ───────────────────────────────────────────────────────
|
|
14
|
+
// chosen — the value just entered (string)
|
|
15
|
+
// context — answers accumulated so far { clientFw, backendFwObj, backendDb, ... }
|
|
16
|
+
// returns { valid, reason, alternatives }
|
|
17
|
+
|
|
18
|
+
const validateCombination = (chosen, context = {}) => {
|
|
19
|
+
const clientFw = context.clientFw?.value || context.clientFw || null;
|
|
20
|
+
const backendFw = context.backendFwObj?.value || context.backendFwObj || null;
|
|
21
|
+
const backendDb = context.backendDb || null;
|
|
22
|
+
|
|
23
|
+
// Node.js ORM + Python backend
|
|
24
|
+
if (NODE_ONLY_ORMS.includes(chosen) && PYTHON_BACKENDS.includes(backendFw)) {
|
|
25
|
+
return {
|
|
26
|
+
valid: false,
|
|
27
|
+
reason: `${chosen} is a Node.js ORM and does not support ${backendFw} (Python).`,
|
|
28
|
+
alternatives: backendFw === 'Django'
|
|
29
|
+
? ['Django ORM (built-in)', 'SQLAlchemy']
|
|
30
|
+
: ['SQLAlchemy', 'Tortoise ORM', 'raw asyncpg'],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Python ORM + Node.js backend
|
|
35
|
+
if (PYTHON_ONLY_ORMS.includes(chosen) && NODE_BACKENDS.includes(backendFw)) {
|
|
36
|
+
return {
|
|
37
|
+
valid: false,
|
|
38
|
+
reason: `${chosen} is a Python ORM and does not support ${backendFw} (Node.js).`,
|
|
39
|
+
alternatives: ['Prisma', 'TypeORM', 'Drizzle'],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Beanie + non-MongoDB
|
|
44
|
+
if (MONGO_ONLY_ORMS.includes(chosen) && backendDb && !MONGO_DBS.includes(backendDb)) {
|
|
45
|
+
return {
|
|
46
|
+
valid: false,
|
|
47
|
+
reason: `Beanie is a MongoDB ODM and does not support ${backendDb}.`,
|
|
48
|
+
alternatives: ['Prisma', 'TypeORM', 'Drizzle'],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Next.js-only UI libs + Vite+React
|
|
53
|
+
if (NEXTJS_ONLY_LIBS.includes(chosen) && clientFw === 'Vite+React') {
|
|
54
|
+
return {
|
|
55
|
+
valid: false,
|
|
56
|
+
reason: `${chosen} is optimized for Next.js and may not work correctly with Vite+React.`,
|
|
57
|
+
alternatives: ['MUI', 'Chakra UI', 'Ant Design'],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Ambiguous cross-boundary (e.g. dotnet as both client and backend)
|
|
62
|
+
if (chosen && backendFw && chosen.toLowerCase() === backendFw.toLowerCase()) {
|
|
63
|
+
return {
|
|
64
|
+
valid: false,
|
|
65
|
+
reason: `${chosen} appears in both client and backend selections - this may cause scope ambiguity.`,
|
|
66
|
+
alternatives: [],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { valid: true, reason: null, alternatives: [] };
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
module.exports = { validateCombination };
|
package/package.json
CHANGED
|
File without changes
|