Cleaned up code and comments

This commit is contained in:
justin committed 2025-07-11 16:36:21 -04:00
1 parent d4c5d3bb13
commit 2c16b451ad
1 file changed
+77 -103
+77 -103
View File
@@ -1,4 +1,3 @@
// Content script for artifact processing
(function() { (function() {
/*! /*!
@@ -15,7 +14,6 @@
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).JSZip=e()}}(function(){return function s(a,o,h){function u(r,e){if(!o[r]){if(!a[r]){var t="function"==typeof require&&require;if(!e&&t)return t(r,!0);if(l)return l(r,!0);var n=new Error("Cannot find module '"+r+"'");throw n.code="MODULE_NOT_FOUND",n}var i=o[r]={exports:{}};a[r][0].call(i.exports,function(e){var t=a[r][1][e];return u(t||e)},i,i.exports,s,a,o,h)}return o[r].exports}for(var l="function"==typeof require&&require,e=0;e<h.length;e++)u(h[e]);return u}({1:[function(e,t,r){"use strict";var d=e("./utils"),c=e("./support"),p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";r.encode=function(e){for(var t,r,n,i,s,a,o,h=[],u=0,l=e.length,f=l,c="string"!==d.getTypeOf(e);u<e.length;)f=l-u,n=c?(t=e[u++],r=u<l?e[u++]:0,u<l?e[u++]:0):(t=e.charCodeAt(u++),r=u<l?e.charCodeAt(u++):0,u<l?e.charCodeAt(u++):0),i=t>>2,s=(3&t)<<4|r>>4,a=1<f?(15&r)<<2|n>>6:64,o=2<f?63&n:64,h.push(p.charAt(i)+p.charAt(s)+p.charAt(a)+p.charAt(o));return h.join("")},r.decode=function(e){var t,r,n,i,s,a,o=0,h=0,u="data:";if(e.substr(0,u.length)===u)throw new Error("Invalid base64 input, it looks like a data url.");var l,f=3*(e=e.replace(/[^A-Za-z0-9+/=]/g,"")).length/4;if(e.charAt(e.length-1)===p.charAt(64)&&f--,e.charAt(e.length-2)===p.charAt(64)&&f--,f%1!=0)throw new Error("Invalid base64 input, bad content length.");for(l=c.uint8array?new Uint8Array(0|f):new Array(0|f);o<e.length;)t=p.indexOf(e.charAt(o++))<<2|(i=p.indexOf(e.charAt(o++)))>>4,r=(15&i)<<4|(s=p.indexOf(e.charAt(o++)))>>2,n=(3&s)<<6|(a=p.indexOf(e.charAt(o++))),l[h++]=t,64!==s&&(l[h++]=r),64!==a&&(l[h++]=n);return l}},{"./support":30,"./utils":32}],2:[function(e,t,r){"use strict";var n=e("./external"),i=e("./stream/DataWorker"),s=e("./stream/Crc32Probe"),a=e("./stream/DataLengthProbe");function o(e,t,r,n,i){this.compressedSize=e,this.uncompressedSize=t,this.crc32=r,this.compression=n,this.compressedContent=i}o.prototype={getContentWorker:function(){var e=new i(n.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")),t=this;return e.on("end",function(){if(this.streamInfo.data_length!==t.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),e},getCompressedWorker:function(){return new i(n.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(e,t,r){return e.pipe(new s).pipe(new a("uncompressedSize")).pipe(t.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression",t)},t.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(e,t,r){"use strict";var n=e("./stream/GenericWorker");r.STORE={magic:"\0\0",compressWorker:function(){return new n("STORE compression")},uncompressWorker:function(){return new n("STORE decompression")}},r.DEFLATE=e("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(e,t,r){"use strict";var n=e("./utils");var o=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t){return void 0!==e&&e.length?"string"!==n.getTypeOf(e)?function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t[a])];return-1^e}(0|t,e,e.length,0):function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t.charCodeAt(a))];return-1^e}(0|t,e,e.length,0):0}},{"./utils":32}],5:[function(e,t,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(e,t,r){"use strict";var n=null;n="undefined"!=typeof Promise?Promise:e("lie"),t.exports={Promise:n}},{lie:37}],7:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,i=e("pako"),s=e("./utils"),a=e("./stream/GenericWorker"),o=n?"uint8array":"array";function h(e,t){a.call(this,"FlateWorker/"+e),this._pako=null,this._pakoAction=e,this._pakoOptions=t,this.meta={}}r.magic="\b\0",s.inherits(h,a),h.prototype.processChunk=function(e){this.meta=e.meta,null===this._pako&&this._createPako(),this._pako.push(s.transformTo(o,e.data),!1)},h.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},h.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},h.prototype._createPako=function(){this._pako=new i[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var t=this;this._pako.onData=functioLine truncated !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).JSZip=e()}}(function(){return function s(a,o,h){function u(r,e){if(!o[r]){if(!a[r]){var t="function"==typeof require&&require;if(!e&&t)return t(r,!0);if(l)return l(r,!0);var n=new Error("Cannot find module '"+r+"'");throw n.code="MODULE_NOT_FOUND",n}var i=o[r]={exports:{}};a[r][0].call(i.exports,function(e){var t=a[r][1][e];return u(t||e)},i,i.exports,s,a,o,h)}return o[r].exports}for(var l="function"==typeof require&&require,e=0;e<h.length;e++)u(h[e]);return u}({1:[function(e,t,r){"use strict";var d=e("./utils"),c=e("./support"),p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";r.encode=function(e){for(var t,r,n,i,s,a,o,h=[],u=0,l=e.length,f=l,c="string"!==d.getTypeOf(e);u<e.length;)f=l-u,n=c?(t=e[u++],r=u<l?e[u++]:0,u<l?e[u++]:0):(t=e.charCodeAt(u++),r=u<l?e.charCodeAt(u++):0,u<l?e.charCodeAt(u++):0),i=t>>2,s=(3&t)<<4|r>>4,a=1<f?(15&r)<<2|n>>6:64,o=2<f?63&n:64,h.push(p.charAt(i)+p.charAt(s)+p.charAt(a)+p.charAt(o));return h.join("")},r.decode=function(e){var t,r,n,i,s,a,o=0,h=0,u="data:";if(e.substr(0,u.length)===u)throw new Error("Invalid base64 input, it looks like a data url.");var l,f=3*(e=e.replace(/[^A-Za-z0-9+/=]/g,"")).length/4;if(e.charAt(e.length-1)===p.charAt(64)&&f--,e.charAt(e.length-2)===p.charAt(64)&&f--,f%1!=0)throw new Error("Invalid base64 input, bad content length.");for(l=c.uint8array?new Uint8Array(0|f):new Array(0|f);o<e.length;)t=p.indexOf(e.charAt(o++))<<2|(i=p.indexOf(e.charAt(o++)))>>4,r=(15&i)<<4|(s=p.indexOf(e.charAt(o++)))>>2,n=(3&s)<<6|(a=p.indexOf(e.charAt(o++))),l[h++]=t,64!==s&&(l[h++]=r),64!==a&&(l[h++]=n);return l}},{"./support":30,"./utils":32}],2:[function(e,t,r){"use strict";var n=e("./external"),i=e("./stream/DataWorker"),s=e("./stream/Crc32Probe"),a=e("./stream/DataLengthProbe");function o(e,t,r,n,i){this.compressedSize=e,this.uncompressedSize=t,this.crc32=r,this.compression=n,this.compressedContent=i}o.prototype={getContentWorker:function(){var e=new i(n.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")),t=this;return e.on("end",function(){if(this.streamInfo.data_length!==t.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),e},getCompressedWorker:function(){return new i(n.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(e,t,r){return e.pipe(new s).pipe(new a("uncompressedSize")).pipe(t.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression",t)},t.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(e,t,r){"use strict";var n=e("./stream/GenericWorker");r.STORE={magic:"\0\0",compressWorker:function(){return new n("STORE compression")},uncompressWorker:function(){return new n("STORE decompression")}},r.DEFLATE=e("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(e,t,r){"use strict";var n=e("./utils");var o=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t){return void 0!==e&&e.length?"string"!==n.getTypeOf(e)?function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t[a])];return-1^e}(0|t,e,e.length,0):function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t.charCodeAt(a))];return-1^e}(0|t,e,e.length,0):0}},{"./utils":32}],5:[function(e,t,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(e,t,r){"use strict";var n=null;n="undefined"!=typeof Promise?Promise:e("lie"),t.exports={Promise:n}},{lie:37}],7:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,i=e("pako"),s=e("./utils"),a=e("./stream/GenericWorker"),o=n?"uint8array":"array";function h(e,t){a.call(this,"FlateWorker/"+e),this._pako=null,this._pakoAction=e,this._pakoOptions=t,this.meta={}}r.magic="\b\0",s.inherits(h,a),h.prototype.processChunk=function(e){this.meta=e.meta,null===this._pako&&this._createPako(),this._pako.push(s.transformTo(o,e.data),!1)},h.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},h.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},h.prototype._createPako=function(){this._pako=new i[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var t=this;this._pako.onData=functioLine truncated
// Application code
console.log('Content script loaded'); console.log('Content script loaded');
// Check if we're in a project chat by looking for the project link with forward slash // Check if we're in a project chat by looking for the project link with forward slash
@@ -32,15 +30,11 @@
console.log('Not in a project chat - no forward slash separator found'); console.log('Not in a project chat - no forward slash separator found');
return false; return false;
} }
console.log('Confirmed we are in a project chat');
return true; return true;
} }
// Static object containing all artifact scraping functionality
const ArtifactScraper = { const ArtifactScraper = {
// Open the artifact list menu
openArtifactList() { openArtifactList() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Find the global artifact menu button (has the hamburger/list icon) // Find the global artifact menu button (has the hamburger/list icon)
@@ -52,8 +46,6 @@
return; return;
} }
console.log('Opening artifact list menu...');
let hasResolved = false; let hasResolved = false;
// Listen for menu opening // Listen for menu opening
@@ -69,7 +61,7 @@
); );
if (menuAdded) { if (menuAdded) {
console.log('Artifact list menu detected as opened'); console.log('Artifact list menu has been opened');
observer.disconnect(); observer.disconnect();
if (!hasResolved) { if (!hasResolved) {
hasResolved = true; hasResolved = true;
@@ -82,7 +74,8 @@
observer.observe(document.body, { childList: true, subtree: true }); observer.observe(document.body, { childList: true, subtree: true });
// Focus and send Enter key event to open menu // Focus and send Enter key event to open menu. Click doesn't actually work,
// likely due to react pseudoevents.
menuButton.focus(); menuButton.focus();
const enterEvent = new KeyboardEvent('keydown', { const enterEvent = new KeyboardEvent('keydown', {
key: 'Enter', key: 'Enter',
@@ -92,7 +85,7 @@
}); });
menuButton.dispatchEvent(enterEvent); menuButton.dispatchEvent(enterEvent);
// Fallback timeout // Fallback timeout so we don't accidentally listen forever.
setTimeout(() => { setTimeout(() => {
if (!hasResolved) { if (!hasResolved) {
observer.disconnect(); observer.disconnect();
@@ -103,7 +96,8 @@
}); });
}, },
// Parse the artifact list and return filename -> isSelected hash // Once we're sure the menu is open, this will give us the list of artifacts and tell
// us which one is currently selected.
getArtifactListItems() { getArtifactListItems() {
const menuItems = document.querySelectorAll('li[role="none"] div[role="menuitem"]'); const menuItems = document.querySelectorAll('li[role="none"] div[role="menuitem"]');
const artifactHash = {}; const artifactHash = {};
@@ -130,7 +124,7 @@
return artifactHash; return artifactHash;
}, },
// Select a specific artifact list item // When the menu is open, this changes the active artifact to that with the given filename.
selectArtifactListItem(filename) { selectArtifactListItem(filename) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const menuItems = document.querySelectorAll('li[role="none"] div[role="menuitem"]'); const menuItems = document.querySelectorAll('li[role="none"] div[role="menuitem"]');
@@ -152,6 +146,8 @@
console.log(`Selecting artifact: ${filename}`); console.log(`Selecting artifact: ${filename}`);
// Focus and send Enter key event to select the item // Focus and send Enter key event to select the item
// Click may or may not work here, but keydown was needed elsewhere, so we're
// sticking to it here.
targetItem.focus(); targetItem.focus();
const enterEvent = new KeyboardEvent('keydown', { const enterEvent = new KeyboardEvent('keydown', {
key: 'Enter', key: 'Enter',
@@ -171,12 +167,9 @@
// Get artifact content and handle selection if needed // Get artifact content and handle selection if needed
getArtifact(filename, isCurrentlySelected) { getArtifact(filename, isCurrentlySelected) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
// If not currently selected, we need to select it
if (!isCurrentlySelected) { if (!isCurrentlySelected) {
// Check if menu is already open
const menuOpen = document.querySelector('li[role="none"] div[role="menuitem"]'); const menuOpen = document.querySelector('li[role="none"] div[role="menuitem"]');
if (!menuOpen) { if (!menuOpen) {
// Menu is not open, so open it
try { try {
await this.openArtifactList(); await this.openArtifactList();
} catch (error) { } catch (error) {
@@ -185,7 +178,6 @@
} }
} }
// Select the artifact
try { try {
await this.selectArtifactListItem(filename); await this.selectArtifactListItem(filename);
} catch (error) { } catch (error) {
@@ -194,32 +186,26 @@
} }
} }
// Extract content from the code block using the updated selector
const codeBlock = document.querySelector('div.right-0 code'); const codeBlock = document.querySelector('div.right-0 code');
if (!codeBlock) { if (!codeBlock) {
reject(new Error(`No code block found for artifact: ${filename}`)); reject(new Error(`No code block found for artifact: ${filename}`));
return; return;
} }
const content = codeBlock.textContent || codeBlock.innerText || ''; resolve(codeBlock.textContent || codeBlock.innerText || '');
console.log(`Extracted content for ${filename}: ${content.length} characters`);
resolve(content);
}); });
}, },
// Main collection method // Main collection method
async collectArtifacts() { async collectArtifacts() {
console.log('Starting artifact collection...'); const artifactCollection = {}; // We'll keep a map of filename: content here
// There's no path information in a Claude artifact, just filenames.
// Initialize empty collection and fileMap // However, the LLM itself is smart enough to know where the files should go.
const artifactCollection = {}; // So our strategy is to make it save those paths to a files.txt artifact and store
// that separately for use in making a final map of artifacts to files for our zip export.
let fileMap = null; let fileMap = null;
// Open artifact list
await this.openArtifactList(); await this.openArtifactList();
// Get list of artifacts
const artifactList = this.getArtifactListItems(); const artifactList = this.getArtifactListItems();
if (Object.keys(artifactList).length === 0) { if (Object.keys(artifactList).length === 0) {
@@ -229,15 +215,12 @@
console.log(`Found ${Object.keys(artifactList).length} artifacts to collect`); console.log(`Found ${Object.keys(artifactList).length} artifacts to collect`);
// Iterate through artifacts and collect content
for (const [filename, isSelected] of Object.entries(artifactList)) { for (const [filename, isSelected] of Object.entries(artifactList)) {
console.log(`Processing artifact: ${filename}`);
try { try {
const content = await this.getArtifact(filename, isSelected); const content = await this.getArtifact(filename, isSelected);
// Check if this is files.txt
if (filename === 'files.txt') { if (filename === 'files.txt') {
// This is our special llm-generated mapping that we'll need to finish our export.
fileMap = content; fileMap = content;
console.log(`✓ Found files.txt - storing in fileMap`); console.log(`✓ Found files.txt - storing in fileMap`);
} else { } else {
@@ -256,59 +239,51 @@
} }
}; };
// Export function to handle logging of collected artifacts
function exportArtifacts(artifactCollection, fileMap) { function exportArtifacts(artifactCollection, fileMap) {
console.log('=== ARTIFACT COLLECTION COMPLETE ==='); if (!artifactCollection || artifactCollection.length === 0) {
console.log(`Collected ${Object.keys(artifactCollection).length} artifacts:`); console.warn("No non-filemap artifacts were found. Ask the LLM to copy them from the project to this chat first.");
console.log(artifactCollection); return;
}
if (!fileMap) {
console.warn("No filemap was found. Ask the LLM to generate one. See the README for a sample prompt.");
return;
}
// Parse the file map into paths
const lines = fileMap.split('\n').filter(line => line.trim());
const pathMapping = {};
let currentPath = '';
if (fileMap) { lines.forEach(line => {
console.log('=== FILE MAP ==='); const trimmedLine = line.trim();
console.log(fileMap);
// Parse the file map into paths // Check if this is a directory (ends with /)
const lines = fileMap.split('\n').filter(line => line.trim()); if (trimmedLine.endsWith('/')) {
const pathMapping = {}; currentPath = trimmedLine;
let currentPath = ''; } else if (trimmedLine && !trimmedLine.startsWith('/')) {
if (line.startsWith(' ')) {
lines.forEach(line => { // File is indented, so it's in the current directory
const trimmedLine = line.trim(); pathMapping[trimmedLine] = currentPath + trimmedLine;
} else {
// Check if this is a directory (ends with /) // File is at root level
if (trimmedLine.endsWith('/')) { pathMapping[trimmedLine] = trimmedLine;
currentPath = trimmedLine;
} else if (trimmedLine && !trimmedLine.startsWith('/')) {
// This is a file
if (line.startsWith(' ')) {
// File is indented, so it's in the current directory
pathMapping[trimmedLine] = currentPath + trimmedLine;
} else {
// File is at root level
pathMapping[trimmedLine] = trimmedLine;
}
} }
}); }
});
console.log('=== PARSED PATH MAPPING ==='); // Create final map with full paths and contents
console.log(pathMapping); const finalMap = {};
Object.entries(artifactCollection).forEach(([filename, content]) => {
const fullPath = pathMapping[filename] || filename; // Use filename as-is if not in mapping
finalMap[fullPath] = content;
});
// Create final map with full paths and contents // Create zip file
const finalMap = {}; const zip = new JSZip();
// Map artifact contents to their full paths // Add each file to the zip
Object.entries(artifactCollection).forEach(([filename, content]) => { Object
const fullPath = pathMapping[filename] || filename; // Use filename as-is if not in mapping .entries(finalMap)
finalMap[fullPath] = content; .forEach(([fullPath, content]) => {
});
console.log('=== FINAL PATH + CONTENT MAP ===');
console.log(finalMap);
// Create zip file
const zip = new JSZip();
// Add each file to the zip
Object.entries(finalMap).forEach(([fullPath, content]) => {
// Handle directory structure // Handle directory structure
const pathParts = fullPath.split('/'); const pathParts = fullPath.split('/');
@@ -324,32 +299,31 @@
} }
}); });
// Generate timestamp for filename // Generate timestamp for filename
const now = new Date(); const now = new Date();
const timestamp = now.getFullYear() + '-' + const timestamp = now.getFullYear() + '-' +
String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' +
String(now.getDate()).padStart(2, '0'); String(now.getDate()).padStart(2, '0');
const zipFilename = `${timestamp}_claude-artifacts.zip`; const zipFilename = `${timestamp}_claude-artifacts.zip`;
// Generate and download the zip // Generate and download the zip
zip.generateAsync({type: "blob"}) zip.generateAsync({type: "blob"})
.then(function(content) { .then(function(content) {
// Create download link // Create download link
const url = URL.createObjectURL(content); const url = URL.createObjectURL(content);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
a.download = zipFilename; a.download = zipFilename;
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
console.log(`✓ Downloaded: ${zipFilename}`); console.log(`✓ Downloaded: ${zipFilename}`);
}) })
.catch(function(error) { .catch(function(error) {
console.error('Failed to generate zip:', error); console.error('Failed to generate zip:', error);
}); });
}
} }
// Main function // Main function