multi-agents-cli 1.1.9 → 1.1.11
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/README.md +18 -2
- package/core/templates/.agents/backend/API.md +270 -0
- package/core/templates/.agents/backend/AUTH.md +257 -0
- package/core/templates/.agents/backend/DB.md +268 -0
- package/core/templates/.agents/backend/EVENTS.md +264 -0
- package/core/templates/.agents/backend/INIT.md +250 -0
- package/core/templates/.agents/backend/JOBS.md +267 -0
- package/core/templates/.agents/backend/LOGIC.md +302 -0
- package/core/templates/.agents/backend/TESTING.md +277 -0
- package/core/templates/.agents/client/ACCESSIBILITY.md +277 -0
- package/core/templates/.agents/client/FORMS.md +245 -0
- package/core/templates/.agents/client/LOGIC.md +288 -0
- package/core/templates/.agents/client/ROUTING.md +246 -0
- package/core/templates/.agents/client/TESTING.md +252 -0
- package/core/templates/.agents/client/UI.md +237 -0
- package/core/templates/.agents/shared/CLOUD.md +229 -0
- package/core/templates/.agents/shared/CLOUD_TEARDOWN.md +158 -0
- package/core/templates/.agents/shared/SECURITY.md +297 -0
- package/core/templates/.frameworks/backend/django.md +55 -0
- package/core/templates/.frameworks/backend/express.md +74 -0
- package/core/templates/.frameworks/backend/fastapi.md +107 -0
- package/core/templates/.frameworks/backend/fastify.md +74 -0
- package/core/templates/.frameworks/backend/laravel.md +79 -0
- package/core/templates/.frameworks/backend/nestjs.md +75 -0
- package/core/templates/.frameworks/backend/rails.md +84 -0
- package/core/templates/.frameworks/client/angular.md +80 -0
- package/core/templates/.frameworks/client/nextjs.md +47 -0
- package/core/templates/.frameworks/client/nuxt.md +45 -0
- package/core/templates/.frameworks/client/remix.md +44 -0
- package/core/templates/.frameworks/client/sveltekit.md +44 -0
- package/core/templates/.frameworks/client/vite-react.md +45 -0
- package/core/templates/CLAUDE.md +562 -0
- package/core/templates/CONTRACTS.md +16 -0
- package/core/templates/backend/CLAUDE.md +207 -0
- package/core/templates/client/CLAUDE.md +213 -0
- package/core/workflow/agent.js +285 -6
- package/core/workflow/complete.js +155 -36
- package/core/workflow/reset.js +28 -21
- package/core/workflow/run.js +3 -0
- package/init.js +2 -2
- package/lib/questions-flow.js +13 -2
- package/lib/steps.js +13 -1
- package/lib/ui.js +8 -0
- package/package.json +1 -1
package/core/workflow/reset.js
CHANGED
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
|
|
18
18
|
const fs = require('fs');
|
|
19
19
|
const path = require('path');
|
|
20
|
-
const readline = require('readline');
|
|
21
20
|
const { execSync } = require('child_process');
|
|
22
21
|
|
|
23
22
|
let prompts = null;
|
|
@@ -69,6 +68,7 @@ const main = async () => {
|
|
|
69
68
|
|
|
70
69
|
// ── Load config and tracking ─────────────────────────────────────────────────
|
|
71
70
|
|
|
71
|
+
const confirmed = process.argv.includes('--confirmed');
|
|
72
72
|
const config = fs.existsSync(CONFIG_PATH) ? JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')) : {};
|
|
73
73
|
const tracking = fs.existsSync(TRACKING_PATH) ? JSON.parse(fs.readFileSync(TRACKING_PATH, 'utf8')) : {};
|
|
74
74
|
const projectName = config.projectName || path.basename(ROOT);
|
|
@@ -79,7 +79,7 @@ const main = async () => {
|
|
|
79
79
|
for (const scope of ['client', 'backend', 'shared']) {
|
|
80
80
|
const agents = tracking[scope] || {};
|
|
81
81
|
for (const [agent, data] of Object.entries(agents)) {
|
|
82
|
-
if (data && data.branch) {
|
|
82
|
+
if (data && data.branch && data.status !== 'COMPLETED') {
|
|
83
83
|
activeAgents.push({ scope, agent, data });
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -89,11 +89,12 @@ const main = async () => {
|
|
|
89
89
|
|
|
90
90
|
let remoteUrl = null;
|
|
91
91
|
try {
|
|
92
|
-
remoteUrl = execSync('git remote get-url origin', { cwd: ROOT, encoding: 'utf8' }).trim();
|
|
92
|
+
remoteUrl = execSync('git remote get-url origin', { cwd: ROOT, encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
93
93
|
} catch {}
|
|
94
94
|
|
|
95
95
|
// ── Display warning ───────────────────────────────────────────────────────────
|
|
96
96
|
|
|
97
|
+
if (!confirmed) {
|
|
97
98
|
separator();
|
|
98
99
|
console.log(`${red(bold(' ⚠ FULL PROJECT RESET'))}\n`);
|
|
99
100
|
console.log(` ${bold('This will permanently delete:')}\n`);
|
|
@@ -128,8 +129,7 @@ const main = async () => {
|
|
|
128
129
|
|
|
129
130
|
// ── Step 1: First confirmation ────────────────────────────────────────────────
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
|
|
132
|
+
|
|
133
133
|
|
|
134
134
|
if (prompts && process.stdin.isTTY) {
|
|
135
135
|
const step1 = await prompts({
|
|
@@ -146,13 +146,6 @@ const main = async () => {
|
|
|
146
146
|
console.log(dim('\n Reset cancelled.\n'));
|
|
147
147
|
process.exit(0);
|
|
148
148
|
}
|
|
149
|
-
} else {
|
|
150
|
-
const ans = await ask(` ${bold('Are you sure?')} ${dim('(y/N)')}: `);
|
|
151
|
-
if (ans.toLowerCase() !== 'y') {
|
|
152
|
-
console.log(dim('\n Reset cancelled.\n'));
|
|
153
|
-
rl.close();
|
|
154
|
-
process.exit(0);
|
|
155
|
-
}
|
|
156
149
|
}
|
|
157
150
|
|
|
158
151
|
// ── Step 2: Type project name ─────────────────────────────────────────────────
|
|
@@ -161,14 +154,20 @@ const main = async () => {
|
|
|
161
154
|
console.log(` ${yellow('To confirm, type the project name exactly:')}`);
|
|
162
155
|
console.log(` ${cyan(bold(projectName))}\n`);
|
|
163
156
|
|
|
164
|
-
const
|
|
165
|
-
|
|
157
|
+
const step2 = await prompts({
|
|
158
|
+
type: 'text',
|
|
159
|
+
name: 'value',
|
|
160
|
+
message: 'Project name',
|
|
161
|
+
}, { onCancel: () => process.exit(0) });
|
|
162
|
+
const typed = (step2.value || '').trim();
|
|
166
163
|
|
|
167
164
|
if (typed !== projectName) {
|
|
168
165
|
console.log(`\n${red(' ✗ Project name does not match. Reset cancelled.')}\n`);
|
|
169
166
|
process.exit(1);
|
|
170
167
|
}
|
|
171
168
|
|
|
169
|
+
} // end if (!confirmed)
|
|
170
|
+
|
|
172
171
|
// ── Execute wipe ──────────────────────────────────────────────────────────────
|
|
173
172
|
|
|
174
173
|
separator();
|
|
@@ -212,7 +211,7 @@ const main = async () => {
|
|
|
212
211
|
}
|
|
213
212
|
}
|
|
214
213
|
|
|
215
|
-
// Wipe project files (keep .git
|
|
214
|
+
// Wipe project files (keep .git — git repo is infrastructure, not framework state)
|
|
216
215
|
const keepList = ['.git'];
|
|
217
216
|
const entries = fs.readdirSync(ROOT);
|
|
218
217
|
for (const entry of entries) {
|
|
@@ -223,18 +222,26 @@ const main = async () => {
|
|
|
223
222
|
}
|
|
224
223
|
console.log(` ${green('✓')} Project files removed`);
|
|
225
224
|
|
|
226
|
-
|
|
225
|
+
|
|
226
|
+
// ── Re-plant package.json ────────────────────────────────────────────
|
|
227
|
+
|
|
227
228
|
try {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
const freshPkg = {
|
|
230
|
+
name: projectName.toLowerCase().replace(/\s+/g, '-'),
|
|
231
|
+
version: '1.0.0',
|
|
232
|
+
scripts: { init: 'multi-agents init' },
|
|
233
|
+
};
|
|
234
|
+
fs.writeFileSync(path.join(ROOT, 'package.json'), JSON.stringify(freshPkg, null, 2) + '\n', 'utf8');
|
|
235
|
+
console.log(' ' + green('\u2713') + ' package.json re-planted');
|
|
236
|
+
} catch { /* best-effort */ }
|
|
231
237
|
|
|
232
238
|
// ── Post-wipe instructions ────────────────────────────────────────────────────
|
|
233
239
|
|
|
234
240
|
separator();
|
|
235
241
|
console.log(`${green(bold(' Project wiped successfully.'))}\n`);
|
|
236
|
-
console.log(`
|
|
237
|
-
console.log(` ${cyan(
|
|
242
|
+
console.log(` Run to start fresh:\n`);
|
|
243
|
+
console.log(` ${cyan('npm run init')}
|
|
244
|
+
`);
|
|
238
245
|
separator();
|
|
239
246
|
|
|
240
247
|
process.exit(0);
|
package/core/workflow/run.js
CHANGED
|
@@ -56,6 +56,9 @@ const copyWorkflow = (cliRoot) => {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
const findCliRoot = () => {
|
|
59
|
+
// Local node_modules first (npx or local install)
|
|
60
|
+
const localPkg = path.join(__dirname, '..', 'node_modules', 'multi-agents-cli');
|
|
61
|
+
if (fs.existsSync(localPkg)) return localPkg;
|
|
59
62
|
try {
|
|
60
63
|
const globalPkg = execSync('npm root -g', { stdio: 'pipe', encoding: 'utf8' }).trim();
|
|
61
64
|
const p = path.join(globalPkg, 'multi-agents-cli');
|
package/init.js
CHANGED
|
@@ -78,7 +78,7 @@ const projectArg = isGlobalCLI ? args[1] : null;
|
|
|
78
78
|
|
|
79
79
|
if (isReInit) {
|
|
80
80
|
try {
|
|
81
|
-
const gitCommonDir = execSync('git rev-parse --git-common-dir', { encoding: 'utf8' }).trim();
|
|
81
|
+
const gitCommonDir = execSync('git rev-parse --git-common-dir', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
82
82
|
const repoRoot = path.resolve(gitCommonDir, '..');
|
|
83
83
|
process.chdir(repoRoot);
|
|
84
84
|
} catch { /* stay in current directory */ }
|
|
@@ -275,7 +275,7 @@ const main = async () => {
|
|
|
275
275
|
],
|
|
276
276
|
}, { onCancel: () => process.exit(0) });
|
|
277
277
|
if (confirm.value !== 'yes') { console.log(dim('\n Cancelled.\n')); process.exit(0); }
|
|
278
|
-
const resetChild = spawn('node', [path.join(ROOT, '.workflow', 'reset.js')], { stdio: 'inherit', cwd: ROOT });
|
|
278
|
+
const resetChild = spawn('node', [path.join(ROOT, '.workflow', 'reset.js'), '--confirmed'], { stdio: 'inherit', cwd: ROOT });
|
|
279
279
|
resetChild.on('exit', code => process.exit(code ?? 0));
|
|
280
280
|
return;
|
|
281
281
|
} else {
|
package/lib/questions-flow.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// ── Imports ───────────────────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
|
-
const { dim, bold, blue, green, cyan, separator, arrowSelect, arrowConfirm, selectRequired, selectOptional } = require('./ui');
|
|
5
|
+
const { dim, bold, blue, green, cyan, separator, arrowSelect, arrowConfirm, selectRequired, selectOptional, stepHeader } = require('./ui');
|
|
6
6
|
const { BACK, RESTART } = require('./steps');
|
|
7
7
|
const {
|
|
8
8
|
CLIENT_FRAMEWORKS, BACKEND_FRAMEWORKS, FRAMEWORK_VERSION_FALLBACK,
|
|
@@ -21,6 +21,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
21
21
|
{
|
|
22
22
|
name: 'clientFw',
|
|
23
23
|
run: async (machine, answers) => {
|
|
24
|
+
stepHeader(1, 12);
|
|
24
25
|
console.log(`\n${bold(blue('Client configuration'))}`);
|
|
25
26
|
return await selectRequired('* Client framework (required):', CLIENT_FRAMEWORKS, machine, 0);
|
|
26
27
|
},
|
|
@@ -30,6 +31,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
30
31
|
{
|
|
31
32
|
name: 'clientFwVersion',
|
|
32
33
|
run: async (machine, answers) => {
|
|
34
|
+
stepHeader(2, 12);
|
|
33
35
|
const fw = answers.clientFw;
|
|
34
36
|
const versions = await fetchLatestVersions(fw.value) || FRAMEWORK_VERSION_FALLBACK[fw.value] || [];
|
|
35
37
|
if (!versions.length) return null; // skip silently
|
|
@@ -56,6 +58,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
56
58
|
{
|
|
57
59
|
name: 'clientState',
|
|
58
60
|
run: async (machine, answers) => {
|
|
61
|
+
stepHeader(3, 12);
|
|
59
62
|
const opts = STATE_OPTIONS[answers.clientFw.value] || [];
|
|
60
63
|
return await selectOptional('State management:', opts, machine, 2);
|
|
61
64
|
},
|
|
@@ -65,6 +68,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
65
68
|
{
|
|
66
69
|
name: 'clientUi',
|
|
67
70
|
run: async (machine, answers) => {
|
|
71
|
+
stepHeader(4, 12);
|
|
68
72
|
const opts = UI_OPTIONS[answers.clientFw.value] || [];
|
|
69
73
|
return await selectOptional('UI library:', opts, machine, 3);
|
|
70
74
|
},
|
|
@@ -74,6 +78,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
74
78
|
{
|
|
75
79
|
name: 'clientStyle',
|
|
76
80
|
run: async (machine, answers) => {
|
|
81
|
+
stepHeader(5, 12);
|
|
77
82
|
return await selectOptional('Styling:', STYLING_OPTIONS, machine, 4);
|
|
78
83
|
},
|
|
79
84
|
},
|
|
@@ -82,6 +87,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
82
87
|
{
|
|
83
88
|
name: 'useIntegratedBackend',
|
|
84
89
|
run: async (machine, answers) => {
|
|
90
|
+
stepHeader(6, 12);
|
|
85
91
|
const fw = answers.clientFw;
|
|
86
92
|
if (!fw.integratedBackend) return false; // skip — not applicable
|
|
87
93
|
|
|
@@ -98,6 +104,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
98
104
|
{
|
|
99
105
|
name: 'backendFwObj',
|
|
100
106
|
run: async (machine, answers) => {
|
|
107
|
+
stepHeader(7, 12);
|
|
101
108
|
if (answers.useIntegratedBackend) return null; // skip
|
|
102
109
|
|
|
103
110
|
separator();
|
|
@@ -121,6 +128,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
121
128
|
{
|
|
122
129
|
name: 'backendFwVersion',
|
|
123
130
|
run: async (machine, answers) => {
|
|
131
|
+
stepHeader(8, 12);
|
|
124
132
|
const fwObj = answers.backendFwObj;
|
|
125
133
|
if (!fwObj) return null; // skip — no backend selected
|
|
126
134
|
|
|
@@ -147,6 +155,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
147
155
|
{
|
|
148
156
|
name: 'backendDb',
|
|
149
157
|
run: async (machine, answers) => {
|
|
158
|
+
stepHeader(9, 12);
|
|
150
159
|
if (!answers.backendFwObj) return null;
|
|
151
160
|
return await selectOptional('Database type:', DB_OPTIONS, machine, 8);
|
|
152
161
|
},
|
|
@@ -156,6 +165,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
156
165
|
{
|
|
157
166
|
name: 'backendOrm',
|
|
158
167
|
run: async (machine, answers) => {
|
|
168
|
+
stepHeader(10, 12);
|
|
159
169
|
const { backendFwObj, backendDb } = answers;
|
|
160
170
|
if (!backendFwObj || !backendDb) return null;
|
|
161
171
|
const ormChoices = ORM_OPTIONS_BY_DB[backendDb] || ORM_OPTIONS[backendFwObj.value] || [];
|
|
@@ -167,6 +177,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
167
177
|
{
|
|
168
178
|
name: 'backendAuth',
|
|
169
179
|
run: async (machine, answers) => {
|
|
180
|
+
stepHeader(11, 12);
|
|
170
181
|
const { backendFwObj } = answers;
|
|
171
182
|
if (!backendFwObj) return null;
|
|
172
183
|
return await selectOptional('Auth strategy:', AUTH_OPTIONS[backendFwObj.value] || [], machine, 10);
|
|
@@ -177,6 +188,7 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
177
188
|
{
|
|
178
189
|
name: 'ideChoice',
|
|
179
190
|
run: async (machine, answers) => {
|
|
191
|
+
stepHeader(12, 12);
|
|
180
192
|
separator();
|
|
181
193
|
console.log(`\n${bold(blue('Environment'))}`);
|
|
182
194
|
|
|
@@ -186,7 +198,6 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
186
198
|
|
|
187
199
|
const ideOptions = buildIDEOptions(IDE_CANDIDATES);
|
|
188
200
|
const detectedIDEs = ideOptions.filter(o => o.detected);
|
|
189
|
-
const undetectedIDEs = ideOptions.filter(o => !o.detected && o.cmd);
|
|
190
201
|
const manualOption = ideOptions.filter(o => !o.cmd);
|
|
191
202
|
const sorted = [...detectedIDEs, ...manualOption];
|
|
192
203
|
|
package/lib/steps.js
CHANGED
|
@@ -103,13 +103,25 @@ const runQuestions = async (stepDefs, machine) => {
|
|
|
103
103
|
if (result === RESTART) return RESTART;
|
|
104
104
|
|
|
105
105
|
if (result === BACK) {
|
|
106
|
-
if (i === 0) return RESTART; // nowhere to go back
|
|
106
|
+
if (i === 0) return RESTART; // nowhere to go back from step 0
|
|
107
|
+
machine.enterBackNav(i); // record where back-nav started
|
|
107
108
|
const popped = machine.pop();
|
|
108
109
|
if (popped) delete answers[popped.stepName];
|
|
109
110
|
i--;
|
|
110
111
|
continue;
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
if (result === CONTINUE) {
|
|
115
|
+
// User chose to resume — skip forward to where they first pressed Back
|
|
116
|
+
const resumeTarget = machine.resumeIndex ?? i + 1;
|
|
117
|
+
machine.exitBackNav();
|
|
118
|
+
i = resumeTarget;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Normal answer — if re-answering during back-nav, exit back-nav mode
|
|
123
|
+
if (machine.inBackNav) machine.exitBackNav();
|
|
124
|
+
|
|
113
125
|
machine.push(i, def.name, result);
|
|
114
126
|
answers[def.name] = result;
|
|
115
127
|
i++;
|
package/lib/ui.js
CHANGED
|
@@ -109,6 +109,13 @@ const renderTrajectoryLines = (lines) => {
|
|
|
109
109
|
});
|
|
110
110
|
};
|
|
111
111
|
|
|
112
|
+
// ── Step progress header ─────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
const stepHeader = (stepIndex, totalSteps) => {
|
|
115
|
+
console.log(`
|
|
116
|
+
${dim(`Step ${stepIndex} of ${totalSteps}`)}`);
|
|
117
|
+
};
|
|
118
|
+
|
|
112
119
|
// ── Step selection helpers ────────────────────────────────────────────────────
|
|
113
120
|
|
|
114
121
|
const { BACK, RESTART } = require('./steps');
|
|
@@ -150,6 +157,7 @@ const selectOptional = async (prompt, items, stepMachine, stepIndex) => {
|
|
|
150
157
|
// ── Exports ───────────────────────────────────────────────────────────────────
|
|
151
158
|
|
|
152
159
|
module.exports = {
|
|
160
|
+
stepHeader,
|
|
153
161
|
c, bold, green, yellow, dim, cyan, blue, red,
|
|
154
162
|
rl, ask,
|
|
155
163
|
arrowSelect, arrowConfirm,
|
package/package.json
CHANGED