umadev 1.0.8 → 1.0.10

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.
Files changed (2) hide show
  1. package/bin/cli.js +80 -25
  2. package/package.json +7 -7
package/bin/cli.js CHANGED
@@ -128,6 +128,26 @@ function modelPresent(dir) {
128
128
  }
129
129
  });
130
130
  }
131
+ // Render one frame of the download progress bar (in place, via \r). Block-glyph
132
+ // bar + percent + downloaded/total + live speed; ANSI-colored only on a TTY so a
133
+ // piped/redirected install stays clean.
134
+ function drawBar(label, got, total, startTime) {
135
+ const tty = process.stderr.isTTY;
136
+ const c = (code) => (tty ? '\x1b[' + code + 'm' : '');
137
+ const w = 22;
138
+ const ratio = total > 0 ? Math.min(1, got / total) : 0;
139
+ const fill = Math.round(ratio * w);
140
+ const bar = c('38;5;45') + '█'.repeat(fill) + c('0') + c('38;5;238') + '░'.repeat(w - fill) + c('0');
141
+ const pct = String(Math.floor(ratio * 100)).padStart(3);
142
+ const mb = (got / 1048576).toFixed(1);
143
+ const tot = (total / 1048576).toFixed(0);
144
+ const sec = (Date.now() - startTime) / 1000;
145
+ const spd = sec > 0.3 ? (got / 1048576 / sec).toFixed(1) + ' MB/s' : '…';
146
+ process.stderr.write(
147
+ '\r ' + c('1') + label + c('0') + ' ' + bar + ' ' + c('1') + pct + '%' + c('0') +
148
+ c('2') + ' · ' + mb + '/' + tot + ' MB · ' + spd + c('0') + ' ',
149
+ );
150
+ }
131
151
  // Download one URL to `dest`, following redirects (GitHub → CDN), drawing a
132
152
  // progress bar when `withBar`. Resolves on success, rejects on any error.
133
153
  function downloadTo(url, dest, withBar, label) {
@@ -149,22 +169,24 @@ function downloadTo(url, dest, withBar, label) {
149
169
  const total = parseInt(res.headers['content-length'] || '0', 10);
150
170
  let got = 0;
151
171
  let lastPct = -1;
172
+ let lastDraw = 0;
173
+ const startTime = Date.now();
152
174
  const tmp = dest + '.part';
153
175
  const out = fs.createWriteStream(tmp);
176
+ // Draw the bar at 0% the instant the response starts — on a slow link the
177
+ // first 1% can take a while, and a silent gap reads as "stuck / failed".
178
+ if (withBar && total > 0) drawBar(label, 0, total, startTime);
154
179
  res.on('data', (chunk) => {
155
180
  got += chunk.length;
156
181
  if (withBar && total > 0) {
182
+ const now = Date.now();
157
183
  const pct = Math.floor((got / total) * 100);
158
- if (pct !== lastPct) {
184
+ // Redraw on each new percent OR every ~250ms — keeps the live speed
185
+ // ticking even while a single percent of a large file streams in.
186
+ if (pct !== lastPct || now - lastDraw > 250) {
159
187
  lastPct = pct;
160
- const w = 24;
161
- const fill = Math.round((pct / 100) * w);
162
- const bar = '#'.repeat(fill) + '-'.repeat(w - fill);
163
- const mb = (got / 1048576).toFixed(0);
164
- const tot = (total / 1048576).toFixed(0);
165
- process.stderr.write(
166
- '\r ' + label + ' [' + bar + '] ' + pct + '% (' + mb + '/' + tot + ' MB)',
167
- );
188
+ lastDraw = now;
189
+ drawBar(label, got, total, startTime);
168
190
  }
169
191
  }
170
192
  });
@@ -177,7 +199,10 @@ function downloadTo(url, dest, withBar, label) {
177
199
  } catch (er) {
178
200
  return reject(er);
179
201
  }
180
- if (withBar) process.stderr.write('\n');
202
+ if (withBar && total > 0) {
203
+ drawBar(label, total, total, startTime);
204
+ process.stderr.write('\n');
205
+ }
181
206
  resolve();
182
207
  }),
183
208
  );
@@ -188,21 +213,24 @@ function downloadTo(url, dest, withBar, label) {
188
213
  req.setTimeout(120000, () => req.destroy(new Error('timeout')));
189
214
  });
190
215
  }
191
- // Ordered list of base URLs to try for the release assets. An explicit override
192
- // (UMADEV_MODEL_BASE_URL) wins; otherwise zh-CN / China-timezone users get GitHub
193
- // PROXY MIRRORS first (github.com's release CDN is frequently slow or blocked in
194
- // mainland China), and everyone else gets github.com first with the others as
195
- // fallback either way, so a blocked github.com or a down mirror still recovers.
216
+ // Ordered list of base URLs to try for the model files. An explicit override
217
+ // (UMADEV_MODEL_BASE_URL) wins; otherwise EVERYONE pulls the SAME upstream f32 from
218
+ // HuggingFace (international) / hf-mirror.com (China), region-ordered, with the
219
+ // GitHub fp16 release as a last-resort fallbackso the download is consistent in
220
+ // size everywhere and the model is always reachable.
196
221
  function releaseBases(version) {
197
222
  if (process.env.UMADEV_MODEL_BASE_URL) {
198
223
  return [process.env.UMADEV_MODEL_BASE_URL.replace(/\/+$/, '')];
199
224
  }
225
+ // GitHub Release ships the quantized fp16 model (~224MB, smaller). HuggingFace
226
+ // and its China mirror hf-mirror.com serve the upstream f32 model (~448MB —
227
+ // bigger, but the candle loader handles either). hf-mirror is the FAST + reliable
228
+ // source inside mainland China, where github.com's release CDN is slow and the
229
+ // community GitHub proxies are flaky for release-asset URLs.
200
230
  const gh = 'https://github.com/umacloud/umadev/releases/download/v' + version;
201
- const mirrors = [
202
- 'https://ghproxy.net/' + gh,
203
- 'https://ghfast.top/' + gh,
204
- 'https://gh-proxy.com/' + gh,
205
- ];
231
+ const ghProxies = ['https://ghproxy.net/' + gh, 'https://ghfast.top/' + gh];
232
+ const hf = 'https://huggingface.co/intfloat/multilingual-e5-small/resolve/main';
233
+ const hfMirror = 'https://hf-mirror.com/intfloat/multilingual-e5-small/resolve/main';
206
234
  let cn = false;
207
235
  try {
208
236
  const opts = Intl.DateTimeFormat().resolvedOptions();
@@ -212,9 +240,15 @@ function releaseBases(version) {
212
240
  /Shanghai|Chongqing|Urumqi|Harbin|Hong_Kong|Macau/.test(tz) ||
213
241
  /zh[_-]?(CN|Hans)/i.test(loc);
214
242
  } catch (_) {
215
- /* default to direct-first */
243
+ /* default to international order */
216
244
  }
217
- return cn ? [...mirrors, gh] : [gh, ...mirrors];
245
+ // Unified on the upstream f32 model from HuggingFace (international) + its China
246
+ // mirror hf-mirror.com, so BOTH regions download the SAME ~448MB f32 — consistent
247
+ // everywhere (no more "some get 200MB, some 400MB"). China: hf-mirror first (fast
248
+ // in CN); international: huggingface.co first. The GitHub Release fp16 (~224MB) +
249
+ // proxies stay only as a LAST-RESORT fallback if both HF endpoints are down (the
250
+ // candle loader casts either precision to f32, so a fallback still loads).
251
+ return cn ? [hfMirror, hf, gh, ...ghProxies] : [hf, hfMirror, gh, ...ghProxies];
218
252
  }
219
253
  // Try each base for `name` in order; resolve on first success, throw the last
220
254
  // error if all fail. A China mirror can cover a blocked github.com (or vice
@@ -244,13 +278,13 @@ async function ensureModel() {
244
278
  try {
245
279
  fs.mkdirSync(dir, { recursive: true });
246
280
  process.stderr.write(
247
- '\n 本地向量检索模型缺失,正在下载 (multilingual-e5-small · fp16 · ~224MB)…\n',
281
+ '\n 本地向量检索模型缺失,正在下载 multilingual-e5-small(国内自动走镜像)…\n',
248
282
  );
249
283
  process.stderr.write(
250
284
  ' 一次性下载;之后完全本地、运行时无需联网。失败不影响使用(降级为 BM25)。\n',
251
285
  );
252
286
  await downloadFile(bases, 'config.json', path.join(dir, 'config.json'), false, '');
253
- await downloadFile(bases, 'tokenizer.json', path.join(dir, 'tokenizer.json'), false, '');
287
+ await downloadFile(bases, 'tokenizer.json', path.join(dir, 'tokenizer.json'), true, '下载分词器 ');
254
288
  await downloadFile(
255
289
  bases,
256
290
  'model.safetensors',
@@ -270,6 +304,22 @@ async function ensureModel() {
270
304
  }
271
305
  }
272
306
 
307
+ // Subcommands that do NOT drive the agent runtime — they never retrieve
308
+ // knowledge, so they must not trigger the one-time vector-model download.
309
+ // Without this, `umadev update` (and `--version` / `--help` / `doctor` / …) would
310
+ // appear to hang on a machine that doesn't have the model yet, while it streams
311
+ // in — which reads as "the update command broke". The model is fetched lazily on
312
+ // the first command that actually needs it (the TUI / run / quick / …).
313
+ const NO_MODEL_COMMANDS = new Set([
314
+ 'update', 'install', 'uninstall', 'init',
315
+ '--version', '-V', 'version',
316
+ '--help', '-h', 'help',
317
+ 'doctor', 'mcp', 'hook', 'ci',
318
+ 'usage', 'lessons', 'history',
319
+ 'examples', 'guide',
320
+ 'mcp-manage', 'skill', 'knowledge-manage', 'pr',
321
+ ]);
322
+
273
323
  async function main() {
274
324
  const binary = findBinary();
275
325
  // npm artifact round-trips (upload/download-artifact in CI) can strip the
@@ -280,10 +330,15 @@ async function main() {
280
330
  // read-only install dir or already +x — spawnSync below reports real errors
281
331
  }
282
332
  const extraEnv = {};
333
+ // A utility command (update/--version/--help/doctor/…) skips the model fetch so
334
+ // it returns instantly even before the model is installed; the agent runtime
335
+ // commands still fetch it lazily on first use.
336
+ const firstArg = process.argv[2] || '';
337
+ const needsModel = !NO_MODEL_COMMANDS.has(firstArg);
283
338
  // Prefer a bundled npm model package (dev / sibling layout); otherwise fetch
284
339
  // it on demand into ~/.umadev/embed-model (the binary's model_dir() fallback).
285
340
  let modelDir = findModelDir();
286
- if (!modelDir) modelDir = await ensureModel();
341
+ if (needsModel && !modelDir) modelDir = await ensureModel();
287
342
  if (modelDir && !process.env.UMADEV_EMBED_MODEL_DIR) {
288
343
  extraEnv.UMADEV_EMBED_MODEL_DIR = modelDir;
289
344
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "umadev",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "A project-director Agent for AI coding hosts — drives your logged-in Claude Code / Codex through a 9-phase commercial delivery pipeline with governance. No API key needed.",
5
5
  "keywords": [
6
6
  "ai",
@@ -37,11 +37,11 @@
37
37
  "node": ">=18"
38
38
  },
39
39
  "optionalDependencies": {
40
- "@umacloud/cli-darwin-arm64": "1.0.8",
41
- "@umacloud/cli-darwin-x64": "1.0.8",
42
- "@umacloud/cli-linux-x64": "1.0.8",
43
- "@umacloud/cli-linux-arm64": "1.0.8",
44
- "@umacloud/cli-win32-x64": "1.0.8",
45
- "@umacloud/knowledge": "1.0.8"
40
+ "@umacloud/cli-darwin-arm64": "1.0.10",
41
+ "@umacloud/cli-darwin-x64": "1.0.10",
42
+ "@umacloud/cli-linux-x64": "1.0.10",
43
+ "@umacloud/cli-linux-arm64": "1.0.10",
44
+ "@umacloud/cli-win32-x64": "1.0.10",
45
+ "@umacloud/knowledge": "1.0.10"
46
46
  }
47
47
  }