multi-agents-cli 1.1.64 → 1.1.66

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.
@@ -557,19 +557,34 @@ const main = async () => {
557
557
  }
558
558
 
559
559
  separator();
560
- console.log(`\n ${bold('What to do next:')}`);
560
+ const boxWidth = 54;
561
+ const rev = (s) => `\x1b[7m${s}\x1b[27m`;
562
+ const pad = (s, w) => s + ' '.repeat(Math.max(0, w - s.length));
563
+
564
+ console.log('');
565
+ console.log(' ' + rev(' ' + pad(' What happens next', boxWidth) + ' '));
566
+ console.log(' ' + rev(' ' + pad('', boxWidth) + ' '));
561
567
 
562
568
  if (nextAgent) {
563
- console.log(`\n ${green('→')} ${bold(nextAgent)} ${dim('(' + nextScope + ')')}`);
564
- console.log(`\n ${cyan('npm run agent')}`);
565
- console.log(` ${dim('Select: ' + nextScope + ' → ' + nextAgent)}`);
569
+ console.log(' ' + rev(' ' + pad(` -> Run: npm run agent`, boxWidth) + ' '));
570
+ console.log(' ' + rev(' ' + pad(` -> Select: ${nextScope} -> ${nextAgent}`, boxWidth) + ' '));
566
571
  } else {
567
- console.log(`\n ${cyan('npm run agent')}`);
572
+ console.log(' ' + rev(' ' + pad(` -> All agents complete - ready to deploy`, boxWidth) + ' '));
568
573
  }
574
+
575
+ console.log(' ' + rev(' ' + pad('', boxWidth) + ' '));
576
+ console.log('');
569
577
  } catch {
570
578
  separator();
571
- console.log(`\n ${bold('What to do next:')}\n`);
572
- console.log(` ${cyan('npm run agent')}`);
579
+ const boxWidth2 = 54;
580
+ const rev2 = (s) => `\x1b[7m${s}\x1b[27m`;
581
+ const pad2 = (s, w) => s + ' '.repeat(Math.max(0, w - s.length));
582
+ console.log('');
583
+ console.log(' ' + rev2(' ' + pad2(' What happens next', boxWidth2) + ' '));
584
+ console.log(' ' + rev2(' ' + pad2('', boxWidth2) + ' '));
585
+ console.log(' ' + rev2(' ' + pad2(` -> Run: npm run agent`, boxWidth2) + ' '));
586
+ console.log(' ' + rev2(' ' + pad2('', boxWidth2) + ' '));
587
+ console.log('');
573
588
  }
574
589
 
575
590
  console.log('\n');
package/init.js CHANGED
@@ -38,7 +38,7 @@ const {
38
38
  ORM_OPTIONS,
39
39
  AUTH_OPTIONS,
40
40
  IDE_CANDIDATES,
41
- } = require('./lib/questions');
41
+ } = require('./lib/data-config');
42
42
 
43
43
  const {
44
44
  expandWinPath,
@@ -8,7 +8,7 @@ const {
8
8
  CLIENT_FRAMEWORKS, BACKEND_FRAMEWORKS, FRAMEWORK_VERSION_FALLBACK,
9
9
  fetchLatestVersions, STATE_OPTIONS, UI_OPTIONS, STYLING_OPTIONS,
10
10
  DB_OPTIONS, ORM_OPTIONS_BY_DB, ORM_OPTIONS, AUTH_OPTIONS,
11
- } = require('./questions');
11
+ } = require('./data-config');
12
12
  const { buildIDEOptions, verifyIDE, detectTerminal } = require('./detect');
13
13
 
14
14
  // ── Step definitions ──────────────────────────────────────────────────────────
@@ -23,7 +23,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
23
23
  run: async (machine, answers) => {
24
24
  stepHeader(1, 12);
25
25
  console.log(`\n${bold(blue('Client configuration'))}`);
26
- return await selectRequired('* Client framework (required):', CLIENT_FRAMEWORKS, machine, 0);
26
+ return await selectRequired('* Client framework (required):', CLIENT_FRAMEWORKS, machine, 0, answers);
27
27
  },
28
28
  },
29
29
 
@@ -60,7 +60,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
60
60
  run: async (machine, answers) => {
61
61
  stepHeader(3, 12);
62
62
  const opts = STATE_OPTIONS[answers.clientFw.value] || [];
63
- return await selectOptional('State management:', opts, machine, 2);
63
+ return await selectOptional('State management:', opts, machine, 2, answers);
64
64
  },
65
65
  },
66
66
 
@@ -70,7 +70,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
70
70
  run: async (machine, answers) => {
71
71
  stepHeader(4, 12);
72
72
  const opts = UI_OPTIONS[answers.clientFw.value] || [];
73
- return await selectOptional('UI library:', opts, machine, 3);
73
+ return await selectOptional('UI library:', opts, machine, 3, answers);
74
74
  },
75
75
  },
76
76
 
@@ -79,7 +79,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
79
79
  name: 'clientStyle',
80
80
  run: async (machine, answers) => {
81
81
  stepHeader(5, 12);
82
- return await selectOptional('Styling:', STYLING_OPTIONS, machine, 4);
82
+ return await selectOptional('Styling:', STYLING_OPTIONS, machine, 4, answers);
83
83
  },
84
84
  },
85
85
 
@@ -157,7 +157,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
157
157
  run: async (machine, answers) => {
158
158
  stepHeader(9, 12);
159
159
  if (!answers.backendFwObj) return null;
160
- return await selectOptional('Database type:', DB_OPTIONS, machine, 8);
160
+ return await selectOptional('Database type:', DB_OPTIONS, machine, 8, answers);
161
161
  },
162
162
  },
163
163
 
@@ -169,7 +169,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
169
169
  const { backendFwObj, backendDb } = answers;
170
170
  if (!backendFwObj || !backendDb) return null;
171
171
  const ormChoices = ORM_OPTIONS_BY_DB[backendDb] || ORM_OPTIONS[backendFwObj.value] || [];
172
- return await selectOptional('ORM / query layer:', ormChoices, machine, 9);
172
+ return await selectOptional('ORM / query layer:', ormChoices, machine, 9, answers);
173
173
  },
174
174
  },
175
175
 
@@ -180,7 +180,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
180
180
  stepHeader(11, 12);
181
181
  const { backendFwObj } = answers;
182
182
  if (!backendFwObj) return null;
183
- return await selectOptional('Auth strategy:', AUTH_OPTIONS[backendFwObj.value] || [], machine, 10);
183
+ return await selectOptional('Auth strategy:', AUTH_OPTIONS[backendFwObj.value] || [], machine, 10, answers);
184
184
  },
185
185
  },
186
186
 
@@ -206,7 +206,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
206
206
  else console.log(`\n ${'\x1b[33m'}No IDEs detected.${'\x1b[0m'}\n`);
207
207
 
208
208
  while (true) {
209
- const ideChoice = await selectRequired('* IDE / editor (required):', sorted, machine, 11);
209
+ const ideChoice = await selectRequired('* IDE / editor (required):', sorted, machine, 11, answers);
210
210
  if (ideChoice === RESTART || ideChoice === BACK) return ideChoice;
211
211
 
212
212
  if (ideChoice.cmd && !ideChoice.detected) {
package/lib/ui.js CHANGED
@@ -121,36 +121,86 @@ const stepHeader = (stepIndex, totalSteps) => {
121
121
  // ── Step selection helpers ────────────────────────────────────────────────────
122
122
 
123
123
  const { BACK, RESTART } = require('./steps');
124
+ const { validateCombination } = require('./validate');
124
125
 
125
- const selectRequired = async (prompt, items, stepMachine, stepIndex) => {
126
+ const NONE_LABEL = '→ None of these - I\'ll specify my own';
127
+
128
+ const selectRequired = async (prompt, items, stepMachine, stepIndex, context = {}) => {
126
129
  const isFirstStep = stepIndex <= 1;
127
130
  const navOpts = stepMachine ? stepMachine.navOptions(stepIndex, isFirstStep) : [];
128
- const allItems = [...items, ...navOpts];
131
+ const noneOpt = { label: NONE_LABEL };
132
+ const allItems = [...items, noneOpt, ...navOpts];
129
133
 
130
134
  const idx = await arrowSelect(prompt, allItems.map(i => ({ label: typeof i === 'string' ? i : i.label })));
131
135
 
132
- if (idx >= items.length) {
133
- const nav = navOpts[idx - items.length];
136
+ // None of these - free text input
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
+ }
157
+
158
+ if (idx > items.length) {
159
+ const nav = navOpts[idx - items.length - 1];
134
160
  return nav ? nav.value : RESTART;
135
161
  }
136
162
  return items[idx];
137
163
  };
138
164
 
139
- const selectOptional = async (prompt, items, stepMachine, stepIndex) => {
165
+ const selectOptional = async (prompt, items, stepMachine, stepIndex, context = {}) => {
140
166
  if (!items || items.length === 0) return null;
141
167
  const isFirstStep = stepIndex <= 1;
142
168
  const navOpts = stepMachine ? stepMachine.navOptions(stepIndex, isFirstStep) : [];
143
169
  const choices = [
144
170
  ...items.map(i => ({ label: typeof i === 'string' ? i : i.label })),
145
171
  { label: dim('Skip (agent will propose when needed)') },
172
+ { label: NONE_LABEL },
146
173
  ...navOpts.map(n => ({ label: n.label })),
147
174
  ];
148
175
 
149
176
  const idx = await arrowSelect(prompt, choices);
150
177
 
151
178
  if (idx === items.length) return null; // skip
152
- if (idx > items.length) {
153
- const nav = navOpts[idx - items.length - 1];
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
+ }
201
+
202
+ if (idx > items.length + 1) {
203
+ const nav = navOpts[idx - items.length - 2];
154
204
  return nav ? nav.value : RESTART;
155
205
  }
156
206
  return typeof items[idx] === 'string' ? items[idx] : items[idx].value;
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ // ── Compatibility matrix ──────────────────────────────────────────────────────
4
+
5
+ const PYTHON_BACKENDS = ['FastAPI', 'Django'];
6
+ const NODE_BACKENDS = ['Express', 'NestJS', 'Fastify'];
7
+ const NODE_ONLY_ORMS = ['Prisma', 'TypeORM', 'Drizzle', 'Sequelize', 'MikroORM', 'raw pg driver', 'raw mysql2 driver', 'better-sqlite3'];
8
+ const PYTHON_ONLY_ORMS = ['SQLAlchemy', 'Tortoise ORM', 'Django ORM (built-in)'];
9
+ const MONGO_ONLY_ORMS = ['Beanie (MongoDB)'];
10
+ const NEXTJS_ONLY_LIBS = ['shadcn/ui', 'Radix UI'];
11
+ const MONGO_DBS = ['MongoDB'];
12
+
13
+ // ── validateCombination ───────────────────────────────────────────────────────
14
+ // chosen — the value just entered (string)
15
+ // context — answers accumulated so far { clientFw, backendFwObj, backendDb, ... }
16
+ // returns { valid, reason, alternatives }
17
+
18
+ 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;
22
+
23
+ // Node.js ORM + Python backend
24
+ if (NODE_ONLY_ORMS.includes(chosen) && PYTHON_BACKENDS.includes(backendFw)) {
25
+ return {
26
+ valid: false,
27
+ reason: `${chosen} is a Node.js ORM and does not support ${backendFw} (Python).`,
28
+ alternatives: backendFw === 'Django'
29
+ ? ['Django ORM (built-in)', 'SQLAlchemy']
30
+ : ['SQLAlchemy', 'Tortoise ORM', 'raw asyncpg'],
31
+ };
32
+ }
33
+
34
+ // Python ORM + Node.js backend
35
+ if (PYTHON_ONLY_ORMS.includes(chosen) && NODE_BACKENDS.includes(backendFw)) {
36
+ return {
37
+ valid: false,
38
+ reason: `${chosen} is a Python ORM and does not support ${backendFw} (Node.js).`,
39
+ alternatives: ['Prisma', 'TypeORM', 'Drizzle'],
40
+ };
41
+ }
42
+
43
+ // Beanie + non-MongoDB
44
+ if (MONGO_ONLY_ORMS.includes(chosen) && backendDb && !MONGO_DBS.includes(backendDb)) {
45
+ return {
46
+ valid: false,
47
+ reason: `Beanie is a MongoDB ODM and does not support ${backendDb}.`,
48
+ alternatives: ['Prisma', 'TypeORM', 'Drizzle'],
49
+ };
50
+ }
51
+
52
+ // Next.js-only UI libs + Vite+React
53
+ if (NEXTJS_ONLY_LIBS.includes(chosen) && clientFw === 'Vite+React') {
54
+ return {
55
+ valid: false,
56
+ reason: `${chosen} is optimized for Next.js and may not work correctly with Vite+React.`,
57
+ alternatives: ['MUI', 'Chakra UI', 'Ant Design'],
58
+ };
59
+ }
60
+
61
+ // Ambiguous cross-boundary (e.g. dotnet as both client and backend)
62
+ if (chosen && backendFw && chosen.toLowerCase() === backendFw.toLowerCase()) {
63
+ return {
64
+ valid: false,
65
+ reason: `${chosen} appears in both client and backend selections - this may cause scope ambiguity.`,
66
+ alternatives: [],
67
+ };
68
+ }
69
+
70
+ return { valid: true, reason: null, alternatives: [] };
71
+ };
72
+
73
+ module.exports = { validateCombination };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multi-agents-cli",
3
- "version": "1.1.64",
3
+ "version": "1.1.66",
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",
File without changes