mirror of
https://github.com/sune-org/store.git
synced 2026-01-13 16:17:58 +00:00
1 line
17 KiB
JSON
1 line
17 KiB
JSON
[{"id":"znphli6","name":"GitHub Recent","pinned":false,"avatar":"","url":"gh://sune-org/store/github-utilities/recent.sune","updatedAt":1762646322875,"settings":{"model":"google/gemini-2.5-pro","temperature":"","top_p":"","top_k":"","frequency_penalty":"","repetition_penalty":"","min_p":"","top_a":"","verbosity":"","reasoning_effort":"default","system_prompt":"","html":"<!-- Sune: My GitHub Commits (Direct API) · v2.0.0 -->\n\n<section\n id=\"sune-github-commits\"\n class=\"w-full max-w-3xl mx-auto my-3 px-3\n bg-white border border-gray-200 rounded-2xl\n shadow-sm sm:shadow-md\n text-xs text-gray-800\"\n x-data=\"githubCommitsSune()\"\n x-init=\"init()\"\n>\n <!-- Header -->\n <header class=\"flex items-center gap-2 py-2 border-b border-gray-100\">\n <div class=\"h-7 w-7 rounded-xl bg-gray-900 text-white flex items-center justify-center text-[11px]\">\n GH\n </div>\n <div class=\"flex-1 min-w-0\">\n <div class=\"flex items-center gap-1\">\n <h2 class=\"text-[11px] font-semibold truncate\">\n My Recent GitHub Commits\n </h2>\n <span class=\"text-[9px] text-gray-400\">v2.0.0</span>\n </div>\n <p class=\"text-[9px] text-gray-500 truncate\">\n Uses USER.githubToken to show your latest commits (via /search/commits).\n </p>\n </div>\n <button\n type=\"button\"\n @click=\"collapsed = !collapsed\"\n class=\"ml-1 h-7 w-7 rounded-xl flex items-center justify-center\n bg-gray-50 hover:bg-gray-100 text-gray-500\"\n :title=\"collapsed ? 'Expand' : 'Collapse'\"\n >\n <i data-lucide=\"chevron-down\"\n class=\"w-4 h-4 transition-transform\"\n :class=\"collapsed ? '-rotate-90' : 'rotate-0'\"></i>\n </button>\n </header>\n\n <!-- Body -->\n <div\n x-show=\"!collapsed\"\n x-transition\n class=\"py-2 flex flex-col gap-2\"\n style=\"display:none\"\n >\n <!-- Status / Controls -->\n <div class=\"flex items-center gap-2\">\n <div class=\"flex items-center gap-1.5 text-[9px] text-gray-500\">\n <span\n class=\"inline-flex h-1.5 w-1.5 rounded-full\"\n :class=\"{\n 'bg-emerald-500': status === 'ok',\n 'bg-amber-400': status === 'warn',\n 'bg-red-500': status === 'error',\n 'bg-gray-300': status === 'idle'\n }\"\n ></span>\n <span x-text=\"statusText\"></span>\n </div>\n\n <div class=\"flex items-center gap-1 ml-auto\">\n <button\n type=\"button\"\n @click=\"refresh()\"\n class=\"inline-flex items-center gap-1 px-1.5 py-0.5 rounded-lg\n bg-gray-900 text-white text-[9px] hover:bg-gray-800\"\n >\n <i data-lucide=\"refresh-cw\" class=\"w-3 h-3\"></i>\n <span>Refresh</span>\n </button>\n\n <button\n type=\"button\"\n @click=\"toggleSettings()\"\n class=\"inline-flex items-center justify-center h-5 w-5 rounded-lg\n bg-gray-50 hover:bg-gray-100 text-gray-500\"\n title=\"Settings\"\n >\n <i data-lucide=\"settings\" class=\"w-3 h-3\"></i>\n </button>\n\n <button\n type=\"button\"\n @click=\"toggleLog()\"\n class=\"inline-flex items-center justify-center h-5 px-1.5 rounded-lg\n bg-gray-50 hover:bg-gray-100 text-[8px] text-gray-500\"\n :class=\"showLog ? 'bg-gray-900 text-white hover:bg-gray-800' : ''\"\n title=\"Debug log\"\n >\n log\n </button>\n </div>\n </div>\n\n <!-- Settings -->\n <div\n x-show=\"showSettings\"\n x-transition\n class=\"p-2 bg-gray-50 rounded-xl border border-gray-100 flex flex-col gap-1.5 text-[9px]\"\n style=\"display:none\"\n >\n <div class=\"flex items-center gap-2\">\n <label class=\"w-20 text-gray-600\">Username</label>\n <input\n x-model.trim=\"settings.username\"\n @change=\"persistSettings();refresh()\"\n type=\"text\"\n placeholder=\"auto from /user\"\n class=\"flex-1 px-2 py-1 rounded-lg border border-gray-200\n text-[9px] placeholder:text-gray-300 focus:outline-none\n focus:ring-1 focus:ring-gray-300\"\n />\n </div>\n <div class=\"flex items-center gap-2\">\n <label class=\"w-20 text-gray-600\">Per page</label>\n <input\n x-model.number=\"settings.perPage\"\n @change=\"persistSettings();refresh()\"\n type=\"number\" min=\"1\" max=\"50\"\n class=\"w-14 px-1.5 py-0.5 rounded-lg border border-gray-200\n text-[9px] focus:outline-none focus:ring-1 focus:ring-gray-300\"\n />\n <label class=\"ml-1 text-gray-600\">Since (days)</label>\n <input\n x-model.number=\"settings.sinceDays\"\n @change=\"persistSettings();refresh()\"\n type=\"number\" min=\"1\" max=\"365\"\n class=\"w-14 px-1.5 py-0.5 rounded-lg border border-gray-200\n text-[9px] focus:outline-none focus:ring-1 focus:ring-gray-300\"\n />\n </div>\n <div class=\"flex justify-between items-center pt-0.5\">\n <p class=\"text-[8px] text-gray-500 pr-1\">\n Query: <code class=\"bg-gray-200 px-1 rounded\">author:you</code> via <code>/search/commits</code>.\n </p>\n <button\n type=\"button\"\n @click=\"resetSettings()\"\n class=\"px-1.5 py-0.5 rounded-lg border border-gray-200\n text-[8px] text-gray-500 hover:bg-gray-100\"\n >\n Reset\n </button>\n </div>\n </div>\n\n <!-- Debug Log -->\n <div\n x-show=\"showLog\"\n x-transition\n class=\"mt-1 p-1.5 bg-slate-950 rounded-xl border border-slate-800\n text-[8px] font-mono text-slate-200 space-y-0.5 max-h-32 overflow-y-auto\"\n style=\"display:none\"\n >\n <template x-if=\"!logLines.length\">\n <div class=\"text-slate-500\">Log is empty. Refresh to see diagnostics.</div>\n </template>\n <template x-for=\"(line, i) in logLines\" :key=\"i\">\n <div class=\"flex gap-1\">\n <span class=\"text-slate-500 shrink-0\" x-text=\"line.t\"></span>\n <span class=\"truncate\" x-text=\"line.m\"></span>\n </div>\n </template>\n </div>\n\n <!-- Empty / Error -->\n <template x-if=\"!loading && commits.length === 0\">\n <div class=\"mt-1 p-2 rounded-xl border border-dashed border-gray-200 text-[9px] text-gray-500 flex flex-col gap-1\">\n <span x-show=\"!hasToken\">\n Add your GitHub token in Account → API to view commits.\n </span>\n <span x-show=\"hasToken && status === 'ok'\">\n No commits matched the current query window. Check log for query details.\n </span>\n <span x-show=\"status === 'error'\" class=\"text-red-500\" x-text=\"errorMessage\"></span>\n </div>\n </template>\n\n <!-- Commits -->\n <div\n x-show=\"commits.length\"\n class=\"mt-0.5 space-y-1 overflow-y-auto no-scrollbar pr-1\"\n style=\"max-height:40vh;display:none\"\n >\n <template x-for=\"c in commits\" :key=\"c.sha\">\n <div\n class=\"group flex flex-col gap-0.5 px-2 py-1.5 rounded-xl border border-gray-100\n bg-gray-50/60 hover:bg-gray-100/90 transition\"\n >\n <div class=\"flex items-center gap-1.5\">\n <div class=\"h-4 w-4 rounded-full bg-gray-900 text-white text-[8px] flex items-center justify-center\">\n <i data-lucide=\"git-commit\" class=\"w-3 h-3\"></i>\n </div>\n <div class=\"flex-1 min-w-0\">\n <p class=\"text-[9px] font-medium text-gray-800 truncate\" x-text=\"c.messageShort\"></p>\n <p class=\"text-[8px] text-gray-500 truncate\">\n <span class=\"text-gray-700\" x-text=\"c.repo\"></span>\n <span class=\"mx-0.5\">•</span>\n <span x-text=\"c.branch || 'commit'\"></span>\n </p>\n </div>\n <span\n class=\"ml-1 text-[8px] px-1.5 py-0.5 rounded-full bg-gray-900/90 text-white whitespace-nowrap\"\n x-text=\"c.ago\"\n ></span>\n </div>\n <div class=\"flex items-center gap-1.5 text-[7.5px] text-gray-500 mt-0.5\">\n <span class=\"truncate\" x-text=\"c.sha.slice(0,7)\"></span>\n <span class=\"mx-0.5 text-gray-300\">•</span>\n <span class=\"truncate\" x-text=\"c.author\"></span>\n <a\n class=\"ml-auto inline-flex items-center gap-0.5 text-[7.5px] text-gray-500\n hover:text-gray-900 hover:underline\"\n :href=\"c.url\"\n target=\"_blank\"\n rel=\"noopener\"\n >\n View\n <i data-lucide=\"external-link\" class=\"w-2.5 h-2.5\"></i>\n </a>\n </div>\n </div>\n </template>\n </div>\n </div>\n</section>\n\n<script>\n;(() => {\n const lsKey=()=>{try{return'sune_github_commits_v2_'+(window.SUNE?.id||'default')}catch(_){return'sune_github_commits_v2_default'}}\n const load=()=>{try{return{\n username:'',\n perPage:20,\n sinceDays:14,\n ...JSON.parse(localStorage.getItem(lsKey())||'{}')\n }}catch(_){return{username:'',perPage:20,sinceDays:14}}}\n const save=s=>{try{localStorage.setItem(lsKey(),JSON.stringify(s))}catch(_){}}\n const ago=iso=>{\n const t=new Date(iso).getTime();if(!t)return''\n const d=(Date.now()-t)/1e3\n if(d<60)return`${Math.max(1,Math.floor(d))}s`\n const m=d/60;if(m<60)return`${Math.floor(m)}m`\n const h=m/60;if(h<24)return`${Math.floor(h)}h`\n const dd=h/24;if(dd<7)return`${Math.floor(dd)}d`\n const w=dd/7;if(w<4)return`${Math.floor(w)}w`\n const mo=dd/30;if(mo<12)return`${Math.floor(mo)}mo`\n const y=dd/365;return`${Math.floor(y)}y`\n }\n\n // Worker for /search/commits to offload JSON parsing\n const searchCommitsWorker=p=>new Promise(res=>{\n try{\n const blob=new Blob([`\n self.onmessage=async e=>{\n const{url,headers}=e.data;\n try{\n const r=await fetch(url,{headers});\n const rate=r.headers.get('x-ratelimit-remaining');\n if(!r.ok){\n const txt=await r.text().catch(()=> '');\n self.postMessage({error:'HTTP '+r.status,body:txt.slice(0,400),rate});\n return;\n }\n const data=await r.json();\n self.postMessage({items:data.items||[],total:data.total_count||0,rate});\n }catch(err){\n self.postMessage({error:String(err&&err.message||err||'Error')});\n }\n };\n `],{type:'application/javascript'});\n const w=new Worker(URL.createObjectURL(blob));\n w.onmessage=e=>{w.terminate();res(e.data||{})}\n w.postMessage(p)\n }catch(err){res({error:String(err&&err.message||err||'Worker error')})}\n })\n\n window.githubCommitsSune=()=>({\n version:'2.0.0',\n settings:load(),\n commits:[],\n loading:false,\n status:'idle',\n statusText:'Waiting…',\n errorMessage:'',\n collapsed:false,\n showSettings:false,\n showLog:false,\n logLines:[],\n hasToken:false,\n\n log(m){\n const t=new Date().toISOString().split('T')[1].replace('Z','')\n this.logLines.push({t,m:String(m)})\n if(this.logLines.length>160)this.logLines.shift()\n },\n toggleLog(){this.showLog=!this.showLog},\n\n persistSettings(){\n const s=this.settings\n if(!s.perPage||s.perPage<1)s.perPage=20\n if(s.perPage>50)s.perPage=50\n if(!s.sinceDays||s.sinceDays<1)s.sinceDays=14\n if(s.sinceDays>365)s.sinceDays=365\n save(s)\n },\n resetSettings(){\n this.settings={username:'',perPage:20,sinceDays:14}\n this.persistSettings()\n this.refresh()\n },\n toggleSettings(){this.showSettings=!this.showSettings},\n\n init(){\n this.hasToken=!!(window.USER&&window.USER.githubToken)\n this.log('init; hasToken='+(this.hasToken?'yes':'no'))\n if(!this.hasToken){\n this.status='warn'\n this.statusText='Add your GitHub token in Account → API.'\n return\n }\n this.refresh()\n },\n\n async resolveUsername(token){\n let u=(this.settings.username||'').trim()\n if(u){\n this.log('using configured username='+u)\n return u\n }\n try{\n this.log('GET /user to resolve login')\n const r=await fetch('https://api.github.com/user',{\n headers:{Authorization:'token '+token,Accept:'application/vnd.github+json'}\n })\n this.log('/user -> '+r.status)\n if(r.ok){\n const me=await r.json()\n u=me.login||''\n if(u){\n this.settings.username=u\n this.persistSettings()\n this.log('resolved username='+u)\n }\n }else{\n this.log('failed to resolve /user; status='+r.status)\n }\n }catch(e){\n this.log('error resolving /user: '+(e&&e.message||e))\n }\n return u\n },\n\n async refresh(){\n this.errorMessage=''\n this.loading=true\n this.status='idle'\n this.statusText='Loading commits…'\n this.commits=[]\n this.log('refresh')\n\n const token=window.USER&&window.USER.githubToken\n if(!token){\n this.loading=false\n this.hasToken=false\n this.status='warn'\n this.statusText='Missing GitHub token. Set it in Account → API.'\n this.log('no token')\n return\n }\n this.hasToken=true\n\n const username=await this.resolveUsername(token)\n if(!username){\n this.loading=false\n this.status='error'\n this.statusText='Cannot resolve your GitHub username.'\n this.errorMessage='Set \"Username\" in settings or ensure your token is valid.'\n this.log('no username; aborting')\n return\n }\n\n // Build search query: author:<user> pushed within sinceDays\n const d=new Date()\n d.setDate(d.getDate()-(this.settings.sinceDays||14))\n const sinceISO=d.toISOString().slice(0,10) // YYYY-MM-DD\n const perPage=this.settings.perPage||20\n\n const q=`author:${encodeURIComponent(username)}+author-date:>=${sinceISO}`\n const url=`https://api.github.com/search/commits?q=${q}&sort=author-date&order=desc&per_page=${perPage}`\n\n this.log('search url='+url)\n\n const {items,total,rate,error,body}=await searchCommitsWorker({\n url,\n headers:{\n Authorization:'token '+token,\n Accept:'application/vnd.github.cloak-preview+json'\n }\n })\n\n if(rate!=null)this.log('rate remaining='+rate)\n\n if(error){\n this.loading=false\n this.status='error'\n this.statusText='Failed to load commits.'\n this.errorMessage=error\n if(body)this.log('body='+body)\n this.log('error='+error)\n return\n }\n\n this.log('total_count='+(total||0)+'; items='+(items?items.length:0))\n\n const list=[]\n try{\n for(const it of items||[]){\n const c=it.commit||{}\n const msg=(c.message||'').split('\\n')[0]\n const sha=it.sha\n const repoFull=it.repository?.full_name||'unknown'\n const authorName=c.author?.name||it.author?.login||'unknown'\n const date=c.author?.date||c.committer?.date||it.committer?.date\n list.push({\n sha,\n messageShort:msg||'(no message)',\n author:authorName,\n repo:repoFull,\n branch:'', // branch not available from search API; keep minimal\n url:it.html_url||(`https://github.com/${repoFull}/commit/${sha}`),\n ago:date?ago(date):'',\n ts:date?new Date(date).getTime():0\n })\n }\n }catch(e){\n this.loading=false\n this.status='error'\n this.statusText='Parse error.'\n this.errorMessage=String(e&&e.message||e)\n this.log('parse error='+this.errorMessage)\n return\n }\n\n this.log('raw entries='+list.length)\n\n const seen=new Set\n const normalized=(list||[])\n .filter(c=>c.sha)\n .sort((a,b)=>(b.ts||0)-(a.ts||0))\n .filter(c=>{\n const k=c.sha\n if(seen.has(k))return false\n seen.add(k)\n return true\n })\n\n this.commits=normalized\n this.loading=false\n\n if(!normalized.length){\n this.status='ok'\n this.statusText='No commits matched the query.'\n this.log('no commits after normalization')\n }else{\n this.status='ok'\n this.statusText=`Loaded ${normalized.length} commit${normalized.length>1?'s':''}.`\n this.log('showing '+normalized.length+' commits')\n }\n\n try{window.lucide&&window.lucide.createIcons()}catch(_){}\n }\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,"include_thoughts":false,"json_output":false,"ignore_master_prompt":false,"json_schema":""},"storage":{}}] |