Greasy Fork is available in English.

Claude API Exporter (Minimal)

Simple export of Claude conversations and artifacts

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Claude API Exporter (Minimal)
// @namespace    http://tampermonkey.net/
// @version      6.2.0
// @description  Simple export of Claude conversations and artifacts
// @author       MRL (Minimized)
// @match        https://claude.ai/*
// @grant        GM_registerMenuCommand
// @icon         https://www.google.com/s2/favicons?sz=64&domain=claude.ai
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // =============================================
    // UTILITIES
    // =============================================
    const sanitize = (name) => (name || 'Untitled').replace(/[\\/:*?"<>|]/g, '_').replace(/\s+/g, '_').slice(0, 100);
    const formatDate = (d) => d ? new Date(d).toLocaleString() : '';

    const timestamp = (d) => {
        const dt = new Date(d || Date.now());
        const p = (n) => String(n).padStart(2, '0');
        return `${dt.getFullYear()}-${p(dt.getMonth()+1)}-${p(dt.getDate())}_${p(dt.getHours())}-${p(dt.getMinutes())}-${p(dt.getSeconds())}`;
    };

    const download = (name, content) => {
        const url = URL.createObjectURL(new Blob([content], { type: 'text/plain' }));
        Object.assign(document.createElement('a'), { href: url, download: name }).click();
        setTimeout(() => URL.revokeObjectURL(url), 100);
    };

    const notify = (msg, type = 'info') => {
        document.querySelectorAll('.claude-notif').forEach(n => n.remove());
        const el = document.createElement('div');
        el.className = 'claude-notif';
        el.style.cssText = `position:fixed;top:20px;right:20px;padding:15px 20px;border-radius:8px;color:#fff;
            font-family:system-ui;font-size:14px;z-index:10000;box-shadow:0 4px 12px rgba(0,0,0,0.3);
            background:${type === 'error' ? '#e53935' : type === 'success' ? '#43a047' : '#1e88e5'}`;
        el.textContent = msg;
        document.body.appendChild(el);
        setTimeout(() => el.remove(), 4000);
    };

    // File extensions
    const EXT = {
        'application/vnd.ant.code': (lang) => ({
            javascript: '.js', typescript: '.ts', python: '.py', html: '.html', css: '.css',
            java: '.java', cpp: '.cpp', c: '.c', go: '.go', rust: '.rs', php: '.php',
            ruby: '.rb', json: '.json', yaml: '.yaml', sql: '.sql', bash: '.sh', markdown: '.md',
            jsx: '.jsx', tsx: '.tsx', swift: '.swift', kotlin: '.kt'
        }[(lang || '').toLowerCase()] || '.txt'),
        'text/html': '.html',
        'text/markdown': '.md',
        'image/svg+xml': '.svg',
        'application/vnd.ant.mermaid': '.mmd',
        'application/vnd.ant.react': '.jsx'
    };

    const getExt = (type, lang) => typeof EXT[type] === 'function' ? EXT[type](lang) : EXT[type] || '.txt';

    // =============================================
    // API
    // =============================================
    const getOrgId = () => {
        const c = document.cookie.split(';').find(c => c.trim().startsWith('lastActiveOrg='));
        if (!c) throw new Error('Organization ID not found');
        return c.trim().split('=')[1];
    };

    const getConvId = () => (window.location.pathname.match(/\/chat\/([^/?]+)/) || [])[1];

    const fetchConv = async (id) => {
        const cid = id || getConvId();
        if (!cid) return null;
        const r = await fetch(`/api/organizations/${getOrgId()}/chat_conversations/${cid}?tree=true&rendering_mode=messages&render_all_tools=true`);
        if (!r.ok) throw new Error(`API error: ${r.status}`);
        return r.json();
    };

    // =============================================
    // ARTIFACT EXTRACTION
    // =============================================
    const extractArtifacts = (conv) => {
        const artifacts = new Map();
        const msgs = [...conv.chat_messages].sort((a, b) => a.index - b.index);

        msgs.forEach(msg => {
            msg.content.forEach(c => {
                if (c.type === 'tool_use' && c.name === 'artifacts' && c.input) {
                    const inp = c.input;
                    if (!artifacts.has(inp.id)) {
                        artifacts.set(inp.id, { versions: [], content: '', title: inp.title || inp.id, type: inp.type, lang: inp.language });
                    }
                    const art = artifacts.get(inp.id);

                    if (inp.command === 'create') {
                        art.content = inp.content || '';
                        art.title = inp.title || art.title;
                        art.type = inp.type;
                        art.lang = inp.language;
                    } else if (inp.command === 'rewrite') {
                        art.content = inp.content || '';
                        art.title = inp.title || art.title;
                    } else if (inp.command === 'update') {
                        art.content = art.content.replace(inp.old_str || '', inp.new_str || '');
                    }

                    art.versions.push({
                        version: art.versions.length + 1,
                        command: inp.command,
                        title: inp.title || art.title,
                        content: art.content,
                        type: art.type,
                        lang: art.lang,
                        timestamp: c.stop_timestamp || msg.created_at,
                        msgUuid: msg.uuid
                    });
                }
            });
        });

        return artifacts;
    };

    // =============================================
    // MARKDOWN GENERATION
    // =============================================
    const generateMarkdown = (conv, artifacts = null, embedArtifacts = false) => {
        const lines = [];

        // Header
        if (conv.project) lines.push(`*Project:* [${conv.project.name}](https://claude.ai/project/${conv.project.uuid})  `);
        lines.push(`*URL:* https://claude.ai/chat/${conv.uuid}  `);
        lines.push(`*Created:* ${formatDate(conv.created_at)}  `);
        lines.push(`*Updated:* ${formatDate(conv.updated_at)}`);
        if (conv.model) lines.push(`*Model:* \`${conv.model}\``);
        lines.push(`\n# ${conv.name || 'Untitled'}\n`);

        const msgs = [...conv.chat_messages].sort((a, b) => a.index - b.index);

        msgs.forEach(msg => {
            const role = msg.sender === 'human' ? 'Human' : 'Claude';
            lines.push('__________\n');
            lines.push(`## ${msg.index} - ${role}  `);
            lines.push(`*Created:* ${formatDate(msg.created_at)}  `);
            lines.push(`*UUID:* \`${msg.uuid}\`\n`);

            msg.content.forEach(c => {
                if (c.type === 'text') {
                    lines.push(c.text + '\n');
                } else if (c.type === 'tool_use' && c.name === 'artifacts' && c.input) {
                    const inp = c.input;
                    lines.push(`**Artifact:** ${inp.title || inp.id}  `);
                    lines.push(`*ID:* \`${inp.id}\` | *Command:* \`${inp.command}\`\n`);

                    if (embedArtifacts && artifacts?.has(inp.id)) {
                        const art = artifacts.get(inp.id);
                        const ver = art.versions.find(v => v.msgUuid === msg.uuid);
                        if (ver) {
                            const lang = ver.type === 'text/markdown' ? 'markdown' :
                                        ver.type === 'text/html' ? 'html' :
                                        ver.type === 'application/vnd.ant.react' ? 'jsx' :
                                        (ver.lang || '').toLowerCase() || '';
                            lines.push('```' + lang);
                            lines.push(ver.content);
                            lines.push('```\n');
                        }
                    }
                } else if (c.type === 'tool_use' && c.name === 'web_search' && c.input) {
                    lines.push(`**🔍 Web Search:** \`${c.input.query}\`\n`);
                } else if (c.type === 'thinking' && c.thinking) {
                    const sum = c.summaries?.length ? c.summaries[c.summaries.length - 1]?.summary : 'Thinking...';
                    lines.push(`*[Claude thinking...]*\n<details>\n<summary>${sum}</summary>\n${c.thinking}\n</details>\n`);
                }
            });

            (msg.files_v2 || msg.files || []).forEach(f => {
                lines.push(`**File:** ${f.file_name} | *ID:* \`${f.file_uuid}\`\n`);
            });
        });

        return lines.join('\n');
    };

    // =============================================
    // EXPORT FUNCTIONS
    // =============================================
    const genFilename = (conv) => `[${timestamp(conv.created_at)}] ${sanitize(conv.name)}.md`;

    const exportConversation = async () => {
        try {
            notify('Exporting...', 'info');
            const conv = await fetchConv();
            if (!conv) { notify('No conversation found', 'error'); return; }

            download(genFilename(conv), generateMarkdown(conv));
            notify('✅ Conversation exported!', 'success');
        } catch (e) {
            console.error('[Exporter]', e);
            notify(`Export failed: ${e.message}`, 'error');
        }
    };

    const exportWithArtifacts = async (embedOnly = false) => {
        try {
            notify('Exporting...', 'info');
            const conv = await fetchConv();
            if (!conv) { notify('No conversation found', 'error'); return; }

            const artifacts = extractArtifacts(conv);
            download(genFilename(conv), generateMarkdown(conv, artifacts, true));

            if (!embedOnly) {
                let count = 0;
                artifacts.forEach((art) => {
                    if (art.versions.length) {
                        const final = art.versions[art.versions.length - 1];
                        const ext = getExt(final.type, final.lang);
                        const fn = `[${timestamp(final.timestamp)}] ${sanitize(conv.name)} - ${sanitize(final.title)}${ext}`;
                        download(fn, final.content);
                        count++;
                    }
                });
                notify(`✅ Exported + ${count} artifact files!`, 'success');
            } else {
                notify(`✅ Exported with ${artifacts.size} artifacts inline!`, 'success');
            }
        } catch (e) {
            console.error('[Exporter]', e);
            notify(`Export failed: ${e.message}`, 'error');
        }
    };

    // =============================================
    // INIT - v6.2: Removed Raw JSON option
    // =============================================
    const init = () => {
        console.log('[Claude Exporter v6.2] Ready');
        GM_registerMenuCommand('📄 Conversation Only', exportConversation);
        GM_registerMenuCommand('📝 Conversation + Code Inline', () => exportWithArtifacts(true));
        GM_registerMenuCommand('📁 Conversation + Separate Files', () => exportWithArtifacts(false));
    };

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
})();