Merge origin/main into notebooklm-agent-harness-scaffold

This commit is contained in:
liuche
2026-03-18 08:53:00 +08:00
4 changed files with 516 additions and 2 deletions

View File

@@ -25,6 +25,7 @@ Fixes #<!-- issue number -->
- [ ] Unit tests at `cli_anything/<software>/tests/test_core.py` are present and pass without backend
- [ ] E2E tests at `cli_anything/<software>/tests/test_full_e2e.py` are present
- [ ] `README.md` includes the new software (with link to harness directory)
- [ ] `registry.json` includes an entry for the new software (for the [CLI-Hub](https://hkuds.github.io/CLI-Anything/hub/))
- [ ] `repl_skin.py` in `utils/` is an unmodified copy from the plugin
### For Existing CLI Modifications
@@ -34,6 +35,7 @@ Fixes #<!-- issue number -->
- [ ] All unit tests pass: `python3 -m pytest cli_anything/<software>/tests/test_core.py -v`
- [ ] All E2E tests pass: `python3 -m pytest cli_anything/<software>/tests/test_full_e2e.py -v`
- [ ] No test regressions — no previously passing tests were removed or weakened
- [ ] `registry.json` entry is updated if version, description, or requirements changed
### General Checklist

View File

@@ -14,7 +14,8 @@ Adding a new CLI harness is the most impactful contribution. Before submitting a
2. **`SKILL.md`** — the AI-discoverable skill definition exists inside the Python package at `cli_anything/<software>/SKILL.md`.
3. **Tests** — unit tests (`test_core.py`, passable without backend) and E2E tests (`test_full_e2e.py`) are present and passing.
4. **`README.md`** — the project README includes the new software with a link to its harness directory.
5. **`repl_skin.py`** — an unmodified copy from the plugin exists in `utils/`.
5. **`registry.json`** — add an entry for the new software so it appears on the [CLI-Hub](https://hkuds.github.io/CLI-Anything/hub/).
6. **`repl_skin.py`** — an unmodified copy from the plugin exists in `utils/`.
### B) New Features
@@ -32,6 +33,36 @@ Bug fixes resolve incorrect behavior in existing harnesses or the plugin.
- Include a test that reproduces the bug and verifies the fix.
- Ensure all existing tests for the affected harness still pass.
## CLI-Hub & Registry
All available CLIs are listed in `registry.json` at the repo root and displayed on the [CLI-Hub](https://hkuds.github.io/CLI-Anything/hub/). The hub reads `registry.json` directly from `main`, so it updates immediately when a PR is merged.
### Adding a new CLI to the Hub
Include an entry in `registry.json` as part of your PR. Each entry has this shape:
```json
{
"name": "my-software",
"display_name": "My Software",
"version": "1.0.0",
"description": "Short description of what the CLI does",
"requires": "backend software or null",
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=my-software/agent-harness",
"entry_point": "cli-anything-my-software",
"skill_md": "my-software/agent-harness/cli_anything/my_software/skills/SKILL.md",
"category": "category-name"
}
```
### Updating an existing CLI on the Hub
When you modify an existing harness, update its `registry.json` entry in the same PR:
- Bump the `version` field to reflect the change.
- Update `description`, `requires`, or `category` if they changed.
- The hub will reflect the update as soon as the PR is merged to `main`.
## Development Setup
Each generated CLI lives in `<software>/agent-harness/` and is an independent Python package:

View File

@@ -42,7 +42,6 @@ CLI-Anything: Bridging the Gap Between AI Agents and the World's Software</stron
> Thanks to all invaluable efforts from the community! More updates continuously on the way everyday..
- **2026-03-18** 🧠 Added an **experimental NotebookLM harness scaffold** from the community. It wraps the installed `notebooklm` CLI for notebook discovery, source management, chat, artifact workflows, downloads, and sharing while preserving a clear experimental/community-maintained boundary.
- **2026-03-17** 🌐 Launched the **[CLI-Hub](https://hkuds.github.io/CLI-Anything/hub/)** — a central registry where you can browse, search, and install any CLI with a single `pip` command. Contributors can add new CLIs or update existing ones by simply opening a PR with a `registry.json` entry. The hub updates automatically on merge.
- **2026-03-16** 🤖 Added **SKILL.md generation** (Phase 6.5) — every generated CLI now ships with an AI-discoverable skill definition inside the Python package. ReplSkin auto-detects the skill file after `pip install`, and the REPL banner displays the absolute path for agents. Includes `skill_generator.py`, Jinja2 template, `package_data` in all setup.py files, and 51 new tests.

482
docs/hub/index.html Normal file
View File

@@ -0,0 +1,482 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CLI-Hub</title>
<style>
:root {
--bg: #0d1117;
--surface: #161b22;
--border: #30363d;
--text: #e6edf3;
--text-muted: #8b949e;
--accent: #58a6ff;
--accent-hover: #79c0ff;
--green: #3fb950;
--tag-bg: #1f2937;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
}
/* ── Header ── */
.header {
text-align: center;
padding: 3.5rem 2rem 2.5rem;
border-bottom: 1px solid var(--border);
max-width: 960px;
margin: 0 auto;
}
.header h1 {
font-size: 2.8rem;
font-weight: 800;
letter-spacing: -0.02em;
margin-bottom: 0.6rem;
}
.header h1 span { color: var(--accent); }
.tagline {
font-size: 1.2rem;
color: var(--text-muted);
line-height: 1.5;
max-width: 800px;
margin: 0 auto 1.25rem;
}
.tagline strong { color: var(--text); font-weight: 600; }
.header-links {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-bottom: 1.5rem;
flex-wrap: wrap;
}
.header-btn {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.45rem 1rem;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text);
text-decoration: none;
font-size: 0.85rem;
font-weight: 500;
transition: border-color 0.15s, color 0.15s;
}
.header-btn:hover { border-color: var(--accent); color: var(--accent); }
.cta {
font-size: 0.95rem;
color: var(--text-muted);
line-height: 1.65;
max-width: 800px;
margin: 0 auto;
}
.cta em { color: var(--accent); font-style: normal; font-weight: 600; }
.stats {
display: flex;
justify-content: center;
gap: 3rem;
margin-top: 1.75rem;
}
.stat-num {
font-size: 2rem;
font-weight: 700;
color: var(--accent);
}
.stat-label {
font-size: 0.8rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* ── Controls ── */
.controls {
max-width: 1200px;
margin: 1.5rem auto;
padding: 0 1rem;
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
align-items: center;
}
.search {
flex: 1;
min-width: 200px;
padding: 0.6rem 1rem;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text);
font-size: 0.95rem;
outline: none;
}
.search:focus { border-color: var(--accent); }
.filter-btn {
padding: 0.45rem 0.85rem;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 20px;
color: var(--text-muted);
font-size: 0.82rem;
cursor: pointer;
transition: all 0.15s;
}
.filter-btn:hover { border-color: var(--accent); color: var(--text); }
.filter-btn.active { background: var(--accent); color: #000; border-color: var(--accent); font-weight: 600; }
/* ── Grid ── */
.grid {
max-width: 1200px;
margin: 1rem auto 3rem;
padding: 0 1rem;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 1rem;
}
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1.25rem;
transition: border-color 0.15s;
}
.card:hover { border-color: var(--accent); }
.card-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.card-title {
font-size: 1.2rem;
font-weight: 600;
}
.card-title a {
color: var(--text);
text-decoration: none;
}
.card-title a:hover { color: var(--accent); }
.card-title a svg {
width: 12px;
height: 12px;
margin-left: 4px;
vertical-align: middle;
opacity: 0.4;
}
.card-title a:hover svg { opacity: 1; }
.card-version {
font-size: 0.75rem;
color: var(--green);
background: rgba(63, 185, 80, 0.15);
padding: 0.15rem 0.5rem;
border-radius: 10px;
flex-shrink: 0;
}
.card-desc {
color: var(--text-muted);
font-size: 0.9rem;
margin-bottom: 0.75rem;
}
.card-requires {
font-size: 0.8rem;
color: var(--text-muted);
margin-bottom: 0.75rem;
}
.card-requires strong { color: var(--text); }
.card-install {
background: #0d1117;
border: 1px solid var(--border);
border-radius: 6px;
padding: 0.5rem 0.75rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.card-install code {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 0.78rem;
color: var(--accent);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.copy-btn {
background: none;
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-muted);
cursor: pointer;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
transition: all 0.15s;
white-space: nowrap;
}
.copy-btn:hover { color: var(--text); border-color: var(--accent); }
.card-links {
display: flex;
gap: 0.75rem;
font-size: 0.82rem;
}
.card-links a {
color: var(--accent);
text-decoration: none;
}
.card-links a:hover { text-decoration: underline; color: var(--accent-hover); }
.category-tag {
font-size: 0.72rem;
color: var(--text-muted);
background: var(--tag-bg);
padding: 0.1rem 0.45rem;
border-radius: 4px;
margin-left: 0.5rem;
}
.empty {
grid-column: 1 / -1;
text-align: center;
padding: 3rem;
color: var(--text-muted);
}
/* ── Footer ── */
.footer {
text-align: center;
padding: 2rem 1rem;
border-top: 1px solid var(--border);
color: var(--text-muted);
font-size: 0.85rem;
}
.footer a { color: var(--accent); text-decoration: none; }
.footer a:hover { text-decoration: underline; }
@media (max-width: 480px) {
.grid { grid-template-columns: 1fr; }
.header h1 { font-size: 2rem; }
.header { padding: 2rem 1rem 1.5rem; }
}
</style>
</head>
<body>
<div class="header">
<h1>CLI-<span>Anything</span> Hub</h1>
<p class="tagline"><strong>Any software. Any codebase. Any Web API.</strong> Generate an agent-native CLI and let AI agents operate it &mdash; install with a single pip command.</p>
<div class="header-links">
<a class="header-btn" href="https://github.com/HKUDS/CLI-Anything" target="_blank">
<svg viewBox="0 0 98 96" width="18" height="18" fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6C29.304 70.25 17.9 66.013 17.9 47.02c0-5.52 1.94-10.046 5.127-13.58-.486-1.302-2.264-6.437.486-13.34 0 0 4.206-1.302 13.59 5.216 3.963-1.14 8.17-1.628 12.34-1.628 4.17 0 8.376.568 12.34 1.628 9.384-6.518 13.59-5.216 13.59-5.216 2.75 6.903.972 12.038.486 13.34 3.268 3.534 5.127 8.06 5.127 13.58 0 19.074-11.485 23.15-22.328 24.29 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"/></svg>
Star on GitHub
</a>
<a class="header-btn" href="https://github.com/HKUDS/CLI-Anything/blob/main/CONTRIBUTING.md" target="_blank">
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Zm.176 4.823L9.75 4.81l-6.286 6.287a.253.253 0 0 0-.064.108l-.558 1.953 1.953-.558a.253.253 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354Z"/></svg>
Contributing Guide
</a>
<a class="header-btn" href="https://github.com/HKUDS/CLI-Anything/blob/main/.github/PULL_REQUEST_TEMPLATE.md" target="_blank">
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z"/></svg>
PR Template
</a>
</div>
<p class="cta">We welcome contributions for <em>any</em> application &mdash; desktop apps, dev tools, cloud services, SaaS APIs, creative suites, and beyond. If it has a GUI or an API, it deserves an agent-native CLI. Open a PR and bring it to the hub!</p>
<div class="stats">
<div class="stat">
<div class="stat-num" id="cli-count">-</div>
<div class="stat-label">CLIs Available</div>
</div>
<div class="stat">
<div class="stat-num" id="category-count">-</div>
<div class="stat-label">Categories</div>
</div>
</div>
</div>
<div class="controls">
<input class="search" type="text" placeholder="Search CLIs..." id="search">
<div id="filters"></div>
</div>
<div class="grid" id="grid"></div>
<div class="footer">
<p>Want to add or update a CLI? Read the <a href="https://github.com/HKUDS/CLI-Anything/blob/main/CONTRIBUTING.md" target="_blank">Contributing Guide</a> and use the <a href="https://github.com/HKUDS/CLI-Anything/blob/main/.github/PULL_REQUEST_TEMPLATE.md" target="_blank">PR Template</a> &mdash; Powered by <a href="https://github.com/HKUDS/CLI-Anything" target="_blank">CLI-Anything</a></p>
</div>
<script>
const REPO = 'https://github.com/HKUDS/CLI-Anything';
const REGISTRY_URLS = [
'../../registry.json',
'https://raw.githubusercontent.com/HKUDS/CLI-Anything/main/registry.json'
];
const EXTERNAL_LINK_SVG = '<svg viewBox="0 0 16 16" fill="currentColor"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5H4.56l6.72 6.72a.75.75 0 1 1-1.06 1.06L3.5 4.56v2.69a.75.75 0 0 1-1.5 0v-3.5A1.75 1.75 0 0 1 3.75 2Zm7.5 0h1A1.75 1.75 0 0 1 14 3.75v8.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-1a.75.75 0 0 1 1.5 0v1c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-8.5a.25.25 0 0 0-.25-.25h-1a.75.75 0 0 1 0-1.5Z"/></svg>';
let clis = [];
let activeFilter = 'all';
async function loadRegistry() {
try {
let resp;
for (const url of REGISTRY_URLS) {
try {
resp = await fetch(url);
if (resp.ok) break;
} catch (_) { continue; }
}
if (!resp || !resp.ok) throw new Error('All sources failed');
const data = await resp.json();
clis = data.clis;
document.getElementById('cli-count').textContent = clis.length;
const categories = [...new Set(clis.map(c => c.category))];
document.getElementById('category-count').textContent = categories.length;
buildFilters(categories);
render();
} catch (e) {
document.getElementById('grid').innerHTML =
'<div class="empty">Failed to load registry. <a href="' + REPO + '/blob/main/registry.json" style="color:var(--accent)">Check source</a>.</div>';
}
}
const CATEGORY_LABELS = { 'ai': 'AI', '3d': '3D' };
function catLabel(cat) {
return CATEGORY_LABELS[cat] || cat.charAt(0).toUpperCase() + cat.slice(1);
}
function buildFilters(categories) {
const container = document.getElementById('filters');
const allBtn = document.createElement('button');
allBtn.className = 'filter-btn active';
allBtn.textContent = 'All';
allBtn.dataset.cat = 'all';
allBtn.onclick = () => setFilter('all');
container.appendChild(allBtn);
categories.sort().forEach(cat => {
const btn = document.createElement('button');
btn.className = 'filter-btn';
btn.textContent = catLabel(cat);
btn.dataset.cat = cat;
btn.onclick = () => setFilter(cat);
container.appendChild(btn);
});
}
function setFilter(cat) {
activeFilter = cat;
document.querySelectorAll('.filter-btn').forEach(b => {
b.classList.toggle('active', b.dataset.cat === cat);
});
render();
}
function render() {
const query = document.getElementById('search').value.toLowerCase();
const filtered = clis.filter(c => {
const matchFilter = activeFilter === 'all' || c.category === activeFilter;
const matchSearch = !query ||
c.display_name.toLowerCase().includes(query) ||
c.description.toLowerCase().includes(query) ||
c.name.toLowerCase().includes(query);
return matchFilter && matchSearch;
});
const grid = document.getElementById('grid');
if (filtered.length === 0) {
grid.innerHTML = '<div class="empty">No CLIs match your search.</div>';
return;
}
grid.innerHTML = filtered.map(c => {
const requiresHtml = c.requires
? `<div class="card-requires"><strong>Requires:</strong> ${esc(c.requires)}</div>`
: '';
const skillLink = c.skill_md
? `<a href="${REPO}/blob/main/${c.skill_md}" target="_blank">SKILL.md</a>`
: '';
const sourceLink = `<a href="${REPO}/tree/main/${c.name}/agent-harness" target="_blank">Source</a>`;
const titleHtml = c.homepage
? `<a href="${esc(c.homepage)}" target="_blank">${esc(c.display_name)}${EXTERNAL_LINK_SVG}</a>`
: esc(c.display_name);
return `
<div class="card">
<div class="card-head">
<span>
<span class="card-title">${titleHtml}</span>
<span class="category-tag">${catLabel(c.category)}</span>
</span>
<span class="card-version">v${esc(c.version)}</span>
</div>
<div class="card-desc">${esc(c.description)}</div>
${requiresHtml}
<div class="card-install">
<code>${esc(c.install_cmd)}</code>
<button class="copy-btn" onclick="copyCmd(this, '${esc(c.install_cmd)}')">Copy</button>
</div>
<div class="card-links">${sourceLink}${skillLink ? ' &middot; ' + skillLink : ''}</div>
</div>`;
}).join('');
}
function copyCmd(btn, cmd) {
navigator.clipboard.writeText(cmd).then(() => {
btn.textContent = 'Copied!';
setTimeout(() => btn.textContent = 'Copy', 1500);
});
}
function esc(s) {
if (!s) return '';
const d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
document.getElementById('search').addEventListener('input', render);
loadRegistry();
</script>
</body>
</html>