#!/usr/bin/env node // auto_connect_aistudio.js (v2.9 - Refined Launch & Page Handling + Beautified Output) const { spawn, execSync } = require('child_process'); const path = require('path'); const fs = require('fs'); const readline = require('readline'); // --- Configuration --- const DEBUGGING_PORT = 8848; const TARGET_URL = 'https://aistudio.google.com/prompts/new_chat'; // Target page const SERVER_SCRIPT_FILENAME = 'server.cjs'; // Corrected script name const CONNECTION_RETRIES = 5; const RETRY_DELAY_MS = 4000; const CONNECT_TIMEOUT_MS = 20000; // Timeout for connecting to CDP const NAVIGATION_TIMEOUT_MS = 35000; // Increased timeout for page navigation const CDP_ADDRESS = `http://127.0.0.1:${DEBUGGING_PORT}`; // --- ANSI Colors --- const RESET = '\x1b[0m'; const BRIGHT = '\x1b[1m'; const DIM = '\x1b[2m'; const RED = '\x1b[31m'; const GREEN = '\x1b[32m'; const YELLOW = '\x1b[33m'; const BLUE = '\x1b[34m'; const MAGENTA = '\x1b[35m'; const CYAN = '\x1b[36m'; // --- Globals --- const SERVER_SCRIPT_PATH = path.join(__dirname, SERVER_SCRIPT_FILENAME); let playwright; // Loaded in checkDependencies // --- Platform-Specific Chrome Path --- function getChromePath() { switch (process.platform) { case 'darwin': return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'; case 'win32': // 尝试 Program Files 和 Program Files (x86) const winPaths = [ path.join(process.env.ProgramFiles || '', 'Google\Chrome\Application\chrome.exe'), path.join(process.env['ProgramFiles(x86)'] || '', 'Google\Chrome\Application\chrome.exe') ]; return winPaths.find(p => fs.existsSync(p)); case 'linux': // 尝试常见的 Linux 路径 const linuxPaths = [ '/usr/bin/google-chrome', '/usr/bin/google-chrome-stable', '/opt/google/chrome/chrome', // Add path for Flatpak installation if needed // '/var/lib/flatpak/exports/bin/com.google.Chrome' ]; return linuxPaths.find(p => fs.existsSync(p)); default: return null; // 不支持的平台 } } const chromeExecutablePath = getChromePath(); // --- 端口检查函数 --- function isPortInUse(port) { const platform = process.platform; let command; // console.log(`${DIM} 检查端口 ${port}...${RESET}`); // Optional: Verbose check try { if (platform === 'win32') { // 在 Windows 上,查找监听状态的 TCP 端口 command = `netstat -ano | findstr LISTENING | findstr :${port}`; execSync(command); // 如果找到,不会抛出错误 return true; } else if (platform === 'darwin' || platform === 'linux') { // 在 macOS 或 Linux 上,查找监听该端口的进程 command = `lsof -i tcp:${port} -sTCP:LISTEN`; execSync(command); // 如果找到,不会抛出错误 return true; } } catch (error) { // 如果命令执行失败(通常意味着找不到匹配的进程),则端口未被占用 // console.log(`端口 ${port} 检查命令执行失败或未找到进程:`, error.message.split('\n')[0]); // 可选的调试信息 return false; } // 对于不支持的平台,保守地假设端口未被占用 return false; } // --- 查找占用端口的 PID --- (新增) function findPidsUsingPort(port) { const platform = process.platform; const pids = []; let command; try { console.log(`${DIM} 正在查找占用端口 ${port} 的进程...${RESET}`); if (platform === 'win32') { command = `netstat -ano | findstr LISTENING | findstr :${port}`; const output = execSync(command).toString(); const lines = output.trim().split('\n'); for (const line of lines) { const parts = line.trim().split(/\s+/); const pid = parts[parts.length - 1]; // PID is the last column if (pid && !isNaN(pid)) { pids.push(pid); } } } else { // macOS or Linux command = `lsof -t -i tcp:${port} -sTCP:LISTEN`; const output = execSync(command).toString(); const lines = output.trim().split('\n'); for (const line of lines) { const pid = line.trim(); if (pid && !isNaN(pid)) { pids.push(pid); } } } if (pids.length > 0) { console.log(` ${YELLOW}找到占用端口 ${port} 的 PID: ${pids.join(', ')}${RESET}`); } else { console.log(` ${GREEN}未找到明确监听端口 ${port} 的进程。${RESET}`); } } catch (error) { // 命令失败通常意味着没有找到进程 console.log(` ${GREEN}查找端口 ${port} 进程的命令执行失败或无结果。${RESET}`); } return [...new Set(pids)]; // 返回去重后的 PID 列表 } // --- 结束进程 --- (新增) function killProcesses(pids) { if (pids.length === 0) return true; // 没有进程需要结束 const platform = process.platform; let success = true; console.log(`${YELLOW} 正在尝试结束 PID: ${pids.join(', ')}...${RESET}`); for (const pid of pids) { try { if (platform === 'win32') { execSync(`taskkill /F /PID ${pid}`); console.log(` ${GREEN}✅ 成功结束 PID ${pid} (Windows)${RESET}`); } else { // macOS or Linux execSync(`kill -9 ${pid}`); console.log(` ${GREEN}✅ 成功结束 PID ${pid} (macOS/Linux)${RESET}`); } } catch (error) { console.warn(` ${RED}⚠️ 结束 PID ${pid} 时出错: ${error.message.split('\n')[0]}${RESET}`); // 可能原因:进程已不存在、权限不足等 success = false; // 标记至少有一个失败了 } } return success; } // --- 创建 Readline Interface --- function askQuestion(query) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); return new Promise(resolve => rl.question(query, ans => { rl.close(); resolve(ans); })) } // --- 步骤 1: 检查 Playwright 依赖 --- async function checkDependencies() { console.log(`${CYAN}-------------------------------------------------${RESET}`); console.log(`${CYAN}--- 步骤 1: 检查依赖项 ---${RESET}`); console.log('将检查以下模块是否已安装:'); const requiredModules = ['express', 'playwright', '@playwright/test', 'cors']; const missingModules = []; let allFound = true; for (const moduleName of requiredModules) { process.stdout.write(` - ${moduleName} ... `); try { require.resolve(moduleName); // Use require.resolve for checking existence without loading console.log(`${GREEN}✓ 已找到${RESET}`); // Green checkmark } catch (error) { if (error.code === 'MODULE_NOT_FOUND') { console.log(`${RED}❌ 未找到${RESET}`); // Red X missingModules.push(moduleName); allFound = false; } else { console.log(`${RED}❌ 检查时出错: ${error.message}${RESET}`); allFound = false; // Consider exiting if it's not MODULE_NOT_FOUND? // return false; } } } process.stdout.write(` - 服务器脚本 (${SERVER_SCRIPT_FILENAME}) ... `); if (!fs.existsSync(SERVER_SCRIPT_PATH)) { console.log(`${RED}❌ 未找到${RESET}`); // Red X console.error(` ${RED}错误: 未在预期路径找到 '${SERVER_SCRIPT_FILENAME}' 文件。${RESET}`); console.error(` 预期路径: ${SERVER_SCRIPT_PATH}`); console.error(` 请确保 '${SERVER_SCRIPT_FILENAME}' 与此脚本位于同一目录。`); allFound = false; } else { console.log(`${GREEN}✓ 已找到${RESET}`); // Green checkmark } if (!allFound) { console.log(`\n${RED}-------------------------------------------------${RESET}`); console.error(`${RED}❌ 错误: 依赖项检查未通过!${RESET}`); if (missingModules.length > 0) { console.error(` ${RED}缺少以下 Node.js 模块: ${missingModules.join(', ')}${RESET}`); console.log(' 请根据您使用的包管理器运行以下命令安装依赖:'); console.log(` ${MAGENTA}npm install ${missingModules.join(' ')}${RESET}`); console.log(' 或'); console.log(` ${MAGENTA}yarn add ${missingModules.join(' ')}${RESET}`); console.log(' 或'); console.log(` ${MAGENTA}pnpm install ${missingModules.join(' ')}${RESET}`); console.log(' (如果已安装但仍提示未找到,请尝试删除 node_modules 目录和 package-lock.json/yarn.lock 文件后重新安装)'); } if (!fs.existsSync(SERVER_SCRIPT_PATH)) { console.error(` ${RED}缺少必要的服务器脚本文件: ${SERVER_SCRIPT_FILENAME}${RESET}`); console.error(` 请确保它和 auto_connect_aistudio.cjs 在同一个文件夹内。`); } console.log(`${RED}-------------------------------------------------${RESET}`); return false; } console.log(`\n${GREEN}✅ 所有依赖检查通过。${RESET}`); playwright = require('playwright'); // Load playwright only after checks return true; } // --- 步骤 2: 检查并启动 Chrome --- async function launchChrome() { console.log(`${CYAN}-------------------------------------------------${RESET}`); console.log(`${CYAN}--- 步骤 2: 启动或连接 Chrome (调试端口 ${DEBUGGING_PORT}) ---${RESET}`); // 首先检查端口是否被占用 if (isPortInUse(DEBUGGING_PORT)) { console.log(`${YELLOW}⚠️ 警告: 端口 ${DEBUGGING_PORT} 已被占用。${RESET}`); console.log(' 这通常意味着已经有一个 Chrome 实例在监听此端口。'); const question = `选择操作: [Y/n] ${GREEN}Y (默认): 尝试连接现有 Chrome 实例并启动 API 服务器。${RESET} ${YELLOW}n: 自动强行结束占用端口 ${DEBUGGING_PORT} 的进程,然后启动新的 Chrome 实例。${RESET} 请输入选项 [Y/n]: `; const answer = await askQuestion(question); if (answer.toLowerCase() === 'n') { console.log(`\n好的,您选择了启动新实例。将尝试自动清理端口...`); const pids = findPidsUsingPort(DEBUGGING_PORT); if (pids.length > 0) { const killSuccess = killProcesses(pids); if (killSuccess) { console.log(` ${GREEN}✅ 尝试结束进程完成。等待 1 秒检查端口...${RESET}`); await new Promise(resolve => setTimeout(resolve, 1000)); // 短暂等待 if (isPortInUse(DEBUGGING_PORT)) { console.error(`${RED}❌ 错误: 尝试结束后,端口 ${DEBUGGING_PORT} 仍然被占用。${RESET}`); console.error(' 可能原因:权限不足,或进程未能正常终止。请尝试手动结束进程。' ); // 提供手动清理提示 console.log(`${YELLOW}提示: 您可以使用以下命令查找进程 ID (PID):${RESET}`); if (process.platform === 'win32') { console.log(` - 在 CMD 或 PowerShell 中: netstat -ano | findstr :${DEBUGGING_PORT}`); console.log(' - 找到 PID 后,使用: taskkill /F /PID '); } else { // macOS or Linux console.log(` - 在终端中: lsof -t -i:${DEBUGGING_PORT}`); console.log(' - 找到 PID 后,使用: kill -9 '); } await askQuestion('请在手动结束进程后,按 Enter 键重试脚本...'); process.exit(1); // 退出,让用户处理后重跑 } else { console.log(` ${GREEN}✅ 端口 ${DEBUGGING_PORT} 现在空闲。${RESET}`); // 端口已清理,继续执行下面的 Chrome 启动流程 } } else { console.error(`${RED}❌ 错误: 尝试结束部分或全部占用端口的进程失败。${RESET}`); console.error(' 请检查日志中的具体错误信息,可能需要手动结束进程。'); await askQuestion('请在手动结束进程后,按 Enter 键重试脚本...'); process.exit(1); // 退出,让用户处理后重跑 } } else { console.log(`${YELLOW} 虽然端口被占用,但未能找到具体监听的进程 PID。可能情况复杂,建议手动检查。${RESET}` ); await askQuestion('请手动检查并确保端口空闲后,按 Enter 键重试脚本...'); process.exit(1); // 退出 } // 如果代码执行到这里,意味着端口清理成功,将继续启动 Chrome console.log(`\n准备启动新的 Chrome 实例...`); } else { console.log(`\n好的,将尝试连接到现有的 Chrome 实例...`); return 'use_existing'; // 特殊返回值,告知主流程跳过启动,直接连接 } } // --- 如果端口未被占用,或者用户选择 'n' 且自动清理成功 --- if (!chromeExecutablePath) { console.error(`${RED}❌ 错误: 未能在当前操作系统 (${process.platform}) 的常见路径找到 Chrome 可执行文件。${RESET}`); console.error(' 请确保已安装 Google Chrome,或修改脚本中的 getChromePath 函数以指向正确的路径。'); if (process.platform === 'win32') { console.error(' (已尝试查找 %ProgramFiles% 和 %ProgramFiles(x86)% 下的路径)'); } else if (process.platform === 'linux') { console.error(' (已尝试查找 /usr/bin/google-chrome, /usr/bin/google-chrome-stable, /opt/google/chrome/chrome)'); } return false; } console.log(` ${GREEN}找到 Chrome 路径:${RESET} ${chromeExecutablePath}`); // 只有在明确需要启动新实例时才提示关闭其他实例 // (如果上面选择了 'n' 并清理成功,这里 isPortInUse 应该返回 false) if (!isPortInUse(DEBUGGING_PORT)) { console.log(`${YELLOW}⚠️ 重要提示:为了确保新的调试端口生效,建议先手动完全退出所有*其他*可能干扰的 Google Chrome 实例。${RESET}`); console.log(' (在 macOS 上通常是 Cmd+Q,Windows/Linux 上是关闭所有窗口)'); await askQuestion('请确认已处理好其他 Chrome 实例,然后按 Enter 键继续启动...'); } else { // 理论上不应该到这里,因为端口已被清理或选择了 use_existing console.warn(` ${YELLOW}警告:端口 ${DEBUGGING_PORT} 意外地仍被占用。继续尝试启动,但这极有可能失败。${RESET}`); await askQuestion('请按 Enter 键继续尝试启动...'); } console.log(`正在尝试启动 Chrome...`); console.log(` 路径: "${chromeExecutablePath}"`); // --- 修改:添加启动参数 --- const chromeArgs = [ `--remote-debugging-port=${DEBUGGING_PORT}`, `--window-size=460,800` // 指定宽度为 460px,高度暂定为 800px (可以根据需要调整) // 你可以在这里添加其他需要的 Chrome 启动参数 ]; console.log(` 参数: ${chromeArgs.join(' ')}`); // 打印所有参数 try { const chromeProcess = spawn( chromeExecutablePath, chromeArgs, // 使用包含窗口大小的参数数组 { detached: true, stdio: 'ignore' } // Detach to allow script to exit independently if needed ); chromeProcess.unref(); // Allow parent process to exit independently console.log(`${GREEN}✅ Chrome 启动命令已发送 (指定窗口大小)。稍后将尝试连接...${RESET}`); console.log(`${DIM}⏳ 等待 3 秒让 Chrome 进程启动...${RESET}`); await new Promise(resolve => setTimeout(resolve, 3000)); return true; // 表示启动流程已尝试 } catch (error) { console.error(`${RED}❌ 启动 Chrome 时出错: ${error.message}${RESET}`); console.error(` 请检查路径 "${chromeExecutablePath}" 是否正确,以及是否有权限执行。`); return false; } } // --- 步骤 3: 连接 Playwright 并管理页面 (带重试) --- async function connectAndManagePage() { console.log(`${CYAN}-------------------------------------------------${RESET}`); console.log(`${CYAN}--- 步骤 3: 连接 Playwright 到 ${CDP_ADDRESS} (最多尝试 ${CONNECTION_RETRIES} 次) ---${RESET}`); let browser = null; let context = null; for (let i = 0; i < CONNECTION_RETRIES; i++) { try { console.log(`\n${DIM}尝试连接 Playwright (第 ${i + 1}/${CONNECTION_RETRIES} 次)...${RESET}`); browser = await playwright.chromium.connectOverCDP(CDP_ADDRESS, { timeout: CONNECT_TIMEOUT_MS }); console.log(`${GREEN}✅ 成功连接到 Chrome!${RESET}`); // Simplified context fetching await new Promise(resolve => setTimeout(resolve, 500)); // Short delay after connect const contexts = browser.contexts(); if (contexts && contexts.length > 0) { context = contexts[0]; console.log(`-> 获取到浏览器默认上下文。`); break; // Connection and context successful } else { // This case should be rare if connectOverCDP succeeded with a responsive Chrome throw new Error('连接成功,但无法获取浏览器上下文。Chrome 可能没有响应或未完全初始化。'); } } catch (error) { console.warn(` ${YELLOW}连接尝试 ${i + 1} 失败: ${error.message.split('\n')[0]}${RESET}`); if (browser && browser.isConnected()) { // Should not happen if connectOverCDP failed, but good practice await browser.close().catch(e => console.error("尝试关闭连接失败的浏览器时出错:", e)); } browser = null; context = null; if (i < CONNECTION_RETRIES - 1) { console.log(` ${YELLOW}可能原因: Chrome 未完全启动 / 端口 ${DEBUGGING_PORT} 未监听 / 端口被占用。${RESET}`); console.log(`${DIM} 等待 ${RETRY_DELAY_MS / 1000} 秒后重试...${RESET}`); await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS)); } else { console.error(`\n${RED}❌ 在 ${CONNECTION_RETRIES} 次尝试后仍然无法连接。${RESET}`); console.error(' 请再次检查:'); console.error(' 1. Chrome 是否真的已经通过脚本成功启动,并且窗口可见、已加载?(可能需要登录Google)'); console.error(` 2. 是否有其他程序占用了端口 ${DEBUGGING_PORT}?(检查命令: macOS/Linux: lsof -i :${DEBUGGING_PORT} | Windows: netstat -ano | findstr ${DEBUGGING_PORT})`); console.error(' 3. 启动 Chrome 时终端或系统是否有报错信息?'); console.error(' 4. 防火墙或安全软件是否阻止了本地回环地址(127.0.0.1)的连接?'); return false; } } } if (!browser || !context) { console.error(`${RED}-> 未能成功连接到浏览器或获取上下文。${RESET}`); return false; } // --- 连接成功后的页面管理逻辑 --- console.log(`\n${CYAN}--- 页面管理 ---${RESET}`); try { let targetPage = null; let pages = []; try { pages = context.pages(); } catch (err) { console.error(`${RED}❌ 获取现有页面列表时出错:${RESET}`, err); console.log(" 将尝试打开新页面..."); } console.log(`${DIM}-> 检查 ${pages.length} 个已存在的页面...${RESET}`); const aiStudioUrlPattern = 'aistudio.google.com/'; const loginUrlPattern = 'accounts.google.com/'; for (const page of pages) { try { if (!page.isClosed()) { const pageUrl = page.url(); console.log(`${DIM} 检查页面: ${pageUrl}${RESET}`); // Prioritize AI Studio pages, then login pages if (pageUrl.includes(aiStudioUrlPattern)) { console.log(`-> ${GREEN}找到 AI Studio 页面:${RESET} ${pageUrl}`); targetPage = page; // Ensure it's the target URL if possible if (!pageUrl.includes('/prompts/new_chat')) { console.log(`${YELLOW} 非目标页面,尝试导航到 ${TARGET_URL}...${RESET}`); try { await targetPage.goto(TARGET_URL, { waitUntil: 'domcontentloaded', timeout: NAVIGATION_TIMEOUT_MS }); console.log(` ${GREEN}导航成功:${RESET} ${targetPage.url()}`); } catch (navError) { console.warn(` ${YELLOW}警告:导航到 ${TARGET_URL} 失败: ${navError.message.split('\n')[0]}${RESET}`); console.warn(` ${YELLOW}将使用当前页面 (${pageUrl}),请稍后手动确认。${RESET}`); } } else { console.log(` ${GREEN}页面已在目标路径或子路径。${RESET}`); } break; // Found a good AI Studio page } else if (pageUrl.includes(loginUrlPattern) && !targetPage) { // Keep track of a login page if no AI studio page is found yet console.log(`-> ${YELLOW}发现 Google 登录页面,暂存。${RESET}`); targetPage = page; // Don't break here, keep looking for a direct AI Studio page } } } catch (pageError) { if (!page.isClosed()) { console.warn(` ${YELLOW}警告:评估或导航页面时出错: ${pageError.message.split('\n')[0]}${RESET}`); } // Avoid using a page that caused an error if (targetPage === page) { targetPage = null; } } } // If after checking all pages, the best we found was a login page if (targetPage && targetPage.url().includes(loginUrlPattern)) { console.log(`-> ${YELLOW}未找到直接的 AI Studio 页面,将使用之前找到的登录页面。${RESET}`); console.log(` ${YELLOW}请确保在该页面手动完成登录。${RESET}`); } // If no suitable page was found at all if (!targetPage) { console.log(`-> ${YELLOW}未找到合适的现有页面。正在打开新页面并导航到 ${TARGET_URL}...${RESET}`); try { targetPage = await context.newPage(); console.log(`${DIM} 正在导航...${RESET}`); await targetPage.goto(TARGET_URL, { waitUntil: 'domcontentloaded', timeout: NAVIGATION_TIMEOUT_MS }); console.log(`-> ${GREEN}新页面已打开并导航到:${RESET} ${targetPage.url()}`); } catch (newPageError) { console.error(`${RED}❌ 打开或导航新页面到 ${TARGET_URL} 失败: ${newPageError.message}${RESET}`); console.error(" 请检查网络连接,以及 Chrome 是否能正常访问该网址。可能需要手动登录。" ); await browser.close().catch(e => {}); return false; } } try { await targetPage.bringToFront(); console.log('-> 已尝试将目标页面置于前台。'); } catch (bringToFrontError) { console.warn(` ${YELLOW}警告:将页面置于前台失败: ${bringToFrontError.message.split('\n')[0]}${RESET}`); console.warn(` (这可能发生在窗口最小化或位于不同虚拟桌面上时,通常不影响连接)`); } await new Promise(resolve => setTimeout(resolve, 500)); // Small delay after bringToFront console.log(`\n${BRIGHT}${GREEN}🎉 --- AI Studio 连接准备完成 --- 🎉${RESET}`); console.log(`${GREEN}Chrome 已启动,Playwright 已连接,相关页面已找到或创建。${RESET}`); console.log(`${YELLOW}请确保在 Chrome 窗口中 AI Studio 页面处于可交互状态 (例如,已登录Google, 无弹窗)。${RESET}`); return true; } catch (error) { console.error(`\n${RED}❌ --- 步骤 3 页面管理失败 ---${RESET}`); console.error(' 在连接成功后,处理页面时发生错误:', error); if (browser && browser.isConnected()) { await browser.close().catch(e => console.error("关闭浏览器时出错:", e)); } return false; } finally { // 这里不再打印即将退出的日志,因为脚本会继续运行 server.js // console.log("-> auto_connect_aistudio.js 步骤3结束。"); // 不需要手动断开 browser 连接,因为是 connectOverCDP } } // --- 步骤 4: 启动 API 服务器 --- function startApiServer() { console.log(`${CYAN}-------------------------------------------------${RESET}`); console.log(`${CYAN}--- 步骤 4: 启动 API 服务器 ('node ${SERVER_SCRIPT_FILENAME}') ---${RESET}`); console.log(`${DIM} 脚本路径: ${SERVER_SCRIPT_PATH}${RESET}`); if (!fs.existsSync(SERVER_SCRIPT_PATH)) { console.error(`${RED}❌ 错误: 无法启动服务器,文件不存在: ${SERVER_SCRIPT_PATH}${RESET}`); process.exit(1); } console.log(`${DIM}正在启动: node ${SERVER_SCRIPT_PATH}${RESET}`); try { const serverProcess = spawn('node', [SERVER_SCRIPT_PATH], { stdio: 'inherit', cwd: __dirname }); serverProcess.on('error', (err) => { console.error(`${RED}❌ 启动 '${SERVER_SCRIPT_FILENAME}' 失败: ${err.message}${RESET}`); console.error(`请检查 Node.js 是否已安装并配置在系统 PATH 中,以及 '${SERVER_SCRIPT_FILENAME}' 文件是否有效。`); process.exit(1); }); serverProcess.on('exit', (code, signal) => { console.log(`\n${MAGENTA}👋 '${SERVER_SCRIPT_FILENAME}' 进程已退出 (代码: ${code}, 信号: ${signal})。${RESET}`); console.log("自动连接脚本执行结束。"); process.exit(code ?? 0); }); // Don't print the success message here, let server.cjs print its own ready message // console.log("✅ '${SERVER_SCRIPT_FILENAME}' 已启动。脚本将保持运行,直到服务器进程结束或被手动中断。"); } catch (error) { console.error(`${RED}❌ 启动 '${SERVER_SCRIPT_FILENAME}' 时发生意外错误: ${error.message}${RESET}`); process.exit(1); } } // --- 主执行流程 --- (async () => { console.log(`${MAGENTA}🚀 欢迎使用 AI Studio 自动连接与启动脚本 (跨平台优化, v2.9 自动端口清理) 🚀${RESET}`); console.log(`${MAGENTA}=================================================${RESET}`); if (!await checkDependencies()) { process.exit(1); } console.log(`${MAGENTA}=================================================${RESET}`); const launchResult = await launchChrome(); if (launchResult === false) { console.log(`${RED}❌ 启动 Chrome 失败,脚本终止。${RESET}`); process.exit(1); } // 如果 launchResult 是 'use_existing' 或 true, 都需要连接 console.log(`${MAGENTA}=================================================${RESET}`); if (!await connectAndManagePage()) { // 如果连接失败,并且我们是尝试连接到现有实例,给出更具体的提示 if (launchResult === 'use_existing') { console.error(`${RED}❌ 连接到现有 Chrome 实例 (端口 ${DEBUGGING_PORT}) 失败。${RESET}`); console.error(' 请确认:'); console.error(' 1. 占用该端口的确实是您想连接的 Chrome 实例。'); console.error(' 2. 该 Chrome 实例是以 --remote-debugging-port 参数启动的。'); console.error(' 3. Chrome 实例本身运行正常,没有崩溃或无响应。'); } process.exit(1); } // 无论 Chrome 是新启动的还是已存在的,只要连接成功,就启动 API 服务器 console.log(`${MAGENTA}=================================================${RESET}`); startApiServer(); })();