Claude-code-sdk chatgpt-search
git clone https://github.com/SeifBenayed/cloclo
T=$(mktemp -d) && git clone --depth=1 https://github.com/SeifBenayed/cloclo "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/chatgpt-search" ~/.claude/skills/seifbenayed-claude-code-sdk-chatgpt-search && rm -rf "$T"
.claude/skills/chatgpt-search/SKILL.mdChatGPT Search — Extract structured response + hydration JSON
Prerequisites
A Chrome instance must be running with
--remote-debugging-port=9222.
If not running, launch it:
# Kill existing Chrome first pkill -9 -f "Google Chrome" 2>/dev/null; sleep 2 # Launch with debug port + user's real profile copy "/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:
— user + assistant messages/tmp/chatgpt-search-messages.json
— UI hydration JSON/tmp/chatgpt-search-hydration.json
— SSE stream data (if captured)/tmp/chatgpt-search-sse.json
— API call responses/tmp/chatgpt-search-api.json
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" }