multi-agents-cli 1.1.69 → 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
 
@@ -133,7 +134,7 @@ const handleFreeText = async (prompt, items, stepMachine, stepIndex, context, is
133
134
  if (!typed) return isOptional ? null : handleFreeText(prompt, items, stepMachine, stepIndex, context, isOptional);
134
135
 
135
136
  // Fuzzy match first - before registry check
136
- const matches = fuzzyMatchFramework(typed);
137
+ const matches = fuzzyMatchFramework(typed, block);
137
138
  if (matches.length > 0) {
138
139
  const didYouMeanOpts = [
139
140
  ...matches.map(m => `→ ${m.label || m}`),
@@ -193,41 +194,44 @@ const handleFreeText = async (prompt, items, stepMachine, stepIndex, context, is
193
194
  return typed;
194
195
  };
195
196
 
196
- const selectRequired = async (prompt, items, stepMachine, stepIndex, context = {}) => {
197
+ const selectRequired = async (prompt, items, stepMachine, stepIndex, context = {}, block = null) => {
197
198
  const isFirstStep = stepIndex <= 1;
198
199
  const navOpts = stepMachine ? stepMachine.navOptions(stepIndex, isFirstStep) : [];
199
- const noneOpt = { label: NONE_LABEL };
200
- const allItems = [...items, noneOpt, ...navOpts];
200
+ const allItems = block !== null ? [...items, { label: NONE_LABEL }, ...navOpts] : [...items, ...navOpts];
201
201
 
202
202
  const idx = await arrowSelect(prompt, allItems.map(i => ({ label: typeof i === 'string' ? i : i.label })));
203
203
 
204
- 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);
205
205
 
206
- if (idx > items.length) {
207
- 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];
208
209
  return nav ? nav.value : RESTART;
209
210
  }
210
211
  return items[idx];
211
212
  };
212
213
 
213
- const selectOptional = async (prompt, items, stepMachine, stepIndex, context = {}) => {
214
+ const selectOptional = async (prompt, items, stepMachine, stepIndex, context = {}, block = null) => {
214
215
  if (!items || items.length === 0) return null;
215
216
  const isFirstStep = stepIndex <= 1;
216
217
  const navOpts = stepMachine ? stepMachine.navOptions(stepIndex, isFirstStep) : [];
217
218
  const choices = [
218
219
  ...items.map(i => ({ label: typeof i === 'string' ? i : i.label })),
219
220
  { label: dim('Skip (agent will propose when needed)') },
220
- { label: NONE_LABEL },
221
+ ...(block !== null ? [{ label: NONE_LABEL }] : []),
221
222
  ...navOpts.map(n => ({ label: n.label })),
222
223
  ];
223
224
 
224
225
  const idx = await arrowSelect(prompt, choices);
225
226
 
226
227
  if (idx === items.length) return null; // skip
227
- if (idx === items.length + 1) return handleFreeText(prompt, items, stepMachine, stepIndex, context, true);
228
228
 
229
- if (idx > items.length + 1) {
230
- 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];
231
235
  return nav ? nav.value : RESTART;
232
236
  }
233
237
  return typeof items[idx] === 'string' ? items[idx] : items[idx].value;
package/lib/validate.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { FRAMEWORK_REGISTRY, CLIENT_FRAMEWORKS, BACKEND_FRAMEWORKS } = require('./data-config');
3
+ const { FRAMEWORK_REGISTRY, CLIENT_FRAMEWORKS, BACKEND_FRAMEWORKS, BLOCKS } = require('./data-config');
4
4
 
5
5
  const ALL_FRAMEWORKS = [...CLIENT_FRAMEWORKS, ...BACKEND_FRAMEWORKS];
6
6
 
@@ -17,13 +17,17 @@ const MONGO_DBS = ['MongoDB'];
17
17
  // ── fuzzyMatchFramework ───────────────────────────────────────────────────────
18
18
  // Returns up to 3 known framework names that loosely match the typed value
19
19
 
20
- 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;
21
24
  const lower = typed.toLowerCase().replace(/[^a-z0-9]/g, '');
22
25
  const matchedNames = Object.keys(FRAMEWORK_REGISTRY).filter(name => {
23
26
  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)));
27
+ return pool.some(f => f.value === name) &&
28
+ (n.includes(lower) || lower.includes(n) || (lower.length >= 3 && n.startsWith(lower.slice(0, 3))));
25
29
  }).slice(0, 3);
26
- return matchedNames.map(name => ALL_FRAMEWORKS.find(f => f.value === name) || { label: name, value: name });
30
+ return matchedNames.map(name => pool.find(f => f.value === name) || { label: name, value: name });
27
31
  };
28
32
 
29
33
  // ── checkFrameworkExists ──────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multi-agents-cli",
3
- "version": "1.1.69",
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",