multi-agents-cli 1.1.68 → 1.1.70
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/lib/data-config.js +9 -0
- package/lib/questions-flow.js +9 -8
- package/lib/ui.js +37 -35
- package/lib/validate.js +11 -4
- package/package.json +1 -1
package/lib/data-config.js
CHANGED
|
@@ -250,7 +250,16 @@ const IDE_CANDIDATES = [
|
|
|
250
250
|
|
|
251
251
|
// ── Exports ───────────────────────────────────────────────────────────────────
|
|
252
252
|
|
|
253
|
+
// ── Block identifiers ────────────────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
const BLOCKS = {
|
|
256
|
+
CLIENT: 'client',
|
|
257
|
+
BACKEND: 'backend',
|
|
258
|
+
SHARED: 'shared',
|
|
259
|
+
};
|
|
260
|
+
|
|
253
261
|
module.exports = {
|
|
262
|
+
BLOCKS,
|
|
254
263
|
FRAMEWORK_CONVENTIONS,
|
|
255
264
|
CLIENT_FRAMEWORKS,
|
|
256
265
|
BACKEND_FRAMEWORKS,
|
package/lib/questions-flow.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// ── Imports ───────────────────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
5
|
const { dim, bold, blue, green, cyan, separator, arrowSelect, arrowConfirm, selectRequired, selectOptional, stepHeader } = require('./ui');
|
|
6
|
+
const { BLOCKS } = require('./data-config');
|
|
6
7
|
const { BACK, RESTART } = require('./steps');
|
|
7
8
|
const {
|
|
8
9
|
CLIENT_FRAMEWORKS, BACKEND_FRAMEWORKS, FRAMEWORK_VERSION_FALLBACK,
|
|
@@ -23,7 +24,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
23
24
|
run: async (machine, answers) => {
|
|
24
25
|
stepHeader(1, 12);
|
|
25
26
|
console.log(`\n${bold(blue('Client configuration'))}`);
|
|
26
|
-
return await selectRequired('* Client framework (required):', CLIENT_FRAMEWORKS, machine, 0, answers);
|
|
27
|
+
return await selectRequired('* Client framework (required):', CLIENT_FRAMEWORKS, machine, 0, answers, BLOCKS.CLIENT);
|
|
27
28
|
},
|
|
28
29
|
},
|
|
29
30
|
|
|
@@ -60,7 +61,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
60
61
|
run: async (machine, answers) => {
|
|
61
62
|
stepHeader(3, 12);
|
|
62
63
|
const opts = STATE_OPTIONS[answers.clientFw.value] || [];
|
|
63
|
-
return await selectOptional('State management:', opts, machine, 2, answers);
|
|
64
|
+
return await selectOptional('State management:', opts, machine, 2, answers, BLOCKS.CLIENT);
|
|
64
65
|
},
|
|
65
66
|
},
|
|
66
67
|
|
|
@@ -70,7 +71,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
70
71
|
run: async (machine, answers) => {
|
|
71
72
|
stepHeader(4, 12);
|
|
72
73
|
const opts = UI_OPTIONS[answers.clientFw.value] || [];
|
|
73
|
-
return await selectOptional('UI library:', opts, machine, 3, answers);
|
|
74
|
+
return await selectOptional('UI library:', opts, machine, 3, answers, BLOCKS.CLIENT);
|
|
74
75
|
},
|
|
75
76
|
},
|
|
76
77
|
|
|
@@ -79,7 +80,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
79
80
|
name: 'clientStyle',
|
|
80
81
|
run: async (machine, answers) => {
|
|
81
82
|
stepHeader(5, 12);
|
|
82
|
-
return await selectOptional('Styling:', STYLING_OPTIONS, machine, 4, answers);
|
|
83
|
+
return await selectOptional('Styling:', STYLING_OPTIONS, machine, 4, answers, BLOCKS.CLIENT);
|
|
83
84
|
},
|
|
84
85
|
},
|
|
85
86
|
|
|
@@ -157,7 +158,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
157
158
|
run: async (machine, answers) => {
|
|
158
159
|
stepHeader(9, 12);
|
|
159
160
|
if (!answers.backendFwObj) return null;
|
|
160
|
-
return await selectOptional('Database type:', DB_OPTIONS, machine, 8, answers);
|
|
161
|
+
return await selectOptional('Database type:', DB_OPTIONS, machine, 8, answers, BLOCKS.BACKEND);
|
|
161
162
|
},
|
|
162
163
|
},
|
|
163
164
|
|
|
@@ -171,7 +172,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
171
172
|
const byDb = ORM_OPTIONS_BY_DB[backendDb] || [];
|
|
172
173
|
const byFw = ORM_OPTIONS[backendFwObj.value] || [];
|
|
173
174
|
const ormChoices = byDb.length && byFw.length ? byDb.filter(o => byFw.includes(o)) : byDb.length ? byDb : byFw;
|
|
174
|
-
return await selectOptional('ORM / query layer:', ormChoices, machine, 9, answers);
|
|
175
|
+
return await selectOptional('ORM / query layer:', ormChoices, machine, 9, answers, BLOCKS.BACKEND);
|
|
175
176
|
},
|
|
176
177
|
},
|
|
177
178
|
|
|
@@ -182,7 +183,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
182
183
|
stepHeader(11, 12);
|
|
183
184
|
const { backendFwObj } = answers;
|
|
184
185
|
if (!backendFwObj) return null;
|
|
185
|
-
return await selectOptional('Auth strategy:', AUTH_OPTIONS[backendFwObj.value] || [], machine, 10, answers);
|
|
186
|
+
return await selectOptional('Auth strategy:', AUTH_OPTIONS[backendFwObj.value] || [], machine, 10, answers, BLOCKS.BACKEND);
|
|
186
187
|
},
|
|
187
188
|
},
|
|
188
189
|
|
|
@@ -208,7 +209,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
208
209
|
else console.log(`\n ${'\x1b[33m'}No IDEs detected.${'\x1b[0m'}\n`);
|
|
209
210
|
|
|
210
211
|
while (true) {
|
|
211
|
-
const ideChoice = await selectRequired('* IDE / editor (required):', sorted, machine, 11, answers);
|
|
212
|
+
const ideChoice = await selectRequired('* IDE / editor (required):', sorted, machine, 11, answers, null);
|
|
212
213
|
if (ideChoice === RESTART || ideChoice === BACK) return ideChoice;
|
|
213
214
|
|
|
214
215
|
if (ideChoice.cmd && !ideChoice.detected) {
|
package/lib/ui.js
CHANGED
|
@@ -122,6 +122,7 @@ const stepHeader = (stepIndex, totalSteps) => {
|
|
|
122
122
|
|
|
123
123
|
const { BACK, RESTART } = require('./steps');
|
|
124
124
|
const { validateCombination, checkFrameworkExists, fuzzyMatchFramework } = require('./validate');
|
|
125
|
+
const { BLOCKS } = require('./data-config');
|
|
125
126
|
|
|
126
127
|
const NONE_LABEL = '→ None of these - I\'ll specify my own';
|
|
127
128
|
|
|
@@ -130,34 +131,33 @@ const NONE_LABEL = '→ None of these - I\'ll specify my own';
|
|
|
130
131
|
const handleFreeText = async (prompt, items, stepMachine, stepIndex, context, isOptional) => {
|
|
131
132
|
const _res = await prompts({ type: 'text', name: 'value', message: 'Type your own value:' }, { onCancel: () => process.exit(0) });
|
|
132
133
|
const typed = (_res.value || '').trim();
|
|
133
|
-
if (!typed) return isOptional ? null :
|
|
134
|
+
if (!typed) return isOptional ? null : handleFreeText(prompt, items, stepMachine, stepIndex, context, isOptional);
|
|
135
|
+
|
|
136
|
+
// Fuzzy match first - before registry check
|
|
137
|
+
const matches = fuzzyMatchFramework(typed, block);
|
|
138
|
+
if (matches.length > 0) {
|
|
139
|
+
const didYouMeanOpts = [
|
|
140
|
+
...matches.map(m => `→ ${m.label || m}`),
|
|
141
|
+
`→ Type a different value`,
|
|
142
|
+
];
|
|
143
|
+
const choice = await arrowSelect(`"${typed}" couldn't be verified. Did you mean:`, didYouMeanOpts.map(o => ({ label: o })));
|
|
144
|
+
if (choice < matches.length) return matches[choice]; // returns full framework object
|
|
145
|
+
return handleFreeText(prompt, items, stepMachine, stepIndex, context, isOptional);
|
|
146
|
+
}
|
|
134
147
|
|
|
135
|
-
// Live registry check
|
|
148
|
+
// Live registry check - only when no fuzzy match
|
|
136
149
|
console.log(dim(' Verifying...'));
|
|
137
150
|
const existence = await checkFrameworkExists(typed);
|
|
138
151
|
|
|
139
152
|
if (!existence.exists) {
|
|
140
|
-
const matches = fuzzyMatchFramework(typed);
|
|
141
153
|
console.log('');
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (choice < matches.length) return matches[choice];
|
|
150
|
-
if (choice === matches.length) return handleFreeText(prompt, items, stepMachine, stepIndex, context, isOptional);
|
|
151
|
-
// Proceed with risks
|
|
152
|
-
} else {
|
|
153
|
-
const riskOpts = [
|
|
154
|
-
`→ Proceed with "${typed}" - I accept the risks`,
|
|
155
|
-
`→ Type a different value`,
|
|
156
|
-
];
|
|
157
|
-
const choice = await arrowSelect(`"${typed}" couldn't be verified.`, riskOpts.map(o => ({ label: o })));
|
|
158
|
-
if (choice === 1) return handleFreeText(prompt, items, stepMachine, stepIndex, context, isOptional);
|
|
159
|
-
}
|
|
160
|
-
// Show risk consequence block before proceeding
|
|
154
|
+
const riskOpts = [
|
|
155
|
+
`→ Proceed with "${typed}" - I accept the risks`,
|
|
156
|
+
`→ Type a different value`,
|
|
157
|
+
];
|
|
158
|
+
const choice = await arrowSelect(`"${typed}" couldn't be verified.`, riskOpts.map(o => ({ label: o })));
|
|
159
|
+
if (choice === 1) return handleFreeText(prompt, items, stepMachine, stepIndex, context, isOptional);
|
|
160
|
+
// Show risk consequence block
|
|
161
161
|
console.log('');
|
|
162
162
|
console.log(` ${yellow('⚠ Proceeding with an unverified framework may result in:')}`);
|
|
163
163
|
console.log(dim(' · Agent spending excessive tokens resolving unknown setup'));
|
|
@@ -173,8 +173,7 @@ const handleFreeText = async (prompt, items, stepMachine, stepIndex, context, is
|
|
|
173
173
|
return typed;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
// Valid
|
|
177
|
-
const isBackend = context.backendFwObj !== undefined || context.clientFw !== undefined;
|
|
176
|
+
// Valid + verified - check compatibility
|
|
178
177
|
const check = validateCombination(typed, context);
|
|
179
178
|
if (!check.valid) {
|
|
180
179
|
console.log(`
|
|
@@ -195,41 +194,44 @@ const handleFreeText = async (prompt, items, stepMachine, stepIndex, context, is
|
|
|
195
194
|
return typed;
|
|
196
195
|
};
|
|
197
196
|
|
|
198
|
-
const selectRequired = async (prompt, items, stepMachine, stepIndex, context = {}) => {
|
|
197
|
+
const selectRequired = async (prompt, items, stepMachine, stepIndex, context = {}, block = null) => {
|
|
199
198
|
const isFirstStep = stepIndex <= 1;
|
|
200
199
|
const navOpts = stepMachine ? stepMachine.navOptions(stepIndex, isFirstStep) : [];
|
|
201
|
-
const
|
|
202
|
-
const allItems = [...items, noneOpt, ...navOpts];
|
|
200
|
+
const allItems = block !== null ? [...items, { label: NONE_LABEL }, ...navOpts] : [...items, ...navOpts];
|
|
203
201
|
|
|
204
202
|
const idx = await arrowSelect(prompt, allItems.map(i => ({ label: typeof i === 'string' ? i : i.label })));
|
|
205
203
|
|
|
206
|
-
if (idx === items.length) return handleFreeText(prompt, items, stepMachine, stepIndex, context, false);
|
|
204
|
+
if (block !== null && idx === items.length) return handleFreeText(prompt, items, stepMachine, stepIndex, context, false, block);
|
|
207
205
|
|
|
208
|
-
|
|
209
|
-
|
|
206
|
+
const navStart = block !== null ? items.length + 1 : items.length;
|
|
207
|
+
if (idx >= navStart) {
|
|
208
|
+
const nav = navOpts[idx - navStart];
|
|
210
209
|
return nav ? nav.value : RESTART;
|
|
211
210
|
}
|
|
212
211
|
return items[idx];
|
|
213
212
|
};
|
|
214
213
|
|
|
215
|
-
const selectOptional = async (prompt, items, stepMachine, stepIndex, context = {}) => {
|
|
214
|
+
const selectOptional = async (prompt, items, stepMachine, stepIndex, context = {}, block = null) => {
|
|
216
215
|
if (!items || items.length === 0) return null;
|
|
217
216
|
const isFirstStep = stepIndex <= 1;
|
|
218
217
|
const navOpts = stepMachine ? stepMachine.navOptions(stepIndex, isFirstStep) : [];
|
|
219
218
|
const choices = [
|
|
220
219
|
...items.map(i => ({ label: typeof i === 'string' ? i : i.label })),
|
|
221
220
|
{ label: dim('Skip (agent will propose when needed)') },
|
|
222
|
-
{ label: NONE_LABEL },
|
|
221
|
+
...(block !== null ? [{ label: NONE_LABEL }] : []),
|
|
223
222
|
...navOpts.map(n => ({ label: n.label })),
|
|
224
223
|
];
|
|
225
224
|
|
|
226
225
|
const idx = await arrowSelect(prompt, choices);
|
|
227
226
|
|
|
228
227
|
if (idx === items.length) return null; // skip
|
|
229
|
-
if (idx === items.length + 1) return handleFreeText(prompt, items, stepMachine, stepIndex, context, true);
|
|
230
228
|
|
|
231
|
-
|
|
232
|
-
|
|
229
|
+
const noneIdx = block !== null ? items.length + 1 : -1;
|
|
230
|
+
if (block !== null && idx === noneIdx) return handleFreeText(prompt, items, stepMachine, stepIndex, context, true, block);
|
|
231
|
+
|
|
232
|
+
const navStart = block !== null ? items.length + 2 : items.length + 1;
|
|
233
|
+
if (idx >= navStart) {
|
|
234
|
+
const nav = navOpts[idx - navStart];
|
|
233
235
|
return nav ? nav.value : RESTART;
|
|
234
236
|
}
|
|
235
237
|
return typeof items[idx] === 'string' ? items[idx] : items[idx].value;
|
package/lib/validate.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { FRAMEWORK_REGISTRY } = require('./data-config');
|
|
3
|
+
const { FRAMEWORK_REGISTRY, CLIENT_FRAMEWORKS, BACKEND_FRAMEWORKS, BLOCKS } = require('./data-config');
|
|
4
|
+
|
|
5
|
+
const ALL_FRAMEWORKS = [...CLIENT_FRAMEWORKS, ...BACKEND_FRAMEWORKS];
|
|
4
6
|
|
|
5
7
|
// ── Compatibility matrix ──────────────────────────────────────────────────────
|
|
6
8
|
|
|
@@ -15,12 +17,17 @@ const MONGO_DBS = ['MongoDB'];
|
|
|
15
17
|
// ── fuzzyMatchFramework ───────────────────────────────────────────────────────
|
|
16
18
|
// Returns up to 3 known framework names that loosely match the typed value
|
|
17
19
|
|
|
18
|
-
const fuzzyMatchFramework = (typed) => {
|
|
20
|
+
const fuzzyMatchFramework = (typed, block = null) => {
|
|
21
|
+
const pool = block === BLOCKS.CLIENT ? CLIENT_FRAMEWORKS
|
|
22
|
+
: block === BLOCKS.BACKEND ? BACKEND_FRAMEWORKS
|
|
23
|
+
: ALL_FRAMEWORKS;
|
|
19
24
|
const lower = typed.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
20
|
-
|
|
25
|
+
const matchedNames = Object.keys(FRAMEWORK_REGISTRY).filter(name => {
|
|
21
26
|
const n = name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
22
|
-
return
|
|
27
|
+
return pool.some(f => f.value === name) &&
|
|
28
|
+
(n.includes(lower) || lower.includes(n) || (lower.length >= 3 && n.startsWith(lower.slice(0, 3))));
|
|
23
29
|
}).slice(0, 3);
|
|
30
|
+
return matchedNames.map(name => pool.find(f => f.value === name) || { label: name, value: name });
|
|
24
31
|
};
|
|
25
32
|
|
|
26
33
|
// ── checkFrameworkExists ──────────────────────────────────────────────────────
|
package/package.json
CHANGED