Refactor configuration and cryptographic services to use a unified API structure

This commit is contained in:
Snider 2025-10-24 15:16:34 +01:00
parent 2bfa3b7250
commit 4a2cc5b8ab
16 changed files with 537 additions and 130 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
wails3
build/
vendor/
.idea

124
README.md
View file

@ -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

View file

@ -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
)))

61
cmd/demo/demo.go Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -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()

View file

@ -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
}
}

View file

@ -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)

24
crypt/header.go Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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
}

View file

@ -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,

View file

@ -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

152
go.sum Normal file
View file

@ -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=