Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion cmd/configcmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ func NewCmd(injectedApp *application.Lux) *cobra.Command {
},
}
app = injectedApp
// set user metrics collection preferences cmd
// config subcommands
cmd.AddCommand(newInitCmd())
cmd.AddCommand(newSetCmd())
cmd.AddCommand(newGetCmd())
cmd.AddCommand(newListCmd())
cmd.AddCommand(newMetricsCmd())

return cmd
Expand Down
157 changes: 157 additions & 0 deletions cmd/configcmd/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package configcmd

import (
"fmt"
"strings"

"github.com/luxfi/cli/pkg/globalconfig"
"github.com/spf13/cobra"
)

var getShowSource bool

func newGetCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "get <key>",
Short: "Get a configuration value",
Long: `Get a configuration value, showing the effective value after merging all config sources.

Keys use dot notation for nested values:
local.numNodes - Number of nodes for local network
local.autoTrackSubnets - Auto-track subnets in dev mode
network.defaultNetwork - Default network
evm.defaultTokenName - Default token name for EVM chains

Use --source to also show where the value came from (default, global, project).

Examples:
lux config get local.numNodes
lux config get --source evm.defaultTokenName`,
Args: cobra.ExactArgs(1),
RunE: runGet,
}

cmd.Flags().BoolVar(&getShowSource, "source", false, "Show the source of the value")

return cmd
}

func runGet(_ *cobra.Command, args []string) error {
key := args[0]
baseDir := app.GetBaseDir()

merged, err := globalconfig.GetEffectiveConfig(baseDir)
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}

value, source, err := getConfigValue(merged, key)
if err != nil {
return err
}

if getShowSource {
fmt.Printf("%s = %s (source: %s)\n", key, value, source)
} else {
fmt.Printf("%s = %s\n", key, value)
}

return nil
}

func getConfigValue(merged *globalconfig.MergedConfig, key string) (string, globalconfig.ConfigSource, error) {
parts := strings.SplitN(key, ".", 2)
if len(parts) != 2 {
return "", "", fmt.Errorf("invalid key format: use section.setting (e.g., local.numNodes)")
}

section := parts[0]
setting := parts[1]

switch section {
case "local":
return getLocalValue(merged, setting)
case "network":
return getNetworkValue(merged, setting)
case "evm":
return getEVMValue(merged, setting)
case "staking":
return getStakingValue(merged, setting)
case "node":
return getNodeValue(merged, setting)
default:
return "", "", fmt.Errorf("unknown section: %s", section)
}
}

func getLocalValue(merged *globalconfig.MergedConfig, setting string) (string, globalconfig.ConfigSource, error) {
switch setting {
case "numNodes":
if merged.Config.Local.NumNodes != nil {
return fmt.Sprintf("%d", *merged.Config.Local.NumNodes), merged.Sources.NumNodes, nil
}
return fmt.Sprintf("%d", globalconfig.DefaultNumNodes), globalconfig.SourceDefault, nil
case "autoTrackSubnets":
if merged.Config.Local.AutoTrackSubnets != nil {
return fmt.Sprintf("%t", *merged.Config.Local.AutoTrackSubnets), merged.Sources.AutoTrackSubnets, nil
}
return "true", globalconfig.SourceDefault, nil
default:
return "", "", fmt.Errorf("unknown local setting: %s", setting)
}
}

func getNetworkValue(merged *globalconfig.MergedConfig, setting string) (string, globalconfig.ConfigSource, error) {
switch setting {
case "defaultNetwork":
return merged.Config.Network.DefaultNetwork, merged.Sources.DefaultNetwork, nil
case "luxdVersion":
return merged.Config.Network.LuxdVersion, merged.Sources.LuxdVersion, nil
default:
return "", "", fmt.Errorf("unknown network setting: %s", setting)
}
}

func getEVMValue(merged *globalconfig.MergedConfig, setting string) (string, globalconfig.ConfigSource, error) {
switch setting {
case "defaultTokenName":
return merged.Config.EVM.DefaultTokenName, merged.Sources.DefaultTokenName, nil
case "defaultTokenSymbol":
return merged.Config.EVM.DefaultTokenSymbol, merged.Sources.DefaultTokenSymbol, nil
case "defaultTokenSupply":
return merged.Config.EVM.DefaultTokenSupply, merged.Sources.DefaultTokenSupply, nil
default:
return "", "", fmt.Errorf("unknown evm setting: %s", setting)
}
}

func getStakingValue(merged *globalconfig.MergedConfig, setting string) (string, globalconfig.ConfigSource, error) {
switch setting {
case "bootstrapValidatorBalance":
if merged.Config.Staking.BootstrapValidatorBalance != nil {
return fmt.Sprintf("%.2f", *merged.Config.Staking.BootstrapValidatorBalance), merged.Sources.BootstrapValidatorBalance, nil
}
return fmt.Sprintf("%.2f", globalconfig.DefaultBootstrapValidatorBalance), globalconfig.SourceDefault, nil
case "bootstrapValidatorWeight":
if merged.Config.Staking.BootstrapValidatorWeight != nil {
return fmt.Sprintf("%d", *merged.Config.Staking.BootstrapValidatorWeight), merged.Sources.BootstrapValidatorWeight, nil
}
return fmt.Sprintf("%d", globalconfig.DefaultBootstrapValidatorWeight), globalconfig.SourceDefault, nil
default:
return "", "", fmt.Errorf("unknown staking setting: %s", setting)
}
}

func getNodeValue(merged *globalconfig.MergedConfig, setting string) (string, globalconfig.ConfigSource, error) {
switch setting {
case "defaultInstanceType":
return merged.Config.Node.DefaultInstanceType, merged.Sources.DefaultInstanceType, nil
case "defaultRegion":
return merged.Config.Node.DefaultRegion, merged.Sources.DefaultRegion, nil
default:
return "", "", fmt.Errorf("unknown node setting: %s", setting)
}
}
101 changes: 101 additions & 0 deletions cmd/configcmd/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package configcmd

import (
"fmt"

"github.com/luxfi/cli/pkg/globalconfig"
"github.com/spf13/cobra"
)

var (
initProject bool
initForce bool
)

func newInitCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Initialize configuration with smart defaults",
Long: `Initialize a new configuration file with smart defaults based on your environment.

By default, creates a global configuration at ~/.lux/config.json.
Use --project to create a project-local configuration at .luxconfig.json.

The command auto-detects your environment (CI, Codespace, development, production)
and suggests optimal defaults.`,
RunE: runInit,
}

cmd.Flags().BoolVar(&initProject, "project", false, "Create project-local config instead of global")
cmd.Flags().BoolVar(&initForce, "force", false, "Overwrite existing config file")

return cmd
}

func runInit(_ *cobra.Command, _ []string) error {
// Get smart defaults based on environment
smart := globalconfig.GetSmartDefaults()

fmt.Printf("Detected environment: %s\n", smart.Environment)
fmt.Printf("Suggested nodes: %d\n", smart.SuggestedNumNodes)
fmt.Printf("Suggested instance: %s\n", smart.SuggestedInstance)

if initProject {
return initProjectConfig(smart)
}
return initGlobalConfig(smart)
}

func initGlobalConfig(smart *globalconfig.SmartDefaults) error {
baseDir := app.GetBaseDir()

// Check if config exists
existing, err := globalconfig.LoadGlobalConfig(baseDir)
if err != nil {
return fmt.Errorf("failed to check existing config: %w", err)
}
if existing != nil && !initForce {
return fmt.Errorf("config already exists at %s/config.json (use --force to overwrite)", baseDir)
}

// Create config with smart defaults
config := globalconfig.DefaultGlobalConfig()
config.Local.NumNodes = &smart.SuggestedNumNodes
config.Node.DefaultInstanceType = smart.SuggestedInstance

if err := globalconfig.SaveGlobalConfig(baseDir, &config); err != nil {
return fmt.Errorf("failed to save config: %w", err)
}

fmt.Printf("Created global config at %s/config.json\n", baseDir)
return nil
}

func initProjectConfig(smart *globalconfig.SmartDefaults) error {
// Check if project config exists
existing, err := globalconfig.LoadProjectConfig()
if err != nil {
return fmt.Errorf("failed to check existing config: %w", err)
}
if existing != nil && !initForce {
return fmt.Errorf("project config already exists (use --force to overwrite)")
}

// Create config with smart defaults
config := &globalconfig.ProjectConfig{
GlobalConfig: globalconfig.DefaultGlobalConfig(),
ProjectName: "",
}
config.Local.NumNodes = &smart.SuggestedNumNodes
config.Node.DefaultInstanceType = smart.SuggestedInstance

if err := globalconfig.SaveProjectConfig(config); err != nil {
return fmt.Errorf("failed to save config: %w", err)
}

fmt.Println("Created project config at .luxconfig.json")
return nil
}
89 changes: 89 additions & 0 deletions cmd/configcmd/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package configcmd

import (
"fmt"

"github.com/luxfi/cli/pkg/globalconfig"
"github.com/spf13/cobra"
)

var listShowSources bool

func newListCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List all configuration values",
Long: `List all configuration values with their effective values.

Shows the merged configuration from all sources (defaults, global, project).
Use --sources to see where each value comes from.`,
RunE: runList,
}

cmd.Flags().BoolVar(&listShowSources, "sources", false, "Show the source of each value")

return cmd
}

func runList(_ *cobra.Command, _ []string) error {
baseDir := app.GetBaseDir()

merged, err := globalconfig.GetEffectiveConfig(baseDir)
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}

fmt.Println("Configuration:")
fmt.Println()

// Network settings
fmt.Println("[network]")
printValue("defaultNetwork", merged.Config.Network.DefaultNetwork, merged.Sources.DefaultNetwork)
printValue("luxdVersion", merged.Config.Network.LuxdVersion, merged.Sources.LuxdVersion)
fmt.Println()

// Local settings
fmt.Println("[local]")
if merged.Config.Local.NumNodes != nil {
printValue("numNodes", fmt.Sprintf("%d", *merged.Config.Local.NumNodes), merged.Sources.NumNodes)
}
if merged.Config.Local.AutoTrackSubnets != nil {
printValue("autoTrackSubnets", fmt.Sprintf("%t", *merged.Config.Local.AutoTrackSubnets), merged.Sources.AutoTrackSubnets)
}
fmt.Println()

// EVM settings
fmt.Println("[evm]")
printValue("defaultTokenName", merged.Config.EVM.DefaultTokenName, merged.Sources.DefaultTokenName)
printValue("defaultTokenSymbol", merged.Config.EVM.DefaultTokenSymbol, merged.Sources.DefaultTokenSymbol)
printValue("defaultTokenSupply", merged.Config.EVM.DefaultTokenSupply, merged.Sources.DefaultTokenSupply)
fmt.Println()

// Staking settings
fmt.Println("[staking]")
if merged.Config.Staking.BootstrapValidatorBalance != nil {
printValue("bootstrapValidatorBalance", fmt.Sprintf("%.2f", *merged.Config.Staking.BootstrapValidatorBalance), merged.Sources.BootstrapValidatorBalance)
}
if merged.Config.Staking.BootstrapValidatorWeight != nil {
printValue("bootstrapValidatorWeight", fmt.Sprintf("%d", *merged.Config.Staking.BootstrapValidatorWeight), merged.Sources.BootstrapValidatorWeight)
}
fmt.Println()

// Node settings
fmt.Println("[node]")
printValue("defaultInstanceType", merged.Config.Node.DefaultInstanceType, merged.Sources.DefaultInstanceType)
printValue("defaultRegion", merged.Config.Node.DefaultRegion, merged.Sources.DefaultRegion)

return nil
}

func printValue(key, value string, source globalconfig.ConfigSource) {
if listShowSources {
fmt.Printf(" %s = %s (%s)\n", key, value, source)
} else {
fmt.Printf(" %s = %s\n", key, value)
}
}
Loading
Loading