chrome-devtools-mcp-for-extension 0.9.18 → 0.9.20

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.
@@ -8,6 +8,33 @@ import path from 'node:path';
8
8
  import z from 'zod';
9
9
  import { ToolCategories } from './categories.js';
10
10
  import { defineTool } from './ToolDefinition.js';
11
+ /**
12
+ * Path to store chat session data
13
+ */
14
+ const CHAT_SESSIONS_FILE = path.join(process.cwd(), 'docs/ask/chatgpt/.chat-sessions.json');
15
+ /**
16
+ * Load chat sessions from JSON file
17
+ */
18
+ async function loadChatSessions() {
19
+ try {
20
+ const data = await fs.promises.readFile(CHAT_SESSIONS_FILE, 'utf-8');
21
+ return JSON.parse(data);
22
+ }
23
+ catch {
24
+ return {};
25
+ }
26
+ }
27
+ /**
28
+ * Save a chat session for a project
29
+ */
30
+ async function saveChatSession(projectName, session) {
31
+ const sessions = await loadChatSessions();
32
+ sessions[projectName] = session;
33
+ // Ensure directory exists
34
+ const dir = path.dirname(CHAT_SESSIONS_FILE);
35
+ await fs.promises.mkdir(dir, { recursive: true });
36
+ await fs.promises.writeFile(CHAT_SESSIONS_FILE, JSON.stringify(sessions, null, 2), 'utf-8');
37
+ }
11
38
  /**
12
39
  * Sanitize question to remove sensitive information like passwords
13
40
  */
@@ -107,60 +134,30 @@ export const askChatGPTWeb = defineTool({
107
134
  return;
108
135
  }
109
136
  response.appendResponseLine('✅ ログイン確認完了');
110
- // Step 2: Search for existing chat or create new one
137
+ // Step 2: Load existing session or create new chat
138
+ let isNewChat = false;
139
+ let sessionChatId;
111
140
  if (!createNewChat) {
112
- response.appendResponseLine(`既存のプロジェクトチャット「[Project: ${project}]」を検索中...`);
113
- // Open search
114
- const searchOpened = await page.evaluate(() => {
115
- const searchButton = Array.from(document.querySelectorAll('div.group.__menu-item.hoverable')).find((elem) => elem.textContent?.includes('チャットを検索'));
116
- if (searchButton) {
117
- searchButton.click();
118
- return true;
119
- }
120
- return false;
121
- });
122
- if (searchOpened) {
123
- await new Promise((resolve) => setTimeout(resolve, 500));
124
- // Search for project chat
125
- const chatFound = await page.evaluate((projectName) => {
126
- const searchInput = document.querySelector('input[placeholder*="チャットを検索"]');
127
- if (searchInput) {
128
- searchInput.value = `[Project: ${projectName}]`;
129
- searchInput.dispatchEvent(new Event('input', { bubbles: true }));
130
- return true;
131
- }
132
- return false;
133
- }, project);
134
- if (chatFound) {
135
- await new Promise((resolve) => setTimeout(resolve, 1000));
136
- // Try to find and click the chat
137
- const existingChat = await page.evaluate((projectName) => {
138
- const chatLinks = Array.from(document.querySelectorAll('a[href^="/c/"]'));
139
- const targetChat = chatLinks.find((link) => link.textContent?.includes(`[Project: ${projectName}]`));
140
- if (targetChat) {
141
- targetChat.click();
142
- return {
143
- found: true,
144
- href: targetChat.href,
145
- };
146
- }
147
- return { found: false };
148
- }, project);
149
- if (existingChat.found) {
150
- response.appendResponseLine(`✅ 既存チャットを使用: ${existingChat.href}`);
151
- await new Promise((resolve) => setTimeout(resolve, 1000));
152
- }
153
- else {
154
- response.appendResponseLine('既存チャットが見つかりませんでした。新規作成します。');
155
- }
156
- }
141
+ // Try to load existing session for this project
142
+ const sessions = await loadChatSessions();
143
+ const existingSession = sessions[project];
144
+ if (existingSession) {
145
+ response.appendResponseLine(`既存のプロジェクトチャットを使用: ${existingSession.url}`);
146
+ await page.goto(existingSession.url, { waitUntil: 'networkidle2' });
147
+ sessionChatId = existingSession.chatId;
148
+ await new Promise((resolve) => setTimeout(resolve, 1000));
149
+ }
150
+ else {
151
+ response.appendResponseLine('既存チャットが見つかりませんでした。新規作成します。');
152
+ isNewChat = true;
157
153
  }
158
154
  }
155
+ else {
156
+ isNewChat = true;
157
+ }
159
158
  // Step 3: Create new chat if needed
160
- let isNewChat = false;
161
- if (createNewChat || page.url() === 'https://chatgpt.com/') {
159
+ if (isNewChat) {
162
160
  response.appendResponseLine('新規チャットを作成中...');
163
- isNewChat = true;
164
161
  // Click "新しいチャット"
165
162
  await page.evaluate(() => {
166
163
  const newChatLink = document.querySelector('a[href="/"]');
@@ -207,14 +204,8 @@ export const askChatGPTWeb = defineTool({
207
204
  await new Promise((resolve) => setTimeout(resolve, 500));
208
205
  // Click send button
209
206
  const sent = await page.evaluate(() => {
210
- const buttons = Array.from(document.querySelectorAll('button'));
211
- const sendButton = buttons.find((btn) => {
212
- const svg = btn.querySelector('svg');
213
- return (svg &&
214
- !btn.disabled &&
215
- btn.offsetParent !== null);
216
- });
217
- if (sendButton) {
207
+ const sendButton = document.querySelector('button[data-testid="send-button"]');
208
+ if (sendButton && !sendButton.disabled) {
218
209
  sendButton.click();
219
210
  return true;
220
211
  }
@@ -224,6 +215,11 @@ export const askChatGPTWeb = defineTool({
224
215
  response.appendResponseLine('❌ 送信ボタンが見つかりません');
225
216
  return;
226
217
  }
218
+ // Wait for message to actually be sent (user message appears in DOM)
219
+ await page.waitForFunction(() => {
220
+ const messages = document.querySelectorAll('[data-message-author-role="user"]');
221
+ return messages.length > 0;
222
+ }, { timeout: 10000 });
227
223
  response.appendResponseLine('✅ 質問送信完了');
228
224
  // Step 5: Monitor streaming with progress updates
229
225
  response.appendResponseLine('ChatGPTの回答を待機中... (10秒ごとに進捗を表示)');
@@ -269,61 +265,37 @@ export const askChatGPTWeb = defineTool({
269
265
  if (status.thinkingTime) {
270
266
  response.appendResponseLine(`🤔 思考時間: ${status.thinkingTime}秒`);
271
267
  }
272
- // Rename chat if it's a new chat
268
+ // Save chat session if it's a new chat
273
269
  if (isNewChat) {
274
- response.appendResponseLine('チャット名を変更中...');
275
- // Wait for chat to be created
276
- await new Promise((resolve) => setTimeout(resolve, 2000));
277
- // Click chat menu
278
- const menuClicked = await page.evaluate(() => {
279
- const menuButtons = Array.from(document.querySelectorAll('button[aria-label="会話のオプションを開く"]'));
280
- // Find the first menu button (current chat)
281
- const btn = menuButtons[0];
282
- if (btn) {
283
- btn.click();
284
- return true;
285
- }
286
- return false;
287
- });
288
- if (menuClicked) {
289
- await new Promise((resolve) => setTimeout(resolve, 500));
290
- // Click "名前を変更する"
291
- const renameClicked = await page.evaluate(() => {
292
- const menuItems = Array.from(document.querySelectorAll('[role="menuitem"]'));
293
- const renameItem = menuItems.find((item) => item.textContent?.includes('名前を変更する'));
294
- if (renameItem) {
295
- renameItem.click();
296
- return true;
297
- }
298
- return false;
270
+ response.appendResponseLine('チャットセッションを保存中...');
271
+ // Extract chat ID from URL
272
+ const chatUrl = page.url();
273
+ const chatIdMatch = chatUrl.match(/\/c\/([a-f0-9-]+)/);
274
+ if (chatIdMatch) {
275
+ const chatId = chatIdMatch[1];
276
+ await saveChatSession(project, {
277
+ chatId,
278
+ url: chatUrl,
279
+ lastUsed: new Date().toISOString(),
280
+ title: `[Project: ${project}]`,
281
+ });
282
+ sessionChatId = chatId;
283
+ response.appendResponseLine(`✅ チャットセッション保存: ${chatId}`);
284
+ }
285
+ else {
286
+ response.appendResponseLine('⚠️ チャットIDが取得できませんでした');
287
+ }
288
+ }
289
+ else {
290
+ // Update last used timestamp for existing session
291
+ if (sessionChatId) {
292
+ const chatUrl = page.url();
293
+ await saveChatSession(project, {
294
+ chatId: sessionChatId,
295
+ url: chatUrl,
296
+ lastUsed: new Date().toISOString(),
297
+ title: `[Project: ${project}]`,
299
298
  });
300
- if (renameClicked) {
301
- await new Promise((resolve) => setTimeout(resolve, 500));
302
- // Enter new name
303
- await page.evaluate((projectName) => {
304
- const textbox = document.querySelector('input[type="text"]');
305
- if (textbox) {
306
- textbox.value = `[Project: ${projectName}]`;
307
- textbox.dispatchEvent(new Event('input', { bubbles: true }));
308
- // Press Enter to confirm
309
- textbox.dispatchEvent(new KeyboardEvent('keydown', {
310
- key: 'Enter',
311
- code: 'Enter',
312
- keyCode: 13,
313
- bubbles: true,
314
- }));
315
- textbox.blur();
316
- }
317
- }, project);
318
- await new Promise((resolve) => setTimeout(resolve, 500));
319
- response.appendResponseLine(`✅ チャット名を「[Project: ${project}]」に変更`);
320
- // Close the menu popup by clicking outside
321
- await page.evaluate(() => {
322
- const body = document.body;
323
- body.click();
324
- });
325
- await new Promise((resolve) => setTimeout(resolve, 300));
326
- }
327
299
  }
328
300
  }
329
301
  // Save conversation log
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-devtools-mcp-for-extension",
3
- "version": "0.9.18",
3
+ "version": "0.9.20",
4
4
  "description": "MCP server for Chrome extension development with Web Store automation. Fork of chrome-devtools-mcp with extension-specific tools.",
5
5
  "type": "module",
6
6
  "bin": "./build/src/index.js",