AI-powered tool to export all project code files into a zip file in chronological order
Size
14.1 KB
Version
1.1.1
Created
Feb 4, 2026
Updated
13 days ago
1// ==UserScript==
2// @name Rork Project Code Exporter
3// @description AI-powered tool to export all project code files into a zip file in chronological order
4// @version 1.1.1
5// @match https://*.rork.com/*
6// @icon https://rork.com/favicon.ico
7// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js
8// ==/UserScript==
9(function() {
10 'use strict';
11
12 console.log('Rork Project Code Exporter initialized');
13
14 // Utility function to create a debounced function
15 function debounce(func, wait) {
16 let timeout;
17 return function executedFunction(...args) {
18 const later = () => {
19 clearTimeout(timeout);
20 func(...args);
21 };
22 clearTimeout(timeout);
23 timeout = setTimeout(later, wait);
24 };
25 }
26
27 // Function to show loading indicator
28 function showLoadingIndicator(message = 'Processing...') {
29 let loader = document.getElementById('rork-export-loader');
30 if (!loader) {
31 loader = document.createElement('div');
32 loader.id = 'rork-export-loader';
33 loader.style.cssText = `
34 position: fixed;
35 top: 20px;
36 right: 20px;
37 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
38 color: white;
39 padding: 16px 24px;
40 border-radius: 12px;
41 z-index: 100000;
42 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
43 font-size: 14px;
44 font-weight: 500;
45 box-shadow: 0 10px 40px rgba(0,0,0,0.3);
46 display: flex;
47 align-items: center;
48 gap: 12px;
49 `;
50 document.body.appendChild(loader);
51 }
52 loader.innerHTML = `
53 <div style="width: 20px; height: 20px; border: 3px solid rgba(255,255,255,0.3); border-top-color: white; border-radius: 50%; animation: spin 1s linear infinite;"></div>
54 <span>${message}</span>
55 `;
56
57 // Add animation
58 if (!document.getElementById('rork-export-styles')) {
59 const style = document.createElement('style');
60 style.id = 'rork-export-styles';
61 style.textContent = `
62 @keyframes spin {
63 to { transform: rotate(360deg); }
64 }
65 `;
66 document.head.appendChild(style);
67 }
68 }
69
70 // Function to hide loading indicator
71 function hideLoadingIndicator() {
72 const loader = document.getElementById('rork-export-loader');
73 if (loader) {
74 loader.remove();
75 }
76 }
77
78 // Function to show success message
79 function showSuccessMessage(message) {
80 const successDiv = document.createElement('div');
81 successDiv.style.cssText = `
82 position: fixed;
83 top: 20px;
84 right: 20px;
85 background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
86 color: white;
87 padding: 16px 24px;
88 border-radius: 12px;
89 z-index: 100000;
90 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
91 font-size: 14px;
92 font-weight: 500;
93 box-shadow: 0 10px 40px rgba(0,0,0,0.3);
94 `;
95 successDiv.textContent = message;
96 document.body.appendChild(successDiv);
97
98 setTimeout(() => {
99 successDiv.remove();
100 }, 3000);
101 }
102
103 // Function to show error message
104 function showErrorMessage(message) {
105 const errorDiv = document.createElement('div');
106 errorDiv.style.cssText = `
107 position: fixed;
108 top: 20px;
109 right: 20px;
110 background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);
111 color: white;
112 padding: 16px 24px;
113 border-radius: 12px;
114 z-index: 100000;
115 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
116 font-size: 14px;
117 font-weight: 500;
118 box-shadow: 0 10px 40px rgba(0,0,0,0.3);
119 `;
120 errorDiv.textContent = message;
121 document.body.appendChild(errorDiv);
122
123 setTimeout(() => {
124 errorDiv.remove();
125 }, 5000);
126 }
127
128 // Function to extract all code from the page using AI
129 async function extractCodeFilesWithAI() {
130 try {
131 showLoadingIndicator('AI is analyzing the page...');
132
133 // Get all text content from the page
134 const pageContent = document.body.innerText;
135
136 // Get project ID from URL
137 const projectId = window.location.pathname.split('/').pop();
138
139 console.log('Extracting code files from project:', projectId);
140 console.log('Page content length:', pageContent.length);
141
142 // Use AI to analyze and extract file structure with more comprehensive prompt
143 const prompt = `You are analyzing a Rork project page. Your task is to extract EVERY SINGLE code file visible on this page.
144
145CRITICAL INSTRUCTIONS:
146- Extract ALL files you can find - do not skip any files
147- Include the COMPLETE content of each file - do not truncate or summarize
148- Look for file names, code blocks, file tabs, file lists, or any indication of files
149- Include configuration files, source code files, data files, everything
150- Preserve all code exactly as shown including whitespace, indentation, and formatting
151- If you see partial content, include what you can see
152- Look for files in sidebars, tabs, panels, modals, or any UI element
153
154Page content (first 10000 characters):
155${pageContent.substring(0, 10000)}
156
157${pageContent.length > 10000 ? `\n\nPage content (next 10000 characters):\n${pageContent.substring(10000, 20000)}` : ''}
158
159${pageContent.length > 20000 ? `\n\nPage content (remaining):\n${pageContent.substring(20000, 30000)}` : ''}
160
161Extract EVERY file with:
1621. Exact file name (with extension)
1632. Complete file content (all code, no truncation)
1643. File type/extension
1654. Any timestamp or order information if visible
166
167Return ALL files you find - missing files is not acceptable.`;
168
169 showLoadingIndicator('AI is extracting ALL files...');
170
171 const fileStructure = await RM.aiCall(prompt, {
172 type: 'json_schema',
173 json_schema: {
174 name: 'code_files_extraction',
175 schema: {
176 type: 'object',
177 properties: {
178 projectName: { type: 'string' },
179 files: {
180 type: 'array',
181 items: {
182 type: 'object',
183 properties: {
184 fileName: { type: 'string' },
185 content: { type: 'string' },
186 fileType: { type: 'string' },
187 timestamp: { type: 'string' }
188 },
189 required: ['fileName', 'content', 'fileType']
190 }
191 }
192 },
193 required: ['projectName', 'files']
194 }
195 }
196 });
197
198 console.log('AI extracted files:', fileStructure);
199 console.log('Total files found:', fileStructure.files?.length || 0);
200
201 if (!fileStructure.files || fileStructure.files.length === 0) {
202 throw new Error('No files found in the project. Please make sure all files are visible on the page.');
203 }
204
205 return fileStructure;
206
207 } catch (error) {
208 console.error('Error extracting files with AI:', error);
209 throw error;
210 }
211 }
212
213 // Function to create and download zip file
214 async function createAndDownloadZip(fileStructure) {
215 try {
216 showLoadingIndicator('Creating zip file...');
217
218 const zip = new JSZip();
219
220 // Sort files chronologically if timestamp is available
221 const sortedFiles = fileStructure.files.sort((a, b) => {
222 if (a.timestamp && b.timestamp) {
223 return new Date(a.timestamp) - new Date(b.timestamp);
224 }
225 return 0;
226 });
227
228 // Add files to zip
229 sortedFiles.forEach((file, index) => {
230 const fileName = file.fileName || `file_${index + 1}.${file.fileType}`;
231 console.log('Adding file to zip:', fileName);
232 zip.file(fileName, file.content);
233 });
234
235 // Add a README with file order
236 const readmeContent = `# ${fileStructure.projectName || 'Project'} - Code Export
237
238Files exported in chronological order:
239
240${sortedFiles.map((file, index) => `${index + 1}. ${file.fileName} ${file.timestamp ? `(${file.timestamp})` : ''}`).join('\n')}
241
242Total files: ${sortedFiles.length}
243Export date: ${new Date().toISOString()}
244`;
245
246 zip.file('README.md', readmeContent);
247
248 showLoadingIndicator('Generating zip file...');
249
250 // Generate zip file
251 const content = await zip.generateAsync({
252 type: 'blob',
253 compression: 'DEFLATE',
254 compressionOptions: {
255 level: 9
256 }
257 });
258
259 // Download the zip file
260 const projectName = fileStructure.projectName || 'project';
261 const fileName = `${projectName.replace(/[^a-z0-9]/gi, '_')}_code_export_${Date.now()}.zip`;
262
263 const url = URL.createObjectURL(content);
264 const a = document.createElement('a');
265 a.href = url;
266 a.download = fileName;
267 document.body.appendChild(a);
268 a.click();
269 document.body.removeChild(a);
270 URL.revokeObjectURL(url);
271
272 hideLoadingIndicator();
273 showSuccessMessage(`✓ Exported ${sortedFiles.length} files successfully!`);
274
275 console.log('Zip file downloaded:', fileName);
276
277 } catch (error) {
278 console.error('Error creating zip file:', error);
279 hideLoadingIndicator();
280 showErrorMessage('Failed to create zip file');
281 throw error;
282 }
283 }
284
285 // Main export function
286 async function exportProjectCode() {
287 try {
288 console.log('Starting project code export...');
289
290 // Extract files using AI
291 const fileStructure = await extractCodeFilesWithAI();
292
293 // Create and download zip
294 await createAndDownloadZip(fileStructure);
295
296 } catch (error) {
297 console.error('Export failed:', error);
298 hideLoadingIndicator();
299 showErrorMessage('Export failed: ' + error.message);
300 }
301 }
302
303 // Create export button
304 function createExportButton() {
305 // Check if button already exists
306 if (document.getElementById('rork-export-button')) {
307 return;
308 }
309
310 const button = document.createElement('button');
311 button.id = 'rork-export-button';
312 button.innerHTML = `
313 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
314 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
315 <polyline points="7 10 12 15 17 10"></polyline>
316 <line x1="12" y1="15" x2="12" y2="3"></line>
317 </svg>
318 <span>Export Code</span>
319 `;
320 button.style.cssText = `
321 position: fixed;
322 bottom: 30px;
323 right: 30px;
324 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
325 color: white;
326 border: none;
327 padding: 14px 24px;
328 border-radius: 12px;
329 cursor: pointer;
330 z-index: 99999;
331 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
332 font-size: 14px;
333 font-weight: 600;
334 box-shadow: 0 8px 32px rgba(102, 126, 234, 0.4);
335 display: flex;
336 align-items: center;
337 gap: 8px;
338 transition: all 0.3s ease;
339 `;
340
341 button.addEventListener('mouseenter', () => {
342 button.style.transform = 'translateY(-2px)';
343 button.style.boxShadow = '0 12px 40px rgba(102, 126, 234, 0.5)';
344 });
345
346 button.addEventListener('mouseleave', () => {
347 button.style.transform = 'translateY(0)';
348 button.style.boxShadow = '0 8px 32px rgba(102, 126, 234, 0.4)';
349 });
350
351 button.addEventListener('click', async () => {
352 button.disabled = true;
353 button.style.opacity = '0.7';
354 button.style.cursor = 'not-allowed';
355
356 await exportProjectCode();
357
358 button.disabled = false;
359 button.style.opacity = '1';
360 button.style.cursor = 'pointer';
361 });
362
363 document.body.appendChild(button);
364 console.log('Export button created');
365 }
366
367 // Initialize the extension
368 function init() {
369 console.log('Initializing Rork Project Code Exporter...');
370
371 // Wait for page to be fully loaded
372 if (document.readyState === 'loading') {
373 document.addEventListener('DOMContentLoaded', () => {
374 setTimeout(createExportButton, 1000);
375 });
376 } else {
377 setTimeout(createExportButton, 1000);
378 }
379
380 // Re-create button on navigation (for SPAs)
381 const debouncedCreateButton = debounce(createExportButton, 500);
382 const observer = new MutationObserver(debouncedCreateButton);
383 observer.observe(document.body, {
384 childList: true,
385 subtree: true
386 });
387 }
388
389 // Start the extension
390 init();
391
392})();