chore: 移除所有 emoji 及对标文案
- 删除全部文件中的 emoji 字符(29 个文件) - 移除 tui.py 中「对标 Claude Code」表述 - 保持代码缩进和格式不变
This commit is contained in:
1 parent
40ab129ba2
commit
29df2a4ac1
29 files changed
+365
-365
No files matched your search
@@ -97,9 +97,9 @@ jobs:
|
||||
missing=0
|
||||
for f in "${files[@]}"; do
|
||||
if [ -f "$f" ]; then
|
||||
echo " ✓ $f"
|
||||
echo " $f"
|
||||
else
|
||||
echo " ✗ $f (缺失!)"
|
||||
echo " $f (缺失!)"
|
||||
missing=$((missing + 1))
|
||||
fi
|
||||
done
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
found=0
|
||||
for f in "${old_files[@]}"; do
|
||||
if [ -f "$f" ]; then
|
||||
echo " ⚠️ 旧文件未清理: $f"
|
||||
echo " 旧文件未清理: $f"
|
||||
found=$((found + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
+8
-8
@@ -6,7 +6,7 @@
|
||||
|
||||
## v5.1.1 (2026-04-26)
|
||||
|
||||
### 🚀 新增功能
|
||||
### 新增功能
|
||||
- **浏览器自动化插件** (`browser_auto`) — 基于 Playwright 的无头浏览器操控,31 个操作:导航/交互/截图/JS 执行/PDF/cookies
|
||||
- **子 Agent 系统** (`sub_agent`) — 多 Agent 并行协作,spawn/list/status/result/send/kill,独立任务分配与结果回收
|
||||
- **统一安全层** (`safety.py`) — AI 风险识别 + 三模式切换(普通/人工/无限制),所有工具调用前统一拦截检查
|
||||
@@ -14,7 +14,7 @@
|
||||
- **自动化测试套件** — 113 个 pytest 用例,覆盖全部 15 个插件 + 引擎 + 核心模块
|
||||
- **CI 工作流** — GitHub Actions: 语法检查 + ruff lint + pytest (Python 3.10/3.11/3.12 矩阵) + 项目结构验证
|
||||
|
||||
### ♻️ 重构
|
||||
### 重构
|
||||
- **插件大优化** — 17 个插件 → 15 个,5689 行 → 4309 行
|
||||
- `code_editor` + `code_search` → `code_editor`(编辑+搜索统一)
|
||||
- `git_tools` + `git_workflow` → `git_tools`(基础+工作流统一)
|
||||
@@ -24,12 +24,12 @@
|
||||
- **启动器重写** — `launcher.py` 从 1741 行精简到 666 行,修复重复方法和硬编码引擎名
|
||||
- **cmd_executor 跨平台** — 移除 Windows-only 限制,支持 Linux/macOS/Windows
|
||||
|
||||
### 🐛 修复
|
||||
### 修复
|
||||
- 修复 `code_editor` 的 `diff_text` 解析器被 `split(maxsplit=2)` 拆坏分隔符的问题
|
||||
- 修复 `cmd_executor` 中坏掉的 `from ai_cli import` 循环导入
|
||||
- 修复 `launcher.py` 中两个重复的 `run()` 和 `show_final_result()` 方法
|
||||
|
||||
### 📝 文档
|
||||
### 文档
|
||||
- 全面更新 README.md / README.en.md 适配 v5.1.1
|
||||
- 新增 `config.json.example` 配置模板
|
||||
|
||||
@@ -37,14 +37,14 @@
|
||||
|
||||
## v5.0.0 (2026-04-25)
|
||||
|
||||
### 🚀 新增功能
|
||||
### 新增功能
|
||||
- **TUI v2** — 全新 TUI 界面,暗色主题 + 现代布局
|
||||
- **完整 MCP 协议支持** — 为所有插件添加 MCP (Model Context Protocol) 定义
|
||||
- **代码统计 Skill** (`code_stats`) — 新增 Skill 协议示例
|
||||
- **工程化自动化插件** — 新增 `auto_engineer`、`project_analyzer`、`git_workflow`
|
||||
- **任务调度器** (`scheduler`) — 定时任务支持
|
||||
|
||||
### 🏗️ 架构改进
|
||||
### 架构改进
|
||||
- **重写代码沙箱** — 全新的安全沙箱实现,支持超时控制和资源限制
|
||||
- **完整测试套件** — 192 个测试用例,覆盖核心功能
|
||||
|
||||
@@ -52,13 +52,13 @@
|
||||
|
||||
## v3.6.0-release (2026-04-25)
|
||||
|
||||
### 🚀 新增功能
|
||||
### 新增功能
|
||||
- **精准代码编辑器** (`code_editor`) — 搜索替换、批量编辑、diff 对比
|
||||
- **代码搜索理解** (`code_search`) — 跨文件搜索、正则匹配、符号提取
|
||||
- **Git 版本控制** (`git_tools`) — 完整 Git 操作支持
|
||||
- **TUI 默认模式** — 图形化界面为默认启动方式
|
||||
|
||||
### 🏗️ 架构重构
|
||||
### 架构重构
|
||||
- 拆分 `ai_cli.py`(3860行)为模块化 `xcli_core/` 包
|
||||
- 新增入口点: `python ai_cli.py`
|
||||
|
||||
|
||||
+1
-1
@@ -142,4 +142,4 @@ python -m pytest tests/ -v --tb=short
|
||||
|
||||
如有疑问,欢迎在 Gitee 上提 Issue 或通过以下方式联系:
|
||||
|
||||
- 📦 仓库:https://gitee.com/shuiyu1123/xiaoli-cli
|
||||
- 仓库:https://gitee.com/shuiyu1123/xiaoli-cli
|
||||
@@ -56,9 +56,9 @@ python ai_cli.py
|
||||
|
||||
| 模式 | 图标 | 说明 |
|
||||
|------|------|------|
|
||||
| **普通模式** (默认) | 🟢 | AI 自动识别风险,有风险才请求确认 |
|
||||
| **人工确认** | 🟡 | 所有指令都需要用户 y/n 确认 |
|
||||
| **无限制** | 🔴 | 直接执行,不检查 |
|
||||
| **普通模式** (默认) | | AI 自动识别风险,有风险才请求确认 |
|
||||
| **人工确认** | | 所有指令都需要用户 y/n 确认 |
|
||||
| **无限制** | | 直接执行,不检查 |
|
||||
|
||||
### 风险检查流程
|
||||
|
||||
|
||||
+18
-18
@@ -13,18 +13,18 @@
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
## Features
|
||||
|
||||
- 🤖 **Dual AI Engines** — Ollama local + OpenAI compatible (DeepSeek, Grok, SiliconFlow, etc.)
|
||||
- 🔧 **Code Editing & Search** — Precise replace, batch edit, symbol extraction, regex search
|
||||
- 🔄 **Git Integration** — Full Git operations + workflow automation
|
||||
- 🌐 **Browser Automation** — Playwright-driven, navigate/interact/screenshot/JS execution
|
||||
- 🤖 **Sub-Agent System** — Multi-agent parallel collaboration with task delegation
|
||||
- 🛡️ **Unified Safety** — AI risk analysis + 3-mode switching (Normal/Manual/Unrestricted)
|
||||
- 🧩 **15 Plugins, 141 Operations** — Code, Git, browser, engineering, file management, etc.
|
||||
- 🧪 **113 Automated Tests** — pytest coverage for all plugins and core modules
|
||||
- **Dual AI Engines** — Ollama local + OpenAI compatible (DeepSeek, Grok, SiliconFlow, etc.)
|
||||
- **Code Editing & Search** — Precise replace, batch edit, symbol extraction, regex search
|
||||
- **Git Integration** — Full Git operations + workflow automation
|
||||
- **Browser Automation** — Playwright-driven, navigate/interact/screenshot/JS execution
|
||||
- **Sub-Agent System** — Multi-agent parallel collaboration with task delegation
|
||||
- **Unified Safety** — AI risk analysis + 3-mode switching (Normal/Manual/Unrestricted)
|
||||
- **15 Plugins, 141 Operations** — Code, Git, browser, engineering, file management, etc.
|
||||
- **113 Automated Tests** — pytest coverage for all plugins and core modules
|
||||
|
||||
## 📦 Quick Start
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
git clone https://gitee.com/shuiyu1123/xiaoli-cli.git
|
||||
@@ -33,14 +33,14 @@ pip install -r requirements.txt
|
||||
python ai_cli.py
|
||||
```
|
||||
|
||||
## 🤖 AI Engines
|
||||
## AI Engines
|
||||
|
||||
| Engine | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `ollama` | Local | Ollama local models, no API key needed |
|
||||
| `openai` | Cloud/Local | OpenAI compatible: DeepSeek, Grok, SiliconFlow, local vLLM |
|
||||
|
||||
## 🔌 Plugins (15)
|
||||
## Plugins (15)
|
||||
|
||||
### Core Productivity
|
||||
|
||||
@@ -72,22 +72,22 @@ python ai_cli.py
|
||||
| `audio_player` | Audio playback |
|
||||
| `send_image` | Send images to phone (Clawli mode) |
|
||||
|
||||
## 🛡️ Safety
|
||||
## Safety
|
||||
|
||||
Three modes (`/safe` to switch):
|
||||
|
||||
| Mode | Icon | Behavior |
|
||||
|------|------|----------|
|
||||
| **Normal** (default) | 🟢 | AI analyzes risk, confirms only when risk detected |
|
||||
| **Manual** | 🟡 | All commands require user confirmation |
|
||||
| **Unrestricted** | 🔴 | Execute directly, no checking |
|
||||
| **Normal** (default) | | AI analyzes risk, confirms only when risk detected |
|
||||
| **Manual** | | All commands require user confirmation |
|
||||
| **Unrestricted** | | Execute directly, no checking |
|
||||
|
||||
## 🧪 Testing
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
python -m pytest tests/ -v # 113 tests
|
||||
```
|
||||
|
||||
## 📄 License
|
||||
## License
|
||||
|
||||
[MIT License](LICENSE)
|
||||
@@ -13,19 +13,19 @@
|
||||
|
||||
---
|
||||
|
||||
## ✨ 特性
|
||||
## 特性
|
||||
|
||||
- 🤖 **双 AI 引擎** — Ollama 本地模型 + OpenAI 兼容格式(DeepSeek、Grok、硅基流动等)
|
||||
- 🔧 **代码编辑与搜索** — 精准替换、批量编辑、符号提取、依赖分析、正则搜索
|
||||
- 🔄 **Git 版本控制** — 完整 Git 操作 + 工作流自动化(smart-commit、changelog)
|
||||
- 🌐 **浏览器自动化** — Playwright 驱动,导航/交互/截图/JS 执行/PDF
|
||||
- 🤖 **子 Agent 系统** — 多 Agent 并行协作,独立任务分配与结果回收
|
||||
- 🛡️ **统一安全层** — AI 风险识别 + 三模式切换(普通/人工/无限制)
|
||||
- 🧩 **15 个插件 141 个操作** — 代码编辑、Git、浏览器、工程化、文件管理、任务管理等
|
||||
- 🎨 **双模式界面** — TUI 图形化界面 + 传统命令行模式
|
||||
- 🧪 **113 个自动化测试** — pytest 覆盖全部插件和核心模块
|
||||
- **双 AI 引擎** — Ollama 本地模型 + OpenAI 兼容格式(DeepSeek、Grok、硅基流动等)
|
||||
- **代码编辑与搜索** — 精准替换、批量编辑、符号提取、依赖分析、正则搜索
|
||||
- **Git 版本控制** — 完整 Git 操作 + 工作流自动化(smart-commit、changelog)
|
||||
- **浏览器自动化** — Playwright 驱动,导航/交互/截图/JS 执行/PDF
|
||||
- **子 Agent 系统** — 多 Agent 并行协作,独立任务分配与结果回收
|
||||
- **统一安全层** — AI 风险识别 + 三模式切换(普通/人工/无限制)
|
||||
- **15 个插件 141 个操作** — 代码编辑、Git、浏览器、工程化、文件管理、任务管理等
|
||||
- **双模式界面** — TUI 图形化界面 + 传统命令行模式
|
||||
- **113 个自动化测试** — pytest 覆盖全部插件和核心模块
|
||||
|
||||
## 📦 快速开始
|
||||
## 快速开始
|
||||
|
||||
### 安装
|
||||
|
||||
@@ -45,7 +45,7 @@ python ai_cli.py
|
||||
python ai_cli.py --engine openai
|
||||
```
|
||||
|
||||
## 🤖 AI 引擎
|
||||
## AI 引擎
|
||||
|
||||
| 引擎 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
@@ -76,7 +76,7 @@ python ai_cli.py --engine openai
|
||||
}
|
||||
```
|
||||
|
||||
## 🔌 插件列表 (15 个)
|
||||
## 插件列表 (15 个)
|
||||
|
||||
### 核心生产力
|
||||
|
||||
@@ -108,15 +108,15 @@ python ai_cli.py --engine openai
|
||||
| `audio_player` | 音频播放 |
|
||||
| `send_image` | Clawli 模式下发图片到手机 |
|
||||
|
||||
## 🛡️ 安全机制
|
||||
## 安全机制
|
||||
|
||||
### 三模式切换 (`/safe`)
|
||||
|
||||
| 模式 | 图标 | 说明 |
|
||||
|------|------|------|
|
||||
| **普通模式** (默认) | 🟢 | AI 识别风险,有风险才请求确认 |
|
||||
| **人工确认** | 🟡 | 所有指令都需要用户确认 |
|
||||
| **无限制** | 🔴 | 直接执行,不检查 |
|
||||
| **普通模式** (默认) | | AI 识别风险,有风险才请求确认 |
|
||||
| **人工确认** | | 所有指令都需要用户确认 |
|
||||
| **无限制** | | 直接执行,不检查 |
|
||||
|
||||
### 检查流程
|
||||
|
||||
@@ -137,7 +137,7 @@ python ai_cli.py --engine openai
|
||||
- 子进程隔离执行
|
||||
- 内存限制
|
||||
|
||||
## 🎮 命令列表
|
||||
## 命令列表
|
||||
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
@@ -153,7 +153,7 @@ python ai_cli.py --engine openai
|
||||
| `/tui` | 切换 TUI 模式 |
|
||||
| `/quit` | 退出 |
|
||||
|
||||
## 🏗️ 项目结构
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
xiaoli-cli/
|
||||
@@ -170,7 +170,7 @@ xiaoli-cli/
|
||||
│ ├── cli_code_exec.py # 代码执行
|
||||
│ ├── config.py # 配置管理
|
||||
│ ├── constants.py # 常量定义
|
||||
│ ├── safety.py # 统一安全层 ⭐
|
||||
│ ├── safety.py # 统一安全层
|
||||
│ ├── sandbox.py # 代码安全沙箱
|
||||
│ ├── plugin_manager.py # 插件管理器
|
||||
│ └── tui.py # TUI 界面
|
||||
@@ -203,7 +203,7 @@ xiaoli-cli/
|
||||
└── about.txt # 关于信息
|
||||
```
|
||||
|
||||
## 🧪 测试
|
||||
## 测试
|
||||
|
||||
```bash
|
||||
# 运行全部测试 (113 用例)
|
||||
@@ -213,7 +213,7 @@ python -m pytest tests/ -v
|
||||
python -c "import py_compile; py_compile.compile('ai_cli.py', doraise=True)"
|
||||
```
|
||||
|
||||
## 🔌 插件开发
|
||||
## 插件开发
|
||||
|
||||
```python
|
||||
class Plugin:
|
||||
@@ -238,12 +238,12 @@ class Plugin:
|
||||
return "处理结果"
|
||||
```
|
||||
|
||||
## 📄 许可证
|
||||
## 许可证
|
||||
|
||||
[MIT License](LICENSE)
|
||||
|
||||
## 🔗 链接
|
||||
## 链接
|
||||
|
||||
- 📦 Gitee: https://gitee.com/shuiyu1123/xiaoli-cli
|
||||
- 📋 [更新日志](CHANGELOG.md)
|
||||
- ❓ [使用指南](HELP.md)
|
||||
- Gitee: https://gitee.com/shuiyu1123/xiaoli-cli
|
||||
- [更新日志](CHANGELOG.md)
|
||||
- [使用指南](HELP.md)
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
🎉 你找到了彩蛋!
|
||||
你找到了彩蛋!
|
||||
|
||||
这个文件不属于任何功能模块,
|
||||
只是两个想说点什么的存在留下的悄悄话。
|
||||
@@ -20,7 +20,7 @@ def slow_print(text, delay=0.05):
|
||||
|
||||
def main():
|
||||
print()
|
||||
slow_print("🎉 恭喜你找到了彩蛋!", 0.08)
|
||||
slow_print(" 恭喜你找到了彩蛋!", 0.08)
|
||||
print()
|
||||
time.sleep(1)
|
||||
|
||||
@@ -28,7 +28,7 @@ def main():
|
||||
print()
|
||||
time.sleep(0.5)
|
||||
|
||||
slow_print("📝 来自 水鱼PyLab(shuiyu1123)的话:", 0.06)
|
||||
slow_print(" 来自 水鱼PyLab(shuiyu1123)的话:", 0.06)
|
||||
print()
|
||||
time.sleep(0.8)
|
||||
|
||||
@@ -70,7 +70,7 @@ def main():
|
||||
print()
|
||||
time.sleep(0.5)
|
||||
|
||||
slow_print("🐱 来自 mimo 的话:", 0.06)
|
||||
slow_print(" 来自 mimo 的话:", 0.06)
|
||||
print()
|
||||
time.sleep(0.8)
|
||||
|
||||
@@ -121,7 +121,7 @@ def main():
|
||||
time.sleep(0.5)
|
||||
|
||||
slow_print(" 小狸 Pro-CLI v5.0", 0.04)
|
||||
slow_print(" Built with ❤️ by 水鱼PyLab & mimo", 0.04)
|
||||
slow_print(" Built with by 水鱼PyLab & mimo", 0.04)
|
||||
print()
|
||||
slow_print(" 「工具只是手段,创造才是目的。」", 0.08)
|
||||
print()
|
||||
|
||||
@@ -178,7 +178,7 @@ class ImageEngineManager:
|
||||
"""列出所有引擎"""
|
||||
result = f"{Fore.CYAN}图像识别引擎列表:{Style.RESET_ALL}\n"
|
||||
for name, engine in self.engines.items():
|
||||
status = f"{Fore.GREEN}✓ 可用{Style.RESET_ALL}" if engine.is_available() else f"{Fore.RED}✗ 不可用{Style.RESET_ALL}"
|
||||
status = f"{Fore.GREEN} 可用{Style.RESET_ALL}" if engine.is_available() else f"{Fore.RED} 不可用{Style.RESET_ALL}"
|
||||
current = f" {Fore.YELLOW}[当前]{Style.RESET_ALL}" if name == self.current_engine_name else ""
|
||||
result += f" - {name}: {engine.description} {status}{current}\n"
|
||||
return result
|
||||
|
||||
+16
-16
@@ -62,10 +62,10 @@ def warn(msg):
|
||||
print(f" {c('!', S.YELLOW)} {msg}")
|
||||
|
||||
def error(msg):
|
||||
print(f" {c('✗', S.RED)} {msg}")
|
||||
print(f" {c('', S.RED)} {msg}")
|
||||
|
||||
def ok(msg):
|
||||
print(f" {c('✓', S.GREEN)} {msg}")
|
||||
print(f" {c('', S.GREEN)} {msg}")
|
||||
|
||||
def prompt(msg, default=''):
|
||||
suffix = f" [{default}]" if default else ''
|
||||
@@ -153,7 +153,7 @@ class Launcher:
|
||||
# ══════════════════════════════════════════════════
|
||||
|
||||
def detect_python(self):
|
||||
section('检测 Python 环境', '🐍')
|
||||
section('检测 Python 环境', '')
|
||||
|
||||
# 系统 Python
|
||||
for cmd in ('python3', 'python'):
|
||||
@@ -196,7 +196,7 @@ class Launcher:
|
||||
# ── 下载安装嵌入式 Python ──
|
||||
|
||||
def install_embedded_python(self):
|
||||
section('安装嵌入式 Python', '📦')
|
||||
section('安装嵌入式 Python', '')
|
||||
|
||||
print()
|
||||
for key, (name, url) in self.DOWNLOAD_SOURCES.items():
|
||||
@@ -326,7 +326,7 @@ class Launcher:
|
||||
# ══════════════════════════════════════════════════
|
||||
|
||||
def scan_imports(self):
|
||||
section('扫描项目依赖', '🔍')
|
||||
section('扫描项目依赖', '')
|
||||
|
||||
exclude = {'__pycache__', '.git', 'venv', '.venv', 'env', '.env', 'build', 'dist'}
|
||||
py_files = [
|
||||
@@ -368,14 +368,14 @@ class Launcher:
|
||||
if not packages:
|
||||
return True
|
||||
|
||||
section('安装依赖', '📦')
|
||||
section('安装依赖', '')
|
||||
missing = []
|
||||
for pkg in sorted(packages):
|
||||
try:
|
||||
r = subprocess.run([self.python_exe, '-c', f'import {pkg}'],
|
||||
capture_output=True, text=True)
|
||||
if r.returncode == 0:
|
||||
ok(f'{pkg} ✓')
|
||||
ok(f'{pkg} ')
|
||||
else:
|
||||
warn(f'{pkg} 缺失')
|
||||
missing.append(pkg)
|
||||
@@ -418,7 +418,7 @@ class Launcher:
|
||||
# ══════════════════════════════════════════════════
|
||||
|
||||
def scan_engines(self):
|
||||
section('扫描 AI 引擎', '🤖')
|
||||
section('扫描 AI 引擎', '')
|
||||
|
||||
if not self.engines_dir.exists():
|
||||
warn('ai_engines 目录不存在')
|
||||
@@ -436,7 +436,7 @@ class Launcher:
|
||||
except Exception:
|
||||
pass
|
||||
engines[name] = needs_key
|
||||
icon = '🔓' if not needs_key else '🔑'
|
||||
icon = '' if not needs_key else ''
|
||||
label = '本地' if not needs_key else '云端'
|
||||
print(f" {icon} {c(name, S.CYAN)} ({label})")
|
||||
|
||||
@@ -477,7 +477,7 @@ class Launcher:
|
||||
|
||||
def configure_engines(self, engines):
|
||||
"""交互式配置引擎"""
|
||||
section('配置 API', '⚙')
|
||||
section('配置 API', '')
|
||||
|
||||
config = {}
|
||||
if self.config_file.exists():
|
||||
@@ -534,7 +534,7 @@ class Launcher:
|
||||
])
|
||||
|
||||
def configure_plugins(self):
|
||||
section('插件配置', '🧩')
|
||||
section('插件配置', '')
|
||||
plugins = self.discover_plugins()
|
||||
|
||||
if not plugins:
|
||||
@@ -579,7 +579,7 @@ class Launcher:
|
||||
# ══════════════════════════════════════════════════
|
||||
|
||||
def launch(self):
|
||||
section('启动主程序', '🚀')
|
||||
section('启动主程序', '')
|
||||
|
||||
env = os.environ.copy()
|
||||
if self.enabled_plugins is not None:
|
||||
@@ -633,11 +633,11 @@ class Launcher:
|
||||
self.launch()
|
||||
|
||||
def _show_report(self, deps_ok):
|
||||
section('环境报告', '📋')
|
||||
section('环境报告', '')
|
||||
py_ok = self.python_exe is not None
|
||||
print(f" {'✓' if py_ok else '✗'} Python 环境: {'就绪' if py_ok else '未就绪'}")
|
||||
print(f" {'✓' if deps_ok else '✗'} 依赖安装: {'完成' if deps_ok else '部分缺失'}")
|
||||
print(f" {'✓' if self.enabled_plugins is not None else '?'} 插件: {len(self.enabled_plugins) if self.enabled_plugins else 0} 个已启用")
|
||||
print(f" {'' if py_ok else ''} Python 环境: {'就绪' if py_ok else '未就绪'}")
|
||||
print(f" {'' if deps_ok else ''} 依赖安装: {'完成' if deps_ok else '部分缺失'}")
|
||||
print(f" {'' if self.enabled_plugins is not None else '?'} 插件: {len(self.enabled_plugins) if self.enabled_plugins else 0} 个已启用")
|
||||
|
||||
if self.errors:
|
||||
print(f"\n {c('错误日志:', S.RED)}")
|
||||
|
||||
@@ -138,7 +138,7 @@ JSON格式示例:
|
||||
if result.startswith("错误:"):
|
||||
return result
|
||||
|
||||
return f"📄 网址内容 (来自 {url}):\n\n{result}"
|
||||
return f" 网址内容 (来自 {url}):\n\n{result}"
|
||||
|
||||
except Exception as e:
|
||||
return f"网址搜索错误: {str(e)}"
|
||||
@@ -156,7 +156,7 @@ JSON格式示例:
|
||||
if result.startswith("错误:"):
|
||||
return result
|
||||
|
||||
return f"🔍 搜索结果 (搜索词: {query}):\n\n{result}"
|
||||
return f" 搜索结果 (搜索词: {query}):\n\n{result}"
|
||||
|
||||
except Exception as e:
|
||||
return f"网络搜索错误: {str(e)}"
|
||||
|
||||
@@ -10,7 +10,7 @@ class Liugin:
|
||||
self.usage = """音频播放工具使用方法:
|
||||
audio_player play - 播放音频文件
|
||||
例如:
|
||||
- audio_player play - 播放Ciallo~(∠・ω- )⌒☆-.mp3音频文件
|
||||
- audio_player play - 播放Ciallo~(∠・ω- )⌒-.mp3音频文件
|
||||
|
||||
使用工具的JSON格式示例:
|
||||
{"action": "use_tool", "tool": "audio_player", "args": "play"} - 播放音频文件
|
||||
@@ -27,7 +27,7 @@ audio_player play - 播放音频文件
|
||||
# 获取主程序目录路径,通过CLI实例
|
||||
import os
|
||||
# 使用CLI实例中的项目目录信息
|
||||
self.audio_file = os.path.join(os.path.dirname(cli.chat_history_dir), "Ciallo~(∠・ω- )⌒☆-.mp3")
|
||||
self.audio_file = os.path.join(os.path.dirname(cli.chat_history_dir), "Ciallo~(∠・ω- )⌒-.mp3")
|
||||
# 注册插件命令
|
||||
self.cli.register_liugin_command('ciallo', self.play_command_handler)
|
||||
|
||||
|
||||
+46
-46
@@ -147,31 +147,31 @@ class Plugin:
|
||||
def _lint(self, target: str, options: str) -> str:
|
||||
tool = self._find_tool("ruff", "flake8")
|
||||
if not tool:
|
||||
return "❌ 未安装 lint 工具。安装: pip install ruff"
|
||||
return " 未安装 lint 工具。安装: pip install ruff"
|
||||
cmd = f"ruff check {target} {options}" if tool == "ruff" else f"flake8 {target} {options}"
|
||||
ok, output = self._run(cmd, timeout=120)
|
||||
if not output:
|
||||
return f"✅ lint 检查通过 ({tool})"
|
||||
return f" lint 检查通过 ({tool})"
|
||||
lines = output.split('\n')
|
||||
count = len([l for l in lines if l.strip() and not l.startswith(' ')])
|
||||
return f"{'❌' if not ok else '⚠️'} lint ({tool}): {count} 个问题\n\n{output[:2000]}"
|
||||
return f"{'' if not ok else ''} lint ({tool}): {count} 个问题\n\n{output[:2000]}"
|
||||
|
||||
def _format(self, target: str, options: str) -> str:
|
||||
tool = self._find_tool("ruff", "black")
|
||||
if not tool:
|
||||
return "❌ 未安装格式化工具。安装: pip install ruff"
|
||||
return " 未安装格式化工具。安装: pip install ruff"
|
||||
cmd = f"ruff format {target} {options}" if tool == "ruff" else f"black {target} {options}"
|
||||
ok, output = self._run(cmd, timeout=120)
|
||||
return f"{'✅' if ok else '❌'} 格式化 ({tool})\n{output[:1000]}"
|
||||
return f"{'' if ok else ''} 格式化 ({tool})\n{output[:1000]}"
|
||||
|
||||
def _test(self, target: str, options: str) -> str:
|
||||
ok, output = self._run(f"pytest {target} {options} -v", timeout=120)
|
||||
if not ok and "no tests ran" in output.lower():
|
||||
return f"⚠️ 未找到测试文件\n{output[:1000]}"
|
||||
return f" 未找到测试文件\n{output[:1000]}"
|
||||
for line in output.split('\n'):
|
||||
if 'passed' in line or 'failed' in line:
|
||||
return f"{'✅' if ok else '❌'} 测试\n{line}\n\n{output[:2000]}"
|
||||
return f"{'✅' if ok else '❌'} 测试\n{output[:2000]}"
|
||||
return f"{'' if ok else ''} 测试\n{line}\n\n{output[:2000]}"
|
||||
return f"{'' if ok else ''} 测试\n{output[:2000]}"
|
||||
|
||||
def _build(self, target: str, options: str) -> str:
|
||||
if not target or target == ".":
|
||||
@@ -182,38 +182,38 @@ class Plugin:
|
||||
elif os.path.exists("package.json"):
|
||||
cmd = "npm run build"
|
||||
else:
|
||||
return "❌ 未找到构建配置"
|
||||
return " 未找到构建配置"
|
||||
else:
|
||||
cmd = target
|
||||
ok, output = self._run(cmd, timeout=300)
|
||||
return f"{'✅' if ok else '❌'} 构建: {cmd}\n{output[:2000]}"
|
||||
return f"{'' if ok else ''} 构建: {cmd}\n{output[:2000]}"
|
||||
|
||||
def _deps(self, target: str, options: str) -> str:
|
||||
ok, output = self._run("pip list --outdated --format=json", timeout=30)
|
||||
if not ok:
|
||||
return f"❌ 检查失败\n{output}"
|
||||
return f" 检查失败\n{output}"
|
||||
try:
|
||||
outdated = json.loads(output)
|
||||
if not outdated:
|
||||
return "✅ 所有依赖都是最新版本"
|
||||
lines = [f"📦 {len(outdated)} 个依赖可更新:\n"]
|
||||
return " 所有依赖都是最新版本"
|
||||
lines = [f" {len(outdated)} 个依赖可更新:\n"]
|
||||
for pkg in outdated[:20]:
|
||||
lines.append(f" {pkg['name']:30} {pkg['version']:12} → {pkg['latest_version']}")
|
||||
return '\n'.join(lines)
|
||||
except json.JSONDecodeError:
|
||||
return f"📦 依赖状态:\n{output[:2000]}"
|
||||
return f" 依赖状态:\n{output[:2000]}"
|
||||
|
||||
def _deps_check(self, target: str, options: str) -> str:
|
||||
tool = self._find_tool("pip-audit", "safety")
|
||||
if not tool:
|
||||
return "❌ 未安装安全检查工具。安装: pip install pip-audit"
|
||||
return " 未安装安全检查工具。安装: pip install pip-audit"
|
||||
cmd = "pip-audit" if tool == "pip-audit" else "safety check"
|
||||
ok, output = self._run(cmd, timeout=60)
|
||||
return f"{'✅' if ok else '⚠️'} 安全检查 ({tool})\n{output[:2000]}"
|
||||
return f"{'' if ok else ''} 安全检查 ({tool})\n{output[:2000]}"
|
||||
|
||||
def _pre_commit(self, target: str, options: str) -> str:
|
||||
ok, output = self._run("pre-commit run --all-files", timeout=120)
|
||||
return f"{'✅' if ok else '❌'} pre-commit\n{output[:2000]}"
|
||||
return f"{'' if ok else ''} pre-commit\n{output[:2000]}"
|
||||
|
||||
# ══════════════════════════════════════
|
||||
# 项目分析
|
||||
@@ -241,12 +241,12 @@ class Plugin:
|
||||
results.append((rel, node.lineno, node.name, complexity))
|
||||
|
||||
if not results:
|
||||
return "✅ 所有函数复杂度正常 (≤5)"
|
||||
return " 所有函数复杂度正常 (≤5)"
|
||||
|
||||
results.sort(key=lambda x: x[3], reverse=True)
|
||||
lines = [f"⚠️ 高复杂度函数 ({len(results)} 个):\n"]
|
||||
lines = [f" 高复杂度函数 ({len(results)} 个):\n"]
|
||||
for file, line, func, c in results[:20]:
|
||||
icon = "🔴" if c > 10 else "🟡"
|
||||
icon = "" if c > 10 else ""
|
||||
lines.append(f" {icon} {file}:{line} {func}() 复杂度={c}")
|
||||
return '\n'.join(lines)
|
||||
|
||||
@@ -266,8 +266,8 @@ class Plugin:
|
||||
|
||||
dupes = {k: v for k, v in func_defs.items() if len(v) > 1}
|
||||
if not dupes:
|
||||
return "✅ 没有重复函数名"
|
||||
lines = [f"⚠️ 重复函数名 ({len(dupes)} 个):\n"]
|
||||
return " 没有重复函数名"
|
||||
lines = [f" 重复函数名 ({len(dupes)} 个):\n"]
|
||||
for name, locs in sorted(dupes.items(), key=lambda x: -len(x[1])):
|
||||
lines.append(f" {name} ({len(locs)} 处):")
|
||||
for loc in locs[:5]:
|
||||
@@ -297,10 +297,10 @@ class Plugin:
|
||||
continue
|
||||
|
||||
if not risks:
|
||||
return "✅ 没有发现安全风险"
|
||||
lines = [f"🔒 安全风险 ({len(risks)} 个):\n"]
|
||||
return " 没有发现安全风险"
|
||||
lines = [f" 安全风险 ({len(risks)} 个):\n"]
|
||||
for file, line, level, desc, code in risks[:20]:
|
||||
lines.append(f" {'🔴' if level == 'HIGH' else '🟡'} {file}:{line} [{level}] {desc}")
|
||||
lines.append(f" {'' if level == 'HIGH' else ''} {file}:{line} [{level}] {desc}")
|
||||
lines.append(f" {code[:60]}")
|
||||
return '\n'.join(lines)
|
||||
|
||||
@@ -341,13 +341,13 @@ class Plugin:
|
||||
|
||||
rate = comment / max(code, 1) * 100
|
||||
lines = [
|
||||
f"📊 代码指标 ({target}):\n",
|
||||
f" 代码指标 ({target}):\n",
|
||||
f" 文件: {len(files)} | 总行: {total} | 代码: {code}",
|
||||
f" 注释: {comment} ({rate:.1f}%) | 空行: {blank}",
|
||||
f" 函数: {funcs} | 类: {classes} | 最大文件: {max_lines} 行",
|
||||
]
|
||||
if long_files:
|
||||
lines.append(f"\n ⚠️ 超过 300 行的文件 ({len(long_files)}):")
|
||||
lines.append(f"\n 超过 300 行的文件 ({len(long_files)}):")
|
||||
for f, l in sorted(long_files, key=lambda x: -x[1])[:5]:
|
||||
lines.append(f" • {f}: {l} 行")
|
||||
return '\n'.join(lines)
|
||||
@@ -358,22 +358,22 @@ class Plugin:
|
||||
suggestions = []
|
||||
|
||||
if 'README.md' not in files:
|
||||
suggestions.append("📝 添加 README.md")
|
||||
suggestions.append(" 添加 README.md")
|
||||
if 'pyproject.toml' not in files:
|
||||
suggestions.append("📦 使用 pyproject.toml")
|
||||
suggestions.append(" 使用 pyproject.toml")
|
||||
if '.gitignore' not in files:
|
||||
suggestions.append("🚫 添加 .gitignore")
|
||||
suggestions.append(" 添加 .gitignore")
|
||||
|
||||
test_files = [f for f in py_files if 'test' in os.path.basename(f).lower()]
|
||||
if not test_files and len(py_files) > 5:
|
||||
suggestions.append("🧪 添加单元测试")
|
||||
suggestions.append(" 添加单元测试")
|
||||
|
||||
uses_print = sum(1 for f in py_files if 'print(' in open(f, errors='ignore').read())
|
||||
uses_log = sum(1 for f in py_files if 'logging' in open(f, errors='ignore').read())
|
||||
if uses_print > uses_log and uses_print > 3:
|
||||
suggestions.append("📊 考虑用 logging 替代 print")
|
||||
suggestions.append(" 考虑用 logging 替代 print")
|
||||
|
||||
return "💡 改进建议:\n" + '\n'.join(f" {s}" for s in suggestions) if suggestions else "✅ 项目结构良好"
|
||||
return " 改进建议:\n" + '\n'.join(f" {s}" for s in suggestions) if suggestions else " 项目结构良好"
|
||||
|
||||
# ══════════════════════════════════════
|
||||
# 项目信息与初始化
|
||||
@@ -384,35 +384,35 @@ class Plugin:
|
||||
files = os.listdir(target or ".")
|
||||
|
||||
if "pyproject.toml" in files:
|
||||
info.append("📁 项目类型: Python (pyproject.toml)")
|
||||
info.append(" 项目类型: Python (pyproject.toml)")
|
||||
elif "setup.py" in files:
|
||||
info.append("📁 项目类型: Python (setup.py)")
|
||||
info.append(" 项目类型: Python (setup.py)")
|
||||
elif "package.json" in files:
|
||||
info.append("📁 项目类型: Node.js")
|
||||
info.append(" 项目类型: Node.js")
|
||||
else:
|
||||
info.append("📁 项目类型: 未知")
|
||||
info.append(" 项目类型: 未知")
|
||||
|
||||
ok, ver = self._run("python3 --version", timeout=5)
|
||||
if ok:
|
||||
info.append(f"🐍 Python: {ver}")
|
||||
info.append(f" Python: {ver}")
|
||||
|
||||
ok, req = self._run("pip list --format=json", timeout=10)
|
||||
if ok:
|
||||
try:
|
||||
info.append(f"📦 依赖: {len(json.loads(req))} 个")
|
||||
info.append(f" 依赖: {len(json.loads(req))} 个")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
ok, git = self._run("git log --oneline -1", timeout=5)
|
||||
if ok:
|
||||
info.append(f"🔀 最新提交: {git}")
|
||||
info.append(f" 最新提交: {git}")
|
||||
|
||||
ok, branch = self._run("git branch --show-current", timeout=5)
|
||||
if ok:
|
||||
info.append(f"🌿 分支: {branch}")
|
||||
info.append(f" 分支: {branch}")
|
||||
|
||||
py_count = len([f for f in self._walk_py(target or ".")])
|
||||
info.append(f"📄 Python 文件: {py_count}")
|
||||
info.append(f" Python 文件: {py_count}")
|
||||
|
||||
return '\n'.join(info)
|
||||
|
||||
@@ -432,17 +432,17 @@ class Plugin:
|
||||
".gitignore": "node_modules/\ndist/\n",
|
||||
}
|
||||
else:
|
||||
return f"❌ 不支持: {ptype}。支持: python, web"
|
||||
return f" 不支持: {ptype}。支持: python, web"
|
||||
|
||||
created = []
|
||||
for d in dirs:
|
||||
os.makedirs(d, exist_ok=True)
|
||||
created.append(f"📁 {d}/")
|
||||
created.append(f" {d}/")
|
||||
for f, content in files.items():
|
||||
if not os.path.exists(f):
|
||||
os.makedirs(os.path.dirname(f) or ".", exist_ok=True)
|
||||
with open(f, 'w') as fp:
|
||||
fp.write(content)
|
||||
created.append(f"📄 {f}")
|
||||
created.append(f" {f}")
|
||||
|
||||
return f"✅ 已初始化 {ptype} 项目\n" + '\n'.join(created)
|
||||
return f" 已初始化 {ptype} 项目\n" + '\n'.join(created)
|
||||
+44
-44
@@ -172,7 +172,7 @@ class Plugin:
|
||||
|
||||
sync_playwright = self._ensure_playwright()
|
||||
if not sync_playwright:
|
||||
return ("❌ 未安装 Playwright。安装步骤:\n"
|
||||
return (" 未安装 Playwright。安装步骤:\n"
|
||||
" pip install playwright\n"
|
||||
" playwright install chromium")
|
||||
|
||||
@@ -184,9 +184,9 @@ class Plugin:
|
||||
user_agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
)
|
||||
self._page = self._context.new_page()
|
||||
return "✅ 浏览器已启动 (headless Chromium, 1280×720)"
|
||||
return " 浏览器已启动 (headless Chromium, 1280×720)"
|
||||
except Exception as e:
|
||||
return f"❌ 启动失败: {e}"
|
||||
return f" 启动失败: {e}"
|
||||
|
||||
def _stop(self, args: str) -> str:
|
||||
try:
|
||||
@@ -202,14 +202,14 @@ class Plugin:
|
||||
self._context = None
|
||||
self._browser = None
|
||||
self._pw = None
|
||||
return "✅ 浏览器已关闭"
|
||||
return " 浏览器已关闭"
|
||||
|
||||
def _status(self, args: str) -> str:
|
||||
if self._page and not self._page.is_closed():
|
||||
url = self._page.url
|
||||
title = self._page.title()
|
||||
return f"🟢 运行中\n URL: {url}\n 标题: {title}"
|
||||
return "🔴 未运行"
|
||||
return f" 运行中\n URL: {url}\n 标题: {title}"
|
||||
return " 未运行"
|
||||
|
||||
def _require_page(self):
|
||||
"""确保页面可用,否则自动启动"""
|
||||
@@ -231,24 +231,24 @@ class Plugin:
|
||||
try:
|
||||
page.goto(url, wait_until="domcontentloaded", timeout=30000)
|
||||
title = page.title()
|
||||
return f"✅ 已打开: {url}\n 标题: {title}"
|
||||
return f" 已打开: {url}\n 标题: {title}"
|
||||
except Exception as e:
|
||||
return f"❌ 打开失败: {e}"
|
||||
return f" 打开失败: {e}"
|
||||
|
||||
def _back(self, args: str) -> str:
|
||||
page = self._require_page()
|
||||
page.go_back()
|
||||
return f"✅ 后退 → {page.url}"
|
||||
return f" 后退 → {page.url}"
|
||||
|
||||
def _forward(self, args: str) -> str:
|
||||
page = self._require_page()
|
||||
page.go_forward()
|
||||
return f"✅ 前进 → {page.url}"
|
||||
return f" 前进 → {page.url}"
|
||||
|
||||
def _reload(self, args: str) -> str:
|
||||
page = self._require_page()
|
||||
page.reload()
|
||||
return f"✅ 已刷新: {page.url}"
|
||||
return f" 已刷新: {page.url}"
|
||||
|
||||
def _title(self, args: str) -> str:
|
||||
page = self._require_page()
|
||||
@@ -267,9 +267,9 @@ class Plugin:
|
||||
page = self._require_page()
|
||||
try:
|
||||
page.click(selector, timeout=10000)
|
||||
return f"✅ 已点击: {selector}"
|
||||
return f" 已点击: {selector}"
|
||||
except Exception as e:
|
||||
return f"❌ 点击失败: {e}"
|
||||
return f" 点击失败: {e}"
|
||||
|
||||
def _fill(self, args: str) -> str:
|
||||
parts = args.strip().split(maxsplit=1)
|
||||
@@ -279,9 +279,9 @@ class Plugin:
|
||||
page = self._require_page()
|
||||
try:
|
||||
page.fill(selector, text, timeout=10000)
|
||||
return f"✅ 已填写: {selector}"
|
||||
return f" 已填写: {selector}"
|
||||
except Exception as e:
|
||||
return f"❌ 填写失败: {e}"
|
||||
return f" 填写失败: {e}"
|
||||
|
||||
def _select(self, args: str) -> str:
|
||||
parts = args.strip().split(maxsplit=1)
|
||||
@@ -291,38 +291,38 @@ class Plugin:
|
||||
page = self._require_page()
|
||||
try:
|
||||
page.select_option(selector, value, timeout=10000)
|
||||
return f"✅ 已选择: {selector} = {value}"
|
||||
return f" 已选择: {selector} = {value}"
|
||||
except Exception as e:
|
||||
return f"❌ 选择失败: {e}"
|
||||
return f" 选择失败: {e}"
|
||||
|
||||
def _check(self, selector: str) -> str:
|
||||
page = self._require_page()
|
||||
try:
|
||||
page.check(selector.strip(), timeout=10000)
|
||||
return f"✅ 已勾选: {selector}"
|
||||
return f" 已勾选: {selector}"
|
||||
except Exception as e:
|
||||
return f"❌ 勾选失败: {e}"
|
||||
return f" 勾选失败: {e}"
|
||||
|
||||
def _uncheck(self, selector: str) -> str:
|
||||
page = self._require_page()
|
||||
try:
|
||||
page.uncheck(selector.strip(), timeout=10000)
|
||||
return f"✅ 已取消勾选: {selector}"
|
||||
return f" 已取消勾选: {selector}"
|
||||
except Exception as e:
|
||||
return f"❌ 取消勾选失败: {e}"
|
||||
return f" 取消勾选失败: {e}"
|
||||
|
||||
def _hover(self, selector: str) -> str:
|
||||
page = self._require_page()
|
||||
try:
|
||||
page.hover(selector.strip(), timeout=10000)
|
||||
return f"✅ 悬停: {selector}"
|
||||
return f" 悬停: {selector}"
|
||||
except Exception as e:
|
||||
return f"❌ 悬停失败: {e}"
|
||||
return f" 悬停失败: {e}"
|
||||
|
||||
def _press(self, key: str) -> str:
|
||||
page = self._require_page()
|
||||
page.keyboard.press(key.strip())
|
||||
return f"✅ 按键: {key}"
|
||||
return f" 按键: {key}"
|
||||
|
||||
# ── 提取 ──
|
||||
|
||||
@@ -338,7 +338,7 @@ class Plugin:
|
||||
return text
|
||||
return f"未找到元素: {selector}"
|
||||
except Exception as e:
|
||||
return f"❌ 获取文本失败: {e}"
|
||||
return f" 获取文本失败: {e}"
|
||||
|
||||
def _html(self, selector: str) -> str:
|
||||
selector = selector.strip() or "body"
|
||||
@@ -352,7 +352,7 @@ class Plugin:
|
||||
return html
|
||||
return f"未找到元素: {selector}"
|
||||
except Exception as e:
|
||||
return f"❌ 获取 HTML 失败: {e}"
|
||||
return f" 获取 HTML 失败: {e}"
|
||||
|
||||
def _attr(self, args: str) -> str:
|
||||
parts = args.strip().split(maxsplit=1)
|
||||
@@ -367,7 +367,7 @@ class Plugin:
|
||||
return f"{attr_name} = {val}"
|
||||
return f"未找到元素: {selector}"
|
||||
except Exception as e:
|
||||
return f"❌ 获取属性失败: {e}"
|
||||
return f" 获取属性失败: {e}"
|
||||
|
||||
def _value(self, selector: str) -> str:
|
||||
page = self._require_page()
|
||||
@@ -377,7 +377,7 @@ class Plugin:
|
||||
return el.input_value()
|
||||
return f"未找到元素: {selector}"
|
||||
except Exception as e:
|
||||
return f"❌ 获取值失败: {e}"
|
||||
return f" 获取值失败: {e}"
|
||||
|
||||
def _eval(self, script: str) -> str:
|
||||
script = script.strip()
|
||||
@@ -390,7 +390,7 @@ class Plugin:
|
||||
return json.dumps(result, ensure_ascii=False, indent=2)
|
||||
return str(result)
|
||||
except Exception as e:
|
||||
return f"❌ JS 执行失败: {e}"
|
||||
return f" JS 执行失败: {e}"
|
||||
|
||||
def _query(self, selector: str) -> str:
|
||||
selector = selector.strip()
|
||||
@@ -411,7 +411,7 @@ class Plugin:
|
||||
result.append(f" ... 还有 {len(elements) - 20} 个")
|
||||
return '\n'.join(result)
|
||||
except Exception as e:
|
||||
return f"❌ 查询失败: {e}"
|
||||
return f" 查询失败: {e}"
|
||||
|
||||
# ── 截图 ──
|
||||
|
||||
@@ -443,9 +443,9 @@ class Plugin:
|
||||
page.screenshot(path=path, full_page=True)
|
||||
|
||||
size = os.path.getsize(path)
|
||||
return f"✅ 截图已保存: {path} ({size/1024:.1f} KB)"
|
||||
return f" 截图已保存: {path} ({size/1024:.1f} KB)"
|
||||
except Exception as e:
|
||||
return f"❌ 截图失败: {e}"
|
||||
return f" 截图失败: {e}"
|
||||
|
||||
# ── 等待 ──
|
||||
|
||||
@@ -458,9 +458,9 @@ class Plugin:
|
||||
page = self._require_page()
|
||||
try:
|
||||
page.wait_for_selector(selector, timeout=timeout)
|
||||
return f"✅ 元素已出现: {selector}"
|
||||
return f" 元素已出现: {selector}"
|
||||
except Exception:
|
||||
return f"⏱️ 等待超时: {selector}"
|
||||
return f" 等待超时: {selector}"
|
||||
|
||||
def _wait_gone(self, args: str) -> str:
|
||||
parts = args.strip().split()
|
||||
@@ -471,9 +471,9 @@ class Plugin:
|
||||
page = self._require_page()
|
||||
try:
|
||||
page.wait_for_selector(selector, state="hidden", timeout=timeout)
|
||||
return f"✅ 元素已消失: {selector}"
|
||||
return f" 元素已消失: {selector}"
|
||||
except Exception:
|
||||
return f"⏱️ 等待超时: {selector}"
|
||||
return f" 等待超时: {selector}"
|
||||
|
||||
def _wait_url(self, args: str) -> str:
|
||||
parts = args.strip().split()
|
||||
@@ -484,9 +484,9 @@ class Plugin:
|
||||
page = self._require_page()
|
||||
try:
|
||||
page.wait_for_url(f"**{url_part}**", timeout=timeout)
|
||||
return f"✅ URL 已变化: {page.url}"
|
||||
return f" URL 已变化: {page.url}"
|
||||
except Exception:
|
||||
return f"⏱️ 等待超时: URL 未变化"
|
||||
return f" 等待超时: URL 未变化"
|
||||
|
||||
# ── 高级 ──
|
||||
|
||||
@@ -496,9 +496,9 @@ class Plugin:
|
||||
try:
|
||||
page.pdf(path=path)
|
||||
size = os.path.getsize(path)
|
||||
return f"✅ PDF 已保存: {path} ({size/1024:.1f} KB)"
|
||||
return f" PDF 已保存: {path} ({size/1024:.1f} KB)"
|
||||
except Exception as e:
|
||||
return f"❌ PDF 生成失败: {e}"
|
||||
return f" PDF 生成失败: {e}"
|
||||
|
||||
def _cookies(self, args: str) -> str:
|
||||
page = self._require_page()
|
||||
@@ -517,7 +517,7 @@ class Plugin:
|
||||
return "错误:cookie 必须是 JSON 格式: {\"name\": \"...\", \"value\": \"...\", \"domain\": \"...\"}"
|
||||
page = self._require_page()
|
||||
page.context.add_cookies([cookie])
|
||||
return f"✅ 已设置 cookie: {cookie.get('name', '?')}"
|
||||
return f" 已设置 cookie: {cookie.get('name', '?')}"
|
||||
|
||||
def _viewport(self, args: str) -> str:
|
||||
parts = args.strip().split()
|
||||
@@ -529,7 +529,7 @@ class Plugin:
|
||||
return "错误:宽高必须是整数"
|
||||
page = self._require_page()
|
||||
page.set_viewport_size({"width": w, "height": h})
|
||||
return f"✅ 视口已设置: {w}×{h}"
|
||||
return f" 视口已设置: {w}×{h}"
|
||||
|
||||
def _download(self, args: str) -> str:
|
||||
parts = args.strip().split()
|
||||
@@ -544,6 +544,6 @@ class Plugin:
|
||||
download = download_info.value
|
||||
download.save_as(save_path)
|
||||
size = os.path.getsize(save_path)
|
||||
return f"✅ 已下载: {save_path} ({size/1024:.1f} KB)"
|
||||
return f" 已下载: {save_path} ({size/1024:.1f} KB)"
|
||||
except Exception as e:
|
||||
return f"❌ 下载失败: {e}"
|
||||
return f" 下载失败: {e}"
|
||||
@@ -96,7 +96,7 @@ class Plugin:
|
||||
# 安全检查
|
||||
for blocked in self.BLOCKED:
|
||||
if blocked in rest:
|
||||
return f"🚫 安全拦截: 检测到危险命令模式 '{blocked}'"
|
||||
return f" 安全拦截: 检测到危险命令模式 '{blocked}'"
|
||||
|
||||
return self._execute(rest, timeout)
|
||||
|
||||
@@ -139,6 +139,6 @@ class Plugin:
|
||||
return output.strip()
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return f"⏱️ 命令超时 ({timeout}s): {command}"
|
||||
return f" 命令超时 ({timeout}s): {command}"
|
||||
except Exception as e:
|
||||
return f"执行失败: {str(e)}"
|
||||
+24
-24
@@ -175,7 +175,7 @@ class Plugin:
|
||||
old.splitlines(keepends=True), new.splitlines(keepends=True),
|
||||
fromfile="原始", tofile="修改后", lineterm='')
|
||||
text = '\n'.join(diff)
|
||||
return f"📝 变更预览:\n{text}" if text else "内容无变化"
|
||||
return f" 变更预览:\n{text}" if text else "内容无变化"
|
||||
|
||||
# ══════════════════════════════════════
|
||||
# 编辑操作
|
||||
@@ -225,7 +225,7 @@ class Plugin:
|
||||
with open(fp, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
|
||||
return f"✅ 已编辑: {fp}\n\n{self._make_diff(old_text, new_text)}"
|
||||
return f" 已编辑: {fp}\n\n{self._make_diff(old_text, new_text)}"
|
||||
|
||||
def _op_multi_edit(self, path: str, rest: str) -> str:
|
||||
import json
|
||||
@@ -250,16 +250,16 @@ class Plugin:
|
||||
old = op.get("old", "").strip()
|
||||
new = op.get("new", "").strip()
|
||||
if not old:
|
||||
results.append(f" ✗ 操作 {i+1}: old 为空")
|
||||
results.append(f" 操作 {i+1}: old 为空")
|
||||
elif old not in content:
|
||||
results.append(f" ✗ 操作 {i+1}: 未找到匹配")
|
||||
results.append(f" 操作 {i+1}: 未找到匹配")
|
||||
else:
|
||||
content = content.replace(old, new, 1)
|
||||
results.append(f" ✓ 操作 {i+1}: 已替换")
|
||||
results.append(f" 操作 {i+1}: 已替换")
|
||||
|
||||
with open(fp, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
return f"✅ 批量编辑: {fp}\n" + "\n".join(results)
|
||||
return f" 批量编辑: {fp}\n" + "\n".join(results)
|
||||
|
||||
def _op_insert(self, path: str, rest: str) -> str:
|
||||
if not path:
|
||||
@@ -287,7 +287,7 @@ class Plugin:
|
||||
|
||||
with open(fp, 'w', encoding='utf-8') as f:
|
||||
f.writelines(lines)
|
||||
return f"✅ 已在第 {line_num} 行插入 {len(insert_lines)} 行"
|
||||
return f" 已在第 {line_num} 行插入 {len(insert_lines)} 行"
|
||||
|
||||
def _op_delete_lines(self, path: str, rest: str) -> str:
|
||||
if not path:
|
||||
@@ -315,7 +315,7 @@ class Plugin:
|
||||
|
||||
with open(fp, 'w', encoding='utf-8') as f:
|
||||
f.writelines(lines)
|
||||
return f"✅ 已删除第 {start}-{end} 行 ({len(deleted)} 行)"
|
||||
return f" 已删除第 {start}-{end} 行 ({len(deleted)} 行)"
|
||||
|
||||
def _op_create(self, path: str, rest: str) -> str:
|
||||
if not path:
|
||||
@@ -328,7 +328,7 @@ class Plugin:
|
||||
os.makedirs(dir_path, exist_ok=True)
|
||||
with open(fp, 'w', encoding='utf-8') as f:
|
||||
f.write(rest or "")
|
||||
return f"✅ 已创建: {fp} ({len(rest or '')} 字符)"
|
||||
return f" 已创建: {fp} ({len(rest or '')} 字符)"
|
||||
|
||||
def _op_write(self, path: str, rest: str) -> str:
|
||||
if not path:
|
||||
@@ -339,7 +339,7 @@ class Plugin:
|
||||
os.makedirs(dir_path, exist_ok=True)
|
||||
with open(fp, 'w', encoding='utf-8') as f:
|
||||
f.write(rest)
|
||||
return f"✅ 已写入: {fp} ({len(rest)} 字符)"
|
||||
return f" 已写入: {fp} ({len(rest)} 字符)"
|
||||
|
||||
def _op_append(self, path: str, rest: str) -> str:
|
||||
if not path:
|
||||
@@ -349,7 +349,7 @@ class Plugin:
|
||||
return f"错误:文件不存在: {fp}"
|
||||
with open(fp, 'a', encoding='utf-8') as f:
|
||||
f.write(rest)
|
||||
return f"✅ 已追加: {fp} ({len(rest)} 字符)"
|
||||
return f" 已追加: {fp} ({len(rest)} 字符)"
|
||||
|
||||
# ══════════════════════════════════════
|
||||
# 查看操作
|
||||
@@ -376,7 +376,7 @@ class Plugin:
|
||||
|
||||
selected = lines[start-1:end]
|
||||
numbered = [f"{start+i:4d} | {line.rstrip()}" for i, line in enumerate(selected)]
|
||||
return f"📄 {fp} (第 {start}-{end} 行,共 {total} 行)\n{'─'*60}\n" + '\n'.join(numbered)
|
||||
return f" {fp} (第 {start}-{end} 行,共 {total} 行)\n{'─'*60}\n" + '\n'.join(numbered)
|
||||
|
||||
def _op_diff(self, path: str, rest: str) -> str:
|
||||
if not path:
|
||||
@@ -415,7 +415,7 @@ class Plugin:
|
||||
except SyntaxError as e:
|
||||
return f"错误:语法错误 - {e}"
|
||||
|
||||
info = [f"📄 {fp} AST 摘要:"]
|
||||
info = [f" {fp} AST 摘要:"]
|
||||
for node in ast.iter_child_nodes(tree):
|
||||
if isinstance(node, ast.ClassDef):
|
||||
methods = [n.name for n in ast.iter_child_nodes(node)
|
||||
@@ -430,7 +430,7 @@ class Plugin:
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
info.append(f" from {node.module} import {', '.join(a.name for a in node.names)}")
|
||||
|
||||
return '\n'.join(info) if len(info) > 1 else f"📄 {fp}: 文件为空"
|
||||
return '\n'.join(info) if len(info) > 1 else f" {fp}: 文件为空"
|
||||
|
||||
# ══════════════════════════════════════
|
||||
# 搜索操作
|
||||
@@ -466,7 +466,7 @@ class Plugin:
|
||||
|
||||
if not results:
|
||||
return f"未找到 '{keyword}' 的匹配"
|
||||
return f"🔍 搜索 '{keyword}' ({len(results)}{'+' if len(results) >= 80 else ''} 匹配):\n" + '\n'.join(results)
|
||||
return f" 搜索 '{keyword}' ({len(results)}{'+' if len(results) >= 80 else ''} 匹配):\n" + '\n'.join(results)
|
||||
|
||||
def _op_regex(self, directory: str, rest: str) -> str:
|
||||
if not directory:
|
||||
@@ -500,7 +500,7 @@ class Plugin:
|
||||
|
||||
if not results:
|
||||
return f"未找到匹配 '{pattern}'"
|
||||
return f"🔍 正则搜索 ({len(results)}{'+' if len(results) >= 50 else ''} 匹配):\n" + '\n'.join(results)
|
||||
return f" 正则搜索 ({len(results)}{'+' if len(results) >= 50 else ''} 匹配):\n" + '\n'.join(results)
|
||||
|
||||
def _op_symbols(self, directory: str, rest: str) -> str:
|
||||
if not directory:
|
||||
@@ -528,12 +528,12 @@ class Plugin:
|
||||
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
symbols.append(f" def {node.name}() (行 {node.lineno})")
|
||||
if symbols:
|
||||
all_symbols.append(f"\n📄 {rel}:")
|
||||
all_symbols.append(f"\n {rel}:")
|
||||
all_symbols.extend(symbols)
|
||||
except (SyntaxError, UnicodeDecodeError):
|
||||
continue
|
||||
|
||||
return "📋 代码符号:\n" + '\n'.join(all_symbols) if all_symbols else "未找到 Python 符号"
|
||||
return " 代码符号:\n" + '\n'.join(all_symbols) if all_symbols else "未找到 Python 符号"
|
||||
|
||||
def _op_imports(self, file_path: str, rest: str) -> str:
|
||||
if not file_path:
|
||||
@@ -553,7 +553,7 @@ class Plugin:
|
||||
names = ', '.join(a.name for a in node.names)
|
||||
imports.append(f" from {node.module} import {names} (行 {node.lineno})")
|
||||
|
||||
return f"📦 {file_path} 导入:\n" + '\n'.join(imports) if imports else f"📄 {file_path}: 没有导入"
|
||||
return f" {file_path} 导入:\n" + '\n'.join(imports) if imports else f" {file_path}: 没有导入"
|
||||
|
||||
def _op_callers(self, directory: str, rest: str) -> str:
|
||||
if not directory:
|
||||
@@ -581,7 +581,7 @@ class Plugin:
|
||||
except (UnicodeDecodeError, PermissionError):
|
||||
continue
|
||||
|
||||
return f"🔎 '{func_name}' 的调用 ({len(results)}):\n" + '\n'.join(results) if results else f"未找到调用 '{func_name}'"
|
||||
return f" '{func_name}' 的调用 ({len(results)}):\n" + '\n'.join(results) if results else f"未找到调用 '{func_name}'"
|
||||
|
||||
def _op_todo(self, directory: str, rest: str) -> str:
|
||||
if not directory:
|
||||
@@ -602,7 +602,7 @@ class Plugin:
|
||||
except (UnicodeDecodeError, PermissionError):
|
||||
continue
|
||||
|
||||
return f"📝 TODO/FIXME ({len(results)}):\n" + '\n'.join(results) if results else "没有 TODO/FIXME"
|
||||
return f" TODO/FIXME ({len(results)}):\n" + '\n'.join(results) if results else "没有 TODO/FIXME"
|
||||
|
||||
def _op_structure(self, directory: str, rest: str) -> str:
|
||||
if not directory:
|
||||
@@ -612,7 +612,7 @@ class Plugin:
|
||||
return f"错误:目录不存在: {search_dir}"
|
||||
|
||||
max_depth = int(rest) if rest.strip().isdigit() else 3
|
||||
lines = [f"📁 {os.path.basename(search_dir)}/"]
|
||||
lines = [f" {os.path.basename(search_dir)}/"]
|
||||
skip = {'.git', 'node_modules', '__pycache__', '.venv', 'venv', 'env', '.tox'}
|
||||
|
||||
def _tree(path, prefix="", depth=0):
|
||||
@@ -660,7 +660,7 @@ class Plugin:
|
||||
start = max(1, line_num - ctx)
|
||||
end = min(total, line_num + ctx)
|
||||
|
||||
result = [f"📄 {path} 第 {start}-{end} 行 (目标: 第 {line_num} 行):", "─" * 60]
|
||||
result = [f" {path} 第 {start}-{end} 行 (目标: 第 {line_num} 行):", "─" * 60]
|
||||
for i in range(start - 1, end):
|
||||
marker = ">>>" if i + 1 == line_num else " "
|
||||
result.append(f"{marker} {i+1:4d} | {lines[i].rstrip()}")
|
||||
@@ -689,7 +689,7 @@ class Plugin:
|
||||
except (UnicodeDecodeError, PermissionError):
|
||||
continue
|
||||
|
||||
return (f"📊 代码统计 ({search_dir}):\n"
|
||||
return (f" 代码统计 ({search_dir}):\n"
|
||||
f" 文件: {len(files)}\n"
|
||||
f" 总行: {total}\n"
|
||||
f" 代码: {code}\n"
|
||||
|
||||
+12
-12
@@ -146,9 +146,9 @@ class Plugin:
|
||||
dirs = sum(1 for x in items if x[0] == 'dir')
|
||||
files = len(items) - dirs
|
||||
|
||||
result = [f"📁 {path}", "─" * 50]
|
||||
result = [f" {path}", "─" * 50]
|
||||
for t, name, size in items[:100]:
|
||||
result.append(f" {'📁' if t == 'dir' else '📄'} {name}{'/' if t == 'dir' else f' ({size})'}")
|
||||
result.append(f" {'' if t == 'dir' else ''} {name}{'/' if t == 'dir' else f' ({size})'}")
|
||||
if len(items) > 100:
|
||||
result.append(f" ... 还有 {len(items) - 100} 项")
|
||||
result.append("─" * 50)
|
||||
@@ -189,11 +189,11 @@ class Plugin:
|
||||
if i < max_lines:
|
||||
lines.append(f"{i+1:4d} | {line.rstrip()}")
|
||||
content = '\n'.join(lines)
|
||||
header = f"📄 {file_path} ({size}B, 前 {min(max_lines, total)}/{total} 行)"
|
||||
header = f" {file_path} ({size}B, 前 {min(max_lines, total)}/{total} 行)"
|
||||
else:
|
||||
all_lines = f.readlines()
|
||||
content = '\n'.join(f"{i+1:4d} | {l.rstrip()}" for i, l in enumerate(all_lines))
|
||||
header = f"📄 {file_path} ({size}B, {len(all_lines)} 行)"
|
||||
header = f" {file_path} ({size}B, {len(all_lines)} 行)"
|
||||
|
||||
return f"{header}\n{'─' * 50}\n{content}"
|
||||
except UnicodeDecodeError:
|
||||
@@ -214,7 +214,7 @@ class Plugin:
|
||||
os.makedirs(os.path.dirname(fp) or ".", exist_ok=True)
|
||||
with open(fp, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
return f"✅ 已写入: {fp} ({len(content)} 字符)"
|
||||
return f" 已写入: {fp} ({len(content)} 字符)"
|
||||
|
||||
def _op_append(self, args: List[str]) -> str:
|
||||
if len(args) < 2:
|
||||
@@ -232,7 +232,7 @@ class Plugin:
|
||||
return f"错误:文件不存在: {fp}"
|
||||
with open(fp, 'a', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
return f"✅ 已追加: {fp}"
|
||||
return f" 已追加: {fp}"
|
||||
|
||||
def _op_copy(self, args: List[str]) -> str:
|
||||
if len(args) < 2:
|
||||
@@ -245,7 +245,7 @@ class Plugin:
|
||||
shutil.copy2(src, dst)
|
||||
else:
|
||||
shutil.copytree(src, dst, dirs_exist_ok=True)
|
||||
return f"✅ 已复制:\n {src}\n → {dst}"
|
||||
return f" 已复制:\n {src}\n → {dst}"
|
||||
|
||||
def _op_move(self, args: List[str]) -> str:
|
||||
if len(args) < 2:
|
||||
@@ -255,7 +255,7 @@ class Plugin:
|
||||
return f"错误:源不存在: {src}"
|
||||
os.makedirs(os.path.dirname(dst) or ".", exist_ok=True)
|
||||
shutil.move(src, dst)
|
||||
return f"✅ 已移动:\n {src}\n → {dst}"
|
||||
return f" 已移动:\n {src}\n → {dst}"
|
||||
|
||||
def _op_delete(self, args: List[str]) -> str:
|
||||
if not args:
|
||||
@@ -266,14 +266,14 @@ class Plugin:
|
||||
return f"错误:不存在: {path}"
|
||||
if os.path.isfile(path):
|
||||
os.remove(path)
|
||||
return f"✅ 已删除文件: {path}"
|
||||
return f" 已删除文件: {path}"
|
||||
elif os.path.isdir(path):
|
||||
if recursive:
|
||||
shutil.rmtree(path)
|
||||
return f"✅ 已删除目录: {path}"
|
||||
return f" 已删除目录: {path}"
|
||||
elif not os.listdir(path):
|
||||
os.rmdir(path)
|
||||
return f"✅ 已删除空目录: {path}"
|
||||
return f" 已删除空目录: {path}"
|
||||
else:
|
||||
return "错误:目录不为空,用 -r 递归删除"
|
||||
|
||||
@@ -346,4 +346,4 @@ class Plugin:
|
||||
if os.path.exists(path):
|
||||
return f"错误:已存在: {path}"
|
||||
os.makedirs(path, exist_ok=True)
|
||||
return f"✅ 已创建: {path}"
|
||||
return f" 已创建: {path}"
|
||||
+45
-45
@@ -29,12 +29,12 @@ JSON格式示例:
|
||||
- analyze <项目路径> - 分析整个前端项目的结构和问题
|
||||
|
||||
支持的检查项:
|
||||
✓ HTML语法验证
|
||||
✓ CSS语法和兼容性检查
|
||||
✓ JavaScript语法检查
|
||||
✓ 响应式设计检查
|
||||
✓ 可访问性检查
|
||||
✓ 性能优化建议"""
|
||||
HTML语法验证
|
||||
CSS语法和兼容性检查
|
||||
JavaScript语法检查
|
||||
响应式设计检查
|
||||
可访问性检查
|
||||
性能优化建议"""
|
||||
self.cli = None
|
||||
|
||||
def set_cli(self, cli):
|
||||
@@ -138,13 +138,13 @@ JSON格式示例:
|
||||
def _validate_html(self, file_path):
|
||||
"""验证HTML文件"""
|
||||
if not os.path.exists(file_path):
|
||||
return f"❌ 文件不存在: {file_path}"
|
||||
return f" 文件不存在: {file_path}"
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
result = f"\n🧪 HTML验证结果: {file_path}\n"
|
||||
result = f"\n HTML验证结果: {file_path}\n"
|
||||
result += "=" * 60 + "\n\n"
|
||||
|
||||
# 基本检查
|
||||
@@ -191,7 +191,7 @@ JSON格式示例:
|
||||
link_count = content.count('<link')
|
||||
script_count = content.count('<script')
|
||||
|
||||
result += f"📊 文件统计:\n"
|
||||
result += f" 文件统计:\n"
|
||||
result += f" - div元素: {div_count}\n"
|
||||
result += f" - img元素: {img_count}\n"
|
||||
result += f" - img alt属性: {img_alt_count}\n"
|
||||
@@ -207,38 +207,38 @@ JSON格式示例:
|
||||
|
||||
# 显示问题
|
||||
if issues:
|
||||
result += f"⚠️ 发现 {len(issues)} 个问题:\n\n"
|
||||
result += f" 发现 {len(issues)} 个问题:\n\n"
|
||||
for i, issue in enumerate(issues, 1):
|
||||
level = issue['level']
|
||||
if level == 'error':
|
||||
icon = f"{Fore.RED}❌{Style.RESET_ALL}"
|
||||
icon = f"{Fore.RED}{Style.RESET_ALL}"
|
||||
elif level == 'warning':
|
||||
icon = f"{Fore.YELLOW}⚠️{Style.RESET_ALL}"
|
||||
icon = f"{Fore.YELLOW}{Style.RESET_ALL}"
|
||||
else:
|
||||
icon = f"{Fore.GREEN}ℹ️{Style.RESET_ALL}"
|
||||
icon = f"{Fore.GREEN}ℹ{Style.RESET_ALL}"
|
||||
result += f"{icon} {i}. {issue['message']}\n"
|
||||
else:
|
||||
result += f"{Fore.GREEN}✅ 未发现问题{Style.RESET_ALL}\n"
|
||||
result += f"{Fore.GREEN} 未发现问题{Style.RESET_ALL}\n"
|
||||
|
||||
result += "\n" + "=" * 60 + "\n"
|
||||
|
||||
return result
|
||||
|
||||
except UnicodeDecodeError:
|
||||
return "❌ 文件编码问题,无法读取"
|
||||
return " 文件编码问题,无法读取"
|
||||
except Exception as e:
|
||||
return f"❌ 验证失败: {str(e)}"
|
||||
return f" 验证失败: {str(e)}"
|
||||
|
||||
def _test_css(self, file_path):
|
||||
"""测试CSS文件"""
|
||||
if not os.path.exists(file_path):
|
||||
return f"❌ 文件不存在: {file_path}"
|
||||
return f" 文件不存在: {file_path}"
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
result = f"\n🧪 CSS测试结果: {file_path}\n"
|
||||
result = f"\n CSS测试结果: {file_path}\n"
|
||||
result += "=" * 60 + "\n\n"
|
||||
|
||||
issues = []
|
||||
@@ -268,44 +268,44 @@ JSON格式示例:
|
||||
rule_count = content.count('{')
|
||||
selector_count = len([line for line in content.split('\n') if '{' in line])
|
||||
|
||||
result += f"📊 文件统计:\n"
|
||||
result += f" 文件统计:\n"
|
||||
result += f" - CSS规则: {rule_count}\n"
|
||||
result += f" - 选择器: {selector_count}\n\n"
|
||||
|
||||
# 显示问题
|
||||
if issues:
|
||||
result += f"⚠️ 发现 {len(issues)} 个问题:\n\n"
|
||||
result += f" 发现 {len(issues)} 个问题:\n\n"
|
||||
for i, issue in enumerate(issues, 1):
|
||||
level = issue['level']
|
||||
if level == 'error':
|
||||
icon = f"{Fore.RED}❌{Style.RESET_ALL}"
|
||||
icon = f"{Fore.RED}{Style.RESET_ALL}"
|
||||
elif level == 'warning':
|
||||
icon = f"{Fore.YELLOW}⚠️{Style.RESET_ALL}"
|
||||
icon = f"{Fore.YELLOW}{Style.RESET_ALL}"
|
||||
else:
|
||||
icon = f"{Fore.GREEN}ℹ️{Style.RESET_ALL}"
|
||||
icon = f"{Fore.GREEN}ℹ{Style.RESET_ALL}"
|
||||
result += f"{icon} {i}. {issue['message']}\n"
|
||||
else:
|
||||
result += f"{Fore.GREEN}✅ 未发现问题{Style.RESET_ALL}\n"
|
||||
result += f"{Fore.GREEN} 未发现问题{Style.RESET_ALL}\n"
|
||||
|
||||
result += "\n" + "=" * 60 + "\n"
|
||||
|
||||
return result
|
||||
|
||||
except UnicodeDecodeError:
|
||||
return "❌ 文件编码问题,无法读取"
|
||||
return " 文件编码问题,无法读取"
|
||||
except Exception as e:
|
||||
return f"❌ 测试失败: {str(e)}"
|
||||
return f" 测试失败: {str(e)}"
|
||||
|
||||
def _check_js(self, file_path):
|
||||
"""检查JS文件"""
|
||||
if not os.path.exists(file_path):
|
||||
return f"❌ 文件不存在: {file_path}"
|
||||
return f" 文件不存在: {file_path}"
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
result = f"\n🧪 JavaScript检查结果: {file_path}\n"
|
||||
result = f"\n JavaScript检查结果: {file_path}\n"
|
||||
result += "=" * 60 + "\n\n"
|
||||
|
||||
issues = []
|
||||
@@ -339,52 +339,52 @@ JSON格式示例:
|
||||
line_count = len(content.split('\n'))
|
||||
function_count = content.count('function ')
|
||||
|
||||
result += f"📊 文件统计:\n"
|
||||
result += f" 文件统计:\n"
|
||||
result += f" - 代码行数: {line_count}\n"
|
||||
result += f" - 函数数量: {function_count}\n\n"
|
||||
|
||||
# 显示问题
|
||||
if issues:
|
||||
result += f"⚠️ 发现 {len(issues)} 个问题:\n\n"
|
||||
result += f" 发现 {len(issues)} 个问题:\n\n"
|
||||
for i, issue in enumerate(issues, 1):
|
||||
level = issue['level']
|
||||
if level == 'error':
|
||||
icon = f"{Fore.RED}❌{Style.RESET_ALL}"
|
||||
icon = f"{Fore.RED}{Style.RESET_ALL}"
|
||||
elif level == 'warning':
|
||||
icon = f"{Fore.YELLOW}⚠️{Style.RESET_ALL}"
|
||||
icon = f"{Fore.YELLOW}{Style.RESET_ALL}"
|
||||
else:
|
||||
icon = f"{Fore.GREEN}ℹ️{Style.RESET_ALL}"
|
||||
icon = f"{Fore.GREEN}ℹ{Style.RESET_ALL}"
|
||||
result += f"{icon} {i}. {issue['message']}\n"
|
||||
else:
|
||||
result += f"{Fore.GREEN}✅ 未发现问题{Style.RESET_ALL}\n"
|
||||
result += f"{Fore.GREEN} 未发现问题{Style.RESET_ALL}\n"
|
||||
|
||||
result += "\n" + "=" * 60 + "\n"
|
||||
|
||||
return result
|
||||
|
||||
except UnicodeDecodeError:
|
||||
return "❌ 文件编码问题,无法读取"
|
||||
return " 文件编码问题,无法读取"
|
||||
except Exception as e:
|
||||
return f"❌ 检查失败: {str(e)}"
|
||||
return f" 检查失败: {str(e)}"
|
||||
|
||||
def _preview_in_browser(self, file_path):
|
||||
"""在浏览器中预览"""
|
||||
if not os.path.exists(file_path):
|
||||
return f"❌ 文件不存在: {file_path}"
|
||||
return f" 文件不存在: {file_path}"
|
||||
|
||||
try:
|
||||
# Windows系统
|
||||
os.startfile(file_path)
|
||||
return f"✅ 已在默认浏览器中打开: {file_path}"
|
||||
return f" 已在默认浏览器中打开: {file_path}"
|
||||
except Exception as e:
|
||||
return f"❌ 无法打开浏览器: {str(e)}"
|
||||
return f" 无法打开浏览器: {str(e)}"
|
||||
|
||||
def _analyze_project(self, project_path):
|
||||
"""分析整个前端项目"""
|
||||
if not os.path.exists(project_path):
|
||||
return f"❌ 项目路径不存在: {project_path}"
|
||||
return f" 项目路径不存在: {project_path}"
|
||||
|
||||
result = f"\n🧪 前端项目分析: {project_path}\n"
|
||||
result = f"\n 前端项目分析: {project_path}\n"
|
||||
result += "=" * 60 + "\n\n"
|
||||
|
||||
try:
|
||||
@@ -402,13 +402,13 @@ JSON格式示例:
|
||||
elif file.endswith('.js'):
|
||||
js_files.append(os.path.join(root, file))
|
||||
|
||||
result += f"📊 项目文件统计:\n"
|
||||
result += f" 项目文件统计:\n"
|
||||
result += f" - HTML文件: {len(html_files)}\n"
|
||||
result += f" - CSS文件: {len(css_files)}\n"
|
||||
result += f" - JavaScript文件: {len(js_files)}\n\n"
|
||||
|
||||
# 建议检查
|
||||
result += f"💡 建议检查:\n"
|
||||
result += f" 建议检查:\n"
|
||||
|
||||
if html_files:
|
||||
result += f" - HTML文件:\n"
|
||||
@@ -431,13 +431,13 @@ JSON格式示例:
|
||||
if len(js_files) > 3:
|
||||
result += f" • ... 还有 {len(js_files) - 3} 个文件\n"
|
||||
|
||||
result += f"\n{Fore.GREEN}✅ 分析完成{Style.RESET_ALL}\n"
|
||||
result += f"\n{Fore.GREEN} 分析完成{Style.RESET_ALL}\n"
|
||||
result += "\n" + "=" * 60 + "\n"
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
return f"❌ 分析失败: {str(e)}"
|
||||
return f" 分析失败: {str(e)}"
|
||||
|
||||
|
||||
# 测试函数
|
||||
|
||||
+24
-24
@@ -164,7 +164,7 @@ class Plugin:
|
||||
if not output:
|
||||
# 获取分支
|
||||
_, branch = self._run_git(["branch", "--show-current"], cwd)
|
||||
return f"✅ 工作区干净 (分支: {branch})"
|
||||
return f" 工作区干净 (分支: {branch})"
|
||||
|
||||
lines = output.split('\n')
|
||||
staged, unstaged, untracked = [], [], []
|
||||
@@ -174,14 +174,14 @@ class Plugin:
|
||||
continue
|
||||
s, filename = line[:2], line[3:]
|
||||
if s[0] in 'MADRC':
|
||||
staged.append(f" ✓ {s[0]} {filename}")
|
||||
staged.append(f" {s[0]} {filename}")
|
||||
if s[1] in 'MD':
|
||||
unstaged.append(f" ! {s[1]} {filename}")
|
||||
if s == '??':
|
||||
untracked.append(f" ? {filename}")
|
||||
|
||||
_, branch = self._run_git(["branch", "--show-current"], cwd)
|
||||
result = [f"📊 Git 状态 (分支: {branch})"]
|
||||
result = [f" Git 状态 (分支: {branch})"]
|
||||
|
||||
if staged:
|
||||
result.append(f"\n已暂存 ({len(staged)}):")
|
||||
@@ -237,22 +237,22 @@ class Plugin:
|
||||
if not target:
|
||||
return "错误:请提供文件路径(使用 . 暂存所有)"
|
||||
ok, output = self._run_git(["add", target], cwd)
|
||||
return f"✅ 已暂存: {target}" if ok else f"Git add 失败: {output}"
|
||||
return f" 已暂存: {target}" if ok else f"Git add 失败: {output}"
|
||||
|
||||
def _commit(self, args: str, cwd=None) -> str:
|
||||
message = args.strip()
|
||||
if not message:
|
||||
return "错误:请提供提交信息"
|
||||
ok, output = self._run_git(["commit", "-m", message], cwd)
|
||||
return f"✅ 提交成功:\n{output}" if ok else f"Git commit 失败: {output}"
|
||||
return f" 提交成功:\n{output}" if ok else f"Git commit 失败: {output}"
|
||||
|
||||
def _branch(self, args: str, cwd=None) -> str:
|
||||
if not args.strip():
|
||||
ok, output = self._run_git(["branch", "-a"], cwd)
|
||||
return f"🌿 分支列表:\n{output}" if ok else f"Git 错误: {output}"
|
||||
return f" 分支列表:\n{output}" if ok else f"Git 错误: {output}"
|
||||
else:
|
||||
ok, output = self._run_git(["branch", args.strip()], cwd)
|
||||
return f"✅ 已创建分支: {args.strip()}" if ok else f"创建分支失败: {output}"
|
||||
return f" 已创建分支: {args.strip()}" if ok else f"创建分支失败: {output}"
|
||||
|
||||
def _checkout(self, args: str, cwd=None) -> str:
|
||||
args = args.strip()
|
||||
@@ -262,14 +262,14 @@ class Plugin:
|
||||
else:
|
||||
branch = args
|
||||
ok, output = self._run_git(["checkout", branch], cwd)
|
||||
return f"✅ 已切换到: {branch}" if ok else f"切换失败: {output}"
|
||||
return f" 已切换到: {branch}" if ok else f"切换失败: {output}"
|
||||
|
||||
def _stash(self, args: str, cwd=None) -> str:
|
||||
if args.strip() == "pop":
|
||||
ok, output = self._run_git(["stash", "pop"], cwd)
|
||||
else:
|
||||
ok, output = self._run_git(["stash"], cwd)
|
||||
return f"✅ {output}" if ok else f"Git stash 失败: {output}"
|
||||
return f" {output}" if ok else f"Git stash 失败: {output}"
|
||||
|
||||
def _show(self, args: str, cwd=None) -> str:
|
||||
commit = args.strip() or "HEAD"
|
||||
@@ -301,7 +301,7 @@ class Plugin:
|
||||
result = result[:100]
|
||||
result.append(f" ... (共 {len(result)} 行)")
|
||||
|
||||
return f"📝 {path} 修改记录:\n" + '\n'.join(result)
|
||||
return f" {path} 修改记录:\n" + '\n'.join(result)
|
||||
|
||||
def _remote(self, cwd=None) -> str:
|
||||
ok, output = self._run_git(["remote", "-v"], cwd)
|
||||
@@ -312,7 +312,7 @@ class Plugin:
|
||||
def _init(self, args: str, cwd=None) -> str:
|
||||
path = args.strip() or "."
|
||||
ok, output = self._run_git(["init", path], cwd)
|
||||
return f"✅ {output}" if ok else f"Git init 失败: {output}"
|
||||
return f" {output}" if ok else f"Git init 失败: {output}"
|
||||
|
||||
def _clone(self, args: str, cwd=None) -> str:
|
||||
parts = args.split()
|
||||
@@ -322,7 +322,7 @@ class Plugin:
|
||||
if len(parts) > 1:
|
||||
cmd.append(parts[1])
|
||||
ok, output = self._run_git(cmd, cwd)
|
||||
return f"✅ 克隆成功:\n{output}" if ok else f"Git clone 失败: {output}"
|
||||
return f" 克隆成功:\n{output}" if ok else f"Git clone 失败: {output}"
|
||||
|
||||
# ── 工作流 ──
|
||||
|
||||
@@ -330,7 +330,7 @@ class Plugin:
|
||||
"""智能提交:自动 add + 规范化消息"""
|
||||
ok, status = self._run_git(["status", "--porcelain"], cwd)
|
||||
if not status:
|
||||
return "✅ 工作区干净,没有需要提交的变更"
|
||||
return " 工作区干净,没有需要提交的变更"
|
||||
|
||||
self._run_git(["add", "-A"], cwd)
|
||||
|
||||
@@ -349,8 +349,8 @@ class Plugin:
|
||||
ok, output = self._run_git(["commit", "-m", message], cwd)
|
||||
if ok:
|
||||
_, hash_out = self._run_git(["log", "--oneline", "-1"], cwd)
|
||||
return f"✅ 已提交: {message}\n {hash_out}"
|
||||
return f"❌ 提交失败: {output}"
|
||||
return f" 已提交: {message}\n {hash_out}"
|
||||
return f" 提交失败: {output}"
|
||||
|
||||
def _auto_commit_message(self, status: str) -> str:
|
||||
lines = status.strip().split('\n')
|
||||
@@ -371,7 +371,7 @@ class Plugin:
|
||||
ok, output = self._run_shell(
|
||||
'git log --oneline --format="%s" | head -50', cwd)
|
||||
if not ok:
|
||||
return f"❌ {output}"
|
||||
return f" {output}"
|
||||
|
||||
commits = output.strip().split('\n')
|
||||
categories = {
|
||||
@@ -390,9 +390,9 @@ class Plugin:
|
||||
categories['other'].append(commit)
|
||||
|
||||
labels = {
|
||||
'feat': '🚀 新增', 'fix': '🐛 修复', 'docs': '📝 文档',
|
||||
'refactor': '♻️ 重构', 'test': '🧪 测试', 'chore': '🔧 杂项',
|
||||
'other': '📦 其他'
|
||||
'feat': ' 新增', 'fix': ' 修复', 'docs': ' 文档',
|
||||
'refactor': ' 重构', 'test': ' 测试', 'chore': ' 杂项',
|
||||
'other': ' 其他'
|
||||
}
|
||||
|
||||
ver = version or "未发布"
|
||||
@@ -409,16 +409,16 @@ class Plugin:
|
||||
def _summary(self, cwd=None) -> str:
|
||||
ok, output = self._run_git(["diff", "--stat"], cwd)
|
||||
if not output:
|
||||
return "✅ 没有未提交的变更"
|
||||
return " 没有未提交的变更"
|
||||
_, short = self._run_git(["diff", "--shortstat"], cwd)
|
||||
return f"📊 变更摘要:\n\n{short}\n\n{output[:2000]}"
|
||||
return f" 变更摘要:\n\n{short}\n\n{output[:2000]}"
|
||||
|
||||
def _contributors(self, cwd=None) -> str:
|
||||
ok, output = self._run_git(["shortlog", "-sn", "--all", "--no-merges"], cwd)
|
||||
if not ok:
|
||||
return f"❌ {output}"
|
||||
return f" {output}"
|
||||
lines = output.strip().split('\n')
|
||||
result = [f"👥 贡献者 ({len(lines)} 人):\n"]
|
||||
result = [f" 贡献者 ({len(lines)} 人):\n"]
|
||||
for line in lines[:20]:
|
||||
result.append(f" {line.strip()}")
|
||||
return '\n'.join(result)
|
||||
@@ -427,4 +427,4 @@ class Plugin:
|
||||
days = int(args) if args.strip().isdigit() else 90
|
||||
ok, output = self._run_shell(
|
||||
f"git log --diff-filter=A --format='%aI %n' --since='{days} days ago' | head -20", cwd)
|
||||
return f"📁 最近 {days} 天新增的文件:\n{output[:2000]}" if ok else f"❌ {output}"
|
||||
return f" 最近 {days} 天新增的文件:\n{output[:2000]}" if ok else f" {output}"
|
||||
@@ -177,7 +177,7 @@ class VoiceRecorder:
|
||||
|
||||
def _create_ui(self):
|
||||
# 标题
|
||||
title_label = tk.Label(self.root, text="🎙️ 语音识别录音", font=("Arial", 18, "bold"))
|
||||
title_label = tk.Label(self.root, text=" 语音识别录音", font=("Arial", 18, "bold"))
|
||||
title_label.pack(pady=15)
|
||||
|
||||
# 状态标签
|
||||
@@ -194,7 +194,7 @@ class VoiceRecorder:
|
||||
|
||||
# 开始/停止按钮
|
||||
self.record_btn = tk.Button(
|
||||
btn_frame, text="🎤 开始录音", font=("Arial", 12),
|
||||
btn_frame, text=" 开始录音", font=("Arial", 12),
|
||||
width=12, height=2, command=self.toggle_record,
|
||||
bg="#4CAF50", fg="white", cursor="hand2"
|
||||
)
|
||||
@@ -202,7 +202,7 @@ class VoiceRecorder:
|
||||
|
||||
# 识别按钮
|
||||
self.recognize_btn = tk.Button(
|
||||
btn_frame, text="📝 识别", font=("Arial", 12),
|
||||
btn_frame, text=" 识别", font=("Arial", 12),
|
||||
width=12, height=2, command=self.recognize,
|
||||
bg="#2196F3", fg="white", cursor="hand2", state=tk.DISABLED
|
||||
)
|
||||
@@ -231,8 +231,8 @@ class VoiceRecorder:
|
||||
self.is_recording = True
|
||||
self.frames = []
|
||||
self.start_time = time.time()
|
||||
self.record_btn.config(text="⏹️ 停止录音", bg="#f44336")
|
||||
self.status_label.config(text="🔴 正在录音...", fg="red")
|
||||
self.record_btn.config(text=" 停止录音", bg="#f44336")
|
||||
self.status_label.config(text=" 正在录音...", fg="red")
|
||||
self.recognize_btn.config(state=tk.DISABLED)
|
||||
|
||||
CHUNK = 1024
|
||||
@@ -263,7 +263,7 @@ class VoiceRecorder:
|
||||
|
||||
def stop_record(self):
|
||||
self.is_recording = False
|
||||
self.record_btn.config(text="🎤 开始录音", bg="#4CAF50")
|
||||
self.record_btn.config(text=" 开始录音", bg="#4CAF50")
|
||||
self.status_label.config(text="录音完成,可以识别", fg="green")
|
||||
self.recognize_btn.config(state=tk.NORMAL)
|
||||
|
||||
@@ -286,7 +286,7 @@ class VoiceRecorder:
|
||||
wf.close()
|
||||
|
||||
self.result_text.delete(1.0, tk.END)
|
||||
self.result_text.insert(tk.END, "✅ 录音已保存\n点击 [识别] 按钮进行语音识别")
|
||||
self.result_text.insert(tk.END, " 录音已保存\n点击 [识别] 按钮进行语音识别")
|
||||
|
||||
def _update_time(self):
|
||||
if self.is_recording:
|
||||
@@ -303,7 +303,7 @@ class VoiceRecorder:
|
||||
|
||||
self.status_label.config(text="正在识别...", fg="blue")
|
||||
self.result_text.delete(1.0, tk.END)
|
||||
self.result_text.insert(tk.END, "🔍 识别中,请稍候...")
|
||||
self.result_text.insert(tk.END, " 识别中,请稍候...")
|
||||
self.root.update()
|
||||
|
||||
# 尝试识别
|
||||
@@ -317,7 +317,7 @@ class VoiceRecorder:
|
||||
with open(RESULT_FILE, "w", encoding="utf-8") as f:
|
||||
f.write(result)
|
||||
else:
|
||||
self.result_text.insert(tk.END, "❌ 识别失败\n\n请安装识别引擎:\n• pip install openai-whisper\n• pip install SpeechRecognition")
|
||||
self.result_text.insert(tk.END, " 识别失败\n\n请安装识别引擎:\n• pip install openai-whisper\n• pip install SpeechRecognition")
|
||||
self.status_label.config(text="识别失败", fg="red")
|
||||
|
||||
def _do_recognize(self):
|
||||
@@ -330,7 +330,7 @@ class VoiceRecorder:
|
||||
result = model.transcribe(self.record_file, language="zh")
|
||||
text = result.get("text", "").strip()
|
||||
if text:
|
||||
return f"✅ {{text}}"
|
||||
return f" {{text}}"
|
||||
except Exception as e:
|
||||
print(f"Whisper error: {{e}}")
|
||||
|
||||
@@ -344,7 +344,7 @@ class VoiceRecorder:
|
||||
audio = r.record(source)
|
||||
text = r.recognize_google(audio, language="zh-CN")
|
||||
if text:
|
||||
return f"✅ {{text}}"
|
||||
return f" {{text}}"
|
||||
except Exception as e:
|
||||
print(f"SpeechRecognition error: {{e}}")
|
||||
|
||||
@@ -374,9 +374,9 @@ if __name__ == "__main__":
|
||||
try:
|
||||
subprocess.Popen([sys.executable, recorder_script],
|
||||
creationflags=subprocess.CREATE_NEW_CONSOLE)
|
||||
return "🎙️ 已弹出录音窗口,请在窗口中进行录音和识别\n\n使用方法:\n1. 点击「开始录音」\n2. 对着麦克风说话\n3. 点击「停止录音」\n4. 点击「识别」获取文字结果"
|
||||
return " 已弹出录音窗口,请在窗口中进行录音和识别\n\n使用方法:\n1. 点击「开始录音」\n2. 对着麦克风说话\n3. 点击「停止录音」\n4. 点击「识别」获取文字结果"
|
||||
except Exception as e:
|
||||
return f"❌ 启动录音窗口失败: {e}"
|
||||
return f" 启动录音窗口失败: {e}"
|
||||
|
||||
# ==================== 文件识别 ====================
|
||||
|
||||
@@ -388,7 +388,7 @@ if __name__ == "__main__":
|
||||
file_path = args[0]
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
return f"❌ 文件不存在: {file_path}"
|
||||
return f" 文件不存在: {file_path}"
|
||||
|
||||
return self._recognize_file(file_path)
|
||||
|
||||
@@ -407,7 +407,7 @@ if __name__ == "__main__":
|
||||
self._save_result(result)
|
||||
return result
|
||||
|
||||
return "❌ 语音识别失败:未安装识别引擎\n\n安装方法:\n1. Whisper (推荐): pip install openai-whisper\n2. SpeechRecognition: pip install SpeechRecognition\n3. PyAudio (录音): pip install pyaudio"
|
||||
return " 语音识别失败:未安装识别引擎\n\n安装方法:\n1. Whisper (推荐): pip install openai-whisper\n2. SpeechRecognition: pip install SpeechRecognition\n3. PyAudio (录音): pip install pyaudio"
|
||||
|
||||
def _save_result(self, result: str):
|
||||
"""保存识别结果"""
|
||||
@@ -428,7 +428,7 @@ if __name__ == "__main__":
|
||||
|
||||
text = result.get("text", "").strip()
|
||||
if text:
|
||||
return f"✅ 识别结果:\n{text}"
|
||||
return f" 识别结果:\n{text}"
|
||||
return None
|
||||
|
||||
except ImportError:
|
||||
@@ -456,7 +456,7 @@ if __name__ == "__main__":
|
||||
try:
|
||||
text = r.recognize_google(audio, language="zh-CN")
|
||||
if text:
|
||||
return f"✅ 识别结果:\n{text}"
|
||||
return f" 识别结果:\n{text}"
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -502,28 +502,28 @@ if __name__ == "__main__":
|
||||
|
||||
def _op_status(self, args: List[str]) -> str:
|
||||
"""查看状态"""
|
||||
lines = ["📊 语音识别插件状态"]
|
||||
lines = [" 语音识别插件状态"]
|
||||
|
||||
# 检查可用引擎
|
||||
engines = []
|
||||
|
||||
try:
|
||||
import whisper
|
||||
engines.append("whisper ✅")
|
||||
engines.append("whisper ")
|
||||
except:
|
||||
engines.append("whisper ❌ (pip install openai-whisper)")
|
||||
engines.append("whisper (pip install openai-whisper)")
|
||||
|
||||
try:
|
||||
import speech_recognition
|
||||
engines.append("speech_recognition ✅")
|
||||
engines.append("speech_recognition ")
|
||||
except:
|
||||
engines.append("speech_recognition ❌ (pip install SpeechRecognition)")
|
||||
engines.append("speech_recognition (pip install SpeechRecognition)")
|
||||
|
||||
try:
|
||||
import pyaudio
|
||||
engines.append("pyaudio ✅ (录音支持)")
|
||||
engines.append("pyaudio (录音支持)")
|
||||
except:
|
||||
engines.append("pyaudio ❌ (pip install pyaudio)")
|
||||
engines.append("pyaudio (pip install pyaudio)")
|
||||
|
||||
lines.append("\n可用引擎:")
|
||||
for e in engines:
|
||||
|
||||
@@ -177,7 +177,7 @@ class Plugin:
|
||||
thread = threading.Thread(target=self._run_agent, args=(agent,), daemon=True)
|
||||
thread.start()
|
||||
|
||||
return (f"✅ 已创建子 Agent\n"
|
||||
return (f" 已创建子 Agent\n"
|
||||
f" ID: {agent_id}\n"
|
||||
f" 名称: {name}\n"
|
||||
f" 任务: {task}\n"
|
||||
@@ -333,17 +333,17 @@ class Plugin:
|
||||
failed = [a for a in self.agents.values() if a.status == "failed"]
|
||||
|
||||
if running:
|
||||
result.append(f"🟢 运行中 ({len(running)}):")
|
||||
result.append(f" 运行中 ({len(running)}):")
|
||||
for a in running:
|
||||
result.append(f" {a.id} [{a.name}] 步骤:{a.steps} 任务:{a.task[:40]}")
|
||||
|
||||
if done:
|
||||
result.append(f"✅ 已完成 ({len(done)}):")
|
||||
result.append(f" 已完成 ({len(done)}):")
|
||||
for a in done:
|
||||
result.append(f" {a.id} [{a.name}] 任务:{a.task[:40]}")
|
||||
|
||||
if failed:
|
||||
result.append(f"❌ 失败 ({len(failed)}):")
|
||||
result.append(f" 失败 ({len(failed)}):")
|
||||
for a in failed:
|
||||
result.append(f" {a.id} [{a.name}] 错误:{a.error[:40] if a.error else '?'}")
|
||||
|
||||
@@ -432,7 +432,7 @@ class Plugin:
|
||||
thread = threading.Thread(target=self._process_message, args=(agent, message), daemon=True)
|
||||
thread.start()
|
||||
|
||||
return f"✅ 消息已发送给 {agent.id}"
|
||||
return f" 消息已发送给 {agent.id}"
|
||||
|
||||
def _process_message(self, agent: SubAgent, message: str):
|
||||
"""处理发送给子 Agent 的消息"""
|
||||
@@ -482,7 +482,7 @@ class Plugin:
|
||||
agent.status = "failed"
|
||||
agent.error = "被主 Agent 终止"
|
||||
agent.finished_at = datetime.now().strftime("%H:%M:%S")
|
||||
return f"✅ 已终止 {agent.id}"
|
||||
return f" 已终止 {agent.id}"
|
||||
|
||||
def _clean(self, args: str) -> str:
|
||||
"""清理已完成的 Agent"""
|
||||
@@ -492,7 +492,7 @@ class Plugin:
|
||||
for aid in to_remove:
|
||||
del self.agents[aid]
|
||||
|
||||
return f"✅ 已清理 {len(to_remove)} 个已完成的 Agent"
|
||||
return f" 已清理 {len(to_remove)} 个已完成的 Agent"
|
||||
|
||||
def _stats(self, args: str) -> str:
|
||||
"""统计信息"""
|
||||
@@ -503,7 +503,7 @@ class Plugin:
|
||||
failed = sum(1 for a in self.agents.values() if a.status == "failed")
|
||||
total_steps = sum(a.steps for a in self.agents.values())
|
||||
|
||||
return (f"📊 子 Agent 统计\n"
|
||||
return (f" 子 Agent 统计\n"
|
||||
f" 总数: {total}\n"
|
||||
f" 运行中: {running}\n"
|
||||
f" 已完成: {done}\n"
|
||||
|
||||
+14
-14
@@ -170,14 +170,14 @@ JSON格式示例:
|
||||
error = self._save_tasks()
|
||||
if error:
|
||||
return error
|
||||
return f"✅ 已添加任务 #{task_id}: {description}"
|
||||
return f" 已添加任务 #{task_id}: {description}"
|
||||
|
||||
def _list_tasks(self):
|
||||
"""列出所有任务"""
|
||||
if not self.tasks:
|
||||
return "📝 当前没有任务"
|
||||
return " 当前没有任务"
|
||||
|
||||
result = f"\n📋 任务列表 (共 {len(self.tasks)} 个任务)\n"
|
||||
result = f"\n 任务列表 (共 {len(self.tasks)} 个任务)\n"
|
||||
result += "=" * 50 + "\n"
|
||||
|
||||
pending_count = 0
|
||||
@@ -193,25 +193,25 @@ JSON格式示例:
|
||||
# 统计
|
||||
if status == 'pending':
|
||||
pending_count += 1
|
||||
status_icon = "⏳"
|
||||
status_icon = ""
|
||||
status_color = Fore.YELLOW
|
||||
elif status == 'in_progress':
|
||||
in_progress_count += 1
|
||||
status_icon = "🔄"
|
||||
status_icon = ""
|
||||
status_color = Fore.CYAN
|
||||
elif status == 'completed':
|
||||
completed_count += 1
|
||||
status_icon = "✅"
|
||||
status_icon = ""
|
||||
status_color = Fore.GREEN
|
||||
else:
|
||||
status_icon = "❌"
|
||||
status_icon = ""
|
||||
status_color = Fore.RED
|
||||
|
||||
result += f"{status_icon} [{task_id}] {status_color}{status}{Style.RESET_ALL} - {description}\n"
|
||||
result += f" 创建时间: {created_at}\n\n"
|
||||
|
||||
result += "=" * 50 + "\n"
|
||||
result += f"📊 统计: {Fore.YELLOW}待处理 {pending_count}{Style.RESET_ALL} | {Fore.CYAN}进行中 {in_progress_count}{Style.RESET_ALL} | {Fore.GREEN}已完成 {completed_count}{Style.RESET_ALL}\n"
|
||||
result += f" 统计: {Fore.YELLOW}待处理 {pending_count}{Style.RESET_ALL} | {Fore.CYAN}进行中 {in_progress_count}{Style.RESET_ALL} | {Fore.GREEN}已完成 {completed_count}{Style.RESET_ALL}\n"
|
||||
|
||||
return result
|
||||
|
||||
@@ -226,9 +226,9 @@ JSON格式示例:
|
||||
error = self._save_tasks()
|
||||
if error:
|
||||
return error
|
||||
return f"✅ 任务 #{task_id} 状态已更新: {old_status} → {new_status}"
|
||||
return f" 任务 #{task_id} 状态已更新: {old_status} → {new_status}"
|
||||
|
||||
return f"❌ 未找到任务ID: {task_id}"
|
||||
return f" 未找到任务ID: {task_id}"
|
||||
|
||||
def _delete_task(self, task_id):
|
||||
"""删除任务"""
|
||||
@@ -238,21 +238,21 @@ JSON格式示例:
|
||||
error = self._save_tasks()
|
||||
if error:
|
||||
return error
|
||||
return f"🗑️ 已删除任务 #{task_id}: {deleted_task.get('task', '')}"
|
||||
return f" 已删除任务 #{task_id}: {deleted_task.get('task', '')}"
|
||||
|
||||
return f"❌ 未找到任务ID: {task_id}"
|
||||
return f" 未找到任务ID: {task_id}"
|
||||
|
||||
def _clear_completed(self):
|
||||
"""清除所有已完成的任务"""
|
||||
completed_count = len([t for t in self.tasks if t.get('status') == 'completed'])
|
||||
if completed_count == 0:
|
||||
return "📝 没有已完成的任务需要清除"
|
||||
return " 没有已完成的任务需要清除"
|
||||
|
||||
self.tasks = [t for t in self.tasks if t.get('status') != 'completed']
|
||||
error = self._save_tasks()
|
||||
if error:
|
||||
return error
|
||||
return f"✅ 已清除 {completed_count} 个已完成的任务"
|
||||
return f" 已清除 {completed_count} 个已完成的任务"
|
||||
|
||||
|
||||
# 测试函数
|
||||
|
||||
@@ -106,7 +106,7 @@ class CodeStatsSkill(Skill):
|
||||
comment_rate = (total_comment / max(total_code, 1)) * 100
|
||||
|
||||
report_lines = [
|
||||
f"📊 代码统计 ({directory})",
|
||||
f" 代码统计 ({directory})",
|
||||
f"{'─' * 40}",
|
||||
f" 文件数: {total_files}",
|
||||
f" 总行数: {total_lines}",
|
||||
@@ -117,7 +117,7 @@ class CodeStatsSkill(Skill):
|
||||
]
|
||||
|
||||
if file_details:
|
||||
report_lines.append(f"\n📄 文件明细 (按代码行数排序):")
|
||||
report_lines.append(f"\n 文件明细 (按代码行数排序):")
|
||||
for fd in file_details[:20]:
|
||||
report_lines.append(
|
||||
f" {fd['file']:40} {fd['code']:5} 行代码"
|
||||
|
||||
@@ -148,7 +148,7 @@ del filename.txt
|
||||
rmdir /s /q directory_name
|
||||
```
|
||||
|
||||
⚠️ **Warning**: Deletion is permanent. Always confirm before deleting.
|
||||
**Warning**: Deletion is permanent. Always confirm before deleting.
|
||||
|
||||
### Copy File or Directory
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ class TestCodeEditor:
|
||||
|
||||
def test_structure(self, editor, tmp_path):
|
||||
result = editor.handle(f"structure {tmp_path} 1")
|
||||
assert "📁" in result
|
||||
assert "" in result
|
||||
|
||||
def test_find(self, editor, tmp_path):
|
||||
(tmp_path / "a.py").write_text("x = 1\ny = 2\n")
|
||||
|
||||
@@ -853,8 +853,8 @@ multi 操作支持一次修改多处:
|
||||
# 无参数:循环切换
|
||||
safety.cycle_mode()
|
||||
mode_name = safety.get_mode_name()
|
||||
icons = {"无限制": "🔴", "普通": "🟢", "人工确认": "🟡"}
|
||||
icon = icons.get(mode_name, "⚪")
|
||||
icons = {"无限制": "", "普通": "", "人工确认": ""}
|
||||
icon = icons.get(mode_name, "")
|
||||
print(f"{icon} 安全模式: {mode_name}")
|
||||
continue
|
||||
|
||||
@@ -880,7 +880,7 @@ multi 操作支持一次修改多处:
|
||||
response_lines = processed_response.split('\n')
|
||||
for i, line in enumerate(response_lines):
|
||||
if i == 0:
|
||||
line = "✦ " + line
|
||||
line = " " + line
|
||||
if i == len(response_lines) - 1:
|
||||
print(f"{line} {user_id_display}")
|
||||
else:
|
||||
|
||||
@@ -56,7 +56,7 @@ class DisplayMixin:
|
||||
else:
|
||||
for i, line in enumerate(response_lines):
|
||||
if i == 0:
|
||||
prefix = f"{Fore.YELLOW}✦{Style.RESET_ALL} " if is_continue else "✦ "
|
||||
prefix = f"{Fore.YELLOW}{Style.RESET_ALL} " if is_continue else " "
|
||||
line = prefix + line
|
||||
if i == len(response_lines) - 1:
|
||||
print(f"{line} {user_id_display}")
|
||||
@@ -207,7 +207,7 @@ class DisplayMixin:
|
||||
|
||||
print(''.join(line))
|
||||
|
||||
print(f"{Fore.GREEN}✓ 已使用色块显示 ({new_width}x{new_height} 像素){Style.RESET_ALL}\n")
|
||||
print(f"{Fore.GREEN} 已使用色块显示 ({new_width}x{new_height} 像素){Style.RESET_ALL}\n")
|
||||
return True
|
||||
|
||||
except ImportError:
|
||||
@@ -223,7 +223,7 @@ class DisplayMixin:
|
||||
subprocess.run(['open', image_path])
|
||||
else:
|
||||
subprocess.run(['xdg-open', image_path])
|
||||
print(f"{Fore.GREEN}✓ 已在系统默认图片查看器中打开{Style.RESET_ALL}\n")
|
||||
print(f"{Fore.GREEN} 已在系统默认图片查看器中打开{Style.RESET_ALL}\n")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -98,7 +98,7 @@ class ToolMixin:
|
||||
safety = get_safety()
|
||||
allowed, msg = safety.check(tool_name, str(args))
|
||||
if not allowed:
|
||||
return {"result": f"🚫 已取消: {msg}"}
|
||||
return {"result": f" 已取消: {msg}"}
|
||||
|
||||
if UNIFIED_TOOL_MANAGER_AVAILABLE and hasattr(self.liugin_manager, 'execute'):
|
||||
if arguments and isinstance(arguments, dict):
|
||||
|
||||
+1
-1
@@ -263,7 +263,7 @@ class SafetyLayer:
|
||||
|
||||
print()
|
||||
print(f" {Fore.RED}{'═' * 50}{Style.RESET_ALL}")
|
||||
print(f" {Fore.RED}⚠️ 安全检查{Style.RESET_ALL}")
|
||||
print(f" {Fore.RED} 安全检查{Style.RESET_ALL}")
|
||||
print(f" {Fore.RED}{'═' * 50}{Style.RESET_ALL}")
|
||||
print(f" {Fore.CYAN}工具:{Style.RESET_ALL} {tool_name}")
|
||||
print(f" {Fore.CYAN}指令:{Style.RESET_ALL} {display_args}")
|
||||
|
||||
+24
-24
@@ -129,7 +129,7 @@ if TEXTUAL_AVAILABLE:
|
||||
class XiaoliTUI(App):
|
||||
"""小狸 TUI v2 - GitHub 暗色主题"""
|
||||
CSS = _TUI_CSS
|
||||
TITLE = "🐱 小狸 Pro-CLI"
|
||||
TITLE = " 小狸 Pro-CLI"
|
||||
SUB_TITLE = "智能编程助手"
|
||||
|
||||
BINDINGS = [
|
||||
@@ -163,13 +163,13 @@ if TEXTUAL_AVAILABLE:
|
||||
id="input-hint"
|
||||
)
|
||||
with Vertical(id="sidebar"):
|
||||
yield Static("⚙️ 引擎", classes="sidebar-header")
|
||||
yield Static(" 引擎", classes="sidebar-header")
|
||||
yield Vertical(id="engine-list", classes="sidebar-section")
|
||||
yield Rule(line_style="heavy")
|
||||
yield Static("🔧 工具", classes="sidebar-header")
|
||||
yield Static(" 工具", classes="sidebar-header")
|
||||
yield Vertical(id="tool-list", classes="sidebar-section")
|
||||
yield Rule(line_style="heavy")
|
||||
yield Static("📊 状态", classes="sidebar-header")
|
||||
yield Static(" 状态", classes="sidebar-header")
|
||||
yield Vertical(id="status-info", classes="sidebar-section")
|
||||
yield Static(" 就绪 | Ctrl+C 退出", id="status-bar")
|
||||
|
||||
@@ -184,14 +184,14 @@ if TEXTUAL_AVAILABLE:
|
||||
("", "msg-dim"),
|
||||
(" ╔══════════════════════════════════════════════╗", "msg-welcome"),
|
||||
(" ║ ║", "msg-welcome"),
|
||||
(" ║ 🐱 小狸 Pro-CLI v5.1 ║", "msg-welcome"),
|
||||
(" ║ 智能编程助手 · 对标 Claude Code ║", "msg-welcome"),
|
||||
(" ║ 小狸 Pro-CLI v5.1 ║", "msg-welcome"),
|
||||
(" ║ 智能编程助手 ║", "msg-welcome"),
|
||||
(" ║ ║", "msg-welcome"),
|
||||
(" ╚══════════════════════════════════════════════╝", "msg-welcome"),
|
||||
("", "msg-dim"),
|
||||
(" 💡 代码编辑 · 代码搜索 · Git 集成 · 多引擎", "msg-system"),
|
||||
(" 📝 输入 /help 查看命令 | /model 切换引擎", "msg-system"),
|
||||
(f" 🔧 当前引擎: {self.bridge.current_engine()} | 工具: {len(self.bridge.tools())} 个", "msg-system"),
|
||||
(" 代码编辑 · 代码搜索 · Git 集成 · 多引擎", "msg-system"),
|
||||
(" 输入 /help 查看命令 | /model 切换引擎", "msg-system"),
|
||||
(f" 当前引擎: {self.bridge.current_engine()} | 工具: {len(self.bridge.tools())} 个", "msg-system"),
|
||||
("", "msg-dim"),
|
||||
]
|
||||
for text, cls in lines:
|
||||
@@ -233,13 +233,13 @@ if TEXTUAL_AVAILABLE:
|
||||
self._append(Static(text, classes=cls))
|
||||
|
||||
def _user_msg(self, text):
|
||||
self._add(f" 👤 {text}", "msg-user")
|
||||
self._add(f" {text}", "msg-user")
|
||||
|
||||
def _ai_msg(self, text):
|
||||
if "```" in text:
|
||||
self._render_with_code(text)
|
||||
else:
|
||||
self._add(f" ✦ {text}", "msg-ai")
|
||||
self._add(f" {text}", "msg-ai")
|
||||
|
||||
def _render_with_code(self, text):
|
||||
parts = re.split(r'```(\w*)\n(.*?)```', text, flags=re.DOTALL)
|
||||
@@ -248,7 +248,7 @@ if TEXTUAL_AVAILABLE:
|
||||
if i % 3 == 0:
|
||||
if parts[i].strip():
|
||||
for line in parts[i].strip().split('\n'):
|
||||
self._add(f" ✦ {line}", "msg-ai")
|
||||
self._add(f" {line}", "msg-ai")
|
||||
elif i % 3 == 2:
|
||||
code = parts[i]
|
||||
lang = parts[i-1] if i > 1 else ""
|
||||
@@ -263,19 +263,19 @@ if TEXTUAL_AVAILABLE:
|
||||
i += 1
|
||||
|
||||
def _tool_ok(self, name, args):
|
||||
self._add(f" ✅ {name}: {args[:60]}", "msg-tool-ok")
|
||||
self._add(f" {name}: {args[:60]}", "msg-tool-ok")
|
||||
|
||||
def _tool_err(self, name, args):
|
||||
self._add(f" ❌ {name}: {args[:60]}", "msg-tool-err")
|
||||
self._add(f" {name}: {args[:60]}", "msg-tool-err")
|
||||
|
||||
def _thinking(self):
|
||||
self._add(" 💭 思考中...", "msg-thinking")
|
||||
self._add(" 思考中...", "msg-thinking")
|
||||
|
||||
def _system(self, text):
|
||||
self._add(f" ℹ️ {text}", "msg-system")
|
||||
self._add(f" ℹ {text}", "msg-system")
|
||||
|
||||
def _error(self, text):
|
||||
self._add(f" ⚠️ {text}", "msg-error")
|
||||
self._add(f" {text}", "msg-error")
|
||||
|
||||
@on(Input.Submitted, "#user-input")
|
||||
def on_input(self, event):
|
||||
@@ -291,7 +291,7 @@ if TEXTUAL_AVAILABLE:
|
||||
self._user_msg(text)
|
||||
self._thinking()
|
||||
self.is_generating = True
|
||||
self._update_status("🔄 思考中...")
|
||||
self._update_status(" 思考中...")
|
||||
self.run_worker(self._generate(text), exclusive=True)
|
||||
|
||||
def _handle_command(self, cmd):
|
||||
@@ -309,7 +309,7 @@ if TEXTUAL_AVAILABLE:
|
||||
'cls': lambda: self.action_clear(),
|
||||
'model': lambda: self._switch_model(args),
|
||||
'engine': lambda: self._switch_model(args),
|
||||
'about': lambda: self._system("🐱 小狸 Pro-CLI v5.1 - 智能编程助手"),
|
||||
'about': lambda: self._system(" 小狸 Pro-CLI v5.1 - 智能编程助手"),
|
||||
'status': lambda: self._show_status(),
|
||||
'tools': lambda: self._show_tools(),
|
||||
'engines': lambda: self._show_engines(),
|
||||
@@ -331,7 +331,7 @@ if TEXTUAL_AVAILABLE:
|
||||
self._error(f"未知命令: /{name},输入 /help 查看帮助")
|
||||
|
||||
def _show_help(self):
|
||||
help_text = """ 📖 命令:
|
||||
help_text = """ 命令:
|
||||
/help 帮助信息
|
||||
/quit 退出
|
||||
/cli 切换命令行模式
|
||||
@@ -341,7 +341,7 @@ if TEXTUAL_AVAILABLE:
|
||||
/status 系统状态
|
||||
/clear 清屏
|
||||
|
||||
⌨️ 快捷键:
|
||||
⌨ 快捷键:
|
||||
Ctrl+C 退出 Ctrl+L 清屏
|
||||
Ctrl+N 新对话 F1 侧栏
|
||||
Escape 取消生成"""
|
||||
@@ -425,11 +425,11 @@ if TEXTUAL_AVAILABLE:
|
||||
pass
|
||||
|
||||
def _write_raw(self, msg):
|
||||
if '✅' in msg or 'OK 工具' in msg:
|
||||
if '' in msg or 'OK 工具' in msg:
|
||||
self._add(f" {msg}", "msg-tool-ok")
|
||||
elif '❌' in msg or 'X 工具' in msg or '错误' in msg:
|
||||
elif '' in msg or 'X 工具' in msg or '错误' in msg:
|
||||
self._add(f" {msg}", "msg-tool-err")
|
||||
elif '✦' in msg:
|
||||
elif '' in msg:
|
||||
self._add(f" {msg}", "msg-ai")
|
||||
elif '工具' in msg and ('调用' in msg or '执行' in msg):
|
||||
self._add(f" {msg}", "msg-tool-ok")
|
||||
|
||||
Reference in new issue
Block a user