cli-generator

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

AI-Friendly CLI Generator Skill

适配AI的CLI生成Skill

Generate Python command-line interfaces optimized for AI agents and agentic coding environments.
生成针对AI智能体和智能体编码环境优化的Python命令行界面。

Core Principle: Every Output is a Prompt

核心原则:每个输出都是一个提示

In an agentic coding environment, every interaction with a CLI tool is a turn in a conversation. The tool's output—whether it succeeds or fails—should be designed as a helpful, guiding prompt for the agent's next action.
在智能体编码环境中,与CLI工具的每一次交互都是对话中的一个回合。工具的输出——无论成功还是失败——都应被设计为对智能体下一步操作有帮助的指导性提示。

Tech Stack

技术栈

  • Python - Primary language
  • Click - CLI framework
  • Pydantic - Data validation and response models
  • Rich - Terminal formatting and tables
  • uv - Package management
  • Python - 主要开发语言
  • Click - CLI框架
  • Pydantic - 数据验证与响应模型
  • Rich - 终端格式化与表格展示
  • uv - 包管理工具

Project Structure

项目结构

my-cli/
├── pyproject.toml
├── README.md
├── src/
│   └── my_cli/
│       ├── __init__.py
│       ├── main.py              # CLI entry point
│       ├── commands/            # Command modules
│       │   └── __init__.py
│       ├── models/
│       │   ├── __init__.py
│       │   └── responses.py     # Pydantic response models
│       ├── output/
│       │   ├── __init__.py
│       │   └── conversational.py # AI-friendly output
│       └── core/
│           ├── __init__.py
│           ├── client.py        # API client
│           └── config.py        # Configuration
└── tests/
my-cli/
├── pyproject.toml
├── README.md
├── src/
│   └── my_cli/
│       ├── __init__.py
│       ├── main.py              # CLI entry point
│       ├── commands/            # Command modules
│       │   └── __init__.py
│       ├── models/
│       │   ├── __init__.py
│       │   └── responses.py     # Pydantic response models
│       ├── output/
│       │   ├── __init__.py
│       │   └── conversational.py # AI-friendly output
│       └── core/
│           ├── __init__.py
│           ├── client.py        # API client
│           └── config.py        # Configuration
└── tests/

Quick Start

快速开始

  1. Create project directory:
bash
mkdir my-cli && cd my-cli
  1. Initialize with uv:
bash
uv init
  1. Add dependencies to
    pyproject.toml
    :
toml
dependencies = [
    "click>=8.1.0",
    "rich>=13.0.0",
    "pydantic>=2.0.0",
]
  1. Create the source structure:
bash
mkdir -p src/my_cli/{commands,models,output,core}
touch src/my_cli/__init__.py
touch src/my_cli/{commands,models,output,core}/__init__.py
  1. Copy templates from
    templates/
    directory
  1. 创建项目目录:
bash
mkdir my-cli && cd my-cli
  1. 使用uv初始化项目:
bash
uv init
  1. pyproject.toml
    添加依赖:
toml
dependencies = [
    "click>=8.1.0",
    "rich>=13.0.0",
    "pydantic>=2.0.0",
]
  1. 创建源码结构:
bash
mkdir -p src/my_cli/{commands,models,output,core}
touch src/my_cli/__init__.py
touch src/my_cli/{commands,models,output,core}/__init__.py
  1. templates/
    目录复制模板文件

AI-Friendly Output Patterns

适配AI的输出模式

Pattern 1: Success Output

模式1:成功输出

A successful output confirms the action AND suggests next steps with exact commands:
Bad (Traditional):
Success!
Good (AI-Friendly):
✅ Found 4 documents matching 'AI'

📋 Available Resources:
  • Total documents: 4
  • First document ID: 2oLo0Z72BR
  • First document name: AI experience design

📊 Results:
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Name                        ┃ ID         ┃ Updated    ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ AI experience design        │ 2oLo0Z72BR │ 2025-11-26 │
└─────────────────────────────┴────────────┴────────────┘

💡 What's next? Try these commands:
  1. 👁️ mycli show 2oLo0Z72BR - View document details
  2. 📤 mycli export 2oLo0Z72BR --format json - Export as JSON
成功输出应同时确认操作完成,并提供带有精确命令的下一步建议:
不佳示例(传统方式):
Success!
良好示例(适配AI):
✅ Found 4 documents matching 'AI'

📋 Available Resources:
  • Total documents: 4
  • First document ID: 2oLo0Z72BR
  • First document name: AI experience design

📊 Results:
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Name                        ┃ ID         ┃ Updated    ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ AI experience design        │ 2oLo0Z72BR │ 2025-11-26 │
└─────────────────────────────┴────────────┴────────────┘

💡 What's next? Try these commands:
  1. 👁️ mycli show 2oLo0Z72BR - View document details
  2. 📤 mycli export 2oLo0Z72BR --format json - Export as JSON

Pattern 2: Error Output (Three Parts)

模式2:错误输出(三部分)

Every error must include:
  1. What went wrong - Clear description
  2. How to fix - Step-by-step instructions
  3. What's next - Commands to try after fixing
Example:
❌ Command failed
   Authentication error

🔍 What went wrong:
  The Coda API returned an error: API key is invalid or expired.

🔧 How to fix:
  1. Check your internet connection
  2. Verify your API key is correct
  3. Try regenerating your API token

💡 What's next:
  • mycli auth test - Test your authentication
  • mycli auth setup - Re-run interactive setup
每个错误输出必须包含:
  1. 问题所在 - 清晰的描述
  2. 修复方法 - 分步说明
  3. 下一步操作 - 修复后可尝试的命令
示例:
❌ Command failed
   Authentication error

🔍 What went wrong:
  The Coda API returned an error: API key is invalid or expired.

🔧 How to fix:
  1. Check your internet connection
  2. Verify your API key is correct
  3. Try regenerating your API token

💡 What's next:
  • mycli auth test - Test your authentication
  • mycli auth setup - Re-run interactive setup

Pattern 3: Help Text with Examples

模式3:带示例的帮助文本

Always include working examples in
--help
:
python
@click.command(
    epilog="""
Examples:
    # Search for documents
    mycli search "machine learning"

    # Export a table as JSON
    mycli export DOC_ID TABLE_ID --format json

    # List all your documents
    mycli list --mine
"""
)
def search(query: str):
    """Search for documents matching a query."""
    pass
--help
中始终包含可运行的示例:
python
@click.command(
    epilog="""
Examples:
    # Search for documents
    mycli search "machine learning"

    # Export a table as JSON
    mycli export DOC_ID TABLE_ID --format json

    # List all your documents
    mycli list --mine
"""
)
def search(query: str):
    """Search for documents matching a query."""
    pass

Code Patterns

代码模式

Response Models (
models/responses.py
)

响应模型(
models/responses.py

python
"""Pydantic models for CLI command responses."""

from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field


class Suggestion(BaseModel):
    """A suggested next command with description."""
    command: str = Field(..., description="The exact command to run")
    description: str = Field(..., description="What the command does")
    category: Optional[str] = Field(None, description="Category: view, export, search, etc.")


class ErrorDetail(BaseModel):
    """Detailed error following 'what/how/next' pattern."""
    what_went_wrong: str = Field(..., description="Clear explanation of the failure")
    how_to_fix: List[str] = Field(..., description="Step-by-step fix instructions")
    whats_next: List[Suggestion] = Field(..., description="Commands to try after fixing")
    error_code: Optional[str] = Field(None, description="Machine-readable error code")


class CommandResult(BaseModel):
    """Result of a CLI command with conversational context."""
    success: bool = Field(..., description="Whether command succeeded")
    message: str = Field(..., description="Primary result message")
    context: Dict[str, Any] = Field(default_factory=dict, description="Resource IDs and metadata")
    data: Optional[List[Any]] = Field(None, description="Structured data results")
    suggestions: List[Suggestion] = Field(default_factory=list, description="Suggested next commands")
    error: Optional[ErrorDetail] = Field(None, description="Error details if failed")
python
"""Pydantic models for CLI command responses."""

from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field


class Suggestion(BaseModel):
    """A suggested next command with description."""
    command: str = Field(..., description="The exact command to run")
    description: str = Field(..., description="What the command does")
    category: Optional[str] = Field(None, description="Category: view, export, search, etc.")


class ErrorDetail(BaseModel):
    """Detailed error following 'what/how/next' pattern."""
    what_went_wrong: str = Field(..., description="Clear explanation of the failure")
    how_to_fix: List[str] = Field(..., description="Step-by-step fix instructions")
    whats_next: List[Suggestion] = Field(..., description="Commands to try after fixing")
    error_code: Optional[str] = Field(None, description="Machine-readable error code")


class CommandResult(BaseModel):
    """Result of a CLI command with conversational context."""
    success: bool = Field(..., description="Whether command succeeded")
    message: str = Field(..., description="Primary result message")
    context: Dict[str, Any] = Field(default_factory=dict, description="Resource IDs and metadata")
    data: Optional[List[Any]] = Field(None, description="Structured data results")
    suggestions: List[Suggestion] = Field(default_factory=list, description="Suggested next commands")
    error: Optional[ErrorDetail] = Field(None, description="Error details if failed")

Conversational Output (
output/conversational.py
)

对话式输出(
output/conversational.py

python
"""Conversational output following 'Every Output is a Prompt' pattern."""

from typing import Any, Optional, List
from rich.console import Console
from rich.table import Table
from .responses import CommandResult, Suggestion


class ConversationalOutput:
    """Output manager that makes every interaction conversational."""

    def __init__(self, console: Console, show_suggestions: bool = True):
        self.console = console
        self.show_suggestions = show_suggestions

    def success(self, result: CommandResult) -> None:
        """Display success with context and suggestions."""
        # Main success message
        self.console.print(f"✅ {result.message}", style="bold green")

        # Show context (resource IDs, counts, etc.)
        if result.context:
            self.console.print("\n📋 Available Resources:", style="bold blue")
            for key, value in result.context.items():
                self.console.print(f"  • {key}: [cyan]{value}[/cyan]")

        # Show data in table format
        if result.data:
            self._render_data(result.data)

        # Show suggested next commands
        if self.show_suggestions and result.suggestions:
            self._render_suggestions(result.suggestions)

    def error(self, result: CommandResult) -> None:
        """Display error with three-part pattern."""
        if not result.error:
            self.console.print(f"❌ {result.message}", style="bold red")
            return

        error = result.error

        # What went wrong
        self.console.print("❌ Command failed", style="bold red")
        self.console.print(f"   {result.message}")
        self.console.print("\n🔍 What went wrong:", style="bold yellow")
        self.console.print(f"  {error.what_went_wrong}")

        # How to fix
        if error.how_to_fix:
            self.console.print("\n🔧 How to fix:", style="bold green")
            for i, step in enumerate(error.how_to_fix, 1):
                self.console.print(f"  {i}. {step}")

        # What's next
        if error.whats_next:
            self.console.print("\n💡 What's next:", style="bold blue")
            for suggestion in error.whats_next:
                self.console.print(
                    f"  • [cyan]{suggestion.command}[/cyan] - {suggestion.description}"
                )

    def _render_data(self, data: List[Any]) -> None:
        """Render structured data as a table."""
        if not data:
            return

        self.console.print("\n📊 Results:", style="bold blue")
        table = Table(show_header=True, header_style="bold magenta")

        # Build table from first item's keys
        if isinstance(data[0], dict):
            for key in list(data[0].keys())[:5]:  # Limit columns
                table.add_column(key.replace("_", " ").title())

            for item in data[:10]:  # Limit rows
                table.add_row(*[str(v)[:40] for v in list(item.values())[:5]])

        self.console.print(table)

    def _render_suggestions(self, suggestions: List[Suggestion]) -> None:
        """Render suggested next commands."""
        self.console.print("\n💡 What's next? Try these commands:", style="bold yellow")

        emoji_map = {
            "view": "👁️", "export": "📤", "search": "🔍",
            "create": "✨", "edit": "✏️", "auth": "🔐",
        }

        for i, s in enumerate(suggestions[:5], 1):
            emoji = emoji_map.get(s.category, "")
            self.console.print(f"  {i}. {emoji}[cyan]{s.command}[/cyan] - {s.description}")
python
"""Conversational output following 'Every Output is a Prompt' pattern."""

from typing import Any, Optional, List
from rich.console import Console
from rich.table import Table
from .responses import CommandResult, Suggestion


class ConversationalOutput:
    """Output manager that makes every interaction conversational."""

    def __init__(self, console: Console, show_suggestions: bool = True):
        self.console = console
        self.show_suggestions = show_suggestions

    def success(self, result: CommandResult) -> None:
        """Display success with context and suggestions."""
        # Main success message
        self.console.print(f"✅ {result.message}", style="bold green")

        # Show context (resource IDs, counts, etc.)
        if result.context:
            self.console.print("\n📋 Available Resources:", style="bold blue")
            for key, value in result.context.items():
                self.console.print(f"  • {key}: [cyan]{value}[/cyan]")

        # Show data in table format
        if result.data:
            self._render_data(result.data)

        # Show suggested next commands
        if self.show_suggestions and result.suggestions:
            self._render_suggestions(result.suggestions)

    def error(self, result: CommandResult) -> None:
        """Display error with three-part pattern."""
        if not result.error:
            self.console.print(f"❌ {result.message}", style="bold red")
            return

        error = result.error

        # What went wrong
        self.console.print("❌ Command failed", style="bold red")
        self.console.print(f"   {result.message}")
        self.console.print("\n🔍 What went wrong:", style="bold yellow")
        self.console.print(f"  {error.what_went_wrong}")

        # How to fix
        if error.how_to_fix:
            self.console.print("\n🔧 How to fix:", style="bold green")
            for i, step in enumerate(error.how_to_fix, 1):
                self.console.print(f"  {i}. {step}")

        # What's next
        if error.whats_next:
            self.console.print("\n💡 What's next:", style="bold blue")
            for suggestion in error.whats_next:
                self.console.print(
                    f"  • [cyan]{suggestion.command}[/cyan] - {suggestion.description}"
                )

    def _render_data(self, data: List[Any]) -> None:
        """Render structured data as a table."""
        if not data:
            return

        self.console.print("\n📊 Results:", style="bold blue")
        table = Table(show_header=True, header_style="bold magenta")

        # Build table from first item's keys
        if isinstance(data[0], dict):
            for key in list(data[0].keys())[:5]:  # Limit columns
                table.add_column(key.replace("_", " ").title())

            for item in data[:10]:  # Limit rows
                table.add_row(*[str(v)[:40] for v in list(item.values())[:5]])

        self.console.print(table)

    def _render_suggestions(self, suggestions: List[Suggestion]) -> None:
        """Render suggested next commands."""
        self.console.print("\n💡 What's next? Try these commands:", style="bold yellow")

        emoji_map = {
            "view": "👁️", "export": "📤", "search": "🔍",
            "create": "✨", "edit": "✏️", "auth": "🔐",
        }

        for i, s in enumerate(suggestions[:5], 1):
            emoji = emoji_map.get(s.category, "")
            self.console.print(f"  {i}. {emoji}[cyan]{s.command}[/cyan] - {s.description}")

Main CLI Entry Point (
main.py
)

主CLI入口文件(
main.py

python
"""Main CLI entry point."""

import click
from rich.console import Console

from .models.responses import CommandResult, Suggestion, ErrorDetail
from .output.conversational import ConversationalOutput

console = Console()
output = ConversationalOutput(console)


@click.group()
@click.version_option()
def cli():
    """My CLI tool - AI-friendly command interface.

    Examples:
        mycli search "query"
        mycli show RESOURCE_ID
        mycli export RESOURCE_ID --format json
    """
    pass


@cli.command(epilog="""
Examples:
    mycli search "machine learning"
    mycli search "climate" --limit 5
""")
@click.argument("query")
@click.option("--limit", default=10, help="Maximum results to return")
def search(query: str, limit: int):
    """Search for resources matching a query."""
    try:
        # Your search logic here
        results = []  # fetch_results(query, limit)

        result = CommandResult(
            success=True,
            message=f"Found {len(results)} results for '{query}'",
            context={
                "Query": query,
                "Total results": len(results),
            },
            data=results,
            suggestions=[
                Suggestion(
                    command=f"mycli show {results[0]['id']}" if results else "mycli list",
                    description="View details" if results else "List all resources",
                    category="view"
                ),
                Suggestion(
                    command=f"mycli export {results[0]['id']} --format json" if results else "mycli search 'other'",
                    description="Export as JSON" if results else "Try another search",
                    category="export" if results else "search"
                ),
            ]
        )
        output.success(result)

    except Exception as e:
        result = CommandResult(
            success=False,
            message="Search failed",
            error=ErrorDetail(
                what_went_wrong=str(e),
                how_to_fix=[
                    "Check your query syntax",
                    "Verify your authentication",
                ],
                whats_next=[
                    Suggestion(command="mycli auth test", description="Test authentication", category="auth"),
                ]
            )
        )
        output.error(result)


if __name__ == "__main__":
    cli()
python
"""Main CLI entry point."""

import click
from rich.console import Console

from .models.responses import CommandResult, Suggestion, ErrorDetail
from .output.conversational import ConversationalOutput

console = Console()
output = ConversationalOutput(console)


@click.group()
@click.version_option()
def cli():
    """My CLI tool - AI-friendly command interface.

    Examples:
        mycli search "query"
        mycli show RESOURCE_ID
        mycli export RESOURCE_ID --format json
    """
    pass


@cli.command(epilog="""
Examples:
    mycli search "machine learning"
    mycli search "climate" --limit 5
""")
@click.argument("query")
@click.option("--limit", default=10, help="Maximum results to return")
def search(query: str, limit: int):
    """Search for resources matching a query."""
    try:
        # Your search logic here
        results = []  # fetch_results(query, limit)

        result = CommandResult(
            success=True,
            message=f"Found {len(results)} results for '{query}'",
            context={
                "Query": query,
                "Total results": len(results),
            },
            data=results,
            suggestions=[
                Suggestion(
                    command=f"mycli show {results[0]['id']}" if results else "mycli list",
                    description="View details" if results else "List all resources",
                    category="view"
                ),
                Suggestion(
                    command=f"mycli export {results[0]['id']} --format json" if results else "mycli search 'other'",
                    description="Export as JSON" if results else "Try another search",
                    category="export" if results else "search"
                ),
            ]
        )
        output.success(result)

    except Exception as e:
        result = CommandResult(
            success=False,
            message="Search failed",
            error=ErrorDetail(
                what_went_wrong=str(e),
                how_to_fix=[
                    "Check your query syntax",
                    "Verify your authentication",
                ],
                whats_next=[
                    Suggestion(command="mycli auth test", description="Test authentication", category="auth"),
                ]
            )
        )
        output.error(result)


if __name__ == "__main__":
    cli()

pyproject.toml Template

pyproject.toml模板

toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "my-cli"
version = "0.1.0"
description = "AI-friendly CLI tool"
requires-python = ">=3.8"
dependencies = [
    "click>=8.1.0",
    "rich>=13.0.0",
    "pydantic>=2.0.0",
    "python-dotenv>=1.0.0",
]

[project.scripts]
mycli = "my_cli.main:cli"

[tool.hatch.build.targets.wheel]
packages = ["src/my_cli"]
toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "my-cli"
version = "0.1.0"
description = "AI-friendly CLI tool"
requires-python = ">=3.8"
dependencies = [
    "click>=8.1.0",
    "rich>=13.0.0",
    "pydantic>=2.0.0",
    "python-dotenv>=1.0.0",
]

[project.scripts]
mycli = "my_cli.main:cli"

[tool.hatch.build.targets.wheel]
packages = ["src/my_cli"]

Reference Implementation

参考实现

See the
coda-cli
project for a complete working example:
  • Location:
    .claude/skills/coda/scripts/coda-cli/
  • Key files:
    • src/coda_cli/output/conversational.py
      - Full output implementation
    • src/coda_cli/models/responses.py
      - Complete response models
    • pyproject.toml
      - Project configuration
查看
coda-cli
项目获取完整的工作示例:
  • 位置:
    .claude/skills/coda/scripts/coda-cli/
  • 关键文件:
    • src/coda_cli/output/conversational.py
      - 完整的输出实现
    • src/coda_cli/models/responses.py
      - 完整的响应模型
    • pyproject.toml
      - 项目配置

Checklist for New CLIs

新CLI检查清单

  • Every success output includes suggested next commands
  • Every error includes: what went wrong, how to fix, what's next
  • All commands have
    epilog
    with usage examples
  • Response models use Pydantic for validation
  • Rich is used for formatted terminal output
  • Context includes resource IDs for follow-up commands
  • Table output is limited to prevent overwhelming agents
  • 所有成功输出都包含下一步命令建议
  • 所有错误输出都包含:问题所在、修复方法、下一步操作
  • 所有命令都带有包含使用示例的
    epilog
  • 响应模型使用Pydantic进行验证
  • 使用Rich实现格式化的终端输出
  • 上下文包含用于后续命令的资源ID
  • 表格输出做了限制,避免信息过载影响智能体