diff --git a/.gitignore b/.gitignore index fee8173e..c2bc8063 100644 --- a/.gitignore +++ b/.gitignore @@ -1,28 +1,28 @@ -wails3 -.task -vendor/ -.idea -node_modules/ +.idea/ +.vscode/ .DS_Store *.log -.env -.env.*.local -coverage/ -coverage.out -coverage.html -*.cache -/coverage.txt -bin/ +.core/ + +# Build artefacts dist/ -tasks -/cli +bin/ /core -local.test -/i18n-validate -.angular/ +/cli -patch_cov.* +# Go +vendor/ go.work.sum -.kb -.core/ -.idea/ +coverage/ +coverage.out +coverage.html +coverage.txt + +# Environment / secrets +.env +.env.*.local + +# OS / tooling +.task +*.cache +node_modules/ diff --git a/cmd/config/cmd.go b/cmd/core/config/cmd.go similarity index 100% rename from cmd/config/cmd.go rename to cmd/core/config/cmd.go diff --git a/cmd/config/cmd_get.go b/cmd/core/config/cmd_get.go similarity index 100% rename from cmd/config/cmd_get.go rename to cmd/core/config/cmd_get.go diff --git a/cmd/config/cmd_list.go b/cmd/core/config/cmd_list.go similarity index 100% rename from cmd/config/cmd_list.go rename to cmd/core/config/cmd_list.go diff --git a/cmd/config/cmd_path.go b/cmd/core/config/cmd_path.go similarity index 100% rename from cmd/config/cmd_path.go rename to cmd/core/config/cmd_path.go diff --git a/cmd/config/cmd_set.go b/cmd/core/config/cmd_set.go similarity index 100% rename from cmd/config/cmd_set.go rename to cmd/core/config/cmd_set.go diff --git a/cmd/doctor/cmd_checks.go b/cmd/core/doctor/cmd_checks.go similarity index 100% rename from cmd/doctor/cmd_checks.go rename to cmd/core/doctor/cmd_checks.go diff --git a/cmd/doctor/cmd_commands.go b/cmd/core/doctor/cmd_commands.go similarity index 79% rename from cmd/doctor/cmd_commands.go rename to cmd/core/doctor/cmd_commands.go index 0bd07e94..6b9bb447 100644 --- a/cmd/doctor/cmd_commands.go +++ b/cmd/core/doctor/cmd_commands.go @@ -10,9 +10,14 @@ // Provides platform-specific installation instructions for missing tools. package doctor -import "github.com/spf13/cobra" +import ( + "forge.lthn.ai/core/go-i18n" + "github.com/spf13/cobra" +) // AddDoctorCommands registers the 'doctor' command and all subcommands. func AddDoctorCommands(root *cobra.Command) { + doctorCmd.Short = i18n.T("cmd.doctor.short") + doctorCmd.Long = i18n.T("cmd.doctor.long") root.AddCommand(doctorCmd) } diff --git a/cmd/doctor/cmd_doctor.go b/cmd/core/doctor/cmd_doctor.go similarity index 97% rename from cmd/doctor/cmd_doctor.go rename to cmd/core/doctor/cmd_doctor.go index bac1f126..a3354d79 100644 --- a/cmd/doctor/cmd_doctor.go +++ b/cmd/core/doctor/cmd_doctor.go @@ -21,9 +21,7 @@ var ( var doctorVerbose bool var doctorCmd = &cobra.Command{ - Use: "doctor", - Short: i18n.T("cmd.doctor.short"), - Long: i18n.T("cmd.doctor.long"), + Use: "doctor", RunE: func(cmd *cobra.Command, args []string) error { return runDoctor(doctorVerbose) }, diff --git a/cmd/doctor/cmd_environment.go b/cmd/core/doctor/cmd_environment.go similarity index 100% rename from cmd/doctor/cmd_environment.go rename to cmd/core/doctor/cmd_environment.go diff --git a/cmd/doctor/cmd_install.go b/cmd/core/doctor/cmd_install.go similarity index 89% rename from cmd/doctor/cmd_install.go rename to cmd/core/doctor/cmd_install.go index 07cfd6aa..4ffb59c2 100644 --- a/cmd/doctor/cmd_install.go +++ b/cmd/core/doctor/cmd_install.go @@ -7,7 +7,7 @@ import ( "forge.lthn.ai/core/go-i18n" ) -// printInstallInstructions prints OS-specific installation instructions +// printInstallInstructions prints OperatingSystem-specific installation instructions func printInstallInstructions() { switch runtime.GOOS { case "darwin": diff --git a/cmd/core/go.mod b/cmd/core/go.mod new file mode 100644 index 00000000..0d351d8e --- /dev/null +++ b/cmd/core/go.mod @@ -0,0 +1,116 @@ +module forge.lthn.ai/core/cli/cmd/core + +go 1.26.0 + +require ( + forge.lthn.ai/core/cli v0.3.3 + forge.lthn.ai/core/config v0.1.3 + forge.lthn.ai/core/go-build v0.2.3 + forge.lthn.ai/core/go-cache v0.1.2 + forge.lthn.ai/core/go-crypt v0.1.7 + forge.lthn.ai/core/go-devops v0.1.9 + forge.lthn.ai/core/go-help v0.1.3 + forge.lthn.ai/core/go-i18n v0.1.4 + forge.lthn.ai/core/go-io v0.1.2 + forge.lthn.ai/core/go-scm v0.3.1 + forge.lthn.ai/core/lint v0.3.2 + github.com/spf13/cobra v1.10.2 + github.com/stretchr/testify v1.11.1 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + cloud.google.com/go v0.123.0 // indirect + code.gitea.io/sdk/gitea v0.23.2 // indirect + codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0 // indirect + forge.lthn.ai/core/agent v0.3.1 // indirect + forge.lthn.ai/core/go v0.3.1 // indirect + forge.lthn.ai/core/go-container v0.1.3 // indirect + forge.lthn.ai/core/go-inference v0.1.4 // indirect + forge.lthn.ai/core/go-log v0.0.4 // indirect + forge.lthn.ai/core/go-process v0.2.3 // indirect + forge.lthn.ai/core/go-store v0.1.6 // indirect + github.com/42wim/httpsig v1.2.3 // indirect + github.com/ProtonMail/go-crypto v1.4.0 // indirect + github.com/Snider/Borg v0.2.0 // indirect + github.com/TwiN/go-color v1.4.1 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charmbracelet/bubbletea v1.3.10 // indirect + github.com/charmbracelet/colorprofile v0.4.3 // indirect + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect + github.com/charmbracelet/x/ansi v0.11.6 // indirect + github.com/charmbracelet/x/cellbuf v0.0.15 // indirect + github.com/charmbracelet/x/term v0.2.2 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/davidmz/go-pageant v1.0.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/getkin/kin-openapi v0.134.0 // indirect + github.com/go-fed/httpsig v1.1.0 // indirect + github.com/go-openapi/jsonpointer v0.22.5 // indirect + github.com/go-openapi/swag/jsonname v0.25.5 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/gofrs/flock v0.13.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/go-version v1.8.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/kluctl/go-embed-python v0.0.0-3.13.1-20241219-1 // indirect + github.com/leaanthony/debme v1.2.1 // indirect + github.com/leaanthony/gosod v1.0.4 // indirect + github.com/lucasb-eyer/go-colorful v1.3.0 // indirect + github.com/mailru/easyjson v0.9.2 // 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.21 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/oasdiff/kin-openapi v0.136.1 // indirect + github.com/oasdiff/oasdiff v1.12.3 // indirect + github.com/oasdiff/yaml v0.0.1 // indirect + github.com/oasdiff/yaml3 v0.0.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/redis/go-redis/v9 v9.18.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/sagikazarmark/locafero v0.12.0 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/spf13/viper v1.21.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + github.com/ulikunitz/xz v0.5.15 // indirect + github.com/wI2L/jsondiff v0.7.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yargevad/filepathx v1.0.0 // indirect + github.com/yuin/goldmark v1.7.16 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.49.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/term v0.41.0 // indirect + golang.org/x/text v0.35.0 // indirect + modernc.org/libc v1.70.0 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.46.1 // indirect +) diff --git a/cmd/core/go.sum b/cmd/core/go.sum new file mode 100644 index 00000000..141f9233 --- /dev/null +++ b/cmd/core/go.sum @@ -0,0 +1,295 @@ +cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= +cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= +code.gitea.io/sdk/gitea v0.23.2 h1:iJB1FDmLegwfwjX8gotBDHdPSbk/ZR8V9VmEJaVsJYg= +code.gitea.io/sdk/gitea v0.23.2/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM= +codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0 h1:HTCWpzyWQOHDWt3LzI6/d2jvUDsw/vgGRWm/8BTvcqI= +codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0/go.mod h1:ZglEEDj+qkxYUb+SQIeqGtFxQrbaMYqIOgahNKb7uxs= +forge.lthn.ai/core/agent v0.3.1 h1:Q6lkSg9nr2c1oj2Pr/s3LN7xItIyOmSRgLSfMaaNuyQ= +forge.lthn.ai/core/agent v0.3.1/go.mod h1:FfHS10AkPcxnc+ms93QzNJtZ7dgcET0LvMcJAzY2h+w= +forge.lthn.ai/core/cli v0.3.3 h1:dWvpiLZifuydqU4eH5+UdgCQ6/LOpS1x+O03pU7jkhk= +forge.lthn.ai/core/cli v0.3.3/go.mod h1:PJ/cTufrVLz4KdlBhUkT/sOeh6uOSN6W7+/IvglRoBU= +forge.lthn.ai/core/config v0.1.3 h1:mq02v7LFf9jHSqJakO08qYQnPP8oVMbJHlOxNARXBa8= +forge.lthn.ai/core/config v0.1.3/go.mod h1:4+/ytojOSaPoAQ1uub1+GeOM8OoYdR9xqMtVA3SZ8Qk= +forge.lthn.ai/core/go v0.3.1 h1:5FMTsUhLcxSr07F9q3uG0Goy4zq4eLivoqi8shSY4UM= +forge.lthn.ai/core/go v0.3.1/go.mod h1:gE6c8h+PJ2287qNhVUJ5SOe1kopEwHEquvinstpuyJc= +forge.lthn.ai/core/go-build v0.2.3 h1:iTb0YpJj7PFAWJ+LmO6lusUfYvnyKIfABi1ohnYrdcw= +forge.lthn.ai/core/go-build v0.2.3/go.mod h1:0CVFglD7cc07ew1c9IEv/BAniHGndHGwrWk27m4c4L8= +forge.lthn.ai/core/go-cache v0.1.2 h1:mIt+dqe2Gnqcj3Q6y6wGOXu0MklPO/oWuF09UZUmS6w= +forge.lthn.ai/core/go-cache v0.1.2/go.mod h1:7WbprZVfx/+t4cbJFXMo4sloWk2Eny+rZd8x1Ay9rLk= +forge.lthn.ai/core/go-container v0.1.3 h1:Pb/latnVFBgyI4zDyYxAiRRqKrOYIAxL6om+k2YS1q8= +forge.lthn.ai/core/go-container v0.1.3/go.mod h1:wIlly3pAluVQnQ+DLnZ15pENOFkJicWRRke6msCudLI= +forge.lthn.ai/core/go-crypt v0.1.7 h1:tyDFnXjEksHFQpkFwCpEn+x7zvwh4LnaU+/fP3WmqZc= +forge.lthn.ai/core/go-crypt v0.1.7/go.mod h1:mQdr6K8lWOcyHmSEW24vZPTThQF8fteVgZi8CO+Ko3Y= +forge.lthn.ai/core/go-devops v0.1.9 h1:pgGTvCDeg1SgJIkpZfy1l6ZvkOGGGY+fa3aAcl3vRG4= +forge.lthn.ai/core/go-devops v0.1.9/go.mod h1:uY37IzpargbgDBwazqYv6X5+e2bcCO+cn0jCYQA/YMk= +forge.lthn.ai/core/go-help v0.1.3 h1:eKrj3o3ruvDD3c6NWUve8Y/uMqpfIE5/yR2eU6gdAF0= +forge.lthn.ai/core/go-help v0.1.3/go.mod h1:JSZVb4Gd+P/dTc9laDJsqVCI6OrVbBbBPyPmvw3j4p4= +forge.lthn.ai/core/go-i18n v0.1.4 h1:zOHUUJDgRo88/3tj++kN+VELg/buyZ4T2OSdG3HBbLQ= +forge.lthn.ai/core/go-i18n v0.1.4/go.mod h1:aDyAfz7MMgWYgLkZCptfFmZ7jJg3ocwjEJ1WkJSvv4U= +forge.lthn.ai/core/go-inference v0.1.4 h1:fuAgWbqsEDajHniqAKyvHYbRcBrkGEiGSqR2pfTMRY0= +forge.lthn.ai/core/go-inference v0.1.4/go.mod h1:jfWz+IJX55wAH98+ic6FEqqGB6/P31CHlg7VY7pxREw= +forge.lthn.ai/core/go-io v0.1.2 h1:q8hj2jtOFqAgHlBr5wsUAOXtaFkxy9gqGrQT/il0WYA= +forge.lthn.ai/core/go-io v0.1.2/go.mod h1:PbNKW1Q25ywSOoQXeGdQHbV5aiIrTXvHIQ5uhplA//g= +forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0= +forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw= +forge.lthn.ai/core/go-process v0.2.3 h1:/ERqRYHgCNZjNT9NMinAAJJGJWSsHuCTiHFNEm6nTPY= +forge.lthn.ai/core/go-process v0.2.3/go.mod h1:gVTbxL16ccUIexlFcyDtCy7LfYvD8Rtyzfo8bnXAXrU= +forge.lthn.ai/core/go-scm v0.3.1 h1:G+DqVJLT+UjgUzu2Hnseyl2szhb0wB+DB8VYhq/bLOI= +forge.lthn.ai/core/go-scm v0.3.1/go.mod h1:ER9fQBs8nnlJZQ6+ALnwv+boK/xiwg8jEc9VP6DMijk= +forge.lthn.ai/core/go-store v0.1.6 h1:7T+K5cciXOaWRxge0WnGkt0PcK3epliWBa1G2FLEuac= +forge.lthn.ai/core/go-store v0.1.6/go.mod h1:/2vqaAn+HgGU14N29B+vIfhjIsBzy7RC+AluI6BIUKI= +forge.lthn.ai/core/lint v0.3.2 h1:3ZzHfb4OQS0r0NsQpsIrnBscgOE058KIDty3b45r00E= +forge.lthn.ai/core/lint v0.3.2/go.mod h1:fInfXFlOCljqWh6fkjHqAUXok5vhblKc+toQJIihIPY= +github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs= +github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM= +github.com/ProtonMail/go-crypto v1.4.0 h1:Zq/pbM3F5DFgJiMouxEdSVY44MVoQNEKp5d5QxIQceQ= +github.com/ProtonMail/go-crypto v1.4.0/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= +github.com/Snider/Borg v0.2.0 h1:iCyDhY4WTXi39+FexRwXbn2YpZ2U9FUXVXDZk9xRCXQ= +github.com/Snider/Borg v0.2.0/go.mod h1:TqlKnfRo9okioHbgrZPfWjQsztBV0Nfskz4Om1/vdMY= +github.com/TwiN/go-color v1.4.1 h1:mqG0P/KBgHKVqmtL5ye7K0/Gr4l6hTksPgTgMk3mUzc= +github.com/TwiN/go-color v1.4.1/go.mod h1:WcPf/jtiW95WBIsEeY1Lc/b8aaWoiqQpu5cf8WFxu+s= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= +github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= +github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= +github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= +github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= +github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= +github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= +github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= +github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= +github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= +github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= +github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= +github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +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/getkin/kin-openapi v0.134.0 h1:/L5+1+kfe6dXh8Ot/wqiTgUkjOIEJiC0bbYVziHB8rU= +github.com/getkin/kin-openapi v0.134.0/go.mod h1:wK6ZLG/VgoETO9pcLJ/VmAtIcl/DNlMayNTb716EUxE= +github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= +github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= +github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA= +github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0= +github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo= +github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU= +github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM= +github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= +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/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +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/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= +github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kluctl/go-embed-python v0.0.0-3.13.1-20241219-1 h1:x1cSEj4Ug5mpuZgUHLvUmlc5r//KHFn6iYiRSrRcVy4= +github.com/kluctl/go-embed-python v0.0.0-3.13.1-20241219-1/go.mod h1:3ebNU9QBrNpUO+Hj6bHaGpkh5pymDHQ+wwVPHTE4mCE= +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= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= +github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= +github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI= +github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw= +github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= +github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= +github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= +github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= +github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M= +github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +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-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/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= +github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/oasdiff/kin-openapi v0.136.1 h1:x1G9doDyPcagCNXDcMK5dt5yAmIgsSCiK7F5gPUiQdM= +github.com/oasdiff/oasdiff v1.12.3 h1:eUzJ/AiyyCY1KwUZPv7fosgDyETacIZbFesJrRz+QdY= +github.com/oasdiff/yaml v0.0.1 h1:dPrn0F2PJ7HdzHPndJkArvB2Fw0cwgFdVUKCEkoFuds= +github.com/oasdiff/yaml3 v0.0.1 h1:kReOSraQLTxuuGNX9aNeJ7tcsvUB2MS+iupdUrWe4Z0= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= +github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= +github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ= +github.com/wI2L/jsondiff v0.7.0/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc= +github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA= +github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= +github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +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/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= +modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw= +modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= +modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw= +modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= +modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/cmd/help/cmd.go b/cmd/core/help/cmd.go similarity index 100% rename from cmd/help/cmd.go rename to cmd/core/help/cmd.go diff --git a/cmd/core/main.go b/cmd/core/main.go new file mode 100644 index 00000000..cfc18809 --- /dev/null +++ b/cmd/core/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "forge.lthn.ai/core/cli/cmd/core/config" + "forge.lthn.ai/core/cli/cmd/core/doctor" + "forge.lthn.ai/core/cli/cmd/core/help" + "forge.lthn.ai/core/cli/cmd/core/pkgcmd" + "forge.lthn.ai/core/cli/pkg/cli" + + // Ecosystem commands — self-register via init() + cli.RegisterCommands() + _ "forge.lthn.ai/core/go-build/cmd/build" + _ "forge.lthn.ai/core/go-build/cmd/ci" + _ "forge.lthn.ai/core/go-build/cmd/sdk" + _ "forge.lthn.ai/core/go-crypt/cmd/crypt" + _ "forge.lthn.ai/core/go-devops/cmd/deploy" + _ "forge.lthn.ai/core/go-devops/cmd/dev" + _ "forge.lthn.ai/core/go-devops/cmd/docs" + _ "forge.lthn.ai/core/go-devops/cmd/gitcmd" + _ "forge.lthn.ai/core/go-devops/cmd/setup" + _ "forge.lthn.ai/core/go-scm/cmd/collect" + _ "forge.lthn.ai/core/go-scm/cmd/forge" + _ "forge.lthn.ai/core/go-scm/cmd/gitea" + _ "forge.lthn.ai/core/lint/cmd/qa" +) + +func main() { + cli.Main( + cli.WithCommands("config", config.AddConfigCommands), + cli.WithCommands("doctor", doctor.AddDoctorCommands), + cli.WithCommands("help", help.AddHelpCommands), + cli.WithCommands("pkg", pkgcmd.AddPkgCommands), + ) +} diff --git a/cmd/pkgcmd/cmd_commands.go b/cmd/core/pkgcmd/cmd_commands.go similarity index 100% rename from cmd/pkgcmd/cmd_commands.go rename to cmd/core/pkgcmd/cmd_commands.go diff --git a/cmd/pkgcmd/cmd_install.go b/cmd/core/pkgcmd/cmd_install.go similarity index 99% rename from cmd/pkgcmd/cmd_install.go rename to cmd/core/pkgcmd/cmd_install.go index f61b65da..a4869103 100644 --- a/cmd/pkgcmd/cmd_install.go +++ b/cmd/core/pkgcmd/cmd_install.go @@ -2,7 +2,6 @@ package pkgcmd import ( "context" - "errors" "fmt" "os" "path/filepath" @@ -14,6 +13,10 @@ import ( "github.com/spf13/cobra" ) +import ( + "errors" +) + var ( installTargetDir string installAddToReg bool diff --git a/cmd/pkgcmd/cmd_manage.go b/cmd/core/pkgcmd/cmd_manage.go similarity index 100% rename from cmd/pkgcmd/cmd_manage.go rename to cmd/core/pkgcmd/cmd_manage.go diff --git a/cmd/pkgcmd/cmd_pkg.go b/cmd/core/pkgcmd/cmd_pkg.go similarity index 100% rename from cmd/pkgcmd/cmd_pkg.go rename to cmd/core/pkgcmd/cmd_pkg.go diff --git a/cmd/pkgcmd/cmd_remove.go b/cmd/core/pkgcmd/cmd_remove.go similarity index 100% rename from cmd/pkgcmd/cmd_remove.go rename to cmd/core/pkgcmd/cmd_remove.go diff --git a/cmd/pkgcmd/cmd_remove_test.go b/cmd/core/pkgcmd/cmd_remove_test.go similarity index 100% rename from cmd/pkgcmd/cmd_remove_test.go rename to cmd/core/pkgcmd/cmd_remove_test.go diff --git a/cmd/pkgcmd/cmd_search.go b/cmd/core/pkgcmd/cmd_search.go similarity index 100% rename from cmd/pkgcmd/cmd_search.go rename to cmd/core/pkgcmd/cmd_search.go diff --git a/cmd/gocmd/cmd_commands.go b/cmd/gocmd/cmd_commands.go deleted file mode 100644 index 5b2943a4..00000000 --- a/cmd/gocmd/cmd_commands.go +++ /dev/null @@ -1,15 +0,0 @@ -// Package gocmd provides Go development commands with enhanced output. -// -// Note: Package named gocmd because 'go' is a reserved keyword. -// -// Commands: -// - test: Run tests with colour-coded coverage summary -// - cov: Run tests with detailed coverage reports (HTML, thresholds) -// - fmt: Format code using goimports or gofmt -// - lint: Run golangci-lint -// - install: Install binary to $GOPATH/bin -// - mod: Module management (tidy, download, verify, graph) -// - work: Workspace management (sync, init, use) -// -// Sets MACOSX_DEPLOYMENT_TARGET to suppress linker warnings on macOS. -package gocmd diff --git a/cmd/gocmd/cmd_format.go b/cmd/gocmd/cmd_format.go deleted file mode 100644 index 5eb643b1..00000000 --- a/cmd/gocmd/cmd_format.go +++ /dev/null @@ -1,177 +0,0 @@ -package gocmd - -import ( - "bufio" - "os" - "os/exec" - "path/filepath" - "strings" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" -) - -var ( - fmtFix bool - fmtDiff bool - fmtCheck bool - fmtAll bool -) - -func addGoFmtCommand(parent *cli.Command) { - fmtCmd := &cli.Command{ - Use: "fmt", - Short: "Format Go code", - Long: "Format Go code using goimports or gofmt. By default only checks changed files.", - RunE: func(cmd *cli.Command, args []string) error { - // Get list of files to check - var files []string - if fmtAll { - // Check all Go files - files = []string{"."} - } else { - // Only check changed Go files (git-aware) - files = getChangedGoFiles() - if len(files) == 0 { - cli.Print("%s\n", i18n.T("cmd.go.fmt.no_changes")) - return nil - } - } - - // Validate flag combinations - if fmtCheck && fmtFix { - return cli.Err("--check and --fix are mutually exclusive") - } - - fmtArgs := []string{} - if fmtFix { - fmtArgs = append(fmtArgs, "-w") - } - if fmtDiff { - fmtArgs = append(fmtArgs, "-d") - } - if !fmtFix && !fmtDiff { - fmtArgs = append(fmtArgs, "-l") - } - fmtArgs = append(fmtArgs, files...) - - // Try goimports first, fall back to gofmt - var execCmd *exec.Cmd - if _, err := exec.LookPath("goimports"); err == nil { - execCmd = exec.Command("goimports", fmtArgs...) - } else { - execCmd = exec.Command("gofmt", fmtArgs...) - } - - // For --check mode, capture output to detect unformatted files - if fmtCheck { - output, err := execCmd.CombinedOutput() - if err != nil { - _, _ = os.Stderr.Write(output) - return err - } - if len(output) > 0 { - _, _ = os.Stdout.Write(output) - return cli.Err("files need formatting (use --fix)") - } - return nil - } - - execCmd.Stdout = os.Stdout - execCmd.Stderr = os.Stderr - return execCmd.Run() - }, - } - - fmtCmd.Flags().BoolVar(&fmtFix, "fix", false, i18n.T("common.flag.fix")) - fmtCmd.Flags().BoolVar(&fmtDiff, "diff", false, i18n.T("common.flag.diff")) - fmtCmd.Flags().BoolVar(&fmtCheck, "check", false, i18n.T("cmd.go.fmt.flag.check")) - fmtCmd.Flags().BoolVar(&fmtAll, "all", false, i18n.T("cmd.go.fmt.flag.all")) - - parent.AddCommand(fmtCmd) -} - -// getChangedGoFiles returns Go files that have been modified, staged, or are untracked. -func getChangedGoFiles() []string { - var files []string - - // Get modified and staged files - cmd := exec.Command("git", "diff", "--name-only", "--diff-filter=ACMR", "HEAD") - output, err := cmd.Output() - if err == nil { - files = append(files, filterGoFiles(string(output))...) - } - - // Get untracked files - cmd = exec.Command("git", "ls-files", "--others", "--exclude-standard") - output, err = cmd.Output() - if err == nil { - files = append(files, filterGoFiles(string(output))...) - } - - // Deduplicate - seen := make(map[string]bool) - var unique []string - for _, f := range files { - if !seen[f] { - seen[f] = true - // Verify file exists (might have been deleted) - if _, err := os.Stat(f); err == nil { - unique = append(unique, f) - } - } - } - - return unique -} - -// filterGoFiles filters a newline-separated list of files to only include .go files. -func filterGoFiles(output string) []string { - var goFiles []string - scanner := bufio.NewScanner(strings.NewReader(output)) - for scanner.Scan() { - file := strings.TrimSpace(scanner.Text()) - if file != "" && filepath.Ext(file) == ".go" { - goFiles = append(goFiles, file) - } - } - return goFiles -} - -var ( - lintFix bool - lintAll bool -) - -func addGoLintCommand(parent *cli.Command) { - lintCmd := &cli.Command{ - Use: "lint", - Short: "Run golangci-lint", - Long: "Run golangci-lint for comprehensive static analysis. By default only lints changed files.", - RunE: func(cmd *cli.Command, args []string) error { - lintArgs := []string{"run"} - if lintFix { - lintArgs = append(lintArgs, "--fix") - } - - if !lintAll { - // Use --new-from-rev=HEAD to only report issues in uncommitted changes - // This is golangci-lint's native way to handle incremental linting - lintArgs = append(lintArgs, "--new-from-rev=HEAD") - } - - // Always lint all packages - lintArgs = append(lintArgs, "./...") - - execCmd := exec.Command("golangci-lint", lintArgs...) - execCmd.Stdout = os.Stdout - execCmd.Stderr = os.Stderr - return execCmd.Run() - }, - } - - lintCmd.Flags().BoolVar(&lintFix, "fix", false, i18n.T("common.flag.fix")) - lintCmd.Flags().BoolVar(&lintAll, "all", false, i18n.T("cmd.go.lint.flag.all")) - - parent.AddCommand(lintCmd) -} diff --git a/cmd/gocmd/cmd_fuzz.go b/cmd/gocmd/cmd_fuzz.go deleted file mode 100644 index 305ec78d..00000000 --- a/cmd/gocmd/cmd_fuzz.go +++ /dev/null @@ -1,169 +0,0 @@ -package gocmd - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "regexp" - "strings" - "time" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" -) - -var ( - fuzzDuration time.Duration - fuzzPkg string - fuzzRun string - fuzzVerbose bool -) - -func addGoFuzzCommand(parent *cli.Command) { - fuzzCmd := &cli.Command{ - Use: "fuzz", - Short: "Run Go fuzz tests", - Long: `Run Go fuzz tests with configurable duration. - -Discovers Fuzz* functions across the project and runs each with go test -fuzz. - -Examples: - core go fuzz # Run all fuzz targets for 10s each - core go fuzz --duration=30s # Run each target for 30s - core go fuzz --pkg=./pkg/... # Fuzz specific package - core go fuzz --run=FuzzE # Run only matching fuzz targets`, - RunE: func(cmd *cli.Command, args []string) error { - return runGoFuzz(fuzzDuration, fuzzPkg, fuzzRun, fuzzVerbose) - }, - } - - fuzzCmd.Flags().DurationVar(&fuzzDuration, "duration", 10*time.Second, "Duration per fuzz target") - fuzzCmd.Flags().StringVar(&fuzzPkg, "pkg", "", "Package to fuzz (default: auto-discover)") - fuzzCmd.Flags().StringVar(&fuzzRun, "run", "", "Only run fuzz targets matching pattern") - fuzzCmd.Flags().BoolVarP(&fuzzVerbose, "verbose", "v", false, "Verbose output") - - parent.AddCommand(fuzzCmd) -} - -// fuzzTarget represents a discovered fuzz function and its package. -type fuzzTarget struct { - Pkg string - Name string -} - -func runGoFuzz(duration time.Duration, pkg, run string, verbose bool) error { - cli.Print("%s %s\n", dimStyle.Render(i18n.Label("fuzz")), i18n.ProgressSubject("run", "fuzz tests")) - cli.Blank() - - targets, err := discoverFuzzTargets(pkg, run) - if err != nil { - return cli.Wrap(err, "discover fuzz targets") - } - - if len(targets) == 0 { - cli.Print(" %s no fuzz targets found\n", dimStyle.Render("—")) - return nil - } - - cli.Print(" %s %d target(s), %s each\n", dimStyle.Render(i18n.Label("targets")), len(targets), duration) - cli.Blank() - - passed := 0 - failed := 0 - - for _, t := range targets { - cli.Print(" %s %s in %s\n", dimStyle.Render("→"), t.Name, t.Pkg) - - args := []string{ - "test", - fmt.Sprintf("-fuzz=^%s$", t.Name), - fmt.Sprintf("-fuzztime=%s", duration), - "-run=^$", // Don't run unit tests - } - if verbose { - args = append(args, "-v") - } - args = append(args, t.Pkg) - - cmd := exec.Command("go", args...) - cmd.Env = append(os.Environ(), "CGO_ENABLED=0") - cmd.Dir, _ = os.Getwd() - - output, runErr := cmd.CombinedOutput() - outputStr := string(output) - - if runErr != nil { - failed++ - cli.Print(" %s %s\n", errorStyle.Render(cli.Glyph(":cross:")), runErr.Error()) - if outputStr != "" { - cli.Text(outputStr) - } - } else { - passed++ - cli.Print(" %s %s\n", successStyle.Render(cli.Glyph(":check:")), i18n.T("i18n.done.pass")) - if verbose && outputStr != "" { - cli.Text(outputStr) - } - } - } - - cli.Blank() - if failed > 0 { - cli.Print("%s %d passed, %d failed\n", errorStyle.Render(cli.Glyph(":cross:")), passed, failed) - return cli.Err("fuzz: %d target(s) failed", failed) - } - - cli.Print("%s %d passed\n", successStyle.Render(cli.Glyph(":check:")), passed) - return nil -} - -// discoverFuzzTargets scans for Fuzz* functions in test files. -func discoverFuzzTargets(pkg, pattern string) ([]fuzzTarget, error) { - root := "." - if pkg != "" { - // Convert Go package pattern to filesystem path - root = strings.TrimPrefix(pkg, "./") - root = strings.TrimSuffix(root, "/...") - } - - fuzzRe := regexp.MustCompile(`^func\s+(Fuzz\w+)\s*\(\s*\w+\s+\*testing\.F\s*\)`) - var matchRe *regexp.Regexp - if pattern != "" { - var err error - matchRe, err = regexp.Compile(pattern) - if err != nil { - return nil, fmt.Errorf("invalid --run pattern: %w", err) - } - } - - var targets []fuzzTarget - err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { - if err != nil { - return nil - } - if info.IsDir() || !strings.HasSuffix(info.Name(), "_test.go") { - return nil - } - - data, readErr := os.ReadFile(path) - if readErr != nil { - return nil - } - - dir := "./" + filepath.Dir(path) - for line := range strings.SplitSeq(string(data), "\n") { - m := fuzzRe.FindStringSubmatch(line) - if m == nil { - continue - } - name := m[1] - if matchRe != nil && !matchRe.MatchString(name) { - continue - } - targets = append(targets, fuzzTarget{Pkg: dir, Name: name}) - } - return nil - }) - return targets, err -} diff --git a/cmd/gocmd/cmd_go.go b/cmd/gocmd/cmd_go.go deleted file mode 100644 index f272e5c8..00000000 --- a/cmd/gocmd/cmd_go.go +++ /dev/null @@ -1,36 +0,0 @@ -// Package gocmd provides Go development commands. -// -// Note: Package named gocmd because 'go' is a reserved keyword. -package gocmd - -import ( - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" -) - -// Style aliases for shared styles -var ( - successStyle = cli.SuccessStyle - errorStyle = cli.ErrorStyle - dimStyle = cli.DimStyle -) - -// AddGoCommands adds Go development commands. -func AddGoCommands(root *cli.Command) { - goCmd := &cli.Command{ - Use: "go", - Short: i18n.T("cmd.go.short"), - Long: i18n.T("cmd.go.long"), - } - - root.AddCommand(goCmd) - addGoQACommand(goCmd) - addGoTestCommand(goCmd) - addGoCovCommand(goCmd) - addGoFmtCommand(goCmd) - addGoLintCommand(goCmd) - addGoInstallCommand(goCmd) - addGoModCommand(goCmd) - addGoWorkCommand(goCmd) - addGoFuzzCommand(goCmd) -} diff --git a/cmd/gocmd/cmd_gotest.go b/cmd/gocmd/cmd_gotest.go deleted file mode 100644 index 688472a7..00000000 --- a/cmd/gocmd/cmd_gotest.go +++ /dev/null @@ -1,430 +0,0 @@ -package gocmd - -import ( - "bufio" - "errors" - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "regexp" - "strconv" - "strings" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" -) - -var ( - testCoverage bool - testPkg string - testRun string - testShort bool - testRace bool - testJSON bool - testVerbose bool -) - -func addGoTestCommand(parent *cli.Command) { - testCmd := &cli.Command{ - Use: "test", - Short: "Run Go tests", - Long: "Run Go tests with optional coverage, filtering, and race detection", - RunE: func(cmd *cli.Command, args []string) error { - return runGoTest(testCoverage, testPkg, testRun, testShort, testRace, testJSON, testVerbose) - }, - } - - testCmd.Flags().BoolVar(&testCoverage, "coverage", false, "Generate coverage report") - testCmd.Flags().StringVar(&testPkg, "pkg", "", "Package to test") - testCmd.Flags().StringVar(&testRun, "run", "", "Run only tests matching pattern") - testCmd.Flags().BoolVar(&testShort, "short", false, "Run only short tests") - testCmd.Flags().BoolVar(&testRace, "race", false, "Enable race detector") - testCmd.Flags().BoolVar(&testJSON, "json", false, "Output as JSON") - testCmd.Flags().BoolVarP(&testVerbose, "verbose", "v", false, "Verbose output") - - parent.AddCommand(testCmd) -} - -func runGoTest(coverage bool, pkg, run string, short, race, jsonOut, verbose bool) error { - if pkg == "" { - pkg = "./..." - } - - args := []string{"test"} - - var covPath string - if coverage { - args = append(args, "-cover", "-covermode=atomic") - covFile, err := os.CreateTemp("", "coverage-*.out") - if err == nil { - covPath = covFile.Name() - _ = covFile.Close() - args = append(args, "-coverprofile="+covPath) - defer os.Remove(covPath) - } - } - - if run != "" { - args = append(args, "-run", run) - } - if short { - args = append(args, "-short") - } - if race { - args = append(args, "-race") - } - if verbose { - args = append(args, "-v") - } - - args = append(args, pkg) - - if !jsonOut { - cli.Print("%s %s\n", dimStyle.Render(i18n.Label("test")), i18n.ProgressSubject("run", "tests")) - cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("package")), pkg) - cli.Blank() - } - - cmd := exec.Command("go", args...) - cmd.Env = append(os.Environ(), "CGO_ENABLED=0") - cmd.Dir, _ = os.Getwd() - - output, err := cmd.CombinedOutput() - outputStr := string(output) - - // Filter linker warnings - lines := strings.Split(outputStr, "\n") - var filtered []string - for _, line := range lines { - if !strings.Contains(line, "ld: warning:") { - filtered = append(filtered, line) - } - } - outputStr = strings.Join(filtered, "\n") - - // Parse results - passed, failed, skipped := parseTestResults(outputStr) - cov := parseOverallCoverage(outputStr) - - if jsonOut { - cli.Print(`{"passed":%d,"failed":%d,"skipped":%d,"coverage":%.1f,"exit_code":%d}`, - passed, failed, skipped, cov, cmd.ProcessState.ExitCode()) - cli.Blank() - return err - } - - // Print filtered output if verbose or failed - if verbose || err != nil { - cli.Text(outputStr) - } - - // Summary - if err == nil { - cli.Print(" %s %s\n", successStyle.Render(cli.Glyph(":check:")), i18n.T("i18n.count.test", passed)+" "+i18n.T("i18n.done.pass")) - } else { - cli.Print(" %s %s, %s\n", errorStyle.Render(cli.Glyph(":cross:")), - i18n.T("i18n.count.test", passed)+" "+i18n.T("i18n.done.pass"), - i18n.T("i18n.count.test", failed)+" "+i18n.T("i18n.done.fail")) - } - - if cov > 0 { - cli.Print("\n %s %s\n", cli.KeyStyle.Render(i18n.Label("statements")), formatCoverage(cov)) - if covPath != "" { - branchCov, err := calculateBlockCoverage(covPath) - if err != nil { - cli.Print(" %s %s\n", cli.KeyStyle.Render(i18n.Label("branches")), cli.ErrorStyle.Render("unable to calculate")) - } else { - cli.Print(" %s %s\n", cli.KeyStyle.Render(i18n.Label("branches")), formatCoverage(branchCov)) - } - } - } - - if err == nil { - cli.Print("\n%s\n", successStyle.Render(i18n.T("i18n.done.pass"))) - } else { - cli.Print("\n%s\n", errorStyle.Render(i18n.T("i18n.done.fail"))) - } - - return err -} - -func parseTestResults(output string) (passed, failed, skipped int) { - passRe := regexp.MustCompile(`(?m)^ok\s+`) - failRe := regexp.MustCompile(`(?m)^FAIL\s+`) - skipRe := regexp.MustCompile(`(?m)^\?\s+`) - - passed = len(passRe.FindAllString(output, -1)) - failed = len(failRe.FindAllString(output, -1)) - skipped = len(skipRe.FindAllString(output, -1)) - return -} - -func parseOverallCoverage(output string) float64 { - re := regexp.MustCompile(`coverage:\s+([\d.]+)%`) - matches := re.FindAllStringSubmatch(output, -1) - if len(matches) == 0 { - return 0 - } - - var total float64 - for _, m := range matches { - var cov float64 - _, _ = fmt.Sscanf(m[1], "%f", &cov) - total += cov - } - return total / float64(len(matches)) -} - -var ( - covPkg string - covHTML bool - covOpen bool - covThreshold float64 - covBranchThreshold float64 - covOutput string -) - -func addGoCovCommand(parent *cli.Command) { - covCmd := &cli.Command{ - Use: "cov", - Short: "Run tests with coverage report", - Long: "Run tests with detailed coverage reports, HTML output, and threshold checking", - RunE: func(cmd *cli.Command, args []string) error { - pkg := covPkg - if pkg == "" { - // Auto-discover packages with tests - pkgs, err := findTestPackages(".") - if err != nil { - return cli.Wrap(err, i18n.T("i18n.fail.find", "test packages")) - } - if len(pkgs) == 0 { - return errors.New("no test packages found") - } - pkg = strings.Join(pkgs, " ") - } - - // Create temp file for coverage data - covFile, err := os.CreateTemp("", "coverage-*.out") - if err != nil { - return cli.Wrap(err, i18n.T("i18n.fail.create", "coverage file")) - } - covPath := covFile.Name() - _ = covFile.Close() - defer func() { - if covOutput == "" { - _ = os.Remove(covPath) - } else { - // Copy to output destination before removing - src, _ := os.Open(covPath) - dst, _ := os.Create(covOutput) - if src != nil && dst != nil { - _, _ = io.Copy(dst, src) - _ = src.Close() - _ = dst.Close() - } - _ = os.Remove(covPath) - } - }() - - cli.Print("%s %s\n", dimStyle.Render(i18n.Label("coverage")), i18n.ProgressSubject("run", "tests")) - // Truncate package list if too long for display - displayPkg := pkg - if len(displayPkg) > 60 { - displayPkg = displayPkg[:57] + "..." - } - cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("package")), displayPkg) - cli.Blank() - - // Run tests with coverage - // We need to split pkg into individual arguments if it contains spaces - pkgArgs := strings.Fields(pkg) - cmdArgs := append([]string{"test", "-coverprofile=" + covPath, "-covermode=atomic"}, pkgArgs...) - - goCmd := exec.Command("go", cmdArgs...) - goCmd.Env = append(os.Environ(), "CGO_ENABLED=0") - goCmd.Stdout = os.Stdout - goCmd.Stderr = os.Stderr - - testErr := goCmd.Run() - - // Get coverage percentage - coverCmd := exec.Command("go", "tool", "cover", "-func="+covPath) - covOutput, err := coverCmd.Output() - if err != nil { - if testErr != nil { - return testErr - } - return cli.Wrap(err, i18n.T("i18n.fail.get", "coverage")) - } - - // Parse total coverage from last line - lines := strings.Split(strings.TrimSpace(string(covOutput)), "\n") - var statementCov float64 - if len(lines) > 0 { - lastLine := lines[len(lines)-1] - // Format: "total: (statements) XX.X%" - if strings.Contains(lastLine, "total:") { - parts := strings.Fields(lastLine) - if len(parts) >= 3 { - covStr := strings.TrimSuffix(parts[len(parts)-1], "%") - _, _ = fmt.Sscanf(covStr, "%f", &statementCov) - } - } - } - - // Calculate branch coverage (block coverage) - branchCov, err := calculateBlockCoverage(covPath) - if err != nil { - return cli.Wrap(err, "calculate branch coverage") - } - - // Print coverage summary - cli.Blank() - cli.Print(" %s %s\n", cli.KeyStyle.Render(i18n.Label("statements")), formatCoverage(statementCov)) - cli.Print(" %s %s\n", cli.KeyStyle.Render(i18n.Label("branches")), formatCoverage(branchCov)) - - // Generate HTML if requested - if covHTML || covOpen { - htmlPath := "coverage.html" - htmlCmd := exec.Command("go", "tool", "cover", "-html="+covPath, "-o="+htmlPath) - if err := htmlCmd.Run(); err != nil { - return cli.Wrap(err, i18n.T("i18n.fail.generate", "HTML")) - } - cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("html")), htmlPath) - - if covOpen { - // Open in browser - var openCmd *exec.Cmd - switch { - case exec.Command("which", "open").Run() == nil: - openCmd = exec.Command("open", htmlPath) - case exec.Command("which", "xdg-open").Run() == nil: - openCmd = exec.Command("xdg-open", htmlPath) - default: - cli.Print(" %s\n", dimStyle.Render("Open coverage.html in your browser")) - } - if openCmd != nil { - _ = openCmd.Run() - } - } - } - - // Check thresholds - if covThreshold > 0 && statementCov < covThreshold { - cli.Print("\n%s Statements: %.1f%% < %.1f%%\n", errorStyle.Render(i18n.T("i18n.fail.meet", "threshold")), statementCov, covThreshold) - return errors.New("statement coverage below threshold") - } - if covBranchThreshold > 0 && branchCov < covBranchThreshold { - cli.Print("\n%s Branches: %.1f%% < %.1f%%\n", errorStyle.Render(i18n.T("i18n.fail.meet", "threshold")), branchCov, covBranchThreshold) - return errors.New("branch coverage below threshold") - } - - if testErr != nil { - return testErr - } - - cli.Print("\n%s\n", successStyle.Render(i18n.T("i18n.done.pass"))) - return nil - }, - } - - covCmd.Flags().StringVar(&covPkg, "pkg", "", "Package to test") - covCmd.Flags().BoolVar(&covHTML, "html", false, "Generate HTML report") - covCmd.Flags().BoolVar(&covOpen, "open", false, "Open HTML report in browser") - covCmd.Flags().Float64Var(&covThreshold, "threshold", 0, "Minimum statement coverage percentage") - covCmd.Flags().Float64Var(&covBranchThreshold, "branch-threshold", 0, "Minimum branch coverage percentage") - covCmd.Flags().StringVarP(&covOutput, "output", "o", "", "Output file for coverage profile") - - parent.AddCommand(covCmd) -} - -// calculateBlockCoverage parses a Go coverage profile and returns the percentage of basic -// blocks that have a non-zero execution count. Go's coverage profile contains one line per -// basic block, where the last field is the execution count, not explicit branch coverage. -// The resulting block coverage is used here only as a proxy for branch coverage; computing -// true branch coverage would require more detailed control-flow analysis. -func calculateBlockCoverage(path string) (float64, error) { - file, err := os.Open(path) - if err != nil { - return 0, err - } - defer file.Close() - - scanner := bufio.NewScanner(file) - var totalBlocks, coveredBlocks int - - // Skip the first line (mode: atomic/set/count) - if !scanner.Scan() { - return 0, nil - } - - for scanner.Scan() { - line := scanner.Text() - if line == "" { - continue - } - fields := strings.Fields(line) - if len(fields) < 3 { - continue - } - - // Last field is the count - count, err := strconv.Atoi(fields[len(fields)-1]) - if err != nil { - continue - } - - totalBlocks++ - if count > 0 { - coveredBlocks++ - } - } - - if err := scanner.Err(); err != nil { - return 0, err - } - - if totalBlocks == 0 { - return 0, nil - } - - return (float64(coveredBlocks) / float64(totalBlocks)) * 100, nil -} - -func findTestPackages(root string) ([]string, error) { - pkgMap := make(map[string]bool) - err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { - if err != nil { - return nil - } - if !info.IsDir() && strings.HasSuffix(info.Name(), "_test.go") { - dir := filepath.Dir(path) - if !strings.HasPrefix(dir, ".") { - dir = "./" + dir - } - pkgMap[dir] = true - } - return nil - }) - if err != nil { - return nil, err - } - - var pkgs []string - for pkg := range pkgMap { - pkgs = append(pkgs, pkg) - } - return pkgs, nil -} - -func formatCoverage(cov float64) string { - s := fmt.Sprintf("%.1f%%", cov) - if cov >= 80 { - return cli.SuccessStyle.Render(s) - } else if cov >= 50 { - return cli.WarningStyle.Render(s) - } - return cli.ErrorStyle.Render(s) -} diff --git a/cmd/gocmd/cmd_qa.go b/cmd/gocmd/cmd_qa.go deleted file mode 100644 index 69d825ff..00000000 --- a/cmd/gocmd/cmd_qa.go +++ /dev/null @@ -1,635 +0,0 @@ -package gocmd - -import ( - "context" - "encoding/json" - "fmt" - "os" - "os/exec" - "regexp" - "strings" - "time" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/lint/cmd/qa" - "forge.lthn.ai/core/go-i18n" -) - -// QA command flags - comprehensive options for all agents -var ( - qaFix bool - qaChanged bool - qaAll bool - qaSkip string - qaOnly string - qaCoverage bool - qaThreshold float64 - qaBranchThreshold float64 - qaDocblockThreshold float64 - qaJSON bool - qaVerbose bool - qaQuiet bool - qaTimeout time.Duration - qaShort bool - qaRace bool - qaBench bool - qaFailFast bool - qaMod bool - qaCI bool -) - -func addGoQACommand(parent *cli.Command) { - qaCmd := &cli.Command{ - Use: "qa", - Short: "Run QA checks", - Long: `Run comprehensive code quality checks for Go projects. - -Checks available: fmt, vet, lint, test, race, fuzz, vuln, sec, bench, docblock - -Examples: - core go qa # Default: fmt, lint, test - core go qa --fix # Auto-fix formatting and lint issues - core go qa --only=test # Only run tests - core go qa --skip=vuln,sec # Skip vulnerability and security scans - core go qa --coverage --threshold=80 # Require 80% coverage - core go qa --changed # Only check changed files (git-aware) - core go qa --ci # CI mode: strict, coverage, fail-fast - core go qa --race --short # Quick tests with race detection - core go qa --json # Output results as JSON`, - RunE: runGoQA, - } - - // Fix and modification flags (persistent so subcommands inherit them) - qaCmd.PersistentFlags().BoolVar(&qaFix, "fix", false, "Auto-fix issues where possible") - qaCmd.PersistentFlags().BoolVar(&qaMod, "mod", false, "Run go mod tidy before checks") - - // Scope flags - qaCmd.PersistentFlags().BoolVar(&qaChanged, "changed", false, "Only check changed files (git-aware)") - qaCmd.PersistentFlags().BoolVar(&qaAll, "all", false, "Check all files (override git-aware)") - qaCmd.PersistentFlags().StringVar(&qaSkip, "skip", "", "Skip checks (comma-separated: fmt,vet,lint,test,race,fuzz,vuln,sec,bench)") - qaCmd.PersistentFlags().StringVar(&qaOnly, "only", "", "Only run these checks (comma-separated)") - - // Coverage flags - qaCmd.PersistentFlags().BoolVar(&qaCoverage, "coverage", false, "Include coverage reporting") - qaCmd.PersistentFlags().BoolVarP(&qaCoverage, "cov", "c", false, "Include coverage reporting (shorthand)") - qaCmd.PersistentFlags().Float64Var(&qaThreshold, "threshold", 0, "Minimum statement coverage threshold (0-100), fail if below") - qaCmd.PersistentFlags().Float64Var(&qaBranchThreshold, "branch-threshold", 0, "Minimum branch coverage threshold (0-100), fail if below") - qaCmd.PersistentFlags().Float64Var(&qaDocblockThreshold, "docblock-threshold", 80, "Minimum docblock coverage threshold (0-100)") - - // Test flags - qaCmd.PersistentFlags().BoolVar(&qaShort, "short", false, "Run tests with -short flag") - qaCmd.PersistentFlags().BoolVar(&qaRace, "race", false, "Include race detection in tests") - qaCmd.PersistentFlags().BoolVar(&qaBench, "bench", false, "Include benchmarks") - - // Output flags - qaCmd.PersistentFlags().BoolVar(&qaJSON, "json", false, "Output results as JSON") - qaCmd.PersistentFlags().BoolVarP(&qaVerbose, "verbose", "v", false, "Show verbose output") - qaCmd.PersistentFlags().BoolVarP(&qaQuiet, "quiet", "q", false, "Only show errors") - - // Control flags - qaCmd.PersistentFlags().DurationVar(&qaTimeout, "timeout", 10*time.Minute, "Timeout for all checks") - qaCmd.PersistentFlags().BoolVar(&qaFailFast, "fail-fast", false, "Stop on first failure") - qaCmd.PersistentFlags().BoolVar(&qaCI, "ci", false, "CI mode: strict checks, coverage required, fail-fast") - - // Preset subcommands for convenience - qaCmd.AddCommand(&cli.Command{ - Use: "quick", - Short: "Quick QA: fmt, vet, lint (no tests)", - RunE: func(cmd *cli.Command, args []string) error { qaOnly = "fmt,vet,lint"; return runGoQA(cmd, args) }, - }) - - qaCmd.AddCommand(&cli.Command{ - Use: "full", - Short: "Full QA: all checks including race, vuln, sec", - RunE: func(cmd *cli.Command, args []string) error { - qaOnly = "fmt,vet,lint,test,race,vuln,sec" - return runGoQA(cmd, args) - }, - }) - - qaCmd.AddCommand(&cli.Command{ - Use: "pre-commit", - Short: "Pre-commit checks: fmt --fix, lint --fix, test --short", - RunE: func(cmd *cli.Command, args []string) error { - qaFix = true - qaShort = true - qaOnly = "fmt,lint,test" - return runGoQA(cmd, args) - }, - }) - - qaCmd.AddCommand(&cli.Command{ - Use: "pr", - Short: "PR checks: full QA with coverage threshold", - RunE: func(cmd *cli.Command, args []string) error { - qaCoverage = true - if qaThreshold == 0 { - qaThreshold = 50 // Default PR threshold - } - qaOnly = "fmt,vet,lint,test" - return runGoQA(cmd, args) - }, - }) - - parent.AddCommand(qaCmd) -} - -// QAResult holds the result of a QA run for JSON output -type QAResult struct { - Success bool `json:"success"` - Duration string `json:"duration"` - Checks []CheckResult `json:"checks"` - Coverage *float64 `json:"coverage,omitempty"` - BranchCoverage *float64 `json:"branch_coverage,omitempty"` - Threshold *float64 `json:"threshold,omitempty"` - BranchThreshold *float64 `json:"branch_threshold,omitempty"` -} - -// CheckResult holds the result of a single check -type CheckResult struct { - Name string `json:"name"` - Passed bool `json:"passed"` - Duration string `json:"duration"` - Error string `json:"error,omitempty"` - Output string `json:"output,omitempty"` - FixHint string `json:"fix_hint,omitempty"` -} - -func runGoQA(cmd *cli.Command, args []string) error { - // Apply CI mode defaults - if qaCI { - qaCoverage = true - qaFailFast = true - if qaThreshold == 0 { - qaThreshold = 50 - } - } - - cwd, err := os.Getwd() - if err != nil { - return cli.Wrap(err, i18n.T("i18n.fail.get", "working directory")) - } - - // Detect if this is a Go project - if _, err := os.Stat("go.mod"); os.IsNotExist(err) { - return cli.Err("not a Go project (no go.mod found)") - } - - // Determine which checks to run - checkNames := determineChecks() - - if !qaJSON && !qaQuiet { - cli.Print("%s %s\n\n", cli.DimStyle.Render(i18n.Label("qa")), i18n.ProgressSubject("run", "Go QA")) - } - - // Run go mod tidy if requested - if qaMod { - if !qaQuiet { - cli.Print("%s %s\n", cli.DimStyle.Render("→"), "Running go mod tidy...") - } - modCmd := exec.Command("go", "mod", "tidy") - modCmd.Dir = cwd - if err := modCmd.Run(); err != nil { - return cli.Wrap(err, "go mod tidy failed") - } - } - - ctx, cancel := context.WithTimeout(context.Background(), qaTimeout) - defer cancel() - - startTime := time.Now() - checks := buildChecks(checkNames) - results := make([]CheckResult, 0, len(checks)) - passed := 0 - failed := 0 - - for _, check := range checks { - checkStart := time.Now() - - if !qaJSON && !qaQuiet { - cli.Print("%s %s\n", cli.DimStyle.Render("→"), i18n.Progress(check.Name)) - } - - output, err := runCheckCapture(ctx, cwd, check) - checkDuration := time.Since(checkStart) - - result := CheckResult{ - Name: check.Name, - Duration: checkDuration.Round(time.Millisecond).String(), - } - - if err != nil { - result.Passed = false - result.Error = err.Error() - if qaVerbose { - result.Output = output - } - result.FixHint = fixHintFor(check.Name, output) - failed++ - - if !qaJSON && !qaQuiet { - cli.Print(" %s %s\n", cli.ErrorStyle.Render(cli.Glyph(":cross:")), err.Error()) - if qaVerbose && output != "" { - cli.Text(output) - } - if result.FixHint != "" { - cli.Hint("fix", result.FixHint) - } - } - - if qaFailFast { - results = append(results, result) - break - } - } else { - result.Passed = true - if qaVerbose { - result.Output = output - } - passed++ - - if !qaJSON && !qaQuiet { - cli.Print(" %s %s\n", cli.SuccessStyle.Render(cli.Glyph(":check:")), i18n.T("i18n.done.pass")) - } - } - - results = append(results, result) - } - - // Run coverage if requested - var coverageVal *float64 - var branchVal *float64 - if qaCoverage && !qaFailFast || (qaCoverage && failed == 0) { - cov, branch, err := runCoverage(ctx, cwd) - if err == nil { - coverageVal = &cov - branchVal = &branch - if !qaJSON && !qaQuiet { - cli.Print("\n%s %.1f%%\n", cli.DimStyle.Render("Statement Coverage:"), cov) - cli.Print("%s %.1f%%\n", cli.DimStyle.Render("Branch Coverage:"), branch) - } - if qaThreshold > 0 && cov < qaThreshold { - failed++ - if !qaJSON && !qaQuiet { - cli.Print(" %s Statement coverage %.1f%% below threshold %.1f%%\n", - cli.ErrorStyle.Render(cli.Glyph(":cross:")), cov, qaThreshold) - } - } - if qaBranchThreshold > 0 && branch < qaBranchThreshold { - failed++ - if !qaJSON && !qaQuiet { - cli.Print(" %s Branch coverage %.1f%% below threshold %.1f%%\n", - cli.ErrorStyle.Render(cli.Glyph(":cross:")), branch, qaBranchThreshold) - } - } - - if failed > 0 && !qaJSON && !qaQuiet { - cli.Hint("fix", "Run 'core go cov --open' to see uncovered lines, then add tests.") - } - } - } - - duration := time.Since(startTime).Round(time.Millisecond) - - if qaJSON { - return emitQAJSON(results, coverageVal, branchVal, failed, duration) - } - - return emitQASummary(passed, failed, duration) -} - -func emitQAJSON(results []CheckResult, coverageVal, branchVal *float64, failed int, duration time.Duration) error { - qaResult := QAResult{ - Success: failed == 0, - Duration: duration.String(), - Checks: results, - Coverage: coverageVal, - BranchCoverage: branchVal, - } - if qaThreshold > 0 { - qaResult.Threshold = &qaThreshold - } - if qaBranchThreshold > 0 { - qaResult.BranchThreshold = &qaBranchThreshold - } - enc := json.NewEncoder(os.Stdout) - enc.SetIndent("", " ") - return enc.Encode(qaResult) -} - -func emitQASummary(passed, failed int, duration time.Duration) error { - if !qaQuiet { - cli.Blank() - if failed > 0 { - cli.Print("%s %s, %s (%s)\n", - cli.ErrorStyle.Render(cli.Glyph(":cross:")), - i18n.T("i18n.count.check", passed)+" "+i18n.T("i18n.done.pass"), - i18n.T("i18n.count.check", failed)+" "+i18n.T("i18n.done.fail"), - duration) - } else { - cli.Print("%s %s (%s)\n", - cli.SuccessStyle.Render(cli.Glyph(":check:")), - i18n.T("i18n.count.check", passed)+" "+i18n.T("i18n.done.pass"), - duration) - } - } - - if failed > 0 { - return cli.Err("QA checks failed: %d passed, %d failed", passed, failed) - } - return nil -} - -func determineChecks() []string { - // If --only is specified, use those - if qaOnly != "" { - return strings.Split(qaOnly, ",") - } - - // Default checks - checks := []string{"fmt", "lint", "test", "fuzz", "docblock"} - - // Add race if requested - if qaRace { - // Replace test with race (which includes test) - for i, c := range checks { - if c == "test" { - checks[i] = "race" - break - } - } - } - - // Add bench if requested - if qaBench { - checks = append(checks, "bench") - } - - // Remove skipped checks - if qaSkip != "" { - skipMap := make(map[string]bool) - for _, s := range strings.Split(qaSkip, ",") { - skipMap[strings.TrimSpace(s)] = true - } - filtered := make([]string, 0, len(checks)) - for _, c := range checks { - if !skipMap[c] { - filtered = append(filtered, c) - } - } - checks = filtered - } - - return checks -} - -// QACheck represents a single QA check. -type QACheck struct { - Name string - Command string - Args []string -} - -func buildChecks(names []string) []QACheck { - var checks []QACheck - for _, name := range names { - name = strings.TrimSpace(name) - check := buildCheck(name) - if check.Command != "" { - checks = append(checks, check) - } - } - return checks -} - -func buildCheck(name string) QACheck { - switch name { - case "fmt", "format": - args := []string{"-l", "."} - if qaFix { - args = []string{"-w", "."} - } - return QACheck{Name: "format", Command: "gofmt", Args: args} - - case "vet": - return QACheck{Name: "vet", Command: "go", Args: []string{"vet", "./..."}} - - case "lint": - args := []string{"run"} - if qaFix { - args = append(args, "--fix") - } - if qaChanged && !qaAll { - args = append(args, "--new-from-rev=HEAD") - } - args = append(args, "./...") - return QACheck{Name: "lint", Command: "golangci-lint", Args: args} - - case "test": - args := []string{"test"} - if qaShort { - args = append(args, "-short") - } - if qaVerbose { - args = append(args, "-v") - } - args = append(args, "./...") - return QACheck{Name: "test", Command: "go", Args: args} - - case "race": - args := []string{"test", "-race"} - if qaShort { - args = append(args, "-short") - } - if qaVerbose { - args = append(args, "-v") - } - args = append(args, "./...") - return QACheck{Name: "race", Command: "go", Args: args} - - case "bench": - args := []string{"test", "-bench=.", "-benchmem", "-run=^$"} - args = append(args, "./...") - return QACheck{Name: "bench", Command: "go", Args: args} - - case "vuln": - return QACheck{Name: "vuln", Command: "govulncheck", Args: []string{"./..."}} - - case "sec": - return QACheck{Name: "sec", Command: "gosec", Args: []string{"-quiet", "./..."}} - - case "fuzz": - return QACheck{Name: "fuzz", Command: "_internal_"} - - case "docblock": - // Special internal check - handled separately - return QACheck{Name: "docblock", Command: "_internal_"} - - default: - return QACheck{} - } -} - -// fixHintFor returns an actionable fix instruction for a given check failure. -func fixHintFor(checkName, output string) string { - switch checkName { - case "format", "fmt": - return "Run 'core go qa fmt --fix' to auto-format." - case "vet": - return "Fix the issues reported by go vet — typically genuine bugs." - case "lint": - return "Run 'core go qa lint --fix' for auto-fixable issues." - case "test": - if name := extractFailingTest(output); name != "" { - return fmt.Sprintf("Run 'go test -run %s -v ./...' to debug.", name) - } - return "Run 'go test -run -v ./path/' to debug." - case "race": - return "Data race detected. Add mutex, channel, or atomic to synchronise shared state." - case "bench": - return "Benchmark regression. Run 'go test -bench=. -benchmem' to reproduce." - case "vuln": - return "Run 'govulncheck ./...' for details. Update affected deps with 'go get -u'." - case "sec": - return "Review gosec findings. Common fixes: validate inputs, parameterised queries." - case "fuzz": - return "Add a regression test for the crashing input in testdata/fuzz//." - case "docblock": - return "Add doc comments to exported symbols: '// Name does X.' before each declaration." - default: - return "" - } -} - -var failTestRe = regexp.MustCompile(`--- FAIL: (\w+)`) - -// extractFailingTest parses the first failing test name from go test output. -func extractFailingTest(output string) string { - if m := failTestRe.FindStringSubmatch(output); len(m) > 1 { - return m[1] - } - return "" -} - -func runCheckCapture(ctx context.Context, dir string, check QACheck) (string, error) { - // Handle internal checks - if check.Command == "_internal_" { - return runInternalCheck(check) - } - - // Check if command exists - if _, err := exec.LookPath(check.Command); err != nil { - return "", cli.Err("%s: not installed", check.Command) - } - - cmd := exec.CommandContext(ctx, check.Command, check.Args...) - cmd.Dir = dir - - // For gofmt -l, capture output to check if files need formatting - if check.Name == "format" && len(check.Args) > 0 && check.Args[0] == "-l" { - output, err := cmd.Output() - if err != nil { - return string(output), err - } - if len(output) > 0 { - // Show files that need formatting - if !qaQuiet && !qaJSON { - cli.Text(string(output)) - } - return string(output), cli.Err("files need formatting (use --fix)") - } - return "", nil - } - - // For other commands, stream or capture based on quiet mode - if qaQuiet || qaJSON { - output, err := cmd.CombinedOutput() - return string(output), err - } - - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return "", cmd.Run() -} - -func runCoverage(ctx context.Context, dir string) (float64, float64, error) { - // Create temp file for coverage data - covFile, err := os.CreateTemp("", "coverage-*.out") - if err != nil { - return 0, 0, err - } - covPath := covFile.Name() - _ = covFile.Close() - defer os.Remove(covPath) - - args := []string{"test", "-cover", "-covermode=atomic", "-coverprofile=" + covPath} - if qaShort { - args = append(args, "-short") - } - args = append(args, "./...") - - cmd := exec.CommandContext(ctx, "go", args...) - cmd.Dir = dir - if !qaQuiet && !qaJSON { - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - } - - if err := cmd.Run(); err != nil { - return 0, 0, err - } - - // Parse statement coverage - coverCmd := exec.CommandContext(ctx, "go", "tool", "cover", "-func="+covPath) - output, err := coverCmd.Output() - if err != nil { - return 0, 0, err - } - - // Parse last line for total coverage - lines := strings.Split(strings.TrimSpace(string(output)), "\n") - var statementPct float64 - if len(lines) > 0 { - lastLine := lines[len(lines)-1] - fields := strings.Fields(lastLine) - if len(fields) >= 3 { - // Parse percentage (e.g., "45.6%") - pctStr := strings.TrimSuffix(fields[len(fields)-1], "%") - _, _ = fmt.Sscanf(pctStr, "%f", &statementPct) - } - } - - // Parse branch coverage - branchPct, err := calculateBlockCoverage(covPath) - if err != nil { - return statementPct, 0, err - } - - return statementPct, branchPct, nil -} - -// runInternalCheck runs internal Go-based checks (not external commands). -func runInternalCheck(check QACheck) (string, error) { - switch check.Name { - case "fuzz": - // Short burst fuzz in QA (3s per target) - duration := 3 * time.Second - if qaTimeout > 0 && qaTimeout < 30*time.Second { - duration = 2 * time.Second - } - return "", runGoFuzz(duration, "", "", qaVerbose) - - case "docblock": - result, err := qa.CheckDocblockCoverage([]string{"./..."}) - if err != nil { - return "", err - } - if result.Coverage < qaDocblockThreshold { - return "", cli.Err("docblock coverage %.1f%% below threshold %.1f%%", result.Coverage, qaDocblockThreshold) - } - return fmt.Sprintf("docblock coverage: %.1f%% (%d/%d)", result.Coverage, result.Documented, result.Total), nil - - default: - return "", cli.Err("unknown internal check: %s", check.Name) - } -} diff --git a/cmd/gocmd/cmd_tools.go b/cmd/gocmd/cmd_tools.go deleted file mode 100644 index cfa7981b..00000000 --- a/cmd/gocmd/cmd_tools.go +++ /dev/null @@ -1,236 +0,0 @@ -package gocmd - -import ( - "errors" - "os" - "os/exec" - "path/filepath" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" -) - -var ( - installVerbose bool - installNoCgo bool -) - -func addGoInstallCommand(parent *cli.Command) { - installCmd := &cli.Command{ - Use: "install [path]", - Short: "Install Go binary", - Long: "Install Go binary to $GOPATH/bin", - RunE: func(cmd *cli.Command, args []string) error { - // Get install path from args or default to current dir - installPath := "./..." - if len(args) > 0 { - installPath = args[0] - } - - // Detect if we're in a module with cmd/ subdirectories or a root main.go - if installPath == "./..." { - if _, err := os.Stat("core.go"); err == nil { - installPath = "." - } else if entries, err := os.ReadDir("cmd"); err == nil && len(entries) > 0 { - installPath = "./cmd/..." - } else if _, err := os.Stat("main.go"); err == nil { - installPath = "." - } - } - - cli.Print("%s %s\n", dimStyle.Render(i18n.Label("install")), i18n.Progress("install")) - cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("path")), installPath) - if installNoCgo { - cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("cgo")), "disabled") - } - - cmdArgs := []string{"install"} - if installVerbose { - cmdArgs = append(cmdArgs, "-v") - } - cmdArgs = append(cmdArgs, installPath) - - execCmd := exec.Command("go", cmdArgs...) - if installNoCgo { - execCmd.Env = append(os.Environ(), "CGO_ENABLED=0") - } - execCmd.Stdout = os.Stdout - execCmd.Stderr = os.Stderr - - if err := execCmd.Run(); err != nil { - cli.Print("\n%s\n", errorStyle.Render(i18n.T("i18n.fail.install", "binary"))) - return err - } - - // Show where it was installed - gopath := os.Getenv("GOPATH") - if gopath == "" { - home, _ := os.UserHomeDir() - gopath = filepath.Join(home, "go") - } - binDir := filepath.Join(gopath, "bin") - - cli.Print("\n%s %s\n", successStyle.Render(i18n.T("i18n.done.install")), binDir) - return nil - }, - } - - installCmd.Flags().BoolVarP(&installVerbose, "verbose", "v", false, "Verbose output") - installCmd.Flags().BoolVar(&installNoCgo, "no-cgo", false, "Disable CGO") - - parent.AddCommand(installCmd) -} - -func addGoModCommand(parent *cli.Command) { - modCmd := &cli.Command{ - Use: "mod", - Short: "Module management", - Long: "Go module management commands", - } - - // tidy - tidyCmd := &cli.Command{ - Use: "tidy", - Short: "Run go mod tidy", - RunE: func(cmd *cli.Command, args []string) error { - execCmd := exec.Command("go", "mod", "tidy") - execCmd.Stdout = os.Stdout - execCmd.Stderr = os.Stderr - return execCmd.Run() - }, - } - - // download - downloadCmd := &cli.Command{ - Use: "download", - Short: "Download module dependencies", - RunE: func(cmd *cli.Command, args []string) error { - execCmd := exec.Command("go", "mod", "download") - execCmd.Stdout = os.Stdout - execCmd.Stderr = os.Stderr - return execCmd.Run() - }, - } - - // verify - verifyCmd := &cli.Command{ - Use: "verify", - Short: "Verify module checksums", - RunE: func(cmd *cli.Command, args []string) error { - execCmd := exec.Command("go", "mod", "verify") - execCmd.Stdout = os.Stdout - execCmd.Stderr = os.Stderr - return execCmd.Run() - }, - } - - // graph - graphCmd := &cli.Command{ - Use: "graph", - Short: "Print module dependency graph", - RunE: func(cmd *cli.Command, args []string) error { - execCmd := exec.Command("go", "mod", "graph") - execCmd.Stdout = os.Stdout - execCmd.Stderr = os.Stderr - return execCmd.Run() - }, - } - - modCmd.AddCommand(tidyCmd) - modCmd.AddCommand(downloadCmd) - modCmd.AddCommand(verifyCmd) - modCmd.AddCommand(graphCmd) - parent.AddCommand(modCmd) -} - -func addGoWorkCommand(parent *cli.Command) { - workCmd := &cli.Command{ - Use: "work", - Short: "Workspace management", - Long: "Go workspace management commands", - } - - // sync - syncCmd := &cli.Command{ - Use: "sync", - Short: "Sync workspace modules", - RunE: func(cmd *cli.Command, args []string) error { - execCmd := exec.Command("go", "work", "sync") - execCmd.Stdout = os.Stdout - execCmd.Stderr = os.Stderr - return execCmd.Run() - }, - } - - // init - initCmd := &cli.Command{ - Use: "init", - Short: "Initialise a new workspace", - RunE: func(cmd *cli.Command, args []string) error { - execCmd := exec.Command("go", "work", "init") - execCmd.Stdout = os.Stdout - execCmd.Stderr = os.Stderr - if err := execCmd.Run(); err != nil { - return err - } - // Auto-add current module if go.mod exists - if _, err := os.Stat("go.mod"); err == nil { - execCmd = exec.Command("go", "work", "use", ".") - execCmd.Stdout = os.Stdout - execCmd.Stderr = os.Stderr - return execCmd.Run() - } - return nil - }, - } - - // use - useCmd := &cli.Command{ - Use: "use [modules...]", - Short: "Add modules to workspace", - RunE: func(cmd *cli.Command, args []string) error { - if len(args) == 0 { - // Auto-detect modules - modules := findGoModules(".") - if len(modules) == 0 { - return errors.New("no Go modules found") - } - for _, mod := range modules { - execCmd := exec.Command("go", "work", "use", mod) - execCmd.Stdout = os.Stdout - execCmd.Stderr = os.Stderr - if err := execCmd.Run(); err != nil { - return err - } - cli.Print("%s %s\n", successStyle.Render(i18n.T("i18n.done.add")), mod) - } - return nil - } - - cmdArgs := append([]string{"work", "use"}, args...) - execCmd := exec.Command("go", cmdArgs...) - execCmd.Stdout = os.Stdout - execCmd.Stderr = os.Stderr - return execCmd.Run() - }, - } - - workCmd.AddCommand(syncCmd) - workCmd.AddCommand(initCmd) - workCmd.AddCommand(useCmd) - parent.AddCommand(workCmd) -} - -func findGoModules(root string) []string { - var modules []string - _ = filepath.Walk(root, func(path string, info os.FileInfo, err error) error { - if err != nil { - return nil - } - if info.Name() == "go.mod" && path != "go.mod" { - modules = append(modules, filepath.Dir(path)) - } - return nil - }) - return modules -} diff --git a/cmd/gocmd/coverage_test.go b/cmd/gocmd/coverage_test.go deleted file mode 100644 index 77ebf0ef..00000000 --- a/cmd/gocmd/coverage_test.go +++ /dev/null @@ -1,240 +0,0 @@ -package gocmd - -import ( - "os" - "testing" - - "forge.lthn.ai/core/cli/pkg/cli" - "github.com/stretchr/testify/assert" -) - -func TestCalculateBlockCoverage(t *testing.T) { - // Create a dummy coverage profile - content := `mode: set -forge.lthn.ai/core/go/pkg/foo.go:1.2,3.4 5 1 -forge.lthn.ai/core/go/pkg/foo.go:5.6,7.8 2 0 -forge.lthn.ai/core/go/pkg/bar.go:10.1,12.20 10 5 -` - tmpfile, err := os.CreateTemp("", "test-coverage-*.out") - assert.NoError(t, err) - defer os.Remove(tmpfile.Name()) - - _, err = tmpfile.Write([]byte(content)) - assert.NoError(t, err) - err = tmpfile.Close() - assert.NoError(t, err) - - // Test calculation - // 3 blocks total, 2 covered (count > 0) - // Expect (2/3) * 100 = 66.666... - pct, err := calculateBlockCoverage(tmpfile.Name()) - assert.NoError(t, err) - assert.InDelta(t, 66.67, pct, 0.01) - - // Test empty file (only header) - contentEmpty := "mode: atomic\n" - tmpfileEmpty, err := os.CreateTemp("", "test-coverage-empty-*.out") - assert.NoError(t, err) - defer os.Remove(tmpfileEmpty.Name()) - _, err = tmpfileEmpty.Write([]byte(contentEmpty)) - assert.NoError(t, err) - err = tmpfileEmpty.Close() - assert.NoError(t, err) - - pct, err = calculateBlockCoverage(tmpfileEmpty.Name()) - assert.NoError(t, err) - assert.Equal(t, 0.0, pct) - - // Test non-existent file - pct, err = calculateBlockCoverage("non-existent-file") - assert.Error(t, err) - assert.Equal(t, 0.0, pct) - - // Test malformed file - contentMalformed := `mode: set -forge.lthn.ai/core/go/pkg/foo.go:1.2,3.4 5 -forge.lthn.ai/core/go/pkg/foo.go:1.2,3.4 5 notanumber -` - tmpfileMalformed, err := os.CreateTemp("", "test-coverage-malformed-*.out") - assert.NoError(t, err) - defer os.Remove(tmpfileMalformed.Name()) - _, err = tmpfileMalformed.Write([]byte(contentMalformed)) - assert.NoError(t, err) - err = tmpfileMalformed.Close() - assert.NoError(t, err) - - pct, err = calculateBlockCoverage(tmpfileMalformed.Name()) - assert.NoError(t, err) - assert.Equal(t, 0.0, pct) - - // Test malformed file - missing fields - contentMalformed2 := `mode: set -forge.lthn.ai/core/go/pkg/foo.go:1.2,3.4 5 -` - tmpfileMalformed2, err := os.CreateTemp("", "test-coverage-malformed2-*.out") - assert.NoError(t, err) - defer os.Remove(tmpfileMalformed2.Name()) - _, err = tmpfileMalformed2.Write([]byte(contentMalformed2)) - assert.NoError(t, err) - err = tmpfileMalformed2.Close() - assert.NoError(t, err) - - pct, err = calculateBlockCoverage(tmpfileMalformed2.Name()) - assert.NoError(t, err) - assert.Equal(t, 0.0, pct) - - // Test completely empty file - tmpfileEmpty2, err := os.CreateTemp("", "test-coverage-empty2-*.out") - assert.NoError(t, err) - defer os.Remove(tmpfileEmpty2.Name()) - err = tmpfileEmpty2.Close() - assert.NoError(t, err) - pct, err = calculateBlockCoverage(tmpfileEmpty2.Name()) - assert.NoError(t, err) - assert.Equal(t, 0.0, pct) -} - -func TestParseOverallCoverage(t *testing.T) { - output := `ok forge.lthn.ai/core/go/pkg/foo 0.100s coverage: 50.0% of statements -ok forge.lthn.ai/core/go/pkg/bar 0.200s coverage: 100.0% of statements -` - pct := parseOverallCoverage(output) - assert.Equal(t, 75.0, pct) - - outputNoCov := "ok forge.lthn.ai/core/go/pkg/foo 0.100s" - pct = parseOverallCoverage(outputNoCov) - assert.Equal(t, 0.0, pct) -} - -func TestFormatCoverage(t *testing.T) { - assert.Contains(t, formatCoverage(85.0), "85.0%") - assert.Contains(t, formatCoverage(65.0), "65.0%") - assert.Contains(t, formatCoverage(25.0), "25.0%") -} - -func TestAddGoCovCommand(t *testing.T) { - cmd := &cli.Command{Use: "test"} - addGoCovCommand(cmd) - assert.True(t, cmd.HasSubCommands()) - sub := cmd.Commands()[0] - assert.Equal(t, "cov", sub.Name()) -} - -func TestAddGoQACommand(t *testing.T) { - cmd := &cli.Command{Use: "test"} - addGoQACommand(cmd) - assert.True(t, cmd.HasSubCommands()) - sub := cmd.Commands()[0] - assert.Equal(t, "qa", sub.Name()) -} - -func TestDetermineChecks(t *testing.T) { - // Default checks - qaOnly = "" - qaSkip = "" - qaRace = false - qaBench = false - checks := determineChecks() - assert.Contains(t, checks, "fmt") - assert.Contains(t, checks, "test") - - // Only - qaOnly = "fmt,lint" - checks = determineChecks() - assert.Equal(t, []string{"fmt", "lint"}, checks) - - // Skip - qaOnly = "" - qaSkip = "fmt,lint" - checks = determineChecks() - assert.NotContains(t, checks, "fmt") - assert.NotContains(t, checks, "lint") - assert.Contains(t, checks, "test") - - // Race - qaSkip = "" - qaRace = true - checks = determineChecks() - assert.Contains(t, checks, "race") - assert.NotContains(t, checks, "test") - - // Reset - qaRace = false -} - -func TestBuildCheck(t *testing.T) { - qaFix = false - c := buildCheck("fmt") - assert.Equal(t, "format", c.Name) - assert.Equal(t, []string{"-l", "."}, c.Args) - - qaFix = true - c = buildCheck("fmt") - assert.Equal(t, []string{"-w", "."}, c.Args) - - c = buildCheck("vet") - assert.Equal(t, "vet", c.Name) - - c = buildCheck("lint") - assert.Equal(t, "lint", c.Name) - - c = buildCheck("test") - assert.Equal(t, "test", c.Name) - - c = buildCheck("race") - assert.Equal(t, "race", c.Name) - - c = buildCheck("bench") - assert.Equal(t, "bench", c.Name) - - c = buildCheck("vuln") - assert.Equal(t, "vuln", c.Name) - - c = buildCheck("sec") - assert.Equal(t, "sec", c.Name) - - c = buildCheck("fuzz") - assert.Equal(t, "fuzz", c.Name) - - c = buildCheck("docblock") - assert.Equal(t, "docblock", c.Name) - - c = buildCheck("unknown") - assert.Equal(t, "", c.Name) -} - -func TestBuildChecks(t *testing.T) { - checks := buildChecks([]string{"fmt", "vet", "unknown"}) - assert.Equal(t, 2, len(checks)) - assert.Equal(t, "format", checks[0].Name) - assert.Equal(t, "vet", checks[1].Name) -} - -func TestFixHintFor(t *testing.T) { - assert.Contains(t, fixHintFor("format", ""), "core go qa fmt --fix") - assert.Contains(t, fixHintFor("vet", ""), "go vet") - assert.Contains(t, fixHintFor("lint", ""), "core go qa lint --fix") - assert.Contains(t, fixHintFor("test", "--- FAIL: TestFoo"), "TestFoo") - assert.Contains(t, fixHintFor("race", ""), "Data race") - assert.Contains(t, fixHintFor("bench", ""), "Benchmark regression") - assert.Contains(t, fixHintFor("vuln", ""), "govulncheck") - assert.Contains(t, fixHintFor("sec", ""), "gosec") - assert.Contains(t, fixHintFor("fuzz", ""), "crashing input") - assert.Contains(t, fixHintFor("docblock", ""), "doc comments") - assert.Equal(t, "", fixHintFor("unknown", "")) -} - -func TestRunGoQA_NoGoMod(t *testing.T) { - // runGoQA should fail if go.mod is not present in CWD - // We run it in a temp dir without go.mod - tmpDir, _ := os.MkdirTemp("", "test-qa-*") - defer os.RemoveAll(tmpDir) - cwd, _ := os.Getwd() - os.Chdir(tmpDir) - defer os.Chdir(cwd) - - cmd := &cli.Command{Use: "qa"} - err := runGoQA(cmd, []string{}) - assert.Error(t, err) - assert.Contains(t, err.Error(), "no go.mod found") -} diff --git a/cmd/module/cmd.go b/cmd/module/cmd.go deleted file mode 100644 index a2fd5ee0..00000000 --- a/cmd/module/cmd.go +++ /dev/null @@ -1,55 +0,0 @@ -// Package module provides CLI commands for managing marketplace modules. -// -// Commands: -// - install: Install a module from a Git repo -// - list: List installed modules -// - update: Update a module or all modules -// - remove: Remove an installed module -package module - -import ( - "os" - "path/filepath" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" - "forge.lthn.ai/core/go-scm/marketplace" - "forge.lthn.ai/core/go-io/store" -) - -// AddModuleCommands registers the 'module' command and all subcommands. -func AddModuleCommands(root *cli.Command) { - moduleCmd := &cli.Command{ - Use: "module", - Short: i18n.T("Manage marketplace modules"), - } - root.AddCommand(moduleCmd) - - addInstallCommand(moduleCmd) - addListCommand(moduleCmd) - addUpdateCommand(moduleCmd) - addRemoveCommand(moduleCmd) -} - -// moduleSetup returns the modules directory, store, and installer. -// The caller must defer st.Close(). -func moduleSetup() (string, *store.Store, *marketplace.Installer, error) { - home, err := os.UserHomeDir() - if err != nil { - return "", nil, nil, cli.Wrap(err, "failed to determine home directory") - } - - modulesDir := filepath.Join(home, ".core", "modules") - if err := os.MkdirAll(modulesDir, 0755); err != nil { - return "", nil, nil, cli.Wrap(err, "failed to create modules directory") - } - - dbPath := filepath.Join(modulesDir, "modules.db") - st, err := store.New(dbPath) - if err != nil { - return "", nil, nil, cli.Wrap(err, "failed to open module store") - } - - inst := marketplace.NewInstaller(modulesDir, st) - return modulesDir, st, inst, nil -} diff --git a/cmd/module/cmd_install.go b/cmd/module/cmd_install.go deleted file mode 100644 index 2c03ad8b..00000000 --- a/cmd/module/cmd_install.go +++ /dev/null @@ -1,59 +0,0 @@ -package module - -import ( - "context" - "errors" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" - "forge.lthn.ai/core/go-scm/marketplace" -) - -var ( - installRepo string - installSignKey string -) - -func addInstallCommand(parent *cli.Command) { - installCmd := cli.NewCommand( - "install ", - i18n.T("Install a module from a Git repo"), - i18n.T("Install a module by cloning its Git repository, verifying the manifest signature, and registering it.\n\nThe --repo flag is required and specifies the Git URL to clone from."), - func(cmd *cli.Command, args []string) error { - if installRepo == "" { - return errors.New("--repo flag is required") - } - return runInstall(args[0], installRepo, installSignKey) - }, - ) - installCmd.Args = cli.ExactArgs(1) - installCmd.Example = " core module install my-module --repo https://forge.lthn.ai/modules/my-module.git\n core module install signed-mod --repo ssh://git@forge.lthn.ai:2223/modules/signed.git --sign-key abc123" - - cli.StringFlag(installCmd, &installRepo, "repo", "r", "", i18n.T("Git repository URL to clone")) - cli.StringFlag(installCmd, &installSignKey, "sign-key", "k", "", i18n.T("Hex-encoded ed25519 public key for manifest verification")) - - parent.AddCommand(installCmd) -} - -func runInstall(code, repo, signKey string) error { - _, st, inst, err := moduleSetup() - if err != nil { - return err - } - defer st.Close() - - cli.Dim("Installing module " + code + " from " + repo + "...") - - mod := marketplace.Module{ - Code: code, - Repo: repo, - SignKey: signKey, - } - - if err := inst.Install(context.Background(), mod); err != nil { - return err - } - - cli.Success("Module " + code + " installed successfully") - return nil -} diff --git a/cmd/module/cmd_list.go b/cmd/module/cmd_list.go deleted file mode 100644 index a5d1624a..00000000 --- a/cmd/module/cmd_list.go +++ /dev/null @@ -1,51 +0,0 @@ -package module - -import ( - "fmt" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" -) - -func addListCommand(parent *cli.Command) { - listCmd := cli.NewCommand( - "list", - i18n.T("List installed modules"), - "", - func(cmd *cli.Command, args []string) error { - return runList() - }, - ) - - parent.AddCommand(listCmd) -} - -func runList() error { - _, st, inst, err := moduleSetup() - if err != nil { - return err - } - defer st.Close() - - installed, err := inst.Installed() - if err != nil { - return err - } - - if len(installed) == 0 { - cli.Dim("No modules installed") - return nil - } - - table := cli.NewTable("Code", "Name", "Version", "Repo") - for _, m := range installed { - table.AddRow(m.Code, m.Name, m.Version, m.Repo) - } - - fmt.Println() - table.Render() - fmt.Println() - cli.Dim(fmt.Sprintf("%d module(s) installed", len(installed))) - - return nil -} diff --git a/cmd/module/cmd_remove.go b/cmd/module/cmd_remove.go deleted file mode 100644 index 1598f6f4..00000000 --- a/cmd/module/cmd_remove.go +++ /dev/null @@ -1,40 +0,0 @@ -package module - -import ( - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" -) - -func addRemoveCommand(parent *cli.Command) { - removeCmd := cli.NewCommand( - "remove ", - i18n.T("Remove an installed module"), - "", - func(cmd *cli.Command, args []string) error { - return runRemove(args[0]) - }, - ) - removeCmd.Args = cli.ExactArgs(1) - - parent.AddCommand(removeCmd) -} - -func runRemove(code string) error { - _, st, inst, err := moduleSetup() - if err != nil { - return err - } - defer st.Close() - - if !cli.Confirm("Remove module " + code + "?") { - cli.Dim("Cancelled") - return nil - } - - if err := inst.Remove(code); err != nil { - return err - } - - cli.Success("Module " + code + " removed") - return nil -} diff --git a/cmd/module/cmd_update.go b/cmd/module/cmd_update.go deleted file mode 100644 index 23d3626a..00000000 --- a/cmd/module/cmd_update.go +++ /dev/null @@ -1,85 +0,0 @@ -package module - -import ( - "context" - "errors" - "fmt" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" -) - -var updateAll bool - -func addUpdateCommand(parent *cli.Command) { - updateCmd := cli.NewCommand( - "update [code]", - i18n.T("Update a module or all modules"), - i18n.T("Update a specific module to the latest version, or use --all to update all installed modules."), - func(cmd *cli.Command, args []string) error { - if updateAll { - return runUpdateAll() - } - if len(args) == 0 { - return errors.New("module code required (or use --all)") - } - return runUpdate(args[0]) - }, - ) - - cli.BoolFlag(updateCmd, &updateAll, "all", "a", false, i18n.T("Update all installed modules")) - - parent.AddCommand(updateCmd) -} - -func runUpdate(code string) error { - _, st, inst, err := moduleSetup() - if err != nil { - return err - } - defer st.Close() - - cli.Dim("Updating " + code + "...") - - if err := inst.Update(context.Background(), code); err != nil { - return err - } - - cli.Success("Module " + code + " updated successfully") - return nil -} - -func runUpdateAll() error { - _, st, inst, err := moduleSetup() - if err != nil { - return err - } - defer st.Close() - - installed, err := inst.Installed() - if err != nil { - return err - } - - if len(installed) == 0 { - cli.Dim("No modules installed") - return nil - } - - ctx := context.Background() - var updated, failed int - for _, m := range installed { - cli.Dim("Updating " + m.Code + "...") - if err := inst.Update(ctx, m.Code); err != nil { - cli.Errorf("Failed to update %s: %v", m.Code, err) - failed++ - continue - } - cli.Success(m.Code + " updated") - updated++ - } - - fmt.Println() - cli.Dim(fmt.Sprintf("%d updated, %d failed", updated, failed)) - return nil -} diff --git a/cmd/plugin/cmd.go b/cmd/plugin/cmd.go deleted file mode 100644 index 3eb09e6b..00000000 --- a/cmd/plugin/cmd.go +++ /dev/null @@ -1,29 +0,0 @@ -// Package plugin provides CLI commands for managing core plugins. -// -// Commands: -// - install: Install a plugin from GitHub -// - list: List installed plugins -// - info: Show detailed plugin information -// - update: Update a plugin or all plugins -// - remove: Remove an installed plugin -package plugin - -import ( - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" -) - -// AddPluginCommands registers the 'plugin' command and all subcommands. -func AddPluginCommands(root *cli.Command) { - pluginCmd := &cli.Command{ - Use: "plugin", - Short: i18n.T("Manage plugins"), - } - root.AddCommand(pluginCmd) - - addInstallCommand(pluginCmd) - addListCommand(pluginCmd) - addInfoCommand(pluginCmd) - addUpdateCommand(pluginCmd) - addRemoveCommand(pluginCmd) -} diff --git a/cmd/plugin/cmd_info.go b/cmd/plugin/cmd_info.go deleted file mode 100644 index 285fce26..00000000 --- a/cmd/plugin/cmd_info.go +++ /dev/null @@ -1,86 +0,0 @@ -package plugin - -import ( - "fmt" - "path/filepath" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" - "forge.lthn.ai/core/go-io" - "forge.lthn.ai/core/go-scm/plugin" -) - -func addInfoCommand(parent *cli.Command) { - infoCmd := cli.NewCommand( - "info ", - i18n.T("Show detailed plugin information"), - "", - func(cmd *cli.Command, args []string) error { - return runInfo(args[0]) - }, - ) - infoCmd.Args = cli.ExactArgs(1) - - parent.AddCommand(infoCmd) -} - -func runInfo(name string) error { - basePath, err := pluginBasePath() - if err != nil { - return err - } - - registry := plugin.NewRegistry(io.Local, basePath) - if err := registry.Load(); err != nil { - return err - } - - cfg, ok := registry.Get(name) - if !ok { - return fmt.Errorf("plugin not found: %s", name) - } - - // Try to load the manifest for extended information - loader := plugin.NewLoader(io.Local, basePath) - manifest, manifestErr := loader.LoadPlugin(name) - - fmt.Println() - cli.Label("Name", cfg.Name) - cli.Label("Version", cfg.Version) - cli.Label("Source", cfg.Source) - - status := "disabled" - if cfg.Enabled { - status = "enabled" - } - cli.Label("Status", status) - cli.Label("Installed", cfg.InstalledAt) - cli.Label("Path", filepath.Join(basePath, name)) - - if manifestErr == nil && manifest != nil { - if manifest.Description != "" { - cli.Label("Description", manifest.Description) - } - if manifest.Author != "" { - cli.Label("Author", manifest.Author) - } - if manifest.Entrypoint != "" { - cli.Label("Entrypoint", manifest.Entrypoint) - } - if manifest.MinVersion != "" { - cli.Label("Min Version", manifest.MinVersion) - } - if len(manifest.Dependencies) > 0 { - for i, dep := range manifest.Dependencies { - if i == 0 { - cli.Label("Dependencies", dep) - } else { - fmt.Printf(" %s\n", dep) - } - } - } - } - - fmt.Println() - return nil -} diff --git a/cmd/plugin/cmd_install.go b/cmd/plugin/cmd_install.go deleted file mode 100644 index e81f0d8f..00000000 --- a/cmd/plugin/cmd_install.go +++ /dev/null @@ -1,61 +0,0 @@ -package plugin - -import ( - "context" - "os" - "path/filepath" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" - "forge.lthn.ai/core/go-io" - "forge.lthn.ai/core/go-scm/plugin" -) - -func addInstallCommand(parent *cli.Command) { - installCmd := cli.NewCommand( - "install ", - i18n.T("Install a plugin from GitHub"), - i18n.T("Install a plugin from a GitHub repository.\n\nSource format: org/repo or org/repo@version"), - func(cmd *cli.Command, args []string) error { - return runInstall(args[0]) - }, - ) - installCmd.Args = cli.ExactArgs(1) - installCmd.Example = " core plugin install host-uk/core-plugin-example\n core plugin install host-uk/core-plugin-example@v1.0.0" - - parent.AddCommand(installCmd) -} - -func runInstall(source string) error { - basePath, err := pluginBasePath() - if err != nil { - return err - } - - registry := plugin.NewRegistry(io.Local, basePath) - if err := registry.Load(); err != nil { - return err - } - - installer := plugin.NewInstaller(io.Local, registry) - - cli.Dim("Installing plugin from " + source + "...") - - if err := installer.Install(context.Background(), source); err != nil { - return err - } - - _, repo, _, _ := plugin.ParseSource(source) - cli.Success("Plugin " + repo + " installed successfully") - - return nil -} - -// pluginBasePath returns the default plugin directory (~/.core/plugins/). -func pluginBasePath() (string, error) { - home, err := os.UserHomeDir() - if err != nil { - return "", cli.Wrap(err, "failed to determine home directory") - } - return filepath.Join(home, ".core", "plugins"), nil -} diff --git a/cmd/plugin/cmd_list.go b/cmd/plugin/cmd_list.go deleted file mode 100644 index cb0b2138..00000000 --- a/cmd/plugin/cmd_list.go +++ /dev/null @@ -1,57 +0,0 @@ -package plugin - -import ( - "fmt" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" - "forge.lthn.ai/core/go-io" - "forge.lthn.ai/core/go-scm/plugin" -) - -func addListCommand(parent *cli.Command) { - listCmd := cli.NewCommand( - "list", - i18n.T("List installed plugins"), - "", - func(cmd *cli.Command, args []string) error { - return runList() - }, - ) - - parent.AddCommand(listCmd) -} - -func runList() error { - basePath, err := pluginBasePath() - if err != nil { - return err - } - - registry := plugin.NewRegistry(io.Local, basePath) - if err := registry.Load(); err != nil { - return err - } - - plugins := registry.List() - if len(plugins) == 0 { - cli.Dim("No plugins installed") - return nil - } - - table := cli.NewTable("Name", "Version", "Source", "Status") - for _, p := range plugins { - status := "disabled" - if p.Enabled { - status = "enabled" - } - table.AddRow(p.Name, p.Version, p.Source, status) - } - - fmt.Println() - table.Render() - fmt.Println() - cli.Dim(fmt.Sprintf("%d plugin(s) installed", len(plugins))) - - return nil -} diff --git a/cmd/plugin/cmd_remove.go b/cmd/plugin/cmd_remove.go deleted file mode 100644 index 951edfe3..00000000 --- a/cmd/plugin/cmd_remove.go +++ /dev/null @@ -1,48 +0,0 @@ -package plugin - -import ( - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" - "forge.lthn.ai/core/go-io" - "forge.lthn.ai/core/go-scm/plugin" -) - -func addRemoveCommand(parent *cli.Command) { - removeCmd := cli.NewCommand( - "remove ", - i18n.T("Remove an installed plugin"), - "", - func(cmd *cli.Command, args []string) error { - return runRemove(args[0]) - }, - ) - removeCmd.Args = cli.ExactArgs(1) - - parent.AddCommand(removeCmd) -} - -func runRemove(name string) error { - basePath, err := pluginBasePath() - if err != nil { - return err - } - - registry := plugin.NewRegistry(io.Local, basePath) - if err := registry.Load(); err != nil { - return err - } - - if !cli.Confirm("Remove plugin " + name + "?") { - cli.Dim("Cancelled") - return nil - } - - installer := plugin.NewInstaller(io.Local, registry) - - if err := installer.Remove(name); err != nil { - return err - } - - cli.Success("Plugin " + name + " removed") - return nil -} diff --git a/cmd/plugin/cmd_update.go b/cmd/plugin/cmd_update.go deleted file mode 100644 index 737f3cd2..00000000 --- a/cmd/plugin/cmd_update.go +++ /dev/null @@ -1,95 +0,0 @@ -package plugin - -import ( - "context" - "errors" - "fmt" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-i18n" - "forge.lthn.ai/core/go-io" - "forge.lthn.ai/core/go-scm/plugin" -) - -var updateAll bool - -func addUpdateCommand(parent *cli.Command) { - updateCmd := cli.NewCommand( - "update [name]", - i18n.T("Update a plugin or all plugins"), - i18n.T("Update a specific plugin to the latest version, or use --all to update all installed plugins."), - func(cmd *cli.Command, args []string) error { - if updateAll { - return runUpdateAll() - } - if len(args) == 0 { - return errors.New("plugin name required (or use --all)") - } - return runUpdate(args[0]) - }, - ) - - cli.BoolFlag(updateCmd, &updateAll, "all", "a", false, i18n.T("Update all installed plugins")) - - parent.AddCommand(updateCmd) -} - -func runUpdate(name string) error { - basePath, err := pluginBasePath() - if err != nil { - return err - } - - registry := plugin.NewRegistry(io.Local, basePath) - if err := registry.Load(); err != nil { - return err - } - - installer := plugin.NewInstaller(io.Local, registry) - - cli.Dim("Updating " + name + "...") - - if err := installer.Update(context.Background(), name); err != nil { - return err - } - - cli.Success("Plugin " + name + " updated successfully") - return nil -} - -func runUpdateAll() error { - basePath, err := pluginBasePath() - if err != nil { - return err - } - - registry := plugin.NewRegistry(io.Local, basePath) - if err := registry.Load(); err != nil { - return err - } - - plugins := registry.List() - if len(plugins) == 0 { - cli.Dim("No plugins installed") - return nil - } - - installer := plugin.NewInstaller(io.Local, registry) - ctx := context.Background() - - var updated, failed int - for _, p := range plugins { - cli.Dim("Updating " + p.Name + "...") - if err := installer.Update(ctx, p.Name); err != nil { - cli.Errorf("Failed to update %s: %v", p.Name, err) - failed++ - continue - } - cli.Success(p.Name + " updated") - updated++ - } - - fmt.Println() - cli.Dim(fmt.Sprintf("%d updated, %d failed", updated, failed)) - return nil -} diff --git a/cmd/service/cmd.go b/cmd/service/cmd.go deleted file mode 100644 index 2f56e781..00000000 --- a/cmd/service/cmd.go +++ /dev/null @@ -1,274 +0,0 @@ -package service - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "syscall" - "time" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-process" - "forge.lthn.ai/core/go-scm/manifest" -) - -// AddServiceCommands registers core start/stop/list/restart as top-level commands. -func AddServiceCommands(root *cli.Command) { - startCmd := cli.NewCommand("start", "Start a project daemon", - "Reads .core/manifest.yaml and starts the named daemon (or the default).\n"+ - "The daemon runs detached in the background.", - func(cmd *cli.Command, args []string) error { - return runStart(args) - }, - ) - - stopCmd := cli.NewCommand("stop", "Stop a project daemon", - "Stops the named daemon for the current project, or all daemons if no name given.", - func(cmd *cli.Command, args []string) error { - return runStop(args) - }, - ) - - listCmd := cli.NewCommand("list", "List running daemons", - "Shows all running daemons tracked in ~/.core/daemons/.", - func(cmd *cli.Command, args []string) error { - return runList() - }, - ) - - restartCmd := cli.NewCommand("restart", "Restart a project daemon", - "Stops then starts the named daemon.", - func(cmd *cli.Command, args []string) error { - if err := runStop(args); err != nil { - return err - } - return runStart(args) - }, - ) - - root.AddCommand(startCmd, stopCmd, listCmd, restartCmd) -} - -func runStart(args []string) error { - m, projectDir, err := findManifest() - if err != nil { - return err - } - - daemonName, spec, err := resolveDaemon(m, args) - if err != nil { - return err - } - - reg := process.DefaultRegistry() - - // Check if already running. - if _, ok := reg.Get(m.Code, daemonName); ok { - return fmt.Errorf("%s/%s is already running", m.Code, daemonName) - } - - // Resolve binary. - binary := spec.Binary - if binary == "" { - return fmt.Errorf("daemon %q has no binary specified", daemonName) - } - - binPath, err := exec.LookPath(binary) - if err != nil { - return fmt.Errorf("binary %q not found in PATH: %w", binary, err) - } - - // Launch detached. - cmd := exec.Command(binPath, spec.Args...) - cmd.Dir = projectDir - cmd.Env = append(os.Environ(), "CORE_DAEMON=1") - cmd.Stdout = nil - cmd.Stderr = nil - cmd.Stdin = nil - cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} - - if err := cmd.Start(); err != nil { - return fmt.Errorf("failed to start %s: %w", daemonName, err) - } - - pid := cmd.Process.Pid - _ = cmd.Process.Release() - - // Wait for health if configured. - health := spec.Health - if health != "" && health != "127.0.0.1:0" { - if process.WaitForHealth(health, 5000) { - cli.LogInfo(fmt.Sprintf("Started %s/%s (PID %d, health %s)", m.Code, daemonName, pid, health)) - } else { - cli.LogInfo(fmt.Sprintf("Started %s/%s (PID %d, health not yet ready)", m.Code, daemonName, pid)) - } - } else { - cli.LogInfo(fmt.Sprintf("Started %s/%s (PID %d)", m.Code, daemonName, pid)) - } - - // Register in the daemon registry. - if err := reg.Register(process.DaemonEntry{ - Code: m.Code, - Daemon: daemonName, - PID: pid, - Health: health, - Project: projectDir, - Binary: binPath, - }); err != nil { - cli.LogWarn(fmt.Sprintf("Daemon started but registry failed: %v", err)) - } - - return nil -} - -func runStop(args []string) error { - reg := process.DefaultRegistry() - - m, _, err := findManifest() - if err != nil { - return err - } - - // If a specific daemon name was given, stop only that one. - if len(args) > 0 { - return stopDaemon(reg, m.Code, args[0]) - } - - // No args: stop all daemons for this project. - entries, err := reg.List() - if err != nil { - return err - } - - stopped := 0 - for _, e := range entries { - if e.Code == m.Code { - if err := stopDaemon(reg, e.Code, e.Daemon); err != nil { - cli.LogError(fmt.Sprintf("Failed to stop %s/%s: %v", e.Code, e.Daemon, err)) - } else { - stopped++ - } - } - } - - if stopped == 0 { - cli.LogInfo("No running daemons for " + m.Code) - } - - return nil -} - -func stopDaemon(reg *process.Registry, code, daemon string) error { - entry, ok := reg.Get(code, daemon) - if !ok { - return fmt.Errorf("%s/%s is not running", code, daemon) - } - - proc, err := os.FindProcess(entry.PID) - if err != nil { - return fmt.Errorf("process %d not found: %w", entry.PID, err) - } - - if err := proc.Signal(syscall.SIGTERM); err != nil { - return fmt.Errorf("failed to signal PID %d: %w", entry.PID, err) - } - - // Wait for process to exit, escalate to SIGKILL after 30s. - // Poll the process directly via Signal(0) rather than relying on - // the daemon to self-unregister, which avoids PID reuse issues. - deadline := time.Now().Add(30 * time.Second) - for time.Now().Before(deadline) { - if err := proc.Signal(syscall.Signal(0)); err != nil { - // Process is gone. - _ = reg.Unregister(code, daemon) - cli.LogInfo(fmt.Sprintf("Stopped %s/%s (PID %d)", code, daemon, entry.PID)) - return nil - } - time.Sleep(250 * time.Millisecond) - } - - cli.LogWarn(fmt.Sprintf("%s/%s did not stop within 30s, sending SIGKILL", code, daemon)) - _ = proc.Signal(syscall.SIGKILL) - _ = reg.Unregister(code, daemon) - cli.LogInfo(fmt.Sprintf("Killed %s/%s (PID %d)", code, daemon, entry.PID)) - return nil -} - -func runList() error { - reg := process.DefaultRegistry() - entries, err := reg.List() - if err != nil { - return err - } - - if len(entries) == 0 { - fmt.Println("No running daemons") - return nil - } - - fmt.Printf("%-20s %-12s %-8s %-24s %s\n", "CODE", "DAEMON", "PID", "HEALTH", "PROJECT") - for _, e := range entries { - project := e.Project - if project == "" { - project = "-" - } - fmt.Printf("%-20s %-12s %-8d %-24s %s\n", e.Code, e.Daemon, e.PID, e.Health, project) - } - - return nil -} - -// findManifest walks from cwd up to / looking for .core/manifest.yaml. -func findManifest() (*manifest.Manifest, string, error) { - dir, err := os.Getwd() - if err != nil { - return nil, "", err - } - - for { - path := filepath.Join(dir, ".core", "manifest.yaml") - data, err := os.ReadFile(path) - if err == nil { - m, err := manifest.Parse(data) - if err != nil { - return nil, "", fmt.Errorf("invalid manifest at %s: %w", path, err) - } - return m, dir, nil - } - - parent := filepath.Dir(dir) - if parent == dir { - break - } - dir = parent - } - - return nil, "", fmt.Errorf("no .core/manifest.yaml found (checked cwd and parent directories)") -} - -// resolveDaemon finds the daemon entry by name or returns the default. -func resolveDaemon(m *manifest.Manifest, args []string) (string, manifest.DaemonSpec, error) { - if len(args) > 0 { - name := args[0] - spec, ok := m.Daemons[name] - if !ok { - return "", manifest.DaemonSpec{}, fmt.Errorf("daemon %q not found in manifest (available: %v)", name, daemonNames(m)) - } - return name, spec, nil - } - - name, spec, ok := m.DefaultDaemon() - if !ok { - return "", manifest.DaemonSpec{}, fmt.Errorf("no default daemon in manifest (use: core start )") - } - return name, spec, nil -} - -func daemonNames(m *manifest.Manifest) []string { - var names []string - for name := range m.Daemons { - names = append(names, name) - } - return names -} diff --git a/cmd/session/cmd_session.go b/cmd/session/cmd_session.go deleted file mode 100644 index 3f310057..00000000 --- a/cmd/session/cmd_session.go +++ /dev/null @@ -1,235 +0,0 @@ -// Package session provides commands for replaying and searching Claude Code session transcripts. -package session - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-session" -) - -// AddSessionCommands registers the 'session' command group. -func AddSessionCommands(root *cli.Command) { - sessionCmd := &cli.Command{ - Use: "session", - Short: "Session recording and replay", - } - root.AddCommand(sessionCmd) - - addListCommand(sessionCmd) - addReplayCommand(sessionCmd) - addSearchCommand(sessionCmd) -} - -func projectsDir() string { - home, _ := os.UserHomeDir() - // Walk .claude/projects/ looking for dirs with .jsonl files - base := filepath.Join(home, ".claude", "projects") - entries, err := os.ReadDir(base) - if err != nil { - return base - } - // Return the first project dir that has .jsonl files - for _, e := range entries { - if !e.IsDir() { - continue - } - dir := filepath.Join(base, e.Name()) - matches, _ := filepath.Glob(filepath.Join(dir, "*.jsonl")) - if len(matches) > 0 { - return dir - } - } - return base -} - -func addListCommand(parent *cli.Command) { - listCmd := &cli.Command{ - Use: "list", - Short: "List recent sessions", - RunE: func(cmd *cli.Command, args []string) error { - sessions, err := session.ListSessions(projectsDir()) - if err != nil { - return err - } - if len(sessions) == 0 { - cli.Print("No sessions found") - return nil - } - - cli.Print("%s", cli.HeaderStyle.Render("Recent Sessions")) - cli.Print("%s", "") - for i, s := range sessions { - if i >= 20 { - cli.Print(" ... and %d more", len(sessions)-20) - break - } - dur := s.EndTime.Sub(s.StartTime) - durStr := "" - if dur > 0 { - durStr = fmt.Sprintf(" (%s)", formatDur(dur)) - } - id := s.ID - if len(id) > 8 { - id = id[:8] - } - cli.Print(" %s %s%s", - cli.ValueStyle.Render(id), - s.StartTime.Format("2006-01-02 15:04"), - cli.DimStyle.Render(durStr)) - } - return nil - }, - } - parent.AddCommand(listCmd) -} - -func addReplayCommand(parent *cli.Command) { - var mp4 bool - var output string - - replayCmd := &cli.Command{ - Use: "replay ", - Short: "Generate HTML timeline (and optional MP4) from a session", - Args: cli.MinimumNArgs(1), - RunE: func(cmd *cli.Command, args []string) error { - id := args[0] - path := findSession(id) - if path == "" { - return fmt.Errorf("session not found: %s", id) - } - - cli.Print("Parsing %s...", cli.ValueStyle.Render(filepath.Base(path))) - - sess, _, err := session.ParseTranscript(path) - if err != nil { - return fmt.Errorf("parse: %w", err) - } - - toolCount := 0 - for _, e := range sess.Events { - if e.Type == "tool_use" { - toolCount++ - } - } - cli.Print(" %d events, %d tool calls", - len(sess.Events), toolCount) - - // HTML output - htmlPath := output - if htmlPath == "" { - htmlPath = fmt.Sprintf("session-%s.html", shortID(sess.ID)) - } - if err := session.RenderHTML(sess, htmlPath); err != nil { - return fmt.Errorf("render html: %w", err) - } - cli.Print("%s", cli.SuccessStyle.Render(fmt.Sprintf(" HTML: %s", htmlPath))) - - // MP4 output - if mp4 { - mp4Path := strings.TrimSuffix(htmlPath, ".html") + ".mp4" - if err := session.RenderMP4(sess, mp4Path); err != nil { - cli.Print("%s", cli.ErrorStyle.Render(fmt.Sprintf(" MP4: %s", err))) - } else { - cli.Print("%s", cli.SuccessStyle.Render(fmt.Sprintf(" MP4: %s", mp4Path))) - } - } - - return nil - }, - } - replayCmd.Flags().BoolVar(&mp4, "mp4", false, "Also generate MP4 video (requires vhs + ffmpeg)") - replayCmd.Flags().StringVarP(&output, "output", "o", "", "Output file path") - parent.AddCommand(replayCmd) -} - -func addSearchCommand(parent *cli.Command) { - searchCmd := &cli.Command{ - Use: "search ", - Short: "Search across session transcripts", - Args: cli.MinimumNArgs(1), - RunE: func(cmd *cli.Command, args []string) error { - query := strings.ToLower(strings.Join(args, " ")) - results, err := session.Search(projectsDir(), query) - if err != nil { - return err - } - if len(results) == 0 { - cli.Print("No matches found") - return nil - } - - cli.Print("%s", cli.HeaderStyle.Render(fmt.Sprintf("Found %d matches", len(results)))) - cli.Print("%s", "") - for _, r := range results { - id := r.SessionID - if len(id) > 8 { - id = id[:8] - } - cli.Print(" %s %s %s", - cli.ValueStyle.Render(id), - r.Timestamp.Format("15:04:05"), - cli.DimStyle.Render(r.Tool)) - cli.Print(" %s", truncateStr(r.Match, 100)) - cli.Print("%s", "") - } - return nil - }, - } - parent.AddCommand(searchCmd) -} - -func findSession(id string) string { - dir := projectsDir() - // Try exact match first - path := filepath.Join(dir, id+".jsonl") - if _, err := os.Stat(path); err == nil { - return path - } - // Try prefix match - matches, _ := filepath.Glob(filepath.Join(dir, id+"*.jsonl")) - if len(matches) == 1 { - return matches[0] - } - return "" -} - -func shortID(id string) string { - if len(id) > 8 { - return id[:8] - } - return id -} - -func formatDur(d interface { - Hours() float64 - Minutes() float64 - Seconds() float64 -}) string { - type dur interface { - Hours() float64 - Minutes() float64 - Seconds() float64 - } - dd := d.(dur) - h := int(dd.Hours()) - m := int(dd.Minutes()) % 60 - if h > 0 { - return fmt.Sprintf("%dh%dm", h, m) - } - s := int(dd.Seconds()) % 60 - if m > 0 { - return fmt.Sprintf("%dm%ds", m, s) - } - return fmt.Sprintf("%ds", s) -} - -func truncateStr(s string, max int) string { - if len(s) <= max { - return s - } - return s[:max] + "..." -} diff --git a/docs/pkg/cli/daemon.md b/docs/pkg/cli/daemon.md index 140c7eea..05c16d9f 100644 --- a/docs/pkg/cli/daemon.md +++ b/docs/pkg/cli/daemon.md @@ -5,7 +5,7 @@ description: Daemon process management, PID files, health checks, and execution # Daemon Mode -The framework provides both low-level daemon primitives and a high-level command group that adds `start`, `stop`, `status`, and `run` subcommands to your CLI. +The framework provides execution mode detection and signal handling for daemon processes. ## Execution Modes @@ -29,63 +29,9 @@ cli.IsStdinTTY() // stdin is a terminal? cli.IsStderrTTY() // stderr is a terminal? ``` -## Adding Daemon Commands +## Simple Daemon -`AddDaemonCommand` registers a command group with four subcommands: - -```go -func AddMyCommands(root *cli.Command) { - cli.AddDaemonCommand(root, cli.DaemonCommandConfig{ - Name: "daemon", // Command group name (default: "daemon") - Description: "Manage the worker", // Short description - PIDFile: "/var/run/myapp.pid", - HealthAddr: ":9090", - RunForeground: func(ctx context.Context, daemon *process.Daemon) error { - // Your long-running service logic here. - // ctx is cancelled on SIGINT/SIGTERM. - return runWorker(ctx) - }, - }) -} -``` - -This creates: - -- `myapp daemon start` -- Re-executes the binary as a background process with `CORE_DAEMON=1` -- `myapp daemon stop` -- Sends SIGTERM to the daemon, waits for shutdown (30s timeout, then SIGKILL) -- `myapp daemon status` -- Reports whether the daemon is running and queries health endpoints -- `myapp daemon run` -- Runs in the foreground (for development or process managers like systemd) - -### Custom Persistent Flags - -Add flags that apply to all daemon subcommands: - -```go -cli.AddDaemonCommand(root, cli.DaemonCommandConfig{ - // ... - Flags: func(cmd *cli.Command) { - cli.PersistentStringFlag(cmd, &configPath, "config", "c", "", "Config file") - }, - ExtraStartArgs: func() []string { - return []string{"--config", configPath} - }, -}) -``` - -`ExtraStartArgs` passes additional flags when re-executing the binary as a daemon. - -### Health Endpoints - -When `HealthAddr` is set, the daemon serves: - -- `GET /health` -- Liveness check (200 if server is up, 503 if health checks fail) -- `GET /ready` -- Readiness check (200 if `daemon.SetReady(true)` has been called) - -The `start` command waits up to 5 seconds for the health endpoint to become available before reporting success. - -## Simple Daemon (Manual) - -For cases where you do not need the full command group: +Use `cli.Context()` for cancellation-aware daemon loops: ```go func runDaemon(cmd *cli.Command, args []string) error { @@ -117,15 +63,3 @@ cli.Init(cli.Options{ ``` No manual signal handling is needed in commands. Use `cli.Context()` for cancellation-aware operations. - -## DaemonCommandConfig Reference - -| Field | Type | Description | -|-------|------|-------------| -| `Name` | `string` | Command group name (default: `"daemon"`) | -| `Description` | `string` | Short description for help text | -| `PIDFile` | `string` | PID file path (default flag value) | -| `HealthAddr` | `string` | Health check listen address (default flag value) | -| `RunForeground` | `func(ctx, daemon) error` | Service logic for foreground/daemon mode | -| `Flags` | `func(cmd)` | Registers custom persistent flags | -| `ExtraStartArgs` | `func() []string` | Additional args for background re-exec | diff --git a/docs/pkg/cli/getting-started.md b/docs/pkg/cli/getting-started.md index 8f669d95..9405f19e 100644 --- a/docs/pkg/cli/getting-started.md +++ b/docs/pkg/cli/getting-started.md @@ -57,10 +57,10 @@ If a command returns an `*ExitError`, the process exits with that code. All othe This is the preferred way to register commands. It wraps your registration function in a Core service that participates in the lifecycle: ```go -func WithCommands(name string, register func(root *Command)) core.Option +func WithCommands(name string, register func(root *Command), localeFS ...fs.FS) CommandSetup ``` -During startup, the Core framework calls your function with the root cobra command. Your function adds subcommands to it: +During `Main()`, the CLI calls your function with the Core instance. Internally it retrieves the root cobra command and passes it to your register function: ```go func AddScoreCommands(root *cli.Command) { @@ -98,18 +98,17 @@ func main() { } ``` -Where `Commands()` returns a slice of framework options: +Where `Commands()` returns a slice of `CommandSetup` functions: ```go package lemcmd import ( - "forge.lthn.ai/core/go/pkg/core" "forge.lthn.ai/core/cli/pkg/cli" ) -func Commands() []core.Option { - return []core.Option{ +func Commands() []cli.CommandSetup { + return []cli.CommandSetup{ cli.WithCommands("score", addScoreCommands), cli.WithCommands("gen", addGenCommands), cli.WithCommands("data", addDataCommands), @@ -141,7 +140,7 @@ If you need more control over the lifecycle: cli.Init(cli.Options{ AppName: "myapp", Version: "1.0.0", - Services: []core.Option{...}, + Services: []core.Service{...}, OnReload: func() error { return reloadConfig() }, }) defer cli.Shutdown() diff --git a/go.mod b/go.mod index 3b992e64..9ab25215 100644 --- a/go.mod +++ b/go.mod @@ -2,197 +2,44 @@ module forge.lthn.ai/core/cli go 1.26.0 -require ( - forge.lthn.ai/core/config v0.1.0 - forge.lthn.ai/core/go v0.3.0 - forge.lthn.ai/core/go-cache v0.1.0 - forge.lthn.ai/core/go-crypt v0.1.0 -) +require dappco.re/go/core v0.4.7 require ( - forge.lthn.ai/core/agent v0.2.0 - forge.lthn.ai/core/api v0.1.0 - forge.lthn.ai/core/go-ansible v0.1.1 - forge.lthn.ai/core/go-build v0.2.0 - forge.lthn.ai/core/go-container v0.1.1 - forge.lthn.ai/core/go-devops v0.0.3 - forge.lthn.ai/core/go-help v0.1.2 - forge.lthn.ai/core/go-i18n v0.1.0 - forge.lthn.ai/core/go-infra v0.1.1 - forge.lthn.ai/core/go-io v0.1.0 - forge.lthn.ai/core/go-log v0.0.1 - forge.lthn.ai/core/go-process v0.1.2 - forge.lthn.ai/core/go-scm v0.2.0 - forge.lthn.ai/core/go-session v0.1.4 - forge.lthn.ai/core/lint v0.3.0 + forge.lthn.ai/core/go-i18n v0.1.7 + forge.lthn.ai/core/go-log v0.0.4 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 - golang.org/x/term v0.40.0 - gopkg.in/yaml.v3 v3.0.1 + golang.org/x/term v0.41.0 ) require ( - cloud.google.com/go v0.123.0 // indirect - code.gitea.io/sdk/gitea v0.23.2 // indirect - codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0 // indirect - forge.lthn.ai/core/go-agentic v0.0.2 // indirect - forge.lthn.ai/core/go-inference v0.1.0 // indirect - forge.lthn.ai/core/go-ratelimit v0.1.0 // indirect - forge.lthn.ai/core/go-store v0.1.3 // indirect - github.com/42wim/httpsig v1.2.3 // indirect - github.com/99designs/gqlgen v0.17.87 // indirect - github.com/KyleBanks/depth v1.2.1 // indirect - github.com/ProtonMail/go-crypto v1.3.0 // indirect - github.com/Snider/Borg v0.2.0 // indirect - github.com/TwiN/go-color v1.4.1 // indirect - github.com/agnivade/levenshtein v1.2.1 // indirect - github.com/andybalholm/brotli v1.2.0 // indirect + forge.lthn.ai/core/go v0.3.2 // indirect + forge.lthn.ai/core/go-inference v0.1.7 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect - github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic v1.15.0 // indirect - github.com/bytedance/sonic/loader v0.5.0 // indirect - github.com/casbin/casbin/v2 v2.135.0 // indirect - github.com/casbin/govaluate v1.10.0 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/charmbracelet/colorprofile v0.4.2 // indirect + github.com/charmbracelet/colorprofile v0.4.3 // indirect github.com/charmbracelet/x/ansi v0.11.6 // indirect github.com/charmbracelet/x/cellbuf v0.0.15 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect - github.com/cloudflare/circl v1.6.3 // indirect - github.com/cloudwego/base64x v0.1.6 // indirect - github.com/coreos/go-oidc/v3 v3.17.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/davidmz/go-pageant v1.0.2 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.13 // indirect - github.com/getkin/kin-openapi v0.133.0 // indirect - github.com/gin-contrib/authz v1.0.6 // indirect - github.com/gin-contrib/cors v1.7.6 // indirect - github.com/gin-contrib/expvar v1.0.3 // indirect - github.com/gin-contrib/gzip v1.2.5 // indirect - github.com/gin-contrib/httpsign v1.0.3 // indirect - github.com/gin-contrib/location/v2 v2.0.0 // indirect - github.com/gin-contrib/pprof v1.5.3 // indirect - github.com/gin-contrib/secure v1.1.2 // indirect - github.com/gin-contrib/sessions v1.0.4 // indirect - github.com/gin-contrib/slog v1.2.0 // indirect - github.com/gin-contrib/sse v1.1.0 // indirect - github.com/gin-contrib/static v1.1.5 // indirect - github.com/gin-contrib/timeout v1.1.0 // indirect - github.com/gin-gonic/gin v1.12.0 // indirect - github.com/go-fed/httpsig v1.1.0 // indirect - github.com/go-jose/go-jose/v4 v4.1.3 // indirect - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.4 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/spec v0.22.0 // indirect - github.com/go-openapi/swag/conv v0.25.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.4 // indirect - github.com/go-openapi/swag/jsonutils v0.25.1 // indirect - github.com/go-openapi/swag/loading v0.25.1 // indirect - github.com/go-openapi/swag/stringutils v0.25.1 // indirect - github.com/go-openapi/swag/typeutils v0.25.1 // indirect - github.com/go-openapi/swag/yamlutils v0.25.1 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.30.1 // indirect - github.com/go-viper/mapstructure/v2 v2.5.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect - github.com/goccy/go-yaml v1.19.2 // indirect - github.com/gofrs/flock v0.12.1 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/context v1.1.2 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect - github.com/gorilla/sessions v1.4.0 // indirect - github.com/gorilla/websocket v1.5.3 // indirect - github.com/hashicorp/go-version v1.8.0 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/kluctl/go-embed-python v0.0.0-3.13.1-20241219-1 // indirect - github.com/leaanthony/debme v1.2.1 // indirect - github.com/leaanthony/gosod v1.0.4 // indirect - github.com/leodido/go-urn v1.4.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect - github.com/mailru/easyjson v0.9.1 // 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.20 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/mattn/go-runewidth v0.0.21 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.16.0 // indirect - github.com/ncruces/go-strftime v1.0.0 // indirect - github.com/oasdiff/oasdiff v1.11.10 // indirect - github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect - github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.59.0 // indirect - github.com/redis/go-redis/v9 v9.18.0 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/sagikazarmark/locafero v0.12.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/sosodev/duration v1.3.1 // indirect - github.com/spf13/afero v1.15.0 // indirect - github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/spf13/viper v1.21.0 // indirect - github.com/subosito/gotenv v1.6.0 // indirect - github.com/swaggo/files v1.0.1 // indirect - github.com/swaggo/gin-swagger v1.6.1 // indirect - github.com/swaggo/swag v1.16.6 // indirect - github.com/tidwall/gjson v1.18.0 // indirect - github.com/tidwall/match v1.2.0 // indirect - github.com/tidwall/pretty v1.2.1 // indirect - github.com/tidwall/sjson v1.2.5 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.3.1 // indirect - github.com/ulikunitz/xz v0.5.15 // indirect - github.com/vektah/gqlparser/v2 v2.5.32 // indirect - github.com/wI2L/jsondiff v0.7.0 // indirect - github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/yargevad/filepathx v1.0.0 // indirect - github.com/yuin/goldmark v1.7.16 // indirect - go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.65.0 // indirect - go.opentelemetry.io/otel v1.40.0 // indirect - go.opentelemetry.io/otel/metric v1.40.0 // indirect - go.opentelemetry.io/otel/sdk v1.40.0 // indirect - go.opentelemetry.io/otel/trace v1.40.0 // indirect - go.uber.org/atomic v1.11.0 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/arch v0.23.0 // indirect - golang.org/x/crypto v0.48.0 // indirect - golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect - golang.org/x/mod v0.33.0 // indirect - golang.org/x/net v0.51.0 // indirect - golang.org/x/oauth2 v0.35.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.34.0 // indirect - golang.org/x/tools v0.42.0 // indirect - google.golang.org/protobuf v1.36.11 // indirect - modernc.org/libc v1.68.0 // indirect - modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.11.0 // indirect - modernc.org/sqlite v1.46.1 // indirect + golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1854b41b..b3913d60 100644 --- a/go.sum +++ b/go.sum @@ -1,105 +1,19 @@ -cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= -cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= -code.gitea.io/sdk/gitea v0.23.2 h1:iJB1FDmLegwfwjX8gotBDHdPSbk/ZR8V9VmEJaVsJYg= -code.gitea.io/sdk/gitea v0.23.2/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM= -codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0 h1:HTCWpzyWQOHDWt3LzI6/d2jvUDsw/vgGRWm/8BTvcqI= -codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0/go.mod h1:ZglEEDj+qkxYUb+SQIeqGtFxQrbaMYqIOgahNKb7uxs= -forge.lthn.ai/core/agent v0.2.0 h1:sx5NEeDd9uAi6lJJKj/MHIYfK+aIgcBm4hx8pJ/GvKs= -forge.lthn.ai/core/agent v0.2.0/go.mod h1:8LwRpgyAW70zTmPGVa6Ncs6+Y/ddFd6WLmGhJry41wU= -forge.lthn.ai/core/api v0.1.0 h1:ZKnQx+L9vxLQSEjwpsD1eNcIQrE4YKV1c2AlMtseM6o= -forge.lthn.ai/core/api v0.1.0/go.mod h1:c86Lk9AmaS0xbiRCEG/+du8s9KyYNHnp8RED35gR/Fo= -forge.lthn.ai/core/config v0.1.0 h1:qj14x/dnOWcsXMBQWAT3FtA+/sy6Qd+1NFTg5Xoil1I= -forge.lthn.ai/core/config v0.1.0/go.mod h1:8HYA29drAWlX+bO4VI1JhmKUgGU66E2Xge8D3tKd3Dg= -forge.lthn.ai/core/go v0.3.0 h1:mOG97ApMprwx9Ked62FdWVwXTGSF6JO6m0DrVpoH2Q4= -forge.lthn.ai/core/go v0.3.0/go.mod h1:gE6c8h+PJ2287qNhVUJ5SOe1kopEwHEquvinstpuyJc= -forge.lthn.ai/core/go-agentic v0.0.2 h1:G2nhiFY0j66A8/dyPXrS3CDYT1VLIin//GDszz4zEEo= -forge.lthn.ai/core/go-agentic v0.0.2/go.mod h1:wTZRajs+rt0YJbRk26ijC1sfICbg8O2782ZhCz2tv/k= -forge.lthn.ai/core/go-ansible v0.1.1 h1:OexkGQ5uxu1ZY6oFsBdhE6uYfdJH4vClmSsqrLCtJUo= -forge.lthn.ai/core/go-ansible v0.1.1/go.mod h1:YzzsLN6oMvA3WsiXBuvVVSs7CrNc4ncPHaGw/hst9sc= -forge.lthn.ai/core/go-build v0.2.0 h1:wFn343k/VWUneUGlVqq12Zh+FHQFPxoo90SePCK0RsM= -forge.lthn.ai/core/go-build v0.2.0/go.mod h1:7+Olm65EhM4OWwDFPpqG2WW9y9D5gl52WhOJA7sRdTY= -forge.lthn.ai/core/go-cache v0.1.0 h1:yxPf4bWPZ1jxMnXg8UHBv2xLhet2CRsq5E9PLQYjyj4= -forge.lthn.ai/core/go-cache v0.1.0/go.mod h1:7WbprZVfx/+t4cbJFXMo4sloWk2Eny+rZd8x1Ay9rLk= -forge.lthn.ai/core/go-container v0.1.1 h1:dpx0BLwGZEz1k5e7Jjmq1PgyP0Q8VgC1C/IvCN+6y5Y= -forge.lthn.ai/core/go-container v0.1.1/go.mod h1:fw/UHnrSW4cEsnRZLZkkJd+b57d1o2Lk/lOl9LwXIXQ= -forge.lthn.ai/core/go-crypt v0.1.0 h1:92gwdQi7iAwktpvZhL/8Cu+QS6xKCtGP4FJfyInPGnw= -forge.lthn.ai/core/go-crypt v0.1.0/go.mod h1:zVAgx6ZiGtC+dbX4R/VKvEPqsEqjyuLl4gQZH9SXBUw= -forge.lthn.ai/core/go-devops v0.0.3 h1:tiSZ2x6a/H1A1IYYUmaM+bEuZqT9Hot7KGCEFN6PSYY= -forge.lthn.ai/core/go-devops v0.0.3/go.mod h1:V5/YaRsrDsYlSnCCJXKX7h1zSbaGyRdRQApPF5XwGAo= -forge.lthn.ai/core/go-help v0.1.2 h1:JP8hhJDAvfjvPuCyLRbU/VEm7YkENAs8debItLkon3w= -forge.lthn.ai/core/go-help v0.1.2/go.mod h1:JSZVb4Gd+P/dTc9laDJsqVCI6OrVbBbBPyPmvw3j4p4= -forge.lthn.ai/core/go-i18n v0.1.0 h1:F7JVSoVkZtzx9JfhpntM9z3iQm1vnuMUi/Zklhz8PCI= -forge.lthn.ai/core/go-i18n v0.1.0/go.mod h1:Q4xsrxuNCl/6NfMv1daria7t1RSiyy8ml+6jiPtUcBs= -forge.lthn.ai/core/go-inference v0.1.0 h1:pO7etYgqV8LMKFdpW8/2RWncuECZJCIcf8nnezeZ5R4= -forge.lthn.ai/core/go-inference v0.1.0/go.mod h1:jfWz+IJX55wAH98+ic6FEqqGB6/P31CHlg7VY7pxREw= -forge.lthn.ai/core/go-infra v0.1.1 h1:1vagpgFHuvtqWtUXM3vej164Y6lDboo1HigvhpMgt7A= -forge.lthn.ai/core/go-infra v0.1.1/go.mod h1:TQdwQuMf7NJHoXU+XV5JiNF9K5VKKxVpkZvJMk+iJ4c= -forge.lthn.ai/core/go-io v0.1.0 h1:aYNvmbU2VVsjXnut0WQ4DfVxcFdheziahJB32mfeJ7g= -forge.lthn.ai/core/go-io v0.1.0/go.mod h1:ZlU9OQpsvNFNmTJoaHbFIkisZyc0eCq0p8znVWQLRf0= -forge.lthn.ai/core/go-log v0.0.1 h1:x/E6EfF9vixzqiLHQOl2KT25HyBcMc9qiBkomqVlpPg= -forge.lthn.ai/core/go-log v0.0.1/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw= -forge.lthn.ai/core/go-process v0.1.2 h1:0fdLJq/DPssilN9E5yude/xHNfZRKHghIjo++b5aXgc= -forge.lthn.ai/core/go-process v0.1.2/go.mod h1:9oxVALrZaZCqFe8YDdheIS5bRUV1SBz4tVW/MflAtxM= -forge.lthn.ai/core/go-ratelimit v0.1.0 h1:8Y6Mb/K5FMDng4B0wIh7beD05KXddi1BDwatI96XouA= -forge.lthn.ai/core/go-ratelimit v0.1.0/go.mod h1:YdpKYTjx0Amw5Wn2fenl50zVLkdfZcp7pIb3wmv0fHw= -forge.lthn.ai/core/go-scm v0.2.0 h1:TvDyCzw0HWzXjmqe6uPc46nPaRzc7MPGswmwZt0CmXo= -forge.lthn.ai/core/go-scm v0.2.0/go.mod h1:Q/PV2FbqDlWnAOsXAd1pgSiHOlRCPW4HcPmOt8Z9H+E= -forge.lthn.ai/core/go-session v0.1.4 h1:AWdE7g2Aze2XE/yMfJVE/I907Secd5Mp1CODgAm4xWY= -forge.lthn.ai/core/go-session v0.1.4/go.mod h1:c0mzZE6U05+T9s0MaNsJZ2kgW1cqIRH/KIGbaBXG16k= -forge.lthn.ai/core/go-store v0.1.3 h1:CSVTRdsOXm2pl+FCs12fHOc9eM88DcZRY6HghN98w/I= -forge.lthn.ai/core/go-store v0.1.3/go.mod h1:op+ftjAqYskPv4OGvHZQf7/DLiRnFIdT0XCQTKR/GjE= -forge.lthn.ai/core/lint v0.3.0 h1:ar5TSsoMsHWbfubQbWQAEqBzHixy93un1rWZQTjB/B0= -forge.lthn.ai/core/lint v0.3.0/go.mod h1:0/1HXRIX2qV2+dQH0HmUMMV9u1hEta6tj3K+mpo4Kdg= -github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs= -github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM= -github.com/99designs/gqlgen v0.17.87 h1:pSnCIMhBQezAE8bc1GNmfdLXFmnWtWl1GRDFEE/nHP8= -github.com/99designs/gqlgen v0.17.87/go.mod h1:fK05f1RqSNfQpd4CfW5qk/810Tqi4/56Wf6Nem0khAg= -github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= -github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -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/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw= -github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ= -github.com/Snider/Borg v0.2.0 h1:iCyDhY4WTXi39+FexRwXbn2YpZ2U9FUXVXDZk9xRCXQ= -github.com/Snider/Borg v0.2.0/go.mod h1:TqlKnfRo9okioHbgrZPfWjQsztBV0Nfskz4Om1/vdMY= -github.com/TwiN/go-color v1.4.1 h1:mqG0P/KBgHKVqmtL5ye7K0/Gr4l6hTksPgTgMk3mUzc= -github.com/TwiN/go-color v1.4.1/go.mod h1:WcPf/jtiW95WBIsEeY1Lc/b8aaWoiqQpu5cf8WFxu+s= -github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= -github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= -github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= -github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= -github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= -github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= -github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +dappco.re/go/core v0.4.7 h1:KmIA/2lo6rl1NMtLrKqCWfMlUqpDZYH3q0/d10dTtGA= +dappco.re/go/core v0.4.7/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A= +forge.lthn.ai/core/go v0.3.2 h1:VB9pW6ggqBhe438cjfE2iSI5Lg+62MmRbaOFglZM+nQ= +forge.lthn.ai/core/go v0.3.2/go.mod h1:f7/zb3Labn4ARfwTq5Bi2AFHY+uxyPHozO+hLb54eFo= +forge.lthn.ai/core/go-i18n v0.1.7 h1:aHkAoc3W8fw3RPNvw/UszQbjyFWXHszzbZgty3SwyAA= +forge.lthn.ai/core/go-i18n v0.1.7/go.mod h1:0VDjwtY99NSj2iqwrI09h5GUsJeM9s48MLkr+/Dn4G8= +forge.lthn.ai/core/go-inference v0.1.7 h1:9Dy6v03jX5ZRH3n5iTzlYyGtucuBIgSe+S7GWvBzx9Q= +forge.lthn.ai/core/go-inference v0.1.7/go.mod h1:jfWz+IJX55wAH98+ic6FEqqGB6/P31CHlg7VY7pxREw= +forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0= +forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE= -github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= -github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= -github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= -github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= -github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= -github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= -github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= -github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= -github.com/casbin/casbin/v2 v2.135.0 h1:6BLkMQiGotYyS5yYeWgW19vxqugUlvHFkFiLnLR/bxk= -github.com/casbin/casbin/v2 v2.135.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18= -github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= -github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0= -github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= -github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY= -github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8= +github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= +github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= @@ -112,408 +26,60 @@ github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSE github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= -github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= -github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= -github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= -github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= -github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= -github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= -github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= -github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -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/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= -github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= -github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= -github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= -github.com/gin-contrib/authz v1.0.6 h1:qAO4sSSzOPCwYRZI6YtubC+h2tZVwhwSJeyEZn2W+5k= -github.com/gin-contrib/authz v1.0.6/go.mod h1:A2B5Im1M/HIoHPjLc31j3RlENSE6j8euJY9NFdzZeYo= -github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY= -github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk= -github.com/gin-contrib/expvar v1.0.3 h1:nIbUaokxZfUEC/35h+RyWCP1SMF/suV/ARbXL3H3jrw= -github.com/gin-contrib/expvar v1.0.3/go.mod h1:bwqqmhty1Zl2JYVLzBIL6CSHDWDbQoQoicalAnBvUnY= -github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI= -github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw= -github.com/gin-contrib/httpsign v1.0.3 h1:NpeDQjmUV0qFjGCm/rkXSp3HH0hU7r84q1v+VtTiI5I= -github.com/gin-contrib/httpsign v1.0.3/go.mod h1:n4GC7StmHNBhIzWzuW2njKbZMeEWh4tDbmn3bD1ab+k= -github.com/gin-contrib/location/v2 v2.0.0 h1:iLx5RatHQHSxgC0tm2AG0sIuQKecI7FhREessVd6RWY= -github.com/gin-contrib/location/v2 v2.0.0/go.mod h1:276TDNr25NENBA/NQZUuEIlwxy/I5CYVFIr/d2TgOdU= -github.com/gin-contrib/pprof v1.5.3 h1:Bj5SxJ3kQDVez/s/+f9+meedJIqLS+xlkIVDe/lcvgM= -github.com/gin-contrib/pprof v1.5.3/go.mod h1:0+LQSZ4SLO0B6+2n6JBzaEygpTBxe/nI+YEYpfQQ6xY= -github.com/gin-contrib/secure v1.1.2 h1:6G8/NCOTSywWY7TeaH/0Yfaa6bfkE5ukkqtIm7lK11U= -github.com/gin-contrib/secure v1.1.2/go.mod h1:xI3jI5/BpOYMCBtjgmIVrMA3kI7y9LwCFxs+eLf5S3w= -github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U= -github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs= -github.com/gin-contrib/slog v1.2.0 h1:vAxZfr7knD1ZYK5+pMJLP52sZXIkJXkcRPa/0dx9hSk= -github.com/gin-contrib/slog v1.2.0/go.mod h1:vYK6YltmpsEFkO0zfRMLTKHrWS3DwUSn0TMpT+kMagI= -github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= -github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= -github.com/gin-contrib/static v1.1.5 h1:bAPqT4KTZN+4uDY1b90eSrD1t8iNzod7Jj8njwmnzz4= -github.com/gin-contrib/static v1.1.5/go.mod h1:8JSEXwZHcQ0uCrLPcsvnAJ4g+ODxeupP8Zetl9fd8wM= -github.com/gin-contrib/timeout v1.1.0 h1:WAmWseo5gfBUbMrMJu5hJxDclehfSJUmK2wGwCC/EFw= -github.com/gin-contrib/timeout v1.1.0/go.mod h1:NpRo4gd1Ad8ZQ4T6bQLVFDqiplCmPRs2nvfckxS2Fw4= -github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= -github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= -github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= -github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= -github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= -github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= -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-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= -github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/spec v0.22.0 h1:xT/EsX4frL3U09QviRIZXvkh80yibxQmtoEvyqug0Tw= -github.com/go-openapi/spec v0.22.0/go.mod h1:K0FhKxkez8YNS94XzF8YKEMULbFrRw4m15i2YUht4L0= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0= -github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs= -github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= -github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= -github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8= -github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg= -github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw= -github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc= -github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw= -github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg= -github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA= -github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= -github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk= -github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= -github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= -github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= -github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= -github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= -github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= -github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= -github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= -github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= -github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= -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/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= -github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= -github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= -github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= -github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/kluctl/go-embed-python v0.0.0-3.13.1-20241219-1 h1:x1cSEj4Ug5mpuZgUHLvUmlc5r//KHFn6iYiRSrRcVy4= -github.com/kluctl/go-embed-python v0.0.0-3.13.1-20241219-1/go.mod h1:3ebNU9QBrNpUO+Hj6bHaGpkh5pymDHQ+wwVPHTE4mCE= 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= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= -github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= -github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI= -github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw= -github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= -github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= -github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= -github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -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-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/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ= -github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= +github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= -github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/oasdiff/oasdiff v1.11.10 h1:4I9VrktUoHmwydkJqVOC7Bd6BXKu9dc4UUP3PIu1VjM= -github.com/oasdiff/oasdiff v1.11.10/go.mod h1:GXARzmqBKN8lZHsTQD35ZM41ePbu6JdAZza4sRMeEKg= -github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= -github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= -github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= -github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= -github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= -github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= -github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= -github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= -github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= -github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= -github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= -github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= -github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= -github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= -github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= -github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= -github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= -github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= -github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= -github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY= -github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw= -github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= -github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= -github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= -github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= -github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= -github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= -github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= -github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/vektah/gqlparser/v2 v2.5.32 h1:k9QPJd4sEDTL+qB4ncPLflqTJ3MmjB9SrVzJrawpFSc= -github.com/vektah/gqlparser/v2 v2.5.32/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts= -github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ= -github.com/wI2L/jsondiff v0.7.0/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM= -github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= -github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= -github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc= -github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= -github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= -github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= -github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= -go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= -go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.65.0 h1:LSJsvNqhj2sBNFb5NWHbyDK4QJ/skQ2ydjeOZ9OYNZ4= -go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.65.0/go.mod h1:0Q5ocj6h/+C6KYq8cnl4tDFVd4I1HBdsJ440aeagHos= -go.opentelemetry.io/contrib/propagators/b3 v1.40.0 h1:xariChe8OOVF3rNlfzGFgQc61npQmXhzZj/i82mxMfg= -go.opentelemetry.io/contrib/propagators/b3 v1.40.0/go.mod h1:72WvbdxbOfXaELEQfonFfOL6osvcVjI7uJEE8C2nkrs= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= -go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= -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/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= -golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= -golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= -golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= -golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= -modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.30.2 h1:4yPaaq9dXYXZ2V8s1UgrC3KIj580l2N4ClrLwnbv2so= -modernc.org/ccgo/v4 v4.30.2/go.mod h1:yZMnhWEdW0qw3EtCndG1+ldRrVGS+bIwyWmAWzS0XEw= -modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= -modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= -modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= -modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= -modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= -modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= -modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.68.0 h1:PJ5ikFOV5pwpW+VqCK1hKJuEWsonkIJhhIXyuF/91pQ= -modernc.org/libc v1.68.0/go.mod h1:NnKCYeoYgsEqnY3PgvNgAeaJnso968ygU8Z0DxjoEc0= -modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= -modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= -modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= -modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= -modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= -modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= -modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= -modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= -modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/main.go b/main.go deleted file mode 100644 index dfc7ee32..00000000 --- a/main.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "forge.lthn.ai/core/cli/cmd/config" - "forge.lthn.ai/core/cli/cmd/doctor" - "forge.lthn.ai/core/cli/cmd/gocmd" - "forge.lthn.ai/core/cli/cmd/help" - "forge.lthn.ai/core/cli/cmd/module" - "forge.lthn.ai/core/cli/cmd/pkgcmd" - "forge.lthn.ai/core/cli/cmd/plugin" - "forge.lthn.ai/core/cli/cmd/service" - "forge.lthn.ai/core/cli/cmd/session" - "forge.lthn.ai/core/cli/pkg/cli" - - // Ecosystem command packages — self-register via init() + cli.RegisterCommands() - _ "forge.lthn.ai/core/agent/cmd/agent" - _ "forge.lthn.ai/core/agent/cmd/dispatch" - _ "forge.lthn.ai/core/agent/cmd/taskgit" - _ "forge.lthn.ai/core/go-ansible/cmd/ansible" - _ "forge.lthn.ai/core/api/cmd/api" - _ "forge.lthn.ai/core/go-build/cmd/build" - _ "forge.lthn.ai/core/go-build/cmd/ci" - _ "forge.lthn.ai/core/go-build/cmd/sdk" - _ "forge.lthn.ai/core/go-container/cmd/vm" - _ "forge.lthn.ai/core/go-crypt/cmd/crypt" - _ "forge.lthn.ai/core/go-devops/cmd/deploy" - _ "forge.lthn.ai/core/go-devops/cmd/dev" - _ "forge.lthn.ai/core/go-devops/cmd/docs" - _ "forge.lthn.ai/core/go-devops/cmd/gitcmd" - _ "forge.lthn.ai/core/go-devops/cmd/setup" - _ "forge.lthn.ai/core/go-infra/cmd/monitor" - _ "forge.lthn.ai/core/go-infra/cmd/prod" - _ "forge.lthn.ai/core/go-scm/cmd/collect" - _ "forge.lthn.ai/core/go-scm/cmd/forge" - _ "forge.lthn.ai/core/go-scm/cmd/gitea" - _ "forge.lthn.ai/core/lint/cmd/qa" -) - -func main() { - cli.Main( - cli.WithCommands("config", config.AddConfigCommands), - cli.WithCommands("doctor", doctor.AddDoctorCommands), - cli.WithCommands("help", help.AddHelpCommands), - cli.WithCommands("module", module.AddModuleCommands), - cli.WithCommands("pkg", pkgcmd.AddPkgCommands), - cli.WithCommands("plugin", plugin.AddPluginCommands), - cli.WithCommands("session", session.AddSessionCommands), - cli.WithCommands("go", gocmd.AddGoCommands), - cli.WithCommands("service", service.AddServiceCommands), - ) -} diff --git a/pkg/cli/app.go b/pkg/cli/app.go index cdcdfebe..fbc96c67 100644 --- a/pkg/cli/app.go +++ b/pkg/cli/app.go @@ -1,17 +1,21 @@ package cli import ( + "embed" "fmt" + "io/fs" "os" "runtime/debug" - "forge.lthn.ai/core/go-crypt/crypt/openpgp" - "forge.lthn.ai/core/go/pkg/core" + "forge.lthn.ai/core/go-i18n" "forge.lthn.ai/core/go-log" - "forge.lthn.ai/core/go-io/workspace" + "dappco.re/go/core" "github.com/spf13/cobra" ) +//go:embed locales/*.json +var cliLocaleFS embed.FS + // AppName is the default CLI application name. // Override with WithAppName before calling Main. var AppName = "core" @@ -56,17 +60,24 @@ func WithAppName(name string) { AppName = name } -// Main initialises and runs the CLI application. -// Pass command services via WithCommands to register CLI commands -// through the Core framework lifecycle. -// -// cli.Main( -// cli.WithCommands("config", config.AddConfigCommands), -// cli.WithCommands("doctor", doctor.AddDoctorCommands), -// ) -// -// Exits with code 1 on error or panic. -func Main(commands ...core.Option) { +// LocaleSource pairs a filesystem with a directory for loading translations. +type LocaleSource = i18n.FSSource + +// WithLocales returns a locale source for use with MainWithLocales. +func WithLocales(fsys fs.FS, dir string) LocaleSource { + return LocaleSource{FS: fsys, Dir: dir} +} + +// CommandSetup is a function that registers commands on the CLI after init. +type CommandSetup func(c *core.Core) + +// Main initialises and runs the CLI with the framework's built-in translations. +func Main(commands ...CommandSetup) { + MainWithLocales(nil, commands...) +} + +// MainWithLocales initialises and runs the CLI with additional translation sources. +func MainWithLocales(locales []LocaleSource, commands ...CommandSetup) { // Recovery from panics defer func() { if r := recover(); r != nil { @@ -76,28 +87,31 @@ func Main(commands ...core.Option) { } }() - // Core services load first, then command services - services := []core.Option{ - core.WithName("i18n", NewI18nService(I18nOptions{})), - core.WithName("log", NewLogService(log.Options{ - Level: log.LevelInfo, - })), - core.WithName("crypt", openpgp.New), - core.WithName("workspace", workspace.New), + // Build locale sources: framework built-in + caller's extras + registered packages + extraFS := []i18n.FSSource{ + {FS: cliLocaleFS, Dir: "locales"}, + } + extraFS = append(extraFS, locales...) + for _, lfs := range RegisteredLocales() { + extraFS = append(extraFS, i18n.FSSource{FS: lfs, Dir: "."}) } - services = append(services, commands...) - // Initialise CLI runtime with services + // Initialise CLI runtime if err := Init(Options{ - AppName: AppName, - Version: SemVer(), - Services: services, + AppName: AppName, + Version: SemVer(), + I18nSources: extraFS, }); err != nil { Error(err.Error()) os.Exit(1) } defer Shutdown() + // Run command setup functions + for _, setup := range commands { + setup(Core()) + } + // Add completion command to the CLI's root RootCmd().AddCommand(newCompletionCmd()) diff --git a/pkg/cli/commands.go b/pkg/cli/commands.go index 5709fddc..e1d64950 100644 --- a/pkg/cli/commands.go +++ b/pkg/cli/commands.go @@ -2,68 +2,82 @@ package cli import ( - "context" + "io/fs" "iter" "sync" - "forge.lthn.ai/core/go/pkg/core" + "dappco.re/go/core" "github.com/spf13/cobra" ) -// WithCommands creates a framework Option that registers a command group. -// The register function receives the root command during service startup, -// allowing commands to participate in the Core lifecycle. +// WithCommands returns a CommandSetup that registers a command group. +// The register function receives the root cobra command during Main(). // // cli.Main( // cli.WithCommands("config", config.AddConfigCommands), // cli.WithCommands("doctor", doctor.AddDoctorCommands), // ) -func WithCommands(name string, register func(root *Command)) core.Option { - return core.WithName("cmd."+name, func(c *core.Core) (any, error) { - return &commandService{core: c, register: register}, nil - }) -} - -type commandService struct { - core *core.Core - register func(root *Command) -} - -func (s *commandService) OnStartup(_ context.Context) error { - if root, ok := s.core.App.(*cobra.Command); ok { - s.register(root) +func WithCommands(name string, register func(root *Command), localeFS ...fs.FS) CommandSetup { + return func(c *core.Core) { + if root, ok := c.App().Runtime.(*cobra.Command); ok { + register(root) + } + appendLocales(localeFS...) } - return nil } -// CommandRegistration is a function that adds commands to the root. +// CommandRegistration is a function that adds commands to the CLI root. type CommandRegistration func(root *cobra.Command) var ( registeredCommands []CommandRegistration registeredCommandsMu sync.Mutex commandsAttached bool + registeredLocales []fs.FS ) // RegisterCommands registers a function that adds commands to the CLI. -// Call this in your package's init() to register commands. +// Optionally pass a locale fs.FS to provide translations for the commands. // // func init() { -// cli.RegisterCommands(AddCommands) +// cli.RegisterCommands(AddCommands, locales.FS) // } -// -// func AddCommands(root *cobra.Command) { -// root.AddCommand(myCmd) -// } -func RegisterCommands(fn CommandRegistration) { +func RegisterCommands(fn CommandRegistration, localeFS ...fs.FS) { registeredCommandsMu.Lock() - defer registeredCommandsMu.Unlock() registeredCommands = append(registeredCommands, fn) + attached := commandsAttached && instance != nil && instance.root != nil + root := instance + registeredCommandsMu.Unlock() + + appendLocales(localeFS...) // If commands already attached (CLI already running), attach immediately - if commandsAttached && instance != nil && instance.root != nil { - fn(instance.root) + if attached { + fn(root.root) + } +} + +// appendLocales appends non-nil locale filesystems to the registry. +func appendLocales(localeFS ...fs.FS) { + var nonempty []fs.FS + for _, lfs := range localeFS { + if lfs != nil { + nonempty = append(nonempty, lfs) + } } + if len(nonempty) == 0 { + return + } + registeredCommandsMu.Lock() + registeredLocales = append(registeredLocales, nonempty...) + registeredCommandsMu.Unlock() +} + +// RegisteredLocales returns all locale filesystems registered by command packages. +func RegisteredLocales() []fs.FS { + registeredCommandsMu.Lock() + defer registeredCommandsMu.Unlock() + return registeredLocales } // RegisteredCommands returns an iterator over the registered command functions. @@ -90,4 +104,3 @@ func attachRegisteredCommands(root *cobra.Command) { } commandsAttached = true } - diff --git a/pkg/cli/commands_test.go b/pkg/cli/commands_test.go index f5229564..a2f6d1f4 100644 --- a/pkg/cli/commands_test.go +++ b/pkg/cli/commands_test.go @@ -12,21 +12,16 @@ import ( // resetGlobals clears the CLI singleton and command registry for test isolation. func resetGlobals(t *testing.T) { t.Helper() - t.Cleanup(func() { - // Restore clean state after each test. - registeredCommandsMu.Lock() - registeredCommands = nil - commandsAttached = false - registeredCommandsMu.Unlock() - if instance != nil { - Shutdown() - } - instance = nil - once = sync.Once{} - }) + doReset() + t.Cleanup(doReset) +} +// doReset clears all package-level state. Only safe from a single goroutine +// with no concurrent RegisterCommands calls in flight (i.e. test setup/teardown). +func doReset() { registeredCommandsMu.Lock() registeredCommands = nil + registeredLocales = nil commandsAttached = false registeredCommandsMu.Unlock() if instance != nil { diff --git a/pkg/cli/daemon_cmd.go b/pkg/cli/daemon_cmd.go deleted file mode 100644 index 96d12c41..00000000 --- a/pkg/cli/daemon_cmd.go +++ /dev/null @@ -1,264 +0,0 @@ -package cli - -import ( - "context" - "fmt" - "net/http" - "os" - "os/exec" - "syscall" - "time" - - "forge.lthn.ai/core/go-process" -) - -// DaemonCommandConfig configures the generic daemon CLI command group. -type DaemonCommandConfig struct { - // Name is the command group name (default: "daemon"). - Name string - - // Description is the short description for the command group. - Description string - - // RunForeground is called when the daemon runs in foreground mode. - // Receives context (cancelled on SIGINT/SIGTERM) and the started Daemon. - // If nil, the run command just blocks until signal. - RunForeground func(ctx context.Context, daemon *process.Daemon) error - - // PIDFile default path. - PIDFile string - - // HealthAddr default address. - HealthAddr string - - // ExtraStartArgs returns additional CLI args to pass when re-execing - // the binary as a background daemon. - ExtraStartArgs func() []string - - // Flags registers custom persistent flags on the daemon command group. - Flags func(cmd *Command) -} - -// AddDaemonCommand registers start/stop/status/run subcommands on root. -func AddDaemonCommand(root *Command, cfg DaemonCommandConfig) { - if cfg.Name == "" { - cfg.Name = "daemon" - } - if cfg.Description == "" { - cfg.Description = "Manage the background daemon" - } - - daemonCmd := NewGroup( - cfg.Name, - cfg.Description, - fmt.Sprintf("Manage the background daemon process.\n\n"+ - "Subcommands:\n"+ - " start - Start the daemon in the background\n"+ - " stop - Stop the running daemon\n"+ - " status - Show daemon status\n"+ - " run - Run in foreground (for development/debugging)"), - ) - - PersistentStringFlag(daemonCmd, &cfg.HealthAddr, "health-addr", "", cfg.HealthAddr, - "Health check endpoint address (empty to disable)") - PersistentStringFlag(daemonCmd, &cfg.PIDFile, "pid-file", "", cfg.PIDFile, - "PID file path (empty to disable)") - - if cfg.Flags != nil { - cfg.Flags(daemonCmd) - } - - startCmd := NewCommand("start", "Start the daemon in the background", - "Re-executes the binary as a background daemon process.\n"+ - "The daemon PID is written to the PID file for later management.", - func(cmd *Command, args []string) error { - return daemonRunStart(cfg) - }, - ) - - stopCmd := NewCommand("stop", "Stop the running daemon", - "Sends SIGTERM to the daemon process identified by the PID file.\n"+ - "Waits for graceful shutdown before returning.", - func(cmd *Command, args []string) error { - return daemonRunStop(cfg) - }, - ) - - statusCmd := NewCommand("status", "Show daemon status", - "Checks if the daemon is running and queries its health endpoint.", - func(cmd *Command, args []string) error { - return daemonRunStatus(cfg) - }, - ) - - runCmd := NewCommand("run", "Run the daemon in the foreground", - "Runs the daemon in the current terminal (blocks until SIGINT/SIGTERM).\n"+ - "Useful for development, debugging, or running under a process manager.", - func(cmd *Command, args []string) error { - return daemonRunForeground(cfg) - }, - ) - - daemonCmd.AddCommand(startCmd, stopCmd, statusCmd, runCmd) - root.AddCommand(daemonCmd) -} - -func daemonRunStart(cfg DaemonCommandConfig) error { - if pid, running := process.ReadPID(cfg.PIDFile); running { - return fmt.Errorf("daemon already running (PID %d)", pid) - } - - exePath, err := os.Executable() - if err != nil { - return fmt.Errorf("failed to find executable: %w", err) - } - - args := []string{cfg.Name, "run", - "--health-addr", cfg.HealthAddr, - "--pid-file", cfg.PIDFile, - } - - if cfg.ExtraStartArgs != nil { - args = append(args, cfg.ExtraStartArgs()...) - } - - cmd := exec.Command(exePath, args...) - cmd.Env = append(os.Environ(), "CORE_DAEMON=1") - cmd.Stdout = nil - cmd.Stderr = nil - cmd.Stdin = nil - cmd.SysProcAttr = &syscall.SysProcAttr{ - Setsid: true, - } - - if err := cmd.Start(); err != nil { - return fmt.Errorf("failed to start daemon: %w", err) - } - - pid := cmd.Process.Pid - _ = cmd.Process.Release() - - if cfg.HealthAddr != "" { - if process.WaitForHealth(cfg.HealthAddr, 5_000) { - LogInfo(fmt.Sprintf("Daemon started (PID %d, health %s)", pid, cfg.HealthAddr)) - } else { - LogInfo(fmt.Sprintf("Daemon started (PID %d, health not yet ready)", pid)) - } - } else { - LogInfo(fmt.Sprintf("Daemon started (PID %d)", pid)) - } - - return nil -} - -func daemonRunStop(cfg DaemonCommandConfig) error { - pid, running := process.ReadPID(cfg.PIDFile) - if !running { - LogInfo("Daemon is not running") - return nil - } - - proc, err := os.FindProcess(pid) - if err != nil { - return fmt.Errorf("failed to find process %d: %w", pid, err) - } - - LogInfo(fmt.Sprintf("Stopping daemon (PID %d)", pid)) - if err := proc.Signal(syscall.SIGTERM); err != nil { - return fmt.Errorf("failed to send SIGTERM to PID %d: %w", pid, err) - } - - deadline := time.Now().Add(30 * time.Second) - for time.Now().Before(deadline) { - if err := proc.Signal(syscall.Signal(0)); err != nil { - // Process is gone — clean up PID file if it lingers. - _ = os.Remove(cfg.PIDFile) - LogInfo("Daemon stopped") - return nil - } - time.Sleep(250 * time.Millisecond) - } - - LogWarn("Daemon did not stop within 30s, sending SIGKILL") - _ = proc.Signal(syscall.SIGKILL) - _ = os.Remove(cfg.PIDFile) - LogInfo("Daemon killed") - return nil -} - -func daemonRunStatus(cfg DaemonCommandConfig) error { - pid, running := process.ReadPID(cfg.PIDFile) - if !running { - fmt.Println("Daemon is not running") - return nil - } - - fmt.Printf("Daemon is running (PID %d)\n", pid) - - if cfg.HealthAddr != "" { - healthURL := fmt.Sprintf("http://%s/health", cfg.HealthAddr) - resp, err := http.Get(healthURL) - if err != nil { - fmt.Printf("Health: unreachable (%v)\n", err) - return nil - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusOK { - fmt.Println("Health: ok") - } else { - fmt.Printf("Health: unhealthy (HTTP %d)\n", resp.StatusCode) - } - - readyURL := fmt.Sprintf("http://%s/ready", cfg.HealthAddr) - resp2, err := http.Get(readyURL) - if err == nil { - defer resp2.Body.Close() - if resp2.StatusCode == http.StatusOK { - fmt.Println("Ready: yes") - } else { - fmt.Println("Ready: no") - } - } - } - - return nil -} - -func daemonRunForeground(cfg DaemonCommandConfig) error { - os.Setenv("CORE_DAEMON", "1") - - daemon := process.NewDaemon(process.DaemonOptions{ - PIDFile: cfg.PIDFile, - HealthAddr: cfg.HealthAddr, - ShutdownTimeout: 30 * time.Second, - }) - - if err := daemon.Start(); err != nil { - return fmt.Errorf("failed to start daemon: %w", err) - } - - daemon.SetReady(true) - - ctx := Context() - - if cfg.RunForeground != nil { - svcErr := make(chan error, 1) - go func() { - svcErr <- cfg.RunForeground(ctx, daemon) - }() - - select { - case <-ctx.Done(): - LogInfo("Shutting down daemon") - case err := <-svcErr: - if err != nil { - LogError(fmt.Sprintf("Service exited with error: %v", err)) - } - } - } else { - <-ctx.Done() - } - - return daemon.Stop() -} diff --git a/pkg/cli/daemon_cmd_test.go b/pkg/cli/daemon_cmd_test.go deleted file mode 100644 index fbab05e7..00000000 --- a/pkg/cli/daemon_cmd_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package cli - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAddDaemonCommand_RegistersSubcommands(t *testing.T) { - root := &Command{Use: "test"} - - AddDaemonCommand(root, DaemonCommandConfig{ - Name: "daemon", - PIDFile: "/tmp/test-daemon.pid", - HealthAddr: "127.0.0.1:0", - }) - - // Should have the daemon command - daemonCmd, _, err := root.Find([]string{"daemon"}) - require.NoError(t, err) - require.NotNil(t, daemonCmd) - - // Should have subcommands - var subNames []string - for _, sub := range daemonCmd.Commands() { - subNames = append(subNames, sub.Name()) - } - assert.Contains(t, subNames, "start") - assert.Contains(t, subNames, "stop") - assert.Contains(t, subNames, "status") - assert.Contains(t, subNames, "run") -} - -func TestDaemonCommandConfig_DefaultName(t *testing.T) { - root := &Command{Use: "test"} - - AddDaemonCommand(root, DaemonCommandConfig{}) - - // Should default to "daemon" - daemonCmd, _, err := root.Find([]string{"daemon"}) - require.NoError(t, err) - require.NotNil(t, daemonCmd) -} diff --git a/pkg/cli/i18n.go b/pkg/cli/i18n.go index 0a54af6f..b5dc9987 100644 --- a/pkg/cli/i18n.go +++ b/pkg/cli/i18n.go @@ -1,170 +1,14 @@ package cli import ( - "context" - "sync" - - "forge.lthn.ai/core/go/pkg/core" "forge.lthn.ai/core/go-i18n" ) -// I18nService wraps i18n as a Core service. -type I18nService struct { - *core.ServiceRuntime[I18nOptions] - svc *i18n.Service - - // Collect mode state - missingKeys []i18n.MissingKey - missingKeysMu sync.Mutex -} - -// I18nOptions configures the i18n service. -type I18nOptions struct { - // Language overrides auto-detection (e.g., "en-GB", "de") - Language string - // Mode sets the translation mode (Normal, Strict, Collect) - Mode i18n.Mode -} - -// NewI18nService creates an i18n service factory. -func NewI18nService(opts I18nOptions) func(*core.Core) (any, error) { - return func(c *core.Core) (any, error) { - svc, err := i18n.New() - if err != nil { - return nil, err - } - - if opts.Language != "" { - _ = svc.SetLanguage(opts.Language) - } - - // Set mode if specified - svc.SetMode(opts.Mode) - - // Set as global default so i18n.T() works everywhere - i18n.SetDefault(svc) - - return &I18nService{ - ServiceRuntime: core.NewServiceRuntime(c, opts), - svc: svc, - missingKeys: make([]i18n.MissingKey, 0), - }, nil - } -} - -// OnStartup initialises the i18n service. -func (s *I18nService) OnStartup(ctx context.Context) error { - s.Core().RegisterQuery(s.handleQuery) - - // Register action handler for collect mode - if s.svc.Mode() == i18n.ModeCollect { - i18n.OnMissingKey(s.handleMissingKey) - } - - return nil -} - -// handleMissingKey accumulates missing keys in collect mode. -func (s *I18nService) handleMissingKey(mk i18n.MissingKey) { - s.missingKeysMu.Lock() - defer s.missingKeysMu.Unlock() - s.missingKeys = append(s.missingKeys, mk) -} - -// MissingKeys returns all missing keys collected in collect mode. -// Call this at the end of a QA session to report missing translations. -func (s *I18nService) MissingKeys() []i18n.MissingKey { - s.missingKeysMu.Lock() - defer s.missingKeysMu.Unlock() - result := make([]i18n.MissingKey, len(s.missingKeys)) - copy(result, s.missingKeys) - return result -} - -// ClearMissingKeys resets the collected missing keys. -func (s *I18nService) ClearMissingKeys() { - s.missingKeysMu.Lock() - defer s.missingKeysMu.Unlock() - s.missingKeys = s.missingKeys[:0] -} - -// SetMode changes the translation mode. -func (s *I18nService) SetMode(mode i18n.Mode) { - s.svc.SetMode(mode) - - // Update action handler registration - if mode == i18n.ModeCollect { - i18n.OnMissingKey(s.handleMissingKey) - } else { - i18n.OnMissingKey(nil) - } -} - -// Mode returns the current translation mode. -func (s *I18nService) Mode() i18n.Mode { - return s.svc.Mode() -} - -// Queries for i18n service - -// QueryTranslate requests a translation. -type QueryTranslate struct { - Key string - Args map[string]any -} - -func (s *I18nService) handleQuery(c *core.Core, q core.Query) (any, bool, error) { - switch m := q.(type) { - case QueryTranslate: - return s.svc.T(m.Key, m.Args), true, nil - } - return nil, false, nil -} - -// T translates a key with optional arguments. -func (s *I18nService) T(key string, args ...map[string]any) string { - if len(args) > 0 { - return s.svc.T(key, args[0]) - } - return s.svc.T(key) -} - -// SetLanguage changes the current language. -func (s *I18nService) SetLanguage(lang string) { - _ = s.svc.SetLanguage(lang) -} - -// Language returns the current language. -func (s *I18nService) Language() string { - return s.svc.Language() -} - -// AvailableLanguages returns all available languages. -func (s *I18nService) AvailableLanguages() []string { - return s.svc.AvailableLanguages() -} - -// --- Package-level convenience --- - // T translates a key using the CLI's i18n service. // Falls back to the global i18n.T if CLI not initialised. func T(key string, args ...map[string]any) string { - if instance == nil { - // CLI not initialised, use global i18n - if len(args) > 0 { - return i18n.T(key, args[0]) - } - return i18n.T(key) - } - - svc, err := core.ServiceFor[*I18nService](instance.core, "i18n") - if err != nil { - // i18n service not registered, use global - if len(args) > 0 { - return i18n.T(key, args[0]) - } - return i18n.T(key) + if len(args) > 0 { + return i18n.T(key, args[0]) } - - return svc.T(key, args...) + return i18n.T(key) } diff --git a/pkg/cli/locales/en.json b/pkg/cli/locales/en.json new file mode 100644 index 00000000..50cc311d --- /dev/null +++ b/pkg/cli/locales/en.json @@ -0,0 +1,135 @@ +{ + "cmd": { + "doctor": { + "short": "Check development environment", + "long": "Diagnose your development environment and report missing tools, configuration issues, and connectivity problems.", + "verbose_flag": "Show detailed output", + "required": "Required tools:", + "optional": "Optional tools:", + "github": "GitHub integration:", + "workspace": "Workspace:", + "ready": "Environment is ready", + "install_missing": "Install missing tools:", + "install_macos": "brew install", + "install_macos_cask": "brew install --cask", + "install_linux_header": "Install on Linux:", + "install_linux_git": "sudo apt install git", + "install_linux_node": "curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt install -y nodejs", + "install_linux_php": "sudo apt install php php-cli php-mbstring php-xml php-curl", + "install_linux_pnpm": "npm install -g pnpm", + "install_linux_gh": "See https://github.com/cli/cli/blob/trunk/docs/install_linux.md", + "install_other": "See tool documentation for installation", + "issues": "Open issues assigned to you:", + "issues_error": "Failed to fetch issues", + "cli_auth": "GitHub CLI authenticated", + "cli_auth_missing": "GitHub CLI not authenticated — run: gh auth login", + "ssh_found": "SSH key found", + "ssh_missing": "SSH key not found — run: ssh-keygen", + "repos_yaml_found": "Workspace registry found: {{.Path}}", + "repos_cloned": "{{.Cloned}}/{{.Total}} repos cloned", + "no_repos_yaml": "No repos.yaml found (run from workspace root)", + "check": { + "git": { "name": "Git", "description": "Version control" }, + "docker": { "name": "Docker", "description": "Container runtime" }, + "node": { "name": "Node.js", "description": "JavaScript runtime" }, + "php": { "name": "PHP", "description": "PHP interpreter" }, + "composer": { "name": "Composer", "description": "PHP package manager" }, + "pnpm": { "name": "pnpm", "description": "Node package manager" }, + "gh": { "name": "GitHub CLI", "description": "GitHub integration" }, + "claude": { "name": "Claude Code", "description": "AI coding assistant" } + } + }, + "pkg": { + "short": "Manage packages", + "long": "Install, list, search, update, and remove packages from the Core ecosystem.", + "no_description": "(no description)", + "error": { + "repo_required": "Repository argument required (e.g., core/go-io)", + "invalid_repo_format": "Invalid format — use org/repo (e.g., core/go-io)", + "no_repos_yaml": "No repos.yaml found", + "no_repos_yaml_workspace": "No repos.yaml found in workspace", + "specify_package": "Specify a package name", + "auth_failed": "Authentication failed", + "gh_not_authenticated": "GitHub CLI not authenticated — run: gh auth login", + "search_failed": "Search failed" + }, + "install": { + "short": "Install a package", + "long": "Clone a package from the Git forge into your workspace.", + "installing_label": "Installing", + "already_exists": "{{.Name}} already exists at {{.Path}}", + "installed": "{{.Name}} installed successfully", + "add_to_registry": "Adding to registry", + "added_to_registry": "Added to repos.yaml", + "flag": { + "dir": "Target directory (default: workspace base path)", + "add": "Add to repos.yaml registry after install" + } + }, + "list": { + "short": "List installed packages", + "long": "Show all packages registered in repos.yaml with their status.", + "title": "Installed packages", + "no_packages": "No packages found", + "summary": "{{.Count}} packages", + "install_missing": "Install missing: core pkg install" + }, + "search": { + "short": "Search available packages", + "long": "Search the forge for available packages by name or pattern.", + "fetching_label": "Searching", + "cache_label": "Cached", + "found_repos": "Found {{.Count}} repositories", + "no_repos_found": "No matching repositories found", + "private_label": "private", + "gh_token_unset": "GITHUB_TOKEN not set", + "gh_token_warning": "Set GITHUB_TOKEN for private repo access", + "flag": { + "org": "Organisation to search (default: core)", + "pattern": "Filter by name pattern", + "type": "Filter by type (package, application, template)", + "limit": "Maximum results", + "refresh": "Refresh cache" + } + }, + "update": { + "short": "Update a package", + "long": "Pull latest changes for a package or all packages.", + "updating": "Updating {{.Name}}", + "not_installed": "{{.Name}} is not installed", + "summary": "{{.Updated}}/{{.Total}} updated", + "flag": { + "all": "Update all packages" + } + }, + "outdated": { + "short": "Show outdated packages", + "long": "Check which packages have unpulled commits.", + "all_up_to_date": "All packages are up to date", + "commits_behind": "{{.Count}} commits behind", + "update_with": "Update with: core pkg update {{.Name}}", + "summary": "{{.Outdated}}/{{.Total}} outdated" + } + } + }, + "common": { + "hint": { + "install_with": "Install with: {{.Command}}" + }, + "progress": { + "checking": "Checking {{.Item}}...", + "checking_updates": "Checking for updates..." + }, + "status": { + "cloning": "Cloning", + "up_to_date": "Up to date" + } + }, + "i18n": { + "fail": { + "create": "Failed to create {{.Item}}", + "load": "Failed to load {{.Item}}", + "parse": "Failed to parse {{.Item}}" + } + } +} diff --git a/pkg/cli/log.go b/pkg/cli/log.go index c752f5a3..7a2e3df3 100644 --- a/pkg/cli/log.go +++ b/pkg/cli/log.go @@ -1,115 +1,28 @@ package cli import ( - "forge.lthn.ai/core/go/pkg/core" - "forge.lthn.ai/core/go/pkg/log" + "forge.lthn.ai/core/go-log" ) -// LogLevel aliases for backwards compatibility. +// LogLevel aliases for convenience. type LogLevel = log.Level -// Log level constants aliased from the log package. const ( - // LogLevelQuiet suppresses all output. LogLevelQuiet = log.LevelQuiet - // LogLevelError shows only error messages. LogLevelError = log.LevelError - // LogLevelWarn shows warnings and errors. - LogLevelWarn = log.LevelWarn - // LogLevelInfo shows info, warnings, and errors. - LogLevelInfo = log.LevelInfo - // LogLevelDebug shows all messages including debug. + LogLevelWarn = log.LevelWarn + LogLevelInfo = log.LevelInfo LogLevelDebug = log.LevelDebug ) -// LogService wraps log.Service with CLI styling. -type LogService struct { - *log.Service -} +// LogDebug logs a debug message if the default logger is available. +func LogDebug(msg string, keyvals ...any) { log.Debug(msg, keyvals...) } -// LogOptions configures the log service. -type LogOptions = log.Options +// LogInfo logs an info message. +func LogInfo(msg string, keyvals ...any) { log.Info(msg, keyvals...) } -// NewLogService creates a log service factory with CLI styling. -func NewLogService(opts LogOptions) func(*core.Core) (any, error) { - return func(c *core.Core) (any, error) { - // Create the underlying service - factory := log.NewService(opts) - svc, err := factory(c) - if err != nil { - return nil, err - } +// LogWarn logs a warning message. +func LogWarn(msg string, keyvals ...any) { log.Warn(msg, keyvals...) } - logSvc := svc.(*log.Service) - - // Apply CLI styles - logSvc.StyleTimestamp = func(s string) string { return DimStyle.Render(s) } - logSvc.StyleDebug = func(s string) string { return DimStyle.Render(s) } - logSvc.StyleInfo = func(s string) string { return InfoStyle.Render(s) } - logSvc.StyleWarn = func(s string) string { return WarningStyle.Render(s) } - logSvc.StyleError = func(s string) string { return ErrorStyle.Render(s) } - logSvc.StyleSecurity = func(s string) string { return SecurityStyle.Render(s) } - - return &LogService{Service: logSvc}, nil - } -} - -// --- Package-level convenience --- - -// Log returns the CLI's log service, or nil if not available. -func Log() *LogService { - if instance == nil { - return nil - } - svc, err := core.ServiceFor[*LogService](instance.core, "log") - if err != nil { - return nil - } - return svc -} - -// LogDebug logs a debug message with optional key-value pairs if log service is available. -func LogDebug(msg string, keyvals ...any) { - if l := Log(); l != nil { - l.Debug(msg, keyvals...) - } -} - -// LogInfo logs an info message with optional key-value pairs if log service is available. -func LogInfo(msg string, keyvals ...any) { - if l := Log(); l != nil { - l.Info(msg, keyvals...) - } -} - -// LogWarn logs a warning message with optional key-value pairs if log service is available. -func LogWarn(msg string, keyvals ...any) { - if l := Log(); l != nil { - l.Warn(msg, keyvals...) - } -} - -// LogError logs an error message with optional key-value pairs if log service is available. -func LogError(msg string, keyvals ...any) { - if l := Log(); l != nil { - l.Error(msg, keyvals...) - } -} - -// LogSecurity logs a security message if log service is available. -func LogSecurity(msg string, keyvals ...any) { - if l := Log(); l != nil { - // Ensure user context is included if not already present - hasUser := false - for i := 0; i < len(keyvals); i += 2 { - if keyvals[i] == "user" { - hasUser = true - break - } - } - if !hasUser { - keyvals = append(keyvals, "user", log.Username()) - } - l.Security(msg, keyvals...) - } -} +// LogError logs an error message. +func LogError(msg string, keyvals ...any) { log.Error(msg, keyvals...) } diff --git a/pkg/cli/runtime.go b/pkg/cli/runtime.go index e5a3a471..17cb6f03 100644 --- a/pkg/cli/runtime.go +++ b/pkg/cli/runtime.go @@ -20,7 +20,7 @@ import ( "sync" "syscall" - "forge.lthn.ai/core/go/pkg/core" + "dappco.re/go/core" "github.com/spf13/cobra" ) @@ -39,9 +39,10 @@ type runtime struct { // Options configures the CLI runtime. type Options struct { - AppName string - Version string - Services []core.Option // Additional services to register + AppName string + Version string + Services []core.Service // Additional services to register + I18nSources []LocaleSource // Additional i18n translation sources // OnReload is called when SIGHUP is received (daemon mode). // Use for configuration reloading. Leave nil to ignore SIGHUP. @@ -63,28 +64,35 @@ func Init(opts Options) error { SilenceUsage: true, } - // Attach all registered commands - attachRegisteredCommands(rootCmd) + // Create Core with app identity + c := core.New(core.Options{ + {Key: "name", Value: opts.AppName}, + }) + c.App().Version = opts.Version + c.App().Runtime = rootCmd - // Build signal service options - var signalOpts []SignalOption - if opts.OnReload != nil { - signalOpts = append(signalOpts, WithReloadHandler(opts.OnReload)) + // Register signal service + signalSvc := &signalService{ + cancel: cancel, + sigChan: make(chan os.Signal, 1), } - - // Build options: app, signal service + any additional services - coreOpts := []core.Option{ - core.WithApp(rootCmd), - core.WithName("signal", newSignalService(cancel, signalOpts...)), + if opts.OnReload != nil { + signalSvc.onReload = opts.OnReload } - coreOpts = append(coreOpts, opts.Services...) - coreOpts = append(coreOpts, core.WithServiceLock()) - - c, err := core.New(coreOpts...) - if err != nil { - initErr = err - cancel() - return + c.Service("signal", core.Service{ + OnStart: func() core.Result { + return signalSvc.start(ctx) + }, + OnStop: func() core.Result { + return signalSvc.stop() + }, + }) + + // Register additional services + for _, svc := range opts.Services { + if svc.Name != "" { + c.Service(svc.Name, svc) + } } instance = &runtime{ @@ -94,10 +102,16 @@ func Init(opts Options) error { cancel: cancel, } - if err := c.ServiceStartup(ctx, nil); err != nil { - initErr = err + r := c.ServiceStartup(ctx, nil) + if !r.OK { + if err, ok := r.Value.(error); ok { + initErr = err + } return } + + // Attach registered commands AFTER Core startup so i18n is available + attachRegisteredCommands(rootCmd) }) return initErr } @@ -145,7 +159,7 @@ func Shutdown() { _ = instance.core.ServiceShutdown(instance.ctx) } -// --- Signal Service (internal) --- +// --- Signal Srv (internal) --- type signalService struct { cancel context.CancelFunc @@ -154,30 +168,7 @@ type signalService struct { shutdownOnce sync.Once } -// SignalOption configures signal handling. -type SignalOption func(*signalService) - -// WithReloadHandler sets a callback for SIGHUP. -func WithReloadHandler(fn func() error) SignalOption { - return func(s *signalService) { - s.onReload = fn - } -} - -func newSignalService(cancel context.CancelFunc, opts ...SignalOption) func(*core.Core) (any, error) { - return func(c *core.Core) (any, error) { - svc := &signalService{ - cancel: cancel, - sigChan: make(chan os.Signal, 1), - } - for _, opt := range opts { - opt(svc) - } - return svc, nil - } -} - -func (s *signalService) OnStartup(ctx context.Context) error { +func (s *signalService) start(ctx context.Context) core.Result { signals := []os.Signal{syscall.SIGINT, syscall.SIGTERM} if s.onReload != nil { signals = append(signals, syscall.SIGHUP) @@ -207,13 +198,13 @@ func (s *signalService) OnStartup(ctx context.Context) error { } }() - return nil + return core.Result{OK: true} } -func (s *signalService) OnShutdown(ctx context.Context) error { +func (s *signalService) stop() core.Result { s.shutdownOnce.Do(func() { signal.Stop(s.sigChan) close(s.sigChan) }) - return nil + return core.Result{OK: true} } diff --git a/pkg/cli/utils.go b/pkg/cli/utils.go index 4b41a8c1..8a33b27f 100644 --- a/pkg/cli/utils.go +++ b/pkg/cli/utils.go @@ -22,9 +22,9 @@ func GhAuthenticated() bool { authenticated := strings.Contains(string(output), "Logged in") if authenticated { - LogSecurity("GitHub CLI authenticated", "user", log.Username()) + LogWarn("GitHub CLI authenticated", "user", log.Username()) } else { - LogSecurity("GitHub CLI not authenticated", "user", log.Username()) + LogWarn("GitHub CLI not authenticated", "user", log.Username()) } return authenticated