diff --git a/.env b/.env new file mode 100644 index 0000000..0183542 --- /dev/null +++ b/.env @@ -0,0 +1,25 @@ +# ── Gemini Skill 环境变量 / Environment Variables ── + +# Chrome / Chromium 可执行文件路径(不设则需手动启动 Chrome) +# Path to Chrome/Chromium executable (if not set, you need to start Chrome manually) +CHROME_PATH= + +# CDP 远程调试端口 +# CDP remote debugging port +CHROME_DEBUG_PORT= + +# Chrome 用户数据目录(保持登录态、cookies 等) +# Chrome user data directory (persists login session, cookies, etc.) +CHROME_USER_DATA_DIR= + +# 是否无头模式(true / false) +# Headless mode (true / false) +CHROME_HEADLESS= + +# CDP 协议超时时间(毫秒) +# CDP protocol timeout (milliseconds) +CHROME_PROTOCOL_TIMEOUT= + +# 截图 / 图片输出目录 +# Screenshot / image output directory +OUTPUT_DIR= diff --git a/src/browser.js b/src/browser.js index f46a25f..18a7593 100644 --- a/src/browser.js +++ b/src/browser.js @@ -14,9 +14,8 @@ import puppeteerCore from 'puppeteer-core'; import { addExtra } from 'puppeteer-extra'; import StealthPlugin from 'puppeteer-extra-plugin-stealth'; -import { homedir } from 'node:os'; -import { join } from 'node:path'; import { createConnection } from 'node:net'; +import config from './config.js'; // ── 用 puppeteer-extra 包装 puppeteer-core,注入 stealth 插件 ── const puppeteer = addExtra(puppeteerCore); @@ -25,14 +24,6 @@ puppeteer.use(StealthPlugin()); // ── 模块级单例:跨调用复用同一个浏览器 ── let _browser = null; -/** 默认配置 */ -const DEFAULTS = { - port: 9222, - userDataDir: join(homedir(), '.gemini-skill', 'chrome-data'), - headless: false, - protocolTimeout: 60_000, -}; - /** * 探测指定端口是否有 Chrome 在监听 * @param {number} port @@ -99,7 +90,7 @@ async function connectToChrome(port) { const browser = await puppeteer.connect({ browserURL, defaultViewport: null, - protocolTimeout: DEFAULTS.protocolTimeout, + protocolTimeout: config.chromeProtocolTimeout, }); console.log('[browser] connected to existing Chrome on port', port); return browser; @@ -125,7 +116,7 @@ async function launchChrome({ executablePath, port, userDataDir, headless }) { `--remote-debugging-port=${port}`, ], ignoreDefaultArgs: ['--enable-automation'], - protocolTimeout: DEFAULTS.protocolTimeout, + protocolTimeout: config.chromeProtocolTimeout, }); console.log('[browser] launched Chrome, pid:', browser.process()?.pid, 'port:', port, 'dataDir:', userDataDir); return browser; @@ -176,10 +167,10 @@ async function findOrCreateGeminiPage(browser) { */ export async function ensureBrowser(opts = {}) { const { - executablePath, - port = DEFAULTS.port, - userDataDir = DEFAULTS.userDataDir, - headless = DEFAULTS.headless, + executablePath = config.chromePath, + port = config.chromeDebugPort, + userDataDir = config.chromeUserDataDir, + headless = config.chromeHeadless, } = opts; // 1. 复用已有连接 diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..e69c4f1 --- /dev/null +++ b/src/config.js @@ -0,0 +1,63 @@ +/** + * config.js — 统一配置中心 + * + * 所有可配置项集中在这里,从环境变量读取,提供合理默认值。 + * 其他模块一律从 config 取值,不自己硬编码。 + * + * 环境变量来源(优先级从高到低): + * 1. 进程环境变量(process.env) + * 2. .env 文件(需调用方自行加载,如 dotenv) + * 3. 代码默认值 + */ +import { homedir } from 'node:os'; +import { join, resolve } from 'node:path'; + +const env = process.env; + +/** 辅助:读取布尔型环境变量 */ +function envBool(key, fallback) { + const val = env[key]; + if (val === undefined || val === '') return fallback; + return val === 'true' || val === '1'; +} + +/** 辅助:读取数字型环境变量 */ +function envInt(key, fallback) { + const val = env[key]; + if (val === undefined || val === '') return fallback; + const n = parseInt(val, 10); + return Number.isNaN(n) ? fallback : n; +} + +/** 辅助:读取字符串环境变量 */ +function envStr(key, fallback) { + const val = env[key]; + return (val !== undefined && val !== '') ? val : fallback; +} + +// ── 导出配置 ── + +const config = { + /** Chrome / Chromium 可执行文件路径(不设则需手动启动 Chrome) */ + chromePath: envStr('CHROME_PATH', undefined), + + /** CDP 远程调试端口 */ + chromeDebugPort: envInt('CHROME_DEBUG_PORT', 9222), + + /** Chrome 用户数据目录 */ + chromeUserDataDir: envStr( + 'CHROME_USER_DATA_DIR', + join(homedir(), '.gemini-skill', 'chrome-data'), + ), + + /** 是否无头模式 */ + chromeHeadless: envBool('CHROME_HEADLESS', false), + + /** CDP 协议超时时间(ms) */ + chromeProtocolTimeout: envInt('CHROME_PROTOCOL_TIMEOUT', 60_000), + + /** 截图 / 图片输出目录 */ + outputDir: envStr('OUTPUT_DIR', resolve('output')), +}; + +export default config; diff --git a/src/demo.js b/src/demo.js index e1ad62d..166936a 100644 --- a/src/demo.js +++ b/src/demo.js @@ -7,18 +7,18 @@ * chrome --remote-debugging-port=9222 --user-data-dir="~/.gemini-skill/chrome-data" * node src/demo.js * - * 方式 2:让 skill 自动启动 Chrome + * 方式 2:通过环境变量让 skill 自动启动 Chrome * CHROME_PATH="C:/Program Files/Google/Chrome/Application/chrome.exe" node src/demo.js + * + * 所有配置项见 .env,可直接编辑或通过命令行设环境变量。 */ import { createGeminiSession, disconnect } from './index.js'; async function main() { console.log('=== Gemini Skill Demo ===\n'); - // 创建会话(自动 connect 或 launch) - const { ops } = await createGeminiSession({ - executablePath: process.env.CHROME_PATH || undefined, - }); + // 创建会话(配置自动从环境变量读取,也可以传 opts 覆盖) + const { ops } = await createGeminiSession(); try { // 1. 探测页面状态 diff --git a/src/index.js b/src/index.js index a3a231d..a4ffba9 100644 --- a/src/index.js +++ b/src/index.js @@ -23,11 +23,13 @@ export { disconnect, close }; * 2. 无 Chrome + 提供了 executablePath → 自动 launch * 3. 无 Chrome + 无 executablePath → 报错并提示手动启动 * + * 所有参数均可通过环境变量配置(见 .env),opts 传参优先级更高。 + * * @param {object} [opts] - * @param {string} [opts.executablePath] - Chrome 路径(可选,仅自动启动时需要) - * @param {number} [opts.port=9222] - 调试端口 - * @param {string} [opts.userDataDir] - 用户数据目录(默认 ~/.gemini-skill/chrome-data) - * @param {boolean} [opts.headless=false] + * @param {string} [opts.executablePath] - Chrome 路径(env: CHROME_PATH) + * @param {number} [opts.port] - 调试端口(env: CHROME_DEBUG_PORT,默认 9222) + * @param {string} [opts.userDataDir] - 用户数据目录(env: CHROME_USER_DATA_DIR) + * @param {boolean} [opts.headless] - 无头模式(env: CHROME_HEADLESS,默认 false) * @returns {Promise<{ops: ReturnType, page: import('puppeteer-core').Page, browser: import('puppeteer-core').Browser}>} */ export async function createGeminiSession(opts = {}) {