Spaces:
Running
Running
Download index.html from willykeenan/contextlib: direct link, hf CLI and curl.
- Browser
- Download file 11.7 kB
-
https://huggingface.co/spaces/willykeenan/contextlib/resolve/main/index.html
- Command line
-
hf download hf://spaces/willykeenan/contextlib/index.html
-
curl -L -o index.html https://huggingface.co/spaces/willykeenan/contextlib/resolve/main/index.html
11.7 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>ContextLib</title> | |
| <style> | |
| :root { | |
| --paper: #f4efe6; | |
| --ink: #1c1917; | |
| --muted: #57534e; | |
| --line: #d6d3d1; | |
| --card: #fffcf7; | |
| --accent: #0f4c5c; | |
| --current: #166534; | |
| --proposed: #a16207; | |
| --superseded: #57534e; | |
| --retired: #9f1239; | |
| } | |
| * { box-sizing: border-box; } | |
| html { -webkit-text-size-adjust: 100%; } | |
| body { | |
| margin: 0; | |
| background: var(--paper); | |
| color: var(--ink); | |
| font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; | |
| } | |
| header, main, footer { | |
| max-width: 42rem; | |
| margin: 0 auto; | |
| padding: 1rem 1rem 0; | |
| } | |
| footer { padding-bottom: 2.5rem; color: var(--muted); font-size: 0.9rem; } | |
| h1 { font-size: 1.6rem; margin: 0 0 0.25rem; letter-spacing: -0.02em; } | |
| h2 { font-size: 1.15rem; margin: 1.5rem 0 0.5rem; } | |
| h3 { font-size: 1.02rem; margin: 0 0 0.35rem; } | |
| .lede { color: var(--muted); margin: 0 0 1rem; } | |
| .search-label { display: block; font-weight: 600; font-size: 0.85rem; margin-bottom: 0.35rem; } | |
| #q { | |
| width: 100%; | |
| font-size: 16px; | |
| padding: 0.7rem 0.8rem; | |
| border: 1px solid var(--line); | |
| border-radius: 0.5rem; | |
| background: var(--card); | |
| color: var(--ink); | |
| } | |
| .meta-line { color: var(--muted); font-size: 0.88rem; margin: 0 0 0.75rem; } | |
| .brief, .record { | |
| background: var(--card); | |
| border: 1px solid var(--line); | |
| border-radius: 0.7rem; | |
| padding: 0.9rem 1rem; | |
| margin: 0 0 0.75rem; | |
| } | |
| .record:target { outline: 2px solid var(--accent); } | |
| .row { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 0.35rem; | |
| align-items: center; | |
| margin-bottom: 0.35rem; | |
| } | |
| .badge { | |
| display: inline-block; | |
| font-size: 0.72rem; | |
| font-weight: 700; | |
| letter-spacing: 0.04em; | |
| text-transform: uppercase; | |
| padding: 0.15rem 0.45rem; | |
| border-radius: 999px; | |
| color: #fff; | |
| background: var(--muted); | |
| } | |
| .badge.type { background: var(--accent); } | |
| .badge.current { background: var(--current); } | |
| .badge.proposed { background: var(--proposed); } | |
| .badge.superseded { background: var(--superseded); } | |
| .badge.retired { background: var(--retired); } | |
| .body { white-space: pre-wrap; overflow-wrap: anywhere; } | |
| .body p { margin: 0 0 0.6rem; } | |
| .body p:last-child { margin-bottom: 0; } | |
| .body ul { margin: 0 0 0.6rem; padding-left: 1.2rem; } | |
| .chain { font-size: 0.88rem; margin-top: 0.7rem; color: var(--muted); } | |
| .use { | |
| background: var(--card); | |
| border: 1px solid var(--line); | |
| border-radius: 0.7rem; | |
| padding: 0.8rem 1rem; | |
| margin: 0 0 1rem; | |
| font-size: 0.92rem; | |
| } | |
| .use h2 { margin: 0 0 0.4rem; font-size: 1rem; } | |
| .use p { margin: 0.5rem 0 0.3rem; color: var(--muted); } | |
| .use pre { | |
| margin: 0; | |
| padding: 0.55rem 0.7rem; | |
| overflow-x: auto; | |
| background: var(--paper); | |
| border: 1px solid var(--line); | |
| border-radius: 0.5rem; | |
| font: 0.8rem/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; | |
| } | |
| .use a { color: var(--accent); } | |
| .chain a { color: var(--accent); } | |
| .empty { color: var(--muted); padding: 1rem 0; } | |
| .error { color: var(--retired); padding: 1rem 0; } | |
| @media (max-width: 640px) { | |
| header, main, footer { padding-left: 0.85rem; padding-right: 0.85rem; } | |
| h1 { font-size: 1.4rem; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>ContextLib</h1> | |
| <p class="lede" id="lede">Read-only sample library. Markdown files stay the source of truth.</p> | |
| <section class="use" aria-labelledby="use-title"> | |
| <h2 id="use-title">Use it</h2> | |
| <p>Install the <code>ctxlib</code> CLI and MCP server (Python 3.9+, standard library only):</p> | |
| <pre><code>pip install "git+https://github.com/willykeenan/agentbrain-contextlib"</code></pre> | |
| <p>Or add the published Docker image to Claude Code, with your library mounted at <code>/library</code>:</p> | |
| <pre><code>claude mcp add contextlib -- docker run -i --rm --user "$(id -u):$(id -g)" -v ~/ContextLib:/library ghcr.io/willykeenan/agentbrain-contextlib mcp --identity you</code></pre> | |
| <p>Codex and Cursor setup, the CLI and the <code>context_*</code> tools: <a href="https://github.com/willykeenan/agentbrain-contextlib" rel="noopener">github.com/willykeenan/agentbrain-contextlib</a>. Listed on the official MCP Registry as <code>io.github.willykeenan/agentbrain-contextlib</code>.</p> | |
| </section> | |
| <label class="search-label" for="q">Search records</label> | |
| <input aria-label="Search library records" id="q" type="search" placeholder="Title, body, id, tag, status" autocomplete="off"> | |
| </header> | |
| <main> | |
| <p id="status" class="meta-line">Loading library.json…</p> | |
| <section id="brief"></section> | |
| <section id="records"></section> | |
| </main> | |
| <footer> | |
| ContextLib is open source under Apache-2.0. This page is read-only. | |
| Source: <a href="https://github.com/willykeenan/agentbrain-contextlib" rel="noopener">github.com/willykeenan/agentbrain-contextlib</a>. | |
| </footer> | |
| <script> | |
| (function () { | |
| var TYPES = [ | |
| "decision", | |
| "requirement", | |
| "fact", | |
| "lesson", | |
| "glossary", | |
| "source", | |
| "return", | |
| "brief-note" | |
| ]; | |
| var TYPE_LABEL = { | |
| decision: "Decisions", | |
| requirement: "Requirements", | |
| fact: "Facts", | |
| lesson: "Lessons", | |
| glossary: "Glossary", | |
| source: "Sources", | |
| "return": "Returns", | |
| "brief-note": "Brief notes" | |
| }; | |
| function esc(value) { | |
| return String(value == null ? "" : value) | |
| .replace(/&/g, "&") | |
| .replace(/</g, "<") | |
| .replace(/>/g, ">") | |
| .replace(/"/g, """); | |
| } | |
| function md(text) { | |
| var raw = String(text || "").replace(/\r\n/g, "\n"); | |
| var blocks = raw.split(/\n{2,}/); | |
| return blocks.map(function (block) { | |
| var lines = block.split("\n"); | |
| var list = lines.length && lines.every(function (line) { | |
| return /^\s*[-*]\s+/.test(line) || line === ""; | |
| }); | |
| if (list) { | |
| var items = lines.filter(Boolean).map(function (line) { | |
| return "<li>" + inline(line.replace(/^\s*[-*]\s+/, "")) + "</li>"; | |
| }).join(""); | |
| return "<ul>" + items + "</ul>"; | |
| } | |
| var first = lines[0] || ""; | |
| var heading = first.match(/^(#{1,3})\s+(.*)$/); | |
| if (heading && lines.length === 1) { | |
| var tag = "h" + heading[1].length; | |
| return "<" + tag + ">" + inline(heading[2]) + "</" + tag + ">"; | |
| } | |
| return "<p>" + inline(lines.join("\n")) + "</p>"; | |
| }).join(""); | |
| } | |
| function inline(text) { | |
| var out = esc(text); | |
| out = out.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>"); | |
| out = out.replace(/`([^`]+)`/g, "<code>$1</code>"); | |
| return out; | |
| } | |
| function recId(id) { | |
| return "rec-" + String(id || "").replace(/[^A-Za-z0-9_-]/g, "-"); | |
| } | |
| function chainLinks(ids) { | |
| return (ids || []).map(function (id) { | |
| return '<a href="#' + recId(id) + '">' + esc(id) + "</a>"; | |
| }).join(" → "); | |
| } | |
| function matches(rec, query) { | |
| if (!query) return true; | |
| var meta = rec.meta || {}; | |
| var hay = [ | |
| meta.id, meta.type, meta.title, meta.status, meta.author, | |
| (meta.tags || []).join(" "), rec.body, rec.path | |
| ].join(" ").toLowerCase(); | |
| return hay.indexOf(query) !== -1; | |
| } | |
| function render(data, query) { | |
| var projects = data.projects || []; | |
| var project = projects[0] || { meta: {}, brief: "", records: [] }; | |
| var meta = project.meta || {}; | |
| var records = project.records || []; | |
| var q = (query || "").trim().toLowerCase(); | |
| var shown = records.filter(function (rec) { return matches(rec, q); }); | |
| document.getElementById("lede").textContent = | |
| (meta.name || meta.slug || "Library") + | |
| (meta.purpose ? " — " + meta.purpose : ""); | |
| document.getElementById("status").textContent = | |
| shown.length + " of " + records.length + " records" + | |
| (meta.slug ? " in " + meta.slug : "") + | |
| (q ? " matching “" + query.trim() + "”" : "") + "."; | |
| var briefEl = document.getElementById("brief"); | |
| briefEl.innerHTML = "<h2>Project brief</h2><article class=\"brief\">" + | |
| md(project.brief || "") + "</article>"; | |
| var groups = {}; | |
| TYPES.forEach(function (t) { groups[t] = []; }); | |
| shown.forEach(function (rec) { | |
| var type = (rec.meta && rec.meta.type) || "fact"; | |
| if (!groups[type]) groups[type] = []; | |
| groups[type].push(rec); | |
| }); | |
| var html = []; | |
| TYPES.forEach(function (type) { | |
| var list = groups[type] || []; | |
| if (!list.length) return; | |
| html.push("<h2>" + esc(TYPE_LABEL[type] || type) + "</h2>"); | |
| list.forEach(function (rec) { | |
| var m = rec.meta || {}; | |
| var status = m.status || "current"; | |
| var supersedes = m.supersedes || []; | |
| var supersededBy = m.superseded_by; | |
| var chain = rec.supersession_chain || []; | |
| var bits = []; | |
| bits.push('<article class="record" id="' + recId(m.id) + '">'); | |
| bits.push('<div class="row">'); | |
| bits.push('<span class="badge type">' + esc(m.type || type) + "</span>"); | |
| bits.push('<span class="badge ' + esc(status) + '">' + esc(status) + "</span>"); | |
| bits.push("</div>"); | |
| bits.push("<h3>" + esc(m.title || m.id || "") + "</h3>"); | |
| bits.push('<p class="meta-line">' + esc(m.id || "") + | |
| (m.created ? " · " + esc(m.created) : "") + | |
| (m.author ? " · " + esc(m.author) : "") + "</p>"); | |
| bits.push('<div class="body">' + md(rec.body || "") + "</div>"); | |
| var links = []; | |
| if (supersedes.length) { | |
| links.push("Supersedes " + chainLinks(supersedes)); | |
| } | |
| if (supersededBy) { | |
| links.push("Superseded by " + chainLinks([supersededBy])); | |
| } | |
| if (chain.length > 1) { | |
| links.push("Chain " + chainLinks(chain)); | |
| } | |
| if (links.length) { | |
| bits.push('<div class="chain">' + links.join("<br>") + "</div>"); | |
| } | |
| bits.push("</article>"); | |
| html.push(bits.join("")); | |
| }); | |
| }); | |
| if (!shown.length) { | |
| html.push('<p class="empty">No records match that search.</p>'); | |
| } | |
| document.getElementById("records").innerHTML = html.join(""); | |
| } | |
| var cache = null; | |
| function apply() { | |
| if (!cache) return; | |
| render(cache, document.getElementById("q").value); | |
| } | |
| document.getElementById("q").addEventListener("input", apply); | |
| fetch("library.json").then(function (res) { | |
| if (!res.ok) throw new Error("library.json missing"); | |
| return res.json(); | |
| }).then(function (data) { | |
| cache = data; | |
| apply(); | |
| }).catch(function () { | |
| document.getElementById("status").innerHTML = | |
| '<span class="error">Could not load library.json. Serve this folder as a static site.</span>'; | |
| }); | |
| })(); | |
| </script> | |
| </body> | |
| </html> | |