zen-gitsync 2.16.24 → 2.16.27
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 +2 -2
- package/package.json +1 -1
- package/src/cli/ai/platformGuard.js +200 -200
- package/src/cli/ai/platformGuard.test.js +253 -253
- package/src/gitCommit.js +484 -484
- package/src/ui/public/assets/AgentView-Bytr0HbV.js +4 -0
- package/src/ui/public/assets/AppVersionBadge-BW4vXJxH.js +2 -0
- package/src/ui/public/assets/BranchSelector-DsLQi1AR.js +1 -0
- package/src/ui/public/assets/CommitForm-CmmxW73q.js +6 -0
- package/src/ui/public/assets/CommonDialog-CQz6iEqp.js +1 -0
- package/src/ui/public/assets/EditorView-CtygNTuz.js +1 -0
- package/src/ui/public/assets/{FlowExecutionViewer-BLv0xSX3.js → FlowExecutionViewer-B7CdeiSp.js} +1 -1
- package/src/ui/public/assets/FlowOrchestrationWorkspace-DtTItIzf.js +21 -0
- package/src/ui/public/assets/LogList-Bo53PBwg.css +1 -0
- package/src/ui/public/assets/LogList-Cwem9ONv.js +7 -0
- package/src/ui/public/assets/MindmapView-DR5bT_oQ.js +2 -0
- package/src/ui/public/assets/MonitorView-DoLNabxl.js +1 -0
- package/src/ui/public/assets/ProjectStartupButton-BiNiH43W.js +1 -0
- package/src/ui/public/assets/{RemoteRepoCard-LHrI3uIa.js → RemoteRepoCard-DJ7ficOj.js} +2 -2
- package/src/ui/public/assets/SourceMapView-CSVCaU8d.js +3 -0
- package/src/ui/public/assets/{SvgIcon-BfPnov-Z.js → SvgIcon-CL2qPswh.js} +1 -1
- package/src/ui/public/assets/UserInputNode-BftMuqji.js +1 -0
- package/src/ui/public/assets/WorkbenchView-Bp2YaSGS.css +1 -0
- package/src/ui/public/assets/WorkbenchView-Cl9H5uXg.js +28 -0
- package/src/ui/public/assets/{_plugin-vue_export-helper-BpqSqjxA.js → _plugin-vue_export-helper-D3_upOKo.js} +3 -3
- package/src/ui/public/assets/configStore-Cqnbpz5-.js +1 -0
- package/src/ui/public/assets/{css.worker-Wv5dxAWO.js → css.worker-CvXBzhp8.js} +1 -1
- package/src/ui/public/assets/{dagre-DgwsqYn1.js → dagre-CWIGcX9d.js} +1 -1
- package/src/ui/public/assets/element-plus-B3pTS_5R.js +32 -0
- package/src/ui/public/assets/element-plus-CQrjg9us.css +1 -0
- package/src/ui/public/assets/{flow-mindmap-ByUzqcTH.js → flow-mindmap-B5aqOviR.js} +38 -38
- package/src/ui/public/assets/{html.worker-CQP8QQsS.js → html.worker-BO6WuOEO.js} +1 -1
- package/src/ui/public/assets/index-D89M9m30.js +71 -0
- package/src/ui/public/assets/{json.worker-DzV-CpCQ.js → json.worker-BkJRGcCJ.js} +1 -1
- package/src/ui/public/assets/{monaco-Dje8Rg13.js → monaco-BFLoT2VD.js} +198 -198
- package/src/ui/public/assets/socket-io-Bcae2P7B.js +1 -0
- package/src/ui/public/assets/toolsStore-RkALTxME.js +1 -0
- package/src/ui/public/assets/{ts.worker-Dth06zuC.js → ts.worker-B0J26iPs.js} +15 -15
- package/src/ui/public/assets/{vendor-DKiix7Qv.js → vendor-Cx8PA1NP.js} +14 -14
- package/src/ui/public/assets/{vue-flow-B-YyhjTY.js → vue-flow-COROsN-V.js} +2 -2
- package/src/ui/public/favicon.svg +75 -75
- package/src/ui/public/index.html +31 -30
- package/src/ui/public/logo.svg +74 -74
- package/src/ui/server/.claude/codediff.txt +8 -0
- package/src/ui/server/index.js +2 -1
- package/src/ui/server/middleware/requestLogger.js +53 -53
- package/src/ui/server/routes/branchStatus.js +105 -105
- package/src/ui/server/routes/fileOpen.js +171 -1
- package/src/ui/server/routes/fs.js +2 -2
- package/src/ui/server/routes/git/diff.js +426 -426
- package/src/ui/server/routes/git/diffUtils.js +149 -149
- package/src/ui/server/routes/git/stash.js +550 -550
- package/src/ui/server/routes/git/tags.js +176 -176
- package/src/ui/server/routes/gitOps.js +10 -0
- package/src/ui/server/routes/process.js +86 -86
- package/src/ui/server/routes/status.js +67 -67
- package/src/ui/server/routes/workbench/pdfText.js +145 -145
- package/src/ui/server/utils/createSavePortToFile.js +48 -48
- package/src/ui/server/utils/instanceRegistry.js +282 -282
- package/src/ui/server/utils/pathGuard.js +21 -0
- package/src/ui/server/utils/startServerOnAvailablePort.js +116 -116
- package/src/utils/index.js +6 -3
- package/src/utils/utils.test.js +89 -2
- package/src/ui/public/assets/AgentView-B1P7BpdL.js +0 -4
- package/src/ui/public/assets/AppVersionBadge-CxbNViwm.js +0 -2
- package/src/ui/public/assets/BranchSelector-D4dKHxxK.js +0 -1
- package/src/ui/public/assets/CommitForm-klcrUCW7.js +0 -6
- package/src/ui/public/assets/CommonDialog-DfPvpafn.js +0 -1
- package/src/ui/public/assets/EditorView-CZofJ-wy.js +0 -1
- package/src/ui/public/assets/FlowOrchestrationWorkspace-BcPCI3Cq.js +0 -21
- package/src/ui/public/assets/LogList-BYrHPSZw.js +0 -15
- package/src/ui/public/assets/LogList-CeFFytjB.css +0 -1
- package/src/ui/public/assets/MindmapView-CNd4Q8B8.js +0 -2
- package/src/ui/public/assets/MonitorView-BlmWenOX.js +0 -1
- package/src/ui/public/assets/ProjectStartupButton-706xOj0N.js +0 -1
- package/src/ui/public/assets/SourceMapView-e96cgLn7.js +0 -3
- package/src/ui/public/assets/UserInputNode-W1uGFT5P.js +0 -1
- package/src/ui/public/assets/WorkbenchView-Cfp_bLC8.js +0 -28
- package/src/ui/public/assets/WorkbenchView-DFO8IfcU.css +0 -1
- package/src/ui/public/assets/configStore-CqqnJlVS.js +0 -1
- package/src/ui/public/assets/element-plus--ayTjGvF.js +0 -25
- package/src/ui/public/assets/element-plus-D3Dn7nX0.css +0 -1
- package/src/ui/public/assets/index-BNymf6-p.js +0 -71
- package/src/ui/public/assets/socket-io-CGezBd--.js +0 -1
- /package/src/ui/public/assets/{editor.worker-Bd9IXS8d.js → editor.worker-Cn2oRESe.js} +0 -0
- /package/src/ui/public/assets/{rolldown-runtime-BM3Ffeng.js → rolldown-runtime-CMxvf4Kt.js} +0 -0
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
// Copyright 2026 xz333221
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
//
|
|
15
|
-
import { asyncRoute, HttpError } from '../utils/asyncRoute.js'
|
|
16
|
-
|
|
17
|
-
import logger from '../utils/logger.js'
|
|
18
|
-
|
|
19
|
-
export function registerBranchStatusRoutes({
|
|
20
|
-
app,
|
|
21
|
-
execGitCommand,
|
|
22
|
-
getIsGitRepo,
|
|
23
|
-
getBranchStatusCache,
|
|
24
|
-
setBranchStatusCache,
|
|
25
|
-
getRecentPushStatus,
|
|
26
|
-
setRecentPushStatus
|
|
27
|
-
}) {
|
|
28
|
-
// 获取当前分支 - 直接读取,不缓存(git symbolic-ref 极快,<5ms)
|
|
29
|
-
app.get('/api/branch', asyncRoute(async (req, res) => {
|
|
30
|
-
try {
|
|
31
|
-
const { stdout } = await execGitCommand(['symbolic-ref', '--short', 'HEAD']);
|
|
32
|
-
res.json({ branch: stdout.trim() });
|
|
33
|
-
} catch (error) {
|
|
34
|
-
res.status(500).json({ error: error.message });
|
|
35
|
-
}
|
|
36
|
-
}));
|
|
37
|
-
|
|
38
|
-
// 获取分支与远程的差异状态(领先/落后提交数)
|
|
39
|
-
app.get('/api/branch-status', asyncRoute(async (req, res) => {
|
|
40
|
-
try {
|
|
41
|
-
if (!getIsGitRepo()) {
|
|
42
|
-
return res.json({ hasUpstream: false, ahead: 0, behind: 0 });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const now = Date.now();
|
|
46
|
-
const forceRefresh = req.query.force === 'true';
|
|
47
|
-
|
|
48
|
-
const recentPushStatus = getRecentPushStatus();
|
|
49
|
-
const branchStatusCache = getBranchStatusCache();
|
|
50
|
-
|
|
51
|
-
// push 后10秒内直接返回同步状态
|
|
52
|
-
if (recentPushStatus.justPushed &&
|
|
53
|
-
(now - recentPushStatus.pushTime) < recentPushStatus.validDuration) {
|
|
54
|
-
logger.info('检测到最近推送过,直接返回同步状态');
|
|
55
|
-
return res.json({
|
|
56
|
-
hasUpstream: true,
|
|
57
|
-
upstreamBranch: branchStatusCache.upstreamBranch || 'origin/main',
|
|
58
|
-
ahead: 0,
|
|
59
|
-
behind: 0
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// 5秒缓存分支名,防止短时间内重复执行 git symbolic-ref / git rev-parse
|
|
64
|
-
const branchInfoCacheValid = !forceRefresh &&
|
|
65
|
-
branchStatusCache.currentBranch &&
|
|
66
|
-
branchStatusCache.upstreamBranch &&
|
|
67
|
-
(now - branchStatusCache.lastUpdate) < branchStatusCache.cacheTimeout;
|
|
68
|
-
|
|
69
|
-
let currentBranch, upstreamBranch;
|
|
70
|
-
|
|
71
|
-
if (branchInfoCacheValid) {
|
|
72
|
-
currentBranch = branchStatusCache.currentBranch;
|
|
73
|
-
upstreamBranch = branchStatusCache.upstreamBranch;
|
|
74
|
-
logger.info(`使用5秒缓存的分支名: ${currentBranch} -> ${upstreamBranch}`);
|
|
75
|
-
} else {
|
|
76
|
-
// 直接读取,不再走5分钟长缓存
|
|
77
|
-
const { stdout: branchOut } = await execGitCommand(['symbolic-ref', '--short', 'HEAD']);
|
|
78
|
-
currentBranch = branchOut.trim();
|
|
79
|
-
|
|
80
|
-
const { stdout: upstreamOut } = await execGitCommand(
|
|
81
|
-
['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'],
|
|
82
|
-
{ ignoreError: true }
|
|
83
|
-
);
|
|
84
|
-
upstreamBranch = upstreamOut.trim() || null;
|
|
85
|
-
|
|
86
|
-
if (!upstreamBranch) {
|
|
87
|
-
setBranchStatusCache({ currentBranch: null, upstreamBranch: null, lastUpdate: 0, cacheTimeout: 5000 });
|
|
88
|
-
return res.json({ hasUpstream: false, ahead: 0, behind: 0 });
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
setBranchStatusCache({ currentBranch, upstreamBranch, lastUpdate: now, cacheTimeout: 5000 });
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const { stdout: aheadBehindOutput } = await execGitCommand(
|
|
95
|
-
['rev-list', '--left-right', '--count', `${currentBranch}...${upstreamBranch}`]
|
|
96
|
-
);
|
|
97
|
-
const [ahead, behind] = aheadBehindOutput.trim().split('\t').map(Number);
|
|
98
|
-
|
|
99
|
-
res.json({ hasUpstream: true, upstreamBranch, ahead, behind });
|
|
100
|
-
} catch (error) {
|
|
101
|
-
logger.error('获取分支状态失败:', error);
|
|
102
|
-
res.status(500).json({ error: error.message });
|
|
103
|
-
}
|
|
104
|
-
}));
|
|
105
|
-
}
|
|
1
|
+
// Copyright 2026 xz333221
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
//
|
|
15
|
+
import { asyncRoute, HttpError } from '../utils/asyncRoute.js'
|
|
16
|
+
|
|
17
|
+
import logger from '../utils/logger.js'
|
|
18
|
+
|
|
19
|
+
export function registerBranchStatusRoutes({
|
|
20
|
+
app,
|
|
21
|
+
execGitCommand,
|
|
22
|
+
getIsGitRepo,
|
|
23
|
+
getBranchStatusCache,
|
|
24
|
+
setBranchStatusCache,
|
|
25
|
+
getRecentPushStatus,
|
|
26
|
+
setRecentPushStatus
|
|
27
|
+
}) {
|
|
28
|
+
// 获取当前分支 - 直接读取,不缓存(git symbolic-ref 极快,<5ms)
|
|
29
|
+
app.get('/api/branch', asyncRoute(async (req, res) => {
|
|
30
|
+
try {
|
|
31
|
+
const { stdout } = await execGitCommand(['symbolic-ref', '--short', 'HEAD']);
|
|
32
|
+
res.json({ branch: stdout.trim() });
|
|
33
|
+
} catch (error) {
|
|
34
|
+
res.status(500).json({ error: error.message });
|
|
35
|
+
}
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
// 获取分支与远程的差异状态(领先/落后提交数)
|
|
39
|
+
app.get('/api/branch-status', asyncRoute(async (req, res) => {
|
|
40
|
+
try {
|
|
41
|
+
if (!getIsGitRepo()) {
|
|
42
|
+
return res.json({ hasUpstream: false, ahead: 0, behind: 0 });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
const forceRefresh = req.query.force === 'true';
|
|
47
|
+
|
|
48
|
+
const recentPushStatus = getRecentPushStatus();
|
|
49
|
+
const branchStatusCache = getBranchStatusCache();
|
|
50
|
+
|
|
51
|
+
// push 后10秒内直接返回同步状态
|
|
52
|
+
if (recentPushStatus.justPushed &&
|
|
53
|
+
(now - recentPushStatus.pushTime) < recentPushStatus.validDuration) {
|
|
54
|
+
logger.info('检测到最近推送过,直接返回同步状态');
|
|
55
|
+
return res.json({
|
|
56
|
+
hasUpstream: true,
|
|
57
|
+
upstreamBranch: branchStatusCache.upstreamBranch || 'origin/main',
|
|
58
|
+
ahead: 0,
|
|
59
|
+
behind: 0
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 5秒缓存分支名,防止短时间内重复执行 git symbolic-ref / git rev-parse
|
|
64
|
+
const branchInfoCacheValid = !forceRefresh &&
|
|
65
|
+
branchStatusCache.currentBranch &&
|
|
66
|
+
branchStatusCache.upstreamBranch &&
|
|
67
|
+
(now - branchStatusCache.lastUpdate) < branchStatusCache.cacheTimeout;
|
|
68
|
+
|
|
69
|
+
let currentBranch, upstreamBranch;
|
|
70
|
+
|
|
71
|
+
if (branchInfoCacheValid) {
|
|
72
|
+
currentBranch = branchStatusCache.currentBranch;
|
|
73
|
+
upstreamBranch = branchStatusCache.upstreamBranch;
|
|
74
|
+
logger.info(`使用5秒缓存的分支名: ${currentBranch} -> ${upstreamBranch}`);
|
|
75
|
+
} else {
|
|
76
|
+
// 直接读取,不再走5分钟长缓存
|
|
77
|
+
const { stdout: branchOut } = await execGitCommand(['symbolic-ref', '--short', 'HEAD']);
|
|
78
|
+
currentBranch = branchOut.trim();
|
|
79
|
+
|
|
80
|
+
const { stdout: upstreamOut } = await execGitCommand(
|
|
81
|
+
['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'],
|
|
82
|
+
{ ignoreError: true }
|
|
83
|
+
);
|
|
84
|
+
upstreamBranch = upstreamOut.trim() || null;
|
|
85
|
+
|
|
86
|
+
if (!upstreamBranch) {
|
|
87
|
+
setBranchStatusCache({ currentBranch: null, upstreamBranch: null, lastUpdate: 0, cacheTimeout: 5000 });
|
|
88
|
+
return res.json({ hasUpstream: false, ahead: 0, behind: 0 });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setBranchStatusCache({ currentBranch, upstreamBranch, lastUpdate: now, cacheTimeout: 5000 });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const { stdout: aheadBehindOutput } = await execGitCommand(
|
|
95
|
+
['rev-list', '--left-right', '--count', `${currentBranch}...${upstreamBranch}`]
|
|
96
|
+
);
|
|
97
|
+
const [ahead, behind] = aheadBehindOutput.trim().split('\t').map(Number);
|
|
98
|
+
|
|
99
|
+
res.json({ hasUpstream: true, upstreamBranch, ahead, behind });
|
|
100
|
+
} catch (error) {
|
|
101
|
+
logger.error('获取分支状态失败:', error);
|
|
102
|
+
res.status(500).json({ error: error.message });
|
|
103
|
+
}
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
@@ -29,6 +29,8 @@ const TOOL_DOCS_URLS = Object.freeze({
|
|
|
29
29
|
claude: 'https://docs.anthropic.com/en/docs/claude-code/getting-started',
|
|
30
30
|
codex: 'https://help.openai.com/en/articles/11096431-openai-codex-cli-getting-started',
|
|
31
31
|
opencode: 'https://opencode.ai/docs',
|
|
32
|
+
kimi: 'https://moonshotai.github.io/kimi-code/en/guides/getting-started',
|
|
33
|
+
zcode: 'https://zcode.z.ai/',
|
|
32
34
|
});
|
|
33
35
|
|
|
34
36
|
function commandExists(command, platform = process.platform) {
|
|
@@ -104,6 +106,36 @@ export function getToolInstallers(platform = process.platform, hasCommand = comm
|
|
|
104
106
|
};
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
installers.kimi = {
|
|
110
|
+
supported: platform === 'win32' || platform === 'darwin' || platform === 'linux',
|
|
111
|
+
command: platform === 'win32'
|
|
112
|
+
? 'irm https://code.kimi.com/kimi-code/install.ps1 | iex'
|
|
113
|
+
: 'curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash',
|
|
114
|
+
packageManager: 'Kimi Code 官方安装脚本',
|
|
115
|
+
docsUrl: TOOL_DOCS_URLS.kimi,
|
|
116
|
+
note: '将打开终端执行官方安装脚本;Windows 首次使用还需要 Git for Windows。',
|
|
117
|
+
kind: 'script',
|
|
118
|
+
executionCommand: platform === 'win32'
|
|
119
|
+
? [
|
|
120
|
+
"if (-not (Get-Command Get-FileHash -ErrorAction SilentlyContinue)) {",
|
|
121
|
+
"function global:Get-FileHash { param([string]$Path, [string]$Algorithm = 'SHA256')",
|
|
122
|
+
"$stream = [System.IO.File]::OpenRead($Path)",
|
|
123
|
+
"try { $sha = [System.Security.Cryptography.SHA256]::Create(); try { $bytes = $sha.ComputeHash($stream) } finally { $sha.Dispose() } } finally { $stream.Dispose() }",
|
|
124
|
+
"$hash = ([System.BitConverter]::ToString($bytes)).Replace('-', '')",
|
|
125
|
+
"New-Object PSObject -Property @{ Algorithm = 'SHA256'; Hash = $hash; Path = $Path }",
|
|
126
|
+
"}",
|
|
127
|
+
"}",
|
|
128
|
+
'Invoke-RestMethod https://code.kimi.com/kimi-code/install.ps1 | Invoke-Expression',
|
|
129
|
+
].join('; ')
|
|
130
|
+
: undefined,
|
|
131
|
+
};
|
|
132
|
+
installers.zcode = {
|
|
133
|
+
supported: false,
|
|
134
|
+
command: '打开 ZCode 官方下载页,选择当前系统安装包',
|
|
135
|
+
packageManager: 'ZCode 官方安装包',
|
|
136
|
+
docsUrl: TOOL_DOCS_URLS.zcode,
|
|
137
|
+
note: 'ZCode 是桌面应用,官方提供 Windows、macOS 和 Linux 安装包,没有官方 npm CLI。',
|
|
138
|
+
};
|
|
107
139
|
return installers;
|
|
108
140
|
}
|
|
109
141
|
|
|
@@ -224,7 +256,115 @@ async function launchOpenCode(dirPath) {
|
|
|
224
256
|
return launchInTerminal(dirPath, 'opencode');
|
|
225
257
|
}
|
|
226
258
|
|
|
259
|
+
async function launchKimiCode(dirPath) {
|
|
260
|
+
if (process.platform === 'win32') {
|
|
261
|
+
const executable = await findKimiExecutable();
|
|
262
|
+
if (!executable) throw new Error('未检测到 Kimi Code,请先安装 kimi CLI');
|
|
263
|
+
return spawnDetached('cmd.exe', ['/c', 'start', '""', executable], { cwd: dirPath });
|
|
264
|
+
}
|
|
265
|
+
return launchInTerminal(dirPath, 'kimi');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function findKimiExecutable() {
|
|
269
|
+
if (process.platform !== 'win32') return commandExists('kimi') ? 'kimi' : null;
|
|
270
|
+
|
|
271
|
+
const candidates = [
|
|
272
|
+
path.join(process.env.USERPROFILE || '', '.kimi-code', 'bin', 'kimi.exe'),
|
|
273
|
+
];
|
|
274
|
+
const registryPath = spawnSync('reg.exe', ['query', 'HKCU\\Environment', '/v', 'Path'], {
|
|
275
|
+
encoding: 'utf8',
|
|
276
|
+
windowsHide: true,
|
|
277
|
+
}).stdout || '';
|
|
278
|
+
const pathLine = registryPath.split(/\r?\n/).find(line => /\sPath\s+REG_(?:EXPAND_)?SZ\s+/i.test(line));
|
|
279
|
+
if (pathLine) {
|
|
280
|
+
const userPath = pathLine.replace(/^.*?REG_(?:EXPAND_)?SZ\s+/i, '').trim();
|
|
281
|
+
for (const directory of userPath.split(';').map(value => value.trim()).filter(Boolean)) {
|
|
282
|
+
candidates.push(path.join(directory, 'kimi.exe'));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for (const candidate of candidates) {
|
|
287
|
+
try {
|
|
288
|
+
const stat = await fs.stat(candidate);
|
|
289
|
+
if (stat.isFile()) return candidate;
|
|
290
|
+
} catch {}
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function launchZCode(dirPath) {
|
|
296
|
+
const executable = await findZCodeExecutable();
|
|
297
|
+
if (!executable) throw new Error('未检测到 ZCode,请从官网下载桌面安装包');
|
|
298
|
+
return spawnDetached(executable, [dirPath]);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function findZCodeExecutable() {
|
|
302
|
+
const candidates = process.platform === 'win32'
|
|
303
|
+
? [
|
|
304
|
+
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'ZCode', 'ZCode.exe'),
|
|
305
|
+
path.join(process.env.LOCALAPPDATA || '', 'ZCode', 'ZCode.exe'),
|
|
306
|
+
path.join(process.env.PROGRAMFILES || '', 'ZCode', 'ZCode.exe'),
|
|
307
|
+
]
|
|
308
|
+
: process.platform === 'darwin'
|
|
309
|
+
? ['/Applications/ZCode.app/Contents/MacOS/ZCode']
|
|
310
|
+
: ['/usr/bin/zcode', '/usr/local/bin/zcode'];
|
|
311
|
+
|
|
312
|
+
if (process.platform === 'win32') {
|
|
313
|
+
const registryRoots = [
|
|
314
|
+
'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
|
|
315
|
+
'HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
|
|
316
|
+
'HKLM\\Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
|
|
317
|
+
];
|
|
318
|
+
for (const root of registryRoots) {
|
|
319
|
+
const output = spawnSync('reg.exe', ['query', root, '/s'], {
|
|
320
|
+
encoding: 'utf8',
|
|
321
|
+
windowsHide: true,
|
|
322
|
+
}).stdout || '';
|
|
323
|
+
for (const line of output.split(/\r?\n/)) {
|
|
324
|
+
if (!/DisplayIcon|InstallLocation/i.test(line) || !/zcode/i.test(line)) continue;
|
|
325
|
+
const value = line.replace(/^.*?REG_SZ\s+/i, '').trim().replace(/^"|"$/g, '').replace(/,\d+$/, '');
|
|
326
|
+
if (value && !/\.(ico|png|jpg|jpeg)$/i.test(value)) candidates.push(value);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const running = spawnSync('powershell.exe', [
|
|
330
|
+
'-NoLogo', '-NoProfile', '-Command',
|
|
331
|
+
"Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -match 'ZCode' } | Select-Object -ExpandProperty Path",
|
|
332
|
+
], { encoding: 'utf8', windowsHide: true }).stdout || '';
|
|
333
|
+
candidates.push(...running.split(/\r?\n/).map(value => value.trim()).filter(Boolean));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
for (const candidate of candidates.filter(Boolean)) {
|
|
337
|
+
const normalized = String(candidate).trim().replace(/^"|"$/g, '').replace(/,\d+$/, '');
|
|
338
|
+
if (/\.(ico|png|jpg|jpeg)$/i.test(normalized)) continue;
|
|
339
|
+
try {
|
|
340
|
+
const stat = await fs.stat(normalized);
|
|
341
|
+
if (stat.isFile()) return normalized;
|
|
342
|
+
if (stat.isDirectory()) {
|
|
343
|
+
for (const name of ['ZCode.exe', 'zcode.exe']) {
|
|
344
|
+
const executable = path.join(normalized, name);
|
|
345
|
+
try {
|
|
346
|
+
const executableStat = await fs.stat(executable);
|
|
347
|
+
if (executableStat.isFile()) return executable;
|
|
348
|
+
} catch {}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
} catch {}
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
|
|
227
356
|
async function launchToolInstaller(installer, dirPath = process.cwd()) {
|
|
357
|
+
if (installer.kind === 'script') {
|
|
358
|
+
if (process.platform === 'win32') {
|
|
359
|
+
// 显式创建新控制台;编码脚本可避免 cmd.exe 截获 PowerShell 管道符。
|
|
360
|
+
const encoded = Buffer.from(installer.executionCommand || installer.command, 'utf16le').toString('base64');
|
|
361
|
+
return spawnDetached('cmd.exe', [
|
|
362
|
+
'/c', 'start', '""', 'powershell.exe', '-NoLogo', '-NoExit',
|
|
363
|
+
'-ExecutionPolicy', 'Bypass', '-EncodedCommand', encoded,
|
|
364
|
+
], { cwd: dirPath });
|
|
365
|
+
}
|
|
366
|
+
return launchInTerminal(dirPath, 'bash', ['-lc', installer.command]);
|
|
367
|
+
}
|
|
228
368
|
if (process.platform === 'win32') {
|
|
229
369
|
// 安装命令完全来自服务端白名单。使用可见 cmd 窗口,让用户看到进度和错误。
|
|
230
370
|
const commandLine = [installer.executable, ...installer.args].join(' ');
|
|
@@ -516,6 +656,32 @@ export function registerFileOpenRoutes({
|
|
|
516
656
|
}
|
|
517
657
|
}));
|
|
518
658
|
|
|
659
|
+
// 用 Kimi Code 打开目录
|
|
660
|
+
app.post('/api/open-directory-with-kimi', asyncRoute(async (req, res) => {
|
|
661
|
+
const { path: dirPath } = req.body || {};
|
|
662
|
+
if (!dirPath) throw new HttpError(400, '目录路径不能为空');
|
|
663
|
+
try {
|
|
664
|
+
await fs.access(dirPath);
|
|
665
|
+
await launchKimiCode(dirPath);
|
|
666
|
+
res.json({ success: true, message: '已用 Kimi Code 打开目录' });
|
|
667
|
+
} catch (error) {
|
|
668
|
+
res.status(400).json({ success: false, error: error.message || '未检测到 Kimi Code,请先安装 kimi CLI' });
|
|
669
|
+
}
|
|
670
|
+
}));
|
|
671
|
+
|
|
672
|
+
// 用 ZCode 打开目录
|
|
673
|
+
app.post('/api/open-directory-with-zcode', asyncRoute(async (req, res) => {
|
|
674
|
+
const { path: dirPath } = req.body || {};
|
|
675
|
+
if (!dirPath) throw new HttpError(400, '目录路径不能为空');
|
|
676
|
+
try {
|
|
677
|
+
await fs.access(dirPath);
|
|
678
|
+
await launchZCode(dirPath);
|
|
679
|
+
res.json({ success: true, message: '已用 ZCode 打开目录' });
|
|
680
|
+
} catch (error) {
|
|
681
|
+
res.status(400).json({ success: false, error: error.message || '未检测到 ZCode,请从官网下载桌面安装包' });
|
|
682
|
+
}
|
|
683
|
+
}));
|
|
684
|
+
|
|
519
685
|
// 安装本地工具:前端只能传固定 tool id,实际命令由服务端按平台从白名单选择。
|
|
520
686
|
app.post('/api/install-tool', asyncRoute(async (req, res) => {
|
|
521
687
|
const { tool } = req.body || {};
|
|
@@ -559,11 +725,13 @@ export function registerFileOpenRoutes({
|
|
|
559
725
|
setTimeout(() => finish(false), 15000);
|
|
560
726
|
});
|
|
561
727
|
|
|
562
|
-
const [vscode, claude, codex, opencode] = await Promise.all([
|
|
728
|
+
const [vscode, claude, codex, opencode, kimiExecutable, zcodeExecutable] = await Promise.all([
|
|
563
729
|
checkCmd('code'),
|
|
564
730
|
checkCmd('claude'),
|
|
565
731
|
checkCmd('codex'),
|
|
566
732
|
checkCmd('opencode'),
|
|
733
|
+
findKimiExecutable(),
|
|
734
|
+
findZCodeExecutable(),
|
|
567
735
|
]);
|
|
568
736
|
|
|
569
737
|
const installers = getToolInstallers();
|
|
@@ -575,6 +743,8 @@ export function registerFileOpenRoutes({
|
|
|
575
743
|
claude,
|
|
576
744
|
codex,
|
|
577
745
|
opencode,
|
|
746
|
+
kimi: !!kimiExecutable,
|
|
747
|
+
zcode: !!zcodeExecutable,
|
|
578
748
|
});
|
|
579
749
|
}));
|
|
580
750
|
}
|
|
@@ -20,7 +20,7 @@ import path from 'path';
|
|
|
20
20
|
import open from 'open';
|
|
21
21
|
import os from 'os';
|
|
22
22
|
import { spawn, exec, execSync } from 'child_process';
|
|
23
|
-
import { ensureWithinCwd } from '../utils/pathGuard.js';
|
|
23
|
+
import { ensureWithinCwd, normalizeProjectPath } from '../utils/pathGuard.js';
|
|
24
24
|
import { asyncRoute, HttpError } from '../utils/asyncRoute.js';
|
|
25
25
|
import { invalidateCurrentProjectKey, invalidateRawConfigCache } from '../../../config.js';
|
|
26
26
|
import { invalidateCwdCache } from '../../../utils/index.js';
|
|
@@ -148,7 +148,7 @@ export function registerFsRoutes({
|
|
|
148
148
|
invalidateCurrentProjectKey();
|
|
149
149
|
invalidateRawConfigCache();
|
|
150
150
|
invalidateCwdCache();
|
|
151
|
-
const newDirectory = process.cwd();
|
|
151
|
+
const newDirectory = normalizeProjectPath(process.cwd());
|
|
152
152
|
|
|
153
153
|
// 更新当前项目路径和房间ID
|
|
154
154
|
const oldProjectPath = getCurrentProjectPath();
|