refactor: split library from binary, remove ecosystem commands
core/cli is now a pure library (pkg/cli). The binary moves to cmd/core/ as a separate sub-module with its own go.mod. Removed from binary: gocmd (→ lint/go-build), service (→ go-process), session (→ go-session), module (→ go-scm), plugin (→ go-scm). Removed from framework: go-crypt, workspace, daemon_cmd. Root go.mod: 1 direct forge dep (core/go). Cross-compiles CGO_ENABLED=0. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
7e2c7cd2f6
commit
55b556d1af
47 changed files with 216 additions and 4317 deletions
63
cmd/core/go.mod
Normal file
63
cmd/core/go.mod
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
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-cache v0.1.2
|
||||
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
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
forge.lthn.ai/core/go v0.3.1 // indirect
|
||||
forge.lthn.ai/core/go-crypt v0.1.7 // 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
|
||||
github.com/ProtonMail/go-crypto v1.4.0 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // 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/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.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/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // 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/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yuin/goldmark v1.7.16 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.49.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
|
||||
)
|
||||
128
cmd/core/go.sum
Normal file
128
cmd/core/go.sum
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
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-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-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-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=
|
||||
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/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/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/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/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
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/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/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/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/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/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/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/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/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/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
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.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/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.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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
18
cmd/core/main.go
Normal file
18
cmd/core/main.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
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"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cli.Main(
|
||||
cli.WithCommands("config", config.AddConfigCommands),
|
||||
cli.WithCommands("doctor", doctor.AddDoctorCommands),
|
||||
cli.WithCommands("help", help.AddHelpCommands),
|
||||
cli.WithCommands("pkg", pkgcmd.AddPkgCommands),
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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 <TestName> -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/<Target>/."
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -1,56 +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"
|
||||
coreio "forge.lthn.ai/core/go-io"
|
||||
"forge.lthn.ai/core/go-io/store"
|
||||
"forge.lthn.ai/core/go-scm/marketplace"
|
||||
)
|
||||
|
||||
// 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(coreio.Local, modulesDir, st)
|
||||
return modulesDir, st, inst, nil
|
||||
}
|
||||
|
|
@ -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 <code>",
|
||||
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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 <code>",
|
||||
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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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 <name>",
|
||||
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
|
||||
}
|
||||
|
|
@ -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 <source>",
|
||||
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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 <name>",
|
||||
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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 <name>)")
|
||||
}
|
||||
return name, spec, nil
|
||||
}
|
||||
|
||||
func daemonNames(m *manifest.Manifest) []string {
|
||||
var names []string
|
||||
for name := range m.Daemons {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
|
@ -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 <session-id>",
|
||||
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 <query>",
|
||||
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] + "..."
|
||||
}
|
||||
159
go.mod
159
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 forge.lthn.ai/core/go v0.3.0
|
||||
|
||||
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
|
||||
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.41.0
|
||||
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/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.88 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // 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/agnivade/levenshtein v1.2.1 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
forge.lthn.ai/core/go-io v0.1.0 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 // 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.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.134.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.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.5 // indirect
|
||||
github.com/go-openapi/spec v0.22.4 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/loading v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.5 // 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.6 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/gofrs/flock v0.13.0 // 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.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/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/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.12.1 // indirect
|
||||
github.com/oasdiff/yaml v0.0.0-20260313112342-a3ea61cb4d4c // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.0-20260224194419-61cd415a242b // 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.4 // indirect
|
||||
github.com/sosodev/duration v1.4.0 // 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.67.0 // indirect
|
||||
go.opentelemetry.io/otel v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.42.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/arch v0.25.0 // indirect
|
||||
golang.org/x/crypto v0.49.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect
|
||||
golang.org/x/mod v0.34.0 // indirect
|
||||
golang.org/x/net v0.52.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
golang.org/x/tools v0.43.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // 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
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
750
go.sum
750
go.sum
|
|
@ -1,189 +1,15 @@
|
|||
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
||||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/auth v0.16.4 h1:fXOAIQmkApVvcIn7Pc2+5J8QTMVbUGLscnSVNl11su8=
|
||||
cloud.google.com/go/auth v0.16.4/go.mod h1:j10ncYwjX/g3cdX7GpEzsdM+d+ZNsXAbb6qXA7p1Y5M=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
|
||||
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
|
||||
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
|
||||
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
|
||||
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
|
||||
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
|
||||
cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI=
|
||||
cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU=
|
||||
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=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
forge.lthn.ai/Snider/Borg v0.3.1 h1:gfC1ZTpLoZai07oOWJiVeQ8+qJYK8A795tgVGJHbVL8=
|
||||
forge.lthn.ai/Snider/Borg v0.3.1/go.mod h1:Z7DJD0yHXsxSyM7Mjl6/g4gH1NBsIz44Bf5AFlV76Wg=
|
||||
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-ai v0.1.0 h1:Z7Gbxsq4d8vnO35zZxJXvk8wEUGSazpxncN1voJkNT0=
|
||||
forge.lthn.ai/core/go-ai v0.1.0/go.mod h1:YUsCZmWereE2wZPDEmk46Xu+xxmfsNZktpY7H9jgNJI=
|
||||
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-config v0.1.0 h1:bQnlt8MvFvgPisl//jw4IMHMoCcaIt5FLurwYWqlMx0=
|
||||
forge.lthn.ai/core/go-config v0.1.0/go.mod h1:jsCzg3BykHqlHZs13PDhP/dq8yTZjsiEyZ35q6jA3Aw=
|
||||
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-ml v0.0.3 h1:wdjat/9v99ydAbtDlNIoEPAmGaRNqSofyQCMGDd87z4=
|
||||
forge.lthn.ai/core/go-ml v0.0.3/go.mod h1:8isfojBGXjMr6Co0GkTcxisj5rq0E4ftYzxSxRISFGc=
|
||||
forge.lthn.ai/core/go-mlx v0.0.2 h1:pimttr/O6y182nK6iuUIODoW+Rn9HHaf3aB4zEams9M=
|
||||
forge.lthn.ai/core/go-mlx v0.0.2/go.mod h1:0gvpTa77tSgKQ9SbzSTE5fRnDWrBQkCG0JPsj8xl9pg=
|
||||
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-rag v0.1.0 h1:H5umiRryuq6J6l889s0OsxWpmq5P5c3A9Bkj0cQyO7k=
|
||||
forge.lthn.ai/core/go-rag v0.1.0/go.mod h1:bB8Fy98G2zxVoe7k2B85gXvim6frJdbAMnDyW4peUVU=
|
||||
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/go-ws v0.1.0 h1:P3lH2BM7UyIJAX5R2iVszEZ3M5B6oXGdEWGtuAW054M=
|
||||
forge.lthn.ai/core/go-ws v0.1.0/go.mod h1:wBQLXDUod6FqESh1CM4OnAjyP3cmWg8Vd5M43RIdTwA=
|
||||
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.88 h1:neMQDgehMwT1vYIOx/w5ZYPUU/iMNAJzRO44I5Intoc=
|
||||
github.com/99designs/gqlgen v0.17.88/go.mod h1:qeqYFEgOeSKqWedOjogPizimp2iu4E23bdPvl4jTYic=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.4.0 h1:Zq/pbM3F5DFgJiMouxEdSVY44MVoQNEKp5d5QxIQceQ=
|
||||
github.com/ProtonMail/go-crypto v1.4.0/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
||||
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/Snider/Borg v0.2.0 h1:iCyDhY4WTXi39+FexRwXbn2YpZ2U9FUXVXDZk9xRCXQ=
|
||||
github.com/Snider/Borg v0.2.0/go.mod h1:TqlKnfRo9okioHbgrZPfWjQsztBV0Nfskz4Om1/vdMY=
|
||||
github.com/Snider/Enchantrix v0.0.2 h1:ExZQiBhfS/p/AHFTKhY80TOd+BXZjK95EzByAEgwvjs=
|
||||
github.com/Snider/Enchantrix v0.0.2/go.mod h1:CtFcLAvnDT1KcuF1JBb/DJj0KplY8jHryO06KzQ1hsQ=
|
||||
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/antonlindstrom/pgstore v0.0.0-20220421113606-e3a6e3fed12a h1:dIdcLbck6W67B5JFMewU5Dba1yKZA3MsT67i4No/zh0=
|
||||
github.com/antonlindstrom/pgstore v0.0.0-20220421113606-e3a6e3fed12a/go.mod h1:Sdr/tmSOLEnncCuXS5TwZRxuk7deH1WXVY8cve3eVBM=
|
||||
github.com/apache/arrow-go/v18 v18.5.1 h1:yaQ6zxMGgf9YCYw4/oaeOU3AULySDlAYDOcnr4LdHdI=
|
||||
github.com/apache/arrow-go/v18 v18.5.1/go.mod h1:OCCJsmdq8AsRm8FkBSSmYTwL/s4zHW9CqxeBxEytkNE=
|
||||
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=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.3/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.6 h1:N4lRUXZpZ1KVEUn6hxtco/1d2lgYhNn1fHkkl8WhlyQ=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.6/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 h1:/sECfyq2JTifMI2JPyZ4bdRN77zJmr6SrS1eL3augIA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19/go.mod h1:dMf8A5oAqr9/oxOfLkC/c2LU/uMcALP0Rgn2BD5LWn0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 h1:AWeJMk33GTBf6J20XJe6qZoRSJo0WfUhsMdUKhoODXE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19/go.mod h1:+GWrYoaAsV7/4pNHpwh1kiNLXkKaSoppxQq9lbH8Ejw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20 h1:qi3e/dmpdONhj1RyIZdi6DKKpDXS5Lb8ftr3p7cyHJc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20/go.mod h1:V1K+TeJVD5JOk3D9e5tsX2KUdL7BlB+FV6cBhdobN8c=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 h1:XAq62tBTJP/85lFD5oqOOe7YYgWxY9LvWq8plyDvDVg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 h1:BYf7XNsJMzl4mObARUBUib+j2tf0U//JAAtTnYqvqCw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11/go.mod h1:aEUS4WrNk/+FxkBZZa7tVgp4pGH+kFGW40Y8rCPqt5g=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 h1:X1Tow7suZk9UCJHE1Iw9GMZJJl0dAnKXXP1NaSDHwmw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19/go.mod h1:/rARO8psX+4sfjUQXp5LLifjUt8DuATZ31WptNJTyQA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19 h1:JnQeStZvPHFHeyky/7LbMlyQjUa+jIBj36OlWm0pzIk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19/go.mod h1:HGyasyHvYdFQeJhvDHfH7HXkHh57htcJGKDZ+7z+I24=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 h1:4ExZyubQ6LQQVuF2Qp9OsfEvsTdAWh5Gfwf6PgIdLdk=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4/go.mod h1:NF3JcMGOiARAss1ld3WGORCw71+4ExDD2cbbdKS5PpA=
|
||||
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
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/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
|
||||
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
|
||||
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/boj/redistore v1.4.1 h1:lP9ZZWqKMq2RIqexlZX1w1ODSnegL+puxGIujkU5tIw=
|
||||
github.com/boj/redistore v1.4.1/go.mod h1:c0Tvw6aMjslog4jHIAcNv6EtJM849YoOAhMY7JBbWpI=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
|
||||
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20240916143655-c0e34fd2f304 h1:f/AUyZ4PoqHhBJnhMrrNtSNYH5RvLxr5UQ0qrOZ9jkE=
|
||||
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20240916143655-c0e34fd2f304/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
||||
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/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
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.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
||||
|
|
@ -194,642 +20,66 @@ github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF
|
|||
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/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
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/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
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/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
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/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
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 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/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/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||
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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
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.134.0 h1:/L5+1+kfe6dXh8Ot/wqiTgUkjOIEJiC0bbYVziHB8rU=
|
||||
github.com/getkin/kin-openapi v0.134.0/go.mod h1:wK6ZLG/VgoETO9pcLJ/VmAtIcl/DNlMayNTb716EUxE=
|
||||
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/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
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-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8=
|
||||
github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
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-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/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/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=
|
||||
github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=
|
||||
github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ=
|
||||
github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
|
||||
github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
|
||||
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/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo=
|
||||
github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
|
||||
github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
|
||||
github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
|
||||
github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
|
||||
github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
|
||||
github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=
|
||||
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-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.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||
github.com/goccy/go-json v0.10.6/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/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
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/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s=
|
||||
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
||||
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
|
||||
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github/v39 v39.2.0 h1:rNNM311XtPOz5rDdsJXAp2o8F67X9FnROXTvto3aSnQ=
|
||||
github.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
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/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
|
||||
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
|
||||
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
|
||||
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/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
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/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
|
||||
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/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
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/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
|
||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jordanlewis/gcassert v0.0.0-20250430164644-389ef753e22e h1:a+PGEeXb+exwBS3NboqXHyxarD9kaboBbrSp+7GuBuc=
|
||||
github.com/jordanlewis/gcassert v0.0.0-20250430164644-389ef753e22e/go.mod h1:ZybsQk6DWyN5t7An1MuPm1gtSZ1xDaTXS9ZjIOxvQrk=
|
||||
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/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b h1:TLCm7HR+P9HM2NXaAJaIiHerOUMedtFJeAfaYwZ8YhY=
|
||||
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
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/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
|
||||
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/laziness-coders/mongostore v0.0.14 h1:4RrtOeTsGr3pBbImtpCZT7L4LB/kXfAzpCPXds69RgA=
|
||||
github.com/laziness-coders/mongostore v0.0.14/go.mod h1:Rh+yJax2Vxc2QY62clIM/kRnLk+TxivgSLHOXENXPtk=
|
||||
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/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||
github.com/leaanthony/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/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
||||
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||
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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA=
|
||||
github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ=
|
||||
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/marcboeker/go-duckdb v1.8.5 h1:tkYp+TANippy0DaIOP5OEfBEwbUINqiFqgwMQ44jME0=
|
||||
github.com/marcboeker/go-duckdb v1.8.5/go.mod h1:6mK7+WQE4P4u5AFLvVBmhFxY5fvhymFptghgJX6B+/8=
|
||||
github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I=
|
||||
github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
|
||||
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/matryer/moq v0.6.0 h1:FCccG09c3o4cg3gnrZ+7ty5Pa/sjmN24BMHp/0pwhjQ=
|
||||
github.com/matryer/moq v0.6.0/go.mod h1:iEVhY/XBwFG/nbRyEf0oV+SqnTHZJ5wectzx7yT+y98=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/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/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/memcachier/mc v2.0.1+incompatible h1:s8EDz0xrJLP8goitwZOoq1vA/sm0fPS4X3KAF0nyhWQ=
|
||||
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||
github.com/memcachier/mc/v3 v3.0.3 h1:qii+lDiPKi36O4Xg+HVKwHu6Oq+Gt17b+uEiA0Drwv4=
|
||||
github.com/memcachier/mc/v3 v3.0.3/go.mod h1:GzjocBahcXPxt2cmqzknrgqCOmMxiSzhVKPOe90Tpug=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/modelcontextprotocol/go-sdk v1.3.0 h1:gMfZkv3DzQF5q/DcQePo5rahEY+sguyPfXDfNBcT0Zs=
|
||||
github.com/modelcontextprotocol/go-sdk v1.3.0/go.mod h1:AnQ//Qc6+4nIyyrB4cxBU7UW9VibK4iOZBeyP/rF1IE=
|
||||
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/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
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.12.1 h1:wnvBQS/WSqGqH23u1Jo3XVaF5y5X67TC5znSiy5nIug=
|
||||
github.com/oasdiff/oasdiff v1.12.1/go.mod h1:4l8lF8SkdyiBVpa7AH3xc+oyDDXS1QTegX25mBS11/E=
|
||||
github.com/oasdiff/yaml v0.0.0-20260313112342-a3ea61cb4d4c h1:7ACFcSaQsrWtrH4WHHfUqE1C+f8r2uv8KGaW0jTNjus=
|
||||
github.com/oasdiff/yaml v0.0.0-20260313112342-a3ea61cb4d4c/go.mod h1:JKox4Gszkxt57kj27u7rvi7IFoIULvCZHUsBTUmQM/s=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20260224194419-61cd415a242b h1:vivRhVUAa9t1q0Db4ZmezBP8pWQWnXHFokZj0AOea2g=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20260224194419-61cd415a242b/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
github.com/ollama/ollama v0.16.1 h1:DIxnLdS0om3hb7HheJqj6+ZnPCCMWmy/vyUxiQgRYoI=
|
||||
github.com/ollama/ollama v0.16.1/go.mod h1:FEk95NbAJJZk+t7cLh+bPGTul72j1O3PLLlYNV3FVZ0=
|
||||
github.com/parquet-go/bitpack v1.0.0 h1:AUqzlKzPPXf2bCdjfj4sTeacrUwsT7NlcYDMUQxPcQA=
|
||||
github.com/parquet-go/bitpack v1.0.0/go.mod h1:XnVk9TH+O40eOOmvpAVZ7K2ocQFrQwysLMnc6M/8lgs=
|
||||
github.com/parquet-go/jsonlite v1.4.0 h1:RTG7prqfO0HD5egejU8MUDBN8oToMj55cgSV1I0zNW4=
|
||||
github.com/parquet-go/jsonlite v1.4.0/go.mod h1:nDjpkpL4EOtqs6NQugUsi0Rleq9sW/OtC1NnZEnxzF0=
|
||||
github.com/parquet-go/parquet-go v0.27.0 h1:vHWK2xaHbj+v1DYps03yDRpEsdtOeKbhiXUaixoPb3g=
|
||||
github.com/parquet-go/parquet-go v0.27.0/go.mod h1:navtkAYr2LGoJVp141oXPlO/sxLvaOe3la2JEoD8+rg=
|
||||
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/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=
|
||||
github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
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/qdrant/go-client v1.16.2 h1:UUMJJfvXTByhwhH1DwWdbkhZ2cTdvSqVkXSIfBrVWSg=
|
||||
github.com/qdrant/go-client v1.16.2/go.mod h1:I+EL3h4HRoRTeHtbfOd/4kDXwCukZfkd41j/9wryGkw=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||
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 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
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/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
|
||||
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
|
||||
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/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/sosodev/duration v1.4.0 h1:35ed0KiVFriGHHzZZJaZLgmTEEICIyt8Sx0RQfj9IjE=
|
||||
github.com/sosodev/duration v1.4.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
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/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
|
||||
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 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
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.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/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
|
||||
github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
|
||||
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/twpayne/go-geom v1.6.1 h1:iLE+Opv0Ihm/ABIcvQFGIiFBXd76oBIar9drAwHFhR4=
|
||||
github.com/twpayne/go-geom v1.6.1/go.mod h1:Kr+Nly6BswFsKM5sd31YaoWS5PeDDH2NftJTK7Gd028=
|
||||
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/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/urfave/cli/v3 v3.7.0 h1:AGSnbUyjtLiM+WJUb4dzXKldl/gL+F8OwmRDtVr6g2U=
|
||||
github.com/urfave/cli/v3 v3.7.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
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/wader/gormstore/v2 v2.0.3 h1:/29GWPauY8xZkpLnB8hsp+dZfP3ivA9fiDw1YVNTp6U=
|
||||
github.com/wader/gormstore/v2 v2.0.3/go.mod h1:sr3N3a8F1+PBc3fHoKaphFqDXLRJ9Oe6Yow0HxKFbbg=
|
||||
github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58=
|
||||
github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
github.com/wailsapp/wails/v2 v2.11.0 h1:seLacV8pqupq32IjS4Y7V8ucab0WZwtK6VvUVxSBtqQ=
|
||||
github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc=
|
||||
github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
|
||||
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
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/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
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/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
|
||||
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||
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 v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
|
||||
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
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/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
|
||||
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.67.0 h1:E7DmskpIO7ZR6QI6zKSEKIDNUYoKw9oHXP23gzbCdU0=
|
||||
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.67.0/go.mod h1:WB2cS9y+AwqqKhoo9gw6/ZxlSjFBUQGZ8BQOaD3FVXM=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.42.0 h1:B2Pew5ufEtgkjLF+tSkXjgYZXQr9m7aCm1wLKB0URbU=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.42.0/go.mod h1:iPgUcSEF5DORW6+yNbdw/YevUy+QqJ508ncjhrRSCjc=
|
||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
||||
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
|
||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
|
||||
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
|
||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
||||
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.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=
|
||||
golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||
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.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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
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.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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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-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.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c h1:6a8FdnNk6bTXBjR4AGKFgUKuo+7GnR3FX5L7CbveeZc=
|
||||
golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw=
|
||||
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.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.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.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
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.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
|
||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc=
|
||||
google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 h1:mVXdvnmR3S3BQOqHECm9NGMjYiRtEvDYcqAqedTXY6s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:vYFwMYFbmA8vl6Z/krj/h7+U/AqpHknwJX4Uqgfyc7I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
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/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
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=
|
||||
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
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=
|
||||
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
|
|
|
|||
28
main.go
28
main.go
|
|
@ -1,28 +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"
|
||||
)
|
||||
|
||||
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),
|
||||
)
|
||||
}
|
||||
|
|
@ -5,10 +5,8 @@ import (
|
|||
"os"
|
||||
"runtime/debug"
|
||||
|
||||
"forge.lthn.ai/core/go-crypt/crypt/openpgp"
|
||||
"forge.lthn.ai/core/go/pkg/core"
|
||||
"forge.lthn.ai/core/go-log"
|
||||
"forge.lthn.ai/core/go-io/workspace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -82,8 +80,6 @@ func Main(commands ...core.Option) {
|
|||
core.WithName("log", NewLogService(log.Options{
|
||||
Level: log.LevelInfo,
|
||||
})),
|
||||
core.WithName("crypt", openpgp.New),
|
||||
core.WithName("workspace", workspace.New),
|
||||
}
|
||||
services = append(services, commands...)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue