Compare commits
2 Commits
b2eb9d3116
...
92b9c356b8
Author | SHA1 | Date | |
---|---|---|---|
92b9c356b8 | |||
c86165f88c |
11
go.mod
11
go.mod
@ -1,4 +1,4 @@
|
||||
module github.com/freeflowuniverse/heroagent
|
||||
module git.ourworld.tf/herocode/heroagent
|
||||
|
||||
go 1.23.0
|
||||
|
||||
@ -10,12 +10,10 @@ require (
|
||||
github.com/emersion/go-smtp v0.21.3
|
||||
github.com/emersion/go-webdav v0.6.0
|
||||
github.com/gofiber/fiber/v2 v2.52.6
|
||||
github.com/gofiber/swagger v1.1.1
|
||||
github.com/gofiber/template/pug/v2 v2.1.8
|
||||
github.com/knusbaum/go9p v1.18.0
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tidwall/redcon v1.6.2
|
||||
github.com/yuin/goldmark v1.7.8
|
||||
golang.org/x/crypto v0.36.0
|
||||
@ -37,7 +35,6 @@ require (
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
@ -49,7 +46,6 @@ require (
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/gofiber/template v1.8.3 // indirect
|
||||
github.com/gofiber/template/jet/v2 v2.1.11 // indirect
|
||||
github.com/gofiber/utils v1.1.0 // indirect
|
||||
@ -69,9 +65,8 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||
github.com/metoro-io/mcp-golang v0.8.0 // indirect
|
||||
github.com/mholt/archiver/v3 v3.5.1 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/openaiproxy/openaiproxy-go v0.1.0-beta.9 // indirect
|
||||
github.com/openai/openai-go v0.1.0-beta.9 // indirect
|
||||
github.com/pb33f/libopenapi v0.21.8 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
@ -116,3 +111,5 @@ require (
|
||||
modernc.org/sqlite v1.23.1 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/openaiproxy/openaiproxy-go => github.com/sashabaranov/go-openai v1.20.0
|
||||
|
351
go.sum
351
go.sum
@ -1,351 +0,0 @@
|
||||
9fans.net/go v0.0.2 h1:RYM6lWITV8oADrwLfdzxmt8ucfW6UtP9v1jg4qAbqts=
|
||||
9fans.net/go v0.0.2/go.mod h1:lfPdxjq9v8pVQXUMBCx5EO5oLXWQFlKRQgs1kEkjoIM=
|
||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
|
||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
||||
github.com/CloudyKit/jet/v6 v6.3.1 h1:6IAo5Cx21xrHVaR8zzXN5gJatKV/wO7Nf6bfCnCSbUw=
|
||||
github.com/CloudyKit/jet/v6 v6.3.1/go.mod h1:lf8ksdNsxZt7/yH/3n4vJQWA9RUq4wpaHtArHhGVMOw=
|
||||
github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc=
|
||||
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
|
||||
github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk=
|
||||
github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Plan9-Archive/libauth v0.0.0-20180917063427-d1ca9e94969d/go.mod h1:UKp8dv9aeaZoQFWin7eQXtz89iHly1YAFZNn3MCutmQ=
|
||||
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/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9N28=
|
||||
github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo=
|
||||
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/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
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/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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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/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/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
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/emersion/go-ical v0.0.0-20240127095438-fc1c9d8fb2b6/go.mod h1:BEksegNspIkjCQfmzWgsgbu6KdeJ/4LwUZs7DMBzjzw=
|
||||
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
|
||||
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
||||
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
||||
github.com/emersion/go-message v0.18.2 h1:rl55SQdjd9oJcIoQNhubD2Acs1E6IzlZISRTK7x/Lpg=
|
||||
github.com/emersion/go-message v0.18.2/go.mod h1:XpJyL70LwRvq2a8rVbHXikPgKj8+aI0kGdHlg16ibYA=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y=
|
||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.21.3 h1:7uVwagE8iPYE48WhNsng3RRpCUpFvNl39JGNSIyGVMY=
|
||||
github.com/emersion/go-smtp v0.21.3/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||
github.com/emersion/go-webdav v0.6.0 h1:rbnBUEXvUM2Zk65Him13LwJOBY0ISltgqM5k6T5Lq4w=
|
||||
github.com/emersion/go-webdav v0.6.0/go.mod h1:mI8iBx3RAODwX7PJJ7qzsKAKs/vY429YfS2/9wKnDbQ=
|
||||
github.com/fhs/mux9p v0.3.1 h1:x1UswUWZoA9vrA02jfisndCq3xQm+wrQUxUt5N99E08=
|
||||
github.com/fhs/mux9p v0.3.1/go.mod h1:F4hwdenmit0WDoNVT2VMWlLJrBVCp/8UhzJa7scfjEQ=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
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.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
|
||||
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
||||
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/gofiber/swagger v1.1.1 h1:FZVhVQQ9s1ZKLHL/O0loLh49bYB5l1HEAgxDlcTtkRA=
|
||||
github.com/gofiber/swagger v1.1.1/go.mod h1:vtvY/sQAMc/lGTUCg0lqmBL7Ht9O7uzChpbvJeJQINw=
|
||||
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
|
||||
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
|
||||
github.com/gofiber/template/jet/v2 v2.1.11 h1:irnR6GeM2SGTdvg7dxFts564a5evApMUKpOn3mt/RNE=
|
||||
github.com/gofiber/template/jet/v2 v2.1.11/go.mod h1:Kb1oBdrx90oEvP71MDTUB9k+IWRF082Td5OPW7SoUMQ=
|
||||
github.com/gofiber/template/pug/v2 v2.1.8 h1:SNs0wE96S5P5Ggb54jNOtlP5Qads63gR31PvBBEgNns=
|
||||
github.com/gofiber/template/pug/v2 v2.1.8/go.mod h1:e0Sg0YBMtC+RQMRm0swaAvqIBDJmhhDIKfFFtQRjvlQ=
|
||||
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
|
||||
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
|
||||
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
|
||||
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
|
||||
github.com/hanwen/go-fuse/v2 v2.0.3/go.mod h1:0EQM6aH2ctVpvZ6a+onrQ/vaykxh2GH7hy3e13vzTUY=
|
||||
github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
|
||||
github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
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/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/knusbaum/go9p v1.18.0 h1:/Y67RNvNKX1ZV1IOdnO1lIetiF0X+CumOyvEc0011GI=
|
||||
github.com/knusbaum/go9p v1.18.0/go.mod h1:HtMoJKqZUe1Oqag5uJqG5RKQ9gWPSP+wolsnLLv44r8=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
|
||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
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-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/metoro-io/mcp-golang v0.8.0 h1:DkigHa3w7WwMFomcEz5wiMDX94DsvVm/3mCV3d1obnc=
|
||||
github.com/metoro-io/mcp-golang v0.8.0/go.mod h1:ifLP9ZzKpN1UqFWNTpAHOqSvNkMK6b7d1FSZ5Lu0lN0=
|
||||
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
||||
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
|
||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/openaiproxy/openaiproxy-go v0.1.0-beta.9 h1:ABpubc5yU/3ejee2GgRrbFta81SG/d7bQbB8mIdP0Xo=
|
||||
github.com/openaiproxy/openaiproxy-go v0.1.0-beta.9/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
|
||||
github.com/pb33f/libopenapi v0.21.8 h1:Fi2dAogMwC6av/5n3YIo7aMOGBZH/fBMO4OnzFB3dQA=
|
||||
github.com/pb33f/libopenapi v0.21.8/go.mod h1:Gc8oQkjr2InxwumK0zOBtKN9gIlv9L2VmSVIUk2YxcU=
|
||||
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
|
||||
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc=
|
||||
github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
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.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/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/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
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/speakeasy-api/jsonpath v0.6.1 h1:FWbuCEPGaJTVB60NZg2orcYHGZlelbNJAcIk/JGnZvo=
|
||||
github.com/speakeasy-api/jsonpath v0.6.1/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU=
|
||||
github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0=
|
||||
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
||||
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
||||
github.com/teambition/rrule-go v1.8.2/go.mod h1:Ieq5AbrKGciP1V//Wq8ktsTXwSwJHDD5mD/wLBGl3p4=
|
||||
github.com/tidwall/btree v1.1.0 h1:5P+9WU8ui5uhmcg3SoPyTwoI0mVyZ1nps7YQzTZFkYM=
|
||||
github.com/tidwall/btree v1.1.0/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4=
|
||||
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 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/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/redcon v1.6.2 h1:5qfvrrybgtO85jnhSravmkZyC0D+7WstbfCs3MmPhow=
|
||||
github.com/tidwall/redcon v1.6.2/go.mod h1:p5Wbsgeyi2VSTBWOcA5vRXrOb9arFTcU2+ZzFjqV75Y=
|
||||
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/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
|
||||
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
|
||||
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
|
||||
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
|
||||
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
|
||||
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||
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/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd h1:dLuIF2kX9c+KknGJUdJi1Il1SDiTSK158/BB9kdgAew=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd/go.mod h1:DbzwytT4g/odXquuOCqroKvtxxldI4nb3nuesHF/Exo=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/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-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201020230747-6e5568b54d1a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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/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.6/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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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.0-20200615113413-eeeca48fe776/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/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
@ -11,7 +11,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/mycelium_client"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/mycelium_client"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/mycelium_client"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/mycelium_client"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -14,7 +14,7 @@ Dedupestor is a Go package that provides a key-value store with deduplication ba
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/freeflowuniverse/heroagent/pkg/dedupestor"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/dedupestor"
|
||||
)
|
||||
|
||||
// Create a new dedupe store
|
||||
|
@ -7,8 +7,8 @@ import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/data/ourdb"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/data/radixtree"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/data/ourdb"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/data/radixtree"
|
||||
)
|
||||
|
||||
// MaxValueSize is the maximum allowed size for values (1MB)
|
||||
|
@ -18,7 +18,7 @@ The DocTree package provides functionality for managing collections of markdown
|
||||
### Creating a DocTree
|
||||
|
||||
```go
|
||||
import "github.com/freeflowuniverse/heroagent/pkg/doctree"
|
||||
import "git.ourworld.tf/herocode/heroagent/pkg/doctree"
|
||||
|
||||
// Create a new DocTree with a path and name
|
||||
dt, err := doctree.New("/path/to/collection", "My Collection")
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/tools"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/tools"
|
||||
)
|
||||
|
||||
// Collection represents a collection of markdown pages and files
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/tools"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/tools"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/tools"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/tools"
|
||||
)
|
||||
|
||||
// Global variable to track the current DocTree instance
|
||||
|
@ -34,7 +34,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/ourdb"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/ourdb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -78,7 +78,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/ourdb"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/ourdb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -4,7 +4,7 @@ package radixtree
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/data/ourdb"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/data/ourdb"
|
||||
)
|
||||
|
||||
// Node represents a node in the radix tree
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/stats"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/stats"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
@ -77,7 +77,7 @@ func (h *AdminHandler) getProcessStatsJSON(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"success": false,
|
||||
"error": "Failed to get process stats: " + err.Error(),
|
||||
"error": "Failed to get process stats: " + err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ package api
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/sal/executor"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/sal/executor"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
@ -7,9 +7,9 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces/openrpc"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces/openrpc"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
@ -102,26 +102,26 @@ func (h *ServiceHandler) getProcessList() ([]ProcessDisplayInfo, error) {
|
||||
if !ok {
|
||||
// Try to handle the result as a map or other structure
|
||||
h.logger.Printf("Warning: unexpected result type from ListProcesses, trying alternative parsing")
|
||||
|
||||
|
||||
// Try to convert the result to JSON and then parse it
|
||||
resultJSON, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
h.logger.Printf("Error marshaling result to JSON: %v", err)
|
||||
return nil, fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
|
||||
|
||||
var processStatuses []interfaces.ProcessStatus
|
||||
if err := json.Unmarshal(resultJSON, &processStatuses); err != nil {
|
||||
h.logger.Printf("Error unmarshaling result to ProcessStatus: %v", err)
|
||||
return nil, fmt.Errorf("failed to unmarshal process list result: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Convert to display info format
|
||||
displayInfoList := make([]ProcessDisplayInfo, 0, len(processStatuses))
|
||||
for _, proc := range processStatuses {
|
||||
// Calculate uptime based on start time
|
||||
uptime := formatUptime(time.Since(proc.StartTime))
|
||||
|
||||
|
||||
displayInfo := ProcessDisplayInfo{
|
||||
ID: fmt.Sprintf("%d", proc.PID),
|
||||
Name: proc.Name,
|
||||
@ -133,7 +133,7 @@ func (h *ServiceHandler) getProcessList() ([]ProcessDisplayInfo, error) {
|
||||
}
|
||||
displayInfoList = append(displayInfoList, displayInfo)
|
||||
}
|
||||
|
||||
|
||||
// Debug: Log the number of processes
|
||||
h.logger.Printf("Found %d processes", len(displayInfoList))
|
||||
return displayInfoList, nil
|
||||
@ -144,7 +144,7 @@ func (h *ServiceHandler) getProcessList() ([]ProcessDisplayInfo, error) {
|
||||
for _, proc := range processStatuses {
|
||||
// Calculate uptime based on start time
|
||||
uptime := formatUptime(time.Since(proc.StartTime))
|
||||
|
||||
|
||||
displayInfo := ProcessDisplayInfo{
|
||||
ID: fmt.Sprintf("%d", proc.PID),
|
||||
Name: proc.Name,
|
||||
@ -276,7 +276,7 @@ func (h *ServiceHandler) stopService(c *fiber.Ctx) error {
|
||||
"error": fmt.Sprintf("Failed to stop service: %v", err),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Check if the result indicates success
|
||||
if !result.Success {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
@ -331,7 +331,7 @@ func (h *ServiceHandler) restartService(c *fiber.Ctx) error {
|
||||
"error": fmt.Sprintf("Failed to restart service: %v", err),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Check if the result indicates success
|
||||
if !result.Success {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
@ -382,7 +382,7 @@ func (h *ServiceHandler) deleteService(c *fiber.Ctx) error {
|
||||
"error": fmt.Sprintf("Failed to delete service: %v", err),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Check if the result indicates success
|
||||
if !result.Success {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
|
@ -12,16 +12,16 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroagent/api"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroagent/handlers"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroagent/pages"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/sal/executor"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/servers/redisserver"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/stats"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroagent/api"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroagent/handlers"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroagent/pages"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/sal/executor"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/servers/redisserver"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/stats"
|
||||
|
||||
// "github.com/freeflowuniverse/heroagent/pkg/vfs/interfaces"
|
||||
// "github.com/freeflowuniverse/heroagent/pkg/vfs/interfaces/mock"
|
||||
// "git.ourworld.tf/herocode/heroagent/pkg/vfs/interfaces"
|
||||
// "git.ourworld.tf/herocode/heroagent/pkg/vfs/interfaces/mock"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
@ -239,7 +239,7 @@ func (hl *HeroLauncher) GetUptime() string {
|
||||
func (hl *HeroLauncher) startProcessManager() error {
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
projectRoot := filepath.Join(filepath.Dir(filename), "../..")
|
||||
processManagerPath := filepath.Join(projectRoot, "cmd/processmanager/main.go")
|
||||
processManagerPath := filepath.Join(projectRoot, "pkg/processmanager/examples/openrpc/main.go")
|
||||
|
||||
log.Printf("Starting process manager from: %s", processManagerPath)
|
||||
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"log"
|
||||
"strconv" // Added strconv for JobID parsing
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/herojobs"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/herojobs"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/herojobs"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/herojobs"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/logger"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/logger"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
@ -65,40 +65,40 @@ func NewLogHandler(logPath string) (*LogHandler, error) {
|
||||
type LogType string
|
||||
|
||||
const (
|
||||
LogTypeSystem LogType = "system"
|
||||
LogTypeService LogType = "service"
|
||||
LogTypeJob LogType = "job"
|
||||
LogTypeProcess LogType = "process"
|
||||
LogTypeAll LogType = "all" // Special type to retrieve logs from all sources
|
||||
LogTypeSystem LogType = "system"
|
||||
LogTypeService LogType = "service"
|
||||
LogTypeJob LogType = "job"
|
||||
LogTypeProcess LogType = "process"
|
||||
LogTypeAll LogType = "all" // Special type to retrieve logs from all sources
|
||||
)
|
||||
|
||||
// GetLogs renders the logs page with logs content
|
||||
func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
|
||||
// Check which logger to use based on the log type parameter
|
||||
logTypeParam := c.Query("log_type", string(LogTypeSystem))
|
||||
|
||||
|
||||
// Parse query parameters
|
||||
category := c.Query("category", "")
|
||||
logItemType := parseLogType(c.Query("type", ""))
|
||||
maxItems := c.QueryInt("max_items", 100)
|
||||
page := c.QueryInt("page", 1)
|
||||
itemsPerPage := 20 // Default items per page
|
||||
|
||||
|
||||
// Parse time range
|
||||
fromTime := parseTimeParam(c.Query("from", ""))
|
||||
toTime := parseTimeParam(c.Query("to", ""))
|
||||
|
||||
|
||||
// Create search arguments
|
||||
searchArgs := logger.SearchArgs{
|
||||
Category: category,
|
||||
LogType: logItemType,
|
||||
MaxItems: maxItems,
|
||||
Category: category,
|
||||
LogType: logItemType,
|
||||
MaxItems: maxItems,
|
||||
}
|
||||
|
||||
|
||||
if !fromTime.IsZero() {
|
||||
searchArgs.TimestampFrom = &fromTime
|
||||
}
|
||||
|
||||
|
||||
if !toTime.IsZero() {
|
||||
searchArgs.TimestampTo = &toTime
|
||||
}
|
||||
@ -107,7 +107,7 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
|
||||
var logs []logger.LogItem
|
||||
var err error
|
||||
var logTypeTitle string
|
||||
|
||||
|
||||
// Check if we want to merge logs from all sources
|
||||
if LogType(logTypeParam) == LogTypeAll {
|
||||
// Get merged logs from all loggers
|
||||
@ -116,7 +116,7 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
|
||||
} else {
|
||||
// Select the appropriate logger based on the log type
|
||||
var selectedLogger *logger.Logger
|
||||
|
||||
|
||||
switch LogType(logTypeParam) {
|
||||
case LogTypeService:
|
||||
selectedLogger = h.serviceLogger
|
||||
@ -131,13 +131,13 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
|
||||
selectedLogger = h.systemLogger
|
||||
logTypeTitle = "System Logs"
|
||||
}
|
||||
|
||||
|
||||
// Check if the selected logger is properly initialized
|
||||
if selectedLogger == nil {
|
||||
return c.Render("admin/system/logs", fiber.Map{
|
||||
"title": logTypeTitle,
|
||||
"error": "Logger not initialized",
|
||||
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
|
||||
"title": logTypeTitle,
|
||||
"error": "Logger not initialized",
|
||||
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
|
||||
"selectedLogType": logTypeParam,
|
||||
})
|
||||
}
|
||||
@ -149,25 +149,24 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
|
||||
// Handle search error
|
||||
if err != nil {
|
||||
return c.Render("admin/system/logs", fiber.Map{
|
||||
"title": logTypeTitle,
|
||||
"error": err.Error(),
|
||||
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
|
||||
"title": logTypeTitle,
|
||||
"error": err.Error(),
|
||||
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
|
||||
"selectedLogType": logTypeParam,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Calculate total pages
|
||||
totalLogs := len(logs)
|
||||
totalPages := (totalLogs + itemsPerPage - 1) / itemsPerPage
|
||||
|
||||
|
||||
// Apply pagination
|
||||
startIndex := (page - 1) * itemsPerPage
|
||||
endIndex := startIndex + itemsPerPage
|
||||
if endIndex > totalLogs {
|
||||
endIndex = totalLogs
|
||||
}
|
||||
|
||||
|
||||
// Slice logs for current page
|
||||
pagedLogs := logs
|
||||
if startIndex < totalLogs {
|
||||
@ -175,7 +174,7 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
|
||||
} else {
|
||||
pagedLogs = []logger.LogItem{}
|
||||
}
|
||||
|
||||
|
||||
// Convert logs to a format suitable for the UI
|
||||
formattedLogs := make([]fiber.Map, 0, len(pagedLogs))
|
||||
for _, log := range pagedLogs {
|
||||
@ -185,7 +184,7 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
|
||||
logTypeStr = "ERROR"
|
||||
logTypeClass = "log-error"
|
||||
}
|
||||
|
||||
|
||||
formattedLogs = append(formattedLogs, fiber.Map{
|
||||
"timestamp": log.Timestamp.Format("2006-01-02T15:04:05"),
|
||||
"category": log.Category,
|
||||
@ -194,20 +193,20 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
|
||||
"typeClass": logTypeClass,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return c.Render("admin/system/logs", fiber.Map{
|
||||
"title": logTypeTitle,
|
||||
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
|
||||
"title": logTypeTitle,
|
||||
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
|
||||
"selectedLogType": logTypeParam,
|
||||
"logs": formattedLogs,
|
||||
"total": totalLogs,
|
||||
"showing": len(formattedLogs),
|
||||
"page": page,
|
||||
"totalPages": totalPages,
|
||||
"categoryParam": category,
|
||||
"typeParam": c.Query("type", ""),
|
||||
"fromParam": c.Query("from", ""),
|
||||
"toParam": c.Query("to", ""),
|
||||
"logs": formattedLogs,
|
||||
"total": totalLogs,
|
||||
"showing": len(formattedLogs),
|
||||
"page": page,
|
||||
"totalPages": totalPages,
|
||||
"categoryParam": category,
|
||||
"typeParam": c.Query("type", ""),
|
||||
"fromParam": c.Query("from", ""),
|
||||
"toParam": c.Query("to", ""),
|
||||
})
|
||||
}
|
||||
|
||||
@ -215,27 +214,27 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
|
||||
func (h *LogHandler) GetLogsAPI(c *fiber.Ctx) error {
|
||||
// Check which logger to use based on the log type parameter
|
||||
logTypeParam := c.Query("log_type", string(LogTypeSystem))
|
||||
|
||||
|
||||
// Parse query parameters
|
||||
category := c.Query("category", "")
|
||||
logItemType := parseLogType(c.Query("type", ""))
|
||||
maxItems := c.QueryInt("max_items", 100)
|
||||
|
||||
|
||||
// Parse time range
|
||||
fromTime := parseTimeParam(c.Query("from", ""))
|
||||
toTime := parseTimeParam(c.Query("to", ""))
|
||||
|
||||
|
||||
// Create search arguments
|
||||
searchArgs := logger.SearchArgs{
|
||||
Category: category,
|
||||
LogType: logItemType,
|
||||
MaxItems: maxItems,
|
||||
Category: category,
|
||||
LogType: logItemType,
|
||||
MaxItems: maxItems,
|
||||
}
|
||||
|
||||
|
||||
if !fromTime.IsZero() {
|
||||
searchArgs.TimestampFrom = &fromTime
|
||||
}
|
||||
|
||||
|
||||
if !toTime.IsZero() {
|
||||
searchArgs.TimestampTo = &toTime
|
||||
}
|
||||
@ -243,7 +242,7 @@ func (h *LogHandler) GetLogsAPI(c *fiber.Ctx) error {
|
||||
// Variables for logs and error
|
||||
var logs []logger.LogItem
|
||||
var err error
|
||||
|
||||
|
||||
// Check if we want to merge logs from all sources
|
||||
if LogType(logTypeParam) == LogTypeAll {
|
||||
// Get merged logs from all loggers
|
||||
@ -251,7 +250,7 @@ func (h *LogHandler) GetLogsAPI(c *fiber.Ctx) error {
|
||||
} else {
|
||||
// Select the appropriate logger based on the log type
|
||||
var selectedLogger *logger.Logger
|
||||
|
||||
|
||||
switch LogType(logTypeParam) {
|
||||
case LogTypeService:
|
||||
selectedLogger = h.serviceLogger
|
||||
@ -262,7 +261,7 @@ func (h *LogHandler) GetLogsAPI(c *fiber.Ctx) error {
|
||||
default:
|
||||
selectedLogger = h.systemLogger
|
||||
}
|
||||
|
||||
|
||||
// Check if the selected logger is properly initialized
|
||||
if selectedLogger == nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
@ -280,7 +279,7 @@ func (h *LogHandler) GetLogsAPI(c *fiber.Ctx) error {
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Convert logs to a format suitable for the UI
|
||||
response := make([]fiber.Map, 0, len(logs))
|
||||
for _, log := range logs {
|
||||
@ -288,7 +287,7 @@ func (h *LogHandler) GetLogsAPI(c *fiber.Ctx) error {
|
||||
if log.LogType == logger.LogTypeError {
|
||||
logTypeStr = "ERROR"
|
||||
}
|
||||
|
||||
|
||||
response = append(response, fiber.Map{
|
||||
"timestamp": log.Timestamp.Format(time.RFC3339),
|
||||
"category": log.Category,
|
||||
@ -296,9 +295,9 @@ func (h *LogHandler) GetLogsAPI(c *fiber.Ctx) error {
|
||||
"type": logTypeStr,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"logs": response,
|
||||
"logs": response,
|
||||
"total": len(logs),
|
||||
})
|
||||
}
|
||||
@ -309,29 +308,29 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
|
||||
|
||||
// Check which logger to use based on the log type parameter
|
||||
logTypeParam := c.Query("log_type", string(LogTypeSystem))
|
||||
|
||||
|
||||
// Parse query parameters
|
||||
category := c.Query("category", "")
|
||||
logItemType := parseLogType(c.Query("type", ""))
|
||||
maxItems := c.QueryInt("max_items", 100)
|
||||
page := c.QueryInt("page", 1)
|
||||
itemsPerPage := 20 // Default items per page
|
||||
|
||||
|
||||
// Parse time range
|
||||
fromTime := parseTimeParam(c.Query("from", ""))
|
||||
toTime := parseTimeParam(c.Query("to", ""))
|
||||
|
||||
|
||||
// Create search arguments
|
||||
searchArgs := logger.SearchArgs{
|
||||
Category: category,
|
||||
LogType: logItemType,
|
||||
MaxItems: maxItems,
|
||||
Category: category,
|
||||
LogType: logItemType,
|
||||
MaxItems: maxItems,
|
||||
}
|
||||
|
||||
|
||||
if !fromTime.IsZero() {
|
||||
searchArgs.TimestampFrom = &fromTime
|
||||
}
|
||||
|
||||
|
||||
if !toTime.IsZero() {
|
||||
searchArgs.TimestampTo = &toTime
|
||||
}
|
||||
@ -340,7 +339,7 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
|
||||
var logs []logger.LogItem
|
||||
var err error
|
||||
var logTypeTitle string
|
||||
|
||||
|
||||
// Check if we want to merge logs from all sources
|
||||
if LogType(logTypeParam) == LogTypeAll {
|
||||
// Get merged logs from all loggers
|
||||
@ -349,7 +348,7 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
|
||||
} else {
|
||||
// Select the appropriate logger based on the log type
|
||||
var selectedLogger *logger.Logger
|
||||
|
||||
|
||||
switch LogType(logTypeParam) {
|
||||
case LogTypeService:
|
||||
selectedLogger = h.serviceLogger
|
||||
@ -364,13 +363,13 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
|
||||
selectedLogger = h.systemLogger
|
||||
logTypeTitle = "System Logs"
|
||||
}
|
||||
|
||||
|
||||
// Check if the selected logger is properly initialized
|
||||
if selectedLogger == nil {
|
||||
return c.Render("admin/system/logs_fragment", fiber.Map{
|
||||
"title": logTypeTitle,
|
||||
"error": "Logger not initialized",
|
||||
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
|
||||
"title": logTypeTitle,
|
||||
"error": "Logger not initialized",
|
||||
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
|
||||
"selectedLogType": logTypeParam,
|
||||
})
|
||||
}
|
||||
@ -382,24 +381,24 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
|
||||
// Handle search error
|
||||
if err != nil {
|
||||
return c.Render("admin/system/logs_fragment", fiber.Map{
|
||||
"title": logTypeTitle,
|
||||
"error": err.Error(),
|
||||
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
|
||||
"title": logTypeTitle,
|
||||
"error": err.Error(),
|
||||
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
|
||||
"selectedLogType": logTypeParam,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Calculate total pages
|
||||
totalLogs := len(logs)
|
||||
totalPages := (totalLogs + itemsPerPage - 1) / itemsPerPage
|
||||
|
||||
|
||||
// Apply pagination
|
||||
startIndex := (page - 1) * itemsPerPage
|
||||
endIndex := startIndex + itemsPerPage
|
||||
if endIndex > totalLogs {
|
||||
endIndex = totalLogs
|
||||
}
|
||||
|
||||
|
||||
// Slice logs for current page
|
||||
pagedLogs := logs
|
||||
if startIndex < totalLogs {
|
||||
@ -407,7 +406,7 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
|
||||
} else {
|
||||
pagedLogs = []logger.LogItem{}
|
||||
}
|
||||
|
||||
|
||||
// Convert logs to a format suitable for the UI
|
||||
formattedLogs := make([]fiber.Map, 0, len(pagedLogs))
|
||||
for _, log := range pagedLogs {
|
||||
@ -417,7 +416,7 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
|
||||
logTypeStr = "ERROR"
|
||||
logTypeClass = "log-error"
|
||||
}
|
||||
|
||||
|
||||
formattedLogs = append(formattedLogs, fiber.Map{
|
||||
"timestamp": log.Timestamp.Format("2006-01-02T15:04:05"),
|
||||
"category": log.Category,
|
||||
@ -426,18 +425,18 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
|
||||
"typeClass": logTypeClass,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Set layout to empty to disable the layout for fragment responses
|
||||
return c.Render("admin/system/logs_fragment", fiber.Map{
|
||||
"title": logTypeTitle,
|
||||
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
|
||||
"title": logTypeTitle,
|
||||
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
|
||||
"selectedLogType": logTypeParam,
|
||||
"logs": formattedLogs,
|
||||
"total": totalLogs,
|
||||
"showing": len(formattedLogs),
|
||||
"page": page,
|
||||
"totalPages": totalPages,
|
||||
"layout": "", // Disable layout for partial template
|
||||
"logs": formattedLogs,
|
||||
"total": totalLogs,
|
||||
"showing": len(formattedLogs),
|
||||
"page": page,
|
||||
"totalPages": totalPages,
|
||||
"layout": "", // Disable layout for partial template
|
||||
})
|
||||
}
|
||||
|
||||
@ -458,12 +457,12 @@ func parseTimeParam(timeStr string) time.Time {
|
||||
if timeStr == "" {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
|
||||
t, err := time.Parse(time.RFC3339, timeStr)
|
||||
if err != nil {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
@ -471,10 +470,10 @@ func parseTimeParam(timeStr string) time.Time {
|
||||
func (h *LogHandler) getMergedLogs(args logger.SearchArgs) ([]logger.LogItem, error) {
|
||||
// Create a slice to hold all logs
|
||||
allLogs := make([]logger.LogItem, 0)
|
||||
|
||||
|
||||
// Create a map to track errors
|
||||
errors := make(map[string]error)
|
||||
|
||||
|
||||
// Get logs from system logger if available
|
||||
if h.systemLogger != nil {
|
||||
systemLogs, err := h.systemLogger.Search(args)
|
||||
@ -488,7 +487,7 @@ func (h *LogHandler) getMergedLogs(args logger.SearchArgs) ([]logger.LogItem, er
|
||||
allLogs = append(allLogs, systemLogs...)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Get logs from service logger if available
|
||||
if h.serviceLogger != nil {
|
||||
serviceLogs, err := h.serviceLogger.Search(args)
|
||||
@ -502,7 +501,7 @@ func (h *LogHandler) getMergedLogs(args logger.SearchArgs) ([]logger.LogItem, er
|
||||
allLogs = append(allLogs, serviceLogs...)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Get logs from job logger if available
|
||||
if h.jobLogger != nil {
|
||||
jobLogs, err := h.jobLogger.Search(args)
|
||||
@ -516,7 +515,7 @@ func (h *LogHandler) getMergedLogs(args logger.SearchArgs) ([]logger.LogItem, er
|
||||
allLogs = append(allLogs, jobLogs...)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Get logs from process logger if available
|
||||
if h.processLogger != nil {
|
||||
processLogs, err := h.processLogger.Search(args)
|
||||
@ -530,7 +529,7 @@ func (h *LogHandler) getMergedLogs(args logger.SearchArgs) ([]logger.LogItem, er
|
||||
allLogs = append(allLogs, processLogs...)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check if we have any logs
|
||||
if len(allLogs) == 0 && len(errors) > 0 {
|
||||
// Combine error messages
|
||||
@ -540,16 +539,16 @@ func (h *LogHandler) getMergedLogs(args logger.SearchArgs) ([]logger.LogItem, er
|
||||
}
|
||||
return nil, fmt.Errorf("failed to retrieve logs: %s", strings.Join(errorMsgs, "; "))
|
||||
}
|
||||
|
||||
|
||||
// Sort logs by timestamp (newest first)
|
||||
sort.Slice(allLogs, func(i, j int) bool {
|
||||
return allLogs[i].Timestamp.After(allLogs[j].Timestamp)
|
||||
})
|
||||
|
||||
|
||||
// Apply max items limit if specified
|
||||
if args.MaxItems > 0 && len(allLogs) > args.MaxItems {
|
||||
allLogs = allLogs[:args.MaxItems]
|
||||
}
|
||||
|
||||
|
||||
return allLogs, nil
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/stats"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/stats"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
@ -43,8 +43,8 @@ func (h *ProcessHandler) GetProcessStatsJSON(c *fiber.Ctx) error {
|
||||
|
||||
// Convert to fiber.Map for JSON response
|
||||
response := fiber.Map{
|
||||
"total": processData.Total,
|
||||
"filtered": processData.Filtered,
|
||||
"total": processData.Total,
|
||||
"filtered": processData.Filtered,
|
||||
"timestamp": time.Now().Unix(),
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ func (h *ProcessHandler) GetProcessStatsJSON(c *fiber.Ctx) error {
|
||||
"is_current": proc.IsCurrent,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
response["processes"] = processes
|
||||
|
||||
// Return JSON response
|
||||
@ -127,8 +127,8 @@ func (h *ProcessHandler) GetProcessesData(c *fiber.Ctx) error {
|
||||
// Check if StatsManager is properly initialized
|
||||
if h.statsManager == nil {
|
||||
return c.Render("admin/system/processes_data", fiber.Map{
|
||||
"error": "System error: Stats manager not initialized",
|
||||
"layout": "",
|
||||
"error": "System error: Stats manager not initialized",
|
||||
"layout": "",
|
||||
})
|
||||
}
|
||||
|
||||
@ -156,7 +156,7 @@ func (h *ProcessHandler) GetProcessesData(c *fiber.Ctx) error {
|
||||
} else {
|
||||
processData, err = h.statsManager.GetProcessStatsFresh(0)
|
||||
}
|
||||
|
||||
|
||||
if err != nil {
|
||||
// Handle AJAX requests differently from regular requests
|
||||
isAjax := c.Get("X-Requested-With") == "XMLHttpRequest"
|
||||
@ -165,8 +165,8 @@ func (h *ProcessHandler) GetProcessesData(c *fiber.Ctx) error {
|
||||
}
|
||||
// For regular requests, render the error within the fragment
|
||||
return c.Render("admin/system/processes_data", fiber.Map{
|
||||
"error": "Failed to get process data: " + err.Error(),
|
||||
"layout": "",
|
||||
"error": "Failed to get process data: " + err.Error(),
|
||||
"layout": "",
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -197,9 +197,7 @@ func (h *ProcessHandler) GetProcessesData(c *fiber.Ctx) error {
|
||||
"processStats": processStats,
|
||||
"layout": "", // Disable layout for partial template
|
||||
}
|
||||
|
||||
|
||||
// Return only the table HTML content directly to be injected into the processes-table-content div
|
||||
return c.Render("admin/system/processes_data", templateData)
|
||||
}
|
||||
|
||||
|
||||
|
@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces/openrpc"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces/openrpc"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/stats"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/stats"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
)
|
||||
@ -335,7 +335,7 @@ func (h *SystemHandler) GetProcessStatsAPI(c *fiber.Ctx) error {
|
||||
"is_current": proc.IsCurrent,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
response["processes"] = processes
|
||||
|
||||
// Return JSON response
|
||||
|
@ -7,8 +7,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroagent/handlers"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/stats"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroagent/handlers"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/stats"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
)
|
||||
@ -68,12 +68,12 @@ func (h *AdminHandler) RegisterRoutes(app *fiber.App) {
|
||||
// System routes
|
||||
admin.Get("/system/info", h.getSystemInfo)
|
||||
admin.Get("/system/hardware-stats", h.getHardwareStats)
|
||||
|
||||
|
||||
// Create process handler
|
||||
processHandler := handlers.NewProcessHandler(h.statsManager)
|
||||
admin.Get("/system/processes", processHandler.GetProcesses)
|
||||
admin.Get("/system/processes-data", processHandler.GetProcessesData)
|
||||
|
||||
|
||||
// Create log handler
|
||||
// Ensure log directory exists
|
||||
// Using the same shared logs path as process manager
|
||||
@ -81,7 +81,7 @@ func (h *AdminHandler) RegisterRoutes(app *fiber.App) {
|
||||
if err := os.MkdirAll(logDir, 0755); err != nil {
|
||||
fmt.Printf("Error creating log directory: %v\n", err)
|
||||
}
|
||||
|
||||
|
||||
logHandler, err := handlers.NewLogHandler(logDir)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating log handler: %v\n", err)
|
||||
@ -96,11 +96,11 @@ func (h *AdminHandler) RegisterRoutes(app *fiber.App) {
|
||||
// but it now just redirects to the main logs endpoint
|
||||
admin.Get("/system/logs-fragment", logHandler.GetLogsFragment)
|
||||
admin.Get("/system/logs-test", h.getSystemLogsTest) // Keep the test logs route
|
||||
|
||||
|
||||
// Log API endpoints
|
||||
app.Get("/api/logs", logHandler.GetLogsAPI)
|
||||
}
|
||||
|
||||
|
||||
admin.Get("/system/settings", h.getSystemSettings)
|
||||
|
||||
// OpenRPC routes
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/herojobs"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/herojobs"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
|
@ -4,14 +4,14 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces/openrpc"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces/openrpc"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// ServiceHandler handles service-related page routes
|
||||
type ServiceHandler struct {
|
||||
client *openrpc.Client
|
||||
logger *log.Logger
|
||||
client *openrpc.Client
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// NewServiceHandler creates a new service handler with the provided socket path and secret
|
||||
@ -90,7 +90,7 @@ func (h *ServiceHandler) getProcessList() ([]ProcessDisplayInfo, error) {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// Create a ProcessDisplayInfo from the map
|
||||
displayInfo := ProcessDisplayInfo{
|
||||
ID: fmt.Sprintf("%v", procMap["pid"]),
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager"
|
||||
)
|
||||
|
||||
// ProcessDisplayInfo represents information about a process for display purposes
|
||||
@ -22,7 +22,7 @@ type ProcessDisplayInfo struct {
|
||||
func ConvertToDisplayInfo(info *processmanager.ProcessInfo) ProcessDisplayInfo {
|
||||
// Calculate uptime from start time
|
||||
uptime := formatUptime(time.Since(info.StartTime))
|
||||
|
||||
|
||||
return ProcessDisplayInfo{
|
||||
ID: fmt.Sprintf("%d", info.PID),
|
||||
Name: info.Name,
|
||||
|
@ -7,8 +7,8 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/data/ourdb"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/tools"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/data/ourdb"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/tools"
|
||||
)
|
||||
|
||||
// JobStatus represents the status of a job
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/tools"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/tools"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
|
@ -35,7 +35,7 @@ Key features:
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/playbook"
|
||||
)
|
||||
|
||||
// Create a new playbook from HeroScript text
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/handlerfactory/herohandler"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/handlerfactory/herohandler"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -24,7 +24,7 @@ func main() {
|
||||
fmt.Printf("Failed to initialize hero handler: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
// Get the default instance
|
||||
handler := herohandler.DefaultInstance
|
||||
|
||||
@ -43,7 +43,7 @@ func main() {
|
||||
tcpAddressStr = *tcpAddress
|
||||
fmt.Printf("TCP address: %s\n", tcpAddressStr)
|
||||
}
|
||||
|
||||
|
||||
err = handler.StartTelnet(socketPathStr, tcpAddressStr)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to start telnet server: %v\n", err)
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/playbook"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -3,8 +3,8 @@ package internal
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/handlerfactory"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlers"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/handlerfactory"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/handlers"
|
||||
)
|
||||
|
||||
// ExampleHandler handles example actions
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/cmd/herohandler/internal"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/cmd/herohandler/internal"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -30,7 +30,7 @@ func main() {
|
||||
|
||||
// Get the command from arguments
|
||||
command := strings.Join(os.Args[1:], " ")
|
||||
|
||||
|
||||
// Format as proper HeroScript with !! prefix if not already prefixed
|
||||
script := command
|
||||
if !strings.HasPrefix(script, "!!") {
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/handlerfactory/herohandler"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/handlerfactory/herohandler"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -92,7 +92,7 @@ func showSupportedActions() {
|
||||
// We need to implement this function to get supported actions
|
||||
// Since we can't directly access the factory field, we'll use the telnet interface
|
||||
script := "!!core.actions"
|
||||
|
||||
|
||||
// Try TCP first, then Unix socket if TCP fails
|
||||
result, err := Send(script, "localhost:8023", false)
|
||||
if err != nil {
|
||||
@ -103,7 +103,7 @@ func showSupportedActions() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fmt.Println("Supported actions by actor:")
|
||||
fmt.Println(result)
|
||||
}
|
||||
@ -151,7 +151,7 @@ func Send(command string, address string, isUnixSocket bool) (string, error) {
|
||||
return
|
||||
}
|
||||
response.WriteString(line)
|
||||
|
||||
|
||||
// If we've received a complete response, break
|
||||
if strings.Contains(line, "\n") && strings.TrimSpace(line) == "" {
|
||||
break
|
||||
@ -187,7 +187,7 @@ func runTestScript() {
|
||||
result, err = Send(script, "/tmp/hero.sock", true)
|
||||
if err != nil {
|
||||
fmt.Printf("Unix socket connection failed: %v\n", err)
|
||||
|
||||
|
||||
// We can't directly access the factory field, so we'll just report the error
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/playbook"
|
||||
)
|
||||
|
||||
const exampleScript = `
|
||||
|
@ -16,7 +16,7 @@ The VM handler example shows how to:
|
||||
To run the example:
|
||||
|
||||
```bash
|
||||
cd ~/code/github/freeflowuniverse/heroagent/pkg/handlerfactory/cmd/vmhandler
|
||||
cd ~/code/github/freeflowuniverse/herocode/heroagent/pkg/handlerfactory/cmd/vmhandler
|
||||
go run . tutorial
|
||||
#to run just the server do
|
||||
go run .
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/handlerfactory"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/handlerfactory"
|
||||
)
|
||||
|
||||
// runTutorial runs an interactive tutorial demonstrating the VM handler
|
||||
@ -50,7 +50,7 @@ func runTutorial() {
|
||||
|
||||
// Process heroscript commands
|
||||
fmt.Println("\nStep 5: Process heroscript commands")
|
||||
|
||||
|
||||
// Define a VM
|
||||
defineScript := `!!vm.define name:'tutorial_vm' cpu:2 memory:'4GB' storage:'50GB'
|
||||
description: 'A tutorial VM for demonstration purposes'`
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/handlerfactory"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/handlerfactory"
|
||||
)
|
||||
|
||||
// VMHandler handles VM-related actions
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/handlerfactory"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/handlerfactory"
|
||||
)
|
||||
|
||||
// The tutorial functions are defined in tutorial.go
|
||||
|
@ -30,7 +30,7 @@ The Handler Factory exposes two interfaces for communication:
|
||||
to get started
|
||||
|
||||
```bash
|
||||
cd /root/code/github/freeflowuniverse/heroagent/pkg/handlerfactory/herohandler/cmd
|
||||
cd /root/code/github/freeflowuniverse/herocode/heroagent/pkg/handlerfactory/herohandler/cmd
|
||||
go run .
|
||||
```
|
||||
|
||||
|
@ -5,8 +5,8 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/paramsparser"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/paramsparser"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/playbook"
|
||||
)
|
||||
|
||||
// Handler interface defines methods that all handlers must implement
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/playbook"
|
||||
)
|
||||
|
||||
// HandlerFactory manages a collection of handlers
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/playbook"
|
||||
)
|
||||
|
||||
// ANSI color codes for terminal output
|
||||
@ -43,23 +43,23 @@ type TelnetServer struct {
|
||||
sigCh chan os.Signal
|
||||
onShutdown func()
|
||||
// Map to store client preferences (like json formatting)
|
||||
clientPrefs map[net.Conn]map[string]bool
|
||||
prefsMutex sync.RWMutex
|
||||
clientPrefs map[net.Conn]map[string]bool
|
||||
prefsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewTelnetServer creates a new telnet server
|
||||
func NewTelnetServer(factory *HandlerFactory, secrets ...string) *TelnetServer {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &TelnetServer{
|
||||
factory: factory,
|
||||
secrets: secrets,
|
||||
clients: make(map[net.Conn]bool),
|
||||
factory: factory,
|
||||
secrets: secrets,
|
||||
clients: make(map[net.Conn]bool),
|
||||
clientPrefs: make(map[net.Conn]map[string]bool),
|
||||
running: false,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
sigCh: make(chan os.Signal, 1),
|
||||
onShutdown: func() {},
|
||||
running: false,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
sigCh: make(chan os.Signal, 1),
|
||||
onShutdown: func() {},
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +205,7 @@ func (ts *TelnetServer) handleConnection(conn net.Conn) {
|
||||
ts.clientsMutex.Lock()
|
||||
ts.clients[conn] = false
|
||||
ts.clientsMutex.Unlock()
|
||||
|
||||
|
||||
// Initialize client preferences
|
||||
ts.prefsMutex.Lock()
|
||||
ts.clientPrefs[conn] = make(map[string]bool)
|
||||
@ -284,7 +284,7 @@ func (ts *TelnetServer) handleConnection(conn net.Conn) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// Handle JSON format toggle
|
||||
if line == "!!json" {
|
||||
ts.prefsMutex.Lock()
|
||||
@ -293,12 +293,12 @@ func (ts *TelnetServer) handleConnection(conn net.Conn) {
|
||||
prefs = make(map[string]bool)
|
||||
ts.clientPrefs[conn] = prefs
|
||||
}
|
||||
|
||||
|
||||
// Toggle JSON format preference
|
||||
currentSetting := prefs["json"]
|
||||
prefs["json"] = !currentSetting
|
||||
ts.prefsMutex.Unlock()
|
||||
|
||||
|
||||
if prefs["json"] {
|
||||
conn.Write([]byte("JSON format will be automatically added to all heroscripts.\n"))
|
||||
} else {
|
||||
@ -416,7 +416,7 @@ func (ts *TelnetServer) executeHeroscript(script string, conn net.Conn, interact
|
||||
ts.prefsMutex.RLock()
|
||||
prefs, exists := ts.clientPrefs[conn]
|
||||
ts.prefsMutex.RUnlock()
|
||||
|
||||
|
||||
if exists && prefs["json"] {
|
||||
// Add format:json if not already present
|
||||
if !strings.Contains(script, "format:json") {
|
||||
@ -424,7 +424,7 @@ func (ts *TelnetServer) executeHeroscript(script string, conn net.Conn, interact
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if interactive {
|
||||
// Format the script with colors
|
||||
formattedScript := formatHeroscript(script)
|
||||
@ -466,8 +466,6 @@ func (ts *TelnetServer) addJsonFormat(script string) string {
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
|
||||
|
||||
// formatHeroscript formats heroscript with colors for console output only
|
||||
// This is not used for telnet responses, only for server-side logging
|
||||
func formatHeroscript(script string) string {
|
||||
@ -611,12 +609,12 @@ func (ts *TelnetServer) generateHelpText(interactive bool) string {
|
||||
// Try to call the Help method on each handler using reflection
|
||||
handlerValue := reflect.ValueOf(handler)
|
||||
helpMethod := handlerValue.MethodByName("Help")
|
||||
|
||||
|
||||
if helpMethod.IsValid() {
|
||||
// Call the Help method
|
||||
args := []reflect.Value{reflect.ValueOf("")}
|
||||
result := helpMethod.Call(args)
|
||||
|
||||
|
||||
// Get the result
|
||||
if len(result) > 0 && result[0].Kind() == reflect.String {
|
||||
helpText := result[0].String()
|
||||
|
@ -3,7 +3,7 @@ package handlers
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/core"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/handlerfactory/core"
|
||||
)
|
||||
|
||||
// AuthHandler handles authentication actions
|
||||
|
@ -5,9 +5,9 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/core"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/paramsparser"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/handlerfactory/core"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/paramsparser"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/playbook"
|
||||
)
|
||||
|
||||
// BaseHandler provides common functionality for all handlers
|
||||
|
@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/core"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/handlerfactory/core"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/playbook"
|
||||
)
|
||||
|
||||
// HandlerFactory manages a collection of handlers for processing HeroScript commands
|
||||
|
@ -1,7 +1,7 @@
|
||||
package herohandler
|
||||
|
||||
import (
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/core"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/handlerfactory/core"
|
||||
)
|
||||
|
||||
// GetFactory returns the handler factory
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/herohandler"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/handlerfactory/herohandler"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -4,10 +4,10 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/core"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/handlerfactory/core"
|
||||
|
||||
// "github.com/freeflowuniverse/heroagent/pkg/handlerfactory/heroscript/handlerfactory/fakehandler"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/processmanagerhandler"
|
||||
// "git.ourworld.tf/herocode/heroagent/pkg/handlerfactory/heroscript/handlerfactory/fakehandler"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/handlerfactory/processmanagerhandler"
|
||||
)
|
||||
|
||||
// HeroHandler is the main handler factory that manages all registered handlers
|
||||
|
@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/playbook"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -3,8 +3,8 @@ package processmanagerhandler
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/core"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/handlerfactory/core"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager"
|
||||
)
|
||||
|
||||
// ProcessManagerHandler handles process manager-related actions
|
||||
|
@ -19,7 +19,7 @@ A Go package for parsing and manipulating parameters from text in a key-value fo
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/freeflowuniverse/heroagent/pkg/paramsparser"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/paramsparser"
|
||||
)
|
||||
|
||||
// Create a new parser
|
||||
|
@ -4,7 +4,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/paramsparser"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/paramsparser"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/tools"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/tools"
|
||||
)
|
||||
|
||||
// ParamsParser represents a parameter parser that can handle various parameter sources
|
||||
@ -32,12 +32,12 @@ func New() *ParamsParser {
|
||||
func (p *ParamsParser) Parse(input string) error {
|
||||
// Normalize line endings
|
||||
input = strings.ReplaceAll(input, "\r\n", "\n")
|
||||
|
||||
|
||||
// Track the current state
|
||||
var currentKey string
|
||||
var currentValue strings.Builder
|
||||
var inMultilineString bool
|
||||
|
||||
|
||||
// Process each line
|
||||
lines := strings.Split(input, "\n")
|
||||
for i := 0; i < len(lines); i++ {
|
||||
@ -48,12 +48,12 @@ func (p *ParamsParser) Parse(input string) error {
|
||||
} else {
|
||||
line = lines[i]
|
||||
}
|
||||
|
||||
|
||||
// Skip empty lines unless we're in a multiline string
|
||||
if line == "" && !inMultilineString {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// If we're in a multiline string
|
||||
if inMultilineString {
|
||||
// Check if this line ends the multiline string
|
||||
@ -71,7 +71,7 @@ func (p *ParamsParser) Parse(input string) error {
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// Process the line to extract key-value pairs
|
||||
var processedPos int
|
||||
for processedPos < len(line) {
|
||||
@ -79,62 +79,62 @@ func (p *ParamsParser) Parse(input string) error {
|
||||
for processedPos < len(line) && (line[processedPos] == ' ' || line[processedPos] == '\t') {
|
||||
processedPos++
|
||||
}
|
||||
|
||||
|
||||
if processedPos >= len(line) {
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
// Find the next key by looking for a colon
|
||||
keyStart := processedPos
|
||||
colonPos := -1
|
||||
|
||||
|
||||
for j := processedPos; j < len(line); j++ {
|
||||
if line[j] == ':' {
|
||||
colonPos = j
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if colonPos == -1 {
|
||||
// No colon found, skip this part
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
// Extract key and use NameFix to standardize it
|
||||
rawKey := strings.TrimSpace(line[keyStart:colonPos])
|
||||
key := tools.NameFix(rawKey)
|
||||
|
||||
|
||||
if key == "" {
|
||||
// Invalid key, move past the colon and continue
|
||||
processedPos = colonPos + 1
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// Move position past the colon
|
||||
processedPos = colonPos + 1
|
||||
|
||||
|
||||
if processedPos >= len(line) {
|
||||
// End of line reached, store empty value
|
||||
p.params[key] = ""
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
// Skip whitespace after the colon
|
||||
for processedPos < len(line) && (line[processedPos] == ' ' || line[processedPos] == '\t') {
|
||||
processedPos++
|
||||
}
|
||||
|
||||
|
||||
if processedPos >= len(line) {
|
||||
// End of line reached after whitespace, store empty value
|
||||
p.params[key] = ""
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
// Check if the value is quoted
|
||||
if line[processedPos] == '\'' {
|
||||
// This is a quoted string
|
||||
processedPos++ // Skip the opening quote
|
||||
|
||||
|
||||
// Look for the closing quote
|
||||
quoteEnd := -1
|
||||
for j := processedPos; j < len(line); j++ {
|
||||
@ -144,7 +144,7 @@ func (p *ParamsParser) Parse(input string) error {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if quoteEnd != -1 {
|
||||
// Single-line quoted string
|
||||
value := line[processedPos:quoteEnd]
|
||||
@ -167,12 +167,12 @@ func (p *ParamsParser) Parse(input string) error {
|
||||
// This is an unquoted value
|
||||
valueStart := processedPos
|
||||
valueEnd := valueStart
|
||||
|
||||
|
||||
// Find the end of the value (space or end of line)
|
||||
for valueEnd < len(line) && line[valueEnd] != ' ' && line[valueEnd] != '\t' {
|
||||
valueEnd++
|
||||
}
|
||||
|
||||
|
||||
value := line[valueStart:valueEnd]
|
||||
// For unquoted values, use NameFix to standardize them
|
||||
// This handles the 'without' keyword and other special cases
|
||||
@ -181,12 +181,12 @@ func (p *ParamsParser) Parse(input string) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If we're still in a multiline string at the end, that's an error
|
||||
if inMultilineString {
|
||||
return errors.New("unterminated multiline string")
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -196,7 +196,7 @@ func (p *ParamsParser) Parse(input string) error {
|
||||
func (p *ParamsParser) ParseString(input string) error {
|
||||
// Trim the input
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
|
||||
// Process the input to extract key-value pairs
|
||||
var processedPos int
|
||||
for processedPos < len(input) {
|
||||
@ -204,62 +204,62 @@ func (p *ParamsParser) ParseString(input string) error {
|
||||
for processedPos < len(input) && (input[processedPos] == ' ' || input[processedPos] == '\t') {
|
||||
processedPos++
|
||||
}
|
||||
|
||||
|
||||
if processedPos >= len(input) {
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
// Find the next key by looking for a colon
|
||||
keyStart := processedPos
|
||||
colonPos := -1
|
||||
|
||||
|
||||
for j := processedPos; j < len(input); j++ {
|
||||
if input[j] == ':' {
|
||||
colonPos = j
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if colonPos == -1 {
|
||||
// No colon found, skip this part
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
// Extract key and use NameFix to standardize it
|
||||
rawKey := strings.TrimSpace(input[keyStart:colonPos])
|
||||
key := tools.NameFix(rawKey)
|
||||
|
||||
|
||||
if key == "" {
|
||||
// Invalid key, move past the colon and continue
|
||||
processedPos = colonPos + 1
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// Move position past the colon
|
||||
processedPos = colonPos + 1
|
||||
|
||||
|
||||
if processedPos >= len(input) {
|
||||
// End of input reached, store empty value
|
||||
p.params[key] = ""
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
// Skip whitespace after the colon
|
||||
for processedPos < len(input) && (input[processedPos] == ' ' || input[processedPos] == '\t') {
|
||||
processedPos++
|
||||
}
|
||||
|
||||
|
||||
if processedPos >= len(input) {
|
||||
// End of input reached after whitespace, store empty value
|
||||
p.params[key] = ""
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
// Check if the value is quoted
|
||||
if input[processedPos] == '\'' {
|
||||
// This is a quoted string
|
||||
processedPos++ // Skip the opening quote
|
||||
|
||||
|
||||
// Look for the closing quote
|
||||
quoteEnd := -1
|
||||
for j := processedPos; j < len(input); j++ {
|
||||
@ -269,11 +269,11 @@ func (p *ParamsParser) ParseString(input string) error {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if quoteEnd == -1 {
|
||||
return errors.New("unterminated quoted string")
|
||||
}
|
||||
|
||||
|
||||
value := input[processedPos:quoteEnd]
|
||||
// For quoted values in ParseString, we can apply NameFix
|
||||
// since this method doesn't handle multiline strings
|
||||
@ -286,12 +286,12 @@ func (p *ParamsParser) ParseString(input string) error {
|
||||
// This is an unquoted value
|
||||
valueStart := processedPos
|
||||
valueEnd := valueStart
|
||||
|
||||
|
||||
// Find the end of the value (space or end of input)
|
||||
for valueEnd < len(input) && input[valueEnd] != ' ' && input[valueEnd] != '\t' {
|
||||
valueEnd++
|
||||
}
|
||||
|
||||
|
||||
value := input[valueStart:valueEnd]
|
||||
// For unquoted values, use NameFix to standardize them
|
||||
// This handles the 'without' keyword and other special cases
|
||||
@ -299,7 +299,7 @@ func (p *ParamsParser) ParseString(input string) error {
|
||||
processedPos = valueEnd
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -364,7 +364,7 @@ func (p *ParamsParser) GetBool(key string) bool {
|
||||
if value == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// Check for common boolean string representations
|
||||
value = strings.ToLower(value)
|
||||
return value == "true" || value == "yes" || value == "1" || value == "on"
|
||||
@ -405,17 +405,17 @@ func (p *ParamsParser) Has(key string) bool {
|
||||
// GetAll returns all parameters as a map
|
||||
func (p *ParamsParser) GetAll() map[string]string {
|
||||
result := make(map[string]string)
|
||||
|
||||
|
||||
// First add defaults
|
||||
for k, v := range p.defaultParams {
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
|
||||
// Then override with actual params
|
||||
for k, v := range p.params {
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,8 @@ package playbook
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/paramsparser"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/tools"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/paramsparser"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/tools"
|
||||
)
|
||||
|
||||
// State represents the parser state
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/paramsparser"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/paramsparser"
|
||||
)
|
||||
|
||||
// ActionType represents the type of action
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroservices/billing/models"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroservices/billing/models"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -7,9 +7,9 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/data/ourdb"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/data/radixtree"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/tools"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/data/ourdb"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/data/radixtree"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/tools"
|
||||
)
|
||||
|
||||
// DBStore represents the central database store for all models
|
||||
|
@ -9,8 +9,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
openaiproxy "github.com/freeflowuniverse/heroagent/pkg/heroservices/openaiproxy"
|
||||
"github.com/openai/openai-go"
|
||||
openaiproxy "git.ourworld.tf/herocode/heroagent/pkg/heroservices/openaiproxy"
|
||||
"github.com/openai/openai-go/option"
|
||||
)
|
||||
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/jobsmanager"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/jobsmanager"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
)
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/logger"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/logger"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -1,209 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/openrpcmanager"
|
||||
)
|
||||
|
||||
// Common errors
|
||||
var (
|
||||
ErrConnectionFailed = errors.New("failed to connect to OpenRPC server")
|
||||
ErrRequestFailed = errors.New("failed to send request to OpenRPC server")
|
||||
ErrResponseFailed = errors.New("failed to read response from OpenRPC server")
|
||||
ErrUnmarshalFailed = errors.New("failed to unmarshal response")
|
||||
ErrUnexpectedResponse = errors.New("unexpected response format")
|
||||
ErrRPCError = errors.New("RPC error")
|
||||
ErrAuthenticationFailed = errors.New("authentication failed")
|
||||
)
|
||||
|
||||
// RPCRequest represents an outgoing RPC request
|
||||
type RPCRequest struct {
|
||||
Method string `json:"method"`
|
||||
Params json.RawMessage `json:"params"`
|
||||
ID int `json:"id"`
|
||||
Secret string `json:"secret,omitempty"`
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
}
|
||||
|
||||
// RPCResponse represents an incoming RPC response
|
||||
type RPCResponse struct {
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
Error *RPCError `json:"error,omitempty"`
|
||||
ID interface{} `json:"id,omitempty"`
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
}
|
||||
|
||||
// RPCError represents an RPC error
|
||||
type RPCError struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// Error returns a string representation of the RPC error
|
||||
func (e *RPCError) Error() string {
|
||||
if e.Data != nil {
|
||||
return fmt.Sprintf("RPC error %d: %s - %v", e.Code, e.Message, e.Data)
|
||||
}
|
||||
return fmt.Sprintf("RPC error %d: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// IntrospectionResponse represents the response from the rpc.introspect method
|
||||
type IntrospectionResponse struct {
|
||||
Logs []openrpcmanager.CallLog `json:"logs"`
|
||||
Total int `json:"total"`
|
||||
Filtered int `json:"filtered"`
|
||||
}
|
||||
|
||||
// Client is the interface that all OpenRPC clients must implement
|
||||
type Client interface {
|
||||
// Discover returns the OpenRPC schema
|
||||
Discover() (openrpcmanager.OpenRPCSchema, error)
|
||||
|
||||
// Introspect returns information about recent RPC calls
|
||||
Introspect(limit int, method string, status string) (IntrospectionResponse, error)
|
||||
|
||||
// Request sends a request to the OpenRPC server and returns the result
|
||||
Request(method string, params json.RawMessage, secret string) (interface{}, error)
|
||||
|
||||
// Close closes the client connection
|
||||
Close() error
|
||||
}
|
||||
|
||||
// BaseClient provides a base implementation of the Client interface
|
||||
type BaseClient struct {
|
||||
socketPath string
|
||||
secret string
|
||||
nextID int
|
||||
}
|
||||
|
||||
// NewClient creates a new OpenRPC client
|
||||
func NewClient(socketPath, secret string) *BaseClient {
|
||||
return &BaseClient{
|
||||
socketPath: socketPath,
|
||||
secret: secret,
|
||||
nextID: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// Discover returns the OpenRPC schema
|
||||
func (c *BaseClient) Discover() (openrpcmanager.OpenRPCSchema, error) {
|
||||
result, err := c.Request("rpc.discover", json.RawMessage("{}"), "")
|
||||
if err != nil {
|
||||
return openrpcmanager.OpenRPCSchema{}, err
|
||||
}
|
||||
|
||||
// Convert result to schema
|
||||
resultJSON, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return openrpcmanager.OpenRPCSchema{}, fmt.Errorf("%w: %v", ErrUnmarshalFailed, err)
|
||||
}
|
||||
|
||||
var schema openrpcmanager.OpenRPCSchema
|
||||
if err := json.Unmarshal(resultJSON, &schema); err != nil {
|
||||
return openrpcmanager.OpenRPCSchema{}, fmt.Errorf("%w: %v", ErrUnmarshalFailed, err)
|
||||
}
|
||||
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
// Introspect returns information about recent RPC calls
|
||||
func (c *BaseClient) Introspect(limit int, method string, status string) (IntrospectionResponse, error) {
|
||||
// Create the params object
|
||||
params := struct {
|
||||
Limit int `json:"limit,omitempty"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
}{
|
||||
Limit: limit,
|
||||
Method: method,
|
||||
Status: status,
|
||||
}
|
||||
|
||||
// Marshal the params
|
||||
paramsJSON, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return IntrospectionResponse{}, fmt.Errorf("failed to marshal introspection params: %v", err)
|
||||
}
|
||||
|
||||
// Make the request
|
||||
result, err := c.Request("rpc.introspect", paramsJSON, c.secret)
|
||||
if err != nil {
|
||||
return IntrospectionResponse{}, err
|
||||
}
|
||||
|
||||
// Convert result to introspection response
|
||||
resultJSON, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return IntrospectionResponse{}, fmt.Errorf("%w: %v", ErrUnmarshalFailed, err)
|
||||
}
|
||||
|
||||
var response IntrospectionResponse
|
||||
if err := json.Unmarshal(resultJSON, &response); err != nil {
|
||||
return IntrospectionResponse{}, fmt.Errorf("%w: %v", ErrUnmarshalFailed, err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// Request sends a request to the OpenRPC server and returns the result
|
||||
func (c *BaseClient) Request(method string, params json.RawMessage, secret string) (interface{}, error) {
|
||||
// Connect to the Unix socket
|
||||
conn, err := net.Dial("unix", c.socketPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrConnectionFailed, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Create the request
|
||||
request := RPCRequest{
|
||||
Method: method,
|
||||
Params: params,
|
||||
ID: c.nextID,
|
||||
Secret: secret,
|
||||
JSONRPC: "2.0",
|
||||
}
|
||||
c.nextID++
|
||||
|
||||
// Marshal the request
|
||||
requestData, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request: %v", err)
|
||||
}
|
||||
|
||||
// Send the request
|
||||
_, err = conn.Write(requestData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrRequestFailed, err)
|
||||
}
|
||||
|
||||
// Read the response
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrResponseFailed, err)
|
||||
}
|
||||
|
||||
// Parse the response
|
||||
var response RPCResponse
|
||||
if err := json.Unmarshal(buf[:n], &response); err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrUnmarshalFailed, err)
|
||||
}
|
||||
|
||||
// Check for errors
|
||||
if response.Error != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrRPCError, response.Error)
|
||||
}
|
||||
|
||||
return response.Result, nil
|
||||
}
|
||||
|
||||
// Close closes the client connection
|
||||
func (c *BaseClient) Close() error {
|
||||
// Nothing to do for the base client since we create a new connection for each request
|
||||
return nil
|
||||
}
|
@ -1,283 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/openrpcmanager"
|
||||
)
|
||||
|
||||
// MockClient implements the Client interface for testing
|
||||
type MockClient struct {
|
||||
BaseClient
|
||||
}
|
||||
|
||||
// TestMethod is a test method that returns a greeting
|
||||
func (c *MockClient) TestMethod(name string) (string, error) {
|
||||
params := map[string]string{"name": name}
|
||||
paramsJSON, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result, err := c.Request("test.method", paramsJSON, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Convert result to string
|
||||
greeting, ok := result.(string)
|
||||
if !ok {
|
||||
return "", ErrUnexpectedResponse
|
||||
}
|
||||
|
||||
return greeting, nil
|
||||
}
|
||||
|
||||
// SecureMethod is a test method that requires authentication
|
||||
func (c *MockClient) SecureMethod() (map[string]interface{}, error) {
|
||||
result, err := c.Request("secure.method", json.RawMessage("{}"), c.secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert result to map
|
||||
data, ok := result.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, ErrUnexpectedResponse
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func TestClient(t *testing.T) {
|
||||
// Create a temporary socket path
|
||||
tempDir, err := os.MkdirTemp("", "openrpc-client-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
socketPath := filepath.Join(tempDir, "openrpc.sock")
|
||||
secret := "test-secret"
|
||||
|
||||
// Create test schema and handlers
|
||||
schema := openrpcmanager.OpenRPCSchema{
|
||||
OpenRPC: "1.2.6",
|
||||
Info: openrpcmanager.InfoObject{
|
||||
Title: "Test API",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Methods: []openrpcmanager.MethodObject{
|
||||
{
|
||||
Name: "test.method",
|
||||
Params: []openrpcmanager.ContentDescriptorObject{
|
||||
{
|
||||
Name: "name",
|
||||
Schema: openrpcmanager.SchemaObject{"type": "string"},
|
||||
},
|
||||
},
|
||||
Result: &openrpcmanager.ContentDescriptorObject{
|
||||
Name: "result",
|
||||
Schema: openrpcmanager.SchemaObject{"type": "string"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "secure.method",
|
||||
Params: []openrpcmanager.ContentDescriptorObject{},
|
||||
Result: &openrpcmanager.ContentDescriptorObject{
|
||||
Name: "result",
|
||||
Schema: openrpcmanager.SchemaObject{"type": "object"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
handlers := map[string]openrpcmanager.RPCHandler{
|
||||
"test.method": func(params json.RawMessage) (interface{}, error) {
|
||||
var request struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.Unmarshal(params, &request); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return "Hello, " + request.Name + "!", nil
|
||||
},
|
||||
"secure.method": func(params json.RawMessage) (interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"secure": true,
|
||||
"data": "sensitive information",
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
// Create and start OpenRPC manager and Unix server
|
||||
manager, err := openrpcmanager.NewOpenRPCManager(schema, handlers, secret)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create OpenRPCManager: %v", err)
|
||||
}
|
||||
|
||||
server, err := openrpcmanager.NewUnixServer(manager, socketPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create UnixServer: %v", err)
|
||||
}
|
||||
|
||||
if err := server.Start(); err != nil {
|
||||
t.Fatalf("Failed to start UnixServer: %v", err)
|
||||
}
|
||||
defer server.Stop()
|
||||
|
||||
// Wait for server to start
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Create client
|
||||
client := &MockClient{
|
||||
BaseClient: BaseClient{
|
||||
socketPath: socketPath,
|
||||
secret: secret,
|
||||
},
|
||||
}
|
||||
|
||||
// Test Discover method
|
||||
t.Run("Discover", func(t *testing.T) {
|
||||
schema, err := client.Discover()
|
||||
if err != nil {
|
||||
t.Fatalf("Discover failed: %v", err)
|
||||
}
|
||||
|
||||
if schema.OpenRPC != "1.2.6" {
|
||||
t.Errorf("Expected OpenRPC version 1.2.6, got: %s", schema.OpenRPC)
|
||||
}
|
||||
|
||||
if len(schema.Methods) < 2 {
|
||||
t.Errorf("Expected at least 2 methods, got: %d", len(schema.Methods))
|
||||
}
|
||||
|
||||
// Check if our test methods are in the schema
|
||||
foundTestMethod := false
|
||||
foundSecureMethod := false
|
||||
for _, method := range schema.Methods {
|
||||
if method.Name == "test.method" {
|
||||
foundTestMethod = true
|
||||
}
|
||||
if method.Name == "secure.method" {
|
||||
foundSecureMethod = true
|
||||
}
|
||||
}
|
||||
|
||||
if !foundTestMethod {
|
||||
t.Error("test.method not found in schema")
|
||||
}
|
||||
if !foundSecureMethod {
|
||||
t.Error("secure.method not found in schema")
|
||||
}
|
||||
})
|
||||
|
||||
// Test TestMethod
|
||||
t.Run("TestMethod", func(t *testing.T) {
|
||||
greeting, err := client.TestMethod("World")
|
||||
if err != nil {
|
||||
t.Fatalf("TestMethod failed: %v", err)
|
||||
}
|
||||
|
||||
expected := "Hello, World!"
|
||||
if greeting != expected {
|
||||
t.Errorf("Expected greeting %q, got: %q", expected, greeting)
|
||||
}
|
||||
})
|
||||
|
||||
// Test Introspect method
|
||||
t.Run("Introspect", func(t *testing.T) {
|
||||
// Make several requests to generate logs
|
||||
_, err := client.TestMethod("World")
|
||||
if err != nil {
|
||||
t.Fatalf("TestMethod failed: %v", err)
|
||||
}
|
||||
|
||||
_, err = client.SecureMethod()
|
||||
if err != nil {
|
||||
t.Fatalf("SecureMethod failed: %v", err)
|
||||
}
|
||||
|
||||
// Test introspection
|
||||
response, err := client.Introspect(10, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Introspect failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify we have logs
|
||||
if response.Total < 2 {
|
||||
t.Errorf("Expected at least 2 logs, got: %d", response.Total)
|
||||
}
|
||||
|
||||
// Test filtering by method
|
||||
response, err = client.Introspect(10, "test.method", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Introspect with method filter failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify filtering works
|
||||
for _, log := range response.Logs {
|
||||
if log.Method != "test.method" {
|
||||
t.Errorf("Expected only test.method logs, got: %s", log.Method)
|
||||
}
|
||||
}
|
||||
|
||||
// Test filtering by status
|
||||
response, err = client.Introspect(10, "", "success")
|
||||
if err != nil {
|
||||
t.Fatalf("Introspect with status filter failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify status filtering works
|
||||
for _, log := range response.Logs {
|
||||
if log.Status != "success" {
|
||||
t.Errorf("Expected only success logs, got: %s", log.Status)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Test SecureMethod with valid secret
|
||||
t.Run("SecureMethod", func(t *testing.T) {
|
||||
data, err := client.SecureMethod()
|
||||
if err != nil {
|
||||
t.Fatalf("SecureMethod failed: %v", err)
|
||||
}
|
||||
|
||||
secure, ok := data["secure"].(bool)
|
||||
if !ok || !secure {
|
||||
t.Errorf("Expected secure to be true, got: %v", data["secure"])
|
||||
}
|
||||
|
||||
sensitiveData, ok := data["data"].(string)
|
||||
if !ok || sensitiveData != "sensitive information" {
|
||||
t.Errorf("Expected data to be 'sensitive information', got: %v", data["data"])
|
||||
}
|
||||
})
|
||||
|
||||
// Test SecureMethod with invalid secret
|
||||
t.Run("SecureMethod with invalid secret", func(t *testing.T) {
|
||||
invalidClient := &MockClient{
|
||||
BaseClient: BaseClient{
|
||||
socketPath: socketPath,
|
||||
secret: "wrong-secret",
|
||||
},
|
||||
}
|
||||
|
||||
_, err := invalidClient.SecureMethod()
|
||||
if err == nil {
|
||||
t.Error("Expected error for invalid secret, but got nil")
|
||||
}
|
||||
})
|
||||
|
||||
// Test non-existent method
|
||||
t.Run("Non-existent method", func(t *testing.T) {
|
||||
_, err := client.Request("non.existent", json.RawMessage("{}"), "")
|
||||
if err == nil {
|
||||
t.Error("Expected error for non-existent method, but got nil")
|
||||
}
|
||||
})
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/openrpcmanager"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Parse command line arguments
|
||||
socketPath := flag.String("socket", "/tmp/openrpc.sock", "Path to the Unix socket")
|
||||
secret := flag.String("secret", "test-secret", "Secret for authenticated methods")
|
||||
flag.Parse()
|
||||
|
||||
// Create a simple OpenRPC schema
|
||||
schema := openrpcmanager.OpenRPCSchema{
|
||||
OpenRPC: "1.2.6",
|
||||
Info: openrpcmanager.InfoObject{
|
||||
Title: "Hero Launcher API",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Methods: []openrpcmanager.MethodObject{
|
||||
{
|
||||
Name: "echo",
|
||||
Params: []openrpcmanager.ContentDescriptorObject{
|
||||
{
|
||||
Name: "message",
|
||||
Schema: openrpcmanager.SchemaObject{"type": "object"},
|
||||
},
|
||||
},
|
||||
Result: &openrpcmanager.ContentDescriptorObject{
|
||||
Name: "result",
|
||||
Schema: openrpcmanager.SchemaObject{"type": "object"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ping",
|
||||
Params: []openrpcmanager.ContentDescriptorObject{},
|
||||
Result: &openrpcmanager.ContentDescriptorObject{
|
||||
Name: "result",
|
||||
Schema: openrpcmanager.SchemaObject{"type": "string"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "secure.info",
|
||||
Params: []openrpcmanager.ContentDescriptorObject{},
|
||||
Result: &openrpcmanager.ContentDescriptorObject{
|
||||
Name: "result",
|
||||
Schema: openrpcmanager.SchemaObject{"type": "object"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create handlers
|
||||
handlers := map[string]openrpcmanager.RPCHandler{
|
||||
"echo": func(params json.RawMessage) (interface{}, error) {
|
||||
var data interface{}
|
||||
if err := json.Unmarshal(params, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
},
|
||||
"ping": func(params json.RawMessage) (interface{}, error) {
|
||||
return "pong", nil
|
||||
},
|
||||
"secure.info": func(params json.RawMessage) (interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"server": "Hero Launcher",
|
||||
"version": "1.0.0",
|
||||
"status": "running",
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
// Create OpenRPC manager
|
||||
manager, err := openrpcmanager.NewOpenRPCManager(schema, handlers, *secret)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create OpenRPC manager: %v", err)
|
||||
}
|
||||
|
||||
// Create Unix server
|
||||
server, err := openrpcmanager.NewUnixServer(manager, *socketPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create Unix server: %v", err)
|
||||
}
|
||||
|
||||
// Start the server
|
||||
if err := server.Start(); err != nil {
|
||||
log.Fatalf("Failed to start Unix server: %v", err)
|
||||
}
|
||||
defer server.Stop()
|
||||
|
||||
fmt.Printf("OpenRPC server started on Unix socket: %s\n", *socketPath)
|
||||
fmt.Println("Available methods:")
|
||||
for _, method := range manager.ListMethods() {
|
||||
fmt.Printf(" - %s\n", method)
|
||||
}
|
||||
fmt.Println("\nPress Ctrl+C to stop the server")
|
||||
|
||||
// Wait for interrupt signal
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigCh
|
||||
|
||||
fmt.Println("\nShutting down...")
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
// RPCRequest represents an outgoing RPC request
|
||||
type RPCRequest struct {
|
||||
Method string `json:"method"`
|
||||
Params json.RawMessage `json:"params"`
|
||||
ID interface{} `json:"id,omitempty"`
|
||||
Secret string `json:"secret,omitempty"`
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
}
|
||||
|
||||
// RPCResponse represents an incoming RPC response
|
||||
type RPCResponse struct {
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
Error *RPCError `json:"error,omitempty"`
|
||||
ID interface{} `json:"id,omitempty"`
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
}
|
||||
|
||||
// RPCError represents an RPC error
|
||||
type RPCError struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Parse command line arguments
|
||||
socketPath := flag.String("socket", "/tmp/openrpc.sock", "Path to the Unix socket")
|
||||
method := flag.String("method", "rpc.discover", "RPC method to call")
|
||||
params := flag.String("params", "{}", "JSON parameters for the method")
|
||||
secret := flag.String("secret", "", "Secret for authenticated methods")
|
||||
flag.Parse()
|
||||
|
||||
// Connect to the Unix socket
|
||||
conn, err := net.Dial("unix", *socketPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to connect to Unix socket: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Create the request
|
||||
var paramsJSON json.RawMessage
|
||||
if err := json.Unmarshal([]byte(*params), ¶msJSON); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Invalid JSON parameters: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
request := RPCRequest{
|
||||
Method: *method,
|
||||
Params: paramsJSON,
|
||||
ID: 1,
|
||||
Secret: *secret,
|
||||
JSONRPC: "2.0",
|
||||
}
|
||||
|
||||
// Marshal the request
|
||||
requestData, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to marshal request: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Send the request
|
||||
_, err = conn.Write(requestData)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to send request: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Read the response
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to read response: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Parse the response
|
||||
var response RPCResponse
|
||||
if err := json.Unmarshal(buf[:n], &response); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to unmarshal response: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check for errors
|
||||
if response.Error != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s (code: %d)\n", response.Error.Message, response.Error.Code)
|
||||
if response.Error.Data != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error data: %v\n", response.Error.Data)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Print the result
|
||||
resultJSON, err := json.MarshalIndent(response.Result, "", " ")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to marshal result: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println(string(resultJSON))
|
||||
}
|
@ -1,416 +0,0 @@
|
||||
// Package openrpcmanager provides functionality for managing and handling OpenRPC method calls.
|
||||
package openrpcmanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RPCHandler is a function that handles an OpenRPC method call
|
||||
type RPCHandler func(params json.RawMessage) (interface{}, error)
|
||||
|
||||
// CallLog represents a log entry for an RPC method call
|
||||
type CallLog struct {
|
||||
Timestamp time.Time `json:"timestamp"` // When the call was made
|
||||
Method string `json:"method"` // Method that was called
|
||||
Params interface{} `json:"params"` // Parameters passed to the method (may be redacted for security)
|
||||
Duration time.Duration `json:"duration"` // How long the call took to execute
|
||||
Status string `json:"status"` // Success or error
|
||||
ErrorMsg string `json:"error,omitempty"` // Error message if status is error
|
||||
Authenticated bool `json:"authenticated"` // Whether the call was authenticated
|
||||
}
|
||||
|
||||
// OpenRPCManager manages OpenRPC method handlers and processes requests
|
||||
type OpenRPCManager struct {
|
||||
handlers map[string]RPCHandler
|
||||
schema OpenRPCSchema
|
||||
mutex sync.RWMutex
|
||||
secret string
|
||||
|
||||
// Call logging
|
||||
callLogs []CallLog
|
||||
callLogsMutex sync.RWMutex
|
||||
maxCallLogs int // Maximum number of call logs to keep
|
||||
}
|
||||
|
||||
// NewOpenRPCManager creates a new OpenRPC manager with the given schema and handlers
|
||||
func NewOpenRPCManager(schema OpenRPCSchema, handlers map[string]RPCHandler, secret string) (*OpenRPCManager, error) {
|
||||
manager := &OpenRPCManager{
|
||||
handlers: make(map[string]RPCHandler),
|
||||
schema: schema,
|
||||
secret: secret,
|
||||
callLogs: make([]CallLog, 0, 100),
|
||||
maxCallLogs: 1000, // Default to keeping the last 1000 calls
|
||||
}
|
||||
|
||||
// Validate that all methods in the schema have corresponding handlers
|
||||
for _, method := range schema.Methods {
|
||||
handler, exists := handlers[method.Name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("missing handler for method '%s' defined in schema", method.Name)
|
||||
}
|
||||
manager.handlers[method.Name] = handler
|
||||
}
|
||||
|
||||
// Check for handlers that don't have a corresponding method in the schema
|
||||
for name := range handlers {
|
||||
found := false
|
||||
for _, method := range schema.Methods {
|
||||
if method.Name == name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, fmt.Errorf("handler '%s' has no corresponding method in schema", name)
|
||||
}
|
||||
}
|
||||
|
||||
// Add the discovery method
|
||||
manager.handlers["rpc.discover"] = manager.handleDiscovery
|
||||
|
||||
// Add the introspection method
|
||||
manager.handlers["rpc.introspect"] = manager.handleIntrospection
|
||||
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
// handleDiscovery implements the OpenRPC service discovery method
|
||||
func (m *OpenRPCManager) handleDiscovery(params json.RawMessage) (interface{}, error) {
|
||||
return m.schema, nil
|
||||
}
|
||||
|
||||
// handleIntrospection implements the OpenRPC service introspection method
|
||||
// It returns information about recent RPC calls for monitoring and debugging
|
||||
func (m *OpenRPCManager) handleIntrospection(params json.RawMessage) (interface{}, error) {
|
||||
m.callLogsMutex.RLock()
|
||||
defer m.callLogsMutex.RUnlock()
|
||||
|
||||
// Parse parameters to see if we need to filter or limit results
|
||||
var requestParams struct {
|
||||
Limit int `json:"limit,omitempty"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// Default limit to 100 if not specified
|
||||
requestParams.Limit = 100
|
||||
|
||||
// Try to parse parameters, but don't fail if they're invalid
|
||||
if len(params) > 0 {
|
||||
_ = json.Unmarshal(params, &requestParams)
|
||||
}
|
||||
|
||||
// Apply limit
|
||||
if requestParams.Limit <= 0 || requestParams.Limit > m.maxCallLogs {
|
||||
requestParams.Limit = 100
|
||||
}
|
||||
|
||||
// Create a copy of the logs to avoid holding the lock while filtering
|
||||
allLogs := make([]CallLog, len(m.callLogs))
|
||||
copy(allLogs, m.callLogs)
|
||||
|
||||
// Filter logs based on parameters
|
||||
filteredLogs := []CallLog{}
|
||||
for i := len(allLogs) - 1; i >= 0 && len(filteredLogs) < requestParams.Limit; i-- {
|
||||
log := allLogs[i]
|
||||
|
||||
// Apply method filter if specified
|
||||
if requestParams.Method != "" && log.Method != requestParams.Method {
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply status filter if specified
|
||||
if requestParams.Status != "" && log.Status != requestParams.Status {
|
||||
continue
|
||||
}
|
||||
|
||||
filteredLogs = append(filteredLogs, log)
|
||||
}
|
||||
|
||||
// Create response
|
||||
response := struct {
|
||||
Logs []CallLog `json:"logs"`
|
||||
Total int `json:"total"`
|
||||
Filtered int `json:"filtered"`
|
||||
}{
|
||||
Logs: filteredLogs,
|
||||
Total: len(allLogs),
|
||||
Filtered: len(filteredLogs),
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// RegisterHandler is deprecated. Use NewOpenRPCManager with a complete set of handlers instead.
|
||||
// This method is kept for backward compatibility but will return an error for methods not in the schema.
|
||||
func (m *OpenRPCManager) RegisterHandler(method string, handler RPCHandler) error {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
// Check if handler already exists
|
||||
if _, exists := m.handlers[method]; exists {
|
||||
return fmt.Errorf("handler for method '%s' already registered", method)
|
||||
}
|
||||
|
||||
// Check if method exists in schema
|
||||
found := false
|
||||
for _, schemaMethod := range m.schema.Methods {
|
||||
if schemaMethod.Name == method {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found && method != "rpc.discover" {
|
||||
return fmt.Errorf("method '%s' not defined in schema", method)
|
||||
}
|
||||
|
||||
m.handlers[method] = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnregisterHandler removes a handler for the specified method
|
||||
// Note: This will make the service non-compliant with its schema
|
||||
func (m *OpenRPCManager) UnregisterHandler(method string) error {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
// Check if handler exists
|
||||
if _, exists := m.handlers[method]; !exists {
|
||||
return fmt.Errorf("handler for method '%s' not found", method)
|
||||
}
|
||||
|
||||
// Don't allow unregistering the discovery method
|
||||
if method == "rpc.discover" {
|
||||
return fmt.Errorf("cannot unregister the discovery method 'rpc.discover'")
|
||||
}
|
||||
|
||||
delete(m.handlers, method)
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleRequest processes an RPC request for the specified method
|
||||
func (m *OpenRPCManager) HandleRequest(method string, params json.RawMessage) (interface{}, error) {
|
||||
// Start timing the request
|
||||
startTime := time.Now()
|
||||
|
||||
// Create a call log entry
|
||||
callLog := CallLog{
|
||||
Timestamp: startTime,
|
||||
Method: method,
|
||||
Authenticated: false,
|
||||
}
|
||||
|
||||
// Parse params for logging, but don't fail if we can't
|
||||
var parsedParams interface{}
|
||||
if len(params) > 0 {
|
||||
if err := json.Unmarshal(params, &parsedParams); err == nil {
|
||||
callLog.Params = parsedParams
|
||||
} else {
|
||||
// If we can't parse the params, just store them as a string
|
||||
callLog.Params = string(params)
|
||||
}
|
||||
}
|
||||
|
||||
// Find the handler
|
||||
m.mutex.RLock()
|
||||
handler, exists := m.handlers[method]
|
||||
m.mutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
// Log the error
|
||||
callLog.Status = "error"
|
||||
callLog.ErrorMsg = fmt.Sprintf("method '%s' not found", method)
|
||||
callLog.Duration = time.Since(startTime)
|
||||
|
||||
// Add to call logs
|
||||
m.logCall(callLog)
|
||||
|
||||
return nil, fmt.Errorf("method '%s' not found", method)
|
||||
}
|
||||
|
||||
// Execute the handler
|
||||
result, err := handler(params)
|
||||
|
||||
// Complete the call log
|
||||
callLog.Duration = time.Since(startTime)
|
||||
if err != nil {
|
||||
callLog.Status = "error"
|
||||
callLog.ErrorMsg = err.Error()
|
||||
} else {
|
||||
callLog.Status = "success"
|
||||
}
|
||||
|
||||
// Add to call logs
|
||||
m.logCall(callLog)
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
// logCall adds a call log entry to the call logs, maintaining the maximum size
|
||||
func (m *OpenRPCManager) logCall(log CallLog) {
|
||||
m.callLogsMutex.Lock()
|
||||
defer m.callLogsMutex.Unlock()
|
||||
|
||||
// Add the log to the call logs
|
||||
m.callLogs = append(m.callLogs, log)
|
||||
|
||||
// Trim the call logs if they exceed the maximum size
|
||||
if len(m.callLogs) > m.maxCallLogs {
|
||||
m.callLogs = m.callLogs[len(m.callLogs)-m.maxCallLogs:]
|
||||
}
|
||||
}
|
||||
|
||||
// HandleRequestWithAuthentication processes an authenticated RPC request
|
||||
func (m *OpenRPCManager) HandleRequestWithAuthentication(method string, params json.RawMessage, secret string) (interface{}, error) {
|
||||
// Start timing the request
|
||||
startTime := time.Now()
|
||||
|
||||
// Create a call log entry
|
||||
callLog := CallLog{
|
||||
Timestamp: startTime,
|
||||
Method: method,
|
||||
Authenticated: true,
|
||||
}
|
||||
|
||||
// Parse params for logging, but don't fail if we can't
|
||||
var parsedParams interface{}
|
||||
if len(params) > 0 {
|
||||
if err := json.Unmarshal(params, &parsedParams); err == nil {
|
||||
callLog.Params = parsedParams
|
||||
} else {
|
||||
// If we can't parse the params, just store them as a string
|
||||
callLog.Params = string(params)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the secret
|
||||
if secret != m.secret {
|
||||
// Log the authentication failure
|
||||
callLog.Status = "error"
|
||||
callLog.ErrorMsg = "authentication failed"
|
||||
callLog.Duration = time.Since(startTime)
|
||||
m.logCall(callLog)
|
||||
|
||||
return nil, fmt.Errorf("authentication failed")
|
||||
}
|
||||
|
||||
// Execute the handler
|
||||
m.mutex.RLock()
|
||||
handler, exists := m.handlers[method]
|
||||
m.mutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
// Log the error
|
||||
callLog.Status = "error"
|
||||
callLog.ErrorMsg = fmt.Sprintf("method '%s' not found", method)
|
||||
callLog.Duration = time.Since(startTime)
|
||||
m.logCall(callLog)
|
||||
|
||||
return nil, fmt.Errorf("method '%s' not found", method)
|
||||
}
|
||||
|
||||
// Execute the handler
|
||||
result, err := handler(params)
|
||||
|
||||
// Complete the call log
|
||||
callLog.Duration = time.Since(startTime)
|
||||
if err != nil {
|
||||
callLog.Status = "error"
|
||||
callLog.ErrorMsg = err.Error()
|
||||
} else {
|
||||
callLog.Status = "success"
|
||||
}
|
||||
|
||||
// Add to call logs
|
||||
m.logCall(callLog)
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
// ListMethods returns a list of all registered method names
|
||||
func (m *OpenRPCManager) ListMethods() []string {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
methods := make([]string, 0, len(m.handlers))
|
||||
for method := range m.handlers {
|
||||
methods = append(methods, method)
|
||||
}
|
||||
|
||||
return methods
|
||||
}
|
||||
|
||||
// GetSecret returns the authentication secret
|
||||
func (m *OpenRPCManager) GetSecret() string {
|
||||
return m.secret
|
||||
}
|
||||
|
||||
// GetSchema returns the OpenRPC schema
|
||||
func (m *OpenRPCManager) GetSchema() OpenRPCSchema {
|
||||
return m.schema
|
||||
}
|
||||
|
||||
// NewDefaultOpenRPCManager creates a new OpenRPC manager with a default schema
|
||||
// This is provided for backward compatibility and testing
|
||||
func NewDefaultOpenRPCManager(secret string) *OpenRPCManager {
|
||||
// Create a minimal default schema
|
||||
defaultSchema := OpenRPCSchema{
|
||||
OpenRPC: "1.2.6",
|
||||
Info: InfoObject{
|
||||
Title: "Default OpenRPC Service",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Methods: []MethodObject{
|
||||
{
|
||||
Name: "rpc.discover",
|
||||
Description: "Returns the OpenRPC schema for this service",
|
||||
Params: []ContentDescriptorObject{},
|
||||
Result: &ContentDescriptorObject{
|
||||
Name: "schema",
|
||||
Description: "The OpenRPC schema",
|
||||
Schema: SchemaObject{"type": "object"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "rpc.introspect",
|
||||
Description: "Returns information about recent RPC calls for monitoring and debugging",
|
||||
Params: []ContentDescriptorObject{
|
||||
{
|
||||
Name: "limit",
|
||||
Description: "Maximum number of call logs to return",
|
||||
Required: false,
|
||||
Schema: SchemaObject{"type": "integer", "default": 100},
|
||||
},
|
||||
{
|
||||
Name: "method",
|
||||
Description: "Filter logs by method name",
|
||||
Required: false,
|
||||
Schema: SchemaObject{"type": "string"},
|
||||
},
|
||||
{
|
||||
Name: "status",
|
||||
Description: "Filter logs by status (success or error)",
|
||||
Required: false,
|
||||
Schema: SchemaObject{"type": "string", "enum": []interface{}{"success", "error"}},
|
||||
},
|
||||
},
|
||||
Result: &ContentDescriptorObject{
|
||||
Name: "introspection",
|
||||
Description: "Introspection data including call logs",
|
||||
Schema: SchemaObject{"type": "object"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create the manager directly without validation since we're starting with empty methods
|
||||
return &OpenRPCManager{
|
||||
handlers: make(map[string]RPCHandler),
|
||||
schema: defaultSchema,
|
||||
secret: secret,
|
||||
}
|
||||
}
|
@ -1,446 +0,0 @@
|
||||
package openrpcmanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// createTestSchema creates a test OpenRPC schema
|
||||
func createTestSchema() OpenRPCSchema {
|
||||
return OpenRPCSchema{
|
||||
OpenRPC: "1.2.6",
|
||||
Info: InfoObject{
|
||||
Title: "Test API",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Methods: []MethodObject{
|
||||
{
|
||||
Name: "echo",
|
||||
Params: []ContentDescriptorObject{
|
||||
{
|
||||
Name: "message",
|
||||
Schema: SchemaObject{"type": "object"},
|
||||
},
|
||||
},
|
||||
Result: &ContentDescriptorObject{
|
||||
Name: "result",
|
||||
Schema: SchemaObject{"type": "object"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "add",
|
||||
Params: []ContentDescriptorObject{
|
||||
{
|
||||
Name: "numbers",
|
||||
Schema: SchemaObject{"type": "object"},
|
||||
},
|
||||
},
|
||||
Result: &ContentDescriptorObject{
|
||||
Name: "result",
|
||||
Schema: SchemaObject{"type": "number"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "secure.method",
|
||||
Params: []ContentDescriptorObject{},
|
||||
Result: &ContentDescriptorObject{
|
||||
Name: "result",
|
||||
Schema: SchemaObject{"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// createTestHandlers creates test handlers for the schema
|
||||
func createTestHandlers() map[string]RPCHandler {
|
||||
return map[string]RPCHandler{
|
||||
"echo": func(params json.RawMessage) (interface{}, error) {
|
||||
var data interface{}
|
||||
if err := json.Unmarshal(params, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
},
|
||||
"add": func(params json.RawMessage) (interface{}, error) {
|
||||
var numbers struct {
|
||||
A float64 `json:"a"`
|
||||
B float64 `json:"b"`
|
||||
}
|
||||
if err := json.Unmarshal(params, &numbers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return numbers.A + numbers.B, nil
|
||||
},
|
||||
"secure.method": func(params json.RawMessage) (interface{}, error) {
|
||||
return "secure data", nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewOpenRPCManager tests the creation of a new OpenRPC manager
|
||||
func TestNewOpenRPCManager(t *testing.T) {
|
||||
secret := "test-secret"
|
||||
schema := createTestSchema()
|
||||
handlers := createTestHandlers()
|
||||
|
||||
manager, err := NewOpenRPCManager(schema, handlers, secret)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create OpenRPCManager: %v", err)
|
||||
}
|
||||
|
||||
if manager == nil {
|
||||
t.Fatal("Manager is nil")
|
||||
}
|
||||
|
||||
if manager.GetSecret() != secret {
|
||||
t.Errorf("Secret mismatch. Expected: %s, Got: %s", secret, manager.GetSecret())
|
||||
}
|
||||
|
||||
if manager.handlers == nil {
|
||||
t.Error("handlers map not initialized")
|
||||
}
|
||||
|
||||
// Test the default manager for backward compatibility
|
||||
defaultManager := NewDefaultOpenRPCManager(secret)
|
||||
if defaultManager == nil {
|
||||
t.Fatal("Default manager is nil")
|
||||
}
|
||||
|
||||
if defaultManager.GetSecret() != secret {
|
||||
t.Errorf("Secret mismatch in default manager. Expected: %s, Got: %s", secret, defaultManager.GetSecret())
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterHandler tests registering a handler to the OpenRPC manager
|
||||
func TestRegisterHandler(t *testing.T) {
|
||||
manager := NewDefaultOpenRPCManager("test-secret")
|
||||
|
||||
// Define a mock handler
|
||||
mockHandler := func(params json.RawMessage) (interface{}, error) {
|
||||
return "mock response", nil
|
||||
}
|
||||
|
||||
// Add a method to the schema
|
||||
manager.schema.Methods = append(manager.schema.Methods, MethodObject{
|
||||
Name: "test.method",
|
||||
Params: []ContentDescriptorObject{},
|
||||
Result: &ContentDescriptorObject{
|
||||
Name: "result",
|
||||
Schema: SchemaObject{"type": "string"},
|
||||
},
|
||||
})
|
||||
|
||||
// Register the handler
|
||||
err := manager.RegisterHandler("test.method", mockHandler)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to register handler: %v", err)
|
||||
}
|
||||
|
||||
// Check if handler was registered
|
||||
if _, exists := manager.handlers["test.method"]; !exists {
|
||||
t.Error("Handler was not registered")
|
||||
}
|
||||
|
||||
// Try to register the same handler again, should fail
|
||||
err = manager.RegisterHandler("test.method", mockHandler)
|
||||
if err == nil {
|
||||
t.Error("Expected error when registering duplicate handler, but got nil")
|
||||
}
|
||||
|
||||
// Try to register a handler for a method not in the schema
|
||||
err = manager.RegisterHandler("not.in.schema", mockHandler)
|
||||
if err == nil {
|
||||
t.Error("Expected error when registering handler for method not in schema, but got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleRequest tests handling an RPC request
|
||||
func TestHandleRequest(t *testing.T) {
|
||||
schema := createTestSchema()
|
||||
handlers := createTestHandlers()
|
||||
manager, err := NewOpenRPCManager(schema, handlers, "test-secret")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create OpenRPCManager: %v", err)
|
||||
}
|
||||
|
||||
// Test the echo handler
|
||||
testParams := json.RawMessage(`{"message":"hello world"}`)
|
||||
result, err := manager.HandleRequest("echo", testParams)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to handle request: %v", err)
|
||||
}
|
||||
|
||||
// Convert result to map for comparison
|
||||
resultMap, ok := result.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("Expected map result, got: %T", result)
|
||||
}
|
||||
|
||||
if resultMap["message"] != "hello world" {
|
||||
t.Errorf("Expected 'hello world', got: %v", resultMap["message"])
|
||||
}
|
||||
|
||||
// Test the add handler
|
||||
addParams := json.RawMessage(`{"a":5,"b":7}`)
|
||||
addResult, err := manager.HandleRequest("add", addParams)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to handle add request: %v", err)
|
||||
}
|
||||
|
||||
// Check the result type and value
|
||||
resultValue, ok := addResult.(float64)
|
||||
if !ok {
|
||||
t.Fatalf("Expected result type float64, got: %T", addResult)
|
||||
}
|
||||
if resultValue != float64(12) {
|
||||
t.Errorf("Expected 12, got: %v", resultValue)
|
||||
}
|
||||
|
||||
// Test with non-existent method
|
||||
_, err = manager.HandleRequest("nonexistent", testParams)
|
||||
if err == nil {
|
||||
t.Error("Expected error for non-existent method, but got nil")
|
||||
}
|
||||
|
||||
// Test the discovery method
|
||||
discoveryResult, err := manager.HandleRequest("rpc.discover", json.RawMessage(`{}`))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to handle discovery request: %v", err)
|
||||
}
|
||||
|
||||
// Verify the discovery result is the schema
|
||||
discoverySchema, ok := discoveryResult.(OpenRPCSchema)
|
||||
if !ok {
|
||||
t.Fatalf("Expected OpenRPCSchema result, got: %T", discoveryResult)
|
||||
}
|
||||
|
||||
if discoverySchema.OpenRPC != schema.OpenRPC {
|
||||
t.Errorf("Expected OpenRPC version %s, got: %s", schema.OpenRPC, discoverySchema.OpenRPC)
|
||||
}
|
||||
|
||||
if len(discoverySchema.Methods) != len(schema.Methods) {
|
||||
t.Errorf("Expected %d methods, got: %d", len(schema.Methods), len(discoverySchema.Methods))
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleRequestWithAuthentication tests handling a request with authentication
|
||||
func TestHandleRequestWithAuthentication(t *testing.T) {
|
||||
secret := "test-secret"
|
||||
schema := createTestSchema()
|
||||
handlers := createTestHandlers()
|
||||
manager, err := NewOpenRPCManager(schema, handlers, secret)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create OpenRPCManager: %v", err)
|
||||
}
|
||||
|
||||
// Test with correct secret
|
||||
result, err := manager.HandleRequestWithAuthentication("secure.method", json.RawMessage(`{}`), secret)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to handle authenticated request: %v", err)
|
||||
}
|
||||
|
||||
if result != "secure data" {
|
||||
t.Errorf("Expected 'secure data', got: %v", result)
|
||||
}
|
||||
|
||||
// Test with incorrect secret
|
||||
_, err = manager.HandleRequestWithAuthentication("secure.method", json.RawMessage(`{}`), "wrong-secret")
|
||||
if err == nil {
|
||||
t.Error("Expected authentication error, but got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnregisterHandler tests removing a handler from the OpenRPC manager
|
||||
func TestUnregisterHandler(t *testing.T) {
|
||||
manager := NewDefaultOpenRPCManager("test-secret")
|
||||
|
||||
// Define a mock handler
|
||||
mockHandler := func(params json.RawMessage) (interface{}, error) {
|
||||
return "mock response", nil
|
||||
}
|
||||
|
||||
// Add a method to the schema
|
||||
manager.schema.Methods = append(manager.schema.Methods, MethodObject{
|
||||
Name: "test.method",
|
||||
Params: []ContentDescriptorObject{},
|
||||
Result: &ContentDescriptorObject{
|
||||
Name: "result",
|
||||
Schema: SchemaObject{"type": "string"},
|
||||
},
|
||||
})
|
||||
|
||||
// Register the handler
|
||||
manager.RegisterHandler("test.method", mockHandler)
|
||||
|
||||
// Unregister the handler
|
||||
err := manager.UnregisterHandler("test.method")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unregister handler: %v", err)
|
||||
}
|
||||
|
||||
// Check if handler was unregistered
|
||||
if _, exists := manager.handlers["test.method"]; exists {
|
||||
t.Error("Handler was not unregistered")
|
||||
}
|
||||
|
||||
// Try to unregister non-existent handler
|
||||
err = manager.UnregisterHandler("nonexistent")
|
||||
if err == nil {
|
||||
t.Error("Expected error when unregistering non-existent handler, but got nil")
|
||||
}
|
||||
|
||||
// Try to unregister the discovery method
|
||||
err = manager.UnregisterHandler("rpc.discover")
|
||||
if err == nil {
|
||||
t.Error("Expected error when unregistering discovery method, but got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIntrospection tests the introspection functionality
|
||||
func TestIntrospection(t *testing.T) {
|
||||
// Create a test manager
|
||||
schema := createTestSchema()
|
||||
handlers := createTestHandlers()
|
||||
manager, err := NewOpenRPCManager(schema, handlers, "test-secret")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// The introspection handler is already registered in NewOpenRPCManager
|
||||
|
||||
// Make some test calls to generate logs
|
||||
_, err = manager.HandleRequest("echo", json.RawMessage(`{"message":"hello"}`))
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = manager.HandleRequestWithAuthentication("echo", json.RawMessage(`{"message":"authenticated"}`), "test-secret")
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = manager.HandleRequestWithAuthentication("echo", json.RawMessage(`{"message":"auth-fail"}`), "wrong-secret")
|
||||
assert.Error(t, err)
|
||||
|
||||
// Wait a moment to ensure timestamps are different
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Call the introspection handler
|
||||
result, err := manager.handleIntrospection(json.RawMessage(`{"limit":10}`))
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify the result
|
||||
response, ok := result.(struct {
|
||||
Logs []CallLog `json:"logs"`
|
||||
Total int `json:"total"`
|
||||
Filtered int `json:"filtered"`
|
||||
})
|
||||
assert.True(t, ok)
|
||||
|
||||
// Should have 3 logs (2 successful calls, 1 auth failure)
|
||||
assert.Equal(t, 3, response.Total)
|
||||
assert.Equal(t, 3, response.Filtered)
|
||||
assert.Len(t, response.Logs, 3)
|
||||
|
||||
// Test filtering by method
|
||||
result, err = manager.handleIntrospection(json.RawMessage(`{"method":"echo"}`))
|
||||
assert.NoError(t, err)
|
||||
response, ok = result.(struct {
|
||||
Logs []CallLog `json:"logs"`
|
||||
Total int `json:"total"`
|
||||
Filtered int `json:"filtered"`
|
||||
})
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 3, response.Total) // Total is still 3
|
||||
assert.Equal(t, 3, response.Filtered) // All 3 match the method filter
|
||||
|
||||
// Test filtering by status
|
||||
result, err = manager.handleIntrospection(json.RawMessage(`{"status":"error"}`))
|
||||
assert.NoError(t, err)
|
||||
response, ok = result.(struct {
|
||||
Logs []CallLog `json:"logs"`
|
||||
Total int `json:"total"`
|
||||
Filtered int `json:"filtered"`
|
||||
})
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 3, response.Total) // Total is still 3
|
||||
assert.Equal(t, 1, response.Filtered) // Only 1 error
|
||||
assert.Len(t, response.Logs, 1)
|
||||
assert.Equal(t, "error", response.Logs[0].Status)
|
||||
}
|
||||
|
||||
// TestListMethods tests listing all registered methods
|
||||
func TestListMethods(t *testing.T) {
|
||||
schema := createTestSchema()
|
||||
handlers := createTestHandlers()
|
||||
manager, err := NewOpenRPCManager(schema, handlers, "test-secret")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create OpenRPCManager: %v", err)
|
||||
}
|
||||
|
||||
// List all methods
|
||||
registeredMethods := manager.ListMethods()
|
||||
|
||||
// Check if all methods plus discovery and introspection methods are listed
|
||||
expectedCount := len(schema.Methods) + 2 // +2 for rpc.discover and rpc.introspect
|
||||
if len(registeredMethods) != expectedCount {
|
||||
t.Errorf("Expected %d methods, got %d", expectedCount, len(registeredMethods))
|
||||
}
|
||||
|
||||
// Check if all schema methods are in the list
|
||||
for _, methodObj := range schema.Methods {
|
||||
found := false
|
||||
for _, registeredMethod := range registeredMethods {
|
||||
if registeredMethod == methodObj.Name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Method %s not found in list", methodObj.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if discovery method is in the list
|
||||
found := false
|
||||
for _, registeredMethod := range registeredMethods {
|
||||
if registeredMethod == "rpc.discover" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("Discovery method 'rpc.discover' not found in list")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSchemaValidation tests that the schema validation works correctly
|
||||
func TestSchemaValidation(t *testing.T) {
|
||||
secret := "test-secret"
|
||||
schema := createTestSchema()
|
||||
|
||||
// Test with missing handler
|
||||
incompleteHandlers := map[string]RPCHandler{
|
||||
"echo": func(params json.RawMessage) (interface{}, error) {
|
||||
return nil, nil
|
||||
},
|
||||
// Missing "add" handler
|
||||
"secure.method": func(params json.RawMessage) (interface{}, error) {
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := NewOpenRPCManager(schema, incompleteHandlers, secret)
|
||||
if err == nil {
|
||||
t.Error("Expected error when missing handler for schema method, but got nil")
|
||||
}
|
||||
|
||||
// Test with extra handler not in schema
|
||||
extraHandlers := createTestHandlers()
|
||||
extraHandlers["not.in.schema"] = func(params json.RawMessage) (interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
_, err = NewOpenRPCManager(schema, extraHandlers, secret)
|
||||
if err == nil {
|
||||
t.Error("Expected error when handler has no corresponding method in schema, but got nil")
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
package openrpcmanager
|
||||
|
||||
// OpenRPCSchema represents the OpenRPC specification document
|
||||
// Based on OpenRPC Specification 1.2.6: https://spec.open-rpc.org/
|
||||
type OpenRPCSchema struct {
|
||||
OpenRPC string `json:"openrpc"` // Required: Version of the OpenRPC specification
|
||||
Info InfoObject `json:"info"` // Required: Information about the API
|
||||
Methods []MethodObject `json:"methods"` // Required: List of method objects
|
||||
ExternalDocs *ExternalDocsObject `json:"externalDocs,omitempty"` // Optional: External documentation
|
||||
Servers []ServerObject `json:"servers,omitempty"` // Optional: List of servers
|
||||
Components *ComponentsObject `json:"components,omitempty"` // Optional: Reusable components
|
||||
}
|
||||
|
||||
// InfoObject provides metadata about the API
|
||||
type InfoObject struct {
|
||||
Title string `json:"title"` // Required: Title of the API
|
||||
Description string `json:"description,omitempty"` // Optional: Description of the API
|
||||
Version string `json:"version"` // Required: Version of the API
|
||||
TermsOfService string `json:"termsOfService,omitempty"` // Optional: Terms of service URL
|
||||
Contact *ContactObject `json:"contact,omitempty"` // Optional: Contact information
|
||||
License *LicenseObject `json:"license,omitempty"` // Optional: License information
|
||||
}
|
||||
|
||||
// ContactObject provides contact information for the API
|
||||
type ContactObject struct {
|
||||
Name string `json:"name,omitempty"` // Optional: Name of the contact
|
||||
URL string `json:"url,omitempty"` // Optional: URL of the contact
|
||||
Email string `json:"email,omitempty"` // Optional: Email of the contact
|
||||
}
|
||||
|
||||
// LicenseObject provides license information for the API
|
||||
type LicenseObject struct {
|
||||
Name string `json:"name"` // Required: Name of the license
|
||||
URL string `json:"url,omitempty"` // Optional: URL of the license
|
||||
}
|
||||
|
||||
// ExternalDocsObject provides a URL to external documentation
|
||||
type ExternalDocsObject struct {
|
||||
Description string `json:"description,omitempty"` // Optional: Description of the external docs
|
||||
URL string `json:"url"` // Required: URL of the external docs
|
||||
}
|
||||
|
||||
// ServerObject provides connection information to a server
|
||||
type ServerObject struct {
|
||||
Name string `json:"name,omitempty"` // Optional: Name of the server
|
||||
Description string `json:"description,omitempty"` // Optional: Description of the server
|
||||
URL string `json:"url"` // Required: URL of the server
|
||||
Variables map[string]ServerVariable `json:"variables,omitempty"` // Optional: Server variables
|
||||
}
|
||||
|
||||
// ServerVariable is a variable for server URL template substitution
|
||||
type ServerVariable struct {
|
||||
Default string `json:"default"` // Required: Default value of the variable
|
||||
Description string `json:"description,omitempty"` // Optional: Description of the variable
|
||||
Enum []string `json:"enum,omitempty"` // Optional: Enumeration of possible values
|
||||
}
|
||||
|
||||
// MethodObject describes an RPC method
|
||||
type MethodObject struct {
|
||||
Name string `json:"name"` // Required: Name of the method
|
||||
Description string `json:"description,omitempty"` // Optional: Description of the method
|
||||
Summary string `json:"summary,omitempty"` // Optional: Summary of the method
|
||||
Params []ContentDescriptorObject `json:"params"` // Required: List of parameters
|
||||
Result *ContentDescriptorObject `json:"result"` // Required: Description of the result
|
||||
Deprecated bool `json:"deprecated,omitempty"` // Optional: Whether the method is deprecated
|
||||
Errors []ErrorObject `json:"errors,omitempty"` // Optional: List of possible errors
|
||||
Tags []TagObject `json:"tags,omitempty"` // Optional: List of tags
|
||||
ExternalDocs *ExternalDocsObject `json:"externalDocs,omitempty"` // Optional: External documentation
|
||||
ParamStructure string `json:"paramStructure,omitempty"` // Optional: Structure of the parameters
|
||||
}
|
||||
|
||||
// ContentDescriptorObject describes the content of a parameter or result
|
||||
type ContentDescriptorObject struct {
|
||||
Name string `json:"name"` // Required: Name of the parameter
|
||||
Description string `json:"description,omitempty"` // Optional: Description of the parameter
|
||||
Summary string `json:"summary,omitempty"` // Optional: Summary of the parameter
|
||||
Required bool `json:"required,omitempty"` // Optional: Whether the parameter is required
|
||||
Deprecated bool `json:"deprecated,omitempty"` // Optional: Whether the parameter is deprecated
|
||||
Schema SchemaObject `json:"schema"` // Required: JSON Schema of the parameter
|
||||
}
|
||||
|
||||
// SchemaObject is a JSON Schema definition
|
||||
// This is a simplified version, in a real implementation you would use a full JSON Schema library
|
||||
type SchemaObject map[string]interface{}
|
||||
|
||||
// ErrorObject describes an error that may be returned
|
||||
type ErrorObject struct {
|
||||
Code int `json:"code"` // Required: Error code
|
||||
Message string `json:"message"` // Required: Error message
|
||||
Data interface{} `json:"data,omitempty"` // Optional: Additional error data
|
||||
}
|
||||
|
||||
// TagObject describes a tag for documentation purposes
|
||||
type TagObject struct {
|
||||
Name string `json:"name"` // Required: Name of the tag
|
||||
Description string `json:"description,omitempty"` // Optional: Description of the tag
|
||||
ExternalDocs *ExternalDocsObject `json:"externalDocs,omitempty"` // Optional: External documentation
|
||||
}
|
||||
|
||||
// ComponentsObject holds reusable objects for different aspects of the OpenRPC spec
|
||||
type ComponentsObject struct {
|
||||
Schemas map[string]SchemaObject `json:"schemas,omitempty"` // Optional: Reusable schemas
|
||||
ContentDescriptors map[string]ContentDescriptorObject `json:"contentDescriptors,omitempty"` // Optional: Reusable content descriptors
|
||||
Examples map[string]interface{} `json:"examples,omitempty"` // Optional: Reusable examples
|
||||
Links map[string]LinkObject `json:"links,omitempty"` // Optional: Reusable links
|
||||
Errors map[string]ErrorObject `json:"errors,omitempty"` // Optional: Reusable errors
|
||||
}
|
||||
|
||||
// LinkObject describes a link between operations
|
||||
type LinkObject struct {
|
||||
Name string `json:"name,omitempty"` // Optional: Name of the link
|
||||
Description string `json:"description,omitempty"` // Optional: Description of the link
|
||||
Summary string `json:"summary,omitempty"` // Optional: Summary of the link
|
||||
Method string `json:"method"` // Required: Method name
|
||||
Params map[string]interface{} `json:"params,omitempty"` // Optional: Parameters for the method
|
||||
Server *ServerObject `json:"server,omitempty"` // Optional: Server for the method
|
||||
}
|
@ -1,241 +0,0 @@
|
||||
package openrpcmanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// RPCRequest represents an incoming RPC request
|
||||
type RPCRequest struct {
|
||||
Method string `json:"method"`
|
||||
Params json.RawMessage `json:"params"`
|
||||
ID interface{} `json:"id,omitempty"`
|
||||
Secret string `json:"secret,omitempty"`
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
}
|
||||
|
||||
// RPCResponse represents an outgoing RPC response
|
||||
type RPCResponse struct {
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
Error *RPCError `json:"error,omitempty"`
|
||||
ID interface{} `json:"id,omitempty"`
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
}
|
||||
|
||||
// RPCError represents an RPC error
|
||||
type RPCError struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// UnixServer represents a Unix socket server for the OpenRPC manager
|
||||
type UnixServer struct {
|
||||
manager *OpenRPCManager
|
||||
socketPath string
|
||||
listener net.Listener
|
||||
connections map[net.Conn]bool
|
||||
mutex sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// NewUnixServer creates a new Unix socket server for the OpenRPC manager
|
||||
func NewUnixServer(manager *OpenRPCManager, socketPath string) (*UnixServer, error) {
|
||||
// Create directory if it doesn't exist
|
||||
dir := filepath.Dir(socketPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create socket directory: %w", err)
|
||||
}
|
||||
|
||||
// Remove socket if it already exists
|
||||
if _, err := os.Stat(socketPath); err == nil {
|
||||
if err := os.Remove(socketPath); err != nil {
|
||||
return nil, fmt.Errorf("failed to remove existing socket: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &UnixServer{
|
||||
manager: manager,
|
||||
socketPath: socketPath,
|
||||
connections: make(map[net.Conn]bool),
|
||||
done: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start starts the Unix socket server
|
||||
func (s *UnixServer) Start() error {
|
||||
listener, err := net.Listen("unix", s.socketPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen on unix socket: %w", err)
|
||||
}
|
||||
s.listener = listener
|
||||
|
||||
// Set socket permissions
|
||||
if err := os.Chmod(s.socketPath, 0660); err != nil {
|
||||
s.listener.Close()
|
||||
return fmt.Errorf("failed to set socket permissions: %w", err)
|
||||
}
|
||||
|
||||
s.wg.Add(1)
|
||||
go s.acceptConnections()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the Unix socket server
|
||||
func (s *UnixServer) Stop() error {
|
||||
close(s.done)
|
||||
|
||||
// Close the listener
|
||||
if s.listener != nil {
|
||||
s.listener.Close()
|
||||
}
|
||||
|
||||
// Close all connections
|
||||
s.mutex.Lock()
|
||||
for conn := range s.connections {
|
||||
conn.Close()
|
||||
}
|
||||
s.mutex.Unlock()
|
||||
|
||||
// Wait for all goroutines to finish
|
||||
s.wg.Wait()
|
||||
|
||||
// Remove the socket file
|
||||
os.Remove(s.socketPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// acceptConnections accepts incoming connections
|
||||
func (s *UnixServer) acceptConnections() {
|
||||
defer s.wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.done:
|
||||
return
|
||||
default:
|
||||
conn, err := s.listener.Accept()
|
||||
if err != nil {
|
||||
select {
|
||||
case <-s.done:
|
||||
return
|
||||
default:
|
||||
fmt.Printf("Error accepting connection: %v\n", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
s.mutex.Lock()
|
||||
s.connections[conn] = true
|
||||
s.mutex.Unlock()
|
||||
|
||||
s.wg.Add(1)
|
||||
go s.handleConnection(conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleConnection handles a client connection
|
||||
func (s *UnixServer) handleConnection(conn net.Conn) {
|
||||
defer func() {
|
||||
s.mutex.Lock()
|
||||
delete(s.connections, conn)
|
||||
s.mutex.Unlock()
|
||||
conn.Close()
|
||||
s.wg.Done()
|
||||
}()
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
select {
|
||||
case <-s.done:
|
||||
return
|
||||
default:
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
fmt.Printf("Error reading from connection: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
go s.handleRequest(conn, buf[:n])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleRequest processes an RPC request
|
||||
func (s *UnixServer) handleRequest(conn net.Conn, data []byte) {
|
||||
var req RPCRequest
|
||||
if err := json.Unmarshal(data, &req); err != nil {
|
||||
s.sendErrorResponse(conn, nil, -32700, "Parse error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate JSON-RPC version
|
||||
if req.JSONRPC != "2.0" {
|
||||
s.sendErrorResponse(conn, req.ID, -32600, "Invalid Request", "Invalid JSON-RPC version")
|
||||
return
|
||||
}
|
||||
|
||||
var result interface{}
|
||||
var err error
|
||||
|
||||
// Check if authentication is required
|
||||
if req.Secret != "" {
|
||||
result, err = s.manager.HandleRequestWithAuthentication(req.Method, req.Params, req.Secret)
|
||||
} else {
|
||||
result, err = s.manager.HandleRequest(req.Method, req.Params)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.sendErrorResponse(conn, req.ID, -32603, "Internal error", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Send success response
|
||||
response := RPCResponse{
|
||||
Result: result,
|
||||
ID: req.ID,
|
||||
JSONRPC: "2.0",
|
||||
}
|
||||
|
||||
responseData, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
s.sendErrorResponse(conn, req.ID, -32603, "Internal error", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
conn.Write(responseData)
|
||||
}
|
||||
|
||||
// sendErrorResponse sends an error response
|
||||
func (s *UnixServer) sendErrorResponse(conn net.Conn, id interface{}, code int, message string, data interface{}) {
|
||||
response := RPCResponse{
|
||||
Error: &RPCError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Data: data,
|
||||
},
|
||||
ID: id,
|
||||
JSONRPC: "2.0",
|
||||
}
|
||||
|
||||
responseData, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
fmt.Printf("Error marshaling error response: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
conn.Write(responseData)
|
||||
}
|
@ -1,362 +0,0 @@
|
||||
package openrpcmanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestUnixServer(t *testing.T) {
|
||||
// Create a temporary socket path
|
||||
tempDir, err := os.MkdirTemp("", "openrpc-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
socketPath := filepath.Join(tempDir, "openrpc.sock")
|
||||
|
||||
// Create OpenRPC manager
|
||||
schema := createTestSchema()
|
||||
handlers := createTestHandlers()
|
||||
manager, err := NewOpenRPCManager(schema, handlers, "test-secret")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create OpenRPCManager: %v", err)
|
||||
}
|
||||
|
||||
// Create and start Unix server
|
||||
server, err := NewUnixServer(manager, socketPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create UnixServer: %v", err)
|
||||
}
|
||||
|
||||
if err := server.Start(); err != nil {
|
||||
t.Fatalf("Failed to start UnixServer: %v", err)
|
||||
}
|
||||
defer server.Stop()
|
||||
|
||||
// Wait for server to start
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Test connection
|
||||
conn, err := net.Dial("unix", socketPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to connect to Unix socket: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Test echo method
|
||||
t.Run("Echo method", func(t *testing.T) {
|
||||
request := RPCRequest{
|
||||
Method: "echo",
|
||||
Params: json.RawMessage(`{"message":"hello world"}`),
|
||||
ID: 1,
|
||||
JSONRPC: "2.0",
|
||||
}
|
||||
|
||||
requestData, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal request: %v", err)
|
||||
}
|
||||
|
||||
_, err = conn.Write(requestData)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to send request: %v", err)
|
||||
}
|
||||
|
||||
// Read response
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read response: %v", err)
|
||||
}
|
||||
|
||||
var response RPCResponse
|
||||
if err := json.Unmarshal(buf[:n], &response); err != nil {
|
||||
t.Fatalf("Failed to unmarshal response: %v", err)
|
||||
}
|
||||
|
||||
// Check response
|
||||
if response.Error != nil {
|
||||
t.Fatalf("Received error response: %v", response.Error)
|
||||
}
|
||||
|
||||
// Note: JSON unmarshaling may convert numbers to float64, so we need to check the value not exact type
|
||||
if fmt.Sprintf("%v", response.ID) != fmt.Sprintf("%v", request.ID) {
|
||||
t.Errorf("Response ID mismatch. Expected: %v, Got: %v", request.ID, response.ID)
|
||||
}
|
||||
|
||||
// Check result
|
||||
resultMap, ok := response.Result.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("Expected map result, got: %T", response.Result)
|
||||
}
|
||||
|
||||
if resultMap["message"] != "hello world" {
|
||||
t.Errorf("Expected 'hello world', got: %v", resultMap["message"])
|
||||
}
|
||||
})
|
||||
|
||||
// Test add method
|
||||
t.Run("Add method", func(t *testing.T) {
|
||||
request := RPCRequest{
|
||||
Method: "add",
|
||||
Params: json.RawMessage(`{"a":5,"b":7}`),
|
||||
ID: 2,
|
||||
JSONRPC: "2.0",
|
||||
}
|
||||
|
||||
requestData, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal request: %v", err)
|
||||
}
|
||||
|
||||
_, err = conn.Write(requestData)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to send request: %v", err)
|
||||
}
|
||||
|
||||
// Read response
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read response: %v", err)
|
||||
}
|
||||
|
||||
var response RPCResponse
|
||||
if err := json.Unmarshal(buf[:n], &response); err != nil {
|
||||
t.Fatalf("Failed to unmarshal response: %v", err)
|
||||
}
|
||||
|
||||
// Check response
|
||||
if response.Error != nil {
|
||||
t.Fatalf("Received error response: %v", response.Error)
|
||||
}
|
||||
|
||||
// Note: JSON unmarshaling may convert numbers to float64, so we need to check the value not exact type
|
||||
if fmt.Sprintf("%v", response.ID) != fmt.Sprintf("%v", request.ID) {
|
||||
t.Errorf("Response ID mismatch. Expected: %v, Got: %v", request.ID, response.ID)
|
||||
}
|
||||
|
||||
// Check result
|
||||
resultValue, ok := response.Result.(float64)
|
||||
if !ok {
|
||||
t.Fatalf("Expected float64 result, got: %T", response.Result)
|
||||
}
|
||||
|
||||
if resultValue != float64(12) {
|
||||
t.Errorf("Expected 12, got: %v", resultValue)
|
||||
}
|
||||
})
|
||||
|
||||
// Test authenticated method
|
||||
t.Run("Authenticated method", func(t *testing.T) {
|
||||
request := RPCRequest{
|
||||
Method: "secure.method",
|
||||
Params: json.RawMessage(`{}`),
|
||||
ID: 3,
|
||||
Secret: "test-secret",
|
||||
JSONRPC: "2.0",
|
||||
}
|
||||
|
||||
requestData, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal request: %v", err)
|
||||
}
|
||||
|
||||
_, err = conn.Write(requestData)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to send request: %v", err)
|
||||
}
|
||||
|
||||
// Read response
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read response: %v", err)
|
||||
}
|
||||
|
||||
var response RPCResponse
|
||||
if err := json.Unmarshal(buf[:n], &response); err != nil {
|
||||
t.Fatalf("Failed to unmarshal response: %v", err)
|
||||
}
|
||||
|
||||
// Check response
|
||||
if response.Error != nil {
|
||||
t.Fatalf("Received error response: %v", response.Error)
|
||||
}
|
||||
|
||||
// Note: JSON unmarshaling may convert numbers to float64, so we need to check the value not exact type
|
||||
if fmt.Sprintf("%v", response.ID) != fmt.Sprintf("%v", request.ID) {
|
||||
t.Errorf("Response ID mismatch. Expected: %v, Got: %v", request.ID, response.ID)
|
||||
}
|
||||
|
||||
// Check result
|
||||
resultValue, ok := response.Result.(string)
|
||||
if !ok {
|
||||
t.Fatalf("Expected string result, got: %T", response.Result)
|
||||
}
|
||||
|
||||
if resultValue != "secure data" {
|
||||
t.Errorf("Expected 'secure data', got: %v", resultValue)
|
||||
}
|
||||
})
|
||||
|
||||
// Test authentication failure
|
||||
t.Run("Authentication failure", func(t *testing.T) {
|
||||
request := RPCRequest{
|
||||
Method: "secure.method",
|
||||
Params: json.RawMessage(`{}`),
|
||||
ID: 4,
|
||||
Secret: "wrong-secret",
|
||||
JSONRPC: "2.0",
|
||||
}
|
||||
|
||||
requestData, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal request: %v", err)
|
||||
}
|
||||
|
||||
_, err = conn.Write(requestData)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to send request: %v", err)
|
||||
}
|
||||
|
||||
// Read response
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read response: %v", err)
|
||||
}
|
||||
|
||||
var response RPCResponse
|
||||
if err := json.Unmarshal(buf[:n], &response); err != nil {
|
||||
t.Fatalf("Failed to unmarshal response: %v", err)
|
||||
}
|
||||
|
||||
// Check response
|
||||
if response.Error == nil {
|
||||
t.Fatal("Expected error response, but got nil")
|
||||
}
|
||||
|
||||
// Note: JSON unmarshaling may convert numbers to float64, so we need to check the value not exact type
|
||||
if fmt.Sprintf("%v", response.ID) != fmt.Sprintf("%v", request.ID) {
|
||||
t.Errorf("Response ID mismatch. Expected: %v, Got: %v", request.ID, response.ID)
|
||||
}
|
||||
|
||||
if response.Error.Code != -32603 {
|
||||
t.Errorf("Expected error code -32603, got: %v", response.Error.Code)
|
||||
}
|
||||
})
|
||||
|
||||
// Test non-existent method
|
||||
t.Run("Non-existent method", func(t *testing.T) {
|
||||
request := RPCRequest{
|
||||
Method: "nonexistent",
|
||||
Params: json.RawMessage(`{}`),
|
||||
ID: 5,
|
||||
JSONRPC: "2.0",
|
||||
}
|
||||
|
||||
requestData, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal request: %v", err)
|
||||
}
|
||||
|
||||
_, err = conn.Write(requestData)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to send request: %v", err)
|
||||
}
|
||||
|
||||
// Read response
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read response: %v", err)
|
||||
}
|
||||
|
||||
var response RPCResponse
|
||||
if err := json.Unmarshal(buf[:n], &response); err != nil {
|
||||
t.Fatalf("Failed to unmarshal response: %v", err)
|
||||
}
|
||||
|
||||
// Check response
|
||||
if response.Error == nil {
|
||||
t.Fatal("Expected error response, but got nil")
|
||||
}
|
||||
|
||||
// Note: JSON unmarshaling may convert numbers to float64, so we need to check the value not exact type
|
||||
if fmt.Sprintf("%v", response.ID) != fmt.Sprintf("%v", request.ID) {
|
||||
t.Errorf("Response ID mismatch. Expected: %v, Got: %v", request.ID, response.ID)
|
||||
}
|
||||
|
||||
if response.Error.Code != -32603 {
|
||||
t.Errorf("Expected error code -32603, got: %v", response.Error.Code)
|
||||
}
|
||||
})
|
||||
|
||||
// Test discovery method
|
||||
t.Run("Discovery method", func(t *testing.T) {
|
||||
request := RPCRequest{
|
||||
Method: "rpc.discover",
|
||||
Params: json.RawMessage(`{}`),
|
||||
ID: 6,
|
||||
JSONRPC: "2.0",
|
||||
}
|
||||
|
||||
requestData, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal request: %v", err)
|
||||
}
|
||||
|
||||
_, err = conn.Write(requestData)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to send request: %v", err)
|
||||
}
|
||||
|
||||
// Read response
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read response: %v", err)
|
||||
}
|
||||
|
||||
var response RPCResponse
|
||||
if err := json.Unmarshal(buf[:n], &response); err != nil {
|
||||
t.Fatalf("Failed to unmarshal response: %v", err)
|
||||
}
|
||||
|
||||
// Check response
|
||||
if response.Error != nil {
|
||||
t.Fatalf("Received error response: %v", response.Error)
|
||||
}
|
||||
|
||||
// Note: JSON unmarshaling may convert numbers to float64, so we need to check the value not exact type
|
||||
if fmt.Sprintf("%v", response.ID) != fmt.Sprintf("%v", request.ID) {
|
||||
t.Errorf("Response ID mismatch. Expected: %v, Got: %v", request.ID, response.ID)
|
||||
}
|
||||
|
||||
// Check that we got a valid schema
|
||||
resultMap, ok := response.Result.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("Expected map result, got: %T", response.Result)
|
||||
}
|
||||
|
||||
if resultMap["openrpc"] != "1.2.6" {
|
||||
t.Errorf("Expected OpenRPC version 1.2.6, got: %v", resultMap["openrpc"])
|
||||
}
|
||||
|
||||
methods, ok := resultMap["methods"].([]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("Expected methods array, got: %T", resultMap["methods"])
|
||||
}
|
||||
|
||||
if len(methods) < 3 {
|
||||
t.Errorf("Expected at least 3 methods, got: %d", len(methods))
|
||||
}
|
||||
})
|
||||
}
|
@ -5,8 +5,8 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces/openrpc"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces/openrpc"
|
||||
)
|
||||
|
||||
// RunClientExample runs a complete example of using the process manager OpenRPC client
|
||||
@ -39,10 +39,10 @@ func RunClientExample(socketPath, secret string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list processes: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// For simplicity in this example, just log that we got a response
|
||||
log.Printf("Got process list response: %T", processList)
|
||||
|
||||
|
||||
// Try to handle the response in a more robust way
|
||||
switch v := processList.(type) {
|
||||
case []interface{}:
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces/openrpc"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces/openrpc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -6,8 +6,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces"
|
||||
)
|
||||
|
||||
// MockProcessManager implements the interfaces.ProcessManagerInterface for testing purposes
|
||||
|
@ -4,8 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/openrpcmanager/client"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/openrpcmanager/client"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces"
|
||||
)
|
||||
|
||||
// Client provides a client for interacting with process manager operations via RPC
|
||||
|
@ -4,9 +4,9 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/openrpcmanager"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/openrpcmanager"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces"
|
||||
)
|
||||
|
||||
// Handler implements the OpenRPC handlers for process manager operations
|
||||
|
@ -6,8 +6,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/openrpcmanager"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/openrpcmanager"
|
||||
)
|
||||
|
||||
// LoadSchema loads the OpenRPC schema from the embedded JSON file
|
||||
|
@ -6,8 +6,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/openrpcmanager"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/openrpcmanager"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces"
|
||||
)
|
||||
|
||||
// Server represents the Process Manager OpenRPC server
|
||||
|
@ -3,7 +3,7 @@ package interfaces
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/processmanager"
|
||||
)
|
||||
|
||||
// ProcessManagerInterface defines the interface for process management operations
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/logger"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/logger"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
)
|
||||
|
||||
|
@ -24,7 +24,7 @@ The server implements the following Redis commands:
|
||||
### Basic Usage
|
||||
|
||||
```go
|
||||
import "github.com/freeflowuniverse/heroagent/pkg/redisserver"
|
||||
import "git.ourworld.tf/herocode/heroagent/pkg/redisserver"
|
||||
|
||||
// Create a new server with default configuration
|
||||
server := redisserver.NewServer(redisserver.ServerConfig{
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/servers/redisserver"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/servers/redisserver"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/servers/redisserver"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/servers/redisserver"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/builders/hetznerinstall"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/builders/hetznerinstall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -6,10 +6,10 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/builders/postgresql/dependencies"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/builders/postgresql/gosp"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/builders/postgresql/postgres"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/builders/postgresql/verification"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/builders/postgresql/dependencies"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/builders/postgresql/gosp"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/builders/postgresql/postgres"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/builders/postgresql/verification"
|
||||
)
|
||||
|
||||
// Constants for PostgreSQL installation
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/builders/postgresql"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/builders/postgresql"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/builders/postgresql/postgres"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/builders/postgresql/postgres"
|
||||
)
|
||||
|
||||
// Constants for Go stored procedure
|
||||
@ -46,10 +46,10 @@ func (b *GoSPBuilder) run(cmd string, args ...string) error {
|
||||
fmt.Println("Running:", cmd, args)
|
||||
c := exec.Command(cmd, args...)
|
||||
// Set environment variables
|
||||
c.Env = append(os.Environ(),
|
||||
c.Env = append(os.Environ(),
|
||||
"GOROOT=/usr/local/go",
|
||||
"GOPATH=/root/go",
|
||||
"PATH=/usr/local/go/bin:" + os.Getenv("PATH"))
|
||||
"GOPATH=/root/go",
|
||||
"PATH=/usr/local/go/bin:"+os.Getenv("PATH"))
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
return c.Run()
|
||||
@ -58,7 +58,7 @@ func (b *GoSPBuilder) run(cmd string, args ...string) error {
|
||||
// Build builds a Go stored procedure
|
||||
func (b *GoSPBuilder) Build() error {
|
||||
fmt.Println("Building Go stored procedure...")
|
||||
|
||||
|
||||
// Use the explicitly provided Go path if available
|
||||
var goExePath string
|
||||
if b.GoPath != "" {
|
||||
@ -74,7 +74,7 @@ func (b *GoSPBuilder) Build() error {
|
||||
}
|
||||
fmt.Printf("Using detected Go executable from: %s\n", goExePath)
|
||||
}
|
||||
|
||||
|
||||
if err := os.MkdirAll(b.GoSharedLibDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
@ -98,27 +98,27 @@ func main() {}
|
||||
|
||||
// Use the full path to Go rather than relying on PATH
|
||||
fmt.Println("Running Go build with full path:", goExePath)
|
||||
|
||||
|
||||
// Show debug information
|
||||
fmt.Println("Environment variables that will be set:")
|
||||
fmt.Println(" GOROOT=/usr/local/go")
|
||||
fmt.Println(" GOPATH=/root/go")
|
||||
fmt.Println(" PATH=/usr/local/go/bin:" + os.Getenv("PATH"))
|
||||
|
||||
|
||||
// Verify that the Go executable exists before using it
|
||||
if _, err := os.Stat(goExePath); err != nil {
|
||||
return fmt.Errorf("Go executable not found at %s: %w", goExePath, err)
|
||||
}
|
||||
|
||||
|
||||
// Create the output directory if it doesn't exist
|
||||
outputDir := filepath.Join(b.InstallPrefix, "lib")
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create output directory %s: %w", outputDir, err)
|
||||
}
|
||||
|
||||
|
||||
// Prepare output path
|
||||
outputPath := filepath.Join(outputDir, "libgosp.so")
|
||||
|
||||
|
||||
// Instead of relying on environment variables, create a wrapper shell script
|
||||
// that sets all required environment variables and then calls the Go executable
|
||||
tempDir, err := os.MkdirTemp("", "go-build-")
|
||||
@ -126,7 +126,7 @@ func main() {}
|
||||
return fmt.Errorf("failed to create temp directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir) // Clean up when done
|
||||
|
||||
|
||||
goRoot := filepath.Dir(filepath.Dir(goExePath)) // /usr/local/go
|
||||
wrapperScript := filepath.Join(tempDir, "go-wrapper.sh")
|
||||
wrapperContent := fmt.Sprintf(`#!/bin/sh
|
||||
@ -143,25 +143,25 @@ echo "PATH=$PATH"
|
||||
echo "=== Running Go command ==="
|
||||
echo "%s $@"
|
||||
exec %s "$@"
|
||||
`,
|
||||
goRoot,
|
||||
`,
|
||||
goRoot,
|
||||
filepath.Dir(goExePath),
|
||||
goExePath,
|
||||
goExePath)
|
||||
|
||||
|
||||
// Write the wrapper script
|
||||
if err := os.WriteFile(wrapperScript, []byte(wrapperContent), 0755); err != nil {
|
||||
return fmt.Errorf("failed to write wrapper script: %w", err)
|
||||
}
|
||||
|
||||
|
||||
fmt.Printf("Created wrapper script at %s\n", wrapperScript)
|
||||
|
||||
|
||||
// Use the wrapper script to build the Go shared library
|
||||
cmd := exec.Command(wrapperScript, "build", "-buildmode=c-shared", "-o", outputPath, libPath)
|
||||
cmd.Dir = filepath.Dir(libPath) // Set working directory to where the source file is
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
|
||||
fmt.Printf("Executing Go build via wrapper script\n")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to build Go stored procedure: %w", err)
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/mholt/archiver/v3"
|
||||
"github.com/mholt/archiver/v4"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -51,14 +51,14 @@ func (g *GoInstaller) GetGoVersion() (string, error) {
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get Go version: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Parse go version output (format: "go version go1.x.x ...")
|
||||
version := strings.TrimSpace(string(output))
|
||||
parts := strings.Split(version, " ")
|
||||
if len(parts) < 3 {
|
||||
return "", fmt.Errorf("unexpected go version output format: %s", version)
|
||||
}
|
||||
|
||||
|
||||
// Return just the version number without the "go" prefix
|
||||
return strings.TrimPrefix(parts[2], "go"), nil
|
||||
}
|
||||
@ -77,7 +77,7 @@ func (g *GoInstaller) InstallGo() (string, error) {
|
||||
// Default Go installation location
|
||||
var installDir string = "/usr/local"
|
||||
var goExePath string = filepath.Join(installDir, "go", "bin", "go")
|
||||
|
||||
|
||||
// Check if Go is already installed by checking the binary directly
|
||||
if _, err := os.Stat(goExePath); err == nil {
|
||||
version, err := g.GetGoVersion()
|
||||
@ -86,7 +86,7 @@ func (g *GoInstaller) InstallGo() (string, error) {
|
||||
return goExePath, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Also check if Go is available in PATH as a fallback
|
||||
if g.IsGoInstalled() {
|
||||
path, err := exec.LookPath("go")
|
||||
@ -98,31 +98,31 @@ func (g *GoInstaller) InstallGo() (string, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fmt.Printf("Installing Go version %s...\n", g.Version)
|
||||
|
||||
|
||||
// Determine architecture and OS
|
||||
goOS := runtime.GOOS
|
||||
goArch := runtime.GOARCH
|
||||
|
||||
|
||||
// Construct download URL
|
||||
downloadURL := fmt.Sprintf("https://golang.org/dl/go%s.%s-%s.tar.gz", g.Version, goOS, goArch)
|
||||
|
||||
|
||||
// Create a temporary directory for download
|
||||
tempDir, err := os.MkdirTemp("", "go-install-")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create temporary directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
|
||||
// Download Go tarball
|
||||
tarballPath := filepath.Join(tempDir, "go.tar.gz")
|
||||
if err := downloadFile(downloadURL, tarballPath); err != nil {
|
||||
return "", fmt.Errorf("failed to download Go: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Install directory - typically /usr/local for Linux/macOS
|
||||
|
||||
|
||||
// Check if existing Go installation exists and remove it
|
||||
existingGoDir := filepath.Join(installDir, "go")
|
||||
if _, err := os.Stat(existingGoDir); err == nil {
|
||||
@ -131,34 +131,34 @@ func (g *GoInstaller) InstallGo() (string, error) {
|
||||
return "", fmt.Errorf("failed to remove existing Go installation: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Extract tarball to install directory
|
||||
fmt.Printf("Extracting Go to %s\n", installDir)
|
||||
err = extractTarGz(tarballPath, installDir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to extract Go tarball: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Verify installation
|
||||
var goExePathVerify = filepath.Join(installDir, "go", "bin", "go") // Use = instead of := to avoid variable shadowing
|
||||
|
||||
|
||||
// Check if the Go binary exists
|
||||
var statErr error
|
||||
_, statErr = os.Stat(goExePathVerify)
|
||||
if statErr != nil {
|
||||
return "", fmt.Errorf("Go installation failed - go executable not found at %s", goExePathVerify)
|
||||
}
|
||||
|
||||
|
||||
// Set up environment variables
|
||||
fmt.Println("Setting up Go environment variables...")
|
||||
|
||||
|
||||
// Update PATH in /etc/profile
|
||||
profilePath := "/etc/profile"
|
||||
profileContent, err := os.ReadFile(profilePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read profile: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Add Go bin to PATH if not already there
|
||||
goBinPath := filepath.Join(installDir, "go", "bin")
|
||||
if !strings.Contains(string(profileContent), goBinPath) {
|
||||
@ -167,7 +167,7 @@ func (g *GoInstaller) InstallGo() (string, error) {
|
||||
return "", fmt.Errorf("failed to update profile: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fmt.Printf("✅ Go %s installed successfully!\n", g.Version)
|
||||
return goExePath, nil
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/stats"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/stats"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -18,11 +18,11 @@ func main() {
|
||||
// Create a new stats manager with Redis connection
|
||||
// Create a custom configuration
|
||||
config := &stats.Config{
|
||||
RedisAddr: "localhost:6379",
|
||||
RedisPassword: "",
|
||||
RedisDB: 0,
|
||||
Debug: false,
|
||||
QueueSize: 100,
|
||||
RedisAddr: "localhost:6379",
|
||||
RedisPassword: "",
|
||||
RedisDB: 0,
|
||||
Debug: false,
|
||||
QueueSize: 100,
|
||||
DefaultTimeout: 5 * time.Second,
|
||||
ExpirationTimes: map[string]time.Duration{
|
||||
"system": 60 * time.Second, // System info expires after 60 seconds
|
||||
@ -32,7 +32,7 @@ func main() {
|
||||
"hardware": 120 * time.Second, // Hardware stats expire after 2 minutes
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
manager, err := stats.NewStatsManager(config)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating stats manager: %v\n", err)
|
||||
@ -61,12 +61,12 @@ func main() {
|
||||
fmt.Printf(" Cores: %d\n", sysInfo.CPU.Cores)
|
||||
fmt.Printf(" Model: %s\n", sysInfo.CPU.ModelName)
|
||||
fmt.Printf(" Usage: %.1f%%\n", sysInfo.CPU.UsagePercent)
|
||||
|
||||
|
||||
fmt.Println("\nMemory Information:")
|
||||
fmt.Printf(" Total: %.1f GB\n", sysInfo.Memory.Total)
|
||||
fmt.Printf(" Used: %.1f GB (%.1f%%)\n", sysInfo.Memory.Used, sysInfo.Memory.UsedPercent)
|
||||
fmt.Printf(" Free: %.1f GB\n", sysInfo.Memory.Free)
|
||||
|
||||
|
||||
fmt.Println("\nNetwork Information:")
|
||||
fmt.Printf(" Upload Speed: %s\n", sysInfo.Network.UploadSpeed)
|
||||
fmt.Printf(" Download Speed: %s\n", sysInfo.Network.DownloadSpeed)
|
||||
@ -81,7 +81,7 @@ func main() {
|
||||
} else {
|
||||
fmt.Printf("Found %d disks:\n", len(diskStats.Disks))
|
||||
for _, disk := range diskStats.Disks {
|
||||
fmt.Printf(" %s: %.1f GB total, %.1f GB free (%.1f%% used)\n",
|
||||
fmt.Printf(" %s: %.1f GB total, %.1f GB free (%.1f%% used)\n",
|
||||
disk.Path, disk.Total, disk.Free, disk.UsedPercent)
|
||||
}
|
||||
}
|
||||
@ -93,12 +93,12 @@ func main() {
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting process stats: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("Total processes: %d (showing top %d)\n",
|
||||
fmt.Printf("Total processes: %d (showing top %d)\n",
|
||||
processStats.Total, len(processStats.Processes))
|
||||
|
||||
|
||||
fmt.Println("\nTop Processes by CPU Usage:")
|
||||
for i, proc := range processStats.Processes {
|
||||
fmt.Printf(" %d. PID %d: %s (CPU: %.1f%%, Memory: %.1f MB)\n",
|
||||
fmt.Printf(" %d. PID %d: %s (CPU: %.1f%%, Memory: %.1f MB)\n",
|
||||
i+1, proc.PID, proc.Name, proc.CPUPercent, proc.MemoryMB)
|
||||
}
|
||||
}
|
||||
@ -107,10 +107,10 @@ func main() {
|
||||
fmt.Println("\n4. CACHING DEMONSTRATION")
|
||||
fmt.Println("----------------------")
|
||||
fmt.Println("Getting network speed multiple times (should use cache):")
|
||||
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
netSpeed := manager.GetNetworkSpeedResult()
|
||||
fmt.Printf(" Request %d: Upload: %s, Download: %s\n",
|
||||
fmt.Printf(" Request %d: Upload: %s, Download: %s\n",
|
||||
i+1, netSpeed.UploadSpeed, netSpeed.DownloadSpeed)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
@ -127,17 +127,17 @@ func main() {
|
||||
fmt.Println("--------------------------")
|
||||
fmt.Println("Enabling debug mode (direct fetching without cache)...")
|
||||
manager.Debug = true
|
||||
|
||||
|
||||
fmt.Println("Getting system info in debug mode:")
|
||||
debugSysInfo, err := manager.GetSystemInfo()
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" CPU Usage: %.1f%%\n", debugSysInfo.CPU.UsagePercent)
|
||||
fmt.Printf(" Memory Used: %.1f GB (%.1f%%)\n",
|
||||
fmt.Printf(" Memory Used: %.1f GB (%.1f%%)\n",
|
||||
debugSysInfo.Memory.Used, debugSysInfo.Memory.UsedPercent)
|
||||
}
|
||||
|
||||
|
||||
// Reset debug mode
|
||||
manager.Debug = false
|
||||
|
||||
@ -148,17 +148,17 @@ func main() {
|
||||
for statsType, duration := range manager.Expiration {
|
||||
fmt.Printf(" %s: %v\n", statsType, duration)
|
||||
}
|
||||
|
||||
|
||||
fmt.Println("\nChanging system stats expiration to 10 seconds...")
|
||||
manager.Expiration["system"] = 10 * time.Second
|
||||
|
||||
|
||||
fmt.Println("Updated expiration times:")
|
||||
for statsType, duration := range manager.Expiration {
|
||||
fmt.Printf(" %s: %v\n", statsType, duration)
|
||||
}
|
||||
|
||||
fmt.Println("\nDemo complete. Press Ctrl+C to exit.")
|
||||
|
||||
|
||||
// Keep the program running
|
||||
select {}
|
||||
}
|
||||
|
@ -11,27 +11,27 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/stats"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/stats"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
)
|
||||
|
||||
// TestResult stores the results of a single test run
|
||||
type TestResult struct {
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
SystemInfoTime time.Duration
|
||||
DiskStatsTime time.Duration
|
||||
ProcessTime time.Duration
|
||||
NetworkTime time.Duration
|
||||
HardwareTime time.Duration
|
||||
TotalTime time.Duration
|
||||
UserCPU float64
|
||||
SystemCPU float64
|
||||
TotalCPU float64
|
||||
OverallCPU float64
|
||||
MemoryUsageMB float32
|
||||
NumGoroutines int
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
SystemInfoTime time.Duration
|
||||
DiskStatsTime time.Duration
|
||||
ProcessTime time.Duration
|
||||
NetworkTime time.Duration
|
||||
HardwareTime time.Duration
|
||||
TotalTime time.Duration
|
||||
UserCPU float64
|
||||
SystemCPU float64
|
||||
TotalCPU float64
|
||||
OverallCPU float64
|
||||
MemoryUsageMB float32
|
||||
NumGoroutines int
|
||||
}
|
||||
|
||||
func main() {
|
||||
@ -66,11 +66,11 @@ func main() {
|
||||
|
||||
// Create a new stats manager with Redis connection
|
||||
config := &stats.Config{
|
||||
RedisAddr: "localhost:6379",
|
||||
RedisPassword: "",
|
||||
RedisDB: 0,
|
||||
Debug: false,
|
||||
QueueSize: 100,
|
||||
RedisAddr: "localhost:6379",
|
||||
RedisPassword: "",
|
||||
RedisDB: 0,
|
||||
Debug: false,
|
||||
QueueSize: 100,
|
||||
DefaultTimeout: 5 * time.Second,
|
||||
ExpirationTimes: map[string]time.Duration{
|
||||
"system": 60 * time.Second, // System info expires after 60 seconds
|
||||
@ -80,7 +80,7 @@ func main() {
|
||||
"hardware": 120 * time.Second, // Hardware stats expire after 2 minutes
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
manager, err := stats.NewStatsManager(config)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating stats manager: %v\n", err)
|
||||
@ -101,11 +101,11 @@ func main() {
|
||||
// Set up signal handling for graceful shutdown
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
|
||||
// Create a ticker for running tests at the specified interval
|
||||
ticker := time.NewTicker(time.Duration(*intervalPtr) * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
|
||||
// Store the sleep duration between operations
|
||||
sleepDuration := time.Duration(*sleepPtr) * time.Millisecond
|
||||
|
||||
@ -118,7 +118,7 @@ func main() {
|
||||
|
||||
// Store test results
|
||||
var results []TestResult
|
||||
|
||||
|
||||
// Print header
|
||||
fmt.Printf("%-20s %-20s %-12s %-12s %-12s %-12s %-12s %-12s %-12s %-12s %-12s %-12s %-12s\n",
|
||||
"Start Time", "End Time", "System(ms)", "Disk(ms)", "Process(ms)", "Network(ms)", "Hardware(ms)", "Total(ms)", "UserCPU(%)", "SysCPU(%)", "TotalCPU(%)", "Memory(MB)", "Goroutines")
|
||||
@ -131,7 +131,7 @@ func main() {
|
||||
// Run a test and record the results
|
||||
result := runTest(manager, currentProcess, sleepDuration)
|
||||
results = append(results, result)
|
||||
|
||||
|
||||
// Print the result
|
||||
fmt.Printf("%-20s %-20s %-12.2f %-12.2f %-12.2f %-12.2f %-12.2f %-12.2f %-12.2f %-12.2f %-12.2f %-12.2f %-12d\n",
|
||||
result.StartTime.Format("15:04:05.000000"),
|
||||
@ -147,16 +147,16 @@ func main() {
|
||||
result.TotalCPU,
|
||||
result.MemoryUsageMB,
|
||||
result.NumGoroutines)
|
||||
|
||||
|
||||
case <-sigChan:
|
||||
// Calculate and print summary statistics
|
||||
fmt.Println("\nTest Summary:")
|
||||
fmt.Println(strings.Repeat("-", 50))
|
||||
|
||||
|
||||
var totalSystemTime, totalDiskTime, totalProcessTime, totalNetworkTime, totalHardwareTime, totalTime time.Duration
|
||||
var totalUserCPU, totalSystemCPU, totalCombinedCPU, totalOverallCPU float64
|
||||
var totalMemory float32
|
||||
|
||||
|
||||
for _, r := range results {
|
||||
totalSystemTime += r.SystemInfoTime
|
||||
totalDiskTime += r.DiskStatsTime
|
||||
@ -170,7 +170,7 @@ func main() {
|
||||
totalOverallCPU += r.OverallCPU
|
||||
totalMemory += r.MemoryUsageMB
|
||||
}
|
||||
|
||||
|
||||
count := float64(len(results))
|
||||
if count > 0 {
|
||||
fmt.Printf("Average System Info Time: %.2f ms\n", float64(totalSystemTime.Microseconds())/(count*1000))
|
||||
@ -185,7 +185,7 @@ func main() {
|
||||
fmt.Printf("Average Overall CPU: %.2f%%\n", totalOverallCPU/count)
|
||||
fmt.Printf("Average Memory Usage: %.2f MB\n", float64(totalMemory)/count)
|
||||
}
|
||||
|
||||
|
||||
fmt.Println("\nTest completed. Exiting...")
|
||||
return
|
||||
}
|
||||
@ -196,90 +196,90 @@ func main() {
|
||||
func runTest(manager *stats.StatsManager, proc *process.Process, sleepBetweenOps time.Duration) TestResult {
|
||||
// Get initial CPU times for the process
|
||||
initialTimes, _ := proc.Times()
|
||||
|
||||
|
||||
// Get initial overall CPU usage
|
||||
_, _ = cpu.Percent(0, false) // Discard initial reading, we'll only use the final reading
|
||||
|
||||
|
||||
result := TestResult{
|
||||
StartTime: time.Now(),
|
||||
}
|
||||
|
||||
|
||||
// Measure total time
|
||||
totalStart := time.Now()
|
||||
|
||||
|
||||
// Measure system info time
|
||||
start := time.Now()
|
||||
_, _ = manager.GetSystemInfo()
|
||||
result.SystemInfoTime = time.Since(start)
|
||||
|
||||
|
||||
// Sleep between operations if configured
|
||||
if sleepBetweenOps > 0 {
|
||||
time.Sleep(sleepBetweenOps)
|
||||
}
|
||||
|
||||
|
||||
// Measure disk stats time
|
||||
start = time.Now()
|
||||
_, _ = manager.GetDiskStats()
|
||||
result.DiskStatsTime = time.Since(start)
|
||||
|
||||
|
||||
// Sleep between operations if configured
|
||||
if sleepBetweenOps > 0 {
|
||||
time.Sleep(sleepBetweenOps)
|
||||
}
|
||||
|
||||
|
||||
// Measure process stats time
|
||||
start = time.Now()
|
||||
_, _ = manager.GetProcessStats(10)
|
||||
result.ProcessTime = time.Since(start)
|
||||
|
||||
|
||||
// Sleep between operations if configured
|
||||
if sleepBetweenOps > 0 {
|
||||
time.Sleep(sleepBetweenOps)
|
||||
}
|
||||
|
||||
|
||||
// Measure network speed time
|
||||
start = time.Now()
|
||||
_ = manager.GetNetworkSpeedResult()
|
||||
result.NetworkTime = time.Since(start)
|
||||
|
||||
|
||||
// Sleep between operations if configured
|
||||
if sleepBetweenOps > 0 {
|
||||
time.Sleep(sleepBetweenOps)
|
||||
}
|
||||
|
||||
|
||||
// Measure hardware stats time
|
||||
start = time.Now()
|
||||
_ = manager.GetHardwareStatsJSON()
|
||||
result.HardwareTime = time.Since(start)
|
||||
|
||||
|
||||
// Record total time
|
||||
result.TotalTime = time.Since(totalStart)
|
||||
result.EndTime = time.Now()
|
||||
|
||||
|
||||
// Get final CPU times for the process
|
||||
finalTimes, _ := proc.Times()
|
||||
|
||||
|
||||
// Calculate CPU usage for this specific operation
|
||||
if initialTimes != nil && finalTimes != nil {
|
||||
result.UserCPU = (finalTimes.User - initialTimes.User) * 100
|
||||
result.SystemCPU = (finalTimes.System - initialTimes.System) * 100
|
||||
result.TotalCPU = result.UserCPU + result.SystemCPU
|
||||
}
|
||||
|
||||
|
||||
// Get overall CPU usage
|
||||
finalOverallCPU, _ := cpu.Percent(0, false)
|
||||
if len(finalOverallCPU) > 0 {
|
||||
result.OverallCPU = finalOverallCPU[0]
|
||||
}
|
||||
|
||||
|
||||
// Measure memory usage
|
||||
memInfo, _ := proc.MemoryInfo()
|
||||
if memInfo != nil {
|
||||
result.MemoryUsageMB = float32(memInfo.RSS) / (1024 * 1024)
|
||||
}
|
||||
|
||||
|
||||
// Record number of goroutines
|
||||
result.NumGoroutines = runtime.NumGoroutine()
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/stats"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/stats"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/system/stats"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/system/stats"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -15,21 +15,21 @@ func main() {
|
||||
|
||||
// Create a new stats manager with Redis connection
|
||||
config := &stats.Config{
|
||||
RedisAddr: "localhost:6379",
|
||||
RedisPassword: "",
|
||||
RedisDB: 0,
|
||||
Debug: false,
|
||||
QueueSize: 100,
|
||||
RedisAddr: "localhost:6379",
|
||||
RedisPassword: "",
|
||||
RedisDB: 0,
|
||||
Debug: false,
|
||||
QueueSize: 100,
|
||||
DefaultTimeout: 5 * time.Second,
|
||||
ExpirationTimes: map[string]time.Duration{
|
||||
"system": 30 * time.Second, // System info expires after 30 seconds
|
||||
"disk": 60 * time.Second, // Disk info expires after 1 minute
|
||||
"process": 15 * time.Second, // Process info expires after 15 seconds
|
||||
"network": 20 * time.Second, // Network info expires after 20 seconds
|
||||
"hardware": 60 * time.Second, // Hardware stats expire after 1 minute
|
||||
"system": 30 * time.Second, // System info expires after 30 seconds
|
||||
"disk": 60 * time.Second, // Disk info expires after 1 minute
|
||||
"process": 15 * time.Second, // Process info expires after 15 seconds
|
||||
"network": 20 * time.Second, // Network info expires after 20 seconds
|
||||
"hardware": 60 * time.Second, // Hardware stats expire after 1 minute
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
manager, err := stats.NewStatsManager(config)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating stats manager: %v\n", err)
|
||||
@ -40,7 +40,7 @@ func main() {
|
||||
// DISK INFORMATION
|
||||
fmt.Println("\n1. DISK INFORMATION")
|
||||
fmt.Println("------------------")
|
||||
|
||||
|
||||
// Get all disk stats using the manager
|
||||
diskStats, err := manager.GetDiskStats()
|
||||
if err != nil {
|
||||
@ -48,7 +48,7 @@ func main() {
|
||||
} else {
|
||||
fmt.Printf("Found %d disks:\n", len(diskStats.Disks))
|
||||
for _, disk := range diskStats.Disks {
|
||||
fmt.Printf(" %s: %.1f GB total, %.1f GB free (%.1f%% used)\n",
|
||||
fmt.Printf(" %s: %.1f GB total, %.1f GB free (%.1f%% used)\n",
|
||||
disk.Path, disk.Total, disk.Free, disk.UsedPercent)
|
||||
}
|
||||
}
|
||||
@ -58,7 +58,7 @@ func main() {
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting root disk info: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("\nRoot Disk: %.1f GB total, %.1f GB free (%.1f%% used)\n",
|
||||
fmt.Printf("\nRoot Disk: %.1f GB total, %.1f GB free (%.1f%% used)\n",
|
||||
rootDisk.Total, rootDisk.Free, rootDisk.UsedPercent)
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ func main() {
|
||||
// SYSTEM INFORMATION
|
||||
fmt.Println("\n2. SYSTEM INFORMATION")
|
||||
fmt.Println("--------------------")
|
||||
|
||||
|
||||
// Get system info using the manager
|
||||
sysInfo, err := manager.GetSystemInfo()
|
||||
if err != nil {
|
||||
@ -78,12 +78,12 @@ func main() {
|
||||
fmt.Printf(" Cores: %d\n", sysInfo.CPU.Cores)
|
||||
fmt.Printf(" Model: %s\n", sysInfo.CPU.ModelName)
|
||||
fmt.Printf(" Usage: %.1f%%\n", sysInfo.CPU.UsagePercent)
|
||||
|
||||
|
||||
fmt.Println("\nMemory Information:")
|
||||
fmt.Printf(" Total: %.1f GB\n", sysInfo.Memory.Total)
|
||||
fmt.Printf(" Used: %.1f GB (%.1f%%)\n", sysInfo.Memory.Used, sysInfo.Memory.UsedPercent)
|
||||
fmt.Printf(" Free: %.1f GB\n", sysInfo.Memory.Free)
|
||||
|
||||
|
||||
fmt.Println("\nNetwork Information:")
|
||||
fmt.Printf(" Upload Speed: %s\n", sysInfo.Network.UploadSpeed)
|
||||
fmt.Printf(" Download Speed: %s\n", sysInfo.Network.DownloadSpeed)
|
||||
@ -98,18 +98,18 @@ func main() {
|
||||
// PROCESS INFORMATION
|
||||
fmt.Println("\n3. PROCESS INFORMATION")
|
||||
fmt.Println("---------------------")
|
||||
|
||||
|
||||
// Get process stats using the manager
|
||||
processStats, err := manager.GetProcessStats(5) // Get top 5 processes
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting process stats: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("Total processes: %d (showing top %d)\n",
|
||||
fmt.Printf("Total processes: %d (showing top %d)\n",
|
||||
processStats.Total, len(processStats.Processes))
|
||||
|
||||
|
||||
fmt.Println("\nTop Processes by CPU Usage:")
|
||||
for i, proc := range processStats.Processes {
|
||||
fmt.Printf(" %d. PID %d: %s (CPU: %.1f%%, Memory: %.1f MB)\n",
|
||||
fmt.Printf(" %d. PID %d: %s (CPU: %.1f%%, Memory: %.1f MB)\n",
|
||||
i+1, proc.PID, proc.Name, proc.CPUPercent, proc.MemoryMB)
|
||||
}
|
||||
}
|
||||
@ -128,7 +128,7 @@ func main() {
|
||||
// COMBINED STATS
|
||||
fmt.Println("\n4. COMBINED STATS FUNCTIONS")
|
||||
fmt.Println("--------------------------")
|
||||
|
||||
|
||||
// Hardware stats using the manager
|
||||
fmt.Println("\nHardware Stats:")
|
||||
hardwareStats := manager.GetHardwareStats()
|
||||
@ -151,17 +151,17 @@ func main() {
|
||||
// Wait and measure network speed again
|
||||
fmt.Println("\nWaiting 2 seconds for another network speed measurement...")
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
|
||||
// Get updated network speed using the manager
|
||||
updatedNetSpeed := manager.GetNetworkSpeedResult()
|
||||
fmt.Println("\nUpdated Network Speed:")
|
||||
fmt.Printf(" Upload: %s\n", updatedNetSpeed.UploadSpeed)
|
||||
fmt.Printf(" Download: %s\n", updatedNetSpeed.DownloadSpeed)
|
||||
|
||||
|
||||
// CACHE MANAGEMENT
|
||||
fmt.Println("\n5. CACHE MANAGEMENT")
|
||||
fmt.Println("------------------")
|
||||
|
||||
|
||||
// Force update of system stats
|
||||
fmt.Println("\nForcing update of system stats...")
|
||||
err = manager.ForceUpdate("system")
|
||||
@ -170,7 +170,7 @@ func main() {
|
||||
} else {
|
||||
fmt.Println("System stats updated successfully")
|
||||
}
|
||||
|
||||
|
||||
// Get updated system info
|
||||
updatedSysInfo, err := manager.GetSystemInfo()
|
||||
if err != nil {
|
||||
@ -178,7 +178,7 @@ func main() {
|
||||
} else {
|
||||
fmt.Println("\nUpdated CPU Usage: " + fmt.Sprintf("%.1f%%", updatedSysInfo.CPU.UsagePercent))
|
||||
}
|
||||
|
||||
|
||||
// Clear cache for disk stats
|
||||
fmt.Println("\nClearing cache for disk stats...")
|
||||
err = manager.ClearCache("disk")
|
||||
@ -187,7 +187,7 @@ func main() {
|
||||
} else {
|
||||
fmt.Println("Disk stats cache cleared successfully")
|
||||
}
|
||||
|
||||
|
||||
// Toggle debug mode
|
||||
fmt.Println("\nToggling debug mode (direct fetching without cache)...")
|
||||
manager.Debug = !manager.Debug
|
||||
|
Loading…
Reference in New Issue
Block a user