diff --git a/.agents/skills/cli-anything/SKILL.md b/.agents/skills/cli-anything/SKILL.md new file mode 100644 index 0000000..5712d50 --- /dev/null +++ b/.agents/skills/cli-anything/SKILL.md @@ -0,0 +1,85 @@ +# cli-anything Skill + +Build powerful, stateful CLI interfaces for any GUI application using the cli-anything harness methodology. + +## Overview + +This skill enables AI agents to transform any software application into an agent-controllable CLI. It follows the proven cli-anything methodology that has successfully generated CLIs for GIMP, Blender, Inkscape, Audacity, LibreOffice, OBS Studio, and Kdenlive. + +## Usage + +```bash +# Build CLI for local source +cli-anything /path/to/software + +# Build CLI from GitHub +cli-anything https://github.com/org/repo +``` + +## Prerequisites + +- Python 3.10+ +- click >= 8.0 +- pytest + +Install dependencies: +```bash +pip install click pytest pyyaml +``` + +## Methodology + +This skill follows the 7-phase cli-anything methodology: + +### Phase 0: Source Acquisition +- Clone GitHub repos or validate local paths +- Verify source code structure + +### Phase 1: Codebase Analysis +- Analyze application architecture +- Map GUI actions to API calls +- Identify existing CLI tools + +### Phase 2: CLI Architecture Design +- Design command groups matching app domains +- Plan state model and output formats +- Create software-specific SOP documents + +### Phase 3: Implementation +- Create directory structure: `agent-harness/cli_anything//` +- Implement core modules with Click +- Add REPL support with JSON output mode + +### Phase 4: Test Planning +- Design unit and E2E test coverage +- Plan validation scenarios + +### Phase 5: Test Implementation +- Write comprehensive tests +- Ensure 100% pass rate + +### Phase 6: Documentation & Publishing +- Document commands and usage +- Prepare for PyPI publishing + +## Key Files + +| File | Purpose | +|------|---------| +| `HARNESS.md` | Complete methodology specification | +| `commands/cli-anything.md` | Main build command | +| `commands/validate.md` | Validation command | +| `commands/test.md` | Testing command | +| `commands/refine.md` | Refinement command | + +## Output + +- Stateful CLI with REPL mode +- JSON output for agent consumption +- Undo/redo support +- Full test coverage + +## Related + +- Plugin workflow: See `../cli-anything-plugin/` for Claude Code plugin +- Examples: See `../blender/`, `../gimp/`, `../audacity/` for implemented CLIs diff --git a/.gitignore b/.gitignore index a26c609..517b1e3 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,12 @@ # Step 3: Allow cli-anything-plugin entirely !/cli-anything-plugin/ +# Step 3.5: Allow .agents for repo-local skills +!/.agents/ + +# Step 3.6: Allow tests directory +!/tests/ + # Step 4: Allow each software dir (top level only) !/gimp/ !/blender/ diff --git a/README.md b/README.md index 0e35d01..7dd40db 100644 --- a/README.md +++ b/README.md @@ -514,6 +514,19 @@ The playbook distills key insights from successfully building all 8 diverse, pro /cli-anything ``` +### For Repo-Local Usage (OpenClaw Agents) + +This project supports dual-entrypoint design: + +1. **Plugin workflow** (see above): Use `cli-anything-plugin/` as a Claude Code plugin +2. **Repo-local skill**: Use `.agents/skills/cli-anything/` for OpenClaw agents + +```bash +# The repo-local skill at .agents/skills/cli-anything/ +# reuses HARNESS.md and command specs from cli-anything-plugin/ +# No additional installation needed for OpenClaw agents +``` + ### For Generated CLIs ```bash diff --git a/tests/test_skill_layout.py b/tests/test_skill_layout.py new file mode 100644 index 0000000..c8dcbbd --- /dev/null +++ b/tests/test_skill_layout.py @@ -0,0 +1,87 @@ +""" +Test suite for CLI-Anything repo structure validation. + +This test ensures the dual-entrypoint design (plugin + repo-local skill) +is properly documented and aligned with README claims. +""" + +import os +import re +from pathlib import Path + +REPO_ROOT = Path(__file__).parent.parent + + +def test_readme_mentions_plugin_and_skill(): + """README should mention both plugin and repo-local skill paths.""" + readme = REPO_ROOT / "README.md" + content = readme.read_text() + + # Check plugin path is documented + assert "cli-anything-plugin" in content, "README should mention cli-anything-plugin" + + # Check skill path is documented (dual-entrypoint) + assert ".agents/skills" in content or "repo-local" in content.lower(), \ + "README should mention repo-local skill path" + + +def test_skill_directory_exists(): + """Repo-local skill directory should exist.""" + skill_dir = REPO_ROOT / ".agents" / "skills" / "cli-anything" + assert skill_dir.exists(), f"Skill directory should exist at {skill_dir}" + assert (skill_dir / "SKILL.md").exists(), "Skill should have SKILL.md" + + +def test_skill_reuses_harness(): + """Skill should reference the existing HARNESS.md.""" + skill_md = REPO_ROOT / ".agents" / "skills" / "cli-anything" / "SKILL.md" + content = skill_md.read_text() + + assert "HARNESS.md" in content, "Skill should reference HARNESS.md" + assert "cli-anything-plugin" in content, "Skill should mention plugin for shared specs" + + +def test_plugin_directory_exists(): + """Plugin directory should exist for dual-entrypoint design.""" + plugin_dir = REPO_ROOT / "cli-anything-plugin" + assert plugin_dir.exists(), "cli-anything-plugin directory should exist" + assert (plugin_dir / "HARNESS.md").exists(), "Plugin should have HARNESS.md" + assert (plugin_dir / "commands").exists(), "Plugin should have commands directory" + + +def test_example_appsocumented(): + """README should list example applications.""" + readme = REPO_ROOT / "README.md" + content = readme.read_text() + + examples = ["GIMP", "Blender", "Inkscape", "Audacity"] + for example in examples: + assert example in content, f"README should mention {example} as example" + + +if __name__ == "__main__": + import sys + + tests = [ + test_readme_mentions_plugin_and_skill, + test_skill_directory_exists, + test_skill_reuses_harness, + test_plugin_directory_exists, + test_example_appsocumented, + ] + + failed = [] + for test in tests: + try: + test() + print(f"✓ {test.__name__}") + except AssertionError as e: + print(f"✗ {test.__name__}: {e}") + failed.append(test.__name__) + + if failed: + print(f"\n{len(failed)} test(s) failed") + sys.exit(1) + else: + print(f"\nAll {len(tests)} tests passed!") + sys.exit(0)