@tedbrain/vlink 0.1.61 → 0.1.63

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
@@ -9,7 +9,14 @@ npx --registry=https://registry.npmmirror.com -y @tedbrain/vlink@latest install
9
9
  ```
10
10
 
11
11
  该命令会识别平台和架构,始终安装与当前 npm 包版本一致的 vlink,配置用户
12
- PATH,并启动 `vlink setup tedbrain`。安装完成后可直接运行:
12
+ PATH,并启动 `vlink setup tedbrain`。如果 PATH 中已有原生 vlink,会优先更新该位置;
13
+ 旧文件或目录不可写时,自动回退到用户安装目录(Unix 为 `~/.local/bin`,Windows 为
14
+ `%LOCALAPPDATA%\TedBrain\bin`),保留原来的共享安装。显式设置 `VLINK_INSTALL_DIR`
15
+ 时始终使用该目录,写入失败会报错,不会再改装到其他位置。
16
+
17
+ 安装目录会优先加入当前安装进程的 PATH,补全和 setup 均使用新二进制的绝对路径。
18
+ 默认目录还会写入 shell 配置或 Windows 用户 PATH;自定义 Unix 目录需要自行配置
19
+ 永久 PATH。完成后请打开新终端,让 PATH 和 shell 命令缓存更新,再运行:
13
20
 
14
21
  ```bash
15
22
  vlink --version
@@ -28,6 +35,8 @@ Windows 会在 `vlink.exe` 旁同时生成 `vlink.cmd`。安装目录加入用
28
35
  npx --registry=https://registry.npmmirror.com -y @tedbrain/vlink@latest --version
29
36
  ```
30
37
 
38
+ 安装命令会输出安装目标、权限回退原因及 setup 状态;失败时输出错误并返回非零退出码。
39
+
31
40
  手动配置 Agent 时可以只安装或更新 vlink,并跳过 TedBrain 配置向导:
32
41
 
33
42
  ```bash
package/binaries.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
- "version": "0.1.61",
2
+ "version": "0.1.63",
3
3
  "binaries": {
4
- "linux-x86_64": "2aa860b80fa2a18da6671e83f98e6e3dc4ab3f1849fc9c62f37db749537270c9",
5
- "linux-x86_64-glibc2.17": "dbd7c8825781de13a680657833ac5a7a22f8ab6ae218377989acecc6c5e2da89",
6
- "linux-aarch64": "fbf0f330163139acfcd0d78bc6d8496230cc9887655d018d4acfa9df6d1109b4",
7
- "macos-x86_64": "6718b75c74f04a1f3a2208977811cbd8e6a8a4b406c56c815ec66669d41532f8",
8
- "macos-aarch64": "4504b0c3e90e405d8d0188bab0057246cf34bb12b803761a9f44270e88c76a87",
9
- "windows-x86_64.exe": "296dd61a57e8a7428627b07987747cdd86a2042ea9be968773437fd445a9dd48",
10
- "windows-aarch64.exe": "ad6f40383f44c09252f4e98d4fba22929315b0b1b0a05e469c5d3ab9b361e207"
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"
11
11
  }
12
12
  }
package/installer.js CHANGED
@@ -8,6 +8,8 @@ const { spawnSync } = require('child_process');
8
8
 
9
9
  const installModule = require('./install');
10
10
 
11
+ const PERMISSION_ERRORS = new Set(['EACCES', 'EPERM', 'EROFS']);
12
+
11
13
  const CURRENT_WRAPPER = path.resolve(__dirname, 'bin', 'vlink.js');
12
14
 
13
15
  class VlinkSetupError extends Error {}
@@ -110,8 +112,8 @@ function prependEnvironmentPath(directory, env, platform) {
110
112
  const entries = String(current)
111
113
  .split(delimiter)
112
114
  .filter(Boolean);
113
- const alreadyPresent = entries.some((entry) => entry === directory);
114
- const next = alreadyPresent ? entries.join(delimiter) : [directory, ...entries].join(delimiter);
115
+ const next = [directory, ...entries.filter((entry) => !samePath(entry, directory, platform))]
116
+ .join(delimiter);
115
117
 
116
118
  for (const key of Object.keys(env)) {
117
119
  if (key.toLowerCase() === 'path') delete env[key];
@@ -137,8 +139,9 @@ function updateWindowsUserPath(directory, options) {
137
139
  "$entry = $env:TEDBRAIN_INSTALL_DIR.TrimEnd('\\')",
138
140
  "$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')",
139
141
  "$entries = @($userPath -split ';' | Where-Object { $_ })",
140
- "if (-not ($entries | Where-Object { $_.TrimEnd('\\') -ieq $entry })) {",
141
- " $newPath = (@($env:TEDBRAIN_INSTALL_DIR) + $entries) -join ';'",
142
+ "$remaining = @($entries | Where-Object { $_.TrimEnd('\\') -ine $entry })",
143
+ "$newPath = (@($env:TEDBRAIN_INSTALL_DIR) + $remaining) -join ';'",
144
+ "if ($newPath -cne $userPath) {",
142
145
  " [Environment]::SetEnvironmentVariable('Path', $newPath, 'User')",
143
146
  " Write-Output 'changed'",
144
147
  "}"
@@ -293,7 +296,7 @@ function installBinary(sourcePath, targetPath, options = {}) {
293
296
 
294
297
  if (options.stopDaemon) {
295
298
  log('stopping the existing vlink daemon before replacement', output);
296
- const stop = spawnSyncImpl(sourcePath, ['daemon', 'stop'], { stdio: 'inherit' });
299
+ const stop = spawnSyncImpl(sourcePath, ['daemon', 'stop'], { stdio: 'inherit', env: options.env });
297
300
  const failure = commandFailure(stop, 'vlink daemon stop');
298
301
  if (failure) throw new VlinkSetupError(failure);
299
302
  }
@@ -311,7 +314,9 @@ function installBinary(sourcePath, targetPath, options = {}) {
311
314
  fsImpl.renameSync(temporary, targetPath);
312
315
  } catch (error) {
313
316
  fsImpl.rmSync(temporary, { force: true });
314
- throw new VlinkSetupError(`failed to install vlink at ${targetPath}: ${error.message}`);
317
+ const failure = new VlinkSetupError(`failed to install vlink at ${targetPath}: ${error.message}`);
318
+ failure.code = error.code;
319
+ throw failure;
315
320
  }
316
321
  fsImpl.rmSync(temporary, { force: true });
317
322
  log(`installed vlink at ${targetPath}`, output);
@@ -364,6 +369,7 @@ function installShellCompletion(vlinkPath, options = {}) {
364
369
  vlinkPath,
365
370
  ['completion', '--install', '--quiet'],
366
371
  {
372
+ env: options.env,
367
373
  encoding: 'utf8',
368
374
  stdio: ['ignore', 'pipe', 'pipe']
369
375
  }
@@ -423,7 +429,7 @@ async function runInstaller(options = {}) {
423
429
  : env.VLINK_GLIBC_VERSION,
424
430
  spawnSync: spawnSyncImpl
425
431
  });
426
- const installDir = defaultInstallDir(spec, env, home);
432
+ const installDir = path.resolve(defaultInstallDir(spec, env, home));
427
433
  const targetPath = installPathFor(spec, installDir);
428
434
  const sharedOptions = {
429
435
  env,
@@ -447,7 +453,6 @@ async function runInstaller(options = {}) {
447
453
  );
448
454
  } else if (isExecutableFile(targetPath, platform, fsImpl)) {
449
455
  vlinkPath = targetPath;
450
- ensureUserPath(installDir, sharedOptions);
451
456
  log(
452
457
  `found vlink at ${vlinkPath}; installing vlink ${installModule.VERSION}`,
453
458
  output
@@ -457,16 +462,35 @@ async function runInstaller(options = {}) {
457
462
  }
458
463
 
459
464
  const replacingExisting = Boolean(vlinkPath);
460
- const installTarget = vlinkPath || targetPath;
465
+ let installTarget = env.VLINK_INSTALL_DIR ? targetPath : (vlinkPath || targetPath);
466
+ // Avoid trying to replace a shared installation, even if its parent is writable.
467
+ if (vlinkPath && !env.VLINK_INSTALL_DIR && !samePath(vlinkPath, targetPath, platform)) {
468
+ try {
469
+ fsImpl.accessSync(vlinkPath, fs.constants.W_OK);
470
+ fsImpl.accessSync(path.dirname(vlinkPath), fs.constants.W_OK);
471
+ } catch (error) {
472
+ if (!PERMISSION_ERRORS.has(error.code)) throw error;
473
+ log(`cannot replace ${vlinkPath} (${error.code}); falling back to ${targetPath}`, output);
474
+ installTarget = targetPath;
475
+ }
476
+ }
477
+ installTarget = path.resolve(installTarget);
478
+ log(`installation target: ${installTarget}`, output);
461
479
  const sourcePath = await ensureBinary({});
462
- installBinary(sourcePath, installTarget, {
463
- ...sharedOptions,
464
- stopDaemon: replacingExisting
465
- });
466
- if (
467
- !replacingExisting ||
468
- samePath(path.dirname(installTarget), installDir, platform)
469
- ) {
480
+ try {
481
+ installBinary(sourcePath, installTarget, {
482
+ ...sharedOptions,
483
+ stopDaemon: replacingExisting
484
+ });
485
+ } catch (error) {
486
+ // Permissions may change after the access check, or an ACL can deny rename.
487
+ if (env.VLINK_INSTALL_DIR || samePath(installTarget, targetPath, platform) ||
488
+ !PERMISSION_ERRORS.has(error.code)) throw error;
489
+ log(`cannot replace ${installTarget} (${error.code}); falling back to ${targetPath}`, output);
490
+ installTarget = targetPath;
491
+ installBinary(sourcePath, installTarget, sharedOptions);
492
+ }
493
+ if (samePath(path.dirname(installTarget), installDir, platform)) {
470
494
  ensureUserPath(installDir, sharedOptions);
471
495
  }
472
496
  vlinkPath = installTarget;
@@ -483,12 +507,15 @@ async function runInstaller(options = {}) {
483
507
  } else {
484
508
  log('starting TedBrain Agent setup', output);
485
509
  const setup = spawnSyncImpl(vlinkPath, ['setup', 'tedbrain'], {
510
+ env,
486
511
  stdio: 'inherit'
487
512
  });
488
513
  const failure = commandFailure(setup, 'vlink setup tedbrain');
489
514
  if (failure) throw new VlinkSetupError(failure);
490
515
  }
491
516
 
517
+ log(`installation complete: ${vlinkPath}`, output);
518
+ log('open a new terminal to use the updated PATH', output);
492
519
  return { installDir, vlinkPath, commandShimPath, setupSkipped: skipSetup };
493
520
  }
494
521
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tedbrain/vlink",
3
- "version": "0.1.61",
3
+ "version": "0.1.63",
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": {