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/lib/detect.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
// ── Colors (inline — avoid circular dep with ui.js) ──────────────────────────
|
|
9
|
+
|
|
10
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
11
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
12
|
+
|
|
13
|
+
// ── IDE detection ─────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
const expandWinPath = (p) =>
|
|
16
|
+
p.replace('{LOCALAPPDATA}', process.env.LOCALAPPDATA || '')
|
|
17
|
+
.replace('{ProgramFiles}', process.env.ProgramFiles || 'C:\\Program Files');
|
|
18
|
+
|
|
19
|
+
const buildIDEOptions = (IDE_CANDIDATES) => {
|
|
20
|
+
const platform = process.platform;
|
|
21
|
+
|
|
22
|
+
return IDE_CANDIDATES.map(ide => {
|
|
23
|
+
if (!ide.cmd) {
|
|
24
|
+
const noteStr = ide.note ? dim(` (${ide.note})`) : '';
|
|
25
|
+
return { ...ide, detected: false, strategy: 'manual', label: `${ide.name} ${dim('→')}${noteStr}` };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let detected = false;
|
|
29
|
+
let strategy = 'cli';
|
|
30
|
+
|
|
31
|
+
if (platform === 'darwin' && ide.mac) {
|
|
32
|
+
const system = `/Applications/${ide.mac.app}.app`;
|
|
33
|
+
const user = path.join(os.homedir(), 'Applications', `${ide.mac.app}.app`);
|
|
34
|
+
const toolbox = path.join(os.homedir(), 'Applications', 'JetBrains Toolbox', `${ide.mac.app}.app`);
|
|
35
|
+
detected = fs.existsSync(system) || fs.existsSync(user) || fs.existsSync(toolbox);
|
|
36
|
+
if (detected) strategy = 'mac-app';
|
|
37
|
+
|
|
38
|
+
} else if (platform === 'win32' && ide.win) {
|
|
39
|
+
try {
|
|
40
|
+
execSync(`where ${ide.cmd}`, { stdio: 'pipe' });
|
|
41
|
+
detected = true;
|
|
42
|
+
strategy = 'cli';
|
|
43
|
+
} catch {
|
|
44
|
+
const expanded = (ide.win.paths || []).map(expandWinPath);
|
|
45
|
+
detected = expanded.some(p => fs.existsSync(p));
|
|
46
|
+
if (detected) strategy = 'win-exe';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
} else if (platform === 'linux' && ide.linux) {
|
|
50
|
+
try {
|
|
51
|
+
execSync(`which ${ide.cmd}`, { stdio: 'pipe' });
|
|
52
|
+
detected = true;
|
|
53
|
+
strategy = 'cli';
|
|
54
|
+
} catch {
|
|
55
|
+
detected = (ide.linux.paths || []).some(p => fs.existsSync(p));
|
|
56
|
+
if (detected) strategy = 'linux-path';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const statusStr = detected ? green('✓ detected') : dim('✗ not found');
|
|
61
|
+
const noteStr = ide.note ? dim(` (${ide.note})`) : '';
|
|
62
|
+
return { ...ide, detected, strategy, label: `${ide.name} ${statusStr}${noteStr}` };
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const verifyIDE = (ide) => {
|
|
67
|
+
const platform = process.platform;
|
|
68
|
+
|
|
69
|
+
if (ide.strategy === 'mac-app' && ide.mac) {
|
|
70
|
+
const appPath = `/Applications/${ide.mac.app}.app`;
|
|
71
|
+
const userPath = path.join(os.homedir(), 'Applications', `${ide.mac.app}.app`);
|
|
72
|
+
if (!fs.existsSync(appPath) && !fs.existsSync(userPath)) return { ok: false };
|
|
73
|
+
try {
|
|
74
|
+
const version = execSync(
|
|
75
|
+
`defaults read "/Applications/${ide.mac.app}.app/Contents/Info.plist" CFBundleShortVersionString`,
|
|
76
|
+
{ stdio: 'pipe', encoding: 'utf8' }
|
|
77
|
+
).trim();
|
|
78
|
+
return { ok: true, version };
|
|
79
|
+
} catch {
|
|
80
|
+
return { ok: true, version: null };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const cmd = ide.strategy === 'win-exe'
|
|
86
|
+
? `"${(ide.win?.paths || []).map(expandWinPath).find(p => fs.existsSync(p))}"`
|
|
87
|
+
: ide.strategy === 'linux-path'
|
|
88
|
+
? `"${(ide.linux?.paths || []).find(p => fs.existsSync(p))}"`
|
|
89
|
+
: `"${ide.cmd}"`;
|
|
90
|
+
const result = execSync(`${cmd} --version`, { stdio: 'pipe', encoding: 'utf8' });
|
|
91
|
+
const version = result.split('\n')[0].trim();
|
|
92
|
+
return { ok: true, version };
|
|
93
|
+
} catch {
|
|
94
|
+
return { ok: false };
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// ── Terminal detection ────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
const detectTerminal = () => {
|
|
101
|
+
const platform = process.platform;
|
|
102
|
+
|
|
103
|
+
if (platform === 'darwin') {
|
|
104
|
+
const apps = [
|
|
105
|
+
{ name: 'iTerm2', cmd: 'iTerm2', path: '/Applications/iTerm.app' },
|
|
106
|
+
{ name: 'Warp', cmd: 'Warp', path: '/Applications/Warp.app' },
|
|
107
|
+
{ name: 'Terminal.app', cmd: 'Terminal', path: '/System/Applications/Utilities/Terminal.app' },
|
|
108
|
+
];
|
|
109
|
+
return apps.find(a => fs.existsSync(a.path)) || { name: 'Terminal.app', cmd: 'Terminal' };
|
|
110
|
+
|
|
111
|
+
} else if (platform === 'win32') {
|
|
112
|
+
const wtPath = (process.env.LOCALAPPDATA || '') + '\\Microsoft\\WindowsApps\\wt.exe';
|
|
113
|
+
if (fs.existsSync(wtPath)) return { name: 'Windows Terminal', cmd: 'wt' };
|
|
114
|
+
return { name: 'Command Prompt', cmd: 'cmd' };
|
|
115
|
+
|
|
116
|
+
} else {
|
|
117
|
+
const terms = ['gnome-terminal', 'konsole', 'xterm'];
|
|
118
|
+
for (const t of terms) {
|
|
119
|
+
try { execSync('which ' + t, { stdio: 'pipe' }); return { name: t, cmd: t }; } catch {}
|
|
120
|
+
}
|
|
121
|
+
return { name: 'xterm', cmd: 'xterm' };
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// ── Exports ───────────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
expandWinPath,
|
|
129
|
+
buildIDEOptions,
|
|
130
|
+
verifyIDE,
|
|
131
|
+
detectTerminal,
|
|
132
|
+
};
|
package/lib/questions.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
// ── Framework conventions ─────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
const FRAMEWORK_CONVENTIONS = {
|
|
8
|
+
client: {
|
|
9
|
+
'Next.js': { root: 'client', typesDir: 'client/src/types', importAlias: '@/types' },
|
|
10
|
+
'Angular': { root: 'client', typesDir: 'client/src/app/core/types', importAlias: null },
|
|
11
|
+
'Nuxt': { root: 'client', typesDir: 'client/types', importAlias: '~/types' },
|
|
12
|
+
'SvelteKit': { root: 'client', typesDir: 'client/src/lib/types', importAlias: '$lib/types' },
|
|
13
|
+
'Vite+React': { root: 'client', typesDir: 'client/src/types', importAlias: null },
|
|
14
|
+
'Remix': { root: 'client', typesDir: 'client/app/types', importAlias: null },
|
|
15
|
+
},
|
|
16
|
+
backend: {
|
|
17
|
+
'Express': { root: 'backend', typesDir: 'backend/src/types', routesDir: 'backend/src/routes' },
|
|
18
|
+
'NestJS': { root: 'backend', dtoDir: 'backend/src/dto', entitiesDir:'backend/src/entities' },
|
|
19
|
+
'Fastify': { root: 'backend', typesDir: 'backend/src/types', routesDir: 'backend/src/routes' },
|
|
20
|
+
'FastAPI': { root: 'backend', schemasDir: 'backend/app/schemas', modelsDir: 'backend/app/models' },
|
|
21
|
+
'Django': { root: 'backend', schemasDir: 'backend/api/serializers', modelsDir: 'backend/api/models' },
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ── Client frameworks ─────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
const CLIENT_FRAMEWORKS = [
|
|
28
|
+
{ label: 'Next.js', value: 'Next.js', language: 'TypeScript', integratedBackend: true },
|
|
29
|
+
{ label: 'Angular', value: 'Angular', language: 'TypeScript', integratedBackend: false },
|
|
30
|
+
{ label: 'Vue / Nuxt', value: 'Nuxt', language: 'TypeScript', integratedBackend: true },
|
|
31
|
+
{ label: 'SvelteKit', value: 'SvelteKit', language: 'TypeScript', integratedBackend: true },
|
|
32
|
+
{ label: 'Remix', value: 'Remix', language: 'TypeScript', integratedBackend: true },
|
|
33
|
+
{ label: 'Vite + React', value: 'Vite+React', language: 'TypeScript', integratedBackend: false },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// ── Backend frameworks ────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
const BACKEND_FRAMEWORKS = [
|
|
39
|
+
{ label: 'NestJS', value: 'NestJS', language: 'TypeScript' },
|
|
40
|
+
{ label: 'Express', value: 'Express', language: 'TypeScript' },
|
|
41
|
+
{ label: 'Fastify', value: 'Fastify', language: 'TypeScript' },
|
|
42
|
+
{ label: 'Django', value: 'Django', language: 'Python' },
|
|
43
|
+
{ label: 'FastAPI', value: 'FastAPI', language: 'Python' },
|
|
44
|
+
{ label: 'Laravel', value: 'Laravel', language: 'PHP' },
|
|
45
|
+
{ label: 'Rails', value: 'Rails', language: 'Ruby' },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// ── Framework version registry ────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
const FRAMEWORK_REGISTRY = {
|
|
51
|
+
'Next.js': { registry: 'npm', package: 'next' },
|
|
52
|
+
'Angular': { registry: 'npm', package: '@angular/core' },
|
|
53
|
+
'Nuxt': { registry: 'npm', package: 'nuxt' },
|
|
54
|
+
'SvelteKit': { registry: 'npm', package: '@sveltejs/kit' },
|
|
55
|
+
'Remix': { registry: 'npm', package: '@remix-run/react' },
|
|
56
|
+
'Vite+React': { registry: 'npm', package: 'vite' },
|
|
57
|
+
'NestJS': { registry: 'npm', package: '@nestjs/core' },
|
|
58
|
+
'Express': { registry: 'npm', package: 'express' },
|
|
59
|
+
'Fastify': { registry: 'npm', package: 'fastify' },
|
|
60
|
+
'FastAPI': { registry: 'pypi', package: 'fastapi' },
|
|
61
|
+
'Django': { registry: 'pypi', package: 'django' },
|
|
62
|
+
'Laravel': { registry: 'npm', package: null },
|
|
63
|
+
'Rails': { registry: 'npm', package: null },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const FRAMEWORK_VERSION_FALLBACK = {
|
|
67
|
+
'Next.js': ['15', '14', '13'],
|
|
68
|
+
'Angular': ['22', '21', '20'],
|
|
69
|
+
'Nuxt': ['3', '2', null],
|
|
70
|
+
'SvelteKit': ['2', '1', null],
|
|
71
|
+
'Remix': ['2', '1', null],
|
|
72
|
+
'Vite+React': ['8', '7', '6'],
|
|
73
|
+
'NestJS': ['11', '10', '9' ],
|
|
74
|
+
'Express': ['5', '4'],
|
|
75
|
+
'Fastify': ['5', '4', null],
|
|
76
|
+
'FastAPI': ['0.115', '0.111', '0.104'],
|
|
77
|
+
'Django': ['5.1', '4.2', '3.2'],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const fetchLatestVersions = async (frameworkValue) => {
|
|
81
|
+
const entry = FRAMEWORK_REGISTRY[frameworkValue];
|
|
82
|
+
if (!entry || !entry.package) return null;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const https = require('https');
|
|
86
|
+
const fetch = (url) => new Promise((resolve, reject) => {
|
|
87
|
+
const req = https.get(url, { timeout: 3000 }, (res) => {
|
|
88
|
+
let data = '';
|
|
89
|
+
res.on('data', chunk => data += chunk);
|
|
90
|
+
res.on('end', () => resolve(data));
|
|
91
|
+
});
|
|
92
|
+
req.on('error', reject);
|
|
93
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (entry.registry === 'npm') {
|
|
97
|
+
const raw = await fetch(`https://registry.npmjs.org/${entry.package}`);
|
|
98
|
+
const json = JSON.parse(raw);
|
|
99
|
+
const versions = Object.keys(json.versions || {})
|
|
100
|
+
.filter(v => /^\d+\.\d+\.\d+$/.test(v) && !v.includes('-'))
|
|
101
|
+
.map(v => parseInt(v.split('.')[0]))
|
|
102
|
+
.filter((v, i, arr) => arr.indexOf(v) === i)
|
|
103
|
+
.sort((a, b) => b - a)
|
|
104
|
+
.slice(0, 3);
|
|
105
|
+
return versions.length ? versions.map(String) : null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (entry.registry === 'pypi') {
|
|
109
|
+
const raw = await fetch(`https://pypi.org/pypi/${entry.package}/json`);
|
|
110
|
+
const json = JSON.parse(raw);
|
|
111
|
+
const versions = Object.keys(json.releases || {})
|
|
112
|
+
.filter(v => /^\d+\.\d+(\.\d+)?$/.test(v))
|
|
113
|
+
.sort((a, b) => {
|
|
114
|
+
const [aMaj, aMin = 0] = a.split('.').map(Number);
|
|
115
|
+
const [bMaj, bMin = 0] = b.split('.').map(Number);
|
|
116
|
+
return bMaj !== aMaj ? bMaj - aMaj : bMin - aMin;
|
|
117
|
+
})
|
|
118
|
+
.map(v => v.split('.').slice(0, 2).join('.'))
|
|
119
|
+
.filter((v, i, arr) => arr.indexOf(v) === i)
|
|
120
|
+
.slice(0, 3);
|
|
121
|
+
return versions.length ? versions : null;
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// ── Option maps ───────────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
const STATE_OPTIONS = {
|
|
132
|
+
'Next.js': ['Zustand', 'Redux Toolkit', 'Jotai', 'TanStack Query'],
|
|
133
|
+
'Vite+React': ['Zustand', 'Redux Toolkit', 'Jotai', 'TanStack Query'],
|
|
134
|
+
'Remix': ['Zustand', 'Redux Toolkit', 'Jotai', 'TanStack Query'],
|
|
135
|
+
'Angular': ['NgRx', 'Signals (built-in)', 'Akita'],
|
|
136
|
+
'Nuxt': ['Pinia', 'Vuex'],
|
|
137
|
+
'SvelteKit': ['Svelte stores (built-in)', 'Zustand'],
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const UI_OPTIONS = {
|
|
141
|
+
'Next.js': ['shadcn/ui', 'Radix UI', 'MUI', 'Chakra UI', 'Ant Design'],
|
|
142
|
+
'Vite+React': ['Radix UI', 'MUI', 'Chakra UI', 'Ant Design'],
|
|
143
|
+
'Remix': ['shadcn/ui', 'Radix UI', 'MUI', 'Chakra UI', 'Ant Design'],
|
|
144
|
+
'Angular': ['Angular Material', 'PrimeNG', 'Clarity'],
|
|
145
|
+
'Nuxt': ['Vuetify', 'PrimeVue', 'Naive UI'],
|
|
146
|
+
'SvelteKit': ['Skeleton', 'daisyUI', 'shadcn-svelte'],
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const STYLING_OPTIONS = [
|
|
150
|
+
'Tailwind CSS',
|
|
151
|
+
'CSS Modules',
|
|
152
|
+
'Styled Components',
|
|
153
|
+
'SCSS / SASS',
|
|
154
|
+
'UnoCSS',
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
const DB_OPTIONS = ['PostgreSQL', 'MySQL', 'MongoDB', 'SQLite', 'Skip (agent will propose when needed)'];
|
|
158
|
+
|
|
159
|
+
const ORM_OPTIONS_BY_DB = {
|
|
160
|
+
'PostgreSQL': ['Prisma', 'TypeORM', 'Drizzle', 'Sequelize', 'raw pg driver', 'Skip (agent will propose when needed)'],
|
|
161
|
+
'MySQL': ['Prisma', 'TypeORM', 'Drizzle', 'Sequelize', 'raw mysql2 driver', 'Skip (agent will propose when needed)'],
|
|
162
|
+
'MongoDB': ['Mongoose', 'Prisma', 'raw MongoDB driver', 'Skip (agent will propose when needed)'],
|
|
163
|
+
'SQLite': ['Prisma', 'Drizzle', 'better-sqlite3', 'Skip (agent will propose when needed)'],
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const ORM_OPTIONS = {
|
|
167
|
+
'NestJS': ['TypeORM', 'Prisma', 'MikroORM', 'Drizzle'],
|
|
168
|
+
'Express': ['Prisma', 'TypeORM', 'Drizzle', 'Sequelize'],
|
|
169
|
+
'Fastify': ['Prisma', 'TypeORM', 'Drizzle'],
|
|
170
|
+
'Django': ['Django ORM (built-in)', 'SQLAlchemy'],
|
|
171
|
+
'FastAPI': ['SQLAlchemy', 'Tortoise ORM', 'Beanie (MongoDB)'],
|
|
172
|
+
'Laravel': ['Eloquent (built-in)'],
|
|
173
|
+
'Rails': ['Active Record (built-in)'],
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const AUTH_OPTIONS = {
|
|
177
|
+
'NestJS': ['Passport.js', 'JWT-only', 'OAuth2', 'Auth.js'],
|
|
178
|
+
'Express': ['Passport.js', 'JWT-only', 'OAuth2'],
|
|
179
|
+
'Fastify': ['fastify-jwt', 'Passport.js', 'OAuth2'],
|
|
180
|
+
'Django': ['Django Auth (built-in)', 'DRF TokenAuth', 'OAuth2'],
|
|
181
|
+
'FastAPI': ['JWT-only', 'OAuth2', 'FastAPI-Users'],
|
|
182
|
+
'Laravel': ['Laravel Sanctum', 'Laravel Passport', 'JWT'],
|
|
183
|
+
'Rails': ['Devise', 'JWT', 'OAuth2'],
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// ── IDE candidates ────────────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
const IDE_CANDIDATES = [
|
|
189
|
+
{
|
|
190
|
+
cmd: 'code',
|
|
191
|
+
name: 'VS Code',
|
|
192
|
+
mac: { app: 'Visual Studio Code', args: ['--new-window'] },
|
|
193
|
+
win: { paths: ['{LOCALAPPDATA}\\Programs\\Microsoft VS Code\\Code.exe', '{ProgramFiles}\\Microsoft VS Code\\Code.exe'], args: ['--new-window'] },
|
|
194
|
+
linux: { paths: ['/snap/bin/code', '/usr/bin/code', '/usr/local/bin/code'], args: ['--new-window'] },
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
cmd: 'cursor',
|
|
198
|
+
name: 'Cursor',
|
|
199
|
+
mac: { app: 'Cursor', args: ['--new-window'] },
|
|
200
|
+
win: { paths: ['{LOCALAPPDATA}\\Programs\\cursor\\Cursor.exe'], args: ['--new-window'] },
|
|
201
|
+
linux: { paths: ['/usr/bin/cursor', '/opt/cursor/cursor'], args: ['--new-window'] },
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
cmd: 'webstorm',
|
|
205
|
+
name: 'WebStorm',
|
|
206
|
+
mac: { app: 'WebStorm', toolboxApp: 'WebStorm', args: [] },
|
|
207
|
+
win: { paths: [
|
|
208
|
+
'{LOCALAPPDATA}\\JetBrains\\Toolbox\\scripts\\webstorm.cmd',
|
|
209
|
+
'{LOCALAPPDATA}\\Programs\\WebStorm\\bin\\webstorm64.exe',
|
|
210
|
+
], args: [] },
|
|
211
|
+
linux: { paths: [
|
|
212
|
+
`${os.homedir()}/.local/bin/webstorm`,
|
|
213
|
+
'/opt/webstorm/bin/webstorm.sh',
|
|
214
|
+
'/snap/webstorm/current/bin/webstorm.sh',
|
|
215
|
+
], args: [] },
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
cmd: 'idea',
|
|
219
|
+
name: 'IntelliJ IDEA',
|
|
220
|
+
mac: { app: 'IntelliJ IDEA', toolboxApp: 'IntelliJ IDEA', args: [] },
|
|
221
|
+
win: { paths: [
|
|
222
|
+
'{LOCALAPPDATA}\\JetBrains\\Toolbox\\scripts\\idea.cmd',
|
|
223
|
+
'{LOCALAPPDATA}\\Programs\\IntelliJ IDEA Community Edition\\bin\\idea64.exe',
|
|
224
|
+
'{ProgramFiles}\\JetBrains\\IntelliJ IDEA\\bin\\idea64.exe',
|
|
225
|
+
], args: [] },
|
|
226
|
+
linux: { paths: [
|
|
227
|
+
`${os.homedir()}/.local/bin/idea`,
|
|
228
|
+
'/opt/idea/bin/idea.sh',
|
|
229
|
+
'/snap/intellij-idea-community/current/bin/idea.sh',
|
|
230
|
+
], args: [] },
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
cmd: 'zed',
|
|
234
|
+
name: 'Zed',
|
|
235
|
+
mac: { app: 'Zed', args: [] },
|
|
236
|
+
win: { paths: [], args: [] },
|
|
237
|
+
linux: { paths: ['/usr/bin/zed', `${os.homedir()}/.local/bin/zed`], args: [] },
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
cmd: null,
|
|
241
|
+
name: 'Other / Manual',
|
|
242
|
+
note: 'prints worktree path, open it yourself',
|
|
243
|
+
mac: null,
|
|
244
|
+
win: null,
|
|
245
|
+
linux: null,
|
|
246
|
+
},
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
// ── Exports ───────────────────────────────────────────────────────────────────
|
|
250
|
+
|
|
251
|
+
module.exports = {
|
|
252
|
+
FRAMEWORK_CONVENTIONS,
|
|
253
|
+
CLIENT_FRAMEWORKS,
|
|
254
|
+
BACKEND_FRAMEWORKS,
|
|
255
|
+
FRAMEWORK_REGISTRY,
|
|
256
|
+
FRAMEWORK_VERSION_FALLBACK,
|
|
257
|
+
fetchLatestVersions,
|
|
258
|
+
STATE_OPTIONS,
|
|
259
|
+
UI_OPTIONS,
|
|
260
|
+
STYLING_OPTIONS,
|
|
261
|
+
DB_OPTIONS,
|
|
262
|
+
ORM_OPTIONS_BY_DB,
|
|
263
|
+
ORM_OPTIONS,
|
|
264
|
+
AUTH_OPTIONS,
|
|
265
|
+
IDE_CANDIDATES,
|
|
266
|
+
};
|
package/lib/steps.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ── Sentinel values ───────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
const BACK = Symbol('BACK');
|
|
6
|
+
const CONTINUE = Symbol('CONTINUE');
|
|
7
|
+
const RESTART = Symbol('RESTART');
|
|
8
|
+
|
|
9
|
+
// ── Step machine ──────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
class StepMachine {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.history = []; // [{ stepIndex, stepName, answer }]
|
|
14
|
+
this.inBackNav = false;
|
|
15
|
+
this.resumeIndex = null; // step index where back-nav started
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Push a completed step answer onto the history stack
|
|
19
|
+
push(stepIndex, stepName, answer) {
|
|
20
|
+
// Replace if already exists (user went back and re-answered)
|
|
21
|
+
const existing = this.history.findIndex(h => h.stepIndex === stepIndex);
|
|
22
|
+
if (existing > -1) {
|
|
23
|
+
this.history[existing] = { stepIndex, stepName, answer };
|
|
24
|
+
} else {
|
|
25
|
+
this.history.push({ stepIndex, stepName, answer });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Pop the last step from history (go back)
|
|
30
|
+
pop() {
|
|
31
|
+
return this.history.pop() || null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Get the answer for a given step index (for pre-filling on back-nav)
|
|
35
|
+
getAnswer(stepIndex) {
|
|
36
|
+
const entry = this.history.find(h => h.stepIndex === stepIndex);
|
|
37
|
+
return entry ? entry.answer : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Current step count (1-based, for display)
|
|
41
|
+
get stepCount() {
|
|
42
|
+
return this.history.length + 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Build the navigation options to append to a step's choices
|
|
46
|
+
// stepIndex: current step (1-based)
|
|
47
|
+
// isFirstStep: true if this is step 2 (first real choice after project name)
|
|
48
|
+
navOptions(stepIndex, isFirstStep = false) {
|
|
49
|
+
const opts = [];
|
|
50
|
+
|
|
51
|
+
if (!isFirstStep) {
|
|
52
|
+
opts.push({ label: '← Back to previous step', value: BACK });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (this.inBackNav) {
|
|
56
|
+
const resumeName = this.history[this.resumeIndex]?.stepName || `step ${this.resumeIndex + 1}`;
|
|
57
|
+
opts.push({ label: `→ Continue flow (resume from ${resumeName} - no changes applied)`, value: CONTINUE });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
opts.push({ label: '← Restart configuration', value: RESTART });
|
|
61
|
+
|
|
62
|
+
return opts;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Enter back-nav mode — record where the user first pressed Back
|
|
66
|
+
enterBackNav(currentStepIndex) {
|
|
67
|
+
if (!this.inBackNav) {
|
|
68
|
+
this.inBackNav = true;
|
|
69
|
+
this.resumeIndex = currentStepIndex;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Exit back-nav mode
|
|
74
|
+
exitBackNav() {
|
|
75
|
+
this.inBackNav = false;
|
|
76
|
+
this.resumeIndex = null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Reset everything
|
|
80
|
+
reset() {
|
|
81
|
+
this.history = [];
|
|
82
|
+
this.inBackNav = false;
|
|
83
|
+
this.resumeIndex = null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Exports ───────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
module.exports = { StepMachine, BACK, CONTINUE, RESTART };
|
package/lib/summary.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
|
|
7
|
+
// ── Colors (inline — avoid circular dep) ─────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
10
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
11
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
12
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
13
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
14
|
+
const separator = () => console.log(`\n\x1b[2m${'─'.repeat(60)}\x1b[0m`);
|
|
15
|
+
|
|
16
|
+
// ── Trajectory details ────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const TRAJECTORY_DETAILS = {
|
|
19
|
+
'1': {
|
|
20
|
+
label: 'Multi-Agent Driven Orchestration',
|
|
21
|
+
full: [
|
|
22
|
+
'Every task must start with npm run agent.',
|
|
23
|
+
'Agent sessions load only task-relevant context, enabling reliable',
|
|
24
|
+
'chaining, predictable behavior, and efficient token usage.',
|
|
25
|
+
'',
|
|
26
|
+
'⚠ If you commit directly to main yourself, you bypass the framework',
|
|
27
|
+
' and break task tracking for any active agent branches.',
|
|
28
|
+
'',
|
|
29
|
+
'Benefits',
|
|
30
|
+
'· Scoped context per task',
|
|
31
|
+
'· Predictable token consumption',
|
|
32
|
+
'· Lower cost than maintaining large, persistent sessions',
|
|
33
|
+
'· Better isolation between parallel work streams',
|
|
34
|
+
],
|
|
35
|
+
next: 'launch',
|
|
36
|
+
},
|
|
37
|
+
'2': {
|
|
38
|
+
label: 'Shared Orchestration',
|
|
39
|
+
full: [
|
|
40
|
+
'You and agents work in the same codebase, each with clearly',
|
|
41
|
+
'defined ownership. File boundaries must be established before',
|
|
42
|
+
'work begins and remain fixed throughout the task.',
|
|
43
|
+
'Agents excel when scope is well-defined;',
|
|
44
|
+
'you excel when requirements are evolving.',
|
|
45
|
+
'',
|
|
46
|
+
'Use agents for',
|
|
47
|
+
'· Multi-file features',
|
|
48
|
+
'· Structured implementation work',
|
|
49
|
+
'· Domain-specific tasks',
|
|
50
|
+
'· Changes expected to exceed ~200 lines',
|
|
51
|
+
'',
|
|
52
|
+
'Handle manually',
|
|
53
|
+
'· Targeted bug fixes',
|
|
54
|
+
'· Configuration changes',
|
|
55
|
+
'· Small refactors',
|
|
56
|
+
'· Single-file edits under ~50 lines',
|
|
57
|
+
'',
|
|
58
|
+
'⚠ Avoid overlapping file ownership. Working on the same files',
|
|
59
|
+
' as an active agent will create merge conflicts when merged.',
|
|
60
|
+
'⚠ If you are spending time repeatedly clarifying scope, stop',
|
|
61
|
+
' and do the task yourself. The coordination cost often',
|
|
62
|
+
' exceeds the implementation cost.',
|
|
63
|
+
'',
|
|
64
|
+
'Benefits',
|
|
65
|
+
'· Maximum agent efficiency for well-defined work',
|
|
66
|
+
'· Human flexibility where requirements change',
|
|
67
|
+
'· Scales well across large projects',
|
|
68
|
+
'· Most adaptable workflow — requires the most discipline',
|
|
69
|
+
],
|
|
70
|
+
next: 'launch',
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// ── Render trajectory lines ───────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
const renderTrajectoryLines = (lines) => {
|
|
77
|
+
const HEADERS = ['Benefits', 'Best for', 'Use agents for', 'Handle manually'];
|
|
78
|
+
lines.forEach(l => {
|
|
79
|
+
if (!l) { console.log(''); return; }
|
|
80
|
+
if (l.startsWith('⚠')) console.log(` ${yellow(l)}`);
|
|
81
|
+
else if (HEADERS.includes(l)) console.log(`\n ${bold(l)}`);
|
|
82
|
+
else if (l.startsWith('·')) console.log(` ${l}`);
|
|
83
|
+
else console.log(` ${dim(l)}`);
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// ── Init summary output ───────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
const printInitSummary = ({ projectName, config, selectedLabel, ROOT, rl }) => {
|
|
90
|
+
const bt = config.backend?.type;
|
|
91
|
+
|
|
92
|
+
separator();
|
|
93
|
+
console.log(`\n${bold(green(' Project initialized successfully!'))}\n`);
|
|
94
|
+
|
|
95
|
+
console.log(` ${dim('Project')} : ${bold(projectName)}`);
|
|
96
|
+
console.log(` ${dim('Client')} : ${config.client.framework} / ${config.client.language}${config.client.uiLibrary ? ' / ' + config.client.uiLibrary : ''}`);
|
|
97
|
+
if (bt === 'separate') {
|
|
98
|
+
console.log(` ${dim('Backend')} : ${config.backend.framework} / ${config.backend.language}${config.backend.orm ? ' / ' + config.backend.orm : ''}`);
|
|
99
|
+
} else {
|
|
100
|
+
console.log(` ${dim('Backend')} : integrated (API routes / SSR)`);
|
|
101
|
+
}
|
|
102
|
+
console.log(` ${dim('Workflow')} : ${selectedLabel}\n`);
|
|
103
|
+
|
|
104
|
+
console.log(` ${dim('Files generated:')}`);
|
|
105
|
+
console.log(` ${green('+')} CLAUDE.md, client/CLAUDE.md${bt === 'separate' ? ', backend/CLAUDE.md' : ''}`);
|
|
106
|
+
console.log(` ${green('+')} BUILD_STATE.md, TASKS_HISTORY.md, CONTRACTS.md`);
|
|
107
|
+
console.log(` ${green('+')} CLOUD_STATE.md, shared/wiring.config.json`);
|
|
108
|
+
console.log(` ${green('+')} .scaffold/.config.json, .scaffold/scope-policy.json`);
|
|
109
|
+
console.log(` ${green('+')} .agents/, .frameworks/, .workflow/\n`);
|
|
110
|
+
|
|
111
|
+
console.log(` ${dim('Git:')}`);
|
|
112
|
+
console.log(` ${green('+')} Repository initialized on main`);
|
|
113
|
+
console.log(` ${green('+')} Pre-commit hook installed (direct main commits blocked)`);
|
|
114
|
+
console.log(` ${green('+')} Initial commit created\n`);
|
|
115
|
+
|
|
116
|
+
console.log(` ${dim('Agents available:')}`);
|
|
117
|
+
console.log(` ${dim('client')} : UI, LOGIC, FORMS, ROUTING, ACCESSIBILITY, TESTING`);
|
|
118
|
+
if (bt === 'separate') {
|
|
119
|
+
console.log(` ${dim('backend')} : INIT, API, AUTH, DB, LOGIC, EVENTS, JOBS, TESTING`);
|
|
120
|
+
}
|
|
121
|
+
console.log(` ${dim('shared')} : CLOUD, SECURITY\n`);
|
|
122
|
+
|
|
123
|
+
console.log(` ${dim('Starting your first agent session...\n')}`);
|
|
124
|
+
separator();
|
|
125
|
+
console.log('');
|
|
126
|
+
rl.close();
|
|
127
|
+
|
|
128
|
+
const agentProc = spawn('npm', ['run', 'agent'], { cwd: ROOT, stdio: 'inherit' });
|
|
129
|
+
agentProc.on('error', (err) => {
|
|
130
|
+
console.error(' Could not start agent:', err.message);
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// ── Exports ───────────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
module.exports = {
|
|
137
|
+
TRAJECTORY_DETAILS,
|
|
138
|
+
renderTrajectoryLines,
|
|
139
|
+
printInitSummary,
|
|
140
|
+
};
|