Skip to content
Merged
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
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ require (
github.com/gorilla/websocket v1.5.3
github.com/itchyny/json2yaml v0.1.4
github.com/kernel/hypeman-go v0.11.0
github.com/knadh/koanf/parsers/yaml v1.1.0
github.com/knadh/koanf/providers/file v1.2.1
github.com/knadh/koanf/v2 v2.3.2
github.com/muesli/reflow v0.3.0
github.com/stretchr/testify v1.11.1
github.com/tidwall/gjson v1.18.0
Expand Down Expand Up @@ -42,15 +45,21 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/klauspost/compress v1.18.1 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/env v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
Expand All @@ -70,6 +79,7 @@ require (
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/text v0.31.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
Expand Down
20 changes: 20 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,15 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I=
Expand All @@ -76,6 +80,16 @@ github.com/kernel/hypeman-go v0.11.0 h1:hCXNUHtrhGKswJapzyWyozBOXhKK/oreKvm0AXHu
github.com/kernel/hypeman-go v0.11.0/go.mod h1:guRrhyP9QW/ebUS1UcZ0uZLLJeGAAhDNzSi68U4M9hI=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/parsers/yaml v1.1.0 h1:3ltfm9ljprAHt4jxgeYLlFPmUaunuCgu1yILuTXRdM4=
github.com/knadh/koanf/parsers/yaml v1.1.0/go.mod h1:HHmcHXUrp9cOPcuC+2wrr44GTUB0EC+PyfN3HZD9tFg=
github.com/knadh/koanf/providers/env v1.1.0 h1:U2VXPY0f+CsNDkvdsG8GcsnK4ah85WwWyJgef9oQMSc=
github.com/knadh/koanf/providers/env v1.1.0/go.mod h1:QhHHHZ87h9JxJAn2czdEl6pdkNnDh/JS1Vtsyt65hTY=
github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP3AESOJYp9wM=
github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
github.com/knadh/koanf/v2 v2.3.2 h1:Ee6tuzQYFwcZXQpc2MiVeC6qHMandf5SMUJJNoFp/c4=
github.com/knadh/koanf/v2 v2.3.2/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand All @@ -89,8 +103,12 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
Expand Down Expand Up @@ -169,6 +187,8 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
Expand Down
7 changes: 5 additions & 2 deletions pkg/cmd/cmdutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ func getDefaultRequestOptions(cmd *cli.Command) []option.RequestOption {
option.WithHeader("User-Agent", fmt.Sprintf("Hypeman/CLI %s", Version)),
}

// Override base URL if the --base-url flag is provided
if baseURL := cmd.String("base-url"); baseURL != "" {
if baseURL := resolveBaseURL(cmd); baseURL != "" {
opts = append(opts, option.WithBaseURL(baseURL))
}

if apiKey := resolveAPIKey(); apiKey != "" {
opts = append(opts, option.WithAPIKey(apiKey))
}

return opts
}

Expand Down
74 changes: 74 additions & 0 deletions pkg/cmd/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package cmd

import (
"os"
"path/filepath"
"strings"

"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/v2"
"github.com/urfave/cli/v3"
)

// CLIConfig holds CLI configuration loaded from cli.yaml
type CLIConfig struct {
BaseURL string `koanf:"base_url"`
APIKey string `koanf:"api_key"`
}

// getCLIConfigPath returns the path to the CLI config file.
// The CLI uses ~/.config/hypeman/cli.yaml on all platforms.
func getCLIConfigPath() string {
home, err := os.UserHomeDir()
if err != nil {
return ""
}
return filepath.Join(home, ".config", "hypeman", "cli.yaml")
}

// loadCLIConfig loads CLI configuration from the config file, then
// overlays HYPEMAN_-prefixed environment variables (highest precedence).
// HYPEMAN_BASE_URL -> base_url, HYPEMAN_API_KEY -> api_key.
// Returns an empty config if the file doesn't exist or can't be parsed.
func loadCLIConfig() *CLIConfig {
cfg := &CLIConfig{}
k := koanf.New(".")

configPath := getCLIConfigPath()
if configPath != "" {
_ = k.Load(file.Provider(configPath), yaml.Parser())
}

// Overlay HYPEMAN_-prefixed env vars: HYPEMAN_BASE_URL -> base_url
_ = k.Load(env.ProviderWithValue("HYPEMAN_", ".", func(key string, value string) (string, interface{}) {
if value == "" {
return "", nil
}
return strings.ToLower(strings.TrimPrefix(key, "HYPEMAN_")), value
}), nil)

_ = k.Unmarshal("", cfg)
return cfg
}

// resolveBaseURL returns the effective base URL with precedence:
// CLI flag > HYPEMAN_BASE_URL env > config file > default.
func resolveBaseURL(cmd *cli.Command) string {
if u := cmd.Root().String("base-url"); u != "" {
return u
}
cfg := loadCLIConfig()
if cfg.BaseURL != "" {
return cfg.BaseURL
}
return "http://localhost:8080"
}

// resolveAPIKey returns the effective API key with precedence:
// HYPEMAN_API_KEY env > config file.
func resolveAPIKey() string {
cfg := loadCLIConfig()
return cfg.APIKey
}
14 changes: 4 additions & 10 deletions pkg/cmd/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,18 +145,12 @@ func handleCp(ctx context.Context, cmd *cli.Command) error {
return err
}

// Get base URL and API key
baseURL := cmd.Root().String("base-url")
if baseURL == "" {
baseURL = os.Getenv("HYPEMAN_BASE_URL")
}
if baseURL == "" {
baseURL = "http://localhost:8080"
}
// Get base URL and API key (flag > env > config file)
baseURL := resolveBaseURL(cmd)

apiKey := os.Getenv("HYPEMAN_API_KEY")
apiKey := resolveAPIKey()
if apiKey == "" {
return fmt.Errorf("HYPEMAN_API_KEY environment variable required")
return fmt.Errorf("API key required: set HYPEMAN_API_KEY or configure api_key in ~/.config/hypeman/cli.yaml")
}

archive := cmd.Bool("archive")
Expand Down
14 changes: 4 additions & 10 deletions pkg/cmd/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,18 +145,12 @@ func handleExec(ctx context.Context, cmd *cli.Command) error {
return fmt.Errorf("failed to marshal request: %w", err)
}

// Get base URL and API key
baseURL := cmd.Root().String("base-url")
if baseURL == "" {
baseURL = os.Getenv("HYPEMAN_BASE_URL")
}
if baseURL == "" {
baseURL = "http://localhost:8080"
}
// Get base URL and API key (flag > env > config file)
baseURL := resolveBaseURL(cmd)

apiKey := os.Getenv("HYPEMAN_API_KEY")
apiKey := resolveAPIKey()
if apiKey == "" {
return fmt.Errorf("HYPEMAN_API_KEY environment variable required")
return fmt.Errorf("API key required: set HYPEMAN_API_KEY or configure api_key in ~/.config/hypeman/cli.yaml")
}

// Build WebSocket URL
Expand Down
13 changes: 2 additions & 11 deletions pkg/cmd/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,7 @@ func handlePush(ctx context.Context, cmd *cli.Command) error {
targetName = args[1]
}

baseURL := cmd.String("base-url")
if baseURL == "" {
baseURL = os.Getenv("HYPEMAN_BASE_URL")
}
if baseURL == "" {
baseURL = "http://localhost:8080"
}
baseURL := resolveBaseURL(cmd)

parsedURL, err := url.Parse(baseURL)
if err != nil {
Expand Down Expand Up @@ -71,10 +65,7 @@ func handlePush(ctx context.Context, cmd *cli.Command) error {
return fmt.Errorf("invalid target: %w", err)
}

token := os.Getenv("HYPEMAN_BEARER_TOKEN")
if token == "" {
token = os.Getenv("HYPEMAN_API_KEY")
}
token := resolveAPIKey()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Push drops legacy token env fallback

Medium Severity

handlePush now reads auth only via resolveAPIKey(), which excludes the previous HYPEMAN_BEARER_TOKEN fallback. Existing environments that still set only HYPEMAN_BEARER_TOKEN will now push without credentials and fail authentication, creating a regression in push behavior.

Fix in Cursor Fix in Web


// Use custom transport that always sends Basic auth header
transport := &authTransport{
Expand Down