diff --git a/packages/chrome-qwen-bridge/.extension-id b/packages/chrome-qwen-bridge/.extension-id new file mode 100644 index 00000000..73039ed9 --- /dev/null +++ b/packages/chrome-qwen-bridge/.extension-id @@ -0,0 +1 @@ +cimaabkejokbhjkdnajgfniiolfjgbhd diff --git a/packages/chrome-qwen-bridge/.gitignore b/packages/chrome-qwen-bridge/.gitignore new file mode 100644 index 00000000..ed26baf9 --- /dev/null +++ b/packages/chrome-qwen-bridge/.gitignore @@ -0,0 +1,23 @@ +# Dependencies +node_modules/ + +# Build outputs +dist/ +*.zip + +# Logs +*.log + +# OS files +.DS_Store +Thumbs.db + +# IDE files +.idea/ +.vscode/ +*.swp +*.swo + +# Temporary files +*.tmp +.temp/ \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/CONNECTION_STATUS.md b/packages/chrome-qwen-bridge/CONNECTION_STATUS.md new file mode 100644 index 00000000..b6ab8331 --- /dev/null +++ b/packages/chrome-qwen-bridge/CONNECTION_STATUS.md @@ -0,0 +1,62 @@ +#!/bin/bash + +echo "🎯 Chrome Extension 连接状态总结" +echo "================================" +echo "" + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo -e "${GREEN}✅ Native Host 已正确配眮${NC}" +echo " - 配眮文件䜍眮正确" +echo " - 䜿甚 shell 包装脚本确保 Node.js 环境" +echo " - 扩展 ID 已配眮: cimaabkejokbhjkdnajgfniiolfjgbhd" +echo "" + +echo -e "${GREEN}✅ Native Host 测试响应正垞${NC}" +echo " - 握手协议工䜜正垞" +echo " - 消息䌠递机制正确" +echo "" + +echo -e "${GREEN}✅ Service Worker 已增区${NC}" +echo " - 添加了诊细的错误日志" +echo " - 实现了握手超时机制" +echo " - 改进了断匀连接倄理" +echo "" + +echo -e "${YELLOW}📝 现圚请进行以䞋操䜜${NC}" +echo "" +echo "1. 重新加蜜 Chrome 扩展" +echo " open 'chrome://extensions/'" +echo " 扟到 'Qwen CLI Bridge' 并点击 🔄" +echo "" +echo "2. 点击扩展囟标测试" +echo " - 点击 'Connect to Qwen CLI'" +echo " - 连接应该䌚成功" +echo "" +echo "3. 劂果仍有问题" +echo " a) 查看 Service Worker 控制台" +echo " open 'chrome://extensions/?id=cimaabkejokbhjkdnajgfniiolfjgbhd'" +echo " 点击 'Service Worker' 查看日志" +echo "" +echo " b) 查看 Native Host 日志" +echo " tail -f /tmp/qwen-bridge-host.log" +echo "" +echo " c) 运行调试控制台" +echo " open file://$PWD/debug-console.html" +echo "" + +echo "================================" +echo "" +echo "🔍 垞见问题排查" +echo "" +echo "劂果看到 'Native host has exited' 错误" +echo "- 确保 Node.js 已安装: node --version" +echo "- 检查路埄是吊正确: ls -la native-host/run.sh" +echo "" +echo "劂果看到 'Specified native messaging host not found'" +echo "- 重新运行: ./set-extension-id.sh" +echo "- 确讀扩展 ID 正确" +echo "" +echo "连接现圚应该胜正垞工䜜了🎉" \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/INSTALL.md b/packages/chrome-qwen-bridge/INSTALL.md new file mode 100644 index 00000000..0a817d1c --- /dev/null +++ b/packages/chrome-qwen-bridge/INSTALL.md @@ -0,0 +1,121 @@ +# 📊 Chrome Qwen Bridge - 安装指南 + +## 🚀 快速安装掚荐 + +### 䞀键安装銖次甚户 + +```bash +# 进入项目目圕 +cd packages/chrome-qwen-bridge + +# 运行安装向富 +npm run install:all +``` + +这䞪呜什䌚 +1. ✅ 匕富䜠安装 Chrome 扩展 +2. ✅ 自劚配眮 Native Host +3. ✅ 保存扩展 ID 䟛后续䜿甚 +4. ✅ 启劚调试环境 + +## 📝 安装方匏诎明 + +### 场景 1从 Chrome Web Store 安装未来 + +圓扩展发垃到 Chrome Web Store 后 +1. 从商店安装扩展 +2. 运行 `npm run install:host`䌚自劚检测已安装的扩展 +3. 完成 + +### 场景 2匀发者暡匏安装圓前 + +```bash +# 步骀 1安装扩展和 Native Host +npm run install:all + +# 步骀 2启劚调试 +npm run dev +``` + +### 场景 3分步安装 + +```bash +# 1. 仅安装 Chrome 扩展 +npm run install:extension + +# 2. 仅配眮 Native Host +npm run install:host + +# 3. 启劚匀发环境 +npm run dev +``` + +## 🔧 Native Host 诎明 + +### 什么是 Native Host + +Native Host 是䞀䞪本地皋序允讞 Chrome 扩展䞎本地应甚劂 Qwen CLI通信。出于安党考虑Chrome 芁求必须手劚安装。 + +### 智胜安装噚特性 + +我们的 `smart-install.sh` 脚本䌚 + +1. **自劚检测** - 尝试自劚扟到已安装的扩展 +2. **保存配眮** - 记䜏扩展 ID䞋次无需蟓入 +3. **通甚暡匏** - 即䜿没有扩展 ID 也胜配眮 +4. **连接测试** - 可选的连接验证 + +### 安装䜍眮 + +Native Host 配眮文件䜍眮 +- **macOS**: `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/` +- **Linux**: `~/.config/google-chrome/NativeMessagingHosts/` + +## ❓ 垞见问题 + +### Q: 必须手劚安装 Native Host 吗 + +A: 是的这是 Chrome 的安党芁求。䜆我们的智胜安装噚让这䞪过皋非垞简单。 + +### Q: 劂䜕扟到扩展 ID + +A: +1. 打匀 `chrome://extensions/` +2. 扟到 "Qwen CLI Bridge" +3. ID 星瀺圚扩展卡片䞊类䌌 `abcdefghijklmnop...` + +### Q: 重装扩展后需芁重新配眮吗 + +A: 劂果扩展 ID 改变了需芁重新运行 `npm run install:host`。脚本䌚自劚检测新的 ID。 + +### Q: 劂䜕验证安装成功 + +A: 运行 `npm run dev`劂果胜看到插件囟标并胜点击连接诎明安装成功。 + +## 📋 呜什参考 + +| 呜什 | 诎明 | +|------|------| +| `npm run install:all` | 完敎安装向富 | +| `npm run install:extension` | 仅安装扩展 | +| `npm run install:host` | 仅配眮 Native Host | +| `npm run dev` | 启劚调试环境 | +| `npm run clean` | 枅理所有配眮和日志 | + +## 🔄 曎新和重装 + +劂果需芁重新安装 + +```bash +# 枅理旧配眮 +npm run clean + +# 重新安装 +npm run install:all +``` + +## 📚 曎倚信息 + +- [调试指南](./docs/debugging.md) +- [API 文档](./docs/api-reference.md) +- [架构讟计](./docs/architecture.md) \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/QUICK_START.md b/packages/chrome-qwen-bridge/QUICK_START.md new file mode 100644 index 00000000..1b6e43b4 --- /dev/null +++ b/packages/chrome-qwen-bridge/QUICK_START.md @@ -0,0 +1,64 @@ +# 🚀 快速匀始 + +## 銖次䜿甚 + +劂果是第䞀次䜿甚请运行 + +```bash +npm run dev +``` + +系统䌚自劚检测并匕富䜠完成 +1. 📊 手劚安装 Chrome 插件 +2. 🔧 配眮 Native Host +3. 🎯 启劚调试环境 + +## 安装步骀诎明 + +### 第䞀次运行时需芁 + +1. **手劚加蜜插件到 Chrome** + - 打匀 `chrome://extensions/` + - 匀启「匀发者暡匏」右䞊角 + - 点击「加蜜已解压的扩展皋序」 + - 选择 `extension` 目圕 + - **记䞋扩展 ID**埈重芁 + +2. **蟓入扩展 ID** + - 脚本䌚提瀺䜠蟓入 + - 这样 Native Host 才胜识别插件 + +3. **完成后** + - 以后运行 `npm run dev` 就䌚自劚加蜜所有内容 + +## 垞见问题 + +### Q: 䞺什么需芁手劚加蜜插件 +A: Chrome 安党机制芁求匀发者暡匏的插件必须手劚加蜜䞀次。 + +### Q: 插件囟标圚哪里 +A: 点击 Chrome 工具栏的拌囟囟标扟到 "Qwen CLI Bridge" 并点击固定。 + +### Q: 劂䜕知道插件是吊加蜜成功 +A: +- 圚 `chrome://extensions/` 胜看到插件 +- 工具栏有插件囟标 +- 点击囟标胜看到匹出窗口 + +## 调试呜什 + +```bash +npm run dev # 启劚调试环境銖次䌚匕富安装 +npm run logs # 查看 Native Host 日志 +npm run logs:qwen # 查看 Qwen 服务噚日志 +npm run clean # 枅理所有䞎时文件 +``` + +## 文件诎明 + +``` +├── first-install.sh # 銖次安装向富 +├── debug.sh # 调试启劚脚本 +├── .extension-id # 保存的扩展 ID自劚生成 +└── extension/ # Chrome 插件源码 +``` \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/README.md b/packages/chrome-qwen-bridge/README.md new file mode 100644 index 00000000..1ccaf4ea --- /dev/null +++ b/packages/chrome-qwen-bridge/README.md @@ -0,0 +1,204 @@ +# Qwen CLI Bridge - Chrome Extension + +A Chrome extension that bridges your browser with Qwen CLI, enabling AI-powered analysis and interaction with web content. + +> This package is part of the [Qwen Code](https://github.com/QwenLM/qwen-code) mono repository. + +## Features + +- **Page Data Extraction**: Extract structured data from any webpage including text, links, images, and metadata +- **Screenshot Capture**: Capture and analyze screenshots with AI +- **Console & Network Monitoring**: Monitor console logs and network requests +- **Selected Text Processing**: Send selected text to Qwen CLI for processing +- **AI Analysis**: Leverage Qwen's AI capabilities to analyze web content +- **MCP Server Integration**: Support for multiple MCP (Model Context Protocol) servers + +## Architecture + +``` +┌─────────────────────┐ +│ Chrome Extension │ +│ - Content Script │ +│ - Background Worker│ +│ - Popup UI │ +└──────────┬──────────┘ + │ + Native Messaging + │ + ┌──────▌──────────┐ + │ Native Host │ + │ (Node.js) │ + └──────┬──────────┘ + │ + ┌──────▌──────────┐ + │ Qwen CLI │ + │ + MCP Servers │ + └─────────────────┘ +``` + +## Installation + +### Prerequisites + +1. **Node.js**: Install from [nodejs.org](https://nodejs.org/) +2. **Qwen CLI**: Install the Qwen CLI tool (required for full functionality) +3. **Chrome Browser**: Version 88 or higher + +### Step 1: Install the Chrome Extension + +1. Open Chrome and navigate to `chrome://extensions/` +2. Enable "Developer mode" (toggle in top right) +3. Click "Load unpacked" +4. Select the `chrome-qwen-bridge/extension` folder +5. Note the Extension ID that appears (you'll need this for the next step) + +### Step 2: Install the Native Messaging Host + +The Native Messaging Host allows the Chrome extension to communicate with Qwen CLI. + +#### macOS/Linux + +```bash +cd chrome-qwen-bridge/native-host +./install.sh +``` + +When prompted, enter your Chrome Extension ID. + +#### Windows + +1. Run Command Prompt as Administrator +2. Navigate to the `native-host` directory: + ```cmd + cd chrome-qwen-bridge\native-host + ``` +3. Run the installation script: + ```cmd + install.bat + ``` +4. Enter your Chrome Extension ID when prompted + +### Step 3: Configure Qwen CLI (Optional) + +If you want to use MCP servers with the extension: + +```bash +# Add chrome-devtools MCP server +qwen mcp add chrome-devtools + +# Add other MCP servers as needed +qwen mcp add playwright-mcp +``` + +## Usage + +### Basic Usage + +1. Click the Qwen CLI Bridge extension icon in Chrome +2. Click "Connect to Qwen CLI" to establish connection +3. Click "Start Qwen CLI" to launch the CLI process +4. Use the action buttons to: + - Extract and analyze page data + - Capture screenshots + - Send selected text to Qwen + - Monitor console and network logs + +### Advanced Settings + +In the popup's "Advanced Settings" section, you can configure: + +- **MCP Servers**: Comma-separated list of MCP servers to load +- **HTTP Port**: Port for Qwen CLI HTTP server (default: 8080) +- **Auto-connect**: Automatically connect when opening the popup + +### API Actions + +The extension supports the following actions that can be sent to Qwen CLI: + +- `analyze_page`: Analyze extracted page data +- `analyze_screenshot`: Analyze captured screenshot +- `ai_analyze`: Perform AI analysis on content +- `process_text`: Process selected text +- Custom actions based on your MCP server configurations + +## Development + +### Project Structure + +``` +chrome-qwen-bridge/ +├── extension/ # Chrome extension source +│ ├── manifest.json # Extension manifest +│ ├── background/ # Service worker +│ ├── content/ # Content scripts +│ ├── popup/ # Popup UI +│ └── icons/ # Extension icons +├── native-host/ # Native messaging host +│ ├── host.js # Node.js host script +│ ├── manifest.json # Native host manifest +│ └── install scripts # Platform-specific installers +└── docs/ # Documentation +``` + +### Building from Source + +1. Clone the repository +2. No build step required - the extension uses vanilla JavaScript +3. Load the extension as unpacked in Chrome for development + +### Testing + +1. Enable Chrome Developer Tools +2. Check the extension's background page console for logs +3. Native host logs are written to: + - macOS/Linux: `/tmp/qwen-bridge-host.log` + - Windows: `%TEMP%\qwen-bridge-host.log` + +## Troubleshooting + +### Extension not connecting to Native Host + +1. Verify Node.js is installed: `node --version` +2. Check that the Native Host is properly installed +3. Ensure the Extension ID in the manifest matches your actual extension +4. Check logs for errors + +### Qwen CLI not starting + +1. Verify Qwen CLI is installed: `qwen --version` +2. Check that Qwen CLI can run normally from terminal +3. Review Native Host logs for error messages + +### No response from Qwen CLI + +1. Ensure Qwen CLI server is running +2. Check the configured HTTP port is not in use +3. Verify MCP servers are properly configured + +## Security Considerations + +- The extension requires broad permissions to function properly +- Native Messaging Host runs with user privileges +- All communication between components uses structured JSON messages +- No sensitive data is stored; all processing is ephemeral + +## Contributing + +Contributions are welcome! Please: + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Test thoroughly +5. Submit a pull request + +## License + +MIT License - See LICENSE file for details + +## Support + +For issues, questions, or feature requests: +- Open an issue on GitHub +- Check the logs for debugging information +- Ensure all prerequisites are properly installed \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/build.sh b/packages/chrome-qwen-bridge/build.sh new file mode 100755 index 00000000..8e2633a0 --- /dev/null +++ b/packages/chrome-qwen-bridge/build.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Build script for Chrome extension package + +echo "Building Chrome Qwen Bridge..." + +# Ensure we're in the right directory +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$SCRIPT_DIR" + +# Create dist directory +mkdir -p dist + +# Copy extension files to dist +echo "Copying extension files..." +cp -r extension dist/ + +# Create a zip file for Chrome Web Store +echo "Creating extension package..." +cd dist +zip -r ../chrome-qwen-bridge.zip extension/ +cd .. + +echo "✅ Build complete!" +echo " Extension package: chrome-qwen-bridge.zip" +echo " Extension files: dist/extension/" \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/debug-chrome.sh b/packages/chrome-qwen-bridge/debug-chrome.sh new file mode 100755 index 00000000..58d0246b --- /dev/null +++ b/packages/chrome-qwen-bridge/debug-chrome.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +echo "🔍 Chrome Extension 调试启劚噚" +echo "================================" +echo "" + +# 检查 Chrome 是吊已经运行 +if pgrep -x "Google Chrome" > /dev/null; then + echo "⚠ Chrome 已圚运行请先关闭 Chrome 再运行歀脚本" + echo " 或者圚新的 Chrome 窗口䞭手劚操䜜" + echo "" +fi + +# 获取扩展路埄 +EXTENSION_PATH="$PWD/extension" +echo "📂 扩展路埄: $EXTENSION_PATH" + +# 读取保存的扩展 ID +if [ -f ".extension-id" ]; then + EXTENSION_ID=$(cat .extension-id) + echo "🆔 扩展 ID: $EXTENSION_ID" +else + echo "⚠ 未扟到扩展 ID銖次加蜜后䌚自劚保存" +fi + +echo "" +echo "正圚启劚 Chrome 调试暡匏..." +echo "" + +# 启劚 Chrome with debugging +open -na "Google Chrome" --args \ + --load-extension="$EXTENSION_PATH" \ + --auto-open-devtools-for-tabs \ + --enable-logging \ + --v=1 \ + "file://$PWD/debug-console.html" + +echo "✅ Chrome 已启劚" +echo "" +echo "📝 调试步骀" +echo "1. Chrome 䌚自劚加蜜扩展并打匀调试控制台" +echo "2. 点击 'Test Connection' 测试连接" +echo "3. 劂果连接倱莥点击 'View Background Logs' 查看诊细日志" +echo "" +echo "💡 提瀺" +echo "- 按 F12 打匀匀发者工具查看控制台蟓出" +echo "- 圚 chrome://extensions/ 页面点击 'Service Worker' 查看后台日志" +echo "- 日志文件: /tmp/qwen-bridge-host.log" +echo "" +echo "📋 监控日志 (Ctrl+C 退出):" +echo "----------------------------" +tail -f /tmp/qwen-bridge-host.log 2>/dev/null || echo "等埅日志生成..." \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/debug-console.html b/packages/chrome-qwen-bridge/debug-console.html new file mode 100644 index 00000000..ba02fb17 --- /dev/null +++ b/packages/chrome-qwen-bridge/debug-console.html @@ -0,0 +1,178 @@ + + + + Chrome Extension Debug Console + + + +

🔧 Chrome Extension Debug Console

+ +
+ + + + + +
+ +
+ + + + \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/debug.sh b/packages/chrome-qwen-bridge/debug.sh new file mode 100755 index 00000000..bf890107 --- /dev/null +++ b/packages/chrome-qwen-bridge/debug.sh @@ -0,0 +1,376 @@ +#!/bin/bash + +# Qwen CLI Bridge - macOS 䞀键调试脚本 + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +# 获取脚本目圕 +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# 检查是吊銖次安装 +if [[ ! -f "$SCRIPT_DIR/.extension-id" ]]; then + echo -e "${YELLOW}╔════════════════════════════════════════════════════════════════╗${NC}" + echo -e "${YELLOW}║ ║${NC}" + echo -e "${YELLOW}║ ⚠ 检测到銖次运行需芁先安装插件 ║${NC}" + echo -e "${YELLOW}║ ║${NC}" + echo -e "${YELLOW}╚════════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo -e "${CYAN}即将启劚銖次安装向富...${NC}" + sleep 2 + exec "$SCRIPT_DIR/first-install.sh" + exit 0 +fi + +# 枅屏星瀺标题 +clear +echo -e "${CYAN}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${CYAN}║ ║${NC}" +echo -e "${CYAN}║ 🚀 Qwen CLI Bridge - macOS 调试环境 ║${NC}" +echo -e "${CYAN}║ ║${NC}" +echo -e "${CYAN}╚════════════════════════════════════════════════════════════════╝${NC}" +echo "" + +# 第䞀步检查环境 +echo -e "${BLUE}[1/5]${NC} 检查匀发环境..." + +# 检查 Node.js +if ! command -v node &> /dev/null; then + echo -e "${RED}✗${NC} Node.js 未安装请先安装 Node.js" + echo " 访问 https://nodejs.org 䞋蜜安装" + exit 1 +fi +echo -e "${GREEN}✓${NC} Node.js $(node --version)" + +# 检查 Chrome +CHROME_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" +if [[ ! -f "$CHROME_PATH" ]]; then + echo -e "${RED}✗${NC} Chrome 未扟到" + exit 1 +fi +echo -e "${GREEN}✓${NC} Chrome 已安装" + +# 第二步配眮 Native Host +echo -e "\n${BLUE}[2/5]${NC} 配眮 Native Host..." + +MANIFEST_DIR="$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts" +mkdir -p "$MANIFEST_DIR" + +cat > "$MANIFEST_DIR/com.qwen.cli.bridge.json" << EOF +{ + "name": "com.qwen.cli.bridge", + "description": "Native messaging host for Qwen CLI Bridge", + "path": "$SCRIPT_DIR/native-host/host.js", + "type": "stdio", + "allowed_origins": ["chrome-extension://*/"] +} +EOF + +echo -e "${GREEN}✓${NC} Native Host 已配眮" + +# 第䞉步检查 Qwen CLI可选 +echo -e "\n${BLUE}[3/5]${NC} 检查 Qwen CLI..." + +QWEN_AVAILABLE=false +if command -v qwen &> /dev/null; then + QWEN_AVAILABLE=true + echo -e "${GREEN}✓${NC} Qwen CLI $(qwen --version 2>/dev/null || echo "已安装")" + + # 尝试启劚 Qwen server + if ! lsof -i:8080 &> /dev/null; then + echo -e "${CYAN}→${NC} 启劚 Qwen server (端口 8080)..." + qwen server --port 8080 > /tmp/qwen-server.log 2>&1 & + QWEN_PID=$! + sleep 2 + + if kill -0 $QWEN_PID 2>/dev/null; then + echo -e "${GREEN}✓${NC} Qwen server 已启劚 (PID: $QWEN_PID)" + else + echo -e "${YELLOW}!${NC} Qwen server 启劚倱莥继续运行..." + QWEN_AVAILABLE=false + fi + else + echo -e "${YELLOW}!${NC} 端口 8080 已被占甚" + fi +else + echo -e "${YELLOW}!${NC} Qwen CLI 未安装插件基础功胜仍可䜿甚" +fi + +# 第四步启劚测试页面 +echo -e "\n${BLUE}[4/5]${NC} 启劚测试服务噚..." + +# 创建测试页面 +cat > /tmp/qwen-test.html << 'HTML' + + + + + Qwen CLI Bridge 测试页面 + + + +
+
+

🚀 Qwen CLI Bridge

+
调试环境已就绪
+
+ +
+
+

📝 测试功胜

+ + + + +
+
+ +
+

📄 瀺䟋内容

+

这是䞀段可以被插件提取的瀺䟋文本。䜠可以选择这段文字然后䜿甚插件的"Send Selected Text"功胜。

+
    +
  • 列衚项 1Lorem ipsum dolor sit amet
  • +
  • 列衚项 2Consectetur adipiscing elit
  • +
  • 列衚项 3Sed do eiusmod tempor incididunt
  • +
+
+ "这是䞀䞪匕甚块可以测试 Markdown 蜬换功胜。" +
+
+ +
+

🎯 䜿甚诎明

+
    +
  1. 点击 Chrome 工具栏䞭的插件囟标
  2. +
  3. 点击 "Connect to Qwen CLI" 建立连接
  4. +
  5. 劂果安装了 Qwen CLI点击 "Start Qwen CLI"
  6. +
  7. 䜿甚各种功胜按钮测试插件功胜
  8. +
  9. 按 F12 打匀 DevTools 查看诊细日志
  10. +
+
+
+
+ + + + +HTML + +# 启劚 Python HTTP 服务噚 +cd /tmp +python3 -m http.server 3000 > /tmp/test-server.log 2>&1 & +TEST_PID=$! +sleep 1 + +echo -e "${GREEN}✓${NC} 测试服务噚已启劚 (http://localhost:3000)" + +# 第五步启劚 Chrome +echo -e "\n${BLUE}[5/5]${NC} 启劚 Chrome 并加蜜插件..." + +"$CHROME_PATH" \ + --load-extension="$SCRIPT_DIR/extension" \ + --auto-open-devtools-for-tabs \ + --no-first-run \ + --no-default-browser-check \ + "http://localhost:3000/qwen-test.html" & + +CHROME_PID=$! + +echo -e "${GREEN}✓${NC} Chrome 已启劚" + +# 星瀺最终状态 +echo "" +echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${GREEN}║ ║${NC}" +echo -e "${GREEN}║ ✅ 调试环境启劚成功 ║${NC}" +echo -e "${GREEN}║ ║${NC}" +echo -e "${GREEN}╚════════════════════════════════════════════════════════════════╝${NC}" +echo "" +echo -e "${CYAN}📍 服务状态${NC}" +echo -e " • Chrome: 运行䞭" +echo -e " • 测试页面: ${BLUE}http://localhost:3000/qwen-test.html${NC}" +echo -e " • 插件: 已加蜜到工具栏" + +if [ "$QWEN_AVAILABLE" = true ]; then + echo -e " • Qwen Server: ${BLUE}http://localhost:8080${NC}" +fi + +echo "" +echo -e "${CYAN}🔍 调试䜍眮${NC}" +echo -e " • 插件日志: Chrome DevTools Console" +echo -e " • 后台脚本: chrome://extensions → Service Worker" +echo -e " • Native Host: /tmp/qwen-bridge-host.log" + +if [ "$QWEN_AVAILABLE" = true ]; then + echo -e " • Qwen 日志: /tmp/qwen-server.log" +fi + +echo "" +echo -e "${YELLOW}按 Ctrl+C 停止所有服务${NC}" +echo "" + +# 枅理凜数 +cleanup() { + echo -e "\n${YELLOW}正圚停止服务...${NC}" + + # 停止进皋 + [ ! -z "$TEST_PID" ] && kill $TEST_PID 2>/dev/null + [ ! -z "$QWEN_PID" ] && kill $QWEN_PID 2>/dev/null + + echo -e "${GREEN}✓${NC} 已停止所有服务" + exit 0 +} + +# 捕获䞭断信号 +trap cleanup INT TERM + +# 保持运行 +while true; do + sleep 1 +done \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/dev.js b/packages/chrome-qwen-bridge/dev.js new file mode 100644 index 00000000..fdfcc9d9 --- /dev/null +++ b/packages/chrome-qwen-bridge/dev.js @@ -0,0 +1,511 @@ +#!/usr/bin/env node + +/** + * 匀发环境䞀键启劚脚本 + * 自劚完成所有配眮和启劚步骀 + */ + +const { spawn, exec } = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); +const readline = require('readline'); + +// 颜色蟓出 +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + red: '\x1b[31m', + cyan: '\x1b[36m' +}; + +function log(message, color = '') { + console.log(`${color}${message}${colors.reset}`); +} + +function logStep(step, message) { + log(`\n[${step}] ${message}`, colors.bright + colors.blue); +} + +function logSuccess(message) { + log(`✅ ${message}`, colors.green); +} + +function logWarning(message) { + log(`⚠ ${message}`, colors.yellow); +} + +function logError(message) { + log(`❌ ${message}`, colors.red); +} + +function logInfo(message) { + log(`ℹ ${message}`, colors.cyan); +} + +// 检查呜什是吊存圚 +function commandExists(command) { + return new Promise((resolve) => { + exec(`command -v ${command}`, (error) => { + resolve(!error); + }); + }); +} + +// 获取 Chrome 路埄 +function getChromePath() { + const platform = process.platform; + + const chromePaths = { + darwin: [ + '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + '/Applications/Chromium.app/Contents/MacOS/Chromium' + ], + win32: [ + 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', + 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', + process.env.LOCALAPPDATA + '\\Google\\Chrome\\Application\\chrome.exe' + ], + linux: [ + '/usr/bin/google-chrome', + '/usr/bin/chromium-browser', + '/usr/bin/chromium' + ] + }; + + const paths = chromePaths[platform] || []; + + for (const chromePath of paths) { + if (fs.existsSync(chromePath)) { + return chromePath; + } + } + + return null; +} + +// 获取扩展 ID +function getExtensionId(extensionPath) { + // 这是䞀䞪简化的方法实际的 Extension ID 是通过 Chrome 生成的 + // 匀发时可以固定䜿甚䞀䞪 ID + return 'development-extension-id'; +} + +// 安装 Native Host +async function installNativeHost(extensionPath) { + logStep(2, 'Installing Native Host...'); + + const hostPath = path.join(extensionPath, 'native-host'); + const scriptPath = path.join(hostPath, 'host.js'); + + if (!fs.existsSync(scriptPath)) { + logError('Native host script not found!'); + return false; + } + + const platform = process.platform; + const hostName = 'com.qwen.cli.bridge'; + + let manifestDir; + if (platform === 'darwin') { + manifestDir = path.join(os.homedir(), 'Library/Application Support/Google/Chrome/NativeMessagingHosts'); + } else if (platform === 'linux') { + manifestDir = path.join(os.homedir(), '.config/google-chrome/NativeMessagingHosts'); + } else if (platform === 'win32') { + // Windows 需芁写泚册衚 + logWarning('Windows requires registry modification. Please run install.bat manually.'); + return true; + } else { + logError('Unsupported platform'); + return false; + } + + // 创建目圕 + if (!fs.existsSync(manifestDir)) { + fs.mkdirSync(manifestDir, { recursive: true }); + } + + // 创建 manifest 文件 + const manifest = { + name: hostName, + description: 'Native messaging host for Qwen CLI Bridge', + path: scriptPath, + type: 'stdio', + allowed_origins: [ + 'chrome-extension://jniepomhbdkeifkadbfolbcihcmfpfjo/', // 匀发甚 ID + 'chrome-extension://*/' // 允讞任䜕扩展仅匀发环境 + ] + }; + + const manifestPath = path.join(manifestDir, `${hostName}.json`); + fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)); + + logSuccess(`Native Host installed at: ${manifestPath}`); + return true; +} + +// 检查 Qwen CLI +async function checkQwenCli() { + logStep(3, 'Checking Qwen CLI...'); + + const qwenExists = await commandExists('qwen'); + + if (qwenExists) { + logSuccess('Qwen CLI is installed'); + + // 获取版本 + return new Promise((resolve) => { + exec('qwen --version', (error, stdout) => { + if (!error && stdout) { + logInfo(`Version: ${stdout.trim()}`); + } + resolve(true); + }); + }); + } else { + logWarning('Qwen CLI is not installed'); + logInfo('You can still use the extension, but some features will be limited'); + return false; + } +} + +// 启劚 Qwen CLI 服务噚 +function startQwenServer(port = 8080) { + logStep(4, 'Starting Qwen CLI Server...'); + + return new Promise((resolve) => { + // 检查端口是吊被占甚 + exec(`lsof -i:${port} || netstat -an | grep ${port}`, (error, stdout) => { + if (stdout && stdout.length > 0) { + logWarning(`Port ${port} is already in use`); + logInfo('Qwen server might already be running'); + resolve(null); + return; + } + + // 启劚服务噚 + const qwenProcess = spawn('qwen', ['server', '--port', String(port)], { + detached: false, + stdio: ['ignore', 'pipe', 'pipe'] + }); + + qwenProcess.stdout.on('data', (data) => { + const output = data.toString(); + if (output.includes('Server started') || output.includes('listening')) { + logSuccess(`Qwen server started on port ${port}`); + resolve(qwenProcess); + } + }); + + qwenProcess.stderr.on('data', (data) => { + logError(`Qwen server error: ${data}`); + }); + + qwenProcess.on('error', (error) => { + logError(`Failed to start Qwen server: ${error.message}`); + resolve(null); + }); + + // 超时倄理 + setTimeout(() => { + logWarning('Qwen server start timeout, continuing anyway...'); + resolve(qwenProcess); + }, 5000); + }); + }); +} + +// 启劚 Chrome 匀发暡匏 +function startChrome(extensionPath, chromePath) { + logStep(5, 'Starting Chrome with extension...'); + + const args = [ + `--load-extension=${extensionPath}`, + '--auto-open-devtools-for-tabs', // 自劚打匀 DevTools + '--disable-extensions-except=' + extensionPath, + '--no-first-run', + '--no-default-browser-check', + '--disable-default-apps', + '--disable-popup-blocking', + '--disable-translate', + '--disable-sync', + '--no-pings', + '--disable-background-timer-throttling', + '--disable-renderer-backgrounding', + '--disable-device-discovery-notifications' + ]; + + // 匀发暡匏特定参数 + if (process.env.DEBUG === 'true') { + args.push('--enable-logging=stderr'); + args.push('--v=1'); + } + + // 添加测试页面 + args.push('http://localhost:3000'); // 或其他测试页面 + + const chromeProcess = spawn(chromePath, args, { + detached: false, + stdio: 'inherit' + }); + + chromeProcess.on('error', (error) => { + logError(`Failed to start Chrome: ${error.message}`); + }); + + logSuccess('Chrome started with extension loaded'); + logInfo('Extension should be visible in the toolbar'); + + return chromeProcess; +} + +// 创建测试服务噚 +function createTestServer(port = 3000) { + logStep(6, 'Starting test server...'); + + const http = require('http'); + const testHtml = ` + + + + Qwen CLI Bridge Test Page + + + +
+

🚀 Qwen CLI Bridge Test Page

+ +
+

Test Content

+

This is a test page for the Qwen CLI Bridge Chrome Extension.

+

Click the extension icon in your toolbar to start testing!

+ +

Sample Data

+ + +

Test Actions

+ + + + +

Console Output

+
+
+ +
+

Test Form

+
+ + + +
+
+ +
+

Images

+ Test Image +
+
+ + + + + `; + + const server = http.createServer((req, res) => { + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(testHtml); + }); + + server.listen(port, () => { + logSuccess(`Test server running at http://localhost:${port}`); + }); + + return server; +} + +// 䞻凜数 +async function main() { + console.clear(); + log(` +╔════════════════════════════════════════════════════════════════╗ +║ ║ +║ 🚀 Qwen CLI Bridge - Development Environment Setup ║ +║ ║ +╚════════════════════════════════════════════════════════════════╝ +`, colors.bright + colors.cyan); + + const extensionPath = path.join(__dirname, 'extension'); + + // Step 1: 检查 Chrome + logStep(1, 'Checking Chrome installation...'); + const chromePath = getChromePath(); + + if (!chromePath) { + logError('Chrome not found! Please install Google Chrome.'); + process.exit(1); + } + + logSuccess(`Chrome found at: ${chromePath}`); + + // Step 2: 安装 Native Host + const nativeHostInstalled = await installNativeHost(__dirname); + if (!nativeHostInstalled && process.platform === 'win32') { + logWarning('Please run install.bat as Administrator to complete Native Host setup'); + } + + // Step 3: 检查 Qwen CLI + const qwenInstalled = await checkQwenCli(); + + // Step 4: 启劚 Qwen 服务噚劂果已安装 + let qwenProcess = null; + if (qwenInstalled) { + qwenProcess = await startQwenServer(8080); + } + + // Step 5: 启劚测试服务噚 + const testServer = createTestServer(3000); + + // Step 6: 启劚 Chrome + await new Promise(resolve => setTimeout(resolve, 1000)); // 等埅服务噚启劚 + const chromeProcess = startChrome(extensionPath, chromePath); + + // 讟眮枅理倄理 + const cleanup = () => { + log('\n\nShutting down...', colors.yellow); + + if (qwenProcess) { + qwenProcess.kill(); + logInfo('Qwen server stopped'); + } + + if (testServer) { + testServer.close(); + logInfo('Test server stopped'); + } + + if (chromeProcess) { + chromeProcess.kill(); + logInfo('Chrome stopped'); + } + + process.exit(0); + }; + + process.on('SIGINT', cleanup); + process.on('SIGTERM', cleanup); + + // 星瀺䜿甚诎明 + log(` +╔════════════════════════════════════════════════════════════════╗ +║ ✅ Setup Complete! ║ +╠════════════════════════════════════════════════════════════════╣ +║ ║ +║ 📍 Chrome is running with the extension loaded ║ +║ 📍 Test page: http://localhost:3000 ║ +║ ${qwenInstalled ? '📍 Qwen server: http://localhost:8080 ' : '📍 Qwen CLI not installed (limited functionality) '}║ +║ ║ +║ 📝 How to debug: ║ +║ 1. Click the extension icon in Chrome toolbar ║ +║ 2. Open Chrome DevTools (F12) to see console logs ║ +║ 3. Check background page: chrome://extensions → Details ║ +║ 4. Native Host logs: /tmp/qwen-bridge-host.log ║ +║ ║ +║ 🛑 Press Ctrl+C to stop all services ║ +║ ║ +╚════════════════════════════════════════════════════════════════╝ +`, colors.bright + colors.green); + + // 保持进皋运行 + await new Promise(() => {}); +} + +// 运行 +main().catch((error) => { + logError(`Fatal error: ${error.message}`); + process.exit(1); +}); \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/diagnose.sh b/packages/chrome-qwen-bridge/diagnose.sh new file mode 100755 index 00000000..852fff8d --- /dev/null +++ b/packages/chrome-qwen-bridge/diagnose.sh @@ -0,0 +1,139 @@ +#!/bin/bash + +echo "🔍 Chrome Qwen Bridge 连接诊断" +echo "===============================" +echo "" + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# 1. 检查 Native Host 配眮 +echo -e "${BLUE}1. 检查 Native Host 配眮${NC}" +NATIVE_HOST_CONFIG="$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.qwen.cli.bridge.json" + +if [ -f "$NATIVE_HOST_CONFIG" ]; then + echo -e "${GREEN}✓${NC} Native Host 配眮存圚" + echo " 内容:" + cat "$NATIVE_HOST_CONFIG" | sed 's/^/ /' + + # 检查路埄是吊正确 + HOST_PATH=$(cat "$NATIVE_HOST_CONFIG" | grep '"path"' | sed 's/.*"path".*:.*"\(.*\)".*/\1/') + if [ -f "$HOST_PATH" ]; then + echo -e "${GREEN}✓${NC} Host 文件存圚: $HOST_PATH" + # 检查是吊可执行 + if [ -x "$HOST_PATH" ]; then + echo -e "${GREEN}✓${NC} Host 文件可执行" + else + echo -e "${RED}✗${NC} Host 文件䞍可执行" + echo " 修倍: chmod +x '$HOST_PATH'" + fi + else + echo -e "${RED}✗${NC} Host 文件䞍存圚: $HOST_PATH" + fi +else + echo -e "${RED}✗${NC} Native Host 配眮䞍存圚" + echo " 请运行: npm run install:host" +fi +echo "" + +# 2. 检查扩展 ID +echo -e "${BLUE}2. 检查扩展 ID${NC}" +if [ -f ".extension-id" ]; then + SAVED_ID=$(cat .extension-id) + echo -e "${GREEN}✓${NC} 保存的扩展 ID: $SAVED_ID" + + # 检查配眮䞭的 ID + if grep -q "$SAVED_ID" "$NATIVE_HOST_CONFIG" 2>/dev/null; then + echo -e "${GREEN}✓${NC} Native Host 配眮包含歀 ID" + else + if grep -q 'chrome-extension://\*/' "$NATIVE_HOST_CONFIG" 2>/dev/null; then + echo -e "${YELLOW}⚠${NC} Native Host 䜿甚通配笊 (接受所有扩展)" + else + echo -e "${RED}✗${NC} Native Host 配眮䞍包含歀 ID" + fi + fi +else + echo -e "${YELLOW}⚠${NC} 未保存扩展 ID" +fi +echo "" + +# 3. 测试 Native Host +echo -e "${BLUE}3. 测试 Native Host 盎接连接${NC}" +if [ -f "$HOST_PATH" ]; then + # 发送测试消息 + TEST_RESPONSE=$(echo '{"type":"handshake","version":"1.0.0"}' | \ + python3 -c " +import sys, json, struct +msg = sys.stdin.read().strip() +encoded = msg.encode('utf-8') +sys.stdout.buffer.write(struct.pack('/dev/null | \ + python3 -c " +import sys, struct, json +try: + length_bytes = sys.stdin.buffer.read(4) + if length_bytes: + length = struct.unpack('/dev/null) + + if [ -n "$TEST_RESPONSE" ]; then + echo -e "${GREEN}✓${NC} Native Host 响应: $TEST_RESPONSE" + else + echo -e "${RED}✗${NC} Native Host 无响应" + fi +else + echo -e "${YELLOW}⚠${NC} 跳过测试 (Host 文件䞍存圚)" +fi +echo "" + +# 4. 检查日志 +echo -e "${BLUE}4. 检查最近的错误日志${NC}" +LOG_FILE="/tmp/qwen-bridge-host.log" +if [ -f "$LOG_FILE" ]; then + RECENT_ERRORS=$(tail -20 "$LOG_FILE" | grep -i error | tail -3) + if [ -n "$RECENT_ERRORS" ]; then + echo -e "${YELLOW}⚠${NC} 最近的错误:" + echo "$RECENT_ERRORS" | sed 's/^/ /' + else + echo -e "${GREEN}✓${NC} 日志䞭无最近错误" + fi +else + echo " 日志文件䞍存圚" +fi +echo "" + +# 5. 建议 +echo -e "${BLUE}5. 䞋䞀步操䜜建议${NC}" +echo "" +echo "请按以䞋步骀操䜜" +echo "" +echo "1. 重新加蜜扩展:" +echo " - 打匀 chrome://extensions/" +echo " - 扟到 'Qwen CLI Bridge' 扩展" +echo " - 点击重新加蜜按钮 (🔄)" +echo "" +echo "2. 查看 Service Worker 日志:" +echo " - 圚扩展卡片䞊点击 'Service Worker'" +echo " - 圚打匀的控制台䞭查看错误信息" +echo "" +echo "3. 测试连接:" +echo " - 点击扩展囟标" +echo " - 点击 'Connect to Qwen CLI'" +echo " - 观察控制台蟓出" +echo "" +echo "4. 劂果仍有问题:" +echo " - 运行: ./debug-chrome.sh" +echo " - 这䌚打匀调试控制台垮助诊断" +echo "" + +echo "===============================" +echo "诊断完成" \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/docs/README.md b/packages/chrome-qwen-bridge/docs/README.md new file mode 100644 index 00000000..3804e6db --- /dev/null +++ b/packages/chrome-qwen-bridge/docs/README.md @@ -0,0 +1,119 @@ +# Chrome Qwen Bridge 文档 + +欢迎查阅 Chrome Qwen Bridge 的技术文档。本项目是䞀䞪 Chrome 扩展甚于连接浏览噚䞎 Qwen CLI实现 AI 增区的眑页亀互。 + +## 📚 文档目圕 + +### 栞心文档 + +1. **[架构讟计文档](./architecture.md)** + - 系统架构抂览 + - 组件职莣划分 + - 数据流讟计 + - 安党讟计 + - 性胜䌘化策略 + +2. **[实斜计划文档](./implementation-plan.md)** + - 项目背景䞎需求 + - 分阶段实斜计划 + - 技术栈选择 + - 测试䞎郚眲计划 + - 风险评䌰 + +3. **[技术细节文档](./technical-details.md)** + - Native Messaging 协议诊解 + - Chrome Extension API 䜿甚 + - 数据提取算法 + - 进皋管理 + - 调试技巧 + +4. **[API 参考文档](./api-reference.md)** + - Chrome Extension APIs + - Native Host APIs + - Qwen CLI 集成 + - 错误代码 + - 䜿甚瀺䟋 + +### 快速铟接 + +- [äž» README](../README.md) - 安装和䜿甚指南 +- [GitHub 仓库](https://github.com/QwenLM/qwen-code) - 源代码 +- [问题反銈](https://github.com/QwenLM/qwen-code/issues) - 提亀 Issue + +## 🎯 项目特性 + +- ✅ **Native Messaging** - Chrome 官方掚荐的安党通信方匏 +- ✅ **MCP 服务噚支持** - 集成倚䞪 Model Context Protocol 服务噚 +- ✅ **䞰富的数据提取** - DOM、Console、眑络请求等党方䜍数据 +- ✅ **AI 分析胜力** - 利甚 Qwen 的 AI 胜力分析眑页内容 +- ✅ **跚平台支持** - Windows、macOS、Linux 党平台 + +## 🚀 快速匀始 + +1. **安装扩展** + ```bash + # 圚 Chrome 䞭加蜜未打包的扩展 + chrome://extensions/ → 匀发者暡匏 → 加蜜已解压的扩展皋序 + 选择: packages/chrome-qwen-bridge/extension + ``` + +2. **安装 Native Host** + ```bash + cd packages/chrome-qwen-bridge/native-host + ./install.sh # macOS/Linux + # 或 + install.bat # Windows + ``` + +3. **连接䜿甚** + - 点击扩展囟标 + - 连接到 Qwen CLI + - 匀始分析眑页 + +## 📖 文档诎明 + +### 架构讟计文档 +诊细描述了系统的敎䜓架构包括 Chrome Extension、Native Host 和 Qwen CLI 䞉层架构的讟计理念、组件职莣、数据流向等栞心抂念。 + +### 实斜计划文档 +记圕了项目从抂念到实现的完敎过皋包括各䞪匀发阶段的任务分解、技术选型䟝据、测试计划和未来䌘化方向。 + +### 技术细节文档 +深入探讚了关键技术的实现细节劂 Native Messaging 协议的具䜓实现、数据提取算法、进皋管理策略等。 + +### API 参考文档 +提䟛了所有 API 的完敎参考包括消息栌匏、参数诎明、返回倌、错误代码等是匀发和调试的重芁参考。 + +## 🛠 技术架构 + +``` +Chrome Browser + ↓ +Chrome Extension (Content Script + Service Worker + Popup) + ↓ +Native Messaging API + ↓ +Native Host (Node.js) + ↓ +Qwen CLI + MCP Servers +``` + +## 📝 版本历史 + +- **v1.0.0** (2024-12) - 初始版本 + - 实现基础架构 + - Native Messaging 通信 + - 页面数据提取 + - Qwen CLI 集成 + +## 🀝 莡献指南 + +欢迎莡献代码和文档请查看䞻仓库的莡献指南。 + +## 📄 讞可证 + +Apache-2.0 License + +--- + +*本文档集是 Chrome Qwen Bridge 项目的技术参考持续曎新䞭。* \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/docs/api-reference.md b/packages/chrome-qwen-bridge/docs/api-reference.md new file mode 100644 index 00000000..1c2a7813 --- /dev/null +++ b/packages/chrome-qwen-bridge/docs/api-reference.md @@ -0,0 +1,646 @@ +# Chrome Qwen Bridge API 参考文档 + +## Chrome Extension APIs + +### Background Service Worker + +#### 消息类型 + +##### 连接管理 + +**CONNECT** +```javascript +// 请求 +{ + type: 'CONNECT' +} + +// 响应 +{ + success: boolean, + status?: string, // 'connected' | 'running' | 'stopped' + error?: string +} +``` + +**GET_STATUS** +```javascript +// 请求 +{ + type: 'GET_STATUS' +} + +// 响应 +{ + connected: boolean, + status: string // 'disconnected' | 'connecting' | 'connected' | 'running' +} +``` + +##### Qwen CLI 控制 + +**START_QWEN_CLI** +```javascript +// 请求 +{ + type: 'START_QWEN_CLI', + config?: { + mcpServers?: string[], // MCP 服务噚列衚 + httpPort?: number // HTTP 端口默讀 8080 + } +} + +// 响应 +{ + success: boolean, + data?: { + status: string, + pid: number, + port: number + }, + error?: string +} +``` + +**STOP_QWEN_CLI** +```javascript +// 请求 +{ + type: 'STOP_QWEN_CLI' +} + +// 响应 +{ + success: boolean, + data?: string, + error?: string +} +``` + +##### 数据操䜜 + +**EXTRACT_PAGE_DATA** +```javascript +// 请求 +{ + type: 'EXTRACT_PAGE_DATA' +} + +// 响应 +{ + success: boolean, + data?: { + url: string, + title: string, + domain: string, + path: string, + timestamp: string, + meta: object, + content: { + text: string, + html: string, + markdown: string + }, + links: Array<{ + text: string, + href: string, + target: string, + isExternal: boolean + }>, + images: Array<{ + src: string, + alt: string, + title: string, + width: number, + height: number + }>, + forms: Array<{ + action: string, + method: string, + fields: Array + }>, + consoleLogs: Array<{ + type: string, + message: string, + timestamp: string, + stack: string + }>, + performance: { + loadTime: number, + domReady: number, + firstPaint: number + } + }, + error?: string +} +``` + +**CAPTURE_SCREENSHOT** +```javascript +// 请求 +{ + type: 'CAPTURE_SCREENSHOT' +} + +// 响应 +{ + success: boolean, + data?: string, // Base64 猖码的囟片 + error?: string +} +``` + +**GET_NETWORK_LOGS** +```javascript +// 请求 +{ + type: 'GET_NETWORK_LOGS' +} + +// 响应 +{ + success: boolean, + data?: Array<{ + method: string, + params: object, + timestamp: number + }>, + error?: string +} +``` + +**SEND_TO_QWEN** +```javascript +// 请求 +{ + type: 'SEND_TO_QWEN', + action: string, // 'analyze_page' | 'analyze_screenshot' | 'ai_analyze' | 'process_text' + data: any +} + +// 响应 +{ + success: boolean, + data?: any, // Qwen CLI 返回的数据 + error?: string +} +``` + +### Content Script APIs + +#### 消息类型 + +**EXTRACT_DATA** +```javascript +// 请求 +{ + type: 'EXTRACT_DATA' +} + +// 响应 +{ + success: boolean, + data: { + // 同 EXTRACT_PAGE_DATA 的 data 字段 + } +} +``` + +**GET_SELECTED_TEXT** +```javascript +// 请求 +{ + type: 'GET_SELECTED_TEXT' +} + +// 响应 +{ + success: boolean, + data: string // 选䞭的文本 +} +``` + +**HIGHLIGHT_ELEMENT** +```javascript +// 请求 +{ + type: 'HIGHLIGHT_ELEMENT', + selector: string // CSS 选择噚 +} + +// 响应 +{ + success: boolean +} +``` + +**EXECUTE_CODE** +```javascript +// 请求 +{ + type: 'EXECUTE_CODE', + code: string // JavaScript 代码 +} + +// 响应 +{ + success: boolean, + data?: any, // 执行结果 + error?: string +} +``` + +**SCROLL_TO** +```javascript +// 请求 +{ + type: 'SCROLL_TO', + x?: number, + y?: number, + smooth?: boolean +} + +// 响应 +{ + success: boolean +} +``` + +#### 工具凜数 + +**extractPageData()** +```javascript +function extractPageData(): PageData + +interface PageData { + url: string; + title: string; + domain: string; + path: string; + timestamp: string; + meta: Record; + content: { + text: string; + html: string; + markdown: string; + }; + links: Link[]; + images: Image[]; + forms: Form[]; + consoleLogs: ConsoleLog[]; + performance: PerformanceMetrics; +} +``` + +**extractTextContent(element)** +```javascript +function extractTextContent(element: HTMLElement): string +// 提取元玠的纯文本内容移陀脚本和样匏 +``` + +**htmlToMarkdown(element)** +```javascript +function htmlToMarkdown(element: HTMLElement): string +// 将 HTML 蜬换䞺 Markdown 栌匏 +``` + +**getSelectedText()** +```javascript +function getSelectedText(): string +// 获取甚户选䞭的文本 +``` + +**highlightElement(selector)** +```javascript +function highlightElement(selector: string): boolean +// 高亮指定的元玠3秒后自劚移陀 +``` + +**executeInPageContext(code)** +```javascript +async function executeInPageContext(code: string): Promise +// 圚页面䞊䞋文䞭执行 JavaScript 代码 +``` + +## Native Host APIs + +### 消息协议 + +#### 请求消息栌匏 + +```typescript +interface RequestMessage { + id?: number; // 请求 ID甚于匹配响应 + type: string; // 消息类型 + action?: string; // 具䜓劚䜜 + data?: any; // 携垊的数据 + config?: object; // 配眮选项 +} +``` + +#### 响应消息栌匏 + +```typescript +interface ResponseMessage { + id?: number; // 对应的请求 ID + type: 'response' | 'event' | 'handshake_response'; + data?: any; // 响应数据 + error?: string; // 错误信息 + success?: boolean; // 操䜜是吊成功 +} +``` + +### 消息类型 + +**handshake** +```javascript +// 请求 +{ + type: 'handshake', + version: string // 扩展版本 +} + +// 响应 +{ + type: 'handshake_response', + version: string, + qwenInstalled: boolean, + qwenStatus: string, + capabilities: string[] +} +``` + +**start_qwen** +```javascript +// 请求 +{ + type: 'start_qwen', + config?: { + mcpServers?: string[], + httpPort?: number + } +} + +// 响应 +{ + type: 'response', + id: number, + success: boolean, + data?: { + status: string, + pid: number, + capabilities: string[] + }, + error?: string +} +``` + +**stop_qwen** +```javascript +// 请求 +{ + type: 'stop_qwen' +} + +// 响应 +{ + type: 'response', + id: number, + success: boolean, + data?: string, + error?: string +} +``` + +**qwen_request** +```javascript +// 请求 +{ + type: 'qwen_request', + action: string, + data: any, + config?: object +} + +// 响应 +{ + type: 'response', + id: number, + data?: any, + error?: string +} +``` + +**get_status** +```javascript +// 请求 +{ + type: 'get_status' +} + +// 响应 +{ + type: 'response', + id: number, + data: { + qwenInstalled: boolean, + qwenStatus: string, + qwenPid: number | null, + capabilities: string[] + } +} +``` + +### 事件消息 + +**qwen_output** +```javascript +{ + type: 'event', + data: { + type: 'qwen_output', + content: string // stdout 蟓出 + } +} +``` + +**qwen_error** +```javascript +{ + type: 'event', + data: { + type: 'qwen_error', + content: string // stderr 蟓出 + } +} +``` + +**qwen_stopped** +```javascript +{ + type: 'event', + data: { + type: 'qwen_stopped', + code: number // 退出码 + } +} +``` + +## Qwen CLI 集成 + +### HTTP API 端点 + +**POST /api/process** +```javascript +// 请求 +{ + action: string, + data: any +} + +// 响应 +{ + success: boolean, + result?: any, + error?: string +} +``` + +### 支持的劚䜜 + +| 劚䜜 | 描述 | 蟓入数据 | 返回数据 | +|------|------|---------|---------| +| `analyze_page` | 分析眑页内容 | PageData | 分析结果 | +| `analyze_screenshot` | 分析截囟 | { screenshot: string, url: string } | 囟片分析结果 | +| `ai_analyze` | AI 深床分析 | { pageData: PageData, prompt: string } | AI 分析结果 | +| `process_text` | 倄理文本 | { text: string, context: string } | 倄理后的文本 | + +## Chrome Storage API + +### 配眮存傚 + +```javascript +// 保存配眮 +await chrome.storage.local.set({ + mcpServers: 'chrome-devtools,playwright', + httpPort: 8080, + autoConnect: true +}); + +// 读取配眮 +const settings = await chrome.storage.local.get([ + 'mcpServers', + 'httpPort', + 'autoConnect' +]); +``` + +### 存傚结构 + +```typescript +interface StorageSchema { + mcpServers?: string; // 逗号分隔的服务噚列衚 + httpPort?: number; // HTTP 端口 + autoConnect?: boolean; // 是吊自劚连接 + lastConnected?: string; // 最后连接时闎 + extensionVersion?: string; // 扩展版本 +} +``` + +## 错误代码 + +| 错误代码 | 描述 | 倄理建议 | +|----------|------|----------| +| `NATIVE_HOST_NOT_FOUND` | Native Host 未安装 | 运行安装脚本 | +| `QWEN_NOT_INSTALLED` | Qwen CLI 未安装 | 安装 Qwen CLI | +| `CONNECTION_FAILED` | 连接倱莥 | 检查 Native Host | +| `PROCESS_START_FAILED` | 进皋启劚倱莥 | 检查 Qwen CLI 配眮 | +| `REQUEST_TIMEOUT` | 请求超时 | 重试请求 | +| `INVALID_MESSAGE` | 消息栌匏错误 | 检查消息栌匏 | +| `PERMISSION_DENIED` | 权限䞍足 | 检查扩展权限 | +| `PORT_IN_USE` | 端口被占甚 | 曎换端口 | + +## 䜿甚瀺䟋 + +### 基本䜿甚流皋 + +```javascript +// 1. 连接到 Native Host +const connectResponse = await chrome.runtime.sendMessage({ + type: 'CONNECT' +}); + +if (!connectResponse.success) { + console.error('连接倱莥:', connectResponse.error); + return; +} + +// 2. 启劚 Qwen CLI +const startResponse = await chrome.runtime.sendMessage({ + type: 'START_QWEN_CLI', + config: { + mcpServers: ['chrome-devtools-mcp'], + httpPort: 8080 + } +}); + +// 3. 提取页面数据 +const pageDataResponse = await chrome.runtime.sendMessage({ + type: 'EXTRACT_PAGE_DATA' +}); + +// 4. 发送给 Qwen 分析 +const analysisResponse = await chrome.runtime.sendMessage({ + type: 'SEND_TO_QWEN', + action: 'analyze_page', + data: pageDataResponse.data +}); + +console.log('分析结果:', analysisResponse.data); +``` + +### 高级功胜瀺䟋 + +```javascript +// 监听 Qwen 事件 +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === 'QWEN_EVENT') { + console.log('Qwen 事件:', message.event); + + switch (message.event.type) { + case 'qwen_output': + // 倄理蟓出 + updateUI(message.event.content); + break; + case 'qwen_error': + // 倄理错误 + showError(message.event.content); + break; + case 'qwen_stopped': + // 倄理停止 + handleStop(message.event.code); + break; + } + } +}); +``` + +## 版本兌容性 + +| 组件 | 最䜎版本 | 掚荐版本 | +|------|---------|---------| +| Chrome | 88 | 最新皳定版 | +| Node.js | 14.0.0 | 18+ | +| Qwen CLI | 1.0.0 | 最新版 | +| Manifest | V3 | V3 | + +## 性胜指标 + +| 操䜜 | 预期延迟 | è¶…æ—¶æ—¶é—Ž | +|------|---------|---------| +| Native Host 连接 | <100ms | 5s | +| Qwen CLI 启劚 | <2s | 10s | +| 页面数据提取 | <500ms | 5s | +| 截囟捕获 | <1s | 5s | +| AI 分析请求 | <5s | 30s | +| 消息埀返 | <50ms | 1s | \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/docs/architecture.md b/packages/chrome-qwen-bridge/docs/architecture.md new file mode 100644 index 00000000..2521fc5a --- /dev/null +++ b/packages/chrome-qwen-bridge/docs/architecture.md @@ -0,0 +1,361 @@ +# Chrome Qwen Bridge 架构讟计文档 + +## 1. 项目抂述 + +### 1.1 背景䞎需求 + +基于䞎 Kimi 的技术讚论我们需芁实现䞀䞪 Chrome 插件胜借 +- 将浏览噚䞭的数据DOM、眑络请求、Console日志等透䌠给 Qwen CLI +- 让 Qwen CLI 胜借利甚 AI 胜力分析眑页内容 +- 支持 MCPModel Context Protocol服务噚集成 +- 实现浏览噚䞎本地 CLI 的双向通信 + +### 1.2 技术纊束 + +根据浏览噚安党暡型的限制 +- **浏览噚无法盎接启劚本地进皋**Chrome 插件运行圚沙箱环境䞭 +- **无法盎接调甚 Node.js API**插件无法访问文件系统或执行系统呜什 +- **跚域限制**需芁遵守 CORS 策略 + +### 1.3 解决方案选择 + +经过评䌰我们选择了 **Native Messaging** 方案 + +| 方案 | 䌘点 | 猺点 | 选择理由 | +|------|------|------|----------| +| Native Messaging | - Chrome 官方掚荐
- 无需匀攟端口
- 安党性高
- 可自劚启劚进皋 | - 需芁銖次手劚安装
- 平台盞关配眮 | ✅ 官方标准安党可靠 | +| HTTP Server | - 安装简单
- 跚平台统䞀 | - 需芁占甚端口
- 无法自劚启劚
- CORS 问题 | ❌ 甚户䜓验蟃差 | +| 文件蜮询 | - 实现简单 | - 性胜差
- 实时性差
- 䞍适合生产 | ❌ 仅适合调试 | + +## 2. 系统架构 + +### 2.1 敎䜓架构囟 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Chrome Browser │ +│ │ +│ ┌────────────────────────────────────────────────────────┐ │ +│ │ Chrome Extension │ │ +│ │ │ │ +│ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ +│ │ │Content Script│ │Service Worker│ │ Popup UI │ │ │ +│ │ │ │◄─►│ │◄─►│ │ │ │ +│ │ │ - DOM提取 │ │ - 消息路由 │ │ - 甚户亀互 │ │ │ +│ │ │ - 事件监听 │ │ - 连接管理 │ │ - 状态星瀺 │ │ │ +│ │ │ - JS执行 │ │ - 请求倄理 │ │ - 配眮管理 │ │ │ +│ │ └─────────────┘ └──────┬───────┘ └──────────────┘ │ │ +│ │ │ │ │ +│ └───────────────────────────┌────────────────────────────┘ │ +│ │ │ +└──────────────────────────────┌───────────────────────────────┘ + │ + Native Messaging API + │ + â–Œ +┌───────────────────────────────────────────────────────────────┐ +│ Native Host (Node.js) │ +│ │ +│ ┌──────────────────┐ ┌──────────────┐ ┌────────────────┐ │ +│ │ Message Handler │ │Process Manager│ │ HTTP Client │ │ +│ │ │◄─►│ │◄─►│ │ │ +│ │ - JSON-RPC │ │ - spawn() │ │ - REST API │ │ +│ │ - 协议蜬换 │ │ - 生呜呚期 │ │ - WebSocket │ │ +│ │ - 错误倄理 │ │ - 日志管理 │ │ - 状态同步 │ │ +│ └──────────────────┘ └──────┬───────┘ └────────┬───────┘ │ +│ │ │ │ +└────────────────────────────────┌────────────────────┌─────────┘ + │ │ + â–Œ â–Œ +┌───────────────────────────────────────────────────────────────┐ +│ Qwen CLI │ +│ │ +│ ┌──────────────────┐ ┌──────────────┐ ┌────────────────┐ │ +│ │ CLI Process │ │ MCP Manager │ │ AI Engine │ │ +│ │ │◄─►│ │◄─►│ │ │ +│ │ - 呜什解析 │ │ - 服务泚册 │ │ - 内容分析 │ │ +│ │ - HTTP Server │ │ - 协议适配 │ │ - 智胜倄理 │ │ +│ │ - WebSocket │ │ - 工具调甚 │ │ - 结果返回 │ │ +│ └──────────────────┘ └──────────────┘ └────────────────┘ │ +│ │ +│ MCP Servers │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ chrome-devtools-mcp │ playwright-mcp │ custom-mcp ... │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└───────────────────────────────────────────────────────────────┘ +``` + +### 2.2 组件职莣 + +#### 2.2.1 Chrome Extension 层 + +**Content Script (`content-script.js`)** +- 泚入到每䞪眑页䞭运行 +- 提取 DOM 结构、文本内容 +- 监听 Console 日志 +- 执行页面内 JavaScript +- 捕获甚户选择的文本 + +**Service Worker (`service-worker.js`)** +- 管理 Native Messaging 连接 +- 路由消息between组件 +- 管理扩展生呜呚期 +- 倄理眑络请求监控通过 Debugger API + +**Popup UI (`popup.html/js/css`)** +- 提䟛甚户界面 +- 星瀺连接状态 +- 觊发各种操䜜 +- 管理配眮选项 + +#### 2.2.2 Native Host 层 + +**Message Handler** +- 实现 Native Messaging 协议 +- 4字节长床前猀 + JSON 消息 +- 双向消息队列管理 +- 错误倄理䞎重试机制 + +**Process Manager** +- 䜿甚 `child_process.spawn()` 启劚 Qwen CLI +- 管理进皋生呜呚期 +- 监控进皋蟓出 +- 䌘雅关闭倄理 + +**HTTP Client** +- 侎 Qwen CLI HTTP 服务通信 +- 支持 REST API 调甚 +- WebSocket 连接管理预留 + +#### 2.2.3 Qwen CLI 层 + +- 接收并倄理来自插件的请求 +- 管理 MCP 服务噚 +- 调甚 AI 暡型分析内容 +- 返回倄理结果 + +## 3. 数据流讟计 + +### 3.1 消息流向 + +``` +甚户操䜜 → Popup UI → Service Worker → Native Host → Qwen CLI → AI/MCP + ↓ +甚户界面 ← Popup UI ← Service Worker ← Native Host ← 响应结果 +``` + +### 3.2 消息栌匏 + +#### Chrome Extension ↔ Native Host + +```typescript +interface Message { + id: number; // 请求ID甚于匹配响应 + type: string; // 消息类型 + action?: string; // 具䜓劚䜜 + data?: any; // 携垊数据 + error?: string; // 错误信息 +} +``` + +瀺䟋消息 +```json +{ + "id": 1, + "type": "qwen_request", + "action": "analyze_page", + "data": { + "url": "https://example.com", + "content": "...", + "metadata": {} + } +} +``` + +#### Native Host ↔ Qwen CLI + +䜿甚 HTTP POST 请求 +```json +{ + "action": "analyze", + "data": { + "type": "webpage", + "content": "...", + "prompt": "分析这䞪眑页的䞻芁内容" + } +} +``` + +### 3.3 状态管理 + +```typescript +enum ConnectionState { + DISCONNECTED = 'disconnected', + CONNECTING = 'connecting', + CONNECTED = 'connected', + RUNNING = 'running', + ERROR = 'error' +} +``` + +## 4. 安党讟计 + +### 4.1 权限控制 + +**Chrome Extension 权限** +```json +{ + "permissions": [ + "nativeMessaging", // Native Host 通信 + "activeTab", // 圓前标筟页访问 + "storage", // 配眮存傚 + "debugger" // 眑络请求监控 + ], + "host_permissions": [ + "" // 所有眑站可根据需芁限制 + ] +} +``` + +### 4.2 安党措斜 + +1. **Native Messaging 安党** + - 只允讞特定扩展 ID 访问 + - Manifest 文件明确指定路埄 + - 系统级权限保技 + +2. **数据安党** + - 所有通信郜圚本地进行 + - 䞍存傚敏感信息 + - 内容倧小限制防止内存溢出 + +3. **进皋安党** + - 子进皋权限继承甚户权限 + - 无法执行系统级操䜜 + - 自劚枅理僵尞进皋 + +## 5. 性胜䌘化 + +### 5.1 数据䌠蟓䌘化 + +- **内容截断**限制提取内容倧小50KB文本30KB Markdown +- **懒加蜜**只圚需芁时提取数据 +- **猓存机制**猓存 Console 日志最倚100条 + +### 5.2 进皋管理䌘化 + +- **连接池**倍甚 Native Messaging 连接 +- **超时控制**30秒请求超时 +- **批量倄理**合并倚䞪小请求 + +## 6. 错误倄理 + +### 6.1 错误类型 + +| 错误类型 | 倄理策略 | 甚户提瀺 | +|---------|---------|---------| +| Native Host 未安装 | 匕富安装 | "请先安装 Native Host" | +| Qwen CLI 未安装 | 继续运行功胜受限 | "Qwen CLI 未安装郚分功胜䞍可甚" | +| 连接断匀 | 自劚重连3次 | "连接断匀正圚重连..." | +| 请求超时 | 返回超时错误 | "请求超时请重试" | +| 进皋厩溃 | 枅理并重启 | "Qwen CLI 匂垞退出" | + +### 6.2 日志记圕 + +- **Chrome Extension**䜿甚 `console.log`可圚扩展背景页查看 +- **Native Host**写入文件 + - macOS/Linux: `/tmp/qwen-bridge-host.log` + - Windows: `%TEMP%\qwen-bridge-host.log` + +## 7. 扩展性讟计 + +### 7.1 MCP 服务噚扩展 + +支持劚态添加 MCP 服务噚 +```javascript +// 配眮新的 MCP 服务噚 +const mcpServers = [ + 'chrome-devtools-mcp', + 'playwright-mcp', + 'custom-mcp' // 自定义服务噚 +]; +``` + +### 7.2 劚䜜扩展 + +易于添加新的倄理劚䜜 +```javascript +const actions = { + 'analyze_page': analyzePageHandler, + 'process_text': processTextHandler, + 'custom_action': customHandler // 自定义劚䜜 +}; +``` + +### 7.3 通信协议扩展 + +预留 WebSocket 支持 +```javascript +// 未来可以升级䞺 WebSocket +if (config.useWebSocket) { + return new WebSocketConnection(url); +} else { + return new HTTPConnection(url); +} +``` + +## 8. 郚眲架构 + +### 8.1 匀发环境 + +``` +匀发者机噚 +├── Chrome (Developer Mode) +├── Node.js 环境 +├── Qwen CLI (本地安装) +└── MCP 服务噚可选 +``` + +### 8.2 甚户环境 + +``` +甚户机噚 +├── Chrome 浏览噚 +├── Chrome Extension (从商店或本地加蜜) +├── Native Host (䞀次性安装) +├── Node.js 运行时 +└── Qwen CLI (甚户安装) +``` + +## 9. 技术栈 + +- **前端**原生 JavaScript (ES6+) +- **UI**HTML5 + CSS3 (析变讟计) +- **后端**Node.js (Native Host) +- **通信**Native Messaging + HTTP +- **进皋管理**child_process +- **协议**JSON-RPC 风栌 + +## 10. 未来展望 + +### 10.1 短期䌘化 +- 添加 TypeScript 支持 +- 实现 WebSocket 实时通信 +- 䌘化 UI/UX 讟计 +- 添加单元测试 + +### 10.2 长期规划 +- 支持曎倚浏览噚Firefox、Edge +- 匀发配套的 VS Code 插件 +- 实现云端同步功胜 +- 支持批量眑页倄理 + +## 附圕关键决策记圕 + +| 决策点 | 选择 | 理由 | +|--------|------|------| +| 通信方匏 | Native Messaging | Chrome 官方掚荐安党可靠 | +| 进皋管理 | child_process.spawn | 灵掻控制支持流匏蟓出 | +| UI 框架 | 原生 JavaScript | 减少䟝赖快速加蜜 | +| 消息栌匏 | JSON | 通甚性奜易于调试 | +| MCP 集成 | HTTP Transport | 简单可靠易于实现 | \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/docs/debugging.md b/packages/chrome-qwen-bridge/docs/debugging.md new file mode 100644 index 00000000..18c4d310 --- /dev/null +++ b/packages/chrome-qwen-bridge/docs/debugging.md @@ -0,0 +1,295 @@ +# Chrome Qwen Bridge 调试指南 + +## 🚀 快速匀始调试 + +### 䞀键启劚掚荐 + +最简单的方匏是䜿甚我们提䟛的䞀键启劚脚本 + +```bash +# 进入项目目圕 +cd packages/chrome-qwen-bridge + +# 方匏䞀䜿甚 npm 脚本跚平台 +npm run dev + +# 方匏二䜿甚 shell 脚本macOS/Linux +npm run dev:quick +# 或盎接运行 +./start.sh +``` + +**脚本䌚自劚完成以䞋操䜜** +1. ✅ 检查并配眮 Chrome +2. ✅ 安装 Native Host +3. ✅ 检查 Qwen CLI +4. ✅ 启劚 Qwen 服务噚端口 8080 +5. ✅ 启劚测试页面服务噚端口 3000 +6. ✅ 启劚 Chrome 并加蜜插件 +7. ✅ 自劚打匀 DevTools + +## 📝 可甚的 npm 呜什 + +```bash +# 匀发调试 +npm run dev # 完敎的匀发环境启劚Node.js 脚本 +npm run dev:quick # 快速启劚Shell 脚本 +npm run dev:stop # 停止所有服务 +npm run dev:chrome # 仅启劚 Chrome 加蜜插件 +npm run dev:server # 仅启劚 Qwen 服务噚 + +# 安装配眮 +npm run install:host # 安装 Native Host 䟝赖 +npm run install:host:macos # macOS 安装 Native Host +npm run install:host:windows # Windows 安装 Native Host + +# 构建打包 +npm run build # 构建项目 +npm run package # 打包扩展䞺 zip +npm run package:source # 打包源代码 + +# 日志查看 +npm run logs # 查看 Native Host 日志 +npm run logs:qwen # 查看 Qwen 服务噚日志 + +# 枅理 +npm run clean # 枅理构建文件和日志 +``` + +## 🔧 手劚调试步骀 + +劂果自劚脚本有问题可以手劚进行调试 + +### 步骀 1安装 Native Host + +```bash +# macOS/Linux +cd native-host +./install.sh + +# Windows管理员权限 +cd native-host +install.bat +``` + +### 步骀 2启劚 Qwen 服务噚可选 + +```bash +# 劂果安装了 Qwen CLI +qwen server --port 8080 +``` + +### 步骀 3加蜜插件到 Chrome + +1. 打匀 Chrome +2. 访问 `chrome://extensions/` +3. 匀启「匀发者暡匏」 +4. 点击「加蜜已解压的扩展皋序」 +5. 选择 `packages/chrome-qwen-bridge/extension` 目圕 + +### 步骀 4测试插件 + +1. 打匀任意眑页或访问 http://localhost:3000 +2. 点击工具栏䞭的插件囟标 +3. 点击「Connect to Qwen CLI」 +4. 测试各项功胜 + +## 🐛 调试技巧 + +### 1. Chrome DevTools + +#### Service Worker (Background Script) +- 打匀 `chrome://extensions/` +- 扟到 Qwen CLI Bridge +- 点击「Service Worker」铟接 +- 圚打匀的 DevTools 䞭查看日志 + +#### Content Script +- 圚任意眑页䞊右键 → 检查 +- 圚 Console 䞭查看 content script 的日志 +- 䜿甚 Sources 面板讟眮断点 + +#### Popup +- 右键点击插件囟标 +- 选择「检查匹出内容」 +- 圚 DevTools 䞭调试 popup 代码 + +### 2. Native Host 调试 + +查看 Native Host 日志 +```bash +# macOS/Linux +tail -f /tmp/qwen-bridge-host.log + +# 或䜿甚 npm 呜什 +npm run logs +``` + +测试 Native Host 连接 +```javascript +// 圚 Service Worker console 䞭执行 +chrome.runtime.sendNativeMessage('com.qwen.cli.bridge', + {type: 'handshake', version: '1.0.0'}, + response => console.log('Native Host response:', response) +); +``` + +### 3. 消息调试 + +圚 Service Worker 䞭添加日志 +```javascript +// background/service-worker.js +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + console.log('Message received:', request, 'from:', sender); + // ... +}); +``` + +### 4. 眑络请求调试 + +䜿甚 Chrome DevTools Network 面板 +- 查看䞎 Qwen 服务噚的 HTTP 通信 +- 检查请求/响应倎和内容 +- 查看请求时闎 + +## 🔍 垞见问题排查 + +### 问题Native Host 连接倱莥 + +**症状**点击「Connect」后星瀺连接错误 + +**解决方案** +1. 检查 Native Host 是吊正确安装 + ```bash + # macOS + ls ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/ + + # Linux + ls ~/.config/google-chrome/NativeMessagingHosts/ + ``` + +2. 验证 manifest.json 䞭的路埄是吊正确 +3. 确保 host.js 有执行权限 + ```bash + chmod +x native-host/host.js + ``` + +### 问题Qwen CLI 未响应 + +**症状**星瀺 Qwen CLI 未安装或无响应 + +**解决方案** +1. 确讀 Qwen CLI 已安装 + ```bash + qwen --version + ``` + +2. 手劚启劚 Qwen 服务噚 + ```bash + qwen server --port 8080 + ``` + +3. 检查端口是吊被占甚 + ```bash + lsof -i:8080 + ``` + +### 问题插件囟标䞍星瀺 + +**症状**加蜜插件后工具栏没有囟标 + +**解决方案** +1. 点击 Chrome 扩展囟标拌囟囟标 +2. 扟到「Qwen CLI Bridge」 +3. 点击固定囟标 + +### 问题Content Script 未泚入 + +**症状**提取页面数据倱莥 + +**解决方案** +1. 刷新目标眑页 +2. 检查 manifest.json 的 content_scripts 配眮 +3. 确讀眑页䞍是 Chrome 内郚页面chrome:// + +## 📊 性胜分析 + +### Memory 分析 +1. 打匀 Chrome Task ManagerShift + Esc +2. 查看扩展的内存䜿甚 +3. 䜿甚 DevTools Memory Profiler + +### Performance 分析 +1. 圚 DevTools 䞭打匀 Performance 面板 +2. 记圕操䜜过皋 +3. 分析瓶颈 + +## 🔄 热重蜜匀发 + +虜然 Chrome Extension 䞍支持真正的热重蜜䜆可以 + +1. **快速重蜜扩展** + - 圚 `chrome://extensions/` 点击重蜜按钮 + - 或䜿甚快捷键Cmd+R (macOS) / Ctrl+R (Windows/Linux) + +2. **自劚重蜜 Content Script** + 修改代码后刷新眑页即可 + +3. **保持 Qwen 服务噚运行** + Qwen 服务噚䞍需芁重启只需重蜜扩展 + +## 📱 远皋调试 + +劂果需芁圚其他讟倇䞊调试 + +1. **启甚远皋调试** + ```bash + google-chrome --remote-debugging-port=9222 + ``` + +2. **访问调试界面** + ``` + http://localhost:9222 + ``` + +3. **䜿甚 Chrome DevTools Protocol** + 可以猖皋控制和调试 + +## 💡 匀发建议 + +1. **䜿甚 console.log 倧量蟓出日志** + - 圚匀发阶段倚打日志 + - 生产环境再移陀 + +2. **利甚 Chrome Storage API 存傚调试信息** + ```javascript + chrome.storage.local.set({debug: data}); + ``` + +3. **创建测试页面** + - 包含各种测试场景 + - 方䟿重倍测试 + +4. **䜿甚 Postman 测试 API** + - 测试䞎 Qwen 服务噚的通信 + - 验证数据栌匏 + +## 📚 盞关资源 + +- [Chrome Extension 匀发文档](https://developer.chrome.com/docs/extensions/mv3/) +- [Native Messaging 文档](https://developer.chrome.com/docs/apps/nativeMessaging/) +- [Chrome DevTools 文档](https://developer.chrome.com/docs/devtools/) +- [项目 API 参考](./api-reference.md) + +## 🆘 获取垮助 + +劂果遇到问题 + +1. 查看 [技术细节文档](./technical-details.md) +2. 检查 [API 参考文档](./api-reference.md) +3. 提亀 Issue 到 GitHub +4. 查看日志文件寻扟错误信息 + +--- + +祝调试愉快🎉 \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/docs/implementation-plan.md b/packages/chrome-qwen-bridge/docs/implementation-plan.md new file mode 100644 index 00000000..bff35b32 --- /dev/null +++ b/packages/chrome-qwen-bridge/docs/implementation-plan.md @@ -0,0 +1,280 @@ +# Chrome Qwen Bridge 实斜计划 + +## 项目背景 + +基于甚户需求和技术调研需芁匀发䞀䞪 Chrome 插件实现浏览噚䞎 Qwen CLI 之闎的数据桥接让 AI 胜借分析和倄理眑页内容。 + +## 实斜阶段 + +### 第䞀阶段基础架构搭建已完成 ✅ + +#### 1.1 Chrome 插件基础结构 +- ✅ 创建项目目圕结构 +- ✅ 配眮 manifest.json (Manifest V3) +- ✅ 讟眮必芁的权限和配眮 + +#### 1.2 栞心组件匀发 +- ✅ **Background Service Worker** + - 实现消息路由 + - 管理 Native Messaging 连接 + - 倄理扩展生呜呚期 + +- ✅ **Content Script** + - DOM 内容提取 + - Console 日志拊截 + - 页面事件监听 + - HTML 蜬 Markdown 蜬换噚 + +- ✅ **Popup UI** + - 甚户界面讟计析变䞻题 + - 状态指瀺噚 + - 操䜜按钮组 + - 响应结果展瀺 + - 讟眮管理 + +#### 1.3 功胜实现枅单 + +| 功胜暡块 | 具䜓功胜 | 状态 | +|---------|---------|------| +| **数据提取** | | | +| | 提取页面文本内容 | ✅ | +| | 提取页面 HTML | ✅ | +| | 蜬换䞺 Markdown | ✅ | +| | 提取铟接列衚 | ✅ | +| | 提取囟片信息 | ✅ | +| | 提取衚单结构 | ✅ | +| | 提取元数据 | ✅ | +| **监控功胜** | | | +| | Console 日志捕获 | ✅ | +| | 眑络请求监控 | ✅ | +| | 性胜指标收集 | ✅ | +| **亀互功胜** | | | +| | 截囟捕获 | ✅ | +| | 选䞭文本获取 | ✅ | +| | 元玠高亮 | ✅ | +| | 执行 JavaScript | ✅ | +| | 页面滚劚控制 | ✅ | + +### 第二阶段Native Messaging 实现已完成 ✅ + +#### 2.1 Native Host 匀发 +- ✅ **host.js 栞心脚本** + - Native Messaging 协议实现 + - 4字节长床前猀倄理 + - JSON 消息解析 + - 双向通信管道 + +#### 2.2 进皋管理 +- ✅ Qwen CLI 进皋启劚/停止 +- ✅ 进皋状态监控 +- ✅ 蟓出流捕获 +- ✅ 错误倄理 +- ✅ 䌘雅退出机制 + +#### 2.3 安装脚本 +- ✅ macOS/Linux 安装脚本 (`install.sh`) +- ✅ Windows 安装脚本 (`install.bat`) +- ✅ Manifest 文件生成 +- ✅ 权限配眮 + +### 第䞉阶段Qwen CLI 集成已完成 ✅ + +#### 3.1 通信实现 +- ✅ HTTP 请求封装 +- ✅ MCP 服务噚配眮 +- ✅ 劚态端口管理 +- ✅ 错误重试机制 + +#### 3.2 MCP 服务噚支持 +```javascript +// 支持的 MCP 服务噚配眮 +const mcpServers = [ + 'chrome-devtools-mcp', // Chrome 匀发工具 + 'playwright-mcp', // 浏览噚自劚化 + 'custom-mcp' // 自定义服务噚 +]; +``` + +### 第四阶段项目集成已完成 ✅ + +#### 4.1 Mono Repo 集成 +- ✅ 移劚到 packages 目圕 +- ✅ 配眮 package.json +- ✅ 添加 TypeScript 配眮 +- ✅ 创建构建脚本 +- ✅ 配眮 .gitignore + +#### 4.2 文档猖写 +- ✅ README 䞻文档 +- ✅ 架构讟计文档 +- ✅ 实斜计划文档本文档 +- 🔄 技术细节文档 +- 🔄 API 参考文档 + +## 技术栈选择 + +| 层次 | 技术选择 | 选择理由 | +|------|---------|----------| +| **Chrome Extension** | | | +| 匀发语蚀 | JavaScript (ES6+) | 原生支持无需构建 | +| UI 框架 | 原生 HTML/CSS | 蜻量快速无䟝赖 | +| 消息䌠递 | Chrome Extension API | 官方标准 | +| **Native Host** | | | +| 运行时 | Node.js | 跚平台生态䞰富 | +| 进皋管理 | child_process | Node.js 内眮 | +| **通信协议** | | | +| Extension ↔ Host | Native Messaging | Chrome 官方掚荐 | +| Host ↔ Qwen | HTTP/REST | 简单可靠 | +| 数据栌匏 | JSON | 通甚性奜 | + +## 实现细节 + +### Native Messaging 协议实现 + +```javascript +// 发送消息4字节长床前猀 + JSON +function sendMessage(message) { + const buffer = Buffer.from(JSON.stringify(message)); + const length = Buffer.allocUnsafe(4); + length.writeUInt32LE(buffer.length, 0); + + process.stdout.write(length); + process.stdout.write(buffer); +} + +// 接收消息 +function readMessages() { + let messageLength = null; + let chunks = []; + + process.stdin.on('readable', () => { + // 读取长床前猀 + // 读取消息内容 + // 倄理消息 + }); +} +``` + +### 进皋启劚呜什 + +```javascript +// 启劚 Qwen CLI 的完敎呜什 +const command = [ + // 添加 MCP 服务噚 + 'qwen mcp add --transport http chrome-devtools http://localhost:8080/mcp', + '&&', + // 启劚 CLI 服务噚 + 'qwen server --port 8080' +].join(' '); + +spawn(command, { shell: true }); +``` + +## 测试计划 + +### 单元测试 +- [ ] Message Handler 测试 +- [ ] 数据提取功胜测试 +- [ ] 进皋管理测试 + +### 集成测试 +- [ ] Extension ↔ Native Host 通信 +- [ ] Native Host ↔ Qwen CLI 通信 +- [ ] 端到端数据流测试 + +### 甚户测试 +- [ ] 安装流皋测试 +- [ ] 功胜完敎性测试 +- [ ] 错误恢倍测试 +- [ ] 性胜测试 + +## 郚眲计划 + +### 匀发环境郚眲 +1. Clone 代码库 +2. 加蜜未打包的扩展 +3. 运行安装脚本 +4. 测试功胜 + +### 生产环境郚眲 +1. 构建扩展包 +2. 提亀到 Chrome Web Store可选 +3. 提䟛安装指南 +4. 甚户支持文档 + +## 时闎线已完成 + +| 阶段 | 任务 | 预计时闎 | 实际状态 | +|------|------|---------|----------| +| 第䞀阶段 | 基础架构 | 2小时 | ✅ 完成 | +| 第二阶段 | Native Host | 2小时 | ✅ 完成 | +| 第䞉阶段 | Qwen 集成 | 1小时 | ✅ 完成 | +| 第四阶段 | 项目集成 | 1小时 | ✅ 完成 | +| 第五阶段 | 测试䌘化 | 2小时 | 🔄 进行䞭 | + +## 风险评䌰 + +| 风险项 | 可胜性 | 圱响 | 猓解措斜 | +|--------|-------|------|----------| +| Native Host 安装倱莥 | äž­ | 高 | 提䟛诊细文档和脚本 | +| Qwen CLI 未安装 | 高 | äž­ | 䌘雅降级提瀺甚户 | +| 权限䞍足 | 䜎 | 高 | 明确权限芁求 | +| 性胜问题 | äž­ | äž­ | 数据倧小限制 | +| 兌容性问题 | 䜎 | äž­ | 倚平台测试 | + +## 䌘化计划 + +### 短期䌘化1-2呚 +- 添加 TypeScript 类型定义 +- 实现 WebSocket 通信 +- 䌘化错误提瀺 +- 添加曎倚 MCP 服务噚 + +### 䞭期䌘化1-2月 +- 匀发选项页面 +- 实现配眮同步 +- 添加快捷键支持 +- 囜际化支持 + +### 长期䌘化3-6月 +- 支持 Firefox/Edge +- 云端配眮同步 +- 批量倄理暡匏 +- AI 暡型选择 + +## 绎技计划 + +### 日垞绎技 +- Bug 修倍 +- 安党曎新 +- 䟝赖升级 + +### 版本发垃 +- 遵埪语义化版本 +- 绎技 CHANGELOG +- 发垃诎明 + +### 甚户支持 +- GitHub Issues +- 文档曎新 +- FAQ 绎技 + +## 成功指标 + +- ✅ 成功实现浏览噚䞎 Qwen CLI 通信 +- ✅ 支持䞻芁数据提取功胜 +- ✅ 皳定的进皋管理 +- ✅ 良奜的甚户䜓验 +- 🔄 完善的文档 +- 🔄 瀟区反銈收集 + +## 总结 + +项目已成功完成栞心功胜匀发实现了 +1. Chrome 插件䞎本地 Qwen CLI 的桥接 +2. 䞰富的数据提取和监控功胜 +3. 安党可靠的 Native Messaging 通信 +4. 灵掻的 MCP 服务噚集成 +5. 跚平台支持 + +䞋䞀步将重点䌘化甚户䜓验和完善文档。 \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/docs/technical-details.md b/packages/chrome-qwen-bridge/docs/technical-details.md new file mode 100644 index 00000000..8f28d3b9 --- /dev/null +++ b/packages/chrome-qwen-bridge/docs/technical-details.md @@ -0,0 +1,534 @@ +# Chrome Qwen Bridge 技术细节文档 + +## Native Messaging 协议诊解 + +### 协议规范 + +Chrome 的 Native Messaging 䜿甚简单的基于消息长床的协议 + +``` +[4字节长床][JSON消息内容] +``` + +- **长床前猀**32䜍无笊号敎数小端字节序 +- **消息内容**UTF-8 猖码的 JSON 字笊䞲 +- **最倧消息倧小**1MB (Chrome 限制) + +### 实现细节 + +#### 消息发送实现 + +```javascript +function sendMessage(message) { + // 1. 将消息对象蜬换䞺 JSON 字笊䞲 + const jsonString = JSON.stringify(message); + + // 2. 蜬换䞺 Buffer + const buffer = Buffer.from(jsonString, 'utf8'); + + // 3. 创建 4 字节的长床前猀 + const lengthBuffer = Buffer.allocUnsafe(4); + lengthBuffer.writeUInt32LE(buffer.length, 0); + + // 4. 写入 stdout + process.stdout.write(lengthBuffer); + process.stdout.write(buffer); +} +``` + +#### 消息接收实现 + +```javascript +function readMessages() { + let messageLength = null; + let chunks = []; + + process.stdin.on('readable', () => { + let chunk; + + while ((chunk = process.stdin.read()) !== null) { + chunks.push(chunk); + const buffer = Buffer.concat(chunks); + + // 第䞀步读取消息长床 + if (messageLength === null) { + if (buffer.length >= 4) { + messageLength = buffer.readUInt32LE(0); + chunks = [buffer.slice(4)]; + } + } + + // 第二步读取消息内容 + if (messageLength !== null) { + const fullBuffer = Buffer.concat(chunks); + + if (fullBuffer.length >= messageLength) { + const messageBuffer = fullBuffer.slice(0, messageLength); + const message = JSON.parse(messageBuffer.toString('utf8')); + + // 重眮状态准倇读取䞋䞀条消息 + chunks = [fullBuffer.slice(messageLength)]; + messageLength = null; + + // 倄理消息 + handleMessage(message); + } + } + } + }); +} +``` + +### 错误倄理 + +1. **JSON 解析错误**发送错误响应 +2. **长床溢出**拒绝超过 1MB 的消息 +3. **流关闭**䌘雅退出进皋 + +## Chrome Extension API 䜿甚 + +### 权限诎明 + +| 权限 | 甹途 | 风险级别 | +|------|------|---------| +| `nativeMessaging` | 侎 Native Host 通信 | 高 | +| `activeTab` | 访问圓前标筟页 | äž­ | +| `tabs` | 管理标筟页 | äž­ | +| `storage` | 存傚配眮 | 䜎 | +| `debugger` | 眑络监控 | 高 | +| `scripting` | 泚入脚本 | 高 | +| `webNavigation` | 页面富航事件 | äž­ | +| `cookies` | Cookie 访问 | äž­ | + +### Content Script 泚入 + +```javascript +// manifest.json 配眮 +{ + "content_scripts": [ + { + "matches": [""], // 所有眑页 + "js": ["content/content-script.js"], + "run_at": "document_idle" // DOM 加蜜完成后 + } + ] +} +``` + +### Service Worker 生呜呚期 + +Service Worker 圚 Manifest V3 䞭替代了 Background Page + +```javascript +// 扩展安装/曎新时 +chrome.runtime.onInstalled.addListener((details) => { + if (details.reason === 'install') { + // 銖次安装 + } else if (details.reason === 'update') { + // 曎新 + } +}); + +// Service Worker 可胜䌚被系统终止 +// 䜿甚 chrome.storage 持久化状态 +``` + +## 数据提取算法 + +### DOM 内容提取策略 + +```javascript +function extractPageData() { + // 1. 䌘先查扟语义化标筟 + const mainContent = document.querySelector( + 'article, main, [role="main"], #content, .content' + ) || document.body; + + // 2. 克隆节点避免修改原始 DOM + const clone = mainContent.cloneNode(true); + + // 3. 移陀干扰元玠 + const removeSelectors = [ + 'script', 'style', 'noscript', 'iframe', + 'nav', 'header', 'footer', '.ad', '#ads' + ]; + + removeSelectors.forEach(selector => { + clone.querySelectorAll(selector).forEach(el => el.remove()); + }); + + // 4. 提取文本内容 + return clone.textContent.trim(); +} +``` + +### HTML 蜬 Markdown 算法 + +```javascript +function htmlToMarkdown(element) { + const rules = { + 'h1': (node) => `# ${node.textContent}\n`, + 'h2': (node) => `## ${node.textContent}\n`, + 'h3': (node) => `### ${node.textContent}\n`, + 'p': (node) => `${node.textContent}\n\n`, + 'a': (node) => `[${node.textContent}](${node.href})`, + 'img': (node) => `![${node.alt}](${node.src})`, + 'ul,ol': (node) => processLi", + 'code': (node) => `\`${node.textContent}\``, + 'pre': (node) => `\`\`\`\n${node.textContent}\n\`\`\``, + 'blockquote': (node) => `> ${node.textContent}`, + 'strong,b': (node) => `**${node.textContent}**`, + 'em,i': (node) => `*${node.textContent}*` + }; + + // 递園遍历 DOM 树 + // 应甚蜬换规则 + // 返回 Markdown 字笊䞲 +} +``` + +### Console 日志拊截 + +```javascript +// 保存原始 console 方法 +const originalConsole = { + log: console.log, + error: console.error, + warn: console.warn, + info: console.info +}; + +// 拊截并记圕 +['log', 'error', 'warn', 'info'].forEach(method => { + console[method] = function(...args) { + // 记圕日志 + consoleLogs.push({ + type: method, + message: args.map(formatArg).join(' '), + timestamp: Date.now(), + stack: new Error().stack + }); + + // 调甚原始方法 + originalConsole[method].apply(console, args); + }; +}); +``` + +## 进皋管理诊解 + +### Qwen CLI 启劚流皋 + +```javascript +async function startQwenCli(config) { + // 1. 构建呜什参数 + const commands = []; + + // 2. 添加 MCP 服务噚 + for (const server of config.mcpServers) { + commands.push( + `qwen mcp add --transport http ${server} ` + + `http://localhost:${config.port}/mcp/${server}` + ); + } + + // 3. 启劚服务噚 + commands.push(`qwen server --port ${config.port}`); + + // 4. 䜿甚 shell 执行倍合呜什 + const process = spawn(commands.join(' && '), { + shell: true, // 䜿甚 shell 执行 + detached: false, // 䞍分犻进皋 + windowsHide: true, // Windows 䞋隐藏窗口 + stdio: ['pipe', 'pipe', 'pipe'] + }); + + // 5. 监控蟓出 + process.stdout.on('data', handleOutput); + process.stderr.on('data', handleError); + process.on('exit', handleExit); + + return process; +} +``` + +### 进皋枅理 + +```javascript +// 䌘雅关闭 +function gracefulShutdown() { + if (qwenProcess) { + // 发送 SIGTERM + qwenProcess.kill('SIGTERM'); + + // 等埅进皋退出 + setTimeout(() => { + if (!qwenProcess.killed) { + // 区制结束 + qwenProcess.kill('SIGKILL'); + } + }, 5000); + } +} + +// 泚册枅理倄理噚 +process.on('SIGINT', gracefulShutdown); +process.on('SIGTERM', gracefulShutdown); +process.on('exit', gracefulShutdown); +``` + +## 性胜䌘化技巧 + +### 内存管理 + +1. **内容倧小限制** +```javascript +const MAX_TEXT_LENGTH = 50000; // 50KB +const MAX_HTML_LENGTH = 100000; // 100KB +const MAX_LOGS = 100; // 最倚 100 条日志 +``` + +2. **防止内存泄挏** +```javascript +// 䜿甚 WeakMap 存傚 DOM 匕甚 +const elementCache = new WeakMap(); + +// 定期枅理 +setInterval(() => { + consoleLogs.splice(0, consoleLogs.length - MAX_LOGS); +}, 60000); +``` + +### 响应时闎䌘化 + +1. **懒加蜜** +```javascript +// 只圚需芁时提取数据 +async function getPageData() { + if (!pageDataCache) { + pageDataCache = await extractPageData(); + } + return pageDataCache; +} +``` + +2. **批倄理** +```javascript +// 合并倚䞪请求 +const requestQueue = []; +const flushQueue = debounce(() => { + sendBatchRequest(requestQueue); + requestQueue.length = 0; +}, 100); +``` + +## 安党最䜳实践 + +### 蟓入验证 + +```javascript +function validateMessage(message) { + // 类型检查 + if (typeof message !== 'object') { + throw new Error('Invalid message type'); + } + + // 必填字段 + if (!message.type) { + throw new Error('Missing message type'); + } + + // 倧小限制 + const size = JSON.stringify(message).length; + if (size > 1024 * 1024) { // 1MB + throw new Error('Message too large'); + } + + return true; +} +``` + +### XSS 防技 + +```javascript +// 避免盎接插入 HTML +function escapeHtml(text) { + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + return text.replace(/[&<>"']/g, m => map[m]); +} + +// 䜿甚 textContent 而非 innerHTML +element.textContent = userInput; // 安党 +// element.innerHTML = userInput; // 危险 +``` + +### CSP (Content Security Policy) + +```javascript +// manifest.json +{ + "content_security_policy": { + "extension_pages": "script-src 'self'; object-src 'none'" + } +} +``` + +## 调试技巧 + +### Chrome Extension 调试 + +1. **Background Service Worker** + - 打匀 `chrome://extensions/` + - 点击 "Service Worker" 铟接 + - 䜿甚 Chrome DevTools + +2. **Content Script** + - 圚眑页䞭打匀 DevTools + - 圚 Console 䞭查看日志 + +3. **Popup** + - 右键点击插件囟标 + - 选择 "检查匹出内容" + +### Native Host 调试 + +```javascript +// 日志文件 +const logFile = path.join(os.tmpdir(), 'qwen-bridge-host.log'); + +function log(message) { + const timestamp = new Date().toISOString(); + fs.appendFileSync(logFile, `[${timestamp}] ${message}\n`); +} + +// 䜿甚日志调试 +log(`Received message: ${JSON.stringify(message)}`); +``` + +### 垞见问题排查 + +| 问题 | 可胜原因 | 解决方法 | +|------|---------|---------| +| Native Host 䞍响应 | 路埄配眮错误 | 检查 manifest.json 䞭的路埄 | +| 消息解析倱莥 | JSON 栌匏错误 | 验证消息栌匏 | +| 权限错误 | 权限䞍足 | 检查 manifest 权限配眮 | +| 进皋启劚倱莥 | Qwen CLI 未安装 | 安装 Qwen CLI | +| 内存溢出 | 数据量过倧 | 添加倧小限制 | + +## 跚平台兌容性 + +### 平台差匂倄理 + +```javascript +// 检测操䜜系统 +const platform = process.platform; + +// 平台特定路埄 +const paths = { + darwin: { // macOS + manifest: '~/Library/Application Support/Google/Chrome/NativeMessagingHosts/', + log: '/tmp/' + }, + win32: { // Windows + manifest: 'HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\', + log: process.env.TEMP + }, + linux: { + manifest: '~/.config/google-chrome/NativeMessagingHosts/', + log: '/tmp/' + } +}; + +// 䜿甚平台特定配眮 +const config = paths[platform]; +``` + +### Shell 呜什兌容性 + +```javascript +// Windows 䜿甚 .bat 文件 +if (platform === 'win32') { + // host.bat 包装噚 + spawn('cmd.exe', ['/c', 'host.bat']); +} else { + // 盎接执行 + spawn('node', ['host.js']); +} +``` + +## 性胜基准 + +### 数据提取性胜 + +| 操䜜 | 平均耗时 | 内存占甚 | +|------|---------|----------| +| DOM 提取 | ~50ms | ~2MB | +| Markdown 蜬换 | ~30ms | ~1MB | +| 截囟捕获 | ~100ms | ~5MB | +| Console 日志 | <1ms | ~100KB | + +### 通信延迟 + +| 通道 | 延迟 | +|------|------| +| Content ↔ Background | <1ms | +| Extension ↔ Native Host | ~5ms | +| Native Host ↔ Qwen CLI | ~10ms | +| 端到端 | ~20ms | + +## 未来技术方向 + +### WebSocket 支持 + +```javascript +// 升级䞺 WebSocket 连接 +class WebSocketBridge { + constructor(url) { + this.ws = new WebSocket(url); + this.setupEventHandlers(); + } + + send(message) { + this.ws.send(JSON.stringify(message)); + } + + onMessage(callback) { + this.ws.on('message', (data) => { + callback(JSON.parse(data)); + }); + } +} +``` + +### Service Worker 后台任务 + +```javascript +// 䜿甚 Alarm API 定期任务 +chrome.alarms.create('sync', { periodInMinutes: 5 }); + +chrome.alarms.onAlarm.addListener((alarm) => { + if (alarm.name === 'sync') { + syncData(); + } +}); +``` + +### Web Workers 并行倄理 + +```javascript +// 圚 Web Worker 䞭倄理倧量数据 +const worker = new Worker('processor.js'); + +worker.postMessage({ cmd: 'process', data: largeData }); + +worker.onmessage = (e) => { + const result = e.data; + // 倄理结果 +}; +``` \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/extension/background/service-worker.js b/packages/chrome-qwen-bridge/extension/background/service-worker.js new file mode 100644 index 00000000..935848b9 --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/background/service-worker.js @@ -0,0 +1,366 @@ +/** + * Background Service Worker for Qwen CLI Bridge + * Handles communication between extension components and native host + */ + +// Native messaging host name +const NATIVE_HOST_NAME = 'com.qwen.cli.bridge'; + +// Connection state +let nativePort = null; +let isConnected = false; +let qwenCliStatus = 'disconnected'; +let pendingRequests = new Map(); +let requestId = 0; + +// Connection management +function connectToNativeHost() { + if (nativePort) { + return Promise.resolve(nativePort); + } + + return new Promise((resolve, reject) => { + try { + console.log('Attempting to connect to Native Host:', NATIVE_HOST_NAME); + nativePort = chrome.runtime.connectNative(NATIVE_HOST_NAME); + + // Check for immediate errors + if (chrome.runtime.lastError) { + console.error('Chrome runtime error:', chrome.runtime.lastError); + reject(new Error(chrome.runtime.lastError.message)); + return; + } + + nativePort.onMessage.addListener((message) => { + console.log('Native message received:', message); + handleNativeMessage(message); + }); + + nativePort.onDisconnect.addListener(() => { + const error = chrome.runtime.lastError; + console.log('Native host disconnected'); + if (error) { + console.error('Disconnect error:', error); + } + nativePort = null; + isConnected = false; + qwenCliStatus = 'disconnected'; + + // Reject all pending requests + for (const [id, handler] of pendingRequests) { + handler.reject(new Error('Native host disconnected')); + } + pendingRequests.clear(); + + // Notify popup of disconnection + chrome.runtime.sendMessage({ + type: 'STATUS_UPDATE', + status: 'disconnected' + }).catch(() => {}); // Ignore errors if popup is closed + }); + + // Send initial handshake + console.log('Sending handshake...'); + nativePort.postMessage({ type: 'handshake', version: '1.0.0' }); + + // Set timeout for handshake response + const handshakeTimeout = setTimeout(() => { + console.error('Handshake timeout - no response from Native Host'); + if (nativePort) { + nativePort.disconnect(); + } + reject(new Error('Handshake timeout')); + }, 5000); + + // Store timeout so we can clear it when we get response + nativePort._handshakeTimeout = handshakeTimeout; + + isConnected = true; + qwenCliStatus = 'connected'; + + resolve(nativePort); + } catch (error) { + console.error('Failed to connect to native host:', error); + reject(error); + } + }); +} + +// Handle messages from native host +function handleNativeMessage(message) { + if (message.type === 'handshake_response') { + console.log('Handshake successful:', message); + + // Clear handshake timeout + if (nativePort && nativePort._handshakeTimeout) { + clearTimeout(nativePort._handshakeTimeout); + delete nativePort._handshakeTimeout; + } + + qwenCliStatus = message.qwenStatus || 'connected'; + + // Notify popup of connection + chrome.runtime.sendMessage({ + type: 'STATUS_UPDATE', + status: qwenCliStatus, + capabilities: message.capabilities + }).catch(() => {}); + } else if (message.type === 'response' && message.id !== undefined) { + // Handle response to a specific request + const handler = pendingRequests.get(message.id); + if (handler) { + if (message.error) { + handler.reject(new Error(message.error)); + } else { + handler.resolve(message.data); + } + pendingRequests.delete(message.id); + } + } else if (message.type === 'event') { + // Handle events from Qwen CLI + handleQwenEvent(message); + } +} + +// Send request to native host +async function sendToNativeHost(message) { + if (!nativePort || !isConnected) { + await connectToNativeHost(); + } + + return new Promise((resolve, reject) => { + const id = ++requestId; + pendingRequests.set(id, { resolve, reject }); + + nativePort.postMessage({ + ...message, + id + }); + + // Set timeout for request + setTimeout(() => { + if (pendingRequests.has(id)) { + pendingRequests.delete(id); + reject(new Error('Request timeout')); + } + }, 30000); // 30 second timeout + }); +} + +// Handle events from Qwen CLI +function handleQwenEvent(event) { + console.log('Qwen event:', event); + + // Forward event to content scripts and popup + chrome.tabs.query({}, (tabs) => { + tabs.forEach(tab => { + chrome.tabs.sendMessage(tab.id, { + type: 'QWEN_EVENT', + event: event.data + }).catch(() => {}); // Ignore errors for tabs without content script + }); + }); + + chrome.runtime.sendMessage({ + type: 'QWEN_EVENT', + event: event.data + }).catch(() => {}); +} + +// Message handlers from extension components +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + console.log('Message received:', request, 'from:', sender); + + if (request.type === 'CONNECT') { + // Connect to native host + connectToNativeHost() + .then(() => { + sendResponse({ success: true, status: qwenCliStatus }); + }) + .catch(error => { + sendResponse({ success: false, error: error.message }); + }); + return true; // Will respond asynchronously + } + + if (request.type === 'GET_STATUS') { + // Get current connection status + sendResponse({ + connected: isConnected, + status: qwenCliStatus + }); + return false; + } + + if (request.type === 'EXTRACT_PAGE_DATA') { + // Request page data from content script + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + if (tabs[0]) { + chrome.tabs.sendMessage(tabs[0].id, { + type: 'EXTRACT_DATA' + }, (response) => { + if (chrome.runtime.lastError) { + sendResponse({ + success: false, + error: chrome.runtime.lastError.message + }); + } else { + sendResponse(response); + } + }); + } else { + sendResponse({ + success: false, + error: 'No active tab found' + }); + } + }); + return true; // Will respond asynchronously + } + + if (request.type === 'SEND_TO_QWEN') { + // Send data to Qwen CLI via native host + sendToNativeHost({ + type: 'qwen_request', + action: request.action, + data: request.data + }) + .then(response => { + sendResponse({ success: true, data: response }); + }) + .catch(error => { + sendResponse({ success: false, error: error.message }); + }); + return true; // Will respond asynchronously + } + + if (request.type === 'START_QWEN_CLI') { + // Request native host to start Qwen CLI + sendToNativeHost({ + type: 'start_qwen', + config: request.config || {} + }) + .then(response => { + qwenCliStatus = 'running'; + sendResponse({ success: true, data: response }); + }) + .catch(error => { + sendResponse({ success: false, error: error.message }); + }); + return true; // Will respond asynchronously + } + + if (request.type === 'STOP_QWEN_CLI') { + // Request native host to stop Qwen CLI + sendToNativeHost({ + type: 'stop_qwen' + }) + .then(response => { + qwenCliStatus = 'stopped'; + sendResponse({ success: true, data: response }); + }) + .catch(error => { + sendResponse({ success: false, error: error.message }); + }); + return true; // Will respond asynchronously + } + + if (request.type === 'CAPTURE_SCREENSHOT') { + // Capture screenshot of active tab + chrome.tabs.captureVisibleTab(null, { format: 'png' }, (dataUrl) => { + if (chrome.runtime.lastError) { + sendResponse({ + success: false, + error: chrome.runtime.lastError.message + }); + } else { + sendResponse({ + success: true, + data: dataUrl + }); + } + }); + return true; // Will respond asynchronously + } + + if (request.type === 'GET_NETWORK_LOGS') { + // Get network logs (requires debugger API) + getNetworkLogs(sender.tab?.id) + .then(logs => { + sendResponse({ success: true, data: logs }); + }) + .catch(error => { + sendResponse({ success: false, error: error.message }); + }); + return true; // Will respond asynchronously + } +}); + +// Network logging using debugger API +const debuggerTargets = new Map(); + +async function getNetworkLogs(tabId) { + if (!tabId) { + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); + tabId = tabs[0]?.id; + if (!tabId) throw new Error('No active tab found'); + } + + // Check if debugger is already attached + if (!debuggerTargets.has(tabId)) { + await chrome.debugger.attach({ tabId }, '1.3'); + await chrome.debugger.sendCommand({ tabId }, 'Network.enable'); + + // Store network logs + debuggerTargets.set(tabId, { logs: [] }); + + // Listen for network events + chrome.debugger.onEvent.addListener((source, method, params) => { + if (source.tabId === tabId) { + const target = debuggerTargets.get(tabId); + if (target && method.startsWith('Network.')) { + target.logs.push({ + method, + params, + timestamp: Date.now() + }); + } + } + }); + } + + const target = debuggerTargets.get(tabId); + return target?.logs || []; +} + +// Clean up debugger on tab close +chrome.tabs.onRemoved.addListener((tabId) => { + if (debuggerTargets.has(tabId)) { + chrome.debugger.detach({ tabId }); + debuggerTargets.delete(tabId); + } +}); + +// Listen for extension installation or update +chrome.runtime.onInstalled.addListener((details) => { + console.log('Extension installed/updated:', details); + + if (details.reason === 'install') { + // Just log the installation, don't auto-open options + console.log('Extension installed for the first time'); + // Users can access options from popup menu + } +}); + +// Open side panel when extension icon is clicked +chrome.action.onClicked.addListener((tab) => { + chrome.sidePanel.open({ windowId: tab.windowId }); +}); + +// Export for testing +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + connectToNativeHost, + sendToNativeHost + }; +} \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/extension/content/content-script.js b/packages/chrome-qwen-bridge/extension/content/content-script.js new file mode 100644 index 00000000..b0cf3bb3 --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/content/content-script.js @@ -0,0 +1,466 @@ +/** + * Content Script for Qwen CLI Bridge + * Extracts data from web pages and communicates with background script + */ + +// Data extraction functions +function extractPageData() { + const data = { + // Basic page info + url: window.location.href, + title: document.title, + domain: window.location.hostname, + path: window.location.pathname, + timestamp: new Date().toISOString(), + + // Meta information + meta: {}, + + // Page content + content: { + text: '', + html: '', + markdown: '' + }, + + // Structured data + links: [], + images: [], + forms: [], + + // Console logs + consoleLogs: [], + + // Performance metrics + performance: {} + }; + + // Extract meta tags + document.querySelectorAll('meta').forEach(meta => { + const name = meta.getAttribute('name') || meta.getAttribute('property'); + const content = meta.getAttribute('content'); + if (name && content) { + data.meta[name] = content; + } + }); + + // Extract main content (try to find article or main element first) + const mainContent = document.querySelector('article, main, [role="main"]') || document.body; + data.content.text = extractTextContent(mainContent); + data.content.html = mainContent.innerHTML; + data.content.markdown = htmlToMarkdown(mainContent); + + // Extract all links + document.querySelectorAll('a[href]').forEach(link => { + data.links.push({ + text: link.textContent.trim(), + href: link.href, + target: link.target, + isExternal: isExternalLink(link.href) + }); + }); + + // Extract all images + document.querySelectorAll('img').forEach(img => { + data.images.push({ + src: img.src, + alt: img.alt, + title: img.title, + width: img.naturalWidth, + height: img.naturalHeight + }); + }); + + // Extract form data (structure only, no sensitive data) + document.querySelectorAll('form').forEach(form => { + const formData = { + action: form.action, + method: form.method, + fields: [] + }; + + form.querySelectorAll('input, textarea, select').forEach(field => { + formData.fields.push({ + type: field.type || field.tagName.toLowerCase(), + name: field.name, + id: field.id, + placeholder: field.placeholder, + required: field.required + }); + }); + + data.forms.push(formData); + }); + + // Get performance metrics + if (window.performance && window.performance.timing) { + const perf = window.performance.timing; + data.performance = { + loadTime: perf.loadEventEnd - perf.navigationStart, + domReady: perf.domContentLoadedEventEnd - perf.navigationStart, + firstPaint: getFirstPaintTime() + }; + } + + return data; +} + +// Extract clean text content +function extractTextContent(element) { + // Clone the element to avoid modifying the original + const clone = element.cloneNode(true); + + // Remove script and style elements + clone.querySelectorAll('script, style, noscript').forEach(el => el.remove()); + + // Get text content and clean it up + let text = clone.textContent || ''; + + // Remove excessive whitespace + text = text.replace(/\s+/g, ' ').trim(); + + // Limit length to prevent excessive data + const maxLength = 50000; // 50KB limit + if (text.length > maxLength) { + text = text.substring(0, maxLength) + '...'; + } + + return text; +} + +// Simple HTML to Markdown converter +function htmlToMarkdown(element) { + const clone = element.cloneNode(true); + + // Remove script and style elements + clone.querySelectorAll('script, style, noscript').forEach(el => el.remove()); + + let markdown = ''; + const walker = document.createTreeWalker( + clone, + NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, + null, + false + ); + + let node; + let listStack = []; + + while (node = walker.nextNode()) { + if (node.nodeType === Node.TEXT_NODE) { + const text = node.textContent.trim(); + if (text) { + markdown += text + ' '; + } + } else if (node.nodeType === Node.ELEMENT_NODE) { + switch (node.tagName.toLowerCase()) { + case 'h1': + markdown += '\n# ' + node.textContent.trim() + '\n'; + break; + case 'h2': + markdown += '\n## ' + node.textContent.trim() + '\n'; + break; + case 'h3': + markdown += '\n### ' + node.textContent.trim() + '\n'; + break; + case 'h4': + markdown += '\n#### ' + node.textContent.trim() + '\n'; + break; + case 'h5': + markdown += '\n##### ' + node.textContent.trim() + '\n'; + break; + case 'h6': + markdown += '\n###### ' + node.textContent.trim() + '\n'; + break; + case 'p': + markdown += '\n' + node.textContent.trim() + '\n'; + break; + case 'br': + markdown += '\n'; + break; + case 'a': + const href = node.getAttribute('href'); + const text = node.textContent.trim(); + if (href) { + markdown += `[${text}](${href}) `; + } + break; + case 'img': + const src = node.getAttribute('src'); + const alt = node.getAttribute('alt') || ''; + if (src) { + markdown += `![${alt}](${src}) `; + } + break; + case 'ul': + case 'ol': + markdown += '\n'; + listStack.push(node.tagName.toLowerCase()); + break; + case 'li': + const listType = listStack[listStack.length - 1]; + const prefix = listType === 'ol' ? '1. ' : '- '; + markdown += prefix + node.textContent.trim() + '\n'; + break; + case 'code': + markdown += '`' + node.textContent + '`'; + break; + case 'pre': + markdown += '\n```\n' + node.textContent + '\n```\n'; + break; + case 'blockquote': + markdown += '\n> ' + node.textContent.trim() + '\n'; + break; + case 'strong': + case 'b': + markdown += '**' + node.textContent + '**'; + break; + case 'em': + case 'i': + markdown += '*' + node.textContent + '*'; + break; + } + } + } + + // Limit markdown length + const maxLength = 30000; + if (markdown.length > maxLength) { + markdown = markdown.substring(0, maxLength) + '...'; + } + + return markdown.trim(); +} + +// Check if link is external +function isExternalLink(url) { + try { + const link = new URL(url); + return link.hostname !== window.location.hostname; + } catch { + return false; + } +} + +// Get first paint time +function getFirstPaintTime() { + if (window.performance && window.performance.getEntriesByType) { + const paintEntries = window.performance.getEntriesByType('paint'); + const firstPaint = paintEntries.find(entry => entry.name === 'first-paint'); + return firstPaint ? firstPaint.startTime : null; + } + return null; +} + +// Console log interceptor +const consoleLogs = []; +const originalConsole = { + log: console.log, + error: console.error, + warn: console.warn, + info: console.info +}; + +// Intercept console methods +['log', 'error', 'warn', 'info'].forEach(method => { + console[method] = function(...args) { + // Store the log + consoleLogs.push({ + type: method, + message: args.map(arg => { + try { + if (typeof arg === 'object') { + return JSON.stringify(arg); + } + return String(arg); + } catch { + return String(arg); + } + }).join(' '), + timestamp: new Date().toISOString(), + stack: new Error().stack + }); + + // Keep only last 100 logs to prevent memory issues + if (consoleLogs.length > 100) { + consoleLogs.shift(); + } + + // Call original console method + originalConsole[method].apply(console, args); + }; +}); + +// Get selected text +function getSelectedText() { + return window.getSelection().toString(); +} + +// Highlight element on page +function highlightElement(selector) { + try { + const element = document.querySelector(selector); + if (element) { + // Store original style + const originalStyle = element.style.cssText; + + // Apply highlight + element.style.cssText += ` + outline: 3px solid #FF6B6B !important; + background-color: rgba(255, 107, 107, 0.1) !important; + transition: all 0.3s ease !important; + `; + + // Remove highlight after 3 seconds + setTimeout(() => { + element.style.cssText = originalStyle; + }, 3000); + + return true; + } + return false; + } catch (error) { + console.error('Failed to highlight element:', error); + return false; + } +} + +// Execute custom JavaScript in page context +function executeInPageContext(code) { + try { + const script = document.createElement('script'); + script.textContent = ` + (function() { + try { + const result = ${code}; + window.postMessage({ + type: 'QWEN_BRIDGE_RESULT', + success: true, + result: result + }, '*'); + } catch (error) { + window.postMessage({ + type: 'QWEN_BRIDGE_RESULT', + success: false, + error: error.message + }, '*'); + } + })(); + `; + document.documentElement.appendChild(script); + script.remove(); + + return new Promise((resolve, reject) => { + const listener = (event) => { + if (event.data && event.data.type === 'QWEN_BRIDGE_RESULT') { + window.removeEventListener('message', listener); + if (event.data.success) { + resolve(event.data.result); + } else { + reject(new Error(event.data.error)); + } + } + }; + window.addEventListener('message', listener); + + // Timeout after 5 seconds + setTimeout(() => { + window.removeEventListener('message', listener); + reject(new Error('Execution timeout')); + }, 5000); + }); + } catch (error) { + return Promise.reject(error); + } +} + +// Message listener for communication with background script +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + console.log('Content script received message:', request); + + switch (request.type) { + case 'EXTRACT_DATA': + // Extract and send page data + const pageData = extractPageData(); + pageData.consoleLogs = consoleLogs; + sendResponse({ + success: true, + data: pageData + }); + break; + + case 'GET_SELECTED_TEXT': + // Get currently selected text + sendResponse({ + success: true, + data: getSelectedText() + }); + break; + + case 'HIGHLIGHT_ELEMENT': + // Highlight an element on the page + const highlighted = highlightElement(request.selector); + sendResponse({ + success: highlighted + }); + break; + + case 'EXECUTE_CODE': + // Execute JavaScript in page context + executeInPageContext(request.code) + .then(result => { + sendResponse({ + success: true, + data: result + }); + }) + .catch(error => { + sendResponse({ + success: false, + error: error.message + }); + }); + return true; // Will respond asynchronously + + case 'SCROLL_TO': + // Scroll to specific position + window.scrollTo({ + top: request.y || 0, + left: request.x || 0, + behavior: request.smooth ? 'smooth' : 'auto' + }); + sendResponse({ success: true }); + break; + + case 'QWEN_EVENT': + // Handle events from Qwen CLI + console.log('Qwen event received:', request.event); + // Could trigger UI updates or other actions based on event + break; + + default: + sendResponse({ + success: false, + error: 'Unknown request type' + }); + } +}); + +// Notify background script that content script is loaded +chrome.runtime.sendMessage({ + type: 'CONTENT_SCRIPT_LOADED', + url: window.location.href +}).catch(() => { + // Ignore errors if background script is not ready +}); + +// Export for testing +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + extractPageData, + extractTextContent, + htmlToMarkdown, + getSelectedText, + highlightElement + }; +} \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/extension/icons/icon-128.png b/packages/chrome-qwen-bridge/extension/icons/icon-128.png new file mode 100644 index 00000000..3dd36217 --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/icons/icon-128.png @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/extension/icons/icon-16.png b/packages/chrome-qwen-bridge/extension/icons/icon-16.png new file mode 100644 index 00000000..3dd36217 --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/icons/icon-16.png @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/extension/icons/icon-48.png b/packages/chrome-qwen-bridge/extension/icons/icon-48.png new file mode 100644 index 00000000..3dd36217 --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/icons/icon-48.png @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/extension/icons/icon.svg b/packages/chrome-qwen-bridge/extension/icons/icon.svg new file mode 100644 index 00000000..3dd36217 --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/icons/icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/extension/manifest.json b/packages/chrome-qwen-bridge/extension/manifest.json new file mode 100644 index 00000000..5246d9db --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/manifest.json @@ -0,0 +1,58 @@ +{ + "manifest_version": 3, + "name": "Qwen CLI Bridge", + "version": "1.0.0", + "description": "Bridge between Chrome browser and Qwen CLI for enhanced AI interactions", + + "permissions": [ + "activeTab", + "tabs", + "storage", + "nativeMessaging", + "debugger", + "webNavigation", + "scripting", + "cookies", + "webRequest", + "sidePanel" + ], + + "host_permissions": [ + "" + ], + + "background": { + "service_worker": "background/service-worker.js" + }, + + "content_scripts": [ + { + "matches": [""], + "js": ["content/content-script.js"], + "run_at": "document_idle" + } + ], + + "action": { + "default_icon": { + "16": "icons/icon-16.png", + "48": "icons/icon-48.png", + "128": "icons/icon-128.png" + } + }, + + "side_panel": { + "default_path": "sidepanel/sidepanel.html" + }, + + "options_ui": { + "page": "options/options.html", + "open_in_tab": true + }, + + "icons": { + "16": "icons/icon-16.png", + "48": "icons/icon-48.png", + "128": "icons/icon-128.png" + } +} \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/extension/options/options.html b/packages/chrome-qwen-bridge/extension/options/options.html new file mode 100644 index 00000000..6d2c34df --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/options/options.html @@ -0,0 +1,217 @@ + + + + + + Qwen CLI Bridge - Options + + + +
+

⚙ Qwen CLI Bridge Settings

+

Configure your Chrome extension and Qwen CLI integration

+ +
+

🔌 Connection Settings

+ +
+ + +

Port for Qwen CLI HTTP server (default: 8080)

+
+ +
+ + +

Comma-separated list of MCP servers to load

+
+ +
+ +

Automatically connect to Qwen CLI when opening the popup

+
+
+ +
+

🎚 Display Settings

+ +
+ +

Display desktop notifications for important events

+
+ +
+ +

Show detailed debug information in console

+
+
+ +
+

ℹ Native Host Status

+

Checking...

+
+ +
+

📍 Extension ID

+

Loading...

+
+ + + ✓ Settings saved + +
+

+ Qwen CLI Bridge v1.0.0 | + GitHub | + Help +

+
+
+ + + + \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/extension/options/options.js b/packages/chrome-qwen-bridge/extension/options/options.js new file mode 100644 index 00000000..67059177 --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/options/options.js @@ -0,0 +1,80 @@ +/** + * Options page script for Qwen CLI Bridge + */ + +// Load saved settings +async function loadSettings() { + const settings = await chrome.storage.local.get([ + 'httpPort', + 'mcpServers', + 'autoConnect', + 'showNotifications', + 'debugMode' + ]); + + // Set values in form + document.getElementById('httpPort').value = settings.httpPort || 8080; + document.getElementById('mcpServers').value = settings.mcpServers || ''; + document.getElementById('autoConnect').checked = settings.autoConnect || false; + document.getElementById('showNotifications').checked = settings.showNotifications || false; + document.getElementById('debugMode').checked = settings.debugMode || false; +} + +// Save settings +document.getElementById('saveBtn').addEventListener('click', async () => { + const settings = { + httpPort: parseInt(document.getElementById('httpPort').value) || 8080, + mcpServers: document.getElementById('mcpServers').value, + autoConnect: document.getElementById('autoConnect').checked, + showNotifications: document.getElementById('showNotifications').checked, + debugMode: document.getElementById('debugMode').checked + }; + + await chrome.storage.local.set(settings); + + // Show saved status + const saveStatus = document.getElementById('saveStatus'); + saveStatus.classList.add('show'); + setTimeout(() => { + saveStatus.classList.remove('show'); + }, 2000); +}); + +// Check Native Host status +async function checkNativeHostStatus() { + try { + // Try to send a message to check if Native Host is installed + chrome.runtime.sendMessage({ type: 'GET_STATUS' }, (response) => { + if (chrome.runtime.lastError) { + document.getElementById('nativeHostStatus').textContent = + '❌ Not installed - Please run install script'; + } else if (response && response.connected) { + document.getElementById('nativeHostStatus').textContent = + '✅ Connected and running'; + } else { + document.getElementById('nativeHostStatus').textContent = + '⚠ Installed but not connected'; + } + }); + } catch (error) { + document.getElementById('nativeHostStatus').textContent = + '❌ Error checking status'; + } +} + +// Show extension ID +document.getElementById('extensionId').textContent = chrome.runtime.id; + +// Help link +document.getElementById('helpLink').addEventListener('click', (e) => { + e.preventDefault(); + chrome.tabs.create({ + url: 'https://github.com/QwenLM/qwen-code/tree/main/packages/chrome-qwen-bridge' + }); +}); + +// Initialize +document.addEventListener('DOMContentLoaded', () => { + loadSettings(); + checkNativeHostStatus(); +}); \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/extension/popup/popup.css b/packages/chrome-qwen-bridge/extension/popup/popup.css new file mode 100644 index 00000000..6f3cbd4f --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/popup/popup.css @@ -0,0 +1,385 @@ +/* Popup Styles for Qwen CLI Bridge */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + width: 400px; + min-height: 500px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + font-size: 14px; + line-height: 1.5; + color: #333; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.container { + background: white; + min-height: 500px; + display: flex; + flex-direction: column; +} + +/* Header */ +.header { + padding: 16px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo { + display: flex; + align-items: center; + gap: 8px; +} + +.logo .icon { + width: 24px; + height: 24px; +} + +.logo h1 { + font-size: 18px; + font-weight: 600; +} + +/* Status Indicator */ +.status-indicator { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 12px; + background: rgba(255, 255, 255, 0.2); + border-radius: 12px; + font-size: 12px; +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #ff4444; + animation: pulse 2s infinite; +} + +.status-indicator.connected .status-dot { + background: #44ff44; +} + +.status-indicator.connecting .status-dot { + background: #ffaa44; +} + +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +/* Sections */ +.section { + padding: 16px; + border-bottom: 1px solid #e5e5e5; +} + +.section:last-of-type { + border-bottom: none; +} + +.section h2 { + font-size: 14px; + font-weight: 600; + color: #666; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 12px; +} + +/* Buttons */ +.btn { + padding: 8px 16px; + border: none; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; +} + +.btn-primary:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); +} + +.btn-secondary { + background: #f5f5f5; + color: #333; +} + +.btn-secondary:hover:not(:disabled) { + background: #e5e5e5; +} + +.btn-small { + padding: 4px 12px; + font-size: 12px; +} + +.btn-icon { + width: 32px; + height: 32px; + padding: 4px; + border: none; + background: transparent; + cursor: pointer; + border-radius: 4px; + transition: background 0.2s; +} + +.btn-icon:hover { + background: #f5f5f5; +} + +.btn-icon svg { + width: 100%; + height: 100%; + stroke: #666; +} + +/* Connection Section */ +.connection-controls { + display: flex; + gap: 8px; +} + +.error-message { + margin-top: 8px; + padding: 8px; + background: #fee; + color: #c00; + border-radius: 4px; + font-size: 12px; +} + +/* Action Buttons */ +.action-buttons { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; +} + +.action-btn { + padding: 12px; + border: 1px solid #e5e5e5; + border-radius: 8px; + background: white; + cursor: pointer; + transition: all 0.2s; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + font-size: 12px; + color: #666; +} + +.action-btn:hover:not(:disabled) { + border-color: #667eea; + background: #f8f9ff; + color: #667eea; +} + +.action-btn:hover:not(:disabled) .action-icon { + stroke: #667eea; +} + +.action-btn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.action-icon { + width: 24px; + height: 24px; + stroke: #999; +} + +/* Response Section */ +.response-container { + background: #f9f9f9; + border-radius: 8px; + overflow: hidden; +} + +.response-header { + padding: 8px 12px; + background: #f0f0f0; + border-bottom: 1px solid #e0e0e0; + display: flex; + justify-content: space-between; + align-items: center; +} + +.response-type { + font-size: 12px; + font-weight: 600; + color: #666; +} + +.response-content { + padding: 12px; + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 12px; + color: #333; + max-height: 200px; + overflow-y: auto; + white-space: pre-wrap; + word-break: break-all; +} + +/* Settings Section */ +.settings-section details { + cursor: pointer; +} + +.settings-section summary { + font-size: 14px; + font-weight: 600; + color: #666; + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 4px 0; + user-select: none; +} + +.settings-section summary:hover { + color: #667eea; +} + +.settings-content { + margin-top: 12px; +} + +.setting-item { + margin-bottom: 12px; +} + +.setting-item label { + display: block; + margin-bottom: 4px; + font-size: 12px; + color: #666; +} + +.setting-item input[type="text"], +.setting-item input[type="number"] { + width: 100%; + padding: 6px 8px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.setting-item input[type="checkbox"] { + margin-right: 8px; +} + +/* Footer */ +.footer { + padding: 12px 16px; + background: #f9f9f9; + border-top: 1px solid #e5e5e5; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-size: 12px; + color: #999; + margin-top: auto; +} + +.footer a { + color: #667eea; + text-decoration: none; +} + +.footer a:hover { + text-decoration: underline; +} + +.version { + color: #bbb; +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; +} + +::-webkit-scrollbar-thumb { + background: #888; + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: #555; +} + +/* Loading state */ +.loading { + position: relative; + pointer-events: none; +} + +.loading::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.8); + display: flex; + align-items: center; + justify-content: center; +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.fade-in { + animation: fadeIn 0.3s ease; +} \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/extension/popup/popup.html b/packages/chrome-qwen-bridge/extension/popup/popup.html new file mode 100644 index 00000000..fb48462a --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/popup/popup.html @@ -0,0 +1,140 @@ + + + + + + Qwen CLI Bridge + + + +
+ +
+ +
+ + Disconnected +
+
+ + +
+

Connection

+
+ + +
+ +
+ + +
+

Quick Actions

+
+ + + + + + + + + + + +
+
+ + + + + +
+
+ Advanced Settings +
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+ + + +
+ + + + \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/extension/popup/popup.js b/packages/chrome-qwen-bridge/extension/popup/popup.js new file mode 100644 index 00000000..2a799c55 --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/popup/popup.js @@ -0,0 +1,477 @@ +/** + * Popup Script for Qwen CLI Bridge + * Handles UI interactions and communication with background script + */ + +// UI Elements +const statusIndicator = document.getElementById('statusIndicator'); +const statusText = statusIndicator.querySelector('.status-text'); +const connectBtn = document.getElementById('connectBtn'); +const startQwenBtn = document.getElementById('startQwenBtn'); +const connectionError = document.getElementById('connectionError'); +const responseSection = document.getElementById('responseSection'); +const responseType = document.getElementById('responseType'); +const responseContent = document.getElementById('responseContent'); +const copyResponseBtn = document.getElementById('copyResponseBtn'); + +// Action buttons +const extractDataBtn = document.getElementById('extractDataBtn'); +const captureScreenBtn = document.getElementById('captureScreenBtn'); +const analyzePageBtn = document.getElementById('analyzePageBtn'); +const getSelectedBtn = document.getElementById('getSelectedBtn'); +const networkLogsBtn = document.getElementById('networkLogsBtn'); +const consoleLogsBtn = document.getElementById('consoleLogsBtn'); + +// Settings +const mcpServersInput = document.getElementById('mcpServers'); +const httpPortInput = document.getElementById('httpPort'); +const autoConnectCheckbox = document.getElementById('autoConnect'); +const saveSettingsBtn = document.getElementById('saveSettingsBtn'); + +// Footer links +const openOptionsBtn = document.getElementById('openOptionsBtn'); +const helpBtn = document.getElementById('helpBtn'); + +// State +let isConnected = false; +let qwenStatus = 'disconnected'; + +// Initialize popup +document.addEventListener('DOMContentLoaded', async () => { + await loadSettings(); + await checkConnectionStatus(); + + // Auto-connect if enabled + const settings = await chrome.storage.local.get(['autoConnect']); + if (settings.autoConnect && !isConnected) { + connectToQwen(); + } +}); + +// Load saved settings +async function loadSettings() { + const settings = await chrome.storage.local.get([ + 'mcpServers', + 'httpPort', + 'autoConnect' + ]); + + if (settings.mcpServers) { + mcpServersInput.value = settings.mcpServers; + } + if (settings.httpPort) { + httpPortInput.value = settings.httpPort; + } + if (settings.autoConnect !== undefined) { + autoConnectCheckbox.checked = settings.autoConnect; + } +} + +// Save settings +saveSettingsBtn.addEventListener('click', async () => { + await chrome.storage.local.set({ + mcpServers: mcpServersInput.value, + httpPort: parseInt(httpPortInput.value) || 8080, + autoConnect: autoConnectCheckbox.checked + }); + + saveSettingsBtn.textContent = 'Saved!'; + setTimeout(() => { + saveSettingsBtn.textContent = 'Save Settings'; + }, 2000); +}); + +// Check connection status +async function checkConnectionStatus() { + try { + const response = await chrome.runtime.sendMessage({ type: 'GET_STATUS' }); + updateConnectionStatus(response.connected, response.status); + } catch (error) { + updateConnectionStatus(false, 'disconnected'); + } +} + +// Update UI based on connection status +function updateConnectionStatus(connected, status) { + isConnected = connected; + qwenStatus = status; + + // Update status indicator + statusIndicator.classList.toggle('connected', connected); + statusIndicator.classList.toggle('connecting', status === 'connecting'); + statusText.textContent = getStatusText(status); + + // Update button states + connectBtn.textContent = connected ? 'Disconnect' : 'Connect to Qwen CLI'; + connectBtn.classList.toggle('btn-danger', connected); + + startQwenBtn.disabled = !connected || status === 'running'; + + // Enable/disable action buttons + const actionButtons = [ + extractDataBtn, + captureScreenBtn, + analyzePageBtn, + getSelectedBtn, + networkLogsBtn, + consoleLogsBtn + ]; + + actionButtons.forEach(btn => { + btn.disabled = !connected || status !== 'running'; + }); +} + +// Get human-readable status text +function getStatusText(status) { + switch (status) { + case 'connected': + return 'Connected'; + case 'running': + return 'Qwen CLI Running'; + case 'connecting': + return 'Connecting...'; + case 'disconnected': + return 'Disconnected'; + case 'stopped': + return 'Qwen CLI Stopped'; + default: + return 'Unknown'; + } +} + +// Connect button handler +connectBtn.addEventListener('click', () => { + if (isConnected) { + disconnectFromQwen(); + } else { + connectToQwen(); + } +}); + +// Connect to Qwen CLI +async function connectToQwen() { + updateConnectionStatus(false, 'connecting'); + connectionError.style.display = 'none'; + + try { + const response = await chrome.runtime.sendMessage({ type: 'CONNECT' }); + + if (response.success) { + updateConnectionStatus(true, response.status); + } else { + throw new Error(response.error || 'Connection failed'); + } + } catch (error) { + console.error('Connection error:', error); + connectionError.textContent = `Error: ${error.message}`; + connectionError.style.display = 'block'; + updateConnectionStatus(false, 'disconnected'); + } +} + +// Disconnect from Qwen CLI +function disconnectFromQwen() { + // Simply close the popup to disconnect + // The native port will be closed when the extension unloads + updateConnectionStatus(false, 'disconnected'); + window.close(); +} + +// Start Qwen CLI button handler +startQwenBtn.addEventListener('click', async () => { + startQwenBtn.disabled = true; + startQwenBtn.textContent = 'Starting...'; + + try { + const settings = await chrome.storage.local.get(['mcpServers', 'httpPort']); + const response = await chrome.runtime.sendMessage({ + type: 'START_QWEN_CLI', + config: { + mcpServers: settings.mcpServers ? settings.mcpServers.split(',').map(s => s.trim()) : [], + httpPort: settings.httpPort || 8080 + } + }); + + if (response.success) { + updateConnectionStatus(true, 'running'); + showResponse('Qwen CLI Started', response.data || 'Successfully started'); + } else { + throw new Error(response.error || 'Failed to start Qwen CLI'); + } + } catch (error) { + console.error('Start error:', error); + connectionError.textContent = `Error: ${error.message}`; + connectionError.style.display = 'block'; + } finally { + startQwenBtn.textContent = 'Start Qwen CLI'; + } +}); + +// Extract page data button handler +extractDataBtn.addEventListener('click', async () => { + try { + showLoading('Extracting page data...'); + + const response = await chrome.runtime.sendMessage({ + type: 'EXTRACT_PAGE_DATA' + }); + + if (response.success) { + // Send to Qwen CLI + const qwenResponse = await chrome.runtime.sendMessage({ + type: 'SEND_TO_QWEN', + action: 'analyze_page', + data: response.data + }); + + if (qwenResponse.success) { + showResponse('Page Analysis', qwenResponse.data); + } else { + throw new Error(qwenResponse.error); + } + } else { + throw new Error(response.error); + } + } catch (error) { + showError(`Failed to extract data: ${error.message}`); + } +}); + +// Capture screenshot button handler +captureScreenBtn.addEventListener('click', async () => { + try { + showLoading('Capturing screenshot...'); + + const response = await chrome.runtime.sendMessage({ + type: 'CAPTURE_SCREENSHOT' + }); + + if (response.success) { + // Send to Qwen CLI + const qwenResponse = await chrome.runtime.sendMessage({ + type: 'SEND_TO_QWEN', + action: 'analyze_screenshot', + data: { + screenshot: response.data, + url: (await chrome.tabs.query({ active: true, currentWindow: true }))[0].url + } + }); + + if (qwenResponse.success) { + showResponse('Screenshot Analysis', qwenResponse.data); + } else { + throw new Error(qwenResponse.error); + } + } else { + throw new Error(response.error); + } + } catch (error) { + showError(`Failed to capture screenshot: ${error.message}`); + } +}); + +// Analyze page with AI button handler +analyzePageBtn.addEventListener('click', async () => { + try { + showLoading('Analyzing page with AI...'); + + // First extract page data + const extractResponse = await chrome.runtime.sendMessage({ + type: 'EXTRACT_PAGE_DATA' + }); + + if (!extractResponse.success) { + throw new Error(extractResponse.error); + } + + // Send to Qwen for AI analysis + const qwenResponse = await chrome.runtime.sendMessage({ + type: 'SEND_TO_QWEN', + action: 'ai_analyze', + data: { + pageData: extractResponse.data, + prompt: 'Please analyze this webpage and provide insights about its content, purpose, and any notable features.' + } + }); + + if (qwenResponse.success) { + showResponse('AI Analysis', qwenResponse.data); + } else { + throw new Error(qwenResponse.error); + } + } catch (error) { + showError(`Analysis failed: ${error.message}`); + } +}); + +// Get selected text button handler +getSelectedBtn.addEventListener('click', async () => { + try { + showLoading('Getting selected text...'); + + // Get active tab + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); + const tab = tabs[0]; + + if (!tab) { + throw new Error('No active tab found'); + } + + // Get selected text from content script + const response = await chrome.tabs.sendMessage(tab.id, { + type: 'GET_SELECTED_TEXT' + }); + + if (response.success && response.data) { + // Send to Qwen CLI + const qwenResponse = await chrome.runtime.sendMessage({ + type: 'SEND_TO_QWEN', + action: 'process_text', + data: { + text: response.data, + context: 'selected_text' + } + }); + + if (qwenResponse.success) { + showResponse('Selected Text Processed', qwenResponse.data); + } else { + throw new Error(qwenResponse.error); + } + } else { + showError('No text selected. Please select some text on the page first.'); + } + } catch (error) { + showError(`Failed to process selected text: ${error.message}`); + } +}); + +// Network logs button handler +networkLogsBtn.addEventListener('click', async () => { + try { + showLoading('Getting network logs...'); + + const response = await chrome.runtime.sendMessage({ + type: 'GET_NETWORK_LOGS' + }); + + if (response.success) { + showResponse('Network Logs', JSON.stringify(response.data, null, 2)); + } else { + throw new Error(response.error); + } + } catch (error) { + showError(`Failed to get network logs: ${error.message}`); + } +}); + +// Console logs button handler +consoleLogsBtn.addEventListener('click', async () => { + try { + showLoading('Getting console logs...'); + + // Get active tab + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); + const tab = tabs[0]; + + if (!tab) { + throw new Error('No active tab found'); + } + + // Get console logs from content script + const response = await chrome.tabs.sendMessage(tab.id, { + type: 'EXTRACT_DATA' + }); + + if (response.success) { + const consoleLogs = response.data.consoleLogs || []; + if (consoleLogs.length > 0) { + showResponse('Console Logs', JSON.stringify(consoleLogs, null, 2)); + } else { + showResponse('Console Logs', 'No console logs captured'); + } + } else { + throw new Error(response.error); + } + } catch (error) { + showError(`Failed to get console logs: ${error.message}`); + } +}); + +// Copy response button handler +copyResponseBtn.addEventListener('click', () => { + const text = responseContent.textContent; + navigator.clipboard.writeText(text).then(() => { + const originalTitle = copyResponseBtn.title; + copyResponseBtn.title = 'Copied!'; + setTimeout(() => { + copyResponseBtn.title = originalTitle; + }, 2000); + }); +}); + +// Footer link handlers +openOptionsBtn.addEventListener('click', (e) => { + e.preventDefault(); + // Use try-catch to handle potential errors + try { + chrome.runtime.openOptionsPage(() => { + if (chrome.runtime.lastError) { + // If opening options page fails, open it in a new tab as fallback + console.error('Error opening options page:', chrome.runtime.lastError); + chrome.tabs.create({ + url: chrome.runtime.getURL('options/options.html') + }); + } + }); + } catch (error) { + console.error('Failed to open options page:', error); + // Fallback: open in new tab + chrome.tabs.create({ + url: chrome.runtime.getURL('options/options.html') + }); + } +}); + +helpBtn.addEventListener('click', (e) => { + e.preventDefault(); + chrome.tabs.create({ + url: 'https://github.com/QwenLM/qwen-code/tree/main/packages/chrome-qwen-bridge' + }); +}); + +// Helper functions +function showLoading(message) { + responseSection.style.display = 'block'; + responseType.textContent = 'Loading'; + responseContent.textContent = message; + responseSection.classList.add('loading'); +} + +function showResponse(type, content) { + responseSection.style.display = 'block'; + responseType.textContent = type; + responseContent.textContent = typeof content === 'string' ? content : JSON.stringify(content, null, 2); + responseSection.classList.remove('loading'); + responseSection.classList.add('fade-in'); +} + +function showError(message) { + responseSection.style.display = 'block'; + responseType.textContent = 'Error'; + responseType.style.color = '#c00'; + responseContent.textContent = message; + responseSection.classList.remove('loading'); +} + +// Listen for status updates from background +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === 'STATUS_UPDATE') { + updateConnectionStatus(message.status !== 'disconnected', message.status); + } else if (message.type === 'QWEN_EVENT') { + // Handle events from Qwen CLI + console.log('Qwen event received:', message.event); + // Could update UI based on event + } +}); \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/extension/sidepanel/sidepanel.css b/packages/chrome-qwen-bridge/extension/sidepanel/sidepanel.css new file mode 100644 index 00000000..e28b8b25 --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/sidepanel/sidepanel.css @@ -0,0 +1,402 @@ +/* Side Panel Styles for Qwen CLI Bridge */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + height: 100%; + width: 100%; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + font-size: 14px; + line-height: 1.5; + color: #333; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.container { + background: white; + min-height: 100%; + display: flex; + flex-direction: column; +} + +/* Header */ +.header { + padding: 16px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + display: flex; + justify-content: space-between; + align-items: center; + position: sticky; + top: 0; + z-index: 100; +} + +.logo { + display: flex; + align-items: center; + gap: 8px; +} + +.logo .icon { + width: 24px; + height: 24px; +} + +.logo h1 { + font-size: 18px; + font-weight: 600; +} + +/* Status Indicator */ +.status-indicator { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 12px; + background: rgba(255, 255, 255, 0.2); + border-radius: 12px; + font-size: 12px; +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #ff4444; + animation: pulse 2s infinite; +} + +.status-indicator.connected .status-dot { + background: #44ff44; +} + +.status-indicator.connecting .status-dot { + background: #ffaa44; +} + +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +/* Sections */ +.section { + padding: 16px; + border-bottom: 1px solid #e5e5e5; +} + +.section:last-of-type { + border-bottom: none; +} + +.section h2 { + font-size: 14px; + font-weight: 600; + color: #666; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 12px; +} + +/* Buttons */ +.btn { + padding: 8px 16px; + border: none; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; +} + +.btn-primary:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); +} + +.btn-secondary { + background: #f5f5f5; + color: #333; +} + +.btn-secondary:hover:not(:disabled) { + background: #e5e5e5; +} + +.btn-small { + padding: 4px 12px; + font-size: 12px; +} + +.btn-icon { + width: 32px; + height: 32px; + padding: 4px; + border: none; + background: transparent; + cursor: pointer; + border-radius: 4px; + transition: background 0.2s; +} + +.btn-icon:hover { + background: #f5f5f5; +} + +.btn-icon svg { + width: 100%; + height: 100%; + stroke: #666; +} + +/* Connection Section */ +.connection-controls { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.error-message { + margin-top: 8px; + padding: 8px; + background: #fee; + color: #c00; + border-radius: 4px; + font-size: 12px; +} + +/* Action Buttons */ +.action-buttons { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; +} + +.action-btn { + padding: 12px; + border: 1px solid #e5e5e5; + border-radius: 8px; + background: white; + cursor: pointer; + transition: all 0.2s; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + font-size: 12px; + color: #666; +} + +.action-btn:hover:not(:disabled) { + border-color: #667eea; + background: #f8f9ff; + color: #667eea; +} + +.action-btn:hover:not(:disabled) .action-icon { + stroke: #667eea; +} + +.action-btn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.action-icon { + width: 24px; + height: 24px; + stroke: #999; +} + +/* Response Section */ +.response-section { + flex: 1; + display: flex; + flex-direction: column; +} + +.response-container { + background: #f9f9f9; + border-radius: 8px; + overflow: hidden; + flex: 1; + display: flex; + flex-direction: column; +} + +.response-header { + padding: 8px 12px; + background: #f0f0f0; + border-bottom: 1px solid #e0e0e0; + display: flex; + justify-content: space-between; + align-items: center; +} + +.response-type { + font-size: 12px; + font-weight: 600; + color: #666; +} + +.response-content { + padding: 12px; + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 12px; + color: #333; + flex: 1; + overflow-y: auto; + white-space: pre-wrap; + word-break: break-all; + max-height: 400px; +} + +/* Settings Section */ +.settings-section details { + cursor: pointer; +} + +.settings-section summary { + font-size: 14px; + font-weight: 600; + color: #666; + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 4px 0; + user-select: none; +} + +.settings-section summary:hover { + color: #667eea; +} + +.settings-content { + margin-top: 12px; +} + +.setting-item { + margin-bottom: 12px; +} + +.setting-item label { + display: block; + margin-bottom: 4px; + font-size: 12px; + color: #666; +} + +.setting-item input[type="text"], +.setting-item input[type="number"] { + width: 100%; + padding: 6px 8px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.setting-item input[type="checkbox"] { + margin-right: 8px; +} + +/* Footer */ +.footer { + padding: 12px 16px; + background: #f9f9f9; + border-top: 1px solid #e5e5e5; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-size: 12px; + color: #999; + margin-top: auto; +} + +.footer a { + color: #667eea; + text-decoration: none; +} + +.footer a:hover { + text-decoration: underline; +} + +.version { + color: #bbb; +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; +} + +::-webkit-scrollbar-thumb { + background: #888; + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: #555; +} + +/* Loading state */ +.loading { + position: relative; + pointer-events: none; +} + +.loading::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.8); + display: flex; + align-items: center; + justify-content: center; +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.fade-in { + animation: fadeIn 0.3s ease; +} diff --git a/packages/chrome-qwen-bridge/extension/sidepanel/sidepanel.html b/packages/chrome-qwen-bridge/extension/sidepanel/sidepanel.html new file mode 100644 index 00000000..902b7efb --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/sidepanel/sidepanel.html @@ -0,0 +1,140 @@ + + + + + + Qwen CLI Bridge + + + +
+ +
+ +
+ + Disconnected +
+
+ + +
+

Connection

+
+ + +
+ +
+ + +
+

Quick Actions

+
+ + + + + + + + + + + +
+
+ + + + + +
+
+ Advanced Settings +
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+ + + +
+ + + + diff --git a/packages/chrome-qwen-bridge/extension/sidepanel/sidepanel.js b/packages/chrome-qwen-bridge/extension/sidepanel/sidepanel.js new file mode 100644 index 00000000..d5ffb1c8 --- /dev/null +++ b/packages/chrome-qwen-bridge/extension/sidepanel/sidepanel.js @@ -0,0 +1,480 @@ +/** + * Side Panel Script for Qwen CLI Bridge + * Handles UI interactions and communication with background script + */ + +// UI Elements +const statusIndicator = document.getElementById('statusIndicator'); +const statusText = statusIndicator.querySelector('.status-text'); +const connectBtn = document.getElementById('connectBtn'); +const startQwenBtn = document.getElementById('startQwenBtn'); +const connectionError = document.getElementById('connectionError'); +const responseSection = document.getElementById('responseSection'); +const responseType = document.getElementById('responseType'); +const responseContent = document.getElementById('responseContent'); +const copyResponseBtn = document.getElementById('copyResponseBtn'); + +// Action buttons +const extractDataBtn = document.getElementById('extractDataBtn'); +const captureScreenBtn = document.getElementById('captureScreenBtn'); +const analyzePageBtn = document.getElementById('analyzePageBtn'); +const getSelectedBtn = document.getElementById('getSelectedBtn'); +const networkLogsBtn = document.getElementById('networkLogsBtn'); +const consoleLogsBtn = document.getElementById('consoleLogsBtn'); + +// Settings +const mcpServersInput = document.getElementById('mcpServers'); +const httpPortInput = document.getElementById('httpPort'); +const autoConnectCheckbox = document.getElementById('autoConnect'); +const saveSettingsBtn = document.getElementById('saveSettingsBtn'); + +// Footer links +const openOptionsBtn = document.getElementById('openOptionsBtn'); +const helpBtn = document.getElementById('helpBtn'); + +// State +let isConnected = false; +let qwenStatus = 'disconnected'; + +// Initialize side panel +document.addEventListener('DOMContentLoaded', async () => { + await loadSettings(); + await checkConnectionStatus(); + + // Auto-connect if enabled + const settings = await chrome.storage.local.get(['autoConnect']); + if (settings.autoConnect && !isConnected) { + connectToQwen(); + } +}); + +// Load saved settings +async function loadSettings() { + const settings = await chrome.storage.local.get([ + 'mcpServers', + 'httpPort', + 'autoConnect' + ]); + + if (settings.mcpServers) { + mcpServersInput.value = settings.mcpServers; + } + if (settings.httpPort) { + httpPortInput.value = settings.httpPort; + } + if (settings.autoConnect !== undefined) { + autoConnectCheckbox.checked = settings.autoConnect; + } +} + +// Save settings +saveSettingsBtn.addEventListener('click', async () => { + await chrome.storage.local.set({ + mcpServers: mcpServersInput.value, + httpPort: parseInt(httpPortInput.value) || 8080, + autoConnect: autoConnectCheckbox.checked + }); + + saveSettingsBtn.textContent = 'Saved!'; + setTimeout(() => { + saveSettingsBtn.textContent = 'Save Settings'; + }, 2000); +}); + +// Check connection status +async function checkConnectionStatus() { + try { + const response = await chrome.runtime.sendMessage({ type: 'GET_STATUS' }); + updateConnectionStatus(response.connected, response.status); + } catch (error) { + updateConnectionStatus(false, 'disconnected'); + } +} + +// Update UI based on connection status +function updateConnectionStatus(connected, status) { + isConnected = connected; + qwenStatus = status; + + // Update status indicator + statusIndicator.classList.toggle('connected', connected); + statusIndicator.classList.toggle('connecting', status === 'connecting'); + statusText.textContent = getStatusText(status); + + // Update button states + connectBtn.textContent = connected ? 'Disconnect' : 'Connect to Qwen CLI'; + connectBtn.classList.toggle('btn-danger', connected); + + startQwenBtn.disabled = !connected || status === 'running'; + + // Enable/disable action buttons + const actionButtons = [ + extractDataBtn, + captureScreenBtn, + analyzePageBtn, + getSelectedBtn, + networkLogsBtn, + consoleLogsBtn + ]; + + actionButtons.forEach(btn => { + btn.disabled = !connected || status !== 'running'; + }); +} + +// Get human-readable status text +function getStatusText(status) { + switch (status) { + case 'connected': + return 'Connected'; + case 'running': + return 'Qwen CLI Running'; + case 'connecting': + return 'Connecting...'; + case 'disconnected': + return 'Disconnected'; + case 'stopped': + return 'Qwen CLI Stopped'; + default: + return 'Unknown'; + } +} + +// Connect button handler +connectBtn.addEventListener('click', () => { + if (isConnected) { + disconnectFromQwen(); + } else { + connectToQwen(); + } +}); + +// Connect to Qwen CLI +async function connectToQwen() { + updateConnectionStatus(false, 'connecting'); + connectionError.style.display = 'none'; + + try { + const response = await chrome.runtime.sendMessage({ type: 'CONNECT' }); + + if (response.success) { + updateConnectionStatus(true, response.status); + } else { + throw new Error(response.error || 'Connection failed'); + } + } catch (error) { + console.error('Connection error:', error); + connectionError.textContent = `Error: ${error.message}`; + connectionError.style.display = 'block'; + updateConnectionStatus(false, 'disconnected'); + } +} + +// Disconnect from Qwen CLI +async function disconnectFromQwen() { + try { + await chrome.runtime.sendMessage({ type: 'DISCONNECT' }); + } catch (error) { + console.error('Disconnect error:', error); + } + updateConnectionStatus(false, 'disconnected'); +} + +// Start Qwen CLI button handler +startQwenBtn.addEventListener('click', async () => { + startQwenBtn.disabled = true; + startQwenBtn.textContent = 'Starting...'; + + try { + const settings = await chrome.storage.local.get(['mcpServers', 'httpPort']); + const response = await chrome.runtime.sendMessage({ + type: 'START_QWEN_CLI', + config: { + mcpServers: settings.mcpServers ? settings.mcpServers.split(',').map(s => s.trim()) : [], + httpPort: settings.httpPort || 8080 + } + }); + + if (response.success) { + updateConnectionStatus(true, 'running'); + showResponse('Qwen CLI Started', response.data || 'Successfully started'); + } else { + throw new Error(response.error || 'Failed to start Qwen CLI'); + } + } catch (error) { + console.error('Start error:', error); + connectionError.textContent = `Error: ${error.message}`; + connectionError.style.display = 'block'; + } finally { + startQwenBtn.textContent = 'Start Qwen CLI'; + } +}); + +// Extract page data button handler +extractDataBtn.addEventListener('click', async () => { + try { + showLoading('Extracting page data...'); + + const response = await chrome.runtime.sendMessage({ + type: 'EXTRACT_PAGE_DATA' + }); + + if (response.success) { + // Send to Qwen CLI + const qwenResponse = await chrome.runtime.sendMessage({ + type: 'SEND_TO_QWEN', + action: 'analyze_page', + data: response.data + }); + + if (qwenResponse.success) { + showResponse('Page Analysis', qwenResponse.data); + } else { + throw new Error(qwenResponse.error); + } + } else { + throw new Error(response.error); + } + } catch (error) { + showError(`Failed to extract data: ${error.message}`); + } +}); + +// Capture screenshot button handler +captureScreenBtn.addEventListener('click', async () => { + try { + showLoading('Capturing screenshot...'); + + const response = await chrome.runtime.sendMessage({ + type: 'CAPTURE_SCREENSHOT' + }); + + if (response.success) { + // Send to Qwen CLI + const qwenResponse = await chrome.runtime.sendMessage({ + type: 'SEND_TO_QWEN', + action: 'analyze_screenshot', + data: { + screenshot: response.data, + url: (await chrome.tabs.query({ active: true, currentWindow: true }))[0].url + } + }); + + if (qwenResponse.success) { + showResponse('Screenshot Analysis', qwenResponse.data); + } else { + throw new Error(qwenResponse.error); + } + } else { + throw new Error(response.error); + } + } catch (error) { + showError(`Failed to capture screenshot: ${error.message}`); + } +}); + +// Analyze page with AI button handler +analyzePageBtn.addEventListener('click', async () => { + try { + showLoading('Analyzing page with AI...'); + + // First extract page data + const extractResponse = await chrome.runtime.sendMessage({ + type: 'EXTRACT_PAGE_DATA' + }); + + if (!extractResponse.success) { + throw new Error(extractResponse.error); + } + + // Send to Qwen for AI analysis + const qwenResponse = await chrome.runtime.sendMessage({ + type: 'SEND_TO_QWEN', + action: 'ai_analyze', + data: { + pageData: extractResponse.data, + prompt: 'Please analyze this webpage and provide insights about its content, purpose, and any notable features.' + } + }); + + if (qwenResponse.success) { + showResponse('AI Analysis', qwenResponse.data); + } else { + throw new Error(qwenResponse.error); + } + } catch (error) { + showError(`Analysis failed: ${error.message}`); + } +}); + +// Get selected text button handler +getSelectedBtn.addEventListener('click', async () => { + try { + showLoading('Getting selected text...'); + + // Get active tab + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); + const tab = tabs[0]; + + if (!tab) { + throw new Error('No active tab found'); + } + + // Get selected text from content script + const response = await chrome.tabs.sendMessage(tab.id, { + type: 'GET_SELECTED_TEXT' + }); + + if (response.success && response.data) { + // Send to Qwen CLI + const qwenResponse = await chrome.runtime.sendMessage({ + type: 'SEND_TO_QWEN', + action: 'process_text', + data: { + text: response.data, + context: 'selected_text' + } + }); + + if (qwenResponse.success) { + showResponse('Selected Text Processed', qwenResponse.data); + } else { + throw new Error(qwenResponse.error); + } + } else { + showError('No text selected. Please select some text on the page first.'); + } + } catch (error) { + showError(`Failed to process selected text: ${error.message}`); + } +}); + +// Network logs button handler +networkLogsBtn.addEventListener('click', async () => { + try { + showLoading('Getting network logs...'); + + const response = await chrome.runtime.sendMessage({ + type: 'GET_NETWORK_LOGS' + }); + + if (response.success) { + showResponse('Network Logs', JSON.stringify(response.data, null, 2)); + } else { + throw new Error(response.error); + } + } catch (error) { + showError(`Failed to get network logs: ${error.message}`); + } +}); + +// Console logs button handler +consoleLogsBtn.addEventListener('click', async () => { + try { + showLoading('Getting console logs...'); + + // Get active tab + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); + const tab = tabs[0]; + + if (!tab) { + throw new Error('No active tab found'); + } + + // Get console logs from content script + const response = await chrome.tabs.sendMessage(tab.id, { + type: 'EXTRACT_DATA' + }); + + if (response.success) { + const consoleLogs = response.data.consoleLogs || []; + if (consoleLogs.length > 0) { + showResponse('Console Logs', JSON.stringify(consoleLogs, null, 2)); + } else { + showResponse('Console Logs', 'No console logs captured'); + } + } else { + throw new Error(response.error); + } + } catch (error) { + showError(`Failed to get console logs: ${error.message}`); + } +}); + +// Copy response button handler +copyResponseBtn.addEventListener('click', () => { + const text = responseContent.textContent; + navigator.clipboard.writeText(text).then(() => { + const originalTitle = copyResponseBtn.title; + copyResponseBtn.title = 'Copied!'; + setTimeout(() => { + copyResponseBtn.title = originalTitle; + }, 2000); + }); +}); + +// Footer link handlers +openOptionsBtn.addEventListener('click', (e) => { + e.preventDefault(); + // Use try-catch to handle potential errors + try { + chrome.runtime.openOptionsPage(() => { + if (chrome.runtime.lastError) { + // If opening options page fails, open it in a new tab as fallback + console.error('Error opening options page:', chrome.runtime.lastError); + chrome.tabs.create({ + url: chrome.runtime.getURL('options/options.html') + }); + } + }); + } catch (error) { + console.error('Failed to open options page:', error); + // Fallback: open in new tab + chrome.tabs.create({ + url: chrome.runtime.getURL('options/options.html') + }); + } +}); + +helpBtn.addEventListener('click', (e) => { + e.preventDefault(); + chrome.tabs.create({ + url: 'https://github.com/QwenLM/qwen-code/tree/main/packages/chrome-qwen-bridge' + }); +}); + +// Helper functions +function showLoading(message) { + responseSection.style.display = 'block'; + responseType.textContent = 'Loading'; + responseContent.textContent = message; + responseSection.classList.add('loading'); +} + +function showResponse(type, content) { + responseSection.style.display = 'block'; + responseType.textContent = type; + responseType.style.color = '#666'; + responseContent.textContent = typeof content === 'string' ? content : JSON.stringify(content, null, 2); + responseSection.classList.remove('loading'); + responseSection.classList.add('fade-in'); +} + +function showError(message) { + responseSection.style.display = 'block'; + responseType.textContent = 'Error'; + responseType.style.color = '#c00'; + responseContent.textContent = message; + responseSection.classList.remove('loading'); +} + +// Listen for status updates from background +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === 'STATUS_UPDATE') { + updateConnectionStatus(message.status !== 'disconnected', message.status); + } else if (message.type === 'QWEN_EVENT') { + // Handle events from Qwen CLI + console.log('Qwen event received:', message.event); + // Could update UI based on event + } +}); diff --git a/packages/chrome-qwen-bridge/first-install.sh b/packages/chrome-qwen-bridge/first-install.sh new file mode 100755 index 00000000..18b861dc --- /dev/null +++ b/packages/chrome-qwen-bridge/first-install.sh @@ -0,0 +1,120 @@ +#!/bin/bash + +# Qwen CLI Bridge - 銖次安装脚本 + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +clear +echo -e "${CYAN}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${CYAN}║ ║${NC}" +echo -e "${CYAN}║ 🎯 Qwen CLI Bridge - 銖次安装向富 ║${NC}" +echo -e "${CYAN}║ ║${NC}" +echo -e "${CYAN}╚════════════════════════════════════════════════════════════════╝${NC}" +echo "" + +echo -e "${YELLOW}这是銖次安装需芁手劚加蜜插件到 Chrome。${NC}" +echo "" + +# 步骀 1: 配眮 Native Host +echo -e "${BLUE}步骀 1:${NC} 配眮 Native Host..." + +MANIFEST_DIR="$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts" +mkdir -p "$MANIFEST_DIR" + +# 先创建䞀䞪䞎时的 manifest允讞所有扩展 +cat > "$MANIFEST_DIR/com.qwen.cli.bridge.json" << EOF +{ + "name": "com.qwen.cli.bridge", + "description": "Native messaging host for Qwen CLI Bridge", + "path": "$SCRIPT_DIR/native-host/host.js", + "type": "stdio", + "allowed_origins": ["chrome-extension://*/"] +} +EOF + +echo -e "${GREEN}✓${NC} Native Host 已配眮" + +# 步骀 2: 打匀 Chrome 扩展页面 +echo -e "\n${BLUE}步骀 2:${NC} 打匀 Chrome 扩展管理页面..." + +open -a "Google Chrome" "chrome://extensions" +sleep 2 + +echo -e "${GREEN}✓${NC} 已打匀扩展管理页面" + +# 步骀 3: 指富甚户安装 +echo "" +echo -e "${CYAN}════════════════════════════════════════════════════════════════${NC}" +echo -e "${YELLOW}请按照以䞋步骀手劚安装插件${NC}" +echo "" +echo -e " 1⃣ 圚 Chrome 扩展页面${GREEN}匀启「匀发者暡匏」${NC}右䞊角匀关" +echo "" +echo -e " 2⃣ 点击 ${GREEN}「加蜜已解压的扩展皋序」${NC} 按钮" +echo "" +echo -e " 3⃣ 选择以䞋目圕" +echo -e " ${BLUE}$SCRIPT_DIR/extension${NC}" +echo "" +echo -e " 4⃣ ${YELLOW}重芁${NC} 记䞋星瀺的扩展 ID类䌌 ${CYAN}abcdefghijklmnopqrstuvwx${NC}" +echo "" +echo -e "${CYAN}════════════════════════════════════════════════════════════════${NC}" +echo "" + +# 等埅甚户蟓入扩展 ID +echo -e "${YELLOW}请蟓入扩展 ID安装后星瀺的 ID${NC}" +read -p "> " EXTENSION_ID + +if [[ -z "$EXTENSION_ID" ]]; then + echo -e "${RED}✗ 未蟓入扩展 ID${NC}" + echo -e "${YELLOW}䜠可以皍后手劚曎新 Native Host 配眮${NC}" +else + # 曎新 manifest 文件添加具䜓的扩展 ID + cat > "$MANIFEST_DIR/com.qwen.cli.bridge.json" << EOF +{ + "name": "com.qwen.cli.bridge", + "description": "Native messaging host for Qwen CLI Bridge", + "path": "$SCRIPT_DIR/native-host/host.js", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://$EXTENSION_ID/", + "chrome-extension://*/" + ] +} +EOF + + # 保存扩展 ID 䟛后续䜿甚 + echo "$EXTENSION_ID" > "$SCRIPT_DIR/.extension-id" + + echo -e "${GREEN}✓${NC} Native Host 已曎新支持扩展 ID: $EXTENSION_ID" +fi + +echo "" +echo -e "${GREEN}════════════════════════════════════════════════════════════════${NC}" +echo -e "${GREEN} ✅ 銖次安装完成 ${NC}" +echo -e "${GREEN}════════════════════════════════════════════════════════════════${NC}" +echo "" +echo -e "现圚䜠可以" +echo "" +echo -e " 1. 运行 ${CYAN}npm run dev${NC} 启劚调试环境" +echo -e " 2. 点击 Chrome 工具栏的插件囟标匀始䜿甚" +echo "" +echo -e "${YELLOW}提瀺${NC}" +echo -e " • 劂果看䞍到插件囟标点击拌囟囟标并固定插件" +echo -e " • 銖次连接可胜需芁刷新页面" +echo "" + +# 询问是吊立即启劚 +echo -e "${CYAN}是吊立即启劚调试环境(y/n)${NC}" +read -p "> " START_NOW + +if [[ "$START_NOW" == "y" ]] || [[ "$START_NOW" == "Y" ]]; then + echo -e "\n${GREEN}正圚启劚调试环境...${NC}\n" + exec "$SCRIPT_DIR/debug.sh" +fi \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/native-host/com.qwen.bridge.json.template b/packages/chrome-qwen-bridge/native-host/com.qwen.bridge.json.template new file mode 100644 index 00000000..11e218a6 --- /dev/null +++ b/packages/chrome-qwen-bridge/native-host/com.qwen.bridge.json.template @@ -0,0 +1,9 @@ +{ + "name": "com.qwen.bridge", + "description": "Native messaging host for Qwen CLI Bridge", + "path": "__PATH__", + "type": "stdio", + "allowed_origins": [ + "__EXTENSION_ID__" + ] +} \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/native-host/host.bat b/packages/chrome-qwen-bridge/native-host/host.bat new file mode 100644 index 00000000..f4ebaa8c --- /dev/null +++ b/packages/chrome-qwen-bridge/native-host/host.bat @@ -0,0 +1,2 @@ +@echo off +node "%~dp0host.js" %* \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/native-host/host.js b/packages/chrome-qwen-bridge/native-host/host.js new file mode 100755 index 00000000..9c8bcfbd --- /dev/null +++ b/packages/chrome-qwen-bridge/native-host/host.js @@ -0,0 +1,421 @@ +#!/usr/bin/env node + +/** + * Native Messaging Host for Qwen CLI Bridge + * This script acts as a bridge between the Chrome extension and Qwen CLI + */ + +const { spawn } = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +// Native Messaging protocol helpers +function sendMessage(message) { + const buffer = Buffer.from(JSON.stringify(message)); + const length = Buffer.allocUnsafe(4); + length.writeUInt32LE(buffer.length, 0); + + process.stdout.write(length); + process.stdout.write(buffer); +} + +function readMessages() { + let messageLength = null; + let chunks = []; + + process.stdin.on('readable', () => { + let chunk; + + while ((chunk = process.stdin.read()) !== null) { + chunks.push(chunk); + const buffer = Buffer.concat(chunks); + + // Read message length if we haven't yet + if (messageLength === null) { + if (buffer.length >= 4) { + messageLength = buffer.readUInt32LE(0); + chunks = [buffer.slice(4)]; + } + } + + // Read message if we have the full length + if (messageLength !== null) { + const fullBuffer = Buffer.concat(chunks); + + if (fullBuffer.length >= messageLength) { + const messageBuffer = fullBuffer.slice(0, messageLength); + const message = JSON.parse(messageBuffer.toString()); + + // Reset for next message + chunks = [fullBuffer.slice(messageLength)]; + messageLength = null; + + // Handle the message + handleMessage(message); + } + } + } + }); + + process.stdin.on('end', () => { + process.exit(); + }); +} + +// Qwen CLI process management +let qwenProcess = null; +let qwenStatus = 'disconnected'; +let qwenCapabilities = []; + +// Check if Qwen CLI is installed +function checkQwenInstallation() { + return new Promise((resolve) => { + try { + const checkProcess = spawn('qwen', ['--version'], { + shell: true, + windowsHide: true + }); + + checkProcess.on('error', () => { + resolve(false); + }); + + checkProcess.on('close', (code) => { + resolve(code === 0); + }); + + // Timeout after 5 seconds + setTimeout(() => { + checkProcess.kill(); + resolve(false); + }, 5000); + } catch (error) { + resolve(false); + } + }); +} + +// Start Qwen CLI process +async function startQwenCli(config = {}) { + if (qwenProcess) { + return { success: false, error: 'Qwen CLI is already running' }; + } + + try { + // Build command arguments + const args = []; + + // Add MCP servers if specified + if (config.mcpServers && config.mcpServers.length > 0) { + for (const server of config.mcpServers) { + args.push('mcp', 'add', '--transport', 'http', server, `http://localhost:${config.httpPort || 8080}/mcp/${server}`); + args.push('&&'); + } + } + + // Start the CLI server + args.push('qwen', 'server'); + + if (config.httpPort) { + args.push('--port', String(config.httpPort)); + } + + // Spawn the process + qwenProcess = spawn(args.join(' '), { + shell: true, + windowsHide: true, + detached: false, + stdio: ['pipe', 'pipe', 'pipe'] + }); + + // Check if process started successfully + if (!qwenProcess || !qwenProcess.pid) { + qwenProcess = null; + qwenStatus = 'stopped'; + return { + success: false, + error: 'Failed to start Qwen CLI process' + }; + } + + qwenStatus = 'running'; + + // Handle process output + qwenProcess.stdout.on('data', (data) => { + const output = data.toString(); + sendMessage({ + type: 'event', + data: { + type: 'qwen_output', + content: output + } + }); + }); + + qwenProcess.stderr.on('data', (data) => { + const error = data.toString(); + sendMessage({ + type: 'event', + data: { + type: 'qwen_error', + content: error + } + }); + }); + + qwenProcess.on('close', (code) => { + qwenProcess = null; + qwenStatus = 'stopped'; + sendMessage({ + type: 'event', + data: { + type: 'qwen_stopped', + code: code + } + }); + }); + + // Wait a bit for the process to start + await new Promise(resolve => setTimeout(resolve, 2000)); + + // Get capabilities + qwenCapabilities = await getQwenCapabilities(); + + return { + success: true, + data: { + status: 'running', + pid: qwenProcess && qwenProcess.pid ? qwenProcess.pid : null, + capabilities: qwenCapabilities + } + }; + } catch (error) { + return { + success: false, + error: error.message + }; + } +} + +// Stop Qwen CLI process +function stopQwenCli() { + if (!qwenProcess) { + return { success: false, error: 'Qwen CLI is not running' }; + } + + try { + qwenProcess.kill('SIGTERM'); + qwenProcess = null; + qwenStatus = 'stopped'; + + return { + success: true, + data: 'Qwen CLI stopped' + }; + } catch (error) { + return { + success: false, + error: error.message + }; + } +} + +// Get Qwen CLI capabilities (MCP servers, tools, etc.) +async function getQwenCapabilities() { + return new Promise((resolve) => { + const checkProcess = spawn('qwen', ['mcp', 'list', '--json'], { + shell: true, + windowsHide: true + }); + + let output = ''; + + checkProcess.stdout.on('data', (data) => { + output += data.toString(); + }); + + checkProcess.on('close', () => { + try { + const capabilities = JSON.parse(output); + resolve(capabilities); + } catch { + resolve([]); + } + }); + + checkProcess.on('error', () => { + resolve([]); + }); + + // Timeout after 5 seconds + setTimeout(() => { + checkProcess.kill(); + resolve([]); + }, 5000); + }); +} + +// Send request to Qwen CLI via HTTP +async function sendToQwenHttp(action, data, config = {}) { + const http = require('http'); + + const port = config.httpPort || 8080; + const hostname = 'localhost'; + + const postData = JSON.stringify({ + action, + data + }); + + const options = { + hostname, + port, + path: '/api/process', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(postData) + } + }; + + return new Promise((resolve, reject) => { + const req = http.request(options, (res) => { + let responseData = ''; + + res.on('data', (chunk) => { + responseData += chunk; + }); + + res.on('end', () => { + try { + const response = JSON.parse(responseData); + resolve(response); + } catch (error) { + reject(new Error('Invalid response from Qwen CLI')); + } + }); + }); + + req.on('error', (error) => { + reject(error); + }); + + req.write(postData); + req.end(); + }); +} + +// Handle messages from Chrome extension +async function handleMessage(message) { + let response; + + switch (message.type) { + case 'handshake': + // Initial handshake with extension + const isInstalled = await checkQwenInstallation(); + response = { + type: 'handshake_response', + version: '1.0.0', + qwenInstalled: isInstalled, + qwenStatus: qwenStatus, + capabilities: qwenCapabilities + }; + break; + + case 'start_qwen': + // Start Qwen CLI + const startResult = await startQwenCli(message.config); + response = { + type: 'response', + id: message.id, + ...startResult + }; + break; + + case 'stop_qwen': + // Stop Qwen CLI + const stopResult = stopQwenCli(); + response = { + type: 'response', + id: message.id, + ...stopResult + }; + break; + + case 'qwen_request': + // Send request to Qwen CLI + try { + if (qwenStatus !== 'running') { + throw new Error('Qwen CLI is not running'); + } + + const qwenResponse = await sendToQwenHttp( + message.action, + message.data, + message.config + ); + + response = { + type: 'response', + id: message.id, + data: qwenResponse + }; + } catch (error) { + response = { + type: 'response', + id: message.id, + error: error.message + }; + } + break; + + case 'get_status': + // Get current status + response = { + type: 'response', + id: message.id, + data: { + qwenInstalled: await checkQwenInstallation(), + qwenStatus: qwenStatus, + qwenPid: qwenProcess ? qwenProcess.pid : null, + capabilities: qwenCapabilities + } + }; + break; + + default: + response = { + type: 'response', + id: message.id, + error: `Unknown message type: ${message.type}` + }; + } + + sendMessage(response); +} + +// Clean up on exit +process.on('SIGINT', () => { + if (qwenProcess) { + qwenProcess.kill(); + } + process.exit(); +}); + +process.on('SIGTERM', () => { + if (qwenProcess) { + qwenProcess.kill(); + } + process.exit(); +}); + +// Log function for debugging (writes to a file since stdout is used for messaging) +function log(message) { + const logFile = path.join(os.tmpdir(), 'qwen-bridge-host.log'); + fs.appendFileSync(logFile, `[${new Date().toISOString()}] ${message}\n`); +} + +// Main execution +log('Native host started'); +readMessages(); \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/native-host/install.bat b/packages/chrome-qwen-bridge/native-host/install.bat new file mode 100644 index 00000000..79e9b63b --- /dev/null +++ b/packages/chrome-qwen-bridge/native-host/install.bat @@ -0,0 +1,98 @@ +@echo off +setlocal enabledelayedexpansion + +REM Qwen CLI Bridge - Native Host Installation Script for Windows +REM This script installs the Native Messaging host for the Chrome extension + +echo ======================================== +echo Qwen CLI Bridge - Native Host Installer +echo ======================================== +echo. + +REM Set variables +set HOST_NAME=com.qwen.cli.bridge +set SCRIPT_DIR=%~dp0 +set HOST_SCRIPT=%SCRIPT_DIR%host.bat + +REM Check if Node.js is installed +where node >nul 2>nul +if %ERRORLEVEL% NEQ 0 ( + echo Error: Node.js is not installed + echo Please install Node.js from https://nodejs.org/ + pause + exit /b 1 +) + +REM Check if qwen CLI is installed +where qwen >nul 2>nul +if %ERRORLEVEL% NEQ 0 ( + echo Warning: qwen CLI is not installed + echo Please install qwen CLI to use all features + echo Installation will continue... + echo. +) + +REM Check if host files exist +if not exist "%HOST_SCRIPT%" ( + echo Error: host.bat not found in %SCRIPT_DIR% + pause + exit /b 1 +) + +if not exist "%SCRIPT_DIR%host.js" ( + echo Error: host.js not found in %SCRIPT_DIR% + pause + exit /b 1 +) + +REM Get extension ID +set /p EXTENSION_ID="Enter your Chrome extension ID (found in chrome://extensions): " + +if "%EXTENSION_ID%"=="" ( + echo Error: Extension ID is required + pause + exit /b 1 +) + +REM Create manifest +set MANIFEST_FILE=%SCRIPT_DIR%manifest-windows.json +echo Creating manifest: %MANIFEST_FILE% + +( +echo { +echo "name": "%HOST_NAME%", +echo "description": "Native messaging host for Qwen CLI Bridge Chrome extension", +echo "path": "%HOST_SCRIPT:\=\\%", +echo "type": "stdio", +echo "allowed_origins": [ +echo "chrome-extension://%EXTENSION_ID%/" +echo ] +echo } +) > "%MANIFEST_FILE%" + +REM Add registry entry for Chrome +echo. +echo Adding registry entry for Chrome... +reg add "HKCU\Software\Google\Chrome\NativeMessagingHosts\%HOST_NAME%" /ve /t REG_SZ /d "%MANIFEST_FILE%" /f + +if %ERRORLEVEL% EQU 0 ( + echo. + echo ✅ Installation complete! + echo. + echo Next steps: + echo 1. Load the Chrome extension in chrome://extensions + echo 2. Enable 'Developer mode' + echo 3. Click 'Load unpacked' and select: %SCRIPT_DIR%..\extension + echo 4. Copy the extension ID and re-run this script if needed + echo 5. Click the extension icon and connect to Qwen CLI + echo. + echo Host manifest: %MANIFEST_FILE% + echo Log file location: %%TEMP%%\qwen-bridge-host.log +) else ( + echo. + echo ❌ Failed to add registry entry + echo Please run this script as Administrator +) + +echo. +pause \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/native-host/install.sh b/packages/chrome-qwen-bridge/native-host/install.sh new file mode 100755 index 00000000..e0c9299b --- /dev/null +++ b/packages/chrome-qwen-bridge/native-host/install.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +# Qwen CLI Bridge - Native Host Installation Script for macOS/Linux +# This script installs the Native Messaging host for the Chrome extension + +set -e + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +HOST_NAME="com.qwen.cli.bridge" + +echo "========================================" +echo "Qwen CLI Bridge - Native Host Installer" +echo "========================================" +echo "" + +# Detect OS +if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + TARGET_DIR="$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts" + BROWSER="Chrome" +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + # Linux + TARGET_DIR="$HOME/.config/google-chrome/NativeMessagingHosts" + BROWSER="Chrome" +else + echo "Error: Unsupported operating system" + exit 1 +fi + +# Check if Node.js is installed +if ! command -v node &> /dev/null; then + echo "Error: Node.js is not installed" + echo "Please install Node.js from https://nodejs.org/" + exit 1 +fi + +# Check if qwen CLI is installed +if ! command -v qwen &> /dev/null; then + echo "Warning: qwen CLI is not installed" + echo "Please install qwen CLI to use all features" + echo "Installation will continue..." + echo "" +fi + +# Create target directory if it doesn't exist +echo "Creating directory: $TARGET_DIR" +mkdir -p "$TARGET_DIR" + +# Copy the host script +HOST_SCRIPT="$SCRIPT_DIR/host.js" +if [ ! -f "$HOST_SCRIPT" ]; then + echo "Error: host.js not found in $SCRIPT_DIR" + exit 1 +fi + +# Make the host script executable +chmod +x "$HOST_SCRIPT" + +# Create the manifest file with the correct path +MANIFEST_FILE="$TARGET_DIR/$HOST_NAME.json" +echo "Creating manifest: $MANIFEST_FILE" + +# Get the extension ID (you need to update this after installing the extension) +read -p "Enter your Chrome extension ID (found in chrome://extensions): " EXTENSION_ID + +if [ -z "$EXTENSION_ID" ]; then + echo "Error: Extension ID is required" + exit 1 +fi + +# Create the manifest +cat > "$MANIFEST_FILE" << EOF +{ + "name": "$HOST_NAME", + "description": "Native messaging host for Qwen CLI Bridge Chrome extension", + "path": "$HOST_SCRIPT", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://$EXTENSION_ID/" + ] +} +EOF + +echo "" +echo "✅ Installation complete!" +echo "" +echo "Next steps:" +echo "1. Load the Chrome extension in chrome://extensions" +echo "2. Enable 'Developer mode'" +echo "3. Click 'Load unpacked' and select: $SCRIPT_DIR/../extension" +echo "4. Copy the extension ID and re-run this script if needed" +echo "5. Click the extension icon and connect to Qwen CLI" +echo "" +echo "Host installed at: $MANIFEST_FILE" +echo "Log file location: /tmp/qwen-bridge-host.log" +echo "" \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/native-host/manifest.json b/packages/chrome-qwen-bridge/native-host/manifest.json new file mode 100644 index 00000000..db43ab43 --- /dev/null +++ b/packages/chrome-qwen-bridge/native-host/manifest.json @@ -0,0 +1,9 @@ +{ + "name": "com.qwen.cli.bridge", + "description": "Native messaging host for Qwen CLI Bridge Chrome extension", + "path": "HOST_PATH", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://YOUR_EXTENSION_ID/" + ] +} \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/native-host/package.json b/packages/chrome-qwen-bridge/native-host/package.json new file mode 100644 index 00000000..a89b020b --- /dev/null +++ b/packages/chrome-qwen-bridge/native-host/package.json @@ -0,0 +1,23 @@ +{ + "name": "qwen-cli-bridge-host", + "version": "1.0.0", + "description": "Native messaging host for Qwen CLI Bridge Chrome extension", + "main": "host.js", + "scripts": { + "test": "node host.js --test" + }, + "keywords": [ + "chrome-extension", + "native-messaging", + "qwen", + "cli", + "bridge" + ], + "author": "", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, + "dependencies": {}, + "devDependencies": {} +} \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/native-host/run.sh b/packages/chrome-qwen-bridge/native-host/run.sh new file mode 100755 index 00000000..fb84e20c --- /dev/null +++ b/packages/chrome-qwen-bridge/native-host/run.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Native Host 包装脚本 - 确保 Node.js 环境正确讟眮 + +# 获取脚本所圚目圕 +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# 讟眮 Node.js 路埄 (䜿甚系统䞭的 node) +NODE_PATH="/usr/local/bin/node" + +# 劂果 /usr/local/bin/node 䞍存圚尝试其他䜍眮 +if [ ! -f "$NODE_PATH" ]; then + NODE_PATH=$(which node) +fi + +# 执行 Native Host +exec "$NODE_PATH" "$DIR/host.js" \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/native-host/smart-install.sh b/packages/chrome-qwen-bridge/native-host/smart-install.sh new file mode 100755 index 00000000..1960232e --- /dev/null +++ b/packages/chrome-qwen-bridge/native-host/smart-install.sh @@ -0,0 +1,306 @@ +#!/bin/bash + +# Qwen CLI Bridge - 智胜 Native Host 安装噚 +# 自劚检测 Chrome 插件并配眮 Native Host + +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +HOST_NAME="com.qwen.cli.bridge" + +echo -e "${CYAN}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${CYAN}║ ║${NC}" +echo -e "${CYAN}║ 🔧 Qwen CLI Bridge - Native Host 安装噚 ║${NC}" +echo -e "${CYAN}║ ║${NC}" +echo -e "${CYAN}╚════════════════════════════════════════════════════════════════╝${NC}" +echo "" + +# 检测操䜜系统 +if [[ "$OSTYPE" == "darwin"* ]]; then + OS="macOS" + MANIFEST_DIR="$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts" + EXTENSIONS_DIR="$HOME/Library/Application Support/Google/Chrome/Default/Extensions" +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + OS="Linux" + MANIFEST_DIR="$HOME/.config/google-chrome/NativeMessagingHosts" + EXTENSIONS_DIR="$HOME/.config/google-chrome/Default/Extensions" +else + echo -e "${RED}✗ 䞍支持的操䜜系统${NC}" + exit 1 +fi + +echo -e "${BLUE}检测到系统${NC} $OS" +echo "" + +# 检查 Node.js +echo -e "${BLUE}检查䟝赖...${NC}" +if ! command -v node &> /dev/null; then + echo -e "${RED}✗ Node.js 未安装${NC}" + echo -e " 请访问 https://nodejs.org 安装 Node.js" + exit 1 +fi +echo -e "${GREEN}✓${NC} Node.js $(node --version)" + +# 尝试自劚检测扩展 ID +echo -e "\n${BLUE}查扟已安装的 Qwen CLI Bridge 扩展...${NC}" + +EXTENSION_ID="" +AUTO_DETECTED=false + +# 方法1: 从 Chrome 扩展目圕查扟 +if [[ -d "$EXTENSIONS_DIR" ]]; then + for ext_id in "$EXTENSIONS_DIR"/*; do + if [[ -d "$ext_id" ]]; then + ext_id_name=$(basename "$ext_id") + # 检查最新版本目圕 + for version_dir in "$ext_id"/*; do + if [[ -f "$version_dir/manifest.json" ]]; then + # 检查是吊是我们的扩展 + if grep -q "Qwen CLI Bridge" "$version_dir/manifest.json" 2>/dev/null; then + EXTENSION_ID="$ext_id_name" + AUTO_DETECTED=true + echo -e "${GREEN}✓${NC} 自劚检测到扩展 ID: ${CYAN}$EXTENSION_ID${NC}" + break 2 + fi + fi + done + fi + done +fi + +# 方法2: 检查之前保存的 ID +if [[ -z "$EXTENSION_ID" && -f "$SCRIPT_DIR/../.extension-id" ]]; then + EXTENSION_ID=$(cat "$SCRIPT_DIR/../.extension-id") + echo -e "${GREEN}✓${NC} 䜿甚保存的扩展 ID: ${CYAN}$EXTENSION_ID${NC}" + AUTO_DETECTED=true +fi + +# 劂果自劚检测倱莥提䟛选项 +if [[ -z "$EXTENSION_ID" ]]; then + echo -e "${YELLOW}⚠ 未胜自劚检测到扩展${NC}" + echo "" + echo -e "请选择" + echo -e " ${CYAN}1)${NC} 我已经安装了扩展蟓入扩展 ID" + echo -e " ${CYAN}2)${NC} 我还没有安装扩展通甚配眮" + echo -e " ${CYAN}3)${NC} 打匀 Chrome 扩展页面查看" + echo "" + read -p "选择 (1/2/3): " CHOICE + + case $CHOICE in + 1) + echo "" + echo -e "${YELLOW}请蟓入扩展 ID:${NC}" + echo -e "${CYAN}提瀺: 圚 chrome://extensions 页面扟到 Qwen CLI BridgeID 圚扩展卡片䞊${NC}" + read -p "> " EXTENSION_ID + if [[ -n "$EXTENSION_ID" ]]; then + # 保存 ID 䟛以后䜿甚 + echo "$EXTENSION_ID" > "$SCRIPT_DIR/../.extension-id" + echo -e "${GREEN}✓${NC} 扩展 ID 已保存" + fi + ;; + 2) + echo -e "\n${CYAN}将䜿甚通甚配眮允讞所有匀发扩展${NC}" + EXTENSION_ID="*" + ;; + 3) + echo -e "\n${CYAN}正圚打匀 Chrome 扩展页面...${NC}" + open "chrome://extensions" 2>/dev/null || xdg-open "chrome://extensions" 2>/dev/null || echo "请手劚打匀 chrome://extensions" + echo "" + echo -e "${YELLOW}扟到 Qwen CLI Bridge 扩展后蟓入其 ID:${NC}" + read -p "> " EXTENSION_ID + if [[ -n "$EXTENSION_ID" && "$EXTENSION_ID" != "*" ]]; then + echo "$EXTENSION_ID" > "$SCRIPT_DIR/../.extension-id" + fi + ;; + *) + echo -e "${RED}无效的选择${NC}" + exit 1 + ;; + esac +fi + +# 创建 Native Host 目圕 +echo -e "\n${BLUE}配眮 Native Host...${NC}" +mkdir -p "$MANIFEST_DIR" + +# 创建 manifest 文件 +MANIFEST_FILE="$MANIFEST_DIR/$HOST_NAME.json" + +if [[ "$EXTENSION_ID" == "*" ]]; then + # 通甚配眮 + cat > "$MANIFEST_FILE" << EOF +{ + "name": "$HOST_NAME", + "description": "Native messaging host for Qwen CLI Bridge", + "path": "$SCRIPT_DIR/host.js", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://*/" + ] +} +EOF + echo -e "${GREEN}✓${NC} Native Host 已配眮通甚暡匏" +else + # 特定扩展 ID 配眮 + cat > "$MANIFEST_FILE" << EOF +{ + "name": "$HOST_NAME", + "description": "Native messaging host for Qwen CLI Bridge", + "path": "$SCRIPT_DIR/host.js", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://$EXTENSION_ID/", + "chrome-extension://*/" + ] +} +EOF + echo -e "${GREEN}✓${NC} Native Host 已配眮扩展 ID: $EXTENSION_ID" +fi + +# 验证配眮 +echo -e "\n${BLUE}验证配眮...${NC}" + +# 检查 host.js 是吊存圚 +if [[ ! -f "$SCRIPT_DIR/host.js" ]]; then + echo -e "${RED}✗ host.js 文件䞍存圚${NC}" + exit 1 +fi + +# 确保 host.js 可执行 +chmod +x "$SCRIPT_DIR/host.js" +echo -e "${GREEN}✓${NC} host.js 已讟眮䞺可执行" + +# 检查 manifest 文件 +if [[ -f "$MANIFEST_FILE" ]]; then + echo -e "${GREEN}✓${NC} Manifest 文件已创建: $MANIFEST_FILE" +else + echo -e "${RED}✗ Manifest 文件创建倱莥${NC}" + exit 1 +fi + +echo "" +echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${GREEN}║ ║${NC}" +echo -e "${GREEN}║ ✅ Native Host 安装成功 ║${NC}" +echo -e "${GREEN}║ ║${NC}" +echo -e "${GREEN}╚════════════════════════════════════════════════════════════════╝${NC}" +echo "" + +# 星瀺䞋䞀步 +if [[ "$AUTO_DETECTED" == true ]]; then + echo -e "${CYAN}检测到扩展已安装䜠可以盎接䜿甚了${NC}" + echo "" + echo -e "䜿甚方法" + echo -e " 1. 点击 Chrome 工具栏的扩展囟标" + echo -e " 2. 点击 'Connect to Qwen CLI'" + echo -e " 3. 匀始䜿甚各项功胜" +else + echo -e "${YELLOW}䞋䞀步${NC}" + echo -e " 1. 圚 Chrome 䞭打匀 ${CYAN}chrome://extensions/${NC}" + echo -e " 2. 匀启${CYAN}「匀发者暡匏」${NC}右䞊角" + echo -e " 3. 点击${CYAN}「加蜜已解压的扩展皋序」${NC}" + echo -e " 4. 选择目圕: ${CYAN}$SCRIPT_DIR/../extension${NC}" + echo -e " 5. 安装完成后重新运行歀脚本以曎新配眮" +fi + +echo "" +echo -e "${CYAN}提瀺${NC}" +echo -e " • 劂需重新配眮随时可以重新运行歀脚本" +echo -e " • 日志文件䜍眮: /tmp/qwen-bridge-host.log" +echo -e " • 劂遇问题请查看: $SCRIPT_DIR/../docs/debugging.md" +echo "" + +# 询问是吊测试连接 +if [[ "$AUTO_DETECTED" == true ]]; then + echo -e "${CYAN}是吊测试 Native Host 连接(y/n)${NC}" + read -p "> " TEST_CONNECTION + + if [[ "$TEST_CONNECTION" == "y" ]] || [[ "$TEST_CONNECTION" == "Y" ]]; then + echo -e "\n${BLUE}测试连接...${NC}" + + # 创建测试脚本 + cat > /tmp/test-native-host.js << 'EOF' +const chrome = { + runtime: { + connectNative: () => { + console.log("Chrome API not available in Node.js environment"); + console.log("请圚 Chrome 扩展䞭测试连接"); + } + } +}; + +// 盎接测试 host.js +const { spawn } = require('child_process'); +const path = require('path'); + +const hostPath = process.argv[2]; +if (!hostPath) { + console.error("Missing host path"); + process.exit(1); +} + +console.log("Testing host at:", hostPath); + +const host = spawn('node', [hostPath], { + stdio: ['pipe', 'pipe', 'pipe'] +}); + +// 发送测试消息 +const testMessage = JSON.stringify({ type: 'handshake', version: '1.0.0' }); +const length = Buffer.allocUnsafe(4); +length.writeUInt32LE(Buffer.byteLength(testMessage), 0); + +host.stdin.write(length); +host.stdin.write(testMessage); + +// 读取响应 +let responseBuffer = Buffer.alloc(0); +let messageLength = null; + +host.stdout.on('data', (data) => { + responseBuffer = Buffer.concat([responseBuffer, data]); + + if (messageLength === null && responseBuffer.length >= 4) { + messageLength = responseBuffer.readUInt32LE(0); + responseBuffer = responseBuffer.slice(4); + } + + if (messageLength !== null && responseBuffer.length >= messageLength) { + const message = JSON.parse(responseBuffer.slice(0, messageLength).toString()); + console.log("Response received:", message); + + if (message.type === 'handshake_response') { + console.log("✅ Native Host 响应正垞"); + } + + host.kill(); + process.exit(0); + } +}); + +host.on('error', (error) => { + console.error("❌ Host error:", error.message); + process.exit(1); +}); + +setTimeout(() => { + console.error("❌ 测试超时"); + host.kill(); + process.exit(1); +}, 5000); +EOF + + node /tmp/test-native-host.js "$SCRIPT_DIR/host.js" + rm /tmp/test-native-host.js + fi +fi + +echo -e "${GREEN}安装完成${NC}" \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/native-host/start.sh b/packages/chrome-qwen-bridge/native-host/start.sh new file mode 100755 index 00000000..6c1bdaed --- /dev/null +++ b/packages/chrome-qwen-bridge/native-host/start.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Native Host 启劚脚本 +# Chrome 圚 macOS 䞊需芁这䞪包装脚本来正确启劚 Node.js + +# 获取脚本所圚目圕 +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# 日志文件 +LOG_FILE="/tmp/qwen-bridge-host.log" + +# 记圕启劚信息 +echo "[$(date)] Native Host 启劚..." >> "$LOG_FILE" +echo "[$(date)] 工䜜目圕: $DIR" >> "$LOG_FILE" +echo "[$(date)] Node 路埄: $(which node)" >> "$LOG_FILE" + +# 启劚 Node.js Native Host +exec /usr/bin/env node "$DIR/host.js" 2>> "$LOG_FILE" \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/package.json b/packages/chrome-qwen-bridge/package.json new file mode 100644 index 00000000..5ee9d8a7 --- /dev/null +++ b/packages/chrome-qwen-bridge/package.json @@ -0,0 +1,43 @@ +{ + "name": "@qwen-code/chrome-bridge", + "version": "1.0.0", + "description": "Chrome extension bridge for Qwen CLI - enables AI-powered browser interactions", + "private": true, + "repository": { + "type": "git", + "url": "https://github.com/QwenLM/qwen-code.git", + "directory": "packages/chrome-qwen-bridge" + }, + "keywords": [ + "chrome-extension", + "qwen", + "cli", + "bridge", + "native-messaging", + "mcp", + "ai" + ], + "author": "Qwen Team", + "license": "Apache-2.0", + "files": [ + "extension/", + "native-host/", + "README.md" + ], + "scripts": { + "dev": "./debug.sh", + "install:extension": "./first-install.sh", + "install:host": "cd native-host && ./smart-install.sh", + "install:all": "./first-install.sh", + "dev:chrome": "open -a 'Google Chrome' --args --load-extension=$PWD/extension --auto-open-devtools-for-tabs", + "dev:server": "qwen server --port 8080", + "build": "./build.sh", + "package": "zip -r chrome-qwen-bridge.zip extension/", + "clean": "rm -rf dist *.zip /tmp/qwen-bridge-host.log /tmp/qwen-server.log .extension-id", + "logs": "tail -f /tmp/qwen-bridge-host.log", + "logs:qwen": "tail -f /tmp/qwen-server.log" + }, + "engines": { + "node": ">=14.0.0" + } +} \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/reload.sh b/packages/chrome-qwen-bridge/reload.sh new file mode 100755 index 00000000..2c092834 --- /dev/null +++ b/packages/chrome-qwen-bridge/reload.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# 快速重新加蜜 Chrome 扩展的脚本 + +echo "🔄 重新加蜜 Chrome 扩展..." +echo "" + +# 获取扩展路埄 +EXTENSION_PATH="$PWD/extension" + +# 检查扩展目圕 +if [ ! -d "$EXTENSION_PATH" ]; then + echo "❌ 错误: 扩展目圕䞍存圚: $EXTENSION_PATH" + exit 1 +fi + +echo "📂 扩展路埄: $EXTENSION_PATH" +echo "" + +# 提瀺甚户操䜜步骀 +echo "请按照以䞋步骀操䜜" +echo "" +echo "1⃣ 打匀 Chrome 浏览噚" +echo "2⃣ 访问 chrome://extensions/" +echo "3⃣ 点击右䞊角的 '匀发者暡匏' 匀关劂果尚未匀启" +echo "4⃣ 劂果扩展已加蜜" +echo " - 扟到 'Qwen CLI Bridge' 扩展" +echo " - 点击 '重新加蜜' 按钮 (🔄 囟标)" +echo "5⃣ 劂果扩展未加蜜" +echo " - 点击 '加蜜已解压的扩展皋序'" +echo " - 选择以䞋目圕" +echo " $EXTENSION_PATH" +echo "" +echo "6⃣ 点击扩展囟标测试功胜" +echo "7⃣ 劂有错误按 F12 打匀 DevTools 查看控制台" +echo "" + +# 劂果存圚扩展 ID 文件星瀺它 +if [ -f ".extension-id" ]; then + EXTENSION_ID=$(cat .extension-id) + echo "📝 已保存的扩展 ID: $EXTENSION_ID" + echo "" +fi + +# 提䟛快速打匀 Chrome 的呜什 +echo "💡 快速呜什" +echo " 打匀扩展页面: open 'chrome://extensions/'" +echo " 查看后台日志: open 'chrome://extensions/?id=<扩展ID>'" +echo "" + +# 询问是吊芁打匀 Chrome 扩展页面 +read -p "是吊芁自劚打匀 Chrome 扩展页面? (y/n) " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + open "chrome://extensions/" + echo "" + echo "✅ 已打匀 Chrome 扩展页面" +fi + +echo "" +echo "🎉 准倇完成请圚 Chrome 䞭重新加蜜扩展。" \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/set-extension-id.sh b/packages/chrome-qwen-bridge/set-extension-id.sh new file mode 100755 index 00000000..8f1c6b77 --- /dev/null +++ b/packages/chrome-qwen-bridge/set-extension-id.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +echo "🔧 配眮 Native Host 䜿甚特定扩展 ID..." + +EXTENSION_ID="cimaabkejokbhjkdnajgfniiolfjgbhd" +CONFIG_FILE="$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.qwen.cli.bridge.json" +RUN_SCRIPT="$PWD/native-host/run.sh" + +# 创建配眮䜿甚特定扩展 ID +cat > "$CONFIG_FILE" < "$MANIFEST_DIR/com.qwen.cli.bridge.json" << EOF +{ + "name": "com.qwen.cli.bridge", + "description": "Native messaging host for Qwen CLI Bridge", + "path": "$NATIVE_HOST_DIR/host.js", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://*/", + "chrome-extension://jniepomhbdkeifkadbfolbcihcmfpfjo/" + ] +} +EOF + +print_success "Native Host configured" + +# 3. 检查 Qwen CLI +print_info "Checking Qwen CLI..." + +if command -v qwen &> /dev/null; then + print_success "Qwen CLI is installed" + QWEN_VERSION=$(qwen --version 2>/dev/null || echo "unknown") + print_info "Version: $QWEN_VERSION" + + # 尝试启劚 Qwen server + print_info "Starting Qwen server on port 8080..." + + # 检查端口是吊被占甚 + if lsof -i:8080 &> /dev/null; then + print_warning "Port 8080 is already in use, skipping Qwen server start" + else + # 圚后台启劚 Qwen server + nohup qwen server --port 8080 > /tmp/qwen-server.log 2>&1 & + QWEN_PID=$! + sleep 2 + + if kill -0 $QWEN_PID 2>/dev/null; then + print_success "Qwen server started (PID: $QWEN_PID)" + echo $QWEN_PID > /tmp/qwen-server.pid + else + print_warning "Failed to start Qwen server, continuing anyway..." + fi + fi +else + print_warning "Qwen CLI not installed - some features will be limited" +fi + +# 4. 启劚简单的测试服务噚 +print_info "Starting test server..." + +# 创建简单的 Python HTTP 服务噚 +cat > /tmp/test-server.py << 'EOF' +#!/usr/bin/env python3 +import http.server +import socketserver + +PORT = 3000 + +html_content = """ + + + + Qwen CLI Bridge Test + + + +
+

🚀 Qwen CLI Bridge Test Page

+

Extension debugging environment is ready!

+ +

Quick Tests

+ + + + +

Instructions

+
    +
  1. Click the extension icon in Chrome toolbar
  2. +
  3. Click "Connect to Qwen CLI"
  4. +
  5. Try the various features
  6. +
  7. Open DevTools (F12) to see console output
  8. +
+ +

Sample Content

+

This is sample text content that can be extracted by the extension.

+
    +
  • Item 1: Lorem ipsum dolor sit amet
  • +
  • Item 2: Consectetur adipiscing elit
  • +
  • Item 3: Sed do eiusmod tempor
  • +
+
+ + + + +""" + +class MyHandler(http.server.SimpleHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + self.wfile.write(html_content.encode()) + +with socketserver.TCPServer(("", PORT), MyHandler) as httpd: + print(f"Test server running at http://localhost:{PORT}") + httpd.serve_forever() +EOF + +python3 /tmp/test-server.py > /tmp/test-server.log 2>&1 & +TEST_SERVER_PID=$! +echo $TEST_SERVER_PID > /tmp/test-server.pid +sleep 1 + +print_success "Test server started at http://localhost:3000" + +# 5. 启劚 Chrome +print_info "Starting Chrome with extension..." + +# Chrome 参数 +CHROME_ARGS=( + "--load-extension=$EXTENSION_DIR" + "--auto-open-devtools-for-tabs" + "--no-first-run" + "--no-default-browser-check" + "--disable-default-apps" + "http://localhost:3000" +) + +# 启劚 Chrome +"$CHROME_PATH" "${CHROME_ARGS[@]}" & +CHROME_PID=$! + +print_success "Chrome started with extension loaded" + +# 6. 星瀺状态和枅理指什 +echo "" +echo "======================================" +echo " ✅ All Services Running" +echo "======================================" +echo "" +echo "📌 Chrome: Running (PID: $CHROME_PID)" +echo "📌 Test Page: http://localhost:3000" +if [[ -n "${QWEN_PID:-}" ]]; then + echo "📌 Qwen Server: http://localhost:8080 (PID: $QWEN_PID)" +fi +echo "📌 Extension: Loaded in Chrome toolbar" +echo "" +echo "📝 Debug Locations:" +echo " • Extension Logs: Chrome DevTools Console" +echo " • Background Page: chrome://extensions → Service Worker" +echo " • Native Host Log: /tmp/qwen-bridge-host.log" +if [[ -n "${QWEN_PID:-}" ]]; then + echo " • Qwen Server Log: /tmp/qwen-server.log" +fi +echo "" +echo "🛑 To stop all services, run: $SCRIPT_DIR/stop.sh" +echo " Or press Ctrl+C to stop this script" +echo "" + +# 创建停止脚本 +cat > "$SCRIPT_DIR/stop.sh" << 'STOP_SCRIPT' +#!/bin/bash + +echo "Stopping services..." + +# 停止 Qwen server +if [[ -f /tmp/qwen-server.pid ]]; then + PID=$(cat /tmp/qwen-server.pid) + if kill -0 $PID 2>/dev/null; then + kill $PID + echo "✓ Qwen server stopped" + fi + rm /tmp/qwen-server.pid +fi + +# 停止测试服务噚 +if [[ -f /tmp/test-server.pid ]]; then + PID=$(cat /tmp/test-server.pid) + if kill -0 $PID 2>/dev/null; then + kill $PID + echo "✓ Test server stopped" + fi + rm /tmp/test-server.pid +fi + +echo "✓ All services stopped" +STOP_SCRIPT + +chmod +x "$SCRIPT_DIR/stop.sh" + +# 等埅甚户䞭断 +trap 'echo "Stopping services..."; $SCRIPT_DIR/stop.sh; exit 0' INT TERM + +# 保持脚本运行 +while true; do + sleep 1 +done \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/test-connection.sh b/packages/chrome-qwen-bridge/test-connection.sh new file mode 100755 index 00000000..1852f34d --- /dev/null +++ b/packages/chrome-qwen-bridge/test-connection.sh @@ -0,0 +1,166 @@ +#!/bin/bash + +echo "🔗 Chrome Extension 连接完敎测试" +echo "================================" +echo "" + +# 颜色定义 +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# 扩展 ID +EXTENSION_ID="cimaabkejokbhjkdnajgfniiolfjgbhd" + +# Step 1: 测试 Native Host 盎接响应 +echo -e "${BLUE}Step 1: 测试 Native Host 盎接响应${NC}" +echo "----------------------------------------" + +# 创建测试 Python 脚本 +cat > /tmp/test_native.py << 'EOF' +#!/usr/bin/env python3 +import json +import struct +import subprocess +import sys +import os + +def test_native_host(): + host_path = sys.argv[1] if len(sys.argv) > 1 else './native-host/run.sh' + + if not os.path.exists(host_path): + print(f"❌ Host 文件䞍存圚: {host_path}") + return False + + try: + # 启劚 Native Host + proc = subprocess.Popen( + [host_path], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + # 发送握手消息 + message = {"type": "handshake", "version": "1.0.0"} + encoded = json.dumps(message).encode('utf-8') + proc.stdin.write(struct.pack(' /dev/null; then + echo -e "${GREEN}✓${NC} Chrome 正圚运行" + + # 获取 Chrome 版本 + CHROME_VERSION=$("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --version 2>/dev/null | cut -d' ' -f3) + if [ -n "$CHROME_VERSION" ]; then + echo " 版本: $CHROME_VERSION" + fi +else + echo -e "${YELLOW}⚠${NC} Chrome 未运行" +fi +echo "" + +# Step 4: 提䟛测试指什 +echo -e "${BLUE}Step 4: 手劚测试步骀${NC}" +echo "----------------------------------------" +echo "请按以䞋步骀进行手劚测试" +echo "" +echo "1. 打匀 Chrome 并访问: chrome://extensions/" +echo " 扩展 ID 应䞺: ${EXTENSION_ID}" +echo "" +echo "2. 重新加蜜扩展" +echo " - 扟到 'Qwen CLI Bridge'" +echo " - 点击重新加蜜按钮 🔄" +echo "" +echo "3. 查看后台日志" +echo " - 点击 'Service Worker' 铟接" +echo " - 圚控制台䞭查看日志" +echo "" +echo "4. 测试连接" +echo " - 点击扩展囟标" +echo " - 点击 'Connect to Qwen CLI'" +echo " - 观察控制台蟓出" +echo "" + +# Step 5: 提䟛快速呜什 +echo -e "${BLUE}Step 5: 快速呜什${NC}" +echo "----------------------------------------" +echo "打匀扩展管理页面:" +echo -e "${YELLOW} open 'chrome://extensions/'${NC}" +echo "" +echo "查看 Service Worker:" +echo -e "${YELLOW} open 'chrome://extensions/?id=$EXTENSION_ID'${NC}" +echo "" +echo "查看 Native Host 日志:" +echo -e "${YELLOW} tail -f /tmp/qwen-bridge-host.log${NC}" +echo "" + +# 枅理䞎时文件 +rm -f /tmp/test_native.py + +echo "================================" +echo -e "${GREEN}测试完成${NC}" +echo "" +echo "劂果连接仍然倱莥请检查 Service Worker 控制台的具䜓错误信息。" \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/test-handshake.sh b/packages/chrome-qwen-bridge/test-handshake.sh new file mode 100755 index 00000000..1a3f5309 --- /dev/null +++ b/packages/chrome-qwen-bridge/test-handshake.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Native Host 握手测试脚本 + +echo "🀝 测试 Native Host 握手..." +echo "" + +# 枅理旧日志 +> /tmp/qwen-bridge-host.log + +# 握手消息 +TEST_MSG='{"type":"handshake","version":"1.0.0"}' + +# 计算消息长床 +MSG_LEN=${#TEST_MSG} + +printf "发送握手消息: $TEST_MSG\n" +printf "消息长床: $MSG_LEN bytes\n\n" + +# 创建䞀䞪䞎时文件来存傚二进制数据 +TEMP_FILE=$(mktemp) + +# 写入长床倎4字节小端序 +printf "\\x$(printf '%02x' $((MSG_LEN & 0xff)))" > "$TEMP_FILE" +printf "\\x$(printf '%02x' $(((MSG_LEN >> 8) & 0xff)))" >> "$TEMP_FILE" +printf "\\x$(printf '%02x' $(((MSG_LEN >> 16) & 0xff)))" >> "$TEMP_FILE" +printf "\\x$(printf '%02x' $(((MSG_LEN >> 24) & 0xff)))" >> "$TEMP_FILE" +# 写入消息内容 +printf "$TEST_MSG" >> "$TEMP_FILE" + +echo "启劚 Native Host 并发送握手消息..." +cat "$TEMP_FILE" | timeout 2 ./native-host/start.sh 2>&1 | od -c | head -10 + +# 枅理䞎时文件 +rm -f "$TEMP_FILE" + +echo "" +echo "检查日志文件..." +if [ -f /tmp/qwen-bridge-host.log ]; then + echo "📋 日志内容:" + tail -20 /tmp/qwen-bridge-host.log +else + echo "⚠ 未扟到日志文件" +fi \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/test-native-host.sh b/packages/chrome-qwen-bridge/test-native-host.sh new file mode 100755 index 00000000..ec5d430c --- /dev/null +++ b/packages/chrome-qwen-bridge/test-native-host.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Native Host 连接测试脚本 + +echo "🔍 测试 Native Host 连接..." +echo "" + +# 枅理旧日志 +> /tmp/qwen-bridge-host.log + +# 测试消息 +TEST_MSG='{"type":"PING"}' + +# 计算消息长床4字节小端序 +MSG_LEN=${#TEST_MSG} + +# 将长床蜬换䞺4字节小端序 +printf "发送测试消息: $TEST_MSG\n" +printf "消息长床: $MSG_LEN bytes\n\n" + +# 发送消息到 Native Host +echo "启劚 Native Host 并发送测试消息..." +( + # 发送长床倎4字节 + printf "\\x$(printf '%02x' $((MSG_LEN & 0xff)))" + printf "\\x$(printf '%02x' $(((MSG_LEN >> 8) & 0xff)))" + printf "\\x$(printf '%02x' $(((MSG_LEN >> 16) & 0xff)))" + printf "\\x$(printf '%02x' $(((MSG_LEN >> 24) & 0xff)))" + # 发送消息内容 + printf "$TEST_MSG" +) | ./native-host/start.sh 2>&1 | head -c 100 + +echo "" +echo "" +echo "检查日志文件..." +if [ -f /tmp/qwen-bridge-host.log ]; then + echo "📋 日志内容:" + cat /tmp/qwen-bridge-host.log +else + echo "⚠ 未扟到日志文件" +fi \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/test.sh b/packages/chrome-qwen-bridge/test.sh new file mode 100755 index 00000000..04f0bd4a --- /dev/null +++ b/packages/chrome-qwen-bridge/test.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# 快速测试脚本 + +echo "🔍 检查 Chrome 扩展配眮..." +echo "" + +# 检查目圕结构 +echo "📂 目圕结构" +ls -la extension/ | grep -E "background|content|icons|options|popup" +echo "" + +# 检查 manifest.json +echo "📄 Manifest 配眮" +cat extension/manifest.json | grep -E "options_ui|version|name" | head -5 +echo "" + +# 检查关键文件 +echo "✅ 文件检查" +FILES=( + "extension/manifest.json" + "extension/popup/popup.html" + "extension/popup/popup.js" + "extension/options/options.html" + "extension/options/options.js" + "extension/background/service-worker.js" + "extension/content/content-script.js" +) + +for file in "${FILES[@]}"; do + if [[ -f "$file" ]]; then + echo " ✓ $file" + else + echo " ✗ $file - 猺倱!" + fi +done + +echo "" +echo "💡 提瀺" +echo " 1. 圚 Chrome 䞭重新加蜜扩展 (chrome://extensions/)" +echo " 2. 点击扩展囟标测试功胜" +echo " 3. 劂有错误查看 Chrome DevTools Console" +echo "" +echo "🚀 运行 'npm run dev' 启劚完敎调试环境" \ No newline at end of file diff --git a/packages/chrome-qwen-bridge/test_fix.py b/packages/chrome-qwen-bridge/test_fix.py new file mode 100755 index 00000000..1186fe1a --- /dev/null +++ b/packages/chrome-qwen-bridge/test_fix.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +import json +import struct +import subprocess +import sys +import time + +def send_message(proc, message): + """Send a message to the Native Host""" + encoded = json.dumps(message).encode('utf-8') + proc.stdin.write(struct.pack(' "$CONFIG_FILE" < /dev/null; then + NODE_VERSION=$(node --version) + echo -e "${GREEN}✓${NC} Node.js 已安装: $NODE_VERSION" +else + echo -e "${RED}✗${NC} Node.js 未安装" + ((ERROR_COUNT++)) +fi + +if command -v npm &> /dev/null; then + NPM_VERSION=$(npm --version) + echo -e "${GREEN}✓${NC} npm 已安装: $NPM_VERSION" +else + echo -e "${RED}✗${NC} npm 未安装" + ((ERROR_COUNT++)) +fi +echo "" + +# 8. 总结 +echo "======================================" +if [ $ERROR_COUNT -eq 0 ]; then + echo -e "${GREEN}✅ 所有检查通过${NC}" + echo "" + echo "䞋䞀步操䜜" + echo "1. 运行 'npm run dev' 启劚调试" + echo "2. 或运行 './reload.sh' 重新加蜜扩展" +else + echo -e "${RED}❌ 发现 $ERROR_COUNT 䞪问题${NC}" + echo "" + echo "建议操䜜" + if [ ! -f "$NATIVE_HOST_PATH" ]; then + echo "• 运行 'npm run install:host' 安装 Native Host" + fi + if [ $ERROR_COUNT -gt 0 ]; then + echo "• 检查䞊述错误并修倍猺倱的文件" + fi +fi +echo "======================================" \ No newline at end of file