@tedbrain/vlink 0.1.83 → 0.1.85
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 +32 -0
- package/bin/vlink.js +6 -0
- package/binaries.json +8 -8
- package/mcp-installer.js +154 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -77,6 +77,38 @@ npm exec --registry=https://registry.npmmirror.com \
|
|
|
77
77
|
npx --registry=https://registry.npmmirror.com -y @tedbrain/vlink@latest install --no-setup
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
## 安装 TedBrain MCP
|
|
81
|
+
|
|
82
|
+
已安装原生 vlink 后,可以通过现有 npm 包注册 MCP:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npx -y @tedbrain/vlink@latest mcp install
|
|
86
|
+
npx -y @tedbrain/vlink@latest mcp install claude
|
|
87
|
+
npx -y @tedbrain/vlink@latest mcp install codex
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
省略 Agent 时使用 vlink 的交互多选;也支持透传 `--proxy-url` 等原生安装参数。
|
|
91
|
+
入口查找 PATH 中的原生 vlink,再检查用户安装目录(支持 `VLINK_INSTALL_DIR`),
|
|
92
|
+
不使用 npm 缓存二进制注册 MCP。缺少原生 vlink 时会提示先执行
|
|
93
|
+
`npx -y @tedbrain/vlink@latest install --no-setup`。
|
|
94
|
+
|
|
95
|
+
注册前使用 `vlink update --json` 查询运行时最新正式 release,并要求本机版本
|
|
96
|
+
不低于该 release。npm 的 `@latest` 标签或包版本不能代替这次检查。
|
|
97
|
+
版本过旧时显示当前版本、最新版本和程序路径,说明升级可能停止 daemon、
|
|
98
|
+
回退到用户安装目录,再询问 `是否升级 vlink 并继续安装 MCP?[y/N]`。
|
|
99
|
+
只有明确输入 `y` 或 `yes` 才会执行 `vlink update --apply --json`;
|
|
100
|
+
随后重新检查升级后的程序,验证通过才调用其绝对路径执行 `mcp install`。
|
|
101
|
+
版本已达标时直接注册,不降级较新版本。
|
|
102
|
+
|
|
103
|
+
拒绝升级、无法确认 release、版本检查被跳过、升级失败或 Windows 后台替换
|
|
104
|
+
尚未完成时停止,不注册 MCP。需要升级的非交互环境也会停止,提示手动运行
|
|
105
|
+
`vlink update --apply` 后重试;`npx -y` 只确认 npm 包获取,不代表同意升级 vlink。
|
|
106
|
+
`mcp install --help` 直接显示原生帮助,不查询 release 或触发升级。
|
|
107
|
+
|
|
108
|
+
Agent 最终启动的是持久安装的 `vlink mcp run`,不依赖 npx 缓存。
|
|
109
|
+
注册项及安装记录仍由 vlink 管理,卸载使用 `vlink mcp uninstall codex`
|
|
110
|
+
或 `vlink mcp uninstall claude`。此入口不修改模型供应商配置。
|
|
111
|
+
|
|
80
112
|
## 从旧版全局 npm 安装迁移
|
|
81
113
|
|
|
82
114
|
旧版 npm 包会注册 `vlink`、`vlink.cmd`、`vlink.ps1` 入口。新版改为由 npm
|
package/bin/vlink.js
CHANGED
|
@@ -35,6 +35,12 @@ async function main() {
|
|
|
35
35
|
verbose(`argv=${JSON.stringify(diagnosticArgs(args))}`);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
if (args[0] === 'mcp' && args[1] === 'install') {
|
|
39
|
+
const { runMcpInstaller } = require('../mcp-installer');
|
|
40
|
+
await runMcpInstaller(args.slice(2));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
38
44
|
const { ensureBinary, VERSION } = require('../install');
|
|
39
45
|
const { runInstallerFromArgs } = require('../installer');
|
|
40
46
|
if (diagnostic) verbose(`package version=${VERSION}`);
|
package/binaries.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.1.
|
|
2
|
+
"version": "0.1.85",
|
|
3
3
|
"binaries": {
|
|
4
|
-
"linux-x86_64": "
|
|
5
|
-
"linux-x86_64-glibc2.17": "
|
|
6
|
-
"linux-aarch64": "
|
|
7
|
-
"macos-x86_64": "
|
|
8
|
-
"macos-aarch64": "
|
|
9
|
-
"windows-x86_64.exe": "
|
|
10
|
-
"windows-aarch64.exe": "
|
|
4
|
+
"linux-x86_64": "9667555f9cf3a2ecef6be8d562ebf15a1d28fb6f5cfd99f7531ca04e301cf2a0",
|
|
5
|
+
"linux-x86_64-glibc2.17": "ab7b76dde34b52d6ce92e97784f0213906d600fbd172112fe3f5385ea5443dc5",
|
|
6
|
+
"linux-aarch64": "35eadcb1bfca715cdf49f2c1b107b75ccac384e55447b0a0b55edd254caba451",
|
|
7
|
+
"macos-x86_64": "d8d4765aa2c1be19d0888ca02526063bbc370c612e8f6c056e3aef073171de39",
|
|
8
|
+
"macos-aarch64": "ce07019e0ee0c2d98193f85017eebd22d14df2e1d98f47aae1eb3583fce59ca1",
|
|
9
|
+
"windows-x86_64.exe": "64177f28f47ef23b2fac1871e1225834f9a44cffc6c7848435ad222e0127e149",
|
|
10
|
+
"windows-aarch64.exe": "7c24504d79a0440cdb42ae8c815645f8e65750a60abc0002201af431b3b28059"
|
|
11
11
|
}
|
|
12
12
|
}
|
package/mcp-installer.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
const {
|
|
7
|
+
defaultInstallDir, findOnPath, isExecutableFile, isVlinkWrapper, VlinkSetupError
|
|
8
|
+
} = require('./installer');
|
|
9
|
+
|
|
10
|
+
const RETRY_HINT = '请手动运行 vlink update --apply,完成后重新执行 MCP 安装。';
|
|
11
|
+
|
|
12
|
+
function parseVersion(value) {
|
|
13
|
+
const match = typeof value === 'string' && value.match(
|
|
14
|
+
/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/
|
|
15
|
+
);
|
|
16
|
+
if (!match) throw new VlinkSetupError('无法确认有效的 vlink 版本,未安装 MCP。');
|
|
17
|
+
const prerelease = match[4] ? match[4].split('.') : [];
|
|
18
|
+
if (prerelease.some((part) => /^0\d+$/.test(part))) {
|
|
19
|
+
throw new VlinkSetupError('无法确认有效的 vlink 预发布版本,未安装 MCP。');
|
|
20
|
+
}
|
|
21
|
+
return { core: match.slice(1, 4).map(BigInt), prerelease };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function compareVersions(left, right) {
|
|
25
|
+
const a = parseVersion(left);
|
|
26
|
+
const b = parseVersion(right);
|
|
27
|
+
for (let i = 0; i < 3; i += 1) {
|
|
28
|
+
if (a.core[i] !== b.core[i]) return a.core[i] > b.core[i] ? 1 : -1;
|
|
29
|
+
}
|
|
30
|
+
if (!a.prerelease.length || !b.prerelease.length) {
|
|
31
|
+
return Number(!a.prerelease.length) - Number(!b.prerelease.length);
|
|
32
|
+
}
|
|
33
|
+
for (let i = 0; i < Math.max(a.prerelease.length, b.prerelease.length); i += 1) {
|
|
34
|
+
const x = a.prerelease[i];
|
|
35
|
+
const y = b.prerelease[i];
|
|
36
|
+
if (x === y) continue;
|
|
37
|
+
if (x === undefined || y === undefined) return x === undefined ? -1 : 1;
|
|
38
|
+
const nx = /^\d+$/.test(x);
|
|
39
|
+
const ny = /^\d+$/.test(y);
|
|
40
|
+
if (nx !== ny) return nx ? -1 : 1;
|
|
41
|
+
return (nx ? BigInt(x) > BigInt(y) : x > y) ? 1 : -1;
|
|
42
|
+
}
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function confirmUpgrade(input, output) {
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
const reader = readline.createInterface({ input, output, terminal: true });
|
|
49
|
+
reader.once('close', () => resolve(false));
|
|
50
|
+
reader.once('SIGINT', () => reader.close());
|
|
51
|
+
reader.question('是否升级 vlink 并继续安装 MCP?[y/N] ', (answer) => {
|
|
52
|
+
resolve(/^(y|yes)$/i.test(answer.trim()));
|
|
53
|
+
reader.close();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function runMcpInstaller(args, options = {}) {
|
|
59
|
+
const env = options.env || process.env;
|
|
60
|
+
const platform = options.platform || process.platform;
|
|
61
|
+
const input = options.input || process.stdin;
|
|
62
|
+
const output = options.output || process.stderr;
|
|
63
|
+
const run = options.spawnSync || spawnSync;
|
|
64
|
+
const binaryName = platform === 'win32' ? 'vlink.exe' : 'vlink';
|
|
65
|
+
const discovery = { env, platform };
|
|
66
|
+
const fallback = path.join(defaultInstallDir({ platform }, env, options.home), binaryName);
|
|
67
|
+
let binary = findOnPath(binaryName, discovery);
|
|
68
|
+
if (!binary && isExecutableFile(fallback, platform) && !isVlinkWrapper(fallback)) {
|
|
69
|
+
binary = fallback;
|
|
70
|
+
}
|
|
71
|
+
if (!binary) {
|
|
72
|
+
throw new VlinkSetupError(
|
|
73
|
+
'未找到已安装的原生 vlink。请先运行 npx -y @tedbrain/vlink@latest install --no-setup。'
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
binary = path.resolve(binary);
|
|
77
|
+
|
|
78
|
+
function execute(command, commandArgs, capture = true) {
|
|
79
|
+
const result = run(command, commandArgs, {
|
|
80
|
+
env,
|
|
81
|
+
encoding: 'utf8',
|
|
82
|
+
stdio: capture ? ['ignore', 'pipe', 'pipe'] : 'inherit'
|
|
83
|
+
});
|
|
84
|
+
if (capture && result.stderr) output.write(result.stderr);
|
|
85
|
+
if (result.error || result.status !== 0) {
|
|
86
|
+
throw new VlinkSetupError(
|
|
87
|
+
`vlink ${commandArgs[0]} 失败(${result.error?.message || result.signal || result.status})。` +
|
|
88
|
+
(commandArgs[0] === 'update' ? RETRY_HINT : '请检查原生 vlink 的错误信息后重试。')
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return result.stdout;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function updateReport(command, apply = false) {
|
|
95
|
+
const raw = execute(command, ['update', ...(apply ? ['--apply'] : []), '--json']);
|
|
96
|
+
let report;
|
|
97
|
+
try {
|
|
98
|
+
report = JSON.parse(raw);
|
|
99
|
+
} catch {
|
|
100
|
+
throw new VlinkSetupError(`vlink update 未返回有效 JSON。${RETRY_HINT}`);
|
|
101
|
+
}
|
|
102
|
+
if (!report || report.ok !== true || typeof report.update_required !== 'boolean') {
|
|
103
|
+
throw new VlinkSetupError(`vlink update 未返回成功的版本检查结果。${RETRY_HINT}`);
|
|
104
|
+
}
|
|
105
|
+
// A skipped check or a build-date guard is not proof of the minimum release version.
|
|
106
|
+
parseVersion(report.local_version);
|
|
107
|
+
parseVersion(report.latest_version);
|
|
108
|
+
if (parseVersion(report.latest_version).prerelease.length) {
|
|
109
|
+
throw new VlinkSetupError(`未获取到最新正式 release。${RETRY_HINT}`);
|
|
110
|
+
}
|
|
111
|
+
return report;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Help must remain read-only and work without a release lookup.
|
|
115
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
116
|
+
execute(binary, ['mcp', 'install', ...args], false);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const checked = updateReport(binary);
|
|
121
|
+
output.write(`[TedBrain] vlink: ${binary}\n当前版本: ${checked.local_version}\n最新 release: ${checked.latest_version}\n`);
|
|
122
|
+
if (compareVersions(checked.local_version, checked.latest_version) < 0) {
|
|
123
|
+
if (!checked.update_required) {
|
|
124
|
+
throw new VlinkSetupError(`当前版本低于最新 release,但更新被跳过:${checked.skip_reason || '原因未知'}。${RETRY_HINT}`);
|
|
125
|
+
}
|
|
126
|
+
output.write('继续将升级上述 vlink;原生更新器可能回退到用户安装目录,并停止现有 daemon。\n');
|
|
127
|
+
if (!input.isTTY || !output.isTTY) {
|
|
128
|
+
throw new VlinkSetupError(`非交互环境无法确认升级,未安装 MCP。${RETRY_HINT}`);
|
|
129
|
+
}
|
|
130
|
+
const confirmed = await (options.confirmUpgrade || confirmUpgrade)(input, output);
|
|
131
|
+
if (!confirmed) throw new VlinkSetupError(`已取消升级,未安装 MCP。${RETRY_HINT}`);
|
|
132
|
+
output.write('[TedBrain] 正在升级 vlink…\n');
|
|
133
|
+
const applied = updateReport(binary, true);
|
|
134
|
+
if (applied.update_scheduled) {
|
|
135
|
+
throw new VlinkSetupError('vlink 更新已交给后台程序,尚未确认替换完成,未安装 MCP。请完成更新后重新执行 MCP 安装。');
|
|
136
|
+
}
|
|
137
|
+
if (applied.applied !== true || typeof applied.installed_path !== 'string' ||
|
|
138
|
+
!path.isAbsolute(applied.installed_path) ||
|
|
139
|
+
!isExecutableFile(applied.installed_path, platform) || isVlinkWrapper(applied.installed_path)) {
|
|
140
|
+
throw new VlinkSetupError(`vlink 升级未返回有效的已安装程序路径,未安装 MCP。${RETRY_HINT}`);
|
|
141
|
+
}
|
|
142
|
+
binary = applied.installed_path;
|
|
143
|
+
const verified = updateReport(binary);
|
|
144
|
+
if (compareVersions(verified.local_version, checked.latest_version) < 0 ||
|
|
145
|
+
compareVersions(verified.local_version, applied.latest_version) < 0 ||
|
|
146
|
+
compareVersions(verified.local_version, verified.latest_version) < 0) {
|
|
147
|
+
throw new VlinkSetupError(`升级后的 vlink 仍低于最新 release,未安装 MCP。${RETRY_HINT}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
output.write(`[TedBrain] 使用 ${binary} 安装 MCP\n`);
|
|
151
|
+
execute(binary, ['mcp', 'install', ...args], false);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = { runMcpInstaller, confirmUpgrade };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tedbrain/vlink",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.85",
|
|
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": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"bin/",
|
|
11
11
|
"install.js",
|
|
12
12
|
"installer.js",
|
|
13
|
+
"mcp-installer.js",
|
|
13
14
|
"npm-shims.js",
|
|
14
15
|
"binaries.json",
|
|
15
16
|
"README.md"
|