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 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
@@ -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: 'npm', package: null },
63
- 'Rails': { registry: 'npm', package: null },
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) => new Promise((resolve, reject) => {
89
- const req = https.get(url, { timeout: 3000 }, (res) => {
90
- let data = '';
91
- res.on('data', chunk => data += chunk);
92
- res.on('end', () => resolve(data));
93
- });
94
- req.on('error', reject);
95
- req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
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
- return versions.length ? versions.map(String) : null;
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
- return versions.length ? versions : null;
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,
@@ -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 versions = await fetchLatestVersions(fwValue(fw)) || FRAMEWORK_VERSION_FALLBACK[fwValue(fw)] || [];
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 versions = await fetchLatestVersions(fwValue(fwObj)) || FRAMEWORK_VERSION_FALLBACK[fwValue(fwObj)] || [];
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multi-agents-cli",
3
- "version": "1.1.79",
3
+ "version": "1.1.80",
4
4
  "description": "Multi-agent workflow orchestration for Claude Code — isolated git worktrees, structured state tracking, autonomous task chaining",
5
5
  "keywords": [
6
6
  "claude-code",