mirror of
https://fastgit.cc/github.com/HKUDS/CLI-Anything
synced 2026-04-20 21:00:28 +08:00
WIP transfer root-skills and hub updates
This commit is contained in:
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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
72
.github/scripts/sync_root_skills.py
vendored
Normal 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
60
.github/scripts/validate_root_skills.py
vendored
Normal 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
35
.github/workflows/check-root-skills.yml
vendored
Normal 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
8
.gitignore
vendored
@@ -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/
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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">—</span> visits
|
||||
@@ -2091,7 +2012,7 @@
|
||||
<span class="analytics-sep">·</span>
|
||||
<span class="stat-dot dot-agent"></span>
|
||||
<span id="stat-agent">—</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, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
@@ -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 — 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 — install with a single command.</p>
|
||||
|
||||
<div class="empower-row">
|
||||
<div class="empower-card">
|
||||
<h3>Empower your agents — <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 & 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 & installation</p>
|
||||
|
||||
<p class="hero-cta">We welcome contributions for <em>any</em> application — 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">—</span> visits
|
||||
@@ -1406,7 +1335,7 @@
|
||||
<span class="analytics-sep">·</span>
|
||||
<span class="stat-dot dot-agent"></span>
|
||||
<span id="stat-agent">—</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 && 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 ? ' · ' + 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 && 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, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
---
|
||||
name: "cli-anything-exa"
|
||||
description: >-
|
||||
Agent-native CLI for Exa web search and content retrieval workflows.
|
||||
---
|
||||
|
||||
# Exa CLI Skill
|
||||
|
||||
## Identity
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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]):
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
31
skills/README.md
Normal 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.
|
||||
255
skills/cli-anything-adguardhome/SKILL.md
Normal file
255
skills/cli-anything-adguardhome/SKILL.md
Normal 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
|
||||
173
skills/cli-anything-anygen/SKILL.md
Normal file
173
skills/cli-anything-anygen/SKILL.md
Normal 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
|
||||
244
skills/cli-anything-audacity/SKILL.md
Normal file
244
skills/cli-anything-audacity/SKILL.md
Normal 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
|
||||
241
skills/cli-anything-blender/SKILL.md
Normal file
241
skills/cli-anything-blender/SKILL.md
Normal 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
|
||||
213
skills/cli-anything-browser/SKILL.md
Normal file
213
skills/cli-anything-browser/SKILL.md
Normal 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
|
||||
116
skills/cli-anything-chromadb/SKILL.md
Normal file
116
skills/cli-anything-chromadb/SKILL.md
Normal 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
|
||||
301
skills/cli-anything-cloudanalyzer/SKILL.md
Normal file
301
skills/cli-anything-cloudanalyzer/SKILL.md
Normal 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
|
||||
```
|
||||
789
skills/cli-anything-cloudcompare/SKILL.md
Normal file
789
skills/cli-anything-cloudcompare/SKILL.md
Normal 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` (1–10), `--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 2–3 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 |
|
||||
190
skills/cli-anything-comfyui/SKILL.md
Normal file
190
skills/cli-anything-comfyui/SKILL.md
Normal 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
|
||||
56
skills/cli-anything-dify-workflow/SKILL.md
Normal file
56
skills/cli-anything-dify-workflow/SKILL.md
Normal 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.
|
||||
213
skills/cli-anything-drawio/SKILL.md
Normal file
213
skills/cli-anything-drawio/SKILL.md
Normal 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
|
||||
73
skills/cli-anything-eth2-quickstart/SKILL.md
Normal file
73
skills/cli-anything-eth2-quickstart/SKILL.md
Normal 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.
|
||||
107
skills/cli-anything-exa/SKILL.md
Normal file
107
skills/cli-anything-exa/SKILL.md
Normal 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 1–100 (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`
|
||||
255
skills/cli-anything-freecad/SKILL.md
Normal file
255
skills/cli-anything-freecad/SKILL.md
Normal 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`
|
||||
237
skills/cli-anything-gimp/SKILL.md
Normal file
237
skills/cli-anything-gimp/SKILL.md
Normal 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
|
||||
106
skills/cli-anything-godot/SKILL.md
Normal file
106
skills/cli-anything-godot/SKILL.md
Normal 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
|
||||
```
|
||||
273
skills/cli-anything-inkscape/SKILL.md
Normal file
273
skills/cli-anything-inkscape/SKILL.md
Normal 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
|
||||
50
skills/cli-anything-intelwatch/SKILL.md
Normal file
50
skills/cli-anything-intelwatch/SKILL.md
Normal 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`
|
||||
100
skills/cli-anything-iterm2/SKILL.md
Normal file
100
skills/cli-anything-iterm2/SKILL.md
Normal 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 (~10–30 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
|
||||
```
|
||||
233
skills/cli-anything-kdenlive/SKILL.md
Normal file
233
skills/cli-anything-kdenlive/SKILL.md
Normal 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
|
||||
113
skills/cli-anything-krita/SKILL.md
Normal file
113
skills/cli-anything-krita/SKILL.md
Normal 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
|
||||
```
|
||||
226
skills/cli-anything-libreoffice/SKILL.md
Normal file
226
skills/cli-anything-libreoffice/SKILL.md
Normal 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
|
||||
182
skills/cli-anything-mermaid/SKILL.md
Normal file
182
skills/cli-anything-mermaid/SKILL.md
Normal 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
|
||||
201
skills/cli-anything-mubu/SKILL.md
Normal file
201
skills/cli-anything-mubu/SKILL.md
Normal 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
|
||||
94
skills/cli-anything-musescore/SKILL.md
Normal file
94
skills/cli-anything-musescore/SKILL.md
Normal 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`
|
||||
50
skills/cli-anything-n8n/SKILL.md
Normal file
50
skills/cli-anything-n8n/SKILL.md
Normal 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
|
||||
78
skills/cli-anything-notebooklm/SKILL.md
Normal file
78
skills/cli-anything-notebooklm/SKILL.md
Normal 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
|
||||
170
skills/cli-anything-novita/SKILL.md
Normal file
170
skills/cli-anything-novita/SKILL.md
Normal 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
|
||||
230
skills/cli-anything-obs-studio/SKILL.md
Normal file
230
skills/cli-anything-obs-studio/SKILL.md
Normal 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
|
||||
234
skills/cli-anything-obsidian/SKILL.md
Normal file
234
skills/cli-anything-obsidian/SKILL.md
Normal 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
|
||||
220
skills/cli-anything-ollama/SKILL.md
Normal file
220
skills/cli-anything-ollama/SKILL.md
Normal 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
|
||||
178
skills/cli-anything-openscreen/SKILL.md
Normal file
178
skills/cli-anything-openscreen/SKILL.md
Normal 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
|
||||
110
skills/cli-anything-pm2/SKILL.md
Normal file
110
skills/cli-anything-pm2/SKILL.md
Normal 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
|
||||
126
skills/cli-anything-renderdoc/SKILL.md
Normal file
126
skills/cli-anything-renderdoc/SKILL.md
Normal 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
|
||||
141
skills/cli-anything-rms/SKILL.md
Normal file
141
skills/cli-anything-rms/SKILL.md
Normal 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
|
||||
```
|
||||
394
skills/cli-anything-safari/SKILL.md
Normal file
394
skills/cli-anything-safari/SKILL.md
Normal 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.
|
||||
133
skills/cli-anything-seaclip/SKILL.md
Normal file
133
skills/cli-anything-seaclip/SKILL.md
Normal 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
|
||||
246
skills/cli-anything-shotcut/SKILL.md
Normal file
246
skills/cli-anything-shotcut/SKILL.md
Normal 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
|
||||
190
skills/cli-anything-slay-the-spire-ii/SKILL.md
Normal file
190
skills/cli-anything-slay-the-spire-ii/SKILL.md
Normal 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
|
||||
182
skills/cli-anything-unimol-tools/SKILL.md
Normal file
182
skills/cli-anything-unimol-tools/SKILL.md
Normal 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
|
||||
122
skills/cli-anything-videocaptioner/SKILL.md
Normal file
122
skills/cli-anything-videocaptioner/SKILL.md
Normal 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.
|
||||
147
skills/cli-anything-wiremock/SKILL.md
Normal file
147
skills/cli-anything-wiremock/SKILL.md
Normal 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.
|
||||
176
skills/cli-anything-zoom/SKILL.md
Normal file
176
skills/cli-anything-zoom/SKILL.md
Normal 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
Reference in New Issue
Block a user