multi-agents-cli 1.1.67 → 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.
@@ -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 ormChoices = ORM_OPTIONS_BY_DB[backendDb] || ORM_OPTIONS[backendFwObj.value] || [];
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,29 +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
- // None of these - free text input
137
- if (idx === items.length) {
138
- const _res = await prompts({ type: 'text', name: 'value', message: 'Type your own value:' }, { onCancel: () => process.exit(0) });
139
- const typed = (_res.value || '').trim();
140
- if (!typed) return null;
141
- const check = validateCombination(typed, context);
142
- if (!check.valid) {
143
- console.log(`
144
- ${yellow('⚠')} ${check.reason}
145
- `);
146
- const opts = [
147
- '→ Continue - I understand the tradeoff',
148
- ...check.alternatives.map(a => `→ ${a}`),
149
- '→ Type a different value',
150
- ];
151
- const choice = await arrowSelect('How would you like to proceed?', opts.map(o => ({ label: o })));
152
- if (choice === 0) return typed;
153
- if (choice === opts.length - 1) return selectRequired(prompt, items, stepMachine, stepIndex, context);
154
- return check.alternatives[choice - 1];
155
- }
156
- console.log(dim(' Custom value - the agent will handle compatibility at runtime.'));
157
- return typed;
158
- }
206
+ if (idx === items.length) return handleFreeText(prompt, items, stepMachine, stepIndex, context, false);
159
207
 
160
208
  if (idx > items.length) {
161
209
  const nav = navOpts[idx - items.length - 1];
@@ -178,30 +226,7 @@ const selectOptional = async (prompt, items, stepMachine, stepIndex, context = {
178
226
  const idx = await arrowSelect(prompt, choices);
179
227
 
180
228
  if (idx === items.length) return null; // skip
181
-
182
- // None of these - free text input
183
- if (idx === items.length + 1) {
184
- const _res = await prompts({ type: 'text', name: 'value', message: 'Type your own value:' }, { onCancel: () => process.exit(0) });
185
- const typed = (_res.value || '').trim();
186
- if (!typed) return null;
187
- const check = validateCombination(typed, context);
188
- if (!check.valid) {
189
- console.log(`
190
- ${yellow('⚠')} ${check.reason}
191
- `);
192
- const opts = [
193
- '→ Continue - I understand the tradeoff',
194
- ...check.alternatives.map(a => `→ ${a}`),
195
- '→ Type a different value',
196
- ];
197
- const choice = await arrowSelect('How would you like to proceed?', opts.map(o => ({ label: o })));
198
- if (choice === 0) return typed;
199
- if (choice === opts.length - 1) return selectOptional(prompt, items, stepMachine, stepIndex, context);
200
- return check.alternatives[choice - 1];
201
- }
202
- console.log(dim(' Custom value - the agent will handle compatibility at runtime.'));
203
- return typed;
204
- }
229
+ if (idx === items.length + 1) return handleFreeText(prompt, items, stepMachine, stepIndex, context, true);
205
230
 
206
231
  if (idx > items.length + 1) {
207
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 = context.clientFw?.value || context.clientFw || null;
20
- const backendFw = context.backendFwObj?.value || context.backendFwObj || null;
21
- const backendDb = context.backendDb || null;
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 (e.g. dotnet as both client and backend)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multi-agents-cli",
3
- "version": "1.1.67",
3
+ "version": "1.1.68",
4
4
  "description": "Multi-agent workflow orchestration for Claude Code — isolated git worktrees, structured state tracking, autonomous task chaining",
5
5
  "keywords": [
6
6
  "claude-code",