From 4a2cc5b8ab1480e5dba7b75499fd2f8880c01e1c Mon Sep 17 00:00:00 2001 From: Snider Date: Fri, 24 Oct 2025 15:16:34 +0100 Subject: [PATCH] Refactor configuration and cryptographic services to use a unified API structure --- .gitignore | 1 + README.md | 126 +++++++++++++++++++++++++++++++++++-- cmd/app/main.go | 4 +- cmd/demo/demo.go | 61 ++++++++++++++++++ config/config.go | 40 +++++++++--- config/header.go | 4 +- crypt/crypt.go | 72 ++++++++++++++++----- crypt/hash.go | 2 +- crypt/header.go | 24 +++++++ crypt/service.go | 43 ------------- crypt/sum.go | 8 +-- display/display.go | 24 ++++--- display/header.go | 26 -------- display/tray.go | 3 +- display/window.go | 77 +++++++++++++++++++---- go.sum | 152 +++++++++++++++++++++++++++++++++++++++++++++ 16 files changed, 537 insertions(+), 130 deletions(-) create mode 100644 cmd/demo/demo.go create mode 100644 crypt/header.go delete mode 100644 crypt/service.go delete mode 100644 display/header.go create mode 100644 go.sum diff --git a/.gitignore b/.gitignore index 506762c8..37882a5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ wails3 build/ vendor/ +.idea \ No newline at end of file diff --git a/README.md b/README.md index 14171f2a..2ab9c4f4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,126 @@ -# Core +# Core by Snider -A Helper for GoLang projects, who also use, but not exclusive to Wails.io v3+ +Core is an opinionated framework for building Go applications written for assured information exchange between the front-end and back-end of a Web3 environment. -You need a file called apptray.png in your assets folder +There is no assumption of what front-end framework you are using, and no assumption of what back-end framework you are using. + +However, Core provides a set of services that are useful for building applications that are built for the Web3 environment. + +With working defaults, Core is designed to be as simple as possible to use, yet powerful enough to be used in a wide variety of applications. + +## Libraries used in Core +- [Wails.io](https://github.com/wailsapp/wails) - A framework for building desktop applications using Go and web technologies. +- [ProtonMail OpenPGP](https://github.com/ProtonMail/go-crypto) +## Features +- [Core](#core) - Manages application configuration +- [Core.Config](#coreconfig) - Manages application configuration +- [Core.Crypto](#corecrypto) - Manages cryptographic operations +- [Core.Database](#coredatabase) - Manages database operations +- [Core.IPC](#coreipc) - Manages inter-process communication +- [Core.Log](#corelog) - Manages logging +- [Core.Network](#corenetwork) - Manages network operations +- [Core.Storage](#corestorage) - Manages storage operations +- [Core.UI](#coreui) - Manages user interface operations +- [Core.Utils](#coreutils) - Manages utility operations +- [Core.Display](#coredisplay) - Manages Windows, system tray and their states + + +## Core + +Core allows you to easily instantiate complex services within a locked-down environment enforcing logical enclaves. + +### Basic Usage +```go +package main + +import ( + "cmd/demo" + + "https://github.com/Snider/Core" +) + +func main() { + demoService := core.New( + core.WithService(demo.Register), + core.WithServiceLock(), // This can go anywhere in the chain to be the Final Service (provides security clarity) + ) + demoService.Run() // handled by wails, not implemented yet + demoService.Wait() // handled by wails, not implemented yet + demoService.Close() // handled by wails, not implemented yet +} +``` + +### Multiple Services +```go +package main + +import ( + "cmd/demo" + + "https://github.com/Snider/Core" +) + +func main() { + coreService := core.New( + core.WithService(demo.Register), + core.WithService(demo.RegisterDemo2), + core.WithServiceLock(), + ) + + rickService := core.New( + core.WithService(demo.Register), + core.WithService(demo.RegisterDemo2), + core.WithServiceLock(), + ) + mortyService := core.New( + core.WithService(demo.Register), + core.WithService(demo.RegisterDemo2), + core.WithServiceLock(), + ) + + core.Mod[API](coreService,"demo").name = "demo" + core.Mod[API](rickService).name = "demo2" + core.Mod[API](mortyService).name = "demo2" + +} +``` +### Registering Services +Core aunties that your application is not able to access any of the services that are not explicitly registered. + +That said, + +## Core.Display + +Display is a Wails.io service that provides the ability to open windows. +Core.Display remembers the locations of your users windows and will reopen them when the application is restarted. +Also allows you to adjust your windowing programmatically via JavaScript bindings, and for it to be persistent. + +### Open A Window On Startup + +```go +func (d *API) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + d.OpenWindow( + OptName("main"), + OptHeight(900), + OptWidth(1280), + OptURL("/"), + OptTitle("Core"), + ) + return nil +} +``` + +```go +window := d.OpenWindow( + OptName("main"), + OptHeight(900), + OptWidth(1280), + OptURL("/"), + OptTitle("Core"), +) +``` + +### Full Wails Example ```go package main @@ -38,4 +156,4 @@ func main() { panic(err) } } -``` \ No newline at end of file +``` diff --git a/cmd/app/main.go b/cmd/app/main.go index abfa33da..d1a6d95f 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -5,6 +5,7 @@ import ( "github.com/Snider/Core" "github.com/Snider/Core/config" + "github.com/Snider/Core/crypt" "github.com/Snider/Core/display" "github.com/wailsapp/wails/v3/pkg/application" ) @@ -23,8 +24,9 @@ func main() { app.RegisterService(application.NewService(core.Service( core.WithWails(app), // Provides the Wails application instance to core services core.WithAssets(assets), // Provides the embed.FS to core services - core.WithService(display.Register), // Provides the ability to open windows core.WithService(config.Register), // Provides the ability to persist UI state (windows reopen where they closed) + core.WithService(display.Register), // Provides the ability to open windows + core.WithService(crypt.Register), // Provides cryptographic functions core.WithServiceLock(), // locks core from accepting new services blocking access to IPC ))) diff --git a/cmd/demo/demo.go b/cmd/demo/demo.go new file mode 100644 index 00000000..2ae0e6cc --- /dev/null +++ b/cmd/demo/demo.go @@ -0,0 +1,61 @@ +package demo + +import "github.com/Snider/Core" + +// this instance is the singleton instance of the demo module. +var instance *API + +type API struct { + name string + core *core.Core +} + +func main() { + coreService := core.New( + core.WithService(demo.Register), + core.WithService(demo.RegisterDemo2), + core.WithServiceLock(), + ) + + rickService := core.New( + core.WithService(demo.Register), + core.WithService(demo.RegisterDemo2), + core.WithServiceLock(), + ) + mortyService := core.New( + core.WithService(demo.Register), + core.WithService(demo.RegisterDemo2), + core.WithServiceLock(), + ) + + core.Mod[API](coreService, "demo").name = "demo" + core.Mod[API](rickService).name = "demo2" + core.Mod[API](mortyService).name = "demo2" + +} + +func RegisterDemo(c *core.Core) error { + instance = &API{ + core: c, + } + if err := c.RegisterModule("demo", instance); err != nil { + return err + } + c.RegisterAction(handleActionCall) + return nil +} + +func RegisterDemo2(c *core.Core) error { + instance = &API{ + core: c, + } + if err := c.RegisterModule("demo", instance); err != nil { + return err + } + c.RegisterAction(handleActionCall) + return nil +} + +func handleActionCall(c *core.Core, msg core.Message) error { + return nil +} diff --git a/config/config.go b/config/config.go index 62f39761..bf4b8160 100644 --- a/config/config.go +++ b/config/config.go @@ -19,7 +19,7 @@ const configFileName = "config.json" var ErrSetupRequired = errors.New("setup required: config.json not found") // Service provides access to the application's configuration. -var service *Config +var instance *API // NewService creates and initializes a new configuration service. // It loads an existing configuration or creates a default one if not found. @@ -32,7 +32,7 @@ func Register(c *core.Core) error { configDir := filepath.Join(userHomeDir, "config") //configPath := filepath.Join(configDir, configFileName) - service = &Config{ + instance = &API{ core: c, UserHomeDir: userHomeDir, ConfigDir: configDir, @@ -43,11 +43,31 @@ func Register(c *core.Core) error { Language: "en", } - return c.RegisterModule("config", service) + err = c.RegisterModule("config", instance) + if err != nil { + return err + } + c.RegisterAction(handleActionCall) + return nil +} +func handleActionCall(c *core.Core, msg core.Message) error { + switch m := msg.(type) { + + case core.ActionServiceStartup: + //err := instance.ServiceStartup(context.Background(), application.ServiceOptions{}) + //if err != nil { + // return err + //} + c.App.Logger.Info("Config service started") + return nil + default: + c.App.Logger.Error("Unknown message type", "type", fmt.Sprintf("%T", m)) + return nil + } } // newDefaultConfig creates a default configuration with resolved paths and ensures directories exist. -func newDefaultConfig() (*Config, error) { +func newDefaultConfig() (*API, error) { if strings.Contains(appName, "..") || strings.Contains(appName, string(filepath.Separator)) { return nil, fmt.Errorf("invalid app name '%s': contains path traversal characters", appName) } @@ -68,7 +88,7 @@ func newDefaultConfig() (*Config, error) { return nil, fmt.Errorf("could not resolve cache directory: %w", err) } - cfg := &Config{ + instance := &API{ UserHomeDir: userHomeDir, RootDir: rootDir, CacheDir: cacheDir, @@ -80,18 +100,18 @@ func newDefaultConfig() (*Config, error) { Language: "en", // Hardcoded default, will be overridden if loaded or detected } - dirs := []string{cfg.RootDir, cfg.ConfigDir, cfg.DataDir, cfg.CacheDir, cfg.WorkspacesDir, cfg.UserHomeDir} + dirs := []string{instance.RootDir, instance.ConfigDir, instance.DataDir, instance.CacheDir, instance.WorkspacesDir, instance.UserHomeDir} for _, dir := range dirs { if err := os.MkdirAll(dir, os.ModePerm); err != nil { return nil, fmt.Errorf("could not create directory %s: %w", dir, err) } } - return cfg, nil + return instance, nil } // Save writes the current configuration to config.json. -func (c *Config) Save() error { +func (c *API) Save() error { configPath := filepath.Join(c.ConfigDir, configFileName) data, err := json.MarshalIndent(*c, "", " ") @@ -106,7 +126,7 @@ func (c *Config) Save() error { } // IsFeatureEnabled checks if a given feature is enabled in the configuration. -func (c *Config) IsFeatureEnabled(feature string) bool { +func (c *API) IsFeatureEnabled(feature string) bool { for _, f := range c.Features { if f == feature { return true @@ -116,7 +136,7 @@ func (c *Config) IsFeatureEnabled(feature string) bool { } // EnableFeature adds a feature to the list of enabled features and saves the config. -func (c *Config) EnableFeature(feature string) error { +func (c *API) EnableFeature(feature string) error { if c.IsFeatureEnabled(feature) { return nil } diff --git a/config/header.go b/config/header.go index ae5da433..7569a844 100644 --- a/config/header.go +++ b/config/header.go @@ -9,7 +9,7 @@ import ( ) // Config holds the resolved paths and user-configurable settings for the application. -type Config struct { +type API struct { // --- Dynamic Paths (not stored in config.json) --- core *core.Core DataDir string `json:"-"` @@ -27,7 +27,7 @@ type Config struct { } // Key retrieves a configuration value by its key. It checks JSON tags and field names (case-insensitive). -func (c *Config) Key(key string) (interface{}, error) { +func (c *API) Key(key string) (interface{}, error) { // Use reflection to inspect the struct fields. val := reflect.ValueOf(c).Elem() typ := val.Type() diff --git a/crypt/crypt.go b/crypt/crypt.go index 49d590e7..c36e7959 100644 --- a/crypt/crypt.go +++ b/crypt/crypt.go @@ -1,23 +1,63 @@ package crypt import ( - "github.com/Snider/Core/config" + "context" + "fmt" + + core "github.com/Snider/Core" + "github.com/Snider/Core/crypt/lib/openpgp" + "github.com/wailsapp/wails/v3/pkg/application" ) -// HashType defines the supported hashing algorithms. -type HashType string +// createServerKeyPair is a package-level variable that can be swapped for testing. +var createServerKeyPair = openpgp.CreateServerKeyPair -const ( - LTHN HashType = "lthn" - SHA512 HashType = "sha512" - SHA256 HashType = "sha256" - SHA1 HashType = "sha1" - MD5 HashType = "md5" -) - -// Service provides cryptographic functions. -// It is the main entry point for all cryptographic operations -// and is bound to the frontend. -type Service struct { - config *config.Config +// ServiceStartup Startup is called when the app starts. It handles one-time cryptographic setup. +func (s *API) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // Define the directory for server keys based on the central config. + //serverKeysDir := filepath.Join(s.config.DataDir, "server_keys") + //if err := filesystem.EnsureDir(filesystem.Local, serverKeysDir); err != nil { + // return fmt.Errorf("failed to create server keys directory: %w", err) + //} + // + //// Check for server key pair using the configured path. + //serverKeyPath := filepath.Join(serverKeysDir, "server.lthn.pub") + //if !filesystem.IsFile(filesystem.Local, serverKeyPath) { + // log.Println("Creating server key pair...") + // if err := createServerKeyPair(serverKeysDir); err != nil { + // return fmt.Errorf("failed to create server key pair: %w", err) + // } + // log.Println("Server key pair created.") + //} + return nil +} + +// Register creates a new crypt service and registers it with the core. +var instance *API + +func Register(c *core.Core) error { + instance = &API{ + core: c, + } + if err := c.RegisterModule("crypt", instance); err != nil { + return err + } + c.RegisterAction(handleActionCall) + return nil +} + +func handleActionCall(c *core.Core, msg core.Message) error { + switch m := msg.(type) { + + case core.ActionServiceStartup: + err := instance.ServiceStartup(context.Background(), application.ServiceOptions{}) + if err != nil { + return err + } + c.App.Logger.Info("Crypt service started") + return nil + default: + c.App.Logger.Error("Unknown message type", "type", fmt.Sprintf("%T", m)) + return nil + } } diff --git a/crypt/hash.go b/crypt/hash.go index 5ca021f0..55f7e86b 100644 --- a/crypt/hash.go +++ b/crypt/hash.go @@ -11,7 +11,7 @@ import ( ) // Hash computes a hash of the payload using the specified algorithm. -func (s *Service) Hash(lib HashType, payload string) string { +func (s *API) Hash(lib HashType, payload string) string { switch lib { case LTHN: return lthn.Hash(payload) diff --git a/crypt/header.go b/crypt/header.go new file mode 100644 index 00000000..448f48df --- /dev/null +++ b/crypt/header.go @@ -0,0 +1,24 @@ +package crypt + +import ( + core "github.com/Snider/Core" +) + +// HashType defines the supported hashing algorithms. +type HashType string + +const ( + LTHN HashType = "lthn" + SHA512 HashType = "sha512" + SHA256 HashType = "sha256" + SHA1 HashType = "sha1" + MD5 HashType = "md5" +) + +// Service provides cryptographic functions. +// It is the main entry point for all cryptographic operations +// and is bound to the frontend. + +type API struct { + core *core.Core +} diff --git a/crypt/service.go b/crypt/service.go deleted file mode 100644 index 8690ecf5..00000000 --- a/crypt/service.go +++ /dev/null @@ -1,43 +0,0 @@ -package crypt - -import ( - "context" - "fmt" - "log" - "path/filepath" - - "github.com/Snider/Core/config" - "github.com/Snider/Core/crypt/lib/openpgp" - "github.com/Snider/Core/filesystem" - "github.com/wailsapp/wails/v3/pkg/application" -) - -// createServerKeyPair is a package-level variable that can be swapped for testing. -var createServerKeyPair = openpgp.CreateServerKeyPair - -// NewService creates a new crypt.Service, accepting a config service instance. -func NewService(cfg *config.Config) *Service { - return &Service{ - config: cfg, - } -} - -// ServiceStartup Startup is called when the app starts. It handles one-time cryptographic setup. -func (s *Service) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { - // Define the directory for server keys based on the central config. - serverKeysDir := filepath.Join(s.config.DataDir, "server_keys") - if err := filesystem.EnsureDir(filesystem.Local, serverKeysDir); err != nil { - return fmt.Errorf("failed to create server keys directory: %w", err) - } - - // Check for server key pair using the configured path. - serverKeyPath := filepath.Join(serverKeysDir, "server.lthn.pub") - if !filesystem.IsFile(filesystem.Local, serverKeyPath) { - log.Println("Creating server key pair...") - if err := createServerKeyPair(serverKeysDir); err != nil { - return fmt.Errorf("failed to create server key pair: %w", err) - } - log.Println("Server key pair created.") - } - return nil -} diff --git a/crypt/sum.go b/crypt/sum.go index 74530374..f8339fb6 100644 --- a/crypt/sum.go +++ b/crypt/sum.go @@ -7,7 +7,7 @@ import ( ) // Luhn validates a number using the Luhn algorithm. -func (s *Service) Luhn(payload string) bool { +func (s *API) Luhn(payload string) bool { payload = strings.ReplaceAll(payload, " ", "") sum := 0 isSecond := false @@ -31,7 +31,7 @@ func (s *Service) Luhn(payload string) bool { } // Fletcher16 computes the Fletcher-16 checksum. -func (s *Service) Fletcher16(payload string) uint16 { +func (s *API) Fletcher16(payload string) uint16 { data := []byte(payload) var sum1, sum2 uint16 for _, b := range data { @@ -42,7 +42,7 @@ func (s *Service) Fletcher16(payload string) uint16 { } // Fletcher32 computes the Fletcher-32 checksum. -func (s *Service) Fletcher32(payload string) uint32 { +func (s *API) Fletcher32(payload string) uint32 { data := []byte(payload) // Pad with 0 to make it even length for uint16 conversion if len(data)%2 != 0 { @@ -59,7 +59,7 @@ func (s *Service) Fletcher32(payload string) uint32 { } // Fletcher64 computes the Fletcher-64 checksum. -func (s *Service) Fletcher64(payload string) uint64 { +func (s *API) Fletcher64(payload string) uint64 { data := []byte(payload) // Pad to multiple of 4 if len(data)%4 != 0 { diff --git a/display/display.go b/display/display.go index 40907bd1..366c1b68 100644 --- a/display/display.go +++ b/display/display.go @@ -12,13 +12,15 @@ import ( type ActionOpenWindow struct { Target string } +type API struct { + core *core.Core +} var instance *API func Register(c *core.Core) error { instance = &API{ - core: c, - windowHandles: make(map[string]*application.WebviewWindow), + core: c, } if err := c.RegisterModule("display", instance); err != nil { return err @@ -30,7 +32,8 @@ func Register(c *core.Core) error { func handleActionCall(c *core.Core, msg core.Message) error { switch m := msg.(type) { case *ActionOpenWindow: - instance.OpenWindow(m.Target, application.WebviewWindowOptions{ + instance.NewWithStruct(&Window{ + Name: "main", Title: "Core", Height: 900, Width: 1280, @@ -42,6 +45,7 @@ func handleActionCall(c *core.Core, msg core.Message) error { if err != nil { return err } + c.App.Logger.Info("Display service started") return nil default: c.App.Logger.Error("Unknown message type", "type", fmt.Sprintf("%T", m)) @@ -139,16 +143,16 @@ Platform Information:`, } func (d *API) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { - d.core.App.Logger.Info("Display service starting up") d.analyzeScreens() d.monitorScreenChanges() d.buildMenu() d.systemTray() - d.core.App.Window.NewWithOptions(application.WebviewWindowOptions{ - Title: "Core", - Height: 900, - Width: 1280, - URL: "/", - }) + d.OpenWindow( + OptName("main"), + OptHeight(900), + OptWidth(1280), + OptURL("/"), + OptTitle("Core"), + ) return nil } diff --git a/display/header.go b/display/header.go deleted file mode 100644 index c17adf7f..00000000 --- a/display/header.go +++ /dev/null @@ -1,26 +0,0 @@ -package display - -import ( - "github.com/Snider/Core" - "github.com/wailsapp/wails/v3/pkg/application" -) - -// Brand defines the type for different application brands. -type Brand string - -const ( - AdminHub Brand = "admin-hub" - ServerHub Brand = "server-hub" - GatewayHub Brand = "gateway-hub" - DeveloperHub Brand = "developer-hub" - ClientHub Brand = "client-hub" -) - -// Service manages all OS-level UI interactions (menus, windows, tray). -// It is the main entry point for all display-related operations. -type API struct { - // --- Injected Dependencies --- - core *core.Core - - windowHandles map[string]*application.WebviewWindow -} diff --git a/display/tray.go b/display/tray.go index 7f5156c5..87670117 100644 --- a/display/tray.go +++ b/display/tray.go @@ -22,7 +22,8 @@ func (d *API) systemTray() { // systray.SetIcon(appTrayIcon) //} // Create a hidden window for the system tray menu to interact with - trayWindow := d.core.App.Window.NewWithOptions(application.WebviewWindowOptions{ + trayWindow := d.NewWithStruct(&Window{ + Name: "system-tray", Title: "System Tray Status", URL: "/#/system-tray", Width: 400, diff --git a/display/window.go b/display/window.go index 15d2c0e0..334d3776 100644 --- a/display/window.go +++ b/display/window.go @@ -1,19 +1,73 @@ package display -import "github.com/wailsapp/wails/v3/pkg/application" +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) -// OpenWindow creates and shows a new webview window. -// This function is callable from the frontend. -func (d *API) OpenWindow(name string, options application.WebviewWindowOptions) { - // Check if a window with that name already exists - if window, exists := d.core.App.Window.GetByName(name); exists { - window.Focus() - return +type Option func(*Window) error + +type Window = application.WebviewWindowOptions + +func OptName(s string) Option { + return func(o *Window) error { + o.Name = s + return nil } +} +func OptTitle(s string) Option { + return func(o *Window) error { + o.Title = s + return nil + } +} - window := d.core.App.Window.NewWithOptions(options) - d.windowHandles[name] = window - window.Show() +func OptURL(s string) Option { + return func(o *Window) error { + o.URL = s + return nil + } +} + +func OptWidth(i int) Option { + return func(o *Window) error { + o.Width = i + return nil + } +} + +func OptHeight(i int) Option { + return func(o *Window) error { + o.Height = i + return nil + } +} + +func applyOptions(opts ...Option) *Window { + w := &Window{} + if opts == nil { + return w + } + for _, o := range opts { + if err := o(w); err != nil { + return nil + } + } + return w +} + +// NewWithOptions creates a new window using the provided options and returns its handle. +// The caller is responsible for showing the window. +func (d *API) NewWithStruct(options *Window) *application.WebviewWindow { + return d.core.App.Window.NewWithOptions(*options) +} +func (d *API) NewWithOptions(opts ...Option) *application.WebviewWindow { + return d.NewWithStruct(applyOptions(opts...)) +} + +// OpenWindow is a legacy or direct method to open a window using Wails-native options. +// It returns the handle of the created window. The caller is responsible for showing the window. +func (d *API) OpenWindow(opts ...Option) *application.WebviewWindow { + return d.NewWithOptions(opts...) } // SelectDirectory opens a directory selection dialog and returns the selected path. @@ -21,7 +75,6 @@ func (d *API) SelectDirectory() (string, error) { dialog := application.OpenFileDialog() dialog.SetTitle("Select Project Directory") if path, err := dialog.PromptForSingleSelection(); err == nil { - // Use selected directory path return path, nil } return "", nil diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..0b28822f --- /dev/null +++ b/go.sum @@ -0,0 +1,152 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= +github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= +github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU= +github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58= +github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/wailsapp/wails/v3 v3.0.0-alpha.36 h1:GQ8vSrFgafITwMd/p4k+WBjG9K/anma9Pk2eJ/5CLsI= +github.com/wailsapp/wails/v3 v3.0.0-alpha.36/go.mod h1:7i8tSuA74q97zZ5qEJlcVZdnO+IR7LT2KU8UpzYMPsw= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=