@ian2018cs/agenthub 0.1.55 → 0.1.56
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 +173 -226
- package/dist/assets/index-Darx5qlF.js +162 -0
- package/dist/assets/index-gz7uqQ7s.css +32 -0
- package/dist/assets/{vendor-icons-D0_WToWG.js → vendor-icons-twCb5Vhp.js} +72 -87
- package/dist/index.html +3 -18
- package/package.json +6 -7
- package/server/claude-sdk.js +3 -1
- package/server/cli.js +61 -18
- package/server/index.js +1 -15
- package/server/middleware/auth.js +0 -31
- package/server/projects.js +1 -1
- package/server/services/feishu/command-handler.js +1 -1
- package/server/services/feishu/sdk-bridge.js +1 -1
- package/server/services/tool-guard/llm-reviewer.js +1 -1
- package/server/services/tool-guard/rules.js +22 -4
- package/dist/assets/index-8gJOK0K5.js +0 -162
- package/dist/assets/index-BdtjtPre.css +0 -32
- package/dist/clear-cache.html +0 -85
- package/dist/icons/cursor-white.svg +0 -12
- package/dist/icons/cursor.svg +0 -1
- package/dist/screenshots/cli-selection.png +0 -0
- package/dist/screenshots/desktop-main.png +0 -0
- package/dist/screenshots/mobile-chat.png +0 -0
- package/dist/screenshots/tools-modal.png +0 -0
- package/dist/sw.js +0 -49
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ian2018cs/agenthub",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.56",
|
|
4
4
|
"description": "A web-based UI for AI Agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/index.js",
|
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
"dist/",
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
|
-
"homepage": "https://github.com/IAn2018cs/
|
|
22
|
+
"homepage": "https://github.com/IAn2018cs/AgentHub",
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "git+https://github.com/IAn2018cs/
|
|
25
|
+
"url": "git+https://github.com/IAn2018cs/AgentHub.git"
|
|
26
26
|
},
|
|
27
27
|
"bugs": {
|
|
28
|
-
"url": "https://github.com/IAn2018cs/
|
|
28
|
+
"url": "https://github.com/IAn2018cs/AgentHub/issues"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"dev": "concurrently --kill-others \"npm run server\" \"npm run client\"",
|
|
@@ -34,8 +34,7 @@
|
|
|
34
34
|
"build": "vite build",
|
|
35
35
|
"preview": "vite preview",
|
|
36
36
|
"start": "npm run build && npm run server",
|
|
37
|
-
"release": "./release.sh"
|
|
38
|
-
"setup-feishu-cards": "node scripts/setup-feishu-cards.mjs"
|
|
37
|
+
"release": "./release.sh"
|
|
39
38
|
},
|
|
40
39
|
"keywords": [
|
|
41
40
|
"claude coode",
|
|
@@ -44,7 +43,7 @@
|
|
|
44
43
|
"ui",
|
|
45
44
|
"mobile"
|
|
46
45
|
],
|
|
47
|
-
"author": "
|
|
46
|
+
"author": "IAn2018cs",
|
|
48
47
|
"license": "MIT",
|
|
49
48
|
"publishConfig": {
|
|
50
49
|
"registry": "https://registry.npmjs.org",
|
package/server/claude-sdk.js
CHANGED
|
@@ -205,10 +205,12 @@ function mapCliOptionsToSDK(options = {}) {
|
|
|
205
205
|
console.log(`Using model: ${sdkOptions.model}`);
|
|
206
206
|
|
|
207
207
|
// Map system prompt configuration
|
|
208
|
+
const baseAppend = 'You are an AI assistant running inside AgentHub, a platform that supports diverse work tasks beyond coding.';
|
|
209
|
+
const extraAppend = options.appendSystemPrompt ? `\n\n${options.appendSystemPrompt}` : '';
|
|
208
210
|
sdkOptions.systemPrompt = {
|
|
209
211
|
type: 'preset',
|
|
210
212
|
preset: 'claude_code', // Required to use CLAUDE.md
|
|
211
|
-
|
|
213
|
+
append: baseAppend + extraAppend,
|
|
212
214
|
};
|
|
213
215
|
|
|
214
216
|
// Map setting sources for CLAUDE.md loading
|
package/server/cli.js
CHANGED
|
@@ -135,9 +135,9 @@ function showStatus() {
|
|
|
135
135
|
|
|
136
136
|
console.log('\n' + c.dim('═'.repeat(60)));
|
|
137
137
|
console.log(`\n${c.tip('[TIP]')} Hints:`);
|
|
138
|
-
console.log(` ${c.dim('>')} Use ${c.bright('
|
|
139
|
-
console.log(` ${c.dim('>')} Use ${c.bright('
|
|
140
|
-
console.log(` ${c.dim('>')} Run ${c.bright('
|
|
138
|
+
console.log(` ${c.dim('>')} Use ${c.bright('agenthub --port 8080')} to run on a custom port`);
|
|
139
|
+
console.log(` ${c.dim('>')} Use ${c.bright('agenthub --database-path /path/to/db')} for custom database`);
|
|
140
|
+
console.log(` ${c.dim('>')} Run ${c.bright('agenthub help')} for all options`);
|
|
141
141
|
console.log(` ${c.dim('>')} Access the UI at http://localhost:${process.env.PORT || '3001'}\n`);
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -149,8 +149,7 @@ function showHelp() {
|
|
|
149
149
|
╚═══════════════════════════════════════════════════════════════╝
|
|
150
150
|
|
|
151
151
|
Usage:
|
|
152
|
-
|
|
153
|
-
cloudcli [command] [options]
|
|
152
|
+
agenthub [command] [options]
|
|
154
153
|
|
|
155
154
|
Commands:
|
|
156
155
|
start Start the Claude Code UI server (default)
|
|
@@ -167,11 +166,11 @@ Options:
|
|
|
167
166
|
-v, --version Show version information
|
|
168
167
|
|
|
169
168
|
Examples:
|
|
170
|
-
$
|
|
171
|
-
$
|
|
172
|
-
$
|
|
173
|
-
$
|
|
174
|
-
$
|
|
169
|
+
$ agenthub # Start with defaults
|
|
170
|
+
$ agenthub --port 8080 # Start on port 8080
|
|
171
|
+
$ agenthub -p 3000 # Short form for port
|
|
172
|
+
$ agenthub start --port 4000 # Explicit start command
|
|
173
|
+
$ agenthub status # Show configuration
|
|
175
174
|
|
|
176
175
|
Environment Variables:
|
|
177
176
|
PORT Set server port (default: 3001)
|
|
@@ -180,10 +179,10 @@ Environment Variables:
|
|
|
180
179
|
CONTEXT_WINDOW Set context window size (default: 160000)
|
|
181
180
|
|
|
182
181
|
Documentation:
|
|
183
|
-
${packageJson.homepage || 'https://github.com/
|
|
182
|
+
${packageJson.homepage || 'https://github.com/IAn2018cs/AgentHub'}
|
|
184
183
|
|
|
185
184
|
Report Issues:
|
|
186
|
-
${packageJson.bugs?.url || 'https://github.com/
|
|
185
|
+
${packageJson.bugs?.url || 'https://github.com/IAn2018cs/AgentHub/issues'}
|
|
187
186
|
`);
|
|
188
187
|
}
|
|
189
188
|
|
|
@@ -203,17 +202,49 @@ function isNewerVersion(v1, v2) {
|
|
|
203
202
|
return false;
|
|
204
203
|
}
|
|
205
204
|
|
|
205
|
+
// Download a file with optional GitHub token auth, following redirects
|
|
206
|
+
async function downloadWithAuth(url, destPath, token) {
|
|
207
|
+
const https = await import('https');
|
|
208
|
+
const fs = await import('fs');
|
|
209
|
+
return new Promise((resolve, reject) => {
|
|
210
|
+
const headers = { 'User-Agent': 'agenthub-cli' };
|
|
211
|
+
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
212
|
+
const download = (downloadUrl) => {
|
|
213
|
+
const req = https.get(downloadUrl, { headers }, (res) => {
|
|
214
|
+
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
215
|
+
download(res.headers.location);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (res.statusCode !== 200) {
|
|
219
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const file = fs.createWriteStream(destPath);
|
|
223
|
+
res.pipe(file);
|
|
224
|
+
file.on('finish', () => file.close(resolve));
|
|
225
|
+
file.on('error', reject);
|
|
226
|
+
});
|
|
227
|
+
req.on('error', reject);
|
|
228
|
+
req.setTimeout(60000, () => { req.destroy(); reject(new Error('Download timeout')); });
|
|
229
|
+
};
|
|
230
|
+
download(url);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
206
234
|
// Check for updates via GitHub Releases
|
|
207
235
|
async function checkForUpdates(silent = false) {
|
|
208
236
|
try {
|
|
209
237
|
const https = await import('https');
|
|
210
|
-
const REPO = 'IAn2018cs/
|
|
238
|
+
const REPO = 'IAn2018cs/AgentHub';
|
|
211
239
|
const currentVersion = packageJson.version;
|
|
240
|
+
const githubToken = process.env.GITHUB_TOKEN;
|
|
241
|
+
const headers = { 'User-Agent': 'agenthub-cli' };
|
|
242
|
+
if (githubToken) headers['Authorization'] = `Bearer ${githubToken}`;
|
|
212
243
|
|
|
213
244
|
const latestVersion = await new Promise((resolve, reject) => {
|
|
214
245
|
const req = https.get(
|
|
215
246
|
`https://api.github.com/repos/${REPO}/releases/latest`,
|
|
216
|
-
{ headers
|
|
247
|
+
{ headers },
|
|
217
248
|
(res) => {
|
|
218
249
|
let data = '';
|
|
219
250
|
res.on('data', chunk => data += chunk);
|
|
@@ -270,15 +301,27 @@ async function updatePackage() {
|
|
|
270
301
|
|
|
271
302
|
console.log(`${c.info('[INFO]')} Updating from ${currentVersion} to ${latestVersion}...`);
|
|
272
303
|
const tgzName = `ian2018cs-agenthub-${latestVersion}.tgz`;
|
|
273
|
-
const url = `https://github.com/IAn2018cs/
|
|
274
|
-
|
|
304
|
+
const url = `https://github.com/IAn2018cs/AgentHub/releases/download/v${latestVersion}/${tgzName}`;
|
|
305
|
+
const githubToken = process.env.GITHUB_TOKEN;
|
|
306
|
+
if (githubToken) {
|
|
307
|
+
const os = await import('os');
|
|
308
|
+
const path = await import('path');
|
|
309
|
+
const fsSync = await import('fs');
|
|
310
|
+
const tmpFile = path.join(os.tmpdir(), tgzName);
|
|
311
|
+
console.log(`${c.info('[INFO]')} Downloading with GitHub token...`);
|
|
312
|
+
await downloadWithAuth(url, tmpFile, githubToken);
|
|
313
|
+
execSync(`npm install -g "${tmpFile}"`, { stdio: 'inherit' });
|
|
314
|
+
fsSync.unlinkSync(tmpFile);
|
|
315
|
+
} else {
|
|
316
|
+
execSync(`npm install -g "${url}"`, { stdio: 'inherit' });
|
|
317
|
+
}
|
|
275
318
|
console.log(`${c.ok('[OK]')} Update complete!`);
|
|
276
319
|
console.log(` Please restart the server to use the new version:`);
|
|
277
320
|
console.log(` - pm2: ${c.bright('pm2 restart agenthub')} or ${c.bright('./pm2.sh restart')}`);
|
|
278
321
|
console.log(` - manual: stop and run ${c.bright('agenthub')} again`);
|
|
279
322
|
} catch (e) {
|
|
280
323
|
console.error(`${c.error('[ERROR]')} Update failed: ${e.message}`);
|
|
281
|
-
console.log(`${c.tip('[TIP]')} Check releases: https://github.com/IAn2018cs/
|
|
324
|
+
console.log(`${c.tip('[TIP]')} Check releases: https://github.com/IAn2018cs/AgentHub/releases`);
|
|
282
325
|
}
|
|
283
326
|
}
|
|
284
327
|
|
|
@@ -392,7 +435,7 @@ async function main() {
|
|
|
392
435
|
break;
|
|
393
436
|
default:
|
|
394
437
|
console.error(`\n❌ Unknown command: ${command}`);
|
|
395
|
-
console.log(' Run "
|
|
438
|
+
console.log(' Run "agenthub help" for usage information.\n');
|
|
396
439
|
process.exit(1);
|
|
397
440
|
}
|
|
398
441
|
}
|
package/server/index.js
CHANGED
|
@@ -38,7 +38,6 @@ import os from 'os';
|
|
|
38
38
|
import http from 'http';
|
|
39
39
|
import cors from 'cors';
|
|
40
40
|
import { promises as fsPromises } from 'fs';
|
|
41
|
-
import { spawn } from 'child_process';
|
|
42
41
|
import pty from 'node-pty';
|
|
43
42
|
import fetch from 'node-fetch';
|
|
44
43
|
import mime from 'mime-types';
|
|
@@ -225,19 +224,6 @@ const wss = new WebSocketServer({
|
|
|
225
224
|
verifyClient: (info) => {
|
|
226
225
|
console.log('WebSocket connection attempt to:', info.req.url);
|
|
227
226
|
|
|
228
|
-
// Platform mode: always allow connection
|
|
229
|
-
if (process.env.VITE_IS_PLATFORM === 'true') {
|
|
230
|
-
const user = authenticateWebSocket(null); // Will return first user
|
|
231
|
-
if (!user) {
|
|
232
|
-
console.log('[WARN] Platform mode: No user found in database');
|
|
233
|
-
return false;
|
|
234
|
-
}
|
|
235
|
-
info.req.user = user;
|
|
236
|
-
console.log('[OK] Platform mode WebSocket authenticated for user:', user.username);
|
|
237
|
-
return true;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Normal mode: verify token
|
|
241
227
|
// Extract token from query parameters or headers
|
|
242
228
|
const url = new URL(info.req.url, 'http://localhost');
|
|
243
229
|
const token = url.searchParams.get('token') ||
|
|
@@ -2359,7 +2345,7 @@ async function startServer() {
|
|
|
2359
2345
|
console.log('');
|
|
2360
2346
|
console.log(`${c.info('[INFO]')} Server URL: ${c.bright('http://0.0.0.0:' + PORT)}`);
|
|
2361
2347
|
console.log(`${c.info('[INFO]')} Installed at: ${c.dim(appInstallPath)}`);
|
|
2362
|
-
console.log(`${c.tip('[TIP]')} Run "
|
|
2348
|
+
console.log(`${c.tip('[TIP]')} Run "agenthub status" for full configuration details`);
|
|
2363
2349
|
console.log('');
|
|
2364
2350
|
|
|
2365
2351
|
// Start usage scanner service
|
|
@@ -20,22 +20,6 @@ const validateApiKey = (req, res, next) => {
|
|
|
20
20
|
|
|
21
21
|
// JWT authentication middleware
|
|
22
22
|
const authenticateToken = async (req, res, next) => {
|
|
23
|
-
// Platform mode: use single database user
|
|
24
|
-
if (process.env.VITE_IS_PLATFORM === 'true') {
|
|
25
|
-
try {
|
|
26
|
-
const user = userDb.getFirstUser();
|
|
27
|
-
if (!user) {
|
|
28
|
-
return res.status(500).json({ error: 'Platform mode: No user found in database' });
|
|
29
|
-
}
|
|
30
|
-
req.user = user;
|
|
31
|
-
return next();
|
|
32
|
-
} catch (error) {
|
|
33
|
-
console.error('Platform mode error:', error);
|
|
34
|
-
return res.status(500).json({ error: 'Platform mode: Failed to fetch user' });
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Normal OSS JWT validation
|
|
39
23
|
const authHeader = req.headers['authorization'];
|
|
40
24
|
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
|
41
25
|
|
|
@@ -88,21 +72,6 @@ const generateToken = (user) => {
|
|
|
88
72
|
|
|
89
73
|
// WebSocket authentication function
|
|
90
74
|
const authenticateWebSocket = (token) => {
|
|
91
|
-
// Platform mode: bypass token validation, return first user
|
|
92
|
-
if (process.env.VITE_IS_PLATFORM === 'true') {
|
|
93
|
-
try {
|
|
94
|
-
const user = userDb.getFirstUser();
|
|
95
|
-
if (user) {
|
|
96
|
-
return { userId: user.id, username: user.username };
|
|
97
|
-
}
|
|
98
|
-
return null;
|
|
99
|
-
} catch (error) {
|
|
100
|
-
console.error('Platform mode WebSocket error:', error);
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Normal OSS JWT validation
|
|
106
75
|
if (!token) {
|
|
107
76
|
return null;
|
|
108
77
|
}
|
package/server/projects.js
CHANGED
|
@@ -888,7 +888,7 @@ async function addProjectManually(projectPath, displayName = null, userUuid) {
|
|
|
888
888
|
}
|
|
889
889
|
|
|
890
890
|
// Allow adding projects even if the directory exists - this enables tracking
|
|
891
|
-
// existing Claude Code
|
|
891
|
+
// existing Claude Code projects in the UI
|
|
892
892
|
|
|
893
893
|
const createdAt = new Date().toISOString();
|
|
894
894
|
|
|
@@ -397,7 +397,7 @@ class FakeSendWriter {
|
|
|
397
397
|
* @param {string} opts.messageId 原始消息 ID
|
|
398
398
|
* @param {string} opts.content 用户输入文字
|
|
399
399
|
* @param {Array|null} opts.images 图片附件(已转换为 data URI 格式)
|
|
400
|
-
* @param {string} opts.userUuid
|
|
400
|
+
* @param {string} opts.userUuid agenthub 用户 UUID
|
|
401
401
|
* @param {Object} opts.state feishu_session_state 行
|
|
402
402
|
* @param {Object} opts.larkClient LarkClient 实例
|
|
403
403
|
* @param {Map} opts.pendingApprovals engine 共享的 pendingApprovals Map
|
|
@@ -73,7 +73,7 @@ function buildSystemPrompt(userUuid, allowedPrefixes, cwd) {
|
|
|
73
73
|
2. 临时目录(/tmp/、/var/tmp/)必须使用用户 ID 子文件夹(/tmp/${userUuid}/)
|
|
74
74
|
3. 不允许修改系统配置(nginx、docker、systemd、防火墙等)
|
|
75
75
|
4. 不允许执行危险命令(sudo、kill 系统进程、rm -rf /、shutdown 等)
|
|
76
|
-
5. 不允许全局安装包(npm -g、pip
|
|
76
|
+
5. 不允许全局安装包(npm -g、pip 非虚拟环境、pip --user 等);pip 必须在虚拟环境内执行(venv/bin/pip 或 .venv/bin/pip)
|
|
77
77
|
6. 允许的操作:运行用户自己项目(node/npm/python)、本地安装依赖、git 操作、创建虚拟环境
|
|
78
78
|
|
|
79
79
|
## 判断原则
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import path from 'path';
|
|
11
|
-
import { readFileSync, statSync } from 'fs';
|
|
11
|
+
import { readFileSync, statSync, existsSync } from 'fs';
|
|
12
12
|
|
|
13
13
|
// ─── 脚本内容审查模式(供 script-execution-guard 和 write-content-guard 复用)───
|
|
14
14
|
|
|
@@ -77,15 +77,27 @@ function checkSystemPortsInContent(content) {
|
|
|
77
77
|
* 检查文本内容中的绝对路径是否越界(复用白名单逻辑)
|
|
78
78
|
*/
|
|
79
79
|
function checkPathsInContent(content, context) {
|
|
80
|
+
// 去除注释行(# 开头的行),避免示例路径被误判
|
|
81
|
+
const nonCommentContent = content
|
|
82
|
+
.split('\n')
|
|
83
|
+
.filter(line => !/^\s*#/.test(line))
|
|
84
|
+
.join('\n');
|
|
85
|
+
|
|
80
86
|
const pathPattern = /(?:^|\s|=|"|')(\/[^\s"'`;|&><){}$]+)/gm;
|
|
81
87
|
let match;
|
|
82
|
-
while ((match = pathPattern.exec(
|
|
88
|
+
while ((match = pathPattern.exec(nonCommentContent)) !== null) {
|
|
83
89
|
const resolvedPath = path.resolve(match[1]);
|
|
84
90
|
// 安全路径跳过
|
|
85
91
|
if (SAFE_SPECIAL_PATHS.includes(resolvedPath)) continue;
|
|
86
92
|
if (SAFE_COMMAND_PATHS.some(prefix => resolvedPath.startsWith(prefix))) continue;
|
|
87
93
|
if (isPathAllowed(resolvedPath, context)) continue;
|
|
88
94
|
if (isInTempDir(resolvedPath, context)) continue;
|
|
95
|
+
// 占位符/示例路径跳过
|
|
96
|
+
if (PLACEHOLDER_PATH_PREFIXES.some(prefix => resolvedPath.startsWith(prefix))) continue;
|
|
97
|
+
// API 端点路径跳过(/v1/..., /api/... 等,不是文件系统路径)
|
|
98
|
+
if (API_PATH_PATTERN.test(resolvedPath)) continue;
|
|
99
|
+
// 路径在文件系统上不存在 → 不是真实文件(API 路径、变量占位符等),无法被访问
|
|
100
|
+
if (!existsSync(resolvedPath)) continue;
|
|
89
101
|
return { denied: true, reason: `访问项目目录之外的路径: ${resolvedPath}` };
|
|
90
102
|
}
|
|
91
103
|
return { denied: false };
|
|
@@ -148,6 +160,12 @@ const SAFE_COMMAND_PATHS = ['/usr/bin/', '/usr/local/bin/', '/bin/', '/sbin/', '
|
|
|
148
160
|
/** 安全的特殊路径 */
|
|
149
161
|
const SAFE_SPECIAL_PATHS = ['/dev/null', '/dev/stdin', '/dev/stdout', '/dev/stderr', '/dev/zero', '/dev/urandom', '/dev/random'];
|
|
150
162
|
|
|
163
|
+
/** 常见占位符路径前缀(示例/文档用途,不应被视为真实访问路径) */
|
|
164
|
+
const PLACEHOLDER_PATH_PREFIXES = ['/path/to/', '/your/', '/example/', '/placeholder/'];
|
|
165
|
+
|
|
166
|
+
/** API 路径模式:匹配 REST API 风格路径(如 /v1/images/generations),不是文件系统路径 */
|
|
167
|
+
const API_PATH_PATTERN = /^\/(?:v\d+|api|graphql|rpc|rest|openapi|swagger)\//;
|
|
168
|
+
|
|
151
169
|
/**
|
|
152
170
|
* 检查单个文件路径的安全性
|
|
153
171
|
* @returns {{ result: 'allow'|'deny'|'uncertain', reason?: string }}
|
|
@@ -430,9 +448,9 @@ const rules = [
|
|
|
430
448
|
if (/venv\/bin\/pip|\.venv\/bin\/pip|virtualenv/.test(command)) {
|
|
431
449
|
return { result: 'allow' };
|
|
432
450
|
}
|
|
433
|
-
//
|
|
451
|
+
// --user 仍然写入服务器宿主机 ~/.local/,多用户平台下污染共享环境
|
|
434
452
|
if (/--user\b/.test(command)) {
|
|
435
|
-
return { result: '
|
|
453
|
+
return { result: 'deny', reason: '不允许使用 pip install --user(会写入服务器共享的 ~/.local/),请在虚拟环境内安装' };
|
|
436
454
|
}
|
|
437
455
|
// 其他情况交给 LLM 判断
|
|
438
456
|
return { result: 'uncertain', reason: 'pip install 可能在虚拟环境外执行,需要 LLM 审查' };
|