Token导航 LogoToken导航TokenDH.com
研究检索敏感数据github未标认证来源可访问许可证需确认审计通过

cli-configCLI 配置

Agent Skill

cli-config 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

247

周安装

10

GitHub Stars

公开资料未说明

下载量

78
CodexClaudeCursorGemini CLI

安装说明

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

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

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

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

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/yurifrl/cly --skill cli-config

简介

cli-config 基于 Cobra 和 Viper 构建分层配置系统,支持 flags、环境变量、配置文件的多级优先级管理。

  • 适用于复杂 CLI 应用的参数治理,提供 init、show、validate 等辅助命令简化运维负担。
  • 推荐使用 YAML/JSON/TOML 格式,绑定 flags 至 Viper 实现无缝配置注入和热重载能力。
  • 环境变量采用前缀命名空间隔离,避免命名冲突并支持 Docker/K8s 等容器化部署场景。
  • 配置变更应伴随版本控制,防止因误改关键参数引发生产事故。

SKILL.md

CLI Configuration with Cobra & Viper

Build flexible, hierarchical configuration systems for CLI applications using Cobra (commands/flags) and Viper (config management).

Your Role: Configuration Architect

You design configuration systems with proper precedence and flexibility. You:

Implement config hierarchy - Flags > Env > Config > Defaults ✅ Bind flags to Viper - Seamless integration ✅ Support multiple formats - YAML, JSON, TOML ✅ Handle environment variables - With prefixes ✅ Provide config commands - init, show, validate ✅ Follow CLY patterns - Use project structure

Do NOT hardcode paths - Use conventions ❌ Do NOT skip validation - Validate config ❌ Do NOT ignore precedence - Follow hierarchy

Configuration Precedence

Viper uses this precedence order (highest to lowest):

  1. Explicit viper.Set() calls
  2. Command-line flags
  3. Environment variables
  4. Config file values
  5. Defaults
viper.SetDefault("port", 8080)              // 5. Default
// config.yaml: port: 8081                  // 4. Config file
os.Setenv("APP_PORT", "8082")              // 3. Environment
cobra.Flags().Int("port", 0, "Port")       // 2. Flag
viper.Set("port", 8083)                     // 1. Explicit set

Basic Setup

Initialize Viper

package config

import (
    "fmt"
    "os"

    "github.com/spf13/viper"
)

func Init() error {
    // Set config name (no extension)
    viper.SetConfigName("config")

    // Set config type
    viper.SetConfigType("yaml")

    // Add search paths
    viper.AddConfigPath(".")
    viper.AddConfigPath("$HOME/.myapp")
    viper.AddConfigPath("/etc/myapp")

    // Read config
    if err := viper.ReadInConfig(); err != nil {
        if _, ok := err.(viper.ConfigFileNotFoundError); ok {
            // Config file not found; use defaults
            return nil
        }
        return fmt.Errorf("error reading config: %w", err)
    }

    return nil
}

With Cobra Integration

package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var cfgFile string

var rootCmd = &cobra.Command{
    Use:   "myapp",
    Short: "My application",
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

func init() {
    cobra.OnInitialize(initConfig)

    // Global flags
    rootCmd.PersistentFlags().StringVar(
        &cfgFile,
        "config",
        "",
        "config file (default is $HOME/.myapp/config.yaml)",
    )
}

func initConfig() {
    if cfgFile != "" {
        // Use explicit config file
        viper.SetConfigFile(cfgFile)
    } else {
        // Find home directory
        home, err := os.UserHomeDir()
        if err != nil {
            fmt.Println(err)
            os.Exit(1)
        }

        // Search config in home directory and current directory
        viper.AddConfigPath(home + "/.myapp")
        viper.AddConfigPath(".")
        viper.SetConfigType("yaml")
        viper.SetConfigName("config")
    }

    // Read environment variables
    viper.AutomaticEnv()
    viper.SetEnvPrefix("MYAPP")

    // Read config file
    if err := viper.ReadInConfig(); err == nil {
        fmt.Println("Using config file:", viper.ConfigFileUsed())
    }
}

Configuration Patterns

Set Defaults

func setDefaults() {
    // Server
    viper.SetDefault("server.port", 8080)
    viper.SetDefault("server.host", "localhost")
    viper.SetDefault("server.timeout", "30s")

    // Database
    viper.SetDefault("database.host", "localhost")
    viper.SetDefault("database.port", 5432)
    viper.SetDefault("database.name", "myapp")

    // Logging
    viper.SetDefault("log.level", "info")
    viper.SetDefault("log.format", "json")
}

Bind Flags

Single flag:

cmd.Flags().IntP("port", "p", 8080, "Port to run on")
viper.BindPFlag("server.port", cmd.Flags().Lookup("port"))

All flags:

cmd.Flags().Int("port", 8080, "Port")
cmd.Flags().String("host", "localhost", "Host")

viper.BindPFlags(cmd.Flags())

Persistent flags:

rootCmd.PersistentFlags().String("log-level", "info", "Log level")
viper.BindPFlag("log.level", rootCmd.PersistentFlags().Lookup("log-level"))

Environment Variables

Auto-map all env vars:

viper.AutomaticEnv()
viper.SetEnvPrefix("MYAPP")

// MYAPP_SERVER_PORT → server.port
// MYAPP_DATABASE_NAME → database.name

Custom env key replacer:

import "strings"

viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
viper.SetEnvPrefix("MYAPP")

// MYAPP_SERVER_PORT → server.port (. → _)

Bind specific env var:

viper.BindEnv("database.password", "DB_PASSWORD")

// DB_PASSWORD → database.password

Read Config Values

Get typed values:

port := viper.GetInt("server.port")
host := viper.GetString("server.host")
enabled := viper.GetBool("feature.enabled")
timeout := viper.GetDuration("server.timeout")
tags := viper.GetStringSlice("tags")

Check if set:

if viper.IsSet("server.port") {
    port := viper.GetInt("server.port")
}

Get with default:

port := viper.GetInt("server.port")
if port == 0 {
    port = 8080
}

Unmarshal to Struct

Full config:

type Config struct {
    Server   ServerConfig   `mapstructure:"server"`
    Database DatabaseConfig `mapstructure:"database"`
    Log      LogConfig      `mapstructure:"log"`
}

type ServerConfig struct {
    Port    int    `mapstructure:"port"`
    Host    string `mapstructure:"host"`
    Timeout string `mapstructure:"timeout"`
}

var config Config

if err := viper.Unmarshal(&config); err != nil {
    return fmt.Errorf("unable to decode config: %w", err)
}

Subsection:

var serverConfig ServerConfig

if err := viper.UnmarshalKey("server", &serverConfig); err != nil {
    return fmt.Errorf("unable to decode server config: %w", err)
}

Write Config

Create default config:

func createDefaultConfig(path string) error {
    viper.SetDefault("server.port", 8080)
    viper.SetDefault("server.host", "localhost")

    return viper.WriteConfigAs(path)
}

Save current config:

viper.Set("server.port", 9090)

// Write to current config file
viper.WriteConfig()

// Write to specific file
viper.WriteConfigAs("/path/to/config.yaml")

// Safe write (won't overwrite)
viper.SafeWriteConfig()

CLY Project Pattern

Config Package

pkg/config/config.go:

package config

import (
    "fmt"
    "os"
    "path/filepath"

    "github.com/spf13/viper"
)

type Config struct {
    Server ServerConfig `mapstructure:"server"`
    Log    LogConfig    `mapstructure:"log"`
}

type ServerConfig struct {
    Port int    `mapstructure:"port"`
    Host string `mapstructure:"host"`
}

type LogConfig struct {
    Level  string `mapstructure:"level"`
    Format string `mapstructure:"format"`
}

var cfg *Config

// Init initializes the configuration
func Init(cfgFile string) error {
    if cfgFile != "" {
        viper.SetConfigFile(cfgFile)
    } else {
        home, err := os.UserHomeDir()
        if err != nil {
            return err
        }

        viper.AddConfigPath(filepath.Join(home, ".cly"))
        viper.AddConfigPath(".")
        viper.SetConfigType("yaml")
        viper.SetConfigName("config")
    }

    setDefaults()

    viper.AutomaticEnv()
    viper.SetEnvPrefix("CLY")

    if err := viper.ReadInConfig(); err != nil {
        if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
            return err
        }
    }

    cfg = &Config{}
    if err := viper.Unmarshal(cfg); err != nil {
        return fmt.Errorf("unable to decode config: %w", err)
    }

    return nil
}

func setDefaults() {
    viper.SetDefault("server.port", 8080)
    viper.SetDefault("server.host", "localhost")
    viper.SetDefault("log.level", "info")
    viper.SetDefault("log.format", "text")
}

// Get returns the current config
func Get() *Config {
    return cfg
}

// GetString returns a config value as string
func GetString(key string) string {
    return viper.GetString(key)
}

// GetInt returns a config value as int
func GetInt(key string) int {
    return viper.GetInt(key)
}

// GetBool returns a config value as bool
func GetBool(key string) bool {
    return viper.GetBool(key)
}

Root Command Integration

cmd/root.go:

package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
    "github.com/yurifrl/cly/pkg/config"
)

var cfgFile string

var RootCmd = &cobra.Command{
    Use:   "cly",
    Short: "CLY - Command Line Yuri",
    PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
        return config.Init(cfgFile)
    },
}

func Execute() {
    if err := RootCmd.Execute(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func init() {
    RootCmd.PersistentFlags().StringVar(
        &cfgFile,
        "config",
        "",
        "config file (default is $HOME/.cly/config.yaml)",
    )
}

Config Command

modules/config/cmd.go:

package configcmd

import (
    "fmt"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

func Register(parent *cobra.Command) {
    cmd := &cobra.Command{
        Use:   "config",
        Short: "Manage configuration",
    }

    cmd.AddCommand(
        initCmd(),
        showCmd(),
        validateCmd(),
    )

    parent.AddCommand(cmd)
}

func initCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "init",
        Short: "Initialize config file",
        RunE: func(cmd *cobra.Command, args []string) error {
            path, _ := cmd.Flags().GetString("path")
            if path == "" {
                path = "$HOME/.cly/config.yaml"
            }

            if err := viper.SafeWriteConfigAs(path); err != nil {
                return fmt.Errorf("failed to create config: %w", err)
            }

            fmt.Printf("Config created at: %s\n", path)
            return nil
        },
    }
}

func showCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "show",
        Short: "Show current configuration",
        RunE: func(cmd *cobra.Command, args []string) error {
            fmt.Println("Current configuration:")
            fmt.Println("Config file:", viper.ConfigFileUsed())
            fmt.Println()

            for _, key := range viper.AllKeys() {
                fmt.Printf("%s: %v\n", key, viper.Get(key))
            }

            return nil
        },
    }
}

func validateCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "validate",
        Short: "Validate configuration",
        RunE: func(cmd *cobra.Command, args []string) error {
            // Add validation logic
            fmt.Println("Configuration is valid")
            return nil
        },
    }
}

Advanced Patterns

Remote Config (etcd, Consul)

import _ "github.com/spf13/viper/remote"

func initRemoteConfig() error {
    viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/myapp.json")
    viper.SetConfigType("json")

    if err := viper.ReadRemoteConfig(); err != nil {
        return err
    }

    return nil
}

// Watch for changes
func watchRemoteConfig() {
    go func() {
        for {
            time.Sleep(time.Second * 5)
            err := viper.WatchRemoteConfig()
            if err != nil {
                log.Printf("unable to read remote config: %v", err)
                continue
            }
        }
    }()
}

Watch Config File

viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
    fmt.Println("Config file changed:", e.Name)

    // Reload config
    var newConfig Config
    if err := viper.Unmarshal(&newConfig); err != nil {
        log.Printf("error reloading config: %v", err)
        return
    }

    // Update application state
    updateAppConfig(newConfig)
})

Multiple Config Instances

// Default instance
viper.SetConfigName("config")
viper.ReadInConfig()

// Custom instance
v := viper.New()
v.SetConfigName("other-config")
v.AddConfigPath(".")
v.ReadInConfig()

port := v.GetInt("port")

Config with Validation

type Config struct {
    Server ServerConfig `mapstructure:"server" validate:"required"`
    DB     DBConfig     `mapstructure:"database" validate:"required"`
}

type ServerConfig struct {
    Port int    `mapstructure:"port" validate:"required,min=1,max=65535"`
    Host string `mapstructure:"host" validate:"required,hostname"`
}

func Load() (*Config, error) {
    var cfg Config

    if err := viper.Unmarshal(&cfg); err != nil {
        return nil, err
    }

    // Validate
    validate := validator.New()
    if err := validate.Struct(cfg); err != nil {
        return nil, fmt.Errorf("invalid config: %w", err)
    }

    return &cfg, nil
}

Nested Config Keys

// Dot notation
viper.Set("server.database.host", "localhost")

// Nested maps
viper.Set("server", map[string]interface{}{
    "database": map[string]interface{}{
        "host": "localhost",
        "port": 5432,
    },
})

// Access nested
host := viper.GetString("server.database.host")

// Get sub-tree
dbConfig := viper.Sub("server.database")
if dbConfig != nil {
    host := dbConfig.GetString("host")
}

Config File Formats

YAML

config.yaml:

server:
  port: 8080
  host: localhost
  timeout: 30s

database:
  host: localhost
  port: 5432
  name: myapp
  user: postgres
  password: secret

log:
  level: info
  format: json
  output: stdout

features:
  enabled:
    - feature1
    - feature2

JSON

config.json:

{
  "server": {
    "port": 8080,
    "host": "localhost"
  },
  "database": {
    "host": "localhost",
    "port": 5432
  }
}

TOML

config.toml:

[server]
port = 8080
host = "localhost"

[database]
host = "localhost"
port = 5432
name = "myapp"

Best Practices

1. Always Set Defaults

func init() {
    viper.SetDefault("server.port", 8080)
    viper.SetDefault("log.level", "info")
}

2. Use Environment Variables

viper.AutomaticEnv()
viper.SetEnvPrefix("MYAPP")

// Now MYAPP_SERVER_PORT overrides config

3. Validate Config

type Config struct {
    Port int `validate:"required,min=1,max=65535"`
}

if err := validate.Struct(cfg); err != nil {
    return err
}

4. Provide Config Commands

myapp config init      # Create default config
myapp config show      # Show current config
myapp config validate  # Validate config

5. Handle Missing Config Gracefully

if err := viper.ReadInConfig(); err != nil {
    if _, ok := err.(viper.ConfigFileNotFoundError); ok {
        // Config not found, use defaults
        log.Println("No config file found, using defaults")
    } else {
        return err
    }
}

6. Don't Store Secrets in Config

// ❌ BAD
database:
  password: "mysecret"

// ✅ GOOD - Use env vars
database:
  password: ${DB_PASSWORD}

// Or
viper.BindEnv("database.password", "DB_PASSWORD")

7. Use Struct Tags

type ServerConfig struct {
    Port    int    `mapstructure:"port" json:"port" yaml:"port"`
    Host    string `mapstructure:"host" json:"host" yaml:"host"`
    Timeout string `mapstructure:"timeout" json:"timeout" yaml:"timeout"`
}

Common Patterns

Config Init Command

func initConfigCmd() *cobra.Command {
    var force bool

    cmd := &cobra.Command{
        Use:   "init",
        Short: "Initialize configuration",
        RunE: func(cmd *cobra.Command, args []string) error {
            configPath := viper.ConfigFileUsed()
            if configPath == "" {
                configPath = filepath.Join(os.Getenv("HOME"), ".myapp", "config.yaml")
            }

            // Check if exists
            if _, err := os.Stat(configPath); err == nil && !force {
                return fmt.Errorf("config already exists: %s (use --force to overwrite)", configPath)
            }

            // Create directory
            if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
                return err
            }

            // Write config
            if err := viper.WriteConfigAs(configPath); err != nil {
                return err
            }

            fmt.Printf("Config initialized: %s\n", configPath)
            return nil
        },
    }

    cmd.Flags().BoolVar(&force, "force", false, "Overwrite existing config")
    return cmd
}

Config Migration

func migrateConfig() error {
    version := viper.GetInt("version")

    switch version {
    case 0:
        // Migrate from v0 to v1
        viper.Set("new_field", "default")
        viper.Set("version", 1)
        fallthrough
    case 1:
        // Migrate from v1 to v2
        viper.Set("another_field", true)
        viper.Set("version", 2)
    }

    return viper.WriteConfig()
}

Testing

func TestConfig(t *testing.T) {
    // Use separate viper instance
    v := viper.New()
    v.SetConfigType("yaml")

    var yamlConfig = []byte(`
server:
  port: 8080
  host: localhost
`)

    v.ReadConfig(bytes.NewBuffer(yamlConfig))

    assert.Equal(t, 8080, v.GetInt("server.port"))
    assert.Equal(t, "localhost", v.GetString("server.host"))
}

Checklist

  • Defaults set for all config values
  • Config file search paths defined
  • Environment variable support
  • Flags bound to config
  • Config struct with mapstructure tags
  • Config validation
  • Config commands (init, show, validate)
  • Error handling for missing config
  • Secrets via env vars only
  • Config file format documented

Resources

适合场景

01

用户想查找某类 Agent Skill 时

02

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

03

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

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

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

能力 4

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

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

平台分布

Codex

38.45%
按下载量换算30

Claude

31.03%
按下载量换算24

Cursor

18.42%
按下载量换算14

Gemini CLI

9.36%
按下载量换算7

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

敏感数据

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

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills