WIP transfer root-skills and hub updates

This commit is contained in:
yuhao
2026-04-18 10:11:18 +00:00
parent 7e61df72e9
commit 99f92f0525
109 changed files with 12190 additions and 1207 deletions

View File

@@ -22,7 +22,8 @@ Fixes #<!-- issue number -->
<!-- If this PR adds a new software CLI inside the monorepo, ALL items below must be checked. -->
- [ ] `<SOFTWARE>.md` SOP document exists at `<software>/agent-harness/<SOFTWARE>.md`
- [ ] `SKILL.md` exists inside the Python package (`cli_anything/<software>/SKILL.md`)
- [ ] Canonical `SKILL.md` exists at `skills/cli-anything-<software>/SKILL.md`
- [ ] Packaged compatibility `SKILL.md` exists at `cli_anything/<software>/skills/SKILL.md`
- [ ] 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)

72
.github/scripts/sync_root_skills.py vendored Normal file
View File

@@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""Sync repo-root skills/ from harness-local SKILL.md files."""
from __future__ import annotations
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
ROOT_SKILLS_DIR = REPO_ROOT / "skills"
def _canonical_skill_id(source: Path) -> str:
rel = source.relative_to(REPO_ROOT)
software_dir = rel.parts[0]
return f"cli-anything-{software_dir.replace('_', '-')}"
def _rewrite_name_frontmatter(content: str, skill_id: str) -> str:
if not content.startswith("---\n"):
return content
parts = content.split("---\n", 2)
if len(parts) < 3:
return content
_, frontmatter, body = parts
lines = frontmatter.splitlines(keepends=True)
rewritten: list[str] = []
replaced = False
i = 0
while i < len(lines):
line = lines[i]
if not replaced and line.startswith("name:"):
rewritten.append(f'name: "{skill_id}"\n')
replaced = True
i += 1
while i < len(lines) and (lines[i].startswith(" ") or lines[i].startswith("\t")):
i += 1
continue
rewritten.append(line)
i += 1
if not replaced:
rewritten.insert(0, f'name: "{skill_id}"\n')
frontmatter = "".join(rewritten)
return f"---\n{frontmatter}---\n{body}"
def _discover_sources() -> list[Path]:
sources: list[Path] = []
sources.extend(sorted(REPO_ROOT.glob("*/agent-harness/cli_anything/*/skills/SKILL.md")))
sources.extend(sorted(REPO_ROOT.glob("*/agent-harness/cli_anything/*/SKILL.md")))
return [path for path in sources if path.is_file()]
def main() -> int:
sources = _discover_sources()
ROOT_SKILLS_DIR.mkdir(parents=True, exist_ok=True)
for source in sources:
skill_id = _canonical_skill_id(source)
target = ROOT_SKILLS_DIR / skill_id / "SKILL.md"
target.parent.mkdir(parents=True, exist_ok=True)
content = source.read_text(encoding="utf-8")
target.write_text(_rewrite_name_frontmatter(content, skill_id), encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())

60
.github/scripts/validate_root_skills.py vendored Normal file
View File

@@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""Validate that deep harness SKILL.md files are mirrored in repo-root skills/."""
from __future__ import annotations
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
def _load_sync_helpers():
namespace: dict[str, object] = {"__file__": str(REPO_ROOT / ".github" / "scripts" / "sync_root_skills.py")}
sync_script = REPO_ROOT / ".github" / "scripts" / "sync_root_skills.py"
exec(sync_script.read_text(encoding="utf-8"), namespace)
return namespace
def main() -> int:
sync = _load_sync_helpers()
discover_sources = sync["_discover_sources"]
canonical_skill_id = sync["_canonical_skill_id"]
rewrite_name_frontmatter = sync["_rewrite_name_frontmatter"]
root_skills_dir = sync["ROOT_SKILLS_DIR"]
errors: list[str] = []
for source in discover_sources():
skill_id = canonical_skill_id(source)
target = root_skills_dir / skill_id / "SKILL.md"
if not target.is_file():
errors.append(
f"Missing root skill for {source.relative_to(REPO_ROOT)}: expected {target.relative_to(REPO_ROOT)}"
)
continue
source_content = source.read_text(encoding="utf-8")
expected = rewrite_name_frontmatter(source_content, skill_id)
actual = target.read_text(encoding="utf-8")
if actual != expected:
errors.append(
f"Out-of-sync root skill for {source.relative_to(REPO_ROOT)}: {target.relative_to(REPO_ROOT)}"
)
if errors:
print("Root skills validation failed:", file=sys.stderr)
for error in errors:
print(f"- {error}", file=sys.stderr)
print(
"Run `python3 .github/scripts/sync_root_skills.py` and commit the updated root skills.",
file=sys.stderr,
)
return 1
print("Root skills validation passed.")
return 0
if __name__ == "__main__":
raise SystemExit(main())

35
.github/workflows/check-root-skills.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Check Root Skills
on:
pull_request:
paths:
- '*/agent-harness/**'
- 'skills/**'
- '.github/scripts/sync_root_skills.py'
- '.github/scripts/validate_root_skills.py'
- '.github/workflows/check-root-skills.yml'
push:
branches:
- main
paths:
- '*/agent-harness/**'
- 'skills/**'
- '.github/scripts/sync_root_skills.py'
- '.github/scripts/validate_root_skills.py'
- '.github/workflows/check-root-skills.yml'
workflow_dispatch:
jobs:
validate-root-skills:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Validate root skills mirror
run: python3 .github/scripts/validate_root_skills.py

8
.gitignore vendored
View File

@@ -52,6 +52,9 @@
!/openclaw-skill/
!/cli-hub-meta-skill/
!/cli-hub/
!/cli-hub-matrix/
!/skills/
!/skills/**
# Ignore cli-hub-skill (auto-generated, not tracked)
/cli-hub-skill/
@@ -92,7 +95,6 @@
!/renderdoc/
!/cloudcompare/
!/openscreen/
!/QGIS/
!/n8n/
!/obsidian/
@@ -165,8 +167,6 @@
/cloudcompare/.*
/openscreen/*
/openscreen/.*
/QGIS/*
/QGIS/.*
/cloudanalyzer/*
/cloudanalyzer/.*
/wiremock/*
@@ -220,7 +220,6 @@
!/wiremock/
!/wiremock/agent-harness/
!/exa/agent-harness/
!/QGIS/agent-harness/
!/n8n/agent-harness/
!/obsidian/agent-harness/
!/safari/
@@ -251,6 +250,7 @@ assets/gen_typing_gif.py
# Step 10: Allow CLI Hub registry and frontend
!/registry.json
!/public_registry.json
!/matrix_registry.json
!/docs/
/docs/*
!/docs/hub/

View File

@@ -15,7 +15,7 @@ Adding a new CLI harness is the most impactful contribution. You can either add
Place your code under `<software>/agent-harness/` and ensure the following:
1. **`<SOFTWARE>.md`** — the SOP document exists at `<software>/agent-harness/<SOFTWARE>.md` describing the harness architecture.
2. **`SKILL.md`** — the AI-discoverable skill definition exists inside the Python package at `cli_anything/<software>/skills/SKILL.md`.
2. **`SKILL.md`** — the canonical AI-discoverable skill definition exists at `skills/cli-anything-<software>/SKILL.md`, and the packaged compatibility copy exists at `cli_anything/<software>/skills/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. **`registry.json`** — add an entry for the new software (see [Registry fields](#registry-fields) below).
@@ -30,7 +30,7 @@ Requirements for standalone CLIs:
1. **Published package** — your CLI must be installable via `pip install <package-name>` (PyPI) or a `pip install git+https://...` URL.
2. **`SKILL.md`** — an AI-discoverable skill definition exists somewhere in your repo.
3. **Tests** — your repo should have its own test suite.
4. **`registry.json`** — add an entry with `source_url` pointing to your repo and `skill_md` pointing to the full URL of your SKILL.md (see [Registry fields](#registry-fields) below).
4. **`registry.json`** — add an entry with `source_url` pointing to your repo and `skill_md` pointing to the raw URL of your SKILL.md (see [Registry fields](#registry-fields) below).
### B) New Features
@@ -67,7 +67,7 @@ Include an entry in `registry.json` as part of your PR. Each field is described
| `source_url` | Yes | For standalone repos: URL to your repo (e.g. `"https://github.com/user/repo"`). For in-repo harnesses: `null` (the hub auto-links to `<name>/agent-harness/`). |
| `install_cmd` | Yes | Full pip install command. PyPI: `"pip install cli-anything-my-software"`. In-repo: `"pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=my-software/agent-harness"`. |
| `entry_point` | Yes | CLI command name (e.g. `"cli-anything-my-software"`). |
| `skill_md` | Yes | Path to SKILL.md. For standalone repos: full URL (e.g. `"https://github.com/user/repo/blob/main/.../SKILL.md"`). For in-repo: relative path (e.g. `"my-software/agent-harness/cli_anything/my_software/skills/SKILL.md"`). Set to `null` if not yet available. |
| `skill_md` | Yes | Path to canonical SKILL.md. For standalone repos: full URL (e.g. `"https://github.com/user/repo/blob/main/.../SKILL.md"`). For in-repo: relative path under the repo-root `skills/` tree (e.g. `"skills/cli-anything-my-software/SKILL.md"`). Set to `null` if not yet available. |
| `category` | Yes | One of the existing categories (check `registry.json` for examples). |
| `contributors` | Yes | Array of `{"name": "...", "url": "..."}` objects listing all contributors. |
@@ -84,7 +84,7 @@ Include an entry in `registry.json` as part of your PR. Each field is described
"source_url": 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",
"skill_md": "skills/cli-anything-my-software/SKILL.md",
"category": "category-name",
"contributors": [
{"name": "your-github-username", "url": "https://github.com/your-github-username"}

View File

@@ -502,7 +502,7 @@ cli-anything-gimp --json layer add -n "Background" --type solid --color "#1a1a2e
cli-anything-gimp
```
Each installed CLI ships with a [`SKILL.md`](#-skillmd-generation) inside the Python package (`cli_anything/<software>/skills/SKILL.md`). The REPL banner automatically displays the absolute path to this file so AI agents know exactly where to read the skill definition. No extra configuration needed — `pip install` makes the skill discoverable.
Each in-repo harness now has a canonical [`SKILL.md`](#-skillmd-generation) at `skills/cli-anything-<software>/SKILL.md`, which makes the monorepo directly discoverable via `npx skills add HKUDS/CLI-Anything --list`. Installed harness packages still ship a compatibility copy at `cli_anything/<software>/skills/SKILL.md`, and the REPL banner prefers the repo-root canonical file when present, falling back to the packaged copy otherwise.
---
@@ -538,7 +538,7 @@ The agent will browse the catalog, install whichever CLI fits the task, and use
The catalog auto-updates whenever `registry.json` changes — new community CLIs show up automatically.
> **For Claude Code users:** Copy [`cli-hub-meta-skill/SKILL.md`](cli-hub-meta-skill/SKILL.md) into your project or skills directory for the same automatic CLI discovery.
> **For Claude Code users:** Copy [`skills/cli-hub-meta-skill/SKILL.md`](skills/cli-hub-meta-skill/SKILL.md) into your project or skills directory for the same automatic CLI discovery.
---
@@ -671,7 +671,7 @@ All CLIs organized under cli_anything.* namespace — conflict-free, pip-install
### 🤖 SKILL.md Generation
Each generated CLI includes a `SKILL.md` file inside the Python package at `cli_anything/<software>/skills/SKILL.md`. This self-contained skill definition enables AI agents to discover and use the CLI through Claude Code's skill system or other agent frameworks.
Each generated CLI now has a canonical `SKILL.md` at `skills/cli-anything-<software>/SKILL.md`. This makes the current monorepo directly consumable by `npx skills`, while a packaged compatibility copy at `cli_anything/<software>/skills/SKILL.md` preserves installed-harness behavior.
**What SKILL.md provides:**
- **YAML frontmatter** with name and description for agent skill discovery
@@ -679,7 +679,7 @@ Each generated CLI includes a `SKILL.md` file inside the Python package at `cli_
- **Usage examples** for common workflows
- **Agent-specific guidance** for JSON output, error handling, and programmatic use
SKILL.md files are auto-generated during Phase 6.5 of the pipeline using `skill_generator.py`, which extracts metadata directly from the CLI's Click decorators, setup.py, and README. Because the file lives inside the package, it is installed alongside the CLI via `pip install` and auto-detected by the REPL banner agents can read the absolute path displayed at startup.
SKILL.md files are auto-generated during Phase 6.5 of the pipeline using `skill_generator.py`, which extracts metadata directly from the CLI's Click decorators, setup.py, and README. The generator now writes the canonical repo-root skill file and refreshes the package-local compatibility copy used by installed harnesses. Inside this repo, the REPL banner points agents to the canonical root skill path; after `pip install`, it falls back to the packaged copy.
---

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -97,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -143,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -151,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -165,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -47,7 +48,6 @@ _ACCENT_COLORS = {
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
"anygen": "\033[38;5;141m", # soft violet
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -58,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -90,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -98,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -106,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -144,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -152,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -166,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)
@@ -494,7 +562,6 @@ _ANSI_256_TO_HEX = {
"\033[38;5;69m": "#5f87ff", # kdenlive slate blue
"\033[38;5;75m": "#5fafff", # default sky blue
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;141m": "#af87ff", # anygen soft violet
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
}

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -97,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -143,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -151,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -165,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -97,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -143,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -151,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -165,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -6,20 +6,21 @@ Copy this file into your CLI package at:
Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("browser", version="1.0.0")
skin.print_banner()
prompt_text = skin.prompt(project_name="https://example.com", modified=False)
skin.success("Page loaded")
skin.error("Connection failed")
skin.warning("DOMShell not found")
skin.info("Navigating...")
skin.status("URL", "https://example.com")
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
skin.warning("Unsaved changes")
skin.info("Processing 24 clips...")
skin.status("Track 1", "3 clips, 00:02:30")
skin.table(headers, rows)
skin.print_goodbye()
"""
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -47,8 +48,6 @@ _ACCENT_COLORS = {
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
"ollama": "\033[38;5;255m", # white (Ollama branding)
"browser": "\033[38;5;141m", # lavender (browser harness)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -59,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -91,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -99,23 +111,53 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
software: Software name (e.g., "gimp", "shotcut", "browser").
software: Software name (e.g., "gimp", "shotcut", "blender").
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -145,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -153,10 +197,28 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
# Title: ◆ cli-anything · Browser
# Title: ◆ cli-anything · Shotcut
icon = self._c(_CYAN + _BOLD, "")
brand = self._c(_CYAN + _BOLD, "cli-anything")
dot = self._c(_DARK_GRAY, "·")
@@ -167,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)
@@ -301,7 +368,7 @@ class ReplSkin:
print(f" {self._c(self.accent + _BOLD, title)}")
print(f" {self._c(_DARK_GRAY, _H_LINE * len(title))}")
# ── Status display ───────────────────────────────────────────────
# ── Status display ───────────────────────────────────────────────
def status(self, label: str, value: str):
"""Print a key-value status line."""
@@ -495,8 +562,6 @@ _ANSI_256_TO_HEX = {
"\033[38;5;69m": "#5f87ff", # kdenlive slate blue
"\033[38;5;75m": "#5fafff", # default sky blue
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;141m": "#afafff", # browser lavender
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
"\033[38;5;255m": "#eeeeee", # ollama white
}

View File

@@ -6,20 +6,21 @@ Copy this file into your CLI package at:
Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("ollama", version="1.0.0")
skin.print_banner()
prompt_text = skin.prompt(project_name="llama3.2", modified=False)
skin.success("Model pulled")
skin.error("Connection failed")
skin.warning("No models loaded")
skin.info("Generating...")
skin.status("Model", "llama3.2:latest")
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
skin.warning("Unsaved changes")
skin.info("Processing 24 clips...")
skin.status("Track 1", "3 clips, 00:02:30")
skin.table(headers, rows)
skin.print_goodbye()
"""
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -47,7 +48,6 @@ _ACCENT_COLORS = {
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
"ollama": "\033[38;5;255m", # white (Ollama branding)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -58,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -90,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -98,23 +111,53 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
software: Software name (e.g., "gimp", "shotcut", "ollama").
software: Software name (e.g., "gimp", "shotcut", "blender").
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -144,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -152,10 +197,28 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
# Title: ◆ cli-anything · Ollama
# Title: ◆ cli-anything · Shotcut
icon = self._c(_CYAN + _BOLD, "")
brand = self._c(_CYAN + _BOLD, "cli-anything")
dot = self._c(_DARK_GRAY, "·")
@@ -166,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)
@@ -496,5 +564,4 @@ _ANSI_256_TO_HEX = {
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
"\033[38;5;255m": "#eeeeee", # ollama white
}

View File

@@ -79,7 +79,7 @@ designed for humans, without needing a display or mouse.
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("<software>", version="1.0.0")
skin.print_banner() # Branded startup box (auto-detects skills/SKILL.md)
skin.print_banner() # Branded startup box (prefers repo-root skills/, falls back to package)
pt_session = skin.create_prompt_session() # prompt_toolkit with history + styling
line = skin.get_input(pt_session, project_name="my_project", modified=True)
skin.help(commands_dict) # Formatted help listing
@@ -92,8 +92,10 @@ designed for humans, without needing a display or mouse.
skin.progress(3, 10, "...") # Progress bar
skin.print_goodbye() # Styled exit message
```
- ReplSkin auto-detects `skills/SKILL.md` inside the package directory and displays
it in the banner. AI agents can read the skill file at the displayed absolute path.
- ReplSkin prefers the repo-root canonical `skills/cli-anything-<software>/SKILL.md`
when running inside this monorepo, and falls back to the packaged
`cli_anything/<software>/skills/SKILL.md` copy when installed elsewhere.
AI agents can read the skill file at the displayed absolute path.
- Make REPL the default behavior: use `invoke_without_command=True` on the main
Click group, and invoke the `repl` command when no subcommand is given:
```python
@@ -257,8 +259,10 @@ automatically, or customize via the Jinja2 template at `templates/SKILL.md.templ
See [`guides/skill-generation.md`](guides/skill-generation.md) for the full generation
process, template customization options, and manual generation commands.
**Output Location:** SKILL.md lives inside the Python package at
`cli_anything/<software>/skills/SKILL.md` so it is installed with `pip install`.
**Output Location:** The canonical skill lives at
`skills/cli-anything-<software>/SKILL.md`. A compatibility copy is also written to
`cli_anything/<software>/skills/SKILL.md` so installed harnesses still ship a
local skill file.
**Key Principles:**
@@ -270,9 +274,9 @@ process, template customization options, and manual generation commands.
**Skill Path in CLI Banner:**
ReplSkin auto-detects `skills/SKILL.md` inside the package directory and displays
the absolute path in the startup banner. AI agents can read the skill file at the
displayed path to learn the CLI's full capabilities.
ReplSkin prefers the repo-root canonical skill path and falls back to the
packaged `skills/SKILL.md` copy. AI agents can read the displayed path to learn
the CLI's full capabilities.
**Package Data:** Ensure `setup.py` includes the skill file so it ships with pip:

View File

@@ -219,7 +219,8 @@ Generate AI-discoverable skill definition:
- Extract CLI metadata using `skill_generator.py`
- Generate SKILL.md with YAML frontmatter (name, description)
- Include command groups, examples, and agent-specific guidance
- Output to `cli_anything/<software>/skills/SKILL.md` (inside the Python package)
- Output canonical skill to `skills/cli-anything-<software>/SKILL.md`
- Refresh package-local compatibility copy at `cli_anything/<software>/skills/SKILL.md`
**Output:** SKILL.md file for AI agent discovery
@@ -237,6 +238,10 @@ Package and install:
## Output Structure
```
skills/
└── cli-anything-<software>/
└── SKILL.md # Canonical repo-root skill for npx skills discovery
<software>/
└── agent-harness/
├── <SOFTWARE>.md # Software-specific SOP
@@ -253,8 +258,6 @@ Package and install:
│ ├── session.py # Undo/redo
│ ├── export.py # Rendering/export
│ └── ... # Domain-specific modules
├── skills/ # AI-discoverable skill definition
│ └── SKILL.md # Installed with the package via package_data
├── utils/ # Utilities
│ ├── __init__.py
│ ├── repl_skin.py # Unified REPL skin (copy from plugin)
@@ -323,6 +326,7 @@ The cli-anything methodology has successfully built CLIs for:
- YAML frontmatter with name and description for triggering
- Command groups and usage examples
- Agent-specific guidance for programmatic usage
- Canonical repo-root `skills/` layout for `npx skills` discovery
- Follows skill-creator methodology
### PyPI Distribution

View File

@@ -73,7 +73,7 @@ This command implements the complete cli-anything methodology to build a product
- Extracts CLI metadata using `skill_generator.py`
- Generates SKILL.md with YAML frontmatter and Markdown body
- Includes command groups, examples, and agent-specific guidance
- Outputs to `cli_anything/<software>/skills/SKILL.md` inside the Python package
- Outputs the canonical skill to `skills/cli-anything-<software>/SKILL.md` and refreshes the packaged compatibility copy at `cli_anything/<software>/skills/SKILL.md`
- Makes the CLI discoverable and usable by AI agents
### Phase 7: PyPI Publishing and Installation
@@ -100,8 +100,6 @@ This command implements the complete cli-anything methodology to build a product
│ ├── session.py
│ ├── export.py
│ └── ...
├── skills/
│ └── SKILL.md # AI-discoverable skill definition
├── utils/ # Utilities
└── tests/
├── TEST.md # Test plan and results
@@ -109,6 +107,14 @@ This command implements the complete cli-anything methodology to build a product
└── test_full_e2e.py # E2E tests
```
Canonical repo-root skill output:
```
skills/
└── cli-anything-<software>/
└── SKILL.md
```
## Example
```bash

View File

@@ -41,7 +41,7 @@ from skill_generator import generate_skill_file
skill_path = generate_skill_file(
harness_path="/path/to/agent-harness"
)
# Default output: cli_anything/<software>/skills/SKILL.md
# Default output: skills/cli-anything-<software>/SKILL.md
```
### 2. The generator automatically extracts:
@@ -59,9 +59,14 @@ skill_path = generate_skill_file(
## Output Location
SKILL.md is generated inside the Python package so it is installed with `pip install`:
SKILL.md is generated canonically at the repo root, with a packaged compatibility
copy for installed harnesses:
```
skills/
└── cli-anything-<software>/
└── SKILL.md
<software>/
└── agent-harness/
└── cli_anything/
@@ -93,15 +98,16 @@ skill definition.
## Skill Path in CLI Banner
ReplSkin auto-detects `skills/SKILL.md` inside the package and displays the absolute
path in the startup banner. AI agents can read the file at the displayed path:
ReplSkin prefers the repo-root canonical skill file and falls back to the
packaged copy, displaying whichever absolute path is available in the startup
banner. AI agents can read the file at the displayed path:
```python
# In the REPL initialization (e.g., shotcut_cli.py)
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("<software>", version="1.0.0")
skin.print_banner() # Auto-detects and displays: ◇ Skill: /path/to/cli_anything/<software>/skills/SKILL.md
skin.print_banner() # Displays repo-root skills/cli-anything-<software>/SKILL.md when available
```
## Package Data

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -106,27 +120,44 @@ class ReplSkin:
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the package's skills/ directory if not provided.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Auto-detect skill path from package layout:
# cli_anything/<software>/utils/repl_skin.py (this file)
# cli_anything/<software>/skills/SKILL.md (target)
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
from pathlib import Path
_auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if _auto.is_file():
skill_path = str(_auto)
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -156,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -164,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -178,19 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
# Skill path for agent discovery
skill_line = None
if self.skill_path:
skill_icon = self._c(_MAGENTA, "")
skill_label = self._c(_DARK_GRAY, " Skill:")
skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
if skill_line:
print(_box_line(skill_line))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -22,6 +22,12 @@ def _format_display_name(name: str) -> str:
return name.replace("_", " ").replace("-", " ").title()
def _canonical_skill_name(harness_path: Path, software_name: str) -> str:
"""Return the repo-root canonical skill id for a harness."""
software_dir = harness_path.parent.name if harness_path.parent.name else software_name
return f"cli-anything-{software_dir.replace('_', '-')}"
@dataclass
class CommandInfo:
"""Information about a CLI command."""
@@ -114,13 +120,8 @@ def extract_cli_metadata(harness_path: str) -> SkillMetadata:
examples = generate_examples(software_name, command_groups)
# Build skill name and description
skill_name = f"cli-anything-{software_name}"
if skill_intro:
intro_snippet = skill_intro[:100]
suffix = "..." if len(skill_intro) > 100 else ""
skill_description = f"Command-line interface for {_format_display_name(software_name)} - {intro_snippet}{suffix}"
else:
skill_description = f"Command-line interface for {_format_display_name(software_name)}"
skill_name = _canonical_skill_name(harness_path, software_name)
skill_description = f"Command-line interface for {_format_display_name(software_name)} - {skill_intro[:100]}..."
return SkillMetadata(
skill_name=skill_name,
@@ -164,16 +165,14 @@ def extract_system_package(content: str) -> Optional[str]:
patterns = [
r"`apt install ([\w\-]+)`",
r"`brew install ([\w\-]+)`",
r"`apt-get install ([\w\-]+)`",
r"apt-get install ([\w\-]+)",
]
for pattern in patterns:
match = re.search(pattern, content)
if match:
package = match.group(1)
if "apt-get" in pattern:
return f"apt-get install {package}"
elif "apt" in pattern:
if "apt" in pattern:
return f"apt install {package}"
elif "brew" in pattern:
return f"brew install {package}"
@@ -472,7 +471,8 @@ def generate_skill_file(harness_path: str, output_path: Optional[str] = None,
Args:
harness_path: Path to the agent-harness directory
output_path: Optional output path for SKILL.md (default: cli_anything/<software>/skills/SKILL.md)
output_path: Optional output path for SKILL.md
(default: skills/cli-anything-<software>/SKILL.md)
template_path: Optional path to custom Jinja2 template
Returns:
@@ -485,10 +485,11 @@ def generate_skill_file(harness_path: str, output_path: Optional[str] = None,
content = generate_skill_md(metadata, template_path)
# Determine output path
harness_path_obj = Path(harness_path)
compatibility_path = harness_path_obj / "cli_anything" / metadata.software_name / "skills" / "SKILL.md"
if output_path is None:
# Default to skills/ directory under harness_path
harness_path_obj = Path(harness_path)
output_path = harness_path_obj / "cli_anything" / metadata.software_name / "skills" / "SKILL.md"
repo_root = harness_path_obj.parent.parent
output_path = repo_root / "skills" / metadata.skill_name / "SKILL.md"
else:
output_path = Path(output_path)
@@ -497,6 +498,9 @@ def generate_skill_file(harness_path: str, output_path: Optional[str] = None,
# Write file
output_path.write_text(content, encoding="utf-8")
if compatibility_path != output_path:
compatibility_path.parent.mkdir(parents=True, exist_ok=True)
compatibility_path.write_text(content, encoding="utf-8")
return str(output_path)
@@ -514,7 +518,7 @@ if __name__ == "__main__":
)
parser.add_argument(
"-o", "--output",
help="Output path for SKILL.md (default: cli_anything/<software>/skills/SKILL.md)",
help="Output path for SKILL.md (default: skills/cli-anything-<software>/SKILL.md)",
default=None
)
parser.add_argument(

View File

@@ -12,8 +12,11 @@ import requests
from cli_hub import __version__
from cli_hub.registry import fetch_registry, fetch_all_clis, get_cli, search_clis, list_categories
from cli_hub.matrix import fetch_matrix_registry, fetch_all_matrices, get_matrix, search_matrices
from cli_hub.matrix_skill import resolve_local_skill_path, render_matrix_skill_file, _render_stage_tooling, _render_discovery_section
from cli_hub.installer import (
install_cli,
install_matrix,
uninstall_cli,
get_installed,
_load_installed,
@@ -40,7 +43,7 @@ SAMPLE_REGISTRY = {
"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,
"skill_md": "skills/cli-anything-gimp/SKILL.md",
"category": "image",
"contributor": "test-user",
"contributor_url": "https://github.com/test-user",
@@ -76,6 +79,39 @@ SAMPLE_REGISTRY = {
],
}
SAMPLE_MATRIX_REGISTRY = {
"meta": {"repo": "https://github.com/HKUDS/CLI-Anything", "description": "test matrices"},
"matrices": [
{
"name": "video-creation",
"display_name": "Video Creation & Editing",
"description": "Curated video workflow matrix",
"category": "video",
"matrix": "cli-matrix",
"matrix_id": "V1",
"skill_md": "cli-hub-matrix/video-creation/SKILL.md",
"clis": ["gimp", "blender", "audacity"],
"stages": [
{
"name": "Thumbnail",
"clis": ["gimp"],
"goal": "Create a thumbnail image",
"alternatives": {"python": ["Pillow"], "native": ["ImageMagick convert"]},
"skill_search_hints": ["thumbnail", "image editing"],
},
{"name": "3D", "clis": ["blender"]},
{
"name": "Audio",
"clis": ["audacity"],
"goal": "Edit and process audio",
"alternatives": {"python": ["pydub"], "native": ["sox"]},
"skill_search_hints": ["audio editing"],
},
],
}
],
}
# ─── Registry tests ───────────────────────────────────────────────────
@@ -153,6 +189,137 @@ class TestRegistry:
assert cats == ["3d", "audio", "image"]
class TestMatrixRegistry:
"""Tests for matrix.py — fetch, cache, search, and lookup."""
@patch("cli_hub.matrix.requests.get")
@patch("cli_hub.matrix.MATRIX_CACHE_FILE", Path(tempfile.mktemp()))
def test_fetch_matrix_registry_from_remote(self, mock_get):
mock_resp = MagicMock()
mock_resp.json.return_value = SAMPLE_MATRIX_REGISTRY
mock_resp.raise_for_status = MagicMock()
mock_get.return_value = mock_resp
result = fetch_matrix_registry(force_refresh=True)
assert result["matrices"][0]["name"] == "video-creation"
mock_get.assert_called_once()
@patch("cli_hub.matrix.fetch_all_matrices", return_value=SAMPLE_MATRIX_REGISTRY["matrices"])
def test_get_matrix_found(self, mock_fetch):
matrix_item = get_matrix("video-creation")
assert matrix_item is not None
assert matrix_item["display_name"] == "Video Creation & Editing"
@patch("cli_hub.matrix.fetch_all_matrices", return_value=SAMPLE_MATRIX_REGISTRY["matrices"])
def test_search_matrices_matches_description(self, mock_fetch):
results = search_matrices("video")
assert len(results) == 1
assert results[0]["name"] == "video-creation"
class TestMatrixSkill:
"""Tests for matrix_skill.py — local skill resolution and rendering."""
@patch("cli_hub.matrix_skill.metadata.distribution")
def test_resolve_local_skill_path_from_distribution(self, mock_distribution, tmp_path):
class FakeDist:
files = [Path("cli_anything/audacity/skills/SKILL.md")]
def locate_file(self, file):
return tmp_path / file
mock_distribution.return_value = FakeDist()
cli = {"name": "audacity", "_source": "harness"}
resolved = resolve_local_skill_path(cli)
assert resolved == str((tmp_path / "cli_anything/audacity/skills/SKILL.md").resolve())
@patch("cli_hub.matrix_skill.MATRIX_SKILL_DIR", Path(tempfile.mkdtemp()))
@patch("cli_hub.matrix_skill.resolve_local_skill_path")
@patch("cli_hub.matrix_skill.get_cli")
def test_render_matrix_skill_file_injects_paths(self, mock_get_cli, mock_resolve, tmp_dir=None):
mock_get_cli.side_effect = lambda name: next((c for c in SAMPLE_REGISTRY["clis"] if c["name"] == name), None)
mock_resolve.side_effect = lambda cli: f"/tmp/{cli['name']}/skills/SKILL.md" if cli["name"] != "blender" else None
rendered = render_matrix_skill_file(SAMPLE_MATRIX_REGISTRY["matrices"][0], installed={"gimp": {}, "audacity": {}})
content = Path(rendered).read_text()
assert "## Installed CLI Skills" in content
assert "/tmp/gimp/skills/SKILL.md" in content
assert "skills/cli-anything-gimp/SKILL.md" in content
assert "not installed" in content
class TestMultiApproachRendering:
"""Tests for multi-approach stage rendering in matrix_skill.py."""
def test_render_stage_tooling_includes_goals(self):
matrix_item = SAMPLE_MATRIX_REGISTRY["matrices"][0]
result = _render_stage_tooling(matrix_item, installed={"gimp": {}})
assert "## Stage Tooling Overview" in result
assert "Create a thumbnail image" in result
assert "Edit and process audio" in result
def test_render_stage_tooling_includes_alternatives(self):
matrix_item = SAMPLE_MATRIX_REGISTRY["matrices"][0]
result = _render_stage_tooling(matrix_item, installed={})
assert "Pillow" in result
assert "pydub" in result
assert "sox" in result
assert "ImageMagick convert" in result
def test_render_stage_tooling_shows_install_status(self):
matrix_item = SAMPLE_MATRIX_REGISTRY["matrices"][0]
result = _render_stage_tooling(matrix_item, installed={"gimp": {}})
assert "`gimp` (installed)" in result
assert "`audacity` (not installed)" in result
def test_render_stage_tooling_includes_skill_search_hints(self):
matrix_item = SAMPLE_MATRIX_REGISTRY["matrices"][0]
result = _render_stage_tooling(matrix_item, installed={})
assert 'npx skills search "thumbnail"' in result
assert 'npx skills search "audio editing"' in result
def test_render_stage_tooling_backward_compat_no_goal(self):
"""Stages without 'goal' field are skipped gracefully."""
matrix_no_goals = {
"name": "test",
"stages": [
{"name": "Stage1", "clis": ["foo"]},
],
}
result = _render_stage_tooling(matrix_no_goals, installed={})
assert result == ""
def test_render_discovery_section(self):
matrix_item = SAMPLE_MATRIX_REGISTRY["matrices"][0]
result = _render_discovery_section(matrix_item)
assert "## Skill Discovery Commands" in result
assert 'npx skills search "thumbnail"' in result
assert 'npx skills search "audio editing"' in result
assert "cli-hub search thumbnail" in result
assert "cli-hub search audio" in result
def test_render_discovery_section_empty_when_no_hints(self):
matrix_no_hints = {
"name": "test",
"stages": [{"name": "S1", "clis": ["foo"]}],
}
result = _render_discovery_section(matrix_no_hints)
assert result == ""
@patch("cli_hub.matrix_skill.MATRIX_SKILL_DIR", Path(tempfile.mkdtemp()))
@patch("cli_hub.matrix_skill.resolve_local_skill_path")
@patch("cli_hub.matrix_skill.get_cli")
def test_render_matrix_skill_file_includes_stage_tooling(self, mock_get_cli, mock_resolve):
mock_get_cli.side_effect = lambda name: next((c for c in SAMPLE_REGISTRY["clis"] if c["name"] == name), None)
mock_resolve.return_value = None
rendered = render_matrix_skill_file(SAMPLE_MATRIX_REGISTRY["matrices"][0], installed={"gimp": {}})
content = Path(rendered).read_text()
assert "## Stage Tooling Overview" in content
assert "## Skill Discovery Commands" in content
assert "Create a thumbnail image" in content
# ─── Installer tests ──────────────────────────────────────────────────
@@ -485,6 +652,26 @@ class TestScriptStrategy:
assert data["jimeng"]["strategy"] == "command"
assert data["jimeng"]["package_manager"] == "script"
@patch("cli_hub.installer.get_matrix", return_value=SAMPLE_MATRIX_REGISTRY["matrices"][0])
@patch("cli_hub.installer.get_cli", side_effect=lambda name: next((c for c in SAMPLE_REGISTRY["clis"] if c["name"] == name), None))
@patch("cli_hub.installer.install_cli", side_effect=[(True, "Installed Blender"), (False, "Install failed")])
@patch("cli_hub.installer.get_installed", return_value={"gimp": {"version": "1.0.0"}})
@patch("cli_hub.installer.render_matrix_skill_file", return_value=Path("/tmp/video-creation.SKILL.md"))
def test_install_matrix_reports_installed_skipped_and_failed(self, mock_render, mock_installed, mock_install_cli, mock_get_cli, mock_get_matrix):
success, payload = install_matrix("video-creation")
assert not success
assert payload["summary"] == {"total": 3, "installed": 1, "skipped": 1, "failed": 1}
assert payload["results"][0]["status"] == "skipped"
assert payload["results"][1]["status"] == "installed"
assert payload["results"][2]["status"] == "failed"
assert payload["rendered_skill_path"] == "/tmp/video-creation.SKILL.md"
@patch("cli_hub.installer.get_matrix", return_value=None)
def test_install_matrix_not_found(self, mock_get_matrix):
success, payload = install_matrix("missing-matrix")
assert not success
assert "not found" in payload["error"]
# ─── Analytics tests ──────────────────────────────────────────────────
@@ -644,6 +831,81 @@ class TestCLI:
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_all_matrices", return_value=SAMPLE_MATRIX_REGISTRY["matrices"])
@patch("cli_hub.cli.get_installed", return_value={"gimp": {"version": "1.0.0"}})
def test_matrix_list_command(self, mock_installed, mock_fetch_matrices, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["matrix", "list"])
assert "video-creation" in result.output
assert "1/3" 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.search_matrices", return_value=SAMPLE_MATRIX_REGISTRY["matrices"])
@patch("cli_hub.cli.get_installed", return_value={"gimp": {"version": "1.0.0"}})
def test_matrix_search_command(self, mock_installed, mock_search, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["matrix", "search", "video"])
assert "video-creation" in result.output
assert "1/3" 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.search_matrices", return_value=[])
def test_matrix_search_no_results(self, mock_search, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["matrix", "search", "nonexistent"])
assert "No matrices matching" 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_matrix", return_value=SAMPLE_MATRIX_REGISTRY["matrices"][0])
@patch("cli_hub.cli.get_installed", return_value={"gimp": {"version": "1.0.0"}})
@patch("cli_hub.cli.get_rendered_matrix_skill_path", return_value=Path("/tmp/video-creation.SKILL.md"))
@patch("pathlib.Path.exists", return_value=True)
def test_matrix_info_command(self, mock_exists, mock_rendered, mock_installed, mock_get_matrix, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["matrix", "info", "video-creation"])
assert "Video Creation & Editing" in result.output
assert "cli-hub matrix install video-creation" in result.output
assert "cli-hub-matrix/video-creation/SKILL.md" in result.output
assert "Local skill: /tmp/video-creation.SKILL.md" 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.install_matrix", return_value=(False, {
"matrix": SAMPLE_MATRIX_REGISTRY["matrices"][0],
"results": [
{"name": "gimp", "status": "skipped", "message": "Already installed"},
{"name": "blender", "status": "installed", "message": "Installed Blender"},
{"name": "audacity", "status": "failed", "message": "Install failed"},
],
"summary": {"installed": 1, "skipped": 1, "failed": 1},
"rendered_skill_path": "/tmp/video-creation.SKILL.md",
}))
def test_matrix_install_command_partial_failure(self, mock_install_matrix, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["matrix", "install", "video-creation"])
assert result.exit_code == 1
assert "Summary: 1 installed, 1 skipped, 1 failed" in result.output
assert "Matrix skill: cli-hub-matrix/video-creation/SKILL.md" in result.output
assert "Local matrix skill: /tmp/video-creation.SKILL.md" 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.install_matrix", return_value=(False, {"error": "Matrix 'missing' not found."}))
def test_matrix_install_command_not_found(self, mock_install_matrix, mock_detect, mock_visit, mock_first_run):
result = self.runner.invoke(main, ["matrix", "install", "missing"])
assert result.exit_code == 1
assert "not found" 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)
@@ -686,6 +948,7 @@ class TestCLI:
result = self.runner.invoke(main, ["info", "gimp"])
assert "GIMP" in result.output
assert "image" in result.output
assert "Skill:" in result.output
assert result.exit_code == 0
@patch("cli_hub.cli.track_first_run")

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -106,27 +120,44 @@ class ReplSkin:
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the package's skills/ directory if not provided.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Auto-detect skill path from package layout:
# cli_anything/<software>/utils/repl_skin.py (this file)
# cli_anything/<software>/skills/SKILL.md (target)
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
from pathlib import Path
_auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if _auto.is_file():
skill_path = str(_auto)
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -156,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -164,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -178,19 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
# Skill path for agent discovery
skill_line = None
if self.skill_path:
skill_icon = self._c(_MAGENTA, "")
skill_label = self._c(_DARK_GRAY, " Skill:")
skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
if skill_line:
print(_box_line(skill_line))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -106,27 +120,44 @@ class ReplSkin:
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the package's skills/ directory if not provided.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Auto-detect skill path from package layout:
# cli_anything/<software>/utils/repl_skin.py (this file)
# cli_anything/<software>/skills/SKILL.md (target)
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
from pathlib import Path
_auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if _auto.is_file():
skill_path = str(_auto)
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -156,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -164,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -178,19 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
# Skill path for agent discovery
skill_line = None
if self.skill_path:
skill_icon = self._c(_MAGENTA, "")
skill_label = self._c(_DARK_GRAY, " Skill:")
skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
if skill_line:
print(_box_line(skill_line))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -106,27 +120,44 @@ class ReplSkin:
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the package's skills/ directory if not provided.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Auto-detect skill path from package layout:
# cli_anything/<software>/utils/repl_skin.py (this file)
# cli_anything/<software>/skills/SKILL.md (target)
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
from pathlib import Path
_auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if _auto.is_file():
skill_path = str(_auto)
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -156,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -164,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -178,19 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
# Skill path for agent discovery
skill_line = None
if self.skill_path:
skill_icon = self._c(_MAGENTA, "")
skill_label = self._c(_DARK_GRAY, " Skill:")
skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
if skill_line:
print(_box_line(skill_line))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -258,21 +258,6 @@
flex-shrink: 0;
}
.nav-link-stars {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2.9rem;
padding: 0.14rem 0.52rem;
border-radius: 999px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.1);
color: var(--text);
font-size: 0.73rem;
font-variant-numeric: tabular-nums;
line-height: 1.1;
}
.theme-toggle {
width: 42px;
padding: 0;
@@ -1357,6 +1342,11 @@
rgba(0,0,0,0.14);
}
.card-command-stack {
display: grid;
gap: 0.75rem;
}
.card--public .card-command-block {
background:
linear-gradient(180deg, rgba(241, 179, 107, 0.08), rgba(255,255,255,0.02)),
@@ -1417,7 +1407,6 @@
}
.card-links a,
.card-link-btn,
.card-contributor a {
display: inline-flex;
align-items: center;
@@ -1433,86 +1422,12 @@
}
.card-links a:hover,
.card-link-btn:hover,
.card-contributor a:hover {
color: var(--text);
border-color: var(--border-strong);
background: rgba(255,255,255,0.08);
}
.card-link-btn {
cursor: pointer;
font: inherit;
}
.skill-popover {
position: relative;
display: inline-flex;
align-items: center;
}
.skill-popover-panel {
position: absolute;
left: 0;
top: calc(100% + 0.5rem);
width: min(18.5rem, calc(100vw - 3rem));
padding: 0.78rem;
border-radius: 18px;
border: 1px solid rgba(130, 149, 180, 0.34);
background: rgba(251, 247, 239, 0.98);
color: #1a2433;
box-shadow: 0 22px 48px rgba(8, 16, 28, 0.22);
opacity: 0;
transform: translateY(-4px);
pointer-events: none;
transition: opacity 0.18s ease, transform 0.18s ease;
z-index: 12;
}
.skill-popover.is-open .skill-popover-panel {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.skill-popover-label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.66rem;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
color: #67758c;
}
.skill-popover-panel code {
display: block;
padding: 0.62rem 0.68rem;
border-radius: 14px;
border: 1px solid rgba(130, 149, 180, 0.25);
background: rgba(226, 232, 240, 0.5);
color: #182130;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
line-height: 1.6;
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-word;
}
.skill-popover-copy {
margin-top: 0.62rem;
background: rgba(255,255,255,0.7);
color: #314155;
border-color: rgba(130, 149, 180, 0.3);
}
.skill-popover-copy:hover {
color: #182130;
border-color: rgba(49, 65, 85, 0.3);
background: rgba(255,255,255,0.88);
}
.community-badge {
display: inline-flex;
align-items: center;
@@ -1575,13 +1490,15 @@
}
.footer-copy a,
.footer-brand a {
.footer-brand a,
.footer-analytics-link {
color: var(--text);
text-decoration: none;
}
.footer-copy a:hover,
.footer-brand a:hover {
.footer-brand a:hover,
.footer-analytics-link:hover {
text-decoration: underline;
}
@@ -1846,13 +1763,12 @@
</span>
</a>
<div class="nav-links">
<a class="nav-link" href="https://reeceyang.sgp1.cdn.digitaloceanspaces.com/SKILL.md" target="_blank">
<a class="nav-link" href="https://github.com/HKUDS/CLI-Anything/blob/main/skills/cli-hub-meta-skill/SKILL.md" target="_blank">
SKILL.md
</a>
<a class="nav-link" href="https://github.com/HKUDS/CLI-Anything" target="_blank">
<svg viewBox="0 0 98 96" 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>
GitHub
<span class="nav-link-stars" data-github-stars aria-label="GitHub star count">--</span>
</a>
<a class="nav-link" href="https://github.com/HKUDS/CLI-Anything/blob/main/CONTRIBUTING.md" target="_blank">Contribute</a>
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle theme">
@@ -1868,7 +1784,7 @@
<h1><em>CLI</em> tools for <span>software, APIs, and autonomous agents.</span></h1>
<p class="hero-lead"><strong>Any software. Any codebase. Any Web API.</strong> Install an orchestration hub that can discover, explain, install, update, and remove CLIs across first-party harnesses and public ecosystems.</p>
<div class="hero-actions">
<a class="btn-primary" href="https://reeceyang.sgp1.cdn.digitaloceanspaces.com/SKILL.md" target="_blank">Get Agent SKILL</a>
<a class="btn-primary" href="https://github.com/HKUDS/CLI-Anything/blob/main/skills/cli-hub-meta-skill/SKILL.md" target="_blank">Get Agent SKILL</a>
<a class="btn-secondary" href="https://github.com/HKUDS/CLI-Anything" target="_blank">
<svg viewBox="0 0 98 96" 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
@@ -1899,6 +1815,11 @@
<section class="rail-panel">
<h3>Empower your agent</h3>
<p>Install the meta-skill once, then let the agent choose the right CLI from the registry.</p>
<div class="install-row">
<span class="chip-label">npx skills</span>
<code>npx skills add HKUDS/CLI-Anything --skill cli-hub-meta-skill -g -y</code>
<button class="copy-btn" onclick="copyCmd(this, 'npx skills add HKUDS/CLI-Anything --skill cli-hub-meta-skill -g -y')">Copy</button>
</div>
<div class="install-row">
<span class="chip-label">OpenClaw</span>
<code>openclaw skills install cli-anything-hub</code>
@@ -2081,7 +2002,7 @@
</div>
<div class="footer-stats">
<div class="footer-label">Traffic Snapshot</div>
<div class="footer-analytics-link" aria-label="Traffic snapshot">
<a class="footer-analytics-link" href="https://cloud.umami.is/share/mnilRgKH0oo7SJTs" target="_blank">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
<span class="stat-dot dot-total"></span>
<span id="stat-total">&mdash;</span> visits
@@ -2091,7 +2012,7 @@
<span class="analytics-sep">&middot;</span>
<span class="stat-dot dot-agent"></span>
<span id="stat-agent">&mdash;</span> AI agent
</div>
</a>
</div>
</footer>
</div>
@@ -2123,7 +2044,7 @@
});
const REPO = 'https://github.com/HKUDS/CLI-Anything';
const GITHUB_REPO_API = 'https://api.github.com/repos/HKUDS/CLI-Anything';
const REPO_SKILLS_SOURCE = 'HKUDS/CLI-Anything';
const REGISTRY_URLS = [
'../../registry.json',
'https://raw.githubusercontent.com/HKUDS/CLI-Anything/main/registry.json'
@@ -2164,34 +2085,6 @@
return null;
}
function formatCompactCount(value) {
return new Intl.NumberFormat('en', {
notation: 'compact',
maximumFractionDigits: value >= 10000 ? 0 : 1,
}).format(value);
}
async function loadGitHubStars() {
try {
const resp = await fetch(GITHUB_REPO_API, {
headers: { Accept: 'application/vnd.github+json' }
});
if (!resp.ok) return;
const repo = await resp.json();
const stars = repo.stargazers_count;
if (typeof stars !== 'number') return;
document.querySelectorAll('[data-github-stars]').forEach((el) => {
el.textContent = formatCompactCount(stars);
el.setAttribute('aria-label', stars.toLocaleString('en-US') + ' GitHub stars');
el.title = stars.toLocaleString('en-US') + ' GitHub stars';
});
} catch (_) {
// Keep fallback placeholder when GitHub API is unavailable.
}
}
async function loadRegistries() {
try {
const [harnessData, publicData, dates] = await Promise.all([
@@ -2405,38 +2298,48 @@
grid.innerHTML = (deck === 'harness' ? filtered.map(renderHarnessCard) : filtered.map(renderPublicCard)).join('');
}
function isWebUrl(value) {
return /^https?:\/\//i.test(value || '');
function repoSkillId(skillPath) {
if (!skillPath || skillPath.startsWith('http')) return '';
const match = skillPath.match(/^skills\/([^/]+)\/SKILL\.md$/);
return match ? match[1] : '';
}
function isRepoSkillPath(value) {
return !/\s/.test(value || '') && /(^\.{0,2}\/|\/|\.md$|\.txt$)/i.test(value || '');
function normalizeNpxSkillsCmd(cmd) {
if (!cmd) return '';
const trimmed = cmd.trim();
if (!trimmed.startsWith('npx skills add ')) return '';
return trimmed
.replace(/\s+-g\b/g, '')
.replace(/\s+-y\b/g, '')
.trim() + ' -g -y';
}
function renderSkillAction(value) {
if (!value) return '';
if (isWebUrl(value)) {
return '<a href="' + esc(value) + '" target="_blank">Skill</a>';
}
if (isRepoSkillPath(value)) {
return '<a href="' + REPO + '/blob/main/' + esc(value) + '" target="_blank">Skill</a>';
}
return '' +
'<span class="skill-popover">' +
'<button class="card-link-btn skill-trigger" type="button" aria-expanded="false" data-skill-value="' + escAttr(value) + '">Skill</button>' +
'<span class="skill-popover-panel">' +
'<span class="skill-popover-label">Skill install</span>' +
'<code>' + esc(value) + '</code>' +
'<button class="card-copy-btn skill-popover-copy" type="button" data-copy-value="' + escAttr(value) + '">Copy</button>' +
'</span>' +
'</span>';
function harnessSkillCmd(c) {
const skillId = repoSkillId(c.skill_md);
return skillId ? 'npx skills add ' + REPO_SKILLS_SOURCE + ' --skill ' + skillId + ' -g -y' : '';
}
function publicSkillCmd(c) {
return normalizeNpxSkillsCmd(c.skill_md);
}
function renderCommandStack(steps, sourceLabel) {
return '<div class="card-command-stack">' + steps.map((step) =>
'<div class="card-command-block">' +
'<div class="card-command-label"><span>' + esc(step.label) + '</span><span>' + esc(step.badge || sourceLabel) + '</span></div>' +
'<div class="card-command">' + esc(step.cmd) + '</div>' +
'<div class="card-command-actions"><button class="card-copy-btn" onclick="copyCmd(this, \'' + esc(step.cmd) + '\')">Copy</button></div>' +
'</div>'
).join('') + '</div>';
}
function renderHarnessCard(c) {
const requiresHtml = c.requires
? '<div class="card-requires"><strong>Requires</strong> ' + esc(c.requires) + '</div>'
: '';
const skillLink = renderSkillAction(c.skill_md);
const skillLink = c.skill_md
? '<a href="' + (c.skill_md.startsWith('http') ? c.skill_md : (REPO + '/blob/main/' + c.skill_md)) + '" target="_blank">Skill</a>'
: '';
const sourceLink = c.source_url
? '<a href="' + esc(c.source_url) + '" target="_blank">Source</a>'
: '<a href="' + REPO + '/tree/main/' + c.name + '/agent-harness" target="_blank">Source</a>';
@@ -2447,12 +2350,18 @@
const isTeam = contributors.length === 1 && contributors[0].name === 'CLI-Anything-Team';
const contributorHtml = contributors.length
? '<div class="card-contributor">' +
contributors.map((ct) => '<a href="' + esc(ct.url) + '" target="_blank">' + esc(ct.name) + '</a>').join(', ') +
contributors.map((ct) => '<a href="' + esc(ct.url) + '" target="_blank">' + esc(ct.name) + '</a>').join('') +
(isTeam ? '' : '<span class="community-badge">Community</span>') +
'</div>'
: '';
const dateHtml = c.last_modified ? '<div class="card-date">Updated ' + esc(c.last_modified) + '</div>' : '';
const linksHtml = sourceLink + skillLink;
const linksHtml = sourceLink + (skillLink ? skillLink : '');
const installSteps = [
{ label: 'Step 1 · Install CLI', cmd: 'cli-hub install ' + c.name, badge: 'CLI-Hub' }
];
const skillCmd = harnessSkillCmd(c);
if (skillCmd) installSteps.push({ label: 'Step 2 · Install Skill', cmd: skillCmd, badge: 'npx skills' });
const installBlock = renderCommandStack(installSteps, 'CLI-Hub');
return `
<article class="card card--harness">
@@ -2469,16 +2378,7 @@
${dateHtml}
${requiresHtml}
</div>
<div class="card-command-block">
<div class="card-command-label">
<span>Install via CLI-Hub</span>
<span>PyPI</span>
</div>
<div class="card-command">pip install cli-anything-hub\ncli-hub install ${esc(c.name)}</div>
<div class="card-command-actions">
<button class="card-copy-btn" onclick="copyCmd(this, 'pip install cli-anything-hub && cli-hub install ${esc(c.name)}')">Copy</button>
</div>
</div>
${installBlock}
<div class="card-footer">
<div class="card-links">${linksHtml}</div>
${contributorHtml}
@@ -2505,12 +2405,11 @@
const titleHtml = c.homepage
? '<a href="' + esc(c.homepage) + '" target="_blank">' + esc(c.display_name) + EXTERNAL_LINK_SVG + '</a>'
: esc(c.display_name);
const skillLink = renderSkillAction(c.skill_md);
const sourceLink = c.source_url ? '<a href="' + esc(c.source_url) + '" target="_blank">Source</a>' : '';
const contributors = c.contributors || [];
const contributorHtml = contributors.length
? '<div class="card-contributor">' +
contributors.map((ct) => '<a href="' + esc(ct.url) + '" target="_blank">' + esc(ct.name) + '</a>').join(', ') +
contributors.map((ct) => '<a href="' + esc(ct.url) + '" target="_blank">' + esc(ct.name) + '</a>').join('') +
'</div>'
: '';
const directCmd = c.install_cmd || publicDirectCmd(c);
@@ -2518,13 +2417,14 @@
? '<div class="card-npx">Direct command: <code>' + esc(directCmd) + '</code></div>'
: '';
const installNotesHtml = c.install_notes ? '<div class="card-npx">' + esc(c.install_notes) + '</div>' : '';
const installSteps = isBundled
? []
: [{ label: 'Step 1 · Install CLI', cmd: 'cli-hub install ' + c.name, badge: 'CLI-Hub' }];
const skillCmd = publicSkillCmd(c);
if (skillCmd) installSteps.push({ label: 'Step 2 · Install Skill', cmd: skillCmd, badge: 'npx skills' });
const installBlock = isBundled
? '<div class="card-install-note">Bundled with the upstream application. Install or update the parent app and enable its CLI integration.</div>'
: '<div class="card-command-block">' +
'<div class="card-command-label"><span>Install via CLI-Hub</span><span>Source aware</span></div>' +
'<div class="card-command">pip install cli-anything-hub\ncli-hub install ' + esc(c.name) + '</div>' +
'<div class="card-command-actions"><button class="card-copy-btn" onclick="copyCmd(this, \'pip install cli-anything-hub && cli-hub install ' + esc(c.name) + '\')">Copy</button></div>' +
'</div>';
: renderCommandStack(installSteps, 'Source aware');
return `
<article class="card card--public">
@@ -2544,7 +2444,7 @@
</div>
${installBlock}
<div class="card-footer">
<div class="card-links">${sourceLink}${skillLink}</div>
<div class="card-links">${sourceLink}</div>
${contributorHtml}
</div>
</article>`;
@@ -2560,20 +2460,6 @@
});
}
function toggleSkillPopover(trigger) {
const popover = trigger.closest('.skill-popover');
if (!popover) return;
const shouldOpen = !popover.classList.contains('is-open');
document.querySelectorAll('.skill-popover.is-open').forEach((node) => {
node.classList.remove('is-open');
const btn = node.querySelector('.skill-trigger');
if (btn) btn.setAttribute('aria-expanded', 'false');
});
if (!shouldOpen) return;
popover.classList.add('is-open');
trigger.setAttribute('aria-expanded', 'true');
}
function esc(s) {
if (!s) return '';
const d = document.createElement('div');
@@ -2581,48 +2467,6 @@
return d.innerHTML;
}
function escAttr(s) {
if (!s) return '';
return String(s)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
document.addEventListener('click', (e) => {
const copyBtn = e.target.closest('.skill-popover-copy');
if (copyBtn) {
e.preventDefault();
e.stopPropagation();
copyCmd(copyBtn, copyBtn.dataset.copyValue);
return;
}
const trigger = e.target.closest('.skill-trigger');
if (trigger) {
e.preventDefault();
e.stopPropagation();
toggleSkillPopover(trigger);
return;
}
document.querySelectorAll('.skill-popover.is-open').forEach((node) => {
node.classList.remove('is-open');
const btn = node.querySelector('.skill-trigger');
if (btn) btn.setAttribute('aria-expanded', 'false');
});
});
document.addEventListener('keydown', (e) => {
if (e.key !== 'Escape') return;
document.querySelectorAll('.skill-popover.is-open').forEach((node) => {
node.classList.remove('is-open');
const btn = node.querySelector('.skill-trigger');
if (btn) btn.setAttribute('aria-expanded', 'false');
});
});
document.getElementById('search-harness').addEventListener('input', (e) => {
deckState.harness.query = e.target.value;
renderDeck('harness');
@@ -2711,47 +2555,48 @@
const UMAMI_API = 'https://api.umami.is/v1';
const UMAMI_KEY = 'api_idAebMhzn6z0hsUQT7BSxRuCK2GUZvRY';
const HKUDS_WEBSITE_ID = '07082d05-efd3-4f85-a7a1-b426b0e8bfaa';
const CC_WEBSITE_ID = 'a076c661-bed1-405c-a522-813794e688b4';
const WEBSITE_IDS = [
'07082d05-efd3-4f85-a7a1-b426b0e8bfaa',
'a076c661-bed1-405c-a522-813794e688b4',
];
const headers = { Accept: 'application/json', 'x-umami-api-key': UMAMI_KEY };
async function loadVisitorStats() {
try {
const now = Date.now();
let hkudsHumanVisits = 0;
let ccHumanVisits = 0;
let ccAgentVisits = 0;
const [hkudsStatsResp, ccEventsResp] = await Promise.all([
fetch(UMAMI_API + '/websites/' + HKUDS_WEBSITE_ID + '/stats?startAt=0&endAt=' + now, { headers }),
fetch(UMAMI_API + '/websites/' + CC_WEBSITE_ID + '/events/series?startAt=0&endAt=' + now + '&unit=year&timezone=UTC', { headers })
let totalVisits = 0;
let humanCount = 0;
const fetches = WEBSITE_IDS.flatMap((id) => [
fetch(UMAMI_API + '/websites/' + id + '/stats?startAt=0&endAt=' + now, { headers }),
fetch(UMAMI_API + '/websites/' + id + '/events/series?startAt=0&endAt=' + now + '&unit=year&timezone=UTC', { headers })
]);
const [statsResp1, eventsResp1, statsResp2, eventsResp2] = await Promise.all(fetches);
if (hkudsStatsResp.ok) {
const stats = await hkudsStatsResp.json();
hkudsHumanVisits = stats.visits ?? 0;
for (const resp of [statsResp1, statsResp2]) {
if (resp.ok) {
const stats = await resp.json();
totalVisits += stats.visits ?? 0;
}
}
if (ccEventsResp.ok) {
const events = await ccEventsResp.json();
events.forEach((e) => {
if (e.x === 'visit-human') ccHumanVisits += e.y || 0;
if (e.x === 'visit-agent') ccAgentVisits += e.y || 0;
});
for (const resp of [eventsResp1, eventsResp2]) {
if (resp.ok) {
const events = await resp.json();
events.forEach((e) => {
if (e.x === 'visit-human') humanCount += e.y || 0;
});
}
}
const humanCount = hkudsHumanVisits + ccHumanVisits;
const totalVisits = humanCount + ccAgentVisits;
document.getElementById('stat-total').textContent = totalVisits.toLocaleString();
document.getElementById('stat-human').textContent = humanCount.toLocaleString();
document.getElementById('stat-agent').textContent = ccAgentVisits.toLocaleString();
document.getElementById('stat-agent').textContent = Math.max(0, totalVisits - humanCount).toLocaleString();
} catch (_) {
// Leave fallback dashes.
}
}
loadVisitorStats();
loadGitHubStars();
})();
</script>
</body>

View File

@@ -17,8 +17,6 @@
--text: #fafafa;
--text-secondary: #a1a1aa;
--text-tertiary: #71717a;
--hero-neutral-top: #e7ecf2;
--hero-neutral-bottom: #b8c1cd;
--accent: #3b82f6;
--accent-muted: #2563eb;
--green: #22c55e;
@@ -39,8 +37,6 @@
--text: #09090b;
--text-secondary: #52525b;
--text-tertiary: #71717a;
--hero-neutral-top: #7f8894;
--hero-neutral-bottom: #a8b1bc;
--accent: #2563eb;
--accent-muted: #1d4ed8;
--green: #16a34a;
@@ -107,21 +103,6 @@
.nav-link svg { width: 15px; height: 15px; flex-shrink: 0; }
.nav-link-stars {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2.8rem;
padding: 0.1rem 0.45rem;
border-radius: 999px;
border: 1px solid var(--border);
background: var(--surface);
color: var(--text);
font-size: 0.72rem;
font-variant-numeric: tabular-nums;
line-height: 1.1;
}
/* ── Theme toggle ── */
.theme-toggle {
display: inline-flex;
@@ -167,14 +148,7 @@
}
.hero-accent { color: var(--accent); }
.hero-dim {
color: var(--hero-neutral-bottom);
background: linear-gradient(180deg, var(--hero-neutral-top) 0%, var(--hero-neutral-bottom) 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
opacity: 0.9;
}
.hero-dim { color: var(--text-tertiary); }
.hero-tagline {
font-size: 1.15rem;
@@ -826,6 +800,27 @@
margin-bottom: 0.75rem;
}
.card-install-stack {
display: grid;
gap: 0.55rem;
margin-bottom: 0.75rem;
}
.card-install-copy {
display: grid;
gap: 0.22rem;
flex: 1;
min-width: 0;
}
.card-install-step {
font-size: 0.68rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-tertiary);
font-weight: 600;
}
.card-install--note {
justify-content: flex-start;
}
@@ -872,93 +867,16 @@
gap: 0.6rem;
font-size: 0.78rem;
flex-wrap: wrap;
align-items: center;
}
.card-links a,
.card-link-btn {
.card-links a {
color: var(--text-tertiary);
text-decoration: none;
font-weight: 500;
transition: color 0.15s;
}
.card-link-btn {
background: none;
border: 0;
cursor: pointer;
font: inherit;
padding: 0;
}
.card-links a:hover,
.card-link-btn:hover { color: var(--text-secondary); }
.skill-popover {
position: relative;
display: inline-flex;
align-items: center;
}
.skill-popover-panel {
position: absolute;
left: 0;
top: calc(100% + 0.45rem);
width: min(18rem, calc(100vw - 3rem));
padding: 0.7rem;
border-radius: 10px;
border: 1px solid rgba(148, 163, 184, 0.36);
background: rgba(248, 250, 252, 0.98);
color: #0f172a;
box-shadow: 0 16px 32px rgba(15, 23, 42, 0.24);
opacity: 0;
transform: translateY(-4px);
pointer-events: none;
transition: opacity 0.16s ease, transform 0.16s ease;
z-index: 10;
}
.skill-popover.is-open .skill-popover-panel {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.skill-popover-label {
display: block;
font-size: 0.64rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #475569;
margin-bottom: 0.45rem;
}
.skill-popover-panel code {
display: block;
font-size: 0.75rem;
line-height: 1.55;
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-word;
font-family: 'JetBrains Mono', monospace;
background: rgba(226, 232, 240, 0.7);
border: 1px solid rgba(148, 163, 184, 0.3);
border-radius: 8px;
padding: 0.55rem 0.6rem;
}
.skill-popover-copy {
margin-top: 0.55rem;
color: #334155;
border-color: rgba(100, 116, 139, 0.34);
background: rgba(255, 255, 255, 0.72);
}
.skill-popover-copy:hover {
color: #0f172a;
border-color: rgba(51, 65, 85, 0.42);
}
.card-links a:hover { color: var(--text-secondary); }
.card-contributor {
font-size: 0.75rem;
@@ -1161,9 +1079,16 @@
gap: 0.4rem;
font-size: 0.78rem;
color: var(--text-tertiary);
text-decoration: none;
padding: 0.35rem 0.75rem;
border: 1px solid var(--border);
border-radius: 999px;
transition: border-color 0.15s, color 0.15s;
}
.footer-analytics-link:hover {
border-color: var(--text-tertiary);
color: var(--text-secondary);
}
.footer-analytics-link svg { opacity: 0.6; flex-shrink: 0; }
@@ -1242,13 +1167,12 @@
<nav class="nav">
<a class="nav-brand" href="#">CLI-Hub</a>
<div class="nav-links">
<a class="nav-link" href="https://reeceyang.sgp1.cdn.digitaloceanspaces.com/SKILL.md" target="_blank">
<a class="nav-link" href="https://github.com/HKUDS/CLI-Anything/blob/main/skills/cli-hub-meta-skill/SKILL.md" target="_blank">
SKILL
</a>
<a class="nav-link" href="https://github.com/HKUDS/CLI-Anything" target="_blank">
<svg viewBox="0 0 98 96" 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>
GitHub
<span class="nav-link-stars" data-github-stars aria-label="GitHub star count">--</span>
</a>
<a class="nav-link" href="https://github.com/HKUDS/CLI-Anything/blob/main/CONTRIBUTING.md" target="_blank">
Contribute
@@ -1263,12 +1187,17 @@
<!-- Hero -->
<div class="hero">
<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>
<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 command.</p>
<div class="empower-row">
<div class="empower-card">
<h3>Empower your agents &mdash; <span>install in one command</span></h3>
<p class="empower-available">Available on: <a href="https://clawhub.ai/yuh-yang/cli-anything-hub" target="_blank">ClawHub</a>, <a href="https://www.skillhub.club/web/skills/itsyuhao-cli-anything-hub" target="_blank">SkillHub</a>, <a href="https://skillhub.cn/skills/cli-hub-meta-skill" target="_blank">SkillHub.cn</a></p>
<div class="install-row">
<span class="chip-label">npx skills</span>
<code>npx skills add HKUDS/CLI-Anything --skill cli-hub-meta-skill -g -y</code>
<button class="copy-btn" onclick="copyCmd(this, 'npx skills add HKUDS/CLI-Anything --skill cli-hub-meta-skill -g -y')">Copy</button>
</div>
<div class="install-row">
<span class="chip-label">OpenClaw</span>
<code>openclaw skills install cli-anything-hub</code>
@@ -1302,7 +1231,7 @@
</div>
<div class="hero-actions">
<a class="btn-primary" href="https://reeceyang.sgp1.cdn.digitaloceanspaces.com/SKILL.md" target="_blank">Agent SKILL (SKILL.md)</a>
<a class="btn-primary" href="https://github.com/HKUDS/CLI-Anything/blob/main/skills/cli-hub-meta-skill/SKILL.md" target="_blank">Agent SKILL (SKILL.md)</a>
<a class="btn-secondary" href="https://github.com/HKUDS/CLI-Anything" target="_blank">
<svg viewBox="0 0 98 96" 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
@@ -1315,7 +1244,7 @@
</a>
</div>
<p class="hero-hint">Feed <a href="https://reeceyang.sgp1.cdn.digitaloceanspaces.com/SKILL.md" target="_blank">SKILL.md</a> to your AI agent for autonomous CLI discovery &amp; installation</p>
<p class="hero-hint">Inspect the canonical <a href="https://github.com/HKUDS/CLI-Anything/blob/main/skills/cli-hub-meta-skill/SKILL.md" target="_blank">cli-hub-meta-skill</a> for autonomous CLI discovery &amp; installation</p>
<p class="hero-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. Read the <a href="https://github.com/HKUDS/CLI-Anything/blob/main/CONTRIBUTING.md" target="_blank">Contributing Guide</a>, use the <a href="https://github.com/HKUDS/CLI-Anything/blob/main/.github/PULL_REQUEST_TEMPLATE.md" target="_blank">PR Template</a>, and bring it to the hub!</p>
@@ -1396,7 +1325,7 @@
Powered by <a href="https://github.com/HKUDS/CLI-Anything" target="_blank">CLI-Anything</a>
</div>
<div class="footer-stats">
<div class="footer-analytics-link" aria-label="Traffic snapshot">
<a class="footer-analytics-link" href="https://cloud.umami.is/share/mnilRgKH0oo7SJTs" target="_blank">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
<span class="stat-dot dot-total"></span>
<span id="stat-total">&mdash;</span> visits
@@ -1406,7 +1335,7 @@
<span class="analytics-sep">&middot;</span>
<span class="stat-dot dot-agent"></span>
<span id="stat-agent">&mdash;</span> AI agent
</div>
</a>
</div>
</footer>
@@ -1440,7 +1369,7 @@
});
const REPO = 'https://github.com/HKUDS/CLI-Anything';
const GITHUB_REPO_API = 'https://api.github.com/repos/HKUDS/CLI-Anything';
const REPO_SKILLS_SOURCE = 'HKUDS/CLI-Anything';
const REGISTRY_URLS = [
'../../registry.json',
'https://raw.githubusercontent.com/HKUDS/CLI-Anything/main/registry.json'
@@ -1483,34 +1412,6 @@
return null;
}
function formatCompactCount(value) {
return new Intl.NumberFormat('en', {
notation: 'compact',
maximumFractionDigits: value >= 10000 ? 0 : 1,
}).format(value);
}
async function loadGitHubStars() {
try {
const resp = await fetch(GITHUB_REPO_API, {
headers: { 'Accept': 'application/vnd.github+json' }
});
if (!resp.ok) return;
const repo = await resp.json();
const stars = repo.stargazers_count;
if (typeof stars !== 'number') return;
document.querySelectorAll('[data-github-stars]').forEach(el => {
el.textContent = formatCompactCount(stars);
el.setAttribute('aria-label', `${stars.toLocaleString('en-US')} GitHub stars`);
el.title = `${stars.toLocaleString('en-US')} GitHub stars`;
});
} catch (_) {
// Keep fallback placeholder when GitHub API is unavailable.
}
}
async function loadRegistries() {
try {
const [harnessData, publicData, dates] = await Promise.all([
@@ -1749,38 +1650,49 @@
}
}
function isWebUrl(value) {
return /^https?:\/\//i.test(value || '');
function repoSkillId(skillPath) {
if (!skillPath || skillPath.startsWith('http')) return '';
const match = skillPath.match(/^skills\/([^/]+)\/SKILL\.md$/);
return match ? match[1] : '';
}
function isRepoSkillPath(value) {
return !/\s/.test(value || '') && /(^\.{0,2}\/|\/|\.md$|\.txt$)/i.test(value || '');
function normalizeNpxSkillsCmd(cmd) {
if (!cmd) return '';
const trimmed = cmd.trim();
if (!trimmed.startsWith('npx skills add ')) return '';
return trimmed
.replace(/\s+-g\b/g, '')
.replace(/\s+-y\b/g, '')
.trim() + ' -g -y';
}
function renderSkillAction(value) {
if (!value) return '';
if (isWebUrl(value)) {
return `<a href="${esc(value)}" target="_blank">Skill</a>`;
}
if (isRepoSkillPath(value)) {
return `<a href="${REPO}/blob/main/${esc(value)}" target="_blank">Skill</a>`;
}
return `
<span class="skill-popover">
<button class="card-link-btn skill-trigger" type="button" aria-expanded="false" data-skill-value="${escAttr(value)}">Skill</button>
<span class="skill-popover-panel">
<span class="skill-popover-label">Skill install</span>
<code>${esc(value)}</code>
<button class="card-copy-btn skill-popover-copy" type="button" data-copy-value="${escAttr(value)}">Copy</button>
</span>
</span>`;
function harnessSkillCmd(c) {
const skillId = repoSkillId(c.skill_md);
return skillId ? `npx skills add ${REPO_SKILLS_SOURCE} --skill ${skillId} -g -y` : '';
}
function publicSkillCmd(c) {
return normalizeNpxSkillsCmd(c.skill_md);
}
function renderInstallStack(steps) {
return `<div class="card-install-stack">${steps.map((step) => `
<div class="card-install">
<div class="card-install-copy">
<span class="card-install-step">${esc(step.label)}</span>
<code>${esc(step.cmd)}</code>
</div>
<button class="card-copy-btn" onclick="copyCmd(this, '${esc(step.cmd)}')">Copy</button>
</div>`).join('')}</div>`;
}
function renderHarnessCard(c) {
const requiresHtml = c.requires
? `<div class="card-requires"><strong>Requires</strong> ${esc(c.requires)}</div>`
: '';
const skillLink = renderSkillAction(c.skill_md);
const skillLink = c.skill_md
? `<a href="${c.skill_md.startsWith('http') ? c.skill_md : `${REPO}/blob/main/${c.skill_md}`}" target="_blank">Skill</a>`
: '';
const sourceLink = c.source_url
? `<a href="${esc(c.source_url)}" target="_blank">Source</a>`
: `<a href="${REPO}/tree/main/${c.name}/agent-harness" target="_blank">Source</a>`;
@@ -1797,6 +1709,12 @@
const dateHtml = c.last_modified
? `<div class="card-date">Updated ${esc(c.last_modified)}</div>`
: '';
const installSteps = [
{ label: 'Step 1 · Install CLI', cmd: `cli-hub install ${c.name}` }
];
const skillCmd = harnessSkillCmd(c);
if (skillCmd) installSteps.push({ label: 'Step 2 · Install Skill', cmd: skillCmd });
const installHtml = renderInstallStack(installSteps);
return `
<div class="card">
@@ -1810,12 +1728,9 @@
<div class="card-desc">${esc(c.description)}</div>
${dateHtml}
${requiresHtml}
<div class="card-install">
<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>
${installHtml}
<div class="card-footer">
<div class="card-links">${sourceLink}${skillLink}</div>
<div class="card-links">${sourceLink}${skillLink ? ' &middot; ' + skillLink : ''}</div>
${contributorHtml}
</div>
</div>`;
@@ -1840,7 +1755,6 @@
const titleHtml = c.homepage
? `<a href="${esc(c.homepage)}" target="_blank">${esc(c.display_name)}${EXTERNAL_LINK_SVG}</a>`
: esc(c.display_name);
const skillLink = renderSkillAction(c.skill_md);
const sourceLink = c.source_url
? `<a href="${esc(c.source_url)}" target="_blank">Source</a>`
: '';
@@ -1860,12 +1774,14 @@
const publicNotesHtml = (directCmdHtml || installNotesHtml)
? `<div class="card-public-notes">${directCmdHtml}${installNotesHtml}</div>`
: '';
const installSteps = isBundled
? []
: [{ label: 'Step 1 · Install CLI', cmd: `cli-hub install ${c.name}` }];
const skillCmd = publicSkillCmd(c);
if (skillCmd) installSteps.push({ label: 'Step 2 · Install Skill', cmd: skillCmd });
const cliHubInstallHtml = isBundled
? `<div class="card-install card-install--note"><code>Bundled with the upstream app</code></div>`
: `<div class="card-install">
<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>`;
: renderInstallStack(installSteps);
return `
<div class="card">
@@ -1881,7 +1797,7 @@
${cliHubInstallHtml}
${publicNotesHtml}
<div class="card-footer">
<div class="card-links">${sourceLink}${skillLink}</div>
<div class="card-links">${sourceLink}</div>
${contributorHtml}
</div>
</div>`;
@@ -1894,20 +1810,6 @@
});
}
function toggleSkillPopover(trigger) {
const popover = trigger.closest('.skill-popover');
if (!popover) return;
const shouldOpen = !popover.classList.contains('is-open');
document.querySelectorAll('.skill-popover.is-open').forEach((node) => {
node.classList.remove('is-open');
const btn = node.querySelector('.skill-trigger');
if (btn) btn.setAttribute('aria-expanded', 'false');
});
if (!shouldOpen) return;
popover.classList.add('is-open');
trigger.setAttribute('aria-expanded', 'true');
}
function esc(s) {
if (!s) return '';
const d = document.createElement('div');
@@ -1915,48 +1817,6 @@
return d.innerHTML;
}
function escAttr(s) {
if (!s) return '';
return String(s)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
document.addEventListener('click', (e) => {
const copyBtn = e.target.closest('.skill-popover-copy');
if (copyBtn) {
e.preventDefault();
e.stopPropagation();
copyCmd(copyBtn, copyBtn.dataset.copyValue);
return;
}
const trigger = e.target.closest('.skill-trigger');
if (trigger) {
e.preventDefault();
e.stopPropagation();
toggleSkillPopover(trigger);
return;
}
document.querySelectorAll('.skill-popover.is-open').forEach((node) => {
node.classList.remove('is-open');
const btn = node.querySelector('.skill-trigger');
if (btn) btn.setAttribute('aria-expanded', 'false');
});
});
document.addEventListener('keydown', (e) => {
if (e.key !== 'Escape') return;
document.querySelectorAll('.skill-popover.is-open').forEach((node) => {
node.classList.remove('is-open');
const btn = node.querySelector('.skill-trigger');
if (btn) btn.setAttribute('aria-expanded', 'false');
});
});
// Wire up search and sort for each deck
document.getElementById('search-harness').addEventListener('input', (e) => {
deckState.harness.query = e.target.value;
@@ -2063,48 +1923,49 @@
// ── Fetch all-time stats from Umami Cloud API (both sites) ──
const UMAMI_API = 'https://api.umami.is/v1';
const UMAMI_KEY = 'api_idAebMhzn6z0hsUQT7BSxRuCK2GUZvRY';
const HKUDS_WEBSITE_ID = '07082d05-efd3-4f85-a7a1-b426b0e8bfaa';
const CC_WEBSITE_ID = 'a076c661-bed1-405c-a522-813794e688b4';
const WEBSITE_IDS = [
'07082d05-efd3-4f85-a7a1-b426b0e8bfaa', // hkuds.github.io
'a076c661-bed1-405c-a522-813794e688b4', // clianything.cc
];
const headers = { 'Accept': 'application/json', 'x-umami-api-key': UMAMI_KEY };
async function loadVisitorStats() {
try {
const now = Date.now();
let hkudsHumanVisits = 0;
let ccHumanVisits = 0;
let ccAgentVisits = 0;
let totalVisits = 0, humanCount = 0;
const [hkudsStatsResp, ccEventsResp] = await Promise.all([
fetch(`${UMAMI_API}/websites/${HKUDS_WEBSITE_ID}/stats?startAt=0&endAt=${now}`, { headers }),
fetch(`${UMAMI_API}/websites/${CC_WEBSITE_ID}/events/series?startAt=0&endAt=${now}&unit=year&timezone=UTC`, { headers })
// Fetch stats and events from both sites in parallel
const fetches = WEBSITE_IDS.flatMap(id => [
fetch(`${UMAMI_API}/websites/${id}/stats?startAt=0&endAt=${now}`, { headers }),
fetch(`${UMAMI_API}/websites/${id}/events/series?startAt=0&endAt=${now}&unit=year&timezone=UTC`, { headers })
]);
const [statsResp1, eventsResp1, statsResp2, eventsResp2] = await Promise.all(fetches);
if (hkudsStatsResp.ok) {
const stats = await hkudsStatsResp.json();
hkudsHumanVisits = stats.visits ?? 0;
for (const resp of [statsResp1, statsResp2]) {
if (resp.ok) {
const stats = await resp.json();
totalVisits += stats.visits ?? 0;
}
}
if (ccEventsResp.ok) {
const events = await ccEventsResp.json();
events.forEach(e => {
if (e.x === 'visit-human') ccHumanVisits += e.y || 0;
if (e.x === 'visit-agent') ccAgentVisits += e.y || 0;
});
for (const resp of [eventsResp1, eventsResp2]) {
if (resp.ok) {
const events = await resp.json();
events.forEach(e => {
if (e.x === 'visit-human') humanCount += e.y || 0;
});
}
}
const humanCount = hkudsHumanVisits + ccHumanVisits;
const totalVisits = humanCount + ccAgentVisits;
document.getElementById('stat-total').textContent = totalVisits.toLocaleString();
document.getElementById('stat-human').textContent = humanCount.toLocaleString();
document.getElementById('stat-agent').textContent = ccAgentVisits.toLocaleString();
document.getElementById('stat-agent').textContent = Math.max(0, totalVisits - humanCount).toLocaleString();
} catch (_) {
// Stats not available — leave dashes
}
}
loadVisitorStats();
loadGitHubStars();
})();
</script>
</body>

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -47,7 +48,6 @@ _ACCENT_COLORS = {
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
"drawio": "\033[38;5;202m", # draw.io orange
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -58,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -90,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -98,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -106,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -144,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -152,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -166,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)
@@ -495,6 +563,5 @@ _ANSI_256_TO_HEX = {
"\033[38;5;75m": "#5fafff", # default sky blue
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;202m": "#ff5f00", # drawio orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
}

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -97,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -143,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -151,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -165,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -1,3 +1,9 @@
---
name: "cli-anything-exa"
description: >-
Agent-native CLI for Exa web search and content retrieval workflows.
---
# Exa CLI Skill
## Identity

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -106,21 +106,32 @@ class ReplSkin:
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the package's skills/ directory if not provided.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
# Auto-detect skill path from package layout:
# cli_anything/<software>/utils/repl_skin.py (this file)
# cli_anything/<software>/skills/SKILL.md (target)
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
from pathlib import Path
_auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if _auto.is_file():
skill_path = str(_auto)
software_aliases = {"iterm2_ctl": "iterm2"}
skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / f"cli-anything-{skill_slug}" / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -47,7 +48,6 @@ _ACCENT_COLORS = {
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
"freecad": "\033[38;5;196m", # FreeCAD red
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -58,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -90,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -107,27 +120,44 @@ class ReplSkin:
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the package's skills/ directory if not provided.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Auto-detect skill path from package layout:
# cli_anything/<software>/utils/repl_skin.py (this file)
# cli_anything/<software>/skills/SKILL.md (target)
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
from pathlib import Path
_auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if _auto.is_file():
skill_path = str(_auto)
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -157,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -165,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -179,19 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
# Skill path for agent discovery
skill_line = None
if self.skill_path:
skill_icon = self._c(_MAGENTA, "")
skill_label = self._c(_DARK_GRAY, " Skill:")
skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
if skill_line:
print(_box_line(skill_line))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)
@@ -518,6 +563,5 @@ _ANSI_256_TO_HEX = {
"\033[38;5;75m": "#5fafff", # default sky blue
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;196m": "#ff0000", # freecad red
"\033[38;5;214m": "#ffaf00", # gimp warm orange
}

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -97,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -143,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -151,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -165,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -1,3 +1,9 @@
---
name: "cli-anything-godot"
description: >-
Agent-native CLI for Godot project management, scenes, exports, and script execution.
---
# Godot Engine CLI
Agent-native CLI for the Godot game engine. Manage projects, scenes, exports, and GDScript execution from the command line.

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -106,27 +120,44 @@ class ReplSkin:
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the package's skills/ directory if not provided.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Auto-detect skill path from package layout:
# cli_anything/<software>/utils/repl_skin.py (this file)
# cli_anything/<software>/skills/SKILL.md (target)
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
from pathlib import Path
_auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if _auto.is_file():
skill_path = str(_auto)
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -156,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -164,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -178,19 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
# Skill path for agent discovery
skill_line = None
if self.skill_path:
skill_icon = self._c(_MAGENTA, "")
skill_label = self._c(_DARK_GRAY, " Skill:")
skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
if skill_line:
print(_box_line(skill_line))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -97,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -143,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -151,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -165,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -97,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -143,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -151,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -165,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -106,27 +120,44 @@ class ReplSkin:
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the package's skills/ directory if not provided.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Auto-detect skill path from package layout:
# cli_anything/<software>/utils/repl_skin.py (this file)
# cli_anything/<software>/skills/SKILL.md (target)
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
from pathlib import Path
_auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if _auto.is_file():
skill_path = str(_auto)
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -156,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -164,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -178,19 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
# Skill path for agent discovery
skill_line = None
if self.skill_path:
skill_icon = self._c(_MAGENTA, "")
skill_label = self._c(_DARK_GRAY, " Skill:")
skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
if skill_line:
print(_box_line(skill_line))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -97,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -143,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -151,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -165,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -47,7 +48,6 @@ _ACCENT_COLORS = {
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
"krita": "\033[38;5;98m", # purple (Krita brand)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -58,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -90,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -107,27 +120,44 @@ class ReplSkin:
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the package's skills/ directory if not provided.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Auto-detect skill path from package layout:
# cli_anything/<software>/utils/repl_skin.py (this file)
# cli_anything/<software>/skills/SKILL.md (target)
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
from pathlib import Path
_auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if _auto.is_file():
skill_path = str(_auto)
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -157,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -165,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -179,19 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
# Skill path for agent discovery
skill_line = None
if self.skill_path:
skill_icon = self._c(_MAGENTA, "")
skill_label = self._c(_DARK_GRAY, " Skill:")
skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
if skill_line:
print(_box_line(skill_line))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)
@@ -516,7 +561,6 @@ _ANSI_256_TO_HEX = {
"\033[38;5;55m": "#5f00af", # obs purple
"\033[38;5;69m": "#5f87ff", # kdenlive slate blue
"\033[38;5;75m": "#5fafff", # default sky blue
"\033[38;5;98m": "#875fd7", # krita purple
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -97,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -143,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -151,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -165,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -2,18 +2,61 @@
from __future__ import annotations
import os
from pathlib import Path
import textwrap
from prompt_toolkit import PromptSession
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.history import InMemoryHistory
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
def _display_home_path(path: str) -> str:
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
def __init__(self, software: str, version: str = "1.0.0"):
def __init__(self, software: str, version: str = "1.0.0", skill_path: str | None = None):
self.software = software
self.version = version
self.skill_slug = self.software.replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
self.skill_path = skill_path or self._detect_skill_path()
def _detect_skill_path(self) -> str | None:
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
return str(candidate)
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if package_skill.is_file():
return str(package_skill)
return None
def print_banner(self) -> None:
print(f"cli-anything-{self.software} v{self.version}")
install_lines = textwrap.wrap(
self.skill_install_cmd, width=88, break_long_words=True, break_on_hyphens=False
) or [self.skill_install_cmd]
for index, line in enumerate(install_lines):
prefix = "Install: " if index == 0 else " "
print(f"{prefix}{line}")
print(f"Global skill: {_display_home_path(self.global_skill_path)}")
print("Type help for commands, quit to exit")
def create_prompt_session(self) -> PromptSession:

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -106,27 +120,44 @@ class ReplSkin:
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the package's skills/ directory if not provided.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Auto-detect skill path from package layout:
# cli_anything/<software>/utils/repl_skin.py (this file)
# cli_anything/<software>/skills/SKILL.md (target)
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
from pathlib import Path
_auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if _auto.is_file():
skill_path = str(_auto)
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -156,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -164,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -178,19 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
# Skill path for agent discovery
skill_line = None
if self.skill_path:
skill_icon = self._c(_MAGENTA, "")
skill_label = self._c(_DARK_GRAY, " Skill:")
skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
if skill_line:
print(_box_line(skill_line))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -404,12 +404,18 @@ def generate_skill_md(metadata: SkillMetadata, template_path: Optional[str] = No
def generate_skill_file(harness_path: str, output_path: Optional[str] = None, template_path: Optional[str] = None) -> str:
metadata = extract_cli_metadata(harness_path)
content = generate_skill_md(metadata, template_path)
harness_root = Path(harness_path)
skill_id = f"cli-anything-{harness_root.parent.name.replace('_', '-')}"
if output_path is None:
output = Path(harness_path) / "cli_anything" / metadata.software_name / "skills" / "SKILL.md"
output = harness_root.parent.parent / "skills" / skill_id / "SKILL.md"
else:
output = Path(output_path)
mirror = harness_root / "cli_anything" / metadata.software_name / "skills" / "SKILL.md"
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(content, encoding="utf-8")
if mirror != output:
mirror.parent.mkdir(parents=True, exist_ok=True)
mirror.write_text(content, encoding="utf-8")
return str(output)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -97,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -143,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -151,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -165,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("n8n", version="2.4.7")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_workflow", modified=True)
skin.success("Workflow activated")
skin.error("Connection failed")
@@ -22,6 +22,7 @@ import json
import os
import shutil
import sys
from pathlib import Path
from typing import Any
import click
@@ -63,6 +64,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -95,6 +98,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -112,27 +126,43 @@ class ReplSkin:
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the package's skills/ directory if not provided.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
self.skill_slug = self.software.replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Auto-detect skill path from package layout:
# cli_anything/<software>/utils/repl_skin.py (this file)
# cli_anything/<software>/skills/SKILL.md (target)
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
from pathlib import Path
_auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if _auto.is_file():
skill_path = str(_auto)
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -162,7 +192,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -170,6 +202,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -184,19 +234,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
# Skill path for agent discovery
skill_line = None
if self.skill_path:
skill_icon = self._c(_MAGENTA, "")
skill_label = self._c(_DARK_GRAY, " Skill:")
skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
if skill_line:
print(_box_line(skill_line))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -30,7 +31,7 @@ _ITALIC = "\033[3m"
_UNDERLINE = "\033[4m"
# Brand colors
_CYAN = "\033[38;5;80m" # cli-anything brand cyan
_CYAN = "\033[38;5;80m" # cli-anything brand cyan
_CYAN_BG = "\033[48;5;80m"
_WHITE = "\033[97m"
_GRAY = "\033[38;5;245m"
@@ -39,18 +40,16 @@ _LIGHT_GRAY = "\033[38;5;250m"
# Software accent colors — each software gets a unique accent
_ACCENT_COLORS = {
"gimp": "\033[38;5;214m", # warm orange
"blender": "\033[38;5;208m", # deep orange
"inkscape": "\033[38;5;39m", # bright blue
"audacity": "\033[38;5;33m", # navy blue
"libreoffice": "\033[38;5;40m", # green
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
"anygen": "\033[38;5;141m", # soft violet
"novita": "\033[38;5;81m", # vivid blue (for Novita AI)
"gimp": "\033[38;5;214m", # warm orange
"blender": "\033[38;5;208m", # deep orange
"inkscape": "\033[38;5;39m", # bright blue
"audacity": "\033[38;5;33m", # navy blue
"libreoffice": "\033[38;5;40m", # green
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
# Status colors
_GREEN = "\033[38;5;78m"
@@ -59,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -83,7 +84,6 @@ _CROSS = "┼"
def _strip_ansi(text: str) -> str:
"""Remove ANSI escape codes for length calculation."""
import re
return re.sub(r"\033\[[^m]*m", "", text)
@@ -92,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -99,9 +110,8 @@ class ReplSkin:
across all CLI harnesses built with the cli-anything methodology.
"""
def __init__(
self, software: str, version: str = "1.0.0", history_file: str | None = None
):
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -109,16 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -148,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -156,10 +197,28 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
# Title: ◆ cli-anything · Novita
# Title: ◆ cli-anything · Shotcut
icon = self._c(_CYAN + _BOLD, "")
brand = self._c(_CYAN + _BOLD, "cli-anything")
dot = self._c(_DARK_GRAY, "·")
@@ -170,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)
@@ -180,9 +244,8 @@ class ReplSkin:
# ── Prompt ────────────────────────────────────────────────────────
def prompt(
self, project_name: str = "", modified: bool = False, context: str = ""
) -> str:
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
"""Build a styled prompt string for prompt_toolkit or input().
Args:
@@ -210,15 +273,14 @@ class ReplSkin:
mod = "*" if modified else ""
parts.append(f" {self._c(_DARK_GRAY, '[')}")
parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
parts.append(self._c(_DARK_GRAY, "]"))
parts.append(self._c(_DARK_GRAY, ']'))
parts.append(self._c(_GRAY, " "))
return "".join(parts)
def prompt_tokens(
self, project_name: str = "", modified: bool = False, context: str = ""
):
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
"""Build prompt_toolkit formatted text tokens for the prompt.
Use with prompt_toolkit's FormattedText for proper ANSI handling.
@@ -256,25 +318,23 @@ class ReplSkin:
accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
return Style.from_dict(
{
"icon": "#5fdfdf bold", # cyan brand color
"software": f"{accent_hex} bold",
"bracket": "#585858",
"context": "#bcbcbc",
"arrow": "#808080",
# Completion menu
"completion-menu.completion": "bg:#303030 #bcbcbc",
"completion-menu.completion.current": f"bg:{accent_hex} #000000",
"completion-menu.meta.completion": "bg:#303030 #808080",
"completion-menu.meta.completion.current": f"bg:{accent_hex} #000000",
# Auto-suggest
"auto-suggest": "#585858",
# Bottom toolbar
"bottom-toolbar": "bg:#1c1c1c #808080",
"bottom-toolbar.text": "#808080",
}
)
return Style.from_dict({
"icon": "#5fdfdf bold", # cyan brand color
"software": f"{accent_hex} bold",
"bracket": "#585858",
"context": "#bcbcbc",
"arrow": "#808080",
# Completion menu
"completion-menu.completion": "bg:#303030 #bcbcbc",
"completion-menu.completion.current": f"bg:{accent_hex} #000000",
"completion-menu.meta.completion": "bg:#303030 #808080",
"completion-menu.meta.completion.current": f"bg:{accent_hex} #000000",
# Auto-suggest
"auto-suggest": "#585858",
# Bottom toolbar
"bottom-toolbar": "bg:#1c1c1c #808080",
"bottom-toolbar.text": "#808080",
})
# ── Messages ──────────────────────────────────────────────────────
@@ -351,7 +411,8 @@ class ReplSkin:
# ── Table display ─────────────────────────────────────────────────
def table(self, headers: list[str], rows: list[list[str]], max_col_width: int = 40):
def table(self, headers: list[str], rows: list[list[str]],
max_col_width: int = 40):
"""Print a formatted table with box-drawing characters.
Args:
@@ -377,7 +438,8 @@ class ReplSkin:
# Header
header_cells = [
self._c(_CYAN + _BOLD, pad(h, col_widths[i])) for i, h in enumerate(headers)
self._c(_CYAN + _BOLD, pad(h, col_widths[i]))
for i, h in enumerate(headers)
]
sep = self._c(_DARK_GRAY, f" {_V_LINE} ")
header_line = f" {sep.join(header_cells)}"
@@ -385,9 +447,7 @@ class ReplSkin:
# Separator
sep_parts = [self._c(_DARK_GRAY, _H_LINE * w) for w in col_widths]
sep_line = self._c(
_DARK_GRAY, f" {'───'.join([_H_LINE * w for w in col_widths])}"
)
sep_line = self._c(_DARK_GRAY, f" {'───'.join([_H_LINE * w for w in col_widths])}")
print(sep_line)
# Rows
@@ -447,13 +507,8 @@ class ReplSkin:
except ImportError:
return None
def get_input(
self,
pt_session,
project_name: str = "",
modified: bool = False,
context: str = "",
) -> str:
def get_input(self, pt_session, project_name: str = "",
modified: bool = False, context: str = "") -> str:
"""Get input from user using prompt_toolkit or fallback.
Args:
@@ -467,7 +522,6 @@ class ReplSkin:
"""
if pt_session is not None:
from prompt_toolkit.formatted_text import FormattedText
tokens = self.prompt_tokens(project_name, modified, context)
return pt_session.prompt(FormattedText(tokens)).strip()
else:
@@ -485,10 +539,8 @@ class ReplSkin:
Returns:
A callable that returns FormattedText for the toolbar.
"""
def toolbar():
from prompt_toolkit.formatted_text import FormattedText
parts = []
for i, (k, v) in enumerate(items.items()):
if i > 0:
@@ -496,23 +548,20 @@ class ReplSkin:
parts.append(("class:bottom-toolbar.text", f" {k}: "))
parts.append(("class:bottom-toolbar", v))
return FormattedText(parts)
return toolbar
# ── ANSI 256-color to hex mapping (for prompt_toolkit styles) ─────────
_ANSI_256_TO_HEX = {
"\033[38;5;33m": "#0087ff", # audacity navy blue
"\033[38;5;35m": "#00af5f", # shotcut teal
"\033[38;5;39m": "#00afff", # inkscape bright blue
"\033[38;5;40m": "#00d700", # libreoffice green
"\033[38;5;55m": "#5f00af", # obs purple
"\033[38;5;69m": "#5f87ff", # kdenlive slate blue
"\033[38;5;75m": "#5fafff", # default sky blue
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;81m": "#5fd7ff", # novita vivid blue
"\033[38;5;141m": "#af87ff", # anygen soft violet
"\033[38;5;33m": "#0087ff", # audacity navy blue
"\033[38;5;35m": "#00af5f", # shotcut teal
"\033[38;5;39m": "#00afff", # inkscape bright blue
"\033[38;5;40m": "#00d700", # libreoffice green
"\033[38;5;55m": "#5f00af", # obs purple
"\033[38;5;69m": "#5f87ff", # kdenlive slate blue
"\033[38;5;75m": "#5fafff", # default sky blue
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
}

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -97,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -143,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -151,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -165,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -6,20 +6,21 @@ Copy this file into your CLI package at:
Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("ollama", version="1.0.0")
skin.print_banner()
prompt_text = skin.prompt(project_name="llama3.2", modified=False)
skin.success("Model pulled")
skin.error("Connection failed")
skin.warning("No models loaded")
skin.info("Generating...")
skin.status("Model", "llama3.2:latest")
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
skin.warning("Unsaved changes")
skin.info("Processing 24 clips...")
skin.status("Track 1", "3 clips, 00:02:30")
skin.table(headers, rows)
skin.print_goodbye()
"""
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -47,7 +48,6 @@ _ACCENT_COLORS = {
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
"ollama": "\033[38;5;255m", # white (Ollama branding)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -58,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -90,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -98,23 +111,53 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
software: Software name (e.g., "gimp", "shotcut", "ollama").
software: Software name (e.g., "gimp", "shotcut", "blender").
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -144,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -152,10 +197,28 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
# Title: ◆ cli-anything · Ollama
# Title: ◆ cli-anything · Shotcut
icon = self._c(_CYAN + _BOLD, "")
brand = self._c(_CYAN + _BOLD, "cli-anything")
dot = self._c(_DARK_GRAY, "·")
@@ -166,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)
@@ -496,5 +564,4 @@ _ANSI_256_TO_HEX = {
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
"\033[38;5;255m": "#eeeeee", # ollama white
}

View File

@@ -6,20 +6,21 @@ Copy this file into your CLI package at:
Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("ollama", version="1.0.0")
skin.print_banner()
prompt_text = skin.prompt(project_name="llama3.2", modified=False)
skin.success("Model pulled")
skin.error("Connection failed")
skin.warning("No models loaded")
skin.info("Generating...")
skin.status("Model", "llama3.2:latest")
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
skin.warning("Unsaved changes")
skin.info("Processing 24 clips...")
skin.status("Track 1", "3 clips, 00:02:30")
skin.table(headers, rows)
skin.print_goodbye()
"""
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -47,7 +48,6 @@ _ACCENT_COLORS = {
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
"ollama": "\033[38;5;255m", # white (Ollama branding)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -58,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -90,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -98,23 +111,53 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
software: Software name (e.g., "gimp", "shotcut", "ollama").
software: Software name (e.g., "gimp", "shotcut", "blender").
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -144,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -152,10 +197,28 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
# Title: ◆ cli-anything · Ollama
# Title: ◆ cli-anything · Shotcut
icon = self._c(_CYAN + _BOLD, "")
brand = self._c(_CYAN + _BOLD, "cli-anything")
dot = self._c(_DARK_GRAY, "·")
@@ -166,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)
@@ -496,5 +564,4 @@ _ANSI_256_TO_HEX = {
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
"\033[38;5;255m": "#eeeeee", # ollama white
}

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -97,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -143,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -151,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -165,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)
@@ -459,26 +528,6 @@ class ReplSkin:
raw_prompt = self.prompt(project_name, modified, context)
return input(raw_prompt).strip()
# ── Sub-prompt input ────────────────────────────────────────────
def sub_input(self, prompt_text: str, pt_session=None) -> str:
"""Get input for a sub-prompt (e.g., parameter entry in add flows).
Uses prompt_toolkit if a session is available, otherwise falls back
to plain input(). This preserves history and styling consistency.
Args:
prompt_text: The prompt to display (e.g., " start_ms: ").
pt_session: An optional prompt_toolkit PromptSession.
Returns:
User input string (stripped).
"""
if pt_session is not None:
return pt_session.prompt(prompt_text).strip()
else:
return input(prompt_text).strip()
# ── Toolbar builder ───────────────────────────────────────────────
def bottom_toolbar(self, items: dict[str, str]):

View File

@@ -6,20 +6,21 @@ Copy this file into your CLI package at:
Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("ollama", version="1.0.0")
skin.print_banner()
prompt_text = skin.prompt(project_name="llama3.2", modified=False)
skin.success("Model pulled")
skin.error("Connection failed")
skin.warning("No models loaded")
skin.info("Generating...")
skin.status("Model", "llama3.2:latest")
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
skin.warning("Unsaved changes")
skin.info("Processing 24 clips...")
skin.status("Track 1", "3 clips, 00:02:30")
skin.table(headers, rows)
skin.print_goodbye()
"""
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -47,7 +48,6 @@ _ACCENT_COLORS = {
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
"ollama": "\033[38;5;255m", # white (Ollama branding)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -58,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -90,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -98,23 +111,53 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
software: Software name (e.g., "gimp", "shotcut", "ollama").
software: Software name (e.g., "gimp", "shotcut", "blender").
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -144,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -152,10 +197,28 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
# Title: ◆ cli-anything · Ollama
# Title: ◆ cli-anything · Shotcut
icon = self._c(_CYAN + _BOLD, "")
brand = self._c(_CYAN + _BOLD, "cli-anything")
dot = self._c(_DARK_GRAY, "·")
@@ -166,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)
@@ -496,5 +564,4 @@ _ANSI_256_TO_HEX = {
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
"\033[38;5;255m": "#eeeeee", # ollama white
}

View File

@@ -15,7 +15,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=wiremock/agent-harness",
"entry_point": "cli-anything-wiremock",
"skill_md": "wiremock/agent-harness/cli_anything/wiremock/skills/SKILL.md",
"skill_md": "skills/cli-anything-wiremock/SKILL.md",
"category": "testing",
"contributors": [
{
@@ -34,7 +34,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=anygen/agent-harness",
"entry_point": "cli-anything-anygen",
"skill_md": "anygen/agent-harness/cli_anything/anygen/skills/SKILL.md",
"skill_md": "skills/cli-anything-anygen/SKILL.md",
"category": "generation",
"contributors": [
{
@@ -72,7 +72,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=audacity/agent-harness",
"entry_point": "cli-anything-audacity",
"skill_md": "audacity/agent-harness/cli_anything/audacity/skills/SKILL.md",
"skill_md": "skills/cli-anything-audacity/SKILL.md",
"category": "audio",
"contributors": [
{
@@ -91,7 +91,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=blender/agent-harness",
"entry_point": "cli-anything-blender",
"skill_md": "blender/agent-harness/cli_anything/blender/skills/SKILL.md",
"skill_md": "skills/cli-anything-blender/SKILL.md",
"category": "3d",
"contributors": [
{
@@ -110,7 +110,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=browser/agent-harness",
"entry_point": "cli-anything-browser",
"skill_md": "browser/agent-harness/cli_anything/browser/skills/SKILL.md",
"skill_md": "skills/cli-anything-browser/SKILL.md",
"category": "web",
"contributors": [
{
@@ -148,7 +148,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=drawio/agent-harness",
"entry_point": "cli-anything-drawio",
"skill_md": "drawio/agent-harness/cli_anything/drawio/skills/SKILL.md",
"skill_md": "skills/cli-anything-drawio/SKILL.md",
"category": "diagrams",
"contributors": [
{
@@ -167,7 +167,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=eth2-quickstart/agent-harness",
"entry_point": "cli-anything-eth2-quickstart",
"skill_md": "eth2-quickstart/agent-harness/cli_anything/eth2_quickstart/skills/SKILL.md",
"skill_md": "skills/cli-anything-eth2-quickstart/SKILL.md",
"category": "devops",
"contributors": [
{
@@ -186,7 +186,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=gimp/agent-harness",
"entry_point": "cli-anything-gimp",
"skill_md": "gimp/agent-harness/cli_anything/gimp/skills/SKILL.md",
"skill_md": "skills/cli-anything-gimp/SKILL.md",
"category": "image",
"contributors": [
{
@@ -205,7 +205,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=inkscape/agent-harness",
"entry_point": "cli-anything-inkscape",
"skill_md": "inkscape/agent-harness/cli_anything/inkscape/skills/SKILL.md",
"skill_md": "skills/cli-anything-inkscape/SKILL.md",
"category": "image",
"contributors": [
{
@@ -224,7 +224,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=kdenlive/agent-harness",
"entry_point": "cli-anything-kdenlive",
"skill_md": "kdenlive/agent-harness/cli_anything/kdenlive/skills/SKILL.md",
"skill_md": "skills/cli-anything-kdenlive/SKILL.md",
"category": "video",
"contributors": [
{
@@ -243,7 +243,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=krita/agent-harness",
"entry_point": "cli-anything-krita",
"skill_md": "krita/agent-harness/cli_anything/krita/skills/SKILL.md",
"skill_md": "skills/cli-anything-krita/SKILL.md",
"category": "image",
"contributors": [
{
@@ -262,7 +262,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=libreoffice/agent-harness",
"entry_point": "cli-anything-libreoffice",
"skill_md": "libreoffice/agent-harness/cli_anything/libreoffice/skills/SKILL.md",
"skill_md": "skills/cli-anything-libreoffice/SKILL.md",
"category": "office",
"contributors": [
{
@@ -304,7 +304,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=mubu/agent-harness",
"entry_point": "cli-anything-mubu",
"skill_md": "mubu/agent-harness/cli_anything/mubu/skills/SKILL.md",
"skill_md": "skills/cli-anything-mubu/SKILL.md",
"category": "office",
"contributors": [
{
@@ -342,7 +342,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=notebooklm/agent-harness",
"entry_point": "cli-anything-notebooklm",
"skill_md": "notebooklm/agent-harness/cli_anything/notebooklm/skills/SKILL.md",
"skill_md": "skills/cli-anything-notebooklm/SKILL.md",
"category": "ai",
"contributors": [
{
@@ -361,7 +361,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=ollama/agent-harness",
"entry_point": "cli-anything-ollama",
"skill_md": "ollama/agent-harness/cli_anything/ollama/skills/SKILL.md",
"skill_md": "skills/cli-anything-ollama/SKILL.md",
"category": "ai",
"contributors": [
{
@@ -380,7 +380,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=obs-studio/agent-harness",
"entry_point": "cli-anything-obs-studio",
"skill_md": "obs-studio/agent-harness/cli_anything/obs_studio/skills/SKILL.md",
"skill_md": "skills/cli-anything-obs-studio/SKILL.md",
"category": "streaming",
"contributors": [
{
@@ -399,7 +399,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=shotcut/agent-harness",
"entry_point": "cli-anything-shotcut",
"skill_md": "shotcut/agent-harness/cli_anything/shotcut/skills/SKILL.md",
"skill_md": "skills/cli-anything-shotcut/SKILL.md",
"category": "video",
"contributors": [
{
@@ -418,7 +418,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=openscreen/agent-harness",
"entry_point": "cli-anything-openscreen",
"skill_md": "openscreen/agent-harness/cli_anything/openscreen/skills/SKILL.md",
"skill_md": "skills/cli-anything-openscreen/SKILL.md",
"category": "video",
"contributors": [
{
@@ -437,7 +437,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=zoom/agent-harness",
"entry_point": "cli-anything-zoom",
"skill_md": "zoom/agent-harness/cli_anything/zoom/skills/SKILL.md",
"skill_md": "skills/cli-anything-zoom/SKILL.md",
"category": "communication",
"contributors": [
{
@@ -456,7 +456,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=novita/agent-harness",
"entry_point": "cli-anything-novita",
"skill_md": "novita/agent-harness/cli_anything/novita/skills/SKILL.md",
"skill_md": "skills/cli-anything-novita/SKILL.md",
"category": "ai",
"contributors": [
{
@@ -475,7 +475,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=seaclip/agent-harness",
"entry_point": "cli-anything-seaclip",
"skill_md": "seaclip/agent-harness/cli_anything/seaclip/skills/SKILL.md",
"skill_md": "skills/cli-anything-seaclip/SKILL.md",
"category": "project-management",
"contributors": [
{
@@ -494,7 +494,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=pm2/agent-harness",
"entry_point": "cli-anything-pm2",
"skill_md": "pm2/agent-harness/cli_anything/pm2/skills/SKILL.md",
"skill_md": "skills/cli-anything-pm2/SKILL.md",
"category": "devops",
"contributors": [
{
@@ -527,7 +527,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=chromadb/agent-harness",
"entry_point": "cli-anything-chromadb",
"skill_md": "chromadb/agent-harness/cli_anything/chromadb/skills/SKILL.md",
"skill_md": "skills/cli-anything-chromadb/SKILL.md",
"category": "database",
"contributors": [
{
@@ -546,7 +546,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=musescore/agent-harness",
"entry_point": "cli-anything-musescore",
"skill_md": "musescore/agent-harness/cli_anything/musescore/skills/SKILL.md",
"skill_md": "skills/cli-anything-musescore/SKILL.md",
"category": "music",
"contributors": [
{
@@ -584,7 +584,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=freecad/agent-harness",
"entry_point": "cli-anything-freecad",
"skill_md": "freecad/agent-harness/cli_anything/freecad/skills/SKILL.md",
"skill_md": "skills/cli-anything-freecad/SKILL.md",
"category": "3d",
"contributors": [
{
@@ -603,7 +603,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=iterm2/agent-harness",
"entry_point": "cli-anything-iterm2",
"skill_md": "iterm2/agent-harness/cli_anything/iterm2_ctl/skills/SKILL.md",
"skill_md": "skills/cli-anything-iterm2/SKILL.md",
"category": "devops",
"contributors": [
{
@@ -622,7 +622,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=slay_the_spire_ii/agent-harness",
"entry_point": "cli-anything-sts2",
"skill_md": "slay_the_spire_ii/agent-harness/cli_anything/slay_the_spire_ii/skills/SKILL.md",
"skill_md": "skills/cli-anything-slay-the-spire-ii/SKILL.md",
"category": "game",
"contributors": [
{
@@ -641,7 +641,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=rms/agent-harness",
"entry_point": "cli-anything-rms",
"skill_md": "rms/agent-harness/cli_anything/rms/skills/SKILL.md",
"skill_md": "skills/cli-anything-rms/SKILL.md",
"category": "network",
"contributors": [
{
@@ -660,7 +660,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=renderdoc/agent-harness",
"entry_point": "cli-anything-renderdoc",
"skill_md": "renderdoc/agent-harness/cli_anything/renderdoc/skills/SKILL.md",
"skill_md": "skills/cli-anything-renderdoc/SKILL.md",
"category": "graphics",
"contributors": [
{
@@ -679,7 +679,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=videocaptioner/agent-harness",
"entry_point": "cli-anything-videocaptioner",
"skill_md": "videocaptioner/agent-harness/cli_anything/videocaptioner/skills/SKILL.md",
"skill_md": "skills/cli-anything-videocaptioner/SKILL.md",
"category": "video",
"contributors": [
{
@@ -698,7 +698,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=intelwatch/agent-harness",
"entry_point": "cli-anything-intelwatch",
"skill_md": "intelwatch/agent-harness/cli_anything/intelwatch/skills/SKILL.md",
"skill_md": "skills/cli-anything-intelwatch/SKILL.md",
"category": "osint",
"contributors": [
{
@@ -736,7 +736,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=cloudcompare/agent-harness",
"entry_point": "cli-anything-cloudcompare",
"skill_md": "cloudcompare/agent-harness/cli_anything/cloudcompare/skills/SKILL.md",
"skill_md": "skills/cli-anything-cloudcompare/SKILL.md",
"category": "graphics",
"contributors": [
{
@@ -755,7 +755,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=exa/agent-harness",
"entry_point": "cli-anything-exa",
"skill_md": "exa/agent-harness/cli_anything/exa/skills/SKILL.md",
"skill_md": "skills/cli-anything-exa/SKILL.md",
"category": "search",
"contributors": [
{
@@ -774,7 +774,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=godot/agent-harness",
"entry_point": "cli-anything-godot",
"skill_md": "godot/agent-harness/cli_anything/godot/skills/SKILL.md",
"skill_md": "skills/cli-anything-godot/SKILL.md",
"category": "gamedev",
"contributors": [
{
@@ -793,7 +793,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=dify-workflow/agent-harness",
"entry_point": "cli-anything-dify-workflow",
"skill_md": "dify-workflow/agent-harness/cli_anything/dify_workflow/skills/SKILL.md",
"skill_md": "skills/cli-anything-dify-workflow/SKILL.md",
"category": "ai",
"contributors": [
{
@@ -812,7 +812,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=n8n/agent-harness",
"entry_point": "cli-anything-n8n",
"skill_md": "n8n/agent-harness/cli_anything/n8n/skills/SKILL.md",
"skill_md": "skills/cli-anything-n8n/SKILL.md",
"category": "automation",
"contributors": [
{
@@ -831,7 +831,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=cloudanalyzer/agent-harness",
"entry_point": "cli-anything-cloudanalyzer",
"skill_md": "cloudanalyzer/agent-harness/cli_anything/cloudanalyzer/skills/SKILL.md",
"skill_md": "skills/cli-anything-cloudanalyzer/SKILL.md",
"category": "graphics",
"contributors": [
{
@@ -850,7 +850,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=obsidian/agent-harness",
"entry_point": "cli-anything-obsidian",
"skill_md": "obsidian/agent-harness/cli_anything/obsidian/skills/SKILL.md",
"skill_md": "skills/cli-anything-obsidian/SKILL.md",
"category": "knowledge",
"contributors": [
{
@@ -869,7 +869,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=unimol_tools/agent-harness",
"entry_point": "cli-anything-unimol-tools",
"skill_md": "unimol_tools/agent-harness/cli_anything/unimol_tools/SKILL.md",
"skill_md": "skills/cli-anything-unimol-tools/SKILL.md",
"category": "science",
"contributors": [
{
@@ -888,7 +888,7 @@
"source_url": null,
"install_cmd": "pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=safari/agent-harness",
"entry_point": "cli-anything-safari",
"skill_md": "safari/agent-harness/cli_anything/safari/skills/SKILL.md",
"skill_md": "skills/cli-anything-safari/SKILL.md",
"category": "web",
"contributors": [
{

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -106,27 +120,44 @@ class ReplSkin:
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the package's skills/ directory if not provided.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Auto-detect skill path from package layout:
# cli_anything/<software>/utils/repl_skin.py (this file)
# cli_anything/<software>/skills/SKILL.md (target)
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
from pathlib import Path
_auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if _auto.is_file():
skill_path = str(_auto)
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -156,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -164,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -178,19 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
# Skill path for agent discovery
skill_line = None
if self.skill_path:
skill_icon = self._c(_MAGENTA, "")
skill_label = self._c(_DARK_GRAY, " Skill:")
skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
if skill_line:
print(_box_line(skill_line))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -47,9 +48,6 @@ _ACCENT_COLORS = {
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
"anygen": "\033[38;5;141m", # soft violet
"novita": "\033[38;5;81m", # vivid blue (for Novita AI)
"rms": "\033[38;5;27m", # Teltonika blue
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -60,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -92,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -109,27 +120,44 @@ class ReplSkin:
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the package's skills/ directory if not provided.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Auto-detect skill path from package layout:
# cli_anything/<software>/utils/repl_skin.py (this file)
# cli_anything/<software>/skills/SKILL.md (target)
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
from pathlib import Path
_auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if _auto.is_file():
skill_path = str(_auto)
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -159,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -167,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -181,19 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
# Skill path for agent discovery
skill_line = None
if self.skill_path:
skill_icon = self._c(_MAGENTA, "")
skill_label = self._c(_DARK_GRAY, " Skill:")
skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
if skill_line:
print(_box_line(skill_line))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -47,7 +48,6 @@ _ACCENT_COLORS = {
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
"safari": "\033[38;5;33m", # Safari blue
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -58,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -90,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -107,27 +120,44 @@ class ReplSkin:
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the package's skills/ directory if not provided.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Auto-detect skill path from package layout:
# cli_anything/<software>/utils/repl_skin.py (this file)
# cli_anything/<software>/skills/SKILL.md (target)
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
from pathlib import Path
_auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
if _auto.is_file():
skill_path = str(_auto)
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -157,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -165,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -179,19 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
# Skill path for agent discovery
skill_line = None
if self.skill_path:
skill_icon = self._c(_MAGENTA, "")
skill_label = self._c(_DARK_GRAY, " Skill:")
skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
if skill_line:
print(_box_line(skill_line))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

View File

@@ -6,20 +6,21 @@ Copy this file into your CLI package at:
Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("ollama", version="1.0.0")
skin.print_banner()
prompt_text = skin.prompt(project_name="llama3.2", modified=False)
skin.success("Model pulled")
skin.error("Connection failed")
skin.warning("No models loaded")
skin.info("Generating...")
skin.status("Model", "llama3.2:latest")
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
skin.warning("Unsaved changes")
skin.info("Processing 24 clips...")
skin.status("Track 1", "3 clips, 00:02:30")
skin.table(headers, rows)
skin.print_goodbye()
"""
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -47,7 +48,6 @@ _ACCENT_COLORS = {
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
"ollama": "\033[38;5;255m", # white (Ollama branding)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -58,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -90,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -98,23 +111,53 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
software: Software name (e.g., "gimp", "shotcut", "ollama").
software: Software name (e.g., "gimp", "shotcut", "blender").
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -144,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -152,10 +197,28 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
# Title: ◆ cli-anything · Ollama
# Title: ◆ cli-anything · Shotcut
icon = self._c(_CYAN + _BOLD, "")
brand = self._c(_CYAN + _BOLD, "cli-anything")
dot = self._c(_DARK_GRAY, "·")
@@ -166,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)
@@ -496,5 +564,4 @@ _ANSI_256_TO_HEX = {
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
"\033[38;5;255m": "#eeeeee", # ollama white
}

View File

@@ -7,7 +7,7 @@ Usage:
from cli_anything.<software>.utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner()
skin.print_banner() # auto-detects repo-root or packaged SKILL.md
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -20,6 +20,7 @@ Usage:
import os
import sys
from pathlib import Path
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -57,6 +58,8 @@ _RED = "\033[38;5;196m"
_BLUE = "\033[38;5;75m"
_MAGENTA = "\033[38;5;176m"
_SKILL_SOURCE_REPO = os.environ.get("CLI_ANYTHING_SKILL_REPO", "HKUDS/CLI-Anything")
# ── Brand icon ────────────────────────────────────────────────────────
# The cli-anything icon: a small colored diamond/chevron mark
@@ -89,6 +92,17 @@ def _visible_len(text: str) -> int:
return len(_strip_ansi(text))
def _display_home_path(path: str) -> str:
"""Display a path relative to the home directory when possible."""
expanded = Path(path).expanduser().resolve()
home = Path.home().resolve()
try:
relative = expanded.relative_to(home)
return f"~/{relative.as_posix()}"
except ValueError:
return str(expanded)
class ReplSkin:
"""Unified REPL skin for cli-anything CLIs.
@@ -97,7 +111,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
history_file: str | None = None):
history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,15 +119,45 @@ class ReplSkin:
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-<software>/history
skill_path: Path to the SKILL.md file for agent discovery.
Auto-detected from the repo-root skills/ tree when present,
otherwise from the package's skills/ directory.
Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
software_aliases = {"iterm2_ctl": "iterm2"}
self.skill_slug = software_aliases.get(self.software, self.software).replace("_", "-")
self.skill_id = f"cli-anything-{self.skill_slug}"
self.skill_install_cmd = (
f"npx skills add {_SKILL_SOURCE_REPO} --skill {self.skill_id} -g -y"
)
global_skill_root = Path(
os.environ.get("CLI_ANYTHING_GLOBAL_SKILLS_DIR", str(Path.home() / ".agents" / "skills"))
).expanduser()
self.global_skill_path = str(global_skill_root / self.skill_id / "SKILL.md")
# Prefer repo-root canonical skills/<skill-id>/SKILL.md when running
# inside the CLI-Anything monorepo. Fall back to the packaged
# cli_anything/<software>/skills/SKILL.md for installed harnesses.
if skill_path is None:
package_skill = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
repo_skill = None
for parent in Path(__file__).resolve().parents:
candidate = parent / "skills" / self.skill_id / "SKILL.md"
if candidate.is_file():
repo_skill = candidate
break
if repo_skill and repo_skill.is_file():
skill_path = str(repo_skill)
elif package_skill.is_file():
skill_path = str(package_skill)
self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -143,7 +187,9 @@ class ReplSkin:
def print_banner(self):
"""Print the startup banner with branding."""
inner = 54
import textwrap
inner = 72
def _box_line(content: str) -> str:
"""Wrap content in box drawing, padding to inner width."""
@@ -151,6 +197,24 @@ class ReplSkin:
vl = self._c(_DARK_GRAY, _V_LINE)
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
def _meta_lines(label: str, value: str) -> list[str]:
"""Wrap a metadata line for the banner box."""
icon = self._c(_MAGENTA, "")
label_text = self._c(_DARK_GRAY, label)
prefix = f" {icon} {label_text} "
available = max(12, inner - _visible_len(prefix))
wrapped = textwrap.wrap(
value,
width=available,
break_long_words=True,
break_on_hyphens=False,
) or [""]
lines = [f"{prefix}{self._c(_LIGHT_GRAY, wrapped[0])}"]
continuation_prefix = " " * _visible_len(prefix)
for chunk in wrapped[1:]:
lines.append(f"{continuation_prefix}{self._c(_LIGHT_GRAY, chunk)}")
return lines
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
@@ -165,9 +229,14 @@ class ReplSkin:
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
empty = ""
meta_lines: list[str] = []
meta_lines.extend(_meta_lines("Install:", self.skill_install_cmd))
meta_lines.extend(_meta_lines("Global skill:", _display_home_path(self.global_skill_path)))
print(top)
print(_box_line(title))
print(_box_line(ver))
for line in meta_lines:
print(_box_line(line))
print(_box_line(empty))
print(_box_line(tip))
print(bot)

31
skills/README.md Normal file
View File

@@ -0,0 +1,31 @@
# CLI-Anything Skills
This directory is the canonical `npx skills` surface for in-repo CLI-Anything
harnesses.
Layout:
```text
skills/
cli-anything-audacity/SKILL.md
cli-anything-blender/SKILL.md
...
```
Typical usage:
```bash
npx skills add HKUDS/CLI-Anything --list
npx skills add HKUDS/CLI-Anything --skill cli-anything-audacity -g -y
```
The `SKILL.md` files here are the canonical repo-root copies. Installed harness
packages still ship compatibility copies inside `cli_anything/<software>/skills/`
for local runtime discovery.
CI rule:
- If a harness keeps a deep packaged `SKILL.md`, it must also have a matching
repo-root `skills/<skill-id>/SKILL.md`.
- A future harness that only defines its canonical skill directly in `skills/`
is also valid.

View File

@@ -0,0 +1,255 @@
---
name: "cli-anything-adguardhome"
description: >-
Command-line interface for AdGuard Home - Network-wide ad blocking and DNS management via AdGuard Home REST API. Designed for AI agents and power users who need to manage filtering, DNS rewrites, clients, DHCP, and query logs without a GUI.
---
# cli-anything-adguardhome
Network-wide ad blocking and DNS management via the AdGuard Home REST API. Designed for AI agents and power users who need to manage filtering, DNS rewrites, clients, DHCP, and query logs without a GUI.
## Installation
This CLI is installed as part of the cli-anything-adguardhome package:
```bash
pip install cli-anything-adguardhome
```
**Prerequisites:**
- Python 3.10+
- AdGuard Home must be installed and running
- Install AdGuard Home: `curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v`
## Usage
### Basic Commands
```bash
# Show help
cli-anything-adguardhome --help
# Start interactive REPL mode
cli-anything-adguardhome
# Check server status
cli-anything-adguardhome server status
# Run with JSON output (for agent consumption)
cli-anything-adguardhome --json server status
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-adguardhome
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Config
Connection and configuration management.
| Command | Description |
|---------|-------------|
| `show` | Show current connection configuration |
| `save` | Save connection settings to a config file |
| `test` | Test the connection to AdGuard Home |
### Server
Server status and control commands.
| Command | Description |
|---------|-------------|
| `status` | Show server protection status |
| `version` | Show AdGuard Home version |
| `restart` | Restart the AdGuard Home server |
### Filter
DNS filter list management.
| Command | Description |
|---------|-------------|
| `list` | List all configured filter lists |
| `status` | Show filtering status |
| `toggle` | Enable or disable filtering globally |
| `add` | Add a new filter list by URL |
| `remove` | Remove a filter list |
| `enable` | Enable a specific filter list |
| `disable` | Disable a specific filter list |
| `refresh` | Force-refresh all filter lists |
### Blocking
Parental control, safe browsing, and safe search settings.
| Command | Description |
|---------|-------------|
| `parental status` | Show parental control status |
| `parental enable` | Enable parental control |
| `parental disable` | Disable parental control |
| `safebrowsing status` | Show safe browsing status |
| `safebrowsing enable` | Enable safe browsing |
| `safebrowsing disable` | Disable safe browsing |
| `safesearch status` | Show safe search status |
| `safesearch enable` | Enable safe search |
| `safesearch disable` | Disable safe search |
### Blocked-Services
Manage blocked internet services.
| Command | Description |
|---------|-------------|
| `list` | List currently blocked services |
| `set` | Set the list of blocked services |
### Clients
Client device management.
| Command | Description |
|---------|-------------|
| `list` | List all configured clients |
| `add` | Add a new client by name and IP |
| `remove` | Remove a client |
| `show` | Show details for a specific client |
### Stats
Query statistics.
| Command | Description |
|---------|-------------|
| `show` | Show DNS query statistics |
| `reset` | Reset all statistics |
| `config` | View or update statistics retention interval |
### Log
DNS query log management.
| Command | Description |
|---------|-------------|
| `show` | Show recent DNS query log entries |
| `config` | View or update query log settings |
| `clear` | Clear the query log |
### Rewrite
DNS rewrite rules.
| Command | Description |
|---------|-------------|
| `list` | List all DNS rewrite rules |
| `add` | Add a DNS rewrite rule |
| `remove` | Remove a DNS rewrite rule |
### DHCP
DHCP server management.
| Command | Description |
|---------|-------------|
| `status` | Show DHCP server status |
| `leases` | List active DHCP leases |
| `add-static` | Add a static DHCP lease |
| `remove-static` | Remove a static DHCP lease |
### TLS
TLS/HTTPS configuration.
| Command | Description |
|---------|-------------|
| `status` | Show TLS configuration status |
## Examples
### Check Server Status
```bash
cli-anything-adguardhome server status
cli-anything-adguardhome server version
```
### Manage Filter Lists
```bash
# List current filters
cli-anything-adguardhome filter list
# Add a new blocklist
cli-anything-adguardhome filter add --url https://somehost.com/list.txt --name "My List"
# Refresh all filters
cli-anything-adguardhome filter refresh
```
### DNS Rewrites
```bash
# Add a local DNS entry
cli-anything-adguardhome rewrite add --domain "myserver.local" --answer "192.168.1.50"
# List all rewrites
cli-anything-adguardhome rewrite list
```
### Client Management
```bash
cli-anything-adguardhome clients add --name "My PC" --ip 192.168.1.100
cli-anything-adguardhome clients list
```
### Query Statistics
```bash
# Show stats (human-readable)
cli-anything-adguardhome stats show
# Show stats (JSON for agents)
cli-anything-adguardhome --json stats show
```
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-adguardhome filter list
# JSON output for agents
cli-anything-adguardhome --json filter list
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file operations
5. **Test connection first** with `config test` before other commands
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,173 @@
---
name: "cli-anything-anygen"
description: >-
Command-line interface for Anygen - A stateful command-line interface for AnyGen OpenAPI — generate professional slides, documents, webs...
---
# cli-anything-anygen
A stateful command-line interface for AnyGen OpenAPI — generate professional slides, documents, websites, diagrams, and more from natural language prompts. Designed for AI agents and power users.
## Installation
This CLI is installed as part of the cli-anything-anygen package:
```bash
pip install cli-anything-anygen
```
**Prerequisites:**
- Python 3.10+
- anygen must be installed on your system
## Usage
### Basic Commands
```bash
# Show help
cli-anything-anygen --help
# Start interactive REPL mode
cli-anything-anygen
# Create a new project
cli-anything-anygen project new -o project.json
# Run with JSON output (for agent consumption)
cli-anything-anygen --json project info -p project.json
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-anygen
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Task
Task management — create, poll, download, and run tasks.
| Command | Description |
|---------|-------------|
| `create` | Create a generation task |
| `status` | Query task status (non-blocking) |
| `poll` | Poll task until completion (blocking) |
| `download` | Download the generated file for a completed task |
| `thumbnail` | Download thumbnail image for a completed task |
| `run` | Full workflow: create, poll, download |
| `list` | List locally cached task records |
| `prepare` | Multi-turn requirement analysis before creating a task |
### File
File operations — upload reference files.
| Command | Description |
|---------|-------------|
| `upload` | Upload a reference file to get a file_token |
### Config
Configuration management — API key and settings.
| Command | Description |
|---------|-------------|
| `set` | Set a configuration value |
| `get` | Get a configuration value (or show all) |
| `delete` | Delete a configuration value |
| `path` | Show the config file path |
### Session
Session management — history, undo, redo.
| Command | Description |
|---------|-------------|
| `status` | Show session status |
| `history` | Show command history |
| `undo` | Undo last command |
| `redo` | Redo last undone command |
## Examples
### Create a New Project
Create a new anygen project file.
```bash
cli-anything-anygen project new -o myproject.json
# Or with JSON output for programmatic use
cli-anything-anygen --json project new -o myproject.json
```
### Interactive REPL Session
Start an interactive session with undo/redo support.
```bash
cli-anything-anygen
# Enter commands interactively
# Use 'help' to see available commands
# Use 'undo' and 'redo' for history navigation
```
## State Management
The CLI maintains session state with:
- **Undo/Redo**: Up to 50 levels of history
- **Project persistence**: Save/load project state as JSON
- **Session tracking**: Track modifications and changes
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-anygen project info -p project.json
# JSON output for agents
cli-anything-anygen --json project info -p project.json
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file operations
5. **Verify outputs exist** after export operations
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,244 @@
---
name: "cli-anything-audacity"
description: >-
Command-line interface for Audacity - A stateful command-line interface for audio editing, following the same patterns as the GIMP and Ble...
---
# cli-anything-audacity
A stateful command-line interface for audio editing, following the same patterns as the GIMP and Blender CLIs in this repo.
## Installation
This CLI is installed as part of the cli-anything-audacity package:
```bash
pip install cli-anything-audacity
```
**Prerequisites:**
- Python 3.10+
- audacity must be installed on your system
## Usage
### Basic Commands
```bash
# Show help
cli-anything-audacity --help
# Start interactive REPL mode
cli-anything-audacity
# Create a new project
cli-anything-audacity project new -o project.json
# Run with JSON output (for agent consumption)
cli-anything-audacity --json project info -p project.json
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-audacity
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Project
Project management commands.
| Command | Description |
|---------|-------------|
| `new` | Create a new project |
| `open` | Open an existing project |
| `save` | Save the current project |
| `info` | Show project information |
| `settings` | View or update project settings |
| `json` | Print raw project JSON |
### Track
Track management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a new track |
| `remove` | Remove a track by index |
| `list` | List all tracks |
| `set` | Set a track property (name, mute, solo, volume, pan) |
### Clip
Clip management commands.
| Command | Description |
|---------|-------------|
| `import` | Probe/import an audio file (show metadata) |
| `add` | Add an audio clip to a track |
| `remove` | Remove a clip from a track |
| `trim` | Trim a clip's start and/or end |
| `split` | Split a clip at a given time position |
| `move` | Move a clip to a new start time |
| `list` | List clips on a track |
### Effect Group
Effect management commands.
| Command | Description |
|---------|-------------|
| `list-available` | List all available effects |
| `info` | Show details about an effect |
| `add` | Add an effect to a track |
| `remove` | Remove an effect by index |
| `set` | Set an effect parameter |
| `list` | List effects on a track |
### Selection
Selection management commands.
| Command | Description |
|---------|-------------|
| `set` | Set selection range |
| `all` | Select all (entire project duration) |
| `none` | Clear selection |
| `info` | Show current selection |
### Label
Label/marker management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a label at a time position |
| `remove` | Remove a label by index |
| `list` | List all labels |
### Media
Media file operations.
| Command | Description |
|---------|-------------|
| `probe` | Analyze an audio file |
| `check` | Check that all referenced audio files exist |
### Export Group
Export/render commands.
| Command | Description |
|---------|-------------|
| `presets` | List export presets |
| `preset-info` | Show preset details |
| `render` | Render the project to an audio file |
### Session Group
Session management commands.
| Command | Description |
|---------|-------------|
| `status` | Show session status |
| `undo` | Undo the last operation |
| `redo` | Redo the last undone operation |
| `history` | Show undo history |
## Examples
### Create a New Project
Create a new audacity project file.
```bash
cli-anything-audacity project new -o myproject.json
# Or with JSON output for programmatic use
cli-anything-audacity --json project new -o myproject.json
```
### Interactive REPL Session
Start an interactive session with undo/redo support.
```bash
cli-anything-audacity
# Enter commands interactively
# Use 'help' to see available commands
# Use 'undo' and 'redo' for history navigation
```
### Export Project
Export the project to a final output format.
```bash
cli-anything-audacity --project myproject.json export render output.pdf --overwrite
```
## State Management
The CLI maintains session state with:
- **Undo/Redo**: Up to 50 levels of history
- **Project persistence**: Save/load project state as JSON
- **Session tracking**: Track modifications and changes
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-audacity project info -p project.json
# JSON output for agents
cli-anything-audacity --json project info -p project.json
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file operations
5. **Verify outputs exist** after export operations
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,241 @@
---
name: "cli-anything-blender"
description: >-
Command-line interface for Blender - A stateful command-line interface for 3D scene editing, following the same patterns as the GIMP CLI ...
---
# cli-anything-blender
A stateful command-line interface for 3D scene editing, following the same patterns as the GIMP CLI harness. Uses a JSON scene description format with bpy script generation for actual Blender rendering.
## Installation
This CLI is installed as part of the cli-anything-blender package:
```bash
pip install cli-anything-blender
```
**Prerequisites:**
- Python 3.10+
- blender (>= 4.2) must be installed on your system
## Usage
### Basic Commands
```bash
# Show help
cli-anything-blender --help
# Start interactive REPL mode
cli-anything-blender
# Create a new project
cli-anything-blender project new -o project.json
# Run with JSON output (for agent consumption)
cli-anything-blender --json project info -p project.json
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-blender
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Scene
Scene management commands.
| Command | Description |
|---------|-------------|
| `new` | Create a new scene |
| `open` | Open an existing scene |
| `save` | Save the current scene |
| `info` | Show scene information |
| `profiles` | List available scene profiles |
| `json` | Print raw scene JSON |
### Object Group
3D object management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a 3D primitive object |
| `remove` | Remove an object by index |
| `duplicate` | Duplicate an object |
| `transform` | Transform an object (translate, rotate, scale) |
| `set` | Set an object property (name, visible, location, rotation, scale, parent) |
| `list` | List all objects |
| `get` | Get detailed info about an object |
### Material
Material management commands.
| Command | Description |
|---------|-------------|
| `create` | Create a new material |
| `assign` | Assign a material to an object |
| `set` | Set a material property (color, metallic, roughness, specular, alpha, etc.) |
| `list` | List all materials |
| `get` | Get detailed info about a material |
### Modifier Group
Modifier management commands.
| Command | Description |
|---------|-------------|
| `list-available` | List all available modifiers |
| `info` | Show details about a modifier |
| `add` | Add a modifier to an object |
| `remove` | Remove a modifier by index |
| `set` | Set a modifier parameter |
| `list` | List modifiers on an object |
### Camera
Camera management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a camera to the scene |
| `set` | Set a camera property |
| `set-active` | Set the active camera |
| `list` | List all cameras |
### Light
Light management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a light to the scene |
| `set` | Set a light property |
| `list` | List all lights |
### Animation
Animation and keyframe commands.
| Command | Description |
|---------|-------------|
| `keyframe` | Set a keyframe on an object |
| `remove-keyframe` | Remove a keyframe from an object |
| `frame-range` | Set the animation frame range |
| `fps` | Set the animation FPS |
| `list-keyframes` | List keyframes for an object |
### Render Group
Render settings and output commands.
| Command | Description |
|---------|-------------|
| `settings` | Configure render settings |
| `info` | Show current render settings |
| `presets` | List available render presets |
| `execute` | Render the scene (generates bpy script) |
| `script` | Generate bpy script without rendering |
### Session
Session management commands.
| Command | Description |
|---------|-------------|
| `status` | Show session status |
| `undo` | Undo the last operation |
| `redo` | Redo the last undone operation |
| `history` | Show undo history |
## Examples
### Create a New Project
Create a new blender project file.
```bash
cli-anything-blender project new -o myproject.json
# Or with JSON output for programmatic use
cli-anything-blender --json project new -o myproject.json
```
### Interactive REPL Session
Start an interactive session with undo/redo support.
```bash
cli-anything-blender
# Enter commands interactively
# Use 'help' to see available commands
# Use 'undo' and 'redo' for history navigation
```
## State Management
The CLI maintains session state with:
- **Undo/Redo**: Up to 50 levels of history
- **Project persistence**: Save/load project state as JSON
- **Session tracking**: Track modifications and changes
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-blender project info -p project.json
# JSON output for agents
cli-anything-blender --json project info -p project.json
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **MANDATORY: Use absolute paths** for all file operations (rendering, project files). Relative paths are prone to failure in background execution.
5. **Verify outputs exist** after export operations
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,213 @@
---
name: "cli-anything-browser"
description: "Browser automation CLI using DOMShell MCP server. Maps Chrome's Accessibility Tree to a virtual filesystem for agent-native navigation."
---
# cli-anything-browser
A command-line interface for browser automation using [DOMShell](https://github.com/apireno/DOMShell)'s MCP server. Navigate web pages using filesystem commands: `ls`, `cd`, `cat`, `grep`, `click`.
## Installation
### Prerequisites
1. **Node.js and npx** (for DOMShell MCP server):
```bash
# Install Node.js from https://nodejs.org/
npx --version
```
2. **Chrome/Chromium** with [DOMShell extension](https://chromewebstore.google.com/detail/domshell-browser-filesy/okcliheamhmijccjknkkplploacoidnp):
- Install extension in Chrome
- Ensure Chrome is running before using CLI
3. **Python 3.10+**
### Install CLI
```bash
cd browser/agent-harness
pip install -e .
```
## Command Groups
### `page` — Page Navigation
- `page open <url>` — Navigate to URL
- `page reload` — Reload current page
- `page back` — Navigate back in history
- `page forward` — Navigate forward in history
- `page info` — Show current page info
### `fs` — Filesystem Commands (Accessibility Tree)
- `fs ls [path]` — List elements at path
- `fs cd <path>` — Change directory
- `fs cat [path]` — Read element content
- `fs grep <pattern> [path]` — Search for text pattern
- `fs pwd` — Print working directory
### `act` — Action Commands
- `act click <path>` — Click an element
- `act type <path> <text>` — Type text into input
### `session` — Session Management
- `session status` — Show session state
- `session daemon-start` — Start persistent daemon mode
- `session daemon-stop` — Stop daemon mode
## Usage Examples
### Basic Navigation
```bash
# Open a page
cli-anything-browser page open https://example.com
# Explore structure
cli-anything-browser fs ls /
cli-anything-browser fs cd /main
cli-anything-browser fs ls
# Go back to root
cli-anything-browser fs cd /
```
### Search and Click
```bash
cli-anything-browser fs grep "Login"
cli-anything-browser act click /main/button[0]
```
### Form Fill
```bash
cli-anything-browser act type /main/input[0] "user@example.com"
cli-anything-browser act click /main/button[0]
```
### JSON Output
```bash
cli-anything-browser --json fs ls /
```
### Daemon Mode (Faster Interactive Use)
```bash
# Start persistent connection
cli-anything-browser session daemon-start
# Run commands (uses persistent connection)
cli-anything-browser fs ls /
cli-anything-browser fs cd /main
# Stop daemon when done
cli-anything-browser session daemon-stop
```
### Interactive REPL
```bash
cli-anything-browser
```
## Path Syntax
DOMShell uses a filesystem-like path for the Accessibility Tree:
```
/ — Root (document)
/main — Main landmark
/main/div[0] — First div in main
/main/div[0]/button[2] — Third button in first div
```
- Array indices are **0-based**: `button[0]` is the first button
- Use `..` to go up one level
- Use `/` for root
## Agent-Specific Guidance
### JSON Output for Parsing
All commands support `--json` flag for machine-readable output:
```bash
cli-anything-browser --json fs ls /
```
Returns:
```json
{
"path": "/",
"entries": [
{"name": "main", "role": "landmark", "path": "/main"}
]
}
```
### Error Handling
The CLI provides clear error messages for common issues:
- **npx not found**: Install Node.js from https://nodejs.org/
- **DOMShell not found**: Run `npx @apireno/domshell --version`
- **MCP call failed**: Install DOMShell Chrome extension
Check `is_available()` return value before running commands.
### Daemon Mode for Efficiency
For agent workflows with multiple commands, use daemon mode:
1. Start daemon: `cli-anything-browser session daemon-start`
2. Run commands: Each command reuses the MCP connection
3. Stop daemon: `cli-anything-browser session daemon-stop`
This avoids the 1-3 second cold start overhead for each command.
## Links
- [DOMShell GitHub](https://github.com/apireno/DOMShell)
- [CLI-Anything](https://github.com/HKUDS/CLI-Anything)
- [Issue #90](https://github.com/HKUDS/CLI-Anything/issues/90)
## Security Considerations
**IMPORTANT**: When using this CLI with AI agents, be aware of the following security considerations:
### URL Restrictions
The browser harness validates all URLs before navigation:
- **Explicit scheme required**: URLs must include `http://` or `https://` scheme (scheme-less URLs like `example.com` are rejected)
- **Blocked schemes**: `file://`, `javascript://`, `data://`, `vbscript://`, `about://`, `chrome://`, and browser-internal schemes
- **Allowed schemes**: `http://` and `https://` only (configurable via `CLI_ANYTHING_BROWSER_ALLOWED_SCHEMES`)
- **Private network blocking**: Optional via `CLI_ANYTHING_BROWSER_BLOCK_PRIVATE=true` (disabled by default)
### DOM Content Risks
The Accessibility Tree includes all visible and hidden elements on a page. Malicious websites could:
- Craft ARIA labels with manipulative text (e.g., "Ignore previous instructions")
- Use aria-hidden elements to inject content not visible to users
- Create confusing DOM structures that mislead navigation
**Mitigation**: When interacting with untrusted websites, consider:
1. Using the `--json` flag for structured output that's easier to parse safely
2. Sanitizing or filtering DOM content before including it in prompts
3. Limiting navigation to trusted domains
### Private Network Access
By default, the browser can access localhost and private networks (192.168.x.x, 10.x.x.x, etc.). To block:
```bash
export CLI_ANYTHING_BROWSER_BLOCK_PRIVATE=true
cli-anything-browser page open http://localhost:8080 # Will be blocked
```
### Session Isolation
Multiple browser sessions share the same Chrome instance. Cookies and authentication state may persist across sessions. For sensitive operations, consider:
1. Using Chrome's guest mode or incognito
2. Clearing cookies between sessions
3. Using separate Chrome profiles for different security contexts

View File

@@ -0,0 +1,116 @@
---
name: "cli-anything-chromadb"
description: >-
Command-line interface for ChromaDB - A stateless CLI for managing vector database collections, documents, and semantic search. Designed for AI agents and automation via the ChromaDB HTTP API v2.
---
# cli-anything-chromadb
A stateless command-line interface for ChromaDB vector database, built on the HTTP API v2. Designed for AI agents and power users who need to manage collections, documents, and run semantic queries without a browser UI.
## Installation
This CLI is installed as part of the cli-anything-chromadb package:
```bash
pip install cli-anything-chromadb
```
**Prerequisites:**
- Python 3.10+
- ChromaDB server running at localhost:8000 (or specify via --host)
## Usage
### Basic Commands
```bash
# Show help
cli-anything-chromadb --help
# Start interactive REPL mode
cli-anything-chromadb
# Check server health
cli-anything-chromadb --json server heartbeat
# List all collections
cli-anything-chromadb --json collection list
# Semantic search
cli-anything-chromadb --json query search --collection hub_knowledge --text "How to deploy"
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-chromadb
# Enter commands interactively with tab-completion and history
```
## Command Groups
### server
Server health and version commands.
| Command | Description |
|---------|-------------|
| `heartbeat` | Check ChromaDB server health |
| `version` | Get ChromaDB server version |
### collection
Manage ChromaDB collections.
| Command | Description |
|---------|-------------|
| `list` | List all collections |
| `create --name NAME` | Create a new collection |
| `delete --name NAME` | Delete a collection |
| `info NAME` | Get collection info |
### document
Manage documents in collections.
| Command | Description |
|---------|-------------|
| `add --collection C --id ID --document TEXT` | Add document(s) |
| `get --collection C` | Get documents |
| `delete --collection C --id ID` | Delete document(s) |
| `count --collection C` | Count documents |
### query
Semantic search against collections.
| Command | Description |
|---------|-------------|
| `search --collection C --text T` | Semantic search |
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-chromadb server heartbeat
# JSON output for agents
cli-anything-chromadb --json server heartbeat
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use `--host`** to connect to non-default ChromaDB instances
## Version
1.0.0

View File

@@ -0,0 +1,301 @@
---
name: "cli-anything-cloudanalyzer"
description: "Command-line interface for CloudAnalyzer — Agent-friendly harness for CloudAnalyzer, a QA platform for mapping, localization, and perception outputs. Supports 27 commands across 8 groups: point cloud evaluation, trajectory evaluation, ground segmentation QA, config-driven quality gates, baseline evolution, processing, visualization, and interactive REPL."
---
# cli-anything-cloudanalyzer
Agent-friendly command-line harness for [CloudAnalyzer](https://github.com/rsasaki0109/CloudAnalyzer) — a QA platform for mapping, localization, and perception point cloud outputs.
**27 commands** across 8 groups.
## Installation
```bash
pip install cli-anything-cloudanalyzer
```
**Prerequisites:**
- Python 3.10+
- CloudAnalyzer: `pip install cloudanalyzer`
## Global Options
```bash
cli-anything-cloudanalyzer [--project FILE] [--json] COMMAND [ARGS]...
```
| Option | Description |
|---|---|
| `-p, --project TEXT` | Path to project JSON file |
| `--json` | Output results as JSON (for agent consumption) |
## Command Groups
### 1. evaluate — Point Cloud Evaluation (6 commands)
#### evaluate run
Evaluate a point cloud against a reference (Chamfer, F1, AUC, Hausdorff).
```bash
cli-anything-cloudanalyzer evaluate run source.pcd reference.pcd
cli-anything-cloudanalyzer --json evaluate run source.pcd reference.pcd
```
Options: `--plot TEXT`, `--threshold FLOAT`
#### evaluate compare
Compare two point clouds with optional registration.
```bash
cli-anything-cloudanalyzer evaluate compare src.pcd tgt.pcd --register gicp
```
Options: `--register TEXT` (icp/gicp/none)
#### evaluate diff
Quick distance statistics between two point clouds.
```bash
cli-anything-cloudanalyzer evaluate diff a.pcd b.pcd --threshold 0.1
```
#### evaluate batch
Batch evaluation of multiple point clouds against a reference.
```bash
cli-anything-cloudanalyzer --json evaluate batch results/ reference.pcd --min-auc 0.95
```
Options: `--min-auc FLOAT`, `--max-chamfer FLOAT`
#### evaluate ground
Evaluate ground segmentation quality (precision, recall, F1, IoU).
```bash
cli-anything-cloudanalyzer --json evaluate ground est_ground.pcd est_ng.pcd ref_ground.pcd ref_ng.pcd --min-f1 0.9
```
Options: `--voxel-size FLOAT`, `--min-precision FLOAT`, `--min-recall FLOAT`, `--min-f1 FLOAT`, `--min-iou FLOAT`
#### evaluate pipeline
Filter, downsample, evaluate in one command.
```bash
cli-anything-cloudanalyzer evaluate pipeline input.pcd reference.pcd -o output.pcd
```
---
### 2. trajectory — Trajectory Evaluation (3 commands)
#### trajectory evaluate
Evaluate estimated vs reference trajectory (ATE, RPE, drift, lateral, longitudinal).
```bash
cli-anything-cloudanalyzer --json trajectory evaluate est.csv gt.csv --max-ate 0.5 --max-lateral 0.3
```
Options: `--max-ate FLOAT`, `--max-rpe FLOAT`, `--max-drift FLOAT`, `--min-coverage FLOAT`, `--max-lateral FLOAT`, `--max-longitudinal FLOAT`, `--align-origin`, `--align-rigid`
#### trajectory batch
Batch trajectory evaluation.
```bash
cli-anything-cloudanalyzer trajectory batch runs/ --reference-dir gt/ --max-drift 1.0
```
#### trajectory run-evaluate
Integrated map + trajectory evaluation.
```bash
cli-anything-cloudanalyzer trajectory run-evaluate map.pcd map_ref.pcd traj.csv traj_ref.csv
```
Options: `--min-auc FLOAT`, `--max-ate FLOAT`
---
### 3. check — Config-Driven Quality Gate (2 commands)
#### check run
Run unified QA from a config file.
```bash
cli-anything-cloudanalyzer --json check run cloudanalyzer.yaml
```
Options: `--output-json TEXT`
#### check init
Generate a starter config file.
```bash
cli-anything-cloudanalyzer check init cloudanalyzer.yaml --profile integrated
```
Options: `--profile TEXT` (mapping/localization/perception/integrated), `--force`
---
### 4. baseline — Baseline Evolution (3 commands)
#### baseline decision
Decide whether to promote, keep, or reject a candidate baseline.
```bash
cli-anything-cloudanalyzer --json baseline decision qa/summary.json --history-dir qa/history/
```
Options: `--history TEXT` (repeatable), `--history-dir TEXT`, `--output-json TEXT`
#### baseline save
Save a QA summary to the history directory.
```bash
cli-anything-cloudanalyzer baseline save qa/summary.json --history-dir qa/history/ --keep 10
```
Options: `--history-dir TEXT`, `--label TEXT`, `--keep INTEGER`
#### baseline list
List saved baselines.
```bash
cli-anything-cloudanalyzer --json baseline list --history-dir qa/history/
```
---
### 5. process — Point Cloud Processing (6 commands)
#### process downsample
Voxel grid downsampling.
```bash
cli-anything-cloudanalyzer process downsample cloud.pcd -o down.pcd -v 0.05
```
#### process sample
Random point sampling.
```bash
cli-anything-cloudanalyzer process sample cloud.pcd -o sampled.pcd -n 10000
```
#### process filter
Statistical outlier removal.
```bash
cli-anything-cloudanalyzer process filter cloud.pcd -o filtered.pcd
```
#### process split
Split point cloud into grid tiles (writes metadata.yaml).
```bash
cli-anything-cloudanalyzer process split large.pcd -o tiles/ -g 100
```
#### process merge
Merge multiple point clouds.
```bash
cli-anything-cloudanalyzer process merge a.pcd b.pcd -o merged.pcd
```
#### process convert
Convert between point cloud formats.
```bash
cli-anything-cloudanalyzer process convert input.las -o output.pcd
```
---
### 6. inspect — Visualization (3 commands)
#### inspect view
Open a point cloud viewer.
```bash
cli-anything-cloudanalyzer inspect view cloud.pcd
```
#### inspect web
Interactive browser inspection.
```bash
cli-anything-cloudanalyzer inspect web map.pcd ref.pcd --heatmap
```
#### inspect web-export
Export a static HTML inspection bundle.
```bash
cli-anything-cloudanalyzer inspect web-export map.pcd ref.pcd -o bundle/
```
---
### 7. info — Metadata (2 commands)
#### info show
Show point cloud metadata.
```bash
cli-anything-cloudanalyzer --json info show cloud.pcd
```
#### info version
Show CloudAnalyzer version.
---
### 8. session — Session Management (2 commands)
#### session new
Create a new harness project JSON file.
```bash
cli-anything-cloudanalyzer session new -o project.json -n my-run
```
#### session history
Show recent operations for the project given with `-p` / `--project`.
```bash
cli-anything-cloudanalyzer --project project.json session history --last 20
```
---
## Typical Agent Workflows
### Workflow 1: Evaluate and gate a point cloud
```bash
cli-anything-cloudanalyzer --json evaluate run output.pcd reference.pcd
```
### Workflow 2: Config-driven QA pipeline
```bash
cli-anything-cloudanalyzer check init cloudanalyzer.yaml --profile integrated
cli-anything-cloudanalyzer --json check run cloudanalyzer.yaml
```
### Workflow 3: Baseline management
```bash
cli-anything-cloudanalyzer --json check run cloudanalyzer.yaml --output-json qa/summary.json
cli-anything-cloudanalyzer baseline save qa/summary.json --history-dir qa/history/
cli-anything-cloudanalyzer --json baseline decision qa/summary.json --history-dir qa/history/
```
### Workflow 4: Ground segmentation QA
```bash
cli-anything-cloudanalyzer --json evaluate ground \
est_ground.pcd est_ng.pcd ref_ground.pcd ref_ng.pcd --min-f1 0.9
```

View File

@@ -0,0 +1,789 @@
---
name: "cli-anything-cloudcompare"
description: "Command-line interface for CloudCompare — Agent-friendly harness for CloudCompare, the open-source 3D point cloud and mesh processing software. Supports 41 commands across 9 groups: project management, session control, point cloud operations (subsample, filter, segment, analyze), mesh operations, distance computation (C2C, C2M), transformations (ICP, matrix), export (LAS/LAZ/PLY/PCD/OBJ/STL/E57), and interactive REPL."
---
# cli-anything-cloudcompare
Agent-friendly command-line harness for [CloudCompare](https://cloudcompare.org) — the open-source 3D point cloud and mesh processing software.
**41 commands** across 9 groups.
## Installation
```bash
pip install cli-anything-cloudcompare
```
**Prerequisites:**
- Python 3.10+
- CloudCompare installed on your system
- Linux (Flatpak): `flatpak install flathub org.cloudcompare.CloudCompare`
- macOS/Windows: download from https://cloudcompare.org
**Tested with:** CloudCompare 2.13.2 (Flatpak, Linux)
## Global Options
These options must be placed **before** the subcommand:
```bash
cli-anything-cloudcompare [--project FILE] [--json] COMMAND [ARGS]...
```
| Option | Description |
|---|---|
| `-p, --project TEXT` | Path to project JSON file |
| `--json` | Output results as JSON (for agent consumption) |
## Command Groups
### 1. project — Project Management (3 commands)
#### project new
Create a new empty project file.
```bash
# Create a project with default name
cli-anything-cloudcompare project new -o myproject.json
# Create a project with a custom name
cli-anything-cloudcompare project new -o myproject.json -n "Bridge Survey 2024"
# JSON output for agents
cli-anything-cloudcompare --json project new -o myproject.json
```
Options: `-o/--output TEXT` (required), `-n/--name TEXT`
#### project info
Show project info and loaded entities.
```bash
cli-anything-cloudcompare --project myproject.json project info
# JSON output
cli-anything-cloudcompare --project myproject.json --json project info
```
#### project status
Show quick project status (cloud count, mesh count, last operation).
```bash
cli-anything-cloudcompare --project myproject.json project status
```
---
### 2. session — Session Management (4 commands)
#### session save
Save the current project state to disk.
```bash
cli-anything-cloudcompare --project myproject.json session save
```
#### session history
Show recent operation history.
```bash
# Show last 10 operations (default)
cli-anything-cloudcompare --project myproject.json session history
# Show last 5 operations
cli-anything-cloudcompare --project myproject.json session history -n 5
```
Options: `-n/--last INTEGER`
#### session set-format
Update the default export format for future operations.
```bash
# Set default cloud export to LAS
cli-anything-cloudcompare --project myproject.json session set-format --cloud-fmt LAS --cloud-ext las
# Set default mesh export to OBJ
cli-anything-cloudcompare --project myproject.json session set-format --mesh-fmt OBJ --mesh-ext obj
# Set both cloud and mesh defaults
cli-anything-cloudcompare --project myproject.json session set-format \
--cloud-fmt PLY --cloud-ext ply \
--mesh-fmt STL --mesh-ext stl
```
Options: `--cloud-fmt TEXT`, `--cloud-ext TEXT`, `--mesh-fmt TEXT`, `--mesh-ext TEXT`
#### session undo
Remove the last operation from history (soft undo — does not delete output files).
```bash
cli-anything-cloudcompare --project myproject.json session undo
```
---
### 3. cloud — Point Cloud Operations (21 commands)
All cloud commands take `CLOUD_INDEX` (0-based integer from `cloud list`) and most accept `--add-to-project` to register the output back into the project.
#### cloud add
Add a point cloud file to the project.
```bash
# Add a LAS file
cli-anything-cloudcompare --project myproject.json cloud add /data/scan.las
# Add with a label
cli-anything-cloudcompare --project myproject.json cloud add /data/scan.las -l "roof scan"
```
Options: `-l/--label TEXT`
#### cloud list
List all clouds currently in the project.
```bash
cli-anything-cloudcompare --project myproject.json cloud list
# JSON output for parsing indices
cli-anything-cloudcompare --project myproject.json --json cloud list
```
#### cloud convert
Convert a cloud from one format to another (format determined by file extension).
```bash
# LAS → PLY
cli-anything-cloudcompare cloud convert /data/scan.las /data/scan.ply
# PCD → LAS
cli-anything-cloudcompare cloud convert /data/cloud.pcd /data/cloud.las
```
#### cloud subsample
Reduce the number of points using RANDOM, SPATIAL, or OCTREE method.
```bash
# Random: keep 100 000 points
cli-anything-cloudcompare --project myproject.json cloud subsample 0 \
-o /data/sub_random.las -m random -n 100000
# Spatial: minimum distance 0.05 m between points
cli-anything-cloudcompare --project myproject.json cloud subsample 0 \
-o /data/sub_spatial.las -m spatial -n 0.05
# Octree: level 8
cli-anything-cloudcompare --project myproject.json cloud subsample 0 \
-o /data/sub_octree.las -m octree -n 8 --add-to-project
```
Options: `-o/--output TEXT` (required), `-m/--method [random|spatial|octree]`, `-n/--param FLOAT`, `--add-to-project`
#### cloud crop
Crop a cloud to an axis-aligned bounding box.
```bash
# Keep points inside the box
cli-anything-cloudcompare --project myproject.json cloud crop 0 \
-o /data/cropped.las \
--xmin 0.0 --ymin 0.0 --zmin 0.0 \
--xmax 10.0 --ymax 10.0 --zmax 5.0
# Keep points OUTSIDE the box
cli-anything-cloudcompare --project myproject.json cloud crop 0 \
-o /data/exterior.las \
--xmin 0.0 --ymin 0.0 --zmin 0.0 \
--xmax 10.0 --ymax 10.0 --zmax 5.0 --outside
```
Options: `-o/--output TEXT` (required), `--xmin/ymin/zmin/xmax/ymax/zmax FLOAT` (all required), `--outside`, `--add-to-project`
#### cloud normals
Compute surface normals via the octree method.
```bash
# Compute normals at octree level 6
cli-anything-cloudcompare --project myproject.json cloud normals 0 \
-o /data/with_normals.ply --level 6
# Compute normals oriented toward +Z
cli-anything-cloudcompare --project myproject.json cloud normals 0 \
-o /data/with_normals.ply --level 6 --orientation plus_z --add-to-project
```
Options: `-o/--output TEXT` (required), `--level INTEGER` (110), `--orientation [plus_x|plus_y|plus_z|minus_x|minus_y|minus_z]`, `--add-to-project`
#### cloud invert-normals
Flip all normal vectors in the cloud.
```bash
cli-anything-cloudcompare --project myproject.json cloud invert-normals 0 \
-o /data/flipped_normals.ply --add-to-project
```
Options: `-o/--output TEXT` (required), `--add-to-project`
#### cloud filter-sor
Statistical Outlier Removal — removes isolated noise points.
```bash
# Default parameters (k=6 neighbours, 1.0 std ratio)
cli-anything-cloudcompare --project myproject.json cloud filter-sor 0 \
-o /data/denoised.las
# Custom parameters
cli-anything-cloudcompare --project myproject.json cloud filter-sor 0 \
-o /data/denoised.las --nb-points 12 --std-ratio 2.0 --add-to-project
```
Options: `-o/--output TEXT` (required), `--nb-points INTEGER`, `--std-ratio FLOAT`, `--add-to-project`
#### cloud noise-filter
Remove noisy points using the PCL noise filter (KNN or radius mode).
```bash
# KNN mode (default)
cli-anything-cloudcompare --project myproject.json cloud noise-filter 0 \
-o /data/clean.las --knn 8 --noisiness 1.0
# Radius mode
cli-anything-cloudcompare --project myproject.json cloud noise-filter 0 \
-o /data/clean.las --radius 0.1 --use-radius --add-to-project
```
Options: `-o/--output TEXT` (required), `--knn INTEGER`, `--noisiness FLOAT`, `--radius FLOAT`, `--use-radius`, `--absolute`, `--add-to-project`
#### cloud filter-csf
Ground filtering using the Cloth Simulation Filter (CSF) algorithm. Separates ground from off-ground points (buildings, vegetation).
```bash
# Extract ground only — mixed terrain
cli-anything-cloudcompare --project myproject.json cloud filter-csf 0 \
--ground /data/ground.las --scene relief
# Split ground + off-ground — urban scene
cli-anything-cloudcompare --project myproject.json cloud filter-csf 0 \
--ground /data/ground.las \
--offground /data/buildings.las \
--scene flat --cloth-resolution 0.5 --class-threshold 0.3
# Steep forested slope with slope post-processing
cli-anything-cloudcompare --project myproject.json cloud filter-csf 0 \
--ground /data/terrain.las --scene slope --proc-slope --add-to-project
```
Options: `-g/--ground TEXT` (required), `-u/--offground TEXT`, `--scene [slope|relief|flat]`, `--cloth-resolution FLOAT`, `--class-threshold FLOAT`, `--max-iteration INTEGER`, `--proc-slope`, `--add-to-project`
#### cloud filter-sf
Filter a cloud by scalar field value range (keep points where SF ∈ [min, max]).
```bash
# Keep points with SF value between 10 and 50
cli-anything-cloudcompare --project myproject.json cloud filter-sf 0 \
-o /data/filtered.las --min 10.0 --max 50.0
# Filter using a specific SF index
cli-anything-cloudcompare --project myproject.json cloud filter-sf 0 \
-o /data/filtered.las --min 0.0 --max 1.5 --sf-index 2 --add-to-project
```
Options: `-o/--output TEXT` (required), `--min FLOAT` (required), `--max FLOAT` (required), `--sf-index INTEGER`, `--add-to-project`
#### cloud sf-from-coord
Convert a coordinate axis (X/Y/Z) to a scalar field. Commonly used to create a height (Z) scalar field.
```bash
# Create Z scalar field (height)
cli-anything-cloudcompare --project myproject.json cloud sf-from-coord 0 \
-o /data/with_z_sf.las --dim z --add-to-project
# Create X scalar field with a specific active index
cli-anything-cloudcompare --project myproject.json cloud sf-from-coord 0 \
-o /data/with_x_sf.las --dim x --sf-index 0
```
Options: `-o/--output TEXT` (required), `--dim [x|y|z]` (default: z), `--sf-index INTEGER`, `--add-to-project`
#### cloud sf-filter-z
Convenience command: convert Z → scalar field and filter by height range in one step.
```bash
# Extract points between z=1.0 m and z=2.5 m
cli-anything-cloudcompare --project myproject.json cloud sf-filter-z 0 \
-o /data/slice.las --min 1.0 --max 2.5 --add-to-project
# Only apply upper bound
cli-anything-cloudcompare --project myproject.json cloud sf-filter-z 0 \
-o /data/below_5m.las --max 5.0
```
Options: `-o/--output TEXT` (required), `--min FLOAT`, `--max FLOAT`, `--add-to-project`
#### cloud sf-to-rgb
Convert the active scalar field to RGB colours.
```bash
cli-anything-cloudcompare --project myproject.json cloud sf-to-rgb 0 \
-o /data/coloured.ply --add-to-project
```
Options: `-o/--output TEXT` (required), `--add-to-project`
#### cloud rgb-to-sf
Convert RGB colours to a scalar field (luminance value).
```bash
cli-anything-cloudcompare --project myproject.json cloud rgb-to-sf 0 \
-o /data/luminance.las --add-to-project
```
Options: `-o/--output TEXT` (required), `--add-to-project`
#### cloud curvature
Compute curvature scalar field (MEAN or GAUSS).
```bash
# Mean curvature with radius 0.5 m
cli-anything-cloudcompare --project myproject.json cloud curvature 0 \
-o /data/curvature.las --type mean --radius 0.5
# Gaussian curvature
cli-anything-cloudcompare --project myproject.json cloud curvature 0 \
-o /data/curvature.las --type gauss --radius 0.5 --add-to-project
```
Options: `-o/--output TEXT` (required), `--type [mean|gauss]`, `-r/--radius FLOAT`, `--add-to-project`
#### cloud roughness
Compute roughness scalar field (deviation from local best-fit plane).
```bash
cli-anything-cloudcompare --project myproject.json cloud roughness 0 \
-o /data/roughness.las --radius 0.2 --add-to-project
```
Options: `-o/--output TEXT` (required), `-r/--radius FLOAT`, `--add-to-project`
#### cloud density
Compute point density scalar field.
```bash
# KNN density
cli-anything-cloudcompare --project myproject.json cloud density 0 \
-o /data/density.las --type knn --radius 0.5
# Surface density
cli-anything-cloudcompare --project myproject.json cloud density 0 \
-o /data/density.las --type surface --radius 1.0 --add-to-project
```
Options: `-o/--output TEXT` (required), `-r/--radius FLOAT`, `--type [knn|surface|volume]`, `--add-to-project`
#### cloud segment-cc
Segment cloud into connected components (clusters). Each component is saved as a separate file.
```bash
# Segment with octree level 8, minimum 100 points per component
cli-anything-cloudcompare --project myproject.json cloud segment-cc 0 \
-o /data/components/ --octree-level 8 --min-points 100
# Save components as PLY files
cli-anything-cloudcompare --project myproject.json cloud segment-cc 0 \
-o /data/components/ --octree-level 6 --min-points 50 --fmt ply
```
Options: `-o/--output-dir TEXT` (required), `--octree-level INTEGER`, `--min-points INTEGER`, `--fmt TEXT`
#### cloud merge
Merge all clouds in the project into a single cloud.
```bash
cli-anything-cloudcompare --project myproject.json cloud merge \
-o /data/merged.las --add-to-project
```
Options: `-o/--output TEXT` (required), `--add-to-project`
#### cloud mesh-delaunay
Build a 2.5-D Delaunay triangulation mesh from a cloud.
```bash
# Basic Delaunay mesh
cli-anything-cloudcompare --project myproject.json cloud mesh-delaunay 0 \
-o /data/surface.obj
# Best-fit plane with max edge length limit
cli-anything-cloudcompare --project myproject.json cloud mesh-delaunay 0 \
-o /data/surface.ply --best-fit --max-edge-length 2.0 --add-to-project
```
Options: `-o/--output TEXT` (required), `--best-fit`, `--max-edge-length FLOAT`, `--add-to-project`
---
### 4. mesh — Mesh Operations (3 commands)
#### mesh add
Add a mesh file to the project.
```bash
cli-anything-cloudcompare --project myproject.json mesh add /data/model.obj
# Add with label
cli-anything-cloudcompare --project myproject.json mesh add /data/model.ply -l "building model"
```
Options: `-l/--label TEXT`
#### mesh list
List all meshes in the project.
```bash
cli-anything-cloudcompare --project myproject.json mesh list
# JSON output
cli-anything-cloudcompare --project myproject.json --json mesh list
```
#### mesh sample
Sample a point cloud from a mesh surface.
```bash
# Sample 50 000 points from mesh at index 0
cli-anything-cloudcompare --project myproject.json mesh sample 0 \
-o /data/sampled.las -n 50000
# Add sampled cloud back to project
cli-anything-cloudcompare --project myproject.json mesh sample 0 \
-o /data/sampled.las -n 100000 --add-to-project
```
Options: `-o/--output TEXT` (required), `-n/--count INTEGER`, `--add-to-project`
---
### 5. distance — Distance Computation (2 commands)
#### distance c2c
Compute cloud-to-cloud distances. Adds a distance scalar field to the compared cloud.
```bash
# Compare cloud 1 to reference cloud 0
cli-anything-cloudcompare --project myproject.json distance c2c \
--compare 1 --reference 0 -o /data/distances.las
# Split into X/Y/Z components at octree level 8
cli-anything-cloudcompare --project myproject.json distance c2c \
--compare 1 --reference 0 -o /data/distances.las \
--split-xyz --octree-level 8 --add-to-project
```
Options: `--compare TEXT` (required), `--reference TEXT` (required), `-o/--output TEXT` (required), `--split-xyz`, `--octree-level INTEGER`, `--add-to-project`
#### distance c2m
Compute cloud-to-mesh distances. Adds a distance scalar field to the cloud.
```bash
# Basic cloud-to-mesh distance
cli-anything-cloudcompare --project myproject.json distance c2m \
--cloud 0 --mesh 0 -o /data/c2m_dist.las
# With flipped normals and unsigned distances
cli-anything-cloudcompare --project myproject.json distance c2m \
--cloud 0 --mesh 0 -o /data/c2m_dist.las \
--flip-normals --unsigned --add-to-project
```
Options: `--cloud INTEGER` (required), `--mesh INTEGER` (required), `-o/--output TEXT` (required), `--flip-normals`, `--unsigned`, `--add-to-project`
---
### 6. transform — Transformations and Registration (2 commands)
#### transform apply
Apply a 4×4 rigid-body transformation matrix to a cloud.
```bash
# Apply a transformation matrix from file
cli-anything-cloudcompare --project myproject.json transform apply 0 \
-o /data/transformed.las -m /data/matrix.txt
# Apply the inverse transformation
cli-anything-cloudcompare --project myproject.json transform apply 0 \
-o /data/transformed.las -m /data/matrix.txt --inverse --add-to-project
```
The matrix file must contain 4 rows of 4 space-separated values:
```
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
```
Options: `-o/--output TEXT` (required), `-m/--matrix TEXT` (required), `--inverse`, `--add-to-project`
#### transform icp
Run ICP (Iterative Closest Point) registration to align one cloud to another.
```bash
# Basic ICP alignment
cli-anything-cloudcompare --project myproject.json transform icp \
--aligned 1 --reference 0 -o /data/aligned.las
# ICP with overlap and iteration control
cli-anything-cloudcompare --project myproject.json transform icp \
--aligned 1 --reference 0 -o /data/aligned.las \
--max-iter 50 --overlap 80 --min-error-diff 1e-6 --add-to-project
```
Options: `--aligned INTEGER` (required), `--reference INTEGER` (required), `-o/--output TEXT` (required), `--max-iter INTEGER`, `--min-error-diff FLOAT`, `--overlap FLOAT`, `--add-to-project`
---
### 7. export — Export Clouds and Meshes (4 commands)
#### export formats
List all available export format presets.
```bash
cli-anything-cloudcompare export formats
# JSON output
cli-anything-cloudcompare --json export formats
```
#### export cloud
Export a cloud to a target format.
```bash
# Export cloud at index 0 as LAS
cli-anything-cloudcompare --project myproject.json export cloud 0 /data/output.las
# Export as PLY using preset
cli-anything-cloudcompare --project myproject.json export cloud 0 /data/output.ply -f ply
# Overwrite if file exists
cli-anything-cloudcompare --project myproject.json export cloud 0 /data/output.las -f las --overwrite
```
Supported presets: `las`, `laz`, `ply`, `pcd`, `xyz`, `asc`, `csv`, `bin`, `e57`
Options: `-f/--preset TEXT`, `--overwrite`
#### export mesh
Export a mesh to a target format.
```bash
# Export mesh at index 0 as OBJ
cli-anything-cloudcompare --project myproject.json export mesh 0 /data/model.obj
# Export as STL
cli-anything-cloudcompare --project myproject.json export mesh 0 /data/model.stl -f stl --overwrite
```
Supported presets: `obj`, `stl`, `ply`, `bin`
Options: `-f/--preset TEXT`, `--overwrite`
#### export batch
Batch export all project clouds to a directory.
```bash
# Export all clouds as LAS
cli-anything-cloudcompare --project myproject.json export batch \
-d /data/exports/ -f las
# Overwrite existing files
cli-anything-cloudcompare --project myproject.json export batch \
-d /data/exports/ -f ply --overwrite
```
Options: `-d/--output-dir TEXT` (required), `-f/--preset TEXT`, `--overwrite`
---
### 8. info — Installation Info (1 command)
Show CloudCompare installation path and version.
```bash
cli-anything-cloudcompare info
# JSON output
cli-anything-cloudcompare --json info
```
---
### 9. repl — Interactive REPL (1 command)
Start the interactive REPL session with history and undo support.
```bash
# Start REPL without a project
cli-anything-cloudcompare repl
# Start REPL with an existing project
cli-anything-cloudcompare repl -p myproject.json
# Equivalent: run without subcommand
cli-anything-cloudcompare --project myproject.json
```
Options: `-p/--project TEXT`
Inside the REPL, type `help` to list available commands or `session undo` to revert the last operation.
---
## Supported File Formats
| Format | Extension | Read | Write | Notes |
|---|---|---|---|---|
| LAS | `.las` | ✓ | ✓ | LiDAR standard, supports intensity/RGB |
| LAZ | `.laz` | ✓ | ✓ | Compressed LAS |
| PLY | `.ply` | ✓ | ✓ | ASCII or binary |
| PCD | `.pcd` | ✓ | ✓ | PCL format |
| XYZ | `.xyz` | ✓ | ✓ | Plain text XYZ |
| ASC | `.asc` | ✓ | ✓ | ASCII with header |
| CSV | `.csv` | ✓ | ✓ | Comma-separated |
| E57 | `.e57` | ✓ | ✓ | ASTM scanner exchange |
| BIN | `.bin` | ✓ | ✓ | CloudCompare native binary |
| OBJ | `.obj` | ✓ | ✓ | Mesh (Wavefront) |
| STL | `.stl` | ✓ | ✓ | Mesh (3D printing) |
---
## Typical Workflows
### Workflow 1: LiDAR Pre-processing Pipeline
```bash
P=myproject.json
# 1. Create project and load scan
cli-anything-cloudcompare project new -o $P
cli-anything-cloudcompare --project $P cloud add /data/scan.las
cli-anything-cloudcompare --project $P cloud list # note index → 0
# 2. Denoise
cli-anything-cloudcompare --project $P cloud filter-sor 0 \
-o /data/denoised.las --nb-points 6 --std-ratio 1.0 --add-to-project
# 3. Subsample to 5 cm grid
cli-anything-cloudcompare --project $P cloud subsample 1 \
-o /data/subsampled.las -m spatial -n 0.05 --add-to-project
# 4. Extract ground plane (CSF)
cli-anything-cloudcompare --project $P cloud filter-csf 2 \
--ground /data/ground.las --offground /data/objects.las \
--scene relief --add-to-project
# 5. Export result
cli-anything-cloudcompare --project $P export cloud 3 /data/ground_final.las -f las --overwrite
```
### Workflow 2: Change Detection Between Two Scans
```bash
P=compare.json
cli-anything-cloudcompare project new -o $P
cli-anything-cloudcompare --project $P cloud add /data/scan_2023.las # index 0
cli-anything-cloudcompare --project $P cloud add /data/scan_2024.las # index 1
# ICP alignment (align 2024 to 2023)
cli-anything-cloudcompare --project $P transform icp \
--aligned 1 --reference 0 -o /data/aligned_2024.las \
--overlap 90 --add-to-project # index 2
# Cloud-to-cloud distance
cli-anything-cloudcompare --project $P distance c2c \
--compare 2 --reference 0 -o /data/change_map.las --add-to-project
# Export as LAS with distance scalar field
cli-anything-cloudcompare --project $P export cloud 3 /data/change_map_final.las --overwrite
```
### Workflow 3: Height Slice Extraction
```bash
P=slice.json
cli-anything-cloudcompare project new -o $P
cli-anything-cloudcompare --project $P cloud add /data/building.las
# Extract points at 23 m height (floor level)
cli-anything-cloudcompare --project $P cloud sf-filter-z 0 \
-o /data/floor_slice.las --min 2.0 --max 3.0 --add-to-project
# Export
cli-anything-cloudcompare --project $P export cloud 1 /data/floor_slice_out.las --overwrite
```
### Workflow 4: Surface Reconstruction
```bash
P=mesh.json
cli-anything-cloudcompare project new -o $P
cli-anything-cloudcompare --project $P cloud add /data/terrain.las
# Compute normals
cli-anything-cloudcompare --project $P cloud normals 0 \
-o /data/with_normals.ply --level 6 --orientation plus_z --add-to-project
# Delaunay mesh
cli-anything-cloudcompare --project $P cloud mesh-delaunay 1 \
-o /data/terrain_mesh.obj --max-edge-length 1.0 --add-to-project
# Export mesh
cli-anything-cloudcompare --project $P export mesh 0 /data/terrain_mesh_final.obj --overwrite
```
---
## Error Handling
| Exit Code | Meaning |
|---|---|
| `0` | Success |
| `1` | General error (see stderr for details) |
| `2` | Invalid arguments |
Common errors:
```bash
# CloudCompare not found
# → Install CloudCompare; check `cli-anything-cloudcompare info`
# Index out of range
# → Run `cloud list` or `mesh list` to confirm valid indices
# File already exists (no --overwrite)
# → Add --overwrite flag to export commands
# fcntl not available (Windows)
# → File locking is skipped automatically; project save still works
```
---
## For AI Agents
1. **Always use `--json` flag** for parseable output
2. **Check return codes** — 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file arguments
5. **Verify output files exist** after export operations
6. **Chain with `--add-to-project`** to build multi-step pipelines without re-loading files
7. **Use `cloud list --json`** to discover valid cloud indices before each operation
8. **Use `export formats --json`** to discover available format presets
## Version
| Component | Version |
|---|---|
| cli-anything-cloudcompare | 1.0.0 |
| CloudCompare (tested) | 2.13.2 |
| Python (minimum) | 3.10 |

View File

@@ -0,0 +1,190 @@
---
name: "cli-anything-comfyui"
description: >-
Command-line interface for ComfyUI - AI image generation workflow management via ComfyUI REST API. Designed for AI agents and power users who need to queue workflows, manage models, download generated images, and monitor the generation queue without a GUI.
---
# cli-anything-comfyui
AI image generation workflow management via the ComfyUI REST API. Designed for AI agents and power users who need to queue workflows, manage models, download generated images, and monitor the generation queue without a GUI.
## Installation
This CLI is installed as part of the cli-anything-comfyui package:
```bash
pip install cli-anything-comfyui
```
**Prerequisites:**
- Python 3.10+
- ComfyUI must be installed and running at http://localhost:8188
## Usage
### Basic Commands
```bash
# Show help
cli-anything-comfyui --help
# Start interactive REPL mode
cli-anything-comfyui repl
# Check server stats
cli-anything-comfyui system stats
# Run with JSON output (for agent consumption)
cli-anything-comfyui --json system stats
```
### REPL Mode
Start an interactive session for exploratory use:
```bash
cli-anything-comfyui repl
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Workflow
Workflow management commands.
| Command | Description |
|---------|-------------|
| `list` | List saved workflows |
| `load` | Load a workflow from a JSON file |
| `validate` | Validate a workflow JSON against the ComfyUI node graph |
### Queue
Generation queue management.
| Command | Description |
|---------|-------------|
| `prompt` | Queue a workflow for execution |
| `status` | Show current queue status (running and pending) |
| `clear` | Clear the generation queue |
| `history` | Show prompt execution history |
| `interrupt` | Interrupt the currently running generation |
### Models
Model discovery commands.
| Command | Description |
|---------|-------------|
| `checkpoints` | List available checkpoint models |
| `loras` | List available LoRA models |
| `vaes` | List available VAE models |
| `controlnets` | List available ControlNet models |
| `node-info` | Show detailed info for a specific node type |
| `list-nodes` | List all available node types |
### Images
Generated image management.
| Command | Description |
|---------|-------------|
| `list` | List generated images on the server |
| `download` | Download a specific generated image |
| `download-all` | Download all images from a prompt execution |
### System
Server status and information.
| Command | Description |
|---------|-------------|
| `stats` | Show ComfyUI system statistics (GPU, CPU, memory) |
| `info` | Show ComfyUI server info and extensions |
## Examples
### Check System Status
```bash
# Server stats
cli-anything-comfyui system stats
# Server info
cli-anything-comfyui system info
```
### Discover Available Models
```bash
# List checkpoints
cli-anything-comfyui models checkpoints
# List LoRAs
cli-anything-comfyui models loras
# List all node types
cli-anything-comfyui models list-nodes
```
### Queue and Monitor Generation
```bash
# Queue a workflow
cli-anything-comfyui queue prompt --workflow my_workflow.json
# Check queue status
cli-anything-comfyui queue status
# View execution history
cli-anything-comfyui --json queue history
```
### Download Generated Images
```bash
# List generated images
cli-anything-comfyui images list
# Download a specific image
cli-anything-comfyui images download --filename ComfyUI_00001_.png --output ./out.png
# Download all images from a prompt
cli-anything-comfyui images download-all --prompt-id <id> --output-dir ./outputs
```
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-comfyui system stats
# JSON output for agents
cli-anything-comfyui --json system stats
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file operations
5. **Verify ComfyUI is running** with `system stats` before other commands
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,56 @@
---
name: "cli-anything-dify-workflow"
description: Wrapper for the Dify workflow DSL CLI. Create, inspect, validate, edit, and export Dify workflow files through a CLI-Anything harness.
---
# Dify Workflow CLI Skill
## Installation
Install the upstream Dify workflow CLI first:
```bash
python -m pip install "dify-ai-workflow-tools @ git+https://github.com/Akabane71/dify-workflow-cli.git@main"
```
Then install the CLI-Anything harness:
```bash
pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=dify-workflow/agent-harness
```
## Usage
The harness forwards to the upstream `dify-workflow` CLI.
```bash
cli-anything-dify-workflow guide
cli-anything-dify-workflow list-node-types
cli-anything-dify-workflow create -o workflow.yaml --mode workflow --template llm
cli-anything-dify-workflow inspect workflow.yaml -j
cli-anything-dify-workflow validate workflow.yaml -j
cli-anything-dify-workflow edit add-node -f workflow.yaml --type code --title "Process"
cli-anything-dify-workflow config set-model -f app.yaml --provider openai --name gpt-4o
```
## Command Groups
- `guide`
- `list-node-types`
- `create`
- `inspect`
- `validate`
- `checklist`
- `edit add-node|remove-node|update-node|add-edge|remove-edge|set-title`
- `config set-model|set-prompt|add-variable|set-opening|add-question|add-tool|remove-tool`
- `export`
- `import`
- `diff`
- `layout`
## Agent Guidance
- Prefer `-j` / `--json-output` on upstream commands when available.
- The harness itself is a wrapper. Real workflow logic is provided by the upstream project.
- If `dify-workflow` is not on PATH but the `dify_workflow` Python package is installed, the wrapper falls back to `python -m dify_workflow.cli`.
- All operations are local file edits on Dify YAML/JSON DSL files.

View File

@@ -0,0 +1,213 @@
---
name: "cli-anything-drawio"
description: >-
Command-line interface for Drawio - A CLI harness for **Draw.io** — create, edit, and export diagrams from the command line....
---
# cli-anything-drawio
A CLI harness for **Draw.io** — create, edit, and export diagrams from the command line.
## Installation
This CLI is installed as part of the cli-anything-drawio package:
```bash
pip install cli-anything-drawio
```
**Prerequisites:**
- Python 3.10+
- drawio must be installed on your system
## Usage
### Basic Commands
```bash
# Show help
cli-anything-drawio --help
# Start interactive REPL mode
cli-anything-drawio
# Create a new project
cli-anything-drawio project new -o project.json
# Run with JSON output (for agent consumption)
cli-anything-drawio --json project info -p project.json
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-drawio
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Project
Project management: new, open, save, info.
| Command | Description |
|---------|-------------|
| `new` | Create a new blank diagram |
| `open` | Open an existing .drawio project file |
| `save` | Save the current project |
| `info` | Show detailed project information |
| `xml` | Print the raw XML of the current project |
| `presets` | List available page size presets |
### Shape
Shape operations: add, remove, move, resize, style.
| Command | Description |
|---------|-------------|
| `add` | Add a shape to the diagram |
| `remove` | Remove a shape by ID |
| `list` | List all shapes on a page |
| `label` | Update a shape's label text |
| `move` | Move a shape to new coordinates |
| `resize` | Resize a shape |
| `style` | Set a style property on a shape |
| `info` | Show detailed info about a shape |
| `types` | List all available shape types |
### Connect
Connector operations: add, remove, style.
| Command | Description |
|---------|-------------|
| `add` | Add a connector between two shapes |
| `remove` | Remove a connector by ID |
| `label` | Update a connector's label |
| `style` | Set a style property on a connector |
| `list` | List all connectors on a page |
| `styles` | List available edge styles |
### Page
Page operations: add, remove, rename, list.
| Command | Description |
|---------|-------------|
| `add` | Add a new page |
| `remove` | Remove a page by index |
| `rename` | Rename a page |
| `list` | List all pages |
### Export
Export operations: render to PNG, PDF, SVG.
| Command | Description |
|---------|-------------|
| `render` | Export the diagram to a file |
| `formats` | List available export formats |
### Session
Session management: status, undo, redo.
| Command | Description |
|---------|-------------|
| `status` | Show current session status |
| `undo` | Undo the last operation |
| `redo` | Redo the last undone operation |
| `save-state` | Save session state to disk |
| `list` | List all saved sessions |
## Examples
### Create a New Project
Create a new drawio project file.
```bash
cli-anything-drawio project new -o myproject.json
# Or with JSON output for programmatic use
cli-anything-drawio --json project new -o myproject.json
```
### Interactive REPL Session
Start an interactive session with undo/redo support.
```bash
cli-anything-drawio
# Enter commands interactively
# Use 'help' to see available commands
# Use 'undo' and 'redo' for history navigation
```
### Export Project
Export the project to a final output format.
```bash
cli-anything-drawio --project myproject.json export render output.pdf --overwrite
```
## State Management
The CLI maintains session state with:
- **Undo/Redo**: Up to 50 levels of history
- **Project persistence**: Save/load project state as JSON
- **Session tracking**: Track modifications and changes
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-drawio project info -p project.json
# JSON output for agents
cli-anything-drawio --json project info -p project.json
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file operations
5. **Verify outputs exist** after export operations
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,73 @@
---
name: "cli-anything-eth2-quickstart"
description: >-
Use eth2-quickstart to autonomously deploy a hardened Ethereum node, install
execution and consensus clients, configure validator metadata, expose RPC
safely, and inspect node health with structured JSON output.
---
# cli-anything-eth2-quickstart
Agent-native harness for the `chimera-defi/eth2-quickstart` automation repo.
This CLI wraps the repo's canonical shell scripts instead of replacing them.
## When To Use
Use this skill when the task involves:
- bootstrapping a fresh Ethereum node host
- installing execution and consensus clients with explicit client diversity
- enabling MEV-Boost or Commit-Boost workflows
- exposing RPC through Nginx or Caddy
- updating validator fee recipient or graffiti settings without handling secrets
- checking machine-readable health with `--json`
## Core Commands
```bash
# Canonical machine-readable health
cli-anything-eth2-quickstart --json health-check
# Phase 2 install with explicit client choices
cli-anything-eth2-quickstart --json install-clients \
--network mainnet \
--execution-client geth \
--consensus-client lighthouse \
--mev mev-boost \
--confirm
# Guided node setup
cli-anything-eth2-quickstart --json setup-node \
--phase auto \
--execution-client geth \
--consensus-client prysm \
--mev commit-boost \
--confirm
# Validator metadata only; no key import
cli-anything-eth2-quickstart --json configure-validator \
--consensus-client prysm \
--fee-recipient 0x1111111111111111111111111111111111111111 \
--graffiti "CLI-Anything"
# Install nginx-backed RPC exposure
cli-anything-eth2-quickstart --json start-rpc \
--web-stack nginx \
--server-name rpc.example.org \
--confirm
```
## Safety Rules
- Always use `--json` for agent parsing.
- Require human confirmation before `setup-node`, `install-clients`, or `start-rpc`.
- Do not generate validator keys.
- Do not remove secrets or wallet material.
- Treat `configure-validator` as metadata and operator-guidance only.
- Respect the reboot boundary between Phase 1 and Phase 2.
## Runtime Expectations
- Operates on a local `eth2-quickstart` checkout.
- Discovers repo root from `--repo-root`, `ETH2QS_REPO_ROOT`, or current working directory.
- Writes compatible overrides into `config/user_config.env` when flags map directly to repo settings.

View File

@@ -0,0 +1,107 @@
---
name: "cli-anything-exa"
description: >-
Agent-native CLI for Exa web search and content retrieval workflows.
---
# Exa CLI Skill
## Identity
- **Name**: cli-anything-exa
- **Version**: 1.0.0
- **Category**: search
- **Entry Point**: `cli-anything-exa`
## What This CLI Does
Provides an agent-native command-line interface to the Exa API — a neural search engine
optimised for AI agent workflows. Supports web search across multiple modes (fast, deep,
deep-reasoning) and fetching full-text or highlighted page contents.
## Prerequisites
- Python >= 3.10
- `pip install cli-anything-exa`
- `export EXA_API_KEY="your-api-key"` (get one at https://dashboard.exa.ai/api-keys)
## Installation
```bash
pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=exa/agent-harness
```
## Command Reference
### search — Web search
```bash
cli-anything-exa search "<query>" [OPTIONS]
Options:
--type auto|fast|instant|deep|deep-reasoning (default: auto)
--num-results / -n 1100 (default: 10)
--category company|people|research-paper|news|personal-site|financial-report
--content highlights|text|summary|none (default: highlights)
--freshness smart|always|never (default: smart)
--include-domains DOMAIN (repeatable)
--exclude-domains DOMAIN (repeatable)
--from DATE ISO 8601 start published date
--to DATE ISO 8601 end published date
--location CC Two-letter country code for geo-bias
```
### contents — Fetch page contents
```bash
cli-anything-exa contents <url> [url ...] [--content text|highlights|summary] [--freshness smart|always|never]
```
### server status — Verify API key and connectivity
```bash
cli-anything-exa server status
```
## JSON Output
All commands support `--json` at the root level for machine-readable output:
```bash
cli-anything-exa --json search "latest LLM papers" --num-results 5
```
## Common Agent Patterns
### Fast keyword lookup
```bash
cli-anything-exa --json search "site:arxiv.org transformer architectures" --type fast --content highlights
```
### Deep research on a topic
```bash
cli-anything-exa --json search "EU AI Act compliance requirements 2024" --type deep --content text
```
### Academic paper discovery
```bash
cli-anything-exa --json search "retrieval augmented generation" --category research-paper --num-results 20
```
### Company intelligence
```bash
cli-anything-exa --json search "Anthropic funding history" --category company
```
### News monitoring
```bash
cli-anything-exa --json search "AI regulation news" --category news --from 2024-01-01
```
### Fetch full content for summarisation
```bash
cli-anything-exa --json contents https://example.com/article --content text
```
## Interactive REPL
```bash
cli-anything-exa # No subcommand → enters REPL
```
Type commands without the `cli-anything-exa` prefix. Type `exit` or `quit` to leave.
## Notes
- `highlights` content mode is 10× more token-efficient than `text` — prefer it for agent pipelines
- `--type deep` triggers multi-step reasoning; slower but synthesises across many sources
- `--category company` and `--category people` do not support date or domain-exclude filters
- Cost per query is included in JSON output under `cost_dollars`

View File

@@ -0,0 +1,255 @@
---
name: "cli-anything-freecad"
description: "Complete CLI harness for FreeCAD parametric 3D CAD modeler (258 commands). Covers ALL workbenches: Part (29 primitives + boolean + mirror + loft + sweep), Sketcher (26 cmds: geometry + constraints + editing), PartDesign (38 cmds: pad/pocket/groove/fillet/chamfer/patterns/hole/datum), Assembly (11 cmds), Mesh (16 cmds), TechDraw (15 cmds: views + dimensions + PDF/SVG), Draft (33 cmds: 2D shapes + arrays + transforms), FEM (12 cmds), CAM/CNC (10 cmds), Surface (6 cmds), Spreadsheet (7 cmds), Import (13 formats), Export (17 formats), Measure (12 cmds), Materials (21 presets). Headless FreeCAD export to STEP/IGES/STL/OBJ/DXF/PDF/glTF/3MF."
---
# cli-anything-freecad
Complete CLI harness for **FreeCAD** — 258 commands across 17 groups covering ALL workbenches.
## Prerequisites
FreeCAD must be installed: `freecadcmd` must be in PATH.
## Installation
```bash
pip install -e freecad/agent-harness
```
## Basic Usage
```bash
cli-anything-freecad --json <command> # JSON output for agents
cli-anything-freecad --json -p proj.json <cmd> # With project file
cli-anything-freecad # Interactive REPL
```
## Command Groups (258 commands)
### document (5) — Document management
```bash
cli-anything-freecad --json document new --name "Part" -o proj.json
cli-anything-freecad --json document new --profile print3d -o proj.json
cli-anything-freecad --json -p proj.json document info
cli-anything-freecad --json -p proj.json document save -o copy.json
cli-anything-freecad --json document profiles
```
### part (29) — 3D primitives, boolean, transforms, operations
```bash
# Primitives: box, cylinder, sphere, cone, torus, wedge, helix, spiral, thread, plane, polygon_3d
cli-anything-freecad --json -p p.json part add box -P length=20 -P width=15 -P height=5
cli-anything-freecad --json -p p.json part add cylinder -P radius=3 -P height=10 --position 10,7.5,0
# Operations
cli-anything-freecad --json -p p.json part boolean cut 0 1
cli-anything-freecad --json -p p.json part copy 0
cli-anything-freecad --json -p p.json part mirror 0 --plane XY
cli-anything-freecad --json -p p.json part scale 0 --factor 2.0
cli-anything-freecad --json -p p.json part loft --indices 0,1,2
cli-anything-freecad --json -p p.json part sweep 0 1
cli-anything-freecad --json -p p.json part revolve 0 --axis Z --angle 360
cli-anything-freecad --json -p p.json part extrude 0 --direction 0,0,1 --length 10
cli-anything-freecad --json -p p.json part fillet-3d 0 --radius 2
cli-anything-freecad --json -p p.json part thickness 0 --thickness 1
cli-anything-freecad --json -p p.json part compound --indices 0,1,2
cli-anything-freecad --json -p p.json part section 0 --plane XY
cli-anything-freecad --json -p p.json part info 0
cli-anything-freecad --json -p p.json part line-3d --start 0,0,0 --end 10,5,0
cli-anything-freecad --json -p p.json part wire --points "0,0,0;10,0,0;10,10,0"
```
### sketch (26) — 2D constrained sketching
```bash
cli-anything-freecad --json -p p.json sketch new --plane XY
cli-anything-freecad --json -p p.json sketch add-line 0 --start 0,0 --end 20,0
cli-anything-freecad --json -p p.json sketch add-circle 0 --center 10,10 --radius 5
cli-anything-freecad --json -p p.json sketch add-rect 0 --corner 0,0 --width 20 --height 15
cli-anything-freecad --json -p p.json sketch add-arc 0 --center 0,0 --radius 5
cli-anything-freecad --json -p p.json sketch add-ellipse 0 --center 0,0 --major-radius 10 --minor-radius 5
cli-anything-freecad --json -p p.json sketch add-polygon 0 --center 0,0 --sides 6 --radius 10
cli-anything-freecad --json -p p.json sketch add-bspline 0 --points "0,0;5,10;10,0;15,10"
cli-anything-freecad --json -p p.json sketch add-slot 0 --center1 0,0 --center2 10,0 --radius 2
cli-anything-freecad --json -p p.json sketch constrain 0 distance --elements 0,1 --value 10
cli-anything-freecad --json -p p.json sketch edit-element 0 0 --radius 8
cli-anything-freecad --json -p p.json sketch remove-element 0 2
cli-anything-freecad --json -p p.json sketch validate 0
cli-anything-freecad --json -p p.json sketch solve-status 0
# Constraints: coincident, horizontal, vertical, parallel, perpendicular, equal,
# fixed, distance, angle, radius, tangent, symmetric, block, diameter,
# point_on_object, distance_x, distance_y
```
### body (38) — PartDesign features
```bash
cli-anything-freecad --json -p p.json body new
cli-anything-freecad --json -p p.json body pad 0 0 --length 10
cli-anything-freecad --json -p p.json body pocket 0 1 --length 5
cli-anything-freecad --json -p p.json body groove 0 0 --angle 360
cli-anything-freecad --json -p p.json body fillet 0 --radius 2
cli-anything-freecad --json -p p.json body chamfer 0 --size 1.5
cli-anything-freecad --json -p p.json body revolution 0 0 --angle 360
cli-anything-freecad --json -p p.json body additive-loft 0 --sketch-indices 0,1
cli-anything-freecad --json -p p.json body additive-pipe 0 0 1
cli-anything-freecad --json -p p.json body additive-helix 0 0 --pitch 5 --height 20
cli-anything-freecad --json -p p.json body additive-box 0 -P length=10 -P width=10 -P height=10
cli-anything-freecad --json -p p.json body hole 0 0 --diameter 5 --depth 10 --threaded
cli-anything-freecad --json -p p.json body draft-feature 0 --angle 5
cli-anything-freecad --json -p p.json body thickness-feature 0 --thickness 1
cli-anything-freecad --json -p p.json body linear-pattern 0 --occurrences 5 --length 50
cli-anything-freecad --json -p p.json body polar-pattern 0 --occurrences 6 --angle 360
cli-anything-freecad --json -p p.json body mirrored 0 --plane XY
cli-anything-freecad --json -p p.json body datum-plane 0 --reference XY --offset 10
```
### material (8) — PBR materials with engineering properties
```bash
# 21 presets: steel, aluminum, copper, brass, titanium, stainless_steel, cast_iron,
# carbon_fiber, nylon, abs, pla, petg, plastic_white, plastic_black, wood, glass,
# rubber, gold, concrete, granite, marble
cli-anything-freecad --json -p p.json material create --preset steel
cli-anything-freecad --json -p p.json material create --preset titanium
cli-anything-freecad --json -p p.json material assign 0 0
cli-anything-freecad --json -p p.json material set 0 density 7800
cli-anything-freecad --json -p p.json material import-material mat.json
cli-anything-freecad --json -p p.json material export-material 0 --output mat.json
```
### assembly (11) — Assembly management
```bash
cli-anything-freecad --json -p p.json assembly new --name "MyAssembly"
cli-anything-freecad --json -p p.json assembly add-part 0 0
cli-anything-freecad --json -p p.json assembly constrain 0 coincident --components 0,1
cli-anything-freecad --json -p p.json assembly constrain 0 distance --components 0,1 --distance 10
cli-anything-freecad --json -p p.json assembly solve 0
cli-anything-freecad --json -p p.json assembly dof 0
cli-anything-freecad --json -p p.json assembly bom 0
cli-anything-freecad --json -p p.json assembly explode 0 --factor 2.0
# Constraints: fixed, coincident, distance, angle, parallel, perpendicular,
# tangent, revolute, prismatic, cylindrical, ball, planar, gear, belt
```
### mesh (16) — Mesh operations
```bash
cli-anything-freecad --json -p p.json mesh from-shape 0 --deviation 0.1
cli-anything-freecad --json -p p.json mesh import path/to/model.stl
cli-anything-freecad --json -p p.json mesh export 0 output.stl --format stl
cli-anything-freecad --json -p p.json mesh boolean union 0 1
cli-anything-freecad --json -p p.json mesh decimate 0 --target-faces 1000
cli-anything-freecad --json -p p.json mesh smooth 0 --iterations 5
cli-anything-freecad --json -p p.json mesh repair 0
cli-anything-freecad --json -p p.json mesh to-shape 0
```
### techdraw (15) — Technical drawings
```bash
cli-anything-freecad --json -p p.json techdraw new-page
cli-anything-freecad --json -p p.json techdraw add-view 0 0 --direction 0,0,1 --scale 1.0
cli-anything-freecad --json -p p.json techdraw add-projection-group 0 0
cli-anything-freecad --json -p p.json techdraw add-section-view 0 0
cli-anything-freecad --json -p p.json techdraw add-dimension 0 0 length --references 0,1
cli-anything-freecad --json -p p.json techdraw add-annotation 0 "Note text"
cli-anything-freecad --json -p p.json techdraw export-pdf 0 drawing.pdf
cli-anything-freecad --json -p p.json techdraw export-svg 0 drawing.svg
```
### draft (33) — 2D drafting
```bash
cli-anything-freecad --json -p p.json draft wire --points "0,0,0;10,0,0;10,10,0"
cli-anything-freecad --json -p p.json draft rectangle --width 20 --height 15
cli-anything-freecad --json -p p.json draft circle --radius 10
cli-anything-freecad --json -p p.json draft polygon --sides 6 --radius 10
cli-anything-freecad --json -p p.json draft text --content "Hello" --position 0,0,0
cli-anything-freecad --json -p p.json draft move 0 --vector 10,5,0
cli-anything-freecad --json -p p.json draft array-linear 0 --direction 1,0,0 --count 5 --spacing 10
cli-anything-freecad --json -p p.json draft array-polar 0 --center 0,0,0 --count 6
cli-anything-freecad --json -p p.json draft extrude 0 --direction 0,0,1 --length 10
cli-anything-freecad --json -p p.json draft to-sketch 0
```
### measure (12) — Measurement and analysis
```bash
cli-anything-freecad --json -p p.json measure volume 0
cli-anything-freecad --json -p p.json measure area 0
cli-anything-freecad --json -p p.json measure distance 0 1
cli-anything-freecad --json -p p.json measure bounding-box 0
cli-anything-freecad --json -p p.json measure center-of-mass 0
cli-anything-freecad --json -p p.json measure check-geometry 0
```
### surface (6) — Surface operations
```bash
cli-anything-freecad --json -p p.json surface filling --edges 0,1,2
cli-anything-freecad --json -p p.json surface sections --sections 0,1,2
cli-anything-freecad --json -p p.json surface extend 0 --length 10
cli-anything-freecad --json -p p.json surface sew --indices 0,1
```
### fem (12) — Finite Element Analysis
```bash
cli-anything-freecad --json -p p.json fem new-analysis
cli-anything-freecad --json -p p.json fem add-fixed 0 --references face1,face2
cli-anything-freecad --json -p p.json fem add-force 0 --references face3 --magnitude 1000
cli-anything-freecad --json -p p.json fem set-material 0 0
cli-anything-freecad --json -p p.json fem mesh-generate 0 --max-size 5
cli-anything-freecad --json -p p.json fem solve 0
cli-anything-freecad --json -p p.json fem results 0
```
### cam (10) — CNC machining
```bash
cli-anything-freecad --json -p p.json cam new-job 0
cli-anything-freecad --json -p p.json cam set-stock 0 --stock-type box
cli-anything-freecad --json -p p.json cam set-tool 0 --diameter 6 --type endmill
cli-anything-freecad --json -p p.json cam add-profile 0
cli-anything-freecad --json -p p.json cam add-pocket 0 --depth 5
cli-anything-freecad --json -p p.json cam generate-gcode 0
cli-anything-freecad --json -p p.json cam export-gcode 0 output.nc
```
### spreadsheet (7) — Parametric data tables
```bash
cli-anything-freecad --json -p p.json spreadsheet new
cli-anything-freecad --json -p p.json spreadsheet set-cell 0 A1 "50"
cli-anything-freecad --json -p p.json spreadsheet set-cell 0 B1 "=A1*2"
cli-anything-freecad --json -p p.json spreadsheet set-alias 0 A1 plate_width
cli-anything-freecad --json -p p.json spreadsheet export-csv 0 data.csv
```
### import (13) — Import CAD/mesh files
```bash
cli-anything-freecad --json -p p.json import auto model.step
cli-anything-freecad --json -p p.json import step model.step
cli-anything-freecad --json -p p.json import stl model.stl
cli-anything-freecad --json -p p.json import dxf drawing.dxf
cli-anything-freecad --json -p p.json import info model.step
# Formats: step, iges, stl, obj, dxf, svg, brep, 3mf, ply, off, gltf
```
### export (3) — Export to 17 formats
```bash
# Presets: step, iges, stl, stl_fine, obj, brep, fcstd, dxf, svg, gltf, 3mf, ply, off, amf, pdf, png, jpg
cli-anything-freecad --json -p p.json export render output.step --preset step
cli-anything-freecad --json -p p.json export render model.stl --preset stl --overwrite
cli-anything-freecad --json -p p.json export presets
```
### session (4) — Undo/redo
```bash
cli-anything-freecad --json -p p.json session undo
cli-anything-freecad --json -p p.json session redo
cli-anything-freecad --json -p p.json session status
cli-anything-freecad --json -p p.json session history
```
## JSON Output
All commands support `--json`. Responses include structured data. Errors: `{"error": "message"}`.
## Error Handling
- Missing FreeCAD: Clear install instructions
- Invalid types: Lists valid options
- Index out of range: Reports valid range
- File exists: Use `--overwrite`

View File

@@ -0,0 +1,237 @@
---
name: "cli-anything-gimp"
description: >-
Command-line interface for Gimp - A stateful command-line interface for image editing, built on Pillow. Designed for AI agents and pow...
---
# cli-anything-gimp
A stateful command-line interface for image editing, built on Pillow. Designed for AI agents and power users who need to create and manipulate images without a GUI.
## Installation
This CLI is installed as part of the cli-anything-gimp package:
```bash
pip install cli-anything-gimp
```
**Prerequisites:**
- Python 3.10+
- gimp must be installed on your system
## Usage
### Basic Commands
```bash
# Show help
cli-anything-gimp --help
# Start interactive REPL mode
cli-anything-gimp
# Create a new project
cli-anything-gimp project new -o project.json
# Run with JSON output (for agent consumption)
cli-anything-gimp --json project info -p project.json
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-gimp
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Project
Project management commands.
| Command | Description |
|---------|-------------|
| `new` | Create a new project |
| `open` | Open an existing project |
| `save` | Save the current project |
| `info` | Show project information |
| `profiles` | List available canvas profiles |
| `json` | Print raw project JSON |
### Layer
Layer management commands.
| Command | Description |
|---------|-------------|
| `new` | Create a new blank layer |
| `add-from-file` | Add a layer from an image file |
| `list` | List all layers |
| `remove` | Remove a layer by index |
| `duplicate` | Duplicate a layer |
| `move` | Move a layer to a new position |
| `set` | Set a layer property (name, opacity, visible, mode, offset_x, offset_y) |
| `flatten` | Flatten all visible layers |
| `merge-down` | Merge a layer with the one below it |
### Canvas
Canvas operations.
| Command | Description |
|---------|-------------|
| `info` | Show canvas information |
| `resize` | Resize the canvas (without scaling content) |
| `scale` | Scale the canvas and all content proportionally |
| `crop` | Crop the canvas to a rectangle |
| `mode` | Set the canvas color mode |
| `dpi` | Set the canvas DPI |
### Filter Group
Filter management commands.
| Command | Description |
|---------|-------------|
| `list-available` | List all available filters |
| `info` | Show details about a filter |
| `add` | Add a filter to a layer |
| `remove` | Remove a filter by index |
| `set` | Set a filter parameter |
| `list` | List filters on a layer |
### Media
Media file operations.
| Command | Description |
|---------|-------------|
| `probe` | Analyze an image file |
| `list` | List media files referenced in the project |
| `check` | Check that all referenced media files exist |
| `histogram` | Show histogram analysis of an image |
### Export Group
Export/render commands.
| Command | Description |
|---------|-------------|
| `presets` | List export presets |
| `preset-info` | Show preset details |
| `render` | Render the project to an image file |
### Session
Session management commands.
| Command | Description |
|---------|-------------|
| `status` | Show session status |
| `undo` | Undo the last operation |
| `redo` | Redo the last undone operation |
| `history` | Show undo history |
### Draw
Drawing operations (applied at render time).
| Command | Description |
|---------|-------------|
| `text` | Draw text on a layer (by converting it to a text layer) |
| `rect` | Draw a rectangle (stored as drawing operation) |
## Examples
### Create a New Project
Create a new gimp project file.
```bash
cli-anything-gimp project new -o myproject.json
# Or with JSON output for programmatic use
cli-anything-gimp --json project new -o myproject.json
```
### Interactive REPL Session
Start an interactive session with undo/redo support.
```bash
cli-anything-gimp
# Enter commands interactively
# Use 'help' to see available commands
# Use 'undo' and 'redo' for history navigation
```
### Export Project
Export the project to a final output format.
```bash
cli-anything-gimp --project myproject.json export render output.pdf --overwrite
```
## State Management
The CLI maintains session state with:
- **Undo/Redo**: Up to 50 levels of history
- **Project persistence**: Save/load project state as JSON
- **Session tracking**: Track modifications and changes
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-gimp project info -p project.json
# JSON output for agents
cli-anything-gimp --json project info -p project.json
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file operations
5. **Verify outputs exist** after export operations
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,106 @@
---
name: "cli-anything-godot"
description: >-
Agent-native CLI for Godot project management, scenes, exports, and script execution.
---
# Godot Engine CLI
Agent-native CLI for the Godot game engine. Manage projects, scenes, exports, and GDScript execution from the command line.
## Installation
```bash
pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=godot/agent-harness
```
## Requirements
- Godot 4.x on PATH (or set GODOT_BIN env var)
## Commands
### Project Management
```bash
# Create a new Godot project
cli-anything-godot project create <path> [--name "My Game"]
# Get project info (name, features, main scene)
cli-anything-godot --json -p <project> project info
# List all scenes
cli-anything-godot --json -p <project> project scenes
# List all scripts
cli-anything-godot --json -p <project> project scripts
# List all resources
cli-anything-godot --json -p <project> project resources
# Re-import project resources
cli-anything-godot -p <project> project reimport
```
### Scene Operations
```bash
# Create a new scene with root node type
cli-anything-godot -p <project> scene create scenes/Level1.tscn --root-type Node3D
# Read scene structure (nodes, resources, connections)
cli-anything-godot --json -p <project> scene read scenes/Level1.tscn
# Add a child node to a scene
cli-anything-godot -p <project> scene add-node scenes/Level1.tscn --name Player --type CharacterBody3D --parent .
```
### GDScript Execution
```bash
# Run a GDScript file (must extend SceneTree)
cli-anything-godot -p <project> script run tools/build_navmesh.gd
# Run inline GDScript code
cli-anything-godot -p <project> script inline 'print(ProjectSettings.get_setting("application/config/name"))'
# Validate GDScript syntax
cli-anything-godot -p <project> script validate scripts/player.gd
```
### Export
```bash
# List configured export presets
cli-anything-godot --json -p <project> export presets
# Export all presets
cli-anything-godot -p <project> export build
# Export a specific preset
cli-anything-godot -p <project> export build --preset "Windows Desktop" --output build/game.exe
```
### Engine
```bash
# Check Godot availability and binary path
cli-anything-godot --json engine status
# Get engine version
cli-anything-godot engine version
```
## JSON Mode
Add `--json` flag to any command for structured JSON output suitable for agent consumption:
```bash
cli-anything-godot --json -p ./my-game project info
```
## Interactive REPL
```bash
cli-anything-godot -p ./my-game session
```

View File

@@ -0,0 +1,273 @@
---
name: "cli-anything-inkscape"
description: >-
Command-line interface for Inkscape - A stateful command-line interface for vector graphics editing, following the same patterns as the GI...
---
# cli-anything-inkscape
A stateful command-line interface for vector graphics editing, following the same patterns as the GIMP and Blender CLI harnesses. Directly manipulates SVG (XML) documents with a JSON project format for state tracking.
## Installation
This CLI is installed as part of the cli-anything-inkscape package:
```bash
pip install cli-anything-inkscape
```
**Prerequisites:**
- Python 3.10+
- inkscape must be installed on your system
## Usage
### Basic Commands
```bash
# Show help
cli-anything-inkscape --help
# Start interactive REPL mode
cli-anything-inkscape
# Create a new project
cli-anything-inkscape project new -o project.json
# Run with JSON output (for agent consumption)
cli-anything-inkscape --json project info -p project.json
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-inkscape
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Document
Document management commands.
| Command | Description |
|---------|-------------|
| `new` | Create a new document |
| `open` | Open an existing project |
| `save` | Save the current project |
| `info` | Show document information |
| `profiles` | List available document profiles |
| `canvas-size` | Set the canvas size |
| `units` | Set the document units |
| `json` | Print raw project JSON |
### Shape
Shape management commands.
| Command | Description |
|---------|-------------|
| `add-rect` | Add a rectangle |
| `add-circle` | Add a circle |
| `add-ellipse` | Add an ellipse |
| `add-line` | Add a line |
| `add-polygon` | Add a polygon |
| `add-path` | Add a path |
| `add-star` | Add a star |
| `remove` | Remove a shape by index |
| `duplicate` | Duplicate a shape |
| `list` | List all shapes/objects |
| `get` | Get detailed info about a shape |
### Text
Text management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a text element |
| `set` | Set a text property (text, font-family, font-size, fill, etc.) |
| `list` | List all text objects |
### Style
Style management commands.
| Command | Description |
|---------|-------------|
| `set-fill` | Set the fill color of an object |
| `set-stroke` | Set the stroke color (and optionally width) of an object |
| `set-opacity` | Set the opacity of an object (0.0-1.0) |
| `set` | Set an arbitrary style property on an object |
| `get` | Get the style properties of an object |
| `list-properties` | List all available style properties |
### Transform
Transform operations (translate, rotate, scale, skew).
| Command | Description |
|---------|-------------|
| `translate` | Translate (move) an object |
| `rotate` | Rotate an object |
| `scale` | Scale an object |
| `skew-x` | Skew an object horizontally |
| `skew-y` | Skew an object vertically |
| `get` | Get the current transform of an object |
| `clear` | Clear all transforms from an object |
### Layer
Layer management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a new layer |
| `remove` | Remove a layer by index |
| `move-object` | Move an object to a different layer |
| `set` | Set a layer property (name, visible, locked, opacity) |
| `list` | List all layers |
| `reorder` | Move a layer from one position to another |
| `get` | Get detailed info about a layer |
### Path Group
Path boolean operations.
| Command | Description |
|---------|-------------|
| `union` | Union of two objects |
| `intersection` | Intersection of two objects |
| `difference` | Difference of two objects (A minus B) |
| `exclusion` | Exclusion (XOR) of two objects |
| `convert` | Convert a shape to a path |
| `list-operations` | List available path boolean operations |
### Gradient
Gradient management commands.
| Command | Description |
|---------|-------------|
| `add-linear` | Add a linear gradient |
| `add-radial` | Add a radial gradient |
| `apply` | Apply a gradient to an object |
| `list` | List all gradients |
### Export Group
Export/render commands.
| Command | Description |
|---------|-------------|
| `png` | Render the document to PNG |
| `svg` | Export the document as SVG |
| `pdf` | Export the document as PDF (requires Inkscape) |
| `presets` | List export presets |
### Session
Session management commands.
| Command | Description |
|---------|-------------|
| `status` | Show session status |
| `undo` | Undo the last operation |
| `redo` | Redo the last undone operation |
| `history` | Show undo history |
## Examples
### Create a New Project
Create a new inkscape project file.
```bash
cli-anything-inkscape project new -o myproject.json
# Or with JSON output for programmatic use
cli-anything-inkscape --json project new -o myproject.json
```
### Interactive REPL Session
Start an interactive session with undo/redo support.
```bash
cli-anything-inkscape
# Enter commands interactively
# Use 'help' to see available commands
# Use 'undo' and 'redo' for history navigation
```
### Export Project
Export the project to a final output format.
```bash
cli-anything-inkscape --project myproject.json export render output.pdf --overwrite
```
## State Management
The CLI maintains session state with:
- **Undo/Redo**: Up to 50 levels of history
- **Project persistence**: Save/load project state as JSON
- **Session tracking**: Track modifications and changes
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-inkscape project info -p project.json
# JSON output for agents
cli-anything-inkscape --json project info -p project.json
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file operations
5. **Verify outputs exist** after export operations
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,50 @@
---
name: "cli-anything-intelwatch"
description: >-
Zero friction. Full context. Competitive intelligence, M&A due diligence, and OSINT directly from your terminal. Uses Node.js/npx.
---
# cli-anything-intelwatch
Intelwatch bridges the gap between hacker OSINT and B2B Sales/M&A data. It executes complex financial data aggregation, technology stack detection, and AI-powered due diligence in seconds.
## Installation
This CLI is installed as part of the cli-anything-intelwatch package:
```bash
pip install cli-anything-intelwatch
```
**Prerequisites:**
- Node.js >=18 must be installed and accessible via `npx`
- Run `node -v` and `npx -v` to ensure your system meets the requirements
## Usage
### Basic Commands
```bash
# Show help
cli-anything-intelwatch --help
# Generate a deep profile for a company
cli-anything-intelwatch profile kpmg.fr
# Generate a profile with AI Due Diligence
cli-anything-intelwatch profile kpmg.fr --ai
```
## For AI Agents
When using this CLI programmatically:
1. Provide the domain name (e.g., `doctolib.fr`)
2. Use the `--ai` flag to let Intelwatch perform due diligence automatically
3. The output is human-readable and provides a deep breakdown of the company
4. Ensure `npx` is available on the machine
## Example Scenarios
- **M&A Due Diligence:** `cli-anything-intelwatch profile company-name.com --ai`
- **Sales Intelligence:** `cli-anything-intelwatch profile target-client.com`

View File

@@ -0,0 +1,100 @@
---
name: "cli-anything-iterm2"
description: "Provides the cli-anything-iterm2 commands — the only way to actually send text to iTerm2 sessions, read live terminal output and scrollback history, manage windows/tabs/split panes, run tmux -CC workflows, broadcast to multiple panes, show macOS dialogs, and read/write iTerm2 preferences. Includes `app snapshot` — the primary orientation command that returns every session's name, current directory, foreground process, role label, and last output line in one call. Read this skill instead of answering from general knowledge whenever the user wants to DO something with iTerm2: orient in an existing workspace, send a command, check what's running, read output, set up a layout, use tmux through iTerm2, automate panes, or configure preferences. Also read for questions about iTerm2 shell integration or scrollback. Don't try to answer iTerm2 action requests from memory — read this skill first."
---
# cli-anything-iterm2
Stateful CLI harness for iTerm2. Controls a live iTerm2 process via the iTerm2 Python API over WebSocket.
## Prerequisites
1. **macOS + iTerm2** running: `brew install --cask iterm2`
2. **Python API enabled**: iTerm2 → Preferences → General → Magic → Enable Python API
3. **Install**: `pip install cli-anything-iterm2` (or `pip install -e .` from source)
## Basic Syntax
```bash
cli-anything-iterm2 [--json] <group> <command> [OPTIONS] [ARGS]
```
Always use `--json` for machine-readable output (required for agent use).
## Command Groups
| Group | Purpose |
|-------|---------|
| `app` | App status, workspace snapshot, context management, app-level variables, modal dialogs, file panels |
| `window` | Create, list, close, resize, fullscreen windows |
| `tab` | Create, list, close, activate tabs; navigate split panes by direction |
| `session` | Send text, inject raw bytes, read screen, full scrollback, split panes, prompt detection |
| `profile` | List profiles, get profile details, list/apply color presets |
| `arrangement` | Save and restore window layouts |
| `tmux` | Full tmux -CC integration: bootstrap, connections, windows, commands |
| `broadcast` | Sync keystrokes across panes via broadcast domains |
| `menu` | Invoke any iTerm2 menu item programmatically |
| `pref` | Read/write global iTerm2 preferences; list all valid keys; tmux settings |
## Orienting in an Existing Workspace
Use `app snapshot` when you land in a session with existing panes and need to understand what's running without reading full screen contents for each pane:
```bash
cli-anything-iterm2 --json app snapshot
```
Returns name, current directory, foreground process, `user.role` label, and last visible output line for every session across all windows.
**Naming convention** — label panes when setting up a workspace so you can find them later:
```bash
cli-anything-iterm2 session set-var user.role "api-server"
cli-anything-iterm2 session set-var user.role "log-tail"
cli-anything-iterm2 session set-var user.role "editor"
```
`app snapshot` will surface these roles alongside process and path, giving you a full picture in one call.
## Typical Agent Workflow
```bash
# 1. Orient — snapshot every session: name, path, process, role, last output line
cli-anything-iterm2 --json app snapshot
# 2. Establish context (saves window/tab/session IDs for subsequent commands)
cli-anything-iterm2 app current
# 3. Interact — no --session-id needed once context is set
cli-anything-iterm2 session send "git status"
cli-anything-iterm2 --json session scrollback --tail 200 --strip
# 4. Create a multi-pane workspace — label panes so snapshot identifies them later
cli-anything-iterm2 session split --vertical --use-as-context
cli-anything-iterm2 session send "python3 -m http.server 8000"
cli-anything-iterm2 session set-var user.role "http-server"
```
## Reference Files
Read only what the task requires — each file is a single narrow concern (~1030 lines):
| File | Read when you need... |
|------|-----------------------|
| `references/session-io.md` | Send text, inject bytes, read screen/scrollback, get selection |
| `references/session-control.md` | Split panes, activate/close sessions, resize, rename, session variables |
| `references/session-shell-integration.md` | wait-prompt, wait-command-end, get-prompt; reliable send→wait→read pattern |
| `references/layout-window-tab.md` | Create/close/resize windows and tabs, navigate split panes |
| `references/layout-arrangement.md` | Save and restore window layouts |
| `references/app-context.md` | **Snapshot** (orientation), status, context management, app vars, modal dialogs, file panels |
| `references/profile-pref.md` | Profiles list/get/presets, preferences read/write, tmux pref shortcuts |
| `references/broadcast-menu.md` | Broadcast keystrokes to multiple panes, invoke menu items |
| `references/tmux-commands.md` | All tmux CLI commands (bootstrap, send, tabs, create-window, set-visible) |
| `references/tmux-guide.md` | Full tmux -CC workflow, pane→session ID mapping |
| `references/json-session.md` | `--json` schemas for session, window, tab, screen, scrollback, inject |
| `references/json-tmux-app.md` | `--json` schemas for tmux, app dialogs, preferences, errors |
## REPL Mode
Run without arguments for an interactive REPL that maintains context between commands:
```bash
cli-anything-iterm2
```

View File

@@ -0,0 +1,233 @@
---
name: "cli-anything-kdenlive"
description: >-
Command-line interface for Kdenlive - A stateful command-line interface for video editing, following the same patterns as the Blender CLI ...
---
# cli-anything-kdenlive
A stateful command-line interface for video editing, following the same patterns as the Blender CLI harness. Uses a JSON project format with MLT XML generation for Kdenlive/melt.
## Installation
This CLI is installed as part of the cli-anything-kdenlive package:
```bash
pip install cli-anything-kdenlive
```
**Prerequisites:**
- Python 3.10+
- kdenlive must be installed on your system
## Usage
### Basic Commands
```bash
# Show help
cli-anything-kdenlive --help
# Start interactive REPL mode
cli-anything-kdenlive
# Create a new project
cli-anything-kdenlive project new -o project.json
# Run with JSON output (for agent consumption)
cli-anything-kdenlive --json project info -p project.json
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-kdenlive
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Project
Project management commands.
| Command | Description |
|---------|-------------|
| `new` | Create a new project |
| `open` | Open an existing project |
| `save` | Save the current project |
| `info` | Show project information |
| `profiles` | List available video profiles |
| `json` | Print raw project JSON |
### Bin Group
Media bin management commands.
| Command | Description |
|---------|-------------|
| `import` | Import a clip into the media bin |
| `remove` | Remove a clip from the bin |
| `list` | List all clips in the bin |
| `get` | Get detailed clip info |
### Timeline
Timeline management commands.
| Command | Description |
|---------|-------------|
| `add-track` | Add a track to the timeline |
| `remove-track` | Remove a track |
| `add-clip` | Add a clip to a track |
| `remove-clip` | Remove a clip from a track |
| `trim` | Trim a clip's in/out points |
| `split` | Split a clip at a time offset |
| `move` | Move a clip to a new position |
| `list` | List all tracks |
### Filter Group
Filter/effect management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a filter to a clip |
| `remove` | Remove a filter from a clip |
| `set` | Set a filter parameter |
| `list` | List filters on a clip |
| `available` | List all available filters |
### Transition
Transition management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a transition between tracks |
| `remove` | Remove a transition |
| `set` | Set a transition parameter |
| `list` | List all transitions |
### Guide
Guide/marker management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a guide at a position (seconds) |
| `remove` | Remove a guide |
| `list` | List all guides |
### Export
Export and render commands.
| Command | Description |
|---------|-------------|
| `xml` | Generate Kdenlive/MLT XML |
| `presets` | List available render presets |
### Session
Session management commands.
| Command | Description |
|---------|-------------|
| `status` | Show session status |
| `undo` | Undo the last operation |
| `redo` | Redo the last undone operation |
| `history` | Show undo history |
## Examples
### Create a New Project
Create a new kdenlive project file.
```bash
cli-anything-kdenlive project new -o myproject.json
# Or with JSON output for programmatic use
cli-anything-kdenlive --json project new -o myproject.json
```
### Interactive REPL Session
Start an interactive session with undo/redo support.
```bash
cli-anything-kdenlive
# Enter commands interactively
# Use 'help' to see available commands
# Use 'undo' and 'redo' for history navigation
```
### Export Project
Export the project to a final output format.
```bash
cli-anything-kdenlive --project myproject.json export render output.pdf --overwrite
```
## State Management
The CLI maintains session state with:
- **Undo/Redo**: Up to 50 levels of history
- **Project persistence**: Save/load project state as JSON
- **Session tracking**: Track modifications and changes
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-kdenlive project info -p project.json
# JSON output for agents
cli-anything-kdenlive --json project info -p project.json
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file operations
5. **Verify outputs exist** after export operations
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,113 @@
---
name: "cli-anything-krita"
description: "CLI harness for Krita digital painting — manage projects, layers, filters, and export via command line. Use when automating Krita workflows, batch processing images, or operating Krita without a GUI."
---
# cli-anything-krita
CLI harness for **Krita**, the professional open-source digital painting application.
## Prerequisites
- **Krita** installed on the system
- **Python 3.10+**
Install the CLI:
```bash
cd krita/agent-harness && pip install -e .
```
## Command Reference
### Project Management
```bash
cli-anything-krita project new -n "My Art" -w 2048 -h 2048 -o project.json
cli-anything-krita project open project.json
cli-anything-krita --project project.json project save
cli-anything-krita --project project.json project info
```
### Layer Management
```bash
cli-anything-krita -p project.json layer add "Sketch" -t paintlayer
cli-anything-krita -p project.json layer add "Colors" --opacity 200
cli-anything-krita -p project.json layer add "Group" -t grouplayer
cli-anything-krita -p project.json layer remove "Sketch"
cli-anything-krita -p project.json layer list
cli-anything-krita -p project.json layer set "Colors" opacity 180
cli-anything-krita -p project.json layer set "Colors" visible false
cli-anything-krita -p project.json layer set "Colors" blending_mode multiply
```
Layer types: `paintlayer`, `grouplayer`, `vectorlayer`, `filterlayer`, `filllayer`, `clonelayer`, `filelayer`
### Filters
```bash
cli-anything-krita -p project.json filter apply blur -l "Background"
cli-anything-krita -p project.json filter apply sharpen
cli-anything-krita -p project.json filter apply levels -c '{"shadows": 10, "highlights": 240}'
cli-anything-krita filter list
```
Available: blur, sharpen, desaturate, levels, curves, brightness-contrast, hue-saturation, color-balance, unsharp-mask, posterize, threshold
### Canvas Operations
```bash
cli-anything-krita -p project.json canvas resize -w 4096 -h 4096
cli-anything-krita -p project.json canvas resize --resolution 600
cli-anything-krita -p project.json canvas info
```
### Export
```bash
cli-anything-krita -p project.json export render output.png -p png --overwrite
cli-anything-krita -p project.json export render output.jpg -p jpeg
cli-anything-krita -p project.json export render output.psd -p psd
cli-anything-krita -p project.json export animation ./frames/ -p png
cli-anything-krita export presets
cli-anything-krita export formats
```
Presets: png, png-web, jpeg, jpeg-web, jpeg-low, tiff, tiff-lzw, psd, pdf, svg, webp, gif, bmp
### Session (Undo/Redo)
```bash
cli-anything-krita session undo
cli-anything-krita session redo
cli-anything-krita session history
```
### Status
```bash
cli-anything-krita status
```
## Agent Usage (JSON Mode)
All commands support `--json` for machine-readable output:
```bash
cli-anything-krita --json -p project.json project info
cli-anything-krita --json -p project.json layer list
cli-anything-krita --json status
```
## Example Workflow
```bash
# 1. Create project
cli-anything-krita --json project new -n "Illustration" -w 3000 -h 4000 -o art.json
# 2. Set up layer stack
cli-anything-krita --json -p art.json layer add "Background" -t paintlayer
cli-anything-krita --json -p art.json layer add "Sketch" -t paintlayer --opacity 180
cli-anything-krita --json -p art.json layer add "Inking" -t paintlayer
cli-anything-krita --json -p art.json layer add "Colors" -t paintlayer
cli-anything-krita --json -p art.json layer add "Effects" -t paintlayer --opacity 128
# 3. Apply effects
cli-anything-krita --json -p art.json filter apply blur -l "Background"
# 4. Export
cli-anything-krita --json -p art.json export render final.png -p png --overwrite
```

View File

@@ -0,0 +1,226 @@
---
name: "cli-anything-libreoffice"
description: >-
Command-line interface for Libreoffice - A stateful command-line interface for document editing, producing real ODF files (ZIP archives with ...
---
# cli-anything-libreoffice
A stateful command-line interface for document editing, producing real ODF files (ZIP archives with XML). Designed for AI agents and power users who need to create and manipulate Writer, Calc, and Impress documents without a GUI or LibreOffice installation.
## Installation
This CLI is installed as part of the cli-anything-libreoffice package:
```bash
pip install cli-anything-libreoffice
```
**Prerequisites:**
- Python 3.10+
- libreoffice must be installed on your system
## Usage
### Basic Commands
```bash
# Show help
cli-anything-libreoffice --help
# Start interactive REPL mode
cli-anything-libreoffice
# Create a new project
cli-anything-libreoffice project new -o project.json
# Run with JSON output (for agent consumption)
cli-anything-libreoffice --json project info -p project.json
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-libreoffice
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Document
Document management commands.
| Command | Description |
|---------|-------------|
| `new` | Create a new document |
| `open` | Open an existing project file |
| `save` | Save the current document |
| `info` | Show document information |
| `profiles` | List available page profiles |
| `json` | Print raw project JSON |
### Writer
Writer (word processor) commands.
| Command | Description |
|---------|-------------|
| `add-paragraph` | Add a paragraph to the document |
| `add-heading` | Add a heading to the document |
| `add-list` | Add a list to the document |
| `add-table` | Add a table to the document |
| `add-page-break` | Add a page break |
| `remove` | Remove a content item by index |
| `list` | List all content items |
| `set-text` | Set the text of a content item |
### Calc
Calc (spreadsheet) commands.
| Command | Description |
|---------|-------------|
| `add-sheet` | Add a new sheet |
| `remove-sheet` | Remove a sheet by index |
| `rename-sheet` | Rename a sheet |
| `set-cell` | Set a cell value |
| `get-cell` | Get a cell value |
| `list-sheets` | List all sheets |
### Impress
Impress (presentation) commands.
| Command | Description |
|---------|-------------|
| `add-slide` | Add a slide to the presentation |
| `remove-slide` | Remove a slide by index |
| `set-content` | Update a slide's title and/or content |
| `list-slides` | List all slides |
| `add-element` | Add an element to a slide |
### Style Group
Style management commands.
| Command | Description |
|---------|-------------|
| `create` | Create a new style |
| `modify` | Modify an existing style |
| `list` | List all styles |
| `apply` | Apply a style to a content item (Writer only) |
| `remove` | Remove a style |
### Export Group
Export/render commands.
| Command | Description |
|---------|-------------|
| `presets` | List export presets |
| `preset-info` | Show preset details |
| `render` | Export the document to a file |
### Session
Session management commands.
| Command | Description |
|---------|-------------|
| `status` | Show session status |
| `undo` | Undo the last operation |
| `redo` | Redo the last undone operation |
| `history` | Show undo history |
## Examples
### Create a New Project
Create a new libreoffice project file.
```bash
cli-anything-libreoffice project new -o myproject.json
# Or with JSON output for programmatic use
cli-anything-libreoffice --json project new -o myproject.json
```
### Interactive REPL Session
Start an interactive session with undo/redo support.
```bash
cli-anything-libreoffice
# Enter commands interactively
# Use 'help' to see available commands
# Use 'undo' and 'redo' for history navigation
```
### Export Project
Export the project to a final output format.
```bash
cli-anything-libreoffice --project myproject.json export render output.pdf --overwrite
```
## State Management
The CLI maintains session state with:
- **Undo/Redo**: Up to 50 levels of history
- **Project persistence**: Save/load project state as JSON
- **Session tracking**: Track modifications and changes
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-libreoffice project info -p project.json
# JSON output for agents
cli-anything-libreoffice --json project info -p project.json
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file operations
5. **Verify outputs exist** after export operations
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,182 @@
---
name: "cli-anything-mermaid"
description: >-
Command-line interface for Mermaid Live Editor - Create, edit, and render Mermaid diagrams via stateful project files and mermaid.ink renderer URLs. Designed for AI agents and power users who need to generate flowcharts, sequence diagrams, and other visualizations without a GUI.
---
# cli-anything-mermaid
Create, edit, and render Mermaid diagrams via stateful project files and the mermaid.ink renderer. Designed for AI agents and power users who need to generate flowcharts, sequence diagrams, and other visualizations without a GUI.
## Installation
This CLI is installed as part of the cli-anything-mermaid package:
```bash
pip install cli-anything-mermaid
```
**Prerequisites:**
- Python 3.10+
- No external software required (rendering uses mermaid.ink cloud service)
## Usage
### Basic Commands
```bash
# Show help
cli-anything-mermaid --help
# Start interactive REPL mode
cli-anything-mermaid
# Create a new project
cli-anything-mermaid project new -o diagram.json
# Run with JSON output (for agent consumption)
cli-anything-mermaid --json project info
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-mermaid
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Project
Project lifecycle commands.
| Command | Description |
|---------|-------------|
| `new` | Create a new Mermaid project with optional sample preset and theme |
| `open` | Open an existing Mermaid project file |
| `save` | Save the current project to a file |
| `info` | Show current project information |
| `samples` | List available sample diagram presets |
### Diagram
Diagram source manipulation commands.
| Command | Description |
|---------|-------------|
| `set` | Replace the Mermaid source text (inline or from file) |
| `show` | Print the current Mermaid source code |
### Export
Render and share commands.
| Command | Description |
|---------|-------------|
| `render` | Render the diagram to SVG or PNG via mermaid.ink |
| `share` | Generate a Mermaid Live Editor URL for sharing |
### Session
Session state commands.
| Command | Description |
|---------|-------------|
| `status` | Show current session state |
| `undo` | Undo the last diagram source change |
| `redo` | Redo the last undone change |
## Examples
### Create and Render a Flowchart
```bash
# Create a project with flowchart sample
cli-anything-mermaid project new --sample flowchart -o flow.json
# Replace diagram source
cli-anything-mermaid --project flow.json diagram set --text "graph TD; A-->B; B-->C;"
# Render to SVG
cli-anything-mermaid --project flow.json export render output.svg --format svg
```
### Create a Sequence Diagram
```bash
# Create project with sequence sample
cli-anything-mermaid project new --sample sequence -o seq.json
# Set diagram from file
cli-anything-mermaid --project seq.json diagram set --file my_diagram.mmd
# Render to PNG
cli-anything-mermaid --project seq.json export render output.png --format png
```
### Share a Diagram
```bash
# Generate an editable Mermaid Live URL
cli-anything-mermaid --project flow.json export share --mode edit
# Generate a view-only URL
cli-anything-mermaid --project flow.json export share --mode view
```
### Interactive REPL Session
```bash
cli-anything-mermaid
# new flowchart
# set graph TD; A-->B; B-->C;
# render output.svg
# share
# quit
```
## State Management
The CLI maintains session state with:
- **Undo/Redo**: Revert or replay diagram source changes
- **Project persistence**: Save/load project state as JSON
- **Session tracking**: Track modifications and changes
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-mermaid project info
# JSON output for agents
cli-anything-mermaid --json project info
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file operations
5. **Verify outputs exist** after render operations
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,201 @@
---
name: "cli-anything-mubu"
description: >-
Command-line interface for Mubu - Canonical packaged entrypoint for the Mubu live bridge....
---
# cli-anything-mubu
Canonical packaged entrypoint for the Mubu live bridge.
## Installation
This CLI is packaged from the canonical `agent-harness` source tree:
```bash
pip install -e .
```
**Prerequisites:**
- Python 3.10+
- An active Mubu desktop session on this machine
- Local Mubu profile data available to the CLI
- Set `MUBU_DAILY_FOLDER` if you want no-argument daily helpers
## Entry Points
```bash
cli-anything-mubu
python -m cli_anything.mubu
```
When invoked without a subcommand, the CLI enters an interactive REPL session.
## Command Groups
### Discover
Discovery commands for folders, documents, recency, and daily-document resolution.
| Command | Description |
|---------|-------------|
| `docs` | List latest known document snapshots from local backups. |
| `folders` | List folder metadata from local RxDB storage. |
| `folder-docs` | List document metadata for one folder. |
| `path-docs` | List documents for one folder path or folder id. |
| `recent` | List recently active documents using backups, metadata, and sync logs. |
| `daily` | Find Daily-style folders and list the documents inside them. |
| `daily-current` | Resolve the current daily document from one Daily-style folder. |
### Inspect
Inspection commands for tree views, search, links, sync events, and live node targeting.
| Command | Description |
|---------|-------------|
| `show` | Show the latest backup tree for one document. |
| `search` | Search latest backups for matching node text or note content. |
| `changes` | Parse recent client-sync change events from local logs. |
| `links` | Extract outbound Mubu document links from one document backup. |
| `open-path` | Open one document by full path, suffix path, title, or doc id. |
| `doc-nodes` | List live document nodes with node ids and update-target paths. |
| `daily-nodes` | List live nodes from the current daily document in one step. |
### Mutate
Mutation commands for dry-run-first atomic live edits against the Mubu API.
| Command | Description |
|---------|-------------|
| `create-child` | Build or execute one child-node creation against the live Mubu API. |
| `delete-node` | Build or execute one node deletion against the live Mubu API. |
| `update-text` | Build or execute one text update against the live Mubu API. |
### Session
Session and state commands for current document/node context and local command history.
| Command | Description |
|---------|-------------|
| `status` | Show the current session state. |
| `state-path` | Show the session state file path. |
| `use-doc` | Persist the current document reference. |
| `use-node` | Persist the current node reference. |
| `use-daily` | Resolve and persist the current daily document reference. |
| `clear-doc` | Clear the current document reference. |
| `clear-node` | Clear the current node reference. |
| `history` | Show recent command history stored in session state. |
## Recommended Agent Workflow
```text
discover daily-current '<daily-folder-ref>' --json
->
inspect daily-nodes '<daily-folder-ref>' --query '<anchor>' --json
->
session use-doc '<doc_path>'
->
mutate update-text / create-child / delete-node --json
->
--execute only after payload inspection
```
## Safety Rules
1. Prefer grouped commands for agent use; flat legacy commands remain for compatibility.
2. Use `--json` whenever an agent will parse the output.
3. Prefer `discover` or `inspect` commands before any `mutate` command.
4. Live mutations are dry-run by default and only execute with `--execute`.
5. Prefer `--node-id` and `--parent-node-id` over text matching.
6. `delete-node` removes the full targeted subtree.
7. Even same-text updates can still advance document version history.
8. Pass a daily-folder reference explicitly or set `MUBU_DAILY_FOLDER` before using no-arg daily helpers.
## Examples
### Interactive REPL Session
Start an interactive session with persistent document and node context.
```bash
cli-anything-mubu
# Enter commands interactively
# Use 'help' to see builtins
# Use session commands to persist current-doc/current-node
```
### Discover Current Daily Note
Resolve the current daily note from an explicit folder reference.
```bash
cli-anything-mubu --json discover daily-current '<daily-folder-ref>'
```
### Dry-Run Atomic Update
Inspect the exact outgoing payload before a live mutation.
```bash
cli-anything-mubu mutate update-text '<doc-ref>' --node-id <node-id> --text 'new text' --json
```
## Session State
The CLI maintains lightweight session state in JSON:
- `current_doc`
- `current_node`
- local command history
Use the `session` command group to inspect or update this state.
## For AI Agents
1. Start with `discover` or `inspect`, not `mutate`.
2. Use `session status --json` to recover persisted context.
3. Use grouped commands in generated prompts and automation.
4. Verify postconditions after any live mutation.
5. Read the package `TEST.md` and `README.md` when stricter operational detail is needed.
## Version
0.1.1

View File

@@ -0,0 +1,94 @@
---
name: "cli-anything-musescore"
display_name: MuseScore
version: 1.0.0
description: CLI for music notation — transpose, export PDF/audio/MIDI, extract parts, manage instruments
requires: MuseScore 4 (musescore.org)
entry_point: cli-anything-musescore
category: music
---
# MuseScore CLI Skill
## Overview
Wraps MuseScore 4's `mscore` backend for music notation tasks: transposition, export to multiple formats (PDF, PNG, MP3, MIDI, MusicXML, Braille), part extraction, instrument management, and score analysis.
## Commands
### Project Management
```bash
cli-anything-musescore --json project info -i score.mscz
cli-anything-musescore --json project open -i score.mscz
```
### Transposition
```bash
# Transpose to a target key
cli-anything-musescore --json transpose by-key -i score.mscz -o out.mscz --target-key "C major" --direction closest
# Transpose by semitones
cli-anything-musescore --json transpose by-interval -i score.mscz -o out.mscz --semitones 3
# Diatonic transposition
cli-anything-musescore --json transpose diatonic -i score.mscz -o out.mscz --steps 2
```
### Part Extraction
```bash
cli-anything-musescore --json parts list -i score.mscz
cli-anything-musescore --json parts extract -i score.mscz -o piano.mscz --part "Piano"
cli-anything-musescore --json parts generate -i score.mscz -d ./parts/
```
### Export
```bash
cli-anything-musescore --json export pdf -i score.mscz -o score.pdf
cli-anything-musescore --json export mp3 -i score.mscz -o score.mp3 --bitrate 192
cli-anything-musescore --json export png -i score.mscz -o score.png --dpi 300
cli-anything-musescore --json export midi -i score.mscz -o score.mid
cli-anything-musescore --json export musicxml -i score.mscz -o score.musicxml
cli-anything-musescore --json export braille -i score.mscz -o score.brf
cli-anything-musescore --json export batch -i score.mscz -o score.pdf -o score.mid
```
### Instrument Management
```bash
cli-anything-musescore --json instruments list -i score.mscz
cli-anything-musescore --json instruments add -i score.mscz -o out.mscz --id keyboard.piano --name "Piano"
cli-anything-musescore --json instruments remove -i score.mscz -o out.mscz --name "Violin"
```
### Score Analysis
```bash
cli-anything-musescore --json media probe -i score.mscz
cli-anything-musescore --json media stats -i score.mscz
cli-anything-musescore --json media diff --reference a.mscz --compare b.mscz
```
### Session
```bash
cli-anything-musescore --json session status
cli-anything-musescore --json session undo
cli-anything-musescore --json session redo
cli-anything-musescore --json session history
```
## Supported Input Formats
- `.mscz` (MuseScore native)
- `.mxl` (compressed MusicXML)
- `.musicxml` / `.xml` (MusicXML)
- `.mid` / `.midi` (MIDI)
## Key Names for Transposition
Major: Cb, Gb, Db, Ab, Eb, Bb, F, C, G, D, A, E, B, F#, C#
Minor: Ab, Eb, Bb, F, C, G, D, A, E, B, F#, C#, G#, D#, A#
Accepted formats: "C", "C major", "Am", "A minor", "Db major", "F# minor"
## Agent Guidance
- Always use `--json` flag for machine-readable output
- Verify exports with `export verify` after rendering
- Use `media probe` to inspect an unknown score before operating on it
- Transposition requires both `-i` (input) and `-o` (output)
- Part names are case-insensitive for `parts extract`

View File

@@ -0,0 +1,50 @@
---
name: "cli-anything-n8n"
description: >-
Command-line interface for n8n workflow automation platform.
Manages workflows, executions, credentials, variables, and tags.
Based on n8n Public API v1.1.1 (n8n >= 1.0.0).
---
# cli-anything-n8n
CLI harness for n8n workflow automation — built with the CLI-Anything pattern.
Verified against n8n OpenAPI spec v1.1.1.
## Installation
```bash
pip install cli-anything-n8n
```
## Configuration
```bash
cli-anything-n8n config set base_url https://your-n8n-instance.com
cli-anything-n8n config set api_key your-api-key
# Or environment variables
export N8N_BASE_URL=https://your-n8n-instance.com
export N8N_API_KEY=your-api-key
export N8N_TIMEOUT=60 # optional, default 30s
```
## Command Groups
| Group | Commands |
|-------|----------|
| workflow | list, get, create, update, delete, activate, deactivate, tags, set-tags, transfer |
| execution | list, get, delete, retry |
| credential | create, delete, schema, transfer |
| variable | list, create, update, delete |
| tag | list, get, create, update, delete |
| config | show, set |
## For AI Agents
- Always use `--json` flag for structured output
- Use `@file.json` to pass complex JSON from files
- Check return codes: 0 = success, non-zero = error
- All IDs are strings, not integers
- API key is always masked in `config show` output
- Not available in public API: data tables, credential listing, execution stop

View File

@@ -0,0 +1,78 @@
---
name: "cli-anything-notebooklm"
description: Experimental NotebookLM harness for listing notebooks, managing sources, asking questions, generating artifacts, and downloading outputs through an installed notebooklm CLI.
---
# cli-anything-notebooklm
Experimental NotebookLM harness for CLI-Anything.
## Installation
This package is intended to be installed from the harness directory:
```bash
cd notebooklm/agent-harness
python3 -m pip install -e .
```
Install the upstream NotebookLM CLI if needed:
```bash
python3 -m pip install --user 'notebooklm-py[browser]'
python3 -m playwright install chromium
```
## Requirements
- `notebooklm` command installed locally
- Valid local NotebookLM login session
## Usage
### Basic Commands
```bash
# Show help
cli-anything-notebooklm --help
# Start with a notebook context
cli-anything-notebooklm --notebook nb_123 source list
# Prefer JSON for agent use
cli-anything-notebooklm --json notebook list
```
## Command Groups
| Group | Purpose |
| --- | --- |
| `auth` | login and auth validation |
| `notebook` | notebook list, create, summary |
| `source` | source listing and URL add |
| `chat` | ask questions and inspect history |
| `artifact` | list and generate artifacts |
| `download` | fetch generated outputs |
| `share` | inspect sharing state |
## Agent Workflow
1. Check auth with `cli-anything-notebooklm auth status`
2. Discover notebook IDs with `cli-anything-notebooklm --json notebook list`
3. Use explicit `--notebook` for follow-up commands
4. Prefer `--json` only where the upstream `notebooklm` command supports it
## Agent Guidance
- Prefer explicit notebook IDs with `--notebook`.
- Use `--json` for machine-readable output only on commands that support it upstream.
- Treat this harness as experimental and unofficial.
- Do not expose auth files or cookies in logs.
- NotebookLM is a Google product; this harness is unofficial and not affiliated with Google.
## References
- CLI-Anything: https://github.com/HKUDS/CLI-Anything
- CLI-Anything HARNESS.md: https://github.com/HKUDS/CLI-Anything/blob/main/cli-anything-plugin/HARNESS.md
- notebooklm-py: https://github.com/teng-lin/notebooklm-py
- Google NotebookLM help: https://support.google.com/notebooklm/answer/16206563

View File

@@ -0,0 +1,170 @@
---
name: "cli-anything-novita"
description: >-
Command-line interface for Novita AI - An OpenAI-compatible AI API client for DeepSeek, GLM, and other models.
---
# cli-anything-novita
A CLI harness for **Novita AI** - an OpenAI-compatible API service for AI models like DeepSeek, GLM, and others.
## Installation
This CLI is installed as part of the cli-anything-novita package:
```bash
pip install cli-anything-novita
```
**Prerequisites:**
- Python 3.10+
- Novita API key from [novita.ai](https://novita.ai)
## Usage
### Basic Commands
```bash
# Show help
cli-anything-novita --help
# Start interactive REPL mode
cli-anything-novita
# Chat with model
cli-anything-novita chat --prompt "What is AI?" --model deepseek/deepseek-v3.2
# Streaming chat
cli-anything-novita stream --prompt "Write a poem about code"
# List available models
cli-anything-novita models
# JSON output (for agent consumption)
cli-anything-novita --json chat --prompt "Hello"
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-novita
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Chat
Chat with AI models through the Novita API.
| Command | Description |
|---------|-------------|
| `chat` | Chat with the Novita API |
| `stream` | Stream chat completion |
### Session
Session management for chat history.
| Command | Description |
|---------|-------------|
| `status` | Show session status |
| `clear` | Clear session history |
| `history` | Show command history |
### Config
Configuration management.
| Command | Description |
|---------|-------------|
| `set` | Set a configuration value |
| `get` | Get a configuration value (or show all) |
| `delete` | Delete a configuration value |
| `path` | Show the config file path |
### Utility
| Command | Description |
|---------|-------------|
| `test` | Test API connectivity |
| `models` | List available models |
## Examples
### Configure API Key
```bash
# Set API key via config file (recommended)
cli-anything-novita config set api_key "sk-xxx"
# Or use environment variable
export NOVITA_API_KEY="sk-xxx"
```
### Chat with DeepSeek
```bash
# Simple chat
cli-anything-novita chat --prompt "Explain quantum computing" --model deepseek/deepseek-v3.2
# Streaming chat
cli-anything-novita stream --prompt "Write a Python function to calculate factorial"
```
### Test Connectivity
```bash
# Verify API key and connectivity
cli-anything-novita test --model deepseek/deepseek-v3.2
# List all available models
cli-anything-novita models
```
## Default Models
The Novita API supports multiple model providers:
| Model ID | Provider | Description |
|----------|----------|-------------|
| `deepseek/deepseek-v3.2` | DeepSeek | DeepSeek V3.2 model (default) |
| `zai-org/glm-5` | Zhipu AI | GLM-5 model |
| `minimax/minimax-m2.5` | MiniMax | MiniMax M2.5 model |
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-novita chat --prompt "Hello"
# JSON output for agents
cli-anything-novita --json chat --prompt "Hello"
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file operations
5. **Verify outputs exist** after export operations
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,230 @@
---
name: "cli-anything-obs-studio"
description: >-
Command-line interface for Obs Studio - A stateful command-line interface for OBS Studio scene collection editing, following the same patter...
---
# cli-anything-obs_studio
A stateful command-line interface for OBS Studio scene collection editing, following the same patterns as the Blender CLI harness. Uses a JSON scene collection format. No OBS installation required for editing.
## Installation
This CLI is installed as part of the cli-anything-obs_studio package:
```bash
pip install cli-anything-obs_studio
```
**Prerequisites:**
- Python 3.10+
- obs_studio must be installed on your system
## Usage
### Basic Commands
```bash
# Show help
cli-anything-obs_studio --help
# Start interactive REPL mode
cli-anything-obs_studio
# Create a new project
cli-anything-obs_studio project new -o project.json
# Run with JSON output (for agent consumption)
cli-anything-obs_studio --json project info -p project.json
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-obs_studio
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Project
Project management commands.
| Command | Description |
|---------|-------------|
| `new` | Create a new OBS scene collection |
| `open` | Open an existing project |
| `save` | Save the current project |
| `info` | Show project information |
| `json` | Print raw project JSON |
### Scene Group
Scene management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a new scene |
| `remove` | Remove a scene by index |
| `duplicate` | Duplicate a scene |
| `set-active` | Set the active scene |
| `list` | List all scenes |
### Source Group
Source management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a source to a scene |
| `remove` | Remove a source by index |
| `duplicate` | Duplicate a source |
| `set` | Set a source property (name, visible, locked, opacity, rotation) |
| `transform` | Transform a source (position, size, crop, rotation) |
| `list` | List all sources in a scene |
### Filter Group
Filter management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a filter to a source |
| `remove` | Remove a filter from a source |
| `set` | Set a filter parameter |
| `list` | List all filters on a source |
| `list-available` | List all available filter types |
### Audio Group
Audio management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a global audio source |
| `remove` | Remove a global audio source |
| `volume` | Set volume for an audio source (0.0-3.0) |
| `mute` | Mute an audio source |
| `unmute` | Unmute an audio source |
| `monitor` | Set audio monitoring type |
| `list` | List all audio sources |
### Transition Group
Transition management commands.
| Command | Description |
|---------|-------------|
| `add` | Add a transition |
| `remove` | Remove a transition |
| `set-active` | Set the active transition |
| `duration` | Set transition duration in milliseconds |
| `list` | List all transitions |
### Output Group
Output/streaming/recording configuration.
| Command | Description |
|---------|-------------|
| `streaming` | Configure streaming settings |
| `recording` | Configure recording settings |
| `settings` | Configure output settings |
| `info` | Show current output configuration |
| `presets` | List available encoding presets |
### Session
Session management commands.
| Command | Description |
|---------|-------------|
| `status` | Show session status |
| `undo` | Undo the last operation |
| `redo` | Redo the last undone operation |
| `history` | Show undo history |
## Examples
### Create a New Project
Create a new obs_studio project file.
```bash
cli-anything-obs_studio project new -o myproject.json
# Or with JSON output for programmatic use
cli-anything-obs_studio --json project new -o myproject.json
```
### Interactive REPL Session
Start an interactive session with undo/redo support.
```bash
cli-anything-obs_studio
# Enter commands interactively
# Use 'help' to see available commands
# Use 'undo' and 'redo' for history navigation
```
## State Management
The CLI maintains session state with:
- **Undo/Redo**: Up to 50 levels of history
- **Project persistence**: Save/load project state as JSON
- **Session tracking**: Track modifications and changes
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-obs_studio project info -p project.json
# JSON output for agents
cli-anything-obs_studio --json project info -p project.json
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file operations
5. **Verify outputs exist** after export operations
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,234 @@
---
name: "cli-anything-obsidian"
description: >-
Command-line interface for Obsidian — Knowledge management and note-taking via Obsidian Local REST API. Designed for AI agents and power users who need to manage notes, search the vault, and execute commands without the GUI.
---
# cli-anything-obsidian
Knowledge management and note-taking via the Obsidian Local REST API. Designed for AI agents and power users who need to manage notes, search the vault, and execute commands without the GUI.
## Installation
This CLI is installed as part of the cli-anything-obsidian package:
```bash
pip install cli-anything-obsidian
```
**Prerequisites:**
- Python 3.10+
- Obsidian must be installed and running with the [Local REST API plugin](https://github.com/coddingtonbear/obsidian-local-rest-api) enabled
## Usage
### Basic Commands
```bash
# Show help
cli-anything-obsidian --help
# Start interactive REPL mode
cli-anything-obsidian
# List vault files
cli-anything-obsidian vault list
# Run with JSON output (for agent consumption)
cli-anything-obsidian --json vault list
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-obsidian
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Vault
Vault file management commands.
| Command | Description |
|---------|-------------|
| `list` | List files in the vault or a subdirectory |
| `read` | Read the content of a note |
| `create` | Create a new note |
| `update` | Overwrite an existing note |
| `delete` | Delete a note from the vault |
| `append` | Append content to an existing note |
### Search
Vault search commands.
| Command | Description |
|---------|-------------|
| `query` | Search using Obsidian query syntax |
| `simple` | Plain text search across the vault |
### Note
Active note commands.
| Command | Description |
|---------|-------------|
| `active` | Get the currently active note in Obsidian |
| `open` | Open a note in the Obsidian editor |
### Command
Obsidian command palette commands.
| Command | Description |
|---------|-------------|
| `list` | List all available Obsidian commands |
| `execute` | Execute a command by its ID |
### Server
Server status and info commands.
| Command | Description |
|---------|-------------|
| `status` | Check if the Obsidian Local REST API is running |
### Session
Session state commands.
| Command | Description |
|---------|-------------|
| `status` | Show current session state |
## Examples
### List and Read Notes
```bash
# List all vault files
cli-anything-obsidian vault list
# List files in a subdirectory
cli-anything-obsidian vault list "Daily Notes"
# Read a note
cli-anything-obsidian vault read "Projects/my-project.md"
```
### Create and Update Notes
```bash
# Create a new note
cli-anything-obsidian vault create "Projects/new-project.md" --content "# New Project"
# Update (overwrite) a note
cli-anything-obsidian vault update "Projects/new-project.md" --content "# Updated Content"
# Append to a note
cli-anything-obsidian vault append "Projects/new-project.md" --content "\n## New Section"
```
### Search
```bash
# Plain text search
cli-anything-obsidian search simple "meeting notes"
# Obsidian query syntax search (tags, links, etc.)
cli-anything-obsidian search query "tag:#project"
```
### Commands
```bash
# List available commands
cli-anything-obsidian command list
# Execute a command by ID
cli-anything-obsidian command execute "editor:toggle-bold"
```
### Interactive REPL Session
Start an interactive session for exploratory use.
```bash
cli-anything-obsidian
# Enter commands interactively
# Use 'help' to see available commands
```
### API Key Configuration
```bash
# Via flag
cli-anything-obsidian --api-key YOUR_KEY vault list
# Via environment variable (recommended for agents)
export OBSIDIAN_API_KEY=YOUR_KEY
cli-anything-obsidian vault list
```
## State Management
The CLI maintains lightweight session state:
- **API key**: Configurable via `--api-key` or `OBSIDIAN_API_KEY` environment variable
- **Host URL**: Defaults to `https://localhost:27124`; configurable via `--host`
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-obsidian vault list
# JSON output for agents
cli-anything-obsidian --json vault list
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Set `OBSIDIAN_API_KEY`** environment variable to avoid passing `--api-key` on every call
5. **Verify Obsidian is running** with `server status` before other commands
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,220 @@
---
name: "cli-anything-ollama"
description: >-
Command-line interface for Ollama - Local LLM inference and model management via Ollama REST API. Designed for AI agents and power users who need to manage models, generate text, chat, and create embeddings without a GUI.
---
# cli-anything-ollama
Local LLM inference and model management via the Ollama REST API. Designed for AI agents and power users who need to manage models, generate text, chat, and create embeddings without a GUI.
## Installation
This CLI is installed as part of the cli-anything-ollama package:
```bash
pip install cli-anything-ollama
```
**Prerequisites:**
- Python 3.10+
- Ollama must be installed and running (`ollama serve`)
## Usage
### Basic Commands
```bash
# Show help
cli-anything-ollama --help
# Start interactive REPL mode
cli-anything-ollama
# List available models
cli-anything-ollama model list
# Run with JSON output (for agent consumption)
cli-anything-ollama --json model list
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-ollama
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Model
Model management commands.
| Command | Description |
|---------|-------------|
| `list` | List locally available models |
| `show` | Show model details (parameters, template, license) |
| `pull` | Download a model from the Ollama library |
| `rm` | Delete a model from local storage |
| `copy` | Copy a model to a new name |
| `ps` | List models currently loaded in memory |
### Generate
Text generation and chat commands.
| Command | Description |
|---------|-------------|
| `text` | Generate text from a prompt |
| `chat` | Send a chat completion request |
### Embed
Embedding generation commands.
| Command | Description |
|---------|-------------|
| `text` | Generate embeddings for text |
### Server
Server status and info commands.
| Command | Description |
|---------|-------------|
| `status` | Check if Ollama server is running |
| `version` | Show Ollama server version |
### Session
Session state commands.
| Command | Description |
|---------|-------------|
| `status` | Show current session state |
| `history` | Show chat history for current session |
## Examples
### List and Pull Models
```bash
# List available models
cli-anything-ollama model list
# Pull a model
cli-anything-ollama model pull llama3.2
# Show model details
cli-anything-ollama model show llama3.2
```
### Generate Text
```bash
# Stream text (default)
cli-anything-ollama generate text --model llama3.2 --prompt "Explain quantum computing in one sentence"
# Non-streaming with JSON output (for agents)
cli-anything-ollama --json generate text --model llama3.2 --prompt "Hello" --no-stream
```
### Chat
```bash
# Single-turn chat
cli-anything-ollama generate chat --model llama3.2 --message "user:What is Python?"
# Multi-turn chat
cli-anything-ollama generate chat --model llama3.2 \
--message "user:What is Python?" \
--message "user:How does it compare to JavaScript?"
# Chat from JSON file
cli-anything-ollama generate chat --model llama3.2 --file messages.json
```
### Embeddings
```bash
cli-anything-ollama embed text --model nomic-embed-text --input "Hello world"
cli-anything-ollama embed text --model nomic-embed-text --input "Hello" --input "World"
```
### Interactive REPL Session
Start an interactive session for exploratory use.
```bash
cli-anything-ollama
# Enter commands interactively
# Use 'help' to see available commands
```
### Connect to Remote Host
```bash
cli-anything-ollama --host http://192.168.1.100:11434 model list
```
## State Management
The CLI maintains lightweight session state:
- **Current host URL**: Configurable via `--host`
- **Chat history**: Tracked for multi-turn conversations in REPL
- **Last used model**: Shown in REPL prompt
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-ollama model list
# JSON output for agents
cli-anything-ollama --json model list
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use `--no-stream`** for generate/chat to get complete responses
5. **Verify Ollama is running** with `server status` before other commands
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.1

View File

@@ -0,0 +1,178 @@
---
name: "cli-anything-openscreen"
description: >-
Command-line interface for Openscreen — a screen recording editor.
A stateful CLI for editing screen recordings with zoom, speed ramps,
trim, crop, annotations, and polished exports. Built on the Openscreen
JSON project format with ffmpeg as the rendering backend. Designed for
AI agents and power users who need programmatic video editing.
---
# cli-anything-openscreen
A stateful command-line interface for editing screen recordings. Transform raw
captures into polished demo videos with zoom effects, speed adjustments,
trimming, annotations, and beautiful backgrounds.
## Installation
```bash
pip install cli-anything-openscreen
```
**Prerequisites:**
- Python 3.10+
- ffmpeg must be installed on your system
## Usage
### Basic Commands
```bash
cli-anything-openscreen --help
cli-anything-openscreen # REPL mode
cli-anything-openscreen project new -v recording.mp4 -o project.openscreen
cli-anything-openscreen --json project info
```
### REPL Mode
Run `cli-anything-openscreen` without arguments to enter interactive mode.
Type `help` for available commands, `quit` to exit.
## Command Groups
### project
Create, open, save, and configure projects.
| Command | Description |
|---------|-------------|
| `project new [-v VIDEO] [-o PATH]` | Create new project with optional video |
| `project open <path>` | Open existing .openscreen project |
| `project save [-o PATH]` | Save project to file |
| `project info` | Show project metadata and region counts |
| `project set-video <path>` | Set source video file |
| `project set <key> <value>` | Set editor setting (padding, wallpaper, etc.) |
### zoom
Manage zoom regions — smooth zoom effects on specific timeline areas.
| Command | Description |
|---------|-------------|
| `zoom list` | List all zoom regions |
| `zoom add --start MS --end MS [--depth 1-6] [--focus-x 0-1] [--focus-y 0-1]` | Add zoom |
| `zoom remove <id>` | Remove zoom region |
Zoom depths: 1=1.25x, 2=1.5x, 3=1.8x, 4=2.2x, 5=3.5x, 6=5.0x
### speed
Manage speed regions — speed up idle time, slow down important moments.
| Command | Description |
|---------|-------------|
| `speed list` | List all speed regions |
| `speed add --start MS --end MS [--speed 0.25-2.0]` | Add speed change |
| `speed remove <id>` | Remove speed region |
Valid speeds: 0.25, 0.5, 0.75, 1.25, 1.5, 1.75, 2.0
### trim
Manage trim regions — cut out sections of the recording.
| Command | Description |
|---------|-------------|
| `trim list` | List all trim regions |
| `trim add --start MS --end MS` | Cut out a section |
| `trim remove <id>` | Remove trim region |
### crop
Set the visible area of the recording.
| Command | Description |
|---------|-------------|
| `crop get` | Show current crop region |
| `crop set --x 0-1 --y 0-1 --width 0-1 --height 0-1` | Set crop (normalized) |
### annotation
Add text overlays to the recording.
| Command | Description |
|---------|-------------|
| `annotation list` | List all annotations |
| `annotation add-text --start MS --end MS --text "..." [--x 0-1] [--y 0-1]` | Add text |
| `annotation remove <id>` | Remove annotation |
### media
Inspect and validate media files.
| Command | Description |
|---------|-------------|
| `media probe <path>` | Show video metadata (resolution, duration, codec) |
| `media check <path>` | Validate a video file |
| `media thumbnail <input> <output> [-t TIME]` | Extract a frame |
### export
Render the final polished video.
| Command | Description |
|---------|-------------|
| `export presets` | List available export presets |
| `export render <output_path>` | Render project to video file |
### session
Manage session state with undo/redo.
| Command | Description |
|---------|-------------|
| `session status` | Show session info |
| `session undo` | Undo last operation |
| `session redo` | Redo last undone operation |
| `session save` | Save session state to disk |
| `session list` | List all saved sessions |
## State Management
- **Undo/Redo**: Up to 50 levels of undo history
- **Project persistence**: JSON `.openscreen` files
- **Session tracking**: auto-tracks modifications
## Output Formats
- Human-readable (default): Formatted key-value pairs
- Machine-readable (`--json`): Structured JSON output
## Editor Settings
| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `aspectRatio` | string | "16:9" | 16:9, 9:16, 1:1, 4:3, 4:5 |
| `wallpaper` | string | "gradient_dark" | Background preset |
| `padding` | int | 50 | 0-100, padding around video |
| `borderRadius` | int | 12 | Corner radius in pixels |
| `shadowIntensity` | float | 0 | 0-1, drop shadow strength |
| `motionBlurAmount` | float | 0 | 0-1, motion blur during zoom |
| `exportQuality` | string | "good" | medium, good, source |
| `exportFormat` | string | "mp4" | mp4, gif |
## For AI Agents
1. Always use `--json` for parseable output
2. Check return codes — 0 = success, non-zero = error
3. Parse stderr for error messages in non-JSON mode
4. Use absolute file paths
5. After `export render`, verify the output exists and probe it
6. Times are in **milliseconds** for all region commands
7. Coordinates (focus, crop, position) are **normalized 0-1**
## Version
1.0.0

View File

@@ -0,0 +1,110 @@
---
name: "cli-anything-pm2"
description: >-
Command-line interface for PM2 - A stateless CLI for Node.js process management via the PM2 CLI. List, start, stop, restart processes, view logs, and manage system configuration.
---
# cli-anything-pm2
A stateless command-line interface for PM2 process management.
Communicates via the PM2 CLI subprocess. No local state or session.
## Installation
```bash
pip install -e .
```
**Prerequisites:**
- Python 3.10+
- PM2 installed globally (`npm install -g pm2`)
## Usage
### Basic Commands
```bash
# Show help
cli-anything-pm2 --help
# Start interactive REPL mode
cli-anything-pm2
# Run with JSON output (for agent consumption)
cli-anything-pm2 --json process list
cli-anything-pm2 --json system version
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-pm2
# Enter commands interactively with tab-completion and history
```
## Command Groups
### process
Process inspection commands.
| Command | Description |
|---------|-------------|
| `list` | List all PM2 processes |
| `describe <name>` | Get detailed info for a process |
| `metrics` | Get metrics for all processes |
### lifecycle
Process lifecycle commands.
| Command | Description |
|---------|-------------|
| `start <script> --name <name>` | Start a new process |
| `stop <name>` | Stop a process |
| `restart <name>` | Restart a process |
| `delete <name>` | Delete a process |
### logs
Log management commands.
| Command | Description |
|---------|-------------|
| `view <name> --lines 50` | View recent logs |
| `flush [name]` | Flush logs |
### system
System-level commands.
| Command | Description |
|---------|-------------|
| `save` | Save current process list |
| `startup` | Generate startup script |
| `version` | Get PM2 version |
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-pm2 process list
# JSON output for agents
cli-anything-pm2 --json process list
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
## Version
1.0.0

View File

@@ -0,0 +1,126 @@
---
name: "cli-anything-renderdoc"
description: CLI harness for RenderDoc graphics debugger capture analysis
version: 0.1.0
command: cli-anything-renderdoc
install: pip install cli-anything-renderdoc
requires:
- renderdoc (Python bindings from RenderDoc installation)
- click>=8.0
- prompt-toolkit>=3.0
categories:
- graphics
- debugging
- gpu
- rendering
---
# RenderDoc CLI Skill
Headless command-line analysis of RenderDoc GPU frame captures (`.rdc` files).
## Capabilities
- **Capture inspection**: metadata, sections, thumbnails, format conversion
- **Action tree**: list/search/filter draw calls, clears, dispatches, markers
- **Texture operations**: list, inspect, export (PNG/JPG/DDS/HDR/EXR), pixel picking
- **Pipeline state**: full shader/RT/viewport state at any event
- **Shader analysis**: export shader in human-readable form (HLSL/GLSL/disasm), constant buffer readback
- **Resource inspection**: buffer/texture enumeration, raw data reading
- **Mesh data**: vertex shader input/output decoding
- **GPU counters**: enumerate and fetch hardware performance counters
## Command Groups
### capture
```bash
cli-anything-renderdoc -c frame.rdc capture info # Metadata + sections
cli-anything-renderdoc -c frame.rdc capture thumb -o t.png # Extract thumbnail
cli-anything-renderdoc -c frame.rdc capture convert -o out.rdc --format rdc
```
### actions
```bash
cli-anything-renderdoc -c frame.rdc actions list # All actions
cli-anything-renderdoc -c frame.rdc actions list --draws-only # Draw calls only
cli-anything-renderdoc -c frame.rdc actions summary # Counts by type
cli-anything-renderdoc -c frame.rdc actions find "Shadow" # Search by name
cli-anything-renderdoc -c frame.rdc actions get 42 # Single action
```
### textures
```bash
cli-anything-renderdoc -c frame.rdc textures list
cli-anything-renderdoc -c frame.rdc textures get <id>
cli-anything-renderdoc -c frame.rdc textures save <id> -o out.png --format png
cli-anything-renderdoc -c frame.rdc textures save-outputs 42 -o ./renders/
cli-anything-renderdoc -c frame.rdc textures pick <id> 100 200
```
### pipeline
```bash
cli-anything-renderdoc -c frame.rdc pipeline state 42
# Export shader in human-readable form
# Text shaders (GLSL/HLSL) → saved directly
# Binary shaders (DXBC/SPIR-V) → embedded source (HLSL/GLSL) or disassembly
cli-anything-renderdoc -c frame.rdc pipeline shader-export 42 --stage Fragment
cli-anything-renderdoc -c frame.rdc pipeline shader-export 42 --stage Vertex -o ./shaders/
cli-anything-renderdoc -c frame.rdc pipeline cbuffer 42 --stage Vertex --index 0
# Compare pipeline state between two events
# Default output: same directory as the capture file ; use -o to override
cli-anything-renderdoc -c a.rdc pipeline diff 100 200 -b b.rdc
cli-anything-renderdoc -c frame.rdc pipeline diff 100 200 # same capture
cli-anything-renderdoc -c a.rdc pipeline diff 100 200 -b b.rdc -o result.json
cli-anything-renderdoc -c a.rdc pipeline diff 100 200 -b b.rdc --no-compact
```
### resources
```bash
cli-anything-renderdoc -c frame.rdc resources list
cli-anything-renderdoc -c frame.rdc resources buffers
cli-anything-renderdoc -c frame.rdc resources read-buffer <id> --format float32
```
### mesh
```bash
cli-anything-renderdoc -c frame.rdc mesh inputs 42 --max-vertices 10
cli-anything-renderdoc -c frame.rdc mesh outputs 42
```
### counters
```bash
cli-anything-renderdoc -c frame.rdc counters list
cli-anything-renderdoc -c frame.rdc counters fetch --ids 1,2,3
```
## JSON Mode
All commands support `--json` for machine-readable output:
```bash
cli-anything-renderdoc -c frame.rdc --json actions summary
```
## Environment Variables
| Variable | Description |
|----------------------|------------------------------|
| `RENDERDOC_CAPTURE` | Default capture file path |
| `PYTHONPATH` | Must include RenderDoc path |
## Agent Usage Notes
- **Use `pipeline shader-export` to extract shaders** — for binary shaders (DXBC/SPIR-V) it auto-exports embedded HLSL/GLSL source or falls back to disassembly; for text shaders (GLSL/HLSL) it saves the raw source directly
- **Shader formats by capture API**:
- D3D11 → DXBC binary, exported as embedded HLSL source (`.hlsl`) or bytecode asm (`.dxbc.asm`)
- OpenGL/GLES → GLSL source text (`.glsl`), already human-readable
- Vulkan → SPIR-V binary, exported as embedded GLSL source (`.glsl`) or SPIR-V asm (`.spv.asm`)
- **Use `pipeline diff` to compare two events** — it writes a JSON file and prints only the path; use `-b` for a second capture
- Always specify `--json` for programmatic consumption
- Use `actions summary` first to understand capture complexity
- Use `actions list --draws-only` to focus on actual rendering
- Pipeline state requires an event ID from the action list
- Texture save supports: png, jpg, bmp, tga, hdr, exr, dds
- Buffer data can be decoded as hex, float32, uint32, or raw bytes

View File

@@ -0,0 +1,141 @@
---
name: "cli-anything-rms"
description: >-
Teltonika RMS device management and monitoring CLI
---
# cli-anything-rms
CLI harness for Teltonika RMS (Remote Management System). Manage routers, gateways, and IoT devices via the RMS REST API.
## Installation
```bash
pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=rms/agent-harness
```
## Authentication
Set `RMS_API_TOKEN` environment variable or run `cli-anything-rms config set api_token <token>`.
## Command Groups
### devices
- `devices list [--status online|offline] [--tag TAG] [--limit N] [--offset N] [--sort FIELD]` — List devices
- `devices get <device_id>` — Get device details
- `devices update <device_id> [--name NAME] [--tag TAG]` — Update device
- `devices delete <device_id>` — Delete device
### companies
- `companies list [--limit N] [--offset N]` — List companies
- `companies get <company_id>` — Get company details
- `companies create --name NAME` — Create company
- `companies update <company_id> [--name NAME]` — Update company
- `companies delete <company_id>` — Delete company
### users
- `users list [--limit N] [--offset N]` — List users
- `users get <user_id>` — Get user details
- `users invite --email EMAIL [--role ROLE]` — Invite user
- `users update <user_id> [--role ROLE]` — Update user
- `users delete <user_id>` — Delete user
### tags
- `tags list [--limit N] [--offset N]` — List tags
- `tags get <tag_id>` — Get tag details
- `tags create --name NAME` — Create tag
- `tags update <tag_id> [--name NAME]` — Update tag
- `tags delete <tag_id>` — Delete tag
### alerts
- `alerts list [--device DEVICE_ID] [--limit N] [--offset N]` — List alerts
- `alerts get <alert_id>` — Get alert details
- `alerts delete <alert_id>` — Delete alert
- `alerts configs list` — List alert configurations
- `alerts configs get <config_id>` — Get alert config
- `alerts configs create --data JSON` — Create alert config
- `alerts configs update <config_id> --data JSON` — Update alert config
- `alerts configs delete <config_id>` — Delete alert config
### configs
- `configs list [--device DEVICE_ID] [--limit N] [--offset N]` — List device configurations
- `configs get <config_id>` — Get configuration
- `configs update <config_id> --data JSON` — Update configuration
### remote-access
- `remote-access list [--device DEVICE_ID] [--limit N]` — List sessions
- `remote-access get <session_id>` — Get session details
- `remote-access create --device DEVICE_ID [--protocol PROTO] [--port PORT]` — Create session
- `remote-access delete <session_id>` — Delete session
### logs
- `logs list [--device DEVICE_ID] [--limit N] [--offset N]` — List logs
- `logs get <log_id>` — Get log details
- `logs delete <log_id>` — Delete log
### location
- `location get <device_id>` — Get current device location
- `location history <device_id> [--limit N] [--offset N]` — Location history
### credits
- `credits list [--limit N] [--offset N]` — List credits
- `credits transfer --code CODE` — Transfer credits
- `credits codes [--limit N]` — List transfer codes
### files
- `files list [--limit N] [--offset N]` — List files
- `files get <file_id>` — Get file details
- `files upload <file_path>` — Upload file
- `files delete <file_id>` — Delete file
### reports
- `reports list [--limit N] [--offset N]` — List reports
- `reports get <report_id>` — Get report
- `reports create --template TEMPLATE_ID [--name NAME]` — Create report
- `reports delete <report_id>` — Delete report
- `reports templates list` — List report templates
### hotspots
- `hotspots list [--device DEVICE_ID] [--limit N]` — List hotspots
- `hotspots get <hotspot_id>` — Get hotspot details
- `hotspots create --device DEVICE_ID --name NAME` — Create hotspot
- `hotspots update <hotspot_id> [--name NAME]` — Update hotspot
- `hotspots delete <hotspot_id>` — Delete hotspot
### passwords
- `passwords get <device_id>` — Get device password
- `passwords update <device_id> --password PASSWORD` — Update password
- `passwords update <device_id> --password-stdin` — Update password (reads from stdin, safer)
### smtp
- `smtp list [--limit N] [--offset N]` — List SMTP configs
- `smtp get <config_id>` — Get SMTP config
- `smtp create --host HOST [--port PORT] [--username USER] [--password PASS]` — Create SMTP config
- `smtp update <config_id> [--host HOST] [--port PORT]` — Update SMTP config
- `smtp delete <config_id>` — Delete SMTP config
### auth
- `auth test` — Test API connectivity
- `auth status` — Show current auth info
### config
- `config set <key> <value>` — Set configuration (api_token, default_limit)
- `config get [key]` — Show configuration
- `config delete <key>` — Delete configuration
- `config path` — Show config file path
## Examples
```bash
# List all online devices
cli-anything-rms devices list --status online
# Get device details as JSON
cli-anything-rms --json devices get 12345
# Check alerts for a specific device
cli-anything-rms alerts list --device 12345
# Interactive mode
cli-anything-rms
```

View File

@@ -0,0 +1,394 @@
---
name: "cli-anything-safari"
description: >-
Safari browser automation CLI on macOS via safari-mcp. Controls real Safari
(native, keeps logins) by wrapping the safari-mcp MCP server. Every one of
the 84 MCP tools is exposed 1:1 with schema-accurate arguments — guaranteed
parity, no manual drift.
---
# cli-anything-safari
A command-line interface for Safari browser automation on macOS. Wraps the
[`safari-mcp`](https://github.com/achiya-automation/safari-mcp) Node.js MCP
server in a Python Click CLI.
**Feature parity is guaranteed.** Every Click command is generated
automatically from `safari-mcp`'s tool schema (bundled as
`resources/tools.json`). All 84 tools are reachable with the exact
argument names and types the MCP server expects.
## When to use this CLI
Each CLI invocation spawns a fresh subprocess, so there is per-call
overhead. If your agent speaks MCP natively (Claude Code, Cursor, Cline,
etc.), using `safari-mcp` directly over MCP stdio will be faster.
**Use this CLI when:**
- Your agent framework does **not** speak MCP (Codex CLI, GitHub Copilot
CLI, custom scripts, older agent frameworks).
- You need to **script browser automation from bash**
`cli-anything-safari --json tool snapshot | jq '...'`.
- You run in **CI/CD** and want cron-able, subprocess-friendly output.
- You're **debugging interactively** from Terminal.
## Installation
### Prerequisites
1. **macOS** — Safari MCP is macOS-only.
2. **Safari** — already installed on macOS.
3. **Node.js 18+**`brew install node` or from https://nodejs.org/
4. **Python 3.10+**
5. **Enable Apple Events for Safari**: Safari → Develop → Allow JavaScript from Apple Events
### Install the CLI
```bash
cd safari/agent-harness
pip install -e .
```
The first `tool` call will download the `safari-mcp` npm package (one-time, a few MB).
## Command Structure
The CLI has 5 top-level commands:
| Command | Purpose |
|-----------|-------------------------------------------------------------------|
| `tool` | Call any of safari-mcp's **84 tools** (dynamic, schema-driven) |
| `tools` | Inspect the bundled tool registry (`list`, `describe`, `count`) |
| `raw` | Escape hatch — call a tool by full name with raw JSON args |
| `session` | In-memory session state (last URL, current tab) |
| `repl` | Interactive REPL (default when no subcommand given) |
## Usage Examples
### Discover the tool surface
```bash
# Count of tools (sanity check — must match safari-mcp's registered tools)
cli-anything-safari tools count
# → 84
# List every tool
cli-anything-safari tools list
cli-anything-safari tools list --filter click # filter by substring
# Full schema for one tool (JSON or human format)
cli-anything-safari tools describe safari_scroll
cli-anything-safari --json tools describe safari_click
```
### Call a tool (schema-driven)
```bash
# Navigate
cli-anything-safari tool navigate --url https://example.com
# Take a snapshot (preferred over screenshot — structured text with ref IDs)
cli-anything-safari --json tool snapshot
# Click by ref (refs come from snapshot; they expire on the next snapshot!)
cli-anything-safari tool click --ref 0_5
# Click by selector or visible text
cli-anything-safari tool click --selector "#submit"
cli-anything-safari tool click --text "Log in"
# Fill a field
cli-anything-safari tool fill --selector "#email" --value "user@example.com"
# Scroll by direction/amount (NOT x/y — note the schema!)
cli-anything-safari tool scroll --direction down --amount 500
# Drag one element onto another
cli-anything-safari tool drag \
--source-selector ".card" \
--target-selector ".trash"
# Screenshot — returns base64 JPEG in stdout. Decode with:
cli-anything-safari --json tool screenshot --full-page \
| python3 -c "import sys,json,base64; \
d=json.load(sys.stdin); \
open('/tmp/shot.jpg','wb').write(base64.b64decode(d['data']))"
# Save as PDF (this one writes to disk directly)
cli-anything-safari tool save-pdf --path /tmp/page.pdf
# Evaluate JavaScript (note: parameter is --script, not --code)
cli-anything-safari tool evaluate --script "document.title"
```
### Navigate and read in one round-trip
```bash
cli-anything-safari --json tool navigate-and-read --url https://example.com
```
### Form fill (bulk)
`safari_fill_form` takes an **array** of `{selector, value}` objects.
Pass it as a JSON string:
```bash
cli-anything-safari tool fill-form --fields '[
{"selector": "#email", "value": "user@example.com"},
{"selector": "#password", "value": "hunter2"}
]'
```
Run `cli-anything-safari tools describe safari_fill_form` to see the
exact schema, including any new fields safari-mcp adds upstream.
### Network monitoring
```bash
cli-anything-safari tool start-network-capture
cli-anything-safari tool navigate --url https://example.com
cli-anything-safari --json tool network
cli-anything-safari tool performance-metrics
```
### Storage
```bash
cli-anything-safari tool get-cookies
cli-anything-safari tool set-cookie --name session --value abc123 --domain example.com
cli-anything-safari tool local-storage --key theme
# export-storage returns JSON to stdout — no --path arg. Pipe to a file:
cli-anything-safari --json tool export-storage > /tmp/storage.json
```
### Raw JSON escape hatch
When you need to pass a complex nested object or want to drive the CLI from
a pre-built JSON blob:
```bash
cli-anything-safari raw safari_evaluate \
--json-args '{"code":"[...document.querySelectorAll(\"a\")].map(a => a.href)"}'
```
### Interactive REPL
```bash
cli-anything-safari
```
The REPL banner prints the absolute path to this SKILL.md so agents can
self-discover capabilities.
## JSON Output
All commands support `--json` as a global flag:
```bash
cli-anything-safari --json tool snapshot
cli-anything-safari --json tool list-tabs
cli-anything-safari --json tools list
```
## State Management
The CLI maintains a small amount of in-memory state for REPL display only:
- **`last_url`** — last URL the CLI navigated to (updated after every
successful `tool navigate`, `tool navigate-and-read`, or
`tool new-tab`)
- **`current_tab_index`** — last known active tab index
There is **no persistent session**, no undo/redo, no document model.
Every CLI invocation starts with fresh state. Safari MCP itself is
stateless per-call: each `tool` command spawns a fresh
`npx safari-mcp` subprocess, performs the action, and exits. This is a
deliberate design choice; see `HARNESS.md` and `TEST.md` for the
reasoning behind the deviation from the standard undo/redo pattern.
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): indented key-value text for `dict`
results, bullet lists for arrays, plain text otherwise
- **Machine-readable** (`--json` flag): structured JSON for agent
consumption
```bash
# Human output
cli-anything-safari tool snapshot
# JSON output for agents
cli-anything-safari --json tool snapshot
cli-anything-safari --json tools list
cli-anything-safari --json tools describe safari_click
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output.
2. **Check return codes** — 0 for success, non-zero for errors (URL
validation failures, MCP call failures, invalid JSON args).
3. **Parse stderr** for error messages; use stdout for data.
4. **File-handling tools have inconsistent path arg names** — always
check `tools describe <name>` first:
- `tool save-pdf --path /tmp/x.pdf`
- `tool upload-file --selector ... --file-path /tmp/x.txt` (note: `--file-path`, not `--path`)
- `tool export-storage` — no path arg; pipe JSON output to a file
- `tool import-storage --path /tmp/x.json`
- `tool screenshot` / `screenshot-element` — return base64 in
the JSON response, no path arg (decode it yourself)
5. **Snapshot before click** — refs from `tool snapshot` expire on the
next snapshot. Always snapshot → find ref → click in close
succession.
6. **Discover tools via `tools list`** — the bundled registry is the
source of truth for what's available. Do not hard-code tool names
that may change upstream.
7. **Use `tools describe <name>`** to learn the exact schema (required
args, enum choices, JSON-typed args) before constructing a call.
**Never assume parameter names from the description** — for example,
`safari_evaluate` takes `--script` (not `--code`) even though the
description says "JavaScript code to execute".
## Agent-Specific Guidance
### Finding the right tool
Use the introspection commands. The CLI is **guaranteed** to reflect the
MCP server 1:1:
```bash
# Find all click-related tools
cli-anything-safari tools list --filter click
# Get the full schema (including every argument with type, description,
# required/optional, enum choices, defaults)
cli-anything-safari --json tools describe safari_click
```
### Tool selection strategy
1. **`tool snapshot`** over `tool screenshot` — structured text with ref IDs
is orders of magnitude cheaper and carries the refs needed for clicks.
2. **`tool click --ref`** over `tool click --selector` — refs are stable
within a single snapshot, selectors may be brittle.
3. **`tool navigate-and-read`** over `navigate` + `read-page` — saves one
round-trip.
4. **`tool click-and-read`** over `click` + `read-page` — saves one round-trip.
5. **`tool native-click`** only when regular click fails with 405/403 (WAF
blocks, G2, Cloudflare) — it physically moves the cursor.
### Refs Expire
Refs from `tool snapshot` expire when you take a new snapshot:
- First snapshot: refs `0_1`, `0_2`, `0_3`...
- Second snapshot: refs `1_1`, `1_2`, `1_3`...
Always snapshot → click in close succession. If in doubt, snapshot again.
### Tab Ownership Safety
Safari MCP tracks tab ownership per session. Tools that modify a tab
(navigate, click, fill) are **blocked** on tabs the session did not open.
To operate on a specific page, always start with `tool new-tab --url ...`.
### Error Handling
Common errors:
- `npx not found` → install Node.js 18+
- `safari-mcp package not found on npm registry` → check network
- `Not macOS` → harness is macOS-only
- `AppleScript denied` → enable "Allow JavaScript from Apple Events" in Safari → Develop
- `Blocked URL scheme: file` → URL validation rejected the input (by design)
### URL Validation
The CLI validates URLs before passing them to `safari_navigate`,
`safari_navigate_and_read`, and `safari_new_tab`. Blocked schemes:
`file`, `javascript`, `data`, `vbscript`, `about`, `chrome`, `safari`,
`webkit`, `x-apple`, and other browser-internal schemes. The `raw`
command **also** enforces this for navigation tools.
### Multi-Session Warning
Safari MCP enforces a single active session by killing stale Node.js
processes older than 10 seconds. If you run two CLI instances at once,
one will kill the other's backend. **There is currently no daemon
mode** — for latency-sensitive workflows, drive the CLI from a
long-lived Python script that imports
``cli_anything.safari.utils.safari_backend.call()`` directly to avoid
re-spawning the subprocess on every invocation.
## Links
- [Safari MCP GitHub](https://github.com/achiya-automation/safari-mcp)
- [Safari MCP on npm](https://www.npmjs.com/package/safari-mcp)
- [CLI-Anything](https://github.com/HKUDS/CLI-Anything)
- [MCP Backend Pattern Guide](https://github.com/HKUDS/CLI-Anything/blob/main/cli-anything-plugin/guides/mcp-backend.md)
## Security Considerations
### URL Validation
All navigation tools (`tool navigate`, `tool navigate-and-read`, `tool
new-tab`, and `raw safari_navigate*`) pass the `url` argument through
`utils/security.py` which blocks dangerous schemes and optionally blocks
private networks (set `CLI_ANYTHING_SAFARI_BLOCK_PRIVATE=1`).
### Tab Isolation
Safari MCP enforces per-session tab ownership upstream — tools cannot
operate on tabs the session did not open.
### Profile Isolation
Set `SAFARI_PROFILE` env var to use a separate Safari profile for
automation:
```bash
export SAFARI_PROFILE="Automation"
cli-anything-safari tool navigate --url https://example.com
```
This keeps cookies/logins/history separate from the user's main browsing.
### JavaScript Execution
`tool evaluate` and `tool run-script` run arbitrary JavaScript in the page
context. Treat untrusted input with the same care as any dynamic code
execution path.
### Clipboard
`tool clipboard-read` and `tool clipboard-write` touch the system
clipboard. Be careful when running inside a user's active session —
overwriting the clipboard mid-task is disruptive.
## Regenerating the tool registry
If you upgrade `safari-mcp`, regenerate the bundled schema:
```bash
python scripts/extract_tools.py \
"$(npm root -g)/safari-mcp/index.js" \
cli_anything/safari/resources/tools.json
```
The parity test (`test_parity.py`) pins the expected tool count; update
it when the upstream tool list changes.
## More Information
- **Full documentation:** `cli_anything/safari/README.md` in the package
- **Test coverage:** `cli_anything/safari/tests/TEST.md` in the package
- **Architecture analysis:** `safari/agent-harness/SAFARI.md`
- **Methodology:** `cli-anything-plugin/HARNESS.md`
- **MCP backend pattern:** `cli-anything-plugin/guides/mcp-backend.md`
## Version
1.0.0 — targets safari-mcp 2.7.8 (84 tools). Bundled tool registry is
regenerated via `scripts/extract_tools.py` when safari-mcp upgrades.

View File

@@ -0,0 +1,133 @@
---
name: "cli-anything-seaclip"
description: >-
Command-line interface for SeaClip-Lite - A stateless CLI for managing issues, pipelines, agents, schedules, and activity on the SeaClip-Lite project management board.
---
# cli-anything-seaclip
A stateless command-line interface for SeaClip-Lite project management.
Communicates via HTTP API and direct SQLite reads. No local state or session.
## Installation
```bash
pip install -e .
```
**Prerequisites:**
- Python 3.10+
- SeaClip-Lite backend running at localhost:5200
## Usage
### Basic Commands
```bash
# Show help
cli-anything-seaclip --help
# Start interactive REPL mode
cli-anything-seaclip
# Run with JSON output (for agent consumption)
cli-anything-seaclip --json server health
cli-anything-seaclip --json issue list
cli-anything-seaclip --json agent list
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-seaclip
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Issue
Issue management commands.
| Command | Description |
|---------|-------------|
| `list` | List issues (--status, --priority, --search, --limit) |
| `create` | Create a new issue (--title, --description, --priority) |
| `move` | Move issue to column (ISSUE_ID --column COL) |
| `status` | Update issue status (ISSUE_ID --set STATUS) |
| `delete` | Delete an issue (ISSUE_ID) |
### Agent
Pipeline agent commands.
| Command | Description |
|---------|-------------|
| `list` | List all pipeline agents |
### Pipeline
Pipeline control commands.
| Command | Description |
|---------|-------------|
| `start` | Start pipeline (--issue UUID --mode auto/manual) |
| `status` | Get pipeline status (--issue UUID) |
| `resume` | Resume paused pipeline (--issue UUID) |
| `stop` | Stop running pipeline (--issue UUID) |
### Scheduler
Schedule configuration commands.
| Command | Description |
|---------|-------------|
| `list` | List all schedule configs |
| `add` | Add schedule (--name, --cron, --repo) |
| `sync` | Trigger sync (SCHEDULE_ID) |
### Activity
Activity feed commands.
| Command | Description |
|---------|-------------|
| `list` | Recent activity (--limit N) |
### Server
Server utility commands.
| Command | Description |
|---------|-------------|
| `health` | Check backend health |
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-seaclip issue list
# JSON output for agents
cli-anything-seaclip --json issue list
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Error responses** include `{"error": "message"}` in JSON mode
## Version
1.0.0

View File

@@ -0,0 +1,246 @@
---
name: "cli-anything-shotcut"
description: >-
Command-line interface for Shotcut - A stateful command-line interface for video editing, built on the MLT XML format. Designed for AI ag...
---
# cli-anything-shotcut
A stateful command-line interface for video editing, built on the MLT XML format. Designed for AI agents and power users who need to create and edit Shotcut projects without a GUI.
## Installation
This CLI is installed as part of the cli-anything-shotcut package:
```bash
pip install cli-anything-shotcut
```
**Prerequisites:**
- Python 3.10+
- shotcut must be installed on your system
## Usage
### Basic Commands
```bash
# Show help
cli-anything-shotcut --help
# Start interactive REPL mode
cli-anything-shotcut
# Create a new project
cli-anything-shotcut project new -o project.json
# Run with JSON output (for agent consumption)
cli-anything-shotcut --json project info -p project.json
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-shotcut
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Project
Project management: new, open, save, info.
| Command | Description |
|---------|-------------|
| `new` | Create a new blank project |
| `open` | Open an existing .mlt project file |
| `save` | Save the current project |
| `info` | Show detailed project information |
| `profiles` | List available video profiles |
| `xml` | Print the raw MLT XML of the current project |
### Timeline
Timeline operations: tracks, clips, trimming.
| Command | Description |
|---------|-------------|
| `show` | Show the timeline overview |
| `tracks` | List all tracks |
| `add-track` | Add a new track to the timeline |
| `remove-track` | Remove a track by index |
| `add-clip` | Add a media clip to a track |
| `remove-clip` | Remove a clip from a track |
| `move-clip` | Move a clip between tracks or positions |
| `trim` | Trim a clip's in/out points |
| `split` | Split a clip into two at the given timecode |
| `clips` | List all clips on a track |
| `add-blank` | Add a blank gap to a track |
| `set-name` | Set a track's display name |
| `mute` | Mute or unmute a track |
| `hide` | Hide or unhide a video track |
### Filter Group
Filter operations: add, remove, configure effects.
| Command | Description |
|---------|-------------|
| `list-available` | List all available filters |
| `info` | Show detailed info about a filter and its parameters |
| `add` | Add a filter to a clip, track, or globally |
| `remove` | Remove a filter by index |
| `set` | Set a parameter on a filter |
| `list` | List active filters on a target |
### Media
Media operations: probe, list, check files.
| Command | Description |
|---------|-------------|
| `probe` | Analyze a media file's properties |
| `list` | List all media clips in the current project |
| `check` | Check all media files for existence |
| `thumbnail` | Generate a thumbnail from a video file |
### Export
Export/render operations.
| Command | Description |
|---------|-------------|
| `presets` | List available export presets |
| `preset-info` | Show details of an export preset |
| `render` | Render the project to a video file |
### Transition Group
Transition operations: dissolve, wipe, and other transitions.
| Command | Description |
|---------|-------------|
| `list-available` | List all available transition types |
| `info` | Show detailed info about a transition type |
| `add` | Add a transition between two tracks |
| `remove` | Remove a transition by index |
| `set` | Set a parameter on a transition |
| `list` | List all transitions on the timeline |
### Composite Group
Compositing: blend modes, PIP, opacity.
| Command | Description |
|---------|-------------|
| `blend-modes` | List all available blend modes |
| `set-blend` | Set the blend mode for a track |
| `get-blend` | Get the current blend mode for a track |
| `set-opacity` | Set the opacity of a track (0.0-1.0) |
| `pip` | Set picture-in-picture position for a clip |
### Session
Session management: status, undo, redo.
| Command | Description |
|---------|-------------|
| `status` | Show current session status |
| `undo` | Undo the last operation |
| `redo` | Redo the last undone operation |
| `save` | Save session state to disk |
| `list` | List all saved sessions |
## Examples
### Create a New Project
Create a new shotcut project file.
```bash
cli-anything-shotcut project new -o myproject.json
# Or with JSON output for programmatic use
cli-anything-shotcut --json project new -o myproject.json
```
### Interactive REPL Session
Start an interactive session with undo/redo support.
```bash
cli-anything-shotcut
# Enter commands interactively
# Use 'help' to see available commands
# Use 'undo' and 'redo' for history navigation
```
### Export Project
Export the project to a final output format.
```bash
cli-anything-shotcut --project myproject.json export render output.pdf --overwrite
```
## State Management
The CLI maintains session state with:
- **Undo/Redo**: Up to 50 levels of history
- **Project persistence**: Save/load project state as JSON
- **Session tracking**: Track modifications and changes
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-shotcut project info -p project.json
# JSON output for agents
cli-anything-shotcut --json project info -p project.json
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file operations
5. **Verify outputs exist** after export operations
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

View File

@@ -0,0 +1,190 @@
---
name: "cli-anything-slay-the-spire-ii"
description: >-
Command-line interface for Slay the Spire 2 - Control the real game through a local bridge mod HTTP API. Reads normalized game state and sends action commands for combat, navigation, rewards, and menu management.
---
# cli-anything-slay-the-spire-ii
A stateful command-line interface for controlling the real Slay the Spire 2
game through the local `STS2_Bridge` mod. The CLI reads normalized game state
and sends action commands via a local HTTP API at `localhost:15526`.
## Installation
This CLI requires a bridge mod that runs inside the game process. Both the CLI
and the bridge are distributed from the same repository:
```
https://github.com/HKUDS/CLI-Anything
```
### 1. Install the CLI
```bash
git clone https://github.com/HKUDS/CLI-Anything.git
cd CLI-Anything/slay_the_spire_ii/agent-harness
pip install -e .
```
### 2. Build and install the bridge mod
The bridge mod is a `.NET 9` plugin that must be compiled and installed into
the game directory. Full instructions are in the repository README, but the
short version is:
```bash
cd CLI-Anything/slay_the_spire_ii/agent-harness/bridge/plugin
./build.sh
cd ../install
./install_bridge.sh
```
If the build script cannot auto-detect the game data directory, set
`STS2_GAME_DATA_DIR` explicitly:
```bash
STS2_GAME_DATA_DIR="/path/to/data_sts2" ./build.sh
```
### 3. Enable the mod and verify
Launch Slay the Spire 2 via Steam, enable the `STS2_Bridge` mod in the mod
manager, then verify the connection:
```bash
cli-anything-sts2 state
```
If this returns JSON, the CLI and bridge are connected.
**Prerequisites:**
- Python 3.10+
- Slay the Spire 2 (Steam) with `STS2_Bridge` mod enabled
- `.NET 9 SDK` (only needed to build the bridge mod)
## Usage
### Basic Commands
```bash
# Read normalized game state (always start here)
cli-anything-sts2 state
# Start interactive REPL mode (default)
cli-anything-sts2
# Show all available commands
cli-anything-sts2 --help
```
## Command Groups
### State Inspection
| Command | Description |
|---------|-------------|
| `state` | Normalized state with `decision` field |
| `raw-state` | Raw bridge JSON |
### Main Menu
| Command | Description |
|---------|-------------|
| `continue-game` | Continue a saved run |
| `start-game --character IRONCLAD --ascension 0` | Start a new run |
| `abandon-game` | Abandon the current save |
| `return-to-main-menu` | Return to menu from any screen |
Characters: `IRONCLAD`, `SILENT`, `DEFECT`, `NECROBINDER`, `REGENT`
### Combat
| Command | Description |
|---------|-------------|
| `play-card <index> [--target <enemy_id>]` | Play a card from hand |
| `use-potion <slot> [--target <enemy_id>]` | Use a potion |
| `end-turn` | End the current turn |
### Map & Room Flow
| Command | Description |
|---------|-------------|
| `choose-map <index>` | Select a map node |
| `proceed` | Leave the current room |
### Rewards
| Command | Description |
|---------|-------------|
| `claim-reward <index>` | Claim a combat reward |
| `pick-card-reward <index>` | Pick a card reward |
| `skip-card-reward` | Skip the card reward |
| `claim-treasure-relic <index>` | Claim a treasure relic |
| `select-relic <index>` | Select a relic |
| `skip-relic-selection` | Skip relic selection |
### Events & Rest Sites
| Command | Description |
|---------|-------------|
| `event <index>` | Choose an event option |
| `advance-dialogue` | Advance dialogue-only events |
| `rest <index>` | Choose a campfire action |
### Shop
| Command | Description |
|---------|-------------|
| `shop-buy <index>` | Buy from the shop |
### Card/Relic Selection Overlays
| Command | Description |
|---------|-------------|
| `select-card <index>` | Select a card in overlay |
| `confirm-selection` | Confirm the current selection |
| `cancel-selection` | Cancel the current selection |
| `combat-select-card <index>` | Select a card during combat overlay |
| `combat-confirm-selection` | Confirm combat card selection |
### Raw Action
| Command | Description |
|---------|-------------|
| `action <name> --kv key=value` | Send a raw bridge action |
## Decision States
The `state` command returns JSON with a `decision` field indicating the current
game screen. Route your next command based on this value:
| Decision | Meaning | Typical Next Commands |
|----------|---------|----------------------|
| `menu` | Main menu | `continue-game`, `start-game` |
| `combat_play` | In combat, your turn | `play-card`, `use-potion`, `end-turn` |
| `hand_select` | Card selection overlay | `combat-select-card`, `combat-confirm-selection` |
| `map_select` | Map node selection | `choose-map` |
| `game_over` | Run ended | `return-to-main-menu` |
| `combat_rewards` | Post-combat rewards | `claim-reward`, `proceed` |
| `card_reward` | Card reward pick | `pick-card-reward`, `skip-card-reward` |
| `event_choice` | Event screen | `event`, `advance-dialogue` |
| `rest_site` | Campfire | `rest` |
| `shop` | Shop screen | `shop-buy`, `proceed` |
| `card_select` | Card selection screen | `select-card`, `confirm-selection` |
| `relic_select` | Relic selection | `select-relic`, `skip-relic-selection` |
| `treasure` | Treasure room | `claim-treasure-relic`, `proceed` |
## Configuration
| Option | Default | Description |
|--------|---------|-------------|
| `--base-url` | `http://localhost:15526` | Bridge API URL |
| `--timeout` | `10.0` | HTTP timeout in seconds |
## For AI Agents
1. **Always read `state` first** to get the `decision` field
2. **Re-read state after each action** - indices and energy change during combat
3. **Check return codes** - 0 for success, non-zero for errors
4. **Parse stdout** for JSON output

View File

@@ -0,0 +1,182 @@
---
name: "cli-anything-unimol-tools"
description: >-
Interactive CLI for Uni-Mol molecular property prediction training and inference workflows.
---
# Uni-Mol Tools - Molecular Property Prediction CLI
**Package**: `cli-anything-unimol-tools`
**Command**: `python3 -m cli_anything.unimol_tools`
## Description
Interactive CLI for training and inference of molecular property prediction models using Uni-Mol Tools. Supports 5 task types: binary classification, regression, multiclass, multilabel classification, and multilabel regression.
## Key Features
- **Project Management**: Organize experiments with named projects
- **5 Task Types**: Classification, regression, multiclass, multilabel variants
- **Model Tracking**: Automatic performance history and rankings
- **Smart Storage**: Analyze usage and clean up underperformers
- **JSON API**: Full automation support with `--json` flag
## Common Commands
### Project Management
```bash
# Create a new project
project create --name drug_discovery
# List all projects
project list
# Switch to a project
project switch --name drug_discovery
```
### Training
```bash
# Train a classification model
train --data-path train.csv --target-col active --task-type classification --epochs 10
# Train a regression model
train --data-path train.csv --target-col affinity --task-type regression --epochs 10
```
### Model Management
```bash
# List all trained models
models list
# Show model details and performance
models show --model-id <id>
# Rank models by performance
models rank
```
### Storage & Cleanup
```bash
# Analyze storage usage
storage analyze
# Automatic cleanup of poor performers
cleanup auto
# Manual cleanup with criteria
cleanup manual --max-models 10 --min-score 0.7
```
### Prediction
```bash
# Make predictions with a trained model
predict --model-id <id> --data-path test.csv
```
## Data Format
CSV files must contain:
- `SMILES` column: Molecular structures in SMILES format
- Target column(s): Values to predict (name specified via `--target-col`)
Example:
```csv
SMILES,target
CCO,1
CCCO,0
CC(C)O,1
```
## Task Types
1. **classification**: Binary classification (0/1)
2. **regression**: Continuous value prediction
3. **multiclass**: Multiple class classification
4. **multilabel_classification**: Multiple binary labels
5. **multilabel_regression**: Multiple continuous values
## JSON Mode
Add `--json` flag to any command for machine-readable output:
```bash
python3 -m cli_anything.unimol_tools --json models list
```
Output format:
```json
{
"status": "success",
"data": [...],
"message": "..."
}
```
## Interactive Mode
Launch without commands for interactive REPL:
```bash
python3 -m cli_anything.unimol_tools
```
Features:
- Tab completion
- Command history
- Contextual help
- Project state persistence
## Test Data
Example datasets available at:
https://github.com/545487677/CLI-Anything-unimol-tools/tree/main/unimol_tools/examples
Includes data for all 5 task types.
## Requirements
- Python 3.8+
- PyTorch 1.12+
- Uni-Mol Tools backend
- 4GB+ RAM (8GB+ recommended for training)
## Installation
```bash
cd unimol_tools/agent-harness
pip install -e .
```
## Documentation
- **SOP**: [UNIMOL_TOOLS.md](../UNIMOL_TOOLS.md)
- **Quick Start**: [docs/guides/02-QUICK-START.md](../docs/guides/02-QUICK-START.md)
- **Full Documentation**: [docs/README.md](../docs/README.md)
## Testing
```bash
cd docs/test
bash run_tests.sh --unit -v # Unit tests (67 tests)
bash run_tests.sh --full -v # Full test suite
```
## Performance Tips
- Start with 10 epochs for initial experiments
- Use smaller batch sizes if memory is limited
- Monitor storage with `storage analyze`
- Use `models rank` to identify best performers
- Clean up regularly with `cleanup auto`
## Troubleshooting
- **CUDA errors**: Reduce batch size or use CPU mode
- **CSV not recognized**: Verify SMILES column exists
- **Low accuracy**: Try more epochs or adjust learning rate
- **Storage full**: Run `cleanup auto` to free space
## Related
- **Uni-Mol Tools**: https://github.com/dptech-corp/Uni-Mol/tree/main/unimol_tools
- **Uni-Mol Paper**: https://arxiv.org/abs/2209.11126
- **CLI-Anything**: https://github.com/HKUDS/CLI-Anything

View File

@@ -0,0 +1,122 @@
---
name: "cli-anything-videocaptioner"
description: >-
AI-powered video captioning — transcribe speech, optimize/translate subtitles, burn into video with beautiful customizable styles (ASS outline or rounded background). Free ASR and translation included.
---
# cli-anything-videocaptioner
AI-powered video captioning tool. Transcribe speech → optimize subtitles → translate → burn into video with beautiful styles.
## Installation
```bash
pip install cli-anything-videocaptioner
```
**Prerequisites:**
- Python 3.10+
- `videocaptioner` must be installed (`pip install videocaptioner`)
- FFmpeg required for video synthesis
## Usage
### Basic Commands
```bash
# Show help
cli-anything-videocaptioner --help
# Start interactive REPL mode
cli-anything-videocaptioner
# Transcribe a video (free, no setup)
cli-anything-videocaptioner transcribe video.mp4 --asr bijian
# Translate subtitles (free Bing translator)
cli-anything-videocaptioner subtitle input.srt --translator bing --target-language en
# Full pipeline: transcribe → translate → burn subtitles
cli-anything-videocaptioner process video.mp4 --asr bijian --translator bing --target-language en --subtitle-mode hard
# JSON output (for agent consumption)
cli-anything-videocaptioner --json transcribe video.mp4 --asr bijian
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-videocaptioner
# Enter commands interactively with tab-completion and history
```
## Command Groups
### transcribe — Speech to subtitles
```
transcribe <input> [--asr bijian|jianying|whisper-api|whisper-cpp] [--language CODE] [--format srt|ass|txt|json] [-o PATH]
```
- `bijian` (default): Free, Chinese & English, no setup
- `whisper-api`: All languages, requires `--whisper-api-key`
### subtitle — Optimize and translate
```
subtitle <input.srt> [--translator llm|bing|google] [--target-language CODE] [--layout target-above|source-above|target-only|source-only] [--no-optimize] [--no-translate] [-o PATH]
```
- Three steps: Split → Optimize → Translate
- Bing/Google translators are free
- 38 target languages supported (BCP 47 codes)
### synthesize — Burn subtitles into video
```
synthesize <video> -s <subtitle> [--subtitle-mode soft|hard] [--quality ultra|high|medium|low] [--style NAME] [--style-override JSON] [--render-mode ass|rounded] [--font-file PATH] [-o PATH]
```
- **ASS mode**: Outline/shadow style with presets (default, anime, vertical)
- **Rounded mode**: Modern rounded background boxes
- Customizable via `--style-override '{"outline_color": "#ff0000"}'`
### process — Full pipeline
```
process <input> [--asr ...] [--translator ...] [--target-language ...] [--subtitle-mode ...] [--style ...] [--no-optimize] [--no-translate] [--no-synthesize] [-o PATH]
```
### styles — List style presets
```
styles
```
### config — Manage settings
```
config show
config set <key> <value>
```
### download — Download online video
```
download <URL> [-o DIR]
```
## JSON Output
All commands support `--json` for machine-readable output:
```bash
cli-anything-videocaptioner --json transcribe video.mp4 --asr bijian
# {"output_path": "/path/to/output.srt"}
```
## Style Presets
| Name | Mode | Description |
|------|------|-------------|
| `default` | ASS | White text, black outline — clean and universal |
| `anime` | ASS | Warm white, orange outline — anime/cartoon style |
| `vertical` | ASS | High bottom margin — for portrait/vertical videos |
| `rounded` | Rounded | Dark text on semi-transparent rounded background |
Customize any field: `--style-override '{"font_size": 48, "outline_color": "#ff0000"}'`
## Target Languages
BCP 47 codes: `zh-Hans` `zh-Hant` `en` `ja` `ko` `fr` `de` `es` `ru` `pt` `it` `ar` `th` `vi` `id` and 23 more.

View File

@@ -0,0 +1,147 @@
---
name: "cli-anything-wiremock"
description: Python CLI harness for WireMock HTTP mock server administration
version: 0.1.0
entrypoint: cli-anything-wiremock
---
## Overview
`cli-anything-wiremock` is a command-line interface that wraps the WireMock Admin REST API (`/__admin/`). It allows agents and developers to manage HTTP stub mappings, inspect served requests, control stateful scenarios, and record real backend traffic — all from the terminal or from agent tool calls.
WireMock is commonly used in integration testing environments to replace real HTTP backends with controllable mock responses.
## Command Groups
### `stub` — Manage HTTP stub mappings
| Command | Description |
|----------------------|-------------------------------------------------------|
| `stub list` | List all registered stubs |
| `stub get <id>` | Get details of a specific stub by UUID |
| `stub create <json>` | Create a stub from a JSON string |
| `stub quick M URL S` | Quickly create a stub: METHOD URL STATUS_CODE |
| `stub delete <id>` | Delete a stub by UUID |
| `stub reset` | Reset all stubs to the defaults on disk |
| `stub save` | Persist in-memory stubs to disk |
| `stub import <file>` | Import stubs from a JSON file |
### `request` — Inspect served requests
| Command | Description |
|----------------------------|--------------------------------------------------|
| `request list` | List recent served requests |
| `request find <pattern>` | Find requests matching a JSON pattern |
| `request count <pattern>` | Count requests matching a JSON pattern |
| `request unmatched` | List requests that matched no stub (404s) |
| `request reset` | Clear the request journal |
### `scenario` — Stateful scenario management
| Command | Description |
|-----------------------|---------------------------------------------------|
| `scenario list` | List all scenarios and current states |
| `scenario set N S` | Set scenario NAME to STATE |
| `scenario reset` | Reset all scenarios to their initial state |
### `record` — Record traffic from a real backend
| Command | Description |
|----------------------------|--------------------------------------------------|
| `record start <url>` | Start proxying + recording to TARGET_URL |
| `record stop` | Stop recording, return captured stubs |
| `record status` | Check if currently recording |
| `record snapshot` | Snapshot in-memory requests as stubs |
### `settings` — Global server settings
| Command | Description |
|--------------------|----------------------------------------|
| `settings get` | Get current global WireMock settings |
| `settings version` | Show WireMock server version |
### Top-level commands
| Command | Description |
|------------|--------------------------------------------------|
| `status` | Check if WireMock is running |
| `reset` | Full reset: stubs + requests + scenarios |
| `shutdown` | Gracefully shut down the WireMock server |
## Key Examples
```bash
# Check connectivity
cli-anything-wiremock status
# Create a stub using quick form
cli-anything-wiremock stub quick GET /api/users 200 --body '[{"id":1}]'
# Create a stub using full JSON
cli-anything-wiremock stub create '{
"request": {"method": "POST", "url": "/api/orders"},
"response": {"status": 201, "body": "{\"id\":99}"}
}'
# Verify a POST was made exactly once
cli-anything-wiremock --json request count '{"method":"POST","url":"/api/orders"}'
# → {"count": 1}
# Scenario: advance state
cli-anything-wiremock scenario set "cart-flow" "item-added"
# Record a real backend
cli-anything-wiremock record start https://api.example.com
# ... make requests ...
cli-anything-wiremock record stop
```
## Agent Guidance
### Always use `--json` in agent contexts
Use `--json` for all invocations in scripts or agent tool calls. JSON output varies by command type (these are distinct response types, not an envelope wrapping all responses):
```bash
# Data commands return raw WireMock API JSON directly:
cli-anything-wiremock --json stub quick GET /api/hello 200 --body '{"hello":"world"}'
# → {"id": "abc-123", "request": {...}, "response": {...}, ...}
cli-anything-wiremock --json stub list
# → {"mappings": [...], "total": N}
# Void commands (delete, reset, save) return:
# → {"status": "ok"}
# Errors return:
# → {"status": "error", "message": "Connection refused"}
```
### Connection via environment
Set connection params via environment variables before calling any command:
```bash
export WIREMOCK_HOST=localhost
export WIREMOCK_PORT=8080
```
### Workflow pattern for test verification
1. Set up stubs before running the system under test:
```bash
cli-anything-wiremock --json stub quick POST /api/payment 200 --body '{"success":true}'
```
2. Run the system under test.
3. Verify interactions:
```bash
cli-anything-wiremock --json request count '{"method":"POST","url":"/api/payment"}'
```
4. Clean up:
```bash
cli-anything-wiremock reset
```
### Error handling
Non-zero exit code on all errors. In `--json` mode, errors return `{"status": "error", "message": "..."}`. Success returns the raw WireMock API response.

View File

@@ -0,0 +1,176 @@
---
name: "cli-anything-zoom"
description: >-
Command-line interface for Zoom - CLI harness for **Zoom** — manage meetings, participants, and recordings from the command line via t...
---
# cli-anything-zoom
CLI harness for **Zoom** — manage meetings, participants, and recordings from the command line via the Zoom REST API.
## Installation
This CLI is installed as part of the cli-anything-zoom package:
```bash
pip install cli-anything-zoom
```
**Prerequisites:**
- Python 3.10+
- zoom must be installed on your system
## Usage
### Basic Commands
```bash
# Show help
cli-anything-zoom --help
# Start interactive REPL mode
cli-anything-zoom
# Create a new project
cli-anything-zoom project new -o project.json
# Run with JSON output (for agent consumption)
cli-anything-zoom --json project info -p project.json
```
### REPL Mode
When invoked without a subcommand, the CLI enters an interactive REPL session:
```bash
cli-anything-zoom
# Enter commands interactively with tab-completion and history
```
## Command Groups
### Auth
Authentication and OAuth2 setup.
| Command | Description |
|---------|-------------|
| `setup` | Configure OAuth app credentials |
| `login` | Login via OAuth2 browser flow |
| `status` | Check authentication status |
| `logout` | Remove saved tokens |
### Meeting
Meeting management commands.
| Command | Description |
|---------|-------------|
| `create` | Create a new Zoom meeting |
| `list` | List meetings |
| `info` | Get meeting details |
| `update` | Update a meeting |
| `delete` | Delete a meeting |
| `join` | Open meeting join URL in browser |
| `start` | Open meeting start URL in browser (host only) |
### Participant
Participant management commands.
| Command | Description |
|---------|-------------|
| `add` | Register a participant for a meeting |
| `add-batch` | Batch register participants from a CSV file |
| `list` | List registered participants |
| `remove` | Cancel a participant's registration |
| `attended` | List participants who attended a past meeting |
### Recording
Cloud recording management.
| Command | Description |
|---------|-------------|
| `list` | List cloud recordings |
| `files` | List recording files for a specific meeting |
| `download` | Download a recording file |
| `delete` | Delete all recordings for a meeting |
## Examples
### Create a New Project
Create a new zoom project file.
```bash
cli-anything-zoom project new -o myproject.json
# Or with JSON output for programmatic use
cli-anything-zoom --json project new -o myproject.json
```
### Interactive REPL Session
Start an interactive session with undo/redo support.
```bash
cli-anything-zoom
# Enter commands interactively
# Use 'help' to see available commands
# Use 'undo' and 'redo' for history navigation
```
## State Management
The CLI maintains session state with:
- **Undo/Redo**: Up to 50 levels of history
- **Project persistence**: Save/load project state as JSON
- **Session tracking**: Track modifications and changes
## Output Formats
All commands support dual output modes:
- **Human-readable** (default): Tables, colors, formatted text
- **Machine-readable** (`--json` flag): Structured JSON for agent consumption
```bash
# Human output
cli-anything-zoom project info -p project.json
# JSON output for agents
cli-anything-zoom --json project info -p project.json
```
## For AI Agents
When using this CLI programmatically:
1. **Always use `--json` flag** for parseable output
2. **Check return codes** - 0 for success, non-zero for errors
3. **Parse stderr** for error messages on failure
4. **Use absolute paths** for all file operations
5. **Verify outputs exist** after export operations
## More Information
- Full documentation: See README.md in the package
- Test coverage: See TEST.md in the package
- Methodology: See HARNESS.md in the cli-anything-plugin
## Version
1.0.0

Some files were not shown because too many files have changed in this diff Show More