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.
@@ -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,
@@ -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 : (isOptional === false ? handleFreeText(prompt, items, stepMachine, stepIndex, context, 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
- 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
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 framework - check compatibility for backend block
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 noneOpt = { label: NONE_LABEL };
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
- if (idx > items.length) {
209
- const nav = navOpts[idx - items.length - 1];
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
- if (idx > items.length + 1) {
232
- const nav = navOpts[idx - items.length - 2];
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
- return Object.keys(FRAMEWORK_REGISTRY).filter(name => {
25
+ const matchedNames = Object.keys(FRAMEWORK_REGISTRY).filter(name => {
21
26
  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)));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multi-agents-cli",
3
- "version": "1.1.68",
3
+ "version": "1.1.70",
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",