multi-agents-cli 1.1.67 → 1.1.69

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,78 @@ 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 : handleFreeText(prompt, items, stepMachine, stepIndex, context, isOptional);
134
+
135
+ // Fuzzy match first - before registry check
136
+ const matches = fuzzyMatchFramework(typed);
137
+ if (matches.length > 0) {
138
+ const didYouMeanOpts = [
139
+ ...matches.map(m => `→ ${m.label || m}`),
140
+ `→ Type a different value`,
141
+ ];
142
+ const choice = await arrowSelect(`"${typed}" couldn't be verified. Did you mean:`, didYouMeanOpts.map(o => ({ label: o })));
143
+ if (choice < matches.length) return matches[choice]; // returns full framework object
144
+ return handleFreeText(prompt, items, stepMachine, stepIndex, context, isOptional);
145
+ }
146
+
147
+ // Live registry check - only when no fuzzy match
148
+ console.log(dim(' Verifying...'));
149
+ const existence = await checkFrameworkExists(typed);
150
+
151
+ if (!existence.exists) {
152
+ console.log('');
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
+ // Show risk consequence block
160
+ console.log('');
161
+ console.log(` ${yellow('⚠ Proceeding with an unverified framework may result in:')}`);
162
+ console.log(dim(' · Agent spending excessive tokens resolving unknown setup'));
163
+ console.log(dim(' · Incorrect scaffold structure for this framework'));
164
+ console.log(dim(' · Missing contracts and integration points'));
165
+ console.log(dim(' · Potential mid-flow failures requiring full restart'));
166
+ console.log('');
167
+ const confirm = await arrowSelect('Confirm you understand and want to continue?', [
168
+ { label: `→ Yes - proceed with "${typed}"` },
169
+ { label: `→ No - let me pick something else` },
170
+ ]);
171
+ if (confirm === 1) return handleFreeText(prompt, items, stepMachine, stepIndex, context, isOptional);
172
+ return typed;
173
+ }
174
+
175
+ // Valid + verified - check compatibility
176
+ const check = validateCombination(typed, context);
177
+ if (!check.valid) {
178
+ console.log(`
179
+ ${yellow('⚠')} ${check.reason}
180
+ `);
181
+ const opts = [
182
+ '→ Continue - I understand the tradeoff',
183
+ ...check.alternatives.map(a => `→ ${a}`),
184
+ '→ Type a different value',
185
+ ];
186
+ const choice = await arrowSelect('How would you like to proceed?', opts.map(o => ({ label: o })));
187
+ if (choice === 0) return typed;
188
+ if (choice === opts.length - 1) return handleFreeText(prompt, items, stepMachine, stepIndex, context, isOptional);
189
+ return check.alternatives[choice - 1];
190
+ }
191
+
192
+ console.log(dim(' Custom value - the agent will handle compatibility at runtime.'));
193
+ return typed;
194
+ };
195
+
128
196
  const selectRequired = async (prompt, items, stepMachine, stepIndex, context = {}) => {
129
197
  const isFirstStep = stepIndex <= 1;
130
198
  const navOpts = stepMachine ? stepMachine.navOptions(stepIndex, isFirstStep) : [];
@@ -133,29 +201,7 @@ const selectRequired = async (prompt, items, stepMachine, stepIndex, context = {
133
201
 
134
202
  const idx = await arrowSelect(prompt, allItems.map(i => ({ label: typeof i === 'string' ? i : i.label })));
135
203
 
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
- }
204
+ if (idx === items.length) return handleFreeText(prompt, items, stepMachine, stepIndex, context, false);
159
205
 
160
206
  if (idx > items.length) {
161
207
  const nav = navOpts[idx - items.length - 1];
@@ -178,30 +224,7 @@ const selectOptional = async (prompt, items, stepMachine, stepIndex, context = {
178
224
  const idx = await arrowSelect(prompt, choices);
179
225
 
180
226
  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
- }
227
+ if (idx === items.length + 1) return handleFreeText(prompt, items, stepMachine, stepIndex, context, true);
205
228
 
206
229
  if (idx > items.length + 1) {
207
230
  const nav = navOpts[idx - items.length - 2];
package/lib/validate.js CHANGED
@@ -1,5 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ const { FRAMEWORK_REGISTRY, CLIENT_FRAMEWORKS, BACKEND_FRAMEWORKS } = require('./data-config');
4
+
5
+ const ALL_FRAMEWORKS = [...CLIENT_FRAMEWORKS, ...BACKEND_FRAMEWORKS];
6
+
3
7
  // ── Compatibility matrix ──────────────────────────────────────────────────────
4
8
 
5
9
  const PYTHON_BACKENDS = ['FastAPI', 'Django'];
@@ -10,15 +14,64 @@ const MONGO_ONLY_ORMS = ['Beanie (MongoDB)'];
10
14
  const NEXTJS_ONLY_LIBS = ['shadcn/ui', 'Radix UI'];
11
15
  const MONGO_DBS = ['MongoDB'];
12
16
 
17
+ // ── fuzzyMatchFramework ───────────────────────────────────────────────────────
18
+ // Returns up to 3 known framework names that loosely match the typed value
19
+
20
+ const fuzzyMatchFramework = (typed) => {
21
+ const lower = typed.toLowerCase().replace(/[^a-z0-9]/g, '');
22
+ const matchedNames = Object.keys(FRAMEWORK_REGISTRY).filter(name => {
23
+ const n = name.toLowerCase().replace(/[^a-z0-9]/g, '');
24
+ return n.includes(lower) || lower.includes(n) || (lower.length >= 3 && n.startsWith(lower.slice(0, 3)));
25
+ }).slice(0, 3);
26
+ return matchedNames.map(name => ALL_FRAMEWORKS.find(f => f.value === name) || { label: name, value: name });
27
+ };
28
+
29
+ // ── checkFrameworkExists ──────────────────────────────────────────────────────
30
+ // Checks npm then pypi for the typed framework name
31
+ // Returns { exists: boolean, registry: string|null }
32
+
33
+ const checkFrameworkExists = async (name) => {
34
+ const known = FRAMEWORK_REGISTRY[name];
35
+ if (known && known.package) return { exists: true, registry: known.registry };
36
+ if (known && !known.package) return { exists: true, registry: known.registry }; // Laravel/Rails - known but no package
37
+
38
+ const https = require('https');
39
+ const fetch = (url) => new Promise((resolve, reject) => {
40
+ const req = https.get(url, { timeout: 4000 }, (res) => {
41
+ let data = '';
42
+ res.on('data', chunk => data += chunk);
43
+ res.on('end', () => resolve({ status: res.statusCode, data }));
44
+ });
45
+ req.on('error', reject);
46
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
47
+ });
48
+
49
+ const slug = name.toLowerCase().replace(/\s+/g, '-');
50
+
51
+ // Try npm
52
+ try {
53
+ const res = await fetch(`https://registry.npmjs.org/${slug}`);
54
+ if (res.status === 200) return { exists: true, registry: 'npm' };
55
+ } catch {}
56
+
57
+ // Try pypi
58
+ try {
59
+ const res = await fetch(`https://pypi.org/pypi/${slug}/json`);
60
+ if (res.status === 200) return { exists: true, registry: 'pypi' };
61
+ } catch {}
62
+
63
+ return { exists: false, registry: null };
64
+ };
65
+
13
66
  // ── validateCombination ───────────────────────────────────────────────────────
14
67
  // chosen — the value just entered (string)
15
68
  // context — answers accumulated so far { clientFw, backendFwObj, backendDb, ... }
16
69
  // returns { valid, reason, alternatives }
17
70
 
18
71
  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;
72
+ const clientFw = context.clientFw?.value || context.clientFw || null;
73
+ const backendFw = context.backendFwObj?.value || context.backendFwObj || null;
74
+ const backendDb = context.backendDb || null;
22
75
 
23
76
  // Node.js ORM + Python backend
24
77
  if (NODE_ONLY_ORMS.includes(chosen) && PYTHON_BACKENDS.includes(backendFw)) {
@@ -58,7 +111,7 @@ const validateCombination = (chosen, context = {}) => {
58
111
  };
59
112
  }
60
113
 
61
- // Ambiguous cross-boundary (e.g. dotnet as both client and backend)
114
+ // Ambiguous cross-boundary
62
115
  if (chosen && backendFw && chosen.toLowerCase() === backendFw.toLowerCase()) {
63
116
  return {
64
117
  valid: false,
@@ -70,4 +123,4 @@ const validateCombination = (chosen, context = {}) => {
70
123
  return { valid: true, reason: null, alternatives: [] };
71
124
  };
72
125
 
73
- module.exports = { validateCombination };
126
+ 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.69",
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",