Download shell.html from kacapower/opencode: direct link, hf CLI and curl.
- Browser
- Download file 15.4 kB
-
https://huggingface.co/spaces/kacapower/opencode/resolve/main/shell.html
- Command line
-
hf download hf://spaces/kacapower/opencode/shell.html
-
curl -L -o shell.html https://huggingface.co/spaces/kacapower/opencode/resolve/main/shell.html
15.4 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>opencode</title> | |
| <style> | |
| :root { | |
| --bg: #0b0d10; | |
| --bg-alt: #11151a; | |
| --bg-hover: #1a2028; | |
| --border: #232b35; | |
| --text: #d7dde5; | |
| --muted: #7d8794; | |
| --accent: #63b3ed; | |
| --accent-dim: #2b6cb0; | |
| --danger: #f56565; | |
| --radius: 6px; | |
| --sidebar: 300px; | |
| } | |
| * { box-sizing: border-box; } | |
| html, body { height: 100%; margin: 0; } | |
| body { | |
| background: var(--bg); | |
| color: var(--text); | |
| font: 13px/1.45 ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; | |
| overflow: hidden; | |
| } | |
| #app { display: flex; height: 100%; } | |
| /* ---------- sidebar ---------- */ | |
| #sidebar { | |
| width: var(--sidebar); | |
| min-width: 200px; | |
| max-width: 70vw; | |
| display: flex; | |
| flex-direction: column; | |
| background: var(--bg-alt); | |
| border-right: 1px solid var(--border); | |
| } | |
| #sidebar.collapsed { display: none; } | |
| .bar { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 8px; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .bar .title { | |
| font-weight: 600; | |
| letter-spacing: .04em; | |
| text-transform: uppercase; | |
| font-size: 10px; | |
| color: var(--muted); | |
| flex: 1; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| button, select, input { | |
| font: inherit; | |
| color: var(--text); | |
| background: var(--bg); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| padding: 4px 8px; | |
| outline: none; | |
| } | |
| button { cursor: pointer; } | |
| button:hover { background: var(--bg-hover); border-color: var(--accent-dim); } | |
| button.icon { padding: 4px 7px; line-height: 1; } | |
| select { padding: 4px 6px; } | |
| input:focus, select:focus { border-color: var(--accent-dim); } | |
| .search { padding: 8px; border-bottom: 1px solid var(--border); } | |
| .search input { width: 100%; } | |
| #tree { flex: 1; overflow: auto; padding: 4px 0 24px; } | |
| .node { user-select: none; } | |
| .row { | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| padding: 3px 8px 3px 0; | |
| white-space: nowrap; | |
| cursor: pointer; | |
| border-left: 2px solid transparent; | |
| } | |
| .row:hover { background: var(--bg-hover); } | |
| .row.active { background: var(--bg-hover); border-left-color: var(--accent); } | |
| .row .caret { | |
| width: 12px; | |
| color: var(--muted); | |
| flex: none; | |
| text-align: center; | |
| transition: transform .12s; | |
| } | |
| .row .caret.open { transform: rotate(90deg); } | |
| .row .ico { flex: none; width: 14px; text-align: center; color: var(--muted); } | |
| .row .ico.dir { color: var(--accent); } | |
| .row .name { overflow: hidden; text-overflow: ellipsis; } | |
| .row .name.dir { color: var(--text); font-weight: 600; } | |
| .row .size { margin-left: auto; color: var(--muted); font-size: 10px; padding-left: 8px; } | |
| .children { display: none; } | |
| .children.open { display: block; } | |
| .empty, .hint { padding: 10px; color: var(--muted); font-size: 11px; } | |
| .status { | |
| border-top: 1px solid var(--border); | |
| padding: 6px 10px; | |
| font-size: 10px; | |
| color: var(--muted); | |
| display: flex; | |
| justify-content: space-between; | |
| } | |
| /* ---------- resizer ---------- */ | |
| #resizer { | |
| width: 4px; | |
| cursor: col-resize; | |
| background: transparent; | |
| flex: none; | |
| } | |
| #resizer:hover { background: var(--accent-dim); } | |
| /* ---------- main ---------- */ | |
| #main { flex: 1; position: relative; min-width: 0; } | |
| #frame { width: 100%; height: 100%; border: 0; background: var(--bg); display: block; } | |
| #float { | |
| position: absolute; top: 8px; right: 12px; z-index: 5; | |
| display: flex; gap: 6px; opacity: .25; transition: opacity .15s; | |
| } | |
| #float:hover { opacity: 1; } | |
| #float a, #float button { | |
| background: rgba(17,21,26,.9); | |
| border: 1px solid var(--border); | |
| color: var(--muted); | |
| text-decoration: none; | |
| padding: 3px 8px; | |
| border-radius: var(--radius); | |
| font-size: 11px; | |
| } | |
| /* ---------- modal ---------- */ | |
| #modal { | |
| position: fixed; inset: 0; z-index: 50; | |
| background: rgba(0,0,0,.6); | |
| display: none; | |
| padding: 4vh 4vw; | |
| } | |
| #modal.open { display: flex; } | |
| #modal .panel { | |
| margin: auto; | |
| width: min(1100px, 100%); | |
| height: 100%; | |
| display: flex; | |
| flex-direction: column; | |
| background: var(--bg-alt); | |
| border: 1px solid var(--border); | |
| border-radius: 10px; | |
| overflow: hidden; | |
| box-shadow: 0 20px 60px rgba(0,0,0,.6); | |
| } | |
| #modal .head { | |
| display: flex; align-items: center; gap: 8px; | |
| padding: 8px 10px; border-bottom: 1px solid var(--border); | |
| } | |
| #modal .head .fname { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | |
| #modal .head .meta { color: var(--muted); font-size: 10px; } | |
| #modal pre { | |
| flex: 1; margin: 0; padding: 12px 14px; | |
| overflow: auto; | |
| font-size: 12px; line-height: 1.5; | |
| tab-size: 2; | |
| white-space: pre; | |
| } | |
| #modal .foot { color: var(--muted); font-size: 10px; padding: 6px 12px; border-top: 1px solid var(--border); } | |
| a.btn { | |
| text-decoration: none; color: var(--text); | |
| border: 1px solid var(--border); border-radius: var(--radius); | |
| padding: 3px 8px; font-size: 11px; background: var(--bg); | |
| } | |
| a.btn:hover { background: var(--bg-hover); border-color: var(--accent-dim); } | |
| @media (max-width: 720px) { | |
| #sidebar { width: 80vw; position: absolute; z-index: 20; height: 100%; } | |
| #resizer { display: none; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="app"> | |
| <aside id="sidebar"> | |
| <div class="bar"> | |
| <span class="title" id="rootLabel">workspace</span> | |
| <select id="rootSelect" title="Storage root"></select> | |
| <button class="icon" id="refreshBtn" title="Refresh">⟳</button> | |
| <button class="icon" id="collapseBtn" title="Hide sidebar">«</button> | |
| </div> | |
| <div class="search"><input id="searchInput" placeholder="filter / search files…" /></div> | |
| <div id="tree"><div class="hint">loading…</div></div> | |
| <div class="status"><span id="statLeft">—</span><span id="statRight"></span></div> | |
| </aside> | |
| <div id="resizer"></div> | |
| <main id="main"> | |
| <iframe id="frame" src="/?iframe=1" title="opencode"></iframe> | |
| <div id="float"> | |
| <button id="showSidebar" title="Show file explorer" style="display:none">»</button> | |
| <a id="pop" href="/?iframe=1" target="_blank" title="Open chat in new tab">↗</a> | |
| </div> | |
| </main> | |
| </div> | |
| <div id="modal"> | |
| <div class="panel"> | |
| <div class="head"> | |
| <span class="fname" id="mName"></span> | |
| <span class="meta" id="mMeta"></span> | |
| <a class="btn" id="mRaw" target="_blank">raw</a> | |
| <a class="btn" id="mZip" target="_blank">zip</a> | |
| <button class="icon" id="mClose">×</button> | |
| </div> | |
| <pre id="mBody"></pre> | |
| <div class="foot" id="mFoot"></div> | |
| </div> | |
| </div> | |
| <script> | |
| (() => { | |
| const $ = (id) => document.getElementById(id); | |
| const treeEl = $('tree'); | |
| const state = { root: 'workspace', open: new Set(), active: null, search: '' }; | |
| const fmtSize = (n) => { | |
| if (!n) return ''; | |
| const u = ['B', 'K', 'M', 'G', 'T']; | |
| let i = 0; | |
| while (n >= 1024 && i < u.length - 1) { n /= 1024; i++; } | |
| return (i === 0 ? n : n.toFixed(1)) + u[i]; | |
| }; | |
| const enc = encodeURIComponent; | |
| async function api(url) { | |
| const r = await fetch(url, { headers: { accept: 'application/json' } }); | |
| if (!r.ok) throw new Error((await r.json().catch(() => ({}))).detail || r.statusText); | |
| return r.json(); | |
| } | |
| function rowFor(entry, depth) { | |
| const node = document.createElement('div'); | |
| node.className = 'node'; | |
| const row = document.createElement('div'); | |
| row.className = 'row' + (state.active === state.root + ':' + entry.path ? ' active' : ''); | |
| row.style.paddingLeft = (depth * 14 + 6) + 'px'; | |
| row.dataset.path = entry.path; | |
| row.dataset.type = entry.type; | |
| const caret = document.createElement('span'); | |
| caret.className = 'caret'; | |
| caret.textContent = entry.type === 'dir' ? '\u25B8' : ''; | |
| const ico = document.createElement('span'); | |
| ico.className = 'ico' + (entry.type === 'dir' ? ' dir' : ''); | |
| ico.textContent = entry.type === 'dir' ? '\u25A0' : '\u2022'; | |
| const name = document.createElement('span'); | |
| name.className = 'name' + (entry.type === 'dir' ? ' dir' : ''); | |
| name.textContent = entry.name; | |
| const size = document.createElement('span'); | |
| size.className = 'size'; | |
| size.textContent = entry.type === 'dir' ? '' : fmtSize(entry.size); | |
| row.append(caret, ico, name, size); | |
| node.append(row); | |
| let childBox = null; | |
| const key = state.root + ':' + entry.path; | |
| async function toggle() { | |
| if (entry.type !== 'dir') { preview(entry); return; } | |
| if (!childBox) { | |
| childBox = document.createElement('div'); | |
| childBox.className = 'children'; | |
| childBox.style.paddingLeft = '0px'; | |
| node.append(childBox); | |
| try { | |
| const data = await api(`/__api/tree?root=${enc(state.root)}&path=${enc(entry.path)}`); | |
| if (!data.entries.length) { | |
| childBox.innerHTML = '<div class="empty">empty</div>'; | |
| } else { | |
| for (const child of data.entries) childBox.append(rowFor(child, depth + 1)); | |
| } | |
| } catch (e) { | |
| childBox.innerHTML = `<div class="empty">${e.message}</div>`; | |
| } | |
| } | |
| const isOpen = childBox.classList.toggle('open'); | |
| caret.classList.toggle('open', isOpen); | |
| if (isOpen) state.open.add(key); else state.open.delete(key); | |
| } | |
| row.addEventListener('click', () => { setActive(key); toggle(); }); | |
| return node; | |
| } | |
| function setActive(key) { | |
| state.active = key; | |
| document.querySelectorAll('.row.active').forEach((el) => el.classList.remove('active')); | |
| const [root, ...rest] = key.split(':'); | |
| const path = rest.join(':'); | |
| const sel = `.row[data-path="${CSS.escape(path)}"]`; | |
| document.querySelectorAll(sel).forEach((el) => el.classList.add('active')); | |
| } | |
| async function loadRoot() { | |
| treeEl.innerHTML = '<div class="hint">loading…</div>'; | |
| state.open.clear(); | |
| try { | |
| const data = await api(`/__api/tree?root=${enc(state.root)}&path=`); | |
| treeEl.innerHTML = ''; | |
| if (!data.entries.length) treeEl.innerHTML = '<div class="empty">no files yet — ask the agent to create some</div>'; | |
| for (const e of data.entries) treeEl.append(rowFor(e, 0)); | |
| } catch (e) { | |
| treeEl.innerHTML = `<div class="empty">${e.message}</div>`; | |
| } | |
| loadStats(); | |
| } | |
| async function loadStats() { | |
| try { | |
| const s = await api(`/__api/stats?root=${enc(state.root)}`); | |
| $('statLeft').textContent = `${s.files} files`; | |
| $('statRight').textContent = fmtSize(s.bytes); | |
| $('rootLabel').textContent = state.root; | |
| } catch (_) { /* ignore */ } | |
| } | |
| let searchTimer = null; | |
| $('searchInput').addEventListener('input', (ev) => { | |
| const q = ev.target.value.trim(); | |
| clearTimeout(searchTimer); | |
| if (!q) { loadRoot(); return; } | |
| searchTimer = setTimeout(async () => { | |
| treeEl.innerHTML = '<div class="hint">searching…</div>'; | |
| try { | |
| const data = await api(`/__api/search?root=${enc(state.root)}&q=${enc(q)}`); | |
| treeEl.innerHTML = ''; | |
| if (!data.results.length) treeEl.innerHTML = '<div class="empty">no matches</div>'; | |
| for (const e of data.results) { | |
| const row = rowFor(e, 0); | |
| treeEl.append(row); | |
| } | |
| if (data.truncated) treeEl.insertAdjacentHTML('beforeend', '<div class="hint">results truncated…</div>'); | |
| } catch (e) { | |
| treeEl.innerHTML = `<div class="empty">${e.message}</div>`; | |
| } | |
| }, 250); | |
| }); | |
| async function preview(entry) { | |
| $('modal').classList.add('open'); | |
| $('mName').textContent = entry.path || entry.name; | |
| $('mMeta').textContent = fmtSize(entry.size); | |
| $('mBody').textContent = 'loading…'; | |
| $('mFoot').textContent = ''; | |
| const qs = `root=${enc(state.root)}&path=${enc(entry.path)}`; | |
| $('mRaw').href = `/__api/raw?${qs}`; | |
| $('mZip').href = `/__api/zip?${qs}`; | |
| try { | |
| const data = await api(`/__api/file?${qs}`); | |
| if (data.binary) { | |
| $('mBody').textContent = '(binary file — use raw/zip to download)'; | |
| } else { | |
| $('mBody').textContent = data.content ?? ''; | |
| } | |
| $('mMeta').textContent = fmtSize(data.size); | |
| $('mFoot').textContent = data.truncated ? `truncated at ${fmtSize(512 * 1024)} — use raw for full file` : ''; | |
| } catch (e) { | |
| $('mBody').textContent = e.message; | |
| } | |
| } | |
| $('mClose').addEventListener('click', () => $('modal').classList.remove('open')); | |
| $('modal').addEventListener('click', (e) => { if (e.target.id === 'modal') $('modal').classList.remove('open'); }); | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key === 'Escape') $('modal').classList.remove('open'); | |
| if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'b') { | |
| e.preventDefault(); | |
| toggleSidebar(); | |
| } | |
| }); | |
| $('refreshBtn').addEventListener('click', loadRoot); | |
| $('rootSelect').addEventListener('change', (e) => { state.root = e.target.value; state.active = null; $('searchInput').value = ''; loadRoot(); }); | |
| function toggleSidebar(force) { | |
| const sb = $('sidebar'); | |
| const hidden = force !== undefined ? !force : !sb.classList.contains('collapsed'); | |
| sb.classList.toggle('collapsed', hidden); | |
| $('resizer').style.display = hidden ? 'none' : ''; | |
| $('showSidebar').style.display = hidden ? '' : 'none'; | |
| } | |
| $('collapseBtn').addEventListener('click', () => toggleSidebar(false)); | |
| $('showSidebar').addEventListener('click', () => toggleSidebar(true)); | |
| // drag to resize | |
| (() => { | |
| const resizer = $('resizer'); | |
| let dragging = false; | |
| resizer.addEventListener('mousedown', () => { dragging = true; document.body.style.cursor = 'col-resize'; }); | |
| window.addEventListener('mousemove', (e) => { | |
| if (!dragging) return; | |
| const w = Math.max(200, Math.min(e.clientX, window.innerWidth * 0.7)); | |
| document.documentElement.style.setProperty('--sidebar', w + 'px'); | |
| }); | |
| window.addEventListener('mouseup', () => { dragging = false; document.body.style.cursor = ''; }); | |
| })(); | |
| api('/__api/roots').then((d) => { | |
| const sel = $('rootSelect'); | |
| sel.innerHTML = d.roots.map((r) => `<option value="${r.id}">${r.id}</option>`).join(''); | |
| }).catch(() => {}); | |
| setInterval(() => { if (!state.search) loadStats(); }, 15000); | |
| setInterval(() => { | |
| const openDirs = []; | |
| document.querySelectorAll('.children.open').forEach((el) => openDirs.push(el)); | |
| if (openDirs.length) refreshOpen(); | |
| }, 10000); | |
| async function refreshOpen() { | |
| const nodes = document.querySelectorAll('.children.open'); | |
| for (const box of nodes) { | |
| const row = box.previousElementSibling; | |
| if (!row) continue; | |
| const path = row.dataset.path; | |
| if (path === undefined) continue; | |
| try { | |
| const data = await api(`/__api/tree?root=${enc(state.root)}&path=${enc(path)}`); | |
| const depth = parseInt(row.style.paddingLeft || '0', 10) / 14; | |
| box.innerHTML = ''; | |
| if (!data.entries.length) box.innerHTML = '<div class="empty">empty</div>'; | |
| for (const child of data.entries) box.append(rowFor(child, Math.round(depth))); | |
| } catch (_) { /* ignore */ } | |
| } | |
| } | |
| loadRoot(); | |
| })(); | |
| </script> | |
| </body> | |
| </html> | |