@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ian2018cs/agenthub",
3
- "version": "0.1.55",
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/claudecodeui",
22
+ "homepage": "https://github.com/IAn2018cs/AgentHub",
23
23
  "repository": {
24
24
  "type": "git",
25
- "url": "git+https://github.com/IAn2018cs/claudecodeui.git"
25
+ "url": "git+https://github.com/IAn2018cs/AgentHub.git"
26
26
  },
27
27
  "bugs": {
28
- "url": "https://github.com/IAn2018cs/claudecodeui/issues"
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": "Claude Code UI Contributors",
46
+ "author": "IAn2018cs",
48
47
  "license": "MIT",
49
48
  "publishConfig": {
50
49
  "registry": "https://registry.npmjs.org",
@@ -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
- ...(options.appendSystemPrompt ? { append: options.appendSystemPrompt } : {}),
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('cloudcli --port 8080')} to run on a custom port`);
139
- console.log(` ${c.dim('>')} Use ${c.bright('cloudcli --database-path /path/to/db')} for custom database`);
140
- console.log(` ${c.dim('>')} Run ${c.bright('cloudcli help')} for all options`);
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
- claude-code-ui [command] [options]
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
- $ cloudcli # Start with defaults
171
- $ cloudcli --port 8080 # Start on port 8080
172
- $ cloudcli -p 3000 # Short form for port
173
- $ cloudcli start --port 4000 # Explicit start command
174
- $ cloudcli status # Show configuration
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/siteboon/claudecodeui'}
182
+ ${packageJson.homepage || 'https://github.com/IAn2018cs/AgentHub'}
184
183
 
185
184
  Report Issues:
186
- ${packageJson.bugs?.url || 'https://github.com/siteboon/claudecodeui/issues'}
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/claudecodeui';
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: { 'User-Agent': 'agenthub-cli' } },
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/claudecodeui/releases/download/v${latestVersion}/${tgzName}`;
274
- execSync(`npm install -g "${url}"`, { stdio: 'inherit' });
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/claudecodeui/releases`);
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 "cloudcli help" for usage information.\n');
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 "cloudcli status" for full configuration details`);
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
  }
@@ -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 or Cursor projects in the UI
891
+ // existing Claude Code projects in the UI
892
892
 
893
893
  const createdAt = new Date().toISOString();
894
894
 
@@ -2,7 +2,7 @@
2
2
  * command-handler.js — 飞书斜杠命令处理
3
3
  *
4
4
  * 支持命令:
5
- * /auth <token> 绑定 claudecodeui 账号
5
+ * /auth <token> 绑定 agenthub 账号
6
6
  * /unbind 解除绑定
7
7
  * /new 新建 Claude 会话
8
8
  * /list 列出当前项目的会话
@@ -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 claudecodeui 用户 UUID
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(content)) !== null) {
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
- // 如果有 --user 标志,允许
451
+ // --user 仍然写入服务器宿主机 ~/.local/,多用户平台下污染共享环境
434
452
  if (/--user\b/.test(command)) {
435
- return { result: 'allow' };
453
+ return { result: 'deny', reason: '不允许使用 pip install --user(会写入服务器共享的 ~/.local/),请在虚拟环境内安装' };
436
454
  }
437
455
  // 其他情况交给 LLM 判断
438
456
  return { result: 'uncertain', reason: 'pip install 可能在虚拟环境外执行,需要 LLM 审查' };