Token导航 LogoToken导航TokenDH.com
前端设计敏感数据github未标认证来源可访问clear审计通过

building-cli-appsbuilding CLI apps 命令行

Agent Skill

building-cli-apps 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

759

周安装

31

GitHub Stars

2

下载量

243
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

3

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:building-cli-apps(building CLI apps 命令行)
来源仓库:https://github.com/mhagrelius/dotfiles
仓库路径:skills/building-cli-apps
安装命令:
npx skills add https://github.com/mhagrelius/dotfiles --skill building-cli-apps
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/mhagrelius/dotfiles --skill building-cli-apps

简介

building-cli-apps 阐述 CLI 应用作为管道过滤器的设计理念与实现原则。

  • 对比 CLI/TUI/GUI 适用场景,强调单一职责与可组合性核心价值。
  • 提供参数解析、配置管理与 Shell 补全等企业级 CLI 功能实现指南。
  • 遵循 Unix 哲学,追求简洁高效,避免过度复杂化交互逻辑。
  • 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

SKILL.md

Building CLI Applications

Overview

CLI apps are filters in a pipeline. They read input, transform it, write output. The Unix philosophy applies: do one thing well, compose with others.

When to Use CLI vs TUI vs GUI

digraph decision {
    rankdir=TB;
    "User interaction needed?" [shape=diamond];
    "Complex state/navigation?" [shape=diamond];
    "Scriptable/automatable?" [shape=diamond];
    "CLI" [shape=box, style=filled, fillcolor=lightblue];
    "TUI" [shape=box, style=filled, fillcolor=lightgreen];
    "GUI" [shape=box, style=filled, fillcolor=lightyellow];

    "User interaction needed?" -> "Complex state/navigation?" [label="yes"];
    "User interaction needed?" -> "Scriptable/automatable?" [label="no"];
    "Scriptable/automatable?" -> "CLI" [label="yes"];
    "Scriptable/automatable?" -> "GUI" [label="no"];
    "Complex state/navigation?" -> "TUI" [label="yes"];
    "Complex state/navigation?" -> "CLI" [label="no"];
}

Choose CLI when: Single operation, pipeable, scriptable, CI/CD, simple prompts Choose TUI when: Dashboard, multi-view navigation, real-time monitoring Choose GUI when: Non-technical users, complex visualizations, drag/drop

Quick Reference: Libraries by Language

LanguageArgument ParsingProgress/SpinnersColorsPrompts
Pythontyper (modern) or clickrich.progressrichrich.prompt
TypeScriptcommander or yargsorachalkinquirer
C#System.CommandLineSpectre.ConsoleSpectre.ConsoleSpectre.Console

Core Patterns

1. Streams: stdout vs stderr

stdout → Data/results (pipeable)
stderr → Progress, logs, errors (human feedback)

Python:

import sys
from rich.console import Console

console = Console(stderr=True)  # Progress/logs to stderr
output = Console()              # Results to stdout

console.print("[dim]Processing...[/]")  # → stderr
output.print_json(data=result)          # → stdout (pipeable)

TypeScript:

// Results to stdout
console.log(JSON.stringify(result));

// Progress to stderr
process.stderr.write('Processing...\n');

C#:

Console.WriteLine(result);           // stdout
Console.Error.WriteLine("Working..."); // stderr

2. Exit Codes

CodeMeaningUse When
0SuccessOperation completed
1General errorUser/input errors
2MisuseInvalid arguments
130SIGINTCtrl+C interrupted
# Python
import sys
sys.exit(0)  # Success
sys.exit(1)  # Error
// TypeScript
process.exit(0);
process.exitCode = 1;  // Preferred - allows cleanup
// C#
Environment.Exit(0);
return 1;  // From Main

3. Configuration Hierarchy

Precedence (highest to lowest):

  1. CLI arguments (--config value)
  2. Environment variables (APP_CONFIG)
  3. Config file (.apprc, config.json)
  4. Defaults
# Python with typer
import typer
import os

def main(
    config: str = typer.Option(
        os.environ.get("APP_CONFIG", "default"),
        "--config", "-c"
    )
):
    pass

4. Subcommand Structure

mycli/
├── src/
│   ├── main.py          # Entry point, registers commands
│   ├── commands/
│   │   ├── __init__.py
│   │   ├── process.py   # mycli process <file>
│   │   └── config.py    # mycli config show|set
│   └── lib/             # Shared logic
└── tests/
    └── commands/
        └── test_process.py

Python with typer:

# main.py
import typer
from commands import process, config

app = typer.Typer()
app.add_typer(process.app, name="process")
app.add_typer(config.app, name="config")

if __name__ == "__main__":
    app()

TypeScript with commander:

// index.ts
import { Command } from 'commander';
import { processCommand } from './commands/process';
import { configCommand } from './commands/config';

const program = new Command();
program.addCommand(processCommand);
program.addCommand(configCommand);
program.parse();

C# with System.CommandLine:

var rootCommand = new RootCommand("My CLI");
rootCommand.AddCommand(ProcessCommand.Create());
rootCommand.AddCommand(ConfigCommand.Create());
await rootCommand.InvokeAsync(args);

5. Interactive vs Non-Interactive Mode

import sys
import typer
from rich.prompt import Confirm

def main(
    force: bool = typer.Option(False, "--force", "-f"),
    file: str = typer.Argument(...)
):
    # Check if running interactively
    is_interactive = sys.stdin.isatty()

    if not force and is_interactive:
        if not Confirm.ask(f"Delete {file}?"):
            raise typer.Abort()
    elif not force and not is_interactive:
        # Non-interactive without --force: fail safe
        typer.echo("Use --force in non-interactive mode", err=True)
        raise typer.Exit(1)

    # Proceed with operation
    delete_file(file)

6. Reading from stdin (Piped Input)

Support both file arguments and piped input (- convention):

import sys
import typer

@app.command()
def process(
    file: str = typer.Argument(..., help="Input file (or - for stdin)")
):
    if file == "-":
        content = sys.stdin.read()
    else:
        content = Path(file).read_text()
    # Process content...
import { createInterface } from 'readline';

async function readInput(file: string): Promise<string> {
    if (file === '-') {
        const lines: string[] = [];
        const rl = createInterface({ input: process.stdin });
        for await (const line of rl) lines.push(line);
        return lines.join('\n');
    }
    return fs.readFileSync(file, 'utf-8');
}

Usage: cat data.txt | mycli process - or echo "test" | mycli process -

7. Signal Handling

import signal
import sys

def handle_sigint(signum, frame):
    print("\nInterrupted, cleaning up...", file=sys.stderr)
    cleanup()
    sys.exit(130)

signal.signal(signal.SIGINT, handle_sigint)
process.on('SIGINT', () => {
    console.error('\nInterrupted, cleaning up...');
    cleanup();
    process.exit(130);
});
Console.CancelKeyPress += (sender, e) => {
    e.Cancel = true;  // Prevent immediate termination
    Console.Error.WriteLine("\nInterrupted, cleaning up...");
    Cleanup();
    Environment.Exit(130);
};

Anti-Patterns

Anti-PatternProblemFix
Progress to stdoutBreaks pipingUse stderr
Silent failuresUser doesn't know what failedPrint error + exit non-zero
No --helpUnusableUse typer/commander (auto-generates)
Hardcoded pathsNot portableUse env vars or config
No exit codesScripts can't check successExit 0/1 appropriately
Require confirmation in pipesHangs automationCheck isatty(), use --force
Catching all exceptionsHides bugsCatch specific, let others crash

Testing CLI Apps

Python with pytest:

from typer.testing import CliRunner
from myapp.main import app

runner = CliRunner()

def test_process_success():
    result = runner.invoke(app, ["process", "test.txt"])
    assert result.exit_code == 0
    assert "processed" in result.stdout

def test_process_missing_file():
    result = runner.invoke(app, ["process", "nonexistent.txt"])
    assert result.exit_code == 1
    assert "not found" in result.stderr

def test_piped_input(tmp_path):
    input_file = tmp_path / "input.txt"
    input_file.write_text("test data")
    result = runner.invoke(app, ["process", "-"], input="test data")
    assert result.exit_code == 0

TypeScript with Jest:

import { execSync } from 'child_process';

test('process command succeeds', () => {
    const result = execSync('npx ts-node src/index.ts process test.txt');
    expect(result.toString()).toContain('processed');
});

test('process command fails on missing file', () => {
    expect(() => {
        execSync('npx ts-node src/index.ts process nonexistent.txt');
    }).toThrow();
});

Help Text Best Practices

import typer

app = typer.Typer(
    help="Process loan notices with AI classification.",
    no_args_is_help=True,  # Show help if no args
)

@app.command()
def process(
    file: str = typer.Argument(..., help="Path to notice file (or - for stdin)"),
    output: str = typer.Option(None, "--output", "-o", help="Output file (default: stdout)"),
    format: str = typer.Option("json", "--format", "-f", help="Output format: json, csv, table"),
    verbose: bool = typer.Option(False, "--verbose", "-v", help="Show processing details"),
):
    """
    Process a loan notice through the classification pipeline.

    Examples:
        mycli process notice.pdf
        mycli process notice.pdf --format table
        cat notice.txt | mycli process - --output result.json
    """
    pass

Error Messages

Good error messages include:

  1. What went wrong
  2. Why it's a problem
  3. How to fix it
# Bad
print("Error: invalid input")
sys.exit(1)

# Good
print(f"Error: File '{path}' is not a valid PDF.", file=sys.stderr)
print(f"Expected: PDF file with loan notice content", file=sys.stderr)
print(f"Try: mycli process --help for supported formats", file=sys.stderr)
sys.exit(1)

Distribution

LanguageMethodCommand
PythonPyPIpip install myapp or pipx install myapp
PythonSingle filepyinstaller --onefile main.py
TypeScriptnpmnpm install -g myapp
TypeScriptBinarypkg. or bun build --compile
C#NuGet tooldotnet tool install -g myapp
C#Single filedotnet publish -c Release -p:PublishSingleFile=true

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

04

需要参考平台分布和安装热度时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Antigravity

31.59%
按下载量换算77

Claude Code

21.26%
按下载量换算52

trae

19.04%
按下载量换算46

OpenCode

13.78%
按下载量换算33

Gemini CLI

8.15%
按下载量换算20

windsurf

3.88%
按下载量换算9

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

来源信息

继续浏览同类 Skills