mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-22 09:47:47 +00:00
feat(chrome-qwen-bridge): 🔥 init chrome qwen code bridge
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "com.qwen.bridge",
|
||||
"description": "Native messaging host for Qwen CLI Bridge",
|
||||
"path": "__PATH__",
|
||||
"type": "stdio",
|
||||
"allowed_origins": [
|
||||
"__EXTENSION_ID__"
|
||||
]
|
||||
}
|
||||
2
packages/chrome-qwen-bridge/native-host/host.bat
Normal file
2
packages/chrome-qwen-bridge/native-host/host.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
node "%~dp0host.js" %*
|
||||
421
packages/chrome-qwen-bridge/native-host/host.js
Executable file
421
packages/chrome-qwen-bridge/native-host/host.js
Executable file
@@ -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();
|
||||
98
packages/chrome-qwen-bridge/native-host/install.bat
Normal file
98
packages/chrome-qwen-bridge/native-host/install.bat
Normal file
@@ -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
|
||||
96
packages/chrome-qwen-bridge/native-host/install.sh
Executable file
96
packages/chrome-qwen-bridge/native-host/install.sh
Executable file
@@ -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 ""
|
||||
9
packages/chrome-qwen-bridge/native-host/manifest.json
Normal file
9
packages/chrome-qwen-bridge/native-host/manifest.json
Normal file
@@ -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/"
|
||||
]
|
||||
}
|
||||
23
packages/chrome-qwen-bridge/native-host/package.json
Normal file
23
packages/chrome-qwen-bridge/native-host/package.json
Normal file
@@ -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": {}
|
||||
}
|
||||
17
packages/chrome-qwen-bridge/native-host/run.sh
Executable file
17
packages/chrome-qwen-bridge/native-host/run.sh
Executable file
@@ -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"
|
||||
306
packages/chrome-qwen-bridge/native-host/smart-install.sh
Executable file
306
packages/chrome-qwen-bridge/native-host/smart-install.sh
Executable file
@@ -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}"
|
||||
18
packages/chrome-qwen-bridge/native-host/start.sh
Executable file
18
packages/chrome-qwen-bridge/native-host/start.sh
Executable file
@@ -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"
|
||||
Reference in New Issue
Block a user