Token导航 LogoToken导航

Agent Skill · 研究检索

cli-config(CLI 配置)

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

cli-config 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为接触敏感数据;许可证信息还不明确,第三方扫描已通过,按安装量在研究检索类的 73,675 个 Skill 里排第 67,317。

总安装 247周安装 10GitHub Stars 公开资料未说明下载 78核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 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

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

cli-config 基于 Cobra 和 Viper 构建分层配置系统,支持 flags、环境变量、配置文件的多级优先级管理。适用于复杂 CLI 应用的参数治理,提供 init、show、validate 等辅助命令简化运维负担。

推荐使用 YAML/JSON/TOML 格式,绑定 flags 至 Viper 实现无缝配置注入和热重载能力。环境变量采用前缀命名空间隔离,避免命名冲突并支持 Docker/K8s 等容器化部署场景。配置变更应伴随版本控制,防止因误改关键参数引发生产事故。

来源仓库内路径:skills/cli-config

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/yurifrl/cly --skill cli-config;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要接触敏感数据,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

cli-config 怎么安装?

在终端执行 npx skills add https://github.com/yurifrl/cly --skill cli-config。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

cli-config 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

cli-config 需要什么权限?

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

cli-config 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

cli-config 用的人多吗?

公开数据是累计安装 247 次,近一周安装 10 次,下载 78 次。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

cli-config 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 需要接触敏感数据,装到常用环境前要先评估影响面。
  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。

不太适合

  • 环境里有大量密钥、凭证,不方便给第三方 Skill 读取的人。
  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

研究检索类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
find-skills

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
cli-config

CLI 配置 · 本页

247—接触敏感数据未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

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

平台分布

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