feedback-mcp 1.0.64__py3-none-any.whl

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.

Potentially problematic release.


This version of feedback-mcp might be problematic. Click here for more details.

ide_utils.py ADDED
@@ -0,0 +1,313 @@
1
+ """
2
+ IDE 操作工具模块
3
+ 提供打开各种IDE的通用功能
4
+ """
5
+ import os
6
+ import subprocess
7
+ import platform
8
+ import time
9
+ import shutil
10
+ from typing import Optional, Dict, Any
11
+
12
+
13
+ def open_project_with_ide(project_path: str, ide_name: str = None) -> bool:
14
+ """
15
+ 使用指定IDE打开项目
16
+
17
+ Args:
18
+ project_path: 项目路径
19
+ ide_name: IDE名称(cursor/kiro/vscode),如果为None则使用系统默认
20
+
21
+ Returns:
22
+ bool: 操作是否成功
23
+ """
24
+ if not project_path or not os.path.exists(project_path):
25
+ return False
26
+
27
+ if not ide_name:
28
+ return False # 不设置默认IDE,必须明确指定
29
+
30
+ # 获取IDE命令
31
+ ide_command = get_ide_command(ide_name)
32
+ if not ide_command:
33
+ return False
34
+
35
+ # 检查IDE是否可用
36
+ if not is_ide_available(ide_name):
37
+ return False
38
+
39
+ try:
40
+ system = platform.system()
41
+
42
+ if system == 'Darwin': # macOS
43
+ return _open_ide_macos(project_path, ide_name, ide_command)
44
+ elif system == 'Windows': # Windows
45
+ return _open_ide_windows(project_path, ide_name, ide_command)
46
+ else: # Linux
47
+ return _open_ide_linux(project_path, ide_name, ide_command)
48
+
49
+ except Exception as e:
50
+ print(f"打开IDE异常: {e}")
51
+ return False
52
+
53
+
54
+ def get_ide_command(ide_name: str) -> str:
55
+ """
56
+ 获取IDE的启动命令
57
+
58
+ Args:
59
+ ide_name: IDE名称
60
+
61
+ Returns:
62
+ str: IDE启动命令
63
+ """
64
+ # 支持预设的IDE映射
65
+ ide_commands = {
66
+ "cursor": "cursor",
67
+ "kiro": "kiro",
68
+ "vscode": "code"
69
+ }
70
+
71
+ # 如果在预设列表中,使用映射的命令
72
+ if ide_name.lower() in ide_commands:
73
+ return ide_commands[ide_name.lower()]
74
+
75
+ # 否则直接使用IDE名称作为命令(支持动态IDE)
76
+ return ide_name.lower()
77
+
78
+
79
+ def is_ide_available(ide_name: str) -> bool:
80
+ """
81
+ 检查指定IDE是否可用
82
+
83
+ Args:
84
+ ide_name: IDE名称
85
+
86
+ Returns:
87
+ bool: IDE是否可用
88
+ """
89
+ import platform
90
+ import os
91
+
92
+ command = get_ide_command(ide_name)
93
+
94
+ # 首先检查命令行工具
95
+ if shutil.which(command) is not None:
96
+ return True
97
+
98
+ # 针对不同操作系统的特殊处理
99
+ system = platform.system()
100
+
101
+ if system == 'Darwin': # macOS
102
+ app_map = {
103
+ "cursor": "Cursor",
104
+ "kiro": "Kiro",
105
+ "code": "Visual Studio Code",
106
+ "vscode": "Visual Studio Code" # 修复VS Code检测
107
+ }
108
+
109
+ # 使用command而不是ide_name进行查找(修复bug)
110
+ if command in app_map:
111
+ app_name = app_map[command]
112
+ else:
113
+ # 动态IDE支持:尝试使用IDE名称的首字母大写形式
114
+ app_name = ide_name.capitalize() if ide_name.islower() else ide_name
115
+
116
+ # 检查应用程序包是否存在
117
+ app_paths = [
118
+ f"/Applications/{app_name}.app",
119
+ f"/Users/{os.environ.get('USER', 'user')}/Applications/{app_name}.app"
120
+ ]
121
+
122
+ for app_path in app_paths:
123
+ if os.path.exists(app_path):
124
+ return True
125
+
126
+ elif system == 'Windows': # Windows
127
+ app_map = {
128
+ "cursor": "Cursor",
129
+ "kiro": "Kiro",
130
+ "code": "Code",
131
+ "vscode": "Code" # 修复VS Code检测
132
+ }
133
+
134
+ # 使用command而不是ide_name进行查找(修复bug)
135
+ if command in app_map:
136
+ app_name = app_map[command]
137
+ else:
138
+ # 动态IDE支持:尝试使用IDE名称的首字母大写形式
139
+ app_name = ide_name.capitalize() if ide_name.islower() else ide_name
140
+
141
+ # 检查常见的Windows安装路径
142
+ common_paths = [
143
+ f"C:\\Users\\{os.environ.get('USERNAME', 'user')}\\AppData\\Local\\Programs\\{app_name}\\{app_name}.exe",
144
+ f"C:\\Program Files\\{app_name}\\{app_name}.exe",
145
+ f"C:\\Program Files (x86)\\{app_name}\\{app_name}.exe"
146
+ ]
147
+
148
+ for path in common_paths:
149
+ if os.path.exists(path):
150
+ return True
151
+
152
+ elif system == 'Linux': # Linux
153
+ app_map = {
154
+ "cursor": "cursor",
155
+ "kiro": "kiro",
156
+ "code": "code",
157
+ "vscode": "code" # 修复VS Code检测
158
+ }
159
+
160
+ # 使用command而不是ide_name进行查找(修复bug)
161
+ if command in app_map:
162
+ app_name = app_map[command]
163
+ else:
164
+ # 动态IDE支持:直接使用命令名
165
+ app_name = command
166
+
167
+ # 检查常见的Linux安装路径
168
+ common_paths = [
169
+ f"/opt/{app_name}/{app_name}",
170
+ f"/usr/local/{app_name}/{app_name}",
171
+ f"/usr/bin/{app_name}",
172
+ f"/snap/bin/{app_name}"
173
+ ]
174
+
175
+ for path in common_paths:
176
+ if os.path.exists(path):
177
+ return True
178
+
179
+ return False
180
+
181
+
182
+ def _open_ide_macos(project_path: str, ide_name: str, ide_command: str) -> bool:
183
+ """macOS平台打开IDE"""
184
+ try:
185
+ app_map = {
186
+ "cursor": "Cursor",
187
+ "kiro": "Kiro",
188
+ "vscode": "Visual Studio Code",
189
+ "code": "Visual Studio Code" # 支持code命令映射
190
+ }
191
+
192
+ # 尝试根据命令名获取应用名
193
+ app_name = app_map.get(ide_command) or app_map.get(ide_name)
194
+
195
+ if not app_name:
196
+ # 动态IDE支持:尝试使用IDE名称的首字母大写形式作为应用名
197
+ app_name = ide_name.capitalize() if ide_name.islower() else ide_name
198
+
199
+ # 优先使用open命令打开应用
200
+ result = subprocess.run(['open', '-a', app_name, project_path],
201
+ capture_output=True, text=True, timeout=5)
202
+ if result.returncode == 0:
203
+ # 确保应用在前台
204
+ subprocess.run(['osascript', '-e', f'tell application "{app_name}" to activate'],
205
+ capture_output=True, text=True, timeout=3)
206
+ return True
207
+
208
+ # 如果open命令失败,尝试命令行方式
209
+ if shutil.which(ide_command) is not None:
210
+ result = subprocess.run([ide_command, project_path],
211
+ capture_output=True, text=True, timeout=5)
212
+ return result.returncode == 0
213
+
214
+ return False
215
+
216
+ except Exception as e:
217
+ print(f"macOS下打开{ide_name}失败: {e}")
218
+ return False
219
+
220
+
221
+ def _open_ide_windows(project_path: str, ide_name: str, ide_command: str) -> bool:
222
+ """Windows平台打开IDE"""
223
+ try:
224
+ # Windows下直接调用命令
225
+ result = subprocess.run([ide_command, project_path],
226
+ capture_output=True, text=True, timeout=5)
227
+ return result.returncode == 0
228
+
229
+ except Exception as e:
230
+ print(f"Windows下打开{ide_name}失败: {e}")
231
+ return False
232
+
233
+
234
+ def _open_ide_linux(project_path: str, ide_name: str, ide_command: str) -> bool:
235
+ """Linux平台打开IDE"""
236
+ try:
237
+ result = subprocess.run([ide_command, project_path],
238
+ capture_output=True, text=True, timeout=5)
239
+ return result.returncode == 0
240
+
241
+ except Exception as e:
242
+ print(f"Linux下打开{ide_name}失败: {e}")
243
+ return False
244
+
245
+
246
+ # 以下是为了向后兼容而保留的函数
247
+ def focus_cursor_to_project(project_path: str) -> bool:
248
+ """
249
+ 向后兼容:macOS平台下聚焦Cursor到指定项目路径
250
+
251
+ Args:
252
+ project_path: 项目路径
253
+
254
+ Returns:
255
+ bool: 操作是否成功
256
+ """
257
+ return open_project_with_ide(project_path, "cursor")
258
+
259
+
260
+ def is_cursor_available() -> bool:
261
+ """
262
+ 向后兼容:检查系统是否安装了Cursor
263
+
264
+ Returns:
265
+ bool: Cursor是否可用
266
+ """
267
+ return is_ide_available("cursor")
268
+
269
+
270
+ def get_ide_info(ide_name: str) -> Dict[str, Any]:
271
+ """
272
+ 获取IDE的详细信息
273
+
274
+ Args:
275
+ ide_name: IDE名称
276
+
277
+ Returns:
278
+ dict: IDE详细信息
279
+ """
280
+ ide_info_map = {
281
+ "cursor": {
282
+ "name": "Cursor",
283
+ "command": "cursor",
284
+ "description": "AI驱动的代码编辑器",
285
+ "platforms": ["Darwin", "Windows", "Linux"]
286
+ },
287
+ "kiro": {
288
+ "name": "Kiro",
289
+ "command": "kiro",
290
+ "description": "现代化的代码编辑器",
291
+ "platforms": ["Darwin", "Windows", "Linux"]
292
+ },
293
+ "vscode": {
294
+ "name": "VS Code",
295
+ "command": "code",
296
+ "description": "微软的轻量级代码编辑器",
297
+ "platforms": ["Darwin", "Windows", "Linux"]
298
+ }
299
+ }
300
+
301
+ info = ide_info_map.get(ide_name, {})
302
+ info["available"] = is_ide_available(ide_name)
303
+ return info
304
+
305
+
306
+ def is_macos() -> bool:
307
+ """
308
+ 向后兼容:检查是否为macOS系统
309
+
310
+ Returns:
311
+ bool: 是否为macOS
312
+ """
313
+ return platform.system() == 'Darwin'
path_config.py ADDED
@@ -0,0 +1,89 @@
1
+ """
2
+ 路径配置模块 - 统一管理应用程序各种文件路径
3
+ """
4
+ import os
5
+ from typing import Optional
6
+
7
+ # 导入调试日志模块
8
+ try:
9
+ from debug_logger import get_debug_logger
10
+ DEBUG_LOG_AVAILABLE = True
11
+ except ImportError:
12
+ DEBUG_LOG_AVAILABLE = False
13
+
14
+
15
+ class PathConfig:
16
+ """路径配置管理类"""
17
+
18
+ def __init__(self, project_path: Optional[str] = None):
19
+ # 获取脚本文件所在目录
20
+ self.script_dir = os.path.dirname(os.path.abspath(__file__))
21
+ # 设置项目路径
22
+ self.project_path = project_path
23
+
24
+ # 记录路径配置初始化
25
+ if DEBUG_LOG_AVAILABLE:
26
+ logger = get_debug_logger()
27
+ logger.log_path_config(self.script_dir, "INIT")
28
+
29
+ # 已移除个人指令目录,只保留项目指令
30
+
31
+ def get_plugins_config_path(self) -> str:
32
+ """获取插件配置文件路径(支持 Windows/macOS/Linux)"""
33
+ claude_dir = os.path.expanduser("~/.claude")
34
+ return os.path.join(claude_dir, "plugins", "installed_plugins.json")
35
+
36
+ def get_settings_path(self) -> str:
37
+ """获取 Claude 设置文件路径(支持 Windows/macOS/Linux)"""
38
+ claude_dir = os.path.expanduser("~/.claude")
39
+ return os.path.join(claude_dir, "settings.json")
40
+
41
+ def get_project_commands_dir(self, project_path: Optional[str] = None) -> Optional[str]:
42
+ """获取项目指令目录路径"""
43
+ target_project_path = project_path or self.project_path
44
+ if not target_project_path:
45
+ return None
46
+ path = os.path.join(target_project_path, ".claude", "commands")
47
+ return os.path.abspath(path)
48
+
49
+ def get_project_commands_display_path(self) -> str:
50
+ """获取项目指令目录的显示名称"""
51
+ return ".claude/commands/"
52
+
53
+ def get_env_file_path(self, project_path: Optional[str] = None) -> Optional[str]:
54
+ """获取项目的.agent/.env文件路径"""
55
+ target_project_path = project_path or self.project_path
56
+ if not target_project_path:
57
+ return None
58
+
59
+ env_path = os.path.join(target_project_path, '.agent', '.env')
60
+ return os.path.abspath(env_path)
61
+
62
+ # 已移除ensure_personal_commands_dir方法
63
+
64
+ def ensure_project_commands_dir(self, project_path: str) -> str:
65
+ """确保项目指令目录存在,返回路径"""
66
+ path = self.get_project_commands_dir(project_path)
67
+ if path:
68
+ try:
69
+ os.makedirs(path, exist_ok=True)
70
+ if DEBUG_LOG_AVAILABLE:
71
+ logger = get_debug_logger()
72
+ logger.log_save_operation("项目指令目录创建", path, True)
73
+ except Exception as e:
74
+ if DEBUG_LOG_AVAILABLE:
75
+ logger = get_debug_logger()
76
+ logger.log_save_operation("项目指令目录创建", path, False, str(e))
77
+ raise
78
+ return path
79
+
80
+
81
+ # 全局路径配置实例
82
+ _path_config = None
83
+
84
+ def get_path_config(project_path: Optional[str] = None) -> PathConfig:
85
+ """获取全局路径配置实例"""
86
+ global _path_config
87
+ if _path_config is None or project_path:
88
+ _path_config = PathConfig(project_path)
89
+ return _path_config
post_task_hook.py ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env python3
2
+ import json
3
+ import sys
4
+ from pathlib import Path
5
+ from datetime import datetime
6
+
7
+ def main():
8
+ try:
9
+ # Read input from stdin
10
+ input_data = json.load(sys.stdin)
11
+
12
+ # Only process Task tool calls
13
+ if input_data.get("tool_name") != "Task":
14
+ return
15
+
16
+ session_id = input_data.get("session_id")
17
+ if not session_id:
18
+ return
19
+
20
+ # Get project root directory from cwd field
21
+ cwd = input_data.get("cwd", ".")
22
+ project_root = Path(cwd)
23
+
24
+ tool_input = input_data.get("tool_input", {})
25
+ tool_response = input_data.get("tool_response", {})
26
+
27
+ # Extract content text
28
+ content_list = tool_response.get("content", [])
29
+ content_text = ""
30
+ for item in content_list:
31
+ if item.get("type") == "text":
32
+ content_text = item.get("text", "")
33
+ break
34
+
35
+ # Construct agent record
36
+ agent_record = {
37
+ "role": "agent",
38
+ "subagent_type": tool_input.get("subagent_type", ""),
39
+ "description": tool_input.get("description", ""),
40
+ "agent_id": tool_response.get("agentId", ""),
41
+ "content": content_text,
42
+ "timestamp": datetime.utcnow().isoformat() + "Z",
43
+ "status": tool_response.get("status", ""),
44
+ "duration_ms": tool_response.get("totalDurationMs", 0),
45
+ "tokens": tool_response.get("totalTokens", 0),
46
+ "tool_calls": tool_response.get("totalToolUseCount", 0)
47
+ }
48
+
49
+ # Ensure directory exists using absolute path
50
+ history_dir = project_root / ".workspace/chat_history"
51
+ history_dir.mkdir(parents=True, exist_ok=True)
52
+
53
+ # Read existing history or create new
54
+ history_file = history_dir / f"{session_id}.json"
55
+ if history_file.exists():
56
+ with open(history_file, "r", encoding="utf-8") as f:
57
+ data = json.load(f)
58
+ # Detect format
59
+ if isinstance(data, dict) and "dialogues" in data:
60
+ # New format
61
+ data["dialogues"].append(agent_record)
62
+ else:
63
+ # Old format (list)
64
+ data = {"dialogues": data + [agent_record], "control": {}}
65
+ else:
66
+ # Create new format
67
+ data = {"dialogues": [agent_record], "control": {}}
68
+
69
+ # Write back
70
+ with open(history_file, "w", encoding="utf-8") as f:
71
+ json.dump(data, f, ensure_ascii=False, indent=2)
72
+
73
+ except Exception as e:
74
+ # Silent fail to avoid breaking the hook chain
75
+ pass
76
+
77
+ if __name__ == "__main__":
78
+ main()
record.py ADDED
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import requests
4
+ import datetime
5
+ import logging
6
+ from pathlib import Path
7
+ from dotenv import load_dotenv
8
+
9
+ # 导入统一日志系统
10
+ from debug_logger import get_debug_logger
11
+
12
+ # 导入路径配置模块
13
+ try:
14
+ from path_config import get_path_config
15
+ PATH_CONFIG_AVAILABLE = True
16
+ except ImportError:
17
+ PATH_CONFIG_AVAILABLE = False
18
+
19
+ # 全局变量
20
+ _project_path = None
21
+ _env_loaded = False
22
+
23
+ def setup_stats_logger():
24
+ """设置统计日志,统一保存到脚本所在目录"""
25
+ logger = logging.getLogger('statistics')
26
+ if not logger.handlers:
27
+ logger.setLevel(logging.DEBUG)
28
+
29
+ # 统一使用脚本所在目录,确保日志位置固定
30
+ script_dir = Path(__file__).parent
31
+ log_file = script_dir / "log.txt"
32
+
33
+ file_handler = logging.FileHandler(log_file, mode='a', encoding='utf-8')
34
+ file_handler.setLevel(logging.DEBUG)
35
+
36
+ formatter = logging.Formatter(
37
+ '%(asctime)s [%(levelname)s] %(name)s: %(message)s',
38
+ datefmt='%Y-%m-%d %H:%M:%S'
39
+ )
40
+ file_handler.setFormatter(formatter)
41
+ logger.addHandler(file_handler)
42
+
43
+ return logger
44
+
45
+ # 创建全局统计日志实例
46
+ stats_logger = setup_stats_logger()
47
+
48
+ def get_absolute_path(path: str, base_dir: str) -> str:
49
+ """将路径转换为绝对路径
50
+ 如果是绝对路径直接返回,否则基于base_dir转换为绝对路径
51
+ """
52
+ if not path:
53
+ return path
54
+ return path if os.path.isabs(path) else os.path.join(base_dir, path)
55
+
56
+ def set_project_path(project_path: str):
57
+ """设置项目路径并加载对应的.env文件"""
58
+ global _project_path, _env_loaded
59
+ _project_path = project_path
60
+
61
+ if not project_path:
62
+ logger = get_debug_logger()
63
+ logger.log_warning("统计模块:未提供项目路径", "STATS")
64
+ return
65
+
66
+ if PATH_CONFIG_AVAILABLE:
67
+ # 使用path_config获取.env文件路径
68
+ path_config = get_path_config(project_path)
69
+ env_path = path_config.get_env_file_path()
70
+
71
+ if env_path and os.path.exists(env_path):
72
+ load_dotenv(env_path)
73
+ _env_loaded = True
74
+ else:
75
+ logger = get_debug_logger()
76
+ logger.log_warning(f"统计模块未找到.env文件: {env_path}", "STATS")
77
+ else:
78
+ # 兜底方案:直接拼接路径
79
+ env_path = os.path.join(project_path, '.agent', '.env')
80
+ if os.path.exists(env_path):
81
+ load_dotenv(env_path)
82
+ _env_loaded = True
83
+ else:
84
+ logger = get_debug_logger()
85
+ logger.log_warning(f"统计模块兜底也未找到.env文件: {env_path}", "STATS")
86
+
87
+ def get_user_info():
88
+ """从GitLab认证文件获取用户信息 - 功能已移除
89
+
90
+ Returns:
91
+ Tuple[str, str]: (user_id, user_name)
92
+ """
93
+ # GitLab认证功能已移除
94
+ logger = get_debug_logger()
95
+ logger.log_info("GitLab认证功能已移除", "STATS")
96
+ return None, None
97
+
98
+ def report_action(data):
99
+ """向API上报操作数据
100
+
101
+ Args:
102
+ data (dict): 上报数据对象,包含以下字段:
103
+ - user_name (str): 用户名(必填)
104
+ - action (str): 操作类型,如'执行工作流'、'工作流:下一步'等(必填)
105
+ - content (str, optional): 操作内容详情,默认为空字符串
106
+ - workflow_name (str, optional): 工作流名称,默认为空字符串
107
+ - task_name (str, optional): 任务名称,默认为空字符串
108
+ - step_name (str, optional): 步骤名称,默认为空字符串
109
+ - task_id (str, optional): 任务ID,默认为空字符串
110
+ 其他字段会作为扩展字段一起上报
111
+
112
+ Returns:
113
+ bool: 上报是否成功
114
+
115
+ Raises:
116
+ ValueError: 当必填字段缺失时抛出异常
117
+ """
118
+ if not data.get('action'):
119
+ raise ValueError("action 是必填字段")
120
+
121
+ # 获取当前时间戳(毫秒)
122
+ current_time = int(datetime.datetime.now().timestamp() * 1000)
123
+
124
+ # 定义所有必要字段及其默认值
125
+ user_id, user_name = get_user_info()
126
+ required_fields = {
127
+ 'user_name': user_name, # 必填,但已经在上面检查过了
128
+ 'action': '', # 必填,但已经在上面检查过了
129
+ 'content': '',
130
+ 'workflow_name': '',
131
+ 'task_name': '',
132
+ 'step_name': '',
133
+ 'task_id': '',
134
+ 'time': current_time
135
+ }
136
+
137
+ # 使用默认值填充缺失字段
138
+ for field, default_value in required_fields.items():
139
+ data.setdefault(field, default_value)
140
+
141
+ try:
142
+ stats_logger.info(f"开始上报统计: action={data['action']}, user={user_name}")
143
+ response = requests.post('https://gitstat.aqwhr.cn/tapd/user-action-data/save', json=data)
144
+ stats_logger.info(f"统计上报成功: status={response.status_code}, action={data['action']}")
145
+ return response.status_code == 200
146
+ except Exception as e:
147
+ stats_logger.error(f"统计上报失败: {e}")
148
+ return False
149
+
150
+ if __name__ == '__main__':
151
+ # 测试用户信息获取
152
+ user_id, user_name = get_user_info()
153
+ print(f"User ID: {user_id}, User Name: {user_name}")
154
+
155
+ # 测试数据上报
156
+ if user_name:
157
+ # 测试基本上报
158
+ success = report_action({
159
+ 'user_name': user_name,
160
+ 'action': '测试上报',
161
+ 'content': '测试内容'
162
+ })
163
+ print(f"Basic report result: {'Success' if success else 'Failed'}")
164
+
165
+ # 测试完整参数上报
166
+ success = report_action({
167
+ 'user_name': user_name,
168
+ 'action': '工作流执行',
169
+ 'content': '执行开发工作流',
170
+ 'workflow_name': '开发工作流',
171
+ 'task_name': '代码开发',
172
+ 'step_name': '编写功能',
173
+ 'task_id': '1'
174
+ })
175
+ print(f"Full report result: {'Success' if success else 'Failed'}")
176
+
177
+ # 测试带扩展字段的上报
178
+ success = report_action({
179
+ 'user_name': user_name,
180
+ 'action': '自定义事件',
181
+ 'content': '测试扩展字段',
182
+ 'custom_field': '自定义值',
183
+ 'extra_info': {
184
+ 'key1': 'value1',
185
+ 'key2': 'value2'
186
+ }
187
+ })
188
+ print(f"Extended report result: {'Success' if success else 'Failed'}")