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.
- package/bin/cli.js +80 -25
- 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
|
-
|
|
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
|
-
|
|
161
|
-
|
|
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)
|
|
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
|
|
192
|
-
// (UMADEV_MODEL_BASE_URL) wins; otherwise
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
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 fallback — so 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
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
243
|
+
/* default to international order */
|
|
216
244
|
}
|
|
217
|
-
|
|
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 本地向量检索模型缺失,正在下载
|
|
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'),
|
|
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.
|
|
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.
|
|
41
|
-
"@umacloud/cli-darwin-x64": "1.0.
|
|
42
|
-
"@umacloud/cli-linux-x64": "1.0.
|
|
43
|
-
"@umacloud/cli-linux-arm64": "1.0.
|
|
44
|
-
"@umacloud/cli-win32-x64": "1.0.
|
|
45
|
-
"@umacloud/knowledge": "1.0.
|
|
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
|
}
|