mirror of
https://github.com/sune-org/store.git
synced 2026-01-13 16:17:58 +00:00
27 lines
17 KiB
JSON
27 lines
17 KiB
JSON
[
|
|
{
|
|
"id": "e1yibwd",
|
|
"name": "Marketplace",
|
|
"pinned": false,
|
|
"avatar": "data:image/webp;base64,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",
|
|
"url": "gh://sune-org/store@main/marketplace.sune",
|
|
"updatedAt": 1757010334918,
|
|
"settings": {
|
|
"model": "openai/gpt-5",
|
|
"temperature": 1,
|
|
"top_p": 0.97,
|
|
"top_k": 0,
|
|
"frequency_penalty": 0,
|
|
"presence_penalty": 0,
|
|
"repetition_penalty": 1,
|
|
"min_p": 0,
|
|
"top_a": 0,
|
|
"max_tokens": 0,
|
|
"verbosity": "",
|
|
"reasoning_effort": "default",
|
|
"system_prompt": "",
|
|
"html": "<!--\n Sune: Marketplace\n Version: 1.8.1\n Description: Discover and install new sunes from the official sune-org/store repository.\n-->\n<div id=\"suneMarketplaceContainer\" class=\"p-4 md:p-6 bg-slate-50\">\n <!-- Header -->\n <div class=\"mb-6 pb-4 border-b border-slate-200\">\n <div class=\"flex justify-between items-center\">\n <h1 class=\"text-2xl font-bold text-slate-800\">Sune Marketplace</h1>\n <span class=\"text-xs font-mono text-slate-400\">v1.8.1</span>\n </div>\n <p class=\"mt-1 text-sm text-slate-600\">Discover and install new capabilities for your Sune.</p>\n </div>\n\n <!-- Content Area: Populated by script -->\n <div id=\"marketplaceContent\">\n <div class=\"flex justify-center items-center py-20\">\n <div class=\"animate-spin rounded-full h-12 w-12 border-b-2 border-slate-500\"></div>\n </div>\n </div>\n\n <!-- Log Viewer -->\n <div class=\"mt-8\">\n <div class=\"flex justify-between items-center mb-2\">\n <h3 class=\"text-sm font-semibold text-slate-600\">Installation Log</h3>\n <button id=\"marketplaceClearLogBtn\" class=\"text-xs text-slate-500 hover:text-slate-800 hover:underline\">Clear Log</button>\n </div>\n <div id=\"marketplaceLogContainer\" class=\"bg-slate-800 text-white font-mono text-xs rounded-lg p-4 h-40 overflow-y-auto\" style=\"scroll-behavior: smooth;\">\n </div>\n </div>\n\n <!-- Contribution Snippet -->\n <div class=\"mt-8 pt-6 border-t border-slate-200\">\n <div class=\"text-center bg-white p-6 rounded-lg border border-slate-200 shadow-sm\">\n <div class=\"flex items-center justify-center gap-2\">\n <i data-lucide=\"git-pull-request-arrow\" class=\"h-5 w-5 text-slate-600\"></i>\n <h3 class=\"text-lg font-semibold text-slate-800\">Contribute your Sune</h3>\n </div>\n <p class=\"mt-2 text-sm text-slate-600 max-w-lg mx-auto\">\n Have a sune you'd like to share? Add it by opening a pull request in the\n <a href=\"https://github.com/sune-org/store\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"font-medium text-sky-600 hover:underline\">sune-org/store</a> repository.\n </p>\n </div>\n </div>\n</div>\n\n<script>\n(() => {\n const rootElement = document.getElementById('suneMarketplaceContainer');\n if (!rootElement) {\n console.error(\"Marketplace Sune: Root element #suneMarketplaceContainer not found.\");\n return;\n }\n\n const marketplaceContent = rootElement.querySelector('#marketplaceContent');\n const logContainer = rootElement.querySelector('#marketplaceLogContainer');\n\n window.SuneMarketplace = window.SuneMarketplace || { catalog: null, isLoading: false };\n\n const logMessage = (message, type = 'info') => {\n if (!logContainer) return;\n const entry = document.createElement('div');\n const sanitizedMessage = message.toString().replace(/</g, '<').replace(/>/g, '>');\n let prefix = '»';\n let colorClass = 'text-slate-300';\n switch (type) {\n case 'success': prefix = '✔'; colorClass = 'text-green-400'; break;\n case 'error': prefix = '✖'; colorClass = 'text-red-400'; break;\n case 'warn': prefix = '⚠'; colorClass = 'text-yellow-400'; break;\n }\n entry.className = `flex items-start gap-2 ${colorClass}`;\n entry.innerHTML = `<span class=\"flex-shrink-0 pt-0.5\">${prefix}</span><span class=\"flex-grow\">${sanitizedMessage}</span>`;\n logContainer.appendChild(entry);\n logContainer.scrollTop = logContainer.scrollHeight;\n };\n\n const clearLog = () => {\n if (!logContainer) return;\n logContainer.innerHTML = '';\n logMessage(\"Log cleared.\");\n };\n\n const convertToJsDelivrUrl = (rawUrl) => {\n if (!rawUrl || typeof rawUrl !== 'string' || !rawUrl.startsWith('https://raw.githubusercontent.com/')) return rawUrl;\n try {\n const url = new URL(rawUrl);\n const [, user, repo, branch, ...filePath] = url.pathname.split('/');\n return `https://cdn.jsdelivr.net/gh/${user}/${repo}@${branch}/${filePath.join('/')}`;\n } catch (e) { return rawUrl; }\n };\n\n const convertToShortUrl = (rawUrl) => {\n if (!rawUrl || !rawUrl.startsWith('https://raw.githubusercontent.com/')) return rawUrl;\n try {\n const path = rawUrl.substring('https://raw.githubusercontent.com/'.length);\n const parts = path.split('/');\n const user = parts.shift();\n const repo = parts.shift();\n const branch = parts.shift();\n return `${user}/${repo}@${branch}/${parts.join('/')}`;\n } catch {\n return rawUrl;\n }\n };\n\n const renderMarketplace = (items) => {\n const installedSunes = window.SUNE?.list || [];\n const html = items.map(item => {\n const rawUrl = item.raw;\n const shortUrl = convertToShortUrl(rawUrl);\n const ghUrl = `gh://${shortUrl}`;\n const isInstalled = installedSunes.some(s => s.url === rawUrl || s.url === shortUrl || s.url === ghUrl);\n\n const buttonText = isInstalled ? 'Installed' : 'Install';\n const buttonIcon = isInstalled ? 'check-circle' : 'download-cloud';\n const buttonClasses = isInstalled \n ? \"bg-slate-100 text-slate-500 cursor-default\" \n : \"bg-slate-600 text-white hover:bg-slate-700 active:bg-slate-800 focus:ring-2 focus:ring-offset-2 focus:ring-slate-500\";\n \n const displayName = (item.name || 'Untitled Sune').replace(/\\.sune$/, '').replace(/[-_]/g, ' ').replace(/\\b\\w/g, l => l.toUpperCase());\n const description = item.description || \"Official utility sune to enhance workflow.\";\n\n return `\n <div class=\"bg-white border border-slate-200 rounded-lg shadow-sm p-5 flex flex-col justify-between transition-shadow hover:shadow-md\">\n <div>\n <h3 class=\"text-base font-semibold text-slate-800\">${displayName}</h3>\n <p class=\"text-sm text-slate-500 mt-1 h-10 overflow-hidden\">${description}</p>\n </div>\n <button \n data-name=\"${displayName}\" \n data-short-url=\"${shortUrl}\" \n data-raw-url=\"${rawUrl}\"\n class=\"install-btn mt-4 w-full px-4 py-2 text-sm font-medium rounded-md transition-colors duration-200 flex items-center justify-center gap-2 ${buttonClasses}\" \n ${isInstalled ? 'disabled' : ''}>\n <i data-lucide=\"${buttonIcon}\" class=\"h-4 w-4\"></i>\n <span>${buttonText}</span>\n </button>\n </div>`;\n }).join('');\n marketplaceContent.innerHTML = `<div class=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4\">${html}</div>`;\n window.lucide?.createIcons();\n };\n\n const handleInstallClick = async (button) => {\n if (button.disabled) return;\n \n const displayName = button.dataset.name || 'Unknown Sune';\n const rawUrl = button.dataset.rawUrl;\n const shortUrl = button.dataset.shortUrl;\n const ghUrl = `gh://${shortUrl}`;\n const fetchUrl = convertToJsDelivrUrl(rawUrl);\n\n clearLog();\n logMessage(`Starting installation for '${displayName}'...`);\n\n button.disabled = true;\n button.innerHTML = `<i data-lucide=\"loader-circle\" class=\"h-4 w-4 animate-spin\"></i><span>Installing...</span>`;\n window.lucide?.createIcons();\n\n try {\n logMessage(`Fetching from: ${fetchUrl}`);\n const response = await fetch(fetchUrl);\n if (!response.ok) throw new Error(`Fetch failed with status: ${response.status}`);\n\n const suneDataArray = await response.json();\n if (!Array.isArray(suneDataArray)) throw new Error('Invalid or empty sune file format.');\n \n logMessage(`Parsed ${suneDataArray.length} sune(s) from file.`);\n logMessage(`Assigning sync URL: ${ghUrl}`);\n\n const allSunes = window.SUNE.list;\n let added = 0, updated = 0, skipped = 0;\n\n for (const suneObject of suneDataArray) {\n if (!suneObject || !suneObject.id) {\n logMessage('Skipping invalid sune entry without an ID.', 'warn');\n skipped++;\n continue;\n }\n \n suneObject.url = ghUrl; // Assign the correct gh:// prefixed URL\n const existing = allSunes.find(s => s.id === suneObject.id);\n\n if (!existing) {\n window.SUNE.create(suneObject);\n added++;\n logMessage(`Installed: '${suneObject.name || suneObject.id}'.`, 'success');\n } else if (+(suneObject.updatedAt || 0) > +(existing.updatedAt || 0)) {\n Object.assign(existing, suneObject);\n updated++;\n logMessage(`Updated: '${suneObject.name || suneObject.id}'.`, 'success');\n } else {\n skipped++;\n }\n }\n\n if (updated > 0) {\n window.SUNE.save(); // Persist changes from Object.assign\n }\n if (skipped > 0 && added === 0 && updated === 0) {\n logMessage('All sunes are already up to date.', 'info');\n }\n if (added > 0 || updated > 0) {\n logMessage('Refreshing sune list...', 'success');\n window.renderSidebar();\n }\n\n button.innerHTML = `<i data-lucide=\"check-circle\" class=\"h-4 w-4\"></i><span>${updated > 0 ? 'Updated' : 'Installed'}</span>`;\n button.classList.remove('bg-slate-600', 'hover:bg-slate-700');\n button.classList.add('bg-slate-100', 'text-slate-500', 'cursor-default');\n\n } catch (error) {\n logMessage(`Installation failed: ${error.message}`, 'error');\n button.disabled = false;\n button.innerHTML = `<i data-lucide=\"alert-triangle\" class=\"h-4 w-4\"></i><span>Retry Install</span>`;\n } finally {\n window.lucide?.createIcons();\n }\n };\n \n const initMarketplace = async () => {\n clearLog();\n if (window.SuneMarketplace.catalog) {\n renderMarketplace(window.SuneMarketplace.catalog);\n logMessage(\"Loaded cached catalog.\");\n return;\n }\n if (window.SuneMarketplace.isLoading) return;\n \n try {\n window.SuneMarketplace.isLoading = true;\n logMessage(\"Fetching sune catalog...\");\n \n const catalogUrl = 'https://cdn.jsdelivr.net/gh/sune-org/store@main/catalog.json';\n const response = await fetch(catalogUrl, { cache: 'no-store' });\n if (!response.ok) throw new Error(`Catalog fetch failed: Status ${response.status}`);\n \n const catalogData = (await response.json()).filter(i => i.name && i.name.endsWith('.sune'));\n window.SuneMarketplace.catalog = catalogData;\n logMessage(`Catalog loaded with ${catalogData.length} items.`);\n renderMarketplace(catalogData);\n } catch (error) {\n logMessage(`Error initializing marketplace: ${error.message}`, 'error');\n marketplaceContent.innerHTML = `<div class=\"text-center py-20 bg-red-50 text-red-700 rounded-lg\"><p>Could not load Marketplace.</p><p class=\"text-sm mt-2\">${error.message}</p></div>`;\n } finally {\n window.SuneMarketplace.isLoading = false;\n }\n };\n \n rootElement.addEventListener('click', (event) => {\n const installButton = event.target.closest('.install-btn');\n if (installButton) {\n handleInstallClick(installButton);\n return;\n }\n if (event.target.closest('#marketplaceClearLogBtn')) {\n clearLog();\n return;\n }\n });\n\n initMarketplace();\n window.lucide?.createIcons();\n})();\n</script>\n",
|
|
"extension_html": "<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/sync.sune' private />"
|
|
}
|
|
}
|
|
] |