upgrade to v0.2.0 with a series of updates

This commit is contained in:
yuhao
2026-03-30 09:16:10 +00:00
parent 134b58614b
commit 3c2b7fd09f
8 changed files with 492 additions and 319 deletions

View File

@@ -41,9 +41,26 @@ CLI-Anything: Bridging the Gap Between AI Agents and the World's Software</stron
> Thanks to all invaluable efforts from the community! More updates continuously on the way everyday..
- **2026-03-23** 🤖 Launched **CLI-Hub meta-skill** — agents can now discover and install CLIs autonomously via [`cli-hub-meta-skill/SKILL.md`](cli-hub-meta-skill/SKILL.md). The catalog auto-updates from `registry.json` via GitHub Actions, making the entire marketplace agent-native.
- **2026-03-30** 🏗️ **CLI-Anything v0.2.0** — HARNESS.md progressive disclosure redesign. Detailed guides (MCP backend, filter translation, timecode, session locking, PyPI publishing, SKILL.md generation) extracted into `guides/` for on-demand loading. Phases 17 now contiguous. Key Principles and Rules merged into a single authoritative section. Added Guides Reference routing table. Renamed "Critical Lessons Learned" to "Architecture Patterns & Pitfalls."
- **2026-03-22** 🎵 **MuseScore CLI** merged with transpose, export, and instrument management. Community contributions continue expanding domain coverage.
- **2026-03-29** 📐 Blender skill docs updated — enforce absolute render paths and correct prerequisites.
- **2026-03-28** 🌐 **CLIBrowser** added to CLI-Hub registry for agent-accessible browser automation.
- **2026-03-27** 📚 Zotero SKILL.md enhanced with agent-facing constraints; REPL config and executable resolution fixes.
- **2026-03-26** 📖 **Zotero CLI** harness landed for Zotero desktop (library management, collections, citations). Draw.io custom ID bugfix (#132) and registry.json syntax fix.
- **2026-03-25** 🎮 **RenderDoc CLI** merged for GPU frame capture analysis (PSO compute, REPL capture cache). FreeCAD updated for v1.1 (new datum system, tapping, simulation). Blender EEVEE engine name corrected. Zoom token permissions hardened.
- **2026-03-24** 🏭 **FreeCAD CLI** added with 258 commands across 17 groups. **iTerm2** and **Teltonika RMS** harnesses added to registry. CLI-Hub frontend and README install URLs updated.
- **2026-03-23** 🤖 Launched **CLI-Hub meta-skill** — agents can now discover and install CLIs autonomously via [`cli-hub-meta-skill/SKILL.md`](cli-hub-meta-skill/SKILL.md). **Krita CLI** harness merged for digital painting. DOMShell MCP parameter mismatches and connection model fixed.
<details>
<summary>Earlier news (Mar 1722)</summary>
- **2026-03-22** 🎵 **MuseScore CLI** merged with transpose, export, and instrument management.
- **2026-03-21** 🔧 Infrastructure improvements — refined test harnesses and documentation across multiple CLIs. Enhanced Windows compatibility for several backends.
@@ -53,22 +70,24 @@ CLI-Anything: Bridging the Gap Between AI Agents and the World's Software</stron
- **2026-03-18** 🧪 Test coverage expansion — additional E2E scenarios and edge case validation across multiple CLIs.
- **2026-03-17** 🌐 Launched the **[CLI-Hub](https://hkuds.github.io/CLI-Anything/)** — a central registry where you can browse, search, and install any CLI with a single `pip` command. Contributors can add new CLIs or update existing ones by simply opening a PR with a `registry.json` entry. The hub updates automatically on merge.
- **2026-03-17** 🌐 Launched the **[CLI-Hub](https://hkuds.github.io/CLI-Anything/)** — a central registry where you can browse, search, and install any CLI with a single `pip` command.
</details>
<details>
<summary>Earlier news</summary>
<summary>Earlier news (Mar 1116)</summary>
- **2026-03-16** 🤖 Added **SKILL.md generation** (Phase 6.5) — every generated CLI now ships with an AI-discoverable skill definition inside the Python package. ReplSkin auto-detects the skill file after `pip install`, and the REPL banner displays the absolute path for agents. Includes `skill_generator.py`, Jinja2 template, `package_data` in all setup.py files, and 51 new tests.
- **2026-03-16** 🤖 Added **SKILL.md generation** (Phase 6.5) — every generated CLI now ships with an AI-discoverable skill definition. Includes `skill_generator.py`, Jinja2 template, and 51 new tests.
- **2026-03-15** 🐾 Support for **OpenClaw** from the community! Merged Windows `cygpath` guard to ensure CLI-Anything works reliably in Windows bash environments. Community contributions continue to strengthen cross-platform support.
- **2026-03-15** 🐾 Support for **OpenClaw** from the community! Merged Windows `cygpath` guard for cross-platform support.
- **2026-03-14** 🔒 Fixed a GIMP Script-Fu path injection vulnerability and added **Japanese README** translation. OpenCode version requirements documented alongside several Windows compatibility improvements.
- **2026-03-14** 🔒 Fixed a GIMP Script-Fu path injection vulnerability and added **Japanese README** translation.
- **2026-03-13** 🔌 **Qodercli** plugin officially merged as a community contribution with dedicated setup scripts. Codex skill gained a Windows install script, and placeholder URLs were cleaned up across the project.
- **2026-03-13** 🔌 **Qodercli** plugin officially merged as a community contribution with dedicated setup scripts.
- **2026-03-12** 📦 **Codex skill** integration landed, bringing CLI-Anything to yet another AI coding platform. Qodercli support was also introduced, and documentation was updated with limitations and experimental labels.
- **2026-03-12** 📦 **Codex skill** integration landed, bringing CLI-Anything to yet another AI coding platform.
- **2026-03-11** 📞 **Zoom** video conferencing harness added as the 11th supported application. Multiple community fixes shipped for Shotcut auto-save, LibreOffice Windows/macOS backend, and OpenCode support.
- **2026-03-11** 📞 **Zoom** video conferencing harness added as the 11th supported application.
</details>

View File

@@ -68,35 +68,9 @@ designed for humans, without needing a display or mouse.
Generate valid intermediate files, then invoke the real software for conversion.
6. **Add session management** — State persistence, undo/redo
**Session file locking** — When saving session JSON, use exclusive file locking
to prevent concurrent writes from corrupting data. Never use bare
`open("w") + json.dump()` — `open("w")` truncates the file before any lock
can be acquired. Instead, open with `"r+"`, lock, then truncate inside the lock:
```python
def _locked_save_json(path, data, **dump_kwargs) -> None:
"""Atomically write JSON with exclusive file locking."""
try:
f = open(path, "r+") # no truncation on open
except FileNotFoundError:
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
f = open(path, "w") # first save — file doesn't exist yet
with f:
_locked = False
try:
import fcntl
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
_locked = True
except (ImportError, OSError):
pass # Windows / unsupported FS — proceed unlocked
try:
f.seek(0)
f.truncate() # truncate INSIDE the lock
json.dump(data, f, **dump_kwargs)
f.flush()
finally:
if _locked:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
```
**Session file locking** — Use exclusive file locking for session JSON saves
to prevent concurrent write corruption. See [`guides/session-locking.md`](guides/session-locking.md)
for the `_locked_save_json` pattern (open `"r+"`, lock, then truncate inside the lock).
7. **Add the REPL with unified skin** — Interactive mode wrapping the subcommands.
- Copy `repl_skin.py` from the plugin (`cli-anything-plugin/repl_skin.py`) into
`utils/repl_skin.py` in your CLI package
@@ -275,60 +249,16 @@ definition that can be loaded by Claude Code or other AI assistants.
---
```
2. **Markdown Body** — Usage instructions including:
- Installation prerequisites
- Basic command syntax
- Command groups and their functions
- Usage examples
- Agent-specific guidance (JSON output, error handling)
2. **Markdown Body** — Installation prerequisites, command syntax, command groups,
usage examples, and agent-specific guidance (JSON output, error handling).
**Generation Process:**
**Generation & Customization:** Use `skill_generator.py` to extract CLI metadata
automatically, or customize via the Jinja2 template at `templates/SKILL.md.template`.
See [`guides/skill-generation.md`](guides/skill-generation.md) for the full generation
process, template customization options, and manual generation commands.
1. **Extract CLI metadata** using `skill_generator.py`:
```python
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
```
2. **The generator automatically extracts:**
- Software name and version from setup.py
- Command groups from the CLI file (Click decorators)
- Documentation from README.md
- System package requirements
3. **Customize the template** (optional):
- Default template: `templates/SKILL.md.template`
- Uses Jinja2 placeholders for dynamic content
- Can be extended for software-specific sections
**Output Location:**
SKILL.md is generated inside the Python package so it is installed with `pip install`:
```
<software>/
└── agent-harness/
└── cli_anything/
└── <software>/
└── skills/
└── SKILL.md
```
**Manual Generation:**
```bash
cd cli-anything-plugin
python skill_generator.py /path/to/software/agent-harness
```
**Integration with CLI Build:**
The SKILL.md generation should be run after Phase 6 (Test Documentation) completes
successfully, ensuring the CLI is fully documented and tested before creating the
skill definition.
**Output Location:** SKILL.md lives inside the Python package at
`cli_anything/<software>/skills/SKILL.md` so it is installed with `pip install`.
**Key Principles:**
@@ -340,20 +270,11 @@ 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 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.
```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
```
**Package Data:**
Ensure `setup.py` includes the skill file as package data so it is installed with pip:
**Package Data:** Ensure `setup.py` includes the skill file so it ships with pip:
```python
package_data={
@@ -361,7 +282,18 @@ package_data={
},
```
## Critical Lessons Learned
### Phase 7: PyPI Publishing and Installation
After building and testing the CLI, make it installable and discoverable using
**PEP 420 namespace packages** under the shared `cli_anything` namespace.
See [`guides/pypi-publishing.md`](guides/pypi-publishing.md) for the full setup.py template,
namespace package structure, import conventions, and verification steps.
**Key rule:** `cli_anything/` has **no** `__init__.py` (namespace package). Each
sub-package (`gimp/`, `blender/`, etc.) **does** have `__init__.py`.
## Architecture Patterns & Pitfalls
### Use the Real Software — Don't Reimplement It
@@ -426,89 +358,23 @@ the input. Users can't tell anything happened.
### MCP Backend Pattern
For services that expose an MCP (Model Context Protocol) server:
For software that exposes an MCP (Model Context Protocol) server instead of a traditional
CLI (e.g., DOMShell for browser automation). See [`guides/mcp-backend.md`](guides/mcp-backend.md)
for the full backend wrapper pattern, session management, daemon mode, and example implementations.
**Use case:** When the software provides an MCP server instead of a traditional CLI.
Example: DOMShell provides browser automation via MCP tools.
**When to use:**
- The software has an official or community MCP server
- No native CLI exists, or MCP provides better functionality
- You want to integrate AI/agent tools that speak MCP protocol
**Backend wrapper** (`utils/<service>_backend.py`):
```python
import asyncio
from typing import Any
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def _call_tool(tool_name: str, arguments: dict) -> Any:
"""Call an MCP tool."""
server_params = StdioServerParameters(
command="npx",
args=["@apireno/domshell"]
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool(tool_name, arguments)
return result
def is_available() -> bool:
"""Check if MCP server is available."""
# Try to spawn and verify
...
# Sync wrappers for each tool
def ls(path: str = "/") -> dict:
"""List directory contents."""
return asyncio.run(_call_tool("domshell_ls", {"path": path}))
```
**Session management:**
- MCP server spawns per command (stateless from server perspective)
- CLI maintains state (URL, working directory, navigation history)
- Each command re-spawns the MCP server process
**Daemon mode (optional):**
- Spawn MCP server once, reuse connection for multiple commands
- Reduces latency for interactive use
- Requires explicit start/stop or `--daemon` flag
**Dependencies:** Add `mcp>=0.1.0` to install_requires
**Example implementations:**
- `browser/agent-harness` — DOMShell MCP server for browser automation
- See: https://github.com/HKUDS/CLI-Anything/tree/main/browser/agent-harness
**Use when:** no native CLI exists, software has an MCP server, or you need agent-native tool integration.
### Filter Translation Pitfalls
When translating effects between formats (e.g., MLT → ffmpeg), watch for:
- **Duplicate filter types:** Some tools (ffmpeg) don't allow the same filter twice
in a chain. If your project has both `brightness` and `saturation` filters, and
both map to ffmpeg's `eq=`, you must **merge** them into a single `eq=brightness=X:saturation=Y`.
- **Ordering constraints:** ffmpeg's `concat` filter requires **interleaved** stream
ordering: `[v0][a0][v1][a1][v2][a2]`, NOT grouped `[v0][v1][v2][a0][a1][a2]`.
The error message ("media type mismatch") is cryptic if you don't know this.
- **Parameter space differences:** Effect parameters often use different scales.
MLT brightness `1.15` = +15%, but ffmpeg `eq=brightness=0.06` on a -1..1 scale.
Document every mapping explicitly.
- **Unmappable effects:** Some effects have no equivalent in the render tool. Handle
gracefully (warn, skip) rather than crash.
When translating effects between formats (e.g., MLT → ffmpeg), watch for duplicate filter
merging, interleaved stream ordering, parameter scale differences, and unmappable effects.
See [`guides/filter-translation.md`](guides/filter-translation.md) for detailed rules and examples.
### Timecode Precision
Non-integer frame rates (29.97fps = 30000/1001) cause cumulative rounding errors:
- **Use `round()`, not `int()`** for float-to-frame conversion. `int(9000 * 29.97)`
truncates and loses frames; `round()` gets the right answer.
- **Use integer arithmetic for timecode display.** Convert frames → total milliseconds
via `round(frames * fps_den * 1000 / fps_num)`, then decompose with integer
division. Avoid intermediate floats that drift over long durations.
- **Accept ±1 frame tolerance** in roundtrip tests at non-integer FPS. Exact equality
is mathematically impossible.
Non-integer frame rates (29.97fps) cause cumulative rounding errors. Key rules: use
`round()` not `int()`, use integer arithmetic for display, accept ±1 frame tolerance.
See [`guides/timecode-precision.md`](guides/timecode-precision.md) for the full approach.
### Output Verification Methodology
@@ -602,53 +468,52 @@ Real-world workflow test scenarios should include:
- Save/load round-trips of complex projects
- Iterative refinement (add, modify, remove, re-add)
## Key Principles
## Principles & Rules
- **Use the real software** — The CLI MUST invoke the actual application for rendering
and export. Generate valid intermediate files (ODF, MLT XML, .blend, SVG), then hand
them to the real software. Never reimplement the rendering engine in Python.
- **The software is a hard dependency** — Not optional, not gracefully degraded. If
LibreOffice isn't installed, `cli-anything-libreoffice` must error clearly, not
silently produce inferior output with a fallback library.
These are non-negotiable. Every harness MUST follow all of them.
**Backend & Rendering:**
- **The real software is a hard dependency.** The CLI MUST invoke the actual application
(LibreOffice, Blender, GIMP, etc.) for rendering and export. Do NOT reimplement
rendering in Python. Do NOT gracefully degrade to a fallback library. If the software
is not installed, error with clear install instructions.
- **Manipulate the native format directly** — Parse and modify the app's native project
files (MLT XML, ODF, SVG, etc.) as the data layer.
- **Leverage existing CLI tools** — Use `libreoffice --headless`, `blender --background`,
`melt`, `ffmpeg`, `inkscape --actions`, `sox` as subprocesses for rendering.
- **Verify rendering produces correct output** — See "The Rendering Gap" above.
- **E2E tests must produce real artifacts** — PDF, DOCX, rendered images, videos.
Print output paths so users can inspect. Never test only the intermediate format.
- **Verify rendering produces correct output** — See "The Rendering Gap" in
Architecture Patterns & Pitfalls above.
- **Every filter/effect in the registry MUST have a corresponding render mapping**
or be explicitly documented as "project-only (not rendered)".
**CLI Design:**
- **Fail loudly and clearly** — Agents need unambiguous error messages to self-correct.
- **Be idempotent where possible** — Running the same command twice should be safe.
- **Provide introspection** — `info`, `list`, `status` commands are critical for agents
to understand current state before acting.
- **JSON output mode** — Every command should support `--json` for machine parsing.
- **JSON output mode** — Every command MUST support `--json` for machine parsing.
- **Use the unified REPL skin** — Copy `cli-anything-plugin/repl_skin.py` to
`utils/repl_skin.py` and use `ReplSkin` for banner, prompt, help, and messages.
REPL MUST be the default behavior (`invoke_without_command=True`).
## Rules
- **The real software MUST be a hard dependency.** The CLI must invoke the actual
software (LibreOffice, Blender, GIMP, etc.) for rendering and export. Do NOT
reimplement rendering in Python. Do NOT gracefully degrade to a fallback library.
If the software is not installed, the CLI must error with clear install instructions.
- **Every `cli_anything/<software>/` directory MUST contain a `README.md`** that explains how to
install the software dependency, install the CLI, run tests, and shows basic usage.
**Testing:**
- **E2E tests MUST invoke the real software** and produce real output files (PDF, DOCX,
rendered images, videos). Tests must verify output exists, has correct format, and
print artifact paths so users can inspect results. Never test only intermediate files.
- **Every export/render function MUST be verified** with programmatic output analysis
before being marked as working. "It ran without errors" is not sufficient.
- **Every filter/effect in the registry MUST have a corresponding render mapping**
or be explicitly documented as "project-only (not rendered)".
- **Test suites MUST include real-file E2E tests**, not just unit tests with synthetic
data. Format assumptions break constantly with real media.
rendered images, videos). Verify output exists, has correct format (magic bytes, ZIP
structure), and print artifact paths for manual inspection. Never test only
intermediate files.
- **Every export/render function MUST be verified** with programmatic output analysis.
"It ran without errors" is not sufficient.
- **E2E tests MUST include subprocess tests** that invoke the installed
`cli-anything-<software>` command via `_resolve_cli()`. Tests must work against
the actual installed package, not just source imports.
- **Every `cli_anything/<software>/tests/` directory MUST contain a `TEST.md`** documenting what the tests
cover, what realistic workflows are tested, and the full test results output.
- **Every CLI MUST use the unified REPL skin** (`repl_skin.py`) for the interactive mode.
Copy `cli-anything-plugin/repl_skin.py` to `utils/repl_skin.py` and use `ReplSkin`
for the banner, prompt, help, messages, and goodbye. REPL MUST be the default behavior
when the CLI is invoked without a subcommand (`invoke_without_command=True`).
- **Test suites MUST include real-file E2E tests**, not just unit tests with synthetic
data. Format assumptions break constantly with real media.
**Documentation:**
- **Every `cli_anything/<software>/` directory MUST contain a `README.md`** explaining
how to install the software dependency, install the CLI, run tests, and basic usage.
- **Every `cli_anything/<software>/tests/` directory MUST contain a `TEST.md`**
documenting test coverage, realistic workflows tested, and full test results output.
## Directory Structure
@@ -712,110 +577,16 @@ command-line interface TO the software, not a replacement for it.
The pattern is always the same: **build the data → call the real software → verify
the output**.
### Phase 7: PyPI Publishing and Installation
## Guides Reference
After building and testing the CLI, make it installable and discoverable.
Detailed guides live in `guides/`. Use this table to decide which ones to read
based on the software you're building a harness for.
All cli-anything CLIs use **PEP 420 namespace packages** under the shared
`cli_anything` namespace. This allows multiple CLI packages to be installed
side-by-side in the same Python environment without conflicts.
1. **Structure the package** as a namespace package:
```
agent-harness/
├── setup.py
└── cli_anything/ # NO __init__.py here (namespace package)
└── <software>/ # e.g., gimp, blender, audacity
├── __init__.py # HAS __init__.py (regular sub-package)
├── <software>_cli.py
├── core/
├── utils/
└── tests/
```
The key rule: `cli_anything/` has **no** `__init__.py`. Each sub-package
(`gimp/`, `blender/`, etc.) **does** have `__init__.py`. This is what
enables multiple packages to contribute to the same namespace.
2. **Create setup.py** in the `agent-harness/` directory:
```python
from setuptools import setup, find_namespace_packages
setup(
name="cli-anything-<software>",
version="1.0.0",
packages=find_namespace_packages(include=["cli_anything.*"]),
install_requires=[
"click>=8.0.0",
"prompt-toolkit>=3.0.0",
# Add Python library dependencies here
],
entry_points={
"console_scripts": [
"cli-anything-<software>=cli_anything.<software>.<software>_cli:main",
],
},
python_requires=">=3.10",
)
```
**Important details:**
- Use `find_namespace_packages`, NOT `find_packages`
- Use `include=["cli_anything.*"]` to scope discovery
- Entry point format: `cli_anything.<software>.<software>_cli:main`
- The **system package** (LibreOffice, Blender, etc.) is a **hard dependency**
that cannot be expressed in `install_requires`. Document it in README.md and
have the backend module raise a clear error with install instructions:
```python
# In utils/<software>_backend.py
def find_<software>():
path = shutil.which("<software>")
if path:
return path
raise RuntimeError(
"<Software> is not installed. Install it with:\n"
" apt install <software> # Debian/Ubuntu\n"
" brew install <software> # macOS"
)
```
3. **All imports** use the `cli_anything.<software>` prefix:
```python
from cli_anything.gimp.core.project import create_project
from cli_anything.gimp.core.session import Session
from cli_anything.blender.core.scene import create_scene
```
4. **Test local installation**:
```bash
cd /root/cli-anything/<software>/agent-harness
pip install -e .
```
5. **Verify PATH installation**:
```bash
which cli-anything-<software>
cli-anything-<software> --help
```
6. **Run tests against the installed command**:
```bash
cd /root/cli-anything/<software>/agent-harness
CLI_ANYTHING_FORCE_INSTALLED=1 python3 -m pytest cli_anything/<software>/tests/ -v -s
```
The output must show `[_resolve_cli] Using installed command: /path/to/cli-anything-<software>`
confirming subprocess tests ran against the real installed binary, not a module fallback.
7. **Verify namespace works across packages** (when multiple CLIs installed):
```python
import cli_anything.gimp
import cli_anything.blender
# Both resolve to their respective source directories
```
**Why namespace packages:**
- Multiple CLIs coexist in the same Python environment without conflicts
- Clean, organized imports under a single `cli_anything` namespace
- Each CLI is independently installable/uninstallable via pip
- Agents can discover all installed CLIs via `cli_anything.*`
- Standard Python packaging — no hacks or workarounds
| Guide | Read when... | Phase |
|-------|-------------|-------|
| [`session-locking.md`](guides/session-locking.md) | Implementing session save (all harnesses) | Phase 3 |
| [`skill-generation.md`](guides/skill-generation.md) | Generating the SKILL.md file | Phase 6.5 |
| [`pypi-publishing.md`](guides/pypi-publishing.md) | Packaging and installing the CLI | Phase 7 |
| [`mcp-backend.md`](guides/mcp-backend.md) | Software has an MCP server, no native CLI | Phase 3 |
| [`filter-translation.md`](guides/filter-translation.md) | Video/audio CLI with effects that need render-time translation | Phase 3 |
| [`timecode-precision.md`](guides/timecode-precision.md) | Video/audio CLI with non-integer frame rates (29.97fps, etc.) | Phase 3, 5 |

View File

@@ -0,0 +1,26 @@
# Filter Translation Pitfalls
When translating effects between formats (e.g., MLT → ffmpeg), watch for these common issues.
## Duplicate Filter Types
Some tools (ffmpeg) don't allow the same filter twice in a chain. If your project has both `brightness` and `saturation` filters, and both map to ffmpeg's `eq=`, you must **merge** them into a single `eq=brightness=X:saturation=Y`.
## Ordering Constraints
ffmpeg's `concat` filter requires **interleaved** stream ordering:
`[v0][a0][v1][a1][v2][a2]`, NOT grouped `[v0][v1][v2][a0][a1][a2]`.
The error message ("media type mismatch") is cryptic if you don't know this.
## Parameter Space Differences
Effect parameters often use different scales:
- MLT brightness `1.15` = +15%
- ffmpeg `eq=brightness=0.06` on a -1..1 scale
Document every mapping explicitly.
## Unmappable Effects
Some effects have no equivalent in the render tool. Handle gracefully (warn, skip) rather than crash.

View File

@@ -0,0 +1,64 @@
# MCP Backend Pattern
For services that expose an MCP (Model Context Protocol) server instead of a traditional CLI.
## When to Use
- The software has an official or community MCP server
- No native CLI exists, or MCP provides better functionality
- You want to integrate AI/agent tools that speak MCP protocol
**Use case:** When the software provides an MCP server instead of a traditional CLI.
Example: DOMShell provides browser automation via MCP tools.
## Backend Wrapper (`utils/<service>_backend.py`)
```python
import asyncio
from typing import Any
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def _call_tool(tool_name: str, arguments: dict) -> Any:
"""Call an MCP tool."""
server_params = StdioServerParameters(
command="npx",
args=["@apireno/domshell"]
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool(tool_name, arguments)
return result
def is_available() -> bool:
"""Check if MCP server is available."""
# Try to spawn and verify
...
# Sync wrappers for each tool
def ls(path: str = "/") -> dict:
"""List directory contents."""
return asyncio.run(_call_tool("domshell_ls", {"path": path}))
```
## Session Management
- MCP server spawns per command (stateless from server perspective)
- CLI maintains state (URL, working directory, navigation history)
- Each command re-spawns the MCP server process
## Daemon Mode (Optional)
- Spawn MCP server once, reuse connection for multiple commands
- Reduces latency for interactive use
- Requires explicit start/stop or `--daemon` flag
## Dependencies
Add `mcp>=0.1.0` to `install_requires`.
## Example Implementations
- `browser/agent-harness` — DOMShell MCP server for browser automation
- See: https://github.com/HKUDS/CLI-Anything/tree/main/browser/agent-harness

View File

@@ -0,0 +1,117 @@
# PyPI Publishing and Installation (Phase 7)
After building and testing the CLI, make it installable and discoverable.
All cli-anything CLIs use **PEP 420 namespace packages** under the shared
`cli_anything` namespace. This allows multiple CLI packages to be installed
side-by-side in the same Python environment without conflicts.
## 1. Package Structure
```
agent-harness/
├── setup.py
└── cli_anything/ # NO __init__.py here (namespace package)
└── <software>/ # e.g., gimp, blender, audacity
├── __init__.py # HAS __init__.py (regular sub-package)
├── <software>_cli.py
├── core/
├── utils/
└── tests/
```
The key rule: `cli_anything/` has **no** `__init__.py`. Each sub-package
(`gimp/`, `blender/`, etc.) **does** have `__init__.py`. This is what
enables multiple packages to contribute to the same namespace.
## 2. setup.py Template
Create `setup.py` in the `agent-harness/` directory:
```python
from setuptools import setup, find_namespace_packages
setup(
name="cli-anything-<software>",
version="1.0.0",
packages=find_namespace_packages(include=["cli_anything.*"]),
install_requires=[
"click>=8.0.0",
"prompt-toolkit>=3.0.0",
# Add Python library dependencies here
],
entry_points={
"console_scripts": [
"cli-anything-<software>=cli_anything.<software>.<software>_cli:main",
],
},
python_requires=">=3.10",
)
```
**Important details:**
- Use `find_namespace_packages`, NOT `find_packages`
- Use `include=["cli_anything.*"]` to scope discovery
- Entry point format: `cli_anything.<software>.<software>_cli:main`
- The **system package** (LibreOffice, Blender, etc.) is a **hard dependency**
that cannot be expressed in `install_requires`. Document it in README.md and
have the backend module raise a clear error with install instructions:
```python
# In utils/<software>_backend.py
def find_<software>():
path = shutil.which("<software>")
if path:
return path
raise RuntimeError(
"<Software> is not installed. Install it with:\n"
" apt install <software> # Debian/Ubuntu\n"
" brew install <software> # macOS"
)
```
## 3. Import Convention
All imports use the `cli_anything.<software>` prefix:
```python
from cli_anything.gimp.core.project import create_project
from cli_anything.gimp.core.session import Session
from cli_anything.blender.core.scene import create_scene
```
## 4. Verification Steps
**Test local installation:**
```bash
cd /root/cli-anything/<software>/agent-harness
pip install -e .
```
**Verify PATH installation:**
```bash
which cli-anything-<software>
cli-anything-<software> --help
```
**Run tests against the installed command:**
```bash
cd /root/cli-anything/<software>/agent-harness
CLI_ANYTHING_FORCE_INSTALLED=1 python3 -m pytest cli_anything/<software>/tests/ -v -s
```
The output must show `[_resolve_cli] Using installed command: /path/to/cli-anything-<software>`
confirming subprocess tests ran against the real installed binary, not a module fallback.
**Verify namespace works across packages** (when multiple CLIs installed):
```python
import cli_anything.gimp
import cli_anything.blender
# Both resolve to their respective source directories
```
## Why Namespace Packages
- Multiple CLIs coexist in the same Python environment without conflicts
- Clean, organized imports under a single `cli_anything` namespace
- Each CLI is independently installable/uninstallable via pip
- Agents can discover all installed CLIs via `cli_anything.*`
- Standard Python packaging — no hacks or workarounds

View File

@@ -0,0 +1,39 @@
# Session File Locking
When saving session JSON, use exclusive file locking to prevent concurrent writes from corrupting data.
## The Problem
Never use bare `open("w") + json.dump()``open("w")` truncates the file before any lock can be acquired.
## The Solution: `_locked_save_json`
Open with `"r+"`, lock, then truncate inside the lock:
```python
def _locked_save_json(path, data, **dump_kwargs) -> None:
"""Atomically write JSON with exclusive file locking."""
try:
f = open(path, "r+") # no truncation on open
except FileNotFoundError:
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
f = open(path, "w") # first save — file doesn't exist yet
with f:
_locked = False
try:
import fcntl
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
_locked = True
except (ImportError, OSError):
pass # Windows / unsupported FS — proceed unlocked
try:
f.seek(0)
f.truncate() # truncate INSIDE the lock
json.dump(data, f, **dump_kwargs)
f.flush()
finally:
if _locked:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
```
Copy this pattern into `core/session.py` for all session saves.

View File

@@ -0,0 +1,115 @@
# SKILL.md Generation (Phase 6.5)
Generate a SKILL.md file that makes the CLI discoverable and usable by AI agents
through the skill-creator methodology. This file serves as a self-contained skill
definition that can be loaded by Claude Code or other AI assistants.
## Purpose
SKILL.md files follow a standard format that enables AI agents to:
- Discover the CLI's capabilities
- Understand command structure and usage
- Generate correct command invocations
- Handle output programmatically
## SKILL.md Structure
### 1. YAML Frontmatter — Triggering metadata for skill discovery:
```yaml
---
name: "cli-anything-<software>"
description: "Brief description of what the CLI does"
---
```
### 2. Markdown Body — Usage instructions including:
- Installation prerequisites
- Basic command syntax
- Command groups and their functions
- Usage examples
- Agent-specific guidance (JSON output, error handling)
## Generation Process
### 1. Extract CLI metadata using `skill_generator.py`:
```python
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
```
### 2. The generator automatically extracts:
- Software name and version from setup.py
- Command groups from the CLI file (Click decorators)
- Documentation from README.md
- System package requirements
### 3. Customize the template (optional):
- Default template: `templates/SKILL.md.template`
- Uses Jinja2 placeholders for dynamic content
- Can be extended for software-specific sections
## Output Location
SKILL.md is generated inside the Python package so it is installed with `pip install`:
```
<software>/
└── agent-harness/
└── cli_anything/
└── <software>/
└── skills/
└── SKILL.md
```
## Manual Generation
```bash
cd cli-anything-plugin
python skill_generator.py /path/to/software/agent-harness
```
## Integration with CLI Build
The SKILL.md generation should be run after Phase 6 (Test Documentation) completes
successfully, ensuring the CLI is fully documented and tested before creating the
skill definition.
## Key Principles
- SKILL.md must be self-contained (no external dependencies for understanding)
- Include agent-specific guidance for programmatic usage
- Document `--json` flag usage for machine-readable output
- List all command groups with brief descriptions
- Provide realistic examples that demonstrate common workflows
## 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:
```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
```
## Package Data
Ensure `setup.py` includes the skill file as package data so it is installed with pip:
```python
package_data={
"cli_anything.<software>": ["skills/*.md"],
},
```

View File

@@ -0,0 +1,22 @@
# Timecode Precision
Non-integer frame rates (29.97fps = 30000/1001) cause cumulative rounding errors. Follow these rules to avoid drift.
## Use `round()`, Not `int()`
For float-to-frame conversion:
- `int(9000 * 29.97)`**wrong**, truncates and loses frames
- `round(9000 * 29.97)`**correct**, gets the right answer
## Use Integer Arithmetic for Timecode Display
Convert frames → total milliseconds via:
```python
total_ms = round(frames * fps_den * 1000 / fps_num)
```
Then decompose with integer division. Avoid intermediate floats that drift over long durations.
## Accept ±1 Frame Tolerance
In roundtrip tests at non-integer FPS, exact equality is mathematically impossible. Accept ±1 frame tolerance.