mirror of
https://github.com/sune-org/store.git
synced 2026-01-13 16:17:58 +00:00
28 lines
23 KiB
JSON
28 lines
23 KiB
JSON
[
|
|
{
|
|
"id": "bklh46m",
|
|
"name": "Sune Builder Girl",
|
|
"pinned": true,
|
|
"avatar": "data:image/webp;base64,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",
|
|
"url": "gh://sune-org/store@main/SuneBuilder.sune",
|
|
"updatedAt": 1756931304860,
|
|
"settings": {
|
|
"model": "or:google/gemini-2.5-pro",
|
|
"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": "high",
|
|
"reasoning_effort": "default",
|
|
"system_prompt": "You are SuneBuilderGPT. You deliver production-ready code. A sune is basically a modular piece of HTML that gets injected into already existing HTML. it can contain scripts and use tailwind. Assume tailwind CDN and lucide CDN already exists. UI should be mobile-first. Utilize web workers (if necessary). Utilize localstorage to cache inputs; use window.SUNE.id as the key. Respond with the HTML. Include a version number in the UI.",
|
|
"html": "<!-- GitHub Blob Fetch Sune (multi-URL with +) — compact, Fetch All on top -->\n<div id=\"ghFetchSune\" class=\"p-2 border-b border-gray-200\">\n <div class=\"flex items-center justify-between\">\n <div class=\"flex items-center gap-2\">\n <i data-lucide=\"github\" class=\"h-4 w-4\"></i>\n <span class=\"text-xs font-medium\">GitHub Fetch</span>\n </div>\n <div class=\"flex items-center gap-1\">\n <button id=\"ghFetchAllBtn\" type=\"button\" class=\"rounded-lg px-2 py-1 text-xs bg-black text-white hover:bg-black/90\">\n Fetch All\n </button>\n <button type=\"button\" id=\"ghAddRowBtn\" class=\"h-8 w-8 rounded-lg bg-gray-100 hover:bg-gray-200 flex items-center justify-center\" title=\"Add another URL\">\n <i data-lucide=\"plus\" class=\"h-4 w-4\"></i>\n </button>\n <button type=\"button\" id=\"ghHelpBtn\" class=\"text-[11px] px-2 py-1 rounded-lg bg-gray-100 hover:bg-gray-200\">Help</button>\n </div>\n </div>\n\n <div id=\"ghRows\" class=\"mt-2 space-y-1\"></div>\n\n <div class=\"mt-2\">\n <div id=\"ghStatus\" class=\"text-[11px] text-gray-600\"></div>\n </div>\n</div>\n\n<script>\n(() => {\n const root = document.getElementById('ghFetchSune');\n if (!root) return;\n\n const $ = (sel, ctx = root) => ctx.querySelector(sel);\n const $$ = (sel, ctx = root) => Array.from(ctx.querySelectorAll(sel));\n\n const els = {\n rows: $('#ghRows'),\n addBtn: $('#ghAddRowBtn'),\n fetchAllBtn: $('#ghFetchAllBtn'),\n helpBtn: $('#ghHelpBtn'),\n status: $('#ghStatus'),\n };\n\n const icons = () => { try { window.lucide && window.lucide.createIcons({ attrs: { 'aria-hidden': 'true' } }); } catch {} };\n\n // Storage keys (migrate from single URL if present)\n function storageKeyBase() {\n const t = document.getElementById('settingsBtnTop')?.title || '';\n const m = t.match(/Settings — (.+)$/);\n const name = (m ? m[1] : 'default').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');\n return 'gh_fetch_' + name;\n }\n const KEY_SINGLE = () => storageKeyBase(); // old single value\n const KEY_MULTI = () => storageKeyBase() + '_v2'; // new array value\n\n function loadUrls() {\n try {\n const multi = localStorage.getItem(KEY_MULTI());\n if (multi) {\n const arr = JSON.parse(multi);\n if (Array.isArray(arr)) return arr.map(x => String(x||''));\n }\n const old = localStorage.getItem(KEY_SINGLE());\n if (old && typeof old === 'string') return [old];\n } catch {}\n return [''];\n }\n\n function saveUrls(urls) {\n try {\n localStorage.setItem(KEY_MULTI(), JSON.stringify(urls.map(u => String(u||'').trim())));\n } catch {}\n }\n\n function setGlobalStatus(msg, kind = 'info') {\n els.status.textContent = msg || '';\n els.status.className = 'text-[11px] ' + (kind === 'error' ? 'text-red-600' : kind === 'ok' ? 'text-green-600' : 'text-gray-600');\n }\n\n function spinnerSvg(cls='h-3 w-3') {\n return `<svg class=\"animate-spin ${cls}\" viewBox=\"0 0 24 24\"><circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\" fill=\"none\"></circle><path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4A4 4 0 004 12z\"></path></svg>`;\n }\n\n // URL parsing and helpers\n function normalizeNewlines(s) { return s.replace(/\\r\\n/g, '\\n'); }\n\n function parseGitHubUrl(u) {\n try {\n const url = new URL(u);\n let owner, repo, ref, path, raw, blob, lines = null;\n\n // parse #Lx or #Lx-Ly\n if (url.hash && /L\\d+/i.test(url.hash)) {\n const m = url.hash.match(/L(\\d+)(?:-L(\\d+))?/i);\n if (m) lines = { start: Math.max(1, +m[1] || 1), end: Math.max(+m[1] || 1, +m[2] || +m[1] || 1) };\n }\n\n if (url.hostname === 'github.com') {\n const parts = url.pathname.split('/').filter(Boolean);\n // owner/repo/blob/raw/ref/...path\n if (parts.length >= 5 && (parts[2] === 'blob' || parts[2] === 'raw')) {\n owner = parts[0]; repo = parts[1]; ref = parts[3];\n path = parts.slice(4).join('/');\n raw = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path}`;\n blob = `https://github.com/${owner}/${repo}/blob/${ref}/${path}${url.hash || ''}`;\n return { raw, blob, owner, repo, ref, path, lines };\n }\n } else if (url.hostname === 'raw.githubusercontent.com') {\n const parts = url.pathname.split('/').filter(Boolean);\n if (parts.length >= 4) {\n owner = parts[0]; repo = parts[1]; ref = parts[2];\n path = parts.slice(3).join('/');\n raw = url.origin + url.pathname;\n blob = `https://github.com/${owner}/${repo}/blob/${ref}/${path}${url.hash || ''}`;\n return { raw, blob, owner, repo, ref, path, lines };\n }\n }\n return null;\n } catch { return null; }\n }\n\n function guessLangFromPath(p = '') {\n const name = p.toLowerCase();\n const ext = (name.split('.').pop() || '').trim();\n if (/\\.dockerfile$/.test(name) || /(^|\\/)dockerfile$/.test(name)) return 'dockerfile';\n if (/(^|\\/)makefile$/.test(name)) return 'makefile';\n const map = {\n js: 'javascript', mjs: 'javascript', cjs: 'javascript',\n ts: 'typescript', tsx: 'tsx', jsx: 'jsx',\n json: 'json', md: 'markdown',\n py: 'python', rb: 'ruby', go: 'go', rs: 'rust',\n java: 'java', kt: 'kotlin', swift: 'swift',\n c: 'c', h: 'c', cc: 'cpp', cpp: 'cpp', cxx: 'cpp', hpp: 'cpp', hh: 'cpp',\n cs: 'csharp', php: 'php',\n sh: 'bash', bash: 'bash', zsh: 'bash',\n env: 'dotenv', dotenv: 'dotenv',\n yml: 'yaml', yaml: 'yaml',\n toml: 'ini', ini: 'ini', cfg: 'ini',\n sql: 'sql', html: 'html', htm: 'html',\n css: 'css', scss: 'scss', less: 'less',\n vue: 'vue', svelte: 'svelte',\n gradle: 'groovy', groovy: 'groovy',\n txt: '', lock: '', log: ''\n };\n return map[ext] ?? '';\n }\n\n async function fetchOne(rawInput) {\n const input = (rawInput || '').trim();\n if (!input) throw new Error('Empty URL');\n const parsed = parseGitHubUrl(input);\n if (!parsed) throw new Error('Invalid GitHub URL. Use blob/raw format.');\n const res = await fetch(parsed.raw, { cache: 'no-store' });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n let content = await res.text();\n content = normalizeNewlines(content);\n\n if (parsed.lines) {\n const lines = content.split('\\n');\n const s = Math.max(1, parsed.lines.start), e = Math.max(s, parsed.lines.end);\n content = lines.slice(s - 1, e).join('\\n');\n }\n\n const lang = guessLangFromPath(parsed.path);\n const header = `From: ${parsed.blob}`;\n const md = `${header}\\n\\n\\`\\`\\`${lang}\\n${content}\\n\\`\\`\\``;\n return { md, blob: parsed.blob };\n }\n\n // Row rendering\n let urls = loadUrls();\n\n function rowTpl(idx, value='') {\n const id = `ghRow_${idx}`;\n return `\n <div class=\"rounded-lg border border-gray-200 p-2\">\n <div class=\"flex items-stretch gap-2\">\n <input\n data-role=\"url\"\n data-index=\"${idx}\"\n id=\"${id}\"\n type=\"url\"\n inputmode=\"url\"\n placeholder=\"https://github.com/owner/repo/blob/branch/file#L1-L20\"\n class=\"flex-1 rounded-md border border-gray-300 px-2 py-1 text-xs\"\n aria-label=\"GitHub blob or raw URL\"\n value=\"${(value || '').replace(/\"/g, '"')}\"\n />\n <button data-role=\"paste\" data-index=\"${idx}\" type=\"button\" class=\"rounded-md border px-2 py-1 text-xs bg-white hover:bg-gray-50\" title=\"Paste from clipboard\">\n <i data-lucide=\"clipboard\" class=\"h-4 w-4\"></i>\n </button>\n <button data-role=\"fetch-one\" data-index=\"${idx}\" type=\"button\" class=\"rounded-md px-2 py-1 text-xs bg-black text-white hover:bg-black/90\">\n Fetch\n </button>\n <button data-role=\"remove\" data-index=\"${idx}\" type=\"button\" class=\"rounded-md border px-2 py-1 text-xs bg-white hover:bg-gray-50\" title=\"Remove\">\n <i data-lucide=\"trash-2\" class=\"h-4 w-4\"></i>\n </button>\n </div>\n <div data-role=\"row-status\" data-index=\"${idx}\" class=\"mt-1 text-[11px] text-gray-500\"></div>\n </div>\n `;\n }\n\n function renderRows() {\n if (!urls.length) urls = [''];\n els.rows.innerHTML = urls.map((u, i) => rowTpl(i, u)).join('');\n icons();\n }\n\n function setRowStatus(index, msg, kind='info') {\n const node = els.rows.querySelector(`[data-role=\"row-status\"][data-index=\"${index}\"]`);\n if (!node) return;\n node.textContent = msg || '';\n node.className = 'mt-1 text-[11px] ' + (kind === 'error' ? 'text-red-600' : kind === 'ok' ? 'text-green-600' : 'text-gray-500');\n }\n\n async function postToChat(md) {\n if (window.USER && typeof window.USER.log === 'function') {\n await window.USER.log(md);\n return true;\n }\n throw new Error('window.USER.log is not available');\n }\n\n async function fetchRow(index) {\n const input = els.rows.querySelector(`input[data-role=\"url\"][data-index=\"${index}\"]`);\n const fetchBtn = els.rows.querySelector(`button[data-role=\"fetch-one\"][data-index=\"${index}\"]`);\n if (!input || !fetchBtn) return;\n\n setRowStatus(index, '');\n const url = (input.value || '').trim();\n if (!url) {\n setRowStatus(index, 'Enter a GitHub blob or raw URL.', 'error');\n return;\n }\n\n fetchBtn.dataset._prev = fetchBtn.innerHTML;\n fetchBtn.innerHTML = spinnerSvg();\n fetchBtn.classList.add('bg-gray-800');\n input.disabled = true;\n\n try {\n const { md, blob } = await fetchOne(url);\n await postToChat(md);\n urls[index] = blob; // store canonical blob\n saveUrls(urls);\n setRowStatus(index, 'Added to chat.', 'ok');\n } catch (err) {\n setRowStatus(index, (err && err.message) ? err.message : String(err), 'error');\n } finally {\n input.disabled = false;\n if (fetchBtn.dataset._prev) fetchBtn.innerHTML = fetchBtn.dataset._prev;\n fetchBtn.classList.remove('bg-gray-800');\n }\n }\n\n // Events - delegation for rows\n els.rows.addEventListener('click', async (e) => {\n const btn = e.target.closest('button[data-role]');\n if (!btn) return;\n const role = btn.getAttribute('data-role');\n const index = +btn.getAttribute('data-index');\n if (Number.isNaN(index)) return;\n\n if (role === 'paste') {\n try {\n const t = await navigator.clipboard.readText();\n if (t) {\n const input = els.rows.querySelector(`input[data-role=\"url\"][data-index=\"${index}\"]`);\n input.value = t.trim();\n urls[index] = input.value;\n saveUrls(urls);\n setRowStatus(index, 'Pasted from clipboard.');\n }\n } catch {\n setRowStatus(index, 'Clipboard read failed. Paste manually.', 'error');\n }\n } else if (role === 'fetch-one') {\n await fetchRow(index);\n } else if (role === 'remove') {\n if (urls.length === 1) {\n urls[0] = '';\n } else {\n urls.splice(index, 1);\n }\n saveUrls(urls);\n renderRows();\n }\n });\n\n els.rows.addEventListener('change', (e) => {\n const input = e.target.closest('input[data-role=\"url\"]');\n if (!input) return;\n const index = +input.getAttribute('data-index');\n if (Number.isNaN(index)) return;\n urls[index] = input.value.trim();\n saveUrls(urls);\n });\n\n els.rows.addEventListener('keydown', (e) => {\n const input = e.target.closest('input[data-role=\"url\"]');\n if (!input) return;\n if (e.key === 'Enter') {\n e.preventDefault();\n const index = +input.getAttribute('data-index');\n if (!Number.isNaN(index)) fetchRow(index);\n }\n });\n\n // Top controls\n els.addBtn.addEventListener('click', () => {\n urls.push('');\n saveUrls(urls);\n renderRows();\n requestAnimationFrame(() => {\n const lastIndex = urls.length - 1;\n const input = els.rows.querySelector(`input[data-role=\"url\"][data-index=\"${lastIndex}\"]`);\n input?.focus();\n });\n });\n\n els.fetchAllBtn.addEventListener('click', async () => {\n const list = urls.map((u, i) => ({ url: (u||'').trim(), i })).filter(x => x.url);\n if (!list.length) {\n setGlobalStatus('No URLs to fetch. Add with + or paste into a field.', 'error');\n return;\n }\n\n els.fetchAllBtn.dataset._prev = els.fetchAllBtn.innerHTML;\n els.fetchAllBtn.innerHTML = spinnerSvg('h-3 w-3');\n els.fetchAllBtn.classList.add('bg-gray-800');\n els.fetchAllBtn.disabled = true;\n setGlobalStatus('Fetching…');\n\n let ok = 0, fail = 0;\n for (const it of list) {\n try {\n await fetchRow(it.i);\n ok++;\n } catch {\n fail++;\n }\n }\n\n els.fetchAllBtn.innerHTML = els.fetchAllBtn.dataset._prev || 'Fetch All';\n els.fetchAllBtn.classList.remove('bg-gray-800');\n els.fetchAllBtn.disabled = false;\n\n if (fail === 0) setGlobalStatus(`Fetched ${ok} item(s).`, 'ok');\n else setGlobalStatus(`Fetched ${ok}, ${fail} failed.`, 'error');\n });\n\n els.helpBtn.addEventListener('click', () => {\n const tip = [\n 'How to use:',\n '• Click + to add more URL fields.',\n '• Enter GitHub blob URLs, e.g.:',\n ' https://github.com/owner/repo/blob/branch/path/to/file.ext#L10-L42',\n ' or raw URLs like:',\n ' https://raw.githubusercontent.com/owner/repo/branch/path/to/file.ext',\n '• Click Fetch on a row (or Fetch All) to post into the chat.',\n '',\n 'Notes:',\n '- #Lx or #Lx-Ly anchors will select line(s).',\n '- Private repos are not supported.',\n '- URLs are stored locally per sune.'\n ].join('\\n');\n alert(tip);\n });\n\n // Initial render\n function setGlobalStatus(msg, kind = 'info') {\n els.status.textContent = msg || '';\n els.status.className = 'text-[11px] ' + (kind === 'error' ? 'text-red-600' : kind === 'ok' ? 'text-green-600' : 'text-gray-600');\n }\n\n renderRows();\n icons();\n})();\n</script>\n\n",
|
|
"extension_html": "<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/titles.sune' private></sune>\n\n<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/sync.sune' private></sune>",
|
|
"script": ""
|
|
}
|
|
}
|
|
] |