shizai-agent-mcp 0.2.6 → 0.2.8
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 +13 -10
- package/bin/shizai-agent-mcp.js +203 -198
- package/package.json +23 -23
package/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# shizai-agent-mcp
|
|
2
2
|
|
|
3
|
-
实在 Agent 本地 MCP 的轻量 npm/npx 安装器。它只安装和注册 Shizai MCP,浏览器识别与控制复用实在 Agent 客户端自带的 Chrome 扩展和 DeepCapture。
|
|
3
|
+
实在 Agent 本地 MCP 的轻量 npm/npx 安装器。它只安装和注册 Shizai MCP,浏览器识别与控制复用实在 Agent 客户端自带的 Chrome 扩展和 DeepCapture。
|
|
4
4
|
|
|
5
5
|
## 使用
|
|
6
6
|
|
|
7
7
|
```powershell
|
|
8
8
|
npx -y shizai-agent-mcp install
|
|
9
|
-
npx -y shizai-agent-mcp register codex
|
|
9
|
+
npx -y shizai-agent-mcp register codex
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
也可以注册 Cursor 或 Claude Desktop:
|
|
13
13
|
|
|
14
14
|
```powershell
|
|
15
|
-
npx -y shizai-agent-mcp register cursor
|
|
16
|
-
npx -y shizai-agent-mcp register claude
|
|
15
|
+
npx -y shizai-agent-mcp register cursor
|
|
16
|
+
npx -y shizai-agent-mcp register claude
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
常用命令:
|
|
@@ -24,13 +24,16 @@ npx -y shizai-agent-mcp path
|
|
|
24
24
|
npx -y shizai-agent-mcp uninstall
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
当前 npm launcher 版本为 `0.2.
|
|
28
|
-
`shizai-agent-v0.6.
|
|
27
|
+
当前 npm launcher 版本为 `0.2.8`,默认下载最新的 `v0.6.7` Windows x64 资产:
|
|
28
|
+
`shizai-agent-v0.6.7-windows-x64.zip`。npm 包不附带浏览器自动化依赖,也不要求用户安装额外扩展。
|
|
29
|
+
|
|
30
|
+
`install` 覆盖文件后会自动注册 Codex MCP;需要跳过配置写入时使用
|
|
31
|
+
`install --skip-register`,之后可单独运行 `register codex`。
|
|
29
32
|
|
|
30
|
-
发布 Release 时必须同时上传同名 `.sha256` 文件。安装器先校验、解压到同盘暂存目录,
|
|
31
|
-
再原子替换现有版本;失败会恢复旧安装。测试企业内网镜像时可设置
|
|
32
|
-
`SHIZAI_AGENT_RELEASE_BASE_URL`,测试其他版本可设置 `SHIZAI_AGENT_VERSION`。
|
|
33
|
-
仅临时调试未签名镜像时可显式设置 `SHIZAI_AGENT_ALLOW_UNVERIFIED=1`。
|
|
33
|
+
发布 Release 时必须同时上传同名 `.sha256` 文件。安装器先校验、解压到同盘暂存目录,
|
|
34
|
+
再原子替换现有版本;失败会恢复旧安装。测试企业内网镜像时可设置
|
|
35
|
+
`SHIZAI_AGENT_RELEASE_BASE_URL`,测试其他版本可设置 `SHIZAI_AGENT_VERSION`。
|
|
36
|
+
仅临时调试未签名镜像时可显式设置 `SHIZAI_AGENT_ALLOW_UNVERIFIED=1`。
|
|
34
37
|
|
|
35
38
|
本地设计器需要正在运行且已登录。MCP 是本地 stdio 服务,Codex 显示 `Auth: Unsupported`
|
|
36
39
|
属于正常状态,实在 Agent 的登录会话由桌面设计器提供。
|
package/bin/shizai-agent-mcp.js
CHANGED
|
@@ -7,97 +7,97 @@ const os = require('node:os');
|
|
|
7
7
|
const crypto = require('node:crypto');
|
|
8
8
|
const { execFileSync, spawn } = require('node:child_process');
|
|
9
9
|
|
|
10
|
-
const SERVER = 'shizai-agent';
|
|
11
|
-
const REPO = process.env.SHIZAI_AGENT_REPO || 'yimoxzy/shizai-agent';
|
|
12
|
-
const DEFAULT_VERSION = process.env.SHIZAI_AGENT_VERSION || '0.6.
|
|
10
|
+
const SERVER = 'shizai-agent';
|
|
11
|
+
const REPO = process.env.SHIZAI_AGENT_REPO || 'yimoxzy/shizai-agent';
|
|
12
|
+
const DEFAULT_VERSION = process.env.SHIZAI_AGENT_VERSION || '0.6.7';
|
|
13
13
|
const installRoot = process.env.SHIZAI_AGENT_HOME || path.join(
|
|
14
14
|
process.env.LOCALAPPDATA || path.join(os.homedir(), '.local'), 'Programs', 'ShizaiAgent'
|
|
15
15
|
);
|
|
16
16
|
|
|
17
17
|
function fail(message) { console.error(`shizai-agent-mcp: ${message}`); process.exitCode = 1; }
|
|
18
18
|
function json(value) { console.log(JSON.stringify(value, null, 2)); }
|
|
19
|
-
function help() {
|
|
20
|
-
console.log(`Shizai Agent MCP installer\n\nUsage:\n shizai-agent-mcp install [--version VERSION]\n shizai-agent-mcp register <codex|cursor|claude>\n shizai-agent-mcp status\n shizai-agent-mcp path\n shizai-agent-mcp uninstall\n\nEnvironment:\n SHIZAI_AGENT_VERSION Release version (default: ${DEFAULT_VERSION})\n SHIZAI_AGENT_HOME Installation directory\n SHIZAI_AGENT_REPO GitHub repository owner/name\n SHIZAI_AGENT_RELEASE_BASE_URL Override release asset base URL\n`);
|
|
19
|
+
function help() {
|
|
20
|
+
console.log(`Shizai Agent MCP installer\n\nUsage:\n shizai-agent-mcp install [--version VERSION]\n shizai-agent-mcp register <codex|cursor|claude>\n shizai-agent-mcp status\n shizai-agent-mcp path\n shizai-agent-mcp uninstall\n\nEnvironment:\n SHIZAI_AGENT_VERSION Release version (default: ${DEFAULT_VERSION})\n SHIZAI_AGENT_HOME Installation directory\n SHIZAI_AGENT_REPO GitHub repository owner/name\n SHIZAI_AGENT_RELEASE_BASE_URL Override release asset base URL\n`);
|
|
21
|
+
}
|
|
22
|
+
function binaryPath() { return path.join(installRoot, 'shizai-agent-mcp.exe'); }
|
|
23
|
+
function validateInstallRoot() {
|
|
24
|
+
const resolved = path.resolve(installRoot);
|
|
25
|
+
const parsed = path.parse(resolved);
|
|
26
|
+
if (resolved === parsed.root || resolved === path.resolve(os.homedir())) {
|
|
27
|
+
throw new Error(`拒绝使用过宽的安装目录:${resolved}`);
|
|
28
|
+
}
|
|
29
|
+
return resolved;
|
|
21
30
|
}
|
|
22
|
-
function binaryPath() { return path.join(installRoot, 'shizai-agent-mcp.exe'); }
|
|
23
|
-
function validateInstallRoot() {
|
|
24
|
-
const resolved = path.resolve(installRoot);
|
|
25
|
-
const parsed = path.parse(resolved);
|
|
26
|
-
if (resolved === parsed.root || resolved === path.resolve(os.homedir())) {
|
|
27
|
-
throw new Error(`拒绝使用过宽的安装目录:${resolved}`);
|
|
28
|
-
}
|
|
29
|
-
return resolved;
|
|
30
|
-
}
|
|
31
31
|
function psQuote(value) { return `'${String(value).replaceAll("'", "''")}'`; }
|
|
32
|
-
function runPowerShell(script, args = []) {
|
|
33
|
-
return execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script, ...args], { encoding: 'utf8' });
|
|
34
|
-
}
|
|
35
|
-
function sleepSync(milliseconds) {
|
|
36
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
37
|
-
}
|
|
38
|
-
function removeDirectoryWithRetries(directory, attempts = 10) {
|
|
39
|
-
if (!fs.existsSync(directory)) return;
|
|
40
|
-
let lastError;
|
|
41
|
-
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
42
|
-
try {
|
|
43
|
-
fs.rmSync(directory, { recursive: true, force: true });
|
|
44
|
-
return;
|
|
45
|
-
} catch (error) {
|
|
46
|
-
lastError = error;
|
|
47
|
-
sleepSync(200 * (attempt + 1));
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
throw lastError;
|
|
51
|
-
}
|
|
52
|
-
function stopInstalledProcesses(destination) {
|
|
53
|
-
if (process.platform !== 'win32') return [];
|
|
54
|
-
const executables = [
|
|
55
|
-
path.join(destination, 'shizai-agent-mcp.exe'),
|
|
56
|
-
path.join(destination, 'shizai-agent.exe')
|
|
57
|
-
];
|
|
58
|
-
const targetList = executables.map(psQuote).join(', ');
|
|
59
|
-
const script = String.raw`
|
|
60
|
-
$ErrorActionPreference = 'Stop'
|
|
61
|
-
$targets = @(${targetList} | ForEach-Object { [IO.Path]::GetFullPath([string]$_).ToLowerInvariant() })
|
|
62
|
-
$matches = @(Get-CimInstance Win32_Process | Where-Object {
|
|
63
|
-
if (-not $_.ExecutablePath) { return $false }
|
|
64
|
-
try { $targets -contains [IO.Path]::GetFullPath([string]$_.ExecutablePath).ToLowerInvariant() } catch { $false }
|
|
65
|
-
})
|
|
66
|
-
$matchedIds = @($matches | ForEach-Object { [int]$_.ProcessId })
|
|
67
|
-
foreach ($process in $matches) {
|
|
68
|
-
Stop-Process -Id $process.ProcessId -Force -ErrorAction Stop
|
|
69
|
-
}
|
|
70
|
-
$deadline = [DateTime]::UtcNow.AddSeconds(5)
|
|
71
|
-
do {
|
|
72
|
-
$remainingIds = @()
|
|
73
|
-
foreach ($targetId in $matchedIds) {
|
|
74
|
-
if ($null -ne (Get-Process -Id $targetId -ErrorAction SilentlyContinue)) {
|
|
75
|
-
$remainingIds += $targetId
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if ($remainingIds.Count -eq 0) { break }
|
|
79
|
-
Start-Sleep -Milliseconds 100
|
|
80
|
-
} while ([DateTime]::UtcNow -lt $deadline)
|
|
81
|
-
if ($remainingIds.Count -gt 0) {
|
|
82
|
-
throw ('安装前无法停止实在 Agent 进程:' + (($remainingIds | Sort-Object) -join ', '))
|
|
83
|
-
}
|
|
84
|
-
@($matchedIds) | ConvertTo-Json -Compress
|
|
85
|
-
`;
|
|
86
|
-
const output = runPowerShell(script).trim();
|
|
87
|
-
if (!output) return [];
|
|
88
|
-
const parsed = JSON.parse(output);
|
|
89
|
-
return Array.isArray(parsed) ? parsed : [parsed];
|
|
90
|
-
}
|
|
91
|
-
function cleanupInstallArtifacts(parent, excluded = []) {
|
|
92
|
-
const excludedPaths = new Set(excluded.map(item => path.resolve(item).toLowerCase()));
|
|
93
|
-
for (const entry of fs.readdirSync(parent, { withFileTypes: true })) {
|
|
94
|
-
if (!entry.isDirectory()) continue;
|
|
95
|
-
if (!entry.name.startsWith('.shizai-agent-stage-') && !entry.name.startsWith('.shizai-agent-backup-')) continue;
|
|
96
|
-
const candidate = path.join(parent, entry.name);
|
|
97
|
-
if (excludedPaths.has(path.resolve(candidate).toLowerCase())) continue;
|
|
98
|
-
removeDirectoryWithRetries(candidate);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
32
|
+
function runPowerShell(script, args = []) {
|
|
33
|
+
return execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script, ...args], { encoding: 'utf8' });
|
|
34
|
+
}
|
|
35
|
+
function sleepSync(milliseconds) {
|
|
36
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
37
|
+
}
|
|
38
|
+
function removeDirectoryWithRetries(directory, attempts = 10) {
|
|
39
|
+
if (!fs.existsSync(directory)) return;
|
|
40
|
+
let lastError;
|
|
41
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
42
|
+
try {
|
|
43
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
44
|
+
return;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
lastError = error;
|
|
47
|
+
sleepSync(200 * (attempt + 1));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
throw lastError;
|
|
51
|
+
}
|
|
52
|
+
function stopInstalledProcesses(destination) {
|
|
53
|
+
if (process.platform !== 'win32') return [];
|
|
54
|
+
const executables = [
|
|
55
|
+
path.join(destination, 'shizai-agent-mcp.exe'),
|
|
56
|
+
path.join(destination, 'shizai-agent.exe')
|
|
57
|
+
];
|
|
58
|
+
const targetList = executables.map(psQuote).join(', ');
|
|
59
|
+
const script = String.raw`
|
|
60
|
+
$ErrorActionPreference = 'Stop'
|
|
61
|
+
$targets = @(${targetList} | ForEach-Object { [IO.Path]::GetFullPath([string]$_).ToLowerInvariant() })
|
|
62
|
+
$matches = @(Get-CimInstance Win32_Process | Where-Object {
|
|
63
|
+
if (-not $_.ExecutablePath) { return $false }
|
|
64
|
+
try { $targets -contains [IO.Path]::GetFullPath([string]$_.ExecutablePath).ToLowerInvariant() } catch { $false }
|
|
65
|
+
})
|
|
66
|
+
$matchedIds = @($matches | ForEach-Object { [int]$_.ProcessId })
|
|
67
|
+
foreach ($process in $matches) {
|
|
68
|
+
Stop-Process -Id $process.ProcessId -Force -ErrorAction Stop
|
|
69
|
+
}
|
|
70
|
+
$deadline = [DateTime]::UtcNow.AddSeconds(5)
|
|
71
|
+
do {
|
|
72
|
+
$remainingIds = @()
|
|
73
|
+
foreach ($targetId in $matchedIds) {
|
|
74
|
+
if ($null -ne (Get-Process -Id $targetId -ErrorAction SilentlyContinue)) {
|
|
75
|
+
$remainingIds += $targetId
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if ($remainingIds.Count -eq 0) { break }
|
|
79
|
+
Start-Sleep -Milliseconds 100
|
|
80
|
+
} while ([DateTime]::UtcNow -lt $deadline)
|
|
81
|
+
if ($remainingIds.Count -gt 0) {
|
|
82
|
+
throw ('安装前无法停止实在 Agent 进程:' + (($remainingIds | Sort-Object) -join ', '))
|
|
83
|
+
}
|
|
84
|
+
@($matchedIds) | ConvertTo-Json -Compress
|
|
85
|
+
`;
|
|
86
|
+
const output = runPowerShell(script).trim();
|
|
87
|
+
if (!output) return [];
|
|
88
|
+
const parsed = JSON.parse(output);
|
|
89
|
+
return Array.isArray(parsed) ? parsed : [parsed];
|
|
90
|
+
}
|
|
91
|
+
function cleanupInstallArtifacts(parent, excluded = []) {
|
|
92
|
+
const excludedPaths = new Set(excluded.map(item => path.resolve(item).toLowerCase()));
|
|
93
|
+
for (const entry of fs.readdirSync(parent, { withFileTypes: true })) {
|
|
94
|
+
if (!entry.isDirectory()) continue;
|
|
95
|
+
if (!entry.name.startsWith('.shizai-agent-stage-') && !entry.name.startsWith('.shizai-agent-backup-')) continue;
|
|
96
|
+
const candidate = path.join(parent, entry.name);
|
|
97
|
+
if (excludedPaths.has(path.resolve(candidate).toLowerCase())) continue;
|
|
98
|
+
removeDirectoryWithRetries(candidate);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
101
|
function assetName(version) {
|
|
102
102
|
if (process.platform !== 'win32' || process.arch !== 'x64') {
|
|
103
103
|
throw new Error(`当前 npm 安装器暂时只提供 Windows x64,检测到 ${process.platform}/${process.arch}`);
|
|
@@ -116,104 +116,109 @@ function download(url, destination) {
|
|
|
116
116
|
const script = `Invoke-WebRequest -UseBasicParsing -Uri ${psQuote(url)} -OutFile ${psQuote(destination)}`;
|
|
117
117
|
runPowerShell(script);
|
|
118
118
|
}
|
|
119
|
-
function install(version) {
|
|
120
|
-
const destination = validateInstallRoot();
|
|
121
|
-
const asset = assetName(version);
|
|
122
|
-
const url = releaseUrl(version, asset);
|
|
123
|
-
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'shizai-agent-'));
|
|
124
|
-
const archive = path.join(temp, asset);
|
|
125
|
-
const checksum = path.join(temp, `${asset}.sha256`);
|
|
126
|
-
const parent = path.dirname(destination);
|
|
127
|
-
const nonce = `${process.pid}-${Date.now()}`;
|
|
128
|
-
const stage = path.join(parent, `.shizai-agent-stage-${nonce}`);
|
|
129
|
-
const backup = path.join(parent, `.shizai-agent-backup-${nonce}`);
|
|
130
|
-
let verified = false;
|
|
131
|
-
const stoppedProcesses = new Set();
|
|
132
|
-
try {
|
|
133
|
-
console.log(`下载 ${url}`);
|
|
134
|
-
download(url, archive);
|
|
135
|
-
try {
|
|
136
|
-
download(`${url}.sha256`, checksum);
|
|
137
|
-
const text = fs.readFileSync(checksum, 'utf8');
|
|
138
|
-
const expected = (text.match(/[a-f0-9]{64}/i) || [])[0];
|
|
139
|
-
if (!expected) throw new Error('校验文件中没有 SHA-256');
|
|
140
|
-
verifySha256(archive, expected);
|
|
141
|
-
verified = true;
|
|
142
|
-
} catch (error) {
|
|
143
|
-
if (process.env.SHIZAI_AGENT_ALLOW_UNVERIFIED !== '1') throw error;
|
|
144
|
-
console.warn('SHIZAI_AGENT_ALLOW_UNVERIFIED=1:本次跳过发布包校验。');
|
|
145
|
-
}
|
|
146
|
-
fs.mkdirSync(parent, { recursive: true });
|
|
147
|
-
fs.mkdirSync(stage);
|
|
148
|
-
runPowerShell(`Expand-Archive -LiteralPath ${psQuote(archive)} -DestinationPath ${psQuote(stage)} -Force`);
|
|
149
|
-
let source = stage;
|
|
150
|
-
if (!fs.existsSync(path.join(source, 'shizai-agent-mcp.exe'))) {
|
|
151
|
-
const nested = fs.readdirSync(stage, { withFileTypes: true })
|
|
152
|
-
.filter(entry => entry.isDirectory())
|
|
153
|
-
.map(entry => path.join(stage, entry.name))
|
|
154
|
-
.find(candidate => fs.existsSync(path.join(candidate, 'shizai-agent-mcp.exe')));
|
|
155
|
-
if (!nested) throw new Error('发布包中未找到 shizai-agent-mcp.exe');
|
|
156
|
-
source = nested;
|
|
157
|
-
}
|
|
158
|
-
fs.writeFileSync(path.join(source, 'version.json'), JSON.stringify({ version, asset, installedAt: new Date().toISOString() }, null, 2));
|
|
159
|
-
cleanupInstallArtifacts(parent, [stage, backup]);
|
|
160
|
-
for (const processId of stopInstalledProcesses(destination)) stoppedProcesses.add(processId);
|
|
161
|
-
let previousMoved = false;
|
|
162
|
-
try {
|
|
163
|
-
if (fs.existsSync(destination)) {
|
|
164
|
-
fs.renameSync(destination, backup);
|
|
165
|
-
previousMoved = true;
|
|
166
|
-
for (const processId of stopInstalledProcesses(destination)) stoppedProcesses.add(processId);
|
|
167
|
-
}
|
|
168
|
-
fs.renameSync(source, destination);
|
|
169
|
-
} catch (error) {
|
|
170
|
-
if (!fs.existsSync(destination) && previousMoved && fs.existsSync(backup)) {
|
|
171
|
-
fs.renameSync(backup, destination);
|
|
172
|
-
}
|
|
173
|
-
throw error;
|
|
174
|
-
}
|
|
175
|
-
if (previousMoved) {
|
|
176
|
-
try {
|
|
177
|
-
removeDirectoryWithRetries(backup);
|
|
178
|
-
} catch (error) {
|
|
179
|
-
for (const processId of stopInstalledProcesses(destination)) stoppedProcesses.add(processId);
|
|
180
|
-
try {
|
|
181
|
-
removeDirectoryWithRetries(backup);
|
|
182
|
-
} catch (retryError) {
|
|
183
|
-
const failedInstall = path.join(temp, 'failed-install');
|
|
184
|
-
fs.renameSync(destination, failedInstall);
|
|
185
|
-
fs.renameSync(backup, destination);
|
|
186
|
-
removeDirectoryWithRetries(failedInstall);
|
|
187
|
-
throw new Error(`旧版本清理失败,已恢复原安装:${retryError.message}`);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
cleanupInstallArtifacts(parent, [stage, backup]);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
119
|
+
function install(version, options = {}) {
|
|
120
|
+
const destination = validateInstallRoot();
|
|
121
|
+
const asset = assetName(version);
|
|
122
|
+
const url = releaseUrl(version, asset);
|
|
123
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'shizai-agent-'));
|
|
124
|
+
const archive = path.join(temp, asset);
|
|
125
|
+
const checksum = path.join(temp, `${asset}.sha256`);
|
|
126
|
+
const parent = path.dirname(destination);
|
|
127
|
+
const nonce = `${process.pid}-${Date.now()}`;
|
|
128
|
+
const stage = path.join(parent, `.shizai-agent-stage-${nonce}`);
|
|
129
|
+
const backup = path.join(parent, `.shizai-agent-backup-${nonce}`);
|
|
130
|
+
let verified = false;
|
|
131
|
+
const stoppedProcesses = new Set();
|
|
132
|
+
try {
|
|
133
|
+
console.log(`下载 ${url}`);
|
|
134
|
+
download(url, archive);
|
|
135
|
+
try {
|
|
136
|
+
download(`${url}.sha256`, checksum);
|
|
137
|
+
const text = fs.readFileSync(checksum, 'utf8');
|
|
138
|
+
const expected = (text.match(/[a-f0-9]{64}/i) || [])[0];
|
|
139
|
+
if (!expected) throw new Error('校验文件中没有 SHA-256');
|
|
140
|
+
verifySha256(archive, expected);
|
|
141
|
+
verified = true;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (process.env.SHIZAI_AGENT_ALLOW_UNVERIFIED !== '1') throw error;
|
|
144
|
+
console.warn('SHIZAI_AGENT_ALLOW_UNVERIFIED=1:本次跳过发布包校验。');
|
|
145
|
+
}
|
|
146
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
147
|
+
fs.mkdirSync(stage);
|
|
148
|
+
runPowerShell(`Expand-Archive -LiteralPath ${psQuote(archive)} -DestinationPath ${psQuote(stage)} -Force`);
|
|
149
|
+
let source = stage;
|
|
150
|
+
if (!fs.existsSync(path.join(source, 'shizai-agent-mcp.exe'))) {
|
|
151
|
+
const nested = fs.readdirSync(stage, { withFileTypes: true })
|
|
152
|
+
.filter(entry => entry.isDirectory())
|
|
153
|
+
.map(entry => path.join(stage, entry.name))
|
|
154
|
+
.find(candidate => fs.existsSync(path.join(candidate, 'shizai-agent-mcp.exe')));
|
|
155
|
+
if (!nested) throw new Error('发布包中未找到 shizai-agent-mcp.exe');
|
|
156
|
+
source = nested;
|
|
157
|
+
}
|
|
158
|
+
fs.writeFileSync(path.join(source, 'version.json'), JSON.stringify({ version, asset, installedAt: new Date().toISOString() }, null, 2));
|
|
159
|
+
cleanupInstallArtifacts(parent, [stage, backup]);
|
|
160
|
+
for (const processId of stopInstalledProcesses(destination)) stoppedProcesses.add(processId);
|
|
161
|
+
let previousMoved = false;
|
|
162
|
+
try {
|
|
163
|
+
if (fs.existsSync(destination)) {
|
|
164
|
+
fs.renameSync(destination, backup);
|
|
165
|
+
previousMoved = true;
|
|
166
|
+
for (const processId of stopInstalledProcesses(destination)) stoppedProcesses.add(processId);
|
|
167
|
+
}
|
|
168
|
+
fs.renameSync(source, destination);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (!fs.existsSync(destination) && previousMoved && fs.existsSync(backup)) {
|
|
171
|
+
fs.renameSync(backup, destination);
|
|
172
|
+
}
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
if (previousMoved) {
|
|
176
|
+
try {
|
|
177
|
+
removeDirectoryWithRetries(backup);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
for (const processId of stopInstalledProcesses(destination)) stoppedProcesses.add(processId);
|
|
180
|
+
try {
|
|
181
|
+
removeDirectoryWithRetries(backup);
|
|
182
|
+
} catch (retryError) {
|
|
183
|
+
const failedInstall = path.join(temp, 'failed-install');
|
|
184
|
+
fs.renameSync(destination, failedInstall);
|
|
185
|
+
fs.renameSync(backup, destination);
|
|
186
|
+
removeDirectoryWithRetries(failedInstall);
|
|
187
|
+
throw new Error(`旧版本清理失败,已恢复原安装:${retryError.message}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
cleanupInstallArtifacts(parent, [stage, backup]);
|
|
192
|
+
const result = { installed: true, version, directory: destination, command: binaryPath(), verified, stopped_processes: [...stoppedProcesses].sort((left, right) => left - right) };
|
|
193
|
+
if (!options.skipRegister) result.registration = registerCodex(true);
|
|
194
|
+
json(result);
|
|
195
|
+
return result;
|
|
196
|
+
} finally {
|
|
197
|
+
removeDirectoryWithRetries(temp);
|
|
198
|
+
removeDirectoryWithRetries(stage);
|
|
199
|
+
if (fs.existsSync(backup) && fs.existsSync(destination)) removeDirectoryWithRetries(backup);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
199
202
|
function replaceTomlSection(content, section, replacement) {
|
|
200
203
|
const escaped = section.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
201
204
|
const pattern = new RegExp(`^\\[${escaped}\\]\\r?\\n[\\s\\S]*?(?=^\\[|$)`, 'm');
|
|
202
205
|
const next = content.replace(pattern, '').trimEnd();
|
|
203
206
|
return `${next}${next ? '\r\n\r\n' : ''}${replacement.trim()}\r\n`;
|
|
204
207
|
}
|
|
205
|
-
function tomlString(value) { return JSON.stringify(String(value)); }
|
|
206
|
-
function registerCodex() {
|
|
208
|
+
function tomlString(value) { return JSON.stringify(String(value)); }
|
|
209
|
+
function registerCodex(silent = false) {
|
|
207
210
|
const config = process.env.SHIZAI_CODEX_CONFIG || path.join(os.homedir(), '.codex', 'config.toml');
|
|
208
211
|
fs.mkdirSync(path.dirname(config), { recursive: true });
|
|
209
212
|
const old = fs.existsSync(config) ? fs.readFileSync(config, 'utf8') : '';
|
|
210
213
|
if (old) fs.copyFileSync(config, `${config}.shizai-agent.bak`);
|
|
211
|
-
const section = `[mcp_servers.${SERVER}]\ncommand = ${tomlString(binaryPath())}\nargs = []\nstartup_timeout_sec = 30\ntool_timeout_sec = 120`;
|
|
212
|
-
const next = replaceTomlSection(old, `mcp_servers.${SERVER}`, section);
|
|
213
|
-
fs.writeFileSync(config, next, 'utf8');
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
const section = `[mcp_servers.${SERVER}]\ncommand = ${tomlString(binaryPath())}\nargs = []\nstartup_timeout_sec = 30\ntool_timeout_sec = 120`;
|
|
215
|
+
const next = replaceTomlSection(old, `mcp_servers.${SERVER}`, section);
|
|
216
|
+
fs.writeFileSync(config, next, 'utf8');
|
|
217
|
+
const result = { registered: true, client: 'codex', config, command: binaryPath() };
|
|
218
|
+
if (!silent) json(result);
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
function registerJsonClient(client) {
|
|
217
222
|
const locations = {
|
|
218
223
|
cursor: process.env.SHIZAI_CURSOR_CONFIG || path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'Cursor', 'User', 'globalStorage', 'mcp.json'),
|
|
219
224
|
claude: process.env.SHIZAI_CLAUDE_CONFIG || path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json')
|
|
@@ -222,42 +227,42 @@ function registerJsonClient(client) {
|
|
|
222
227
|
if (!file) throw new Error(`未知客户端:${client}`);
|
|
223
228
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
224
229
|
const data = fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')) : {};
|
|
225
|
-
data.mcpServers = data.mcpServers || {};
|
|
226
|
-
data.mcpServers[SERVER] = { command: binaryPath(), args: [] };
|
|
227
|
-
fs.writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
|
|
228
|
-
json({ registered: true, client, config: file, command: binaryPath() });
|
|
230
|
+
data.mcpServers = data.mcpServers || {};
|
|
231
|
+
data.mcpServers[SERVER] = { command: binaryPath(), args: [] };
|
|
232
|
+
fs.writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
|
|
233
|
+
json({ registered: true, client, config: file, command: binaryPath() });
|
|
229
234
|
}
|
|
230
235
|
function launch() {
|
|
231
236
|
if (!fs.existsSync(binaryPath())) install(DEFAULT_VERSION);
|
|
232
237
|
const child = spawn(binaryPath(), process.argv.slice(2), { stdio: 'inherit', windowsHide: true });
|
|
233
238
|
child.on('exit', code => { process.exitCode = code ?? 0; });
|
|
234
239
|
}
|
|
235
|
-
function main() {
|
|
236
|
-
const [command, target, ...rest] = process.argv.slice(2);
|
|
240
|
+
function main() {
|
|
241
|
+
const [command, target, ...rest] = process.argv.slice(2);
|
|
237
242
|
if (!command) return launch();
|
|
238
243
|
if (command === '--help' || command === '-h') return help();
|
|
239
|
-
if (command === 'install') {
|
|
240
|
-
const args = [target, ...rest].filter(value => value !== undefined);
|
|
241
|
-
const index = args.indexOf('--version');
|
|
242
|
-
const version = index >= 0 ? args[index + 1] : args.find(value => !value.startsWith('-'));
|
|
243
|
-
if (index >= 0 && !version) throw new Error('--version 后需要版本号');
|
|
244
|
-
return install(version || DEFAULT_VERSION);
|
|
245
|
-
}
|
|
246
|
-
if (command === 'register') {
|
|
247
|
-
if (!fs.existsSync(binaryPath())) throw new Error('尚未安装,请先运行 install');
|
|
248
|
-
if (target === 'codex') return registerCodex();
|
|
249
|
-
if (target === 'cursor' || target === 'claude') return registerJsonClient(target);
|
|
250
|
-
throw new Error('请指定 codex、cursor 或 claude');
|
|
251
|
-
}
|
|
252
|
-
if (command === 'status') return json({ installed: fs.existsSync(binaryPath()), directory: installRoot, command: binaryPath(), versionFile: path.join(installRoot, 'version.json') });
|
|
244
|
+
if (command === 'install') {
|
|
245
|
+
const args = [target, ...rest].filter(value => value !== undefined);
|
|
246
|
+
const index = args.indexOf('--version');
|
|
247
|
+
const version = index >= 0 ? args[index + 1] : args.find(value => !value.startsWith('-'));
|
|
248
|
+
if (index >= 0 && !version) throw new Error('--version 后需要版本号');
|
|
249
|
+
return install(version || DEFAULT_VERSION, { skipRegister: args.includes('--skip-register') });
|
|
250
|
+
}
|
|
251
|
+
if (command === 'register') {
|
|
252
|
+
if (!fs.existsSync(binaryPath())) throw new Error('尚未安装,请先运行 install');
|
|
253
|
+
if (target === 'codex') return registerCodex();
|
|
254
|
+
if (target === 'cursor' || target === 'claude') return registerJsonClient(target);
|
|
255
|
+
throw new Error('请指定 codex、cursor 或 claude');
|
|
256
|
+
}
|
|
257
|
+
if (command === 'status') return json({ installed: fs.existsSync(binaryPath()), directory: installRoot, command: binaryPath(), versionFile: path.join(installRoot, 'version.json') });
|
|
253
258
|
if (command === 'path') return console.log(binaryPath());
|
|
254
|
-
if (command === 'uninstall') {
|
|
255
|
-
const destination = validateInstallRoot();
|
|
256
|
-
const stoppedProcesses = stopInstalledProcesses(destination);
|
|
257
|
-
removeDirectoryWithRetries(destination);
|
|
258
|
-
if (fs.existsSync(path.dirname(destination))) cleanupInstallArtifacts(path.dirname(destination));
|
|
259
|
-
return json({ uninstalled: true, directory: destination, stopped_processes: stoppedProcesses });
|
|
260
|
-
}
|
|
259
|
+
if (command === 'uninstall') {
|
|
260
|
+
const destination = validateInstallRoot();
|
|
261
|
+
const stoppedProcesses = stopInstalledProcesses(destination);
|
|
262
|
+
removeDirectoryWithRetries(destination);
|
|
263
|
+
if (fs.existsSync(path.dirname(destination))) cleanupInstallArtifacts(path.dirname(destination));
|
|
264
|
+
return json({ uninstalled: true, directory: destination, stopped_processes: stoppedProcesses });
|
|
265
|
+
}
|
|
261
266
|
throw new Error(`未知命令:${command}`);
|
|
262
267
|
}
|
|
263
268
|
try { main(); } catch (error) { fail(error.message); }
|
package/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "shizai-agent-mcp",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Installer and launcher for the Shizai Agent local MCP server",
|
|
5
|
-
"bin": {
|
|
6
|
-
"shizai-agent-mcp": "bin/shizai-agent-mcp.js"
|
|
7
|
-
},
|
|
8
|
-
"engines": {
|
|
9
|
-
"node": ">=18"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"bin",
|
|
13
|
-
"README.md"
|
|
14
|
-
],
|
|
15
|
-
"keywords": [
|
|
16
|
-
"mcp",
|
|
17
|
-
"shizai-agent",
|
|
18
|
-
"codex",
|
|
19
|
-
"cursor",
|
|
20
|
-
"claude"
|
|
21
|
-
],
|
|
22
|
-
"license": "MIT"
|
|
23
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "shizai-agent-mcp",
|
|
3
|
+
"version": "0.2.8",
|
|
4
|
+
"description": "Installer and launcher for the Shizai Agent local MCP server",
|
|
5
|
+
"bin": {
|
|
6
|
+
"shizai-agent-mcp": "bin/shizai-agent-mcp.js"
|
|
7
|
+
},
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=18"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"shizai-agent",
|
|
18
|
+
"codex",
|
|
19
|
+
"cursor",
|
|
20
|
+
"claude"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT"
|
|
23
|
+
}
|