| name | chatgpt-search |
| description | Search ChatGPT and extract the full response + hydration JSON that powers the UI.
Attaches to a running Chrome instance (port 9222 by default), opens ChatGPT,
submits a query, waits for the streamed response, and returns structured data:
messages, product cards, hydration JSON, and API calls.
Use when asked to "search chatgpt", "ask chatgpt", "chatgpt search",
"get chatgpt response", or "scrape chatgpt".
|
| allowed-tools | Browser,Bash,Read,Write,Glob,Grep |
ChatGPT Search — Extract structured response + hydration JSON
Prerequisites
A Chrome instance must be running with --remote-debugging-port=9222.
If not running, launch it:
pkill -9 -f "Google Chrome" 2>/dev/null; sleep 2
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--remote-debugging-port=9222 \
--user-data-dir="$HOME/.claude/browser-profiles/chrome-debug-full" \
--no-first-run &>/dev/null &
If the profile doesn't exist yet, create it by copying the user's real Chrome profile:
REAL="$HOME/Library/Application Support/Google/Chrome"
COPY="$HOME/.claude/browser-profiles/chrome-debug-full"
mkdir -p "$COPY"
cp "$REAL/Local State" "$COPY/" 2>/dev/null
cp -R "$REAL/Default" "$COPY/Default" 2>/dev/null
rm -rf "$COPY/Default/Cache" "$COPY/Default/Code Cache" 2>/dev/null
Workflow
The user provides a search query. Execute these steps:
Step 1: Verify Chrome is running with debug port
curl -s http://127.0.0.1:9222/json/version | head -1
If it fails, launch Chrome as described in Prerequisites.
Step 2: Attach + enable network log + open ChatGPT
Browser { action: "new_session", session_id: "chatgpt", cdp_url: "http://127.0.0.1:9222" }
Browser { action: "enable_network_log", session_id: "chatgpt" }
Browser { action: "new_tab", url: "https://chatgpt.com", session_id: "chatgpt" }
Wait 5 seconds for page load.
Step 3: Inject fetch/SSE interceptor
Browser { action: "evaluate", session_id: "chatgpt", value: "window.__sse=[];window.__api=[];const _f=window.fetch;window.fetch=async function(...a){const url=typeof a[0]==='string'?a[0]:a[0]?.url||'?';const m=a[1]?.method||'GET';const rb=a[1]?.body;const resp=await _f.apply(this,a);const ct=resp.headers.get('content-type')||'';const cl=resp.clone();if(ct.includes('event-stream')){const rd=cl.body.getReader();const dc=new TextDecoder();let full='';(async()=>{try{while(true){const{done,value:v}=await rd.read();if(done)break;full+=dc.decode(v,{stream:true});}}catch{}window.__sse.push({url,m,s:resp.status,sz:full.length,body:full,ts:Date.now()});})();}else{cl.text().then(t=>{if(t.length>30)window.__api.push({url,m,s:resp.status,sz:t.length,body:t,ts:Date.now()});}).catch(()=>{});}return resp;};'ok'" }
Step 4: Type query + submit
Use real CDP key events for React compatibility:
Browser { action: "click", selector: "#prompt-textarea, textarea", session_id: "chatgpt" }
Then type each character of the query using send_keys — but for long queries, use evaluate with the native setter:
Browser { action: "evaluate", session_id: "chatgpt", value: "Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype,'value').set.call(document.querySelector('#prompt-textarea,textarea'),'THE QUERY HERE');document.querySelector('#prompt-textarea,textarea').dispatchEvent(new Event('input',{bubbles:true}));'typed'" }
Wait 800ms, then submit:
Browser { action: "evaluate", session_id: "chatgpt", value: "(function(){var btn=document.querySelector('[data-testid=\"send-button\"],button[aria-label*=\"Envoyer\"],button[aria-label*=\"Send\"]');if(btn&&!btn.disabled){btn.click();return 'sent';}var btns=[...document.querySelectorAll('button')];var s=btns.find(function(b){return b.querySelector('svg')&&!b.disabled&&b.closest('[class*=\"composer\"]');});if(s){s.click();return 'sent-svg';}return 'no-btn';})()" }
Step 5: Wait for response
Poll every 5 seconds for up to 60 seconds, checking message count:
Browser { action: "evaluate", session_id: "chatgpt", value: "document.querySelectorAll('[data-message-author-role]').length" }
Stop when message count >= 2 (user + assistant).
Step 6: Extract results
Messages (user + assistant full text):
Browser { action: "evaluate", session_id: "chatgpt", value: "(function(){var msgs=document.querySelectorAll('[data-message-author-role]');var r=[];msgs.forEach(function(m){r.push({role:m.getAttribute('data-message-author-role'),text:m.textContent.trim()})});return JSON.stringify(r);})()" }
Hydration JSON (session, config, feature flags):
Browser { action: "evaluate", session_id: "chatgpt", value: "var e=document.querySelector('script[type=\"application/json\"]');e?e.textContent:''" }
Intercepted SSE streams (conversation stream + search product updates):
Browser { action: "evaluate", session_id: "chatgpt", value: "JSON.stringify((window.__sse||[]).map(function(s){return{url:s.url,method:s.m,status:s.s,size:s.sz}}))" }
To get a specific SSE body (e.g., index 0):
Browser { action: "evaluate", session_id: "chatgpt", value: "(window.__sse||[])[0]?.body||''" }
Intercepted API calls:
Browser { action: "evaluate", session_id: "chatgpt", value: "JSON.stringify((window.__api||[]).filter(function(a){return a.url.includes('backend')}).map(function(a){return{url:a.url,method:a.m,status:a.s,size:a.sz}}))" }
CDP network log (all HTTP requests/responses):
Browser { action: "get_network_log", session_id: "chatgpt", filter: "backend" }
Step 7: Save to /tmp
Save all extracted data to /tmp/chatgpt-search-*.json:
/tmp/chatgpt-search-messages.json — user + assistant messages
/tmp/chatgpt-search-hydration.json — UI hydration JSON
/tmp/chatgpt-search-sse.json — SSE stream data (if captured)
/tmp/chatgpt-search-api.json — API call responses
Step 8: Report
Present the assistant's response to the user, and list saved files with sizes.
Output format
## ChatGPT Search: [query]
### Response
[Full assistant message text]
### Files saved
- /tmp/chatgpt-search-messages.json (X KB) — conversation messages
- /tmp/chatgpt-search-hydration.json (X KB) — UI hydration data
- /tmp/chatgpt-search-sse.json (X KB) — SSE streams
- /tmp/chatgpt-search-api.json (X KB) — API responses
### Metadata
- Model: [from hydration or conversation init]
- Session ID: [from hydration]
- Country: [from hydration]
Important notes
- ChatGPT works without login for basic queries (anonymous mode)
- For authenticated queries, the user must log in manually in the debug Chrome
- The fetch interceptor captures SSE streams (conversation + search/product_update)
- CDP network log captures JSON API calls but NOT SSE stream bodies
- Always close the session when done:
Browser { action: "close_session", session_id: "chatgpt" }