zen-gitsync 2.16.42 → 2.16.43
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/package.json +1 -1
- package/src/ui/public/assets/AgentView-CVfH_WTL.js +4 -0
- package/src/ui/public/assets/AppVersionBadge-BKQZrA46.js +2 -0
- package/src/ui/public/assets/BranchSelector-BrmzYPb2.js +1 -0
- package/src/ui/public/assets/CommitForm-CSAwdqxy.js +6 -0
- package/src/ui/public/assets/CommonDialog-ChSKsEz2.css +1 -0
- package/src/ui/public/assets/CommonDialog-Clc3mo5h.js +1 -0
- package/src/ui/public/assets/EditorView-B05M4l8X.js +1 -0
- package/src/ui/public/assets/FlowExecutionViewer-Du0JPzV8.js +1 -0
- package/src/ui/public/assets/FlowOrchestrationWorkspace-Kg7HaggD.js +21 -0
- package/src/ui/public/assets/LogList-DSl8gjqq.js +7 -0
- package/src/ui/public/assets/MindmapView-KeS9uPEF.js +2 -0
- package/src/ui/public/assets/MonitorView-BYqPPmpL.js +1 -0
- package/src/ui/public/assets/ProjectStartupButton-COEXIVyh.js +1 -0
- package/src/ui/public/assets/{RemoteRepoCard-DFWs9d6t.js → RemoteRepoCard-Cqyksiy4.js} +2 -2
- package/src/ui/public/assets/SourceMapView-DhqMQTQD.js +3 -0
- package/src/ui/public/assets/SvgIcon-CDNsk4-Q.css +1 -0
- package/src/ui/public/assets/SvgIcon-DLr4V32f.js +1 -0
- package/src/ui/public/assets/UserInputNode-Do__vhj9.js +1 -0
- package/src/ui/public/assets/WorkbenchView-Dk5OlqZz.js +28 -0
- package/src/ui/public/assets/_plugin-vue_export-helper-CMAHJXhc.js +17 -0
- package/src/ui/public/assets/configStore-D666qKi0.js +1 -0
- package/src/ui/public/assets/element-plus-D3Dn7nX0.css +1 -0
- package/src/ui/public/assets/element-plus-DXuL5yWp.js +39 -0
- package/src/ui/public/assets/{flow-mindmap-BqudHiZw.js → flow-mindmap-Bi227PSm.js} +1 -1
- package/src/ui/public/assets/index-BT90zGWp.js +89 -0
- package/src/ui/public/assets/index-RKGE_YSG.css +1 -0
- package/src/ui/public/assets/{monaco-DPQ3OaiL.js → monaco-43aputPh.js} +2 -2
- package/src/ui/public/assets/{office-docx-DOvu6SBd.js → office-docx-ZOPof2Jf.js} +12 -12
- package/src/ui/public/assets/{office-excel-oizyPOWk.js → office-excel-DxreApmb.js} +2 -2
- package/src/ui/public/assets/{office-pptx-CCaqOF63.js → office-pptx-CR0bf9zY.js} +1 -1
- package/src/ui/public/assets/socket-io-BZIzo1US.js +1 -0
- package/src/ui/public/assets/toolsStore-DsvD8S_8.js +1 -0
- package/src/ui/public/assets/{ts.worker-METxwbDZ.js → ts.worker-Dth06zuC.js} +16 -1
- package/src/ui/public/assets/{vendor-CD2SBxQj.js → vendor-I3yQCqJK.js} +38 -38
- package/src/ui/public/assets/{vue-flow-BEmfD-aa.js → vue-flow-DV5FYL9P.js} +2 -2
- package/src/ui/public/index.html +16 -9
- package/src/ui/server/routes/fileOpen.js +248 -20
- package/src/ui/server/routes/fileOpen.test.js +239 -0
- package/src/ui/server/routes/workbench/agentSessionStore.js +173 -173
- package/src/ui/public/assets/AgentView-DjZTGHd_.js +0 -4
- package/src/ui/public/assets/AppVersionBadge-jSrdjNi3.js +0 -2
- package/src/ui/public/assets/BranchSelector-CNHhFgaH.js +0 -1
- package/src/ui/public/assets/CommitForm-B4rwEBIH.js +0 -6
- package/src/ui/public/assets/EditorView-D8Zdu0id.js +0 -1
- package/src/ui/public/assets/FlowExecutionViewer-Ci56tI0_.js +0 -1
- package/src/ui/public/assets/FlowOrchestrationWorkspace-WqB1AJl6.js +0 -21
- package/src/ui/public/assets/LogList-DkwwTvhf.js +0 -7
- package/src/ui/public/assets/MindmapView-CX1qlHl3.js +0 -2
- package/src/ui/public/assets/MonitorView-fnUCn0oW.js +0 -1
- package/src/ui/public/assets/ProjectStartupButton-DJZiuAuR.js +0 -1
- package/src/ui/public/assets/SourceMapView-mCG7TN0y.js +0 -3
- package/src/ui/public/assets/UserInputNode-C6pUnTWe.js +0 -1
- package/src/ui/public/assets/WorkbenchView-DgeUjdHD.js +0 -28
- package/src/ui/public/assets/element-plus-CXfbWXaJ.css +0 -1
- package/src/ui/public/assets/element-plus-DqggPOBO.js +0 -47
- package/src/ui/public/assets/index-B2a65GAv.css +0 -1
- package/src/ui/public/assets/index-DAgSqrPI.js +0 -104
- package/src/ui/public/assets/socket-io-B9UuzwTN.js +0 -1
- /package/src/ui/public/assets/{dagre-BCXBo6I9.js → dagre-97WoLjFt.js} +0 -0
package/src/ui/public/index.html
CHANGED
|
@@ -10,20 +10,27 @@
|
|
|
10
10
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
11
11
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
12
12
|
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-BT90zGWp.js"></script>
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-BM3Ffeng.js">
|
|
15
|
-
<link rel="modulepreload" crossorigin href="/assets/monaco-
|
|
16
|
-
<link rel="modulepreload" crossorigin href="/assets/element-plus-
|
|
17
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
18
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
19
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
20
|
-
<link rel="modulepreload" crossorigin href="/assets/flow-
|
|
15
|
+
<link rel="modulepreload" crossorigin href="/assets/monaco-43aputPh.js">
|
|
16
|
+
<link rel="modulepreload" crossorigin href="/assets/element-plus-DXuL5yWp.js">
|
|
17
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-I3yQCqJK.js">
|
|
18
|
+
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-CMAHJXhc.js">
|
|
19
|
+
<link rel="modulepreload" crossorigin href="/assets/socket-io-BZIzo1US.js">
|
|
20
|
+
<link rel="modulepreload" crossorigin href="/assets/vue-flow-DV5FYL9P.js">
|
|
21
|
+
<link rel="modulepreload" crossorigin href="/assets/flow-mindmap-Bi227PSm.js">
|
|
22
|
+
<link rel="modulepreload" crossorigin href="/assets/SvgIcon-DLr4V32f.js">
|
|
23
|
+
<link rel="modulepreload" crossorigin href="/assets/CommonDialog-Clc3mo5h.js">
|
|
24
|
+
<link rel="modulepreload" crossorigin href="/assets/configStore-D666qKi0.js">
|
|
25
|
+
<link rel="modulepreload" crossorigin href="/assets/toolsStore-DsvD8S_8.js">
|
|
21
26
|
<link rel="stylesheet" crossorigin href="/assets/monaco-B2lPvkrc.css">
|
|
22
|
-
<link rel="stylesheet" crossorigin href="/assets/element-plus-
|
|
27
|
+
<link rel="stylesheet" crossorigin href="/assets/element-plus-D3Dn7nX0.css">
|
|
23
28
|
<link rel="stylesheet" crossorigin href="/assets/vendor-BkgN6wPK.css">
|
|
24
29
|
<link rel="stylesheet" crossorigin href="/assets/vue-flow-CavUTEzp.css">
|
|
25
30
|
<link rel="stylesheet" crossorigin href="/assets/flow-mindmap-DnzM9SlK.css">
|
|
26
|
-
<link rel="stylesheet" crossorigin href="/assets/
|
|
31
|
+
<link rel="stylesheet" crossorigin href="/assets/SvgIcon-CDNsk4-Q.css">
|
|
32
|
+
<link rel="stylesheet" crossorigin href="/assets/CommonDialog-ChSKsEz2.css">
|
|
33
|
+
<link rel="stylesheet" crossorigin href="/assets/index-RKGE_YSG.css">
|
|
27
34
|
</head>
|
|
28
35
|
<body>
|
|
29
36
|
<div id="app"></div>
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import fs from 'fs/promises';
|
|
16
16
|
import { asyncRoute, HttpError } from '../utils/asyncRoute.js';
|
|
17
17
|
import path from 'path';
|
|
18
|
+
import os from 'os';
|
|
18
19
|
import open from 'open';
|
|
19
20
|
import { spawn, spawnSync } from 'child_process';
|
|
20
21
|
|
|
@@ -44,6 +45,61 @@ function commandExists(command, platform = process.platform) {
|
|
|
44
45
|
return result.status === 0;
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
/**
|
|
49
|
+
* 从 `--version` 的输出里提取 semver 版本号。
|
|
50
|
+
* 各工具输出格式五花八门:
|
|
51
|
+
* dsh → "0.1.1-rc.2"
|
|
52
|
+
* claude → "1.0.60 (Claude Code)"
|
|
53
|
+
* code → "1.92.0 abc123def (commit ...)"
|
|
54
|
+
* opencode → "opencode 0.1.30"
|
|
55
|
+
* 策略:取第一个 semver 模式(含 prerelease 0.1.1-rc.2 / build 1.0.0+build.1)。
|
|
56
|
+
* 提不出来返回 null(调用方按"未知版本"处理,不影响安装判定)。
|
|
57
|
+
*/
|
|
58
|
+
export function parseVersionOutput(text) {
|
|
59
|
+
if (!text) return null;
|
|
60
|
+
const match = String(text).match(/\d+\.\d+\.\d+(?:[-+][\w.-]+)?/);
|
|
61
|
+
return match ? match[0] : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── npm registry 最新版本查询(右键更新菜单的「当前 → 最新」提示用)──────────
|
|
65
|
+
// 为什么走 HTTP 而不是 `npm view <pkg> version`:spawnSync('npm.cmd', ...) 在
|
|
66
|
+
// server 子进程里不稳(prefix -g 曾返回 "…npm:3" 废地址,见 findDshExecutable
|
|
67
|
+
// 的修复记录),HTTP 直查又快又可控。
|
|
68
|
+
// registry 优先级:~/.npmrc 的 registry 字段(用户可能配了镜像)→ npmmirror 默认。
|
|
69
|
+
|
|
70
|
+
let cachedRegistryBase = null;
|
|
71
|
+
|
|
72
|
+
export function getRegistryBase(readFile = fs.readFile) {
|
|
73
|
+
if (cachedRegistryBase) return cachedRegistryBase;
|
|
74
|
+
return readFile(path.join(os.homedir(), '.npmrc'), 'utf8').then((npmrc) => {
|
|
75
|
+
const match = String(npmrc).match(/^\s*registry\s*=\s*(\S+)/m);
|
|
76
|
+
cachedRegistryBase = match
|
|
77
|
+
? match[1].replace(/\/+$/, '')
|
|
78
|
+
: 'https://registry.npmmirror.com';
|
|
79
|
+
return cachedRegistryBase;
|
|
80
|
+
}).catch(() => (cachedRegistryBase = 'https://registry.npmmirror.com'));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function fetchLatestVersion(packageName, { timeoutMs = 8000, fetchImpl = fetch } = {}) {
|
|
84
|
+
if (!packageName || !/^[\w@/.-]+$/.test(packageName)) return null;
|
|
85
|
+
const base = await getRegistryBase();
|
|
86
|
+
const controller = new AbortController();
|
|
87
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
88
|
+
try {
|
|
89
|
+
const resp = await fetchImpl(`${base}/${packageName}/latest`, {
|
|
90
|
+
signal: controller.signal,
|
|
91
|
+
headers: { accept: 'application/json' },
|
|
92
|
+
});
|
|
93
|
+
if (!resp.ok) return null;
|
|
94
|
+
const data = await resp.json();
|
|
95
|
+
return typeof data?.version === 'string' ? data.version : null;
|
|
96
|
+
} catch {
|
|
97
|
+
return null; // 网络失败/超时:菜单显示原文案,不影响任何功能
|
|
98
|
+
} finally {
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
47
103
|
/**
|
|
48
104
|
* 返回当前平台可展示、可执行的安装方案。
|
|
49
105
|
* executable/args 只在服务端使用;前端只能提交固定 tool id,不能提交命令。
|
|
@@ -64,6 +120,12 @@ export function getToolInstallers(platform = process.platform, hasCommand = comm
|
|
|
64
120
|
: '未检测到 npm,请先安装 Node.js/npm,或按照官方文档手动安装。',
|
|
65
121
|
executable: npmExecutable,
|
|
66
122
|
args: ['install', '-g', packageName],
|
|
123
|
+
// 升级变体:@latest 强制同步到最新版。已是最新时 npm 自然跳过。
|
|
124
|
+
updateCommand: `npm install -g ${packageName}@latest`,
|
|
125
|
+
updatePackageManager: 'npm',
|
|
126
|
+
updateNote: '将在新终端中执行 `npm install -g <pkg>@latest`。已是最新时 npm 会跳过。',
|
|
127
|
+
updateExecutable: npmExecutable,
|
|
128
|
+
updateArgs: ['install', '-g', `${packageName}@latest`],
|
|
67
129
|
};
|
|
68
130
|
}
|
|
69
131
|
|
|
@@ -79,6 +141,18 @@ export function getToolInstallers(platform = process.platform, hasCommand = comm
|
|
|
79
141
|
: '未检测到 winget,请使用 VS Code 官方安装程序。',
|
|
80
142
|
executable: 'winget',
|
|
81
143
|
args: ['install', '--id', 'Microsoft.VisualStudioCode', '-e', '--accept-package-agreements', '--accept-source-agreements'],
|
|
144
|
+
// winget 的升级动词是 upgrade(不是 install)
|
|
145
|
+
updateCommand: wingetAvailable
|
|
146
|
+
? 'winget upgrade --id Microsoft.VisualStudioCode -e --accept-package-agreements --accept-source-agreements'
|
|
147
|
+
: undefined,
|
|
148
|
+
updatePackageManager: 'winget',
|
|
149
|
+
updateNote: wingetAvailable
|
|
150
|
+
? '将在新终端中通过 winget upgrade 升级 VS Code。'
|
|
151
|
+
: '未检测到 winget,请使用 VS Code 官方安装包。',
|
|
152
|
+
updateExecutable: wingetAvailable ? 'winget' : undefined,
|
|
153
|
+
updateArgs: wingetAvailable
|
|
154
|
+
? ['upgrade', '--id', 'Microsoft.VisualStudioCode', '-e', '--accept-package-agreements', '--accept-source-agreements']
|
|
155
|
+
: undefined,
|
|
82
156
|
};
|
|
83
157
|
} else if (platform === 'darwin') {
|
|
84
158
|
const brewAvailable = hasCommand('brew', platform);
|
|
@@ -92,6 +166,11 @@ export function getToolInstallers(platform = process.platform, hasCommand = comm
|
|
|
92
166
|
: '未检测到 Homebrew,请使用 VS Code 官方安装程序。',
|
|
93
167
|
executable: 'brew',
|
|
94
168
|
args: ['install', '--cask', 'visual-studio-code'],
|
|
169
|
+
updateCommand: brewAvailable ? 'brew upgrade --cask visual-studio-code' : undefined,
|
|
170
|
+
updatePackageManager: 'Homebrew',
|
|
171
|
+
updateNote: brewAvailable ? '将在新终端中通过 brew upgrade 升级 VS Code。' : '未检测到 brew。',
|
|
172
|
+
updateExecutable: brewAvailable ? 'brew' : undefined,
|
|
173
|
+
updateArgs: brewAvailable ? ['upgrade', '--cask', 'visual-studio-code'] : undefined,
|
|
95
174
|
};
|
|
96
175
|
} else {
|
|
97
176
|
const snapAvailable = hasCommand('snap', platform);
|
|
@@ -105,6 +184,12 @@ export function getToolInstallers(platform = process.platform, hasCommand = comm
|
|
|
105
184
|
: '当前 Linux 环境未检测到 snap,请按照发行版对应的官方说明安装。',
|
|
106
185
|
executable: 'sudo',
|
|
107
186
|
args: ['snap', 'install', 'code', '--classic'],
|
|
187
|
+
// snap 的升级动词是 refresh
|
|
188
|
+
updateCommand: snapAvailable ? 'sudo snap refresh code' : undefined,
|
|
189
|
+
updatePackageManager: 'snap',
|
|
190
|
+
updateNote: snapAvailable ? '将在新终端中通过 snap refresh 升级 VS Code。' : '未检测到 snap。',
|
|
191
|
+
updateExecutable: snapAvailable ? 'sudo' : undefined,
|
|
192
|
+
updateArgs: snapAvailable ? ['snap', 'refresh', 'code'] : undefined,
|
|
108
193
|
};
|
|
109
194
|
}
|
|
110
195
|
|
|
@@ -130,6 +215,26 @@ export function getToolInstallers(platform = process.platform, hasCommand = comm
|
|
|
130
215
|
'Invoke-RestMethod https://code.kimi.com/kimi-code/install.ps1 | Invoke-Expression',
|
|
131
216
|
].join('; ')
|
|
132
217
|
: undefined,
|
|
218
|
+
// 升级:kimi 官方脚本幂等,已安装时执行会自动升级到最新版,直接复用同一脚本
|
|
219
|
+
updateCommand: platform === 'win32'
|
|
220
|
+
? 'irm https://code.kimi.com/kimi-code/install.ps1 | iex'
|
|
221
|
+
: 'curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash',
|
|
222
|
+
updatePackageManager: 'Kimi Code 官方安装脚本',
|
|
223
|
+
updateNote: '官方安装脚本幂等,已安装时执行会自动升级到最新版本。',
|
|
224
|
+
updateKind: 'script',
|
|
225
|
+
updateExecutionCommand: platform === 'win32'
|
|
226
|
+
? [
|
|
227
|
+
"if (-not (Get-Command Get-FileHash -ErrorAction SilentlyContinue)) {",
|
|
228
|
+
"function global:Get-FileHash { param([string]$Path, [string]$Algorithm = 'SHA256')",
|
|
229
|
+
"$stream = [System.IO.File]::OpenRead($Path)",
|
|
230
|
+
"try { $sha = [System.Security.Cryptography.SHA256]::Create(); try { $bytes = $sha.ComputeHash($stream) } finally { $sha.Dispose() } } finally { $stream.Dispose() }",
|
|
231
|
+
"$hash = ([System.BitConverter]::ToString($bytes)).Replace('-', '')",
|
|
232
|
+
"New-Object PSObject -Property @{ Algorithm = 'SHA256'; Hash = $hash; Path = $Path }",
|
|
233
|
+
"}",
|
|
234
|
+
"}",
|
|
235
|
+
'Invoke-RestMethod https://code.kimi.com/kimi-code/install.ps1 | Invoke-Expression',
|
|
236
|
+
].join('; ')
|
|
237
|
+
: undefined,
|
|
133
238
|
};
|
|
134
239
|
installers.zcode = {
|
|
135
240
|
supported: false,
|
|
@@ -137,6 +242,10 @@ export function getToolInstallers(platform = process.platform, hasCommand = comm
|
|
|
137
242
|
packageManager: 'ZCode 官方安装包',
|
|
138
243
|
docsUrl: TOOL_DOCS_URLS.zcode,
|
|
139
244
|
note: 'ZCode 是桌面应用,官方提供 Windows、macOS 和 Linux 安装包,没有官方 npm CLI。',
|
|
245
|
+
// ZCode 是桌面应用、没有 CLI 更新通道;/api/update-tool 会对它返回 400
|
|
246
|
+
updateCommand: undefined,
|
|
247
|
+
updatePackageManager: 'ZCode 官方安装包',
|
|
248
|
+
updateNote: 'ZCode 是桌面应用,升级请到官网下载新版安装包覆盖安装。',
|
|
140
249
|
};
|
|
141
250
|
return installers;
|
|
142
251
|
}
|
|
@@ -283,17 +392,34 @@ async function launchDsh(dirPath) {
|
|
|
283
392
|
return launchInTerminal(dirPath, executable, ['web']);
|
|
284
393
|
}
|
|
285
394
|
|
|
286
|
-
|
|
395
|
+
// 在 Windows 上找 dsh 可执行文件。
|
|
396
|
+
// 优先用 where.exe dsh(覆盖 nvm4w 的 C:\nvm4w\nodejs\、默认的 %APPDATA%\npm、
|
|
397
|
+
// pnpm/yarn 自定义 prefix、以及用户手摆到 PATH 里的任意位置),
|
|
398
|
+
//再兜底 APPDATA\npm + npm prefix -g 两个写死路径(为 where.exe 漏检的边缘情况,
|
|
399
|
+
//比如 PATH 里只有 junction/symlink 时)。
|
|
400
|
+
export async function findDshExecutable() {
|
|
287
401
|
if (process.platform !== 'win32') return commandExists('dsh') ? 'dsh' : null;
|
|
288
402
|
|
|
289
|
-
const
|
|
403
|
+
const where = spawnSync('where.exe', ['dsh'], { encoding: 'utf8', windowsHide: true });
|
|
404
|
+
if (where.status === 0 && where.stdout) {
|
|
405
|
+
for (const line of where.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean)) {
|
|
406
|
+
// 偶尔会输出 "PATH=..." 这种变量行,跳过
|
|
407
|
+
if (/^[A-Z_]+=/i.test(line)) continue;
|
|
408
|
+
try {
|
|
409
|
+
const stat = await fs.stat(line);
|
|
410
|
+
if (stat.isFile()) return line;
|
|
411
|
+
} catch {}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const fallback = [path.join(process.env.APPDATA || '', 'npm', 'dsh.cmd')];
|
|
290
416
|
const npmPrefix = spawnSync('npm.cmd', ['prefix', '-g'], {
|
|
291
417
|
encoding: 'utf8',
|
|
292
418
|
windowsHide: true,
|
|
293
419
|
}).stdout?.trim();
|
|
294
|
-
if (npmPrefix)
|
|
420
|
+
if (npmPrefix) fallback.push(path.join(npmPrefix, 'dsh.cmd'));
|
|
295
421
|
|
|
296
|
-
for (const candidate of
|
|
422
|
+
for (const candidate of fallback.filter(Boolean)) {
|
|
297
423
|
try {
|
|
298
424
|
const stat = await fs.stat(candidate);
|
|
299
425
|
if (stat.isFile()) return candidate;
|
|
@@ -390,27 +516,35 @@ async function findZCodeExecutable() {
|
|
|
390
516
|
return null;
|
|
391
517
|
}
|
|
392
518
|
|
|
393
|
-
async function launchToolInstaller(installer, dirPath = process.cwd()) {
|
|
394
|
-
|
|
519
|
+
async function launchToolInstaller(installer, dirPath = process.cwd(), { update = false } = {}) {
|
|
520
|
+
// update=true 时改用 updateXxx 字段(升级命令与安装命令动词不同:
|
|
521
|
+
// npm @latest / winget upgrade / brew upgrade / snap refresh / kimi 脚本幂等重跑)
|
|
522
|
+
const kindField = update ? 'updateKind' : 'kind';
|
|
523
|
+
const execCmdField = update ? 'updateExecutionCommand' : 'executionCommand';
|
|
524
|
+
const cmdField = update ? 'updateCommand' : 'command';
|
|
525
|
+
const executableField = update ? 'updateExecutable' : 'executable';
|
|
526
|
+
const argsField = update ? 'updateArgs' : 'args';
|
|
527
|
+
|
|
528
|
+
if (installer[kindField] === 'script') {
|
|
395
529
|
if (process.platform === 'win32') {
|
|
396
530
|
// 显式创建新控制台;编码脚本可避免 cmd.exe 截获 PowerShell 管道符。
|
|
397
|
-
const encoded = Buffer.from(installer
|
|
531
|
+
const encoded = Buffer.from(installer[execCmdField] || installer[cmdField], 'utf16le').toString('base64');
|
|
398
532
|
return spawnDetached('cmd.exe', [
|
|
399
533
|
'/c', 'start', '""', 'powershell.exe', '-NoLogo', '-NoExit',
|
|
400
534
|
'-ExecutionPolicy', 'Bypass', '-EncodedCommand', encoded,
|
|
401
535
|
], { cwd: dirPath });
|
|
402
536
|
}
|
|
403
|
-
return launchInTerminal(dirPath, 'bash', ['-lc', installer
|
|
537
|
+
return launchInTerminal(dirPath, 'bash', ['-lc', installer[cmdField]]);
|
|
404
538
|
}
|
|
405
539
|
if (process.platform === 'win32') {
|
|
406
|
-
//
|
|
407
|
-
const commandLine = [installer
|
|
540
|
+
// 安装/更新命令完全来自服务端白名单。使用可见 cmd 窗口,让用户看到进度和错误。
|
|
541
|
+
const commandLine = [installer[executableField], ...installer[argsField]].join(' ');
|
|
408
542
|
return spawnDetached('cmd.exe', ['/c', 'start', '', 'cmd.exe', '/k', commandLine], {
|
|
409
543
|
cwd: dirPath,
|
|
410
544
|
});
|
|
411
545
|
}
|
|
412
546
|
|
|
413
|
-
return launchInTerminal(dirPath, installer
|
|
547
|
+
return launchInTerminal(dirPath, installer[executableField], installer[argsField]);
|
|
414
548
|
}
|
|
415
549
|
|
|
416
550
|
export function registerFileOpenRoutes({
|
|
@@ -752,30 +886,113 @@ export function registerFileOpenRoutes({
|
|
|
752
886
|
});
|
|
753
887
|
}));
|
|
754
888
|
|
|
755
|
-
//
|
|
756
|
-
//
|
|
889
|
+
// 更新本地工具:与 install-tool 同一套白名单,只是换用 updateXxx 命令变体
|
|
890
|
+
// (npm @latest / winget upgrade / brew upgrade / snap refresh / kimi 脚本重跑)。
|
|
891
|
+
// 没有 updateCommand 的工具(如 zcode 桌面应用)返回 400。
|
|
892
|
+
app.post('/api/update-tool', asyncRoute(async (req, res) => {
|
|
893
|
+
const { tool } = req.body || {};
|
|
894
|
+
const installers = getToolInstallers();
|
|
895
|
+
const installer = installers[tool];
|
|
896
|
+
|
|
897
|
+
if (!installer) {
|
|
898
|
+
throw new HttpError(400, '不支持的工具');
|
|
899
|
+
}
|
|
900
|
+
if (!installer.updateCommand) {
|
|
901
|
+
throw new HttpError(400, installer.updateNote || '当前工具不支持一键更新,请按官方文档手动升级');
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
await launchToolInstaller(installer, process.cwd(), { update: true });
|
|
905
|
+
res.json({
|
|
906
|
+
success: true,
|
|
907
|
+
message: '更新命令已在新终端中启动',
|
|
908
|
+
});
|
|
909
|
+
}));
|
|
910
|
+
|
|
911
|
+
// 查询 npm registry 上的最新版本,供前端更新菜单显示「当前 v1 → 最新 v2」。
|
|
912
|
+
// 只支持 TOOL_INSTALL_PACKAGES 里的 npm 工具;网络失败的工具返回 null(前端降级为原文案)。
|
|
913
|
+
// body 可选 { tools: ['claude', ...] } —— 缺省查全部 npm 工具。
|
|
914
|
+
app.post('/api/latest-tool-versions', asyncRoute(async (req, res) => {
|
|
915
|
+
const { tools } = req.body || {};
|
|
916
|
+
let requested;
|
|
917
|
+
if (tools === undefined) {
|
|
918
|
+
requested = Object.keys(TOOL_INSTALL_PACKAGES);
|
|
919
|
+
} else {
|
|
920
|
+
if (!Array.isArray(tools) || tools.some((t) => typeof t !== 'string')) {
|
|
921
|
+
throw new HttpError(400, 'tools 必须是字符串数组');
|
|
922
|
+
}
|
|
923
|
+
const unknown = tools.filter((t) => !TOOL_INSTALL_PACKAGES[t]);
|
|
924
|
+
if (unknown.length) {
|
|
925
|
+
throw new HttpError(400, `不支持的工具: ${unknown.join(', ')}`);
|
|
926
|
+
}
|
|
927
|
+
requested = tools;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const entries = await Promise.all(
|
|
931
|
+
requested.map(async (tool) => [tool, await fetchLatestVersion(TOOL_INSTALL_PACKAGES[tool])]),
|
|
932
|
+
);
|
|
933
|
+
res.json({
|
|
934
|
+
success: true,
|
|
935
|
+
latest: Object.fromEntries(entries),
|
|
936
|
+
});
|
|
937
|
+
}));
|
|
938
|
+
|
|
939
|
+
// 检测本地工具是否已安装 + 采集版本号(供前端决定按钮行为、tooltip 显示版本)
|
|
940
|
+
// 检测方式: spawn 'tool --version',exit 0 即视为已安装;stdout/stderr 里提取 semver。
|
|
757
941
|
// 超时 15s:Claude CLI / opencode 等 Node 包装的工具首次冷启动
|
|
758
942
|
// 在 Windows 上经常 5-7s(磁盘 cache + Defender 扫描),3s 永远命中超时。
|
|
759
943
|
app.get('/api/check-tools', asyncRoute(async (req, res) => {
|
|
760
944
|
const checkCmd = (cmd) => new Promise((resolve) => {
|
|
761
945
|
const child = spawn(cmd, ['--version'], {
|
|
762
|
-
|
|
946
|
+
// 要捕获输出来解析版本号,不能再用 'ignore'
|
|
947
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
763
948
|
shell: process.platform === 'win32',
|
|
764
949
|
windowsHide: true,
|
|
765
950
|
});
|
|
951
|
+
let output = '';
|
|
766
952
|
let done = false;
|
|
767
953
|
const finish = (ok) => {
|
|
768
954
|
if (done) return;
|
|
769
955
|
done = true;
|
|
770
956
|
try { child.kill('SIGKILL'); } catch {}
|
|
771
|
-
resolve(ok);
|
|
957
|
+
resolve({ installed: ok, version: parseVersionOutput(output) });
|
|
772
958
|
};
|
|
959
|
+
// 有些 CLI 把版本号打到 stderr(如部分 Node 包装工具),两边都收
|
|
960
|
+
child.stdout.on('data', (chunk) => { output += chunk; });
|
|
961
|
+
child.stderr.on('data', (chunk) => { output += chunk; });
|
|
773
962
|
child.on('error', () => finish(false));
|
|
774
963
|
child.on('exit', (code) => finish(code === 0));
|
|
775
964
|
setTimeout(() => finish(false), 15000);
|
|
776
965
|
});
|
|
777
966
|
|
|
778
|
-
|
|
967
|
+
// dsh / kimi 走 find*Executable 拿到完整路径,再补一次 --version 采版本。
|
|
968
|
+
// zcode 是纯 GUI 桌面应用,ZCode.exe --version 行为未知(可能弹窗),不采集。
|
|
969
|
+
const versionOfExecutable = (executable) => {
|
|
970
|
+
if (!executable) return Promise.resolve(null);
|
|
971
|
+
return new Promise((resolve) => {
|
|
972
|
+
const child = spawn(executable, ['--version'], {
|
|
973
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
974
|
+
shell: process.platform === 'win32',
|
|
975
|
+
windowsHide: true,
|
|
976
|
+
});
|
|
977
|
+
let output = '';
|
|
978
|
+
let done = false;
|
|
979
|
+
const finish = () => {
|
|
980
|
+
if (done) return;
|
|
981
|
+
done = true;
|
|
982
|
+
try { child.kill('SIGKILL'); } catch {}
|
|
983
|
+
resolve(parseVersionOutput(output));
|
|
984
|
+
};
|
|
985
|
+
child.stdout.on('data', (chunk) => { output += chunk; });
|
|
986
|
+
child.stderr.on('data', (chunk) => { output += chunk; });
|
|
987
|
+
child.on('error', () => finish());
|
|
988
|
+
child.on('exit', () => finish());
|
|
989
|
+
setTimeout(() => finish(), 15000);
|
|
990
|
+
});
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
// dsh / kimi 的版本采集依赖 find 结果,用 then 链接保持全并行
|
|
994
|
+
// (find 会跑两次——一次拿路径一次链版本——都是纯 fs/注册表查询,开销可忽略)
|
|
995
|
+
const [vscodeRes, claudeRes, codexRes, opencodeRes, kimiExecutable, zcodeExecutable, dshExecutable, kimiVersion, dshVersion] = await Promise.all([
|
|
779
996
|
checkCmd('code'),
|
|
780
997
|
checkCmd('claude'),
|
|
781
998
|
checkCmd('codex'),
|
|
@@ -783,6 +1000,8 @@ export function registerFileOpenRoutes({
|
|
|
783
1000
|
findKimiExecutable(),
|
|
784
1001
|
findZCodeExecutable(),
|
|
785
1002
|
findDshExecutable(),
|
|
1003
|
+
findKimiExecutable().then(versionOfExecutable),
|
|
1004
|
+
findDshExecutable().then(versionOfExecutable),
|
|
786
1005
|
]);
|
|
787
1006
|
|
|
788
1007
|
const installers = getToolInstallers();
|
|
@@ -790,13 +1009,22 @@ export function registerFileOpenRoutes({
|
|
|
790
1009
|
success: true,
|
|
791
1010
|
platform: process.platform,
|
|
792
1011
|
installers: publicInstallerInfo(installers),
|
|
793
|
-
vscode,
|
|
794
|
-
claude,
|
|
795
|
-
codex,
|
|
796
|
-
opencode,
|
|
1012
|
+
vscode: vscodeRes.installed,
|
|
1013
|
+
claude: claudeRes.installed,
|
|
1014
|
+
codex: codexRes.installed,
|
|
1015
|
+
opencode: opencodeRes.installed,
|
|
797
1016
|
kimi: !!kimiExecutable,
|
|
798
1017
|
zcode: !!zcodeExecutable,
|
|
799
1018
|
dsh: !!dshExecutable,
|
|
1019
|
+
versions: {
|
|
1020
|
+
vscode: vscodeRes.version,
|
|
1021
|
+
claude: claudeRes.version,
|
|
1022
|
+
codex: codexRes.version,
|
|
1023
|
+
opencode: opencodeRes.version,
|
|
1024
|
+
kimi: kimiExecutable ? kimiVersion : null,
|
|
1025
|
+
zcode: null, // 桌面应用,无 CLI 版本通道
|
|
1026
|
+
dsh: dshExecutable ? dshVersion : null,
|
|
1027
|
+
},
|
|
800
1028
|
});
|
|
801
1029
|
}));
|
|
802
1030
|
}
|