multi-agents-cli 1.0.68 → 1.0.69
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 +236 -925
- package/lib/detect.js +132 -0
- package/lib/questions.js +266 -0
- package/lib/steps.js +89 -0
- package/lib/summary.js +140 -0
- package/lib/ui.js +119 -0
- package/lib/writers.js +150 -0
- package/package.json +2 -1
package/init.js
CHANGED
|
@@ -1,82 +1,71 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* multi-agents
|
|
5
|
-
* Run with:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* multi-agents-cli — Project Initializer
|
|
5
|
+
* Run with: multi-agents init <project-name>
|
|
6
|
+
* npm run init (inside existing project)
|
|
8
7
|
* Runs once. Locked after completion via .scaffold/.initialized
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
const fs = require('fs');
|
|
13
|
-
const path = require('path');
|
|
10
|
+
'use strict';
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
try { prompts = require('prompts'); } catch { prompts = null; }
|
|
19
|
-
|
|
20
|
-
const arrowSelect = async (message, choices, rl, showBack = false) => {
|
|
21
|
-
const allChoices = showBack
|
|
22
|
-
? [...choices, { label: dim('← Restart configuration') }]
|
|
23
|
-
: choices;
|
|
24
|
-
|
|
25
|
-
if (prompts && process.stdin.isTTY) {
|
|
26
|
-
const res = await prompts({
|
|
27
|
-
type: 'select',
|
|
28
|
-
name: 'value',
|
|
29
|
-
message,
|
|
30
|
-
choices: allChoices.map((c, i) => ({ title: typeof c === 'string' ? c : c.label, value: i })),
|
|
31
|
-
}, { onCancel: () => process.exit(0) });
|
|
32
|
-
return res.value ?? 0;
|
|
33
|
-
}
|
|
34
|
-
allChoices.forEach((c, i) => console.log(` ${dim(`${i + 1}.`)} ${typeof c === 'string' ? c : c.label}`));
|
|
35
|
-
return new Promise(resolve => {
|
|
36
|
-
rl.question(`\n Select (1-${allChoices.length}): `, ans => {
|
|
37
|
-
const n = parseInt(ans) - 1;
|
|
38
|
-
resolve(!isNaN(n) && n >= 0 && n < allChoices.length ? n : 0);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const arrowConfirm = async (message, rl) => {
|
|
44
|
-
if (prompts && process.stdin.isTTY) {
|
|
45
|
-
const res = await prompts({
|
|
46
|
-
type: 'confirm',
|
|
47
|
-
name: 'value',
|
|
48
|
-
message,
|
|
49
|
-
initial: true,
|
|
50
|
-
}, { onCancel: () => process.exit(0) });
|
|
51
|
-
return res.value ?? true;
|
|
52
|
-
}
|
|
53
|
-
return new Promise(resolve => {
|
|
54
|
-
rl.question(`${message} (y/n): `, ans => resolve(ans.toLowerCase() !== 'n'));
|
|
55
|
-
});
|
|
56
|
-
};
|
|
57
|
-
const os = require('os');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const os = require('os');
|
|
58
15
|
const { execSync, spawn } = require('child_process');
|
|
59
16
|
|
|
60
|
-
// ──
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
17
|
+
// ── Modules ───────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
c, bold, green, yellow, dim, cyan, blue, red,
|
|
21
|
+
rl, ask,
|
|
22
|
+
arrowSelect, arrowConfirm,
|
|
23
|
+
separator, showList, summaryLine, renderTrajectoryLines,
|
|
24
|
+
} = require('./lib/ui');
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
FRAMEWORK_CONVENTIONS,
|
|
28
|
+
CLIENT_FRAMEWORKS,
|
|
29
|
+
BACKEND_FRAMEWORKS,
|
|
30
|
+
FRAMEWORK_VERSION_FALLBACK,
|
|
31
|
+
fetchLatestVersions,
|
|
32
|
+
STATE_OPTIONS,
|
|
33
|
+
UI_OPTIONS,
|
|
34
|
+
STYLING_OPTIONS,
|
|
35
|
+
DB_OPTIONS,
|
|
36
|
+
ORM_OPTIONS_BY_DB,
|
|
37
|
+
ORM_OPTIONS,
|
|
38
|
+
AUTH_OPTIONS,
|
|
39
|
+
IDE_CANDIDATES,
|
|
40
|
+
} = require('./lib/questions');
|
|
41
|
+
|
|
42
|
+
const {
|
|
43
|
+
expandWinPath,
|
|
44
|
+
buildIDEOptions,
|
|
45
|
+
verifyIDE,
|
|
46
|
+
detectTerminal,
|
|
47
|
+
} = require('./lib/detect');
|
|
48
|
+
|
|
49
|
+
const {
|
|
50
|
+
writeConfig,
|
|
51
|
+
ensureGitignore,
|
|
52
|
+
copyDir,
|
|
53
|
+
generateTrackingStructure,
|
|
54
|
+
setupUserRemote,
|
|
55
|
+
} = require('./lib/writers');
|
|
56
|
+
|
|
57
|
+
const {
|
|
58
|
+
TRAJECTORY_DETAILS,
|
|
59
|
+
renderTrajectoryLines: renderTraj,
|
|
60
|
+
printInitSummary,
|
|
61
|
+
} = require('./lib/summary');
|
|
62
|
+
|
|
63
|
+
const { StepMachine, BACK, CONTINUE, RESTART } = require('./lib/steps');
|
|
64
|
+
|
|
65
|
+
// ── Prompts ───────────────────────────────────────────────────────────────────
|
|
72
66
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const yellow = (s) => `${c.yellow}${s}${c.reset}`;
|
|
76
|
-
const dim = (s) => `${c.dim}${s}${c.reset}`;
|
|
77
|
-
const cyan = (s) => `${c.cyan}${s}${c.reset}`;
|
|
78
|
-
const blue = (s) => `${c.blue}${s}${c.reset}`;
|
|
79
|
-
const red = (s) => `${c.red}${s}${c.reset}`;
|
|
67
|
+
let prompts;
|
|
68
|
+
try { prompts = require('prompts'); } catch { prompts = null; }
|
|
80
69
|
|
|
81
70
|
// ── CLI argument handling ─────────────────────────────────────────────────────
|
|
82
71
|
|
|
@@ -86,11 +75,9 @@ const isReInit = args[0] === 'init' && !args[1];
|
|
|
86
75
|
const projectArg = isGlobalCLI ? args[1] : null;
|
|
87
76
|
|
|
88
77
|
if (isReInit) {
|
|
89
|
-
// Re-init from inside project or worktree - self-relocate to repo root
|
|
90
78
|
try {
|
|
91
|
-
const { execSync } = require('child_process');
|
|
92
79
|
const gitCommonDir = execSync('git rev-parse --git-common-dir', { encoding: 'utf8' }).trim();
|
|
93
|
-
const repoRoot =
|
|
80
|
+
const repoRoot = path.resolve(gitCommonDir, '..');
|
|
94
81
|
process.chdir(repoRoot);
|
|
95
82
|
} catch { /* stay in current directory */ }
|
|
96
83
|
}
|
|
@@ -104,19 +91,16 @@ if (isGlobalCLI) {
|
|
|
104
91
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
105
92
|
process.chdir(targetDir);
|
|
106
93
|
|
|
107
|
-
// Write temporary package.json so npm run init works if abandoned mid-init
|
|
108
94
|
fs.writeFileSync(
|
|
109
95
|
path.join(targetDir, 'package.json'),
|
|
110
96
|
JSON.stringify({ name: path.basename(targetDir), version: '1.0.0', scripts: { init: 'multi-agents init' } }, null, 2),
|
|
111
97
|
'utf8'
|
|
112
98
|
);
|
|
113
99
|
|
|
114
|
-
// Initialize git
|
|
115
100
|
try {
|
|
116
101
|
execSync('git init -b main', { cwd: targetDir, stdio: 'pipe' });
|
|
117
102
|
execSync('git commit --allow-empty -m "init: project created"', { cwd: targetDir, stdio: 'pipe' });
|
|
118
103
|
} catch {
|
|
119
|
-
// Fallback for older git versions that don't support -b flag
|
|
120
104
|
try {
|
|
121
105
|
execSync('git init', { cwd: targetDir, stdio: 'pipe' });
|
|
122
106
|
execSync('git checkout -b main', { cwd: targetDir, stdio: 'pipe' });
|
|
@@ -126,537 +110,60 @@ if (isGlobalCLI) {
|
|
|
126
110
|
}
|
|
127
111
|
}
|
|
128
112
|
|
|
129
|
-
// ──
|
|
113
|
+
// ── Paths ─────────────────────────────────────────────────────────────────────
|
|
130
114
|
|
|
131
115
|
const ROOT = process.cwd();
|
|
132
116
|
const RUNTIME_DIR = path.join(ROOT, '.scaffold');
|
|
133
117
|
const LOCK_FILE = path.join(RUNTIME_DIR, '.initialized');
|
|
134
118
|
|
|
135
119
|
// Ensure .scaffold/ exists
|
|
136
|
-
|
|
137
|
-
fs.mkdirSync(RUNTIME_DIR, { recursive: true });
|
|
138
|
-
}
|
|
120
|
+
fs.mkdirSync(RUNTIME_DIR, { recursive: true });
|
|
139
121
|
|
|
140
|
-
// ──
|
|
141
|
-
|
|
142
|
-
const FRAMEWORK_CONVENTIONS = {
|
|
143
|
-
client: {
|
|
144
|
-
'Next.js': { root: 'client', typesDir: 'client/src/types', importAlias: '@/types' },
|
|
145
|
-
'Angular': { root: 'client', typesDir: 'client/src/app/core/types', importAlias: null },
|
|
146
|
-
'Nuxt': { root: 'client', typesDir: 'client/types', importAlias: '~/types' },
|
|
147
|
-
'SvelteKit': { root: 'client', typesDir: 'client/src/lib/types', importAlias: '$lib/types' },
|
|
148
|
-
'Vite+React': { root: 'client', typesDir: 'client/src/types', importAlias: null },
|
|
149
|
-
'Remix': { root: 'client', typesDir: 'client/app/types', importAlias: null },
|
|
150
|
-
},
|
|
151
|
-
backend: {
|
|
152
|
-
'Express': { root: 'backend', typesDir: 'backend/src/types', routesDir: 'backend/src/routes' },
|
|
153
|
-
'NestJS': { root: 'backend', dtoDir: 'backend/src/dto', entitiesDir:'backend/src/entities' },
|
|
154
|
-
'Fastify': { root: 'backend', typesDir: 'backend/src/types', routesDir: 'backend/src/routes' },
|
|
155
|
-
'FastAPI': { root: 'backend', schemasDir: 'backend/app/schemas', modelsDir: 'backend/app/models' },
|
|
156
|
-
'Django': { root: 'backend', schemasDir: 'backend/api/serializers', modelsDir: 'backend/api/models' },
|
|
157
|
-
},
|
|
158
|
-
};
|
|
122
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
159
123
|
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
{ label: 'SvelteKit', value: 'SvelteKit', language: 'TypeScript', integratedBackend: true },
|
|
165
|
-
{ label: 'Remix', value: 'Remix', language: 'TypeScript', integratedBackend: true },
|
|
166
|
-
{ label: 'Vite + React', value: 'Vite+React', language: 'TypeScript', integratedBackend: false },
|
|
167
|
-
];
|
|
168
|
-
|
|
169
|
-
const BACKEND_FRAMEWORKS = [
|
|
170
|
-
{ label: 'NestJS', value: 'NestJS', language: 'TypeScript' },
|
|
171
|
-
{ label: 'Express', value: 'Express', language: 'TypeScript' },
|
|
172
|
-
{ label: 'Fastify', value: 'Fastify', language: 'TypeScript' },
|
|
173
|
-
{ label: 'Django', value: 'Django', language: 'Python' },
|
|
174
|
-
{ label: 'FastAPI', value: 'FastAPI', language: 'Python' },
|
|
175
|
-
{ label: 'Laravel', value: 'Laravel', language: 'PHP' },
|
|
176
|
-
{ label: 'Rails', value: 'Rails', language: 'Ruby' },
|
|
177
|
-
];
|
|
178
|
-
|
|
179
|
-
// ── Framework version registry ────────────────────────────────────────────────
|
|
180
|
-
|
|
181
|
-
const FRAMEWORK_REGISTRY = {
|
|
182
|
-
'Next.js': { registry: 'npm', package: 'next' },
|
|
183
|
-
'Angular': { registry: 'npm', package: '@angular/core' },
|
|
184
|
-
'Nuxt': { registry: 'npm', package: 'nuxt' },
|
|
185
|
-
'SvelteKit': { registry: 'npm', package: '@sveltejs/kit' },
|
|
186
|
-
'Remix': { registry: 'npm', package: '@remix-run/react' },
|
|
187
|
-
'Vite+React': { registry: 'npm', package: 'vite' },
|
|
188
|
-
'NestJS': { registry: 'npm', package: '@nestjs/core' },
|
|
189
|
-
'Express': { registry: 'npm', package: 'express' },
|
|
190
|
-
'Fastify': { registry: 'npm', package: 'fastify' },
|
|
191
|
-
'FastAPI': { registry: 'pypi', package: 'fastapi' },
|
|
192
|
-
'Django': { registry: 'pypi', package: 'django' },
|
|
193
|
-
'Laravel': { registry: 'npm', package: null }, // skip — no npm package
|
|
194
|
-
'Rails': { registry: 'npm', package: null }, // skip — no npm package
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
const FRAMEWORK_VERSION_FALLBACK = {
|
|
198
|
-
'Next.js': ['15', '14', '13'],
|
|
199
|
-
'Angular': ['22', '21', '20'],
|
|
200
|
-
'Nuxt': ['3', '2', null],
|
|
201
|
-
'SvelteKit': ['2', '1', null],
|
|
202
|
-
'Remix': ['2', '1', null],
|
|
203
|
-
'Vite+React': ['8', '7', '6'],
|
|
204
|
-
'NestJS': ['11', '10', '9' ],
|
|
205
|
-
'Express': ['5', '4'],
|
|
206
|
-
'Fastify': ['5', '4', null],
|
|
207
|
-
'FastAPI': ['0.115', '0.111', '0.104'],
|
|
208
|
-
'Django': ['5.1', '4.2', '3.2'],
|
|
209
|
-
};
|
|
124
|
+
const selectRequired = async (prompt, items, stepMachine, stepIndex) => {
|
|
125
|
+
const isFirstStep = stepIndex <= 1;
|
|
126
|
+
const navOpts = stepMachine ? stepMachine.navOptions(stepIndex, isFirstStep) : [];
|
|
127
|
+
const allItems = [...items, ...navOpts];
|
|
210
128
|
|
|
211
|
-
const
|
|
212
|
-
const entry = FRAMEWORK_REGISTRY[frameworkValue];
|
|
213
|
-
if (!entry || !entry.package) return null;
|
|
129
|
+
const idx = await arrowSelect(prompt, allItems.map(i => ({ label: typeof i === 'string' ? i : i.label })));
|
|
214
130
|
|
|
215
|
-
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
const req = https.get(url, { timeout: 3000 }, (res) => {
|
|
219
|
-
let data = '';
|
|
220
|
-
res.on('data', chunk => data += chunk);
|
|
221
|
-
res.on('end', () => resolve(data));
|
|
222
|
-
});
|
|
223
|
-
req.on('error', reject);
|
|
224
|
-
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
if (entry.registry === 'npm') {
|
|
228
|
-
const raw = await fetch(`https://registry.npmjs.org/${entry.package}`);
|
|
229
|
-
const json = JSON.parse(raw);
|
|
230
|
-
const versions = Object.keys(json.versions || {})
|
|
231
|
-
.filter(v => /^\d+\.\d+\.\d+$/.test(v) && !v.includes('-'))
|
|
232
|
-
.map(v => parseInt(v.split('.')[0]))
|
|
233
|
-
.filter((v, i, arr) => arr.indexOf(v) === i)
|
|
234
|
-
.sort((a, b) => b - a)
|
|
235
|
-
.slice(0, 3);
|
|
236
|
-
return versions.length ? versions.map(String) : null;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (entry.registry === 'pypi') {
|
|
240
|
-
const raw = await fetch(`https://pypi.org/pypi/${entry.package}/json`);
|
|
241
|
-
const json = JSON.parse(raw);
|
|
242
|
-
const versions = Object.keys(json.releases || {})
|
|
243
|
-
.filter(v => /^\d+\.\d+(\.\d+)?$/.test(v))
|
|
244
|
-
.sort((a, b) => {
|
|
245
|
-
const [aMaj, aMin = 0] = a.split('.').map(Number);
|
|
246
|
-
const [bMaj, bMin = 0] = b.split('.').map(Number);
|
|
247
|
-
return bMaj !== aMaj ? bMaj - aMaj : bMin - aMin;
|
|
248
|
-
})
|
|
249
|
-
.map(v => v.split('.').slice(0, 2).join('.'))
|
|
250
|
-
.filter((v, i, arr) => arr.indexOf(v) === i)
|
|
251
|
-
.slice(0, 3);
|
|
252
|
-
return versions.length ? versions : null;
|
|
253
|
-
}
|
|
254
|
-
} catch {
|
|
255
|
-
return null;
|
|
131
|
+
if (idx >= items.length) {
|
|
132
|
+
const nav = navOpts[idx - items.length];
|
|
133
|
+
return nav ? nav.value : RESTART;
|
|
256
134
|
}
|
|
257
|
-
return null;
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
const STATE_OPTIONS = {
|
|
261
|
-
'Next.js': ['Zustand', 'Redux Toolkit', 'Jotai', 'TanStack Query'],
|
|
262
|
-
'Vite+React': ['Zustand', 'Redux Toolkit', 'Jotai', 'TanStack Query'],
|
|
263
|
-
'Remix': ['Zustand', 'Redux Toolkit', 'Jotai', 'TanStack Query'],
|
|
264
|
-
'Angular': ['NgRx', 'Signals (built-in)', 'Akita'],
|
|
265
|
-
'Nuxt': ['Pinia', 'Vuex'],
|
|
266
|
-
'SvelteKit': ['Svelte stores (built-in)', 'Zustand'],
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
const UI_OPTIONS = {
|
|
270
|
-
'Next.js': ['shadcn/ui', 'Radix UI', 'MUI', 'Chakra UI', 'Ant Design'],
|
|
271
|
-
'Vite+React': ['Radix UI', 'MUI', 'Chakra UI', 'Ant Design'],
|
|
272
|
-
'Remix': ['shadcn/ui', 'Radix UI', 'MUI', 'Chakra UI', 'Ant Design'],
|
|
273
|
-
'Angular': ['Angular Material', 'PrimeNG', 'Clarity'],
|
|
274
|
-
'Nuxt': ['Vuetify', 'PrimeVue', 'Naive UI'],
|
|
275
|
-
'SvelteKit': ['Skeleton', 'daisyUI', 'shadcn-svelte'],
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
const STYLING_OPTIONS = [
|
|
279
|
-
'Tailwind CSS',
|
|
280
|
-
'CSS Modules',
|
|
281
|
-
'Styled Components',
|
|
282
|
-
'SCSS / SASS',
|
|
283
|
-
'UnoCSS',
|
|
284
|
-
];
|
|
285
|
-
|
|
286
|
-
const DB_OPTIONS = ['PostgreSQL', 'MySQL', 'MongoDB', 'SQLite', 'Skip (agent will propose when needed)'];
|
|
287
|
-
|
|
288
|
-
const ORM_OPTIONS_BY_DB = {
|
|
289
|
-
'PostgreSQL': ['Prisma', 'TypeORM', 'Drizzle', 'Sequelize', 'raw pg driver', 'Skip (agent will propose when needed)'],
|
|
290
|
-
'MySQL': ['Prisma', 'TypeORM', 'Drizzle', 'Sequelize', 'raw mysql2 driver', 'Skip (agent will propose when needed)'],
|
|
291
|
-
'MongoDB': ['Mongoose', 'Prisma', 'raw MongoDB driver', 'Skip (agent will propose when needed)'],
|
|
292
|
-
'SQLite': ['Prisma', 'Drizzle', 'better-sqlite3', 'Skip (agent will propose when needed)'],
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
const ORM_OPTIONS = {
|
|
296
|
-
'NestJS': ['TypeORM', 'Prisma', 'MikroORM', 'Drizzle'],
|
|
297
|
-
'Express': ['Prisma', 'TypeORM', 'Drizzle', 'Sequelize'],
|
|
298
|
-
'Fastify': ['Prisma', 'TypeORM', 'Drizzle'],
|
|
299
|
-
'Django': ['Django ORM (built-in)', 'SQLAlchemy'],
|
|
300
|
-
'FastAPI': ['SQLAlchemy', 'Tortoise ORM', 'Beanie (MongoDB)'],
|
|
301
|
-
'Laravel': ['Eloquent (built-in)'],
|
|
302
|
-
'Rails': ['Active Record (built-in)'],
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
const AUTH_OPTIONS = {
|
|
306
|
-
'NestJS': ['Passport.js', 'JWT-only', 'OAuth2', 'Auth.js'],
|
|
307
|
-
'Express': ['Passport.js', 'JWT-only', 'OAuth2'],
|
|
308
|
-
'Fastify': ['fastify-jwt', 'Passport.js', 'OAuth2'],
|
|
309
|
-
'Django': ['Django Auth (built-in)', 'DRF TokenAuth', 'OAuth2'],
|
|
310
|
-
'FastAPI': ['JWT-only', 'OAuth2', 'FastAPI-Users'],
|
|
311
|
-
'Laravel': ['Laravel Sanctum', 'Laravel Passport', 'JWT'],
|
|
312
|
-
'Rails': ['Devise', 'JWT', 'OAuth2'],
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
const IDE_CANDIDATES = [
|
|
316
|
-
{
|
|
317
|
-
cmd: 'code',
|
|
318
|
-
name: 'VS Code',
|
|
319
|
-
mac: { app: 'Visual Studio Code', args: ['--new-window'] },
|
|
320
|
-
win: { paths: ['{LOCALAPPDATA}\\Programs\\Microsoft VS Code\\Code.exe', '{ProgramFiles}\\Microsoft VS Code\\Code.exe'], args: ['--new-window'] },
|
|
321
|
-
linux: { paths: ['/snap/bin/code', '/usr/bin/code', '/usr/local/bin/code'], args: ['--new-window'] },
|
|
322
|
-
},
|
|
323
|
-
{
|
|
324
|
-
cmd: 'cursor',
|
|
325
|
-
name: 'Cursor',
|
|
326
|
-
mac: { app: 'Cursor', args: ['--new-window'] },
|
|
327
|
-
win: { paths: ['{LOCALAPPDATA}\\Programs\\cursor\\Cursor.exe'], args: ['--new-window'] },
|
|
328
|
-
linux: { paths: ['/usr/bin/cursor', '/opt/cursor/cursor'], args: ['--new-window'] },
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
cmd: 'webstorm',
|
|
332
|
-
name: 'WebStorm',
|
|
333
|
-
mac: { app: 'WebStorm', toolboxApp: 'WebStorm', args: [] },
|
|
334
|
-
win: { paths: [
|
|
335
|
-
'{LOCALAPPDATA}\\JetBrains\\Toolbox\\scripts\\webstorm.cmd',
|
|
336
|
-
'{LOCALAPPDATA}\\Programs\\WebStorm\\bin\\webstorm64.exe',
|
|
337
|
-
], args: [] },
|
|
338
|
-
linux: { paths: [
|
|
339
|
-
`${os.homedir()}/.local/bin/webstorm`,
|
|
340
|
-
'/opt/webstorm/bin/webstorm.sh',
|
|
341
|
-
'/snap/webstorm/current/bin/webstorm.sh',
|
|
342
|
-
], args: [] },
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
cmd: 'idea',
|
|
346
|
-
name: 'IntelliJ IDEA',
|
|
347
|
-
mac: { app: 'IntelliJ IDEA', toolboxApp: 'IntelliJ IDEA', args: [] },
|
|
348
|
-
win: { paths: [
|
|
349
|
-
'{LOCALAPPDATA}\\JetBrains\\Toolbox\\scripts\\idea.cmd',
|
|
350
|
-
'{LOCALAPPDATA}\\Programs\\IntelliJ IDEA Community Edition\\bin\\idea64.exe',
|
|
351
|
-
'{ProgramFiles}\\JetBrains\\IntelliJ IDEA\\bin\\idea64.exe',
|
|
352
|
-
], args: [] },
|
|
353
|
-
linux: { paths: [
|
|
354
|
-
`${os.homedir()}/.local/bin/idea`,
|
|
355
|
-
'/opt/idea/bin/idea.sh',
|
|
356
|
-
'/snap/intellij-idea-community/current/bin/idea.sh',
|
|
357
|
-
], args: [] },
|
|
358
|
-
},
|
|
359
|
-
{
|
|
360
|
-
cmd: 'zed',
|
|
361
|
-
name: 'Zed',
|
|
362
|
-
mac: { app: 'Zed', args: [] },
|
|
363
|
-
win: { paths: [], args: [] },
|
|
364
|
-
linux: { paths: ['/usr/bin/zed', `${os.homedir()}/.local/bin/zed`], args: [] },
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
cmd: null,
|
|
368
|
-
name: 'Other / Manual',
|
|
369
|
-
note: 'prints worktree path, open it yourself',
|
|
370
|
-
mac: null,
|
|
371
|
-
win: null,
|
|
372
|
-
linux:null,
|
|
373
|
-
},
|
|
374
|
-
];
|
|
375
|
-
|
|
376
|
-
// Expands {LOCALAPPDATA} / {ProgramFiles} placeholders for Windows paths
|
|
377
|
-
const expandWinPath = (p) =>
|
|
378
|
-
p.replace('{LOCALAPPDATA}', process.env.LOCALAPPDATA || '')
|
|
379
|
-
.replace('{ProgramFiles}', process.env.ProgramFiles || 'C:\\Program Files');
|
|
380
|
-
|
|
381
|
-
const buildIDEOptions = () => {
|
|
382
|
-
const platform = process.platform;
|
|
383
|
-
|
|
384
|
-
return IDE_CANDIDATES.map(ide => {
|
|
385
|
-
if (!ide.cmd) {
|
|
386
|
-
const noteStr = ide.note ? dim(` (${ide.note})`) : '';
|
|
387
|
-
return { ...ide, detected: false, strategy: 'manual', label: `${ide.name} ${dim('→')}${noteStr}` };
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
let detected = false;
|
|
391
|
-
let strategy = 'cli';
|
|
392
|
-
|
|
393
|
-
if (platform === 'darwin' && ide.mac) {
|
|
394
|
-
// Mac — check .app bundle in /Applications, ~/Applications, and JetBrains Toolbox
|
|
395
|
-
const system = `/Applications/${ide.mac.app}.app`;
|
|
396
|
-
const user = path.join(os.homedir(), 'Applications', `${ide.mac.app}.app`);
|
|
397
|
-
const toolbox = path.join(os.homedir(), 'Applications', 'JetBrains Toolbox', `${ide.mac.app}.app`);
|
|
398
|
-
detected = fs.existsSync(system) || fs.existsSync(user) || fs.existsSync(toolbox);
|
|
399
|
-
if (detected) strategy = 'mac-app';
|
|
400
|
-
|
|
401
|
-
} else if (platform === 'win32' && ide.win) {
|
|
402
|
-
// Windows — CLI first, then known exe paths
|
|
403
|
-
try {
|
|
404
|
-
execSync(`where ${ide.cmd}`, { stdio: 'pipe' });
|
|
405
|
-
detected = true;
|
|
406
|
-
strategy = 'cli';
|
|
407
|
-
} catch {
|
|
408
|
-
const expanded = (ide.win.paths || []).map(expandWinPath);
|
|
409
|
-
detected = expanded.some(p => fs.existsSync(p));
|
|
410
|
-
if (detected) strategy = 'win-exe';
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
} else if (platform === 'linux' && ide.linux) {
|
|
414
|
-
// Linux — CLI first, then known install paths
|
|
415
|
-
try {
|
|
416
|
-
execSync(`which ${ide.cmd}`, { stdio: 'pipe' });
|
|
417
|
-
detected = true;
|
|
418
|
-
strategy = 'cli';
|
|
419
|
-
} catch {
|
|
420
|
-
detected = (ide.linux.paths || []).some(p => fs.existsSync(p));
|
|
421
|
-
if (detected) strategy = 'linux-path';
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const statusStr = detected ? green('✓ detected') : dim('✗ not found');
|
|
426
|
-
const noteStr = ide.note ? dim(` (${ide.note})`) : '';
|
|
427
|
-
return {
|
|
428
|
-
...ide,
|
|
429
|
-
detected,
|
|
430
|
-
strategy,
|
|
431
|
-
label: `${ide.name} ${statusStr}${noteStr}`,
|
|
432
|
-
};
|
|
433
|
-
});
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
const verifyIDE = (ide) => {
|
|
437
|
-
const platform = process.platform;
|
|
438
|
-
|
|
439
|
-
if (ide.strategy === 'mac-app' && ide.mac) {
|
|
440
|
-
// Mac — confirm .app exists and try to read version from plist
|
|
441
|
-
const appPath = `/Applications/${ide.mac.app}.app`;
|
|
442
|
-
if (!fs.existsSync(appPath) && !fs.existsSync(path.join(os.homedir(), 'Applications', `${ide.mac.app}.app`))) {
|
|
443
|
-
return { ok: false };
|
|
444
|
-
}
|
|
445
|
-
try {
|
|
446
|
-
const version = execSync(
|
|
447
|
-
`defaults read "/Applications/${ide.mac.app}.app/Contents/Info.plist" CFBundleShortVersionString`,
|
|
448
|
-
{ stdio: 'pipe', encoding: 'utf8' }
|
|
449
|
-
).trim();
|
|
450
|
-
return { ok: true, version };
|
|
451
|
-
} catch {
|
|
452
|
-
return { ok: true, version: null };
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Windows exe / Linux path / CLI — try --version
|
|
457
|
-
try {
|
|
458
|
-
const cmd = ide.strategy === 'win-exe'
|
|
459
|
-
? `"${(ide.win?.paths || []).map(expandWinPath).find(p => fs.existsSync(p))}"`
|
|
460
|
-
: ide.strategy === 'linux-path'
|
|
461
|
-
? `"${(ide.linux?.paths || []).find(p => fs.existsSync(p))}"`
|
|
462
|
-
: `"${ide.cmd}"`;
|
|
463
|
-
const result = execSync(`${cmd} --version`, { stdio: 'pipe', encoding: 'utf8' });
|
|
464
|
-
const version = result.split('\n')[0].trim();
|
|
465
|
-
return { ok: true, version };
|
|
466
|
-
} catch {
|
|
467
|
-
return { ok: false };
|
|
468
|
-
}
|
|
469
|
-
};
|
|
470
|
-
|
|
471
|
-
// ── Tracking structure ────────────────────────────────────────────────────────
|
|
472
|
-
|
|
473
|
-
const emptySlot = () => ({
|
|
474
|
-
branch: null,
|
|
475
|
-
timestamp: null,
|
|
476
|
-
launchedAt: null,
|
|
477
|
-
status: null,
|
|
478
|
-
missingCount: 0,
|
|
479
|
-
worktreePath: null,
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
const generateTrackingStructure = (config) => {
|
|
483
|
-
const bt = config.backend?.type;
|
|
484
|
-
|
|
485
|
-
const structure = {
|
|
486
|
-
client: {
|
|
487
|
-
UI: emptySlot(),
|
|
488
|
-
LOGIC: emptySlot(),
|
|
489
|
-
FORMS: emptySlot(),
|
|
490
|
-
ROUTING: emptySlot(),
|
|
491
|
-
TESTING: emptySlot(),
|
|
492
|
-
ACCESSIBILITY: emptySlot(),
|
|
493
|
-
},
|
|
494
|
-
shared: {
|
|
495
|
-
SECURITY: emptySlot(),
|
|
496
|
-
},
|
|
497
|
-
};
|
|
498
|
-
|
|
499
|
-
if (bt === 'separate') {
|
|
500
|
-
structure.backend = {
|
|
501
|
-
API: emptySlot(),
|
|
502
|
-
LOGIC: emptySlot(),
|
|
503
|
-
AUTH: emptySlot(),
|
|
504
|
-
DB: emptySlot(),
|
|
505
|
-
EVENTS: emptySlot(),
|
|
506
|
-
JOBS: emptySlot(),
|
|
507
|
-
TESTING: emptySlot(),
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
return structure;
|
|
512
|
-
};
|
|
513
|
-
|
|
514
|
-
// ── GitHub remote setup ───────────────────────────────────────────────────────
|
|
515
|
-
|
|
516
|
-
const detectGitHubUser = () => {
|
|
517
|
-
try {
|
|
518
|
-
return execSync('gh api user --jq .login',
|
|
519
|
-
{ encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
520
|
-
} catch {}
|
|
521
|
-
try {
|
|
522
|
-
return execSync('git config user.name',
|
|
523
|
-
{ encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
524
|
-
} catch {}
|
|
525
|
-
return null;
|
|
526
|
-
};
|
|
527
|
-
|
|
528
|
-
const setupUserRemote = (ROOT, projectName) => {
|
|
529
|
-
let currentOrigin = null;
|
|
530
|
-
try {
|
|
531
|
-
currentOrigin = execSync('git remote get-url origin',
|
|
532
|
-
{ cwd: ROOT, encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
533
|
-
} catch {}
|
|
534
|
-
|
|
535
|
-
// Already has their own remote — nothing to do
|
|
536
|
-
if (currentOrigin && !currentOrigin.includes('multi-agents-template')) return;
|
|
537
|
-
|
|
538
|
-
// Demote template origin to upstream
|
|
539
|
-
if (currentOrigin?.includes('multi-agents-template')) {
|
|
540
|
-
try {
|
|
541
|
-
execSync('git remote remove origin', { cwd: ROOT, stdio: 'pipe' });
|
|
542
|
-
execSync(`git remote add upstream ${currentOrigin}`, { cwd: ROOT, stdio: 'pipe' });
|
|
543
|
-
console.log(dim(' ℹ Template remote moved to upstream'));
|
|
544
|
-
} catch {}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// Write flag — agent will handle remote setup on first session
|
|
548
|
-
const flagPath = path.join(ROOT, '.scaffold', '.remote-setup-needed');
|
|
549
|
-
fs.writeFileSync(flagPath, JSON.stringify({
|
|
550
|
-
projectName,
|
|
551
|
-
createdAt: new Date().toISOString(),
|
|
552
|
-
}), 'utf8');
|
|
553
|
-
|
|
554
|
-
console.log(`\n ${yellow('ℹ No remote configured.')} Your first agent session will set this up.`);
|
|
555
|
-
console.log(dim(' All work stays local until then.\n'));
|
|
556
|
-
};
|
|
557
|
-
|
|
558
|
-
const renderTrajectoryLines = (lines) => {
|
|
559
|
-
const HEADERS = ['Benefits', 'Best for', 'Use agents for', 'Handle manually'];
|
|
560
|
-
lines.forEach(l => {
|
|
561
|
-
if (!l) { console.log(''); return; }
|
|
562
|
-
if (l.startsWith('⚠')) console.log(` ${yellow(l)}`);
|
|
563
|
-
else if (HEADERS.includes(l)) console.log(`\n ${bold(l)}`);
|
|
564
|
-
else if (l.startsWith('·')) console.log(` ${l}`);
|
|
565
|
-
else console.log(` ${dim(l)}`);
|
|
566
|
-
});
|
|
567
|
-
};
|
|
568
|
-
|
|
569
|
-
const rl = readline.createInterface({
|
|
570
|
-
input: process.stdin,
|
|
571
|
-
output: process.stdout,
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
const ask = (question) =>
|
|
575
|
-
new Promise((resolve) => rl.question(question, (a) => resolve(a.trim())));
|
|
576
|
-
|
|
577
|
-
// ── Selection helpers ─────────────────────────────────────────────────────────
|
|
578
|
-
|
|
579
|
-
const showList = (items, showSkip = false) => {
|
|
580
|
-
items.forEach((item, i) => {
|
|
581
|
-
const label = typeof item === 'string' ? item : item.label;
|
|
582
|
-
console.log(` ${dim(`${i + 1}.`)} ${label}`);
|
|
583
|
-
});
|
|
584
|
-
if (showSkip) console.log(` ${dim('0.')} Skip ${dim('(agent will propose when needed)')}`);
|
|
585
|
-
};
|
|
586
|
-
|
|
587
|
-
// Sentinel value returned when user picks ← Restart
|
|
588
|
-
const BACK = Symbol('BACK');
|
|
589
|
-
|
|
590
|
-
const selectRequired = async (prompt, items) => {
|
|
591
|
-
const idx = await arrowSelect(prompt, items.map(i => ({ label: typeof i === 'string' ? i : i.label })), rl, true);
|
|
592
|
-
if (idx === items.length) return BACK;
|
|
593
135
|
return items[idx];
|
|
594
136
|
};
|
|
595
137
|
|
|
596
|
-
const selectOptional = async (prompt, items) => {
|
|
138
|
+
const selectOptional = async (prompt, items, stepMachine, stepIndex) => {
|
|
597
139
|
if (!items || items.length === 0) return null;
|
|
140
|
+
const isFirstStep = stepIndex <= 1;
|
|
141
|
+
const navOpts = stepMachine ? stepMachine.navOptions(stepIndex, isFirstStep) : [];
|
|
598
142
|
const choices = [
|
|
599
143
|
...items.map(i => ({ label: typeof i === 'string' ? i : i.label })),
|
|
600
144
|
{ label: dim('Skip (agent will propose when needed)') },
|
|
145
|
+
...navOpts.map(n => ({ label: n.label })),
|
|
601
146
|
];
|
|
602
|
-
const idx = await arrowSelect(prompt, choices, rl, true);
|
|
603
|
-
if (idx === choices.length) return BACK;
|
|
604
|
-
if (idx === items.length) return null;
|
|
605
|
-
return typeof items[idx] === 'string' ? items[idx] : items[idx].value;
|
|
606
|
-
};
|
|
607
|
-
|
|
608
|
-
const separator = () => console.log(`\n${dim('─'.repeat(60))}`);
|
|
609
|
-
|
|
610
|
-
// ── Config writer ─────────────────────────────────────────────────────────────
|
|
611
|
-
|
|
612
|
-
const writeConfig = (filePath, configs) => {
|
|
613
|
-
if (!fs.existsSync(filePath)) return;
|
|
614
|
-
let content = fs.readFileSync(filePath, 'utf8');
|
|
615
|
-
|
|
616
|
-
for (const [key, value] of Object.entries(configs)) {
|
|
617
|
-
if (!value) continue;
|
|
618
|
-
const regex = new RegExp(`(# @config ${key}\\s*:)([^\\n]*)`, 'g');
|
|
619
|
-
content = content.replace(regex, `$1 ${value}`);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
for (const [key, value] of Object.entries(configs)) {
|
|
623
|
-
if (!value) continue;
|
|
624
|
-
const token = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
|
|
625
|
-
content = content.replace(token, value);
|
|
626
|
-
}
|
|
627
147
|
|
|
628
|
-
|
|
629
|
-
};
|
|
630
|
-
|
|
631
|
-
const ensureGitignore = (entry) => {
|
|
632
|
-
const p = path.join(ROOT, '.gitignore');
|
|
633
|
-
const content = fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : '';
|
|
634
|
-
if (!content.includes(entry)) fs.appendFileSync(p, `\n${entry}\n`);
|
|
635
|
-
};
|
|
148
|
+
const idx = await arrowSelect(prompt, choices);
|
|
636
149
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
} else {
|
|
642
|
-
console.log(` ${dim(padded)}: ${green(value)}`);
|
|
150
|
+
if (idx === items.length) return null; // skip
|
|
151
|
+
if (idx > items.length) {
|
|
152
|
+
const nav = navOpts[idx - items.length - 1];
|
|
153
|
+
return nav ? nav.value : RESTART;
|
|
643
154
|
}
|
|
155
|
+
return typeof items[idx] === 'string' ? items[idx] : items[idx].value;
|
|
644
156
|
};
|
|
645
157
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
const copyDir = (src, dest) => {
|
|
649
|
-
if (!fs.existsSync(src)) return;
|
|
158
|
+
const copyDirExcluding = (src, dest, exclude = []) => {
|
|
650
159
|
fs.mkdirSync(dest, { recursive: true });
|
|
651
|
-
fs.readdirSync(src)
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
}
|
|
659
|
-
});
|
|
160
|
+
for (const entry of fs.readdirSync(src)) {
|
|
161
|
+
if (exclude.includes(entry)) continue;
|
|
162
|
+
const srcFile = path.join(src, entry);
|
|
163
|
+
const destFile = path.join(dest, entry);
|
|
164
|
+
if (fs.statSync(srcFile).isDirectory()) copyDirExcluding(srcFile, destFile, []);
|
|
165
|
+
else fs.copyFileSync(srcFile, destFile);
|
|
166
|
+
}
|
|
660
167
|
};
|
|
661
168
|
|
|
662
169
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
@@ -670,11 +177,10 @@ const main = async () => {
|
|
|
670
177
|
const trackingPath = path.join(RUNTIME_DIR, '.tracking.json');
|
|
671
178
|
const tracking = fs.existsSync(trackingPath) ? JSON.parse(fs.readFileSync(trackingPath, 'utf8')) : {};
|
|
672
179
|
|
|
673
|
-
// Dependency map — primary agents whose restart cascades to dependents
|
|
674
180
|
const DEPENDENCIES = {
|
|
675
|
-
client:
|
|
181
|
+
client: { UI: ['LOGIC', 'FORMS', 'ROUTING', 'TESTING', 'ACCESSIBILITY'] },
|
|
676
182
|
backend: { DB: ['API', 'AUTH', 'LOGIC', 'EVENTS', 'JOBS', 'TESTING'] },
|
|
677
|
-
shared:
|
|
183
|
+
shared: {},
|
|
678
184
|
};
|
|
679
185
|
|
|
680
186
|
const getActiveAgents = (scope) => {
|
|
@@ -683,7 +189,6 @@ const main = async () => {
|
|
|
683
189
|
};
|
|
684
190
|
|
|
685
191
|
const showRestartProcess = async () => {
|
|
686
|
-
// Build list of active processes across all scopes
|
|
687
192
|
const active = [];
|
|
688
193
|
for (const scope of ['client', 'backend', 'shared']) {
|
|
689
194
|
for (const [agent, data] of getActiveAgents(scope)) {
|
|
@@ -699,8 +204,8 @@ const main = async () => {
|
|
|
699
204
|
separator();
|
|
700
205
|
|
|
701
206
|
const pickRes = await prompts({
|
|
702
|
-
type:
|
|
703
|
-
name:
|
|
207
|
+
type: 'select',
|
|
208
|
+
name: 'value',
|
|
704
209
|
message: 'Which process do you want to restart?',
|
|
705
210
|
choices: [
|
|
706
211
|
...active.map(({ scope, agent, data }) => ({
|
|
@@ -718,54 +223,39 @@ const main = async () => {
|
|
|
718
223
|
const deps = (DEPENDENCIES[scope] || {})[agent] || [];
|
|
719
224
|
const affectedAgents = [{ scope, agent, data }];
|
|
720
225
|
|
|
721
|
-
// Find dependent agents that are also active
|
|
722
226
|
for (const dep of deps) {
|
|
723
227
|
const depData = (tracking[scope] || {})[dep];
|
|
724
|
-
if (depData && depData.branch) {
|
|
725
|
-
affectedAgents.push({ scope, agent: dep, data: depData });
|
|
726
|
-
}
|
|
228
|
+
if (depData && depData.branch) affectedAgents.push({ scope, agent: dep, data: depData });
|
|
727
229
|
}
|
|
728
230
|
|
|
729
|
-
// Show warning with exact names
|
|
730
231
|
separator();
|
|
731
232
|
console.log(`\n${yellow(` ⚠ Restarting ${agent} will delete:`)}`);
|
|
732
233
|
for (const { agent: a, data: d } of affectedAgents) {
|
|
733
234
|
console.log(`\n ${bold(a)}`);
|
|
734
235
|
console.log(` - Branch (${d.branch})`);
|
|
735
236
|
console.log(` - Remote branch (origin/${d.branch})`);
|
|
736
|
-
if (d.worktreePath) {
|
|
737
|
-
const wtName = path.relative(ROOT, d.worktreePath);
|
|
738
|
-
console.log(` - Worktree (${wtName})`);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
if (affectedAgents.length > 1) {
|
|
743
|
-
console.log(`\n ${yellow('Dependent processes will also be wiped.')}`);
|
|
237
|
+
if (d.worktreePath) console.log(` - Worktree (${path.relative(ROOT, d.worktreePath)})`);
|
|
744
238
|
}
|
|
745
239
|
|
|
240
|
+
if (affectedAgents.length > 1) console.log(`\n ${yellow('Dependent processes will also be wiped.')}`);
|
|
746
241
|
console.log(`\n ${red('This cannot be undone.')}\n`);
|
|
747
242
|
|
|
748
243
|
const confirmRes = await prompts({
|
|
749
|
-
type:
|
|
750
|
-
name:
|
|
244
|
+
type: 'select',
|
|
245
|
+
name: 'value',
|
|
751
246
|
message: 'Confirm restart?',
|
|
752
247
|
choices: [
|
|
753
248
|
{ title: 'Yes - wipe and restart', value: 'y' },
|
|
754
|
-
{ title: 'Cancel',
|
|
249
|
+
{ title: 'Cancel', value: 'n' },
|
|
755
250
|
],
|
|
756
251
|
}, { onCancel: () => process.exit(0) });
|
|
757
252
|
|
|
758
|
-
if (confirmRes.value !== 'y') {
|
|
759
|
-
console.log(dim('\n Cancelled.\n'));
|
|
760
|
-
return 'back';
|
|
761
|
-
}
|
|
253
|
+
if (confirmRes.value !== 'y') { console.log(dim('\n Cancelled.\n')); return 'back'; }
|
|
762
254
|
|
|
763
|
-
// Perform wipe
|
|
764
255
|
for (const { agent: a, data: d, scope: s } of affectedAgents) {
|
|
765
256
|
try { execSync(`git worktree remove "${d.worktreePath}" --force`, { cwd: ROOT, stdio: 'pipe' }); } catch {}
|
|
766
257
|
try { execSync(`git branch -D ${d.branch}`, { cwd: ROOT, stdio: 'pipe' }); } catch {}
|
|
767
258
|
try { execSync(`git push origin --delete ${d.branch}`, { cwd: ROOT, stdio: 'pipe' }); } catch {}
|
|
768
|
-
// Clear tracking
|
|
769
259
|
if (tracking[s] && tracking[s][a]) {
|
|
770
260
|
tracking[s][a] = { branch: null, timestamp: null, launchedAt: null, status: null, missingCount: 0, worktreePath: null };
|
|
771
261
|
}
|
|
@@ -780,9 +270,9 @@ const main = async () => {
|
|
|
780
270
|
separator();
|
|
781
271
|
console.log(`\n${yellow(' This project has already been initialized.')}`);
|
|
782
272
|
console.log(dim(` Initialized on: ${ts}\n`));
|
|
783
|
-
console.log(dim(
|
|
784
|
-
console.log(dim(
|
|
785
|
-
console.log(dim(
|
|
273
|
+
console.log(dim(' To start a task: ') + cyan('npm run agent'));
|
|
274
|
+
console.log(dim(' To restart an agent: ') + cyan('npm run restart'));
|
|
275
|
+
console.log(dim(' To wipe everything: ') + cyan('npm run reset') + '\n');
|
|
786
276
|
|
|
787
277
|
if (prompts && process.stdin.isTTY) {
|
|
788
278
|
const res = await prompts({
|
|
@@ -791,7 +281,7 @@ const main = async () => {
|
|
|
791
281
|
message: 'What would you like to do?',
|
|
792
282
|
choices: [
|
|
793
283
|
{ title: 'Re-initialize project', description: 'Wipe everything and start fresh', value: '1' },
|
|
794
|
-
{ title: 'Cancel',
|
|
284
|
+
{ title: 'Cancel', value: '2' },
|
|
795
285
|
],
|
|
796
286
|
}, { onCancel: () => process.exit(0) });
|
|
797
287
|
|
|
@@ -808,12 +298,8 @@ const main = async () => {
|
|
|
808
298
|
{ title: 'No - Cancel', value: 'no' },
|
|
809
299
|
],
|
|
810
300
|
}, { onCancel: () => process.exit(0) });
|
|
811
|
-
if (confirm.value !== 'yes') {
|
|
812
|
-
|
|
813
|
-
process.exit(0);
|
|
814
|
-
}
|
|
815
|
-
const { spawn: sp } = require('child_process');
|
|
816
|
-
const resetChild = sp('node', [path.join(ROOT, '.workflow', 'reset.js')], { stdio: 'inherit', cwd: ROOT });
|
|
301
|
+
if (confirm.value !== 'yes') { console.log(dim('\n Cancelled.\n')); process.exit(0); }
|
|
302
|
+
const resetChild = spawn('node', [path.join(ROOT, '.workflow', 'reset.js')], { stdio: 'inherit', cwd: ROOT });
|
|
817
303
|
resetChild.on('exit', code => process.exit(code ?? 0));
|
|
818
304
|
return;
|
|
819
305
|
} else {
|
|
@@ -828,36 +314,36 @@ const main = async () => {
|
|
|
828
314
|
console.log(dim(' Project Initializer\n'));
|
|
829
315
|
separator();
|
|
830
316
|
|
|
831
|
-
console.log(`\n${bold(
|
|
317
|
+
console.log(`\n${bold("Let's configure your project.")}`);
|
|
832
318
|
console.log(dim(' Use arrow keys to select. Optional fields can be skipped.\n'));
|
|
833
319
|
console.log(dim(' Skipped fields will be resolved by the agent when first needed.\n'));
|
|
834
320
|
|
|
835
|
-
// ──
|
|
321
|
+
// ── Step machine ─────────────────────────────────────────────────────────────
|
|
322
|
+
|
|
323
|
+
const steps = new StepMachine();
|
|
324
|
+
|
|
325
|
+
// ── Project name (step 1) ─────────────────────────────────────────────────────
|
|
836
326
|
|
|
837
327
|
let projectName = '';
|
|
838
328
|
while (!projectName) {
|
|
839
329
|
projectName = await ask(`${bold('* Project name')}: `);
|
|
840
330
|
if (!projectName) console.log(yellow(' Project name is required. Please enter a name.'));
|
|
841
331
|
}
|
|
842
|
-
|
|
843
|
-
const restartIfBack = (val) => {
|
|
844
|
-
if (val !== BACK) return false;
|
|
845
|
-
rl.close();
|
|
846
|
-
const { spawn } = require('child_process');
|
|
847
|
-
spawn('node', [__filename], { stdio: 'inherit', cwd: ROOT }).on('exit', c => process.exit(c));
|
|
848
|
-
return true;
|
|
849
|
-
};
|
|
332
|
+
steps.push(0, 'Project name', projectName);
|
|
850
333
|
|
|
851
334
|
separator();
|
|
852
335
|
|
|
853
|
-
// ──
|
|
336
|
+
// ── Questions loop ────────────────────────────────────────────────────────────
|
|
854
337
|
|
|
855
|
-
|
|
338
|
+
let stepIdx = 1;
|
|
856
339
|
|
|
857
|
-
|
|
858
|
-
|
|
340
|
+
// Step 1: Client framework
|
|
341
|
+
console.log(`\n${bold(blue('Client configuration'))}`);
|
|
342
|
+
let clientFw = await selectRequired('* Client framework (required):', CLIENT_FRAMEWORKS, steps, stepIdx);
|
|
343
|
+
if (clientFw === RESTART) { rl.close(); spawn('node', [__filename], { stdio: 'inherit', cwd: ROOT }).on('exit', c => process.exit(c)); return; }
|
|
344
|
+
steps.push(stepIdx++, 'Client framework', clientFw);
|
|
859
345
|
|
|
860
|
-
//
|
|
346
|
+
// Step 2: Client framework version
|
|
861
347
|
let clientFwVersion = null;
|
|
862
348
|
const clientVersions = await fetchLatestVersions(clientFw.value) || FRAMEWORK_VERSION_FALLBACK[clientFw.value] || [];
|
|
863
349
|
if (clientVersions.length) {
|
|
@@ -867,37 +353,52 @@ const main = async () => {
|
|
|
867
353
|
value: v,
|
|
868
354
|
}));
|
|
869
355
|
const versionLabel = clientFw.value === 'Vite+React' ? '* Vite version:' : `* ${clientFw.value} version:`;
|
|
870
|
-
const vIdx = await arrowSelect(versionLabel,
|
|
871
|
-
|
|
872
|
-
|
|
356
|
+
const vIdx = await arrowSelect(versionLabel, [
|
|
357
|
+
...versionChoices,
|
|
358
|
+
...steps.navOptions(stepIdx, false).map(n => ({ label: n.label })),
|
|
359
|
+
]);
|
|
360
|
+
if (vIdx < clientVersions.length) {
|
|
361
|
+
clientFwVersion = clientVersions[vIdx];
|
|
362
|
+
steps.push(stepIdx++, 'Client version', clientFwVersion);
|
|
363
|
+
} else {
|
|
364
|
+
rl.close(); spawn('node', [__filename], { stdio: 'inherit', cwd: ROOT }).on('exit', c => process.exit(c)); return;
|
|
365
|
+
}
|
|
873
366
|
}
|
|
874
367
|
|
|
875
|
-
const clientLang
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
if (
|
|
880
|
-
|
|
881
|
-
|
|
368
|
+
const clientLang = clientFw.language;
|
|
369
|
+
|
|
370
|
+
// Step 3: State management
|
|
371
|
+
let clientState = await selectOptional('State management:', STATE_OPTIONS[clientFw.value] || [], steps, stepIdx);
|
|
372
|
+
if (clientState === RESTART) { rl.close(); spawn('node', [__filename], { stdio: 'inherit', cwd: ROOT }).on('exit', c => process.exit(c)); return; }
|
|
373
|
+
steps.push(stepIdx++, 'State management', clientState);
|
|
374
|
+
|
|
375
|
+
// Step 4: UI library
|
|
376
|
+
let clientUi = await selectOptional('UI library:', UI_OPTIONS[clientFw.value] || [], steps, stepIdx);
|
|
377
|
+
if (clientUi === RESTART) { rl.close(); spawn('node', [__filename], { stdio: 'inherit', cwd: ROOT }).on('exit', c => process.exit(c)); return; }
|
|
378
|
+
steps.push(stepIdx++, 'UI library', clientUi);
|
|
379
|
+
|
|
380
|
+
// Step 5: Styling
|
|
381
|
+
let clientStyle = await selectOptional('Styling:', STYLING_OPTIONS, steps, stepIdx);
|
|
382
|
+
if (clientStyle === RESTART) { rl.close(); spawn('node', [__filename], { stdio: 'inherit', cwd: ROOT }).on('exit', c => process.exit(c)); return; }
|
|
383
|
+
steps.push(stepIdx++, 'Styling', clientStyle);
|
|
882
384
|
|
|
883
385
|
separator();
|
|
884
386
|
|
|
885
|
-
// ── Backend
|
|
387
|
+
// ── Backend ──────────────────────────────────────────────────────────────────
|
|
886
388
|
|
|
887
389
|
console.log(`\n${bold(blue('Backend configuration'))}`);
|
|
888
390
|
|
|
889
|
-
// Check if client framework has integrated backend support
|
|
890
391
|
let useIntegratedBackend = false;
|
|
891
|
-
let backendFw
|
|
892
|
-
let backendLang
|
|
893
|
-
let backendOrm
|
|
894
|
-
let backendAuth
|
|
895
|
-
let backendType
|
|
392
|
+
let backendFw = null;
|
|
393
|
+
let backendLang = null;
|
|
394
|
+
let backendOrm = null;
|
|
395
|
+
let backendAuth = null;
|
|
396
|
+
let backendType = null;
|
|
896
397
|
let backendFwObj = null;
|
|
897
398
|
|
|
898
399
|
if (clientFw.integratedBackend) {
|
|
899
400
|
console.log(dim(` ${clientFw.value} supports server-side rendering and API routes.\n`));
|
|
900
|
-
useIntegratedBackend = await arrowConfirm(`Use integrated backend (${clientFw.value} API routes/SSR) instead of a separate backend
|
|
401
|
+
useIntegratedBackend = await arrowConfirm(`Use integrated backend (${clientFw.value} API routes/SSR) instead of a separate backend?`);
|
|
901
402
|
|
|
902
403
|
if (useIntegratedBackend) {
|
|
903
404
|
backendType = 'integrated';
|
|
@@ -912,13 +413,12 @@ const main = async () => {
|
|
|
912
413
|
...BACKEND_FRAMEWORKS.map(f => ({ label: f.label || f.value })),
|
|
913
414
|
{ label: dim('Skip (decide later)') },
|
|
914
415
|
];
|
|
915
|
-
const backendIdx = await arrowSelect('Backend framework:', backendChoices
|
|
416
|
+
const backendIdx = await arrowSelect('Backend framework:', backendChoices);
|
|
916
417
|
backendFwObj = backendIdx === BACKEND_FRAMEWORKS.length ? null : BACKEND_FRAMEWORKS[backendIdx];
|
|
418
|
+
backendFw = backendFwObj ? backendFwObj.value : null;
|
|
419
|
+
backendLang = backendFwObj ? backendFwObj.language : null;
|
|
420
|
+
steps.push(stepIdx++, 'Backend framework', backendFw);
|
|
917
421
|
|
|
918
|
-
backendFw = backendFwObj ? backendFwObj.value : null;
|
|
919
|
-
backendLang = backendFwObj ? backendFwObj.language : null;
|
|
920
|
-
|
|
921
|
-
// ── Backend framework version ──────────────────────────────────────────────
|
|
922
422
|
if (backendFw) {
|
|
923
423
|
const backendVersions = await fetchLatestVersions(backendFw) || FRAMEWORK_VERSION_FALLBACK[backendFw] || [];
|
|
924
424
|
if (backendVersions.length) {
|
|
@@ -926,33 +426,40 @@ const main = async () => {
|
|
|
926
426
|
label: i === 0 ? `v${v} ${dim('(latest)')}` : `v${v}`,
|
|
927
427
|
value: v,
|
|
928
428
|
}));
|
|
929
|
-
const vIdx = await arrowSelect(`* ${backendFw} version:`,
|
|
930
|
-
|
|
931
|
-
|
|
429
|
+
const vIdx = await arrowSelect(`* ${backendFw} version:`, [
|
|
430
|
+
...vChoices,
|
|
431
|
+
...steps.navOptions(stepIdx, false).map(n => ({ label: n.label })),
|
|
432
|
+
]);
|
|
433
|
+
if (vIdx < backendVersions.length) {
|
|
434
|
+
backendFwObj = { ...backendFwObj, version: backendVersions[vIdx] };
|
|
435
|
+
steps.push(stepIdx++, 'Backend version', backendVersions[vIdx]);
|
|
436
|
+
}
|
|
932
437
|
}
|
|
933
438
|
}
|
|
934
439
|
|
|
935
|
-
// DB type
|
|
936
440
|
let backendDb = null;
|
|
937
441
|
if (backendFw) {
|
|
938
|
-
backendDb = await selectOptional('Database type:', DB_OPTIONS);
|
|
939
|
-
if (
|
|
442
|
+
backendDb = await selectOptional('Database type:', DB_OPTIONS, steps, stepIdx);
|
|
443
|
+
if (backendDb === RESTART) { rl.close(); spawn('node', [__filename], { stdio: 'inherit', cwd: ROOT }).on('exit', c => process.exit(c)); return; }
|
|
444
|
+
steps.push(stepIdx++, 'Database', backendDb);
|
|
940
445
|
}
|
|
941
|
-
|
|
942
|
-
backendOrm = null;
|
|
446
|
+
|
|
943
447
|
if (backendFw && backendDb && backendDb !== 'Skip (agent will propose when needed)') {
|
|
944
448
|
const ormChoices = ORM_OPTIONS_BY_DB[backendDb] || ORM_OPTIONS[backendFw] || [];
|
|
945
|
-
backendOrm = await selectOptional('ORM / query layer:', ormChoices);
|
|
946
|
-
if (
|
|
449
|
+
backendOrm = await selectOptional('ORM / query layer:', ormChoices, steps, stepIdx);
|
|
450
|
+
if (backendOrm === RESTART) { rl.close(); spawn('node', [__filename], { stdio: 'inherit', cwd: ROOT }).on('exit', c => process.exit(c)); return; }
|
|
451
|
+
steps.push(stepIdx++, 'ORM', backendOrm);
|
|
947
452
|
}
|
|
948
|
-
|
|
949
|
-
|
|
453
|
+
|
|
454
|
+
backendAuth = backendFw ? await selectOptional('Auth strategy:', AUTH_OPTIONS[backendFw] || [], steps, stepIdx) : null;
|
|
455
|
+
if (backendAuth === RESTART) { rl.close(); spawn('node', [__filename], { stdio: 'inherit', cwd: ROOT }).on('exit', c => process.exit(c)); return; }
|
|
456
|
+
steps.push(stepIdx++, 'Auth', backendAuth);
|
|
950
457
|
backendType = backendFw ? 'separate' : null;
|
|
951
458
|
}
|
|
952
459
|
|
|
953
460
|
separator();
|
|
954
461
|
|
|
955
|
-
// ──
|
|
462
|
+
// ── IDE ───────────────────────────────────────────────────────────────────────
|
|
956
463
|
|
|
957
464
|
console.log(`\n${bold(blue('Environment'))}`);
|
|
958
465
|
|
|
@@ -960,48 +467,31 @@ const main = async () => {
|
|
|
960
467
|
console.log(`\n ${dim('OS detected:')} ${bold(osName)}`);
|
|
961
468
|
console.log(dim(' Scanning for installed IDEs...\n'));
|
|
962
469
|
|
|
963
|
-
const ideOptions
|
|
964
|
-
|
|
470
|
+
const ideOptions = buildIDEOptions(IDE_CANDIDATES);
|
|
965
471
|
const detectedIDEs = ideOptions.filter(o => o.detected);
|
|
966
472
|
const undetectedIDEs = ideOptions.filter(o => !o.detected && o.cmd);
|
|
967
473
|
const manualOption = ideOptions.filter(o => !o.cmd);
|
|
968
|
-
|
|
969
|
-
// Detected first → undetected → manual
|
|
970
474
|
const sortedIdeOptions = [...detectedIDEs, ...undetectedIDEs, ...manualOption];
|
|
971
475
|
|
|
972
|
-
if (detectedIDEs.length > 1)
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
console.log(`\n ${green(`1 IDE found:`)} ${bold(detectedIDEs[0].name)}\n`);
|
|
976
|
-
} else {
|
|
977
|
-
console.log(`\n ${yellow('No IDEs detected on this machine.')}\n`);
|
|
978
|
-
}
|
|
476
|
+
if (detectedIDEs.length > 1) console.log(`\n ${yellow('Multiple IDEs found on this machine')} — select your preference:\n`);
|
|
477
|
+
else if (detectedIDEs.length === 1) console.log(`\n ${green('1 IDE found:')} ${bold(detectedIDEs[0].name)}\n`);
|
|
478
|
+
else console.log(`\n ${yellow('No IDEs detected on this machine.')}\n`);
|
|
979
479
|
|
|
980
480
|
let ideChoice;
|
|
981
481
|
while (true) {
|
|
982
|
-
ideChoice = await selectRequired('* IDE / editor (required):', sortedIdeOptions);
|
|
983
|
-
if (
|
|
482
|
+
ideChoice = await selectRequired('* IDE / editor (required):', sortedIdeOptions, steps, stepIdx);
|
|
483
|
+
if (ideChoice === RESTART) { rl.close(); spawn('node', [__filename], { stdio: 'inherit', cwd: ROOT }).on('exit', c => process.exit(c)); return; }
|
|
984
484
|
|
|
985
|
-
// ── Confirmation ──────────────────────────────────────────────────────────
|
|
986
485
|
if (ideChoice.cmd && !ideChoice.detected) {
|
|
987
486
|
console.log(`\n ${yellow('⚠')} ${bold(ideChoice.name)} was not detected on this machine.`);
|
|
988
487
|
console.log(dim(' It may not open automatically when launching a task.\n'));
|
|
989
|
-
if (!await arrowConfirm('Continue with this IDE anyway?'
|
|
990
|
-
console.log(dim(' Re-selecting...\n'));
|
|
991
|
-
continue;
|
|
992
|
-
}
|
|
488
|
+
if (!await arrowConfirm('Continue with this IDE anyway?')) { console.log(dim(' Re-selecting...\n')); continue; }
|
|
993
489
|
}
|
|
994
490
|
|
|
995
|
-
|
|
996
|
-
if (!ideChoice.cmd) {
|
|
997
|
-
// Manual — no verification needed
|
|
998
|
-
console.log(dim(' Manual mode — worktree path will be printed at launch.'));
|
|
999
|
-
break;
|
|
1000
|
-
}
|
|
491
|
+
if (!ideChoice.cmd) { console.log(dim(' Manual mode — worktree path will be printed at launch.')); break; }
|
|
1001
492
|
|
|
1002
493
|
console.log(dim(`\n Verifying ${ideChoice.name}...`));
|
|
1003
494
|
const verified = verifyIDE(ideChoice);
|
|
1004
|
-
|
|
1005
495
|
if (verified.ok) {
|
|
1006
496
|
const versionStr = verified.version ? dim(` (${verified.version})`) : '';
|
|
1007
497
|
console.log(` ${green('✓')} ${ideChoice.name} confirmed${versionStr}`);
|
|
@@ -1009,39 +499,19 @@ const main = async () => {
|
|
|
1009
499
|
}
|
|
1010
500
|
|
|
1011
501
|
console.log(` ${yellow('!')} Could not verify ${ideChoice.name}. The CLI may not be installed or accessible.`);
|
|
1012
|
-
if (await arrowConfirm('Continue with this IDE anyway?'
|
|
502
|
+
if (await arrowConfirm('Continue with this IDE anyway?')) break;
|
|
1013
503
|
console.log(dim(' Re-selecting...\n'));
|
|
1014
504
|
}
|
|
1015
|
-
|
|
505
|
+
steps.push(stepIdx++, 'IDE', ideChoice.name);
|
|
1016
506
|
|
|
1017
507
|
// ── Terminal detection ────────────────────────────────────────────────────────
|
|
1018
508
|
|
|
1019
|
-
const detectTerminal = () => {
|
|
1020
|
-
const platform = process.platform;
|
|
1021
|
-
if (platform === 'darwin') {
|
|
1022
|
-
const apps = [
|
|
1023
|
-
{ name: 'iTerm2', cmd: 'iTerm2', path: '/Applications/iTerm.app' },
|
|
1024
|
-
{ name: 'Warp', cmd: 'Warp', path: '/Applications/Warp.app' },
|
|
1025
|
-
{ name: 'Terminal.app', cmd: 'Terminal', path: '/System/Applications/Utilities/Terminal.app' },
|
|
1026
|
-
];
|
|
1027
|
-
return apps.find(a => fs.existsSync(a.path)) || { name: 'Terminal.app', cmd: 'Terminal' };
|
|
1028
|
-
} else if (platform === 'win32') {
|
|
1029
|
-
const wtPath = process.env.LOCALAPPDATA + '\\Microsoft\\WindowsApps\\wt.exe';
|
|
1030
|
-
if (fs.existsSync(wtPath)) return { name: 'Windows Terminal', cmd: 'wt' };
|
|
1031
|
-
return { name: 'Command Prompt', cmd: 'cmd' };
|
|
1032
|
-
} else {
|
|
1033
|
-
const terms = ['gnome-terminal', 'konsole', 'xterm'];
|
|
1034
|
-
for (const t of terms) {
|
|
1035
|
-
try { execSync('which ' + t, { stdio: 'pipe' }); return { name: t, cmd: t }; } catch {}
|
|
1036
|
-
}
|
|
1037
|
-
return { name: 'xterm', cmd: 'xterm' };
|
|
1038
|
-
}
|
|
1039
|
-
};
|
|
1040
|
-
|
|
1041
509
|
const termChoice = detectTerminal();
|
|
1042
510
|
console.log(dim(' Terminal detected: ') + green(termChoice.name));
|
|
1043
511
|
|
|
1044
|
-
|
|
512
|
+
separator();
|
|
513
|
+
|
|
514
|
+
// ── Summary ───────────────────────────────────────────────────────────────────
|
|
1045
515
|
|
|
1046
516
|
console.log(`\n${bold('Review your configuration:')}\n`);
|
|
1047
517
|
summaryLine('Project', projectName);
|
|
@@ -1050,7 +520,7 @@ const main = async () => {
|
|
|
1050
520
|
summaryLine('State management', clientState);
|
|
1051
521
|
summaryLine('UI library', clientUi);
|
|
1052
522
|
summaryLine('Styling', clientStyle);
|
|
1053
|
-
summaryLine('Backend type',
|
|
523
|
+
summaryLine('Backend type', backendType === 'integrated' ? `${clientFw.value} integrated` : backendFw || '(skipped)');
|
|
1054
524
|
if (backendType !== 'integrated') {
|
|
1055
525
|
summaryLine('Backend language', backendLang);
|
|
1056
526
|
summaryLine('ORM', backendOrm);
|
|
@@ -1060,18 +530,17 @@ const main = async () => {
|
|
|
1060
530
|
|
|
1061
531
|
console.log('');
|
|
1062
532
|
console.log(dim(' y = confirm | n = abort | e = edit (start over)\n'));
|
|
533
|
+
|
|
1063
534
|
const confirmIdx = await arrowSelect('Confirm and write to config files?', [
|
|
1064
535
|
{ label: `${green('✓')} Confirm — write config and set up project` },
|
|
1065
536
|
{ label: `${yellow('↺')} Restart — redo configuration` },
|
|
1066
537
|
{ label: `${red('✗')} Abort` },
|
|
1067
|
-
]
|
|
538
|
+
]);
|
|
1068
539
|
|
|
1069
540
|
if (confirmIdx === 1) {
|
|
1070
541
|
console.log(yellow('\n Restarting configuration...\n'));
|
|
1071
542
|
rl.close();
|
|
1072
|
-
|
|
1073
|
-
const child = spawn('node', [__filename], { stdio: 'inherit', cwd: ROOT });
|
|
1074
|
-
child.on('exit', (code) => process.exit(code));
|
|
543
|
+
spawn('node', [__filename], { stdio: 'inherit', cwd: ROOT }).on('exit', c => process.exit(c));
|
|
1075
544
|
return;
|
|
1076
545
|
}
|
|
1077
546
|
|
|
@@ -1081,33 +550,15 @@ const main = async () => {
|
|
|
1081
550
|
return;
|
|
1082
551
|
}
|
|
1083
552
|
|
|
1084
|
-
// ── Write configs
|
|
553
|
+
// ── Write configs ─────────────────────────────────────────────────────────────
|
|
1085
554
|
|
|
1086
555
|
separator();
|
|
1087
556
|
console.log(`\n${bold('Setting up your project...')}\n`);
|
|
1088
557
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
const CORE_DIR = path.join(__dirname, 'core');
|
|
1092
|
-
|
|
1093
|
-
console.log(` ${green('✓')} Templates ready`);
|
|
1094
|
-
|
|
558
|
+
const CORE_DIR = path.join(__dirname, 'core');
|
|
1095
559
|
const TEMPLATES = path.join(CORE_DIR, 'templates');
|
|
1096
560
|
|
|
1097
|
-
|
|
1098
|
-
// agents/ and frameworks/ are now at repo root as .agents/ and .frameworks/
|
|
1099
|
-
// We copy client/backend/shared but exclude agents/ and frameworks/ subdirs
|
|
1100
|
-
|
|
1101
|
-
const copyDirExcluding = (src, dest, exclude = []) => {
|
|
1102
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
1103
|
-
for (const entry of fs.readdirSync(src)) {
|
|
1104
|
-
if (exclude.includes(entry)) continue;
|
|
1105
|
-
const srcFile = path.join(src, entry);
|
|
1106
|
-
const destFile = path.join(dest, entry);
|
|
1107
|
-
if (fs.statSync(srcFile).isDirectory()) copyDirExcluding(srcFile, destFile, []);
|
|
1108
|
-
else fs.copyFileSync(srcFile, destFile);
|
|
1109
|
-
}
|
|
1110
|
-
};
|
|
561
|
+
console.log(` ${green('✓')} Templates ready`);
|
|
1111
562
|
|
|
1112
563
|
copyDirExcluding(path.join(TEMPLATES, 'client'), path.join(ROOT, 'client'), ['agents', 'frameworks']);
|
|
1113
564
|
if (fs.existsSync(path.join(TEMPLATES, 'shared')))
|
|
@@ -1117,37 +568,27 @@ const main = async () => {
|
|
|
1117
568
|
fs.writeFileSync(path.join(ROOT, 'backend', '.gitkeep'), '', 'utf8');
|
|
1118
569
|
}
|
|
1119
570
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
copyDir(path.join(TEMPLATES, '.agents',
|
|
1123
|
-
copyDir(path.join(TEMPLATES, '.frameworks', 'client'), path.join(ROOT, '.frameworks', 'client'));
|
|
1124
|
-
copyDir(path.join(TEMPLATES, '.agents', 'shared'), path.join(ROOT, '.agents', 'shared'));
|
|
571
|
+
copyDir(path.join(TEMPLATES, '.agents', 'client'), path.join(ROOT, '.agents', 'client'));
|
|
572
|
+
copyDir(path.join(TEMPLATES, '.frameworks', 'client'), path.join(ROOT, '.frameworks', 'client'));
|
|
573
|
+
copyDir(path.join(TEMPLATES, '.agents', 'shared'), path.join(ROOT, '.agents', 'shared'));
|
|
1125
574
|
if (backendType === 'separate') {
|
|
1126
|
-
copyDir(path.join(TEMPLATES, '.agents',
|
|
1127
|
-
copyDir(path.join(TEMPLATES, '.frameworks',
|
|
575
|
+
copyDir(path.join(TEMPLATES, '.agents', 'backend'), path.join(ROOT, '.agents', 'backend'));
|
|
576
|
+
copyDir(path.join(TEMPLATES, '.frameworks', 'backend'), path.join(ROOT, '.frameworks', 'backend'));
|
|
1128
577
|
}
|
|
1129
578
|
|
|
1130
|
-
fs.copyFileSync(path.join(TEMPLATES, 'CLAUDE.md'),
|
|
1131
|
-
fs.copyFileSync(path.join(TEMPLATES, 'CONTRACTS.md'),
|
|
579
|
+
fs.copyFileSync(path.join(TEMPLATES, 'CLAUDE.md'), path.join(ROOT, 'CLAUDE.md'));
|
|
580
|
+
fs.copyFileSync(path.join(TEMPLATES, 'CONTRACTS.md'), path.join(ROOT, 'CONTRACTS.md'));
|
|
1132
581
|
fs.copyFileSync(path.join(TEMPLATES, 'TASKS_HISTORY.md'), path.join(ROOT, 'TASKS_HISTORY.md'));
|
|
1133
|
-
fs.copyFileSync(path.join(TEMPLATES, 'CLOUD_STATE.md'),
|
|
582
|
+
fs.copyFileSync(path.join(TEMPLATES, 'CLOUD_STATE.md'), path.join(ROOT, 'CLOUD_STATE.md'));
|
|
1134
583
|
console.log(` ${green('✓')} Templates copied`);
|
|
1135
584
|
|
|
1136
|
-
// ── Copy workflow scripts ────────────────────────────────────────────────────
|
|
1137
|
-
|
|
1138
585
|
const WORKFLOW_SRC = path.join(CORE_DIR, 'workflow');
|
|
1139
586
|
const WORKFLOW_DEST = path.join(ROOT, '.workflow');
|
|
1140
587
|
fs.mkdirSync(WORKFLOW_DEST, { recursive: true });
|
|
1141
588
|
copyDir(WORKFLOW_SRC, WORKFLOW_DEST);
|
|
1142
589
|
console.log(` ${green('✓')} Workflow scripts copied (.workflow/)`);
|
|
1143
|
-
console.log(` ${green('✓')} Temporary files cleaned up`);
|
|
1144
|
-
|
|
1145
|
-
// ── Write @config values ─────────────────────────────────────────────────────
|
|
1146
590
|
|
|
1147
|
-
writeConfig(path.join(ROOT, 'CLAUDE.md'), {
|
|
1148
|
-
PROJECT_NAME: projectName,
|
|
1149
|
-
PROJECT_ROOT: projectName,
|
|
1150
|
-
});
|
|
591
|
+
writeConfig(path.join(ROOT, 'CLAUDE.md'), { PROJECT_NAME: projectName, PROJECT_ROOT: projectName });
|
|
1151
592
|
console.log(` ${green('✓')} CLAUDE.md configured`);
|
|
1152
593
|
|
|
1153
594
|
writeConfig(path.join(ROOT, 'client', 'CLAUDE.md'), {
|
|
@@ -1173,23 +614,20 @@ const main = async () => {
|
|
|
1173
614
|
console.log(` ${green('✓')} backend/CLAUDE.md configured`);
|
|
1174
615
|
}
|
|
1175
616
|
|
|
1176
|
-
ensureGitignore('worktrees/');
|
|
1177
|
-
ensureGitignore('.scaffold/');
|
|
1178
|
-
ensureGitignore('.workflow/');
|
|
1179
|
-
ensureGitignore('node_modules/');
|
|
617
|
+
ensureGitignore(ROOT, 'worktrees/');
|
|
618
|
+
ensureGitignore(ROOT, '.scaffold/');
|
|
619
|
+
ensureGitignore(ROOT, '.workflow/');
|
|
620
|
+
ensureGitignore(ROOT, 'node_modules/');
|
|
1180
621
|
|
|
1181
|
-
// Remove template-specific gitignore entries so generated files can be committed
|
|
1182
622
|
const gitignorePath = path.join(ROOT, '.gitignore');
|
|
1183
623
|
let gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
|
1184
624
|
['client/', 'backend/', 'shared/', 'CLAUDE.md', 'CONTRACTS.md', 'BUILD_STATE.md', 'TASKS_HISTORY.md', 'CLOUD_STATE.md'].forEach(entry => {
|
|
1185
|
-
gitignoreContent = gitignoreContent.replace(`\n${entry}`, '');
|
|
1186
|
-
gitignoreContent = gitignoreContent.replace(`${entry}\n`, '');
|
|
1187
|
-
gitignoreContent = gitignoreContent.replace(entry, '');
|
|
625
|
+
gitignoreContent = gitignoreContent.replace(`\n${entry}`, '').replace(`${entry}\n`, '').replace(entry, '');
|
|
1188
626
|
});
|
|
1189
627
|
fs.writeFileSync(gitignorePath, gitignoreContent.trim() + '\n', 'utf8');
|
|
1190
628
|
console.log(` ${green('✓')} .gitignore updated`);
|
|
1191
629
|
|
|
1192
|
-
// ──
|
|
630
|
+
// ── .config.json ─────────────────────────────────────────────────────────────
|
|
1193
631
|
|
|
1194
632
|
const config = {
|
|
1195
633
|
projectName,
|
|
@@ -1228,34 +666,24 @@ const main = async () => {
|
|
|
1228
666
|
},
|
|
1229
667
|
};
|
|
1230
668
|
|
|
1231
|
-
fs.writeFileSync(
|
|
1232
|
-
path.join(RUNTIME_DIR, '.config.json'),
|
|
1233
|
-
JSON.stringify(config, null, 2),
|
|
1234
|
-
'utf8'
|
|
1235
|
-
);
|
|
669
|
+
fs.writeFileSync(path.join(RUNTIME_DIR, '.config.json'), JSON.stringify(config, null, 2), 'utf8');
|
|
1236
670
|
console.log(` ${green('✓')} .scaffold/.config.json written`);
|
|
1237
671
|
|
|
1238
|
-
// ──
|
|
672
|
+
// ── scope-policy.json ─────────────────────────────────────────────────────────
|
|
1239
673
|
|
|
1240
674
|
const scopePolicy = {
|
|
1241
675
|
client: {
|
|
1242
676
|
allowed: ['client/**'],
|
|
1243
677
|
blocked: ['backend/**', 'shared/**', 'CONTRACTS.md', 'BUILD_STATE.md', 'TASKS_HISTORY.md'],
|
|
1244
678
|
agentOverrides: {
|
|
1245
|
-
UI: {
|
|
1246
|
-
allowed: ['client/**', 'shared/wiring.config.json'],
|
|
1247
|
-
onlyBeforeScaffolded: true,
|
|
1248
|
-
},
|
|
679
|
+
UI: { allowed: ['client/**', 'shared/wiring.config.json'], onlyBeforeScaffolded: true },
|
|
1249
680
|
},
|
|
1250
681
|
},
|
|
1251
682
|
backend: {
|
|
1252
683
|
allowed: ['backend/**'],
|
|
1253
684
|
blocked: ['client/**', 'shared/**', 'CONTRACTS.md', 'BUILD_STATE.md', 'TASKS_HISTORY.md'],
|
|
1254
685
|
agentOverrides: {
|
|
1255
|
-
INIT: {
|
|
1256
|
-
allowed: ['backend/**', 'shared/wiring.config.json', 'CONTRACTS.md'],
|
|
1257
|
-
onlyBeforeScaffolded: true,
|
|
1258
|
-
},
|
|
686
|
+
INIT: { allowed: ['backend/**', 'shared/wiring.config.json', 'CONTRACTS.md'], onlyBeforeScaffolded: true },
|
|
1259
687
|
},
|
|
1260
688
|
},
|
|
1261
689
|
shared: {
|
|
@@ -1264,22 +692,16 @@ const main = async () => {
|
|
|
1264
692
|
},
|
|
1265
693
|
};
|
|
1266
694
|
|
|
1267
|
-
fs.writeFileSync(
|
|
1268
|
-
path.join(RUNTIME_DIR, 'scope-policy.json'),
|
|
1269
|
-
JSON.stringify(scopePolicy, null, 2),
|
|
1270
|
-
'utf8'
|
|
1271
|
-
);
|
|
695
|
+
fs.writeFileSync(path.join(RUNTIME_DIR, 'scope-policy.json'), JSON.stringify(scopePolicy, null, 2), 'utf8');
|
|
1272
696
|
console.log(` ${green('✓')} .scaffold/scope-policy.json written`);
|
|
1273
697
|
|
|
1274
|
-
// ──
|
|
698
|
+
// ── BUILD_STATE.md ────────────────────────────────────────────────────────────
|
|
1275
699
|
|
|
1276
700
|
const backendDisplay = backendType === 'integrated'
|
|
1277
701
|
? `${clientFw.value} integrated (API routes/SSR)`
|
|
1278
702
|
: backendFw || 'Not configured';
|
|
1279
703
|
|
|
1280
|
-
const clientStack = [clientFw.value, clientLang, clientStyle, clientUi, clientState]
|
|
1281
|
-
.filter(Boolean).join(' + ');
|
|
1282
|
-
|
|
704
|
+
const clientStack = [clientFw.value, clientLang, clientStyle, clientUi, clientState].filter(Boolean).join(' + ');
|
|
1283
705
|
const backendStack = backendType === 'separate'
|
|
1284
706
|
? [backendFw, backendLang, backendOrm, backendAuth].filter(Boolean).join(' + ')
|
|
1285
707
|
: backendDisplay;
|
|
@@ -1335,10 +757,6 @@ Before starting any task, verify:
|
|
|
1335
757
|
- Backend AUTH requires: DB User entity done
|
|
1336
758
|
- Any cross-boundary types: Must exist in CONTRACTS.md first
|
|
1337
759
|
|
|
1338
|
-
If a dependency is not met:
|
|
1339
|
-
DEPENDENCY NOT MET - surface what is missing and propose options.
|
|
1340
|
-
Never proceed silently on a missing dependency.
|
|
1341
|
-
|
|
1342
760
|
## Agent Log
|
|
1343
761
|
| Date | Agent | Scope | Task | Status | Branch |
|
|
1344
762
|
|------|-------|-------|------|--------|--------|
|
|
@@ -1347,15 +765,13 @@ If a dependency is not met:
|
|
|
1347
765
|
fs.writeFileSync(path.join(ROOT, 'BUILD_STATE.md'), buildState, 'utf8');
|
|
1348
766
|
console.log(` ${green('✓')} BUILD_STATE.md generated`);
|
|
1349
767
|
|
|
1350
|
-
// ──
|
|
768
|
+
// ── package.json ──────────────────────────────────────────────────────────────
|
|
1351
769
|
|
|
1352
770
|
const userPackage = {
|
|
1353
771
|
name: projectName.toLowerCase().replace(/\s+/g, '-'),
|
|
1354
772
|
version: '1.0.0',
|
|
1355
773
|
private: true,
|
|
1356
|
-
dependencies: {
|
|
1357
|
-
prompts: '^2.4.2',
|
|
1358
|
-
},
|
|
774
|
+
dependencies: { prompts: '^2.4.2' },
|
|
1359
775
|
scripts: {
|
|
1360
776
|
init: 'multi-agents init',
|
|
1361
777
|
agent: 'node .workflow/agent.js',
|
|
@@ -1367,8 +783,6 @@ If a dependency is not met:
|
|
|
1367
783
|
fs.writeFileSync(path.join(ROOT, 'package.json'), JSON.stringify(userPackage, null, 2), 'utf8');
|
|
1368
784
|
console.log(` ${green('✓')} package.json generated`);
|
|
1369
785
|
|
|
1370
|
-
// ── Install dependencies ──────────────────────────────────────────────────────
|
|
1371
|
-
|
|
1372
786
|
try {
|
|
1373
787
|
console.log(dim(' Installing dependencies...'));
|
|
1374
788
|
execSync('npm install', { cwd: ROOT, stdio: 'pipe' });
|
|
@@ -1381,17 +795,16 @@ If a dependency is not met:
|
|
|
1381
795
|
|
|
1382
796
|
const trackingPath = path.join(RUNTIME_DIR, '.tracking.json');
|
|
1383
797
|
if (!fs.existsSync(trackingPath)) {
|
|
1384
|
-
|
|
1385
|
-
fs.writeFileSync(trackingPath, JSON.stringify(trackingStructure, null, 2), 'utf8');
|
|
798
|
+
fs.writeFileSync(trackingPath, JSON.stringify(generateTrackingStructure(config), null, 2), 'utf8');
|
|
1386
799
|
console.log(` ${green('✓')} .tracking.json generated`);
|
|
1387
800
|
} else {
|
|
1388
801
|
console.log(dim(' ℹ .tracking.json already exists — preserved'));
|
|
1389
802
|
}
|
|
1390
803
|
|
|
1391
|
-
// ──
|
|
804
|
+
// ── .paths.json ───────────────────────────────────────────────────────────────
|
|
1392
805
|
|
|
1393
806
|
const pathsMap = {};
|
|
1394
|
-
const clientConventions
|
|
807
|
+
const clientConventions = FRAMEWORK_CONVENTIONS.client[clientFw?.value] || {};
|
|
1395
808
|
const backendConventions = FRAMEWORK_CONVENTIONS.backend[backendFwObj?.value] || {};
|
|
1396
809
|
|
|
1397
810
|
if (Object.keys(clientConventions).length) {
|
|
@@ -1400,7 +813,6 @@ If a dependency is not met:
|
|
|
1400
813
|
pathsMap.client[key] = { expected: value, current: null, status: 'pending' };
|
|
1401
814
|
});
|
|
1402
815
|
}
|
|
1403
|
-
|
|
1404
816
|
if (Object.keys(backendConventions).length) {
|
|
1405
817
|
pathsMap.backend = {};
|
|
1406
818
|
Object.entries(backendConventions).forEach(([key, value]) => {
|
|
@@ -1411,7 +823,7 @@ If a dependency is not met:
|
|
|
1411
823
|
fs.writeFileSync(path.join(RUNTIME_DIR, '.paths.json'), JSON.stringify(pathsMap, null, 2), 'utf8');
|
|
1412
824
|
console.log(` ${green('✓')} .paths.json generated`);
|
|
1413
825
|
|
|
1414
|
-
// ── Lock
|
|
826
|
+
// ── Lock ──────────────────────────────────────────────────────────────────────
|
|
1415
827
|
|
|
1416
828
|
fs.writeFileSync(LOCK_FILE, new Date().toISOString());
|
|
1417
829
|
console.log(` ${green('✓')} Initialization locked`);
|
|
@@ -1422,16 +834,15 @@ If a dependency is not met:
|
|
|
1422
834
|
execSync('git add .', { cwd: ROOT, stdio: 'pipe' });
|
|
1423
835
|
execSync('git commit -m "init: project configuration"', { cwd: ROOT, stdio: 'pipe' });
|
|
1424
836
|
console.log(` ${green('✓')} Project configuration committed`);
|
|
1425
|
-
} catch
|
|
837
|
+
} catch {
|
|
1426
838
|
console.log(` ${yellow('!')} Could not auto-commit. Run manually:`);
|
|
1427
839
|
console.log(dim(' git add . && git commit -m "init: project configuration"'));
|
|
1428
840
|
}
|
|
1429
841
|
|
|
1430
|
-
// ── Pre-commit hook
|
|
842
|
+
// ── Pre-commit hook ───────────────────────────────────────────────────────────
|
|
1431
843
|
|
|
1432
844
|
try {
|
|
1433
|
-
const
|
|
1434
|
-
const hookPath = path.join(hooksDir, 'pre-commit');
|
|
845
|
+
const hookPath = path.join(ROOT, '.git', 'hooks', 'pre-commit');
|
|
1435
846
|
const hookScript = `#!/bin/sh
|
|
1436
847
|
branch=$(git symbolic-ref --short HEAD 2>/dev/null)
|
|
1437
848
|
if [ "$branch" = "main" ]; then
|
|
@@ -1448,11 +859,11 @@ fi
|
|
|
1448
859
|
}
|
|
1449
860
|
} catch { /* best-effort */ }
|
|
1450
861
|
|
|
1451
|
-
// ── Remote setup
|
|
862
|
+
// ── Remote setup ──────────────────────────────────────────────────────────────
|
|
1452
863
|
|
|
1453
864
|
setupUserRemote(ROOT, projectName);
|
|
1454
865
|
|
|
1455
|
-
// ── Trajectory selection
|
|
866
|
+
// ── Trajectory selection ──────────────────────────────────────────────────────
|
|
1456
867
|
|
|
1457
868
|
separator();
|
|
1458
869
|
console.log(`\n${bold(green(' Project initialized successfully!'))}\n`);
|
|
@@ -1474,69 +885,12 @@ fi
|
|
|
1474
885
|
console.log(`${dim(' you for areas where requirements are still evolving')}`);
|
|
1475
886
|
console.log(`${yellow(' ⚠ If you and an agent touch the same file, expect merge conflicts')}\n`);
|
|
1476
887
|
|
|
1477
|
-
const TRAJECTORY_DETAILS = {
|
|
1478
|
-
'1': {
|
|
1479
|
-
label: 'Multi-Agent Driven Orchestration',
|
|
1480
|
-
full: [
|
|
1481
|
-
'Every task must start with npm run agent.',
|
|
1482
|
-
'Agent sessions load only task-relevant context, enabling reliable',
|
|
1483
|
-
'chaining, predictable behavior, and efficient token usage.',
|
|
1484
|
-
'',
|
|
1485
|
-
'⚠ If you commit directly to main yourself, you bypass the framework',
|
|
1486
|
-
' and break task tracking for any active agent branches.',
|
|
1487
|
-
'',
|
|
1488
|
-
'Benefits',
|
|
1489
|
-
'· Scoped context per task',
|
|
1490
|
-
'· Predictable token consumption',
|
|
1491
|
-
'· Lower cost than maintaining large, persistent sessions',
|
|
1492
|
-
'· Better isolation between parallel work streams',
|
|
1493
|
-
],
|
|
1494
|
-
next: 'launch',
|
|
1495
|
-
},
|
|
1496
|
-
'2': {
|
|
1497
|
-
label: 'Shared Orchestration',
|
|
1498
|
-
full: [
|
|
1499
|
-
'You and agents work in the same codebase, each with clearly',
|
|
1500
|
-
'defined ownership. File boundaries must be established before',
|
|
1501
|
-
'work begins and remain fixed throughout the task.',
|
|
1502
|
-
'Agents excel when scope is well-defined;',
|
|
1503
|
-
'you excel when requirements are evolving.',
|
|
1504
|
-
'',
|
|
1505
|
-
'Use agents for',
|
|
1506
|
-
'· Multi-file features',
|
|
1507
|
-
'· Structured implementation work',
|
|
1508
|
-
'· Domain-specific tasks',
|
|
1509
|
-
'· Changes expected to exceed ~200 lines',
|
|
1510
|
-
'',
|
|
1511
|
-
'Handle manually',
|
|
1512
|
-
'· Targeted bug fixes',
|
|
1513
|
-
'· Configuration changes',
|
|
1514
|
-
'· Small refactors',
|
|
1515
|
-
'· Single-file edits under ~50 lines',
|
|
1516
|
-
'',
|
|
1517
|
-
'⚠ Avoid overlapping file ownership. Working on the same files',
|
|
1518
|
-
' as an active agent will create merge conflicts when merged.',
|
|
1519
|
-
'⚠ If you are spending time repeatedly clarifying scope, stop',
|
|
1520
|
-
' and do the task yourself. The coordination cost often',
|
|
1521
|
-
' exceeds the implementation cost.',
|
|
1522
|
-
'',
|
|
1523
|
-
'Benefits',
|
|
1524
|
-
'· Maximum agent efficiency for well-defined work',
|
|
1525
|
-
'· Human flexibility where requirements change',
|
|
1526
|
-
'· Scales well across large projects',
|
|
1527
|
-
'· Most adaptable workflow — requires the most discipline',
|
|
1528
|
-
],
|
|
1529
|
-
next: 'launch',
|
|
1530
|
-
},
|
|
1531
|
-
};
|
|
1532
|
-
|
|
1533
|
-
// Wrap in loop to support back navigation
|
|
1534
888
|
let trajectory = null;
|
|
1535
889
|
trajectoryLoop: while (true) {
|
|
1536
890
|
const trajIdx = await arrowSelect('How do you want to build?', [
|
|
1537
891
|
{ label: bold('Multi-Agent Driven Orchestration') },
|
|
1538
892
|
{ label: bold('Shared Orchestration') },
|
|
1539
|
-
]
|
|
893
|
+
]);
|
|
1540
894
|
trajectory = String(trajIdx + 1);
|
|
1541
895
|
|
|
1542
896
|
const selected = TRAJECTORY_DETAILS[trajectory];
|
|
@@ -1545,11 +899,11 @@ fi
|
|
|
1545
899
|
renderTrajectoryLines(selected.full);
|
|
1546
900
|
console.log('');
|
|
1547
901
|
|
|
1548
|
-
const
|
|
902
|
+
const confirmIdx2 = await arrowSelect('Confirm?', [
|
|
1549
903
|
{ label: `${green('✓')} Confirm` },
|
|
1550
904
|
{ label: `${yellow('←')} Back — pick differently` },
|
|
1551
|
-
]
|
|
1552
|
-
if (
|
|
905
|
+
]);
|
|
906
|
+
if (confirmIdx2 === 0) break trajectoryLoop;
|
|
1553
907
|
trajectory = null;
|
|
1554
908
|
separator();
|
|
1555
909
|
console.log(`\n ${bold('How do you want to build?')}\n`);
|
|
@@ -1571,7 +925,6 @@ fi
|
|
|
1571
925
|
|
|
1572
926
|
const selected = TRAJECTORY_DETAILS[trajectory];
|
|
1573
927
|
|
|
1574
|
-
// Store trajectory in config
|
|
1575
928
|
try {
|
|
1576
929
|
const cfg = JSON.parse(fs.readFileSync(path.join(RUNTIME_DIR, '.config.json'), 'utf8'));
|
|
1577
930
|
cfg.trajectory = selected.label.toLowerCase().replace(/ /g, '-');
|
|
@@ -1579,49 +932,7 @@ fi
|
|
|
1579
932
|
} catch { /* best-effort */ }
|
|
1580
933
|
|
|
1581
934
|
if (selected.next === 'launch') {
|
|
1582
|
-
|
|
1583
|
-
console.log(`\n${bold(green(' Project initialized successfully!'))}\n`);
|
|
1584
|
-
|
|
1585
|
-
// ── Summary block ─────────────────────────────────────────────────────────
|
|
1586
|
-
const bt = config.backend?.type;
|
|
1587
|
-
|
|
1588
|
-
console.log(` ${dim('Project')} : ${bold(projectName)}`);
|
|
1589
|
-
console.log(` ${dim('Client')} : ${config.client.framework} / ${config.client.language}${config.client.uiLibrary ? ' / ' + config.client.uiLibrary : ''}`);
|
|
1590
|
-
if (bt === 'separate') {
|
|
1591
|
-
console.log(` ${dim('Backend')} : ${config.backend.framework} / ${config.backend.language}${config.backend.orm ? ' / ' + config.backend.orm : ''}`);
|
|
1592
|
-
} else {
|
|
1593
|
-
console.log(` ${dim('Backend')} : integrated (API routes / SSR)`);
|
|
1594
|
-
}
|
|
1595
|
-
console.log(` ${dim('Workflow')} : ${selected.label}\n`);
|
|
1596
|
-
|
|
1597
|
-
console.log(` ${dim('Files generated:')}`);
|
|
1598
|
-
console.log(` ${green('+')} CLAUDE.md, client/CLAUDE.md${bt === 'separate' ? ', backend/CLAUDE.md' : ''}`);
|
|
1599
|
-
console.log(` ${green('+')} BUILD_STATE.md, TASKS_HISTORY.md, CONTRACTS.md`);
|
|
1600
|
-
console.log(` ${green('+')} CLOUD_STATE.md, shared/wiring.config.json`);
|
|
1601
|
-
console.log(` ${green('+')} .scaffold/.config.json, .scaffold/scope-policy.json`);
|
|
1602
|
-
console.log(` ${green('+')} .agents/, .frameworks/, .workflow/\n`);
|
|
1603
|
-
|
|
1604
|
-
console.log(` ${dim('Git:')}`);
|
|
1605
|
-
console.log(` ${green('+')} Repository initialized on main`);
|
|
1606
|
-
console.log(` ${green('+')} Pre-commit hook installed (direct main commits blocked)`);
|
|
1607
|
-
console.log(` ${green('+')} Initial commit created\n`);
|
|
1608
|
-
|
|
1609
|
-
console.log(` ${dim('Agents available:')}`);
|
|
1610
|
-
console.log(` ${dim('client')} : UI, LOGIC, FORMS, ROUTING, ACCESSIBILITY, TESTING`);
|
|
1611
|
-
if (bt === 'separate') {
|
|
1612
|
-
console.log(` ${dim('backend')} : INIT, API, AUTH, DB, LOGIC, EVENTS, JOBS, TESTING`);
|
|
1613
|
-
}
|
|
1614
|
-
console.log(` ${dim('shared')} : CLOUD, SECURITY\n`);
|
|
1615
|
-
|
|
1616
|
-
console.log(` ${dim('Starting your first agent session...\n')}`);
|
|
1617
|
-
separator();
|
|
1618
|
-
console.log('');
|
|
1619
|
-
rl.close();
|
|
1620
|
-
const { spawn: sp } = require('child_process');
|
|
1621
|
-
const agentProc = sp('npm', ['run', 'agent'], { cwd: ROOT, stdio: 'inherit' });
|
|
1622
|
-
agentProc.on('error', (err) => {
|
|
1623
|
-
console.error(' Could not start agent:', err.message);
|
|
1624
|
-
});
|
|
935
|
+
printInitSummary({ projectName, config, selectedLabel: selected.label, ROOT, rl });
|
|
1625
936
|
return;
|
|
1626
937
|
}
|
|
1627
938
|
|
|
@@ -1636,4 +947,4 @@ fi
|
|
|
1636
947
|
main().catch((err) => {
|
|
1637
948
|
console.error('\n Error:', err.message);
|
|
1638
949
|
process.exit(1);
|
|
1639
|
-
});
|
|
950
|
+
});
|