multi-agents-cli 1.1.79 → 1.1.80
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 +4 -1
- package/lib/data-config.js +60 -13
- package/lib/questions-flow.js +25 -11
- package/lib/ui.js +17 -1
- package/package.json +1 -1
package/init.js
CHANGED
|
@@ -29,7 +29,7 @@ const {
|
|
|
29
29
|
CLIENT_FRAMEWORKS,
|
|
30
30
|
BACKEND_FRAMEWORKS,
|
|
31
31
|
FRAMEWORK_VERSION_FALLBACK,
|
|
32
|
-
fetchLatestVersions,
|
|
32
|
+
fetchLatestVersions, prefetchVersions,
|
|
33
33
|
STATE_OPTIONS,
|
|
34
34
|
UI_OPTIONS,
|
|
35
35
|
STYLING_OPTIONS,
|
|
@@ -408,6 +408,9 @@ const main = async () => {
|
|
|
408
408
|
}
|
|
409
409
|
steps.blockEntries = blockEntries;
|
|
410
410
|
|
|
411
|
+
// Prefetch versions for active blocks in background
|
|
412
|
+
if (BLOCK_CONFIG.client.isActive) prefetchVersions(CLIENT_FRAMEWORKS);
|
|
413
|
+
|
|
411
414
|
separator();
|
|
412
415
|
|
|
413
416
|
// Run all question steps with back-nav support
|
package/lib/data-config.js
CHANGED
|
@@ -59,8 +59,8 @@ const FRAMEWORK_REGISTRY = {
|
|
|
59
59
|
'Fastify': { registry: 'npm', package: 'fastify' },
|
|
60
60
|
'FastAPI': { registry: 'pypi', package: 'fastapi' },
|
|
61
61
|
'Django': { registry: 'pypi', package: 'django' },
|
|
62
|
-
'Laravel': { registry: '
|
|
63
|
-
'Rails': { registry: '
|
|
62
|
+
'Laravel': { registry: 'packagist', package: 'laravel/framework' },
|
|
63
|
+
'Rails': { registry: 'rubygems', package: 'rails' },
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
const FRAMEWORK_VERSION_FALLBACK = {
|
|
@@ -79,21 +79,35 @@ const FRAMEWORK_VERSION_FALLBACK = {
|
|
|
79
79
|
'Rails': ['7.2', '7.1', '7.0'],
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
const _versionCache = {};
|
|
83
|
+
|
|
84
|
+
const getVersionCache = (frameworkValue) => _versionCache[frameworkValue] || null;
|
|
85
|
+
|
|
86
|
+
const prefetchVersions = (frameworks) => {
|
|
87
|
+
frameworks.forEach(fw => {
|
|
88
|
+
if (_versionCache[fw.value]) return;
|
|
89
|
+
fetchLatestVersions(fw.value).catch(() => {});
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
82
93
|
const fetchLatestVersions = async (frameworkValue) => {
|
|
94
|
+
if (_versionCache[frameworkValue]) return _versionCache[frameworkValue];
|
|
83
95
|
const entry = FRAMEWORK_REGISTRY[frameworkValue];
|
|
84
96
|
if (!entry || !entry.package) return null;
|
|
85
97
|
|
|
86
98
|
try {
|
|
87
99
|
const https = require('https');
|
|
88
|
-
const fetch = (url) =>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
const fetch = (url) => Promise.race([
|
|
101
|
+
new Promise((resolve, reject) => {
|
|
102
|
+
const req = https.get(url, (res) => {
|
|
103
|
+
let data = '';
|
|
104
|
+
res.on('data', chunk => data += chunk);
|
|
105
|
+
res.on('end', () => resolve(data));
|
|
106
|
+
});
|
|
107
|
+
req.on('error', reject);
|
|
108
|
+
}),
|
|
109
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 2500)),
|
|
110
|
+
]);
|
|
97
111
|
|
|
98
112
|
if (entry.registry === 'npm') {
|
|
99
113
|
const raw = await fetch(`https://registry.npmjs.org/${entry.package}`);
|
|
@@ -104,7 +118,9 @@ const fetchLatestVersions = async (frameworkValue) => {
|
|
|
104
118
|
.filter((v, i, arr) => arr.indexOf(v) === i)
|
|
105
119
|
.sort((a, b) => b - a)
|
|
106
120
|
.slice(0, 3);
|
|
107
|
-
|
|
121
|
+
const result = versions.length ? versions.map(String) : null;
|
|
122
|
+
if (result) _versionCache[frameworkValue] = result;
|
|
123
|
+
return result;
|
|
108
124
|
}
|
|
109
125
|
|
|
110
126
|
if (entry.registry === 'pypi') {
|
|
@@ -120,8 +136,37 @@ const fetchLatestVersions = async (frameworkValue) => {
|
|
|
120
136
|
.map(v => v.split('.').slice(0, 2).join('.'))
|
|
121
137
|
.filter((v, i, arr) => arr.indexOf(v) === i)
|
|
122
138
|
.slice(0, 3);
|
|
123
|
-
|
|
139
|
+
const result = versions.length ? versions : null;
|
|
140
|
+
if (result) _versionCache[frameworkValue] = result;
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
if (entry.registry === 'packagist') {
|
|
144
|
+
const raw = await fetch(`https://packagist.org/packages/${entry.package}.json`);
|
|
145
|
+
const json = JSON.parse(raw);
|
|
146
|
+
const versions = Object.keys(json.package?.versions || {})
|
|
147
|
+
.filter(v => /^v?\d+\.\d+\.\d+$/.test(v) && !v.includes('-'))
|
|
148
|
+
.map(v => parseInt(v.replace(/^v/, '').split('.')[0]))
|
|
149
|
+
.filter((v, i, arr) => arr.indexOf(v) === i)
|
|
150
|
+
.sort((a, b) => b - a)
|
|
151
|
+
.slice(0, 3);
|
|
152
|
+
const result = versions.length ? versions.map(String) : null;
|
|
153
|
+
if (result) _versionCache[frameworkValue] = result;
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (entry.registry === 'rubygems') {
|
|
158
|
+
const raw = await fetch(`https://rubygems.org/api/v1/versions/${entry.package}.json`);
|
|
159
|
+
const json = JSON.parse(raw);
|
|
160
|
+
const versions = json
|
|
161
|
+
.map(v => parseInt(v.number.split('.')[0]))
|
|
162
|
+
.filter((v, i, arr) => arr.indexOf(v) === i)
|
|
163
|
+
.sort((a, b) => b - a)
|
|
164
|
+
.slice(0, 3);
|
|
165
|
+
const result = versions.length ? versions.map(String) : null;
|
|
166
|
+
if (result) _versionCache[frameworkValue] = result;
|
|
167
|
+
return result;
|
|
124
168
|
}
|
|
169
|
+
|
|
125
170
|
} catch {
|
|
126
171
|
return null;
|
|
127
172
|
}
|
|
@@ -295,6 +340,8 @@ module.exports = {
|
|
|
295
340
|
FRAMEWORK_REGISTRY,
|
|
296
341
|
FRAMEWORK_VERSION_FALLBACK,
|
|
297
342
|
fetchLatestVersions,
|
|
343
|
+
getVersionCache,
|
|
344
|
+
prefetchVersions,
|
|
298
345
|
STATE_OPTIONS,
|
|
299
346
|
UI_OPTIONS,
|
|
300
347
|
STYLING_OPTIONS,
|
package/lib/questions-flow.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
// ── Imports ───────────────────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
|
-
const { dim, bold, blue, green, cyan, separator, arrowSelect, arrowConfirm, selectRequired, selectOptional, stepHeader } = require('./ui');
|
|
5
|
+
const { dim, bold, blue, green, cyan, separator, arrowSelect, arrowConfirm, selectRequired, selectOptional, stepHeader, createSpinner } = require('./ui');
|
|
6
6
|
const { BLOCKS } = require('./data-config');
|
|
7
7
|
const { BACK, RESTART } = require('./steps');
|
|
8
8
|
const {
|
|
9
9
|
CLIENT_FRAMEWORKS, BACKEND_FRAMEWORKS, FRAMEWORK_VERSION_FALLBACK,
|
|
10
|
-
fetchLatestVersions, STATE_OPTIONS, UI_OPTIONS, STYLING_OPTIONS,
|
|
10
|
+
fetchLatestVersions, getVersionCache, prefetchVersions, STATE_OPTIONS, UI_OPTIONS, STYLING_OPTIONS,
|
|
11
11
|
DB_OPTIONS, ORM_OPTIONS_BY_DB, ORM_OPTIONS, AUTH_OPTIONS,
|
|
12
12
|
} = require('./data-config');
|
|
13
13
|
const { buildIDEOptions, verifyIDE, detectTerminal } = require('./detect');
|
|
@@ -36,10 +36,16 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
36
36
|
run: async (machine, answers) => {
|
|
37
37
|
stepHeader(2, 12);
|
|
38
38
|
const fw = answers.clientFw;
|
|
39
|
-
const
|
|
39
|
+
const cached = getVersionCache(fwValue(fw));
|
|
40
|
+
let versions;
|
|
41
|
+
if (cached) {
|
|
42
|
+
versions = cached;
|
|
43
|
+
} else {
|
|
44
|
+
const spinner = createSpinner('Fetching latest versions...');
|
|
45
|
+
versions = await fetchLatestVersions(fwValue(fw)) || FRAMEWORK_VERSION_FALLBACK[fwValue(fw)] || [];
|
|
46
|
+
spinner.stop();
|
|
47
|
+
}
|
|
40
48
|
if (!versions.length) return null; // skip silently
|
|
41
|
-
|
|
42
|
-
console.log(dim(' Fetching latest versions...'));
|
|
43
49
|
const versionChoices = versions.map((v, i) => ({
|
|
44
50
|
label: i === 0 ? `v${v} ${dim('(latest)')}` : `v${v}`,
|
|
45
51
|
value: v,
|
|
@@ -107,8 +113,8 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
107
113
|
{
|
|
108
114
|
name: 'backendFw',
|
|
109
115
|
run: async (machine, answers) => {
|
|
110
|
-
stepHeader(7, 12);
|
|
111
116
|
if (answers.useIntegratedBackend) return null; // skip
|
|
117
|
+
stepHeader(7, 12);
|
|
112
118
|
|
|
113
119
|
separator();
|
|
114
120
|
console.log(`\n${bold(blue('Backend configuration'))}`);
|
|
@@ -122,11 +128,19 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
122
128
|
{
|
|
123
129
|
name: 'backendFwVersion',
|
|
124
130
|
run: async (machine, answers) => {
|
|
125
|
-
stepHeader(8, 12);
|
|
126
131
|
const fwObj = answers.backendFw;
|
|
127
132
|
if (!fwObj) return null; // skip — no backend selected
|
|
133
|
+
stepHeader(8, 12);
|
|
128
134
|
|
|
129
|
-
const
|
|
135
|
+
const cached = getVersionCache(fwValue(fwObj));
|
|
136
|
+
let versions;
|
|
137
|
+
if (cached) {
|
|
138
|
+
versions = cached;
|
|
139
|
+
} else {
|
|
140
|
+
const spinner = createSpinner('Fetching latest versions...');
|
|
141
|
+
versions = await fetchLatestVersions(fwValue(fwObj)) || FRAMEWORK_VERSION_FALLBACK[fwValue(fwObj)] || [];
|
|
142
|
+
spinner.stop();
|
|
143
|
+
}
|
|
130
144
|
if (!versions.length) return null;
|
|
131
145
|
|
|
132
146
|
const vChoices = versions.map((v, i) => ({
|
|
@@ -149,8 +163,8 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
149
163
|
{
|
|
150
164
|
name: 'backendDb',
|
|
151
165
|
run: async (machine, answers) => {
|
|
152
|
-
stepHeader(9, 12);
|
|
153
166
|
if (!answers.backendFw) return null;
|
|
167
|
+
stepHeader(9, 12);
|
|
154
168
|
return await selectOptional('Database type:', DB_OPTIONS, machine, 8, answers, BLOCKS.BACKEND);
|
|
155
169
|
},
|
|
156
170
|
},
|
|
@@ -159,9 +173,9 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
159
173
|
{
|
|
160
174
|
name: 'backendOrm',
|
|
161
175
|
run: async (machine, answers) => {
|
|
162
|
-
stepHeader(10, 12);
|
|
163
176
|
const { backendFw, backendDb } = answers;
|
|
164
177
|
if (!backendFw || !backendDb) return null;
|
|
178
|
+
stepHeader(10, 12);
|
|
165
179
|
const byDb = ORM_OPTIONS_BY_DB[backendDb] || [];
|
|
166
180
|
const byFw = ORM_OPTIONS[fwValue(backendFw)] || [];
|
|
167
181
|
const ormChoices = byDb.length && byFw.length ? byDb.filter(o => byFw.includes(o)) : byDb.length ? byDb : byFw;
|
|
@@ -173,9 +187,9 @@ const buildStepDefs = (IDE_CANDIDATES) => [
|
|
|
173
187
|
{
|
|
174
188
|
name: 'backendAuth',
|
|
175
189
|
run: async (machine, answers) => {
|
|
176
|
-
stepHeader(11, 12);
|
|
177
190
|
const { backendFw } = answers;
|
|
178
191
|
if (!backendFw) return null;
|
|
192
|
+
stepHeader(11, 12);
|
|
179
193
|
return await selectOptional('Auth strategy:', AUTH_OPTIONS[fwValue(backendFw)] || [], machine, 10, answers, BLOCKS.BACKEND);
|
|
180
194
|
},
|
|
181
195
|
},
|
package/lib/ui.js
CHANGED
|
@@ -38,6 +38,22 @@ const rl = readline.createInterface({
|
|
|
38
38
|
const ask = (question) =>
|
|
39
39
|
new Promise((resolve) => rl.question(question, (a) => resolve(a.trim())));
|
|
40
40
|
|
|
41
|
+
const createSpinner = (label) => {
|
|
42
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
43
|
+
let i = 0;
|
|
44
|
+
process.stdout.write(dim(' ' + label + ' '));
|
|
45
|
+
const timer = setInterval(() => {
|
|
46
|
+
process.stdout.write(String.fromCharCode(13) + dim(' ' + label + ' ' + frames[i % frames.length]));
|
|
47
|
+
i++;
|
|
48
|
+
}, 80);
|
|
49
|
+
return {
|
|
50
|
+
stop: () => {
|
|
51
|
+
clearInterval(timer);
|
|
52
|
+
process.stdout.write(String.fromCharCode(13) + dim(' ' + label + ' done') + String.fromCharCode(10));
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
41
57
|
const arrowSelect = async (message, choices, showBack = false, backLabel = '← Restart configuration') => {
|
|
42
58
|
const allChoices = showBack
|
|
43
59
|
? [...choices, { label: dim(backLabel) }]
|
|
@@ -241,7 +257,7 @@ module.exports = {
|
|
|
241
257
|
stepHeader,
|
|
242
258
|
c, bold, green, yellow, dim, cyan, blue, red,
|
|
243
259
|
rl, ask,
|
|
244
|
-
arrowSelect, arrowConfirm,
|
|
260
|
+
arrowSelect, arrowConfirm, createSpinner,
|
|
245
261
|
selectRequired, selectOptional,
|
|
246
262
|
separator, showList, summaryLine, renderTrajectoryLines,
|
|
247
263
|
};
|
package/package.json
CHANGED