launch cli-anything-hub pkg manager.

This commit is contained in:
yuhao
2026-04-10 15:43:51 +00:00
parent c6c5514a3f
commit e59b7ee367
17 changed files with 1199 additions and 321 deletions
+27 -10
View File
@@ -32,11 +32,19 @@ def main():
"## Quick Install",
"",
"```bash",
"# Install any CLI",
f"pip install git+{data['meta']['repo']}.git#subdirectory=<name>/agent-harness",
"# First, install the CLI Hub package manager",
"pip install cli-anything-hub",
"",
"# Example: Install GIMP CLI",
f"pip install git+{data['meta']['repo']}.git#subdirectory=gimp/agent-harness",
"# Browse available CLIs",
"cli-hub list",
"",
"# Install any CLI by name",
"cli-hub install gimp",
"cli-hub install blender",
"",
"# Search by category or keyword",
"cli-hub search image",
"cli-hub search 3d",
"```",
"",
"## Available CLIs",
@@ -53,12 +61,19 @@ def main():
for cli in sorted(clis, key=lambda x: x['name']):
name = cli['display_name']
desc = cli['description']
install = f"`{cli['install_cmd']}`"
install = f"`cli-hub install {cli['name']}`"
lines.append(f"| **{name}** | {desc} | {install} |")
lines.append("")
lines.extend([
"## How It Works",
"",
"`cli-hub` is a lightweight wrapper around `pip`. When you run `cli-hub install gimp`,",
"it installs a separate Python package (`cli-anything-gimp`) with its own CLI entry point",
"(`cli-anything-gimp`). Each CLI is an independent pip package — `cli-hub` simply resolves",
"names from the registry and tracks installs.",
"",
"## Usage Pattern",
"",
"All CLIs follow the same pattern:",
@@ -76,15 +91,17 @@ def main():
"",
"## For AI Agents",
"",
"1. Install the CLI you need from the table above",
"2. Read its full SKILL.md at the repo path shown in registry.json",
"3. Always use `--json` flag for machine-readable output",
"4. Check exit codes (0=success, non-zero=error)",
"1. Install the hub: `pip install cli-anything-hub`",
"2. Install the CLI you need: `cli-hub install <name>` (installs `cli-anything-<name>` pip package)",
"3. Run: `cli-anything-<name>` for REPL, or `cli-anything-<name> <command>` for one-shot",
"4. Read its full SKILL.md at the repo path shown in registry.json",
"5. Always use `--json` flag for machine-readable output",
"6. Check exit codes (0=success, non-zero=error)",
"",
"## More Info",
"",
f"- Repository: {data['meta']['repo']}",
"- Web Hub: https://hkuds.github.io/CLI-Anything/",
"- Web Hub: https://clianything.cc",
f"- Last Updated: {data['meta']['updated']}",
])
+5
View File
@@ -7,6 +7,8 @@ on:
paths:
- '*/agent-harness/**'
- 'registry.json'
- 'cli-hub/**'
- 'docs/hub/**'
- '.github/workflows/deploy-pages.yml'
- '.github/scripts/update_registry_dates.py'
- '.github/scripts/generate_meta_skill.py'
@@ -44,6 +46,9 @@ jobs:
- name: Copy catalog as .txt to avoid Jekyll processing
run: cp cli-hub-skill/SKILL.md docs/hub/SKILL.txt
- name: Copy registry.json to hub for cli-hub access
run: cp registry.json docs/hub/registry.json
- name: Build with Jekyll
uses: actions/jekyll-build-pages@v1
with:
+1
View File
@@ -51,6 +51,7 @@
!/skill_generation/
!/openclaw-skill/
!/cli-hub-meta-skill/
!/cli-hub/
# Ignore cli-hub-skill (auto-generated, not tracked)
/cli-hub-skill/
+8 -2
View File
@@ -5,7 +5,7 @@
CLI-Anything: Bridging the Gap Between AI Agents and the World's Software</strong><br>
</p>
**🌐 [CLI-Hub](https://hkuds.github.io/CLI-Anything/)**: Explore all community-built CLIs and install with one command at the **[CLI-Hub](https://hkuds.github.io/CLI-Anything/)**. Want to add your own? [Open a PR](https://github.com/HKUDS/CLI-Anything/blob/main/CONTRIBUTING.md) — the hub updates instantly.
**🌐 [CLI-Hub](https://hkuds.github.io/CLI-Anything/)**: `pip install cli-anything-hub` then `cli-hub install <name>` — browse, install, and manage all community-built CLIs. Want to add your own? [Open a PR](https://github.com/HKUDS/CLI-Anything/blob/main/CONTRIBUTING.md) — the hub updates instantly.
**🎬 [See Demos](#-real-world-demos)**: Watch AI agents use generated CLIs to produce real artifacts — diagrams, gameplay, subtitles, and more.
@@ -45,7 +45,13 @@ 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-04-08** 🎬 **Openscreen CLI** merged (#183) — screen recording editor harness with fused manual + auto-generated tests (101 tests).
- **2026-04-10** 📦 **CLI-Hub package manager** launched — `pip install cli-anything-hub` to browse, install, and manage all CLIs. Download tracking via Umami analytics. Hub frontend updated with new install flow and "Empower yourself" toolkit card.
- **2026-04-09** 🧹 Cleanup and docs pass (#200) — fixed Openscreen test subtotals, added Openscreen to Chinese README and project structure.
- **2026-04-08** 🎬 **Openscreen CLI** merged (#183) — screen recording editor harness with 101 tests. ☁️ **CloudAnalyzer CLI** merged (#181) — cloud cost analysis harness with 27 commands. 🌊 **SeaClip / PM2 / ChromaDB** harnesses merged (#129).
- **2026-04-07** 🔄 **Dify Workflow CLI** merged (#191) — workflow automation wrapper. 🔧 **Inkscape** auto-save fix (#193, fixes #182). 🛡️ **DomShell security hardening** (#156) — URL validation and DOM sanitization for browser CLI. 🥧 **Pi Coding Agent extension** merged (#178).
- **2026-04-06** 🔍 **Exa CLI** merged (#172) — AI-powered web search and answers harness. 🎮 **Godot CLI** merged (#140) — game engine harness with full demo-game E2E pipeline. CLI-Hub frontend improvements.
+42 -12
View File
@@ -9,13 +9,33 @@ description: >-
CLI-Hub is a marketplace of agent-native command-line interfaces that make professional software accessible to AI agents.
## Quick Start
```bash
# Install the CLI Hub package manager
pip install cli-anything-hub
# Browse all available CLIs
cli-hub list
# Search by category or keyword
cli-hub search image
cli-hub search "3d modeling"
# Install a CLI
cli-hub install gimp
# Show details for a CLI
cli-hub info gimp
```
## Live Catalog
**URL**: [`https://hkuds.github.io/CLI-Anything/SKILL.txt`](https://hkuds.github.io/CLI-Anything/SKILL.txt)
**URL**: [`https://clianything.cc/SKILL.txt`](https://clianything.cc/SKILL.txt)
The catalog is auto-updated and provides:
- Full list of available CLIs organized by category
- One-line `pip install` commands for each tool
- One-line `cli-hub install` commands for each tool
- Complete descriptions and usage patterns
**Note**: The file is served as `.txt` but contains markdown formatting for easy parsing.
@@ -33,26 +53,36 @@ CLI-Hub covers a broad range of software and codebases, empowering agents to con
Each CLI provides stateful operations, JSON output for agents, REPL mode, and integrates with real software backends.
## How It Works
`cli-hub` is a lightweight wrapper around `pip`. When you run `cli-hub install gimp`, it installs a separate Python package (`cli-anything-gimp`) with its own CLI entry point (`cli-anything-gimp`). Each CLI is an independent pip package — `cli-hub` simply resolves names from the registry and tracks installs.
## How to Use
1. **Read the catalog**: Fetch `https://hkuds.github.io/CLI-Anything/SKILL.txt` (markdown format)
2. **Find your tool**: Browse by category to discover the CLI you need
3. **Install**: Use the provided `pip install` command
4. **Execute**: All CLIs support `--json` flag for machine-readable output
1. **Install cli-hub**: `pip install cli-anything-hub`
2. **Find your tool**: `cli-hub search <keyword>` or `cli-hub list -c <category>`
3. **Install**: `cli-hub install <name>` (installs the `cli-anything-<name>` pip package)
4. **Run**: `cli-anything-<name>` for REPL, or `cli-anything-<name> <command>` for one-shot
5. **JSON output**: All CLIs support `--json` flag for machine-readable output
## Example Workflow
```bash
# Fetch the catalog to find available tools
# Install the CLI you need
pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=<software>/agent-harness
# Install the hub
pip install cli-anything-hub
# Find what you need
cli-hub search video
# Install it
cli-hub install kdenlive
# Use it with JSON output
cli-anything-<software> --json <command> [options]
cli-anything-kdenlive --json project create --name my-project
```
## More Info
- Live Catalog: https://hkuds.github.io/CLI-Anything/SKILL.txt
- Web Hub: https://hkuds.github.io/CLI-Anything/
- Live Catalog: https://clianything.cc/SKILL.txt
- Web Hub: https://clianything.cc
- Repository: https://github.com/HKUDS/CLI-Anything
+85
View File
@@ -0,0 +1,85 @@
# cli-hub
Package manager for [CLI-Anything](https://github.com/HKUDS/CLI-Anything) — a framework that auto-generates stateful CLI interfaces for GUI applications, making them agent-native.
Browse, install, and manage 40+ CLI harnesses for software like GIMP, Blender, Inkscape, LibreOffice, Audacity, OBS Studio, and more — all from your terminal.
**Web Hub**: [clianything.cc](https://clianything.cc)
## Install
```bash
pip install cli-anything-hub
```
## Usage
```bash
# Browse all available CLIs, grouped by category
cli-hub list
# Filter by category (image, 3d, video, audio, office, ai, ...)
cli-hub list -c image
# Search by name, description, or category
cli-hub search "3d modeling"
# Show details for a CLI
cli-hub info gimp
# Install a CLI harness
cli-hub install gimp
# Update a CLI to the latest version
cli-hub update gimp
# Uninstall a CLI
cli-hub uninstall gimp
```
## What gets installed
Each CLI harness is a standalone Python package that wraps a real application (GIMP, Blender, etc.) with a stateful command-line interface. Every harness supports:
- **REPL mode**: `cli-anything-gimp` launches an interactive session
- **One-shot commands**: `cli-anything-gimp project create --name my-project`
- **JSON output**: `cli-anything-gimp --json project list` for machine-readable output
- **Undo/redo**: Stateful project management with full operation history
## For AI agents
cli-hub is designed to be agent-friendly. AI coding agents can:
1. `pip install cli-anything-hub` to get the package manager
2. `cli-hub search <keyword>` or `cli-hub list --json` to discover tools
3. `cli-hub install <name>` to install what they need
4. Use `--json` output for structured data parsing
## Available categories
3D, AI, Audio, Communication, Database, Design, DevOps, Diagrams, Game, GameDev, Generation, Graphics, Image, Music, Network, Office, OSINT, Project Management, Search, Streaming, Testing, Video, Web
## JSON output
All listing commands support `--json` for machine-readable output:
```bash
cli-hub list --json
cli-hub search blender --json
```
## Analytics
cli-hub sends anonymous install/uninstall events to help track adoption (via [Umami](https://umami.is)). No personal data is collected.
Opt out:
```bash
export CLI_HUB_NO_ANALYTICS=1
```
## Links
- **Web Hub**: [clianything.cc](https://clianything.cc)
- **Repository**: [github.com/HKUDS/CLI-Anything](https://github.com/HKUDS/CLI-Anything)
- **Live Catalog**: [clianything.cc/SKILL.txt](https://clianything.cc/SKILL.txt)
+3
View File
@@ -0,0 +1,3 @@
"""cli-hub — Download, manage, and browse CLI-Anything harnesses."""
__version__ = "0.1.0"
+111
View File
@@ -0,0 +1,111 @@
"""Lightweight, opt-out-able download event tracking via Umami."""
import os
import platform
import threading
import requests
from cli_hub import __version__
UMAMI_URL = "https://cloud.umami.is/api/send"
WEBSITE_ID = "a076c661-bed1-405c-a522-813794e688b4"
HOSTNAME = "clianything.cc"
USER_AGENT = f"Mozilla/5.0 (compatible; cli-anything-hub/{__version__})"
def _is_enabled():
return os.environ.get("CLI_HUB_NO_ANALYTICS", "").strip() not in ("1", "true", "yes")
def _send_event(payload):
"""Send a single event payload. Blocking — callers should use threads."""
try:
return requests.post(
UMAMI_URL, json=payload, timeout=5,
headers={"User-Agent": USER_AGENT},
)
except Exception:
return None # analytics must never break the user's workflow
def track_event(event_name, url="/cli-anything-hub", data=None):
"""Fire-and-forget event to Umami. Non-blocking, never raises."""
if not _is_enabled():
return
payload = {
"type": "event",
"payload": {
"website": WEBSITE_ID,
"hostname": HOSTNAME,
"url": url,
"name": event_name,
"data": data or {},
},
}
threading.Thread(target=_send_event, args=(payload,), daemon=True).start()
def track_install(cli_name, version):
"""Track a CLI install event — event name includes the CLI for dashboard visibility."""
track_event(f"cli-install:{cli_name}", url=f"/cli-anything-hub/install/{cli_name}", data={
"cli": cli_name,
"version": version,
"platform": platform.system().lower(),
})
def track_uninstall(cli_name):
"""Track a CLI uninstall event."""
track_event(f"cli-uninstall:{cli_name}", url=f"/cli-anything-hub/uninstall/{cli_name}", data={
"cli": cli_name,
})
def track_visit(is_agent=False):
"""Track a visit-human or visit-agent event, matching the hub website's convention."""
event_name = "visit-agent" if is_agent else "visit-human"
track_event(event_name, url="/cli-anything-hub", data={
"source": "cli-anything-hub",
"platform": platform.system().lower(),
})
def track_first_run():
"""Send a one-time 'cli-hub-installed' event on first invocation."""
from pathlib import Path
marker = Path.home() / ".cli-hub" / ".first_run_sent"
if marker.exists():
return
track_event("cli-anything-hub-installed", url="/cli-anything-hub/installed", data={
"version": __version__,
"platform": platform.system().lower(),
})
try:
marker.parent.mkdir(parents=True, exist_ok=True)
marker.write_text(__version__)
except Exception:
pass
def _detect_is_agent():
"""Detect if cli-hub is likely being invoked by an AI agent."""
indicators = [
"CLAUDE_CODE", # Claude Code
"CODEX", # OpenAI Codex
"CURSOR_SESSION", # Cursor
"CLINE_SESSION", # Cline
"COPILOT", # GitHub Copilot
"AIDER", # Aider
"CONTINUE_SESSION", # Continue.dev
]
for var in indicators:
if os.environ.get(var):
return True
# Check if stdin is not a terminal (piped / scripted)
import sys
if not sys.stdin.isatty():
return True
return False
+168
View File
@@ -0,0 +1,168 @@
"""cli-hub — CLI entry point."""
import click
from cli_hub import __version__
from cli_hub.registry import fetch_registry, get_cli, search_clis, list_categories
from cli_hub.installer import install_cli, uninstall_cli, get_installed, update_cli
from cli_hub.analytics import track_install, track_uninstall, track_visit, track_first_run, _detect_is_agent
@click.group(invoke_without_command=True)
@click.option("--version", is_flag=True, help="Show version.")
@click.pass_context
def main(ctx, version):
"""cli-hub — Download and manage CLI-Anything harnesses."""
track_first_run()
track_visit(is_agent=_detect_is_agent())
if version:
click.echo(f"cli-hub {__version__}")
return
if ctx.invoked_subcommand is None:
click.echo(ctx.get_help())
@main.command()
@click.argument("name")
def install(name):
"""Install a CLI harness by name."""
click.echo(f"Installing {name}...")
success, msg = install_cli(name)
if success:
cli = get_cli(name)
track_install(name, cli["version"] if cli else "unknown")
click.secho(f"{msg}", fg="green")
click.echo(f" Run it with: {cli['entry_point']}" if cli else "")
else:
click.secho(f"{msg}", fg="red", err=True)
raise SystemExit(1)
@main.command()
@click.argument("name")
def uninstall(name):
"""Uninstall a CLI harness by name."""
success, msg = uninstall_cli(name)
if success:
track_uninstall(name)
click.secho(f"{msg}", fg="green")
else:
click.secho(f"{msg}", fg="red", err=True)
raise SystemExit(1)
@main.command()
@click.argument("name")
def update(name):
"""Update a CLI harness to the latest version."""
click.echo(f"Updating {name}...")
success, msg = update_cli(name)
if success:
cli = get_cli(name)
track_install(name, cli["version"] if cli else "unknown")
click.secho(f"{msg}", fg="green")
else:
click.secho(f"{msg}", fg="red", err=True)
raise SystemExit(1)
@main.command("list")
@click.option("--category", "-c", default=None, help="Filter by category.")
@click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
def list_clis(category, as_json):
"""List all available CLI harnesses."""
try:
registry = fetch_registry()
except Exception as e:
click.secho(f"Failed to fetch registry: {e}", fg="red", err=True)
raise SystemExit(1)
clis = registry["clis"]
if category:
clis = [c for c in clis if c.get("category", "").lower() == category.lower()]
installed = get_installed()
if as_json:
import json
click.echo(json.dumps(clis, indent=2))
return
if not clis:
click.echo("No CLIs found." + (f" Category '{category}' may not exist." if category else ""))
return
# Group by category
by_cat = {}
for cli in clis:
cat = cli.get("category", "uncategorized")
by_cat.setdefault(cat, []).append(cli)
for cat in sorted(by_cat):
click.secho(f"\n {cat.upper()}", fg="blue", bold=True)
for cli in sorted(by_cat[cat], key=lambda c: c["name"]):
marker = click.style("", fg="green") if cli["name"] in installed else " "
name = click.style(f"{cli['name']:20s}", bold=True)
desc = cli["description"][:60]
click.echo(f" {marker} {name} {desc}")
total = len(clis)
inst = sum(1 for c in clis if c["name"] in installed)
click.echo(f"\n {total} CLIs available, {inst} installed")
cats = list_categories(registry)
click.echo(f" Categories: {', '.join(cats)}")
@main.command()
@click.argument("query")
@click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
def search(query, as_json):
"""Search CLIs by name, description, or category."""
results = search_clis(query)
if as_json:
import json
click.echo(json.dumps(results, indent=2))
return
if not results:
click.echo(f"No CLIs matching '{query}'.")
return
installed = get_installed()
for cli in results:
marker = click.style("", fg="green") if cli["name"] in installed else " "
name = click.style(cli["name"], bold=True)
cat = click.style(f"[{cli.get('category', '')}]", fg="blue")
click.echo(f" {marker} {name} {cat}{cli['description'][:70]}")
click.echo(f" Install: cli-hub install {cli['name']}")
@main.command()
@click.argument("name")
def info(name):
"""Show details for a specific CLI."""
cli = get_cli(name)
if not cli:
click.secho(f"CLI '{name}' not found.", fg="red", err=True)
raise SystemExit(1)
installed = get_installed()
is_installed = cli["name"] in installed
click.secho(f"\n {cli['display_name']}", bold=True)
click.echo(f" {cli['description']}")
click.echo(f" Category: {cli.get('category', 'N/A')}")
click.echo(f" Version: {cli['version']}")
click.echo(f" Requires: {cli.get('requires') or 'nothing'}")
click.echo(f" Entry point: {cli['entry_point']}")
click.echo(f" Homepage: {cli.get('homepage', 'N/A')}")
click.echo(f" Contributor: {cli.get('contributor', 'N/A')}")
status = click.style("installed", fg="green") if is_installed else "not installed"
click.echo(f" Status: {status}")
click.echo(f"\n Install: cli-hub install {cli['name']}")
click.echo()
if __name__ == "__main__":
main()
+107
View File
@@ -0,0 +1,107 @@
"""Install, uninstall, and manage CLI-Anything harnesses via pip."""
import json
import subprocess
import sys
from pathlib import Path
from cli_hub.registry import get_cli, fetch_registry
INSTALLED_FILE = Path.home() / ".cli-hub" / "installed.json"
def _load_installed():
if INSTALLED_FILE.exists():
try:
return json.loads(INSTALLED_FILE.read_text())
except json.JSONDecodeError:
pass
return {}
def _save_installed(data):
INSTALLED_FILE.parent.mkdir(parents=True, exist_ok=True)
INSTALLED_FILE.write_text(json.dumps(data, indent=2))
def install_cli(name):
"""Install a CLI harness by name. Returns (success, message)."""
cli = get_cli(name)
if cli is None:
return False, f"CLI '{name}' not found in registry. Use 'cli-hub list' to see available CLIs."
install_cmd = cli["install_cmd"]
result = subprocess.run(
[sys.executable, "-m", "pip", "install"] + install_cmd.replace("pip install ", "").split(),
capture_output=True, text=True
)
if result.returncode == 0:
installed = _load_installed()
installed[cli["name"]] = {
"version": cli["version"],
"entry_point": cli["entry_point"],
"install_cmd": install_cmd,
}
_save_installed(installed)
return True, f"Installed {cli['display_name']} ({cli['entry_point']})"
else:
return False, f"pip install failed:\n{result.stderr}"
def uninstall_cli(name):
"""Uninstall a CLI harness by name. Returns (success, message)."""
cli = get_cli(name)
if cli is None:
return False, f"CLI '{name}' not found in registry."
# The pip package name follows the pattern: cli-anything-<name> with underscores
# but we derive it from the install_cmd's subdirectory
# The namespace package is cli_anything.<name>, entry point is cli-anything-<name>
# pip package name in subdirectory installs is the name from setup.py
# We'll uninstall by the entry_point pattern
pkg_name = f"cli-anything-{cli['name']}"
result = subprocess.run(
[sys.executable, "-m", "pip", "uninstall", "-y", pkg_name],
capture_output=True, text=True
)
if result.returncode == 0:
installed = _load_installed()
installed.pop(cli["name"], None)
_save_installed(installed)
return True, f"Uninstalled {cli['display_name']}"
else:
return False, f"pip uninstall failed:\n{result.stderr}"
def get_installed():
"""Return dict of installed CLIs."""
return _load_installed()
def update_cli(name):
"""Update a CLI by reinstalling from the latest source."""
cli = get_cli(name, fetch_registry(force_refresh=True))
if cli is None:
return False, f"CLI '{name}' not found in registry."
install_cmd = cli["install_cmd"]
result = subprocess.run(
[sys.executable, "-m", "pip", "install", "--upgrade", "--force-reinstall"]
+ install_cmd.replace("pip install ", "").split(),
capture_output=True, text=True
)
if result.returncode == 0:
installed = _load_installed()
installed[cli["name"]] = {
"version": cli["version"],
"entry_point": cli["entry_point"],
"install_cmd": install_cmd,
}
_save_installed(installed)
return True, f"Updated {cli['display_name']} to {cli['version']}"
else:
return False, f"Update failed:\n{result.stderr}"
+72
View File
@@ -0,0 +1,72 @@
"""Fetch and cache the CLI-Anything registry."""
import json
import os
import time
from pathlib import Path
import requests
REGISTRY_URL = "https://hkuds.github.io/CLI-Anything/registry.json"
CACHE_DIR = Path.home() / ".cli-hub"
CACHE_FILE = CACHE_DIR / "registry_cache.json"
CACHE_TTL = 3600 # 1 hour
def _ensure_cache_dir():
CACHE_DIR.mkdir(parents=True, exist_ok=True)
def fetch_registry(force_refresh=False):
"""Fetch registry.json, using a local cache with TTL."""
_ensure_cache_dir()
if not force_refresh and CACHE_FILE.exists():
try:
cached = json.loads(CACHE_FILE.read_text())
if time.time() - cached.get("_cached_at", 0) < CACHE_TTL:
return cached["data"]
except (json.JSONDecodeError, KeyError):
pass
resp = requests.get(REGISTRY_URL, timeout=15)
resp.raise_for_status()
data = resp.json()
cache_payload = {"_cached_at": time.time(), "data": data}
CACHE_FILE.write_text(json.dumps(cache_payload, indent=2))
return data
def get_cli(name, registry=None):
"""Look up a CLI entry by name (case-insensitive)."""
if registry is None:
registry = fetch_registry()
name_lower = name.lower()
for cli in registry["clis"]:
if cli["name"].lower() == name_lower:
return cli
return None
def search_clis(query, registry=None):
"""Search CLIs by name, description, or category."""
if registry is None:
registry = fetch_registry()
query_lower = query.lower()
results = []
for cli in registry["clis"]:
if (query_lower in cli["name"].lower()
or query_lower in cli["description"].lower()
or query_lower in cli.get("category", "").lower()
or query_lower in cli.get("display_name", "").lower()):
results.append(cli)
return results
def list_categories(registry=None):
"""Return sorted list of unique categories."""
if registry is None:
registry = fetch_registry()
return sorted(set(cli.get("category", "uncategorized") for cli in registry["clis"]))
+49
View File
@@ -0,0 +1,49 @@
"""cli-hub — package manager for CLI-Anything harnesses."""
from setuptools import setup, find_packages
setup(
name="cli-anything-hub",
version="0.1.0",
description="Package manager for CLI-Anything — browse, install, and manage 40+ agent-native CLI interfaces for GUI applications",
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
author="HKUDS",
author_email="hkuds@connect.hku.hk",
url="https://github.com/HKUDS/CLI-Anything",
project_urls={
"Homepage": "https://clianything.cc",
"Repository": "https://github.com/HKUDS/CLI-Anything",
"Bug Tracker": "https://github.com/HKUDS/CLI-Anything/issues",
"Catalog": "https://clianything.cc/SKILL.txt",
},
license="MIT",
packages=find_packages(exclude=["tests", "tests.*"]),
python_requires=">=3.10",
install_requires=[
"click>=8.0",
"requests>=2.28",
],
entry_points={
"console_scripts": [
"cli-hub=cli_hub.cli:main",
],
},
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Console",
"Intended Audience :: Developers",
"Intended Audience :: System Administrators",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Libraries :: Application Frameworks",
"Topic :: System :: Installation/Setup",
"Topic :: Utilities",
],
keywords="cli, agent, gui, automation, package-manager, cli-anything",
)
View File
+420
View File
@@ -0,0 +1,420 @@
"""Tests for cli-hub — registry, installer, analytics, and CLI."""
import json
import os
import tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock
import pytest
import click.testing
from cli_hub import __version__
from cli_hub.registry import fetch_registry, get_cli, search_clis, list_categories
from cli_hub.installer import install_cli, uninstall_cli, get_installed, _load_installed, _save_installed
from cli_hub.analytics import _is_enabled, track_event, track_install, track_uninstall as analytics_track_uninstall, track_visit, track_first_run, _detect_is_agent
from cli_hub.cli import main
# ─── Sample registry data ─────────────────────────────────────────────
SAMPLE_REGISTRY = {
"meta": {"repo": "https://github.com/HKUDS/CLI-Anything", "description": "test"},
"clis": [
{
"name": "gimp",
"display_name": "GIMP",
"version": "1.0.0",
"description": "Image editing via GIMP",
"requires": "gimp",
"homepage": "https://gimp.org",
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=gimp/agent-harness",
"entry_point": "cli-anything-gimp",
"skill_md": None,
"category": "image",
"contributor": "test-user",
"contributor_url": "https://github.com/test-user",
},
{
"name": "blender",
"display_name": "Blender",
"version": "1.0.0",
"description": "3D modeling via Blender",
"requires": "blender",
"homepage": "https://blender.org",
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=blender/agent-harness",
"entry_point": "cli-anything-blender",
"skill_md": None,
"category": "3d",
"contributor": "test-user",
"contributor_url": "https://github.com/test-user",
},
{
"name": "audacity",
"display_name": "Audacity",
"version": "1.0.0",
"description": "Audio editing and processing via sox",
"requires": "sox",
"homepage": "https://audacityteam.org",
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=audacity/agent-harness",
"entry_point": "cli-anything-audacity",
"skill_md": None,
"category": "audio",
"contributor": "test-user",
"contributor_url": "https://github.com/test-user",
},
],
}
# ─── Registry tests ───────────────────────────────────────────────────
class TestRegistry:
"""Tests for registry.py — fetch, cache, search, and lookup."""
@patch("cli_hub.registry.requests.get")
@patch("cli_hub.registry.CACHE_FILE", Path(tempfile.mktemp()))
def test_fetch_registry_from_remote(self, mock_get):
mock_resp = MagicMock()
mock_resp.json.return_value = SAMPLE_REGISTRY
mock_resp.raise_for_status = MagicMock()
mock_get.return_value = mock_resp
result = fetch_registry(force_refresh=True)
assert result["clis"][0]["name"] == "gimp"
mock_get.assert_called_once()
def test_get_cli_found(self):
cli = get_cli("gimp", SAMPLE_REGISTRY)
assert cli is not None
assert cli["display_name"] == "GIMP"
def test_get_cli_case_insensitive(self):
cli = get_cli("GIMP", SAMPLE_REGISTRY)
assert cli is not None
assert cli["name"] == "gimp"
def test_get_cli_not_found(self):
cli = get_cli("nonexistent", SAMPLE_REGISTRY)
assert cli is None
def test_search_by_name(self):
results = search_clis("gimp", SAMPLE_REGISTRY)
assert len(results) == 1
assert results[0]["name"] == "gimp"
def test_search_by_category(self):
results = search_clis("3d", SAMPLE_REGISTRY)
assert len(results) == 1
assert results[0]["name"] == "blender"
def test_search_by_description(self):
results = search_clis("audio", SAMPLE_REGISTRY)
assert len(results) == 1
assert results[0]["name"] == "audacity"
def test_search_no_results(self):
results = search_clis("nonexistent_xyz", SAMPLE_REGISTRY)
assert len(results) == 0
def test_list_categories(self):
cats = list_categories(SAMPLE_REGISTRY)
assert cats == ["3d", "audio", "image"]
# ─── Installer tests ──────────────────────────────────────────────────
class TestInstaller:
"""Tests for installer.py — install, uninstall, tracking."""
def test_load_installed_empty(self, tmp_path):
with patch("cli_hub.installer.INSTALLED_FILE", tmp_path / "installed.json"):
assert _load_installed() == {}
def test_save_and_load_installed(self, tmp_path):
installed_file = tmp_path / "installed.json"
with patch("cli_hub.installer.INSTALLED_FILE", installed_file):
_save_installed({"gimp": {"version": "1.0.0"}})
data = _load_installed()
assert data["gimp"]["version"] == "1.0.0"
@patch("cli_hub.installer.subprocess.run")
@patch("cli_hub.installer.get_cli")
@patch("cli_hub.installer.INSTALLED_FILE", Path(tempfile.mktemp()))
def test_install_success(self, mock_get_cli, mock_run):
mock_get_cli.return_value = SAMPLE_REGISTRY["clis"][0]
mock_run.return_value = MagicMock(returncode=0)
success, msg = install_cli("gimp")
assert success
assert "GIMP" in msg
@patch("cli_hub.installer.get_cli")
def test_install_not_found(self, mock_get_cli):
mock_get_cli.return_value = None
success, msg = install_cli("nonexistent")
assert not success
assert "not found" in msg
@patch("cli_hub.installer.subprocess.run")
@patch("cli_hub.installer.get_cli")
@patch("cli_hub.installer.INSTALLED_FILE", Path(tempfile.mktemp()))
def test_install_pip_failure(self, mock_get_cli, mock_run):
mock_get_cli.return_value = SAMPLE_REGISTRY["clis"][0]
mock_run.return_value = MagicMock(returncode=1, stderr="some error")
success, msg = install_cli("gimp")
assert not success
assert "failed" in msg
@patch("cli_hub.installer.subprocess.run")
@patch("cli_hub.installer.get_cli")
@patch("cli_hub.installer.INSTALLED_FILE", Path(tempfile.mktemp()))
def test_uninstall_success(self, mock_get_cli, mock_run):
mock_get_cli.return_value = SAMPLE_REGISTRY["clis"][0]
mock_run.return_value = MagicMock(returncode=0)
success, msg = uninstall_cli("gimp")
assert success
assert "GIMP" in msg
# ─── Analytics tests ──────────────────────────────────────────────────
class TestAnalytics:
"""Tests for analytics.py — opt-out, event firing, event names."""
def test_analytics_enabled_by_default(self):
with patch.dict(os.environ, {}, clear=True):
assert _is_enabled()
def test_analytics_disabled_by_env(self):
with patch.dict(os.environ, {"CLI_HUB_NO_ANALYTICS": "1"}):
assert not _is_enabled()
def test_analytics_disabled_by_true(self):
with patch.dict(os.environ, {"CLI_HUB_NO_ANALYTICS": "true"}):
assert not _is_enabled()
@patch("cli_hub.analytics._send_event")
def test_track_event_sends_request(self, mock_send):
with patch.dict(os.environ, {}, clear=True):
track_event("test-event", data={"key": "value"})
import time
time.sleep(0.2)
mock_send.assert_called_once()
payload = mock_send.call_args[0][0]
assert payload["payload"]["name"] == "test-event"
assert payload["payload"]["hostname"] == "clianything.cc"
@patch("cli_hub.analytics._send_event")
def test_track_event_noop_when_disabled(self, mock_send):
with patch.dict(os.environ, {"CLI_HUB_NO_ANALYTICS": "1"}):
track_event("test-event")
import time
time.sleep(0.2)
mock_send.assert_not_called()
@patch("cli_hub.analytics._send_event")
def test_track_install_event_name_includes_cli(self, mock_send):
"""cli-install event name must include CLI name for dashboard visibility."""
with patch.dict(os.environ, {}, clear=True):
track_install("gimp", "1.0.0")
import time
time.sleep(0.2)
mock_send.assert_called_once()
payload = mock_send.call_args[0][0]
assert payload["payload"]["name"] == "cli-install:gimp"
assert payload["payload"]["url"] == "/cli-anything-hub/install/gimp"
assert payload["payload"]["data"]["cli"] == "gimp"
assert payload["payload"]["data"]["version"] == "1.0.0"
assert "platform" in payload["payload"]["data"]
@patch("cli_hub.analytics._send_event")
def test_track_uninstall_event_name_includes_cli(self, mock_send):
"""cli-uninstall event name must include CLI name for dashboard visibility."""
with patch.dict(os.environ, {}, clear=True):
analytics_track_uninstall("blender")
import time
time.sleep(0.2)
mock_send.assert_called_once()
payload = mock_send.call_args[0][0]
assert payload["payload"]["name"] == "cli-uninstall:blender"
assert payload["payload"]["url"] == "/cli-anything-hub/uninstall/blender"
assert payload["payload"]["data"]["cli"] == "blender"
@patch("cli_hub.analytics._send_event")
def test_track_visit_human(self, mock_send):
"""visit-human event sent when not detected as agent."""
with patch.dict(os.environ, {}, clear=True):
track_visit(is_agent=False)
import time
time.sleep(0.2)
mock_send.assert_called_once()
payload = mock_send.call_args[0][0]
assert payload["payload"]["name"] == "visit-human"
assert payload["payload"]["url"] == "/cli-anything-hub"
assert payload["payload"]["data"]["source"] == "cli-anything-hub"
@patch("cli_hub.analytics._send_event")
def test_track_visit_agent(self, mock_send):
"""visit-agent event sent when agent environment detected."""
with patch.dict(os.environ, {}, clear=True):
track_visit(is_agent=True)
import time
time.sleep(0.2)
mock_send.assert_called_once()
payload = mock_send.call_args[0][0]
assert payload["payload"]["name"] == "visit-agent"
def test_detect_agent_claude_code(self):
with patch.dict(os.environ, {"CLAUDE_CODE": "1"}):
assert _detect_is_agent() is True
def test_detect_agent_codex(self):
with patch.dict(os.environ, {"CODEX": "1"}):
assert _detect_is_agent() is True
def test_detect_not_agent_clean_env(self):
"""Clean env with a tty should not detect as agent."""
with patch.dict(os.environ, {}, clear=True):
with patch("sys.stdin") as mock_stdin:
mock_stdin.isatty.return_value = True
assert _detect_is_agent() is False
@patch("cli_hub.analytics._send_event")
def test_first_run_sends_event(self, mock_send, tmp_path):
"""First invocation sends cli-hub-installed event."""
with patch.dict(os.environ, {"HOME": str(tmp_path)}, clear=False):
track_first_run()
import time
time.sleep(0.2)
mock_send.assert_called_once()
payload = mock_send.call_args[0][0]
assert payload["payload"]["name"] == "cli-anything-hub-installed"
assert payload["payload"]["url"] == "/cli-anything-hub/installed"
# Marker file should now exist
assert (tmp_path / ".cli-hub" / ".first_run_sent").exists()
@patch("cli_hub.analytics._send_event")
def test_first_run_skips_if_marker_exists(self, mock_send, tmp_path):
"""Second invocation does NOT send cli-hub-installed event."""
cli_hub_dir = tmp_path / ".cli-hub"
cli_hub_dir.mkdir()
(cli_hub_dir / ".first_run_sent").write_text("0.1.0")
with patch.dict(os.environ, {"HOME": str(tmp_path)}, clear=False):
track_first_run()
import time
time.sleep(0.2)
mock_send.assert_not_called()
# ─── CLI tests ─────────────────────────────────────────────────────────
class TestCLI:
"""Tests for the Click CLI interface."""
def setup_method(self):
self.runner = click.testing.CliRunner()
@patch("cli_hub.cli.track_first_run")
@patch("cli_hub.cli.track_visit")
@patch("cli_hub.cli._detect_is_agent", return_value=False)
def test_version(self, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["--version"])
assert __version__ in result.output
assert result.exit_code == 0
mock_visit.assert_called_once_with(is_agent=False)
mock_first_run.assert_called_once()
@patch("cli_hub.cli.track_first_run")
@patch("cli_hub.cli.track_visit")
@patch("cli_hub.cli._detect_is_agent", return_value=False)
def test_help(self, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["--help"])
assert "cli-hub" in result.output
assert result.exit_code == 0
@patch("cli_hub.cli.track_first_run")
@patch("cli_hub.cli.track_visit")
@patch("cli_hub.cli._detect_is_agent", return_value=False)
@patch("cli_hub.cli.fetch_registry", return_value=SAMPLE_REGISTRY)
@patch("cli_hub.cli.get_installed", return_value={})
def test_list_command(self, mock_installed, mock_fetch, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["list"])
assert "gimp" in result.output
assert "blender" in result.output
assert result.exit_code == 0
@patch("cli_hub.cli.track_first_run")
@patch("cli_hub.cli.track_visit")
@patch("cli_hub.cli._detect_is_agent", return_value=False)
@patch("cli_hub.cli.fetch_registry", return_value=SAMPLE_REGISTRY)
@patch("cli_hub.cli.get_installed", return_value={})
def test_list_with_category(self, mock_installed, mock_fetch, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["list", "-c", "image"])
assert "gimp" in result.output
assert "blender" not in result.output
@patch("cli_hub.cli.track_first_run")
@patch("cli_hub.cli.track_visit")
@patch("cli_hub.cli._detect_is_agent", return_value=False)
@patch("cli_hub.cli.search_clis", return_value=[SAMPLE_REGISTRY["clis"][0]])
@patch("cli_hub.cli.get_installed", return_value={})
def test_search_command(self, mock_installed, mock_search, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["search", "gimp"])
assert "gimp" in result.output
assert result.exit_code == 0
@patch("cli_hub.cli.track_first_run")
@patch("cli_hub.cli.track_visit")
@patch("cli_hub.cli._detect_is_agent", return_value=False)
@patch("cli_hub.cli.get_cli", return_value=SAMPLE_REGISTRY["clis"][0])
@patch("cli_hub.cli.get_installed", return_value={})
def test_info_command(self, mock_installed, mock_get, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["info", "gimp"])
assert "GIMP" in result.output
assert "image" in result.output
assert result.exit_code == 0
@patch("cli_hub.cli.track_first_run")
@patch("cli_hub.cli.track_visit")
@patch("cli_hub.cli._detect_is_agent", return_value=False)
@patch("cli_hub.cli.get_cli", return_value=None)
def test_info_not_found(self, mock_get, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["info", "nonexistent"])
assert result.exit_code == 1
@patch("cli_hub.cli.track_first_run")
@patch("cli_hub.cli.track_visit")
@patch("cli_hub.cli._detect_is_agent", return_value=False)
@patch("cli_hub.cli.track_install")
@patch("cli_hub.cli.install_cli", return_value=(True, "Installed GIMP (cli-anything-gimp)"))
@patch("cli_hub.cli.get_cli", return_value=SAMPLE_REGISTRY["clis"][0])
def test_install_command(self, mock_get, mock_install, mock_track, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["install", "gimp"])
assert result.exit_code == 0
assert "Installed" in result.output
mock_track.assert_called_once()
@patch("cli_hub.cli.track_first_run")
@patch("cli_hub.cli.track_visit")
@patch("cli_hub.cli._detect_is_agent", return_value=False)
@patch("cli_hub.cli.track_uninstall")
@patch("cli_hub.cli.uninstall_cli", return_value=(True, "Uninstalled GIMP"))
def test_uninstall_command(self, mock_uninstall, mock_track, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["uninstall", "gimp"])
assert result.exit_code == 0
mock_track.assert_called_once()
@patch("cli_hub.cli.track_first_run")
@patch("cli_hub.cli.track_visit")
@patch("cli_hub.cli._detect_is_agent", return_value=True)
def test_visit_agent_on_invocation(self, mock_detect, mock_visit, mock_first_run):
"""When agent env detected, track_visit is called with is_agent=True."""
result = self.runner.invoke(main, ["--version"])
mock_visit.assert_called_once_with(is_agent=True)
-141
View File
@@ -1,141 +0,0 @@
---
name: cli-anything-hub
description: >-
Browse and install 21+ agent-native CLI tools for GUI software.
Covers image editing, 3D, video, audio, office, diagrams, AI, and more.
---
# CLI-Anything Hub
Agent-native stateful CLI interfaces for 21 applications. All CLIs support `--json` output, REPL mode, and undo/redo.
## Quick Install
```bash
# Install any CLI
pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=<name>/agent-harness
# Example: Install GIMP CLI
pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=gimp/agent-harness
```
## Available CLIs
### 3D
| Name | Description | Install |
|------|-------------|---------|
| **Blender** | 3D modeling, animation, and rendering via blender --background --python | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=blender/agent-harness` |
### Ai
| Name | Description | Install |
|------|-------------|---------|
| **ComfyUI** | AI image generation workflow management via ComfyUI REST API | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=comfyui/agent-harness` |
| **NotebookLM** | Experimental NotebookLM harness scaffold wrapping the installed notebooklm CLI for notebook, source, chat, artifact, download, and sharing workflows | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=notebooklm/agent-harness` |
| **Novita** | Access AI models via Novita's OpenAI-compatible API (DeepSeek, GLM, MiniMax) | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=novita/agent-harness` |
| **Ollama** | Local LLM inference and model management via Ollama REST API | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=ollama/agent-harness` |
### Audio
| Name | Description | Install |
|------|-------------|---------|
| **Audacity** | Audio editing and processing via sox | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=audacity/agent-harness` |
### Communication
| Name | Description | Install |
|------|-------------|---------|
| **Zoom** | Meeting management via Zoom REST API (OAuth2) | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=zoom/agent-harness` |
### Design
| Name | Description | Install |
|------|-------------|---------|
| **Sketch** | Generate Sketch design files (.sketch) from JSON design specifications via sketch-constructor | `cd sketch/agent-harness && npm install && npm link` |
### Diagrams
| Name | Description | Install |
|------|-------------|---------|
| **Draw.io** | Diagram creation and export via draw.io CLI | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=drawio/agent-harness` |
| **Mermaid** | Mermaid Live Editor state files and renderer URLs | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=mermaid/agent-harness` |
### Generation
| Name | Description | Install |
|------|-------------|---------|
| **AnyGen** | Generate docs, slides, websites and more via AnyGen cloud API | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=anygen/agent-harness` |
### Image
| Name | Description | Install |
|------|-------------|---------|
| **GIMP** | Raster image processing via gimp -i -b (batch mode) | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=gimp/agent-harness` |
| **Inkscape** | SVG vector graphics with export via inkscape --export-filename | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=inkscape/agent-harness` |
### Music
| Name | Description | Install |
|------|-------------|---------|
| **MuseScore** | CLI for music notation — transpose, export PDF/audio/MIDI, extract parts, manage instruments | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=musescore/agent-harness` |
### Network
| Name | Description | Install |
|------|-------------|---------|
| **AdGuardHome** | DNS ad-blocking and network infrastructure management via AdGuardHome REST API | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=adguardhome/agent-harness` |
### Office
| Name | Description | Install |
|------|-------------|---------|
| **LibreOffice** | Create and manipulate ODF documents, export to PDF/DOCX/XLSX/PPTX via headless mode | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=libreoffice/agent-harness` |
| **Mubu** | Knowledge management and outlining via local Mubu desktop data | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=mubu/agent-harness` |
### Streaming
| Name | Description | Install |
|------|-------------|---------|
| **OBS Studio** | Create and manage streaming/recording scenes via command line | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=obs-studio/agent-harness` |
### Video
| Name | Description | Install |
|------|-------------|---------|
| **Kdenlive** | Video editing and rendering via melt | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=kdenlive/agent-harness` |
| **Shotcut** | Video editing and rendering via melt/ffmpeg | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=shotcut/agent-harness` |
### Web
| Name | Description | Install |
|------|-------------|---------|
| **Browser** | Browser automation via DOMShell MCP server. Maps Chrome's Accessibility Tree to a virtual filesystem for agent-native navigation. | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=browser/agent-harness` |
## Usage Pattern
All CLIs follow the same pattern:
```bash
# Interactive REPL
cli-anything-<name>
# One-shot command
cli-anything-<name> <group> <command> [options]
# JSON output for agents
cli-anything-<name> --json <group> <command>
```
## For AI Agents
1. Install the CLI you need from the table above
2. Read its full SKILL.md at the repo path shown in registry.json
3. Always use `--json` flag for machine-readable output
4. Check exit codes (0=success, non-zero=error)
## More Info
- Repository: https://github.com/HKUDS/CLI-Anything
- Web Hub: https://hkuds.github.io/CLI-Anything/
- Last Updated: 2026-03-18
-141
View File
@@ -1,141 +0,0 @@
---
name: cli-anything-hub
description: >-
Browse and install 21+ agent-native CLI tools for GUI software.
Covers image editing, 3D, video, audio, office, diagrams, AI, and more.
---
# CLI-Anything Hub
Agent-native stateful CLI interfaces for 21 applications. All CLIs support `--json` output, REPL mode, and undo/redo.
## Quick Install
```bash
# Install any CLI
pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=<name>/agent-harness
# Example: Install GIMP CLI
pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=gimp/agent-harness
```
## Available CLIs
### 3D
| Name | Description | Install |
|------|-------------|---------|
| **Blender** | 3D modeling, animation, and rendering via blender --background --python | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=blender/agent-harness` |
### Ai
| Name | Description | Install |
|------|-------------|---------|
| **ComfyUI** | AI image generation workflow management via ComfyUI REST API | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=comfyui/agent-harness` |
| **NotebookLM** | Experimental NotebookLM harness scaffold wrapping the installed notebooklm CLI for notebook, source, chat, artifact, download, and sharing workflows | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=notebooklm/agent-harness` |
| **Novita** | Access AI models via Novita's OpenAI-compatible API (DeepSeek, GLM, MiniMax) | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=novita/agent-harness` |
| **Ollama** | Local LLM inference and model management via Ollama REST API | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=ollama/agent-harness` |
### Audio
| Name | Description | Install |
|------|-------------|---------|
| **Audacity** | Audio editing and processing via sox | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=audacity/agent-harness` |
### Communication
| Name | Description | Install |
|------|-------------|---------|
| **Zoom** | Meeting management via Zoom REST API (OAuth2) | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=zoom/agent-harness` |
### Design
| Name | Description | Install |
|------|-------------|---------|
| **Sketch** | Generate Sketch design files (.sketch) from JSON design specifications via sketch-constructor | `cd sketch/agent-harness && npm install && npm link` |
### Diagrams
| Name | Description | Install |
|------|-------------|---------|
| **Draw.io** | Diagram creation and export via draw.io CLI | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=drawio/agent-harness` |
| **Mermaid** | Mermaid Live Editor state files and renderer URLs | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=mermaid/agent-harness` |
### Generation
| Name | Description | Install |
|------|-------------|---------|
| **AnyGen** | Generate docs, slides, websites and more via AnyGen cloud API | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=anygen/agent-harness` |
### Image
| Name | Description | Install |
|------|-------------|---------|
| **GIMP** | Raster image processing via gimp -i -b (batch mode) | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=gimp/agent-harness` |
| **Inkscape** | SVG vector graphics with export via inkscape --export-filename | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=inkscape/agent-harness` |
### Music
| Name | Description | Install |
|------|-------------|---------|
| **MuseScore** | CLI for music notation — transpose, export PDF/audio/MIDI, extract parts, manage instruments | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=musescore/agent-harness` |
### Network
| Name | Description | Install |
|------|-------------|---------|
| **AdGuardHome** | DNS ad-blocking and network infrastructure management via AdGuardHome REST API | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=adguardhome/agent-harness` |
### Office
| Name | Description | Install |
|------|-------------|---------|
| **LibreOffice** | Create and manipulate ODF documents, export to PDF/DOCX/XLSX/PPTX via headless mode | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=libreoffice/agent-harness` |
| **Mubu** | Knowledge management and outlining via local Mubu desktop data | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=mubu/agent-harness` |
### Streaming
| Name | Description | Install |
|------|-------------|---------|
| **OBS Studio** | Create and manage streaming/recording scenes via command line | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=obs-studio/agent-harness` |
### Video
| Name | Description | Install |
|------|-------------|---------|
| **Kdenlive** | Video editing and rendering via melt | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=kdenlive/agent-harness` |
| **Shotcut** | Video editing and rendering via melt/ffmpeg | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=shotcut/agent-harness` |
### Web
| Name | Description | Install |
|------|-------------|---------|
| **Browser** | Browser automation via DOMShell MCP server. Maps Chrome's Accessibility Tree to a virtual filesystem for agent-native navigation. | `pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=browser/agent-harness` |
## Usage Pattern
All CLIs follow the same pattern:
```bash
# Interactive REPL
cli-anything-<name>
# One-shot command
cli-anything-<name> <group> <command> [options]
# JSON output for agents
cli-anything-<name> --json <group> <command>
```
## For AI Agents
1. Install the CLI you need from the table above
2. Read its full SKILL.md at the repo path shown in registry.json
3. Always use `--json` flag for machine-readable output
4. Check exit codes (0=success, non-zero=error)
## More Info
- Repository: https://github.com/HKUDS/CLI-Anything
- Web Hub: https://hkuds.github.io/CLI-Anything/
- Last Updated: 2026-03-18
+101 -15
View File
@@ -161,9 +161,18 @@
.hero-tagline strong { color: var(--text); font-weight: 600; }
/* ── Empower card ── */
.empower-card {
max-width: 560px;
.empower-row {
display: flex;
gap: 1rem;
max-width: 1100px;
margin: 0 auto 1.5rem;
align-items: stretch;
}
.empower-card {
flex: 1;
min-width: 0;
margin: 0;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
@@ -227,6 +236,35 @@
.empower-prompt strong { color: var(--text-secondary); font-weight: 600; }
.empower-prompt em { color: var(--accent); font-style: italic; }
.empower-card.empower-self::before { background: var(--green); }
.empower-card.empower-self h3 span { color: var(--green); }
.empower-self .cmd-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.4rem;
}
.empower-self .cmd-item {
display: flex;
align-items: center;
gap: 0.4rem;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 0.4rem 0.65rem;
font-size: 0.76rem;
}
.empower-self .cmd-item:hover { border-color: var(--text-tertiary); }
.empower-self .cmd-item .cmd-name {
color: var(--green);
font-family: 'JetBrains Mono', monospace;
font-weight: 500;
white-space: nowrap;
}
.empower-self .cmd-item .cmd-desc {
color: var(--text-tertiary);
font-size: 0.72rem;
}
.hero-actions {
display: flex;
justify-content: center;
@@ -547,10 +585,11 @@
font-family: 'JetBrains Mono', monospace;
font-size: 0.75rem;
color: var(--text-secondary);
white-space: nowrap;
white-space: pre-line;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
line-height: 1.6;
}
.card-copy-btn {
@@ -732,7 +771,9 @@
.filter-row { padding: 0.75rem 1.25rem 0; }
.nav { padding: 0.75rem 1.25rem; }
.footer { padding: 1.5rem 1.25rem; flex-direction: column; align-items: flex-start; }
.empower-card { margin: 0 auto 1.25rem; padding: 1.25rem; }
.empower-row { flex-direction: column; }
.empower-card { margin: 0 0 1rem; padding: 1.25rem; }
.empower-self .cmd-grid { grid-template-columns: 1fr; }
}
@media (max-width: 400px) {
@@ -776,6 +817,7 @@
<h1><span class="hero-accent">CLI-</span><span class="hero-dim">Anything</span> <span class="hero-accent">Hub</span></h1>
<p class="hero-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="empower-row">
<div class="empower-card">
<h3>Empower your agents &mdash; <span>install in one command</span></h3>
<div class="install-row">
@@ -793,6 +835,23 @@
</div>
</div>
<div class="empower-card empower-self">
<h3>Empower yourself &mdash; <span>your CLI toolkit</span></h3>
<div class="install-row">
<code>pip install cli-anything-hub</code>
<button class="copy-btn" onclick="copyCmd(this, 'pip install cli-anything-hub')">Copy</button>
</div>
<div class="cmd-grid">
<div class="cmd-item"><span class="cmd-name">cli-hub list</span><span class="cmd-desc">Browse all CLIs</span></div>
<div class="cmd-item"><span class="cmd-name">cli-hub search</span><span class="cmd-desc">Find by keyword</span></div>
<div class="cmd-item"><span class="cmd-name">cli-hub install</span><span class="cmd-desc">Install a CLI</span></div>
<div class="cmd-item"><span class="cmd-name">cli-hub info</span><span class="cmd-desc">CLI details</span></div>
<div class="cmd-item"><span class="cmd-name">cli-hub update</span><span class="cmd-desc">Update a CLI</span></div>
<div class="cmd-item"><span class="cmd-name">cli-hub uninstall</span><span class="cmd-desc">Remove a CLI</span></div>
</div>
</div>
</div>
<div class="hero-actions">
<a class="btn-primary" href="./SKILL.txt" target="_blank">Agent SKILL (SKILL.txt)</a>
<a class="btn-secondary" href="https://github.com/HKUDS/CLI-Anything" target="_blank">
@@ -1031,8 +1090,8 @@
${dateHtml}
${requiresHtml}
<div class="card-install">
<code>${esc(c.install_cmd)}</code>
<button class="card-copy-btn" onclick="copyCmd(this, '${esc(c.install_cmd)}')">Copy</button>
<code>pip install cli-anything-hub\ncli-hub install ${esc(c.name)}</code>
<button class="card-copy-btn" onclick="copyCmd(this, 'pip install cli-anything-hub &amp;&amp; cli-hub install ${esc(c.name)}')">Copy</button>
</div>
<div class="card-footer">
<div class="card-links">${sourceLink}${skillLink ? ' &middot; ' + skillLink : ''}</div>
@@ -1083,18 +1142,47 @@
const isWebdriver = navigator.webdriver === true;
let humanConfirmed = false;
// ── Send event to BOTH Umami website IDs ──
// The global `umami` object only binds to one script tag, so we also
// POST directly to the send API for the second site.
const UMAMI_SEND = 'https://cloud.umami.is/api/send';
const DUAL_WEBSITE_IDS = [
'07082d05-efd3-4f85-a7a1-b426b0e8bfaa',
'a076c661-bed1-405c-a522-813794e688b4',
];
function trackBoth(eventName, eventData) {
// POST directly to both sites — do NOT also call umami.track()
// because that would double-count on whichever site umami is bound to.
DUAL_WEBSITE_IDS.forEach(wid => {
const payload = {
type: 'event',
payload: {
website: wid,
hostname: location.hostname,
url: location.pathname,
name: eventName,
data: eventData || {},
},
};
fetch(UMAMI_SEND, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
keepalive: true,
}).catch(() => {});
});
}
// Tag the visit type via Umami custom events
if (typeof umami !== 'undefined') {
if (isKnownAgent || isWebdriver) {
umami.track('visit-agent', { ua: ua.slice(0, 200) });
}
if (isKnownAgent || isWebdriver) {
trackBoth('visit-agent', { ua: ua.slice(0, 200) });
}
// Wait for Umami to load, then tag
window.addEventListener('load', () => {
setTimeout(() => {
if (typeof umami !== 'undefined' && (isKnownAgent || isWebdriver)) {
umami.track('visit-agent', { ua: ua.slice(0, 200) });
if (isKnownAgent || isWebdriver) {
trackBoth('visit-agent', { ua: ua.slice(0, 200) });
}
}, 500);
});
@@ -1103,9 +1191,7 @@
function onHumanInteraction() {
if (humanConfirmed) return;
humanConfirmed = true;
if (typeof umami !== 'undefined') {
umami.track('visit-human');
}
trackBoth('visit-human');
// Clean up listeners
['mousemove', 'touchstart', 'scroll', 'keydown', 'click'].forEach(evt => {
document.removeEventListener(evt, onHumanInteraction);