Иконка подарка

Весенняя распродажа! Скидка 15% по промокоду

до 01.04.2026

Создание и использование CLI инструментов cli tools

27 марта 2026
Автор

Олег Марков

Введение

CLI инструменты (cli-tools, command line interface tools) — это программы, которые вы запускаете из командной строки и управляете ими через текстовые команды и параметры. Вы постоянно встречаете их в работе: git, docker, kubectl, go, npm, make, curl и десятки других утилит.

CLI-инструменты важны по нескольким причинам:

  • позволяют автоматизировать однотипные действия;
  • удобно интегрируются в CI/CD;
  • не требуют сложного интерфейса и легко поддерживаются;
  • хорошо комбинируются между собой через пайпы и скрипты.

В этой статье вы разберетесь, как устроены CLI-инструменты, какие возможности они обычно предоставляют, как проектировать свой CLI так, чтобы им было удобно пользоваться, и какие есть практические подходы к реализации на примере нескольких технологий.

Давайте шаг за шагом посмотрим, как создать осмысленный, удобный и расширяемый CLI-инструмент.

Что делает CLI инструмент удобным

Основные свойства хорошего CLI

Удобный CLI-инструмент обычно:

  1. Предсказуемо ведет себя при одинаковых входных данных.
  2. Имеет понятные команды и флаги.
  3. Выводит внятные сообщения об ошибках.
  4. Поддерживает --help и документацию.
  5. Легко встраивается в скрипты (читаемый stdout, коды возврата).
  6. Имеет стабильный интерфейс, чтобы не ломать существующие сценарии.

Смотрите, я покажу вам на простом примере. Представим утилиту imgtool для работы с изображениями:

  • imgtool resize --width 800 input.jpg -o output.jpg
  • imgtool info input.jpg
  • imgtool convert input.png --format jpeg -o output.jpg

Команды (resize, info, convert) читаются как действия, а флаги (--width, --format, -o) уточняют поведение. Такой стиль хорошо воспринимается и пользователем, и в скриптах.

Структура типичного CLI

Обычно CLI-инструмент работает по следующей схеме:

  1. Разбор аргументов командной строки.
  2. Валидация значений и проверка совместимости флагов.
  3. Выполнение основной логики.
  4. Формирование результата:
    • полезные данные в stdout;
    • ошибки и подсказки в stderr;
    • код возврата (0 — успех, не 0 — ошибка).

Давайте разберемся подробно, как все это организовать.

Проектирование интерфейса CLI

Команды и подкоманды

У многих CLI есть подкоманды. Например:

  • git commit
  • docker run
  • kubectl apply

Такой подход помогает разделять ответственность. Вместо одной огромной команды с десятками флагов вы делите функциональность на группы.

Вы можете спроектировать свое cli-tool примерно так:

  • app init — инициализация проекта;
  • app build — сборка;
  • app deploy — деплой;
  • app config set — установка конфигурации;
  • app config get — чтение конфигурации.

Обратите внимание, как подкоманда config сама имеет подкоманды set и get. Это делает интерфейс логичным и предсказуемым.

Именование флагов

Рекомендации по флагам:

  • короткие: -v, -o, -f — удобно набирать часто;
  • длинные: --verbose, --output, --force — хорошо читаются и понятны.

Частая схема:

  • -v для --verbose;
  • -h для --help;
  • -o для --output.

Некоторые флаги логические (включают режим), например:

  • --force — игнорировать предупреждения;
  • --dry-run — ничего не менять, только показать план.

Другие принимают значения:

  • --config path/to/file.yaml;
  • --format json;
  • --timeout 30s.

Вход и выход: stdout, stderr, exit codes

Чтобы ваш CLI хорошо жил в экосистеме UNIX-подобных систем, важно:

  • отправлять основной результат в stdout;
  • отправлять ошибки и служебную информацию в stderr;
  • использовать коды возврата.

Пример хорошего поведения:

  • 0 — успех;
  • 1 — общая ошибка;
  • 2 — ошибка валидации аргументов;
  • 3+ — другие типы ошибок по вашему выбору.

Теперь вы увидите, как это выглядит в коде на примере псевдокода:

#!/usr/bin/env bash

input="$1"              # Аргумент, который передали первым
if [ -z "$input" ]; then
  echo "Ошибка - не указан входной файл" >&2  # Сообщение в stderr
  exit 2                                      # Код ошибки валидации
fi

# Здесь мы делаем какую-то обработку
echo "Результат обработки файла $input"       # Выводим результат в stdout
exit 0                                        # Успех

Такой подход позволяет использовать ваш инструмент в пайпах:

toolA input.txt | toolB --format json | toolC > final.txt

Разбор аргументов: базовые подходы

Теперь давайте перейдем к конкретным способам реализации. Я покажу вам несколько распространенных подходов: на чистом языке, через стандартные библиотеки и с помощью специализированных фреймворков.

CLI на bash: минимум зависимостей

Для очень простых cli-tools часто используют shell-скрипты. Например, вспомогательная утилита для разработки.

Пример скрипта с разбором флагов:

#!/usr/bin/env bash

# Здесь мы включаем "строгий" режим bash
set -euo pipefail

show_help() {
  # Здесь мы выводим краткую справку по использованию
  echo "Usage: devtool [command] [options]"
  echo
  echo "Commands:"
  echo "  build          Собрать проект"
  echo "  test           Запустить тесты"
  echo
  echo "Options:"
  echo "  -h, --help     Показать эту справку"
}

command="${1:-}"    # Здесь берем первую позиционную переменную, если нет — пустая строка

case "$command" in
  build)
    # Здесь мы вызываем команду сборки проекта
    echo "Сборка проекта..."
    npm run build
    ;;
  test)
    # Здесь мы запускаем тесты
    echo "Запуск тестов..."
    npm test
    ;;
  -h|--help|"")
    # Здесь мы показываем справку, если команда не указана или запрошен help
    show_help
    ;;
  *)
    # Здесь мы обрабатываем неизвестную команду
    echo "Неизвестная команда $command" >&2
    show_help
    exit 1
    ;;
esac

Скрипты хорошо подходят:

  • для внутренних инструментов команды;
  • для оберток над уже существующими утилитами;
  • когда нет необходимости в сложной логике.

Но по мере роста функциональности удобнее перейти к полноценному языку (Go, Python, Node.js и др.) и библиотекам для CLI.

CLI на Go: пример с библиотекой flag

Go часто выбирают для cli-tools из-за:

  • простого деплоя (один бинарник);
  • скорости запуска;
  • статической типизации;
  • богатой экосистемы.

Сначала давайте посмотрим на стандартную библиотеку flag. Она подходит для простых утилит без подкоманд.

package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {
    // Здесь мы объявляем флаги
    var verbose bool
    var output string

    flag.BoolVar(&verbose, "verbose", false, "включить подробный вывод")
    flag.BoolVar(&verbose, "v", false, "включить подробный вывод (короткий флаг)")
    flag.StringVar(&output, "output", "result.txt", "путь к файлу вывода")
    flag.StringVar(&output, "o", "result.txt", "путь к файлу вывода (короткий флаг)")

    // Здесь мы разбираем аргументы командной строки
    flag.Parse()

    // Здесь мы получаем оставшиеся позиционные аргументы
    args := flag.Args()
    if len(args) == 0 {
        // Если не передали обязательный аргумент, показываем ошибку
        fmt.Fprintln(os.Stderr, "ошибка - не указан входной файл")
        os.Exit(2)
    }
    input := args[0]

    if verbose {
        // Здесь мы выводим отладочную информацию, если включен verbose
        fmt.Fprintf(os.Stderr, "обработка файла %s, вывод в %s\n", input, output)
    }

    // Здесь могла бы быть основная логика обработки файла
    result := "обработанные данные"

    // Здесь мы записываем результат в файл
    err := os.WriteFile(output, []byte(result), 0644)
    if err != nil {
        // Здесь мы выводим ошибку записи файла
        fmt.Fprintf(os.Stderr, "ошибка записи файла - %v\n", err)
        os.Exit(1)
    }

    // Здесь мы завершаем программу с кодом 0 - успех
}

Этот пример показывает базу:

  • объявление флагов;
  • разбор аргументов;
  • обработку ошибок и коды возврата.

Но когда нужны подкоманды (app build, app deploy), стандартной flag становится мало. Тогда используют библиотеки вроде Cobra, urfave/cli и т.п.

CLI на Go с подкомандами: пример на Cobra

Cobra — популярный фреймворк для CLI в Go. Много известных инструментов (например, kubectl) сделано с его помощью.

Давайте разберемся на примере мини-утилиты mytool с командами hello и version.

Структура проекта:

  • cmd/root.go — корневая команда;
  • cmd/hello.go — подкоманда hello;
  • cmd/version.go — подкоманда version;
  • main.go — входная точка.

main.go:

package main

import "mytool/cmd"

func main() {
    // Здесь мы запускаем корневую команду CLI
    cmd.Execute()
}

cmd/root.go:

package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

// Здесь мы создаем корневую команду
var rootCmd = &cobra.Command{
    Use:   "mytool",                     // Имя команды, как её вызывают в терминале
    Short: "Пример CLI на Cobra",       // Краткое описание
    Long:  "Mytool - это пример..." ,   // Более подробное описание
}

// Execute запускает корневую команду
func Execute() {
    // Здесь мы обрабатываем ошибку выполнения команды
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintln(os.Stderr, err) // Выводим ошибку в stderr
        os.Exit(1)                   // Выходим с кодом ошибки
    }
}

func init() {
    // Здесь можно добавить глобальные флаги, общие для всех подкоманд
    // Например - mytool --verbose ...
}

cmd/hello.go:

package cmd

import (
    "fmt"

    "github.com/spf13/cobra"
)

// Здесь мы объявляем переменную для флага
var name string

// Здесь мы создаем подкоманду hello
var helloCmd = &cobra.Command{
    Use:   "hello",
    Short: "Печатает приветствие",
    RunE: func(cmd *cobra.Command, args []string) error {
        // Здесь мы реализуем основную логику команды
        if name == "" {
            // Если имя не указано - используем значение по умолчанию
            name = "мир"
        }
        fmt.Printf("Привет, %s\n", name)
        return nil
    },
}

func init() {
    // Здесь мы добавляем команду hello к корневой команде
    rootCmd.AddCommand(helloCmd)

    // Здесь мы добавляем флаг --name к команде hello
    helloCmd.Flags().StringVarP(&name, "name", "n", "", "имя для приветствия")
}

cmd/version.go:

package cmd

import (
    "fmt"

    "github.com/spf13/cobra"
)

// Здесь мы задаем версию - в реальном проекте её часто подставляют при сборке
var version = "0.1.0"

// Здесь мы создаем команду version
var versionCmd = &cobra.Command{
    Use:   "version",
    Short: "Показывает версию программы",
    RunE: func(cmd *cobra.Command, args []string) error {
        // Здесь мы печатаем версию
        fmt.Println(version)
        return nil
    },
}

func init() {
    // Здесь мы регистрируем команду version
    rootCmd.AddCommand(versionCmd)
}

Как видите, каждая команда реализуется как отдельный объект Cobra. Такой подход:

  • помогает структурировать код;
  • упрощает добавление новых команд;
  • автоматически дает вложенный --help.

Например:

  • mytool --help — общая справка;
  • mytool hello --help — справка по конкретной команде.

CLI на Python: пример с argparse

Python часто используют для CLI-утилит благодаря простоте и богатой стандартной библиотеке. Базовый инструмент — модуль argparse.

Простой пример:

import argparse
import sys

def main():
    # Здесь мы создаем парсер аргументов
    parser = argparse.ArgumentParser(
        description="Пример Python CLI инструмента"
    )

    # Здесь мы добавляем флаг --verbose
    parser.add_argument(
        "-v", "--verbose",
        action="store_true",
        help="включить подробный вывод"
    )

    # Здесь мы добавляем позиционный аргумент input
    parser.add_argument(
        "input",
        help="путь к входному файлу"
    )

    # Здесь мы разбираем аргументы командной строки
    args = parser.parse_args()

    if args.verbose:
        # Здесь мы выводим дополнительную информацию, если включен verbose
        print(f"Обработка файла {args.input}...", file=sys.stderr)

    # Здесь могла бы быть логика обработки файла
    print(f"Результат обработки {args.input}")

if __name__ == "__main__":
    # Здесь мы вызываем основную функцию
    main()

Аргумент --help создается автоматически. Если вы попробуете:

python tool.py --help

вы получите сгенерированную справку.

Подкоманды в argparse

Теперь давайте посмотрим, как сделать подкоманды, например calc add и calc mul.

import argparse

def cmd_add(args):
    # Здесь мы реализуем команду сложения
    result = args.a + args.b
    print(result)

def cmd_mul(args):
    # Здесь мы реализуем команду умножения
    result = args.a * args.b
    print(result)

def main():
    # Здесь мы создаем корневой парсер
    parser = argparse.ArgumentParser(
        prog="calc",
        description="Пример калькулятора с подкомандами"
    )

    # Здесь мы создаем подкоманды
    subparsers = parser.add_subparsers(
        title="команды",
        dest="command"
    )

    # Здесь мы добавляем подкоманду add
    parser_add = subparsers.add_parser("add", help="сложить два числа")
    parser_add.add_argument("a", type=int, help="первое число")
    parser_add.add_argument("b", type=int, help="второе число")
    parser_add.set_defaults(func=cmd_add)

    # Здесь мы добавляем подкоманду mul
    parser_mul = subparsers.add_parser("mul", help="умножить два числа")
    parser_mul.add_argument("a", type=int, help="первое число")
    parser_mul.add_argument("b", type=int, help="второе число")
    parser_mul.set_defaults(func=cmd_mul)

    # Здесь мы разбираем аргументы
    args = parser.parse_args()

    if not hasattr(args, "func"):
        # Если команда не указана - показываем справку
        parser.print_help()
        return

    # Здесь мы вызываем соответствующую функцию для подкоманды
    args.func(args)

if __name__ == "__main__":
    main()

Здесь я размещаю пример, чтобы вам было проще понять структуру:

  • есть корневой парсер;
  • через add_subparsers создаются подкоманды;
  • каждая подкоманда получает свою функцию-обработчик.

CLI на Node.js: пример с Commander

Для JavaScript / Node.js одна из популярных библиотек — Commander. Она позволяет довольно быстро собрать удобный CLI.

Пример простого инструмента:

#!/usr/bin/env node

// Здесь мы подключаем Commander
const { Command } = require("commander");
const fs = require("fs");

const program = new Command();

// Здесь мы задаем основную информацию о программе
program
  .name("mycli")
  .description("Пример CLI на Node.js")
  .version("0.1.0");

// Здесь мы добавляем команду read
program
  .command("read")
  .description("прочитать файл и вывести содержимое")
  .argument("<file>", "путь к файлу")
  .option("-u, --uppercase", "вывести текст в верхнем регистре")
  .action((file, options) => {
    // Здесь мы читаем файл
    fs.readFile(file, "utf8", (err, data) => {
      if (err) {
        // Здесь мы выводим ошибку, если файл не прочитан
        console.error("Ошибка чтения файла", err.message);
        process.exit(1);
      }

      // Здесь мы применяем опцию --uppercase
      if (options.uppercase) {
        data = data.toUpperCase();
      }

      // Здесь мы печатаем результат
      console.log(data);
    });
  });

// Здесь мы разбираем аргументы и запускаем нужную команду
program.parse(process.argv);

Чтобы этот файл можно было запускать как команду, вы:

  1. Добавляете в начало шебанг #!/usr/bin/env node.
  2. Даете ему права на исполнение.
  3. Устанавливаете как глобальный пакет через npm или pnpm.

Например, через npm:

npm init -y
npm install commander
# здесь вы настраиваете в package.json секцию "bin"
npm link

Теперь команда mycli будет доступна в системе как глобальный CLI-инструмент.

Работа с конфигурацией

CLI-инструменты часто получают настройки не только через флаги, но и через:

  • конфигурационные файлы;
  • переменные окружения.

Такой подход удобен, когда:

  • флагов становится слишком много;
  • вам нужно переиспользовать одинаковые настройки.

Приоритеты конфигураций

Частая схема:

  1. Значения по умолчанию.
  2. Конфигурационный файл (например, ~/.toolrc или tool.yaml в проекте).
  3. Переменные окружения (TOOL_TOKEN, TOOL_URL).
  4. Параметры командной строки (флаги).

Ближе к пользователю — выше приоритет. Например, флаг --url должен переопределить значение из конфигурационного файла.

Пример на Python — читаем конфиг и переопределяем его флагами:

import argparse
import json
import os

def load_config(path):
    # Здесь мы пытаемся прочитать конфигурационный файл
    if not os.path.exists(path):
        return {}
    with open(path, "r", encoding="utf-8") as f:
        return json.load(f)

def main():
    # Здесь мы читаем конфиг по умолчанию
    default_config_path = os.path.expanduser("~/.mytool.json")
    config = load_config(default_config_path)

    # Здесь мы создаем парсер
    parser = argparse.ArgumentParser(description="Пример с конфигом")

    # Здесь мы добавляем флаг --url с значением по умолчанию из конфига
    parser.add_argument(
        "--url",
        default=config.get("url", "https://api.example.com"),
        help="URL сервиса"
    )

    args = parser.parse_args()

    # Здесь мы используем итоговый URL, который возможен из конфига или флага
    print(f"Используем URL - {args.url}")

if __name__ == "__main__":
    main()

Давайте посмотрим, что происходит в этом примере:

  • если конфиг не найден — берем дефолтное значение;
  • если конфиг есть — берем значение из него;
  • если задан флаг --url — он переопределит конфиг.

Логирование и уровни подробности

Многие CLI имеют флаг --verbose или -v, иногда несколько уровней: -v, -vv, -vvv.

Задача:

  • не загромождать стандартный вывод обычному пользователю;
  • давать достаточно информации для отладки, когда это нужно.

Пример на Go — несколько уровней подробности:

package main

import (
    "flag"
    "fmt"
    "os"
)

var verbose int

func debug(level int, format string, args ...interface{}) {
    // Здесь мы печатаем сообщение, только если текущий уровень verbose
    // не меньше уровня сообщения
    if verbose >= level {
        msg := fmt.Sprintf(format, args...)
        fmt.Fprintln(os.Stderr, msg)
    }
}

func main() {
    // Здесь мы добавляем флаг -v, который можно указать несколько раз
    flag.CountVar(&verbose, "v", "уровень подробности вывода")
    flag.Parse()

    debug(1, "запуск программы")
    debug(2, "подробная отладочная информация")

    // Здесь основная логика CLI
    fmt.Println("Основной результат")
}

В этом примере:

  • -v — уровень 1;
  • -vv — уровень 2;
  • -vvv — и так далее.

Вы можете выводить менее важные сообщения только при большем уровне.

Тестирование CLI-инструмента

Чтобы CLI уверенно работал, важно его тестировать. Обычно это:

  • юнит-тесты основных функций;
  • интеграционные тесты — запуск программы как бинарника и проверка вывода и кода выхода.

Интеграционный тест на Go

Здесь я покажу вам пример через os/exec:

package main_test

import (
    "os/exec"
    "testing"
)

func TestMyToolHelp(t *testing.T) {
    // Здесь мы готовим команду для запуска
    cmd := exec.Command("mytool", "--help")

    // Здесь мы собираем вывод
    output, err := cmd.CombinedOutput()
    if err != nil {
        t.Fatalf("ожидался код возврата 0, получили ошибку - %v", err)
    }

    // Здесь мы проверяем, что в выводе есть ключевая строка
    if !contains(string(output), "Пример CLI") {
        t.Fatalf("в выводе не найдено описание - %s", output)
    }
}

func contains(str, substr string) bool {
    // Здесь простая функция поиска подстроки
    return len(str) >= len(substr) && (str == substr || len([]rune(str)) >= len([]rune(substr)) && (string([]rune(str)[:len([]rune(substr))]) == substr || contains(string([]rune(str)[1:]), substr)))
}

На практике вы будете использовать более аккуратные вспомогательные функции для проверки строки. Важно, что тест:

  • запускает CLI как внешний процесс;
  • проверяет код возврата;
  • анализирует вывод.

Так вы защищаете инструмент от регрессий.

Распространение и установка CLI

После разработки нужно, чтобы ваш cli-tool было легко установить и использовать.

Один бинарник (Go, Rust и др.)

Для языков, которые собирают статический бинарник, удобно:

  • выкладывать релизы на GitHub Releases;
  • использовать менеджеры версий и установщики (например, brew, scoop, apt, dnf, chocolatey).

Частый паттерн:

  • вы создаете GitHub-репозиторий;
  • добавляете CI, который собирает бинарники под нужные платформы;
  • публикуете их как релизы;
  • пишете в README инструкции:
    • curl-скрипт загрузки;
    • команды для установки через пакетные менеджеры.

Python: pip и консольные скрипты

В Python CLI обычно распространяют как пакет, у которого в setup.cfg или pyproject.toml указаны entry points.

Пример фрагмента pyproject.toml:

[project]
name = "mytool"
version = "0.1.0"

[project.scripts]
mytool = "mytool.__main__:main"

Здесь:

  • mytool — имя команды в консоли;
  • mytool.__main__:main — путь к функции Python, которая запускается.

После установки через pip install . команда mytool появится в PATH.

Node.js: npm global

Мы уже коснулись этого, но уточню схему:

  • в package.json добавляется секция:
{
  "name": "mycli",
  "version": "0.1.0",
  "bin": {
    "mycli": "./bin/mycli.js"
  }
}
  • ставим пакет глобально:
npm install -g .

Теперь mycli доступна как команда.

Удобство использования и UX CLI

CLI — это тоже интерфейс, только текстовый. Есть несколько практик, которые сильно влияют на удобство.

Справка и примеры

Старайтесь:

  • описывать каждый флаг и команду;
  • добавлять раздел с примерами использования.

Например, в выводе --help вы можете сделать:

Примеры:
  mytool hello --name Иван
  mytool version

Это помогает новичкам быстро запустить инструмент, не читая длинную документацию.

Понятные сообщения об ошибках

Сообщения вида "error 42" не дают пользователю почти ничего. Лучше:

  • указать, что именно не так;
  • подсказать, как исправить.

Например:

  • Плохо: Invalid argument
  • Лучше: Неизвестная команда deploy. Проверьте список доступных команд через --help

Безопасность по умолчанию

Если ваш инструмент может:

  • удалять файлы;
  • менять конфигурацию;
  • отправлять запросы в продакшн;

стоит:

  • явно требовать подтверждения (или флаг --force);
  • иметь режим --dry-run (показывать, что будет сделано без применения).

Так вы уменьшите риск случайных ошибок.

Заключение

CLI-инструменты — ключевой элемент рабочего процесса разработчика. Они автоматизируют рутинные операции, связывают разные системы, упрощают деплой и тестирование. Вы посмотрели, как спроектировать понятный и удобный CLI:

  • продумать команды и флаги;
  • аккуратно работать с входом и выходом;
  • обрабатывать ошибки и формировать коды возврата;
  • использовать конфигурацию, окружение и логирование.

Мы прошлись по нескольким практическим примерам на bash, Go, Python и Node.js, чтобы вы могли выбрать подходящий инструмент под свой стек и задачу. Теперь у вас есть база, на которой можно строить как небольшие утилиты для своей команды, так и серьезные cli-tools, которыми будут пользоваться другие разработчики.


Частозадаваемые технические вопросы по теме CLI инструменты

Как сделать автодополнение команд и флагов для моего CLI

Для популярных фреймворков (например, Cobra в Go) есть встроенная генерация скриптов автодополнения для bash, zsh, fish. Инструкция:

  1. Посмотрите в документации библиотеки раздел про completion.
  2. Сгенерируйте скрипт, например mytool completion bash > /etc/bash_completion.d/mytool.
  3. Перезапустите терминал или загрузите файл source /etc/bash_completion.d/mytool. Для Python и Node.js существуют отдельные пакеты, которые помогают генерировать подобные скрипты или интегрировать с существующими системами автодополнения.

Как безопасно передавать секреты в CLI инструменте

Рекомендуется не передавать пароли и токены через аргументы командной строки, так как они могут попасть в историю shell и быть видимыми через ps. Лучше:

  1. Использовать переменные окружения MYTOOL_TOKEN.
  2. Или читать секреты из защищенного файла (права 600) в домашней директории.
  3. Или запрашивать ввод через stdin с маскировкой (например, getpass в Python).

Как сделать, чтобы мой CLI работал кроссплатформенно

Основные шаги:

  1. Не полагаться на специфичные команды ОС (ls, grep) без проверки.
  2. Избегать жестко прописанных путей вроде /tmp или C:\Temp — использовать функции стандартной библиотеки для временных каталогов и домашней директории.
  3. Тестировать CLI минимум на Linux, macOS и Windows (включая PowerShell).
  4. Для бинарников на Go или Rust собирать их для разных платформ и архитектур через систему сборки или CI.

Как обрабатывать очень длинный вывод так, чтобы его было удобно читать

Несколько подходов:

  1. Добавить флаг --format и поддержать варианты text, json, yaml.
  2. Для человека по умолчанию выводить человекочитаемый текст, для скриптов — JSON.
  3. Добавить флаг --pager, который отправляет вывод в less или аналог, если пользователь этого хочет.
  4. При большом объеме данных добавлять заголовки, разделители и краткую сводку в конце.

Как версионировать CLI и не ломать старые скрипты пользователей

Основные практики:

  1. Явно указывать версию в выводе --version.
  2. Избегать изменения поведения без изменения мажорной версии.
  3. Для несовместимых изменений:
    • повышать мажорную версию;
    • документировать изменения в CHANGELOG;
    • по возможности оставлять временные алиасы старого поведения с предупреждениями.
  4. Если требуется резкий переход, можно поддерживать несколько бинарников, например tool и tool2, в течение переходного периода.
Стрелочка влевоВизуализатор зависимостей dependency-graph в GitHub Actions

Все гайды по Feature-sliced_design

Открыть базу знаний

Отправить комментарий