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 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.backendFwObj || null;
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;
@@ -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,
@@ -105,7 +105,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
105
105
 
106
106
  // ── Step 6: Backend framework ────────────────────────────────────────────────
107
107
  {
108
- name: 'backendFwObj',
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
- const choices = [
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.backendFwObj;
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.backendFwObj) return null;
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 { backendFwObj, backendDb } = answers;
173
- if (!backendFwObj || !backendDb) return null;
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(backendFwObj)] || [];
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 { backendFwObj } = answers;
187
- if (!backendFwObj) return null;
188
- return await selectOptional('Auth strategy:', AUTH_OPTIONS[fwValue(backendFwObj)] || [], machine, 10, answers, BLOCKS.BACKEND);
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
- i--;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multi-agents-cli",
3
- "version": "1.1.78",
3
+ "version": "1.1.79",
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",