@tedbrain/vlink 0.1.63 → 0.1.64

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/README.md CHANGED
@@ -37,6 +37,29 @@ npx --registry=https://registry.npmmirror.com -y @tedbrain/vlink@latest --versio
37
37
 
38
38
  安装命令会输出安装目标、权限回退原因及 setup 状态;失败时输出错误并返回非零退出码。
39
39
 
40
+ 需要排查安装过程时,加上 `--verbose`:
41
+
42
+ ```bash
43
+ npx --registry=https://registry.npmmirror.com -y \
44
+ @tedbrain/vlink@latest install --verbose
45
+ ```
46
+
47
+ verbose 会从 npm 包的 JavaScript wrapper 开始输出 Node、包版本、平台选择、PATH
48
+ 查找、权限检查、二进制缓存或下载、复制、补全和 setup 各阶段。也可以设置
49
+ `VLINK_VERBOSE=1`。日志不会打印完整环境变量、registry 凭据或下载 URL 的查询参数。
50
+
51
+ 如果上面的命令连 `[TedBrain:verbose] wrapper entered` 都没有输出,使用 npm 的显式
52
+ package 形式,绕过 npx 的命令推断:
53
+
54
+ ```bash
55
+ npm exec --registry=https://registry.npmmirror.com \
56
+ --package=@tedbrain/vlink@latest -- \
57
+ vlink install --verbose
58
+ ```
59
+
60
+ 若显式形式可以输出,问题发生在本机 npx 的参数路由或缓存阶段;若仍无 wrapper
61
+ 输出,可再加 npm 自身的 `--loglevel verbose` 检查 npm 在执行包之前的行为。
62
+
40
63
  手动配置 Agent 时可以只安装或更新 vlink,并跳过 TedBrain 配置向导:
41
64
 
42
65
  ```bash
package/bin/vlink.js CHANGED
@@ -2,12 +2,45 @@
2
2
  'use strict';
3
3
 
4
4
  const { spawn } = require('child_process');
5
- const { ensureBinary } = require('../install');
6
- const { runInstallerFromArgs } = require('../installer');
5
+
6
+ function verboseEnabled(args, env = process.env) {
7
+ const value = String(env.VLINK_VERBOSE || '').toLowerCase();
8
+ return args[0] === 'install' && (
9
+ args.includes('--verbose') || ['1', 'true', 'yes', 'on'].includes(value)
10
+ );
11
+ }
12
+
13
+ function verbose(message) {
14
+ process.stderr.write(`[TedBrain:verbose] ${message}\n`);
15
+ }
16
+
17
+ function diagnosticArgs(args) {
18
+ const safe = new Set(['install', '--verbose', '--no-setup', '--help', '-h']);
19
+ return args.map((arg) => {
20
+ if (safe.has(arg)) return arg;
21
+ if (arg.startsWith('--')) return arg.split('=', 1)[0];
22
+ return '<argument>';
23
+ });
24
+ }
7
25
 
8
26
  async function main() {
9
27
  const args = process.argv.slice(2);
28
+ const diagnostic = verboseEnabled(args);
29
+ if (diagnostic) {
30
+ process.env.VLINK_VERBOSE = '1';
31
+ verbose('wrapper entered');
32
+ verbose(`node=${process.version} executable=${process.execPath}`);
33
+ verbose(`wrapper=${__filename}`);
34
+ verbose(`cwd=${process.cwd()}`);
35
+ verbose(`argv=${JSON.stringify(diagnosticArgs(args))}`);
36
+ }
37
+
38
+ const { ensureBinary, VERSION } = require('../install');
39
+ const { runInstallerFromArgs } = require('../installer');
40
+ if (diagnostic) verbose(`package version=${VERSION}`);
41
+
10
42
  if (args[0] === 'install') {
43
+ if (diagnostic) verbose('dispatching install command');
11
44
  await runInstallerFromArgs(args.slice(1));
12
45
  return;
13
46
  }
package/binaries.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
- "version": "0.1.63",
2
+ "version": "0.1.64",
3
3
  "binaries": {
4
- "linux-x86_64": "dffb9115be72e1b1546e5d5adc9a208de7ac869f89cfc1692edb8ef60a05e8d1",
5
- "linux-x86_64-glibc2.17": "0acfdaec9e600817d55a389b9889c476dbfa9842768c9e431ef3c44df7e0fa5a",
6
- "linux-aarch64": "f8a3a086cf963db72d380e410c5264dc25659c44f3770597bc36d3021bf2376c",
7
- "macos-x86_64": "bc917cf21d802dc804acdcbd06eb769cd520a12475b25db9e8238036db7383ed",
8
- "macos-aarch64": "14b831e1f7ff7b09def2efc34e74d0a05d437cd1650c1c6f3c254cd08fc48b64",
9
- "windows-x86_64.exe": "c9ca055f7791b095f8cc958ffa0fab4d01eb0fa9b36801e25a51982ceeaf30fd",
10
- "windows-aarch64.exe": "717d470b48a10b1a16d897e56738447b3b7d6128cf02ea8f428e481117e80cd4"
4
+ "linux-x86_64": "cd46ed189a1cb56a4d2ff84af7cda6a4870ad202f6172500e02a29654cb82b54",
5
+ "linux-x86_64-glibc2.17": "d578c70bafe98de520b25e78ea10919a12cb14fff0524ab3339bb0d912a78961",
6
+ "linux-aarch64": "8acbcce5bd84e14652f778ab987abe2a9f461b3ba4bb0dbe4022f8900a42868e",
7
+ "macos-x86_64": "5e96d897d75dc02a0e8acca05bfb8a5910751d38b10de0eda9f1c0ae9ac6df13",
8
+ "macos-aarch64": "8481807cd40cac885908550959e259164eaf9d2a20f10e2972c57a5f3e125815",
9
+ "windows-x86_64.exe": "90a2a090f993bc6258ccddacf62aa05d99d96b411a39a041cf2e6f19c6f8fd69",
10
+ "windows-aarch64.exe": "e96d0dcbfde313747a62cb7b5f945cfdf444927f40e758f25ed06da6c0d517b6"
11
11
  }
12
12
  }
package/install.js CHANGED
@@ -26,6 +26,31 @@ const GLIBC217_SUFFIX = 'linux-x86_64-glibc2.17';
26
26
 
27
27
  class VlinkInstallError extends Error {}
28
28
 
29
+ function verboseEnabled(options = {}) {
30
+ if (options.verbose === true) return true;
31
+ return ['1', 'true', 'yes', 'on'].includes(
32
+ String(process.env.VLINK_VERBOSE || '').toLowerCase()
33
+ );
34
+ }
35
+
36
+ function verboseLog(message, options = {}) {
37
+ if (!verboseEnabled(options)) return;
38
+ (options.output || process.stderr).write(`[vlink:verbose] ${message}\n`);
39
+ }
40
+
41
+ function displayUrl(value) {
42
+ try {
43
+ const url = new URL(value);
44
+ url.username = '';
45
+ url.password = '';
46
+ url.search = '';
47
+ url.hash = '';
48
+ return url.toString();
49
+ } catch {
50
+ return '<invalid-url>';
51
+ }
52
+ }
53
+
29
54
  function parseGlibcVersion(text) {
30
55
  for (const line of String(text || '').split(/\r?\n/)) {
31
56
  const glibcMatch = line.match(/\bglibc\s+([0-9]+(?:\.[0-9]+)+)/);
@@ -137,7 +162,7 @@ function httpGetText(url) {
137
162
  if (res.statusCode !== 200) {
138
163
  res.resume();
139
164
  return reject(
140
- new VlinkInstallError(`HTTP ${res.statusCode} from ${target}`)
165
+ new VlinkInstallError(`HTTP ${res.statusCode} from ${displayUrl(target)}`)
141
166
  );
142
167
  }
143
168
 
@@ -308,7 +333,9 @@ function downloadFile(url, destPath) {
308
333
  if (res.statusCode !== 200) {
309
334
  res.resume();
310
335
  return reject(
311
- new VlinkInstallError(`download failed: HTTP ${res.statusCode} from ${target}`)
336
+ new VlinkInstallError(
337
+ `download failed: HTTP ${res.statusCode} from ${displayUrl(target)}`
338
+ )
312
339
  );
313
340
  }
314
341
  res.pipe(fileStream);
@@ -339,14 +366,23 @@ function verifyBinary(binaryPath) {
339
366
  }
340
367
  }
341
368
 
342
- async function ensureBinary({ force = false } = {}) {
369
+ async function ensureBinary(options = {}) {
370
+ const force = options.force === true;
343
371
  const spec = platformSpec();
344
372
  const binaryPath = resolveBinaryPath(spec);
373
+ verboseLog(
374
+ `binary resolver: version=${VERSION} platform=${spec.platform}/${spec.arch} ` +
375
+ `suffixes=${spec.suffixes.join(',')}`,
376
+ options
377
+ );
378
+ verboseLog(`binary resolver: selected path=${binaryPath} force=${force}`, options);
345
379
  if (!force && isExecutable(binaryPath)) {
380
+ verboseLog('binary resolver: using existing executable', options);
346
381
  return binaryPath;
347
382
  }
348
383
 
349
384
  const dir = path.dirname(binaryPath);
385
+ verboseLog(`binary resolver: creating download directory ${dir}`, options);
350
386
  fs.mkdirSync(dir, { recursive: true });
351
387
 
352
388
  let lastError = new VlinkInstallError('no download source available');
@@ -354,15 +390,22 @@ async function ensureBinary({ force = false } = {}) {
354
390
  for (const suffix of spec.suffixes) {
355
391
  const tempPath = path.join(dir, `.${spec.binaryName}.download-${process.pid}`);
356
392
  const sources = buildDownloadSources(spec, suffix);
393
+ verboseLog(
394
+ `download stage: suffix=${suffix} sources=${sources.map((source) => source.name).join(',')}`,
395
+ options
396
+ );
357
397
  for (const source of sources) {
358
398
  try {
399
+ verboseLog(`download stage: resolving ${source.name}`, options);
359
400
  const url = await source.resolve();
360
- console.error(`[vlink] downloading ${url} [${source.name}]`);
401
+ console.error(`[vlink] downloading ${displayUrl(url)} [${source.name}]`);
361
402
  fs.rmSync(tempPath, { force: true });
362
403
  await downloadFile(url, tempPath);
404
+ verboseLog(`download stage: received ${source.name} payload`, options);
363
405
 
364
406
  const checksum = expectedChecksum(spec, suffix);
365
407
  if (checksum) {
408
+ verboseLog(`verification stage: checking SHA-256 for ${suffix}`, options);
366
409
  const actual = await sha256File(tempPath);
367
410
  if (actual !== checksum) {
368
411
  throw new VlinkInstallError(
@@ -376,7 +419,9 @@ async function ensureBinary({ force = false } = {}) {
376
419
  }
377
420
  if (fs.existsSync(binaryPath)) fs.rmSync(binaryPath, { force: true });
378
421
  fs.renameSync(tempPath, binaryPath);
422
+ verboseLog(`verification stage: executing ${binaryPath} -V`, options);
379
423
  verifyBinary(binaryPath);
424
+ verboseLog('verification stage: binary is ready', options);
380
425
  return binaryPath;
381
426
  } catch (err) {
382
427
  lastError = err;
@@ -393,6 +438,8 @@ module.exports = {
393
438
  VlinkInstallError,
394
439
  VERSION,
395
440
  GLIBC217_SUFFIX,
441
+ verboseEnabled,
442
+ displayUrl,
396
443
  parseGlibcVersion,
397
444
  glibcVersion,
398
445
  versionAtLeast,
package/installer.js CHANGED
@@ -18,6 +18,20 @@ function log(message, output = process.stderr) {
18
18
  output.write(`[TedBrain] ${message}\n`);
19
19
  }
20
20
 
21
+ function verboseEnabled(options = {}) {
22
+ if (options.verbose === true) return true;
23
+ const env = options.env || process.env;
24
+ return ['1', 'true', 'yes', 'on'].includes(
25
+ String(env.VLINK_VERBOSE || '').toLowerCase()
26
+ );
27
+ }
28
+
29
+ function verboseLog(message, options = {}) {
30
+ if (!verboseEnabled(options)) return;
31
+ const output = options.output || process.stderr;
32
+ output.write(`[TedBrain:verbose] ${message}\n`);
33
+ }
34
+
21
35
  function environmentValue(env, name) {
22
36
  const expected = name.toLowerCase();
23
37
  for (const key of Object.keys(env)) {
@@ -170,6 +184,7 @@ function ensureUserPath(installDir, options = {}) {
170
184
  const fsImpl = options.fs || fs;
171
185
  const output = options.output || process.stderr;
172
186
 
187
+ verboseLog(`PATH stage: prioritizing ${installDir}`, options);
173
188
  prependEnvironmentPath(installDir, env, platform);
174
189
 
175
190
  if (platform === 'win32') {
@@ -291,7 +306,11 @@ function installBinary(sourcePath, targetPath, options = {}) {
291
306
  const spawnSyncImpl = options.spawnSync || spawnSync;
292
307
  const output = options.output || process.stderr;
293
308
 
294
- if (samePath(sourcePath, targetPath, platform)) return targetPath;
309
+ if (samePath(sourcePath, targetPath, platform)) {
310
+ verboseLog(`copy stage: source already equals target ${targetPath}`, options);
311
+ return targetPath;
312
+ }
313
+ verboseLog(`copy stage: ${sourcePath} -> ${targetPath}`, options);
295
314
  fsImpl.mkdirSync(path.dirname(targetPath), { recursive: true });
296
315
 
297
316
  if (options.stopDaemon) {
@@ -365,6 +384,7 @@ function installWindowsCommandShim(vlinkPath, options = {}) {
365
384
  function installShellCompletion(vlinkPath, options = {}) {
366
385
  const spawnSyncImpl = options.spawnSync || spawnSync;
367
386
  const output = options.output || process.stderr;
387
+ verboseLog(`completion stage: executing ${vlinkPath}`, options);
368
388
  const result = spawnSyncImpl(
369
389
  vlinkPath,
370
390
  ['completion', '--install', '--quiet'],
@@ -400,7 +420,11 @@ async function migrateStaticVlink(vlinkPath, spec, options) {
400
420
  if (!binaryIsStaticElf(vlinkPath, options)) return vlinkPath;
401
421
 
402
422
  log('migrating the installed static vlink to the glibc 2.17 build', options.output);
403
- const sourcePath = await options.ensureBinary({ force: true });
423
+ const sourcePath = await options.ensureBinary({
424
+ force: true,
425
+ verbose: verboseEnabled(options),
426
+ output: options.output
427
+ });
404
428
  return installBinary(sourcePath, vlinkPath, {
405
429
  ...options,
406
430
  stopDaemon: true
@@ -412,6 +436,7 @@ async function runInstaller(options = {}) {
412
436
  const platform = options.platform || env.VLINK_PLATFORM || process.platform;
413
437
  const arch = options.arch || env.VLINK_ARCH || process.arch;
414
438
  const skipSetup = Boolean(options.skipSetup);
439
+ const verbose = verboseEnabled(options);
415
440
  const home = options.home || os.homedir();
416
441
  const fsImpl = options.fs || fs;
417
442
  const output = options.output || process.stderr;
@@ -420,6 +445,17 @@ async function runInstaller(options = {}) {
420
445
  const ensureBinary =
421
446
  options.ensureBinary || ((settings) => installModule.ensureBinary(settings));
422
447
 
448
+ verboseLog(
449
+ `installer entered: package=${installModule.VERSION} platform=${platform} arch=${arch}`,
450
+ { ...options, env, output, verbose }
451
+ );
452
+ verboseLog(`runtime: node=${process.version} executable=${process.execPath}`, {
453
+ ...options,
454
+ env,
455
+ output,
456
+ verbose
457
+ });
458
+
423
459
  const spec = platformSpec({
424
460
  platform,
425
461
  arch,
@@ -437,14 +473,32 @@ async function runInstaller(options = {}) {
437
473
  home,
438
474
  fs: fsImpl,
439
475
  output,
440
- spawnSync: spawnSyncImpl
476
+ spawnSync: spawnSyncImpl,
477
+ verbose
441
478
  };
442
479
 
480
+ verboseLog(
481
+ `platform stage: suffixes=${spec.suffixes.join(',')} binary=${spec.binaryName}`,
482
+ sharedOptions
483
+ );
484
+ verboseLog(
485
+ `target stage: installDir=${installDir} configured=${Boolean(env.VLINK_INSTALL_DIR)}`,
486
+ sharedOptions
487
+ );
488
+ const pathEntries = String(environmentValue(env, 'PATH'))
489
+ .split(platform === 'win32' ? ';' : path.delimiter)
490
+ .filter(Boolean).length;
491
+ verboseLog(`PATH stage: inspecting ${pathEntries} entries`, sharedOptions);
492
+
443
493
  let vlinkPath = findOnPath(spec.binaryName, {
444
494
  env,
445
495
  platform,
446
496
  fs: fsImpl
447
497
  });
498
+ verboseLog(
499
+ `discovery stage: native PATH binary=${vlinkPath || 'none'}`,
500
+ sharedOptions
501
+ );
448
502
 
449
503
  if (vlinkPath) {
450
504
  log(
@@ -465,9 +519,11 @@ async function runInstaller(options = {}) {
465
519
  let installTarget = env.VLINK_INSTALL_DIR ? targetPath : (vlinkPath || targetPath);
466
520
  // Avoid trying to replace a shared installation, even if its parent is writable.
467
521
  if (vlinkPath && !env.VLINK_INSTALL_DIR && !samePath(vlinkPath, targetPath, platform)) {
522
+ verboseLog(`permission stage: checking ${vlinkPath} and its parent directory`, sharedOptions);
468
523
  try {
469
524
  fsImpl.accessSync(vlinkPath, fs.constants.W_OK);
470
525
  fsImpl.accessSync(path.dirname(vlinkPath), fs.constants.W_OK);
526
+ verboseLog('permission stage: existing installation is replaceable', sharedOptions);
471
527
  } catch (error) {
472
528
  if (!PERMISSION_ERRORS.has(error.code)) throw error;
473
529
  log(`cannot replace ${vlinkPath} (${error.code}); falling back to ${targetPath}`, output);
@@ -476,7 +532,9 @@ async function runInstaller(options = {}) {
476
532
  }
477
533
  installTarget = path.resolve(installTarget);
478
534
  log(`installation target: ${installTarget}`, output);
479
- const sourcePath = await ensureBinary({});
535
+ verboseLog('binary stage: resolving packaged or cached binary', sharedOptions);
536
+ const sourcePath = await ensureBinary({ verbose, output });
537
+ verboseLog(`binary stage: source=${sourcePath}`, sharedOptions);
480
538
  try {
481
539
  installBinary(sourcePath, installTarget, {
482
540
  ...sharedOptions,
@@ -500,18 +558,24 @@ async function runInstaller(options = {}) {
500
558
  ensureBinary
501
559
  });
502
560
  const commandShimPath = installWindowsCommandShim(vlinkPath, sharedOptions);
561
+ verboseLog(
562
+ `shim stage: ${commandShimPath || 'not required on this platform'}`,
563
+ sharedOptions
564
+ );
503
565
  installShellCompletion(vlinkPath, sharedOptions);
504
566
 
505
567
  if (skipSetup) {
506
568
  log('skipping TedBrain Agent setup', output);
507
569
  } else {
508
570
  log('starting TedBrain Agent setup', output);
571
+ verboseLog(`setup stage: executing ${vlinkPath} setup tedbrain`, sharedOptions);
509
572
  const setup = spawnSyncImpl(vlinkPath, ['setup', 'tedbrain'], {
510
573
  env,
511
574
  stdio: 'inherit'
512
575
  });
513
576
  const failure = commandFailure(setup, 'vlink setup tedbrain');
514
577
  if (failure) throw new VlinkSetupError(failure);
578
+ verboseLog('setup stage: completed successfully', sharedOptions);
515
579
  }
516
580
 
517
581
  log(`installation complete: ${vlinkPath}`, output);
@@ -522,11 +586,12 @@ async function runInstaller(options = {}) {
522
586
  function printInstallHelp(output = process.stdout) {
523
587
  output.write(
524
588
  [
525
- 'Usage: npx --registry=https://registry.npmmirror.com -y @tedbrain/vlink@latest install [--no-setup]',
589
+ 'Usage: npx --registry=https://registry.npmmirror.com -y @tedbrain/vlink@latest install [--no-setup] [--verbose]',
526
590
  '',
527
591
  'Installs or updates vlink and adds it to the user PATH.',
528
592
  '',
529
- ' --no-setup Install without starting the TedBrain setup wizard.'
593
+ ' --no-setup Install without starting the TedBrain setup wizard.',
594
+ ' --verbose Print diagnostics for every installation stage.'
530
595
  ].join('\n') + '\n'
531
596
  );
532
597
  }
@@ -538,18 +603,23 @@ async function runInstallerFromArgs(args, options = {}) {
538
603
  }
539
604
 
540
605
  let skipSetup = false;
606
+ let verbose = verboseEnabled(options);
541
607
  for (const arg of args) {
542
608
  if (arg === '--no-setup') {
543
609
  skipSetup = true;
610
+ } else if (arg === '--verbose') {
611
+ verbose = true;
544
612
  } else {
545
- throw new VlinkSetupError(`unknown install option: ${arg}`);
613
+ const displayArg = arg.startsWith('--') ? arg.split('=', 1)[0] : '<argument>';
614
+ throw new VlinkSetupError(`unknown install option: ${displayArg}`);
546
615
  }
547
616
  }
548
- return runInstaller({ ...options, skipSetup });
617
+ return runInstaller({ ...options, skipSetup, verbose });
549
618
  }
550
619
 
551
620
  module.exports = {
552
621
  VlinkSetupError,
622
+ verboseEnabled,
553
623
  defaultInstallDir,
554
624
  installPathFor,
555
625
  isExecutableFile,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tedbrain/vlink",
3
- "version": "0.1.63",
3
+ "version": "0.1.64",
4
4
  "description": "vlink Virtuoso bridge CLI installer. Installs the matching platform binary at npm install time, like Claude Code / Codex.",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {