Skip to content

Commit d76e8bd

Browse files
pmarsceillclaude
andcommitted
feat(cli): add Viper configuration support
Add persistent configuration management using Viper, allowing users to set defaults via ~/.mapd/config.yaml. Configuration values can be overridden by environment variables (MAP_* prefix) or CLI flags. New commands: - map config list (alias: ls) - List all configuration values - map config get <key> - Get a specific value - map config set <key> <value> - Set and persist a value Configuration options: - socket: daemon socket path - data-dir: data directory path - agent.default-type: claude or codex - agent.default-count: number of agents to spawn - agent.default-branch: git branch for worktrees - agent.use-worktree: worktree isolation - agent.skip-permissions: skip permission prompts Closes #17 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a6dc44a commit d76e8bd

15 files changed

Lines changed: 308 additions & 30 deletions

File tree

go.mod

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,33 @@ require (
1212

1313
require (
1414
github.com/dustin/go-humanize v1.0.1 // indirect
15+
github.com/fsnotify/fsnotify v1.7.0 // indirect
1516
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
17+
github.com/hashicorp/hcl v1.0.0 // indirect
1618
github.com/inconshreveable/mousetrap v1.1.0 // indirect
19+
github.com/magiconair/properties v1.8.7 // indirect
1720
github.com/mattn/go-isatty v0.0.20 // indirect
21+
github.com/mitchellh/mapstructure v1.5.0 // indirect
1822
github.com/ncruces/go-strftime v0.1.9 // indirect
23+
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
1924
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
25+
github.com/sagikazarmark/locafero v0.4.0 // indirect
26+
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
27+
github.com/sourcegraph/conc v0.3.0 // indirect
28+
github.com/spf13/afero v1.11.0 // indirect
29+
github.com/spf13/cast v1.6.0 // indirect
2030
github.com/spf13/pflag v1.0.5 // indirect
31+
github.com/spf13/viper v1.18.2 // indirect
32+
github.com/subosito/gotenv v1.6.0 // indirect
33+
go.uber.org/atomic v1.9.0 // indirect
34+
go.uber.org/multierr v1.9.0 // indirect
35+
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect
2136
golang.org/x/net v0.28.0 // indirect
2237
golang.org/x/sys v0.40.0 // indirect
2338
golang.org/x/text v0.17.0 // indirect
2439
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
40+
gopkg.in/ini.v1 v1.67.0 // indirect
41+
gopkg.in/yaml.v3 v3.0.1 // indirect
2542
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
2643
modernc.org/libc v1.55.3 // indirect
2744
modernc.org/mathutil v1.6.0 // indirect

go.sum

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
24
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
35
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
6+
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
7+
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
48
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
59
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
610
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
@@ -9,21 +13,56 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
913
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
1014
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
1115
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
16+
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
17+
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
1218
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
1319
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
20+
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
21+
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
1422
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
1523
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
24+
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
25+
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
1626
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
1727
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
28+
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
29+
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
1830
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1931
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
2032
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
2133
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
2234
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
35+
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
36+
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
37+
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
38+
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
39+
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
40+
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
41+
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
42+
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
43+
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
44+
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
2345
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
2446
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
2547
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
2648
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
49+
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
50+
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
51+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
52+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
53+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
54+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
55+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
56+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
57+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
58+
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
59+
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
60+
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
61+
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
62+
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
63+
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
64+
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
65+
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
2766
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
2867
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
2968
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
@@ -44,6 +83,10 @@ google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFN
4483
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
4584
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
4685
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
86+
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
87+
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
88+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
89+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
4790
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
4891
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
4992
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=

internal/cli/agent_merge.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func runAgentMerge(cmd *cobra.Command, args []string) error {
4646
agentID := args[0]
4747

4848
// Connect to daemon to get agent info
49-
c, err := client.New(socketPath)
49+
c, err := client.New(getSocketPath())
5050
if err != nil {
5151
return fmt.Errorf("connect to daemon: %w", err)
5252
}

internal/cli/agent_watch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func runAgentWatch(cmd *cobra.Command, args []string) error {
4545
return fmt.Errorf("tmux not found in PATH - required for agent watch")
4646
}
4747

48-
c, err := client.New(socketPath)
48+
c, err := client.New(getSocketPath())
4949
if err != nil {
5050
return fmt.Errorf("connect to daemon: %w", err)
5151
}

internal/cli/agents.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func init() {
2323
}
2424

2525
func runAgents(cmd *cobra.Command, args []string) error {
26-
c, err := client.New(socketPath)
26+
c, err := client.New(getSocketPath())
2727
if err != nil {
2828
return fmt.Errorf("connect to daemon: %w", err)
2929
}

internal/cli/clean.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ func runClean(cmd *cobra.Command, args []string) error {
4949
}
5050

5151
// 3. Remove socket file if it exists
52-
if _, err := os.Stat(socketPath); err == nil {
53-
if err := os.Remove(socketPath); err != nil {
54-
fmt.Printf("warning: failed to remove socket %s: %v\n", socketPath, err)
52+
if _, err := os.Stat(getSocketPath()); err == nil {
53+
if err := os.Remove(getSocketPath()); err != nil {
54+
fmt.Printf("warning: failed to remove socket %s: %v\n", getSocketPath(), err)
5555
} else {
56-
fmt.Printf("removed socket %s\n", socketPath)
56+
fmt.Printf("removed socket %s\n", getSocketPath())
5757
cleaned = true
5858
}
5959
}

internal/cli/config.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"sort"
8+
"strings"
9+
10+
"github.com/spf13/cobra"
11+
"github.com/spf13/viper"
12+
)
13+
14+
var cfgFile string
15+
16+
// configCmd is the parent command for config management
17+
var configCmd = &cobra.Command{
18+
Use: "config",
19+
Short: "Manage configuration settings",
20+
Long: `View and modify MAP configuration settings stored in ~/.mapd/config.yaml.`,
21+
}
22+
23+
var configListCmd = &cobra.Command{
24+
Use: "list",
25+
Aliases: []string{"ls"},
26+
Short: "List all configuration values",
27+
Long: `Display all configuration values including defaults and overrides.`,
28+
RunE: runConfigList,
29+
}
30+
31+
var configGetCmd = &cobra.Command{
32+
Use: "get <key>",
33+
Short: "Get a configuration value",
34+
Long: `Get a specific configuration value by key.
35+
36+
Examples:
37+
map config get socket
38+
map config get agent.default-type
39+
map config get agent.default-count`,
40+
Args: cobra.ExactArgs(1),
41+
RunE: runConfigGet,
42+
}
43+
44+
var configSetCmd = &cobra.Command{
45+
Use: "set <key> <value>",
46+
Short: "Set a configuration value",
47+
Long: `Set a configuration value and persist it to ~/.mapd/config.yaml.
48+
49+
Examples:
50+
map config set socket /custom/path.sock
51+
map config set agent.default-type codex
52+
map config set agent.default-count 3
53+
map config set agent.use-worktree false`,
54+
Args: cobra.ExactArgs(2),
55+
RunE: runConfigSet,
56+
}
57+
58+
func init() {
59+
rootCmd.AddCommand(configCmd)
60+
configCmd.AddCommand(configListCmd)
61+
configCmd.AddCommand(configGetCmd)
62+
configCmd.AddCommand(configSetCmd)
63+
}
64+
65+
// initConfig reads in config file and ENV variables if set
66+
func initConfig() error {
67+
// Set defaults
68+
viper.SetDefault("socket", "/tmp/mapd.sock")
69+
viper.SetDefault("data-dir", filepath.Join(os.Getenv("HOME"), ".mapd"))
70+
viper.SetDefault("agent.default-type", "claude")
71+
viper.SetDefault("agent.default-count", 1)
72+
viper.SetDefault("agent.default-branch", "")
73+
viper.SetDefault("agent.use-worktree", true)
74+
viper.SetDefault("agent.skip-permissions", true)
75+
76+
if cfgFile != "" {
77+
// Use config file from the flag
78+
viper.SetConfigFile(cfgFile)
79+
} else {
80+
// Search for config in ~/.mapd directory
81+
home, err := os.UserHomeDir()
82+
if err != nil {
83+
return fmt.Errorf("get home directory: %w", err)
84+
}
85+
86+
configDir := filepath.Join(home, ".mapd")
87+
viper.AddConfigPath(configDir)
88+
viper.SetConfigType("yaml")
89+
viper.SetConfigName("config")
90+
}
91+
92+
// Environment variables with MAP_ prefix
93+
viper.SetEnvPrefix("MAP")
94+
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
95+
viper.AutomaticEnv()
96+
97+
// Read config file (ignore if not found)
98+
if err := viper.ReadInConfig(); err != nil {
99+
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
100+
return fmt.Errorf("read config: %w", err)
101+
}
102+
}
103+
104+
// Bind the socket flag to viper
105+
if err := viper.BindPFlag("socket", rootCmd.PersistentFlags().Lookup("socket")); err != nil {
106+
return fmt.Errorf("bind socket flag: %w", err)
107+
}
108+
109+
return nil
110+
}
111+
112+
// writeConfig writes the current configuration to the config file
113+
func writeConfig() error {
114+
home, err := os.UserHomeDir()
115+
if err != nil {
116+
return fmt.Errorf("get home directory: %w", err)
117+
}
118+
119+
configDir := filepath.Join(home, ".mapd")
120+
if err := os.MkdirAll(configDir, 0755); err != nil {
121+
return fmt.Errorf("create config directory: %w", err)
122+
}
123+
124+
configPath := filepath.Join(configDir, "config.yaml")
125+
if err := viper.WriteConfigAs(configPath); err != nil {
126+
return fmt.Errorf("write config: %w", err)
127+
}
128+
129+
return nil
130+
}
131+
132+
func runConfigList(cmd *cobra.Command, args []string) error {
133+
keys := viper.AllKeys()
134+
sort.Strings(keys)
135+
136+
fmt.Printf("%-25s %s\n", "KEY", "VALUE")
137+
fmt.Println(strings.Repeat("-", 50))
138+
139+
for _, key := range keys {
140+
value := viper.Get(key)
141+
fmt.Printf("%-25s %v\n", key, value)
142+
}
143+
144+
// Show config file location if it exists
145+
if viper.ConfigFileUsed() != "" {
146+
fmt.Printf("\nConfig file: %s\n", viper.ConfigFileUsed())
147+
}
148+
149+
return nil
150+
}
151+
152+
func runConfigGet(cmd *cobra.Command, args []string) error {
153+
key := args[0]
154+
155+
if !viper.IsSet(key) {
156+
return fmt.Errorf("key %q not found", key)
157+
}
158+
159+
fmt.Println(viper.Get(key))
160+
return nil
161+
}
162+
163+
func runConfigSet(cmd *cobra.Command, args []string) error {
164+
key := args[0]
165+
value := args[1]
166+
167+
// Handle boolean values
168+
switch strings.ToLower(value) {
169+
case "true":
170+
viper.Set(key, true)
171+
case "false":
172+
viper.Set(key, false)
173+
default:
174+
// Try to parse as integer
175+
var intVal int
176+
if _, err := fmt.Sscanf(value, "%d", &intVal); err == nil {
177+
viper.Set(key, intVal)
178+
} else {
179+
viper.Set(key, value)
180+
}
181+
}
182+
183+
if err := writeConfig(); err != nil {
184+
return err
185+
}
186+
187+
fmt.Printf("set %s = %v\n", key, viper.Get(key))
188+
return nil
189+
}

internal/cli/down.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ func init() {
2424
}
2525

2626
func runDown(cmd *cobra.Command, args []string) error {
27-
if !client.IsDaemonRunning(socketPath) {
27+
if !client.IsDaemonRunning(getSocketPath()) {
2828
fmt.Println("daemon is not running")
2929
return nil
3030
}
3131

32-
c, err := client.New(socketPath)
32+
c, err := client.New(getSocketPath())
3333
if err != nil {
3434
return fmt.Errorf("connect to daemon: %w", err)
3535
}

internal/cli/root.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ import (
55
"os"
66

77
"github.com/spf13/cobra"
8+
"github.com/spf13/viper"
89
)
910

1011
// Version is set via -ldflags at build time
1112
var Version = "dev"
1213

13-
var socketPath string
14-
1514
// rootCmd is the base command
1615
var rootCmd = &cobra.Command{
1716
Use: "map",
@@ -28,6 +27,16 @@ func Execute() {
2827
}
2928
}
3029

30+
// getSocketPath returns the socket path from Viper (flag > env > config > default)
31+
func getSocketPath() string {
32+
return viper.GetString("socket")
33+
}
34+
3135
func init() {
32-
rootCmd.PersistentFlags().StringVarP(&socketPath, "socket", "s", "/tmp/mapd.sock", "daemon socket path")
36+
rootCmd.PersistentFlags().StringP("socket", "s", "/tmp/mapd.sock", "daemon socket path")
37+
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default: ~/.mapd/config.yaml)")
38+
39+
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
40+
return initConfig()
41+
}
3342
}

0 commit comments

Comments
 (0)