multi-agents-cli 1.1.65 → 1.1.67

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.
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,90 @@ 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 _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
+ }
159
+
160
+ if (idx > items.length) {
161
+ const nav = navOpts[idx - items.length - 1];
134
162
  return nav ? nav.value : RESTART;
135
163
  }
136
164
  return items[idx];
137
165
  };
138
166
 
139
- const selectOptional = async (prompt, items, stepMachine, stepIndex) => {
167
+ const selectOptional = async (prompt, items, stepMachine, stepIndex, context = {}) => {
140
168
  if (!items || items.length === 0) return null;
141
169
  const isFirstStep = stepIndex <= 1;
142
170
  const navOpts = stepMachine ? stepMachine.navOptions(stepIndex, isFirstStep) : [];
143
171
  const choices = [
144
172
  ...items.map(i => ({ label: typeof i === 'string' ? i : i.label })),
145
173
  { label: dim('Skip (agent will propose when needed)') },
174
+ { label: NONE_LABEL },
146
175
  ...navOpts.map(n => ({ label: n.label })),
147
176
  ];
148
177
 
149
178
  const idx = await arrowSelect(prompt, choices);
150
179
 
151
180
  if (idx === items.length) return null; // skip
152
- if (idx > items.length) {
153
- const nav = navOpts[idx - items.length - 1];
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
+ }
205
+
206
+ if (idx > items.length + 1) {
207
+ const nav = navOpts[idx - items.length - 2];
154
208
  return nav ? nav.value : RESTART;
155
209
  }
156
210
  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.65",
3
+ "version": "1.1.67",
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