chrome-ai-bridge 2.4.0 → 2.5.2

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.
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  /**
7
- * Registry for managing MCP tools.
7
+ * Registry for managing tools.
8
8
  * Allows dynamic registration and querying of tools.
9
9
  */
10
10
  export class ToolRegistry {
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * By default, scope is derived from the current git root (or cwd fallback),
5
5
  * then hashed into a stable namespace.
6
- * This isolates lock files between different projects using the same MCP.
6
+ * This isolates lock files between different projects using the same chrome-ai-bridge instance.
7
7
  */
8
8
  import crypto from 'node:crypto';
9
9
  import path from 'node:path';
@@ -3,7 +3,7 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { askChatGPTFastWithTimings, askGeminiFastWithTimings, getClient } from '../fast-cdp/fast-chat.js';
6
+ import { askChatGPTFastWithTimings, askGeminiFastWithTimings, getClient, resetConnection } from '../fast-cdp/fast-chat.js';
7
7
  /**
8
8
  * GEMINI_STUCK_* エラーかどうかを判定
9
9
  */
@@ -13,19 +13,49 @@ function isGeminiStuckError(error) {
13
13
  }
14
14
  return false;
15
15
  }
16
+ /**
17
+ * 接続系エラー(リトライ対象)かどうかを判定。
18
+ * これらのエラーは resetConnection → 再接続で回復する可能性がある。
19
+ */
20
+ function isRetryableConnectionError(error) {
21
+ if (!(error instanceof Error))
22
+ return false;
23
+ const msg = error.message;
24
+ const patterns = [
25
+ 'RELAY_DISCONNECTED',
26
+ 'RELAY_STOPPED',
27
+ 'RELAY_REQUEST_TIMEOUT',
28
+ 'EXT_READY_TIMEOUT',
29
+ 'EXT_DISCONNECTED',
30
+ 'Extension not connected',
31
+ 'WebSocket not open',
32
+ ];
33
+ return patterns.some(p => msg.includes(p));
34
+ }
35
+ /**
36
+ * リトライすべきでないエラーかどうかを判定。
37
+ * 質問送信済みの場合やバジェット超過は再試行しても無意味または有害。
38
+ */
39
+ function isNonRetryableError(error) {
40
+ if (!(error instanceof Error))
41
+ return false;
42
+ const msg = error.message;
43
+ return msg.includes('TOOL_BUDGET_EXCEEDED') ||
44
+ msg.includes('Timed out waiting for function');
45
+ }
16
46
  /**
17
47
  * AIに質問を送信し、結果を返す
18
48
  * 接続確立からクエリ送信までを一括で行う
19
- * Geminiのスタックエラーの場合は自動リトライ
49
+ * 接続エラー・Geminiスタックエラーの場合は自動リトライ(resetConnection → 再接続)
20
50
  */
21
- export async function askAI(kind, question, debug) {
51
+ export async function askAI(kind, question, debug, budgetMs) {
22
52
  const askFn = kind === 'chatgpt' ? askChatGPTFastWithTimings : askGeminiFastWithTimings;
23
53
  const label = kind === 'chatgpt' ? 'ChatGPT' : 'Gemini';
24
- const maxRetries = kind === 'gemini' ? 2 : 1;
54
+ const maxRetries = 2;
25
55
  let lastError = null;
26
56
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
27
57
  try {
28
- const result = await askFn(question, debug);
58
+ const result = await askFn(question, debug, budgetMs);
29
59
  return {
30
60
  provider: label,
31
61
  success: true,
@@ -35,12 +65,26 @@ export async function askAI(kind, question, debug) {
35
65
  }
36
66
  catch (error) {
37
67
  lastError = error instanceof Error ? error : new Error(String(error));
38
- // Geminiのスタックエラーの場合はリトライ
39
- if (kind === 'gemini' && isGeminiStuckError(error) && attempt < maxRetries) {
40
- console.error(`[askAI] Gemini stuck error on attempt ${attempt}, retrying...`);
41
- // セッションは既にクリアされているのでそのままリトライ
68
+ if (isNonRetryableError(error) || attempt >= maxRetries) {
69
+ return {
70
+ provider: label,
71
+ success: false,
72
+ answer: '',
73
+ error: lastError.message,
74
+ };
75
+ }
76
+ // 接続系エラーまたは Gemini stuck → resetConnection してリトライ
77
+ if (isRetryableConnectionError(error) || isGeminiStuckError(error)) {
78
+ console.error(`[askAI] ${label} error on attempt ${attempt} (${isRetryableConnectionError(error) ? 'connection' : 'stuck'}), resetting and retrying...`);
79
+ try {
80
+ await resetConnection(kind);
81
+ }
82
+ catch {
83
+ // resetConnection failure is not fatal — retry anyway
84
+ }
42
85
  continue;
43
86
  }
87
+ // Unknown error — don't retry
44
88
  return {
45
89
  provider: label,
46
90
  success: false,
@@ -49,7 +93,7 @@ export async function askAI(kind, question, debug) {
49
93
  };
50
94
  }
51
95
  }
52
- // ここには到達しないはずだが、型安全のため
96
+ // Unreachable, but satisfies type checker
53
97
  return {
54
98
  provider: label,
55
99
  success: false,
@@ -59,10 +103,10 @@ export async function askAI(kind, question, debug) {
59
103
  }
60
104
  /**
61
105
  * AIへの接続を確立する(並列接続用)
62
- * Geminiのスタックエラーの場合は自動リトライ
106
+ * 接続エラー・Geminiスタックエラーの場合は自動リトライ(resetConnection → 再接続)
63
107
  */
64
108
  export async function connectAI(kind) {
65
- const maxRetries = kind === 'gemini' ? 2 : 1;
109
+ const maxRetries = 2;
66
110
  let lastError = null;
67
111
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
68
112
  try {
@@ -71,19 +115,30 @@ export async function connectAI(kind) {
71
115
  }
72
116
  catch (error) {
73
117
  lastError = error instanceof Error ? error : new Error(String(error));
74
- // Geminiのスタックエラーの場合はリトライ
75
- if (kind === 'gemini' && isGeminiStuckError(error) && attempt < maxRetries) {
76
- console.error(`[connectAI] Gemini stuck error on attempt ${attempt}, retrying...`);
77
- // セッションは既にクリアされているのでそのままリトライ
118
+ if (isNonRetryableError(error) || attempt >= maxRetries) {
119
+ return {
120
+ success: false,
121
+ error: lastError.message,
122
+ };
123
+ }
124
+ if (isRetryableConnectionError(error) || isGeminiStuckError(error)) {
125
+ console.error(`[connectAI] ${kind} error on attempt ${attempt} (${isRetryableConnectionError(error) ? 'connection' : 'stuck'}), resetting and retrying...`);
126
+ try {
127
+ await resetConnection(kind);
128
+ }
129
+ catch {
130
+ // resetConnection failure is not fatal — retry anyway
131
+ }
78
132
  continue;
79
133
  }
134
+ // Unknown error — don't retry
80
135
  return {
81
136
  success: false,
82
137
  error: lastError.message,
83
138
  };
84
139
  }
85
140
  }
86
- // ここには到達しないはずだが、型安全のため
141
+ // Unreachable, but satisfies type checker
87
142
  return {
88
143
  success: false,
89
144
  error: lastError?.message || 'Unknown error',
@@ -61,7 +61,7 @@ export const askChatGptGeminiWeb = defineTool({
61
61
  schema: {
62
62
  question: z
63
63
  .string()
64
- .describe('Question to ask. Do not include secrets/PII. No mention of MCP/AI.'),
64
+ .describe('Question to ask. Do not include secrets/PII. No mention of AI bridging.'),
65
65
  debug: z
66
66
  .boolean()
67
67
  .optional()
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import z from 'zod';
7
- import { askChatGPTFastWithTimings } from '../fast-cdp/fast-chat.js';
7
+ import { askAI } from './ai-helpers.js';
8
8
  import { ToolCategories } from './categories.js';
9
9
  import { defineTool } from './ToolDefinition.js';
10
10
  /**
@@ -57,7 +57,7 @@ export const askChatGPTWeb = defineTool({
57
57
  schema: {
58
58
  question: z
59
59
  .string()
60
- .describe('Question to ask. Do not include secrets/PII. No mention of MCP/AI.'),
60
+ .describe('Question to ask. Do not include secrets/PII. No mention of AI bridging.'),
61
61
  debug: z
62
62
  .boolean()
63
63
  .optional()
@@ -70,15 +70,15 @@ export const askChatGPTWeb = defineTool({
70
70
  },
71
71
  handler: async (request, response) => {
72
72
  const { question, debug } = request.params;
73
- try {
74
- const result = await askChatGPTFastWithTimings(question, debug);
75
- response.appendResponseLine(result.answer || '(空の応答)');
73
+ const result = await askAI('chatgpt', question, debug);
74
+ if (result.success) {
75
+ response.appendResponseLine(result.answer);
76
76
  if (debug && result.debug) {
77
77
  response.appendResponseLine(formatDebugInfo(result.debug));
78
78
  }
79
79
  }
80
- catch (error) {
81
- response.appendResponseLine(`❌ ChatGPT接続に失敗しました: ${error instanceof Error ? error.message : String(error)}`);
80
+ else {
81
+ response.appendResponseLine(`❌ ChatGPT接続に失敗しました: ${result.error}`);
82
82
  }
83
83
  },
84
84
  });
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import z from 'zod';
7
- import { askGeminiFastWithTimings } from '../fast-cdp/fast-chat.js';
7
+ import { askAI } from './ai-helpers.js';
8
8
  import { ToolCategories } from './categories.js';
9
9
  import { defineTool } from './ToolDefinition.js';
10
10
  /**
@@ -58,7 +58,7 @@ export const askGeminiWeb = defineTool({
58
58
  schema: {
59
59
  question: z
60
60
  .string()
61
- .describe('Question to ask. Do not include secrets/PII. No mention of MCP/AI.'),
61
+ .describe('Question to ask. Do not include secrets/PII. No mention of AI bridging.'),
62
62
  debug: z
63
63
  .boolean()
64
64
  .optional()
@@ -71,27 +71,15 @@ export const askGeminiWeb = defineTool({
71
71
  },
72
72
  handler: async (request, response) => {
73
73
  const { question, debug } = request.params;
74
- const maxRetries = 2;
75
- let lastError = null;
76
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
77
- try {
78
- const result = await askGeminiFastWithTimings(question, debug);
79
- response.appendResponseLine(result.answer || '(空の応答)');
80
- if (debug && result.debug) {
81
- response.appendResponseLine(formatDebugInfo(result.debug));
82
- }
83
- return;
84
- }
85
- catch (error) {
86
- lastError = error instanceof Error ? error : new Error(String(error));
87
- // GEMINI_STUCK_* エラーの場合はリトライ
88
- if (lastError.message.includes('GEMINI_STUCK_') && attempt < maxRetries) {
89
- console.error(`[ask_gemini_web] Gemini stuck error on attempt ${attempt}, retrying...`);
90
- continue;
91
- }
92
- response.appendResponseLine(`❌ Gemini接続に失敗しました: ${lastError.message}`);
93
- return;
74
+ const result = await askAI('gemini', question, debug);
75
+ if (result.success) {
76
+ response.appendResponseLine(result.answer);
77
+ if (debug && result.debug) {
78
+ response.appendResponseLine(formatDebugInfo(result.debug));
94
79
  }
95
80
  }
81
+ else {
82
+ response.appendResponseLine(`❌ Gemini接続に失敗しました: ${result.error}`);
83
+ }
96
84
  },
97
85
  });
@@ -18,19 +18,22 @@ export const optionalTools = [
18
18
  ];
19
19
  /**
20
20
  * Check if web-llm tools should be loaded.
21
- * Returns false if MCP_DISABLE_WEB_LLM is set to 'true'.
21
+ * Returns false if CAI_DISABLE_WEB_LLM is set to 'true'.
22
22
  */
23
23
  export function shouldLoadWebLlmTools() {
24
- const disable = process.env.MCP_DISABLE_WEB_LLM;
24
+ const disable = process.env.CAI_DISABLE_WEB_LLM || process.env.MCP_DISABLE_WEB_LLM;
25
+ if (process.env.MCP_DISABLE_WEB_LLM && !process.env.CAI_DISABLE_WEB_LLM) {
26
+ console.error('[deprecation] MCP_DISABLE_WEB_LLM is deprecated, use CAI_DISABLE_WEB_LLM instead');
27
+ }
25
28
  return disable !== 'true' && disable !== '1';
26
29
  }
27
30
  /**
28
31
  * Register optional tools with a ToolRegistry.
29
- * Respects MCP_DISABLE_WEB_LLM environment variable.
32
+ * Respects CAI_DISABLE_WEB_LLM environment variable.
30
33
  */
31
34
  export function registerOptionalTools(registry) {
32
35
  if (!shouldLoadWebLlmTools()) {
33
- console.error('[tools] Web-LLM tools disabled via MCP_DISABLE_WEB_LLM');
36
+ console.error('[tools] Web-LLM tools disabled via CAI_DISABLE_WEB_LLM');
34
37
  return 0;
35
38
  }
36
39
  let count = 0;
@@ -68,7 +71,7 @@ export const WEB_LLM_TOOLS_INFO = {
68
71
  disclaimer: 'Web-LLM tools (ask_chatgpt_web, ask_gemini_web, ask_chatgpt_gemini_web, take_cdp_snapshot, get_page_dom) are experimental and best-effort. ' +
69
72
  'They depend on specific website UIs and may break when those UIs change. ' +
70
73
  'For production use, consider using official APIs instead.',
71
- disableEnvVar: 'MCP_DISABLE_WEB_LLM',
74
+ disableEnvVar: 'CAI_DISABLE_WEB_LLM',
72
75
  tools: [
73
76
  'ask_chatgpt_web',
74
77
  'ask_gemini_web',
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "chrome-ai-bridge",
3
- "version": "2.4.0",
4
- "description": "MCP server bridging Chrome extension and AI assistants (ChatGPT, Gemini). Extension-only mode - no Puppeteer.",
3
+ "version": "2.5.2",
4
+ "description": "CLI tool for querying ChatGPT and Gemini via Chrome extension. No Puppeteer required.",
5
5
  "type": "module",
6
- "bin": "./scripts/cli.mjs",
6
+ "bin": {
7
+ "chrome-ai-bridge": "./scripts/cli.mjs",
8
+ "cab": "./scripts/cab"
9
+ },
7
10
  "main": "index.js",
8
11
  "exports": {
9
12
  ".": "./index.js",
@@ -13,14 +16,14 @@
13
16
  "build": "tsc && cp -R src/extension build/ && node scripts/reload-extension.mjs",
14
17
  "build:noext": "tsc && cp -R src/extension build/",
15
18
  "reload-ext": "node scripts/reload-extension.mjs",
16
- "dev": "MCP_ENV=development node scripts/mcp-wrapper.mjs",
19
+ "dev": "CAI_ENV=development node scripts/daemon-wrapper.mjs",
17
20
  "typecheck": "tsc --noEmit",
18
21
  "format": "eslint --cache --fix . && prettier --write --cache .",
19
22
  "check-format": "eslint --cache . && prettier --check --cache .;",
20
23
  "start": "npm run build && node build/src/index.js",
21
- "start-debug": "DEBUG=mcp:* DEBUG_COLORS=false npm run build && node build/src/index.js",
24
+ "start-debug": "DEBUG=cab:* DEBUG_COLORS=false npm run build && node build/src/index.js",
22
25
  "cleanup": "node scripts/cleanup.mjs",
23
- "restart-mcp": "node scripts/cleanup.mjs",
26
+ "restart": "node scripts/cleanup.mjs",
24
27
  "test:chatgpt": "npm run build && node scripts/test-fast-chat.mjs chatgpt",
25
28
  "test:gemini": "npm run build && node scripts/test-fast-chat.mjs gemini",
26
29
  "test:both": "npm run build && node scripts/test-fast-chat.mjs both",
@@ -31,23 +34,20 @@
31
34
  "cdp:gemini": "npm run build && node scripts/cdp-snapshot.mjs gemini",
32
35
  "measure:chatgpt": "npm run build && node scripts/measure-timings.mjs chatgpt",
33
36
  "measure:gemini": "npm run build && node scripts/measure-timings.mjs gemini",
34
- "test:mcp": "npm run build && node scripts/test-mcp.mjs",
35
- "test:mcp:chatgpt": "npm run build && node scripts/test-mcp.mjs --chatgpt",
36
- "test:mcp:gemini": "npm run build && node scripts/test-mcp.mjs --gemini",
37
- "test:mcp:parallel": "npm run build && node scripts/test-mcp.mjs --parallel",
38
37
  "test:network": "npm run build && node scripts/test-network-intercept.mjs",
39
- "test": "npm run build:noext && node scripts/test-mcp.mjs --tools-only",
38
+ "test": "npm run build:noext && npm run typecheck",
39
+ "check:extension-discovery": "node scripts/check-extension-discovery.mjs",
40
40
  "discord:collect": "node scripts/discord-readonly-collector.mjs",
41
41
  "discord:preflight": "node scripts/discord-readonly-preflight.mjs",
42
42
  "discord:status": "node scripts/discord-readonly-status.mjs",
43
43
  "docs": "npm run build:noext && node --experimental-strip-types scripts/generate-docs.ts",
44
- "generate-docs": "npm run docs",
45
- "sync-server-json-version": "node --experimental-strip-types scripts/sync-server-json-version.ts"
44
+ "generate-docs": "npm run docs"
46
45
  },
47
46
  "files": [
48
47
  "build/src",
49
48
  "build/extension",
50
49
  "scripts/cli.mjs",
50
+ "scripts/cab",
51
51
  "scripts/browser-globals-mock.mjs",
52
52
  "README.md",
53
53
  "LICENSE",
@@ -63,21 +63,20 @@
63
63
  "url": "https://github.com/usedhonda/chrome-ai-bridge/issues"
64
64
  },
65
65
  "homepage": "https://github.com/usedhonda/chrome-ai-bridge#readme",
66
- "mcpName": "chrome-ai-bridge",
67
66
  "keywords": [
68
- "mcp",
69
67
  "chrome",
70
68
  "chrome-extension",
71
69
  "chatgpt",
72
70
  "gemini",
73
- "ai-bridge"
71
+ "ai-bridge",
72
+ "cli"
74
73
  ],
75
74
  "dependencies": {
76
- "@modelcontextprotocol/sdk": "1.18.1",
77
75
  "debug": "4.4.3",
78
76
  "playwright": "^1.58.1",
79
77
  "ws": "^8.19.0",
80
- "yargs": "18.0.0"
78
+ "yargs": "18.0.0",
79
+ "zod": "^3.25.76"
81
80
  },
82
81
  "devDependencies": {
83
82
  "@eslint/js": "^9.35.0",
package/scripts/cab ADDED
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env bash
2
+ # cab - Chrome AI Bridge CLI
3
+ # Usage: cab <chatgpt|gemini|both> "question"
4
+ # cab serve - Start daemon directly
5
+ # cab health - Check daemon health
6
+ # cab stop - Stop the daemon
7
+ # cab --help - Show help
8
+
9
+ set -euo pipefail
10
+
11
+ CAB_PORT="${CAI_IPC_PORT:-9321}"
12
+ CAB_HOST="127.0.0.1"
13
+ CAB_BASE="http://${CAB_HOST}:${CAB_PORT}"
14
+ # Resolve symlinks to get the real script path (macOS compatible)
15
+ _CAB_SELF="$(realpath "$0" 2>/dev/null || python3 -c "import os,sys; print(os.path.realpath(sys.argv[1]))" "$0")"
16
+ CAB_DIR="$(cd "$(dirname "$_CAB_SELF")" && pwd)"
17
+ CAB_LOG_DIR="${HOME}/.cache/chrome-ai-bridge"
18
+ CAB_LOG="${CAB_LOG_DIR}/cab-daemon.log"
19
+ CAB_STARTUP_TIMEOUT=30
20
+
21
+ usage() {
22
+ cat <<'HELP'
23
+ cab - Chrome AI Bridge CLI
24
+
25
+ Usage:
26
+ cab chatgpt "question" Ask ChatGPT
27
+ cab gemini "question" Ask Gemini
28
+ cab both "question" Ask both in parallel
29
+
30
+ cab serve Start daemon (foreground)
31
+ cab health Check daemon status
32
+ cab stop Stop the daemon
33
+ cab --help Show this help
34
+
35
+ Environment:
36
+ CAI_IPC_PORT Daemon port (default: 9321)
37
+
38
+ Examples:
39
+ cab chatgpt "How to deep copy in JS?"
40
+ cab both "Explain async/await"
41
+ HELP
42
+ }
43
+
44
+ # Check if daemon is healthy
45
+ check_health() {
46
+ curl -sf "${CAB_BASE}/health" 2>/dev/null
47
+ }
48
+
49
+ # Start daemon in background, wait for health
50
+ ensure_daemon() {
51
+ if check_health >/dev/null 2>&1; then
52
+ return 0
53
+ fi
54
+
55
+ mkdir -p "${CAB_LOG_DIR}"
56
+ echo "Starting cab daemon..." >&2
57
+
58
+ nohup node \
59
+ --import "${CAB_DIR}/browser-globals-mock.mjs" \
60
+ "${CAB_DIR}/../build/src/main.js" \
61
+ --daemon \
62
+ >> "${CAB_LOG}" 2>&1 &
63
+ local daemon_pid=$!
64
+ disown "$daemon_pid" 2>/dev/null || true
65
+
66
+ local waited=0
67
+ while [ "$waited" -lt "$CAB_STARTUP_TIMEOUT" ]; do
68
+ if check_health >/dev/null 2>&1; then
69
+ echo "Daemon ready (pid=${daemon_pid}, port=${CAB_PORT})" >&2
70
+ return 0
71
+ fi
72
+ # Check if process is still alive
73
+ if ! kill -0 "$daemon_pid" 2>/dev/null; then
74
+ echo "Error: daemon process exited unexpectedly. Check ${CAB_LOG}" >&2
75
+ return 1
76
+ fi
77
+ sleep 1
78
+ waited=$((waited + 1))
79
+ done
80
+
81
+ echo "Error: daemon did not become healthy within ${CAB_STARTUP_TIMEOUT}s" >&2
82
+ return 1
83
+ }
84
+
85
+ # Send question via REST API
86
+ ask_ai() {
87
+ local target="$1"
88
+ local question="$2"
89
+ local debug="${3:-false}"
90
+
91
+ ensure_daemon || exit 1
92
+
93
+ local payload
94
+ payload=$(printf '{"target":"%s","question":"%s","debug":%s}' \
95
+ "$target" \
96
+ "$(echo "$question" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' ' ')" \
97
+ "$debug")
98
+
99
+ local response
100
+ response=$(curl -sf -X POST "${CAB_BASE}/api/ask" \
101
+ -H 'Content-Type: application/json' \
102
+ -d "$payload" \
103
+ --max-time 300) || {
104
+ echo "Error: failed to connect to daemon at ${CAB_BASE}" >&2
105
+ exit 1
106
+ }
107
+
108
+ # Parse and output results
109
+ local success
110
+ success=$(echo "$response" | node -e "
111
+ let d='';
112
+ process.stdin.on('data',c=>d+=c);
113
+ process.stdin.on('end',()=>{
114
+ try {
115
+ const r=JSON.parse(d);
116
+ if(!r.success && r.error){
117
+ console.error('Error: '+r.error);
118
+ process.exit(1);
119
+ }
120
+ for(const res of r.results||[]){
121
+ if(r.results.length>1) console.log('--- '+res.provider+' ---');
122
+ if(res.success){
123
+ console.log(res.answer);
124
+ } else {
125
+ console.error('['+res.provider+' error] '+(res.error||'Unknown error'));
126
+ }
127
+ if(r.results.length>1) console.log('');
128
+ }
129
+ } catch(e){
130
+ console.error('Error parsing response: '+e.message);
131
+ process.exit(1);
132
+ }
133
+ });
134
+ " 2>&1) || exit 1
135
+
136
+ echo "$success"
137
+ }
138
+
139
+ # --- Main ---
140
+
141
+ if [ $# -eq 0 ]; then
142
+ usage
143
+ exit 1
144
+ fi
145
+
146
+ case "$1" in
147
+ --help|-h)
148
+ usage
149
+ exit 0
150
+ ;;
151
+ health)
152
+ if result=$(check_health 2>/dev/null); then
153
+ echo "$result" | node -e "
154
+ let d='';
155
+ process.stdin.on('data',c=>d+=c);
156
+ process.stdin.on('end',()=>{
157
+ const r=JSON.parse(d);
158
+ console.log('Status: '+r.status);
159
+ console.log('PID: '+r.pid);
160
+ console.log('Version: '+r.version);
161
+ console.log('Sessions: '+r.activeSessions+'/'+r.sessionCapacity);
162
+ });
163
+ "
164
+ else
165
+ echo "Daemon is not running" >&2
166
+ exit 1
167
+ fi
168
+ ;;
169
+ serve)
170
+ exec node \
171
+ --import "${CAB_DIR}/browser-globals-mock.mjs" \
172
+ "${CAB_DIR}/../build/src/main.js" \
173
+ --daemon \
174
+ "${@:2}"
175
+ ;;
176
+ stop)
177
+ if result=$(check_health 2>/dev/null); then
178
+ pid=$(echo "$result" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.parse(d).pid));")
179
+ if [ -n "$pid" ] && kill -TERM "$pid" 2>/dev/null; then
180
+ echo "Stopped daemon (pid=${pid})" >&2
181
+ else
182
+ echo "Failed to stop daemon" >&2
183
+ exit 1
184
+ fi
185
+ else
186
+ echo "Daemon is not running" >&2
187
+ fi
188
+ ;;
189
+ chatgpt|gemini|both)
190
+ if [ $# -lt 2 ]; then
191
+ echo "Error: missing question argument" >&2
192
+ echo "Usage: cab $1 \"your question here\"" >&2
193
+ exit 1
194
+ fi
195
+ ask_ai "$1" "$2" "${3:-false}"
196
+ ;;
197
+ *)
198
+ echo "Error: unknown command '$1'" >&2
199
+ usage
200
+ exit 1
201
+ ;;
202
+ esac
package/scripts/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * CLI Entry Point for chrome-ai-bridge
4
4
  *
5
- * Launches the MCP server with browser-globals mock in a child process.
5
+ * Launches Chrome AI Bridge daemon with browser-globals mock in a child process.
6
6
  *
7
7
  * Why child process:
8
8
  * - main.js may intentionally enter a never-returning proxy path.
@@ -1,60 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- export class McpResponse {
7
- #textResponseLines = [];
8
- #images = [];
9
- // Stub methods for interface compatibility (not supported in extension mode)
10
- setIncludePages(_value) {
11
- // Not supported in extension-only mode
12
- }
13
- setIncludeSnapshot(_value) {
14
- // Not supported in extension-only mode
15
- }
16
- setIncludeNetworkRequests(_value, _options) {
17
- // Not supported in extension-only mode
18
- }
19
- setIncludeConsoleData(_value) {
20
- // Not supported in extension-only mode
21
- }
22
- attachNetworkRequest(_url) {
23
- // Not supported in extension-only mode
24
- }
25
- appendResponseLine(value) {
26
- this.#textResponseLines.push(value);
27
- }
28
- attachImage(value) {
29
- this.#images.push(value);
30
- }
31
- get responseLines() {
32
- return this.#textResponseLines;
33
- }
34
- get images() {
35
- return this.#images;
36
- }
37
- async handle(toolName, _context) {
38
- return this.format(toolName);
39
- }
40
- format(toolName) {
41
- const response = [`# ${toolName} response`];
42
- for (const line of this.#textResponseLines) {
43
- response.push(line);
44
- }
45
- const text = {
46
- type: 'text',
47
- text: response.join('\n'),
48
- };
49
- const images = this.#images.map(imageData => {
50
- return {
51
- type: 'image',
52
- ...imageData,
53
- };
54
- });
55
- return [text, ...images];
56
- }
57
- resetResponseLineForTesting() {
58
- this.#textResponseLines = [];
59
- }
60
- }