chrome-ai-bridge 1.0.16 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
+ import { execSync } from 'node:child_process';
6
7
  import fs from 'node:fs';
7
8
  import os from 'node:os';
8
9
  import path from 'node:path';
@@ -421,7 +422,7 @@ export function getDevelopmentExtensionPaths() {
421
422
  return developmentExtensionPaths;
422
423
  }
423
424
  export async function launch(options) {
424
- const { channel, executablePath, customDevTools, headless, isolated, loadExtension, loadExtensionsDir, loadSystemExtensions, chromeProfile, } = options;
425
+ const { channel, executablePath, customDevTools, headless, isolated, loadExtension, loadExtensionsDir, loadSystemExtensions, chromeProfile, focus = false, } = options;
425
426
  // Reset development extension paths
426
427
  developmentExtensionPaths = [];
427
428
  // Resolve user data directory using new profile resolver (v0.15.0+)
@@ -562,6 +563,22 @@ export async function launch(options) {
562
563
  }
563
564
  let browser;
564
565
  let finalUserDataDir = userDataDir;
566
+ // Remember current foreground app before Chrome launch (for background mode)
567
+ let previousApp = null;
568
+ if (!focus && !effectiveHeadless && os.platform() === 'darwin') {
569
+ try {
570
+ previousApp = execSync(`osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true'`, { encoding: 'utf-8', timeout: 5000 }).trim();
571
+ console.error(`📋 Current foreground app: ${previousApp}`);
572
+ }
573
+ catch (error) {
574
+ console.warn(`⚠️ Could not detect foreground app: ${error instanceof Error ? error.message : String(error)}`);
575
+ }
576
+ }
577
+ // Windows/Linux: Add --start-minimized for background mode
578
+ if (!focus && !effectiveHeadless && os.platform() !== 'darwin') {
579
+ args.push('--start-minimized');
580
+ console.error('📋 Added --start-minimized for background mode');
581
+ }
565
582
  try {
566
583
  browser = await puppeteer.launch({
567
584
  ...connectOptions,
@@ -660,6 +677,18 @@ export async function launch(options) {
660
677
  });
661
678
  console.error('Applied navigator.webdriver bypass to existing page');
662
679
  }
680
+ // Restore focus to previous app (background mode on macOS)
681
+ if (!focus && !effectiveHeadless && previousApp && os.platform() === 'darwin') {
682
+ try {
683
+ // Small delay to ensure Chrome window is fully rendered
684
+ await new Promise(resolve => setTimeout(resolve, 500));
685
+ execSync(`osascript -e 'tell application "${previousApp}" to activate'`, { timeout: 5000 });
686
+ console.error(`✅ Restored focus to: ${previousApp}`);
687
+ }
688
+ catch (error) {
689
+ console.warn(`⚠️ Could not restore focus: ${error instanceof Error ? error.message : String(error)}`);
690
+ }
691
+ }
663
692
  return browser;
664
693
  }
665
694
  catch (error) {
package/build/src/cli.js CHANGED
@@ -79,6 +79,11 @@ export const cliOptions = {
79
79
  description: 'Explicitly specify the project root directory for profile isolation. Overrides MCP roots/list. Useful when roots/list is not available.',
80
80
  conflicts: 'browserUrl',
81
81
  },
82
+ focus: {
83
+ type: 'boolean',
84
+ description: 'Bring Chrome window to foreground on launch. By default, Chrome launches in the background to avoid interrupting your work.',
85
+ default: false,
86
+ },
82
87
  };
83
88
  export function parseArguments(version, argv = process.argv) {
84
89
  const yargsInstance = yargs(hideBin(argv))
package/build/src/main.js CHANGED
@@ -140,6 +140,7 @@ async function getContext() {
140
140
  userDataDir: args.userDataDir,
141
141
  logFile,
142
142
  rootsInfo: cachedRootsInfo, // Pass roots info to browser
143
+ focus: args.focus, // v1.0.18: Background mode control
143
144
  };
144
145
  const browser = await resolveBrowser(browserOptions);
145
146
  // Announce browser PID for graceful shutdown
@@ -267,32 +267,27 @@ export const askGeminiWeb = defineTool({
267
267
  }
268
268
  response.appendResponseLine('✅ ログイン確認完了');
269
269
  response.appendResponseLine('質問を送信中...');
270
- // Input text using Puppeteer's type() for proper Angular state updates
271
- // Gemini uses Angular and requires real keyboard events to update internal state
270
+ // Input text using innerText + event dispatch for proper Angular state updates
271
+ // This is more reliable than keyboard.type() which can cause sync issues with Angular
272
272
  const textboxSelector = '[role="textbox"]';
273
273
  const textbox = await page.$(textboxSelector);
274
274
  if (!textbox) {
275
275
  response.appendResponseLine('❌ 入力欄が見つかりません');
276
276
  return;
277
277
  }
278
- // Click to focus and clear existing content
279
- await textbox.click({ clickCount: 3 }); // Triple-click to select all
280
- await page.keyboard.press('Backspace'); // Clear selection
281
- // Type the question using real keyboard events (essential for Angular)
282
- // IMPORTANT: Split by newlines and use Shift+Enter for line breaks
283
- // (Enter alone triggers send in Gemini)
284
- const lines = sanitizedQuestion.split('\n');
285
- for (let i = 0; i < lines.length; i++) {
286
- if (i > 0) {
287
- // Shift+Enter for newline (Enter alone sends the message)
288
- await page.keyboard.down('Shift');
289
- await page.keyboard.press('Enter');
290
- await page.keyboard.up('Shift');
291
- }
292
- await page.keyboard.type(lines[i], { delay: 2 });
293
- }
278
+ // Click to focus
279
+ await textbox.click();
280
+ // Use innerText + input event dispatch for proper Angular state updates
281
+ // This approach is more reliable than keyboard.type() which can cause sync issues
282
+ await textbox.evaluate((el, text) => {
283
+ // Clear and set content
284
+ el.innerText = text;
285
+ // Dispatch input event to notify Angular of the change
286
+ el.dispatchEvent(new Event('input', { bubbles: true }));
287
+ el.dispatchEvent(new Event('change', { bubbles: true }));
288
+ }, sanitizedQuestion);
294
289
  // Wait for Angular to process the input and show send button
295
- await new Promise(resolve => setTimeout(resolve, 500));
290
+ await new Promise(resolve => setTimeout(resolve, 300));
296
291
  // 質問送信前に model-response 要素数を記録(ChatGPTと同じカウント方式)
297
292
  const initialModelResponseCount = await page.evaluate(() => {
298
293
  return document.querySelectorAll('model-response').length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-ai-bridge",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "MCP server bridging Chrome browser and AI assistants (ChatGPT, Gemini). Browser automation + AI consultation.",
5
5
  "type": "module",
6
6
  "bin": "./scripts/cli.mjs",