multi-agents-cli 1.1.66 → 1.1.68
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/questions-flow.js +3 -1
- package/lib/ui.js +73 -44
- package/lib/validate.js +55 -5
- package/package.json +1 -1
package/lib/questions-flow.js
CHANGED
|
@@ -168,7 +168,9 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
168
168
|
stepHeader(10, 12);
|
|
169
169
|
const { backendFwObj, backendDb } = answers;
|
|
170
170
|
if (!backendFwObj || !backendDb) return null;
|
|
171
|
-
const
|
|
171
|
+
const byDb = ORM_OPTIONS_BY_DB[backendDb] || [];
|
|
172
|
+
const byFw = ORM_OPTIONS[backendFwObj.value] || [];
|
|
173
|
+
const ormChoices = byDb.length && byFw.length ? byDb.filter(o => byFw.includes(o)) : byDb.length ? byDb : byFw;
|
|
172
174
|
return await selectOptional('ORM / query layer:', ormChoices, machine, 9, answers);
|
|
173
175
|
},
|
|
174
176
|
},
|
package/lib/ui.js
CHANGED
|
@@ -121,10 +121,80 @@ const stepHeader = (stepIndex, totalSteps) => {
|
|
|
121
121
|
// ── Step selection helpers ────────────────────────────────────────────────────
|
|
122
122
|
|
|
123
123
|
const { BACK, RESTART } = require('./steps');
|
|
124
|
-
const { validateCombination } = require('./validate');
|
|
124
|
+
const { validateCombination, checkFrameworkExists, fuzzyMatchFramework } = require('./validate');
|
|
125
125
|
|
|
126
126
|
const NONE_LABEL = '→ None of these - I\'ll specify my own';
|
|
127
127
|
|
|
128
|
+
// ── Free-text handler (shared by selectRequired + selectOptional) ────────────
|
|
129
|
+
|
|
130
|
+
const handleFreeText = async (prompt, items, stepMachine, stepIndex, context, isOptional) => {
|
|
131
|
+
const _res = await prompts({ type: 'text', name: 'value', message: 'Type your own value:' }, { onCancel: () => process.exit(0) });
|
|
132
|
+
const typed = (_res.value || '').trim();
|
|
133
|
+
if (!typed) return isOptional ? null : (isOptional === false ? handleFreeText(prompt, items, stepMachine, stepIndex, context, isOptional) : null);
|
|
134
|
+
|
|
135
|
+
// Live registry check
|
|
136
|
+
console.log(dim(' Verifying...'));
|
|
137
|
+
const existence = await checkFrameworkExists(typed);
|
|
138
|
+
|
|
139
|
+
if (!existence.exists) {
|
|
140
|
+
const matches = fuzzyMatchFramework(typed);
|
|
141
|
+
console.log('');
|
|
142
|
+
if (matches.length > 0) {
|
|
143
|
+
const didYouMeanOpts = [
|
|
144
|
+
...matches.map(m => `→ ${m}`),
|
|
145
|
+
`→ Type a different value`,
|
|
146
|
+
`→ Proceed with "${typed}" - I accept the risks`,
|
|
147
|
+
];
|
|
148
|
+
const choice = await arrowSelect(`"${typed}" couldn't be verified. Did you mean:`, didYouMeanOpts.map(o => ({ label: o })));
|
|
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
|
|
161
|
+
console.log('');
|
|
162
|
+
console.log(` ${yellow('⚠ Proceeding with an unverified framework may result in:')}`);
|
|
163
|
+
console.log(dim(' · Agent spending excessive tokens resolving unknown setup'));
|
|
164
|
+
console.log(dim(' · Incorrect scaffold structure for this framework'));
|
|
165
|
+
console.log(dim(' · Missing contracts and integration points'));
|
|
166
|
+
console.log(dim(' · Potential mid-flow failures requiring full restart'));
|
|
167
|
+
console.log('');
|
|
168
|
+
const confirm = await arrowSelect('Confirm you understand and want to continue?', [
|
|
169
|
+
{ label: `→ Yes - proceed with "${typed}"` },
|
|
170
|
+
{ label: `→ No - let me pick something else` },
|
|
171
|
+
]);
|
|
172
|
+
if (confirm === 1) return handleFreeText(prompt, items, stepMachine, stepIndex, context, isOptional);
|
|
173
|
+
return typed;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Valid framework - check compatibility for backend block
|
|
177
|
+
const isBackend = context.backendFwObj !== undefined || context.clientFw !== undefined;
|
|
178
|
+
const check = validateCombination(typed, context);
|
|
179
|
+
if (!check.valid) {
|
|
180
|
+
console.log(`
|
|
181
|
+
${yellow('⚠')} ${check.reason}
|
|
182
|
+
`);
|
|
183
|
+
const opts = [
|
|
184
|
+
'→ Continue - I understand the tradeoff',
|
|
185
|
+
...check.alternatives.map(a => `→ ${a}`),
|
|
186
|
+
'→ Type a different value',
|
|
187
|
+
];
|
|
188
|
+
const choice = await arrowSelect('How would you like to proceed?', opts.map(o => ({ label: o })));
|
|
189
|
+
if (choice === 0) return typed;
|
|
190
|
+
if (choice === opts.length - 1) return handleFreeText(prompt, items, stepMachine, stepIndex, context, isOptional);
|
|
191
|
+
return check.alternatives[choice - 1];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log(dim(' Custom value - the agent will handle compatibility at runtime.'));
|
|
195
|
+
return typed;
|
|
196
|
+
};
|
|
197
|
+
|
|
128
198
|
const selectRequired = async (prompt, items, stepMachine, stepIndex, context = {}) => {
|
|
129
199
|
const isFirstStep = stepIndex <= 1;
|
|
130
200
|
const navOpts = stepMachine ? stepMachine.navOptions(stepIndex, isFirstStep) : [];
|
|
@@ -133,27 +203,7 @@ const selectRequired = async (prompt, items, stepMachine, stepIndex, context = {
|
|
|
133
203
|
|
|
134
204
|
const idx = await arrowSelect(prompt, allItems.map(i => ({ label: typeof i === 'string' ? i : i.label })));
|
|
135
205
|
|
|
136
|
-
|
|
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
|
-
}
|
|
206
|
+
if (idx === items.length) return handleFreeText(prompt, items, stepMachine, stepIndex, context, false);
|
|
157
207
|
|
|
158
208
|
if (idx > items.length) {
|
|
159
209
|
const nav = navOpts[idx - items.length - 1];
|
|
@@ -176,28 +226,7 @@ const selectOptional = async (prompt, items, stepMachine, stepIndex, context = {
|
|
|
176
226
|
const idx = await arrowSelect(prompt, choices);
|
|
177
227
|
|
|
178
228
|
if (idx === items.length) return null; // skip
|
|
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
|
-
}
|
|
229
|
+
if (idx === items.length + 1) return handleFreeText(prompt, items, stepMachine, stepIndex, context, true);
|
|
201
230
|
|
|
202
231
|
if (idx > items.length + 1) {
|
|
203
232
|
const nav = navOpts[idx - items.length - 2];
|
package/lib/validate.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { FRAMEWORK_REGISTRY } = require('./data-config');
|
|
4
|
+
|
|
3
5
|
// ── Compatibility matrix ──────────────────────────────────────────────────────
|
|
4
6
|
|
|
5
7
|
const PYTHON_BACKENDS = ['FastAPI', 'Django'];
|
|
@@ -10,15 +12,63 @@ const MONGO_ONLY_ORMS = ['Beanie (MongoDB)'];
|
|
|
10
12
|
const NEXTJS_ONLY_LIBS = ['shadcn/ui', 'Radix UI'];
|
|
11
13
|
const MONGO_DBS = ['MongoDB'];
|
|
12
14
|
|
|
15
|
+
// ── fuzzyMatchFramework ───────────────────────────────────────────────────────
|
|
16
|
+
// Returns up to 3 known framework names that loosely match the typed value
|
|
17
|
+
|
|
18
|
+
const fuzzyMatchFramework = (typed) => {
|
|
19
|
+
const lower = typed.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
20
|
+
return Object.keys(FRAMEWORK_REGISTRY).filter(name => {
|
|
21
|
+
const n = name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
22
|
+
return n.includes(lower) || lower.includes(n) || (lower.length >= 3 && n.startsWith(lower.slice(0, 3)));
|
|
23
|
+
}).slice(0, 3);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// ── checkFrameworkExists ──────────────────────────────────────────────────────
|
|
27
|
+
// Checks npm then pypi for the typed framework name
|
|
28
|
+
// Returns { exists: boolean, registry: string|null }
|
|
29
|
+
|
|
30
|
+
const checkFrameworkExists = async (name) => {
|
|
31
|
+
const known = FRAMEWORK_REGISTRY[name];
|
|
32
|
+
if (known && known.package) return { exists: true, registry: known.registry };
|
|
33
|
+
if (known && !known.package) return { exists: true, registry: known.registry }; // Laravel/Rails - known but no package
|
|
34
|
+
|
|
35
|
+
const https = require('https');
|
|
36
|
+
const fetch = (url) => new Promise((resolve, reject) => {
|
|
37
|
+
const req = https.get(url, { timeout: 4000 }, (res) => {
|
|
38
|
+
let data = '';
|
|
39
|
+
res.on('data', chunk => data += chunk);
|
|
40
|
+
res.on('end', () => resolve({ status: res.statusCode, data }));
|
|
41
|
+
});
|
|
42
|
+
req.on('error', reject);
|
|
43
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const slug = name.toLowerCase().replace(/\s+/g, '-');
|
|
47
|
+
|
|
48
|
+
// Try npm
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch(`https://registry.npmjs.org/${slug}`);
|
|
51
|
+
if (res.status === 200) return { exists: true, registry: 'npm' };
|
|
52
|
+
} catch {}
|
|
53
|
+
|
|
54
|
+
// Try pypi
|
|
55
|
+
try {
|
|
56
|
+
const res = await fetch(`https://pypi.org/pypi/${slug}/json`);
|
|
57
|
+
if (res.status === 200) return { exists: true, registry: 'pypi' };
|
|
58
|
+
} catch {}
|
|
59
|
+
|
|
60
|
+
return { exists: false, registry: null };
|
|
61
|
+
};
|
|
62
|
+
|
|
13
63
|
// ── validateCombination ───────────────────────────────────────────────────────
|
|
14
64
|
// chosen — the value just entered (string)
|
|
15
65
|
// context — answers accumulated so far { clientFw, backendFwObj, backendDb, ... }
|
|
16
66
|
// returns { valid, reason, alternatives }
|
|
17
67
|
|
|
18
68
|
const validateCombination = (chosen, context = {}) => {
|
|
19
|
-
const clientFw
|
|
20
|
-
const backendFw
|
|
21
|
-
const backendDb
|
|
69
|
+
const clientFw = context.clientFw?.value || context.clientFw || null;
|
|
70
|
+
const backendFw = context.backendFwObj?.value || context.backendFwObj || null;
|
|
71
|
+
const backendDb = context.backendDb || null;
|
|
22
72
|
|
|
23
73
|
// Node.js ORM + Python backend
|
|
24
74
|
if (NODE_ONLY_ORMS.includes(chosen) && PYTHON_BACKENDS.includes(backendFw)) {
|
|
@@ -58,7 +108,7 @@ const validateCombination = (chosen, context = {}) => {
|
|
|
58
108
|
};
|
|
59
109
|
}
|
|
60
110
|
|
|
61
|
-
// Ambiguous cross-boundary
|
|
111
|
+
// Ambiguous cross-boundary
|
|
62
112
|
if (chosen && backendFw && chosen.toLowerCase() === backendFw.toLowerCase()) {
|
|
63
113
|
return {
|
|
64
114
|
valid: false,
|
|
@@ -70,4 +120,4 @@ const validateCombination = (chosen, context = {}) => {
|
|
|
70
120
|
return { valid: true, reason: null, alternatives: [] };
|
|
71
121
|
};
|
|
72
122
|
|
|
73
|
-
module.exports = { validateCombination };
|
|
123
|
+
module.exports = { validateCombination, checkFrameworkExists, fuzzyMatchFramework };
|
package/package.json
CHANGED