multi-agents-cli 1.1.78 → 1.1.79
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 +38 -1
- package/lib/data-config.js +29 -0
- package/lib/questions-flow.js +10 -19
- package/lib/steps.js +49 -2
- package/package.json +1 -1
package/init.js
CHANGED
|
@@ -38,6 +38,8 @@ const {
|
|
|
38
38
|
ORM_OPTIONS,
|
|
39
39
|
AUTH_OPTIONS,
|
|
40
40
|
IDE_CANDIDATES,
|
|
41
|
+
BLOCK_CONFIG,
|
|
42
|
+
INTENT_MAP,
|
|
41
43
|
} = require('./lib/data-config');
|
|
42
44
|
|
|
43
45
|
const {
|
|
@@ -373,6 +375,41 @@ const main = async () => {
|
|
|
373
375
|
steps.push(0, 'Project name', projectName);
|
|
374
376
|
separator();
|
|
375
377
|
|
|
378
|
+
// ── Intent selection ────────────────────────────────────────────────────────
|
|
379
|
+
rl.pause();
|
|
380
|
+
const intentChoices = Object.entries(INTENT_MAP).map(([k, v]) => ({ label: v.label, value: k }));
|
|
381
|
+
const intentIdx = await arrowSelect('What would you like to build?', intentChoices);
|
|
382
|
+
const intentKey = intentChoices[intentIdx].value;
|
|
383
|
+
const intent = INTENT_MAP[intentKey];
|
|
384
|
+
|
|
385
|
+
// Secondary prompt for fullstack order
|
|
386
|
+
let blockOrder = intent.blocks;
|
|
387
|
+
if (intentKey === 'fullstack') {
|
|
388
|
+
const orderIdx = await arrowSelect('Full-stack order?', [
|
|
389
|
+
{ label: 'Client first, then backend' },
|
|
390
|
+
{ label: 'Backend first, then client' },
|
|
391
|
+
]);
|
|
392
|
+
blockOrder = orderIdx === 0 ? ['client', 'backend'] : ['backend', 'client'];
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Activate blocks and resolve absolute step indices
|
|
396
|
+
let absoluteStep = 0;
|
|
397
|
+
const blockEntries = [];
|
|
398
|
+
for (const blockKey of blockOrder) {
|
|
399
|
+
const block = BLOCK_CONFIG[blockKey];
|
|
400
|
+
block.isActive = true;
|
|
401
|
+
let isFirstSeg = true;
|
|
402
|
+
for (const [segKey, seg] of Object.entries(block)) {
|
|
403
|
+
if (segKey === 'isActive' || typeof seg !== 'object' || !('step' in seg)) continue;
|
|
404
|
+
seg.absoluteStep = absoluteStep;
|
|
405
|
+
if (isFirstSeg) { blockEntries.push(absoluteStep); isFirstSeg = false; }
|
|
406
|
+
absoluteStep++;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
steps.blockEntries = blockEntries;
|
|
410
|
+
|
|
411
|
+
separator();
|
|
412
|
+
|
|
376
413
|
// Run all question steps with back-nav support
|
|
377
414
|
const answers = await runQuestions(stepDefs, steps);
|
|
378
415
|
if (answers === RESTART) {
|
|
@@ -389,7 +426,7 @@ const main = async () => {
|
|
|
389
426
|
const clientUi = answers.clientUi || null;
|
|
390
427
|
const clientStyle = answers.clientStyle || null;
|
|
391
428
|
const useIntegratedBackend = answers.useIntegratedBackend || false;
|
|
392
|
-
const backendFwObj = answers.
|
|
429
|
+
const backendFwObj = answers.backendFw || null;
|
|
393
430
|
const backendFw = backendFwObj ? fwValue(backendFwObj) : null;
|
|
394
431
|
const backendLang = backendFwObj ? backendFwObj.language : null;
|
|
395
432
|
const backendOrm = answers.backendOrm || null;
|
package/lib/data-config.js
CHANGED
|
@@ -258,8 +258,37 @@ const BLOCKS = {
|
|
|
258
258
|
SHARED: 'shared',
|
|
259
259
|
};
|
|
260
260
|
|
|
261
|
+
// ── Block config ─────────────────────────────────────────────────────────────
|
|
262
|
+
|
|
263
|
+
const BLOCK_CONFIG = {
|
|
264
|
+
client: {
|
|
265
|
+
isActive: true,
|
|
266
|
+
framework: { data: CLIENT_FRAMEWORKS, required: true, key: 'clientFw', step: 0 },
|
|
267
|
+
stateManagement: { data: STATE_OPTIONS, required: false, key: 'clientState', step: 1 },
|
|
268
|
+
uiLibrary: { data: UI_OPTIONS, required: false, key: 'clientUi', step: 2 },
|
|
269
|
+
styling: { data: STYLING_OPTIONS, required: false, key: 'clientStyle', step: 3 },
|
|
270
|
+
},
|
|
271
|
+
backend: {
|
|
272
|
+
isActive: false,
|
|
273
|
+
framework: { data: BACKEND_FRAMEWORKS, required: false, key: 'backendFw', step: 0 },
|
|
274
|
+
database: { data: DB_OPTIONS, required: false, key: 'backendDb', step: 1 },
|
|
275
|
+
orm: { data: ORM_OPTIONS, required: false, key: 'backendOrm', step: 2 },
|
|
276
|
+
auth: { data: AUTH_OPTIONS, required: false, key: 'backendAuth', step: 3 },
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// ── Intent map ────────────────────────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
const INTENT_MAP = {
|
|
283
|
+
fullstack: { label: 'Full-stack', blocks: ['client', 'backend'], order: null },
|
|
284
|
+
frontend: { label: 'Frontend only', blocks: ['client'], order: null },
|
|
285
|
+
backend: { label: 'Backend only', blocks: ['backend'], order: null },
|
|
286
|
+
};
|
|
287
|
+
|
|
261
288
|
module.exports = {
|
|
262
289
|
BLOCKS,
|
|
290
|
+
BLOCK_CONFIG,
|
|
291
|
+
INTENT_MAP,
|
|
263
292
|
FRAMEWORK_CONVENTIONS,
|
|
264
293
|
CLIENT_FRAMEWORKS,
|
|
265
294
|
BACKEND_FRAMEWORKS,
|
package/lib/questions-flow.js
CHANGED
|
@@ -105,7 +105,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
105
105
|
|
|
106
106
|
// ── Step 6: Backend framework ────────────────────────────────────────────────
|
|
107
107
|
{
|
|
108
|
-
name: '
|
|
108
|
+
name: 'backendFw',
|
|
109
109
|
run: async (machine, answers) => {
|
|
110
110
|
stepHeader(7, 12);
|
|
111
111
|
if (answers.useIntegratedBackend) return null; // skip
|
|
@@ -114,16 +114,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
114
114
|
console.log(`\n${bold(blue('Backend configuration'))}`);
|
|
115
115
|
console.log(dim(' You can skip the backend framework and decide later.\n'));
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
...BACKEND_FRAMEWORKS.map(f => ({ label: f.label || f.value })),
|
|
119
|
-
{ label: dim('Skip (decide later)') },
|
|
120
|
-
];
|
|
121
|
-
const idx = await arrowSelect('Backend framework:', choices);
|
|
122
|
-
|
|
123
|
-
// Back nav
|
|
124
|
-
if (idx >= BACKEND_FRAMEWORKS.length + 1) return BACK;
|
|
125
|
-
|
|
126
|
-
return idx === BACKEND_FRAMEWORKS.length ? null : BACKEND_FRAMEWORKS[idx];
|
|
117
|
+
return await selectOptional('Backend framework:', BACKEND_FRAMEWORKS, machine, 6, answers, BLOCKS.BACKEND);
|
|
127
118
|
},
|
|
128
119
|
},
|
|
129
120
|
|
|
@@ -132,7 +123,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
132
123
|
name: 'backendFwVersion',
|
|
133
124
|
run: async (machine, answers) => {
|
|
134
125
|
stepHeader(8, 12);
|
|
135
|
-
const fwObj = answers.
|
|
126
|
+
const fwObj = answers.backendFw;
|
|
136
127
|
if (!fwObj) return null; // skip — no backend selected
|
|
137
128
|
|
|
138
129
|
const versions = await fetchLatestVersions(fwValue(fwObj)) || FRAMEWORK_VERSION_FALLBACK[fwValue(fwObj)] || [];
|
|
@@ -159,7 +150,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
159
150
|
name: 'backendDb',
|
|
160
151
|
run: async (machine, answers) => {
|
|
161
152
|
stepHeader(9, 12);
|
|
162
|
-
if (!answers.
|
|
153
|
+
if (!answers.backendFw) return null;
|
|
163
154
|
return await selectOptional('Database type:', DB_OPTIONS, machine, 8, answers, BLOCKS.BACKEND);
|
|
164
155
|
},
|
|
165
156
|
},
|
|
@@ -169,10 +160,10 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
169
160
|
name: 'backendOrm',
|
|
170
161
|
run: async (machine, answers) => {
|
|
171
162
|
stepHeader(10, 12);
|
|
172
|
-
const {
|
|
173
|
-
if (!
|
|
163
|
+
const { backendFw, backendDb } = answers;
|
|
164
|
+
if (!backendFw || !backendDb) return null;
|
|
174
165
|
const byDb = ORM_OPTIONS_BY_DB[backendDb] || [];
|
|
175
|
-
const byFw = ORM_OPTIONS[fwValue(
|
|
166
|
+
const byFw = ORM_OPTIONS[fwValue(backendFw)] || [];
|
|
176
167
|
const ormChoices = byDb.length && byFw.length ? byDb.filter(o => byFw.includes(o)) : byDb.length ? byDb : byFw;
|
|
177
168
|
return await selectOptional('ORM / query layer:', ormChoices, machine, 9, answers, BLOCKS.BACKEND);
|
|
178
169
|
},
|
|
@@ -183,9 +174,9 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
183
174
|
name: 'backendAuth',
|
|
184
175
|
run: async (machine, answers) => {
|
|
185
176
|
stepHeader(11, 12);
|
|
186
|
-
const {
|
|
187
|
-
if (!
|
|
188
|
-
return await selectOptional('Auth strategy:', AUTH_OPTIONS[fwValue(
|
|
177
|
+
const { backendFw } = answers;
|
|
178
|
+
if (!backendFw) return null;
|
|
179
|
+
return await selectOptional('Auth strategy:', AUTH_OPTIONS[fwValue(backendFw)] || [], machine, 10, answers, BLOCKS.BACKEND);
|
|
189
180
|
},
|
|
190
181
|
},
|
|
191
182
|
|
package/lib/steps.js
CHANGED
|
@@ -10,10 +10,12 @@ const SHOW_LIST = Symbol('SHOW_LIST');
|
|
|
10
10
|
// ── Step machine ──────────────────────────────────────────────────────────────
|
|
11
11
|
|
|
12
12
|
class StepMachine {
|
|
13
|
-
constructor() {
|
|
13
|
+
constructor(blockEntries = []) {
|
|
14
14
|
this.history = []; // [{ stepIndex, stepName, answer }]
|
|
15
15
|
this.inBackNav = false;
|
|
16
16
|
this.resumeIndex = null; // step index where back-nav started
|
|
17
|
+
this.blockEntries = blockEntries; // absolute step indices that are block entry points
|
|
18
|
+
this.snapshot = null; // history snapshot taken at back-nav start
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
// Push a completed step answer onto the history stack
|
|
@@ -63,11 +65,30 @@ class StepMachine {
|
|
|
63
65
|
return opts;
|
|
64
66
|
}
|
|
65
67
|
|
|
68
|
+
// Take a snapshot of current history + answers (for ignore-changes restore)
|
|
69
|
+
snapshotHistory() {
|
|
70
|
+
this.snapshot = this.history.map(h => ({ ...h }));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Restore history from snapshot
|
|
74
|
+
restoreSnapshot() {
|
|
75
|
+
if (this.snapshot) {
|
|
76
|
+
this.history = this.snapshot.map(h => ({ ...h }));
|
|
77
|
+
this.snapshot = null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check if a step index is a block entry point
|
|
82
|
+
isBlockEntry(stepIndex) {
|
|
83
|
+
return this.blockEntries.includes(stepIndex);
|
|
84
|
+
}
|
|
85
|
+
|
|
66
86
|
// Enter back-nav mode — record where the user first pressed Back
|
|
67
87
|
enterBackNav(currentStepIndex) {
|
|
68
88
|
if (!this.inBackNav) {
|
|
69
89
|
this.inBackNav = true;
|
|
70
90
|
this.resumeIndex = currentStepIndex;
|
|
91
|
+
this.snapshotHistory();
|
|
71
92
|
}
|
|
72
93
|
}
|
|
73
94
|
|
|
@@ -82,6 +103,7 @@ class StepMachine {
|
|
|
82
103
|
this.history = [];
|
|
83
104
|
this.inBackNav = false;
|
|
84
105
|
this.resumeIndex = null;
|
|
106
|
+
this.snapshot = null;
|
|
85
107
|
}
|
|
86
108
|
}
|
|
87
109
|
|
|
@@ -106,9 +128,34 @@ const runQuestions = async (stepDefs, machine) => {
|
|
|
106
128
|
if (result === BACK) {
|
|
107
129
|
if (i === 0) return RESTART; // nowhere to go back from step 0
|
|
108
130
|
machine.enterBackNav(i); // record where back-nav started
|
|
131
|
+
i--;
|
|
132
|
+
// Skip back over auto-skipped steps (not in history), stop at block entry
|
|
133
|
+
while (i > 0 && !machine.history.find(h => h.stepIndex === i) && !machine.isBlockEntry(i)) i--;
|
|
134
|
+
// Pop the step we landed on
|
|
109
135
|
const popped = machine.pop();
|
|
110
136
|
if (popped) delete answers[popped.stepName];
|
|
111
|
-
|
|
137
|
+
// Block boundary checkpoint — fires when back-nav reaches a block entry point
|
|
138
|
+
if (machine.isBlockEntry(i)) {
|
|
139
|
+
const { arrowSelect } = require('./ui');
|
|
140
|
+
const boundaryChoice = await arrowSelect('You\'ve reached the start of this block.', [
|
|
141
|
+
{ label: '→ Continue from here (re-answer this block)' },
|
|
142
|
+
{ label: '→ Ignore changes (restore to where you started)' },
|
|
143
|
+
{ label: '← Restart configuration' },
|
|
144
|
+
]);
|
|
145
|
+
if (boundaryChoice === 1) {
|
|
146
|
+
const resumeTarget = machine.resumeIndex;
|
|
147
|
+
machine.restoreSnapshot();
|
|
148
|
+
Object.keys(answers).forEach(k => delete answers[k]);
|
|
149
|
+
machine.history.forEach(h => { answers[h.stepName] = h.answer; });
|
|
150
|
+
machine.exitBackNav();
|
|
151
|
+
const entryStep = machine.blockEntries.find(e => e <= i) ?? 0;
|
|
152
|
+
i = resumeTarget != null ? resumeTarget : entryStep + 1;
|
|
153
|
+
|
|
154
|
+
} else if (boundaryChoice === 2) {
|
|
155
|
+
return RESTART;
|
|
156
|
+
}
|
|
157
|
+
// 'continue' — just proceed from current block entry step
|
|
158
|
+
}
|
|
112
159
|
continue;
|
|
113
160
|
}
|
|
114
161
|
|
package/package.json
CHANGED