Last active
April 3, 2026 08:50
-
-
Save lumpsoid/66e835287f7dd5ccd27e61b0068c5f6b to your computer and use it in GitHub Desktop.
claude-code-web-backup-chat.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (async function backup(){const sanitizeFilename=s=>s.replace(/[/\\?%*:|"<>]/g,'-');const sleep=ms=>new Promise(r=>setTimeout(r,ms));const BINARY_EXTENSIONS=new Set(['png','jpg','jpeg','gif','webp','svg','ico','pdf','zip','tar','gz','wasm','ttf','woff','woff2']);function getOrgUuid(){const orgUuid=document.cookie.match(/lastActiveOrg=([^;]+)/)?.[1];if(!orgUuid){throw new Error('No orgUuid found in cookies')}return orgUuid}function getConversationFromReactFiber(){const msgEl=document.querySelector('[data-testid="user-message"]');if(!msgEl){throw new Error('No user-message element found in DOM')}const fiberKey=Object.keys(msgEl).find(k=>k.startsWith('__reactFiber'));let fiber=msgEl[fiberKey];for(let depth=0;fiber&&depth<100;fiber=fiber.return,depth+=1){if(fiber.memoizedProps?.conversation?.chat_messages){return fiber.memoizedProps.conversation}}throw new Error('conversation not found in React fiber tree')}async function fetchFileAsEmbeddable(url,filename){const ext=filename.split('.').pop().toLowerCase();const isBinary=BINARY_EXTENSIONS.has(ext);const resp=await fetch(url,{credentials:'include'});if(!resp.ok){throw new Error(`HTTP ${resp.status }`)}const mime_type=resp.headers.get('content-type')||null;if(isBinary){const buffer=await resp.arrayBuffer();const content=btoa(String.fromCharCode(...new Uint8Array(buffer)));return{ext,encoding:'base64',mime_type,content}}else{const content=await resp.text();return{ext,encoding:'utf8',mime_type,content}}}async function collectArtifactFiles(messages,orgUuid,convUuid){const files={};for(const msg of messages){for(const block of(msg.content||[])){if(block.type!=='tool_result'){continue}for(const item of(block.content||[])){if(item.type!=='local_resource'||!item.file_path){continue}const fname=item.file_path.split('/').pop();const url=`/api/organizations/${ orgUuid }/conversations/${ convUuid }/wiggle/download-file`+`?path=${encodeURIComponent(item.file_path)}`;try{files[fname]={source:'artifact',file_path:item.file_path,...(await fetchFileAsEmbeddable(url,fname))};console.log(`✅ artifact: ${ fname }`)}catch(err){files[fname]={source:'artifact',file_path:item.file_path,error:String(err)};console.warn(`⚠️ artifact failed: ${ fname }`,err)}await sleep(100)}}}return files}async function collectHumanUploadedFiles(messages){const files={};for(const msg of messages){for(const f of(msg.files||[])){const fileUuid=f.file_uuid||f.uuid;if(!fileUuid){continue}const fname=f.file_name||`file-${ fileUuid }`;const url=f.preview_url;try{files[fname]={source:'human_upload',file_uuid:fileUuid,file_name:fname,actual_encoding:'webp',...(await fetchFileAsEmbeddable(url,fname))};console.log(`✅ human file: ${ fname }`)}catch(err){files[fname]={source:'human_upload',file_uuid:fileUuid,file_name:fname,error:String(err)};console.warn(`⚠️ human file failed: ${ fname }`,err)}await sleep(100)}}return files}function downloadAsJson(data,title){const timestamp=new Date().toISOString().replace(/[:.]/g,'-');const filename=sanitizeFilename(`${ title }-${ timestamp }.json`);const blob=new Blob([JSON.stringify(data,null,2)],{type:'application/json'});const a=Object.assign(document.createElement('a'),{href:URL.createObjectURL(blob),download:filename});a.click();URL.revokeObjectURL(a.href);return filename}async function backupClaudeChat(){const orgUuid=getOrgUuid();const conv=getConversationFromReactFiber();const[artifactFiles,humanFiles]=await Promise.all([collectArtifactFiles(conv.chat_messages,orgUuid,conv.uuid),collectHumanUploadedFiles(conv.chat_messages)]);const files={...artifactFiles,...humanFiles};const backup={...conv,files};const filename=downloadAsJson(backup,conv.name||'claude-chat');console.log(`✅ Saved: ${ filename }`);console.log(` messages: ${conv.chat_messages.length }, files embedded: ${Object.keys(files).length }`)}backupClaudeChat()})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment