CLI Tool Development Guide

Development standards for CLI tools based on WinClaw CLI Tool Market, covering progressive disclosure, daemon mode, session management and more.

This guide summarizes the development standards and best practices for building AI Agent-oriented CLI tools, based on real implementations in the WinClaw CLI Tool Market. All tools follow a unified architecture to ensure composability and a consistent experience.

Three Tool Types

CLI tools are categorized into three types based on their runtime characteristics and state requirements:

TypeCharacteristicsTypical ScenariosExample Tools
Plain CLIStateless, execute and exitSend messages, export data, file conversionagent_feishu, agent_excel, markdown2word
Daemon CLIBackground persistent, maintains external connectionsMessage agents, scheduled tasks, long-connection serviceswechat_agent, agent_cron
Session CLIMaintains context across invocationsMulti-turn conversations, incremental operationsagent_cursor

Which type to choose depends on whether your tool needs to run persistently in the background or remember state across invocations. Most tools are Plain CLI — simple, side-effect-free, and easy to compose.

Common Standards

The following standards apply to all three types of CLI tools.

Project Structure

Each CLI tool follows this standard directory layout:

my_tool/
├── main.go
├── go.mod
├── go.sum
├── cmd/
│   ├── root.go          # Root command, --help / --skill
│   ├── skill.go         # skill subcommand & skillSpec constant
│   ├── version.go       # Version info
│   └── <command>.go     # Subcommand implementations
└── internal/
    ├── output/          # Unified JSON output
    │   └── output.go
    ├── session/         # Session management (for Session CLI)
    │   └── session.go
    └── daemon/          # Daemon process (for Daemon CLI)
        ├── manager.go
        └── server.go

Authentication and Sensitive Config Handling (Account/Password/API Key)

If your tool needs sensitive parameters such as account credentials, tokens, or API keys, do not require users to type them in every command. Use config-file-based injection instead.

Based on execute_external_tool_resolver.py, the runtime resolves configuration in this order:

  1. <tool_basename>.key in the tool directory
  2. <tool_filename>.key in the tool directory (legacy compatibility)
  3. ~/.<tool_basename>/config.key
  4. ~/.<tool_basename>/config.json

In practice, #3 and #4 are the most user-friendly: create ~/.<name>/ in the user's home directory and place config.key or config.json there.

~/.<name>/
├── config.key
└── config.json

Use either config.key or config.json. If both exist, config.key is preferred.

config.key Example (YAML)

global:
  group-id: "1764789583133814932"
  api-key: "your_api_key"

config.json Example (JSON)

{
  "global": {
    "group-id": "1764789583133814932",
    "api-key": "your_api_key"
  }
}

Advanced: Per-subcommand Parameter Injection

You can use subcommands + _params to inject different credentials per subcommand path. It also supports:

  • param_style: "space" (default, generates --key value)
  • param_style: "equals" (generates --key=value)
  • param_prefix (default --, can be changed to -)

Notes

  • Help commands (--help / -h / help / --skill) do not inject sensitive params.
  • Sensitive values are masked in displayed command logs.

Progressive Disclosure: --help and --skill

Progressive disclosure is the core design pattern of this tool ecosystem. Users (or AI Agents) can access information at different depths on demand.

Three-Layer Information Architecture

Layer 1: tool --help         → Feature overview, command list, quick start
Layer 2: tool <cmd> --help   → Parameter details and usage for a specific subcommand
Layer 3: tool --skill        → Complete best practices, typical scenarios, workflow guides

--help: Functionality and Parameters

--help answers "what can this tool do" and "how to use the parameters".

Root command --help should include:

  • Brief description of the tool's capabilities
  • List of available commands with short descriptions
  • Quick start examples
  • Guidance to use --skill for more information

Example using Cobra framework for agent_feishu:

rootCmd = &cobra.Command{
    Use:   "agent_feishu",
    Short: "A CLI tool for Feishu bot operations, designed for AI agents",
    Long: `agent_feishu is a command-line tool that provides Feishu (Lark) bot operations
through a set of composable commands, designed for AI agent workflows.

Key Features:
  - Send text, image, and file messages to Feishu chats
  - User management with local caching for fast lookups

Quick Start:
  agent_feishu initapi --app-id "cli_xxx" --app-secret "xxx"
  agent_feishu send-text --receive-id "ou_xxx" --receive-type "open_id" --text "Hello!"

Use 'agent_feishu --skill' or 'agent_feishu skill' for detailed command specifications.

For more information about a specific command, use:
  agent_feishu [command] --help`,
    Version: version,
    Run: func(cmd *cobra.Command, args []string) {
        if showSkill {
            fmt.Print(skillSpec)
            return
        }
        cmd.Help()
    },
}

Subcommand --help should include:

  • What the subcommand does
  • All parameters (required/optional) with detailed descriptions
  • Usage examples
  • Guidance to deeper subcommand --help
var tableCmd = &cobra.Command{
    Use:   "table",
    Short: "Table operations (add, delete)",
    Long: `Manage tables in a Feishu Bitable app.

Use 'agent_bitable table <subcommand> --help' for more information.`,
}

Progressive disclosure path for multi-level subcommands:

agent_bitable --help              → All top-level commands overview
agent_bitable record --help       → record command group overview
agent_bitable record list --help  → list subcommand parameter details
agent_bitable --skill             → Complete workflows and scenario guide

--skill: Best Practices and Workflows

--skill answers "how should I use this tool to accomplish a specific task". This is the core interface for AI Agents, providing complete operational guidance.

--skill should include:

  1. Overview — Tool positioning and core capabilities
  2. Prerequisites — Authentication, environment variables, dependencies
  3. Best Practice Workflows — Recommended step-by-step procedures
  4. Typical Scenarios — Complete operation paths for different goals
  5. Command Reference — Detailed parameters for all commands
  6. Output Format — JSON structure documentation
  7. Error Handling — Common errors and resolution strategies
  8. Tool Composition — How to combine with other agent_* tools

--skill is provided through two methods for convenient access:

Method 1: Root command --skill flag

func init() {
    rootCmd.Flags().BoolVar(&showSkill, "skill", false,
        "Show the detailed command specification for agent_feishu")
}

Method 2: skill subcommand

var skillCmd = &cobra.Command{
    Use:   "skill",
    Short: "Show detailed command specification",
    Long:  `Display the complete skill specification, designed for AI agents.`,
    Run: func(cmd *cobra.Command, args []string) {
        cmd.Print(skillSpec)
    },
}

func init() {
    rootCmd.AddCommand(skillCmd)
}

Both methods output the same skillSpec constant:

const skillSpec = `# agent_feishu Skill Specification

A CLI tool for Feishu bot operations, designed for AI agent workflows.

================================================================================
AI Agent Important Notes
================================================================================

First-time Setup:
  Before using any other commands, you MUST initialize API credentials:
  agent_feishu initapi --app-id "cli_xxx" --app-secret "xxx"

User Management Workflow:
  1. Search local cache first (fast): agent_feishu search-users --query "name"
  2. If not found, sync from Feishu: agent_feishu sync-users --force
  3. Retry search after sync

================================================================================
Available Commands
================================================================================
...

================================================================================
Common Scenarios
================================================================================
...
`

Tools not using Cobra can parse --skill manually:

func Execute() {
    args := os.Args[1:]
    if len(args) == 0 {
        printUsage(os.Stdout)
        return
    }

    switch args[0] {
    case "help", "-h", "--help":
        printUsage(os.Stdout)
    case "skill", "--skill":
        fmt.Print(skillSpec)
    case "version", "-v", "--version":
        fmt.Println(version)
    // ... other subcommands
    default:
        fmt.Fprintf(os.Stderr, "Unknown command: %s\n\n", args[0])
        printUsage(os.Stderr)
        os.Exit(1)
    }
}

Unified JSON Output

All tools use a unified JSON output format for composability and AI parsability.

Output Structure

type Result struct {
    Success bool        `json:"success"`
    Data    interface{} `json:"data,omitempty"`
    Error   string      `json:"error,omitempty"`
}

Success response:

{
  "success": true,
  "data": {
    "message": "Text message sent successfully",
    "message_id": "om_xxx"
  }
}

Error response:

{
  "success": false,
  "error": "API credentials not configured. Run 'agent_feishu initapi' first"
}

Global --json Flag

Register a global --json flag via PersistentFlags so all subcommands can output JSON:

func init() {
    rootCmd.PersistentFlags().BoolVar(&jsonOutput, "json", false,
        "Output in JSON format: {success, data, error}")
}

Output Utility Functions

Encapsulate output logic in internal/output/:

func Print(jsonMode bool, data interface{}, err error) {
    if jsonMode {
        PrintJSON(data, err)
    } else {
        PrintText(data, err)
    }
}

func Success(jsonMode bool, message string) {
    Print(jsonMode, map[string]string{"message": message}, nil)
}

func Error(jsonMode bool, err error) {
    Print(jsonMode, nil, err)
    if !jsonMode {
        os.Exit(1)
    }
}

Tool Composition and Interoperability

Tools compose not through code-level imports, but through these conventions:

Unified output format — All tools use the same {success, data, error} JSON structure, supporting pipeline chaining:

agent_feishu search-users --query "John" --json | jq '.data[0].open_id'

Cross-tool references in skill docs — Document how to work with other tools in --skill output:

agent_cursor wraps Cursor CLI's print mode (agent -p) to provide:
  - Structured JSON output compatible with agent_excel and other tools

Command guidance in error messages — When an operation fails, suggest the relevant fix command:

output.Error(cmd, fmt.Errorf(
    "API credentials not configured. Run 'agent_feishu initapi' first"))

How to Choose a Tool Type

Does your tool need to run persistently in the background?
  ├── Yes → Maintain external connections / scheduled execution / shared state?
  │         └── Yes → Daemon CLI
  └── No  → Does it need to maintain context across invocations?
            ├── Yes → Session CLI
            └── No  → Plain CLI ✓ (most cases)

Prefer Plain CLI — it's the simplest, easiest to test, and easiest to compose. Only consider Daemon or Session when Plain CLI cannot meet your requirements.

Deep Dives

Next Steps