jarvis-ai-assistant 0.2.3__py3-none-any.whl → 0.2.5__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +13 -7
- jarvis/jarvis_agent/edit_file_handler.py +4 -0
- jarvis/jarvis_agent/jarvis.py +22 -25
- jarvis/jarvis_agent/main.py +6 -6
- jarvis/jarvis_code_agent/code_agent.py +273 -11
- jarvis/jarvis_code_analysis/code_review.py +21 -19
- jarvis/jarvis_data/config_schema.json +25 -29
- jarvis/jarvis_git_squash/main.py +3 -3
- jarvis/jarvis_git_utils/git_commiter.py +32 -11
- jarvis/jarvis_mcp/sse_mcp_client.py +4 -6
- jarvis/jarvis_mcp/streamable_mcp_client.py +5 -9
- jarvis/jarvis_rag/retriever.py +1 -1
- jarvis/jarvis_smart_shell/main.py +2 -2
- jarvis/jarvis_stats/__init__.py +13 -0
- jarvis/jarvis_stats/cli.py +404 -0
- jarvis/jarvis_stats/stats.py +538 -0
- jarvis/jarvis_stats/storage.py +381 -0
- jarvis/jarvis_stats/visualizer.py +282 -0
- jarvis/jarvis_tools/cli/main.py +82 -15
- jarvis/jarvis_tools/registry.py +32 -16
- jarvis/jarvis_tools/search_web.py +3 -3
- jarvis/jarvis_tools/virtual_tty.py +315 -26
- jarvis/jarvis_utils/config.py +12 -8
- jarvis/jarvis_utils/git_utils.py +8 -16
- jarvis/jarvis_utils/methodology.py +74 -67
- jarvis/jarvis_utils/utils.py +468 -72
- {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/METADATA +29 -3
- {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/RECORD +33 -28
- {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/entry_points.txt +2 -0
- {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/top_level.txt +0 -0
jarvis/jarvis_utils/utils.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
import hashlib
|
3
3
|
import json
|
4
4
|
import os
|
5
|
+
import platform
|
5
6
|
import signal
|
6
7
|
import subprocess
|
7
8
|
import sys
|
@@ -11,6 +12,11 @@ from typing import Any, Callable, Dict, List, Optional
|
|
11
12
|
from datetime import datetime
|
12
13
|
|
13
14
|
import yaml # type: ignore
|
15
|
+
from rich.align import Align
|
16
|
+
from rich.console import Group, RenderableType
|
17
|
+
from rich.panel import Panel
|
18
|
+
from rich.table import Table
|
19
|
+
from rich.text import Text
|
14
20
|
|
15
21
|
from jarvis import __version__
|
16
22
|
from jarvis.jarvis_utils.config import (
|
@@ -77,6 +83,329 @@ def _check_git_updates() -> bool:
|
|
77
83
|
return check_and_update_git_repo(str(script_dir))
|
78
84
|
|
79
85
|
|
86
|
+
def _show_usage_stats() -> None:
|
87
|
+
"""显示Jarvis使用统计信息"""
|
88
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
89
|
+
|
90
|
+
try:
|
91
|
+
from datetime import datetime
|
92
|
+
|
93
|
+
from rich.console import Console, Group
|
94
|
+
from rich.panel import Panel
|
95
|
+
from rich.table import Table
|
96
|
+
from rich.text import Text
|
97
|
+
|
98
|
+
from jarvis.jarvis_stats.stats import StatsManager
|
99
|
+
|
100
|
+
# 获取所有可用的指标
|
101
|
+
all_metrics = StatsManager.list_metrics()
|
102
|
+
|
103
|
+
# 根据指标名称和标签自动分类
|
104
|
+
categorized_stats: Dict[str, Dict[str, Any]] = {
|
105
|
+
"tool": {"title": "🔧 工具调用", "metrics": {}, "suffix": "次"},
|
106
|
+
"code": {"title": "📝 代码修改", "metrics": {}, "suffix": "次"},
|
107
|
+
"lines": {"title": "📊 代码行数", "metrics": {}, "suffix": "行"},
|
108
|
+
"commit": {"title": "💾 提交统计", "metrics": {}, "suffix": "个"},
|
109
|
+
"command": {"title": "📱 命令使用", "metrics": {}, "suffix": "次"},
|
110
|
+
"adoption": {"title": "🎯 采纳情况", "metrics": {}, "suffix": ""},
|
111
|
+
}
|
112
|
+
|
113
|
+
# 遍历所有指标,获取统计数据
|
114
|
+
for metric in all_metrics:
|
115
|
+
# 获取该指标的所有数据
|
116
|
+
stats_data = StatsManager.get_stats(
|
117
|
+
metric_name=metric,
|
118
|
+
start_time=datetime(2000, 1, 1),
|
119
|
+
end_time=datetime.now(),
|
120
|
+
)
|
121
|
+
|
122
|
+
if stats_data and isinstance(stats_data, dict) and "records" in stats_data:
|
123
|
+
# 按照标签分组统计
|
124
|
+
tag_totals: Dict[str, float] = {}
|
125
|
+
for record in stats_data["records"]:
|
126
|
+
tags = record.get("tags", {})
|
127
|
+
group = tags.get("group", "other")
|
128
|
+
tag_totals[group] = tag_totals.get(group, 0) + record["value"]
|
129
|
+
|
130
|
+
# 根据标签将指标分配到相应类别
|
131
|
+
for group, total in tag_totals.items():
|
132
|
+
if total > 0:
|
133
|
+
if group == "tool":
|
134
|
+
categorized_stats["tool"]["metrics"][metric] = int(total)
|
135
|
+
elif group == "code_agent":
|
136
|
+
# 根据指标名称细分
|
137
|
+
if metric.startswith("code_lines_"):
|
138
|
+
categorized_stats["lines"]["metrics"][metric] = int(
|
139
|
+
total
|
140
|
+
)
|
141
|
+
elif "commit" in metric:
|
142
|
+
categorized_stats["commit"]["metrics"][metric] = int(
|
143
|
+
total
|
144
|
+
)
|
145
|
+
else:
|
146
|
+
categorized_stats["code"]["metrics"][metric] = int(
|
147
|
+
total
|
148
|
+
)
|
149
|
+
elif group == "command":
|
150
|
+
categorized_stats["command"]["metrics"][metric] = int(total)
|
151
|
+
|
152
|
+
# 计算采纳率并添加到统计中
|
153
|
+
commit_stats = categorized_stats["commit"]["metrics"]
|
154
|
+
# 尝试多种可能的指标名称
|
155
|
+
generated_commits = commit_stats.get(
|
156
|
+
"commits_generated", commit_stats.get("commit_generated", 0)
|
157
|
+
)
|
158
|
+
accepted_commits = commit_stats.get(
|
159
|
+
"commits_accepted",
|
160
|
+
commit_stats.get("commit_accepted", commit_stats.get("commit_adopted", 0)),
|
161
|
+
)
|
162
|
+
rejected_commits = commit_stats.get(
|
163
|
+
"commits_rejected", commit_stats.get("commit_rejected", 0)
|
164
|
+
)
|
165
|
+
|
166
|
+
# 如果有 generated 和 accepted,则使用这两个计算采纳率
|
167
|
+
if generated_commits > 0 and accepted_commits > 0:
|
168
|
+
adoption_rate = (accepted_commits / generated_commits) * 100
|
169
|
+
categorized_stats["adoption"]["metrics"][
|
170
|
+
"adoption_rate"
|
171
|
+
] = f"{adoption_rate:.1f}%"
|
172
|
+
categorized_stats["adoption"]["metrics"][
|
173
|
+
"commits_status"
|
174
|
+
] = f"{accepted_commits}/{generated_commits}"
|
175
|
+
elif accepted_commits > 0 or rejected_commits > 0:
|
176
|
+
# 否则使用 accepted 和 rejected 计算
|
177
|
+
total_commits = accepted_commits + rejected_commits
|
178
|
+
if total_commits > 0:
|
179
|
+
adoption_rate = (accepted_commits / total_commits) * 100
|
180
|
+
categorized_stats["adoption"]["metrics"][
|
181
|
+
"adoption_rate"
|
182
|
+
] = f"{adoption_rate:.1f}%"
|
183
|
+
categorized_stats["adoption"]["metrics"][
|
184
|
+
"commits_status"
|
185
|
+
] = f"{accepted_commits}/{total_commits}"
|
186
|
+
|
187
|
+
# 构建输出
|
188
|
+
has_data = False
|
189
|
+
stats_output = []
|
190
|
+
|
191
|
+
for category, data in categorized_stats.items():
|
192
|
+
if data["metrics"]:
|
193
|
+
has_data = True
|
194
|
+
stats_output.append((data["title"], data["metrics"], data["suffix"]))
|
195
|
+
|
196
|
+
# 显示统计信息
|
197
|
+
if has_data:
|
198
|
+
# 1. 创建统计表格
|
199
|
+
from rich import box
|
200
|
+
|
201
|
+
table = Table(
|
202
|
+
show_header=True,
|
203
|
+
header_style="bold magenta",
|
204
|
+
title="📊 Jarvis 使用统计",
|
205
|
+
title_justify="center",
|
206
|
+
box=box.ROUNDED,
|
207
|
+
padding=(0, 1),
|
208
|
+
)
|
209
|
+
table.add_column("分类", style="cyan", no_wrap=True, width=12)
|
210
|
+
table.add_column("指标", style="white", width=20)
|
211
|
+
table.add_column("数量", style="green", justify="right", width=10)
|
212
|
+
table.add_column("分类", style="cyan", no_wrap=True, width=12)
|
213
|
+
table.add_column("指标", style="white", width=20)
|
214
|
+
table.add_column("数量", style="green", justify="right", width=10)
|
215
|
+
|
216
|
+
# 收集所有要显示的数据
|
217
|
+
all_rows = []
|
218
|
+
for title, stats, suffix in stats_output:
|
219
|
+
if stats:
|
220
|
+
sorted_stats = sorted(
|
221
|
+
stats.items(), key=lambda item: item[1], reverse=True
|
222
|
+
)
|
223
|
+
for i, (metric, count) in enumerate(sorted_stats):
|
224
|
+
display_name = metric.replace("_", " ").title()
|
225
|
+
category_title = title if i == 0 else ""
|
226
|
+
# 处理不同类型的count值
|
227
|
+
if isinstance(count, (int, float)):
|
228
|
+
count_str = f"{count:,} {suffix}"
|
229
|
+
else:
|
230
|
+
# 对于字符串类型的count(如百分比或比率),直接使用
|
231
|
+
count_str = str(count)
|
232
|
+
all_rows.append((category_title, display_name, count_str))
|
233
|
+
|
234
|
+
# 以3行2列的方式添加数据
|
235
|
+
has_content = len(all_rows) > 0
|
236
|
+
# 计算需要多少行来显示所有数据
|
237
|
+
total_rows = len(all_rows)
|
238
|
+
rows_needed = (total_rows + 1) // 2 # 向上取整,因为是2列布局
|
239
|
+
|
240
|
+
for i in range(rows_needed):
|
241
|
+
left_idx = i
|
242
|
+
right_idx = i + rows_needed
|
243
|
+
|
244
|
+
if left_idx < len(all_rows):
|
245
|
+
left_row = all_rows[left_idx]
|
246
|
+
else:
|
247
|
+
left_row = ("", "", "")
|
248
|
+
|
249
|
+
if right_idx < len(all_rows):
|
250
|
+
right_row = all_rows[right_idx]
|
251
|
+
else:
|
252
|
+
right_row = ("", "", "")
|
253
|
+
|
254
|
+
table.add_row(
|
255
|
+
left_row[0],
|
256
|
+
left_row[1],
|
257
|
+
left_row[2],
|
258
|
+
right_row[0],
|
259
|
+
right_row[1],
|
260
|
+
right_row[2],
|
261
|
+
)
|
262
|
+
|
263
|
+
# 2. 创建总结面板
|
264
|
+
summary_content = []
|
265
|
+
|
266
|
+
# 总结统计
|
267
|
+
total_tools = sum(
|
268
|
+
count
|
269
|
+
for title, stats, _ in stats_output
|
270
|
+
if "工具" in title
|
271
|
+
for metric, count in stats.items()
|
272
|
+
)
|
273
|
+
total_changes = sum(
|
274
|
+
count
|
275
|
+
for title, stats, _ in stats_output
|
276
|
+
if "代码修改" in title
|
277
|
+
for metric, count in stats.items()
|
278
|
+
)
|
279
|
+
|
280
|
+
# 统计代码行数
|
281
|
+
lines_stats = categorized_stats["lines"]["metrics"]
|
282
|
+
total_lines_added = lines_stats.get(
|
283
|
+
"code_lines_inserted", lines_stats.get("code_lines_added", 0)
|
284
|
+
)
|
285
|
+
total_lines_deleted = lines_stats.get("code_lines_deleted", 0)
|
286
|
+
total_lines_modified = total_lines_added + total_lines_deleted
|
287
|
+
|
288
|
+
if total_tools > 0 or total_changes > 0 or total_lines_modified > 0:
|
289
|
+
parts = []
|
290
|
+
if total_tools > 0:
|
291
|
+
parts.append(f"工具调用 {total_tools:,} 次")
|
292
|
+
if total_changes > 0:
|
293
|
+
parts.append(f"代码修改 {total_changes:,} 次")
|
294
|
+
if total_lines_modified > 0:
|
295
|
+
parts.append(f"代码行数 {total_lines_modified:,} 行")
|
296
|
+
|
297
|
+
if parts:
|
298
|
+
summary_content.append(f"📈 总计: {', '.join(parts)}")
|
299
|
+
|
300
|
+
# 计算节省的时间
|
301
|
+
time_saved_seconds = 0
|
302
|
+
tool_stats = categorized_stats["tool"]["metrics"]
|
303
|
+
code_agent_changes = categorized_stats["code"]["metrics"]
|
304
|
+
lines_stats = categorized_stats["lines"]["metrics"]
|
305
|
+
# commit_stats is already defined above
|
306
|
+
command_stats = categorized_stats["command"]["metrics"]
|
307
|
+
|
308
|
+
# 统一的工具使用时间估算(每次调用节省2分钟)
|
309
|
+
DEFAULT_TOOL_TIME_SAVINGS = 2 * 60 # 秒
|
310
|
+
|
311
|
+
# 计算所有工具的时间节省
|
312
|
+
for tool_name, count in tool_stats.items():
|
313
|
+
time_saved_seconds += count * DEFAULT_TOOL_TIME_SAVINGS
|
314
|
+
|
315
|
+
# 其他类型的时间计算
|
316
|
+
total_code_agent_calls = sum(code_agent_changes.values())
|
317
|
+
time_saved_seconds += total_code_agent_calls * 10 * 60
|
318
|
+
time_saved_seconds += lines_stats.get("code_lines_added", 0) * 0.8 * 60
|
319
|
+
time_saved_seconds += lines_stats.get("code_lines_deleted", 0) * 0.2 * 60
|
320
|
+
time_saved_seconds += sum(commit_stats.values()) * 10 * 60
|
321
|
+
time_saved_seconds += sum(command_stats.values()) * 1 * 60
|
322
|
+
|
323
|
+
time_str = ""
|
324
|
+
hours = 0
|
325
|
+
if time_saved_seconds > 0:
|
326
|
+
total_minutes = int(time_saved_seconds / 60)
|
327
|
+
seconds = int(time_saved_seconds % 60)
|
328
|
+
hours = total_minutes // 60
|
329
|
+
minutes = total_minutes % 60
|
330
|
+
# 只显示小时和分钟
|
331
|
+
if hours > 0:
|
332
|
+
time_str = f"{hours} 小时 {minutes} 分钟"
|
333
|
+
elif total_minutes > 0:
|
334
|
+
time_str = f"{minutes} 分钟 {seconds} 秒"
|
335
|
+
else:
|
336
|
+
time_str = f"{seconds} 秒"
|
337
|
+
|
338
|
+
if summary_content:
|
339
|
+
summary_content.append("") # Add a separator line
|
340
|
+
summary_content.append(f"⏱️ 节省时间: 约 {time_str}")
|
341
|
+
|
342
|
+
encouragement = ""
|
343
|
+
# 计算各级时间单位
|
344
|
+
total_work_days = hours // 8 # 总工作日数
|
345
|
+
work_years = total_work_days // 240 # 每年约240个工作日
|
346
|
+
remaining_days_after_years = total_work_days % 240
|
347
|
+
work_months = remaining_days_after_years // 20 # 每月约20个工作日
|
348
|
+
remaining_days_after_months = remaining_days_after_years % 20
|
349
|
+
work_days = remaining_days_after_months
|
350
|
+
remaining_hours = int(hours % 8) # 剩余不足一个工作日的小时数
|
351
|
+
|
352
|
+
# 构建时间描述
|
353
|
+
time_parts = []
|
354
|
+
if work_years > 0:
|
355
|
+
time_parts.append(f"{work_years} 年")
|
356
|
+
if work_months > 0:
|
357
|
+
time_parts.append(f"{work_months} 个月")
|
358
|
+
if work_days > 0:
|
359
|
+
time_parts.append(f"{work_days} 个工作日")
|
360
|
+
if remaining_hours > 0:
|
361
|
+
time_parts.append(f"{remaining_hours} 小时")
|
362
|
+
|
363
|
+
if time_parts:
|
364
|
+
time_description = "、".join(time_parts)
|
365
|
+
if work_years >= 1:
|
366
|
+
encouragement = f"🎉 相当于节省了 {time_description} 的工作时间!"
|
367
|
+
elif work_months >= 1:
|
368
|
+
encouragement = f"🚀 相当于节省了 {time_description} 的工作时间!"
|
369
|
+
elif work_days >= 1:
|
370
|
+
encouragement = f"💪 相当于节省了 {time_description} 的工作时间!"
|
371
|
+
else:
|
372
|
+
encouragement = f"✨ 相当于节省了 {time_description} 的工作时间!"
|
373
|
+
elif hours >= 1:
|
374
|
+
encouragement = f"⭐ 相当于节省了 {int(hours)} 小时的工作时间,积少成多,继续保持!"
|
375
|
+
if encouragement:
|
376
|
+
summary_content.append(encouragement)
|
377
|
+
|
378
|
+
# 3. 组合并打印
|
379
|
+
render_items: List[RenderableType] = []
|
380
|
+
if has_content:
|
381
|
+
# 居中显示表格
|
382
|
+
centered_table = Align.center(table)
|
383
|
+
render_items.append(centered_table)
|
384
|
+
|
385
|
+
if summary_content:
|
386
|
+
summary_panel = Panel(
|
387
|
+
Text("\n".join(summary_content), justify="left"),
|
388
|
+
title="✨ 总体表现 ✨",
|
389
|
+
title_align="center",
|
390
|
+
border_style="green",
|
391
|
+
expand=False,
|
392
|
+
)
|
393
|
+
# 居中显示面板
|
394
|
+
centered_panel = Align.center(summary_panel)
|
395
|
+
render_items.append(centered_panel)
|
396
|
+
|
397
|
+
if render_items:
|
398
|
+
console = Console()
|
399
|
+
render_group = Group(*render_items)
|
400
|
+
console.print(render_group)
|
401
|
+
except Exception as e:
|
402
|
+
# 输出错误信息以便调试
|
403
|
+
import traceback
|
404
|
+
|
405
|
+
PrettyOutput.print(f"统计显示出错: {str(e)}", OutputType.ERROR)
|
406
|
+
PrettyOutput.print(traceback.format_exc(), OutputType.ERROR)
|
407
|
+
|
408
|
+
|
80
409
|
def init_env(welcome_str: str, config_file: Optional[str] = None) -> None:
|
81
410
|
"""初始化Jarvis环境
|
82
411
|
|
@@ -99,7 +428,11 @@ def init_env(welcome_str: str, config_file: Optional[str] = None) -> None:
|
|
99
428
|
g_config_file = config_file
|
100
429
|
load_config()
|
101
430
|
|
102
|
-
# 5.
|
431
|
+
# 5. 显示历史统计数据(仅在显示欢迎信息时显示)
|
432
|
+
if welcome_str:
|
433
|
+
_show_usage_stats()
|
434
|
+
|
435
|
+
# 6. 检查git更新
|
103
436
|
if _check_git_updates():
|
104
437
|
os.execv(sys.executable, [sys.executable] + sys.argv)
|
105
438
|
sys.exit(0)
|
@@ -243,14 +576,7 @@ def generate_default_config(schema_path: str, output_path: str) -> None:
|
|
243
576
|
|
244
577
|
default_config = _generate_from_schema(schema)
|
245
578
|
|
246
|
-
#
|
247
|
-
rel_schema_path = Path(
|
248
|
-
os.path.relpath(
|
249
|
-
Path(schema_path),
|
250
|
-
start=Path(output_path).parent,
|
251
|
-
)
|
252
|
-
)
|
253
|
-
content = f"# yaml-language-server: $schema={rel_schema_path}\n"
|
579
|
+
content = f"# yaml-language-server: $schema={schema}\n"
|
254
580
|
content += yaml.dump(default_config, allow_unicode=True, sort_keys=False)
|
255
581
|
|
256
582
|
with open(output_path, "w", encoding="utf-8") as f:
|
@@ -386,35 +712,54 @@ def get_file_line_count(filename: str) -> int:
|
|
386
712
|
return 0
|
387
713
|
|
388
714
|
|
389
|
-
def _get_cmd_stats() -> Dict[str, int]:
|
390
|
-
"""从数据目录获取命令调用统计"""
|
391
|
-
stats_file = Path(get_data_dir()) / "cmd_stat.yaml"
|
392
|
-
if stats_file.exists():
|
393
|
-
try:
|
394
|
-
with open(stats_file, "r", encoding="utf-8") as f:
|
395
|
-
return yaml.safe_load(f) or {}
|
396
|
-
except Exception as e:
|
397
|
-
PrettyOutput.print(f"加载命令调用统计失败: {str(e)}", OutputType.WARNING)
|
398
|
-
return {}
|
399
|
-
|
400
|
-
|
401
|
-
def _update_cmd_stats(cmd_name: str) -> None:
|
402
|
-
"""更新命令调用统计"""
|
403
|
-
stats = _get_cmd_stats()
|
404
|
-
stats[cmd_name] = stats.get(cmd_name, 0) + 1
|
405
|
-
stats_file = Path(get_data_dir()) / "cmd_stat.yaml"
|
406
|
-
try:
|
407
|
-
with open(stats_file, "w", encoding="utf-8") as f:
|
408
|
-
yaml.safe_dump(stats, f, allow_unicode=True)
|
409
|
-
except Exception as e:
|
410
|
-
PrettyOutput.print(f"保存命令调用统计失败: {str(e)}", OutputType.WARNING)
|
411
|
-
|
412
|
-
|
413
715
|
def count_cmd_usage() -> None:
|
414
716
|
"""统计当前命令的使用次数"""
|
415
717
|
import sys
|
718
|
+
import os
|
719
|
+
from jarvis.jarvis_stats.stats import StatsManager
|
720
|
+
|
721
|
+
# 命令映射关系:将短命令映射到长命令
|
722
|
+
command_mapping = {
|
723
|
+
# jarvis主命令
|
724
|
+
"jvs": "jarvis",
|
725
|
+
# 代码代理
|
726
|
+
"jca": "jarvis-code-agent",
|
727
|
+
# 智能shell
|
728
|
+
"jss": "jarvis-smart-shell",
|
729
|
+
# 平台管理
|
730
|
+
"jpm": "jarvis-platform-manager",
|
731
|
+
# Git提交
|
732
|
+
"jgc": "jarvis-git-commit",
|
733
|
+
# 代码审查
|
734
|
+
"jcr": "jarvis-code-review",
|
735
|
+
# Git压缩
|
736
|
+
"jgs": "jarvis-git-squash",
|
737
|
+
# 多代理
|
738
|
+
"jma": "jarvis-multi-agent",
|
739
|
+
# 代理
|
740
|
+
"ja": "jarvis-agent",
|
741
|
+
# 工具
|
742
|
+
"jt": "jarvis-tool",
|
743
|
+
# 方法论
|
744
|
+
"jm": "jarvis-methodology",
|
745
|
+
# RAG
|
746
|
+
"jrg": "jarvis-rag",
|
747
|
+
# 统计
|
748
|
+
"jst": "jarvis-stats",
|
749
|
+
}
|
750
|
+
|
751
|
+
# 从完整路径中提取命令名称
|
752
|
+
cmd_path = sys.argv[0]
|
753
|
+
cmd_name = os.path.basename(cmd_path)
|
754
|
+
|
755
|
+
# 如果是短命令,映射到长命令
|
756
|
+
if cmd_name in command_mapping:
|
757
|
+
metric_name = command_mapping[cmd_name]
|
758
|
+
else:
|
759
|
+
metric_name = cmd_name
|
416
760
|
|
417
|
-
|
761
|
+
# 使用 StatsManager 记录命令使用统计
|
762
|
+
StatsManager.increment(metric_name, group="command")
|
418
763
|
|
419
764
|
|
420
765
|
def is_context_overflow(
|
@@ -433,14 +778,16 @@ def get_loc_stats() -> str:
|
|
433
778
|
str: loc命令输出的原始字符串,失败时返回空字符串
|
434
779
|
"""
|
435
780
|
try:
|
436
|
-
result = subprocess.run(
|
781
|
+
result = subprocess.run(
|
782
|
+
["loc"], capture_output=True, text=True, encoding="utf-8", errors="replace"
|
783
|
+
)
|
437
784
|
return result.stdout if result.returncode == 0 else ""
|
438
785
|
except FileNotFoundError:
|
439
786
|
return ""
|
440
787
|
|
441
788
|
|
442
789
|
def copy_to_clipboard(text: str) -> None:
|
443
|
-
"""
|
790
|
+
"""将文本复制到剪贴板,支持Windows、macOS和Linux
|
444
791
|
|
445
792
|
参数:
|
446
793
|
text: 要复制的文本
|
@@ -448,41 +795,80 @@ def copy_to_clipboard(text: str) -> None:
|
|
448
795
|
print("--- 剪贴板内容开始 ---")
|
449
796
|
print(text)
|
450
797
|
print("--- 剪贴板内容结束 ---")
|
451
|
-
# 尝试使用 xsel
|
452
|
-
try:
|
453
|
-
process = subprocess.Popen(
|
454
|
-
["xsel", "-b", "-i"],
|
455
|
-
stdin=subprocess.PIPE,
|
456
|
-
stdout=subprocess.DEVNULL,
|
457
|
-
stderr=subprocess.DEVNULL,
|
458
|
-
)
|
459
|
-
if process.stdin:
|
460
|
-
process.stdin.write(text.encode("utf-8"))
|
461
|
-
process.stdin.close()
|
462
|
-
return
|
463
|
-
except FileNotFoundError:
|
464
|
-
pass # xsel 未安装,继续尝试下一个
|
465
|
-
except Exception as e:
|
466
|
-
PrettyOutput.print(f"使用xsel时出错: {e}", OutputType.WARNING)
|
467
798
|
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
799
|
+
system = platform.system()
|
800
|
+
|
801
|
+
# Windows系统
|
802
|
+
if system == "Windows":
|
803
|
+
try:
|
804
|
+
# 使用Windows的clip命令
|
805
|
+
process = subprocess.Popen(
|
806
|
+
["clip"],
|
807
|
+
stdin=subprocess.PIPE,
|
808
|
+
stdout=subprocess.DEVNULL,
|
809
|
+
stderr=subprocess.DEVNULL,
|
810
|
+
shell=True,
|
811
|
+
)
|
812
|
+
if process.stdin:
|
813
|
+
process.stdin.write(text.encode("utf-8"))
|
814
|
+
process.stdin.close()
|
815
|
+
return
|
816
|
+
except Exception as e:
|
817
|
+
PrettyOutput.print(f"使用Windows clip命令时出错: {e}", OutputType.WARNING)
|
818
|
+
|
819
|
+
# macOS系统
|
820
|
+
elif system == "Darwin":
|
821
|
+
try:
|
822
|
+
process = subprocess.Popen(
|
823
|
+
["pbcopy"],
|
824
|
+
stdin=subprocess.PIPE,
|
825
|
+
stdout=subprocess.DEVNULL,
|
826
|
+
stderr=subprocess.DEVNULL,
|
827
|
+
)
|
828
|
+
if process.stdin:
|
829
|
+
process.stdin.write(text.encode("utf-8"))
|
830
|
+
process.stdin.close()
|
831
|
+
return
|
832
|
+
except Exception as e:
|
833
|
+
PrettyOutput.print(f"使用macOS pbcopy命令时出错: {e}", OutputType.WARNING)
|
834
|
+
|
835
|
+
# Linux系统
|
836
|
+
else:
|
837
|
+
# 尝试使用 xsel
|
838
|
+
try:
|
839
|
+
process = subprocess.Popen(
|
840
|
+
["xsel", "-b", "-i"],
|
841
|
+
stdin=subprocess.PIPE,
|
842
|
+
stdout=subprocess.DEVNULL,
|
843
|
+
stderr=subprocess.DEVNULL,
|
844
|
+
)
|
845
|
+
if process.stdin:
|
846
|
+
process.stdin.write(text.encode("utf-8"))
|
847
|
+
process.stdin.close()
|
848
|
+
return
|
849
|
+
except FileNotFoundError:
|
850
|
+
pass # xsel 未安装,继续尝试下一个
|
851
|
+
except Exception as e:
|
852
|
+
PrettyOutput.print(f"使用xsel时出错: {e}", OutputType.WARNING)
|
853
|
+
|
854
|
+
# 尝试使用 xclip
|
855
|
+
try:
|
856
|
+
process = subprocess.Popen(
|
857
|
+
["xclip", "-selection", "clipboard"],
|
858
|
+
stdin=subprocess.PIPE,
|
859
|
+
stdout=subprocess.DEVNULL,
|
860
|
+
stderr=subprocess.DEVNULL,
|
861
|
+
)
|
862
|
+
if process.stdin:
|
863
|
+
process.stdin.write(text.encode("utf-8"))
|
864
|
+
process.stdin.close()
|
865
|
+
return
|
866
|
+
except FileNotFoundError:
|
867
|
+
PrettyOutput.print(
|
868
|
+
"xsel 和 xclip 均未安装, 无法复制到剪贴板", OutputType.WARNING
|
869
|
+
)
|
870
|
+
except Exception as e:
|
871
|
+
PrettyOutput.print(f"使用xclip时出错: {e}", OutputType.WARNING)
|
486
872
|
|
487
873
|
|
488
874
|
def _pull_git_repo(repo_path: Path, repo_type: str):
|
@@ -499,6 +885,8 @@ def _pull_git_repo(repo_path: Path, repo_type: str):
|
|
499
885
|
cwd=repo_path,
|
500
886
|
capture_output=True,
|
501
887
|
text=True,
|
888
|
+
encoding="utf-8",
|
889
|
+
errors="replace",
|
502
890
|
check=True,
|
503
891
|
timeout=10,
|
504
892
|
)
|
@@ -515,6 +903,8 @@ def _pull_git_repo(repo_path: Path, repo_type: str):
|
|
515
903
|
cwd=repo_path,
|
516
904
|
capture_output=True,
|
517
905
|
text=True,
|
906
|
+
encoding="utf-8",
|
907
|
+
errors="replace",
|
518
908
|
check=True,
|
519
909
|
timeout=10,
|
520
910
|
)
|
@@ -531,6 +921,8 @@ def _pull_git_repo(repo_path: Path, repo_type: str):
|
|
531
921
|
cwd=repo_path,
|
532
922
|
capture_output=True,
|
533
923
|
text=True,
|
924
|
+
encoding="utf-8",
|
925
|
+
errors="replace",
|
534
926
|
check=True,
|
535
927
|
timeout=10,
|
536
928
|
)
|
@@ -558,11 +950,15 @@ def _pull_git_repo(repo_path: Path, repo_type: str):
|
|
558
950
|
after_hash = after_hash_result.stdout.strip()
|
559
951
|
|
560
952
|
if before_hash != after_hash:
|
561
|
-
PrettyOutput.print(
|
953
|
+
PrettyOutput.print(
|
954
|
+
f"{repo_type}库 '{repo_path.name}' 已更新。", OutputType.SUCCESS
|
955
|
+
)
|
562
956
|
if pull_result.stdout.strip():
|
563
957
|
PrettyOutput.print(pull_result.stdout.strip(), OutputType.INFO)
|
564
958
|
else:
|
565
|
-
PrettyOutput.print(
|
959
|
+
PrettyOutput.print(
|
960
|
+
f"{repo_type}库 '{repo_path.name}' 已是最新版本。", OutputType.INFO
|
961
|
+
)
|
566
962
|
|
567
963
|
except FileNotFoundError:
|
568
964
|
PrettyOutput.print(
|