cli-anything-agent-native-software
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCLI-Anything: Agent-Native Software Interface Generator
CLI-Anything:面向Agent的软件接口生成器
Skill by ara.so — Devtools Skills collection.
CLI-Anything transforms GUI applications and services into agent-ready command-line interfaces with structured JSON output. It provides a framework for generating CLIs that AI agents can discover, understand, and use to control software programmatically.
由ara.so开发的Skill — 属于Devtools Skills合集。
CLI-Anything可将GUI应用程序和服务转换为适合Agent使用的命令行界面,并输出结构化JSON。它提供了一个生成CLI的框架,AI Agent可以发现、理解并通过编程方式使用这些CLI来控制软件。
What It Does
功能介绍
CLI-Anything bridges the gap between AI agents and traditional software by:
- Generating CLIs for GUI applications (GIMP, Blender, LibreOffice, etc.)
- Structured Output - All commands return JSON for reliable agent parsing
- Self-Documenting - Auto-generated SKILL.md files agents can discover
- Preview Loops - Commands support for visual feedback before committing
--preview - Trajectory Recording - Captures command sequences for reproducible workflows
- Hub Distribution - Central registry for browsing and installing community CLIs
The project includes 18+ production harnesses (GIMP, Inkscape, Shotcut, Blender, Godot, Obsidian, n8n, etc.) and a framework for building your own.
CLI-Anything通过以下方式搭建AI Agent与传统软件之间的桥梁:
- 生成CLI:为GUI应用(GIMP、Blender、LibreOffice等)创建命令行界面
- 结构化输出:所有命令返回JSON格式,便于Agent可靠解析
- 自文档化:自动生成AI Agent可发现的SKILL.md文件
- 预览循环:命令支持参数,可在提交操作前查看视觉反馈
--preview - 轨迹记录:捕获命令序列,实现可复现的工作流
- 中心仓库分发:提供中央注册表,用于浏览和安装社区贡献的CLI
该项目包含18+个生产级工具包(GIMP、Inkscape、Shotcut、Blender、Godot、Obsidian、n8n等),同时提供用于构建自定义CLI的框架。
Installation
安装
Core Framework
核心框架
bash
undefinedbash
undefinedClone the repository
Clone the repository
git clone https://github.com/HKUDS/CLI-Anything.git
cd CLI-Anything
git clone https://github.com/HKUDS/CLI-Anything.git
cd CLI-Anything
Install dependencies
Install dependencies
pip install -r requirements.txt
pip install -r requirements.txt
Install a specific CLI harness (e.g., GIMP)
Install a specific CLI harness (e.g., GIMP)
cd src/gimp
pip install -e .
undefinedcd src/gimp
pip install -e .
undefinedCLI Hub (Package Manager)
CLI Hub(包管理器)
bash
undefinedbash
undefinedInstall the hub package manager
Install the hub package manager
pip install cli-anything-hub
pip install cli-anything-hub
Browse available CLIs
Browse available CLIs
cli-hub list
cli-hub list
Search for specific tools
Search for specific tools
cli-hub search blender
cli-hub search blender
Install a CLI
Install a CLI
cli-hub install gimp
cli-hub install gimp
Update installed CLIs
Update installed CLIs
cli-hub update gimp
cli-hub update gimp
Uninstall
Uninstall
cli-hub uninstall gimp
undefinedcli-hub uninstall gimp
undefinedInstall Skills for AI Agents
为AI Agent安装Skill
bash
undefinedbash
undefinedInstall a skill for Pi, Claude Code, Cursor, etc.
Install a skill for Pi, Claude Code, Cursor, etc.
npx skills add HKUDS/CLI-Anything --skill gimp -g -y
npx skills add HKUDS/CLI-Anything --skill blender -g -y
npx skills add HKUDS/CLI-Anything --skill inkscape -g -y
npx skills add HKUDS/CLI-Anything --skill gimp -g -y
npx skills add HKUDS/CLI-Anything --skill blender -g -y
npx skills add HKUDS/CLI-Anything --skill inkscape -g -y
All skills are in the top-level skills/ directory
All skills are in the top-level skills/ directory
undefinedundefinedKey Concepts
核心概念
Harness Structure
工具包结构
Every CLI harness follows this pattern:
src/<app-name>/
├── cli.py # Click-based CLI entry point
├── commands/ # Command groups (edit, export, analyze, etc.)
│ ├── edit.py
│ ├── export.py
│ └── ...
├── core/ # Application-specific logic
│ ├── backend.py # Software interaction layer
│ └── utils.py
├── tests/
│ ├── unit/ # Fast, isolated tests
│ └── e2e/ # Full integration tests
├── setup.py # Package definition
└── SKILL.md # Agent-discoverable documentation每个CLI工具包都遵循以下结构:
src/<app-name>/
├── cli.py # Click-based CLI entry point
├── commands/ # Command groups (edit, export, analyze, etc.)
│ ├── edit.py
│ ├── export.py
│ └── ...
├── core/ # Application-specific logic
│ ├── backend.py # Software interaction layer
│ └── utils.py
├── tests/
│ ├── unit/ # Fast, isolated tests
│ └── e2e/ # Full integration tests
├── setup.py # Package definition
└── SKILL.md # Agent-discoverable documentationCommand Pattern
命令模式
All commands follow this JSON-output pattern:
python
import click
import json
@click.command()
@click.option('--input', required=True, help='Input file path')
@click.option('--output', required=True, help='Output file path')
@click.option('--format', default='JSON', type=click.Choice(['JSON', 'HUMAN']))
def process(input, output, format):
"""Process an image with specific operations."""
try:
# Perform operation
result = {
'success': True,
'input': input,
'output': output,
'message': 'Operation completed successfully'
}
if format == 'JSON':
click.echo(json.dumps(result, indent=2))
else:
click.echo(f"✓ Processed {input} → {output}")
except Exception as e:
error = {
'success': False,
'error': str(e)
}
click.echo(json.dumps(error, indent=2))
raise click.Abort()所有命令都遵循以下JSON输出模式:
python
import click
import json
@click.command()
@click.option('--input', required=True, help='Input file path')
@click.option('--output', required=True, help='Output file path')
@click.option('--format', default='JSON', type=click.Choice(['JSON', 'HUMAN']))
def process(input, output, format):
"""Process an image with specific operations."""
try:
# Perform operation
result = {
'success': True,
'input': input,
'output': output,
'message': 'Operation completed successfully'
}
if format == 'JSON':
click.echo(json.dumps(result, indent=2))
else:
click.echo(f"✓ Processed {input} → {output}")
except Exception as e:
error = {
'success': False,
'error': str(e)
}
click.echo(json.dumps(error, indent=2))
raise click.Abort()Using Existing CLIs
使用现有CLI
GIMP Example
GIMP示例
bash
undefinedbash
undefinedCreate a new image
Create a new image
gimp-cli create --width 800 --height 600 --output /tmp/canvas.xcf
gimp-cli create --width 800 --height 600 --output /tmp/canvas.xcf
Add a text layer
Add a text layer
gimp-cli layer add-text
--image /tmp/canvas.xcf
--text "Hello AI"
--x 100 --y 100
--font-size 48
--color "#FF0000"
--image /tmp/canvas.xcf
--text "Hello AI"
--x 100 --y 100
--font-size 48
--color "#FF0000"
gimp-cli layer add-text
--image /tmp/canvas.xcf
--text "Hello AI"
--x 100 --y 100
--font-size 48
--color "#FF0000"
--image /tmp/canvas.xcf
--text "Hello AI"
--x 100 --y 100
--font-size 48
--color "#FF0000"
Export as PNG
Export as PNG
gimp-cli export --input /tmp/canvas.xcf --output /tmp/result.png --format png
gimp-cli export --input /tmp/canvas.xcf --output /tmp/result.png --format png
All commands support JSON output
All commands support JSON output
gimp-cli layer list --image /tmp/canvas.xcf --format JSON
undefinedgimp-cli layer list --image /tmp/canvas.xcf --format JSON
undefinedBlender Example
Blender示例
bash
undefinedbash
undefinedCreate a scene with primitives
Create a scene with primitives
blender-cli scene create --name "MyScene" --output /tmp/scene.blend
blender-cli scene create --name "MyScene" --output /tmp/scene.blend
Add objects
Add objects
blender-cli object add-cube --name "Box1" --location 0,0,0 --scene /tmp/scene.blend
blender-cli object add-sphere --name "Ball" --location 2,0,1 --scene /tmp/scene.blend
blender-cli object add-cube --name "Box1" --location 0,0,0 --scene /tmp/scene.blend
blender-cli object add-sphere --name "Ball" --location 2,0,1 --scene /tmp/scene.blend
Render
Render
blender-cli render
--input /tmp/scene.blend
--output /tmp/render.png
--resolution-x 1920
--resolution-y 1080
--samples 128
--engine CYCLES
--input /tmp/scene.blend
--output /tmp/render.png
--resolution-x 1920
--resolution-y 1080
--samples 128
--engine CYCLES
undefinedblender-cli render
--input /tmp/scene.blend
--output /tmp/render.png
--resolution-x 1920
--resolution-y 1080
--samples 128
--engine CYCLES
--input /tmp/scene.blend
--output /tmp/render.png
--resolution-x 1920
--resolution-y 1080
--samples 128
--engine CYCLES
undefinedInkscape Example
Inkscape示例
bash
undefinedbash
undefinedCreate SVG with shapes
Create SVG with shapes
inkscape-cli create --width 500 --height 500 --output /tmp/drawing.svg
inkscape-cli create --width 500 --height 500 --output /tmp/drawing.svg
Add elements
Add elements
inkscape-cli shape add-rect
--svg /tmp/drawing.svg
--x 50 --y 50 --width 100 --height 100
--fill "#3498db"
--svg /tmp/drawing.svg
--x 50 --y 50 --width 100 --height 100
--fill "#3498db"
inkscape-cli shape add-rect
--svg /tmp/drawing.svg
--x 50 --y 50 --width 100 --height 100
--fill "#3498db"
--svg /tmp/drawing.svg
--x 50 --y 50 --width 100 --height 100
--fill "#3498db"
Export to PNG
Export to PNG
inkscape-cli export
--input /tmp/drawing.svg
--output /tmp/drawing.png
--dpi 300
--input /tmp/drawing.svg
--output /tmp/drawing.png
--dpi 300
undefinedinkscape-cli export
--input /tmp/drawing.svg
--output /tmp/drawing.png
--dpi 300
--input /tmp/drawing.svg
--output /tmp/drawing.png
--dpi 300
undefinedBuilding a New CLI Harness
构建新的CLI工具包
Step 1: Project Setup
步骤1:项目初始化
bash
undefinedbash
undefinedCreate harness directory
Create harness directory
mkdir -p src/myapp/{commands,core,tests/{unit,e2e}}
cd src/myapp
mkdir -p src/myapp/{commands,core,tests/{unit,e2e}}
cd src/myapp
Create setup.py
Create setup.py
cat > setup.py << 'EOF'
from setuptools import setup, find_packages
setup(
name='myapp-cli',
version='0.1.0',
packages=find_packages(),
install_requires=[
'click>=8.0',
],
entry_points={
'console_scripts': [
'myapp-cli=cli:cli',
],
},
)
EOF
undefinedcat > setup.py << 'EOF'
from setuptools import setup, find_packages
setup(
name='myapp-cli',
version='0.1.0',
packages=find_packages(),
install_requires=[
'click>=8.0',
],
entry_points={
'console_scripts': [
'myapp-cli=cli:cli',
],
},
)
EOF
undefinedStep 2: Create CLI Entry Point
步骤2:创建CLI入口文件
python
undefinedpython
undefinedcli.py
cli.py
import click
from commands import process, export
@click.group()
@click.version_option()
def cli():
"""MyApp CLI - Agent-native interface for MyApp."""
pass
import click
from commands import process, export
@click.group()
@click.version_option()
def cli():
"""MyApp CLI - Agent-native interface for MyApp."""
pass
Register command groups
Register command groups
cli.add_command(process.process_group)
cli.add_command(export.export_group)
if name == 'main':
cli()
undefinedcli.add_command(process.process_group)
cli.add_command(export.export_group)
if name == 'main':
cli()
undefinedStep 3: Implement Command Groups
步骤3:实现命令组
python
undefinedpython
undefinedcommands/process.py
commands/process.py
import click
import json
from core.backend import MyAppBackend
@click.group(name='process')
def process_group():
"""Processing operations."""
pass
@process_group.command(name='enhance')
@click.option('--input', required=True, type=click.Path(exists=True))
@click.option('--output', required=True, type=click.Path())
@click.option('--strength', default=0.5, type=float, help='Enhancement strength (0-1)')
@click.option('--format', default='JSON', type=click.Choice(['JSON', 'HUMAN']))
@click.option('--preview', is_flag=True, help='Show preview without saving')
def enhance(input, output, strength, format, preview):
"""Enhance image quality."""
try:
backend = MyAppBackend()
if preview:
preview_path = backend.generate_preview(input, 'enhance', strength=strength)
result = {
'success': True,
'preview': preview_path,
'message': 'Preview generated. Remove --preview to apply.'
}
else:
backend.load_file(input)
backend.apply_enhancement(strength)
backend.save_file(output)
result = {
'success': True,
'input': input,
'output': output,
'strength': strength,
'message': 'Enhancement applied successfully'
}
if format == 'JSON':
click.echo(json.dumps(result, indent=2))
else:
click.echo(f"✓ Enhanced {input} → {output}")
except Exception as e:
error = {'success': False, 'error': str(e)}
click.echo(json.dumps(error, indent=2))
raise click.Abort()undefinedimport click
import json
from core.backend import MyAppBackend
@click.group(name='process')
def process_group():
"""Processing operations."""
pass
@process_group.command(name='enhance')
@click.option('--input', required=True, type=click.Path(exists=True))
@click.option('--output', required=True, type=click.Path())
@click.option('--strength', default=0.5, type=float, help='Enhancement strength (0-1)')
@click.option('--format', default='JSON', type=click.Choice(['JSON', 'HUMAN']))
@click.option('--preview', is_flag=True, help='Show preview without saving')
def enhance(input, output, strength, format, preview):
"""Enhance image quality."""
try:
backend = MyAppBackend()
if preview:
preview_path = backend.generate_preview(input, 'enhance', strength=strength)
result = {
'success': True,
'preview': preview_path,
'message': 'Preview generated. Remove --preview to apply.'
}
else:
backend.load_file(input)
backend.apply_enhancement(strength)
backend.save_file(output)
result = {
'success': True,
'input': input,
'output': output,
'strength': strength,
'message': 'Enhancement applied successfully'
}
if format == 'JSON':
click.echo(json.dumps(result, indent=2))
else:
click.echo(f"✓ Enhanced {input} → {output}")
except Exception as e:
error = {'success': False, 'error': str(e)}
click.echo(json.dumps(error, indent=2))
raise click.Abort()undefinedStep 4: Backend Integration
步骤4:后端集成
python
undefinedpython
undefinedcore/backend.py
core/backend.py
import subprocess
import tempfile
import os
class MyAppBackend:
"""Wrapper for MyApp software."""
def __init__(self, executable_path=None):
self.executable = executable_path or self._find_executable()
self.current_file = None
def _find_executable(self):
"""Locate MyApp executable."""
# Check common installation paths
paths = [
'/usr/bin/myapp',
'/usr/local/bin/myapp',
'C:\\Program Files\\MyApp\\myapp.exe'
]
for path in paths:
if os.path.exists(path):
return path
raise RuntimeError("MyApp not found. Please install MyApp first.")
def load_file(self, filepath):
"""Load a file into MyApp."""
self.current_file = filepath
# Implementation depends on whether MyApp has:
# - Python API: import myapp; myapp.load(filepath)
# - Scripting: Generate script and execute
# - IPC: Send commands via socket/pipe
def apply_enhancement(self, strength):
"""Apply enhancement filter."""
# Call MyApp's API or scripting interface
pass
def save_file(self, output_path):
"""Save current document."""
# Implement save logic
pass
def generate_preview(self, input_path, operation, **params):
"""Generate preview image."""
preview_dir = tempfile.mkdtemp()
preview_path = os.path.join(preview_dir, 'preview.png')
# Generate low-res preview
return preview_pathundefinedimport subprocess
import tempfile
import os
class MyAppBackend:
"""Wrapper for MyApp software."""
def __init__(self, executable_path=None):
self.executable = executable_path or self._find_executable()
self.current_file = None
def _find_executable(self):
"""Locate MyApp executable."""
# Check common installation paths
paths = [
'/usr/bin/myapp',
'/usr/local/bin/myapp',
'C:\\Program Files\\MyApp\\myapp.exe'
]
for path in paths:
if os.path.exists(path):
return path
raise RuntimeError("MyApp not found. Please install MyApp first.")
def load_file(self, filepath):
"""Load a file into MyApp."""
self.current_file = filepath
# Implementation depends on whether MyApp has:
# - Python API: import myapp; myapp.load(filepath)
# - Scripting: Generate script and execute
# - IPC: Send commands via socket/pipe
def apply_enhancement(self, strength):
"""Apply enhancement filter."""
# Call MyApp's API or scripting interface
pass
def save_file(self, output_path):
"""Save current document."""
# Implement save logic
pass
def generate_preview(self, input_path, operation, **params):
"""Generate preview image."""
preview_dir = tempfile.mkdtemp()
preview_path = os.path.join(preview_dir, 'preview.png')
# Generate low-res preview
return preview_pathundefinedStep 5: Write Tests
步骤5:编写测试
python
undefinedpython
undefinedtests/unit/test_backend.py
tests/unit/test_backend.py
import pytest
from core.backend import MyAppBackend
def test_backend_initialization():
"""Test backend can find executable."""
backend = MyAppBackend()
assert backend.executable is not None
def test_file_operations(tmp_path):
"""Test load and save operations."""
backend = MyAppBackend()
test_file = tmp_path / "test.myapp"
test_file.write_text("test content")
backend.load_file(str(test_file))
assert backend.current_file == str(test_file)import pytest
from core.backend import MyAppBackend
def test_backend_initialization():
"""Test backend can find executable."""
backend = MyAppBackend()
assert backend.executable is not None
def test_file_operations(tmp_path):
"""Test load and save operations."""
backend = MyAppBackend()
test_file = tmp_path / "test.myapp"
test_file.write_text("test content")
backend.load_file(str(test_file))
assert backend.current_file == str(test_file)tests/e2e/test_enhance.py
tests/e2e/test_enhance.py
import subprocess
import json
import pytest
def test_enhance_command(tmp_path):
"""Test full enhance workflow."""
input_file = tmp_path / "input.png"
output_file = tmp_path / "output.png"
# Create test input (implementation specific)
# ...
result = subprocess.run([
'myapp-cli', 'process', 'enhance',
'--input', str(input_file),
'--output', str(output_file),
'--strength', '0.7',
'--format', 'JSON'
], capture_output=True, text=True)
assert result.returncode == 0
data = json.loads(result.stdout)
assert data['success'] is True
assert output_file.exists()undefinedimport subprocess
import json
import pytest
def test_enhance_command(tmp_path):
"""Test full enhance workflow."""
input_file = tmp_path / "input.png"
output_file = tmp_path / "output.png"
# Create test input (implementation specific)
# ...
result = subprocess.run([
'myapp-cli', 'process', 'enhance',
'--input', str(input_file),
'--output', str(output_file),
'--strength', '0.7',
'--format', 'JSON'
], capture_output=True, text=True)
assert result.returncode == 0
data = json.loads(result.stdout)
assert data['success'] is True
assert output_file.exists()undefinedStep 6: Generate SKILL.md
步骤6:生成SKILL.md
python
undefinedpython
undefinedCreate skill_generator.py in your harness directory
Create skill_generator.py in your harness directory
import click
import json
import os
def generate_skill_md():
"""Generate SKILL.md for AI agent discovery."""
# Extract command structure
from cli import cli as main_cli
commands = []
for group_name, group in main_cli.commands.items():
if hasattr(group, 'commands'):
for cmd_name, cmd in group.commands.items():
commands.append({
'group': group_name,
'name': cmd_name,
'help': cmd.help or '',
'params': [p.name for p in cmd.params if p.name != 'format']
})
skill_content = f"""---name: myapp-agent-interface
description: Control MyApp through structured commands with JSON output
triggers:
- use myapp to process this
- enhance this with myapp
- export using myapp
- create with myapp
- automate myapp workflow
- generate myapp output
import click
import json
import os
def generate_skill_md():
"""Generate SKILL.md for AI agent discovery."""
# Extract command structure
from cli import cli as main_cli
commands = []
for group_name, group in main_cli.commands.items():
if hasattr(group, 'commands'):
for cmd_name, cmd in group.commands.items():
commands.append({
'group': group_name,
'name': cmd_name,
'help': cmd.help or '',
'params': [p.name for p in cmd.params if p.name != 'format']
})
skill_content = f"""---name: myapp-agent-interface
description: Control MyApp through structured commands with JSON output
triggers:
- use myapp to process this
- enhance this with myapp
- export using myapp
- create with myapp
- automate myapp workflow
- generate myapp output
MyApp CLI - Agent Interface
MyApp CLI - Agent Interface
Skill by ara.so — Devtools Skills collection.
Control MyApp through a structured command-line interface designed for AI agents.
Skill by ara.so — Devtools Skills collection.
Control MyApp through a structured command-line interface designed for AI agents.
Installation
Installation
bash
pip install myapp-clibash
pip install myapp-clior via CLI-Hub
or via CLI-Hub
cli-hub install myapp
undefinedcli-hub install myapp
undefinedAvailable Commands
Available Commands
"""
for cmd in commands:
skill_content += f"\n### {cmd['group']} {cmd['name']}\n\n"
skill_content += f"{cmd['help']}\n\n"
skill_content += f"**Parameters:** {', '.join(cmd['params'])}\n\n"
with open('SKILL.md', 'w') as f:
f.write(skill_content)
print("✓ SKILL.md generated")if name == 'main':
generate_skill_md()
undefined"""
for cmd in commands:
skill_content += f"\n### {cmd['group']} {cmd['name']}\n\n"
skill_content += f"{cmd['help']}\n\n"
skill_content += f"**Parameters:** {', '.join(cmd['params'])}\n\n"
with open('SKILL.md', 'w') as f:
f.write(skill_content)
print("✓ SKILL.md generated")if name == 'main':
generate_skill_md()
undefinedAdvanced Patterns
进阶模式
Preview Loop Implementation
预览循环实现
python
@click.option('--preview', is_flag=True, help='Generate preview without applying')
@click.option('--preview-resolution', default=512, help='Preview image size')
def command_with_preview(preview, preview_resolution, **kwargs):
"""Command supporting preview mode."""
if preview:
# Generate low-resolution preview
preview_path = generate_preview(
kwargs['input'],
resolution=preview_resolution,
operation_params=kwargs
)
return {
'success': True,
'preview': preview_path,
'message': 'Preview ready. Remove --preview to apply changes.'
}
else:
# Execute full operation
return execute_full_operation(**kwargs)python
@click.option('--preview', is_flag=True, help='Generate preview without applying')
@click.option('--preview-resolution', default=512, help='Preview image size')
def command_with_preview(preview, preview_resolution, **kwargs):
"""Command supporting preview mode."""
if preview:
# Generate low-resolution preview
preview_path = generate_preview(
kwargs['input'],
resolution=preview_resolution,
operation_params=kwargs
)
return {
'success': True,
'preview': preview_path,
'message': 'Preview ready. Remove --preview to apply changes.'
}
else:
# Execute full operation
return execute_full_operation(**kwargs)Trajectory Recording
轨迹记录
python
import json
import os
from datetime import datetime
class TrajectoryRecorder:
"""Record command sequences for reproducibility."""
def __init__(self, session_name=None):
self.session_name = session_name or datetime.now().isoformat()
self.trajectory = []
self.output_dir = os.path.expanduser('~/.myapp-cli/trajectories')
os.makedirs(self.output_dir, exist_ok=True)
def record(self, command, params, result):
"""Record a command execution."""
self.trajectory.append({
'timestamp': datetime.now().isoformat(),
'command': command,
'params': params,
'result': result
})
def save(self):
"""Save trajectory to disk."""
filepath = os.path.join(self.output_dir, f"{self.session_name}.json")
with open(filepath, 'w') as f:
json.dump(self.trajectory, f, indent=2)
return filepath
@staticmethod
def replay(trajectory_file):
"""Replay a recorded trajectory."""
with open(trajectory_file) as f:
trajectory = json.load(f)
for step in trajectory:
# Re-execute command with recorded params
print(f"Replaying: {step['command']}")
# Implementation specificpython
import json
import os
from datetime import datetime
class TrajectoryRecorder:
"""Record command sequences for reproducibility."""
def __init__(self, session_name=None):
self.session_name = session_name or datetime.now().isoformat()
self.trajectory = []
self.output_dir = os.path.expanduser('~/.myapp-cli/trajectories')
os.makedirs(self.output_dir, exist_ok=True)
def record(self, command, params, result):
"""Record a command execution."""
self.trajectory.append({
'timestamp': datetime.now().isoformat(),
'command': command,
'params': params,
'result': result
})
def save(self):
"""Save trajectory to disk."""
filepath = os.path.join(self.output_dir, f"{self.session_name}.json")
with open(filepath, 'w') as f:
json.dump(self.trajectory, f, indent=2)
return filepath
@staticmethod
def replay(trajectory_file):
"""Replay a recorded trajectory."""
with open(trajectory_file) as f:
trajectory = json.load(f)
for step in trajectory:
# Re-execute command with recorded params
print(f"Replaying: {step['command']}")
# Implementation specificEnvironment Configuration
环境配置
python
undefinedpython
undefinedcore/config.py
core/config.py
import os
from pathlib import Path
class Config:
"""Configuration management."""
def __init__(self):
self.config_dir = Path.home() / '.myapp-cli'
self.config_file = self.config_dir / 'config.json'
self.config_dir.mkdir(exist_ok=True)
self.load()
def load(self):
"""Load configuration from disk."""
if self.config_file.exists():
import json
with open(self.config_file) as f:
self._config = json.load(f)
else:
self._config = self.defaults()
def defaults(self):
"""Default configuration values."""
return {
'executable_path': os.getenv('MYAPP_EXECUTABLE'),
'default_format': 'JSON',
'enable_trajectories': True,
'preview_resolution': 512,
'temp_dir': str(self.config_dir / 'tmp')
}
def get(self, key, default=None):
"""Get configuration value."""
return self._config.get(key, default)
def set(self, key, value):
"""Set configuration value."""
self._config[key] = value
self.save()
def save(self):
"""Save configuration to disk."""
import json
with open(self.config_file, 'w') as f:
json.dump(self._config, f, indent=2)undefinedimport os
from pathlib import Path
class Config:
"""Configuration management."""
def __init__(self):
self.config_dir = Path.home() / '.myapp-cli'
self.config_file = self.config_dir / 'config.json'
self.config_dir.mkdir(exist_ok=True)
self.load()
def load(self):
"""Load configuration from disk."""
if self.config_file.exists():
import json
with open(self.config_file) as f:
self._config = json.load(f)
else:
self._config = self.defaults()
def defaults(self):
"""Default configuration values."""
return {
'executable_path': os.getenv('MYAPP_EXECUTABLE'),
'default_format': 'JSON',
'enable_trajectories': True,
'preview_resolution': 512,
'temp_dir': str(self.config_dir / 'tmp')
}
def get(self, key, default=None):
"""Get configuration value."""
return self._config.get(key, default)
def set(self, key, value):
"""Set configuration value."""
self._config[key] = value
self.save()
def save(self):
"""Save configuration to disk."""
import json
with open(self.config_file, 'w') as f:
json.dump(self._config, f, indent=2)undefinedIntegration with AI Agents
与AI Agent集成
Claude Code / Cursor
Claude Code / Cursor
python
undefinedpython
undefinedIn your project, agents will discover the skill:
In your project, agents will discover the skill:
"I need to process images with MyApp"
"I need to process images with MyApp"
Agent automatically runs:
Agent automatically runs:
myapp-cli process enhance --input photo.jpg --output enhanced.jpg --strength 0.8
myapp-cli process enhance --input photo.jpg --output enhanced.jpg --strength 0.8
With preview loop:
With preview loop:
myapp-cli process enhance --input photo.jpg --output enhanced.jpg --strength 0.8 --preview
myapp-cli process enhance --input photo.jpg --output enhanced.jpg --strength 0.8 --preview
[Agent reviews preview]
[Agent reviews preview]
myapp-cli process enhance --input photo.jpg --output enhanced.jpg --strength 0.6
myapp-cli process enhance --input photo.jpg --output enhanced.jpg --strength 0.6
undefinedundefinedOpenClaw / Pi
OpenClaw / Pi
python
undefinedpython
undefinedAgents parse JSON output reliably
Agents parse JSON output reliably
result = subprocess.run([
'myapp-cli', 'export', '--input', 'file.myapp', '--format', 'JSON'
], capture_output=True, text=True)
data = json.loads(result.stdout)
if data['success']:
output_path = data['output']
# Continue workflow
undefinedresult = subprocess.run([
'myapp-cli', 'export', '--input', 'file.myapp', '--format', 'JSON'
], capture_output=True, text=True)
data = json.loads(result.stdout)
if data['success']:
output_path = data['output']
# Continue workflow
undefinedPublishing to CLI-Hub
发布到CLI-Hub
1. Add to Registry
1. 添加到注册表
bash
undefinedbash
undefinedFork HKUDS/CLI-Anything
Fork HKUDS/CLI-Anything
Edit hub/registry.json
Edit hub/registry.json
{
"myapp": {
"name": "myapp-cli",
"version": "0.1.0",
"description": "Agent interface for MyApp",
"install_source": "pip",
"package_name": "myapp-cli",
"skill_md": "src/myapp/SKILL.md",
"categories": ["graphics", "automation"],
"maintainer": "your-github-username",
"repo_url": "https://github.com/yourname/myapp-cli"
}
}
{
"myapp": {
"name": "myapp-cli",
"version": "0.1.0",
"description": "Agent interface for MyApp",
"install_source": "pip",
"package_name": "myapp-cli",
"skill_md": "src/myapp/SKILL.md",
"categories": ["graphics", "automation"],
"maintainer": "your-github-username",
"repo_url": "https://github.com/yourname/myapp-cli"
}
}
Submit PR
Submit PR
undefinedundefined2. PyPI Publication
2. PyPI发布
bash
undefinedbash
undefinedBuild package
Build package
python setup.py sdist bdist_wheel
python setup.py sdist bdist_wheel
Upload to PyPI
Upload to PyPI
pip install twine
twine upload dist/*
pip install twine
twine upload dist/*
Users can now:
Users can now:
pip install myapp-cli
pip install myapp-cli
cli-hub install myapp
cli-hub install myapp
undefinedundefinedTroubleshooting
故障排除
Executable Not Found
可执行文件未找到
python
undefinedpython
undefinedOverride executable path
Override executable path
export MYAPP_EXECUTABLE=/custom/path/to/myapp
myapp-cli --help
export MYAPP_EXECUTABLE=/custom/path/to/myapp
myapp-cli --help
Or configure permanently
Or configure permanently
myapp-cli config set executable_path /custom/path/to/myapp
undefinedmyapp-cli config set executable_path /custom/path/to/myapp
undefinedJSON Parsing Errors
JSON解析错误
python
undefinedpython
undefinedAlways validate JSON output in commands
Always validate JSON output in commands
try:
result = {'success': True, 'data': value}
click.echo(json.dumps(result, indent=2))
except TypeError as e:
# Handle non-serializable objects
result = {'success': False, 'error': f'Serialization error: {e}'}
click.echo(json.dumps(result, indent=2))
undefinedtry:
result = {'success': True, 'data': value}
click.echo(json.dumps(result, indent=2))
except TypeError as e:
# Handle non-serializable objects
result = {'success': False, 'error': f'Serialization error: {e}'}
click.echo(json.dumps(result, indent=2))
undefinedPreview Generation Fails
预览生成失败
python
undefinedpython
undefinedEnsure temp directory exists and is writable
Ensure temp directory exists and is writable
import tempfile
preview_dir = tempfile.mkdtemp(prefix='myapp-preview-')
import tempfile
preview_dir = tempfile.mkdtemp(prefix='myapp-preview-')
Clean up after preview
Clean up after preview
import atexit
atexit.register(lambda: shutil.rmtree(preview_dir, ignore_errors=True))
undefinedimport atexit
atexit.register(lambda: shutil.rmtree(preview_dir, ignore_errors=True))
undefinedTesting with Different Software Versions
不同软件版本测试
python
undefinedpython
undefinedtests/conftest.py
tests/conftest.py
import pytest
import subprocess
def get_myapp_version():
"""Detect installed MyApp version."""
result = subprocess.run(['myapp', '--version'], capture_output=True, text=True)
# Parse version
return version
@pytest.fixture
def skip_if_version_below(min_version):
"""Skip test if MyApp version too old."""
current = get_myapp_version()
if current < min_version:
pytest.skip(f"Requires MyApp >= {min_version}")
undefinedimport pytest
import subprocess
def get_myapp_version():
"""Detect installed MyApp version."""
result = subprocess.run(['myapp', '--version'], capture_output=True, text=True)
# Parse version
return version
@pytest.fixture
def skip_if_version_below(min_version):
"""Skip test if MyApp version too old."""
current = get_myapp_version()
if current < min_version:
pytest.skip(f"Requires MyApp >= {min_version}")
undefinedBest Practices
最佳实践
- Always return JSON - Agents rely on structured output
- Implement --preview - Let agents validate before committing
- Use absolute paths - Avoid ambiguity in file operations
- Validate inputs - Check file existence, parameter ranges
- Handle errors gracefully - Return instead of crashing
{"success": false, "error": "..."} - Document in SKILL.md - Keep agent-facing docs updated
- Write E2E tests - Verify full workflows work end-to-end
- Support environment variables - Allow configuration without code changes
- 始终返回JSON:Agent依赖结构化输出
- 实现--preview参数:让Agent在提交前验证结果
- 使用绝对路径:避免文件操作的歧义
- 验证输入:检查文件是否存在、参数范围是否合法
- 优雅处理错误:返回而非直接崩溃
{"success": false, "error": "..."} - 在SKILL.md中更新文档:保持Agent面向的文档最新
- 编写端到端测试:验证完整工作流是否正常运行
- 支持环境变量:允许无需修改代码即可配置
Reference Documentation
参考文档
- Main Repo: https://github.com/HKUDS/CLI-Anything
- CLI Hub: https://clianything.cc/
- Contributing Guide: CONTRIBUTING.md in repo
- Harness Template: See for starting point
src/template/ - Existing Harnesses: Study ,
src/gimp/,src/blender/for patternssrc/inkscape/
- 主仓库:https://github.com/HKUDS/CLI-Anything
- CLI Hub:https://clianything.cc/
- 贡献指南:仓库中的CONTRIBUTING.md
- 工具包模板:参考目录作为起点
src/template/ - 现有工具包:参考、
src/gimp/、src/blender/的实现模式src/inkscape/