mirror of
https://github.com/sune-org/store.git
synced 2026-01-13 16:17:58 +00:00
1 line
14 KiB
JSON
1 line
14 KiB
JSON
[{"id":"hmhdnfo","name":"Action Runner","pinned":false,"avatar":"data:image/webp;base64,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","url":"gh://sune-org/store@main/action_runner.sune","updatedAt":1757365060007,"settings":{"model":"openai/gpt-5","temperature":"1","top_p":"0.96","top_k":"0","frequency_penalty":"0","repetition_penalty":"1","min_p":"0","top_a":"0","verbosity":"","reasoning_effort":"default","system_prompt":"","html":"<!-- Sune: GitHub Actions Runner -->\n<div id=\"ghActionsRunner\" class=\"p-4 space-y-4 text-sm bg-white rounded-lg\">\n\n <div class=\"prose prose-sm max-w-none\">\n <h4>GitHub Actions Runner</h4>\n <p>Enter a repository to trigger workflows. Requires a GitHub token with <code>repo</code> scope, set in <button id=\"ghActionsOpenSettingsBtn\" class=\"text-blue-600 hover:underline\">Account Settings</button>.</p>\n </div>\n\n <div class=\"flex flex-col sm:flex-row items-stretch gap-2\">\n <div class=\"relative flex-grow\">\n <i data-lucide=\"github\" class=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400\"></i>\n <input type=\"text\" id=\"ghRepoPath\" placeholder=\"owner/repo\" class=\"w-full pl-9 pr-3 py-2 bg-white border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:outline-none transition\">\n </div>\n <button id=\"fetchWorkflowsBtn\" class=\"px-4 py-2 bg-black text-white rounded-xl hover:bg-black/90 active:scale-[.98] transition flex items-center justify-center gap-2 disabled:bg-gray-500\">\n <span id=\"fetchBtnText\">Fetch Workflows</span>\n <div id=\"fetchSpinner\" class=\"hidden animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full\"></div>\n </button>\n </div>\n\n <div id=\"ghStatusMessage\" class=\"text-gray-600 min-h-[1.25rem]\"></div>\n <div id=\"workflowsContainer\" class=\"grid grid-cols-1 md:grid-cols-2 gap-3\" data-default-branch=\"\"></div>\n\n</div>\n\n<script>\n(function() {\n // Ensure the sune runs in its own scope to prevent conflicts.\n \"use strict\";\n\n const suneId = window.SUNE ? window.SUNE.id : 'gh_actions_default';\n const container = document.getElementById('ghActionsRunner');\n if (!container) return;\n \n // Check for core dependencies from the main application.\n if (typeof globalStore === 'undefined' || typeof openAccountSettings === 'undefined') {\n container.innerHTML = '<p class=\"text-red-600\">Error: Core dependencies `globalStore` or `openAccountSettings` are missing. This sune cannot function.</p>';\n return;\n }\n\n const el = {\n repoInput: document.getElementById('ghRepoPath'),\n fetchBtn: document.getElementById('fetchWorkflowsBtn'),\n fetchBtnText: document.getElementById('fetchBtnText'),\n fetchSpinner: document.getElementById('fetchSpinner'),\n statusMsg: document.getElementById('ghStatusMessage'),\n workflowsContainer: document.getElementById('workflowsContainer'),\n openSettingsBtn: document.getElementById('ghActionsOpenSettingsBtn'),\n };\n\n // Use the unique SUNE ID for localStorage to avoid conflicts between different sune instances.\n const getStorageKey = () => `sune_gh_actions_repo_${suneId}`;\n\n const getGhToken = () => globalStore.ghToken || '';\n\n /**\n * A wrapper for the GitHub API fetch calls.\n * @param {string} url - The API endpoint path (e.g., /repos/owner/repo).\n * @param {string} token - The GitHub API token.\n * @param {object} options - Standard fetch options.\n * @returns {Promise<Response>}\n */\n const apiFetch = (url, token, options = {}) => {\n const headers = {\n 'Authorization': `Bearer ${token}`,\n 'Accept': 'application/vnd.github.v3+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n ...options.headers,\n };\n return fetch(`https://api.github.com${url}`, { ...options, headers });\n };\n\n /**\n * Toggles the loading state of the fetch button.\n * @param {boolean} isLoading - Whether to show the loading state.\n */\n const showLoading = (isLoading) => {\n el.fetchSpinner.classList.toggle('hidden', !isLoading);\n el.fetchBtnText.textContent = isLoading ? 'Fetching...' : 'Fetch Workflows';\n el.fetchBtn.disabled = isLoading;\n };\n\n /**\n * Displays a status message to the user.\n * @param {string} message - The message to display.\n * @param {boolean} isError - If true, formats the message as an error.\n */\n const setStatus = (message, isError = false) => {\n el.statusMsg.innerHTML = message;\n el.statusMsg.className = isError ? 'text-red-600' : 'text-gray-600';\n };\n\n /**\n * Renders the list of runnable workflows.\n * @param {Array} workflows - The array of workflow objects from the GitHub API.\n * @param {string} repoPath - The \"owner/repo\" string.\n */\n const renderWorkflows = (workflows, repoPath) => {\n el.workflowsContainer.innerHTML = '';\n const activeWorkflows = workflows.filter(wf => wf.state === 'active');\n if (activeWorkflows.length === 0) {\n return setStatus('No runnable workflows found in this repository.');\n }\n\n activeWorkflows.forEach(wf => {\n const btn = document.createElement('button');\n btn.className = 'w-full text-left p-3 border border-gray-200 rounded-lg hover:bg-gray-50 active:bg-gray-100 transition flex items-center justify-between gap-2';\n btn.innerHTML = `<span class=\"font-medium truncate\">${wf.name}</span><span class=\"flex items-center gap-1.5 text-xs text-gray-500\"><span>Run</span><i data-lucide=\"play\" class=\"h-4 w-4\"></i></span>`;\n Object.assign(btn.dataset, { workflowId: wf.id, workflowPath: wf.path, repoPath });\n el.workflowsContainer.appendChild(btn);\n });\n\n if (window.lucide) window.lucide.createIcons();\n };\n\n /**\n * Fetches the repository details and its workflows.\n */\n const fetchWorkflows = async () => {\n const token = getGhToken();\n if (!token) {\n return setStatus('GitHub token not found. Please set it in Account Settings.', true);\n }\n\n const repoPath = el.repoInput.value.trim();\n if (!repoPath.includes('/')) {\n return setStatus('Invalid repository format. Use \"owner/repo\".', true);\n }\n\n showLoading(true);\n setStatus('Fetching repository details...');\n el.workflowsContainer.innerHTML = '';\n\n try {\n const [repoRes, wfRes] = await Promise.all([\n apiFetch(`/repos/${repoPath}`, token),\n apiFetch(`/repos/${repoPath}/actions/workflows`, token)\n ]);\n\n if (!repoRes.ok) throw new Error(repoRes.status === 404 ? 'Repository not found or access denied.' : `Repo fetch error: ${repoRes.statusText}`);\n if (!wfRes.ok) throw new Error(`Workflow fetch error: ${wfRes.statusText}`);\n \n const [repoData, wfData] = await Promise.all([repoRes.json(), wfRes.json()]);\n el.workflowsContainer.dataset.defaultBranch = repoData.default_branch || 'main';\n \n setStatus('');\n renderWorkflows(wfData.workflows, repoPath);\n localStorage.setItem(getStorageKey(), repoPath);\n } catch (error) {\n setStatus(error.message, true);\n } finally {\n showLoading(false);\n }\n };\n\n /**\n * Handles the logic for triggering a specific workflow.\n * @param {Event} e - The click event.\n */\n const triggerWorkflow = async (e) => {\n const button = e.target.closest('button[data-workflow-id]');\n if (!button) return;\n\n const token = getGhToken();\n if (!token) return setStatus('GitHub token not found.', true);\n \n const { workflowId, workflowPath, repoPath } = button.dataset;\n const ref = el.workflowsContainer.dataset.defaultBranch;\n const inputs = {};\n\n const originalContent = button.innerHTML;\n button.disabled = true;\n button.innerHTML = `<span class=\"font-medium truncate\">Inspecting...</span><div class=\"animate-spin h-4 w-4 border-2 border-gray-400 border-t-transparent rounded-full\"></div>`;\n setStatus(`Checking for inputs in ${workflowPath}...`);\n\n try {\n const contentRes = await apiFetch(`/repos/${repoPath}/contents/${workflowPath}?ref=${ref}`, token);\n if (!contentRes.ok) throw new Error('Could not read workflow file to check for inputs.');\n const fileContent = atob((await contentRes.json()).content);\n \n // Lightweight regex to find workflow_dispatch inputs without needing a full YAML parser.\n const dispatchMatch = fileContent.match(/on:\\s*([\\s\\S]*?)(?=\\n\\w|\\n$)/);\n if (dispatchMatch && dispatchMatch[0].includes('workflow_dispatch')) {\n const inputsMatch = dispatchMatch[0].match(/inputs:\\s*([\\s\\S]*?)(?=\\n\\s*\\w|^\\w|\\s*$)/);\n if (inputsMatch) {\n // Extract input keys\n const inputKeys = [...inputsMatch[1].matchAll(/^\\s{2,}(\\w+):/gm)].map(m => m[1]);\n for (const key of inputKeys) {\n const val = prompt(`Enter value for input: \"${key}\"`);\n if (val === null) throw new Error('Workflow run cancelled by user.');\n inputs[key] = val;\n }\n }\n }\n\n button.innerHTML = `<span class=\"font-medium truncate\">Triggering...</span><div class=\"animate-spin h-4 w-4 border-2 border-gray-400 border-t-transparent rounded-full\"></div>`;\n setStatus(`Triggering workflow: ${button.querySelector('span').textContent}`);\n\n const dispatchRes = await apiFetch(`/repos/${repoPath}/actions/workflows/${workflowId}/dispatches`, token, {\n method: 'POST',\n body: JSON.stringify({ ref, inputs })\n });\n\n if (dispatchRes.status !== 204) {\n const errorData = await dispatchRes.json().catch(() => ({}));\n throw new Error(errorData.message || `API error: ${dispatchRes.status}`);\n }\n\n setStatus(`Successfully triggered workflow. <a href=\"https://github.com/${repoPath}/actions\" target=\"_blank\" rel=\"noopener\" class=\"text-blue-600 hover:underline\">View runs</a>.`);\n button.innerHTML = `<span class=\"font-medium truncate text-green-600\">Triggered!</span><i data-lucide=\"check-circle\" class=\"h-4 w-4 text-green-600\"></i>`;\n if (window.lucide) window.lucide.createIcons();\n\n } catch (error) {\n setStatus(error.message, true);\n // Only restore the button if it didn't succeed\n button.innerHTML = originalContent; \n } finally {\n // Restore button after a delay to show success/failure state.\n setTimeout(() => {\n if (!button.innerHTML.includes('Triggered!')) button.innerHTML = originalContent;\n button.disabled = false;\n if (window.lucide) window.lucide.createIcons();\n }, 3000);\n }\n };\n\n /**\n * Initializes the sune.\n */\n const init = () => {\n el.repoInput.value = localStorage.getItem(getStorageKey()) || '';\n el.fetchBtn.addEventListener('click', fetchWorkflows);\n el.repoInput.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n fetchWorkflows();\n }\n });\n el.workflowsContainer.addEventListener('click', triggerWorkflow);\n el.openSettingsBtn.addEventListener('click', () => { \n if (window.openAccountSettings) window.openAccountSettings(); \n });\n \n if (window.lucide) window.lucide.createIcons();\n };\n\n // Run the initialization logic.\n init();\n\n})();\n</script>\n","extension_html":"<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/sync.sune' private />","hide_composer":true,"presence_penalty":"0","max_tokens":""},"storage":{}}] |