mirror of
https://github.com/sune-org/store.git
synced 2026-01-13 16:17:58 +00:00
1 line
9.5 KiB
JSON
1 line
9.5 KiB
JSON
[{"id":"jc30des","name":"Terminal","pinned":false,"avatar":"","url":"gh://sune-org/store/terminal.sune","updatedAt":1757462661106,"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":"<div id=\"sune-terminal-container\" class=\"w-full max-w-4xl mx-auto my-0 font-mono text-sm\">\n <div class=\"flex flex-col bg-gray-900 text-gray-200 rounded-lg shadow-xl overflow-hidden border border-gray-700 h-72 md:h-96\">\n \n <!-- Toolbar -->\n <div class=\"flex items-center justify-between px-3 py-1.5 bg-gray-800/70 border-b border-gray-700\">\n <span class=\"text-xs text-gray-400\">JS Terminal</span>\n <button id=\"sune-terminal-clear\" title=\"Clear Console\" class=\"p-1 rounded text-gray-400 hover:bg-gray-700 hover:text-white transition-colors\">\n <i data-lucide=\"trash-2\" class=\"h-4 w-4\"></i>\n </button>\n </div>\n\n <!-- Output Area -->\n <div id=\"sune-terminal-output\" class=\"flex-1 p-2 overflow-y-auto\">\n <!-- Log entries will be injected here -->\n </div>\n\n <!-- Input Area -->\n <div class=\"flex items-center gap-2 p-2 border-t border-gray-700 bg-gray-800/70\">\n <span class=\"text-green-400\">></span>\n <form id=\"sune-terminal-form\" class=\"flex-1\">\n <input id=\"sune-terminal-input\" type=\"text\" class=\"w-full bg-transparent border-none outline-none focus:ring-0 placeholder:text-gray-500\" placeholder=\"Type a command and press Enter\" spellcheck=\"false\" autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"none\">\n </form>\n </div>\n </div>\n</div>\n\n<script>\n(function() {\n // Ensure we don't run this script multiple times if the sune is re-injected\n if (window.suneTerminalInitialized) {\n // A basic cleanup might be needed here in a more complex SPA,\n // but for now, we just prevent re-initialization.\n return;\n }\n window.suneTerminalInitialized = true;\n\n const suneId = window.SUNE ? window.SUNE.id : 'default-terminal';\n const historyKey = `sune_terminal_history_${suneId}`;\n\n const container = document.getElementById('sune-terminal-container');\n const output = document.getElementById('sune-terminal-output');\n const form = document.getElementById('sune-terminal-form');\n const input = document.getElementById('sune-terminal-input');\n const clearBtn = document.getElementById('sune-terminal-clear');\n\n let commandHistory = [];\n let historyIndex = -1;\n\n // --- Utility Functions ---\n\n const escapeHtml = (unsafe) => {\n return unsafe\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n };\n \n const scrollToBottom = () => {\n output.scrollTop = output.scrollHeight;\n };\n\n const formatValue = (value) => {\n if (typeof value === 'undefined') {\n return `<span class=\"text-gray-500\">undefined</span>`;\n }\n if (value === null) {\n return `<span class=\"text-purple-400\">null</span>`;\n }\n if (typeof value === 'string') {\n return `<span class=\"text-green-400\">'${escapeHtml(value)}'</span>`;\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return `<span class=\"text-purple-400\">${value}</span>`;\n }\n if (typeof value === 'function') {\n return `<span class=\"text-cyan-400 italic\">ƒ ${escapeHtml(value.name) || '(anonymous)'}()</span>`;\n }\n if (typeof value === 'object') {\n try {\n // Pretty print object with 2-space indentation\n const pretty = escapeHtml(JSON.stringify(value, null, 2));\n return `<pre class=\"!p-0 !bg-transparent !border-none\">${pretty}</pre>`;\n } catch (e) {\n return `<span class=\"text-red-400\">[Circular Object]</span>`;\n }\n }\n return escapeHtml(String(value));\n };\n \n // --- Core Logging Logic ---\n\n const logToTerminal = (content, type = 'log') => {\n const entry = document.createElement('div');\n entry.className = 'flex items-start gap-2 mb-1';\n\n let icon = '';\n let textColor = 'text-gray-300';\n\n switch (type) {\n case 'command':\n icon = `<span class=\"text-gray-500 mt-0.5\">></span>`;\n textColor = 'text-gray-100';\n break;\n case 'return':\n icon = `<span class=\"text-gray-500 mt-0.5\"><</span>`;\n textColor = 'text-gray-400';\n break;\n case 'warn':\n icon = `<span class=\"text-yellow-400 mt-0.5\"><i data-lucide=\"alert-triangle\" class=\"h-4 w-4\"></i></span>`;\n textColor = 'text-yellow-400';\n break;\n case 'error':\n icon = `<span class=\"text-red-400 mt-0.5\"><i data-lucide=\"x-circle\" class=\"h-4 w-4\"></i></span>`;\n textColor = 'text-red-400';\n break;\n case 'info':\n icon = `<span class=\"text-blue-400 mt-0.5\"><i data-lucide=\"info\" class=\"h-4 w-4\"></i></span>`;\n textColor = 'text-blue-400';\n break;\n }\n \n entry.innerHTML = `${icon}<div class=\"flex-1 ${textColor}\">${content}</div>`;\n output.appendChild(entry);\n scrollToBottom();\n };\n\n // --- History Management ---\n\n const loadHistory = () => {\n try {\n const storedHistory = localStorage.getItem(historyKey);\n if (storedHistory) {\n commandHistory = JSON.parse(storedHistory);\n historyIndex = commandHistory.length;\n }\n } catch (e) {\n console.error(\"Failed to load command history:\", e);\n commandHistory = [];\n }\n };\n\n const saveHistory = () => {\n try {\n // Limit history size\n if (commandHistory.length > 100) {\n commandHistory.splice(0, commandHistory.length - 100);\n }\n localStorage.setItem(historyKey, JSON.stringify(commandHistory));\n } catch (e) {\n console.error(\"Failed to save command history:\", e);\n }\n };\n\n const addToHistory = (command) => {\n if (command && command !== commandHistory[commandHistory.length - 1]) {\n commandHistory.push(command);\n saveHistory();\n }\n historyIndex = commandHistory.length;\n };\n \n // --- Event Handlers ---\n \n form.addEventListener('submit', (e) => {\n e.preventDefault();\n const command = input.value.trim();\n \n if (command) {\n logToTerminal(escapeHtml(command), 'command');\n addToHistory(command);\n\n try {\n // Using new Function() is slightly safer than eval() as it runs in the global scope\n const result = (new Function(`return ${command}`))();\n if (typeof result !== 'undefined') {\n logToTerminal(formatValue(result), 'return');\n }\n } catch (error) {\n logToTerminal(escapeHtml(error.message), 'error');\n }\n }\n input.value = '';\n scrollToBottom();\n });\n\n input.addEventListener('keydown', (e) => {\n if (e.key === 'ArrowUp') {\n e.preventDefault();\n if (historyIndex > 0) {\n historyIndex--;\n input.value = commandHistory[historyIndex];\n input.setSelectionRange(input.value.length, input.value.length);\n }\n } else if (e.key === 'ArrowDown') {\n e.preventDefault();\n if (historyIndex < commandHistory.length - 1) {\n historyIndex++;\n input.value = commandHistory[historyIndex];\n input.setSelectionRange(input.value.length, input.value.length);\n } else {\n historyIndex = commandHistory.length;\n input.value = '';\n }\n } else if (e.key === 'l' && (e.ctrlKey || e.metaKey)) {\n e.preventDefault();\n output.innerHTML = '';\n logToTerminal('Console was cleared.', 'info');\n }\n });\n\n clearBtn.addEventListener('click', () => {\n output.innerHTML = '';\n logToTerminal('Console was cleared.', 'info');\n });\n\n // --- Console Interception ---\n \n const originalConsole = {};\n const consoleMethods = ['log', 'warn', 'error', 'info', 'debug'];\n\n consoleMethods.forEach(method => {\n originalConsole[method] = console[method];\n console[method] = (...args) => {\n // Call original console method\n originalConsole[method].apply(console, args);\n \n // Log to our sune terminal\n const formattedArgs = args.map(formatValue).join(' ');\n logToTerminal(formattedArgs, method);\n };\n });\n \n // --- Cleanup ---\n\n const observer = new MutationObserver((mutationsList) => {\n for (const mutation of mutationsList) {\n if (mutation.removedNodes) {\n mutation.removedNodes.forEach(node => {\n if (node === container) {\n // Restore original console methods when sune is removed\n consoleMethods.forEach(method => {\n console[method] = originalConsole[method];\n });\n observer.disconnect();\n window.suneTerminalInitialized = false;\n }\n });\n }\n }\n });\n\n if (container.parentElement) {\n observer.observe(container.parentElement, { childList: true });\n }\n\n // --- Initialization ---\n \n const init = () => {\n loadHistory();\n logToTerminal('Sune Terminal Initialized. Ctrl+L to clear.', 'info');\n lucide.createIcons(); // Render any new icons\n input.focus();\n };\n\n init();\n\n})();\n</script>\n","extension_html":"<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/sync.sune' private></sune>","hide_composer":false,"presence_penalty":0,"max_tokens":0},"storage":{}}] |